lemon-tls 0.6.0 → 0.9.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 +253 -20
- package/index.d.ts +94 -6
- package/index.js +126 -0
- package/package.json +2 -7
- package/src/compat.js +316 -45
- package/src/crypto.js +33 -3
- package/src/dtls_session.js +477 -41
- package/src/dtls_socket.js +32 -0
- package/src/node_options.js +138 -0
- package/src/record.js +210 -27
- package/src/secure_context.js +49 -4
- package/src/session/cert_compression.js +157 -0
- package/src/session/ecdh.js +237 -10
- package/src/session/ech.js +715 -0
- package/src/session/hpke.js +414 -0
- package/src/session/message.js +44 -5
- package/src/session/signing.js +56 -3
- package/src/session/ticket.js +44 -28
- package/src/tls_session.js +3003 -217
- package/src/tls_socket.js +839 -149
- package/src/utils.js +75 -2
- package/src/wire.js +220 -13
package/src/session/ticket.js
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import * as crypto from 'node:crypto';
|
|
19
|
-
import {
|
|
19
|
+
import { timingSafeEqualU8 } from '../utils.js';
|
|
20
20
|
|
|
21
21
|
/* =========================================================================
|
|
22
22
|
* SERVER-SIDE ENCRYPTED BLOB
|
|
@@ -82,8 +82,11 @@ function decrypt_session_blob(blob, ticketKeys) {
|
|
|
82
82
|
|
|
83
83
|
let { key_name, aes_key } = split_ticket_keys(ticketKeys);
|
|
84
84
|
|
|
85
|
-
// Verify key_name matches (allows rotation — caller may try multiple keys)
|
|
86
|
-
|
|
85
|
+
// Verify key_name matches (allows rotation — caller may try multiple keys).
|
|
86
|
+
// Constant-time: utils.js names "ticket key_name" explicitly in the list
|
|
87
|
+
// of comparisons that must not short-circuit, but this call site was
|
|
88
|
+
// still using uint8Equal, which returns on the first differing byte.
|
|
89
|
+
if (!timingSafeEqualU8(blob_key_name, key_name)) return null;
|
|
87
90
|
|
|
88
91
|
let decipher = crypto.createDecipheriv('aes-256-gcm', aes_key, iv);
|
|
89
92
|
decipher.setAuthTag(tag);
|
|
@@ -119,6 +122,25 @@ function encode_client_session(state) {
|
|
|
119
122
|
* @param {Uint8Array|Buffer} blob
|
|
120
123
|
* @returns {Object|null}
|
|
121
124
|
*/
|
|
125
|
+
/**
|
|
126
|
+
* What an application may know of a client session blob ('session' event)
|
|
127
|
+
* without its secrets: { version, servername, alpn, created, expiresAt (ms),
|
|
128
|
+
* earlyData (whether its ticket allows 0-RTT) } — or null for a blob that is
|
|
129
|
+
* not one. For a cache to drop what has expired.
|
|
130
|
+
*/
|
|
131
|
+
function client_session_info(blob) {
|
|
132
|
+
let s = decode_client_session(blob);
|
|
133
|
+
if (!s) return null;
|
|
134
|
+
return {
|
|
135
|
+
version: s.v === 13 ? 'TLSv1.3' : s.v === 12 ? 'TLSv1.2' : null,
|
|
136
|
+
servername: s.sni != null ? s.sni : null,
|
|
137
|
+
alpn: s.alpn != null ? s.alpn : null,
|
|
138
|
+
created: typeof s.created === 'number' ? s.created : null,
|
|
139
|
+
expiresAt: typeof s.created === 'number' && typeof s.lifetime === 'number' ? s.created + s.lifetime * 1000 : null,
|
|
140
|
+
earlyData: !!(s.max_early_data > 0),
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
122
144
|
function decode_client_session(blob) {
|
|
123
145
|
try {
|
|
124
146
|
if (!blob || blob.length === 0) return null;
|
|
@@ -135,22 +157,19 @@ function decode_client_session(blob) {
|
|
|
135
157
|
|
|
136
158
|
/**
|
|
137
159
|
* Convert state object to JSON-safe form.
|
|
138
|
-
* Uint8Array/Buffer
|
|
160
|
+
* Uint8Array/Buffer values → { $b: base64 }, inside objects and arrays alike
|
|
161
|
+
* (a client blob holds the server's certificate chain: an array of DER).
|
|
139
162
|
*/
|
|
163
|
+
function serialize_value(v) {
|
|
164
|
+
if (v == null) return null;
|
|
165
|
+
if (v instanceof Uint8Array || Buffer.isBuffer(v)) return { $b: Buffer.from(v).toString('base64') };
|
|
166
|
+
if (Array.isArray(v)) return v.map(serialize_value);
|
|
167
|
+
if (typeof v === 'object') return serialize_state(v);
|
|
168
|
+
return v;
|
|
169
|
+
}
|
|
140
170
|
function serialize_state(state) {
|
|
141
171
|
let out = {};
|
|
142
|
-
for (let k in state)
|
|
143
|
-
let v = state[k];
|
|
144
|
-
if (v == null) {
|
|
145
|
-
out[k] = null;
|
|
146
|
-
} else if (v instanceof Uint8Array || Buffer.isBuffer(v)) {
|
|
147
|
-
out[k] = { $b: Buffer.from(v).toString('base64') };
|
|
148
|
-
} else if (typeof v === 'object' && !Array.isArray(v)) {
|
|
149
|
-
out[k] = serialize_state(v);
|
|
150
|
-
} else {
|
|
151
|
-
out[k] = v;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
172
|
+
for (let k in state) out[k] = serialize_value(state[k]);
|
|
154
173
|
return out;
|
|
155
174
|
}
|
|
156
175
|
|
|
@@ -158,21 +177,17 @@ function serialize_state(state) {
|
|
|
158
177
|
* Convert JSON-decoded form back to state object.
|
|
159
178
|
* { $b: base64 } → Uint8Array
|
|
160
179
|
*/
|
|
180
|
+
function deserialize_value(v) {
|
|
181
|
+
if (v == null) return null;
|
|
182
|
+
if (Array.isArray(v)) return v.map(deserialize_value);
|
|
183
|
+
if (typeof v === 'object' && typeof v.$b === 'string' && Object.keys(v).length === 1) return new Uint8Array(Buffer.from(v.$b, 'base64'));
|
|
184
|
+
if (typeof v === 'object') return deserialize_state(v);
|
|
185
|
+
return v;
|
|
186
|
+
}
|
|
161
187
|
function deserialize_state(obj) {
|
|
162
188
|
if (obj == null) return null;
|
|
163
189
|
let out = {};
|
|
164
|
-
for (let k in obj)
|
|
165
|
-
let v = obj[k];
|
|
166
|
-
if (v == null) {
|
|
167
|
-
out[k] = null;
|
|
168
|
-
} else if (typeof v === 'object' && typeof v.$b === 'string' && Object.keys(v).length === 1) {
|
|
169
|
-
out[k] = new Uint8Array(Buffer.from(v.$b, 'base64'));
|
|
170
|
-
} else if (typeof v === 'object' && !Array.isArray(v)) {
|
|
171
|
-
out[k] = deserialize_state(v);
|
|
172
|
-
} else {
|
|
173
|
-
out[k] = v;
|
|
174
|
-
}
|
|
175
|
-
}
|
|
190
|
+
for (let k in obj) out[k] = deserialize_value(obj[k]);
|
|
176
191
|
return out;
|
|
177
192
|
}
|
|
178
193
|
|
|
@@ -180,6 +195,7 @@ export {
|
|
|
180
195
|
encrypt_session_blob,
|
|
181
196
|
decrypt_session_blob,
|
|
182
197
|
encode_client_session,
|
|
198
|
+
client_session_info,
|
|
183
199
|
decode_client_session,
|
|
184
200
|
split_ticket_keys,
|
|
185
201
|
};
|