Azure Confidential Computing: Secure Key Release

As I was researching confidential computing this year, I heard about “Secure Key Release” being mentioned in a couple of Azure Confidential Compute-related videos, such as this one from Ignite 2022.

💡 At the time of writing Microsoft offers a couple of mechanisms for customers to utilize confidential computing, all are based on the notion of running your applications in hardware-based trusted execution environments (TEE). Picking a particular approach boils down to how much of a trusted computing base (TCB), you are willing to take on. The more code we end up running inside of the TEE, the larger the TCB becomes and potentially your attack surface. If one component inside the TCB is compromised, the entire system’s security may be jeopardized. I have written about this subject at length a few months ago in a different blog post, feel free to take a look.

I had been wondering how one would go about using this feature, so in this blog post I set out to do just that. I will be taking a look at how to release an HSM key from Azure Key Vault to a Trusted Execution Environment, in our case an Azure Confidential Virtual Machine, powered by AMD SEV-SNP.

Azure Key Vault’s secure key release mechanism should let us get more control over which applications get access to a specific key.

🐉 Here be dragons! As I researched this particular topic, I started to realize that the information on SKR is rather scarce. It seems to me as if the feature is live though I believe a lot of the documentation is sparse, which is unfortunate. I’ve tried my best to distil some of the available information.

Key Vault refresher

I like Azure Key Vault, primarily because it is an incredibly straightforward service to use and brings a lot of value, incredibly quickly. I typically use Key Vault whenever I’m in a scenario where I have to store and access a specific sensitive configuration value, better known as a secret, in a secure manner. On top of this, objects stored in Azure Key Vault are versioned. So whenever you create a new instance of an object, a new version is created.

But Azure Key Vault can do quite a lot more than just store secrets. In fact, it also has built-in support for handling X.509 certificates and cryptographic key operations.

Auditing capabilities are also par for the course, so you can see which security principal (user, group, service principal, or managed identity) has accessed a specific object and when. Combine that with Key Vault’s access control system and managed identity for Azure resources, and it becomes incredibly easy to gain access to Key Vault relatively quickly, without the need for storing credentials.

Azure Key Vault has two distinct container types:

  • Vaults: a multi-tenant service comes in two different tiers:
    • Standard SKU: supporting software-protected keys only.
    • Premium SKU: supporting both software-protected keys, as well as keys that are protected by a hardware security module.
  • Managed HSM:
    • Standard B1: supporting only HSM-backed keys.

For those that need to be extra mindful of certain compliance levels, Azure Key Vault Managed HSM gives you a single-tenant service that enables you to safeguard cryptographic keys using FIPS (Federal Information Protection Standard) 140-2 Level 3 validated HSMs. Just like Key Vault, it is a highly available service that is managed by Microsoft for you. Manage HSM’s API surface is the same as Key Vault’s, so you can keep using your Key Vault skills. Managed HSM itself utilizes confidential computing capabilities, since the service runs inside of an Intel SGX enclave.

💡 You can also get a Dedicated HSM service, which is a physical device just for you. The service is also not visible in the Azure Portal by default, moreover “the use of physical devices creates the need for Microsoft to control device allocation to ensure capacity is managed effectively. Any Azure customer requiring access to the Dedicated HSM service, must first contact their Microsoft account executive to request registration”. This solution is not part of Azure Key Vault, however.

Secure Key Release

We can very easily create a new object (secret, certificate or key) in Azure Key Vault and make it so a security principal can get keys from Azure Key Vault.

  • Enable a system-assigned managed identity on an Azure source, in our case a confidential virtual machine.
    • You could also use a user-assigned managed identity and add multiple resources to the identity.
  • Set an access policy for this identity.
    • For instance: allow it to perform the get key operation.
  • By invoking code inside of the Azure resource we can get an access token for Azure Key Vault.
  • Invoke the REST method to get a key from Key Vault via an HTTP request.
    • Stick the access token into the HTTP request’s Authorization header as a Bearer token.
    • If you cannot perform the “list” key operation, then you will need to know the key name and optionally its version.

💡 You can invoke all of these steps through certain functions in the Azure SDKs, by the way.

Though this implies that the security principal can access all the keys in the key vault, as long as they know the name of the object they are trying to find. This might not be ideal for your scenario, especially if you’re looking to keep any potential blast radius small. Using separate Key Vaults might be a good enough option in this case. The Azure Key Vault team recommends the following when it comes to creating additional Key Vaults:

📖 From the documentation: “Our recommendation is to use a vault per application per environment (development, pre-production, and production), per region. This helps you not share secrets across environments and regions. It will also reduce the threat in case of a breach.”

But what if you’re looking for even more control? What if we want to release a key to an application that we have verified to be running in West Europe, running a particular firmware, or maybe it’s enough if our request originates from inside of a compliant Azure Confidential Virtual Machine? That’s where secure key release can help, it takes a policy-based approach to release keys to trusted execution environments. Azure Key Vault Premium and Managed-HSM can release HSM keys.

Performing the release key operation is similar to performing the get key operation, except we will need to change:

  • Key Vault access policies will need to be modified
    • Allow the release key operation for a specific managed identity.
  • The key will need some specific properties:
    • A release policy, since we only want a trusted confidential virtual machine to be able to access our key.
    • Must be marked as exportable.
  • The HTTP request will target a slightly different URL since we are performing a different operation.
    • Additionally, the Confidential VM’s attested platform report (aka the environment assertion) must be included the request body. We can use the Microsoft Azure Attestation service to to attest to the trustworthiness of the state of a Trusted Execution Environment-enabled platform, such as our Confidential VM.

📖 From the documentation: “An Environment Assertion is a signed assertion, in JSON Web Token form, from a trusted authority. An environment assertion contains at least a key encryption key and one or more claims about the target environment (for example, TEE type, publisher, version) that are matched against the Key Release Policy.”

After all of those steps have been done, we send the request off to Key Vault and it releases the key.

Image of the various operations that we will be performing.

Seems simple enough.

Guest Attestation

You might be wondering, what on earth is guest attestation?

The concept of attestation is one of the foundational elements of Azure Confidential Computing, so it’s quite an important concept to learn about. Attestation helps us to cryptographically assess that something is running in the intended operating state. It is the process by which one party, the verifier, assesses the trustworthiness of a potentially untrusted peer, the attester.

Attestation provides verification for three things:

  • The application’s identity.
  • The TEE’s integrity.
    • In other words, it proves that the code has not been tampered with.
  • Application is running securely inside a TEE, on some TEE-enabled Platform.

In our case, the TEE enables a platform that allows us to run an entire operating system inside of it. Can we be confident that our workload is running in the correct state? With guest attestation, we should be able to:

  • Make sure that our confidential VM runs on the expected platform, which is AMD SEV-SNP.
  • Verify that the confidential VM has secure boot enabled.
    • This protects the virtual machine’s firmware, boot loader, and kernel from rootkits and boot kits.
  • Get evidence for a relying party that the confidential VM runs on confidential hardware.

Microsoft has provided us with a C/C++ library (Microsoft.Azure.Security.GuestAttestation) that can help us to get a SEV-SNP platform report from the hardware and to also have it attested by an instance of Azure Attestation service (either one hosted by Microsoft or you).

