Skip to content

Commit

Permalink
Fork OidcClient Internally... for now...
Browse files Browse the repository at this point in the history
  • Loading branch information
drasticactions committed Sep 13, 2024
1 parent 11cd081 commit 20bbcb4
Show file tree
Hide file tree
Showing 47 changed files with 3,236 additions and 26 deletions.
3 changes: 0 additions & 3 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +0,0 @@
[submodule "external/IdentityModel.OidcClient"]
path = external/IdentityModel.OidcClient
url = [email protected]:drasticactions/IdentityModel.OidcClient.git
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
<PackageVersion Include="MSTest" Version="3.3.1" />
<PackageVersion Include="Microsoft.Testing.Extensions.CodeCoverage" Version="17.10.4" />
<PackageVersion Include="GitVersion.MSBuild" Version="5.8.1"/>
<PackageVersion Include="IdentityModel" Version="7.0.0" />
</ItemGroup>
<ItemGroup Condition="$(UseMaui) == true">
<PackageVersion Include="Microsoft.Maui.Controls" Version="8.0.21" />
Expand Down
1 change: 0 additions & 1 deletion external/IdentityModel.OidcClient
Submodule IdentityModel.OidcClient deleted from ff0caf
18 changes: 0 additions & 18 deletions src/FishyFlip.sln
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WhiteWindLib", "WhiteWindLi
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WhiteWindLib.Tests", "WhiteWindLib.Tests\WhiteWindLib.Tests.csproj", "{2E024F2D-D298-4F7C-9644-63C417824128}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DPoP", "..\external\IdentityModel.OidcClient\src\DPoP\DPoP.csproj", "{39A09EE2-D468-4D84-8968-041FE3903C83}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OidcClient", "..\external\IdentityModel.OidcClient\src\OidcClient\OidcClient.csproj", "{7CB80A50-11DA-47EC-A68A-E196569776B9}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "External", "External", "{3B7E42B0-2EF8-4926-85DA-E3BE4B5B698F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -39,22 +33,10 @@ Global
{2E024F2D-D298-4F7C-9644-63C417824128}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2E024F2D-D298-4F7C-9644-63C417824128}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2E024F2D-D298-4F7C-9644-63C417824128}.Release|Any CPU.Build.0 = Release|Any CPU
{39A09EE2-D468-4D84-8968-041FE3903C83}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{39A09EE2-D468-4D84-8968-041FE3903C83}.Debug|Any CPU.Build.0 = Debug|Any CPU
{39A09EE2-D468-4D84-8968-041FE3903C83}.Release|Any CPU.ActiveCfg = Release|Any CPU
{39A09EE2-D468-4D84-8968-041FE3903C83}.Release|Any CPU.Build.0 = Release|Any CPU
{7CB80A50-11DA-47EC-A68A-E196569776B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7CB80A50-11DA-47EC-A68A-E196569776B9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7CB80A50-11DA-47EC-A68A-E196569776B9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7CB80A50-11DA-47EC-A68A-E196569776B9}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{39A09EE2-D468-4D84-8968-041FE3903C83} = {3B7E42B0-2EF8-4926-85DA-E3BE4B5B698F}
{7CB80A50-11DA-47EC-A68A-E196569776B9} = {3B7E42B0-2EF8-4926-85DA-E3BE4B5B698F}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {E919E897-0653-4313-9461-A465EF4AB301}
EndGlobalSection
Expand Down
74 changes: 74 additions & 0 deletions src/FishyFlip/DPoP/DPoPExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// <auto-generated/>
#nullable enable
// Copyright (c) Brock Allen & Dominick Baier. 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)
{
// 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)
{
response.Headers.TryGetValues(OidcConstants.HttpHeaders.DPoPNonce, out var headers);
return headers?.FirstOrDefault() ?? string.Empty;
}

///// <summary>
///// Reads the WWW-Authenticate response header to determine if the respone is in error due to DPoP
///// </summary>
//public static bool IsDPoPError(this HttpResponseMessage response)
//{
// if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
// {
// var header = response.Headers.WwwAuthenticate.Where(x => x.Scheme == OidcConstants.AuthenticationSchemes.AuthorizationHeaderDPoP).FirstOrDefault();
// if (header != null && header.Parameter != null)
// {
// // WWW-Authenticate: DPoP error="use_dpop_nonce"
// var values = header.Parameter.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
// var error = values.Select(x =>
// {
// var parts = x.Split(new[] { '=' }, StringSplitOptions.RemoveEmptyEntries);
// if (parts.Length == 2 && parts[0] == OidcConstants.TokenResponse.Error)
// {
// return parts[1].Trim('"');
// }
// return null;
// }).Where(x => x != null).FirstOrDefault();

// return error == OidcConstants.TokenErrors.UseDPoPNonce || error == OidcConstants.TokenErrors.InvalidDPoPProof;
// }
// }

// return false;
//}

/// <summary>
/// Returns the URL without any query params
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
public static string GetDPoPUrl(this HttpRequestMessage request)
{
return request.RequestUri!.Scheme + "://" + request.RequestUri!.Authority + request.RequestUri!.LocalPath;
}
}
17 changes: 17 additions & 0 deletions src/FishyFlip/DPoP/DPoPProof.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// <auto-generated/>
#nullable enable
// Copyright (c) Brock Allen & Dominick Baier. 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!;
}
30 changes: 30 additions & 0 deletions src/FishyFlip/DPoP/DPoPProofPayload.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// <auto-generated/>
#nullable enable
// Copyright (c) Brock Allen & Dominick Baier. 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; }
}
37 changes: 37 additions & 0 deletions src/FishyFlip/DPoP/DPoPProofRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// <auto-generated/>
#nullable enable
// Copyright (c) Brock Allen & Dominick Baier. 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>
public string Url { get; set; } = default!;

/// <summary>
/// The HTTP method of the request
/// </summary>
public string Method { get; set; } = default!;

///// <summary>
///// The string representation of the JSON web key to use for DPoP.
///// </summary>
//public string DPoPJsonWebKey { 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; }
}
107 changes: 107 additions & 0 deletions src/FishyFlip/DPoP/DPoPProofTokenFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// <auto-generated/>
#nullable enable
// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.

using FishyFlip;
using Microsoft.IdentityModel.JsonWebTokens;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;

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 (_jwk.Alg.IsNullOrEmpty())
{
throw new ArgumentException("alg must be set on proof key");
}
}

/// <summary>
/// Creates a DPoP proof token.
/// </summary>
public DPoPProof CreateProofToken(DPoPProofRequest 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))
{
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))
{
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.
using var sha256 = SHA256.Create();
var hash = sha256.ComputeHash(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! };
}
}
Loading

0 comments on commit 20bbcb4

Please sign in to comment.