Get X509 Certificate from Azure Keyvault to use in a REST call

Viewed 5036

I'm trying to get a certificate from Azure Keyvault, and then use it to call a REST API which requires a certificate for its authentication.

I've tried doing this locally - I have the .pfx file on disk, I load it into a byte array, and then create my certificate from it:

X509Certificate2 x509 = new X509Certificate2(File.ReadAllBytes(path), password);

and then use that certificate in RestSharp to do my REST call:

IRestClient client = new RestClient(url);
client.ClientCertificates = new X509CertificateCollection { x509 };

var request = new RestRequest(lastUrlPart, Method.GET);
request.AddHeader("Cache-Control", "no-cache");
request.AddHeader("Accept", "application/json");
request.AddHeader("Content-Type", "application/json");

IRestResponse response = client.Execute(request);

if (response.IsSuccessful)
{
    // read out the response and process it
}

works like a charm.

Now I'm trying to do the same, but fetching the certificate from Azure Keyvault. I've created an app registration in Azure AD, created my keyvault, and gave my app registration's service identity access to the keyvault. I've uploaded my certificate into the keyvault. So far, so good.

I've found this code to fetch the certificate from the Keyvault:

var azureServiceTokenProvider = new AzureServiceTokenProvider();

var kv = new KeyVaultClient(async (authority, resource, scope) =>
                                        {
                                            var authContext = new AuthenticationContext(authority, TokenCache.DefaultShared);
                                            var clientCred = new ClientCredential(clientAppId, clientSecret);
                                            var result = await authContext.AcquireTokenAsync(resource, clientCred);

                                            if (result == null)
                                            {
                                                throw new InvalidOperationException("Failed to obtain the JWT token");
                                            }

                                            return result.AccessToken;
                                        });

string certIdentifier = "https://mykeyvault.vault.azure.net/certificates/Certificate-TEST/14753af7586445fe9d57efa136ac090c";

var vaultCertificate = kv.GetCertificateAsync(certIdentifier).GetAwaiter().GetResult();

This also works - I can access the keyvault with my app identity, and I can fetch the certificate from the keyvault, and the X.509 thumbprint is valid - but now this is a CertificateBundle from the Microsoft.Azure.KeyVault.Models namespace - how do I "convert" that into a "regular" X509Certificate2 object so that I can use it for the REST call?

I've tried several things, for instance

X509Certificate2 x509 = new X509Certificate2(vaultCertificate.Cer);

but nothing works - when I place my REST call, I get a HTTP 403 - Forbidden error back....

What am I missing?? How can I fetch a certificate from Azure Keyvault in a format that can be used to authenticate in a subsequent REST call??

2 Answers

Of course - right after I posted this question, I stumbled across the solution....

This blog post by Matt Small explains it in great details (and with really nice, and working code examples, too).

Basically, in order to use the certificate for authentication, you need to have the private key, too - and when you do a GetCertificateAsync, you only get back the public information of the certificate.

You need to fetch the certificate as a secret and then base64 decode it - then you get all the necessary bits and the REST call works.

GOSH !! Why is this so darn convoluted?? Why can't there just be a includePrivate: bool parameter on the GetCertificateAsync call to tell Keyvault if you need just the public, or public and private parts of your certificate?

You store a certificate - but to get it, you need to fetch a secret .... that's just all wrong, and violates the Basic Principle Of Least Surprise! MS, if you're listening - this API really needs a bit more work to make it more approachable to novice Azure devs!!

And PS: this of course also means the user/identity you're using to access the keyvault now needs permissions to read secrets - not just certificates.....

Try the following code:

var cert = kvc.GetCertificateAsync(baseUrl, "Demo").ConfigureAwait(false).GetAwaiter().GetResult();
var cert_content = cert.Cer;
X509Certificate2 x509 = new X509Certificate2(cert_content);

You can easily get the raw bytes of your certificate from the CertificateBundle, and then use the raw bytes to create your X509Certificate2 instance.

Related