From 94101488e87a56393693e617d5502c6eae096d50 Mon Sep 17 00:00:00 2001 From: Matthew Miller Date: Sat, 5 Nov 2022 22:41:00 -0700 Subject: Replace most Buffer types with Uint8Array --- packages/server/src/helpers/convertAAGUIDToString.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages/server/src/helpers/convertAAGUIDToString.ts') diff --git a/packages/server/src/helpers/convertAAGUIDToString.ts b/packages/server/src/helpers/convertAAGUIDToString.ts index 0fb8356..3dc837c 100644 --- a/packages/server/src/helpers/convertAAGUIDToString.ts +++ b/packages/server/src/helpers/convertAAGUIDToString.ts @@ -1,7 +1,7 @@ /** * Convert the aaguid buffer in authData into a UUID string */ -export function convertAAGUIDToString(aaguid: Buffer): string { +export function convertAAGUIDToString(aaguid: Uint8Array): string { // Raw Hex: adce000235bcc60a648b0b25f1f05503 const hex = aaguid.toString('hex'); -- cgit v1.2.3 From dc523d81bff8793ee4dc36b1273e08941fc7abaa Mon Sep 17 00:00:00 2001 From: Matthew Miller Date: Sun, 6 Nov 2022 09:06:02 -0800 Subject: Switch to uint8array helpers --- .../server/src/authentication/generateAuthenticationOptions.ts | 9 +++++++++ .../server/src/authentication/verifyAuthenticationResponse.ts | 5 +++-- packages/server/src/helpers/convertAAGUIDToString.ts | 4 +++- packages/server/src/helpers/convertCOSEtoPKCS.ts | 7 ++++--- packages/server/src/helpers/convertCertBufferToPEM.ts | 3 ++- packages/server/src/registration/generateRegistrationOptions.ts | 9 +++++++++ .../registration/verifications/verifyAttestationAndroidKey.ts | 5 +++-- .../verifications/verifyAttestationAndroidSafetyNet.ts | 3 ++- .../src/registration/verifications/verifyAttestationApple.ts | 5 +++-- .../src/registration/verifications/verifyAttestationFIDOU2F.ts | 5 +++-- packages/server/src/registration/verifyRegistrationResponse.ts | 5 +++-- 11 files changed, 44 insertions(+), 16 deletions(-) (limited to 'packages/server/src/helpers/convertAAGUIDToString.ts') diff --git a/packages/server/src/authentication/generateAuthenticationOptions.ts b/packages/server/src/authentication/generateAuthenticationOptions.ts index 1eab513..4c841d9 100644 --- a/packages/server/src/authentication/generateAuthenticationOptions.ts +++ b/packages/server/src/authentication/generateAuthenticationOptions.ts @@ -5,6 +5,7 @@ import type { UserVerificationRequirement, } from '@simplewebauthn/typescript-types'; import base64url from '../helpers/base64url' +import uint8Array from '../helpers/uint8Array'; import { generateChallenge } from '../helpers/generateChallenge'; @@ -42,6 +43,14 @@ export function generateAuthenticationOptions( rpID, } = options; + /** + * Preserve ability to specify `string` values for challenges + */ + let _challenge = challenge; + if (typeof _challenge === 'string') { + _challenge = uint8Array.fromString(_challenge); + } + return { challenge: base64url.fromBuffer(_challenge), allowCredentials: allowCredentials?.map(cred => ({ diff --git a/packages/server/src/authentication/verifyAuthenticationResponse.ts b/packages/server/src/authentication/verifyAuthenticationResponse.ts index 7ae002e..9200758 100644 --- a/packages/server/src/authentication/verifyAuthenticationResponse.ts +++ b/packages/server/src/authentication/verifyAuthenticationResponse.ts @@ -12,6 +12,7 @@ import { parseAuthenticatorData } from '../helpers/parseAuthenticatorData'; import { isBase64URLString } from '../helpers/isBase64URLString'; import { parseBackupFlags } from '../helpers/parseBackupFlags'; import { AuthenticationExtensionsAuthenticatorOutputs } from '../helpers/decodeAuthenticatorExtensions'; +import uint8Array from '../helpers/uint8Array'; import base64url from '../helpers/base64url'; export type VerifyAuthenticationResponseOpts = { @@ -149,14 +150,14 @@ export async function verifyAuthenticationResponse( // Make sure the response's RP ID is ours if (typeof expectedRPID === 'string') { const expectedRPIDHash = toHash(Buffer.from(expectedRPID, 'ascii')); - if (!rpIdHash.equals(expectedRPIDHash)) { + if (!uint8Array.areEqual(rpIdHash, expectedRPIDHash)) { throw new Error(`Unexpected RP ID hash`); } } else { // Go through each expected RP ID and try to find one that matches const foundMatch = expectedRPID.some(expected => { const expectedRPIDHash = toHash(Buffer.from(expected, 'ascii')); - return rpIdHash.equals(expectedRPIDHash); + return uint8Array.areEqual(rpIdHash, expectedRPIDHash); }); if (!foundMatch) { diff --git a/packages/server/src/helpers/convertAAGUIDToString.ts b/packages/server/src/helpers/convertAAGUIDToString.ts index 3dc837c..80dc63c 100644 --- a/packages/server/src/helpers/convertAAGUIDToString.ts +++ b/packages/server/src/helpers/convertAAGUIDToString.ts @@ -1,9 +1,11 @@ +import uint8Array from './uint8Array'; + /** * Convert the aaguid buffer in authData into a UUID string */ export function convertAAGUIDToString(aaguid: Uint8Array): string { // Raw Hex: adce000235bcc60a648b0b25f1f05503 - const hex = aaguid.toString('hex'); + const hex = uint8Array.toHex(aaguid); const segments: string[] = [ hex.slice(0, 8), // 8 diff --git a/packages/server/src/helpers/convertCOSEtoPKCS.ts b/packages/server/src/helpers/convertCOSEtoPKCS.ts index 158fb36..0ee6a97 100644 --- a/packages/server/src/helpers/convertCOSEtoPKCS.ts +++ b/packages/server/src/helpers/convertCOSEtoPKCS.ts @@ -1,5 +1,6 @@ import { COSEAlgorithmIdentifier } from '@simplewebauthn/typescript-types'; import { decodeCborFirst } from './decodeCbor'; +import uint8Array from './uint8array'; /** * Takes COSE-encoded public key and converts it to PKCS key @@ -7,19 +8,19 @@ import { decodeCborFirst } from './decodeCbor'; export function convertCOSEtoPKCS(cosePublicKey: Uint8Array): Uint8Array { const struct: COSEPublicKey = decodeCborFirst(cosePublicKey); - const tag = Buffer.from([0x04]); const x = struct.get(COSEKEYS.x); const y = struct.get(COSEKEYS.y); + const tag = Uint8Array.from([0x04]); if (!x) { throw new Error('COSE public key was missing x'); } if (y) { - return Buffer.concat([tag, x as Buffer, y as Buffer]); + return uint8Array.concat([tag, x as Uint8Array, y as Uint8Array]); } - return Buffer.concat([tag, x as Buffer]); + return uint8Array.concat([tag, x as Uint8Array]); } export type COSEPublicKey = Map; diff --git a/packages/server/src/helpers/convertCertBufferToPEM.ts b/packages/server/src/helpers/convertCertBufferToPEM.ts index 5339282..faf9001 100644 --- a/packages/server/src/helpers/convertCertBufferToPEM.ts +++ b/packages/server/src/helpers/convertCertBufferToPEM.ts @@ -1,5 +1,6 @@ import type { Base64URLString } from '@simplewebauthn/typescript-types'; +import uint8Array from './uint8Array'; import base64url from './base64url'; /** @@ -14,7 +15,7 @@ export function convertCertBufferToPEM(certBuffer: Uint8Array | Base64URLString) if (typeof certBuffer === 'string') { b64cert = base64url.toBase64(certBuffer); } else { - b64cert = certBuffer.toString('base64'); + b64cert = uint8Array.toBase64(certBuffer); } let PEMKey = ''; diff --git a/packages/server/src/registration/generateRegistrationOptions.ts b/packages/server/src/registration/generateRegistrationOptions.ts index 12138b0..6273246 100644 --- a/packages/server/src/registration/generateRegistrationOptions.ts +++ b/packages/server/src/registration/generateRegistrationOptions.ts @@ -10,6 +10,7 @@ import type { import { generateChallenge } from '../helpers/generateChallenge'; import base64url from '../helpers/base64url'; +import uint8Array from '../helpers/uint8Array'; export type GenerateRegistrationOptionsOpts = { rpName: string; @@ -151,6 +152,14 @@ export function generateRegistrationOptions( authenticatorSelection.requireResidentKey = authenticatorSelection.residentKey === 'required'; } + /** + * Preserve ability to specify `string` values for challenges + */ + let _challenge = challenge; + if (typeof _challenge === 'string') { + _challenge = uint8Array.fromString(_challenge); + } + return { challenge: base64url.fromBuffer(_challenge), rp: { diff --git a/packages/server/src/registration/verifications/verifyAttestationAndroidKey.ts b/packages/server/src/registration/verifications/verifyAttestationAndroidKey.ts index 55a0612..6c89df8 100644 --- a/packages/server/src/registration/verifications/verifyAttestationAndroidKey.ts +++ b/packages/server/src/registration/verifications/verifyAttestationAndroidKey.ts @@ -8,6 +8,7 @@ import { convertCertBufferToPEM } from '../../helpers/convertCertBufferToPEM'; import { validateCertificatePath } from '../../helpers/validateCertificatePath'; import { verifySignature } from '../../helpers/verifySignature'; import { COSEALGHASH, convertCOSEtoPKCS } from '../../helpers/convertCOSEtoPKCS'; +import uint8Array from '../../helpers/uint8Array'; import { MetadataService } from '../../services/metadataService'; import { verifyAttestationWithMetadata } from '../../metadata/verifyAttestationWithMetadata'; @@ -36,14 +37,14 @@ export async function verifyAttestationAndroidKey( // Check that credentialPublicKey matches the public key in the attestation certificate // Find the public cert in the certificate as PKCS const parsedCert = AsnParser.parse(x5c[0], Certificate); - const parsedCertPubKey = Buffer.from( + const parsedCertPubKey = new Uint8Array( parsedCert.tbsCertificate.subjectPublicKeyInfo.subjectPublicKey, ); // Convert the credentialPublicKey to PKCS const credPubKeyPKCS = convertCOSEtoPKCS(credentialPublicKey); - if (!credPubKeyPKCS.equals(parsedCertPubKey)) { + if (!uint8Array.areEqual(credPubKeyPKCS, parsedCertPubKey)) { throw new Error('Credential public key does not equal leaf cert public key (AndroidKey)'); } diff --git a/packages/server/src/registration/verifications/verifyAttestationAndroidSafetyNet.ts b/packages/server/src/registration/verifications/verifyAttestationAndroidSafetyNet.ts index 4c1e685..65e5c00 100644 --- a/packages/server/src/registration/verifications/verifyAttestationAndroidSafetyNet.ts +++ b/packages/server/src/registration/verifications/verifyAttestationAndroidSafetyNet.ts @@ -7,6 +7,7 @@ import { verifySignature } from '../../helpers/verifySignature'; import { getCertificateInfo } from '../../helpers/getCertificateInfo'; import { validateCertificatePath } from '../../helpers/validateCertificatePath'; import { convertCertBufferToPEM } from '../../helpers/convertCertBufferToPEM'; +import uint8Array from '../../helpers/uint8Array'; import { MetadataService } from '../../services/metadataService'; import { verifyAttestationWithMetadata } from '../../metadata/verifyAttestationWithMetadata'; @@ -65,7 +66,7 @@ export async function verifyAttestationAndroidSafetyNet( const nonceBase = Buffer.concat([authData, clientDataHash]); const nonceBuffer = toHash(nonceBase); - const expectedNonce = nonceBuffer.toString('base64'); + const expectedNonce = uint8Array.toBase64(nonceBuffer); if (nonce !== expectedNonce) { throw new Error('Could not verify payload nonce (SafetyNet)'); diff --git a/packages/server/src/registration/verifications/verifyAttestationApple.ts b/packages/server/src/registration/verifications/verifyAttestationApple.ts index e0c7890..fc12f9d 100644 --- a/packages/server/src/registration/verifications/verifyAttestationApple.ts +++ b/packages/server/src/registration/verifications/verifyAttestationApple.ts @@ -7,6 +7,7 @@ import { validateCertificatePath } from '../../helpers/validateCertificatePath'; import { convertCertBufferToPEM } from '../../helpers/convertCertBufferToPEM'; import { toHash } from '../../helpers/toHash'; import { convertCOSEtoPKCS } from '../../helpers/convertCOSEtoPKCS'; +import uint8Array from '../../helpers/uint8Array'; export async function verifyAttestationApple( options: AttestationFormatVerifierOpts, @@ -55,7 +56,7 @@ export async function verifyAttestationApple( */ const extNonce = Buffer.from(extCertNonce.extnValue.buffer).slice(6); - if (!nonce.equals(extNonce)) { + if (!uint8Array.areEqual(nonce, extNonce)) { throw new Error(`credCert nonce was not expected value (Apple)`); } @@ -65,7 +66,7 @@ export async function verifyAttestationApple( const credPubKeyPKCS = convertCOSEtoPKCS(credentialPublicKey); const credCertSubjectPublicKey = Buffer.from(subjectPublicKeyInfo.subjectPublicKey); - if (!credPubKeyPKCS.equals(credCertSubjectPublicKey)) { + if (!uint8Array.areEqual(credPubKeyPKCS, credCertSubjectPublicKey)) { throw new Error('Credential public key does not equal credCert public key (Apple)'); } diff --git a/packages/server/src/registration/verifications/verifyAttestationFIDOU2F.ts b/packages/server/src/registration/verifications/verifyAttestationFIDOU2F.ts index 3c79b9e..e73c22f 100644 --- a/packages/server/src/registration/verifications/verifyAttestationFIDOU2F.ts +++ b/packages/server/src/registration/verifications/verifyAttestationFIDOU2F.ts @@ -4,6 +4,7 @@ import { convertCOSEtoPKCS } from '../../helpers/convertCOSEtoPKCS'; import { convertCertBufferToPEM } from '../../helpers/convertCertBufferToPEM'; import { validateCertificatePath } from '../../helpers/validateCertificatePath'; import { verifySignature } from '../../helpers/verifySignature'; +import uint8Array from '../../helpers/uint8Array'; /** * Verify an attestation response with fmt 'fido-u2f' @@ -17,7 +18,7 @@ export async function verifyAttestationFIDOU2F( rpIdHash, credentialID, credentialPublicKey, - aaguid = '', + aaguid, rootCertificates, } = options; @@ -43,7 +44,7 @@ export async function verifyAttestationFIDOU2F( } // FIDO spec says that aaguid _must_ equal 0x00 here to be legit - const aaguidToHex = Number.parseInt(aaguid.toString('hex'), 16); + const aaguidToHex = Number.parseInt(uint8Array.toHex(aaguid), 16); if (aaguidToHex !== 0x00) { throw new Error(`AAGUID "${aaguidToHex}" was not expected value`); } diff --git a/packages/server/src/registration/verifyRegistrationResponse.ts b/packages/server/src/registration/verifyRegistrationResponse.ts index efe7531..60140a4 100644 --- a/packages/server/src/registration/verifyRegistrationResponse.ts +++ b/packages/server/src/registration/verifyRegistrationResponse.ts @@ -18,6 +18,7 @@ import { decodeCredentialPublicKey } from '../helpers/decodeCredentialPublicKey' import { COSEKEYS } from '../helpers/convertCOSEtoPKCS'; import { convertAAGUIDToString } from '../helpers/convertAAGUIDToString'; import { parseBackupFlags } from '../helpers/parseBackupFlags'; +import uint8Array from '../helpers/uint8Array'; import { SettingsService } from '../services/settingsService'; import { supportedCOSEAlgorithmIdentifiers } from './generateRegistrationOptions'; @@ -141,14 +142,14 @@ export async function verifyRegistrationResponse( if (expectedRPID) { if (typeof expectedRPID === 'string') { const expectedRPIDHash = toHash(Buffer.from(expectedRPID, 'ascii')); - if (!rpIdHash.equals(expectedRPIDHash)) { + if (!uint8Array.areEqual(rpIdHash, expectedRPIDHash)) { throw new Error(`Unexpected RP ID hash`); } } else { // Go through each expected RP ID and try to find one that matches const foundMatch = expectedRPID.some(expected => { const expectedRPIDHash = toHash(Buffer.from(expected, 'ascii')); - return rpIdHash.equals(expectedRPIDHash); + return uint8Array.areEqual(rpIdHash, expectedRPIDHash); }); if (!foundMatch) { -- cgit v1.2.3 From 295c5463be1dbdf477ae08d91c936805cd2a5b34 Mon Sep 17 00:00:00 2001 From: Matthew Miller Date: Fri, 11 Nov 2022 15:35:16 -0800 Subject: Update uint8Array import paths --- .../server/src/authentication/generateAuthenticationOptions.test.ts | 2 +- packages/server/src/authentication/generateAuthenticationOptions.ts | 4 ++-- packages/server/src/authentication/verifyAuthenticationResponse.ts | 4 ++-- packages/server/src/helpers/convertAAGUIDToString.ts | 2 +- packages/server/src/helpers/convertCOSEtoPKCS.ts | 2 +- packages/server/src/helpers/convertCertBufferToPEM.ts | 2 +- packages/server/src/registration/generateRegistrationOptions.ts | 4 ++-- .../src/registration/verifications/verifyAttestationAndroidKey.ts | 2 +- .../registration/verifications/verifyAttestationAndroidSafetyNet.ts | 2 +- .../server/src/registration/verifications/verifyAttestationApple.ts | 2 +- .../server/src/registration/verifications/verifyAttestationFIDOU2F.ts | 2 +- packages/server/src/registration/verifyRegistrationResponse.ts | 2 +- 12 files changed, 15 insertions(+), 15 deletions(-) (limited to 'packages/server/src/helpers/convertAAGUIDToString.ts') diff --git a/packages/server/src/authentication/generateAuthenticationOptions.test.ts b/packages/server/src/authentication/generateAuthenticationOptions.test.ts index fe365f2..a1a0588 100644 --- a/packages/server/src/authentication/generateAuthenticationOptions.test.ts +++ b/packages/server/src/authentication/generateAuthenticationOptions.test.ts @@ -1,6 +1,6 @@ jest.mock('../helpers/generateChallenge'); -import base64url from '../helpers/base64url'; +import * as base64url from '../helpers/base64url'; import { generateAuthenticationOptions } from './generateAuthenticationOptions'; diff --git a/packages/server/src/authentication/generateAuthenticationOptions.ts b/packages/server/src/authentication/generateAuthenticationOptions.ts index 4c841d9..69fec10 100644 --- a/packages/server/src/authentication/generateAuthenticationOptions.ts +++ b/packages/server/src/authentication/generateAuthenticationOptions.ts @@ -4,8 +4,8 @@ import type { PublicKeyCredentialDescriptorFuture, UserVerificationRequirement, } from '@simplewebauthn/typescript-types'; -import base64url from '../helpers/base64url' -import uint8Array from '../helpers/uint8Array'; +import * as base64url from '../helpers/base64url' +import * as uint8Array from '../helpers/uint8Array'; import { generateChallenge } from '../helpers/generateChallenge'; diff --git a/packages/server/src/authentication/verifyAuthenticationResponse.ts b/packages/server/src/authentication/verifyAuthenticationResponse.ts index 9200758..c89d39b 100644 --- a/packages/server/src/authentication/verifyAuthenticationResponse.ts +++ b/packages/server/src/authentication/verifyAuthenticationResponse.ts @@ -12,8 +12,8 @@ import { parseAuthenticatorData } from '../helpers/parseAuthenticatorData'; import { isBase64URLString } from '../helpers/isBase64URLString'; import { parseBackupFlags } from '../helpers/parseBackupFlags'; import { AuthenticationExtensionsAuthenticatorOutputs } from '../helpers/decodeAuthenticatorExtensions'; -import uint8Array from '../helpers/uint8Array'; -import base64url from '../helpers/base64url'; +import * as uint8Array from '../helpers/uint8Array'; +import * as base64url from '../helpers/base64url'; export type VerifyAuthenticationResponseOpts = { credential: AuthenticationCredentialJSON; diff --git a/packages/server/src/helpers/convertAAGUIDToString.ts b/packages/server/src/helpers/convertAAGUIDToString.ts index 80dc63c..0bb6c7d 100644 --- a/packages/server/src/helpers/convertAAGUIDToString.ts +++ b/packages/server/src/helpers/convertAAGUIDToString.ts @@ -1,4 +1,4 @@ -import uint8Array from './uint8Array'; +import * as uint8Array from './uint8Array'; /** * Convert the aaguid buffer in authData into a UUID string diff --git a/packages/server/src/helpers/convertCOSEtoPKCS.ts b/packages/server/src/helpers/convertCOSEtoPKCS.ts index 0db7853..70016df 100644 --- a/packages/server/src/helpers/convertCOSEtoPKCS.ts +++ b/packages/server/src/helpers/convertCOSEtoPKCS.ts @@ -1,6 +1,6 @@ import { COSEAlgorithmIdentifier } from '@simplewebauthn/typescript-types'; import { decodeCborFirst } from './decodeCbor'; -import uint8Array from './uint8array'; +import * as uint8Array from './uint8Array'; /** * Takes COSE-encoded public key and converts it to PKCS key diff --git a/packages/server/src/helpers/convertCertBufferToPEM.ts b/packages/server/src/helpers/convertCertBufferToPEM.ts index faf9001..b698773 100644 --- a/packages/server/src/helpers/convertCertBufferToPEM.ts +++ b/packages/server/src/helpers/convertCertBufferToPEM.ts @@ -1,7 +1,7 @@ import type { Base64URLString } from '@simplewebauthn/typescript-types'; import uint8Array from './uint8Array'; -import base64url from './base64url'; +import * as base64url from './base64url'; /** * Convert buffer to an OpenSSL-compatible PEM text format. diff --git a/packages/server/src/registration/generateRegistrationOptions.ts b/packages/server/src/registration/generateRegistrationOptions.ts index 6273246..6f74b74 100644 --- a/packages/server/src/registration/generateRegistrationOptions.ts +++ b/packages/server/src/registration/generateRegistrationOptions.ts @@ -9,8 +9,8 @@ import type { } from '@simplewebauthn/typescript-types'; import { generateChallenge } from '../helpers/generateChallenge'; -import base64url from '../helpers/base64url'; -import uint8Array from '../helpers/uint8Array'; +import * as base64url from '../helpers/base64url'; +import * as uint8Array from '../helpers/uint8Array'; export type GenerateRegistrationOptionsOpts = { rpName: string; diff --git a/packages/server/src/registration/verifications/verifyAttestationAndroidKey.ts b/packages/server/src/registration/verifications/verifyAttestationAndroidKey.ts index 6c89df8..ed0bb61 100644 --- a/packages/server/src/registration/verifications/verifyAttestationAndroidKey.ts +++ b/packages/server/src/registration/verifications/verifyAttestationAndroidKey.ts @@ -8,7 +8,7 @@ import { convertCertBufferToPEM } from '../../helpers/convertCertBufferToPEM'; import { validateCertificatePath } from '../../helpers/validateCertificatePath'; import { verifySignature } from '../../helpers/verifySignature'; import { COSEALGHASH, convertCOSEtoPKCS } from '../../helpers/convertCOSEtoPKCS'; -import uint8Array from '../../helpers/uint8Array'; +import * as uint8Array from '../../helpers/uint8Array'; import { MetadataService } from '../../services/metadataService'; import { verifyAttestationWithMetadata } from '../../metadata/verifyAttestationWithMetadata'; diff --git a/packages/server/src/registration/verifications/verifyAttestationAndroidSafetyNet.ts b/packages/server/src/registration/verifications/verifyAttestationAndroidSafetyNet.ts index 65e5c00..e40028b 100644 --- a/packages/server/src/registration/verifications/verifyAttestationAndroidSafetyNet.ts +++ b/packages/server/src/registration/verifications/verifyAttestationAndroidSafetyNet.ts @@ -7,7 +7,7 @@ import { verifySignature } from '../../helpers/verifySignature'; import { getCertificateInfo } from '../../helpers/getCertificateInfo'; import { validateCertificatePath } from '../../helpers/validateCertificatePath'; import { convertCertBufferToPEM } from '../../helpers/convertCertBufferToPEM'; -import uint8Array from '../../helpers/uint8Array'; +import * as uint8Array from '../../helpers/uint8Array'; import { MetadataService } from '../../services/metadataService'; import { verifyAttestationWithMetadata } from '../../metadata/verifyAttestationWithMetadata'; diff --git a/packages/server/src/registration/verifications/verifyAttestationApple.ts b/packages/server/src/registration/verifications/verifyAttestationApple.ts index fc12f9d..a54c465 100644 --- a/packages/server/src/registration/verifications/verifyAttestationApple.ts +++ b/packages/server/src/registration/verifications/verifyAttestationApple.ts @@ -7,7 +7,7 @@ import { validateCertificatePath } from '../../helpers/validateCertificatePath'; import { convertCertBufferToPEM } from '../../helpers/convertCertBufferToPEM'; import { toHash } from '../../helpers/toHash'; import { convertCOSEtoPKCS } from '../../helpers/convertCOSEtoPKCS'; -import uint8Array from '../../helpers/uint8Array'; +import * as uint8Array from '../../helpers/uint8Array'; export async function verifyAttestationApple( options: AttestationFormatVerifierOpts, diff --git a/packages/server/src/registration/verifications/verifyAttestationFIDOU2F.ts b/packages/server/src/registration/verifications/verifyAttestationFIDOU2F.ts index e73c22f..ea705d0 100644 --- a/packages/server/src/registration/verifications/verifyAttestationFIDOU2F.ts +++ b/packages/server/src/registration/verifications/verifyAttestationFIDOU2F.ts @@ -4,7 +4,7 @@ import { convertCOSEtoPKCS } from '../../helpers/convertCOSEtoPKCS'; import { convertCertBufferToPEM } from '../../helpers/convertCertBufferToPEM'; import { validateCertificatePath } from '../../helpers/validateCertificatePath'; import { verifySignature } from '../../helpers/verifySignature'; -import uint8Array from '../../helpers/uint8Array'; +import * as uint8Array from '../../helpers/uint8Array'; /** * Verify an attestation response with fmt 'fido-u2f' diff --git a/packages/server/src/registration/verifyRegistrationResponse.ts b/packages/server/src/registration/verifyRegistrationResponse.ts index 42870d1..87deb85 100644 --- a/packages/server/src/registration/verifyRegistrationResponse.ts +++ b/packages/server/src/registration/verifyRegistrationResponse.ts @@ -18,7 +18,7 @@ import { decodeCredentialPublicKey } from '../helpers/decodeCredentialPublicKey' import { COSEKEYS } from '../helpers/convertCOSEtoPKCS'; import { convertAAGUIDToString } from '../helpers/convertAAGUIDToString'; import { parseBackupFlags } from '../helpers/parseBackupFlags'; -import uint8Array from '../helpers/uint8Array'; +import * as uint8Array from '../helpers/uint8Array'; import { SettingsService } from '../services/settingsService'; import { supportedCOSEAlgorithmIdentifiers } from './generateRegistrationOptions'; -- cgit v1.2.3 From 6a4ced11ee7d1e872af4215f9aba82c7b64119c2 Mon Sep 17 00:00:00 2001 From: Matthew Miller Date: Sat, 12 Nov 2022 19:11:02 -0800 Subject: Rename isomorphic helpers to avoid collision? I think `helpers/uint8Array.ts` might conflict with CI's ability to resolve the global `Uint8Array` when I `import * as uint8Array`...just a hunch. I decided to rename all the helpers too to help keep them grouped together. --- .../generateAuthenticationOptions.ts | 4 +- .../authentication/verifyAuthenticationResponse.ts | 4 +- packages/server/src/helpers/base64url.ts | 62 -------------- packages/server/src/helpers/cbor.ts | 46 ----------- .../server/src/helpers/convertAAGUIDToString.ts | 2 +- packages/server/src/helpers/convertCOSEtoPKCS.ts | 4 +- .../server/src/helpers/convertCertBufferToPEM.ts | 2 +- .../server/src/helpers/convertPublicKeyToPEM.ts | 4 +- .../server/src/helpers/decodeAttestationObject.ts | 2 +- .../src/helpers/decodeAuthenticatorExtensions.ts | 2 +- .../server/src/helpers/decodeClientDataJSON.ts | 2 +- .../src/helpers/decodeCredentialPublicKey.ts | 2 +- packages/server/src/helpers/index.ts | 6 +- packages/server/src/helpers/isCertRevoked.ts | 2 +- packages/server/src/helpers/isoBase64URL.ts | 65 +++++++++++++++ packages/server/src/helpers/isoCBOR.ts | 49 +++++++++++ packages/server/src/helpers/isoUint8Array.ts | 94 ++++++++++++++++++++++ packages/server/src/helpers/matchExpectedRPID.ts | 2 +- .../server/src/helpers/parseAuthenticatorData.ts | 4 +- packages/server/src/helpers/uint8array.ts | 90 --------------------- packages/server/src/helpers/verifySignature.ts | 2 +- packages/server/src/metadata/parseJWT.ts | 2 +- .../registration/generateRegistrationOptions.ts | 4 +- .../verifications/tpm/parseCertInfo.ts | 2 +- .../registration/verifications/tpm/parsePubArea.ts | 2 +- .../verifications/tpm/verifyAttestationTPM.ts | 2 +- .../verifications/verifyAttestationAndroidKey.ts | 2 +- .../verifyAttestationAndroidSafetyNet.ts | 4 +- .../verifications/verifyAttestationApple.ts | 2 +- .../verifications/verifyAttestationFIDOU2F.ts | 2 +- .../verifications/verifyAttestationPacked.ts | 2 +- .../src/registration/verifyRegistrationResponse.ts | 4 +- 32 files changed, 244 insertions(+), 234 deletions(-) delete mode 100644 packages/server/src/helpers/base64url.ts delete mode 100644 packages/server/src/helpers/cbor.ts create mode 100644 packages/server/src/helpers/isoBase64URL.ts create mode 100644 packages/server/src/helpers/isoCBOR.ts create mode 100644 packages/server/src/helpers/isoUint8Array.ts delete mode 100644 packages/server/src/helpers/uint8array.ts (limited to 'packages/server/src/helpers/convertAAGUIDToString.ts') diff --git a/packages/server/src/authentication/generateAuthenticationOptions.ts b/packages/server/src/authentication/generateAuthenticationOptions.ts index 52060ba..a7c3a85 100644 --- a/packages/server/src/authentication/generateAuthenticationOptions.ts +++ b/packages/server/src/authentication/generateAuthenticationOptions.ts @@ -4,8 +4,8 @@ import type { PublicKeyCredentialDescriptorFuture, UserVerificationRequirement, } from '@simplewebauthn/typescript-types'; -import * as base64url from '../helpers/base64url' -import * as uint8Array from '../helpers/uint8Array'; +import * as isoBase64URL from '../helpers/isoBase64URL' +import * as isoUint8Array from '../helpers/isoUint8Array'; import { generateChallenge } from '../helpers/generateChallenge'; diff --git a/packages/server/src/authentication/verifyAuthenticationResponse.ts b/packages/server/src/authentication/verifyAuthenticationResponse.ts index 4d58078..dbec6b8 100644 --- a/packages/server/src/authentication/verifyAuthenticationResponse.ts +++ b/packages/server/src/authentication/verifyAuthenticationResponse.ts @@ -13,8 +13,8 @@ import { isBase64URLString } from '../helpers/isBase64URLString'; import { parseBackupFlags } from '../helpers/parseBackupFlags'; import { AuthenticationExtensionsAuthenticatorOutputs } from '../helpers/decodeAuthenticatorExtensions'; import { matchExpectedRPID } from '../helpers/matchExpectedRPID'; -import * as uint8Array from '../helpers/uint8Array'; -import * as base64url from '../helpers/base64url'; +import * as isoUint8Array from '../helpers/isoUint8Array'; +import * as isoBase64URL from '../helpers/isoBase64URL'; export type VerifyAuthenticationResponseOpts = { credential: AuthenticationCredentialJSON; diff --git a/packages/server/src/helpers/base64url.ts b/packages/server/src/helpers/base64url.ts deleted file mode 100644 index d03de51..0000000 --- a/packages/server/src/helpers/base64url.ts +++ /dev/null @@ -1,62 +0,0 @@ -import base64 from '@hexagon/base64'; - -/** - * Decode from a Base64URL-encoded string to an ArrayBuffer. Best used when converting a - * credential ID from a JSON string to an ArrayBuffer, like in allowCredentials or - * excludeCredentials. - * - * @param buffer Value to decode from base64 - * @param to (optional) The decoding to use, in case it's desirable to decode from base64 instead - */ -export function toBuffer(base64urlString: string, from: 'base64' | 'base64url' = 'base64url'): Uint8Array { - const _buffer = base64.toArrayBuffer(base64urlString, from === 'base64url'); - return new Uint8Array(_buffer); -} - -/** - * Encode the given array buffer into a Base64URL-encoded string. Ideal for converting various - * credential response ArrayBuffers to string for sending back to the server as JSON. - * - * @param buffer Value to encode to base64 - * @param to (optional) The encoding to use, in case it's desirable to encode to base64 instead - */ -export function fromBuffer(buffer: Uint8Array, to: 'base64' | 'base64url' = 'base64url'): string { - return base64.fromArrayBuffer(buffer, to === 'base64url'); -} - -/** - * Convert a base64url string into base64 - */ -export function toBase64(base64urlString: string): string { - const fromBase64Url = base64.toArrayBuffer(base64urlString, true); - const toBase64 = base64.fromArrayBuffer(fromBase64Url); - return toBase64; -} - -/** - * Encode a string to base64url - */ -export function fromString(ascii: string): string { - return base64.fromString(ascii, true); -} - -/** - * Decode a base64url string into its original string - */ -export function toString(base64urlString: string): string { - return base64.toString(base64urlString, true); -} - -/** - * Confirm that the string is encoded into base64 - */ -export function isBase64(input: string): boolean { - return base64.validate(input, false); -} - -/** - * Confirm that the string is encoded into base64url - */ -export function isBase64url(input: string): boolean { - return base64.validate(input, true); -} diff --git a/packages/server/src/helpers/cbor.ts b/packages/server/src/helpers/cbor.ts deleted file mode 100644 index 9f7cbd7..0000000 --- a/packages/server/src/helpers/cbor.ts +++ /dev/null @@ -1,46 +0,0 @@ -/* eslint-disable @typescript-eslint/ban-ts-comment */ -import * as cborx from 'cbor-x'; - -/** - * This encoder should keep CBOR data the same length when data is re-encoded - * - * MOST CRITICALLY, this means the following needs to be true of whatever CBOR library we use: - * - CBOR Map type values MUST decode to JavaScript Maps - * - CBOR tag 64 (uint8 Typed Array) MUST NOT be used when encoding Uint8Arrays back to CBOR - * - * So long as these requirements are maintained, then CBOR sequences can be encoded and decoded - * freely while maintaining their lengths for the most accurate pointer movement across them. - */ -const encoder = new cborx.Encoder({ mapsAsObjects: false, tagUint8Array: false }); - -/** - * Decode and return the first item in a sequence of CBOR-encoded values - * - * @param input The CBOR data to decode - * @param asObject (optional) Whether to convert any CBOR Maps into JavaScript Objects. Defaults to - * `false` - */ -export function decodeFirst(input: Uint8Array): Type { - const decoded = encoder.decodeMultiple(input) as undefined | Type[]; - - if (decoded === undefined) { - throw new Error('CBOR input data was empty'); - } - - /** - * Typing on `decoded` is `void | []` which causes TypeScript to think that it's an empty array, - * and thus you can't destructure it. I'm ignoring that because the code works fine in JS, and - * so this should be a valid operation. - */ - // @ts-ignore 2493 - const [first] = decoded; - - return first; -} - -/** - * Encode data to CBOR - */ -export function encode(input: any): Uint8Array { - return encoder.encode(input); -} diff --git a/packages/server/src/helpers/convertAAGUIDToString.ts b/packages/server/src/helpers/convertAAGUIDToString.ts index 0bb6c7d..8375099 100644 --- a/packages/server/src/helpers/convertAAGUIDToString.ts +++ b/packages/server/src/helpers/convertAAGUIDToString.ts @@ -1,4 +1,4 @@ -import * as uint8Array from './uint8Array'; +import * as isoUint8Array from './isoUint8Array'; /** * Convert the aaguid buffer in authData into a UUID string diff --git a/packages/server/src/helpers/convertCOSEtoPKCS.ts b/packages/server/src/helpers/convertCOSEtoPKCS.ts index 93140b6..0c1a8b5 100644 --- a/packages/server/src/helpers/convertCOSEtoPKCS.ts +++ b/packages/server/src/helpers/convertCOSEtoPKCS.ts @@ -1,6 +1,6 @@ import { COSEAlgorithmIdentifier } from '@simplewebauthn/typescript-types'; -import * as cbor from './cbor'; -import * as uint8Array from './uint8Array'; +import * as isoCBOR from './isoCBOR'; +import * as isoUint8Array from './isoUint8Array'; /** * Takes COSE-encoded public key and converts it to PKCS key diff --git a/packages/server/src/helpers/convertCertBufferToPEM.ts b/packages/server/src/helpers/convertCertBufferToPEM.ts index 111e481..4a0c674 100644 --- a/packages/server/src/helpers/convertCertBufferToPEM.ts +++ b/packages/server/src/helpers/convertCertBufferToPEM.ts @@ -1,6 +1,6 @@ import type { Base64URLString } from '@simplewebauthn/typescript-types'; -import * as base64url from './base64url'; +import * as isoBase64URL from './isoBase64URL'; /** * Convert buffer to an OpenSSL-compatible PEM text format. diff --git a/packages/server/src/helpers/convertPublicKeyToPEM.ts b/packages/server/src/helpers/convertPublicKeyToPEM.ts index ff8396b..74cc77f 100644 --- a/packages/server/src/helpers/convertPublicKeyToPEM.ts +++ b/packages/server/src/helpers/convertPublicKeyToPEM.ts @@ -1,8 +1,8 @@ import jwkToPem from 'jwk-to-pem'; import { COSEKEYS, COSEKTY, COSECRV, COSEPublicKey } from './convertCOSEtoPKCS'; -import * as cbor from './cbor'; -import * as base64url from './base64url'; +import * as isoCBOR from './isoCBOR'; +import * as isoBase64URL from './isoBase64URL'; export function convertPublicKeyToPEM(publicKey: Uint8Array): string { let struct; diff --git a/packages/server/src/helpers/decodeAttestationObject.ts b/packages/server/src/helpers/decodeAttestationObject.ts index 9d08e33..5c3a6d8 100644 --- a/packages/server/src/helpers/decodeAttestationObject.ts +++ b/packages/server/src/helpers/decodeAttestationObject.ts @@ -1,4 +1,4 @@ -import * as cbor from './cbor'; +import * as isoCBOR from './isoCBOR'; /** * Convert an AttestationObject buffer to a proper object diff --git a/packages/server/src/helpers/decodeAuthenticatorExtensions.ts b/packages/server/src/helpers/decodeAuthenticatorExtensions.ts index e6d0781..af38f1a 100644 --- a/packages/server/src/helpers/decodeAuthenticatorExtensions.ts +++ b/packages/server/src/helpers/decodeAuthenticatorExtensions.ts @@ -1,4 +1,4 @@ -import * as cbor from './cbor'; +import * as isoCBOR from './isoCBOR'; /** * Convert authenticator extension data buffer to a proper object diff --git a/packages/server/src/helpers/decodeClientDataJSON.ts b/packages/server/src/helpers/decodeClientDataJSON.ts index fff6344..82ca17b 100644 --- a/packages/server/src/helpers/decodeClientDataJSON.ts +++ b/packages/server/src/helpers/decodeClientDataJSON.ts @@ -1,4 +1,4 @@ -import * as base64url from "./base64url"; +import * as isoBase64URL from "./isoBase64URL"; /** * Decode an authenticator's base64url-encoded clientDataJSON to JSON diff --git a/packages/server/src/helpers/decodeCredentialPublicKey.ts b/packages/server/src/helpers/decodeCredentialPublicKey.ts index ec9ecd1..5891b03 100644 --- a/packages/server/src/helpers/decodeCredentialPublicKey.ts +++ b/packages/server/src/helpers/decodeCredentialPublicKey.ts @@ -1,5 +1,5 @@ import { COSEPublicKey } from './convertCOSEtoPKCS'; -import * as cbor from './cbor'; +import * as isoCBOR from './isoCBOR'; export function decodeCredentialPublicKey(publicKey: Uint8Array): COSEPublicKey { return cbor.decodeFirst(publicKey); diff --git a/packages/server/src/helpers/index.ts b/packages/server/src/helpers/index.ts index bf75f15..ff565bc 100644 --- a/packages/server/src/helpers/index.ts +++ b/packages/server/src/helpers/index.ts @@ -13,9 +13,9 @@ import { parseAuthenticatorData } from './parseAuthenticatorData'; import { toHash } from './toHash'; import { validateCertificatePath } from './validateCertificatePath'; import { verifySignature } from './verifySignature'; -import * as cbor from './cbor'; -import * as base64url from './base64url'; -import * as uint8Array from './uint8Array'; +import * as isoCBOR from './isoCBOR'; +import * as isoBase64URL from './isoBase64URL'; +import * as isoUint8Array from './isoUint8Array'; export { convertAAGUIDToString, diff --git a/packages/server/src/helpers/isCertRevoked.ts b/packages/server/src/helpers/isCertRevoked.ts index 5c5fa0f..2f5c3a5 100644 --- a/packages/server/src/helpers/isCertRevoked.ts +++ b/packages/server/src/helpers/isCertRevoked.ts @@ -4,7 +4,7 @@ import { AsnParser } from '@peculiar/asn1-schema'; import { CertificateList } from '@peculiar/asn1-x509'; import { convertCertBufferToPEM } from './convertCertBufferToPEM'; -import * as uint8Array from './uint8Array'; +import * as isoUint8Array from './isoUint8Array'; /** * A cache of revoked cert serial numbers by Authority Key ID diff --git a/packages/server/src/helpers/isoBase64URL.ts b/packages/server/src/helpers/isoBase64URL.ts new file mode 100644 index 0000000..cb8c71d --- /dev/null +++ b/packages/server/src/helpers/isoBase64URL.ts @@ -0,0 +1,65 @@ +/** + * A collection of isomorphic methods for working with base64url and base64 values + */ +import base64 from '@hexagon/base64'; + +/** + * Decode from a Base64URL-encoded string to an ArrayBuffer. Best used when converting a + * credential ID from a JSON string to an ArrayBuffer, like in allowCredentials or + * excludeCredentials. + * + * @param buffer Value to decode from base64 + * @param to (optional) The decoding to use, in case it's desirable to decode from base64 instead + */ +export function toBuffer(base64urlString: string, from: 'base64' | 'base64url' = 'base64url'): Uint8Array { + const _buffer = base64.toArrayBuffer(base64urlString, from === 'base64url'); + return new Uint8Array(_buffer); +} + +/** + * Encode the given array buffer into a Base64URL-encoded string. Ideal for converting various + * credential response ArrayBuffers to string for sending back to the server as JSON. + * + * @param buffer Value to encode to base64 + * @param to (optional) The encoding to use, in case it's desirable to encode to base64 instead + */ +export function fromBuffer(buffer: Uint8Array, to: 'base64' | 'base64url' = 'base64url'): string { + return base64.fromArrayBuffer(buffer, to === 'base64url'); +} + +/** + * Convert a base64url string into base64 + */ +export function toBase64(base64urlString: string): string { + const fromBase64Url = base64.toArrayBuffer(base64urlString, true); + const toBase64 = base64.fromArrayBuffer(fromBase64Url); + return toBase64; +} + +/** + * Encode a string to base64url + */ +export function fromString(ascii: string): string { + return base64.fromString(ascii, true); +} + +/** + * Decode a base64url string into its original string + */ +export function toString(base64urlString: string): string { + return base64.toString(base64urlString, true); +} + +/** + * Confirm that the string is encoded into base64 + */ +export function isBase64(input: string): boolean { + return base64.validate(input, false); +} + +/** + * Confirm that the string is encoded into base64url + */ +export function isBase64url(input: string): boolean { + return base64.validate(input, true); +} diff --git a/packages/server/src/helpers/isoCBOR.ts b/packages/server/src/helpers/isoCBOR.ts new file mode 100644 index 0000000..9093ffd --- /dev/null +++ b/packages/server/src/helpers/isoCBOR.ts @@ -0,0 +1,49 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ +/** + * A collection of isomorphic methods for working with CBOR values + */ +import * as cborx from 'cbor-x'; + +/** + * This encoder should keep CBOR data the same length when data is re-encoded + * + * MOST CRITICALLY, this means the following needs to be true of whatever CBOR library we use: + * - CBOR Map type values MUST decode to JavaScript Maps + * - CBOR tag 64 (uint8 Typed Array) MUST NOT be used when encoding Uint8Arrays back to CBOR + * + * So long as these requirements are maintained, then CBOR sequences can be encoded and decoded + * freely while maintaining their lengths for the most accurate pointer movement across them. + */ +const encoder = new cborx.Encoder({ mapsAsObjects: false, tagUint8Array: false }); + +/** + * Decode and return the first item in a sequence of CBOR-encoded values + * + * @param input The CBOR data to decode + * @param asObject (optional) Whether to convert any CBOR Maps into JavaScript Objects. Defaults to + * `false` + */ +export function decodeFirst(input: Uint8Array): Type { + const decoded = encoder.decodeMultiple(input) as undefined | Type[]; + + if (decoded === undefined) { + throw new Error('CBOR input data was empty'); + } + + /** + * Typing on `decoded` is `void | []` which causes TypeScript to think that it's an empty array, + * and thus you can't destructure it. I'm ignoring that because the code works fine in JS, and + * so this should be a valid operation. + */ + // @ts-ignore 2493 + const [first] = decoded; + + return first; +} + +/** + * Encode data to CBOR + */ +export function encode(input: any): Uint8Array { + return encoder.encode(input); +} diff --git a/packages/server/src/helpers/isoUint8Array.ts b/packages/server/src/helpers/isoUint8Array.ts new file mode 100644 index 0000000..0834bb2 --- /dev/null +++ b/packages/server/src/helpers/isoUint8Array.ts @@ -0,0 +1,94 @@ +/** + * A collection of isomorphic methods for working with Uint8Array values + */ + +/** + * Make sure two Uint8Arrays are deeply equivalent + */ +export function areEqual(array1: Uint8Array, array2: Uint8Array): boolean { + if (array1.length != array2.length) { + return false; + } + + return array1.every((val, i) => val === array2[i]); +} + +/** + * Convert a Uint8Array to Hexadecimal. + * + * A replacement for `Buffer.toString('hex')` + */ +export function toHex(array: Uint8Array): string { + const hexParts = Array.from(array, i => i.toString(16).padStart(2, "0")); + + // adce000235bcc60a648b0b25f1f05503 + return hexParts.join(''); +} + +/** + * Convert a hexadecimal string to Uint8Array. + * + * A replacement for `Buffer.from('...', 'hex')` + */ +export function fromHex(hex: string): Uint8Array { + if (!hex) { + return Uint8Array.from([]); + } + + const isValid = hex.length !== 0 && hex.length % 2 === 0 && !/[^a-fA-F0-9]/u.test(hex); + + if (!isValid) { + throw new Error('Invalid hex string'); + } + + const byteStrings = hex.match(/.{1,2}/g) ?? []; + + return Uint8Array.from(byteStrings.map((byte) => parseInt(byte, 16))); +} + +/** + * Combine multiple Uint8Arrays into a single Uint8Array + */ +export function concat(arrays: Uint8Array[]): Uint8Array { + let pointer = 0; + const totalLength = arrays.reduce((prev, curr) => prev + curr.length, 0); + + const toReturn = new Uint8Array(totalLength); + + arrays.forEach((arr) => { + toReturn.set(arr, pointer); + pointer += arr.length; + }); + + return toReturn; +} + +/** + * Convert bytes into a UTF-8 string + */ +export function toUTF8String(array: Uint8Array): string { + const decoder = new globalThis.TextDecoder("utf-8"); + return decoder.decode(array); +} + +/** + * Convert a UTF-8 string back into bytes + */ +export function fromUTF8String(utf8String: string): Uint8Array { + const encoder = new globalThis.TextEncoder(); + return encoder.encode(utf8String); +} + +/** + * Convert an ASCII string to Uint8Array + */ +export function fromASCIIString(value: string): Uint8Array { + return Uint8Array.from(value.split("").map(x => x.charCodeAt(0))); +} + +/** + * Prepare a DataView we can slice our way around in as we parse the bytes in a Uint8Array + */ +export function toDataView(array: Uint8Array): DataView { + return new DataView(array.buffer, array.byteOffset, array.length); +} diff --git a/packages/server/src/helpers/matchExpectedRPID.ts b/packages/server/src/helpers/matchExpectedRPID.ts index ef5b4a9..aa96fee 100644 --- a/packages/server/src/helpers/matchExpectedRPID.ts +++ b/packages/server/src/helpers/matchExpectedRPID.ts @@ -1,5 +1,5 @@ import { toHash } from './toHash'; -import * as uint8Array from './uint8Array'; +import * as isoUint8Array from './isoUint8Array'; /** * Go through each expected RP ID and try to find one that matches. Raises an Error if no diff --git a/packages/server/src/helpers/parseAuthenticatorData.ts b/packages/server/src/helpers/parseAuthenticatorData.ts index 03bfca0..f45e717 100644 --- a/packages/server/src/helpers/parseAuthenticatorData.ts +++ b/packages/server/src/helpers/parseAuthenticatorData.ts @@ -1,9 +1,9 @@ -import * as cbor from './cbor'; +import * as isoCBOR from './isoCBOR'; import { decodeAuthenticatorExtensions, AuthenticationExtensionsAuthenticatorOutputs, } from './decodeAuthenticatorExtensions'; -import * as uint8Array from './uint8Array'; +import * as isoUint8Array from './isoUint8Array'; import { COSEPublicKey } from './convertCOSEtoPKCS'; /** diff --git a/packages/server/src/helpers/uint8array.ts b/packages/server/src/helpers/uint8array.ts deleted file mode 100644 index 92a53ca..0000000 --- a/packages/server/src/helpers/uint8array.ts +++ /dev/null @@ -1,90 +0,0 @@ -/** - * Make sure two Uint8Arrays are deeply equivalent - */ -export function areEqual(array1: Uint8Array, array2: Uint8Array): boolean { - if (array1.length != array2.length) { - return false; - } - - return array1.every((val, i) => val === array2[i]); -} - -/** - * Convert a Uint8Array to Hexadecimal. - * - * A replacement for `Buffer.toString('hex')` - */ -export function toHex(array: Uint8Array): string { - const hexParts = Array.from(array, i => i.toString(16).padStart(2, "0")); - - // adce000235bcc60a648b0b25f1f05503 - return hexParts.join(''); -} - -/** - * Convert a hexadecimal string to Uint8Array. - * - * A replacement for `Buffer.from('...', 'hex')` - */ -export function fromHex(hex: string): Uint8Array { - if (!hex) { - return Uint8Array.from([]); - } - - const isValid = hex.length !== 0 && hex.length % 2 === 0 && !/[^a-fA-F0-9]/u.test(hex); - - if (!isValid) { - throw new Error('Invalid hex string'); - } - - const byteStrings = hex.match(/.{1,2}/g) ?? []; - - return Uint8Array.from(byteStrings.map((byte) => parseInt(byte, 16))); -} - -/** - * Combine multiple Uint8Arrays into a single Uint8Array - */ -export function concat(arrays: Uint8Array[]): Uint8Array { - let pointer = 0; - const totalLength = arrays.reduce((prev, curr) => prev + curr.length, 0); - - const toReturn = new Uint8Array(totalLength); - - arrays.forEach((arr) => { - toReturn.set(arr, pointer); - pointer += arr.length; - }); - - return toReturn; -} - -/** - * Convert bytes into a UTF-8 string - */ -export function toUTF8String(array: Uint8Array): string { - const decoder = new globalThis.TextDecoder("utf-8"); - return decoder.decode(array); -} - -/** - * Convert a UTF-8 string back into bytes - */ -export function fromUTF8String(utf8String: string): Uint8Array { - const encoder = new globalThis.TextEncoder(); - return encoder.encode(utf8String); -} - -/** - * Convert an ASCII string to Uint8Array - */ -export function fromASCIIString(value: string): Uint8Array { - return Uint8Array.from(value.split("").map(x => x.charCodeAt(0))); -} - -/** - * Prepare a DataView we can slice our way around in as we parse the bytes in a Uint8Array - */ -export function toDataView(array: Uint8Array): DataView { - return new DataView(array.buffer, array.byteOffset, array.length); -} diff --git a/packages/server/src/helpers/verifySignature.ts b/packages/server/src/helpers/verifySignature.ts index 97bef50..42a167e 100644 --- a/packages/server/src/helpers/verifySignature.ts +++ b/packages/server/src/helpers/verifySignature.ts @@ -4,7 +4,7 @@ import { verify as ed25519Verify } from '@noble/ed25519'; import { COSEKEYS, COSEKTY, COSEPublicKey } from './convertCOSEtoPKCS'; import { convertCertBufferToPEM } from './convertCertBufferToPEM'; import { convertPublicKeyToPEM } from './convertPublicKeyToPEM'; -import * as cbor from './cbor'; +import * as isoCBOR from './isoCBOR'; type VerifySignatureOptsLeafCert = { signature: Uint8Array; diff --git a/packages/server/src/metadata/parseJWT.ts b/packages/server/src/metadata/parseJWT.ts index 72a83dd..879134d 100644 --- a/packages/server/src/metadata/parseJWT.ts +++ b/packages/server/src/metadata/parseJWT.ts @@ -1,4 +1,4 @@ -import * as base64url from "../helpers/base64url"; +import * as isoBase64URL from "../helpers/isoBase64URL"; /** * Process a JWT into Javascript-friendly data structures diff --git a/packages/server/src/registration/generateRegistrationOptions.ts b/packages/server/src/registration/generateRegistrationOptions.ts index 1c67486..1497ec6 100644 --- a/packages/server/src/registration/generateRegistrationOptions.ts +++ b/packages/server/src/registration/generateRegistrationOptions.ts @@ -9,8 +9,8 @@ import type { } from '@simplewebauthn/typescript-types'; import { generateChallenge } from '../helpers/generateChallenge'; -import * as base64url from '../helpers/base64url'; -import * as uint8Array from '../helpers/uint8Array'; +import * as isoBase64URL from '../helpers/isoBase64URL'; +import * as isoUint8Array from '../helpers/isoUint8Array'; export type GenerateRegistrationOptionsOpts = { rpName: string; diff --git a/packages/server/src/registration/verifications/tpm/parseCertInfo.ts b/packages/server/src/registration/verifications/tpm/parseCertInfo.ts index c590794..d36f6f6 100644 --- a/packages/server/src/registration/verifications/tpm/parseCertInfo.ts +++ b/packages/server/src/registration/verifications/tpm/parseCertInfo.ts @@ -1,5 +1,5 @@ import { TPM_ST, TPM_ALG } from './constants'; -import * as uint8Array from '../../../helpers/uint8Array'; +import * as isoUint8Array from '../../../helpers/isoUint8Array'; /** * Cut up a TPM attestation's certInfo into intelligible chunks diff --git a/packages/server/src/registration/verifications/tpm/parsePubArea.ts b/packages/server/src/registration/verifications/tpm/parsePubArea.ts index 1476ea4..593b9a4 100644 --- a/packages/server/src/registration/verifications/tpm/parsePubArea.ts +++ b/packages/server/src/registration/verifications/tpm/parsePubArea.ts @@ -1,5 +1,5 @@ import { TPM_ALG, TPM_ECC_CURVE } from './constants'; -import * as uint8Array from '../../../helpers/uint8Array'; +import * as isoUint8Array from '../../../helpers/isoUint8Array'; /** * Break apart a TPM attestation's pubArea buffer diff --git a/packages/server/src/registration/verifications/tpm/verifyAttestationTPM.ts b/packages/server/src/registration/verifications/tpm/verifyAttestationTPM.ts index aa21bf2..844b97b 100644 --- a/packages/server/src/registration/verifications/tpm/verifyAttestationTPM.ts +++ b/packages/server/src/registration/verifications/tpm/verifyAttestationTPM.ts @@ -17,7 +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 * as isoUint8Array from '../../../helpers/isoUint8Array'; import { MetadataService } from '../../../services/metadataService'; import { verifyAttestationWithMetadata } from '../../../metadata/verifyAttestationWithMetadata'; diff --git a/packages/server/src/registration/verifications/verifyAttestationAndroidKey.ts b/packages/server/src/registration/verifications/verifyAttestationAndroidKey.ts index e7ae6de..0c41061 100644 --- a/packages/server/src/registration/verifications/verifyAttestationAndroidKey.ts +++ b/packages/server/src/registration/verifications/verifyAttestationAndroidKey.ts @@ -8,7 +8,7 @@ import { convertCertBufferToPEM } from '../../helpers/convertCertBufferToPEM'; import { validateCertificatePath } from '../../helpers/validateCertificatePath'; import { verifySignature } from '../../helpers/verifySignature'; import { COSEALGHASH, convertCOSEtoPKCS } from '../../helpers/convertCOSEtoPKCS'; -import * as uint8Array from '../../helpers/uint8Array'; +import * as isoUint8Array from '../../helpers/isoUint8Array'; import { MetadataService } from '../../services/metadataService'; import { verifyAttestationWithMetadata } from '../../metadata/verifyAttestationWithMetadata'; diff --git a/packages/server/src/registration/verifications/verifyAttestationAndroidSafetyNet.ts b/packages/server/src/registration/verifications/verifyAttestationAndroidSafetyNet.ts index e94e267..3879f40 100644 --- a/packages/server/src/registration/verifications/verifyAttestationAndroidSafetyNet.ts +++ b/packages/server/src/registration/verifications/verifyAttestationAndroidSafetyNet.ts @@ -5,8 +5,8 @@ import { verifySignature } from '../../helpers/verifySignature'; import { getCertificateInfo } from '../../helpers/getCertificateInfo'; import { validateCertificatePath } from '../../helpers/validateCertificatePath'; import { convertCertBufferToPEM } from '../../helpers/convertCertBufferToPEM'; -import * as uint8Array from '../../helpers/uint8Array'; -import * as base64url from '../../helpers/base64url'; +import * as isoUint8Array from '../../helpers/isoUint8Array'; +import * as isoBase64URL from '../../helpers/isoBase64URL'; import { MetadataService } from '../../services/metadataService'; import { verifyAttestationWithMetadata } from '../../metadata/verifyAttestationWithMetadata'; diff --git a/packages/server/src/registration/verifications/verifyAttestationApple.ts b/packages/server/src/registration/verifications/verifyAttestationApple.ts index 50b68da..d8f5465 100644 --- a/packages/server/src/registration/verifications/verifyAttestationApple.ts +++ b/packages/server/src/registration/verifications/verifyAttestationApple.ts @@ -7,7 +7,7 @@ import { validateCertificatePath } from '../../helpers/validateCertificatePath'; import { convertCertBufferToPEM } from '../../helpers/convertCertBufferToPEM'; import { toHash } from '../../helpers/toHash'; import { convertCOSEtoPKCS } from '../../helpers/convertCOSEtoPKCS'; -import * as uint8Array from '../../helpers/uint8Array'; +import * as isoUint8Array from '../../helpers/isoUint8Array'; export async function verifyAttestationApple( options: AttestationFormatVerifierOpts, diff --git a/packages/server/src/registration/verifications/verifyAttestationFIDOU2F.ts b/packages/server/src/registration/verifications/verifyAttestationFIDOU2F.ts index ef6aa7c..7e57ab5 100644 --- a/packages/server/src/registration/verifications/verifyAttestationFIDOU2F.ts +++ b/packages/server/src/registration/verifications/verifyAttestationFIDOU2F.ts @@ -4,7 +4,7 @@ import { convertCOSEtoPKCS } from '../../helpers/convertCOSEtoPKCS'; import { convertCertBufferToPEM } from '../../helpers/convertCertBufferToPEM'; import { validateCertificatePath } from '../../helpers/validateCertificatePath'; import { verifySignature } from '../../helpers/verifySignature'; -import * as uint8Array from '../../helpers/uint8Array'; +import * as isoUint8Array from '../../helpers/isoUint8Array'; /** * Verify an attestation response with fmt 'fido-u2f' diff --git a/packages/server/src/registration/verifications/verifyAttestationPacked.ts b/packages/server/src/registration/verifications/verifyAttestationPacked.ts index c5db840..4250595 100644 --- a/packages/server/src/registration/verifications/verifyAttestationPacked.ts +++ b/packages/server/src/registration/verifications/verifyAttestationPacked.ts @@ -5,7 +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 * as isoUint8Array from '../../helpers/isoUint8Array'; import { MetadataService } from '../../services/metadataService'; import { verifyAttestationWithMetadata } from '../../metadata/verifyAttestationWithMetadata'; diff --git a/packages/server/src/registration/verifyRegistrationResponse.ts b/packages/server/src/registration/verifyRegistrationResponse.ts index 06253ad..6ed3ca9 100644 --- a/packages/server/src/registration/verifyRegistrationResponse.ts +++ b/packages/server/src/registration/verifyRegistrationResponse.ts @@ -18,8 +18,8 @@ import { COSEKEYS } from '../helpers/convertCOSEtoPKCS'; import { convertAAGUIDToString } from '../helpers/convertAAGUIDToString'; import { parseBackupFlags } from '../helpers/parseBackupFlags'; import { matchExpectedRPID } from '../helpers/matchExpectedRPID'; -import * as uint8Array from '../helpers/uint8Array'; -import * as base64url from '../helpers/base64url'; +import * as isoUint8Array from '../helpers/isoUint8Array'; +import * as isoBase64URL from '../helpers/isoBase64URL'; import { SettingsService } from '../services/settingsService'; import { supportedCOSEAlgorithmIdentifiers } from './generateRegistrationOptions'; -- cgit v1.2.3 From 6e3db9655c6aa2aef590c66295df500271bfdd17 Mon Sep 17 00:00:00 2001 From: Matthew Miller Date: Sat, 12 Nov 2022 19:51:29 -0800 Subject: Figure out a better pattern for exposing iso funcs --- .../generateAuthenticationOptions.test.ts | 4 +- .../generateAuthenticationOptions.ts | 9 +-- .../verifyAuthenticationResponse.test.ts | 43 +++++----- .../authentication/verifyAuthenticationResponse.ts | 11 ++- .../server/src/helpers/convertAAGUIDToString.ts | 4 +- .../server/src/helpers/convertCOSEtoPKCS.test.ts | 6 +- packages/server/src/helpers/convertCOSEtoPKCS.ts | 10 +-- .../server/src/helpers/convertCertBufferToPEM.ts | 10 +-- .../src/helpers/convertPublicKeyToPEM.test.ts | 15 ++-- .../server/src/helpers/convertPublicKeyToPEM.ts | 13 ++- .../server/src/helpers/decodeAttestationObject.ts | 4 +- .../helpers/decodeAuthenticatorExtensions.test.ts | 14 ++-- .../src/helpers/decodeAuthenticatorExtensions.ts | 4 +- .../server/src/helpers/decodeClientDataJSON.ts | 4 +- .../src/helpers/decodeCredentialPublicKey.ts | 4 +- packages/server/src/helpers/index.ts | 10 +-- packages/server/src/helpers/isCertRevoked.ts | 6 +- packages/server/src/helpers/iso/index.ts | 10 +++ packages/server/src/helpers/iso/isoBase64URL.ts | 62 ++++++++++++++ packages/server/src/helpers/iso/isoCBOR.ts | 46 +++++++++++ packages/server/src/helpers/iso/isoUint8Array.ts | 90 +++++++++++++++++++++ packages/server/src/helpers/isoBase64URL.ts | 65 --------------- packages/server/src/helpers/isoCBOR.ts | 49 ----------- packages/server/src/helpers/isoUint8Array.ts | 94 ---------------------- packages/server/src/helpers/matchExpectedRPID.ts | 6 +- .../src/helpers/parseAuthenticatorData.test.ts | 10 +-- .../server/src/helpers/parseAuthenticatorData.ts | 13 ++- packages/server/src/helpers/verifySignature.ts | 4 +- packages/server/src/metadata/parseJWT.ts | 6 +- .../metadata/verifyAttestationWithMetadata.test.ts | 8 +- .../registration/generateRegistrationOptions.ts | 9 +-- .../verifications/tpm/parseCertInfo.ts | 6 +- .../registration/verifications/tpm/parsePubArea.ts | 6 +- .../verifications/tpm/verifyAttestationTPM.test.ts | 14 ++-- .../verifications/tpm/verifyAttestationTPM.ts | 14 ++-- .../verifyAttestationAndroidKey.test.ts | 4 +- .../verifications/verifyAttestationAndroidKey.ts | 8 +- .../verifyAttestationAndroidSafetyNet.test.ts | 10 +-- .../verifyAttestationAndroidSafetyNet.ts | 19 +++-- .../verifications/verifyAttestationApple.ts | 8 +- .../verifications/verifyAttestationFIDOU2F.ts | 6 +- .../verifications/verifyAttestationPacked.ts | 4 +- .../verifyRegistrationResponse.test.ts | 61 +++++++------- .../src/registration/verifyRegistrationResponse.ts | 7 +- 44 files changed, 399 insertions(+), 411 deletions(-) create mode 100644 packages/server/src/helpers/iso/index.ts create mode 100644 packages/server/src/helpers/iso/isoBase64URL.ts create mode 100644 packages/server/src/helpers/iso/isoCBOR.ts create mode 100644 packages/server/src/helpers/iso/isoUint8Array.ts delete mode 100644 packages/server/src/helpers/isoBase64URL.ts delete mode 100644 packages/server/src/helpers/isoCBOR.ts delete mode 100644 packages/server/src/helpers/isoUint8Array.ts (limited to 'packages/server/src/helpers/convertAAGUIDToString.ts') diff --git a/packages/server/src/authentication/generateAuthenticationOptions.test.ts b/packages/server/src/authentication/generateAuthenticationOptions.test.ts index a1a0588..65dc66e 100644 --- a/packages/server/src/authentication/generateAuthenticationOptions.test.ts +++ b/packages/server/src/authentication/generateAuthenticationOptions.test.ts @@ -1,11 +1,11 @@ jest.mock('../helpers/generateChallenge'); -import * as base64url from '../helpers/base64url'; +import { isoBase64URL } from '../helpers/iso'; import { generateAuthenticationOptions } from './generateAuthenticationOptions'; const challengeString = 'dG90YWxseXJhbmRvbXZhbHVl'; -const challengeBuffer = base64url.toBuffer(challengeString) +const challengeBuffer = isoBase64URL.toBuffer(challengeString) test('should generate credential request options suitable for sending via JSON', () => { diff --git a/packages/server/src/authentication/generateAuthenticationOptions.ts b/packages/server/src/authentication/generateAuthenticationOptions.ts index a7c3a85..3847b39 100644 --- a/packages/server/src/authentication/generateAuthenticationOptions.ts +++ b/packages/server/src/authentication/generateAuthenticationOptions.ts @@ -4,9 +4,8 @@ import type { PublicKeyCredentialDescriptorFuture, UserVerificationRequirement, } from '@simplewebauthn/typescript-types'; -import * as isoBase64URL from '../helpers/isoBase64URL' -import * as isoUint8Array from '../helpers/isoUint8Array'; +import { isoBase64URL, isoUint8Array } from '../helpers/iso' import { generateChallenge } from '../helpers/generateChallenge'; export type GenerateAuthenticationOptionsOpts = { @@ -48,14 +47,14 @@ export function generateAuthenticationOptions( */ let _challenge = challenge; if (typeof _challenge === 'string') { - _challenge = uint8Array.fromUTF8String(_challenge); + _challenge = isoUint8Array.fromUTF8String(_challenge); } return { - challenge: base64url.fromBuffer(_challenge), + challenge: isoBase64URL.fromBuffer(_challenge), allowCredentials: allowCredentials?.map(cred => ({ ...cred, - id: base64url.fromBuffer(cred.id as Uint8Array), + id: isoBase64URL.fromBuffer(cred.id as Uint8Array), })), timeout, userVerification, diff --git a/packages/server/src/authentication/verifyAuthenticationResponse.test.ts b/packages/server/src/authentication/verifyAuthenticationResponse.test.ts index 58e24b8..6b89bd0 100644 --- a/packages/server/src/authentication/verifyAuthenticationResponse.test.ts +++ b/packages/server/src/authentication/verifyAuthenticationResponse.test.ts @@ -7,8 +7,7 @@ import { AuthenticatorDevice, AuthenticationCredentialJSON, } from '@simplewebauthn/typescript-types'; -import * as uint8Array from '../helpers/uint8array'; -import * as base64url from '../helpers/base64url'; +import { isoUint8Array, isoBase64URL } from '../helpers/iso'; let mockDecodeClientData: jest.SpyInstance; let mockParseAuthData: jest.SpyInstance; @@ -158,7 +157,7 @@ test('should not compare counters if both are 0', async () => { test('should throw an error if user verification is required but user was not verified', async () => { const actualData = esmParseAuthenticatorData.parseAuthenticatorData( - base64url.toBuffer(assertionResponse.response.authenticatorData), + isoBase64URL.toBuffer(assertionResponse.response.authenticatorData), ); mockParseAuthData.mockReturnValue({ @@ -184,7 +183,7 @@ test('should throw an error if user verification is required but user was not ve // TODO: Get a real TPM authentication response in here test.skip('should verify TPM assertion', async () => { const expectedChallenge = 'dG90YWxseVVuaXF1ZVZhbHVlRXZlcnlBc3NlcnRpb24'; - jest.spyOn(base64url, 'toString').mockReturnValueOnce(expectedChallenge); + jest.spyOn(isoBase64URL, 'toString').mockReturnValueOnce(expectedChallenge); const verification = await verifyAuthenticationResponse({ credential: { id: 'YJ8FMM-AmcUt73XPX341WXWd7ypBMylGjjhu0g3VzME', @@ -204,8 +203,8 @@ test.skip('should verify TPM assertion', async () => { expectedOrigin: assertionOrigin, expectedRPID: 'dev.dontneeda.pw', authenticator: { - credentialPublicKey: base64url.toBuffer('BAEAAQ'), - credentialID: base64url.toBuffer('YJ8FMM-AmcUt73XPX341WXWd7ypBMylGjjhu0g3VzME'), + credentialPublicKey: isoBase64URL.toBuffer('BAEAAQ'), + credentialID: isoBase64URL.toBuffer('YJ8FMM-AmcUt73XPX341WXWd7ypBMylGjjhu0g3VzME'), counter: 0, }, }); @@ -280,17 +279,17 @@ test('should pass verification if custom challenge verifier returns true', async }, expectedChallenge: (challenge: string) => { const parsedChallenge: { actualChallenge: string; arbitraryData: string } = JSON.parse( - base64url.toString(challenge), + isoBase64URL.toString(challenge), ); return parsedChallenge.actualChallenge === 'K3QxOjnVJLiGlnVEp5va5QJeMVWNf_7PYgutgbAtAUA'; }, expectedOrigin: 'http://localhost:8000', expectedRPID: 'localhost', authenticator: { - credentialID: base64url.toBuffer( + credentialID: isoBase64URL.toBuffer( 'AaIBxnYfL2pDWJmIii6CYgHBruhVvFGHheWamphVioG_TnEXxKA9MW4FWnJh21zsbmRpRJso9i2JmAtWOtXfVd4oXTgYVusXwhWWsA', ), - credentialPublicKey: base64url.toBuffer( + credentialPublicKey: isoBase64URL.toBuffer( 'pQECAyYgASFYILTrxTUQv3X4DRM6L_pk65FSMebenhCx3RMsTKoBm-AxIlggEf3qk5552QLNSh1T1oQs7_2C2qysDwN4r4fCp52Hsqs', ), counter: 0, @@ -333,10 +332,10 @@ test('should return authenticator extension output', async () => { expectedRPID: 'try-webauthn.appspot.com', expectedChallenge: 'iZsVCztrDW7D2U_GHCIlYKLwV2bCsBTRqVQUnJXn9Tk', authenticator: { - credentialID: base64url.toBuffer( + credentialID: isoBase64URL.toBuffer( 'AaIBxnYfL2pDWJmIii6CYgHBruhVvFGHheWamphVioG_TnEXxKA9MW4FWnJh21zsbmRpRJso9i2JmAtWOtXfVd4oXTgYVusXwhWWsA', ), - credentialPublicKey: base64url.toBuffer( + credentialPublicKey: isoBase64URL.toBuffer( 'pQECAyYgASFYILTrxTUQv3X4DRM6L_pk65FSMebenhCx3RMsTKoBm-AxIlggEf3qk5552QLNSh1T1oQs7_2C2qysDwN4r4fCp52Hsqs', ), counter: 0, @@ -345,15 +344,15 @@ test('should return authenticator extension output', async () => { expect(verification.authenticationInfo?.authenticatorExtensionResults).toMatchObject({ devicePubKey: { - dpk: uint8Array.fromHex( + dpk: isoUint8Array.fromHex( 'A5010203262001215820991AABED9DE4271A9EDEAD8806F9DC96D6DCCD0C476253A5510489EC8379BE5B225820A0973CFDEDBB79E27FEF4EE7481673FB3312504DDCA5434CFD23431D6AD29EDA', ), - sig: uint8Array.fromHex( + sig: isoUint8Array.fromHex( '3045022049526CD28AEF6B4E621A7D5936D2B504952FC0AE2313A4F0357AAFFFAEA964740221009D513ACAEFB0B32C765AAE6FEBA8C294685EFF63FF1CBF11ECF2107AF4FEB8F8', ), - nonce: uint8Array.fromHex(''), - scope: uint8Array.fromHex('00'), - aaguid: uint8Array.fromHex('B93FD961F2E6462FB12282002247DE78'), + nonce: isoUint8Array.fromHex(''), + scope: isoUint8Array.fromHex('00'), + aaguid: isoUint8Array.fromHex('B93FD961F2E6462FB12282002247DE78'), }, }); }); @@ -391,14 +390,14 @@ const assertionResponse: AuthenticationCredentialJSON = { clientExtensionResults: {}, type: 'public-key', }; -const assertionChallenge = base64url.fromString('totallyUniqueValueEveryTime'); +const assertionChallenge = isoBase64URL.fromString('totallyUniqueValueEveryTime'); const assertionOrigin = 'https://dev.dontneeda.pw'; const authenticator: AuthenticatorDevice = { - credentialPublicKey: base64url.toBuffer( + credentialPublicKey: isoBase64URL.toBuffer( 'pQECAyYgASFYIIheFp-u6GvFT2LNGovf3ZrT0iFVBsA_76rRysxRG9A1Ilgg8WGeA6hPmnab0HAViUYVRkwTNcN77QBf_RR0dv3lIvQ', ), - credentialID: base64url.toBuffer( + credentialID: isoBase64URL.toBuffer( 'KEbWNCc7NgaYnUyrNeFGX9_3Y-8oJ3KwzjnaiD1d1LVTxR7v3CaKfCz2Vy_g_MHSh7yJ8yL0Pxg6jo_o0hYiew', ), counter: 143, @@ -420,13 +419,13 @@ const assertionFirstTimeUsedResponse: AuthenticationCredentialJSON = { type: 'public-key', clientExtensionResults: {}, }; -const assertionFirstTimeUsedChallenge = base64url.fromString('totallyUniqueValueEveryAssertion'); +const assertionFirstTimeUsedChallenge = isoBase64URL.fromString('totallyUniqueValueEveryAssertion'); const assertionFirstTimeUsedOrigin = 'https://dev.dontneeda.pw'; const authenticatorFirstTimeUsed: AuthenticatorDevice = { - credentialPublicKey: base64url.toBuffer( + credentialPublicKey: isoBase64URL.toBuffer( 'pQECAyYgASFYIGmaxR4mBbukc2QhtW2ldhAAd555r-ljlGQN8MbcTnPPIlgg9CyUlE-0AB2fbzZbNgBvJuRa7r6o2jPphOmtyNPR_kY', ), - credentialID: base64url.toBuffer( + credentialID: isoBase64URL.toBuffer( 'wSisR0_4hlzw3Y1tj4uNwwifIhRa-ZxWJwWbnfror0pVK9qPdBPO5pW3gasPqn6wXHb0LNhXB_IrA1nFoSQJ9A', ), counter: 0, diff --git a/packages/server/src/authentication/verifyAuthenticationResponse.ts b/packages/server/src/authentication/verifyAuthenticationResponse.ts index dbec6b8..6d68e19 100644 --- a/packages/server/src/authentication/verifyAuthenticationResponse.ts +++ b/packages/server/src/authentication/verifyAuthenticationResponse.ts @@ -13,8 +13,7 @@ import { isBase64URLString } from '../helpers/isBase64URLString'; import { parseBackupFlags } from '../helpers/parseBackupFlags'; import { AuthenticationExtensionsAuthenticatorOutputs } from '../helpers/decodeAuthenticatorExtensions'; import { matchExpectedRPID } from '../helpers/matchExpectedRPID'; -import * as isoUint8Array from '../helpers/isoUint8Array'; -import * as isoBase64URL from '../helpers/isoBase64URL'; +import { isoUint8Array, isoBase64URL } from '../helpers/iso'; export type VerifyAuthenticationResponseOpts = { credential: AuthenticationCredentialJSON; @@ -144,7 +143,7 @@ export async function verifyAuthenticationResponse( } } - const authDataBuffer = base64url.toBuffer(response.authenticatorData); + const authDataBuffer = isoBase64URL.toBuffer(response.authenticatorData); const parsedAuthData = parseAuthenticatorData(authDataBuffer); const { rpIdHash, flags, counter, extensionsData } = parsedAuthData; @@ -187,10 +186,10 @@ export async function verifyAuthenticationResponse( } } - const clientDataHash = await toHash(base64url.toBuffer(response.clientDataJSON)); - const signatureBase = uint8Array.concat([authDataBuffer, clientDataHash]); + const clientDataHash = await toHash(isoBase64URL.toBuffer(response.clientDataJSON)); + const signatureBase = isoUint8Array.concat([authDataBuffer, clientDataHash]); - const signature = base64url.toBuffer(response.signature); + const signature = isoBase64URL.toBuffer(response.signature); if ((counter > 0 || authenticator.counter > 0) && counter <= authenticator.counter) { // Error out when the counter in the DB is greater than or equal to the counter in the diff --git a/packages/server/src/helpers/convertAAGUIDToString.ts b/packages/server/src/helpers/convertAAGUIDToString.ts index 8375099..db9622a 100644 --- a/packages/server/src/helpers/convertAAGUIDToString.ts +++ b/packages/server/src/helpers/convertAAGUIDToString.ts @@ -1,11 +1,11 @@ -import * as isoUint8Array from './isoUint8Array'; +import { isoUint8Array } from './iso'; /** * Convert the aaguid buffer in authData into a UUID string */ export function convertAAGUIDToString(aaguid: Uint8Array): string { // Raw Hex: adce000235bcc60a648b0b25f1f05503 - const hex = uint8Array.toHex(aaguid); + const hex = isoUint8Array.toHex(aaguid); const segments: string[] = [ hex.slice(0, 8), // 8 diff --git a/packages/server/src/helpers/convertCOSEtoPKCS.test.ts b/packages/server/src/helpers/convertCOSEtoPKCS.test.ts index d5d269e..db760d7 100644 --- a/packages/server/src/helpers/convertCOSEtoPKCS.test.ts +++ b/packages/server/src/helpers/convertCOSEtoPKCS.test.ts @@ -1,4 +1,4 @@ -import * as esmDecodeCbor from './cbor'; +import { isoCBOR } from './iso'; import { convertCOSEtoPKCS, COSEKEYS } from './convertCOSEtoPKCS'; @@ -7,7 +7,7 @@ test('should throw an error curve if, somehow, curve coordinate x is missing', ( mockCOSEKey.set(COSEKEYS.y, 1); - jest.spyOn(esmDecodeCbor, 'decodeFirst').mockReturnValue(mockCOSEKey); + jest.spyOn(isoCBOR, 'decodeFirst').mockReturnValue(mockCOSEKey); expect(() => { convertCOSEtoPKCS(Buffer.from('123', 'ascii')); @@ -19,7 +19,7 @@ test('should throw an error curve if, somehow, curve coordinate y is missing', ( mockCOSEKey.set(COSEKEYS.x, 1); - jest.spyOn(esmDecodeCbor, 'decodeFirst').mockReturnValue(mockCOSEKey); + jest.spyOn(isoCBOR, 'decodeFirst').mockReturnValue(mockCOSEKey); expect(() => { convertCOSEtoPKCS(Buffer.from('123', 'ascii')); diff --git a/packages/server/src/helpers/convertCOSEtoPKCS.ts b/packages/server/src/helpers/convertCOSEtoPKCS.ts index 0c1a8b5..f897cc8 100644 --- a/packages/server/src/helpers/convertCOSEtoPKCS.ts +++ b/packages/server/src/helpers/convertCOSEtoPKCS.ts @@ -1,12 +1,12 @@ import { COSEAlgorithmIdentifier } from '@simplewebauthn/typescript-types'; -import * as isoCBOR from './isoCBOR'; -import * as isoUint8Array from './isoUint8Array'; + +import { isoCBOR, isoUint8Array } from './iso'; /** * Takes COSE-encoded public key and converts it to PKCS key */ export function convertCOSEtoPKCS(cosePublicKey: Uint8Array): Uint8Array { - const struct = cbor.decodeFirst(cosePublicKey); + const struct = isoCBOR.decodeFirst(cosePublicKey); const tag = Uint8Array.from([0x04]); const x = struct.get(COSEKEYS.x); @@ -17,10 +17,10 @@ export function convertCOSEtoPKCS(cosePublicKey: Uint8Array): Uint8Array { } if (y) { - return uint8Array.concat([tag, x as Uint8Array, y as Uint8Array]); + return isoUint8Array.concat([tag, x as Uint8Array, y as Uint8Array]); } - return uint8Array.concat([tag, x as Uint8Array]); + return isoUint8Array.concat([tag, x as Uint8Array]); } export type COSEPublicKey = Map; diff --git a/packages/server/src/helpers/convertCertBufferToPEM.ts b/packages/server/src/helpers/convertCertBufferToPEM.ts index 4a0c674..0179eee 100644 --- a/packages/server/src/helpers/convertCertBufferToPEM.ts +++ b/packages/server/src/helpers/convertCertBufferToPEM.ts @@ -1,6 +1,6 @@ import type { Base64URLString } from '@simplewebauthn/typescript-types'; -import * as isoBase64URL from './isoBase64URL'; +import { isoBase64URL } from './iso'; /** * Convert buffer to an OpenSSL-compatible PEM text format. @@ -12,15 +12,15 @@ export function convertCertBufferToPEM(certBuffer: Uint8Array | Base64URLString) * Get certBuffer to a base64 representation */ if (typeof certBuffer === 'string') { - if (base64url.isBase64url(certBuffer)) { - b64cert = base64url.toBase64(certBuffer); - } else if (base64url.isBase64(certBuffer)) { + if (isoBase64URL.isBase64url(certBuffer)) { + b64cert = isoBase64URL.toBase64(certBuffer); + } else if (isoBase64URL.isBase64(certBuffer)) { b64cert = certBuffer } else { throw new Error('Certificate is not a valid base64 or base64url string'); } } else { - b64cert = base64url.fromBuffer(certBuffer, 'base64'); + b64cert = isoBase64URL.fromBuffer(certBuffer, 'base64'); } let PEMKey = ''; diff --git a/packages/server/src/helpers/convertPublicKeyToPEM.test.ts b/packages/server/src/helpers/convertPublicKeyToPEM.test.ts index f7cd401..efeaf3f 100644 --- a/packages/server/src/helpers/convertPublicKeyToPEM.test.ts +++ b/packages/server/src/helpers/convertPublicKeyToPEM.test.ts @@ -1,20 +1,19 @@ import { COSEKEYS } from './convertCOSEtoPKCS'; import { convertPublicKeyToPEM } from './convertPublicKeyToPEM'; -import * as cbor from './cbor'; -import * as uint8Array from './uint8Array'; +import { isoCBOR, isoUint8Array } from './iso'; test('should return pem - EC2', () => { const mockEC2Key = new Map(); - const x = uint8Array.fromHex('821f4c9978ed99c1c57aca1fa9667a8aec52740620a0f56f7c9aa9bf5f35f25a'); - const y = uint8Array.fromHex('dc10d91ec36f2946f955bc863ea70015fe051ae3e12765f2db5e68583c3fd637'); + const x = isoUint8Array.fromHex('821f4c9978ed99c1c57aca1fa9667a8aec52740620a0f56f7c9aa9bf5f35f25a'); + const y = isoUint8Array.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); - const pubKeyCBOR = cbor.encode(mockEC2Key); + const pubKeyCBOR = isoCBOR.encode(mockEC2Key); const actual = convertPublicKeyToPEM(pubKeyCBOR); expect(actual).toEqual(`-----BEGIN PUBLIC KEY----- @@ -37,7 +36,7 @@ test('should return pem - RSA', () => { mockRSAKey.set(COSEKEYS.n, n); mockRSAKey.set(COSEKEYS.e, e); - const pubKeyCBOR = cbor.encode(mockRSAKey); + const pubKeyCBOR = isoCBOR.encode(mockRSAKey); const actual = convertPublicKeyToPEM(pubKeyCBOR); expect(actual).toEqual(`-----BEGIN PUBLIC KEY----- @@ -58,7 +57,7 @@ test('should return pem when input is base64URLString', () => { mockCOSEKey.set(COSEKEYS.kty, 0); mockCOSEKey.set(COSEKEYS.alg, -7); - const pubKeyCBOR = cbor.encode(mockCOSEKey); + const pubKeyCBOR = isoCBOR.encode(mockCOSEKey); try { convertPublicKeyToPEM(pubKeyCBOR); @@ -73,7 +72,7 @@ test('should raise error when kty is OKP (1)', () => { mockOKPKey.set(COSEKEYS.kty, 1); mockOKPKey.set(COSEKEYS.alg, -7); - const pubKeyCBOR = cbor.encode(mockOKPKey); + const pubKeyCBOR = isoCBOR.encode(mockOKPKey); try { convertPublicKeyToPEM(pubKeyCBOR); diff --git a/packages/server/src/helpers/convertPublicKeyToPEM.ts b/packages/server/src/helpers/convertPublicKeyToPEM.ts index 74cc77f..a41952f 100644 --- a/packages/server/src/helpers/convertPublicKeyToPEM.ts +++ b/packages/server/src/helpers/convertPublicKeyToPEM.ts @@ -1,13 +1,12 @@ import jwkToPem from 'jwk-to-pem'; import { COSEKEYS, COSEKTY, COSECRV, COSEPublicKey } from './convertCOSEtoPKCS'; -import * as isoCBOR from './isoCBOR'; -import * as isoBase64URL from './isoBase64URL'; +import { isoBase64URL, isoCBOR } from './iso'; export function convertPublicKeyToPEM(publicKey: Uint8Array): string { let struct; try { - struct = cbor.decodeFirst(publicKey); + struct = isoCBOR.decodeFirst(publicKey); } catch (err) { const _err = err as Error; throw new Error(`Error decoding public key while converting to PEM: ${_err.message}`); @@ -40,8 +39,8 @@ export function convertPublicKeyToPEM(publicKey: Uint8Array): string { kty: 'EC', // Specify curve as "P-256" from "p256" crv: COSECRV[crv as number].replace('p', 'P-'), - x: base64url.fromBuffer(x as Uint8Array, 'base64'), - y: base64url.fromBuffer(y as Uint8Array, 'base64'), + x: isoBase64URL.fromBuffer(x as Uint8Array, 'base64'), + y: isoBase64URL.fromBuffer(y as Uint8Array, 'base64'), }); return ecPEM; @@ -59,8 +58,8 @@ export function convertPublicKeyToPEM(publicKey: Uint8Array): string { const rsaPEM = jwkToPem({ kty: 'RSA', - n: base64url.fromBuffer(n as Uint8Array, 'base64'), - e: base64url.fromBuffer(e as Uint8Array, 'base64'), + n: isoBase64URL.fromBuffer(n as Uint8Array, 'base64'), + e: isoBase64URL.fromBuffer(e as Uint8Array, 'base64'), }); return rsaPEM; diff --git a/packages/server/src/helpers/decodeAttestationObject.ts b/packages/server/src/helpers/decodeAttestationObject.ts index 5c3a6d8..fc98aa6 100644 --- a/packages/server/src/helpers/decodeAttestationObject.ts +++ b/packages/server/src/helpers/decodeAttestationObject.ts @@ -1,4 +1,4 @@ -import * as isoCBOR from './isoCBOR'; +import { isoCBOR } from './iso'; /** * Convert an AttestationObject buffer to a proper object @@ -6,7 +6,7 @@ import * as isoCBOR from './isoCBOR'; * @param base64AttestationObject Attestation Object buffer */ export function decodeAttestationObject(attestationObject: Uint8Array): AttestationObject { - return cbor.decodeFirst(attestationObject); + return isoCBOR.decodeFirst(attestationObject); } export type AttestationFormat = diff --git a/packages/server/src/helpers/decodeAuthenticatorExtensions.test.ts b/packages/server/src/helpers/decodeAuthenticatorExtensions.test.ts index 560a5c4..8afa3d7 100644 --- a/packages/server/src/helpers/decodeAuthenticatorExtensions.test.ts +++ b/packages/server/src/helpers/decodeAuthenticatorExtensions.test.ts @@ -1,9 +1,9 @@ import { decodeAuthenticatorExtensions } from './decodeAuthenticatorExtensions'; -import * as uint8Array from './uint8Array'; +import { isoUint8Array } from './iso'; test('should decode authenticator extensions', () => { const extensions = decodeAuthenticatorExtensions( - uint8Array.fromHex( + isoUint8Array.fromHex( 'A16C6465766963655075624B6579A56364706B584DA5010203262001215820991AABED9D' + 'E4271A9EDEAD8806F9DC96D6DCCD0C476253A5510489EC8379BE5B225820A0973CFDEDBB' + '79E27FEF4EE7481673FB3312504DDCA5434CFD23431D6AD29EDA63736967584730450221' + @@ -14,15 +14,15 @@ test('should decode authenticator extensions', () => { ); expect(extensions).toMatchObject({ devicePubKey: { - dpk: uint8Array.fromHex( + dpk: isoUint8Array.fromHex( 'A5010203262001215820991AABED9DE4271A9EDEAD8806F9DC96D6DCCD0C476253A5510489EC8379BE5B225820A0973CFDEDBB79E27FEF4EE7481673FB3312504DDCA5434CFD23431D6AD29EDA', ), - sig: uint8Array.fromHex( + sig: isoUint8Array.fromHex( '3045022100EFB38074BD15B8C82CF09F87FBC6FB3C7169EA4F1806B7E90937374302345B7A02202B7113040731A0E727D338D48542863CE65880AA79E5EA740AC8CCD94347988E', ), - nonce: uint8Array.fromHex(''), - scope: uint8Array.fromHex('00'), - aaguid: uint8Array.fromHex('00000000000000000000000000000000'), + nonce: isoUint8Array.fromHex(''), + scope: isoUint8Array.fromHex('00'), + aaguid: isoUint8Array.fromHex('00000000000000000000000000000000'), }, }); }); diff --git a/packages/server/src/helpers/decodeAuthenticatorExtensions.ts b/packages/server/src/helpers/decodeAuthenticatorExtensions.ts index af38f1a..7bd583c 100644 --- a/packages/server/src/helpers/decodeAuthenticatorExtensions.ts +++ b/packages/server/src/helpers/decodeAuthenticatorExtensions.ts @@ -1,4 +1,4 @@ -import * as isoCBOR from './isoCBOR'; +import { isoCBOR } from './iso'; /** * Convert authenticator extension data buffer to a proper object @@ -10,7 +10,7 @@ export function decodeAuthenticatorExtensions( ): AuthenticationExtensionsAuthenticatorOutputs | undefined { let toCBOR: Map; try { - toCBOR = cbor.decodeFirst(extensionData); + toCBOR = isoCBOR.decodeFirst(extensionData); } catch (err) { const _err = err as Error; throw new Error(`Error decoding authenticator extensions: ${_err.message}`); diff --git a/packages/server/src/helpers/decodeClientDataJSON.ts b/packages/server/src/helpers/decodeClientDataJSON.ts index 82ca17b..36a949e 100644 --- a/packages/server/src/helpers/decodeClientDataJSON.ts +++ b/packages/server/src/helpers/decodeClientDataJSON.ts @@ -1,10 +1,10 @@ -import * as isoBase64URL from "./isoBase64URL"; +import { isoBase64URL } from "./iso"; /** * Decode an authenticator's base64url-encoded clientDataJSON to JSON */ export function decodeClientDataJSON(data: string): ClientDataJSON { - const toString = base64url.toString(data); + const toString = isoBase64URL.toString(data); const clientData: ClientDataJSON = JSON.parse(toString); return clientData; diff --git a/packages/server/src/helpers/decodeCredentialPublicKey.ts b/packages/server/src/helpers/decodeCredentialPublicKey.ts index 5891b03..a2f8e55 100644 --- a/packages/server/src/helpers/decodeCredentialPublicKey.ts +++ b/packages/server/src/helpers/decodeCredentialPublicKey.ts @@ -1,6 +1,6 @@ import { COSEPublicKey } from './convertCOSEtoPKCS'; -import * as isoCBOR from './isoCBOR'; +import { isoCBOR } from './iso'; export function decodeCredentialPublicKey(publicKey: Uint8Array): COSEPublicKey { - return cbor.decodeFirst(publicKey); + return isoCBOR.decodeFirst(publicKey); } diff --git a/packages/server/src/helpers/index.ts b/packages/server/src/helpers/index.ts index ff565bc..b71e0b1 100644 --- a/packages/server/src/helpers/index.ts +++ b/packages/server/src/helpers/index.ts @@ -13,9 +13,7 @@ import { parseAuthenticatorData } from './parseAuthenticatorData'; import { toHash } from './toHash'; import { validateCertificatePath } from './validateCertificatePath'; import { verifySignature } from './verifySignature'; -import * as isoCBOR from './isoCBOR'; -import * as isoBase64URL from './isoBase64URL'; -import * as isoUint8Array from './isoUint8Array'; +import { isoCBOR, isoBase64URL, isoUint8Array } from './iso'; export { convertAAGUIDToString, @@ -33,9 +31,9 @@ export { toHash, validateCertificatePath, verifySignature, - cbor, - base64url, - uint8Array, + isoCBOR, + isoBase64URL, + isoUint8Array, }; import type { diff --git a/packages/server/src/helpers/isCertRevoked.ts b/packages/server/src/helpers/isCertRevoked.ts index 2f5c3a5..1ea3a8a 100644 --- a/packages/server/src/helpers/isCertRevoked.ts +++ b/packages/server/src/helpers/isCertRevoked.ts @@ -4,7 +4,7 @@ import { AsnParser } from '@peculiar/asn1-schema'; import { CertificateList } from '@peculiar/asn1-x509'; import { convertCertBufferToPEM } from './convertCertBufferToPEM'; -import * as isoUint8Array from './isoUint8Array'; +import { isoUint8Array } from './iso'; /** * A cache of revoked cert serial numbers by Authority Key ID @@ -69,7 +69,7 @@ export async function isCertRevoked(cert: X509): Promise { return false; } - const data = AsnParser.parse(uint8Array.fromHex(crlCert.hex), CertificateList); + const data = AsnParser.parse(isoUint8Array.fromHex(crlCert.hex), CertificateList); const newCached: CAAuthorityInfo = { revokedCerts: [], @@ -86,7 +86,7 @@ export async function isCertRevoked(cert: X509): Promise { if (revokedCerts) { for (const cert of revokedCerts) { - const revokedHex = uint8Array.toHex(new Uint8Array(cert.userCertificate)); + const revokedHex = isoUint8Array.toHex(new Uint8Array(cert.userCertificate)); newCached.revokedCerts.push(revokedHex); } diff --git a/packages/server/src/helpers/iso/index.ts b/packages/server/src/helpers/iso/index.ts new file mode 100644 index 0000000..5cb3bab --- /dev/null +++ b/packages/server/src/helpers/iso/index.ts @@ -0,0 +1,10 @@ +/** + * A collection of methods for isomorphic manipulation of trickier data types + * + * The goal with these is to make it easier to replace dependencies later that might not play well + * with specific server-like runtimes that expose global Web APIs (CloudFlare Workers, Deno, Bun, + * etc...), while also supporting execution in Node. + */ +export * as isoBase64URL from './isoBase64URL'; +export * as isoCBOR from './isoCBOR'; +export * as isoUint8Array from './isoUint8Array'; diff --git a/packages/server/src/helpers/iso/isoBase64URL.ts b/packages/server/src/helpers/iso/isoBase64URL.ts new file mode 100644 index 0000000..d03de51 --- /dev/null +++ b/packages/server/src/helpers/iso/isoBase64URL.ts @@ -0,0 +1,62 @@ +import base64 from '@hexagon/base64'; + +/** + * Decode from a Base64URL-encoded string to an ArrayBuffer. Best used when converting a + * credential ID from a JSON string to an ArrayBuffer, like in allowCredentials or + * excludeCredentials. + * + * @param buffer Value to decode from base64 + * @param to (optional) The decoding to use, in case it's desirable to decode from base64 instead + */ +export function toBuffer(base64urlString: string, from: 'base64' | 'base64url' = 'base64url'): Uint8Array { + const _buffer = base64.toArrayBuffer(base64urlString, from === 'base64url'); + return new Uint8Array(_buffer); +} + +/** + * Encode the given array buffer into a Base64URL-encoded string. Ideal for converting various + * credential response ArrayBuffers to string for sending back to the server as JSON. + * + * @param buffer Value to encode to base64 + * @param to (optional) The encoding to use, in case it's desirable to encode to base64 instead + */ +export function fromBuffer(buffer: Uint8Array, to: 'base64' | 'base64url' = 'base64url'): string { + return base64.fromArrayBuffer(buffer, to === 'base64url'); +} + +/** + * Convert a base64url string into base64 + */ +export function toBase64(base64urlString: string): string { + const fromBase64Url = base64.toArrayBuffer(base64urlString, true); + const toBase64 = base64.fromArrayBuffer(fromBase64Url); + return toBase64; +} + +/** + * Encode a string to base64url + */ +export function fromString(ascii: string): string { + return base64.fromString(ascii, true); +} + +/** + * Decode a base64url string into its original string + */ +export function toString(base64urlString: string): string { + return base64.toString(base64urlString, true); +} + +/** + * Confirm that the string is encoded into base64 + */ +export function isBase64(input: string): boolean { + return base64.validate(input, false); +} + +/** + * Confirm that the string is encoded into base64url + */ +export function isBase64url(input: string): boolean { + return base64.validate(input, true); +} diff --git a/packages/server/src/helpers/iso/isoCBOR.ts b/packages/server/src/helpers/iso/isoCBOR.ts new file mode 100644 index 0000000..9f7cbd7 --- /dev/null +++ b/packages/server/src/helpers/iso/isoCBOR.ts @@ -0,0 +1,46 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ +import * as cborx from 'cbor-x'; + +/** + * This encoder should keep CBOR data the same length when data is re-encoded + * + * MOST CRITICALLY, this means the following needs to be true of whatever CBOR library we use: + * - CBOR Map type values MUST decode to JavaScript Maps + * - CBOR tag 64 (uint8 Typed Array) MUST NOT be used when encoding Uint8Arrays back to CBOR + * + * So long as these requirements are maintained, then CBOR sequences can be encoded and decoded + * freely while maintaining their lengths for the most accurate pointer movement across them. + */ +const encoder = new cborx.Encoder({ mapsAsObjects: false, tagUint8Array: false }); + +/** + * Decode and return the first item in a sequence of CBOR-encoded values + * + * @param input The CBOR data to decode + * @param asObject (optional) Whether to convert any CBOR Maps into JavaScript Objects. Defaults to + * `false` + */ +export function decodeFirst(input: Uint8Array): Type { + const decoded = encoder.decodeMultiple(input) as undefined | Type[]; + + if (decoded === undefined) { + throw new Error('CBOR input data was empty'); + } + + /** + * Typing on `decoded` is `void | []` which causes TypeScript to think that it's an empty array, + * and thus you can't destructure it. I'm ignoring that because the code works fine in JS, and + * so this should be a valid operation. + */ + // @ts-ignore 2493 + const [first] = decoded; + + return first; +} + +/** + * Encode data to CBOR + */ +export function encode(input: any): Uint8Array { + return encoder.encode(input); +} diff --git a/packages/server/src/helpers/iso/isoUint8Array.ts b/packages/server/src/helpers/iso/isoUint8Array.ts new file mode 100644 index 0000000..5237937 --- /dev/null +++ b/packages/server/src/helpers/iso/isoUint8Array.ts @@ -0,0 +1,90 @@ +/** + * Make sure two Uint8Arrays are deeply equivalent + */ +export function areEqual(array1: Uint8Array, array2: Uint8Array): boolean { + if (array1.length != array2.length) { + return false; + } + + return array1.every((val, i) => val === array2[i]); +} + +/** + * Convert a Uint8Array to Hexadecimal. + * + * A replacement for `Buffer.toString('hex')` + */ +export function toHex(array: Uint8Array): string { + const hexParts = Array.from(array, i => i.toString(16).padStart(2, "0")); + + // adce000235bcc60a648b0b25f1f05503 + return hexParts.join(''); +} + +/** + * Convert a hexadecimal string to isoUint8Array. + * + * A replacement for `Buffer.from('...', 'hex')` + */ +export function fromHex(hex: string): Uint8Array { + if (!hex) { + return Uint8Array.from([]); + } + + const isValid = hex.length !== 0 && hex.length % 2 === 0 && !/[^a-fA-F0-9]/u.test(hex); + + if (!isValid) { + throw new Error('Invalid hex string'); + } + + const byteStrings = hex.match(/.{1,2}/g) ?? []; + + return Uint8Array.from(byteStrings.map((byte) => parseInt(byte, 16))); +} + +/** + * Combine multiple Uint8Arrays into a single Uint8Array + */ +export function concat(arrays: Uint8Array[]): Uint8Array { + let pointer = 0; + const totalLength = arrays.reduce((prev, curr) => prev + curr.length, 0); + + const toReturn = new Uint8Array(totalLength); + + arrays.forEach((arr) => { + toReturn.set(arr, pointer); + pointer += arr.length; + }); + + return toReturn; +} + +/** + * Convert bytes into a UTF-8 string + */ +export function toUTF8String(array: Uint8Array): string { + const decoder = new globalThis.TextDecoder("utf-8"); + return decoder.decode(array); +} + +/** + * Convert a UTF-8 string back into bytes + */ +export function fromUTF8String(utf8String: string): Uint8Array { + const encoder = new globalThis.TextEncoder(); + return encoder.encode(utf8String); +} + +/** + * Convert an ASCII string to Uint8Array + */ +export function fromASCIIString(value: string): Uint8Array { + return Uint8Array.from(value.split("").map(x => x.charCodeAt(0))); +} + +/** + * Prepare a DataView we can slice our way around in as we parse the bytes in a Uint8Array + */ +export function toDataView(array: Uint8Array): DataView { + return new DataView(array.buffer, array.byteOffset, array.length); +} diff --git a/packages/server/src/helpers/isoBase64URL.ts b/packages/server/src/helpers/isoBase64URL.ts deleted file mode 100644 index cb8c71d..0000000 --- a/packages/server/src/helpers/isoBase64URL.ts +++ /dev/null @@ -1,65 +0,0 @@ -/** - * A collection of isomorphic methods for working with base64url and base64 values - */ -import base64 from '@hexagon/base64'; - -/** - * Decode from a Base64URL-encoded string to an ArrayBuffer. Best used when converting a - * credential ID from a JSON string to an ArrayBuffer, like in allowCredentials or - * excludeCredentials. - * - * @param buffer Value to decode from base64 - * @param to (optional) The decoding to use, in case it's desirable to decode from base64 instead - */ -export function toBuffer(base64urlString: string, from: 'base64' | 'base64url' = 'base64url'): Uint8Array { - const _buffer = base64.toArrayBuffer(base64urlString, from === 'base64url'); - return new Uint8Array(_buffer); -} - -/** - * Encode the given array buffer into a Base64URL-encoded string. Ideal for converting various - * credential response ArrayBuffers to string for sending back to the server as JSON. - * - * @param buffer Value to encode to base64 - * @param to (optional) The encoding to use, in case it's desirable to encode to base64 instead - */ -export function fromBuffer(buffer: Uint8Array, to: 'base64' | 'base64url' = 'base64url'): string { - return base64.fromArrayBuffer(buffer, to === 'base64url'); -} - -/** - * Convert a base64url string into base64 - */ -export function toBase64(base64urlString: string): string { - const fromBase64Url = base64.toArrayBuffer(base64urlString, true); - const toBase64 = base64.fromArrayBuffer(fromBase64Url); - return toBase64; -} - -/** - * Encode a string to base64url - */ -export function fromString(ascii: string): string { - return base64.fromString(ascii, true); -} - -/** - * Decode a base64url string into its original string - */ -export function toString(base64urlString: string): string { - return base64.toString(base64urlString, true); -} - -/** - * Confirm that the string is encoded into base64 - */ -export function isBase64(input: string): boolean { - return base64.validate(input, false); -} - -/** - * Confirm that the string is encoded into base64url - */ -export function isBase64url(input: string): boolean { - return base64.validate(input, true); -} diff --git a/packages/server/src/helpers/isoCBOR.ts b/packages/server/src/helpers/isoCBOR.ts deleted file mode 100644 index 9093ffd..0000000 --- a/packages/server/src/helpers/isoCBOR.ts +++ /dev/null @@ -1,49 +0,0 @@ -/* eslint-disable @typescript-eslint/ban-ts-comment */ -/** - * A collection of isomorphic methods for working with CBOR values - */ -import * as cborx from 'cbor-x'; - -/** - * This encoder should keep CBOR data the same length when data is re-encoded - * - * MOST CRITICALLY, this means the following needs to be true of whatever CBOR library we use: - * - CBOR Map type values MUST decode to JavaScript Maps - * - CBOR tag 64 (uint8 Typed Array) MUST NOT be used when encoding Uint8Arrays back to CBOR - * - * So long as these requirements are maintained, then CBOR sequences can be encoded and decoded - * freely while maintaining their lengths for the most accurate pointer movement across them. - */ -const encoder = new cborx.Encoder({ mapsAsObjects: false, tagUint8Array: false }); - -/** - * Decode and return the first item in a sequence of CBOR-encoded values - * - * @param input The CBOR data to decode - * @param asObject (optional) Whether to convert any CBOR Maps into JavaScript Objects. Defaults to - * `false` - */ -export function decodeFirst(input: Uint8Array): Type { - const decoded = encoder.decodeMultiple(input) as undefined | Type[]; - - if (decoded === undefined) { - throw new Error('CBOR input data was empty'); - } - - /** - * Typing on `decoded` is `void | []` which causes TypeScript to think that it's an empty array, - * and thus you can't destructure it. I'm ignoring that because the code works fine in JS, and - * so this should be a valid operation. - */ - // @ts-ignore 2493 - const [first] = decoded; - - return first; -} - -/** - * Encode data to CBOR - */ -export function encode(input: any): Uint8Array { - return encoder.encode(input); -} diff --git a/packages/server/src/helpers/isoUint8Array.ts b/packages/server/src/helpers/isoUint8Array.ts deleted file mode 100644 index 0834bb2..0000000 --- a/packages/server/src/helpers/isoUint8Array.ts +++ /dev/null @@ -1,94 +0,0 @@ -/** - * A collection of isomorphic methods for working with Uint8Array values - */ - -/** - * Make sure two Uint8Arrays are deeply equivalent - */ -export function areEqual(array1: Uint8Array, array2: Uint8Array): boolean { - if (array1.length != array2.length) { - return false; - } - - return array1.every((val, i) => val === array2[i]); -} - -/** - * Convert a Uint8Array to Hexadecimal. - * - * A replacement for `Buffer.toString('hex')` - */ -export function toHex(array: Uint8Array): string { - const hexParts = Array.from(array, i => i.toString(16).padStart(2, "0")); - - // adce000235bcc60a648b0b25f1f05503 - return hexParts.join(''); -} - -/** - * Convert a hexadecimal string to Uint8Array. - * - * A replacement for `Buffer.from('...', 'hex')` - */ -export function fromHex(hex: string): Uint8Array { - if (!hex) { - return Uint8Array.from([]); - } - - const isValid = hex.length !== 0 && hex.length % 2 === 0 && !/[^a-fA-F0-9]/u.test(hex); - - if (!isValid) { - throw new Error('Invalid hex string'); - } - - const byteStrings = hex.match(/.{1,2}/g) ?? []; - - return Uint8Array.from(byteStrings.map((byte) => parseInt(byte, 16))); -} - -/** - * Combine multiple Uint8Arrays into a single Uint8Array - */ -export function concat(arrays: Uint8Array[]): Uint8Array { - let pointer = 0; - const totalLength = arrays.reduce((prev, curr) => prev + curr.length, 0); - - const toReturn = new Uint8Array(totalLength); - - arrays.forEach((arr) => { - toReturn.set(arr, pointer); - pointer += arr.length; - }); - - return toReturn; -} - -/** - * Convert bytes into a UTF-8 string - */ -export function toUTF8String(array: Uint8Array): string { - const decoder = new globalThis.TextDecoder("utf-8"); - return decoder.decode(array); -} - -/** - * Convert a UTF-8 string back into bytes - */ -export function fromUTF8String(utf8String: string): Uint8Array { - const encoder = new globalThis.TextEncoder(); - return encoder.encode(utf8String); -} - -/** - * Convert an ASCII string to Uint8Array - */ -export function fromASCIIString(value: string): Uint8Array { - return Uint8Array.from(value.split("").map(x => x.charCodeAt(0))); -} - -/** - * Prepare a DataView we can slice our way around in as we parse the bytes in a Uint8Array - */ -export function toDataView(array: Uint8Array): DataView { - return new DataView(array.buffer, array.byteOffset, array.length); -} diff --git a/packages/server/src/helpers/matchExpectedRPID.ts b/packages/server/src/helpers/matchExpectedRPID.ts index aa96fee..76991bf 100644 --- a/packages/server/src/helpers/matchExpectedRPID.ts +++ b/packages/server/src/helpers/matchExpectedRPID.ts @@ -1,5 +1,5 @@ import { toHash } from './toHash'; -import * as isoUint8Array from './isoUint8Array'; +import { isoUint8Array } from './iso'; /** * Go through each expected RP ID and try to find one that matches. Raises an Error if no @@ -8,8 +8,8 @@ export async function matchExpectedRPID(rpIDHash: Uint8Array, expectedRPIDs: str try { await Promise.any(expectedRPIDs.map((expected) => { return new Promise((resolve, reject) => { - toHash(uint8Array.fromASCIIString(expected)).then((expectedRPIDHash) => { - if (uint8Array.areEqual(rpIDHash, expectedRPIDHash)) { + toHash(isoUint8Array.fromASCIIString(expected)).then((expectedRPIDHash) => { + if (isoUint8Array.areEqual(rpIDHash, expectedRPIDHash)) { resolve(true); } else { reject(); diff --git a/packages/server/src/helpers/parseAuthenticatorData.test.ts b/packages/server/src/helpers/parseAuthenticatorData.test.ts index e08d95b..7885b32 100644 --- a/packages/server/src/helpers/parseAuthenticatorData.test.ts +++ b/packages/server/src/helpers/parseAuthenticatorData.test.ts @@ -1,8 +1,8 @@ import { parseAuthenticatorData } from './parseAuthenticatorData'; -import * as base64url from './base64url'; +import { isoBase64URL } from './iso'; // Grabbed this from a Conformance test, contains attestation data -const authDataWithAT = base64url.toBuffer('SZYN5YgOjGh0NBcPZHZgW4/krrmihjLHmVzzuoMdl2NBAAAAJch83ZdWwUm4niTLNjZU81AAIHa7Ksm5br3hAh3UjxP9+4rqu8BEsD+7SZ2xWe1/yHv6pAEDAzkBACBZAQDcxA7Ehs9goWB2Hbl6e9v+aUub9rvy2M7Hkvf+iCzMGE63e3sCEW5Ru33KNy4um46s9jalcBHtZgtEnyeRoQvszis+ws5o4Da0vQfuzlpBmjWT1dV6LuP+vs9wrfObW4jlA5bKEIhv63+jAxOtdXGVzo75PxBlqxrmrr5IR9n8Fw7clwRsDkjgRHaNcQVbwq/qdNwU5H3hZKu9szTwBS5NGRq01EaDF2014YSTFjwtAmZ3PU1tcO/QD2U2zg6eB5grfWDeAJtRE8cbndDWc8aLL0aeC37Q36+TVsGe6AhBgHEw6eO3I3NW5r9v/26CqMPBDwmEundeq1iGyKfMloobIUMBAAE=', 'base64'); +const authDataWithAT = isoBase64URL.toBuffer('SZYN5YgOjGh0NBcPZHZgW4/krrmihjLHmVzzuoMdl2NBAAAAJch83ZdWwUm4niTLNjZU81AAIHa7Ksm5br3hAh3UjxP9+4rqu8BEsD+7SZ2xWe1/yHv6pAEDAzkBACBZAQDcxA7Ehs9goWB2Hbl6e9v+aUub9rvy2M7Hkvf+iCzMGE63e3sCEW5Ru33KNy4um46s9jalcBHtZgtEnyeRoQvszis+ws5o4Da0vQfuzlpBmjWT1dV6LuP+vs9wrfObW4jlA5bKEIhv63+jAxOtdXGVzo75PxBlqxrmrr5IR9n8Fw7clwRsDkjgRHaNcQVbwq/qdNwU5H3hZKu9szTwBS5NGRq01EaDF2014YSTFjwtAmZ3PU1tcO/QD2U2zg6eB5grfWDeAJtRE8cbndDWc8aLL0aeC37Q36+TVsGe6AhBgHEw6eO3I3NW5r9v/26CqMPBDwmEundeq1iGyKfMloobIUMBAAE=', 'base64'); // Grabbed this from a Conformance test, contains extension data const authDataWithED = Buffer.from( @@ -28,11 +28,11 @@ test('should parse attestation data', () => { const { credentialID, credentialPublicKey, aaguid, counter } = parsed; - expect(base64url.fromBuffer(credentialID!)).toEqual('drsqybluveECHdSPE_37iuq7wESwP7tJnbFZ7X_Ie_o'); - expect(base64url.fromBuffer(credentialPublicKey!, 'base64')).toEqual( + expect(isoBase64URL.fromBuffer(credentialID!)).toEqual('drsqybluveECHdSPE_37iuq7wESwP7tJnbFZ7X_Ie_o'); + expect(isoBase64URL.fromBuffer(credentialPublicKey!, 'base64')).toEqual( 'pAEDAzkBACBZAQDcxA7Ehs9goWB2Hbl6e9v+aUub9rvy2M7Hkvf+iCzMGE63e3sCEW5Ru33KNy4um46s9jalcBHtZgtEnyeRoQvszis+ws5o4Da0vQfuzlpBmjWT1dV6LuP+vs9wrfObW4jlA5bKEIhv63+jAxOtdXGVzo75PxBlqxrmrr5IR9n8Fw7clwRsDkjgRHaNcQVbwq/qdNwU5H3hZKu9szTwBS5NGRq01EaDF2014YSTFjwtAmZ3PU1tcO/QD2U2zg6eB5grfWDeAJtRE8cbndDWc8aLL0aeC37Q36+TVsGe6AhBgHEw6eO3I3NW5r9v/26CqMPBDwmEundeq1iGyKfMloobIUMBAAE=', ); - expect(base64url.fromBuffer(aaguid!, 'base64')).toEqual('yHzdl1bBSbieJMs2NlTzUA=='); + expect(isoBase64URL.fromBuffer(aaguid!, 'base64')).toEqual('yHzdl1bBSbieJMs2NlTzUA=='); expect(counter).toEqual(37); }); diff --git a/packages/server/src/helpers/parseAuthenticatorData.ts b/packages/server/src/helpers/parseAuthenticatorData.ts index f45e717..bcc552d 100644 --- a/packages/server/src/helpers/parseAuthenticatorData.ts +++ b/packages/server/src/helpers/parseAuthenticatorData.ts @@ -1,9 +1,8 @@ -import * as isoCBOR from './isoCBOR'; import { decodeAuthenticatorExtensions, AuthenticationExtensionsAuthenticatorOutputs, } from './decodeAuthenticatorExtensions'; -import * as isoUint8Array from './isoUint8Array'; +import { isoCBOR, isoUint8Array } from './iso'; import { COSEPublicKey } from './convertCOSEtoPKCS'; /** @@ -17,7 +16,7 @@ export function parseAuthenticatorData(authData: Uint8Array): ParsedAuthenticato } let pointer = 0; - const dataView = uint8Array.toDataView(authData); + const dataView = isoUint8Array.toDataView(authData); const rpIdHash = authData.slice(pointer, (pointer += 32)); @@ -53,8 +52,8 @@ export function parseAuthenticatorData(authData: Uint8Array): ParsedAuthenticato credentialID = authData.slice(pointer, (pointer += credIDLen)); // Decode the next CBOR item in the buffer, then re-encode it back to a Buffer - const firstDecoded = cbor.decodeFirst(authData.slice(pointer)); - const firstEncoded = Uint8Array.from(cbor.encode(firstDecoded)); + const firstDecoded = isoCBOR.decodeFirst(authData.slice(pointer)); + const firstEncoded = Uint8Array.from(isoCBOR.encode(firstDecoded)); credentialPublicKey = firstEncoded; pointer += firstEncoded.byteLength; @@ -64,8 +63,8 @@ export function parseAuthenticatorData(authData: Uint8Array): ParsedAuthenticato let extensionsDataBuffer: Uint8Array | undefined = undefined; if (flags.ed) { - const firstDecoded = cbor.decodeFirst(authData.slice(pointer)); - extensionsDataBuffer = Uint8Array.from(cbor.encode(firstDecoded)); + const firstDecoded = isoCBOR.decodeFirst(authData.slice(pointer)); + extensionsDataBuffer = Uint8Array.from(isoCBOR.encode(firstDecoded)); extensionsData = decodeAuthenticatorExtensions(extensionsDataBuffer); pointer += extensionsDataBuffer.byteLength; } diff --git a/packages/server/src/helpers/verifySignature.ts b/packages/server/src/helpers/verifySignature.ts index 42a167e..4373d5d 100644 --- a/packages/server/src/helpers/verifySignature.ts +++ b/packages/server/src/helpers/verifySignature.ts @@ -4,7 +4,7 @@ import { verify as ed25519Verify } from '@noble/ed25519'; import { COSEKEYS, COSEKTY, COSEPublicKey } from './convertCOSEtoPKCS'; import { convertCertBufferToPEM } from './convertCertBufferToPEM'; import { convertPublicKeyToPEM } from './convertPublicKeyToPEM'; -import * as isoCBOR from './isoCBOR'; +import { isoCBOR } from './iso'; type VerifySignatureOptsLeafCert = { signature: Uint8Array; @@ -51,7 +51,7 @@ export async function verifySignature( // Decode CBOR to COSE let struct; try { - struct = cbor.decodeFirst(credentialPublicKey); + struct = isoCBOR.decodeFirst(credentialPublicKey); } catch (err) { const _err = err as Error; throw new Error(`Error decoding public key while converting to PEM: ${_err.message}`); diff --git a/packages/server/src/metadata/parseJWT.ts b/packages/server/src/metadata/parseJWT.ts index 879134d..7b1a291 100644 --- a/packages/server/src/metadata/parseJWT.ts +++ b/packages/server/src/metadata/parseJWT.ts @@ -1,4 +1,4 @@ -import * as isoBase64URL from "../helpers/isoBase64URL"; +import { isoBase64URL } from "../helpers/iso"; /** * Process a JWT into Javascript-friendly data structures @@ -6,8 +6,8 @@ import * as isoBase64URL from "../helpers/isoBase64URL"; export function parseJWT(jwt: string): [T1, T2, string] { const parts = jwt.split('.'); return [ - JSON.parse(base64url.toString(parts[0])) as T1, - JSON.parse(base64url.toString(parts[1])) as T2, + JSON.parse(isoBase64URL.toString(parts[0])) as T1, + JSON.parse(isoBase64URL.toString(parts[1])) as T2, parts[2], ]; } diff --git a/packages/server/src/metadata/verifyAttestationWithMetadata.test.ts b/packages/server/src/metadata/verifyAttestationWithMetadata.test.ts index 228a1b7..57751b1 100644 --- a/packages/server/src/metadata/verifyAttestationWithMetadata.test.ts +++ b/packages/server/src/metadata/verifyAttestationWithMetadata.test.ts @@ -1,6 +1,6 @@ import { verifyAttestationWithMetadata } from './verifyAttestationWithMetadata'; import { MetadataStatement } from '../metadata/mdsTypes'; -import * as base64url from '../helpers/base64url'; +import { isoBase64URL } from '../helpers/iso'; test('should verify attestation with metadata (android-safetynet)', async () => { const metadataStatementJSONSafetyNet: MetadataStatement = { @@ -48,7 +48,7 @@ test('should verify attestation with metadata (android-safetynet)', async () => const verified = await verifyAttestationWithMetadata({ statement: metadataStatementJSONSafetyNet, - credentialPublicKey: base64url.toBuffer(credentialPublicKey), + credentialPublicKey: isoBase64URL.toBuffer(credentialPublicKey), x5c, }); @@ -98,7 +98,7 @@ test('should verify attestation with rsa_emsa_pkcs1_sha256_raw authenticator alg const verified = await verifyAttestationWithMetadata({ statement: metadataStatement, - credentialPublicKey: base64url.toBuffer(credentialPublicKey), + credentialPublicKey: isoBase64URL.toBuffer(credentialPublicKey), x5c, }); @@ -155,7 +155,7 @@ test('should not validate certificate path when authenticator is self-referencin const verified = await verifyAttestationWithMetadata({ statement: metadataStatement, - credentialPublicKey: base64url.toBuffer(credentialPublicKey), + credentialPublicKey: isoBase64URL.toBuffer(credentialPublicKey), x5c, }); diff --git a/packages/server/src/registration/generateRegistrationOptions.ts b/packages/server/src/registration/generateRegistrationOptions.ts index 1497ec6..83bfb3c 100644 --- a/packages/server/src/registration/generateRegistrationOptions.ts +++ b/packages/server/src/registration/generateRegistrationOptions.ts @@ -9,8 +9,7 @@ import type { } from '@simplewebauthn/typescript-types'; import { generateChallenge } from '../helpers/generateChallenge'; -import * as isoBase64URL from '../helpers/isoBase64URL'; -import * as isoUint8Array from '../helpers/isoUint8Array'; +import { isoBase64URL, isoUint8Array } from '../helpers/iso'; export type GenerateRegistrationOptionsOpts = { rpName: string; @@ -157,11 +156,11 @@ export function generateRegistrationOptions( */ let _challenge = challenge; if (typeof _challenge === 'string') { - _challenge = uint8Array.fromASCIIString(_challenge); + _challenge = isoUint8Array.fromASCIIString(_challenge); } return { - challenge: base64url.fromBuffer(_challenge), + challenge: isoBase64URL.fromBuffer(_challenge), rp: { name: rpName, id: rpID, @@ -176,7 +175,7 @@ export function generateRegistrationOptions( attestation: attestationType, excludeCredentials: excludeCredentials.map(cred => ({ ...cred, - id: base64url.fromBuffer(cred.id as Uint8Array), + id: isoBase64URL.fromBuffer(cred.id as Uint8Array), })), authenticatorSelection, extensions, diff --git a/packages/server/src/registration/verifications/tpm/parseCertInfo.ts b/packages/server/src/registration/verifications/tpm/parseCertInfo.ts index d36f6f6..ab4798e 100644 --- a/packages/server/src/registration/verifications/tpm/parseCertInfo.ts +++ b/packages/server/src/registration/verifications/tpm/parseCertInfo.ts @@ -1,12 +1,12 @@ import { TPM_ST, TPM_ALG } from './constants'; -import * as isoUint8Array from '../../../helpers/isoUint8Array'; +import { isoUint8Array } from '../../../helpers/iso'; /** * Cut up a TPM attestation's certInfo into intelligible chunks */ export function parseCertInfo(certInfo: Uint8Array): ParsedCertInfo { let pointer = 0; - const dataView = uint8Array.toDataView(certInfo); + const dataView = isoUint8Array.toDataView(certInfo); // Get a magic constant const magic = dataView.getUint32(pointer); @@ -44,7 +44,7 @@ export function parseCertInfo(certInfo: Uint8Array): ParsedCertInfo { const attestedNameLength = dataView.getUint16(pointer); pointer += 2; const attestedName = certInfo.slice(pointer, (pointer += attestedNameLength)); - const attestedNameDataView = uint8Array.toDataView(attestedName); + const attestedNameDataView = isoUint8Array.toDataView(attestedName); // Attested qualified name, can be ignored const qualifiedNameLength = dataView.getUint16(pointer); diff --git a/packages/server/src/registration/verifications/tpm/parsePubArea.ts b/packages/server/src/registration/verifications/tpm/parsePubArea.ts index 593b9a4..514828c 100644 --- a/packages/server/src/registration/verifications/tpm/parsePubArea.ts +++ b/packages/server/src/registration/verifications/tpm/parsePubArea.ts @@ -1,5 +1,5 @@ import { TPM_ALG, TPM_ECC_CURVE } from './constants'; -import * as isoUint8Array from '../../../helpers/isoUint8Array'; +import { isoUint8Array } from '../../../helpers/iso'; /** * Break apart a TPM attestation's pubArea buffer @@ -9,7 +9,7 @@ import * as isoUint8Array from '../../../helpers/isoUint8Array'; */ export function parsePubArea(pubArea: Uint8Array): ParsedPubArea { let pointer = 0; - const dataView = uint8Array.toDataView(pubArea); + const dataView = isoUint8Array.toDataView(pubArea); const type = TPM_ALG[dataView.getUint16(pointer)]; pointer += 2; @@ -99,7 +99,7 @@ export function parsePubArea(pubArea: Uint8Array): ParsedPubArea { const uniqueY = pubArea.slice(pointer, (pointer += uniqueYLength)); - unique = uint8Array.concat([uniqueX, uniqueY]); + unique = isoUint8Array.concat([uniqueX, uniqueY]); } else { throw new Error(`Unexpected type "${type}" (TPM)`); } diff --git a/packages/server/src/registration/verifications/tpm/verifyAttestationTPM.test.ts b/packages/server/src/registration/verifications/tpm/verifyAttestationTPM.test.ts index b69f24c..3f1531c 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 { isoBase64URL } from '../../../helpers/iso'; import { verifyRegistrationResponse } from '../../verifyRegistrationResponse'; test('should verify TPM response', async () => { const expectedChallenge = 'a4de0d36-057d-4e9d-831a-2c578fa89170'; - jest.spyOn(base64url, 'fromString').mockReturnValueOnce(expectedChallenge); + jest.spyOn(isoBase64URL, '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, 'fromString').mockReturnValueOnce(expectedChallenge); + jest.spyOn(isoBase64URL, '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, 'fromString').mockReturnValueOnce(expectedChallenge); + jest.spyOn(isoBase64URL, '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, 'fromString').mockReturnValueOnce(expectedChallenge); + jest.spyOn(isoBase64URL, '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, 'fromString').mockReturnValueOnce(expectedChallenge); + jest.spyOn(isoBase64URL, '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, 'fromString').mockReturnValueOnce(expectedChallenge); + jest.spyOn(isoBase64URL, '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 844b97b..ff2d4e5 100644 --- a/packages/server/src/registration/verifications/tpm/verifyAttestationTPM.ts +++ b/packages/server/src/registration/verifications/tpm/verifyAttestationTPM.ts @@ -17,7 +17,7 @@ import { convertCertBufferToPEM } from '../../../helpers/convertCertBufferToPEM' import { validateCertificatePath } from '../../../helpers/validateCertificatePath'; import { getCertificateInfo } from '../../../helpers/getCertificateInfo'; import { verifySignature } from '../../../helpers/verifySignature'; -import * as isoUint8Array from '../../../helpers/isoUint8Array'; +import { isoUint8Array } from '../../../helpers/iso'; import { MetadataService } from '../../../services/metadataService'; import { verifyAttestationWithMetadata } from '../../../metadata/verifyAttestationWithMetadata'; @@ -80,7 +80,7 @@ export async function verifyAttestationTPM(options: AttestationFormatVerifierOpt throw new Error('COSE public key missing e (TPM|RSA)'); } - if (!uint8Array.areEqual(unique, (n as Uint8Array))) { + if (!isoUint8Array.areEqual(unique, (n as Uint8Array))) { throw new Error('PubArea unique is not same as credentialPublicKey (TPM|RSA)'); } @@ -113,7 +113,7 @@ export async function verifyAttestationTPM(options: AttestationFormatVerifierOpt throw new Error('COSE public key missing y (TPM|ECC)'); } - if (!uint8Array.areEqual(unique, uint8Array.concat([x as Uint8Array, y as Uint8Array]))) { + if (!isoUint8Array.areEqual(unique, isoUint8Array.concat([x as Uint8Array, y as Uint8Array]))) { throw new Error('PubArea unique is not same as public key x and y (TPM|ECC)'); } @@ -147,22 +147,22 @@ export async function verifyAttestationTPM(options: AttestationFormatVerifierOpt const pubAreaHash = await toHash(pubArea, attested.nameAlg.replace('TPM_ALG_', '')); // Concatenate attested.nameAlg and pubAreaHash to create attestedName. - const attestedName = uint8Array.concat([attested.nameAlgBuffer, pubAreaHash]); + const attestedName = isoUint8Array.concat([attested.nameAlgBuffer, pubAreaHash]); // Check that certInfo.attested.name is equals to attestedName. - if (!uint8Array.areEqual(attested.name, attestedName)) { + if (!isoUint8Array.areEqual(attested.name, attestedName)) { throw new Error(`Attested name comparison failed (TPM)`); } // Concatenate authData with clientDataHash to create attToBeSigned - const attToBeSigned = uint8Array.concat([authData, clientDataHash]); + const attToBeSigned = isoUint8Array.concat([authData, clientDataHash]); // Hash attToBeSigned using the algorithm specified in attStmt.alg to create attToBeSignedHash const hashAlg: string = COSEALGHASH[alg as number]; const attToBeSignedHash = await toHash(attToBeSigned, hashAlg); // Check that certInfo.extraData is equals to attToBeSignedHash. - if (!uint8Array.areEqual(extraData, attToBeSignedHash)) { + if (!isoUint8Array.areEqual(extraData, attToBeSignedHash)) { throw new Error('CertInfo extra data did not equal hashed attestation (TPM)'); } diff --git a/packages/server/src/registration/verifications/verifyAttestationAndroidKey.test.ts b/packages/server/src/registration/verifications/verifyAttestationAndroidKey.test.ts index 7e9ce37..0e5d27b 100644 --- a/packages/server/src/registration/verifications/verifyAttestationAndroidKey.test.ts +++ b/packages/server/src/registration/verifications/verifyAttestationAndroidKey.test.ts @@ -1,5 +1,5 @@ import { SettingsService } from '../../services/settingsService'; -import * as base64url from '../../helpers/base64url'; +import { isoBase64URL } from '../../helpers/iso'; import { verifyRegistrationResponse } from '../verifyRegistrationResponse'; @@ -11,7 +11,7 @@ SettingsService.setRootCertificates({ identifier: 'android-key', certificates: [ test('should verify Android KeyStore response', async () => { const expectedChallenge = '4ab7dfd1-a695-4777-985f-ad2993828e99'; - jest.spyOn(base64url, 'fromString').mockReturnValueOnce(expectedChallenge); + jest.spyOn(isoBase64URL, 'fromString').mockReturnValueOnce(expectedChallenge); const verification = await verifyRegistrationResponse({ credential: { id: 'V51GE29tGbhby7sbg1cZ_qL8V8njqEsXpAnwQBobvgw', diff --git a/packages/server/src/registration/verifications/verifyAttestationAndroidKey.ts b/packages/server/src/registration/verifications/verifyAttestationAndroidKey.ts index 0c41061..8f29cca 100644 --- a/packages/server/src/registration/verifications/verifyAttestationAndroidKey.ts +++ b/packages/server/src/registration/verifications/verifyAttestationAndroidKey.ts @@ -8,7 +8,7 @@ import { convertCertBufferToPEM } from '../../helpers/convertCertBufferToPEM'; import { validateCertificatePath } from '../../helpers/validateCertificatePath'; import { verifySignature } from '../../helpers/verifySignature'; import { COSEALGHASH, convertCOSEtoPKCS } from '../../helpers/convertCOSEtoPKCS'; -import * as isoUint8Array from '../../helpers/isoUint8Array'; +import { isoUint8Array } from '../../helpers/iso'; import { MetadataService } from '../../services/metadataService'; import { verifyAttestationWithMetadata } from '../../metadata/verifyAttestationWithMetadata'; @@ -46,7 +46,7 @@ export async function verifyAttestationAndroidKey( // Convert the credentialPublicKey to PKCS const credPubKeyPKCS = convertCOSEtoPKCS(credentialPublicKey); - if (!uint8Array.areEqual(credPubKeyPKCS, parsedCertPubKey)) { + if (!isoUint8Array.areEqual(credPubKeyPKCS, parsedCertPubKey)) { throw new Error('Credential public key does not equal leaf cert public key (AndroidKey)'); } @@ -64,7 +64,7 @@ export async function verifyAttestationAndroidKey( // Verify extKeyStore values const { attestationChallenge, teeEnforced, softwareEnforced } = parsedExtKeyStore; - if (!uint8Array.areEqual(new Uint8Array(attestationChallenge.buffer), clientDataHash)) { + if (!isoUint8Array.areEqual(new Uint8Array(attestationChallenge.buffer), clientDataHash)) { throw new Error('Attestation challenge was not equal to client data hash (AndroidKey)'); } @@ -101,7 +101,7 @@ export async function verifyAttestationAndroidKey( } } - const signatureBase = uint8Array.concat([authData, clientDataHash]); + const signatureBase = isoUint8Array.concat([authData, clientDataHash]); const hashAlg = COSEALGHASH[alg as number]; return verifySignature({ diff --git a/packages/server/src/registration/verifications/verifyAttestationAndroidSafetyNet.test.ts b/packages/server/src/registration/verifications/verifyAttestationAndroidSafetyNet.test.ts index d46c17f..7c33bb3 100644 --- a/packages/server/src/registration/verifications/verifyAttestationAndroidSafetyNet.test.ts +++ b/packages/server/src/registration/verifications/verifyAttestationAndroidSafetyNet.test.ts @@ -6,7 +6,7 @@ import { } from '../../helpers/decodeAttestationObject'; import { parseAuthenticatorData } from '../../helpers/parseAuthenticatorData'; import { toHash } from '../../helpers/toHash'; -import * as base64url from '../../helpers/base64url'; +import { isoBase64URL } from '../../helpers/iso'; import { SettingsService } from '../../services/settingsService'; const rootCertificates = SettingsService.getRootCertificates({ @@ -24,11 +24,11 @@ let spyDate: jest.SpyInstance; beforeEach(async () => { const { attestationObject, clientDataJSON } = attestationAndroidSafetyNet.response; - const decodedAttestationObject = decodeAttestationObject(base64url.toBuffer(attestationObject)); + const decodedAttestationObject = decodeAttestationObject(isoBase64URL.toBuffer(attestationObject)); authData = decodedAttestationObject.get('authData'); attStmt = decodedAttestationObject.get('attStmt'); - clientDataHash = await toHash(base64url.toBuffer(clientDataJSON)); + clientDataHash = await toHash(isoBase64URL.toBuffer(clientDataJSON)); const parsedAuthData = parseAuthenticatorData(authData); aaguid = parsedAuthData.aaguid!; @@ -87,11 +87,11 @@ test('should validate response with cert path completed with GlobalSign R1 root spyDate.mockReturnValue(new Date('2021-11-15T00:00:42.000Z')); const { attestationObject, clientDataJSON } = safetyNetUsingGSR1RootCert.response; - const decodedAttestationObject = decodeAttestationObject(base64url.toBuffer(attestationObject)); + const decodedAttestationObject = decodeAttestationObject(isoBase64URL.toBuffer(attestationObject)); const _authData = decodedAttestationObject.get('authData'); const _attStmt = decodedAttestationObject.get('attStmt'); - const _clientDataHash = await toHash(base64url.toBuffer(clientDataJSON)); + const _clientDataHash = await toHash(isoBase64URL.toBuffer(clientDataJSON)); const parsedAuthData = parseAuthenticatorData(_authData); const _aaguid = parsedAuthData.aaguid!; diff --git a/packages/server/src/registration/verifications/verifyAttestationAndroidSafetyNet.ts b/packages/server/src/registration/verifications/verifyAttestationAndroidSafetyNet.ts index 3879f40..285f919 100644 --- a/packages/server/src/registration/verifications/verifyAttestationAndroidSafetyNet.ts +++ b/packages/server/src/registration/verifications/verifyAttestationAndroidSafetyNet.ts @@ -5,8 +5,7 @@ import { verifySignature } from '../../helpers/verifySignature'; import { getCertificateInfo } from '../../helpers/getCertificateInfo'; import { validateCertificatePath } from '../../helpers/validateCertificatePath'; import { convertCertBufferToPEM } from '../../helpers/convertCertBufferToPEM'; -import * as isoUint8Array from '../../helpers/isoUint8Array'; -import * as isoBase64URL from '../../helpers/isoBase64URL'; +import { isoUint8Array, isoBase64URL } from '../../helpers/iso'; import { MetadataService } from '../../services/metadataService'; import { verifyAttestationWithMetadata } from '../../metadata/verifyAttestationWithMetadata'; @@ -38,11 +37,11 @@ export async function verifyAttestationAndroidSafetyNet( } // Prepare to verify a JWT - const jwt = uint8Array.toUTF8String(response); + const jwt = isoUint8Array.toUTF8String(response); const jwtParts = jwt.split('.'); - const HEADER: SafetyNetJWTHeader = JSON.parse(base64url.toString(jwtParts[0])); - const PAYLOAD: SafetyNetJWTPayload = JSON.parse(base64url.toString(jwtParts[1])); + const HEADER: SafetyNetJWTHeader = JSON.parse(isoBase64URL.toString(jwtParts[0])); + const PAYLOAD: SafetyNetJWTPayload = JSON.parse(isoBase64URL.toString(jwtParts[1])); const SIGNATURE: SafetyNetJWTSignature = jwtParts[2]; /** @@ -65,9 +64,9 @@ export async function verifyAttestationAndroidSafetyNet( } } - const nonceBase = uint8Array.concat([authData, clientDataHash]); + const nonceBase = isoUint8Array.concat([authData, clientDataHash]); const nonceBuffer = await toHash(nonceBase); - const expectedNonce = base64url.fromBuffer(nonceBuffer, 'base64'); + const expectedNonce = isoBase64URL.fromBuffer(nonceBuffer, 'base64'); if (nonce !== expectedNonce) { throw new Error('Could not verify payload nonce (SafetyNet)'); @@ -84,7 +83,7 @@ export async function verifyAttestationAndroidSafetyNet( * START Verify Header */ // `HEADER.x5c[0]` is definitely a base64 string - const leafCertBuffer = base64url.toBuffer(HEADER.x5c[0], 'base64'); + const leafCertBuffer = isoBase64URL.toBuffer(HEADER.x5c[0], 'base64'); const leafCertInfo = getCertificateInfo(leafCertBuffer); const { subject } = leafCertInfo; @@ -124,8 +123,8 @@ export async function verifyAttestationAndroidSafetyNet( /** * START Verify Signature */ - const signatureBaseBuffer = uint8Array.fromUTF8String(`${jwtParts[0]}.${jwtParts[1]}`); - const signatureBuffer = base64url.toBuffer(SIGNATURE); + const signatureBaseBuffer = isoUint8Array.fromUTF8String(`${jwtParts[0]}.${jwtParts[1]}`); + const signatureBuffer = isoBase64URL.toBuffer(SIGNATURE); const verified = await verifySignature({ signature: signatureBuffer, diff --git a/packages/server/src/registration/verifications/verifyAttestationApple.ts b/packages/server/src/registration/verifications/verifyAttestationApple.ts index d8f5465..bc7ceab 100644 --- a/packages/server/src/registration/verifications/verifyAttestationApple.ts +++ b/packages/server/src/registration/verifications/verifyAttestationApple.ts @@ -7,7 +7,7 @@ import { validateCertificatePath } from '../../helpers/validateCertificatePath'; import { convertCertBufferToPEM } from '../../helpers/convertCertBufferToPEM'; import { toHash } from '../../helpers/toHash'; import { convertCOSEtoPKCS } from '../../helpers/convertCOSEtoPKCS'; -import * as isoUint8Array from '../../helpers/isoUint8Array'; +import { isoUint8Array } from '../../helpers/iso'; export async function verifyAttestationApple( options: AttestationFormatVerifierOpts, @@ -45,7 +45,7 @@ export async function verifyAttestationApple( throw new Error('credCert missing "1.2.840.113635.100.8.2" extension (Apple)'); } - const nonceToHash = uint8Array.concat([authData, clientDataHash]); + const nonceToHash = isoUint8Array.concat([authData, clientDataHash]); const nonce = await toHash(nonceToHash, 'SHA256'); /** * Ignore the first six ASN.1 structure bytes that define the nonce as an OCTET STRING. Should @@ -56,7 +56,7 @@ export async function verifyAttestationApple( */ const extNonce = new Uint8Array(extCertNonce.extnValue.buffer).slice(6); - if (!uint8Array.areEqual(nonce, extNonce)) { + if (!isoUint8Array.areEqual(nonce, extNonce)) { throw new Error(`credCert nonce was not expected value (Apple)`); } @@ -66,7 +66,7 @@ export async function verifyAttestationApple( const credPubKeyPKCS = convertCOSEtoPKCS(credentialPublicKey); const credCertSubjectPublicKey = new Uint8Array(subjectPublicKeyInfo.subjectPublicKey); - if (!uint8Array.areEqual(credPubKeyPKCS, credCertSubjectPublicKey)) { + if (!isoUint8Array.areEqual(credPubKeyPKCS, credCertSubjectPublicKey)) { throw new Error('Credential public key does not equal credCert public key (Apple)'); } diff --git a/packages/server/src/registration/verifications/verifyAttestationFIDOU2F.ts b/packages/server/src/registration/verifications/verifyAttestationFIDOU2F.ts index 7e57ab5..0b681fa 100644 --- a/packages/server/src/registration/verifications/verifyAttestationFIDOU2F.ts +++ b/packages/server/src/registration/verifications/verifyAttestationFIDOU2F.ts @@ -4,7 +4,7 @@ import { convertCOSEtoPKCS } from '../../helpers/convertCOSEtoPKCS'; import { convertCertBufferToPEM } from '../../helpers/convertCertBufferToPEM'; import { validateCertificatePath } from '../../helpers/validateCertificatePath'; import { verifySignature } from '../../helpers/verifySignature'; -import * as isoUint8Array from '../../helpers/isoUint8Array'; +import { isoUint8Array } from '../../helpers/iso'; /** * Verify an attestation response with fmt 'fido-u2f' @@ -25,7 +25,7 @@ export async function verifyAttestationFIDOU2F( const reservedByte = Uint8Array.from([0x00]); const publicKey = convertCOSEtoPKCS(credentialPublicKey); - const signatureBase = uint8Array.concat([ + const signatureBase = isoUint8Array.concat([ reservedByte, rpIdHash, clientDataHash, @@ -45,7 +45,7 @@ export async function verifyAttestationFIDOU2F( } // FIDO spec says that aaguid _must_ equal 0x00 here to be legit - const aaguidToHex = Number.parseInt(uint8Array.toHex(aaguid), 16); + const aaguidToHex = Number.parseInt(isoUint8Array.toHex(aaguid), 16); if (aaguidToHex !== 0x00) { throw new Error(`AAGUID "${aaguidToHex}" was not expected value`); } diff --git a/packages/server/src/registration/verifications/verifyAttestationPacked.ts b/packages/server/src/registration/verifications/verifyAttestationPacked.ts index 4250595..e4fdf92 100644 --- a/packages/server/src/registration/verifications/verifyAttestationPacked.ts +++ b/packages/server/src/registration/verifications/verifyAttestationPacked.ts @@ -5,7 +5,7 @@ import { convertCertBufferToPEM } from '../../helpers/convertCertBufferToPEM'; import { validateCertificatePath } from '../../helpers/validateCertificatePath'; import { getCertificateInfo } from '../../helpers/getCertificateInfo'; import { verifySignature } from '../../helpers/verifySignature'; -import * as isoUint8Array from '../../helpers/isoUint8Array'; +import { isoUint8Array } from '../../helpers/iso'; import { MetadataService } from '../../services/metadataService'; import { verifyAttestationWithMetadata } from '../../metadata/verifyAttestationWithMetadata'; @@ -30,7 +30,7 @@ export async function verifyAttestationPacked( throw new Error(`Attestation Statement alg "${alg}" is not a number (Packed)`); } - const signatureBase = uint8Array.concat([authData, clientDataHash]); + const signatureBase = isoUint8Array.concat([authData, clientDataHash]); let verified = false; diff --git a/packages/server/src/registration/verifyRegistrationResponse.test.ts b/packages/server/src/registration/verifyRegistrationResponse.test.ts index 277aaff..92ea27a 100644 --- a/packages/server/src/registration/verifyRegistrationResponse.test.ts +++ b/packages/server/src/registration/verifyRegistrationResponse.test.ts @@ -7,8 +7,7 @@ import * as esmDecodeClientDataJSON from '../helpers/decodeClientDataJSON'; import * as esmParseAuthenticatorData from '../helpers/parseAuthenticatorData'; import * as esmDecodeCredentialPublicKey from '../helpers/decodeCredentialPublicKey'; import { toHash } from '../helpers/toHash'; -import * as base64url from '../helpers/base64url'; -import * as uint8Array from '../helpers/uint8Array'; +import { isoBase64URL, isoUint8Array } from '../helpers/iso'; import { COSEPublicKey, COSEKEYS } from '../helpers/convertCOSEtoPKCS'; import { SettingsService } from '../services/settingsService'; @@ -54,12 +53,12 @@ test('should verify FIDO U2F attestation', async () => { expect(verification.registrationInfo?.fmt).toEqual('fido-u2f'); expect(verification.registrationInfo?.counter).toEqual(0); expect(verification.registrationInfo?.credentialPublicKey).toEqual( - base64url.toBuffer( + isoBase64URL.toBuffer( 'pQECAyYgASFYIMiRyw5pUoMhBjCrcQND6lJPaRHA0f-XWcKBb5ZwWk1eIlggFJu6aan4o7epl6qa9n9T-6KsIMvZE2PcTnLj8rN58is', ), ); expect(verification.registrationInfo?.credentialID).toEqual( - base64url.toBuffer( + isoBase64URL.toBuffer( 'VHzbxaYaJu2P8m1Y2iHn2gRNHrgK0iYbn9E978L3Qi7Q-chFeicIHwYCRophz5lth2nCgEVKcgWirxlgidgbUQ', ), ); @@ -67,7 +66,7 @@ test('should verify FIDO U2F attestation', async () => { expect(verification.registrationInfo?.credentialType).toEqual('public-key'); expect(verification.registrationInfo?.userVerified).toEqual(false); expect(verification.registrationInfo?.attestationObject).toEqual( - base64url.toBuffer(attestationFIDOU2F.response.attestationObject), + isoBase64URL.toBuffer(attestationFIDOU2F.response.attestationObject), ); }); @@ -83,12 +82,12 @@ test('should verify Packed (EC2) attestation', async () => { expect(verification.registrationInfo?.fmt).toEqual('packed'); expect(verification.registrationInfo?.counter).toEqual(1589874425); expect(verification.registrationInfo?.credentialPublicKey).toEqual( - base64url.toBuffer( + isoBase64URL.toBuffer( 'pQECAyYgASFYIEoxVVqK-oIGmqoDEyO4KjmMx5R2HeMM4LQQXh8sE01PIlggtzuuoMN5fWnAIuuXdlfshOGu1k3ApBUtDJ8eKiuo_6c', ), ); expect(verification.registrationInfo?.credentialID).toEqual( - base64url.toBuffer( + isoBase64URL.toBuffer( 'AYThY1csINY4JrbHyGmqTl1nL_F1zjAF3hSAIngz8kAcjugmAMNVvxZRwqpEH-bNHHAIv291OX5ko9eDf_5mu3U' + 'B2BvsScr2K-ppM4owOpGsqwg5tZglqqmxIm1Q', ), @@ -107,12 +106,12 @@ test('should verify Packed (X5C) attestation', async () => { expect(verification.registrationInfo?.fmt).toEqual('packed'); expect(verification.registrationInfo?.counter).toEqual(28); expect(verification.registrationInfo?.credentialPublicKey).toEqual( - base64url.toBuffer( + isoBase64URL.toBuffer( 'pQECAyYgASFYIGwlsYCNyRb4AD9cyTw6cH5VS-uzflmmO1UldGGe9eIaIlggvadzKD8p6wKLjgYfxRxldjCMGRV0YyM13osWbKIPrF8', ), ); expect(verification.registrationInfo?.credentialID).toEqual( - base64url.toBuffer( + isoBase64URL.toBuffer( '4rrvMciHCkdLQ2HghazIp1sMc8TmV8W8RgoX-x8tqV_1AmlqWACqUK8mBGLandr-htduQKPzgb2yWxOFV56Tlg', ), ); @@ -130,12 +129,12 @@ test('should verify None attestation', async () => { expect(verification.registrationInfo?.fmt).toEqual('none'); expect(verification.registrationInfo?.counter).toEqual(0); expect(verification.registrationInfo?.credentialPublicKey).toEqual( - base64url.toBuffer( + isoBase64URL.toBuffer( 'pQECAyYgASFYID5PQTZQQg6haZFQWFzqfAOyQ_ENsMH8xxQ4GRiNPsqrIlggU8IVUOV8qpgk_Jh-OTaLuZL52KdX1fTht07X4DiQPow', ), ); expect(verification.registrationInfo?.credentialID).toEqual( - base64url.toBuffer( + isoBase64URL.toBuffer( 'AdKXJEch1aV5Wo7bj7qLHskVY4OoNaj9qu8TPdJ7kSAgUeRxWNngXlcNIGt4gexZGKVGcqZpqqWordXb_he1izY', ), ); @@ -165,12 +164,12 @@ test('should verify None attestation w/RSA public key', async () => { expect(verification.registrationInfo?.fmt).toEqual('none'); expect(verification.registrationInfo?.counter).toEqual(0); expect(verification.registrationInfo?.credentialPublicKey).toEqual( - base64url.toBuffer( + isoBase64URL.toBuffer( 'pAEDAzkBACBZAQDxfpXrj0ba_AH30JJ_-W7BHSOPugOD8aEDdNBKc1gjB9AmV3FPl2aL0fwiOMKtM_byI24qXb2FzcyjC7HUVkHRtzkAQnahXckI4wY_01koaY6iwXuIE3Ya0Zjs2iZyz6u4G_abGnWdObqa_kHxc3CHR7Xy5MDkAkKyX6TqU0tgHZcEhDd_Lb5ONJDwg4wvKlZBtZYElfMuZ6lonoRZ7qR_81rGkDZyFaxp6RlyvzEbo4ijeIaHQylqCz-oFm03ifZMOfRHYuF4uTjJDRH-g4BW1f3rdi7DTHk1hJnIw1IyL_VFIQ9NifkAguYjNCySCUNpYli2eMrPhAu5dYJFFjINIUMBAAE', ), ); expect(verification.registrationInfo?.credentialID).toEqual( - base64url.toBuffer('kGXv4RJWLeXRw8Yf3T22K3Gq_GGeDv9OKYmAHLm0Ylo'), + isoBase64URL.toBuffer('kGXv4RJWLeXRw8Yf3T22K3Gq_GGeDv9OKYmAHLm0Ylo'), ); }); @@ -219,7 +218,7 @@ test('should throw when attestation type is not webauthn.create', async () => { test('should throw if an unexpected attestation format is specified', async () => { const realAtteObj = esmDecodeAttestationObject.decodeAttestationObject( - base64url.toBuffer(attestationNone.response.attestationObject), + isoBase64URL.toBuffer(attestationNone.response.attestationObject), ); // Mangle the fmt (realAtteObj as Map).set('fmt', 'fizzbuzz'); @@ -238,7 +237,7 @@ test('should throw if an unexpected attestation format is specified', async () = test('should throw error if assertion RP ID is unexpected value', async () => { const authData = esmDecodeAttestationObject.decodeAttestationObject( - base64url.toBuffer(attestationNone.response.attestationObject), + isoBase64URL.toBuffer(attestationNone.response.attestationObject), ).get('authData'); const actualAuthData = esmParseAuthenticatorData.parseAuthenticatorData(authData); @@ -401,12 +400,12 @@ test('should validate TPM RSA response (SHA256)', async () => { expect(verification.registrationInfo?.fmt).toEqual('tpm'); expect(verification.registrationInfo?.counter).toEqual(30); expect(verification.registrationInfo?.credentialPublicKey).toEqual( - base64url.toBuffer( + isoBase64URL.toBuffer( 'pAEDAzkBACBZAQCtxzw59Wsl8xWP97wPTu2TSDlushwshL8GedHAHO1R62m3nNy21hCLJlQabfLepRUQ_v9mq3PCmV81tBSqtRGU5_YlK0R2yeu756SnT39c6hKC3PBPt_xdjL_ccz4H_73DunfB63QZOtdeAsswV7WPLqMARofuM-LQ_LHnNguCypDcxhADuUqQtogfwZsknTVIPxzGcfqnQ7ERF9D9AOWIQ8YjOsTi_B2zS8SOySKIFUGwwYcPG7DiCE-QJcI-fpydRDnEq6UxbkYgB7XK4BlmPKlwuXkBDX9egl_Ma4B7W2WJvYbKevu6Z8Kc5y-OITpNVDYKbBK3qKyh4yIUpB1NIUMBAAE', ), ); expect(verification.registrationInfo?.credentialID).toEqual( - base64url.toBuffer('lGkWHPe88VpnNYgVBxzon_MRR9-gmgODveQ16uM_bPM'), + isoBase64URL.toBuffer('lGkWHPe88VpnNYgVBxzon_MRR9-gmgODveQ16uM_bPM'), ); }); @@ -434,12 +433,12 @@ test('should validate TPM RSA response (SHA1)', async () => { expect(verification.registrationInfo?.fmt).toEqual('tpm'); expect(verification.registrationInfo?.counter).toEqual(97); expect(verification.registrationInfo?.credentialPublicKey).toEqual( - base64url.toBuffer( + isoBase64URL.toBuffer( 'pAEDAzn__iBZAQCzl_wD24PZ5z-po2FrwoQVdd13got_CkL8p4B_NvJBC5OwAYKDilii_wj-0CA8ManbpSInx9Tdnz6t91OhudwUT0-W_BHSLK_MqFcjZWrR5LYVmVpz1EgH3DrOTra4AlogEq2D2CYktPrPe7joE-oT3vAYXK8vzQDLRyaxI_Z1qS4KLlLCdajW8PGpw1YRjMDw6s69GZU8mXkgNPMCUh1TZ1bnCvJTO9fnmLjDjqdQGRU4bWo8tFjCL8g1-2WD_2n0-twt6n-Uox5VnR1dQJG4awMlanBCkGGpOb3WBDQ8K10YJJ2evPhJKGJahBvu2Dxmq6pLCAXCv0ma3EHj-PmDIUMBAAE', ), ); expect(verification.registrationInfo?.credentialID).toEqual( - base64url.toBuffer('oELnad0f6-g2BtzEn_78iLNoubarlq0xFtOtAMXnflU'), + isoBase64URL.toBuffer('oELnad0f6-g2BtzEn_78iLNoubarlq0xFtOtAMXnflU'), ); }); @@ -467,12 +466,12 @@ test('should validate Android-Key response', async () => { expect(verification.registrationInfo?.fmt).toEqual('android-key'); expect(verification.registrationInfo?.counter).toEqual(108); expect(verification.registrationInfo?.credentialPublicKey).toEqual( - base64url.toBuffer( + isoBase64URL.toBuffer( 'pQECAyYgASFYIEjCq7woGNN_42rbaqMgJvz0nuKTWNRrR29lMX3J239oIlgg6IcAXqPJPIjSrClHDAmbJv_EShYhYq0R9-G3k744n7Y', ), ); expect(verification.registrationInfo?.credentialID).toEqual( - base64url.toBuffer('PPa1spYTB680cQq5q6qBtFuPLLdG1FQ73EastkT8n0o'), + isoBase64URL.toBuffer('PPa1spYTB680cQq5q6qBtFuPLLdG1FQ73EastkT8n0o'), ); }); @@ -538,7 +537,7 @@ test('should pass verification if custom challenge verifier returns true', async }, expectedChallenge: (challenge: string) => { const parsedChallenge: { actualChallenge: string; arbitraryData: string } = JSON.parse( - base64url.toString(challenge), + isoBase64URL.toString(challenge), ); return parsedChallenge.actualChallenge === 'xRsYdCQv5WZOqmxReiZl6C9q5SfrZne4lNSr9QVtPig'; }, @@ -596,15 +595,15 @@ test('should return authenticator extension output', async () => { expect(verification.registrationInfo?.authenticatorExtensionResults).toMatchObject({ devicePubKey: { - dpk: uint8Array.fromHex( + dpk: isoUint8Array.fromHex( 'A5010203262001215820991AABED9DE4271A9EDEAD8806F9DC96D6DCCD0C476253A5510489EC8379BE5B225820A0973CFDEDBB79E27FEF4EE7481673FB3312504DDCA5434CFD23431D6AD29EDA', ), - sig: uint8Array.fromHex( + sig: isoUint8Array.fromHex( '3045022100EFB38074BD15B8C82CF09F87FBC6FB3C7169EA4F1806B7E90937374302345B7A02202B7113040731A0E727D338D48542863CE65880AA79E5EA740AC8CCD94347988E', ), - nonce: uint8Array.fromHex(''), - scope: uint8Array.fromHex('00'), - aaguid: uint8Array.fromHex('00000000000000000000000000000000'), + nonce: isoUint8Array.fromHex(''), + scope: isoUint8Array.fromHex('00'), + aaguid: isoUint8Array.fromHex('00000000000000000000000000000000'), }, }); }); @@ -625,7 +624,7 @@ const attestationFIDOU2F: RegistrationCredentialJSON = { clientExtensionResults: {}, type: 'public-key', }; -const attestationFIDOU2FChallenge = base64url.fromString('totallyUniqueValueEveryAttestation'); +const attestationFIDOU2FChallenge = isoBase64URL.fromString('totallyUniqueValueEveryAttestation'); const attestationPacked: RegistrationCredentialJSON = { id: 'bbb', @@ -646,7 +645,7 @@ const attestationPacked: RegistrationCredentialJSON = { clientExtensionResults: {}, type: 'public-key', }; -const attestationPackedChallenge = base64url.fromString('s6PIbBnPPnrGNSBxNdtDrT7UrVYJK9HM'); +const attestationPackedChallenge = isoBase64URL.fromString('s6PIbBnPPnrGNSBxNdtDrT7UrVYJK9HM'); const attestationPackedX5C: RegistrationCredentialJSON = { // TODO: Grab these from another iPhone attestation @@ -677,7 +676,7 @@ const attestationPackedX5C: RegistrationCredentialJSON = { clientExtensionResults: {}, type: 'public-key', }; -const attestationPackedX5CChallenge = base64url.fromString('totallyUniqueValueEveryTime'); +const attestationPackedX5CChallenge = isoBase64URL.fromString('totallyUniqueValueEveryTime'); const attestationNone: RegistrationCredentialJSON = { id: 'AdKXJEch1aV5Wo7bj7qLHskVY4OoNaj9qu8TPdJ7kSAgUeRxWNngXlcNIGt4gexZGKVGcqZpqqWordXb_he1izY', @@ -696,4 +695,4 @@ const attestationNone: RegistrationCredentialJSON = { clientExtensionResults: {}, type: 'public-key', }; -const attestationNoneChallenge = base64url.fromString('hEccPWuziP00H0p5gxh2_u5_PC4NeYgd'); +const attestationNoneChallenge = isoBase64URL.fromString('hEccPWuziP00H0p5gxh2_u5_PC4NeYgd'); diff --git a/packages/server/src/registration/verifyRegistrationResponse.ts b/packages/server/src/registration/verifyRegistrationResponse.ts index 6ed3ca9..4b1cbc7 100644 --- a/packages/server/src/registration/verifyRegistrationResponse.ts +++ b/packages/server/src/registration/verifyRegistrationResponse.ts @@ -18,8 +18,7 @@ import { COSEKEYS } from '../helpers/convertCOSEtoPKCS'; import { convertAAGUIDToString } from '../helpers/convertAAGUIDToString'; import { parseBackupFlags } from '../helpers/parseBackupFlags'; import { matchExpectedRPID } from '../helpers/matchExpectedRPID'; -import * as isoUint8Array from '../helpers/isoUint8Array'; -import * as isoBase64URL from '../helpers/isoBase64URL'; +import { isoBase64URL } from '../helpers/iso'; import { SettingsService } from '../services/settingsService'; import { supportedCOSEAlgorithmIdentifiers } from './generateRegistrationOptions'; @@ -131,7 +130,7 @@ export async function verifyRegistrationResponse( } } - const attestationObject = base64url.toBuffer(response.attestationObject); + const attestationObject = isoBase64URL.toBuffer(response.attestationObject); const decodedAttestationObject = decodeAttestationObject(attestationObject); const fmt = decodedAttestationObject.get('fmt'); const authData = decodedAttestationObject.get('authData'); @@ -188,7 +187,7 @@ export async function verifyRegistrationResponse( throw new Error(`Unexpected public key alg "${alg}", expected one of "${supported}"`); } - const clientDataHash = await toHash(base64url.toBuffer(response.clientDataJSON)); + const clientDataHash = await toHash(isoBase64URL.toBuffer(response.clientDataJSON)); const rootCertificates = SettingsService.getRootCertificates({ identifier: fmt }); // Prepare arguments to pass to the relevant verification method -- cgit v1.2.3