info@nimbus.expert

Securing workloads in azure: CMK for storage encryption

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 each of those steps.

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.

Note that I didn't say the azure key vault is no good, or that you should always a 3rd party HSM. You could do that if you wanted to, but it doesn't make sense most scenarios.

A big shoutout to: Ignacio Berrozpe Peralta. He's been instrumental to figuring things out on the Thales side. Thank you for your support and for providing answers where needed!

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:

  1. Generate a KEK request,
  2. Download the public key of the KEK request,
  3. Upload the public key of the KEK request,
  4. Create a new RSA key, to be used as the KEK in the key vault later on,
  5. Wrap the KEK using the KEK request key uploaded before,
  6. Download the wrapped key and make a BYOK file, required for importing to the key vault,
  7. Import the BYOK file

 

Setting up the flow from key vault to Thales and back again

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; # this is the minimum at the time of writing
        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
$loginParams = @{
    username = "admin"
    password = ("your password for the cipher trust manager" | ConvertTo-SecureString -AsPlainText -Force)
}
$token = Login-Ciphertrust @loginParams


$getKeyAzKVParams = @{
    ckmKeyName = $AzureKvKeyName
    accessToken = $token.access_token
}
$kekKeyInfo = Get-CiphertrustKeyId @getKeyAzKVParams
if ($null -eq $kekKeyInfo) {
    $kekPubKey = Get-Content "$AzureKvKeyName.pem" -Raw # Raw makes sure we don't get any newlines

    $importKeyParams = @{
        ckmKeyName = $AzureKvKeyName
        pubKEK = $kekPubKey
        accessToken = $token.access_token
    }
    $kekKeyInfo = Import-CiphertrustPubKek @importKeyParams
}

See here 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

$getKeyParams = @{
    ckmKeyName = $ckmKeyName
    accessToken = $token.access_token
}
$keyInfo = Get-CiphertrustKeyId @$getKeyParams
if ($null -eq $keyInfo) {
    $newKeyParams = @{
        ckmKeyName = $ckmKeyName
        keyStrength = 2048
        accessToken = $token.access_token
    }
    $keyInfo = New-CiphertrustKey @newKeyParams
}

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"
$wrapKeyParams = @{
    ckmKeyNameToWrap = $ckmKeyName
    wrapKeyId = $kekKeyInfo.id
    accessToken = $token.access_token
}
$wrapped = Get-CiphertrustWrappedPrivateKey @wrapKeyParams

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 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" = "Your tools name"
}

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.

 

Links:

- here for the entire source code

- Thales Ciphertrust community edition

Hope This Helps!