Google OAuth2 JWT token verification exception

5.8k views Asked by At

I'm facing OAuth2 JWT token verification exception last hour (so no one can access my application):

java.security.SignatureException: Signature length not correct: got 256 but was expecting 128. I'm using google-http-client 1.20.0 and Java 1.7.0. Same configuration worked so far - any ideas?

Stacktrace

java.security.SignatureException: Signature length not correct: got 256 but was expecting 128
    at sun.security.rsa.RSASignature.engineVerify(Unknown Source) ~[na:1.7.0_45]
    at java.security.Signature$Delegate.engineVerify(Unknown Source) ~[na:1.7.0_45]
    at java.security.Signature.verify(Unknown Source) ~[na:1.7.0_45]
    at com.google.api.client.util.SecurityUtils.verify(SecurityUtils.java:164) ~[google-http-client-1.20.0.jar:1.20.0]
6

There are 6 answers

0
qtxo On BEST ANSWER

Same problem here, I added the source code of GoogleIdTokenVerifier to my project and changed the method:

 public boolean verify(GoogleIdToken googleIdToken) throws GeneralSecurityException, IOException {
    // check the payload
    if (!super.verify(googleIdToken)) {
      return false;
    }
    // verify signature
    for (PublicKey publicKey : publicKeys.getPublicKeys()) {
      try {
        if (googleIdToken.verifySignature(publicKey)) {
            return true;
          }
    } catch (Exception e) {
        System.err.println("Verify Token:" + e);
    }
    }
    return false;
  }

just handle the exception, the second certificate works fine.

Edit: you can subclass as Erik-z suggested if you want to make it more clean:

Edit 2: I can't make it work using the code below, I will stick to the ugly hack above.

package com.my.project.package;

import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.PublicKey;

import com.google.api.client.auth.openidconnect.IdTokenVerifier;
import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken;
import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.json.JsonFactory;

// Remember to remove this class later by making it deprecated
@Deprecated
public class GoogleIdTokenVerifier2 extends GoogleIdTokenVerifier {

    // Add constructors as needed
    public GoogleIdTokenVerifier2(HttpTransport transport, JsonFactory jsonFactory) {
        super(transport, jsonFactory);
    }

    @Override
    public boolean verify(GoogleIdToken googleIdToken) throws GeneralSecurityException, IOException {
        // check the payload
        if (!((IdTokenVerifier)this).verify(googleIdToken)) {
            return false;
        }
        // verify signature
        for (PublicKey publicKey : getPublicKeysManager().getPublicKeys()) {
            try {
                if (googleIdToken.verifySignature(publicKey)) {
                    return true;
                }
            } catch (Exception e) {
                System.err.println("Verify Token:" + e);
            }
        }
        return false;
    }
}
0
class On

It looks to me like the libraries may be behaving badly. As an alternative to offline token verification, you can use Google's OAuth2 endpoints for verifying tokens. A basic example from the API explorer can be seen here.

You can check a token with a curl command curl https://www.googleapis.com/oauth2/v2/tokeninfo?id_token=[id_token], for example:

curl https://www.googleapis.com/oauth2/v2/tokeninfo?id_token=eyJhbGciOiJSUzI1NiIsImtpZCI6IjRlYjczOTg0MzBkNTNjZjZjNGZkMGU5YmM4NzkzZWViZWNkMWY1NWUifQ.eyJpc3MiOiJhY2NvdW50cy5nb29nbGUuY29tIiwic3ViIjoiMTA3Mzc3MTkxNjgxODAyNjY5ODY2IiwiYXpwIjoiMTE2MjY4ODY3NTIuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJhdF9oYXNoIjoieGJSVGJOdFJYRnJzcUJHTkRtRTR6USIsImF1ZCI6IjExNjI2ODg2NzUyLmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29tIiwiY19oYXNoIjoiU3JFa25WczRUejhQSWJicExnNXF2QSIsImlhdCI6MTQzNDA0MTY5OSwiZXhwIjoxNDM0MDQ1Mjk5fQ.vqQXCTFfbDqpTYnfFrDD7m68oEuGqd8NWa4s9wstOrrcyuVKUsqFXM_2bH-un_4C8UBvqtQEyzU_-53DxgvhCHQ7S0W-wtQ9YMoJcy7iL1wDjcy1p7aFVoeGCoqxWv1vzlWTUDu_FnD9oIBSAawyDexvRwwGxN8O1D8nzyj__1DQ_ivxIMF-j1W89mc7adK4p5B8ioZA_PI-AGawX2Nm8t58yBMIYrTk0XExr9Pf4eHHRGbrQxcd0ERGHbRMYuG6k-MzdnVNHIScgZ3Cixx9v15PbQ5hXExNvleifG_Wk3Thnz0j6Uacr4fbi-mO93-h8c0r3BSvQ270_JqlpL5q5Q
0
Gerolf Scherr On

