Skip to main content

SIP digest verification model

When a SIP device registers with Devotel Orbit, the digest handshake is verified at more than one layer. Reading any single layer in isolation is misleading — an operator who finds the local registration-cap check can misread its fail-open branch as a security hole. This page lays out the roles each verification layer plays, the challenge-response math those layers recompute, which digest envelopes the local check can and cannot recompute, and why falling open on the un-recomputable envelopes is the correct design. The credential the handshake authenticates is covered end to end in SIP credential lifecycle.

The challenge-response flow

SIP digest authentication is a challenge-response exchange, not a password on the wire. A device that sends a bare REGISTER (or an outbound INVITE) is answered with a 401 Unauthorized that carries a WWW-Authenticate: Digest challenge with three parameters: the realm, a one-time nonce, and the algorithm the response must use (MD5 by RFC 2617 baseline; RFC 7616 upgrades to SHA-256). The device recomputes its authorization and retries with a Digest header carrying the username, realm, nonce, the request uri, and its computed response. The math a verifier re-runs, on an MD5 challenge with qop=auth:
HA1 is the long-lived half of the secret: it is computed once when the credential is created (or rotated) and stored as the password hash — an MD5 variant and a SHA-256 variant sit side by side so the verifier can answer either challenge family. Because HA1 already folds the password in, neither the device nor the verifier ever sends the password itself. The nonce makes each challenge single-use: a captured response cannot be replayed against a fresh nonce, and nc (the nonce count) plus cnonce (the client nonce) bind the response to one exchange. An RFC 2069 legacy challenge omits qop entirely and the response reduces to MD5(HA1:nonce:HA2). The qop and cnonce the device echoes back are validated too — a response computed with the wrong qop for the challenge, or a missing nc/cnonce pair, fails verification even if the password was right.

Where digest runs

The same challenge-response runs on both SIP methods, at the Jambonz SBC edge:
  • REGISTER — binding a device. Jambonz calls its auth hook for the credential, receives the stored A1 hash, and verifies the device’s response against it before the binding forms. Orbit’s hook runs its own checks first, in this order: the realm gate (a digest computed against any realm other than the platform’s own is replay-resistance poisoning and is 401’d as realm_mismatch), the credential gates (disabled, expired, the IP allowlist ip_locked, the User-Agent lock ua_locked, and algorithm_unsupported when the credential holds no hash for the challenged algorithm), and finally the optional local digest re-check and per-credential registration cap. The failure statuses the edge surfaces for outbound attempts (403 / 402 / 480) are described in Jambonz softswitch model.
  • INVITE — placing a call. Outbound INVITEs from your own credential re-verify the same digest before caller-ID ownership and your outbound gates (blocked-country list, daily spend cap) are screened; an accurate SIP status comes back on failure, never a silent drop. Bring-your-own trunks authenticate on a parallel hook that accepts either digest or IP allowlist per trunk; the trunk model is covered in BYO carrier lifecycle.
Both admission hooks terminate on the same realtime edge — the carrier-facing Jambonz tier whose session layout and failover are laid out in Voice gateway: the realtime AI-voice media edge — and REGISTERs still terminate on the Devotel wholesale softswitch, which re-verifies the digest against the stored HA1 before any binding is accepted.

The three roles of SIP authentication

SIP registration authentication in Orbit plays three distinct roles, each at a different layer:
  1. Edge verification — prove the password before the binding forms. When a device sends REGISTER, the Jambonz softswitch challenges with a nonce and re-computes the digest response against the stored HA1 (the RFC 2617 A1 hash over username:realm:password, kept in an MD5 and a SHA-256 variant). A bad response gets a 401/403 and no binding ever forms. A stolen username alone cannot register.
  2. Optional local re-check — keep bogus User-Agents out of the cap. A credential with a registration cap re-verifies the digest locally before the slot is counted, so an attacker who knows only the username cannot fill the cap with junk endpoints and lock out your real devices. This check is accounting, not an auth gate — and this page documents exactly when it abstains.
  3. Softswitch as authority — the last word on registration. The softswitch re-verifies the digest downstream regardless of anything Orbit’s local layer concluded. The local layer can only ever decide whether a cap slot is counted; it can never admit a registration the softswitch would reject, and it never overrides a rejection.

