Author SHA1 Message Date
Michele Balistreri 793c8eedc9 bump pairing slot count to 100 2025-02-12 09:07:40 +01:00
Michele Balistreri 146c049b45 Merge pull request #89 from status-im/internal-factory-reset
Internal factory reset
2023-06-06 17:03:32 +02:00
Michele Balistreri 86faab3f26 update sdk 2023-06-06 17:02:26 +02:00
Michele Balistreri 44adec536e clear also some ram buffers 2023-06-06 15:26:18 +02:00
Michele Balistreri e79d84b9b4 fix and test factory reset 2023-06-06 14:40:24 +02:00
Michele Balistreri 039964e8d0 implement factory reset 2023-06-06 10:46:46 +02:00
guylouis 4a04bdacec Update README.md with enno wallet mention 2023-05-17 07:40:52 +02:00
Michele Balistreri b21fef0fec Merge pull request #87 from status-im/plausible-deniability
implement plausible deniability
2022-12-02 12:47:18 +01:00
Michele Balistreri 40a4168a17 bump version 2022-12-02 12:33:55 +01:00
Michele Balistreri a2595b35e7 constant time comparison 2022-11-25 10:03:55 +01:00
Michele Balistreri 4956fcc1ee rename alt chain code 2022-11-10 15:48:05 +01:00
Michele Balistreri 509abc291b modify init command 2022-11-10 14:12:02 +01:00
Michele Balistreri b55764bdd7 add tests 2022-11-10 08:13:40 +01:00
Michele Balistreri 8ac2e88383 implement plausble deniability 2022-11-09 13:47:03 +01:00
Michele Balistreri 5bc653c012 Merge pull request #86 from status-im/lazy-derivation
add chain code export and remove derivation caches
2022-11-07 13:10:55 +01:00
7 changed files with 240 additions and 76 deletions
+4 -3
View File
@@ -17,10 +17,11 @@ A good place to start is our documentation site https://keycard.tech/docs/
You can also join the dicussion about this project on Status channel: https://get.status.im/chat/public/status-keycard
If you just want to use the Keycard as your hardware wallet there are currently two apps supporting it
If you just want to use the Keycard as your hardware wallet there are currently three apps supporting it
1. WallETH [[Android](https://play.google.com/store/apps/details?id=org.walleth)]
2. Status [[Android](https://play.google.com/store/apps/details?id=im.status.ethereum)][[iOS](https://apps.apple.com/us/app/status-private-communication/id1178893006)]
1. Status [[Android](https://play.google.com/store/apps/details?id=im.status.ethereum)][[iOS](https://apps.apple.com/us/app/status-private-communication/id1178893006)]
2. WallETH [[Android](https://play.google.com/store/apps/details?id=org.walleth)]
3. Enno Walet https://ennowallet.com/
# How to contribute?
+3 -3
View File
@@ -9,7 +9,7 @@ buildscript {
dependencies {
classpath 'com.fidesmo:gradle-javacard:0.2.7'
classpath 'com.github.status-im.status-keycard-java:desktop:31f4ab5'
classpath 'com.github.status-im.status-keycard-java:desktop:64aece4'
}
}
@@ -46,7 +46,7 @@ if (!testTarget) {
def pairingPass = project.properties['im.status.keycard.test.pairing']
if (!pairingPass) {
pairingPass = 'KeycardTest'
pairingPass = 'KeycardDefaultPairing'
}
@@ -59,7 +59,7 @@ dependencies {
testCompile(files("../jcardsim/jcardsim-3.0.5-SNAPSHOT.jar"))
testCompile('org.web3j:core:2.3.1')
testCompile('org.bitcoinj:bitcoinj-core:0.14.5')
testCompile('com.github.status-im.status-keycard-java:desktop:31f4ab5')
testCompile('com.github.status-im.status-keycard-java:desktop:3.1.2')
testCompile('org.bouncycastle:bcprov-jdk15on:1.65')
testCompile("org.junit.jupiter:junit-jupiter-api:5.1.1")
testRuntime("org.junit.jupiter:junit-jupiter-engine:5.1.1")
+1 -1
View File
@@ -4,5 +4,5 @@ repositories {
}
dependencies {
compile 'com.github.status-im.status-keycard-java:desktop:31f4ab5'
compile 'com.github.status-im.status-keycard-java:desktop:3.1.2'
}
+17 -19
View File
@@ -89,11 +89,7 @@ public class Crypto {
addm256(output, outOff, data, dataOff, SECP256k1.SECP256K1_R, (short) 0, output, outOff);
if (isZero256(output, outOff)) {
return false;
}
return true;
return !isZero256(output, outOff);
}
/**
@@ -202,17 +198,22 @@ public class Crypto {
* @return the comparison result
*/
private short ucmp256(byte[] a, short aOff, byte[] b, short bOff) {
short ai, bi;
short gt = 0;
short eq = 1;
for (short i = 0 ; i < 32; i++) {
ai = (short)(a[(short)(aOff + i)] & 0x00ff);
bi = (short)(b[(short)(bOff + i)] & 0x00ff);
short l = (short)(a[(short)(aOff + i)] & 0x00ff);
short r = (short)(b[(short)(bOff + i)] & 0x00ff);
short d = (short)(r - l);
short l_xor_r = (short)(l ^ r);
short l_xor_d = (short)(l ^ d);
short d_xored = (short)(d ^ (short)(l_xor_r & l_xor_d));
if (ai != bi) {
return (short)(ai - bi);
}
gt |= (d_xored >>> 15) & eq;
eq &= ((short)(l_xor_r - 1) >>> 15);
}
return 0;
return (short) ((gt + gt + eq) - 1);
}
/**
@@ -223,16 +224,13 @@ public class Crypto {
* @return true if a is 0, false otherwise
*/
private boolean isZero256(byte[] a, short aOff) {
boolean isZero = true;
byte acc = 0;
for (short i = 0; i < (byte) 32; i++) {
if (a[(short)(aOff + i)] != 0) {
isZero = false;
break;
}
for (short i = 0; i < 32; i++) {
acc |= a[(short)(aOff + i)];
}
return isZero;
return acc == 0;
}
/**
@@ -9,10 +9,11 @@ import static javacard.framework.ISO7816.OFFSET_P1;
* The applet's main class. All incoming commands a processed by this class.
*/
public class KeycardApplet extends Applet {
static final short APPLICATION_VERSION = (short) 0x0300;
static final short APPLICATION_VERSION = (short) 0x0301;
static final byte INS_GET_STATUS = (byte) 0xF2;
static final byte INS_INIT = (byte) 0xFE;
static final byte INS_FACTORY_RESET = (byte) 0xFD;
static final byte INS_VERIFY_PIN = (byte) 0x20;
static final byte INS_CHANGE_PIN = (byte) 0x21;
static final byte INS_UNBLOCK_PIN = (byte) 0x22;
@@ -39,7 +40,7 @@ public class KeycardApplet extends Applet {
static final byte PIN_LENGTH = 6;
static final byte DEFAULT_PIN_MAX_RETRIES = 3;
static final byte KEY_PATH_MAX_DEPTH = 10;
static final byte PAIRING_MAX_CLIENT_COUNT = 5;
static final byte PAIRING_MAX_CLIENT_COUNT = 100;
static final byte UID_LENGTH = 16;
static final byte MAX_DATA_LENGTH = 127;
@@ -85,6 +86,9 @@ public class KeycardApplet extends Applet {
static final byte STORE_DATA_P1_NDEF = 0x01;
static final byte STORE_DATA_P1_CASH = 0x02;
static final byte FACTORY_RESET_P1_MAGIC = (byte) 0xAA;
static final byte FACTORY_RESET_P2_MAGIC = 0x55;
static final byte TLV_SIGNATURE_TEMPLATE = (byte) 0xA0;
static final byte TLV_KEY_TEMPLATE = (byte) 0xA1;
@@ -105,12 +109,15 @@ public class KeycardApplet extends Applet {
static final byte CAPABILITY_KEY_MANAGEMENT = (byte) 0x02;
static final byte CAPABILITY_CREDENTIALS_MANAGEMENT = (byte) 0x04;
static final byte CAPABILITY_NDEF = (byte) 0x08;
static final byte CAPABILITY_FACTORY_RESET = (byte) 0x10;
static final byte APPLICATION_CAPABILITIES = (byte)(CAPABILITY_SECURE_CHANNEL | CAPABILITY_KEY_MANAGEMENT | CAPABILITY_CREDENTIALS_MANAGEMENT | CAPABILITY_NDEF);
static final byte APPLICATION_CAPABILITIES = (byte)(CAPABILITY_SECURE_CHANNEL | CAPABILITY_KEY_MANAGEMENT | CAPABILITY_CREDENTIALS_MANAGEMENT | CAPABILITY_NDEF | CAPABILITY_FACTORY_RESET);
static final byte[] EIP_1581_PREFIX = { (byte) 0x80, 0x00, 0x00, 0x2B, (byte) 0x80, 0x00, 0x00, 0x3C, (byte) 0x80, 0x00, 0x06, 0x2D};
private OwnerPIN pin;
private OwnerPIN mainPIN;
private OwnerPIN altPIN;
private OwnerPIN puk;
private byte[] uid;
private SecureChannel secureChannel;
@@ -118,6 +125,8 @@ public class KeycardApplet extends Applet {
private ECPublicKey masterPublic;
private ECPrivateKey masterPrivate;
private byte[] masterChainCode;
private byte[] altChainCode;
private byte[] chainCode;
private boolean isExtended;
private byte[] tmpPath;
@@ -173,6 +182,8 @@ public class KeycardApplet extends Applet {
masterPublic = (ECPublicKey) KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PUBLIC, SECP256k1.SECP256K1_KEY_SIZE, false);
masterPrivate = (ECPrivateKey) KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PRIVATE, SECP256k1.SECP256K1_KEY_SIZE, false);
masterChainCode = new byte[CHAIN_CODE_SIZE];
altChainCode = new byte[CHAIN_CODE_SIZE];
chainCode = masterChainCode;
keyPath = new byte[KEY_PATH_MAX_DEPTH * 4];
pinlessPath = new byte[KEY_PATH_MAX_DEPTH * 4];
@@ -276,6 +287,9 @@ public class KeycardApplet extends Applet {
case INS_STORE_DATA:
storeData(apdu);
break;
case INS_FACTORY_RESET:
factoryReset(apdu);
return;
default:
ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
break;
@@ -319,21 +333,27 @@ public class KeycardApplet extends Applet {
byte defaultLimitsLen = (byte)(PIN_LENGTH + PUK_LENGTH + SecureChannel.SC_SECRET_LENGTH);
byte withLimitsLen = (byte) (defaultLimitsLen + 2);
byte withAltPIN = (byte) (withLimitsLen + 6);
if (((apduBuffer[ISO7816.OFFSET_LC] != defaultLimitsLen) && (apduBuffer[ISO7816.OFFSET_LC] != withLimitsLen)) || !allDigits(apduBuffer, ISO7816.OFFSET_CDATA, (short)(PIN_LENGTH + PUK_LENGTH))) {
if (((apduBuffer[ISO7816.OFFSET_LC] != defaultLimitsLen) && (apduBuffer[ISO7816.OFFSET_LC] != withLimitsLen) && (apduBuffer[ISO7816.OFFSET_LC] != withAltPIN)) || !allDigits(apduBuffer, ISO7816.OFFSET_CDATA, (short)(PIN_LENGTH + PUK_LENGTH))) {
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
}
byte pinLimit;
byte pukLimit;
short altPinOff = (short)(ISO7816.OFFSET_CDATA + PIN_LENGTH);
if (apduBuffer[ISO7816.OFFSET_LC] == withLimitsLen) {
if (apduBuffer[ISO7816.OFFSET_LC] >= withLimitsLen) {
pinLimit = apduBuffer[(short) (ISO7816.OFFSET_CDATA + defaultLimitsLen)];
pukLimit = apduBuffer[(short) (ISO7816.OFFSET_CDATA + defaultLimitsLen + 1)];
if (pinLimit < PIN_MIN_RETRIES || pinLimit > PIN_MAX_RETRIES || pukLimit < PUK_MIN_RETRIES || pukLimit > PUK_MAX_RETRIES) {
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
}
if (apduBuffer[ISO7816.OFFSET_LC] == withAltPIN) {
altPinOff = (short)(ISO7816.OFFSET_CDATA + withLimitsLen);
}
} else {
pinLimit = DEFAULT_PIN_MAX_RETRIES;
pukLimit = DEFAULT_PUK_MAX_RETRIES;
@@ -341,15 +361,16 @@ public class KeycardApplet extends Applet {
secureChannel.initSecureChannel(apduBuffer, (short)(ISO7816.OFFSET_CDATA + PIN_LENGTH + PUK_LENGTH));
JCSystem.beginTransaction();
mainPIN = new OwnerPIN(pinLimit, PIN_LENGTH);
mainPIN.update(apduBuffer, ISO7816.OFFSET_CDATA, PIN_LENGTH);
pin = new OwnerPIN(pinLimit, PIN_LENGTH);
pin.update(apduBuffer, ISO7816.OFFSET_CDATA, PIN_LENGTH);
altPIN = new OwnerPIN(pinLimit, PIN_LENGTH);
altPIN.update(apduBuffer, altPinOff, PIN_LENGTH);
puk = new OwnerPIN(pukLimit, PUK_LENGTH);
puk.update(apduBuffer, (short)(ISO7816.OFFSET_CDATA + PIN_LENGTH), PUK_LENGTH);
JCSystem.commitTransaction();
pin = mainPIN;
} else if (apduBuffer[ISO7816.OFFSET_INS] == IdentApplet.INS_IDENTIFY_CARD) {
IdentApplet.identifyCard(apdu, null, signature);
} else {
@@ -385,9 +406,11 @@ public class KeycardApplet extends Applet {
* @param apdu the JCRE-owned APDU object.
*/
private void selectApplet(APDU apdu) {
pin.reset();
altPIN.reset();
mainPIN.reset();
puk.reset();
secureChannel.reset();
pin = mainPIN;
byte[] apduBuffer = apdu.getBuffer();
@@ -516,8 +539,24 @@ public class KeycardApplet extends Applet {
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
}
if (!pin.check(apduBuffer, ISO7816.OFFSET_CDATA, len)) {
ISOException.throwIt((short)((short) 0x63c0 | (short) pin.getTriesRemaining()));
short resp = mainPIN.check(apduBuffer, ISO7816.OFFSET_CDATA, len) ? (short) 1 : (short) 0;
resp += altPIN.check(apduBuffer, ISO7816.OFFSET_CDATA, len) ? (short) 2 : (short) 0;
switch(resp) {
case 0:
ISOException.throwIt((short)((short) 0x63c0 | (short) pin.getTriesRemaining()));
break;
case 1:
chainCode = masterChainCode;
altPIN.resetAndUnblock();
pin = mainPIN;
break;
case 2:
case 3: // if pins are equal fake pin takes precedence
chainCode = altChainCode;
mainPIN.resetAndUnblock();
pin = altPIN;
break;
}
}
@@ -615,7 +654,8 @@ public class KeycardApplet extends Applet {
ISOException.throwIt((short)((short) 0x63c0 | (short) puk.getTriesRemaining()));
}
pin.resetAndUnblock();
altPIN.resetAndUnblock();
mainPIN.resetAndUnblock();
pin.update(apduBuffer, (short)(ISO7816.OFFSET_CDATA + PUK_LENGTH), PIN_LENGTH);
pin.check(apduBuffer, (short)(ISO7816.OFFSET_CDATA + PUK_LENGTH), PIN_LENGTH);
puk.reset();
@@ -662,6 +702,10 @@ public class KeycardApplet extends Applet {
* @param apduBuffer the APDU buffer
*/
private void generateKeyUIDAndRespond(APDU apdu, byte[] apduBuffer) {
if (isExtended) {
crypto.sha256.doFinal(masterChainCode, (short) 0, CHAIN_CODE_SIZE, altChainCode, (short) 0);
}
short pubLen = masterPublic.getW(apduBuffer, (short) 0);
crypto.sha256.doFinal(apduBuffer, (short) 0, pubLen, keyUID, (short) 0);
Util.arrayCopyNonAtomic(keyUID, (short) 0, apduBuffer, SecureChannel.SC_OUT_OFFSET, KEY_UID_LENGTH);
@@ -782,14 +826,20 @@ public class KeycardApplet extends Applet {
/**
* Updates the derivation path for a subsequent EXPORT KEY/SIGN APDU. Optionally stores the result in the current path.
*
* @param apduBuffer the APDU buffer
* @param off the offset in the APDU buffer relative to the data field
* @param path the path
* @param off the offset in the path
* @param len the len of the path
* @param source derivation source
*/
private void updateDerivationPath(byte[] apduBuffer, short off, short len, byte source) {
private void updateDerivationPath(byte[] path, short off, short len, byte source) {
if (!isExtended) {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
if (len == 0) {
tmpPathLen = 0;
} else {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}
return;
}
short newPathLen;
@@ -833,7 +883,7 @@ public class KeycardApplet extends Applet {
short pathOff = (short) (ISO7816.OFFSET_CDATA + off);
Util.arrayCopyNonAtomic(srcKeyPath, (short) 0, tmpPath, (short) 0, pathLenOff);
Util.arrayCopyNonAtomic(apduBuffer, pathOff, tmpPath, pathLenOff, len);
Util.arrayCopyNonAtomic(path, pathOff, tmpPath, pathLenOff, len);
tmpPathLen = newPathLen;
}
@@ -862,7 +912,7 @@ public class KeycardApplet extends Applet {
short dataOff = (short) (scratchOff + Crypto.KEY_DERIVATION_SCRATCH_SIZE);
short pubKeyOff = (short) (dataOff + masterPrivate.getS(apduBuffer, dataOff));
pubKeyOff = Util.arrayCopyNonAtomic(masterChainCode, (short) 0, apduBuffer, pubKeyOff, CHAIN_CODE_SIZE);
pubKeyOff = Util.arrayCopyNonAtomic(chainCode, (short) 0, apduBuffer, pubKeyOff, CHAIN_CODE_SIZE);
if (!crypto.bip32IsHardened(tmpPath, (short) 0)) {
masterPublic.getW(apduBuffer, pubKeyOff);
@@ -966,6 +1016,26 @@ public class KeycardApplet extends Applet {
return (short) ((short)((short) 0x4000 >>> (short) (amount - 1)) | tmp);
}
/**
* Clear all keys and erases the key UID.
*/
private void clearKeys() {
keyPathLen = 0;
pinlessPathLen = 0;
tmpPathLen = 0;
isExtended = false;
masterPrivate.clearKey();
masterPublic.clearKey();
resetCurveParameters();
Util.arrayFillNonAtomic(masterChainCode, (short) 0, (short) masterChainCode.length, (byte) 0);
Util.arrayFillNonAtomic(altChainCode, (short) 0, (short) altChainCode.length, (byte) 0);
Util.arrayFillNonAtomic(keyPath, (short) 0, (short) keyPath.length, (byte) 0);
Util.arrayFillNonAtomic(pinlessPath, (short) 0, (short) pinlessPath.length, (byte) 0);
Util.arrayFillNonAtomic(tmpPath, (short) 0, (short) tmpPath.length, (byte) 0);
Util.arrayFillNonAtomic(derivationOutput, (short) 0, (short) derivationOutput.length, (byte) 0);
Util.arrayFillNonAtomic(keyUID, (short) 0, (short) keyUID.length, (byte) 0);
}
/**
* Processes the REMOVE KEY command. Removes the master key and all derived keys. Secure Channel and PIN
* authentication are required.
@@ -980,15 +1050,28 @@ public class KeycardApplet extends Applet {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}
keyPathLen = 0;
pinlessPathLen = 0;
isExtended = false;
masterPrivate.clearKey();
masterPublic.clearKey();
resetCurveParameters();
Util.arrayFillNonAtomic(masterChainCode, (short) 0, (short) masterChainCode.length, (byte) 0);
Util.arrayFillNonAtomic(keyPath, (short) 0, (short) keyPath.length, (byte) 0);
Util.arrayFillNonAtomic(pinlessPath, (short) 0, (short) pinlessPath.length, (byte) 0);
clearKeys();
}
private void factoryReset(APDU apdu) {
byte[] apduBuffer = apdu.getBuffer();
if ((apduBuffer[OFFSET_P1] != FACTORY_RESET_P1_MAGIC) || (apduBuffer[ISO7816.OFFSET_P2] != FACTORY_RESET_P2_MAGIC)) {
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
}
clearKeys();
pin = null;
mainPIN = null;
altPIN = null;
puk = null;
secureChannel = null;
crypto.random.generateData(uid, (short) 0, UID_LENGTH);
Util.arrayFillNonAtomic(data, (short) 0, (short) data.length, (byte) 0);
if (JCSystem.isObjectDeletionSupported()) {
JCSystem.requestObjectDeletion();
}
}
/**
+104 -12
View File
@@ -128,6 +128,10 @@ public class KeycardTest {
capabilities.add("ndef");
}
if (info.hasFactoryResetCapability()) {
capabilities.add("factoryReset");
}
CapabilityCondition.availableCapabilities = capabilities;
}
@@ -210,6 +214,11 @@ public class KeycardTest {
usbManager.start();
}
private static void initCard(KeycardCommandSet cmdSet) throws Exception {
assertEquals(0x9000, cmdSet.init("000000", "024680", "012345678901", sharedSecret, (byte) 3, (byte) 5).getSw());
cmdSet.select().checkOK();
}
private static void initIfNeeded() throws Exception {
KeyPair identKeyPair = Certificate.generateIdentKeyPair();
Certificate cert = Certificate.createCertificate(caKeyPair, identKeyPair);
@@ -222,11 +231,10 @@ public class KeycardTest {
initCapabilities(cmdSet.getApplicationInfo());
sharedSecret = cmdSet.pairingPasswordToSecret(System.getProperty("im.status.keycard.test.pairing", "KeycardTest"));
sharedSecret = cmdSet.pairingPasswordToSecret(System.getProperty("im.status.keycard.test.pairing", "KeycardDefaultPairing"));
if (!cmdSet.getApplicationInfo().isInitializedCard()) {
assertEquals(0x9000, cmdSet.init("000000", "123456789012", sharedSecret).getSw());
cmdSet.select().checkOK();
initCard(cmdSet);
initCapabilities(cmdSet.getApplicationInfo());
}
}
@@ -481,7 +489,7 @@ public class KeycardTest {
}
// Wrong P1
response = cmdSet.unpair((byte) 5);
response = cmdSet.unpair(KeycardApplet.PAIRING_MAX_CLIENT_COUNT);
assertEquals(0x6A86, response.getSw());
// Unpair spare keyset
@@ -567,6 +575,10 @@ public class KeycardTest {
response = cmdSet.verifyPIN("000000");
assertEquals(0x9000, response.getSw());
// Alt PIN
response = cmdSet.verifyPIN("024680");
assertEquals(0x9000, response.getSw());
// Check max retry counter
response = cmdSet.verifyPIN("123456");
assertEquals(0x63C2, response.getSw());
@@ -580,8 +592,11 @@ public class KeycardTest {
response = cmdSet.verifyPIN("000000");
assertEquals(0x63C0, response.getSw());
response = cmdSet.verifyPIN("024680");
assertEquals(0x63C0, response.getSw());
// Unblock PIN to make further tests possible
response = cmdSet.unblockPIN("123456789012", "000000");
response = cmdSet.unblockPIN("012345678901", "024680");
assertEquals(0x9000, response.getSw());
}
@@ -595,7 +610,7 @@ public class KeycardTest {
cmdSet.autoOpenSecureChannel();
// Security condition violation: PIN n ot verified
// Security condition violation: PIN not verified
response = cmdSet.changePIN(KeycardApplet.CHANGE_PIN_P1_USER_PIN, "123456");
assertEquals(0x6985, response.getSw());
@@ -667,7 +682,7 @@ public class KeycardTest {
assertEquals(0x9000, response.getSw());
// Reset PUK
response = cmdSet.changePIN(KeycardApplet.CHANGE_PIN_P1_PUK, "123456789012");
response = cmdSet.changePIN(KeycardApplet.CHANGE_PIN_P1_PUK, "012345678901");
assertEquals(0x9000, response.getSw());
// Change the pairing secret
@@ -687,6 +702,26 @@ public class KeycardTest {
response = cmdSet.changePIN(KeycardApplet.CHANGE_PIN_P1_PAIRING_SECRET, sharedSecret);
assertEquals(0x9000, response.getSw());
// Alt PIN
response = cmdSet.verifyPIN("024680");
assertEquals(0x9000, response.getSw());
response = cmdSet.changePIN(KeycardApplet.CHANGE_PIN_P1_USER_PIN, "123456");
assertEquals(0x9000, response.getSw());
resetAndSelectAndOpenSC();
response = cmdSet.verifyPIN("123456");
assertEquals(0x9000, response.getSw());
response = cmdSet.changePIN(KeycardApplet.CHANGE_PIN_P1_USER_PIN, "024680");
assertEquals(0x9000, response.getSw());
resetAndSelectAndOpenSC();
response = cmdSet.verifyPIN("000000");
assertEquals(0x9000, response.getSw());
}
@Test
@@ -694,13 +729,13 @@ public class KeycardTest {
@Capabilities("credentialsManagement")
void unblockPinTest() throws Exception {
// Security condition violation: SecureChannel not open
APDUResponse response = cmdSet.unblockPIN("123456789012", "000000");
APDUResponse response = cmdSet.unblockPIN("012345678901", "000000");
assertEquals(0x6985, response.getSw());
cmdSet.autoOpenSecureChannel();
// Condition violation: PIN is not blocked
response = cmdSet.unblockPIN("123456789012", "000000");
response = cmdSet.unblockPIN("012345678901", "000000");
assertEquals(0x6985, response.getSw());
// Block the PIN
@@ -725,7 +760,7 @@ public class KeycardTest {
assertEquals(0x63C4, response.getSw());
// Correct PUK
response = cmdSet.unblockPIN("123456789012", "654321");
response = cmdSet.unblockPIN("012345678901", "654321");
assertEquals(0x9000, response.getSw());
// Check that PIN has been changed and unblocked
@@ -905,6 +940,46 @@ public class KeycardTest {
assertEquals(0, info.getKeyUID().length);
}
@Test
@DisplayName("FACTORY RESET command")
@Capabilities("factoryReset")
void factoryResetTest() throws Exception {
KeyPairGenerator g = keypairGenerator();
KeyPair keyPair = g.generateKeyPair();
// Invalid P1 P2
APDUResponse response = sdkChannel.send(new APDUCommand(0x80, KeycardApplet.INS_FACTORY_RESET, 0, 0, new byte[0]));
assertEquals(0x6a86, response.getSw());
// Good case
response = cmdSet.factoryReset();
assertEquals(0x9000, response.getSw());
response = cmdSet.getStatus(KeycardCommandSet.GET_STATUS_P1_KEY_PATH);
assertEquals(0x6d00, response.getSw());
response = cmdSet.select();
assertEquals(0x9000, response.getSw());
assertFalse(cmdSet.getApplicationInfo().isInitializedCard());
initCard(cmdSet);
response = cmdSet.select();
assertEquals(0x9000, response.getSw());
if (cmdSet.getApplicationInfo().hasSecureChannelCapability()) {
cmdSet.autoPair(sharedSecret);
cmdSet.autoOpenSecureChannel();
}
if (cmdSet.getApplicationInfo().hasCredentialsManagementCapability()) {
response = cmdSet.verifyPIN("000000");
assertEquals(0x9000, response.getSw());
}
assertFalse(cmdSet.getKeyInitializationStatus());
}
@Test
@DisplayName("GENERATE KEY command")
@Capabilities("keyManagement")
@@ -1100,6 +1175,13 @@ public class KeycardTest {
response = cmdSet.signPinless(hash);
assertEquals(0x6A88, response.getSw());
// Alt PIN
response = cmdSet.verifyPIN("024680");
assertEquals(0x9000, response.getSw());
response = cmdSet.signWithPath(hash, updatedPath, false);
verifySignResp(data, response);
}
private void verifySignResp(byte[] data, APDUResponse response) throws Exception {
@@ -1305,10 +1387,10 @@ public class KeycardTest {
verifyExportedKey(keyTemplate, keyPair, chainCode, new int[] { 0x8000002b, 0x8000003c, 0x8000062d, 0x00000000, 0x00000000 }, false, false);
// Export extended public
response = cmdSet.exportExtendedPublicKey(new byte[] {(byte) 0x80, 0x00, 0x00, 0x2B, (byte) 0x80, 0x00, 0x00, 0x3C, (byte) 0x80, 0x00, 0x06, 0x2D, (byte) 0x00, 0x00, 0x00, 0x00, (byte) 0x00, 0x00, 0x00, 0x00}, KeycardApplet.DERIVE_P1_SOURCE_MASTER);
response = cmdSet.exportKey(new byte[] {(byte) 0x80, 0x00, 0x00, 0x2B, (byte) 0x80, 0x00, 0x00, 0x3C, (byte) 0x80, 0x00, 0x06, 0x2D, (byte) 0x00, 0x00, 0x00, 0x00, (byte) 0x00, 0x00, 0x00, 0x00}, KeycardApplet.DERIVE_P1_SOURCE_MASTER, false, KeycardCommandSet.EXPORT_KEY_P2_EXTENDED_PUBLIC);
assertEquals(0x6985, response.getSw());
response = cmdSet.exportExtendedPublicKey(new byte[] {(byte) 0x80, 0x00, 0x00, 0x2B, (byte) 0x80, 0x00, 0x00, 0x3C, (byte) 0x80, 0x00, 0x06, 0x2c, (byte) 0x00, 0x00, 0x00, 0x00}, KeycardApplet.DERIVE_P1_SOURCE_MASTER);
response = cmdSet.exportKey(new byte[] {(byte) 0x80, 0x00, 0x00, 0x2B, (byte) 0x80, 0x00, 0x00, 0x3C, (byte) 0x80, 0x00, 0x06, 0x2c, (byte) 0x00, 0x00, 0x00, 0x00}, KeycardApplet.DERIVE_P1_SOURCE_MASTER, false, KeycardCommandSet.EXPORT_KEY_P2_EXTENDED_PUBLIC);
assertEquals(0x9000, response.getSw());
keyTemplate = response.getData();
verifyExportedKey(keyTemplate, keyPair, chainCode, new int[] { 0x8000002b, 0x8000003c, 0x8000062c, 0x00000000 }, true, true);
@@ -1316,6 +1398,16 @@ public class KeycardTest {
// Reset
response = cmdSet.deriveKey(new byte[0], KeycardApplet.DERIVE_P1_SOURCE_MASTER);
assertEquals(0x9000, response.getSw());
// Alt PIN
response = cmdSet.verifyPIN("024680");
assertEquals(0x9000, response.getSw());
response = cmdSet.exportKey(new byte[] {(byte) 0x80, 0x00, 0x00, 0x2B, (byte) 0x80, 0x00, 0x00, 0x3C, (byte) 0x80, 0x00, 0x06, 0x2c, (byte) 0x00, 0x00, 0x00, 0x00}, KeycardApplet.DERIVE_P1_SOURCE_MASTER, false, KeycardCommandSet.EXPORT_KEY_P2_EXTENDED_PUBLIC);
assertEquals(0x9000, response.getSw());
keyTemplate = response.getData();
verifyExportedKey(keyTemplate, keyPair, sha256(chainCode), new int[] { 0x8000002b, 0x8000003c, 0x8000062c, 0x00000000 }, true, true);
}
@Test
@@ -13,17 +13,12 @@ import java.security.interfaces.ECPrivateKey;
public class TestKeycardCommandSet extends KeycardCommandSet {
private CardChannel ac;
private TestSecureChannelSession sc;
public TestKeycardCommandSet(CardChannel apduChannel) {
super(apduChannel);
ac = apduChannel;
}
public void setSecureChannel(TestSecureChannelSession secureChannel) {
super.setSecureChannel(secureChannel);
sc = secureChannel;
}
/**
@@ -83,11 +78,6 @@ public class TestKeycardCommandSet extends KeycardCommandSet {
return loadKey(data, LOAD_KEY_P1_SEED);
}
public APDUResponse exportExtendedPublicKey(byte[] keyPath, byte source) throws IOException {
APDUCommand exportKey = sc.protectedCommand(0x80, 0xc2, (source | 0x01), 2, keyPath);
return sc.transmit(ac, exportKey);
}
/**
* Sends a GET STATUS APDU to retrieve the APPLICATION STATUS template and reads the byte indicating key initialization
* status