mirror of
https://github.com/status-im/status-keycard.git
synced 2025-02-21 09:38:14 +00:00
commit
9677aefcbb
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,10 +1,12 @@
|
|||||||
# IntelliJ IDEA project files
|
# IntelliJ IDEA project files
|
||||||
.idea
|
.idea
|
||||||
*.iml
|
*.iml
|
||||||
|
/.vscode
|
||||||
|
|
||||||
# Gradle output
|
# Gradle output
|
||||||
/.gradle
|
/.gradle
|
||||||
/build
|
/build
|
||||||
|
/bin
|
||||||
/gradle.properties
|
/gradle.properties
|
||||||
buildSrc/build
|
buildSrc/build
|
||||||
buildSrc/.gradle
|
buildSrc/.gradle
|
@ -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:3.0.4'
|
classpath 'com.github.status-im.status-keycard-java:desktop:31f4ab5'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,6 +31,10 @@ javacard {
|
|||||||
aid = '0xA0:0x00:0x00:0x08:0x04:0x00:0x01:0x03'
|
aid = '0xA0:0x00:0x00:0x08:0x04:0x00:0x01:0x03'
|
||||||
className = 'CashApplet'
|
className = 'CashApplet'
|
||||||
}
|
}
|
||||||
|
applet {
|
||||||
|
aid = '0xA0:0x00:0x00:0x08:0x04:0x00:0x01:0x04'
|
||||||
|
className = 'IdentApplet'
|
||||||
|
}
|
||||||
version = '3.1'
|
version = '3.1'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -55,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:3.0.4')
|
testCompile('com.github.status-im.status-keycard-java:desktop:31f4ab5')
|
||||||
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")
|
||||||
|
@ -4,5 +4,5 @@ repositories {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile 'com.github.status-im.status-keycard-java:desktop:3.0.4'
|
compile 'com.github.status-im.status-keycard-java:desktop:31f4ab5'
|
||||||
}
|
}
|
@ -1,9 +1,11 @@
|
|||||||
package im.status.keycard.build;
|
package im.status.keycard.build;
|
||||||
|
|
||||||
|
import im.status.keycard.applet.Identifiers;
|
||||||
import im.status.keycard.desktop.PCSCCardChannel;
|
import im.status.keycard.desktop.PCSCCardChannel;
|
||||||
import im.status.keycard.globalplatform.GlobalPlatformCommandSet;
|
import im.status.keycard.globalplatform.GlobalPlatformCommandSet;
|
||||||
import im.status.keycard.globalplatform.LoadCallback;
|
import im.status.keycard.globalplatform.LoadCallback;
|
||||||
import im.status.keycard.io.APDUException;
|
import im.status.keycard.io.APDUException;
|
||||||
|
import org.bouncycastle.util.encoders.Hex;
|
||||||
import org.gradle.api.DefaultTask;
|
import org.gradle.api.DefaultTask;
|
||||||
import org.gradle.api.GradleException;
|
import org.gradle.api.GradleException;
|
||||||
import org.gradle.api.logging.Logger;
|
import org.gradle.api.logging.Logger;
|
||||||
@ -69,6 +71,8 @@ public class InstallTask extends DefaultTask {
|
|||||||
cmdSet.installNDEFApplet(new byte[0]).checkOK();
|
cmdSet.installNDEFApplet(new byte[0]).checkOK();
|
||||||
logger.info("Installing the Cash Applet");
|
logger.info("Installing the Cash Applet");
|
||||||
cmdSet.installCashApplet().checkOK();
|
cmdSet.installCashApplet().checkOK();
|
||||||
|
logger.info("Installing the Identifier Applet");
|
||||||
|
cmdSet.installIdentApplet().checkOK();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new GradleException("I/O error", e);
|
throw new GradleException("I/O error", e);
|
||||||
} catch (APDUException e) {
|
} catch (APDUException e) {
|
||||||
|
@ -12,7 +12,6 @@ public class CashApplet extends Applet {
|
|||||||
private ECPrivateKey privateKey;
|
private ECPrivateKey privateKey;
|
||||||
|
|
||||||
private Crypto crypto;
|
private Crypto crypto;
|
||||||
private SECP256k1 secp256k1;
|
|
||||||
|
|
||||||
private Signature signature;
|
private Signature signature;
|
||||||
|
|
||||||
@ -40,17 +39,15 @@ public class CashApplet extends Applet {
|
|||||||
*/
|
*/
|
||||||
public CashApplet(byte[] bArray, short bOffset, byte bLength) {
|
public CashApplet(byte[] bArray, short bOffset, byte bLength) {
|
||||||
crypto = new Crypto();
|
crypto = new Crypto();
|
||||||
secp256k1 = new SECP256k1();
|
|
||||||
|
|
||||||
keypair = new KeyPair(KeyPair.ALG_EC_FP, SECP256k1.SECP256K1_KEY_SIZE);
|
keypair = new KeyPair(KeyPair.ALG_EC_FP, SECP256k1.SECP256K1_KEY_SIZE);
|
||||||
publicKey = (ECPublicKey) keypair.getPublic();
|
publicKey = (ECPublicKey) keypair.getPublic();
|
||||||
privateKey = (ECPrivateKey) keypair.getPrivate();
|
privateKey = (ECPrivateKey) keypair.getPrivate();
|
||||||
secp256k1.setCurveParameters(publicKey);
|
SECP256k1.setCurveParameters(publicKey);
|
||||||
secp256k1.setCurveParameters(privateKey);
|
SECP256k1.setCurveParameters(privateKey);
|
||||||
keypair.genKeyPair();
|
keypair.genKeyPair();
|
||||||
|
|
||||||
signature = Signature.getInstance(Signature.ALG_ECDSA_SHA_256, false);
|
signature = Signature.getInstance(Signature.ALG_ECDSA_SHA_256, false);
|
||||||
signature.init(privateKey, Signature.MODE_SIGN);
|
|
||||||
|
|
||||||
short c9Off = (short)(bOffset + bArray[bOffset] + 1); // Skip AID
|
short c9Off = (short)(bOffset + bArray[bOffset] + 1); // Skip AID
|
||||||
c9Off += (short)(bArray[c9Off] + 1); // Skip Privileges and parameter length
|
c9Off += (short)(bArray[c9Off] + 1); // Skip Privileges and parameter length
|
||||||
@ -79,6 +76,9 @@ public class CashApplet extends Applet {
|
|||||||
case KeycardApplet.INS_SIGN:
|
case KeycardApplet.INS_SIGN:
|
||||||
sign(apdu);
|
sign(apdu);
|
||||||
break;
|
break;
|
||||||
|
case IdentApplet.INS_IDENTIFY_CARD:
|
||||||
|
IdentApplet.identifyCard(apdu, null, signature);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
|
ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
|
||||||
break;
|
break;
|
||||||
@ -129,6 +129,7 @@ public class CashApplet extends Applet {
|
|||||||
outLen += 5;
|
outLen += 5;
|
||||||
short sigOff = (short) (SIGN_OUT_OFF + outLen);
|
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 += signature.signPreComputedHash(apduBuffer, ISO7816.OFFSET_CDATA, MessageDigest.LENGTH_SHA_256, apduBuffer, sigOff);
|
||||||
outLen += crypto.fixS(apduBuffer, sigOff);
|
outLen += crypto.fixS(apduBuffer, sigOff);
|
||||||
|
|
||||||
|
142
src/main/java/im/status/keycard/IdentApplet.java
Normal file
142
src/main/java/im/status/keycard/IdentApplet.java
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -247,6 +247,9 @@ public class KeycardApplet extends Applet {
|
|||||||
case SecureChannel.INS_UNPAIR:
|
case SecureChannel.INS_UNPAIR:
|
||||||
unpair(apdu);
|
unpair(apdu);
|
||||||
break;
|
break;
|
||||||
|
case IdentApplet.INS_IDENTIFY_CARD:
|
||||||
|
IdentApplet.identifyCard(apdu, secureChannel, signature);
|
||||||
|
break;
|
||||||
case INS_GET_STATUS:
|
case INS_GET_STATUS:
|
||||||
getStatus(apdu);
|
getStatus(apdu);
|
||||||
break;
|
break;
|
||||||
@ -363,6 +366,8 @@ public class KeycardApplet extends Applet {
|
|||||||
puk.update(apduBuffer, (short)(ISO7816.OFFSET_CDATA + PIN_LENGTH), PUK_LENGTH);
|
puk.update(apduBuffer, (short)(ISO7816.OFFSET_CDATA + PIN_LENGTH), PUK_LENGTH);
|
||||||
|
|
||||||
JCSystem.commitTransaction();
|
JCSystem.commitTransaction();
|
||||||
|
} else if (apduBuffer[ISO7816.OFFSET_INS] == IdentApplet.INS_IDENTIFY_CARD) {
|
||||||
|
IdentApplet.identifyCard(apdu, null, signature);
|
||||||
} else {
|
} else {
|
||||||
ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
|
ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
|
||||||
}
|
}
|
||||||
@ -685,7 +690,7 @@ public class KeycardApplet extends Applet {
|
|||||||
*/
|
*/
|
||||||
private void resetKeyStatus() {
|
private void resetKeyStatus() {
|
||||||
parentPrivateKey.clearKey();
|
parentPrivateKey.clearKey();
|
||||||
secp256k1.setCurveParameters(parentPrivateKey);
|
SECP256k1.setCurveParameters(parentPrivateKey);
|
||||||
keyPathLen = 0;
|
keyPathLen = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1437,16 +1442,16 @@ public class KeycardApplet extends Applet {
|
|||||||
* Set curve parameters to cleared keys
|
* Set curve parameters to cleared keys
|
||||||
*/
|
*/
|
||||||
private void resetCurveParameters() {
|
private void resetCurveParameters() {
|
||||||
secp256k1.setCurveParameters(masterPublic);
|
SECP256k1.setCurveParameters(masterPublic);
|
||||||
secp256k1.setCurveParameters(masterPrivate);
|
SECP256k1.setCurveParameters(masterPrivate);
|
||||||
|
|
||||||
secp256k1.setCurveParameters(parentPublicKey);
|
SECP256k1.setCurveParameters(parentPublicKey);
|
||||||
secp256k1.setCurveParameters(parentPrivateKey);
|
SECP256k1.setCurveParameters(parentPrivateKey);
|
||||||
|
|
||||||
secp256k1.setCurveParameters(publicKey);
|
SECP256k1.setCurveParameters(publicKey);
|
||||||
secp256k1.setCurveParameters(privateKey);
|
SECP256k1.setCurveParameters(privateKey);
|
||||||
|
|
||||||
secp256k1.setCurveParameters(pinlessPublicKey);
|
SECP256k1.setCurveParameters(pinlessPublicKey);
|
||||||
secp256k1.setCurveParameters(pinlessPrivateKey);
|
SECP256k1.setCurveParameters(pinlessPrivateKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -70,7 +70,7 @@ public class SECP256k1 {
|
|||||||
*
|
*
|
||||||
* @param key the key where the curve parameters must be set
|
* @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.setA(SECP256K1_A, (short) 0x00, (short) SECP256K1_A.length);
|
||||||
key.setB(SECP256K1_B, (short) 0x00, (short) SECP256K1_B.length);
|
key.setB(SECP256K1_B, (short) 0x00, (short) SECP256K1_B.length);
|
||||||
key.setFieldFP(SECP256K1_FP, (short) 0x00, (short) SECP256K1_FP.length);
|
key.setFieldFP(SECP256K1_FP, (short) 0x00, (short) SECP256K1_FP.length);
|
||||||
|
@ -60,8 +60,8 @@ public class SecureChannel {
|
|||||||
pairingKeys = new byte[(short)(PAIRING_KEY_LENGTH * pairingLimit)];
|
pairingKeys = new byte[(short)(PAIRING_KEY_LENGTH * pairingLimit)];
|
||||||
|
|
||||||
scKeypair = new KeyPair(KeyPair.ALG_EC_FP, SC_KEY_LENGTH);
|
scKeypair = new KeyPair(KeyPair.ALG_EC_FP, SC_KEY_LENGTH);
|
||||||
secp256k1.setCurveParameters((ECKey) scKeypair.getPrivate());
|
SECP256k1.setCurveParameters((ECKey) scKeypair.getPrivate());
|
||||||
secp256k1.setCurveParameters((ECKey) scKeypair.getPublic());
|
SECP256k1.setCurveParameters((ECKey) scKeypair.getPublic());
|
||||||
scKeypair.genKeyPair();
|
scKeypair.genKeyPair();
|
||||||
|
|
||||||
remainingSlots = pairingLimit;
|
remainingSlots = pairingLimit;
|
||||||
|
@ -1,12 +1,23 @@
|
|||||||
package im.status.keycard;
|
package im.status.keycard;
|
||||||
|
|
||||||
|
import javacard.security.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Keep references to data structures shared across applet instances of this package.
|
* Keep references to data structures shared across applet instances of this package.
|
||||||
*/
|
*/
|
||||||
class SharedMemory {
|
class SharedMemory {
|
||||||
|
static final short CERT_LEN = 98;
|
||||||
|
|
||||||
/** The NDEF data file. Read through the NDEFApplet. **/
|
/** The NDEF data file. Read through the NDEFApplet. **/
|
||||||
static final byte[] ndefDataFile = new byte[SecureChannel.SC_MAX_PLAIN_LENGTH + 1];
|
static final byte[] ndefDataFile = new byte[SecureChannel.SC_MAX_PLAIN_LENGTH + 1];
|
||||||
|
|
||||||
/** The Cash data file. Read through the CashApplet. **/
|
/** The Cash data file. Read through the CashApplet. **/
|
||||||
static final byte[] cashDataFile = new byte[KeycardApplet.MAX_DATA_LENGTH + 1];
|
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)];
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import com.licel.jcardsim.smartcardio.CardSimulator;
|
|||||||
import com.licel.jcardsim.smartcardio.CardTerminalSimulator;
|
import com.licel.jcardsim.smartcardio.CardTerminalSimulator;
|
||||||
import com.licel.jcardsim.utils.AIDUtil;
|
import com.licel.jcardsim.utils.AIDUtil;
|
||||||
import im.status.keycard.applet.*;
|
import im.status.keycard.applet.*;
|
||||||
|
import im.status.keycard.applet.Certificate;
|
||||||
import im.status.keycard.desktop.LedgerUSBManager;
|
import im.status.keycard.desktop.LedgerUSBManager;
|
||||||
import im.status.keycard.desktop.PCSCCardChannel;
|
import im.status.keycard.desktop.PCSCCardChannel;
|
||||||
import im.status.keycard.io.APDUCommand;
|
import im.status.keycard.io.APDUCommand;
|
||||||
@ -56,6 +57,7 @@ public class KeycardTest {
|
|||||||
private static CardChannel apduChannel;
|
private static CardChannel apduChannel;
|
||||||
private static im.status.keycard.io.CardChannel sdkChannel;
|
private static im.status.keycard.io.CardChannel sdkChannel;
|
||||||
private static CardSimulator simulator;
|
private static CardSimulator simulator;
|
||||||
|
private static KeyPair caKeyPair;
|
||||||
|
|
||||||
private static LedgerUSBManager usbManager;
|
private static LedgerUSBManager usbManager;
|
||||||
|
|
||||||
@ -102,6 +104,8 @@ public class KeycardTest {
|
|||||||
throw new IllegalStateException("Unknown target");
|
throw new IllegalStateException("Unknown target");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
caKeyPair = Certificate.generateIdentKeyPair();
|
||||||
|
|
||||||
initIfNeeded();
|
initIfNeeded();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,6 +161,15 @@ public class KeycardTest {
|
|||||||
simulator.installApplet(aid, CashApplet.class, bos.toByteArray(), (short) 0, (byte) bos.size());
|
simulator.installApplet(aid, CashApplet.class, bos.toByteArray(), (short) 0, (byte) bos.size());
|
||||||
bos.reset();
|
bos.reset();
|
||||||
|
|
||||||
|
// Install CashApplet
|
||||||
|
aid = AIDUtil.create(Identifiers.IDENT_AID);
|
||||||
|
bos.write(Identifiers.IDENT_INSTANCE_AID.length);
|
||||||
|
bos.write(Identifiers.IDENT_INSTANCE_AID);
|
||||||
|
bos.write(new byte[] {0x01, 0x00, 0x02, (byte) 0xC9, 0x00});
|
||||||
|
|
||||||
|
simulator.installApplet(aid, IdentApplet.class, bos.toByteArray(), (short) 0, (byte) bos.size());
|
||||||
|
bos.reset();
|
||||||
|
|
||||||
cardTerminal = CardTerminalSimulator.terminal(simulator);
|
cardTerminal = CardTerminalSimulator.terminal(simulator);
|
||||||
|
|
||||||
openPCSCChannel();
|
openPCSCChannel();
|
||||||
@ -198,6 +211,12 @@ public class KeycardTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void initIfNeeded() throws Exception {
|
private static void initIfNeeded() throws Exception {
|
||||||
|
KeyPair identKeyPair = Certificate.generateIdentKeyPair();
|
||||||
|
Certificate cert = Certificate.createCertificate(caKeyPair, identKeyPair);
|
||||||
|
IdentCommandSet idCmdSet = new IdentCommandSet(sdkChannel);
|
||||||
|
idCmdSet.select().checkOK();
|
||||||
|
idCmdSet.storeData(cert.toStoreData()).checkOK();
|
||||||
|
|
||||||
KeycardCommandSet cmdSet = new KeycardCommandSet(sdkChannel);
|
KeycardCommandSet cmdSet = new KeycardCommandSet(sdkChannel);
|
||||||
cmdSet.select().checkOK();
|
cmdSet.select().checkOK();
|
||||||
|
|
||||||
@ -255,6 +274,41 @@ public class KeycardTest {
|
|||||||
assertTrue(new ApplicationInfo(data).isInitializedCard());
|
assertTrue(new ApplicationInfo(data).isInitializedCard());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("IDENT command")
|
||||||
|
void identTest() throws Exception {
|
||||||
|
APDUResponse response = cmdSet.identifyCard(new byte[33]);
|
||||||
|
assertEquals(0x6a80, response.getSw());
|
||||||
|
|
||||||
|
byte[] challenge = new byte[32];
|
||||||
|
Random random = new Random();
|
||||||
|
byte[] expectedCaPub = ((ECPublicKey) caKeyPair.getPublic()).getQ().getEncoded(true);
|
||||||
|
|
||||||
|
|
||||||
|
random.nextBytes(challenge);
|
||||||
|
response = cmdSet.identifyCard(challenge);
|
||||||
|
assertEquals(0x9000, response.getSw());
|
||||||
|
byte[] caPub = Certificate.verifyIdentity(challenge, response.getData());
|
||||||
|
assertArrayEquals(expectedCaPub, caPub);
|
||||||
|
|
||||||
|
cmdSet.autoOpenSecureChannel();
|
||||||
|
|
||||||
|
random.nextBytes(challenge);
|
||||||
|
response = cmdSet.identifyCard(challenge);
|
||||||
|
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
|
@Test
|
||||||
@DisplayName("OPEN SECURE CHANNEL command")
|
@DisplayName("OPEN SECURE CHANNEL command")
|
||||||
@Capabilities("secureChannel")
|
@Capabilities("secureChannel")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user