Browse Source

Revamp the encryption/signing credentials registration mechanism

pull/1027/head
Kévin Chalet 6 years ago
parent
commit
0f42586959
  1. 172
      src/OpenIddict.Server/OpenIddictServerBuilder.cs
  2. 49
      src/OpenIddict.Server/OpenIddictServerConfiguration.cs
  3. 37
      src/OpenIddict.Server/OpenIddictServerHandlers.cs
  4. 33
      src/OpenIddict.Server/OpenIddictServerOptions.cs
  5. 5
      src/OpenIddict.Validation.ServerIntegration/OpenIddictValidationServerIntegrationConfiguration.cs
  6. 41
      src/OpenIddict.Validation/OpenIddictValidationBuilder.cs
  7. 12
      src/OpenIddict.Validation/OpenIddictValidationConfiguration.cs
  8. 12
      src/OpenIddict.Validation/OpenIddictValidationOptions.cs
  9. 396
      test/OpenIddict.Server.Tests/OpenIddictServerBuilderTests.cs

172
src/OpenIddict.Server/OpenIddictServerBuilder.cs

@ -141,7 +141,7 @@ namespace Microsoft.Extensions.DependencyInjection
=> Configure(options => options.AcceptAnonymousClients = true);
/// <summary>
/// Registers the <see cref="EncryptingCredentials"/> used to encrypt the tokens issued by OpenIddict.
/// Registers encryption credentials.
/// </summary>
/// <param name="credentials">The encrypting credentials.</param>
/// <returns>The <see cref="OpenIddictServerBuilder"/>.</returns>
@ -156,7 +156,7 @@ namespace Microsoft.Extensions.DependencyInjection
}
/// <summary>
/// Registers a <see cref="SecurityKey"/> used to encrypt the access tokens issued by OpenIddict.
/// Registers an encryption key.
/// </summary>
/// <param name="key">The security key.</param>
/// <returns>The <see cref="OpenIddictServerBuilder"/>.</returns>
@ -193,16 +193,14 @@ namespace Microsoft.Extensions.DependencyInjection
}
/// <summary>
/// Registers (and generates if necessary) a user-specific development
/// certificate used to encrypt the tokens issued by OpenIddict.
/// Registers (and generates if necessary) a user-specific development encryption certificate.
/// </summary>
/// <returns>The <see cref="OpenIddictServerBuilder"/>.</returns>
public OpenIddictServerBuilder AddDevelopmentEncryptionCertificate()
=> AddDevelopmentEncryptionCertificate(new X500DistinguishedName("CN=OpenIddict Server Encryption Certificate"));
/// <summary>
/// Registers (and generates if necessary) a user-specific development
/// certificate used to encrypt the tokens issued by OpenIddict.
/// Registers (and generates if necessary) a user-specific development encryption certificate.
/// </summary>
/// <param name="subject">The subject name associated with the certificate.</param>
/// <returns>The <see cref="OpenIddictServerBuilder"/>.</returns>
@ -284,8 +282,8 @@ namespace Microsoft.Extensions.DependencyInjection
}
/// <summary>
/// Registers a new ephemeral key used to encrypt the tokens issued by OpenIddict: the key
/// is discarded when the application shuts down and tokens encrypted using this key are
/// Registers a new ephemeral encryption key. Ephemeral encryption keys are automatically
/// discarded when the application shuts down and payloads encrypted using this key are
/// automatically invalidated. This method should only be used during development.
/// On production, using a X.509 certificate stored in the machine store is recommended.
/// </summary>
@ -294,8 +292,8 @@ namespace Microsoft.Extensions.DependencyInjection
=> AddEphemeralEncryptionKey(SecurityAlgorithms.RsaOAEP);
/// <summary>
/// Registers a new ephemeral key used to encrypt the tokens issued by OpenIddict: the key
/// is discarded when the application shuts down and tokens encrypted using this key are
/// Registers a new ephemeral encryption key. Ephemeral encryption keys are automatically
/// discarded when the application shuts down and payloads encrypted using this key are
/// automatically invalidated. This method should only be used during development.
/// On production, using a X.509 certificate stored in the machine store is recommended.
/// </summary>
@ -370,9 +368,9 @@ namespace Microsoft.Extensions.DependencyInjection
}
/// <summary>
/// Registers a <see cref="X509Certificate2"/> that is used to encrypt the tokens issued by OpenIddict.
/// Registers an encryption certificate.
/// </summary>
/// <param name="certificate">The certificate used to encrypt the security tokens issued by the server.</param>
/// <param name="certificate">The encryption certificate.</param>
/// <returns>The <see cref="OpenIddictServerBuilder"/>.</returns>
public OpenIddictServerBuilder AddEncryptionCertificate([NotNull] X509Certificate2 certificate)
{
@ -381,14 +379,15 @@ namespace Microsoft.Extensions.DependencyInjection
throw new ArgumentNullException(nameof(certificate));
}
if (certificate.NotBefore > DateTime.Now)
// If the certificate is a X.509v3 certificate that specifies at least one
// key usage, ensure that the certificate key can be used for key encryption.
if (certificate.Version >= 3)
{
throw new InvalidOperationException("The specified certificate is not yet valid.");
}
if (certificate.NotAfter < DateTime.Now)
{
throw new InvalidOperationException("The specified certificate is no longer valid.");
var extensions = certificate.Extensions.OfType<X509KeyUsageExtension>().ToList();
if (extensions.Count != 0 && !extensions.Any(extension => extension.KeyUsages.HasFlag(X509KeyUsageFlags.KeyEncipherment)))
{
throw new InvalidOperationException("The specified certificate is not a key encryption certificate.");
}
}
if (!certificate.HasPrivateKey)
@ -400,8 +399,7 @@ namespace Microsoft.Extensions.DependencyInjection
}
/// <summary>
/// Registers a <see cref="X509Certificate2"/> retrieved from an
/// embedded resource and used to encrypt the tokens issued by OpenIddict.
/// Registers an encryption certificate retrieved from an embedded resource.
/// </summary>
/// <param name="assembly">The assembly containing the certificate.</param>
/// <param name="resource">The name of the embedded resource.</param>
@ -419,8 +417,7 @@ namespace Microsoft.Extensions.DependencyInjection
#endif
/// <summary>
/// Registers a <see cref="X509Certificate2"/> retrieved from an
/// embedded resource and used to encrypt the tokens issued by OpenIddict.
/// Registers an encryption certificate retrieved from an embedded resource.
/// </summary>
/// <param name="assembly">The assembly containing the certificate.</param>
/// <param name="resource">The name of the embedded resource.</param>
@ -456,8 +453,7 @@ namespace Microsoft.Extensions.DependencyInjection
}
/// <summary>
/// Registers a <see cref="X509Certificate2"/> extracted from a
/// stream and used to encrypt the tokens issued by OpenIddict.
/// Registers an encryption certificate extracted from a stream.
/// </summary>
/// <param name="stream">The stream containing the certificate.</param>
/// <param name="password">The password used to open the certificate.</param>
@ -473,8 +469,7 @@ namespace Microsoft.Extensions.DependencyInjection
#endif
/// <summary>
/// Registers a <see cref="X509Certificate2"/> extracted from a
/// stream and used to encrypt the tokens issued by OpenIddict.
/// Registers an encryption certificate extracted from a stream.
/// </summary>
/// <param name="stream">The stream containing the certificate.</param>
/// <param name="password">The password used to open the certificate.</param>
@ -505,8 +500,7 @@ namespace Microsoft.Extensions.DependencyInjection
}
/// <summary>
/// Registers a <see cref="X509Certificate2"/> retrieved from the X.509
/// machine store and used to encrypt the tokens issued by OpenIddict.
/// Registers an encryption certificate retrieved from the X.509 user or machine store.
/// </summary>
/// <param name="thumbprint">The thumbprint of the certificate used to identify it in the X.509 store.</param>
/// <returns>The <see cref="OpenIddictServerBuilder"/>.</returns>
@ -537,8 +531,7 @@ namespace Microsoft.Extensions.DependencyInjection
}
/// <summary>
/// Registers a <see cref="X509Certificate2"/> retrieved from the given
/// X.509 store and used to encrypt the tokens issued by OpenIddict.
/// Registers an encryption certificate retrieved from the specified X.509 store.
/// </summary>
/// <param name="thumbprint">The thumbprint of the certificate used to identify it in the X.509 store.</param>
/// <param name="name">The name of the X.509 store.</param>
@ -568,8 +561,7 @@ namespace Microsoft.Extensions.DependencyInjection
}
/// <summary>
/// Registers the <see cref="SigningCredentials"/> used to sign the tokens issued by OpenIddict.
/// Note: using <see cref="RsaSecurityKey"/> asymmetric keys is recommended on production.
/// Registers signing credentials.
/// </summary>
/// <param name="credentials">The signing credentials.</param>
/// <returns>The <see cref="OpenIddictServerBuilder"/>.</returns>
@ -584,8 +576,7 @@ namespace Microsoft.Extensions.DependencyInjection
}
/// <summary>
/// Registers a <see cref="SecurityKey"/> used to sign the tokens issued by OpenIddict.
/// Note: using <see cref="RsaSecurityKey"/> asymmetric keys is recommended on production.
/// Registers a signing key.
/// </summary>
/// <param name="key">The security key.</param>
/// <returns>The <see cref="OpenIddictServerBuilder"/>.</returns>
@ -645,16 +636,14 @@ namespace Microsoft.Extensions.DependencyInjection
}
/// <summary>
/// Registers (and generates if necessary) a user-specific development
/// certificate used to sign the tokens issued by OpenIddict.
/// Registers (and generates if necessary) a user-specific development signing certificate.
/// </summary>
/// <returns>The <see cref="OpenIddictServerBuilder"/>.</returns>
public OpenIddictServerBuilder AddDevelopmentSigningCertificate()
=> AddDevelopmentSigningCertificate(new X500DistinguishedName("CN=OpenIddict Server Signing Certificate"));
/// <summary>
/// Registers (and generates if necessary) a user-specific development
/// certificate used to sign the tokens issued by OpenIddict.
/// Registers (and generates if necessary) a user-specific development signing certificate.
/// </summary>
/// <param name="subject">The subject name associated with the certificate.</param>
/// <returns>The <see cref="OpenIddictServerBuilder"/>.</returns>
@ -736,8 +725,8 @@ namespace Microsoft.Extensions.DependencyInjection
}
/// <summary>
/// Registers a new ephemeral key used to sign the tokens issued by OpenIddict: the key
/// is discarded when the application shuts down and tokens signed using this key are
/// Registers a new ephemeral signing key. Ephemeral signing keys are automatically
/// discarded when the application shuts down and payloads signed using this key are
/// automatically invalidated. This method should only be used during development.
/// On production, using a X.509 certificate stored in the machine store is recommended.
/// </summary>
@ -746,8 +735,8 @@ namespace Microsoft.Extensions.DependencyInjection
=> AddEphemeralSigningKey(SecurityAlgorithms.RsaSha256);
/// <summary>
/// Registers a new ephemeral key used to sign the tokens issued by OpenIddict: the key
/// is discarded when the application shuts down and tokens signed using this key are
/// Registers a new ephemeral signing key. Ephemeral signing keys are automatically
/// discarded when the application shuts down and payloads signed using this key are
/// automatically invalidated. This method should only be used during development.
/// On production, using a X.509 certificate stored in the machine store is recommended.
/// </summary>
@ -841,9 +830,9 @@ namespace Microsoft.Extensions.DependencyInjection
}
/// <summary>
/// Registers a <see cref="X509Certificate2"/> that is used to sign the tokens issued by OpenIddict.
/// Registers a signing certificate.
/// </summary>
/// <param name="certificate">The certificate used to sign the security tokens issued by the server.</param>
/// <param name="certificate">The signing certificate.</param>
/// <returns>The <see cref="OpenIddictServerBuilder"/>.</returns>
public OpenIddictServerBuilder AddSigningCertificate([NotNull] X509Certificate2 certificate)
{
@ -852,14 +841,15 @@ namespace Microsoft.Extensions.DependencyInjection
throw new ArgumentNullException(nameof(certificate));
}
if (certificate.NotBefore > DateTime.Now)
// If the certificate is a X.509v3 certificate that specifies at least
// one key usage, ensure that the certificate key can be used for signing.
if (certificate.Version >= 3)
{
throw new InvalidOperationException("The specified certificate is not yet valid.");
}
if (certificate.NotAfter < DateTime.Now)
{
throw new InvalidOperationException("The specified certificate is no longer valid.");
var extensions = certificate.Extensions.OfType<X509KeyUsageExtension>().ToList();
if (extensions.Count != 0 && !extensions.Any(extension => extension.KeyUsages.HasFlag(X509KeyUsageFlags.DigitalSignature)))
{
throw new InvalidOperationException("The specified certificate is not a signing certificate.");
}
}
if (!certificate.HasPrivateKey)
@ -871,8 +861,7 @@ namespace Microsoft.Extensions.DependencyInjection
}
/// <summary>
/// Registers a <see cref="X509Certificate2"/> retrieved from an
/// embedded resource and used to sign the tokens issued by OpenIddict.
/// Registers a signing certificate retrieved from an embedded resource.
/// </summary>
/// <param name="assembly">The assembly containing the certificate.</param>
/// <param name="resource">The name of the embedded resource.</param>
@ -890,8 +879,7 @@ namespace Microsoft.Extensions.DependencyInjection
#endif
/// <summary>
/// Registers a <see cref="X509Certificate2"/> retrieved from an
/// embedded resource and used to sign the tokens issued by OpenIddict.
/// Registers a signing certificate retrieved from an embedded resource.
/// </summary>
/// <param name="assembly">The assembly containing the certificate.</param>
/// <param name="resource">The name of the embedded resource.</param>
@ -927,8 +915,7 @@ namespace Microsoft.Extensions.DependencyInjection
}
/// <summary>
/// Registers a <see cref="X509Certificate2"/> extracted from a
/// stream and used to sign the tokens issued by OpenIddict.
/// Registers a signing certificate extracted from a stream.
/// </summary>
/// <param name="stream">The stream containing the certificate.</param>
/// <param name="password">The password used to open the certificate.</param>
@ -944,8 +931,7 @@ namespace Microsoft.Extensions.DependencyInjection
#endif
/// <summary>
/// Registers a <see cref="X509Certificate2"/> extracted from a
/// stream and used to sign the tokens issued by OpenIddict.
/// Registers a signing certificate extracted from a stream.
/// </summary>
/// <param name="stream">The stream containing the certificate.</param>
/// <param name="password">The password used to open the certificate.</param>
@ -976,8 +962,7 @@ namespace Microsoft.Extensions.DependencyInjection
}
/// <summary>
/// Registers a <see cref="X509Certificate2"/> retrieved from the X.509
/// machine store and used to sign the tokens issued by OpenIddict.
/// Registers a signing certificate retrieved from the X.509 user or machine store.
/// </summary>
/// <param name="thumbprint">The thumbprint of the certificate used to identify it in the X.509 store.</param>
/// <returns>The <see cref="OpenIddictServerBuilder"/>.</returns>
@ -1008,8 +993,7 @@ namespace Microsoft.Extensions.DependencyInjection
}
/// <summary>
/// Registers a <see cref="X509Certificate2"/> retrieved from the given
/// X.509 store and used to sign the tokens issued by OpenIddict.
/// Registers a signing certificate retrieved from the specified X.509 store.
/// </summary>
/// <param name="thumbprint">The thumbprint of the certificate used to identify it in the X.509 store.</param>
/// <param name="name">The name of the X.509 store.</param>
@ -1144,11 +1128,7 @@ namespace Microsoft.Extensions.DependencyInjection
return Configure(options =>
{
options.AuthorizationEndpointUris.Clear();
foreach (var address in addresses)
{
options.AuthorizationEndpointUris.Add(address);
}
options.AuthorizationEndpointUris.AddRange(addresses);
});
}
@ -1191,11 +1171,7 @@ namespace Microsoft.Extensions.DependencyInjection
return Configure(options =>
{
options.ConfigurationEndpointUris.Clear();
foreach (var address in addresses)
{
options.ConfigurationEndpointUris.Add(address);
}
options.ConfigurationEndpointUris.AddRange(addresses);
});
}
@ -1238,11 +1214,7 @@ namespace Microsoft.Extensions.DependencyInjection
return Configure(options =>
{
options.CryptographyEndpointUris.Clear();
foreach (var address in addresses)
{
options.CryptographyEndpointUris.Add(address);
}
options.CryptographyEndpointUris.AddRange(addresses);
});
}
@ -1285,11 +1257,7 @@ namespace Microsoft.Extensions.DependencyInjection
return Configure(options =>
{
options.DeviceEndpointUris.Clear();
foreach (var address in addresses)
{
options.DeviceEndpointUris.Add(address);
}
options.DeviceEndpointUris.AddRange(addresses);
});
}
@ -1332,11 +1300,7 @@ namespace Microsoft.Extensions.DependencyInjection
return Configure(options =>
{
options.IntrospectionEndpointUris.Clear();
foreach (var address in addresses)
{
options.IntrospectionEndpointUris.Add(address);
}
options.IntrospectionEndpointUris.AddRange(addresses);
});
}
@ -1379,11 +1343,7 @@ namespace Microsoft.Extensions.DependencyInjection
return Configure(options =>
{
options.LogoutEndpointUris.Clear();
foreach (var address in addresses)
{
options.LogoutEndpointUris.Add(address);
}
options.LogoutEndpointUris.AddRange(addresses);
});
}
@ -1426,11 +1386,7 @@ namespace Microsoft.Extensions.DependencyInjection
return Configure(options =>
{
options.RevocationEndpointUris.Clear();
foreach (var address in addresses)
{
options.RevocationEndpointUris.Add(address);
}
options.RevocationEndpointUris.AddRange(addresses);
});
}
@ -1473,11 +1429,7 @@ namespace Microsoft.Extensions.DependencyInjection
return Configure(options =>
{
options.TokenEndpointUris.Clear();
foreach (var address in addresses)
{
options.TokenEndpointUris.Add(address);
}
options.TokenEndpointUris.AddRange(addresses);
});
}
@ -1520,11 +1472,7 @@ namespace Microsoft.Extensions.DependencyInjection
return Configure(options =>
{
options.UserinfoEndpointUris.Clear();
foreach (var address in addresses)
{
options.UserinfoEndpointUris.Add(address);
}
options.UserinfoEndpointUris.AddRange(addresses);
});
}
@ -1567,11 +1515,7 @@ namespace Microsoft.Extensions.DependencyInjection
return Configure(options =>
{
options.VerificationEndpointUris.Clear();
foreach (var address in addresses)
{
options.VerificationEndpointUris.Add(address);
}
options.VerificationEndpointUris.AddRange(addresses);
});
}

