Skip to content

Commit

Permalink
add identify card command to cash applet
Browse files Browse the repository at this point in the history
  • Loading branch information
bitgamma committed Jul 26, 2022
1 parent a692b6b commit 01c46f7
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 57 deletions.
3 changes: 3 additions & 0 deletions src/main/java/im/status/keycard/CashApplet.java
Original file line number Diff line number Diff line change
Expand Up @@ -76,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
57 changes: 54 additions & 3 deletions src/main/java/im/status/keycard/IdentApplet.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@
* 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.
Expand Down Expand Up @@ -65,15 +70,15 @@ private void processSelect(APDU apdu) {
byte[] apduBuffer = apdu.getBuffer();
apdu.setIncomingAndReceive();

if (SharedMemory.idCert[0] == SharedMemory.CERT_VALID) {
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] == SharedMemory.CERT_VALID) {
if (SharedMemory.idCert[0] == CERT_VALID) {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}

Expand All @@ -86,6 +91,52 @@ private void processStoreData(APDU apdu) {

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] = SharedMemory.CERT_VALID;
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);
}
}
}
57 changes: 4 additions & 53 deletions src/main/java/im/status/keycard/KeycardApplet.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ public class KeycardApplet extends Applet {

static final byte INS_GET_STATUS = (byte) 0xF2;
static final byte INS_INIT = (byte) 0xFE;
static final byte INS_IDENTIFY_CARD = (byte) 0x14;
static final byte INS_VERIFY_PIN = (byte) 0x20;
static final byte INS_CHANGE_PIN = (byte) 0x21;
static final byte INS_UNBLOCK_PIN = (byte) 0x22;
Expand Down Expand Up @@ -99,7 +98,6 @@ public class KeycardApplet extends Applet {
static final byte TLV_UID = (byte) 0x8F;
static final byte TLV_KEY_UID = (byte) 0x8E;
static final byte TLV_CAPABILITIES = (byte) 0x8D;
static final byte TLV_CERT = (byte) 0x8A;

static final byte CAPABILITY_SECURE_CHANNEL = (byte) 0x01;
static final byte CAPABILITY_KEY_MANAGEMENT = (byte) 0x02;
Expand Down Expand Up @@ -249,8 +247,8 @@ public void process(APDU apdu) throws ISOException {
case SecureChannel.INS_UNPAIR:
unpair(apdu);
break;
case INS_IDENTIFY_CARD:
identifyCard(apdu);
case IdentApplet.INS_IDENTIFY_CARD:
IdentApplet.identifyCard(apdu, secureChannel, signature);
break;
case INS_GET_STATUS:
getStatus(apdu);
Expand Down Expand Up @@ -368,8 +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] == INS_IDENTIFY_CARD) {
identifyCard(apdu);
} 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 @@ -479,53 +477,6 @@ private void getStatus(APDU apdu) {
secureChannel.respond(apdu, len, ISO7816.SW_NO_ERROR);
}

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

short len;

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

if (SharedMemory.idCert[0] != SharedMemory.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++] = 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.isOpen()) {
secureChannel.respond(apdu, outLen, ISO7816.SW_NO_ERROR);
} else {
apdu.setOutgoingAndSend(SecureChannel.SC_OUT_OFFSET, outLen);
}

}

/**
* Writes the Application Status Template to the APDU buffer. Invoked internally by the getStatus method. This
* template is useful to understand if the card is blocked, if it has valid keys and if public key derivation is
Expand Down
1 change: 0 additions & 1 deletion src/main/java/im/status/keycard/SharedMemory.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
* Keep references to data structures shared across applet instances of this package.
*/
class SharedMemory {
static final byte CERT_VALID = (byte) 0xAA;
static final short CERT_LEN = 98;

/** The NDEF data file. Read through the NDEFApplet. **/
Expand Down
9 changes: 9 additions & 0 deletions src/test/java/im/status/keycard/KeycardTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,15 @@ void identTest() throws Exception {
assertEquals(0x9000, response.getSw());
caPub = Certificate.verifyIdentity(challenge, response.getData());
assertArrayEquals(expectedCaPub, caPub);

random.nextBytes(challenge);
CashCommandSet cashCmdSet = new CashCommandSet(sdkChannel);
response = cashCmdSet.select();
assertEquals(0x9000, response.getSw());
response = cashCmdSet.identifyCard(challenge);
assertEquals(0x9000, response.getSw());
caPub = Certificate.verifyIdentity(challenge, response.getData());
assertArrayEquals(expectedCaPub, caPub);
}

@Test
Expand Down

0 comments on commit 01c46f7

Please sign in to comment.