Skip to content

Commit

Permalink
Merge pull request #85 from status-im/ident
Browse files Browse the repository at this point in the history
implement IDENTIFY CARD
  • Loading branch information
bitgamma authored Nov 2, 2022
2 parents f1d3d41 + 01c46f7 commit 9677aef
Show file tree
Hide file tree
Showing 11 changed files with 243 additions and 20 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
# IntelliJ IDEA project files
.idea
*.iml
/.vscode

# Gradle output
/.gradle
/build
/bin
/gradle.properties
buildSrc/build
buildSrc/.gradle
8 changes: 6 additions & 2 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:3.0.4'
classpath 'com.github.status-im.status-keycard-java:desktop:31f4ab5'
}
}

Expand All @@ -31,6 +31,10 @@ javacard {
aid = '0xA0:0x00:0x00:0x08:0x04:0x00:0x01:0x03'
className = 'CashApplet'
}
applet {
aid = '0xA0:0x00:0x00:0x08:0x04:0x00:0x01:0x04'
className = 'IdentApplet'
}
version = '3.1'
}
}
Expand All @@ -55,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:3.0.4')
testCompile('com.github.status-im.status-keycard-java:desktop:31f4ab5')
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:3.0.4'
compile 'com.github.status-im.status-keycard-java:desktop:31f4ab5'
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package im.status.keycard.build;