49
src/OpenIddict.Server/OpenIddictServerConfiguration.cs

@ -129,6 +129,26 @@ namespace OpenIddict.Server
.ToString());
}
// If all the registered encryption credentials are backed by a X.509 certificate, at least one of them must be valid.
if (options.EncryptionCredentials.All(credentials => credentials.Key is X509SecurityKey x509SecurityKey &&
(x509SecurityKey.Certificate.NotBefore > DateTime.Now || x509SecurityKey.Certificate.NotAfter < DateTime.Now)))
{
throw new InvalidOperationException(new StringBuilder()
.AppendLine("When using X.509 encryption credentials, at least one of the registered certificates must be valid.")
.Append("To use key rollover, register both the new certificate and the old one in the credentials collection.")
.ToString());
}
// If all the registered signing credentials are backed by a X.509 certificate, at least one of them must be valid.
if (options.SigningCredentials.All(credentials => credentials.Key is X509SecurityKey x509SecurityKey &&
(x509SecurityKey.Certificate.NotBefore > DateTime.Now || x509SecurityKey.Certificate.NotAfter < DateTime.Now)))
{
throw new InvalidOperationException(new StringBuilder()
.AppendLine("When using X.509 signing credentials, at least one of the registered certificates must be valid.")
.Append("To use key rollover, register both the new certificate and the old one in the credentials collection.")
.ToString());
}
if (options.EnableDegradedMode)
{
// If the degraded mode was enabled, ensure custom validation handlers
@ -252,6 +272,10 @@ namespace OpenIddict.Server
// Sort the handlers collection using the order associated with each handler.
options.Handlers.Sort((left, right) => left.Order.CompareTo(right.Order));
// Sort the encryption and signing credentials.
options.EncryptionCredentials.Sort((left, right) => Compare(left.Key, right.Key));
options.SigningCredentials.Sort((left, right) => Compare(left.Key, right.Key));
// Automatically add the offline_access scope if the refresh token grant has been enabled.
if (options.GrantTypes.Contains(GrantTypes.RefreshToken))
{
@ -312,6 +336,31 @@ namespace OpenIddict.Server
from credentials in options.EncryptionCredentials
select credentials.Key;
static int Compare(SecurityKey left, SecurityKey right) => (left, right) switch
{
// If the two keys refer to the same instances, return 0.
(SecurityKey first, SecurityKey second) when ReferenceEquals(first, second) => 0,
// If one of the keys is a symmetric key, prefer it to the other one.
(SymmetricSecurityKey _, SymmetricSecurityKey _) => 0,
(SymmetricSecurityKey _, SecurityKey _) => -1,
(SecurityKey _, SymmetricSecurityKey _) => 1,
// If one of the keys is backed by a X.509 certificate, don't prefer it if it's not valid yet.
(X509SecurityKey first, SecurityKey _) when first.Certificate.NotBefore > DateTime.Now => 1,
(SecurityKey _, X509SecurityKey second) when second.Certificate.NotBefore > DateTime.Now => 1,
// If the two keys are backed by a X.509 certificate, prefer the one with the furthest expiration date.
(X509SecurityKey first, X509SecurityKey second) => -first.Certificate.NotAfter.CompareTo(second.Certificate.NotAfter),
// If one of the keys is backed by a X.509 certificate, prefer the X.509 security key.
(X509SecurityKey _, SecurityKey _) => -1,
(SecurityKey _, X509SecurityKey _) => 1,
// If the two keys are not backed by a X.509 certificate, none should be preferred to the other.
(SecurityKey _, SecurityKey _) => 0
};
static string GetKeyIdentifier(SecurityKey key)
{
// When no key identifier can be retrieved from the security keys, a value is automatically

37
src/OpenIddict.Server/OpenIddictServerHandlers.cs

@ -2973,8 +2973,7 @@ namespace OpenIddict.Server
Expires = context.AccessTokenPrincipal.GetExpirationDate()?.UtcDateTime,
IssuedAt = context.AccessTokenPrincipal.GetCreationDate()?.UtcDateTime,
Issuer = context.Issuer?.AbsoluteUri,
SigningCredentials = context.Options.SigningCredentials.FirstOrDefault(credentials =>
credentials.Key is SymmetricSecurityKey) ?? context.Options.SigningCredentials.First(),
SigningCredentials = context.Options.SigningCredentials.First(),
Subject = (ClaimsIdentity) principal.Identity
};
@ -2983,9 +2982,7 @@ namespace OpenIddict.Server
if (!context.Options.DisableAccessTokenEncryption)
{
token = context.Options.JsonWebTokenHandler.EncryptToken(token,
encryptingCredentials: context.Options.EncryptionCredentials.FirstOrDefault(
credentials => credentials.Key is SymmetricSecurityKey) ??
context.Options.EncryptionCredentials.First(),
encryptingCredentials: context.Options.EncryptionCredentials.First(),
additionalHeaderClaims: descriptor.AdditionalHeaderClaims);
}
@ -3251,8 +3248,7 @@ namespace OpenIddict.Server
Expires = context.AuthorizationCodePrincipal.GetExpirationDate()?.UtcDateTime,
IssuedAt = context.AuthorizationCodePrincipal.GetCreationDate()?.UtcDateTime,
Issuer = context.Issuer?.AbsoluteUri,
SigningCredentials = context.Options.SigningCredentials.FirstOrDefault(credentials =>
credentials.Key is SymmetricSecurityKey) ?? context.Options.SigningCredentials.First(),
SigningCredentials = context.Options.SigningCredentials.First(),
Subject = (ClaimsIdentity) principal.Identity
};
@ -3270,9 +3266,7 @@ namespace OpenIddict.Server
var token = context.Options.JsonWebTokenHandler.CreateToken(descriptor);
token = context.Options.JsonWebTokenHandler.EncryptToken(token,
encryptingCredentials: context.Options.EncryptionCredentials.FirstOrDefault(
credentials => credentials.Key is SymmetricSecurityKey) ??
context.Options.EncryptionCredentials.First(),
encryptingCredentials: context.Options.EncryptionCredentials.First(),
additionalHeaderClaims: descriptor.AdditionalHeaderClaims);
context.Response.Code = token;
@ -3541,8 +3535,7 @@ namespace OpenIddict.Server
Expires = context.DeviceCodePrincipal.GetExpirationDate()?.UtcDateTime,
IssuedAt = context.DeviceCodePrincipal.GetCreationDate()?.UtcDateTime,
Issuer = context.Issuer?.AbsoluteUri,
SigningCredentials = context.Options.SigningCredentials.FirstOrDefault(credentials =>
credentials.Key is SymmetricSecurityKey) ?? context.Options.SigningCredentials.First(),
SigningCredentials = context.Options.SigningCredentials.First(),
Subject = (ClaimsIdentity) principal.Identity
};
@ -3560,9 +3553,7 @@ namespace OpenIddict.Server
var token = context.Options.JsonWebTokenHandler.CreateToken(descriptor);
token = context.Options.JsonWebTokenHandler.EncryptToken(token,
encryptingCredentials: context.Options.EncryptionCredentials.FirstOrDefault(
credentials => credentials.Key is SymmetricSecurityKey) ??
context.Options.EncryptionCredentials.First(),
encryptingCredentials: context.Options.EncryptionCredentials.First(),
additionalHeaderClaims: descriptor.AdditionalHeaderClaims);
context.Response.DeviceCode = token;
@ -3929,8 +3920,7 @@ namespace OpenIddict.Server
Expires = context.RefreshTokenPrincipal.GetExpirationDate()?.UtcDateTime,
IssuedAt = context.RefreshTokenPrincipal.GetCreationDate()?.UtcDateTime,
Issuer = context.Issuer?.AbsoluteUri,
SigningCredentials = context.Options.SigningCredentials.FirstOrDefault(credentials =>
credentials.Key is SymmetricSecurityKey) ?? context.Options.SigningCredentials.First(),
SigningCredentials = context.Options.SigningCredentials.First(),
Subject = (ClaimsIdentity) principal.Identity
};
@ -3948,9 +3938,7 @@ namespace OpenIddict.Server
var token = context.Options.JsonWebTokenHandler.CreateToken(descriptor);
token = context.Options.JsonWebTokenHandler.EncryptToken(token,
encryptingCredentials: context.Options.EncryptionCredentials.FirstOrDefault(
credentials => credentials.Key is SymmetricSecurityKey) ??
context.Options.EncryptionCredentials.First(),
encryptingCredentials: context.Options.EncryptionCredentials.First(),
additionalHeaderClaims: descriptor.AdditionalHeaderClaims);
context.Response.RefreshToken = token;
@ -4262,8 +4250,7 @@ namespace OpenIddict.Server
Expires = context.UserCodePrincipal.GetExpirationDate()?.UtcDateTime,
IssuedAt = context.UserCodePrincipal.GetCreationDate()?.UtcDateTime,
Issuer = context.Issuer?.AbsoluteUri,
SigningCredentials = context.Options.SigningCredentials.FirstOrDefault(credentials =>
credentials.Key is SymmetricSecurityKey) ?? context.Options.SigningCredentials.First(),
SigningCredentials = context.Options.SigningCredentials.First(),
Subject = (ClaimsIdentity) principal.Identity
};
@ -4271,9 +4258,7 @@ namespace OpenIddict.Server
var token = context.Options.JsonWebTokenHandler.CreateToken(descriptor);
token = context.Options.JsonWebTokenHandler.EncryptToken(token,
encryptingCredentials: context.Options.EncryptionCredentials.FirstOrDefault(
credentials => credentials.Key is SymmetricSecurityKey) ??
context.Options.EncryptionCredentials.First(),
encryptingCredentials: context.Options.EncryptionCredentials.First(),
additionalHeaderClaims: descriptor.AdditionalHeaderClaims);
context.Response.UserCode = token;
@ -4699,6 +4684,8 @@ namespace OpenIddict.Server
Expires = context.IdentityTokenPrincipal.GetExpirationDate()?.UtcDateTime,
IssuedAt = context.IdentityTokenPrincipal.GetCreationDate()?.UtcDateTime,
Issuer = context.Issuer?.AbsoluteUri,
// Note: unlike other tokens, identity tokens can only be signed using an asymmetric key
// as they are meant to be validated by clients using the public keys exposed by the server.
SigningCredentials = context.Options.SigningCredentials.First(credentials =>
credentials.Key is AsymmetricSecurityKey),
Subject = (ClaimsIdentity) principal.Identity

