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/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
- /** Client: trust the bundled CAs when no `ca` is given (default: true). */
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
- psk?: any;
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
- on(event: 'psk', listener: (identity: Buffer, callback: (result: { psk: Buffer; cipher: number } | null) => void) => void): this;
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 (RFC 6125).
610
- * Returns undefined on success, or an Error on mismatch.
611
- * This is the default check used when rejectUnauthorized is true; apps can
612
- * override it via the checkServerIdentity option in tls.connect.
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
- export declare class DTLSSession extends EventEmitter {
667
- constructor(options: any);
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
- export declare class DTLSSocket extends EventEmitter {
670
- constructor(options: any);
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
- export declare function createDTLSServer(options: any, connectionListener?: (socket: DTLSSocket) => void): any;
673
- export declare function connectDTLS(options: any, callback?: () => void): DTLSSocket;
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.10.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",
@@ -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 };