diff --git a/identity-server/nuget.config b/identity-server/nuget.config deleted file mode 100644 index 54e660f9c..000000000 --- a/identity-server/nuget.config +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/identity-server/src/IdentityServer/Validation/Default/PrivateKeyJwtSecretValidator.cs b/identity-server/src/IdentityServer/Validation/Default/PrivateKeyJwtSecretValidator.cs index 354c932a1..09bbc5bf4 100644 --- a/identity-server/src/IdentityServer/Validation/Default/PrivateKeyJwtSecretValidator.cs +++ b/identity-server/src/IdentityServer/Validation/Default/PrivateKeyJwtSecretValidator.cs @@ -96,13 +96,14 @@ public async Task ValidateAsync(IEnumerable secr // Read the token so we can get the "typ" header value if it exists. var handlerForHeader = new JsonWebTokenHandler(); var tokenForHeader = handlerForHeader.ReadJsonWebToken(jwtTokenString); - var jwtTyp = tokenForHeader.GetHeaderValue("typ"); - // If strict mode is not enabled by option but the "typ" header value "client-authentication+jwt" is provided, // enforce strict audience validation. - if (string.Equals(jwtTyp, "client-authentication+jwt", StringComparison.OrdinalIgnoreCase)) + if (tokenForHeader.TryGetHeaderValue("typ", out var jwtTyp)) { - enforceStrictAud = true; + if (string.Equals(jwtTyp, "client-authentication+jwt", StringComparison.OrdinalIgnoreCase)) + { + enforceStrictAud = true; + } } } catch (Exception ex) @@ -252,4 +253,4 @@ private bool AudiencesMatchIgnoringTrailingSlash(string tokenAudience, string va return false; } -} \ No newline at end of file +} diff --git a/identity-server/test/IdentityServer.UnitTests/Validation/Secrets/PrivateKeyJwtSecretValidation.cs b/identity-server/test/IdentityServer.UnitTests/Validation/Secrets/PrivateKeyJwtSecretValidation.cs index fb28c538c..d6852bf23 100644 --- a/identity-server/test/IdentityServer.UnitTests/Validation/Secrets/PrivateKeyJwtSecretValidation.cs +++ b/identity-server/test/IdentityServer.UnitTests/Validation/Secrets/PrivateKeyJwtSecretValidation.cs @@ -44,17 +44,24 @@ public PrivateKeyJwtSecretValidation() _clients = new InMemoryClientStore(ClientValidationTestClients.Get()); } - private JwtSecurityToken CreateToken(string clientId, string aud = "https://idsrv.com/", DateTime? nowOverride = null, bool setTyp = false) + private JwtSecurityToken CreateToken(string clientId, string aud = "https://idsrv.com/", DateTime? nowOverride = null, Typ typ = Typ.None) { - return CreateTokenHelper(clientId, new Claim(JwtClaimTypes.Audience, aud), nowOverride, setTyp); + return CreateTokenHelper(clientId, new Claim(JwtClaimTypes.Audience, aud), nowOverride, typ); } - private JwtSecurityToken CreateToken(string clientId, string[] audiences, DateTime? nowOverride = null, bool setTyp = false) + private JwtSecurityToken CreateToken(string clientId, string[] audiences, DateTime? nowOverride = null, Typ typ = Typ.None) { - return CreateTokenHelper(clientId, new Claim(JwtClaimTypes.Audience, JsonSerializer.Serialize(audiences), JsonClaimValueTypes.JsonArray), nowOverride, setTyp); + return CreateTokenHelper(clientId, new Claim(JwtClaimTypes.Audience, JsonSerializer.Serialize(audiences), JsonClaimValueTypes.JsonArray), nowOverride, typ); } - private JwtSecurityToken CreateTokenHelper(string clientId, Claim aud = null, DateTime? nowOverride = null, bool setJwtTyp = false) + public enum Typ + { + None, + JWT, + ClientAuthentication + } + + private JwtSecurityToken CreateTokenHelper(string clientId, Claim aud = null, DateTime? nowOverride = null, Typ jwtTyp = Typ.None) { var certificate = TestCert.Load(); var now = nowOverride ?? DateTime.UtcNow; @@ -80,10 +87,18 @@ private JwtSecurityToken CreateTokenHelper(string clientId, Claim aud = null, Da ) ); - if (setJwtTyp) + if (jwtTyp == Typ.ClientAuthentication) { token.Header["typ"] = "client-authentication+jwt"; } + else if (jwtTyp == Typ.JWT) + { + token.Header["typ"] = "JWT"; + } + else + { + token.Header.Remove("typ"); + } return token; } @@ -144,12 +159,17 @@ public async Task Valid_Certificate_Base64() } [Theory] - [InlineData("https://idsrv.com")] - [InlineData("https://idsrv.com/")] - [InlineData("https://idsrv.com/connect/token")] - [InlineData("https://idsrv.com/connect/ciba")] - [InlineData("https://idsrv.com/connect/par")] - public async Task Strict_audience_disabled_and_no_typ_header_allows_legacy_aud_values(string aud) + [InlineData("https://idsrv.com", Typ.None)] + [InlineData("https://idsrv.com/", Typ.None)] + [InlineData("https://idsrv.com/connect/token", Typ.None)] + [InlineData("https://idsrv.com/connect/ciba", Typ.None)] + [InlineData("https://idsrv.com/connect/par", Typ.None)] + [InlineData("https://idsrv.com", Typ.JWT)] + [InlineData("https://idsrv.com/", Typ.JWT)] + [InlineData("https://idsrv.com/connect/token", Typ.JWT)] + [InlineData("https://idsrv.com/connect/ciba", Typ.JWT)] + [InlineData("https://idsrv.com/connect/par", Typ.JWT)] + public async Task Strict_audience_disabled_and_no_typ_header_or_JWT_typ_header_allows_legacy_aud_values(string aud, Typ typ) { _options.Preview.StrictClientAssertionAudienceValidation = false; @@ -159,7 +179,7 @@ public async Task Strict_audience_disabled_and_no_typ_header_allows_legacy_aud_v var secret = new ParsedSecret { Id = clientId, - Credential = new JwtSecurityTokenHandler().WriteToken(CreateToken(clientId, aud: aud, setTyp: false)), + Credential = new JwtSecurityTokenHandler().WriteToken(CreateToken(clientId, aud: aud, typ: typ)), Type = IdentityServerConstants.ParsedSecretTypes.JwtBearer }; @@ -185,7 +205,7 @@ public async Task Strict_audience_from_options_validates_audience(string aud, bo var secret = new ParsedSecret { Id = clientId, - Credential = new JwtSecurityTokenHandler().WriteToken(CreateToken(clientId, aud: aud, setTyp: true)), + Credential = new JwtSecurityTokenHandler().WriteToken(CreateToken(clientId, aud: aud, typ: Typ.ClientAuthentication)), Type = IdentityServerConstants.ParsedSecretTypes.JwtBearer }; @@ -211,7 +231,7 @@ public async Task Strict_audience_from_typ_header_validates_audience(string aud, var secret = new ParsedSecret { Id = clientId, - Credential = new JwtSecurityTokenHandler().WriteToken(CreateToken(clientId, aud: aud, setTyp: true)), + Credential = new JwtSecurityTokenHandler().WriteToken(CreateToken(clientId, aud: aud, typ: Typ.ClientAuthentication)), Type = IdentityServerConstants.ParsedSecretTypes.JwtBearer }; @@ -221,11 +241,13 @@ public async Task Strict_audience_from_typ_header_validates_audience(string aud, } [Theory] - [InlineData(true, true, false)] - [InlineData(true, false, false)] - [InlineData(false, true, false)] - [InlineData(false, false, true)] - public async Task Strict_audience_does_not_allow_single_valued_arrays(bool setTyp, bool setStrictOption, bool expectedResult) + [InlineData(Typ.ClientAuthentication, true, false)] + [InlineData(Typ.ClientAuthentication, false, false)] + [InlineData(Typ.None, true, false)] + [InlineData(Typ.None, false, true)] + [InlineData(Typ.JWT, true, false)] + [InlineData(Typ.JWT, false, true)] + public async Task Strict_audience_does_not_allow_single_valued_arrays(Typ typ, bool setStrictOption, bool expectedResult) { _options.Preview.StrictClientAssertionAudienceValidation = setStrictOption; @@ -234,7 +256,7 @@ public async Task Strict_audience_does_not_allow_single_valued_arrays(bool setTy var token = new JwtSecurityTokenHandler().WriteToken(CreateToken( clientId, audiences: ["https://idsrv.com/connect/token"], - setTyp: setTyp)); + typ: typ)); var secret = new ParsedSecret { @@ -249,11 +271,13 @@ public async Task Strict_audience_does_not_allow_single_valued_arrays(bool setTy } [Theory] - [InlineData(true, true, false)] - [InlineData(true, false, false)] - [InlineData(false, true, false)] - [InlineData(false, false, true)] - public async Task Strict_audience_does_not_allow_multi_valued_arrays(bool setTyp, bool setStrictOption, bool expectedResult) + [InlineData(Typ.ClientAuthentication, true, false)] + [InlineData(Typ.ClientAuthentication, false, false)] + [InlineData(Typ.None, true, false)] + [InlineData(Typ.None, false, true)] + [InlineData(Typ.JWT, true, false)] + [InlineData(Typ.JWT, false, true)] + public async Task Strict_audience_does_not_allow_multi_valued_arrays(Typ typ, bool setStrictOption, bool expectedResult) { _options.Preview.StrictClientAssertionAudienceValidation = setStrictOption; @@ -262,7 +286,7 @@ public async Task Strict_audience_does_not_allow_multi_valued_arrays(bool setTyp var token = new JwtSecurityTokenHandler().WriteToken(CreateToken( clientId, audiences: ["https://idsrv.com", "https://idsrv.com/"], - setTyp: setTyp)); + typ: typ)); var secret = new ParsedSecret { @@ -277,17 +301,19 @@ public async Task Strict_audience_does_not_allow_multi_valued_arrays(bool setTyp } [Theory] - [InlineData(true, true, true)] - [InlineData(true, false, true)] - [InlineData(false, true, false)] - [InlineData(false, false, true)] - public async Task Strict_audience_only_allows_correct_type(bool setTyp, bool enforceStrict, bool expectedResult) + [InlineData(Typ.ClientAuthentication, true, true)] + [InlineData(Typ.ClientAuthentication, false, true)] + [InlineData(Typ.None, true, false)] + [InlineData(Typ.None, false, true)] + [InlineData(Typ.JWT, true, false)] + [InlineData(Typ.JWT, false, true)] + public async Task Strict_audience_only_allows_correct_type(Typ typ, bool enforceStrict, bool expectedResult) { _options.Preview.StrictClientAssertionAudienceValidation = enforceStrict; var clientId = "certificate_base64_valid"; var client = await _clients.FindEnabledClientByIdAsync(clientId); - var token = new JwtSecurityTokenHandler().WriteToken(CreateToken(clientId, setTyp: setTyp)); + var token = new JwtSecurityTokenHandler().WriteToken(CreateToken(clientId, typ: typ)); var secret = new ParsedSecret { @@ -419,4 +445,4 @@ public async Task Invalid_Unsigned_Token() result.Success.Should().BeFalse(); } -} \ No newline at end of file +}