Skip to content

Commit

Permalink
Add SecurityTokenSignatureKeyNotFoundWithValidationErrorsException (#…
Browse files Browse the repository at this point in the history
…1615)

* Add SecurityTokenUnableToValidateException 

SecurityTokenUnableToValidateException represents
an exception with validation errors such
that a different signal is sent to the layer above
instead of a key not found exception

Add ValidationFailure as a bit field enum
  • Loading branch information
keegan-caruso authored Mar 30, 2021
1 parent 8e5bb2c commit c67924c
Show file tree
Hide file tree
Showing 18 changed files with 815 additions and 29 deletions.
11 changes: 10 additions & 1 deletion src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1140,7 +1140,16 @@ private static JsonWebToken ValidateSignature(string token, TokenValidationParam
if (kidMatched)
throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidSignatureException(LogHelper.FormatInvariant(TokenLogMessages.IDX10511, keysAttempted, jwtToken.Kid, exceptionStrings, jwtToken)));

throw LogHelper.LogExceptionMessage(new SecurityTokenSignatureKeyNotFoundException(LogHelper.FormatInvariant(TokenLogMessages.IDX10501, jwtToken.Kid, exceptionStrings, jwtToken)));
var expires = jwtToken.TryGetClaim(JwtRegisteredClaimNames.Exp, out var _) ? (DateTime?)jwtToken.ValidTo : null;
var notBefore = jwtToken.TryGetClaim(JwtRegisteredClaimNames.Nbf, out var _) ? (DateTime?)jwtToken.ValidFrom : null;

InternalValidators.ValidateLifetimeAndIssuerAfterSignatureNotValidatedJwt(
jwtToken,
notBefore,
expires,
jwtToken.Kid,
validationParameters,
exceptionStrings);
}

if (keysAttempted.Length > 0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1104,7 +1104,14 @@ private SamlSecurityToken ValidateSignature(SamlSecurityToken samlToken, string
if (keyMatched)
throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidSignatureException(LogHelper.FormatInvariant(TokenLogMessages.IDX10514, keysAttempted, samlToken.Assertion.Signature.KeyInfo, exceptionStrings, samlToken)));

throw LogHelper.LogExceptionMessage(new SecurityTokenSignatureKeyNotFoundException(LogHelper.FormatInvariant(TokenLogMessages.IDX10513, samlToken.Assertion.Signature.KeyInfo, exceptionStrings, samlToken)));
if (samlToken.Assertion.Conditions != null)
InternalValidators.ValidateLifetimeAndIssuerAfterSignatureNotValidatedSaml(
samlToken,
samlToken.Assertion.Conditions.NotBefore,
samlToken.Assertion.Conditions.NotOnOrAfter,
samlToken.Assertion.Signature.KeyInfo.ToString(),
validationParameters,
exceptionStrings);
}

if (keysAttempted.Length > 0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -462,7 +462,14 @@ private Saml2SecurityToken ValidateSignature(Saml2SecurityToken samlToken, strin
if (keyMatched)
throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidSignatureException(LogHelper.FormatInvariant(TokenLogMessages.IDX10514, keysAttempted, samlToken.Assertion.Signature.KeyInfo, exceptionStrings, samlToken)));

throw LogHelper.LogExceptionMessage(new SecurityTokenSignatureKeyNotFoundException(LogHelper.FormatInvariant(TokenLogMessages.IDX10513, samlToken.Assertion.Signature.KeyInfo, exceptionStrings, samlToken)));
if (samlToken.Assertion.Conditions != null)
InternalValidators.ValidateLifetimeAndIssuerAfterSignatureNotValidatedSaml(
samlToken,
samlToken.Assertion.Conditions.NotBefore,
samlToken.Assertion.Conditions.NotOnOrAfter,
samlToken.Assertion.Signature.KeyInfo.ToString(),
validationParameters,
exceptionStrings);
}

if (keysAttempted.Length > 0)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
//------------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation.
// All rights reserved.
//
// This code is licensed under the MIT License.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files(the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions :
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
//------------------------------------------------------------------------------

using System;
using System.Runtime.Serialization;

