@joclaim/tls 0.1.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.
Files changed (80) hide show
  1. package/README.md +221 -0
  2. package/lib/crypto/common.d.ts +3 -0
  3. package/lib/crypto/common.js +26 -0
  4. package/lib/crypto/index.d.ts +3 -0
  5. package/lib/crypto/index.js +4 -0
  6. package/lib/crypto/insecure-rand.d.ts +1 -0
  7. package/lib/crypto/insecure-rand.js +9 -0
  8. package/lib/crypto/pure-js.d.ts +2 -0
  9. package/lib/crypto/pure-js.js +144 -0
  10. package/lib/crypto/webcrypto.d.ts +3 -0
  11. package/lib/crypto/webcrypto.js +310 -0
  12. package/lib/index.d.ts +4 -0
  13. package/lib/index.js +4 -0
  14. package/lib/make-tls-client.d.ts +74 -0
  15. package/lib/make-tls-client.js +657 -0
  16. package/lib/scripts/build-jsc.d.ts +1 -0
  17. package/lib/scripts/build-jsc.js +20 -0
  18. package/lib/scripts/ca-template.d.ts +5 -0
  19. package/lib/scripts/ca-template.js +6 -0
  20. package/lib/scripts/fallbacks/crypto.d.ts +4 -0
  21. package/lib/scripts/fallbacks/crypto.js +2 -0
  22. package/lib/scripts/handshake.d.ts +1 -0
  23. package/lib/scripts/handshake.js +61 -0
  24. package/lib/scripts/jsc.d.ts +28 -0
  25. package/lib/scripts/jsc.js +92 -0
  26. package/lib/scripts/update-ca-certs.d.ts +1 -0
  27. package/lib/scripts/update-ca-certs.js +29 -0
  28. package/lib/types/crypto.d.ts +62 -0
  29. package/lib/types/crypto.js +1 -0
  30. package/lib/types/index.d.ts +15 -0
  31. package/lib/types/index.js +4 -0
  32. package/lib/types/logger.d.ts +6 -0
  33. package/lib/types/logger.js +1 -0
  34. package/lib/types/tls.d.ts +141 -0
  35. package/lib/types/tls.js +1 -0
  36. package/lib/types/x509.d.ts +32 -0
  37. package/lib/types/x509.js +1 -0
  38. package/lib/utils/additional-root-cas.d.ts +1 -0
  39. package/lib/utils/additional-root-cas.js +197 -0
  40. package/lib/utils/client-hello.d.ts +23 -0
  41. package/lib/utils/client-hello.js +167 -0
  42. package/lib/utils/constants.d.ts +239 -0
  43. package/lib/utils/constants.js +244 -0
  44. package/lib/utils/decryption-utils.d.ts +64 -0
  45. package/lib/utils/decryption-utils.js +166 -0
  46. package/lib/utils/finish-messages.d.ts +11 -0
  47. package/lib/utils/finish-messages.js +49 -0
  48. package/lib/utils/generics.d.ts +35 -0
  49. package/lib/utils/generics.js +146 -0
  50. package/lib/utils/index.d.ts +18 -0
  51. package/lib/utils/index.js +18 -0
  52. package/lib/utils/key-share.d.ts +13 -0
  53. package/lib/utils/key-share.js +72 -0
  54. package/lib/utils/key-update.d.ts +2 -0
  55. package/lib/utils/key-update.js +14 -0
  56. package/lib/utils/logger.d.ts +2 -0
  57. package/lib/utils/logger.js +15 -0
  58. package/lib/utils/make-queue.d.ts +3 -0
  59. package/lib/utils/make-queue.js +22 -0
  60. package/lib/utils/mozilla-root-cas.d.ts +5 -0
  61. package/lib/utils/mozilla-root-cas.js +4459 -0
  62. package/lib/utils/packets.d.ts +51 -0
  63. package/lib/utils/packets.js +148 -0
  64. package/lib/utils/parse-alert.d.ts +7 -0
  65. package/lib/utils/parse-alert.js +28 -0
  66. package/lib/utils/parse-certificate.d.ts +29 -0
  67. package/lib/utils/parse-certificate.js +188 -0
  68. package/lib/utils/parse-client-hello.d.ts +11 -0
  69. package/lib/utils/parse-client-hello.js +39 -0
  70. package/lib/utils/parse-extensions.d.ts +11 -0
  71. package/lib/utils/parse-extensions.js +74 -0
  72. package/lib/utils/parse-server-hello.d.ts +10 -0
  73. package/lib/utils/parse-server-hello.js +52 -0
  74. package/lib/utils/session-ticket.d.ts +17 -0
  75. package/lib/utils/session-ticket.js +51 -0
  76. package/lib/utils/wrapped-record.d.ts +25 -0
  77. package/lib/utils/wrapped-record.js +191 -0
  78. package/lib/utils/x509.d.ts +5 -0
  79. package/lib/utils/x509.js +124 -0
  80. package/package.json +82 -0
