Files
Filip Skokan a2ce2c6def tls,tools: update BoringSSL pin and TLS errors
Use BoringSSL 0.20261020.0 for shared-library builds and expose the
package wrapper's version to the matrix updater.

The new verifier no longer emits UNABLE_TO_VERIFY_LEAF_SIGNATURE
after Node's verification callback accepts a missing-issuer error.
Expect UNABLE_TO_GET_ISSUER_CERT_LOCALLY for the affected leaf
certificates and suggest --use-system-ca for that error.

Drop the SSL_get_server_tmp_key() fallback now that BoringSSL supplies
SSL_get_peer_tmp_key() as a compatibility stub.

Refs: https://boringssl-review.googlesource.com/c/boringssl/+/103849
Refs: https://github.com/electron/electron/pull/55110/changes/b457e70e339e8bb3066b210edfb6f96582cdb374
Signed-off-by: Filip Skokan <panva.ip@gmail.com>
Assisted-by: Codex
PR-URL: https://github.com/nodejs/node/pull/66922
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
2026-10-24 22:04:13 +00:00

238 lines
7.6 KiB
JavaScript

/* eslint-disable node-core/crypto-check */
'use strict';
const crypto = require('crypto');
const net = require('net');
const assert = require('assert');
const { isBoringSSL } = require('./crypto');
// BoringSSL reports the missing issuer directly for a lone, non-self-signed
// leaf. OpenSSL replaces it with a leaf signature verification error.
exports.untrustedLeafErrorCode = isBoringSSL ?
'UNABLE_TO_GET_ISSUER_CERT_LOCALLY' : 'UNABLE_TO_VERIFY_LEAF_SIGNATURE';
exports.ccs = Buffer.from('140303000101', 'hex');
class TestTLSSocket extends net.Socket {
constructor(server_cert) {
super();
this.server_cert = server_cert;
this.version = Buffer.from('0303', 'hex');
this.handshake_list = [];
// AES128-GCM-SHA256
this.ciphers = Buffer.from('000002009c0', 'hex');
this.pre_primary_secret =
Buffer.concat([this.version, crypto.randomBytes(46)]);
this.primary_secret = null;
this.write_seq = 0;
this.client_random = crypto.randomBytes(32);
this.on('handshake', (msg) => {
this.handshake_list.push(msg);
});
this.on('server_random', (server_random) => {
this.primary_secret = PRF12('sha256', this.pre_primary_secret,
'primary secret',
Buffer.concat([this.client_random,
server_random]),
48);
const key_block = PRF12('sha256', this.primary_secret,
'key expansion',
Buffer.concat([server_random,
this.client_random]),
40);
this.client_writeKey = key_block.slice(0, 16);
this.client_writeIV = key_block.slice(32, 36);
});
}
createClientHello() {
const compressions = Buffer.from('0100', 'hex'); // null
const msg = addHandshakeHeader(0x01, Buffer.concat([
this.version, this.client_random, this.ciphers, compressions,
]));
this.emit('handshake', msg);
return addRecordHeader(0x16, msg);
}
createClientKeyExchange() {
const encrypted_pre_primary_secret = crypto.publicEncrypt({
key: this.server_cert,
padding: crypto.constants.RSA_PKCS1_PADDING,
}, this.pre_primary_secret);
const length = Buffer.alloc(2);
length.writeUIntBE(encrypted_pre_primary_secret.length, 0, 2);
const msg = addHandshakeHeader(0x10, Buffer.concat([
length, encrypted_pre_primary_secret]));
this.emit('handshake', msg);
return addRecordHeader(0x16, msg);
}
createFinished() {
const shasum = crypto.createHash('sha256');
shasum.update(Buffer.concat(this.handshake_list));
const message_hash = shasum.digest();
const r = PRF12('sha256', this.primary_secret,
'client finished', message_hash, 12);
const msg = addHandshakeHeader(0x14, r);
this.emit('handshake', msg);
return addRecordHeader(0x16, msg);
}
createIllegalHandshake() {
const illegal_handshake = Buffer.alloc(5);
return addRecordHeader(0x16, illegal_handshake);
}
parseTLSFrame(buf) {
let offset = 0;
const record = buf.slice(offset, 5);
const type = record[0];
const length = record.slice(3, 5).readUInt16BE(0);
offset += 5;
let remaining = buf.slice(offset, offset + length);
if (type === 0x16) {
do {
remaining = this.parseTLSHandshake(remaining);
} while (remaining.length > 0);
}
offset += length;
return buf.slice(offset);
}
parseTLSHandshake(buf) {
let offset = 0;
const handshake_type = buf[offset];
if (handshake_type === 0x02) {
const server_random = buf.slice(6, 6 + 32);
this.emit('server_random', server_random);
}
offset += 1;
const length = buf.readUIntBE(offset, 3);
offset += 3;
const handshake = buf.slice(0, offset + length);
this.emit('handshake', handshake);
offset += length;
const remaining = buf.slice(offset);
return remaining;
}
encrypt(plain) {
const type = plain.slice(0, 1);
const version = plain.slice(1, 3);
const nonce = crypto.randomBytes(8);
const iv = Buffer.concat([this.client_writeIV.slice(0, 4), nonce]);
const bob = crypto.createCipheriv('aes-128-gcm', this.client_writeKey, iv);
const write_seq = Buffer.alloc(8);
write_seq.writeUInt32BE(this.write_seq++, 4);
const aad = Buffer.concat([write_seq, plain.slice(0, 5)]);
bob.setAAD(aad);
const encrypted1 = bob.update(plain.slice(5));
const encrypted = Buffer.concat([encrypted1, bob.final()]);
const tag = bob.getAuthTag();
const length = Buffer.alloc(2);
length.writeUInt16BE(nonce.length + encrypted.length + tag.length, 0);
return Buffer.concat([type, version, length, nonce, encrypted, tag]);
}
}
function addRecordHeader(type, frame) {
const record_layer = Buffer.from('0003030000', 'hex');
record_layer[0] = type;
record_layer.writeUInt16BE(frame.length, 3);
return Buffer.concat([record_layer, frame]);
}
function addHandshakeHeader(type, msg) {
const handshake_header = Buffer.alloc(4);
handshake_header[0] = type;
handshake_header.writeUIntBE(msg.length, 1, 3);
return Buffer.concat([handshake_header, msg]);
}
function PRF12(algo, secret, label, seed, size) {
const newSeed = Buffer.concat([Buffer.from(label, 'utf8'), seed]);
return P_hash(algo, secret, newSeed, size);
}
function P_hash(algo, secret, seed, size) {
const result = Buffer.alloc(size);
let hmac = crypto.createHmac(algo, secret);
hmac.update(seed);
let a = hmac.digest();
let j = 0;
while (j < size) {
hmac = crypto.createHmac(algo, secret);
hmac.update(a);
hmac.update(seed);
const b = hmac.digest();
let todo = b.length;
if (j + todo > size) {
todo = size - j;
}
b.copy(result, j, 0, todo);
j += todo;
hmac = crypto.createHmac(algo, secret);
hmac.update(a);
a = hmac.digest();
}
return result;
}
exports.assertIsCAArray = function assertIsCAArray(certs) {
assert(Array.isArray(certs));
assert(certs.length > 0);
// The certificates looks PEM-encoded.
for (const cert of certs) {
const trimmed = cert.trim();
assert.match(trimmed, /^-----BEGIN CERTIFICATE-----/);
assert.match(trimmed, /-----END CERTIFICATE-----$/);
}
};
function extractMetadata(cert) {
const x509 = new crypto.X509Certificate(cert);
return {
serialNumber: x509.serialNumber,
issuer: x509.issuer,
subject: x509.subject,
};
}
exports.extractMetadata = extractMetadata;
// To compare two certificates, we can just compare serialNumber, issuer,
// and subject like X509_comp(). We can't just compare two strings because
// the line endings or order of the fields may differ after PEM serdes by
// OpenSSL.
exports.assertEqualCerts = function assertEqualCerts(a, b) {
const setA = new Set(a.map(extractMetadata));
const setB = new Set(b.map(extractMetadata));
assert.deepStrictEqual(setA, setB);
};
exports.includesCert = function includesCert(certs, cert) {
const metadata = extractMetadata(cert);
for (const c of certs) {
const cMetadata = extractMetadata(c);
if (cMetadata.serialNumber === metadata.serialNumber &&
cMetadata.issuer === metadata.issuer &&
cMetadata.subject === metadata.subject) {
return true;
}
}
return false;
};
exports.TestTLSSocket = TestTLSSocket;
// Dumps certs into a file to pass safely into test/fixtures/list-certs.js
exports.writeCerts = function writeCerts(certs, filename) {
const fs = require('fs');
for (const cert of certs) {
const x509 = new crypto.X509Certificate(cert);
fs.appendFileSync(filename, x509.toString());
}
};