33
src/OpenIddict.Server/OpenIddictServerOptions.cs

@ -27,18 +27,35 @@ namespace OpenIddict.Server
public Uri Issuer { get; set; }
/// <summary>
/// Gets the list of credentials used to encrypt the tokens issued by the
/// OpenIddict server services. Note: the encryption credentials are not
/// used to protect/unprotect tokens issued by ASP.NET Core Data Protection.
/// </summary>
/// Gets the list of encryption credentials used by the OpenIddict server services.
/// Multiple credentials can be added to support key rollover, but if X.509 keys
/// are used, at least one of them must have a valid creation/expiration date.
/// Note: the encryption credentials are not used to protect/unprotect tokens issued
/// by ASP.NET Core Data Protection, that uses its own key ring, configured separately.
/// </summary>
/// <remarks>
/// Note: OpenIddict automatically sorts the credentials based on the following algorithm:
/// <para>• Symmetric keys are always preferred when they can be used for the operation (e.g token encryption).</para>
/// <para>• X.509 keys are always preferred to non-X.509 asymmetric keys.</para>
/// <para>• X.509 keys with the furthest expiration date are preferred.</para>
/// <para>• X.509 keys whose backing certificate is not yet valid are never preferred.</para>
/// </remarks>
public List<EncryptingCredentials> EncryptionCredentials { get; } = new List<EncryptingCredentials>();
/// <summary>
/// Gets the list of credentials used to sign the tokens issued by the OpenIddict server services.
/// Both asymmetric and symmetric keys are supported, but only asymmetric keys can be used to sign identity tokens.
/// Note that only asymmetric RSA and ECDSA keys can be exposed by the JWKS metadata endpoint and that the
/// signing credentials are not used to protect/unprotect tokens issued by ASP.NET Core Data Protection.
/// Gets the list of signing credentials used by the OpenIddict server services.
/// Multiple credentials can be added to support key rollover, but if X.509 keys
/// are used, at least one of them must have a valid creation/expiration date.
/// Note: the signing credentials are not used to protect/unprotect tokens issued
/// by ASP.NET Core Data Protection, that uses its own key ring, configured separately.
/// </summary>
/// <remarks>
/// Note: OpenIddict automatically sorts the credentials based on the following algorithm:
/// <para>• Symmetric keys are always preferred when they can be used for the operation (e.g token signing).</para>
/// <para>• X.509 keys are always preferred to non-X.509 asymmetric keys.</para>
/// <para>• X.509 keys with the furthest expiration date are preferred.</para>
/// <para>• X.509 keys whose backing certificate is not yet valid are never preferred.</para>
/// </remarks>
public List<SigningCredentials> SigningCredentials { get; } = new List<SigningCredentials>();
/// <summary>

