document main applet class
This commit is contained in:
parent
eb27f6c2c4
commit
196e91f3ec
|
@ -70,10 +70,8 @@ public class Crypto {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
JCSystem.beginTransaction();
|
|
||||||
privateKey.setS(tmp, off, (short) KEY_SECRET_SIZE);
|
privateKey.setS(tmp, off, (short) KEY_SECRET_SIZE);
|
||||||
Util.arrayCopy(tmp, (short)(off + KEY_SECRET_SIZE), chain, chainOff, (short) KEY_SECRET_SIZE);
|
Util.arrayCopy(tmp, (short)(off + KEY_SECRET_SIZE), chain, chainOff, (short) KEY_SECRET_SIZE);
|
||||||
JCSystem.commitTransaction();
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,9 @@ package im.status.wallet;
|
||||||
import javacard.framework.*;
|
import javacard.framework.*;
|
||||||
import javacard.security.*;
|
import javacard.security.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The applet's main class. All incoming commands a processed by this class.
|
||||||
|
*/
|
||||||
public class WalletApplet extends Applet {
|
public class WalletApplet extends Applet {
|
||||||
static final byte INS_GET_STATUS = (byte) 0xF2;
|
static final byte INS_GET_STATUS = (byte) 0xF2;
|
||||||
static final byte INS_VERIFY_PIN = (byte) 0x20;
|
static final byte INS_VERIFY_PIN = (byte) 0x20;
|
||||||
|
@ -66,8 +69,8 @@ public class WalletApplet extends Applet {
|
||||||
static final byte TLV_KEY_INITIALIZATION_STATUS = (byte) 0xC2;
|
static final byte TLV_KEY_INITIALIZATION_STATUS = (byte) 0xC2;
|
||||||
static final byte TLV_PUBLIC_KEY_DERIVATION = (byte) 0xC3;
|
static final byte TLV_PUBLIC_KEY_DERIVATION = (byte) 0xC3;
|
||||||
|
|
||||||
private static final byte[] ASSISTED_DERIVATION_HASH = { (byte) 0xAA, (byte) 0x2D, (byte) 0xA9, (byte) 0x9D, (byte) 0x91, (byte) 0x8C, (byte) 0x7D, (byte) 0x95, (byte) 0xB8, (byte) 0x96, (byte) 0x89, (byte) 0x87, (byte) 0x3E, (byte) 0xAA, (byte) 0x37, (byte) 0x67, (byte) 0x25, (byte) 0x0C, (byte) 0xFF, (byte) 0x50, (byte) 0x13, (byte) 0x9A, (byte) 0x2F, (byte) 0x87, (byte) 0xBB, (byte) 0x4F, (byte) 0xCA, (byte) 0xB4, (byte) 0xAE, (byte) 0xC3, (byte) 0xE8, (byte) 0x90};
|
private static final byte[] ASSISTED_DERIVATION_HASH = {(byte) 0xAA, (byte) 0x2D, (byte) 0xA9, (byte) 0x9D, (byte) 0x91, (byte) 0x8C, (byte) 0x7D, (byte) 0x95, (byte) 0xB8, (byte) 0x96, (byte) 0x89, (byte) 0x87, (byte) 0x3E, (byte) 0xAA, (byte) 0x37, (byte) 0x67, (byte) 0x25, (byte) 0x0C, (byte) 0xFF, (byte) 0x50, (byte) 0x13, (byte) 0x9A, (byte) 0x2F, (byte) 0x87, (byte) 0xBB, (byte) 0x4F, (byte) 0xCA, (byte) 0xB4, (byte) 0xAE, (byte) 0xC3, (byte) 0xE8, (byte) 0x90};
|
||||||
private static final byte[] WHISPER_KEY_PATH = { 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01};
|
private static final byte[] WHISPER_KEY_PATH = {0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01};
|
||||||
|
|
||||||
private OwnerPIN pin;
|
private OwnerPIN pin;
|
||||||
private OwnerPIN puk;
|
private OwnerPIN puk;
|
||||||
|
@ -92,10 +95,28 @@ public class WalletApplet extends Applet {
|
||||||
private boolean signInProgress;
|
private boolean signInProgress;
|
||||||
private boolean expectPublicKey;
|
private boolean expectPublicKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
public static void install(byte[] bArray, short bOffset, byte bLength) {
|
||||||
new WalletApplet(bArray, bOffset, bLength);
|
new WalletApplet(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 WalletApplet(byte[] bArray, short bOffset, byte bLength) {
|
public WalletApplet(byte[] bArray, short bOffset, byte bLength) {
|
||||||
SECP256k1.init();
|
SECP256k1.init();
|
||||||
Crypto.init();
|
Crypto.init();
|
||||||
|
@ -133,7 +154,15 @@ public class WalletApplet extends Applet {
|
||||||
register(bArray, (short) (bOffset + 1), bArray[bOffset]);
|
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 {
|
public void process(APDU apdu) throws ISOException {
|
||||||
|
// Since selection can happen not only by a SELECT command, we check for that separately.
|
||||||
if (selectingApplet()) {
|
if (selectingApplet()) {
|
||||||
selectApplet(apdu);
|
selectApplet(apdu);
|
||||||
return;
|
return;
|
||||||
|
@ -181,6 +210,12 @@ public class WalletApplet extends Applet {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoked on applet (re-)selection. Aborts any in-progress signing session and sets PIN and PUK to not verified.
|
||||||
|
* Responds with a SECP256k1 public key which the client must use to establish a secure channel.
|
||||||
|
*
|
||||||
|
* @param apdu the JCRE-owned APDU object.
|
||||||
|
*/
|
||||||
private void selectApplet(APDU apdu) {
|
private void selectApplet(APDU apdu) {
|
||||||
signInProgress = false;
|
signInProgress = false;
|
||||||
pin.reset();
|
pin.reset();
|
||||||
|
@ -191,20 +226,25 @@ public class WalletApplet extends Applet {
|
||||||
apdu.setOutgoingAndSend(ISO7816.OFFSET_CDATA, keyLength);
|
apdu.setOutgoingAndSend(ISO7816.OFFSET_CDATA, keyLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes the GET STATUS command according to the application's specifications. This command is always a Case-2 APDU.
|
||||||
|
* Requires an open secure channel but does not check if the PIN has been verified.
|
||||||
|
*
|
||||||
|
* @param apdu the JCRE-owned APDU object.
|
||||||
|
*/
|
||||||
private void getStatus(APDU apdu) {
|
private void getStatus(APDU apdu) {
|
||||||
if (!secureChannel.isOpen()) {
|
if (!secureChannel.isOpen()) {
|
||||||
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
|
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
|
||||||
}
|
}
|
||||||
|
|
||||||
short off = SecureChannel.SC_OUT_OFFSET;
|
|
||||||
byte[] apduBuffer = apdu.getBuffer();
|
byte[] apduBuffer = apdu.getBuffer();
|
||||||
|
|
||||||
short len;
|
short len;
|
||||||
|
|
||||||
if (apduBuffer[ISO7816.OFFSET_P1] == GET_STATUS_P1_APPLICATION) {
|
if (apduBuffer[ISO7816.OFFSET_P1] == GET_STATUS_P1_APPLICATION) {
|
||||||
len = getApplicationStatus(apduBuffer, off);
|
len = getApplicationStatus(apduBuffer, SecureChannel.SC_OUT_OFFSET);
|
||||||
} else if (apduBuffer[ISO7816.OFFSET_P1] == GET_STATUS_P1_KEY_PATH) {
|
} else if (apduBuffer[ISO7816.OFFSET_P1] == GET_STATUS_P1_KEY_PATH) {
|
||||||
len = getKeyStatus(apduBuffer, off);
|
len = getKeyStatus(apduBuffer, SecureChannel.SC_OUT_OFFSET);
|
||||||
} else {
|
} else {
|
||||||
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
|
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
|
||||||
return;
|
return;
|
||||||
|
@ -214,6 +254,15 @@ public class WalletApplet extends Applet {
|
||||||
apdu.setOutgoingAndSend(ISO7816.OFFSET_CDATA, len);
|
apdu.setOutgoingAndSend(ISO7816.OFFSET_CDATA, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes the Application Status Template to the APDU buffer. Invoked internally by the getStatus method. This
|
||||||
|
* template is useful to understand if the card is blocked, if it has valid keys and if public key derivation is
|
||||||
|
* supported.
|
||||||
|
*
|
||||||
|
* @param apduBuffer the APDU buffer
|
||||||
|
* @param off the offset in the buffer where the application status template must be written at.
|
||||||
|
* @return the length in bytes of the data to output
|
||||||
|
*/
|
||||||
private short getApplicationStatus(byte[] apduBuffer, short off) {
|
private short getApplicationStatus(byte[] apduBuffer, short off) {
|
||||||
apduBuffer[off++] = TLV_APPLICATION_STATUS_TEMPLATE;
|
apduBuffer[off++] = TLV_APPLICATION_STATUS_TEMPLATE;
|
||||||
apduBuffer[off++] = 9;
|
apduBuffer[off++] = 9;
|
||||||
|
@ -233,6 +282,16 @@ public class WalletApplet extends Applet {
|
||||||
return (short) (off - SecureChannel.SC_OUT_OFFSET);
|
return (short) (off - SecureChannel.SC_OUT_OFFSET);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes the key path status to the APDU buffer. Invoked internally by the getStatus method. The key path indicates
|
||||||
|
* at which point in the BIP32 hierarchy we are at. The data is unformatted and is simply a sequence of 32-bit
|
||||||
|
* big endian integers. The Master key is not indicated so nothing will be written if no derivation has been performed.
|
||||||
|
* However, because of the secure channel, the response will still contain the IV and the padding.
|
||||||
|
*
|
||||||
|
* @param apduBuffer the APDU buffer
|
||||||
|
* @param off the offset in the buffer where the key status template must be written at.
|
||||||
|
* @return the length in bytes of the data to output
|
||||||
|
*/
|
||||||
private short getKeyStatus(byte[] apduBuffer, short off) {
|
private short getKeyStatus(byte[] apduBuffer, short off) {
|
||||||
if (expectPublicKey) {
|
if (expectPublicKey) {
|
||||||
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
|
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
|
||||||
|
@ -242,6 +301,13 @@ public class WalletApplet extends Applet {
|
||||||
return keyPathLen;
|
return keyPathLen;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes the VERIFY PIN command. Requires a secure channel to be already open. If a PIN longer or shorter than 6
|
||||||
|
* digits is provided, the method will still proceed with its verification and will decrease the remaining tries
|
||||||
|
* counter.
|
||||||
|
*
|
||||||
|
* @param apdu the JCRE-owned APDU object.
|
||||||
|
*/
|
||||||
private void verifyPIN(APDU apdu) {
|
private void verifyPIN(APDU apdu) {
|
||||||
apdu.setIncomingAndReceive();
|
apdu.setIncomingAndReceive();
|
||||||
|
|
||||||
|
@ -257,6 +323,13 @@ public class WalletApplet extends Applet {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes the VERIFY PIN command. Requires a secure channel to be already open and the PIN to be verified. Since
|
||||||
|
* the PIN is fixed to a 6-digits format, longer or shorter PINs or PINs containing non-numeric characters will be
|
||||||
|
* refused.
|
||||||
|
*
|
||||||
|
* @param apdu the JCRE-owned APDU object.
|
||||||
|
*/
|
||||||
private void changePIN(APDU apdu) {
|
private void changePIN(APDU apdu) {
|
||||||
apdu.setIncomingAndReceive();
|
apdu.setIncomingAndReceive();
|
||||||
|
|
||||||
|
@ -275,6 +348,14 @@ public class WalletApplet extends Applet {
|
||||||
pin.check(apduBuffer, ISO7816.OFFSET_CDATA, len);
|
pin.check(apduBuffer, ISO7816.OFFSET_CDATA, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes the UNBLOCK PIN command. Requires a secure channel to be already open and the PIN to be blocked. The PUK
|
||||||
|
* and the new PIN are sent in the same APDU with no separator. This is possible because the PUK is exactly 12 digits
|
||||||
|
* long and the PIN is 6 digits long. If the data is not in the correct format (i.e: anything other than 18 digits),
|
||||||
|
* PUK verification is not attempted, so the remaining tries counter of the PUK is not decreased.
|
||||||
|
*
|
||||||
|
* @param apdu the JCRE-owned APDU object.
|
||||||
|
*/
|
||||||
private void unblockPIN(APDU apdu) {
|
private void unblockPIN(APDU apdu) {
|
||||||
apdu.setIncomingAndReceive();
|
apdu.setIncomingAndReceive();
|
||||||
|
|
||||||
|
@ -299,6 +380,15 @@ public class WalletApplet extends Applet {
|
||||||
puk.reset();
|
puk.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes the LOAD KEY command. Requires a secure channel to be already open and the PIN to be verified. The key
|
||||||
|
* being loaded will be treated as the master key. If the key is not in extended format (i.e: does not contain a chain
|
||||||
|
* code) no further derivation will be possible. Loading a key resets the current key path and the loaded key becomes
|
||||||
|
* the one used for signing. Transactions are used to make sure that either all key components are loaded correctly
|
||||||
|
* or none is loaded at all.
|
||||||
|
*
|
||||||
|
* @param apdu the JCRE-owned APDU object.
|
||||||
|
*/
|
||||||
private void loadKey(APDU apdu) {
|
private void loadKey(APDU apdu) {
|
||||||
apdu.setIncomingAndReceive();
|
apdu.setIncomingAndReceive();
|
||||||
|
|
||||||
|
@ -330,6 +420,14 @@ public class WalletApplet extends Applet {
|
||||||
keyPathLen = 0;
|
keyPathLen = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called internally by the loadKey method to load a key in the TLV format. The presence of the public key is optional
|
||||||
|
* if public key derivation is supported on card, otherwise it is mandatory. The presence of a chain code is indicated
|
||||||
|
* explicitly through the newExtended argument (which is set depending on the P1 parameter of the command).
|
||||||
|
*
|
||||||
|
* @param apduBuffer the APDU buffer
|
||||||
|
* @param newExtended whether the key to load contains a chain code or not
|
||||||
|
*/
|
||||||
private void loadKeyPair(byte[] apduBuffer, boolean newExtended) {
|
private void loadKeyPair(byte[] apduBuffer, boolean newExtended) {
|
||||||
short pubOffset = (short)(ISO7816.OFFSET_CDATA + (apduBuffer[(short) (ISO7816.OFFSET_CDATA + 1)] == (byte) 0x81 ? 3 : 2));
|
short pubOffset = (short)(ISO7816.OFFSET_CDATA + (apduBuffer[(short) (ISO7816.OFFSET_CDATA + 1)] == (byte) 0x81 ? 3 : 2));
|
||||||
short privOffset = (short)(pubOffset + apduBuffer[(short)(pubOffset + 1)] + 2);
|
short privOffset = (short)(pubOffset + apduBuffer[(short)(pubOffset + 1)] + 2);
|
||||||
|
@ -382,6 +480,14 @@ public class WalletApplet extends Applet {
|
||||||
JCSystem.commitTransaction();
|
JCSystem.commitTransaction();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called internally by the loadKey method to load a key from a sequence of 64 bytes, supposedly generated according
|
||||||
|
* to the algorithms described in the BIP39 specifications. This way of loading keys is only supported when public
|
||||||
|
* key derivation is available. If not, the public key must be derived off-card and the key must be formatted in the
|
||||||
|
* TLV format processed by the loadKeyPair method.
|
||||||
|
*
|
||||||
|
* @param apduBuffer the APDU buffer
|
||||||
|
*/
|
||||||
private void loadSeed(byte[] apduBuffer) {
|
private void loadSeed(byte[] apduBuffer) {
|
||||||
SECP256k1.assetECPointMultiplicationSupport();
|
SECP256k1.assetECPointMultiplicationSupport();
|
||||||
|
|
||||||
|
@ -405,6 +511,30 @@ public class WalletApplet extends Applet {
|
||||||
JCSystem.commitTransaction();
|
JCSystem.commitTransaction();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes the DERIVE KEY command. Requires a secure channel to be already open. Unless a PIN-less path exists, t
|
||||||
|
* the PIN must be verified as well. The master key must be already loaded and have a chain code. In the happy case
|
||||||
|
* this method is quite straightforward, since it takes a sequence of 32-bit big-endian integers and perform key
|
||||||
|
* 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. Note that because of the secure channel, the
|
||||||
|
* command must still contain the IV and padding even if no actual data is sent.
|
||||||
|
*
|
||||||
|
* @param apdu the JCRE-owned APDU object.
|
||||||
|
*/
|
||||||
private void deriveKey(APDU apdu) {
|
private void deriveKey(APDU apdu) {
|
||||||
if (!(secureChannel.isOpen() && (pin.isValidated() || (pinlessPathLen > 0)) && isExtended)) {
|
if (!(secureChannel.isOpen() && (pin.isValidated() || (pinlessPathLen > 0)) && isExtended)) {
|
||||||
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
|
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
|
||||||
|
@ -424,9 +554,11 @@ public class WalletApplet extends Applet {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isPublicKey) {
|
if (isPublicKey) {
|
||||||
|
JCSystem.beginTransaction();
|
||||||
publicKey.setW(apduBuffer, ISO7816.OFFSET_CDATA, len);
|
publicKey.setW(apduBuffer, ISO7816.OFFSET_CDATA, len);
|
||||||
expectPublicKey = false;
|
expectPublicKey = false;
|
||||||
keyPathLen += 4;
|
keyPathLen += 4;
|
||||||
|
JCSystem.commitTransaction();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -442,31 +574,35 @@ public class WalletApplet extends Applet {
|
||||||
|
|
||||||
if (isReset) {
|
if (isReset) {
|
||||||
resetKeys(apduBuffer, chainEnd);
|
resetKeys(apduBuffer, chainEnd);
|
||||||
expectPublicKey = false;
|
|
||||||
keyPathLen = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
signInProgress = false;
|
signInProgress = false;
|
||||||
|
|
||||||
Util.arrayCopyNonAtomic(apduBuffer, ISO7816.OFFSET_CDATA, keyPath, keyPathLen, len);
|
|
||||||
|
|
||||||
for (short i = ISO7816.OFFSET_CDATA; i < chainEnd; i += 4) {
|
for (short i = ISO7816.OFFSET_CDATA; i < chainEnd; i += 4) {
|
||||||
|
JCSystem.beginTransaction();
|
||||||
Crypto.bip32CKDPriv(apduBuffer, i, privateKey, publicKey, chainCode, (short) 0);
|
Crypto.bip32CKDPriv(apduBuffer, i, privateKey, publicKey, chainCode, (short) 0);
|
||||||
|
Util.arrayCopy(apduBuffer, i, keyPath, keyPathLen, (short) 4);
|
||||||
|
|
||||||
if (assistedDerivation) {
|
if (assistedDerivation) {
|
||||||
expectPublicKey = true;
|
expectPublicKey = true;
|
||||||
outputPublicX(apdu, apduBuffer);
|
outputPublicX(apdu, apduBuffer);
|
||||||
return;
|
|
||||||
} else {
|
} else {
|
||||||
short pubLen = SECP256k1.derivePublicKey(privateKey, apduBuffer, chainEnd);
|
short pubLen = SECP256k1.derivePublicKey(privateKey, apduBuffer, chainEnd);
|
||||||
publicKey.setW(apduBuffer, chainEnd, pubLen);
|
publicKey.setW(apduBuffer, chainEnd, pubLen);
|
||||||
|
keyPathLen += 4;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
expectPublicKey = false;
|
JCSystem.commitTransaction();
|
||||||
keyPathLen += len;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Outputs the X of the public key for the current private. Called internally by the deriveKey method and used during
|
||||||
|
* assisted key derivation.
|
||||||
|
*
|
||||||
|
* @param apdu the JCRE-owned APDU object.
|
||||||
|
* @param apduBuffer the APDU buffer.
|
||||||
|
*/
|
||||||
private void outputPublicX(APDU apdu, byte[] apduBuffer) {
|
private void outputPublicX(APDU apdu, byte[] apduBuffer) {
|
||||||
short xLen = SECP256k1.derivePublicX(privateKey, apduBuffer, (short) (SecureChannel.SC_OUT_OFFSET + 4));
|
short xLen = SECP256k1.derivePublicX(privateKey, apduBuffer, (short) (SecureChannel.SC_OUT_OFFSET + 4));
|
||||||
|
|
||||||
|
@ -482,6 +618,13 @@ public class WalletApplet extends Applet {
|
||||||
apdu.setOutgoingAndSend(ISO7816.OFFSET_CDATA, outLen);
|
apdu.setOutgoingAndSend(ISO7816.OFFSET_CDATA, outLen);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets the current key and key path to the master key. A transaction is used to make sure this all happens at once.
|
||||||
|
* This method is called internally by the deriveKey method.
|
||||||
|
*
|
||||||
|
* @param buffer a buffer which can be overwritten (currently the APDU buffer)
|
||||||
|
* @param offset the offset at which the buffer is free
|
||||||
|
*/
|
||||||
private void resetKeys(byte[] buffer, short offset) {
|
private void resetKeys(byte[] buffer, short offset) {
|
||||||
short pubOff = (short) (offset + masterPrivate.getS(buffer, offset));
|
short pubOff = (short) (offset + masterPrivate.getS(buffer, offset));
|
||||||
short pubLen = masterPublic.getW(buffer, pubOff);
|
short pubLen = masterPublic.getW(buffer, pubOff);
|
||||||
|
@ -490,9 +633,24 @@ public class WalletApplet extends Applet {
|
||||||
Util.arrayCopy(masterChainCode, (short) 0, chainCode, (short) 0, CHAIN_CODE_SIZE);
|
Util.arrayCopy(masterChainCode, (short) 0, chainCode, (short) 0, CHAIN_CODE_SIZE);
|
||||||
privateKey.setS(buffer, offset, CHAIN_CODE_SIZE);
|
privateKey.setS(buffer, offset, CHAIN_CODE_SIZE);
|
||||||
publicKey.setW(buffer, pubOff, pubLen);
|
publicKey.setW(buffer, pubOff, pubLen);
|
||||||
|
expectPublicKey = false;
|
||||||
|
keyPathLen = 0;
|
||||||
JCSystem.commitTransaction();
|
JCSystem.commitTransaction();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a mnemonic phrase according to the BIP39 specifications. Requires an open secure channel. Since embedding
|
||||||
|
* the strings in the applet would be unreasonable, the data returned is actually a sequence of 16-bit big-endian
|
||||||
|
* integers with values ranging from 0 to 2047. These numbers should be used by the client as indexes in their own
|
||||||
|
* string tables which is used to actually generate the mnemonic phrase.
|
||||||
|
*
|
||||||
|
* The P1 parameter is the length of the checksum which indirectly also defines the length of the secret and finally
|
||||||
|
* the number of generated words. Although using the length of the checksum as the defining parameter (as opposed to
|
||||||
|
* the word count for example) might seem peculiar, this is done because it's valid values are strictly in the
|
||||||
|
* inclusive range from 4 to 8 which makes it easy to validate input.
|
||||||
|
*
|
||||||
|
* @param apdu the JCRE-owned APDU object.
|
||||||
|
*/
|
||||||
private void generateMnemonic(APDU apdu) {
|
private void generateMnemonic(APDU apdu) {
|
||||||
if (!secureChannel.isOpen()) {
|
if (!secureChannel.isOpen()) {
|
||||||
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
|
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
|
||||||
|
@ -537,7 +695,18 @@ public class WalletApplet extends Applet {
|
||||||
apdu.setOutgoingAndSend(ISO7816.OFFSET_CDATA, outLen);
|
apdu.setOutgoingAndSend(ISO7816.OFFSET_CDATA, outLen);
|
||||||
}
|
}
|
||||||
|
|
||||||
// This works on simulator AND on JavaCard. Since we do not do a lot of these operations, the performance hit is non-existent
|
/**
|
||||||
|
* Logically shifts the given short to the right. Used internally by the generateMnemonic method. This method exists
|
||||||
|
* because a simple logical right shift using shorts would most likely work on the actual target (which does math on
|
||||||
|
* shorts) but not on the simulator since a negative short would first be extended to 32-bit, shifted and then cut
|
||||||
|
* back to 16-bit, doing the equivalent of an arithmetic shift. Simply masking by 0x0000FFFF before shifting is not an
|
||||||
|
* option because the code would not convert to CAP file (because of int usage). Since this method works on both
|
||||||
|
* JavaCard and simulator and it is not invoked very often, the performance hit is non-existent.
|
||||||
|
*
|
||||||
|
* @param v value to shift
|
||||||
|
* @param amount amount
|
||||||
|
* @return logically right shifted value
|
||||||
|
*/
|
||||||
private short logicrShift(short v, short amount) {
|
private short logicrShift(short v, short amount) {
|
||||||
if (amount == 0) return v; // short circuit on 0
|
if (amount == 0) return v; // short circuit on 0
|
||||||
short tmp = (short) (v & 0x7fff);
|
short tmp = (short) (v & 0x7fff);
|
||||||
|
@ -551,6 +720,19 @@ public class WalletApplet extends Applet {
|
||||||
return (short) ((short)((short) 0x4000 >>> (short) (amount - 1)) | tmp);
|
return (short) ((short)((short) 0x4000 >>> (short) (amount - 1)) | tmp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes the SIGN command. Requires a secure channel to open and either the PIN to be verified or the PIN-less key
|
||||||
|
* path to be the current key path. This command supports signing data using SHA-256 with possible segmentation over
|
||||||
|
* multiple APDUs as well as signing a precomputed 32-bytes hash. The latter option is the actual use case at the
|
||||||
|
* moment, since Ethereum signatures actually require Keccak-256 hashes, which are not supported by any version of
|
||||||
|
* JavaCard (including 3.0.5 which supports SHA-3 but not Keccak-256 which is slightly different). The signature is
|
||||||
|
* generated using the current keys, so if no keys are loaded the command does not work. The result of the execution
|
||||||
|
* is not the plain signature, but a TLV object containing the public key which must be used to verify the signature
|
||||||
|
* and the signature itself. The client should use this to calculate 'v' and format the signature according to the
|
||||||
|
* format required for the transaction to be correctly inserted in the blockchain.
|
||||||
|
*
|
||||||
|
* @param apdu the JCRE-owned APDU object.
|
||||||
|
*/
|
||||||
private void sign(APDU apdu) {
|
private void sign(APDU apdu) {
|
||||||
apdu.setIncomingAndReceive();
|
apdu.setIncomingAndReceive();
|
||||||
|
|
||||||
|
@ -596,6 +778,45 @@ public class WalletApplet extends Applet {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes the SET PINLESS PATH command. Requires an open secure channel and the PIN to be verified. It does not
|
||||||
|
* require keys to be loaded or the current key path to be set at a specific value. The data is formatted in the same
|
||||||
|
* way as for DERIVE KEY. In case the sequence of integers is empty, the PIN-less path is simply unset, so the master
|
||||||
|
* key can never become PIN-less.
|
||||||
|
*
|
||||||
|
* Note that because of the secure channel, the command must still contain the IV and padding even if no actual data
|
||||||
|
* is sent.
|
||||||
|
*
|
||||||
|
* @param apdu the JCRE-owned APDU object.
|
||||||
|
*/
|
||||||
|
private void setPinlessPath(APDU apdu) {
|
||||||
|
apdu.setIncomingAndReceive();
|
||||||
|
|
||||||
|
if (!(secureChannel.isOpen() && pin.isValidated())) {
|
||||||
|
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] apduBuffer = apdu.getBuffer();
|
||||||
|
short len = secureChannel.decryptAPDU(apduBuffer);
|
||||||
|
|
||||||
|
if (((short) (len % 4) != 0) || (len > pinlessPath.length)) {
|
||||||
|
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
|
||||||
|
}
|
||||||
|
|
||||||
|
JCSystem.beginTransaction();
|
||||||
|
pinlessPathLen = len;
|
||||||
|
Util.arrayCopy(apduBuffer, ISO7816.OFFSET_CDATA, pinlessPath, (short) 0, len);
|
||||||
|
JCSystem.commitTransaction();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes the EXPORT KEY command. Requires an open secure channel and the PIN to be verified. The P1 parameter is
|
||||||
|
* an index to which key must be exported from the list of exportable ones. At the moment only the Whisper key with
|
||||||
|
* key path m/1/1 is exportable. The key is exported only if the current key path matches the key path of the key to
|
||||||
|
* be exported.
|
||||||
|
*
|
||||||
|
* @param apdu the JCRE-owned APDU object.
|
||||||
|
*/
|
||||||
private void exportKey(APDU apdu) {
|
private void exportKey(APDU apdu) {
|
||||||
apdu.setIncomingAndReceive();
|
apdu.setIncomingAndReceive();
|
||||||
|
|
||||||
|
@ -639,26 +860,14 @@ public class WalletApplet extends Applet {
|
||||||
apdu.setOutgoingAndSend(ISO7816.OFFSET_CDATA, len);
|
apdu.setOutgoingAndSend(ISO7816.OFFSET_CDATA, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setPinlessPath(APDU apdu) {
|
/**
|
||||||
apdu.setIncomingAndReceive();
|
* Utility method to verify if all the bytes in the buffer between off (included) and off + len (excluded) are digits.
|
||||||
|
*
|
||||||
if (!(secureChannel.isOpen() && pin.isValidated())) {
|
* @param buffer the buffer
|
||||||
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
|
* @param off the offset to begin checking
|
||||||
}
|
* @param len the length of the data
|
||||||
|
* @return whether all checked bytes are digits or not
|
||||||
byte[] apduBuffer = apdu.getBuffer();
|
*/
|
||||||
short len = secureChannel.decryptAPDU(apduBuffer);
|
|
||||||
|
|
||||||
if (((short) (len % 4) != 0) || (len > pinlessPath.length)) {
|
|
||||||
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
|
|
||||||
}
|
|
||||||
|
|
||||||
JCSystem.beginTransaction();
|
|
||||||
pinlessPathLen = len;
|
|
||||||
Util.arrayCopy(apduBuffer, ISO7816.OFFSET_CDATA, pinlessPath, (short) 0, len);
|
|
||||||
JCSystem.commitTransaction();
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean allDigits(byte[] buffer, short off, short len) {
|
private boolean allDigits(byte[] buffer, short off, short len) {
|
||||||
while(len > 0) {
|
while(len > 0) {
|
||||||
len--;
|
len--;
|
||||||
|
@ -673,6 +882,10 @@ public class WalletApplet extends Applet {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns 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 == keyPathLen) && (Util.arrayCompare(keyPath, (short) 0, pinlessPath, (short) 0, keyPathLen) == 0);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue