Azure’s Key vault is a great secret store with excellent support in .NET (obviously!). But I recently ran into an issue that sent me in circles trying to work out how to load certificates that have been loaded into Key Vault, from .NET. Or more specifically, there is a bunch of gotchas when loading certificates into a .NET/.NET Core application running in Azure App Service. Which given both are Azure services, you’ll probably run into one time or another.
But first, let’s just talk about the code to load a certificate from Key vault in general.
C# Code To Load Certificates From Keyvault
If you’ve already got a Key vault instance (Or have newly created one), you’ll need to ensure that you, as in your login to Azure, has been added to the access policy for the Key vault.
A quick note on Access Policies in general. They can become pretty complex and Key vault recently added the ability to use role based authentication and a few other nifty features. You can even authenticate against KV using a local certificate on the machine. I’m going to describe how I generally use it for my own projects and other small teams, which involves Managed Identity, but if this doesn’t work for you, you’ll need to investigate the best way of authenticating individuals against Key vault.
Back to the guide. If you created the Key vault yourself, then generally speaking you are automatically added to the access policy. But you can check by looking at the Key vault instance, and checking Access Policies under Settings and ensuring that your user has access.
Next, on your local machine, you need to login to Azure because the .NET Code actually uses the managed identity to gain access to Keyvault. To do that we need to run a couple of Powershell commands.
First, run the command
This will pop up a browser window asking you to login to Azure. Complete this, and your powershell window should update with the following :
You have logged in. Now let us find all the subscriptions to which you have access... [.. All your subscriptions listed here..]
Now if you only have one subscription, you’re good to go. If you have multiple then you need to do something else :
az account set --subscription "YOUR SUBSCRIPTION NAME THAT HAS KEYVAULT"
The reason you need to do this is once logged into Azure, you only have access to one subscription at a time. If you have multiple subscriptions you need to set the subscription that contains your keyvault instance as your “current” one.
Finally onto the C# code.
Now obviously you’ll want to turn this into a helpful service with a re-useable method, but the actual C# code is simple. Here it is in one block :
var _keyVaultName = $"https://YOURKEYVAULTNAME.vault.azure.net/"; var secretName = "YOURCERTIFICATENAME"; var azureServiceTokenProvider = new AzureServiceTokenProvider(); var _client = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback)); var secret = await client.GetSecretAsync(_keyVaultName, secretName); var privateKeyBytes = Convert.FromBase64String(secret); var certificate = new X509Certificate2(privateKeyBytes, string.Empty);
Again, people often leave comments like “Why don’t you load your keyvault name from App Settings?”. Well I do! But when I’m giving example code I want to break it down to the simplest possible example so that you don’t have to deconstruct it, and rebuild it to suit your own application.
With that out of the way, notice that when we call Key Vault, we don’t actually call “GetCertificate”. We just ask to get a secret. If that secret is a text secret, then it will come through as plain text. If it’s a certificate, then actually it will be a Base64 string, which we can then turn into a certificate.
Also note that we aren’t providing any sort of “authentication” to this code, that’s because it uses our managed identity to talk to Key vault.
And we are done! This is all the C# code you need. Now if you’re hosting on Azure App Service.. then that’s a different story.
Getting It Working On Azure App Service
Now I thought that deploying everything to an Azure App Service would be the easy part. But as it turns out, it’s a minefield of gotchas.
The first thing is that you need to turn on Managed Identity for the App Service. You can do this by going to your App Service, then Settings => Identity. Turn on System Assigned identity and save.
Now when you go back to your Key vault, go to Access Policies and search for the name of your App Service. Then you can add permissions for your App Service as if it was an actual user getting permissions.
So if you’re loading certificates, there is 3 main gotchas, and all 3 will generate this error :
Internal.Cryptography.CryptoThrowHelper+WindowsCryptographicException: The system cannot find the file specified.
App Service Plan Level
You must be on a Basic plan or above for the App Service. It cannot be a shared instance (Either F1 or D1). The reason behind this is that behind the scenes, on windows, there is some “User Profile” gunk that needs to be loaded for certificates of any type to be loaded. This apparently does not work on shared plans.
Extra Application Setting
You must add an Application setting on the App Service called “WEBSITE_LOAD_USER_PROFILE” and set this to 1. This is similar to the above and is about the gunk that windows needs, but is apparently not loaded by default in Azure.
Extra Certificate Flags In C#
In your C# code, the only thing that seemed to work for me was adding a couple of extra flags when loading your certificate from the byte array. So we change this :
var certificate = new X509Certificate2(privateKeyBytes, string.Empty);
To this :
var certificate = new X509Certificate2(privateKeyBytes, string.Empty, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable);
Finally, with all of these extras, you should be able to load a certificate from Key vault into your Azure App Service!
If you get the following error :
Microsoft.Azure.KeyVault.Models.KeyVaultErrorException: Access denied
In almost all cases, the managed identity you are running under (either locally or in Azure App Service) does not have access to the Key vault instance. If you’re getting this when trying to develop locally, generally I find it’s because you’ve selected the wrong subscription after using az login. If you’re running this in an App Service, I find it’s typically because you haven’t set up the managed identity between the App Service and Key vault.