Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge Releases/6.3.4 patch into main #1412

Merged
merged 7 commits into from
Sep 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,26 @@
"group": "20-clients",
}
},
{
"name": "client: MvcDPoP",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build-client-MvcDPoP",
"program": "${workspaceFolder}/clients/src/MvcDPoP/bin/Debug/net8.0/MvcDPoP.dll",
"args": [],
"cwd": "${workspaceFolder}/clients/src/MvcDPoP",
"serverReadyAction": {
"action": "openExternally",
"pattern": "\\bNow listening on:\\s+(https?://\\S+)"
},
"env": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"presentation": {
"hidden": false,
"group": "20-clients",
}
},
{
"name": "client: MvcHybridBackChannel",
"type": "coreclr",
Expand Down
12 changes: 12 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,18 @@
],
"problemMatcher": "$msCompile"
},
{
"label": "build-client-MvcDPoP",
"type": "process",
"command": "dotnet",
"args": [
"build",
"${workspaceFolder}/clients/src/MvcDPoP/MvcDPoP.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "build-client-MvcHybridBackChannel",
"type": "process",
Expand Down
6 changes: 3 additions & 3 deletions src/IdentityServer/Extensions/TokenExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -180,11 +180,11 @@ private static ProofKeyThumbprint GetProofKeyThumbprint(string cnf)
{
if (cnf.IsPresent())
{
var data = JsonSerializer.Deserialize<Dictionary<string, object>>(cnf);
var data = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(cnf);

if (data.TryGetValue(JwtClaimTypes.ConfirmationMethods.JwkThumbprint, out var jkt))
{
var thumbprint = jkt as string;
var thumbprint = jkt.ToString();
if (thumbprint.IsPresent())
{
return new ProofKeyThumbprint { Type = ProofType.DPoP, Thumbprint = thumbprint };
Expand All @@ -193,7 +193,7 @@ private static ProofKeyThumbprint GetProofKeyThumbprint(string cnf)

if (data.TryGetValue("x5t#S256", out var x5t))
{
var thumbprint = x5t as string;
var thumbprint = x5t.ToString();
if (thumbprint.IsPresent())
{
return new ProofKeyThumbprint { Type = ProofType.ClientCertificate, Thumbprint = thumbprint };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,9 @@ public DPoPTokenEndpointTests()
RedirectUris = { "https://client1/callback" },
RequirePkce = false,
AllowOfflineAccess = true,
RefreshTokenUsage = TokenUsage.ReUse,
AllowedScopes = new List<string> { "openid", "profile", "scope1" },
},
}
});

_mockPipeline.Users.Add(new TestUser
Expand Down Expand Up @@ -689,6 +690,78 @@ public async Task public_client_should_not_be_able_to_use_different_dpop_key_for
rtResponse.Error.Should().Be("invalid_dpop_proof");
}

[Fact]
[Trait("Category", Category)]
public async Task public_client_using_same_dpop_key_for_refresh_token_request_should_succeed()
{
_dpopClient.RequireClientSecret = false;

await _mockPipeline.LoginAsync("bob");

_mockPipeline.BrowserClient.AllowAutoRedirect = false;

var url = _mockPipeline.CreateAuthorizeUrl(
clientId: "client1",
responseType: "code",
responseMode: "query",
scope: "openid scope1 offline_access",
redirectUri: "https://client1/callback");
var response = await _mockPipeline.BrowserClient.GetAsync(url);

response.StatusCode.Should().Be(HttpStatusCode.Redirect);
response.Headers.Location.ToString().Should().StartWith("https://client1/callback");

var authorization = new AuthorizeResponse(response.Headers.Location.ToString());
authorization.IsError.Should().BeFalse();

var codeRequest = new AuthorizationCodeTokenRequest
{
Address = IdentityServerPipeline.TokenEndpoint,
ClientId = "client1",
ClientSecret = "secret",
Code = authorization.Code,
RedirectUri = "https://client1/callback",
};
codeRequest.Headers.Add("DPoP", CreateDPoPProofToken());

var codeResponse = await _mockPipeline.BackChannelClient.RequestAuthorizationCodeTokenAsync(codeRequest);
codeResponse.IsError.Should().BeFalse();
codeResponse.TokenType.Should().Be("DPoP");
GetJKTFromAccessToken(codeResponse).Should().Be(_JKT);

var firstRefreshRequest = new RefreshTokenRequest
{
Address = IdentityServerPipeline.TokenEndpoint,
ClientId = "client1",
ClientSecret = "secret",
RefreshToken = codeResponse.RefreshToken
};
firstRefreshRequest.Headers.Add("DPoP", CreateDPoPProofToken());

var firstRefreshResponse = await _mockPipeline.BackChannelClient.RequestRefreshTokenAsync(firstRefreshRequest);
firstRefreshResponse.IsError.Should().BeFalse();
firstRefreshResponse.TokenType.Should().Be("DPoP");
GetJKTFromAccessToken(firstRefreshResponse).Should().Be(_JKT);

var secondRefreshRequest = new RefreshTokenRequest
{
Address = IdentityServerPipeline.TokenEndpoint,
ClientId = "client1",
ClientSecret = "secret",
RefreshToken = codeResponse.RefreshToken
};
secondRefreshRequest.Headers.Add("DPoP", CreateDPoPProofToken());

firstRefreshRequest.Headers.GetValues("DPoP").FirstOrDefault().Should().NotBe(
secondRefreshRequest.Headers.GetValues("DPoP").FirstOrDefault());

var secondRefreshResponse = await _mockPipeline.BackChannelClient.RequestRefreshTokenAsync(secondRefreshRequest);
secondRefreshResponse.IsError.Should().BeFalse(secondRefreshResponse.Error);
secondRefreshResponse.TokenType.Should().Be("DPoP");
GetJKTFromAccessToken(secondRefreshResponse).Should().Be(_JKT);
}


[Fact]
[Trait("Category", Category)]
public async Task missing_proof_token_when_required_on_refresh_token_request_should_fail()
Expand Down
27 changes: 27 additions & 0 deletions test/IdentityServer.UnitTests/Extensions/TokenExtensionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
using Duende.IdentityServer.Models;
using IdentityModel;
using Microsoft.AspNetCore.Authentication;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Text.Json;
using UnitTests.Common;
Expand Down Expand Up @@ -43,4 +45,29 @@ public void TestClaimValueTypes(string type, string value, string valueType, str

Assert.Contains(expected, payloadJson);
}

[Fact]
public void refresh_token_should_get_mtls_x5t_thumprint()
{
var expected = "some hash normally goes here";

var cnf = new Dictionary<string, string>
{
{ "x5t#S256", expected }
};

var refreshToken = new RefreshToken()
{
AccessTokens = new Dictionary<string, Token>
{
{ "token", new Token()
{
Confirmation = JsonSerializer.Serialize(cnf)
}
}
}
};
var thumbprint = refreshToken.GetProofKeyThumbprints().Single().Thumbprint;
Assert.Equal(expected, thumbprint);
}
}
Loading