Подтвердить что ты не робот

Конечные точки Google Cloud: verifyToken: длина подписи неверна

Сегодня утром в каждом запросе API моей конечной точки Google из моего приложения для Android появилось следующее исключение:

com.google.api.server.spi.auth.GoogleIdTokenUtils verifyToken: verifyToken: длина подписи неверна: получил 256, но ожидал 128

Вызов по-прежнему отлично работает с моими веб-клиентами javascript. Я ничего не изменил для кода на стороне сервера или кода клиента.

Что-нибудь изменилось с помощью службы в последнее время, которая может произойти с этим?

UPDATE: Первое появление этого, похоже, было в 11:17:07 UTC

ОБНОВЛЕНИЕ: Вещи, которые не работают, включают создание нового идентификатора клиента для Android и обновление до App Engine SDK 1.9.22

4b9b3361

Ответ 1

Причины

  • RSA имеет подписи переменной длины, в зависимости от размера ключа.
  • Google обновил пары ключей, которые он использует для подписи, и теперь одна из пар ключей генерирует другую подпись длины из другого
  • java.security.Signature.verify(byte[] signature) выдает исключение, если сигнатура неправильной длины передается (вместо того, чтобы возвращать false, что обычно делается, когда подпись не соответствует ключу)

Для меня решение заключалось в том, чтобы завернуть вызов проверки (try...catch) и вместо этого вернуть false. Вы также можете выполнить раннюю проверку открытого ключа самостоятельно, проверяя, соответствует ли длина подписи длине модуля открытого ключа.

Если вы используете библиотеку для проверки подписи, убедитесь, что используете последнюю версию.

Если вы посмотрите на код примера http://android-developers.blogspot.nl/2013/01/verifying-back-end-calls-from-android.html, вам придется изменить это:

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

к

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

У меня, к сожалению, нет точной настройки, чтобы проверить это.

Для тех, кто использует Google Cloud Endpoint, как и в случае с вопросом, я думаю, что было очень мало, что вы могли бы сделать, кроме как подождать, пока Google не исправит это. К счастью, это исправлено. (Технически, вы могли бы утверждать, что изменение ключей, как это делается сейчас, является обходным путем, и библиотека Google требует исправления, но она работает, так что хороший старт)

Ответ 2

Такая же проблема, насколько я могу рассказать публичный URL-адрес сертификата (теперь? Я предполагаю, что это было не так раньше или изменился порядок) возвращает два ключа:

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

проверяя их, первый имеет 1024-битный ключ, а второй - ключ 2048 бит. Я считаю, что мои входящие токены от клиентов android были подписаны вторым сертификатом с ключом 2048 бит, поэтому "Длина подписи не верна: получил 256, но ожидал 128".

Глядя на источник верификатора Google (GoogleTokenVerifier.java), он, похоже, выполняет итерацию нескольких ключей:

// verify signature
for (PublicKey publicKey : publicKeys.getPublicKeys()) {
  if (googleIdToken.verifySignature(publicKey)) {
    return true;
  }
}

предполагая, что ключи правильно разобраны (этот код выглядит разумно, но на самом деле не проверял результаты).

Как указала Beestra, этот код ожидает, что false будет возвращен, если он не сможет быть проверен, но вместо этого он выбрасывает исключение. В идеале он должен продолжать выполнять повтор после сбоя и использовать второй открытый ключ для проверки, который должен работать.

Чтобы исправить это, есть два варианта:

  • Закройте библиотеку google api и исправьте
  • Дублировать проверку (GoogleIdTokenVerifier.verify(GoogleIdToken)) в (ваш) код вызова

Я не знаю, насколько реалистичен 2., какая-то суперфункция используется, и много внутреннего состояния является частным, придется дублировать все это. Занятое расследование...

UPDATE: Хорошо, похоже, исправлено в моих тестах с использованием производственных данных, хотя пока еще не развернуло его для производства. Здесь Scala

  val jsonFactory = new JacksonFactory()
  val transport = new NetHttpTransport()
  val googleIdTokenVerifier = new GoogleIdTokenVerifier(transport, jsonFactory)

  class DuplicateVerifier(builder: GoogleIdTokenVerifier.Builder) extends IdTokenVerifier(builder)
  val topIdTokenVerifier = new DuplicateVerifier(new GoogleIdTokenVerifier.Builder(transport, jsonFactory))
  val publicKeysManager = new GooglePublicKeysManager(transport, jsonFactory)
  def duplicateGoogleVerify(token: GoogleIdToken): Boolean = {
    // check the payload
    if (!topIdTokenVerifier.verify(token)) {
      false
    } else {
      // verify signature
      import scala.collection.JavaConverters._
      publicKeysManager.getPublicKeys.asScala.map { k =>
        Try(token.verifySignature(k))
      }.foldLeft(false)((c, x) => c || x.getOrElse(false))
    }
  }

Просто, чтобы быть ясным, если это не очевидно, используя этот метод вместо Google:

// if (googleIdTokenVerifier.verify(token)) {
if (duplicateGoogleVerify(token)) {

Я попытаюсь написать эквивалент Java позже, если кому-то это понадобится.