🔥 I suppose you could try to request the AMD firmware to construct an attestation report without Microsoft’s library, as AMD offers a bit of information on how to do just that. Though you will still need to get the report attested by the Azure Attestation service. I personally recommend against doing this yourself, but here is a link to the SEV Secure Nested Paging Firmware ABI Specification (see Chapter 7.3 - Attestation).

Additionally, Microsoft has open sourced a Windows and Linux client binary that utilizes the guest attestation library so we get more easily integrate the guest attestation process into our existing workloads. To make things even better, it returns the attested platform report as a JSON Web Token which is precisely what we need to pass to Key Vault’s release key operation.

💡 A token from the Azure Attestation service is valid for 8 hours.

Secure Key Release policy

Key Vault secure key release policies have a very similar structure to Azure Policy, except they are implemented with a slightly different grammar. The idea here is that when we pass the attested platform report, in the form of a JSON Web Token (JWT), to Key Vault. It will, in turn, look at that JWT and check whether or not the attested platform report matches parts of the policy.

For example, let’s say we want to release a key only when our attested platform report has:

  • Been attested by Microsoft Azure Attestation (MAA) service endpoint “https://sharedweu.weu.attest.azure.net”.
    • This authority value from the policy is compared to the iss (issuer) property, in the token.
  • And that it also contains an object called x-ms-isolation-tee with a property called x-ms-attestation-type, which holds value sevsnpvm.
    • This tells us that MAA has attested that the CVM is running SEV-SNP.
  • And that it also contains an object called x-ms-isolation-tee with a property called x-ms-compliance-status, which holds the value azure-compliant-cvm.
    • This tells us that MAA has attested that the CVM is a compliant Azure confidential virtual machine (secure boot and everything is OK).

We would simply model the above, as so:

{
    "version": "1.0.0",
    "anyOf": [ // Always starts with "anyOf", meaning you can multiple, even varying rules, per authority.
        {
            "authority": "https://sharedweu.weu.attest.azure.net",
            "allOf": [ // can be replaced by "anyOf", though you cannot nest or combine "anyOf" and "allOf" yet.
                {
                    "claim": "x-ms-isolation-tee.x-ms-attestation-type", // Note the dot notation, so we can reference properties inside complex objects.
                    "equals": "sevsnpvm"
                },
                {
                    "claim": "x-ms-isolation-tee.x-ms-compliance-status",
                    "equals": "azure-compliant-cvm"
                }
            ]
        }
    ]
}

Release policy is an anyOf condition containing an array of key authorities. A claim condition is just another JSON object that identifies a claim name, a condition for matching, and a value. The AnyOf and AllOf condition objects allow for the modelling of a logical OR and AND. Currently, we can only perform an equals comparison on a claim. Condition properties are placed together with authority properties.

An important thing that I want to mention from the documentation is that the attested platform report must always contain a key-encryption key.

📖 From the documentation: “An environment assertion contains at least a key encryption key and one or more claims about the target environment (for example, TEE type, publisher, version) that are matched against the Key Release Policy. The key-encryption key is a public RSA key owned and protected by the target execution environment that is used for key export. It must appear in the TEE keys claim (x-ms-runtime/keys). This claim is a JSON object representing a JSON Web Key Set. Within the JWKS, one of the keys must meet the requirements for use as an encryption key (key_use is “enc”, or key_ops contains “encrypt”). The first suitable key is chosen.”

Key Vault will pick the first suitable key from “keys” array property in the “x-ms-runtime” object, it will look for a public RSA key with "key_use": ["enc"] or "key_ops": ["encrypt"]. Let’s take a look at a part of the attested platform report to see precisely what I mean:

{
    //...
    "x-ms-runtime": {
        "client-payload": {
            "nonce": "MTIzNA=="
        },
        "keys": [
            {
                "e": "AQAB",
                "key_ops": [
                    "encrypt"
                ],
                "kid": "TpmEphemeralEncryptionKey",
                "kty": "RSA",
                "n": "9v2XQgAA6y18CxV8dSGnh..."
            }
        ]
    },
    //...
}

As you can tell, there’s only one key under the path $.x-ms-runtime.keys. If you take a look at the attestation response body on GitHub, you will notice that there is also a key under $.x-ms-isolation-tee.x-ms-runtime.keys but this is not the key that Key Vault will be using.

From the tests that I’ve performed, Key Vault uses the TpmEphemeralEncryptionKey key as the key-encryption key, keep this in mind as we will bump into it later on. This key seems to rotate each time you restart the VM, hence why it’s ephemeral.

Demo time

Now that we have a basic idea of how everything fits together, let’s try to make it work. We will begin by deploying the Azure resources that we will need to perform our tests.

💡 You can find all the files that were used in this demo, including decrypted payloads, in my GitHub repository.

First, we need a confidential virtual machine with a system-assigned managed identity enabled, along with a Premium Key Vault. Second, we will also set a Key Vault access policy that lets the CVM perform the release key operation. Finally, we must load in our release policy, base64 encode it and ship it off to Key Vault at the same time we’re performing our request to create a key. Speaking of which, the key will be an exportable RSA key, backed by an HSM. (RSA-HSM)

I’ve turned some of these deployments into Bicep modules and wrapped them with a main.bicep file. (Hopefully that will save this page some vertical screen real estate.)

targetScope = 'resourceGroup'

@description('Required. Specifies the Azure location where the key vault should be created.')
param location string = resourceGroup().location

@description('Required. Admin username of the Virtual Machine.')
param adminUsername string

@description('Required. Password or ssh key for the Virtual Machine.')
@secure()
param adminPasswordOrKey string

@description('Optional. Type of authentication to use on the Virtual Machine.')
@allowed([
  'password'
  'sshPublicKey'
])
param authenticationType string = 'password'

@description('Not before date in seconds since 1970-01-01T00:00:00Z.')
param keyNotBefore int = dateTimeToEpoch(utcNow())

@description('Expiry date in seconds since 1970-01-01T00:00:00Z.')
param keyExpiration int = dateTimeToEpoch(dateTimeAdd(utcNow(), 'P1Y'))

module cvm 'confidential-vm.bicep' = {
  name: 'cvm'
  params:{
    adminUsername: adminUsername
    adminPasswordOrKey: adminPasswordOrKey
    authenticationType: authenticationType
    location: location
    vmName: 'skr-cvm'
    osImageName: 'Ubuntu 20.04 LTS Gen 2'
    vmSize: 'Standard_DC2as_v5'
    securityType: 'DiskWithVMGuestState'
    bootDiagnostics: false
    osDiskType: 'Premium_LRS'
  }
}

module akv 'keyvault.bicep' = {
  name: 'akv'
  params:{
    keyVaultName: 'skr-kv${uniqueString(resourceGroup().id)}'
    location: location

    // Access policy
    objectId: cvm.outputs.systemAssignedPrincipalId
    keysPermissions: [   // 👈 Access policy associated with keys and the object id. Very important stuff.
      'release'
    ]

    // Key settings
    keyName: 'myskrkey'
    keyType: 'RSA-HSM'
    keySize: 4096
    keyExportable: true // 👈 Enables release, don't forget this or things will not work.
    keyEnabled: true
    keyOps: ['encrypt','decrypt']
    keyNotBefore:keyNotBefore
    keyExpiration: keyExpiration
    releasePolicyContentType: 'application/json; charset=utf-8'
    releasePolicyData: loadFileAsBase64('assets/cvm-release-policy.json')
  }
}

