Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement plausible deniability #87

Merged
merged 6 commits into from
Dec 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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'
}
}

Expand Down Expand Up @@ -46,7 +46,7 @@ if (!testTarget) {

def pairingPass = project.properties['im.status.keycard.test.pairing']
if (!pairingPass) {
pairingPass = 'KeycardTest'
pairingPass = 'KeycardDefaultPairing'
}


Expand All @@ -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")
Expand Down
2 changes: 1 addition & 1 deletion buildSrc/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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'
}
38 changes: 18 additions & 20 deletions src/main/java/im/status/keycard/Crypto.java
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,7 @@ boolean bip32CKDPriv(byte[] i, short iOff, byte[] scratch, short scratchOff, byt

addm256(output, outOff, data, dataOff, SECP256k1.SECP256K1_R, (short) 0, output, outOff);

if (isZero256(output, outOff)) {
return false;
}

return true;
return !isZero256(output, outOff);
}

/**
Expand Down Expand Up @@ -202,17 +198,22 @@ private void addm256(byte[] a, short aOff, byte[] b, short bOff, byte[] n, short
* @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);

if (ai != bi) {
return (short)(ai - bi);
}
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));

gt |= (d_xored >>> 15) & eq;
eq &= ((short)(l_xor_r - 1) >>> 15);
}

return 0;
return (short) ((gt + gt + eq) - 1);
}

/**
Expand All @@ -223,16 +224,13 @@ private short ucmp256(byte[] a, short aOff, byte[] b, short bOff) {
* @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;
}

/**
Expand Down
77 changes: 60 additions & 17 deletions src/main/java/im/status/keycard/KeycardApplet.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
* 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;
Expand Down Expand Up @@ -111,13 +111,17 @@ 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;

private ECPublicKey masterPublic;
private ECPrivateKey masterPrivate;
private byte[] masterChainCode;
private byte[] altChainCode;
private byte[] chainCode;
private boolean isExtended;

private byte[] tmpPath;
Expand Down Expand Up @@ -173,6 +177,8 @@ public KeycardApplet(byte[] bArray, short bOffset, byte bLength) {
masterPublic = (ECPublicKey) KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PUBLIC, SECP256k1.SECP256K1_KEY_SIZE, false);
masterPrivate = (ECPrivateKey) KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PRIVATE, SECP256k1.SECP256K1_KEY_SIZE, false);
masterChainCode = new byte[CHAIN_CODE_SIZE];
altChainCode = new byte[CHAIN_CODE_SIZE];
chainCode = masterChainCode;

keyPath = new byte[KEY_PATH_MAX_DEPTH * 4];
pinlessPath = new byte[KEY_PATH_MAX_DEPTH * 4];
Expand Down Expand Up @@ -319,37 +325,44 @@ private void processInit(APDU apdu) {

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;
}

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 {
Expand Down Expand Up @@ -385,9 +398,11 @@ private void unpair(APDU apdu) {
* @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();

Expand Down Expand Up @@ -516,8 +531,24 @@ private void verifyPIN(APDU apdu) {
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;
}
}

Expand Down Expand Up @@ -615,7 +646,8 @@ private void unblockPIN(APDU apdu) {
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();
Expand Down Expand Up @@ -662,6 +694,10 @@ private void loadKey(APDU apdu) {
* @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);
Expand Down Expand Up @@ -782,14 +818,20 @@ private void deriveKey(APDU apdu) {
/**
* Updates the derivation path for a subsequent EXPORT KEY/SIGN APDU. Optionally stores the result in the current path.
*
* @param apduBuffer the APDU buffer
* @param off the offset in the APDU buffer relative to the data field
* @param path the path
* @param off the offset in the path
* @param len the len of the path
* @param source derivation source
*/
private void updateDerivationPath(byte[] apduBuffer, short off, short len, byte source) {
private void updateDerivationPath(byte[] path, short off, short len, byte source) {
if (!isExtended) {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
if (len == 0) {
tmpPathLen = 0;
} else {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}

return;
}

short newPathLen;
Expand Down Expand Up @@ -833,7 +875,7 @@ private void updateDerivationPath(byte[] apduBuffer, short off, short len, byte
short pathOff = (short) (ISO7816.OFFSET_CDATA + off);

Util.arrayCopyNonAtomic(srcKeyPath, (short) 0, tmpPath, (short) 0, pathLenOff);
Util.arrayCopyNonAtomic(apduBuffer, pathOff, tmpPath, pathLenOff, len);
Util.arrayCopyNonAtomic(path, pathOff, tmpPath, pathLenOff, len);
tmpPathLen = newPathLen;
}

Expand Down Expand Up @@ -862,7 +904,7 @@ private void doDerive(byte[] apduBuffer, short off) {
short dataOff = (short) (scratchOff + Crypto.KEY_DERIVATION_SCRATCH_SIZE);

short pubKeyOff = (short) (dataOff + masterPrivate.getS(apduBuffer, dataOff));
pubKeyOff = Util.arrayCopyNonAtomic(masterChainCode, (short) 0, apduBuffer, pubKeyOff, CHAIN_CODE_SIZE);
pubKeyOff = Util.arrayCopyNonAtomic(chainCode, (short) 0, apduBuffer, pubKeyOff, CHAIN_CODE_SIZE);

if (!crypto.bip32IsHardened(tmpPath, (short) 0)) {
masterPublic.getW(apduBuffer, pubKeyOff);
Expand Down Expand Up @@ -987,6 +1029,7 @@ private void removeKey(APDU apdu) {
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);
}
Expand Down
Loading