This repository contains the source code for the Java Applet used in our FIDO2.1. security keys with PIN complexity enforcement (PIN+) and the 300 passkey storage capacity. The goal is to provide a transparent view of our implementation and to contribute to the broader community of security key developers and researchers.
The applet uses a structured approach to manage passkey storage, designed for efficiency given JavaCard's limitations. Specifically, the PIN+ 300 passkey storage is implemented as follows:
- The
shortdata type on JavaCard allows for a maximum of 32,767 bytes. - Each passkey requires approximately 400 bytes of storage.
- To accommodate 300 passkeys, they are divided into 6 classes, with each class containing 50 passkeys.
The storage for each class is allocated as:
- 1st 50 passkeys:
new[20000] - 2nd 50 passkeys:
new[20000] - 3rd 50 passkeys:
new[20000] - 4th 50 passkeys:
new[20000] - 5th 50 passkeys:
new[20000] - 6th 50 passkeys:
new[20000]
Each class's allocation (20000 bytes) fits within the short limit, allowing for efficient storage management and retrieval.
The CustomFunction.java file implements the PIN complexity rules for the FIDO2 security keys. Here’s an explanation of the main methods used:
-
IsCorrectDelta: This method checks if the differences between consecutive characters in the PIN are not identical. This helps prevent simple patterns like "1234" or "abcd".
public static boolean IsCorrectDelta(byte[] ps1Input, short s2Offset, short s2Len) { final byte s1Delta = (byte)(ps1Input[s2Offset] - ps1Input[s2Offset + 1]); for (short i = s2Offset + 1; i < s2Offset + s2Len - 1; i++) { if ((byte)(ps1Input[i] - ps1Input[i + 1]) != s1Delta) { return true; } } return false; }
-
IsNonMirrored: This function checks that the PIN is not a mirror of itself, i.e., it prevents palindromic PINs like "1221" or "abccba".
public static boolean IsNonMirrored(byte[] ps1Input, short s2Offset, short s2Len) { short s2Head = s2Offset; short s2Tail = (short)(s2Offset + s2Len - 1); while (s2Tail > s2Head) { if (ps1Input[s2Head] != ps1Input[s2Tail]) { return true; } s2Head++; s2Tail--; } return false; }
-
IsNonContinuousChar: This method ensures that there are no sequences of the same character repeated more than a specified number of times. For example, it prevents "aaaa" or "1111".
public static boolean IsNonContinuousChar(byte[] ps1Input, short s2Offset, short s2Len, short s2MaxTimes) { byte s1CurChar = ps1Input[s2Offset]; short s2CurTimes = 1; for (short i = s2Offset + 1; i < s2Offset + s2Len; i++) { if (ps1Input[i] == s1CurChar) { s2CurTimes++; if (s2CurTimes > s2MaxTimes) { return false; } } else { s1CurChar = ps1Input[i]; s2CurTimes = 1; } } return true; }
-
IsCorrectFormat: This function checks that the PIN contains a mix of character types, such as uppercase letters, lowercase letters, digits, and other characters, and verifies the number of distinct character types used.
public static boolean IsCorrectFormat(byte[] ps1Input, short s2Offset, short s2Len, byte s1Type, short s2TypeNum) { byte s1CurType = 0x00; short s2CurTypeNum = 0; for (short i = s2Offset; i < s2Offset + s2Len; i++) { byte s1TempType = GetFormatType(ps1Input[i]); if (s1TempType == INVALID_CHAR) { return false; } if ((s1Type & s1TempType) == 0) { return false; } if ((s1CurType & s1TempType) == 0) { s1CurType |= s1TempType; s2CurTypeNum++; } } return s2CurTypeNum >= s2TypeNum; }
-
GetFormatType: This helper method determines the type of a character (uppercase letter, lowercase letter, digit, or other).
public static byte GetFormatType(byte s1Char) { if (s1Char >= 0x41 && s1Char <= 0x5A) { return UPPER_LETTER; } else if (s1Char >= 0x61 && s1Char <= 0x7A) { return LOWER_LETTER; } else if (s1Char >= 0x30 && s1Char <= 0x39) { return DIGIT_CHAR; } else if (s1Char >= 0x20 && s1Char <= 0x7E) { return OTHER_CHAR; } else { return INVALID_CHAR; } }
This tool emulates the complexity check implemented on our PIN+ firmware.
As of the latest update (release 3.1 and newer) , the firmware now supports Unicode characters in PINs. This enhancement allows users to set PIN codes that include:
- Non-ASCII letters (e.g., accented characters, Cyrillic, CJK)
- Emoji or symbols
- Any valid UTF-8 encoded character
This change improves flexibility and supports international users while preserving PIN format validation and complexity checks. PIN complexity is still enforced.
The relevant PIN validation logic in CustomFunction.java has been updated accordingly ( any non-ASCII characters are treated as their own class ).
Note: PINs are now interpreted as UTF-8 encoded byte arrays internally.
While the provided code can be compiled and installed on standard JavaCard-compliant smart cards, specific hardware chips might require tweaks to ensure proper functionality. The behavior of the applet can vary depending on the JavaCard platform, card firmware, and chip characteristics. This code was implemented and tested on THD 89 and J3H145 (NXP JCOP3) chips only.
Some of our FIDO2 security keys support both NFC and USB interfaces. The USB bridging is handled by a third-party solution, and the manufacturer has not released the source code. However, Linux users can use the Linux Virtual USB-HID FIDO2 device project to access USB functionality: FIDO2 HID Bridge .
Please note that our team is currently unable to offer support for installation or troubleshooting due to capacity constraints. We encourage the community to experiment with the code and share any findings or improvements, but we cannot provide direct assistance with:
- Debugging installation issues.
- Modifying the code for specific hardware compatibility.
- General troubleshooting.
We appreciate your understanding.
This project is licensed under the MIT License - see the LICENSE file for details.
Portions of this code are used from the FIDO2Applet project. We extend our thanks to the authors and contributors of this project.
The code is provided "as-is" and without warranty of any kind. Users are responsible for ensuring the security and suitability of the code for their specific use case.
The actual code running on our devices may include future enhancements, such as additional applets like OpenPGP and increased storage capabilities. The code provided in this repository specifically pertains to the FIDO2 applet of our PIN+ series. Future developments and updates may not be reflected here. If we decide to open-source other applets, they may be hosted in separate repositories.
For inquiries related to this project, please reach out via the contact form: contact us.
Thank you for your interest in our project!
