diff options
Diffstat (limited to 'packages/server')
13 files changed, 115 insertions, 100 deletions
diff --git a/packages/server/src/helpers/convertPublicKeyToPEM.test.ts b/packages/server/src/helpers/convertPublicKeyToPEM.test.ts index 353a9eb..f7cd401 100644 --- a/packages/server/src/helpers/convertPublicKeyToPEM.test.ts +++ b/packages/server/src/helpers/convertPublicKeyToPEM.test.ts @@ -1,45 +1,45 @@ -import base64url from 'base64url'; -import cbor from 'cbor'; import { COSEKEYS } from './convertCOSEtoPKCS'; import { convertPublicKeyToPEM } from './convertPublicKeyToPEM'; +import * as cbor from './cbor'; +import * as uint8Array from './uint8Array'; -test('should return pem when input is base64URLString', () => { - const mockCOSEKey = new Map<number, number | Buffer>(); +test('should return pem - EC2', () => { + const mockEC2Key = new Map<number, number | Uint8Array>(); - const x = Buffer.from('gh9MmXjtmcHFesofqWZ6iuxSdAYgoPVvfJqpv1818lo', 'base64'); - const y = Buffer.from('3BDZHsNvKUb5VbyGPqcAFf4FGuPhJ2Xy215oWDw_1jc', 'base64'); - mockCOSEKey.set(COSEKEYS.kty, 2); - mockCOSEKey.set(COSEKEYS.alg, -7); - mockCOSEKey.set(COSEKEYS.crv, 1); - mockCOSEKey.set(COSEKEYS.x, x); - mockCOSEKey.set(COSEKEYS.y, y); + const x = uint8Array.fromHex('821f4c9978ed99c1c57aca1fa9667a8aec52740620a0f56f7c9aa9bf5f35f25a'); + const y = uint8Array.fromHex('dc10d91ec36f2946f955bc863ea70015fe051ae3e12765f2db5e68583c3fd637'); + mockEC2Key.set(COSEKEYS.kty, 2); + mockEC2Key.set(COSEKEYS.alg, -7); + mockEC2Key.set(COSEKEYS.crv, 1); + mockEC2Key.set(COSEKEYS.x, x); + mockEC2Key.set(COSEKEYS.y, y); - jest.spyOn(cbor, 'decodeAllSync').mockReturnValueOnce([mockCOSEKey]); - const input = base64url.toBuffer('test'); - const actual = convertPublicKeyToPEM(input); + const pubKeyCBOR = cbor.encode(mockEC2Key); + + const actual = convertPublicKeyToPEM(pubKeyCBOR); expect(actual).toEqual(`-----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEgh9MmXjtmcHFesofqWZ6iuxSdAYg\noPVvfJqpv1818lrcENkew28pRvlVvIY+pwAV/gUa4+EnZfLbXmhYPD/WNw== -----END PUBLIC KEY----- `); }); -test('should return pem when input is base64URLString', () => { - const mockCOSEKey = new Map<number, number | Buffer>(); +test('should return pem - RSA', () => { + const mockRSAKey = new Map<number, number | Buffer>(); const n = Buffer.from( '0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw', 'base64', ); const e = Buffer.from('AQAB', 'base64'); - mockCOSEKey.set(COSEKEYS.kty, 3); - mockCOSEKey.set(COSEKEYS.alg, -7); - mockCOSEKey.set(COSEKEYS.crv, 1); - mockCOSEKey.set(COSEKEYS.n, n); - mockCOSEKey.set(COSEKEYS.e, e); + mockRSAKey.set(COSEKEYS.kty, 3); + mockRSAKey.set(COSEKEYS.alg, -7); + mockRSAKey.set(COSEKEYS.crv, 1); + mockRSAKey.set(COSEKEYS.n, n); + mockRSAKey.set(COSEKEYS.e, e); + + const pubKeyCBOR = cbor.encode(mockRSAKey); - jest.spyOn(cbor, 'decodeAllSync').mockReturnValueOnce([mockCOSEKey]); - const input = base64url.toBuffer('test'); - const actual = convertPublicKeyToPEM(input); + const actual = convertPublicKeyToPEM(pubKeyCBOR); expect(actual).toEqual(`-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0vx7agoebGcQSuuPiLJX ZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tS @@ -58,25 +58,25 @@ test('should return pem when input is base64URLString', () => { mockCOSEKey.set(COSEKEYS.kty, 0); mockCOSEKey.set(COSEKEYS.alg, -7); - jest.spyOn(cbor, 'decodeAllSync').mockReturnValueOnce([mockCOSEKey]); - const input = base64url.toBuffer('test'); + const pubKeyCBOR = cbor.encode(mockCOSEKey); + try { - convertPublicKeyToPEM(input); + convertPublicKeyToPEM(pubKeyCBOR); } catch (err) { expect((err as Error).message).toEqual('Public key was missing kty'); } }); test('should raise error when kty is OKP (1)', () => { - const mockCOSEKey = new Map<number, number | Buffer>(); + const mockOKPKey = new Map<number, number | Buffer>(); - mockCOSEKey.set(COSEKEYS.kty, 1); - mockCOSEKey.set(COSEKEYS.alg, -7); + mockOKPKey.set(COSEKEYS.kty, 1); + mockOKPKey.set(COSEKEYS.alg, -7); + + const pubKeyCBOR = cbor.encode(mockOKPKey); - jest.spyOn(cbor, 'decodeAllSync').mockReturnValueOnce([mockCOSEKey]); - const input = base64url.toBuffer('test'); try { - convertPublicKeyToPEM(input); + convertPublicKeyToPEM(pubKeyCBOR); } catch (err) { expect((err as Error).message).toEqual('Could not convert public key type 1 to PEM'); } diff --git a/packages/server/src/helpers/convertPublicKeyToPEM.ts b/packages/server/src/helpers/convertPublicKeyToPEM.ts index 40c8c5f..ff8396b 100644 --- a/packages/server/src/helpers/convertPublicKeyToPEM.ts +++ b/packages/server/src/helpers/convertPublicKeyToPEM.ts @@ -1,27 +1,28 @@ import jwkToPem from 'jwk-to-pem'; -import { COSEKEYS, COSEKTY, COSECRV } from './convertCOSEtoPKCS'; -import { decodeCborFirst } from './decodeCbor'; +import { COSEKEYS, COSEKTY, COSECRV, COSEPublicKey } from './convertCOSEtoPKCS'; +import * as cbor from './cbor'; +import * as base64url from './base64url'; export function convertPublicKeyToPEM(publicKey: Uint8Array): string { let struct; try { - struct = decodeCborFirst(publicKey); + struct = cbor.decodeFirst<COSEPublicKey>(publicKey); } catch (err) { const _err = err as Error; throw new Error(`Error decoding public key while converting to PEM: ${_err.message}`); } - const kty = struct[COSEKEYS.kty]; + const kty = struct.get(COSEKEYS.kty); if (!kty) { throw new Error('Public key was missing kty'); } if (kty === COSEKTY.EC2) { - const crv = struct[COSEKEYS.crv]; - const x = struct[COSEKEYS.x]; - const y = struct[COSEKEYS.y]; + const crv = struct.get(COSEKEYS.crv); + const x = struct.get(COSEKEYS.x); + const y = struct.get(COSEKEYS.y); if (!crv) { throw new Error('Public key was missing crv (EC2)'); @@ -39,14 +40,14 @@ export function convertPublicKeyToPEM(publicKey: Uint8Array): string { kty: 'EC', // Specify curve as "P-256" from "p256" crv: COSECRV[crv as number].replace('p', 'P-'), - x: (x as Buffer).toString('base64'), - y: (y as Buffer).toString('base64'), + x: base64url.fromBuffer(x as Uint8Array, 'base64'), + y: base64url.fromBuffer(y as Uint8Array, 'base64'), }); return ecPEM; } else if (kty === COSEKTY.RSA) { - const n = struct[COSEKEYS.n]; - const e = struct[COSEKEYS.e]; + const n = struct.get(COSEKEYS.n); + const e = struct.get(COSEKEYS.e); if (!n) { throw new Error('Public key was missing n (RSA)'); @@ -58,8 +59,8 @@ export function convertPublicKeyToPEM(publicKey: Uint8Array): string { const rsaPEM = jwkToPem({ kty: 'RSA', - n: (n as Buffer).toString('base64'), - e: (e as Buffer).toString('base64'), + n: base64url.fromBuffer(n as Uint8Array, 'base64'), + e: base64url.fromBuffer(e as Uint8Array, 'base64'), }); return rsaPEM; diff --git a/packages/server/src/helpers/decodeAttestationObject.test.ts b/packages/server/src/helpers/decodeAttestationObject.test.ts index 1ba6bd0..b37d137 100644 --- a/packages/server/src/helpers/decodeAttestationObject.test.ts +++ b/packages/server/src/helpers/decodeAttestationObject.test.ts @@ -11,9 +11,9 @@ test('should decode base64url-encoded indirect attestationObject', () => { ), ); - expect(decoded.fmt).toEqual('none'); - expect(decoded.attStmt).toEqual({}); - expect(decoded.authData).toBeDefined(); + expect(decoded.get('fmt')).toEqual('none'); + expect(decoded.get('attStmt')).toEqual(new Map()); + expect(decoded.get('authData')).toBeDefined(); }); test('should decode base64url-encoded direct attestationObject', () => { @@ -38,8 +38,8 @@ test('should decode base64url-encoded direct attestationObject', () => { ), ); - expect(decoded.fmt).toEqual('fido-u2f'); - expect(decoded.attStmt.sig).toBeDefined(); - expect(decoded.attStmt.x5c).toBeDefined(); - expect(decoded.authData).toBeDefined(); + expect(decoded.get('fmt')).toEqual('fido-u2f'); + expect(decoded.get('attStmt').get('sig')).toBeDefined(); + expect(decoded.get('attStmt').get('x5c')).toBeDefined(); + expect(decoded.get('authData')).toBeDefined(); }); diff --git a/packages/server/src/helpers/decodeAttestationObject.ts b/packages/server/src/helpers/decodeAttestationObject.ts index bab6ced..9d08e33 100644 --- a/packages/server/src/helpers/decodeAttestationObject.ts +++ b/packages/server/src/helpers/decodeAttestationObject.ts @@ -1,13 +1,12 @@ -import { decodeCborFirst } from './decodeCbor'; +import * as cbor from './cbor'; /** * Convert an AttestationObject buffer to a proper object * * @param base64AttestationObject Attestation Object buffer */ -export function decodeAttestationObject(attestationObject: Buffer): AttestationObject { - const toCBOR: AttestationObject = decodeCborFirst(attestationObject); - return toCBOR; +export function decodeAttestationObject(attestationObject: Uint8Array): AttestationObject { + return cbor.decodeFirst<AttestationObject>(attestationObject); } export type AttestationFormat = @@ -20,17 +19,17 @@ export type AttestationFormat = | 'none'; export type AttestationObject = { - fmt: AttestationFormat; - attStmt: AttestationStatement; - authData: Buffer; + get(key: 'fmt'): AttestationFormat; + get(key: 'attStmt'): AttestationStatement; + get(key: 'authData'): Uint8Array; }; export type AttestationStatement = { - sig?: Buffer; - x5c?: Buffer[]; - response?: Buffer; - alg?: number; - ver?: string; - certInfo?: Buffer; - pubArea?: Buffer; + get(key: 'sig'): Uint8Array | undefined; + get(key: 'x5c'): Uint8Array[] | undefined; + get(key: 'response'): Uint8Array | undefined; + get(key: 'alg'): number | undefined; + get(key: 'ver'): string | undefined; + get(key: 'certInfo'): Uint8Array | undefined; + get(key: 'pubArea'): Uint8Array | undefined; }; diff --git a/packages/server/src/metadata/verifyAttestationWithMetadata.ts b/packages/server/src/metadata/verifyAttestationWithMetadata.ts index 08cd8fe..aa45809 100644 --- a/packages/server/src/metadata/verifyAttestationWithMetadata.ts +++ b/packages/server/src/metadata/verifyAttestationWithMetadata.ts @@ -18,7 +18,7 @@ export async function verifyAttestationWithMetadata({ }: { statement: MetadataStatement; credentialPublicKey: Uint8Array; - x5c: Buffer[] | Base64URLString[]; + x5c: Uint8Array[] | Base64URLString[]; attestationStatementAlg?: number; }): Promise<boolean> { const { @@ -43,9 +43,9 @@ export async function verifyAttestationWithMetadata({ const decodedPublicKey = decodeCredentialPublicKey(credentialPublicKey); // Assume everything is a number because these values should be const publicKeyCOSEInfo: COSEInfo = { - kty: decodedPublicKey[COSEKEYS.kty] as number, - alg: decodedPublicKey[COSEKEYS.alg] as number, - crv: decodedPublicKey[COSEKEYS.crv] as number, + kty: decodedPublicKey.get(COSEKEYS.kty) as number, + alg: decodedPublicKey.get(COSEKEYS.alg) as number, + crv: decodedPublicKey.get(COSEKEYS.crv) as number, }; if (!publicKeyCOSEInfo.crv) { delete publicKeyCOSEInfo.crv; diff --git a/packages/server/src/registration/verifications/tpm/verifyAttestationTPM.test.ts b/packages/server/src/registration/verifications/tpm/verifyAttestationTPM.test.ts index 5652640..b69f24c 100644 --- a/packages/server/src/registration/verifications/tpm/verifyAttestationTPM.test.ts +++ b/packages/server/src/registration/verifications/tpm/verifyAttestationTPM.test.ts @@ -1,9 +1,9 @@ +import * as base64url from '../../../helpers/base64url'; import { verifyRegistrationResponse } from '../../verifyRegistrationResponse'; -import base64url from 'base64url'; test('should verify TPM response', async () => { const expectedChallenge = 'a4de0d36-057d-4e9d-831a-2c578fa89170'; - jest.spyOn(base64url, 'encode').mockReturnValueOnce(expectedChallenge); + jest.spyOn(base64url, 'fromString').mockReturnValueOnce(expectedChallenge); const verification = await verifyRegistrationResponse({ credential: { id: 'SErwRhxIzjPowcnM3e-D-u89EQXLUe1NYewpshd7Mc0', @@ -33,7 +33,7 @@ test('should verify SHA1 TPM response', async () => { */ const expectedChallenge = '9JyUfJkg8PqoKZuD7FHzOE9dbyculC9urGTpGqBnEwnhKmni4rGRXxm3-ZBHK8x6riJQqIpC8qEa-T0qIFTKTQ'; - jest.spyOn(base64url, 'encode').mockReturnValueOnce(expectedChallenge); + jest.spyOn(base64url, 'fromString').mockReturnValueOnce(expectedChallenge); const verification = await verifyRegistrationResponse({ credential: { rawId: 'UJDoUJoGiDQF_EEZ3G_z9Lfq16_KFaXtMTjwTUrrRlc', @@ -63,7 +63,7 @@ test('should verify SHA256 TPM response', async () => { */ const expectedChallenge = 'gHrAk4pNe2VlB0HLeKclI2P6QEa83PuGeijTHMtpbhY9KlybyhlwF_VzRe7yhabXagWuY6rkDWfvvhNqgh2o7A'; - jest.spyOn(base64url, 'encode').mockReturnValueOnce(expectedChallenge); + jest.spyOn(base64url, 'fromString').mockReturnValueOnce(expectedChallenge); const verification = await verifyRegistrationResponse({ credential: { rawId: 'h9XMhkVePN1Prq9Ks_VfwIsVZvt-jmSRTEnevTc-KB8', @@ -100,7 +100,7 @@ test('should verify TPM response with spec-compliant tcgAtTpm SAN structure', as * ] */ const expectedChallenge = 'VfmZXKDxqdoXFMHXO3SE2Q2b8u5Ki64OL_XICELcGKg'; - jest.spyOn(base64url, 'encode').mockReturnValueOnce(expectedChallenge); + jest.spyOn(base64url, 'fromString').mockReturnValueOnce(expectedChallenge); const verification = await verifyRegistrationResponse({ credential: { id: 'LVwzXx0fStkvsos_jdl9DTd6O3-6be8Ua4tcdXc5XeM', @@ -133,7 +133,7 @@ test('should verify TPM response with non-spec-compliant tcgAtTpm SAN structure' * ] */ const expectedChallenge = '4STWgmXrgJxzigqe6nFuIg'; - jest.spyOn(base64url, 'encode').mockReturnValueOnce(expectedChallenge); + jest.spyOn(base64url, 'fromString').mockReturnValueOnce(expectedChallenge); const verification = await verifyRegistrationResponse({ credential: { id: 'X7TPi7o8WfiIz1bP0Vciz1xRvSMyiitgOR1sUqY724s', @@ -157,7 +157,7 @@ test('should verify TPM response with non-spec-compliant tcgAtTpm SAN structure' test('should verify TPM response with ECC public area type', async () => { const expectedChallenge = 'uzn9u0Tx-LBdtGgERsbkHRBjiUt5i2rvm2BBTZrWqEo'; - jest.spyOn(base64url, 'encode').mockReturnValueOnce(expectedChallenge); + jest.spyOn(base64url, 'fromString').mockReturnValueOnce(expectedChallenge); const verification = await verifyRegistrationResponse({ credential: { 'id': 'hsS2ywFz_LWf9-lC35vC9uJTVD3ZCVdweZvESUbjXnQ', diff --git a/packages/server/src/registration/verifications/tpm/verifyAttestationTPM.ts b/packages/server/src/registration/verifications/tpm/verifyAttestationTPM.ts index c1c4306..efc2526 100644 --- a/packages/server/src/registration/verifications/tpm/verifyAttestationTPM.ts +++ b/packages/server/src/registration/verifications/tpm/verifyAttestationTPM.ts @@ -17,6 +17,7 @@ import { convertCertBufferToPEM } from '../../../helpers/convertCertBufferToPEM' import { validateCertificatePath } from '../../../helpers/validateCertificatePath'; import { getCertificateInfo } from '../../../helpers/getCertificateInfo'; import { verifySignature } from '../../../helpers/verifySignature'; +import * as uint8Array from '../../../helpers/uint8Array'; import { MetadataService } from '../../../services/metadataService'; import { verifyAttestationWithMetadata } from '../../../metadata/verifyAttestationWithMetadata'; @@ -27,7 +28,12 @@ import { parsePubArea } from './parsePubArea'; export async function verifyAttestationTPM(options: AttestationFormatVerifierOpts): Promise<boolean> { const { aaguid, attStmt, authData, credentialPublicKey, clientDataHash, rootCertificates } = options; - const { ver, sig, alg, x5c, pubArea, certInfo } = attStmt; + const ver = attStmt.get('ver'); + const sig = attStmt.get('sig'); + const alg = attStmt.get('alg'); + const x5c = attStmt.get('x5c'); + const pubArea = attStmt.get('pubArea'); + const certInfo = attStmt.get('certInfo'); /** * Verify structures @@ -64,8 +70,8 @@ export async function verifyAttestationTPM(options: AttestationFormatVerifierOpt const cosePublicKey = decodeCredentialPublicKey(credentialPublicKey); if (pubType === 'TPM_ALG_RSA') { - const n = cosePublicKey[COSEKEYS.n]; - const e = cosePublicKey[COSEKEYS.e]; + const n = cosePublicKey.get(COSEKEYS.n); + const e = cosePublicKey.get(COSEKEYS.e); if (!n) { throw new Error('COSE public key missing n (TPM|RSA)'); @@ -93,9 +99,9 @@ export async function verifyAttestationTPM(options: AttestationFormatVerifierOpt throw new Error(`Unexpected public key exp ${eSum}, expected ${pubAreaExponent} (TPM|RSA)`); } } else if (pubType === 'TPM_ALG_ECC') { - const crv = cosePublicKey[COSEKEYS.crv]; - const x = cosePublicKey[COSEKEYS.x]; - const y = cosePublicKey[COSEKEYS.y]; + const crv = cosePublicKey.get(COSEKEYS.crv); + const x = cosePublicKey.get(COSEKEYS.x); + const y = cosePublicKey.get(COSEKEYS.y); if (!crv) { throw new Error('COSE public key missing crv (TPM|ECC)'); diff --git a/packages/server/src/registration/verifications/verifyAttestationAndroidKey.ts b/packages/server/src/registration/verifications/verifyAttestationAndroidKey.ts index 27bef78..e7ae6de 100644 --- a/packages/server/src/registration/verifications/verifyAttestationAndroidKey.ts +++ b/packages/server/src/registration/verifications/verifyAttestationAndroidKey.ts @@ -20,7 +20,9 @@ export async function verifyAttestationAndroidKey( ): Promise<boolean> { const { authData, clientDataHash, attStmt, credentialPublicKey, aaguid, rootCertificates } = options; - const { x5c, sig, alg } = attStmt; + const x5c = attStmt.get('x5c'); + const sig = attStmt.get('sig'); + const alg = attStmt.get('alg'); if (!x5c) { throw new Error('No attestation certificate provided in attestation statement (AndroidKey)'); diff --git a/packages/server/src/registration/verifications/verifyAttestationAndroidSafetyNet.test.ts b/packages/server/src/registration/verifications/verifyAttestationAndroidSafetyNet.test.ts index 51b0f22..944593f 100644 --- a/packages/server/src/registration/verifications/verifyAttestationAndroidSafetyNet.test.ts +++ b/packages/server/src/registration/verifications/verifyAttestationAndroidSafetyNet.test.ts @@ -26,8 +26,8 @@ beforeEach(() => { const { attestationObject, clientDataJSON } = attestationAndroidSafetyNet.response; const decodedAttestationObject = decodeAttestationObject(base64url.toBuffer(attestationObject)); - authData = decodedAttestationObject.authData; - attStmt = decodedAttestationObject.attStmt; + authData = decodedAttestationObject.get('authData'); + attStmt = decodedAttestationObject.get('attStmt'); clientDataHash = toHash(base64url.toBuffer(clientDataJSON)); const parsedAuthData = parseAuthenticatorData(authData); @@ -89,8 +89,8 @@ test('should validate response with cert path completed with GlobalSign R1 root const { attestationObject, clientDataJSON } = safetyNetUsingGSR1RootCert.response; const decodedAttestationObject = decodeAttestationObject(base64url.toBuffer(attestationObject)); - const _authData = decodedAttestationObject.authData; - const _attStmt = decodedAttestationObject.attStmt; + const _authData = decodedAttestationObject.get('authData'); + const _attStmt = decodedAttestationObject.get('attStmt'); const _clientDataHash = toHash(base64url.toBuffer(clientDataJSON)); const parsedAuthData = parseAuthenticatorData(_authData); diff --git a/packages/server/src/registration/verifications/verifyAttestationAndroidSafetyNet.ts b/packages/server/src/registration/verifications/verifyAttestationAndroidSafetyNet.ts index c32ca5e..45fb99b 100644 --- a/packages/server/src/registration/verifications/verifyAttestationAndroidSafetyNet.ts +++ b/packages/server/src/registration/verifications/verifyAttestationAndroidSafetyNet.ts @@ -25,7 +25,9 @@ export async function verifyAttestationAndroidSafetyNet( verifyTimestampMS = true, credentialPublicKey, } = options; - const { alg, response, ver } = attStmt; + const alg = attStmt.get('alg'); + const response = attStmt.get('response'); + const ver = attStmt.get('ver'); if (!ver) { throw new Error('No ver value in attestation (SafetyNet)'); @@ -36,11 +38,11 @@ export async function verifyAttestationAndroidSafetyNet( } // Prepare to verify a JWT - const jwt = response.toString('utf8'); + const jwt = uint8Array.toUTF8String(response); const jwtParts = jwt.split('.'); - const HEADER: SafetyNetJWTHeader = JSON.parse(base64url.decode(jwtParts[0])); - const PAYLOAD: SafetyNetJWTPayload = JSON.parse(base64url.decode(jwtParts[1])); + const HEADER: SafetyNetJWTHeader = JSON.parse(base64url.toString(jwtParts[0])); + const PAYLOAD: SafetyNetJWTPayload = JSON.parse(base64url.toString(jwtParts[1])); const SIGNATURE: SafetyNetJWTSignature = jwtParts[2]; /** @@ -63,9 +65,9 @@ export async function verifyAttestationAndroidSafetyNet( } } - const nonceBase = Buffer.concat([authData, clientDataHash]); + const nonceBase = uint8Array.concat([authData, clientDataHash]); const nonceBuffer = toHash(nonceBase); - const expectedNonce = uint8Array.toBase64(nonceBuffer); + const expectedNonce = base64url.fromBuffer(nonceBuffer, 'base64'); if (nonce !== expectedNonce) { throw new Error('Could not verify payload nonce (SafetyNet)'); @@ -81,7 +83,8 @@ export async function verifyAttestationAndroidSafetyNet( /** * START Verify Header */ - const leafCertBuffer = base64url.toBuffer(HEADER.x5c[0]); + // `HEADER.x5c[0]` is definitely a base64 string + const leafCertBuffer = base64url.toBuffer(HEADER.x5c[0], 'base64'); const leafCertInfo = getCertificateInfo(leafCertBuffer); const { subject } = leafCertInfo; @@ -121,7 +124,7 @@ export async function verifyAttestationAndroidSafetyNet( /** * START Verify Signature */ - const signatureBaseBuffer = Buffer.from(`${jwtParts[0]}.${jwtParts[1]}`); + const signatureBaseBuffer = uint8Array.fromUTF8String(`${jwtParts[0]}.${jwtParts[1]}`); const signatureBuffer = base64url.toBuffer(SIGNATURE); const verified = await verifySignature({ diff --git a/packages/server/src/registration/verifications/verifyAttestationApple.ts b/packages/server/src/registration/verifications/verifyAttestationApple.ts index 928f6c6..b53c94b 100644 --- a/packages/server/src/registration/verifications/verifyAttestationApple.ts +++ b/packages/server/src/registration/verifications/verifyAttestationApple.ts @@ -13,7 +13,7 @@ export async function verifyAttestationApple( options: AttestationFormatVerifierOpts, ): Promise<boolean> { const { attStmt, authData, clientDataHash, credentialPublicKey, rootCertificates } = options; - const { x5c } = attStmt; + const x5c = attStmt.get('x5c'); if (!x5c) { throw new Error('No attestation certificate provided in attestation statement (Apple)'); diff --git a/packages/server/src/registration/verifications/verifyAttestationFIDOU2F.ts b/packages/server/src/registration/verifications/verifyAttestationFIDOU2F.ts index 591149f..ef6aa7c 100644 --- a/packages/server/src/registration/verifications/verifyAttestationFIDOU2F.ts +++ b/packages/server/src/registration/verifications/verifyAttestationFIDOU2F.ts @@ -33,7 +33,8 @@ export async function verifyAttestationFIDOU2F( publicKey, ]); - const { sig, x5c } = attStmt; + const sig = attStmt.get('sig'); + const x5c = attStmt.get('x5c'); if (!x5c) { throw new Error('No attestation certificate provided in attestation statement (FIDOU2F)'); diff --git a/packages/server/src/registration/verifications/verifyAttestationPacked.ts b/packages/server/src/registration/verifications/verifyAttestationPacked.ts index aa2d4b9..c5db840 100644 --- a/packages/server/src/registration/verifications/verifyAttestationPacked.ts +++ b/packages/server/src/registration/verifications/verifyAttestationPacked.ts @@ -5,6 +5,7 @@ import { convertCertBufferToPEM } from '../../helpers/convertCertBufferToPEM'; import { validateCertificatePath } from '../../helpers/validateCertificatePath'; import { getCertificateInfo } from '../../helpers/getCertificateInfo'; import { verifySignature } from '../../helpers/verifySignature'; +import * as uint8Array from '../../helpers/uint8Array'; import { MetadataService } from '../../services/metadataService'; import { verifyAttestationWithMetadata } from '../../metadata/verifyAttestationWithMetadata'; @@ -17,7 +18,9 @@ export async function verifyAttestationPacked( const { attStmt, clientDataHash, authData, credentialPublicKey, aaguid, rootCertificates } = options; - const { sig, x5c, alg } = attStmt; + const sig = attStmt.get('sig'); + const x5c = attStmt.get('x5c'); + const alg = attStmt.get('alg'); if (!sig) { throw new Error('No attestation signature provided in attestation statement (Packed)'); |