Retrieving Azure KeyVault Secrets and Certificates correctly
In the Azure Portal under a Key Vault instance, Secret objects and Certificate objects are separately visually and therefore appear to be 2 separate value stores in the Azure Portal and this may seem insignificant at first glance but this isn't quite the case.
To me, it appeared an obvious idea that retrieving a 'Secret' and retrieving a 'Certificate' would therefore require using two different SDK methods that are suitable and fit for these specific purposes. And that's where my confusion and wondering began.
Azure SecretClient SDK Secret Retrieval
Consider that in Azure KeyVault we have:
- A Secret called "AISecret" > Visible in the Secrets section in KeyVault
- A Certificate called "mycertificate" > Visible in the Certificates section in KeyVault
To retrieve the Secret, we would typically need to use the following code with the SecretClient SDK:
using Azure.Core;
using Azure.Identity;
using Azure.Security.KeyVault.Secrets;
var keyVaultUrl = "[Your_keyvault_URI]";
//exponential backoff retry strategy
SecretClientOptions retryoptions = new SecretClientOptions()
{
Retry =
{
Delay= TimeSpan.FromSeconds(2),
MaxDelay = TimeSpan.FromSeconds(15),
MaxRetries = 8,
Mode = RetryMode.Exponential
}
};
var client = new SecretClient(new Uri(keyVaultUrl), new DefaultAzureCredential(), retryoptions);
//gets the Secret
KeyVaultSecret secret = await client.GetSecretAsync("AISecret");
//gets the certificate!! this works too! but WHY, it's a certificate?
KeyVaultSecret cert = await client.GetSecretAsync("mycertificate");
Console.WriteLine(secret.Value);
Console.WriteLine(cert.Value);
Console.ReadLine();Retrieving a secret in Azure KeyVault
But notice that the "Certificate" and the value for it is retrievable using what looks more like an SDK method designed for retrieving Azure Key Vault Secrets specificially -> it is in the name GetSecretAsync after all, and how come we can't just use client.GetCertificateAsync("mycertificate") here to be more semantically sound? That would make more sense surely?
So what is happening here?
It turns out that when Azure creates a "Certificate" it will actually store 3 linked entities, those being a Certificate (metadata, a public key, policy and thumbprint) , a Key (a private key) , and a Secret(this is a full PFX/PEM with private key). The key insight is that these 3 entities all share the same name, and therefore when we use GetSecretAsync("mycertificate") to retrieve a Certificate object in the Portal, we get the certificate raw contents (a base64 string that is the contents of the full PFX bundle) because we in fact retrieve an auto-generated and non-visible-in-AzurePortal Secret (that shares the name mycertificate) that is consumed by the certificate "mycertificate".
That is the reason why that works. In Azure, Certificates are backed by auto-generated secrets that we don't see.
We retrieve the base64 string and we can then use in the following manner as an X509 certificate in code :
KeyVaultSecret cert = await client.GetSecretAsync("mycertificate");
byte[] fullpfxBytes = Convert.FromBase64String(cert.Value);
var fullCertificate = new X509Certificate2(fullpfxBytes, (string)null,
X509KeyStorageFlags.MachineKeySet |
X509KeyStorageFlags.PersistKeySet |
X509KeyStorageFlags.Exportable );Using raw PFX bytes as x509 certificate in code
The reason why we cannot use client.GetCertificateAsync here is because this SDK method will only retrieve partial data in the form of just the metadata, policy, thumbprint and public key components, which by themselves would still not be able to construct the contents of the Certificate we desire in code. We would still need the private key component to make it usable and we use GetSecretAsync to retrieve the full PFX to do just that.
A smarter way to retrieve a Certificate in Azure KeyVault
So there must a more semantically sound way to write the code and retrieve the Certificate, correct? After all, it can be deceiving that the Secrets client SDK gives us a GetCertificate and yet we hardly get an actual usable certificate?
This is how it would be done simply and smarter by using the Certificates client SDK instead, without the need to do base64 string manipulation:
using Azure.Security.KeyVault.Certificates;
//using Azure.Security.KeyVault.Secrets; <-- now remove this
CertificateClient certificateClient = new CertificateClient(new Uri(keyVaultUrl), new DefaultAzureCredential());
//this is more immediately usable in code
X509Certificate2 smartCertificate = await certificateClient.DownloadCertificateAsync("mycertificate");Using Certificate Client SDK for Certificate Retrieval
Happy secreting and certificating ☺