From 8b58c4cdcae295977306d895c7d5afd7c5628a22 Mon Sep 17 00:00:00 2001 From: Robert Quattlebaum Date: Tue, 17 Mar 2020 14:00:41 -0700 Subject: [PATCH] Work-around for Native iOS Support iOS/Safari now offers native support for FIDO2 authenticators. However, while all existing U2F authenticators are also FIDO2 CTAP1 authenticators, this applet doesn't seem to be compatible with this new iOS feature. This commit remedies this by adding a lightweight work-around that coerces recent versions of iOS to use CTAP1 to both register and authenticate with this applet via NFC. The work-around is to implement support for the FIDO2 `NFCCTAP_MSG` command and have it always return the error `CTAP1_ERR_INVALID_COMMAND`. This is apparently enough to coerce iOS to use CTAP1 with the authenticator instead of CTAP2. This work-around should remain viable since the FIDO2 CTAP spec states that the platform should attempt to use CTAP1 when the authenticator returns a command error or improperly formated CBOR in response to the get info command. Apparently iOS isn't considering APDU errors to be "command errors", but CTAP errors apparently are interpreted as such. --- src/main/java/com/ledger/u2f/U2FApplet.java | 77 +++++++++++++-------- 1 file changed, 48 insertions(+), 29 deletions(-) diff --git a/src/main/java/com/ledger/u2f/U2FApplet.java b/src/main/java/com/ledger/u2f/U2FApplet.java index 4e50410..7de82cb 100644 --- a/src/main/java/com/ledger/u2f/U2FApplet.java +++ b/src/main/java/com/ledger/u2f/U2FApplet.java @@ -56,6 +56,7 @@ public class U2FApplet extends Applet implements ExtendedLength { private static final byte FIDO_INS_SIGN = (byte)0x02; private static final byte FIDO_INS_VERSION = (byte)0x03; private static final byte ISO_INS_GET_DATA = (byte)0xC0; + private static final byte FIDO2_INS_NFCCTAP_MSG = (byte)0x10; private static final byte PROPRIETARY_CLA = (byte)0xF0; private static final byte FIDO_ADM_SET_ATTESTATION_CERT = (byte)0x01; @@ -102,6 +103,8 @@ public class U2FApplet extends Applet implements ExtendedLength { private static final byte INSTALL_FLAG_DISABLE_USER_PRESENCE = (byte)0x01; + private static final byte CTAP1_ERR_INVALID_COMMAND = (byte)0x01; + // Parameters // 1 byte : flags // 2 bytes big endian short : length of attestation certificate @@ -387,42 +390,58 @@ public void process(APDU apdu) throws ISOException { } return; } - if (buffer[ISO7816.OFFSET_CLA] == PROPRIETARY_CLA) { - if (attestationCertificateSet) { - ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED); - } - switch(buffer[ISO7816.OFFSET_INS]) { - case FIDO_ADM_SET_ATTESTATION_CERT: + + if (!attestationCertificateSet) { + if ((buffer[ISO7816.OFFSET_CLA] & (byte)0x80) == (byte)0x80 + && buffer[ISO7816.OFFSET_INS] == FIDO_ADM_SET_ATTESTATION_CERT + ) { handleSetAttestationCert(apdu); - break; - default: + } else { ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED); } + return; } - else if (buffer[ISO7816.OFFSET_CLA] == FIDO_CLA) { - if (!attestationCertificateSet) { - ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED); - } - switch(buffer[ISO7816.OFFSET_INS]) { - case FIDO_INS_ENROLL: - handleEnroll(apdu); - break; - case FIDO_INS_SIGN: - handleSign(apdu); - break; - case FIDO_INS_VERSION: - handleVersion(apdu); - break; - case ISO_INS_GET_DATA: - handleGetData(apdu); - break; - default: + + if ((buffer[ISO7816.OFFSET_CLA] & (byte)0x80) == (byte)0x80) { + if (buffer[ISO7816.OFFSET_INS] == FIDO2_INS_NFCCTAP_MSG) { + handleCtap2(apdu); + } else { ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED); } + } else { + switch (buffer[ISO7816.OFFSET_INS]) { + case FIDO_INS_ENROLL: + handleEnroll(apdu); + break; + case FIDO_INS_SIGN: + handleSign(apdu); + break; + case FIDO_INS_VERSION: + handleVersion(apdu); + break; + case ISO_INS_GET_DATA: + handleGetData(apdu); + break; + default: + ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED); + } } - else { - ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED); - } + } + + private void + handleCtap2(APDU apdu) + { + // We don't actually support CTAP2, so we always + // return CTAP1_ERR_INVALID_COMMAND. + final byte[] buffer = apdu.getBuffer(); + apdu.setIncomingAndReceive(); + short dataOffset = apdu.getOffsetCdata(); + + short len = 0; + + buffer[len++] = CTAP1_ERR_INVALID_COMMAND; + + apdu.setOutgoingAndSend((short)0, len); } public static void install (byte bArray[], short bOffset, byte bLength) throws ISOException {