Internal Security Review: WebRTC Stack of the .NET GatewayLink to the section Internal Security Review: WebRTC Stack of the .NET Gateway
On this page
Follow-up, 2026-10-16: WR-18 is addressed by authenticated per-client quotas across WHEP and session signaling in both gateways. WR-19 is addressed: Python WHEP enforces creator ownership and random ids, and INTERNAL signaling errors no longer echo exceptions. The finding table and open-finding descriptions below retain the original review snapshot. WR-20, WR-21 and WR-22 remain residual items. See software validation for the follow-up checks.
Status: Internal review, 2026-10-15. The project reviewed its own code. This is not the independent security review and penetration test that the roadmap (Phase 2) requires before any operational use. Until that review has been done, the .NET gateway remains a reference and test implementation. Some findings below are fixed and the rest are open. An independent reviewer should re-examine both groups, along with the residual risks in section 6.
1. ScopeLink to the section 1. Scope
Commit 1733d29 was reviewed: the WebRTC stack written from scratch for the .NET port of the reference gateway (dotnet/src/Univip.Gateway).
| Area | Files |
|---|---|
| ICE-lite and STUN | Media/WebRtc/Stun.cs, RtcPeer.cs, Crc.cs |
| DTLS 1.2 (BouncyCastle.Cryptography 2.7.0) | Media/WebRtc/Dtls.cs, RtcPeer.cs |
| SRTP, SRTCP, RTP, RTCP | Media/WebRtc/Srtp.cs, Rtp.cs |
| SCTP and DCEP data channel | Media/WebRtc/Sctp.cs |
| SDP offer parsing and answer | Media/WebRtc/Sdp.cs, RtcPeer.cs (negotiation) |
| Peer lifecycle, FFmpeg encoder and source | Media/MediaService.cs, IMediaService.cs, H264Encoder.cs, FrameSource.cs, MediaConfig.cs |
WHEP-style REST and the session signal path |
Http/GatewayApi.cs (media handlers), Http/ApiPlumbing.cs, Http/Auth.cs |
Out of scope: BouncyCastle internals beyond how the gateway uses its API (relevant behaviour was checked in its 2.7.0 source, see section 2), the .NET runtime, FFmpeg, the session channel and REST TLS (covered by SecurityTests), the client SDK, and the Python gateway. A Python issue found along the way is listed as WR-19.
2. MethodLink to the section 2. Method
- Every line in scope was read with every network byte treated as attacker-controlled: UDP datagrams, SDP bodies, signal payloads and data-channel messages. Each parser was checked for bounds, integer overflow, unbounded allocation, algorithmic cost, exceptions escaping a receive loop, and state that one peer can grow.
- Suspected issues were measured before they were fixed. For example, a 28 KB SDP offer took 1.6 s on the gateway loop (WR-01). Another example: 2000 SCTP packets with far-ahead TSNs took 17 s to process and produced 560 KB SACKs (WR-05).
- BouncyCastle 2.7.0 behaviour that the stack relies on was read in its source (tag
release-2.7.0) rather than assumed:- Under DTLS 1.2 a client that skips the Certificate message is refused.
- CertificateVerify is required.
- After the handshake, only records of the current epoch are accepted, plus epoch-0 handshake retransmissions for a short time, so unauthenticated alerts are discarded.
- Renegotiation and session resumption are not offered, and peer heartbeats are not allowed.
- Handshake messages are capped at 32 KiB and certificate chains at 10 entries.
new SecureRandom()is a SHA-256 DRBG seeded from the operating system's CSPRNG.
- How .NET behaves was checked by experiment where the answer is platform-specific. A UDP datagram larger than the receive buffer is truncated on Linux but raises
SocketError.MessageSizeon Windows. - Regression tests drive a real
RtcPeerover UDP on loopback with a scripted ICE/DTLS client (TestRtcClient). Fixed-seed fuzz loops cover STUN, SDP, SRTCP, SCTP and live datagrams before ICE, after ICE and after the DTLS handshake. Each key regression test was mutation-checked: reverting its fix makes it fail. - Interoperability with aiortc (decoding, RTCP sender-report sync, data channel, trickle ICE, teardown) was re-run after every change.
3. Threat model assumptionsLink to the section 3. Threat model assumptions
| Actor | Capabilities assumed |
|---|---|
| A1: unauthenticated network peer | Reaches the gateway's UDP ports and can spoof source addresses. Knows no credentials. |
| A2: on-path attacker | Sees and injects datagrams between viewer and gateway. Cannot break TLS on the REST or session channel. |
| A3: authenticated low-privilege viewer | Holds a univip.view token. Creates and deletes WHEP sessions, and so knows the ICE password and DTLS keys of its own peers. May be able to spoof source addresses on its network. |
| A4: the session-channel client endpoint | Sends signal offers. Trusted no more than A3 for media. |
Assets: availability of the gateway loop, which also runs leases, commands and telemetry for every device and so is safety-relevant; the confidentiality and integrity of video and of the telemetry data channel; other viewers' sessions; third parties, who could be targeted by reflection or amplification; and host resources (memory, CPU, sockets, processes).
The SDP offer and answer travel only over the authenticated REST or session channel. The ICE password and the DTLS fingerprint exchanged there are the only things binding UDP traffic to the viewer who was authorized.
4. FindingsLink to the section 4. Findings
Severity: High: one low-privilege viewer, or an attacker who can spoof addresses, can deny service to the whole gateway or use it against third parties at scale. Medium: affects other users' sessions, or is a single-packet denial of service on a supported platform. Low: defence in depth, or impact limited to the attacker's own session. Info: hardening with no demonstrated impact.
| ID | Severity | Finding | Status |
|---|---|---|---|
| WR-01 | High | SDP parsing cost grows quadratically on the single gateway loop | Fixed 2256ae1 |
| WR-02 | Medium | Any univip.view client can DELETE another viewer's WHEP session; resource ids are predictable ULIDs |
Fixed 2256ae1 |
| WR-03 | High | Media is sent to unverified addresses: redirection and amplification toward third parties | Fixed 2256ae1 |
| WR-04 | Low | Unauthenticated STUN requests are answered (reflection) | Fixed 2256ae1 |
| WR-05 | High | SCTP state and CPU grow without bound under a viewer's control | Fixed 2256ae1 |
| WR-06 | Low | No SRTCP replay protection | Fixed 2256ae1 |
| WR-07 | Low | RTP send path not serialized: SRTP counter (keystream) reuse possible while an encoder restarts | Fixed 2256ae1, 5879e3b |
| WR-08 | Medium | One datagram can stop a peer's UDP reader (Windows) | Fixed 2256ae1 |
| WR-09 | Low | DTLS offered CBC cipher suites, one with HMAC-SHA1 | Fixed 2256ae1 |
| WR-10 | Low | Fingerprint check decided by the first supported a=fingerprint; SHA-1 accepted |
Fixed 2256ae1 |
| WR-11 | Low | One DTLS certificate per device for the life of the process (expires after 30 days; links sessions) | Fixed 2256ae1 |
| WR-12 | Low | STUN USERNAME checked by prefix only; attributes after MESSAGE-INTEGRITY honoured; captured checks replayable from other addresses | Fixed 2256ae1 |
| WR-13 | Low | Control characters in offers echoed into the answer; ICE credentials not validated | Fixed 2256ae1 |
| WR-14 | Info | Timer callback without an exception guard (an exception would end the process) | Fixed 2256ae1 |
| WR-15 | Info | DTLS thread lingered after the peer closed | Fixed 2256ae1 |
| WR-16 | Info | INTERNAL signal errors echoed exception text to the client |
Fixed 2256ae1 |
| WR-17 | Info | avcC parser of the encoder output not bounds-checked | Fixed 5879e3b |
| WR-18 | Medium | One viewer can hold every peer slot (max_peers) |
Open |
| WR-19 | Medium | Python gateway: WR-02 and WR-16 apply unchanged | Open |
| WR-20 | Low | DTLS 1.2 handshake alerts are unauthenticated | Open (inherent) |
| WR-21 | Low | The answer discloses every IPv4 interface address of the gateway | Open |
| WR-22 | Info | No rate limit on WHEP POST; a PLI restarts FFmpeg up to every 2 s | Open (accepted) |
WR-01 (High): SDP parsing cost on the gateway loopLink to the section WR-01 (High): SDP parsing cost on the gateway loop
CheckOffer and Negotiate re-parsed a payload type's a=fmtp line once for every format listed on the m-line. Duplicates were allowed, and so were any number of m-lines and lines of any length, up to the 1 MiB REST body limit. Offers are parsed on the gateway loop, which also runs leases, commands and telemetry for every device.
Measured on the reviewed code:
| Offer size | Parse time |
|---|---|
| 7 KB | 146 ms |
| 14 KB | 525 ms |
| 28 KB | 1576 ms |
The cost is quadratic, so a 1 MiB offer from any univip.view client (A3) or session peer (A4) would stall the loop for tens of minutes. Vehicle leases would expire into hold and commands would go unanswered.
Fix:
- Offers are limited to 64 KiB, 4 KiB per line, 64 m-lines, 128 distinct formats per m-line and 64 candidates per m-line. REST answers 413 before parsing.
- Formats are deduplicated and
a=fmtpis parsed once per payload type. - Error messages quote at most 60 characters of the offer.
Tests:
Sdp_offers_are_bounded_and_pathological_ones_fail_fastSdp_parser_survives_fuzzingMediaTests.Oversized_and_malformed_offers_are_refused_before_any_peer_exists
WR-02 (Medium): WHEP DELETE authorization and resource idsLink to the section WR-02 (Medium): WHEP DELETE authorization and resource ids
DELETE .../webrtc/{resource_id} required only univip.view, so any viewer could end any other viewer's WHEP session. Resource ids were ULIDs from the process-wide monotonic generator that also produces msg_id, event_id and command_id. An id made in the same millisecond as one a viewer can see (a telemetry msg_id on its own data channel, for instance) is that id plus one.
Fix:
- A WHEP resource records the
client_idthat created it. DELETE succeeds only for that client; anyone else gets 404 and learns nothing. - Resource ids are 128 bits from
RandomNumberGenerator.
Tests:
MediaTests.Whep_resources_can_only_be_deleted_by_their_creatorWhep_resource_ids_are_128_random_bits
WR-03 (High): media sent to unverified addressesLink to the section WR-03 (High): media sent to unverified addresses
Two flaws let an attacker choose where the gateway sends its stream:
- Any DTLS-range datagram from an address that had passed one ICE check moved the selected pair to that address. An on-path attacker (A2) who replayed a captured check from its own address, and then sent one byte in the DTLS range, received the stream. It is encrypted, but it was denied to the viewer.
- More seriously, a viewer (A3) knows the ICE password of its own peer. With one spoofed, authenticated check carrying USE-CANDIDATE from a victim's address, it could point the video stream (up to
max_bitrate_kbps, 2.5 Mbit/s by default) at that victim. Consent was refreshed by any valid check from any address, so the viewer could keep the flood going indefinitely from its own address. That is an amplification of several orders of magnitude, from a gateway that is often on a constrained radio or cellular link.
The gateway is an ICE-lite agent, which never sends checks, so it had no way to know the destination wanted the media.
Fix: the lite agent now implements RFC 7675 consent freshness itself.
- It sends its own binding requests (USERNAME
remote:local, PRIORITY, ICE-CONTROLLED, MESSAGE-INTEGRITY with the remote password, fresh random transaction id) to the selected address. It does this on nomination and then every 4 to 6 s. - Only a success response from that exact address, verified with the remote password, grants consent.
- Nothing but STUN is sent to an address without fresh consent. A few datagrams (for example, the DTLS flight) are held until consent arrives.
- The peer closes when the selected pair has had no consent for 30 s. This also detects a viewer that went away.
- DTLS datagrams no longer move the selected pair; only authenticated nominations do.
- At most 16 remote addresses can be validated per peer.
aiortc and browsers answer these checks, as RFC 8445 requires; the aiortc interoperability tests pass.
Tests:
Media_goes_only_to_an_address_that_answers_consent_checks: the victim receives only a few small STUN requests and no media, and the peer closes.A_client_that_goes_away_loses_consent_and_the_peer_closes
WR-04 (Low): reflection of unauthenticated STUNLink to the section WR-04 (Low): reflection of unauthenticated STUN
Every datagram that looked like STUN was answered: requests with a foreign USERNAME, with no MESSAGE-INTEGRITY, with a bad FINGERPRINT, or a bare 20-byte header got a 48-byte unsigned 401. That made every peer port an unauthenticated reflector (A1) with about 2.4 times amplification and no rate limit.
Fix:
- Requests not addressed to this agent, or without MESSAGE-INTEGRITY, or with a bad FINGERPRINT, are dropped silently.
- A 401 is sent only when the USERNAME matches and the integrity check fails. Such a request is larger than the answer.
- 401s are limited to 5 per second per peer.
Test: Unauthenticated_stun_is_dropped_and_wrong_integrity_gets_rate_limited_401s.
WR-05 (High): unbounded SCTP stateLink to the section WR-05 (High): unbounded SCTP state
A viewer (A3) speaks SCTP inside its own DTLS connection and could grow gateway memory and CPU without limit:
- DATA chunks far ahead of the cumulative TSN were all remembered. Each SACK listed every gap: 560 KB SACKs, an overflowing 16-bit chunk length, and O(n) work per packet.
- Fragments that never end, including zero-length ones, were buffered forever and re-sorted on every arrival.
- Every DATA_CHANNEL_OPEN created a channel (up to 65 536) with a label of any size and queued an ACK, even when the peer advertised a zero window.
- Each gap block of a received SACK was matched against every outstanding chunk.
- An INIT bundled with other chunks bypassed the verification-tag check for the whole packet.
Fix:
- DATA is accepted at most 4096 TSNs ahead of the cumulative TSN; empty DATA chunks are ignored.
- Reassembly is limited to
a=max-message-size(64 KiB) per message and 256 KiB in all, with a per-fragment overhead charge. - At most 32 channels, labels and protocols of at most 1024 bytes, malformed OPENs ignored, no reopening of a stream in use.
- Received SACKs are processed in one sorted merge.
- At most 64 gap blocks are reported, so a SACK fits one packet; outgoing chunks are bundled up to the path MTU.
- An INIT must be alone in its packet with tag 0, and its parameters are validated.
- After 10 retransmissions of a chunk the association fails (RFC 4960
Association.Max.Retrans).
The state cookie is a per-association random value, compared in constant time and neither signed nor timed. That is acceptable only because SCTP runs inside the authenticated DTLS connection.
Tests:
Sctp_far_ahead_tsns_are_bounded_and_sacks_fit_a_packetSctp_reassembly_and_channels_are_boundedSctp_opens_the_telemetry_channel_and_acknowledges_with_gap_blocksSctp_init_must_be_alone_with_tag_zeroSctp_survives_fuzzing
WR-06 (Low): SRTCP replayLink to the section WR-06 (Low): SRTCP replay
Authenticated SRTCP was accepted any number of times. An on-path attacker (A2) could replay a PLI or FIR to force encoder restarts (each starts an FFmpeg process; rate-limited to one every 2 s) or replay a BYE.
Fix:
- A 64-packet sliding replay window per SSRC (RFC 3711 §3.3.2), for at most 32 SSRCs.
- SRTCP without the E (encrypted) flag is refused; WebRTC always encrypts.
Tests: Srtcp_replay_window and Srtcp_feedback_is_authenticated_and_replays_are_ignored. The second test also checks a tampered packet and a cleared E flag.
WR-07 (Low): RTP send path not serializedLink to the section WR-07 (Low): RTP send path not serialized
SendAccessUnit packetized and protected outside any lock. A PLI or FIR restarts the peer's encoder, and the old encoder's reader could still be delivering when the new one starts. Two threads could then take the same RTP sequence number, giving the same SRTP counter, and so the same keystream, for different plaintexts.
Fix:
- Packetizing, protecting and sending one access unit happen under one per-peer lock, which the sender report also takes.
- A disposed encoder no longer delivers (
5879e3b).
WR-08 (Medium): one datagram stops the UDP reader (Windows)Link to the section WR-08 (Medium): one datagram stops the UDP reader (Windows)
The receive loop returned on any SocketException except ConnectionReset. Windows reports a datagram larger than the 2048-byte buffer as MessageSize, so one unauthenticated datagram (A1) ended the reader. Consent then lapsed and the peer was dropped. Linux truncates instead; this was confirmed by experiment.
Fix: the reader continues after per-datagram errors (MessageSize, ConnectionReset, ConnectionRefused, NetworkReset, unreachable and similar) and stops only on disposal or other errors, which are logged.
Tests:
Receive_loop_treats_per_datagram_socket_errors_as_transient- the 9000-byte datagrams in
Fuzzed_datagrams_before_and_after_the_handshake_do_not_hurt_the_peer
WR-09 to WR-17 (Low and Info)Link to the section WR-09 to WR-17 (Low and Info)
-
WR-09: DTLS also offered
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256and..._CBC_SHA(MAC-then-encrypt, HMAC-SHA1). Now only AEAD suites are offered: AES-128-GCM (mandatory in RFC 8827), AES-256-GCM and ChaCha20-Poly1305. -
WR-10: the first
a=fingerprintwith a supported hash decided the outcome, even when a later one (another certificate or hash, RFC 8122 §5) would match. SHA-1 was accepted. Now any SHA-256, SHA-384 or SHA-512 match is accepted, SHA-1 and MD5 are ignored, an offer without a SHA-2 fingerprint is refused at negotiation, and a mismatch is logged as an authentication failure (Part 13 §13.8). Tests:Dtls_with_another_certificate_is_refused_and_the_peer_closes_without_mediaDtls_without_a_client_certificate_is_refusedFingerprints_match_any_offered_sha2_hash_and_ignore_sha1
-
WR-11: one ECDSA P-256 certificate per device, valid 30 days, was kept for the life of the process. A gateway running longer presented an expired certificate, and the fixed fingerprint linked every session of the device. Now a certificate is generated per peer.
-
WR-12: the USERNAME was checked only for the prefix
local:. Attributes after MESSAGE-INTEGRITY (not authenticated) were honoured, contrary to RFC 8489 §14.5: an on-path attacker could append USE-CANDIDATE to a captured check. A captured check replayed from another address validated that address. Now:- the USERNAME must equal
local:remote; - the parser drops attributes after MESSAGE-INTEGRITY except FINGERPRINT;
- a transaction id already seen from another address is refused (the last 256 are remembered; retransmissions come from the same address).
Test:
Captured_checks_replayed_from_another_address_are_ignored. - the USERNAME must equal
-
WR-13: a lone CR or other control character in an offer value (
a=mid, m-line formats) was copied into the answer. ICE credentials were not checked. The impact is limited to the sender's own answer. Now:- control characters are refused;
a=midand formats must be SDP tokens;- fingerprints must be well-formed;
ice-ufragandice-pwdmust meet RFC 8839 (4 to 256 and 22 to 256 ice-chars).
Test:
Sdp_with_control_characters_or_bad_credentials_is_refused(9 cases). -
WR-14: an exception escaping the peer's
System.Threading.Timercallback would end the process. None is known to be reachable, but the callback is now guarded. -
WR-15: closing the DTLS datagram transport did not wake a blocked receive, so the DTLS thread lived on for up to its handshake wait. Closing now completes the queue.
-
WR-16: an INTERNAL
signalerror carried the exception message to the client. It now says "media error" and the details stay in the log. -
WR-17: the AVCDecoderConfigurationRecord parser trusted its lengths. It now checks them; the input is FFmpeg's own output, so this is hardening.
Open findingsLink to the section Open findings
- WR-18 (Medium): one viewer can hold every peer slot.
max_peers(4 by default) applies to the whole device. Aunivip.viewclient can POST offers it never connects. Each is held for 30 s (the connect timeout) and can be renewed, so other viewers get 409. Fixing this needs a per-client_idquota and a matching setting in the Python gateway (they share the configuration), which is a policy decision for the project. See H-S4. Mitigation today: giveunivip.viewonly to trusted VMS principals. - WR-19 (Medium): Python gateway.
rest.pyandmedia.pyhave the WR-02 gap (DELETE requires onlyunivip.view; resource ids from the shared monotonic ULID generator), and INTERNALsignalerrors echo the exception (WR-16). The same fix applies: recordprincipal.client_idinwhep_offer, compare it inwhep_delete, and usesecrets.token_hex(16)for ids. The Python gateway's ICE (aioice, a full agent with RFC 7675 consent), DTLS and SCTP (aiortc) were not reviewed here. - WR-20 (Low): unauthenticated DTLS 1.2 handshake alerts. Before the handshake completes, alerts are plaintext. An attacker who can send from the viewer's validated address (on-path, or the viewer itself) can abort the handshake. This is inherent to DTLS 1.2; BouncyCastle discards such records after the handshake. DTLS 1.3 would not change pre-handshake alerts either.
- WR-21 (Low): interface disclosure. The answer lists a host candidate for every IPv4 address of every interface that is up, including management networks, to every
univip.viewclient. A configurable interface or address allow-list (with Python parity) would limit this. See H-S8. - WR-22 (Info): request rate. WHEP POST has no rate limit beyond authentication and
max_peers. Each POST binds sockets and generates a certificate, which takes about a millisecond. A viewer can make its own encoder restart (a new FFmpeg process) at most every 2 s with PLI or FIR. Rate limiting belongs in the deployment's reverse proxy for now.
5. Verified as soundLink to the section 5. Verified as sound
-
Randomness. All of these come from the CSPRNG (
RandomNumberGenerator; BouncyCastleSecureRandomis OS-seeded):- ICE
ufrag(48 bits) andpwd(144 bits); - SSRC, RTP timestamp base, SCTP tags and initial TSN;
- consent transaction ids, WHEP resource ids, and certificate keys.
Random.Sharedis used only for the initial RTP sequence number, the SDP session id and consent jitter. - ICE
-
Comparisons. MESSAGE-INTEGRITY, the SRTCP tag, the SCTP cookie and REST bearer tokens are compared in constant time.
-
SRTP. Key derivation and the AES-CM keystream match the RFC 3711 test vectors. The IV layout and per-direction keys are correct: a new end-to-end test re-protects a known access unit with the server write key exported by the client's own DTLS stack and gets the same bytes. The ROC is handled correctly for the gateway's own monotonic sequence.
-
DTLS roles.
a=setup:passiveoffers are refused, because the device is always the DTLS server.use_srtprequiresSRTP_AES128_CM_HMAC_SHA1_80. The handshake times out after 30 s and the peer's connect timeout is also 30 s. -
Demultiplexing. RFC 7983 is respected. Nothing but STUN is processed from an address before it passes an authenticated check, so DTLS and SRTCP from unvalidated addresses never reach BouncyCastle or SRTP. SRTCP is ignored before the handshake.
-
Telemetry channel. It exists only inside a DTLS connection whose certificate matches the offer of an authorized viewer (
univip.viewon REST, or the session channel). Client messages on it are ignored. -
Secrets. No secrets are logged: ICE passwords, keys, SDP bodies and tokens never reach a log call.
-
Cleanup. FFmpeg processes are killed when their peer drops, and unconnected peers expire.
6. Residual risksLink to the section 6. Residual risks
- This review was done by the authors. It is no substitute for the Phase 2 independent review and penetration test, and that review should cover BouncyCastle's DTLS state machine under adversarial input more deeply than the fuzzing here.
- The protocol baseline is DTLS 1.2 with
SRTP_AES128_CM_HMAC_SHA1_80only: no AEAD-GCM SRTP profiles (RFC 7714), no DTLS 1.3 and no rekeying. This is acceptable for current WebRTC peers. See finding H2. - ICE-lite limits. Consent proves that the selected address answers. An on-path attacker can still drop packets or abort a handshake (WR-20), and can deny video but not read it.
- Signaling dependence. The media path is only as strong as the TLS and bearer tokens protecting the offer and answer. Anyone holding a
univip.viewtoken is trusted with live video and position (Part 13 §13.4). - Logging. STUN integrity failures are logged at debug level only (rate-limited), to avoid log flooding by unauthenticated senders. DTLS certificate mismatches are logged as warnings.
7. Specification findingsLink to the section 7. Specification findings
These are proposed for docs/implementation-findings.md (section H) in its format.
| # | Severity | Finding | Reference implementation | Proposal |
|---|---|---|---|---|
| H-S1 | Gap | Part 09 does not require ICE consent freshness (RFC 7675). An ICE-lite device, which sends no checks, cannot otherwise tell that an address wants its media, so an authorized viewer can aim the stream at a third party (WR-03). | .NET: the lite agent sends RFC 7675 checks and sends nothing but STUN without consent. Python: aioice (full agent) does RFC 7675 natively. | Require RFC 7675 consent on every device, including ICE-lite ones: send checks on the selected pair, send media only with fresh consent, stop within 30 s. |
| H-S2 | Gap | The WHEP-style resource (C6) has no authorization or identifier rules: who may DELETE, and how unguessable the id must be. | .NET: only the creating client_id may DELETE (others get 404); 128-bit random ids. Python: any univip.view client; ULIDs (WR-19). |
Resource ids MUST carry at least 122 random bits. Only the creating client (optionally univip.admin) may DELETE; others get 404. Put the DELETE path in the OpenAPI. |
| H-S3 | Gap | No bounds on signaling input: SDP size, m-lines, line length, candidates, and the error for an oversized offer. | .NET: 64 KiB, 4 KiB lines, 64 m-lines, 128 formats, 64 candidates; REST 413, signal MALFORMED. |
State a maximum offer size (64 KiB) and per-element limits. REST answers 413 and the session channel answers MALFORMED. |
| H-S4 | Gap | Part 13 §13.4 grants media with univip.view but sets no per-principal limit, so one client can occupy every peer slot (WR-18). There is also no media error code for it (C4). |
Both: a device-wide max_peers only, with unconnected offers kept 30 s. |
Allow a per-client_id peer limit and name its error (409 stream-busy / RATE_LIMITED); recommend expiring unconnected offers within 30 s. |
| H-S5 | Gap | §13.2 says only "DTLS-SRTP as defined by WebRTC". It does not require checking the DTLS certificate against the a=fingerprint received over the authenticated channel, nor which hash functions count (extends H2). |
.NET: any SHA-256/384/512 match; SHA-1 ignored; one certificate per peer. | The device MUST verify the peer certificate against a SHA-256 or stronger fingerprint from the authenticated signaling; offers with only weaker fingerprints MUST be refused; per-session certificates RECOMMENDED. |
| H-S6 | Ambiguity | §13.8 requires logging authentication failures but does not say whether media-plane failures count. Logging every STUN integrity failure lets unauthenticated senders flood the log. | .NET: DTLS fingerprint mismatch logged as a warning; STUN integrity failures at debug, rate-limited. | DTLS authentication failures MUST be logged; transport-level noise MAY be rate-limited or aggregated. |
| H-S7 | Gap | The direction and limits of univip-telemetry are unstated (C5): may a client send on it, how large a message may be, and may a client open other channels? |
.NET: client messages ignored; a=max-message-size:65536; at most 32 channels, 1 KiB labels. |
Declare the channel device-to-client only. Devices advertise a=max-message-size and MAY refuse other channels. |
| H-S8 | Ambiguity | Host candidates disclose every device interface address, including management networks, to every viewer. Part 13 §13.7 (privacy) does not mention it. | Both gather all interfaces (WR-21). | Let devices restrict ICE interfaces; mention the disclosure in Part 13. |
| H-S9 | Editorial | Part 09 mandates H.264 without an informative note that encoders and decoders carry licence and patent obligations (for example libx264 is GPL, OpenH264's patent licence covers only Cisco's binaries, and the H.264 patent pool). | The .NET gateway lets the operator choose the encoder (media.encoder); see THIRD_PARTY_NOTICES.md. |
Add an informative note to Part 09. |
8. Tests and commitsLink to the section 8. Tests and commits
New gateway test cases: 44, taking Univip.Gateway.Tests from 86 to 130.
| File | Cases | Covers |
|---|---|---|
WebRtcSecurityTests |
30 | the findings above |
EncoderTests |
12 | media.encoder |
MediaTests |
2 | WHEP authorization and limits |
The scripted client is Support/TestRtcClient.cs. The full .NET suite (client 136, gateway 130) and the Python media tests pass with UNIVIP_PYTHON set.
| Commit | Content |
|---|---|
2256ae1 |
Security fixes WR-01 to WR-16 and their tests |
5879e3b |
media.encoder (licensing), WR-07 (encoder side) and WR-17 |