openiddict-documentation/configuration/encryption-and-signing-credentials.md
2022-01-07 18:10:45 +01:00

171 lines
8.2 KiB
Markdown

# Encryption and signing credentials
To protect the tokens it issues, OpenIddict uses 2 types of credentials:
- **Signing credentials are used to protect against tampering**. They can be either asymmetric (e.g a RSA or ECDSA key) or symmetric.
- **Encryption credentials are used to ensure the content of tokens cannot be read by malicious parties**. They can be either asymmetric (e.g a RSA key) or symmetric.
> [!NOTE]
> Tokens generated using the opt-in ASP.NET Core Data Protection integration rely on their own key ring, distinct from the credentials discussed in this documentation.
> For more information about Data Protection, visit [ASP.NET Core Data Protection](https://docs.microsoft.com/en-us/aspnet/core/security/data-protection/introduction).
## Registering credentials in the authorization server options
OpenIddict allows registering one or multiple keys (raw keys or embedded in X.509 certificates).
> [!NOTE]
> When multiple keys/certificates are registered (which can be useful to implement keys rotation), OpenIddict chooses the most appropriate key based on the following algorithm:
> - Symmetric keys are always chosen first, except for identity tokens, that can only be signed using asymmetric keys.
> - Asymmetric keys embedded in X.509 certificates are ordered based on the `NotAfter` and `NotBefore` dates (certificates that are not yet valid
> are not used by OpenIddict and certificates with the furthest expiration date are always preferred).
> - X.509 certificates are always preferred to raw RSA/ECDSA keys.
### Registering an ephemeral key
For development purposes, an ephemeral key - that is not persisted or shared across instances - can be used to sign or encrypt tokens:
```csharp
services.AddOpenIddict()
.AddServer(options =>
{
options.AddEphemeralEncryptionKey()
.AddEphemeralSigningKey();
});
```
> [!NOTE]
> `options.AddEphemeralEncryptionKey()` generates an asymmetric RSA key which is not directly used as-is to encrypt the tokens but is used to encrypt an
> intermediate *per-token* symmetric key with which the token content is first encrypted using [AES](https://datatracker.ietf.org/doc/html/rfc7518#section-5.2.6).
> For more information about this mechanism, read [Key Encryption with RSAES OAEP](https://datatracker.ietf.org/doc/html/rfc7518#section-4.3).
### Registering a development certificate
For development purposes, a certificate can be generated and stored by OpenIddict in the certificates store of the user account running the OpenIddict server feature.
Unlike ephemeral keys, development certificates are persisted - but not shared across instances - and will be reused when the application host is restarted.
```csharp
services.AddOpenIddict()
.AddServer(options =>
{
options.AddDevelopmentEncryptionCertificate()
.AddDevelopmentSigningCertificate();
});
```
> [!WARNING]
> This feature is not available on .NET Framework 4.6.1: calling `options.AddDevelopmentEncryptionCertificate()` or `options.AddDevelopmentSigningCertificate()`
> will result in a `PlatformNotSupportedException` being thrown at runtime if no valid development certificate can be found and a new one must be generated.
### Registering a key
To register a signing or encryption key, an instance of a `SecurityKey` - typically a `SymmetricSecurityKey` or a `RsaSecurityKey` -
can be provided to the `options.AddSigningKey()`/`options.AddEncryptionKey()` methods:
```csharp
services.AddOpenIddict()
.AddServer(options =>
{
options.AddEncryptionKey(new SymmetricSecurityKey(
Convert.FromBase64String("DRjd/GnduI3Efzen9V9BvbNUfc/VKgXltV7Kbk9sMkY=")));
});
```
> [!NOTE]
> While signing keys can be either symmetric or asymmetric, OpenIddict requires registering at least one asymmetric key to sign identity tokens.
> If both an asymmetric and a symmetric signing key are registered, the symmetric key will always be preferred when protecting access tokens,
> authorization codes or refresh tokens, while the asymmetric key will be used to sign identity tokens, that are meant to be publicly validated.
### Registering a certificate (recommended for production-ready scenarios)
To register a signing or encryption certificate, the `options.AddSigningCertificate()`/`options.AddEncryptionCertificate()` methods can be called
with an instance of `X509Certificate2`. Alternatively, a unique `thumbprint` identifying the certificate in the machine or user certificate store
of the operating system can also be provided.
**In production, it is recommended to use two RSA certificates, distinct from the certificate(s) used for HTTPS: one for encryption, one for signing**.
Certificates can be generated and self-signed locally using the .NET Core `CertificateRequest` API:
```csharp
using var algorithm = RSA.Create(keySizeInBits: 2048);
var subject = new X500DistinguishedName("CN=Fabrikam Encryption Certificate");
var request = new CertificateRequest(subject, algorithm, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
request.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.KeyEncipherment, critical: true));
var certificate = request.CreateSelfSigned(DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddYears(2));
var data = certificate.Export(X509ContentType.Pfx, string.Empty);
```
```csharp
using var algorithm = RSA.Create(keySizeInBits: 2048);
var subject = new X500DistinguishedName("CN=Fabrikam Signing Certificate");
var request = new CertificateRequest(subject, algorithm, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
request.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature, critical: true));
var certificate = request.CreateSelfSigned(DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddYears(2));
var data = certificate.Export(X509ContentType.Pfx, string.Empty);
```
The best place to store your certificates will depend on your host:
- For IIS applications, storing the certificates in the machine store is the recommended option.
- On Azure, certificates can be uploaded and exposed to Azure App Services applications using the special `WEBSITE_LOAD_CERTIFICATES` flag.
For more information, visit https://docs.microsoft.com/en-us/azure/app-service/configure-ssl-certificate-in-code
## Importing credentials in the API/resource validation options
### Using the `options.UseLocalServer()` integration
When the API and the authorization server are part of the same project, both the signing and
encryption credentials can be easily imported by calling `options.UseLocalServer()`:
```csharp
services.AddOpenIddict()
.AddValidation(options =>
{
options.UseLocalServer();
});
```
### Using OpenID Connect discovery (asymmetric signing keys only)
When the API and the authorization server are hosted in different applications,
[standard OpenID Connect discovery](https://openid.net/specs/openid-connect-discovery-1_0.html) can be used to automatically import asymmetric signing keys:
```csharp
services.AddOpenIddict()
.AddValidation(options =>
{
options.SetIssuer("https://localhost:44319/");
options.UseSystemNetHttp();
});
```
### Registering a symmetric signing key in the token validation parameters
Unlike asymmetric signing keys, symmetric keys - used with HMAC-based algorithms like [HS256](https://datatracker.ietf.org/doc/html/rfc7518#section-3.2) - cannot
be safely exposed by an OpenID Connect discovery endpoint. As such, they can't be automatically imported by the OpenIddict validation handler.
For applications that require using a symmetric signing key, the advanced configuration APIs can be used to register it in the token validation options:
```csharp
services.AddOpenIddict()
.AddValidation(options =>
{
options.Configure(options => options.TokenValidationParameters.IssuerSigningKey =
new SymmetricSecurityKey(
Convert.FromBase64String("DRjd/GnduI3Efzen9V9BvbNUfc/VKgXltV7Kbk9sMkY=")));
});
```
### Registering an encryption key or certificate
To import an encryption key/certificate, the same overloads as the ones exposed by the OpenIddict server feature can be used:
```csharp
services.AddOpenIddict()
.AddValidation(options =>
{
options.AddEncryptionCertificate("b82f36609cdaff9a95de60e8d5ac774b2e496c4b");
});
```