@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
JavaScript
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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
;