We can verify that Key Vault has created a new, HSM-backed, key and that it contains our secure key release policy by navigating to the Azure Portal and selecting our key. Our key will also be marked as “exportable”.

Image of the Azure Portal with the settings for key named ‘my SKR key’ visible. It shows another panel that shows the details of the secure key release policy.

On the Azure side of things, should have everything in place to perform the release key operation via our confidential virtual machine.

Since I have deployed a Linux CVM, we will need to install an additional shared library for us to perform guest attestation.

sudo apt-get install libcurl4-openssl-dev
sudo apt-get install libjsoncpp-dev
sudo apt-get install libboost-all-dev
sudo apt install nlohmann-json3-dev

wget https://packages.microsoft.com/repos/azurecore/pool/main/a/azguestattestation1/azguestattestation1_1.0.2_amd64.deb
sudo dpkg -i azguestattestation1_1.0.2_amd64.deb

I will be using Microsoft’s Guest Attestation client, so all that there is left to do is for us to download it. I’m going to place the client library next to the PowerShell script that we’re going to use in the next step, though you could add the client library to your $PATH.

sudo apt install unzip

wget https://raw.githubusercontent.com/Azure/confidential-computing-cvm-guest-attestation/main/cvm-platform-checker-exe/Linux/cvm_linux_attestation_client.zip
unzip ./cvm_linux_attestation_client.zip
mv ./cvm_linux_attestation_client/AttestationClient .

chmod +x ./AttestationClient

The PowerShell script is fairly straightforward. It’s going to get the platform report and send it off to the West-Europe instance of Azure Attestation service to get it attested. Once that has been completed we will get a AAD token for Key Vault from the instance metadata service (IMDS).

By settings the attested platform report as the body payload and the AAD token in our authorization header, we will have everything we need to perform the key release operation.

#Requires -Version 7
#Requires -RunAsAdministrator
#Requires -PSEdition Core

<#
.SYNOPSIS
    Perform Secure Key Release operation in Azure Key Vault, provided this script is running inside an Azure Confidential Virtual Machine.
.DESCRIPTION
    Perform Secure Key Release operation in Azure Key Vault, provided this script is running inside an Azure Confidential Virtual Machine.
     The release key operation is applicable to all key types. The target key must be marked exportable. This operation requires the keys/release permission.
.PARAMETER -AttestationTenant
    Provide the attestation instance base URI, for example https://mytenant.attest.azure.net.
.PARAMETER -VaultBaseUrl
    Provide the vault name, for example https://myvault.vault.azure.net.
.PARAMETER -KeyName
    Provide the name of the key to get.
.PARAMETER -KeyName
    Provide the version parameter to retrieve a specific version of a key.
.INPUTS
    None.
.OUTPUTS
    System.Management.Automation.PSObject
.EXAMPLE
    PS C:\> .\Invoke-SecureKeyRelease.ps1 -AttestationTenant "https://sharedweu.weu.attest.azure.net" -VaultBaseUrl "https://skr-kvq6srllol2jntw.vault.azure.net/" -KeyName "myskrkey" -KeyVersion "e473cd4c66224d16870bbe2eb4c58078"
#>

param (
    [Parameter(Mandatory = $true)]
    [string]
    $AttestationTenant,
    [Parameter(Mandatory = $true)]
    [string]
    $VaultBaseUrl,
    [Parameter(Mandatory = $true)]
    [string]
    $KeyName,
    [Parameter(Mandatory = $false)]
    [string]
    $KeyVersion
)
# Check if AttestationClient* exists.
$fileExists = Test-Path -Path "AttestationClient*"
if (!$fileExists) {
    throw "AttestationClient binary not found. Please download it from 'https://github.com/Azure/confidential-computing-cvm-guest-attestation'."
}

# Use correct AttestationClient.
$cmd = $null
if ($isLinux) {
    $cmd = "sudo ./AttestationClient -a $attestationTenant -o token"
}
elseif ($isWindows) {
    $cmd = "./AttestationClientApp.exe -a $attestationTenant -o token"
}

$attestedPlatformReportJwt = Invoke-Expression -Command $cmd
if (!$attestedPlatformReportJwt.StartsWith("eyJ")) {
    throw "AttestationClient failed to get an attested platform report."
}

## Get access token from IMDS for Key Vault
$imdsUrl = 'http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://vault.azure.net'
$kvTokenResponse = Invoke-WebRequest -Uri  $imdsUrl -Headers @{Metadata = "true" }
if ($kvTokenResponse.StatusCode -ne 200) {
    throw "Unable to get access token. Ensure Azure Managed Identity is enabled."
}
$kvAccessToken = ($kvTokenResponse.Content | ConvertFrom-Json).access_token

# Perform release key operation
if ([string]::IsNullOrEmpty($keyVersion)) {
    $kvReleaseKeyUrl = "{0}/keys/{1}/release?api-version=7.3" -f $vaultBaseUrl, $keyName
}
else {
    $kvReleaseKeyUrl = "{0}/keys/{1}/{2}/release?api-version=7.3" -f $vaultBaseUrl, $keyName, $keyVersion
}

$kvReleaseKeyHeaders = @{
    Authorization  = "Bearer $kvAccessToken"
    'Content-Type' = 'application/json'
}

$kvReleaseKeyBody = @{
    target = $attestedPlatformReportJwt
}

$kvReleaseKeyResponse = Invoke-WebRequest -Method POST -Uri $kvReleaseKeyUrl -Headers $kvReleaseKeyHeaders -Body ($kvReleaseKeyBody | ConvertTo-Json)
if ($kvReleaseKeyResponse.StatusCode -ne 200) {
    Write-Error -Message "Unable to perform release key operation."
    Write-Error -Message $kvReleaseKeyResponse.Content
}
else {
    $kvReleaseKeyResponse.Content | ConvertFrom-Json
}

You can invoke the script by entering the following statement in your PowerShell console:

.\Invoke-SecureKeyRelease.ps1 -AttestationTenant "https://sharedweu.weu.attest.azure.net" -VaultBaseUrl "https://skr-kvq6srllol2jntw.vault.azure.net/" -KeyName "myskrkey"

And you should receive a response that contains the following JSON content:

{
    "value": "eyJhbGciOiJSUzI1NiIsImtpZCI6Ijg4RUFDMkRCNkJFNEUwNTFCMEUwNUFFQUY2Q0I3OUU2NzUy..."
}

We can verify that our key release policy actually works, remember that we have it set so it would only allow attested platform reports that were checked by the MAA in West Europe. By changing the MAA regional shared provider endpoint to use the East US endpoint located at “https://sharedeus.eus.attest.azure.net”, we will see that this actually gives us an error when we try to release the key. That’s good! 😎

Invoke-WebRequest: /home/thomas/skr/Invoke-SecureKeyRelease.ps1:85:29
Line |
  85 |  … yResponse = Invoke-WebRequest -Method POST -Uri $kvReleaseKeyUrl -Hea …
     |                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     | {"error":{"code":"Forbidden","message":"Target environment attestation does not meet key release requirements.","innererror":{"code":"AccessDenied"}}}

