feat: 초기 프로젝트 설정 및 룰.md 파일 추가

This commit is contained in:
2025-07-28 09:53:31 +09:00
commit 09a4d38512
8165 changed files with 1021855 additions and 0 deletions

View File

@@ -0,0 +1,48 @@
/**
* 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 declare const TPM_ST: {
[key: number]: string;
};
/**
* 6.3 TPM_ALG_ID
*/
export declare const TPM_ALG: {
[key: number]: string;
};
/**
* 6.4 TPM_ECC_CURVE
*/
export declare const TPM_ECC_CURVE: {
[key: number]: string;
};
type ManufacturerInfo = {
name: string;
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 declare const TPM_MANUFACTURERS: {
[key: string]: ManufacturerInfo;
};
/**
* Match TPM public area curve ID's to `crv` numbers used in COSE public keys
*/
export declare const TPM_ECC_CURVE_COSE_CRV_MAP: {
[key: string]: number;
};
export {};
//# sourceMappingURL=constants.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../../src/registration/verifications/tpm/constants.ts"],"names":[],"mappings":"AACA;;;;;;;;GAQG;AAEH;;GAEG;AACH,eAAO,MAAM,MAAM,EAAE;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;CAkB3C,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,OAAO,EAAE;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;CAsC5C,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,aAAa,EAAE;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;CAUlD,CAAC;AAEF,KAAK,gBAAgB,GAAG;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;CACZ,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,EAAE;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,gBAAgB,CAAA;CAgChE,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,0BAA0B,EAAE;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;CAM/D,CAAC"}

View File

@@ -0,0 +1,140 @@
"use strict";
// deno-lint-ignore-file no-dupe-keys
/**
* 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
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.TPM_ECC_CURVE_COSE_CRV_MAP = exports.TPM_MANUFACTURERS = exports.TPM_ECC_CURVE = exports.TPM_ALG = exports.TPM_ST = void 0;
/**
* 6.9 TPM_ST (Structure Tags)
*/
exports.TPM_ST = {
0x00c4: 'TPM_ST_RSP_COMMAND',
0x8000: 'TPM_ST_NULL',
0x8001: 'TPM_ST_NO_SESSIONS',
0x8002: 'TPM_ST_SESSIONS',
0x8014: 'TPM_ST_ATTEST_NV',
0x8015: 'TPM_ST_ATTEST_COMMAND_AUDIT',
0x8016: 'TPM_ST_ATTEST_SESSION_AUDIT',
0x8017: 'TPM_ST_ATTEST_CERTIFY',
0x8018: 'TPM_ST_ATTEST_QUOTE',
0x8019: 'TPM_ST_ATTEST_TIME',
0x801a: 'TPM_ST_ATTEST_CREATION',
0x8021: 'TPM_ST_CREATION',
0x8022: 'TPM_ST_VERIFIED',
0x8023: 'TPM_ST_AUTH_SECRET',
0x8024: 'TPM_ST_HASHCHECK',
0x8025: 'TPM_ST_AUTH_SIGNED',
0x8029: 'TPM_ST_FU_MANIFEST',
};
/**
* 6.3 TPM_ALG_ID
*/
exports.TPM_ALG = {
0x0000: 'TPM_ALG_ERROR',
0x0001: 'TPM_ALG_RSA',
0x0004: 'TPM_ALG_SHA',
// @ts-ignore 2300
0x0004: 'TPM_ALG_SHA1',
0x0005: 'TPM_ALG_HMAC',
0x0006: 'TPM_ALG_AES',
0x0007: 'TPM_ALG_MGF1',
0x0008: 'TPM_ALG_KEYEDHASH',
0x000a: 'TPM_ALG_XOR',
0x000b: 'TPM_ALG_SHA256',
0x000c: 'TPM_ALG_SHA384',
0x000d: 'TPM_ALG_SHA512',
0x0010: 'TPM_ALG_NULL',
0x0012: 'TPM_ALG_SM3_256',
0x0013: 'TPM_ALG_SM4',
0x0014: 'TPM_ALG_RSASSA',
0x0015: 'TPM_ALG_RSAES',
0x0016: 'TPM_ALG_RSAPSS',
0x0017: 'TPM_ALG_OAEP',
0x0018: 'TPM_ALG_ECDSA',
0x0019: 'TPM_ALG_ECDH',
0x001a: 'TPM_ALG_ECDAA',
0x001b: 'TPM_ALG_SM2',
0x001c: 'TPM_ALG_ECSCHNORR',
0x001d: 'TPM_ALG_ECMQV',
0x0020: 'TPM_ALG_KDF1_SP800_56A',
0x0021: 'TPM_ALG_KDF2',
0x0022: 'TPM_ALG_KDF1_SP800_108',
0x0023: 'TPM_ALG_ECC',
0x0025: 'TPM_ALG_SYMCIPHER',
0x0026: 'TPM_ALG_CAMELLIA',
0x0040: 'TPM_ALG_CTR',
0x0041: 'TPM_ALG_OFB',
0x0042: 'TPM_ALG_CBC',
0x0043: 'TPM_ALG_CFB',
0x0044: 'TPM_ALG_ECB',
};
/**
* 6.4 TPM_ECC_CURVE
*/
exports.TPM_ECC_CURVE = {
0x0000: 'TPM_ECC_NONE',
0x0001: 'TPM_ECC_NIST_P192',
0x0002: 'TPM_ECC_NIST_P224',
0x0003: 'TPM_ECC_NIST_P256',
0x0004: 'TPM_ECC_NIST_P384',
0x0005: 'TPM_ECC_NIST_P521',
0x0010: 'TPM_ECC_BN_P256',
0x0011: 'TPM_ECC_BN_P638',
0x0020: 'TPM_ECC_SM2_P256',
};
/**
* 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
*/
exports.TPM_MANUFACTURERS = {
'id:414D4400': { name: 'AMD', id: 'AMD' },
'id:414E5400': { name: 'Ant Group', id: 'ANT' },
'id:41544D4C': { name: 'Atmel', id: 'ATML' },
'id:4252434D': { name: 'Broadcom', id: 'BRCM' },
'id:4353434F': { name: 'Cisco', id: 'CSCO' },
'id:464C5953': { name: 'Flyslice Technologies', id: 'FLYS' },
'id:524F4343': { name: 'Fuzhou Rockchip', id: 'ROCC' },
'id:474F4F47': { name: 'Google', id: 'GOOG' },
'id:48504900': { name: 'HPI', id: 'HPI' },
'id:48504500': { name: 'HPE', id: 'HPE' },
'id:48495349': { name: 'Huawei', id: 'HISI' },
'id:49424d00': { name: 'IBM', id: 'IBM' },
'id:49424D00': { name: 'IBM', id: 'IBM' }, // Same ID for IBM as above, except the "D" is capitalized as per TPM spec
'id:49465800': { name: 'Infineon', id: 'IFX' },
'id:494E5443': { name: 'Intel', id: 'INTC' },
'id:4C454E00': { name: 'Lenovo', id: 'LEN' },
'id:4D534654': { name: 'Microsoft', id: 'MSFT' },
'id:4E534D20': { name: 'National Semiconductor', id: 'NSM' },
'id:4E545A00': { name: 'Nationz', id: 'NTZ' },
'id:4E534700': { name: 'NSING', id: 'NSG' },
'id:4E544300': { name: 'Nuvoton Technology', id: 'NTC' },
'id:51434F4D': { name: 'Qualcomm', id: 'QCOM' },
'id:534D534E': { name: 'Samsung', id: 'SMSN' },
'id:53454345': { name: 'SecEdge', id: 'SECE' },
'id:534E5300': { name: 'Sinosun', id: 'SNS' },
'id:534D5343': { name: 'SMSC', id: 'SMSC' },
'id:53544D20': { name: 'STMicroelectronics', id: 'STM' },
'id:54584E00': { name: 'Texas Instruments', id: 'TXN' },
'id:57454300': { name: 'Winbond', id: 'WEC' },
'id:5345414C': { name: 'Wisekey', id: 'SEAL' },
'id:FFFFF1D0': { name: 'FIDO Alliance', id: 'FIDO' }, // FIDO Conformance
};
/**
* Match TPM public area curve ID's to `crv` numbers used in COSE public keys
*/
exports.TPM_ECC_CURVE_COSE_CRV_MAP = {
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
};

View File

@@ -0,0 +1,25 @@
/**
* Cut up a TPM attestation's certInfo into intelligible chunks
*/
export declare function parseCertInfo(certInfo: Uint8Array): ParsedCertInfo;
type ParsedCertInfo = {
magic: number;
type: string;
qualifiedSigner: Uint8Array;
extraData: Uint8Array;
clockInfo: {
clock: Uint8Array;
resetCount: number;
restartCount: number;
safe: boolean;
};
firmwareVersion: Uint8Array;
attested: {
nameAlg: string;
nameAlgBuffer: Uint8Array;
name: Uint8Array;
qualifiedName: Uint8Array;
};
};
export {};
//# sourceMappingURL=parseCertInfo.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"parseCertInfo.d.ts","sourceRoot":"","sources":["../../../../src/registration/verifications/tpm/parseCertInfo.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,UAAU,GAAG,cAAc,CAkElE;AAED,KAAK,cAAc,GAAG;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,eAAe,EAAE,UAAU,CAAC;IAC5B,SAAS,EAAE,UAAU,CAAC;IACtB,SAAS,EAAE;QACT,KAAK,EAAE,UAAU,CAAC;QAClB,UAAU,EAAE,MAAM,CAAC;QACnB,YAAY,EAAE,MAAM,CAAC;QACrB,IAAI,EAAE,OAAO,CAAC;KACf,CAAC;IACF,eAAe,EAAE,UAAU,CAAC;IAC5B,QAAQ,EAAE;QACR,OAAO,EAAE,MAAM,CAAC;QAChB,aAAa,EAAE,UAAU,CAAC;QAC1B,IAAI,EAAE,UAAU,CAAC;QACjB,aAAa,EAAE,UAAU,CAAC;KAC3B,CAAC;CACH,CAAC"}

View File

@@ -0,0 +1,61 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseCertInfo = parseCertInfo;
const constants_js_1 = require("./constants.js");
const index_js_1 = require("../../../helpers/iso/index.js");
/**
* Cut up a TPM attestation's certInfo into intelligible chunks
*/
function parseCertInfo(certInfo) {
let pointer = 0;
const dataView = index_js_1.isoUint8Array.toDataView(certInfo);
// Get a magic constant
const magic = dataView.getUint32(pointer);
pointer += 4;
// Determine the algorithm used for attestation
const typeBuffer = dataView.getUint16(pointer);
pointer += 2;
const type = constants_js_1.TPM_ST[typeBuffer];
// The name of a parent entity, can be ignored
const qualifiedSignerLength = dataView.getUint16(pointer);
pointer += 2;
const qualifiedSigner = certInfo.slice(pointer, pointer += qualifiedSignerLength);
// Get the expected hash of `attsToBeSigned`
const extraDataLength = dataView.getUint16(pointer);
pointer += 2;
const extraData = certInfo.slice(pointer, pointer += extraDataLength);
// Information about the TPM device's internal clock, can be ignored
const clock = certInfo.slice(pointer, pointer += 8);
const resetCount = dataView.getUint32(pointer);
pointer += 4;
const restartCount = dataView.getUint32(pointer);
pointer += 4;
const safe = !!certInfo.slice(pointer, pointer += 1);
const clockInfo = { clock, resetCount, restartCount, safe };
// TPM device firmware version
const firmwareVersion = certInfo.slice(pointer, pointer += 8);
// Attested Name
const attestedNameLength = dataView.getUint16(pointer);
pointer += 2;
const attestedName = certInfo.slice(pointer, pointer += attestedNameLength);
const attestedNameDataView = index_js_1.isoUint8Array.toDataView(attestedName);
// Attested qualified name, can be ignored
const qualifiedNameLength = dataView.getUint16(pointer);
pointer += 2;
const qualifiedName = certInfo.slice(pointer, pointer += qualifiedNameLength);
const attested = {
nameAlg: constants_js_1.TPM_ALG[attestedNameDataView.getUint16(0)],
nameAlgBuffer: attestedName.slice(0, 2),
name: attestedName,
qualifiedName,
};
return {
magic,
type,
qualifiedSigner,
extraData,
clockInfo,
firmwareVersion,
attested,
};
}

View File

@@ -0,0 +1,44 @@
/**
* 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 declare function parsePubArea(pubArea: Uint8Array): ParsedPubArea;
type ParsedPubArea = {
type: 'TPM_ALG_RSA' | 'TPM_ALG_ECC';
nameAlg: string;
objectAttributes: {
fixedTPM: boolean;
stClear: boolean;
fixedParent: boolean;
sensitiveDataOrigin: boolean;
userWithAuth: boolean;
adminWithPolicy: boolean;
noDA: boolean;
encryptedDuplication: boolean;
restricted: boolean;
decrypt: boolean;
signOrEncrypt: boolean;
};
authPolicy: Uint8Array;
parameters: {
rsa?: RSAParameters;
ecc?: ECCParameters;
};
unique: Uint8Array;
};
type RSAParameters = {
symmetric: string;
scheme: string;
keyBits: number;
exponent: number;
};
type ECCParameters = {
symmetric: string;
scheme: string;
curveID: string;
kdf: string;
};
export {};
//# sourceMappingURL=parsePubArea.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"parsePubArea.d.ts","sourceRoot":"","sources":["../../../../src/registration/verifications/tpm/parsePubArea.ts"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,UAAU,GAAG,aAAa,CAyG/D;AAED,KAAK,aAAa,GAAG;IACnB,IAAI,EAAE,aAAa,GAAG,aAAa,CAAC;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,gBAAgB,EAAE;QAChB,QAAQ,EAAE,OAAO,CAAC;QAClB,OAAO,EAAE,OAAO,CAAC;QACjB,WAAW,EAAE,OAAO,CAAC;QACrB,mBAAmB,EAAE,OAAO,CAAC;QAC7B,YAAY,EAAE,OAAO,CAAC;QACtB,eAAe,EAAE,OAAO,CAAC;QACzB,IAAI,EAAE,OAAO,CAAC;QACd,oBAAoB,EAAE,OAAO,CAAC;QAC9B,UAAU,EAAE,OAAO,CAAC;QACpB,OAAO,EAAE,OAAO,CAAC;QACjB,aAAa,EAAE,OAAO,CAAC;KACxB,CAAC;IACF,UAAU,EAAE,UAAU,CAAC;IACvB,UAAU,EAAE;QACV,GAAG,CAAC,EAAE,aAAa,CAAC;QACpB,GAAG,CAAC,EAAE,aAAa,CAAC;KACrB,CAAC;IACF,MAAM,EAAE,UAAU,CAAC;CACpB,CAAC;AAEF,KAAK,aAAa,GAAG;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,KAAK,aAAa,GAAG;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;CACb,CAAC"}

View File

@@ -0,0 +1,97 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.parsePubArea = parsePubArea;
const constants_js_1 = require("./constants.js");
const index_js_1 = require("../../../helpers/iso/index.js");
/**
* 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
*/
function parsePubArea(pubArea) {
let pointer = 0;
const dataView = index_js_1.isoUint8Array.toDataView(pubArea);
const type = constants_js_1.TPM_ALG[dataView.getUint16(pointer)];
pointer += 2;
const nameAlg = constants_js_1.TPM_ALG[dataView.getUint16(pointer)];
pointer += 2;
// Get some authenticator attributes(?)
// const objectAttributesInt = pubArea.slice(pointer, (pointer += 4)).readUInt32BE(0);
const objectAttributesInt = dataView.getUint32(pointer);
pointer += 4;
const objectAttributes = {
fixedTPM: !!(objectAttributesInt & 1),
stClear: !!(objectAttributesInt & 2),
fixedParent: !!(objectAttributesInt & 8),
sensitiveDataOrigin: !!(objectAttributesInt & 16),
userWithAuth: !!(objectAttributesInt & 32),
adminWithPolicy: !!(objectAttributesInt & 64),
noDA: !!(objectAttributesInt & 512),
encryptedDuplication: !!(objectAttributesInt & 1024),
restricted: !!(objectAttributesInt & 32768),
decrypt: !!(objectAttributesInt & 65536),
signOrEncrypt: !!(objectAttributesInt & 131072),
};
// Slice out the authPolicy of dynamic length
const authPolicyLength = dataView.getUint16(pointer);
pointer += 2;
const authPolicy = pubArea.slice(pointer, pointer += authPolicyLength);
// Extract additional curve params according to type
const parameters = {};
let unique = Uint8Array.from([]);
if (type === 'TPM_ALG_RSA') {
const symmetric = constants_js_1.TPM_ALG[dataView.getUint16(pointer)];
pointer += 2;
const scheme = constants_js_1.TPM_ALG[dataView.getUint16(pointer)];
pointer += 2;
const keyBits = dataView.getUint16(pointer);
pointer += 2;
const exponent = dataView.getUint32(pointer);
pointer += 4;
parameters.rsa = { symmetric, scheme, keyBits, exponent };
/**
* 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);
const uniqueLength = dataView.getUint16(pointer);
pointer += 2;
unique = pubArea.slice(pointer, pointer += uniqueLength);
}
else if (type === 'TPM_ALG_ECC') {
const symmetric = constants_js_1.TPM_ALG[dataView.getUint16(pointer)];
pointer += 2;
const scheme = constants_js_1.TPM_ALG[dataView.getUint16(pointer)];
pointer += 2;
const curveID = constants_js_1.TPM_ECC_CURVE[dataView.getUint16(pointer)];
pointer += 2;
const kdf = constants_js_1.TPM_ALG[dataView.getUint16(pointer)];
pointer += 2;
parameters.ecc = { symmetric, scheme, curveID, kdf };
/**
* 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 = dataView.getUint16(pointer);
pointer += 2;
const uniqueX = pubArea.slice(pointer, pointer += uniqueXLength);
// Retrieve Y
const uniqueYLength = dataView.getUint16(pointer);
pointer += 2;
const uniqueY = pubArea.slice(pointer, pointer += uniqueYLength);
unique = index_js_1.isoUint8Array.concat([uniqueX, uniqueY]);
}
else {
throw new Error(`Unexpected type "${type}" (TPM)`);
}
return {
type,
nameAlg,
objectAttributes,
authPolicy,
parameters,
unique,
};
}

View File

@@ -0,0 +1,3 @@
import type { AttestationFormatVerifierOpts } from '../../verifyRegistrationResponse.js';
export declare function verifyAttestationTPM(options: AttestationFormatVerifierOpts): Promise<boolean>;
//# sourceMappingURL=verifyAttestationTPM.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"verifyAttestationTPM.d.ts","sourceRoot":"","sources":["../../../../src/registration/verifications/tpm/verifyAttestationTPM.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,qCAAqC,CAAC;AAuBzF,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,OAAO,CAAC,CA+VlB"}

View File

@@ -0,0 +1,334 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.verifyAttestationTPM = verifyAttestationTPM;
const asn1_schema_1 = require("@peculiar/asn1-schema");
const asn1_x509_1 = require("@peculiar/asn1-x509");
const decodeCredentialPublicKey_js_1 = require("../../../helpers/decodeCredentialPublicKey.js");
const cose_js_1 = require("../../../helpers/cose.js");
const toHash_js_1 = require("../../../helpers/toHash.js");
const convertCertBufferToPEM_js_1 = require("../../../helpers/convertCertBufferToPEM.js");
const validateCertificatePath_js_1 = require("../../../helpers/validateCertificatePath.js");
const getCertificateInfo_js_1 = require("../../../helpers/getCertificateInfo.js");
const verifySignature_js_1 = require("../../../helpers/verifySignature.js");
const index_js_1 = require("../../../helpers/iso/index.js");
const validateExtFIDOGenCEAAGUID_js_1 = require("../../../helpers/validateExtFIDOGenCEAAGUID.js");
const metadataService_js_1 = require("../../../services/metadataService.js");
const verifyAttestationWithMetadata_js_1 = require("../../../metadata/verifyAttestationWithMetadata.js");
const constants_js_1 = require("./constants.js");
const parseCertInfo_js_1 = require("./parseCertInfo.js");
const parsePubArea_js_1 = require("./parsePubArea.js");
async function verifyAttestationTPM(options) {
const { aaguid, attStmt, authData, credentialPublicKey, clientDataHash, rootCertificates, } = options;
const ver = attStmt.get('ver');
const sig = attStmt.get('sig');
const alg = attStmt.get('alg');
const x5c = attStmt.get('x5c');
const pubArea = attStmt.get('pubArea');
const certInfo = attStmt.get('certInfo');
/**
* Verify structures
*/
if (ver !== '2.0') {
throw new Error(`Unexpected ver "${ver}", expected "2.0" (TPM)`);
}
if (!sig) {
throw new Error('No attestation signature provided in attestation statement (TPM)');
}
if (!alg) {
throw new Error(`Attestation statement did not contain alg (TPM)`);
}
if (!(0, cose_js_1.isCOSEAlg)(alg)) {
throw new Error(`Attestation statement contained invalid alg ${alg} (TPM)`);
}
if (!x5c) {
throw new Error('No attestation certificate provided in attestation statement (TPM)');
}
if (!pubArea) {
throw new Error('Attestation statement did not contain pubArea (TPM)');
}
if (!certInfo) {
throw new Error('Attestation statement did not contain certInfo (TPM)');
}
const parsedPubArea = (0, parsePubArea_js_1.parsePubArea)(pubArea);
const { unique, type: pubType, parameters } = parsedPubArea;
// Verify that the public key specified by the parameters and unique fields of pubArea is
// identical to the credentialPublicKey in the attestedCredentialData in authenticatorData.
const cosePublicKey = (0, decodeCredentialPublicKey_js_1.decodeCredentialPublicKey)(credentialPublicKey);
if (pubType === 'TPM_ALG_RSA') {
if (!(0, cose_js_1.isCOSEPublicKeyRSA)(cosePublicKey)) {
throw new Error(`Credential public key with kty ${cosePublicKey.get(cose_js_1.COSEKEYS.kty)} did not match ${pubType}`);
}
const n = cosePublicKey.get(cose_js_1.COSEKEYS.n);
const e = cosePublicKey.get(cose_js_1.COSEKEYS.e);
if (!n) {
throw new Error('COSE public key missing n (TPM|RSA)');
}
if (!e) {
throw new Error('COSE public key missing e (TPM|RSA)');
}
if (!index_js_1.isoUint8Array.areEqual(unique, n)) {
throw new Error('PubArea unique is not same as credentialPublicKey (TPM|RSA)');
}
if (!parameters.rsa) {
throw new Error(`Parsed pubArea type is RSA, but missing parameters.rsa (TPM|RSA)`);
}
const eBuffer = e;
// If `exponent` is equal to 0x00, then exponent is the default RSA exponent of 2^16+1 (65537)
const pubAreaExponent = parameters.rsa.exponent || 65537;
// Do some bit shifting to get to an integer
const eSum = eBuffer[0] + (eBuffer[1] << 8) + (eBuffer[2] << 16);
if (pubAreaExponent !== eSum) {
throw new Error(`Unexpected public key exp ${eSum}, expected ${pubAreaExponent} (TPM|RSA)`);
}
}
else if (pubType === 'TPM_ALG_ECC') {
if (!(0, cose_js_1.isCOSEPublicKeyEC2)(cosePublicKey)) {
throw new Error(`Credential public key with kty ${cosePublicKey.get(cose_js_1.COSEKEYS.kty)} did not match ${pubType}`);
}
const crv = cosePublicKey.get(cose_js_1.COSEKEYS.crv);
const x = cosePublicKey.get(cose_js_1.COSEKEYS.x);
const y = cosePublicKey.get(cose_js_1.COSEKEYS.y);
if (!crv) {
throw new Error('COSE public key missing crv (TPM|ECC)');
}
if (!x) {
throw new Error('COSE public key missing x (TPM|ECC)');
}
if (!y) {
throw new Error('COSE public key missing y (TPM|ECC)');
}
if (!index_js_1.isoUint8Array.areEqual(unique, index_js_1.isoUint8Array.concat([x, y]))) {
throw new Error('PubArea unique is not same as public key x and y (TPM|ECC)');
}
if (!parameters.ecc) {
throw new Error(`Parsed pubArea type is ECC, but missing parameters.ecc (TPM|ECC)`);
}
const pubAreaCurveID = parameters.ecc.curveID;
const pubAreaCurveIDMapToCOSECRV = constants_js_1.TPM_ECC_CURVE_COSE_CRV_MAP[pubAreaCurveID];
if (pubAreaCurveIDMapToCOSECRV !== crv) {
throw new Error(`Public area key curve ID "${pubAreaCurveID}" mapped to "${pubAreaCurveIDMapToCOSECRV}" which did not match public key crv of "${crv}" (TPM|ECC)`);
}
}
else {
throw new Error(`Unsupported pubArea.type "${pubType}"`);
}
const parsedCertInfo = (0, parseCertInfo_js_1.parseCertInfo)(certInfo);
const { magic, type: certType, attested, extraData } = parsedCertInfo;
if (magic !== 0xff544347) {
throw new Error(`Unexpected magic value "${magic}", expected "0xff544347" (TPM)`);
}
if (certType !== 'TPM_ST_ATTEST_CERTIFY') {
throw new Error(`Unexpected type "${certType}", expected "TPM_ST_ATTEST_CERTIFY" (TPM)`);
}
// Hash pubArea to create pubAreaHash using the nameAlg in attested
const pubAreaHash = await (0, toHash_js_1.toHash)(pubArea, attestedNameAlgToCOSEAlg(attested.nameAlg));
// Concatenate attested.nameAlg and pubAreaHash to create attestedName.
const attestedName = index_js_1.isoUint8Array.concat([
attested.nameAlgBuffer,
pubAreaHash,
]);
// Check that certInfo.attested.name is equals to attestedName.
if (!index_js_1.isoUint8Array.areEqual(attested.name, attestedName)) {
throw new Error(`Attested name comparison failed (TPM)`);
}
// Concatenate authData with clientDataHash to create attToBeSigned
const attToBeSigned = index_js_1.isoUint8Array.concat([authData, clientDataHash]);
// Hash attToBeSigned using the algorithm specified in attStmt.alg to create attToBeSignedHash
const attToBeSignedHash = await (0, toHash_js_1.toHash)(attToBeSigned, alg);
// Check that certInfo.extraData is equals to attToBeSignedHash.
if (!index_js_1.isoUint8Array.areEqual(extraData, attToBeSignedHash)) {
throw new Error('CertInfo extra data did not equal hashed attestation (TPM)');
}
/**
* Verify signature
*/
if (x5c.length < 1) {
throw new Error('No certificates present in x5c array (TPM)');
}
// Pick a leaf AIK certificate of the x5c array and parse it.
const leafCertInfo = (0, getCertificateInfo_js_1.getCertificateInfo)(x5c[0]);
const { basicConstraintsCA, version, subject, notAfter, notBefore } = leafCertInfo;
if (basicConstraintsCA) {
throw new Error('Certificate basic constraints CA was not `false` (TPM)');
}
// Check that certificate is of version 3 (value must be set to 2).
if (version !== 2) {
throw new Error('Certificate version was not `3` (ASN.1 value of 2) (TPM)');
}
// Check that Subject sequence is empty.
if (subject.combined.length > 0) {
throw new Error('Certificate subject was not empty (TPM)');
}
// Check that certificate is currently valid
let now = new Date();
if (notBefore > now) {
throw new Error(`Certificate not good before "${notBefore.toString()}" (TPM)`);
}
// Check that certificate has not expired
now = new Date();
if (notAfter < now) {
throw new Error(`Certificate not good after "${notAfter.toString()}" (TPM)`);
}
/**
* Plumb the depths of the certificate's ASN.1-formatted data for some values we need to verify
*/
const parsedCert = asn1_schema_1.AsnParser.parse(x5c[0], asn1_x509_1.Certificate);
if (!parsedCert.tbsCertificate.extensions) {
throw new Error('Certificate was missing extensions (TPM)');
}
let subjectAltNamePresent;
let extKeyUsage;
parsedCert.tbsCertificate.extensions.forEach((ext) => {
if (ext.extnID === asn1_x509_1.id_ce_subjectAltName) {
subjectAltNamePresent = asn1_schema_1.AsnParser.parse(ext.extnValue, asn1_x509_1.SubjectAlternativeName);
}
else if (ext.extnID === asn1_x509_1.id_ce_extKeyUsage) {
extKeyUsage = asn1_schema_1.AsnParser.parse(ext.extnValue, asn1_x509_1.ExtendedKeyUsage);
}
});
// Check that certificate contains subjectAltName (2.5.29.17) extension,
if (!subjectAltNamePresent) {
throw new Error('Certificate did not contain subjectAltName extension (TPM)');
}
// TPM-specific values are buried within `directoryName`, so first make sure there are values
// there.
if (!subjectAltNamePresent[0].directoryName?.[0].length) {
throw new Error('Certificate subjectAltName extension directoryName was empty (TPM)');
}
const { tcgAtTpmManufacturer, tcgAtTpmModel, tcgAtTpmVersion } = getTcgAtTpmValues(subjectAltNamePresent[0].directoryName);
if (!tcgAtTpmManufacturer || !tcgAtTpmModel || !tcgAtTpmVersion) {
throw new Error('Certificate contained incomplete subjectAltName data (TPM)');
}
if (!extKeyUsage) {
throw new Error('Certificate did not contain ExtendedKeyUsage extension (TPM)');
}
// Check that tcpaTpmManufacturer (2.23.133.2.1) field is set to a valid manufacturer ID.
if (!constants_js_1.TPM_MANUFACTURERS[tcgAtTpmManufacturer]) {
throw new Error(`Could not match TPM manufacturer "${tcgAtTpmManufacturer}" (TPM)`);
}
// Check that certificate contains extKeyUsage (2.5.29.37) extension and it must contain
// tcg-kp-AIKCertificate (2.23.133.8.3) OID.
if (extKeyUsage[0] !== '2.23.133.8.3') {
throw new Error(`Unexpected extKeyUsage "${extKeyUsage[0]}", expected "2.23.133.8.3" (TPM)`);
}
// Validate attestation statement AAGUID against leaf cert AAGUID
try {
await (0, validateExtFIDOGenCEAAGUID_js_1.validateExtFIDOGenCEAAGUID)(parsedCert.tbsCertificate.extensions, aaguid);
}
catch (err) {
const _err = err;
throw new Error(`${_err.message} (TPM)`);
}
// Run some metadata checks if a statement exists for this authenticator
const statement = await metadataService_js_1.MetadataService.getStatement(aaguid);
if (statement) {
try {
await (0, verifyAttestationWithMetadata_js_1.verifyAttestationWithMetadata)({
statement,
credentialPublicKey,
x5c,
attestationStatementAlg: alg,
});
}
catch (err) {
const _err = err;
throw new Error(`${_err.message} (TPM)`);
}
}
else {
try {
// Try validating the certificate path using the root certificates set via SettingsService
await (0, validateCertificatePath_js_1.validateCertificatePath)(x5c.map(convertCertBufferToPEM_js_1.convertCertBufferToPEM), rootCertificates);
}
catch (err) {
const _err = err;
throw new Error(`${_err.message} (TPM)`);
}
}
// Verify signature over certInfo with the public key extracted from AIK certificate.
// In the wise words of Yuriy Ackermann: "Get Martini friend, you are done!"
return (0, verifySignature_js_1.verifySignature)({
signature: sig,
data: certInfo,
x509Certificate: x5c[0],
hashAlgorithm: alg,
});
}
/**
* Contain logic for pulling TPM-specific values out of subjectAlternativeName extension
*/
function getTcgAtTpmValues(root) {
const oidManufacturer = '2.23.133.2.1';
const oidModel = '2.23.133.2.2';
const oidVersion = '2.23.133.2.3';
let tcgAtTpmManufacturer;
let tcgAtTpmModel;
let tcgAtTpmVersion;
/**
* Iterate through the following potential structures:
*
* (Good, follows the spec)
* https://trustedcomputinggroup.org/wp-content/uploads/TCG_IWG_EKCredentialProfile_v2p3_r2_pub.pdf (page 33)
* Name [
* RelativeDistinguishedName [
* AttributeTypeAndValue { type, value }
* ]
* RelativeDistinguishedName [
* AttributeTypeAndValue { type, value }
* ]
* RelativeDistinguishedName [
* AttributeTypeAndValue { type, value }
* ]
* ]
*
* (Bad, does not follow the spec)
* Name [
* RelativeDistinguishedName [
* AttributeTypeAndValue { type, value }
* AttributeTypeAndValue { type, value }
* AttributeTypeAndValue { type, value }
* ]
* ]
*
* Both structures have been seen in the wild and need to be supported
*/
root.forEach((relName) => {
relName.forEach((attr) => {
if (attr.type === oidManufacturer) {
tcgAtTpmManufacturer = attr.value.toString();
}
else if (attr.type === oidModel) {
tcgAtTpmModel = attr.value.toString();
}
else if (attr.type === oidVersion) {
tcgAtTpmVersion = attr.value.toString();
}
});
});
return {
tcgAtTpmManufacturer,
tcgAtTpmModel,
tcgAtTpmVersion,
};
}
/**
* Convert TPM-specific SHA algorithm ID's with COSE-specific equivalents. Note that the choice to
* use ECDSA SHA IDs is arbitrary; any such COSEALG that would map to SHA-256 in
* `mapCoseAlgToWebCryptoAlg()`
*
* SHA IDs referenced from here:
*
* https://trustedcomputinggroup.org/wp-content/uploads/TCG_TPM2_r1p59_Part2_Structures_pub.pdf
*/
function attestedNameAlgToCOSEAlg(alg) {
if (alg === 'TPM_ALG_SHA256') {
return cose_js_1.COSEALG.ES256;
}
else if (alg === 'TPM_ALG_SHA384') {
return cose_js_1.COSEALG.ES384;
}
else if (alg === 'TPM_ALG_SHA512') {
return cose_js_1.COSEALG.ES512;
}
throw new Error(`Unexpected TPM attested name alg ${alg}`);
}