summaryrefslogtreecommitdiffhomepage
path: root/packages/server/src
diff options
context:
space:
mode:
authorMatthew Miller <matthew@millerti.me>2020-07-04 16:32:04 -0700
committerMatthew Miller <matthew@millerti.me>2020-07-04 16:32:04 -0700
commit30dbc032d2de43b7815fee6f9b4d5446f927be51 (patch)
tree5f882ba985af94791865686884d083934e213d6b /packages/server/src
parentacf6f99834f04e1efd7f972af051de6a004e2d63 (diff)
Add metadata cert path validation to SafetyNet
Diffstat (limited to 'packages/server/src')
-rw-r--r--packages/server/src/attestation/verifications/verifyAndroidSafetyNet.ts51
1 files changed, 38 insertions, 13 deletions
diff --git a/packages/server/src/attestation/verifications/verifyAndroidSafetyNet.ts b/packages/server/src/attestation/verifications/verifyAndroidSafetyNet.ts
index 9e20a92..a48a2ae 100644
--- a/packages/server/src/attestation/verifications/verifyAndroidSafetyNet.ts
+++ b/packages/server/src/attestation/verifications/verifyAndroidSafetyNet.ts
@@ -7,6 +7,7 @@ import verifySignature from '../../helpers/verifySignature';
import getCertificateInfo from '../../helpers/getCertificateInfo';
import validateCertificatePath from '../../helpers/validateCertificatePath';
import convertASN1toPEM from '../../helpers/convertASN1toPEM';
+import MetadataService from '../../metadata/metadataService';
type Options = {
attStmt: AttestationStatement;
@@ -79,14 +80,10 @@ export default async function verifyAttestationAndroidSafetyNet(
/**
* START Verify Header
*/
- // Generate an array of certs constituting a full certificate chain
- const fullpathCert = HEADER.x5c.concat([GlobalSignRootCAR2]).map(convertASN1toPEM);
+ const leafCert = convertASN1toPEM(HEADER.x5c[0]);
+ const leafCertInfo = getCertificateInfo(leafCert);
- const certificate = fullpathCert[0];
-
- const commonCertInfo = getCertificateInfo(certificate);
-
- const { subject } = commonCertInfo;
+ const { subject } = leafCertInfo;
// Ensure the certificate was issued to this hostname
// See https://developer.android.com/training/safetynet/attestation#verify-attestation-response
@@ -94,11 +91,39 @@ export default async function verifyAttestationAndroidSafetyNet(
throw new Error('Certificate common name was not "attest.android.com" (SafetyNet)');
}
- // Validate certificate path
- try {
- validateCertificatePath(fullpathCert);
- } catch (err) {
- throw new Error(`${err} (SafetyNet)`);
+ const statement = await MetadataService.getStatement(aaguid);
+ if (statement) {
+ // Try to validate the chain with each metadata root cert until we find one that works
+ let validated = false;
+ for (const rootCert of statement.attestationRootCertificates) {
+ try {
+ const path = [...HEADER.x5c, rootCert].map(convertASN1toPEM);
+ validated = validateCertificatePath(path);
+ } catch (err) {
+ // Swallow the error for now
+ validated = false;
+ }
+
+ // Don't continue if we've validated a full path
+ if (validated) {
+ break;
+ }
+ }
+
+ if (!validated) {
+ throw new Error(
+ `Could not validate certificate path with any metadata root certificates (SafetyNet)`,
+ );
+ }
+ } else {
+ // Validate certificate path using a fixed global root cert
+ const path = HEADER.x5c.concat([GlobalSignRootCAR2]).map(convertASN1toPEM);
+
+ try {
+ validateCertificatePath(path);
+ } catch (err) {
+ throw new Error(`${err} (SafetyNet)`);
+ }
}
/**
* END Verify Header
@@ -110,7 +135,7 @@ export default async function verifyAttestationAndroidSafetyNet(
const signatureBaseBuffer = Buffer.from(`${jwtParts[0]}.${jwtParts[1]}`);
const signatureBuffer = base64url.toBuffer(SIGNATURE);
- const verified = verifySignature(signatureBuffer, signatureBaseBuffer, certificate);
+ const verified = verifySignature(signatureBuffer, signatureBaseBuffer, leafCert);
/**
* END Verify Signature
*/