💡 You can find a list of all available regional shared providers for Microsoft Azure Attestation in the documentation!

Guest Attestation result

The result from the Guest Attestation client simply is a base64 encoded string! We can quite easily decrypt this, too. There are a couple of sections tucked inside the result, delimited by a . (dot). In reality, this value is a signed JSON Web Token (JWT), with a header, body and signature. We can split the string by the . (dot) value and base64 decode the results.

eyJhbGciO...

The header contains a jku, aka JWK Set URI, that links to a set of JSON-encoded public keys, one of which corresponds to the key used to digitally sign the JWS. The kid indicates which key was used to sign the JWS.

{
    "alg": "RS256",
    "jku": "https://sharedweu.weu.attest.azure.net/certs",
    "kid": "dRKh+hBcWUfQimSl3Iv6ZhStW3TSOt0ThwiTgUUqZAo=",
    "typ": "JWT"
}

The body of the guest attestation reponse is what will be used by Key Vault as input to test against the key release policy. We’ve already discussed that Key Vault will end up using the “TpmEphemeralEncryptionKey” as the key-encryption key.

{
    "exp": 1671865218,
    "iat": 1671836418,
    "iss": "https://sharedweu.weu.attest.azure.net",
    "jti": "ce395e5de9c638d384cd3bd06041e674edee820305596bba3029175af2018da0",
    "nbf": 1671836418,
    "secureboot": true,
    "x-ms-attestation-type": "azurevm",
    "x-ms-azurevm-attestation-protocol-ver": "2.0",
    "x-ms-azurevm-attested-pcrs": [
        0,
        1,
        2,
        3,
        4,
        5,
        6,
        7
    ],
    "x-ms-azurevm-bootdebug-enabled": false,
    "x-ms-azurevm-dbvalidated": true,
    "x-ms-azurevm-dbxvalidated": true,
    "x-ms-azurevm-debuggersdisabled": true,
    "x-ms-azurevm-default-securebootkeysvalidated": true,
    "x-ms-azurevm-elam-enabled": false,
    "x-ms-azurevm-flightsigning-enabled": false,
    "x-ms-azurevm-hvci-policy": 0,
    "x-ms-azurevm-hypervisordebug-enabled": false,
    "x-ms-azurevm-is-windows": false,
    "x-ms-azurevm-kerneldebug-enabled": false,
    "x-ms-azurevm-osbuild": "NotApplication",
    "x-ms-azurevm-osdistro": "Ubuntu",
    "x-ms-azurevm-ostype": "Linux",
    "x-ms-azurevm-osversion-major": 20,
    "x-ms-azurevm-osversion-minor": 4,
    "x-ms-azurevm-signingdisabled": true,
    "x-ms-azurevm-testsigning-enabled": false,
    "x-ms-azurevm-vmid": "6506B531-1634-431E-99D2-42B7D3414AD0",
    "x-ms-isolation-tee": {
        "x-ms-attestation-type": "sevsnpvm",
        "x-ms-compliance-status": "azure-compliant-cvm",
        "x-ms-runtime": {
            "keys": [
                {
                    "e": "AQAB",
                    "key_ops": [
                        "encrypt"
                    ],
                    "kid": "HCLAkPub",
                    "kty": "RSA",
                    "n": "tXkRLAABQ7vgX9642J2jS2l1m70YMp9w6wxSgOYWsfhifCnoFzH-iwie-u06hqfuPkHPCoFf0hS3zGEolRf-SpsWZY4oCK7n3AGKGfdJ4RxyXphxCU4J6U4H7iPd51dPM1FjPrJEr1tWE9gCM-y1y0Vim3vcAp8n70IFXtHv-KvZds9X0WVeGOcKMJM8JT6g71k1EcQ4md6fM64JZT1zTkp6N58nkqF0xCmfA3rbXlPUjSJ8A-GPXQ61tTgwQEMDaxY1jdrYCVCPRgJZrybLEAsjJZNQ6UHxyX0sE5nbhkloIhBX3XNXj5QllqfFFJX_fY9JrfXQzW01bsVHk0e1OQ"
                }
            ],
            "vm-configuration": {
                "console-enabled": true,
                "current-time": 1671835548,
                "secure-boot": true,
                "tpm-enabled": true,
                "vmUniqueId": "6506B531-1634-431E-99D2-42B7D3414AD0"
            }
        },
        "x-ms-sevsnpvm-authorkeydigest": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
        "x-ms-sevsnpvm-bootloader-svn": 3,
        "x-ms-sevsnpvm-familyId": "01000000000000000000000000000000",
        "x-ms-sevsnpvm-guestsvn": 2,
        "x-ms-sevsnpvm-hostdata": "0000000000000000000000000000000000000000000000000000000000000000",
        "x-ms-sevsnpvm-idkeydigest": "57486a447ec0f1958002a22a06b7673b9fd27d11e1c6527498056054c5fa92d23c50f9de44072760fe2b6fb89740b696",
        "x-ms-sevsnpvm-imageId": "02000000000000000000000000000000",
        "x-ms-sevsnpvm-is-debuggable": false,
        "x-ms-sevsnpvm-launchmeasurement": "ad6de16ac59ee52351c6038df58d1be5aeaf41cd0f7c81b2279ecca0df6ef43a2b69d663ad6973d6dbb9db0ffd7a9023",
        "x-ms-sevsnpvm-microcode-svn": 115,
        "x-ms-sevsnpvm-migration-allowed": false,
        "x-ms-sevsnpvm-reportdata": "c6500859af95440206aac5e93eb50a0f2cfd4fa2c5485e05a5c77a5d81c3dee30000000000000000000000000000000000000000000000000000000000000000",
        "x-ms-sevsnpvm-reportid": "cf5ea742f08cb45240e8ad4719b6115028f3e1d9d88175a247eb7c6c86da6493",
        "x-ms-sevsnpvm-smt-allowed": true,
        "x-ms-sevsnpvm-snpfw-svn": 8,
        "x-ms-sevsnpvm-tee-svn": 0,
        "x-ms-sevsnpvm-vmpl": 0
    },
    "x-ms-policy-hash": "wm9mHlvTU82e8UqoOy1Yj1FBRSNkfe99-69IYDq9eWs",
    "x-ms-runtime": {
        "client-payload": {
            "nonce": ""
        },
        "keys": [
            {
                "e": "AQAB",
                "key_ops": [
                    "encrypt"
                ],
                "kid": "TpmEphemeralEncryptionKey", // 👈 That key is back!
                "kty": "RSA",
                "n": "kVTLSwAAQpgtlqkwRrDXhDg_c1MfhRXI3xNPlCV1eVlEh5erlMcKZ1r_FU_r1qfjfbXgwraLbWRA0iPidvsverG08UFiAk76n9HrSGqQszwSX3MG8TnSmLE8lG77Kv8lyMxC7Cy-9g7N_1zb0lG_wh9mCHmHTgIIp1Lu6XSNokskqxABUWUqB71zFNEetLs_6KMWGBwj7wYPGBtcmYWECxf0QJk47qGFtPFbIN4HH81QJjBAJ058J96MyovE6VNfGDXFQHFy_2wKBIO70O0KNmuDUkQgpjIVEW1lkusbNRtxUOuURfQiNijJithxWnwWyeWQsLFhShyO0T9cX5O0pQ"
            }
        ]
    },
    "x-ms-ver": "1.0"
}