5
src/OpenIddict.Validation.ServerIntegration/OpenIddictValidationServerIntegrationConfiguration.cs

@ -54,10 +54,7 @@ namespace OpenIddict.Validation.ServerIntegration
}
// Import the encryption keys from the server configuration.
foreach (var credentials in _options.CurrentValue.EncryptionCredentials)
{
options.EncryptionCredentials.Add(credentials);
}
options.EncryptionCredentials.AddRange(_options.CurrentValue.EncryptionCredentials);
// Note: token entry validation must be enabled to be able to validate reference access tokens.
options.EnableTokenEntryValidation = _options.CurrentValue.UseReferenceAccessTokens;

41
src/OpenIddict.Validation/OpenIddictValidationBuilder.cs

@ -131,7 +131,7 @@ namespace Microsoft.Extensions.DependencyInjection
}
/// <summary>
/// Registers the <see cref="EncryptingCredentials"/> used to decrypt the tokens issued by OpenIddict.
/// Registers encryption credentials.
/// </summary>
/// <param name="credentials">The encrypting credentials.</param>
/// <returns>The <see cref="OpenIddictValidationBuilder"/>.</returns>
@ -146,7 +146,7 @@ namespace Microsoft.Extensions.DependencyInjection
}
/// <summary>
/// Registers a <see cref="SecurityKey"/> used to decrypt the access tokens issued by OpenIddict.
/// Registers an encryption key.
/// </summary>
/// <param name="key">The security key.</param>
/// <returns>The <see cref="OpenIddictValidationBuilder"/>.</returns>
@ -183,9 +183,9 @@ namespace Microsoft.Extensions.DependencyInjection
}
/// <summary>
/// Registers a <see cref="X509Certificate2"/> that is used to decrypt the tokens issued by OpenIddict.
/// Registers an encryption certificate.
/// </summary>
/// <param name="certificate">The certificate used to decrypt the security tokens issued by the validation.</param>
/// <param name="certificate">The encryption certificate.</param>
/// <returns>The <see cref="OpenIddictValidationBuilder"/>.</returns>
public OpenIddictValidationBuilder AddEncryptionCertificate([NotNull] X509Certificate2 certificate)
{
@ -194,14 +194,15 @@ namespace Microsoft.Extensions.DependencyInjection
throw new ArgumentNullException(nameof(certificate));
}
if (certificate.NotBefore > DateTime.Now)
// If the certificate is a X.509v3 certificate that specifies at least one
// key usage, ensure that the certificate key can be used for key encryption.
if (certificate.Version >= 3)
{
throw new InvalidOperationException("The specified certificate is not yet valid.");
}
if (certificate.NotAfter < DateTime.Now)
{
throw new InvalidOperationException("The specified certificate is no longer valid.");
var extensions = certificate.Extensions.OfType<X509KeyUsageExtension>().ToList();
if (extensions.Count != 0 && !extensions.Any(extension => extension.KeyUsages.HasFlag(X509KeyUsageFlags.KeyEncipherment)))
{
throw new InvalidOperationException("The specified certificate is not a key encryption certificate.");
}
}
if (!certificate.HasPrivateKey)
@ -213,8 +214,7 @@ namespace Microsoft.Extensions.DependencyInjection
}
/// <summary>
/// Registers a <see cref="X509Certificate2"/> retrieved from an
/// embedded resource and used to decrypt the tokens issued by OpenIddict.
/// Registers an encryption certificate retrieved from an embedded resource.
/// </summary>
/// <param name="assembly">The assembly containing the certificate.</param>
/// <param name="resource">The name of the embedded resource.</param>
@ -232,8 +232,7 @@ namespace Microsoft.Extensions.DependencyInjection
#endif
/// <summary>
/// Registers a <see cref="X509Certificate2"/> retrieved from an
/// embedded resource and used to decrypt the tokens issued by OpenIddict.
/// Registers an encryption certificate retrieved from an embedded resource.
/// </summary>
/// <param name="assembly">The assembly containing the certificate.</param>
/// <param name="resource">The name of the embedded resource.</param>
@ -269,8 +268,7 @@ namespace Microsoft.Extensions.DependencyInjection
}
/// <summary>
/// Registers a <see cref="X509Certificate2"/> extracted from a
/// stream and used to decrypt the tokens issued by OpenIddict.
/// Registers an encryption certificate extracted from a stream.
/// </summary>
/// <param name="stream">The stream containing the certificate.</param>
/// <param name="password">The password used to open the certificate.</param>
@ -286,8 +284,7 @@ namespace Microsoft.Extensions.DependencyInjection
#endif
/// <summary>
/// Registers a <see cref="X509Certificate2"/> extracted from a
/// stream and used to decrypt the tokens issued by OpenIddict.
/// Registers an encryption certificate extracted from a stream.
/// </summary>
/// <param name="stream">The stream containing the certificate.</param>
/// <param name="password">The password used to open the certificate.</param>
@ -318,8 +315,7 @@ namespace Microsoft.Extensions.DependencyInjection
}
/// <summary>
/// Registers a <see cref="X509Certificate2"/> retrieved from the X.509
/// machine store and used to decrypt the tokens issued by OpenIddict.
/// Registers an encryption certificate retrieved from the X.509 user or machine store.
/// </summary>
/// <param name="thumbprint">The thumbprint of the certificate used to identify it in the X.509 store.</param>
/// <returns>The <see cref="OpenIddictValidationBuilder"/>.</returns>
@ -350,8 +346,7 @@ namespace Microsoft.Extensions.DependencyInjection
}
/// <summary>
/// Registers a <see cref="X509Certificate2"/> retrieved from the given
/// X.509 store and used to decrypt the tokens issued by OpenIddict.
/// Registers an encryption certificate retrieved from the specified X.509 store.
/// </summary>
/// <param name="thumbprint">The thumbprint of the certificate used to identify it in the X.509 store.</param>
/// <param name="name">The name of the X.509 store.</param>

