diff --git a/.editorconfig b/.editorconfig
index c1a5fbd1b6..ce94f2d21d 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -59,7 +59,7 @@ dotnet_style_predefined_type_for_member_access = true:suggestion
# name all constant fields using PascalCase
dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields
-dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style
+dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style
dotnet_naming_symbols.constant_fields.applicable_kinds = field
dotnet_naming_symbols.constant_fields.required_modifiers = const
@@ -69,7 +69,7 @@ dotnet_naming_style.pascal_case_style.capitalization = pascal_case
# static fields should have s_ prefix
dotnet_naming_rule.static_fields_should_have_prefix.severity = suggestion
dotnet_naming_rule.static_fields_should_have_prefix.symbols = static_fields
-dotnet_naming_rule.static_fields_should_have_prefix.style = static_prefix_style
+dotnet_naming_rule.static_fields_should_have_prefix.style = static_prefix_style
dotnet_naming_symbols.static_fields.applicable_kinds = field
dotnet_naming_symbols.static_fields.required_modifiers = static
@@ -80,7 +80,7 @@ dotnet_naming_style.static_prefix_style.capitalization = camel_case
# internal and private fields should be _camelCase
dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion
dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields
-dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style
+dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style
dotnet_naming_symbols.private_internal_fields.applicable_kinds = field
dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal
@@ -140,6 +140,23 @@ csharp_space_between_method_declaration_name_and_open_parenthesis = false
csharp_space_between_method_declaration_parameter_list_parentheses = false
csharp_space_between_parentheses = false
csharp_space_between_square_brackets = false
+csharp_using_directive_placement = outside_namespace:silent
+csharp_prefer_simple_using_statement = true:suggestion
+csharp_prefer_braces = true:silent
+csharp_style_namespace_declarations = block_scoped:silent
+csharp_style_prefer_method_group_conversion = true:silent
+csharp_style_prefer_top_level_statements = true:silent
+csharp_style_prefer_primary_constructors = true:suggestion
+csharp_style_expression_bodied_lambdas = true:silent
+csharp_style_expression_bodied_local_functions = false:silent
+csharp_style_prefer_null_check_over_type_check = true:suggestion
+csharp_prefer_simple_default_expression = true:suggestion
+csharp_style_prefer_local_over_anonymous_function = true:suggestion
+csharp_style_prefer_index_operator = true:suggestion
+csharp_style_prefer_range_operator = true:suggestion
+csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion
+csharp_style_prefer_tuple_swap = true:suggestion
+csharp_style_prefer_utf8_string_literals = true:suggestion
# C++ Files
@@ -826,7 +843,7 @@ dotnet_diagnostic.CA1832.severity = none
dotnet_diagnostic.CA1833.severity = none
# Consider using 'StringBuilder.Append(char)' when applicable
-dotnet_diagnostic.CA1834.severity = none
+dotnet_diagnostic.CA1834.severity = warning
# Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync'
dotnet_diagnostic.CA1835.severity = none
@@ -1284,3 +1301,21 @@ dotnet_diagnostic.RS1012.severity = none
dotnet_diagnostic.RS1013.severity = none
dotnet_diagnostic.RS1014.severity = none
+dotnet_style_operator_placement_when_wrapping = beginning_of_line
+tab_width = 4
+dotnet_style_coalesce_expression = true:suggestion
+dotnet_style_null_propagation = true:suggestion
+dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
+dotnet_style_prefer_auto_properties = true:silent
+dotnet_style_object_initializer = true:suggestion
+dotnet_style_prefer_collection_expression = true:suggestion
+dotnet_style_collection_initializer = true:suggestion
+dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
+dotnet_style_prefer_conditional_expression_over_assignment = true:silent
+dotnet_style_prefer_conditional_expression_over_return = true:silent
+dotnet_style_explicit_tuple_names = true:suggestion
+dotnet_style_prefer_inferred_tuple_names = true:suggestion
+dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
+dotnet_style_prefer_compound_assignment = true:suggestion
+dotnet_style_prefer_simplified_interpolation = true:suggestion
+dotnet_style_namespace_match_folder = true:suggestion
diff --git a/src/client/Microsoft.Identity.Client/AppConfig/AuthorityInfo.cs b/src/client/Microsoft.Identity.Client/AppConfig/AuthorityInfo.cs
index ff2daa5b8c..8a26e3ec43 100644
--- a/src/client/Microsoft.Identity.Client/AppConfig/AuthorityInfo.cs
+++ b/src/client/Microsoft.Identity.Client/AppConfig/AuthorityInfo.cs
@@ -138,6 +138,14 @@ private AuthorityInfo(
AuthorityType == AuthorityType.B2C ||
AuthorityType == AuthorityType.Ciam;
+ ///
+ /// True if SHA2 and PSS can be used for creating the client credential from a certificate
+ ///
+ internal bool IsSha2CredentialSupported =>
+ AuthorityType != AuthorityType.Dsts &&
+ AuthorityType != AuthorityType.Generic &&
+ AuthorityType != AuthorityType.Adfs;
+
#region Builders
internal static AuthorityInfo FromAuthorityUri(string authorityUri, bool validateAuthority)
{
diff --git a/src/client/Microsoft.Identity.Client/AuthScheme/PoP/PoPAuthenticationScheme.cs b/src/client/Microsoft.Identity.Client/AuthScheme/PoP/PoPAuthenticationScheme.cs
index bc9da76395..7a6f0d8fb9 100644
--- a/src/client/Microsoft.Identity.Client/AuthScheme/PoP/PoPAuthenticationScheme.cs
+++ b/src/client/Microsoft.Identity.Client/AuthScheme/PoP/PoPAuthenticationScheme.cs
@@ -75,9 +75,9 @@ public string FormatAccessToken(MsalAccessTokenCacheItem msalAccessTokenCacheIte
}
var header = new JObject();
- header[JsonWebTokenConstants.ReservedHeaderParameters.Algorithm] = _popCryptoProvider.CryptographicAlgorithm;
- header[JsonWebTokenConstants.ReservedHeaderParameters.KeyId] = KeyId;
- header[JsonWebTokenConstants.ReservedHeaderParameters.Type] = Constants.PoPTokenType;
+ header[JsonWebTokenConstants.Algorithm] = _popCryptoProvider.CryptographicAlgorithm;
+ header[JsonWebTokenConstants.KeyId] = KeyId;
+ header[JsonWebTokenConstants.Type] = Constants.PoPTokenType;
var body = CreateBody(msalAccessTokenCacheItem);
diff --git a/src/client/Microsoft.Identity.Client/Internal/ClientCredential/CertificateAndClaimsClientCredential.cs b/src/client/Microsoft.Identity.Client/Internal/ClientCredential/CertificateAndClaimsClientCredential.cs
index 8fa03c353a..d3f482aa75 100644
--- a/src/client/Microsoft.Identity.Client/Internal/ClientCredential/CertificateAndClaimsClientCredential.cs
+++ b/src/client/Microsoft.Identity.Client/Internal/ClientCredential/CertificateAndClaimsClientCredential.cs
@@ -1,7 +1,10 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
+using System;
using System.Collections.Generic;
+using System.Runtime.ConstrainedExecution;
+using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
@@ -17,17 +20,20 @@ internal class CertificateAndClaimsClientCredential : IClientCredential
{
private readonly IDictionary _claimsToSign;
private readonly bool _appendDefaultClaims;
- private readonly string _base64EncodedThumbprint; // x5t
+
public X509Certificate2 Certificate { get; }
public AssertionType AssertionType => AssertionType.CertificateWithoutSni;
- public CertificateAndClaimsClientCredential(X509Certificate2 certificate, IDictionary claimsToSign, bool appendDefaultClaims)
+ public CertificateAndClaimsClientCredential(
+ X509Certificate2 certificate,
+ IDictionary claimsToSign,
+ bool appendDefaultClaims)
{
Certificate = certificate;
_claimsToSign = claimsToSign;
_appendDefaultClaims = appendDefaultClaims;
- _base64EncodedThumbprint = Base64UrlHelpers.Encode(certificate.GetCertHash());
+
}
public Task AddConfidentialClientParametersAsync(
@@ -37,6 +43,7 @@ public Task AddConfidentialClientParametersAsync(
string clientId,
string tokenEndpoint,
bool sendX5C,
+ bool useSha2AndPss,
CancellationToken cancellationToken)
{
var jwtToken = new JsonWebToken(
@@ -46,7 +53,7 @@ public Task AddConfidentialClientParametersAsync(
_claimsToSign,
_appendDefaultClaims);
- string assertion = jwtToken.Sign(Certificate, _base64EncodedThumbprint, sendX5C);
+ string assertion = jwtToken.Sign(Certificate, sendX5C, useSha2AndPss);
oAuth2Client.AddBodyParameter(OAuth2Parameter.ClientAssertionType, OAuth2AssertionType.JwtBearer);
oAuth2Client.AddBodyParameter(OAuth2Parameter.ClientAssertion, assertion);
diff --git a/src/client/Microsoft.Identity.Client/Internal/ClientCredential/IClientCredential.cs b/src/client/Microsoft.Identity.Client/Internal/ClientCredential/IClientCredential.cs
index fed446b39a..7ab578f345 100644
--- a/src/client/Microsoft.Identity.Client/Internal/ClientCredential/IClientCredential.cs
+++ b/src/client/Microsoft.Identity.Client/Internal/ClientCredential/IClientCredential.cs
@@ -27,6 +27,7 @@ Task AddConfidentialClientParametersAsync(
string clientId,
string tokenEndpoint,
bool sendX5C,
+ bool useSha2,
CancellationToken cancellationToken);
}
}
diff --git a/src/client/Microsoft.Identity.Client/Internal/ClientCredential/SecretStringClientCredential.cs b/src/client/Microsoft.Identity.Client/Internal/ClientCredential/SecretStringClientCredential.cs
index 1421291802..ce5f26d7fc 100644
--- a/src/client/Microsoft.Identity.Client/Internal/ClientCredential/SecretStringClientCredential.cs
+++ b/src/client/Microsoft.Identity.Client/Internal/ClientCredential/SecretStringClientCredential.cs
@@ -22,7 +22,15 @@ public SecretStringClientCredential(string secret)
Secret = secret;
}
- public Task AddConfidentialClientParametersAsync(OAuth2Client oAuth2Client, ILoggerAdapter logger, ICryptographyManager cryptographyManager, string clientId, string tokenEndpoint, bool sendX5C, CancellationToken cancellationToken)
+ public Task AddConfidentialClientParametersAsync(
+ OAuth2Client oAuth2Client,
+ ILoggerAdapter logger,
+ ICryptographyManager cryptographyManager,
+ string clientId,
+ string tokenEndpoint,
+ bool sendX5C,
+ bool useSha2,
+ CancellationToken cancellationToken)
{
oAuth2Client.AddBodyParameter(OAuth2Parameter.ClientSecret, Secret);
return Task.CompletedTask;
diff --git a/src/client/Microsoft.Identity.Client/Internal/ClientCredential/SignedAssertionClientCredential.cs b/src/client/Microsoft.Identity.Client/Internal/ClientCredential/SignedAssertionClientCredential.cs
index 10b7c8b9ef..2af6124fcf 100644
--- a/src/client/Microsoft.Identity.Client/Internal/ClientCredential/SignedAssertionClientCredential.cs
+++ b/src/client/Microsoft.Identity.Client/Internal/ClientCredential/SignedAssertionClientCredential.cs
@@ -22,7 +22,15 @@ public SignedAssertionClientCredential(string signedAssertion)
_signedAssertion = signedAssertion;
}
- public Task AddConfidentialClientParametersAsync(OAuth2Client oAuth2Client, ILoggerAdapter logger, ICryptographyManager cryptographyManager, string clientId, string tokenEndpoint, bool sendX5C, CancellationToken cancellationToken)
+ public Task AddConfidentialClientParametersAsync(
+ OAuth2Client oAuth2Client,
+ ILoggerAdapter logger,
+ ICryptographyManager cryptographyManager,
+ string clientId,
+ string tokenEndpoint,
+ bool sendX5C,
+ bool useSha2,
+ CancellationToken cancellationToken)
{
oAuth2Client.AddBodyParameter(OAuth2Parameter.ClientAssertionType, OAuth2AssertionType.JwtBearer);
oAuth2Client.AddBodyParameter(OAuth2Parameter.ClientAssertion, _signedAssertion);
diff --git a/src/client/Microsoft.Identity.Client/Internal/ClientCredential/SignedAssertionDelegateClientCredential.cs b/src/client/Microsoft.Identity.Client/Internal/ClientCredential/SignedAssertionDelegateClientCredential.cs
index 1f7aeed7a3..bb7337b4ad 100644
--- a/src/client/Microsoft.Identity.Client/Internal/ClientCredential/SignedAssertionDelegateClientCredential.cs
+++ b/src/client/Microsoft.Identity.Client/Internal/ClientCredential/SignedAssertionDelegateClientCredential.cs
@@ -28,7 +28,15 @@ public SignedAssertionDelegateClientCredential(Func ClaimsToSign { get; private set; }
- public long ValidTo { get { return Payload.ValidTo; } }
+ public const long JwtToAadLifetimeInSeconds = 60 * 10; // Ten minutes
+
+ private readonly IDictionary _claimsToSign;
private readonly ICryptographyManager _cryptographyManager;
+ private readonly string _clientId;
+ private readonly string _audience;
private readonly bool _appendDefaultClaims;
public JsonWebToken(ICryptographyManager cryptographyManager, string clientId, string audience)
{
_cryptographyManager = cryptographyManager;
- DateTime validFrom = DateTime.UtcNow;
-
- Payload = new JWTPayload
- {
- Audience = audience,
- Issuer = clientId,
- ValidFrom = ConvertToTimeT(validFrom),
- ValidTo = ConvertToTimeT(validFrom + TimeSpan.FromSeconds(JsonWebTokenConstants.JwtToAadLifetimeInSeconds)),
- Subject = clientId,
- JwtIdentifier = Guid.NewGuid().ToString()
- };
+ _clientId = clientId;
+ _audience = audience;
}
- public JsonWebToken(ICryptographyManager cryptographyManager, string clientId, string audience, IDictionary claimsToSign, bool appendDefaultClaims = false)
- : this(cryptographyManager, clientId, audience)
+ public JsonWebToken(
+ ICryptographyManager cryptographyManager,
+ string clientId,
+ string audience,
+ IDictionary claimsToSign,
+ bool appendDefaultClaims = false)
+ : this(cryptographyManager, clientId, audience)
{
- ClaimsToSign = claimsToSign;
+ _claimsToSign = claimsToSign;
_appendDefaultClaims = appendDefaultClaims;
}
- public string Sign(X509Certificate2 certificate, string base64EncodedThumbprint, bool sendX5C)
+ private string CreateJsonPayload()
{
- // Base64Url encoded header and claims
- string token = Encode(certificate, base64EncodedThumbprint, sendX5C);
+ long validFrom = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
+ long validTo = validFrom + JwtToAadLifetimeInSeconds; // 10 min
- // Length check before sign
- if (MaxTokenLength < token.Length)
+ if (_claimsToSign == null || _claimsToSign.Count == 0)
{
- throw new MsalException(MsalError.EncodedTokenTooLong);
+ return $$"""{"aud":"{{_audience}}","iss":"{{_clientId}}","sub":"{{_clientId}}","nbf":"{{validFrom}}","exp":"{{validTo}}","jti":"{{Guid.NewGuid()}}"}""";
}
- byte[] signature = _cryptographyManager.SignWithCertificate(token, certificate);
- return string.Concat(token, ".", UrlEncodeSegment(signature));
- }
+ // extra claims
+ StringBuilder payload = new StringBuilder();
- private static string EncodeSegment(string segment)
- {
- return UrlEncodeSegment(Encoding.UTF8.GetBytes(segment));
- }
+ if (_appendDefaultClaims)
+ {
+ string defaultClaims = $$"""{"aud":"{{_audience}}","iss":"{{_clientId}}","sub":"{{_clientId}}","nbf":"{{validFrom}}","exp":"{{validTo}}","jti":"{{Guid.NewGuid()}}",""";
+ payload.Append(defaultClaims);
+ }
+ else
+ {
+ payload.Append('{');
+ }
- private static string UrlEncodeSegment(byte[] segment)
- {
- return Base64UrlHelpers.Encode(segment);
- }
+ int i = 0;
+ foreach (var kvp in _claimsToSign)
+ {
+ payload.Append($"\"{kvp.Key}\":\"{kvp.Value}\"");
+
+ if (i!= _claimsToSign.Count-1)
+ {
+ i++;
+ payload.Append(',');
+ }
+ }
- private static string EncodeHeaderToJson(X509Certificate2 certificate, string base64EncodedThumbprint, bool sendX5C)
- {
- JWTHeaderWithCertificate header = new JWTHeaderWithCertificate(certificate, base64EncodedThumbprint, sendX5C);
- return JsonHelper.SerializeToJson(header);
- }
+ payload.Append('}');
- internal static long ConvertToTimeT(DateTime time)
- {
- var startTime = new DateTime(1970, 1, 1, 0, 0, 0, 0);
- TimeSpan diff = time - startTime;
- return (long)diff.TotalSeconds;
+ return payload.ToString();
}
- private string Encode(X509Certificate2 certificate, string base64EncodedThumbprint, bool sendCertificate)
+ public string Sign(X509Certificate2 certificate, bool sendX5C, bool useSha2AndPss)
{
- // Header segment
- string jsonHeader = EncodeHeaderToJson(certificate, base64EncodedThumbprint, sendCertificate);
-
- string encodedHeader = EncodeSegment(jsonHeader);
- string jsonPayload;
-
- // Payload segment
- if (ClaimsToSign != null && ClaimsToSign.Any())
- {
- var json = new JObject();
-
- if (_appendDefaultClaims)
- {
- json[JsonWebTokenConstants.ReservedClaims.Audience] = Payload.Audience;
- json[JsonWebTokenConstants.ReservedClaims.Issuer] = Payload.Issuer;
- json[JsonWebTokenConstants.ReservedClaims.NotBefore] = Payload.ValidFrom;
- json[JsonWebTokenConstants.ReservedClaims.ExpiresOn] = Payload.ValidTo;
- json[JsonWebTokenConstants.ReservedClaims.Subject] = Payload.Subject;
- json[JsonWebTokenConstants.ReservedClaims.JwtIdentifier] = Payload.JwtIdentifier;
- }
-
- foreach (var claim in ClaimsToSign)
- {
- json[claim.Key] = claim.Value;
- }
+ // Base64Url encoded header and claims
+ string token = CreateJwtHeaderAndBody(certificate, sendX5C, useSha2AndPss);
- jsonPayload = JsonHelper.JsonObjectToString(json);
- }
- else
+ // Length check before sign
+ if (MaxTokenLength < token.Length)
{
- jsonPayload = JsonHelper.SerializeToJson(Payload);
+ throw new MsalClientException(MsalError.EncodedTokenTooLong);
}
- string encodedPayload = EncodeSegment(jsonPayload);
+ //codeql [SM03799] Backwards Compatibility: Requires using PKCS1 padding for Identity Providers not supporting PSS (AAD, B2C, CIAM support it)
+ byte[] signature = _cryptographyManager.SignWithCertificate(
+ token,
+ certificate,
+ useSha2AndPss ?
+ RSASignaturePadding.Pss : // ESTS added support for PSS
+ RSASignaturePadding.Pkcs1); // Other IdPs may only support PKCS1
- return string.Concat(encodedHeader, ".", encodedPayload);
+ return string.Concat(token, ".", Base64UrlHelpers.Encode(signature));
}
- [JsonObject]
- [Preserve(AllMembers = true)]
- internal class JWTHeader
+ private static string CreateJsonHeader(X509Certificate2 certificate, bool sendX5C, bool useSha2AndPss)
{
- public JWTHeader(X509Certificate2 certificate)
- {
- Certificate = certificate;
- }
+ string thumbprint = ComputeCertThumbprint(certificate, useSha2AndPss);
- protected X509Certificate2 Certificate { get; }
+ string alg = useSha2AndPss ? "PS256" : "RS256";
+ string thumbprintKey = useSha2AndPss ? "x5t#S256" : "x5t";
+ string header;
- [JsonProperty(JsonWebTokenConstants.ReservedHeaderParameters.Type)]
- public string Type
+ if (sendX5C)
{
- get { return JsonWebTokenConstants.JWTHeaderType; }
-
- set
- {
- // This setter is required by the serializer
- }
+#if NETFRAMEWORK
+ string x5cValue = Convert.ToBase64String(certificate.GetRawCertData());
+#else
+ string x5cValue = Convert.ToBase64String(certificate.RawData);
+#endif
+ header = $$"""{"alg":"{{alg}}","typ":"JWT","{{thumbprintKey}}":"{{thumbprint}}","x5c":"{{x5cValue}}"}""";
}
-
- [JsonProperty(JsonWebTokenConstants.ReservedHeaderParameters.Algorithm)]
- public string Algorithm
+ else
{
- get
- {
- return Certificate == null
- ? JsonWebTokenConstants.Algorithms.None
- : JsonWebTokenConstants.Algorithms.RsaSha256;
- }
- set
- {
- // This setter is required by the serializer
- }
+ header = $$"""{"alg":"{{alg}}","typ":"JWT","{{thumbprintKey}}":"{{thumbprint}}"}""";
}
+
+ return header;
}
- [JsonObject]
- [Preserve(AllMembers = true)]
- internal class JWTPayload
+ private static string ComputeCertThumbprint(X509Certificate2 certificate, bool useSha2)
{
- [JsonProperty(JsonWebTokenConstants.ReservedClaims.Audience)]
- public string Audience { get; set; }
-
- [JsonProperty(JsonWebTokenConstants.ReservedClaims.Issuer)]
- public string Issuer { get; set; }
-
- [JsonProperty(JsonWebTokenConstants.ReservedClaims.NotBefore)]
-#if SUPPORTS_SYSTEM_TEXT_JSON
- [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)]
-#endif
- public long ValidFrom { get; set; }
+ string thumbprint = null;
- [JsonProperty(JsonWebTokenConstants.ReservedClaims.ExpiresOn)]
-#if SUPPORTS_SYSTEM_TEXT_JSON
- [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)]
-#endif
- public long ValidTo { get; set; }
+ if (useSha2)
+ {
+#if NET6_0_OR_GREATER
-#if SUPPORTS_SYSTEM_TEXT_JSON
- [JsonProperty(JsonWebTokenConstants.ReservedClaims.Subject)]
- [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
+ thumbprint = Base64UrlHelpers.Encode(certificate.GetCertHash(HashAlgorithmName.SHA256));
#else
- [JsonProperty(
- PropertyName = JsonWebTokenConstants.ReservedClaims.Subject,
- DefaultValueHandling = DefaultValueHandling.Ignore)]
+ using (var hasher = SHA256.Create())
+ {
+ byte[] hash = hasher.ComputeHash(certificate.RawData);
+ thumbprint = Base64UrlHelpers.Encode(hash);
+ }
#endif
- public string Subject { get; set; }
+ }
+ else
+ {
+ thumbprint = Base64UrlHelpers.Encode(certificate.GetCertHash());
+ }
-#if SUPPORTS_SYSTEM_TEXT_JSON
- [JsonProperty(JsonWebTokenConstants.ReservedClaims.JwtIdentifier)]
- [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
-#else
- [JsonProperty(
- PropertyName = JsonWebTokenConstants.ReservedClaims.JwtIdentifier,
- DefaultValueHandling = DefaultValueHandling.Ignore)]
-#endif
- public string JwtIdentifier { get; set; }
+ return thumbprint;
}
- [JsonObject]
- [Preserve(AllMembers = true)]
- internal sealed class JWTHeaderWithCertificate : JWTHeader
+ private string CreateJwtHeaderAndBody(
+ X509Certificate2 certificate,
+ bool addX5C,
+ bool useSha2AndPss)
{
- public JWTHeaderWithCertificate(X509Certificate2 certificate, string base64EncodedThumbprint, bool sendCertificate)
- : base(certificate)
- {
- // this is just Base64UrlHelpers.Encode(certificate.GetCertHash()) but computed higher up so that it can be cached
- X509CertificateThumbprint = base64EncodedThumbprint;
- X509CertificateKeyId = certificate.Thumbprint;
-
- X509CertificatePublicCertValue = null;
-
- if (sendCertificate)
- {
-#if NETFRAMEWORK
- X509CertificatePublicCertValue = Convert.ToBase64String(certificate.GetRawCertData());
-#else
- X509CertificatePublicCertValue = Convert.ToBase64String(certificate.RawData);
-#endif
- }
- }
- ///
- /// x5t = base64 URL encoded cert thumbprint
- ///
- ///
- /// Mandatory for ADFS 2019
- ///
- [JsonProperty(JsonWebTokenConstants.ReservedHeaderParameters.X509CertificateThumbprint)]
- public string X509CertificateThumbprint { get; set; }
+ string jsonHeader = CreateJsonHeader(certificate, addX5C, useSha2AndPss);
+ string encodedHeader = Base64UrlHelpers.EncodeString(jsonHeader);
- ///
- /// kid (key id) = cert thumbprint
- ///
- ///
- /// Key Id is an optional param, but recommended. Wilson adds both kid and x5t to JWT header
- ///
-#if SUPPORTS_SYSTEM_TEXT_JSON
- [JsonProperty(JsonWebTokenConstants.ReservedHeaderParameters.KeyId)]
- [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
-#else
- [JsonProperty(
- PropertyName = JsonWebTokenConstants.ReservedHeaderParameters.KeyId,
- DefaultValueHandling = DefaultValueHandling.Ignore)]
-#endif
- public string X509CertificateKeyId { get; set; }
+ string jsonPayload = CreateJsonPayload();
+ string encodedPayload = Base64UrlHelpers.EncodeString(jsonPayload);
-#if SUPPORTS_SYSTEM_TEXT_JSON
- [JsonProperty(JsonWebTokenConstants.ReservedHeaderParameters.X509CertificatePublicCertValue)]
- [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
-#else
- [JsonProperty(
- PropertyName = JsonWebTokenConstants.ReservedHeaderParameters.X509CertificatePublicCertValue,
- DefaultValueHandling = DefaultValueHandling.Ignore)]
-#endif
- public string X509CertificatePublicCertValue { get; set; }
+ return string.Concat(encodedHeader, ".", encodedPayload);
}
}
}
diff --git a/src/client/Microsoft.Identity.Client/Internal/JsonWebTokenConstants.cs b/src/client/Microsoft.Identity.Client/Internal/JsonWebTokenConstants.cs
index f0d2860dcb..a5bac1fbbe 100644
--- a/src/client/Microsoft.Identity.Client/Internal/JsonWebTokenConstants.cs
+++ b/src/client/Microsoft.Identity.Client/Internal/JsonWebTokenConstants.cs
@@ -5,49 +5,28 @@ namespace Microsoft.Identity.Client.Internal
{
internal class JsonWebTokenConstants
{
- public const uint JwtToAadLifetimeInSeconds = 60 * 10; // Ten minutes
- public const string JWTHeaderType = "JWT";
+ ///
+ /// Encryption algorithm used, e.g. ES256
+ /// https://tools.ietf.org/html/rfc7515#section-4.1.1
+ ///
+ public const string Algorithm = "alg";
- internal class Algorithms
- {
- public const string RsaSha256 = "RS256";
- public const string None = "none";
- }
+ ///
+ /// The type of token e.g. JWT
+ /// https://tools.ietf.org/html/rfc7519#section-5.1
+ ///
+ public const string Type = "typ";
- internal class ReservedClaims
- {
- public const string Audience = "aud";
- public const string Issuer = "iss";
- public const string Subject = "sub";
- public const string NotBefore = "nbf";
- public const string ExpiresOn = "exp";
- public const string JwtIdentifier = "jti";
- }
+ ///
+ /// Key ID, can be an X509 cert thumbprint. When used with a JWK, the "kid" value is used to match a JWK "kid"
+ /// parameter value
+ /// https://tools.ietf.org/html/rfc7515#section-4.1.4
+ ///
+ public const string KeyId = "kid";
- internal static class ReservedHeaderParameters
- {
- ///
- /// Encryption algorithm used, e.g. ES256
- /// https://tools.ietf.org/html/rfc7515#section-4.1.1
- ///
- public const string Algorithm = "alg";
+ public const string X509CertificateThumbprint = "x5t";
- ///
- /// The type of token e.g. JWT
- /// https://tools.ietf.org/html/rfc7519#section-5.1
- ///
- public const string Type = "typ";
+ public const string X509CertificatePublicCertValue = "x5c";
- ///
- /// Key ID, can be an X509 cert thumbprint. When used with a JWK, the "kid" value is used to match a JWK "kid"
- /// parameter value
- /// https://tools.ietf.org/html/rfc7515#section-4.1.4
- ///
- public const string KeyId = "kid";
-
- public const string X509CertificateThumbprint = "x5t";
-
- public const string X509CertificatePublicCertValue = "x5c";
- }
}
}
diff --git a/src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs b/src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs
index d36e7275d2..5dc063725e 100644
--- a/src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs
+++ b/src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs
@@ -185,7 +185,7 @@ private void LogSuccessfulTelemetryToClient(AuthenticationResult authenticationR
if (authenticationResult.AuthenticationResultMetadata.RefreshOn.HasValue)
{
- telemetryEventDetails.SetProperty(TelemetryConstants.RefreshOn, DateTimeHelpers.DateTimeToUnixTimestampMilliseconds(authenticationResult.AuthenticationResultMetadata.RefreshOn.Value));
+ telemetryEventDetails.SetProperty(TelemetryConstants.RefreshOn, authenticationResult.AuthenticationResultMetadata.RefreshOn.Value.ToUnixTimeMilliseconds());
}
telemetryEventDetails.SetProperty(TelemetryConstants.AssertionType, (int)AuthenticationRequestParameters.RequestContext.ApiEvent.AssertionType);
telemetryEventDetails.SetProperty(TelemetryConstants.Endpoint, AuthenticationRequestParameters.Authority.AuthorityInfo.CanonicalAuthority.ToString());
diff --git a/src/client/Microsoft.Identity.Client/OAuth2/TokenClient.cs b/src/client/Microsoft.Identity.Client/OAuth2/TokenClient.cs
index 371dec8017..9341a7381c 100644
--- a/src/client/Microsoft.Identity.Client/OAuth2/TokenClient.cs
+++ b/src/client/Microsoft.Identity.Client/OAuth2/TokenClient.cs
@@ -139,6 +139,8 @@ private async Task AddBodyParamsAndHeadersAsync(
() => "[TokenClient] Before adding the client assertion / secret");
var tokenEndpoint = await _requestParams.Authority.GetTokenEndpointAsync(_requestParams.RequestContext).ConfigureAwait(false);
+
+ bool useSha2 = _requestParams.AuthorityManager.Authority.AuthorityInfo.IsSha2CredentialSupported;
await _serviceBundle.Config.ClientCredential.AddConfidentialClientParametersAsync(
_oAuth2Client,
_requestParams.RequestContext.Logger,
@@ -146,6 +148,7 @@ await _serviceBundle.Config.ClientCredential.AddConfidentialClientParametersAsyn
_requestParams.AppConfig.ClientId,
tokenEndpoint,
_requestParams.SendX5C,
+ useSha2,
cancellationToken).ConfigureAwait(false);
_requestParams.RequestContext.Logger.Verbose(
diff --git a/src/client/Microsoft.Identity.Client/Platforms/net6/MsalJsonSerializerContext.cs b/src/client/Microsoft.Identity.Client/Platforms/net6/MsalJsonSerializerContext.cs
index 9e74633860..dff9c20d40 100644
--- a/src/client/Microsoft.Identity.Client/Platforms/net6/MsalJsonSerializerContext.cs
+++ b/src/client/Microsoft.Identity.Client/Platforms/net6/MsalJsonSerializerContext.cs
@@ -35,8 +35,6 @@ namespace Microsoft.Identity.Client.Platforms.net6
[JsonSerializable(typeof(UserRealmDiscoveryResponse))]
[JsonSerializable(typeof(DeviceCodeResponse))]
[JsonSerializable(typeof(AdfsWebFingerResponse))]
- [JsonSerializable(typeof(JsonWebToken.JWTHeaderWithCertificate))]
- [JsonSerializable(typeof(JsonWebToken.JWTPayload))]
[JsonSerializable(typeof(DeviceAuthHeader))]
[JsonSerializable(typeof(DeviceAuthPayload))]
[JsonSerializable(typeof(ManagedIdentityResponse))]
diff --git a/src/client/Microsoft.Identity.Client/Platforms/uap/UapCryptographyManager.cs b/src/client/Microsoft.Identity.Client/Platforms/uap/UapCryptographyManager.cs
index 5d7c4c23d0..67708746ff 100644
--- a/src/client/Microsoft.Identity.Client/Platforms/uap/UapCryptographyManager.cs
+++ b/src/client/Microsoft.Identity.Client/Platforms/uap/UapCryptographyManager.cs
@@ -10,6 +10,7 @@
using Microsoft.Identity.Client.Utils;
using Microsoft.Identity.Client.PlatformsCommon.Interfaces;
using Microsoft.Identity.Client.Internal;
+using System.Security.Cryptography;
namespace Microsoft.Identity.Client.Platforms.uap
{
@@ -67,7 +68,7 @@ public byte[] CreateSha256HashBytes(string input)
}
///
- public byte[] SignWithCertificate(string message, X509Certificate2 certificate)
+ public byte[] SignWithCertificate(string message, X509Certificate2 certificate, RSASignaturePadding signaturePadding)
{
// Used by Confidential Client, which is hidden on UWP
throw new NotImplementedException();
diff --git a/src/client/Microsoft.Identity.Client/PlatformsCommon/Interfaces/ICryptographyManager.cs b/src/client/Microsoft.Identity.Client/PlatformsCommon/Interfaces/ICryptographyManager.cs
index f9fe94baf8..2d220d3c29 100644
--- a/src/client/Microsoft.Identity.Client/PlatformsCommon/Interfaces/ICryptographyManager.cs
+++ b/src/client/Microsoft.Identity.Client/PlatformsCommon/Interfaces/ICryptographyManager.cs
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
+using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
namespace Microsoft.Identity.Client.PlatformsCommon.Interfaces
@@ -11,6 +12,6 @@ internal interface ICryptographyManager
string GenerateCodeVerifier();
string CreateSha256Hash(string input);
byte[] CreateSha256HashBytes(string input);
- byte[] SignWithCertificate(string message, X509Certificate2 certificate);
+ byte[] SignWithCertificate(string message, X509Certificate2 certificate, RSASignaturePadding signaturePadding);
}
}
diff --git a/src/client/Microsoft.Identity.Client/PlatformsCommon/Shared/CommonCryptographyManager.cs b/src/client/Microsoft.Identity.Client/PlatformsCommon/Shared/CommonCryptographyManager.cs
index 35fdf9ab58..891636c59f 100644
--- a/src/client/Microsoft.Identity.Client/PlatformsCommon/Shared/CommonCryptographyManager.cs
+++ b/src/client/Microsoft.Identity.Client/PlatformsCommon/Shared/CommonCryptographyManager.cs
@@ -60,7 +60,7 @@ public byte[] CreateSha256HashBytes(string input)
}
/// AAD only supports RSA certs for client credentials
- public virtual byte[] SignWithCertificate(string message, X509Certificate2 certificate)
+ public virtual byte[] SignWithCertificate(string message, X509Certificate2 certificate, RSASignaturePadding signaturePadding)
{
// MSAL used to check min key size by looking at certificate.GetRSAPublicKey().KeySize
// but this causes sporadic failures in the crypto stack. Rely on AAD to perform key size validations.
@@ -93,7 +93,7 @@ public virtual byte[] SignWithCertificate(string message, X509Certificate2 certi
byte[] SignDataAndCacheProvider(string message)
{
- var signedData = rsa.SignData(Encoding.UTF8.GetBytes(message), HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
+ var signedData = rsa.SignData(Encoding.UTF8.GetBytes(message), HashAlgorithmName.SHA256, signaturePadding);
// Cache only valid RSA crypto providers, which are able to sign data successfully
s_certificateToRsaMap[certificate.Thumbprint] = rsa;
diff --git a/src/client/Microsoft.Identity.Client/PlatformsCommon/Shared/DeviceAuthManager.cs b/src/client/Microsoft.Identity.Client/PlatformsCommon/Shared/DeviceAuthManager.cs
index 6bb38fc777..efba089984 100644
--- a/src/client/Microsoft.Identity.Client/PlatformsCommon/Shared/DeviceAuthManager.cs
+++ b/src/client/Microsoft.Identity.Client/PlatformsCommon/Shared/DeviceAuthManager.cs
@@ -5,6 +5,7 @@
using System.Collections.Generic;
using System.Globalization;
using System.Net.Http.Headers;
+using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using Microsoft.Identity.Client.OAuth2;
using Microsoft.Identity.Client.PlatformsCommon.Interfaces;
@@ -59,8 +60,7 @@ public bool TryCreateDeviceAuthChallengeResponse(HttpResponseHeaders responseHea
DeviceAuthJWTResponse responseJwt = GetDeviceAuthJwtResponse(submitUrl, challengeData["nonce"], certificate);
string responseToSign = responseJwt.GetResponseToSign();
-
- byte[] signedResponse = SignWithCertificate(certificate, responseToSign);
+ byte[] signedResponse = _cryptographyManager.SignWithCertificate(responseToSign, certificate, RSASignaturePadding.Pkcs1);
FormatResponseHeader(signedResponse, challengeData, responseToSign, out responseHeader);
@@ -72,11 +72,6 @@ private static DeviceAuthJWTResponse GetDeviceAuthJwtResponse(string submitUrl,
return new DeviceAuthJWTResponse(submitUrl, nonce, Convert.ToBase64String(certificate.GetRawCertData()));
}
- private byte[] SignWithCertificate(X509Certificate2 certificate, string responseToSign)
- {
- return _cryptographyManager.SignWithCertificate(responseToSign, certificate);
- }
-
private static void FormatResponseHeader(byte[] signedResponse,
IDictionary challengeData,
string responseToSign,
diff --git a/src/client/Microsoft.Identity.Client/Utils/DateTimeHelpers.cs b/src/client/Microsoft.Identity.Client/Utils/DateTimeHelpers.cs
index 4553d3947e..e9c8a9c91c 100644
--- a/src/client/Microsoft.Identity.Client/Utils/DateTimeHelpers.cs
+++ b/src/client/Microsoft.Identity.Client/Utils/DateTimeHelpers.cs
@@ -40,13 +40,6 @@ public static long CurrDateTimeInUnixTimestamp()
return unixTimestamp;
}
- public static long DateTimeToUnixTimestampMilliseconds(DateTimeOffset dateTimeOffset)
- {
- DateTime dateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
- long unixTimestamp = (long)dateTimeOffset.Subtract(dateTime).TotalMilliseconds;
- return unixTimestamp;
- }
-
public static long GetDurationFromWindowsTimestamp(string windowsTimestampInFuture, ILoggerAdapter logger)
{
if (string.IsNullOrEmpty(windowsTimestampInFuture))
diff --git a/tests/Microsoft.Identity.Test.Common/Core/Mocks/MockHttpManager.cs b/tests/Microsoft.Identity.Test.Common/Core/Mocks/MockHttpManager.cs
index 0ff37afc3f..2668459d5c 100644
--- a/tests/Microsoft.Identity.Test.Common/Core/Mocks/MockHttpManager.cs
+++ b/tests/Microsoft.Identity.Test.Common/Core/Mocks/MockHttpManager.cs
@@ -15,6 +15,7 @@
using Microsoft.Identity.Client.Core;
using Microsoft.Identity.Client.Http;
using Microsoft.VisualStudio.TestTools.UnitTesting;
+using NSubstitute;
namespace Microsoft.Identity.Test.Common.Core.Mocks
{
diff --git a/tests/Microsoft.Identity.Test.Common/TestConstants.cs b/tests/Microsoft.Identity.Test.Common/TestConstants.cs
index 8eb158e070..be25bae982 100644
--- a/tests/Microsoft.Identity.Test.Common/TestConstants.cs
+++ b/tests/Microsoft.Identity.Test.Common/TestConstants.cs
@@ -33,6 +33,8 @@ public static HashSet s_scope
public const uint JwtToAadLifetimeInSeconds = 60 * 10; // Ten minutes
public const string ClientCredentialAudience = "https://login.microsoftonline.com/72f988bf-86f1-41af-91ab-2d7cd011db47/v2.0";
public const string AutomationTestCertName = "AzureADIdentityDivisionTestAgentCert";
+ public static Dictionary AdditionalAssertionClaims =>
+ new Dictionary() { { "Key1", "Val1" }, { "Key2", "Val2" } };
public static readonly SortedSet s_scopeForAnotherResource = new SortedSet(new[] { "r2/scope1", "r2/scope2" }, StringComparer.OrdinalIgnoreCase);
public static readonly SortedSet s_cacheMissScope = new SortedSet(new[] { "r3/scope1", "r3/scope2" }, StringComparer.OrdinalIgnoreCase);
diff --git a/tests/Microsoft.Identity.Test.Core.UIAutomation/Microsoft.Identity.Test.UIAutomation.Infrastructure.csproj b/tests/Microsoft.Identity.Test.Core.UIAutomation/Microsoft.Identity.Test.UIAutomation.Infrastructure.csproj
index 21e663a521..f228ffba8e 100644
--- a/tests/Microsoft.Identity.Test.Core.UIAutomation/Microsoft.Identity.Test.UIAutomation.Infrastructure.csproj
+++ b/tests/Microsoft.Identity.Test.Core.UIAutomation/Microsoft.Identity.Test.UIAutomation.Infrastructure.csproj
@@ -65,4 +65,4 @@
-
+
\ No newline at end of file
diff --git a/tests/Microsoft.Identity.Test.Integration.netfx/HeadlessTests/ClientCredentialsTests.WithRegion.cs b/tests/Microsoft.Identity.Test.Integration.netfx/HeadlessTests/ClientCredentialsTests.WithRegion.cs
index 6f14491d9b..2d4dbc8745 100644
--- a/tests/Microsoft.Identity.Test.Integration.netfx/HeadlessTests/ClientCredentialsTests.WithRegion.cs
+++ b/tests/Microsoft.Identity.Test.Integration.netfx/HeadlessTests/ClientCredentialsTests.WithRegion.cs
@@ -9,6 +9,7 @@
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Identity.Client;
+using Microsoft.Identity.Client.Internal;
using Microsoft.Identity.Client.PlatformsCommon.Factories;
#if NET_CORE
using Microsoft.Identity.Client.PlatformsCommon.Shared;
@@ -190,10 +191,10 @@ private static string GetSignedClientAssertionUsingMsalInternal(string clientId,
{
var manager = PlatformProxyFactory.CreatePlatformProxy(null).CryptographyManager;
- var jwtToken = new Client.Internal.JsonWebToken(manager, clientId, TestConstants.ClientCredentialAudience, claims);
+ var jwtToken = new JsonWebToken(manager, clientId, TestConstants.ClientCredentialAudience, claims);
var cert = ConfidentialAppSettings.GetSettings(Cloud.Public).GetCertificate();
- return jwtToken.Sign(cert, Base64UrlHelpers.Encode(cert.GetCertHash()), true);
+ return jwtToken.Sign(cert, true, true);
}
}
}
\ No newline at end of file
diff --git a/tests/Microsoft.Identity.Test.Integration.netfx/HeadlessTests/ClientCredentialsTests.cs b/tests/Microsoft.Identity.Test.Integration.netfx/HeadlessTests/ClientCredentialsTests.cs
index 6046d63e17..b407d0f0ce 100644
--- a/tests/Microsoft.Identity.Test.Integration.netfx/HeadlessTests/ClientCredentialsTests.cs
+++ b/tests/Microsoft.Identity.Test.Integration.netfx/HeadlessTests/ClientCredentialsTests.cs
@@ -36,9 +36,8 @@ namespace Microsoft.Identity.Test.Integration.HeadlessTests
public class ClientCredentialsTests
{
private static readonly string[] s_scopes = { "User.Read" };
- private static readonly string[] s_keyvaultScope = { "https://vault.azure.net/.default" };
+ private static readonly string[] s_keyvaultScope = ["https://vault.azure.net/.default"];
private const string PublicCloudConfidentialClientID = "16dab2ba-145d-4b1b-8569-bf4b9aed4dc8";
- private const string PublicCloudTestAuthority = "https://login.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47";
private enum CredentialType
{
@@ -58,7 +57,7 @@ public void TestInitialize()
[TestMethod]
[DataRow(Cloud.Public, TargetFrameworks.NetFx | TargetFrameworks.NetCore | TargetFrameworks.NetStandard )]
- [DataRow(Cloud.Adfs, TargetFrameworks.NetCore)]
+ [DataRow(Cloud.Adfs, TargetFrameworks.NetFx | TargetFrameworks.NetCore )]
[DataRow(Cloud.PPE, TargetFrameworks.NetFx)]
[DataRow(Cloud.Public, TargetFrameworks.NetCore, true)]
//[DataRow(Cloud.Arlington)] - cert not setup
@@ -69,8 +68,8 @@ public async Task WithCertificate_TestAsync(Cloud cloud, TargetFrameworks runOn,
}
[TestMethod]
- //[DataRow(Cloud.Public, TargetFrameworks.NetFx | TargetFrameworks.NetCore)]
- //[DataRow(Cloud.Adfs, TargetFrameworks.NetFx)]
+ [DataRow(Cloud.Public, TargetFrameworks.NetFx | TargetFrameworks.NetCore)]
+ [DataRow(Cloud.Adfs, TargetFrameworks.NetFx)]
[DataRow(Cloud.Arlington, TargetFrameworks.NetCore)]
//[DataRow(Cloud.PPE)] - secret not setup
public async Task WithSecret_TestAsync(Cloud cloud, TargetFrameworks runOn)
@@ -81,7 +80,7 @@ public async Task WithSecret_TestAsync(Cloud cloud, TargetFrameworks runOn)
[TestMethod]
[DataRow(Cloud.Public, TargetFrameworks.NetFx | TargetFrameworks.NetCore)]
- [DataRow(Cloud.Adfs, TargetFrameworks.NetFx)]
+ [DataRow(Cloud.Adfs, TargetFrameworks.NetFx | TargetFrameworks.NetCore)]
[DataRow(Cloud.PPE, TargetFrameworks.NetCore)]
// [DataRow(Cloud.Arlington)] - cert not setup
public async Task WithClientAssertion_Manual_TestAsync(Cloud cloud, TargetFrameworks runOn)
@@ -232,7 +231,12 @@ private static void ModifyRequest(OnBeforeTokenRequestData data, X509Certificate
string clientId = data.BodyParameters["client_id"];
string tokenEndpoint = data.RequestUri.AbsoluteUri;
- string assertion = GetSignedClientAssertionManual(issuer: clientId, audience: tokenEndpoint, certificate: certificate);
+ string assertion = GetSignedClientAssertionManual(
+ issuer: clientId,
+ audience: tokenEndpoint,
+ certificate: certificate,
+ useSha2AndPss: true);
+
data.BodyParameters.Add("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer");
data.BodyParameters.Add("client_assertion", assertion);
}
@@ -246,7 +250,7 @@ private async Task RunClientCredsAsync(Cloud cloud, CredentialType credentialTyp
AuthenticationResult authResult;
- IConfidentialClientApplication confidentialApp = CreateApp(credentialType, settings, sendX5C);
+ IConfidentialClientApplication confidentialApp = CreateApp(credentialType, settings, sendX5C, cloud != Cloud.Adfs);
var appCacheRecorder = confidentialApp.AppTokenCache.RecordAccess();
Guid correlationId = Guid.NewGuid();
authResult = await confidentialApp
@@ -292,7 +296,11 @@ private async Task RunClientCredsAsync(Cloud cloud, CredentialType credentialTyp
appCacheRecorder.LastAfterAccessNotificationArgs.SuggestedCacheKey);
}
- private static IConfidentialClientApplication CreateApp(CredentialType credentialType, IConfidentialAppSettings settings, bool sendX5C)
+ private static IConfidentialClientApplication CreateApp(
+ CredentialType credentialType,
+ IConfidentialAppSettings settings,
+ bool sendX5C,
+ bool useSha2AndPssForAssertion)
{
var builder = ConfidentialClientApplicationBuilder
.Create(settings.ClientId)
@@ -316,7 +324,8 @@ private static IConfidentialClientApplication CreateApp(CredentialType credentia
string signedAssertionManual = GetSignedClientAssertionManual(
settings.ClientId,
aud, // for AAD use v2.0, but not for ADFS
- settings.GetCertificate());
+ settings.GetCertificate(),
+ useSha2AndPssForAssertion);
builder.WithClientAssertion(signedAssertionManual);
break;
@@ -416,10 +425,11 @@ private static string GetSignedClientAssertionUsingWilson(
/// the token endpoint, i.e. ${authority}/oauth2/v2.0/token for AAD or ${authority}/oauth2/token for ADFS
///
///
- internal static string GetSignedClientAssertionManual(
+ private static string GetSignedClientAssertionManual(
string issuer,
string audience,
- X509Certificate2 certificate)
+ X509Certificate2 certificate,
+ bool useSha2AndPss)
{
const uint JwtToAadLifetimeInSeconds = 60 * 10; // Ten minutes
@@ -441,24 +451,37 @@ internal static string GetSignedClientAssertionManual(
RSACng rsa = certificate.GetRSAPrivateKey() as RSACng;
- //alg represents the desired signing algorithm, which is SHA-256 in this case
- //kid represents the certificate thumbprint
- var header = new Dictionary()
+ Dictionary header;
+ if (useSha2AndPss)
{
- { "alg", "RS256"},
- { "typ", "JWT"},
- { "x5t", Base64UrlHelpers.Encode(certificate.GetCertHash())},
- };
+ header = new Dictionary()
+ {
+ { "alg", "PS256"},
+ { "typ", "JWT"},
+ { "x5t#S256", Base64UrlHelpers.Encode(certificate.GetCertHash(HashAlgorithmName.SHA256))},
+ };
+ }
+ else
+ {
+ header = new Dictionary()
+ {
+ { "alg", "RS256"},
+ { "typ", "JWT"},
+ { "x5t", Base64UrlHelpers.Encode(certificate.GetCertHash())},
+ };
+ }
+
var headerBytes = JsonSerializer.SerializeToUtf8Bytes(header);
var claimsBytes = JsonSerializer.SerializeToUtf8Bytes(claims);
string token = Base64UrlHelpers.Encode(headerBytes) + "." + Base64UrlHelpers.Encode(claimsBytes);
+ //codeql [SM03799] Backwards Compatibility: Requires accepting PKCS1 for supporting ADFS
string signature = Base64UrlHelpers.Encode(
rsa.SignData(
Encoding.UTF8.GetBytes(token),
HashAlgorithmName.SHA256,
- RSASignaturePadding.Pkcs1));
+ useSha2AndPss ? RSASignaturePadding.Pss : RSASignaturePadding.Pkcs1));
return string.Concat(token, ".", signature);
}
}
diff --git a/tests/Microsoft.Identity.Test.Integration.netfx/SeleniumTests/ConfidentialClientAuthorizationTests.cs b/tests/Microsoft.Identity.Test.Integration.netfx/SeleniumTests/ConfidentialClientAuthorizationTests.cs
index 3fac2eaf1c..6aa89d063b 100644
--- a/tests/Microsoft.Identity.Test.Integration.netfx/SeleniumTests/ConfidentialClientAuthorizationTests.cs
+++ b/tests/Microsoft.Identity.Test.Integration.netfx/SeleniumTests/ConfidentialClientAuthorizationTests.cs
@@ -91,11 +91,11 @@ public async Task GetTokenByAuthCode_HybridSPA_Async()
Assert.IsNotNull(result.SpaAuthCode);
- result = await RunTestForUserAsync(labResponse.App.AppId, labResponse,
- "https://login.microsoftonline.com/f645ad92-e38d-4d1a-b510-d1b09a74a8ca", false,
- "http://localhost:3000/auth/implicit-redirect", false).ConfigureAwait(false);
+ //result = await RunTestForUserAsync(labResponse.App.AppId, labResponse,
+ // "https://login.microsoftonline.com/f645ad92-e38d-4d1a-b510-d1b09a74a8ca", false,
+ // "http://localhost:3000/auth/implicit-redirect", false).ConfigureAwait(false);
- Assert.IsNull(result.SpaAuthCode);
+ //Assert.IsNull(result.SpaAuthCode);
}
private async Task RunTestForUserAsync(string appId, LabResponse labResponse,
@@ -163,12 +163,20 @@ private async Task RunTestForUserAsync(string appId, LabRe
AssertExtraHTTPHeadersAreSent(factory);
- Trace.WriteLine("Part 4 - Remove Account");
+#pragma warning disable CS0618 // Type or member is obsolete
+ var acc = await cca.GetAccountsAsync().ConfigureAwait(false);
+#pragma warning restore CS0618 // Type or member is obsolete
+ var r2 = await cca.AcquireTokenSilent(s_scopes, acc.SingleOrDefault())
+ .WithForceRefresh(true)
+ .ExecuteAsync()
+ .ConfigureAwait(false);
+
+ //Trace.WriteLine("Part 4 - Remove Account");
- await cca.RemoveAsync(result.Account).ConfigureAwait(false);
- cacheAccess.AssertAccessCounts(0, 2);
+ //await cca.RemoveAsync(result.Account).ConfigureAwait(false);
+ //cacheAccess.AssertAccessCounts(0, 2);
- AssertCacheKey(cacheAccess, result.Account.HomeAccountId.Identifier);
+ //AssertCacheKey(cacheAccess, result.Account.HomeAccountId.Identifier);
return result;
}
@@ -196,5 +204,6 @@ private void AssertExtraHTTPHeadersAreSent(HttpSnifferClientFactory factory)
Assert.AreEqual(TestConstants.ExtraHttpHeader.Keys.FirstOrDefault(), ExtraHttpHeader.Key);
Assert.AreEqual(TestConstants.ExtraHttpHeader.Values.FirstOrDefault(), ExtraHttpHeader.Value.FirstOrDefault());
}
+
}
}
diff --git a/tests/Microsoft.Identity.Test.Performance/ClientAssertionTests.cs b/tests/Microsoft.Identity.Test.Performance/ClientAssertionTests.cs
new file mode 100644
index 0000000000..881fa76aba
--- /dev/null
+++ b/tests/Microsoft.Identity.Test.Performance/ClientAssertionTests.cs
@@ -0,0 +1,51 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+using System.Collections.Generic;
+using System.Security.Cryptography;
+using System.Security.Cryptography.X509Certificates;
+using BenchmarkDotNet.Attributes;
+using Microsoft.Identity.Client.Internal;
+using Microsoft.Identity.Client.PlatformsCommon.Shared;
+using Microsoft.Identity.Client.Utils;
+using Microsoft.Identity.Test.Performance.Helpers;
+using Microsoft.Identity.Test.Unit;
+
+namespace Microsoft.Identity.Test.Performance
+{
+ public class ClientAssertionTests
+ {
+ private static X509Certificate2 s_certificate =
+ CertificateHelper.CreateCertificate("CN=rsa2048", RSA.Create(2048), HashAlgorithmName.SHA256, null);
+
+ string base64EncodedThumbprint = Base64UrlHelpers.Encode(s_certificate.GetCertHash());
+
+ private static Dictionary s_cl = new Dictionary()
+ {
+ {"key1", "val1" },
+ {"key2", "val2" }
+ };
+
+ [ParamsAllValues]
+ public bool UseSha2 { get; set; }
+
+ [ParamsAllValues]
+ public bool UseX5C { get; set; }
+
+ [ParamsAllValues]
+ public bool UseExtraClaims { get; set; }
+
+ [Benchmark(Description = "SimpleAssertion")]
+ public void ConfidentialClientAppBuilder_Test()
+ {
+ JsonWebToken msalJwtTokenObj =
+ new JsonWebToken(new CommonCryptographyManager(),
+ TestConstants.ClientId,
+ "aud",
+ UseExtraClaims ? s_cl : null,
+ true);
+
+
+ msalJwtTokenObj.Sign(s_certificate, UseX5C, useSha2AndPss: true);
+ }
+ }
+}
diff --git a/tests/Microsoft.Identity.Test.Unit/ApiConfigTests/AuthorityTests.cs b/tests/Microsoft.Identity.Test.Unit/ApiConfigTests/AuthorityTests.cs
index bd730cb9cd..61bb8d44a0 100644
--- a/tests/Microsoft.Identity.Test.Unit/ApiConfigTests/AuthorityTests.cs
+++ b/tests/Microsoft.Identity.Test.Unit/ApiConfigTests/AuthorityTests.cs
@@ -103,6 +103,30 @@ public void WithTenantIdAtRequestLevel_NonAad(string inputAuthority)
"The tenant id should have been changed");
}
+ [DataTestMethod]
+ [DataRow(TestConstants.AuthorityCommonTenant, true)]
+ [DataRow(TestConstants.AuthorityCommonPpeAuthority, true)]
+ [DataRow(TestConstants.DstsAuthorityCommon, false)]
+ [DataRow(TestConstants.DstsAuthorityTenanted, false)]
+ [DataRow(TestConstants.CiamAuthorityMainFormat, true)]
+ [DataRow(TestConstants.CiamAuthorityWithFriendlyName, true)]
+ [DataRow(TestConstants.CiamAuthorityWithGuid, true)]
+ [DataRow(TestConstants.B2CAuthority, true)]
+ [DataRow(TestConstants.B2CCustomDomain, true)]
+ [DataRow(TestConstants.ADFSAuthority, false)]
+ public void IsSha2Supported(string inputAuthority, bool expected)
+ {
+ Authority a = Authority.CreateAuthority(inputAuthority);
+ Assert.AreEqual(a.AuthorityInfo.IsSha2CredentialSupported, expected);
+ }
+
+ [TestMethod]
+ public void GenericSha2()
+ {
+ var ai = new AuthorityInfo(AuthorityType.Generic, TestConstants.GenericAuthority, false);
+ Assert.IsFalse(ai.IsSha2CredentialSupported);
+ }
+
[DataTestMethod]
[DataRow(TestConstants.AuthorityCommonTenant)]
[DataRow(TestConstants.AuthorityCommonPpeAuthority)]
diff --git a/tests/Microsoft.Identity.Test.Unit/CryptographyTests.cs b/tests/Microsoft.Identity.Test.Unit/CryptographyTests.cs
index 57402dc048..b4a629e526 100644
--- a/tests/Microsoft.Identity.Test.Unit/CryptographyTests.cs
+++ b/tests/Microsoft.Identity.Test.Unit/CryptographyTests.cs
@@ -33,7 +33,7 @@ public void SignWithCertificate()
var cert = new X509Certificate2(
ResourceHelper.GetTestResourceRelativePath("testCert.crtfile"), TestConstants.TestCertPassword);
var crypto = serviceBundle.PlatformProxy.CryptographyManager;
- byte[] result = crypto.SignWithCertificate("TEST", cert);
+ byte[] result = crypto.SignWithCertificate("TEST", cert, RSASignaturePadding.Pkcs1);
string value = Base64UrlHelpers.Encode(result);
Assert.IsNotNull(value);
Assert.AreEqual("MrknKHbOAVu2iuLHMFSk2SK773H1ysxaAjAPcTXYSfH4P2fUfvzP6aIb9MkBknjoE_aBYtTnQ7jOAvyQETvogdeSH7pRDPhCk2aX_8VIQw0bjo_zBZj5yJYVWQDLIu8XvbuzIGEvVaXKz4jJ1nYM6toun4tM74rEHvwa0ferafmqHWOd5puPhlKH1VVK2RPuNOoKNLWBprVBaAQVJVFOdRcd3iR0INBHykxtOsG0pgo0Q2uQBlKP7KQb7Ox8i_sw-M21BuUzdIdGs_oeUYh0B8s-eIGf34JmHRWMwWCnRWzZgY9YuIjRoaWNqlWYb8ASjKOxzvk99x8eFEYKOjgAcA", value);
@@ -52,7 +52,7 @@ public void SignWithNonRsaCertificate_ThrowsException()
MsalClientException ex = AssertException.Throws(() =>
{
- crypto.SignWithCertificate("TEST", cert);
+ crypto.SignWithCertificate("TEST", cert, RSASignaturePadding.Pkcs1);
});
Assert.AreEqual(ex.ErrorCode, MsalError.CertificateNotRsa);
diff --git a/tests/Microsoft.Identity.Test.Unit/PublicApiTests/ClientCredentialWithCertTest.cs b/tests/Microsoft.Identity.Test.Unit/PublicApiTests/ClientCredentialWithCertTest.cs
index 0fb5479900..ac2b92c5df 100644
--- a/tests/Microsoft.Identity.Test.Unit/PublicApiTests/ClientCredentialWithCertTest.cs
+++ b/tests/Microsoft.Identity.Test.Unit/PublicApiTests/ClientCredentialWithCertTest.cs
@@ -4,15 +4,18 @@
#if !ANDROID && !iOS && !WINDOWS_APP
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Net.Http;
+using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Identity.Client;
using Microsoft.Identity.Client.Cache.Keys;
using Microsoft.Identity.Client.Internal;
+using Microsoft.Identity.Client.PlatformsCommon.Shared;
using Microsoft.Identity.Client.Utils;
using Microsoft.Identity.Test.Common.Core.Helpers;
using Microsoft.Identity.Test.Common.Core.Mocks;
@@ -42,7 +45,7 @@ private static MockHttpMessageHandler CreateTokenResponseHttpHandler(bool client
}
private static MockHttpMessageHandler CreateTokenResponseHttpHandlerWithX5CValidation(
- bool clientCredentialFlow,
+ bool clientCredentialFlow,
string expectedX5C = null)
{
return new MockHttpMessageHandler()
@@ -158,7 +161,7 @@ public async Task JsonWebTokenWithX509PublicCertSendCertificateTestSendX5cCombin
[DataRow(true, false, false)] // request overrides
[DataRow(false, true, true)] // request overrides
[System.Diagnostics.CodeAnalysis.SuppressMessage("Internal.Analyzers", "IA5352:DoNotMisuseCryptographicApi", Justification = "Suppressing RoslynAnalyzers: Rule: IA5352 - Do Not Misuse Cryptographic APIs in test only code")]
- public async Task JsonWebTokenWithX509PublicCertSendCertificateWithClaimsTestSendX5cCombinationsAsync(
+ public async Task TestX5C(
bool? appFlag,
bool? requestFlag,
bool expectX5c)
@@ -169,20 +172,27 @@ public async Task JsonWebTokenWithX509PublicCertSendCertificateWithClaimsTestSen
var certificate = CertHelper.GetOrCreateTestCert();
var exportedCertificate = Convert.ToBase64String(certificate.Export(X509ContentType.Cert));
- IDictionary claimsToSign = new Dictionary();
- claimsToSign.Add("Foo", "Bar");
+ IDictionary claimsToSign = new Dictionary
+ {
+ { "Foo", "Bar" }
+ };
var appBuilder = ConfidentialClientApplicationBuilder
- .Create(TestConstants.ClientId)
- .WithHttpManager(harness.HttpManager);
+ .Create(TestConstants.ClientId)
+ .WithHttpManager(harness.HttpManager);
if (appFlag.HasValue)
{
- appBuilder = appBuilder.WithClientClaims(certificate, claimsToSign, sendX5C: appFlag.Value); // app flag
+ appBuilder = appBuilder.WithClientClaims(
+ certificate,
+ claimsToSign,
+ sendX5C: appFlag.Value); // app flag
}
else
{
- appBuilder = appBuilder.WithClientClaims(certificate, claimsToSign); // no app flag
+ appBuilder = appBuilder.WithClientClaims(
+ certificate,
+ claimsToSign); // no app flag
}
var app = appBuilder.BuildConcrete();
@@ -377,7 +387,7 @@ public async Task JsonWebTokenWithX509PublicCertSendCertificateByRefreshTokenTes
Assert.AreEqual(result.Account.HomeAccountId.Identifier,
userCacheAccess.LastAfterAccessNotificationArgs.SuggestedCacheKey);
Assert.AreEqual(result.Account.HomeAccountId.Identifier,
- userCacheAccess.LastBeforeAccessNotificationArgs.SuggestedCacheKey);
+ userCacheAccess.LastBeforeAccessNotificationArgs.SuggestedCacheKey);
}
}
@@ -394,7 +404,7 @@ public async Task JsonWebTokenWithX509PublicCertSendCertificateSilentTestAsync()
var app = ConfidentialClientApplicationBuilder
.Create(TestConstants.ClientId)
- .WithAuthority(new System.Uri("https://login.microsoftonline.com/my-utid"),true)
+ .WithAuthority(new System.Uri("https://login.microsoftonline.com/my-utid"), true)
.WithRedirectUri(TestConstants.RedirectUri)
.WithHttpManager(harness.HttpManager)
.WithCertificate(certificate, true)
@@ -410,7 +420,7 @@ public async Task JsonWebTokenWithX509PublicCertSendCertificateSilentTestAsync()
var result = await app
.AcquireTokenSilent(
- new[] { "someTestScope"},
+ new[] { "someTestScope" },
new Account(TestConstants.s_userIdentifier, TestConstants.DisplayableId, null))
.WithForceRefresh(true)
.ExecuteAsync(CancellationToken.None).ConfigureAwait(false);
@@ -425,32 +435,132 @@ public async Task JsonWebTokenWithX509PublicCertSendCertificateSilentTestAsync()
}
}
- [TestMethod]
- [Description("Check the JWTHeader when sendCert is true")]
- [System.Diagnostics.CodeAnalysis.SuppressMessage("Internal.Analyzers", "IA5352:DoNotMisuseCryptographicApi", Justification = "Suppressing RoslynAnalyzers: Rule: IA5352 - Do Not Misuse Cryptographic APIs in test only code")]
- public void CheckJWTHeaderWithCertTrueTest()
+ [DataTestMethod]
+ [DataRow(true, true, true, true)]
+ [DataRow(true, true, true, false)]
+ [DataRow(true, true, false, true)]
+ [DataRow(true, true, false, false)]
+ [DataRow(true, false, true, true)]
+ [DataRow(true, false, true, false)]
+ [DataRow(true, false, false, true)]
+ [DataRow(true, false, false, false)]
+ [DataRow(false, true, true, true)]
+ [DataRow(false, true, true, false)]
+ [DataRow(false, true, false, true)]
+ [DataRow(false, true, false, false)]
+ [DataRow(false, false, true, true)]
+ [DataRow(false, false, true, false)]
+ [DataRow(false, false, false, true)]
+ [DataRow(false, false, false, false)]
+ public void ClientAssertionTests(bool sendX5C, bool useSha2AndPss, bool addExtraClaims, bool appendDefaultClaims)
{
+ // for asserting nbf and exp - make it bigger for debugging.
+ TimeSpan tolerance = TimeSpan.FromSeconds(3);
+
+ Trace.WriteLine($"sendX5C {sendX5C}, useSha2AndPss {useSha2AndPss}, addExtraClaims {addExtraClaims}, appendDefaultClaims {appendDefaultClaims}");
var cert = new X509Certificate2(
- ResourceHelper.GetTestResourceRelativePath("testCert.crtfile"), TestConstants.TestCertPassword);
+ ResourceHelper.GetTestResourceRelativePath(
+ "testCert.crtfile"),
+ TestConstants.TestCertPassword);
+
+ JsonWebToken msalJwtTokenObj =
+ new JsonWebToken(new CommonCryptographyManager(),
+ TestConstants.ClientId,
+ "aud",
+ addExtraClaims ? TestConstants.AdditionalAssertionClaims : null,
+ appendDefaultClaims);
+
+ string assertion = msalJwtTokenObj.Sign(cert, sendX5C: sendX5C, useSha2AndPss: useSha2AndPss);
+
+ // Use Wilson to decode the token and check its claims
+ JwtSecurityToken decodedToken = new JwtSecurityToken(assertion);
+ AssertClientAssertionHeader(cert, decodedToken, sendX5C, useSha2AndPss);
+
+ // special case - this is treated just as adding default claims
+ if (appendDefaultClaims == false && addExtraClaims == false)
+ appendDefaultClaims = true;
+
+ int expectedPayloadClaimsCount = (appendDefaultClaims ? 6 : 0) + (addExtraClaims ? 2 : 0);
+ Assert.AreEqual(expectedPayloadClaimsCount, decodedToken.Payload.Count);
+ if (appendDefaultClaims)
+ {
+ Assert.AreEqual("aud", decodedToken.Payload["aud"]);
+ Assert.AreEqual(TestConstants.ClientId, decodedToken.Payload["iss"]);
+ Assert.AreEqual(TestConstants.ClientId, decodedToken.Payload["sub"]);
+ long nbf = long.Parse(decodedToken.Payload["nbf"].ToString());
+ var nbfDate = DateTimeOffset.FromUnixTimeSeconds(nbf);
+ CoreAssert.IsWithinRange(
+ DateTimeOffset.Now,
+ nbfDate,
+ tolerance);
+
+ long exp = long.Parse(decodedToken.Payload["exp"].ToString());
+ var expDate = DateTimeOffset.FromUnixTimeSeconds(exp);
+ CoreAssert.IsWithinRange(
+ DateTimeOffset.Now + TimeSpan.FromSeconds(JsonWebToken.JwtToAadLifetimeInSeconds),
+ expDate,
+ tolerance);
+ }
- var header = new JWTHeaderWithCertificate(cert, Base64UrlHelpers.Encode(cert.GetCertHash()), true);
+ if (addExtraClaims)
+ {
+ Assert.AreEqual("Val1", decodedToken.Payload["Key1"]);
+ Assert.AreEqual("Val2", decodedToken.Payload["Key2"]);
+ }
+
+ if (useSha2AndPss)
+ {
+ Assert.AreEqual(
+ "bmQeK7jALQuzsm3zZhXskUB41iAU0lyzzX2AKJAtiZ8",
+ decodedToken.Header["x5t#S256"]);
+ }
+ else
+ {
+ Assert.AreEqual(
+ "5wxQ2k6mb5QimllLwRLLS0_ynrQ",
+ decodedToken.Header["x5t"]);
+ }
+
+ if (sendX5C)
+ {
+ Assert.AreEqual(
+ "MIIDQjCCAiqgAwIBAgIQTuexEO9cdYhC0jy1nmS6jTANBgkqhkiG9w0BAQsFADAiMSAwHgYDVQQDDBd0cndhbGtlLm9ubWljcm9zb2Z0LmNvbTAeFw0xNzA4MTExODEzMTBaFw0xODA4MTExODMzMTBaMCIxIDAeBgNVBAMMF3Ryd2Fsa2Uub25taWNyb3NvZnQuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5Qe3Ah/E97K0o288gYUNa0H8FO/w8pb1dvls/boQDoZxUD11TpAQrKZwstS6+ulGF6cHmj44AH8MNBKNUbW2L1NTjFG9bltaSXpJXzbIH/cUppF9rxngZ0CM7cHtuoccBPBVEuQiJ86pD7qlqE2EA2BdBmfz3Hd41rybdaWkHMxMcBC7nh6w87/KoyikKXCMLUUyRTJLSivo+gfKJsiYGAjqZ54aJraP5LMiPG2qYTOZR6wMme93mYRp85sqGTvgzRCq37STH2HmcYilUQ9kZFe5SR+1vOki97XLg+H7FuFtkSMM7dEnTWkDv+BJ1ZQvCEj623cJxXlq0fd7hVUxIQIDAQABo3QwcjAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMCIGA1UdEQQbMBmCF3Ryd2Fsa2Uub25taWNyb3NvZnQuY29tMB0GA1UdDgQWBBSauRo9cNk8J6RTLWMQSyUQnxjQzDANBgkqhkiG9w0BAQsFAAOCAQEAhYl1I8qETtvVt6m/YrGknA90R/FtIePt/ViBae3mxPJWlVoq5fTTriQcuPHXfI5kbjTQJIwCVTT/CRSlKkzRcrSsQUxxHNE7IdpvvDbkf6AMPxQhNACHQd0cIWmsmf+ItKsC70LKQ+93+VgmBsv2j8XwF0JTqwuKoqXnDjCzHvmU67xhPY6CSPA/0XOiVTx1BDWd5cPdsH2bZnAeApsvrzU8W7iPgV/oN9MMfogocvDUXd6T+QGLMAYoInHXsqG6+SEarqRDUPQZOHo5Ax4Mvhsnd2b4u5d5Y/R0z0wUwtOiF0Tu+w79JIqDRYaaJLTKxZ+2DyYOu54u0LGsGhki1g==",
+ decodedToken.Header["x5c"]);
+ }
- Assert.IsNotNull(header.X509CertificatePublicCertValue);
- Assert.IsNotNull(header.X509CertificateThumbprint);
}
- [TestMethod]
- [Description("Check the JWTHeader when sendCert is false")]
- [System.Diagnostics.CodeAnalysis.SuppressMessage("Internal.Analyzers", "IA5352:DoNotMisuseCryptographicApi", Justification = "Suppressing RoslynAnalyzers: Rule: IA5352 - Do Not Misuse Cryptographic APIs in test only code")]
- public void CheckJWTHeaderWithCertFalseTest()
+ private static void AssertClientAssertionHeader(
+ X509Certificate2 cert,
+ JwtSecurityToken decodedToken,
+ bool sendX5c,
+ bool useSha2AndPss)
{
- var cert = new X509Certificate2(
- ResourceHelper.GetTestResourceRelativePath("testCert.crtfile"), TestConstants.TestCertPassword);
- var header = new JWTHeaderWithCertificate(cert, Base64UrlHelpers.Encode(cert.GetCertHash()), false);
+ // Wilson is guaranteed to parse the token correctly - use it as baseline
+ Assert.AreEqual(sendX5c ? 4 : 3, decodedToken.Header.Count);
+ Assert.AreEqual("JWT", decodedToken.Header["typ"]);
+ Assert.AreEqual(useSha2AndPss ? "PS256" : "RS256", decodedToken.Header["alg"]);
+
+ if (useSha2AndPss)
+ {
+ Assert.AreEqual(
+ ComputeCertThumbprint(cert, true),
+ decodedToken.Header["x5t#S256"]);
+ }
+ else
+ {
+ Assert.AreEqual(
+ ComputeCertThumbprint(cert, false),
+ decodedToken.Header["x5t"]);
+ }
- Assert.IsNull(header.X509CertificatePublicCertValue);
- Assert.IsNotNull(header.X509CertificateThumbprint);
+ if (sendX5c)
+ {
+ Assert.AreEqual(
+ Convert.ToBase64String(cert.RawData),
+ decodedToken.Header["x5c"]);
+ }
}
[TestMethod]
@@ -475,7 +585,7 @@ public async Task CheckRSAPrivateKeyCanSignAssertionAsync()
var userCacheAccess = app.UserTokenCache.RecordAccess();
harness.HttpManager.AddMockHandler(CreateTokenResponseHttpHandler(true));
-
+
AuthenticationResult result = await app
.AcquireTokenForClient(TestConstants.s_scope)
.ExecuteAsync(CancellationToken.None)
@@ -498,7 +608,23 @@ public async Task CheckRSAPrivateKeyCanSignAssertionAsync()
appCacheAccess.AssertAccessCounts(2, 1);
userCacheAccess.AssertAccessCounts(0, 0);
}
- }
+ }
+
+ private static string ComputeCertThumbprint(X509Certificate2 certificate, bool useSha2)
+ {
+ string thumbprint = null;
+
+ if (useSha2)
+ {
+ thumbprint = Base64UrlHelpers.Encode(certificate.GetCertHash(HashAlgorithmName.SHA256));
+ }
+ else
+ {
+ thumbprint = Base64UrlHelpers.Encode(certificate.GetCertHash());
+ }
+
+ return thumbprint;
+ }
}
}
#endif
diff --git a/tests/Microsoft.Identity.Test.Unit/TestBase.cs b/tests/Microsoft.Identity.Test.Unit/TestBase.cs
index 2c84704b01..a0111d17ad 100644
--- a/tests/Microsoft.Identity.Test.Unit/TestBase.cs
+++ b/tests/Microsoft.Identity.Test.Unit/TestBase.cs
@@ -10,6 +10,7 @@
using Microsoft.Identity.Client.OAuth2;
using Microsoft.Identity.Test.Common;
using Microsoft.Identity.Test.Common.Core.Mocks;
+using Microsoft.IdentityModel.Logging;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Microsoft.Identity.Test.Unit
@@ -20,6 +21,7 @@ public partial class TestBase
[AssemblyInitialize]
public static void AssemblyInit(TestContext context)
{
+ IdentityModelEventSource.ShowPII = true;
EnableFileTracingOnEnvVar();
Trace.WriteLine("Test run started");
}
diff --git a/tests/Microsoft.Identity.Test.Unit/UtilTests/JsonHelperTests.cs b/tests/Microsoft.Identity.Test.Unit/UtilTests/JsonHelperTests.cs
index a9d07b7825..53395c5e5c 100644
--- a/tests/Microsoft.Identity.Test.Unit/UtilTests/JsonHelperTests.cs
+++ b/tests/Microsoft.Identity.Test.Unit/UtilTests/JsonHelperTests.cs
@@ -4,14 +4,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using System.Security.Cryptography.X509Certificates;
using Microsoft.Identity.Client.Cache;
using Microsoft.Identity.Client.Internal;
using Microsoft.Identity.Client.OAuth2;
using Microsoft.Identity.Client.Utils;
using Microsoft.Identity.Test.Common.Core.Helpers;
using Microsoft.VisualStudio.TestTools.UnitTesting;
-using static Microsoft.Identity.Client.Internal.JsonWebToken;
namespace Microsoft.Identity.Test.Unit.UtilTests
{
@@ -137,48 +135,6 @@ public void Serialize_OldDictionaryTokenCache()
JsonTestUtils.AssertJsonDeepEquals(expectedJson, actualJson);
}
- [TestMethod]
- [DeploymentItem(@"Resources\RSATestCertDotNet.pfx")]
- public void Serialize_Jwt()
- {
- var payload = new JWTPayload
- {
- Audience = "aud",
- Issuer = "iss",
- ValidFrom = 123,
- ValidTo = 124,
- Subject = "123-456-789",
- JwtIdentifier = "321-654"
- };
-
- var certificate = new X509Certificate2(
- ResourceHelper.GetTestResourceRelativePath("RSATestCertDotNet.pfx"));
-
- var header = new JWTHeaderWithCertificate(certificate, Base64UrlHelpers.Encode(certificate.GetCertHash()), true);
- string actualPayload = JsonHelper.SerializeToJson(payload);
- string actualHeader = JsonHelper.SerializeToJson(header);
-
- string expectedPayload = @"{
- ""aud"": ""aud"",
- ""exp"": 124,
- ""iss"": ""iss"",
- ""jti"": ""321-654"",
- ""nbf"": 123,
- ""sub"": ""123-456-789""
- }";
-
- string expectedHeader = @"{
- ""alg"": ""RS256"",
- ""typ"": ""JWT"",
- ""x5t"": ""lJjBuRyk8s_-oQxT3MgwH5qNS94"",
- ""kid"": ""9498C1B91CA4F2CFFEA10C53DCC8301F9A8D4BDE"",
- ""x5c"": ""MIIDJDCCAgygAwIBAgIQK4SCZgh/R5anP05v4z6VLjANBgkqhkiG9w0BAQsFADAPMQ0wCwYDVQQDEwRUZXN0MB4XDTE5MDgxNTE3MjY1M1oXDTIwMDgxNTE3MzY1M1owDzENMAsGA1UEAxMEVGVzdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAIxSuzLrpxnq44CSux3l2UMvIBwBXnh4tmmZtju4qCNJzVmCrhyC9i5jH7YCicXeFQChWfbZpyo2TpDD/cTw+Rpi9QLhhGvDnMF+uk1pqSp5Fdh11YacX7w76Wc7Er+FM2PiKtyDX6+nFzUvV3SfjfdcAadConDAWOdmpd34UNZ/DzM6dRKynWuaE+0kD843Tr+pCXlMGQBAQatWyROK+rgOKhnv1/vMAZ90SCjxAhnjxj+9GRIGYzonuTa+EOqXRn1XQ+j54Ux953Oq0zGCNbXndGjGKH1U1JP/nAemFsh0h2DcdAdEkxOS3+QrdiZEkPPfe8x5BLJmvoRWJ9eCAT0CAwEAAaN8MHowDgYDVR0PAQH/BAQDAgWgMAkGA1UdEwQCMAAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB8GA1UdIwQYMBaAFB03ltXqrZeIzolZQj8w98DG8HCIMB0GA1UdDgQWBBQdN5bV6q2XiM6JWUI/MPfAxvBwiDANBgkqhkiG9w0BAQsFAAOCAQEAiXAQHHWiJ+8wLk0evDZSXDfQ0brYsKLimxJSrVOzpz4BnHTIr86ZEYA6jCKNfhRnrPU9HQ43CUSU1MRX03ovdJMoYjuWCGAFlZrYMC9PhPwt2B0a3DRl0wsl3jxOYYrFHonBWvjDFdWEP2Nr2T8iWPgpS5uIdgU1GqN9EbI+3B46qH4rTH3vAwpeF38XDjBO8DYycotwG34zgD2zQ2ZoPmQG07Y8rjBo+JW56ri3RfeMu3kZVfM359JXzQhw+L8PDY8MVhltiZ1ufvKS6F5vAZYLUXUGtVmlS7mLgNJKvJN9fxd1BlZdqfD3+o4xBUGVCjS3HR/7NJBl/pPHZtKckQ==""
- }";
-
- JsonTestUtils.AssertJsonDeepEquals(expectedPayload, actualPayload);
- JsonTestUtils.AssertJsonDeepEquals(expectedHeader, actualHeader);
- }
-
[TestMethod]
public void Deserialize_TokenResponse()
{