The documenation for Microsoft Azure Attestation service has an extensive list containing descriptions of all of these SEV-SNP-related claims.

Key Release Response

The secure key release operation only returns a single property inside of its JSON payload. The contents, however, have been base64 encoded as well.

{
  "value": "eyJhbGciOiJSUzI1NiIsImtpZCI6Ijg4RUFDM.."
}

Here we have another header, though this one has a X.509 certificate chain as a property.

{
    "alg": "RS256",
    "kid": "88EAC2DB6BE4E051B0E05AEAF6CB79E675296121",
    "x5t": "iOrC22vk4FGw4Frq9st55nUpYSE",
    "typ": "JWT",
    "x5t#S256": "BO7jbeU3BG0FEjetF8rSisRbkMfcdy0olhcnmYEwApA",
    "x5c": [
        "MIIIfDCCBmSgA..XQ==",
        "MII..8ZZ8m",
        "MII..lMrY="
    ]
}

You can read from the “x5c” array in PowerShell if you wanted to, this can help you verify that this is a valid certificate. Here is an example of what that might look like:

$certBase64 = "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"
$cert = [System.Security.Cryptography.X509Certificates.X509Certificate2]([System.Convert]::FromBase64String($certBase64))
$cert | Format-List *

# EnhancedKeyUsageList :
# DnsNameList          :
# SendAsTrustedIssuer  :
# Archived             : False
# Extensions           : {System.Security.Cryptography.Oid, System.Security.Cryptography.Oid, System.Security.Cryptography.Oid,
#                        System.Security.Cryptography.Oid…}
# FriendlyName         :
# HasPrivateKey        : False
# PrivateKey           :
# IssuerName           : System.Security.Cryptography.X509Certificates.X500DistinguishedName
# NotAfter             : 9/18/2023 6:14:06 PM
# NotBefore            : 9/23/2022 6:14:06 PM
# PublicKey            : System.Security.Cryptography.X509Certificates.PublicKey
# RawData              : {48, 130, 8, 124…}
# SerialNumber         : 330055603EB9BB33ACBFA5625500000055603E
# SignatureAlgorithm   : System.Security.Cryptography.Oid
# SubjectName          : System.Security.Cryptography.X509Certificates.X500DistinguishedName
# Thumbprint           : 88EAC2DB6BE4E051B0E05AEAF6CB79E675296121
# Version              : 3
# Handle               : 140585119070160
# Issuer               : CN=Microsoft Azure TLS Issuing CA 06, O=Microsoft Corporation, C=US
# Subject              : CN=vault.azure.net, O=Microsoft Corporation, L=Redmond, S=WA, C=US

Now for the body, I should mention that this response looks incredibly similar to the response that you get when invoking the get key operation. However, the release operation will also include the hsm_key property, amongst other things.

