我正在编写后端代码,以在SafetyNet API中验证Node.JS中的JWS。我很惊讶地没有找到一个现成的模块,所以我开始使用可用的库查看JWS的一些简单验证:
首先,谷歌表示,这些步骤是必需的:
(来自https://developer.android.com/training/safetynet/attestation#verify-attestation-response)
我找到了节点-何塞,它提供了一个简单的接口来验证JWS,并且它有一个允许嵌入密钥的选项。我试图确切地了解这个过程的作用,以及它是否足以验证JWS的真实性?
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中找到服务器端代码示例。
发布于 2020-05-22 06:27:47
下面是您需要按照谷歌推荐的方式执行的台阶。
绝对可以自由地浏览所有的参考链接,以更好地理解这个过程。请查看这里使用的每个库函数,以了解它们正在做什么,如果这正是您希望它们做的事情。我写了伪码来解释这些步骤。您可能必须在示例认证令牌上运行它们,以测试它们并相应地更改一些内容。
最好在一个地方查看SafetyNet的整个节点实现。
// 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')
}所有帮助我的文章:
https://stackoverflow.com/questions/60434676
复制相似问题