Certificates, part4: PowerShell/WinRM remoting over HTTPS, and cert copying

To do the PowerShell remoting over HTTPS, the WinRM on the remote machine needs a certificate. If you join the machine to a domain, this certificate might be generated automatically, I haven't tried. But for the non-domain-joined machines a self-signed certificate can be used instead.

I'm going to create the cert on my work machine and then copy it to the test machine. It could be created on the test machine as well, and then the public part of it copied to the test machine.

The name in the subject has to match the name by which the machine will be referred. If you plan to connect by an IP address, as might happen with a test machine, use the IP address for the name:

PS C:\WINDOWS\system32> $cert = New-SelfSignedCertificate -CertStoreLocation Cert:\CurrentUser\My\ -DnsName 169.254.4.62

PS C:\WINDOWS\system32> $cert

    Directory: Microsoft.PowerShell.Security\Certificate::CurrentUser\My

Thumbprint                                Subject
----------                                -------
72CEEF1E8AE60E12A170AEDA3ED067C866ADECEE  CN=169.254.4.62

PS C:\WINDOWS\system32> $cert | fl

Subject      : CN=169.254.4.62
Issuer       : CN=169.254.4.62
Thumbprint   : 72CEEF1E8AE60E12A170AEDA3ED067C866ADECEE
FriendlyName :
NotBefore    : 11/6/2015 5:51:55 PM
NotAfter     : 11/6/2016 6:11:55 PM
Extensions   : {System.Security.Cryptography.Oid, System.Security.Cryptography.Oid, System.Security.Cryptography.Oid,
               System.Security.Cryptography.Oid}

An unpleasant thing about the certs generated with New-SelfSignedCertificate is that they are set to expire in a year. Which is OK for a short-lived test machine, but otherwise use makecert.exe instead.

I've created this cert on my real work machine, so it needs to be copied to the target machine. For that let's export it:

PS C:\WINDOWS\system32> $cert.Export("pfx", "somepwd") | Set-Content -Encoding Byte "c:\shared\target.pfx"

The cmdlet Export-PfxCertificate could be used instead but it's more pain. You can use a SecureString for the second argument of Export, the password of the PFX file. It's also possible to export without any password but it's not considered such a good practice, and some commands have issues with such files. Well, another option is to protect the password file with an ACL, but again some commands insist on specifying a password when reading the PFX file.

Then we copy the file onto the target machine and import it there:

PS C:\tmp> $cert = Import-PfxCertificate -FilePath C:\tmp\target.pfx -CertStoreLocation Cert:\LocalMachine\My -Exportable -Password (ConvertTo-SecureString -AsPlainText "somepwd" -Force)

Configure the WSMAN on the remote machine:

PS C:\tmp> $reg2 = "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\WSMAN\Listener\*+HTTPS"
PS C:\tmp> reg add "$reg2" /v Port /t REG_DWORD /d 5986 /f
The operation completed successfully.
PS C:\tmp> reg add "$reg2" /v uriprefix /t REG_SZ /d wsman /f
The operation completed successfully.
PS C:\tmp> reg add "$reg2" /v certThumbprint /t REG_SZ /d $cert.Thumbprint /f
The operation completed successfully.
PS C:\tmp> dir WSMan:\localhost\Listener\

   WSManConfig: Microsoft.WSMan.Management\WSMan::localhost\Listener

Type            Keys                                Name
----            ----                                ----
Container       {Transport=HTTPS, Address=*}        Listener_1305953032
Container       {Transport=HTTP, Address=*}         Listener_1084132640

Note that I had to use the plain reg.exe because PowerShell seems to be unhappy with the path that contains a star character in it. Then complete the network configuration:

PS C:\tmp> $null = New-NetFirewallRule -Name "Windows Remote Management (HTTPS-In) (Azure)" -DisplayName "Windows Remote Management (HTTPS-In)" -Protocol TCP -LocalPort 5986 -Direction Inbound -Profile Any -Action Allow -Group "@firewallapi.dll,-2325"
PS C:\tmp> netsh http add sslcert ipport=0.0.0.0:5986 "appid={AFEBB9AD-9B97-4a91-9AB5-DAF4D59122F6}" verifyclientcertrev
ocation=disable verifyrevocationwithcachedclientcertonly=disable usagecheck=disable "certhash=$($cert.Thumbprint)"

SSL Certificate successfully added

PS C:\tmp> Restart-Service winrm

The arguments that contain the curly braces must be used in quotes, to escape them from PowerShell.

Now return back to the work machine. The cert is there but it's not configured yet to be treated as an independent authority. To do that it has to be copied to the directory cert:\LocalMachine\Root. It could be either imported there, or I wrote a small function to copy the certs around in the store:

function Copy-Cert
{
<#
.SYNOPSIS
Copy a certificate from one path to another.
#>
    param(
        ## Path of the original cert (including the thumbprint).
        [Parameter(ParameterSetName = "Path", Mandatory=$true)]
        [string] $Path,
        ## The certificate object to copy.
        [Parameter(ParameterSetName = "Certificate", Mandatory=$true)]
        [System.Security.Cryptography.X509Certificates.X509Certificate2] $Certificate,
        ## The destination path (excluding the thumbprint).
        [Parameter(Mandatory=$true)]
        [string] $Destination
    )

    if (!$Certificate) {
        $Certificate = Get-Item $Path
    }
    if (!$Certificate) {
        throw "The certificate at path '$Path' is not present"
    }

 $store = Get-Item $Destination
 $store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite)
 $store.Add($Certificate)
 $store.Close()
}

Copy the cert with it:

PS C:\WINDOWS\system32> Copy-Cert -Certificate $cert -Destination "Cert:\LocalMachine\Root\"

Now we're ready to try out the connection:

PS C:\WINDOWS\system32> enter-pssession -ConnectionUri https://169.254.4.62:5986/WSMAN -Credential "administrator"
[169.254.4.62]: PS C:\Users\Administrator\Documents>

Success!

Technically, there was no good reason to keep the private key in the installed root certificate. So instead it can be exported as a proper public-only certificate and then imported back:

PS C:\WINDOWS\system32> Export-Certificate -Cert $cert -FilePath c:\shared\target.cer

    Directory: C:\shared

Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----        11/6/2015   7:08 PM            805 target.cer

PS C:\WINDOWS\system32> Import-Certificate -CertStoreLocation Cert:\LocalMachine\Root\ -FilePath C:\shared\target.cer

    Directory: Microsoft.PowerShell.Security\Certificate::LocalMachine\Root

Thumbprint                                Subject
----------                                -------
72CEEF1E8AE60E12A170AEDA3ED067C866ADECEE  CN=169.254.4.62

The PSSession works with this certificate just as good. By the way, I was wrong about the .cer file being in the PKCS#7 format. According to the help for Export-Certificate, there are multiple formats available: .sst is a Microsoft format, .cer is another Microsoft format, and finally .p7b is the PKCS#7 format.

<<Part3 Part5>>