diff --git a/build/cgmanifest.json b/build/cgmanifest.json
index fb683d916e..8e8d3a2779 100644
--- a/build/cgmanifest.json
+++ b/build/cgmanifest.json
@@ -1,5 +1,5 @@
{
- "Registrations": [
+ "registrations": [
{
"component": {
"type": "git",
@@ -10,6 +10,6 @@
}
}
],
- "Version": 1,
+ "version": 1,
"$schema": "https://json.schemastore.org/component-detection-manifest.json"
-}
\ No newline at end of file
+}
diff --git a/src/Microsoft.IdentityModel.JsonWebTokens/Microsoft.IdentityModel.JsonWebTokens.csproj b/src/Microsoft.IdentityModel.JsonWebTokens/Microsoft.IdentityModel.JsonWebTokens.csproj
index eef130efa1..bfab4b253e 100644
--- a/src/Microsoft.IdentityModel.JsonWebTokens/Microsoft.IdentityModel.JsonWebTokens.csproj
+++ b/src/Microsoft.IdentityModel.JsonWebTokens/Microsoft.IdentityModel.JsonWebTokens.csproj
@@ -28,7 +28,7 @@
-
+
diff --git a/src/Microsoft.IdentityModel.Tokens.Saml/InternalAPI.Unshipped.txt b/src/Microsoft.IdentityModel.Tokens.Saml/InternalAPI.Unshipped.txt
index 98bb8f1d05..748018b3ab 100644
--- a/src/Microsoft.IdentityModel.Tokens.Saml/InternalAPI.Unshipped.txt
+++ b/src/Microsoft.IdentityModel.Tokens.Saml/InternalAPI.Unshipped.txt
@@ -1,3 +1,5 @@
+const Microsoft.IdentityModel.Tokens.Saml.LogMessages.IDX11402 = "IDX11402: Unable to read SamlSecurityToken. Exception thrown: '{0}'." -> string
+const Microsoft.IdentityModel.Tokens.Saml2.LogMessages.IDX13003 = "IDX13003: Unable to read Saml2SecurityToken. Exception thrown: '{0}'." -> string
Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.StackFrames
Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.ValidatedConditions
Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.ValidatedConditions.ValidatedAudience.get -> string
@@ -7,13 +9,22 @@ Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.ValidatedConditions
Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.ValidatedConditions.ValidatedLifetime.get -> Microsoft.IdentityModel.Tokens.ValidatedLifetime?
Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.ValidatedConditions.ValidatedLifetime.set -> void
Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.ValidateTokenAsync(Microsoft.IdentityModel.Tokens.Saml.SamlSecurityToken samlToken, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, Microsoft.IdentityModel.Tokens.CallContext callContext, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task>
+Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.ValidateTokenAsync(string token, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, Microsoft.IdentityModel.Tokens.CallContext callContext, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task>
+Microsoft.IdentityModel.Tokens.Saml.SamlValidationError
+Microsoft.IdentityModel.Tokens.Saml.SamlValidationError.SamlValidationError(Microsoft.IdentityModel.Tokens.MessageDetail MessageDetail, Microsoft.IdentityModel.Tokens.ValidationFailureType failureType, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame) -> void
+Microsoft.IdentityModel.Tokens.Saml.SamlValidationError.SamlValidationError(Microsoft.IdentityModel.Tokens.MessageDetail messageDetail, Microsoft.IdentityModel.Tokens.ValidationFailureType failureType, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame, System.Exception innerException) -> void
Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.StackFrames
Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.ValidateTokenAsync(Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityToken samlToken, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, Microsoft.IdentityModel.Tokens.CallContext callContext, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task>
+Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.ValidateTokenAsync(string token, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, Microsoft.IdentityModel.Tokens.CallContext callContext, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task>
+Microsoft.IdentityModel.Tokens.Saml2.Saml2ValidationError
+Microsoft.IdentityModel.Tokens.Saml2.Saml2ValidationError.Saml2ValidationError(Microsoft.IdentityModel.Tokens.MessageDetail MessageDetail, Microsoft.IdentityModel.Tokens.ValidationFailureType failureType, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame) -> void
+Microsoft.IdentityModel.Tokens.Saml2.Saml2ValidationError.Saml2ValidationError(Microsoft.IdentityModel.Tokens.MessageDetail messageDetail, Microsoft.IdentityModel.Tokens.ValidationFailureType failureType, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame, System.Exception innerException) -> void
+override Microsoft.IdentityModel.Tokens.Saml.SamlValidationError.GetException() -> System.Exception
+override Microsoft.IdentityModel.Tokens.Saml2.Saml2ValidationError.GetException() -> System.Exception
static Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.StackFrames.IssuerValidationFailed -> System.Diagnostics.StackFrame
static Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.StackFrames.SignatureValidationFailed -> System.Diagnostics.StackFrame
static Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.ValidateSignature(Microsoft.IdentityModel.Tokens.Saml.SamlSecurityToken samlToken, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, Microsoft.IdentityModel.Tokens.CallContext callContext) -> Microsoft.IdentityModel.Tokens.ValidationResult
static Microsoft.IdentityModel.Tokens.Saml.SamlTokenUtilities.PopulateValidationParametersWithCurrentConfigurationAsync(Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task
-Microsoft.IdentityModel.Tokens.Saml2.SamlSecurityTokenHandler.ValidateTokenAsync(SamlSecurityToken samlToken, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, Microsoft.IdentityModel.Tokens.CallContext callContext, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task>
static Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.StackFrames.AssertionConditionsNull -> System.Diagnostics.StackFrame
static Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.StackFrames.AssertionConditionsValidationFailed -> System.Diagnostics.StackFrame
static Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.StackFrames.AssertionNull -> System.Diagnostics.StackFrame
@@ -34,4 +45,6 @@ static Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.StackFrame
static Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.StackFrames.TokenNull -> System.Diagnostics.StackFrame
static Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.StackFrames.TokenValidationParametersNull -> System.Diagnostics.StackFrame
static Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.ValidateSignature(Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityToken samlToken, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, Microsoft.IdentityModel.Tokens.CallContext callContext) -> Microsoft.IdentityModel.Tokens.ValidationResult
+virtual Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.ReadSaml2Token(string token, Microsoft.IdentityModel.Tokens.CallContext callContext) -> Microsoft.IdentityModel.Tokens.ValidationResult
+virtual Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.ReadSamlToken(string token, Microsoft.IdentityModel.Tokens.CallContext callContext) -> Microsoft.IdentityModel.Tokens.ValidationResult
virtual Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.ValidateConditions(Microsoft.IdentityModel.Tokens.Saml.SamlSecurityToken samlToken, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, Microsoft.IdentityModel.Tokens.CallContext callContext) -> Microsoft.IdentityModel.Tokens.ValidationResult
diff --git a/src/Microsoft.IdentityModel.Tokens.Saml/Saml/Exceptions/SamlValidationError.cs b/src/Microsoft.IdentityModel.Tokens.Saml/Saml/Exceptions/SamlValidationError.cs
new file mode 100644
index 0000000000..48aff2a17d
--- /dev/null
+++ b/src/Microsoft.IdentityModel.Tokens.Saml/Saml/Exceptions/SamlValidationError.cs
@@ -0,0 +1,26 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.Diagnostics;
+
+namespace Microsoft.IdentityModel.Tokens.Saml
+{
+ internal class SamlValidationError : ValidationError
+ {
+ internal SamlValidationError(MessageDetail messageDetail, ValidationFailureType failureType, Type exceptionType, StackFrame stackFrame, Exception innerException) : base(messageDetail, failureType, exceptionType, stackFrame, innerException)
+ {
+ }
+
+ internal override Exception GetException()
+ {
+ if (ExceptionType == typeof(SamlSecurityTokenReadException))
+ {
+ var exception = new SamlSecurityTokenReadException(MessageDetail.Message, InnerException);
+ return exception;
+ }
+
+ return base.GetException();
+ }
+ }
+}
diff --git a/src/Microsoft.IdentityModel.Tokens.Saml/Saml/LogMessages.cs b/src/Microsoft.IdentityModel.Tokens.Saml/Saml/LogMessages.cs
index 60814cdb2a..ad28653191 100644
--- a/src/Microsoft.IdentityModel.Tokens.Saml/Saml/LogMessages.cs
+++ b/src/Microsoft.IdentityModel.Tokens.Saml/Saml/LogMessages.cs
@@ -15,6 +15,7 @@ internal static class LogMessages
// SecurityTokenHandler messages
internal const string IDX11400 = "IDX11400: The '{0}', can only process SecurityTokens of type: '{1}'. The SecurityToken received is of type: '{2}'.";
internal const string IDX11401 = "IDX11401: Unable to validate token. TokenValidationParameters.RequireAudience is true but no AudienceRestrictions were found in the inbound token.";
+ internal const string IDX11402 = "IDX11402: Unable to read SamlSecurityToken. Exception thrown: '{0}'.";
// signature creation / validation
internal const string IDX11312 = "IDX11312: Unable to validate token. A SamlSamlAttributeStatement can only have one SamlAttribute of type 'Actor'. This special SamlAttribute is used in delegation scenarios.";
diff --git a/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.ReadToken.cs b/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.ReadToken.cs
new file mode 100644
index 0000000000..f9c658aaf1
--- /dev/null
+++ b/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.ReadToken.cs
@@ -0,0 +1,57 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.Text;
+using System.Xml;
+using Microsoft.IdentityModel.Logging;
+using TokenLogMessages = Microsoft.IdentityModel.Tokens.LogMessages;
+
+namespace Microsoft.IdentityModel.Tokens.Saml
+{
+ public partial class SamlSecurityTokenHandler : SecurityTokenHandler
+ {
+ ///
+ /// Converts a string into an instance of .
+ ///
+ /// a Saml token as a string.
+ /// An opaque context used to store work when working with authentication artifacts.
+ /// A
+ /// If is null or empty.
+ /// If 'token.Length' is greater than .
+ internal virtual ValidationResult ReadSamlToken(string token, CallContext callContext)
+ {
+ if (string.IsNullOrEmpty(token))
+ return ValidationError.NullParameter(nameof(token), ValidationError.GetCurrentStackFrame());
+
+ if (token.Length > MaximumTokenSizeInBytes)
+ return new ValidationError(
+ new MessageDetail(
+ TokenLogMessages.IDX10209,
+ LogHelper.MarkAsNonPII(token.Length),
+ LogHelper.MarkAsNonPII(MaximumTokenSizeInBytes)),
+ ValidationFailureType.TokenExceedsMaximumSize,
+ typeof(ArgumentOutOfRangeException),
+ ValidationError.GetCurrentStackFrame());
+
+ try
+ {
+ using (var reader = XmlDictionaryReader.CreateTextReader(Encoding.UTF8.GetBytes(token), XmlDictionaryReaderQuotas.Max))
+ {
+ return ReadSamlToken(reader);
+ }
+ }
+#pragma warning disable CA1031 // Do not catch general exception types
+ catch (Exception ex)
+#pragma warning restore CA1031 // Do not catch general exception types
+ {
+ return new SamlValidationError(
+ new MessageDetail(LogMessages.IDX11402, ex.Message),
+ ValidationFailureType.TokenReadingFailed,
+ typeof(SamlSecurityTokenReadException),
+ ValidationError.GetCurrentStackFrame(),
+ ex);
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.ValidateSignature.cs b/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.ValidateSignature.cs
index e23f3c2267..3691a62159 100644
--- a/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.ValidateSignature.cs
+++ b/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.ValidateSignature.cs
@@ -42,11 +42,10 @@ internal static ValidationResult ValidateSignature(
new MessageDetail(
TokenLogMessages.IDX10504,
samlToken.Assertion.CanonicalString),
- ValidationFailureType.SignatureValidationFailed,
+ ValidationFailureType.TokenIsNotSigned,
typeof(SecurityTokenValidationException),
ValidationError.GetCurrentStackFrame());
- IList? keys = null;
SecurityKey? resolvedKey = null;
bool keyMatched = false;
@@ -65,10 +64,7 @@ internal static ValidationResult ValidateSignature(
resolvedKey = SamlTokenUtilities.ResolveTokenSigningKey(samlToken.Assertion.Signature.KeyInfo, validationParameters);
}
- bool canMatchKey = samlToken.Assertion.Signature.KeyInfo != null;
- List? errors = null;
ValidationError? error = null;
- StringBuilder? keysAttempted = null;
if (resolvedKey is not null)
{
@@ -79,19 +75,21 @@ internal static ValidationResult ValidateSignature(
error = result.UnwrapError();
}
- else
- {
- if (validationParameters.TryAllIssuerSigningKeys)
- keys = validationParameters.IssuerSigningKeys;
- }
- if (keys is not null)
+ bool canMatchKey = samlToken.Assertion.Signature.KeyInfo != null;
+ List? errors = null;
+ StringBuilder? keysAttempted = null;
+
+ if (!keyMatched && validationParameters.TryAllIssuerSigningKeys && validationParameters.IssuerSigningKeys is not null)
{
// Control reaches here only if the key could not be resolved and TryAllIssuerSigningKeys is set to true.
// We try all the keys in the list and return the first valid key. This is the degenerate case.
- for (int i = 0; i < keys.Count; i++)
+ for (int i = 0; i < validationParameters.IssuerSigningKeys.Count; i++)
{
- SecurityKey key = keys[i];
+ SecurityKey key = validationParameters.IssuerSigningKeys[i];
+ if (key is null)
+ continue;
+
var result = ValidateSignatureUsingKey(key, samlToken, validationParameters, callContext);
if (result.IsValid)
return result;
@@ -110,7 +108,7 @@ internal static ValidationResult ValidateSignature(
TokenLogMessages.IDX10514,
keysAttempted?.ToString(),
samlToken.Assertion.Signature.KeyInfo,
- GetErrorStrings(error, errors),
+ GetErrorString(error, errors),
samlToken),
ValidationFailureType.SignatureValidationFailed,
typeof(SecurityTokenInvalidSignatureException),
@@ -127,7 +125,7 @@ internal static ValidationResult ValidateSignature(
new MessageDetail(
TokenLogMessages.IDX10512,
keysAttemptedString,
- GetErrorStrings(error, errors),
+ GetErrorString(error, errors),
samlToken),
ValidationFailureType.SignatureValidationFailed,
typeof(SecurityTokenSignatureKeyNotFoundException),
@@ -156,9 +154,9 @@ private static ValidationResult ValidateSignatureUsingKey(SecurityK
else
{
var validationError = samlToken.Assertion.Signature.Verify(
- key,
- validationParameters.CryptoProviderFactory ?? key.CryptoProviderFactory,
- callContext);
+ key,
+ validationParameters.CryptoProviderFactory ?? key.CryptoProviderFactory,
+ callContext);
if (validationError is null)
{
@@ -173,23 +171,23 @@ private static ValidationResult ValidateSignatureUsingKey(SecurityK
}
}
- private static string GetErrorStrings(ValidationError? error, List? errors)
+ private static string GetErrorString(ValidationError? error, List? errorList)
{
// This method is called if there are errors in the signature validation process.
// This check is there to account for the optional parameter.
if (error is not null)
return error.MessageDetail.Message;
- if (errors is null)
+ if (errorList is null)
return string.Empty;
- if (errors.Count == 1)
- return errors[0].MessageDetail.Message;
+ if (errorList.Count == 1)
+ return errorList[0].MessageDetail.Message;
StringBuilder sb = new();
- for (int i = 0; i < errors.Count; i++)
+ for (int i = 0; i < errorList.Count; i++)
{
- sb.AppendLine(errors[i].MessageDetail.Message);
+ sb.AppendLine(errorList[i].MessageDetail.Message);
}
return sb.ToString();
diff --git a/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.ValidateToken.Internal.cs b/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.ValidateToken.Internal.cs
index 04a2d3c92e..b5115646f3 100644
--- a/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.ValidateToken.Internal.cs
+++ b/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.ValidateToken.Internal.cs
@@ -14,9 +14,26 @@ namespace Microsoft.IdentityModel.Tokens.Saml
///
public partial class SamlSecurityTokenHandler : SecurityTokenHandler
{
-#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
internal async Task> ValidateTokenAsync(
-#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
+ string token,
+ ValidationParameters validationParameters,
+ CallContext callContext,
+ CancellationToken cancellationToken)
+ {
+ if (token is null)
+ return ValidationError.NullParameter(nameof(token), ValidationError.GetCurrentStackFrame());
+
+ if (validationParameters is null)
+ return ValidationError.NullParameter(nameof(validationParameters), ValidationError.GetCurrentStackFrame());
+
+ var tokenReadingResult = ReadSamlToken(token, callContext);
+ if (!tokenReadingResult.IsValid)
+ return tokenReadingResult.UnwrapError().AddCurrentStackFrame();
+
+ return await ValidateTokenAsync(tokenReadingResult.UnwrapResult(), validationParameters, callContext, cancellationToken).ConfigureAwait(false);
+ }
+
+ internal async Task> ValidateTokenAsync(
SamlSecurityToken samlToken,
ValidationParameters validationParameters,
CallContext callContext,
diff --git a/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Exceptions/Saml2ValidationError.cs b/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Exceptions/Saml2ValidationError.cs
new file mode 100644
index 0000000000..b8f5803e23
--- /dev/null
+++ b/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Exceptions/Saml2ValidationError.cs
@@ -0,0 +1,26 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.Diagnostics;
+
+namespace Microsoft.IdentityModel.Tokens.Saml2
+{
+ internal class Saml2ValidationError : ValidationError
+ {
+ internal Saml2ValidationError(MessageDetail messageDetail, ValidationFailureType failureType, Type exceptionType, StackFrame stackFrame, Exception innerException) : base(messageDetail, failureType, exceptionType, stackFrame, innerException)
+ {
+ }
+
+ internal override Exception GetException()
+ {
+ if (ExceptionType == typeof(Saml2SecurityTokenReadException))
+ {
+ var exception = new Saml2SecurityTokenReadException(MessageDetail.Message, InnerException);
+ return exception;
+ }
+
+ return base.GetException();
+ }
+ }
+}
diff --git a/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/LogMessages.cs b/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/LogMessages.cs
index c440f6694f..a0e71cf811 100644
--- a/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/LogMessages.cs
+++ b/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/LogMessages.cs
@@ -16,6 +16,7 @@ internal static class LogMessages
internal const string IDX13400 = "IDX13400: The '{0}', can only process SecurityTokens of type: '{1}'. The SecurityToken received is of type: '{2}'.";
internal const string IDX13001 = "IDX13001: A SAML2 assertion that specifies an AuthenticationContext DeclarationReference is not supported.To handle DeclarationReference, extend the Saml2SecurityTokenHandler and override ProcessAuthenticationStatement.";
internal const string IDX13002 = "IDX13002: Unable to validate token. TokenValidationParameters.RequireAudience is true but no AudienceRestrictions were found in the inbound token.";
+ internal const string IDX13003 = "IDX13003: Unable to read Saml2SecurityToken. Exception thrown: '{0}'.";
// signature creation / validation
internal const string IDX13509 = "IDX13509: Unable to validate token, Subject is null.";
diff --git a/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.ReadToken.cs b/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.ReadToken.cs
new file mode 100644
index 0000000000..cf74c5fc0b
--- /dev/null
+++ b/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.ReadToken.cs
@@ -0,0 +1,59 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.Text;
+using System.Xml;
+using Microsoft.IdentityModel.Logging;
+using Microsoft.IdentityModel.Tokens.Saml;
+
+using TokenLogMessages = Microsoft.IdentityModel.Tokens.LogMessages;
+
+namespace Microsoft.IdentityModel.Tokens.Saml2
+{
+ public partial class Saml2SecurityTokenHandler : SecurityTokenHandler
+ {
+ ///
+ /// Converts a string into an instance of .
+ ///
+ /// a Saml token as a string.
+ /// An opaque context used to store work when working with authentication artifacts.
+ /// A
+ /// If is null or empty.
+ /// If 'token.Length' is greater than .
+ internal virtual ValidationResult ReadSaml2Token(string token, CallContext callContext)
+ {
+ if (string.IsNullOrEmpty(token))
+ return ValidationError.NullParameter(nameof(token), ValidationError.GetCurrentStackFrame());
+
+ if (token.Length > MaximumTokenSizeInBytes)
+ return new ValidationError(
+ new MessageDetail(
+ TokenLogMessages.IDX10209,
+ LogHelper.MarkAsNonPII(token.Length),
+ LogHelper.MarkAsNonPII(MaximumTokenSizeInBytes)),
+ ValidationFailureType.TokenReadingFailed,
+ typeof(ArgumentException),
+ ValidationError.GetCurrentStackFrame());
+
+ try
+ {
+ using (var reader = XmlDictionaryReader.CreateTextReader(Encoding.UTF8.GetBytes(token), XmlDictionaryReaderQuotas.Max))
+ {
+ return ReadSaml2Token(reader);
+ }
+ }
+#pragma warning disable CA1031 // Do not catch general exception types
+ catch (Exception ex)
+#pragma warning restore CA1031 // Do not catch general exception types
+ {
+ return new Saml2ValidationError(
+ new MessageDetail(LogMessages.IDX13003, ex.Message),
+ ValidationFailureType.TokenReadingFailed,
+ typeof(Saml2SecurityTokenReadException),
+ ValidationError.GetCurrentStackFrame(),
+ ex);
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.ValidateSignature.cs b/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.ValidateSignature.cs
index 20df361659..74d2884b26 100644
--- a/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.ValidateSignature.cs
+++ b/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.ValidateSignature.cs
@@ -15,9 +15,7 @@ public partial class Saml2SecurityTokenHandler : SecurityTokenHandler
internal static ValidationResult ValidateSignature(
Saml2SecurityToken samlToken,
ValidationParameters validationParameters,
-#pragma warning disable CA1801 // Review unused parameters
CallContext callContext)
-#pragma warning restore CA1801 // Review unused parameters
{
if (samlToken is null)
{
@@ -43,11 +41,10 @@ internal static ValidationResult ValidateSignature(
new MessageDetail(
TokenLogMessages.IDX10504,
samlToken.Assertion.CanonicalString),
- ValidationFailureType.SignatureValidationFailed,
+ ValidationFailureType.TokenIsNotSigned,
typeof(SecurityTokenValidationException),
ValidationError.GetCurrentStackFrame());
- IList? keys = null;
SecurityKey? resolvedKey = null;
bool keyMatched = false;
@@ -66,10 +63,7 @@ internal static ValidationResult ValidateSignature(
resolvedKey = SamlTokenUtilities.ResolveTokenSigningKey(samlToken.Assertion.Signature.KeyInfo, validationParameters);
}
- bool canMatchKey = samlToken.Assertion.Signature.KeyInfo != null;
- List? errors = null;
ValidationError? error = null;
- StringBuilder? keysAttempted = null;
if (resolvedKey is not null)
{
@@ -80,19 +74,21 @@ internal static ValidationResult ValidateSignature(
error = result.UnwrapError();
}
- else
- {
- if (validationParameters.TryAllIssuerSigningKeys)
- keys = validationParameters.IssuerSigningKeys;
- }
- if (keys is not null)
+ bool canMatchKey = samlToken.Assertion.Signature.KeyInfo != null;
+ List? errors = null;
+ StringBuilder? keysAttempted = null;
+
+ if (!keyMatched && validationParameters.TryAllIssuerSigningKeys && validationParameters.IssuerSigningKeys is not null)
{
// Control reaches here only if the key could not be resolved and TryAllIssuerSigningKeys is set to true.
// We try all the keys in the list and return the first valid key. This is the degenerate case.
- for (int i = 0; i < keys.Count; i++)
+ for (int i = 0; i < validationParameters.IssuerSigningKeys.Count; i++)
{
- SecurityKey key = keys[i];
+ SecurityKey key = validationParameters.IssuerSigningKeys[i];
+ if (key is null)
+ continue;
+
var result = ValidateSignatureUsingKey(key, samlToken, validationParameters, callContext);
if (result.IsValid)
return result;
@@ -157,9 +153,9 @@ private static ValidationResult ValidateSignatureUsingKey(SecurityK
else
{
var validationError = samlToken.Assertion.Signature.Verify(
- key,
- validationParameters.CryptoProviderFactory ?? key.CryptoProviderFactory,
- callContext);
+ key,
+ validationParameters.CryptoProviderFactory ?? key.CryptoProviderFactory,
+ callContext);
if (validationError is null)
{
diff --git a/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.ValidateToken.Internal.cs b/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.ValidateToken.Internal.cs
index 6979857bdb..d806f6a453 100644
--- a/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.ValidateToken.Internal.cs
+++ b/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.ValidateToken.Internal.cs
@@ -15,6 +15,25 @@ namespace Microsoft.IdentityModel.Tokens.Saml2
///
public partial class Saml2SecurityTokenHandler : SecurityTokenHandler
{
+ internal async Task> ValidateTokenAsync(
+ string token,
+ ValidationParameters validationParameters,
+ CallContext callContext,
+ CancellationToken cancellationToken)
+ {
+ if (token is null)
+ return ValidationError.NullParameter(nameof(token), ValidationError.GetCurrentStackFrame());
+
+ if (validationParameters is null)
+ return ValidationError.NullParameter(nameof(validationParameters), ValidationError.GetCurrentStackFrame());
+
+ var tokenReadingResult = ReadSaml2Token(token, callContext);
+ if (!tokenReadingResult.IsValid)
+ return tokenReadingResult.UnwrapError().AddCurrentStackFrame();
+
+ return await ValidateTokenAsync(tokenReadingResult.UnwrapResult(), validationParameters, callContext, cancellationToken).ConfigureAwait(false);
+ }
+
internal async Task> ValidateTokenAsync(
Saml2SecurityToken samlToken,
ValidationParameters validationParameters,
diff --git a/src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt b/src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt
index 1d35266764..e7eaf3cd7f 100644
--- a/src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt
+++ b/src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt
@@ -34,4 +34,6 @@ static Microsoft.IdentityModel.Tokens.ValidationError.GetCurrentStackFrame(strin
static readonly Microsoft.IdentityModel.Tokens.ValidationFailureType.NoTokenAudiencesProvided -> Microsoft.IdentityModel.Tokens.ValidationFailureType
static readonly Microsoft.IdentityModel.Tokens.ValidationFailureType.NoValidationParameterAudiencesProvided -> Microsoft.IdentityModel.Tokens.ValidationFailureType
static readonly Microsoft.IdentityModel.Tokens.ValidationFailureType.SignatureAlgorithmValidationFailed -> Microsoft.IdentityModel.Tokens.ValidationFailureType
+static readonly Microsoft.IdentityModel.Tokens.ValidationFailureType.TokenExceedsMaximumSize -> Microsoft.IdentityModel.Tokens.ValidationFailureType
+static readonly Microsoft.IdentityModel.Tokens.ValidationFailureType.TokenIsNotSigned -> Microsoft.IdentityModel.Tokens.ValidationFailureType
static readonly Microsoft.IdentityModel.Tokens.ValidationFailureType.XmlValidationFailed -> Microsoft.IdentityModel.Tokens.ValidationFailureType
diff --git a/src/Microsoft.IdentityModel.Tokens/Microsoft.IdentityModel.Tokens.csproj b/src/Microsoft.IdentityModel.Tokens/Microsoft.IdentityModel.Tokens.csproj
index 5c0ac53091..459a772334 100644
--- a/src/Microsoft.IdentityModel.Tokens/Microsoft.IdentityModel.Tokens.csproj
+++ b/src/Microsoft.IdentityModel.Tokens/Microsoft.IdentityModel.Tokens.csproj
@@ -57,7 +57,7 @@
-
+
diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/ValidationFailureType.cs b/src/Microsoft.IdentityModel.Tokens/Validation/ValidationFailureType.cs
index 6cf24dc28c..cd58d536c7 100644
--- a/src/Microsoft.IdentityModel.Tokens/Validation/ValidationFailureType.cs
+++ b/src/Microsoft.IdentityModel.Tokens/Validation/ValidationFailureType.cs
@@ -69,6 +69,12 @@ private class TokenTypeValidationFailure : ValidationFailureType { internal Toke
public static readonly ValidationFailureType SignatureValidationFailed = new SignatureValidationFailure("SignatureValidationFailed");
private class SignatureValidationFailure : ValidationFailureType { internal SignatureValidationFailure(string name) : base(name) { } }
+ ///
+ /// Defines a type that represents that the token is not signed.
+ ///
+ public static readonly ValidationFailureType TokenIsNotSigned = new TokenNotSignedFailure("TokenIsNotSigned");
+ private class TokenNotSignedFailure : ValidationFailureType { internal TokenNotSignedFailure(string name) : base(name) { } }
+
///
/// Defines a type that represents that the token's signature algorithm validation failed.
///
@@ -99,6 +105,12 @@ private class TokenReplayValidationFailure : ValidationFailureType { internal To
public static readonly ValidationFailureType TokenReadingFailed = new TokenReadingFailure("TokenReadingFailed");
private class TokenReadingFailure : ValidationFailureType { internal TokenReadingFailure(string name) : base(name) { } }
+ ///
+ /// Defines a type that represents that a token exceeds the maximum size.
+ ///
+ public static readonly ValidationFailureType TokenExceedsMaximumSize = new TokenExceedsMaximumSizeFailure("TokenExceedsMaximumSize");
+ private class TokenExceedsMaximumSizeFailure : ValidationFailureType { internal TokenExceedsMaximumSizeFailure(string name) : base(name) { } }
+
///
/// Defines a type that represents that a JWE could not be decrypted.
///
diff --git a/test/Microsoft.IdentityModel.TestUtils/ExpectedException.cs b/test/Microsoft.IdentityModel.TestUtils/ExpectedException.cs
index 366b357b88..6f8f717392 100644
--- a/test/Microsoft.IdentityModel.TestUtils/ExpectedException.cs
+++ b/test/Microsoft.IdentityModel.TestUtils/ExpectedException.cs
@@ -9,6 +9,8 @@
using System.Text.Json;
using System.Xml;
using Microsoft.IdentityModel.Tokens;
+using Microsoft.IdentityModel.Tokens.Saml;
+using Microsoft.IdentityModel.Tokens.Saml2;
namespace Microsoft.IdentityModel.TestUtils
{
@@ -74,6 +76,16 @@ public static ExpectedException XmlException(string substringExpected = null, Ty
return new ExpectedException(typeof(XmlException), substringExpected, inner);
}
+ public static ExpectedException SamlSecurityTokenReadException(string substringExpected = null, Type inner = null, string contains = null)
+ {
+ return new ExpectedException(typeof(SamlSecurityTokenReadException), substringExpected, inner);
+ }
+
+ public static ExpectedException Saml2SecurityTokenReadException(string substringExpected = null, Type inner = null, string contains = null)
+ {
+ return new ExpectedException(typeof(Saml2SecurityTokenReadException), substringExpected, inner);
+ }
+
public static ExpectedException NoExceptionExpected
{
get { return new ExpectedException(); }
diff --git a/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandlerTests.ReadToken.cs b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandlerTests.ReadToken.cs
new file mode 100644
index 0000000000..d51f1f0c3b
--- /dev/null
+++ b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandlerTests.ReadToken.cs
@@ -0,0 +1,111 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using Xunit;
+using Microsoft.IdentityModel.TestUtils;
+using TokenLogMessages = Microsoft.IdentityModel.Tokens.LogMessages;
+using Microsoft.IdentityModel.Logging;
+
+namespace Microsoft.IdentityModel.Tokens.Saml2.Tests
+{
+ public partial class Saml2SecurityTokenHandlerTests
+ {
+ [Theory, MemberData(nameof(ReadTokenTestCases), DisableDiscoveryEnumeration = true)]
+ public void ReadToken_ResultType(TokenReadingTheoryData theoryData)
+ {
+ CompareContext context = TestUtilities.WriteHeader($"{this}.ReadToken_ResultType", theoryData);
+ Saml2SecurityTokenHandler handler = new Saml2SecurityTokenHandler();
+ ValidationResult result = handler.ReadSaml2Token(
+ theoryData.Token,
+ new CallContext());
+
+ if (result.IsValid)
+ {
+ IdentityComparer.AreEqual(
+ result.UnwrapResult(),
+ handler.ReadToken(theoryData.Token),
+ context);
+
+ theoryData.ExpectedException.ProcessNoException(context);
+ }
+ else
+ {
+ ValidationError validationError = result.UnwrapError();
+ IdentityComparer.AreStringsEqual(
+ validationError.FailureType.Name,
+ theoryData.Result.UnwrapError().FailureType.Name,
+ context);
+
+ Exception exception = validationError.GetException();
+ theoryData.ExpectedException.ProcessException(exception, context);
+ }
+
+ TestUtilities.AssertFailIfErrors(context);
+ }
+
+ public static TheoryData ReadTokenTestCases
+ {
+ get
+ {
+ var theoryData = new TheoryData();
+
+ theoryData.Add(new TokenReadingTheoryData("Valid_SAML_Token")
+ {
+ Token = ReferenceTokens.Saml2Token_Valid,
+ });
+
+ theoryData.Add(new TokenReadingTheoryData("Invalid_NullToken")
+ {
+ Token = null,
+ ExpectedException = ExpectedException.SecurityTokenArgumentNullException("IDX10000:"),
+ Result = new ValidationError(
+ new MessageDetail(
+ TokenLogMessages.IDX10000,
+ LogHelper.MarkAsNonPII("token")),
+ ValidationFailureType.NullArgument,
+ typeof(SecurityTokenArgumentNullException),
+ null)
+ });
+
+ theoryData.Add(new TokenReadingTheoryData("Invalid_EmptyToken")
+ {
+ Token = string.Empty,
+ ExpectedException = ExpectedException.SecurityTokenArgumentNullException("IDX10000:"),
+ Result = new ValidationError(
+ new MessageDetail(
+ TokenLogMessages.IDX10000,
+ LogHelper.MarkAsNonPII("token")),
+ ValidationFailureType.NullArgument,
+ typeof(SecurityTokenArgumentNullException),
+ null)
+ });
+
+ theoryData.Add(new TokenReadingTheoryData("Invalid_MalformedToken")
+ {
+ Token = ReferenceTokens.Saml2Token_MissingVersion,
+ ExpectedException = ExpectedException.Saml2SecurityTokenReadException("IDX13003:", inner: typeof(Saml2SecurityTokenReadException)),
+ Result = new ValidationError(
+ new MessageDetail(LogMessages.IDX13003, "exception message"),
+ ValidationFailureType.TokenReadingFailed,
+ typeof(Saml2SecurityTokenReadException),
+ null),
+ });
+
+ return theoryData;
+ }
+ }
+ }
+
+ public class TokenReadingTheoryData : TheoryDataBase
+ {
+ public TokenReadingTheoryData(string testId)
+ {
+ TestId = testId;
+ }
+
+ public string Token { get; set; }
+
+ internal ValidationResult Result { get; set; }
+ }
+}
diff --git a/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandlerTests.ReadToken.cs b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandlerTests.ReadToken.cs
new file mode 100644
index 0000000000..a62c5c4938
--- /dev/null
+++ b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandlerTests.ReadToken.cs
@@ -0,0 +1,111 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using Xunit;
+using Microsoft.IdentityModel.TestUtils;
+using TokenLogMessages = Microsoft.IdentityModel.Tokens.LogMessages;
+using Microsoft.IdentityModel.Logging;
+
+namespace Microsoft.IdentityModel.Tokens.Saml.Tests
+{
+ public partial class SamlSecurityTokenHandlerTests
+ {
+ [Theory, MemberData(nameof(ReadTokenTestCases), DisableDiscoveryEnumeration = true)]
+ public void ReadToken_ResultType(TokenReadingTheoryData theoryData)
+ {
+ CompareContext context = TestUtilities.WriteHeader($"{this}.ReadToken_ResultType", theoryData);
+ SamlSecurityTokenHandler handler = new SamlSecurityTokenHandler();
+ ValidationResult result = handler.ReadSamlToken(
+ theoryData.Token,
+ new CallContext());
+
+ if (result.IsValid)
+ {
+ IdentityComparer.AreEqual(
+ result.UnwrapResult(),
+ handler.ReadToken(theoryData.Token),
+ context);
+
+ theoryData.ExpectedException.ProcessNoException(context);
+ }
+ else
+ {
+ ValidationError validationError = result.UnwrapError();
+ IdentityComparer.AreStringsEqual(
+ validationError.FailureType.Name,
+ theoryData.Result.UnwrapError().FailureType.Name,
+ context);
+
+ Exception exception = validationError.GetException();
+ theoryData.ExpectedException.ProcessException(exception, context);
+ }
+
+ TestUtilities.AssertFailIfErrors(context);
+ }
+
+ public static TheoryData ReadTokenTestCases
+ {
+ get
+ {
+ var theoryData = new TheoryData();
+
+ theoryData.Add(new TokenReadingTheoryData("Valid_SAML_Token")
+ {
+ Token = ReferenceTokens.SamlToken_Valid,
+ });
+
+ theoryData.Add(new TokenReadingTheoryData("Invalid_NullToken")
+ {
+ Token = null,
+ ExpectedException = ExpectedException.SecurityTokenArgumentNullException("IDX10000:"),
+ Result = new ValidationError(
+ new MessageDetail(
+ TokenLogMessages.IDX10000,
+ LogHelper.MarkAsNonPII("token")),
+ ValidationFailureType.NullArgument,
+ typeof(SecurityTokenArgumentNullException),
+ null)
+ });
+
+ theoryData.Add(new TokenReadingTheoryData("Invalid_EmptyToken")
+ {
+ Token = string.Empty,
+ ExpectedException = ExpectedException.SecurityTokenArgumentNullException("IDX10000:"),
+ Result = new ValidationError(
+ new MessageDetail(
+ TokenLogMessages.IDX10000,
+ LogHelper.MarkAsNonPII("token")),
+ ValidationFailureType.NullArgument,
+ typeof(SecurityTokenArgumentNullException),
+ null)
+ });
+
+ theoryData.Add(new TokenReadingTheoryData("Invalid_MalformedToken")
+ {
+ Token = ReferenceTokens.SamlToken_MissingMajorVersion,
+ ExpectedException = ExpectedException.SamlSecurityTokenReadException("IDX11402:", inner: typeof(SamlSecurityTokenReadException)),
+ Result = new ValidationError(
+ new MessageDetail(LogMessages.IDX11402, "exception message"),
+ ValidationFailureType.TokenReadingFailed,
+ typeof(SamlSecurityTokenReadException),
+ null),
+ });
+
+ return theoryData;
+ }
+ }
+ }
+
+ public class TokenReadingTheoryData : TheoryDataBase
+ {
+ public TokenReadingTheoryData(string testId)
+ {
+ TestId = testId;
+ }
+
+ public string Token { get; set; }
+
+ internal ValidationResult Result { get; set; }
+ }
+}