-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Copy and fork IdentityModel DPoP extensions to fix IdentityModel/Iden…
- Loading branch information
Showing
14 changed files
with
545 additions
and
22 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
53 changes: 53 additions & 0 deletions
53
src/idunno.AtProto/Authentication/IdentityModel.OidcClient.DPoP/DPoPExtensions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
// Copyright (c) Duende Software. All rights reserved. | ||
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. | ||
|
||
using System.Linq; | ||
using System.Net.Http; | ||
|
||
namespace IdentityModel.OidcClient.DPoP; | ||
|
||
/// <summary> | ||
/// Extensions for HTTP request/response messages | ||
/// </summary> | ||
public static class DPoPExtensions | ||
{ | ||
/// <summary> | ||
/// Sets the DPoP nonce request header if nonce is not null. | ||
/// </summary> | ||
public static void SetDPoPProofToken(this HttpRequestMessage request, string? proofToken) | ||
{ | ||
ArgumentNullException.ThrowIfNull(request); | ||
|
||
// remove any old headers | ||
request.Headers.Remove(OidcConstants.HttpHeaders.DPoP); | ||
// set new header | ||
request.Headers.Add(OidcConstants.HttpHeaders.DPoP, proofToken); | ||
} | ||
|
||
/// <summary> | ||
/// Reads the DPoP nonce header from the response | ||
/// </summary> | ||
public static string? GetDPoPNonce(this HttpResponseMessage response) | ||
{ | ||
ArgumentNullException.ThrowIfNull(response); | ||
|
||
|
||
var nonce = response.Headers | ||
.FirstOrDefault(x => x.Key == OidcConstants.HttpHeaders.DPoPNonce) | ||
.Value?.FirstOrDefault(); | ||
return nonce; | ||
} | ||
|
||
/// <summary> | ||
/// Returns the URL without any query params | ||
/// </summary> | ||
/// <param name="request"></param> | ||
/// <returns></returns> | ||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1055:URI-like return values should not be strings", Justification = "Forked code")] | ||
public static string GetDPoPUrl(this HttpRequestMessage request) | ||
{ | ||
ArgumentNullException.ThrowIfNull(request); | ||
|
||
return request.RequestUri!.Scheme + "://" + request.RequestUri!.Authority + request.RequestUri!.LocalPath; | ||
} | ||
} |
15 changes: 15 additions & 0 deletions
15
src/idunno.AtProto/Authentication/IdentityModel.OidcClient.DPoP/DPoPProof.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
// Copyright (c) Duende Software. All rights reserved. | ||
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. | ||
|
||
namespace IdentityModel.OidcClient.DPoP; | ||
|
||
/// <summary> | ||
/// Models a DPoP proof token | ||
/// </summary> | ||
public class DPoPProof | ||
{ | ||
/// <summary> | ||
/// The proof token | ||
/// </summary> | ||
public string ProofToken { get; set; } = default!; | ||
} |
27 changes: 27 additions & 0 deletions
27
src/idunno.AtProto/Authentication/IdentityModel.OidcClient.DPoP/DPoPProofPayload.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
// Copyright (c) Duende Software. All rights reserved. | ||
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. | ||
|
||
using System.Text.Json.Serialization; | ||
|
||
namespace IdentityModel.OidcClient.DPoP; | ||
|
||
/// <summary> | ||
/// Internal class to aid serialization of DPoP proof token payloads. Giving | ||
/// each claim a property allows us to add this type to the source generated | ||
/// serialization | ||
/// </summary> | ||
internal class DPoPProofPayload | ||
{ | ||
[JsonPropertyName(JwtClaimTypes.JwtId)] | ||
public string JwtId { get; set; } = default!; | ||
[JsonPropertyName(JwtClaimTypes.DPoPHttpMethod)] | ||
public string DPoPHttpMethod { get; set; } = default!; | ||
[JsonPropertyName(JwtClaimTypes.DPoPHttpUrl)] | ||
public string DPoPHttpUrl { get; set; } = default!; | ||
[JsonPropertyName(JwtClaimTypes.IssuedAt)] | ||
public long IssuedAt { get; set; } | ||
[JsonPropertyName(JwtClaimTypes. DPoPAccessTokenHash)] | ||
public string? DPoPAccessTokenHash { get; set; } | ||
[JsonPropertyName(JwtClaimTypes. Nonce)] | ||
public string? Nonce { get; set; } | ||
} |
31 changes: 31 additions & 0 deletions
31
src/idunno.AtProto/Authentication/IdentityModel.OidcClient.DPoP/DPoPProofRequest.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
// Copyright (c) Duende Software. All rights reserved. | ||
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. | ||
|
||
namespace IdentityModel.OidcClient.DPoP; | ||
|
||
/// <summary> | ||
/// Models the request information to create a DPoP proof token | ||
/// </summary> | ||
public class DPoPProofRequest | ||
{ | ||
/// <summary> | ||
/// The HTTP URL of the request | ||
/// </summary> | ||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1056:URI-like properties should not be strings", Justification = "Forked Code")] | ||
public string Url { get; set; } = default!; | ||
|
||
/// <summary> | ||
/// The HTTP method of the request | ||
/// </summary> | ||
public string Method { get; set; } = default!; | ||
|
||
/// <summary> | ||
/// The nonce value for the DPoP proof token. | ||
/// </summary> | ||
public string? DPoPNonce { get; set; } | ||
|
||
/// <summary> | ||
/// The access token | ||
/// </summary> | ||
public string? AccessToken { get; set; } | ||
} |
104 changes: 104 additions & 0 deletions
104
src/idunno.AtProto/Authentication/IdentityModel.OidcClient.DPoP/DPoPProofTokenFactory.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
// Copyright (c) Duende Software. All rights reserved. | ||
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. | ||
|
||
using System.Security.Cryptography; | ||
using System.Text; | ||
using System.Text.Json; | ||
|
||
using Microsoft.IdentityModel.JsonWebTokens; | ||
using Microsoft.IdentityModel.Tokens; | ||
|
||
namespace IdentityModel.OidcClient.DPoP; | ||
|
||
/// <summary> | ||
/// Used to create DPoP proof tokens. | ||
/// </summary> | ||
public class DPoPProofTokenFactory | ||
{ | ||
private readonly JsonWebKey _jwk; | ||
|
||
/// <summary> | ||
/// Constructor | ||
/// </summary> | ||
public DPoPProofTokenFactory(string proofKey) | ||
{ | ||
_jwk = new JsonWebKey(proofKey); | ||
|
||
if (string.IsNullOrEmpty(_jwk.Alg)) | ||
{ | ||
throw new ArgumentException("alg must be set on proof key"); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Creates a DPoP proof token. | ||
/// </summary> | ||
public DPoPProof CreateProofToken(DPoPProofRequest request) | ||
{ | ||
ArgumentNullException.ThrowIfNull(request); | ||
|
||
var jsonWebKey = _jwk; | ||
|
||
// jwk: representing the public key chosen by the client, in JSON Web Key (JWK) [RFC7517] format, | ||
// as defined in Section 4.1.3 of [RFC7515]. MUST NOT contain a private key. | ||
Dictionary<string, object> jwk; | ||
if (string.Equals(jsonWebKey.Kty, JsonWebAlgorithmsKeyTypes.EllipticCurve, StringComparison.Ordinal)) | ||
{ | ||
jwk = new Dictionary<string, object> | ||
{ | ||
{ "kty", jsonWebKey.Kty }, | ||
{ "x", jsonWebKey.X }, | ||
{ "y", jsonWebKey.Y }, | ||
{ "crv", jsonWebKey.Crv } | ||
}; | ||
} | ||
else if (string.Equals(jsonWebKey.Kty, JsonWebAlgorithmsKeyTypes.RSA, StringComparison.Ordinal)) | ||
{ | ||
jwk = new Dictionary<string, object> | ||
{ | ||
{ "kty", jsonWebKey.Kty }, | ||
{ "e", jsonWebKey.E }, | ||
{ "n", jsonWebKey.N } | ||
}; | ||
} | ||
else | ||
{ | ||
throw new InvalidOperationException("invalid key type."); | ||
} | ||
|
||
var header = new Dictionary<string, object>() | ||
{ | ||
{ "typ", JwtClaimTypes.JwtTypes.DPoPProofToken }, | ||
{ JwtClaimTypes.JsonWebKey, jwk }, | ||
}; | ||
|
||
var payload = new DPoPProofPayload | ||
{ | ||
JwtId = CryptoRandom.CreateUniqueId(), | ||
DPoPHttpMethod = request.Method, | ||
DPoPHttpUrl = request.Url, | ||
IssuedAt = DateTimeOffset.UtcNow.ToUnixTimeSeconds() | ||
}; | ||
|
||
if (!string.IsNullOrWhiteSpace(request.AccessToken)) | ||
{ | ||
// ath: hash of the access token. The value MUST be the result of a base64url encoding | ||
// the SHA-256 hash of the ASCII encoding of the associated access token's value. | ||
var hash = SHA256.HashData(Encoding.ASCII.GetBytes(request.AccessToken)); | ||
var ath = Base64Url.Encode(hash); | ||
|
||
payload.DPoPAccessTokenHash = ath; | ||
} | ||
|
||
if (!string.IsNullOrEmpty(request.DPoPNonce)) | ||
{ | ||
payload.Nonce = request.DPoPNonce!; | ||
} | ||
|
||
var handler = new JsonWebTokenHandler() { SetDefaultTimesOnTokenCreation = false }; | ||
var key = new SigningCredentials(jsonWebKey, jsonWebKey.Alg); | ||
var proofToken = handler.CreateToken(JsonSerializer.Serialize(payload, SourceGenerationContext.Default.DPoPProofPayload), key, header); | ||
|
||
return new DPoPProof { ProofToken = proofToken! }; | ||
} | ||
} |
88 changes: 88 additions & 0 deletions
88
src/idunno.AtProto/Authentication/IdentityModel.OidcClient.DPoP/JsonWebKeys.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
// Copyright (c) Duende Software. All rights reserved. | ||
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. | ||
|
||
using System; | ||
using System.Security.Cryptography; | ||
using System.Text.Json; | ||
using Microsoft.IdentityModel.Tokens; | ||
|
||
namespace IdentityModel.OidcClient.DPoP; | ||
|
||
/// <summary> | ||
/// Helper to create JSON web keys. | ||
/// </summary> | ||
public static class JsonWebKeys | ||
{ | ||
/// <summary> | ||
/// Creates a new RSA JWK. | ||
/// </summary> | ||
public static JsonWebKey CreateRsa(string algorithm = OidcConstants.Algorithms.Asymmetric.PS256) | ||
{ | ||
using (RSA rsa = RSA.Create()) | ||
{ | ||
var rsaKey = new RsaSecurityKey(rsa); | ||
|
||
var jwk = JsonWebKeyConverter.ConvertFromSecurityKey(rsaKey); | ||
jwk.Alg = algorithm; | ||
|
||
return jwk; | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Creates a new RSA JWK string. | ||
/// </summary> | ||
public static string CreateRsaJson(string algorithm = OidcConstants.Algorithms.Asymmetric.PS256) | ||
{ | ||
return JsonSerializer.Serialize(CreateRsa(algorithm), SourceGenerationContext.Default.JsonWebKey); | ||
} | ||
|
||
/// <summary> | ||
/// Creates a new ECDSA JWK. | ||
/// </summary> | ||
public static JsonWebKey CreateECDsa(string algorithm = OidcConstants.Algorithms.Asymmetric.ES256) | ||
{ | ||
using (ECDsa ecdsa = ECDsa.Create(GetCurveFromCrvValue(GetCurveNameFromSigningAlgorithm(algorithm)))) | ||
{ | ||
|
||
var ecKey = new ECDsaSecurityKey(ecdsa); | ||
JsonWebKey jwk = JsonWebKeyConverter.ConvertFromSecurityKey(ecKey); | ||
jwk.Alg = algorithm; | ||
|
||
return jwk; | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Creates a new ECDSA JWK string. | ||
/// </summary> | ||
public static string CreateECDsaJson(string algorithm = OidcConstants.Algorithms.Asymmetric.ES256) | ||
{ | ||
return JsonSerializer.Serialize(CreateECDsa(algorithm), SourceGenerationContext.Default.JsonWebKey); | ||
} | ||
|
||
internal static string GetCurveNameFromSigningAlgorithm(string alg) | ||
{ | ||
return alg switch | ||
{ | ||
"ES256" => "P-256", | ||
"ES384" => "P-384", | ||
"ES512" => "P-521", | ||
_ => throw new InvalidOperationException($"Unsupported alg type of {alg}"), | ||
}; | ||
} | ||
|
||
/// <summary> | ||
/// Returns the matching named curve for RFC 7518 crv value | ||
/// </summary> | ||
internal static ECCurve GetCurveFromCrvValue(string crv) | ||
{ | ||
return crv switch | ||
{ | ||
JsonWebKeyECTypes.P256 => ECCurve.NamedCurves.nistP256, | ||
JsonWebKeyECTypes.P384 => ECCurve.NamedCurves.nistP384, | ||
JsonWebKeyECTypes.P521 => ECCurve.NamedCurves.nistP521, | ||
_ => throw new InvalidOperationException($"Unsupported curve type of {crv}"), | ||
}; | ||
} | ||
} |
Oops, something went wrong.