Merge pull request #87 from status-im/plausible-deniability

implement plausible deniability
This commit is contained in:
Michele Balistreri 2022-12-02 12:47:18 +01:00 committed by GitHub
commit b21fef0fec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 135 additions and 60 deletions

View File

@ -9,7 +9,7 @@ buildscript {
dependencies { dependencies {
classpath 'com.fidesmo:gradle-javacard:0.2.7' 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'] def pairingPass = project.properties['im.status.keycard.test.pairing']
if (!pairingPass) { if (!pairingPass) {
pairingPass = 'KeycardTest' pairingPass = 'KeycardDefaultPairing'
} }
@ -59,7 +59,7 @@ dependencies {
testCompile(files("../jcardsim/jcardsim-3.0.5-SNAPSHOT.jar")) testCompile(files("../jcardsim/jcardsim-3.0.5-SNAPSHOT.jar"))
testCompile('org.web3j:core:2.3.1') testCompile('org.web3j:core:2.3.1')
testCompile('org.bitcoinj:bitcoinj-core:0.14.5') 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.bouncycastle:bcprov-jdk15on:1.65')
testCompile("org.junit.jupiter:junit-jupiter-api:5.1.1") testCompile("org.junit.jupiter:junit-jupiter-api:5.1.1")
testRuntime("org.junit.jupiter:junit-jupiter-engine:5.1.1") testRuntime("org.junit.jupiter:junit-jupiter-engine:5.1.1")

View File

@ -4,5 +4,5 @@ repositories {
} }
dependencies { dependencies {
compile 'com.github.status-im.status-keycard-java:desktop:31f4ab5' compile 'com.github.status-im.status-keycard-java:desktop:15a61e1'
} }

View File

@ -89,11 +89,7 @@ public class Crypto {
addm256(output, outOff, data, dataOff, SECP256k1.SECP256K1_R, (short) 0, output, outOff); addm256(output, outOff, data, dataOff, SECP256k1.SECP256K1_R, (short) 0, output, outOff);
if (isZero256(output, outOff)) { return !isZero256(output, outOff);
return false;
}
return true;
} }
/** /**
@ -202,17 +198,22 @@ public class Crypto {
* @return the comparison result * @return the comparison result
*/ */
private short ucmp256(byte[] a, short aOff, byte[] b, short bOff) { 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++) { for (short i = 0 ; i < 32; i++) {
ai = (short)(a[(short)(aOff + i)] & 0x00ff); short l = (short)(a[(short)(aOff + i)] & 0x00ff);
bi = (short)(b[(short)(bOff + 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) { gt |= (d_xored >>> 15) & eq;
return (short)(ai - bi); 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 * @return true if a is 0, false otherwise
*/ */
private boolean isZero256(byte[] a, short aOff) { private boolean isZero256(byte[] a, short aOff) {
boolean isZero = true; byte acc = 0;
for (short i = 0; i < (byte) 32; i++) { for (short i = 0; i < 32; i++) {
if (a[(short)(aOff + i)] != 0) { acc |= a[(short)(aOff + i)];
isZero = false;
break;
}
} }
return isZero; return acc == 0;
} }
/** /**

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. * The applet's main class. All incoming commands a processed by this class.
*/ */
public class KeycardApplet extends Applet { 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_GET_STATUS = (byte) 0xF2;
static final byte INS_INIT = (byte) 0xFE; static final byte INS_INIT = (byte) 0xFE;
@ -111,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}; 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 pin;
private OwnerPIN mainPIN;
private OwnerPIN altPIN;
private OwnerPIN puk; private OwnerPIN puk;
private byte[] uid; private byte[] uid;
private SecureChannel secureChannel; private SecureChannel secureChannel;
@ -118,6 +120,8 @@ public class KeycardApplet extends Applet {
private ECPublicKey masterPublic; private ECPublicKey masterPublic;
private ECPrivateKey masterPrivate; private ECPrivateKey masterPrivate;
private byte[] masterChainCode; private byte[] masterChainCode;
private byte[] altChainCode;
private byte[] chainCode;
private boolean isExtended; private boolean isExtended;
private byte[] tmpPath; private byte[] tmpPath;
@ -173,6 +177,8 @@ public class KeycardApplet extends Applet {
masterPublic = (ECPublicKey) KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PUBLIC, SECP256k1.SECP256K1_KEY_SIZE, false); 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); masterPrivate = (ECPrivateKey) KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PRIVATE, SECP256k1.SECP256K1_KEY_SIZE, false);
masterChainCode = new byte[CHAIN_CODE_SIZE]; masterChainCode = new byte[CHAIN_CODE_SIZE];
altChainCode = new byte[CHAIN_CODE_SIZE];
chainCode = masterChainCode;
keyPath = new byte[KEY_PATH_MAX_DEPTH * 4]; keyPath = new byte[KEY_PATH_MAX_DEPTH * 4];
pinlessPath = new byte[KEY_PATH_MAX_DEPTH * 4]; pinlessPath = new byte[KEY_PATH_MAX_DEPTH * 4];
@ -319,21 +325,27 @@ public class KeycardApplet extends Applet {
byte defaultLimitsLen = (byte)(PIN_LENGTH + PUK_LENGTH + SecureChannel.SC_SECRET_LENGTH); byte defaultLimitsLen = (byte)(PIN_LENGTH + PUK_LENGTH + SecureChannel.SC_SECRET_LENGTH);
byte withLimitsLen = (byte) (defaultLimitsLen + 2); 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); ISOException.throwIt(ISO7816.SW_WRONG_DATA);
} }
byte pinLimit; byte pinLimit;
byte pukLimit; 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)]; pinLimit = apduBuffer[(short) (ISO7816.OFFSET_CDATA + defaultLimitsLen)];
pukLimit = apduBuffer[(short) (ISO7816.OFFSET_CDATA + defaultLimitsLen + 1)]; 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) { if (pinLimit < PIN_MIN_RETRIES || pinLimit > PIN_MAX_RETRIES || pukLimit < PUK_MIN_RETRIES || pukLimit > PUK_MAX_RETRIES) {
ISOException.throwIt(ISO7816.SW_WRONG_DATA); ISOException.throwIt(ISO7816.SW_WRONG_DATA);
} }
if (apduBuffer[ISO7816.OFFSET_LC] == withAltPIN) {
altPinOff = (short)(ISO7816.OFFSET_CDATA + withLimitsLen);
}
} else { } else {
pinLimit = DEFAULT_PIN_MAX_RETRIES; pinLimit = DEFAULT_PIN_MAX_RETRIES;
pukLimit = DEFAULT_PUK_MAX_RETRIES; pukLimit = DEFAULT_PUK_MAX_RETRIES;
@ -341,15 +353,16 @@ public class KeycardApplet extends Applet {
secureChannel.initSecureChannel(apduBuffer, (short)(ISO7816.OFFSET_CDATA + PIN_LENGTH + PUK_LENGTH)); 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); altPIN = new OwnerPIN(pinLimit, PIN_LENGTH);
pin.update(apduBuffer, ISO7816.OFFSET_CDATA, PIN_LENGTH); altPIN.update(apduBuffer, altPinOff, PIN_LENGTH);
puk = new OwnerPIN(pukLimit, PUK_LENGTH); puk = new OwnerPIN(pukLimit, PUK_LENGTH);
puk.update(apduBuffer, (short)(ISO7816.OFFSET_CDATA + PIN_LENGTH), 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) { } else if (apduBuffer[ISO7816.OFFSET_INS] == IdentApplet.INS_IDENTIFY_CARD) {
IdentApplet.identifyCard(apdu, null, signature); IdentApplet.identifyCard(apdu, null, signature);
} else { } else {
@ -385,9 +398,11 @@ public class KeycardApplet extends Applet {
* @param apdu the JCRE-owned APDU object. * @param apdu the JCRE-owned APDU object.
*/ */
private void selectApplet(APDU apdu) { private void selectApplet(APDU apdu) {
pin.reset(); altPIN.reset();
mainPIN.reset();
puk.reset(); puk.reset();
secureChannel.reset(); secureChannel.reset();
pin = mainPIN;
byte[] apduBuffer = apdu.getBuffer(); byte[] apduBuffer = apdu.getBuffer();
@ -516,8 +531,24 @@ public class KeycardApplet extends Applet {
ISOException.throwIt(ISO7816.SW_WRONG_DATA); ISOException.throwIt(ISO7816.SW_WRONG_DATA);
} }
if (!pin.check(apduBuffer, ISO7816.OFFSET_CDATA, len)) { 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())); 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 +646,8 @@ public class KeycardApplet extends Applet {
ISOException.throwIt((short)((short) 0x63c0 | (short) puk.getTriesRemaining())); 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.update(apduBuffer, (short)(ISO7816.OFFSET_CDATA + PUK_LENGTH), PIN_LENGTH);
pin.check(apduBuffer, (short)(ISO7816.OFFSET_CDATA + PUK_LENGTH), PIN_LENGTH); pin.check(apduBuffer, (short)(ISO7816.OFFSET_CDATA + PUK_LENGTH), PIN_LENGTH);
puk.reset(); puk.reset();
@ -662,6 +694,10 @@ public class KeycardApplet extends Applet {
* @param apduBuffer the APDU buffer * @param apduBuffer the APDU buffer
*/ */
private void generateKeyUIDAndRespond(APDU apdu, byte[] apduBuffer) { 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); short pubLen = masterPublic.getW(apduBuffer, (short) 0);
crypto.sha256.doFinal(apduBuffer, (short) 0, pubLen, keyUID, (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); Util.arrayCopyNonAtomic(keyUID, (short) 0, apduBuffer, SecureChannel.SC_OUT_OFFSET, KEY_UID_LENGTH);
@ -782,16 +818,22 @@ public class KeycardApplet extends Applet {
/** /**
* Updates the derivation path for a subsequent EXPORT KEY/SIGN APDU. Optionally stores the result in the current path. * 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 path the path
* @param off the offset in the APDU buffer relative to the data field * @param off the offset in the path
* @param len the len of the path * @param len the len of the path
* @param source derivation source * @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) { if (!isExtended) {
if (len == 0) {
tmpPathLen = 0;
} else {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
} }
return;
}
short newPathLen; short newPathLen;
short pathLenOff; short pathLenOff;
@ -833,7 +875,7 @@ public class KeycardApplet extends Applet {
short pathOff = (short) (ISO7816.OFFSET_CDATA + off); short pathOff = (short) (ISO7816.OFFSET_CDATA + off);
Util.arrayCopyNonAtomic(srcKeyPath, (short) 0, tmpPath, (short) 0, pathLenOff); 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; tmpPathLen = newPathLen;
} }
@ -862,7 +904,7 @@ public class KeycardApplet extends Applet {
short dataOff = (short) (scratchOff + Crypto.KEY_DERIVATION_SCRATCH_SIZE); short dataOff = (short) (scratchOff + Crypto.KEY_DERIVATION_SCRATCH_SIZE);
short pubKeyOff = (short) (dataOff + masterPrivate.getS(apduBuffer, dataOff)); 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)) { if (!crypto.bip32IsHardened(tmpPath, (short) 0)) {
masterPublic.getW(apduBuffer, pubKeyOff); masterPublic.getW(apduBuffer, pubKeyOff);
@ -987,6 +1029,7 @@ public class KeycardApplet extends Applet {
masterPublic.clearKey(); masterPublic.clearKey();
resetCurveParameters(); resetCurveParameters();
Util.arrayFillNonAtomic(masterChainCode, (short) 0, (short) masterChainCode.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(keyPath, (short) 0, (short) keyPath.length, (byte) 0);
Util.arrayFillNonAtomic(pinlessPath, (short) 0, (short) pinlessPath.length, (byte) 0); Util.arrayFillNonAtomic(pinlessPath, (short) 0, (short) pinlessPath.length, (byte) 0);
} }

View File

@ -222,10 +222,10 @@ public class KeycardTest {
initCapabilities(cmdSet.getApplicationInfo()); 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()) { 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(); cmdSet.select().checkOK();
initCapabilities(cmdSet.getApplicationInfo()); initCapabilities(cmdSet.getApplicationInfo());
} }
@ -567,6 +567,10 @@ public class KeycardTest {
response = cmdSet.verifyPIN("000000"); response = cmdSet.verifyPIN("000000");
assertEquals(0x9000, response.getSw()); assertEquals(0x9000, response.getSw());
// Alt PIN
response = cmdSet.verifyPIN("024680");
assertEquals(0x9000, response.getSw());
// Check max retry counter // Check max retry counter
response = cmdSet.verifyPIN("123456"); response = cmdSet.verifyPIN("123456");
assertEquals(0x63C2, response.getSw()); assertEquals(0x63C2, response.getSw());
@ -580,8 +584,11 @@ public class KeycardTest {
response = cmdSet.verifyPIN("000000"); response = cmdSet.verifyPIN("000000");
assertEquals(0x63C0, response.getSw()); assertEquals(0x63C0, response.getSw());
response = cmdSet.verifyPIN("024680");
assertEquals(0x63C0, response.getSw());
// Unblock PIN to make further tests possible // Unblock PIN to make further tests possible
response = cmdSet.unblockPIN("123456789012", "000000"); response = cmdSet.unblockPIN("012345678901", "024680");
assertEquals(0x9000, response.getSw()); assertEquals(0x9000, response.getSw());
} }
@ -667,7 +674,7 @@ public class KeycardTest {
assertEquals(0x9000, response.getSw()); assertEquals(0x9000, response.getSw());
// Reset PUK // Reset PUK
response = cmdSet.changePIN(KeycardApplet.CHANGE_PIN_P1_PUK, "123456789012"); response = cmdSet.changePIN(KeycardApplet.CHANGE_PIN_P1_PUK, "012345678901");
assertEquals(0x9000, response.getSw()); assertEquals(0x9000, response.getSw());
// Change the pairing secret // Change the pairing secret
@ -687,6 +694,26 @@ public class KeycardTest {
response = cmdSet.changePIN(KeycardApplet.CHANGE_PIN_P1_PAIRING_SECRET, sharedSecret); response = cmdSet.changePIN(KeycardApplet.CHANGE_PIN_P1_PAIRING_SECRET, sharedSecret);
assertEquals(0x9000, response.getSw()); 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 @Test
@ -694,13 +721,13 @@ public class KeycardTest {
@Capabilities("credentialsManagement") @Capabilities("credentialsManagement")
void unblockPinTest() throws Exception { void unblockPinTest() throws Exception {
// Security condition violation: SecureChannel not open // Security condition violation: SecureChannel not open
APDUResponse response = cmdSet.unblockPIN("123456789012", "000000"); APDUResponse response = cmdSet.unblockPIN("012345678901", "000000");
assertEquals(0x6985, response.getSw()); assertEquals(0x6985, response.getSw());
cmdSet.autoOpenSecureChannel(); cmdSet.autoOpenSecureChannel();
// Condition violation: PIN is not blocked // Condition violation: PIN is not blocked
response = cmdSet.unblockPIN("123456789012", "000000"); response = cmdSet.unblockPIN("012345678901", "000000");
assertEquals(0x6985, response.getSw()); assertEquals(0x6985, response.getSw());
// Block the PIN // Block the PIN
@ -725,7 +752,7 @@ public class KeycardTest {
assertEquals(0x63C4, response.getSw()); assertEquals(0x63C4, response.getSw());
// Correct PUK // Correct PUK
response = cmdSet.unblockPIN("123456789012", "654321"); response = cmdSet.unblockPIN("012345678901", "654321");
assertEquals(0x9000, response.getSw()); assertEquals(0x9000, response.getSw());
// Check that PIN has been changed and unblocked // Check that PIN has been changed and unblocked
@ -1100,6 +1127,13 @@ public class KeycardTest {
response = cmdSet.signPinless(hash); response = cmdSet.signPinless(hash);
assertEquals(0x6A88, response.getSw()); 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 { private void verifySignResp(byte[] data, APDUResponse response) throws Exception {
@ -1305,10 +1339,10 @@ public class KeycardTest {
verifyExportedKey(keyTemplate, keyPair, chainCode, new int[] { 0x8000002b, 0x8000003c, 0x8000062d, 0x00000000, 0x00000000 }, false, false); verifyExportedKey(keyTemplate, keyPair, chainCode, new int[] { 0x8000002b, 0x8000003c, 0x8000062d, 0x00000000, 0x00000000 }, false, false);
// Export extended public // 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()); 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()); assertEquals(0x9000, response.getSw());
keyTemplate = response.getData(); keyTemplate = response.getData();
verifyExportedKey(keyTemplate, keyPair, chainCode, new int[] { 0x8000002b, 0x8000003c, 0x8000062c, 0x00000000 }, true, true); verifyExportedKey(keyTemplate, keyPair, chainCode, new int[] { 0x8000002b, 0x8000003c, 0x8000062c, 0x00000000 }, true, true);
@ -1316,6 +1350,16 @@ public class KeycardTest {
// Reset // Reset
response = cmdSet.deriveKey(new byte[0], KeycardApplet.DERIVE_P1_SOURCE_MASTER); response = cmdSet.deriveKey(new byte[0], KeycardApplet.DERIVE_P1_SOURCE_MASTER);
assertEquals(0x9000, response.getSw()); 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 @Test

View File

@ -13,17 +13,12 @@ import java.security.interfaces.ECPrivateKey;
public class TestKeycardCommandSet extends KeycardCommandSet { public class TestKeycardCommandSet extends KeycardCommandSet {
private CardChannel ac;
private TestSecureChannelSession sc;
public TestKeycardCommandSet(CardChannel apduChannel) { public TestKeycardCommandSet(CardChannel apduChannel) {
super(apduChannel); super(apduChannel);
ac = apduChannel;
} }
public void setSecureChannel(TestSecureChannelSession secureChannel) { public void setSecureChannel(TestSecureChannelSession secureChannel) {
super.setSecureChannel(secureChannel); super.setSecureChannel(secureChannel);
sc = secureChannel;
} }
/** /**
@ -83,11 +78,6 @@ public class TestKeycardCommandSet extends KeycardCommandSet {
return loadKey(data, LOAD_KEY_P1_SEED); 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 * Sends a GET STATUS APDU to retrieve the APPLICATION STATUS template and reads the byte indicating key initialization
* status * status