Skip to content

Commit 9f3c7f3

Browse files
keegan-carusoKeegan Caruso
andauthored
Remove dependency on AadIssuerValidator.GetTenantIdFromToken in ValidateIssuerSigningKey (#2680)
* Remove dep on AadIssuerValidator * Add DontFailOnMissingTidValidateIssuerSigning AppContextSwitch * Fail if multiple tids --------- Co-authored-by: Keegan Caruso <[email protected]>
1 parent c23c013 commit 9f3c7f3

File tree

5 files changed

+182
-4
lines changed

5 files changed

+182
-4
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT License.
33

4+
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Microsoft.IdentityModel.Validators, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")]
45
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Microsoft.IdentityModel.JsonWebTokens.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")]

src/Microsoft.IdentityModel.JsonWebTokens/JsonWebToken.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -625,6 +625,11 @@ public Claim GetClaim(string key)
625625
return Payload.GetClaim(key, Issuer ?? ClaimsIdentity.DefaultIssuer);
626626
}
627627

628+
/// <summary>
629+
/// Gets the names of the payload claims on the JsonWebToken.
630+
/// </summary>
631+
internal IReadOnlyCollection<string> PayloadClaimNames => Payload._jsonClaims.Keys;
632+
628633
internal ClaimsIdentity ClaimsIdentity
629634
{
630635
get

src/Microsoft.IdentityModel.Validators/AadTokenValidationParametersExtension.cs

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@
22
// Licensed under the MIT License.
33

44
using System;
5+
using System.Collections.Generic;
6+
using System.IdentityModel.Tokens.Jwt;
57
using System.Linq;
8+
using System.Security.Claims;
9+
using Microsoft.IdentityModel.JsonWebTokens;
610
using Microsoft.IdentityModel.Logging;
711
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
812
using Microsoft.IdentityModel.Tokens;
@@ -41,6 +45,13 @@ public static void EnableAadSigningKeyIssuerValidation(this TokenValidationParam
4145
};
4246
}
4347

48+
internal const string DontFailOnMissingTidSwitch = "Switch.Microsoft.IdentityModel.DontFailOnMissingTidValidateIssuerSigning";
49+
50+
private static bool DontFailOnMissingTid()
51+
{
52+
return (AppContext.TryGetSwitch(DontFailOnMissingTidSwitch, out bool dontFailOnMissingTid) && dontFailOnMissingTid);
53+
}
54+
4455
/// <summary>
4556
/// Validates the issuer signing key.
4657
/// </summary>
@@ -67,9 +78,14 @@ internal static bool ValidateIssuerSigningKey(SecurityKey securityKey, SecurityT
6778
if (string.IsNullOrWhiteSpace(signingKeyIssuer))
6879
return true;
6980

70-
var tenantIdFromToken = AadIssuerValidator.GetTenantIdFromToken(securityToken);
81+
var tenantIdFromToken = GetTid(securityToken);
7182
if (string.IsNullOrEmpty(tenantIdFromToken))
72-
return true;
83+
{
84+
if (DontFailOnMissingTid())
85+
return true;
86+
87+
throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidIssuerException(LogMessages.IDX40009));
88+
}
7389

7490
var tokenIssuer = securityToken.Issuer;
7591

