While developing my latest course IdentityServer in Production, I discovered that there was a lack of support for storing the data protection key ring in Azure Key Vault. In this blog post I will explain how I implemented this support.
What is the purpose of the data protection API?
The ASP.NET Core data protection API is at the heart of the ASP.NET Core security stack:
The data protection API is in charge of things like:
Encrypting and securing the session cookie that, for example, contains details about the logged in user.
Securing the antiforgery cookie that is used for CSRF protection
The illustration below shows its role in ASP.NET Core:
The keys and the key ring
To do its job, the data protection API uses encryption keys and the keys it creates are stored in a key ring. This key ring contains both expired keys and the current key.
The key ring can out-of-the-box be stored in many places, including:
Azure blob storage
Entity Framework Core
To further improve security, the raw keys inside the key ring should also be protected when the keys are at rest. This protection is typically implemented using one of the following approaches:
Encryption key stored in Azure Key Vault
Windows DPAPI (Data Protection Application Programming Interface)
What does a key look like in the key ring?
A key is represented as a XML document and a sample key that is protected with a separate encrypted key can look like this:
<key id="f3fc6158-523e-40c5-8bcf-28e7f115b36c" version="1">
<descriptor deserializertype="Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AuthenticatedEncryptorDescriptorDeserializer, Microsoft.AspNetCore.DataProtection, Version=220.127.116.11, Culture=neutral, PublicKeyToken=adb9793829ddae60">
,Microsoft.AspNetCore.DataProtection.AzureKeyVault, Version=18.104.22.168, Culture=neutral,
<!-- This key is encrypted with Azure KeyVault. →
(The key and value is truncated for clarity)
What happens if I don’t configure the data protection service in ASP.NET Core?
Your site will work perfectly fine, users can login and everything is automatically secured. By default at startup, a local key ring is automatically created for you and stored on your local filesystem.
But the problem is when you redeploy your application. For example, if you deploy using containers, the locally created key ring is lost and a new one is created in its place when the new container is deployed.
Then, when your users continue to visit your site, all the existing session cookies are no longer valid. This is because the new encryption key used is no longer the same as the one used to create them. The result is that all logged in users are logged out and they need to sign in again. Resulting in unhappy users!
Existing antiforgery tokens will also be invalidated and that can result in users’ form posts failing.
Where to store the key ring?
So clearly we need to persist the key ring in a more permanent place outside your application, that can survive redeployments. Where should we store it?
When I design systems, I really like to focus on reducing complexity and having to store the key ring in one place and the key encryption key in a different place felt a bit overkill for me. So, why not store the key ring in Azure Key Vault as well?
Now the problem is that there are currently no available solutions (that I could find) to store the key ring in Azure Key Vault, so I decided to create one. The main reason I created it was because I wanted to use it in my IdentityServer in Production and also learn more about Azure Key Vault.
The goal is to store both the key ring and the key encryption key in the same vault, as this illustration shows:
Storing the key ring in Azure Key Vault
The key-ring is actually a plain XML document, so all we need to do is to store it as a secret in Azure Key Vault. To avoid any encoding issues we first do a base-64 encoding of it before we send it to AKV. You can try without this step, but I haven’t done this.
Limitations! Oh no!
However, Azure Key Vault has one limitation, which is that the maximum size of a secret value is limited to 25Kb.This means that we can in practice only store about 10-11 keys in our key ring without hitting that limit. However the data protection API only does a key rotation every 90 days by default, so if you only use it for securing the session and antiforgery token then you just need to keep a few of the most recent keys around.
Beware! If you intend to encrypt data that are stored long term (for example sensitive or customer data in your database), then you need to store the key ring in some other place because you need to keep all the old keys to be able to decrypt the data.
To start using my implementation you just need to add the following to your Startup.cs configure method:
The PersistKeysToAzureKeyVault extension method is used to add and configure our storage provider. ProtectKeysWithAzureKeyVault is used to encrypt the keys at rest using an encryption key stored in Azure Key Vault.
You can find the full source code and a sample demonstration application in the AzureKeyVaultKeyRingRepository repository.
IdentityServer In Production
This blog post was developed as part of my research for my new training class and if you are curious about this course, then you can read more about it here.
About the author
When Tore Nestenius is not busy answering IdentityServer related questions on StackOverflow, he works as a trainer, teaching topics like .NET, C#, Web Security, Software Architecture, DDD/CQRS/Event Sourcing, IdentityServer and OpenID Connect, and more.
By Tore Nestenius