summaryrefslogtreecommitdiffhomepage
path: root/packages/server/src
diff options
context:
space:
mode:
Diffstat (limited to 'packages/server/src')
-rw-r--r--packages/server/src/attestation/verifications/verifyApple.ts97
-rw-r--r--packages/server/src/attestation/verifyAttestationResponse.ts7
2 files changed, 101 insertions, 3 deletions
diff --git a/packages/server/src/attestation/verifications/verifyApple.ts b/packages/server/src/attestation/verifications/verifyApple.ts
index 3d8f4c1..905ad96 100644
--- a/packages/server/src/attestation/verifications/verifyApple.ts
+++ b/packages/server/src/attestation/verifications/verifyApple.ts
@@ -1,3 +1,96 @@
-export default async function verifyApple(): Promise<boolean> {
- return false;
+import { AsnParser } from '@peculiar/asn1-schema';
+import { Certificate } from '@peculiar/asn1-x509';
+
+import type { AttestationStatement } from '../../helpers/decodeAttestationObject';
+import validateCertificatePath from '../../helpers/validateCertificatePath';
+import convertX509CertToPEM from '../../helpers/convertX509CertToPEM';
+import toHash from '../../helpers/toHash';
+import convertCOSEtoPKCS from '../../helpers/convertCOSEtoPKCS';
+
+type Options = {
+ attStmt: AttestationStatement;
+ authData: Buffer;
+ clientDataHash: Buffer;
+ credentialPublicKey: Buffer;
+};
+
+export default async function verifyApple(options: Options): Promise<boolean> {
+ const { attStmt, authData, clientDataHash, credentialPublicKey } = options;
+ const { x5c } = attStmt;
+
+ if (!x5c) {
+ throw new Error('No attestation certificate provided in attestation statement (Apple)');
+ }
+
+ /**
+ * Verify certificate path
+ */
+ const certPath = x5c.map(convertX509CertToPEM);
+ certPath.push(AppleWebAuthnRootCertificate);
+
+ try {
+ await validateCertificatePath(certPath);
+ } catch (err) {
+ throw new Error(`${err.message} (Apple)`);
+ }
+
+ /**
+ * Compare nonce in certificate extension to computed nonce
+ */
+ const parsedCredCert = AsnParser.parse(x5c[0], Certificate);
+ const { extensions, subjectPublicKeyInfo } = parsedCredCert.tbsCertificate;
+
+ if (!extensions) {
+ throw new Error('credCert missing extensions (Apple)');
+ }
+
+ const extCertNonce = extensions.find(ext => ext.extnID === '1.2.840.113635.100.8.2');
+
+ if (!extCertNonce) {
+ throw new Error('credCert missing "1.2.840.113635.100.8.2" extension (Apple)');
+ }
+
+ const nonceToHash = Buffer.concat([authData, clientDataHash]);
+ const nonce = toHash(nonceToHash, 'SHA256');
+ const extNonce = Buffer.from(extCertNonce.extnValue);
+
+ if (!nonce.equals(extNonce)) {
+ console.log('nonce:', nonce.toString('hex'));
+ console.log('extNonce:', extNonce.toString('hex'));
+ throw new Error(`credCert nonce was not expected value (Apple)`);
+ }
+
+ /**
+ * Verify credential public key matches the Subject Public Key of credCert
+ */
+ const credPubKeyPKCS = convertCOSEtoPKCS(credentialPublicKey);
+ const credCertSubjectPublicKey = Buffer.from(subjectPublicKeyInfo.subjectPublicKey);
+
+ if (!credPubKeyPKCS.equals(credCertSubjectPublicKey)) {
+ throw new Error('Credential public key does not equal credCert public key (Apple)');
+ }
+
+ return true;
}
+
+/**
+ * Apple WebAuthn Root CA PEM
+ *
+ * Downloaded from https://www.apple.com/certificateauthority/Apple_WebAuthn_Root_CA.pem
+ *
+ * Valid until 03/14/2045 @ 5:00 PM PST
+ */
+const AppleWebAuthnRootCertificate = `-----BEGIN CERTIFICATE-----
+MIICEjCCAZmgAwIBAgIQaB0BbHo84wIlpQGUKEdXcTAKBggqhkjOPQQDAzBLMR8w
+HQYDVQQDDBZBcHBsZSBXZWJBdXRobiBSb290IENBMRMwEQYDVQQKDApBcHBsZSBJ
+bmMuMRMwEQYDVQQIDApDYWxpZm9ybmlhMB4XDTIwMDMxODE4MjEzMloXDTQ1MDMx
+NTAwMDAwMFowSzEfMB0GA1UEAwwWQXBwbGUgV2ViQXV0aG4gUm9vdCBDQTETMBEG
+A1UECgwKQXBwbGUgSW5jLjETMBEGA1UECAwKQ2FsaWZvcm5pYTB2MBAGByqGSM49
+AgEGBSuBBAAiA2IABCJCQ2pTVhzjl4Wo6IhHtMSAzO2cv+H9DQKev3//fG59G11k
+xu9eI0/7o6V5uShBpe1u6l6mS19S1FEh6yGljnZAJ+2GNP1mi/YK2kSXIuTHjxA/
+pcoRf7XkOtO4o1qlcaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUJtdk
+2cV4wlpn0afeaxLQG2PxxtcwDgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMDA2cA
+MGQCMFrZ+9DsJ1PW9hfNdBywZDsWDbWFp28it1d/5w2RPkRX3Bbn/UbDTNLx7Jr3
+jAGGiQIwHFj+dJZYUJR786osByBelJYsVZd2GbHQu209b5RCmGQ21gpSAk9QZW4B
+1bWeT0vT
+-----END CERTIFICATE-----`;
diff --git a/packages/server/src/attestation/verifyAttestationResponse.ts b/packages/server/src/attestation/verifyAttestationResponse.ts
index 937f75c..8acd028 100644
--- a/packages/server/src/attestation/verifyAttestationResponse.ts
+++ b/packages/server/src/attestation/verifyAttestationResponse.ts
@@ -195,7 +195,12 @@ export default async function verifyAttestationResponse(
clientDataHash,
});
} else if (fmt === ATTESTATION_FORMATS.APPLE) {
- verified = await verifyApple();
+ verified = await verifyApple({
+ attStmt,
+ authData,
+ clientDataHash,
+ credentialPublicKey,
+ });
} else if (fmt === ATTESTATION_FORMATS.NONE) {
if (Object.keys(attStmt).length > 0) {
throw new Error('None attestation had unexpected attestation statement');