lemon-tls 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/record.js ADDED
@@ -0,0 +1,232 @@
1
+ /**
2
+ * record.js — Shared record-layer primitives for TLS 1.2 and TLS 1.3.
3
+ *
4
+ * Used by TLSSocket, DTLSSocket, and test harnesses.
5
+ * Handles AEAD encryption/decryption, nonce construction, key derivation,
6
+ * and raw record framing.
7
+ */
8
+
9
+ import crypto from 'node:crypto';
10
+ import {
11
+ TLS_CIPHER_SUITES,
12
+ hkdf_expand_label,
13
+ tls_derive_from_master_secret_tls12
14
+ } from './crypto.js';
15
+
16
+ // ===================== AEAD algorithm resolution =====================
17
+
18
+ /** Resolve node:crypto cipher name from a TLS cipher suite code. */
19
+ function getAeadAlgo(cipherSuite) {
20
+ if (cipherSuite != null) {
21
+ let info = TLS_CIPHER_SUITES[cipherSuite];
22
+ if (info && info.cipher === 'CHACHA20_POLY1305') return 'chacha20-poly1305';
23
+ if (info && info.keylen === 32) return 'aes-256-gcm';
24
+ }
25
+ return 'aes-128-gcm';
26
+ }
27
+
28
+ // ===================== TLS 1.3 primitives =====================
29
+
30
+ /** Derive write key + IV from a TLS 1.3 traffic secret. */
31
+ function deriveKeys(trafficSecret, cipherSuite) {
32
+ const empty = new Uint8Array(0);
33
+ let cs = TLS_CIPHER_SUITES[cipherSuite];
34
+ return {
35
+ key: hkdf_expand_label(cs.hash, trafficSecret, 'key', empty, cs.keylen),
36
+ iv: hkdf_expand_label(cs.hash, trafficSecret, 'iv', empty, 12)
37
+ };
38
+ }
39
+
40
+ /** TLS 1.3 nonce: IV XOR zero-padded 64-bit sequence number. */
41
+ function getNonce(iv, seq) {
42
+ const seqBuf = new Uint8Array(12);
43
+ const view = new DataView(seqBuf.buffer);
44
+ view.setBigUint64(4, BigInt(seq));
45
+ const nonce = new Uint8Array(12);
46
+ for (let i = 0; i < 12; i++) nonce[i] = iv[i] ^ seqBuf[i];
47
+ return nonce;
48
+ }
49
+
50
+ /**
51
+ * Encrypt a TLS 1.3 record (TLSInnerPlaintext).
52
+ * Returns ciphertext || tag (without record header).
53
+ */
54
+ function encryptRecord(innerType, plaintext, key, nonce, algo) {
55
+ const full = new Uint8Array(plaintext.length + 1);
56
+ full.set(plaintext);
57
+ full[plaintext.length] = innerType;
58
+
59
+ const recLen = full.length + 16;
60
+ const aad = new Uint8Array([0x17, 0x03, 0x03, (recLen >>> 8) & 0xff, recLen & 0xff]);
61
+
62
+ if (!algo) algo = key.length === 16 ? 'aes-128-gcm' : 'aes-256-gcm';
63
+ let isChaCha = algo === 'chacha20-poly1305';
64
+ let cipher = crypto.createCipheriv(algo, key, nonce, isChaCha ? { authTagLength: 16 } : undefined);
65
+ cipher.setAAD(aad, isChaCha ? { plaintextLength: full.length } : undefined);
66
+ let ct = cipher.update(full);
67
+ cipher.final();
68
+ let tag = cipher.getAuthTag();
69
+
70
+ let out = new Uint8Array(ct.length + tag.length);
71
+ out.set(ct, 0);
72
+ out.set(tag, ct.length);
73
+ return out;
74
+ }
75
+
76
+ /**
77
+ * Decrypt a TLS 1.3 record.
78
+ * Input: raw ciphertext || tag (without record header).
79
+ * Returns full TLSInnerPlaintext (content || content_type || padding).
80
+ */
81
+ function decryptRecord(ciphertext, key, nonce, algo) {
82
+ const aad = new Uint8Array(5);
83
+ aad[0] = 0x17; aad[1] = 0x03; aad[2] = 0x03;
84
+ aad[3] = (ciphertext.length >> 8) & 0xff;
85
+ aad[4] = ciphertext.length & 0xff;
86
+
87
+ let ct = ciphertext.subarray(0, ciphertext.length - 16);
88
+ let tag = ciphertext.subarray(ciphertext.length - 16);
89
+
90
+ if (!algo) algo = key.length === 16 ? 'aes-128-gcm' : 'aes-256-gcm';
91
+ let isChaCha = algo === 'chacha20-poly1305';
92
+ let decipher = crypto.createDecipheriv(algo, key, nonce, isChaCha ? { authTagLength: 16 } : undefined);
93
+ decipher.setAAD(aad, isChaCha ? { plaintextLength: ct.length } : undefined);
94
+ decipher.setAuthTag(tag);
95
+ let pt = decipher.update(ct);
96
+ decipher.final();
97
+ return new Uint8Array(pt);
98
+ }
99
+
100
+ /** Strip trailing zeros and extract content_type from TLSInnerPlaintext. */
101
+ function parseInnerPlaintext(data) {
102
+ let j = data.length - 1;
103
+ while (j >= 0 && data[j] === 0) j--;
104
+ if (j < 0) throw new Error('Malformed TLSInnerPlaintext');
105
+ return { type: data[j], content: data.slice(0, j) };
106
+ }
107
+
108
+ // ===================== TLS 1.2 primitives =====================
109
+
110
+ /** TLS 1.2 GCM nonce: fixed_salt(4) || explicit_nonce(8). */
111
+ function getNonce12(salt4, explicit8) {
112
+ const out = new Uint8Array(12);
113
+ out.set(salt4, 0);
114
+ out.set(explicit8, 4);
115
+ return out;
116
+ }
117
+
118
+ /** Encode sequence number as 8-byte big-endian. */
119
+ function seqToBytes(seq) {
120
+ const buf = new Uint8Array(8);
121
+ let bn = BigInt(seq);
122
+ for (let i = 0; i < 8; i++) buf[7 - i] = Number((bn >> BigInt(8 * i)) & 0xffn);
123
+ return buf;
124
+ }
125
+
126
+ /** TLS 1.2 AAD: seq(8) || type(1) || version(2) || length(2). */
127
+ function buildAad12(seqNum, recordType, plaintextLen) {
128
+ const aad = new Uint8Array(13);
129
+ aad.set(seqToBytes(seqNum), 0);
130
+ aad[8] = recordType & 0xff;
131
+ aad[9] = 0x03;
132
+ aad[10] = 0x03;
133
+ aad[11] = (plaintextLen >>> 8) & 0xff;
134
+ aad[12] = plaintextLen & 0xff;
135
+ return aad;
136
+ }
137
+
138
+ /** Encrypt a TLS 1.2 GCM record fragment. Returns explicit_nonce(8) || ciphertext || tag(16). */
139
+ function encrypt12(pt, key, salt4, seqNum, recordType) {
140
+ let explicit = seqToBytes(seqNum);
141
+ let nonce = getNonce12(salt4, explicit);
142
+ let aad = buildAad12(seqNum, recordType, pt.length);
143
+
144
+ let algo = key.length === 16 ? 'aes-128-gcm' : 'aes-256-gcm';
145
+ let cipher = crypto.createCipheriv(algo, key, nonce);
146
+ cipher.setAAD(aad);
147
+ let ct = cipher.update(pt);
148
+ cipher.final();
149
+ let tag = cipher.getAuthTag();
150
+
151
+ let out = new Uint8Array(8 + ct.length + tag.length);
152
+ out.set(explicit, 0);
153
+ out.set(ct, 8);
154
+ out.set(tag, 8 + ct.length);
155
+ return out;
156
+ }
157
+
158
+ /** Decrypt a TLS 1.2 GCM record fragment. Input: explicit_nonce(8) || ciphertext || tag(16). */
159
+ function decrypt12(fragment, key, salt4, seqNum, recordType) {
160
+ if (fragment.length < 24) throw new Error('TLS 1.2 fragment too short');
161
+
162
+ let explicit = fragment.slice(0, 8);
163
+ let tag = fragment.slice(fragment.length - 16);
164
+ let ct = fragment.slice(8, fragment.length - 16);
165
+
166
+ let nonce = getNonce12(salt4, explicit);
167
+ let aad = buildAad12(seqNum, recordType, ct.length);
168
+
169
+ let algo = key.length === 16 ? 'aes-128-gcm' : 'aes-256-gcm';
170
+ let decipher = crypto.createDecipheriv(algo, key, nonce);
171
+ decipher.setAAD(aad);
172
+ decipher.setAuthTag(tag);
173
+ let pt = decipher.update(ct);
174
+ decipher.final();
175
+ return new Uint8Array(pt);
176
+ }
177
+
178
+ // ===================== Shared: TLS 1.2 key_block =====================
179
+
180
+ /** Derive TLS 1.2 read/write keys from master_secret. Wraps crypto.js function. */
181
+ function deriveKeys12(masterSecret, localRandom, remoteRandom, cipherSuite, isServer) {
182
+ if (isServer) {
183
+ let d = tls_derive_from_master_secret_tls12(masterSecret, localRandom, remoteRandom, cipherSuite);
184
+ return { readKey: d.client_key, readIv: d.client_iv, writeKey: d.server_key, writeIv: d.server_iv };
185
+ } else {
186
+ let d = tls_derive_from_master_secret_tls12(masterSecret, remoteRandom, localRandom, cipherSuite);
187
+ return { readKey: d.server_key, readIv: d.server_iv, writeKey: d.client_key, writeIv: d.client_iv };
188
+ }
189
+ }
190
+
191
+ // ===================== Record framing =====================
192
+
193
+ /** Content type constants. */
194
+ const CT = { CHANGE_CIPHER_SPEC: 20, ALERT: 21, HANDSHAKE: 22, APPLICATION_DATA: 23 };
195
+
196
+ /** Write a raw TLS record to a writable stream. */
197
+ function writeRecord(transport, type, payload, version) {
198
+ if (!transport || typeof transport.write !== 'function') return;
199
+ let ver = version || 0x0303;
200
+ let rec = Buffer.allocUnsafe(5 + payload.length);
201
+ rec.writeUInt8(type, 0);
202
+ rec.writeUInt16BE(ver, 1);
203
+ rec.writeUInt16BE(payload.length, 3);
204
+ Buffer.from(payload).copy(rec, 5);
205
+ transport.write(rec);
206
+ }
207
+
208
+ // ===================== Exports =====================
209
+
210
+ export {
211
+ // AEAD
212
+ getAeadAlgo,
213
+
214
+ // TLS 1.3
215
+ deriveKeys,
216
+ getNonce,
217
+ encryptRecord,
218
+ decryptRecord,
219
+ parseInnerPlaintext,
220
+
221
+ // TLS 1.2
222
+ getNonce12,
223
+ seqToBytes,
224
+ buildAad12,
225
+ encrypt12,
226
+ decrypt12,
227
+ deriveKeys12,
228
+
229
+ // Record framing
230
+ CT,
231
+ writeRecord,
232
+ };
@@ -1,196 +1,196 @@
1
- var fs = require('fs');
2
- var path = require('path');
3
- var crypto = require('crypto');
4
-
5
- function looksLikePath(x) {
6
- return typeof x === 'string' && (x.indexOf('\n') === -1) && (x.length < 4096) &&
7
- (x.indexOf('-----BEGIN') === -1);
8
- }
9
-
10
- function readMaybeFile(x) {
11
- if (x == null) return null;
12
- if (looksLikePath(x)) return fs.readFileSync(path.resolve(String(x)));
13
- if (Buffer.isBuffer(x)) return x;
14
- if (x instanceof Uint8Array) return Buffer.from(x);
15
- if (typeof x === 'string') return Buffer.from(x, 'utf8');
16
- throw new Error('Unsupported input type (expected path/string/Buffer/Uint8Array).');
17
- }
18
-
19
- function isPEM(buf) {
20
- if (!buf) return false;
21
- var s = buf.toString('utf8');
22
- return s.indexOf('-----BEGIN ') !== -1 && s.indexOf('-----END ') !== -1;
23
- }
24
-
25
- function splitPEMBlocks(pemText) {
26
- var out = [];
27
- var re = /-----BEGIN ([A-Z0-9 \-]+)-----([\s\S]*?)-----END \1-----/g;
28
- var m;
29
- while ((m = re.exec(pemText)) !== null) {
30
- var typ = m[1].trim();
31
- var b64 = m[2].replace(/[\r\n\s]/g, '');
32
- var derBuf = Buffer.from(b64, 'base64');
33
- out.push({ type: typ, der: new Uint8Array(derBuf) });
34
- }
35
- return out;
36
- }
37
-
38
- function ensureArray(x) { return x == null ? [] : (Array.isArray(x) ? x : [x]); }
39
-
40
- function normalizeCA(caOption) {
41
- var arr = ensureArray(caOption);
42
- var ders = [];
43
- for (var i = 0; i < arr.length; i++) {
44
- var raw = readMaybeFile(arr[i]);
45
- if (!raw) continue;
46
- if (isPEM(raw)) {
47
- var blocks = splitPEMBlocks(raw.toString('utf8'));
48
- for (var j = 0; j < blocks.length; j++) {
49
- if (blocks[j].type.indexOf('CERTIFICATE') !== -1) ders.push(blocks[j].der);
50
- }
51
- } else {
52
- ders.push(new Uint8Array(raw));
53
- }
54
- }
55
- return ders;
56
- }
57
-
58
- function makeX509FromDerOrPem(buf) {
59
- return new crypto.X509Certificate(Buffer.from(buf));
60
- }
61
-
62
- function makePrivateKeyFromDerOrPem(buf, passphrase) {
63
- if (isPEM(buf)) {
64
- return crypto.createPrivateKey({ key: buf, format: 'pem', passphrase: passphrase });
65
- } else {
66
- var der = Buffer.from(buf), keyObj = null;
67
- try {
68
- keyObj = crypto.createPrivateKey({ key: der, format: 'der', type: 'pkcs8', passphrase: passphrase });
69
- } catch (e1) {
70
- try {
71
- keyObj = crypto.createPrivateKey({ key: der, format: 'der', type: 'pkcs1', passphrase: passphrase });
72
- } catch (e2) {
73
- keyObj = crypto.createPrivateKey({ key: der, format: 'der', type: 'sec1', passphrase: passphrase });
74
- }
75
- }
76
- return keyObj;
77
- }
78
- }
79
-
80
- function exportKeyPkcs8Der(keyObj) {
81
- return new Uint8Array(keyObj.export({ format: 'der', type: 'pkcs8' }));
82
- }
83
-
84
- function u8eq(a,b){ if (a.length!==b.length) return false; for (var i=0;i<a.length;i++) if (a[i]!==b[i]) return false; return true; }
85
- function dedupeDerArray(arr) {
86
- var out = [];
87
- for (var i=0;i<arr.length;i++) {
88
- var keep = true;
89
- for (var j=0;j<out.length;j++) { if (u8eq(arr[i], out[j])) { keep = false; break; } }
90
- if (keep) out.push(arr[i]);
91
- }
92
- return out;
93
- }
94
-
95
- /**
96
- * createSecureContext(options)
97
- * key/cert/ca יכולים להיות: path | Buffer | Uint8Array | string(PEM)
98
- * מחזיר:
99
- * - certificateChain: [{ cert: Uint8Array }, ...] // leaf תחילה, אח"כ intermediates
100
- * - privateKey: Uint8Array // PKCS#8 DER
101
- * - ca: Uint8Array[] // Trust store (לא משולח ללקוח)
102
- * - ocsp: Uint8Array|null
103
- * - ticketKeys: Uint8Array|null
104
- * - certObjs, keyObj (עזרי debug/לוגיקה)
105
- */
106
- function createSecureContext(options) {
107
- if (!options) options = {};
108
-
109
- // --- תעודות (כולל שרשרת בתוך cert אם קיימת) ---
110
- var certBlocksDer = [];
111
- var certObjs = [];
112
- if (options.cert != null) {
113
- var cRaw = readMaybeFile(options.cert);
114
- if (isPEM(cRaw)) {
115
- var blocks = splitPEMBlocks(cRaw.toString('utf8'));
116
- for (var i=0;i<blocks.length;i++) {
117
- if (blocks[i].type.indexOf('CERTIFICATE') !== -1) {
118
- certBlocksDer.push(blocks[i].der);
119
- certObjs.push(makeX509FromDerOrPem(blocks[i].der));
120
- }
121
- }
122
- } else {
123
- var der = new Uint8Array(cRaw);
124
- certBlocksDer.push(der);
125
- certObjs.push(makeX509FromDerOrPem(der));
126
- }
127
- }
128
-
129
- // --- מפתח פרטי ---
130
- var keyObj = null;
131
- var privateKey = null;
132
- if (options.key != null) {
133
- var kRaw = readMaybeFile(options.key);
134
- keyObj = makePrivateKeyFromDerOrPem(kRaw, options.passphrase);
135
- privateKey = exportKeyPkcs8Der(keyObj);
136
- }
137
-
138
- // אימות בסיסי (כאשר לא משתמשים ב־PFX)
139
- if (!options.pfx) {
140
- if (certBlocksDer.length === 0) throw new Error('createSecureContext: missing cert.');
141
- if (!privateKey) throw new Error('createSecureContext: missing private key.');
142
- }
143
-
144
- // --- CA (Trust store) ---
145
- var ca = normalizeCA(options.ca);
146
-
147
- // --- OCSP stapling (אופציונלי) ---
148
- var ocsp = null;
149
- if (options.ocsp != null) ocsp = new Uint8Array(readMaybeFile(options.ocsp));
150
-
151
- // --- Ticket keys (אופציונלי) ---
152
- var ticketKeys = null;
153
- if (options.ticketKeys != null) ticketKeys = new Uint8Array(readMaybeFile(options.ticketKeys));
154
- // אפשרות: ליצור מפתחי ברירת מחדל כאן אם תרצה
155
-
156
- // --- בניית שרשרת לשיגור ללקוח (leaf → intermediates) ---
157
- var chainDer = dedupeDerArray(certBlocksDer);
158
- var certificateChain = [];
159
- for (var c=0;c<chainDer.length;c++) {
160
- certificateChain.push({ cert: chainDer[c] });
161
- }
162
-
163
- // מידע עזר לזיהוי סוג המפתח הציבורי של ה-leaf
164
- var leafPublicKeyType = null;
165
- if (certObjs.length > 0 && certObjs[0].publicKey) {
166
- try { leafPublicKeyType = certObjs[0].publicKey.asymmetricKeyType || null; } catch (e) { leafPublicKeyType = null; }
167
- }
168
-
169
- return {
170
- // חומר לשכבת ההנדשייק/רקורד:
171
- certificateChain: certificateChain, // [{ cert: DER(Uint8Array) }, ...]
172
- privateKey: privateKey, // PKCS#8 DER (Uint8Array)
173
- ca: ca, // Trust store (DER)
174
- ocsp: ocsp, // DER (אם הוגדר)
175
- ticketKeys: ticketKeys, // Uint8Array (אם הוגדר)
176
-
177
- // עזרי debug/לוגיקה:
178
- certObjs: certObjs, // [X509Certificate...]
179
- keyObj: keyObj, // KeyObject
180
- leafPublicKeyType: leafPublicKeyType,
181
-
182
- // פרמטרים פרוטוקוליים (אחסון; אתה מפרש בזמן ה-handshake):
183
- minVersion: String(options.minVersion || 'TLSv1.2'),
184
- maxVersion: String(options.maxVersion || 'TLSv1.3'),
185
- ciphers: options.ciphers || null,
186
- sigalgs: options.sigalgs || null,
187
- ecdhCurve: options.ecdhCurve || null,
188
- honorCipherOrder: !!options.honorCipherOrder,
189
-
190
- // תמיכה ב־PFX אם תרצה לטפל בזה בשכבה אחרת:
191
- pfx: options.pfx ? new Uint8Array(readMaybeFile(options.pfx)) : null,
192
- passphrase: options.passphrase ? String(options.passphrase) : null
193
- };
194
- }
195
-
196
- module.exports = createSecureContext;
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import crypto from 'node:crypto';
4
+
5
+ function looksLikePath(x) {
6
+ return typeof x === 'string' && (x.indexOf('\n') === -1) && (x.length < 4096) &&
7
+ (x.indexOf('-----BEGIN') === -1);
8
+ }
9
+
10
+ function readMaybeFile(x) {
11
+ if (x == null) return null;
12
+ if (looksLikePath(x)) return fs.readFileSync(path.resolve(String(x)));
13
+ if (Buffer.isBuffer(x)) return x;
14
+ if (x instanceof Uint8Array) return Buffer.from(x);
15
+ if (typeof x === 'string') return Buffer.from(x, 'utf8');
16
+ throw new Error('Unsupported input type (expected path/string/Buffer/Uint8Array).');
17
+ }
18
+
19
+ function isPEM(buf) {
20
+ if (!buf) return false;
21
+ let s = buf.toString('utf8');
22
+ return s.indexOf('-----BEGIN ') !== -1 && s.indexOf('-----END ') !== -1;
23
+ }
24
+
25
+ function splitPEMBlocks(pemText) {
26
+ let out = [];
27
+ let re = /-----BEGIN ([A-Z0-9 \-]+)-----([\s\S]*?)-----END \1-----/g;
28
+ let m;
29
+ while ((m = re.exec(pemText)) !== null) {
30
+ let typ = m[1].trim();
31
+ let b64 = m[2].replace(/[\r\n\s]/g, '');
32
+ let derBuf = Buffer.from(b64, 'base64');
33
+ out.push({ type: typ, der: new Uint8Array(derBuf) });
34
+ }
35
+ return out;
36
+ }
37
+
38
+ function ensureArray(x) { return x == null ? [] : (Array.isArray(x) ? x : [x]); }
39
+
40
+ function normalizeCA(caOption) {
41
+ let arr = ensureArray(caOption);
42
+ let ders = [];
43
+ for (let i = 0; i < arr.length; i++) {
44
+ let raw = readMaybeFile(arr[i]);
45
+ if (!raw) continue;
46
+ if (isPEM(raw)) {
47
+ let blocks = splitPEMBlocks(raw.toString('utf8'));
48
+ for (let j = 0; j < blocks.length; j++) {
49
+ if (blocks[j].type.indexOf('CERTIFICATE') !== -1) ders.push(blocks[j].der);
50
+ }
51
+ } else {
52
+ ders.push(new Uint8Array(raw));
53
+ }
54
+ }
55
+ return ders;
56
+ }
57
+
58
+ function makeX509FromDerOrPem(buf) {
59
+ return new crypto.X509Certificate(Buffer.from(buf));
60
+ }
61
+
62
+ function makePrivateKeyFromDerOrPem(buf, passphrase) {
63
+ if (isPEM(buf)) {
64
+ return crypto.createPrivateKey({ key: buf, format: 'pem', passphrase: passphrase });
65
+ } else {
66
+ let der = Buffer.from(buf), keyObj = null;
67
+ try {
68
+ keyObj = crypto.createPrivateKey({ key: der, format: 'der', type: 'pkcs8', passphrase: passphrase });
69
+ } catch (e1) {
70
+ try {
71
+ keyObj = crypto.createPrivateKey({ key: der, format: 'der', type: 'pkcs1', passphrase: passphrase });
72
+ } catch (e2) {
73
+ keyObj = crypto.createPrivateKey({ key: der, format: 'der', type: 'sec1', passphrase: passphrase });
74
+ }
75
+ }
76
+ return keyObj;
77
+ }
78
+ }
79
+
80
+ function exportKeyPkcs8Der(keyObj) {
81
+ return new Uint8Array(keyObj.export({ format: 'der', type: 'pkcs8' }));
82
+ }
83
+
84
+ function u8eq(a,b){ if (a.length!==b.length) return false; for (let i=0;i<a.length;i++) if (a[i]!==b[i]) return false; return true; }
85
+ function dedupeDerArray(arr) {
86
+ let out = [];
87
+ for (let i=0;i<arr.length;i++) {
88
+ let keep = true;
89
+ for (let j=0;j<out.length;j++) { if (u8eq(arr[i], out[j])) { keep = false; break; } }
90
+ if (keep) out.push(arr[i]);
91
+ }
92
+ return out;
93
+ }
94
+
95
+ /**
96
+ * createSecureContext(options)
97
+ * key/cert/ca יכולים להיות: path | Buffer | Uint8Array | string(PEM)
98
+ * מחזיר:
99
+ * - certificateChain: [{ cert: Uint8Array }, ...] // leaf תחילה, אח"כ intermediates
100
+ * - privateKey: Uint8Array // PKCS#8 DER
101
+ * - ca: Uint8Array[] // Trust store (לא משולח ללקוח)
102
+ * - ocsp: Uint8Array|null
103
+ * - ticketKeys: Uint8Array|null
104
+ * - certObjs, keyObj (עזרי debug/לוגיקה)
105
+ */
106
+ function createSecureContext(options) {
107
+ if (!options) options = {};
108
+
109
+ // --- תעודות (כולל שרשרת בתוך cert אם קיימת) ---
110
+ let certBlocksDer = [];
111
+ let certObjs = [];
112
+ if (options.cert != null) {
113
+ let cRaw = readMaybeFile(options.cert);
114
+ if (isPEM(cRaw)) {
115
+ let blocks = splitPEMBlocks(cRaw.toString('utf8'));
116
+ for (let i=0;i<blocks.length;i++) {
117
+ if (blocks[i].type.indexOf('CERTIFICATE') !== -1) {
118
+ certBlocksDer.push(blocks[i].der);
119
+ certObjs.push(makeX509FromDerOrPem(blocks[i].der));
120
+ }
121
+ }
122
+ } else {
123
+ const der = new Uint8Array(cRaw);
124
+ certBlocksDer.push(der);
125
+ certObjs.push(makeX509FromDerOrPem(der));
126
+ }
127
+ }
128
+
129
+ // --- מפתח פרטי ---
130
+ let keyObj = null;
131
+ let privateKey = null;
132
+ if (options.key != null) {
133
+ let kRaw = readMaybeFile(options.key);
134
+ keyObj = makePrivateKeyFromDerOrPem(kRaw, options.passphrase);
135
+ privateKey = exportKeyPkcs8Der(keyObj);
136
+ }
137
+
138
+ // אימות בסיסי (כאשר לא משתמשים ב־PFX)
139
+ if (!options.pfx) {
140
+ if (certBlocksDer.length === 0) throw new Error('createSecureContext: missing cert.');
141
+ if (!privateKey) throw new Error('createSecureContext: missing private key.');
142
+ }
143
+
144
+ // --- CA (Trust store) ---
145
+ let ca = normalizeCA(options.ca);
146
+
147
+ // --- OCSP stapling (אופציונלי) ---
148
+ let ocsp = null;
149
+ if (options.ocsp != null) ocsp = new Uint8Array(readMaybeFile(options.ocsp));
150
+
151
+ // --- Ticket keys (אופציונלי) ---
152
+ let ticketKeys = null;
153
+ if (options.ticketKeys != null) ticketKeys = new Uint8Array(readMaybeFile(options.ticketKeys));
154
+ // אפשרות: ליצור מפתחי ברירת מחדל כאן אם תרצה
155
+
156
+ // --- בניית שרשרת לשיגור ללקוח (leaf → intermediates) ---
157
+ let chainDer = dedupeDerArray(certBlocksDer);
158
+ let certificateChain = [];
159
+ for (let c=0;c<chainDer.length;c++) {
160
+ certificateChain.push({ cert: chainDer[c] });
161
+ }
162
+
163
+ // מידע עזר לזיהוי סוג המפתח הציבורי של ה-leaf
164
+ let leafPublicKeyType = null;
165
+ if (certObjs.length > 0 && certObjs[0].publicKey) {
166
+ try { leafPublicKeyType = certObjs[0].publicKey.asymmetricKeyType || null; } catch (e) { leafPublicKeyType = null; }
167
+ }
168
+
169
+ return {
170
+ // חומר לשכבת ההנדשייק/רקורד:
171
+ certificateChain: certificateChain, // [{ cert: DER(Uint8Array) }, ...]
172
+ privateKey: privateKey, // PKCS#8 DER (Uint8Array)
173
+ ca: ca, // Trust store (DER)
174
+ ocsp: ocsp, // DER (אם הוגדר)
175
+ ticketKeys: ticketKeys, // Uint8Array (אם הוגדר)
176
+
177
+ // עזרי debug/לוגיקה:
178
+ certObjs: certObjs, // [X509Certificate...]
179
+ keyObj: keyObj, // KeyObject
180
+ leafPublicKeyType: leafPublicKeyType,
181
+
182
+ // פרמטרים פרוטוקוליים (אחסון; אתה מפרש בזמן ה-handshake):
183
+ minVersion: String(options.minVersion || 'TLSv1.2'),
184
+ maxVersion: String(options.maxVersion || 'TLSv1.3'),
185
+ ciphers: options.ciphers || null,
186
+ sigalgs: options.sigalgs || null,
187
+ ecdhCurve: options.ecdhCurve || null,
188
+ honorCipherOrder: !!options.honorCipherOrder,
189
+
190
+ // תמיכה ב־PFX אם תרצה לטפל בזה בשכבה אחרת:
191
+ pfx: options.pfx ? new Uint8Array(readMaybeFile(options.pfx)) : null,
192
+ passphrase: options.passphrase ? String(options.passphrase) : null
193
+ };
194
+ }
195
+
196
+ export default createSecureContext;