The digest algorithm envelope

The local re-check recomputes the expected response from the same stored HA1 it hands the softswitch, using the algorithm the device announced:
  • MD5 or SHA-256 with qop=auth (or RFC 2069 no-qop) — fully recomputable. The expected response is rebuilt over (HA1, nonce, nc, cnonce, qop, HA2) and compared in constant time. A mismatch means the digest is wrong and the slot is not counted.
  • *-sess variants (MD5-sess, SHA-256-sess) — the sess HA1 folds the nonce and client nonce into the A1 hash itself. Orbit stores the base HA1 only, so the sess variant cannot be reconstructed locally. The check abstains and the slot is not counted.
  • qop=auth-int — needs a hash of the SIP message body, which the local hook does not receive. The check abstains and the slot is not counted.
  • Missing digest fields (nonce, URI, response) or an unrecognized algorithm — nothing to recompute against. Abstain and do not count the slot.
Every abstention has the same outcome: the registration proceeds to the softswitch for the real verification, and the cap simply does not reserve a slot for that attempt.

Why fail-open is correct

The fail-open posture follows directly from the split of roles above. The local check exists to keep the cap a fair accounting of verified registrations — it is a guardrail, not a gate. If it failed closed on digests it cannot recompute, any device legitimately negotiating a *-sess algorithm or auth-int quality-of-protection would be turned away from a cap it should pass, and nothing the softswitch decided afterwards could un-block it. Failing open cannot admit an attacker. A bogus digest on a sess algorithm skips the slot accounting and reaches the softswitch, which re-verifies against the stored HA1 and rejects it with a 401/403 — the same outcome the edge challenge produces for any bad digest. The worst case of an abstention is a registration whose slot was not counted, while the binding still has to survive the authoritative verification. The worst case of failing closed would be turning away your own phones.

Which envelopes must fall open

The platform’s outbound and registration boundary is anchored by a single rule — inbound termination stays at the edge, and the softswitch remains the authority on what registers. That rule fixes precisely which digest envelopes the local check is allowed to abstain on rather than misjudge:
  • *-sess algorithms — abstain; the session-HA1 variant is provably un-recomputable from the stored base HA1.
  • qop values other than auth (today auth-int) — abstain; the entity-body hash is not available at the hook.
  • Missing nonce, URI, or response fields — abstain; there is no well-formed digest to compare.
Envelopes outside that list — MD5 or SHA-256 with qop=auth, or RFC 2069 with no qop — must verify exactly, with the comparison in constant time. When you review or extend the digest handling, keep the abstention list complete: an envelope added to the digest path without falling into one of these classes is the defect case, and a *-sess or auth-int attempt blocked by the local check is the regression this page exists to prevent.

Failure taxonomy

A REGISTER that cannot complete breaks in one of two families. Digest-math failures mean the handshake itself did not verify: a badly computed response (wrong password, a device that hashed the wrong URI or realm), a stale or mismatched nonce, a realm_mismatch (the response was computed against a realm the platform does not issue), or algorithm_unsupported (the device was challenged with an algorithm the credential holds no hash for — rotate the credential to mint both variants). Policy failures mean an otherwise valid credential was refused — disabled, expired, ip_locked, ua_locked, or max_registrations — and carry credential-level reasons rather than cryptographic ones. The split also tells you who can act. Tenant-side causes are the ones you can read and fix yourself: a mistyped or rotated-but-not-redistributed password (the mis-computed digest case), a disabled or expired credential, an IP allowlist that the device’s new egress address is not in, a User-Agent pin the firmware update renamed past, or a registration cap filled by stale bindings — evict the binding or raise the cap. Platform-side causes belong to the operator: a realm_mismatch on the correct password means the SBC is challenging with a realm that drifted from the platform’s configured realm, and the register_auth_outcome counter’s ok share cratering while individual credentials look healthy means the edge — not your device — is refusing.

