Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bug] IDX10503: Signature validation failed. Token does not have a kid. (System.NotImplementedException: The method or operation is not implemented. at Microsoft.IdentityModel.Tokens.SignatureProvider.Verify...) #1970

Closed
1 task done
KelDazan opened this issue Nov 25, 2022 · 7 comments · Fixed by #2694
Assignees
Labels
Customer reported Indicates issue was opened by customer Documentation The issue is related to adding documentation IdentityModel8x Future breaking issues/features for IdentityModel 8x
Milestone

Comments

@KelDazan
Copy link

KelDazan commented Nov 25, 2022

Which version of Microsoft.IdentityModel are you using?
Issue is reproduced at latest (atm) version - 6.25.0
Issue is NOT reproducing at 6.22.1 version

Where is the issue?

  • M.IM.JsonWebTokens

Is this a new or an existing app?

a. The app is in production and I have upgraded to a new version of Microsoft.IdentityModel.*

Exactly the issue - everything was working on 6.22.1, but since 6.23.0 i am failing a JWT signature check. I am using https://nsec.rocks/ for custom JWT signing and validating. I am using custom TokenValidationParameters.CryptoProviderFactory that points to custom ICryptoProvider provider, that creates custom SignatureProvider for signing an validating JWT. Simplified code is provided below

Repro

Custom classes that are used:

    public record KeyPair
    {
        public string Public { get; }

        public string Private { get; }

        public byte[] PublicKey => Convert.FromBase64String(Public);

        public byte[] PrivateKey => Convert.FromBase64String(Private);

        public KeyPair(
            string publicKey,
            string privateKey)
        {
            Public = string.IsNullOrWhiteSpace(publicKey) ?
                throw new ArgumentNullException(nameof(publicKey)) : publicKey;

            Private = string.IsNullOrWhiteSpace(privateKey) ?
                throw new ArgumentNullException(nameof(privateKey)) : privateKey;
        }

        public KeyPair(string publicKey)
        {
            Public = string.IsNullOrWhiteSpace(publicKey) ?
                throw new ArgumentNullException(nameof(publicKey)) : publicKey;

            Private = string.Empty;
        }
    }

    public class EdDsaSecurityKey : AsymmetricSecurityKey
    {
        public KeyPair KeyPair { get; }

        public EdDsaSecurityKey(
            KeyPair keyPair)
        {
            KeyPair = keyPair ?? throw new ArgumentNullException(nameof(keyPair));
        }

        [Obsolete("HasPrivateKey method is deprecated, please use PrivateKeyStatus.")]
        public override bool HasPrivateKey => !string.IsNullOrWhiteSpace(KeyPair.Private);

        public override PrivateKeyStatus PrivateKeyStatus =>
            !string.IsNullOrWhiteSpace(KeyPair.Private) ? PrivateKeyStatus.Exists  :  PrivateKeyStatus.DoesNotExist;

        public override int KeySize => KeyPair?.PrivateKey?.Length ?? 0;
    }

    public class EdDsaSignatureProvider : SignatureProvider
    {
        private readonly EdDsaSecurityKey _edDsaSecurityKey;

        public EdDsaSignatureProvider(EdDsaSecurityKey key, string algorithm)
            : base(key, algorithm)
        {
            _edDsaSecurityKey = key ?? throw new ArgumentNullException(nameof(key));
        }

        public override byte[] Sign(byte[] input)
        {
            Ed25519 algorithm = new();
            ReadOnlySpan<byte> blob = new(_edDsaSecurityKey.KeyPair.PrivateKey);

            bool wasImported = NSec.Cryptography.Key.TryImport(
                algorithm,
                blob,
                KeyBlobFormat.NSecPrivateKey,
                out Key? key,
                new KeyCreationParameters
                {
                    ExportPolicy = KeyExportPolicies.AllowPlaintextExport
                });

            if (!wasImported || key == null)
                throw new InvalidOperationException("Private key was not imported successfully");

            return algorithm.Sign(key, input);
        }

        public override bool Verify(byte[] input, byte[] signature)
        {
            Ed25519 algorithm = new();
            ReadOnlySpan<byte> blob = new(_edDsaSecurityKey.KeyPair.PublicKey);

            bool wasImported = PublicKey.TryImport(
                algorithm,
                blob,
                KeyBlobFormat.NSecPublicKey,
                out PublicKey? key);

            if(!wasImported || key == null)
                throw new InvalidOperationException("Public key was not imported successfully");

            return algorithm.Verify(key, input, signature);
        }

        protected override void Dispose(bool disposing)
        {

        }
    }

    public class EdDsaCryptoProvider : ICryptoProvider
    {
        public bool IsSupportedAlgorithm(string algorithm, params object[] args)
            => algorithm.Equals(ExtendedSecurityAlgorithms.EdDsa);

        public object Create(string algorithm, params object[] args)
        {
            if (!algorithm.Equals(ExtendedSecurityAlgorithms.EdDsa))
                throw new NotSupportedException($"Only '{ExtendedSecurityAlgorithms.EdDsa}' algorithm is supported");

            EdDsaSecurityKey? key = args.OfType<EdDsaSecurityKey>().FirstOrDefault();
            if (key == null)
                throw new ArgumentException($"Arguments are expected to contain key of type '{nameof(EdDsaSecurityKey)}'");

            return new EdDsaSignatureProvider(key, algorithm);
        }

        public void Release(object cryptoInstance)
        {
            if (cryptoInstance is IDisposable disposableObject)
                disposableObject.Dispose();
        }
    }

