diff options
-rw-r--r-- | packages/server/src/metadata/verifyAttestationWithMetadata.test.ts | 57 | ||||
-rw-r--r-- | packages/server/src/metadata/verifyAttestationWithMetadata.ts | 36 |
2 files changed, 83 insertions, 10 deletions
diff --git a/packages/server/src/metadata/verifyAttestationWithMetadata.test.ts b/packages/server/src/metadata/verifyAttestationWithMetadata.test.ts index 128e26a..9ba01fd 100644 --- a/packages/server/src/metadata/verifyAttestationWithMetadata.test.ts +++ b/packages/server/src/metadata/verifyAttestationWithMetadata.test.ts @@ -105,3 +105,60 @@ test('should verify attestation with rsa_emsa_pkcs1_sha256_raw authenticator alg 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( + metadataStatement, + 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 940b174..e068a05 100644 --- a/packages/server/src/metadata/verifyAttestationWithMetadata.ts +++ b/packages/server/src/metadata/verifyAttestationWithMetadata.ts @@ -92,16 +92,32 @@ export async function verifyAttestationWithMetadata( ); } - 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}`, - ); + // Prepare to check the certificate chain + const authenticatorCerts = x5c.map(convertCertBufferToPEM); + const statementRootCerts = statement.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; |