Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- --- /home/alice/git/FIDO2Applet/src/main/java/us/q3q/fido2/FIDO2Applet.java 2026-10-26 06:02:29.212198643 +0000
- +++ src/FIDO2Applet.java 2026-10-26 07:29:02.124197844 +0000
- @@ -2,14 +2,14 @@
- import javacard.framework.*;
- import javacard.security.*;
- -import javacardx.apdu.ExtendedLength;
- +
- import javacardx.crypto.Cipher;
- /**
- * Core applet class implementing FIDO2 specifications on Javacard
- */
- -public final class FIDO2Applet extends Applet implements ExtendedLength {
- +public final class FIDO2Applet extends Applet {
- /**
- * The version of this applet in use
- @@ -209,24 +209,24 @@
- /**
- * Random keys for deriving keys for the hmac-secret extension from regular credential private keys
- */
- - private final byte[] hmacWrapperBytesUV;
- - private final byte[] hmacWrapperBytesNoUV;
- + private byte[] hmacWrapperBytesUV;
- + private byte[] hmacWrapperBytesNoUV;
- /**
- * Random key for verifying (HMAC-ing) created credentials
- */
- - private final byte[] credentialVerificationKey;
- + private byte[] credentialVerificationKey;
- /**
- * DH for platform <-> authenticator secure channel
- */
- - private final KeyAgreement keyAgreement;
- + private KeyAgreement keyAgreement;
- /**
- * encipher things destined for the platform
- */
- - private final Cipher sharedSecretWrapper;
- + private Cipher sharedSecretWrapper;
- /**
- * decipher things sent by the platform
- */
- - private final Cipher sharedSecretUnwrapper;
- + private Cipher sharedSecretUnwrapper;
- /**
- * must be zeroes. Is used for encryption and decryption of messages between authenticator and platform
- */
- @@ -242,16 +242,16 @@
- /**
- * Decrypter used to unwrap data by using a key derived from the user's PIN
- */
- - private final Cipher pinUnwrapper;
- + private Cipher pinUnwrapper;
- /**
- * Encrypter used to wrap data by using a key derived from the user's PIN
- */
- - private final Cipher pinWrapper;
- + private Cipher pinWrapper;
- /**
- * per-device salt for deriving keys from PINs
- */
- - private final byte[] pinKDFSalt;
- + private byte[] pinKDFSalt;
- /**
- * key to be set from KDF derivation of PIN
- */
- @@ -263,7 +263,7 @@
- /**
- * used for signing the authData we send to the platform, to prove it came from us
- */
- - private final Signature attester;
- + private Signature attester;
- // Fields for wrapping and unwrapping platform-held blobs
- /**
- @@ -272,48 +272,48 @@
- *
- * If a PIN is set, these bytes are stored encrypted using a key derived from the PIN.
- */
- - private final byte[] wrappingKeySpace;
- + private byte[] wrappingKeySpace;
- /**
- * Authenticator-specific master key, decrypted (if necessary) and loaded into transient storage for use
- */
- - private final AESKey highSecurityWrappingKey;
- + private AESKey highSecurityWrappingKey;
- /**
- * Authenticator-specific master key used for situations where a PIN cannot be presented.
- * This key is never used for credProtect level 3 keys unless LOW_SECURITY_MAXIMUM_COMPLIANCE is set.
- * If FORCE_ALWAYS_UV is set, it's used for all keys.
- */
- - private final AESKey lowSecurityWrappingKey;
- + private AESKey lowSecurityWrappingKey;
- /**
- * random IV for authenticator private wrapping with highSecurityWrappingKey
- */
- - private final byte[] highSecurityWrappingIV;
- + private byte[] highSecurityWrappingIV;
- /**
- * first half random data, second half the HMAC of that using the wrapping key:
- * used for checking if a potential wrapping key is the correct one
- */
- - private final byte[] wrappingKeyValidation;
- + private byte[] wrappingKeyValidation;
- /**
- * encrypt data using authenticator master key
- */
- - private final Cipher symmetricWrapper;
- + private Cipher symmetricWrapper;
- /**
- * decrypt data using authenticator master key
- */
- - private final Cipher symmetricUnwrapper;
- + private Cipher symmetricUnwrapper;
- /**
- * Randomness.
- */
- - private final RandomData random;
- + private RandomData random;
- // Fields for maintaining auth state
- /**
- * Ever-increasing number representing how many sig operations we've made
- */
- - private final SigOpCounter counter;
- + private SigOpCounter counter;
- /**
- * Value that decreases with each failed PIN guess
- */
- - private final PinRetryCounter pinRetryCounter;
- + private PinRetryCounter pinRetryCounter;
- // Parameters for the real elliptic-curve keys :-)
- /**
- @@ -342,7 +342,7 @@
- /**
- * General hashing of stuff
- */
- - private final MessageDigest sha256;
- + private MessageDigest sha256;
- /**
- * Used to make sure the user REALLY wants to reset their authenticator
- */
- @@ -358,7 +358,7 @@
- /**
- * Everything that needs to be hot in RAM instead of stored to the flash. All goes away on deselect or reset!
- */
- - private final TransientStorage transientStorage;
- + private TransientStorage transientStorage;
- /**
- * Same, but for managed buffers
- */
- @@ -373,7 +373,7 @@
- /**
- * Encrypted RPID hashes used for the minPinLength extension
- */
- - private final byte[] minPinRPIDs;
- + private byte[] minPinRPIDs;
- /**
- * How many resident key slots are filled
- */
- @@ -385,8 +385,8 @@
- /**
- * Storage for the largeBlobs extension
- */
- - private final byte[] largeBlobStoreA;
- - private final byte[] largeBlobStoreB;
- + private byte[] largeBlobStoreA;
- + private byte[] largeBlobStoreB;
- /**
- * Which large blob store is in use
- */
- @@ -3459,6 +3459,10 @@
- */
- @Override
- public void process(APDU apdu) throws ISOException {
- + if (initError != null) {
- + sendByteArray(apdu, initError, (short) initError.length);
- + return;
- + }
- if (selectingApplet()) {
- handleAppletSelect(apdu);
- @@ -6832,15 +6836,46 @@
- @SuppressWarnings("unused")
- public static void install(byte[] array, short offset, byte length)
- throws ISOException {
- - if (length > 0) {
- - short aidLen = ub(array[offset]);
- - short infoLen = ub(array[(short)(offset + aidLen + 1)]);
- - length = array[(short)(offset + aidLen + infoLen + 2)];
- - offset = (short)(offset + aidLen + infoLen + 3);
- + FIDO2Applet applet = new FIDO2Applet();
- + short rawLen = (short) (length & 0xFF);
- + if (rawLen > 64) rawLen = 64;
- + applet.rawInstall = new byte[rawLen];
- + Util.arrayCopyNonAtomic(array, offset, applet.rawInstall, (short) 0, rawLen);
- + applet.rawInstallLen = length;
- + try {
- + applet.initStage = 17; // parsing GP install data
- + if (length > 0) {
- + short aidLen = ub(array[offset]);
- + short infoLen = ub(array[(short)(offset + aidLen + 1)]);
- + length = array[(short)(offset + aidLen + infoLen + 2)];
- + offset = (short)(offset + aidLen + infoLen + 3);
- + }
- + applet.init(array, offset, length);
- + } catch (CryptoException e) {
- + applet.recordInitError((byte) 1, e.getReason());
- + } catch (SystemException e) {
- + applet.recordInitError((byte) 2, e.getReason());
- + } catch (ISOException e) {
- + applet.recordInitError((byte) 3, e.getReason());
- + } catch (TransactionException e) {
- + applet.recordInitError((byte) 4, e.getReason());
- + } catch (CardRuntimeException e) {
- + applet.recordInitError((byte) 5, e.getReason());
- + } catch (NullPointerException e) {
- + applet.recordInitError((byte) 6, (short) 0);
- + } catch (ArrayIndexOutOfBoundsException e) {
- + applet.recordInitError((byte) 7, (short) 0);
- + } catch (NegativeArraySizeException e) {
- + applet.recordInitError((byte) 8, (short) 0);
- + } catch (ClassCastException e) {
- + applet.recordInitError((byte) 9, (short) 0);
- + } catch (SecurityException e) {
- + applet.recordInitError((byte) 10, (short) 0);
- + } catch (Exception e) {
- + applet.recordInitError((byte) 15, (short) 0);
- }
- - FIDO2Applet applet = new FIDO2Applet(array, offset, length);
- - // Javacard API requires this call to know we succeeded and set the app up with the platform
- + // Always register, so a failed init is reported at SELECT instead of killing the profile install
- applet.register();
- }
- @@ -6852,10 +6887,33 @@
- * @param length install parameters length
- */
- @SuppressWarnings("unused")
- - private FIDO2Applet(byte[] array, short offset, byte length) {
- + private byte initStage = 0;
- + private byte[] initError = null;
- +
- + private FIDO2Applet() {
- + }
- +
- + private byte[] rawInstall;
- + private byte rawInstallLen;
- +
- + private void recordInitError(byte type, short reason) {
- + // INITERR v3 | type | reason(2) | stage | rawLen | raw install bytes (max 64)
- + initError = new byte[(short) (13 + rawInstall.length)];
- + initError[0] = 'I'; initError[1] = 'N'; initError[2] = 'I'; initError[3] = 'T';
- + initError[4] = 'E'; initError[5] = 'R'; initError[6] = 'R';
- + initError[7] = type;
- + initError[8] = (byte) (reason >> 8);
- + initError[9] = (byte) reason;
- + initError[10] = initStage;
- + initError[11] = (byte) 3; // build marker
- + initError[12] = rawInstallLen;
- + Util.arrayCopyNonAtomic(rawInstall, (short) 0, initError, (short) 13, (short) rawInstall.length);
- + }
- +
- + private void init(byte[] array, short offset, byte length) {
- // set up parameters
- // first, defaults
- - attestationSwitchingEnabled = false;
- + attestationSwitchingEnabled = true; // allow one-time attestation cert install (vendor cmd 0x46)
- LOW_SECURITY_MAXIMUM_COMPLIANCE = true;
- FORCE_ALWAYS_UV = false;
- USE_LOW_SECURITY_FOR_SOME_RKS = true;
- @@ -6872,8 +6930,18 @@
- STORE_PIN_LENGTH = true;
- CERTIFICATION_LEVEL = 0;
- + initStage = 16;
- // Next, read any overrides
- final short initOffset = offset;
- + // Some platforms (e.g. eUICC profile installs) pass the whole C9 TLV instead of just its value
- + if (length >= 2 && array[offset] == (byte) 0xC9) {
- + length = array[(short)(offset + 1)];
- + offset += 2;
- + }
- + // Only treat the parameters as options if they actually start with a CBOR map (A1..AF)
- + if (length > 0 && (ub(array[offset]) & 0xF0) != 0xA0) {
- + length = 0;
- + }
- if (length > 0) {
- short sb = ub(array[offset++]);
- short numOptions = (short)(sb - 0xA0);
- @@ -6987,9 +7055,11 @@
- }
- }
- + initStage = 1;
- alwaysUv = FORCE_ALWAYS_UV;
- // Flash usage
- + initStage = 2;
- pinKDFSalt = new byte[28];
- wrappingKeySpace = new byte[32];
- wrappingKeyValidation = new byte[64];
- @@ -7004,29 +7074,39 @@
- Util.arrayCopyNonAtomic(CannedCBOR.INITIAL_LARGE_BLOB_ARRAY, (short) 0,
- largeBlobStoreB, (short) 0, (short) CannedCBOR.INITIAL_LARGE_BLOB_ARRAY.length);
- largeBlobStoreFill = (short) CannedCBOR.INITIAL_LARGE_BLOB_ARRAY.length;
- + initStage = 3;
- highSecurityWrappingKey = getTransientAESKey(); // Our most important treasure, from which all other crypto is born...
- + initStage = 4;
- lowSecurityWrappingKey = getPersistentAESKey(); // Not really a treasure
- + initStage = 5;
- residentKeys = new ResidentKeyData[NUM_RESIDENT_KEY_SLOTS_PER_BATCH];
- minPinRPIDs = new byte[MAX_RP_IDS_MIN_PIN_LENGTH * RP_HASH_LEN];
- numResidentCredentials = 0;
- numResidentRPs = 0;
- resetRequested = false;
- + initStage = 6;
- counter = new SigOpCounter();
- pinRetryCounter = new PinRetryCounter(MAX_PIN_RETRIES);
- pinCodePointLength = 0;
- // Trivial amounts of flash, object allocations without buffers
- + initStage = 7;
- random = RandomData.getInstance(RandomData.ALG_SECURE_RANDOM);
- + initStage = 8;
- keyAgreement = KeyAgreement.getInstance(KeyAgreement.ALG_EC_SVDP_DH_PLAIN, false);
- + initStage = 9;
- pinUnwrapper = getAES();
- pinWrapper = getAES();
- symmetricWrapper = getAES();
- symmetricUnwrapper = getAES();
- sharedSecretWrapper = getAES();
- sharedSecretUnwrapper = getAES();
- + initStage = 10;
- attester = getECSig();
- + initStage = 11;
- sha256 = MessageDigest.getInstance(MessageDigest.ALG_SHA_256, false);
- + initStage = 12;
- transientStorage = new TransientStorage();
- final short availableMem = JCSystem.getAvailableMemory(JCSystem.MEMORY_TYPE_TRANSIENT_DESELECT);
- @@ -7034,8 +7114,11 @@
- boolean authenticatorKeyInRam = availableMem >= 148; // 96 (desired scratch)+6 (overhead)+32 (key)+16 (params)
- boolean ecPairInRam = availableMem >= 180; // 148 + 32 (key)
- + initStage = 13;
- initAuthenticatorKey(authenticatorKeyInRam);
- + initStage = 14;
- initCredKey(ecPairInRam);
- + initStage = 15;
- }
- /**
- --- /home/alice/git/FIDO2Applet/src/main/java/us/q3q/fido2/CannedCBOR.java 2026-10-26 06:02:29.212198643 +0000
- +++ src/CannedCBOR.java 2026-10-26 07:06:18.017099645 +0000
- @@ -85,7 +85,7 @@
- (byte) 0xF5, // true
- 0x62, // string: two bytes long
- 0x75, 0x70, // up
- - (byte) 0xF4, // false
- + (byte) 0xF5, // true (card insertion/tap counts as presence)
- 0x66, // string: six bytes long
- 0x75, 0x76, 0x41, 0x63, 0x66, 0x67, // uvAcfg
- (byte) 0xF5, // true
Add Comment
Please, Sign In to add comment