Security posture: digest vs IP ACL vs mutual TLS

Digest authentication proves a credential, not a device: anyone who holds username + password can authenticate from anywhere, which is precisely what a stolen HA1 or leaked password buys. Digest on its own stops passive eavesdroppers (the password never crosses the wire and a captured response does not replay) and credential-less username guessing; it does not stop active brute force against a weak password, and it does not bind a source network. An IP allowlist does the opposite: it proves a network, not a credential, so a lost allowlisted address cannot authenticate without the password, and a leaked password cannot authenticate from an unlisted address — but the allowlist alone, on a trunk without digest, trusts any caller that can source the right address. Mutual TLS proves the endpoint and channel — both peers hold certificates and the transcript is encrypted — but in carrier SIP the inter-carrier segment is outside either party’s TLS, so mTLS can only ever cover the hop you terminate. You choose the combination per surface. Per-user softphone credentials are digest-first everywhere, and the two credential knobs — allowedIpCidrs and allowedUserAgent — are yours to layer on when your fleet should be reachable from only a corporate network or a managed client build; both are off (open) until you set them. Customer BYO trunks offer digest or IP allowlist because inbound carriers you interconnect with often cannot hold a secret for you — pick digest when the peer can keep one, and stack the allowlist on top whenever the peer’s egress range is stable. The credential-side knobs are laid out in SIP credential lifecycle; the trunk-side modes in BYO carrier lifecycle.

Operator walkthrough: reading a failing REGISTER

A desk phone that flaps between unregistered and registered, or never registers, almost always resolves in five reads:
  1. Read the reason on the 401. The edge returns one of the taxonomy reasons above; a realm_mismatch on the correct password is the SBC-side drift class — report it, do not re-enter the password.
  2. Re-check what the device hashed. Point the phone’s domain/realm field at the realm that shows in your credential’s connection details and re-enter the password once; a mis-computed response from a saved-but-wrong realm is the most common tenant-side cause.
  3. Check the credential’s own locks. If you set allowedIpCidrs or a User-Agent pin, a new office egress or a firmware rename trips ip_locked / ua_locked even with a perfect digest.
  4. Check the cap, then the slot. When the reason is max_registrations, an earlier device’s binding is still holding a slot — unregister the stale binding from the credential’s page rather than raising the cap.
  5. Confirm the algorithm pairing. On algorithm_unsupported — a phone that only negotiates the variant your credential never minted — rotate the credential to store both hash variants and re-register.

Cluster anchor

This page owns only the challenge-response math — how a REGISTER or INVITE digest is challenged, recomputed, and adjudicated, and which envelopes legitimately abstain from the local re-check. The credential it authenticates — issuance, the plaintext-shown-once guarantee, rotation of the HA1/HA2 pair, revocation, and the per-credential caps — is owned by SIP credential lifecycle. The edge where the checks run and the SBC failures statuses surface is Jambonz softswitch model, with the realtime carrier tier in Voice gateway: the realtime AI-voice media edge. The SMPP side of the same “edge fronts a relay, tenant config reconciles onto it” pattern is SMPP edge model — the direct analogy to how a Jasmin bind’s authentication envelope is the messaging counterpart of the digest HA1 here.

Where the handshake terminates

Every REGISTER — recomputable or abstained — still terminates on the Devotel wholesale softswitch, which re-verifies the digest against the stored HA1 before any binding is accepted. The local envelope rules above only shape how the registration cap accounts for attempts; they never reroute, bypass, or override that termination. The edge handshake and the error statuses it surfaces (403 / 402 / 480) are described in Jambonz softswitch model.

See also