Google Cloud Endpoints:verifyToken:签名长度不正确

Google Cloud Endpoints: verifyToken: Signature length not correct

今天早上,从我的 Android 应用向我的 Google 云端点发出的每个 API 请求开始出现以下异常:

com.google.api.server.spi.auth.GoogleIdTokenUtils verifyToken: verifyToken: Signature length not correct: got 256 but was expecting 128

我的 javascript 网络客户端的调用仍然完美。我没有对服务器端代码或客户端代码进行任何更改。

服务最近有没有发生任何可能导致这种情况发生的变化?

更新:第一次出现这种情况似乎是在 11:17:07 UTC

更新:不起作用的事情包括为 android 生成新的客户端 ID 以及更新到 App Engine SDK 1.9.22

原因

  • RSA 具有可变长度的签名,具体取决于密钥大小。
  • Google 更新了它用于签名的密钥对,现在其中一个密钥对生成与另一个密钥对不同长度的签名
  • 如果传递了错误长度的签名,
  • java.security.Signature.verify(byte[] signature) 将抛出异常(而不是 returning false,通常在签名与密钥不匹配时会这样做)

对我来说,解决方案是包装验证调用 (try...catch),而 return false。 您也可以自己对 public 密钥进行早期检查,检查签名的长度是否与 public 密钥模数的长度相匹配。

如果您使用库来检查签名,请确保您使用的是最新版本。

查看 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 提供的库需要修复。但它有效,所以这是一个好的开始)

这里有同样的问题,据我所知 public 证书 URL(现在?我想以前不是这种情况,或者顺序改变了)returns 两个键:

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

检查那些,第一个有一个 1024 位密钥,第二个有一个 2048 位密钥。我相信来自 android 客户端的传入令牌是由第二个证书使用 2048 位密钥签名的,因此是 "Signature length not correct: got 256 but was expecting 128"。

查看 Google 验证程序源 (GoogleTokenVerifier.java) 它似乎迭代了多个键:

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

假设键被正确解析(该代码看起来合理但实际上没有检查结果)。

正如 beestra 所指出的,此代码期望返回 false 以防无法验证,而是抛出异常。理想情况下,它应该在失败后继续迭代并使用第二个 public 密钥进行验证,这应该有效。

要解决此问题,似乎有两个选择:

  1. 分叉 google api 客户端库并修复
  2. 在(您的)调用代码中复制验证 (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 等价物。