Creating a Key Encrypting Key (KEK)

Previously I have taken the steps to add secrets and keys to the Key Vault as well as create my self signed certificates using PowerShell. With the opportunities to use drive encryption in Azure on IaaS machines it makes sense to go through the steps of using a Key Encryption Key (KEK) to increase security but also to allow the encrypted machines to be backed up and restored.

After doing some preparation for building encrypted IaaS virtual machines I found there was not a lot of “clear” guidance on building and using the KEK so I thought I would describe the steps I took here.

For the purposes of my development efforts I used a self signed certificate although in production I would prefer to use a certificate issued from a trusted authority. I used the self signed certificate creation script outlined in my previous post with the legacy provider (Microsoft Enhanced Cryptographic Provider v1.0) for 2048 bit RSA.

  .\CreateSelfSignedCertificate.ps1 -subjectName 'My KEK Certificate' -fileName 'c:\certs\mykek.pfx' -useProviderV1 -password (ConvertTo-SecureString 'P@ssw0rd' -AsPlainText -Force)

For simplicity, I upload the certificate using the Azure Portal.

uploadkey

After the key has been uploaded I use the portal to get the version number for use later. For creating and uploading the wrapped secret I will use the REST technique discussed in my previous post.

 function Get-OAuth2Uri
(
  [string]$vaultName
)
{
  $response = try { Invoke-RestMethod -Method GET -Uri "https://$vaultName.vault.azure.net/keys" -Headers @{} } catch { $_.Exception.Response }
  $authHeader = $response.Headers['www-authenticate']
  $endpoint = [regex]::match($authHeader, 'authorization="(.*?)"').Groups[1].Value

  return "$endpoint/oauth2/token"
}

function Get-AccessToken
(
  [string]$vaultName,
  [string]$aadClientId,
  [string]$aadClientSecret
)
{
  $oath2Uri = Get-OAuth2Uri -vaultName $vaultName

  $body = 'grant_type=client_credentials'
  $body += '&client_id=' + $aadClientId
  $body += '&client_secret=' + [Uri]::EscapeDataString($aadClientSecret)
  $body += '&resource=' + [Uri]::EscapeDataString("https://vault.azure.net")

  $response = Invoke-RestMethod -Method POST -Uri $oath2Uri -Headers @{} -Body $body

  return $response.access_token
}

These methods are used to leverage the OAUTH2 endpoint with the applications client id and client secret. Rather than trying to build the URL for the KEK I retrieve the key metadata using the following function.

 function Get-Key
(
  [string]$accessToken,
  [string]$vaultName,
  [string]$keyName,
  [string]$keyVersion
)
{
  $headers = @{ 'Authorization' = "Bearer $accessToken" }
  $queryUrl = "https://$vaultName.vault.azure.net/keys/$keyName/$keyVersion" + '?api-version=2016-10-01'
  $keyResponse = Invoke-RestMethod -Method GET -Uri $queryUrl -Headers $headers

  return $keyResponse.key
}

The drive will be encrypted using an AES 256 key created randomly using the following function.

 function Create-AesKey
(
  [int]$keySize
)
{
  $aesProvider = New-Object System.Security.Cryptography.AesCryptoServiceProvider
  $aesProvider.KeySize = $keySize
  $aesProvider.GenerateKey()

  return $aesProvider.Key
}

The final step will be wrapping the AES 256 key with the KEK in the Key Vault. The KEK is never extracted, instead for security reasons I let the Key Vault do the work for me. To wrap the key the following method is used.

 function Wrap-SymmetricKey
(
  [string]$accessToken,
  [string]$vaultName,
  [string]$keyName,
  [string]$keyVersion,
  [byte[]]$plainArray
)
{
  $base64Array = [Convert]::ToBase64String($plainArray)

  $queryUrl = "https://$vaultName.vault.azure.net/keys/$keyName/$keyVersion" + '/wrapkey?api-version=2016-10-01'   
  $headers = @{ 'Authorization' = "Bearer $accessToken"; "Content-Type" = "application/json" }

  $bodyObject = @{ "alg" = "RSA-OAEP"; "value" = $base64Array }
  $bodyJson = ConvertTo-Json -InputObject $bodyObject

  $response = Invoke-RestMethod -Method POST -Ur $queryUrl -Headers $headers -Body $bodyJson

  return $response.value
}

It is as simple as piecing these methods together as follows.

 $accessToken = Get-AccessToken -vaultName $vaultName -aadClientId $aadClientID -aadClientSecret $aadClientSecret

$aesKey = Create-AesKey -keySize 256
$wrappedAesKey = Wrap-SymmetricKey -accessToken $accessToken -vaultName $vaultName -keyName $kekName -keyVersion $kekVersion -plainArray $aesKey

$keyEncryptingKey = Get-Key -accessToken $accessToken -vaultName $vaultName -keyName $kekName -keyVersion $kekVersion
$kekUri = $keyEncryptingKey.kid

$queryUrl = "https://$vaultName.vault.azure.net/secrets/$secretName" + '?api-version=2016-10-01'  
$secretAttributes = @{ 'enabled' = $true }
$secretTags = @{ 'DiskEncryptionKeyEncryptionAlgorithm' = 'RSA-OAEP'; 'DiskEncryptionKeyFileName' = "$secretName.BEK"; 'DiskEncryptionKeyEncryptionKeyURL' = $kekUri }
$headers = @{ 'Authorization' = "Bearer $accessToken"; "Content-Type" = "application/json" }

$bodyObject = @{ 'value' = $wrappedAesKey; 'attributes' = $secretAttributes; 'tags' = $secretTags; 'contentType' = 'Wrapped BEK' }
$body = ConvertTo-Json -InputObject $bodyObject

$secretResponse = Invoke-RestMethod -Method PUT -Uri $queryUrl -Headers $headers -Body $body

Write-Host 'secret URL is ' + $secretResponse.id

The above code generates the AES key, wraps it using the KEK and inserts the secret with the required tags.

After running this script I was able to successfully encrypt my Azure Virtual Machines.