lemon-tls 0.10.0 → 0.11.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 +164 -42
- package/index.d.ts +399 -15
- package/package.json +7 -2
- package/src/ca_store.js +31 -0
- package/src/compat.js +64 -112
- package/src/crypto.js +21 -114
- package/src/dtls_session.js +1238 -412
- package/src/dtls_socket.js +318 -96
- package/src/node_options.js +154 -17
- package/src/record.js +293 -197
- package/src/secure_context.js +86 -42
- package/src/session/ecdh.js +140 -122
- package/src/session/ech.js +92 -32
- package/src/session/hpke.js +12 -44
- package/src/session/message.js +29 -9
- package/src/session/signing.js +80 -22
- package/src/session/ticket.js +5 -3
- package/src/session/x509.js +385 -0
- package/src/tls_session.js +2214 -895
- package/src/tls_socket.js +451 -452
- package/src/wire.js +271 -158
package/index.d.ts
CHANGED
|
@@ -37,6 +37,29 @@ export interface SecureContext {
|
|
|
37
37
|
alternates?: Array<{ certificateChain: Array<{ cert: Buffer }>; privateKey: any }>;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
/**
|
|
41
|
+
* An external PSK (RFC 8446 §2.2): a key both sides hold from elsewhere.
|
|
42
|
+
* TLS 1.3 and DTLS 1.3, psk_dhe_ke (a fresh ECDHE secret is mixed in). The
|
|
43
|
+
* peer that proves it is authenticated by it — no certificate is needed or
|
|
44
|
+
* sent; if the server does not take it, the handshake goes on with its
|
|
45
|
+
* certificate, checked as usual.
|
|
46
|
+
*/
|
|
47
|
+
export interface ExternalPsk {
|
|
48
|
+
/** Its identity: a string (UTF-8) or bytes, 1 to 65535 long. */
|
|
49
|
+
identity: string | Uint8Array;
|
|
50
|
+
psk: Uint8Array;
|
|
51
|
+
/** The hash it is used with (RFC 8446 §4.2.11). Default 'sha256'. */
|
|
52
|
+
hash?: 'sha256' | 'sha384';
|
|
53
|
+
}
|
|
54
|
+
/** A server's answer for an identity: the key (SHA-256), the key and its hash, or none. */
|
|
55
|
+
export type ExternalPskAnswer = Uint8Array | { psk: Uint8Array; hash?: 'sha256' | 'sha384' } | null | undefined;
|
|
56
|
+
|
|
57
|
+
/** The record size limits in force (RFC 8449), in plaintext bytes: what we may send, what we take. */
|
|
58
|
+
export interface RecordSizeLimit {
|
|
59
|
+
send: number;
|
|
60
|
+
receive: number;
|
|
61
|
+
}
|
|
62
|
+
|
|
40
63
|
/** An Encrypted Client Hello key (RFC 9849), from generateEchKey(). */
|
|
41
64
|
export interface EchKey {
|
|
42
65
|
config: Uint8Array;
|
|
@@ -214,6 +237,25 @@ export interface TLSSocketOptions {
|
|
|
214
237
|
/** RFC 8879: ['brotli', 'zlib', 'zstd'] in order, true (all this build has) or false (none). */
|
|
215
238
|
certificateCompression?: Array<'brotli' | 'zlib' | 'zstd'> | boolean;
|
|
216
239
|
|
|
240
|
+
/**
|
|
241
|
+
* RFC 8449: the largest record we take — 64 to 16385 (TLS 1.3 counts the
|
|
242
|
+
* content-type byte). A client sends it; a server answers a client's with
|
|
243
|
+
* this or the protocol's maximum. false: never sent or answered. Records
|
|
244
|
+
* we send are cut to the peer's limit.
|
|
245
|
+
*/
|
|
246
|
+
recordSizeLimit?: number | false;
|
|
247
|
+
|
|
248
|
+
// ---- External PSK (TLS 1.3) ----
|
|
249
|
+
/**
|
|
250
|
+
* node:tls's. Client: (hint) → { psk, identity } or null — asked once,
|
|
251
|
+
* hint is null (TLS 1.3 has no identity hint). Server: (socket, identity)
|
|
252
|
+
* → the key or { psk, hash }, or null for an identity it does not know
|
|
253
|
+
* (then session tickets are tried, then a full handshake).
|
|
254
|
+
*/
|
|
255
|
+
pskCallback?: ((hint: null) => (ExternalPsk | null)) | ((socket: TLSSocket, identity: string) => ExternalPskAnswer);
|
|
256
|
+
/** Client: the external PSK, without a callback. */
|
|
257
|
+
psk?: ExternalPsk;
|
|
258
|
+
|
|
217
259
|
// ---- 0-RTT (RFC 8446 §4.2.10) ----
|
|
218
260
|
/** Server: issue tickets that allow it, and take it. Client: with `session`, what is written before 'secureConnect' goes early. */
|
|
219
261
|
earlyData?: boolean;
|
|
@@ -228,7 +270,11 @@ export interface TLSSocketOptions {
|
|
|
228
270
|
grease?: boolean;
|
|
229
271
|
permuteExtensions?: boolean;
|
|
230
272
|
|
|
231
|
-
/**
|
|
273
|
+
/**
|
|
274
|
+
* Trust the bundled CAs when no `ca` is given (default: true) — a client
|
|
275
|
+
* for the server's chain, a server (requestCert) for the client's. false
|
|
276
|
+
* with no `ca`: no peer is authorized.
|
|
277
|
+
*/
|
|
232
278
|
useDefaultCA?: boolean;
|
|
233
279
|
allowHalfOpen?: boolean;
|
|
234
280
|
highWaterMark?: number;
|
|
@@ -252,10 +298,12 @@ export interface TLSSocketOptions {
|
|
|
252
298
|
noTickets?: boolean;
|
|
253
299
|
sessionTickets?: boolean;
|
|
254
300
|
ticketLifetime?: number;
|
|
301
|
+
/** Signature schemes, in preference order. No SHA-1 (0x0201, 0x0203: RFC 9155) — a list naming one throws. */
|
|
255
302
|
signatureAlgorithms?: number[];
|
|
256
303
|
groups?: number[];
|
|
257
304
|
prioritizeChaCha?: boolean;
|
|
258
305
|
maxRecordSize?: number;
|
|
306
|
+
/** Code points to narrow the suites to; a list with none this library carries throws ("no cipher match"). */
|
|
259
307
|
allowedCipherSuites?: number[];
|
|
260
308
|
pins?: string[];
|
|
261
309
|
handshakeTimeout?: number;
|
|
@@ -362,6 +410,10 @@ export class TLSSocket extends Duplex {
|
|
|
362
410
|
/** Encrypted Client Hello: what happened to it on this connection. */
|
|
363
411
|
readonly echStatus: string | null;
|
|
364
412
|
readonly echAccepted: boolean;
|
|
413
|
+
/** Server, ECH split mode: ECH accepted by the client-facing server in front of this one; echStatus is 'accepted' either way. */
|
|
414
|
+
readonly echBackend: boolean;
|
|
415
|
+
/** External PSK: the identity this connection was authenticated with (or resumed from one that was), or null. */
|
|
416
|
+
getPskIdentity(): Buffer | null;
|
|
365
417
|
/** Server: the certificate to present, from inside ALPNCallback (a context or { key, cert }). */
|
|
366
418
|
setKeyCert(context: SecureContext | SecureContextOptions): void;
|
|
367
419
|
/** The peer's hello extensions: [{ type, name, data, value }]. */
|
|
@@ -470,7 +522,14 @@ export interface TLSSessionOptions {
|
|
|
470
522
|
/** See TLSSocketOptions.ticketData. */
|
|
471
523
|
ticketData?: (resumed: any) => any;
|
|
472
524
|
session?: Buffer | Uint8Array | SessionTicketData;
|
|
473
|
-
|
|
525
|
+
/** Client: an external PSK (or, older, a resumption PSK already decoded). */
|
|
526
|
+
psk?: ExternalPsk | any;
|
|
527
|
+
/** Server: the external PSK for an identity — tried before 'psk' and the session tickets. */
|
|
528
|
+
pskCallback?: (identity: Uint8Array) => ExternalPskAnswer;
|
|
529
|
+
/** See TLSSocketOptions.recordSizeLimit. */
|
|
530
|
+
recordSizeLimit?: number | false;
|
|
531
|
+
/** DTLS: the CID to ask for (DTLSSession turns DTLSCommonOptions.connectionId into it). */
|
|
532
|
+
connectionId?: Uint8Array;
|
|
474
533
|
rejectUnauthorized?: boolean;
|
|
475
534
|
ca?: Buffer | string;
|
|
476
535
|
noTickets?: boolean;
|
|
@@ -517,9 +576,21 @@ export class TLSSession extends EventEmitter {
|
|
|
517
576
|
getVersion(): number | null;
|
|
518
577
|
getCipher(): number | null;
|
|
519
578
|
getALPN(): string | null;
|
|
579
|
+
/** Encrypted Client Hello: null, 'grease' (client), 'accepted', 'rejected'. */
|
|
580
|
+
getEchStatus(): 'grease' | 'accepted' | 'rejected' | null;
|
|
581
|
+
/** Server, ECH split mode: accepted by the client-facing server in front of this one (BoringSSL's SSL_ech_accepted is false here). */
|
|
582
|
+
isEchBackend(): boolean;
|
|
520
583
|
getPeerCertificate(): Array<{ cert: Buffer }> | null;
|
|
521
584
|
/** Our own certificate chain — the one this connection presents. */
|
|
522
585
|
getCertificate(): Array<{ cert: Buffer }> | null;
|
|
586
|
+
/** External PSK: the identity this connection was authenticated with, or null. */
|
|
587
|
+
getPskIdentity(): Uint8Array | null;
|
|
588
|
+
/** RFC 8449: the limits once both sides sent record_size_limit, else null. */
|
|
589
|
+
getRecordSizeLimit(): RecordSizeLimit | null;
|
|
590
|
+
/** The largest handshake message taken: 128 KB, or maxHandshakeSize when smaller. */
|
|
591
|
+
handshakeMessageLimit(): number;
|
|
592
|
+
/** The most content a record we send may carry: the peer's record_size_limit, or 2^14. */
|
|
593
|
+
recordSendLimit(): number;
|
|
523
594
|
getTrafficSecrets(): TrafficSecrets;
|
|
524
595
|
getHandshakeSecrets(): HandshakeSecrets;
|
|
525
596
|
exportKeyingMaterial(length: number, label: string, context: Uint8Array): Uint8Array;
|
|
@@ -540,7 +611,10 @@ export class TLSSession extends EventEmitter {
|
|
|
540
611
|
on(event: 'secureConnect', listener: () => void): this;
|
|
541
612
|
/** Emits the raw internal session blob (Buffer/Uint8Array — encoded session state). */
|
|
542
613
|
on(event: 'session', listener: (data: Buffer) => void): this;
|
|
543
|
-
|
|
614
|
+
/** Server: a PSK identity offered; answer with a resumption PSK, or not at all (the session's own tickets are tried). External PSKs: pskCallback. */
|
|
615
|
+
on(event: 'psk', listener: (info: { identity: Uint8Array; obfuscatedAge: number }, callback: (result: { psk: Uint8Array; cipher: number } | null) => void) => void): this;
|
|
616
|
+
/** DTLS: the Connection IDs were agreed (the record layer's cue). */
|
|
617
|
+
on(event: 'connectionId', listener: (ids: ConnectionIds) => void): this;
|
|
544
618
|
on(event: 'keyUpdate', listener: (info: { direction: 'send' | 'receive'; secret: Uint8Array }) => void): this;
|
|
545
619
|
on(event: 'keylog', listener: (line: Buffer) => void): this;
|
|
546
620
|
on(event: 'clienthello', listener: (raw: Buffer, parsed: any) => void): this;
|
|
@@ -606,13 +680,26 @@ export function getCACertificates(type?: 'default' | 'bundled' | 'extra' | 'syst
|
|
|
606
680
|
export function setDefaultCACertificates(certs: string[]): void;
|
|
607
681
|
|
|
608
682
|
/**
|
|
609
|
-
* Validates that the peer certificate matches the hostname
|
|
610
|
-
*
|
|
611
|
-
*
|
|
612
|
-
*
|
|
683
|
+
* Validates that the peer certificate matches the hostname — the check the
|
|
684
|
+
* handshake itself makes (RFC 9525): the subjectAltName alone (DNS names,
|
|
685
|
+
* whole-label wildcards; IP addresses for an IP), no fallback to the CN.
|
|
686
|
+
* Needs cert.raw, as getPeerCertificate() gives it. Returns undefined on a
|
|
687
|
+
* match, or an Error with code ERR_TLS_CERT_ALTNAME_INVALID, reason, host
|
|
688
|
+
* and cert (Node's shape). The checkServerIdentity option replaces it.
|
|
613
689
|
*/
|
|
614
690
|
export function checkServerIdentity(hostname: string, cert: PeerCertificate): Error | undefined;
|
|
615
691
|
|
|
692
|
+
/**
|
|
693
|
+
* The errors a connection fails with when an alert ends it: ours —
|
|
694
|
+
* code 'ERR_SSL_<ALERT NAME>' (e.g. ERR_SSL_HANDSHAKE_FAILURE) — or the
|
|
695
|
+
* peer's — 'ERR_TLS_ALERT_RECEIVED'; `alert` is the alert's code (RFC 8446
|
|
696
|
+
* §6), the same in TLS and DTLS.
|
|
697
|
+
*/
|
|
698
|
+
export interface TLSAlertError extends Error {
|
|
699
|
+
code: string;
|
|
700
|
+
alert: number;
|
|
701
|
+
}
|
|
702
|
+
|
|
616
703
|
export const DEFAULT_MIN_VERSION: string;
|
|
617
704
|
export const DEFAULT_MAX_VERSION: string;
|
|
618
705
|
/** Node-compat: default colon-separated cipher string. */
|
|
@@ -660,17 +747,314 @@ export declare const record: {
|
|
|
660
747
|
};
|
|
661
748
|
|
|
662
749
|
// ======================== DTLS ========================
|
|
663
|
-
// DTLS bindings are exported from the package but typed loosely here —
|
|
664
|
-
// use the JS source directly for exact signatures.
|
|
665
750
|
|
|
666
|
-
|
|
667
|
-
|
|
751
|
+
/**
|
|
752
|
+
* DTLS Connection IDs (RFC 9146; RFC 9147 §9). local: the CID the peer puts
|
|
753
|
+
* in the records we receive (the one we asked for); peer: the one we put in
|
|
754
|
+
* ours. Empty: the side asked for none.
|
|
755
|
+
*/
|
|
756
|
+
export interface ConnectionIds {
|
|
757
|
+
local: Uint8Array;
|
|
758
|
+
peer: Uint8Array;
|
|
668
759
|
}
|
|
669
|
-
|
|
670
|
-
|
|
760
|
+
|
|
761
|
+
/** DTLS options shared by DTLSSession, DTLSSocket, connectDTLS and createDTLSServer. */
|
|
762
|
+
export interface DTLSCommonOptions {
|
|
763
|
+
/** As createSecureContext: arrays for several certificates, the one the peer can verify chosen per handshake. */
|
|
764
|
+
cert?: SecureContextOptions['cert'];
|
|
765
|
+
key?: SecureContextOptions['key'];
|
|
766
|
+
ca?: PemInput | PemInput[];
|
|
767
|
+
minVersion?: 'DTLSv1.2' | 'DTLSv1.3';
|
|
768
|
+
maxVersion?: 'DTLSv1.2' | 'DTLSv1.3';
|
|
769
|
+
rejectUnauthorized?: boolean;
|
|
770
|
+
checkServerIdentity?: (hostname: string, cert: PeerCertificate) => Error | undefined;
|
|
771
|
+
/** Server: ask the client for a certificate (WebRTC: always). */
|
|
772
|
+
requestCert?: boolean;
|
|
773
|
+
servername?: string;
|
|
774
|
+
/** Also spelled ALPNProtocols, as in TLS. */
|
|
775
|
+
alpnProtocols?: string[];
|
|
776
|
+
/** Key-exchange groups, code points (e.g. [29, 23]). Default: a client X25519, P-256, P-384; a server those, X448 and P-521. */
|
|
777
|
+
groups?: number[];
|
|
778
|
+
/** See TLSSocketOptions.recordSizeLimit. A send larger than the peer's limit is refused (RangeError, code ERR_DTLS_RECORD_TOO_LARGE). */
|
|
779
|
+
recordSizeLimit?: number | false;
|
|
780
|
+
/**
|
|
781
|
+
* Connection ID (RFC 9146, RFC 9147 §9): the records survive the peer's
|
|
782
|
+
* address changing (a NAT rebinding). Each side asks for the CID it wants
|
|
783
|
+
* in what it receives. true — a client asks for an empty one (it needs
|
|
784
|
+
* none: the server does not move), a server for 8 random bytes; or the
|
|
785
|
+
* CID itself (up to 255 bytes; not on createDTLSServer, which gives each
|
|
786
|
+
* connection its own). Off by default.
|
|
787
|
+
*/
|
|
788
|
+
connectionId?: boolean | Uint8Array;
|
|
789
|
+
/** Client, DTLS 1.3: an external PSK (see ExternalPsk). */
|
|
790
|
+
psk?: ExternalPsk;
|
|
791
|
+
/**
|
|
792
|
+
* External PSK, DTLS 1.3. DTLSSession (as TLSSession): server,
|
|
793
|
+
* (identity bytes) → the key. DTLSSocket / createDTLSServer / connectDTLS
|
|
794
|
+
* take node:tls's form, as TLSSocket does: see TLSSocketOptions.pskCallback.
|
|
795
|
+
*/
|
|
796
|
+
pskCallback?: ((identity: Uint8Array) => ExternalPskAnswer) | ((hint: null) => (ExternalPsk | null)) | ((socket: DTLSSocket, identity: string) => ExternalPskAnswer);
|
|
797
|
+
/** DTLS-SRTP protection profiles to offer / accept (RFC 5764), code points. */
|
|
798
|
+
srtpProfiles?: number[];
|
|
799
|
+
/**
|
|
800
|
+
* The path MTU: the largest datagram written (every record is one
|
|
801
|
+
* datagram). Integer in [128, 65507]; default 1200. RangeError otherwise.
|
|
802
|
+
*/
|
|
803
|
+
mtu?: number;
|
|
804
|
+
/**
|
|
805
|
+
* Milliseconds each flight's retransmission timer starts at, doubling on
|
|
806
|
+
* every timeout, to 60 s. Given, it is a fixed schedule: no measured round
|
|
807
|
+
* trip changes it. Not given, the timer starts at 1000 ms until a round
|
|
808
|
+
* trip has been measured, then follows it (RFC 6298: SRTT + 4·RTTVAR, at
|
|
809
|
+
* least 100 ms; Karn's rule). A flight is given up after six retransmissions AND 60 s since it
|
|
810
|
+
* began, so a short timer does not give up early on a lossy path.
|
|
811
|
+
* Integer in [10, 60000]; default 1000 (RFC 9147 §5.8.2). RangeError
|
|
812
|
+
* otherwise.
|
|
813
|
+
*/
|
|
814
|
+
initialRetransmitTimeout?: number;
|
|
815
|
+
/**
|
|
816
|
+
* Client: a 'session' Buffer from an earlier connection, to resume —
|
|
817
|
+
* a DTLS 1.3 PSK ticket, or a DTLS 1.2 RFC 5077 ticket / session id.
|
|
818
|
+
* A DTLS 1.2 ticket offered with DTLS 1.3 as well goes with no session_id
|
|
819
|
+
* (RFC 9147 §5.3 leaves such a ClientHello none to carry it); whether the
|
|
820
|
+
* server took it, the message after its ServerHello says (RFC 5077 §3.4).
|
|
821
|
+
*/
|
|
822
|
+
session?: Buffer | Uint8Array;
|
|
823
|
+
/** Issue (server) / ask for (client, DTLS 1.2) session tickets. Default true. */
|
|
824
|
+
sessionTickets?: boolean;
|
|
825
|
+
/** Older spelling of sessionTickets: false. */
|
|
826
|
+
noTickets?: boolean;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
export interface DTLSSessionOptions extends DTLSCommonOptions {
|
|
830
|
+
isServer?: boolean;
|
|
831
|
+
SNICallback?: (servername: string, cb: (err: Error | null, ctx?: SecureContext | null) => void) => void;
|
|
832
|
+
/** 48 bytes. A server that resumes across connections shares one (createDTLSServer does). */
|
|
833
|
+
ticketKeys?: Buffer | Uint8Array;
|
|
834
|
+
/** Server, DTLS 1.2: HelloVerifyRequest cookie exchange (RFC 6347 §4.2.1). */
|
|
835
|
+
useCookies?: boolean;
|
|
836
|
+
/**
|
|
837
|
+
* Code points; default per version. Suites this library does not carry
|
|
838
|
+
* are skipped; a list with none it carries throws ("no cipher match").
|
|
839
|
+
*/
|
|
840
|
+
cipherSuites?: number[];
|
|
841
|
+
/** Largest reassembled handshake message accepted: at most the TLS limit (128 KB, or maxHandshakeSize). */
|
|
842
|
+
maxHandshakeMessageSize?: number;
|
|
843
|
+
/**
|
|
844
|
+
* Without `ca`: the platform trust store? Default false for DTLS — WebRTC
|
|
845
|
+
* peers are self-signed, checked by SDP fingerprint; with neither, no peer
|
|
846
|
+
* is authorized, so such a caller sets rejectUnauthorized: false.
|
|
847
|
+
*/
|
|
848
|
+
useDefaultCA?: boolean;
|
|
671
849
|
}
|
|
672
|
-
|
|
673
|
-
|
|
850
|
+
|
|
851
|
+
/** The DTLS state machine without a transport: you move the datagrams. */
|
|
852
|
+
export declare class DTLSSession {
|
|
853
|
+
constructor(options: DTLSSessionOptions);
|
|
854
|
+
|
|
855
|
+
/**
|
|
856
|
+
* Hand in one received UDP datagram. Never throws: a malformed one is
|
|
857
|
+
* dropped or ends the session. True when it held a record that
|
|
858
|
+
* authenticated and is the newest yet — the condition (RFC 9146 §6) for
|
|
859
|
+
* taking a new peer address with Connection IDs.
|
|
860
|
+
*/
|
|
861
|
+
feedDatagram(datagram: Uint8Array | Buffer): boolean;
|
|
862
|
+
/** Application data; before 'connect' it is refused ('error'). */
|
|
863
|
+
send(data: Uint8Array | Buffer | string): void;
|
|
864
|
+
/** close_notify, then 'close'. */
|
|
865
|
+
close(): void;
|
|
866
|
+
|
|
867
|
+
readonly state: 'idle' | 'handshaking' | 'connected' | 'closed';
|
|
868
|
+
readonly connected: boolean;
|
|
869
|
+
/** 0xFEFD (DTLS 1.2) or 0xFEFC (DTLS 1.3) once negotiated, else null. */
|
|
870
|
+
readonly version: number | null;
|
|
871
|
+
/** The TLSSession underneath. */
|
|
872
|
+
readonly tls: TLSSession;
|
|
873
|
+
|
|
874
|
+
isSessionReused(): boolean;
|
|
875
|
+
/** The last 'session' Buffer, or undefined — as TLSSocket#getSession. */
|
|
876
|
+
getSession(): Buffer | undefined;
|
|
877
|
+
/** Change the path MTU; for messages fragmented from now on. RangeError outside [128, 65507]. */
|
|
878
|
+
setMTU(mtu: number): void;
|
|
879
|
+
getMTU(): number;
|
|
880
|
+
/** Where every flight's timer starts from now on — a fixed schedule, as the option given. RangeError outside [10, 60000] ms. */
|
|
881
|
+
setInitialRetransmitTimeout(ms: number): void;
|
|
882
|
+
|
|
883
|
+
/** DTLS 1.3 KeyUpdate (RFC 9147 §8); requestPeer asks the peer to update too. */
|
|
884
|
+
requestKeyUpdate(requestPeer?: boolean): void;
|
|
885
|
+
getWriteEpoch(): number | null;
|
|
886
|
+
getReadEpoch(): number | null;
|
|
887
|
+
|
|
888
|
+
/** RFC 5705 / RFC 8446 §7.5. DTLS-SRTP: exportKeyingMaterial(60, 'EXTRACTOR-dtls_srtp'), no context. */
|
|
889
|
+
exportKeyingMaterial(length: number, label: string, context?: Uint8Array | Buffer): Uint8Array | null;
|
|
890
|
+
/** The negotiated DTLS-SRTP profile, or null. */
|
|
891
|
+
getSelectedSrtpProfile(): number | null;
|
|
892
|
+
getRemoteExtensions(): Array<{ type: number; name: string | null; data: Uint8Array; value: any }>;
|
|
893
|
+
getRemoteExtension(type: number): { type: number; name: string | null; data: Uint8Array; value: any } | null;
|
|
894
|
+
getNegotiationResult(): NegotiationResult;
|
|
895
|
+
getALPN(): string | null;
|
|
896
|
+
/** Encrypted Client Hello: null, 'grease' (client), 'accepted', 'rejected'. */
|
|
897
|
+
getEchStatus(): 'grease' | 'accepted' | 'rejected' | null;
|
|
898
|
+
/** Server, ECH split mode: accepted by the client-facing server in front of this one (BoringSSL's SSL_ech_accepted is false here). */
|
|
899
|
+
isEchBackend(): boolean;
|
|
900
|
+
/** External PSK: the identity this connection was authenticated with, or null. */
|
|
901
|
+
getPskIdentity(): Uint8Array | null;
|
|
902
|
+
/** Connection IDs as they are now (NewConnectionId may have changed the peer's), or null. */
|
|
903
|
+
getConnectionIds(): ConnectionIds | null;
|
|
904
|
+
getPeerCertificate(): Array<{ cert: Buffer }> | null;
|
|
905
|
+
setKeyCert(context: SecureContext | SecureContextOptions): void;
|
|
906
|
+
getFinished(): Buffer | null;
|
|
907
|
+
getPeerFinished(): Buffer | null;
|
|
908
|
+
getJA4(opts?: FingerprintOptions): FingerprintResult | null;
|
|
909
|
+
getJA5(opts?: FingerprintOptions): FingerprintResult | null;
|
|
910
|
+
getJA4S(opts?: FingerprintOptions): FingerprintResult | null;
|
|
911
|
+
set_context(options: Record<string, any>): void;
|
|
912
|
+
|
|
913
|
+
/** A datagram to send. */
|
|
914
|
+
on(event: 'packet', listener: (datagram: Uint8Array) => void): void;
|
|
915
|
+
/** DTLS 1.3: a KeyUpdate took effect in this direction. */
|
|
916
|
+
on(event: 'keyUpdate', listener: (info: { direction: 'send' | 'receive'; epoch: number }) => void): void;
|
|
917
|
+
on(event: 'connect', listener: () => void): void;
|
|
918
|
+
/** Decrypted application data: a Buffer (a view of the record, not a copy), as node:tls gives. */
|
|
919
|
+
on(event: 'data', listener: (data: Buffer) => void): void;
|
|
920
|
+
on(event: 'error', listener: (err: Error) => void): void;
|
|
921
|
+
on(event: 'close', listener: () => void): void;
|
|
922
|
+
on(event: 'session', listener: (session: Buffer) => void): void;
|
|
923
|
+
on(event: 'clienthello', listener: (raw: Uint8Array, parsed: any) => void): void;
|
|
924
|
+
/** An alert from the peer (its warnings too). */
|
|
925
|
+
on(event: 'alert', listener: (alert: { level: number; description: number }) => void): void;
|
|
926
|
+
/** Connection IDs agreed, or the peer's moved (NewConnectionId). */
|
|
927
|
+
on(event: 'connectionId', listener: (ids: ConnectionIds) => void): void;
|
|
928
|
+
on(event: string, listener: (...args: any[]) => void): void;
|
|
929
|
+
off(event: string, listener: (...args: any[]) => void): void;
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
export interface DTLSSocketOptions extends DTLSCommonOptions {
|
|
933
|
+
isServer?: boolean;
|
|
934
|
+
/** The peer (a client's server; filled from the first datagram on a server). */
|
|
935
|
+
remoteAddress?: string;
|
|
936
|
+
remotePort?: number;
|
|
937
|
+
SNICallback?: DTLSSessionOptions['SNICallback'];
|
|
938
|
+
ticketKeys?: Buffer | Uint8Array;
|
|
939
|
+
useCookies?: boolean;
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
/** One DTLS connection over a UDP socket. */
|
|
943
|
+
export declare class DTLSSocket {
|
|
944
|
+
constructor(udpSocket: import('node:dgram').Socket, options: DTLSSocketOptions);
|
|
945
|
+
|
|
946
|
+
send(data: Uint8Array | Buffer | string): void;
|
|
947
|
+
close(): void;
|
|
948
|
+
/** Server side: what DTLSServer calls for this peer's datagrams. */
|
|
949
|
+
feedDatagram(datagram: Uint8Array | Buffer, rinfo?: { address: string; port: number }): void;
|
|
950
|
+
/**
|
|
951
|
+
* Send to another address from now on. With Connection IDs, for a peer
|
|
952
|
+
* reported at a new one ('remoteAddressChange'): not taken by itself —
|
|
953
|
+
* RFC 9146 §6 asks for a way of knowing the peer is there first.
|
|
954
|
+
*/
|
|
955
|
+
setRemoteAddress(address: string, port: number): void;
|
|
956
|
+
|
|
957
|
+
readonly state: DTLSSession['state'];
|
|
958
|
+
readonly connected: boolean;
|
|
959
|
+
readonly version: number | null;
|
|
960
|
+
readonly remoteAddress: string | null;
|
|
961
|
+
readonly remotePort: number | null;
|
|
962
|
+
/** The DTLSSession underneath. */
|
|
963
|
+
readonly session: DTLSSession;
|
|
964
|
+
|
|
965
|
+
isSessionReused(): boolean;
|
|
966
|
+
getSession(): Buffer | undefined;
|
|
967
|
+
setMTU(mtu: number): void;
|
|
968
|
+
getMTU(): number;
|
|
969
|
+
setInitialRetransmitTimeout(ms: number): void;
|
|
970
|
+
|
|
971
|
+
exportKeyingMaterial(length: number, label: string, context?: Uint8Array | Buffer): Uint8Array | null;
|
|
972
|
+
getSelectedSrtpProfile(): number | null;
|
|
973
|
+
getRemoteExtensions(): ReturnType<DTLSSession['getRemoteExtensions']>;
|
|
974
|
+
getRemoteExtension(type: number): ReturnType<DTLSSession['getRemoteExtension']>;
|
|
975
|
+
getNegotiationResult(): NegotiationResult;
|
|
976
|
+
getALPN(): string | null;
|
|
977
|
+
/** Encrypted Client Hello: null, 'grease' (client), 'accepted', 'rejected'. */
|
|
978
|
+
getEchStatus(): 'grease' | 'accepted' | 'rejected' | null;
|
|
979
|
+
/** Server, ECH split mode: accepted by the client-facing server in front of this one (BoringSSL's SSL_ech_accepted is false here). */
|
|
980
|
+
isEchBackend(): boolean;
|
|
981
|
+
/** External PSK: the identity (a Buffer, as TLSSocket's), or null. */
|
|
982
|
+
getPskIdentity(): Buffer | null;
|
|
983
|
+
getConnectionIds(): ConnectionIds | null;
|
|
984
|
+
getPeerCertificate(): Array<{ cert: Buffer }> | null;
|
|
985
|
+
setKeyCert(context: SecureContext | SecureContextOptions): void;
|
|
986
|
+
/** undefined (not null) when not available. */
|
|
987
|
+
getFinished(): Buffer | undefined;
|
|
988
|
+
getPeerFinished(): Buffer | undefined;
|
|
989
|
+
getJA4(opts?: FingerprintOptions): FingerprintResult | null;
|
|
990
|
+
getJA5(opts?: FingerprintOptions): FingerprintResult | null;
|
|
991
|
+
getJA4S(opts?: FingerprintOptions): FingerprintResult | null;
|
|
992
|
+
set_context(options: Record<string, any>): void;
|
|
993
|
+
|
|
994
|
+
/** Also emitted as 'secureConnect', right after 'connect'. */
|
|
995
|
+
on(event: 'connect' | 'secureConnect', listener: () => void): void;
|
|
996
|
+
/** Decrypted application data: a Buffer (a view of the record, not a copy), as node:tls gives. */
|
|
997
|
+
on(event: 'data', listener: (data: Buffer) => void): void;
|
|
998
|
+
on(event: 'error', listener: (err: Error) => void): void;
|
|
999
|
+
on(event: 'close', listener: () => void): void;
|
|
1000
|
+
on(event: 'session', listener: (session: Buffer) => void): void;
|
|
1001
|
+
on(event: 'clienthello', listener: (raw: Uint8Array, parsed: any) => void): void;
|
|
1002
|
+
on(event: 'alert', listener: (alert: { level: number; description: number }) => void): void;
|
|
1003
|
+
on(event: 'connectionId', listener: (ids: ConnectionIds) => void): void;
|
|
1004
|
+
/** Connection IDs: a datagram of this connection, the newest authenticated yet, from another address. */
|
|
1005
|
+
on(event: 'remoteAddressChange', listener: (to: { address: string; port: number }) => void): void;
|
|
1006
|
+
on(event: string, listener: (...args: any[]) => void): void;
|
|
1007
|
+
off(event: string, listener: (...args: any[]) => void): void;
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
/** Every DTLSSession option, for each connection (ca, requestCert, cipherSuites, ...). */
|
|
1011
|
+
export interface DTLSServerOptions extends Omit<DTLSSessionOptions, 'isServer' | 'connectionId'> {
|
|
1012
|
+
/** 48 bytes; one for every connection of this server (random if not given). */
|
|
1013
|
+
ticketKeys?: Buffer | Uint8Array;
|
|
1014
|
+
/** Connection IDs: each connection its own 8 random bytes — and found by it when its peer moves. */
|
|
1015
|
+
connectionId?: boolean;
|
|
1016
|
+
/** The UDP socket's receive/send buffers, bytes: 4 MB by default; 0 or false, the OS default. The kernel caps it at net.core.rmem_max. */
|
|
1017
|
+
socketBufferSize?: number | false;
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
/** createDTLSServer()'s: one UDP port, a DTLSSocket per peer address. */
|
|
1021
|
+
export interface DTLSServer {
|
|
1022
|
+
/**
|
|
1023
|
+
* An IPv6 `address`, an IPv6 socket; an IPv4 one, IPv4. None: '::',
|
|
1024
|
+
* dual-stack — IPv4 peers too, as '::ffff:a.b.c.d' — or 0.0.0.0 on a host
|
|
1025
|
+
* without IPv6 (as node:net).
|
|
1026
|
+
*/
|
|
1027
|
+
listen(port: number, address?: string, callback?: () => void): void;
|
|
1028
|
+
listen(port: number, callback?: () => void): void;
|
|
1029
|
+
/** close_notify to every connection, then the socket, once they have gone. */
|
|
1030
|
+
close(callback?: () => void): void;
|
|
1031
|
+
address(): { port: number; family: string; address: string } | null;
|
|
1032
|
+
/** A peer's handshake completed (also the connectionListener). */
|
|
1033
|
+
on(event: 'connection', listener: (socket: DTLSSocket) => void): void;
|
|
1034
|
+
/** A peer's connection failed. */
|
|
1035
|
+
on(event: 'clientError', listener: (err: Error, socket: DTLSSocket) => void): void;
|
|
1036
|
+
on(event: 'listening', listener: () => void): void;
|
|
1037
|
+
/** The UDP socket's error. */
|
|
1038
|
+
on(event: 'error', listener: (err: Error) => void): void;
|
|
1039
|
+
on(event: string, listener: (...args: any[]) => void): void;
|
|
1040
|
+
off(event: string, listener: (...args: any[]) => void): void;
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
/** Every DTLSSession option a client takes (cipherSuites, useDefaultCA, ...). */
|
|
1044
|
+
export interface ConnectDTLSOptions extends Omit<DTLSSessionOptions, 'isServer' | 'ticketKeys' | 'useCookies' | 'SNICallback'> {
|
|
1045
|
+
/** A name (resolved once — IPv4 or IPv6, whichever it has first) or an IP. Default '127.0.0.1'. */
|
|
1046
|
+
host?: string;
|
|
1047
|
+
hostname?: string;
|
|
1048
|
+
/** Default 4433. */
|
|
1049
|
+
port?: number;
|
|
1050
|
+
/** The UDP socket's receive/send buffers, bytes: 4 MB by default; 0 or false, the OS default. */
|
|
1051
|
+
socketBufferSize?: number | false;
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
export declare function createDTLSServer(options: DTLSServerOptions, connectionListener?: (socket: DTLSSocket) => void): DTLSServer;
|
|
1055
|
+
export declare function createDTLSServer(connectionListener?: (socket: DTLSSocket) => void): DTLSServer;
|
|
1056
|
+
/** A UDP socket of its own, closed with the connection. */
|
|
1057
|
+
export declare function connectDTLS(options: ConnectDTLSOptions, callback?: () => void): DTLSSocket;
|
|
674
1058
|
|
|
675
1059
|
// ======================== Default Export ========================
|
|
676
1060
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lemon-tls",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "TLS 1.3/1.2 DTLS 1.3/1.2 implementation for Node.js - full control over cryptographic keys, record layer, and handshake. Drop-in replacement for node:tls with advanced options impossible in OpenSSL.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -58,6 +58,12 @@
|
|
|
58
58
|
"engines": {
|
|
59
59
|
"node": ">=24.0.0"
|
|
60
60
|
},
|
|
61
|
+
"dependencies": {
|
|
62
|
+
"cryptographic-formats": "^0.1.0"
|
|
63
|
+
},
|
|
64
|
+
"scripts": {
|
|
65
|
+
"test": "node tests/test_all.js && node tests/test_x509_chain.js && node tests/test_keys_groups.js"
|
|
66
|
+
},
|
|
61
67
|
"keywords": [
|
|
62
68
|
"tls",
|
|
63
69
|
"dtls",
|
|
@@ -110,7 +116,6 @@
|
|
|
110
116
|
"http3",
|
|
111
117
|
"record-layer",
|
|
112
118
|
"pure-javascript",
|
|
113
|
-
"zero-dependencies",
|
|
114
119
|
"no-openssl",
|
|
115
120
|
"security",
|
|
116
121
|
"network",
|
package/src/ca_store.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// The default trust anchors: Node's CA bundle (Mozilla's)
|
|
3
|
+
// ============================================================
|
|
4
|
+
//
|
|
5
|
+
// One store for every session that is given no `ca` — client or server,
|
|
6
|
+
// TLS over TCP, QUIC (a TLSSession used directly) — and the one
|
|
7
|
+
// tls.setDefaultCACertificates() replaces. It lived in compat.js, and only
|
|
8
|
+
// connect() handed it on: a server (createServer, requestCert) and a session
|
|
9
|
+
// built directly had no anchors at all, and "no anchors" authorized every
|
|
10
|
+
// certificate.
|
|
11
|
+
//
|
|
12
|
+
// Parsing all ~144 certificates costs a few tens of milliseconds, so it is
|
|
13
|
+
// done once, lazily, and only if something actually needs it.
|
|
14
|
+
import nodeTls from 'node:tls';
|
|
15
|
+
|
|
16
|
+
let _rootPems = null;
|
|
17
|
+
function getRootCertificates() {
|
|
18
|
+
if (_rootPems === null) {
|
|
19
|
+
try { _rootPems = nodeTls.rootCertificates.slice(); }
|
|
20
|
+
catch (e) { _rootPems = []; } // a build without the bundle
|
|
21
|
+
}
|
|
22
|
+
return _rootPems;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** tls.setDefaultCACertificates(certs): the store, for every later session. */
|
|
26
|
+
function setRootCertificates(certs) {
|
|
27
|
+
if (!Array.isArray(certs)) throw new TypeError('certs must be an array');
|
|
28
|
+
_rootPems = certs.slice();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export { getRootCertificates, setRootCertificates };
|