import im.status.keycard.applet.Identifiers;
import im.status.keycard.desktop.PCSCCardChannel;
import im.status.keycard.globalplatform.GlobalPlatformCommandSet;
import im.status.keycard.globalplatform.LoadCallback;
import im.status.keycard.io.APDUException;
import org.bouncycastle.util.encoders.Hex;
import org.gradle.api.DefaultTask;
import org.gradle.api.GradleException;
import org.gradle.api.logging.Logger;
Expand Down Expand Up @@ -69,6 +71,8 @@ public void blockLoaded(int loadedBlock, int blockCount) {
cmdSet.installNDEFApplet(new byte[0]).checkOK();
logger.info("Installing the Cash Applet");
cmdSet.installCashApplet().checkOK();
logger.info("Installing the Identifier Applet");
cmdSet.installIdentApplet().checkOK();
} catch (IOException e) {
throw new GradleException("I/O error", e);
} catch (APDUException e) {
Expand Down
11 changes: 6 additions & 5 deletions src/main/java/im/status/keycard/CashApplet.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ public class CashApplet extends Applet {
private ECPrivateKey privateKey;

private Crypto crypto;
private SECP256k1 secp256k1;

private Signature signature;

Expand Down Expand Up @@ -40,17 +39,15 @@ public static void install(byte[] bArray, short bOffset, byte bLength) {
*/
public CashApplet(byte[] bArray, short bOffset, byte bLength) {
crypto = new Crypto();
secp256k1 = new SECP256k1();

keypair = new KeyPair(KeyPair.ALG_EC_FP, SECP256k1.SECP256K1_KEY_SIZE);
publicKey = (ECPublicKey) keypair.getPublic();
privateKey = (ECPrivateKey) keypair.getPrivate();
secp256k1.setCurveParameters(publicKey);
secp256k1.setCurveParameters(privateKey);
SECP256k1.setCurveParameters(publicKey);
SECP256k1.setCurveParameters(privateKey);
keypair.genKeyPair();

signature = Signature.getInstance(Signature.ALG_ECDSA_SHA_256, false);
signature.init(privateKey, Signature.MODE_SIGN);

short c9Off = (short)(bOffset + bArray[bOffset] + 1); // Skip AID
c9Off += (short)(bArray[c9Off] + 1); // Skip Privileges and parameter length
Expand Down Expand Up @@ -79,6 +76,9 @@ public void process(APDU apdu) throws ISOException {
case KeycardApplet.INS_SIGN:
sign(apdu);
break;
case IdentApplet.INS_IDENTIFY_CARD:
IdentApplet.identifyCard(apdu, null, signature);
break;
default:
ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
break;
Expand Down Expand Up @@ -129,6 +129,7 @@ private void sign(APDU apdu) {
outLen += 5;
short sigOff = (short) (SIGN_OUT_OFF + outLen);

signature.init(privateKey, Signature.MODE_SIGN);
outLen += signature.signPreComputedHash(apduBuffer, ISO7816.OFFSET_CDATA, MessageDigest.LENGTH_SHA_256, apduBuffer, sigOff);
outLen += crypto.fixS(apduBuffer, sigOff);

Expand Down
142 changes: 142 additions & 0 deletions src/main/java/im/status/keycard/IdentApplet.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package im.status.keycard;

import javacard.framework.*;
import javacard.security.*;

/**
* The applet's main class. All incoming commands a processed by this class.
*/
public class IdentApplet extends Applet {
static final byte TLV_CERT = (byte) 0x8A;
static final byte CERT_VALID = (byte) 0xAA;

static final byte INS_IDENTIFY_CARD = (byte) 0x14;

/**
* Invoked during applet installation. Creates an instance of this class. The installation parameters are passed in
* the given buffer.
*
* @param bArray installation parameters buffer
* @param bOffset offset where the installation parameters begin
* @param bLength length of the installation parameters
*/
public static void install(byte[] bArray, short bOffset, byte bLength) {
new IdentApplet(bArray, bOffset, bLength);
}

/**
* Application constructor. All memory allocation is done here. The reason for this is two-fold: first the card might
* not have Garbage Collection so dynamic allocation will eventually eat all memory. The second reason is to be sure
* that if the application installs successfully, there is no risk of running out of memory because of other applets
* allocating memory. The constructor also registers the applet with the JCRE so that it becomes selectable.
*
* @param bArray installation parameters buffer
* @param bOffset offset where the installation parameters begin
* @param bLength length of the installation parameters
*/
public IdentApplet(byte[] bArray, short bOffset, byte bLength) {
SharedMemory.idPrivate = (ECPrivateKey) KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PRIVATE, SECP256k1.SECP256K1_KEY_SIZE, false);
SECP256k1.setCurveParameters(SharedMemory.idPrivate);
SharedMemory.idCert[0] = 0;
register(bArray, (short) (bOffset + 1), bArray[bOffset]);
}

/**
* This method is called on every incoming APDU. This method is just a dispatcher which invokes the correct method
* depending on the INS of the APDU.
*
* @param apdu the JCRE-owned APDU object.
* @throws ISOException any processing error
*/
public void process(APDU apdu) throws ISOException {
if (selectingApplet()) {
processSelect(apdu);
return;
}

byte[] apduBuffer = apdu.getBuffer();

switch (apduBuffer[ISO7816.OFFSET_INS]) {
case KeycardApplet.INS_STORE_DATA:
processStoreData(apdu);
break;
default:
ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
break;
}
}

private void processSelect(APDU apdu) {
byte[] apduBuffer = apdu.getBuffer();
apdu.setIncomingAndReceive();

if (SharedMemory.idCert[0] == CERT_VALID) {
Util.arrayCopyNonAtomic(SharedMemory.idCert, (short) 1, apduBuffer, (short) 0, SharedMemory.CERT_LEN);
apdu.setOutgoingAndSend((short) 0, SharedMemory.CERT_LEN);
}

}

private void processStoreData(APDU apdu) {
if (SharedMemory.idCert[0] == CERT_VALID) {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}

byte[] apduBuffer = apdu.getBuffer();
apdu.setIncomingAndReceive();

if (Util.makeShort((byte) 0, apduBuffer[ISO7816.OFFSET_LC]) != (SharedMemory.CERT_LEN + Crypto.KEY_SECRET_SIZE)) {
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
}

Util.arrayCopyNonAtomic(apduBuffer, ISO7816.OFFSET_CDATA, SharedMemory.idCert, (short) 1, SharedMemory.CERT_LEN);
SharedMemory.idPrivate.setS(apduBuffer, (short) (ISO7816.OFFSET_CDATA + SharedMemory.CERT_LEN), Crypto.KEY_SECRET_SIZE);
SharedMemory.idCert[0] = CERT_VALID;
}

/**
* Processes the IDENTIFY CARD command according to the application's specifications.
*
* @param apdu the JCRE-owned APDU object.
*/
static void identifyCard(APDU apdu, SecureChannel secureChannel, Signature signature) {
byte[] apduBuffer = apdu.getBuffer();

short len;

if (secureChannel != null && secureChannel.isOpen()) {
len = secureChannel.preprocessAPDU(apduBuffer);
} else {
len = (short) (apduBuffer[ISO7816.OFFSET_LC] & (short) 0xff);
}

if (SharedMemory.idCert[0] != CERT_VALID) {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}

if (len != MessageDigest.LENGTH_SHA_256) {
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
}

short off = SecureChannel.SC_OUT_OFFSET;
apduBuffer[off++] = KeycardApplet.TLV_SIGNATURE_TEMPLATE;
apduBuffer[off++] = (byte) 0x81;
off++;
apduBuffer[off++] = TLV_CERT;
apduBuffer[off++] = (byte) SharedMemory.CERT_LEN;
Util.arrayCopyNonAtomic(SharedMemory.idCert, (short) 1, apduBuffer, off, SharedMemory.CERT_LEN);
off += SharedMemory.CERT_LEN;

short outLen = (short)(SharedMemory.CERT_LEN + 5);
signature.init(SharedMemory.idPrivate, Signature.MODE_SIGN);
outLen += signature.signPreComputedHash(apduBuffer, ISO7816.OFFSET_CDATA, MessageDigest.LENGTH_SHA_256, apduBuffer, off);

apduBuffer[(short)(SecureChannel.SC_OUT_OFFSET + 2)] = (byte)(outLen - 3);

if (secureChannel != null && secureChannel.isOpen()) {
secureChannel.respond(apdu, outLen, ISO7816.SW_NO_ERROR);
} else {
apdu.setOutgoingAndSend(SecureChannel.SC_OUT_OFFSET, outLen);
}
}
}
23 changes: 14 additions & 9 deletions src/main/java/im/status/keycard/KeycardApplet.java
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,9 @@ public void process(APDU apdu) throws ISOException {
case SecureChannel.INS_UNPAIR:
unpair(apdu);
break;
case IdentApplet.INS_IDENTIFY_CARD:
IdentApplet.identifyCard(apdu, secureChannel, signature);
break;
case INS_GET_STATUS:
getStatus(apdu);
break;
Expand Down Expand Up @@ -363,6 +366,8 @@ private void processInit(APDU apdu) {
puk.update(apduBuffer, (short)(ISO7816.OFFSET_CDATA + PIN_LENGTH), PUK_LENGTH);

JCSystem.commitTransaction();
} else if (apduBuffer[ISO7816.OFFSET_INS] == IdentApplet.INS_IDENTIFY_CARD) {
IdentApplet.identifyCard(apdu, null, signature);
} else {
ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
}
Expand Down Expand Up @@ -685,7 +690,7 @@ private void generateKeyUIDAndRespond(APDU apdu, byte[] apduBuffer) {
*/
private void resetKeyStatus() {
parentPrivateKey.clearKey();
secp256k1.setCurveParameters(parentPrivateKey);
SECP256k1.setCurveParameters(parentPrivateKey);
keyPathLen = 0;
}

Expand Down Expand Up @@ -1437,16 +1442,16 @@ private boolean isPinless() {
* Set curve parameters to cleared keys
*/
private void resetCurveParameters() {
secp256k1.setCurveParameters(masterPublic);
secp256k1.setCurveParameters(masterPrivate);
SECP256k1.setCurveParameters(masterPublic);
SECP256k1.setCurveParameters(masterPrivate);

secp256k1.setCurveParameters(parentPublicKey);
secp256k1.setCurveParameters(parentPrivateKey);
SECP256k1.setCurveParameters(parentPublicKey);
SECP256k1.setCurveParameters(parentPrivateKey);

secp256k1.setCurveParameters(publicKey);
secp256k1.setCurveParameters(privateKey);
SECP256k1.setCurveParameters(publicKey);
SECP256k1.setCurveParameters(privateKey);

secp256k1.setCurveParameters(pinlessPublicKey);
secp256k1.setCurveParameters(pinlessPrivateKey);
SECP256k1.setCurveParameters(pinlessPublicKey);
SECP256k1.setCurveParameters(pinlessPrivateKey);
}
}
2 changes: 1 addition & 1 deletion src/main/java/im/status/keycard/SECP256k1.java
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public class SECP256k1 {
*
* @param key the key where the curve parameters must be set
*/
void setCurveParameters(ECKey key) {
static void setCurveParameters(ECKey key) {
key.setA(SECP256K1_A, (short) 0x00, (short) SECP256K1_A.length);
key.setB(SECP256K1_B, (short) 0x00, (short) SECP256K1_B.length);
key.setFieldFP(SECP256K1_FP, (short) 0x00, (short) SECP256K1_FP.length);
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/im/status/keycard/SecureChannel.java
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ public SecureChannel(byte pairingLimit, Crypto crypto, SECP256k1 secp256k1) {
pairingKeys = new byte[(short)(PAIRING_KEY_LENGTH * pairingLimit)];

scKeypair = new KeyPair(KeyPair.ALG_EC_FP, SC_KEY_LENGTH);
secp256k1.setCurveParameters((ECKey) scKeypair.getPrivate());
secp256k1.setCurveParameters((ECKey) scKeypair.getPublic());
SECP256k1.setCurveParameters((ECKey) scKeypair.getPrivate());
SECP256k1.setCurveParameters((ECKey) scKeypair.getPublic());
scKeypair.genKeyPair();

remainingSlots = pairingLimit;
Expand Down
11 changes: 11 additions & 0 deletions src/main/java/im/status/keycard/SharedMemory.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
package im.status.keycard;

import javacard.security.*;

/**
* Keep references to data structures shared across applet instances of this package.
*/
class SharedMemory {
static final short CERT_LEN = 98;

/** The NDEF data file. Read through the NDEFApplet. **/
static final byte[] ndefDataFile = new byte[SecureChannel.SC_MAX_PLAIN_LENGTH + 1];

/** The Cash data file. Read through the CashApplet. **/
static final byte[] cashDataFile = new byte[KeycardApplet.MAX_DATA_LENGTH + 1];

/** The identification private key **/
static ECPrivateKey idPrivate = null;

/** The certificate. It is the concatenation of: compressed id public key, CA signature.
* The signature is in the format r,s,v where v allows recovering the signer public key. */
static final byte[] idCert = new byte[(short)(CERT_LEN + 1)];
}
Loading

0 comments on commit 9677aef

Please sign in to comment.