RFC 5630 · TLS 1.2/1.3 · per-tenant cert

SIPS — encrypted SIP signalling, RFC 5630 compliant.

The CodeB sovereign SBC terminates SIP-over-TLS on dedicated ports for both trunk-side and public-listener traffic. Each TLS handshake selects the per-tenant certificate by SNI from the operator’s existing Windows certificate store — one Let’s Encrypt cert per tenant, no SAN list to maintain. ACL, auto-blacklist and observability are shared with the rest of the bridge.

Get your tenant

SBC overview SIP-over-WebSocket Compare /sip-tls/health endpoint

Two TLS ports, mirroring the UDP topology

Adopting SIPS on a CodeB tenant does not rearchitect routing. The two TLS listeners sit beside the existing UDP listeners so the operator’s call flows, ACL rules and trunk plumbing continue to work as configured. Per-trunk opt-in means SIPS rolls out one trunk at a time.

TCP 5061 — trunk-side (mirrors UDP 5060)
Carrier ITSP with SIPS endpoint or enterprise SBC dialing in
TCP 6061 — public listener (mirrors UDP / TCP 6070)
Hardphone or SIP Phone requiring TLS-only REGISTER

Per-tenant cert via SNI

Each TLS handshake selects the per-tenant certificate from LocalMachine\My by SNI server name. Same cert store the WSS listener and TURN-TLS already use; same per-tenant LE cert auto-deployed via /acquire-cert (or wacs.exe directly).

Adding tenant N+1 means installing one more cert in the store — no SAN-list reissue, no operator-visible SIPS reconfiguration.

Per-trunk opt-in

In trunks-admin.html, every trunk has a TLS (SIPS) checkbox and an optional TLS port (default 5061). When checked, the bridge registers + dials via sips:user@host:5061;transport=tls per RFC 5630.

Most modern carriers expose a TLS port on 5061; legacy FRITZ!Box or ATA boxes typically don’t — leave the checkbox off for those.

ACL gate (shared with UDP)

The same acl.html rules that block SIP over UDP block SIPS too. SIPS traffic enters the same InboundDispatcher dispatch point, so per-IP, CIDR, glob, sip-user, sip-from, sip-did and tenant rules all apply with no per-protocol carve-out.

Hot-reload from disk, anti-self-lockout for private IPs, per-tenant rule pools — operator manages everything in one place.

Per-IP TLS auto-blacklist

Per-source-IP sliding-window detector: more than 15 failed TLS handshakes from one IP in a 5-minute window blocks that IP at the SIPS accept layer for 1 hour. Catches noisy SIP-scanner traffic at the cheapest possible response point.

Threshold deliberately higher than the TURN default (5) to forgive CGNAT-shared mobile carrier source IPs. Private / loopback / link-local / IPv4-mapped-IPv6 private addresses are never blocked.

Slow-loris defence

A 15-second handshake timeout shuts down peers that dribble bytes after the TLS record header. A per-channel SemaphoreSlim(200) caps in-flight handshakes per port so a /16 scanner can’t starve the threadpool.

Over-cap accepts get a fast TCP RST + a throttled WRN log; legit clients can retry instantly.

Observability without REST exposure

One greppable log line per inbound SIPS request: [sips-arrival] with CallID, method, from, to, remote, local. One [sips-downgrade] WRN per call when a SIPS leg bridges to a plain SIP leg — never silent, never blocked.

Periodic [SIPS-stats] line every 30 s with handshake counters, fail counts and active blocklist size. Operator-facing /sip-tls/health endpoint returns the same data as JSON.

SRTP via SDES — media-plane encryption on the trunk leg

SIPS protects the SIP control plane. SDES (Session Description Protocol Security Descriptions, RFC 4568) keys SRTP for the media plane on the same trunk leg. Together they make the leg between bridge and carrier encrypted end-to-end on both planes: SIPS for signalling, SDES for media.

Per-trunk SRTP toggle

In trunks-admin.html, every trunk has an SRTP checkbox alongside the existing TLS (SIPS) checkbox. When checked, the bridge offers SDES on outbound INVITEs and answers SDES on inbound INVITEs from this trunk. Default OFF per trunk so existing routing is unchanged until an operator opts in.

Crypto suite: AES_CM_128_HMAC_SHA1_80

Outbound INVITEs offer m=audio … RTP/SAVP with a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:<base64-key> — the mandatory baseline crypto suite from RFC 4568. The 32-byte key (128-bit master key + 112-bit master salt) is freshly generated per call.

Asymmetric inbound answer

An inbound INVITE that includes a=crypto: is answered with SDES regardless of the trunk’s outbound toggle — safe-by-default. Peers that prefer SRTP get SRTP back; peers that don’t offer crypto keep working as before.

Fail closed, never silently plaintext

When the per-trunk SRTP toggle is on and the peer rejects the SDES offer (or returns no usable crypto line), the call ends. There is no fallback to plaintext RTP that an operator hasn’t explicitly approved. Every rejection is logged at INF level with the call correlation ID.

Bridge-terminated for AI & recording

The bridge terminates the SDES session and re-emits the right thing on the far leg. AI receptionist (Gemini media path), signed call recording, transcription and lawful-intercept hooks continue to operate because the bridge sees decoded audio internally. The carrier never sees plaintext on the wire.

Complementary to DTLS-SRTP on WebRTC

Browser legs already negotiate DTLS-SRTP automatically. SDES on the trunk leg fills the symmetric gap on the carrier side. Pair both with the per-trunk TLS (SIPS) toggle and the trunk leg is encrypted on signalling and media planes simultaneously.

