Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -1318,7 +1318,9 @@ private static bool IsJkuUriInListOfAllowedDomains(string jkuSetUrl, SignedHttpR
return false;

var uri = new Uri(jkuSetUrl, UriKind.RelativeOrAbsolute);
return signedHttpRequestValidationContext.SignedHttpRequestValidationParameters.AllowedDomainsForJkuRetrieval.Any(domain => uri.Host.EndsWith(domain, StringComparison.OrdinalIgnoreCase));
return signedHttpRequestValidationContext.SignedHttpRequestValidationParameters.AllowedDomainsForJkuRetrieval.Any(domain =>
uri.Host.Equals(domain, StringComparison.OrdinalIgnoreCase) ||
uri.Host.EndsWith("." + domain, StringComparison.OrdinalIgnoreCase));
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -490,17 +490,11 @@ public static TheoryData<ResolvePopKeyTheoryData> ResolvePopKeyFromJkuTheoryData
},
new ResolvePopKeyTheoryData
{
CallContext = new CallContext()
{
PropertyBag = new Dictionary<string, object>()
{
// to simulate http call and satisfy test requirements
{"mockGetPopKeysFromJkuAsync_return1Key", null }
}
},
// TLD-only entries should NOT match arbitrary domains (security fix)
JkuSetUrl = "https://www.contoso.com",
SignedHttpRequestValidationParameters = { AllowResolvingPopKeyFromJku = true, AllowedDomainsForJkuRetrieval = { ".com" }},
TestId = "JkuTurnedOnTopLevelDomainMatch"
ExpectedException = new ExpectedException(typeof(SignedHttpRequestInvalidPopKeyException), string.Format(LogMessages.IDX23038, "https://www.contoso.com", ".com")),
TestId = "JkuTurnedOnTopLevelDomainNoMatch"
},
new ResolvePopKeyTheoryData
{
Expand Down Expand Up @@ -557,6 +551,59 @@ public static TheoryData<ResolvePopKeyTheoryData> ResolvePopKeyFromJkuTheoryData
JkuSetUrl = "https://localhost/keys",
SignedHttpRequestValidationParameters = { AllowResolvingPopKeyFromJku = true, AllowedDomainsForJkuRetrieval = { "localhost" }},
TestId = "JkuTurnedOnLocalUrl"
},
// Security fix: Malicious domain suffix bypass attempts should fail
new ResolvePopKeyTheoryData
{
// evilcontoso.com should NOT match allowlist entry "contoso.com"
JkuSetUrl = "https://evilcontoso.com/jwks",
SignedHttpRequestValidationParameters = { AllowResolvingPopKeyFromJku = true, AllowedDomainsForJkuRetrieval = { "contoso.com" }},
ExpectedException = new ExpectedException(typeof(SignedHttpRequestInvalidPopKeyException), string.Format(LogMessages.IDX23038, "https://evilcontoso.com/jwks", "contoso.com")),
TestId = "JkuDomainBypassAttempt_SuffixWithoutDot_ShouldFail"
},
new ResolvePopKeyTheoryData
{
// evil-contoso.com should NOT match allowlist entry "contoso.com"
JkuSetUrl = "https://evil-contoso.com/jwks",
SignedHttpRequestValidationParameters = { AllowResolvingPopKeyFromJku = true, AllowedDomainsForJkuRetrieval = { "contoso.com" }},
ExpectedException = new ExpectedException(typeof(SignedHttpRequestInvalidPopKeyException), string.Format(LogMessages.IDX23038, "https://evil-contoso.com/jwks", "contoso.com")),
TestId = "JkuDomainBypassAttempt_HyphenatedSuffix_ShouldFail"
},
new ResolvePopKeyTheoryData
{
// notcontoso.com should NOT match allowlist entry "contoso.com"
JkuSetUrl = "https://notcontoso.com/jwks",
SignedHttpRequestValidationParameters = { AllowResolvingPopKeyFromJku = true, AllowedDomainsForJkuRetrieval = { "contoso.com" }},
ExpectedException = new ExpectedException(typeof(SignedHttpRequestInvalidPopKeyException), string.Format(LogMessages.IDX23038, "https://notcontoso.com/jwks", "contoso.com")),
TestId = "JkuDomainBypassAttempt_PrefixedSuffix_ShouldFail"
},
new ResolvePopKeyTheoryData
{
CallContext = new CallContext()
{
PropertyBag = new Dictionary<string, object>()
{
{"mockGetPopKeysFromJkuAsync_return1Key", null }
}
},
// Legitimate subdomain keys.contoso.com SHOULD match allowlist entry "contoso.com"
JkuSetUrl = "https://keys.contoso.com/jwks",
SignedHttpRequestValidationParameters = { AllowResolvingPopKeyFromJku = true, AllowedDomainsForJkuRetrieval = { "contoso.com" }},
TestId = "JkuLegitimateSubdomain_ShouldPass"
},
new ResolvePopKeyTheoryData
{
CallContext = new CallContext()
{
PropertyBag = new Dictionary<string, object>()
{
{"mockGetPopKeysFromJkuAsync_return1Key", null }
}
},
// Exact domain match contoso.com SHOULD match allowlist entry "contoso.com"
JkuSetUrl = "https://contoso.com/jwks",
SignedHttpRequestValidationParameters = { AllowResolvingPopKeyFromJku = true, AllowedDomainsForJkuRetrieval = { "contoso.com" }},
TestId = "JkuExactDomainMatch_ShouldPass"
}
};
}
Expand Down