Author SHA1 Message Date
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
6 changed files with 280 additions and 281 deletions
+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:15a61e1')
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:15a61e1'
}
+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;
}
/**
+177 -236
View File
@@ -9,7 +9,7 @@ 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;
@@ -61,6 +61,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,6 +79,7 @@ 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;
@@ -109,6 +111,8 @@ public class KeycardApplet extends Applet {
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 +120,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 +176,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];
@@ -335,21 +325,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 +353,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 +398,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 +410,7 @@ public class KeycardApplet extends Applet {
apduBuffer[off++] = TLV_APPLICATION_INFO_TEMPLATE;
if (privateKey.isInitialized()) {
if (masterPrivate.isInitialized()) {
apduBuffer[off++] = (byte) 0x81;
}
@@ -437,7 +436,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 +496,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 +531,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 +646,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 +694,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 +709,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 +739,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 +759,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 +786,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 +811,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 +873,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);
}
/**
@@ -1048,18 +1025,11 @@ public class KeycardApplet extends Applet {
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(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);
}
@@ -1100,32 +1070,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 +1103,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 +1133,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 +1167,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 +1179,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 +1238,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 +1267,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 +1381,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 +1394,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);
}
}
@@ -222,10 +222,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());
assertEquals(0x9000, cmdSet.init("000000", "024680", "012345678901", sharedSecret, (byte) 3, (byte) 5).getSw());
cmdSet.select().checkOK();
initCapabilities(cmdSet.getApplicationInfo());
}
@@ -567,6 +567,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 +584,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 +602,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 +674,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 +694,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 +721,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 +752,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
@@ -1100,6 +1127,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 +1318,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 +1327,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 +1338,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 +1726,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;