@@ -91,7 +107,7 @@ internal static bool ValidateIssuerSigningKey(SecurityKey securityKey, SecurityT
91107

92108
// comparing effectiveSigningKeyIssuer with v2TokenIssuer is required as well because of the following scenario:
93109
// 1. service trusts /common/v2.0 endpoint
94-
// 2. service receieves a v1 token that has issuer like sts.windows.net
110+
// 2. service receives a v1 token that has issuer like sts.windows.net
95111
// 3. signing key issuers will never match sts.windows.net as v1 endpoint doesn't have issuers attached to keys
96112
// v2TokenIssuer is the representation of Token.Issuer (if it was a v2 issuer)
97113
if (effectiveSigningKeyIssuer != tokenIssuer && effectiveSigningKeyIssuer != v2TokenIssuer)
@@ -101,6 +117,48 @@ internal static bool ValidateIssuerSigningKey(SecurityKey securityKey, SecurityT
101117
return true;
102118
}
103119

120+
private static string GetTid(SecurityToken securityToken)
121+
{
122+
switch (securityToken)
123+
{
124+
case JsonWebToken jsonWebToken:
125+
if (jsonWebToken.TryGetPayloadValue<string>(AadIssuerValidatorConstants.Tid, out string tid))
126+
{
127+
EnforceSingleClaimCaseInsensitive(jsonWebToken.PayloadClaimNames, AadIssuerValidatorConstants.Tid);
128+
return tid;
129+
}
130+
131+
return string.Empty;
132+
133+
case JwtSecurityToken jwtSecurityToken:
134+
if ((jwtSecurityToken.Payload.TryGetValue(AadIssuerValidatorConstants.Tid, out object tidObject) && tidObject is string jwtTid))
135+
{
136+
EnforceSingleClaimCaseInsensitive(jwtSecurityToken.Payload.Keys, AadIssuerValidatorConstants.Tid);
137+
return jwtTid;
138+
}
139+
140+
return string.Empty;
141+
142+
default:
143+
throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidIssuerException(LogMessages.IDX40010));
144+
}
145+
}
146+
147+
private static void EnforceSingleClaimCaseInsensitive(IEnumerable<string> keys, string claimType)
148+
{
149+
bool claimSeen = false;
150+
foreach (var key in keys)
151+
{
152+
if (string.Equals(key, claimType, StringComparison.OrdinalIgnoreCase))
153+
{
154+
if (claimSeen)
155+
throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidIssuerException(LogHelper.FormatInvariant(LogMessages.IDX40011, claimType)));
156+
157+
claimSeen = true;
158+
}
159+
}
160+
}
161+
104162
/// <summary>
105163
/// Validates the issuer signing key certificate.
106164
/// </summary>

src/Microsoft.IdentityModel.Validators/LogMessages.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,9 @@ internal static class LogMessages
2424
public const string IDX40005 = "IDX40005: Token issuer: '{0}', does not match the signing key issuer: '{1}'.";
2525
public const string IDX40007 = "IDX40007: RequireSignedTokens property on ValidationParameters is set to true, but the issuer signing key is null.";
2626
public const string IDX40008 = "IDX40008: When setting LastKnownGoodLifetime, the value must be greater than or equal to zero. value: '{0}'.";
27+
28+
public const string IDX40009 = "IDX40009: Either the 'tid' claim was not found or it didn't have a value.";
29+
public const string IDX40010 = "IDX40010: The SecurityToken must be a 'JsonWebToken' or 'JwtSecurityToken'";
30+
public const string IDX40011 = "IDX40011: The SecurityToken has multiple instances of the '{0}' claim.";
2731
}
2832
}

test/Microsoft.IdentityModel.Validators.Tests/AadSigningKeyIssuerValidatorTests.cs

Lines changed: 111 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,15 @@
1111
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
1212
using Microsoft.IdentityModel.TestUtils;
1313
using Microsoft.IdentityModel.Tokens;
14+
using Microsoft.IdentityModel.Tokens.Saml2;
1415
using Xunit;
1516

1617
#pragma warning disable CS3016 // Arrays as attribute arguments is not CLS-compliant
1718