If you don't want to (or can't) change the source of the google library, you can just extend the GoogleIdTokenVerifier. (you have to duplicate another method which accesses some private variables - fortunately all of them are accessible via get-members). This works for me:

GoogleIdTokenVerifier myVerifier = new GoogleIdTokenVerifier(httpTransport, jsonFactory) {

    public boolean superVerify(IdToken idToken) {
              return (getIssuer()== null || idToken.verifyIssuer(getIssuer()))
                  && (getAudience() == null || idToken.verifyAudience(getAudience()))
                  && idToken.verifyTime(getClock().currentTimeMillis(), getAcceptableTimeSkewSeconds());
    }


    @Override
  public boolean verify(GoogleIdToken googleIdToken) throws GeneralSecurityException, IOException {
      // check the payload
      if (!superVerify(googleIdToken)) {
          log.info("superVerify returned false");
        return false;
      }
      // verify signature
      for (PublicKey publicKey : getPublicKeysManager().getPublicKeys()) {
              try {
                      if (googleIdToken.verifySignature(publicKey)) {
                              log.info("verifySignature: success!");
                              return true;
                      }
              } catch (Exception e) {
                      log.info("error verifying!", e);
              }
      }
      return false;
    }

};
2
Ittai On

Don't think it's the final solution but a temporary work-around which definitely works is to change the audience of the verifier to the tokenId.

GoogleIdTokenVerifier verifier = new GoogleIdTokenVerifier.Builder(transport, jsonFactory).setAudience(Arrays.asList(clientId)).build();

to

GoogleIdTokenVerifier verifier = new GoogleIdTokenVerifier.Builder(transport, jsonFactory)
                    .setAudience(Arrays.asList(tokenResponse.getIdToken())).build();
0
Gábor AUTH On

The root cause is on the Google side, the certs in the JSON is in bad order:

https://www.googleapis.com/oauth2/v1/certs

You can adjust the order of them, like this:

http://test.gacivs.info/frontend/certs.json

After, you can specify your custom URL (or using mine :) of the JSON with the GooglePublicKeysManager.setPublicCertsEncodedUrl(...) method:

final GoogleIdToken idToken = GoogleIdToken.parse(JSON_FACTORY, token);
final GooglePublicKeysManager manager = new GooglePublicKeysManager.Builder(HTTP_TRANSPORT, JSON_FACTORY).setPublicCertsEncodedUrl(CUSTOM_CERTS_URL).build();
final GoogleIdTokenVerifier verifier = new GoogleIdTokenVerifier.Builder(manager).setAudience(Arrays.asList(CLIENT_ID)).build();
verifier.verify(idToken);

...and it works.

I hope, the Google fix the issue soon... :)

0
beetstra On

This is copied from my answer here, but more relevant for those not using Google Cloud Endpoint (matching this question). The problem is caused by this:

  • RSA has variable length signatures, depending on the key size.
  • Google updated the key pairs it uses for signing, and now one of the key pairs generates a different length signature from the other
  • java.security.Signature.verify(byte[] signature) throws an exception if a signature of the wrong length is passed (instead of returning false which is normally done when a signature does not match the key)

The simplest solution is to wrap the verify call (try...catch), and return false if you get an exception

Looking at the example code on http://android-developers.blogspot.com/2013/01/verifying-back-end-calls-from-android.html, it looks like you can change this line:

GoogleIdToken token = GoogleIdToken.parse(mJFactory, tokenString);

to

JsonWebSignature jws = JsonWebSignature.parser(mJFactory).setPayloadClass(Payload.class).parse(tokenString);
GoogleIdToken token = new GoogleIdToken(jws.getHeader(), (Payload) jws.getPayload(), jws.getSignatureBytes(), jws.getSignedContentBytes()) {
   public boolean verify(GoogleIdTokenVerifier verifier)
  throws GeneralSecurityException, IOException {
       try {
           return verifier.verify(this);
       } catch (java.security.SignatureException e) {
           return false;
       }
   }
};

I unfortunately don't have an exact setup to test this, let me know if this works for you.