lemon-tls 0.9.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 +168 -41
- package/index.cjs +9 -7
- package/index.d.ts +626 -30
- package/package.json +45 -7
- package/src/ca_store.js +31 -0
- package/src/compat.js +76 -116
- package/src/crypto.js +21 -114
- package/src/dtls_session.js +1284 -390
- package/src/dtls_socket.js +318 -96
- package/src/node_options.js +154 -17
- package/src/record.js +355 -191
- package/src/secure_context.js +97 -39
- 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 +2221 -902
- package/src/tls_socket.js +451 -452
- package/src/wire.js +271 -158
- package/types/crypto.d.ts +40 -0
- package/types/record.d.ts +40 -0
- package/types/session.d.ts +3 -0
- package/types/wire.d.ts +62 -0
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
|
-
|
|
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
|
|
|
@@ -665,6 +713,21 @@ function readU48(buf, off) {
|
|
|
665
713
|
}
|
|
666
714
|
|
|
667
715
|
/** AES-ECB encrypt a single 16-byte block (for DTLS 1.3 record number encryption). */
|
|
716
|
+
// One AES-ECB cipher per sn_key, reused for every record's number mask
|
|
717
|
+
// (RFC 9147 §4.2.3) instead of creating one per record. ECB keeps no state
|
|
718
|
+
// between blocks, so update() on 16 bytes always returns exactly that block's
|
|
719
|
+
// encryption. Keyed by the key object, which lives as long as its epoch's keys.
|
|
720
|
+
var _snEcbCache = new WeakMap();
|
|
721
|
+
function _snKeyEcb(key) {
|
|
722
|
+
var c = _snEcbCache.get(key);
|
|
723
|
+
if (!c) {
|
|
724
|
+
c = crypto.createCipheriv(key.length === 32 ? 'aes-256-ecb' : 'aes-128-ecb', key, null);
|
|
725
|
+
c.setAutoPadding(false);
|
|
726
|
+
_snEcbCache.set(key, c);
|
|
727
|
+
}
|
|
728
|
+
return c;
|
|
729
|
+
}
|
|
730
|
+
|
|
668
731
|
function aesEcbEncrypt(key, block) {
|
|
669
732
|
let algo = key.length === 32 ? 'aes-256-ecb' : 'aes-128-ecb';
|
|
670
733
|
let cipher = crypto.createCipheriv(algo, key, null);
|
|
@@ -721,106 +784,136 @@ function parseDtlsPlaintext(data) {
|
|
|
721
784
|
|
|
722
785
|
|
|
723
786
|
// ===================== DTLS 1.2 encrypted records =====================
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
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);
|
|
729
831
|
off = w_u16(aad, off, epoch);
|
|
730
832
|
off = w_u48(aad, off, seq);
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
off = w_u16(aad, off, plaintextLen);
|
|
833
|
+
aad.set(cid, off); off += cid.length;
|
|
834
|
+
w_u16(aad, off, plaintextLen);
|
|
734
835
|
return aad;
|
|
735
836
|
}
|
|
736
837
|
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
let
|
|
745
|
-
|
|
746
|
-
// ChaCha20-Poly1305: full 12-byte IV, no explicit nonce (RFC 7905).
|
|
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;
|
|
747
846
|
if (ivSalt.length === 12) {
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
cipher.setAAD(aad, { plaintextLength: pt.length });
|
|
754
|
-
let ct = cipher.update(pt);
|
|
755
|
-
cipher.final();
|
|
756
|
-
let tag = cipher.getAuthTag();
|
|
757
|
-
// No explicit nonce prefix for ChaCha.
|
|
758
|
-
let out = new Uint8Array(ct.length + tag.length);
|
|
759
|
-
out.set(new Uint8Array(ct), 0);
|
|
760
|
-
out.set(new Uint8Array(tag), ct.length);
|
|
761
|
-
return out;
|
|
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);
|
|
762
852
|
}
|
|
853
|
+
return c;
|
|
854
|
+
}
|
|
763
855
|
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
let
|
|
769
|
-
|
|
770
|
-
let
|
|
771
|
-
|
|
772
|
-
let
|
|
773
|
-
let
|
|
774
|
-
out
|
|
775
|
-
|
|
776
|
-
|
|
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) {
|
|
860
|
+
let key = keys.key, ivSalt = keys.iv;
|
|
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);
|
|
867
|
+
let off = 0;
|
|
868
|
+
off = w_u8(out, off, withCid ? TLS12_CID : type);
|
|
869
|
+
off = w_u16(out, off, 0xFEFD);
|
|
870
|
+
off = w_u16(out, off, epoch);
|
|
871
|
+
off = w_u48(out, off, seq);
|
|
872
|
+
if (withCid) { out.set(cid, off); off += cid.length; }
|
|
873
|
+
off = w_u16(out, off, fragLen);
|
|
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);
|
|
877
|
+
let ct = cipher.update(plaintext);
|
|
878
|
+
out.set(ct, off); off += ct.length;
|
|
879
|
+
if (withCid) { let t = cipher.update(_contentTypeByte(type)); out.set(t, off); off += t.length; }
|
|
880
|
+
let fin = cipher.final();
|
|
881
|
+
if (fin.length) { out.set(fin, off); off += fin.length; }
|
|
882
|
+
out.set(cipher.getAuthTag(), off);
|
|
777
883
|
return out;
|
|
778
884
|
}
|
|
779
885
|
|
|
780
|
-
/**
|
|
781
|
-
*
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
// ChaCha20-Poly1305: no explicit nonce; nonce = iv(12) XOR (epoch||seq).
|
|
786
|
-
if (ivSalt.length === 12) {
|
|
787
|
-
if (fragment.length < 16) throw new Error('DTLS 1.2 ChaCha fragment too short');
|
|
788
|
-
let ct = fragment.slice(0, fragment.length - 16);
|
|
789
|
-
let tag = fragment.slice(fragment.length - 16);
|
|
790
|
-
let seq64 = epoch * 0x1000000000000 + seq;
|
|
791
|
-
let nonce = getChachaNonce12(ivSalt, seq64);
|
|
792
|
-
let aad = buildDtlsAad12(epoch, seq, type, ct.length);
|
|
793
|
-
let decipher = crypto.createDecipheriv('chacha20-poly1305', key, nonce, { authTagLength: 16 });
|
|
794
|
-
decipher.setAAD(aad, { plaintextLength: ct.length });
|
|
795
|
-
decipher.setAuthTag(tag);
|
|
796
|
-
let pt = decipher.update(ct);
|
|
797
|
-
decipher.final();
|
|
798
|
-
// A view, not a copy: pt is a fresh Buffer that owns its whole
|
|
799
|
-
// ArrayBuffer and nothing else holds it. Same Uint8Array type as before.
|
|
800
|
-
return new Uint8Array(pt.buffer, pt.byteOffset, pt.byteLength);
|
|
801
|
-
}
|
|
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
|
+
}
|
|
802
891
|
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
let
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
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));
|
|
814
905
|
let pt = decipher.update(ct);
|
|
815
906
|
decipher.final();
|
|
816
|
-
|
|
817
|
-
return new Uint8Array(pt.buffer, pt.byteOffset, pt.byteLength);
|
|
907
|
+
return pt;
|
|
818
908
|
}
|
|
819
909
|
|
|
820
|
-
/**
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
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));
|
|
824
917
|
}
|
|
825
918
|
|
|
826
919
|
|
|
@@ -864,6 +957,17 @@ function parseUnifiedHdr(b) {
|
|
|
864
957
|
/** Check if a byte is a DTLS 1.3 unified header (0x20..0x3F). */
|
|
865
958
|
function isUnifiedHdr(b) { return (b & 0xE0) === UNIFIED_HDR_FIXED; }
|
|
866
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
|
+
|
|
867
971
|
|
|
868
972
|
// ===================== DTLS 1.3 record number encryption =====================
|
|
869
973
|
|
|
@@ -898,7 +1002,7 @@ function maskRecordNumber(snKey, rnBytes, ciphertext, algo) {
|
|
|
898
1002
|
let c = crypto.createCipheriv('chacha20', snKey, iv);
|
|
899
1003
|
mask = new Uint8Array(c.update(Buffer.alloc(16)));
|
|
900
1004
|
} else {
|
|
901
|
-
mask =
|
|
1005
|
+
mask = _snKeyEcb(snKey).update(ciphertext.subarray(0, 16));
|
|
902
1006
|
}
|
|
903
1007
|
|
|
904
1008
|
let out = new Uint8Array(rnBytes.length);
|
|
@@ -917,51 +1021,59 @@ function maskRecordNumber(snKey, rnBytes, ciphertext, algo) {
|
|
|
917
1021
|
* seq: record sequence number
|
|
918
1022
|
* epoch: low 2 bits (2=handshake, 3=application)
|
|
919
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
|
|
920
1027
|
*/
|
|
921
|
-
function buildEncryptedDtls13(innerType, plaintext, seq, epoch, keys) {
|
|
1028
|
+
function buildEncryptedDtls13(innerType, plaintext, seq, epoch, keys, cid) {
|
|
922
1029
|
let algo = keys.algo || (keys.key.length === 32 ? 'aes-256-gcm' : 'aes-128-gcm');
|
|
923
1030
|
let isChaCha = algo === 'chacha20-poly1305';
|
|
1031
|
+
let cidLen = cid ? cid.length : 0;
|
|
924
1032
|
|
|
925
|
-
// Unified header: 2-byte seq, with length
|
|
926
|
-
let info = buildUnifiedHdr(epoch, true, true,
|
|
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;
|
|
927
1036
|
|
|
928
1037
|
// Plaintext record number
|
|
929
|
-
let
|
|
930
|
-
rn[0] = (seq >>> 8) & 0xFF;
|
|
931
|
-
rn[1] = seq & 0xFF;
|
|
932
|
-
|
|
933
|
-
// Inner plaintext: content + content_type
|
|
934
|
-
let inner = new Uint8Array(plaintext.length + 1);
|
|
935
|
-
inner.set(plaintext, 0);
|
|
936
|
-
inner[plaintext.length] = innerType;
|
|
1038
|
+
let rn0 = (seq >>> 8) & 0xFF, rn1 = seq & 0xFF;
|
|
937
1039
|
|
|
938
|
-
|
|
1040
|
+
// Inner plaintext = content || content_type. Fed to the cipher in two
|
|
1041
|
+
// pieces rather than first copied into one buffer.
|
|
1042
|
+
let innerLen = plaintext.length + 1;
|
|
1043
|
+
let encLen = innerLen + 16;
|
|
939
1044
|
|
|
940
|
-
// AAD = header with PLAINTEXT record number (before RN
|
|
941
|
-
|
|
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);
|
|
942
1048
|
aad[0] = info;
|
|
943
|
-
aad
|
|
944
|
-
aad[
|
|
945
|
-
aad[
|
|
946
|
-
aad[
|
|
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;
|
|
947
1054
|
|
|
948
1055
|
// Nonce (reuse TLS 1.3 nonce construction)
|
|
949
1056
|
let nonce = getNonce(keys.iv, seq);
|
|
950
1057
|
|
|
951
|
-
// AEAD encrypt
|
|
1058
|
+
// AEAD encrypt, written straight into the record: info(1) + encrypted
|
|
1059
|
+
// rn(2) + length(2) + ciphertext. (It used to pass through three buffers:
|
|
1060
|
+
// inner plaintext, ciphertext+tag, then the record.)
|
|
952
1061
|
let cipher = crypto.createCipheriv(algo, keys.key, nonce,
|
|
953
1062
|
isChaCha ? { authTagLength: 16 } : undefined);
|
|
954
|
-
cipher.setAAD(aad, isChaCha ? { plaintextLength:
|
|
955
|
-
let
|
|
956
|
-
|
|
957
|
-
let
|
|
958
|
-
|
|
959
|
-
let
|
|
960
|
-
|
|
961
|
-
|
|
1063
|
+
cipher.setAAD(aad, isChaCha ? { plaintextLength: innerLen } : undefined);
|
|
1064
|
+
let record = new Uint8Array(hdrLen + encLen);
|
|
1065
|
+
let off = hdrLen;
|
|
1066
|
+
let ct1 = cipher.update(plaintext);
|
|
1067
|
+
record.set(ct1, off); off += ct1.length;
|
|
1068
|
+
let ct2 = cipher.update(_contentTypeByte(innerType));
|
|
1069
|
+
record.set(ct2, off); off += ct2.length;
|
|
1070
|
+
let ct3 = cipher.final();
|
|
1071
|
+
if (ct3.length) { record.set(ct3, off); off += ct3.length; }
|
|
1072
|
+
record.set(cipher.getAuthTag(), off);
|
|
1073
|
+
let ciphertext = record.subarray(hdrLen);
|
|
962
1074
|
|
|
963
1075
|
// Encrypt record number
|
|
964
|
-
let encRn = maskRecordNumber(keys.snKey,
|
|
1076
|
+
let encRn = maskRecordNumber(keys.snKey, [rn0, rn1], ciphertext, algo);
|
|
965
1077
|
|
|
966
1078
|
// ── DIAGNOSTIC (LEMON_DEBUG=1 / WEBRTC_DEBUG=1) ──────────────────────
|
|
967
1079
|
// Everything a peer needs to PARSE this record, printed as we build it.
|
|
@@ -971,34 +1083,37 @@ function buildEncryptedDtls13(innerType, plaintext, seq, epoch, keys) {
|
|
|
971
1083
|
// present), the sequence number before and after masking, and the
|
|
972
1084
|
// declared length against the actual ciphertext size. If two records
|
|
973
1085
|
// are accepted and the third is not, the difference between them shows
|
|
974
|
-
// up here.
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
(process.env.LEMON_DEBUG === '1' || process.env.WEBRTC_DEBUG === '1')) {
|
|
1086
|
+
// up here. (Flag read once at load: process.env is slow to read per record.)
|
|
1087
|
+
if (_REC_DEBUG) {
|
|
1088
|
+
try {
|
|
978
1089
|
var _h = function (b) { return ('0' + (b & 0xFF).toString(16)).slice(-2); };
|
|
979
1090
|
console.log('[lemon-rec] TX info=0x' + _h(info) +
|
|
980
1091
|
' [C=' + ((info >> 4) & 1) + ' S=' + ((info >> 3) & 1) +
|
|
981
1092
|
' L=' + ((info >> 2) & 1) + ' epoch=' + (info & 0x03) + ']' +
|
|
982
|
-
' seqPlain=' + _h(
|
|
1093
|
+
' seqPlain=' + _h(rn0) + _h(rn1) +
|
|
983
1094
|
' seqMasked=' + _h(encRn[0]) + _h(encRn[1]) +
|
|
984
1095
|
' declaredLen=' + ciphertext.length +
|
|
985
|
-
' aadLen=' +
|
|
1096
|
+
' aadLen=' + aad.length +
|
|
986
1097
|
' snKey=' + (keys.snKey ? 'set' : 'MISSING'));
|
|
987
|
-
}
|
|
988
|
-
}
|
|
1098
|
+
} catch (_e) {}
|
|
1099
|
+
}
|
|
989
1100
|
// ─────────────────────────────────────────────────────────────────────
|
|
990
1101
|
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
record[
|
|
994
|
-
record[1] = encRn[0];
|
|
995
|
-
record[2] = encRn[1];
|
|
996
|
-
record[3] = (ciphertext.length >>> 8) & 0xFF;
|
|
997
|
-
record[4] = ciphertext.length & 0xFF;
|
|
998
|
-
record.set(ciphertext, 5);
|
|
1102
|
+
record.set(aad, 0);
|
|
1103
|
+
record[rnOff] = encRn[0];
|
|
1104
|
+
record[rnOff + 1] = encRn[1];
|
|
999
1105
|
return record;
|
|
1000
1106
|
}
|
|
1001
1107
|
|
|
1108
|
+
var _REC_DEBUG = typeof process !== 'undefined' && process.env &&
|
|
1109
|
+
(process.env.LEMON_DEBUG === '1' || process.env.WEBRTC_DEBUG === '1');
|
|
1110
|
+
|
|
1111
|
+
// The one-byte content type appended to a DTLS 1.3 inner plaintext.
|
|
1112
|
+
var _CT_BYTES = [];
|
|
1113
|
+
function _contentTypeByte(t) {
|
|
1114
|
+
return _CT_BYTES[t] || (_CT_BYTES[t] = new Uint8Array([t]));
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1002
1117
|
/**
|
|
1003
1118
|
* Reconstruct a full DTLS 1.3 record sequence number from its truncated wire
|
|
1004
1119
|
* encoding (RFC 9147 §4.2.2).
|
|
@@ -1068,15 +1183,27 @@ function resolveReadEpoch(epochBits, keysByEpoch) {
|
|
|
1068
1183
|
* Decrypt a DTLS 1.3 encrypted record.
|
|
1069
1184
|
* data: full record bytes (starting with unified header byte)
|
|
1070
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.
|
|
1071
1191
|
* Returns { epoch, seq, type, content } or null on failure.
|
|
1072
1192
|
*/
|
|
1073
|
-
function decryptEncryptedDtls13(data, keys, fullEpoch) {
|
|
1193
|
+
function decryptEncryptedDtls13(data, keys, fullEpoch, cid, cidRequired) {
|
|
1074
1194
|
if (data.length < 1) return null;
|
|
1075
1195
|
|
|
1076
1196
|
let hdr = parseUnifiedHdr(data[0]);
|
|
1077
1197
|
let off = 1;
|
|
1078
|
-
|
|
1079
|
-
|
|
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
|
+
}
|
|
1080
1207
|
|
|
1081
1208
|
let rnLen = hdr.seqLen2 ? 2 : 1;
|
|
1082
1209
|
if (off + rnLen > data.length) return null;
|
|
@@ -1093,7 +1220,10 @@ function decryptEncryptedDtls13(data, keys, fullEpoch) {
|
|
|
1093
1220
|
}
|
|
1094
1221
|
|
|
1095
1222
|
if (off + ctLen > data.length) return null;
|
|
1096
|
-
|
|
1223
|
+
if (ctLen > DTLS13_MAX_CIPHERTEXT) return null;
|
|
1224
|
+
// A view: only read here (record-number mask, AEAD input); the plaintext
|
|
1225
|
+
// comes out of the decipher as a fresh buffer.
|
|
1226
|
+
let ciphertext = data.subarray(off, off + ctLen);
|
|
1097
1227
|
|
|
1098
1228
|
// Resolve the AEAD before unmasking: the record-number mask is built with
|
|
1099
1229
|
// the same primitive as the AEAD (AES-ECB vs ChaCha20), so `algo` is needed
|
|
@@ -1122,13 +1252,13 @@ function decryptEncryptedDtls13(data, keys, fullEpoch) {
|
|
|
1122
1252
|
// Rebuild AAD with plaintext RN. AAD depends only on the wire bytes (the
|
|
1123
1253
|
// decrypted record number + length), NOT on the reconstructed full seq —
|
|
1124
1254
|
// so it's computed once and reused across seq candidates below.
|
|
1125
|
-
let
|
|
1126
|
-
let aad = new Uint8Array(
|
|
1127
|
-
aad
|
|
1128
|
-
for (let i = 0; i < rnLen; i++) aad[
|
|
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];
|
|
1129
1259
|
if (hdr.hasLength) {
|
|
1130
|
-
aad[
|
|
1131
|
-
aad[
|
|
1260
|
+
aad[rnOff + rnLen] = (ctLen >>> 8) & 0xFF;
|
|
1261
|
+
aad[rnOff + rnLen + 1] = ctLen & 0xFF;
|
|
1132
1262
|
}
|
|
1133
1263
|
|
|
1134
1264
|
let ct = ciphertext.subarray(0, ciphertext.length - 16);
|
|
@@ -1168,34 +1298,13 @@ function decryptEncryptedDtls13(data, keys, fullEpoch) {
|
|
|
1168
1298
|
seq = acceptedSeq;
|
|
1169
1299
|
|
|
1170
1300
|
{
|
|
1171
|
-
//
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
//
|
|
1176
|
-
//
|
|
1177
|
-
|
|
1178
|
-
// next to maxReadSeq, because both are per-epoch receive state.
|
|
1179
|
-
// replayWindow: bitmask of the 64 seqs at or below maxReadSeq that have
|
|
1180
|
-
// been accepted. Bit 0 = maxReadSeq, bit k = maxReadSeq - k.
|
|
1181
|
-
if (keys.maxReadSeq === undefined || keys.maxReadSeq < 0) {
|
|
1182
|
-
// First accepted record in this epoch: seed the window with this seq as
|
|
1183
|
-
// the high-water mark (bit 0 set), so an immediate replay of it is
|
|
1184
|
-
// caught below rather than sailing through an unset window.
|
|
1185
|
-
keys.maxReadSeq = seq;
|
|
1186
|
-
keys.replayWindow = 1n;
|
|
1187
|
-
} else if (seq > keys.maxReadSeq) {
|
|
1188
|
-
// Advance: shift the window up by the gap, then set bit 0.
|
|
1189
|
-
let shift = BigInt(seq - keys.maxReadSeq);
|
|
1190
|
-
keys.replayWindow = shift >= 64n ? 1n : ((keys.replayWindow << shift) | 1n) & ((1n << 64n) - 1n);
|
|
1191
|
-
keys.maxReadSeq = seq;
|
|
1192
|
-
} else {
|
|
1193
|
-
let back = BigInt(keys.maxReadSeq - seq);
|
|
1194
|
-
if (back >= 64n) return null; // too old — outside window
|
|
1195
|
-
let bit = 1n << back;
|
|
1196
|
-
if (keys.replayWindow & bit) return null; // already seen — replay
|
|
1197
|
-
keys.replayWindow |= bit;
|
|
1198
|
-
}
|
|
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;
|
|
1199
1308
|
|
|
1200
1309
|
return {
|
|
1201
1310
|
// The caller resolves the full epoch (resolveReadEpoch); hdr.epoch is
|
|
@@ -1217,17 +1326,26 @@ function decryptEncryptedDtls13(data, keys, fullEpoch) {
|
|
|
1217
1326
|
* Dispatches between plaintext (classic header) and encrypted (unified header).
|
|
1218
1327
|
*
|
|
1219
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.
|
|
1220
1336
|
* Returns array of { type, epoch, seq, content, encrypted }.
|
|
1221
1337
|
*/
|
|
1222
|
-
function parseDtlsDatagram(data, keysByEpoch) {
|
|
1338
|
+
function parseDtlsDatagram(data, keysByEpoch, cid, cidRequired) {
|
|
1223
1339
|
let records = [];
|
|
1224
1340
|
let off = 0;
|
|
1341
|
+
let cidLen = cid ? cid.length : 0;
|
|
1225
1342
|
|
|
1226
1343
|
while (off < data.length) {
|
|
1227
1344
|
let first = data[off];
|
|
1228
1345
|
|
|
1229
1346
|
if (isUnifiedHdr(first)) {
|
|
1230
1347
|
let hdr = parseUnifiedHdr(first);
|
|
1348
|
+
if (hdr.hasCid && cidLen === 0) break;
|
|
1231
1349
|
// Header carries 2 epoch bits; map them back to a real epoch first.
|
|
1232
1350
|
let fullEpoch = resolveReadEpoch(hdr.epoch, keysByEpoch);
|
|
1233
1351
|
let keys = (fullEpoch === null) ? null : keysByEpoch[fullEpoch];
|
|
@@ -1243,8 +1361,7 @@ function parseDtlsDatagram(data, keysByEpoch) {
|
|
|
1243
1361
|
// Keep the raw bytes and mark the record deferred; the caller retries
|
|
1244
1362
|
// it after processing each earlier record, once the keys exist. This
|
|
1245
1363
|
// generalises the CCS-only re-decrypt that DTLS 1.2 already had.
|
|
1246
|
-
let
|
|
1247
|
-
let skip = 1 + rnLen;
|
|
1364
|
+
let skip = 1 + (hdr.hasCid ? cidLen : 0) + (hdr.seqLen2 ? 2 : 1);
|
|
1248
1365
|
let total;
|
|
1249
1366
|
if (hdr.hasLength && off + skip + 2 <= data.length) {
|
|
1250
1367
|
total = skip + 2 + readU16(data, off + skip);
|
|
@@ -1269,7 +1386,7 @@ function parseDtlsDatagram(data, keysByEpoch) {
|
|
|
1269
1386
|
continue;
|
|
1270
1387
|
}
|
|
1271
1388
|
|
|
1272
|
-
let result = decryptEncryptedDtls13(data.subarray(off), keys, fullEpoch);
|
|
1389
|
+
let result = decryptEncryptedDtls13(data.subarray(off), keys, fullEpoch, cid, cidRequired);
|
|
1273
1390
|
if (result) {
|
|
1274
1391
|
records.push({
|
|
1275
1392
|
type: result.type,
|
|
@@ -1284,27 +1401,56 @@ function parseDtlsDatagram(data, keysByEpoch) {
|
|
|
1284
1401
|
}
|
|
1285
1402
|
|
|
1286
1403
|
} else if (first <= 63) {
|
|
1287
|
-
// Classic DTLS record
|
|
1288
|
-
|
|
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;
|
|
1289
1410
|
let type = data[off];
|
|
1290
1411
|
let epoch = readU16(data, off + 3);
|
|
1291
1412
|
let seq = readU48(data, off + 5);
|
|
1292
|
-
let length = readU16(data, off +
|
|
1293
|
-
if (off +
|
|
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
|
+
}
|
|
1294
1430
|
|
|
1295
|
-
let payload =
|
|
1431
|
+
let payload = null;
|
|
1296
1432
|
let encrypted = false;
|
|
1297
1433
|
|
|
1298
|
-
// DTLS 1.2: decrypt if epoch > 0 and keys available
|
|
1434
|
+
// DTLS 1.2: decrypt if epoch > 0 and keys available. The ciphertext is
|
|
1435
|
+
// only read (the plaintext comes out of the decipher as a fresh
|
|
1436
|
+
// buffer), so it is passed as a view; a record kept as it is — not
|
|
1437
|
+
// decrypted — gets its own copy, as before.
|
|
1299
1438
|
if (epoch > 0 && keysByEpoch && keysByEpoch[epoch]) {
|
|
1300
1439
|
let keys = keysByEpoch[epoch];
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
//
|
|
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;
|
|
1306
1451
|
}
|
|
1307
1452
|
}
|
|
1453
|
+
if (payload === null) payload = fragment.slice();
|
|
1308
1454
|
|
|
1309
1455
|
records.push({
|
|
1310
1456
|
type: type,
|
|
@@ -1313,7 +1459,6 @@ function parseDtlsDatagram(data, keysByEpoch) {
|
|
|
1313
1459
|
content: payload,
|
|
1314
1460
|
encrypted: encrypted,
|
|
1315
1461
|
});
|
|
1316
|
-
off += 13 + length;
|
|
1317
1462
|
} else {
|
|
1318
1463
|
break;
|
|
1319
1464
|
}
|
|
@@ -1358,16 +1503,31 @@ function buildDtlsAck(acks) {
|
|
|
1358
1503
|
}
|
|
1359
1504
|
|
|
1360
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.
|
|
1361
1519
|
function parseDtlsAck(data) {
|
|
1520
|
+
if (!data || data.length < 2) throw recordError('ACK: truncated');
|
|
1362
1521
|
let bodyLen = readU16(data, 0);
|
|
1363
|
-
|
|
1364
|
-
|
|
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');
|
|
1365
1524
|
let acks = [];
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
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) });
|
|
1371
1531
|
}
|
|
1372
1532
|
return acks;
|
|
1373
1533
|
}
|
|
@@ -1409,6 +1569,7 @@ export {
|
|
|
1409
1569
|
// DTLS helpers
|
|
1410
1570
|
aesEcbEncrypt,
|
|
1411
1571
|
isUnifiedHdr,
|
|
1572
|
+
datagramCid,
|
|
1412
1573
|
|
|
1413
1574
|
// DTLS plaintext records
|
|
1414
1575
|
buildDtlsPlaintext,
|
|
@@ -1416,9 +1577,12 @@ export {
|
|
|
1416
1577
|
|
|
1417
1578
|
// DTLS 1.2 encrypted records
|
|
1418
1579
|
buildDtlsAad12,
|
|
1580
|
+
acceptRecordSeq,
|
|
1419
1581
|
encryptDtls12,
|
|
1420
1582
|
decryptDtls12,
|
|
1583
|
+
openDtls12,
|
|
1421
1584
|
buildEncryptedDtls12,
|
|
1585
|
+
TLS12_CID,
|
|
1422
1586
|
|
|
1423
1587
|
// DTLS 1.3 unified header
|
|
1424
1588
|
buildUnifiedHdr,
|