6

I get an access token on Azure Portal free subscription, the header is:

{
  "typ": "JWT",
  "alg": "RS256",
  "x5t": "7_Zuf1tvkwLxYaHS3q6lUjUYIGw",
  "kid": "7_Zuf1tvkwLxYaHS3q6lUjUYIGw"
}

so I get the x5c from here, and put

-----Begin Certificate----- MIIDBTCCAe......cNpO9oReBUsX -----End Certificate-----

    ze7xq1zGljQihJgcNpO9oReBUsX

in https://jwt.io/, Signature is Verified.

But when I try to verify the signature with jjwt and jose4j with JDK1.8, following steps in this refrence, I get below exception on the line

PublicKey publicKey = keyFactory.generatePublic(keySpec);

java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: IOException: ObjectIdentifier() -- data isn't an object ID (tag = -96)
at java.base/sun.security.rsa.RSAKeyFactory.engineGeneratePublic(RSAKeyFactory.java:204)
at java.base/java.security.KeyFactory.generatePublic(KeyFactory.java:352)
at com.ipscape.api.v1_0.external.other.JwtExample.decodeJwt(JwtExample.java:41)
at com.ipscape.api.v1_0.external.other.JwtExample.main(JwtExample.java:72)
Caused by: java.security.InvalidKeyException: IOException: ObjectIdentifier() -- data isn't an object ID (tag = -96)
at java.base/sun.security.x509.X509Key.decode(X509Key.java:396)
at java.base/sun.security.x509.X509Key.decode(X509Key.java:401)
at java.base/sun.security.rsa.RSAPublicKeyImpl.<init>(RSAPublicKeyImpl.java:86)
at java.base/sun.security.rsa.RSAKeyFactory.generatePublic(RSAKeyFactory.java:297)
at java.base/sun.security.rsa.RSAKeyFactory.engineGeneratePublic(RSAKeyFactory.java:200)

The codes using jose4j:

        String publicKeyPEM =
        "MIIDBTCCAe2gAwIBAgIQE7nbxEiAlqhFdKnsKV+nuTANBgkqhkiG9w0BAQsFADAtMSswKQYDVQQDEyJhY2NvdW50cy5hY2Nlc3Njb250cm9sLndpbmRvd3MubmV0MB4XDTE4MDYyMTAwMDAwMFoXDTIwMDYyMTAwMDAwMFowLTErMCkGA1UEAxMiYWNjb3VudHMuYWNjZXNzY29udHJvbC53aW5kb3dzLm5ldDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL1cxvfI3wPu1HFGZL6wnSHfcqX6DlvezLF1wbqUs6/DS5GhxtvHk6+9nKyNuCjznkEKql6bTIgnUbvTPr0UUKa3hezq/D9nkGu4qX8LVONVWkb53mjnN45lnVfOLh6VQ7J8/9Ut/ybbnhcn2a12vWiR0c6899TgtRp+i0bkT9dYl+3/wfP8+bBqmolwno7yojv/DxMz86mzhS7lW6mS9zzYtdsy6IHcF2P9XIac2TaP0efUpLrQY81wuTE3gUh2s6j7tqNH7aKK2PNAuxXtyGtZ9r+bg0gMrDXXVKJylQO9m4Z5J0vuz8xgCElLg3uJPOI8q/j0YrLe5rHy67ACg0MCAwEAAaMhMB8wHQYDVR0OBBYEFDnSNW3pMmrshl3iBAS4OSLCu/7GMA0GCSqGSIb3DQEBCwUAA4IBAQAFs3C5sfXSfoi7ea62flYEukqyVMhrDrpxRlvIuXqL11g8KEXlk8pS8gEnRtU6NBeHhMrhYSuiqj7/2jUT1BR3zJ2bChEyEpIgOFaiTUxq6tXdpWi/M7ibf8O/1sUtjgYktwJlSL6FEVAMFH82TxCoTWp2g5i2lmZQ7KxiKhG+Vl9nw1bPX57hkWWhR7Hpes0MbpGNZI2IEpZSjNG1IWPPOBcaOh4ed2WBQcLcaTuAaELlaxanQaC0B3029To80MnzpZuadaul3+jN7JQg0MpHdJJ8GMHAWe/IjXc0evJNhVUcKON41hzTu0R+Sze7xq1zGljQihJgcNpO9oReBUsX";

    X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64.getDecoder().decode(publicKeyPEM.getBytes()));
    KeyFactory keyFactory = KeyFactory.getInstance("RSA");
    PublicKey publicKey = keyFactory.generatePublic(keySpec);

    JwtConsumer jwtConsumer = new JwtConsumerBuilder()
        .setRequireExpirationTime()
        .setVerificationKey(publicKey)
        .build();