12
src/OpenIddict.Validation/OpenIddictValidationConfiguration.cs

@ -11,6 +11,7 @@ using JetBrains.Annotations;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Protocols;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Tokens;
using static OpenIddict.Validation.OpenIddictValidationEvents;
namespace OpenIddict.Validation
@ -96,6 +97,17 @@ namespace OpenIddict.Validation
}
}
// If all the registered encryption credentials are backed by a X.509 certificate, at least one of them must be valid.
if (options.EncryptionCredentials.Count != 0 &&
options.EncryptionCredentials.All(credentials => credentials.Key is X509SecurityKey x509SecurityKey &&
(x509SecurityKey.Certificate.NotBefore > DateTime.Now || x509SecurityKey.Certificate.NotAfter < DateTime.Now)))
{
throw new InvalidOperationException(new StringBuilder()
.AppendLine("When using X.509 encryption credentials, at least one of the registered certificates must be valid.")
.Append("To use key rollover, register both the new certificate and the old one in the credentials collection.")
.ToString());
}
if (options.Configuration == null && options.ConfigurationManager == null)
{
if (!options.Handlers.Any(descriptor => descriptor.ContextType == typeof(ApplyConfigurationRequestContext)) ||

12
src/OpenIddict.Validation/OpenIddictValidationOptions.cs

@ -21,9 +21,17 @@ namespace OpenIddict.Validation
public class OpenIddictValidationOptions
{
/// <summary>
/// Gets the list of credentials used to encrypt the tokens issued by the
/// OpenIddict validation services. Note: only symmetric credentials are supported.
/// Gets the list of encryption credentials used by the OpenIddict validation services.
/// Note: the encryption credentials are not used to protect/unprotect tokens issued
/// by ASP.NET Core Data Protection, that uses its own key ring, configured separately.
/// </summary>
/// <remarks>
/// Note: OpenIddict automatically sorts the credentials based on the following algorithm:
/// <para>• Symmetric keys are always preferred when they can be used for the operation (e.g token decryption).</para>
/// <para>• X.509 keys are always preferred to non-X.509 asymmetric keys.</para>
/// <para>• X.509 keys with the furthest expiration date are preferred.</para>
/// <para>• X.509 keys whose backing certificate is not yet valid are never preferred.</para>
/// </remarks>
public List<EncryptingCredentials> EncryptionCredentials { get; } = new List<EncryptingCredentials>();
/// <summary>

396
test/OpenIddict.Server.Tests/OpenIddictServerBuilderTests.cs

@ -33,11 +33,10 @@ namespace OpenIddict.Server.Tests
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
Action<OpenIddictServerHandlerDescriptor.Builder<CustomContext>> configuration = null;
// Act and assert
var exception = Assert.Throws<ArgumentNullException>(() => builder.AddEventHandler(configuration));
Assert.Equal(nameof(configuration), exception.ParamName);
var exception = Assert.Throws<ArgumentNullException>(() => builder.AddEventHandler<BaseContext>(configuration: null));
Assert.Equal("configuration", exception.ParamName);
}
[Fact]
@ -46,11 +45,10 @@ namespace OpenIddict.Server.Tests
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
OpenIddictServerHandlerDescriptor descriptor = null;
// Act and assert
var exception = Assert.Throws<ArgumentNullException>(() => builder.AddEventHandler(descriptor));
Assert.Equal(nameof(descriptor), exception.ParamName);
var exception = Assert.Throws<ArgumentNullException>(() => builder.AddEventHandler(descriptor: null));
Assert.Equal("descriptor", exception.ParamName);
}
[Fact]
@ -134,11 +132,10 @@ namespace OpenIddict.Server.Tests
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
EncryptingCredentials credentials = null;
// Act and assert
var exception = Assert.Throws<ArgumentNullException>(() => builder.AddEncryptionCredentials(credentials));
Assert.Equal(nameof(credentials), exception.ParamName);
var exception = Assert.Throws<ArgumentNullException>(() => builder.AddEncryptionCredentials(credentials: null));
Assert.Equal("credentials", exception.ParamName);
}
[Fact]
@ -147,11 +144,10 @@ namespace OpenIddict.Server.Tests
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
SecurityKey key = null;
// Act and assert
var exception = Assert.Throws<ArgumentNullException>(() => builder.AddEncryptionKey(key));
Assert.Equal(nameof(key), exception.ParamName);
var exception = Assert.Throws<ArgumentNullException>(() => builder.AddEncryptionKey(key: null));
Assert.Equal("key", exception.ParamName);
}
[Fact]
@ -160,11 +156,10 @@ namespace OpenIddict.Server.Tests
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
var key = new Mock<AsymmetricSecurityKey>();
key.SetupGet(x => x.PrivateKeyStatus).Returns(PrivateKeyStatus.DoesNotExist);
var key = Mock.Of<AsymmetricSecurityKey>(key => key.PrivateKeyStatus == PrivateKeyStatus.DoesNotExist);
// Act and assert
var exception = Assert.Throws<InvalidOperationException>(() => builder.AddEncryptionKey(key.Object));
var exception = Assert.Throws<InvalidOperationException>(() => builder.AddEncryptionKey(key));
Assert.Equal("The asymmetric encryption key doesn't contain the required private key.", exception.Message);
}
@ -192,11 +187,10 @@ namespace OpenIddict.Server.Tests
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
OpenIddictServerHandlerDescriptor descriptor = null;
// Act and assert
var exception = Assert.Throws<ArgumentNullException>(() => builder.RemoveEventHandler(descriptor));
Assert.Equal(nameof(descriptor), exception.ParamName);
var exception = Assert.Throws<ArgumentNullException>(() => builder.RemoveEventHandler(descriptor: null));
Assert.Equal("descriptor", exception.ParamName);
}
[Fact]
@ -240,11 +234,10 @@ namespace OpenIddict.Server.Tests
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
Action<OpenIddictServerOptions> configuration = null;
// Act and assert
var exception = Assert.Throws<ArgumentNullException>(() => builder.Configure(configuration));
Assert.Equal(nameof(configuration), exception.ParamName);
var exception = Assert.Throws<ArgumentNullException>(() => builder.Configure(configuration: null));
Assert.Equal("configuration", exception.ParamName);
}
[Fact]
@ -269,11 +262,10 @@ namespace OpenIddict.Server.Tests
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
X500DistinguishedName subject = null;
// Act and assert
var exception = Assert.Throws<ArgumentNullException>(() => builder.AddDevelopmentEncryptionCertificate(subject));
Assert.Equal(nameof(subject), exception.ParamName);
var exception = Assert.Throws<ArgumentNullException>(() => builder.AddDevelopmentEncryptionCertificate(subject: null));
Assert.Equal("subject", exception.ParamName);
}
#if SUPPORTS_CERTIFICATE_GENERATION
@ -354,6 +346,31 @@ namespace OpenIddict.Server.Tests
Assert.Equal(algorithm, credentials.Algorithm);
}
[Fact]
public void AddSigningKey_ThrowsExceptionWhenKeyIsNull()
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
// Act and assert
var exception = Assert.Throws<ArgumentNullException>(() => builder.AddSigningKey(key: null));
Assert.Equal("key", exception.ParamName);
}
[Fact]
public void AddSigningKey_ThrowsExceptionWhenAsymmetricKeyPrivateKeyIsMissing()
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
var key = Mock.Of<AsymmetricSecurityKey>(key => key.PrivateKeyStatus == PrivateKeyStatus.DoesNotExist);
// Act and assert
var exception = Assert.Throws<InvalidOperationException>(() => builder.AddSigningKey(key));
Assert.Equal("The asymmetric signing key doesn't contain the required private key.", exception.Message);
}
[Theory]
[InlineData(SecurityAlgorithms.HmacSha256)]
[InlineData(SecurityAlgorithms.RsaSha256)]
@ -531,11 +548,10 @@ namespace OpenIddict.Server.Tests
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
Uri[] addresses = null;
// Act and assert
var exception = Assert.Throws<ArgumentNullException>(() => builder.SetConfigurationEndpointUris(addresses));
Assert.Equal(nameof(addresses), exception.ParamName);
var exception = Assert.Throws<ArgumentNullException>(() => builder.SetConfigurationEndpointUris(addresses: null as Uri[]));
Assert.Equal("addresses", exception.ParamName);
}
[Fact]
@ -544,27 +560,23 @@ namespace OpenIddict.Server.Tests
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
string[] addresses = null;
// Act and assert
var exception = Assert.Throws<ArgumentNullException>(() => builder.SetConfigurationEndpointUris(addresses));
Assert.Equal(nameof(addresses), exception.ParamName);
var exception = Assert.Throws<ArgumentNullException>(() => builder.SetConfigurationEndpointUris(addresses: null as string[]));
Assert.Equal("addresses", exception.ParamName);
}
public const string InvalidUriString = @"C:\";
[Theory]
[InlineData(InvalidUriString)]
[InlineData(@"C:\")]
public void SetConfigurationEndpointUris_ThrowsExceptionForUri(string uri)
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
Uri[] addresses = { new Uri(uri), };
// Act and assert
var exception = Assert.Throws<ArgumentException>(() => builder.SetConfigurationEndpointUris(addresses));
Assert.Equal(nameof(addresses), exception.ParamName);
var exception = Assert.Throws<ArgumentException>(() => builder.SetConfigurationEndpointUris(new Uri(uri)));
Assert.Equal("addresses", exception.ParamName);
Assert.Contains("One of the specified addresses is not valid.", exception.Message);
}
@ -606,11 +618,10 @@ namespace OpenIddict.Server.Tests
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
Uri[] addresses = null;
// Act and assert
var exception = Assert.Throws<ArgumentNullException>(() => builder.SetDeviceEndpointUris(addresses));
Assert.Equal(nameof(addresses), exception.ParamName);
var exception = Assert.Throws<ArgumentNullException>(() => builder.SetDeviceEndpointUris(addresses: null as Uri[]));
Assert.Equal("addresses", exception.ParamName);
}
[Fact]
@ -619,25 +630,23 @@ namespace OpenIddict.Server.Tests
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
string[] addresses = null;
// Act and assert
var exception = Assert.Throws<ArgumentNullException>(() => builder.SetDeviceEndpointUris(addresses));
Assert.Equal(nameof(addresses), exception.ParamName);
var exception = Assert.Throws<ArgumentNullException>(() => builder.SetDeviceEndpointUris(addresses: null as string[]));
Assert.Equal("addresses", exception.ParamName);
}
[Theory]
[InlineData(InvalidUriString)]
[InlineData(@"C:\")]
public void SetDeviceEndpointUris_ThrowsExceptionForUri(string uri)
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
Uri[] addresses = { new Uri(uri), };
// Act and assert
var exception = Assert.Throws<ArgumentException>(() => builder.SetDeviceEndpointUris(addresses));
Assert.Equal(nameof(addresses), exception.ParamName);
var exception = Assert.Throws<ArgumentException>(() => builder.SetDeviceEndpointUris(new Uri(uri)));
Assert.Equal("addresses", exception.ParamName);
Assert.Contains("One of the specified addresses is not valid.", exception.Message);
}
@ -695,11 +704,10 @@ namespace OpenIddict.Server.Tests
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
Uri[] addresses = null;
// Act and assert
var exception = Assert.Throws<ArgumentNullException>(() => builder.SetCryptographyEndpointUris(addresses));
Assert.Equal(nameof(addresses), exception.ParamName);
var exception = Assert.Throws<ArgumentNullException>(() => builder.SetCryptographyEndpointUris(addresses: null as Uri[]));
Assert.Equal("addresses", exception.ParamName);
}
[Fact]
@ -708,25 +716,23 @@ namespace OpenIddict.Server.Tests
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
string[] addresses = null;
// Act and assert
var exception = Assert.Throws<ArgumentNullException>(() => builder.SetCryptographyEndpointUris(addresses));
Assert.Equal(nameof(addresses), exception.ParamName);
var exception = Assert.Throws<ArgumentNullException>(() => builder.SetCryptographyEndpointUris(addresses: null as string[]));
Assert.Equal("addresses", exception.ParamName);
}
[Theory]
[InlineData(InvalidUriString)]
[InlineData(@"C:\")]
public void SetCryptographyEndpointUris_ThrowsExceptionForUri(string uri)
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
Uri[] addresses = { new Uri(uri), };
// Act and assert
var exception = Assert.Throws<ArgumentException>(() => builder.SetCryptographyEndpointUris(addresses));
Assert.Equal(nameof(addresses), exception.ParamName);
var exception = Assert.Throws<ArgumentException>(() => builder.SetCryptographyEndpointUris(new Uri(uri)));
Assert.Equal("addresses", exception.ParamName);
Assert.Contains("One of the specified addresses is not valid.", exception.Message);
}
@ -832,11 +838,10 @@ namespace OpenIddict.Server.Tests
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
Uri[] addresses = null;
// Act and assert
var exception = Assert.Throws<ArgumentNullException>(() => builder.SetAuthorizationEndpointUris(addresses));
Assert.Equal(nameof(addresses), exception.ParamName);
var exception = Assert.Throws<ArgumentNullException>(() => builder.SetAuthorizationEndpointUris(addresses: null as Uri[]));
Assert.Equal("addresses", exception.ParamName);
}
[Fact]
@ -845,25 +850,23 @@ namespace OpenIddict.Server.Tests
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
string[] addresses = null;
// Act and assert
var exception = Assert.Throws<ArgumentNullException>(() => builder.SetAuthorizationEndpointUris(addresses));
Assert.Equal(nameof(addresses), exception.ParamName);
var exception = Assert.Throws<ArgumentNullException>(() => builder.SetAuthorizationEndpointUris(addresses: null as string[]));
Assert.Equal("addresses", exception.ParamName);
}
[Theory]
[InlineData(InvalidUriString)]
[InlineData(@"C:\")]
public void SetAuthorizationEndpointUris_ThrowsExceptionForUri(string uri)
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
Uri[] addresses = { new Uri(uri), };
// Act and assert
var exception = Assert.Throws<ArgumentException>(() => builder.SetAuthorizationEndpointUris(addresses));
Assert.Equal(nameof(addresses), exception.ParamName);
var exception = Assert.Throws<ArgumentException>(() => builder.SetAuthorizationEndpointUris(new Uri(uri)));
Assert.Equal("addresses", exception.ParamName);
Assert.Contains("One of the specified addresses is not valid.", exception.Message);
}
@ -905,11 +908,10 @@ namespace OpenIddict.Server.Tests
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
Uri[] addresses = null;
// Act and assert
var exception = Assert.Throws<ArgumentNullException>(() => builder.SetIntrospectionEndpointUris(addresses));
Assert.Equal(nameof(addresses), exception.ParamName);
var exception = Assert.Throws<ArgumentNullException>(() => builder.SetIntrospectionEndpointUris(addresses: null as Uri[]));
Assert.Equal("addresses", exception.ParamName);
}
[Fact]
@ -918,25 +920,23 @@ namespace OpenIddict.Server.Tests
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
string[] addresses = null;
// Act and assert
var exception = Assert.Throws<ArgumentNullException>(() => builder.SetIntrospectionEndpointUris(addresses));
Assert.Equal(nameof(addresses), exception.ParamName);
var exception = Assert.Throws<ArgumentNullException>(() => builder.SetIntrospectionEndpointUris(addresses: null as string[]));
Assert.Equal("addresses", exception.ParamName);
}
[Theory]
[InlineData(InvalidUriString)]
[InlineData(@"C:\")]
public void SetIntrospectionEndpointUris_ThrowsExceptionForUri(string uri)
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
Uri[] addresses = { new Uri(uri), };
// Act and assert
var exception = Assert.Throws<ArgumentException>(() => builder.SetIntrospectionEndpointUris(addresses));
Assert.Equal(nameof(addresses), exception.ParamName);
var exception = Assert.Throws<ArgumentException>(() => builder.SetIntrospectionEndpointUris(new Uri(uri)));
Assert.Equal("addresses", exception.ParamName);
Assert.Contains("One of the specified addresses is not valid.", exception.Message);
}
@ -978,11 +978,10 @@ namespace OpenIddict.Server.Tests
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
Uri[] addresses = null;
// Act and assert
var exception = Assert.Throws<ArgumentNullException>(() => builder.SetLogoutEndpointUris(addresses));
Assert.Equal(nameof(addresses), exception.ParamName);
var exception = Assert.Throws<ArgumentNullException>(() => builder.SetLogoutEndpointUris(addresses: null as Uri[]));
Assert.Equal("addresses", exception.ParamName);
}
[Fact]
@ -991,25 +990,23 @@ namespace OpenIddict.Server.Tests
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
string[] addresses = null;
// Act and assert
var exception = Assert.Throws<ArgumentNullException>(() => builder.SetLogoutEndpointUris(addresses));
Assert.Equal(nameof(addresses), exception.ParamName);
var exception = Assert.Throws<ArgumentNullException>(() => builder.SetLogoutEndpointUris(addresses: null as string[]));
Assert.Equal("addresses", exception.ParamName);
}
[Theory]
[InlineData(InvalidUriString)]
[InlineData(@"C:\")]
public void SetLogoutEndpointUris_ThrowsExceptionForUri(string uri)
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
Uri[] addresses = { new Uri(uri), };
// Act and assert
var exception = Assert.Throws<ArgumentException>(() => builder.SetLogoutEndpointUris(addresses));
Assert.Equal(nameof(addresses), exception.ParamName);
var exception = Assert.Throws<ArgumentException>(() => builder.SetLogoutEndpointUris(new Uri(uri)));
Assert.Equal("addresses", exception.ParamName);
Assert.Contains("One of the specified addresses is not valid.", exception.Message);
}
@ -1051,11 +1048,10 @@ namespace OpenIddict.Server.Tests
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
Uri[] addresses = null;
// Act and assert
var exception = Assert.Throws<ArgumentNullException>(() => builder.SetRevocationEndpointUris(addresses));
Assert.Equal(nameof(addresses), exception.ParamName);
var exception = Assert.Throws<ArgumentNullException>(() => builder.SetRevocationEndpointUris(addresses: null as Uri[]));
Assert.Equal("addresses", exception.ParamName);
}
[Fact]
@ -1064,25 +1060,23 @@ namespace OpenIddict.Server.Tests
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
string[] addresses = null;
// Act and assert
var exception = Assert.Throws<ArgumentNullException>(() => builder.SetRevocationEndpointUris(addresses));
Assert.Equal(nameof(addresses), exception.ParamName);
var exception = Assert.Throws<ArgumentNullException>(() => builder.SetRevocationEndpointUris(addresses: null as string[]));
Assert.Equal("addresses", exception.ParamName);
}
[Theory]
[InlineData(InvalidUriString)]
[InlineData(@"C:\")]
public void SetRevocationEndpointUris_ThrowsExceptionForUri(string uri)
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
Uri[] addresses = { new Uri(uri), };
// Act and assert
var exception = Assert.Throws<ArgumentException>(() => builder.SetRevocationEndpointUris(addresses));
Assert.Equal(nameof(addresses), exception.ParamName);
var exception = Assert.Throws<ArgumentException>(() => builder.SetRevocationEndpointUris(new Uri(uri)));
Assert.Equal("addresses", exception.ParamName);
Assert.Contains("One of the specified addresses is not valid.", exception.Message);
}
@ -1124,11 +1118,10 @@ namespace OpenIddict.Server.Tests
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
Uri[] addresses = null;
// Act and assert
var exception = Assert.Throws<ArgumentNullException>(() => builder.SetTokenEndpointUris(addresses));
Assert.Equal(nameof(addresses), exception.ParamName);
var exception = Assert.Throws<ArgumentNullException>(() => builder.SetTokenEndpointUris(addresses: null as Uri[]));
Assert.Equal("addresses", exception.ParamName);
}
[Fact]
@ -1137,25 +1130,23 @@ namespace OpenIddict.Server.Tests
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
string[] addresses = null;
// Act and assert
var exception = Assert.Throws<ArgumentNullException>(() => builder.SetTokenEndpointUris(addresses));
Assert.Equal(nameof(addresses), exception.ParamName);
var exception = Assert.Throws<ArgumentNullException>(() => builder.SetTokenEndpointUris(addresses: null as string[]));
Assert.Equal("addresses", exception.ParamName);
}
[Theory]
[InlineData(InvalidUriString)]
[InlineData(@"C:\")]
public void SetTokenEndpointUris_ThrowsExceptionForUri(string uri)
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
Uri[] addresses = { new Uri(uri), };
// Act and assert
var exception = Assert.Throws<ArgumentException>(() => builder.SetTokenEndpointUris(addresses));
Assert.Equal(nameof(addresses), exception.ParamName);
var exception = Assert.Throws<ArgumentException>(() => builder.SetTokenEndpointUris(new Uri(uri)));
Assert.Equal("addresses", exception.ParamName);
Assert.Contains("One of the specified addresses is not valid.", exception.Message);
}
@ -1197,11 +1188,10 @@ namespace OpenIddict.Server.Tests
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
Uri[] addresses = null;
// Act and assert
var exception = Assert.Throws<ArgumentNullException>(() => builder.SetUserinfoEndpointUris(addresses));
Assert.Equal(nameof(addresses), exception.ParamName);
var exception = Assert.Throws<ArgumentNullException>(() => builder.SetUserinfoEndpointUris(addresses: null as Uri[]));
Assert.Equal("addresses", exception.ParamName);
}
[Fact]
@ -1210,25 +1200,23 @@ namespace OpenIddict.Server.Tests
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
string[] addresses = null;
// Act and assert
var exception = Assert.Throws<ArgumentNullException>(() => builder.SetUserinfoEndpointUris(addresses));
Assert.Equal(nameof(addresses), exception.ParamName);
var exception = Assert.Throws<ArgumentNullException>(() => builder.SetUserinfoEndpointUris(addresses: null as string[]));
Assert.Equal("addresses", exception.ParamName);
}
[Theory]
[InlineData(InvalidUriString)]
[InlineData(@"C:\")]
public void SetUserinfoEndpointUris_ThrowsExceptionForUri(string uri)
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
Uri[] addresses = {new Uri(uri), };
// Act and assert
var exception = Assert.Throws<ArgumentException>(() => builder.SetUserinfoEndpointUris(addresses));
Assert.Equal(nameof(addresses), exception.ParamName);
var exception = Assert.Throws<ArgumentException>(() => builder.SetUserinfoEndpointUris(new Uri(uri)));
Assert.Equal("addresses", exception.ParamName);
Assert.Contains("One of the specified addresses is not valid.", exception.Message);
}
@ -1264,6 +1252,76 @@ namespace OpenIddict.Server.Tests
Assert.Contains(new Uri("http://localhost/endpoint-path"), options.UserinfoEndpointUris);
}
[Fact]
public void SetVerificationEndpointUris_ThrowsExceptionWhenAddressesIsNull()
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
// Act and assert
var exception = Assert.Throws<ArgumentNullException>(() => builder.SetVerificationEndpointUris(addresses: null as Uri[]));
Assert.Equal("addresses", exception.ParamName);
}
[Fact]
public void SetVerificationEndpointUris_Strings_ThrowsExceptionWhenAddressesIsNull()
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
// Act and assert
var exception = Assert.Throws<ArgumentNullException>(() => builder.SetVerificationEndpointUris(addresses: null as string[]));
Assert.Equal("addresses", exception.ParamName);
}
[Theory]
[InlineData(@"C:\")]
public void SetVerificationEndpointUris_ThrowsExceptionForUri(string uri)
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
// Act and assert
var exception = Assert.Throws<ArgumentException>(() => builder.SetVerificationEndpointUris(new Uri(uri)));
Assert.Equal("addresses", exception.ParamName);
Assert.Contains("One of the specified addresses is not valid.", exception.Message);
}
[Fact]
public void SetVerificationEndpointUris_ClearsUris()
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
// Act
builder.SetVerificationEndpointUris(Array.Empty<Uri>());
var options = GetOptions(services);
// Assert
Assert.Empty(options.VerificationEndpointUris);
}
[Fact]
public void SetVerificationEndpointUris_AddsUri()
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
// Act
builder.SetVerificationEndpointUris("http://localhost/endpoint-path");
var options = GetOptions(services);
// Assert
Assert.Contains(new Uri("http://localhost/endpoint-path"), options.VerificationEndpointUris);
}
[Fact]
public void AcceptAnonymousClients_ClientIdentificationIsOptional()
{
@ -1524,11 +1582,10 @@ namespace OpenIddict.Server.Tests
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
string[] claims = null;
// Act and assert
var exception = Assert.Throws<ArgumentNullException>(() => builder.RegisterClaims(claims));
Assert.Equal(nameof(claims), exception.ParamName);
var exception = Assert.Throws<ArgumentNullException>(() => builder.RegisterClaims(claims: null));
Assert.Equal("claims", exception.ParamName);
}
[Theory]
@ -1570,11 +1627,10 @@ namespace OpenIddict.Server.Tests
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
string[] scopes = null;
// Act and assert
var exception = Assert.Throws<ArgumentNullException>(() => builder.RegisterScopes(scopes));
Assert.Equal(nameof(scopes), exception.ParamName);
var exception = Assert.Throws<ArgumentNullException>(() => builder.RegisterScopes(scopes: null));
Assert.Equal("scopes", exception.ParamName);
}
[Theory]
@ -1641,102 +1697,6 @@ namespace OpenIddict.Server.Tests
Assert.True(options.UseRollingRefreshTokens);
}
[Fact]
public void SetVerificationEndpointUris_ThrowsExceptionWhenNullAddresses()
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
Uri[] addresses = null;
// Act and assert
var exception = Assert.Throws<ArgumentNullException>(() => builder.SetVerificationEndpointUris(addresses));
Assert.Equal(nameof(addresses), exception.ParamName);
}
[Fact]
public void SetVerificationEndpointUris_Strings_ThrowsExceptionWhenNullAddresses()
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
string[] addresses = null;
// Act and assert
var exception = Assert.Throws<ArgumentNullException>(() => builder.SetVerificationEndpointUris(addresses));
Assert.Equal(nameof(addresses), exception.ParamName);
}
[Fact]
public void SetVerificationEndpointUris_Strings_AddedUriIsRelativeOrAbsoluteUriKind()
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
string[] addresses = {"http://localhost/verify"};
// Act
builder.SetVerificationEndpointUris(addresses);
var options = GetOptions(services);
// Assert
Assert.True(options.VerificationEndpointUris[0].IsAbsoluteUri);
}
[Theory]
[InlineData(InvalidUriString)]
public void SetVerificationEndpointUris_ThrowsExceptionForUri(string uri)
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
Uri[] addresses = { new Uri(uri)};
// Act and assert
var exception = Assert.Throws<ArgumentException>(() => builder.SetVerificationEndpointUris(addresses));
Assert.Equal(nameof(addresses), exception.ParamName);
Assert.Contains("One of the specified addresses is not valid.", exception.Message);
}
[Fact]
public void SetVerificationEndpointUris_ClearsExistingUris()
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
Uri[] addresses = Array.Empty<Uri>();
// Act
builder.SetVerificationEndpointUris(addresses);
var options = GetOptions(services);
// Assert
Assert.Empty(options.VerificationEndpointUris);
}
[Theory]
[InlineData("http://localhost/verify")]
[InlineData("http://localhost/verify-1")]
[InlineData("http://localhost/verification")]
[InlineData("http://localhost/verification-1")]
public void SetVerificationEndpointUris_AddsUri(string uri)
{
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
Uri[] addresses = { new Uri(uri), };
// Act
builder.SetVerificationEndpointUris(addresses);
var options = GetOptions(services);
// Assert
Assert.Contains(addresses[0], options.VerificationEndpointUris);
}
private static IServiceCollection CreateServices()
{
return new ServiceCollection().AddOptions();

Loading…
Cancel
Save