how to upload a machine cert to Azure

When you're creating a VM with the Azure Resource Manager ("new Azure portal") and want to connect to it through WinRM/PowerShell with the HTTPS protocol, you have to provide a machine certificate for it. In the old Azure it was easier, it would generate a cert on its own and would let you download its public part, such as described in https://gallery.technet.microsoft.com/scriptcenter/Configures-Secure-Remote-b137f2fe . This has been a major issue for the NanoServer, so we've provided a helper script that does all this, you can find it in the blog in https://blogs.technet.microsoft.com/nanoserver/2016/10/12/nano-server-in-the-azure-gallery-and-vm-agent-support/ . I want to talk about how this script handles the transfer of the certificate to Azure.

First it generates the cert, either with makecert.exe or with a PowerShell one-liner:

 $cert = New-SelfSignedCertificate -CertStoreLocation Cert:\CurrentUser\My\ -DnsName $Computer

The PowerShell-generated certs have issues, so the makecert-generated are better. Among the issues of the PowerShell-generated certs is that they expire in a year and that they don't have all the right permission flags on them if you want to use the cert not only for authentication but also for en/decryption. I'm actually not sure if a PS-generated cert can be used as a machine cert any more. They used to work but then the machine cert code became more picky. Well, if you want to know what exact options to use with makecert.exe, look in the script.

After the script is generated, it gets uploaded to an Azure Key Vault as a "secret":

     $cbytes = $cert.Export("Pfx", $pwd)
    $ecbytes = [System.Convert]::ToBase64String($cbytes)
    $jsonObject = @"
{
"data": "$ecbytes",
"dataType" :"pfx",
"password": "$pwdplain"
}
"@
    $jsonBytes = [System.Text.Encoding]::UTF8.GetBytes($jsonObject)
    $secret = ConvertTo-SecureString -String ([System.Convert]::ToBase64String($jsonBytes)) -AsPlainText –Force
    # Returns the secret object.
    $secobj = Set-AzureKeyVaultSecret -VaultName $VaultName -Name $SecretName -SecretValue $secret

As you can see, the process is quite amazing and includes two conversions to a Base64 string! Then you use the URL from $secobj.id as a parameter to the Azure JSON template (again, you can look at the JSON template included with the script).

The second part is that if you plan to connect to that VM by HTTPS, you have to install the cert locally on your client machine. This is done by copying this cert to the store of the root certs:

 Copy-Cert -Certificate $cert -Destination "Cert:\LocalMachine\Root\"

Unfortunately PowerShell can't just copy the certs around, so a special function had to be written to do it by calling the .NET methods directly:

 function Copy-Cert
{
<#
.SYNOPSIS
Copy a certificate from one path to another.
#>
    [CmdletBinding()]
    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()
}