EU CRA and NIS2 alignment: media-plane encryption between a regulated organisation and its telecoms boundary is increasingly a baseline expectation, not a nice-to-have. SDES on the trunk leg, paired with SIPS on the signalling plane, ticks both boxes with operator-controllable per-trunk granularity — no all-or-nothing rollout.

Why TLS on the signalling plane

SIPS protects the metadata layer of every call: who called whom, what extension was dialled, what the auth realm is, what carrier identity headers (P-Asserted-Identity, P-Preferred-Identity) say about the caller. On a UDP or TCP-without-TLS leg, all of that crosses the network in plain text — visible to any intermediate ISP, IXP or transit AS that captures the packet.

  • NIS2 / DORA / CRA alignment. EU compliance frameworks increasingly call for transport-encrypted control-plane traffic between regulated entities and their telecoms providers. SIPS on the signalling plane is the simplest box to tick.
  • Defence against passive metadata harvest. Caller-ID, called-DID and call-frequency metadata leak business intelligence. TLS encrypts the SIP headers so a passive observer sees only TLS application records.
  • Anti-spoofing at the registration layer. A REGISTER over plain UDP is trivially observable + replayable from a man-on-the-side attacker. Over TLS, the registration password digest is protected by the TLS session key.
  • Carrier-side identity assurance. Carriers that issue P-Asserted-Identity expect SIPS signalling on the trust boundary; with SIPS, the bridge can present + accept PAI without it ever crossing in plaintext.

How an operator turns on SIPS for a trunk

  1. Install the tenant LE cert. If the tenant’s certificate isn’t already in LocalMachine\My, run wacs.exe or use the superuser Get LE cert button on superadmin.html. The SipsCertResolver picks it up by SNI on the next handshake.
  2. Open the trunk in trunks-admin.html. Tick the TLS (SIPS) checkbox. Adjust TLS port only if the carrier exposes TLS on a non-standard port; default 5061 covers ~95% of carriers.
  3. Save the trunk. The bridge hot-reloads the trunks file; the TrunkRegistrar recreates the SIPRegistrationUserAgent with sips:user@host:5061;transport=tls within a few seconds.
  4. Verify in the bridge log. Grep for REGISTER <Label> TLS (sips:) at INF level; on success the carrier responds with REGISTER OK via the existing event chain.
  5. Confirm via REST. GET /sip-tls/health shows status: listening, the per-tenant cert thumbprint, total handshake count and the active autoblock-list size.

Frequently asked

Is SIPS mandatory for every tenant?

No. SIPS is opt-in per trunk in the admin UI. The bridge boots with SIPS listeners bound and ready, but a trunk only switches to sips: URIs when an operator ticks the TLS checkbox. Tenants that don’t need SIPS continue to operate on UDP / TCP exactly as before; no migration cost.

What about SRTP for the media plane on a SIPS call?

SIPS protects the signalling plane (REGISTER, INVITE, BYE, CANCEL, OPTIONS …). SRTP for the media plane is a complementary, separate negotiation. WebRTC browser legs use DTLS-SRTP automatically. For the carrier trunk leg, the bridge offers SDES per RFC 4568 with the AES_CM_128_HMAC_SHA1_80 crypto suite when the per-trunk SRTP toggle is on — outbound INVITEs offer RTP/SAVP with a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:…, and inbound INVITEs answer SDES whenever the peer offers it. If the peer rejects, the call ends — no silent plaintext fallback. The bridge terminates SRTP so recording, AI receptionist and lawful-intercept hooks continue to work. Where a SIPS-shaped INVITE still bridges to a plain SIP trunk (SRTP off), one structured WRN log per call surfaces every plaintext-on-the-far-side handoff.

Does this work with TLS 1.3 and post-quantum hybrid key exchange?

Yes for TLS 1.3 today. The bridge forces TLS 1.2+TLS 1.3 explicitly via the SCHANNEL provider. Post-quantum hybrid key shares (Chrome 124+ ships X25519+Kyber768; later ML-KEM-768 per NIST FIPS 203) are handled by SCHANNEL on Windows Server 2025+ — we just present the cert. The SNI peek buffer is sized for 8KB ClientHellos to comfortably accommodate hybrid key shares.

How does the auto-blacklist avoid locking out legit clients behind CGNAT?

SIPS threshold is 15 failures in 5 minutes per source IP — deliberately higher than the TURN default of 5. The reasoning: many mobile carriers route thousands of users behind one IPv4 source IP. Setting the threshold too low would lock out an entire carrier’s mobile pool on first failure burst. Private, loopback, link-local and IPv4-mapped-IPv6 private addresses are exempt entirely.

Where do I see SIPS health and traffic counters?

The bridge exposes GET /sip-tls/health with no auth (information disclosure is bounded; mirrors /sip-ws/health and /sfu/health). The JSON shape includes per-port wire-up state, lifetime handshake count, current and lifetime blocklist sizes, and a human-readable autoblocklist policy string. The same data is also emitted every 30 seconds as a single greppable INF line tagged [SIPS-stats].

Is this an EU-built solution?

Yes. CodeB is developed and operated by Aloaha Limited under EU law. SIPS deployments run on operator-controlled infrastructure (on-prem or sovereign cloud); there is no CodeB-side data plane, no third-party cloud STUN/TURN, no metadata export. The bridge code is .NET Framework 4.8 on Windows Server with SCHANNEL doing the TLS heavy lifting under FIPS-validated crypto.