Calling the validation:

            TokenValidationParameters validationParameters = new()
            {
                ValidateIssuer = false,
                ValidateAudience = false,
                IssuerSigningKey = new EdDsaSecurityKey(
                    new KeyPair(
                        publicKey: "*public key generated by NSec lib*",
                        privateKey: "*empty - no private key - check only*")),
                ClockSkew = TimeSpan.Zero,
                CryptoProviderFactory = new CryptoProviderFactory()
                {
                    CustomCryptoProvider = new EdDsaCryptoProvider(),
                }
            };

            TokenValidationResult validationResult = new JsonWebTokenHandler().ValidateToken(
                "*a well-formed and signed JWT*",
                validationParameters);

Expected behavior
validationResult.IsValid should return True (if signature is valid, that is)

Actual behavior
An exception in validationResult.Exception, with message:

IDX10503: Signature validation failed. Token does not have a kid. Keys tried: 'Cryptography.Core.EdDsa.EdDsaSecurityKey, KeyId: '3aee9357-d10f-48e1-a993-046c9d639726', InternalId: ''. , KeyId: 3aee9357-d10f-48e1-a993-046c9d639726
'. Number of keys in TokenValidationParameters: '1'. 
Number of keys in Configuration: '0'. 
Exceptions caught:
 'System.NotImplementedException: The method or operation is not implemented.
   at Microsoft.IdentityModel.Tokens.SignatureProvider.Verify(Byte[] input, Int32 inputOffset, Int32 inputLength, Byte[] signature, Int32 signatureOffset, Int32 signatureLength)
   at Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler.IsSignatureValid(Byte[] signatureBytes, Int32 signatureBytesLength, SignatureProvider signatureProvider, Byte[] dataToVerify, Int32 dataToVerifyLength)
   at Microsoft.IdentityModel.Tokens.Base64UrlEncoding.Decode[T,TX,TY,TZ](String input, Int32 offset, Int32 length, TX argx, TY argy, TZ argz, Func`6 action)
   at Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler.ValidateSignature(Byte[] bytes, Int32 len, String stringWithSignature, Int32 signatureStartIndex, SignatureProvider signatureProvider)
   at Microsoft.IdentityModel.Tokens.EncodingUtils.PerformEncodingDependentOperation[T,TX,TY,TZ](String input, Int32 offset, Int32 length, Encoding encoding, TX argx, TY argy, TZ argz, Func`6 action)
   at Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler.ValidateSignature(JsonWebToken jsonWebToken, SecurityKey key, TokenValidationParameters validationParameters)
   at Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler.ValidateSignature(JsonWebToken jwtToken, TokenValidationParameters validationParameters, BaseConfiguration configuration)
'.
token: 'Microsoft.IdentityModel.JsonWebTokens.JsonWebToken'.

Possible solution
Use 6.22.1 version and don't upgrade. I believe i am missing some kind of a setting, or an injection, but i do not understand what exactly. Much obliged for any assistance.

@brentschmaltz
Copy link
Member

@KelDazan our error message is not informative enough.
Can you try implementing: Verify

@brentschmaltz brentschmaltz added Customer reported Indicates issue was opened by customer Documentation The issue is related to adding documentation labels Nov 30, 2022
@KelDazan
Copy link
Author

KelDazan commented Dec 1, 2022

@brentschmaltz

@KelDazan our error message is not informative enough. Can you try implementing: Verify

Thank you very much for the reply!
I was inattentive to full exception message, and didn't realize that it was another Verify method - my mistake.
I have overridden the Verify method that you provided link to and everything worked like a charm.

@brentschmaltz
Copy link
Member

@KelDazan super, sorry for the hassle and this is clearly NOT your issue but ours.
I will improve the message in the exception.
Would you be willing to review the exception message in the hope others would understand what to do?

@KelDazan
Copy link
Author

KelDazan commented Dec 2, 2022

@brentschmaltz
If i will be of any help - yes, sure.

@brentschmaltz
Copy link
Member

@KelDazan finally got to this: #2694

@KelDazan
Copy link
Author

@KelDazan finally got to this: #2694

Much obliged!

@brentschmaltz
Copy link
Member

@KelDazan thanks for bringing this to our attention we have made plans that will help avoid breaks in the future.

  1. We only modify internals on major releases, this addresses 'method not found' issues as our packages all require the same major version.
  2. When adding a new method to a call graph, we will make sure the old call graph still works, it may not be as performant, but if will be non-breaking.
    3 If for some reason we can't make the old call graph work, produce a meaningful error message for NotImplementedExceptions.
  3. Consider postponing the modification until a major release.
  4. Add Roslyn warnings to inform users of the new API(s) to improve performance, reduce allocations, address a bug, etc.

@jennyf19 jennyf19 added this to the 8.0.0 milestone Jul 15, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Customer reported Indicates issue was opened by customer Documentation The issue is related to adding documentation IdentityModel8x Future breaking issues/features for IdentityModel 8x
Projects
None yet
3 participants