diff options
Diffstat (limited to 'packages/server/src')
8 files changed, 243 insertions, 115 deletions
diff --git a/packages/server/src/helpers/convertCertBufferToPEM.ts b/packages/server/src/helpers/convertCertBufferToPEM.ts index 8de140d..b6949c4 100644 --- a/packages/server/src/helpers/convertCertBufferToPEM.ts +++ b/packages/server/src/helpers/convertCertBufferToPEM.ts @@ -5,15 +5,17 @@ import type { Base64URLString } from '@simplewebauthn/typescript-types'; * Convert buffer to an OpenSSL-compatible PEM text format. */ export function convertCertBufferToPEM(certBuffer: Buffer | Base64URLString): string { - let buffer: Buffer; + let b64cert: string; + + /** + * Get certBuffer to a base64 representation + */ if (typeof certBuffer === 'string') { - buffer = base64url.toBuffer(certBuffer); + b64cert = base64url.toBase64(certBuffer); } else { - buffer = certBuffer; + b64cert = certBuffer.toString('base64'); } - const b64cert = buffer.toString('base64'); - let PEMKey = ''; for (let i = 0; i < Math.ceil(b64cert.length / 64); i += 1) { const start = 64 * i; diff --git a/packages/server/src/metadata/mdsTypes.ts b/packages/server/src/metadata/mdsTypes.ts index 22ba564..1bf9f80 100644 --- a/packages/server/src/metadata/mdsTypes.ts +++ b/packages/server/src/metadata/mdsTypes.ts @@ -176,7 +176,7 @@ export type MetadataStatement = { /** * USER_VERIFY - * https://fidoalliance.org/specs/common-specs/fido-registry-v2.1-ps-20191217.html#user-verification-methods + * https://fidoalliance.org/specs/common-specs/fido-registry-v2.2-ps-20220523.html#user-verification-methods */ export type UserVerify = | 'presence_internal' @@ -195,55 +195,56 @@ export type UserVerify = /** * ALG_SIGN - * https://fidoalliance.org/specs/common-specs/fido-registry-v2.1-ps-20191217.html#authentication-algorithms + * https://fidoalliance.org/specs/common-specs/fido-registry-v2.2-ps-20220523.html#authentication-algorithms + * + * Using this helpful TS pattern here so that we can strongly enforce the existence of COSE info + * mappings in `algSignToCOSEInfoMap` in verifyAttestationWithMetadata.ts */ -export type AlgSign = - | 'secp256r1_ecdsa_sha256_raw' - | 'secp256r1_ecdsa_sha256_der' - | 'rsassa_pss_sha256_raw' - | 'rsassa_pss_sha256_der' - | 'secp256k1_ecdsa_sha256_raw' - | 'secp256k1_ecdsa_sha256_der' - | 'sm2_sm3_raw' - | 'rsa_emsa_pkcs1_sha256_raw' - | 'rsa_emsa_pkcs1_sha256_der' - | 'rsassa_pss_sha384_raw' - | 'rsassa_pss_sha256_raw' - | 'rsassa_pkcsv15_sha256_raw' - | 'rsassa_pkcsv15_sha384_raw' - | 'rsassa_pkcsv15_sha512_raw' - | 'rsassa_pkcsv15_sha1_raw' - | 'secp384r1_ecdsa_sha384_raw' - | 'secp512r1_ecdsa_sha256_raw' - | 'ed25519_eddsa_sha512_raw'; +export type AlgSign = typeof AlgSign[number]; +const AlgSign = [ + 'secp256r1_ecdsa_sha256_raw', + 'secp256r1_ecdsa_sha256_der', + 'rsassa_pss_sha256_raw', + 'rsassa_pss_sha256_der', + 'secp256k1_ecdsa_sha256_raw', + 'secp256k1_ecdsa_sha256_der', + 'rsassa_pss_sha384_raw', + 'rsassa_pkcsv15_sha256_raw', + 'rsassa_pkcsv15_sha384_raw', + 'rsassa_pkcsv15_sha512_raw', + 'rsassa_pkcsv15_sha1_raw', + 'secp384r1_ecdsa_sha384_raw', + 'secp512r1_ecdsa_sha256_raw', + 'ed25519_eddsa_sha512_raw', +] as const; /** * ALG_KEY - * https://fidoalliance.org/specs/common-specs/fido-registry-v2.1-ps-20191217.html#public-key-representation-formats + * https://fidoalliance.org/specs/common-specs/fido-registry-v2.2-ps-20220523.html#public-key-representation-formats */ export type AlgKey = 'ecc_x962_raw' | 'ecc_x962_der' | 'rsa_2048_raw' | 'rsa_2048_der' | 'cose'; /** * ATTESTATION - * https://fidoalliance.org/specs/common-specs/fido-registry-v2.1-ps-20191217.html#authenticator-attestation-types + * https://fidoalliance.org/specs/common-specs/fido-registry-v2.2-ps-20220523.html#authenticator-attestation-types */ -export type Attestation = 'basic_full' | 'basic_surrogate' | 'ecdaa' | 'attca'; +export type Attestation = 'basic_full' | 'basic_surrogate' | 'ecdaa' | 'attca' | 'anonca' | 'none'; /** * KEY_PROTECTION - * https://fidoalliance.org/specs/common-specs/fido-registry-v2.1-ps-20191217.html#key-protection-types + * https://fidoalliance.org/specs/common-specs/fido-registry-v2.2-ps-20220523.html#key-protection-types */ export type KeyProtection = 'software' | 'hardware' | 'tee' | 'secure_element' | 'remote_handle'; /** * MATCHER_PROTECTION - * https://fidoalliance.org/specs/common-specs/fido-registry-v2.1-ps-20191217.html#matcher-protection-types + * https://fidoalliance.org/specs/common-specs/fido-registry-v2.2-ps-20220523.html#matcher-protection-types */ export type MatcherProtection = 'software' | 'tee' | 'on_chip'; /** * ATTACHMENT_HINT - * https://fidoalliance.org/specs/common-specs/fido-registry-v2.1-ps-20191217.html#authenticator-attachment-hints + * https://fidoalliance.org/specs/common-specs/fido-registry-v2.2-ps-20220523.html#authenticator-attachment-hints */ export type AttachmentHint = | 'internal' @@ -258,7 +259,7 @@ export type AttachmentHint = /** * TRANSACTION_CONFIRMATION_DISPLAY - * https://fidoalliance.org/specs/common-specs/fido-registry-v2.1-ps-20191217.html#transaction-confirmation-display-types + * https://fidoalliance.org/specs/common-specs/fido-registry-v2.2-ps-20220523.html#transaction-confirmation-display-types */ export type TransactionConfirmationDisplay = | 'any' @@ -291,4 +292,5 @@ export type AuthenticatorGetInfo = { }; maxMsgSize?: number; pinProtocols?: number[]; + algorithms?: { type: 'public-key', alg: number }[]; }; diff --git a/packages/server/src/metadata/verifyAttestationWithMetadata.test.ts b/packages/server/src/metadata/verifyAttestationWithMetadata.test.ts index 24165d2..b48ef2e 100644 --- a/packages/server/src/metadata/verifyAttestationWithMetadata.test.ts +++ b/packages/server/src/metadata/verifyAttestationWithMetadata.test.ts @@ -47,11 +47,11 @@ test('should verify attestation with metadata (android-safetynet)', async () => const credentialPublicKey = 'pQECAyYgASFYIAKH2NrGZT-lUEA3tbBXR9owjW_7OnA1UqoL1UuKY_VCIlggpjeOH0xyBCpGDya55JLXXKrzyOieQN3dvG1pV-Qs-Gs'; - const verified = await verifyAttestationWithMetadata( - metadataStatementJSONSafetyNet, - base64url.toBuffer(credentialPublicKey), + const verified = await verifyAttestationWithMetadata({ + statement: metadataStatementJSONSafetyNet, + credentialPublicKey: base64url.toBuffer(credentialPublicKey), x5c, - ); + }); expect(verified).toEqual(true); }); @@ -65,7 +65,7 @@ test('should verify attestation with rsa_emsa_pkcs1_sha256_raw authenticator alg 'protocolFamily': 'fido2', 'schema': 3, 'upv': [{ 'major': 1, 'minor': 0 }], - 'authenticationAlgorithms': ['rsa_emsa_pkcs1_sha256_raw'], + 'authenticationAlgorithms': ['rsassa_pkcsv15_sha256_raw'], 'publicKeyAlgAndEncodings': ['cose'], 'attestationTypes': ['attca'], 'userVerificationDetails': [ @@ -97,11 +97,68 @@ test('should verify attestation with rsa_emsa_pkcs1_sha256_raw authenticator alg ]; const credentialPublicKey = 'pAEDAzkBACBZAQC3X5SKwYUkxFxxyvCnz_37Z57eSdsgQuiBLDaBOd1R6VEZReAV3nVr_7jiRgmWfu1C-S3Aro65eSG5shcDCgIvY3KdEI8K5ENEPlmucjnFILBAE_MZtPmZlkEDmVCDcVspHX2iKqiVWYV6IFzVX1QUf0SAlWijV9NEfKDbij34ddV0qfG2nEMA0_xVpN2OK2BVXonFg6tS3T00XlFh4MdzIauIHTDT63eAdHlkFrMqU53T5IqDvL3VurBmBjYRJ3VDT9mA2sm7fSrJNXhSVLPst-ZsiOioVKrpzFE9sJmyCQvq2nGZ2RhDo8FfAKiw0kvJRkCSSe1ddxryk9_VSCprIUMBAAE'; - const verified = await verifyAttestationWithMetadata( - metadataStatement, - base64url.toBuffer(credentialPublicKey), + const verified = await verifyAttestationWithMetadata({ + statement: metadataStatement, + credentialPublicKey: base64url.toBuffer(credentialPublicKey), x5c, - ); + }); + + expect(verified).toEqual(true); +}); + +test('should not validate certificate path when authenticator is self-referencing its attestation statement certificates', async () => { + const metadataStatement: MetadataStatement = { + "legalHeader": "https://fidoalliance.org/metadata/metadata-statement-legal-header/", + "description": "Virtual Secp256R1 FIDO2 Conformance Testing CTAP2 Authenticator with Self Batch Referencing", + "aaguid": "5b65dac1-7af4-46e6-8a4f-8701fcc4f3b4", + "alternativeDescriptions": { + "ru-RU": "Виртуальный Secp256R1 CTAP2 аутентификатор для тестирование серверов на соответсвие спецификации FIDO2 с одинаковыми сертификатами" + }, + "protocolFamily": "fido2", + "authenticatorVersion": 2, + "upv": [{ "major": 1, "minor": 0 }], + "authenticationAlgorithms": ["secp256r1_ecdsa_sha256_raw"], + "publicKeyAlgAndEncodings": ["cose"], + "attestationTypes": ["basic_full"], + "schema": 3, + "userVerificationDetails": [ + [{ "userVerificationMethod": "none" }], + [{ "userVerificationMethod": "presence_internal" }], + [{ "userVerificationMethod": "passcode_external", "caDesc": { "base": 10, "minLength": 4 } }], + [ + { "userVerificationMethod": "passcode_external", "caDesc": { "base": 10, "minLength": 4 } }, + { "userVerificationMethod": "presence_internal" } + ] + ], + "keyProtection": ["hardware", "secure_element"], + "matcherProtection": ["on_chip"], + "cryptoStrength": 128, + "attachmentHint": ["external", "wired", "wireless", "nfc"], + "tcDisplay": [], + "attestationRootCertificates": [ + "MIIEQTCCAimgAwIBAgIBATANBgkqhkiG9w0BAQsFADCBoTEYMBYGA1UEAwwPRklETzIgVEVTVCBST09UMTEwLwYJKoZIhvcNAQkBFiJjb25mb3JtYW5jZS10b29sc0BmaWRvYWxsaWFuY2Uub3JnMRYwFAYDVQQKDA1GSURPIEFsbGlhbmNlMQwwCgYDVQQLDANDV0cxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJNWTESMBAGA1UEBwwJV2FrZWZpZWxkMB4XDTE4MDUyMzE0Mzk0M1oXDTI4MDUyMDE0Mzk0M1owgcIxIzAhBgNVBAMMGkZJRE8yIEJBVENIIEtFWSBwcmltZTI1NnYxMTEwLwYJKoZIhvcNAQkBFiJjb25mb3JtYW5jZS10b29sc0BmaWRvYWxsaWFuY2Uub3JnMRYwFAYDVQQKDA1GSURPIEFsbGlhbmNlMSIwIAYDVQQLDBlBdXRoZW50aWNhdG9yIEF0dGVzdGF0aW9uMQswCQYDVQQGEwJVUzELMAkGA1UECAwCTVkxEjAQBgNVBAcMCVdha2VmaWVsZDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABE86Xl6rbB+8rpf232RJlnYse+9yAEAqdsbyMPZVbxeqmZtZf8S/UIqvjp7wzQE/Wrm9J5FL8IBDeMvMsRuJtUajLDAqMAkGA1UdEwQCMAAwHQYDVR0OBBYEFFZN98D4xlW2oR9sTRnzv0Hi/QF5MA0GCSqGSIb3DQEBCwUAA4ICAQCH3aCf+CCJBdEtQc4JpOnUelwGGw7DxnBMokHHBgrzJxDn9BFcFwxGLxrFV7EfYehQNOD+74OS8fZRgZiNf9EDGAYiHh0+CspfBWd20zCIjlCdDBcyhwq3PLJ65JC/og3CT9AK4kvks4DI+01RYxNv9S8Jx1haO1lgU55hBIr1P/p21ZKnpcCEhPjB/cIFrHJqL5iJGfed+LXni9Suq24OHnp44Mrv4h7OD2elu5yWfdfFb+RGG2TYURFIGYGijsii093w0ZMBOfBS+3Xq/DrHeZbZrrNkY455gJCZ5eV83Nrt9J9/UF0VZHl/hwnSAUC/b3tN/l0ZlC9kPcNzJD04l4ndFBD2KdfQ2HGTX7pybWLZ7yH2BM3ui2OpiacaOzd7OE91rHYB2uZyQ7jdg25yF9M8QI9NHM/itCjdBvAYt4QCT8dX6gmZiIGR2F/YXZAsybtJ16pnUmODVbW80lPbzy+PUQYX79opeD9u6MBorzr9g08Elpb1F3DgSd8VSLlsR2QPllKl4AcJDMIOfZHOQGOzatMV7ipEVRa0L5FnjAWpHHvSNcsjD4Cul562mO3MlI2pCyo+US+nIzG5XZmOeu4Db/Kw/dEPOo2ztHwlU0qKJ7REBsbt63jdQtlwLuiLHwkpiwnrAOZfwbLLu9Yz4tL1eJlQffuwS/Aolsz7HA==" + ], + "supportedExtensions": [{ "id": "hmac-secret", "fail_if_unknown": false }, { "id": "credProtect", "fail_if_unknown": false } + ], + "authenticatorGetInfo": { + "versions": ["U2F_V2", "FIDO_2_0"], + "extensions": ["credProtect", "hmac-secret"], + "aaguid": "5b65dac17af446e68a4f8701fcc4f3b4", + "options": { "plat": false, "rk": true, "clientPin": true, "up": true, "uv": true }, + "maxMsgSize": 1200, + } + }; + + const x5c = [ + 'MIIEQTCCAimgAwIBAgIBATANBgkqhkiG9w0BAQsFADCBoTEYMBYGA1UEAwwPRklETzIgVEVTVCBST09UMTEwLwYJKoZIhvcNAQkBFiJjb25mb3JtYW5jZS10b29sc0BmaWRvYWxsaWFuY2Uub3JnMRYwFAYDVQQKDA1GSURPIEFsbGlhbmNlMQwwCgYDVQQLDANDV0cxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJNWTESMBAGA1UEBwwJV2FrZWZpZWxkMB4XDTE4MDUyMzE0Mzk0M1oXDTI4MDUyMDE0Mzk0M1owgcIxIzAhBgNVBAMMGkZJRE8yIEJBVENIIEtFWSBwcmltZTI1NnYxMTEwLwYJKoZIhvcNAQkBFiJjb25mb3JtYW5jZS10b29sc0BmaWRvYWxsaWFuY2Uub3JnMRYwFAYDVQQKDA1GSURPIEFsbGlhbmNlMSIwIAYDVQQLDBlBdXRoZW50aWNhdG9yIEF0dGVzdGF0aW9uMQswCQYDVQQGEwJVUzELMAkGA1UECAwCTVkxEjAQBgNVBAcMCVdha2VmaWVsZDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABE86Xl6rbB-8rpf232RJlnYse-9yAEAqdsbyMPZVbxeqmZtZf8S_UIqvjp7wzQE_Wrm9J5FL8IBDeMvMsRuJtUajLDAqMAkGA1UdEwQCMAAwHQYDVR0OBBYEFFZN98D4xlW2oR9sTRnzv0Hi_QF5MA0GCSqGSIb3DQEBCwUAA4ICAQCH3aCf-CCJBdEtQc4JpOnUelwGGw7DxnBMokHHBgrzJxDn9BFcFwxGLxrFV7EfYehQNOD-74OS8fZRgZiNf9EDGAYiHh0-CspfBWd20zCIjlCdDBcyhwq3PLJ65JC_og3CT9AK4kvks4DI-01RYxNv9S8Jx1haO1lgU55hBIr1P_p21ZKnpcCEhPjB_cIFrHJqL5iJGfed-LXni9Suq24OHnp44Mrv4h7OD2elu5yWfdfFb-RGG2TYURFIGYGijsii093w0ZMBOfBS-3Xq_DrHeZbZrrNkY455gJCZ5eV83Nrt9J9_UF0VZHl_hwnSAUC_b3tN_l0ZlC9kPcNzJD04l4ndFBD2KdfQ2HGTX7pybWLZ7yH2BM3ui2OpiacaOzd7OE91rHYB2uZyQ7jdg25yF9M8QI9NHM_itCjdBvAYt4QCT8dX6gmZiIGR2F_YXZAsybtJ16pnUmODVbW80lPbzy-PUQYX79opeD9u6MBorzr9g08Elpb1F3DgSd8VSLlsR2QPllKl4AcJDMIOfZHOQGOzatMV7ipEVRa0L5FnjAWpHHvSNcsjD4Cul562mO3MlI2pCyo-US-nIzG5XZmOeu4Db_Kw_dEPOo2ztHwlU0qKJ7REBsbt63jdQtlwLuiLHwkpiwnrAOZfwbLLu9Yz4tL1eJlQffuwS_Aolsz7HA' + ]; + const credentialPublicKey = 'pQECAyYgASFYIBdmUVOxrn-OOtkVwGP_vAspH3VkgzcGXVlu3-acb7EZIlggKgDTs0fr2d51sLR6uL3KP2cqR3iIUkKMCjyMJhYOkf4'; + + const verified = await verifyAttestationWithMetadata({ + statement: metadataStatement, + credentialPublicKey: base64url.toBuffer(credentialPublicKey), + x5c, + }); expect(verified).toEqual(true); }); diff --git a/packages/server/src/metadata/verifyAttestationWithMetadata.ts b/packages/server/src/metadata/verifyAttestationWithMetadata.ts index 0e7c736..5193135 100644 --- a/packages/server/src/metadata/verifyAttestationWithMetadata.ts +++ b/packages/server/src/metadata/verifyAttestationWithMetadata.ts @@ -1,6 +1,6 @@ import { Base64URLString } from '@simplewebauthn/typescript-types'; -import { MetadataStatement, AlgSign } from '../metadata/mdsTypes'; +import type { MetadataStatement, AlgSign } from '../metadata/mdsTypes'; import { convertCertBufferToPEM } from '../helpers/convertCertBufferToPEM'; import { validateCertificatePath } from '../helpers/validateCertificatePath'; import { decodeCredentialPublicKey } from '../helpers/decodeCredentialPublicKey'; @@ -10,17 +10,30 @@ import { COSEKEYS, COSEKTY } from '../helpers/convertCOSEtoPKCS'; * Match properties of the authenticator's attestation statement against expected values as * registered with the FIDO Alliance Metadata Service */ -export async function verifyAttestationWithMetadata( - statement: MetadataStatement, - credentialPublicKey: Buffer, - x5c: Buffer[] | Base64URLString[], -): Promise<boolean> { +export async function verifyAttestationWithMetadata({ + statement, + credentialPublicKey, + x5c, + attestationStatementAlg, +}: { + statement: MetadataStatement; + credentialPublicKey: Buffer; + x5c: Buffer[] | Base64URLString[]; + attestationStatementAlg?: number; +}): Promise<boolean> { + const { + authenticationAlgorithms, + authenticatorGetInfo, + attestationRootCertificates, + } = statement; + // Make sure the alg in the attestation statement matches one of the ones specified in metadata const keypairCOSEAlgs: Set<COSEInfo> = new Set(); - statement.authenticationAlgorithms.forEach(algSign => { - // Convert algSign string to { kty, alg, crv } - const algSignCOSEINFO = algSignToCOSEInfo(algSign); + authenticationAlgorithms.forEach(algSign => { + // Map algSign string to { kty, alg, crv } + const algSignCOSEINFO = algSignToCOSEInfoMap[algSign]; + // Keeping this statement here just in case MDS returns something unexpected if (algSignCOSEINFO) { keypairCOSEAlgs.add(algSignCOSEINFO); } @@ -65,35 +78,70 @@ export async function verifyAttestationWithMetadata( // Make sure the public key is one of the allowed algorithms if (!foundMatch) { - const debugMDSAlgs = Array.from(keypairCOSEAlgs); - // Construct some useful error output about the public key - const debugPubKeyAlgInfo: COSEInfo = { - kty: publicKeyCOSEInfo.kty, - alg: publicKeyCOSEInfo.alg, - }; - // Don't output a bunch of bytes for `crv` when the public key is an RSA key - if (publicKeyCOSEInfo.kty !== COSEKTY.RSA) { - debugPubKeyAlgInfo.crv = publicKeyCOSEInfo.crv; - } + /** + * Craft some useful error output from the MDS algorithms + * + * Example: + * + * ``` + * [ + * 'rsassa_pss_sha256_raw' (COSE info: { kty: 3, alg: -37 }), + * 'secp256k1_ecdsa_sha256_raw' (COSE info: { kty: 2, alg: -47, crv: 8 }) + * ] + * ``` + */ + const debugMDSAlgs = authenticationAlgorithms + .map((algSign) => `'${algSign}' (COSE info: ${stringifyCOSEInfo(algSignToCOSEInfoMap[algSign])})`); + const strMDSAlgs = JSON.stringify(debugMDSAlgs, null, 2).replace(/"/g, ''); - const strPubKeyAlg = JSON.stringify(debugPubKeyAlgInfo); - const strMDSAlgs = JSON.stringify(debugMDSAlgs); + /** + * Construct useful error output about the public key + */ + const strPubKeyAlg = stringifyCOSEInfo(publicKeyCOSEInfo); throw new Error( - `Public key algorithm ${strPubKeyAlg} did not match any metadata algorithms ${strMDSAlgs}`, + `Public key parameters ${strPubKeyAlg} did not match any of the following metadata algorithms:\n${strMDSAlgs}`, ); } - try { - await validateCertificatePath( - x5c.map(convertCertBufferToPEM), - statement.attestationRootCertificates.map(convertCertBufferToPEM), - ); - } catch (err) { - const _err = err as Error; - throw new Error( - `Could not validate certificate path with any metadata root certificates: ${_err.message}`, - ); + /** + * Confirm the attestation statement's algorithm is one supported according to metadata + */ + if (attestationStatementAlg !== undefined && authenticatorGetInfo?.algorithms !== undefined) { + const getInfoAlgs = authenticatorGetInfo.algorithms.map(_alg => _alg.alg); + if (getInfoAlgs.indexOf(attestationStatementAlg) < 0) { + throw new Error( + `Attestation statement alg ${attestationStatementAlg} did not match one of ${getInfoAlgs}`, + ); + } + } + + // Prepare to check the certificate chain + const authenticatorCerts = x5c.map(convertCertBufferToPEM); + const statementRootCerts = attestationRootCertificates.map(convertCertBufferToPEM); + + /** + * If an authenticator returns exactly one certificate in its x5c, and that cert is found in the + * metadata statement then the authenticator is "self-referencing". In this case we forego + * certificate chain validation. + */ + let authenticatorIsSelfReferencing = false; + if ( + authenticatorCerts.length === 1 && + statementRootCerts.indexOf(authenticatorCerts[0]) >= 0 + ) { + authenticatorIsSelfReferencing = true; + } + + if (!authenticatorIsSelfReferencing) { + try { + await validateCertificatePath(authenticatorCerts, statementRootCerts); + } catch (err) { + const _err = err as Error; + throw new Error( + `Could not validate certificate path with any metadata root certificates: ${_err.message}`, + ); + } } return true; @@ -110,42 +158,41 @@ type COSEInfo = { * * Values pulled from `ALG_KEY_COSE` definitions in the FIDO Registry of Predefined Values * - * https://fidoalliance.org/specs/common-specs/fido-registry-v2.1-ps-20191217.html#authentication-algorithms + * https://fidoalliance.org/specs/common-specs/fido-registry-v2.2-ps-20220523.html#authentication-algorithms + */ +export const algSignToCOSEInfoMap: { [key in AlgSign]: COSEInfo } = { + secp256r1_ecdsa_sha256_raw: { kty: 2, alg: -7, crv: 1 }, + secp256r1_ecdsa_sha256_der: { kty: 2, alg: -7, crv: 1 }, + rsassa_pss_sha256_raw: { kty: 3, alg: -37 }, + rsassa_pss_sha256_der: { kty: 3, alg: -37 }, + secp256k1_ecdsa_sha256_raw: { kty: 2, alg: -47, crv: 8 }, + secp256k1_ecdsa_sha256_der: { kty: 2, alg: -47, crv: 8 }, + rsassa_pss_sha384_raw: { kty: 3, alg: -38 }, + rsassa_pkcsv15_sha256_raw: { kty: 3, alg: -257 }, + rsassa_pkcsv15_sha384_raw: { kty: 3, alg: -258 }, + rsassa_pkcsv15_sha512_raw: { kty: 3, alg: -259 }, + rsassa_pkcsv15_sha1_raw: { kty: 3, alg: -65535 }, + secp384r1_ecdsa_sha384_raw: { kty: 2, alg: -35, crv: 2 }, + secp512r1_ecdsa_sha256_raw: { kty: 2, alg: -36, crv: 3 }, + ed25519_eddsa_sha512_raw: { kty: 1, alg: -8, crv: 6 }, +}; + +/** + * A helper to format COSEInfo a little nicer than we can achieve with JSON.stringify() + * + * Input: `{ "kty": 3, "alg": -257 }` + * + * Output: `"{ kty: 3, alg: -257 }"` */ -function algSignToCOSEInfo(algSign: AlgSign): COSEInfo | undefined { - switch (algSign) { - case 'secp256r1_ecdsa_sha256_raw': - case 'secp256r1_ecdsa_sha256_der': - return { kty: 2, alg: -7, crv: 1 }; - case 'rsassa_pss_sha256_raw': - case 'rsassa_pss_sha256_der': - return { kty: 3, alg: -37 }; - case 'secp256k1_ecdsa_sha256_raw': - case 'secp256k1_ecdsa_sha256_der': - return { kty: 2, alg: 7, crv: 8 }; - case 'rsassa_pss_sha384_raw': - return { kty: 3, alg: -38 }; - case 'rsassa_pkcsv15_sha256_raw': - return { kty: 3, alg: -257 }; - case 'rsassa_pkcsv15_sha384_raw': - return { kty: 3, alg: -258 }; - case 'rsassa_pkcsv15_sha512_raw': - return { kty: 3, alg: -259 }; - case 'rsassa_pkcsv15_sha1_raw': - return { kty: 3, alg: -65535 }; - case 'secp384r1_ecdsa_sha384_raw': - return { kty: 2, alg: -35, crv: 2 }; - case 'secp512r1_ecdsa_sha256_raw': - return { kty: 2, alg: -36, crv: 3 }; - case 'ed25519_eddsa_sha512_raw': - return { kty: 1, alg: -8, crv: 6 }; - case 'rsa_emsa_pkcs1_sha256_raw': - case 'rsa_emsa_pkcs1_sha256_der': - return { kty: 3, alg: -257 }; - // TODO: COSE info wasn't readily available for these, these seem rare... - // case 'sm2_sm3_raw': - // return {}; - default: - return undefined; +function stringifyCOSEInfo(info: COSEInfo): string { + const { kty, alg, crv } = info; + + let toReturn = ''; + if (kty !== COSEKTY.RSA) { + toReturn = `{ kty: ${kty}, alg: ${alg}, crv: ${crv} }`; + } else { + toReturn = `{ kty: ${kty}, alg: ${alg} }`; } + + return toReturn; } diff --git a/packages/server/src/registration/verifications/tpm/verifyAttestationTPM.ts b/packages/server/src/registration/verifications/tpm/verifyAttestationTPM.ts index 2afd0a4..c74a7fe 100644 --- a/packages/server/src/registration/verifications/tpm/verifyAttestationTPM.ts +++ b/packages/server/src/registration/verifications/tpm/verifyAttestationTPM.ts @@ -257,7 +257,12 @@ export async function verifyAttestationTPM(options: AttestationFormatVerifierOpt const statement = await MetadataService.getStatement(aaguid); if (statement) { try { - await verifyAttestationWithMetadata(statement, credentialPublicKey, x5c); + await verifyAttestationWithMetadata({ + statement, + credentialPublicKey, + x5c, + attestationStatementAlg: alg, + }); } catch (err) { const _err = err as Error; throw new Error(`${_err.message} (TPM)`); diff --git a/packages/server/src/registration/verifications/verifyAttestationAndroidKey.ts b/packages/server/src/registration/verifications/verifyAttestationAndroidKey.ts index dcc3670..0930eb8 100644 --- a/packages/server/src/registration/verifications/verifyAttestationAndroidKey.ts +++ b/packages/server/src/registration/verifications/verifyAttestationAndroidKey.ts @@ -78,7 +78,12 @@ export async function verifyAttestationAndroidKey( const statement = await MetadataService.getStatement(aaguid); if (statement) { try { - await verifyAttestationWithMetadata(statement, credentialPublicKey, x5c); + await verifyAttestationWithMetadata({ + statement, + credentialPublicKey, + x5c, + attestationStatementAlg: alg, + }); } catch (err) { const _err = err as Error; throw new Error(`${_err.message} (AndroidKey)`); diff --git a/packages/server/src/registration/verifications/verifyAttestationAndroidSafetyNet.ts b/packages/server/src/registration/verifications/verifyAttestationAndroidSafetyNet.ts index 14be27e..4b8c31f 100644 --- a/packages/server/src/registration/verifications/verifyAttestationAndroidSafetyNet.ts +++ b/packages/server/src/registration/verifications/verifyAttestationAndroidSafetyNet.ts @@ -25,7 +25,7 @@ export async function verifyAttestationAndroidSafetyNet( verifyTimestampMS = true, credentialPublicKey, } = options; - const { response, ver } = attStmt; + const { alg, response, ver } = attStmt; if (!ver) { throw new Error('No ver value in attestation (SafetyNet)'); @@ -95,7 +95,12 @@ export async function verifyAttestationAndroidSafetyNet( const statement = await MetadataService.getStatement(aaguid); if (statement) { try { - await verifyAttestationWithMetadata(statement, credentialPublicKey, HEADER.x5c); + await verifyAttestationWithMetadata({ + statement, + credentialPublicKey, + x5c: HEADER.x5c, + attestationStatementAlg: alg, + }); } catch (err) { const _err = err as Error; throw new Error(`${_err.message} (SafetyNet)`); diff --git a/packages/server/src/registration/verifications/verifyAttestationPacked.ts b/packages/server/src/registration/verifications/verifyAttestationPacked.ts index 844a1ed..415c814 100644 --- a/packages/server/src/registration/verifications/verifyAttestationPacked.ts +++ b/packages/server/src/registration/verifications/verifyAttestationPacked.ts @@ -99,7 +99,12 @@ export async function verifyAttestationPacked( } try { - await verifyAttestationWithMetadata(statement, credentialPublicKey, x5c); + await verifyAttestationWithMetadata({ + statement, + credentialPublicKey, + x5c, + attestationStatementAlg: alg, + }); } catch (err) { const _err = err as Error; throw new Error(`${_err.message} (Packed|Full)`); |