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