lemon-tls 0.5.5 → 0.7.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.
@@ -52,6 +52,7 @@ function DTLSSocket(udpSocket, options) {
52
52
  mtu: options.mtu,
53
53
  ticketKeys: options.ticketKeys,
54
54
  useCookies: options.useCookies,
55
+ srtpProfiles: options.srtpProfiles,
55
56
  });
56
57
 
57
58
  // Wire session → UDP
@@ -136,6 +137,11 @@ function DTLSSocket(udpSocket, options) {
136
137
  return session.exportKeyingMaterial(length, label, context_value);
137
138
  };
138
139
 
140
+ /** Negotiated DTLS-SRTP protection profile (RFC 5764), or null. */
141
+ self.getSelectedSrtpProfile = function() {
142
+ return session.getSelectedSrtpProfile();
143
+ };
144
+
139
145
  /** Peer hello extensions / single extension by numeric type. */
140
146
  self.getRemoteExtensions = function() { return session.getRemoteExtensions(); };
141
147
  self.getRemoteExtension = function(type) { return session.getRemoteExtension(type); };
@@ -190,6 +196,7 @@ function createDTLSServer(options, connectionListener) {
190
196
  mtu: options.mtu,
191
197
  ticketKeys: options.ticketKeys,
192
198
  useCookies: options.useCookies,
199
+ srtpProfiles: options.srtpProfiles,
193
200
  });
194
201
 
195
202
  connections[key] = socket;
package/src/record.js CHANGED
@@ -16,6 +16,7 @@ import {
16
16
  w_u8,
17
17
  w_u16,
18
18
  w_u48,
19
+ w_u64,
19
20
  } from './wire.js';
20
21
 
21
22
  // ===================== AEAD algorithm resolution =====================