1819
namespace Microsoft.IdentityModel.Validators.Tests
1920
{
21+
// Serialize as one of the tests depends on static state (app context)
22+
[Collection(nameof(AadSigningKeyIssuerValidatorTests))]
2023
public class AadSigningKeyIssuerValidatorTests
2124
{
2225
[Theory, MemberData(nameof(EnableAadSigningKeyIssuerValidationTestCases))]
@@ -167,6 +170,7 @@ public void ValidateIssuerSigningKeyTests(AadSigningKeyIssuerTheoryData theoryDa
167170

168171
try
169172
{
173+
theoryData.SetupAction?.Invoke();
170174
var result = AadTokenValidationParametersExtension.ValidateIssuerSigningKey(theoryData.SecurityKey, theoryData.SecurityToken, theoryData.OpenIdConnectConfiguration);
171175
theoryData.ExpectedException.ProcessNoException(context);
172176
Assert.True(result);
@@ -175,6 +179,10 @@ public void ValidateIssuerSigningKeyTests(AadSigningKeyIssuerTheoryData theoryDa
175179
{
176180
theoryData.ExpectedException.ProcessException(ex, context);
177181
}
182+
finally
183+
{
184+
theoryData.TearDownAction?.Invoke();
185+
}
178186

179187
TestUtilities.AssertFailIfErrors(context);
180188
}
@@ -294,7 +302,17 @@ public static TheoryData<AadSigningKeyIssuerTheoryData> ValidateIssuerSigningKey
294302
TestId = "MissingTenantIdClaimInToken",
295303
SecurityKey = KeyingMaterial.JsonWebKeyP256,
296304
SecurityToken = new JwtSecurityToken(),
297-
OpenIdConnectConfiguration = mockConfiguration
305+
OpenIdConnectConfiguration = mockConfiguration,
306+
ExpectedException = ExpectedException.SecurityTokenInvalidIssuerException("IDX40009")
307+
});
308+
309+
theoryData.Add(new AadSigningKeyIssuerTheoryData
310+
{
311+
TestId = "WrongSecurityKeyType",
312+
SecurityKey = KeyingMaterial.JsonWebKeyP256,
313+
SecurityToken = new Saml2SecurityToken(new Saml2Assertion(new Saml2NameIdentifier("nameIdentifier"))),
314+
OpenIdConnectConfiguration = mockConfiguration,
315+
ExpectedException = ExpectedException.SecurityTokenInvalidIssuerException("IDX40010")
298316
});
299317

300318
theoryData.Add(new AadSigningKeyIssuerTheoryData
@@ -321,6 +339,94 @@ public static TheoryData<AadSigningKeyIssuerTheoryData> ValidateIssuerSigningKey
321339
ExpectedException = ExpectedException.SecurityTokenInvalidIssuerException("IDX40004")
322340
});
323341

342+
theoryData.Add(new AadSigningKeyIssuerTheoryData
343+
{
344+
TestId = "Doesnt_Fail_With_Switch",
345+
SecurityKey = KeyingMaterial.JsonWebKeyP256,
346+
SecurityToken = new JwtSecurityToken(),
347+
OpenIdConnectConfiguration = mockConfiguration,
348+
SetupAction = () => AppContext.SetSwitch(AadTokenValidationParametersExtension.DontFailOnMissingTidSwitch, true),
349+
TearDownAction = () => AppContext.SetSwitch(AadTokenValidationParametersExtension.DontFailOnMissingTidSwitch, false)
350+
});
351+
352+
theoryData.Add(new AadSigningKeyIssuerTheoryData
353+
{
354+
TestId = "Fail_With_Switch_False",
355+
SecurityKey = KeyingMaterial.JsonWebKeyP256,
356+
SecurityToken = new JwtSecurityToken(),
357+
OpenIdConnectConfiguration = mockConfiguration,
358+
ExpectedException = ExpectedException.SecurityTokenInvalidIssuerException("IDX40009"),
359+
SetupAction = () => AppContext.SetSwitch(AadTokenValidationParametersExtension.DontFailOnMissingTidSwitch, false),
360+
TearDownAction = () => AppContext.SetSwitch(AadTokenValidationParametersExtension.DontFailOnMissingTidSwitch, isEnabled: false)
361+
});
362+
363+
theoryData.Add(new AadSigningKeyIssuerTheoryData
364+
{
365+
TestId = "Doesnt_Fail_With_Switch",
366+
SecurityKey = KeyingMaterial.JsonWebKeyP256,
367+
SecurityToken = new JwtSecurityToken(),
368+
OpenIdConnectConfiguration = mockConfiguration,
369+
SetupAction = () => AppContext.SetSwitch(AadTokenValidationParametersExtension.DontFailOnMissingTidSwitch, true),
370+
TearDownAction = () => AppContext.SetSwitch(AadTokenValidationParametersExtension.DontFailOnMissingTidSwitch, false)
371+
});
372+
373+
theoryData.Add(new AadSigningKeyIssuerTheoryData
374+
{
375+
TestId = "Fail_With_Switch_False_JsonWebToken",
376+
SecurityKey = KeyingMaterial.JsonWebKeyP256,
377+
SecurityToken = new JsonWebToken(Default.Jwt(Default.SecurityTokenDescriptor(Default.SymmetricSigningCredentials, [issClaim]))),
378+
OpenIdConnectConfiguration = mockConfiguration,
379+
ExpectedException = ExpectedException.SecurityTokenInvalidIssuerException("IDX40009"),
380+
SetupAction = () => AppContext.SetSwitch(AadTokenValidationParametersExtension.DontFailOnMissingTidSwitch, false),
381+
TearDownAction = () => AppContext.SetSwitch(AadTokenValidationParametersExtension.DontFailOnMissingTidSwitch, isEnabled: false)
382+
});
383+
384+
theoryData.Add(new AadSigningKeyIssuerTheoryData
385+
{
386+
TestId = "Doesnt_Fail_With_Switch_JsonWebToken",
387+
SecurityKey = KeyingMaterial.JsonWebKeyP256,
388+
SecurityToken = new JsonWebToken(Default.Jwt(Default.SecurityTokenDescriptor(Default.SymmetricSigningCredentials, [issClaim]))),
389+
OpenIdConnectConfiguration = mockConfiguration,
390+
SetupAction = () => AppContext.SetSwitch(AadTokenValidationParametersExtension.DontFailOnMissingTidSwitch, true),
391+
TearDownAction = () => AppContext.SetSwitch(AadTokenValidationParametersExtension.DontFailOnMissingTidSwitch, false)
392+
});
393+
394+
theoryData.Add(new AadSigningKeyIssuerTheoryData
395+
{
396+
TestId = "Fails_With_Multiple_tids",
397+
SecurityKey = KeyingMaterial.JsonWebKeyP256,
398+
SecurityToken = new JsonWebToken(
399+
Default.Jwt(Default.SecurityTokenDescriptor(
400+
Default.SymmetricSigningCredentials,
401+
[tidClaim, issClaim, new Claim("TID", Guid.NewGuid().ToString())]))),
402+
ExpectedException = ExpectedException.SecurityTokenInvalidIssuerException("IDX40011"),
403+
OpenIdConnectConfiguration = mockConfiguration
404+
});
405+
406+
theoryData.Add(new AadSigningKeyIssuerTheoryData
407+
{
408+
TestId = "Fails_With_Multiple_tids_alternate_order",
409+
SecurityKey = KeyingMaterial.JsonWebKeyP256,
410+
SecurityToken = new JsonWebToken(
411+
Default.Jwt(Default.SecurityTokenDescriptor(
412+
Default.SymmetricSigningCredentials,
413+
[issClaim, new Claim("TID", Guid.NewGuid().ToString()), tidClaim]))),
414+
ExpectedException = ExpectedException.SecurityTokenInvalidIssuerException("IDX40011"),
415+
OpenIdConnectConfiguration = mockConfiguration
416+
});
417+
418+
theoryData.Add(new AadSigningKeyIssuerTheoryData
419+
{
420+
TestId = "Fails_With_no standard_tid",
421+
SecurityKey = KeyingMaterial.JsonWebKeyP256,
422+
SecurityToken = new JsonWebToken(
423+
Default.Jwt(Default.SecurityTokenDescriptor(
424+
Default.SymmetricSigningCredentials,
425+
[issClaim, new Claim("TID", Guid.NewGuid().ToString())]))),
426+
ExpectedException = ExpectedException.SecurityTokenInvalidIssuerException("IDX40009"),
427+
OpenIdConnectConfiguration = mockConfiguration
428+
});
429+
324430
return theoryData;
325431
}
326432
}
@@ -346,6 +452,10 @@ public class AadSigningKeyIssuerTheoryData : TheoryDataBase
346452
public bool SetDelegateUsingConfig { get; set; } = false;
347453

348454
public bool SetDelegateWithoutConfig { get; set; } = false;
455+
456+
public Action SetupAction { get; set; }
457+
458+
public Action TearDownAction { get; set; }
349459
}
350460
}
351461
}

0 commit comments

Comments
 (0)