From 038594e1e72b6566c86b8d99cb7b9f6db98b7361 Mon Sep 17 00:00:00 2001 From: Joe DeCock Date: Tue, 1 Oct 2024 12:13:37 -0500 Subject: [PATCH] Add cnf check to dpop samples --- .../v6/DPoP/Api/DPoP/DPoPExtensions.cs | 13 -------- .../Api/DPoP/DPoPProofValidatonContext.cs | 6 ++++ .../v6/DPoP/Api/DPoP/DPoPProofValidator.cs | 30 +++++++++++++++++-- .../IdentityServer/IdentityServerHost.csproj | 2 +- .../v7/DPoP/Api/DPoP/DPoPExtensions.cs | 13 -------- .../Api/DPoP/DPoPProofValidatonContext.cs | 8 +++++ .../v7/DPoP/Api/DPoP/DPoPProofValidator.cs | 30 +++++++++++++++++-- 7 files changed, 71 insertions(+), 31 deletions(-) diff --git a/IdentityServer/v6/DPoP/Api/DPoP/DPoPExtensions.cs b/IdentityServer/v6/DPoP/Api/DPoP/DPoPExtensions.cs index ca081d9b..553e503d 100644 --- a/IdentityServer/v6/DPoP/Api/DPoP/DPoPExtensions.cs +++ b/IdentityServer/v6/DPoP/Api/DPoP/DPoPExtensions.cs @@ -57,19 +57,6 @@ public static void SetDPoPNonce(this AuthenticationProperties props, string nonc props.Items["DPoP-Nonce"] = nonce; } - /// - /// Create the value of a thumbprint-based cnf claim - /// - public static string CreateThumbprintCnf(this JsonWebKey jwk) - { - var jkt = jwk.CreateThumbprint(); - var values = new Dictionary - { - { JwtClaimTypes.ConfirmationMethods.JwkThumbprint, jkt } - }; - return JsonSerializer.Serialize(values); - } - /// /// Create the value of a thumbprint /// diff --git a/IdentityServer/v6/DPoP/Api/DPoP/DPoPProofValidatonContext.cs b/IdentityServer/v6/DPoP/Api/DPoP/DPoPProofValidatonContext.cs index 891d1e8f..464587dd 100644 --- a/IdentityServer/v6/DPoP/Api/DPoP/DPoPProofValidatonContext.cs +++ b/IdentityServer/v6/DPoP/Api/DPoP/DPoPProofValidatonContext.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using System.Security.Claims; namespace ApiHost; @@ -29,4 +30,9 @@ public class DPoPProofValidatonContext /// The access token /// public string AccessToken { get; set; } + + /// + /// The claims associated with the access token. + /// + public IEnumerable AccessTokenClaims { get; set; } = Enumerable.Empty(); } diff --git a/IdentityServer/v6/DPoP/Api/DPoP/DPoPProofValidator.cs b/IdentityServer/v6/DPoP/Api/DPoP/DPoPProofValidator.cs index 606a0eae..90305608 100644 --- a/IdentityServer/v6/DPoP/Api/DPoP/DPoPProofValidator.cs +++ b/IdentityServer/v6/DPoP/Api/DPoP/DPoPProofValidator.cs @@ -102,10 +102,10 @@ public async Task ValidateAsync(DPoPProofValidatonCont protected virtual Task ValidateHeaderAsync(DPoPProofValidatonContext context, DPoPProofValidatonResult result) { JsonWebToken token; + var handler = new JsonWebTokenHandler(); try { - var handler = new JsonWebTokenHandler(); token = handler.ReadJsonWebToken(context.ProofToken); } catch (Exception ex) @@ -161,7 +161,33 @@ protected virtual Task ValidateHeaderAsync(DPoPProofValidatonContext context, DP result.JsonWebKey = jwkJson; result.JsonWebKeyThumbprint = jwk.CreateThumbprint(); - result.Confirmation = jwk.CreateThumbprintCnf(); + + var accessToken = handler.ReadJsonWebToken(context.AccessToken); + var cnf = accessToken.Claims.FirstOrDefault(c => c.Type == JwtClaimTypes.Confirmation); + if (cnf == null) + { + result.IsError = true; + result.ErrorDescription = "Missing 'cnf' value."; + return Task.CompletedTask; + } + var json = JsonSerializer.Deserialize>(cnf.Value); + if (json == null) + { + result.IsError = true; + result.ErrorDescription = "Invalid 'cnf' value."; + return Task.CompletedTask; + } + if (json.TryGetValue(JwtClaimTypes.ConfirmationMethods.JwkThumbprint, out var jktJson)) + { + var accessTokenJkt = jktJson.ToString(); + if (accessTokenJkt != result.JsonWebKeyThumbprint) + { + result.IsError = true; + result.ErrorDescription = "Invalid 'cnf' value."; + return Task.CompletedTask; + } + result.Confirmation = cnf.Value; + } return Task.CompletedTask; } diff --git a/IdentityServer/v6/DPoP/IdentityServer/IdentityServerHost.csproj b/IdentityServer/v6/DPoP/IdentityServer/IdentityServerHost.csproj index 2465a911..b8d7ab77 100644 --- a/IdentityServer/v6/DPoP/IdentityServer/IdentityServerHost.csproj +++ b/IdentityServer/v6/DPoP/IdentityServer/IdentityServerHost.csproj @@ -6,7 +6,7 @@ - + diff --git a/IdentityServer/v7/DPoP/Api/DPoP/DPoPExtensions.cs b/IdentityServer/v7/DPoP/Api/DPoP/DPoPExtensions.cs index b5d0c5a7..b56c7bc1 100644 --- a/IdentityServer/v7/DPoP/Api/DPoP/DPoPExtensions.cs +++ b/IdentityServer/v7/DPoP/Api/DPoP/DPoPExtensions.cs @@ -55,19 +55,6 @@ public static void SetDPoPNonce(this AuthenticationProperties props, string nonc props.Items["DPoP-Nonce"] = nonce; } - /// - /// Create the value of a thumbprint-based cnf claim - /// - public static string CreateThumbprintCnf(this JsonWebKey jwk) - { - var jkt = jwk.CreateThumbprint(); - var values = new Dictionary - { - { JwtClaimTypes.ConfirmationMethods.JwkThumbprint, jkt } - }; - return JsonSerializer.Serialize(values); - } - /// /// Create the value of a thumbprint /// diff --git a/IdentityServer/v7/DPoP/Api/DPoP/DPoPProofValidatonContext.cs b/IdentityServer/v7/DPoP/Api/DPoP/DPoPProofValidatonContext.cs index fc4bf60e..a4ca897e 100644 --- a/IdentityServer/v7/DPoP/Api/DPoP/DPoPProofValidatonContext.cs +++ b/IdentityServer/v7/DPoP/Api/DPoP/DPoPProofValidatonContext.cs @@ -1,3 +1,5 @@ +using System.Security.Claims; + namespace Api; public class DPoPProofValidatonContext @@ -26,4 +28,10 @@ public class DPoPProofValidatonContext /// The access token /// public required string AccessToken { get; set; } + + /// + /// The claims associated with the access token. + /// + public IEnumerable AccessTokenClaims { get; set; } = Enumerable.Empty(); + } diff --git a/IdentityServer/v7/DPoP/Api/DPoP/DPoPProofValidator.cs b/IdentityServer/v7/DPoP/Api/DPoP/DPoPProofValidator.cs index 067fc069..df0348a3 100644 --- a/IdentityServer/v7/DPoP/Api/DPoP/DPoPProofValidator.cs +++ b/IdentityServer/v7/DPoP/Api/DPoP/DPoPProofValidator.cs @@ -97,10 +97,10 @@ public async Task ValidateAsync(DPoPProofValidatonCont protected virtual Task ValidateHeaderAsync(DPoPProofValidatonContext context, DPoPProofValidatonResult result) { JsonWebToken token; + var handler = new JsonWebTokenHandler(); try { - var handler = new JsonWebTokenHandler(); token = handler.ReadJsonWebToken(context.ProofToken); } catch (Exception ex) @@ -156,7 +156,33 @@ protected virtual Task ValidateHeaderAsync(DPoPProofValidatonContext context, DP result.JsonWebKey = jwkJson; result.JsonWebKeyThumbprint = jwk.CreateThumbprint(); - result.Confirmation = jwk.CreateThumbprintCnf(); + + var accessToken = handler.ReadJsonWebToken(context.AccessToken); + var cnf = accessToken.Claims.FirstOrDefault(c => c.Type == JwtClaimTypes.Confirmation); + if (cnf == null) + { + result.IsError = true; + result.ErrorDescription = "Missing 'cnf' value."; + return Task.CompletedTask; + } + var json = JsonSerializer.Deserialize>(cnf.Value); + if (json == null) + { + result.IsError = true; + result.ErrorDescription = "Invalid 'cnf' value."; + return Task.CompletedTask; + } + if (json.TryGetValue(JwtClaimTypes.ConfirmationMethods.JwkThumbprint, out var jktJson)) + { + var accessTokenJkt = jktJson.ToString(); + if (accessTokenJkt != result.JsonWebKeyThumbprint) + { + result.IsError = true; + result.ErrorDescription = "Invalid 'cnf' value."; + return Task.CompletedTask; + } + result.Confirmation = cnf.Value; + } return Task.CompletedTask; }