lemon-tls 0.5.0 → 0.6.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
@@ -33,12 +33,12 @@ function getAeadAlgo(cipherSuite) {
33
33
  // ===================== TLS 1.3 primitives =====================
34
34
 
35
35
  /** Derive write key + IV from a TLS 1.3 traffic secret. */
36
- function deriveKeys(trafficSecret, cipherSuite) {
36
+ function deriveKeys(trafficSecret, cipherSuite, labelPrefix) {
37
37
  const empty = new Uint8Array(0);
38
38
  let cs = TLS_CIPHER_SUITES[cipherSuite];
39
39
  return {
40
- key: hkdf_expand_label(cs.hash, trafficSecret, 'key', empty, cs.keylen),
41
- iv: hkdf_expand_label(cs.hash, trafficSecret, 'iv', empty, 12)
40
+ key: hkdf_expand_label(cs.hash, trafficSecret, 'key', empty, cs.keylen, labelPrefix),
41
+ iv: hkdf_expand_label(cs.hash, trafficSecret, 'iv', empty, 12, labelPrefix)
42
42
  };
43
43
  }
44
44
 
@@ -956,7 +956,9 @@ function decryptEncryptedDtls13(data, keys) {
956
956
  let seq = reconstructDtlsSeq(truncated, hdr.seqLen2 ? 16 : 8,
957
957
  (keys.maxReadSeq === undefined ? -1 : keys.maxReadSeq));
958
958
 
959
- // Rebuild AAD with plaintext RN
959
+ // Rebuild AAD with plaintext RN. AAD depends only on the wire bytes (the
960
+ // decrypted record number + length), NOT on the reconstructed full seq —
961
+ // so it's computed once and reused across seq candidates below.
960
962
  let hdrLen = 1 + rnLen + (hdr.hasLength ? 2 : 0);
961
963
  let aad = new Uint8Array(hdrLen);
962
964
  aad[0] = data[0];
@@ -966,7 +968,6 @@ function decryptEncryptedDtls13(data, keys) {
966
968
  aad[1 + rnLen + 1] = ctLen & 0xFF;
967
969
  }
968
970
 
969
- let nonce = getNonce(keys.iv, seq);
970
971
  let algo = keys.algo || (keys.key.length === 32 ? 'aes-256-gcm' : 'aes-128-gcm');
971
972
  let isChaCha = algo === 'chacha20-poly1305';
972
973
 
@@ -974,20 +975,66 @@ function decryptEncryptedDtls13(data, keys) {
974
975
  let ct = ciphertext.subarray(0, ciphertext.length - 16);
975
976
  let tag = ciphertext.subarray(ciphertext.length - 16);
976
977
 
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();
978
+ // The wire carries only the low bits of the record seq. reconstructDtlsSeq
979
+ // returns the most-likely full value, but under large gaps or reordering
980
+ // the true seq can be one window away in either direction. Only the NONCE
981
+ // depends on the full seq (AAD does not), so try the neighbouring
982
+ // candidates on AEAD failure before giving up — this is what lets
983
+ // DTLS-Replay-TLS13 / -LargeGaps / -NonMonotonic deprotect instead of
984
+ // failing as "bad record MAC". Candidates are deduped and non-negative.
985
+ let window = hdr.seqLen2 ? 65536 : 256;
986
+ let candidates = [seq];
987
+ if (seq - window >= 0) candidates.push(seq - window);
988
+ candidates.push(seq + window);
989
+
990
+ let pt = null, acceptedSeq = -1;
991
+ for (let ci = 0; ci < candidates.length; ci++) {
992
+ let cand = candidates[ci];
993
+ let nonce = getNonce(keys.iv, cand);
994
+ try {
995
+ let decipher = crypto.createDecipheriv(algo, keys.key, nonce,
996
+ isChaCha ? { authTagLength: 16 } : undefined);
997
+ decipher.setAAD(aad, isChaCha ? { plaintextLength: ct.length } : undefined);
998
+ decipher.setAuthTag(tag);
999
+ let out = decipher.update(ct);
1000
+ decipher.final();
1001
+ pt = out; acceptedSeq = cand;
1002
+ break;
1003
+ } catch (e) {
1004
+ // try next candidate
1005
+ }
1006
+ }
984
1007
 
1008
+ if (pt === null) return null;
1009
+ seq = acceptedSeq;
1010
+
1011
+ {
985
1012
  let inner = parseInnerPlaintext(new Uint8Array(pt));
986
1013
 
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) {
1014
+ // RFC 9147 §4.5.1 anti-replay: only AFTER the tag verifies do we consult
1015
+ // and update the sliding window. Checking before deprotection would let a
1016
+ // forged record number poison the window; checking after means only
1017
+ // genuine records move it. The window lives on the per-epoch key object,
1018
+ // next to maxReadSeq, because both are per-epoch receive state.
1019
+ // replayWindow: bitmask of the 64 seqs at or below maxReadSeq that have
1020
+ // been accepted. Bit 0 = maxReadSeq, bit k = maxReadSeq - k.
1021
+ if (keys.maxReadSeq === undefined || keys.maxReadSeq < 0) {
1022
+ // First accepted record in this epoch: seed the window with this seq as
1023
+ // the high-water mark (bit 0 set), so an immediate replay of it is
1024
+ // caught below rather than sailing through an unset window.
1025
+ keys.maxReadSeq = seq;
1026
+ keys.replayWindow = 1n;
1027
+ } else if (seq > keys.maxReadSeq) {
1028
+ // Advance: shift the window up by the gap, then set bit 0.
1029
+ let shift = BigInt(seq - keys.maxReadSeq);
1030
+ keys.replayWindow = shift >= 64n ? 1n : ((keys.replayWindow << shift) | 1n) & ((1n << 64n) - 1n);
990
1031
  keys.maxReadSeq = seq;
1032
+ } else {
1033
+ let back = BigInt(keys.maxReadSeq - seq);
1034
+ if (back >= 64n) return null; // too old — outside window
1035
+ let bit = 1n << back;
1036
+ if (keys.replayWindow & bit) return null; // already seen — replay
1037
+ keys.replayWindow |= bit;
991
1038
  }
992
1039
 
993
1040
  return {
@@ -997,8 +1044,6 @@ function decryptEncryptedDtls13(data, keys) {
997
1044
  content: inner.content,
998
1045
  total_length: off + ctLen,
999
1046
  };
1000
- } catch (e) {
1001
- return null;
1002
1047
  }
1003
1048
  }
1004
1049
 
@@ -1024,15 +1069,36 @@ function parseDtlsDatagram(data, keysByEpoch) {
1024
1069
  let keys = keysByEpoch ? keysByEpoch[hdr.epoch] : null;
1025
1070
 
1026
1071
  if (!keys) {
1027
- // Can't decrypt — try to skip
1072
+ // No keys for this epoch YET. Do not discard the record: a DTLS peer
1073
+ // legitimately packs its whole flight into one datagram, so the very
1074
+ // record that lets us derive these keys (the ServerHello) can sit
1075
+ // ahead of records encrypted under them. Throwing the rest away meant
1076
+ // the entire encrypted flight vanished and the handshake stalled until
1077
+ // the peer retransmitted — or forever, if it does not.
1078
+ //
1079
+ // Keep the raw bytes and mark the record deferred; the caller retries
1080
+ // it after processing each earlier record, once the keys exist. This
1081
+ // generalises the CCS-only re-decrypt that DTLS 1.2 already had.
1028
1082
  let rnLen = hdr.seqLen2 ? 2 : 1;
1029
1083
  let skip = 1 + rnLen;
1084
+ let total;
1030
1085
  if (hdr.hasLength && off + skip + 2 <= data.length) {
1031
- skip += 2 + readU16(data, off + skip);
1086
+ total = skip + 2 + readU16(data, off + skip);
1032
1087
  } else {
1033
- skip = data.length - off;
1088
+ total = data.length - off;
1034
1089
  }
1035
- off += skip;
1090
+ if (total <= 0 || off + total > data.length) total = data.length - off;
1091
+
1092
+ records.push({
1093
+ type: null,
1094
+ epoch: hdr.epoch,
1095
+ seq: null,
1096
+ content: null,
1097
+ encrypted: false,
1098
+ deferred: true,
1099
+ raw: data.subarray(off, off + total),
1100
+ });
1101
+ off += total;
1036
1102
  continue;
1037
1103
  }
1038
1104
 
@@ -1174,6 +1240,7 @@ export {
1174
1240
  // DTLS 1.3 unified header
1175
1241
  buildUnifiedHdr,
1176
1242
  parseUnifiedHdr,
1243
+ reconstructDtlsSeq,
1177
1244
  maskRecordNumber,
1178
1245
 
1179
1246
  // DTLS 1.3 encrypted records
@@ -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
 
@@ -218,6 +222,37 @@ function parse_tls_message(data, negotiatedVersion) {
218
222
  } else if (message.type == wire.TLS_MESSAGE_TYPE.CERTIFICATE_REQUEST) {
219
223
  out = wire.parse_certificate_request(message.body);
220
224
  out.type = 'certificate_request';
225
+
226
+ // Surface the requested signature algorithms as a plain field.
227
+ //
228
+ // In TLS 1.3 they arrive inside the extensions vector (RFC 8446 §4.3.2);
229
+ // in TLS 1.2 the decoder already returns them as `signature_algorithms`
230
+ // (RFC 5246 §7.4.4). The session asks for one name, so normalise here —
231
+ // exactly what normalize_hello does for the hello messages. Without this
232
+ // the 1.3 list was silently undefined and the client fell back to signing
233
+ // with its OWN preference, ignoring what the server actually asked for;
234
+ // a server that requested a specific algorithm then rejects the signature
235
+ // as an unsupported one.
236
+ if (out.signature_algorithms === undefined && Array.isArray(out.extensions)) {
237
+ for (let i = 0; i < out.extensions.length; i++) {
238
+ let e = out.extensions[i];
239
+ if (e && e.type === wire.TLS_EXT.SIGNATURE_ALGORITHMS && Array.isArray(e.value)) {
240
+ out.signature_algorithms = e.value;
241
+ break;
242
+ }
243
+ }
244
+ }
245
+
246
+ } else {
247
+ // No silent fall-through. Returning a typeless object here made every
248
+ // unrecognized handshake type vanish: the session's dispatch chain matched
249
+ // nothing and simply returned, so a peer could stream unknown (or
250
+ // version-illegal, e.g. HelloRequest in TLS 1.3) messages indefinitely with
251
+ // no alert and no state change. Name the message instead and let the
252
+ // session decide — which it can only do if it is told.
253
+ out.type = 'unknown_handshake';
254
+ out.handshake_type = message.type;
255
+ out.body = message.body;
221
256
  }
222
257
 
223
258
  return out;