summaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/attestation/verifications/verifyPacked.ts225
-rw-r--r--src/attestation/verifyAttestationResponse.ts9
-rw-r--r--src/helpers/constants.ts50
3 files changed, 230 insertions, 54 deletions
diff --git a/src/attestation/verifications/verifyPacked.ts b/src/attestation/verifications/verifyPacked.ts
new file mode 100644
index 0000000..c5bc1b3
--- /dev/null
+++ b/src/attestation/verifications/verifyPacked.ts
@@ -0,0 +1,225 @@
+import base64url from 'base64url';
+import cbor from 'cbor';
+import elliptic from 'elliptic';
+import NodeRSA, { SigningSchemeHash } from 'node-rsa';
+
+import { AttestationObject, VerifiedAttestation } from "@types";
+import parseAttestationAuthData from "@helpers/parseAttestationAuthData";
+import convertCOSEECDHAtoPKCS from "@helpers/convertCOSEECDHAtoPKCS";
+import toHash from "@helpers/toHash";
+import convertASN1toPEM from '@helpers/convertASN1toPEM';
+import getCertificateInfo from '@helpers/getCertificateInfo';
+import verifySignature from '@helpers/verifySignature';
+
+
+export default function verifyAttestationPacked(attestationObject: AttestationObject,
+ base64ClientDataJSON: string,
+): VerifiedAttestation {
+ const { fmt, authData, attStmt } = attestationObject;
+ const { sig, x5c, ecdaaKeyId } = attStmt;
+
+ const authDataStruct = parseAttestationAuthData(authData);
+
+ const { COSEPublicKey, counter, credentialID } = authDataStruct;
+
+ if (!COSEPublicKey) {
+ throw new Error('No public key was provided by authenticator');
+ }
+
+ if (!credentialID) {
+ throw new Error('No credential ID was provided by authenticator');
+ }
+
+ if (!sig) {
+ throw new Error('No attestation signature provided in attestation statement');
+ }
+
+ const clientDataHash = toHash(base64url.toBuffer(base64ClientDataJSON));
+
+ const signatureBase = Buffer.concat([
+ authData,
+ clientDataHash,
+ ]);
+
+ const toReturn: VerifiedAttestation = { verified: false };
+ const publicKey = convertCOSEECDHAtoPKCS(COSEPublicKey);
+
+ if (x5c) {
+ console.log('FULL Attestation');
+
+ const leafCert = convertASN1toPEM(x5c[0]);
+ const leafCertInfo = getCertificateInfo(leafCert);
+
+ const { subject, basicConstraintsCA, version } = leafCertInfo;
+ const {
+ OU,
+ CN,
+ O,
+ C,
+ } = subject;
+
+ if (OU !== 'Authenticator Attestation') {
+ throw new Error('Batch certificate OU MUST be set strictly to "Authenticator Attestation"!');
+ }
+
+ if (!CN) {
+ throw new Error('Batch certificate CN MUST no be empty!');
+ }
+
+ if (!O) {
+ throw new Error('Batch certificate CN MUST no be empty!');
+ }
+
+ if (!C || C.length !== 2) {
+ throw new Error('Batch certificate C MUST be set to two character ISO 3166 code!');
+ }
+
+ if (basicConstraintsCA) {
+ throw new Error('Batch certificate basic constraints CA MUST be false!');
+ }
+
+ if (version !== 3) {
+ throw new Error('Batch certificate version MUST be 3(ASN1 2)!');
+ }
+
+ toReturn.verified = verifySignature(sig, signatureBase, leafCert);
+ } else if (ecdaaKeyId) {
+ throw new Error('ECDAA not supported yet');
+ } else {
+ console.log('SELF Attestation');
+
+ const publicKeyCOSE: Map<COSEAlgorithmIdentifier, number | Buffer> = cbor.decodeAllSync(COSEPublicKey)[0];
+
+ const kty = publicKeyCOSE.get(COSEKEYS.kty);
+ const alg = publicKeyCOSE.get(COSEKEYS.alg);
+ const x = publicKeyCOSE.get(COSEKEYS.x);
+ const y = publicKeyCOSE.get(COSEKEYS.y);
+
+ if (!alg) {
+ throw new Error('COSE public key was missing alg');
+ }
+
+ if (!kty) {
+ throw new Error('COSE public key was missing kty');
+ }
+
+ if (!x) {
+ throw new Error('COSE public key was missing x');
+ }
+
+ if (!y) {
+ throw new Error('COSE public key was missing y');
+ }
+
+ const hashAlg: string = COSEALGHASH[(alg as number)];
+
+ if (kty === COSEKTY.EC2) {
+ console.log('EC2');
+
+ const crv = publicKeyCOSE.get(COSEKEYS.crv);
+
+ if (!crv) {
+ throw new Error('COSE public key was missing kty crv');
+ }
+
+ const ansiKey = Buffer.concat([
+ Buffer.from([0x04]),
+ (x as Buffer),
+ (y as Buffer),
+ ]);
+
+ const signatureBaseHash = toHash(signatureBase, hashAlg);
+
+ const ec = new elliptic.ec(COSECRV[(crv as number)]);
+ const key = ec.keyFromPublic(ansiKey);
+
+ toReturn.verified = key.verify(signatureBaseHash, sig);
+ } else if (kty === COSEKTY.RSA) {
+ console.log('RSA');
+
+ const n = publicKeyCOSE.get(COSEKEYS.n);
+
+ if (!n) {
+ throw new Error('COSE public key was missing kty n');
+ }
+
+ const signingScheme = COSERSASCHEME[alg as number];
+
+ // TODO: Verify this works
+ const key = new NodeRSA();
+ key.setOptions({ signingScheme });
+ key.importKey({
+ n: (n as Buffer),
+ e: 65537,
+ }, 'components-public');
+
+ toReturn.verified = key.verify(signatureBase, sig);
+ } else if (kty === COSEKTY.OKP) {
+ console.log('OKP');
+
+ const signatureBaseHash = toHash(signatureBase, hashAlg);
+
+ const key = new elliptic.eddsa('ed25519');
+ key.keyFromPublic((x as Buffer));
+
+ // TODO: is `publicKey` right here?
+ toReturn.verified = key.verify(signatureBaseHash, sig, publicKey);
+ }
+ }
+
+ if (toReturn.verified) {
+ toReturn.authenticatorInfo = {
+ fmt,
+ counter,
+ base64PublicKey: base64url.encode(publicKey),
+ base64CredentialID: base64url.encode(credentialID),
+ };
+ }
+
+ return toReturn;
+}
+
+enum COSEKEYS {
+ kty = 1,
+ alg = 3,
+ crv = -1,
+ x = -2,
+ y = -3,
+ n = -1,
+ e = -2,
+}
+
+enum COSEKTY {
+ OKP = 1,
+ EC2 = 2,
+ RSA = 3,
+}
+
+const COSERSASCHEME: { [key: string]: SigningSchemeHash } = {
+ '-3': 'pss-sha256',
+ '-39': 'pss-sha512',
+ '-38': 'pss-sha384',
+ '-65535': 'pkcs1-sha1',
+ '-257': 'pkcs1-sha256',
+ '-258': 'pkcs1-sha384',
+ '-259': 'pkcs1-sha512'
+}
+
+const COSECRV: { [key: number]: string } = {
+ 1: 'p256',
+ 2: 'p384',
+ 3: 'p521',
+};
+
+const COSEALGHASH: { [key: string]: string } = {
+ '-257': 'sha256',
+ '-258': 'sha384',
+ '-259': 'sha512',
+ '-65535': 'sha1',
+ '-39': 'sha512',
+ '-38': 'sha384',
+ '-37': 'sha256',
+ '-7': 'sha256',
+ '-8': 'sha512',
+ '-36': 'sha512'
+}
diff --git a/src/attestation/verifyAttestationResponse.ts b/src/attestation/verifyAttestationResponse.ts
index 3eba00f..8605dfb 100644
--- a/src/attestation/verifyAttestationResponse.ts
+++ b/src/attestation/verifyAttestationResponse.ts
@@ -3,6 +3,7 @@ import decodeClientDataJSON from '@helpers/decodeClientDataJSON';
import { ATTESTATION_FORMATS, EncodedAuthenticatorAttestationResponse, VerifiedAttestation } from '@types';
import verifyFIDOU2F from './verifications/verifyFIDOU2F';
+import verifyPacked from './verifications/verifyPacked';
/**
* Verify that the user has legitimately completed the registration process
@@ -47,10 +48,10 @@ export default function verifyAttestationResponse(
return verifyFIDOU2F(attestationObject, base64ClientDataJSON);
}
- // if (fmt === ATTESTATION_FORMATS.PACKED) {
- // console.log('Decoding Packed attestation');
- // return WebauthnService.verifyAttestationPacked(decodedAttestation, clientDataJSON);
- // }
+ if (fmt === ATTESTATION_FORMATS.PACKED) {
+ console.log('Decoding Packed attestation');
+ return verifyPacked(attestationObject, base64ClientDataJSON);
+ }
// if (fmt === ATTESTATION_FORMATS.ANDROID_SAFETYNET) {
// console.log('Decoding Android Safetynet attestation');
diff --git a/src/helpers/constants.ts b/src/helpers/constants.ts
index 5168671..ab2efa9 100644
--- a/src/helpers/constants.ts
+++ b/src/helpers/constants.ts
@@ -1,54 +1,4 @@
/**
- * U2F Presence constant
- */
-export const U2F_USER_PRESENTED = 0x01;
-
-export const COSEKEYS = {
- 'kty' : 1,
- 'alg' : 3,
- 'crv' : -1,
- 'x' : -2,
- 'y' : -3,
- 'n' : -1,
- 'e' : -2
-}
-
-export const COSEKTY = {
- 'OKP': 1,
- 'EC2': 2,
- 'RSA': 3
-}
-
-export const COSERSASCHEME = {
- '-3': 'pss-sha256',
- '-39': 'pss-sha512',
- '-38': 'pss-sha384',
- '-65535': 'pkcs1-sha1',
- '-257': 'pkcs1-sha256',
- '-258': 'pkcs1-sha384',
- '-259': 'pkcs1-sha512'
-}
-
-export const COSEALGHASH = {
- '-257': 'sha256',
- '-258': 'sha384',
- '-259': 'sha512',
- '-65535': 'sha1',
- '-39': 'sha512',
- '-38': 'sha384',
- '-37': 'sha256',
- '-7': 'sha256',
- '-8': 'sha512',
- '-36': 'sha512'
-}
-
-export const COSECRV = {
- 1: 'p256',
- 2: 'p384',
- 3: 'p521',
-};
-
-/**
* This "GS Root R2" root certificate was downloaded from https://pki.goog/gsr2/GSR2.crt
* on 08/10/2019 and then run through `base64url.encode()` to get this representation.
*