namespace Microsoft.IdentityModel.Tokens
{
/// <summary>
/// This exception is thrown when a security token contained a key identifier but the key was not found by the runtime
/// and when validation errors exist over the security token. This exception is not intended to be used as a signal
/// to refresh keys.
/// </summary>
[Serializable]
public class SecurityTokenUnableToValidateException : SecurityTokenException
{
[NonSerialized]
const string _Prefix = "Microsoft.IdentityModel." + nameof(SecurityTokenUnableToValidateException) + ".";

[NonSerialized]
const string _ValidationFailureKey = _Prefix + nameof(ValidationFailure);

/// <summary>
/// Indicates the type of the validation failure.
/// </summary>
public ValidationFailure ValidationFailure { get; set; } = ValidationFailure.None;

/// <summary>
/// Initializes a new instance of the <see cref="SecurityTokenSignatureKeyNotFoundException"/> class.
/// </summary>
public SecurityTokenUnableToValidateException()
{
}

/// <summary>
/// Initializes a new instance of the <see cref="SecurityTokenSignatureKeyNotFoundException"/> class.
/// </summary>
/// <param name="validationFailure">The validation failures.</param>
/// <param name="message">Addtional information to be included in the exception and displayed to user.</param>
public SecurityTokenUnableToValidateException(ValidationFailure validationFailure, string message)
: base(message)
{
ValidationFailure = validationFailure;
}

/// <summary>
/// Initializes a new instance of the <see cref="SecurityTokenSignatureKeyNotFoundException"/> class.
/// </summary>
/// <param name="message">Addtional information to be included in the exception and displayed to user.</param>
public SecurityTokenUnableToValidateException(string message)
: base(message)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="SecurityTokenSignatureKeyNotFoundException"/> class.
/// </summary>
/// <param name="message">Addtional information to be included in the exception and displayed to user.</param>
/// <param name="innerException">A <see cref="Exception"/> that represents the root cause of the exception.</param>
public SecurityTokenUnableToValidateException(string message, Exception innerException)
: base(message, innerException)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="SecurityTokenSignatureKeyNotFoundException"/> class.
/// </summary>
/// <param name="info">the <see cref="SerializationInfo"/> that holds the serialized object data.</param>
/// <param name="context">The contextual information about the source or destination.</param>
protected SecurityTokenUnableToValidateException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
SerializationInfoEnumerator enumerator = info.GetEnumerator();
while (enumerator.MoveNext())
{
switch (enumerator.Name)
{
case _ValidationFailureKey:
ValidationFailure = (ValidationFailure)info.GetValue(_ValidationFailureKey, typeof(ValidationFailure));
break;

default:
// Ignore other fields.
break;
}
}
}

/// <inheritdoc/>
public override void GetObjectData(SerializationInfo info, StreamingContext context)
{
base.GetObjectData(info, context);

info.AddValue(_ValidationFailureKey, ValidationFailure);
}
}
}
48 changes: 48 additions & 0 deletions src/Microsoft.IdentityModel.Tokens/Exceptions/ValidationFailure.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//------------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation.
// All rights reserved.
//
// This code is licensed under the MIT License.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files(the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions :
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
//------------------------------------------------------------------------------

