diff --git a/src/OpenIddict.Server/OpenIddictServerBuilder.cs b/src/OpenIddict.Server/OpenIddictServerBuilder.cs
index 5cd51dae..2973c5a8 100644
--- a/src/OpenIddict.Server/OpenIddictServerBuilder.cs
+++ b/src/OpenIddict.Server/OpenIddictServerBuilder.cs
@@ -141,7 +141,7 @@ namespace Microsoft.Extensions.DependencyInjection
=> Configure(options => options.AcceptAnonymousClients = true);
///
- /// Registers the used to encrypt the tokens issued by OpenIddict.
+ /// Registers encryption credentials.
///
/// The encrypting credentials.
/// The .
@@ -156,7 +156,7 @@ namespace Microsoft.Extensions.DependencyInjection
}
///
- /// Registers a used to encrypt the access tokens issued by OpenIddict.
+ /// Registers an encryption key.
///
/// The security key.
/// The .
@@ -193,16 +193,14 @@ namespace Microsoft.Extensions.DependencyInjection
}
///
- /// 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.
///
/// The .
public OpenIddictServerBuilder AddDevelopmentEncryptionCertificate()
=> AddDevelopmentEncryptionCertificate(new X500DistinguishedName("CN=OpenIddict Server Encryption Certificate"));
///
- /// 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.
///
/// The subject name associated with the certificate.
/// The .
@@ -284,8 +282,8 @@ namespace Microsoft.Extensions.DependencyInjection
}
///
- /// 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.
///
@@ -294,8 +292,8 @@ namespace Microsoft.Extensions.DependencyInjection
=> AddEphemeralEncryptionKey(SecurityAlgorithms.RsaOAEP);
///
- /// 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.
///
@@ -370,9 +368,9 @@ namespace Microsoft.Extensions.DependencyInjection
}
///
- /// Registers a that is used to encrypt the tokens issued by OpenIddict.
+ /// Registers an encryption certificate.
///
- /// The certificate used to encrypt the security tokens issued by the server.
+ /// The encryption certificate.
/// The .
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().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
}
///
- /// Registers a retrieved from an
- /// embedded resource and used to encrypt the tokens issued by OpenIddict.
+ /// Registers an encryption certificate retrieved from an embedded resource.
///
/// The assembly containing the certificate.
/// The name of the embedded resource.
@@ -419,8 +417,7 @@ namespace Microsoft.Extensions.DependencyInjection
#endif
///
- /// Registers a retrieved from an
- /// embedded resource and used to encrypt the tokens issued by OpenIddict.
+ /// Registers an encryption certificate retrieved from an embedded resource.
///
/// The assembly containing the certificate.
/// The name of the embedded resource.
@@ -456,8 +453,7 @@ namespace Microsoft.Extensions.DependencyInjection
}
///
- /// Registers a extracted from a
- /// stream and used to encrypt the tokens issued by OpenIddict.
+ /// Registers an encryption certificate extracted from a stream.
///
/// The stream containing the certificate.
/// The password used to open the certificate.
@@ -473,8 +469,7 @@ namespace Microsoft.Extensions.DependencyInjection
#endif
///
- /// Registers a extracted from a
- /// stream and used to encrypt the tokens issued by OpenIddict.
+ /// Registers an encryption certificate extracted from a stream.
///
/// The stream containing the certificate.
/// The password used to open the certificate.
@@ -505,8 +500,7 @@ namespace Microsoft.Extensions.DependencyInjection
}
///
- /// Registers a 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.
///
/// The thumbprint of the certificate used to identify it in the X.509 store.
/// The .
@@ -537,8 +531,7 @@ namespace Microsoft.Extensions.DependencyInjection
}
///
- /// Registers a 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.
///
/// The thumbprint of the certificate used to identify it in the X.509 store.
/// The name of the X.509 store.
@@ -568,8 +561,7 @@ namespace Microsoft.Extensions.DependencyInjection
}
///
- /// Registers the used to sign the tokens issued by OpenIddict.
- /// Note: using asymmetric keys is recommended on production.
+ /// Registers signing credentials.
///
/// The signing credentials.
/// The .
@@ -584,8 +576,7 @@ namespace Microsoft.Extensions.DependencyInjection
}
///
- /// Registers a used to sign the tokens issued by OpenIddict.
- /// Note: using asymmetric keys is recommended on production.
+ /// Registers a signing key.
///
/// The security key.
/// The .
@@ -645,16 +636,14 @@ namespace Microsoft.Extensions.DependencyInjection
}
///
- /// 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.
///
/// The .
public OpenIddictServerBuilder AddDevelopmentSigningCertificate()
=> AddDevelopmentSigningCertificate(new X500DistinguishedName("CN=OpenIddict Server Signing Certificate"));
///
- /// 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.
///
/// The subject name associated with the certificate.
/// The .
@@ -736,8 +725,8 @@ namespace Microsoft.Extensions.DependencyInjection
}
///
- /// 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.
///
@@ -746,8 +735,8 @@ namespace Microsoft.Extensions.DependencyInjection
=> AddEphemeralSigningKey(SecurityAlgorithms.RsaSha256);
///
- /// 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.
///
@@ -841,9 +830,9 @@ namespace Microsoft.Extensions.DependencyInjection
}
///
- /// Registers a that is used to sign the tokens issued by OpenIddict.
+ /// Registers a signing certificate.
///
- /// The certificate used to sign the security tokens issued by the server.
+ /// The signing certificate.
/// The .
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().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
}
///
- /// Registers a retrieved from an
- /// embedded resource and used to sign the tokens issued by OpenIddict.
+ /// Registers a signing certificate retrieved from an embedded resource.
///
/// The assembly containing the certificate.
/// The name of the embedded resource.
@@ -890,8 +879,7 @@ namespace Microsoft.Extensions.DependencyInjection
#endif
///
- /// Registers a retrieved from an
- /// embedded resource and used to sign the tokens issued by OpenIddict.
+ /// Registers a signing certificate retrieved from an embedded resource.
///
/// The assembly containing the certificate.
/// The name of the embedded resource.
@@ -927,8 +915,7 @@ namespace Microsoft.Extensions.DependencyInjection
}
///
- /// Registers a extracted from a
- /// stream and used to sign the tokens issued by OpenIddict.
+ /// Registers a signing certificate extracted from a stream.
///
/// The stream containing the certificate.
/// The password used to open the certificate.
@@ -944,8 +931,7 @@ namespace Microsoft.Extensions.DependencyInjection
#endif
///
- /// Registers a extracted from a
- /// stream and used to sign the tokens issued by OpenIddict.
+ /// Registers a signing certificate extracted from a stream.
///
/// The stream containing the certificate.
/// The password used to open the certificate.
@@ -976,8 +962,7 @@ namespace Microsoft.Extensions.DependencyInjection
}
///
- /// Registers a 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.
///
/// The thumbprint of the certificate used to identify it in the X.509 store.
/// The .
@@ -1008,8 +993,7 @@ namespace Microsoft.Extensions.DependencyInjection
}
///
- /// Registers a 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.
///
/// The thumbprint of the certificate used to identify it in the X.509 store.
/// The name of the X.509 store.
@@ -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);
});
}
diff --git a/src/OpenIddict.Server/OpenIddictServerConfiguration.cs b/src/OpenIddict.Server/OpenIddictServerConfiguration.cs
index a5cb2e17..66c24cb3 100644
--- a/src/OpenIddict.Server/OpenIddictServerConfiguration.cs
+++ b/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
diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.cs
index f21c4ee9..9c402e24 100644
--- a/src/OpenIddict.Server/OpenIddictServerHandlers.cs
+++ b/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
diff --git a/src/OpenIddict.Server/OpenIddictServerOptions.cs b/src/OpenIddict.Server/OpenIddictServerOptions.cs
index 79609333..ca501f28 100644
--- a/src/OpenIddict.Server/OpenIddictServerOptions.cs
+++ b/src/OpenIddict.Server/OpenIddictServerOptions.cs
@@ -27,18 +27,35 @@ namespace OpenIddict.Server
public Uri Issuer { get; set; }
///
- /// 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.
- ///
+ /// 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.
+ ///
+ ///
+ /// Note: OpenIddict automatically sorts the credentials based on the following algorithm:
+ /// • Symmetric keys are always preferred when they can be used for the operation (e.g token encryption).
+ /// • X.509 keys are always preferred to non-X.509 asymmetric keys.
+ /// • X.509 keys with the furthest expiration date are preferred.
+ /// • X.509 keys whose backing certificate is not yet valid are never preferred.
+ ///
public List EncryptionCredentials { get; } = new List();
///
- /// 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.
///
+ ///
+ /// Note: OpenIddict automatically sorts the credentials based on the following algorithm:
+ /// • Symmetric keys are always preferred when they can be used for the operation (e.g token signing).
+ /// • X.509 keys are always preferred to non-X.509 asymmetric keys.
+ /// • X.509 keys with the furthest expiration date are preferred.
+ /// • X.509 keys whose backing certificate is not yet valid are never preferred.
+ ///
public List SigningCredentials { get; } = new List();
///
diff --git a/src/OpenIddict.Validation.ServerIntegration/OpenIddictValidationServerIntegrationConfiguration.cs b/src/OpenIddict.Validation.ServerIntegration/OpenIddictValidationServerIntegrationConfiguration.cs
index 1bc085bc..3a2f9bbd 100644
--- a/src/OpenIddict.Validation.ServerIntegration/OpenIddictValidationServerIntegrationConfiguration.cs
+++ b/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;
diff --git a/src/OpenIddict.Validation/OpenIddictValidationBuilder.cs b/src/OpenIddict.Validation/OpenIddictValidationBuilder.cs
index 1576b7b8..73bad25c 100644
--- a/src/OpenIddict.Validation/OpenIddictValidationBuilder.cs
+++ b/src/OpenIddict.Validation/OpenIddictValidationBuilder.cs
@@ -131,7 +131,7 @@ namespace Microsoft.Extensions.DependencyInjection
}
///
- /// Registers the used to decrypt the tokens issued by OpenIddict.
+ /// Registers encryption credentials.
///
/// The encrypting credentials.
/// The .
@@ -146,7 +146,7 @@ namespace Microsoft.Extensions.DependencyInjection
}
///
- /// Registers a used to decrypt the access tokens issued by OpenIddict.
+ /// Registers an encryption key.
///
/// The security key.
/// The .
@@ -183,9 +183,9 @@ namespace Microsoft.Extensions.DependencyInjection
}
///
- /// Registers a that is used to decrypt the tokens issued by OpenIddict.
+ /// Registers an encryption certificate.
///
- /// The certificate used to decrypt the security tokens issued by the validation.
+ /// The encryption certificate.
/// The .
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().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
}
///
- /// Registers a retrieved from an
- /// embedded resource and used to decrypt the tokens issued by OpenIddict.
+ /// Registers an encryption certificate retrieved from an embedded resource.
///
/// The assembly containing the certificate.
/// The name of the embedded resource.
@@ -232,8 +232,7 @@ namespace Microsoft.Extensions.DependencyInjection
#endif
///
- /// Registers a retrieved from an
- /// embedded resource and used to decrypt the tokens issued by OpenIddict.
+ /// Registers an encryption certificate retrieved from an embedded resource.
///
/// The assembly containing the certificate.
/// The name of the embedded resource.
@@ -269,8 +268,7 @@ namespace Microsoft.Extensions.DependencyInjection
}
///
- /// Registers a extracted from a
- /// stream and used to decrypt the tokens issued by OpenIddict.
+ /// Registers an encryption certificate extracted from a stream.
///
/// The stream containing the certificate.
/// The password used to open the certificate.
@@ -286,8 +284,7 @@ namespace Microsoft.Extensions.DependencyInjection
#endif
///
- /// Registers a extracted from a
- /// stream and used to decrypt the tokens issued by OpenIddict.
+ /// Registers an encryption certificate extracted from a stream.
///
/// The stream containing the certificate.
/// The password used to open the certificate.
@@ -318,8 +315,7 @@ namespace Microsoft.Extensions.DependencyInjection
}
///
- /// Registers a 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.
///
/// The thumbprint of the certificate used to identify it in the X.509 store.
/// The .
@@ -350,8 +346,7 @@ namespace Microsoft.Extensions.DependencyInjection
}
///
- /// Registers a 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.
///
/// The thumbprint of the certificate used to identify it in the X.509 store.
/// The name of the X.509 store.
diff --git a/src/OpenIddict.Validation/OpenIddictValidationConfiguration.cs b/src/OpenIddict.Validation/OpenIddictValidationConfiguration.cs
index f3daf110..5708d0e3 100644
--- a/src/OpenIddict.Validation/OpenIddictValidationConfiguration.cs
+++ b/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)) ||
diff --git a/src/OpenIddict.Validation/OpenIddictValidationOptions.cs b/src/OpenIddict.Validation/OpenIddictValidationOptions.cs
index a1efb09a..ea330b26 100644
--- a/src/OpenIddict.Validation/OpenIddictValidationOptions.cs
+++ b/src/OpenIddict.Validation/OpenIddictValidationOptions.cs
@@ -21,9 +21,17 @@ namespace OpenIddict.Validation
public class OpenIddictValidationOptions
{
///
- /// 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.
///
+ ///
+ /// Note: OpenIddict automatically sorts the credentials based on the following algorithm:
+ /// • Symmetric keys are always preferred when they can be used for the operation (e.g token decryption).
+ /// • X.509 keys are always preferred to non-X.509 asymmetric keys.
+ /// • X.509 keys with the furthest expiration date are preferred.
+ /// • X.509 keys whose backing certificate is not yet valid are never preferred.
+ ///
public List EncryptionCredentials { get; } = new List();
///
diff --git a/test/OpenIddict.Server.Tests/OpenIddictServerBuilderTests.cs b/test/OpenIddict.Server.Tests/OpenIddictServerBuilderTests.cs
index cb88d18c..b0964b96 100644
--- a/test/OpenIddict.Server.Tests/OpenIddictServerBuilderTests.cs
+++ b/test/OpenIddict.Server.Tests/OpenIddictServerBuilderTests.cs
@@ -33,11 +33,10 @@ namespace OpenIddict.Server.Tests
// Arrange
var services = CreateServices();
var builder = CreateBuilder(services);
- Action> configuration = null;
// Act and assert
- var exception = Assert.Throws(() => builder.AddEventHandler(configuration));
- Assert.Equal(nameof(configuration), exception.ParamName);
+ var exception = Assert.Throws(() => builder.AddEventHandler(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(() => builder.AddEventHandler(descriptor));
- Assert.Equal(nameof(descriptor), exception.ParamName);
+ var exception = Assert.Throws(() => 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(() => builder.AddEncryptionCredentials(credentials));
- Assert.Equal(nameof(credentials), exception.ParamName);
+ var exception = Assert.Throws(() => 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(() => builder.AddEncryptionKey(key));
- Assert.Equal(nameof(key), exception.ParamName);
+ var exception = Assert.Throws(() => 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();
- key.SetupGet(x => x.PrivateKeyStatus).Returns(PrivateKeyStatus.DoesNotExist);
+ var key = Mock.Of(key => key.PrivateKeyStatus == PrivateKeyStatus.DoesNotExist);
// Act and assert
- var exception = Assert.Throws(() => builder.AddEncryptionKey(key.Object));
+ var exception = Assert.Throws(() => 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(() => builder.RemoveEventHandler(descriptor));
- Assert.Equal(nameof(descriptor), exception.ParamName);
+ var exception = Assert.Throws(() => 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 configuration = null;
// Act and assert
- var exception = Assert.Throws(() => builder.Configure(configuration));
- Assert.Equal(nameof(configuration), exception.ParamName);
+ var exception = Assert.Throws(() => 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(() => builder.AddDevelopmentEncryptionCertificate(subject));
- Assert.Equal(nameof(subject), exception.ParamName);
+ var exception = Assert.Throws(() => 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(() => 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(key => key.PrivateKeyStatus == PrivateKeyStatus.DoesNotExist);
+
+ // Act and assert
+ var exception = Assert.Throws(() => 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(() => builder.SetConfigurationEndpointUris(addresses));
- Assert.Equal(nameof(addresses), exception.ParamName);
+ var exception = Assert.Throws(() => 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(() => builder.SetConfigurationEndpointUris(addresses));
- Assert.Equal(nameof(addresses), exception.ParamName);
+ var exception = Assert.Throws(() => 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(() => builder.SetConfigurationEndpointUris(addresses));
- Assert.Equal(nameof(addresses), exception.ParamName);
+ var exception = Assert.Throws(() => 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(() => builder.SetDeviceEndpointUris(addresses));
- Assert.Equal(nameof(addresses), exception.ParamName);
+ var exception = Assert.Throws(() => 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(() => builder.SetDeviceEndpointUris(addresses));
- Assert.Equal(nameof(addresses), exception.ParamName);
+ var exception = Assert.Throws(() => 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(() => builder.SetDeviceEndpointUris(addresses));
- Assert.Equal(nameof(addresses), exception.ParamName);
+ var exception = Assert.Throws(() => 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(() => builder.SetCryptographyEndpointUris(addresses));
- Assert.Equal(nameof(addresses), exception.ParamName);
+ var exception = Assert.Throws(() => 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(() => builder.SetCryptographyEndpointUris(addresses));
- Assert.Equal(nameof(addresses), exception.ParamName);
+ var exception = Assert.Throws(() => 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(() => builder.SetCryptographyEndpointUris(addresses));
- Assert.Equal(nameof(addresses), exception.ParamName);
+ var exception = Assert.Throws(() => 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(() => builder.SetAuthorizationEndpointUris(addresses));
- Assert.Equal(nameof(addresses), exception.ParamName);
+ var exception = Assert.Throws(() => 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(() => builder.SetAuthorizationEndpointUris(addresses));
- Assert.Equal(nameof(addresses), exception.ParamName);
+ var exception = Assert.Throws(() => 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(() => builder.SetAuthorizationEndpointUris(addresses));
- Assert.Equal(nameof(addresses), exception.ParamName);
+ var exception = Assert.Throws(() => 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(() => builder.SetIntrospectionEndpointUris(addresses));
- Assert.Equal(nameof(addresses), exception.ParamName);
+ var exception = Assert.Throws(() => 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(() => builder.SetIntrospectionEndpointUris(addresses));
- Assert.Equal(nameof(addresses), exception.ParamName);
+ var exception = Assert.Throws(() => 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(() => builder.SetIntrospectionEndpointUris(addresses));
- Assert.Equal(nameof(addresses), exception.ParamName);
+ var exception = Assert.Throws(() => 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(() => builder.SetLogoutEndpointUris(addresses));
- Assert.Equal(nameof(addresses), exception.ParamName);
+ var exception = Assert.Throws(() => 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(() => builder.SetLogoutEndpointUris(addresses));
- Assert.Equal(nameof(addresses), exception.ParamName);
+ var exception = Assert.Throws(() => 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(() => builder.SetLogoutEndpointUris(addresses));
- Assert.Equal(nameof(addresses), exception.ParamName);
+ var exception = Assert.Throws(() => 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(() => builder.SetRevocationEndpointUris(addresses));
- Assert.Equal(nameof(addresses), exception.ParamName);
+ var exception = Assert.Throws(() => 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(() => builder.SetRevocationEndpointUris(addresses));
- Assert.Equal(nameof(addresses), exception.ParamName);
+ var exception = Assert.Throws(() => 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(() => builder.SetRevocationEndpointUris(addresses));
- Assert.Equal(nameof(addresses), exception.ParamName);
+ var exception = Assert.Throws(() => 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(() => builder.SetTokenEndpointUris(addresses));
- Assert.Equal(nameof(addresses), exception.ParamName);
+ var exception = Assert.Throws(() => 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(() => builder.SetTokenEndpointUris(addresses));
- Assert.Equal(nameof(addresses), exception.ParamName);
+ var exception = Assert.Throws(() => 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(() => builder.SetTokenEndpointUris(addresses));
- Assert.Equal(nameof(addresses), exception.ParamName);
+ var exception = Assert.Throws(() => 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(() => builder.SetUserinfoEndpointUris(addresses));
- Assert.Equal(nameof(addresses), exception.ParamName);
+ var exception = Assert.Throws(() => 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(() => builder.SetUserinfoEndpointUris(addresses));
- Assert.Equal(nameof(addresses), exception.ParamName);
+ var exception = Assert.Throws(() => 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(() => builder.SetUserinfoEndpointUris(addresses));
- Assert.Equal(nameof(addresses), exception.ParamName);
+ var exception = Assert.Throws(() => 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(() => 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(() => 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(() => 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());
+
+ 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(() => builder.RegisterClaims(claims));
- Assert.Equal(nameof(claims), exception.ParamName);
+ var exception = Assert.Throws(() => 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(() => builder.RegisterScopes(scopes));
- Assert.Equal(nameof(scopes), exception.ParamName);
+ var exception = Assert.Throws(() => 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(() => 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(() => 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(() => 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();
-
- // 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();