@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.
- package/README.md +221 -0
- package/lib/crypto/common.d.ts +3 -0
- package/lib/crypto/common.js +26 -0
- package/lib/crypto/index.d.ts +3 -0
- package/lib/crypto/index.js +4 -0
- package/lib/crypto/insecure-rand.d.ts +1 -0
- package/lib/crypto/insecure-rand.js +9 -0
- package/lib/crypto/pure-js.d.ts +2 -0
- package/lib/crypto/pure-js.js +144 -0
- package/lib/crypto/webcrypto.d.ts +3 -0
- package/lib/crypto/webcrypto.js +310 -0
- package/lib/index.d.ts +4 -0
- package/lib/index.js +4 -0
- package/lib/make-tls-client.d.ts +74 -0
- package/lib/make-tls-client.js +657 -0
- package/lib/scripts/build-jsc.d.ts +1 -0
- package/lib/scripts/build-jsc.js +20 -0
- package/lib/scripts/ca-template.d.ts +5 -0
- package/lib/scripts/ca-template.js +6 -0
- package/lib/scripts/fallbacks/crypto.d.ts +4 -0
- package/lib/scripts/fallbacks/crypto.js +2 -0
- package/lib/scripts/handshake.d.ts +1 -0
- package/lib/scripts/handshake.js +61 -0
- package/lib/scripts/jsc.d.ts +28 -0
- package/lib/scripts/jsc.js +92 -0
- package/lib/scripts/update-ca-certs.d.ts +1 -0
- package/lib/scripts/update-ca-certs.js +29 -0
- package/lib/types/crypto.d.ts +62 -0
- package/lib/types/crypto.js +1 -0
- package/lib/types/index.d.ts +15 -0
- package/lib/types/index.js +4 -0
- package/lib/types/logger.d.ts +6 -0
- package/lib/types/logger.js +1 -0
- package/lib/types/tls.d.ts +141 -0
- package/lib/types/tls.js +1 -0
- package/lib/types/x509.d.ts +32 -0
- package/lib/types/x509.js +1 -0
- package/lib/utils/additional-root-cas.d.ts +1 -0
- package/lib/utils/additional-root-cas.js +197 -0
- package/lib/utils/client-hello.d.ts +23 -0
- package/lib/utils/client-hello.js +167 -0
- package/lib/utils/constants.d.ts +239 -0
- package/lib/utils/constants.js +244 -0
- package/lib/utils/decryption-utils.d.ts +64 -0
- package/lib/utils/decryption-utils.js +166 -0
- package/lib/utils/finish-messages.d.ts +11 -0
- package/lib/utils/finish-messages.js +49 -0
- package/lib/utils/generics.d.ts +35 -0
- package/lib/utils/generics.js +146 -0
- package/lib/utils/index.d.ts +18 -0
- package/lib/utils/index.js +18 -0
- package/lib/utils/key-share.d.ts +13 -0
- package/lib/utils/key-share.js +72 -0
- package/lib/utils/key-update.d.ts +2 -0
- package/lib/utils/key-update.js +14 -0
- package/lib/utils/logger.d.ts +2 -0
- package/lib/utils/logger.js +15 -0
- package/lib/utils/make-queue.d.ts +3 -0
- package/lib/utils/make-queue.js +22 -0
- package/lib/utils/mozilla-root-cas.d.ts +5 -0
- package/lib/utils/mozilla-root-cas.js +4459 -0
- package/lib/utils/packets.d.ts +51 -0
- package/lib/utils/packets.js +148 -0
- package/lib/utils/parse-alert.d.ts +7 -0
- package/lib/utils/parse-alert.js +28 -0
- package/lib/utils/parse-certificate.d.ts +29 -0
- package/lib/utils/parse-certificate.js +188 -0
- package/lib/utils/parse-client-hello.d.ts +11 -0
- package/lib/utils/parse-client-hello.js +39 -0
- package/lib/utils/parse-extensions.d.ts +11 -0
- package/lib/utils/parse-extensions.js +74 -0
- package/lib/utils/parse-server-hello.d.ts +10 -0
- package/lib/utils/parse-server-hello.js +52 -0
- package/lib/utils/session-ticket.d.ts +17 -0
- package/lib/utils/session-ticket.js +51 -0
- package/lib/utils/wrapped-record.d.ts +25 -0
- package/lib/utils/wrapped-record.js +191 -0
- package/lib/utils/x509.d.ts +5 -0
- package/lib/utils/x509.js +124 -0
- 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,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,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,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
|
+
};
|