4

I am in the process of updating my application to use Azure Active Directory as an OAuth 2.0 Authentication Server. At the moment I am successfully using the Authorization Code grant type and receiving access_token and id_token values.

Now, I am trying to validate the returned id_token. I am following the steps outlined in the doco, and I am able to find the public key that was used to sign the JWT. For example, this is the record being returned by the Azure REST endpoint

Returned Public Keys

As I understand it, these are the available public keys. I can filter it down to one (using the kid value in the returned JWT header). But what I'm struggling to understand is what each field is meant to represent.

Am I using the n/e fields to create the modulus and exponent values for the SHA256 Public Key? Am I meant to use the x5c value instead? Sorry if this is an obvious question, but is there any documentation on, given values like above, how a Public Key can be created for it? I am doing this in Java, so anything specific to that would be greatly appreciated.

EdH
  • 4,918
  • 4
  • 24
  • 34
  • It looks like the above records are in JSON Web Key format. If this is the case, hopefully there some libraries that can create the appropriate Key objects. – EdH Nov 24 '16 at 09:25
  • Considering the attribute containing the REST endpoint returning the above data structure is called "jwks_uri", that would seem to indicate that the data elements are JSON Web Key (JWK) instances! – EdH Nov 24 '16 at 09:31
  • This looks like a relevant library that can create the keys: http://connect2id.com/products/nimbus-jose-jwt – EdH Nov 24 '16 at 09:36
  • 2
    Upvoting as I have searched for hours and failed to find a list or explanation of those fields. I'm retrieving a token and plugging in jwt.io shows the key id, but when I put the corresponding x5c or n values into the signature field, it still has invalid signature. What values should I use to make it work?? Saw someone also using key headers and footers? – Ryan D Jun 06 '17 at 22:11
  • 1
    @RyanD Concatenate "-----BEGIN CERTIFICATE-----" before and "-----END CERTIFICATE-----" after the x5c value, respectfully. – rj2700 Nov 08 '17 at 15:22
  • But @RyanD does being up a good point to EdH's question. I've also been searching but could not find a direct answer to this. What is the process of generating the public key from x5t, e and n value? Is it better practice to use these values in your own application to generate the public key or better to just use the x5c value wrapped as a certificate? Also, speaking of the x5c value, why is that a JSONArray, are we expecting additional values? If so, how would one know how to choose a value? – rj2700 Nov 08 '17 at 15:26
  • With the BEGIN and END CERTIFCATE pieces I indeed can get jwt.io to validate the signature of the token! In regards to the actual question, I ended up asking something similar myself and I think we came to an answer. I'll post answer below. +1 @EdH – Ryan D Nov 08 '17 at 16:29

2 Answers2

1

To verify the signature of id_token, we can use JwtSecurityTokenHandler class if you were developing with C#. And you can refer JsonWebTokenValidator.cs for the code sample to using this class. I also copy this class here for convenience:

 public class JsonWebTokenValidator
{
    public JwtSecurityToken Validate(string token)
    {
        string stsDiscoveryEndpoint = "https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration";

        ConfigurationManager<OpenIdConnectConfiguration> configManager = new ConfigurationManager<OpenIdConnectConfiguration>(stsDiscoveryEndpoint);

        OpenIdConnectConfiguration config = configManager.GetConfigurationAsync().Result;

        TokenValidationParameters validationParameters = new TokenValidationParameters
        {
            ValidateAudience = false,
            ValidateIssuer = false,
            IssuerSigningTokens = config.SigningTokens,
            ValidateLifetime = false
        };

        JwtSecurityTokenHandler tokendHandler = new JwtSecurityTokenHandler();

        SecurityToken jwt;

        var result = tokendHandler.ValidateToken(token, validationParameters, out jwt);

        return jwt as JwtSecurityToken;
    }
}
Fei Xue
  • 14,369
  • 1
  • 19
  • 27
1

Here is a similar question I posted and got help on that might help. I think you want to use the x5c, which is the full certificate chain, and take the public key from that to validate the signature of your JWT. The modulus and exponent (n and e) will produce the public key of only the first certificate in the certificate chain, but that is insufficient to validate the signature.

Here are some code snippets I used to extract the public key from the endpoint. The AzurePublicKey class is just the POJO for the json.

private PublicKey convertKey(AzurePublicKey azKey) {
    PublicKey publicKey = null;

    BigInteger modulus = new BigInteger(1, Base64.getUrlDecoder().decode(azKey.getN()));
    BigInteger exponent = new BigInteger(1, Base64.getUrlDecoder().decode(azKey.getE()));

    try {
        publicKey = KeyFactory.getInstance("RSA").generatePublic(new RSAPublicKeySpec(modulus, exponent));

        // load cert
        CertificateFactory factory;
        X509Certificate cert = null; 
        try {
            factory = CertificateFactory.getInstance("X.509");
            cert = (X509Certificate) factory.generateCertificate(new ByteArrayInputStream(DatatypeConverter.parseBase64Binary(azKey.getX5c().iterator().next())));
        } catch (CertificateException e) {
            e.printStackTrace();
        }

        // grab public key
        publicKey = (RSAPublicKey)cert.getPublicKey();

        System.out.println("[");
        System.out.println("kid : " + azKey.getKeyIdentifier());
        System.out.println("e : " + azKey.getE());
        System.out.println("n : " + azKey.getN());

        System.out.println("Maybe this : " + DatatypeConverter.printBase64Binary(publicKey.getEncoded()) );
        System.out.println("]");

    } catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }

    return publicKey;
}

And the code to use that public key to validate the signature using the JJWT library.

Jws<Claims> claims = Jwts.parser()
                                .setSigningKeyResolver(signingKeyResolver)
                                .parseClaimsJws(token.replace(TOKEN_PREFIX, ""));


        String issuer   = claims.getBody().getIssuer();
        if(issuer == null || !VALID_ISSUERS.contains(issuer))
            throw new IncorrectClaimException(claims.getHeader(), claims.getBody(), "Invalid Issuer in Token.", new Throwable("Invalid Issuer in Token."));

The SigningKeyResolverImpl just provides the correct public key based on the kid of the token header. If parsing does not throw an exception you can validate the token claims against values you were expecting as I've validated the issuer here against a list of VALID_ISSUERS that I'm expecting.

It's been a while so I'm fuzzy but I hope this helps.

Ryan D
  • 1,023
  • 11
  • 16
  • I have just checked that modulus and exponent produce the same key that is extracted by the X509 certificate. However, I couldn't verify a single signature of any of the tokes generated by azure ad on the jwt.io site. Are you using the version 2.0 or the older version? – pedroct92 Nov 17 '17 at 20:42
  • Forgive me, not sure on version of what? For jjwt library, looks like 0.9.0 is the latest but I have used 0.7.0. https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt – Ryan D Nov 21 '17 at 18:42
  • My apologies, I mean the version of the api of Microsoft that you are using to get the keys. I have processed in a similar way as you did but none of my extracted keys validated the signature of my jwt token. – pedroct92 Nov 21 '17 at 18:45
  • 1
    Ah, yep. v2.0 from this URL: https://login.microsoftonline.com/common/discovery/v2.0/keys – Ryan D Nov 21 '17 at 18:56