4

I am trying to implement a signature verification endpoint - or ASP.net WebAPI action filter, to verify that a token has in fact come from AWS Cognito - validate its signature.

I am using the following code, but it always returns invalid. The Javascript code example also below works perfectly with the same keys / token.

Can anyone help?

Thanks, KH

CSharp

public IHttpActionResult Verify([FromBody] string accessToken)
        {
            string[] parts = accessToken.Split('.');

            //From the Cognito JWK set
            //{"alg":"RS256","e":"myE","kid":"myKid","kty":"RSA","n":"myN","use":"sig"}]}
            var n = Base64UrlDecode("q7ocE2u-JSe1P4AF6_Nasae7e7wUoUxJq058CueDFs9R5fvWQTtAN1rMxBCeLQ7Q8Q0u-vqxr83b6N9ZR5zWUU2stgYzrDTANbIn9zMGDZvSR1tMpun5eAArKW5fcxGFj6klQ0bctlUATSGU5y6xmYoe_U9ycLlPxh5mDluR7V6GbunE1IXJHqcyy-s7dxYdGynTbsLemwmyjDaInGGsM3gMdPAJc29PXozm87ZKY52U7XQN0TMB9Ipwsix443zbE_8WX2mvKjU5yvucFdc4WZdoXN9SGs3HGAeL6Asjc0S6DCruuNiKYj4-MkKh_hlTkH7Rj2CeoV7H3GNS0IOqnQ");
            var e = Base64UrlDecode("AQAB");

            RSACryptoServiceProvider provider = new RSACryptoServiceProvider();
            provider.ImportParameters(new RSAParameters
            {
                Exponent = new BigInteger(e).ToByteArrayUnsigned(),
                Modulus = new BigInteger(n).ToByteArrayUnsigned()
            });

            SHA512Managed sha512 = new SHA512Managed();
            byte[] hash = sha512.ComputeHash(Encoding.UTF8.GetBytes(parts[0] + "." + parts[1]));

            RSAPKCS1SignatureDeformatter rsaDeformatter = new RSAPKCS1SignatureDeformatter(provider);
            rsaDeformatter.SetHashAlgorithm(sha512.GetType().FullName);

            if (!rsaDeformatter.VerifySignature(hash, Base64UrlDecode(parts[2])))
                throw new ApplicationException(string.Format("Invalid signature"));

            return Ok(true);
        }

        // from JWT spec
        private static byte[] Base64UrlDecode(string input)
        {
            var output = input;
            output = output.Replace('-', '+'); // 62nd char of encoding
            output = output.Replace('_', '/'); // 63rd char of encoding
            switch (output.Length % 4) // Pad with trailing '='s
            {
                case 0: break; // No pad chars in this case
                case 1: output += "==="; break; // Three pad chars
                case 2: output += "=="; break; // Two pad chars
                case 3: output += "="; break; // One pad char
                default: throw new System.Exception("Illegal base64url string!");
            }
            var converted = Convert.FromBase64String(output); // Standard base64 decoder
            return converted;
        }

JavaScript

var jwkToPem = require('jwk-to-pem');
var jwt = require('jsonwebtoken');
var jwks = //jwk set file, which you can find at https://cognito-idp.{region}.amazonaws.com/{userPoolId}/.well-known/jwks.json.

//Decode token
var decoded = jwt.decode(token, {complete: true});

//Get the correct key from the jwks based on the kid
var jwk = jwks.keys.filter(function(v) {
    return v.kid === decoded.header.kid;
})[0];

//Convert the key to pem
var pem = jwkToPem(jwk);

//Verify the token with the pem
jwt.verify(token, pem, function(err, decoded) {
    //if decoded exists, its valid
});
4

2 回答 2

2

Replace

SHA512Managed sha512 = new SHA512Managed();

by

SHA256CryptoServiceProvider sha256 = new SHA256CryptoServiceProvider();

Don't forget to set properly the hash algorithm properly as well

rsaDeformatter.SetHashAlgorithm("SHA256");   
于 2017-04-05T21:56:57.363 回答
0

Flo's answer works but its built into .net now. Using https://rafpe.ninja/2017/07/30/net-core-jwt-authentication-using-aws-cognito-user-pool/ which has more details on how to build it into the .net core middleware:

    public RsaSecurityKey SigningKey(string Key, string Expo)
    {
        return new RsaSecurityKey(new RSAParameters()
        {
            Modulus = Base64UrlEncoder.DecodeBytes(Key),
            Exponent = Base64UrlEncoder.DecodeBytes(Expo)
        });
    }

    public TokenValidationParameters TokenValidationParameters()
    {
        // Basic settings - signing key to validate with, audience and issuer.
        return new TokenValidationParameters
        {
            // Basic settings - signing key to validate with, IssuerSigningKey and issuer.
            IssuerSigningKey = this.SigningKey(CognitoConstants.key,CognitoConstants.expo),
            ValidIssuer = CognitoConstants.Issuer,
            ValidAudience = CognitoConstants.clientid,//Same value you send in the cognito request url

            // when receiving a token, check that the signing key
            ValidateIssuerSigningKey = true,

            // When receiving a token, check that we've signed it.
            ValidateIssuer = true,

            // When receiving a token, check that it is still valid.
            ValidateLifetime = true,

            // Do not validate Audience on the "access" token since Cognito does not supply it but it is on the "id"
            ValidateAudience = true,

            // This defines the maximum allowable clock skew - i.e. provides a tolerance on the token expiry time 
            // when validating the lifetime. As we're creating the tokens locally and validating them on the same 
            // machines which should have synchronised time, this can be set to zero. Where external tokens are
            // used, some leeway here could be useful.
            ClockSkew = TimeSpan.FromMinutes(0)
        };

    }

    private bool ValidateToken(string token)
    {
        var tokenHandler = new JwtSecurityTokenHandler();

        if (tokenHandler.CanReadToken(token))
        {
            var validationParams = TokenValidationParameters();

            SecurityToken validatedToken;
            //ValidateToken throws if it fails so if you want to return false this needs changing
            var principal = tokenHandler.ValidateToken(token, validationParams, out validatedToken);
            return validatedToken != null;
        }
        return false;
    }
于 2021-02-19T11:52:02.290 回答