Key Management for IdentityServer
This Key Management component for IdentityServer provides a solution for data protection and signing key rotation - ready for load balanced environments.
The Key Management component has the following features:
- IdentityServer key generation, storage, and rotation
- ASP.NET Data Protection key storage
IdentityServer key generation, storage, and rotation
IdentityServer creates tokens, and those tokens must be signed by a key. IdentityServer can be configured with an explicit signing key (i.e., an RSA key often contained in a X509 certificate). Additionally, this explicit signing key must be stored somewhere, for example, in the Windows certificate store, from a directory mounted into a container, or configured in the Azure portal. A good security practice is to rotate this key at some interval such as every 90 days. Doing so is a very manual process and requires more planning than just replacing the existing key. As such, the purpose of the IdentityServer key generation, storage, and rotation feature is to automate all of these tasks.
The IdentityServer key generation, storage, and rotation feature will automatically generate signing keys, store them in a store you provide, and will automatically rotate those keys on an interveral you can configure. The storage is extensible, but an EntityFramework Core based implementation is provided by default.
To use the IdentityServer key generation, storage, and rotation feature, the AddSigningKeyManagement
extension method is provided, and to persist the keys to an EF Core provider the PersistKeysToDatabase
extension method is used.
var connectionString = "...";
services.AddIdentityServer()
.AddSigningKeyManagement()
.PersistKeysToDatabase(new DatabaseKeyManagementOptions {
ConfigureDbContext =
efOptions => efOptions.UseSqlServer(connectionString)
});
Much like the configuration above for the ASP.NET Data Protection key storage feature, the DatabaseKeyManagementOptions
provides a ConfigureDbContext
property which is the lambda expression used to configure the EF Core database provider and connection string. Any EF Core compatible database provider can be used (SQLServer is used in the above example using the UseSqlServer
API).
When keys are needed by IdentityServer, the IdentityServer key generation, storage, and rotation feature will consult the store and use what it finds, or it will create a key if the store is empty.
KeyManagerOptions
An overload of AddSigningKeyManagement
exists that accepts a lambda expression to configure the KeyManagerOptions
. An example of how to configure the options:
services.AddIdentityServer()
.AddSigningKeyManagement(options => {
options.KeyExpiration = TimeSpan.FromDays(180);
});
The KeyManagerOptions
class provides properties to control the key management and rotation feature.
These properties are:
- KeyIdSize (
int
): Size, in bits, of key identifiers. Defaults to 128. - KeySize (
int
): Size, in bits, of RSA keys. Defaults to 2048. - KeyType (
enum
): Key type to create.RSA
will use RSA keys.X509
will use X509 certificates. Defaults toRSA
. - InitializationDuration (
TimeSpan
): When no keys have been created yet, this is the window of time considered to be an initialization period to allow all servers to synchronize if the keys are being created for the first time. Defaults to 5 minutes. - InitializationSynchronizationDelay (
TimeSpan
): Delay used when re-loading from the store when the initialization period. It allows other servers more time to write new keys so other servers can include them. Defaults to 5 seconds. - InitializationKeyCacheDuration (
TimeSpan
): Cache duration when within the initialization period. Defaults to 1 minute. - KeyCacheDuration (
TimeSpan
): When in normal operation, duration to cache keys from store. Defaults to 24 hours. - KeyActivationDelay (
TimeSpan
): Time expected to propigate new keys to all servers, and time expected all clients to refresh discovery. Defaults to 14 days. - KeyExpiration (
TimeSpan
): Age at which keys will no longer be used for signing, but will still be used in discovery for validation. Defaults to 90 days. - KeyRetirement (
TimeSpan
): Age at which keys will no longer be used in discovery. they can be deleted at this point. Defaults to 180 days. - SigningAlgorithm (
RsaSigningAlgorithm
): The signing algorithm to use. Defaults to RS256. - DeleteRetiredKeys (
bool
): Automatically delete retired keys. Defaults tofalse
.
Key Storage and Caching
By default, every time a key is needed by IdentityServer, the store is consulted.
Given that keys do not change frequently, caching the keys from the store is recommended (at least in production), and a default implementation is provided with the EnableInMemoryCaching
extension method, for example:
var connectionString = "...";
services.AddIdentityServer()
.AddSigningKeyManagement()
.PersistKeysToDatabase(new DatabaseKeyManagementOptions {
ConfigureDbContext =
efOptions => efOptions.UseSqlServer(connectionString)
})
.EnableInMemoryCaching();
Protecting signing keys at rest
Much like the Data Protection keys, when the signing keys are stored in the database, by default they are in plaintext (unencrypted). This means any person with read-only access to the database can obtain the keys and forge tokens as if they were from IdentityServer.
To add additional security the signing keys can be encrypted while at rest in the database.
Given that Data Protection exists to protect other sensitive data, we can use it to protect the signing keys at rest in the database. To enable this, the ProtectKeysWithDataProtection
extension method can be used:
var connectionString = "...";
services.AddIdentityServer()
.AddSigningKeyManagement()
.ProtectKeysWithDataProtection()
.PersistKeysToDatabase(new DatabaseKeyManagementOptions {
ConfigureDbContext =
efOptions => efOptions.UseSqlServer(connectionString)
})
.EnableInMemoryCaching();
ASP.NET Data Protection key storage
ASP.NET applications require data protection to protect values that are sent to the browser (such as cookies and anti-forgery tokens). Data protection requires keys to protect and unprotect the values. These keys are automatically generated by ASP.NET Core, but for a load balanced scenario you must decide where to store them.
The ASP.NET Data Protection key storage feature provides an EntityFramework Core-based implementation for storing these keys. It is activated using the PersistKeysToDatabase
extension method:
var connectionString = "...";
var dataProtection = services.AddDataProtection()
.PersistKeysToDatabase(new DatabaseKeyManagementOptions
{
ConfigureDbContext =
dbOptions => dbOptions.UseSqlServer(connectionString)
});
The DatabaseKeyManagementOptions
class provides a ConfigureDbContext
property which is the lambda expression used to configure the EF Core database provider and connection string. Any EF Core compatible database provider can be used (SQLServer is used in the above example using the UseSqlServer
API).
Protecting the data protection keys at rest
When the ASP.NET Data Protection keys are stored in the database, by default they are in plaintext (unencrypted). This means any person with read-only access to the database can obtain the keys and forge values for anything that relies upon data protection for protection (e.g. cookies and anti-forgery values).
To add additional security the data protection keys can be encrypted while at rest in the database. ASP.NET Core Data Protection allows for configuring a certificate to act as a "master key" that is used to protect all of the data protection keys. This certificate then becomes the single secret that will protect all others, and in load balanced environments the same certificate will need to be made available to all servers.
This is configurable with the ProtectKeysWithCertificate
extension method.
var connectionString = "...";
var certName = "...";
var cert = X509.CurrentUser
.My.SubjectDistinguishedName
.Find(certName, false)
.FirstOrDefault();
var dataProtection = services
.AddDataProtection()
.PersistKeysToDatabase(new DatabaseKeyManagementOptions
{
ConfigureDbContext =
dbOptions => dbOptions.UseSqlServer(connectionString)
})
ProtectKeysWithCertificate(cert);
While a certificate is used to hold this "master key", it does not need to be issued from a public CA (it can be self-issued/self-signed), its expiration is not used or validated, and it simply acts as a container for the key material used to protect the data protection keys. As such, if this "master key" is lost then so is all the data protected by it.