Compare commits
15
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
793c8eedc9 | ||
|
|
146c049b45 | ||
|
|
86faab3f26 | ||
|
|
44adec536e | ||
|
|
e79d84b9b4 | ||
|
|
039964e8d0 | ||
|
|
4a04bdacec | ||
|
|
b21fef0fec | ||
|
|
40a4168a17 | ||
|
|
a2595b35e7 | ||
|
|
4956fcc1ee | ||
|
|
509abc291b | ||
|
|
b55764bdd7 | ||
|
|
8ac2e88383 | ||
|
|
5bc653c012 |
@@ -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
@@ -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")
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user