首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >可靠地验证JWS证书链和域

可靠地验证JWS证书链和域
EN

Stack Overflow用户
提问于 2020-02-27 13:45:11
回答 1查看 2.4K关注 0票数 3

我正在编写后端代码,以在SafetyNet API中验证Node.JS中的JWS。我很惊讶地没有找到一个现成的模块,所以我开始使用可用的库查看JWS的一些简单验证:

首先,谷歌表示,这些步骤是必需的:

  1. 从JWS消息中提取SSL证书链。
  2. 验证SSL证书链,并使用SSL匹配验证叶子证书是否已颁发给主机名attest.android.com。
  3. 使用证书验证JWS消息的签名。
  4. 检查JWS消息的数据,以确保它与原始请求中的数据匹配。特别是,确保时间戳已被验证,并且应用的签名证书的现在、包名和散列符合预期的值。

(来自https://developer.android.com/training/safetynet/attestation#verify-attestation-response)

我找到了节点-何塞,它提供了一个简单的接口来验证JWS,并且它有一个允许嵌入密钥的选项。我试图确切地了解这个过程的作用,以及它是否足以验证JWS的真实性?

代码语言:javascript
复制
const {JWS} = require('node-jose');
const result = await JWS.createVerify({allowEmbeddedKey: true}).verify(jws);

if (result.key.kid === 'attest.android.com') {
  // Are we good to go or do we manually need to verify the certificate chain further?
}

使用嵌入式密钥是否确实使用根CA验证嵌入式证书链x5c,并根据证书进行签名?还是需要从Google显式获取公钥以单独验证证书?

然后,一个与此相关的问题涉及Google用于执行此验证的API :有一个API https://www.googleapis.com/androidcheck/v1/attestations/verify?key=...执行此精确操作,但它似乎已从Google的文档中删除,并且只能在过时的文章中找到,因此只能在有关SafetyNet (如这一个 )的回答中找到,这似乎表明该API仅用于测试,而在生产过程中,您应该自己执行证书验证。有人知道这个API是否适合生产使用吗?如果每个人都想手动验证JWS,那么Google不会提供更多的文档和代码示例,因为这个过程很容易出错,错误可能会产生严重的影响,我觉得有点奇怪吗?到目前为止,我只在Java中找到了一些第三方示例,而没有从Google中找到服务器端代码示例。

EN

回答 1

Stack Overflow用户

发布于 2020-05-22 06:27:47

下面是您需要按照谷歌推荐的方式执行的台阶

绝对可以自由地浏览所有的参考链接,以更好地理解这个过程。请查看这里使用的每个库函数,以了解它们正在做什么,如果这正是您希望它们做的事情。我写了伪码来解释这些步骤。您可能必须在示例认证令牌上运行它们,以测试它们并相应地更改一些内容。

最好在一个地方查看SafetyNet的整个节点实现。

代码语言:javascript
复制
// following steps should be performed
// 1. decode the JWS
// 2. the source of the first certificate in x5c array of jws header 
//    should be attest.google.com
// 3. to make sure if the JWS was not tampered with, validate the signature of JWS (how signature verification is done is explained in the reference links)
//    with the certificate whose source we validated
// 4. if the signature was valid, we need to know if the certificate was valid by 
//    explicitly checking the certificate chain
// 5. Validate the payload by matching the package name, apkCertificateDigest
//    and nonce value (apkCertificateDigest is base64 encoding of the hash of signing app's certificate)
// 6. and now you can trust the ctsProfileMatch and BasicIntegrity flags
// let's see some code in node, though this will not run as-is, 
// it provides an outline on how to do it and which functions to consider when implementing

const pki = require('node-forge').pki;
const jws = require('jws');
const pem = require("pem");
const forge = require('node-forge');

const signedAttestation = "Your signed attestation here";

function deviceAttestationCheck(signedAttestation) {
  // 1. decode the jws
  const decodedJws = jws.decode(signedAttestation);
  const payload = JSON.parse(decodedJws.payload);

  // convert the certificate received in the x5c array into valid certificates by adding 
  // '-----BEGIN CERTIFICATE-----\n' and '-----END CERTIFICATE-----'
  // at the start and end respectively for each certificate in the array
  // and by adding '\n' at every 64 char
  // you'll have to write your own function to do the simple string reformatting
  // get the x5c certificate array
  const x5cArray = decodedJws.header.x5c;
  updatedX5cArray = doTheReformatting(x5cArray);

  // 2. verify the source to be attest.google.com
  certToVerify = updatedX5cArray[0];
  const details = pem.readCertificateInfo(certToVerify);
  // check if details.commanName === "attest.google.com"

  const certs = updatedX5cArray.map((cert) => pki.certificateFromPem(cert));

  // 3. Verify the signature with the certificate that we received
  // the first element of the certificate(certs array) is the one that was issued to us, so we should use that to verify the signature
  const isSignatureValid = jws.verify(signedAttestation, 'RS256', certs[0]);

  // 4. to be sure if the certificate we used to verify the signature is the valid one, we should validate the certificate chain
  const gsr2Reformatted = doTheReformatting(gsr2);
  const rootCert = pki.certificateFromPem(gsr2Reformatted);
  const caStore = pki.createCaStore([rootCert]);

  // NOTE: this pki implementation does not check for certificate revocation list, which is something that you'll need to do separately
  const isChainValid = pki.verifyCertificateChain(caStore, certs);

  // 5. now we can validate the payload
  // check the timestamps, to be within certain time say 1 hour
  // check nonce value, to contain the data that you expect, refer links below
  // check apkPackageName to be your app's package name
  // check apkCertificateDigestSha256 to be from your app - quick tip -look at the function below on how to generate this
  // finally you can trust the ctsProfileMatch - true/false depending on strict security need and basicIntegrity - true, minimum to check

}

// this function takes your signing certificate(should be of the form '----BEGIN CERT....data...---END CERT...') and converts into the SHA256 digest in hex, which looks like - 92:8H:N9:84:YT:94:8N.....
// we need to convert this hex digest to base64 
// 1. 92:8H:N9:84:YT:94:8N.....
// 2. 928hn984yt948n - remove the colon and toLowerCase
// 3. encode it in base64
function certificateToSha256DigestHex(certPem) {
  const cert = pki.certificateFromPem(certPem);
  const der = forge.asn1.toDer(pki.certificateToAsn1(cert)).getBytes();
  const m = forge.md.sha256.create();
  m.start();
  m.update(der);
  const fingerprint = m.digest()
      .toHex()
      .match(/.{2}/g)
      .join(':')
      .toUpperCase();

  return fingerprint
}

// 92:8H:N9:84:YT:94:8N => 928hn984yt948n
function stringToHex(sha256string) {
  return sha256string.split(":").join('').toLowerCase();
}

// this is what google sends you in apkCertificateDigestSha256 array
// 928hn984yt948n => "OIHf9wjfjkjf9fj0a="
function hexToBase64(hexString) {
  return Buffer.from(hexString, 'hex').toString('base64')
}

所有帮助我的文章:

  1. 步骤摘要- 这里
  2. 对实现的深入解释- 这里
  3. 你应该记住的事情-- 这里
  4. 来自google的检查表来正确地做它- 这里
  5. 深入研究这个过程- 这里
票数 5
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/60434676

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档