@agentdance/node-webrtc-dtls 1.0.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/dist/certificate.d.ts +31 -0
- package/dist/certificate.d.ts.map +1 -0
- package/dist/certificate.js +199 -0
- package/dist/certificate.js.map +1 -0
- package/dist/crypto.d.ts +89 -0
- package/dist/crypto.d.ts.map +1 -0
- package/dist/crypto.js +193 -0
- package/dist/crypto.js.map +1 -0
- package/dist/handshake.d.ts +113 -0
- package/dist/handshake.d.ts.map +1 -0
- package/dist/handshake.js +420 -0
- package/dist/handshake.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/record.d.ts +19 -0
- package/dist/record.d.ts.map +1 -0
- package/dist/record.js +108 -0
- package/dist/record.js.map +1 -0
- package/dist/state.d.ts +44 -0
- package/dist/state.d.ts.map +1 -0
- package/dist/state.js +10 -0
- package/dist/state.js.map +1 -0
- package/dist/transport.d.ts +87 -0
- package/dist/transport.d.ts.map +1 -0
- package/dist/transport.js +559 -0
- package/dist/transport.js.map +1 -0
- package/dist/types.d.ts +51 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +44 -0
- package/dist/types.js.map +1 -0
- package/package.json +56 -0
- package/src/certificate.ts +253 -0
- package/src/crypto.ts +279 -0
- package/src/handshake.ts +544 -0
- package/src/index.ts +72 -0
- package/src/record.ts +127 -0
- package/src/state.ts +59 -0
- package/src/transport.ts +692 -0
- package/src/types.ts +57 -0
package/src/handshake.ts
ADDED
|
@@ -0,0 +1,544 @@
|
|
|
1
|
+
// DTLS Handshake message types and encode/decode (RFC 6347 Section 4.2)
|
|
2
|
+
//
|
|
3
|
+
// Handshake message header (12 bytes):
|
|
4
|
+
// 1 byte HandshakeType
|
|
5
|
+
// 3 bytes length
|
|
6
|
+
// 2 bytes message_seq
|
|
7
|
+
// 3 bytes fragment_offset
|
|
8
|
+
// 3 bytes fragment_length
|
|
9
|
+
// [body]
|
|
10
|
+
|
|
11
|
+
import { DTLS_VERSION_1_2, type DtlsVersion } from './types.js';
|
|
12
|
+
|
|
13
|
+
export enum HandshakeType {
|
|
14
|
+
HelloRequest = 0,
|
|
15
|
+
ClientHello = 1,
|
|
16
|
+
ServerHello = 2,
|
|
17
|
+
HelloVerifyRequest = 3,
|
|
18
|
+
Certificate = 11,
|
|
19
|
+
ServerKeyExchange = 12,
|
|
20
|
+
CertificateRequest = 13,
|
|
21
|
+
ServerHelloDone = 14,
|
|
22
|
+
CertificateVerify = 15,
|
|
23
|
+
ClientKeyExchange = 16,
|
|
24
|
+
Finished = 20,
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface HandshakeMessage {
|
|
28
|
+
msgType: HandshakeType;
|
|
29
|
+
length: number;
|
|
30
|
+
messageSeq: number;
|
|
31
|
+
fragmentOffset: number;
|
|
32
|
+
fragmentLength: number;
|
|
33
|
+
body: Buffer;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface TlsExtension {
|
|
37
|
+
type: number;
|
|
38
|
+
data: Buffer;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface ClientHello {
|
|
42
|
+
clientVersion: DtlsVersion;
|
|
43
|
+
random: Buffer; // 32 bytes
|
|
44
|
+
sessionId: Buffer; // 0-32 bytes
|
|
45
|
+
cookie: Buffer; // 0-255 bytes
|
|
46
|
+
cipherSuites: number[]; // 2 bytes each
|
|
47
|
+
compressionMethods: number[];
|
|
48
|
+
extensions: TlsExtension[];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface ServerHello {
|
|
52
|
+
serverVersion: DtlsVersion;
|
|
53
|
+
random: Buffer;
|
|
54
|
+
sessionId: Buffer;
|
|
55
|
+
cipherSuite: number;
|
|
56
|
+
compressionMethod: number;
|
|
57
|
+
extensions: TlsExtension[];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface HelloVerifyRequest {
|
|
61
|
+
serverVersion: DtlsVersion;
|
|
62
|
+
cookie: Buffer;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface ServerKeyExchange {
|
|
66
|
+
// ECParameters curve_params (named_curve, 2 bytes)
|
|
67
|
+
// ECPoint public_key (1-byte length + bytes)
|
|
68
|
+
// Signature signature (2-byte hash/sig algos + 2-byte length + bytes)
|
|
69
|
+
curveType: number; // 3 = named_curve
|
|
70
|
+
namedCurve: number; // 23 = secp256r1 / P-256
|
|
71
|
+
publicKey: Buffer; // uncompressed EC point (65 bytes)
|
|
72
|
+
signatureAlgorithm: { hash: number; signature: number };
|
|
73
|
+
signature: Buffer;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface ClientKeyExchange {
|
|
77
|
+
publicKey: Buffer; // uncompressed EC point
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Cipher suite values
|
|
81
|
+
export const CipherSuites = {
|
|
82
|
+
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: 0xc02b,
|
|
83
|
+
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: 0xc02f,
|
|
84
|
+
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: 0xc02c,
|
|
85
|
+
} as const;
|
|
86
|
+
|
|
87
|
+
// Extension type constants
|
|
88
|
+
export const ExtensionType = {
|
|
89
|
+
UseSrtp: 0x000e,
|
|
90
|
+
SupportedGroups: 0x000a, // formerly elliptic_curves
|
|
91
|
+
EcPointFormats: 0x000b,
|
|
92
|
+
SignatureAlgorithms: 0x000d,
|
|
93
|
+
RenegotiationInfo: 0xff01,
|
|
94
|
+
} as const;
|
|
95
|
+
|
|
96
|
+
// Named curves
|
|
97
|
+
export const NamedCurve = {
|
|
98
|
+
secp256r1: 23,
|
|
99
|
+
secp384r1: 24,
|
|
100
|
+
} as const;
|
|
101
|
+
|
|
102
|
+
// SRTP protection profiles (RFC 5764)
|
|
103
|
+
export const SrtpProtectionProfile = {
|
|
104
|
+
SRTP_AES128_CM_SHA1_80: 0x0001,
|
|
105
|
+
SRTP_AES128_CM_SHA1_32: 0x0002,
|
|
106
|
+
} as const;
|
|
107
|
+
|
|
108
|
+
// ─── Handshake Message ────────────────────────────────────────────────────────
|
|
109
|
+
|
|
110
|
+
const HANDSHAKE_HEADER_SIZE = 12;
|
|
111
|
+
|
|
112
|
+
export function encodeHandshakeMessage(msg: HandshakeMessage): Buffer {
|
|
113
|
+
const body = msg.body;
|
|
114
|
+
const buf = Buffer.allocUnsafe(HANDSHAKE_HEADER_SIZE + body.length);
|
|
115
|
+
let off = 0;
|
|
116
|
+
|
|
117
|
+
buf.writeUInt8(msg.msgType, off);
|
|
118
|
+
off += 1;
|
|
119
|
+
// 3-byte length
|
|
120
|
+
writeUInt24BE(buf, off, body.length);
|
|
121
|
+
off += 3;
|
|
122
|
+
buf.writeUInt16BE(msg.messageSeq, off);
|
|
123
|
+
off += 2;
|
|
124
|
+
// 3-byte fragment offset
|
|
125
|
+
writeUInt24BE(buf, off, msg.fragmentOffset);
|
|
126
|
+
off += 3;
|
|
127
|
+
// 3-byte fragment length
|
|
128
|
+
writeUInt24BE(buf, off, body.length);
|
|
129
|
+
off += 3;
|
|
130
|
+
|
|
131
|
+
body.copy(buf, off);
|
|
132
|
+
return buf;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function decodeHandshakeMessage(buf: Buffer): HandshakeMessage {
|
|
136
|
+
if (buf.length < HANDSHAKE_HEADER_SIZE) {
|
|
137
|
+
throw new Error(`Buffer too small for handshake message: ${buf.length}`);
|
|
138
|
+
}
|
|
139
|
+
let off = 0;
|
|
140
|
+
const msgType = buf.readUInt8(off) as HandshakeType;
|
|
141
|
+
off += 1;
|
|
142
|
+
const length = readUInt24BE(buf, off);
|
|
143
|
+
off += 3;
|
|
144
|
+
const messageSeq = buf.readUInt16BE(off);
|
|
145
|
+
off += 2;
|
|
146
|
+
const fragmentOffset = readUInt24BE(buf, off);
|
|
147
|
+
off += 3;
|
|
148
|
+
const fragmentLength = readUInt24BE(buf, off);
|
|
149
|
+
off += 3;
|
|
150
|
+
|
|
151
|
+
if (buf.length < off + fragmentLength) {
|
|
152
|
+
throw new Error('Handshake message body truncated');
|
|
153
|
+
}
|
|
154
|
+
const body = Buffer.from(buf.subarray(off, off + fragmentLength));
|
|
155
|
+
|
|
156
|
+
return { msgType, length, messageSeq, fragmentOffset, fragmentLength, body };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// ─── ClientHello ──────────────────────────────────────────────────────────────
|
|
160
|
+
|
|
161
|
+
export function encodeClientHello(hello: ClientHello): Buffer {
|
|
162
|
+
const parts: Buffer[] = [];
|
|
163
|
+
|
|
164
|
+
// Version (2 bytes)
|
|
165
|
+
const ver = Buffer.allocUnsafe(2);
|
|
166
|
+
ver.writeUInt8(hello.clientVersion.major, 0);
|
|
167
|
+
ver.writeUInt8(hello.clientVersion.minor, 1);
|
|
168
|
+
parts.push(ver);
|
|
169
|
+
|
|
170
|
+
// Random (32 bytes)
|
|
171
|
+
parts.push(hello.random);
|
|
172
|
+
|
|
173
|
+
// Session ID
|
|
174
|
+
const sid = Buffer.allocUnsafe(1 + hello.sessionId.length);
|
|
175
|
+
sid.writeUInt8(hello.sessionId.length, 0);
|
|
176
|
+
hello.sessionId.copy(sid, 1);
|
|
177
|
+
parts.push(sid);
|
|
178
|
+
|
|
179
|
+
// Cookie
|
|
180
|
+
const ck = Buffer.allocUnsafe(1 + hello.cookie.length);
|
|
181
|
+
ck.writeUInt8(hello.cookie.length, 0);
|
|
182
|
+
hello.cookie.copy(ck, 1);
|
|
183
|
+
parts.push(ck);
|
|
184
|
+
|
|
185
|
+
// Cipher suites
|
|
186
|
+
const csLen = hello.cipherSuites.length * 2;
|
|
187
|
+
const cs = Buffer.allocUnsafe(2 + csLen);
|
|
188
|
+
cs.writeUInt16BE(csLen, 0);
|
|
189
|
+
for (let i = 0; i < hello.cipherSuites.length; i++) {
|
|
190
|
+
cs.writeUInt16BE(hello.cipherSuites[i]!, 2 + i * 2);
|
|
191
|
+
}
|
|
192
|
+
parts.push(cs);
|
|
193
|
+
|
|
194
|
+
// Compression methods
|
|
195
|
+
const cm = Buffer.allocUnsafe(1 + hello.compressionMethods.length);
|
|
196
|
+
cm.writeUInt8(hello.compressionMethods.length, 0);
|
|
197
|
+
for (let i = 0; i < hello.compressionMethods.length; i++) {
|
|
198
|
+
cm.writeUInt8(hello.compressionMethods[i]!, 1 + i);
|
|
199
|
+
}
|
|
200
|
+
parts.push(cm);
|
|
201
|
+
|
|
202
|
+
// Extensions
|
|
203
|
+
if (hello.extensions.length > 0) {
|
|
204
|
+
const extsBuf = encodeExtensions(hello.extensions);
|
|
205
|
+
parts.push(extsBuf);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return Buffer.concat(parts);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export function decodeClientHello(buf: Buffer): ClientHello {
|
|
212
|
+
let off = 0;
|
|
213
|
+
|
|
214
|
+
const major = buf.readUInt8(off);
|
|
215
|
+
off += 1;
|
|
216
|
+
const minor = buf.readUInt8(off);
|
|
217
|
+
off += 1;
|
|
218
|
+
|
|
219
|
+
const random = Buffer.from(buf.subarray(off, off + 32));
|
|
220
|
+
off += 32;
|
|
221
|
+
|
|
222
|
+
const sidLen = buf.readUInt8(off);
|
|
223
|
+
off += 1;
|
|
224
|
+
const sessionId = Buffer.from(buf.subarray(off, off + sidLen));
|
|
225
|
+
off += sidLen;
|
|
226
|
+
|
|
227
|
+
const cookieLen = buf.readUInt8(off);
|
|
228
|
+
off += 1;
|
|
229
|
+
const cookie = Buffer.from(buf.subarray(off, off + cookieLen));
|
|
230
|
+
off += cookieLen;
|
|
231
|
+
|
|
232
|
+
const csLen = buf.readUInt16BE(off);
|
|
233
|
+
off += 2;
|
|
234
|
+
const cipherSuites: number[] = [];
|
|
235
|
+
for (let i = 0; i < csLen; i += 2) {
|
|
236
|
+
cipherSuites.push(buf.readUInt16BE(off + i));
|
|
237
|
+
}
|
|
238
|
+
off += csLen;
|
|
239
|
+
|
|
240
|
+
const cmLen = buf.readUInt8(off);
|
|
241
|
+
off += 1;
|
|
242
|
+
const compressionMethods: number[] = [];
|
|
243
|
+
for (let i = 0; i < cmLen; i++) {
|
|
244
|
+
compressionMethods.push(buf.readUInt8(off + i));
|
|
245
|
+
}
|
|
246
|
+
off += cmLen;
|
|
247
|
+
|
|
248
|
+
let extensions: TlsExtension[] = [];
|
|
249
|
+
if (off < buf.length) {
|
|
250
|
+
extensions = decodeExtensions(buf, off);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return {
|
|
254
|
+
clientVersion: { major, minor },
|
|
255
|
+
random,
|
|
256
|
+
sessionId,
|
|
257
|
+
cookie,
|
|
258
|
+
cipherSuites,
|
|
259
|
+
compressionMethods,
|
|
260
|
+
extensions,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// ─── ServerHello ──────────────────────────────────────────────────────────────
|
|
265
|
+
|
|
266
|
+
export function encodeServerHello(hello: ServerHello): Buffer {
|
|
267
|
+
const parts: Buffer[] = [];
|
|
268
|
+
|
|
269
|
+
const ver = Buffer.allocUnsafe(2);
|
|
270
|
+
ver.writeUInt8(hello.serverVersion.major, 0);
|
|
271
|
+
ver.writeUInt8(hello.serverVersion.minor, 1);
|
|
272
|
+
parts.push(ver);
|
|
273
|
+
|
|
274
|
+
parts.push(hello.random);
|
|
275
|
+
|
|
276
|
+
const sid = Buffer.allocUnsafe(1 + hello.sessionId.length);
|
|
277
|
+
sid.writeUInt8(hello.sessionId.length, 0);
|
|
278
|
+
hello.sessionId.copy(sid, 1);
|
|
279
|
+
parts.push(sid);
|
|
280
|
+
|
|
281
|
+
const cs = Buffer.allocUnsafe(2);
|
|
282
|
+
cs.writeUInt16BE(hello.cipherSuite, 0);
|
|
283
|
+
parts.push(cs);
|
|
284
|
+
|
|
285
|
+
const cm = Buffer.allocUnsafe(1);
|
|
286
|
+
cm.writeUInt8(hello.compressionMethod, 0);
|
|
287
|
+
parts.push(cm);
|
|
288
|
+
|
|
289
|
+
if (hello.extensions.length > 0) {
|
|
290
|
+
parts.push(encodeExtensions(hello.extensions));
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return Buffer.concat(parts);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
export function decodeServerHello(buf: Buffer): ServerHello {
|
|
297
|
+
let off = 0;
|
|
298
|
+
|
|
299
|
+
const major = buf.readUInt8(off);
|
|
300
|
+
off += 1;
|
|
301
|
+
const minor = buf.readUInt8(off);
|
|
302
|
+
off += 1;
|
|
303
|
+
|
|
304
|
+
const random = Buffer.from(buf.subarray(off, off + 32));
|
|
305
|
+
off += 32;
|
|
306
|
+
|
|
307
|
+
const sidLen = buf.readUInt8(off);
|
|
308
|
+
off += 1;
|
|
309
|
+
const sessionId = Buffer.from(buf.subarray(off, off + sidLen));
|
|
310
|
+
off += sidLen;
|
|
311
|
+
|
|
312
|
+
const cipherSuite = buf.readUInt16BE(off);
|
|
313
|
+
off += 2;
|
|
314
|
+
const compressionMethod = buf.readUInt8(off);
|
|
315
|
+
off += 1;
|
|
316
|
+
|
|
317
|
+
let extensions: TlsExtension[] = [];
|
|
318
|
+
if (off < buf.length) {
|
|
319
|
+
extensions = decodeExtensions(buf, off);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return {
|
|
323
|
+
serverVersion: { major, minor },
|
|
324
|
+
random,
|
|
325
|
+
sessionId,
|
|
326
|
+
cipherSuite,
|
|
327
|
+
compressionMethod,
|
|
328
|
+
extensions,
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// ─── HelloVerifyRequest ───────────────────────────────────────────────────────
|
|
333
|
+
|
|
334
|
+
export function encodeHelloVerifyRequest(hvr: HelloVerifyRequest): Buffer {
|
|
335
|
+
const buf = Buffer.allocUnsafe(3 + hvr.cookie.length);
|
|
336
|
+
buf.writeUInt8(hvr.serverVersion.major, 0);
|
|
337
|
+
buf.writeUInt8(hvr.serverVersion.minor, 1);
|
|
338
|
+
buf.writeUInt8(hvr.cookie.length, 2);
|
|
339
|
+
hvr.cookie.copy(buf, 3);
|
|
340
|
+
return buf;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
export function decodeHelloVerifyRequest(buf: Buffer): HelloVerifyRequest {
|
|
344
|
+
const major = buf.readUInt8(0);
|
|
345
|
+
const minor = buf.readUInt8(1);
|
|
346
|
+
const cookieLen = buf.readUInt8(2);
|
|
347
|
+
const cookie = Buffer.from(buf.subarray(3, 3 + cookieLen));
|
|
348
|
+
return { serverVersion: { major, minor }, cookie };
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// ─── ServerKeyExchange ────────────────────────────────────────────────────────
|
|
352
|
+
|
|
353
|
+
export function encodeServerKeyExchange(ske: ServerKeyExchange): Buffer {
|
|
354
|
+
// curve_type (1) + named_curve (2) + point_len (1) + point + hash_algo (1) + sig_algo (1) + sig_len (2) + sig
|
|
355
|
+
const pkLen = ske.publicKey.length;
|
|
356
|
+
const sigLen = ske.signature.length;
|
|
357
|
+
const buf = Buffer.allocUnsafe(1 + 2 + 1 + pkLen + 1 + 1 + 2 + sigLen);
|
|
358
|
+
let off = 0;
|
|
359
|
+
buf.writeUInt8(ske.curveType, off++);
|
|
360
|
+
buf.writeUInt16BE(ske.namedCurve, off);
|
|
361
|
+
off += 2;
|
|
362
|
+
buf.writeUInt8(pkLen, off++);
|
|
363
|
+
ske.publicKey.copy(buf, off);
|
|
364
|
+
off += pkLen;
|
|
365
|
+
buf.writeUInt8(ske.signatureAlgorithm.hash, off++);
|
|
366
|
+
buf.writeUInt8(ske.signatureAlgorithm.signature, off++);
|
|
367
|
+
buf.writeUInt16BE(sigLen, off);
|
|
368
|
+
off += 2;
|
|
369
|
+
ske.signature.copy(buf, off);
|
|
370
|
+
return buf;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
export function decodeServerKeyExchange(buf: Buffer): ServerKeyExchange {
|
|
374
|
+
let off = 0;
|
|
375
|
+
const curveType = buf.readUInt8(off++);
|
|
376
|
+
const namedCurve = buf.readUInt16BE(off);
|
|
377
|
+
off += 2;
|
|
378
|
+
const pkLen = buf.readUInt8(off++);
|
|
379
|
+
const publicKey = Buffer.from(buf.subarray(off, off + pkLen));
|
|
380
|
+
off += pkLen;
|
|
381
|
+
const hash = buf.readUInt8(off++);
|
|
382
|
+
const signature_ = buf.readUInt8(off++);
|
|
383
|
+
const sigLen = buf.readUInt16BE(off);
|
|
384
|
+
off += 2;
|
|
385
|
+
const signature = Buffer.from(buf.subarray(off, off + sigLen));
|
|
386
|
+
return {
|
|
387
|
+
curveType,
|
|
388
|
+
namedCurve,
|
|
389
|
+
publicKey,
|
|
390
|
+
signatureAlgorithm: { hash, signature: signature_ },
|
|
391
|
+
signature,
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// ─── Certificate ──────────────────────────────────────────────────────────────
|
|
396
|
+
|
|
397
|
+
export function encodeCertificate(certDer: Buffer): Buffer {
|
|
398
|
+
// CertificateList: 3-byte list length, then each cert: 3-byte cert length + DER
|
|
399
|
+
const certLen = certDer.length;
|
|
400
|
+
const listLen = 3 + certLen;
|
|
401
|
+
const buf = Buffer.allocUnsafe(3 + listLen);
|
|
402
|
+
writeUInt24BE(buf, 0, listLen);
|
|
403
|
+
writeUInt24BE(buf, 3, certLen);
|
|
404
|
+
certDer.copy(buf, 6);
|
|
405
|
+
return buf;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
export function decodeCertificate(buf: Buffer): Buffer[] {
|
|
409
|
+
let off = 0;
|
|
410
|
+
const listLen = readUInt24BE(buf, off);
|
|
411
|
+
off += 3;
|
|
412
|
+
const end = off + listLen;
|
|
413
|
+
const certs: Buffer[] = [];
|
|
414
|
+
while (off < end) {
|
|
415
|
+
const certLen = readUInt24BE(buf, off);
|
|
416
|
+
off += 3;
|
|
417
|
+
certs.push(Buffer.from(buf.subarray(off, off + certLen)));
|
|
418
|
+
off += certLen;
|
|
419
|
+
}
|
|
420
|
+
return certs;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// ─── ClientKeyExchange ────────────────────────────────────────────────────────
|
|
424
|
+
|
|
425
|
+
export function encodeClientKeyExchange(cke: ClientKeyExchange): Buffer {
|
|
426
|
+
const buf = Buffer.allocUnsafe(1 + cke.publicKey.length);
|
|
427
|
+
buf.writeUInt8(cke.publicKey.length, 0);
|
|
428
|
+
cke.publicKey.copy(buf, 1);
|
|
429
|
+
return buf;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
export function decodeClientKeyExchange(buf: Buffer): ClientKeyExchange {
|
|
433
|
+
const pkLen = buf.readUInt8(0);
|
|
434
|
+
const publicKey = Buffer.from(buf.subarray(1, 1 + pkLen));
|
|
435
|
+
return { publicKey };
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// ─── Extensions ───────────────────────────────────────────────────────────────
|
|
439
|
+
|
|
440
|
+
function encodeExtensions(extensions: TlsExtension[]): Buffer {
|
|
441
|
+
const parts: Buffer[] = [];
|
|
442
|
+
for (const ext of extensions) {
|
|
443
|
+
const hdr = Buffer.allocUnsafe(4);
|
|
444
|
+
hdr.writeUInt16BE(ext.type, 0);
|
|
445
|
+
hdr.writeUInt16BE(ext.data.length, 2);
|
|
446
|
+
parts.push(hdr);
|
|
447
|
+
parts.push(ext.data);
|
|
448
|
+
}
|
|
449
|
+
const extsBuf = Buffer.concat(parts);
|
|
450
|
+
const len = Buffer.allocUnsafe(2);
|
|
451
|
+
len.writeUInt16BE(extsBuf.length, 0);
|
|
452
|
+
return Buffer.concat([len, extsBuf]);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
function decodeExtensions(buf: Buffer, off: number): TlsExtension[] {
|
|
456
|
+
if (off + 2 > buf.length) return [];
|
|
457
|
+
const totalLen = buf.readUInt16BE(off);
|
|
458
|
+
off += 2;
|
|
459
|
+
const end = off + totalLen;
|
|
460
|
+
const extensions: TlsExtension[] = [];
|
|
461
|
+
while (off < end && off + 4 <= buf.length) {
|
|
462
|
+
const type = buf.readUInt16BE(off);
|
|
463
|
+
off += 2;
|
|
464
|
+
const dataLen = buf.readUInt16BE(off);
|
|
465
|
+
off += 2;
|
|
466
|
+
const data = Buffer.from(buf.subarray(off, off + dataLen));
|
|
467
|
+
off += dataLen;
|
|
468
|
+
extensions.push({ type, data });
|
|
469
|
+
}
|
|
470
|
+
return extensions;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// ─── Extension builders ───────────────────────────────────────────────────────
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Build use_srtp extension data (RFC 5764)
|
|
477
|
+
* protection_profiles: list of 2-byte profile IDs
|
|
478
|
+
*/
|
|
479
|
+
export function buildUseSrtpExtension(profiles: number[]): Buffer {
|
|
480
|
+
// 2-byte profiles list length + 2 bytes each + 1 byte MKI length (0)
|
|
481
|
+
const listLen = profiles.length * 2;
|
|
482
|
+
const buf = Buffer.allocUnsafe(2 + listLen + 1);
|
|
483
|
+
buf.writeUInt16BE(listLen, 0);
|
|
484
|
+
for (let i = 0; i < profiles.length; i++) {
|
|
485
|
+
buf.writeUInt16BE(profiles[i]!, 2 + i * 2);
|
|
486
|
+
}
|
|
487
|
+
buf.writeUInt8(0, 2 + listLen); // MKI length = 0
|
|
488
|
+
return buf;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
export function parseSrtpProfiles(data: Buffer): number[] {
|
|
492
|
+
if (data.length < 2) return [];
|
|
493
|
+
const listLen = data.readUInt16BE(0);
|
|
494
|
+
const profiles: number[] = [];
|
|
495
|
+
for (let i = 0; i < listLen; i += 2) {
|
|
496
|
+
if (2 + i + 1 < data.length) {
|
|
497
|
+
profiles.push(data.readUInt16BE(2 + i));
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
return profiles;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* Build supported_groups extension (named curves)
|
|
505
|
+
*/
|
|
506
|
+
export function buildSupportedGroupsExtension(curves: number[]): Buffer {
|
|
507
|
+
const listLen = curves.length * 2;
|
|
508
|
+
const buf = Buffer.allocUnsafe(2 + listLen);
|
|
509
|
+
buf.writeUInt16BE(listLen, 0);
|
|
510
|
+
for (let i = 0; i < curves.length; i++) {
|
|
511
|
+
buf.writeUInt16BE(curves[i]!, 2 + i * 2);
|
|
512
|
+
}
|
|
513
|
+
return buf;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* Build signature_algorithms extension
|
|
518
|
+
*/
|
|
519
|
+
export function buildSignatureAlgorithmsExtension(
|
|
520
|
+
pairs: Array<{ hash: number; sig: number }>,
|
|
521
|
+
): Buffer {
|
|
522
|
+
const listLen = pairs.length * 2;
|
|
523
|
+
const buf = Buffer.allocUnsafe(2 + listLen);
|
|
524
|
+
buf.writeUInt16BE(listLen, 0);
|
|
525
|
+
for (let i = 0; i < pairs.length; i++) {
|
|
526
|
+
buf.writeUInt8(pairs[i]!.hash, 2 + i * 2);
|
|
527
|
+
buf.writeUInt8(pairs[i]!.sig, 2 + i * 2 + 1);
|
|
528
|
+
}
|
|
529
|
+
return buf;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// ─── Utility ──────────────────────────────────────────────────────────────────
|
|
533
|
+
|
|
534
|
+
function writeUInt24BE(buf: Buffer, offset: number, value: number): void {
|
|
535
|
+
buf.writeUInt8((value >> 16) & 0xff, offset);
|
|
536
|
+
buf.writeUInt8((value >> 8) & 0xff, offset + 1);
|
|
537
|
+
buf.writeUInt8(value & 0xff, offset + 2);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
function readUInt24BE(buf: Buffer, offset: number): number {
|
|
541
|
+
return (buf.readUInt8(offset) << 16) | (buf.readUInt8(offset + 1) << 8) | buf.readUInt8(offset + 2);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
export { DTLS_VERSION_1_2 };
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// @agentdance/node-webrtc-dtls – DTLS 1.2 implementation
|
|
2
|
+
// Public API
|
|
3
|
+
|
|
4
|
+
export { ContentType, DTLS_VERSION_1_2, type DtlsRecord, type DtlsVersion } from './types.js';
|
|
5
|
+
export {
|
|
6
|
+
encodeRecord,
|
|
7
|
+
decodeRecords,
|
|
8
|
+
isDtlsPacket,
|
|
9
|
+
makeRecord,
|
|
10
|
+
} from './record.js';
|
|
11
|
+
export {
|
|
12
|
+
HandshakeType,
|
|
13
|
+
CipherSuites,
|
|
14
|
+
ExtensionType,
|
|
15
|
+
NamedCurve,
|
|
16
|
+
SrtpProtectionProfile,
|
|
17
|
+
type HandshakeMessage,
|
|
18
|
+
type ClientHello,
|
|
19
|
+
type ServerHello,
|
|
20
|
+
type HelloVerifyRequest,
|
|
21
|
+
type ServerKeyExchange,
|
|
22
|
+
type ClientKeyExchange,
|
|
23
|
+
type TlsExtension,
|
|
24
|
+
encodeHandshakeMessage,
|
|
25
|
+
decodeHandshakeMessage,
|
|
26
|
+
encodeClientHello,
|
|
27
|
+
decodeClientHello,
|
|
28
|
+
encodeServerHello,
|
|
29
|
+
decodeServerHello,
|
|
30
|
+
encodeHelloVerifyRequest,
|
|
31
|
+
decodeHelloVerifyRequest,
|
|
32
|
+
encodeCertificate,
|
|
33
|
+
decodeCertificate,
|
|
34
|
+
encodeServerKeyExchange,
|
|
35
|
+
decodeServerKeyExchange,
|
|
36
|
+
encodeClientKeyExchange,
|
|
37
|
+
decodeClientKeyExchange,
|
|
38
|
+
buildUseSrtpExtension,
|
|
39
|
+
buildSupportedGroupsExtension,
|
|
40
|
+
} from './handshake.js';
|
|
41
|
+
export {
|
|
42
|
+
prf,
|
|
43
|
+
hmacSha256,
|
|
44
|
+
computeMasterSecret,
|
|
45
|
+
expandKeyMaterial,
|
|
46
|
+
exportKeyingMaterial,
|
|
47
|
+
aesgcmEncrypt,
|
|
48
|
+
aesgcmDecrypt,
|
|
49
|
+
generateEcdhKeyPair,
|
|
50
|
+
computeEcdhPreMasterSecret,
|
|
51
|
+
encodeEcPublicKey,
|
|
52
|
+
decodeEcPublicKey,
|
|
53
|
+
ecdsaSign,
|
|
54
|
+
ecdsaVerify,
|
|
55
|
+
sha256,
|
|
56
|
+
type AesGcmResult,
|
|
57
|
+
type EcdhKeyPair,
|
|
58
|
+
type KeyBlock,
|
|
59
|
+
} from './crypto.js';
|
|
60
|
+
export {
|
|
61
|
+
type DtlsCertificate,
|
|
62
|
+
generateSelfSignedCertificate,
|
|
63
|
+
computeFingerprint,
|
|
64
|
+
verifyFingerprint,
|
|
65
|
+
extractPublicKeyFromCert,
|
|
66
|
+
} from './certificate.js';
|
|
67
|
+
export { DtlsState, type SecurityParameters, type HandshakeContext, type CipherState } from './state.js';
|
|
68
|
+
export {
|
|
69
|
+
DtlsTransport,
|
|
70
|
+
type DtlsTransportOptions,
|
|
71
|
+
type SrtpKeyingMaterial,
|
|
72
|
+
} from './transport.js';
|