{
    "request": {
        "api-version": "7.3",
        "enc": "CKM_RSA_AES_KEY_WRAP",
        "kid": "https://skr-kvq6srllol2jntw.vault.azure.net/keys/myskrkey"
    },
    "response": {
        "key": {
            "key": {
                "kid": "https://skr-kvq6srllol2jntw.vault.azure.net/keys/myskrkey/e473cd4c66224d16870bbe2eb4c58078",
                "kty": "RSA-HSM",
                "key_ops": [
                    "encrypt",
                    "decrypt"
                ],
                "n": "nwFQ8pXnQWPmjHDhNrjca0hzZ-fEgNboahlnu_kNBbM85CRBjovcnQPkP-UD_ILZ-KAcQopahYO4lTewBMKRoi547Ks2E6CSTr4FWYfyZXbGGUaxLW-_ziCeCubAXuPiiMUpbgLYVqX0ycvlH5lbbvSib8YoB4EANI7lyPFU6EUi25SxNcV8L5hK3Lx8NDQmPdKfaRldJIJU7IOO8hEZYpQ6leicDpjDvQBIHhYtZVZTd7bEhvsxqnrLK0yyoqh7K9QYJ9Ne7vwdWmRhgxnK47se79hZhqo2QqSBG004t6GjXoq1uLXrg71w57N5vVCVg64Z78theJFVeEdUlPvHkGLThViXkd9zsTpJx1m4beJovFgdD2YOKef-ACz2XRlb8KoOOc-hqfTB3uqi3i3oG2xmox6PyLV4_-wstNoQ0npJ3uhYbYlHq-6mw-d9fS48KutMfY23913zV6FQweil4I_RxTjWwElE8gpdSp01oyeo9UV-M7KHlTY-mFp5dcMdgcl4DTdOhTyntZqZS-KznkMOYJUijEJkVtElGru0NJo8yMbbTdsbdziH5NkIrdqEHX2u2130eQ3lB_NqT4hjErXwBe0N37UDeaJgaD5G-53jWJP1gwbuukE6oKpOnBl62e9NNS3HX0Ctu5Bte9ACii1EICZxjsyy01eXDxQJ20M",
                "e": "AQAB",
                "key_hsm": "eyJzY2hlbWFfdmVyc2lvbiI6IjEuMCIsImhlYWRlciI6eyJraWQiOiJUcG1FcGhlbWVyYWxFbmNyeXB0aW9uS2V5IiwiYWxnIjoiZGlyIiwiZW5jIjoiQ0tNX1JTQV9BRVNfS0VZX1dSQVAifSwiY2lwaGVydGV4dCI6IlJmdHh2cld0N2JXanQ5SFpoeUdtdGlzelhUXzlPRDRtWTJ0Vkc5N0dNSmlOWkVLcmZIX1ZiNkNUOEdPUXFfMEw5OTNqLUpwbklyZlN3Qk5WTTdlcmFQcU9tSFRCc19fMEIweS1XM0QzYjA0dDdHM3Z6cEk2Z2ZzMllOTTRoVURWWkFCWkE1dFJLa2M2NkNadzZQR194SFV2SlB6NHdSSmRNVFhvNXBvUGRWYVdabDdGb002TmFNcDhibXFfaGhobDFpeFlXYjhpTDNVTHVFdW9QM0JtNnVJMThmZ2ZQN1VMaDF0TUlFNEQ4REp6aGJnbDhYMjBoYUo2V0hxV090VFZXajN2U2FhUHFVdEpuQmpmUm1tQ200RHZiZlV1aGo0MVVweDQ3MVhwSXhKTVd5UTd1bklBT09DSUIzc09jdXBYUXNYZFJxbDh5ckNBS2ZuUTlXTHlFMk9aVlZmeEFocjc0WTdZSy1xMjhjZVF4LVc2ZEZQaEtvWnpZRUIzMjZKNk5Cd1I4RENEbEJPMEhjUGF0aUpqelZHOEY3YXNhTHZQRlcyUkk0aU4yNE1mR1R1OUdadjVMUmFXLUJsazZmNnR1Wlk1eTZaMEtUb2xOTVYxNlBFUXVCZzBiN1pDV29zMEVxMlh1azFra0wtLXpYZ2lkcTN3MGU3cGg2dWJTeTNaSUNZWjBTLTJHdlk3N3dkMXozamFBdHRhZU5qY0stcWRSYjVoSkpNSWdfQWQ3cXF0amVwM0IxTXlsblBqWG1kMWJLUEJUZWxwQ3FPaGZuQ2Z5eEh1NTRYZUl0dmNqTTEwYzdtV0d5d0d5MkNIS2JOVlA4ZTlnT25fVmx3UWNqc25DRkpTMXgwV2cxN0JBY0ltczZNRmQ1RHc4cGpSS1gyVUQ1WUktQ016NjFQM1VjRF9CX2ZTSWNybmVPUXJtY1h3Tm1aSVlFTVQzRzAtbTh5T0FNLWlvOEVHbUhzdlFXaThRUFB6VVNOZjU1S08tTFFWRUVOd2ExalpqaTczS2lLeGhTWl81NVJlNEJPQVZjeENUaTJSSjBrOVBDSjVndmp2OXN4cm0xZ2JQaTF4TE9MSEt5WEFrZEVQLXRPNEVpY0NabjJnN18tNmh4ejdvcl9jRDFVQ0JCeGN6ZzdlMUppbW1ISXFWcFV6cHp2S3oyU2dzX1V2YmNhNmVFM3JIcXVkS0QwaFBNWkJtcml2ZE91amVQSDFPdExMajNnY01vcE94emhOelZKX3hCcjdnclM3U1psX0xRTnhZZ2FaLWUwNDZrWEtIUmNaLUxROXNnVHZOY3lmMDZjMnphWG9VaTR3dHp0VVcyMnlsYktWeVpBUmxoeTJObWZ6dnV3WnA1bk1zRzNCbXdDQXU0SkYyb3owYzlrY1BIWXdTMU5tQS0yOHoyUTdLeW1KTHpMMWNVOV9CNmUtTVczMlRWTEFnNmY3enpsMmlkZ3RfeGtUZmVIRm0wVW42X3NGX29YYnRadXdsblFPUzdibENRYnlwOGRHWTdJZ2tKR195OTc3OXpDX0pMQXN6S0J2T05YVm9mTTdHNW4yZk1yQ3Z1TG1HWXpTMU1qRHVOOVNmZjBJZHVQZGM5WnVvN1VVdTFPZUc3NlotTkR1X0o4RzJXSTJadlczOTVjbmZnY1RnbENZSjlTd3FGSkRGVTRyYUtNVlZMb1lqSUlIRl85LUdJRVdyLUlkTFFTT0hXWTFhWjIxNWtVaktsaWpPcGdRMW9waEdBeGpDMXAzWXhWT19RdVkyOXEwUzRpTWFXOHlZbDF1Zk5TaWpuNk9VMENYOFotbHAyZXFkZUpkTFUxSUNyekxLWndVY2ViRGF3MF9VZEpYMkx4dDJ4NG5TY0M2MFQ3Tndpa2F0N19QMHQ4ZG1oSTJwWUp5LXl3ZUE5Sy1NS3ZzR01POG9MVjRULXlQMDhnaHhfc1hBcGpFRHE0NUctVUNKcS00cnI5NE9KTTFQcDg1MXBiU3hmNGt4OGVMZ01kbUpRbWhnbnpuT0xBU193WV83Vl81SFBiOGdYUmFZazRma0xFZVpwcWJ5MW05SGFvUFJ4MkZvX0dLUDdOM1VJRFJUdng2U2cwT1g0aHlTaUtqOGdQWTNUWTBFTGt2X09tdzBiQTBCR2ZWUE1uQmYyLUl4RDR0Q3V1azNlWWIyTXI1TG55N0tVMmlvNmlNS1hjWS1VZmdaMUxONEg4eHVhTmVaeENjWmpGbVY1bmYyNlJCWTBpejkyb25uTmY0Mk9XLUNjWUFBOG5wbHJ0WUlCejFHTmRybS03WkxzTVhuVHNTMlZWRzlYcE9teEl0ak1KOGZ2NTlQQlcyeXNZU0dTSTc3bEZyQ2ZoVE1xYW1kVFktUFBLZnhYLXB2NVE1T3dJeThTUkhUTHZsclA2NjJGZUhaX214OVNQMEV1dEtLcFpFNG1JLVdvZm92YXFTVzM1cmZVZkZIR3doM0tzb25mcExSTHl3MjZPWllkWFNuQm9QRnBTUktKanhPRG1IekFyWWMtNjJxcWJ3Rm9pWG14VExJcHBDQUkwYXBTY05tOFdBNGFUeFU3T21iYzYwRFJ1T2FNN3FZRVhUaE9JblBXRnJfb1dQV2xudnhmSlhWRFdUX0hQVDU2bU1oM0NFOFB3cW11QlhoNEVTMkFRLVU5Z1Vmd1ZRTFI2Snc3cWZkb1dPNVBCMmVlLW1acW0xWU5uZkVtR3pLQTdWM3plQU5kSUQ5MmUzc2VQRk9fUWZCQVdXVnpERFpGdFk5MUJwUUtsVlZ5OFEzTlBVWHBUY0Y3SUVZcGliV29JOFU2YjMwR3kyTllWU2trM3EtVFhxTVBMdW9XRks1QUl6cl9mX0IxaFBzUVcyNWhZdjQ5RDdCekJrN0hCaXpTcWVITkx4LUF0Tk8wYTBseU42VXdqTmdhUnlFRmQ3M2JXRnVpUHVfTlhoLUVtdHhpS0RwMWlsZXUzazkyVG44bEh2OFR0WENFWkhUa3RBdFBCLXVLZDJrNjdVRFd0U2pxcFdyNllVa2Z6VjNsMk5CWUpXUWpYLXMwdktFV1A0a1VmdnhNNGxudjU1YzhOVjdkUWZaQ1REeTJla3EwWVQwVlpSRmZGTll2VG1Rd2NpazNBT1NJVWxTSnRPQWFyM0ZETlFhWlZoQmh0ajY2TFl1Yzdac1A0VEQtRnZKQ3gta2RvamphNko0c1JMVWZDWEJVZzN1NVQ2UGltSDNHOXpYbkNsdEVZdWpJNTJYZ0V6RTJabGs4Z0JSdjlreERoRVBaUnhSenBKVXBJNGdrNGpZV0hnb0g2UXA4UUZwMkxocVhqMVlCSVdpZ1dOdTROMHQ5c1FmaG04X1hjc2dhanVjLXpXbWxwWV81MmZhWVp1M24waG1aSEk3ZlMwNDhtUS1HTk9BVENoakNWbXJwMnJiQXdNbFlpVXhJbllTTVBLaUhZTXhBWWxBWW1vNnpLaW5xWHpNdml1Y1dnWFlsamw0V2Y5SmdrMl9zams3bGl0N0RFSVFvQ3dlTS1KRXE2WDdPd0ZjcUZXZEE0VWNHTjFfS09hQ0ptR3FsRHpuZEhIbkFxb2lCUFdSVzJ5dmtnQWUxUVB2UEtkZERPbmx1X253b0NGU21XelhVSnMyaVMzbTF2ZjRBX0l0M2tlVG9wMkNQTXkxbDJtRXBfbmlYZEFjNUd5M0cxeDExTHBHMUdRYmNCMFFpZHJWVEQxLU5CZFhMSmNKeW5kaFBSWFN1WFZROUU3bUtrQUM4c0xqNkVodXdRaUFIVjR5cTBQQ1dpaGZOSVpheDY4NnlCaXFoVHQ5UTVnMFBLQXZfVy11WXFiQ0JtcXQ4TVVpcGJmc0dWNlJOajJxdkEyNFZFU2lfTlNLUUR1UUZaWnh3ZFBQZXN6b0J6d1hCQUpOdkZqeHpYY1lxUGxSdm1YMVpNbTdBS0VUWDhwcXZza3hKb05JcnN5SUpJYXdyYUxVQmFlRXpEeUhteDNMZDlYSkEwUlc1ajFpX3RsU0pSQ2hyZ3VDYUF0V1VveFJHbTZOZXVMNi10bVIxN2xEUWg1cWt2TzZOQ3hQTm13UGlSVkFTZG01NDNTeDNhNDlBejhLVGNsWXdpZWFuN1BzaEtCUzMteERsQTdDcDVOaS16ZkFCLXY5bHRKcjEwVTBvS29sYW1HaFFvOGFFWjFLV1hEcEEwTG5JVkMyM29EYnZvTlRsNXdmQzFWMlpSMnQxS2FydHMzTmVEUzlTQU5aSjJDWEhkRjU1U1lXMldjbnEyLW1ZSWduRUVPVE5QVGhzWkhlTUZIRUdLeVVmY1FLU0FkU0djWTBXLVRTU3lkeWY5Um1vWnB3OXhtaVpqYXM1R2lUb0o2RTdyOGFxS2trVHV6VU81cWpCcUZCeWRJWUE0NEk1ZmtvOTluOG5SZmJ4b3NmcVF2REhqLW1VbldWRzB1Zy1pV3kwRUdrYVhuaWJWRDZrSDJnZ2M2a3gwbDhNdUdoUzV0cmJMUTc1eUp0bkIycmNEc3ZKSnBYakJpV042b0pUS0dKUWtLQXNRa1pTYW5TQndpdUZTYlo5cmNNTUxCS0F3cjBncVo2Vkl0clJjZ3ZiczY3ZFlqMWZQdVRmbGFqZDE3UjJRZnlXZE9wOWxmUWRvZGNtdE1vQ3MtallIN0k3dERHdVE2MnA0bjE1RkxLY1hHLVdmd3BrN1kya3NzT2JVLWptbGIifQ"
            },
            "attributes": {
                "enabled": true,
                "nbf": 1671577355,
                "exp": 1703113355,
                "created": 1671577377,
                "updated": 1671827011,
                "recoveryLevel": "Recoverable+Purgeable",
                "recoverableDays": 90,
                "exportable": true
            },
            "tags": {},
            "release_policy": {
                "data": "eyJ2ZXJzaW9uIjoiMS4wLjAiLCJhbnlPZiI6W3siYXV0aG9yaXR5IjoiaHR0cHM6Ly9zaGFyZWR3ZXUud2V1LmF0dGVzdC5henVyZS5uZXQiLCJhbGxPZiI6W3siY2xhaW0iOiJ4LW1zLWlzb2xhdGlvbi10ZWUueC1tcy1hdHRlc3RhdGlvbi10eXBlIiwiZXF1YWxzIjoic2V2c25wdm0ifSx7ImNsYWltIjoieC1tcy1pc29sYXRpb24tdGVlLngtbXMtY29tcGxpYW5jZS1zdGF0dXMiLCJlcXVhbHMiOiJhenVyZS1jb21wbGlhbnQtY3ZtIn1dfV19",
                "immutable": false
            }
        }
    }
}

