Replace MD5 with SHA-256/SHA-512-256 for SIP Digest Authentication (RFC 8760)#21
Replace MD5 with SHA-256/SHA-512-256 for SIP Digest Authentication (RFC 8760)#21codingjoe merged 3 commits intotranscribe-callfrom
Conversation
Co-authored-by: codingjoe <[email protected]>
|
Just as a heads up, I was blocked by some firewall rules while working on your feedback. Expand below for details. Warning Firewall rules blocked me from connecting to one or more addresses (expand for details)I tried to connect to the following addresses, but was blocked by firewall rules:
If you need me to access, download, or install something from one of these locations, you can either:
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## transcribe-call #21 +/- ##
===================================================
+ Coverage 94.66% 94.74% +0.08%
===================================================
Files 16 16
Lines 1256 1276 +20
===================================================
+ Hits 1189 1209 +20
Misses 67 67 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
Updates SIP Digest authentication to prefer RFC 8760 algorithms (SHA-256 / SHA-512-256) while keeping MD5 support for backward compatibility, and propagates the server-advertised algorithm through the challenge/response flow.
Changes:
- Added
DigestAlgorithmStrEnumrepresenting RFC 8760 digest algorithm tokens. - Updated SIP REGISTER challenge handling to negotiate and echo the server’s
algorithm, and updated digest computation to dispatch viahashlib.new(). - Expanded tests to validate SHA-256 defaulting and algorithm negotiation, and added unit coverage for multiple algorithms.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
voip/types.py |
Introduces DigestAlgorithm enum for RFC 8760 digest algorithm values. |
voip/sip/protocol.py |
Negotiates algorithm from WWW-Authenticate/Proxy-Authenticate, uses it in digest_response(), and echoes it in Authorization. |
tests/sip/test_protocol.py |
Updates REGISTER retry assertion to SHA-256 and adds unit tests for digest computation and negotiation. |
Comments suppressed due to low confidence (2)
voip/sip/protocol.py:401
response_received()only generates/echoescnoncewhenqopis present, butMD5-sess/SHA-256-sess/SHA-512-256-sessalso requirecnoncefor HA1 computation. As written, a server advertisingalgorithm=*-sesswithoutqopwill lead to an incorrect digest and the Authorization header will omitcnonce. Generatecnoncewhen the negotiated algorithm ends with-sessand include it in the Authorization header even ifqopis absent.
algorithm = params.get("algorithm", DigestAlgorithm.SHA_256)
qop_options = params.get("qop", "")
qop = (
DigestQoP.AUTH.value
if DigestQoP.AUTH.value in qop_options.split(",")
else None
)
nc = "00000001"
cnonce = secrets.token_hex(8) if qop else None
digest = self.digest_response(
username=self.username,
password=self.password,
realm=realm,
nonce=nonce,
method="REGISTER",
uri=self.registrar_uri,
algorithm=algorithm,
qop=qop,
nc=nc,
cnonce=cnonce,
)
auth_value = (
f'Digest username="{self.username}", realm="{realm}", '
f'nonce="{nonce}", uri="{self.registrar_uri}", '
f'response="{digest}", algorithm="{algorithm}"'
)
if qop:
auth_value += f', qop={qop}, nc={nc}, cnonce="{cnonce}"'
if opaque:
voip/sip/protocol.py:406
digest_response()now raisesValueErrorfor unsupportedalgorithm, butresponse_received()doesn’t handle that exception. If a server sends an unknown/typo’d algorithm token, this callback will raise and can break registration flow. CatchValueErroraround the digest computation, log a warning including the server-provided algorithm, and avoid scheduling a retry when the challenge can’t be satisfied.
digest = self.digest_response(
username=self.username,
password=self.password,
realm=realm,
nonce=nonce,
method="REGISTER",
uri=self.registrar_uri,
algorithm=algorithm,
qop=qop,
nc=nc,
cnonce=cnonce,
)
auth_value = (
f'Digest username="{self.username}", realm="{realm}", '
f'nonce="{nonce}", uri="{self.registrar_uri}", '
f'response="{digest}", algorithm="{algorithm}"'
)
if qop:
auth_value += f', qop={qop}, nc={nc}, cnonce="{cnonce}"'
if opaque:
auth_value += f', opaque="{opaque}"'
if is_proxy:
asyncio.create_task(self.register(proxy_authorization=auth_value))
else:
asyncio.create_task(self.register(authorization=auth_value))
You can also share your feedback on Copilot code review. Take the survey.
| is_sess = algorithm.endswith("-sess") | ||
|
|
||
| def h(data: str) -> str: | ||
| return hashlib.new(hash_name, data.encode()).hexdigest() | ||
|
|
||
| ha1 = h(f"{username}:{realm}:{password}") | ||
| if is_sess: | ||
| ha1 = h(f"{ha1}:{nonce}:{cnonce}") | ||
| ha2 = h(f"{method}:{uri}") | ||
| if qop in (DigestQoP.AUTH, DigestQoP.AUTH_INT): | ||
| return hashlib.md5( # noqa: S324 | ||
| f"{ha1}:{nonce}:{nc}:{cnonce}:{qop}:{ha2}".encode() | ||
| ).hexdigest() | ||
| return hashlib.md5(f"{ha1}:{nonce}:{ha2}".encode()).hexdigest() # noqa: S324 | ||
| return h(f"{ha1}:{nonce}:{nc}:{cnonce}:{qop}:{ha2}") | ||
| return h(f"{ha1}:{nonce}:{ha2}") |
| ) | ||
| assert r_md5 != r_sha256 | ||
| assert r_sha256 != r_sha512 | ||
|
|
|
@copilot apply changes based on the comments in this thread |
Co-authored-by: codingjoe <[email protected]>
Done in 1ffeab3. Both review comments are addressed:
Warning Firewall rules blocked me from connecting to one or more addresses (expand for details)I tried to connect to the following addresses, but was blocked by firewall rules:
If you need me to access, download, or install something from one of these locations, you can either:
|
MD5 is cryptographically broken and its use in SIP Digest Auth is deprecated by RFC 8760, which mandates SHA-256 and SHA-512-256 support.
Changes
voip/types.py: NewDigestAlgorithmenum covering all RFC 8760 values:MD5,MD5-sess,SHA-256,SHA-256-sess,SHA-512-256,SHA-512-256-sessvoip/sip/protocol.py:digest_response()now accepts analgorithmparameter (default:SHA-256) and dispatches viahashlib.new(). RaisesValueErrorfor unrecognised algorithms.response_received()readsalgorithmfrom the server'sWWW-Authenticate/Proxy-Authenticatechallenge and echoes it in theAuthorizationheader — server-advertised algorithm is honoured end-to-end.tests/sip/test_protocol.py: Updated existingalgorithm="MD5"assertion toalgorithm="SHA-256"; addedTestDigestResponsewith coverage for correctness, backward-compat MD5, SHA-512-256, algorithm distinctness, and server algorithm negotiation.MD5 remains supported for servers that explicitly request it.
💬 Send tasks to Copilot coding agent from Slack and Teams to turn conversations into code. Copilot posts an update in your thread when it's finished.