private async Task InvokeCryptographyEndpointAsync() { var notification = new CryptographyEndpointContext(Context, Options); // Metadata requests must be made via GET. // See http://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationRequest if (!string.Equals(Request.Method, "GET", StringComparison.OrdinalIgnoreCase)) { Logger.LogError("Cryptography endpoint: invalid method used."); return; } foreach (var credentials in Options.SigningCredentials) { // Ignore the key if it's not supported. if (!credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.RsaSha256Signature) && !credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.RsaSha384Signature) && !credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.RsaSha512Signature)) { Logger.LogDebug("Cryptography endpoint: unsupported signing key ignored. " + "Only asymmetric security keys supporting RS256, RS384 " + "or RS512 can be exposed via the JWKS endpoint."); continue; } // Determine whether the security key is a RSA key embedded in a X.509 certificate. var x509SecurityKey = credentials.Key as X509SecurityKey; if (x509SecurityKey != null) { // Create a new JSON Web Key exposing the // certificate instead of its public RSA key. notification.Keys.Add(new JsonWebKey { Use = JsonWebKeyUseNames.Sig, Kty = JsonWebAlgorithmsKeyTypes.RSA, // Resolve the JWA identifier from the algorithm specified in the credentials. Alg = OpenIdConnectServerHelpers.GetJwtAlgorithm(credentials.Algorithm), // By default, use the key identifier specified in the signing credentials. If none is specified, // use the hexadecimal representation of the certificate's SHA-1 hash as the unique key identifier. Kid = credentials.Kid ?? x509SecurityKey.KeyId ?? x509SecurityKey.Certificate.Thumbprint, // x5t must be base64url-encoded. // See http://tools.ietf.org/html/draft-ietf-jose-json-web-key-31#section-4.8 X5t = Base64UrlEncoder.Encode(x509SecurityKey.Certificate.GetCertHash()), // Unlike E or N, the certificates contained in x5c // must be base64-encoded and not base64url-encoded. // See http://tools.ietf.org/html/draft-ietf-jose-json-web-key-31#section-4.7 X5c = { Convert.ToBase64String(x509SecurityKey.Certificate.RawData) } }); } var rsaSecurityKey = credentials.Key as RsaSecurityKey; if (rsaSecurityKey != null) { // Export the RSA public key. notification.Keys.Add(new JsonWebKey { Use = JsonWebKeyUseNames.Sig, Kty = JsonWebAlgorithmsKeyTypes.RSA, // Resolve the JWA identifier from the algorithm specified in the credentials. Alg = OpenIdConnectServerHelpers.GetJwtAlgorithm(credentials.Algorithm), // By default, use the key identifier specified in the signing credentials. // If none is specified, try to extract it from the security key itself or // create one using the base64url-encoded representation of the modulus. Kid = credentials.Kid ?? rsaSecurityKey.KeyId ?? // Note: use the first 40 chars to avoid using a too long identifier. Base64UrlEncoder.Encode(rsaSecurityKey.Parameters.Modulus) .Substring(0, 40).ToUpperInvariant(), // Both E and N must be base64url-encoded. // See http://tools.ietf.org/html/draft-ietf-jose-json-web-key-31#appendix-A.1 E = Base64UrlEncoder.Encode(rsaSecurityKey.Parameters.Exponent), N = Base64UrlEncoder.Encode(rsaSecurityKey.Parameters.Modulus) }); } } await Options.Provider.CryptographyEndpoint(notification); if (notification.HandledResponse) { return; } var payload = new JObject(); var keys = new JArray(); foreach (var key in notification.Keys) { var item = new JObject(); // Ensure a key type has been provided. // See http://tools.ietf.org/html/draft-ietf-jose-json-web-key-31#section-4.1 if (string.IsNullOrEmpty(key.Kty)) { Logger.LogWarning("Cryptography endpoint: a JSON Web Key didn't " + "contain the mandatory 'Kty' parameter and has been ignored."); continue; } // Create a dictionary associating the // JsonWebKey components with their values. var parameters = new Dictionary <string, string> { [JsonWebKeyParameterNames.Kid] = key.Kid, [JsonWebKeyParameterNames.Use] = key.Use, [JsonWebKeyParameterNames.Kty] = key.Kty, [JsonWebKeyParameterNames.Alg] = key.Alg, [JsonWebKeyParameterNames.X5t] = key.X5t, [JsonWebKeyParameterNames.X5u] = key.X5u, [JsonWebKeyParameterNames.E] = key.E, [JsonWebKeyParameterNames.N] = key.N }; foreach (var parameter in parameters) { if (!string.IsNullOrEmpty(parameter.Value)) { item.Add(parameter.Key, parameter.Value); } } if (key.KeyOps.Any()) { item.Add(JsonWebKeyParameterNames.KeyOps, JArray.FromObject(key.KeyOps)); } if (key.X5c.Any()) { item.Add(JsonWebKeyParameterNames.X5c, JArray.FromObject(key.X5c)); } keys.Add(item); } payload.Add(JsonWebKeyParameterNames.Keys, keys); var context = new CryptographyEndpointResponseContext(Context, Options, payload); await Options.Provider.CryptographyEndpointResponse(context); if (context.HandledResponse) { return; } using (var buffer = new MemoryStream()) using (var writer = new JsonTextWriter(new StreamWriter(buffer))) { payload.WriteTo(writer); writer.Flush(); Response.ContentLength = buffer.Length; Response.ContentType = "application/json;charset=UTF-8"; buffer.Seek(offset: 0, loc: SeekOrigin.Begin); await buffer.CopyToAsync(Response.Body, 4096, Context.RequestAborted); } }
/// <summary> /// Called before the authorization server starts emitting the OpenID Connect JSON Web Key set associated with this instance. /// If the web application wishes to produce the JSON Web Key set directly in this call, it may write to the /// context.Response directly and should call context.RequestCompleted to stop the default behavior from executing. /// </summary> /// <param name="context">The context of the event carries information in and results out.</param> /// <returns>Task to enable asynchronous execution</returns> public virtual Task CryptographyEndpointResponse(CryptographyEndpointResponseContext context) => OnCryptographyEndpointResponse(context);