namespace Microsoft.IdentityModel.Tokens
{
/// <summary>
/// The reason for being unable to validate
/// </summary>
public enum ValidationFailure
{
/// <summary>
/// Indicates no validation failures
/// </summary>
None = 0b_0000_0000_0000_0000, // 0
/// <summary>
/// Indicates that the lifetime was invalid
/// </summary>
InvalidLifetime = 0b_0000_0000_0000_0001, // 1
/// <summary>
/// Indicates that the issuer was invalid
/// </summary>
InvalidIssuer = 0b_0000_0000_0000_0010, // 2
}
}
2 changes: 2 additions & 0 deletions src/Microsoft.IdentityModel.Tokens/GlobalSuppressions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,5 @@
[assembly: SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Used as platform test", Scope = "member", Target = "~M:Microsoft.IdentityModel.Tokens.ECDsaAdapter.SupportsECParameters~System.Boolean")]
[assembly: SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Appropriate exception will be caught.", Scope = "member", Target = "~M:Microsoft.IdentityModel.Tokens.InMemoryCryptoProviderCache.TryRemove(Microsoft.IdentityModel.Tokens.SignatureProvider)~System.Boolean")]
[assembly: SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Appropriate exception will be caught.", Scope = "member", Target = "~M:Microsoft.IdentityModel.Tokens.EventBasedLRUCache`2.OnStart")]
[assembly: SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Used as validation", Scope = "member", Target = "~M:Microsoft.IdentityModel.Tokens.InternalValidators.ValidateLifetimeAndIssuerAfterSignatureNotValidatedJwt(Microsoft.IdentityModel.Tokens.SecurityToken,System.Nullable{System.DateTime},System.Nullable{System.DateTime},System.String,Microsoft.IdentityModel.Tokens.TokenValidationParameters,System.Text.StringBuilder)")]
[assembly: SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Used as validation", Scope = "member", Target = "~M:Microsoft.IdentityModel.Tokens.InternalValidators.ValidateLifetimeAndIssuerAfterSignatureNotValidatedSaml(Microsoft.IdentityModel.Tokens.SecurityToken,System.Nullable{System.DateTime},System.Nullable{System.DateTime},System.String,Microsoft.IdentityModel.Tokens.TokenValidationParameters,System.Text.StringBuilder)")]
144 changes: 144 additions & 0 deletions src/Microsoft.IdentityModel.Tokens/InternalValidators.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
//------------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation.
// All rights reserved.
//
// This code is licensed under the MIT License.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files(the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions :
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
//------------------------------------------------------------------------------

using System;
using System.Text;
using Microsoft.IdentityModel.Logging;
using TokenLogMessages = Microsoft.IdentityModel.Tokens.LogMessages;

namespace Microsoft.IdentityModel.Tokens
{
/// <summary>
/// Validators meant to be kept internal
/// </summary>
internal static class InternalValidators
{
/// <summary>
/// Called after signature validation has failed. Will always throw an exception.
/// </summary>
/// <exception cref="SecurityTokenSignatureKeyNotFoundException">
/// If the lifetime and issuer are valid
/// </exception>
/// <exception cref="SecurityTokenUnableToValidateException">
/// If the lifetime or issuer are invalid
/// </exception>
internal static void ValidateLifetimeAndIssuerAfterSignatureNotValidatedJwt(SecurityToken securityToken, DateTime? notBefore, DateTime? expires, string kid, TokenValidationParameters validationParameters, StringBuilder exceptionStrings)
{
bool validIssuer = false;
bool validLifetime = false;

try
{
Validators.ValidateLifetime(notBefore, expires, securityToken, validationParameters);
validLifetime = true;
}
catch (Exception)
{
// validLifetime will remain false
}

try
{
Validators.ValidateIssuer(securityToken.Issuer, securityToken, validationParameters);
validIssuer = true;
}
catch (Exception)
{
// validIssuer will remain false
}

if (validLifetime && validIssuer)
throw LogHelper.LogExceptionMessage(new SecurityTokenSignatureKeyNotFoundException(LogHelper.FormatInvariant(TokenLogMessages.IDX10501, kid, exceptionStrings, securityToken)));
else
{
var validationFailure = ValidationFailure.None;

if (!validLifetime)
validationFailure |= ValidationFailure.InvalidLifetime;

if (!validIssuer)
validationFailure |= ValidationFailure.InvalidIssuer;

throw LogHelper.LogExceptionMessage(new SecurityTokenUnableToValidateException(
validationFailure,
LogHelper.FormatInvariant(TokenLogMessages.IDX10516, kid, exceptionStrings, securityToken, validLifetime, validIssuer)));
}
}

/// <summary>
/// Called after signature validation has failed. Will always throw an exception.
/// </summary>
/// <exception cref="SecurityTokenSignatureKeyNotFoundException">
/// If the lifetime and issuer are valid
/// </exception>
/// <exception cref="SecurityTokenUnableToValidateException">
/// If the lifetime or issuer are invalid
/// </exception>
internal static void ValidateLifetimeAndIssuerAfterSignatureNotValidatedSaml(SecurityToken securityToken, DateTime? notBefore, DateTime? expires, string keyInfo, TokenValidationParameters validationParameters, StringBuilder exceptionStrings)
{
bool validIssuer = false;
bool validLifetime = false;

try
{
Validators.ValidateLifetime(notBefore, expires, securityToken, validationParameters);
validLifetime = true;
}
catch (Exception)
{
// validLifetime will remain false
}

try
{
Validators.ValidateIssuer(securityToken.Issuer, securityToken, validationParameters);
validIssuer = true;
}
catch (Exception)
{
// validIssuer will remain false
}

if (validLifetime && validIssuer)
throw LogHelper.LogExceptionMessage(new SecurityTokenSignatureKeyNotFoundException(LogHelper.FormatInvariant(TokenLogMessages.IDX10513, keyInfo, exceptionStrings, securityToken)));
else
{
var validationFailure = ValidationFailure.None;

if (!validLifetime)
validationFailure |= ValidationFailure.InvalidLifetime;

if (!validIssuer)
validationFailure |= ValidationFailure.InvalidIssuer;

throw LogHelper.LogExceptionMessage(new SecurityTokenUnableToValidateException(
validationFailure,
LogHelper.FormatInvariant(TokenLogMessages.IDX10515, keyInfo, exceptionStrings, securityToken, validLifetime, validIssuer)));
}
}
}
}
2 changes: 2 additions & 0 deletions src/Microsoft.IdentityModel.Tokens/LogMessages.cs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ internal static class LogMessages
public const string IDX10512 = "IDX10512: Signature validation failed. Token does not have KeyInfo. Keys tried: '{0}'.\nExceptions caught:\n '{1}'.\ntoken: '{2}'.";
public const string IDX10513 = "IDX10513: Signature validation failed. Unable to match key: \nKeyInfo: '{0}'.\nExceptions caught:\n '{1}'. \ntoken: '{2}'.";
public const string IDX10514 = "IDX10514: Signature validation failed. Keys tried: '{0}'. \nKeyInfo: '{1}'. \nExceptions caught:\n '{2}'.\ntoken: '{3}'.";
public const string IDX10515 = "IDX10515: Signature validation failed. Unable to match key: \nKeyInfo: '{0}'.\nExceptions caught:\n '{1}'. \ntoken: '{2}'. Valid Lifetime: '{3}'. Valid Issuer: '{4}'";
public const string IDX10516 = "IDX10516: Signature validation failed. Unable to match key: \nkid: '{0}'.\nExceptions caught:\n '{1}'. \ntoken: '{2}'. Valid Lifetime: '{3}'. Valid Issuer: '{4}'";

// encryption / decryption
// public const string IDX10600 = "IDX10600:";
Expand Down
Loading

0 comments on commit c67924c

Please sign in to comment.