lemon-tls
Version:
JavaScript TLS 1.3/1.2 implementation for Node.js, with full control over cryptographic keys and record layer
1,331 lines (1,036 loc) • 53.9 kB
JavaScript
import { hmac } from '@noble/hashes/hmac.js';
import { hkdf, extract as hkdf_extract, expand as hkdf_expand } from '@noble/hashes/hkdf.js';
import { sha256, sha384 } from '@noble/hashes/sha2.js';
import { p256 } from '@noble/curves/nist.js';
import { ed25519, x25519 } from '@noble/curves/ed25519.js';
var nobleHashes = { hmac, hkdf, hkdf_extract, hkdf_expand, sha256 };
import * as crypto from 'crypto';
import {
TLS_CIPHER_SUITES,
build_cert_verify_tbs,
get_handshake_finished,
derive_handshake_traffic_secrets,
derive_app_traffic_secrets
} from './crypto.js';
import {
concatUint8Arrays,
arraybufferEqual,
arraysEqual
} from './utils.js';
import * as wire from './wire.js';
//var wire = require('./wire');
/** מינימל-Emitter בסגנון שלך */
function Emitter(){
var listeners = {};
return {
on: function(name, fn){ (listeners[name] = listeners[name] || []).push(fn); },
emit: function(name){
var args = Array.prototype.slice.call(arguments, 1);
var arr = listeners[name] || [];
for (var i=0;i<arr.length;i++){ try{ arr[i].apply(null, args); }catch(e){} }
}
};
}
function arrOrDefault(a, d){
return (a && a.length) ? a.slice(0) : d.slice(0);
}
function arrOrNull(a){
return (a && a.length) ? a.slice(0) : null;
}
function normalizeHello(hello) {
var out = {
// Basics
message: hello.message, // 'client_hello' | 'server_hello'
legacy_version: hello.legacy_version, // 0x0303
version: hello.version || hello.version_hint || null,
random: hello.random || null, // Uint8Array(32)
session_id: hello.session_id || null, // Uint8Array
// Negotiation fields
cipher_suites: hello.cipher_suites || null, // ClientHello array
cipher_suite: hello.cipher_suite || null, // ServerHello selected
legacy_compression: hello.legacy_compression || null,
// Commonly used extensions (flattened)
sni: null, // string
alpn: null, // string[]
key_shares: null, // Client: array of {group, key_exchange}; Server: {group, key_exchange}
supported_versions: null, // Client: number[]; Server: number
signature_algorithms: null, // number[]
supported_groups: null, // number[]
// TLS 1.2 / misc
renegotiation_info: null, // Uint8Array
status_request: null, // raw/decoded if available
max_fragment_length: null, // number or enum
signature_algorithms_cert: null, // number[]
certificate_authorities: null, // raw/decoded if available
sct: null, // SignedCertificateTimestamp list (raw/decoded)
heartbeat: null, // heartbeat mode
use_srtp: null, // SRTP profiles
// TLS 1.3 specific
cookie: null, // Uint8Array
early_data: null, // true/params if present
psk_key_exchange_modes: null, // number[]
// Raw list of extensions
extensions: hello.extensions || [],
// Bucket for anything unmapped
unknown: []
};
if (!hello.extensions) {
return out;
}
for (var i = 0; i < hello.extensions.length; i++) {
var e = hello.extensions[i];
var val = (e.value !== undefined && e.value !== null) ? e.value : null;
switch (e.name) {
case 'SERVER_NAME':
out.sni = val; // string
break;
case 'ALPN':
out.alpn = val; // string[]
break;
case 'KEY_SHARE':
out.key_shares = val; // client: array, server: object
break;
case 'SUPPORTED_VERSIONS':
out.supported_versions = val; // array or number
break;
case 'SIGNATURE_ALGORITHMS':
out.signature_algorithms = val; // number[]
break;
case 'SUPPORTED_GROUPS':
out.supported_groups = val; // number[]
break;
// ---- TLS 1.2 & misc ----
case 'RENEGOTIATION_INFO':
out.renegotiation_info = val; // Uint8Array
break;
case 'STATUS_REQUEST':
out.status_request = val; // currently raw unless a decoder is added
break;
case 'MAX_FRAGMENT_LENGTH':
out.max_fragment_length = val; // number/enum (decoder not implemented yet)
break;
case 'SIGNATURE_ALGORITHMS_CERT':
out.signature_algorithms_cert = val; // number[]
break;
case 'CERTIFICATE_AUTHORITIES':
out.certificate_authorities = val; // raw list unless decoder added
break;
case 'SCT':
out.sct = val; // raw/decoded SCT list
break;
case 'HEARTBEAT':
out.heartbeat = val; // heartbeat mode
break;
case 'USE_SRTP':
out.use_srtp = val; // SRTP params
break;
// ---- TLS 1.3 ----
case 'COOKIE':
out.cookie = val; // Uint8Array
break;
case 'EARLY_DATA':
out.early_data = (val === null) ? true : val; // presence indicates support
break;
case 'PSK_KEY_EXCHANGE_MODES':
out.psk_key_exchange_modes = val; // number[]
break;
default:
out.unknown.push(e);
}
}
// --- פוסט-פרוסס: השלמות ל-1.2 כשאין ext SUPPORTED_VERSIONS ---
if (out.supported_versions == null) {
if (out.message === 'client_hello' && typeof out.legacy_version === 'number') {
// ב-1.2 הקליינט לא שולח ext, נשתול מערך עם הגרסה ה"מורשת" (לרוב 0x0303)
out.supported_versions = [out.legacy_version|0];
} else if (out.message === 'server_hello' && typeof out.legacy_version === 'number') {
// בסרבר: הגרסה הנבחרת נמצאת בשדה הישן; שמור גם ב-version
if (out.version == null) out.version = out.legacy_version|0;
out.supported_versions = out.version; // שמור סכימה: בסרבר זה "number"
}
}
// גם אם אין KEY_SHARE ב-1.2, נרצה אינדיקציה ריקה במקום null (קוד downstream נקי יותר)
if (out.key_shares == null && out.message === 'client_hello') {
out.key_shares = []; // ב-1.2 ה-ECDHE יבוא ב-ServerKeyExchange, לא כאן
}
return out;
}
function normalizeOptions(opts){
opts = opts || {};
var out = {
minVersion: opts.minVersion || null,
maxVersion: opts.maxVersion || null,
cipherPreference: arrOrDefault(opts.cipherPreference, null),
groupPreference: arrOrDefault(opts.groupPreference, null),
sigAlgPreference: arrOrDefault(opts.sigAlgPreference, null),
ALPNProtocols: arrOrNull(opts.ALPNProtocols),
requestCert: !!opts.requestCert,
rejectUnauthorized: !!opts.rejectUnauthorized,
secureContext: opts.secureContext || null,
isServer: !!opts.isServer
};
// ודא min<=max
if (out.minVersion > out.maxVersion){
var t = out.minVersion; out.minVersion = out.maxVersion; out.maxVersion = t;
}
return out;
}
function TLSSession(options){
if (!(this instanceof TLSSession)) return new TLSSession(options);
options = options || {};
var ev = Emitter();
var context = {
state: 'new', //new | negotiating | ...
isServer: !!options.isServer,
SNICallback: options.SNICallback || null,
local_versions: [], // [0x0304, 0x0303, ...]
local_cipher_suites: [], //[0x1303, 0x1301, 0x1302]
local_alpns: [], // ['h3','h2','http/1.1', ...]
local_groups: [], //[0x001D, 0x0017, 0x0018]// x25519, secp256r1, secp384r1
local_signature_algorithms: [], //[0x0403, 0x0804, 0x0805] //ecdsa_secp256r1_sha256 (0x0403), rsa_pss_rsae_sha256 (0x0804), rsa_pss_rsae_sha384 (0x0805)
local_extensions: [], // [{ type, data }, ...]
local_key_share_public: null, // Uint8Array (pubkey שנשלח)
local_key_share_private: null, // Uint8Array (privkey זמני)
// REMOTE (מה שהצד המרוחק שלח/מציע)
remote_versions: [],
remote_cipher_suites: [],
remote_alpns: [],
remote_groups: [],
remote_signature_algorithms: [],
remote_extensions: [],
remote_key_shares: [],
remote_sni: null,
remote_session_id: null,
remote_random: null,
// SELECTED (מה שנקבע בפועל)
selected_version: null, // 0x0304 / 0x0303
selected_cipher_suite: null, // למשל 0x1301
selected_alpn: null, // 'h3'/'h2'/... או null
selected_group: null, // למשל 0x001D
selected_signature_algorithm: null, // למשל 0x0804
selected_extensions: [], // [type,...] או [{type,dataConfirmed}]
selected_sni: null, // string|null
selected_session_id: null, // TLS 1.2 בלבד
// שאר מצב/מפתחות/תעודות/תזמון
transcript: [], // Handshake inner messages
local_random: null,
//whats already sent?
hello_sent: false,
encrypted_exts_sent: false,
cert_sent: false,
cert_verify_sent: false,
finished_sent: false,
message_sent_seq: 0,
remote_finished: null,
expected_remote_finished: null,
remote_finished_ok: false,
local_key_share_private: null,
local_key_share_public: null,
ecdhe_shared_secret: null,
handshake_secret: null,
client_handshake_traffic_secret: null,
server_handshake_traffic_secret: null,
client_app_traffic_secret: null,
server_app_traffic_secret: null,
local_cert_chain: null,
remote_cert_chain: null,
selected_cert: null,
cert_private_key: null
};
function process_income_message(data){
var message = wire.parse_message(data);
if (message.type == wire.TLS_MESSAGE_TYPE.CLIENT_HELLO || message.type == wire.TLS_MESSAGE_TYPE.SERVER_HELLO) {
var hello = wire.parse_hello(message.type, message.body);
var info = normalizeHello(hello);
//console.log(info);
context.transcript.push(data);
set_context({
remote_random: info.random || null,
remote_sni: info.sni || null,
remote_session_id: info.session_id || null, // TLS 1.2 בעיקר
remote_cipher_suites: info.cipher_suites || [],
remote_alpns: info.alpn || [],
remote_key_shares: info.key_shares || [],
remote_versions: info.supported_versions || [],
remote_signature_algorithms: info.signature_algorithms || [],
remote_groups: info.supported_groups || [],
remote_extensions: info.extensions || [],
});
ev.emit('hello');
if(typeof context.SNICallback=='function'){
context.SNICallback(context.remote_sni, function (err, creds) {
if (!err && creds) {
set_context({
local_cert_chain: creds.certificateChain,
cert_private_key: creds.privateKey
});
} else {
//console.log('No TLS credentials for', current_params.sni);
}
});
}
} else if (message.type == wire.TLS_MESSAGE_TYPE.FINISHED) {
set_context({
remote_finished: message.body
});
}
}
function set_context(options){
var has_changed=false;
var fields=[
'local_versions',
'local_cipher_suites',
'local_alpns',
'local_groups',
'local_signature_algorithms',
'local_extensions',
'remote_versions',
'remote_cipher_suites',
'remote_alpns',
'remote_groups',
'remote_signature_algorithms',
'remote_extensions',
'remote_key_shares',
'remote_sni',
'remote_session_id',
'remote_random',
'selected_version',
'selected_cipher_suite',
'selected_alpn',
'selected_group',
'selected_signature_algorithm',
'selected_extensions',
'selected_sni',
'selected_session_id',
'local_key_share_public',
'local_key_share_private',
'ecdhe_shared_secret',
'server_handshake_traffic_secret',
'client_handshake_traffic_secret',
'server_app_traffic_secret',
'client_app_traffic_secret',
'local_cert_chain',
'remote_cert_chain',
'cert_private_key',
'remote_finished_ok',
'remote_finished',
'expected_remote_finished'
];
var prev={};
if (options && typeof options === 'object'){
if('local_versions' in options){
if(arraysEqual(options.local_versions,context.local_versions)==false){
prev['local_versions']=context['local_versions'];
context.local_versions=options.local_versions;
has_changed=true;
}
}
if('local_cipher_suites' in options){
if(arraysEqual(options.local_cipher_suites,context.local_cipher_suites)==false){
prev['local_cipher_suites']=context['local_cipher_suites'];
context.local_cipher_suites=options.local_cipher_suites;
has_changed=true;
}
}
if('local_alpns' in options){
if(arraysEqual(options.local_alpns,context.local_alpns)==false){
prev['local_alpns']=context['local_alpns'];
context.local_alpns=options.local_alpns;
has_changed=true;
}
}
if('local_groups' in options){
if(arraysEqual(options.local_groups,context.local_groups)==false){
prev['local_groups']=context['local_groups'];
context.local_groups=options.local_groups;
has_changed=true;
}
}
if('local_signature_algorithms' in options){
if(arraysEqual(options.local_signature_algorithms,context.local_signature_algorithms)==false){
prev['local_signature_algorithms']=context['local_signature_algorithms'];
context.local_signature_algorithms=options.local_signature_algorithms;
has_changed=true;
}
}
if('local_extensions' in options){
if(arraysEqual(options.local_extensions,context.local_extensions)==false){
prev['local_extensions']=context['local_extensions'];
context.local_extensions=options.local_extensions;
has_changed=true;
}
}
if('local_key_share_public' in options){
if((context.local_key_share_public==null && options.local_key_share_public!==null) || !arraybufferEqual(options.local_key_share_public.buffer,context.local_key_share_public.buffer)){
prev['local_key_share_public']=context['local_key_share_public'];
context.local_key_share_public=options.local_key_share_public;
has_changed=true;
}
}
if('local_key_share_private' in options){
if((context.local_key_share_private==null && options.local_key_share_private!==null) || !arraybufferEqual(options.local_key_share_private.buffer,context.local_key_share_private.buffer)){
prev['local_key_share_private']=context['local_key_share_private'];
context.local_key_share_private=options.local_key_share_private;
has_changed=true;
}
}
if('remote_versions' in options){
if(arraysEqual(options.remote_versions,context.remote_versions)==false){
prev['remote_versions']=context['remote_versions'];
context.remote_versions=options.remote_versions;
has_changed=true;
}
}
if('remote_cipher_suites' in options){
if(arraysEqual(options.remote_cipher_suites,context.remote_cipher_suites)==false){
prev['remote_cipher_suites']=context['remote_cipher_suites'];
context.remote_cipher_suites=options.remote_cipher_suites;
has_changed=true;
}
}
if('remote_alpns' in options){
if(arraysEqual(options.remote_alpns,context.remote_alpns)==false){
prev['remote_alpns']=context['remote_alpns'];
context.remote_alpns=options.remote_alpns;
has_changed=true;
}
}
if('remote_groups' in options){
if(arraysEqual(options.remote_groups,context.remote_groups)==false){
prev['remote_groups']=context['remote_groups'];
context.remote_groups=options.remote_groups;
has_changed=true;
}
}
if('remote_signature_algorithms' in options){
if(arraysEqual(options.remote_signature_algorithms,context.remote_signature_algorithms)==false){
prev['remote_signature_algorithms']=context['remote_signature_algorithms'];
context.remote_signature_algorithms=options.remote_signature_algorithms;
has_changed=true;
}
}
if('remote_extensions' in options){
if(arraysEqual(options.remote_extensions,context.remote_extensions)==false){
prev['remote_extensions']=context['remote_extensions'];
context.remote_extensions=options.remote_extensions;
has_changed=true;
}
}
if('remote_key_shares' in options){
if(arraysEqual(options.remote_key_shares,context.remote_key_shares)==false){
prev['remote_key_shares']=context['remote_key_shares'];
context.remote_key_shares=options.remote_key_shares;
has_changed=true;
}
}
if('remote_sni' in options){
if(options.remote_sni!==context.remote_sni){
prev['remote_sni']=context['remote_sni'];
context.remote_sni=options.remote_sni;
has_changed=true;
}
}
if('remote_session_id' in options){
if((context.remote_session_id==null && options.remote_session_id!==null) || !arraybufferEqual(options.remote_session_id.buffer,context.remote_session_id.buffer)){
prev['remote_session_id']=context['remote_session_id'];
context.remote_session_id=options.remote_session_id;
has_changed=true;
}
}
if('remote_random' in options){
if((context.remote_random==null && options.remote_random!==null) || !arraybufferEqual(options.remote_random.buffer,context.remote_random.buffer)){
prev['remote_random']=context['remote_random'];
context.remote_random=options.remote_random;
has_changed=true;
}
}
if('selected_version' in options){
if(options.selected_version!==context.selected_version){
prev['selected_version']=context['selected_version'];
context.selected_version=options.selected_version;
has_changed=true;
}
}
if('selected_cipher_suite' in options){
if(options.selected_cipher_suite!==context.selected_cipher_suite){
prev['selected_cipher_suite']=context['selected_cipher_suite'];
context.selected_cipher_suite=options.selected_cipher_suite;
has_changed=true;
}
}
if('selected_alpn' in options){
if(options.selected_alpn!==context.selected_alpn){
prev['selected_alpn']=context['selected_alpn'];
context.selected_alpn=options.selected_alpn;
has_changed=true;
}
}
if('selected_group' in options){
if(options.selected_group!==context.selected_group){
prev['selected_group']=context['selected_group'];
context.selected_group=options.selected_group;
has_changed=true;
}
}
if('selected_signature_algorithm' in options){
if(options.selected_signature_algorithm!==context.selected_signature_algorithm){
prev['selected_signature_algorithm']=context['selected_signature_algorithm'];
context.selected_signature_algorithm=options.selected_signature_algorithm;
has_changed=true;
}
}
if('selected_extensions' in options){
if(arraysEqual(options.selected_extensions,context.selected_extensions)==false){
prev['selected_extensions']=context['selected_extensions'];
context.selected_extensions=options.selected_extensions;
has_changed=true;
}
}
if('selected_sni' in options){
if(options.selected_sni!==context.selected_sni){
prev['selected_sni']=context['selected_sni'];
context.selected_sni=options.selected_sni;
has_changed=true;
}
}
if('selected_session_id' in options){
if((context.selected_session_id==null && options.selected_session_id!==null) || !arraybufferEqual(options.selected_session_id.buffer,context.selected_session_id.buffer)){
prev['selected_session_id']=context['selected_session_id'];
context.selected_session_id=options.selected_session_id;
has_changed=true;
}
}
if('ecdhe_shared_secret' in options){
if(context.ecdhe_shared_secret==null && options.ecdhe_shared_secret!==null){
prev['ecdhe_shared_secret']=context['ecdhe_shared_secret'];
context.ecdhe_shared_secret=options.ecdhe_shared_secret;
has_changed=true;
}
}
if('handshake_secret' in options){
if(context.handshake_secret==null && options.handshake_secret!==null){
prev['handshake_secret']=context['handshake_secret'];
context.handshake_secret=options.handshake_secret;
has_changed=true;
}
}
if('client_handshake_traffic_secret' in options){
if(context.client_handshake_traffic_secret==null && options.client_handshake_traffic_secret!==null){
prev['client_handshake_traffic_secret']=context['client_handshake_traffic_secret'];
context.client_handshake_traffic_secret=options.client_handshake_traffic_secret;
has_changed=true;
}
}
if('server_handshake_traffic_secret' in options){
if(context.server_handshake_traffic_secret==null && options.server_handshake_traffic_secret!==null){
prev['server_handshake_traffic_secret']=context['server_handshake_traffic_secret'];
context.server_handshake_traffic_secret=options.server_handshake_traffic_secret;
has_changed=true;
}
}
if('client_app_traffic_secret' in options){
if(context.client_app_traffic_secret==null && options.client_app_traffic_secret!==null){
prev['client_app_traffic_secret']=context['client_app_traffic_secret'];
context.client_app_traffic_secret=options.client_app_traffic_secret;
has_changed=true;
}
}
if('server_app_traffic_secret' in options){
if(context.server_app_traffic_secret==null && options.server_app_traffic_secret!==null){
prev['server_app_traffic_secret']=context['server_app_traffic_secret'];
context.server_app_traffic_secret=options.server_app_traffic_secret;
has_changed=true;
}
}
if('local_cert_chain' in options){
if(context.local_cert_chain==null && options.local_cert_chain!==null){
prev['local_cert_chain']=context['local_cert_chain'];
context.local_cert_chain=options.local_cert_chain;
has_changed=true;
}
}
if('cert_private_key' in options){
if(context.cert_private_key==null && options.cert_private_key!==null){
prev['cert_private_key']=context['cert_private_key'];
context.cert_private_key=options.cert_private_key;
has_changed=true;
}
}
if('expected_remote_finished' in options){
if(context.expected_remote_finished==null && options.expected_remote_finished!==null){
prev['expected_remote_finished']=context['expected_remote_finished'];
context.expected_remote_finished=options.expected_remote_finished;
has_changed=true;
}
}
if('remote_finished' in options){
if(context.remote_finished==null && options.remote_finished!==null){
prev['remote_finished']=context['remote_finished'];
context.remote_finished=options.remote_finished;
has_changed=true;
}
}
if('remote_finished_ok' in options){
if(context.remote_finished_ok!==options.remote_finished_ok){
prev['remote_finished_ok']=context['remote_finished_ok'];
context.remote_finished_ok=options.remote_finished_ok;
has_changed=true;
}
}
}
if(has_changed==true){
var params_to_set = {};
if (
!arraysEqual(context.local_versions, prev.local_versions) ||
!arraysEqual(context.remote_versions, prev.remote_versions) ||
!arraysEqual(context.local_cipher_suites, prev.local_cipher_suites) ||
!arraysEqual(context.remote_cipher_suites, prev.remote_cipher_suites) ||
!arraysEqual(context.local_alpns, prev.local_alpns) ||
!arraysEqual(context.remote_alpns, prev.remote_alpns) ||
!arraysEqual(context.local_groups, prev.local_groups) ||
!arraysEqual(context.remote_groups, prev.remote_groups) ||
!arraysEqual(context.local_signature_algorithms, prev.local_signature_algorithms) ||
!arraysEqual(context.remote_signature_algorithms, prev.remote_signature_algorithms) ||
// הרחבות כלליות
!arraysEqual(context.local_extensions, prev.local_extensions) ||
//!arraysEqual(context.local_ee_extensions, prev.local_ee_extensions) || // TLS 1.3 EE
//!arraysEqual(context.remote_extensions_all, prev.remote_extensions_all) ||
//!arraysEqual(context.remote_extensions_unknown, prev.remote_extensions_unknown) ||
// key_shares הם מערך של אובייקטים {group, pubkey} → deep compare
!arraysEqual(context.remote_key_shares, prev.remote_key_shares) ||
// session_id הוא Uint8Array → בדיקה ייעודית
!arraybufferEqual(context.remote_session_id.buffer, prev.remote_session_id.buffer) ||
// סניפים/מחרוזות פשוט
context.remote_sni !== prev.remote_sni ||
1==1
) {
// === גרסה ===
if (context.selected_version == null && context.local_versions.length > 0 && context.remote_versions.length > 0) {
for (var i = 0; i < context.local_versions.length; i++) {
var v = context.local_versions[i] | 0;
for (var j = 0; j < context.remote_versions.length; j++) {
if ((context.remote_versions[j] | 0) == v) {
params_to_set['selected_version'] = v;
break;
}
}
if ('selected_version' in params_to_set==true && params_to_set.selected_version !== null) break;
}
if('selected_version' in params_to_set==false || params_to_set.selected_version==null){
//console.log('no match version...');
}
}
// === Cipher Suite ===
if (context.selected_cipher_suite == null && context.local_cipher_suites.length > 0 && context.remote_cipher_suites.length > 0) {
for (var i2 = 0; i2 < context.local_cipher_suites.length; i2++) {
var cs = context.local_cipher_suites[i2] | 0;
for (var j2 = 0; j2 < context.remote_cipher_suites.length; j2++) {
if ((context.remote_cipher_suites[j2] | 0) == cs) {
params_to_set['selected_cipher_suite'] = cs;
break;
}
}
if ('selected_cipher_suite' in params_to_set==true && params_to_set.selected_cipher_suite !== null) break;
}
if('selected_cipher_suite' in params_to_set==false || params_to_set.selected_cipher_suite==null){
//console.log('no match cipher_suites...');
}
}
// === ALPN ===
if (context.selected_alpn == null && context.local_alpns && context.remote_alpns) {
// עבור על הרשימה המקומית לפי סדר עדיפויות
for (var a = 0; a < context.local_alpns.length; a++) {
var cand = context.local_alpns[a];
for (var b = 0; b < context.remote_alpns.length; b++) {
if (context.remote_alpns[b] === cand) {
params_to_set['selected_alpn'] = cand;
break;
}
}
if ('selected_alpn' in params_to_set==true && params_to_set.selected_alpn !== null) break;
}
}
// === Group (ECDHE) ===
if (context.selected_group == null){
if (context.selected_version == wire.TLS_VERSION.TLS1_3) {
if(context.local_groups.length > 0 && context.remote_key_shares.length > 0) {
for (var g = 0; g < context.local_groups.length; g++) {
var grp = context.local_groups[g] | 0;
for (var k = 0; k < context.remote_key_shares.length; k++) {
var ent = context.remote_key_shares[k];
if ((ent.group | 0) === grp) {
params_to_set['selected_group'] = grp;
params_to_set['remote_key_share_selected_public'] = ent.pubkey || ent.key_exchange || null;
break;
}
}
if ('selected_group' in params_to_set==true && params_to_set.selected_group !== null) break;
}
if (!params_to_set.selected_group && context.selected_version === wire.TLS_VERSION.TLS1_3) {
params_to_set['need_hrr'] = true; // HelloRetryRequest אם לא נמצא group
}
if('selected_group' in params_to_set==false || params_to_set.selected_group==null){
//console.log('no match selected_group...');
}
}
}else if(context.selected_version == wire.TLS_VERSION.TLS1_2){
//console.log('...remote_groups...');
// 1.2 – לבחור עקום מתוך supported_groups של הלקוח (אין key_share)
if (context.local_groups.length > 0 && context.remote_groups.length > 0) {
for (let grp of context.local_groups) {
if (context.remote_groups.some(g => (g|0) === (grp|0))) {
params_to_set.selected_group = grp|0;
break;
}
}
if('selected_group' in params_to_set==false || params_to_set.selected_group==null){
//console.log('no match selected_group...');
}
}
}
}
// === Signature Algorithm ===
if (context.selected_signature_algorithm == null && context.local_signature_algorithms.length > 0 && context.remote_signature_algorithms.length > 0) {
for (var s = 0; s < context.local_signature_algorithms.length; s++) {
var sa = context.local_signature_algorithms[s] | 0;
for (var t = 0; t < context.remote_signature_algorithms.length; t++) {
if ((context.remote_signature_algorithms[t] | 0) === sa) {
params_to_set['selected_signature_algorithm'] = sa;
break;
}
}
if ('selected_signature_algorithm' in params_to_set==true && params_to_set.selected_signature_algorithm != null) break;
}
if('selected_signature_algorithm' in params_to_set==false || params_to_set.selected_signature_algorithm==null){
//console.log('no match selected_signature_algorithm...');
}
}
// === SNI ===
if (context.selected_sni == null) {
params_to_set['selected_sni'] = context.remote_sni || null;
}
// === Session ID (TLS 1.2 בלבד) ===
if (context.selected_session_id == null) {
params_to_set['selected_session_id'] = context.remote_session_id || new Uint8Array(0);
}
// === Extensions ===
if (context.selected_extensions == null && 1==2) {
var sel = [];
var allowed = {};
if (context.local_extensions) {
for (var lx = 0; lx < context.local_extensions.length; lx++) {
var lt = context.local_extensions[lx] && context.local_extensions[lx].type;
if (typeof lt === 'number') allowed[lt | 0] = true;
}
}
if (context.local_ee_extensions) {
for (var ex = 0; ex < context.local_ee_extensions.length; ex++) {
var et = context.local_ee_extensions[ex] && context.local_ee_extensions[ex].type;
if (typeof et === 'number') allowed[et | 0] = true;
}
}
if (context.remote_extensions_all) {
for (var rx = 0; rx < context.remote_extensions_all.length; rx++) {
var rt = context.remote_extensions_all[rx] && context.remote_extensions_all[rx].type;
if (typeof rt === 'number' && allowed[rt | 0]) {
sel.push(rt | 0);
}
}
}
if (params_to_set.selected_version === wire.TLS_VERSION.TLS1_3) {
if (sel.indexOf(0x002b) === -1) sel.push(0x002b); // supported_versions
if (sel.indexOf(0x0033) === -1) sel.push(0x0033); // key_share
}
params_to_set['selected_extensions'] = sel;
}
//console.log(params_to_set);
}
//יצירת מפתח תלוי ב GROUP
if(context.selected_group !== null && context.local_key_share_private === null && context.local_key_share_public === null) {
if (context.selected_version === wire.TLS_VERSION.TLS1_3) {
var client_public_key = null;
for (var i=0; i<context.remote_key_shares.length; i++){
if ((context.remote_key_shares[i].group|0) === (context.selected_group|0)){
client_public_key = context.remote_key_shares[i].pubkey || context.remote_key_shares[i].key_exchange || null;
break;
}
}
if(client_public_key!==null){
// כאן ליצור את זוג המפתחות (priv/pub) בהתאם לקבוצה
if (context.selected_group === 0x001d) { // X25519
// דרישות פורמט: client_public_key צריך להיות באורך 32 בייטים
// (נמנעים כאן מטיפול שגיאות כרגע)
var local_key_share_private = new Uint8Array(crypto.randomBytes(32));
var local_key_share_public = x25519.getPublicKey(local_key_share_private);
var ecdhe_shared_secret = x25519.getSharedSecret(local_key_share_private, client_public_key);
// אפשר לבצע strip של ה־first byte אם הספרייה מחזירה 32/33 — תלוי במימוש.
params_to_set['local_key_share_private']=local_key_share_private;
params_to_set['local_key_share_public']=local_key_share_public;
params_to_set['ecdhe_shared_secret']=ecdhe_shared_secret;
} else if (context.selected_group === 0x0017) { // secp256r1 (P-256)
//console.log('P-256');
var local_key_share_private = p256.utils.randomPrivateKey();
var local_key_share_public = p256.getPublicKey(priv, false); // uncompressed 65B
var clientPoint = p256.ProjectivePoint.fromHex(client_public_key);
// נקודת השיתוף = priv * clientPoint
var sharedPoint = clientPoint.multiply(BigInt('0x' + Buffer.from(priv).toString('hex')));
// שליפת הקואורדינטה X כ-32 בתים big-endian:
var affine = sharedPoint.toAffine(); // { x: bigint, y: bigint }
var xHex = affine.x.toString(16).padStart(64, '0'); // 32B hex
var ecdhe_shared_secret = Uint8Array.from(Buffer.from(xHex, 'hex')); // ← הסוד ל-TLS הוא X בלבד
params_to_set['local_key_share_private']=local_key_share_private;
params_to_set['local_key_share_public']=local_key_share_public;
params_to_set['ecdhe_shared_secret']=ecdhe_shared_secret;
}
}
}else if(context.selected_version === wire.TLS_VERSION.TLS1_2){
//console.log('@@@ 2');
// רק ליצור (priv/pub) עבור ServerKeyExchange
if (context.selected_group === 0x001d) { // X25519
const priv = new Uint8Array(crypto.randomBytes(32));
const pub = x25519.getPublicKey(priv);
params_to_set.local_key_share_private = priv;
params_to_set.local_key_share_public = pub;
// אל תחשב shared כאן; תחכה ל-ClientKeyExchange
} else if (context.selected_group === 0x0017) { // P-256
const priv = p256.utils.randomPrivateKey();
const pub = p256.getPublicKey(priv, false); // 65B uncompressed
params_to_set.local_key_share_private = priv;
params_to_set.local_key_share_public = pub;
}
}
}
if (context.selected_version !== prev.selected_version || context.selected_cipher_suite !== prev.selected_cipher_suite || context.selected_session_id !== prev.selected_session_id || context.selected_group !== prev.selected_group || context.local_key_share_public !== prev.local_key_share_public){
// build_server_hello... 1.3...
var can_send_hello=false;
if(context.hello_sent==false){
if(context.selected_version!==null && context.selected_cipher_suite!==null && context.selected_session_id!==null){
if(context.selected_version === wire.TLS_VERSION.TLS1_3){
if(context.local_key_share_public!==null && context.selected_group !== null){
can_send_hello=true;
}
}else if(context.selected_version === wire.TLS_VERSION.TLS1_2){
can_send_hello=true;
}
}
}
if(can_send_hello==true){
if(context.local_random==null){
context.local_random=new Uint8Array(crypto.randomBytes(32));
}
var build_message_params=null;
if(context.selected_version==wire.TLS_VERSION.TLS1_3){
build_message_params={
type: 'server_hello',
version: context.selected_version,
random: context.local_random,
session_id: context.remote_session_id,
cipher_suite: context.selected_cipher_suite, // TLS_AES_128_GCM_SHA256
extensions: [
{
type: 'SUPPORTED_VERSIONS',
value: wire.TLS_VERSION.TLS1_3
},
{
type: 'KEY_SHARE',
value: {
group: context.selected_group,
key_exchange: context.local_key_share_public
}
}
]
};
}else if(context.selected_version==wire.TLS_VERSION.TLS1_2){
// ⚠️ אין SUPPORTED_VERSIONS/KEY_SHARE בתוך ServerHello של TLS 1.2.
// מומלץ לכלול renegotiation_info (ריק בהנדשייק ראשון) ו-extended_master_secret (type=23).
// ALPN (type=16) – אופציונלי: אם נבחר למשל 'http/1.1' או 'h2'.
var ext_list = [
// RFC 5746 – initial handshake: value ריק (vec<1> באורך 0)
{ type: 'RENEGOTIATION_INFO', value: new Uint8Array(0) },
// RFC 7627 – extended_master_secret (type 23) – ערך ריק.
// מאחר ואין encoder רשום אצלנו ל-23, נשתמש ישירות ב-data ריק:
{ type: 23, data: new Uint8Array(0) }
];
if (context.alpn_selected) {
// RFC 7301 – ב-ServerHello מוחזר פרוטוקול אחד
ext_list.push({ type: 'ALPN', value: [ String(context.alpn_selected) ] });
}
build_message_params = {
type: 'server_hello',
version: context.selected_version,
random: context.local_random,
session_id: context.remote_session_id || new Uint8Array(0), // מקובל להדהד את ה-session_id של הלקוח
cipher_suite: context.selected_cipher_suite, // למשל 0xC02F (ECDHE_RSA_WITH_AES_128_GCM_SHA256)
// compression_method תמיד 0 ב-1.2 אצלנו; ה-builder כבר כותב 0.
extensions: ext_list
};
}
if(build_message_params!==null){
//console.log('sent server hello...')
var message_data = wire.build_message(build_message_params);
context.transcript.push(message_data);
context.hello_sent=true;
ev.emit('message',0,context.message_sent_seq,'hello',message_data);
context.message_sent_seq++;
}
}
}
if (context.selected_version === wire.TLS_VERSION.TLS1_3){
if(context.selected_cipher_suite !== null && (context.ecdhe_shared_secret !== null)){// || context.selected_psk !== null
var d = derive_handshake_traffic_secrets(TLS_CIPHER_SUITES[context.selected_cipher_suite].hash, context.ecdhe_shared_secret, concatUint8Arrays(context.transcript));
params_to_set['handshake_secret']=d.handshake_secret;
params_to_set['client_handshake_traffic_secret']=d.client_handshake_traffic_secret;
params_to_set['server_handshake_traffic_secret']=d.server_handshake_traffic_secret;
}
}
if (context.selected_version === wire.TLS_VERSION.TLS1_3){
if(context.encrypted_exts_sent==false && context.hello_sent==true && context.server_handshake_traffic_secret!==null){
var extensions=[];
if(context.selected_alpn!==null){
extensions.push({ type: 'ALPN', value: [context.selected_alpn] });
}
//console.log('extensions:');
//console.log(extensions);
for(var i in context.local_extensions){
extensions.push(context.local_extensions[i]);
}
var message_data = wire.build_message({
type: 'encrypted_extensions',
extensions: extensions
});
//console.log('message_data:');
//console.log(message_data);
context.transcript.push(message_data);
context.encrypted_exts_sent=true;
ev.emit('message',1,context.message_sent_seq,'encrypted_extensions',message_data);
context.message_sent_seq++;
}
}
if(context.cert_sent==false && context.local_cert_chain!==null){
if((context.selected_version === wire.TLS_VERSION.TLS1_3 && context.encrypted_exts_sent==true && context.server_handshake_traffic_secret!==null) || (context.selected_version === wire.TLS_VERSION.TLS1_2 && context.hello_sent==true)){
var message_data = wire.build_message({
type: 'certificate',
version: context.selected_version,
entries: context.local_cert_chain
});
context.transcript.push(message_data);
context.cert_sent=true;
if (context.selected_version === wire.TLS_VERSION.TLS1_3){
ev.emit('message',1,context.message_sent_seq,'certificate',message_data);
}else{
ev.emit('message',0,context.message_sent_seq,'certificate',message_data);
}
context.message_sent_seq++;
}
}
if (context.selected_version === wire.TLS_VERSION.TLS1_3){
if(context.cert_sent==true && context.cert_verify_sent==false && context.local_cert_chain!==null && context.server_handshake_traffic_secret!==null){
var tbs_data = build_cert_verify_tbs(TLS_CIPHER_SUITES[context.selected_cipher_suite].hash,true,concatUint8Arrays(context.transcript));
var cert_private_key_obj = crypto.createPrivateKey({
key: Buffer.from(context.cert_private_key),
format: 'der',
type: 'pkcs8',
});
var SIG = {
ECDSA_P256_SHA256: 0x0403,
ECDSA_P384_SHA384: 0x0503,
ECDSA_P521_SHA512: 0x0603,
RSA_PSS_SHA256: 0x0804,
RSA_PSS_SHA384: 0x0805,
RSA_PSS_SHA512: 0x0806,
ED25519: 0x0807,
ED448: 0x0808
};
var candidates=[];
if (cert_private_key_obj.asymmetricKeyType === 'ed25519') candidates.push(SIG.ED25519);
if (cert_private_key_obj.asymmetricKeyType === 'ed448') candidates.push(SIG.ED448);
if (cert_private_key_obj.asymmetricKeyType === 'rsa') candidates.push(SIG.RSA_PSS_SHA256, SIG.RSA_PSS_SHA384, SIG.RSA_PSS_SHA512); // TLS 1.3 → רק PSS
if (cert_private_key_obj.asymmetricKeyType === 'ec') {
var c = (cert_private_key_obj.asymmetricKeyDetails && cert_private_key_obj.asymmetricKeyDetails && cert_private_key_obj.asymmetricKeyDetails.namedCurve) || '';
if (c === 'prime256v1') candidates.push(SIG.ECDSA_P256_SHA256);
if (c === 'secp384r1') candidates.push(SIG.ECDSA_P384_SHA384);
if (c === 'secp521r1') candidates.push(SIG.ECDSA_P521_SHA512);
}
//console.log(candidates);
var preference_order = [
SIG.ED25519,
SIG.ED448,
SIG.ECDSA_P256_SHA256,
SIG.ECDSA_P384_SHA384,
SIG.ECDSA_P521_SHA512,
SIG.RSA_PSS_SHA256,
SIG.RSA_PSS_SHA384,
SIG.RSA_PSS_SHA512
];
var selected_scheme = null;
for (var s of preference_order) {
if (context.remote_signature_algorithms.includes(s)==true && candidates.includes(s)==true) {
selected_scheme = s;
break;
}
}
var sig_data=null;
switch (selected_scheme) {
case SIG.ED25519:
sig_data = new Uint8Array(crypto.sign(null, tbs_data, cert_private_key_obj));
break;
case SIG.ECDSA_P256_SHA256:
sig_data = new Uint8Array(crypto.sign('sha256', tbs_data, cert_private_key_obj));
break;
case SIG.ECDSA_P384_SHA384:
sig_data = new Uint8Array(crypto.sign('sha384', tbs_data, cert_private_key_obj));
break;
case SIG.ECDSA_P521_SHA512:
sig_data = new Uint8Array(crypto.sign('sha512', tbs_data, cert_private_key_obj));
break;
case SIG.RSA_PSS_SHA256:
sig_data = new Uint8Array(crypto.sign('sha256', tbs_data, {
key: cert_private_key_obj,
padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
saltLength: 32
}));
break;
case SIG.RSA_PSS_SHA384:
sig_data = new Uint8Array(crypto.sign('sha384', tbs_data, {
key: cert_private_key_obj,
padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
saltLength: 48
}));
break;
case SIG.RSA_PSS_SHA512:
sig_data = new Uint8Array(crypto.sign('sha512', tbs_data, {
key: cert_private_key_obj,
padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
saltLength: 64
}));
break;
}
//console.log('sig_data:');
//console.log(sig_data);
if(sig_data){
var message_data = wire.build_message({
type: 'certificate_verify',
scheme: selected_scheme,
signature: sig_data
});
//console.log(message_data);
//console.log('certificate_verify sent!');
context.transcript.push(message_data);
context.cert_verify_sent=true;
ev.emit('message',1,context.message_sent_seq,'certificate_verify',message_data);
context.message_sent_seq++;
}else{
//..
}
}
}
if (context.selected_version === wire.TLS_VERSION.TLS1_3){
if(context.cert_verify_sent==true && context.finished_sent==false && context.local_cert_chain!==null && context.server_handshake_traffic_secret!==null){
var finished_data=get_handshake_finished(TLS_CIPHER_SUITES[context.selected_cipher_suite].hash,context.server_handshake_traffic_secret,concatUint8Arrays(context.transcript));
var message_data = wire.build_message({
type: 'finished',
data: finished_data
});
context.transcript.push(message_data);
context.finished_sent=true;
ev.emit('message',1,context.message_sent_seq,'finished',message_data);