fix(dmrtd): reject an unprotected success on a secure-messaging session - #23
Conversation
Closes #22. A protected exchange is answered with DO'99' and DO'8E' even when the command returns no data, so a response carrying a bare status word has been authenticated by nobody. The guard treated an empty body as a reason to skip verification, which made an unprotected 9000 indistinguishable from a verified one: anyone able to modify the channel could strip the secure-messaging wrapper from a command whose response is empty — SELECT, MSE:Set AT — and have the reader record it as having succeeded. "Nothing to check" is not "checked". Unprotected errors still pass through. That is how the chip reports that secure messaging failed (6987/6988) or that it rejected the command before applying SM at all, and it cannot be authenticated either — but it is the only diagnosis the caller gets, and MrtdApiError::ChipStatus carries the status word up to PassportError::code, where wrong-BAC-key detection depends on it. Turning those into an opaque SM failure would trade a real bug for a worse one. A status-bearing response that does carry a body is now verified like any other, including when the status claims SM already failed. A status word outside the MAC is not evidence about the bytes inside it. The SSC is left alone on both paths, so a session that somehow continued cannot do so against a counter the chip does not share. The test asserting the old passthrough is replaced by one asserting the rejection; the SSC and body-bearing-error cases get their own.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
📝 WalkthroughSummary by CodeRabbit
Walkthrough
ChangesSecure messaging validation
Estimated code review effort: 2 (Simple) | ~10 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Closes #22. Found by cubic on #21; that PR only reformatted the adjacent line, so this is the actual fix rather than a lint change.
The hole
MrtdSM::unprotectskipped verification whenever the response body was empty:A protected exchange is answered with
DO'99'andDO'8E'even when the command returns no data. So a response carrying a bare status word has been authenticated by nobody — andMrtdApi::transceive_cmdseesSUCCESSand returns it as a legitimate result.Concretely: for any protected command whose response is empty — SELECT, MSE:Set AT — anyone able to modify the channel can strip the secure-messaging wrapper, return a bare
9000, and have the reader record the command as having succeeded with no MAC ever checked. "Nothing to check" is not "checked and fine", and the guard could not tell the two apart.What changed
An unprotected success is now an error. That is the shape with no legitimate meaning on an SM session.
Unprotected errors still pass through, deliberately. That is how the chip reports that secure messaging itself failed (
6987/6988) or that it rejected the command before applying SM at all. It cannot be authenticated either — but it is the only diagnosis the caller gets, andMrtdApiError::ChipStatuscarries the status word up toPassportError::code, where the wrong-BAC-key mapping (63CF→ security status not satisfied) depends on it. Turning those into an opaque SM failure would trade this bug for a worse one: every wrong-key read would stop saying why.A status-bearing response that carries a body is now verified like any other, including when the status claims SM already failed. A status word sitting outside the MAC is not evidence about the bytes inside it.
The SSC is left alone on both paths, so a session that somehow continued cannot do so against a counter the chip does not share.
Tests
unprotect_passthrough_on_empty_bodyasserted the old behaviour, so it is replaced byunprotect_rejects_an_unprotected_success. Two more cover what the fix depends on: that neither path advances the SSC, and that an SM-error status carrying a body is still MAC-checked.681
dmrtdtests pass, including the protect/unprotect loopback that exercises the normal path end to end.Risk
This is a behavioural change against real hardware, and worth a device test before release rather than after. A compliant chip always wraps its responses, so a compliant chip is unaffected — but if any passport in the field answers a protected exchange with a bare
9000, reads that currently succeed will now fail closed. That is the correct direction for a security fix, and it is still a change in what works.Note for merge order: #21 touches the same line and will conflict with whichever of the two lands second. Trivial to rebase either way.
Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.Summary by cubic
Fixes a secure-messaging bypass in
dmrtd: a bare success (9000with no data) on a protected session is now rejected. Unprotected errors still bubble up, any response with a body is MAC-checked, and the SSC is not advanced in either case.Bug Fixes
MrtdSM::unprotectnow returns an error for unprotectedSUCCESSon SM sessions.6987,6988) pass through for diagnosis without advancing the SSC.Migration
9000will now fail; run a device test before release.Written for commit 32ace76. Summary will update on new commits.