@@ -33,12 +34,12 @@ function getAeadAlgo(cipherSuite) {
33
34
  // ===================== TLS 1.3 primitives =====================
34
35
 
35
36
  /** Derive write key + IV from a TLS 1.3 traffic secret. */
36
- function deriveKeys(trafficSecret, cipherSuite) {
37
+ function deriveKeys(trafficSecret, cipherSuite, labelPrefix) {
37
38
  const empty = new Uint8Array(0);
38
39
  let cs = TLS_CIPHER_SUITES[cipherSuite];
39
40
  return {
40
- key: hkdf_expand_label(cs.hash, trafficSecret, 'key', empty, cs.keylen),
41
- iv: hkdf_expand_label(cs.hash, trafficSecret, 'iv', empty, 12)
41
+ key: hkdf_expand_label(cs.hash, trafficSecret, 'key', empty, cs.keylen, labelPrefix),
42
+ iv: hkdf_expand_label(cs.hash, trafficSecret, 'iv', empty, 12, labelPrefix)
42
43
  };
43
44
  }
44
45
 
@@ -589,6 +590,11 @@ function writeRecord(transport, type, payload, version) {
589
590
  // readU16, readU48 return value only (no offset tracking), unlike wire.js r_u16 which returns [value, offset]
590
591
 
591
592
  function readU16(buf, off) { return ((buf[off] << 8) | buf[off+1]) >>> 0; }
593
+ function readU64(buf, off) {
594
+ let hi = ((buf[off] << 24) | (buf[off+1] << 16) | (buf[off+2] << 8) | buf[off+3]) >>> 0;
595
+ let lo = ((buf[off+4] << 24) | (buf[off+5] << 16) | (buf[off+6] << 8) | buf[off+7]) >>> 0;
596
+ return hi * 0x100000000 + lo;
597
+ }
592
598
  function readU48(buf, off) {
593
599
  let hi = ((buf[off] << 8) | buf[off+1]) >>> 0;
594
600
  let lo = ((buf[off+2] << 24) | (buf[off+3] << 16) | (buf[off+4] << 8) | buf[off+5]) >>> 0;
@@ -800,10 +806,35 @@ function isUnifiedHdr(b) { return (b & 0xE0) === UNIFIED_HDR_FIXED; }
800
806
  * mask = AES-ECB(snKey, ciphertext[0..15])
801
807
  * result = rnBytes XOR mask[0..len-1]
802
808
  */
803
- function maskRecordNumber(snKey, rnBytes, ciphertext) {
804
- let sample = new Uint8Array(16);
805
- sample.set(ciphertext.subarray(0, Math.min(16, ciphertext.length)), 0);
806
- let mask = aesEcbEncrypt(snKey, sample);
809
+ function maskRecordNumber(snKey, rnBytes, ciphertext, algo) {
810
+ // RFC 9147 §4.2.3: "This procedure requires the ciphertext length to be at
811
+ // least 16 bytes. Receivers MUST reject shorter records as if they had
812
+ // failed deprotection." Zero-padding a short sample instead (the previous
813
+ // behaviour) both diverges from the spec and yields a predictable mask.
814
+ // Senders must pad short plaintexts; with a 16-byte tag that never happens
815
+ // for the AEADs we support, so this is a guard, not a code path.
816
+ if (!ciphertext || ciphertext.length < 16) return null;
817
+
818
+ let mask;
819
+ if (algo === 'chacha20-poly1305') {
820
+ // Mask = ChaCha20(sn_key, Ciphertext[0..3], Ciphertext[4..15])
821
+ // — first 4 bytes are the block counter, next 12 the nonce. Node's
822
+ // 'chacha20' cipher takes exactly that as a 16-byte IV, so the sample is
823
+ // passed through unchanged and we encrypt a zero block to read off the
824
+ // keystream.
825
+ //
826
+ // The AES branch below is well covered by the DTLS 1.3 suites we
827
+ // negotiate in practice; this branch is not, so cross-check it against a
828
+ // BoringSSL peer before trusting it — in particular the counter's byte
829
+ // order, which the RFC states as a byte range and OpenSSL interprets as a
830
+ // little-endian u32.
831
+ let iv = Buffer.from(ciphertext.subarray(0, 16));
832
+ let c = crypto.createCipheriv('chacha20', snKey, iv);
833
+ mask = new Uint8Array(c.update(Buffer.alloc(16)));
834
+ } else {
835
+ mask = aesEcbEncrypt(snKey, ciphertext.subarray(0, 16));
836
+ }
837
+
807
838
  let out = new Uint8Array(rnBytes.length);
808
839
  for (let i = 0; i < rnBytes.length; i++) out[i] = rnBytes[i] ^ mask[i];
809
840
  return out;
@@ -864,7 +895,32 @@ function buildEncryptedDtls13(innerType, plaintext, seq, epoch, keys) {
864
895
  ciphertext.set(new Uint8Array(tag), ct.length);
865
896
 
866
897
  // Encrypt record number
867
- let encRn = maskRecordNumber(keys.snKey, rn, ciphertext);
898
+ let encRn = maskRecordNumber(keys.snKey, rn, ciphertext, algo);
899
+
900
+ // ── DIAGNOSTIC (LEMON_DEBUG=1 / WEBRTC_DEBUG=1) ──────────────────────
901
+ // Everything a peer needs to PARSE this record, printed as we build it.
902
+ // A decode_error alert means the peer could not make structural sense of
903
+ // what arrived, so the fields to compare are the info byte (which
904
+ // declares whether a CID, a length and a 16-bit sequence number are
905
+ // present), the sequence number before and after masking, and the
906
+ // declared length against the actual ciphertext size. If two records
907
+ // are accepted and the third is not, the difference between them shows
908
+ // up here.
909
+ try {
910
+ if (typeof process !== 'undefined' && process.env &&
911
+ (process.env.LEMON_DEBUG === '1' || process.env.WEBRTC_DEBUG === '1')) {
912
+ var _h = function (b) { return ('0' + (b & 0xFF).toString(16)).slice(-2); };
913
+ console.log('[lemon-rec] TX info=0x' + _h(info) +
914
+ ' [C=' + ((info >> 4) & 1) + ' S=' + ((info >> 3) & 1) +
915
+ ' L=' + ((info >> 2) & 1) + ' epoch=' + (info & 0x03) + ']' +
916
+ ' seqPlain=' + _h(rn[0]) + _h(rn[1]) +
917
+ ' seqMasked=' + _h(encRn[0]) + _h(encRn[1]) +
918
+ ' declaredLen=' + ciphertext.length +
919
+ ' aadLen=' + (aad ? aad.length : 'null') +
920
+ ' snKey=' + (keys.snKey ? 'set' : 'MISSING'));
921
+ }
922
+ } catch (_e) {}
923
+ // ─────────────────────────────────────────────────────────────────────
868
924
 
869
925
  // Assemble: info(1) + encrypted_rn(2) + length(2) + ciphertext
870
926
  let record = new Uint8Array(5 + ciphertext.length);
@@ -909,13 +965,46 @@ function reconstructDtlsSeq(truncated, bits, maxSeq) {
909
965
  }
910
966
 
911
967
 
968
+ /**
969
+ * Resolve the full epoch from the 2 bits carried in the unified header.
970
+ *
971
+ * RFC 9147 §4.2.2: the header holds only the low 2 bits of the epoch, so
972
+ * epoch 4 is indistinguishable from epoch 0 on the wire, 5 from 1, and so on.
973
+ * The spec's rule is to use "the most recent past epoch which has matching
974
+ * bits" — i.e. among the epochs we actually hold read keys for, the highest
975
+ * one whose low 2 bits match.
976
+ *
977
+ * Until KeyUpdate existed, only epochs 0/2/3 were ever in play and each had a
978
+ * unique bit pattern, so indexing the key table directly by the 2 bits worked
979
+ * by accident. The first KeyUpdate moves a sender to epoch 4, whose bits are
980
+ * 00 — and a lookup by bits then hands back the epoch-0 (plaintext) entry.
981
+ *
982
+ * @param epochBits the 2-bit value from the header
983
+ * @param keysByEpoch object keyed by FULL epoch number
984
+ * @returns the full epoch number, or null if we hold no matching keys
985
+ */
986
+ function resolveReadEpoch(epochBits, keysByEpoch) {
987
+ if (!keysByEpoch) return null;
988
+ let best = null;
989
+ for (let k in keysByEpoch) {
990
+ if (!Object.prototype.hasOwnProperty.call(keysByEpoch, k)) continue;
991
+ let e = Number(k);
992
+ if (!Number.isFinite(e)) continue;
993
+ if ((e & 0x03) !== (epochBits & 0x03)) continue;
994
+ if (keysByEpoch[k] == null) continue;
995
+ if (best === null || e > best) best = e;
996
+ }
997
+ return best;
998
+ }
999
+
1000
+
912
1001
  /**
913
1002
  * Decrypt a DTLS 1.3 encrypted record.
914
1003
  * data: full record bytes (starting with unified header byte)
915
1004
  * keys: { key, iv, snKey, algo?, maxReadSeq? }
916
1005
  * Returns { epoch, seq, type, content } or null on failure.
917
1006
  */
918
- function decryptEncryptedDtls13(data, keys) {
1007
+ function decryptEncryptedDtls13(data, keys, fullEpoch) {
919
1008
  if (data.length < 1) return null;
920
1009
 
921
1010
  let hdr = parseUnifiedHdr(data[0]);
@@ -940,8 +1029,16 @@ function decryptEncryptedDtls13(data, keys) {
940
1029
  if (off + ctLen > data.length) return null;
941
1030
  let ciphertext = data.slice(off, off + ctLen);
942
1031
 
943
- // Decrypt record number
944
- let rn = maskRecordNumber(keys.snKey, encRn, ciphertext);
1032
+ // Resolve the AEAD before unmasking: the record-number mask is built with
1033
+ // the same primitive as the AEAD (AES-ECB vs ChaCha20), so `algo` is needed
1034
+ // here, not just at deprotection time further down.
1035
+ let algo = keys.algo || (keys.key.length === 32 ? 'aes-256-gcm' : 'aes-128-gcm');
1036
+ let isChaCha = algo === 'chacha20-poly1305';
1037
+
1038
+ // Decrypt record number. Returns null for a ciphertext under 16 bytes, which
1039
+ // RFC 9147 §4.2.3 requires us to reject as a deprotection failure.
1040
+ let rn = maskRecordNumber(keys.snKey, encRn, ciphertext, algo);
1041
+ if (rn === null) return null;
945
1042
  let truncated = hdr.seqLen2 ? ((rn[0] << 8) | rn[1]) : rn[0];
946
1043
 
947
1044
  // RFC 9147 §4.2.2: the wire carries only the low 8 or 16 bits of the record
@@ -956,7 +1053,9 @@ function decryptEncryptedDtls13(data, keys) {
956
1053
  let seq = reconstructDtlsSeq(truncated, hdr.seqLen2 ? 16 : 8,
957
1054
  (keys.maxReadSeq === undefined ? -1 : keys.maxReadSeq));
958
1055
 
959
- // Rebuild AAD with plaintext RN
1056
+ // Rebuild AAD with plaintext RN. AAD depends only on the wire bytes (the
1057
+ // decrypted record number + length), NOT on the reconstructed full seq —
1058
+ // so it's computed once and reused across seq candidates below.
960
1059
  let hdrLen = 1 + rnLen + (hdr.hasLength ? 2 : 0);
961
1060
  let aad = new Uint8Array(hdrLen);
962
1061
  aad[0] = data[0];
@@ -966,39 +1065,80 @@ function decryptEncryptedDtls13(data, keys) {
966
1065
  aad[1 + rnLen + 1] = ctLen & 0xFF;
967
1066
  }
968
1067
 
969
- let nonce = getNonce(keys.iv, seq);
970
- let algo = keys.algo || (keys.key.length === 32 ? 'aes-256-gcm' : 'aes-128-gcm');
971
- let isChaCha = algo === 'chacha20-poly1305';
972
-
973
- if (ciphertext.length < 16) return null;
974
1068
  let ct = ciphertext.subarray(0, ciphertext.length - 16);
975
1069
  let tag = ciphertext.subarray(ciphertext.length - 16);
976
1070
 
977
- try {
978
- let decipher = crypto.createDecipheriv(algo, keys.key, nonce,
979
- isChaCha ? { authTagLength: 16 } : undefined);
980
- decipher.setAAD(aad, isChaCha ? { plaintextLength: ct.length } : undefined);
981
- decipher.setAuthTag(tag);
982
- let pt = decipher.update(ct);
983
- decipher.final();
1071
+ // The wire carries only the low bits of the record seq. reconstructDtlsSeq
1072
+ // returns the most-likely full value, but under large gaps or reordering
1073
+ // the true seq can be one window away in either direction. Only the NONCE
1074
+ // depends on the full seq (AAD does not), so try the neighbouring
1075
+ // candidates on AEAD failure before giving up — this is what lets
1076
+ // DTLS-Replay-TLS13 / -LargeGaps / -NonMonotonic deprotect instead of
1077
+ // failing as "bad record MAC". Candidates are deduped and non-negative.
1078
+ let window = hdr.seqLen2 ? 65536 : 256;
1079
+ let candidates = [seq];
1080
+ if (seq - window >= 0) candidates.push(seq - window);
1081
+ candidates.push(seq + window);
1082
+
1083
+ let pt = null, acceptedSeq = -1;
1084
+ for (let ci = 0; ci < candidates.length; ci++) {
1085
+ let cand = candidates[ci];
1086
+ let nonce = getNonce(keys.iv, cand);
1087
+ try {
1088
+ let decipher = crypto.createDecipheriv(algo, keys.key, nonce,
1089
+ isChaCha ? { authTagLength: 16 } : undefined);
1090
+ decipher.setAAD(aad, isChaCha ? { plaintextLength: ct.length } : undefined);
1091
+ decipher.setAuthTag(tag);
1092
+ let out = decipher.update(ct);
1093
+ decipher.final();
1094
+ pt = out; acceptedSeq = cand;
1095
+ break;
1096
+ } catch (e) {
1097
+ // try next candidate
1098
+ }
1099
+ }
1100
+
1101
+ if (pt === null) return null;
1102
+ seq = acceptedSeq;
984
1103
 
1104
+ {
985
1105
  let inner = parseInnerPlaintext(new Uint8Array(pt));
986
1106
 
987
- // Advance the per-epoch high-water mark only after the tag verified, so a
988
- // forged/replayed record number can't poison future reconstruction.
989
- if (keys.maxReadSeq === undefined || seq > keys.maxReadSeq) {
1107
+ // RFC 9147 §4.5.1 anti-replay: only AFTER the tag verifies do we consult
1108
+ // and update the sliding window. Checking before deprotection would let a
1109
+ // forged record number poison the window; checking after means only
1110
+ // genuine records move it. The window lives on the per-epoch key object,
1111
+ // next to maxReadSeq, because both are per-epoch receive state.
1112
+ // replayWindow: bitmask of the 64 seqs at or below maxReadSeq that have
1113
+ // been accepted. Bit 0 = maxReadSeq, bit k = maxReadSeq - k.
1114
+ if (keys.maxReadSeq === undefined || keys.maxReadSeq < 0) {
1115
+ // First accepted record in this epoch: seed the window with this seq as
1116
+ // the high-water mark (bit 0 set), so an immediate replay of it is
1117
+ // caught below rather than sailing through an unset window.
1118
+ keys.maxReadSeq = seq;
1119
+ keys.replayWindow = 1n;
1120
+ } else if (seq > keys.maxReadSeq) {
1121
+ // Advance: shift the window up by the gap, then set bit 0.
1122
+ let shift = BigInt(seq - keys.maxReadSeq);
1123
+ keys.replayWindow = shift >= 64n ? 1n : ((keys.replayWindow << shift) | 1n) & ((1n << 64n) - 1n);
990
1124
  keys.maxReadSeq = seq;
1125
+ } else {
1126
+ let back = BigInt(keys.maxReadSeq - seq);
1127
+ if (back >= 64n) return null; // too old — outside window
1128
+ let bit = 1n << back;
1129
+ if (keys.replayWindow & bit) return null; // already seen — replay
1130
+ keys.replayWindow |= bit;
991
1131
  }
992
1132
 
993
1133
  return {
994
- epoch: hdr.epoch,
1134
+ // The caller resolves the full epoch (resolveReadEpoch); hdr.epoch is
1135
+ // only its low 2 bits and must never escape this function as an epoch.
1136
+ epoch: (fullEpoch === undefined || fullEpoch === null) ? hdr.epoch : fullEpoch,
995
1137
  seq: seq,
996
1138
  type: inner.type,
997
1139
  content: inner.content,
998
1140
  total_length: off + ctLen,
999
1141
  };
1000
- } catch (e) {
1001
- return null;
1002
1142
  }
1003
1143
  }
1004
1144
 
@@ -1021,22 +1161,48 @@ function parseDtlsDatagram(data, keysByEpoch) {
1021
1161
 
1022
1162
  if (isUnifiedHdr(first)) {
1023
1163
  let hdr = parseUnifiedHdr(first);
1024
- let keys = keysByEpoch ? keysByEpoch[hdr.epoch] : null;
1164
+ // Header carries 2 epoch bits; map them back to a real epoch first.
1165
+ let fullEpoch = resolveReadEpoch(hdr.epoch, keysByEpoch);
1166
+ let keys = (fullEpoch === null) ? null : keysByEpoch[fullEpoch];
1025
1167
 
1026
1168
  if (!keys) {
1027
- // Can't decrypt — try to skip
1169
+ // No keys for this epoch YET. Do not discard the record: a DTLS peer
1170
+ // legitimately packs its whole flight into one datagram, so the very
1171
+ // record that lets us derive these keys (the ServerHello) can sit
1172
+ // ahead of records encrypted under them. Throwing the rest away meant
1173
+ // the entire encrypted flight vanished and the handshake stalled until
1174
+ // the peer retransmitted — or forever, if it does not.
1175
+ //
1176
+ // Keep the raw bytes and mark the record deferred; the caller retries
1177
+ // it after processing each earlier record, once the keys exist. This
1178
+ // generalises the CCS-only re-decrypt that DTLS 1.2 already had.
1028
1179
  let rnLen = hdr.seqLen2 ? 2 : 1;
1029
1180
  let skip = 1 + rnLen;
1181
+ let total;
1030
1182
  if (hdr.hasLength && off + skip + 2 <= data.length) {
1031
- skip += 2 + readU16(data, off + skip);
1183
+ total = skip + 2 + readU16(data, off + skip);
1032
1184
  } else {
1033
- skip = data.length - off;
1185
+ total = data.length - off;
1034
1186
  }
1035
- off += skip;
1187
+ if (total <= 0 || off + total > data.length) total = data.length - off;
1188
+
1189
+ records.push({
1190
+ // Unresolvable epoch: keep the raw bits so the deferred retry can
1191
+ // resolve again once keys for that generation are installed.
1192
+ type: null,
1193
+ epoch: hdr.epoch,
1194
+ epochBits: hdr.epoch,
1195
+ seq: null,
1196
+ content: null,
1197
+ encrypted: false,
1198
+ deferred: true,
1199
+ raw: data.subarray(off, off + total),
1200
+ });
1201
+ off += total;
1036
1202
  continue;
1037
1203
  }
1038
1204
 
1039
- let result = decryptEncryptedDtls13(data.subarray(off), keys);
1205
+ let result = decryptEncryptedDtls13(data.subarray(off), keys, fullEpoch);
1040
1206
  if (result) {
1041
1207
  records.push({
1042
1208
  type: result.type,
@@ -1101,13 +1267,25 @@ function parseDtlsDatagram(data, keysByEpoch) {
1101
1267
 
1102
1268
  /** Build ACK payload. acks: [{ epoch, seq }, ...] */
1103
1269
  function buildDtlsAck(acks) {
1104
- let bodyLen = acks.length * 8;
1270
+ // RFC 9147 §7:
1271
+ // struct { uint64 epoch; uint64 sequence_number; } RecordNumber;
1272
+ // struct { RecordNumber record_numbers<0..2^16-1>; } ACK;
1273
+ // SIXTEEN bytes per entry. This wrote eight — a uint16 epoch and a uint48
1274
+ // sequence number, which is the DTLS *1.2* record-number layout carried over
1275
+ // by mistake. A conforming peer reads a record_numbers vector whose length is
1276
+ // not a multiple of 16 and answers decode_error.
1277
+ //
1278
+ // It stayed hidden because the only ACKs this stack sent were during the
1279
+ // handshake, where they are usually redundant, and because parseDtlsAck made
1280
+ // the identical mistake — so two copies of this library agreed with each
1281
+ // other and the peer's own well-formed ACKs were quietly misread.
1282
+ let bodyLen = acks.length * 16;
1105
1283
  let out = new Uint8Array(2 + bodyLen);
1106
1284
  let off = 0;
1107
1285
  off = w_u16(out, off, bodyLen);
1108
1286
  for (let i = 0; i < acks.length; i++) {
1109
- off = w_u16(out, off, acks[i].epoch);
1110
- off = w_u48(out, off, acks[i].seq);
1287
+ off = w_u64(out, off, acks[i].epoch);
1288
+ off = w_u64(out, off, acks[i].seq);
1111
1289
  }
1112
1290
  return out;
1113
1291
  }
@@ -1118,9 +1296,10 @@ function parseDtlsAck(data) {
1118
1296
  let off = 2;
1119
1297
  let end = off + bodyLen;
1120
1298
  let acks = [];
1121
- while (off + 8 <= end) {
1122
- let epoch = readU16(data, off); off += 2;
1123
- let seq = readU48(data, off); off += 6;
1299
+ // 16 bytes per RecordNumber — see buildDtlsAck.
1300
+ while (off + 16 <= end && off + 16 <= data.length) {
1301
+ let epoch = readU64(data, off); off += 8;
1302
+ let seq = readU64(data, off); off += 8;
1124
1303
  acks.push({ epoch, seq });
1125
1304
  }
1126
1305
  return acks;
@@ -1133,6 +1312,9 @@ export {
1133
1312
  // AEAD
1134
1313
  getAeadAlgo,
1135
1314
 
1315
+ // DTLS 1.3 epoch resolution
1316
+ resolveReadEpoch,
1317
+
1136
1318
  // TLS 1.3
1137
1319
  deriveKeys,
1138
1320
  getNonce,
@@ -1174,6 +1356,7 @@ export {
1174
1356
  // DTLS 1.3 unified header
1175
1357
  buildUnifiedHdr,
1176
1358
  parseUnifiedHdr,
1359
+ reconstructDtlsSeq,
1177
1360
  maskRecordNumber,
1178
1361
 
1179
1362
  // DTLS 1.3 encrypted records
@@ -1186,4 +1369,4 @@ export {
1186
1369
  // DTLS 1.3 ACK
1187
1370
  buildDtlsAck,
1188
1371
  parseDtlsAck,
1189
- };
1372
+ };
@@ -90,7 +90,131 @@ function p384_get_shared_secret(localPrivateRaw, remotePublicRaw) {
90
90
  return new Uint8Array(ecdh.computeSecret(Buffer.from(remotePublicRaw)));
91
91
  }
92
92
 
93
+ /**
94
+ * Named groups this module actually implements, in preference order.
95
+ *
96
+ * SINGLE SOURCE OF TRUTH: consumers (TLSSession defaults, TLSSocket options,
97
+ * DTLSSession) must derive their advertised supported_groups from this list
98
+ * rather than hardcoding their own. Previously three call sites carried three
99
+ * different hardcoded lists — DTLS advertised P-384 while TLS did not, even
100
+ * though the code below has supported it all along, so a peer offering only
101
+ * P-384 got "no curve supported by both" from a stack that could do P-384.
102
+ *
103
+ * Adding a curve should be a change to THIS file only.
104
+ */
105
+ const NAMED_GROUP = {
106
+ X25519: 0x001d,
107
+ SECP256R1: 0x0017,
108
+ SECP384R1: 0x0018,
109
+ };
110
+
111
+ const SUPPORTED_GROUPS = [
112
+ NAMED_GROUP.X25519, // preferred: fastest, no point validation pitfalls
113
+ NAMED_GROUP.SECP256R1,
114
+ NAMED_GROUP.SECP384R1,
115
+ ];
116
+
117
+ function is_supported_group(group) {
118
+ return SUPPORTED_GROUPS.indexOf(group) >= 0;
119
+ }
120
+
121
+ /**
122
+ * Generate a keypair for any supported group.
123
+ * Returns { private_key, public_key } or null for an unsupported group.
124
+ */
125
+ function generate_keypair(group) {
126
+ if (group === NAMED_GROUP.X25519) {
127
+ let priv = new Uint8Array(crypto.randomBytes(32));
128
+ return { private_key: priv, public_key: x25519_get_public_key(priv) };
129
+ }
130
+ if (group === NAMED_GROUP.SECP256R1) return p256_generate_keypair();
131
+ if (group === NAMED_GROUP.SECP384R1) return p384_generate_keypair();
132
+ return null;
133
+ }
134
+
135
+ /**
136
+ * Validate a peer's key_exchange value for `group` and normalize it to the
137
+ * form the primitives below expect.
138
+ *
139
+ * Point-format knowledge belongs HERE, next to the curve implementations —
140
+ * not in the handshake state machine, which only needs to know "valid or not"
141
+ * so it can decide whether to abort. Throws on anything malformed; the caller
142
+ * converts that into an illegal_parameter alert.
143
+ */
144
+ function normalize_peer_public_key(group, pub) {
145
+ if (!pub || pub.length === 0) throw new Error('empty key_exchange');
146
+
147
+ if (group === NAMED_GROUP.X25519) {
148
+ if (pub.length !== 32) {
149
+ throw new Error('X25519 key_exchange must be 32 bytes, got ' + pub.length);
150
+ }
151
+ return pub;
152
+ }
153
+
154
+ let coordLen = (group === NAMED_GROUP.SECP256R1) ? 32
155
+ : (group === NAMED_GROUP.SECP384R1) ? 48 : 0;
156
+ if (coordLen === 0) throw new Error('unsupported group 0x' + (group >>> 0).toString(16));
157
+
158
+ // RFC 8446 §4.2.8.2: for the secp*r1 groups the key_exchange is an
159
+ // UNCOMPRESSED point — 0x04 || X || Y — and nothing else. The length is
160
+ // therefore exact.
161
+ //
162
+ // This used to also accept a bare X || Y (no 0x04) and re-prefix it, as a
163
+ // tolerance for TLS 1.2 peers that strip the prefix. That tolerance is a
164
+ // security hole, not a kindness: truncating a valid 65-byte P-256 point by
165
+ // one byte yields exactly 64 bytes, which the tolerance then reinterpreted
166
+ // as a DIFFERENT, prefix-less point and happily fed to the ECDH. The same
167
+ // arithmetic applies to P-384 (97 → 96). Malformed input must be rejected,
168
+ // not silently reinterpreted.
169
+ let expected = 1 + 2 * coordLen;
170
+
171
+ if (pub.length !== expected) {
172
+ // Name the two shapes we know we are refusing, so the failure is
173
+ // diagnosable rather than just "bad length".
174
+ if (pub.length === 2 * coordLen) {
175
+ throw new Error('key_exchange is missing the 0x04 uncompressed-point prefix (got ' +
176
+ pub.length + ' bytes, expected ' + expected + ')');
177
+ }
178
+ if (pub.length === 1 + coordLen && (pub[0] === 0x02 || pub[0] === 0x03)) {
179
+ throw new Error('compressed points are not permitted; key_exchange must be uncompressed (RFC 8446 §4.2.8.2)');
180
+ }
181
+ throw new Error('key_exchange must be a ' + expected + '-byte uncompressed point, got ' + pub.length);
182
+ }
183
+
184
+ if (pub[0] !== 0x04) {
185
+ throw new Error('key_exchange must start with the 0x04 uncompressed-point marker, got 0x' +
186
+ pub[0].toString(16));
187
+ }
188
+
189
+ // Whether the point actually lies on the curve is checked by the ECDH
190
+ // primitive below (node throws ERR_CRYPTO_ECDH_INVALID_PUBLIC_KEY), which
191
+ // happens before any secret is produced — so an off-curve point can never
192
+ // yield key material.
193
+ return pub;
194
+ }
195
+
196
+ /**
197
+ * Compute the ECDHE shared secret for any supported group.
198
+ * Validates the peer's public key first; throws on invalid input (including
199
+ * the RFC 7748 §6.1 all-zero X25519 result and node's own on-curve checks).
200
+ */
201
+ function get_shared_secret(group, localPrivateRaw, remotePublicRaw) {
202
+ let pub = normalize_peer_public_key(group, remotePublicRaw);
203
+
204
+ if (group === NAMED_GROUP.X25519) return x25519_get_shared_secret(localPrivateRaw, pub);
205
+ if (group === NAMED_GROUP.SECP256R1) return p256_get_shared_secret(localPrivateRaw, pub);
206
+ if (group === NAMED_GROUP.SECP384R1) return p384_get_shared_secret(localPrivateRaw, pub);
207
+
208
+ throw new Error('unsupported group 0x' + (group >>> 0).toString(16));
209
+ }
210
+
93
211
  export {
212
+ NAMED_GROUP,
213
+ SUPPORTED_GROUPS,
214
+ is_supported_group,
215
+ generate_keypair,
216
+ normalize_peer_public_key,
217
+ get_shared_secret,
94
218
  x25519_get_public_key,
95
219
  x25519_get_shared_secret,
96
220
  p256_generate_keypair,
@@ -193,6 +193,10 @@ function parse_tls_message(data, negotiatedVersion) {
193
193
  out.type = 'certificate';
194
194
 
195
195
  } else if (message.type == wire.TLS_MESSAGE_TYPE.CERTIFICATE_VERIFY) {
196
+ // Decode scheme + signature so the session can actually VERIFY it.
197
+ // Previously only the raw body was handed up and no caller ever checked
198
+ // the signature — every peer signature was accepted unconditionally.
199
+ out = wire.parse_certificate_verify(message.body);
196
200
  out.type = 'certificate_verify';
197
201
  out.body = message.body;
198
202
 
@@ -204,7 +208,11 @@ function parse_tls_message(data, negotiatedVersion) {
204
208
  // TLS 1.2 and 1.3 have different wire formats for this message.
205
209
  // TLS 1.3: ticket_lifetime | ticket_age_add | ticket_nonce | ticket | extensions
206
210
  // TLS 1.2: ticket_lifetime_hint | ticket (RFC 5077)
207
- if (negotiatedVersion === 0x0303) {
211
+ // 0xFEFD is DTLS 1.2, which uses the RFC 5077 wire format exactly as TLS
212
+ // 1.2 does. Testing only for 0x0303 sent a DTLS 1.2 ticket through the
213
+ // TLS 1.3 parser — the same class of bug as the Certificate encoding one:
214
+ // a version comparison that does not know DTLS exists.
215
+ if (negotiatedVersion === 0x0303 || negotiatedVersion === 0xFEFD) {
208
216
  out = wire.parse_new_session_ticket_tls12(message.body);
209
217
  } else {
210
218
  out = wire.parse_new_session_ticket(message.body);
@@ -218,6 +226,37 @@ function parse_tls_message(data, negotiatedVersion) {
218
226
  } else if (message.type == wire.TLS_MESSAGE_TYPE.CERTIFICATE_REQUEST) {
219
227
  out = wire.parse_certificate_request(message.body);
220
228
  out.type = 'certificate_request';
229
+
230
+ // Surface the requested signature algorithms as a plain field.
231
+ //
232
+ // In TLS 1.3 they arrive inside the extensions vector (RFC 8446 §4.3.2);
233
+ // in TLS 1.2 the decoder already returns them as `signature_algorithms`
234
+ // (RFC 5246 §7.4.4). The session asks for one name, so normalise here —
235
+ // exactly what normalize_hello does for the hello messages. Without this
236
+ // the 1.3 list was silently undefined and the client fell back to signing
237
+ // with its OWN preference, ignoring what the server actually asked for;
238
+ // a server that requested a specific algorithm then rejects the signature
239
+ // as an unsupported one.
240
+ if (out.signature_algorithms === undefined && Array.isArray(out.extensions)) {
241
+ for (let i = 0; i < out.extensions.length; i++) {
242
+ let e = out.extensions[i];
243
+ if (e && e.type === wire.TLS_EXT.SIGNATURE_ALGORITHMS && Array.isArray(e.value)) {
244
+ out.signature_algorithms = e.value;
245
+ break;
246
+ }
247
+ }
248
+ }
249
+
250
+ } else {
251
+ // No silent fall-through. Returning a typeless object here made every
252
+ // unrecognized handshake type vanish: the session's dispatch chain matched
253
+ // nothing and simply returned, so a peer could stream unknown (or
254
+ // version-illegal, e.g. HelloRequest in TLS 1.3) messages indefinitely with
255
+ // no alert and no state change. Name the message instead and let the
256
+ // session decide — which it can only do if it is told.
257
+ out.type = 'unknown_handshake';
258
+ out.handshake_type = message.type;
259
+ out.body = message.body;
221
260
  }
222
261
 
223
262
  return out;