@@ -0,0 +1,657 @@
1
+ import { crypto } from "./crypto/index.js";
2
+ import { packClientHello } from "./utils/client-hello.js";
3
+ import { CONTENT_TYPE_MAP, MAX_ENC_PACKET_SIZE, PACKET_TYPE, SUPPORTED_CIPHER_SUITE_MAP, SUPPORTED_NAMED_CURVE_MAP, SUPPORTED_NAMED_CURVES, SUPPORTED_RECORD_TYPE_MAP } from "./utils/constants.js";
4
+ import { computeSharedKeys, computeSharedKeysTls12, computeUpdatedTrafficMasterSecret, deriveTrafficKeysForSide } from "./utils/decryption-utils.js";
5
+ import { generateFinishTls12, packClientFinishTls12, packFinishMessagePacket, verifyFinishMessage } from "./utils/finish-messages.js";
6
+ import { areUint8ArraysEqual, chunkUint8Array, concatenateUint8Arrays, toHexStringWithWhitespace } from "./utils/generics.js";
7
+ import { createRsaPreMasterSecret, packClientCurveKeyShare, packClientRsaKeyShare, processServerKeyShare } from "./utils/key-share.js";
8
+ import { packKeyUpdateRecord } from "./utils/key-update.js";
9
+ import { logger as LOGGER } from "./utils/logger.js";
10
+ import { makeQueue } from "./utils/make-queue.js";
11
+ import { makeMessageProcessor, packPacketHeader, packWithLength, readWithLength } from "./utils/packets.js";
12
+ import { parseTlsAlert } from "./utils/parse-alert.js";
13
+ import { getSignatureDataTls12, getSignatureDataTls13, parseCertificates, parseServerCertificateVerify, verifyCertificateChain, verifyCertificateSignature } from "./utils/parse-certificate.js";
14
+ import { parseServerExtensions } from "./utils/parse-extensions.js";
15
+ import { parseServerHello } from "./utils/parse-server-hello.js";
16
+ import { getPskFromTicket, parseSessionTicket } from "./utils/session-ticket.js";
17
+ import { decryptWrappedRecord, encryptWrappedRecord } from "./utils/wrapped-record.js";
18
+ const RECORD_LENGTH_BYTES = 3;
19
+ export function makeTLSClient({ host, verifyServerCertificate = true, rootCAs, logger: _logger, cipherSuites, namedCurves = SUPPORTED_NAMED_CURVES, supportedProtocolVersions, signatureAlgorithms, applicationLayerProtocols, fetchCertificateBytes, write, onRead, onApplicationData, onSessionTicket, onTlsEnd, onHandshake, onRecvCertificates }) {
20
+ const logger = _logger || LOGGER;
21
+ const processor = makeMessageProcessor(logger);
22
+ const { enqueue: enqueueServerPacket } = makeQueue();
23
+ const keyPairs = {};
24
+ let handshakeDone = false;
25
+ let ended = false;
26
+ let sessionId = new Uint8Array();
27
+ let handshakeMsgs = [];
28
+ let cipherSuite = undefined;
29
+ let earlySecret = undefined;
30
+ let keys = undefined;
31
+ let recordSendCount = 0;
32
+ let recordRecvCount = 0;
33
+ let keyType = undefined;
34
+ let connTlsVersion = undefined;
35
+ let clientRandom = undefined;
36
+ let serverRandom = undefined;
37
+ let cipherSpecChanged = false;
38
+ let selectedAlpn;
39
+ let certificates;
40
+ let handshakePacketStream = new Uint8Array();
41
+ let clientCertificateRequested = false;
42
+ let certificatesVerified = false;
43
+ const processPacketUnsafe = async ({ type, packet: { content, header } }) => {
44
+ if (ended) {
45
+ logger.warn('connection closed, ignoring packet');
46
+ return;
47
+ }
48
+ let contentType;
49
+ let ctx = { type: 'plaintext' };
50
+ // if the cipher spec has changed,
51
+ // the data will be encrypted, so
52
+ // we need to decrypt the packet
53
+ if (cipherSpecChanged || type === PACKET_TYPE.WRAPPED_RECORD) {
54
+ logger.trace('recv wrapped record');
55
+ const macKey = 'serverMacKey' in keys
56
+ ? keys.serverMacKey
57
+ : undefined;
58
+ const decrypted = await decryptWrappedRecord(content, {
59
+ key: keys.serverEncKey,
60
+ iv: keys.serverIv,
61
+ recordHeader: header,
62
+ recordNumber: recordRecvCount,
63
+ cipherSuite: cipherSuite,
64
+ version: connTlsVersion,
65
+ macKey,
66
+ });
67
+ if (connTlsVersion === 'TLS1_3') {
68
+ // TLS 1.3 has an extra byte suffixed
69
+ // this denotes the content type of the
70
+ // packet
71
+ const contentTypeNum = decrypted
72
+ .plaintext[decrypted.plaintext.length - 1];
73
+ contentType = Object.entries(CONTENT_TYPE_MAP)
74
+ .find(([, val]) => val === contentTypeNum)?.[0];
75
+ }
76
+ ctx = {
77
+ type: 'ciphertext',
78
+ encKey: keys.serverEncKey,
79
+ fixedIv: keys.serverIv,
80
+ iv: decrypted.iv,
81
+ recordNumber: recordRecvCount,
82
+ macKey,
83
+ ciphertext: content,
84
+ plaintext: decrypted.plaintext,
85
+ contentType,
86
+ };
87
+ content = decrypted.plaintext;
88
+ if (contentType) {
89
+ content = content.slice(0, -1);
90
+ }
91
+ logger.trace({
92
+ recordRecvCount,
93
+ contentType,
94
+ length: content.length,
95
+ }, 'decrypted wrapped record');
96
+ recordRecvCount += 1;
97
+ }
98
+ onRead?.({ content, header }, ctx);
99
+ if (type === PACKET_TYPE.WRAPPED_RECORD
100
+ || type === PACKET_TYPE.HELLO) {
101
+ // do nothing -- pass through
102
+ }
103
+ else if (type === PACKET_TYPE.CHANGE_CIPHER_SPEC) {
104
+ logger.debug('received change cipher spec');
105
+ cipherSpecChanged = true;
106
+ return;
107
+ }
108
+ else if (type === PACKET_TYPE.ALERT) {
109
+ await handleAlert(content);
110
+ return;
111
+ }
112
+ else {
113
+ logger.warn({
114
+ type: type.toString(16),
115
+ chunk: toHexStringWithWhitespace(content)
116
+ }, 'cannot process message');
117
+ return;
118
+ }
119
+ try {
120
+ await processRecord({
121
+ content,
122
+ contentType: contentType
123
+ ? CONTENT_TYPE_MAP[contentType]
124
+ : undefined,
125
+ header,
126
+ });
127
+ }
128
+ catch (err) {
129
+ logger.error({ err }, 'error processing record');
130
+ end(err);
131
+ }
132
+ };
133
+ const processPacket = (pkt) => (enqueueServerPacket(processPacketUnsafe, pkt));
134
+ async function processRecord({ content: record, contentType, header, }) {
135
+ contentType ??= header[0];
136
+ if (contentType === CONTENT_TYPE_MAP.HANDSHAKE) {
137
+ handshakePacketStream = concatenateUint8Arrays([handshakePacketStream, record]);
138
+ let data;
139
+ while (data = readPacket()) {
140
+ const { type, content } = data;
141
+ switch (type) {
142
+ case SUPPORTED_RECORD_TYPE_MAP.SERVER_HELLO:
143
+ logger.trace('received server hello');
144
+ const hello = await parseServerHello(content);
145
+ if (!hello.supportsPsk && earlySecret) {
146
+ throw new Error('Server does not support PSK');
147
+ }
148
+ cipherSuite = hello.cipherSuite;
149
+ connTlsVersion = hello.serverTlsVersion;
150
+ serverRandom = hello.serverRandom;
151
+ setAlpn(hello.extensions?.ALPN);
152
+ const cipherSuiteData = SUPPORTED_CIPHER_SUITE_MAP[cipherSuite];
153
+ logger.debug({ cipherSuite, connTlsVersion, selectedAlpn }, 'processed server hello');
154
+ if (hello.publicKeyType && hello.publicKey) {
155
+ await processServerPubKey({
156
+ publicKeyType: hello.publicKeyType,
157
+ publicKey: hello.publicKey
158
+ });
159
+ }
160
+ else if ('isRsaEcdh' in cipherSuiteData && cipherSuiteData.isRsaEcdh) {
161
+ keyType = 'RSA';
162
+ }
163
+ break;
164
+ case SUPPORTED_RECORD_TYPE_MAP.ENCRYPTED_EXTENSIONS:
165
+ const extData = parseServerExtensions(content);
166
+ logger.debug({
167
+ len: content.length,
168
+ extData
169
+ }, 'received encrypted extensions');
170
+ setAlpn(extData?.ALPN);
171
+ break;
172
+ case SUPPORTED_RECORD_TYPE_MAP.HELLO_RETRY_REQUEST:
173
+ throw new Error('Hello retry not supported. Please re-establish connection');
174
+ case SUPPORTED_RECORD_TYPE_MAP.CERTIFICATE:
175
+ logger.trace({ len: content.length }, 'received certificate');
176
+ const result = parseCertificates(content, { version: connTlsVersion });
177
+ certificates = result.certificates;
178
+ logger.debug({ len: certificates.length }, 'parsed certificates');
179
+ if (verifyServerCertificate && !certificatesVerified) {
180
+ await verifyCertificateChain(certificates, host, logger, fetchCertificateBytes, rootCAs);
181
+ logger.debug('verified certificate chain');
182
+ certificatesVerified = true;
183
+ }
184
+ onRecvCertificates?.({ certificates });
185
+ break;
186
+ case SUPPORTED_RECORD_TYPE_MAP.CERTIFICATE_VERIFY:
187
+ logger.debug({ len: content.length }, 'received certificate verify');
188
+ const signature = parseServerCertificateVerify(content);
189
+ logger.debug({ alg: signature.algorithm }, 'parsed certificate verify');
190
+ if (!certificates?.length) {
191
+ throw new Error('No certificates received');
192
+ }
193
+ const signatureData = await getSignatureDataTls13(handshakeMsgs.slice(0, -1), cipherSuite);
194
+ await verifyCertificateSignature({
195
+ ...signature,
196
+ publicKey: certificates[0].getPublicKey(),
197
+ signatureData,
198
+ });
199
+ break;
200
+ case SUPPORTED_RECORD_TYPE_MAP.FINISHED:
201
+ await processServerFinish(content);
202
+ break;
203
+ case SUPPORTED_RECORD_TYPE_MAP.KEY_UPDATE:
204
+ const newMasterSecret = await computeUpdatedTrafficMasterSecret(keys.serverSecret, cipherSuite);
205
+ const newKeys = await deriveTrafficKeysForSide(newMasterSecret, cipherSuite);
206
+ keys = {
207
+ ...keys,
208
+ serverSecret: newMasterSecret,
209
+ serverEncKey: newKeys.encKey,
210
+ serverIv: newKeys.iv,
211
+ };
212
+ recordRecvCount = 0;
213
+ logger.debug('updated server traffic keys');
214
+ break;
215
+ case SUPPORTED_RECORD_TYPE_MAP.SESSION_TICKET:
216
+ if (connTlsVersion === 'TLS1_3') {
217
+ logger.debug({ len: record.length }, 'received session ticket');
218
+ const ticket = parseSessionTicket(content);
219
+ onSessionTicket?.(ticket);
220
+ }
221
+ else {
222
+ logger.warn('ignoring received session ticket in TLS 1.2');
223
+ }
224
+ break;
225
+ case SUPPORTED_RECORD_TYPE_MAP.CERTIFICATE_REQUEST:
226
+ logger.debug('received client certificate request');
227
+ clientCertificateRequested = true;
228
+ break;
229
+ case SUPPORTED_RECORD_TYPE_MAP.SERVER_KEY_SHARE:
230
+ logger.trace('received server key share');
231
+ if (!certificates?.length) {
232
+ throw new Error('No certificates received');
233
+ }
234
+ // extract pub key & signature of pub key with cert
235
+ const keyShare = await processServerKeyShare(content);
236
+ logger.debug({
237
+ publicKeyType: keyShare.publicKeyType,
238
+ signatureAlgorithm: keyShare.signatureAlgorithm,
239
+ }, 'got server key share');
240
+ // compute signature data
241
+ const signatureData12 = await getSignatureDataTls12({
242
+ clientRandom: clientRandom,
243
+ serverRandom: serverRandom,
244
+ curveType: keyShare.publicKeyType,
245
+ publicKey: keyShare.publicKey,
246
+ });
247
+ // verify signature
248
+ await verifyCertificateSignature({
249
+ signature: keyShare.signatureBytes,
250
+ algorithm: keyShare.signatureAlgorithm,
251
+ publicKey: certificates[0].getPublicKey(),
252
+ signatureData: signatureData12,
253
+ });
254
+ logger.debug('verified server key share signature');
255
+ if (verifyServerCertificate && !certificatesVerified) {
256
+ await verifyCertificateChain(certificates, host, logger, fetchCertificateBytes, rootCAs);
257
+ logger.debug('verified certificate chain');
258
+ certificatesVerified = true;
259
+ }
260
+ // compute shared keys
261
+ await processServerPubKey(keyShare);
262
+ break;
263
+ case SUPPORTED_RECORD_TYPE_MAP.SERVER_HELLO_DONE:
264
+ logger.debug('server hello done');
265
+ if (!keyType) {
266
+ // need to execute client key share
267
+ throw new Error('Key exchange without key-type not supported');
268
+ }
269
+ let clientKeyShare;
270
+ if (keyType === 'RSA') {
271
+ if (keys) {
272
+ throw new Error('Keys already computed, despite RSA key type');
273
+ }
274
+ const { preMasterSecret, encrypted } = await createRsaPreMasterSecret(certificates[0], connTlsVersion);
275
+ clientKeyShare = await packClientRsaKeyShare(encrypted);
276
+ keys = await computeSharedKeysTls12({
277
+ preMasterSecret: preMasterSecret,
278
+ clientRandom: clientRandom,
279
+ serverRandom: serverRandom,
280
+ cipherSuite: cipherSuite,
281
+ });
282
+ }
283
+ else {
284
+ clientKeyShare
285
+ = await packClientCurveKeyShare(keyPairs[keyType].pubKey);
286
+ }
287
+ await writePacket({ type: 'HELLO', data: clientKeyShare });
288
+ handshakeMsgs.push(clientKeyShare);
289
+ await writeChangeCipherSpec();
290
+ const finishMsg = await packClientFinishTls12({
291
+ secret: keys.masterSecret,
292
+ handshakeMessages: handshakeMsgs,
293
+ cipherSuite: cipherSuite,
294
+ });
295
+ await writeEncryptedPacket({ data: finishMsg, type: 'HELLO' });
296
+ handshakeMsgs.push(finishMsg);
297
+ break;
298
+ default:
299
+ logger.warn({ type: type.toString(16) }, 'cannot process record');
300
+ break;
301
+ }
302
+ }
303
+ function readPacket() {
304
+ if (!handshakePacketStream.length) {
305
+ return;
306
+ }
307
+ const type = handshakePacketStream[0];
308
+ const content = readWithLength(handshakePacketStream.slice(1), RECORD_LENGTH_BYTES);
309
+ if (!content) {
310
+ logger.warn('missing bytes from packet');
311
+ return;
312
+ }
313
+ const totalLength = 1 + RECORD_LENGTH_BYTES + content.length;
314
+ if (!handshakeDone) {
315
+ handshakeMsgs.push(handshakePacketStream.slice(0, totalLength));
316
+ }
317
+ handshakePacketStream = handshakePacketStream.slice(totalLength);
318
+ return { type, content };
319
+ }
320
+ }
321
+ else if (contentType === CONTENT_TYPE_MAP.APPLICATION_DATA) {
322
+ logger.trace({ len: record.length }, 'received application data');
323
+ onApplicationData?.(record);
324
+ }
325
+ else if (contentType === CONTENT_TYPE_MAP.ALERT) {
326
+ await handleAlert(record);
327
+ }
328
+ else {
329
+ logger.warn({ record: record, contentType: contentType?.toString(16) }, 'cannot process record');
330
+ }
331
+ }
332
+ function setAlpn(alpn) {
333
+ selectedAlpn = alpn || applicationLayerProtocols?.[0];
334
+ if (selectedAlpn && !applicationLayerProtocols?.includes(selectedAlpn)) {
335
+ throw new Error(`Server selected unsupported ALPN: "${selectedAlpn}"`);
336
+ }
337
+ }
338
+ async function handleAlert(content) {
339
+ if (ended) {
340
+ logger.warn('connection closed, ignoring alert');
341
+ return;
342
+ }
343
+ const { level, description } = parseTlsAlert(content);
344
+ const msg = (description === 'HANDSHAKE_FAILURE' || description === 'PROTOCOL_VERSION'
345
+ ? 'Unsupported TLS version'
346
+ : 'received alert');
347
+ logger[level === 'WARNING' ? 'warn' : 'error']({ level, description }, msg);
348
+ if (level === 'FATAL'
349
+ || description === 'CLOSE_NOTIFY') {
350
+ end(level === 'FATAL'
351
+ ? new Error(`Fatal alert: ${description}`)
352
+ : undefined);
353
+ }
354
+ }
355
+ async function sendClientCertificate() {
356
+ if (clientCertificateRequested) {
357
+ const clientZeroCert = concatenateUint8Arrays([
358
+ new Uint8Array([SUPPORTED_RECORD_TYPE_MAP.CERTIFICATE, 0x00]),
359
+ packWithLength(new Uint8Array([0, 0, 0, 0]))
360
+ ]);
361
+ logger.trace({ cert: toHexStringWithWhitespace(clientZeroCert) }, 'sending zero certs');
362
+ await writeEncryptedPacket({
363
+ type: 'WRAPPED_RECORD',
364
+ data: clientZeroCert,
365
+ contentType: 'HANDSHAKE'
366
+ });
367
+ handshakeMsgs.push(clientZeroCert);
368
+ }
369
+ }
370
+ async function processServerFinish(serverFinish) {
371
+ logger.debug('received server finish');
372
+ if (!certificatesVerified
373
+ // when using a PSK, the server does not send certificates
374
+ && !earlySecret
375
+ && verifyServerCertificate) {
376
+ throw new Error('Finish received before certificate verification');
377
+ }
378
+ if (connTlsVersion === 'TLS1_2') {
379
+ await processServerFinishTls12(serverFinish);
380
+ }
381
+ else {
382
+ await processServerFinishTls13(serverFinish);
383
+ }
384
+ handshakeDone = true;
385
+ onHandshake?.();
386
+ }
387
+ async function processServerFinishTls12(serverFinish) {
388
+ const genServerFinish = await generateFinishTls12('server', {
389
+ handshakeMessages: handshakeMsgs.slice(0, -1),
390
+ secret: keys.masterSecret,
391
+ cipherSuite: cipherSuite,
392
+ });
393
+ if (!areUint8ArraysEqual(genServerFinish, serverFinish)) {
394
+ throw new Error('Server finish does not match');
395
+ }
396
+ }
397
+ async function processServerFinishTls13(serverFinish) {
398
+ // derive server keys now to streamline handshake messages handling
399
+ const serverKeys = await computeSharedKeys({
400
+ // we only use handshake messages till the server finish
401
+ hellos: handshakeMsgs,
402
+ cipherSuite: cipherSuite,
403
+ secretType: 'ap',
404
+ masterSecret: keys.masterSecret,
405
+ });
406
+ // the server hash computation does not include
407
+ // the server finish, so we need to exclude it
408
+ const handshakeMsgsForServerHash = handshakeMsgs.slice(0, -1);
409
+ await verifyFinishMessage(serverFinish, {
410
+ secret: keys.serverSecret,
411
+ handshakeMessages: handshakeMsgsForServerHash,
412
+ cipherSuite: cipherSuite
413
+ });
414
+ logger.debug('server finish verified');
415
+ // this might add an extra message to handshakeMsgs and affect handshakeHash
416
+ await sendClientCertificate();
417
+ const clientFinish = await packFinishMessagePacket({
418
+ secret: keys.clientSecret,
419
+ handshakeMessages: handshakeMsgs,
420
+ cipherSuite: cipherSuite
421
+ });
422
+ logger.trace({ finish: toHexStringWithWhitespace(clientFinish) }, 'sending client finish');
423
+ await writeEncryptedPacket({
424
+ type: 'WRAPPED_RECORD',
425
+ data: clientFinish,
426
+ contentType: 'HANDSHAKE'
427
+ });
428
+ // add the client finish to the handshake messages
429
+ handshakeMsgs.push(clientFinish);
430
+ // switch to using the provider keys
431
+ keys = serverKeys;
432
+ // also the send/recv counters are reset
433
+ // once we switch to the provider keys
434
+ recordSendCount = 0;
435
+ recordRecvCount = 0;
436
+ }
437
+ async function processServerPubKey(data) {
438
+ keyType = data.publicKeyType;
439
+ const { keyPair, algorithm } = await getKeyPair(data.publicKeyType);
440
+ const sharedSecret = await crypto.calculateSharedSecret(algorithm, keyPair.privKey, data.publicKey);
441
+ if (connTlsVersion === 'TLS1_2') {
442
+ keys = await computeSharedKeysTls12({
443
+ preMasterSecret: sharedSecret,
444
+ clientRandom: clientRandom,
445
+ serverRandom: serverRandom,
446
+ cipherSuite: cipherSuite,
447
+ });
448
+ }
449
+ else {
450
+ keys = await computeSharedKeys({
451
+ hellos: handshakeMsgs,
452
+ cipherSuite: cipherSuite,
453
+ secretType: 'hs',
454
+ masterSecret: sharedSecret,
455
+ earlySecret,
456
+ });
457
+ }
458
+ logger.debug({ keyType }, 'computed shared keys');
459
+ }
460
+ async function writeChangeCipherSpec() {
461
+ logger.debug('sending change cipher spec');
462
+ const changeCipherSpecData = new Uint8Array([1]);
463
+ await writePacket({
464
+ type: 'CHANGE_CIPHER_SPEC',
465
+ data: changeCipherSpecData
466
+ });
467
+ }
468
+ async function writeEncryptedPacket(opts) {
469
+ logger.trace({ ...opts, data: toHexStringWithWhitespace(opts.data) }, 'writing enc packet');
470
+ const macKey = 'clientMacKey' in keys
471
+ ? keys.clientMacKey
472
+ : undefined;
473
+ let plaintext = opts.data;
474
+ if (connTlsVersion === 'TLS1_3'
475
+ && typeof opts.contentType !== 'undefined') {
476
+ plaintext = concatenateUint8Arrays([
477
+ plaintext,
478
+ new Uint8Array([CONTENT_TYPE_MAP[opts.contentType]])
479
+ ]);
480
+ }
481
+ const { ciphertext, iv } = await encryptWrappedRecord(plaintext, {
482
+ key: keys.clientEncKey,
483
+ iv: keys.clientIv,
484
+ recordNumber: recordSendCount,
485
+ cipherSuite: cipherSuite,
486
+ macKey,
487
+ recordHeaderOpts: {
488
+ type: opts.type,
489
+ version: opts.version
490
+ },
491
+ version: connTlsVersion,
492
+ });
493
+ const header = packPacketHeader(ciphertext.length, opts);
494
+ await write({ header, content: ciphertext }, {
495
+ type: 'ciphertext',
496
+ encKey: keys.clientEncKey,
497
+ fixedIv: keys.clientIv,
498
+ iv,
499
+ recordNumber: recordSendCount,
500
+ macKey,
501
+ ciphertext,
502
+ plaintext,
503
+ contentType: opts.contentType,
504
+ });
505
+ recordSendCount += 1;
506
+ }
507
+ async function writePacket(opts) {
508
+ logger.trace({ ...opts, data: toHexStringWithWhitespace(opts.data) }, 'writing packet');
509
+ const header = packPacketHeader(opts.data.length, opts);
510
+ await write({ header, content: opts.data }, { type: 'plaintext' });
511
+ }
512
+ async function end(error) {
513
+ await enqueueServerPacket(() => { });
514
+ logger.trace({ err: error }, 'ended tls connection');
515
+ ended = true;
516
+ handshakeDone = false;
517
+ handshakeMsgs = [];
518
+ keys = undefined;
519
+ recordSendCount = 0;
520
+ recordRecvCount = 0;
521
+ earlySecret = undefined;
522
+ cipherSuite = undefined;
523
+ keyType = undefined;
524
+ clientRandom = undefined;
525
+ serverRandom = undefined;
526
+ processor.reset();
527
+ onTlsEnd?.(error);
528
+ }
529
+ async function getKeyPair(keyType) {
530
+ const algorithm = SUPPORTED_NAMED_CURVE_MAP[keyType].algorithm;
531
+ keyPairs[keyType] ??= await crypto.generateKeyPair(algorithm);
532
+ return { algorithm, keyPair: keyPairs[keyType] };
533
+ }
534
+ return {
535
+ getMetadata() {
536
+ return { cipherSuite, keyType, version: connTlsVersion, selectedAlpn };
537
+ },
538
+ hasEnded() {
539
+ return ended;
540
+ },
541
+ /**
542
+ * Get the current traffic keys
543
+ */
544
+ getKeys() {
545
+ if (!keys) {
546
+ return undefined;
547
+ }
548
+ return { ...keys, recordSendCount, recordRecvCount };
549
+ },
550
+ /**
551
+ * Session ID used to connect to the server
552
+ */
553
+ getSessionId() {
554
+ return sessionId;
555
+ },
556
+ isHandshakeDone() {
557
+ return handshakeDone;
558
+ },
559
+ getPskFromTicket(ticket) {
560
+ return getPskFromTicket(ticket, {
561
+ masterKey: keys.masterSecret,
562
+ hellos: handshakeMsgs,
563
+ cipherSuite: cipherSuite,
564
+ });
565
+ },
566
+ /**
567
+ * Start the handshake with the server
568
+ */
569
+ async startHandshake(opts) {
570
+ if (handshakeDone) {
571
+ throw new Error('Handshake already done');
572
+ }
573
+ sessionId = crypto.randomBytes(32);
574
+ ended = false;
575
+ clientRandom = opts?.random || crypto.randomBytes(32);
576
+ const clientHello = await packClientHello({
577
+ host,
578
+ keysToShare: await Promise.all(namedCurves
579
+ .map(async (keyType) => {
580
+ const { keyPair } = await getKeyPair(keyType);
581
+ return {
582
+ type: keyType,
583
+ key: keyPair.pubKey,
584
+ };
585
+ })),
586
+ random: clientRandom,
587
+ sessionId,
588
+ psk: opts?.psk,
589
+ cipherSuites,
590
+ supportedProtocolVersions,
591
+ signatureAlgorithms,
592
+ applicationLayerProtocols,
593
+ });
594
+ handshakeMsgs.push(clientHello);
595
+ if (opts?.psk) {
596
+ earlySecret = opts.psk.earlySecret;
597
+ }
598
+ await writePacket({
599
+ type: 'HELLO',
600
+ data: clientHello,
601
+ });
602
+ },
603
+ /**
604
+ * Handle bytes received from the server.
605
+ * Could be a complete or partial TLS packet
606
+ */
607
+ async handleReceivedBytes(data) {
608
+ await Promise.all(Array.from(processor.onData(data))
609
+ .map(processPacket));
610
+ },
611
+ /**
612
+ * Handle a complete TLS packet received
613
+ * from the server
614
+ */
615
+ handleReceivedPacket: processPacket,
616
+ /**
617
+ * Utilise the KeyUpdate handshake message to update
618
+ * the traffic keys. Available only in TLS 1.3
619
+ * @param requestUpdateFromServer should the server be requested to
620
+ * update its keys as well
621
+ */
622
+ async updateTrafficKeys(requestUpdateFromServer = false) {
623
+ const packet = packKeyUpdateRecord(requestUpdateFromServer
624
+ ? 'UPDATE_REQUESTED'
625
+ : 'UPDATE_NOT_REQUESTED');
626
+ await writeEncryptedPacket({
627
+ data: packet,
628
+ type: 'WRAPPED_RECORD',
629
+ contentType: 'HANDSHAKE'
630
+ });
631
+ const newMasterSecret = await computeUpdatedTrafficMasterSecret(keys.clientSecret, cipherSuite);
632
+ const newKeys = await deriveTrafficKeysForSide(newMasterSecret, cipherSuite);
633
+ keys = {
634
+ ...keys,
635
+ clientSecret: newMasterSecret,
636
+ clientEncKey: newKeys.encKey,
637
+ clientIv: newKeys.iv,
638
+ };
639
+ recordSendCount = 0;
640
+ logger.info('updated client traffic keys');
641
+ },
642
+ async write(data) {
643
+ if (!handshakeDone) {
644
+ throw new Error('Handshake not done');
645
+ }
646
+ const chunks = chunkUint8Array(data, MAX_ENC_PACKET_SIZE);
647
+ for (const chunk of chunks) {
648
+ await writeEncryptedPacket({
649
+ data: chunk,
650
+ type: 'WRAPPED_RECORD',
651
+ contentType: 'APPLICATION_DATA'
652
+ });
653
+ }
654
+ },
655
+ end,
656
+ };
657
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,20 @@
1
+ import * as esbuild from 'esbuild';
2
+ const rslt = await esbuild.build({
3
+ entryPoints: ['./src/scripts/jsc.ts'],
4
+ bundle: true,
5
+ platform: 'browser',
6
+ outfile: 'out/jsc-bridge.mjs',
7
+ format: 'esm',
8
+ tsconfig: 'tsconfig.json',
9
+ legalComments: 'none',
10
+ metafile: true, // Enable metafile generation
11
+ treeShaking: true,
12
+ alias: {
13
+ '@noble/hashes/crypto': './src/scripts/fallbacks/crypto.ts',
14
+ }
15
+ });
16
+ if (process.argv.includes('--analyze')) {
17
+ // Analyze the metafile
18
+ const analysis = await esbuild.analyzeMetafile(rslt.metafile);
19
+ console.log(analysis);
20
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Mozilla Root CA List
3
+ * downloaded from: https://wiki.mozilla.org/CA/Included_Certificates
4
+ */
5
+ export declare const MOZILLA_ROOT_CA_LIST: string[];