@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,146 @@
1
+ import { TLS_PROTOCOL_VERSION_MAP } from "./constants.js";
2
+ /**
3
+ * Converts a buffer to a hex string with whitespace between each byte
4
+ * @returns eg. '01 02 03 04'
5
+ */
6
+ export function toHexStringWithWhitespace(buff, whitespace = ' ') {
7
+ return [...buff]
8
+ .map(x => x.toString(16).padStart(2, '0'))
9
+ .join(whitespace);
10
+ }
11
+ export function xor(a, b) {
12
+ const result = new Uint8Array(a.length);
13
+ for (const [i, element] of a.entries()) {
14
+ result[i] = element ^ b[i];
15
+ }
16
+ return result;
17
+ }
18
+ export function concatenateUint8Arrays(arrays) {
19
+ const totalLength = arrays.reduce((acc, curr) => acc + curr.length, 0);
20
+ const result = new Uint8Array(totalLength);
21
+ let offset = 0;
22
+ for (const arr of arrays) {
23
+ result.set(arr, offset);
24
+ offset += arr.length;
25
+ }
26
+ return result;
27
+ }
28
+ export function areUint8ArraysEqual(a, b) {
29
+ if (a.length !== b.length) {
30
+ return false;
31
+ }
32
+ for (const [i, element] of a.entries()) {
33
+ if (element !== b[i]) {
34
+ return false;
35
+ }
36
+ }
37
+ return true;
38
+ }
39
+ export function uint8ArrayToDataView(arr) {
40
+ return new DataView(arr.buffer, arr.byteOffset, arr.byteLength);
41
+ }
42
+ export function asciiToUint8Array(str) {
43
+ const bytes = new Uint8Array(str.length);
44
+ for (let i = 0; i < str.length; i++) {
45
+ const charCode = str.charCodeAt(i);
46
+ if (charCode < 0 || charCode > 255) {
47
+ throw new Error(`Invalid ASCII character at index ${i}: ${str[i]}`);
48
+ }
49
+ bytes[i] = charCode;
50
+ }
51
+ return bytes;
52
+ }
53
+ /**
54
+ * convert a Uint8Array to a binary encoded str
55
+ * from: https://github.com/feross/buffer/blob/795bbb5bda1b39f1370ebd784bea6107b087e3a7/index.js#L1063
56
+ * @param buf
57
+ * @returns
58
+ */
59
+ export function uint8ArrayToBinaryStr(buf) {
60
+ let ret = '';
61
+ for (const v of buf) {
62
+ ret += String.fromCharCode(v);
63
+ }
64
+ return ret;
65
+ }
66
+ export function generateIV(iv, recordNumber) {
67
+ // make the recordNumber a buffer, so we can XOR with the main IV
68
+ // to generate the specific IV to decrypt this packet
69
+ const recordBuffer = new Uint8Array(iv.length);
70
+ const recordBufferView = new DataView(recordBuffer.buffer);
71
+ recordBufferView.setUint32(iv.length - 4, recordNumber);
72
+ return xor(iv, recordBuffer);
73
+ }
74
+ /**
75
+ * TLS has this special sort of padding where the last byte
76
+ * is the number of padding bytes, and all the padding bytes
77
+ * are the same as the last byte.
78
+ * Eg. for an 8 byte block [ 0x0a, 0x0b, 0x0c, 0xd ]
79
+ * -> [ 0x0a, 0x0b, 0x0c, 0x04, 0x04, 0x04, 0x04, 0x04 ]
80
+ */
81
+ export function padTls(data, blockSize) {
82
+ const nextMultiple = data.length % blockSize === 0
83
+ ? data.length + blockSize
84
+ : Math.ceil(data.length / blockSize) * blockSize;
85
+ const paddingLength = nextMultiple - data.length;
86
+ const paddingNum = paddingLength - 1;
87
+ const padded = new Uint8Array(nextMultiple);
88
+ padded.set(data);
89
+ padded.fill(paddingNum, data.length);
90
+ padded.fill(paddingNum, nextMultiple - 1);
91
+ return padded;
92
+ }
93
+ /**
94
+ * Unpad a TLS-spec padded buffer
95
+ */
96
+ export function unpadTls(data) {
97
+ const paddingLength = data[data.length - 1];
98
+ for (let i = 0; i < paddingLength; i++) {
99
+ if (data[data.length - 1 - i] !== paddingLength) {
100
+ throw new Error('Invalid padding');
101
+ }
102
+ }
103
+ return data.slice(0, data.length - paddingLength);
104
+ }
105
+ export function isSymmetricCipher(cipher) {
106
+ return cipher === 'AES-128-CBC';
107
+ }
108
+ export function chunkUint8Array(arr, chunkSize) {
109
+ const result = [];
110
+ for (let i = 0; i < arr.length; i += chunkSize) {
111
+ result.push(arr.slice(i, i + chunkSize));
112
+ }
113
+ return result;
114
+ }
115
+ export function getTlsVersionFromBytes(bytes) {
116
+ const supportedV = Object.entries(TLS_PROTOCOL_VERSION_MAP)
117
+ .find(([, v]) => areUint8ArraysEqual(v, bytes));
118
+ if (!supportedV) {
119
+ throw new Error(`Unsupported TLS version '${bytes}'`);
120
+ }
121
+ return supportedV[0];
122
+ }
123
+ export const hkdfExpand = async (alg, hashLength, key, expLength, info, crypto) => {
124
+ info ||= new Uint8Array(0);
125
+ const infoLength = info.length;
126
+ const steps = Math.ceil(expLength / hashLength);
127
+ if (steps > 0xFF) {
128
+ throw new Error(`OKM length ${expLength} is too long for ${alg} hash`);
129
+ }
130
+ // use single buffer with unnecessary create/copy/move operations
131
+ const t = new Uint8Array(hashLength * steps + infoLength + 1);
132
+ for (let c = 1, start = 0, end = 0; c <= steps; ++c) {
133
+ // add info
134
+ t.set(info, end);
135
+ // add counter
136
+ t.set([c], end + infoLength);
137
+ // use view: T(C) = T(C-1) | info | C
138
+ const hmac = await crypto
139
+ .hmac(alg, key, t.slice(start, end + infoLength + 1));
140
+ // put back to the same buffer
141
+ t.set(hmac.slice(0, t.length - end), end);
142
+ start = end; // used for T(C-1) start
143
+ end += hashLength; // used for T(C-1) end & overall end
144
+ }
145
+ return t.slice(0, expLength);
146
+ };
@@ -0,0 +1,18 @@
1
+ export * from './client-hello.ts';
2
+ export * from './constants.ts';
3
+ export * from './decryption-utils.ts';
4
+ export * from './finish-messages.ts';
5
+ export * from './generics.ts';
6
+ export * from './key-share.ts';
7
+ export * from './key-update.ts';
8
+ export * from './logger.ts';
9
+ export * from './make-queue.ts';
10
+ export * from './packets.ts';
11
+ export * from './parse-alert.ts';
12
+ export * from './parse-certificate.ts';
13
+ export * from './parse-server-hello.ts';
14
+ export * from './mozilla-root-cas.ts';
15
+ export * from './session-ticket.ts';
16
+ export * from './wrapped-record.ts';
17
+ export * from './x509.ts';
18
+ export * from './parse-client-hello.ts';
@@ -0,0 +1,18 @@
1
+ export * from "./client-hello.js";
2
+ export * from "./constants.js";
3
+ export * from "./decryption-utils.js";
4
+ export * from "./finish-messages.js";
5
+ export * from "./generics.js";
6
+ export * from "./key-share.js";
7
+ export * from "./key-update.js";
8
+ export * from "./logger.js";
9
+ export * from "./make-queue.js";
10
+ export * from "./packets.js";
11
+ export * from "./parse-alert.js";
12
+ export * from "./parse-certificate.js";
13
+ export * from "./parse-server-hello.js";
14
+ export * from "./mozilla-root-cas.js";
15
+ export * from "./session-ticket.js";
16
+ export * from "./wrapped-record.js";
17
+ export * from "./x509.js";
18
+ export * from "./parse-client-hello.js";
@@ -0,0 +1,13 @@
1
+ import type { Key, TLSProtocolVersion, X509Certificate } from '../types/index.ts';
2
+ export declare function packClientCurveKeyShare(publicKey: Key): Promise<Uint8Array<ArrayBufferLike>>;
3
+ export declare function packClientRsaKeyShare(encPreMaster: Uint8Array): Promise<Uint8Array<ArrayBufferLike>>;
4
+ export declare function processServerKeyShare(data: Uint8Array): Promise<{
5
+ publicKeyType: "X25519" | "SECP256R1" | "SECP384R1";
6
+ publicKey: unknown;
7
+ signatureAlgorithm: "ECDSA_SECP256R1_SHA256" | "ECDSA_SECP384R1_SHA256" | "RSA_PSS_RSAE_SHA256" | "RSA_PKCS1_SHA256" | "RSA_PKCS1_SHA384" | "RSA_PKCS1_SHA512";
8
+ signatureBytes: Uint8Array<ArrayBuffer>;
9
+ }>;
10
+ export declare function createRsaPreMasterSecret(serverCert: X509Certificate, tlsVersion: TLSProtocolVersion, rand?: Uint8Array): Promise<{
11
+ preMasterSecret: Uint8Array<ArrayBufferLike>;
12
+ encrypted: Uint8Array<ArrayBufferLike>;
13
+ }>;
@@ -0,0 +1,72 @@
1
+ import { crypto } from "../crypto/index.js";
2
+ import { SUPPORTED_NAMED_CURVE_MAP, SUPPORTED_RECORD_TYPE_MAP, SUPPORTED_SIGNATURE_ALGS_MAP, TLS_PROTOCOL_VERSION_MAP } from "./constants.js";
3
+ import { areUint8ArraysEqual, concatenateUint8Arrays } from "./generics.js";
4
+ import { expectReadWithLength, packWith3ByteLength, packWithLength } from "./packets.js";
5
+ export async function packClientCurveKeyShare(publicKey) {
6
+ return concatenateUint8Arrays([
7
+ new Uint8Array([SUPPORTED_RECORD_TYPE_MAP['CLIENT_KEY_SHARE']]),
8
+ packWith3ByteLength(
9
+ // pack with 1 byte length
10
+ packWithLength(await crypto.exportKey(publicKey)).slice(1))
11
+ ]);
12
+ }
13
+ export async function packClientRsaKeyShare(encPreMaster) {
14
+ return concatenateUint8Arrays([
15
+ new Uint8Array([SUPPORTED_RECORD_TYPE_MAP['CLIENT_KEY_SHARE']]),
16
+ packWith3ByteLength(packWithLength(encPreMaster))
17
+ ]);
18
+ }
19
+ export async function processServerKeyShare(data) {
20
+ const type = read(1)[0];
21
+ if (type !== 0x03) {
22
+ throw new Error('expected "named_group" key share');
23
+ }
24
+ const curveTypeBytes = read(2);
25
+ const curveTypeEntry = Object.entries(SUPPORTED_NAMED_CURVE_MAP)
26
+ .find(([, { identifier }]) => areUint8ArraysEqual(identifier, curveTypeBytes));
27
+ if (!curveTypeEntry) {
28
+ throw new Error(`unsupported curve type: ${curveTypeBytes}`);
29
+ }
30
+ const publicKeyType = curveTypeEntry[0];
31
+ const publicKeyBytes = readWLength(1);
32
+ const publicKey = await crypto.importKey(curveTypeEntry[1].algorithm, publicKeyBytes, 'public');
33
+ const signatureTypeBytes = read(2);
34
+ const signatureTypeEntry = Object.entries(SUPPORTED_SIGNATURE_ALGS_MAP)
35
+ .find(([, { identifier }]) => areUint8ArraysEqual(identifier, signatureTypeBytes));
36
+ if (!signatureTypeEntry) {
37
+ throw new Error(`unsupported signature type: ${signatureTypeBytes}`);
38
+ }
39
+ const signatureAlgorithm = signatureTypeEntry[0];
40
+ const signatureBytes = readWLength(2);
41
+ return {
42
+ publicKeyType,
43
+ publicKey,
44
+ signatureAlgorithm,
45
+ signatureBytes,
46
+ };
47
+ function read(bytes) {
48
+ const result = data.slice(0, bytes);
49
+ data = data.slice(bytes);
50
+ return result;
51
+ }
52
+ function readWLength(bytesLength = 2) {
53
+ const content = expectReadWithLength(data, bytesLength);
54
+ data = data.slice(content.length + bytesLength);
55
+ return content;
56
+ }
57
+ }
58
+ export async function createRsaPreMasterSecret(serverCert, tlsVersion, rand = crypto.randomBytes(46)) {
59
+ const preMasterSecret = concatenateUint8Arrays([
60
+ TLS_PROTOCOL_VERSION_MAP[tlsVersion],
61
+ rand
62
+ ]);
63
+ const servPubKey = serverCert.getPublicKey();
64
+ if (servPubKey.algorithm !== 'RSASSA-PKCS1-v1_5') {
65
+ throw new Error(`expected RSASSA-PKCS1-v1_5 cert, got ${servPubKey.algorithm}`);
66
+ }
67
+ const publicKey = await crypto
68
+ .importKey('RSA-PCKS1_5', servPubKey.buffer, 'public');
69
+ const rslt = await crypto
70
+ .asymmetricEncrypt('RSA-PCKS1_5', { data: preMasterSecret, publicKey });
71
+ return { preMasterSecret, encrypted: rslt };
72
+ }
@@ -0,0 +1,2 @@
1
+ import { KEY_UPDATE_TYPE_MAP } from './constants.ts';
2
+ export declare function packKeyUpdateRecord(type: keyof typeof KEY_UPDATE_TYPE_MAP): Uint8Array<ArrayBufferLike>;
@@ -0,0 +1,14 @@
1
+ import { KEY_UPDATE_TYPE_MAP, SUPPORTED_RECORD_TYPE_MAP } from "./constants.js";
2
+ import { concatenateUint8Arrays } from "./generics.js";
3
+ import { packWithLength } from "./packets.js";
4
+ export function packKeyUpdateRecord(type) {
5
+ const encoded = packWithLength(new Uint8Array([KEY_UPDATE_TYPE_MAP[type]]));
6
+ const packet = concatenateUint8Arrays([
7
+ new Uint8Array([
8
+ SUPPORTED_RECORD_TYPE_MAP.KEY_UPDATE,
9
+ 0x00,
10
+ ]),
11
+ encoded
12
+ ]);
13
+ return packet;
14
+ }
@@ -0,0 +1,2 @@
1
+ import type { Logger } from '../types/index.ts';
2
+ export declare const logger: Logger;
@@ -0,0 +1,15 @@
1
+ export const logger = {
2
+ info(...args) {
3
+ return console.info(...args);
4
+ },
5
+ debug(...args) {
6
+ return console.debug(...args);
7
+ },
8
+ trace: () => { },
9
+ warn(...args) {
10
+ return console.warn(...args);
11
+ },
12
+ error(...args) {
13
+ return console.error(...args);
14
+ }
15
+ };
@@ -0,0 +1,3 @@
1
+ export declare const makeQueue: () => {
2
+ enqueue<A extends any[], R, T extends (...args: A) => R>(code: T, ...args: A): Promise<R>;
3
+ };
@@ -0,0 +1,22 @@
1
+ export const makeQueue = () => {
2
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
3
+ let task = Promise.resolve();
4
+ return {
5
+ enqueue(code, ...args) {
6
+ task = (async () => {
7
+ // wait for the previous task to complete
8
+ // if there is an error, we swallow so as to not block the queue
9
+ try {
10
+ await task;
11
+ }
12
+ catch { }
13
+ // execute the current task
14
+ const result = await code(...args);
15
+ return result;
16
+ })();
17
+ // we replace the existing task, appending the new piece of execution to it
18
+ // so the next task will have to wait for this one to finish
19
+ return task;
20
+ },
21
+ };
22
+ };
@@ -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[];