Menu ▾ ▴

#90 PassportService.doAA returns an AAResult instead of throwing when the chip rejects INTERNAL AUTHENTICATE

v1.0_(example)
open
nobody
None
1
2026-10-12
2026-10-12
Kazuru
No

This is an API-contract defect: doAA is documented to throw CardServiceException on error, but returns normally on a rejected INTERNAL AUTHENTICATE. A caller that verifies the AAResult signature still rejects the chip; a caller that relies on the documented exception does not.

Version: 0.8.9 (same code in 0.8.6 and current trunk).

When the chip answers INTERNAL AUTHENTICATE with an error status word, doAA returns normally with an empty (or non-signature) response, although its Javadoc says it throws CardServiceException on error.

Cause, in AAAPDUSender.sendInternalAuthenticate: for a status word other than 9000 (and not 61xx), the branch "If we got some data, return it, independent of what the status is" checks responseAPDU.getData() != null. For an error response that carries no data, getData() is an empty array, not null, so the empty data is returned.

Two ways to reach it:

  1. Errors returned under secure messaging (DO99 + MAC). SecureMessagingAPDUSender.transmit unwraps them to a plain status word with empty data. Every one of the thirteen ISO 7816-4 error status words I tried behaves this way, with doAA returning an empty response for each: 6300, 6700, 6800, 6900, 6982, 6A00, 6A80, 6A88, 6B00, 6C00, 6E00, 6F00, 6FFF.
  2. Errors returned in plaintext whose status word matches (sw & ISO7816.SW_WRONG_LENGTH) == ISO7816.SW_WRONG_LENGTH, for example 6F00 and 6FFF. That test is meant to recognise 67xx (wrong length), but as a bitmask it also matches any status word with those bits set, so transmit returns the raw response without SM processing. Other plaintext error status words throw, as expected.

Reproduction: after BAC, a chip answers INTERNAL AUTHENTICATE with an error status word returned under secure messaging (DO99 + MAC), then service.doAA(publicKey, "SHA-256", "SHA256withRSA", challenge). Expected: CardServiceException. Actual: doAA returns an AAResult with an empty response. I reproduced it against a scripted chip that returns the status word in a DO99+MAC response; for 6982 the protected INTERNAL AUTHENTICATE command (0C88...8E08...) is answered 9902 6982 8E08 ... 6982 and doAA returns success with no exception. All thirteen status words above behave the same.

Proposed patch (attached, 2 lines):

  • AAAPDUSender: only return the response data for a non-9000 status word when there is data (getData().length > 0), keeping the existing tolerance for chips that return a signature with a warning status.
  • SecureMessagingAPDUSender.transmit: test wrong length as (sw & 0xFF00) == ISO7816.SW_WRONG_LENGTH.

With the patch, doAA throws for twelve of the thirteen status words. 6700 is the exception: its status word matches the wrong-length shortcut, so transmit returns the response unparsed and doAA still returns it. PACE and BAC behaviour is unchanged in my tests (the PACE fault profiles — 32 status words in each of the two integration patterns — and the successful-read controls all unchanged).

Related effect on Chip Authentication (same transmit branch): when the chip rejects MSE:Set AT during doEACCA with 6700, 6F00 or 6FFF under secure messaging, transmit returns the response without unwrapping it, so the send-sequence counter is not advanced and the next protected command is rejected with 6988 — a caller that catches the CA exception and continues sees the failure only as a later SM error on an unrelated read. The attached mask change removes this for 6F00/6FFF but not 6700 itself; a check that the response carries SM data objects before taking the shortcut would cover all three.

Discussion

Anonymous
Anonymous

Add attachments
Cancel