summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--packages/server/src/attestation/verifications/verifyAndroidSafetyNet.ts46
-rw-r--r--packages/server/src/attestation/verifications/verifyFIDOU2F.ts49
-rw-r--r--packages/server/src/attestation/verifications/verifyNone.ts43
-rw-r--r--packages/server/src/attestation/verifications/verifyPacked.ts63
-rw-r--r--packages/server/src/attestation/verifyAttestationResponse.ts85
5 files changed, 94 insertions, 192 deletions
diff --git a/packages/server/src/attestation/verifications/verifyAndroidSafetyNet.ts b/packages/server/src/attestation/verifications/verifyAndroidSafetyNet.ts
index 31aa53d..9e0c080 100644
--- a/packages/server/src/attestation/verifications/verifyAndroidSafetyNet.ts
+++ b/packages/server/src/attestation/verifications/verifyAndroidSafetyNet.ts
@@ -1,29 +1,22 @@
import base64url from 'base64url';
-import type { AttestationObject } from '../../helpers/decodeAttestationObject';
-import type { ParsedAuthenticatorData } from '../../helpers/parseAuthenticatorData';
-import type { VerifiedAttestation } from '../verifyAttestationResponse';
+import type { AttestationStatement } from '../../helpers/decodeAttestationObject';
import toHash from '../../helpers/toHash';
import verifySignature from '../../helpers/verifySignature';
-import convertCOSEtoPKCS from '../../helpers/convertCOSEtoPKCS';
import getCertificateInfo from '../../helpers/getCertificateInfo';
+type Options = {
+ attStmt: AttestationStatement;
+ clientDataHash: Buffer;
+ authData: Buffer;
+};
+
/**
* Verify an attestation response with fmt 'android-safetynet'
*/
-export default function verifyAttestationAndroidSafetyNet(
- attestationObject: AttestationObject,
- base64ClientDataJSON: string,
- parsedAuthData: ParsedAuthenticatorData,
- credentialPublicKey: Buffer,
-): VerifiedAttestation {
- const { attStmt, authData, fmt } = attestationObject;
- const { counter, credentialID, flags } = parsedAuthData;
-
- if (!credentialID) {
- throw new Error('No credential ID was provided by authenticator (SafetyNet)');
- }
+export default function verifyAttestationAndroidSafetyNet(options: Options): boolean {
+ const { attStmt, clientDataHash, authData } = options;
if (!attStmt.response) {
throw new Error('No response was included in attStmt by authenticator (SafetyNet)');
@@ -41,7 +34,6 @@ export default function verifyAttestationAndroidSafetyNet(
* START Verify PAYLOAD
*/
const { nonce, ctsProfileMatch } = PAYLOAD;
- const clientDataHash = toHash(base64url.toBuffer(base64ClientDataJSON));
const nonceBase = Buffer.concat([authData, clientDataHash]);
const nonceBuffer = toHash(nonceBase);
@@ -95,28 +87,12 @@ export default function verifyAttestationAndroidSafetyNet(
const signatureBaseBuffer = Buffer.from(`${jwtParts[0]}.${jwtParts[1]}`);
const signatureBuffer = base64url.toBuffer(SIGNATURE);
- const toReturn: VerifiedAttestation = {
- verified: verifySignature(signatureBuffer, signatureBaseBuffer, certificate),
- userVerified: false,
- };
+ const verified = verifySignature(signatureBuffer, signatureBaseBuffer, certificate);
/**
* END Verify Signature
*/
- if (toReturn.verified) {
- toReturn.userVerified = flags.uv;
-
- const publicKey = convertCOSEtoPKCS(credentialPublicKey);
-
- toReturn.authenticatorInfo = {
- fmt,
- counter,
- base64PublicKey: base64url.encode(publicKey),
- base64CredentialID: base64url.encode(credentialID),
- };
- }
-
- return toReturn;
+ return verified;
}
/**
diff --git a/packages/server/src/attestation/verifications/verifyFIDOU2F.ts b/packages/server/src/attestation/verifications/verifyFIDOU2F.ts
index 508f167..0fd2e74 100644
--- a/packages/server/src/attestation/verifications/verifyFIDOU2F.ts
+++ b/packages/server/src/attestation/verifications/verifyFIDOU2F.ts
@@ -1,34 +1,23 @@
-import base64url from 'base64url';
+import type { AttestationStatement } from '../../helpers/decodeAttestationObject';
-import type { AttestationObject } from '../../helpers/decodeAttestationObject';
-import type { ParsedAuthenticatorData } from '../../helpers/parseAuthenticatorData';
-import type { VerifiedAttestation } from '../verifyAttestationResponse';
-
-import toHash from '../../helpers/toHash';
import convertCOSEtoPKCS from '../../helpers/convertCOSEtoPKCS';
import convertASN1toPEM from '../../helpers/convertASN1toPEM';
import verifySignature from '../../helpers/verifySignature';
+type Options = {
+ attStmt: AttestationStatement;
+ clientDataHash: Buffer;
+ rpIdHash: Buffer;
+ credentialID: Buffer;
+ credentialPublicKey: Buffer;
+};
+
/**
* Verify an attestation response with fmt 'fido-u2f'
*/
-export default function verifyAttestationFIDOU2F(
- attestationObject: AttestationObject,
- base64ClientDataJSON: string,
- parsedAuthData: ParsedAuthenticatorData,
-): VerifiedAttestation {
- const { fmt, attStmt } = attestationObject;
- const { flags, credentialPublicKey, rpIdHash, credentialID, counter } = parsedAuthData;
+export default function verifyAttestationFIDOU2F(options: Options): boolean {
+ const { attStmt, clientDataHash, rpIdHash, credentialID, credentialPublicKey } = options;
- if (!credentialPublicKey) {
- throw new Error('No public key was provided by authenticator (FIDOU2F)');
- }
-
- if (!credentialID) {
- throw new Error('No credential ID was provided by authenticator (FIDOU2F)');
- }
-
- const clientDataHash = toHash(base64url.toBuffer(base64ClientDataJSON));
const reservedByte = Buffer.from([0x00]);
const publicKey = convertCOSEtoPKCS(credentialPublicKey);
@@ -52,19 +41,5 @@ export default function verifyAttestationFIDOU2F(
const publicKeyCertPEM = convertASN1toPEM(x5c[0]);
- const toReturn: VerifiedAttestation = {
- verified: verifySignature(sig, signatureBase, publicKeyCertPEM),
- userVerified: flags.uv,
- };
-
- if (toReturn.verified) {
- toReturn.authenticatorInfo = {
- fmt,
- counter,
- base64PublicKey: base64url.encode(publicKey),
- base64CredentialID: base64url.encode(credentialID),
- };
- }
-
- return toReturn;
+ return verifySignature(sig, signatureBase, publicKeyCertPEM);
}
diff --git a/packages/server/src/attestation/verifications/verifyNone.ts b/packages/server/src/attestation/verifications/verifyNone.ts
deleted file mode 100644
index f276a83..0000000
--- a/packages/server/src/attestation/verifications/verifyNone.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-import base64url from 'base64url';
-
-import type { AttestationObject } from '../../helpers/decodeAttestationObject';
-import type { ParsedAuthenticatorData } from '../../helpers/parseAuthenticatorData';
-import type { VerifiedAttestation } from '../verifyAttestationResponse';
-
-import convertCOSEtoPKCS from '../../helpers/convertCOSEtoPKCS';
-
-/**
- * Verify an attestation response with fmt 'none'
- *
- * This is the weaker of the attestations, so there are only so many checks we can perform
- */
-export default function verifyAttestationNone(
- attestationObject: AttestationObject,
- parsedAuthData: ParsedAuthenticatorData,
-): VerifiedAttestation {
- const { fmt } = attestationObject;
- const { credentialID, credentialPublicKey, counter, flags } = parsedAuthData;
-
- if (!credentialPublicKey) {
- throw new Error('No public key was provided by authenticator (None)');
- }
-
- if (!credentialID) {
- throw new Error('No credential ID was provided by authenticator (None)');
- }
-
- const publicKey = convertCOSEtoPKCS(credentialPublicKey);
-
- const toReturn: VerifiedAttestation = {
- verified: true,
- userVerified: flags.uv,
- authenticatorInfo: {
- fmt,
- counter,
- base64PublicKey: base64url.encode(publicKey),
- base64CredentialID: base64url.encode(credentialID),
- },
- };
-
- return toReturn;
-}
diff --git a/packages/server/src/attestation/verifications/verifyPacked.ts b/packages/server/src/attestation/verifications/verifyPacked.ts
index c5f8ec1..45bd57e 100644
--- a/packages/server/src/attestation/verifications/verifyPacked.ts
+++ b/packages/server/src/attestation/verifications/verifyPacked.ts
@@ -1,53 +1,38 @@
-import base64url from 'base64url';
import elliptic from 'elliptic';
import NodeRSA, { SigningSchemeHash } from 'node-rsa';
-import type { AttestationObject } from '../../helpers/decodeAttestationObject';
-import type { ParsedAuthenticatorData } from '../../helpers/parseAuthenticatorData';
-import type { VerifiedAttestation } from '../verifyAttestationResponse';
+import type { AttestationStatement } from '../../helpers/decodeAttestationObject';
-import convertCOSEtoPKCS, {
- COSEKEYS,
-} from '../../helpers/convertCOSEtoPKCS';
+import convertCOSEtoPKCS, { COSEKEYS } from '../../helpers/convertCOSEtoPKCS';
import toHash from '../../helpers/toHash';
import convertASN1toPEM from '../../helpers/convertASN1toPEM';
import getCertificateInfo from '../../helpers/getCertificateInfo';
import verifySignature from '../../helpers/verifySignature';
import decodeCredentialPublicKey from '../../helpers/decodeCredentialPublicKey';
+type Options = {
+ attStmt: AttestationStatement;
+ clientDataHash: Buffer;
+ authData: Buffer;
+ credentialPublicKey: Buffer;
+};
+
/**
* Verify an attestation response with fmt 'packed'
*/
-export default function verifyAttestationPacked(
- attestationObject: AttestationObject,
- base64ClientDataJSON: string,
- parsedAuthData: ParsedAuthenticatorData,
-): VerifiedAttestation {
- const { fmt, authData, attStmt } = attestationObject;
- const { sig, x5c } = attStmt;
- const { credentialPublicKey, counter, credentialID, flags } = parsedAuthData;
-
- if (!credentialPublicKey) {
- throw new Error('No public key was provided by authenticator (Packed)');
- }
+export default function verifyAttestationPacked(options: Options): boolean {
+ const { attStmt, clientDataHash, authData, credentialPublicKey } = options;
- if (!credentialID) {
- throw new Error('No credential ID was provided by authenticator (Packed)');
- }
+ const { sig, x5c } = attStmt;
if (!sig) {
throw new Error('No attestation signature provided in attestation statement (Packed)');
}
- const clientDataHash = toHash(base64url.toBuffer(base64ClientDataJSON));
-
const signatureBase = Buffer.concat([authData, clientDataHash]);
- const toReturn: VerifiedAttestation = {
- verified: false,
- userVerified: flags.uv,
- };
- const publicKey = convertCOSEtoPKCS(credentialPublicKey);
+ let verified = false;
+ const pkcsPublicKey = convertCOSEtoPKCS(credentialPublicKey);
if (x5c) {
const leafCert = convertASN1toPEM(x5c[0]);
@@ -80,7 +65,7 @@ export default function verifyAttestationPacked(
throw new Error('Batch certificate version was not `3` (ASN.1 value of 2) (Packed|Full');
}
- toReturn.verified = verifySignature(sig, signatureBase, leafCert);
+ verified = verifySignature(sig, signatureBase, leafCert);
} else {
const cosePublicKey = decodeCredentialPublicKey(credentialPublicKey);
@@ -104,7 +89,6 @@ export default function verifyAttestationPacked(
throw new Error('COSE public key was missing kty crv (Packed|EC2)');
}
- const pkcsPublicKey = convertCOSEtoPKCS(credentialPublicKey);
const signatureBaseHash = toHash(signatureBase, hashAlg);
/**
@@ -119,7 +103,7 @@ export default function verifyAttestationPacked(
const ec = new elliptic.ec(COSECRV[crv as number]);
const key = ec.keyFromPublic(pkcsPublicKey);
- toReturn.verified = key.verify(signatureBaseHash, sig);
+ verified = key.verify(signatureBaseHash, sig);
} else if (kty === COSEKTY.RSA) {
const n = cosePublicKey.get(COSEKEYS.n);
@@ -140,7 +124,7 @@ export default function verifyAttestationPacked(
'components-public',
);
- toReturn.verified = key.verify(signatureBase, sig);
+ verified = key.verify(signatureBase, sig);
} else if (kty === COSEKTY.OKP) {
const x = cosePublicKey.get(COSEKEYS.x);
@@ -154,20 +138,11 @@ export default function verifyAttestationPacked(
key.keyFromPublic(x as Buffer);
// TODO: is `publicKey` right here?
- toReturn.verified = key.verify(signatureBaseHash, sig, publicKey);
+ verified = key.verify(signatureBaseHash, sig, pkcsPublicKey);
}
}
- if (toReturn.verified) {
- toReturn.authenticatorInfo = {
- fmt,
- counter,
- base64PublicKey: base64url.encode(publicKey),
- base64CredentialID: base64url.encode(credentialID),
- };
- }
-
- return toReturn;
+ return verified;
}
enum COSEKTY {
diff --git a/packages/server/src/attestation/verifyAttestationResponse.ts b/packages/server/src/attestation/verifyAttestationResponse.ts
index 96f659a..309c865 100644
--- a/packages/server/src/attestation/verifyAttestationResponse.ts
+++ b/packages/server/src/attestation/verifyAttestationResponse.ts
@@ -1,18 +1,16 @@
-import {
- AttestationCredentialJSON,
-} from '@simplewebauthn/typescript-types';
+import base64url from 'base64url';
+import { AttestationCredentialJSON } from '@simplewebauthn/typescript-types';
import decodeAttestationObject, { ATTESTATION_FORMATS } from '../helpers/decodeAttestationObject';
import decodeClientDataJSON from '../helpers/decodeClientDataJSON';
import parseAuthenticatorData from '../helpers/parseAuthenticatorData';
import toHash from '../helpers/toHash';
import decodeCredentialPublicKey from '../helpers/decodeCredentialPublicKey';
-import { COSEKEYS } from '../helpers/convertCOSEtoPKCS';
+import convertCOSEtoPKCS, { COSEKEYS } from '../helpers/convertCOSEtoPKCS';
import { supportedCOSEAlgorithIdentifiers } from './generateAttestationOptions';
import verifyFIDOU2F from './verifications/verifyFIDOU2F';
import verifyPacked from './verifications/verifyPacked';
-import verifyNone from './verifications/verifyNone';
import verifyAndroidSafetynet from './verifications/verifyAndroidSafetyNet';
/**
@@ -52,10 +50,10 @@ export default function verifyAttestationResponse(
}
const attestationObject = decodeAttestationObject(response.attestationObject);
- const { fmt, authData } = attestationObject;
+ const { fmt, authData, attStmt } = attestationObject;
const parsedAuthData = parseAuthenticatorData(authData);
- const { rpIdHash, flags, credentialPublicKey } = parsedAuthData;
+ const { rpIdHash, flags, credentialID, counter, credentialPublicKey } = parsedAuthData;
// Make sure the response's RP ID is ours
const expectedRPIDHash = toHash(Buffer.from(expectedRPID, 'ascii'));
@@ -68,6 +66,10 @@ export default function verifyAttestationResponse(
throw new Error('User not present during assertion');
}
+ if (!credentialID) {
+ throw new Error('No credential ID was provided by authenticator');
+ }
+
if (!credentialPublicKey) {
throw new Error('No public key was provided by authenticator');
}
@@ -85,42 +87,59 @@ export default function verifyAttestationResponse(
throw new Error(`Unexpected public key alg "${alg}", expected one of "${supported}"`);
}
+ const clientDataHash = toHash(base64url.toBuffer(response.clientDataJSON));
+
/**
* Verification can only be performed when attestation = 'direct'
*/
+ let verified = false;
if (fmt === ATTESTATION_FORMATS.FIDO_U2F) {
- return verifyFIDOU2F(
- attestationObject,
- response.clientDataJSON,
- parsedAuthData,
- );
+ verified = verifyFIDOU2F({
+ attStmt,
+ clientDataHash,
+ credentialID,
+ credentialPublicKey,
+ rpIdHash,
+ });
+ } else if (fmt === ATTESTATION_FORMATS.PACKED) {
+ verified = verifyPacked({
+ attStmt,
+ authData,
+ clientDataHash,
+ credentialPublicKey,
+ });
+ } else if (fmt === ATTESTATION_FORMATS.ANDROID_SAFETYNET) {
+ verified = verifyAndroidSafetynet({
+ attStmt,
+ authData,
+ clientDataHash,
+ });
+ } else if (fmt === ATTESTATION_FORMATS.NONE) {
+ // This is the weaker of the attestations, so there's nothing else to really check
+ verified = true;
+ } else {
+ throw new Error(`Unsupported Attestation Format: ${fmt}`);
}
- if (fmt === ATTESTATION_FORMATS.PACKED) {
- return verifyPacked(
- attestationObject,
- response.clientDataJSON,
- parsedAuthData,
- );
- }
+ const toReturn: VerifiedAttestation = {
+ verified,
+ userVerified: flags.uv,
+ };
- if (fmt === ATTESTATION_FORMATS.ANDROID_SAFETYNET) {
- return verifyAndroidSafetynet(
- attestationObject,
- response.clientDataJSON,
- parsedAuthData,
- credentialPublicKey,
- );
- }
+ if (toReturn.verified) {
+ toReturn.userVerified = flags.uv;
- if (fmt === ATTESTATION_FORMATS.NONE) {
- return verifyNone(
- attestationObject,
- parsedAuthData,
- );
+ const publicKey = convertCOSEtoPKCS(credentialPublicKey);
+
+ toReturn.authenticatorInfo = {
+ fmt,
+ counter,
+ base64PublicKey: base64url.encode(publicKey),
+ base64CredentialID: base64url.encode(credentialID),
+ };
}
- throw new Error(`Unsupported Attestation Format: ${fmt}`);
+ return toReturn;
}
/**