Summary
After a CTAP2 authenticatorReset (command 0x07), every subsequent command on the same applet-selected session is rejected with SW 0x6D00 (SW_INS_NOT_SUPPORTED), until the applet is SELECTed again. Over PC/SC this surfaces in python-fido2 2.1.1 as CtapError 0x7F (OTHER).
Root cause
authenticatorReset() calls transientStorage.fullyReset(), which zeroes the entire transient temp-bytes array, including the BOOL_IDX_APPLET_SELECTED bit. Nothing restores that bit afterwards, so the guard at the top of process():
if (!transientStorage.isAppletSelected()) {
ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
}
rejects every following command.
This is a regression from the applet-selected-bit change (42d16ba "Add an applet selected bit to the boolean omnibus" / a656edc "Allow for safe extended-length SELECT ..."), added for extended-length SELECT support on Android. fullyReset() predates the bit and clears it. A CTAP2 reset does not deselect the applet, so the bit should stay set.
Reproduction
Against the bundled Python suite (jCardSim, RAW/PCSC), the failures are exactly the classes that issue a CTAP2 reset and then keep using the card:
test_ctap_resets (reset in the test body)
test_auth_config (reset in setUp)
test_ctap_pins (reset in setUp)
Before the fix: 248 tests, 217 pass, 7 fail, 22 error, 4 skip. Classes that instead use softResetCard() (which re-SELECTs) are unaffected and pass.
Minimal raw capture: reset -> SW=9000, then the next authenticatorGetInfo -> SW=6D00, rlen=0.
Fix
Restore the applet-selected bit right after fullyReset() in authenticatorReset:
transientStorage.fullyReset();
transientStorage.setAppletSelected();
With this one line, the full suite passes: 248 tests, 244 pass, 4 skip, 0 fail/0 error.
Filed with help from Claude Code.
Summary
After a CTAP2
authenticatorReset(command0x07), every subsequent command on the same applet-selected session is rejected with SW0x6D00(SW_INS_NOT_SUPPORTED), until the applet isSELECTed again. Over PC/SC this surfaces in python-fido2 2.1.1 asCtapError 0x7F (OTHER).Root cause
authenticatorReset()callstransientStorage.fullyReset(), which zeroes the entire transient temp-bytes array, including theBOOL_IDX_APPLET_SELECTEDbit. Nothing restores that bit afterwards, so the guard at the top ofprocess():rejects every following command.
This is a regression from the applet-selected-bit change (
42d16ba"Add an applet selected bit to the boolean omnibus" /a656edc"Allow for safe extended-length SELECT ..."), added for extended-lengthSELECTsupport on Android.fullyReset()predates the bit and clears it. A CTAP2 reset does not deselect the applet, so the bit should stay set.Reproduction
Against the bundled Python suite (jCardSim, RAW/PCSC), the failures are exactly the classes that issue a CTAP2 reset and then keep using the card:
test_ctap_resets(reset in the test body)test_auth_config(reset insetUp)test_ctap_pins(reset insetUp)Before the fix: 248 tests, 217 pass, 7 fail, 22 error, 4 skip. Classes that instead use
softResetCard()(which re-SELECTs) are unaffected and pass.Minimal raw capture:
reset->SW=9000, then the nextauthenticatorGetInfo->SW=6D00,rlen=0.Fix
Restore the applet-selected bit right after
fullyReset()inauthenticatorReset:With this one line, the full suite passes: 248 tests, 244 pass, 4 skip, 0 fail/0 error.
Filed with help from Claude Code.