Bring your own key with a Thales Ciphertrust
Compliance comes in many different flavors. One of those items is encryption at rest. If you’re working for a company or organization (like a government) that really cares about the legal aspects of things, then they will want you to use keys generated in a 3rd party store. Using 3rd party keys in azure for encrypting services is supported and is called BYOK or CMK. Bring Your Own Key or Customer Managed Keys.
FIPS 140-2?
Why does legal want to have keys created in a 3rd party system? Legal’s belief is that if Microsoft generates the key themselves, that MSFT will always have a way to get the key. In the same breath legal will also ask that you store your keys in a FIPS 140-2 level 2 compliant store (or better). MSFT offers the FIPS 140-2 level in the form of a premium key vault, and level 3 in the form of the managed HSM. The price difference is insane, where the Azure Key Vault Managed HSM is around 2400 euro / month. So, we should try our best to stick to a premium, where 1 single key is 1 euro / month.
Now what is this FIPS 140-2 I speak of? It’s a US standards security requirement for cryptographic modules. If you want to read what it’s about, you can find detailed information here: https://csrc.nist.gov/publications/detail/fips/140/2/final . It’s split up in 4 levels of increasing security measures to be taken.
Unless you’re in super-strict environments, A FIPS-140-2 level 2 requirement will usually be the case. As stated above, the azure key vault premium reaches that level of compliance.
If you’re in a company that requires this CMK scenario, but your HSM is a little too old to natively support azure Key Vault. You will need to perform several steps. In this post I’ll walk you through.
Thales Ciphertrust
Thales’ Ciphertrust solution is an HSM solution that can generate keys, certs, etc. The Ciphertrust has support for Key Vault natively built-in (and it does this admirably well). But it also has a community edition that allows me to take you through the manual CMK scenario. You can find more info on the community edition here: https://cpl.thalesgroup.com/encryption/ciphertrust-platform-community-edition . It’s basically key vault on steroids as it can do both key management and also connect to many different cloud-based key solutions. It’s also FIPS 140-2 compliant. Very cool stuff.
I’d like to give a big shoutout to Ignacio Berrozpe Peralta for his assistance on the Thales side! Thank you!
What do we want to do?
We want to enable SSE (Service Side Encryption) using keys generated in the Thales HSM. To do that, we need to perform several steps:
- Generate a KEK request,
- Download the public key of the KEK request,
- Upload the public key of the KEK request,
- Create a new RSA key, to be used as the KEK in the key vault later on,
- Wrap the KEK using the KEK request key uploaded before,
- Download the wrapped key and make a BYOK file, required for importing to the key vault,
- Import the BYOK file
TL;DR; Setting up the flow from key vault to Thales and back again
Finally, the code.
Kids, don’t use this code in production. It’s not production-worthy, it’s only here to guide you through the steps. You were warned.
Step 1: Generating the KEK in Azure Key vault
$vaultName = "YourPremiumKeyVaultName"
$AzureKvKeyName = "name-of-the-key-you-will-create-in-the-azure-key-vault"
$ckmKeyName = "name-of-the-key-you-will-create-in-the-Ciphertrust"
$checkKekExistance = @{
VaultName = $vaultName;
Name = $AzureKvKeyName;
}
$kek = Get-AzKeyVaultKey @checkKekExistance
if ($null -eq $kek) {
$createKekParams = @{
VaultName = $vaultName;
Name = $AzureKvKeyName;
Destination = "HSM";
Size = 2048;
KeyOps = "import";
}
Add-AzKeyVaultKey @createKekParams
}
This are fairly straightforward powershell key vault commands. Checking if the key exists, if not: create it.
Step 2: Downloading the public part of the KEK request
# Download PEM file
$getKekPublic = @{
VaultName = $vaultName;
Name = $AzureKvKeyName;
OutFile = "$AzureKvKeyName.pem"
}
Get-AzKeyVaultKey @getKekPublic
Using the appropriate get-* parameters to download the public key.
Step 3: Upload the KEK to Ciphertrust
Write-Information "Uploading public KEK key to Ciphertrust"
# Log in to ciphertrust
$token = Login-Ciphertrust -username "admin" -password ("Your-CipherTrust-Password" | ConvertTo-SecureString -AsPlainText -Force)
$kekKeyInfo = Get-CiphertrustKeyId -ckmKeyName $AzureKvKeyName -accessToken $token.access_token
if ($null -eq $kekKeyInfo) {
$kekPubKey = Get-Content "$AzureKvKeyName.pem" -Raw
$kekKeyInfo = Import-CiphertrustPubKek -ckmKeyName $AzureKvKeyName -pubKEK $kekPubKey -accessToken $token.access_token
}
See this github link for the entire source code. This code checks if the key exists and if not, uploads it to the ciphertrust.
Step 4: Create a new RSA key in ciphertrust
$keyInfo = Get-CiphertrustKeyId -ckmKeyName $ckmKeyName -accessToken $token.access_token
if ($null -eq $keyInfo) {
$keyInfo = New-CiphertrustKey -ckmKeyName $ckmKeyName -keyStrength 2048 -accessToken $token.access_token
}
This piece of code checks if we already have a key with the given name in the Ciphertrust, and if not, it will create an RSA key with 2048 bit strength.
Step 5: Wrap the new key with the KEK key
Write-Information "Exporting key according to requirements"
$wrapped = Get-CiphertrustWrappedPrivateKey -ckmKeyNameToWrap $ckmKeyName -wrapKeyId $kekKeyInfo.id -accessToken $token.access_token
Wrapping the key.
The exact request looks like:
function Get-CiphertrustWrappedPrivateKey {
param (
[Parameter(Mandatory=$true)]
[string]$ckmKeyNameToWrap,
[Parameter(Mandatory=$true)]
[string]$wrapKeyId,
[Parameter(Mandatory=$true)]
[string]$accessToken
)
$keyInfo = Get-CiphertrustKeyId -ckmKeyName $ckmKeyNameToWrap -accessToken $token.access_token
$uri = "$(Get-CiphertrustApiUrl)vault/keys2/$($keyInfo.id)/export"
# Create the request for exporting the wrapped private key in compliance with AzKeyault's needs
$body = @{
format = "pkcs8"
wrappingMethod = "encrypt"
wrapKeyIDType= "id"
wrapKeyName = $wrapKeyId
wrappingEncryptionAlgo = "rsa/rsaaeskeywrappadding"
wrapRSAAES = @{
aesKeySize = 256
padding = "oaep"
};
pemWrap = $false
}
$params = @{
Headers = @{ Authorization = "Bearer $($accessToken)" }
ContentType = "application/json"
body = $body | ConvertTo-Json
Method = "POST"
Uri = $uri
UseBasicParsing = $true
SkipCertificateCheck = $true
}
$response = Invoke-WebRequest @params
return $response.Content | ConvertFrom-Json
}
Step 6: Making the BYOK file
If you want more info on this, check out: https://learn.microsoft.com/en-us/azure/key-vault/keys/byok-specification . The comment here: https://learn.microsoft.com/en-us/azure/key-vault/keys/byok-specification#key-transfer-blob is particularly misleading:
You don’t need to wrap it in a full JWE token. And that’s good, because creating that can get quite involved.
You need to get the material from the wrapped key you exported and you should be good to go, like this:
$keyInfo = Get-AzKeyVaultKey -VaultName $vaultName -Name $AzureKvKeyName
$jwt_material = @{
"schema_version" = "1.0.0";
"header"= @{
"kid" = $keyInfo.Id;
"alg" = "dir";
"enc" = "CKM_RSA_AES_KEY_WRAP";
}
"ciphertext" = $wrapped.material;
"generator" = "Digitaal Vlaanderen CCKM interface"
}
Write-Information "removing old material byok file"
$filename = "my_kek_response_material.byok"
Remove-Item $filename -ErrorAction SilentlyContinue
Write-Information "Writing new byok file"
$jwt_material | ConvertTo-Json -Depth 10 | Out-File $filename
Step 7: finally uploading the wrapped key
$byokParams = @{
VaultName = $vaultName;
Name = $AzureKvKeyName;
KeyFilePath = $filename;
Destination = 'HSM';
}
Add-AzKeyVaultKey @byokParams
And that should be it. Now your custom key is in the key vault for you to use in a Disk Encryption Set.
Hope This Helps!