Henry ips
  • 61
  • 1
  • 4
  • You can take a look at https://community.auth0.com/t/problem-validating-jwt-with-x509-cert-pubkey-from-jwks/9705 – Valy Dia May 28 '20 at 11:27

2 Answers2

13

x5c is a (X.509) certificate, not a public key. Actually it is an a array of certs forming a chain if necessary, although this example is a single selfsigned cert. The PEM header and trailer lines for a cert should say 'BEGIN CERTIFICATE' and 'END CERTIFICATE' in all-caps, and the base64 should have linebreaks added, unlike the JWT format which is base64 but is NOT PEM.

To read a cert use java.security.cert.CertificateFactory. (This can also handle a chain, as either just a sequence of certs which it calls PkiPath, or a (trivial) PKCS7 message which is standard and usually called p7b or p7c.) Either give it the correct PEM format, or the correct binary/DER format; the latter is easier because it is just base64-decoding the JWT x5c format (as you are now doing, before you put it in a keyspec which is wrong, but java.util.Base64.Decoder doesn't require the base64 in bytes, it can take a String). If you only need the pubkey, you can then get it from the cert. Do something like:

String certb64 = "...";
byte[] certder = Base64.getDecoder().decode(certb64);
InputStream certstream = new ByteArrayInputStream (certder);
Certificate cert = CertificateFactory.getInstance("X.509").generateCertificate(certstream);
PublicKey key = cert.getPublicKey();
Community
  • 1
  • 1
dave_thompson_085
  • 34,712
  • 6
  • 50
  • 70
  • Thanks @dave_thompson_085, I add below codes after getting PublicKey, ` Claims body = null; JwtParser jwtParser = Jwts.parser().setSigningKey(key.getEncoded()); Jws claimsJws=jwtParser.parseClaimsJws(token); body =claimsJws.getBody();` Then it get `Exception in thread "main" java.lang.IllegalArgumentException: Key bytes can only be specified for HMAC signatures. Please specify a PublicKey or PrivateKey instance. at io.jsonwebtoken.impl.DefaultJwtParser.parseClaimsJws(DefaultJwtParser.java:541) ` – Henry ips Aug 23 '18 at 01:57
  • which is io/jsonwebtoken/jjwt/0.9.0/jjwt-0.9.0-sources.jar!/io/jsonwebtoken/impl/DefaultJwtParser.java Assert.isTrue(algorithm.isHmac(), "Key bytes can only be specified for HMAC signatures. Please specify a PublicKey or PrivateKey instance."); but the algorithm="RS256", not HMAC. Is anything wrong? – Henry ips Aug 23 '18 at 01:58
  • @Henryips I was going to say please put changes to your Q in your Q, because Stack policy is that comments can be deleted any time and especially code is unreadable -- but as you already found an answer probably no one needs to read your Q. – dave_thompson_085 Aug 24 '18 at 04:23
0

This works:

        JwtConsumer jwtConsumer = new JwtConsumerBuilder()
        .setRequireExpirationTime()
        .setExpectedAudience("https://example.com/")
        .setVerificationKey(publicKey)
        .build();

    JwtClaims jwtDecoded = null;
    jwtDecoded = jwtConsumer.processToClaims(token);

    Map<String, Object> jwtClaims = jwtDecoded.getClaimsMap();
    String iss = (String) jwtClaims.get("iss");
Henry ips
  • 61
  • 1
  • 4