commit
c574944153
|
@ -54,7 +54,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:425d085')
|
testCompile('com.github.status-im.status-keycard-java:desktop:4ec4e07')
|
||||||
testCompile('org.bouncycastle:bcprov-jdk15on:1.60')
|
testCompile('org.bouncycastle:bcprov-jdk15on:1.60')
|
||||||
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")
|
||||||
|
|
|
@ -8,7 +8,7 @@ import javacardx.crypto.Cipher;
|
||||||
* 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) 0x0201;
|
static final short APPLICATION_VERSION = (short) 0x0202;
|
||||||
|
|
||||||
static final byte INS_GET_STATUS = (byte) 0xF2;
|
static final byte INS_GET_STATUS = (byte) 0xF2;
|
||||||
static final byte INS_SET_NDEF = (byte) 0xF3;
|
static final byte INS_SET_NDEF = (byte) 0xF3;
|
||||||
|
@ -26,6 +26,8 @@ public class KeycardApplet extends Applet {
|
||||||
static final byte INS_SET_PINLESS_PATH = (byte) 0xC1;
|
static final byte INS_SET_PINLESS_PATH = (byte) 0xC1;
|
||||||
static final byte INS_EXPORT_KEY = (byte) 0xC2;
|
static final byte INS_EXPORT_KEY = (byte) 0xC2;
|
||||||
|
|
||||||
|
static final short SW_REFERENCED_DATA_NOT_FOUND = (short) 0x6A88;
|
||||||
|
|
||||||
static final byte PUK_LENGTH = 12;
|
static final byte PUK_LENGTH = 12;
|
||||||
static final byte PUK_MAX_RETRIES = 5;
|
static final byte PUK_MAX_RETRIES = 5;
|
||||||
static final byte PIN_LENGTH = 6;
|
static final byte PIN_LENGTH = 6;
|
||||||
|
@ -63,6 +65,11 @@ public class KeycardApplet extends Applet {
|
||||||
static final byte DUPLICATE_KEY_P1_EXPORT = 0x02;
|
static final byte DUPLICATE_KEY_P1_EXPORT = 0x02;
|
||||||
static final byte DUPLICATE_KEY_P1_IMPORT = 0x03;
|
static final byte DUPLICATE_KEY_P1_IMPORT = 0x03;
|
||||||
|
|
||||||
|
static final byte SIGN_P1_CURRENT_KEY = 0x00;
|
||||||
|
static final byte SIGN_P1_DERIVE = 0x01;
|
||||||
|
static final byte SIGN_P1_DERIVE_AND_MAKE_CURRENT = 0x02;
|
||||||
|
static final byte SIGN_P1_PINLESS = 0x03;
|
||||||
|
|
||||||
static final byte EXPORT_KEY_P1_CURRENT = 0x00;
|
static final byte EXPORT_KEY_P1_CURRENT = 0x00;
|
||||||
static final byte EXPORT_KEY_P1_DERIVE = 0x01;
|
static final byte EXPORT_KEY_P1_DERIVE = 0x01;
|
||||||
static final byte EXPORT_KEY_P1_DERIVE_AND_MAKE_CURRENT = 0x02;
|
static final byte EXPORT_KEY_P1_DERIVE_AND_MAKE_CURRENT = 0x02;
|
||||||
|
@ -113,6 +120,9 @@ public class KeycardApplet extends Applet {
|
||||||
private ECPrivateKey privateKey;
|
private ECPrivateKey privateKey;
|
||||||
private byte[] chainCode;
|
private byte[] chainCode;
|
||||||
|
|
||||||
|
private ECPublicKey pinlessPublicKey;
|
||||||
|
private ECPrivateKey pinlessPrivateKey;
|
||||||
|
|
||||||
private byte[] keyPath;
|
private byte[] keyPath;
|
||||||
private short keyPathLen;
|
private short keyPathLen;
|
||||||
|
|
||||||
|
@ -171,6 +181,9 @@ public class KeycardApplet extends Applet {
|
||||||
publicKey = (ECPublicKey) KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PUBLIC, SECP256k1.SECP256K1_KEY_SIZE, false);
|
publicKey = (ECPublicKey) KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PUBLIC, SECP256k1.SECP256K1_KEY_SIZE, false);
|
||||||
privateKey = (ECPrivateKey) KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PRIVATE, SECP256k1.SECP256K1_KEY_SIZE, false);
|
privateKey = (ECPrivateKey) KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PRIVATE, SECP256k1.SECP256K1_KEY_SIZE, false);
|
||||||
|
|
||||||
|
pinlessPublicKey = (ECPublicKey) KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PUBLIC, SECP256k1.SECP256K1_KEY_SIZE, false);
|
||||||
|
pinlessPrivateKey = (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];
|
||||||
parentChainCode = new byte[CHAIN_CODE_SIZE];
|
parentChainCode = new byte[CHAIN_CODE_SIZE];
|
||||||
chainCode = new byte[CHAIN_CODE_SIZE];
|
chainCode = new byte[CHAIN_CODE_SIZE];
|
||||||
|
@ -646,6 +659,7 @@ public class KeycardApplet extends Applet {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pinlessPathLen = 0;
|
||||||
generateKeyUIDAndRespond(apdu, apduBuffer);
|
generateKeyUIDAndRespond(apdu, apduBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -789,21 +803,22 @@ public class KeycardApplet extends Applet {
|
||||||
byte[] apduBuffer = apdu.getBuffer();
|
byte[] apduBuffer = apdu.getBuffer();
|
||||||
short len = secureChannel.preprocessAPDU(apduBuffer);
|
short len = secureChannel.preprocessAPDU(apduBuffer);
|
||||||
|
|
||||||
if (!((pin.isValidated() || (pinlessPathLen > 0)))) {
|
if (!pin.isValidated()) {
|
||||||
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
|
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
|
||||||
}
|
}
|
||||||
|
|
||||||
doDerive(apduBuffer, len, apduBuffer[ISO7816.OFFSET_P1], true);
|
doDerive(apduBuffer, (short) 0, len, apduBuffer[ISO7816.OFFSET_P1], true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal derivation function, called by DERIVE KEY and EXPORT KEY
|
* Internal derivation function, called by DERIVE KEY and EXPORT KEY
|
||||||
* @param apduBuffer the APDU buffer
|
* @param apduBuffer the APDU buffer
|
||||||
* @param len the APDU len
|
* @param off the offset in the APDU buffer relative to the data field
|
||||||
|
* @param len the len of the path
|
||||||
* @param source derivation source
|
* @param source derivation source
|
||||||
* @param makeCurrent whether the results should be saved or not
|
* @param makeCurrent whether the results should be saved or not
|
||||||
*/
|
*/
|
||||||
private void doDerive(byte[] apduBuffer, short len, byte source, boolean makeCurrent) {
|
private void doDerive(byte[] apduBuffer, short off, short len, byte source, boolean makeCurrent) {
|
||||||
if (!isExtended) {
|
if (!isExtended) {
|
||||||
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
|
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
|
||||||
}
|
}
|
||||||
|
@ -862,7 +877,8 @@ public class KeycardApplet extends Applet {
|
||||||
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
|
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
|
||||||
}
|
}
|
||||||
|
|
||||||
short scratchOff = (short) (ISO7816.OFFSET_CDATA + len);
|
short pathOff = (short) (ISO7816.OFFSET_CDATA + off);
|
||||||
|
short scratchOff = (short) (pathOff + len);
|
||||||
short dataOff = (short) (scratchOff + Crypto.KEY_DERIVATION_SCRATCH_SIZE);
|
short dataOff = (short) (scratchOff + Crypto.KEY_DERIVATION_SCRATCH_SIZE);
|
||||||
|
|
||||||
short pubKeyOff = (short) (dataOff + sourcePriv.getS(apduBuffer, dataOff));
|
short pubKeyOff = (short) (dataOff + sourcePriv.getS(apduBuffer, dataOff));
|
||||||
|
@ -874,8 +890,8 @@ public class KeycardApplet extends Applet {
|
||||||
apduBuffer[pubKeyOff] = 0;
|
apduBuffer[pubKeyOff] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (short i = ISO7816.OFFSET_CDATA; i < scratchOff; i += 4) {
|
for (short i = pathOff; i < scratchOff; i += 4) {
|
||||||
if (i > ISO7816.OFFSET_CDATA) {
|
if (i > pathOff) {
|
||||||
Util.arrayCopyNonAtomic(derivationOutput, (short) 0, apduBuffer, dataOff, (short) (Crypto.KEY_SECRET_SIZE + CHAIN_CODE_SIZE));
|
Util.arrayCopyNonAtomic(derivationOutput, (short) 0, apduBuffer, dataOff, (short) (Crypto.KEY_SECRET_SIZE + CHAIN_CODE_SIZE));
|
||||||
|
|
||||||
if (!crypto.bip32IsHardened(apduBuffer, i)) {
|
if (!crypto.bip32IsHardened(apduBuffer, i)) {
|
||||||
|
@ -908,7 +924,7 @@ public class KeycardApplet extends Applet {
|
||||||
secp256k1.derivePublicKey(privateKey, apduBuffer, scratchOff);
|
secp256k1.derivePublicKey(privateKey, apduBuffer, scratchOff);
|
||||||
publicKey.setW(apduBuffer, scratchOff, Crypto.KEY_PUB_SIZE);
|
publicKey.setW(apduBuffer, scratchOff, Crypto.KEY_PUB_SIZE);
|
||||||
|
|
||||||
Util.arrayCopy(apduBuffer, ISO7816.OFFSET_CDATA, keyPath, pathLenOff, len);
|
Util.arrayCopy(apduBuffer, pathOff, keyPath, pathLenOff, len);
|
||||||
keyPathLen = newPathLen;
|
keyPathLen = newPathLen;
|
||||||
JCSystem.commitTransaction();
|
JCSystem.commitTransaction();
|
||||||
}
|
}
|
||||||
|
@ -1030,6 +1046,8 @@ public class KeycardApplet extends Applet {
|
||||||
masterPublic.clearKey();
|
masterPublic.clearKey();
|
||||||
parentPrivateKey.clearKey();
|
parentPrivateKey.clearKey();
|
||||||
parentPublicKey.clearKey();
|
parentPublicKey.clearKey();
|
||||||
|
pinlessPrivateKey.clearKey();
|
||||||
|
pinlessPublicKey.clearKey();
|
||||||
resetCurveParameters();
|
resetCurveParameters();
|
||||||
Util.arrayFillNonAtomic(chainCode, (short) 0, (short) chainCode.length, (byte) 0);
|
Util.arrayFillNonAtomic(chainCode, (short) 0, (short) chainCode.length, (byte) 0);
|
||||||
Util.arrayFillNonAtomic(parentChainCode, (short) 0, (short) parentChainCode.length, (byte) 0);
|
Util.arrayFillNonAtomic(parentChainCode, (short) 0, (short) parentChainCode.length, (byte) 0);
|
||||||
|
@ -1057,6 +1075,7 @@ public class KeycardApplet extends Applet {
|
||||||
crypto.random.generateData(apduBuffer, ISO7816.OFFSET_CDATA, BIP39_SEED_SIZE);
|
crypto.random.generateData(apduBuffer, ISO7816.OFFSET_CDATA, BIP39_SEED_SIZE);
|
||||||
|
|
||||||
loadSeed(apduBuffer);
|
loadSeed(apduBuffer);
|
||||||
|
pinlessPathLen = 0;
|
||||||
generateKeyUIDAndRespond(apdu, apduBuffer);
|
generateKeyUIDAndRespond(apdu, apduBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1094,6 +1113,7 @@ public class KeycardApplet extends Applet {
|
||||||
break;
|
break;
|
||||||
case DUPLICATE_KEY_P1_IMPORT:
|
case DUPLICATE_KEY_P1_IMPORT:
|
||||||
importDuplicate(apduBuffer);
|
importDuplicate(apduBuffer);
|
||||||
|
pinlessPathLen = 0;
|
||||||
generateKeyUIDAndRespond(apdu, apduBuffer);
|
generateKeyUIDAndRespond(apdu, apduBuffer);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -1180,32 +1200,96 @@ public class KeycardApplet extends Applet {
|
||||||
*/
|
*/
|
||||||
private void sign(APDU apdu) {
|
private void sign(APDU apdu) {
|
||||||
byte[] apduBuffer = apdu.getBuffer();
|
byte[] apduBuffer = apdu.getBuffer();
|
||||||
short len = secureChannel.preprocessAPDU(apduBuffer);
|
boolean usePinless = false;
|
||||||
|
boolean derive = false;
|
||||||
|
boolean makeCurrent = false;
|
||||||
|
|
||||||
if (!((pin.isValidated() || isPinless()) && privateKey.isInitialized())) {
|
ECPrivateKey signingKey;
|
||||||
|
ECPublicKey outputKey;
|
||||||
|
|
||||||
|
switch((byte) (apduBuffer[ISO7816.OFFSET_P1] & ~DERIVE_P1_SOURCE_MASK)) {
|
||||||
|
case SIGN_P1_CURRENT_KEY:
|
||||||
|
signingKey = privateKey;
|
||||||
|
outputKey = publicKey;
|
||||||
|
break;
|
||||||
|
case SIGN_P1_DERIVE:
|
||||||
|
signingKey = secp256k1.tmpECPrivateKey;
|
||||||
|
outputKey = null;
|
||||||
|
derive = true;
|
||||||
|
break;
|
||||||
|
case SIGN_P1_DERIVE_AND_MAKE_CURRENT:
|
||||||
|
signingKey = privateKey;
|
||||||
|
outputKey = publicKey;
|
||||||
|
derive = true;
|
||||||
|
makeCurrent = true;
|
||||||
|
break;
|
||||||
|
case SIGN_P1_PINLESS:
|
||||||
|
usePinless = true;
|
||||||
|
signingKey = pinlessPrivateKey;
|
||||||
|
outputKey = pinlessPublicKey;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ISOException.throwIt(ISO7816.SW_WRONG_P1P2);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
short len;
|
||||||
|
|
||||||
|
if (usePinless && !secureChannel.isOpen()) {
|
||||||
|
len = (short) (apduBuffer[ISO7816.OFFSET_LC] & (short) 0xff);
|
||||||
|
} else {
|
||||||
|
len = secureChannel.preprocessAPDU(apduBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (usePinless && pinlessPathLen == 0) {
|
||||||
|
ISOException.throwIt(SW_REFERENCED_DATA_NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!((pin.isValidated() || usePinless || isPinless()) && privateKey.isInitialized())) {
|
||||||
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
|
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (len != MessageDigest.LENGTH_SHA_256) {
|
if (derive) {
|
||||||
|
short pathLen = (short) (len - MessageDigest.LENGTH_SHA_256);
|
||||||
|
|
||||||
|
if (pathLen <= 0) {
|
||||||
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
|
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
|
||||||
}
|
}
|
||||||
|
|
||||||
signature.init(privateKey, Signature.MODE_SIGN);
|
byte derivationSource = (byte) (apduBuffer[ISO7816.OFFSET_P1] & DERIVE_P1_SOURCE_MASK);
|
||||||
|
doDerive(apduBuffer, MessageDigest.LENGTH_SHA_256, pathLen, derivationSource, makeCurrent);
|
||||||
|
} else {
|
||||||
|
if (len != MessageDigest.LENGTH_SHA_256) {
|
||||||
|
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
apduBuffer[SecureChannel.SC_OUT_OFFSET] = TLV_SIGNATURE_TEMPLATE;
|
apduBuffer[SecureChannel.SC_OUT_OFFSET] = TLV_SIGNATURE_TEMPLATE;
|
||||||
apduBuffer[(short)(SecureChannel.SC_OUT_OFFSET + 3)] = TLV_PUB_KEY;
|
apduBuffer[(short)(SecureChannel.SC_OUT_OFFSET + 3)] = TLV_PUB_KEY;
|
||||||
short outLen = apduBuffer[(short)(SecureChannel.SC_OUT_OFFSET + 4)] = (byte) publicKey.getW(apduBuffer, (short) (SecureChannel.SC_OUT_OFFSET + 5));
|
short outLen = apduBuffer[(short)(SecureChannel.SC_OUT_OFFSET + 4)] = Crypto.KEY_PUB_SIZE;
|
||||||
|
|
||||||
|
if (outputKey != null) {
|
||||||
|
outputKey.getW(apduBuffer, (short) (SecureChannel.SC_OUT_OFFSET + 5));
|
||||||
|
} else {
|
||||||
|
secp256k1.derivePublicKey(derivationOutput, (short) 0, apduBuffer, (short) (SecureChannel.SC_OUT_OFFSET + 5));
|
||||||
|
}
|
||||||
|
|
||||||
outLen += 5;
|
outLen += 5;
|
||||||
short sigOff = (short) (SecureChannel.SC_OUT_OFFSET + outLen);
|
short sigOff = (short) (SecureChannel.SC_OUT_OFFSET + outLen);
|
||||||
|
|
||||||
outLen += signature.signPreComputedHash(apduBuffer, ISO7816.OFFSET_CDATA, len, apduBuffer, sigOff);
|
signature.init(signingKey, Signature.MODE_SIGN);
|
||||||
|
|
||||||
|
outLen += signature.signPreComputedHash(apduBuffer, ISO7816.OFFSET_CDATA, MessageDigest.LENGTH_SHA_256, apduBuffer, sigOff);
|
||||||
outLen += crypto.fixS(apduBuffer, sigOff);
|
outLen += crypto.fixS(apduBuffer, sigOff);
|
||||||
|
|
||||||
apduBuffer[(short)(SecureChannel.SC_OUT_OFFSET + 1)] = (byte) 0x81;
|
apduBuffer[(short)(SecureChannel.SC_OUT_OFFSET + 1)] = (byte) 0x81;
|
||||||
apduBuffer[(short)(SecureChannel.SC_OUT_OFFSET + 2)] = (byte) (outLen - 3);
|
apduBuffer[(short)(SecureChannel.SC_OUT_OFFSET + 2)] = (byte) (outLen - 3);
|
||||||
|
|
||||||
|
if (secureChannel.isOpen()) {
|
||||||
secureChannel.respond(apdu, outLen, ISO7816.SW_NO_ERROR);
|
secureChannel.respond(apdu, outLen, ISO7816.SW_NO_ERROR);
|
||||||
|
} else {
|
||||||
|
apdu.setOutgoingAndSend(SecureChannel.SC_OUT_OFFSET, outLen);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1231,6 +1315,14 @@ public class KeycardApplet extends Applet {
|
||||||
JCSystem.beginTransaction();
|
JCSystem.beginTransaction();
|
||||||
pinlessPathLen = len;
|
pinlessPathLen = len;
|
||||||
Util.arrayCopy(apduBuffer, ISO7816.OFFSET_CDATA, pinlessPath, (short) 0, len);
|
Util.arrayCopy(apduBuffer, ISO7816.OFFSET_CDATA, pinlessPath, (short) 0, len);
|
||||||
|
|
||||||
|
if (pinlessPathLen > 0) {
|
||||||
|
doDerive(apduBuffer, (short) 0, len, DERIVE_P1_SOURCE_MASTER, false);
|
||||||
|
pinlessPrivateKey.setS(derivationOutput, (short) 0, Crypto.KEY_SECRET_SIZE);
|
||||||
|
secp256k1.derivePublicKey(pinlessPrivateKey, apduBuffer, (short) 0);
|
||||||
|
pinlessPublicKey.setW(apduBuffer, (short) 0, Crypto.KEY_PUB_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
JCSystem.commitTransaction();
|
JCSystem.commitTransaction();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1292,7 +1384,7 @@ public class KeycardApplet extends Applet {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (derive) {
|
if (derive) {
|
||||||
doDerive(apduBuffer, dataLen, derivationSource, makeCurrent);
|
doDerive(apduBuffer, (short) 0, dataLen, derivationSource, makeCurrent);
|
||||||
}
|
}
|
||||||
|
|
||||||
short off = SecureChannel.SC_OUT_OFFSET;
|
short off = SecureChannel.SC_OUT_OFFSET;
|
||||||
|
@ -1379,5 +1471,8 @@ public class KeycardApplet extends Applet {
|
||||||
|
|
||||||
secp256k1.setCurveParameters(publicKey);
|
secp256k1.setCurveParameters(publicKey);
|
||||||
secp256k1.setCurveParameters(privateKey);
|
secp256k1.setCurveParameters(privateKey);
|
||||||
|
|
||||||
|
secp256k1.setCurveParameters(pinlessPublicKey);
|
||||||
|
secp256k1.setCurveParameters(pinlessPrivateKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,7 +55,7 @@ public class SECP256k1 {
|
||||||
|
|
||||||
private KeyAgreement ecPointMultiplier;
|
private KeyAgreement ecPointMultiplier;
|
||||||
private Crypto crypto;
|
private Crypto crypto;
|
||||||
private ECPrivateKey tmpECPrivateKey;
|
ECPrivateKey tmpECPrivateKey;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allocates objects needed by this class. Must be invoked during the applet installation exactly 1 time.
|
* Allocates objects needed by this class. Must be invoked during the applet installation exactly 1 time.
|
||||||
|
|
|
@ -37,11 +37,11 @@ import java.math.BigDecimal;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.ByteOrder;
|
import java.nio.ByteOrder;
|
||||||
import java.security.KeyFactory;
|
import java.security.*;
|
||||||
import java.security.KeyPair;
|
|
||||||
import java.security.KeyPairGenerator;
|
|
||||||
import java.security.Signature;
|
|
||||||
import org.bouncycastle.jce.interfaces.ECPublicKey;
|
import org.bouncycastle.jce.interfaces.ECPublicKey;
|
||||||
|
|
||||||
|
import java.security.spec.InvalidKeySpecException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
@ -1002,11 +1002,8 @@ public class KeycardTest {
|
||||||
assertEquals(0x9000, response.getSw());
|
assertEquals(0x9000, response.getSw());
|
||||||
}
|
}
|
||||||
|
|
||||||
Signature signature = Signature.getInstance("SHA256withECDSA", "BC");
|
|
||||||
|
|
||||||
if (!cmdSet.getApplicationInfo().hasMasterKey()) {
|
if (!cmdSet.getApplicationInfo().hasMasterKey()) {
|
||||||
KeyPair keyPair = keypairGenerator().generateKeyPair();
|
response = cmdSet.generateKey();
|
||||||
response = cmdSet.loadKey(keyPair);
|
|
||||||
assertEquals(0x9000, response.getSw());
|
assertEquals(0x9000, response.getSw());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1016,6 +1013,52 @@ public class KeycardTest {
|
||||||
|
|
||||||
// Correctly sign a precomputed hash
|
// Correctly sign a precomputed hash
|
||||||
response = cmdSet.sign(hash);
|
response = cmdSet.sign(hash);
|
||||||
|
verifySignResp(data, response);
|
||||||
|
|
||||||
|
// Sign and derive
|
||||||
|
String currentPath = new KeyPath(cmdSet.getStatus(KeycardCommandSet.GET_STATUS_P1_KEY_PATH).checkOK().getData()).toString();
|
||||||
|
String updatedPath = new KeyPath(currentPath + "/2").toString();
|
||||||
|
response = cmdSet.signWithPath(hash, updatedPath, false);
|
||||||
|
verifySignResp(data, response);
|
||||||
|
assertEquals(currentPath, new KeyPath(cmdSet.getStatus(KeycardCommandSet.GET_STATUS_P1_KEY_PATH).checkOK().getData()).toString());
|
||||||
|
response = cmdSet.signWithPath(hash, updatedPath, true);
|
||||||
|
verifySignResp(data, response);
|
||||||
|
assertEquals(updatedPath, new KeyPath(cmdSet.getStatus(KeycardCommandSet.GET_STATUS_P1_KEY_PATH).checkOK().getData()).toString());
|
||||||
|
|
||||||
|
// Sign with PINless
|
||||||
|
String pinlessPath = currentPath + "/3";
|
||||||
|
response = cmdSet.setPinlessPath(pinlessPath);
|
||||||
|
assertEquals(0x9000, response.getSw());
|
||||||
|
|
||||||
|
// No secure channel or PIN auth
|
||||||
|
response = cmdSet.select();
|
||||||
|
assertEquals(0x9000, response.getSw());
|
||||||
|
|
||||||
|
response = cmdSet.signPinless(hash);
|
||||||
|
verifySignResp(data, response);
|
||||||
|
|
||||||
|
// With secure channel
|
||||||
|
if (cmdSet.getApplicationInfo().hasSecureChannelCapability()) {
|
||||||
|
cmdSet.autoOpenSecureChannel();
|
||||||
|
response = cmdSet.signPinless(hash);
|
||||||
|
verifySignResp(data, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
// No pinless path
|
||||||
|
if (cmdSet.getApplicationInfo().hasCredentialsManagementCapability()) {
|
||||||
|
response = cmdSet.verifyPIN("000000");
|
||||||
|
assertEquals(0x9000, response.getSw());
|
||||||
|
}
|
||||||
|
|
||||||
|
response = cmdSet.resetPinlessPath();
|
||||||
|
assertEquals(0x9000, response.getSw());
|
||||||
|
|
||||||
|
response = cmdSet.signPinless(hash);
|
||||||
|
assertEquals(0x6A88, response.getSw());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void verifySignResp(byte[] data, APDUResponse response) throws Exception {
|
||||||
|
Signature signature = Signature.getInstance("SHA256withECDSA", "BC");
|
||||||
assertEquals(0x9000, response.getSw());
|
assertEquals(0x9000, response.getSw());
|
||||||
byte[] sig = response.getData();
|
byte[] sig = response.getData();
|
||||||
byte[] keyData = extractPublicKeyFromSignature(sig);
|
byte[] keyData = extractPublicKeyFromSignature(sig);
|
||||||
|
@ -1082,12 +1125,19 @@ public class KeycardTest {
|
||||||
resetAndSelectAndOpenSC();
|
resetAndSelectAndOpenSC();
|
||||||
response = cmdSet.sign(hash);
|
response = cmdSet.sign(hash);
|
||||||
assertEquals(0x6985, response.getSw());
|
assertEquals(0x6985, response.getSw());
|
||||||
|
|
||||||
|
if (cmdSet.getApplicationInfo().hasCredentialsManagementCapability()) {
|
||||||
|
response = cmdSet.verifyPIN("000000");
|
||||||
|
assertEquals(0x9000, response.getSw());
|
||||||
|
}
|
||||||
|
|
||||||
response = cmdSet.deriveKey(new byte[] {0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01}, KeycardApplet.DERIVE_P1_SOURCE_MASTER);
|
response = cmdSet.deriveKey(new byte[] {0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01}, KeycardApplet.DERIVE_P1_SOURCE_MASTER);
|
||||||
assertEquals(0x9000, response.getSw());
|
assertEquals(0x9000, response.getSw());
|
||||||
response = cmdSet.sign(hash);
|
|
||||||
assertEquals(0x6985, response.getSw());
|
|
||||||
response = cmdSet.deriveKey(new byte[] {0x00, 0x00, 0x00, 0x02}, KeycardApplet.DERIVE_P1_SOURCE_CURRENT);
|
response = cmdSet.deriveKey(new byte[] {0x00, 0x00, 0x00, 0x02}, KeycardApplet.DERIVE_P1_SOURCE_CURRENT);
|
||||||
assertEquals(0x9000, response.getSw());
|
assertEquals(0x9000, response.getSw());
|
||||||
|
|
||||||
|
resetAndSelectAndOpenSC();
|
||||||
|
|
||||||
response = cmdSet.sign(hash);
|
response = cmdSet.sign(hash);
|
||||||
assertEquals(0x9000, response.getSw());
|
assertEquals(0x9000, response.getSw());
|
||||||
|
|
||||||
|
@ -1102,8 +1152,16 @@ public class KeycardTest {
|
||||||
resetAndSelectAndOpenSC();
|
resetAndSelectAndOpenSC();
|
||||||
response = cmdSet.sign(hash);
|
response = cmdSet.sign(hash);
|
||||||
assertEquals(0x6985, response.getSw());
|
assertEquals(0x6985, response.getSw());
|
||||||
|
|
||||||
|
|
||||||
|
if (cmdSet.getApplicationInfo().hasCredentialsManagementCapability()) {
|
||||||
|
response = cmdSet.verifyPIN("000000");
|
||||||
|
assertEquals(0x9000, response.getSw());
|
||||||
|
}
|
||||||
|
|
||||||
response = cmdSet.deriveKey(new byte[] {0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01}, KeycardApplet.DERIVE_P1_SOURCE_MASTER);
|
response = cmdSet.deriveKey(new byte[] {0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01}, KeycardApplet.DERIVE_P1_SOURCE_MASTER);
|
||||||
assertEquals(0x9000, response.getSw());
|
assertEquals(0x9000, response.getSw());
|
||||||
|
resetAndSelectAndOpenSC();
|
||||||
response = cmdSet.sign(hash);
|
response = cmdSet.sign(hash);
|
||||||
assertEquals(0x9000, response.getSw());
|
assertEquals(0x9000, response.getSw());
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue