UNPKG

@zkp2p/reclaim-witness-sdk

Version:

<div> <div> <img src="https://raw.githubusercontent.com/reclaimprotocol/.github/main/assets/banners/Attestor-Core.png" /> </div> </div>

191 lines 18.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.processHandshake = processHandshake; const tls_1 = require("@reclaimprotocol/tls"); const parse_certificate_1 = require("@reclaimprotocol/tls/lib/utils/parse-certificate"); const api_1 = require("../../proto/api"); const utils_1 = require("../../utils"); const RECORD_LENGTH_BYTES = 3; /** * Verifies server cert chain and removes handshake messages from transcript * @param receipt * @param logger */ async function processHandshake(receipt, logger) { let currentPacketIdx = 0; let readPacketIdx = 0; let handshakeData = Uint8Array.from([]); let packetData; const handshakeRawMessages = []; const certificates = []; let cipherSuite = undefined; let tlsVersion = undefined; let serverRandom = undefined; let clientRandom = undefined; let serverFinishedIdx = -1; let clientFinishedIdx = -1; let certVerified = false; let hostname = undefined; let clientChangeCipherSpecMsgIdx = -1; let serverChangeCipherSpecMsgIdx = -1; while ((packetData = await readPacket())) { const { type, content } = packetData; switch (type) { case tls_1.SUPPORTED_RECORD_TYPE_MAP.CLIENT_HELLO: const clientHello = (0, tls_1.parseClientHello)(handshakeRawMessages[0]); clientRandom = clientHello.serverRandom; const { SERVER_NAME: sni } = clientHello.extensions; hostname = sni === null || sni === void 0 ? void 0 : sni.serverName; if (!hostname) { throw new Error('client hello has no SNI'); } break; case tls_1.SUPPORTED_RECORD_TYPE_MAP.SERVER_HELLO: const serverHello = await (0, tls_1.parseServerHello)(content); cipherSuite = serverHello.cipherSuite; tlsVersion = serverHello.serverTlsVersion; serverRandom = serverHello.serverRandom; logger.info({ serverTLSVersion: tlsVersion, cipherSuite }, 'extracted server hello params'); break; case tls_1.SUPPORTED_RECORD_TYPE_MAP.CERTIFICATE: const parseResult = (0, tls_1.parseCertificates)(content, { version: tlsVersion }); certificates.push(...parseResult.certificates); break; case tls_1.SUPPORTED_RECORD_TYPE_MAP.CERTIFICATE_VERIFY: const signature = (0, tls_1.parseServerCertificateVerify)(content); if (!(certificates === null || certificates === void 0 ? void 0 : certificates.length)) { throw new Error('No provider certificates received'); } const signatureData = await (0, tls_1.getSignatureDataTls13)(handshakeRawMessages.slice(0, -1), cipherSuite); await (0, tls_1.verifyCertificateSignature)({ ...signature, publicKey: certificates[0].getPublicKey(), signatureData, }); await (0, parse_certificate_1.verifyCertificateChain)(certificates, hostname); logger.info({ host: hostname }, 'verified provider certificate chain'); certVerified = true; break; case tls_1.SUPPORTED_RECORD_TYPE_MAP.SERVER_KEY_SHARE: if (!(certificates === null || certificates === void 0 ? void 0 : certificates.length)) { throw new Error('No provider certificates received'); } const keyShare = await (0, tls_1.processServerKeyShare)(content); const signatureData12 = await (0, tls_1.getSignatureDataTls12)({ clientRandom: clientRandom, serverRandom: serverRandom, curveType: keyShare.publicKeyType, publicKey: keyShare.publicKey, }); // verify signature await (0, tls_1.verifyCertificateSignature)({ signature: keyShare.signatureBytes, algorithm: keyShare.signatureAlgorithm, publicKey: certificates[0].getPublicKey(), signatureData: signatureData12, }); await (0, parse_certificate_1.verifyCertificateChain)(certificates, hostname); logger.info({ host: hostname }, 'verified provider certificate chain'); certVerified = true; break; case tls_1.SUPPORTED_RECORD_TYPE_MAP.FINISHED: if (receipt[readPacketIdx].sender === api_1.TranscriptMessageSenderType.TRANSCRIPT_MESSAGE_SENDER_TYPE_CLIENT) { clientFinishedIdx = readPacketIdx; } else { serverFinishedIdx = readPacketIdx; } break; } } if (!certVerified) { throw new Error('No provider certificates received'); } if (tlsVersion === 'TLS1_3' && serverFinishedIdx < 0) { throw new Error('server finished message not found'); } if (tlsVersion === 'TLS1_2' && (serverChangeCipherSpecMsgIdx < 0 || clientChangeCipherSpecMsgIdx < 0)) { throw new Error('change cipher spec message not found'); } async function readPacket(getMoreData = false) { var _a; if (currentPacketIdx > (receipt.length - 1)) { return; } if (certVerified && serverFinishedIdx > 0 && clientFinishedIdx > 0) { return; } readPacketIdx = currentPacketIdx; if (!(handshakeData === null || handshakeData === void 0 ? void 0 : handshakeData.length) || getMoreData) { let newHandshakeData; const { message, reveal, sender } = receipt[currentPacketIdx]; const recordHeader = message.slice(0, 5); const content = getWithoutHeader(message); if (message[0] === tls_1.PACKET_TYPE['CHANGE_CIPHER_SPEC']) { //skip change cipher spec message if (sender === api_1.TranscriptMessageSenderType.TRANSCRIPT_MESSAGE_SENDER_TYPE_CLIENT) { clientChangeCipherSpecMsgIdx = currentPacketIdx; } else { serverChangeCipherSpecMsgIdx = currentPacketIdx; } currentPacketIdx++; return await readPacket(); } if (message[0] === tls_1.PACKET_TYPE['WRAPPED_RECORD'] || (serverChangeCipherSpecMsgIdx > 0 && sender === api_1.TranscriptMessageSenderType.TRANSCRIPT_MESSAGE_SENDER_TYPE_SERVER) || (clientChangeCipherSpecMsgIdx > 0 && sender === api_1.TranscriptMessageSenderType.TRANSCRIPT_MESSAGE_SENDER_TYPE_CLIENT)) { // encrypted if (!tlsVersion || !cipherSuite) { throw new Error('Could not find cipherSuite to use'); } if (!((_a = reveal === null || reveal === void 0 ? void 0 : reveal.directReveal) === null || _a === void 0 ? void 0 : _a.key)) { throw new Error('no direct reveal for handshake packet'); } const { plaintext } = await (0, utils_1.decryptDirect)(reveal === null || reveal === void 0 ? void 0 : reveal.directReveal, cipherSuite, recordHeader, tlsVersion, content); newHandshakeData = plaintext; if (tlsVersion === 'TLS1_3') { newHandshakeData = newHandshakeData.slice(0, -1); } } else { newHandshakeData = content; } handshakeData = (0, tls_1.concatenateUint8Arrays)([handshakeData, newHandshakeData]); } const type = handshakeData[0]; const content = readWithLength(handshakeData.slice(1), RECORD_LENGTH_BYTES); if (!content) { logger.warn('missing bytes from packet'); currentPacketIdx++; return await readPacket(true); } const totalLength = 1 + RECORD_LENGTH_BYTES + content.length; handshakeRawMessages.push(handshakeData.slice(0, totalLength)); handshakeData = handshakeData.slice(totalLength); if (!handshakeData.length) { currentPacketIdx++; } return { type, content }; } const nextMsgIndex = Math.max(serverFinishedIdx, clientFinishedIdx) + 1; return { tlsVersion: tlsVersion, cipherSuite: cipherSuite, hostname: hostname, nextMsgIndex }; } function getWithoutHeader(message) { // strip the record header (xx 03 03 xx xx) return message.slice(5); } function readWithLength(data, lengthBytes = 2) { const dataView = (0, tls_1.uint8ArrayToDataView)(data); const length = lengthBytes === 1 ? dataView.getUint8(0) : dataView.getUint16(lengthBytes === 3 ? 1 : 0); if (data.length < lengthBytes + length) { return undefined; } return data.slice(lengthBytes, lengthBytes + length); } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHJvY2Vzcy1oYW5kc2hha2UuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvc2VydmVyL3V0aWxzL3Byb2Nlc3MtaGFuZHNoYWtlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBNEJBLDRDQTRNQztBQXhPRCw4Q0FjNkI7QUFDN0Isd0ZBQXlGO0FBQ3pGLHVDQUErRTtBQUUvRSxxQ0FBeUM7QUFHekMsTUFBTSxtQkFBbUIsR0FBRyxDQUFDLENBQUE7QUFFN0I7Ozs7R0FJRztBQUNJLEtBQUssVUFBVSxnQkFBZ0IsQ0FBQyxPQUF5QyxFQUFFLE1BQWM7SUFDL0YsSUFBSSxnQkFBZ0IsR0FBRyxDQUFDLENBQUE7SUFDeEIsSUFBSSxhQUFhLEdBQUcsQ0FBQyxDQUFBO0lBQ3JCLElBQUksYUFBYSxHQUFlLFVBQVUsQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDLENBQUE7SUFDbkQsSUFBSSxVQUFrRCxDQUFBO0lBQ3RELE1BQU0sb0JBQW9CLEdBQWlCLEVBQUUsQ0FBQTtJQUM3QyxNQUFNLFlBQVksR0FBc0IsRUFBRSxDQUFBO0lBQzFDLElBQUksV0FBVyxHQUE0QixTQUFTLENBQUE7SUFDcEQsSUFBSSxVQUFVLEdBQW1DLFNBQVMsQ0FBQTtJQUMxRCxJQUFJLFlBQVksR0FBMkIsU0FBUyxDQUFBO0lBQ3BELElBQUksWUFBWSxHQUEyQixTQUFTLENBQUE7SUFDcEQsSUFBSSxpQkFBaUIsR0FBRyxDQUFDLENBQUMsQ0FBQTtJQUMxQixJQUFJLGlCQUFpQixHQUFHLENBQUMsQ0FBQyxDQUFBO0lBQzFCLElBQUksWUFBWSxHQUFHLEtBQUssQ0FBQTtJQUN4QixJQUFJLFFBQVEsR0FBdUIsU0FBUyxDQUFBO0lBQzVDLElBQUksNEJBQTRCLEdBQUcsQ0FBQyxDQUFDLENBQUE7SUFDckMsSUFBSSw0QkFBNEIsR0FBRyxDQUFDLENBQUMsQ0FBQTtJQUNyQyxPQUFNLENBQUMsVUFBVSxHQUFHLE1BQU0sVUFBVSxFQUFFLENBQUMsRUFB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