mirror of
https://github.com/status-im/status-keycard.git
synced 2025-02-19 16:54:19 +00:00
Merge pull request #86 from status-im/lazy-derivation
add chain code export and remove derivation caches
This commit is contained in:
commit
5bc653c012
@ -61,6 +61,7 @@ public class KeycardApplet extends Applet {
|
|||||||
static final byte DERIVE_P1_SOURCE_MASTER = (byte) 0x00;
|
static final byte DERIVE_P1_SOURCE_MASTER = (byte) 0x00;
|
||||||
static final byte DERIVE_P1_SOURCE_PARENT = (byte) 0x40;
|
static final byte DERIVE_P1_SOURCE_PARENT = (byte) 0x40;
|
||||||
static final byte DERIVE_P1_SOURCE_CURRENT = (byte) 0x80;
|
static final byte DERIVE_P1_SOURCE_CURRENT = (byte) 0x80;
|
||||||
|
static final byte DERIVE_P1_SOURCE_PINLESS = (byte) 0xC0;
|
||||||
static final byte DERIVE_P1_SOURCE_MASK = (byte) 0xC0;
|
static final byte DERIVE_P1_SOURCE_MASK = (byte) 0xC0;
|
||||||
|
|
||||||
static final byte GENERATE_MNEMONIC_P1_CS_MIN = 4;
|
static final byte GENERATE_MNEMONIC_P1_CS_MIN = 4;
|
||||||
@ -78,6 +79,7 @@ public class KeycardApplet extends Applet {
|
|||||||
|
|
||||||
static final byte EXPORT_KEY_P2_PRIVATE_AND_PUBLIC = 0x00;
|
static final byte EXPORT_KEY_P2_PRIVATE_AND_PUBLIC = 0x00;
|
||||||
static final byte EXPORT_KEY_P2_PUBLIC_ONLY = 0x01;
|
static final byte EXPORT_KEY_P2_PUBLIC_ONLY = 0x01;
|
||||||
|
static final byte EXPORT_KEY_P2_EXTENDED_PUBLIC = 0x02;
|
||||||
|
|
||||||
static final byte STORE_DATA_P1_PUBLIC = 0x00;
|
static final byte STORE_DATA_P1_PUBLIC = 0x00;
|
||||||
static final byte STORE_DATA_P1_NDEF = 0x01;
|
static final byte STORE_DATA_P1_NDEF = 0x01;
|
||||||
@ -118,16 +120,8 @@ public class KeycardApplet extends Applet {
|
|||||||
private byte[] masterChainCode;
|
private byte[] masterChainCode;
|
||||||
private boolean isExtended;
|
private boolean isExtended;
|
||||||
|
|
||||||
private ECPublicKey parentPublicKey;
|
private byte[] tmpPath;
|
||||||
private ECPrivateKey parentPrivateKey;
|
private short tmpPathLen;
|
||||||
private byte[] parentChainCode;
|
|
||||||
|
|
||||||
private ECPublicKey publicKey;
|
|
||||||
private ECPrivateKey privateKey;
|
|
||||||
private byte[] chainCode;
|
|
||||||
|
|
||||||
private ECPublicKey pinlessPublicKey;
|
|
||||||
private ECPrivateKey pinlessPrivateKey;
|
|
||||||
|
|
||||||
private byte[] keyPath;
|
private byte[] keyPath;
|
||||||
private short keyPathLen;
|
private short keyPathLen;
|
||||||
@ -178,21 +172,11 @@ public class KeycardApplet extends Applet {
|
|||||||
|
|
||||||
masterPublic = (ECPublicKey) KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PUBLIC, SECP256k1.SECP256K1_KEY_SIZE, false);
|
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);
|
masterPrivate = (ECPrivateKey) KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PRIVATE, SECP256k1.SECP256K1_KEY_SIZE, false);
|
||||||
|
|
||||||
parentPublicKey = (ECPublicKey) KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PUBLIC, SECP256k1.SECP256K1_KEY_SIZE, false);
|
|
||||||
parentPrivateKey = (ECPrivateKey) KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PRIVATE, 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);
|
|
||||||
|
|
||||||
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];
|
|
||||||
chainCode = new byte[CHAIN_CODE_SIZE];
|
|
||||||
keyPath = new byte[KEY_PATH_MAX_DEPTH * 4];
|
keyPath = new byte[KEY_PATH_MAX_DEPTH * 4];
|
||||||
pinlessPath = new byte[KEY_PATH_MAX_DEPTH * 4];
|
pinlessPath = new byte[KEY_PATH_MAX_DEPTH * 4];
|
||||||
|
tmpPath = JCSystem.makeTransientByteArray((short)(KEY_PATH_MAX_DEPTH * 4), JCSystem.CLEAR_ON_RESET);
|
||||||
|
|
||||||
keyUID = new byte[KEY_UID_LENGTH];
|
keyUID = new byte[KEY_UID_LENGTH];
|
||||||
|
|
||||||
@ -411,7 +395,7 @@ public class KeycardApplet extends Applet {
|
|||||||
|
|
||||||
apduBuffer[off++] = TLV_APPLICATION_INFO_TEMPLATE;
|
apduBuffer[off++] = TLV_APPLICATION_INFO_TEMPLATE;
|
||||||
|
|
||||||
if (privateKey.isInitialized()) {
|
if (masterPrivate.isInitialized()) {
|
||||||
apduBuffer[off++] = (byte) 0x81;
|
apduBuffer[off++] = (byte) 0x81;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -437,7 +421,7 @@ public class KeycardApplet extends Applet {
|
|||||||
apduBuffer[off++] = secureChannel.getRemainingPairingSlots();
|
apduBuffer[off++] = secureChannel.getRemainingPairingSlots();
|
||||||
apduBuffer[off++] = TLV_KEY_UID;
|
apduBuffer[off++] = TLV_KEY_UID;
|
||||||
|
|
||||||
if (privateKey.isInitialized()) {
|
if (masterPrivate.isInitialized()) {
|
||||||
apduBuffer[off++] = KEY_UID_LENGTH;
|
apduBuffer[off++] = KEY_UID_LENGTH;
|
||||||
Util.arrayCopyNonAtomic(keyUID, (short) 0, apduBuffer, off, KEY_UID_LENGTH);
|
Util.arrayCopyNonAtomic(keyUID, (short) 0, apduBuffer, off, KEY_UID_LENGTH);
|
||||||
off += KEY_UID_LENGTH;
|
off += KEY_UID_LENGTH;
|
||||||
@ -497,7 +481,7 @@ public class KeycardApplet extends Applet {
|
|||||||
apduBuffer[off++] = puk.getTriesRemaining();
|
apduBuffer[off++] = puk.getTriesRemaining();
|
||||||
apduBuffer[off++] = TLV_BOOL;
|
apduBuffer[off++] = TLV_BOOL;
|
||||||
apduBuffer[off++] = 1;
|
apduBuffer[off++] = 1;
|
||||||
apduBuffer[off++] = privateKey.isInitialized() ? (byte) 0xFF : (byte) 0x00;
|
apduBuffer[off++] = masterPrivate.isInitialized() ? (byte) 0xFF : (byte) 0x00;
|
||||||
|
|
||||||
return (short) (off - SecureChannel.SC_OUT_OFFSET);
|
return (short) (off - SecureChannel.SC_OUT_OFFSET);
|
||||||
}
|
}
|
||||||
@ -689,8 +673,6 @@ public class KeycardApplet extends Applet {
|
|||||||
* manipulation has happened to be sure that the state is always consistent.
|
* manipulation has happened to be sure that the state is always consistent.
|
||||||
*/
|
*/
|
||||||
private void resetKeyStatus() {
|
private void resetKeyStatus() {
|
||||||
parentPrivateKey.clearKey();
|
|
||||||
SECP256k1.setCurveParameters(parentPrivateKey);
|
|
||||||
keyPathLen = 0;
|
keyPathLen = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -721,12 +703,10 @@ public class KeycardApplet extends Applet {
|
|||||||
isExtended = (apduBuffer[chainOffset] == TLV_CHAIN_CODE);
|
isExtended = (apduBuffer[chainOffset] == TLV_CHAIN_CODE);
|
||||||
|
|
||||||
masterPrivate.setS(apduBuffer, (short) (privOffset + 2), apduBuffer[(short) (privOffset + 1)]);
|
masterPrivate.setS(apduBuffer, (short) (privOffset + 2), apduBuffer[(short) (privOffset + 1)]);
|
||||||
privateKey.setS(apduBuffer, (short) (privOffset + 2), apduBuffer[(short) (privOffset + 1)]);
|
|
||||||
|
|
||||||
if (isExtended) {
|
if (isExtended) {
|
||||||
if (apduBuffer[(short) (chainOffset + 1)] == CHAIN_CODE_SIZE) {
|
if (apduBuffer[(short) (chainOffset + 1)] == CHAIN_CODE_SIZE) {
|
||||||
Util.arrayCopy(apduBuffer, (short) (chainOffset + 2), masterChainCode, (short) 0, apduBuffer[(short) (chainOffset + 1)]);
|
Util.arrayCopy(apduBuffer, (short) (chainOffset + 2), masterChainCode, (short) 0, apduBuffer[(short) (chainOffset + 1)]);
|
||||||
Util.arrayCopy(apduBuffer, (short) (chainOffset + 2), chainCode, (short) 0, apduBuffer[(short) (chainOffset + 1)]);
|
|
||||||
} else {
|
} else {
|
||||||
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
|
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
|
||||||
}
|
}
|
||||||
@ -743,7 +723,6 @@ public class KeycardApplet extends Applet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
masterPublic.setW(apduBuffer, pubOffset, pubLen);
|
masterPublic.setW(apduBuffer, pubOffset, pubLen);
|
||||||
publicKey.setW(apduBuffer, pubOffset, pubLen);
|
|
||||||
} catch (CryptoException e) {
|
} catch (CryptoException e) {
|
||||||
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
|
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
|
||||||
}
|
}
|
||||||
@ -771,39 +750,20 @@ public class KeycardApplet extends Applet {
|
|||||||
isExtended = true;
|
isExtended = true;
|
||||||
|
|
||||||
masterPrivate.setS(apduBuffer, (short) ISO7816.OFFSET_CDATA, CHAIN_CODE_SIZE);
|
masterPrivate.setS(apduBuffer, (short) ISO7816.OFFSET_CDATA, CHAIN_CODE_SIZE);
|
||||||
privateKey.setS(apduBuffer, (short) ISO7816.OFFSET_CDATA, CHAIN_CODE_SIZE);
|
|
||||||
|
|
||||||
Util.arrayCopy(apduBuffer, (short) (ISO7816.OFFSET_CDATA + CHAIN_CODE_SIZE), masterChainCode, (short) 0, CHAIN_CODE_SIZE);
|
Util.arrayCopy(apduBuffer, (short) (ISO7816.OFFSET_CDATA + CHAIN_CODE_SIZE), masterChainCode, (short) 0, CHAIN_CODE_SIZE);
|
||||||
Util.arrayCopy(apduBuffer, (short) (ISO7816.OFFSET_CDATA + CHAIN_CODE_SIZE), chainCode, (short) 0, CHAIN_CODE_SIZE);
|
|
||||||
short pubLen = secp256k1.derivePublicKey(masterPrivate, apduBuffer, (short) 0);
|
short pubLen = secp256k1.derivePublicKey(masterPrivate, apduBuffer, (short) 0);
|
||||||
|
|
||||||
masterPublic.setW(apduBuffer, (short) 0, pubLen);
|
masterPublic.setW(apduBuffer, (short) 0, pubLen);
|
||||||
publicKey.setW(apduBuffer, (short) 0, pubLen);
|
|
||||||
|
|
||||||
resetKeyStatus();
|
resetKeyStatus();
|
||||||
JCSystem.commitTransaction();
|
JCSystem.commitTransaction();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Processes the DERIVE KEY command. Requires a secure channel to be already open. Unless a PIN-less path exists, t
|
* Processes the DERIVE KEY command. Requires a secure channel to be already open and the PIN must be verified as well.
|
||||||
* the PIN must be verified as well. The master key must be already loaded and have a chain code. In the happy case
|
* The master key must be already loaded and have a chain code. This function only updates the current path but does
|
||||||
* this method is quite straightforward, since it takes a sequence of 32-bit big-endian integers and perform key
|
* not actually perform derivation, which is delayed to exporting/signing.
|
||||||
* derivations, updating the current key path accordingly.
|
|
||||||
*
|
|
||||||
* However, since public key derivation might not be supported on card this method also supports the so called
|
|
||||||
* assisted derivation scheme. In this scheme the client first sends a single 32-bit big-endian integer. The cards
|
|
||||||
* derives the new private key and by taking advantage the EC-DH algorithm returns the X of the public key along with
|
|
||||||
* a signature of the SHA-256 hash of a fixed message ("STATUS KEY DERIVATION" in ASCII). The client must then
|
|
||||||
* calculate the two possible Y and try to verify the signature with each of the 2 candidate public keys. The public
|
|
||||||
* key which correctly verifies the signature is the real one and must be uploaded (as an uncompressed point) through
|
|
||||||
* this command again. At this point the current key path is updated and the derived key can be used for signing.
|
|
||||||
*
|
|
||||||
* In all cases transactions are used to make sure that the current key is always complete (private, chain and public
|
|
||||||
* components are coherent) and the key path matches the actual status of the card. This makes recovery from a sudden
|
|
||||||
* power loss easy.
|
|
||||||
*
|
|
||||||
* When the reset flag is set and the data is empty, the assisted key derivation flag is ignored, since in this case
|
|
||||||
* no derivation is done and the master key becomes the current key.
|
|
||||||
*
|
*
|
||||||
* @param apdu the JCRE-owned APDU object.
|
* @param apdu the JCRE-owned APDU object.
|
||||||
*/
|
*/
|
||||||
@ -815,67 +775,52 @@ public class KeycardApplet extends Applet {
|
|||||||
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
|
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
|
||||||
}
|
}
|
||||||
|
|
||||||
doDerive(apduBuffer, (short) 0, len, apduBuffer[OFFSET_P1], true);
|
updateDerivationPath(apduBuffer, (short) 0, len, apduBuffer[OFFSET_P1]);
|
||||||
|
commitTmpPath();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal derivation function, called by DERIVE KEY and EXPORT KEY
|
* 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 apduBuffer the APDU buffer
|
||||||
* @param off the offset in the APDU buffer relative to the data field
|
* @param off the offset in the APDU buffer relative to the data field
|
||||||
* @param len the len of the path
|
* @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
|
|
||||||
*/
|
*/
|
||||||
private void doDerive(byte[] apduBuffer, short off, short len, byte source, boolean makeCurrent) {
|
private void updateDerivationPath(byte[] apduBuffer, short off, short len, byte source) {
|
||||||
if (!isExtended) {
|
if (!isExtended) {
|
||||||
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
|
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
|
||||||
}
|
}
|
||||||
|
|
||||||
short newPathLen;
|
short newPathLen;
|
||||||
short pathLenOff;
|
short pathLenOff;
|
||||||
ECPublicKey sourcePub;
|
|
||||||
ECPrivateKey sourcePriv;
|
|
||||||
byte[] sourceChain;
|
|
||||||
|
|
||||||
|
byte[] srcKeyPath = keyPath;
|
||||||
|
|
||||||
switch (source) {
|
switch (source) {
|
||||||
case DERIVE_P1_SOURCE_MASTER:
|
case DERIVE_P1_SOURCE_MASTER:
|
||||||
if (len == 0) {
|
|
||||||
resetToMaster(apduBuffer);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
newPathLen = len;
|
newPathLen = len;
|
||||||
sourcePriv = masterPrivate;
|
|
||||||
sourcePub = masterPublic;
|
|
||||||
sourceChain = masterChainCode;
|
|
||||||
pathLenOff = 0;
|
pathLenOff = 0;
|
||||||
break;
|
break;
|
||||||
case DERIVE_P1_SOURCE_PARENT:
|
case DERIVE_P1_SOURCE_PARENT:
|
||||||
if (!parentPrivateKey.isInitialized()) {
|
if (keyPathLen < 4) {
|
||||||
ISOException.throwIt(ISO7816.SW_WRONG_P1P2);
|
ISOException.throwIt(ISO7816.SW_WRONG_P1P2);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (len == 0) {
|
|
||||||
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
|
|
||||||
}
|
|
||||||
|
|
||||||
newPathLen = (short) (keyPathLen + len - 4);
|
newPathLen = (short) (keyPathLen + len - 4);
|
||||||
sourcePriv = parentPrivateKey;
|
|
||||||
sourcePub = parentPublicKey;
|
|
||||||
sourceChain = parentChainCode;
|
|
||||||
pathLenOff = (short) (keyPathLen - 4);
|
pathLenOff = (short) (keyPathLen - 4);
|
||||||
break;
|
break;
|
||||||
case DERIVE_P1_SOURCE_CURRENT:
|
case DERIVE_P1_SOURCE_CURRENT:
|
||||||
if (len == 0) {
|
|
||||||
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
|
|
||||||
}
|
|
||||||
|
|
||||||
newPathLen = (short) (keyPathLen + len);
|
newPathLen = (short) (keyPathLen + len);
|
||||||
sourcePriv = privateKey;
|
|
||||||
sourcePub = publicKey;
|
|
||||||
sourceChain = chainCode;
|
|
||||||
pathLenOff = keyPathLen;
|
pathLenOff = keyPathLen;
|
||||||
break;
|
break;
|
||||||
|
case DERIVE_P1_SOURCE_PINLESS:
|
||||||
|
if (len != 0) {
|
||||||
|
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
|
||||||
|
}
|
||||||
|
srcKeyPath = pinlessPath;
|
||||||
|
newPathLen = pinlessPathLen;
|
||||||
|
pathLenOff = pinlessPathLen;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
|
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
|
||||||
return;
|
return;
|
||||||
@ -886,70 +831,60 @@ public class KeycardApplet extends Applet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
short pathOff = (short) (ISO7816.OFFSET_CDATA + off);
|
short pathOff = (short) (ISO7816.OFFSET_CDATA + off);
|
||||||
short scratchOff = (short) (pathOff + len);
|
|
||||||
|
Util.arrayCopyNonAtomic(srcKeyPath, (short) 0, tmpPath, (short) 0, pathLenOff);
|
||||||
|
Util.arrayCopyNonAtomic(apduBuffer, pathOff, tmpPath, pathLenOff, len);
|
||||||
|
tmpPathLen = newPathLen;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes the tmp path the current path.
|
||||||
|
*/
|
||||||
|
void commitTmpPath() {
|
||||||
|
JCSystem.beginTransaction();
|
||||||
|
Util.arrayCopy(tmpPath, (short) 0, keyPath, (short) 0, tmpPathLen);
|
||||||
|
keyPathLen = tmpPathLen;
|
||||||
|
JCSystem.commitTransaction();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal derivation function, called by DERIVE KEY and EXPORT KEY
|
||||||
|
* @param apduBuffer the APDU buffer
|
||||||
|
* @param off the offset in the APDU buffer relative to the data field
|
||||||
|
*/
|
||||||
|
private void doDerive(byte[] apduBuffer, short off) {
|
||||||
|
if (tmpPathLen == 0) {
|
||||||
|
masterPrivate.getS(derivationOutput, (short) 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
short scratchOff = (short) (ISO7816.OFFSET_CDATA + off);
|
||||||
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 + masterPrivate.getS(apduBuffer, dataOff));
|
||||||
pubKeyOff = Util.arrayCopyNonAtomic(sourceChain, (short) 0, apduBuffer, pubKeyOff, CHAIN_CODE_SIZE);
|
pubKeyOff = Util.arrayCopyNonAtomic(masterChainCode, (short) 0, apduBuffer, pubKeyOff, CHAIN_CODE_SIZE);
|
||||||
|
|
||||||
if (!crypto.bip32IsHardened(apduBuffer, ISO7816.OFFSET_CDATA)) {
|
if (!crypto.bip32IsHardened(tmpPath, (short) 0)) {
|
||||||
sourcePub.getW(apduBuffer, pubKeyOff);
|
masterPublic.getW(apduBuffer, pubKeyOff);
|
||||||
} else {
|
} else {
|
||||||
apduBuffer[pubKeyOff] = 0;
|
apduBuffer[pubKeyOff] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (short i = pathOff; i < scratchOff; i += 4) {
|
for (short i = 0; i < tmpPathLen; i += 4) {
|
||||||
if (i > pathOff) {
|
if (i > 0) {
|
||||||
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(tmpPath, i)) {
|
||||||
secp256k1.derivePublicKey(apduBuffer, dataOff, apduBuffer, pubKeyOff);
|
secp256k1.derivePublicKey(apduBuffer, dataOff, apduBuffer, pubKeyOff);
|
||||||
} else {
|
} else {
|
||||||
apduBuffer[pubKeyOff] = 0;
|
apduBuffer[pubKeyOff] = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!crypto.bip32CKDPriv(apduBuffer, i, apduBuffer, scratchOff, apduBuffer, dataOff, derivationOutput, (short) 0)) {
|
if (!crypto.bip32CKDPriv(tmpPath, i, apduBuffer, scratchOff, apduBuffer, dataOff, derivationOutput, (short) 0)) {
|
||||||
ISOException.throwIt(ISO7816.SW_DATA_INVALID);
|
ISOException.throwIt(ISO7816.SW_DATA_INVALID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (makeCurrent) {
|
|
||||||
JCSystem.beginTransaction();
|
|
||||||
|
|
||||||
parentPrivateKey.setS(apduBuffer, dataOff, Crypto.KEY_SECRET_SIZE);
|
|
||||||
Util.arrayCopy(apduBuffer, (short)(dataOff + Crypto.KEY_SECRET_SIZE), parentChainCode, (short) 0, CHAIN_CODE_SIZE);
|
|
||||||
|
|
||||||
if (apduBuffer[pubKeyOff] == 0x04) {
|
|
||||||
parentPublicKey.setW(apduBuffer, pubKeyOff, Crypto.KEY_PUB_SIZE);
|
|
||||||
} else {
|
|
||||||
secp256k1.derivePublicKey(parentPrivateKey, apduBuffer, scratchOff);
|
|
||||||
parentPublicKey.setW(apduBuffer, scratchOff, Crypto.KEY_PUB_SIZE);
|
|
||||||
}
|
|
||||||
|
|
||||||
privateKey.setS(derivationOutput, (short) 0, Crypto.KEY_SECRET_SIZE);
|
|
||||||
Util.arrayCopy(derivationOutput, Crypto.KEY_SECRET_SIZE, chainCode, (short) 0, CHAIN_CODE_SIZE);
|
|
||||||
secp256k1.derivePublicKey(privateKey, apduBuffer, scratchOff);
|
|
||||||
publicKey.setW(apduBuffer, scratchOff, Crypto.KEY_PUB_SIZE);
|
|
||||||
|
|
||||||
Util.arrayCopy(apduBuffer, pathOff, keyPath, pathLenOff, len);
|
|
||||||
keyPathLen = newPathLen;
|
|
||||||
JCSystem.commitTransaction();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resets to master key
|
|
||||||
*
|
|
||||||
* @param apduBuffer the APDU buffer
|
|
||||||
*/
|
|
||||||
private void resetToMaster(byte[] apduBuffer) {
|
|
||||||
resetKeyStatus();
|
|
||||||
masterPrivate.getS(apduBuffer, ISO7816.OFFSET_CDATA);
|
|
||||||
privateKey.setS(apduBuffer, ISO7816.OFFSET_CDATA, Crypto.KEY_SECRET_SIZE);
|
|
||||||
masterPublic.getW(apduBuffer, ISO7816.OFFSET_CDATA);
|
|
||||||
publicKey.setW(apduBuffer, ISO7816.OFFSET_CDATA, Crypto.KEY_PUB_SIZE);
|
|
||||||
Util.arrayCopyNonAtomic(masterChainCode, (short) 0, chainCode, (short) 0, CHAIN_CODE_SIZE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1048,17 +983,9 @@ public class KeycardApplet extends Applet {
|
|||||||
keyPathLen = 0;
|
keyPathLen = 0;
|
||||||
pinlessPathLen = 0;
|
pinlessPathLen = 0;
|
||||||
isExtended = false;
|
isExtended = false;
|
||||||
privateKey.clearKey();
|
|
||||||
publicKey.clearKey();
|
|
||||||
masterPrivate.clearKey();
|
masterPrivate.clearKey();
|
||||||
masterPublic.clearKey();
|
masterPublic.clearKey();
|
||||||
parentPrivateKey.clearKey();
|
|
||||||
parentPublicKey.clearKey();
|
|
||||||
pinlessPrivateKey.clearKey();
|
|
||||||
pinlessPublicKey.clearKey();
|
|
||||||
resetCurveParameters();
|
resetCurveParameters();
|
||||||
Util.arrayFillNonAtomic(chainCode, (short) 0, (short) chainCode.length, (byte) 0);
|
|
||||||
Util.arrayFillNonAtomic(parentChainCode, (short) 0, (short) parentChainCode.length, (byte) 0);
|
|
||||||
Util.arrayFillNonAtomic(masterChainCode, (short) 0, (short) masterChainCode.length, (byte) 0);
|
Util.arrayFillNonAtomic(masterChainCode, (short) 0, (short) masterChainCode.length, (byte) 0);
|
||||||
Util.arrayFillNonAtomic(keyPath, (short) 0, (short) keyPath.length, (byte) 0);
|
Util.arrayFillNonAtomic(keyPath, (short) 0, (short) keyPath.length, (byte) 0);
|
||||||
Util.arrayFillNonAtomic(pinlessPath, (short) 0, (short) pinlessPath.length, (byte) 0);
|
Util.arrayFillNonAtomic(pinlessPath, (short) 0, (short) pinlessPath.length, (byte) 0);
|
||||||
@ -1100,32 +1027,21 @@ public class KeycardApplet extends Applet {
|
|||||||
private void sign(APDU apdu) {
|
private void sign(APDU apdu) {
|
||||||
byte[] apduBuffer = apdu.getBuffer();
|
byte[] apduBuffer = apdu.getBuffer();
|
||||||
boolean usePinless = false;
|
boolean usePinless = false;
|
||||||
boolean derive = false;
|
|
||||||
boolean makeCurrent = false;
|
boolean makeCurrent = false;
|
||||||
|
byte derivationSource = (byte) (apduBuffer[OFFSET_P1] & DERIVE_P1_SOURCE_MASK);
|
||||||
ECPrivateKey signingKey;
|
|
||||||
ECPublicKey outputKey;
|
|
||||||
|
|
||||||
switch((byte) (apduBuffer[OFFSET_P1] & ~DERIVE_P1_SOURCE_MASK)) {
|
switch((byte) (apduBuffer[OFFSET_P1] & ~DERIVE_P1_SOURCE_MASK)) {
|
||||||
case SIGN_P1_CURRENT_KEY:
|
case SIGN_P1_CURRENT_KEY:
|
||||||
signingKey = privateKey;
|
derivationSource = DERIVE_P1_SOURCE_CURRENT;
|
||||||
outputKey = publicKey;
|
|
||||||
break;
|
break;
|
||||||
case SIGN_P1_DERIVE:
|
case SIGN_P1_DERIVE:
|
||||||
signingKey = secp256k1.tmpECPrivateKey;
|
|
||||||
outputKey = null;
|
|
||||||
derive = true;
|
|
||||||
break;
|
break;
|
||||||
case SIGN_P1_DERIVE_AND_MAKE_CURRENT:
|
case SIGN_P1_DERIVE_AND_MAKE_CURRENT:
|
||||||
signingKey = privateKey;
|
|
||||||
outputKey = publicKey;
|
|
||||||
derive = true;
|
|
||||||
makeCurrent = true;
|
makeCurrent = true;
|
||||||
break;
|
break;
|
||||||
case SIGN_P1_PINLESS:
|
case SIGN_P1_PINLESS:
|
||||||
usePinless = true;
|
usePinless = true;
|
||||||
signingKey = pinlessPrivateKey;
|
derivationSource = DERIVE_P1_SOURCE_PINLESS;
|
||||||
outputKey = pinlessPublicKey;
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
ISOException.throwIt(ISO7816.SW_WRONG_P1P2);
|
ISOException.throwIt(ISO7816.SW_WRONG_P1P2);
|
||||||
@ -1144,39 +1060,29 @@ public class KeycardApplet extends Applet {
|
|||||||
ISOException.throwIt(SW_REFERENCED_DATA_NOT_FOUND);
|
ISOException.throwIt(SW_REFERENCED_DATA_NOT_FOUND);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!((pin.isValidated() || usePinless || isPinless()) && privateKey.isInitialized())) {
|
if (len < MessageDigest.LENGTH_SHA_256) {
|
||||||
|
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
|
||||||
|
}
|
||||||
|
|
||||||
|
short pathLen = (short) (len - MessageDigest.LENGTH_SHA_256);
|
||||||
|
updateDerivationPath(apduBuffer, MessageDigest.LENGTH_SHA_256, pathLen, derivationSource);
|
||||||
|
|
||||||
|
if (!((pin.isValidated() || usePinless || isPinless()) && masterPrivate.isInitialized())) {
|
||||||
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
|
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (derive) {
|
doDerive(apduBuffer, MessageDigest.LENGTH_SHA_256);
|
||||||
short pathLen = (short) (len - MessageDigest.LENGTH_SHA_256);
|
|
||||||
|
|
||||||
if (pathLen <= 0) {
|
|
||||||
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
|
|
||||||
}
|
|
||||||
|
|
||||||
byte derivationSource = (byte) (apduBuffer[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)] = Crypto.KEY_PUB_SIZE;
|
short outLen = apduBuffer[(short)(SecureChannel.SC_OUT_OFFSET + 4)] = Crypto.KEY_PUB_SIZE;
|
||||||
|
|
||||||
if (outputKey != null) {
|
secp256k1.derivePublicKey(derivationOutput, (short) 0, apduBuffer, (short) (SecureChannel.SC_OUT_OFFSET + 5));
|
||||||
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);
|
||||||
|
|
||||||
signature.init(signingKey, Signature.MODE_SIGN);
|
signature.init(secp256k1.tmpECPrivateKey, 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);
|
||||||
@ -1184,6 +1090,10 @@ public class KeycardApplet extends Applet {
|
|||||||
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 (makeCurrent) {
|
||||||
|
commitTmpPath();
|
||||||
|
}
|
||||||
|
|
||||||
if (secureChannel.isOpen()) {
|
if (secureChannel.isOpen()) {
|
||||||
secureChannel.respond(apdu, outLen, ISO7816.SW_NO_ERROR);
|
secureChannel.respond(apdu, outLen, ISO7816.SW_NO_ERROR);
|
||||||
} else {
|
} else {
|
||||||
@ -1214,14 +1124,6 @@ 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();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1234,57 +1136,57 @@ public class KeycardApplet extends Applet {
|
|||||||
byte[] apduBuffer = apdu.getBuffer();
|
byte[] apduBuffer = apdu.getBuffer();
|
||||||
short dataLen = secureChannel.preprocessAPDU(apduBuffer);
|
short dataLen = secureChannel.preprocessAPDU(apduBuffer);
|
||||||
|
|
||||||
if (!pin.isValidated() || !privateKey.isInitialized()) {
|
if (!pin.isValidated() || !masterPrivate.isInitialized()) {
|
||||||
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
|
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean publicOnly;
|
boolean publicOnly;
|
||||||
|
boolean extendedPublic;
|
||||||
|
|
||||||
switch (apduBuffer[ISO7816.OFFSET_P2]) {
|
switch (apduBuffer[ISO7816.OFFSET_P2]) {
|
||||||
case EXPORT_KEY_P2_PRIVATE_AND_PUBLIC:
|
case EXPORT_KEY_P2_PRIVATE_AND_PUBLIC:
|
||||||
publicOnly = false;
|
publicOnly = false;
|
||||||
|
extendedPublic = false;
|
||||||
break;
|
break;
|
||||||
case EXPORT_KEY_P2_PUBLIC_ONLY:
|
case EXPORT_KEY_P2_PUBLIC_ONLY:
|
||||||
publicOnly = true;
|
publicOnly = true;
|
||||||
|
extendedPublic = false;
|
||||||
break;
|
break;
|
||||||
|
case EXPORT_KEY_P2_EXTENDED_PUBLIC:
|
||||||
|
publicOnly = true;
|
||||||
|
extendedPublic = true;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
|
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] exportPath = keyPath;
|
|
||||||
short exportPathOff = (short) 0;
|
|
||||||
short exportPathLen = keyPathLen;
|
|
||||||
|
|
||||||
boolean derive = false;
|
|
||||||
boolean makeCurrent = false;
|
boolean makeCurrent = false;
|
||||||
byte derivationSource = (byte) (apduBuffer[OFFSET_P1] & DERIVE_P1_SOURCE_MASK);
|
byte derivationSource = (byte) (apduBuffer[OFFSET_P1] & DERIVE_P1_SOURCE_MASK);
|
||||||
|
|
||||||
switch ((byte) (apduBuffer[OFFSET_P1] & ~DERIVE_P1_SOURCE_MASK)) {
|
switch ((byte) (apduBuffer[OFFSET_P1] & ~DERIVE_P1_SOURCE_MASK)) {
|
||||||
case EXPORT_KEY_P1_CURRENT:
|
case EXPORT_KEY_P1_CURRENT:
|
||||||
|
derivationSource = DERIVE_P1_SOURCE_CURRENT;
|
||||||
|
break;
|
||||||
|
case EXPORT_KEY_P1_DERIVE:
|
||||||
break;
|
break;
|
||||||
case EXPORT_KEY_P1_DERIVE_AND_MAKE_CURRENT:
|
case EXPORT_KEY_P1_DERIVE_AND_MAKE_CURRENT:
|
||||||
makeCurrent = true;
|
makeCurrent = true;
|
||||||
case EXPORT_KEY_P1_DERIVE:
|
|
||||||
derive = true;
|
|
||||||
if (derivationSource == DERIVE_P1_SOURCE_MASTER) {
|
|
||||||
exportPath = apduBuffer;
|
|
||||||
exportPathOff = ISO7816.OFFSET_CDATA;
|
|
||||||
exportPathLen = dataLen;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
|
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!publicOnly && ((exportPathLen < (short)(((short) EIP_1581_PREFIX.length) + 8)) || (Util.arrayCompare(EIP_1581_PREFIX, (short) 0, exportPath, exportPathOff, (short) EIP_1581_PREFIX.length) != 0))) {
|
updateDerivationPath(apduBuffer, (short) 0, dataLen, derivationSource);
|
||||||
|
|
||||||
|
boolean eip1581 = isEIP1581();
|
||||||
|
|
||||||
|
if (!(publicOnly || eip1581) || (extendedPublic && eip1581)) {
|
||||||
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
|
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (derive) {
|
doDerive(apduBuffer, (short) 0);
|
||||||
doDerive(apduBuffer, (short) 0, dataLen, derivationSource, makeCurrent);
|
|
||||||
}
|
|
||||||
|
|
||||||
short off = SecureChannel.SC_OUT_OFFSET;
|
short off = SecureChannel.SC_OUT_OFFSET;
|
||||||
|
|
||||||
@ -1293,30 +1195,27 @@ public class KeycardApplet extends Applet {
|
|||||||
|
|
||||||
short len;
|
short len;
|
||||||
|
|
||||||
if (!derive || makeCurrent) {
|
if (publicOnly) {
|
||||||
apduBuffer[off++] = TLV_PUB_KEY;
|
|
||||||
off++;
|
|
||||||
len = publicKey.getW(apduBuffer, off);
|
|
||||||
apduBuffer[(short) (off - 1)] = (byte) len;
|
|
||||||
off += len;
|
|
||||||
} else if (publicOnly) {
|
|
||||||
apduBuffer[off++] = TLV_PUB_KEY;
|
apduBuffer[off++] = TLV_PUB_KEY;
|
||||||
off++;
|
off++;
|
||||||
len = secp256k1.derivePublicKey(derivationOutput, (short) 0, apduBuffer, off);
|
len = secp256k1.derivePublicKey(derivationOutput, (short) 0, apduBuffer, off);
|
||||||
apduBuffer[(short) (off - 1)] = (byte) len;
|
apduBuffer[(short) (off - 1)] = (byte) len;
|
||||||
off += len;
|
off += len;
|
||||||
}
|
|
||||||
|
|
||||||
if (!publicOnly) {
|
if (extendedPublic) {
|
||||||
|
apduBuffer[off++] = TLV_CHAIN_CODE;
|
||||||
|
off++;
|
||||||
|
Util.arrayCopyNonAtomic(derivationOutput, Crypto.KEY_SECRET_SIZE, apduBuffer, off, CHAIN_CODE_SIZE);
|
||||||
|
len = CHAIN_CODE_SIZE;
|
||||||
|
apduBuffer[(short) (off - 1)] = (byte) len;
|
||||||
|
off += len;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
apduBuffer[off++] = TLV_PRIV_KEY;
|
apduBuffer[off++] = TLV_PRIV_KEY;
|
||||||
off++;
|
off++;
|
||||||
|
|
||||||
if (!derive || makeCurrent) {
|
Util.arrayCopyNonAtomic(derivationOutput, (short) 0, apduBuffer, off, Crypto.KEY_SECRET_SIZE);
|
||||||
len = privateKey.getS(apduBuffer, off);
|
len = Crypto.KEY_SECRET_SIZE;
|
||||||
} else {
|
|
||||||
Util.arrayCopyNonAtomic(derivationOutput, (short) 0, apduBuffer, off, Crypto.KEY_SECRET_SIZE);
|
|
||||||
len = Crypto.KEY_SECRET_SIZE;
|
|
||||||
}
|
|
||||||
|
|
||||||
apduBuffer[(short) (off - 1)] = (byte) len;
|
apduBuffer[(short) (off - 1)] = (byte) len;
|
||||||
off += len;
|
off += len;
|
||||||
@ -1325,6 +1224,10 @@ public class KeycardApplet extends Applet {
|
|||||||
len = (short) (off - SecureChannel.SC_OUT_OFFSET);
|
len = (short) (off - SecureChannel.SC_OUT_OFFSET);
|
||||||
apduBuffer[(SecureChannel.SC_OUT_OFFSET + 1)] = (byte) (len - 2);
|
apduBuffer[(SecureChannel.SC_OUT_OFFSET + 1)] = (byte) (len - 2);
|
||||||
|
|
||||||
|
if (makeCurrent) {
|
||||||
|
commitTmpPath();
|
||||||
|
}
|
||||||
|
|
||||||
secureChannel.respond(apdu, len, ISO7816.SW_NO_ERROR);
|
secureChannel.respond(apdu, len, ISO7816.SW_NO_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1435,7 +1338,11 @@ public class KeycardApplet extends Applet {
|
|||||||
* @return whether the current key path is the same as the one defined as PIN-less or not
|
* @return whether the current key path is the same as the one defined as PIN-less or not
|
||||||
*/
|
*/
|
||||||
private boolean isPinless() {
|
private boolean isPinless() {
|
||||||
return (pinlessPathLen > 0) && (pinlessPathLen == keyPathLen) && (Util.arrayCompare(keyPath, (short) 0, pinlessPath, (short) 0, keyPathLen) == 0);
|
return (pinlessPathLen > 0) && (pinlessPathLen == tmpPathLen) && (Util.arrayCompare(tmpPath, (short) 0, pinlessPath, (short) 0, tmpPathLen) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isEIP1581() {
|
||||||
|
return (tmpPathLen >= (short)(((short) EIP_1581_PREFIX.length) + 8)) && (Util.arrayCompare(EIP_1581_PREFIX, (short) 0, tmpPath, (short) 0, (short) EIP_1581_PREFIX.length) == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1444,14 +1351,5 @@ public class KeycardApplet extends Applet {
|
|||||||
private void resetCurveParameters() {
|
private void resetCurveParameters() {
|
||||||
SECP256k1.setCurveParameters(masterPublic);
|
SECP256k1.setCurveParameters(masterPublic);
|
||||||
SECP256k1.setCurveParameters(masterPrivate);
|
SECP256k1.setCurveParameters(masterPrivate);
|
||||||
|
|
||||||
SECP256k1.setCurveParameters(parentPublicKey);
|
|
||||||
SECP256k1.setCurveParameters(parentPrivateKey);
|
|
||||||
|
|
||||||
SECP256k1.setCurveParameters(publicKey);
|
|
||||||
SECP256k1.setCurveParameters(privateKey);
|
|
||||||
|
|
||||||
SECP256k1.setCurveParameters(pinlessPublicKey);
|
|
||||||
SECP256k1.setCurveParameters(pinlessPrivateKey);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1284,7 +1284,7 @@ public class KeycardTest {
|
|||||||
verifyExportedKey(keyTemplate, keyPair, chainCode, new int[] { 0x8000002b, 0x8000003c, 0x8000062d, 0x00000000 }, true, false);
|
verifyExportedKey(keyTemplate, keyPair, chainCode, new int[] { 0x8000002b, 0x8000003c, 0x8000062d, 0x00000000 }, true, false);
|
||||||
|
|
||||||
// Derive & Make current
|
// Derive & Make current
|
||||||
response = cmdSet.exportKey(new byte[] {(byte) 0x80, 0x00, 0x00, 0x2B, (byte) 0x80, 0x00, 0x00, 0x3C, (byte) 0x80, 0x00, 0x06, 0x2D, (byte) 0x00, 0x00, 0x00, 0x00, (byte) 0x00, 0x00, 0x00, 0x00}, KeycardApplet.DERIVE_P1_SOURCE_MASTER,true,false);
|
response = cmdSet.exportKey(new byte[] {(byte) 0x80, 0x00, 0x00, 0x2B, (byte) 0x80, 0x00, 0x00, 0x3C, (byte) 0x80, 0x00, 0x06, 0x2D, (byte) 0x00, 0x00, 0x00, 0x00, (byte) 0x00, 0x00, 0x00, 0x00}, KeycardApplet.DERIVE_P1_SOURCE_MASTER, true, false);
|
||||||
assertEquals(0x9000, response.getSw());
|
assertEquals(0x9000, response.getSw());
|
||||||
keyTemplate = response.getData();
|
keyTemplate = response.getData();
|
||||||
verifyExportedKey(keyTemplate, keyPair, chainCode, new int[] { 0x8000002b, 0x8000003c, 0x8000062d, 0x00000000, 0x00000000 }, false, false);
|
verifyExportedKey(keyTemplate, keyPair, chainCode, new int[] { 0x8000002b, 0x8000003c, 0x8000062d, 0x00000000, 0x00000000 }, false, false);
|
||||||
@ -1293,7 +1293,7 @@ public class KeycardTest {
|
|||||||
response = cmdSet.exportKey(new byte[] {(byte) 0x00, 0x00, 0x00, 0x01}, KeycardApplet.DERIVE_P1_SOURCE_PARENT, false,false);
|
response = cmdSet.exportKey(new byte[] {(byte) 0x00, 0x00, 0x00, 0x01}, KeycardApplet.DERIVE_P1_SOURCE_PARENT, false,false);
|
||||||
assertEquals(0x9000, response.getSw());
|
assertEquals(0x9000, response.getSw());
|
||||||
keyTemplate = response.getData();
|
keyTemplate = response.getData();
|
||||||
verifyExportedKey(keyTemplate, keyPair, chainCode, new int[] { 0x8000002b, 0x8000003c, 0x8000062d, 0x00000000, 0x00000001 }, false, true);
|
verifyExportedKey(keyTemplate, keyPair, chainCode, new int[] { 0x8000002b, 0x8000003c, 0x8000062d, 0x00000000, 0x00000001 }, false, false);
|
||||||
response = cmdSet.getStatus(KeycardApplet.GET_STATUS_P1_KEY_PATH);
|
response = cmdSet.getStatus(KeycardApplet.GET_STATUS_P1_KEY_PATH);
|
||||||
assertEquals(0x9000, response.getSw());
|
assertEquals(0x9000, response.getSw());
|
||||||
assertArrayEquals(new byte[] {(byte) 0x80, 0x00, 0x00, 0x2B, (byte) 0x80, 0x00, 0x00, 0x3C, (byte) 0x80, 0x00, 0x06, 0x2D, (byte) 0x00, 0x00, 0x00, 0x00, (byte) 0x00, 0x00, 0x00, 0x00}, response.getData());
|
assertArrayEquals(new byte[] {(byte) 0x80, 0x00, 0x00, 0x2B, (byte) 0x80, 0x00, 0x00, 0x3C, (byte) 0x80, 0x00, 0x06, 0x2D, (byte) 0x00, 0x00, 0x00, 0x00, (byte) 0x00, 0x00, 0x00, 0x00}, response.getData());
|
||||||
@ -1304,6 +1304,15 @@ public class KeycardTest {
|
|||||||
keyTemplate = response.getData();
|
keyTemplate = response.getData();
|
||||||
verifyExportedKey(keyTemplate, keyPair, chainCode, new int[] { 0x8000002b, 0x8000003c, 0x8000062d, 0x00000000, 0x00000000 }, false, false);
|
verifyExportedKey(keyTemplate, keyPair, chainCode, new int[] { 0x8000002b, 0x8000003c, 0x8000062d, 0x00000000, 0x00000000 }, false, false);
|
||||||
|
|
||||||
|
// Export extended public
|
||||||
|
response = cmdSet.exportExtendedPublicKey(new byte[] {(byte) 0x80, 0x00, 0x00, 0x2B, (byte) 0x80, 0x00, 0x00, 0x3C, (byte) 0x80, 0x00, 0x06, 0x2D, (byte) 0x00, 0x00, 0x00, 0x00, (byte) 0x00, 0x00, 0x00, 0x00}, KeycardApplet.DERIVE_P1_SOURCE_MASTER);
|
||||||
|
assertEquals(0x6985, response.getSw());
|
||||||
|
|
||||||
|
response = cmdSet.exportExtendedPublicKey(new byte[] {(byte) 0x80, 0x00, 0x00, 0x2B, (byte) 0x80, 0x00, 0x00, 0x3C, (byte) 0x80, 0x00, 0x06, 0x2c, (byte) 0x00, 0x00, 0x00, 0x00}, KeycardApplet.DERIVE_P1_SOURCE_MASTER);
|
||||||
|
assertEquals(0x9000, response.getSw());
|
||||||
|
keyTemplate = response.getData();
|
||||||
|
verifyExportedKey(keyTemplate, keyPair, chainCode, new int[] { 0x8000002b, 0x8000003c, 0x8000062c, 0x00000000 }, true, true);
|
||||||
|
|
||||||
// Reset
|
// Reset
|
||||||
response = cmdSet.deriveKey(new byte[0], KeycardApplet.DERIVE_P1_SOURCE_MASTER);
|
response = cmdSet.deriveKey(new byte[0], KeycardApplet.DERIVE_P1_SOURCE_MASTER);
|
||||||
assertEquals(0x9000, response.getSw());
|
assertEquals(0x9000, response.getSw());
|
||||||
@ -1673,28 +1682,34 @@ public class KeycardTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void verifyExportedKey(byte[] keyTemplate, KeyPair keyPair, byte[] chainCode, int[] path, boolean publicOnly, boolean noPubKey) {
|
private void verifyExportedKey(byte[] keyTemplate, KeyPair keyPair, byte[] chainCode, int[] path, boolean publicOnly, boolean extendedPublic) {
|
||||||
if (!cmdSet.getApplicationInfo().hasKeyManagementCapability()) {
|
if (!cmdSet.getApplicationInfo().hasKeyManagementCapability()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ECKey key = deriveKey(keyPair, chainCode, path).decompress();
|
DeterministicKey dk = deriveKey(keyPair, chainCode, path);
|
||||||
|
ECKey key = dk.decompress();
|
||||||
assertEquals(KeycardApplet.TLV_KEY_TEMPLATE, keyTemplate[0]);
|
assertEquals(KeycardApplet.TLV_KEY_TEMPLATE, keyTemplate[0]);
|
||||||
int pubKeyLen = 0;
|
|
||||||
|
if (publicOnly) {
|
||||||
if (!noPubKey) {
|
|
||||||
assertEquals(KeycardApplet.TLV_PUB_KEY, keyTemplate[2]);
|
assertEquals(KeycardApplet.TLV_PUB_KEY, keyTemplate[2]);
|
||||||
byte[] pubKey = Arrays.copyOfRange(keyTemplate, 4, 4 + keyTemplate[3]);
|
byte[] pubKey = Arrays.copyOfRange(keyTemplate, 4, 4 + keyTemplate[3]);
|
||||||
|
|
||||||
assertArrayEquals(key.getPubKey(), pubKey);
|
assertArrayEquals(key.getPubKey(), pubKey);
|
||||||
pubKeyLen = 2 + pubKey.length;
|
int templateLen = 2 + pubKey.length;
|
||||||
}
|
|
||||||
|
|
||||||
if (publicOnly) {
|
if (extendedPublic) {
|
||||||
assertEquals(pubKeyLen, keyTemplate[1]);
|
byte[] chain = Arrays.copyOfRange(keyTemplate, templateLen + 4, templateLen + 4 + keyTemplate[3 + templateLen]);
|
||||||
assertEquals(pubKeyLen + 2, keyTemplate.length);
|
assertEquals(KeycardApplet.TLV_CHAIN_CODE, keyTemplate[2 + templateLen]);
|
||||||
|
assertArrayEquals(dk.getChainCode(), chain);
|
||||||
|
templateLen += 2 + chain.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(templateLen, keyTemplate[1]);
|
||||||
|
assertEquals(templateLen + 2, keyTemplate.length);
|
||||||
} else {
|
} else {
|
||||||
assertEquals(KeycardApplet.TLV_PRIV_KEY, keyTemplate[2 + pubKeyLen]);
|
assertEquals(KeycardApplet.TLV_PRIV_KEY, keyTemplate[2]);
|
||||||
byte[] privateKey = Arrays.copyOfRange(keyTemplate, 4 + pubKeyLen, 4 + pubKeyLen + keyTemplate[3 + pubKeyLen]);
|
byte[] privateKey = Arrays.copyOfRange(keyTemplate, 4, 4 + keyTemplate[3]);
|
||||||
|
|
||||||
byte[] tPrivKey = key.getPrivKey().toByteArray();
|
byte[] tPrivKey = key.getPrivKey().toByteArray();
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ package im.status.keycard;
|
|||||||
|
|
||||||
import im.status.keycard.applet.ApplicationStatus;
|
import im.status.keycard.applet.ApplicationStatus;
|
||||||
import im.status.keycard.applet.KeycardCommandSet;
|
import im.status.keycard.applet.KeycardCommandSet;
|
||||||
|
import im.status.keycard.io.APDUCommand;
|
||||||
import im.status.keycard.io.APDUResponse;
|
import im.status.keycard.io.APDUResponse;
|
||||||
import im.status.keycard.io.CardChannel;
|
import im.status.keycard.io.CardChannel;
|
||||||
import org.web3j.crypto.ECKeyPair;
|
import org.web3j.crypto.ECKeyPair;
|
||||||
@ -12,12 +13,17 @@ import java.security.interfaces.ECPrivateKey;
|
|||||||
|
|
||||||
|
|
||||||
public class TestKeycardCommandSet extends KeycardCommandSet {
|
public class TestKeycardCommandSet extends KeycardCommandSet {
|
||||||
|
private CardChannel ac;
|
||||||
|
private TestSecureChannelSession sc;
|
||||||
|
|
||||||
public TestKeycardCommandSet(CardChannel apduChannel) {
|
public TestKeycardCommandSet(CardChannel apduChannel) {
|
||||||
super(apduChannel);
|
super(apduChannel);
|
||||||
|
ac = apduChannel;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSecureChannel(TestSecureChannelSession secureChannel) {
|
public void setSecureChannel(TestSecureChannelSession secureChannel) {
|
||||||
super.setSecureChannel(secureChannel);
|
super.setSecureChannel(secureChannel);
|
||||||
|
sc = secureChannel;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -77,6 +83,11 @@ public class TestKeycardCommandSet extends KeycardCommandSet {
|
|||||||
return loadKey(data, LOAD_KEY_P1_SEED);
|
return loadKey(data, LOAD_KEY_P1_SEED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public APDUResponse exportExtendedPublicKey(byte[] keyPath, byte source) throws IOException {
|
||||||
|
APDUCommand exportKey = sc.protectedCommand(0x80, 0xc2, (source | 0x01), 2, keyPath);
|
||||||
|
return sc.transmit(ac, exportKey);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends a GET STATUS APDU to retrieve the APPLICATION STATUS template and reads the byte indicating key initialization
|
* Sends a GET STATUS APDU to retrieve the APPLICATION STATUS template and reads the byte indicating key initialization
|
||||||
* status
|
* status
|
||||||
|
Loading…
x
Reference in New Issue
Block a user