Auth & security¶
Authentication and the security-agreement machinery: SIP digest and IMS-AKA challenges, P-CSCF IPsec sec-agree (3GPP TS 33.203 / RFC 3329), and STIR/SHAKEN signing and verification.
from siphon import auth
@proxy.on_request("INVITE")
def route(request):
if not auth.verify_digest(request, "example.com"):
auth.require_proxy_digest(request, "example.com")
return
request.relay()
auth namespace¶
Mock authentication namespace.
Control auth behavior in tests::
from siphon import auth
auth._allow = True # all auth checks pass
auth._allow = False # all auth checks fail (challenge sent)
add_user
¶
Add credentials for testing (test helper).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
realm
|
str
|
Auth realm (e.g. |
required |
username
|
str
|
Username. |
required |
password
|
str
|
Password. |
required |
require_www_digest
¶
Challenge with 401 WWW-Authenticate, or verify existing credentials.
If credentials are valid: sets request.auth_user, returns True.
Otherwise: sends 401 response, returns False.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
request
|
Any
|
The SIP request. |
required |
realm
|
Optional[str]
|
Auth realm (e.g. |
None
|
Returns:
| Type | Description |
|---|---|
bool
|
|
require_proxy_digest
¶
Challenge with 407 Proxy-Authenticate.
Same as :meth:require_www_digest but uses 407.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
request
|
Any
|
The SIP request. |
required |
realm
|
Optional[str]
|
Auth realm. |
None
|
require_digest
¶
Convenience alias for :meth:require_www_digest.
require_ims_digest
¶
IMS digest authentication via Diameter Cx MAR/MAA.
Sends a Multimedia-Auth-Request to the HSS and uses the returned authentication vector to challenge or verify the UE.
Returns:
| Type | Description |
|---|---|
bool
|
|
require_aka_digest
¶
IMS AKA digest authentication using local Milenage credentials.
Uses locally-configured K/OP/AMF credentials (from auth.aka_credentials
in siphon.yaml) to generate AKA authentication vectors — no Diameter HSS
connection needed. The nonce contains base64(RAND || AUTN) per 3GPP TS 33.203,
and CK/IK are derived for IPsec SA creation.
Example::
if not auth.require_aka_digest(request, realm="ims.test"):
log.info("sent 401 AKA challenge")
return
Returns:
| Type | Description |
|---|---|
bool
|
|
verify_digest
¶
Verify credentials without sending a challenge.
Returns:
| Type | Description |
|---|---|
bool
|
|
ipsec namespace¶
P-CSCF IPsec security association management for the sec-agree handshake.
Mock :class:Ipsec namespace.
set_allocate_failure
¶
set_allocate_failure(
exc_type: Optional[type[BaseException]],
message: str = "mock allocate failure",
) -> None
Configure the next allocate() call to raise exc_type.
Pass None to clear and let allocate succeed again.
set_pcscf_families
¶
Model which address families this P-CSCF has a listener for.
Both default to available. Set one to False to model a
single-stack P-CSCF so allocate() raises for a UE of the missing
family, exactly as the Rust binding does.
SecurityOffer¶
A Security-Client offer parsed from a REGISTER (request.parse_security_client()).
Mock :class:SecurityOffer — UE-side IPsec proposal.
Transform¶
An operator-policy transform choice (Transform.HmacSha1_96Null, …).
Mock :class:Transform enum — operator policy choice.
AuthVectorHandle¶
The opaque CK/IK container produced by reply.take_av().
Mock :class:AuthVectorHandle — opaque CK/IK container.
The bytes are not exposed to Python in the real binding; the mock
keeps them accessible via _ck/_ik for tests, but treats them
as consumed after one allocate.
PendingSA¶
An allocated-but-not-yet-active SA pair, returned by ipsec.allocate(...).
Mock :class:PendingSA.
activate
¶
Mark the SA pair active.
hard_lifetime_secs (optional) re-pins the kernel
hard-lifetime on all four SAs of the pair via XFRM_MSG_UPDSA,
without rekeying or disturbing selectors / SPIs. Use on the
path that processes the 200 OK to the auth REGISTER to tighten
the SA expiry from the placeholder value installed at
allocation time (typically the UE's Expires: ask) to the
actual grant from the registrar of record (3GPP TS 33.203 §7.4
— IPsec SA lifetime tracks SIP registration lifetime).
None (default) preserves the original metadata-only
transition.
In the mock, this only updates self.expires_secs so tests
can assert the script wired the grant through correctly.
SecurityServerParams¶
The Security-Server parameters to echo back to the UE.
Mock :class:SecurityServerParams.
SAHandle¶
A read-only view of the active SA that decrypted a request
(request.matched_sa).
Mock :class:SAHandle — read-only view of an active SA returned by
request.matched_sa. Tests can construct one directly and assign
it to request._matched_sa.
stir namespace¶
STIR/SHAKEN Identity-header signing and verification.
Mock stir namespace — STIR/SHAKEN signing and verification.
Scripts use::
from siphon import stir
@proxy.on_request("INVITE")
def on_invite(request):
origid = stir.sign(request, attestation="A") # add Identity header
request.relay()
@proxy.on_request("INVITE")
def verify_inbound(request):
result = stir.verify(request)
if result.verstat == "TN-Validation-Failed":
request.reply(438, "Invalid Identity Header")
return
stir.apply_verstat(request, result)
request.relay()
Test helpers: set :attr:signing_enabled / :attr:verification_enabled
to simulate config; call :meth:set_verify_result to pin the next
:meth:verify outcome; inspect :attr:signed / :attr:applied_verstats.
sign
¶
sign(
request: Any,
attestation: str = "A",
origid: Optional[str] = None,
orig_tn: Optional[str] = None,
dest_tn: Optional[str] = None,
) -> str
Build a SHAKEN Identity header and add it to request.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
request
|
Any
|
The outbound SIP request. |
required |
attestation
|
str
|
|
'A'
|
origid
|
Optional[str]
|
UUID origin identifier; a fresh v4 is generated if |
None
|
orig_tn
|
Optional[str]
|
Originating TN; defaults to the From user part. |
None
|
dest_tn
|
Optional[str]
|
Destination TN; defaults to the To / R-URI user part. |
None
|
Returns:
| Type | Description |
|---|---|
str
|
The |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
if signing is not configured. |
ValueError
|
if the orig/dest TN cannot be determined. |
sign_div
¶
sign_div(
request: Any,
orig_tn: Optional[str] = None,
dest_tn: Optional[str] = None,
div_tn: Optional[str] = None,
) -> None
Build a diverted-call (div) Identity header (RFC 8946).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
request
|
Any
|
The outbound (retargeted) SIP request. |
required |
orig_tn
|
Optional[str]
|
Originating TN; defaults to the From user part. |
None
|
dest_tn
|
Optional[str]
|
New destination TN; defaults to the To / R-URI user part. |
None
|
div_tn
|
Optional[str]
|
Diverting TN; defaults to the History-Info / Diversion user. |
None
|
verify
¶
Verify the Identity header(s) on request.
Returns a :class:MockStirResult. By default returns a passing result
when an Identity header is present, else No-TN-Validation;
override with :meth:set_verify_result.
Raises:
| Type | Description |
|---|---|
RuntimeError
|
if verification is not configured. |
apply_verstat
¶
Stamp the verstat parameter onto the asserted identity
(P-Asserted-Identity if present, else From) per ATIS-1000074 §5.3.1.
set_verify_result
¶
set_verify_result(
verstat: str = "TN-Validation-Passed",
passed: bool = True,
attestation: Optional[str] = None,
origid: Optional[str] = None,
orig_tn: Optional[str] = None,
reason: str = "ok",
passports: Optional[list[dict[str, Any]]] = None,
) -> None
Pin the result returned by the next :meth:verify call(s).
StirResult¶
The outcome of stir.verify(...).
Result of :meth:MockStir.verify — mirrors the Rust StirResult.
Attributes:
| Name | Type | Description |
|---|---|---|
verstat |
|
|
passed |
|
|
attestation |
|
|
origid |
|
|
orig_tn |
originating TN from the SHAKEN PASSporT. |
|
reason |
human-readable diagnostic / failure cause. |
|
passports |
list[dict[str, Any]]
|
decoded PASSporT claim dicts. |
passports
property
¶
Decoded claim sets of every PASSporT that parsed.