diff --git a/build/build.xml b/build/build.xml
index 0e53d52..f5a0ada 100644
--- a/build/build.xml
+++ b/build/build.xml
@@ -3,7 +3,7 @@
-
+
diff --git a/doc/asn1/OpenFIPS201-PUT-DATA.asn b/doc/asn1/OpenFIPS201-PUT-DATA.asn
index b0d6d39..5671a9b 100644
--- a/doc/asn1/OpenFIPS201-PUT-DATA.asn
+++ b/doc/asn1/OpenFIPS201-PUT-DATA.asn
@@ -199,8 +199,8 @@ PinPolicyParameter ::= SEQUENCE {
permitContactless [3] BOOLEAN OPTIONAL,
minLength [4] INTEGER (4..32) OPTIONAL,
maxLength [5] INTEGER (4..32) OPTIONAL,
- maxRetriesContact [6] INTEGER (1..127) OPTIONAL,
- maxRetriesContactless [7] INTEGER (1..127) OPTIONAL,
+ maxRetriesContact [6] INTEGER (1..15) OPTIONAL,
+ maxRetriesContactless [7] INTEGER (1..15) OPTIONAL,
charset [8] PinCharSet OPTIONAL,
history [9] INTEGER (0..12) OPTIONAL,
ruleSequence [10] INTEGER (0..32) OPTIONAL,
@@ -211,8 +211,8 @@ PukPolicyParameter ::= SEQUENCE {
enabled [0] BOOLEAN OPTIONAL,
permitContactless [1] BOOLEAN OPTIONAL,
length [2] INTEGER (4..32) OPTIONAL,
- retriesContact [3] INTEGER (1..127) OPTIONAL,
- retriesContactless [4] INTEGER (1..127) OPTIONAL,
+ retriesContact [3] INTEGER (1..15) OPTIONAL,
+ retriesContactless [4] INTEGER (1..15) OPTIONAL,
restrictUpdate [5] BOOLEAN OPTIONAL
}
diff --git a/src/com/makina/security/openfips201/Config.java b/src/com/makina/security/openfips201/Config.java
index 2980e1e..2e2dfa1 100644
--- a/src/com/makina/security/openfips201/Config.java
+++ b/src/com/makina/security/openfips201/Config.java
@@ -46,7 +46,7 @@ final class Config {
static final short LENGTH_APPLICATION_NAME = (short) 11;
static final byte VERSION_MAJOR = (byte) 1;
static final byte VERSION_MINOR = (byte) 10;
- static final byte VERSION_REVISION = (byte) 1;
+ static final byte VERSION_REVISION = (byte) 2;
static final byte VERSION_DEBUG = (byte) 0; // If set to 1, this build is considered DEBUG
///////////////////////////////////////////////////////////////////////////
@@ -319,12 +319,12 @@ final class Config {
//
static final byte LIMIT_PIN_MIN_LENGTH = (byte) 4;
static final byte LIMIT_PIN_MAX_LENGTH = (byte) 16;
- static final byte LIMIT_PIN_MAX_RETRIES = (byte) 127;
+ static final byte LIMIT_PIN_MAX_RETRIES = (byte) 15;
static final byte LIMIT_PIN_HISTORY = (byte) 12;
static final byte LIMIT_PUK_MIN_LENGTH = (byte) 6;
static final byte LIMIT_PUK_MAX_LENGTH = (byte) 16;
- static final byte LIMIT_PUK_MAX_RETRIES = (byte) 127;
+ static final byte LIMIT_PUK_MAX_RETRIES = (byte) 15;
private static final byte DEFAULT_PIN_ENABLE_LOCAL = TLV.TRUE;
private static final byte DEFAULT_PIN_MIN_LENGTH = (byte) 6;
@@ -450,11 +450,11 @@ boolean readFlag(byte address) {
return (config[address] != (byte) 0);
}
- byte getIntermediatePIN() {
+ byte getIntermediatePINRetries() {
return (byte) (config[CONFIG_PIN_RETRIES_CONTACT] - config[CONFIG_PIN_RETRIES_CONTACTLESS]);
}
- byte getIntermediatePUK() {
+ byte getIntermediatePUKRetries() {
return (byte) (config[CONFIG_PUK_RETRIES_CONTACT] - config[CONFIG_PUK_RETRIES_CONTACTLESS]);
}
@@ -562,8 +562,8 @@ void update(TLVReader reader) {
if (value < (byte) 0 || value > LIMIT_PIN_MAX_RETRIES) {
ISOException.throwIt(ISO7816.SW_DATA_INVALID);
}
- // Pre-condition - Must be less than RETRIES_CONTACT
- if (value >= config[CONFIG_PIN_RETRIES_CONTACT]) {
+ // Pre-condition - Cannot be greater than RETRIES_CONTACT
+ if (value > config[CONFIG_PIN_RETRIES_CONTACT]) {
ISOException.throwIt(ISO7816.SW_DATA_INVALID);
}
config[CONFIG_PIN_RETRIES_CONTACTLESS] = value;
@@ -661,8 +661,8 @@ void update(TLVReader reader) {
if (value < (byte) 0 || value > LIMIT_PUK_MAX_RETRIES) {
ISOException.throwIt(ISO7816.SW_DATA_INVALID);
}
- // Pre-condition - Must be less than PUK_RETRIES_CONTACT
- if (value >= config[CONFIG_PUK_RETRIES_CONTACT]) {
+ // Pre-condition - Must not be more than PUK_RETRIES_CONTACT
+ if (value > config[CONFIG_PUK_RETRIES_CONTACT]) {
ISOException.throwIt(ISO7816.SW_DATA_INVALID);
}
config[CONFIG_PUK_RETRIES_CONTACTLESS] = value;
diff --git a/src/com/makina/security/openfips201/ECParamsP256.java b/src/com/makina/security/openfips201/ECParamsP256.java
index 3344eda..840d614 100644
--- a/src/com/makina/security/openfips201/ECParamsP256.java
+++ b/src/com/makina/security/openfips201/ECParamsP256.java
@@ -111,6 +111,11 @@ static ECParams getInstance() {
}
return instance;
}
+
+ static void terminate() {
+ // NOTE: It is the responsibility of the caller to perform garbage collection
+ instance = null;
+ }
@Override
protected byte[] getA() {
diff --git a/src/com/makina/security/openfips201/ECParamsP384.java b/src/com/makina/security/openfips201/ECParamsP384.java
index 9ccbebe..9746793 100644
--- a/src/com/makina/security/openfips201/ECParamsP384.java
+++ b/src/com/makina/security/openfips201/ECParamsP384.java
@@ -42,6 +42,11 @@ static ECParams getInstance() {
}
return instance;
}
+
+ static void terminate() {
+ // NOTE: It is the responsibility of the caller to perform garbage collection
+ instance = null;
+ }
// Curve polynomial element a
protected static final byte[] a = {
diff --git a/src/com/makina/security/openfips201/OpenFIPS201.java b/src/com/makina/security/openfips201/OpenFIPS201.java
index f3e3739..09a8c2b 100644
--- a/src/com/makina/security/openfips201/OpenFIPS201.java
+++ b/src/com/makina/security/openfips201/OpenFIPS201.java
@@ -28,6 +28,7 @@
import javacard.framework.APDU;
import javacard.framework.Applet;
+import javacard.framework.AppletEvent;
import javacard.framework.ISO7816;
import javacard.framework.ISOException;
import org.globalplatform.GPSystem;
@@ -37,7 +38,7 @@
* The main applet class, which is responsible for handling APDU's and dispatching them to the PIV
* provider.
*/
-final class OpenFIPS201 extends Applet {
+public final class OpenFIPS201 extends Applet implements AppletEvent {
/*
* PERSISTENT applet variables (EEPROM)
*/
@@ -130,6 +131,21 @@ public void deselect() {
}
}
+ @Override
+ public void uninstall() {
+ //
+ // NOTE:
+ // - Get rid of all static instances that would prevent GP from deleting the applet instance
+ // without also deleting the corresponding package
+ // - TODO: Change TLVReader and TLVWriter to an instance
+ // - TODO: Change ECParams to public final const arrays, there's no need to instantiate.
+ TLVReader.terminate();
+ TLVWriter.terminate();
+ PIVCrypto.terminate();
+ ECParamsP256.terminate();
+ ECParamsP384.terminate();
+ }
+
@Override
public void process(APDU apdu) {
@@ -247,7 +263,7 @@ public void process(APDU apdu) {
processPIV_PUT_DATA(apdu);
break;
- case INS_PIV_GENERATE_ASYMMETRIC_KEYPAIR:
+ case INS_PIV_GENERATE_ASYMMETRIC_KEYPAIR: // Case 2
processPIV_GENERATE_ASYMMETRIC_KEYPAIR(apdu);
break;
diff --git a/src/com/makina/security/openfips201/PIV.java b/src/com/makina/security/openfips201/PIV.java
index 82a6286..b78a35f 100644
--- a/src/com/makina/security/openfips201/PIV.java
+++ b/src/com/makina/security/openfips201/PIV.java
@@ -60,7 +60,7 @@ final class PIV {
static final short LENGTH_SCRATCH = (short) 284;
//
- // Static PIV identifiers
+ // Static PIV identifiers
//
// Data Objects
@@ -103,7 +103,6 @@ final class PIV {
* PIV APPLICATION CONSTANTS
*/
static final short SW_REFERENCE_NOT_FOUND = (short) 0x6A88;
- static final short SW_OPERATION_BLOCKED = (short) 0x6983;
static final short SW_PUT_DATA_COMMAND_MISSING = (short) 0x6E10;
static final short SW_PUT_DATA_COMMAND_INVALID_LENGTH = (short) 0x6E11;
@@ -192,10 +191,7 @@ final class PIV {
chainBuffer = new ChainBuffer();
// Create our PIV Security Provider
- cspPIV =
- new PIVSecurityProvider(
- config.readValue(Config.CONFIG_PIN_RETRIES_CONTACT),
- config.readValue(Config.CONFIG_PUK_RETRIES_CONTACT));
+ cspPIV = new PIVSecurityProvider();
// Create our TLV objects (we don't care about the result, this is just to allocate)
TLVReader.getInstance();
@@ -261,7 +257,22 @@ short select(byte[] buffer, short offset) {
// EXECUTION STEPS
//
- // STEP 1 - Return the APT
+ // STEP 1 - Evaluate whether any PIV state needs to be updated as a result of
+ // configuration changes
+
+ // STEP 1a) Local PIN try limit
+ PIVPIN localPin = cspPIV.getPIN(PIV.ID_CVM_LOCAL_PIN);
+ if (config.readValue(Config.CONFIG_PIN_RETRIES_CONTACT) != localPin.getTryLimit()) {
+ localPin.setTryLimit(config.readValue(Config.CONFIG_PIN_RETRIES_CONTACT));
+ }
+
+ // STEP 1b) PUK try limit
+ PIVPIN puk = cspPIV.getPIN(PIV.ID_CVM_PUK);
+ if (config.readValue(Config.CONFIG_PUK_RETRIES_CONTACT) != puk.getTryLimit()) {
+ puk.setTryLimit(config.readValue(Config.CONFIG_PUK_RETRIES_CONTACT));
+ }
+
+ // STEP 2 - Return the APT
Util.arrayCopyNonAtomic(
Config.TEMPLATE_APT, ZERO, buffer, offset, (short) Config.TEMPLATE_APT.length);
@@ -634,7 +645,7 @@ void verify(byte id, byte[] buffer, short offset, short length) throws ISOExcept
//
// PRE-CONDITION 1 - The PIN reference must point to a valid PIN
- PIN pin = cspPIV.getPIN(id);
+ PIVPIN pin = cspPIV.getPIN(id);
if (pin == null) {
ISOException.throwIt(SW_REFERENCE_NOT_FOUND);
return;
@@ -681,8 +692,6 @@ void verify(byte id, byte[] buffer, short offset, short length) throws ISOExcept
}
// PRE-CONDITION 4 - The PIN must not be blocked
- if (pin.getTriesRemaining() == (byte) 0) ISOException.throwIt(SW_OPERATION_BLOCKED);
-
// PRE-CONDITION 5 - If using the contactless interface, the pin retries remaining must not
// fall below the specified intermediate retry amount
@@ -694,8 +703,13 @@ void verify(byte id, byte[] buffer, short offset, short length) throws ISOExcept
// the issuer-specified intermediate retry value. If status word '69 83' is returned, then the
// comparison shall not be made, and the security status and the retry counter of the key
// reference shall remain unchanged.
- if (cspPIV.getIsContactless() && (pin.getTriesRemaining() <= config.getIntermediatePIN())) {
- ISOException.throwIt(SW_OPERATION_BLOCKED);
+ byte intermediateRetries = config.getIntermediatePINRetries();
+
+ if (cspPIV.getIsContactless()) {
+ if (pin.getTriesRemaining() <= intermediateRetries)
+ ISOException.throwIt(ISO7816.SW_FILE_INVALID);
+ } else {
+ if (pin.getTriesRemaining() == (byte) 0) ISOException.throwIt(ISO7816.SW_FILE_INVALID);
}
//
@@ -704,12 +718,18 @@ void verify(byte id, byte[] buffer, short offset, short length) throws ISOExcept
// Verify the PIN
if (!pin.check(buffer, offset, (byte) length)) {
+ short remaining = pin.getTriesRemaining();
+
+ // For contactless, we reduce the retries by the difference between contact and contactless
+ if (cspPIV.getIsContactless()) {
+ remaining -= intermediateRetries;
+ }
// Check for blocked again
- if (pin.getTriesRemaining() == (byte) 0) ISOException.throwIt(SW_OPERATION_BLOCKED);
+ if (remaining == (byte) 0) ISOException.throwIt(ISO7816.SW_FILE_INVALID);
// Return the number of retries remaining
- ISOException.throwIt((short) (SW_RETRIES_REMAINING | (short) pin.getTriesRemaining()));
+ ISOException.throwIt((short) (SW_RETRIES_REMAINING | remaining));
}
// Verified, set the PIN ALWAYS flag
@@ -767,7 +787,7 @@ void verifyGetStatus(byte id) throws ISOException {
// needed ('90 00').
// Check for a blocked PIN
- if (pin.getTriesRemaining() == (byte) 0) ISOException.throwIt(SW_OPERATION_BLOCKED);
+ if (pin.getTriesRemaining() == (byte) 0) ISOException.throwIt(ISO7816.SW_FILE_INVALID);
// If we are not validated
if (!pin.isValidated()) {
@@ -869,8 +889,7 @@ void changeReferenceData(byte id, byte[] buffer, short offset, short length) thr
// interface (including SM or VCI), then the card command shall fail. If key reference
// '00' or '80' is specified and the command is not submitted over either the contact interface
// or the VCI, then the card command shall fail. In each case, the security status and the
- // retry counter
- // of the key reference shall remain unchanged.
+ // retry counter of the key reference shall remain unchanged.
// NOTE: This is handled in the switch statement and is configurable at compile-time
byte intermediateRetries;
@@ -891,7 +910,7 @@ void changeReferenceData(byte id, byte[] buffer, short offset, short length) thr
}
// NOTE: This will only work if the 'CVM Management' applet privilege has been set
- intermediateRetries = config.getIntermediatePIN();
+ intermediateRetries = config.getIntermediatePINRetries();
break;
case ID_CVM_LOCAL_PIN:
@@ -907,8 +926,7 @@ void changeReferenceData(byte id, byte[] buffer, short offset, short length) thr
ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
}
- intermediateRetries = config.getIntermediatePIN();
-
+ intermediateRetries = config.getIntermediatePINRetries();
break;
case ID_CVM_PUK:
@@ -924,7 +942,7 @@ void changeReferenceData(byte id, byte[] buffer, short offset, short length) thr
ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
}
- intermediateRetries = config.getIntermediatePUK();
+ intermediateRetries = config.getIntermediatePUKRetries();
puk = true;
break;
@@ -936,17 +954,20 @@ void changeReferenceData(byte id, byte[] buffer, short offset, short length) thr
// If the current value of the retry counter associated with the key reference is zero, then the
// reference data associated with the key reference shall not be changed and the
// PIV Card Application shall return the status word '69 83'.
- if (pin.getTriesRemaining() == ZERO) {
- ISOException.throwIt(SW_OPERATION_BLOCKED);
- }
// If the command is submitted over the contactless interface (VCI) and the current value of the
// retry counter associated with the key reference is at or below the issuer-specified
// intermediate retry value (see Section 3.2.1),
// then the reference data associated with the key reference shall not be changed and the PIV
// Card Application shall return the status word '69 83'.
- if (cspPIV.getIsContactless() && (pin.getTriesRemaining()) <= intermediateRetries) {
- ISOException.throwIt(SW_OPERATION_BLOCKED);
+ if (cspPIV.getIsContactless()) {
+ if (pin.getTriesRemaining() <= intermediateRetries) {
+ ISOException.throwIt(ISO7816.SW_FILE_INVALID);
+ }
+ } else {
+ if (pin.getTriesRemaining() <= ZERO) {
+ ISOException.throwIt(ISO7816.SW_FILE_INVALID);
+ }
}
// If the authentication data in the command data field does not match the current value of the
@@ -991,16 +1012,24 @@ void changeReferenceData(byte id, byte[] buffer, short offset, short length) thr
}
// Verify the authentication reference data (old PIN/PUK) format
- if (!puk) {
- if (!verifyPinFormat(buffer, offset, pinLength)) {
- ISOException.throwIt(ISO7816.SW_WRONG_DATA);
- }
+ if (!puk && !verifyPinFormat(buffer, offset, pinLength)) {
+ ISOException.throwIt(ISO7816.SW_WRONG_DATA);
}
// Verify the authentication reference data (old PIN/PUK) value
if (!pin.check(buffer, offset, pinLength)) {
+ short remaining = pin.getTriesRemaining();
+
+ // For contactless, we reduce the retries by the difference between contact and contactless
+ if (cspPIV.getIsContactless()) {
+ remaining -= intermediateRetries;
+ }
+
+ // Check for blocked again
+ if (remaining == (byte) 0) ISOException.throwIt(ISO7816.SW_FILE_INVALID);
+
// Return the number of retries remaining
- ISOException.throwIt((short) (SW_RETRIES_REMAINING | (short) pin.getTriesRemaining()));
+ ISOException.throwIt((short) (SW_RETRIES_REMAINING | remaining));
}
// Move to the new reference data
@@ -1071,7 +1100,7 @@ void resetRetryCounter(byte id, byte[] buffer, short offset, short length) throw
// interface.
// NOTE: We must check this for both the PIN and the PUK
/*
- Truth table because there's a few balls in the air here:
+ Truth table because there are a few balls in the air here:
IS_CTLESS IGNORE_ACL PIN_PERMIT PUK_PERMIT RESULT
----------------------------------------------------
FALSE X X X FALSE
@@ -1103,7 +1132,7 @@ void resetRetryCounter(byte id, byte[] buffer, short offset, short length) throw
byte pinLength = config.readValue(Config.CONFIG_PIN_MAX_LENGTH);
short expectedLength = (short) (config.readValue(Config.CONFIG_PUK_LENGTH) + pinLength);
- if (length != expectedLength) ISOException.throwIt(SW_OPERATION_BLOCKED);
+ if (length != expectedLength) ISOException.throwIt(ISO7816.SW_FILE_INVALID);
// PRE-CONDITION 6 - The PUK must not be blocked
// If the current value of the PUK's retry counter is zero, then the PIN's retry counter shall
@@ -1113,7 +1142,14 @@ void resetRetryCounter(byte id, byte[] buffer, short offset, short length) throw
ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
return; // Keep compiler happy
}
- if (puk.getTriesRemaining() == ZERO) ISOException.throwIt(SW_OPERATION_BLOCKED);
+
+ byte intermediateRetries = config.getIntermediatePUKRetries();
+ if (cspPIV.getIsContactless()) {
+ if (puk.getTriesRemaining() <= intermediateRetries)
+ ISOException.throwIt(ISO7816.SW_FILE_INVALID);
+ } else {
+ if (puk.getTriesRemaining() == ZERO) ISOException.throwIt(ISO7816.SW_FILE_INVALID);
+ }
// PRE-CONDITION 7 - Verify the PUK value
// If the reset retry counter authentication data (PUK) in the command data field of the command
@@ -1124,13 +1160,18 @@ void resetRetryCounter(byte id, byte[] buffer, short offset, short length) throw
// Reset the PIN's security condition (see paragraph below for explanation)
pin.reset();
- // Check again if we are blocked
- if (puk.getTriesRemaining() == ZERO) {
- ISOException.throwIt(SW_OPERATION_BLOCKED);
- } else {
- // Return the number of retries remaining
- ISOException.throwIt((short) (SW_RETRIES_REMAINING | (short) puk.getTriesRemaining()));
+ short remaining = puk.getTriesRemaining();
+
+ // For contactless, we reduce the retries by the difference between contact and contactless
+ if (cspPIV.getIsContactless()) {
+ remaining -= intermediateRetries;
}
+
+ // Check for blocked again
+ if (remaining == (byte) 0) ISOException.throwIt(ISO7816.SW_FILE_INVALID);
+
+ // Return the number of retries remaining
+ ISOException.throwIt((short) (SW_RETRIES_REMAINING | remaining));
}
// Move to the start of the new PIN
@@ -2275,6 +2316,7 @@ short generateAsymmetricKeyPair(byte[] buffer, short offset) throws ISOException
private boolean verifyPinRules(byte[] buffer, short offset, short length) {
boolean passed = true;
+
//
// RULE 1 - SEQUENCE RULE (Ascending and Descending)
//
@@ -2335,7 +2377,7 @@ private boolean verifyPinRules(byte[] buffer, short offset, short length) {
if (ruleDistinct > (byte) 0) {
byte maxSingle = (byte) 0;
- short end = (short)(offset + length);
+ short end = (short) (offset + length);
for (short i = offset; i < end; i++) {
byte count = (byte) 1; // Every used digit has at least 1
for (short j = (short) (i + (short) 1); j < end; j++) {
@@ -2788,11 +2830,11 @@ private void processCreateKeyRequest(TLVReader reader, boolean legacy) {
//
// EXECUTION STEPS
//
-
+
// STEP 1 - If this is a legacy request, apply the PERMIT_MUTUAL
- // key attribute as a default.
+ // key attribute as a default.
if (legacy && PIVCrypto.isSymmetricMechanism(keyMechanism)) {
- keyAttribute |= PIVKeyObject.ATTR_PERMIT_MUTUAL;
+ keyAttribute |= PIVKeyObject.ATTR_PERMIT_MUTUAL;
}
// STEP 2 - Add the key to the key store
@@ -3058,7 +3100,10 @@ void changeReferenceDataAdmin(byte id, byte[] buffer, short offset, short length
// SPECIAL CASE 2 - PUK
//
if (id == ID_CVM_PUK) {
- // NOTE: No format verification required for the PUK
+ // NOTES:
+ // - We deliberately ignore the value of CONFIG_PUK_ENABLED here as there may be a good
+ // reason for setting a pre-defined PUK value with the anticipation of enabling it later
+ // - No format verification required is for the PUK
// Update the PUK
cspPIV.updatePIN(ID_CVM_PUK, scratch, ZERO, (byte) length, ZERO);
@@ -3232,7 +3277,7 @@ short getDataExtended(byte[] buffer, short offset, short length) throws ISOExcep
ISOException.throwIt(ISO7816.SW_FILE_NOT_FOUND);
}
- // PRE-CONDITION 2 - The 'TAG' value must start with CONST_TAG_EXTENDED
+ // PRE-CONDITION 3 - The 'TAG' value must start with CONST_TAG_EXTENDED
if (!reader.matchData(CONST_TAG_EXTENDED)) {
ISOException.throwIt(ISO7816.SW_FILE_NOT_FOUND);
}
diff --git a/src/com/makina/security/openfips201/CVMPIN.java b/src/com/makina/security/openfips201/PIVCVMPIN.java
similarity index 75%
rename from src/com/makina/security/openfips201/CVMPIN.java
rename to src/com/makina/security/openfips201/PIVCVMPIN.java
index 26a2c21..f6f9da6 100644
--- a/src/com/makina/security/openfips201/CVMPIN.java
+++ b/src/com/makina/security/openfips201/PIVCVMPIN.java
@@ -26,38 +26,20 @@
package com.makina.security.openfips201;
-import javacard.framework.OwnerPIN;
-import javacard.framework.PIN;
import javacard.framework.PINException;
import org.globalplatform.CVM;
import org.globalplatform.GPSystem;
/** Provides an OwnerPIN proxy to the CVM class to allow uniform handling */
-final class CVMPIN extends OwnerPIN implements PIN {
+final class PIVCVMPIN implements PIVPIN {
private final CVM cvm;
- /**
- * Constructor
- *
- * @param tryLimit The number of incorrect attempts before blocking
- * @param maxPINSize The maximum length of the PIN
- */
- CVMPIN(byte tryLimit, byte maxPINSize) throws PINException {
-
- super(tryLimit, maxPINSize);
+ /** Constructor */
+ PIVCVMPIN() throws PINException {
// Get our CVM reference
cvm = GPSystem.getCVM(GPSystem.CVM_GLOBAL_PIN);
-
- // Map the try limit to the CVM
- // NOTE: If the applet does not have the CVM MANAGEMENT privilege, this will fail
- try {
- cvm.setTryLimit(tryLimit);
- } catch (Exception ex) {
- // Do nothing, since the API gives us no way to know if we have this permission
- // all we can do is try.
- }
}
@Override
@@ -87,7 +69,18 @@ public void update(byte[] pin, short offset, byte length) throws PINException {
}
@Override
- public void resetAndUnblock() {
- cvm.resetAndUnblockState();
+ public byte getTryLimit() {
+ return (short) 0;
+ }
+
+ public void setTryLimit(byte limit) {
+ // Do nothing
+ PINException.throwIt(PINException.ILLEGAL_VALUE);
+ }
+
+ public boolean supportsSetTryLimit() {
+ // GPRegistryEntry reg = GPSystem.getRegistryEntry(null);
+ // return reg.isPrivileged(GPRegistryEntry.PRIVILEGE_CVM_MANAGEMENT);
+ return false;
}
}
diff --git a/src/com/makina/security/openfips201/PIVCrypto.java b/src/com/makina/security/openfips201/PIVCrypto.java
index 1817a01..d56b770 100644
--- a/src/com/makina/security/openfips201/PIVCrypto.java
+++ b/src/com/makina/security/openfips201/PIVCrypto.java
@@ -28,6 +28,7 @@
import javacard.framework.ISO7816;
import javacard.framework.ISOException;
+import javacard.framework.JCSystem;
import javacard.security.AESKey;
import javacard.security.CryptoException;
import javacard.security.DESKey;
@@ -85,10 +86,26 @@ private PIVCrypto() {}
private static RandomData cspRNG;
+ static void terminate() {
+ cspRNG = null;
+ cspTDEA = null;
+ cspAES = null;
+ cspRSA = null;
+ cspECDH = null;
+ cspECCSHA1 = null;
+ cspECCSHA256 = null;
+ cspECCSHA384 = null;
+ cspECCSHA512 = null;
+ cspSHA256 = null;
+ cspSHA384 = null;
+
+ JCSystem.requestObjectDeletion();
+ }
+
static void init() {
// Create all CSP's
- // Mandatory - RNG
+ // Mandatory - RNG
try {
cspRNG = RandomData.getInstance(RandomData.ALG_SECURE_RANDOM);
} catch (CryptoException ex) {
@@ -218,7 +235,7 @@ static boolean isSymmetricMechanism(byte mechanism) {
case PIV.ID_ALG_AES_128:
case PIV.ID_ALG_AES_192:
case PIV.ID_ALG_AES_256:
- return true;
+ return true;
default:
return false;
diff --git a/src/com/makina/security/openfips201/PIVOwnerPIN.java b/src/com/makina/security/openfips201/PIVOwnerPIN.java
new file mode 100644
index 0000000..1e0f3d2
--- /dev/null
+++ b/src/com/makina/security/openfips201/PIVOwnerPIN.java
@@ -0,0 +1,132 @@
+/******************************************************************************
+ * MIT License
+ *
+ * Project: OpenFIPS201
+ * Copyright: (c) 2017 Commonwealth of Australia
+ * Author: Kim O'Sullivan - Makina (kim@makina.com.au)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ ******************************************************************************/
+
+package com.makina.security.openfips201;
+
+import javacard.framework.OwnerPIN;
+import javacard.framework.PINException;
+
+/*
+ * Provides an application-specific OwnerPIN implementation.
+ *
+ * This implementation internally uses an OwnerPIN for its implementation,
+ * but it specifically permits the definition of a 'soft' retry limit, which
+ * allows the application to define a retry limit which is less than
+ * the initial hard limit imposed by the OwnerPIN instance.
+ *
+ * The rationale for this is that JCRE 3.0.4 does not support updating the
+ * try limit value. From a security perspective, storing the soft try limit
+ * in a variable in this class means that this limit is less protected than
+ * one stored in OwnerPIN. The exposure for this threat is only limited to the
+ * hard retry limit value however.
+ */
+final class PIVOwnerPIN implements PIVPIN {
+
+ /*
+ * Defines the highest possible try limit, which is derived
+ * from the fact that the SW12 value for an incorrect PIN only
+ * permits the remaining attempts to be stored in a nibble.
+ */
+ public static final byte HARD_PIN_TRY_LIMIT = (byte) 15;
+
+ private final OwnerPIN myPIN;
+ private byte mySoftTryLimit;
+
+ /**
+ * Constructor
+ *
+ * @param tryLimit The number of incorrect attempts before blocking
+ * @param maxPINSize The maximum length of the PIN
+ */
+ PIVOwnerPIN(byte tryLimit, byte maxPINSize) throws PINException {
+ myPIN = new OwnerPIN(tryLimit, maxPINSize);
+
+ // Initialise our soft try limit to the initial hard limit
+ mySoftTryLimit = tryLimit;
+ }
+
+ @Override
+ public byte getTriesRemaining() {
+ byte retries = myPIN.getTriesRemaining();
+ byte delta = (byte) (HARD_PIN_TRY_LIMIT - mySoftTryLimit);
+
+ // NOTE:
+ // - It should never be the case that retries gets under the delta value, because if it has
+ // then the PIN was permitted to be checked more than the soft limit. This provides a basic
+ // sanity check to ensure the PIN blocks regardless.
+ if (retries < delta) {
+ retries = 0;
+ } else {
+ retries -= delta;
+ }
+ return retries;
+ }
+
+ @Override
+ public boolean check(byte[] pin, short offset, byte length)
+ throws ArrayIndexOutOfBoundsException, NullPointerException {
+
+ // Manually check the remaining retries here
+ if (getTriesRemaining() <= 0) {
+ PINException.throwIt(PINException.ILLEGAL_VALUE);
+ return false; // Keep compiler happy
+ }
+
+ return myPIN.check(pin, offset, length);
+ }
+
+ @Override
+ public boolean isValidated() {
+ return myPIN.isValidated();
+ }
+
+ @Override
+ public void reset() {
+ myPIN.reset();
+ }
+
+ @Override
+ public void update(byte[] pin, short offset, byte length) throws PINException {
+ myPIN.update(pin, offset, length);
+ }
+
+ @Override
+ public byte getTryLimit() {
+ return mySoftTryLimit;
+ }
+
+ public void setTryLimit(byte limit) {
+ if (limit < 0 || limit > HARD_PIN_TRY_LIMIT) {
+ PINException.throwIt(PINException.ILLEGAL_VALUE);
+ } else {
+ mySoftTryLimit = limit;
+ }
+ }
+
+ public boolean supportsSetTryLimit() {
+ return true;
+ }
+}
diff --git a/src/com/makina/security/openfips201/PIVPIN.java b/src/com/makina/security/openfips201/PIVPIN.java
new file mode 100644
index 0000000..09d0ace
--- /dev/null
+++ b/src/com/makina/security/openfips201/PIVPIN.java
@@ -0,0 +1,41 @@
+/******************************************************************************
+ * MIT License
+ *
+ * Project: OpenFIPS201
+ * Copyright: (c) 2022 Commonwealth of Australia
+ * Author: Kim O'Sullivan - Makina (kim@makina.com.au)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ ******************************************************************************/
+
+package com.makina.security.openfips201;
+
+import javacard.framework.PIN;
+import javacard.framework.PINException;
+
+/** Provides an OwnerPIN facade that permits retry */
+interface PIVPIN extends PIN {
+ byte getTryLimit();
+
+ void setTryLimit(byte limit);
+
+ boolean supportsSetTryLimit();
+
+ void update(byte[] pin, short offset, byte length) throws PINException;
+}
diff --git a/src/com/makina/security/openfips201/PIVSecurityProvider.java b/src/com/makina/security/openfips201/PIVSecurityProvider.java
index d9c416c..4ca434c 100644
--- a/src/com/makina/security/openfips201/PIVSecurityProvider.java
+++ b/src/com/makina/security/openfips201/PIVSecurityProvider.java
@@ -30,7 +30,6 @@
import javacard.framework.ISOException;
import javacard.framework.JCSystem;
import javacard.framework.OwnerPIN;
-import javacard.framework.PIN;
import javacard.framework.Util;
/**
@@ -68,9 +67,9 @@ final class PIVSecurityProvider {
//
// PERSISTENT - PIN objects
- private final OwnerPIN cardPIN; // 80 - Card Application PIN
- private final OwnerPIN cardPUK; // 81 - PIN Unlocking Key (PUK)
- private final CVMPIN globalPIN; // 00 - Global PIN
+ private final PIVPIN cardPIN; // 80 - Card Application PIN
+ private final PIVPIN cardPUK; // 81 - PIN Unlocking Key (PUK)
+ private final PIVPIN globalPIN; // 00 - Global PIN
private final OwnerPIN[] pinHistory;
// PERSISTENT - Counters related to security operations
@@ -89,7 +88,7 @@ final class PIVSecurityProvider {
private static final byte FLAG_FALSE = (byte) 0;
private static final byte FLAG_TRUE = (byte) 0xFF;
- PIVSecurityProvider(byte pinRetries, byte pukRetries) {
+ PIVSecurityProvider() {
// Initialise our PIV crypto provider
PIVCrypto.init();
@@ -107,13 +106,13 @@ final class PIVSecurityProvider {
// the limitation that it can only be set once. This is because OwnerPIN won't let you change it
// Mandatory
- cardPIN = new OwnerPIN(pinRetries, Config.LIMIT_PIN_MAX_LENGTH);
+ cardPIN = new PIVOwnerPIN(Config.LIMIT_PIN_MAX_RETRIES, Config.LIMIT_PIN_MAX_LENGTH);
// Mandatory
- cardPUK = new OwnerPIN(pukRetries, Config.LIMIT_PUK_MAX_LENGTH);
+ cardPUK = new PIVOwnerPIN(Config.LIMIT_PUK_MAX_RETRIES, Config.LIMIT_PUK_MAX_LENGTH);
// Optional - But we still have to create it because it can be enabled at runtime
- globalPIN = new CVMPIN(pinRetries, Config.LIMIT_PIN_MAX_LENGTH);
+ globalPIN = new PIVCVMPIN();
// Supplemental - PIN History
pinHistory = new OwnerPIN[Config.LIMIT_PIN_HISTORY];
@@ -369,7 +368,7 @@ boolean checkAccessModeObject(PIVObject object) {
return valid;
}
- PIN getPIN(byte id) {
+ PIVPIN getPIN(byte id) {
switch (id) {
case PIV.ID_CVM_LOCAL_PIN:
@@ -388,7 +387,7 @@ PIN getPIN(byte id) {
void updatePIN(byte id, byte[] buffer, short offset, byte length, byte historyCount) {
- OwnerPIN pin;
+ PIVPIN pin;
switch (id) {
case PIV.ID_CVM_LOCAL_PIN:
diff --git a/src/com/makina/security/openfips201/TLVReader.java b/src/com/makina/security/openfips201/TLVReader.java
index c1a81f0..9a89771 100644
--- a/src/com/makina/security/openfips201/TLVReader.java
+++ b/src/com/makina/security/openfips201/TLVReader.java
@@ -70,6 +70,11 @@ static TLVReader getInstance() {
return instance;
}
+ static void terminate() {
+ instance = null;
+ JCSystem.requestObjectDeletion();
+ }
+
/**
* Returns the length of the data element for the tag found at offset
*
diff --git a/src/com/makina/security/openfips201/TLVWriter.java b/src/com/makina/security/openfips201/TLVWriter.java
index ab45796..2f86c10 100644
--- a/src/com/makina/security/openfips201/TLVWriter.java
+++ b/src/com/makina/security/openfips201/TLVWriter.java
@@ -77,6 +77,11 @@ static TLVWriter getInstance() {
return instance;
}
+ static void terminate() {
+ instance = null;
+ JCSystem.requestObjectDeletion();
+ }
+
/**
* Initialises the object with a data buffer, starting offset and content length It is important
* that the supplied buffer has enough length for the content and also the parent Tag and Length
@@ -220,7 +225,7 @@ void move(short length) {
* @param tag The tag to write
* @param value The value to write
*/
- void write(byte tag, byte value) throws ISOException {
+ void write(byte tag, byte value) throws ISOException {
if (dataPtr[0] == null) ISOException.throwIt(ISO7816.SW_DATA_INVALID);
byte[] data = (byte[]) dataPtr[0];
diff --git a/tools/piv_test_runner/config/OpenFIPS201-RSA2048.xml b/tools/piv_test_runner/config/OpenFIPS201-RSA2048.xml
index 89dc259..7b663ba 100644
--- a/tools/piv_test_runner/config/OpenFIPS201-RSA2048.xml
+++ b/tools/piv_test_runner/config/OpenFIPS201-RSA2048.xml
@@ -5,8 +5,8 @@
PIVAPIWrapper_x64
SCARD_PROTOCOL_T1
com.tvec.smart_card.piv.api.tests.PIVAPIWrapper
- JAVACOS Virtual Contact Reader 0
- JAVACOS Virtual Contactless Reader 1
+ Gemalto Prox-DU Contact_12400136 0
+ Gemalto Prox-DU Contactless_12400136 0
80
@@ -25,9 +25,9 @@
true
8
77
- false
+ true
true
- 6
+ 9
07
05cf20
@@ -36,7 +36,7 @@
03
123456
07
- false
+ true
12345678
07
6