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/src/record.js CHANGED
@@ -273,6 +273,52 @@ function decryptRecordWithAadView(aadView, ciphertext, key, nonce, algo) {
273
273
  return pt;
274
274
  }
275
275
 
276
+ /**
277
+ * Anti-replay (RFC 6347 §4.1.2.6, RFC 9147 §4.5.1): has a record with this
278
+ * sequence number been accepted in this epoch before? `win` is the epoch's
279
+ * receive state — its read-key object — which this keeps: maxReadSeq and a
280
+ * 64-record window below it (replayLo = bits 0..31, replayHi = 32..63, bit k
281
+ * = maxReadSeq - k; no BigInt per record). True, and recorded, for a new one;
282
+ * false for a replay or one older than the window.
283
+ *
284
+ * Asked only AFTER the record authenticated: a forged record number must not
285
+ * move the window. DTLS 1.3 and DTLS 1.2 alike — 1.2 had no window at all,
286
+ * so a record received twice (replayed, or duplicated on the way) counted as
287
+ * the peer retransmitting its flight, and ours went out again (BoGo
288
+ * DTLS-Replay-TLS12: the cookie ClientHello resent).
289
+ */
290
+ function acceptRecordSeq(win, seq) {
291
+ if (win.maxReadSeq === undefined || win.maxReadSeq < 0) {
292
+ // First accepted record in this epoch: seed the window with this seq as
293
+ // the high-water mark (bit 0 set), so an immediate replay of it is
294
+ // caught rather than sailing through an unset window.
295
+ win.maxReadSeq = seq;
296
+ win.replayLo = 1; win.replayHi = 0;
297
+ return true;
298
+ }
299
+ if (seq > win.maxReadSeq) {
300
+ // Advance: shift the window up by the gap, then set bit 0.
301
+ let shift = seq - win.maxReadSeq;
302
+ let lo = win.replayLo, hi = win.replayHi;
303
+ if (shift >= 64) { lo = 0; hi = 0; }
304
+ else if (shift >= 32) { hi = (lo << (shift - 32)) >>> 0; lo = 0; }
305
+ else { hi = ((hi << shift) | (lo >>> (32 - shift))) >>> 0; lo = (lo << shift) >>> 0; }
306
+ win.replayLo = (lo | 1) >>> 0; win.replayHi = hi;
307
+ win.maxReadSeq = seq;
308
+ return true;
309
+ }
310
+ let back = win.maxReadSeq - seq;
311
+ if (back >= 64) return false; // too old — outside window
312
+ if (back < 32) {
313
+ if ((win.replayLo >>> back) & 1) return false; // already seen — replay
314
+ win.replayLo = (win.replayLo | (1 << back)) >>> 0;
315
+ } else {
316
+ if ((win.replayHi >>> (back - 32)) & 1) return false;
317
+ win.replayHi = (win.replayHi | (1 << (back - 32))) >>> 0;
318
+ }
319
+ return true;
320
+ }
321
+
276
322
  /** Strip trailing zeros and extract content_type from TLSInnerPlaintext. */
