Google OAuth2 JWT token verification exception
Asked Answered
P

6

21

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]
Pneumatophore answered 11/6, 2015 at 12:10 Comment(8)
Same problem here as well using Java 1.8.0_45.Dieback
I'm getting this on Google app engine when using access tokens from the google account manager account. (Just started getitng them an hour ago)Sayce
@Pneumatophore What's the Audience you set for the GoogleIdTokenVerifier? Do you use client id or token id? (We're been experiencing the same issue for the past 60 minutes)Bandler
I have the same problem, looks like a Google API issue. Do you know where to complain and find information about this?Sitarski
Same here... from ~11:30 UTC... :/Academe
Thanks everyone for the reports and workarounds. This is acknowledged as a Google issue and treated with highest priority by our backend team.Paxwax
apparently it is solved now since Google updated its keys to all use the same key lengthEpispastic
Apparently it isn't solved!Cardoso
B
7

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;
    }
}
Bonnard answered 11/6, 2015 at 13:4 Comment(0)
C
5

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();
Circumstantiality answered 11/6, 2015 at 13:53 Comment(2)
Thanks, Ittai! This workaround is working my code, too. Good stopgap for a serious production issue for my company.Crowther
glad to hear :-) I think, but not sure, that effectively this just turns off verification. It's possible that google has hardened it's certificate checks and that's what caused the sudden surge.Circumstantiality
K
2

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... :)

Klausenburg answered 11/6, 2015 at 15:3 Comment(0)
E
0

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.

Epispastic answered 11/6, 2015 at 16:10 Comment(0)
M
0

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
Meperidine answered 11/6, 2015 at 17:1 Comment(0)
S
0

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;
    }

};
Swirsky answered 11/6, 2015 at 17:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.