diff options
Diffstat (limited to 'packages/server/src')
4 files changed, 88 insertions, 12 deletions
diff --git a/packages/server/src/registration/verifications/tpm/constants.ts b/packages/server/src/registration/verifications/tpm/constants.ts index 9b9cfa6..c470d5b 100644 --- a/packages/server/src/registration/verifications/tpm/constants.ts +++ b/packages/server/src/registration/verifications/tpm/constants.ts @@ -1,4 +1,17 @@ /* eslint-disable @typescript-eslint/ban-ts-comment */ +/** + * A whole lotta domain knowledge is captured here, with hazy connections to source + * documents. Good places to start searching for more info on these values are the + * following Trusted Computing Group TPM Library docs linked in the WebAuthn API: + * + * - https://www.trustedcomputinggroup.org/wp-content/uploads/TPM-Rev-2.0-Part-1-Architecture-01.38.pdf + * - https://www.trustedcomputinggroup.org/wp-content/uploads/TPM-Rev-2.0-Part-2-Structures-01.38.pdf + * - https://www.trustedcomputinggroup.org/wp-content/uploads/TPM-Rev-2.0-Part-3-Commands-01.38.pdf + */ + +/** + * 6.9 TPM_ST (Structure Tags) + */ export const TPM_ST: { [key: number]: string } = { 0x00c4: 'TPM_ST_RSP_COMMAND', 0x8000: 'TPM_ST_NULL', @@ -19,6 +32,9 @@ export const TPM_ST: { [key: number]: string } = { 0x8029: 'TPM_ST_FU_MANIFEST', }; +/** + * 6.3 TPM_ALG_ID + */ export const TPM_ALG: { [key: number]: string } = { 0x0000: 'TPM_ALG_ERROR', 0x0001: 'TPM_ALG_RSA', @@ -59,6 +75,9 @@ export const TPM_ALG: { [key: number]: string } = { 0x0044: 'TPM_ALG_ECB', }; +/** + * 6.4 TPM_ECC_CURVE + */ export const TPM_ECC_CURVE: { [key: number]: string } = { 0x0000: 'TPM_ECC_NONE', 0x0001: 'TPM_ECC_NIST_P192', @@ -76,6 +95,12 @@ type ManufacturerInfo = { id: string; }; +/** + * Sourced from https://trustedcomputinggroup.org/resource/vendor-id-registry/ + * + * Latest version: + * https://trustedcomputinggroup.org/wp-content/uploads/TCG-TPM-Vendor-ID-Registry-Version-1.02-Revision-1.00.pdf + */ export const TPM_MANUFACTURERS: { [key: string]: ManufacturerInfo } = { 'id:414D4400': { name: 'AMD', @@ -154,3 +179,14 @@ export const TPM_MANUFACTURERS: { [key: string]: ManufacturerInfo } = { id: 'FIDO', }, }; + +/** + * Match TPM public area curve ID's to `crv` numbers used in COSE public keys + */ +export const TPM_ECC_CURVE_COSE_CRV_MAP: { [key: string]: number } = { + TPM_ECC_NIST_P256: 1, // p256 + TPM_ECC_NIST_P384: 2, // p384 + TPM_ECC_NIST_P521: 3, // p521 + TPM_ECC_BN_P256: 1, // p256 + TPM_ECC_SM2_P256: 1, // p256 +}; diff --git a/packages/server/src/registration/verifications/tpm/parsePubArea.ts b/packages/server/src/registration/verifications/tpm/parsePubArea.ts index 693f7bb..ca61ddc 100644 --- a/packages/server/src/registration/verifications/tpm/parsePubArea.ts +++ b/packages/server/src/registration/verifications/tpm/parsePubArea.ts @@ -2,6 +2,9 @@ import { TPM_ALG, TPM_ECC_CURVE } from './constants'; /** * Break apart a TPM attestation's pubArea buffer + * + * See 12.2.4 TPMT_PUBLIC here: + * https://trustedcomputinggroup.org/wp-content/uploads/TPM-Rev-2.0-Part-2-Structures-00.96-130315.pdf */ export function parsePubArea(pubArea: Buffer): ParsedPubArea { let pointer = 0; @@ -34,6 +37,8 @@ export function parsePubArea(pubArea: Buffer): ParsedPubArea { // Extract additional curve params according to type const parameters: { rsa?: RSAParameters; ecc?: ECCParameters } = {}; + let unique = Buffer.from([]); + if (type === 'TPM_ALG_RSA') { const rsaBuffer = pubArea.slice(pointer, (pointer += 10)); @@ -43,6 +48,14 @@ export function parsePubArea(pubArea: Buffer): ParsedPubArea { keyBits: rsaBuffer.slice(4, 6).readUInt16BE(0), exponent: rsaBuffer.slice(6, 10).readUInt32BE(0), }; + + /** + * See 11.2.4.5 TPM2B_PUBLIC_KEY_RSA here: + * https://trustedcomputinggroup.org/wp-content/uploads/TPM-Rev-2.0-Part-2-Structures-00.96-130315.pdf + */ + const uniqueLength = pubArea.slice(pointer, (pointer += 2)).readUInt16BE(0); + + unique = pubArea.slice(pointer, (pointer += uniqueLength)); } else if (type === 'TPM_ALG_ECC') { const eccBuffer = pubArea.slice(pointer, (pointer += 8)); @@ -52,14 +65,23 @@ export function parsePubArea(pubArea: Buffer): ParsedPubArea { curveID: TPM_ECC_CURVE[eccBuffer.slice(4, 6).readUInt16BE(0)], kdf: TPM_ALG[eccBuffer.slice(6, 8).readUInt16BE(0)], }; + + /** + * See 11.2.5.1 TPM2B_ECC_PARAMETER here: + * https://trustedcomputinggroup.org/wp-content/uploads/TPM-Rev-2.0-Part-2-Structures-00.96-130315.pdf + */ + // Retrieve X + const uniqueXLength = pubArea.slice(pointer, (pointer += 2)).readUInt16BE(0); + const uniqueX = pubArea.slice(pointer, (pointer += uniqueXLength)); + // Retrieve Y + const uniqueYLength = pubArea.slice(pointer, (pointer += 2)).readUInt16BE(0); + const uniqueY = pubArea.slice(pointer, (pointer += uniqueYLength)); + + unique = Buffer.concat([uniqueX, uniqueY]); } else { throw new Error(`Unexpected type "${type}" (TPM)`); } - // Slice out unique of dynamic length - const uniqueLength = pubArea.slice(pointer, (pointer += 2)).readUInt16BE(0); - const unique = pubArea.slice(pointer, (pointer += uniqueLength)); - return { type, nameAlg, diff --git a/packages/server/src/registration/verifications/tpm/verifyAttestationTPM.test.ts b/packages/server/src/registration/verifications/tpm/verifyAttestationTPM.test.ts index 0584ebd..5652640 100644 --- a/packages/server/src/registration/verifications/tpm/verifyAttestationTPM.test.ts +++ b/packages/server/src/registration/verifications/tpm/verifyAttestationTPM.test.ts @@ -154,3 +154,25 @@ test('should verify TPM response with non-spec-compliant tcgAtTpm SAN structure' expect(verification.verified).toEqual(true); }); + +test('should verify TPM response with ECC public area type', async () => { + const expectedChallenge = 'uzn9u0Tx-LBdtGgERsbkHRBjiUt5i2rvm2BBTZrWqEo'; + jest.spyOn(base64url, 'encode').mockReturnValueOnce(expectedChallenge); + const verification = await verifyRegistrationResponse({ + credential: { + 'id': 'hsS2ywFz_LWf9-lC35vC9uJTVD3ZCVdweZvESUbjXnQ', + 'rawId': 'hsS2ywFz_LWf9-lC35vC9uJTVD3ZCVdweZvESUbjXnQ', + 'type': 'public-key', + 'response': { + 'attestationObject': 'o2NmbXRjdHBtZ2F0dFN0bXSmY2FsZzn__mNzaWdZAQCqAcGoi2IFXCF5xxokjR5yOAwK_11iCOqt8hCkpHE9rW602J3KjhcRQzoFf1UxZvadwmYcHHMxDQDmVuOhH-yW-DfARVT7O3MzlhhzrGTNO_-jhGFsGeEdz0RgNsviDdaVP5lNsV6Pe4bMhgBv1aTkk0zx1T8sxK8B7gKT6x80RIWg89_aYY4gHR4n65SRDp2gOGI2IHDvqTwidyeaAHVPbDrF8iDbQ88O-GH_fheAtFtgjbIq-XQbwVdzQhYdWyL0XVUwGLSSuABuB4seRPkyZCKoOU6VuuQzfWNpH2Nl05ybdXi27HysUexgfPxihB3PbR8LJdi1j04tRg3JvBUvY3ZlcmMyLjBjeDVjglkFuzCCBbcwggOfoAMCAQICEGEZiaSlAkKpqaQOKDYmWPkwDQYJKoZIhvcNAQELBQAwQTE_MD0GA1UEAxM2RVVTLU5UQy1LRVlJRC1FNEE4NjY2RjhGNEM2RDlDMzkzMkE5NDg4NDc3ODBBNjgxMEM0MjEzMB4XDTIyMDExMjIyMTUxOFoXDTI3MDYxMDE4NTQzNlowADCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKo-7DHdiipZTzfA9fpTaIMVK887zM0nXAVIvU0kmGAsPpTYbf7dn1DAl6BhcDkXs2WrwYP02K8RxXWOF4jf7esMAIkr65zPWqLys8WRNM60d7g9GOADwbN8qrY0hepSsaJwjhswbNJI6L8vJwnnrQ6UWVCm3xHqn8CB2iSWNSUnshgTQTkJ1ZEdToeD51sFXUE0fSxXjyIiSAAD4tCIZkmHFVqchzfqUgiiM_mbbKzUnxEZ6c6r39ccHzbm4Ir-u62repQnVXKTpzFBbJ-Eg15REvw6xuYaGtpItk27AXVcEodfAylf7pgQPfExWkoMZfb8faqbQAj5x29mBJvlzj0CAwEAAaOCAeowggHmMA4GA1UdDwEB_wQEAwIHgDAMBgNVHRMBAf8EAjAAMG0GA1UdIAEB_wRjMGEwXwYJKwYBBAGCNxUfMFIwUAYIKwYBBQUHAgIwRB5CAFQAQwBQAEEAIAAgAFQAcgB1AHMAdABlAGQAIAAgAFAAbABhAHQAZgBvAHIAbQAgACAASQBkAGUAbgB0AGkAdAB5MBAGA1UdJQQJMAcGBWeBBQgDMFAGA1UdEQEB_wRGMESkQjBAMT4wEAYFZ4EFAgIMB05QQ1Q3NXgwFAYFZ4EFAgEMC2lkOjRFNTQ0MzAwMBQGBWeBBQIDDAtpZDowMDA3MDAwMjAfBgNVHSMEGDAWgBQ3yjAtSXrnaSNOtzy1PEXxOO1ZUDAdBgNVHQ4EFgQU1ml3H5Tzrs0Nev69tFNhPZnhaV0wgbIGCCsGAQUFBwEBBIGlMIGiMIGfBggrBgEFBQcwAoaBkmh0dHA6Ly9hemNzcHJvZGV1c2Fpa3B1Ymxpc2guYmxvYi5jb3JlLndpbmRvd3MubmV0L2V1cy1udGMta2V5aWQtZTRhODY2NmY4ZjRjNmQ5YzM5MzJhOTQ4ODQ3NzgwYTY4MTBjNDIxMy9lMDFjMjA2Mi1mYmRjLTQwYTUtYTQwZi1jMzc3YzBmNzY1MWMuY2VyMA0GCSqGSIb3DQEBCwUAA4ICAQAz-YGrj0S841gyMZuit-qsKpKNdxbkaEhyB1baexHGcMzC2y1O1kpTrpaH3I80hrIZFtYoA2xKQ1j67uoC6vm1PhsJB6qhs9T7zmWZ1VtleJTYGNZ_bYY2wo65qJHFB5TXkevJUVe2G39kB_W1TKB6g_GSwb4a5e4D_Sjp7b7RZpyIKHT1_UE1H4RXgR9Qi68K4WVaJXJUS6T4PHrRc4PeGUoJLQFUGxYokWIf456G32GwGgvUSX76K77pVv4Y-kT3v5eEJdYxlS4EVT13a17KWd0DdLje0Ae69q_DQSlrHVLUrADvuZMeM8jxyPQvDb7ETKLsSUeHm73KOCGLStcGQ3pB49nt3d9XdWCcUwUrmbBF2G7HsRgTNbj16G6QUcWroQEqNrBG49aO9mMZ0NwSn5d3oNuXSXjLdGBXM1ukLZ-GNrZDYw5KXU102_5VpHpjIHrZh0dXg3Q9eucKe6EkFbH65-O5VaQWUnR5WJpt6-fl_l0iHqHnKXbgL6tjeerCqZWDvFsOak05R-hosAoQs_Ni0EsgZqHwR_VlG86fsSwCVU3_sDKTNs_Je08ewJ_bbMB5Tq6k1Sxs8Aw8R96EwjQLp3z-Zva1myU-KerYYVDl5BdvgPqbD8Xmst-z6vrP3CJbtr8jgqVS7RWy_cJOA8KCZ6IS_75QT7Gblq6UGFkG7zCCBuswggTToAMCAQICEzMAAAbTtnznKsOrB-gAAAAABtMwDQYJKoZIhvcNAQELBQAwgYwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xNjA0BgNVBAMTLU1pY3Jvc29mdCBUUE0gUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxNDAeFw0yMTA2MTAxODU0MzZaFw0yNzA2MTAxODU0MzZaMEExPzA9BgNVBAMTNkVVUy1OVEMtS0VZSUQtRTRBODY2NkY4RjRDNkQ5QzM5MzJBOTQ4ODQ3NzgwQTY4MTBDNDIxMzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAJA7GLwHWWbn2H8DRppxQfre4zll1sgE3Wxt9DTYWt5-v-xKwCQb6z_7F1py7LMe58qLqglAgVhS6nEvN2puZ1GzejdsFFxz2gyEfH1y-X3RGp0dxS6UKwEtmksaMEKIRQn2GgKdUkiuvkaxaoznuExoTPyu0aXk6yFsX5KEDu9UZCgt66bRy6m3KIRnn1VK2frZfqGYi8C8x9Q69oGG316tUwAIm3ypDtv3pREXsDLYE1U5Irdv32hzJ4CqqPyau-qJS18b8CsjvgOppwXRSwpOmU7S3xqo-F7h1eeFw2tgHc7PEPt8MSSKeba8Fz6QyiLhgFr8jFUvKRzk4B41HFUMqXYawbhAtfIBiGGsGrrdNKb7MxISnH1E6yLVCQGGhXiN9U7V0h8Gn56eKzopGlubw7yMmgu8Cu2wBX_a_jFmIBHnn8YgwcRm6NvT96KclDHnFqPVm3On12bG31F7EYkIRGLbaTT6avEu9rL6AJn7Xr245Sa6dC_OSMRKqLSufxp6O6f2TH2g4kvT0Go9SeyM2_acBjIiQ0rFeBOm49H4E4VcJepf79FkljovD68imeZ5MXjxepcCzS138374Jeh7k28JePwJnjDxS8n9Dr6xOU3_wxS1gN5cW6cXSoiPGe0JM4CEyAcUtKrvpUWoTajxxnylZuvS8ou2thfH2PQlAgMBAAGjggGOMIIBijAOBgNVHQ8BAf8EBAMCAoQwGwYDVR0lBBQwEgYJKwYBBAGCNxUkBgVngQUIAzAWBgNVHSAEDzANMAsGCSsGAQQBgjcVHzASBgNVHRMBAf8ECDAGAQH_AgEAMB0GA1UdDgQWBBQ3yjAtSXrnaSNOtzy1PEXxOO1ZUDAfBgNVHSMEGDAWgBR6jArOL0hiF-KU0a5VwVLscXSkVjBwBgNVHR8EaTBnMGWgY6Bhhl9odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNyb3NvZnQlMjBUUE0lMjBSb290JTIwQ2VydGlmaWNhdGUlMjBBdXRob3JpdHklMjAyMDE0LmNybDB9BggrBgEFBQcBAQRxMG8wbQYIKwYBBQUHMAKGYWh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY2VydHMvTWljcm9zb2Z0JTIwVFBNJTIwUm9vdCUyMENlcnRpZmljYXRlJTIwQXV0aG9yaXR5JTIwMjAxNC5jcnQwDQYJKoZIhvcNAQELBQADggIBAFZTSitCISvll6i6rPUPd8Wt2mogRw6I_c-dWQzdc9-SY9iaIGXqVSPKKOlAYU2ju7nvN6AvrIba6sngHeU0AUTeg1UZ5-bDFOWdSgPaGyH_EN_l-vbV6SJPzOmZHJOHfw2WT8hjlFaTaKYRXxzFH7PUR4nxGRbWtdIGgQhUlWg5oo_FO4bvLKfssPSONn684qkAVierq-ly1WeqJzOYhd4EylgVJ9NL3YUhg8dYcHAieptDzF7OcDqffbuZLZUx6xcyibhWQcntAh7a3xPwqXxENsHhme_bqw_kqa-NVk-Wz4zdoiNNLRvUmCSL1WLc4JPsFJ08Ekn1kW7f9ZKnie5aw-29jEf6KIBt4lGDD3tXTfaOVvWcDbu92jMOO1dhEIj63AwQiDJgZhqnrpjlyWU_X0IVQlaPBg80AE0Y3sw1oMrY0XwdeQUjSpH6e5fTYKrNB6NMT1jXGjKIzVg8XbPWlnebP2wEhq8rYiDR31b9B9Sw_naK7Xb-Cqi-VQdUtknSjeljusrBpxGUx-EIJci0-dzeXRT5_376vyKSuYxA1Xd2jd4EknJLIAVLT3rb10DCuKGLDgafbsfTBxVoEa9hSjYOZUr_m3WV6t6I9WPYjVyhyi7fCEIG4JE7YbM4na4jg5q3DM8ibE8jyufAq0PfJZTJyi7c2Q2N_9NgnCNwZ3B1YkFyZWFYdgAjAAsABAByACCd_8vzbDg65pn7mGjcbcuJ1xU4hL4oA5IsEkFYv60irgAQABAAAwAQACAek7g2C8TeORRoKxuN7HrJ5OinVGuHzEgYODyUsF9D1wAggXPPXn-Pm_4IF0c4XVaJjmHO3EB2KBwdg_L60N0IL9xoY2VydEluZm9Yof9UQ0eAFwAiAAvQNGTLa2wT6u8SKDDdwkgaq5Cmh6jcD_6ULvM9ZmvdbwAUtMInD3WtGSdWHPWijMrW_TfYo-gAAAABPuBems3Sywu4aQsGAe85iOosjtXIACIAC5FPRiZSJzjYMNnAz9zFtM62o57FJwv8F5gNEcioqhHwACIACyVXxq1wZhDsqTqdYr7vQUUJ3vwWVrlN0ZQv5HFnHqWdaGF1dGhEYXRhWKR0puqSE8mcL3SyJJKzIM9AJiqUwalQoDl_KSULYIQe8EUAAAAACJhwWMrcS4G24TDeUNy-lgAghsS2ywFz_LWf9-lC35vC9uJTVD3ZCVdweZvESUbjXnSlAQIDJiABIVggHpO4NgvE3jkUaCsbjex6yeTop1Rrh8xIGDg8lLBfQ9ciWCCBc89ef4-b_ggXRzhdVomOYc7cQHYoHB2D8vrQ3Qgv3A', + 'clientDataJSON': 'eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoidXpuOXUwVHgtTEJkdEdnRVJzYmtIUkJqaVV0NWkycnZtMkJCVFpyV3FFbyIsIm9yaWdpbiI6Imh0dHBzOi8vd2ViYXV0aG4uaW8iLCJjcm9zc09yaWdpbiI6ZmFsc2V9' + }, + 'clientExtensionResults': {}, + }, + expectedChallenge, + expectedOrigin: 'https://webauthn.io', + expectedRPID: 'webauthn.io', + }); + + expect(verification.verified).toEqual(true); +}); diff --git a/packages/server/src/registration/verifications/tpm/verifyAttestationTPM.ts b/packages/server/src/registration/verifications/tpm/verifyAttestationTPM.ts index 1b97056..2afd0a4 100644 --- a/packages/server/src/registration/verifications/tpm/verifyAttestationTPM.ts +++ b/packages/server/src/registration/verifications/tpm/verifyAttestationTPM.ts @@ -20,7 +20,7 @@ import { verifySignature } from '../../../helpers/verifySignature'; import { MetadataService } from '../../../services/metadataService'; import { verifyAttestationWithMetadata } from '../../../metadata/verifyAttestationWithMetadata'; -import { TPM_ECC_CURVE, TPM_MANUFACTURERS } from './constants'; +import { TPM_MANUFACTURERS, TPM_ECC_CURVE_COSE_CRV_MAP } from './constants'; import { parseCertInfo } from './parseCertInfo'; import { parsePubArea } from './parsePubArea'; @@ -93,10 +93,6 @@ export async function verifyAttestationTPM(options: AttestationFormatVerifierOpt throw new Error(`Unexpected public key exp ${eSum}, expected ${pubAreaExponent} (TPM|RSA)`); } } else if (pubType === 'TPM_ALG_ECC') { - /** - * TODO: Confirm this all works fine. Conformance tools v1.3.4 don't currently test ECC so I - * had to eyeball it based on the **duo-labs/webauthn** library - */ const crv = cosePublicKey.get(COSEKEYS.crv); const x = cosePublicKey.get(COSEKEYS.x); const y = cosePublicKey.get(COSEKEYS.y); @@ -120,10 +116,10 @@ export async function verifyAttestationTPM(options: AttestationFormatVerifierOpt } const pubAreaCurveID = parameters.ecc.curveID; - const pubKeyCurveID = TPM_ECC_CURVE[(crv as Buffer).readUInt16BE(0)]; - if (pubAreaCurveID !== pubKeyCurveID) { + const pubAreaCurveIDMapToCOSECRV = TPM_ECC_CURVE_COSE_CRV_MAP[pubAreaCurveID] + if (pubAreaCurveIDMapToCOSECRV !== crv) { throw new Error( - `Unexpected public key curve ID "${pubKeyCurveID}", expected "${pubAreaCurveID}" (TPM|ECC)`, + `Public area key curve ID "${pubAreaCurveID}" mapped to "${pubAreaCurveIDMapToCOSECRV}" which did not match public key crv of "${crv}" (TPM|ECC)`, ); } } else { |