@agentdance/node-webrtc-srtp 1.0.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.
@@ -0,0 +1,129 @@
1
+ import { ProtectionProfile } from './types.js';
2
+ import { aes128cmKeystream, computeSrtpIv, computeSrtcpIv } from './cipher.js';
3
+ import { computeSrtpAuthTag, computeSrtcpAuthTag } from './auth.js';
4
+ import { gcmSrtpProtect } from './gcm.js';
5
+ // ---------------------------------------------------------------------------
6
+ // Helpers
7
+ // ---------------------------------------------------------------------------
8
+ /** Number of authentication-tag bytes for a given profile. */
9
+ function tagLength(profile) {
10
+ return profile === ProtectionProfile.AES_128_CM_HMAC_SHA1_32 ? 4 : 10;
11
+ }
12
+ /** Parse the minimum fixed RTP header fields we need. */
13
+ function parseRtpHeader(pkt) {
14
+ if (pkt.length < 12)
15
+ throw new RangeError('RTP packet too short (< 12 bytes)');
16
+ const cc = pkt[0] & 0x0f;
17
+ const x = (pkt[0] & 0x10) !== 0;
18
+ let headerLen = 12 + cc * 4;
19
+ if (x) {
20
+ if (pkt.length < headerLen + 4)
21
+ throw new RangeError('RTP extension header truncated');
22
+ const extLen = pkt.readUInt16BE(headerLen + 2);
23
+ headerLen += 4 + extLen * 4;
24
+ }
25
+ return {
26
+ seq: pkt.readUInt16BE(2),
27
+ ssrc: pkt.readUInt32BE(8),
28
+ headerLen,
29
+ };
30
+ }
31
+ /** Parse the minimum fixed RTCP header fields. */
32
+ function parseRtcpHeader(pkt) {
33
+ if (pkt.length < 8)
34
+ throw new RangeError('RTCP packet too short (< 8 bytes)');
35
+ return { ssrc: pkt.readUInt32BE(4) };
36
+ }
37
+ /**
38
+ * Advance the packet index (and ROC) for a sender-side protect call.
39
+ * The index increments monotonically; ROC increments whenever the 16-bit
40
+ * sequence counter wraps.
41
+ */
42
+ function nextSrtpIndex(ctx, seq) {
43
+ if (ctx.lastSeq === -1) {
44
+ // First packet
45
+ return BigInt(ctx.rolloverCounter) << 16n | BigInt(seq);
46
+ }
47
+ const roc = BigInt(ctx.rolloverCounter);
48
+ const lastSeq = ctx.lastSeq;
49
+ // Detect forward wrap-around (65535 → 0)
50
+ if (seq < lastSeq && lastSeq - seq > 0x8000) {
51
+ return (roc + 1n) << 16n | BigInt(seq);
52
+ }
53
+ return roc << 16n | BigInt(seq);
54
+ }
55
+ // ---------------------------------------------------------------------------
56
+ // SRTP protect (RFC 3711 §3.1)
57
+ // ---------------------------------------------------------------------------
58
+ /**
59
+ * Protect (encrypt + authenticate) an RTP packet.
60
+ *
61
+ * Output layout:
62
+ * RTP Header (unchanged) | Encrypted Payload | Auth Tag (10 or 4 bytes)
63
+ *
64
+ * GCM profiles are handled by delegating to `gcmSrtpProtect`.
65
+ */
66
+ export function srtpProtect(ctx, rtpPacket) {
67
+ if (ctx.profile === ProtectionProfile.AES_128_GCM ||
68
+ ctx.profile === ProtectionProfile.AES_256_GCM) {
69
+ return gcmSrtpProtect(ctx, rtpPacket);
70
+ }
71
+ const { seq, ssrc, headerLen } = parseRtpHeader(rtpPacket);
72
+ const index = nextSrtpIndex(ctx, seq);
73
+ const header = rtpPacket.subarray(0, headerLen);
74
+ const payload = rtpPacket.subarray(headerLen);
75
+ // 1. Encrypt the payload with AES-128-CM
76
+ const iv = computeSrtpIv(ctx.sessionSaltKey, ssrc, index);
77
+ const keystream = aes128cmKeystream(ctx.sessionEncKey, iv, payload.length);
78
+ const encryptedPayload = Buffer.allocUnsafe(payload.length);
79
+ for (let i = 0; i < payload.length; i++) {
80
+ encryptedPayload[i] = payload[i] ^ keystream[i];
81
+ }
82
+ // 2. Compute HMAC-SHA1 auth tag over header || encrypted_payload || ROC
83
+ const roc = Number(index >> 16n) >>> 0;
84
+ const tag = computeSrtpAuthTag(ctx.sessionAuthKey, header, encryptedPayload, roc, tagLength(ctx.profile));
85
+ // 3. Update context state
86
+ ctx.index = index;
87
+ ctx.rolloverCounter = roc;
88
+ ctx.lastSeq = seq;
89
+ return Buffer.concat([header, encryptedPayload, tag]);
90
+ }
91
+ // ---------------------------------------------------------------------------
92
+ // SRTCP protect (RFC 3711 §3.4)
93
+ // ---------------------------------------------------------------------------
94
+ /**
95
+ * Protect (encrypt + authenticate) an RTCP packet.
96
+ *
97
+ * Output layout:
98
+ * RTCP Header (8 bytes, unencrypted) |
99
+ * Encrypted Remainder |
100
+ * E (1 bit, always 1) | SRTCP Index (31 bits) |
101
+ * Auth Tag (10 bytes)
102
+ */
103
+ export function srtcpProtect(ctx, rtcpPacket) {
104
+ if (rtcpPacket.length < 8)
105
+ throw new RangeError('RTCP packet too short');
106
+ const { ssrc } = parseRtcpHeader(rtcpPacket);
107
+ // Increment and clamp to 31 bits
108
+ const index = (ctx.index + 1) & 0x7fffffff;
109
+ ctx.index = index;
110
+ // First 8 bytes of RTCP are left unencrypted (fixed header).
111
+ const header = rtcpPacket.subarray(0, 8);
112
+ const rest = rtcpPacket.subarray(8);
113
+ // Encrypt the rest with AES-128-CM
114
+ const iv = computeSrtcpIv(ctx.sessionSaltKey, ssrc, index);
115
+ const keystream = aes128cmKeystream(ctx.sessionEncKey, iv, rest.length);
116
+ const encrypted = Buffer.allocUnsafe(rest.length);
117
+ for (let i = 0; i < rest.length; i++) {
118
+ encrypted[i] = rest[i] ^ keystream[i];
119
+ }
120
+ // Build E || SRTCP_index word (E=1 means packet is encrypted)
121
+ const eSrtcpIndex = 0x80000000 | (index & 0x7fffffff);
122
+ const indexBuf = Buffer.allocUnsafe(4);
123
+ indexBuf.writeUInt32BE(eSrtcpIndex >>> 0, 0);
124
+ // Auth tag covers: header || encrypted_rest || E_SRTCP_index
125
+ const packetForAuth = Buffer.concat([header, encrypted]);
126
+ const tag = computeSrtcpAuthTag(ctx.sessionAuthKey, packetForAuth, eSrtcpIndex, tagLength(ctx.profile));
127
+ return Buffer.concat([header, encrypted, indexBuf, tag]);
128
+ }
129
+ //# sourceMappingURL=protect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protect.js","sourceRoot":"","sources":["../src/protect.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * RFC 3711 Section 3.3.2 – Sliding window replay protection.
3
+ * The window tracks the highest received index and a bitmask of the
4
+ * WINDOW_SIZE packets below it.
5
+ */
6
+ export declare class ReplayWindow {
7
+ private top;
8
+ private bitmask;
9
+ private readonly windowSize;
10
+ constructor(windowSize?: bigint);
11
+ /**
12
+ * Returns true if the packet index is acceptable (not replayed, inside window
13
+ * or ahead of it).
14
+ */
15
+ check(index: bigint): boolean;
16
+ /**
17
+ * Mark index as received and advance the window top if necessary.
18
+ * Must only be called after a successful auth-tag check.
19
+ */
20
+ update(index: bigint): void;
21
+ }
22
+ //# sourceMappingURL=replay.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replay.d.ts","sourceRoot":"","sources":["../src/replay.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,GAAG,CAAe;IAC1B,OAAO,CAAC,OAAO,CAAc;IAC7B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;gBAExB,UAAU,GAAE,MAAY;IAIpC;;;OAGG;IACH,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;IAmB7B;;;OAGG;IACH,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;CAe5B"}
package/dist/replay.js ADDED
@@ -0,0 +1,56 @@
1
+ /**
2
+ * RFC 3711 Section 3.3.2 – Sliding window replay protection.
3
+ * The window tracks the highest received index and a bitmask of the
4
+ * WINDOW_SIZE packets below it.
5
+ */
6
+ export class ReplayWindow {
7
+ top = -1n;
8
+ bitmask = 0n;
9
+ windowSize;
10
+ constructor(windowSize = 64n) {
11
+ this.windowSize = windowSize;
12
+ }
13
+ /**
14
+ * Returns true if the packet index is acceptable (not replayed, inside window
15
+ * or ahead of it).
16
+ */
17
+ check(index) {
18
+ if (this.top === -1n) {
19
+ // Window not yet initialised – accept any packet.
20
+ return true;
21
+ }
22
+ if (index > this.top) {
23
+ // Ahead of the window – always accept.
24
+ return true;
25
+ }
26
+ const diff = this.top - index;
27
+ if (diff >= this.windowSize) {
28
+ // Too old – outside the window.
29
+ return false;
30
+ }
31
+ // Inside the window – accept only if not already seen.
32
+ const bit = 1n << diff;
33
+ return (this.bitmask & bit) === 0n;
34
+ }
35
+ /**
36
+ * Mark index as received and advance the window top if necessary.
37
+ * Must only be called after a successful auth-tag check.
38
+ */
39
+ update(index) {
40
+ if (index > this.top) {
41
+ // Advance the window.
42
+ const shift = index - this.top;
43
+ this.bitmask = (this.bitmask << shift) | 1n;
44
+ this.top = index;
45
+ }
46
+ else {
47
+ const diff = this.top - index;
48
+ const bit = 1n << diff;
49
+ this.bitmask |= bit;
50
+ }
51
+ // Keep bitmask bounded to windowSize bits.
52
+ const mask = (1n << this.windowSize) - 1n;
53
+ this.bitmask &= mask;
54
+ }
55
+ }
56
+ //# sourceMappingURL=replay.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replay.js","sourceRoot":"","sources":["../src/replay.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,OAAO,YAAY;IACf,GAAG,GAAW,CAAC,EAAE,CAAC;IAClB,OAAO,GAAW,EAAE,CAAC;IACZ,UAAU,CAAS;IAEpC,YAAY,aAAqB,GAAG;QAClC,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;IAC/B,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,KAAa;QACjB,IAAI,IAAI,CAAC,GAAG,KAAK,CAAC,EAAE,EAAE,CAAC;YACrB,kDAAkD;YAClD,OAAO,IAAI,CAAC;QACd,CAAC;QACD,IAAI,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YACrB,uCAAuC;YACvC,OAAO,IAAI,CAAC;QACd,CAAC;QACD,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,GAAG,KAAK,CAAC;QAC9B,IAAI,IAAI,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YAC5B,gCAAgC;YAChC,OAAO,KAAK,CAAC;QACf,CAAC;QACD,uDAAuD;QACvD,MAAM,GAAG,GAAG,EAAE,IAAI,IAAI,CAAC;QACvB,OAAO,CAAC,IAAI,CAAC,OAAO,GAAG,GAAG,CAAC,KAAK,EAAE,CAAC;IACrC,CAAC;IAED;;;OAGG;IACH,MAAM,CAAC,KAAa;QAClB,IAAI,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YACrB,sBAAsB;YACtB,MAAM,KAAK,GAAG,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC;YAC/B,IAAI,CAAC,OAAO,GAAG,CAAC,IAAI,CAAC,OAAO,IAAI,KAAK,CAAC,GAAG,EAAE,CAAC;YAC5C,IAAI,CAAC,GAAG,GAAG,KAAK,CAAC;QACnB,CAAC;aAAM,CAAC;YACN,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,GAAG,KAAK,CAAC;YAC9B,MAAM,GAAG,GAAG,EAAE,IAAI,IAAI,CAAC;YACvB,IAAI,CAAC,OAAO,IAAI,GAAG,CAAC;QACtB,CAAC;QACD,2CAA2C;QAC3C,MAAM,IAAI,GAAG,CAAC,EAAE,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,CAAC;QAC1C,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC;IACvB,CAAC;CACF"}
@@ -0,0 +1,40 @@
1
+ import { ReplayWindow } from './replay.js';
2
+ export declare enum ProtectionProfile {
3
+ AES_128_CM_HMAC_SHA1_80 = 1,
4
+ AES_128_CM_HMAC_SHA1_32 = 2,
5
+ AES_128_GCM = 7,
6
+ AES_256_GCM = 8
7
+ }
8
+ export interface SrtpKeyingMaterial {
9
+ /** 16 bytes for AES-128 profiles; 32 bytes for AES-256 */
10
+ masterKey: Buffer;
11
+ /** 14 bytes */
12
+ masterSalt: Buffer;
13
+ profile: ProtectionProfile;
14
+ }
15
+ export interface SrtpContext {
16
+ profile: ProtectionProfile;
17
+ /** AES session encryption key */
18
+ sessionEncKey: Buffer;
19
+ /** HMAC-SHA1 authentication key – 20 bytes */
20
+ sessionAuthKey: Buffer;
21
+ /** 14-byte session salt used to compute the IV */
22
+ sessionSaltKey: Buffer;
23
+ /** Full 48-bit packet index: (ROC << 16) | SEQ */
24
+ index: bigint;
25
+ /** Roll-Over Counter */
26
+ rolloverCounter: number;
27
+ /** Last observed RTP sequence number */
28
+ lastSeq: number;
29
+ replayWindow: ReplayWindow;
30
+ }
31
+ export interface SrtcpContext {
32
+ profile: ProtectionProfile;
33
+ sessionEncKey: Buffer;
34
+ sessionAuthKey: Buffer;
35
+ sessionSaltKey: Buffer;
36
+ /** 31-bit SRTCP packet index (starts at 1 on first protect call) */
37
+ index: number;
38
+ replayWindow: ReplayWindow;
39
+ }
40
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAM3C,oBAAY,iBAAiB;IAC3B,uBAAuB,IAAS;IAChC,uBAAuB,IAAS;IAChC,WAAW,IAAS;IACpB,WAAW,IAAS;CACrB;AAMD,MAAM,WAAW,kBAAkB;IACjC,0DAA0D;IAC1D,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,iBAAiB,CAAC;CAC5B;AAMD,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,iBAAiB,CAAC;IAC3B,iCAAiC;IACjC,aAAa,EAAE,MAAM,CAAC;IACtB,8CAA8C;IAC9C,cAAc,EAAE,MAAM,CAAC;IACvB,kDAAkD;IAClD,cAAc,EAAE,MAAM,CAAC;IACvB,kDAAkD;IAClD,KAAK,EAAE,MAAM,CAAC;IACd,wBAAwB;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,wCAAwC;IACxC,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,YAAY,CAAC;CAC5B;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,iBAAiB,CAAC;IAC3B,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,oEAAoE;IACpE,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,YAAY,CAAC;CAC5B"}
package/dist/types.js ADDED
@@ -0,0 +1,11 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Protection profiles
3
+ // ---------------------------------------------------------------------------
4
+ export var ProtectionProfile;
5
+ (function (ProtectionProfile) {
6
+ ProtectionProfile[ProtectionProfile["AES_128_CM_HMAC_SHA1_80"] = 1] = "AES_128_CM_HMAC_SHA1_80";
7
+ ProtectionProfile[ProtectionProfile["AES_128_CM_HMAC_SHA1_32"] = 2] = "AES_128_CM_HMAC_SHA1_32";
8
+ ProtectionProfile[ProtectionProfile["AES_128_GCM"] = 7] = "AES_128_GCM";
9
+ ProtectionProfile[ProtectionProfile["AES_256_GCM"] = 8] = "AES_256_GCM";
10
+ })(ProtectionProfile || (ProtectionProfile = {}));
11
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAEA,8EAA8E;AAC9E,sBAAsB;AACtB,8EAA8E;AAE9E,MAAM,CAAN,IAAY,iBAKX;AALD,WAAY,iBAAiB;IAC3B,+FAAgC,CAAA;IAChC,+FAAgC,CAAA;IAChC,uEAAoB,CAAA;IACpB,uEAAoB,CAAA;AACtB,CAAC,EALW,iBAAiB,KAAjB,iBAAiB,QAK5B"}
@@ -0,0 +1,14 @@
1
+ import { SrtpContext, SrtcpContext } from './types.js';
2
+ /**
3
+ * Authenticate and decrypt an SRTP packet.
4
+ *
5
+ * @returns Plaintext RTP packet, or `null` if auth fails / replay detected.
6
+ */
7
+ export declare function srtpUnprotect(ctx: SrtpContext, srtpPacket: Buffer): Buffer | null;
8
+ /**
9
+ * Authenticate and decrypt an SRTCP packet.
10
+ *
11
+ * @returns Plaintext RTCP packet, or `null` if auth fails / replay detected.
12
+ */
13
+ export declare function srtcpUnprotect(ctx: SrtcpContext, srtcpPacket: Buffer): Buffer | null;
14
+ //# sourceMappingURL=unprotect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unprotect.d.ts","sourceRoot":"","sources":["../src/unprotect.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAqB,MAAM,YAAY,CAAC;AAkE1E;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CA+CjF;AAMD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CA4CpF"}
@@ -0,0 +1,156 @@
1
+ import { ProtectionProfile } from './types.js';
2
+ import { aes128cmKeystream, computeSrtpIv, computeSrtcpIv } from './cipher.js';
3
+ import { computeSrtpAuthTag, computeSrtcpAuthTag } from './auth.js';
4
+ import { gcmSrtpUnprotect } from './gcm.js';
5
+ // ---------------------------------------------------------------------------
6
+ // Helpers
7
+ // ---------------------------------------------------------------------------
8
+ function tagLength(profile) {
9
+ return profile === ProtectionProfile.AES_128_CM_HMAC_SHA1_32 ? 4 : 10;
10
+ }
11
+ function timingSafeEqual(a, b) {
12
+ if (a.length !== b.length)
13
+ return false;
14
+ let result = 0;
15
+ for (let i = 0; i < a.length; i++) {
16
+ result |= a[i] ^ b[i];
17
+ }
18
+ return result === 0;
19
+ }
20
+ /** Parse the minimum fixed RTP header fields. */
21
+ function parseRtpHeader(pkt) {
22
+ if (pkt.length < 12)
23
+ return { seq: 0, ssrc: 0, headerLen: 0 };
24
+ const cc = pkt[0] & 0x0f;
25
+ const x = (pkt[0] & 0x10) !== 0;
26
+ let headerLen = 12 + cc * 4;
27
+ if (x && pkt.length >= headerLen + 4) {
28
+ const extLen = pkt.readUInt16BE(headerLen + 2);
29
+ headerLen += 4 + extLen * 4;
30
+ }
31
+ return {
32
+ seq: pkt.readUInt16BE(2),
33
+ ssrc: pkt.readUInt32BE(8),
34
+ headerLen,
35
+ };
36
+ }
37
+ /**
38
+ * RFC 3711 §3.3.1 – estimate the full 48-bit packet index from a received
39
+ * 16-bit sequence number and the current context state.
40
+ */
41
+ function estimateSrtpIndex(ctx, seq) {
42
+ if (ctx.lastSeq === -1) {
43
+ // No previous packet; accept as-is with the current ROC.
44
+ return BigInt(ctx.rolloverCounter) << 16n | BigInt(seq);
45
+ }
46
+ const v = BigInt(ctx.rolloverCounter);
47
+ const diff = seq - ctx.lastSeq;
48
+ if (diff > 0x8000) {
49
+ // seq is much higher than lastSeq → previous ROC (seq wrapped backwards)
50
+ return (v === 0n ? 0n : v - 1n) << 16n | BigInt(seq);
51
+ }
52
+ else if (diff < -0x8000) {
53
+ // seq is much lower than lastSeq → ROC has incremented (forward wrap)
54
+ return (v + 1n) << 16n | BigInt(seq);
55
+ }
56
+ return v << 16n | BigInt(seq);
57
+ }
58
+ // ---------------------------------------------------------------------------
59
+ // SRTP unprotect (RFC 3711 §3.1)
60
+ // ---------------------------------------------------------------------------
61
+ /**
62
+ * Authenticate and decrypt an SRTP packet.
63
+ *
64
+ * @returns Plaintext RTP packet, or `null` if auth fails / replay detected.
65
+ */
66
+ export function srtpUnprotect(ctx, srtpPacket) {
67
+ if (ctx.profile === ProtectionProfile.AES_128_GCM ||
68
+ ctx.profile === ProtectionProfile.AES_256_GCM) {
69
+ return gcmSrtpUnprotect(ctx, srtpPacket);
70
+ }
71
+ const tl = tagLength(ctx.profile);
72
+ if (srtpPacket.length < 12 + tl)
73
+ return null;
74
+ const { seq, ssrc, headerLen } = parseRtpHeader(srtpPacket);
75
+ if (headerLen === 0)
76
+ return null;
77
+ if (srtpPacket.length < headerLen + tl)
78
+ return null;
79
+ // 1. Estimate packet index (handles ROC)
80
+ const index = estimateSrtpIndex(ctx, seq);
81
+ // 2. Replay check (before expensive crypto)
82
+ if (!ctx.replayWindow.check(index))
83
+ return null;
84
+ // 3. Split the packet
85
+ const header = srtpPacket.subarray(0, headerLen);
86
+ const encryptedPayload = srtpPacket.subarray(headerLen, srtpPacket.length - tl);
87
+ const receivedTag = srtpPacket.subarray(srtpPacket.length - tl);
88
+ // 4. Verify auth tag
89
+ const roc = Number(index >> 16n) >>> 0;
90
+ const expectedTag = computeSrtpAuthTag(ctx.sessionAuthKey, header, encryptedPayload, roc, tl);
91
+ if (!timingSafeEqual(expectedTag, receivedTag))
92
+ return null;
93
+ // 5. Decrypt payload (AES-128-CM is symmetric: XOR with keystream)
94
+ const iv = computeSrtpIv(ctx.sessionSaltKey, ssrc, index);
95
+ const keystream = aes128cmKeystream(ctx.sessionEncKey, iv, encryptedPayload.length);
96
+ const payload = Buffer.allocUnsafe(encryptedPayload.length);
97
+ for (let i = 0; i < encryptedPayload.length; i++) {
98
+ payload[i] = encryptedPayload[i] ^ keystream[i];
99
+ }
100
+ // 6. Update state
101
+ ctx.replayWindow.update(index);
102
+ ctx.index = index;
103
+ ctx.rolloverCounter = roc;
104
+ ctx.lastSeq = seq;
105
+ return Buffer.concat([header, payload]);
106
+ }
107
+ // ---------------------------------------------------------------------------
108
+ // SRTCP unprotect (RFC 3711 §3.4)
109
+ // ---------------------------------------------------------------------------
110
+ /**
111
+ * Authenticate and decrypt an SRTCP packet.
112
+ *
113
+ * @returns Plaintext RTCP packet, or `null` if auth fails / replay detected.
114
+ */
115
+ export function srtcpUnprotect(ctx, srtcpPacket) {
116
+ const tl = tagLength(ctx.profile);
117
+ // Minimum: 8 bytes RTCP header + 4 bytes E|index + tl bytes tag
118
+ if (srtcpPacket.length < 8 + 4 + tl)
119
+ return null;
120
+ // 1. Extract E || SRTCP_index (4 bytes before the auth tag)
121
+ const eSrtcpIndexOffset = srtcpPacket.length - tl - 4;
122
+ const eSrtcpIndex = srtcpPacket.readUInt32BE(eSrtcpIndexOffset);
123
+ const encrypted = (eSrtcpIndex & 0x80000000) !== 0;
124
+ const index = eSrtcpIndex & 0x7fffffff;
125
+ // 2. Replay check
126
+ if (!ctx.replayWindow.check(BigInt(index)))
127
+ return null;
128
+ // 3. Auth tag verification
129
+ // Auth input = packet bytes (everything except the tag itself)
130
+ const packetForAuth = srtcpPacket.subarray(0, eSrtcpIndexOffset); // header + encrypted body
131
+ const receivedTag = srtcpPacket.subarray(srtcpPacket.length - tl);
132
+ const expectedTag = computeSrtcpAuthTag(ctx.sessionAuthKey, packetForAuth, eSrtcpIndex, tl);
133
+ if (!timingSafeEqual(expectedTag, receivedTag))
134
+ return null;
135
+ // 4. Decrypt
136
+ const header = srtcpPacket.subarray(0, 8);
137
+ const encryptedRest = srtcpPacket.subarray(8, eSrtcpIndexOffset);
138
+ let decryptedRest;
139
+ if (encrypted) {
140
+ const ssrc = srtcpPacket.readUInt32BE(4);
141
+ const iv = computeSrtcpIv(ctx.sessionSaltKey, ssrc, index);
142
+ const keystream = aes128cmKeystream(ctx.sessionEncKey, iv, encryptedRest.length);
143
+ decryptedRest = Buffer.allocUnsafe(encryptedRest.length);
144
+ for (let i = 0; i < encryptedRest.length; i++) {
145
+ decryptedRest[i] = encryptedRest[i] ^ keystream[i];
146
+ }
147
+ }
148
+ else {
149
+ decryptedRest = encryptedRest;
150
+ }
151
+ // 5. Update state
152
+ ctx.replayWindow.update(BigInt(index));
153
+ ctx.index = index;
154
+ return Buffer.concat([header, decryptedRest]);
155
+ }
156
+ //# sourceMappingURL=unprotect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unprotect.js","sourceRoot":"","sources":["../src/unprotect.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@agentdance/node-webrtc-srtp",
3
+ "version": "1.0.0",
4
+ "description": "RFC 3711 SRTP/SRTCP — AES-128-CM-HMAC-SHA1-80/32 and AES-128-GCM, RFC-verified key derivation, 64-bit replay window. Part of the @agentdance/node-webrtc stack.",
5
+ "keywords": [
6
+ "webrtc",
7
+ "srtp",
8
+ "srtcp",
9
+ "rfc3711",
10
+ "aes",
11
+ "hmac",
12
+ "typescript",
13
+ "node"
14
+ ],
15
+ "license": "MIT",
16
+ "homepage": "https://github.com/agent-dance/node-webrtc#readme",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/agent-dance/node-webrtc.git",
20
+ "directory": "packages/srtp"
21
+ },
22
+ "bugs": {
23
+ "url": "https://github.com/agent-dance/node-webrtc/issues"
24
+ },
25
+ "engines": {
26
+ "node": ">=18"
27
+ },
28
+ "type": "module",
29
+ "main": "./dist/index.js",
30
+ "types": "./dist/index.d.ts",
31
+ "exports": {
32
+ ".": {
33
+ "import": "./dist/index.js",
34
+ "types": "./dist/index.d.ts"
35
+ }
36
+ },
37
+ "files": [
38
+ "dist",
39
+ "src"
40
+ ],
41
+ "sideEffects": false,
42
+ "publishConfig": {
43
+ "access": "public",
44
+ "registry": "https://registry.npmjs.org/"
45
+ },
46
+ "devDependencies": {
47
+ "typescript": "*",
48
+ "vitest": "*",
49
+ "@types/node": "*"
50
+ },
51
+ "scripts": {
52
+ "build": "tsc",
53
+ "test": "vitest run",
54
+ "typecheck": "tsc --noEmit",
55
+ "clean": "rm -rf dist"
56
+ }
57
+ }
package/src/auth.ts ADDED
@@ -0,0 +1,61 @@
1
+ import { createHmac } from 'node:crypto';
2
+
3
+ // ---------------------------------------------------------------------------
4
+ // HMAC-SHA1 authentication tags (RFC 3711 §4.2)
5
+ // ---------------------------------------------------------------------------
6
+
7
+ /**
8
+ * Compute the SRTP authentication tag.
9
+ *
10
+ * auth_tag = first `tagLength` bytes of
11
+ * HMAC-SHA1(k_a, RTP_header || RTP_payload || ROC_be32)
12
+ *
13
+ * @param authKey 20-byte HMAC-SHA1 session authentication key
14
+ * @param rtpHeader The full RTP header (variable length)
15
+ * @param rtpPayload The plain RTP payload (pre-encryption for sender,
16
+ * already encrypted on sender side per RFC 3711 §3.1)
17
+ * @param roc Roll-Over Counter (big-endian 32-bit appended)
18
+ * @param tagLength 10 for 80-bit profile, 4 for 32-bit profile
19
+ */
20
+ export function computeSrtpAuthTag(
21
+ authKey: Buffer,
22
+ rtpHeader: Buffer,
23
+ rtpPayload: Buffer,
24
+ roc: number,
25
+ tagLength: 10 | 4,
26
+ ): Buffer {
27
+ const rocBuf = Buffer.allocUnsafe(4);
28
+ rocBuf.writeUInt32BE(roc >>> 0, 0);
29
+
30
+ const hmac = createHmac('sha1', authKey);
31
+ hmac.update(rtpHeader);
32
+ hmac.update(rtpPayload);
33
+ hmac.update(rocBuf);
34
+ return hmac.digest().subarray(0, tagLength);
35
+ }
36
+
37
+ /**
38
+ * Compute the SRTCP authentication tag.
39
+ *
40
+ * auth_tag = first `tagLength` bytes of
41
+ * HMAC-SHA1(k_a, RTCP_packet || E_SRTCP_index_be32)
42
+ *
43
+ * @param authKey 20-byte HMAC-SHA1 session authentication key
44
+ * @param rtcpPacket The full (partially encrypted) RTCP packet bytes
45
+ * @param eSrtcpIndex The 32-bit word: E(1) || SRTCP_index(31)
46
+ * @param tagLength 10 for 80-bit profile, 4 for 32-bit profile
47
+ */
48
+ export function computeSrtcpAuthTag(
49
+ authKey: Buffer,
50
+ rtcpPacket: Buffer,
51
+ eSrtcpIndex: number,
52
+ tagLength: 10 | 4,
53
+ ): Buffer {
54
+ const indexBuf = Buffer.allocUnsafe(4);
55
+ indexBuf.writeUInt32BE(eSrtcpIndex >>> 0, 0);
56
+
57
+ const hmac = createHmac('sha1', authKey);
58
+ hmac.update(rtcpPacket);
59
+ hmac.update(indexBuf);
60
+ return hmac.digest().subarray(0, tagLength);
61
+ }
package/src/cipher.ts ADDED
@@ -0,0 +1,68 @@
1
+ import { createCipheriv } from 'node:crypto';
2
+
3
+ // ---------------------------------------------------------------------------
4
+ // AES-128-CM keystream (RFC 3711 §4.1.1)
5
+ // ---------------------------------------------------------------------------
6
+
7
+ /**
8
+ * Generate `length` bytes of AES-128-CM (Counter Mode) keystream.
9
+ *
10
+ * @param key 16-byte AES key
11
+ * @param iv 16-byte IV (the initial counter block, lower 16 bits = 0)
12
+ * @param length Number of keystream bytes to return
13
+ */
14
+ export function aes128cmKeystream(key: Buffer, iv: Buffer, length: number): Buffer {
15
+ if (key.length !== 16) throw new RangeError('AES-128-CM requires a 16-byte key');
16
+ if (iv.length !== 16) throw new RangeError('AES-128-CM IV must be 16 bytes');
17
+
18
+ // Encrypt a zero plaintext; CTR mode emits the keystream directly.
19
+ const plaintext = Buffer.alloc(length, 0);
20
+ const cipher = createCipheriv('aes-128-ctr', key, iv);
21
+ const ks = Buffer.concat([cipher.update(plaintext), cipher.final()]);
22
+ return ks.subarray(0, length);
23
+ }
24
+
25
+ // ---------------------------------------------------------------------------
26
+ // IV computation for SRTP (RFC 3711 §4.1)
27
+ // ---------------------------------------------------------------------------
28
+
29
+ /**
30
+ * Compute the 128-bit SRTP IV:
31
+ * IV = (k_s * 2^16) XOR (SSRC * 2^64) XOR (index * 2^16)
32
+ *
33
+ * Bit-position reference (big-endian 128-bit / 16-byte buffer):
34
+ * k_s * 2^16 → salt (14 bytes) placed at buffer bytes [2..15]
35
+ * SSRC * 2^64 → SSRC (4 bytes) placed at buffer bytes [4..7]
36
+ * i * 2^16 → index (6 bytes) placed at buffer bytes [8..13]
37
+ */
38
+ export function computeSrtpIv(salt: Buffer, ssrc: number, index: bigint): Buffer {
39
+ if (salt.length !== 14) throw new RangeError('SRTP salt must be 14 bytes');
40
+
41
+ // Start from all zeros; copy salt into bytes [2..15].
42
+ const iv = Buffer.alloc(16, 0);
43
+ salt.copy(iv, 2);
44
+
45
+ // XOR SSRC into bytes [4..7].
46
+ iv.writeUInt32BE((iv.readUInt32BE(4) ^ (ssrc >>> 0)) >>> 0, 4);
47
+
48
+ // XOR 48-bit index into bytes [8..13].
49
+ // index high 32 bits → bytes [8..11], index low 16 bits → bytes [12..13].
50
+ const idxHi = Number((index >> 16n) & 0xffffffffn);
51
+ const idxLo = Number(index & 0xffffn);
52
+ iv.writeUInt32BE((iv.readUInt32BE(8) ^ idxHi) >>> 0, 8);
53
+ iv.writeUInt16BE((iv.readUInt16BE(12) ^ idxLo) & 0xffff, 12);
54
+
55
+ return iv;
56
+ }
57
+
58
+ // ---------------------------------------------------------------------------
59
+ // IV computation for SRTCP (RFC 3711 §4.1)
60
+ // ---------------------------------------------------------------------------
61
+
62
+ /**
63
+ * Compute the 128-bit SRTCP IV.
64
+ * Identical formula to SRTP; the SRTCP index is 31-bit.
65
+ */
66
+ export function computeSrtcpIv(salt: Buffer, ssrc: number, index: number): Buffer {
67
+ return computeSrtpIv(salt, ssrc, BigInt(index));
68
+ }