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() {