summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorMatthew Miller <matthew@millerti.me>2022-08-05 14:35:17 -0700
committerGitHub <noreply@github.com>2022-08-05 14:35:17 -0700
commit0ec1d61035df1d9f5e22889da7cf975d72173d01 (patch)
treee4e75fa56adb463c54a60c02a08c12b91f8e9515
parentc41351664e47b0606d49da435298ef588bfe137f (diff)
parent41e5aebf6bee0b511b2a179abcb95c8080b24ea0 (diff)
Merge pull request #239 from MasterKale/fix/tpm-pub-area-parsing
fix/tpm-pub-area-parsing
-rw-r--r--packages/server/src/registration/verifications/tpm/constants.ts36
-rw-r--r--packages/server/src/registration/verifications/tpm/parsePubArea.ts30
-rw-r--r--packages/server/src/registration/verifications/tpm/verifyAttestationTPM.test.ts22
-rw-r--r--packages/server/src/registration/verifications/tpm/verifyAttestationTPM.ts12
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 {