@trezor/connect
Version:
High-level javascript interface for Trezor hardware wallet.
138 lines • 6.21 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.verifyAuthenticityProof = exports.getRandomChallenge = void 0;
const tslib_1 = require("tslib");
const crypto = tslib_1.__importStar(require("crypto"));
const utils_1 = require("@trezor/utils");
const x509certificate_1 = require("./x509certificate");
const verifySignature = async (rawKey, data, signature) => {
const signer = crypto.createVerify('sha256');
signer.update(Buffer.from(data));
const SubtleCrypto = typeof window !== 'undefined' ? window.crypto.subtle : crypto.subtle;
if (!SubtleCrypto) {
throw new Error('SubtleCrypto not supported');
}
const ecPubKey = await SubtleCrypto.importKey('raw', rawKey, { name: 'ECDSA', namedCurve: 'P-256' }, true, ['verify']);
const spkiPubKey = await SubtleCrypto.exportKey('spki', ecPubKey);
const key = `-----BEGIN PUBLIC KEY-----\n${Buffer.from(spkiPubKey).toString('base64')}\n-----END PUBLIC KEY-----`;
return signer.verify({ key }, Buffer.from(signature));
};
const getRandomChallenge = () => crypto.randomBytes(32);
exports.getRandomChallenge = getRandomChallenge;
const validateCaCertExtensions = (cert, pathLen) => {
let hasKeyUsage, hasBasicConstraints = false;
cert.tbsCertificate.extensions.forEach(ext => {
if (ext.key === 'keyUsage') {
if (ext.keyCertSign !== '1') {
throw new Error(`CA keyCertSign not set`);
}
hasKeyUsage = true;
}
else if (ext.key === 'basicConstraints') {
if (!ext.cA) {
throw new Error(`CA basicConstraints.cA not set`);
}
if (typeof ext.pathLenConstraint != 'number') {
throw new Error('CA basicConstraints.pathLenConstraint not set');
}
if (ext.pathLenConstraint < pathLen) {
throw new Error('Issuer was not permitted to issue certificate');
}
hasBasicConstraints = true;
}
else if (ext.critical) {
throw new Error(`Unknown critical extension ${ext.key} in CA certificate`);
}
});
if (!hasKeyUsage || !hasBasicConstraints) {
throw new Error(`CA missing extensions keyUsage: ${hasKeyUsage}, basicConstraints: ${hasBasicConstraints}`);
}
};
const verifyAuthenticityProof = async ({ certificates, signature, challenge, config, allowDebugKeys, deviceModel, }) => {
const modelConfig = config[deviceModel];
if (!modelConfig) {
throw new Error(`Pubkeys for ${deviceModel} not found in config`);
}
const { caPubKeys, debug } = modelConfig;
const [deviceCert, caCert] = certificates.map((c, i) => {
const cert = (0, x509certificate_1.parseCertificate)(new Uint8Array(Buffer.from(c, 'hex')));
if (i === 0) {
return cert;
}
validateCaCertExtensions(cert, i - 1);
return cert;
});
const caPubKey = Buffer.from(caCert.tbsCertificate.subjectPublicKeyInfo.bits.bytes).toString('hex');
const rootPubKeys = allowDebugKeys
? modelConfig.rootPubKeys.concat(debug?.rootPubKeys || [])
: modelConfig.rootPubKeys;
const isCertSignedByRootPubkey = await Promise.all(rootPubKeys.map(rootPubKey => verifySignature(Buffer.from(rootPubKey, 'hex'), caCert.tbsCertificate.asn1.raw, caCert.signatureValue.bits.bytes)));
const rootPubKeyIndex = isCertSignedByRootPubkey.findIndex(valid => !!valid);
const rootPubKey = rootPubKeys[rootPubKeyIndex];
const isDebugRootPubKey = debug?.rootPubKeys.includes(rootPubKey);
const caCertValidityFrom = caCert.tbsCertificate.validity.from.getTime();
if (caCertValidityFrom > new Date().getTime()) {
throw new Error(`CA validity from ${caCertValidityFrom} cant't be in the future!`);
}
if (!rootPubKey) {
const configExpired = new Date(config.timestamp).getTime() < caCertValidityFrom;
return {
valid: false,
configExpired,
caPubKey,
error: 'ROOT_PUBKEY_NOT_FOUND',
};
}
const [subject] = deviceCert.tbsCertificate.subject;
if (!subject.parameters || subject.algorithm !== '2.5.4.3') {
throw new Error('Missing certificate subject');
}
const subjectValue = Buffer.from(subject.parameters.asn1.contents.subarray(0, 4)).toString();
if (subjectValue !== deviceModel) {
return {
valid: false,
caPubKey,
error: 'INVALID_DEVICE_MODEL',
};
}
const isDeviceCertValid = await verifySignature(Buffer.from(caCert.tbsCertificate.subjectPublicKeyInfo.bits.bytes), deviceCert.tbsCertificate.asn1.raw, deviceCert.signatureValue.bits.bytes);
const challengePrefix = Buffer.from('AuthenticateDevice:');
const prefixedChallenge = Buffer.concat([
utils_1.bufferUtils.getChunkSize(challengePrefix.length),
challengePrefix,
utils_1.bufferUtils.getChunkSize(challenge.length),
challenge,
]);
const isSignatureValid = await verifySignature(Buffer.from(deviceCert.tbsCertificate.subjectPublicKeyInfo.bits.bytes), prefixedChallenge, (0, x509certificate_1.fixSignature)(Buffer.from(signature, 'hex')));
if (rootPubKey && isDeviceCertValid && isSignatureValid) {
if ((!isDebugRootPubKey && !caPubKeys.includes(caPubKey)) ||
(isDebugRootPubKey && !debug?.caPubKeys.includes(caPubKey))) {
const configExpired = new Date(config.timestamp).getTime() < caCertValidityFrom;
return {
valid: false,
configExpired,
caPubKey,
error: 'CA_PUBKEY_NOT_FOUND',
};
}
return {
valid: true,
caPubKey,
debugKey: isDebugRootPubKey,
};
}
if (!isDeviceCertValid) {
return {
valid: false,
caPubKey,
error: 'INVALID_DEVICE_CERTIFICATE',
};
}
return {
valid: false,
caPubKey,
error: 'INVALID_DEVICE_SIGNATURE',
};
};
exports.verifyAuthenticityProof = verifyAuthenticityProof;
//# sourceMappingURL=verifyAuthenticityProof.js.map