Should you base64 decode the value under $.response.key.release_policy.data, you will actually get the JSON representation of the Key Vault key release policy that we defined earlier in this post.

The hsm_key property its base64 decoded value looks like this:

{
    "schema_version": "1.0",
    "header": {
        "kid": "TpmEphemeralEncryptionKey", // 👈 It's that key again! (key identifier of KEK)
        "alg": "dir", // Direct mode, i.e. the referenced kid is used to directly protect the ciphertext
        "enc": "CKM_RSA_AES_KEY_WRAP"
    },
    "ciphertext": "RftxvrWt7bWjt9HZhyGmtiszXT_9OD4mY2tVG97GMJiNZEKrfH_Vb6CT8GOQq_0L993j-JpnIrfSwBNVM7eraPqOmHTBs__0B0y-W3D3b04t7G3vzpI6gfs2YNM4hUDVZABZA5tRKkc66CZw6PG_xHUvJPz4wRJdMTXo5poPdVaWZl7FoM6NaMp8bmq_hhhl1ixYWb8iL3ULuEuoP3Bm6uI18fgfP7ULh1tMIE4D8DJzhbgl8X20haJ6WHqWOtTVWj3vSaaPqUtJnBjfRmmCm4DvbfUuhj41Upx471XpIxJMWyQ7unIAOOCIB3sOcupXQsXdRql8yrCAKfnQ9WLyE2OZVVfxAhr74Y7YK-q28ceQx-W6dFPhKoZzYEB326J6NBwR8DCDlBO0HcPatiJjzVG8F7asaLvPFW2RI4iN24MfGTu9GZv5LRaW-Blk6f6tuZY5y6Z0KTolNMV16PEQuBg0b7ZCWos0Eq2Xuk1kkL--zXgidq3w0e7ph6ubSy3ZICYZ0S-2GvY77wd1z3jaAttaeNjcK-qdRb5hJJMIg_Ad7qqtjep3B1MylnPjXmd1bKPBTelpCqOhfnCfyxHu54XeItvcjM10c7mWGywGy2CHKbNVP8e9gOn_VlwQcjsnCFJS1x0Wg17BAcIms6MFd5Dw8pjRKX2UD5YI-CMz61P3UcD_B_fSIcrneOQrmcXwNmZIYEMT3G0-m8yOAM-io8EGmHsvQWi8QPPzUSNf55KO-LQVEENwa1jZji73KiKxhSZ_55Re4BOAVcxCTi2RJ0k9PCJ5gvjv9sxrm1gbPi1xLOLHKyXAkdEP-tO4EicCZn2g7_-6hxz7or_cD1UCBBxczg7e1JimmHIqVpUzpzvKz2Sgs_Uvbca6eE3rHqudKD0hPMZBmrivdOujePH1OtLLj3gcMopOxzhNzVJ_xBr7grS7SZl_LQNxYgaZ-e046kXKHRcZ-LQ9sgTvNcyf06c2zaXoUi4wtztUW22ylbKVyZARlhy2NmfzvuwZp5nMsG3BmwCAu4JF2oz0c9kcPHYwS1NmA-28z2Q7KymJLzL1cU9_B6e-MW32TVLAg6f7zzl2idgt_xkTfeHFm0Un6_sF_oXbtZuwlnQOS7blCQbyp8dGY7IgkJG_y9779zC_JLAszKBvONXVofM7G5n2fMrCvuLmGYzS1MjDuN9Sff0IduPdc9Zuo7UUu1OeG76Z-NDu_J8G2WI2ZvW395cnfgcTglCYJ9SwqFJDFU4raKMVVLoYjIIHF_9-GIEWr-IdLQSOHWY1aZ215kUjKlijOpgQ1ophGAxjC1p3YxVO_QuY29q0S4iMaW8yYl1ufNSijn6OU0CX8Z-lp2eqdeJdLU1ICrzLKZwUcebDaw0_UdJX2Lxt2x4nScC60T7Nwikat7_P0t8dmhI2pYJy-yweA9K-MKvsGMO8oLV4T-yP08ghx_sXApjEDq45G-UCJq-4rr94OJM1Pp851pbSxf4kx8eLgMdmJQmhgnznOLAS_wY_7V_5HPb8gXRaYk4fkLEeZpqby1m9HaoPRx2Fo_GKP7N3UIDRTvx6Sg0OX4hySiKj8gPY3TY0ELkv_Omw0bA0BGfVPMnBf2-IxD4tCuuk3eYb2Mr5Lny7KU2io6iMKXcY-UfgZ1LN4H8xuaNeZxCcZjFmV5nf26RBY0iz92onnNf42OW-CcYAA8nplrtYIBz1GNdrm-7ZLsMXnTsS2VVG9XpOmxItjMJ8fv59PBW2ysYSGSI77lFrCfhTMqamdTY-PPKfxX-pv5Q5OwIy8SRHTLvlrP662FeHZ_mx9SP0EutKKpZE4mI-WofovaqSW35rfUfFHGwh3KsonfpLRLyw26OZYdXSnBoPFpSRKJjxODmHzArYc-62qqbwFoiXmxTLIppCAI0apScNm8WA4aTxU7Ombc60DRuOaM7qYEXThOInPWFr_oWPWlnvxfJXVDWT_HPT56mMh3CE8PwqmuBXh4ES2AQ-U9gUfwVQLR6Jw7qfdoWO5PB2ee-mZqm1YNnfEmGzKA7V3zeANdID92e3sePFO_QfBAWWVzDDZFtY91BpQKlVVy8Q3NPUXpTcF7IEYpibWoI8U6b30Gy2NYVSkk3q-TXqMPLuoWFK5AIzr_f_B1hPsQW25hYv49D7BzBk7HBizSqeHNLx-AtNO0a0lyN6UwjNgaRyEFd73bWFuiPu_NXh-EmtxiKDp1ileu3k92Tn8lHv8TtXCEZHTktAtPB-uKd2k67UDWtSjqpWr6YUkfzV3l2NBYJWQjX-s0vKEWP4kUfvxM4lnv55c8NV7dQfZCTDy2ekq0YT0VZRFfFNYvTmQwcik3AOSIUlSJtOAar3FDNQaZVhBhtj66LYuc7ZsP4TD-FvJCx-kdojja6J4sRLUfCXBUg3u5T6PimH3G9zXnCltEYujI52XgEzE2Zlk8gBRv9kxDhEPZRxRzpJUpI4gk4jYWHgoH6Qp8QFp2LhqXj1YBIWigWNu4N0t9sQfhm8_Xcsgajuc-zWmlpY_52faYZu3n0hmZHI7fS048mQ-GNOATChjCVmrp2rbAwMlYiUxInYSMPKiHYMxAYlAYmo6zKinqXzMviucWgXYljl4Wf9Jgk2_sjk7lit7DEIQoCweM-JEq6X7OwFcqFWdA4UcGN1_KOaCJmGqlDzndHHnAqoiBPWRW2yvkgAe1QPvPKddDOnlu_nwoCFSmWzXUJs2iS3m1vf4A_It3keTop2CPMy1l2mEp_niXdAc5Gy3G1x11LpG1GQbcB0QidrVTD1-NBdXLJcJyndhPRXSuXVQ9E7mKkAC8sLj6EhuwQiAHV4yq0PCWihfNIZax686yBiqhTt9Q5g0PKAv_W-uYqbCBmqt8MUipbfsGV6RNj2qvA24VESi_NSKQDuQFZZxwdPPeszoBzwXBAJNvFjxzXcYqPlRvmX1ZMm7AKETX8pqvskxJoNIrsyIJIawraLUBaeEzDyHmx3Ld9XJA0RW5j1i_tlSJRChrguCaAtWUoxRGm6NeuL6-tmR17lDQh5qkvO6NCxPNmwPiRVASdm543Sx3a49Az8KTclYwiean7PshKBS3-xDlA7Cp5Ni-zfAB-v9ltJr10U0oKolamGhQo8aEZ1KWXDpA0LnIVC23oDbvoNTl5wfC1V2ZR2t1Karts3NeDS9SANZJ2CXHdF55SYW2Wcnq2-mYIgnEEOTNPThsZHeMFHEGKyUfcQKSAdSGcY0W-TSSydyf9RmoZpw9xmiZjas5GiToJ6E7r8aqKkkTuzUO5qjBqFBydIYA44I5fko99n8nRfbxosfqQvDHj-mUnWVG0ug-iWy0EGkaXnibVD6kH2ggc6kx0l8MuGhS5trbLQ75yJtnB2rcDsvJJpXjBiWN6oJTKGJQkKAsQkZSanSBwiuFSbZ9rcMMLBKAwr0gqZ6VItrRcgvbs67dYj1fPuTflajd17R2QfyWdOp9lfQdodcmtMoCs-jYH7I7tDGuQ62p4n15FLKcXG-Wfwpk7Y2kssObU-jmlb"
}