277
323
  /**
278
324
  * Strip trailing zeros and extract content_type from TLSInnerPlaintext.
@@ -284,7 +330,9 @@ function decryptRecordWithAadView(aadView, ciphertext, key, nonce, algo) {
284
330
  function parseInnerPlaintext(data) {
285
331
  let j = data.length - 1;
286
332
  while (j >= 0 && data[j] === 0) j--;
287
- if (j < 0) throw new Error('Malformed TLSInnerPlaintext');
333
+ // RFC 8446 §5.4: no non-zero octet — unexpected_message. The record was
334
+ // authenticated (the tag verified), so this is the peer's doing.
335
+ if (j < 0) throw recordError('Malformed TLSInnerPlaintext: padding only', 10);
288
336
  return { type: data[j], content: data.subarray(0, j) };
289
337
  }
290
338
 
@@ -736,138 +784,138 @@ function parseDtlsPlaintext(data) {
736
784
 
737
785
 
738
786
  // ===================== DTLS 1.2 encrypted records =====================
739
-
740
- /** DTLS 1.2 AAD: epoch(2) + seq(6) + type(1) + version(2) + plaintext_length(2). */
741
- function buildDtlsAad12(epoch, seq, type, plaintextLen) {
742
- let aad = new Uint8Array(13);
743
- let off = 0;
787
+ //
788
+ // With a Connection ID (RFC 9146) the peer asked for, a protected record is
789
+ // a tls12_cid one: outer type 25, the CID after the sequence number, and the
790
+ // real type inside, after the content (DTLSInnerPlaintext; zeros may pad it
791
+ // — we send none). Its AAD is §5.3's: the 0xff placeholder, 25, the CID
792
+ // length, 25, version, epoch, sequence number, the CID, and the length of
793
+ // the inner plaintext. `cid` below: that CID — null or empty, no CID.
794
+
795
+ const TLS12_CID = 25;
796
+ // The longest a record's body may be on the wire: TLSCiphertext's
797
+ // 2^14 + 256 in (D)TLS 1.3 (RFC 8446 §5.2), 2^14 + 2048 in 1.2 (RFC 5246
798
+ // §6.2.3) — and 1.2's for an unprotected record too, the most anything
799
+ // before the version is known can be. A DTLS record past it is dropped
800
+ // before anything is done with it: it is not the peer's, or the peer is
801
+ // broken, and which cannot be told before it is authenticated — RFC 9147
802
+ // §4.5.2, RFC 6347 §4.1.2.7: invalid records are discarded. It was
803
+ // decrypted, and its plaintext handed on whatever its size (BoGo
804
+ // LargeCiphertext-DTLS).
805
+ const DTLS13_MAX_CIPHERTEXT = 16384 + 256;
806
+ const DTLS12_MAX_RECORD = 16384 + 2048;
807
+ const SEQ_NUM_PLACEHOLDER = new Uint8Array(8).fill(0xff);
808
+
809
+ function hasCid(cid) { return !!cid && cid.length > 0; }
810
+
811
+ /** DTLS 1.2 AAD: epoch(2) + seq(6) + type(1) + version(2) + plaintext_length(2);
812
+ * with a CID, RFC 9146 §5.3's (the type is then tls12_cid). */
813
+ function buildDtlsAad12(epoch, seq, type, plaintextLen, cid) {
814
+ if (!hasCid(cid)) {
815
+ let aad = new Uint8Array(13);
816
+ let off = 0;
817
+ off = w_u16(aad, off, epoch);
818
+ off = w_u48(aad, off, seq);
819
+ off = w_u8(aad, off, type);
820
+ off = w_u16(aad, off, 0xFEFD);
821
+ off = w_u16(aad, off, plaintextLen);
822
+ return aad;
823
+ }
824
+ let aad = new Uint8Array(23 + cid.length);
825
+ aad.set(SEQ_NUM_PLACEHOLDER, 0);
826
+ let off = 8;
827
+ off = w_u8(aad, off, TLS12_CID);
828
+ off = w_u8(aad, off, cid.length);
829
+ off = w_u8(aad, off, TLS12_CID);
830
+ off = w_u16(aad, off, 0xFEFD);
744
831
  off = w_u16(aad, off, epoch);
745
832
  off = w_u48(aad, off, seq);
746
- off = w_u8(aad, off, type);
747
- off = w_u16(aad, off, 0xFEFD);
748
- off = w_u16(aad, off, plaintextLen);
833
+ aad.set(cid, off); off += cid.length;
834
+ w_u16(aad, off, plaintextLen);
749
835
  return aad;
750
836
  }
751
837
 
752
- /** Encrypt DTLS 1.2 record payload. Returns: [explicit_nonce(8) ||] ciphertext || tag(16).
753
- *
754
- * AES-GCM: prepends an 8-byte explicit nonce (the record seq); nonce = salt(4) || seq(8).
755
- * ChaCha20-Poly1305 (RFC 7905): NO explicit nonce on the wire; nonce = iv(12) XOR
756
- * (epoch(16) || seq(48)). Detected by ivSalt length (12 = ChaCha, 4 = GCM).
757
- */
758
- function encryptDtls12(pt, key, ivSalt, epoch, seq, type) {
759
- let aad = buildDtlsAad12(epoch, seq, type, pt.length);
760
-
761
- // ChaCha20-Poly1305: full 12-byte IV, no explicit nonce (RFC 7905).
762
- if (ivSalt.length === 12) {
763
- // DTLS folds the 16-bit epoch above the 48-bit seq to form the 64-bit
764
- // sequence input, matching the AAD's epoch||seq (RFC 6347 / RFC 9147).
765
- let seq64 = epoch * 0x1000000000000 + seq;
766
- let nonce = getChachaNonce12(ivSalt, seq64);
767
- let cipher = crypto.createCipheriv('chacha20-poly1305', key, nonce, { authTagLength: 16 });
768
- cipher.setAAD(aad, { plaintextLength: pt.length });
769
- let ct = cipher.update(pt);
770
- cipher.final();
771
- let tag = cipher.getAuthTag();
772
- // No explicit nonce prefix for ChaCha.
773
- let out = new Uint8Array(ct.length + tag.length);
774
- out.set(new Uint8Array(ct), 0);
775
- out.set(new Uint8Array(tag), ct.length);
776
- return out;
777
- }
778
-
779
- // AES-GCM: explicit nonce (= record seq) prepended.
780
- let explicit = seqToBytes(seq);
781
- let nonce = getNonce12(ivSalt, explicit);
782
- let algo = key.length === 16 ? 'aes-128-gcm' : 'aes-256-gcm';
783
- let cipher = crypto.createCipheriv(algo, key, nonce);
784
- cipher.setAAD(aad);
785
- let ct = cipher.update(pt);
786
- cipher.final();
787
- let tag = cipher.getAuthTag();
788
- let out = new Uint8Array(8 + ct.length + tag.length);
789
- out.set(explicit, 0);
790
- out.set(new Uint8Array(ct), 8);
791
- out.set(new Uint8Array(tag), 8 + ct.length);
792
- return out;
793
- }
794
-
795
- /** Decrypt DTLS 1.2 record fragment.
796
- * AES-GCM input: explicit_nonce(8) || ciphertext || tag(16).
797
- * ChaCha20 input: ciphertext || tag(16) (no explicit nonce — RFC 7905).
798
- */
799
- function decryptDtls12(fragment, key, ivSalt, epoch, seq, type) {
800
- // ChaCha20-Poly1305: no explicit nonce; nonce = iv(12) XOR (epoch||seq).
838
+ // The AEAD of a DTLS 1.2 record: the cipher (or decipher) and, for AES-GCM,
839
+ // the 8-byte explicit nonce (the record seq). ChaCha20-Poly1305 has none on
840
+ // the wire (RFC 7905): its nonce is iv(12) XOR (epoch(16) || seq(48)) —
841
+ // the suite told apart by the IV's length (12 for ChaCha, a 4-byte salt for
842
+ // GCM). One place for both directions.
843
+ function dtls12Aead(decrypt, key, ivSalt, epoch, seq, aad, ptLen, explicit) {
844
+ let make = decrypt ? crypto.createDecipheriv : crypto.createCipheriv;
845
+ let c;
801
846
  if (ivSalt.length === 12) {
802
- if (fragment.length < 16) throw new Error('DTLS 1.2 ChaCha fragment too short');
803
- let ct = fragment.subarray(0, fragment.length - 16); // views: only read
804
- let tag = fragment.subarray(fragment.length - 16);
805
- let seq64 = epoch * 0x1000000000000 + seq;
806
- let nonce = getChachaNonce12(ivSalt, seq64);
807
- let aad = buildDtlsAad12(epoch, seq, type, ct.length);
808
- let decipher = crypto.createDecipheriv('chacha20-poly1305', key, nonce, { authTagLength: 16 });
809
- decipher.setAAD(aad, { plaintextLength: ct.length });
810
- decipher.setAuthTag(tag);
811
- let pt = decipher.update(ct);
812
- decipher.final();
813
- // A view, not a copy: pt is a fresh Buffer that owns its whole
814
- // ArrayBuffer and nothing else holds it. Same Uint8Array type as before.
815
- return new Uint8Array(pt.buffer, pt.byteOffset, pt.byteLength);
847
+ c = make('chacha20-poly1305', key, getChachaNonce12(ivSalt, epoch * 0x1000000000000 + seq), { authTagLength: 16 });
848
+ c.setAAD(aad, { plaintextLength: ptLen });
849
+ } else {
850
+ c = make(key.length === 16 ? 'aes-128-gcm' : 'aes-256-gcm', key, getNonce12(ivSalt, explicit));
851
+ c.setAAD(aad);
816
852
  }
817
-
818
- // AES-GCM: strip the 8-byte explicit nonce.
819
- if (fragment.length < 24) throw new Error('DTLS 1.2 fragment too short');
820
- let explicit = fragment.subarray(0, 8); // views: only read
821
- let tag = fragment.subarray(fragment.length - 16);
822
- let ct = fragment.subarray(8, fragment.length - 16);
823
- let nonce = getNonce12(ivSalt, explicit);
824
- let aad = buildDtlsAad12(epoch, seq, type, ct.length);
825
- let algo = key.length === 16 ? 'aes-128-gcm' : 'aes-256-gcm';
826
- let decipher = crypto.createDecipheriv(algo, key, nonce);
827
- decipher.setAAD(aad);
828
- decipher.setAuthTag(tag);
829
- let pt = decipher.update(ct);
830
- decipher.final();
831
- // A view, not a copy — see the ChaCha branch above.
832
- return new Uint8Array(pt.buffer, pt.byteOffset, pt.byteLength);
853
+ return c;
833
854
  }
834
855
 
835
- /** Build complete encrypted DTLS 1.2 record (header + encrypted payload). */
836
- function buildEncryptedDtls12(type, epoch, seq, plaintext, keys) {
837
- // Same bytes as buildDtlsPlaintext(type, epoch, seq, encryptDtls12(...)),
838
- // written straight into the record instead of through two more buffers.
856
+ /** Build a complete protected DTLS 1.2 record: the header, then
857
+ * [explicit_nonce(8) ||] ciphertext || tag(16) — with `cid`, a tls12_cid
858
+ * record (RFC 9146 §4). */
859
+ function buildEncryptedDtls12(type, epoch, seq, plaintext, keys, cid) {
839
860
  let key = keys.key, ivSalt = keys.iv;
840
- let chacha = ivSalt.length === 12;
841
- let explicitLen = chacha ? 0 : 8;
842
- let fragLen = explicitLen + plaintext.length + 16;
843
- let out = new Uint8Array(13 + fragLen);
861
+ let withCid = hasCid(cid);
862
+ let explicitLen = ivSalt.length === 12 ? 0 : 8;
863
+ let innerLen = plaintext.length + (withCid ? 1 : 0);
864
+ let fragLen = explicitLen + innerLen + 16;
865
+ let hdrLen = 13 + (withCid ? cid.length : 0);
866
+ let out = new Uint8Array(hdrLen + fragLen);
844
867
  let off = 0;
845
- off = w_u8(out, off, type);
868
+ off = w_u8(out, off, withCid ? TLS12_CID : type);
846
869
  off = w_u16(out, off, 0xFEFD);
847
870
  off = w_u16(out, off, epoch);
848
871
  off = w_u48(out, off, seq);
872
+ if (withCid) { out.set(cid, off); off += cid.length; }
849
873
  off = w_u16(out, off, fragLen);
850
- let aad = buildDtlsAad12(epoch, seq, type, plaintext.length);
851
- let cipher;
852
- if (chacha) {
853
- let nonce = getChachaNonce12(ivSalt, epoch * 0x1000000000000 + seq);
854
- cipher = crypto.createCipheriv('chacha20-poly1305', key, nonce, { authTagLength: 16 });
855
- cipher.setAAD(aad, { plaintextLength: plaintext.length });
856
- } else {
857
- // AES-GCM: explicit nonce (= record seq) prepended.
858
- let explicit = seqToBytes(seq);
859
- out.set(explicit, off); off += 8;
860
- cipher = crypto.createCipheriv(key.length === 16 ? 'aes-128-gcm' : 'aes-256-gcm', key, getNonce12(ivSalt, explicit));
861
- cipher.setAAD(aad);
862
- }
874
+ let explicit = null;
875
+ if (explicitLen) { explicit = seqToBytes(seq); out.set(explicit, off); off += 8; }
876
+ let cipher = dtls12Aead(false, key, ivSalt, epoch, seq, buildDtlsAad12(epoch, seq, type, innerLen, cid), innerLen, explicit);
863
877
  let ct = cipher.update(plaintext);
864
878
  out.set(ct, off); off += ct.length;
879
+ if (withCid) { let t = cipher.update(_contentTypeByte(type)); out.set(t, off); off += t.length; }
865
880
  let fin = cipher.final();
866
881
  if (fin.length) { out.set(fin, off); off += fin.length; }
867
882
  out.set(cipher.getAuthTag(), off);
868
883
  return out;
869
884
  }
870
885
 
886
+ /** The protected part of a DTLS 1.2 record alone: [explicit_nonce(8) ||]
887
+ * ciphertext || tag(16). */
888
+ function encryptDtls12(pt, key, ivSalt, epoch, seq, type) {
889
+ return buildEncryptedDtls12(type, epoch, seq, pt, { key: key, iv: ivSalt }).subarray(13);
890
+ }
891
+
892
+ /** Decrypt a DTLS 1.2 record fragment ([explicit_nonce(8) ||] ciphertext ||
893
+ * tag(16)). Throws when it does not open. `type` is the record's (outer)
894
+ * type; with `cid` the AAD is RFC 9146's, and what comes back is the
895
+ * DTLSInnerPlaintext still — openDtls12 takes it apart. */
896
+ function decryptDtls12(fragment, key, ivSalt, epoch, seq, type, cid) {
897
+ let explicitLen = ivSalt.length === 12 ? 0 : 8;
898
+ if (fragment.length < explicitLen + 16) throw new Error('DTLS 1.2 fragment too short');
899
+ // Views: only read; the plaintext comes out of the decipher as a fresh
900
+ // Buffer (a Uint8Array), handed on as it is, up to 'data' — no copy.
901
+ let explicit = explicitLen ? fragment.subarray(0, 8) : null;
902
+ let ct = fragment.subarray(explicitLen, fragment.length - 16);
903
+ let decipher = dtls12Aead(true, key, ivSalt, epoch, seq, buildDtlsAad12(epoch, seq, type, ct.length, cid), ct.length, explicit);
904
+ decipher.setAuthTag(fragment.subarray(fragment.length - 16));
905
+ let pt = decipher.update(ct);
906
+ decipher.final();
907
+ return pt;
908
+ }
909
+
910
+ /** Open a protected DTLS 1.2 record: { type, content }. A tls12_cid record
911
+ * — under the CID we asked for (`cid`) — gives its real type from inside
912
+ * (RFC 9146 §4: the last non-zero byte; none at all is unexpected_message,
913
+ * as in TLS 1.3). Throws when it does not open. */
914
+ function openDtls12(fragment, keys, epoch, seq, type, cid) {
915
+ if (type !== TLS12_CID) return { type: type, content: decryptDtls12(fragment, keys.key, keys.iv, epoch, seq, type) };
916
+ return parseInnerPlaintext(decryptDtls12(fragment, keys.key, keys.iv, epoch, seq, type, cid));
917
+ }
918
+
871
919
 
872
920
  // ===================== DTLS 1.3 unified header =====================
873
921
  //
@@ -909,6 +957,17 @@ function parseUnifiedHdr(b) {
909
957
  /** Check if a byte is a DTLS 1.3 unified header (0x20..0x3F). */
910
958
  function isUnifiedHdr(b) { return (b & 0xE0) === UNIFIED_HDR_FIXED; }
911
959
 
960
+ /** The Connection ID of a datagram's first record, `cidLen` bytes, or null
961
+ * when it carries none: a tls12_cid record's (after the 11-byte prefix),
962
+ * or a unified header's with the C bit (after its first byte). What a
963
+ * server finds a connection by, whatever address it comes from. */
964
+ function datagramCid(data, cidLen) {
965
+ if (!data || cidLen <= 0) return null;
966
+ if (data[0] === TLS12_CID) return data.length >= 13 + cidLen ? data.subarray(11, 11 + cidLen) : null;
967
+ if (isUnifiedHdr(data[0]) && parseUnifiedHdr(data[0]).hasCid) return data.length > cidLen ? data.subarray(1, 1 + cidLen) : null;
968
+ return null;
969
+ }
970
+
912
971
 
913
972
  // ===================== DTLS 1.3 record number encryption =====================
914
973
 
@@ -962,13 +1021,18 @@ function maskRecordNumber(snKey, rnBytes, ciphertext, algo) {
962
1021
  * seq: record sequence number
963
1022
  * epoch: low 2 bits (2=handshake, 3=application)
964
1023
  * keys: { key, iv, snKey, algo? }
1024
+ * cid: the peer's Connection ID (RFC 9147 §4: C bit, the CID after
1025
+ * the first byte; its length is the one negotiated, not on the
1026
+ * wire), or null / empty for none
965
1027
  */
966
- function buildEncryptedDtls13(innerType, plaintext, seq, epoch, keys) {
1028
+ function buildEncryptedDtls13(innerType, plaintext, seq, epoch, keys, cid) {
967
1029
  let algo = keys.algo || (keys.key.length === 32 ? 'aes-256-gcm' : 'aes-128-gcm');
968
1030
  let isChaCha = algo === 'chacha20-poly1305';
1031
+ let cidLen = cid ? cid.length : 0;
969
1032
 
970
- // Unified header: 2-byte seq, with length, no CID
971
- let info = buildUnifiedHdr(epoch, true, true, false);
1033
+ // Unified header: [CID], 2-byte seq, with length
1034
+ let info = buildUnifiedHdr(epoch, true, true, cidLen > 0);
1035
+ let rnOff = 1 + cidLen, hdrLen = rnOff + 4;
972
1036
 
973
1037
  // Plaintext record number
974
1038
  let rn0 = (seq >>> 8) & 0xFF, rn1 = seq & 0xFF;
@@ -978,13 +1042,15 @@ function buildEncryptedDtls13(innerType, plaintext, seq, epoch, keys) {
978
1042
  let innerLen = plaintext.length + 1;
979
1043
  let encLen = innerLen + 16;
980
1044
 
981
- // AAD = header with PLAINTEXT record number (before RN encryption)
982
- let aad = new Uint8Array(5);
1045
+ // AAD = the header with the PLAINTEXT record number (before RN
1046
+ // encryption) — the CID in it (§4.2.3: the whole header).
1047
+ let aad = new Uint8Array(hdrLen);
983
1048
  aad[0] = info;
984
- aad[1] = rn0;
985
- aad[2] = rn1;
986
- aad[3] = (encLen >>> 8) & 0xFF;
987
- aad[4] = encLen & 0xFF;
1049
+ if (cidLen) aad.set(cid, 1);
1050
+ aad[rnOff] = rn0;
1051
+ aad[rnOff + 1] = rn1;
1052
+ aad[rnOff + 2] = (encLen >>> 8) & 0xFF;
1053
+ aad[rnOff + 3] = encLen & 0xFF;
988
1054
 
989
1055
  // Nonce (reuse TLS 1.3 nonce construction)
990
1056
  let nonce = getNonce(keys.iv, seq);
@@ -995,8 +1061,8 @@ function buildEncryptedDtls13(innerType, plaintext, seq, epoch, keys) {
995
1061
  let cipher = crypto.createCipheriv(algo, keys.key, nonce,
996
1062
  isChaCha ? { authTagLength: 16 } : undefined);
997
1063
  cipher.setAAD(aad, isChaCha ? { plaintextLength: innerLen } : undefined);
998
- let record = new Uint8Array(5 + encLen);
999
- let off = 5;
1064
+ let record = new Uint8Array(hdrLen + encLen);
1065
+ let off = hdrLen;
1000
1066
  let ct1 = cipher.update(plaintext);
1001
1067
  record.set(ct1, off); off += ct1.length;
1002
1068
  let ct2 = cipher.update(_contentTypeByte(innerType));
@@ -1004,7 +1070,7 @@ function buildEncryptedDtls13(innerType, plaintext, seq, epoch, keys) {
1004
1070
  let ct3 = cipher.final();
1005
1071
  if (ct3.length) { record.set(ct3, off); off += ct3.length; }
1006
1072
  record.set(cipher.getAuthTag(), off);
1007
- let ciphertext = record.subarray(5);
1073
+ let ciphertext = record.subarray(hdrLen);
1008
1074
 
1009
1075
  // Encrypt record number
1010
1076
  let encRn = maskRecordNumber(keys.snKey, [rn0, rn1], ciphertext, algo);
@@ -1033,11 +1099,9 @@ function buildEncryptedDtls13(innerType, plaintext, seq, epoch, keys) {
1033
1099
  }
1034
1100
  // ─────────────────────────────────────────────────────────────────────
1035
1101
 
1036
- record[0] = info;
1037
- record[1] = encRn[0];
1038
- record[2] = encRn[1];
1039
- record[3] = (ciphertext.length >>> 8) & 0xFF;
1040
- record[4] = ciphertext.length & 0xFF;
1102
+ record.set(aad, 0);
1103
+ record[rnOff] = encRn[0];
1104
+ record[rnOff + 1] = encRn[1];
1041
1105
  return record;
1042
1106
  }
1043
1107
 
@@ -1119,15 +1183,27 @@ function resolveReadEpoch(epochBits, keysByEpoch) {
1119
1183
  * Decrypt a DTLS 1.3 encrypted record.
1120
1184
  * data: full record bytes (starting with unified header byte)
1121
1185
  * keys: { key, iv, snKey, algo?, maxReadSeq? }
1186
+ * cid: the Connection ID we asked the peer for, or null / empty. A record
1187
+ * with a CID when there is none, or another, does not open;
1188
+ * cidRequired: the peer agreed to it — then nor does one without it (§4:
1189
+ * "it MUST be contained in all datagrams"), but in epoch 1, early data
1190
+ * sent before the CID was known.
1122
1191
  * Returns { epoch, seq, type, content } or null on failure.
1123
1192
  */
1124
- function decryptEncryptedDtls13(data, keys, fullEpoch) {
1193
+ function decryptEncryptedDtls13(data, keys, fullEpoch, cid, cidRequired) {
1125
1194
  if (data.length < 1) return null;
1126
1195
 
1127
1196
  let hdr = parseUnifiedHdr(data[0]);
1128
1197
  let off = 1;
1129
-
1130
- if (hdr.hasCid) return null; // CID not supported yet
1198
+ let cidLen = cid ? cid.length : 0;
1199
+
1200
+ if (hdr.hasCid) {
1201
+ if (cidLen === 0 || data.length < 1 + cidLen) return null;
1202
+ for (let i = 0; i < cidLen; i++) if (data[1 + i] !== cid[i]) return null;
1203
+ off += cidLen;
1204
+ } else if (cidRequired && fullEpoch !== 1) {
1205
+ return null;
1206
+ }
1131
1207
 
1132
1208
  let rnLen = hdr.seqLen2 ? 2 : 1;
1133
1209
  if (off + rnLen > data.length) return null;
@@ -1144,6 +1220,7 @@ function decryptEncryptedDtls13(data, keys, fullEpoch) {
1144
1220
  }
1145
1221
 
1146
1222
  if (off + ctLen > data.length) return null;
1223
+ if (ctLen > DTLS13_MAX_CIPHERTEXT) return null;
1147
1224
  // A view: only read here (record-number mask, AEAD input); the plaintext
1148
1225
  // comes out of the decipher as a fresh buffer.
1149
1226
  let ciphertext = data.subarray(off, off + ctLen);
@@ -1175,13 +1252,13 @@ function decryptEncryptedDtls13(data, keys, fullEpoch) {
1175
1252
  // Rebuild AAD with plaintext RN. AAD depends only on the wire bytes (the
1176
1253
  // decrypted record number + length), NOT on the reconstructed full seq —
1177
1254
  // so it's computed once and reused across seq candidates below.
1178
- let hdrLen = 1 + rnLen + (hdr.hasLength ? 2 : 0);
1179
- let aad = new Uint8Array(hdrLen);
1180
- aad[0] = data[0];
1181
- for (let i = 0; i < rnLen; i++) aad[1 + i] = rn[i];
1255
+ let rnOff = off - rnLen - (hdr.hasLength ? 2 : 0);
1256
+ let aad = new Uint8Array(off);
1257
+ aad.set(data.subarray(0, rnOff), 0); // the first byte, and the CID
1258
+ for (let i = 0; i < rnLen; i++) aad[rnOff + i] = rn[i];
1182
1259
  if (hdr.hasLength) {
1183
- aad[1 + rnLen] = (ctLen >>> 8) & 0xFF;
1184
- aad[1 + rnLen + 1] = ctLen & 0xFF;
1260
+ aad[rnOff + rnLen] = (ctLen >>> 8) & 0xFF;
1261
+ aad[rnOff + rnLen + 1] = ctLen & 0xFF;
1185
1262
  }
1186
1263
 
1187
1264
  let ct = ciphertext.subarray(0, ciphertext.length - 16);
@@ -1221,45 +1298,13 @@ function decryptEncryptedDtls13(data, keys, fullEpoch) {
1221
1298
  seq = acceptedSeq;
1222
1299
 
1223
1300
  {
1224
- // A view, not a copy: pt is the decipher's fresh Buffer, owned by no one else.
1225
- let inner = parseInnerPlaintext(new Uint8Array(pt.buffer, pt.byteOffset, pt.byteLength));
1226
-
1227
- // RFC 9147 §4.5.1 anti-replay: only AFTER the tag verifies do we consult
1228
- // and update the sliding window. Checking before deprotection would let a
1229
- // forged record number poison the window; checking after means only
1230
- // genuine records move it. The window lives on the per-epoch key object,
1231
- // next to maxReadSeq, because both are per-epoch receive state.
1232
- // replayWindow: bitmask of the 64 seqs at or below maxReadSeq that have
1233
- // been accepted. Bit 0 = maxReadSeq, bit k = maxReadSeq - k.
1234
- // The 64-bit window is kept as two 32-bit numbers (replayLo = bits
1235
- // 0..31, replayHi = 32..63): same window, no BigInt allocations per
1236
- // record.
1237
- if (keys.maxReadSeq === undefined || keys.maxReadSeq < 0) {
1238
- // First accepted record in this epoch: seed the window with this seq as
1239
- // the high-water mark (bit 0 set), so an immediate replay of it is
1240
- // caught below rather than sailing through an unset window.
1241
- keys.maxReadSeq = seq;
1242
- keys.replayLo = 1; keys.replayHi = 0;
1243
- } else if (seq > keys.maxReadSeq) {
1244
- // Advance: shift the window up by the gap, then set bit 0.
1245
- let shift = seq - keys.maxReadSeq;
1246
- let lo = keys.replayLo, hi = keys.replayHi;
1247
- if (shift >= 64) { lo = 0; hi = 0; }
1248
- else if (shift >= 32) { hi = (lo << (shift - 32)) >>> 0; lo = 0; }
1249
- else { hi = ((hi << shift) | (lo >>> (32 - shift))) >>> 0; lo = (lo << shift) >>> 0; }
1250
- keys.replayLo = (lo | 1) >>> 0; keys.replayHi = hi;
1251
- keys.maxReadSeq = seq;
1252
- } else {
1253
- let back = keys.maxReadSeq - seq;
1254
- if (back >= 64) return null; // too old — outside window
1255
- if (back < 32) {
1256
- if ((keys.replayLo >>> back) & 1) return null; // already seen — replay
1257
- keys.replayLo = (keys.replayLo | (1 << back)) >>> 0;
1258
- } else {
1259
- if ((keys.replayHi >>> (back - 32)) & 1) return null;
1260
- keys.replayHi = (keys.replayHi | (1 << (back - 32))) >>> 0;
1261
- }
1262
- }
1301
+ // pt is the decipher's fresh Buffer, owned by no one else: the content is
1302
+ // a subarray of it — a Buffer, all the way up to 'data', with no copy.
1303
+ let inner = parseInnerPlaintext(pt);
1304
+
1305
+ // RFC 9147 §4.5.1 anti-replay: only AFTER the tag verifies (see
1306
+ // acceptRecordSeq).
1307
+ if (!acceptRecordSeq(keys, seq)) return null;
1263
1308
 
1264
1309
  return {
1265
1310
  // The caller resolves the full epoch (resolveReadEpoch); hdr.epoch is
@@ -1281,17 +1326,26 @@ function decryptEncryptedDtls13(data, keys, fullEpoch) {
1281
1326
  * Dispatches between plaintext (classic header) and encrypted (unified header).
1282
1327
  *
1283
1328
  * keysByEpoch: { [epoch]: { key, iv, snKey, algo } } — null for plaintext only.
1329
+ * cid: the Connection ID we asked the peer for (RFC 9146, RFC 9147
1330
+ * §9), or null / empty — from the moment it is asked for: its
1331
+ * length is what finds the boundaries of a record carrying it,
1332
+ * which may come in the datagram that says it was agreed to. A
1333
+ * datagram of CID records when we asked for none is not one of
1334
+ * ours, and ends here.
1335
+ * cidRequired: the peer agreed to it: every protected record carries it.
1284
1336
  * Returns array of { type, epoch, seq, content, encrypted }.
1285
1337
  */
1286
- function parseDtlsDatagram(data, keysByEpoch) {
1338
+ function parseDtlsDatagram(data, keysByEpoch, cid, cidRequired) {
1287
1339
  let records = [];
1288
1340
  let off = 0;
1341
+ let cidLen = cid ? cid.length : 0;
1289
1342
 
1290
1343
  while (off < data.length) {
1291
1344
  let first = data[off];
1292
1345
 
1293
1346
  if (isUnifiedHdr(first)) {
1294
1347
  let hdr = parseUnifiedHdr(first);
1348
+ if (hdr.hasCid && cidLen === 0) break;
1295
1349
  // Header carries 2 epoch bits; map them back to a real epoch first.
1296
1350
  let fullEpoch = resolveReadEpoch(hdr.epoch, keysByEpoch);
1297
1351
  let keys = (fullEpoch === null) ? null : keysByEpoch[fullEpoch];
@@ -1307,8 +1361,7 @@ function parseDtlsDatagram(data, keysByEpoch) {
1307
1361
  // Keep the raw bytes and mark the record deferred; the caller retries
1308
1362
  // it after processing each earlier record, once the keys exist. This
1309
1363
  // generalises the CCS-only re-decrypt that DTLS 1.2 already had.
1310
- let rnLen = hdr.seqLen2 ? 2 : 1;
1311
- let skip = 1 + rnLen;
1364
+ let skip = 1 + (hdr.hasCid ? cidLen : 0) + (hdr.seqLen2 ? 2 : 1);
1312
1365
  let total;
1313
1366
  if (hdr.hasLength && off + skip + 2 <= data.length) {
1314
1367
  total = skip + 2 + readU16(data, off + skip);
@@ -1333,7 +1386,7 @@ function parseDtlsDatagram(data, keysByEpoch) {
1333
1386
  continue;
1334
1387
  }
1335
1388
 
1336
- let result = decryptEncryptedDtls13(data.subarray(off), keys, fullEpoch);
1389
+ let result = decryptEncryptedDtls13(data.subarray(off), keys, fullEpoch, cid, cidRequired);
1337
1390
  if (result) {
1338
1391
  records.push({
1339
1392
  type: result.type,
@@ -1348,13 +1401,32 @@ function parseDtlsDatagram(data, keysByEpoch) {
1348
1401
  }
1349
1402
 
1350
1403
  } else if (first <= 63) {
1351
- // Classic DTLS record
1352
- if (off + 13 > data.length) break;
1404
+ // Classic DTLS record — or a tls12_cid one (RFC 9146 §4), the CID
1405
+ // between the sequence number and the length.
1406
+ let withCid = first === TLS12_CID;
1407
+ if (withCid && cidLen === 0) break;
1408
+ let hdrLen = 13 + (withCid ? cidLen : 0);
1409
+ if (off + hdrLen > data.length) break;
1353
1410
  let type = data[off];
1354
1411
  let epoch = readU16(data, off + 3);
1355
1412
  let seq = readU48(data, off + 5);
1356
- let length = readU16(data, off + 11);
1357
- if (off + 13 + length > data.length) break;
1413
+ let length = readU16(data, off + hdrLen - 2);
1414
+ if (off + hdrLen + length > data.length) break;
1415
+ let fragment = data.subarray(off + hdrLen, off + hdrLen + length);
1416
+ let at = off;
1417
+ off += hdrLen + length;
1418
+ if (length > DTLS12_MAX_RECORD) continue;
1419
+
1420
+ // Ours, by its CID — or not. And with a CID asked for, every protected
1421
+ // record carries it (§3: "MUST send with the record format"); one
1422
+ // without is not this connection's.
1423
+ if (withCid) {
1424
+ let mine = true;
1425
+ for (let i = 0; i < cidLen; i++) if (data[at + 11 + i] !== cid[i]) { mine = false; break; }
1426
+ if (!mine) continue;
1427
+ } else if (cidRequired && epoch > 0) {
1428
+ continue;
1429
+ }
1358
1430
 
1359
1431
  let payload = null;
1360
1432
  let encrypted = false;
@@ -1365,14 +1437,20 @@ function parseDtlsDatagram(data, keysByEpoch) {
1365
1437
  // decrypted — gets its own copy, as before.
1366
1438
  if (epoch > 0 && keysByEpoch && keysByEpoch[epoch]) {
1367
1439
  let keys = keysByEpoch[epoch];
1368
- try {
1369
- payload = decryptDtls12(data.subarray(off + 13, off + 13 + length), keys.key, keys.iv, epoch, seq, type);
1370
- encrypted = true;
1371
- } catch(e) {
1372
- // Decryption failed — return raw
1440
+ let opened = null;
1441
+ try { opened = openDtls12(fragment, keys, epoch, seq, type, cid); }
1442
+ catch (e) {
1443
+ // Opened, and malformed inside: the peer's (alertDesc). Otherwise
1444
+ // it did not open — kept raw.
1445
+ if (e && e.alertDesc) throw e;
1446
+ }
1447
+ if (opened) {
1448
+ // Authenticated, and seen before: a replay — dropped, as in 1.3.
1449
+ if (!acceptRecordSeq(keys, seq)) continue;
1450
+ type = opened.type; payload = opened.content; encrypted = true;
1373
1451
  }
1374
1452
  }
1375
- if (payload === null) payload = data.slice(off + 13, off + 13 + length);
1453
+ if (payload === null) payload = fragment.slice();
1376
1454
 
1377
1455
  records.push({
1378
1456
  type: type,
@@ -1381,7 +1459,6 @@ function parseDtlsDatagram(data, keysByEpoch) {
1381
1459
  content: payload,
1382
1460
  encrypted: encrypted,
1383
1461
  });
1384
- off += 13 + length;
1385
1462
  } else {
1386
1463
  break;
1387
1464
  }
@@ -1426,16 +1503,31 @@ function buildDtlsAck(acks) {
1426
1503
  }
1427
1504
 
1428
1505
  /** Parse ACK payload. Returns [{ epoch, seq }, ...]. */
1506
+ // A malformed record body from an authenticated record: decode_error (or
1507
+ // the given alert), carried as .alertDesc like wire.js's parse errors.
1508
+ function recordError(msg, alertDesc) {
1509
+ let e = new Error(msg);
1510
+ e.alertDesc = alertDesc === undefined ? 50 : alertDesc;
1511
+ return e;
1512
+ }
1513
+
1514
+ // Strict (RFC 9147 §7): the vector fills the record exactly, in 16-byte
1515
+ // RecordNumbers, each within what a DTLS record number can be (an epoch
1516
+ // carried as a u16 on the wire of every record, a 48-bit sequence number).
1517
+ // A short body, a length that does not match, a partial entry or a number
1518
+ // beyond those was read as far as it went, or past it.
1429
1519
  function parseDtlsAck(data) {
1520
+ if (!data || data.length < 2) throw recordError('ACK: truncated');
1430
1521
  let bodyLen = readU16(data, 0);
1431
- let off = 2;
1432
- let end = off + bodyLen;
1522
+ if (2 + bodyLen !== data.length) throw recordError('ACK: record_numbers of ' + bodyLen + ' bytes in a body of ' + (data.length - 2));
1523
+ if (bodyLen % 16 !== 0) throw recordError('ACK: record_numbers not a whole number of entries');
1433
1524
  let acks = [];
1434
- // 16 bytes per RecordNumber — see buildDtlsAck.
1435
- while (off + 16 <= end && off + 16 <= data.length) {
1436
- let epoch = readU64(data, off); off += 8;
1437
- let seq = readU64(data, off); off += 8;
1438
- acks.push({ epoch, seq });
1525
+ for (let off = 2; off < data.length; off += 16) {
1526
+ if (data[off] | data[off + 1] | data[off + 2] | data[off + 3] | data[off + 4] | data[off + 5] ||
1527
+ data[off + 8] | data[off + 9]) {
1528
+ throw recordError('ACK: a record number out of range');
1529
+ }
1530
+ acks.push({ epoch: readU64(data, off), seq: readU48(data, off + 10) });
1439
1531
  }
1440
1532
  return acks;
1441
1533
  }
@@ -1477,6 +1569,7 @@ export {
1477
1569
  // DTLS helpers
1478
1570
  aesEcbEncrypt,
1479
1571
  isUnifiedHdr,
1572
+ datagramCid,
1480
1573
 
1481
1574
  // DTLS plaintext records
1482
1575
  buildDtlsPlaintext,
@@ -1484,9 +1577,12 @@ export {
1484
1577
 
1485
1578
  // DTLS 1.2 encrypted records
1486
1579
  buildDtlsAad12,
1580
+ acceptRecordSeq,
1487
1581
  encryptDtls12,
1488
1582
  decryptDtls12,
1583
+ openDtls12,
1489
1584
  buildEncryptedDtls12,
1585
+ TLS12_CID,
1490
1586
 
1491
1587
  // DTLS 1.3 unified header
1492
1588
  buildUnifiedHdr,