20 Commits
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
Michele Balistreri e71c9d7f49 remove debug code 2022-11-07 12:15:21 +01:00
Michele Balistreri a3aba74ffa move chain code outside pubkey 2022-11-04 10:33:05 +03:00
Michele Balistreri f3e834226f allow export of extended public key 2022-11-03 17:50:18 +03:00
Michele Balistreri e6dc3b8189 make key derivation lazy 2022-11-02 10:55:31 +03:00
Michele Balistreri 9677aefcbb Merge pull request #85 from status-im/ident
implement IDENTIFY CARD
2022-11-02 07:25:18 +01:00
7 changed files with 385 additions and 297 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;
}
/**
+228 -247
View File
@@ -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;
@@ -61,6 +62,7 @@ public class KeycardApplet extends Applet {
static final byte DERIVE_P1_SOURCE_MASTER = (byte) 0x00;
static final byte DERIVE_P1_SOURCE_PARENT = (byte) 0x40;
static final byte DERIVE_P1_SOURCE_CURRENT = (byte) 0x80;
static final byte DERIVE_P1_SOURCE_PINLESS = (byte) 0xC0;
static final byte DERIVE_P1_SOURCE_MASK = (byte) 0xC0;
static final byte GENERATE_MNEMONIC_P1_CS_MIN = 4;
@@ -78,11 +80,15 @@ public class KeycardApplet extends Applet {
static final byte EXPORT_KEY_P2_PRIVATE_AND_PUBLIC = 0x00;
static final byte EXPORT_KEY_P2_PUBLIC_ONLY = 0x01;
static final byte EXPORT_KEY_P2_EXTENDED_PUBLIC = 0x02;
static final byte STORE_DATA_P1_PUBLIC = 0x00;
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;
@@ -103,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;
@@ -116,18 +125,12 @@ public class KeycardApplet extends Applet {
private ECPublicKey masterPublic;
private ECPrivateKey masterPrivate;
private byte[] masterChainCode;
private byte[] altChainCode;
private byte[] chainCode;
private boolean isExtended;
private ECPublicKey parentPublicKey;
private ECPrivateKey parentPrivateKey;
private byte[] parentChainCode;
private ECPublicKey publicKey;
private ECPrivateKey privateKey;
private byte[] chainCode;
private ECPublicKey pinlessPublicKey;
private ECPrivateKey pinlessPrivateKey;
private byte[] tmpPath;
private short tmpPathLen;
private byte[] keyPath;
private short keyPathLen;
@@ -178,21 +181,13 @@ 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);
parentPublicKey = (ECPublicKey) KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PUBLIC, SECP256k1.SECP256K1_KEY_SIZE, false);
parentPrivateKey = (ECPrivateKey) KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PRIVATE, SECP256k1.SECP256K1_KEY_SIZE, false);
publicKey = (ECPublicKey) KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PUBLIC, SECP256k1.SECP256K1_KEY_SIZE, false);
privateKey = (ECPrivateKey) KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PRIVATE, SECP256k1.SECP256K1_KEY_SIZE, false);
pinlessPublicKey = (ECPublicKey) KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PUBLIC, SECP256k1.SECP256K1_KEY_SIZE, false);
pinlessPrivateKey = (ECPrivateKey) KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PRIVATE, SECP256k1.SECP256K1_KEY_SIZE, false);
masterChainCode = new byte[CHAIN_CODE_SIZE];
parentChainCode = new byte[CHAIN_CODE_SIZE];
chainCode = 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];
tmpPath = JCSystem.makeTransientByteArray((short)(KEY_PATH_MAX_DEPTH * 4), JCSystem.CLEAR_ON_RESET);
keyUID = new byte[KEY_UID_LENGTH];
@@ -292,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;
@@ -335,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;
@@ -357,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 {
@@ -401,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();
@@ -411,7 +418,7 @@ public class KeycardApplet extends Applet {
apduBuffer[off++] = TLV_APPLICATION_INFO_TEMPLATE;
if (privateKey.isInitialized()) {
if (masterPrivate.isInitialized()) {
apduBuffer[off++] = (byte) 0x81;
}
@@ -437,7 +444,7 @@ public class KeycardApplet extends Applet {
apduBuffer[off++] = secureChannel.getRemainingPairingSlots();
apduBuffer[off++] = TLV_KEY_UID;
if (privateKey.isInitialized()) {
if (masterPrivate.isInitialized()) {
apduBuffer[off++] = KEY_UID_LENGTH;
Util.arrayCopyNonAtomic(keyUID, (short) 0, apduBuffer, off, KEY_UID_LENGTH);
off += KEY_UID_LENGTH;
@@ -497,7 +504,7 @@ public class KeycardApplet extends Applet {
apduBuffer[off++] = puk.getTriesRemaining();
apduBuffer[off++] = TLV_BOOL;
apduBuffer[off++] = 1;
apduBuffer[off++] = privateKey.isInitialized() ? (byte) 0xFF : (byte) 0x00;
apduBuffer[off++] = masterPrivate.isInitialized() ? (byte) 0xFF : (byte) 0x00;
return (short) (off - SecureChannel.SC_OUT_OFFSET);
}
@@ -532,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;
}
}
@@ -631,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();
@@ -678,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);
@@ -689,8 +717,6 @@ public class KeycardApplet extends Applet {
* manipulation has happened to be sure that the state is always consistent.
*/
private void resetKeyStatus() {
parentPrivateKey.clearKey();
SECP256k1.setCurveParameters(parentPrivateKey);
keyPathLen = 0;
}
@@ -721,12 +747,10 @@ public class KeycardApplet extends Applet {
isExtended = (apduBuffer[chainOffset] == TLV_CHAIN_CODE);
masterPrivate.setS(apduBuffer, (short) (privOffset + 2), apduBuffer[(short) (privOffset + 1)]);
privateKey.setS(apduBuffer, (short) (privOffset + 2), apduBuffer[(short) (privOffset + 1)]);
if (isExtended) {
if (apduBuffer[(short) (chainOffset + 1)] == CHAIN_CODE_SIZE) {
Util.arrayCopy(apduBuffer, (short) (chainOffset + 2), masterChainCode, (short) 0, apduBuffer[(short) (chainOffset + 1)]);
Util.arrayCopy(apduBuffer, (short) (chainOffset + 2), chainCode, (short) 0, apduBuffer[(short) (chainOffset + 1)]);
} else {
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
}
@@ -743,7 +767,6 @@ public class KeycardApplet extends Applet {
}
masterPublic.setW(apduBuffer, pubOffset, pubLen);
publicKey.setW(apduBuffer, pubOffset, pubLen);
} catch (CryptoException e) {
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
}
@@ -771,39 +794,20 @@ public class KeycardApplet extends Applet {
isExtended = true;
masterPrivate.setS(apduBuffer, (short) ISO7816.OFFSET_CDATA, CHAIN_CODE_SIZE);
privateKey.setS(apduBuffer, (short) ISO7816.OFFSET_CDATA, CHAIN_CODE_SIZE);
Util.arrayCopy(apduBuffer, (short) (ISO7816.OFFSET_CDATA + CHAIN_CODE_SIZE), masterChainCode, (short) 0, CHAIN_CODE_SIZE);
Util.arrayCopy(apduBuffer, (short) (ISO7816.OFFSET_CDATA + CHAIN_CODE_SIZE), chainCode, (short) 0, CHAIN_CODE_SIZE);
short pubLen = secp256k1.derivePublicKey(masterPrivate, apduBuffer, (short) 0);
masterPublic.setW(apduBuffer, (short) 0, pubLen);
publicKey.setW(apduBuffer, (short) 0, pubLen);
resetKeyStatus();
JCSystem.commitTransaction();
}
/**
* Processes the DERIVE KEY command. Requires a secure channel to be already open. Unless a PIN-less path exists, t
* the PIN must be verified as well. The master key must be already loaded and have a chain code. In the happy case
* this method is quite straightforward, since it takes a sequence of 32-bit big-endian integers and perform key
* derivations, updating the current key path accordingly.
*
* However, since public key derivation might not be supported on card this method also supports the so called
* assisted derivation scheme. In this scheme the client first sends a single 32-bit big-endian integer. The cards
* derives the new private key and by taking advantage the EC-DH algorithm returns the X of the public key along with
* a signature of the SHA-256 hash of a fixed message ("STATUS KEY DERIVATION" in ASCII). The client must then
* calculate the two possible Y and try to verify the signature with each of the 2 candidate public keys. The public
* key which correctly verifies the signature is the real one and must be uploaded (as an uncompressed point) through
* this command again. At this point the current key path is updated and the derived key can be used for signing.
*
* In all cases transactions are used to make sure that the current key is always complete (private, chain and public
* components are coherent) and the key path matches the actual status of the card. This makes recovery from a sudden
* power loss easy.
*
* When the reset flag is set and the data is empty, the assisted key derivation flag is ignored, since in this case
* no derivation is done and the master key becomes the current key.
* Processes the DERIVE KEY command. Requires a secure channel to be already open and the PIN must be verified as well.
* The master key must be already loaded and have a chain code. This function only updates the current path but does
* not actually perform derivation, which is delayed to exporting/signing.
*
* @param apdu the JCRE-owned APDU object.
*/
@@ -815,67 +819,58 @@ public class KeycardApplet extends Applet {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}
doDerive(apduBuffer, (short) 0, len, apduBuffer[OFFSET_P1], true);
updateDerivationPath(apduBuffer, (short) 0, len, apduBuffer[OFFSET_P1]);
commitTmpPath();
}
/**
* Internal derivation function, called by DERIVE KEY and EXPORT KEY
* @param apduBuffer the APDU buffer
* @param off the offset in the APDU buffer relative to the data field
* Updates the derivation path for a subsequent EXPORT KEY/SIGN APDU. Optionally stores the result in the current path.
*
* @param path the path
* @param off the offset in the path
* @param len the len of the path
* @param source derivation source
* @param makeCurrent whether the results should be saved or not
*/
private void doDerive(byte[] apduBuffer, short off, short len, byte source, boolean makeCurrent) {
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;
short pathLenOff;
ECPublicKey sourcePub;
ECPrivateKey sourcePriv;
byte[] sourceChain;
byte[] srcKeyPath = keyPath;
switch (source) {
case DERIVE_P1_SOURCE_MASTER:
if (len == 0) {
resetToMaster(apduBuffer);
return;
}
newPathLen = len;
sourcePriv = masterPrivate;
sourcePub = masterPublic;
sourceChain = masterChainCode;
pathLenOff = 0;
break;
case DERIVE_P1_SOURCE_PARENT:
if (!parentPrivateKey.isInitialized()) {
if (keyPathLen < 4) {
ISOException.throwIt(ISO7816.SW_WRONG_P1P2);
}
if (len == 0) {
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
}
newPathLen = (short) (keyPathLen + len - 4);
sourcePriv = parentPrivateKey;
sourcePub = parentPublicKey;
sourceChain = parentChainCode;
pathLenOff = (short) (keyPathLen - 4);
break;
case DERIVE_P1_SOURCE_CURRENT:
if (len == 0) {
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
}
newPathLen = (short) (keyPathLen + len);
sourcePriv = privateKey;
sourcePub = publicKey;
sourceChain = chainCode;
pathLenOff = keyPathLen;
break;
case DERIVE_P1_SOURCE_PINLESS:
if (len != 0) {
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
}
srcKeyPath = pinlessPath;
newPathLen = pinlessPathLen;
pathLenOff = pinlessPathLen;
break;
default:
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
return;
@@ -886,70 +881,60 @@ public class KeycardApplet extends Applet {
}
short pathOff = (short) (ISO7816.OFFSET_CDATA + off);
short scratchOff = (short) (pathOff + len);
Util.arrayCopyNonAtomic(srcKeyPath, (short) 0, tmpPath, (short) 0, pathLenOff);
Util.arrayCopyNonAtomic(path, pathOff, tmpPath, pathLenOff, len);
tmpPathLen = newPathLen;
}
/**
* Makes the tmp path the current path.
*/
void commitTmpPath() {
JCSystem.beginTransaction();
Util.arrayCopy(tmpPath, (short) 0, keyPath, (short) 0, tmpPathLen);
keyPathLen = tmpPathLen;
JCSystem.commitTransaction();
}
/**
* Internal derivation function, called by DERIVE KEY and EXPORT KEY
* @param apduBuffer the APDU buffer
* @param off the offset in the APDU buffer relative to the data field
*/
private void doDerive(byte[] apduBuffer, short off) {
if (tmpPathLen == 0) {
masterPrivate.getS(derivationOutput, (short) 0);
return;
}
short scratchOff = (short) (ISO7816.OFFSET_CDATA + off);
short dataOff = (short) (scratchOff + Crypto.KEY_DERIVATION_SCRATCH_SIZE);
short pubKeyOff = (short) (dataOff + sourcePriv.getS(apduBuffer, dataOff));
pubKeyOff = Util.arrayCopyNonAtomic(sourceChain, (short) 0, apduBuffer, pubKeyOff, CHAIN_CODE_SIZE);
short pubKeyOff = (short) (dataOff + masterPrivate.getS(apduBuffer, dataOff));
pubKeyOff = Util.arrayCopyNonAtomic(chainCode, (short) 0, apduBuffer, pubKeyOff, CHAIN_CODE_SIZE);
if (!crypto.bip32IsHardened(apduBuffer, ISO7816.OFFSET_CDATA)) {
sourcePub.getW(apduBuffer, pubKeyOff);
if (!crypto.bip32IsHardened(tmpPath, (short) 0)) {
masterPublic.getW(apduBuffer, pubKeyOff);
} else {
apduBuffer[pubKeyOff] = 0;
}
for (short i = pathOff; i < scratchOff; i += 4) {
if (i > pathOff) {
for (short i = 0; i < tmpPathLen; i += 4) {
if (i > 0) {
Util.arrayCopyNonAtomic(derivationOutput, (short) 0, apduBuffer, dataOff, (short) (Crypto.KEY_SECRET_SIZE + CHAIN_CODE_SIZE));
if (!crypto.bip32IsHardened(apduBuffer, i)) {
if (!crypto.bip32IsHardened(tmpPath, i)) {
secp256k1.derivePublicKey(apduBuffer, dataOff, apduBuffer, pubKeyOff);
} else {
apduBuffer[pubKeyOff] = 0;
}
}
if (!crypto.bip32CKDPriv(apduBuffer, i, apduBuffer, scratchOff, apduBuffer, dataOff, derivationOutput, (short) 0)) {
if (!crypto.bip32CKDPriv(tmpPath, i, apduBuffer, scratchOff, apduBuffer, dataOff, derivationOutput, (short) 0)) {
ISOException.throwIt(ISO7816.SW_DATA_INVALID);
}
}
if (makeCurrent) {
JCSystem.beginTransaction();
parentPrivateKey.setS(apduBuffer, dataOff, Crypto.KEY_SECRET_SIZE);
Util.arrayCopy(apduBuffer, (short)(dataOff + Crypto.KEY_SECRET_SIZE), parentChainCode, (short) 0, CHAIN_CODE_SIZE);
if (apduBuffer[pubKeyOff] == 0x04) {
parentPublicKey.setW(apduBuffer, pubKeyOff, Crypto.KEY_PUB_SIZE);
} else {
secp256k1.derivePublicKey(parentPrivateKey, apduBuffer, scratchOff);
parentPublicKey.setW(apduBuffer, scratchOff, Crypto.KEY_PUB_SIZE);
}
privateKey.setS(derivationOutput, (short) 0, Crypto.KEY_SECRET_SIZE);
Util.arrayCopy(derivationOutput, Crypto.KEY_SECRET_SIZE, chainCode, (short) 0, CHAIN_CODE_SIZE);
secp256k1.derivePublicKey(privateKey, apduBuffer, scratchOff);
publicKey.setW(apduBuffer, scratchOff, Crypto.KEY_PUB_SIZE);
Util.arrayCopy(apduBuffer, pathOff, keyPath, pathLenOff, len);
keyPathLen = newPathLen;
JCSystem.commitTransaction();
}
}
/**
* Resets to master key
*
* @param apduBuffer the APDU buffer
*/
private void resetToMaster(byte[] apduBuffer) {
resetKeyStatus();
masterPrivate.getS(apduBuffer, ISO7816.OFFSET_CDATA);
privateKey.setS(apduBuffer, ISO7816.OFFSET_CDATA, Crypto.KEY_SECRET_SIZE);
masterPublic.getW(apduBuffer, ISO7816.OFFSET_CDATA);
publicKey.setW(apduBuffer, ISO7816.OFFSET_CDATA, Crypto.KEY_PUB_SIZE);
Util.arrayCopyNonAtomic(masterChainCode, (short) 0, chainCode, (short) 0, CHAIN_CODE_SIZE);
}
/**
@@ -1031,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.
@@ -1045,23 +1050,28 @@ public class KeycardApplet extends Applet {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}
keyPathLen = 0;
pinlessPathLen = 0;
isExtended = false;
privateKey.clearKey();
publicKey.clearKey();
masterPrivate.clearKey();
masterPublic.clearKey();
parentPrivateKey.clearKey();
parentPublicKey.clearKey();
pinlessPrivateKey.clearKey();
pinlessPublicKey.clearKey();
resetCurveParameters();
Util.arrayFillNonAtomic(chainCode, (short) 0, (short) chainCode.length, (byte) 0);
Util.arrayFillNonAtomic(parentChainCode, (short) 0, (short) parentChainCode.length, (byte) 0);
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();
}
}
/**
@@ -1100,32 +1110,21 @@ public class KeycardApplet extends Applet {
private void sign(APDU apdu) {
byte[] apduBuffer = apdu.getBuffer();
boolean usePinless = false;
boolean derive = false;
boolean makeCurrent = false;
ECPrivateKey signingKey;
ECPublicKey outputKey;
byte derivationSource = (byte) (apduBuffer[OFFSET_P1] & DERIVE_P1_SOURCE_MASK);
switch((byte) (apduBuffer[OFFSET_P1] & ~DERIVE_P1_SOURCE_MASK)) {
case SIGN_P1_CURRENT_KEY:
signingKey = privateKey;
outputKey = publicKey;
derivationSource = DERIVE_P1_SOURCE_CURRENT;
break;
case SIGN_P1_DERIVE:
signingKey = secp256k1.tmpECPrivateKey;
outputKey = null;
derive = true;
break;
case SIGN_P1_DERIVE_AND_MAKE_CURRENT:
signingKey = privateKey;
outputKey = publicKey;
derive = true;
makeCurrent = true;
break;
case SIGN_P1_PINLESS:
usePinless = true;
signingKey = pinlessPrivateKey;
outputKey = pinlessPublicKey;
derivationSource = DERIVE_P1_SOURCE_PINLESS;
break;
default:
ISOException.throwIt(ISO7816.SW_WRONG_P1P2);
@@ -1144,39 +1143,29 @@ public class KeycardApplet extends Applet {
ISOException.throwIt(SW_REFERENCED_DATA_NOT_FOUND);
}
if (!((pin.isValidated() || usePinless || isPinless()) && privateKey.isInitialized())) {
if (len < MessageDigest.LENGTH_SHA_256) {
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
}
short pathLen = (short) (len - MessageDigest.LENGTH_SHA_256);
updateDerivationPath(apduBuffer, MessageDigest.LENGTH_SHA_256, pathLen, derivationSource);
if (!((pin.isValidated() || usePinless || isPinless()) && masterPrivate.isInitialized())) {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}
if (derive) {
short pathLen = (short) (len - MessageDigest.LENGTH_SHA_256);
if (pathLen <= 0) {
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
}
byte derivationSource = (byte) (apduBuffer[OFFSET_P1] & DERIVE_P1_SOURCE_MASK);
doDerive(apduBuffer, MessageDigest.LENGTH_SHA_256, pathLen, derivationSource, makeCurrent);
} else {
if (len != MessageDigest.LENGTH_SHA_256) {
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
}
}
doDerive(apduBuffer, MessageDigest.LENGTH_SHA_256);
apduBuffer[SecureChannel.SC_OUT_OFFSET] = TLV_SIGNATURE_TEMPLATE;
apduBuffer[(short)(SecureChannel.SC_OUT_OFFSET + 3)] = TLV_PUB_KEY;
short outLen = apduBuffer[(short)(SecureChannel.SC_OUT_OFFSET + 4)] = Crypto.KEY_PUB_SIZE;
if (outputKey != null) {
outputKey.getW(apduBuffer, (short) (SecureChannel.SC_OUT_OFFSET + 5));
} else {
secp256k1.derivePublicKey(derivationOutput, (short) 0, apduBuffer, (short) (SecureChannel.SC_OUT_OFFSET + 5));
}
secp256k1.derivePublicKey(derivationOutput, (short) 0, apduBuffer, (short) (SecureChannel.SC_OUT_OFFSET + 5));
outLen += 5;
short sigOff = (short) (SecureChannel.SC_OUT_OFFSET + outLen);
signature.init(signingKey, Signature.MODE_SIGN);
signature.init(secp256k1.tmpECPrivateKey, Signature.MODE_SIGN);
outLen += signature.signPreComputedHash(apduBuffer, ISO7816.OFFSET_CDATA, MessageDigest.LENGTH_SHA_256, apduBuffer, sigOff);
outLen += crypto.fixS(apduBuffer, sigOff);
@@ -1184,6 +1173,10 @@ public class KeycardApplet extends Applet {
apduBuffer[(short)(SecureChannel.SC_OUT_OFFSET + 1)] = (byte) 0x81;
apduBuffer[(short)(SecureChannel.SC_OUT_OFFSET + 2)] = (byte) (outLen - 3);
if (makeCurrent) {
commitTmpPath();
}
if (secureChannel.isOpen()) {
secureChannel.respond(apdu, outLen, ISO7816.SW_NO_ERROR);
} else {
@@ -1214,14 +1207,6 @@ public class KeycardApplet extends Applet {
JCSystem.beginTransaction();
pinlessPathLen = len;
Util.arrayCopy(apduBuffer, ISO7816.OFFSET_CDATA, pinlessPath, (short) 0, len);
if (pinlessPathLen > 0) {
doDerive(apduBuffer, (short) 0, len, DERIVE_P1_SOURCE_MASTER, false);
pinlessPrivateKey.setS(derivationOutput, (short) 0, Crypto.KEY_SECRET_SIZE);
secp256k1.derivePublicKey(pinlessPrivateKey, apduBuffer, (short) 0);
pinlessPublicKey.setW(apduBuffer, (short) 0, Crypto.KEY_PUB_SIZE);
}
JCSystem.commitTransaction();
}
@@ -1234,57 +1219,57 @@ public class KeycardApplet extends Applet {
byte[] apduBuffer = apdu.getBuffer();
short dataLen = secureChannel.preprocessAPDU(apduBuffer);
if (!pin.isValidated() || !privateKey.isInitialized()) {
if (!pin.isValidated() || !masterPrivate.isInitialized()) {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}
boolean publicOnly;
boolean extendedPublic;
switch (apduBuffer[ISO7816.OFFSET_P2]) {
case EXPORT_KEY_P2_PRIVATE_AND_PUBLIC:
publicOnly = false;
extendedPublic = false;
break;
case EXPORT_KEY_P2_PUBLIC_ONLY:
publicOnly = true;
extendedPublic = false;
break;
case EXPORT_KEY_P2_EXTENDED_PUBLIC:
publicOnly = true;
extendedPublic = true;
break;
default:
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
return;
}
byte[] exportPath = keyPath;
short exportPathOff = (short) 0;
short exportPathLen = keyPathLen;
boolean derive = false;
boolean makeCurrent = false;
byte derivationSource = (byte) (apduBuffer[OFFSET_P1] & DERIVE_P1_SOURCE_MASK);
switch ((byte) (apduBuffer[OFFSET_P1] & ~DERIVE_P1_SOURCE_MASK)) {
case EXPORT_KEY_P1_CURRENT:
derivationSource = DERIVE_P1_SOURCE_CURRENT;
break;
case EXPORT_KEY_P1_DERIVE:
break;
case EXPORT_KEY_P1_DERIVE_AND_MAKE_CURRENT:
makeCurrent = true;
case EXPORT_KEY_P1_DERIVE:
derive = true;
if (derivationSource == DERIVE_P1_SOURCE_MASTER) {
exportPath = apduBuffer;
exportPathOff = ISO7816.OFFSET_CDATA;
exportPathLen = dataLen;
}
break;
default:
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
return;
}
if (!publicOnly && ((exportPathLen < (short)(((short) EIP_1581_PREFIX.length) + 8)) || (Util.arrayCompare(EIP_1581_PREFIX, (short) 0, exportPath, exportPathOff, (short) EIP_1581_PREFIX.length) != 0))) {
updateDerivationPath(apduBuffer, (short) 0, dataLen, derivationSource);
boolean eip1581 = isEIP1581();
if (!(publicOnly || eip1581) || (extendedPublic && eip1581)) {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}
if (derive) {
doDerive(apduBuffer, (short) 0, dataLen, derivationSource, makeCurrent);
}
doDerive(apduBuffer, (short) 0);
short off = SecureChannel.SC_OUT_OFFSET;
@@ -1293,30 +1278,27 @@ public class KeycardApplet extends Applet {
short len;
if (!derive || makeCurrent) {
apduBuffer[off++] = TLV_PUB_KEY;
off++;
len = publicKey.getW(apduBuffer, off);
apduBuffer[(short) (off - 1)] = (byte) len;
off += len;
} else if (publicOnly) {
if (publicOnly) {
apduBuffer[off++] = TLV_PUB_KEY;
off++;
len = secp256k1.derivePublicKey(derivationOutput, (short) 0, apduBuffer, off);
apduBuffer[(short) (off - 1)] = (byte) len;
off += len;
}
if (!publicOnly) {
if (extendedPublic) {
apduBuffer[off++] = TLV_CHAIN_CODE;
off++;
Util.arrayCopyNonAtomic(derivationOutput, Crypto.KEY_SECRET_SIZE, apduBuffer, off, CHAIN_CODE_SIZE);
len = CHAIN_CODE_SIZE;
apduBuffer[(short) (off - 1)] = (byte) len;
off += len;
}
} else {
apduBuffer[off++] = TLV_PRIV_KEY;
off++;
if (!derive || makeCurrent) {
len = privateKey.getS(apduBuffer, off);
} else {
Util.arrayCopyNonAtomic(derivationOutput, (short) 0, apduBuffer, off, Crypto.KEY_SECRET_SIZE);
len = Crypto.KEY_SECRET_SIZE;
}
Util.arrayCopyNonAtomic(derivationOutput, (short) 0, apduBuffer, off, Crypto.KEY_SECRET_SIZE);
len = Crypto.KEY_SECRET_SIZE;
apduBuffer[(short) (off - 1)] = (byte) len;
off += len;
@@ -1325,6 +1307,10 @@ public class KeycardApplet extends Applet {
len = (short) (off - SecureChannel.SC_OUT_OFFSET);
apduBuffer[(SecureChannel.SC_OUT_OFFSET + 1)] = (byte) (len - 2);
if (makeCurrent) {
commitTmpPath();
}
secureChannel.respond(apdu, len, ISO7816.SW_NO_ERROR);
}
@@ -1435,7 +1421,11 @@ public class KeycardApplet extends Applet {
* @return whether the current key path is the same as the one defined as PIN-less or not
*/
private boolean isPinless() {
return (pinlessPathLen > 0) && (pinlessPathLen == keyPathLen) && (Util.arrayCompare(keyPath, (short) 0, pinlessPath, (short) 0, keyPathLen) == 0);
return (pinlessPathLen > 0) && (pinlessPathLen == tmpPathLen) && (Util.arrayCompare(tmpPath, (short) 0, pinlessPath, (short) 0, tmpPathLen) == 0);
}
private boolean isEIP1581() {
return (tmpPathLen >= (short)(((short) EIP_1581_PREFIX.length) + 8)) && (Util.arrayCompare(EIP_1581_PREFIX, (short) 0, tmpPath, (short) 0, (short) EIP_1581_PREFIX.length) == 0);
}
/**
@@ -1444,14 +1434,5 @@ public class KeycardApplet extends Applet {
private void resetCurveParameters() {
SECP256k1.setCurveParameters(masterPublic);
SECP256k1.setCurveParameters(masterPrivate);
SECP256k1.setCurveParameters(parentPublicKey);
SECP256k1.setCurveParameters(parentPrivateKey);
SECP256k1.setCurveParameters(publicKey);
SECP256k1.setCurveParameters(privateKey);
SECP256k1.setCurveParameters(pinlessPublicKey);
SECP256k1.setCurveParameters(pinlessPrivateKey);
}
}
+131 -24
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 {
@@ -1284,7 +1366,7 @@ public class KeycardTest {
verifyExportedKey(keyTemplate, keyPair, chainCode, new int[] { 0x8000002b, 0x8000003c, 0x8000062d, 0x00000000 }, true, false);
// Derive & Make current
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,true,false);
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, true, false);
assertEquals(0x9000, response.getSw());
keyTemplate = response.getData();
verifyExportedKey(keyTemplate, keyPair, chainCode, new int[] { 0x8000002b, 0x8000003c, 0x8000062d, 0x00000000, 0x00000000 }, false, false);
@@ -1293,7 +1375,7 @@ public class KeycardTest {
response = cmdSet.exportKey(new byte[] {(byte) 0x00, 0x00, 0x00, 0x01}, KeycardApplet.DERIVE_P1_SOURCE_PARENT, false,false);
assertEquals(0x9000, response.getSw());
keyTemplate = response.getData();
verifyExportedKey(keyTemplate, keyPair, chainCode, new int[] { 0x8000002b, 0x8000003c, 0x8000062d, 0x00000000, 0x00000001 }, false, true);
verifyExportedKey(keyTemplate, keyPair, chainCode, new int[] { 0x8000002b, 0x8000003c, 0x8000062d, 0x00000000, 0x00000001 }, false, false);
response = cmdSet.getStatus(KeycardApplet.GET_STATUS_P1_KEY_PATH);
assertEquals(0x9000, response.getSw());
assertArrayEquals(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}, response.getData());
@@ -1304,9 +1386,28 @@ public class KeycardTest {
keyTemplate = response.getData();
verifyExportedKey(keyTemplate, keyPair, chainCode, new int[] { 0x8000002b, 0x8000003c, 0x8000062d, 0x00000000, 0x00000000 }, false, false);
// Export extended public
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.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);
// 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
@@ -1673,28 +1774,34 @@ public class KeycardTest {
}
}
private void verifyExportedKey(byte[] keyTemplate, KeyPair keyPair, byte[] chainCode, int[] path, boolean publicOnly, boolean noPubKey) {
private void verifyExportedKey(byte[] keyTemplate, KeyPair keyPair, byte[] chainCode, int[] path, boolean publicOnly, boolean extendedPublic) {
if (!cmdSet.getApplicationInfo().hasKeyManagementCapability()) {
return;
}
ECKey key = deriveKey(keyPair, chainCode, path).decompress();
DeterministicKey dk = deriveKey(keyPair, chainCode, path);
ECKey key = dk.decompress();
assertEquals(KeycardApplet.TLV_KEY_TEMPLATE, keyTemplate[0]);
int pubKeyLen = 0;
if (!noPubKey) {
if (publicOnly) {
assertEquals(KeycardApplet.TLV_PUB_KEY, keyTemplate[2]);
byte[] pubKey = Arrays.copyOfRange(keyTemplate, 4, 4 + keyTemplate[3]);
assertArrayEquals(key.getPubKey(), pubKey);
pubKeyLen = 2 + pubKey.length;
}
int templateLen = 2 + pubKey.length;
if (publicOnly) {
assertEquals(pubKeyLen, keyTemplate[1]);
assertEquals(pubKeyLen + 2, keyTemplate.length);
if (extendedPublic) {
byte[] chain = Arrays.copyOfRange(keyTemplate, templateLen + 4, templateLen + 4 + keyTemplate[3 + templateLen]);
assertEquals(KeycardApplet.TLV_CHAIN_CODE, keyTemplate[2 + templateLen]);
assertArrayEquals(dk.getChainCode(), chain);
templateLen += 2 + chain.length;
}
assertEquals(templateLen, keyTemplate[1]);
assertEquals(templateLen + 2, keyTemplate.length);
} else {
assertEquals(KeycardApplet.TLV_PRIV_KEY, keyTemplate[2 + pubKeyLen]);
byte[] privateKey = Arrays.copyOfRange(keyTemplate, 4 + pubKeyLen, 4 + pubKeyLen + keyTemplate[3 + pubKeyLen]);
assertEquals(KeycardApplet.TLV_PRIV_KEY, keyTemplate[2]);
byte[] privateKey = Arrays.copyOfRange(keyTemplate, 4, 4 + keyTemplate[3]);
byte[] tPrivKey = key.getPrivKey().toByteArray();
@@ -2,6 +2,7 @@ package im.status.keycard;
import im.status.keycard.applet.ApplicationStatus;
import im.status.keycard.applet.KeycardCommandSet;
import im.status.keycard.io.APDUCommand;
import im.status.keycard.io.APDUResponse;
import im.status.keycard.io.CardChannel;
import org.web3j.crypto.ECKeyPair;