I didn’t quite understand how CKM_RSA_AES_KEY_WRAP was being applied here, fortunately I found some clues in Key Vault’s Bring your own key specification docs page. More specifically it states the following regarding the enc (encoding) being used:

The bytes for the plaintext key are then transformed using the CKM_RSA_AES_KEY_WRAP mechanism:

  • An ephemeral AES key is generated and encrypted with the wrapping RSA key using RSA-OAEP with SHA1.
  • The encoded plaintext key is encrypted using the AES key using AES Key Wrap with Padding.
  • The encrypted AES key and the encrypted plaintext key are concatenated to produce the final ciphertext blob.

I suppose the next step I’d have to perform would be an unwrap operation, and I believe more specifically I’d need to take a look at the CKM_RSA_AES_KEY_UNWRAP operation. I’ve found some clues on how to do this by looking at some of the Confidential Consortium Framework’s (CFF) source code, for this particular operation. Though I will save that step for another blog.

Closing thoughts

The Microsoft documentation mentions a scenario where the confidential VM can get secrets from a secret management service and with it some steps on how one must decrypt the AES key using the vTPM, though nothing too specfic. I believe this can be done through the guest attestation C/C++ library (Microsoft.Azure.Security.GuestAttestation) and the decrypt API. Unfortunately, whenever I attempted to decrypt the ciphertext, after base64 decrypting it, I’d receive error code 26 (data decryption TPM error)! Dang, so close.

For me to be able to get an understanding of how “Secure Key Release” worked I had to dig through several different sources: PDFs, GitHub repositories and even Twitter feeds. I was quite surprised to see that there isn’t a lot of documentation surrounding SKR in the Microsoft documentation, at least in one spot. I do not mind having to search around a bit, but it would have been more pleasant if there was a dedicated section for SKR, detailing the process.

As a result I am not sure whether all of my assumptions are correct, however I will update this post should I stumble across any new information.

That being said, I must also mention that it was fun to dive into the rabbit hole that is cryptography. 😁