make MAC actually work

This commit is contained in:
Michele Balistreri 2017-11-21 15:46:21 +03:00
parent 9772f17efa
commit 9cab2f993a
6 changed files with 258 additions and 213 deletions

View File

@ -55,7 +55,7 @@ The seed IV is used by the client as the IV for the next encrypted APDU.
* Data = 256-bit random number * Data = 256-bit random number
* Response Data = 256-bit random number * Response Data = 256-bit random number
* Response SW = 0x9000 on success, 0x6985 if the previous successfully executed APDU was not OPEN SECURE CHANNEL, 0x6982 * Response SW = 0x9000 on success, 0x6985 if the previous successfully executed APDU was not OPEN SECURE CHANNEL, 0x6982
if authentication failed, 0x6A80 if the data is not exactly 64 bytes long if authentication failed or the data is not 256-bit long
This APDU allows both parties to verify that the keys generated in the OPEN SECURE CHANNEL step are matching and thus This APDU allows both parties to verify that the keys generated in the OPEN SECURE CHANNEL step are matching and thus
guarantee authentication of the counterpart. The data sent by both parties is a 256-bit random number The APDU data is guarantee authentication of the counterpart. The data sent by both parties is a 256-bit random number The APDU data is
@ -63,7 +63,6 @@ sent encrypted with the keys generated in the OPEN SECURE CHANNEL step. Each par
APDU. If the MAC can be verified, it means that both parties are using the same keys. Only after this step has been APDU. If the MAC can be verified, it means that both parties are using the same keys. Only after this step has been
executed the secure channel can be considered to be open and other commands can be sent. If the authentication fails executed the secure channel can be considered to be open and other commands can be sent. If the authentication fails
the card must respond with 0x6982. In this case the OPEN SECURE CHANNEL command must be repeated to generate new keys. the card must respond with 0x6982. In this case the OPEN SECURE CHANNEL command must be repeated to generate new keys.
This greatly slows down brute-force attempts.
### PAIR ### PAIR
@ -123,9 +122,15 @@ happen.
### Encrypted APDUs ### Encrypted APDUs
After a successful OPEN SECURE CHANNEL command all communication between card and client is encrypted. Note that only After a successful OPEN SECURE CHANNEL command all communication between card and client is encrypted. Note that only
the data fields of C-APDU and R-APDU are encrypted, which means that CLA, INS, P1, P2 for C-APDU and SW1SW2 for R-APDU the data fields of C-APDU are encrypted, which means that CLA, INS, P1, P2 for C-APDU are plaintext. This means no
are plaintext. This means no sensitive data should be sent in these parameters. Additionally a MAC is calculated for the sensitive data should be sent in these parameters. Additionally a MAC is calculated for the entire APDU, including
entire APDU, including the unencrypted fields. the unencrypted fields.
Because R-APDU can only contain data if their SW is a success or warning status word (0x9000, 0x62XX, 0x63XX), when the
secure channel is open all responses will have SW 0x9000. The actual SW is always appended at the end of the response
data before encryption, which means the client must interpret the last two bytes of the plaintext response as the SW.
An exception to this is SW 0x6982, which indicates that the SecureChannel has been aborted and as such is returned
without any MAC.
To encrypt the data both the card and the client do the following: To encrypt the data both the card and the client do the following:
@ -146,8 +151,8 @@ of transmitted bytes and guarantees protection from replay attacks. For the MAC
MAC generation for C-APDUs is calculated on the concatenation of CLA INS P1 P2 LC 00 00 00 00 00 00 00 00 00 00 00 and MAC generation for C-APDUs is calculated on the concatenation of CLA INS P1 P2 LC 00 00 00 00 00 00 00 00 00 00 00 and
the encrypted data field. The 11-byte long padding does not become part of the data field and does not affect LC the encrypted data field. The 11-byte long padding does not become part of the data field and does not affect LC
MAC generation fo R-APDUs is calculated on the concatenation of Lr SW1 SW2 00 00 00 00 00 00 00 00 00 00 00 00 00 and MAC generation fo R-APDUs is calculated on the concatenation of Lr 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 and
the encrypted data field. The 13-byte long padding does not become part of the response field. Lr is the length of the the encrypted data field. The 15-byte long padding does not become part of the response field. Lr is the length of the
encrypted response data field and is not transmitted. encrypted response data field and is not transmitted.
Because AES in CBC mode requires the data field length in bytes to be a multiple of 16, the maximum effective APDU Because AES in CBC mode requires the data field length in bytes to be a multiple of 16, the maximum effective APDU

View File

@ -25,7 +25,7 @@ public class SecureChannel {
private AESKey scEncKey; private AESKey scEncKey;
private AESKey scMacKey; private AESKey scMacKey;
private Cipher scCipher; private Cipher scCipher;
private Signature scSignature; private Signature scMac;
private KeyPair scKeypair; private KeyPair scKeypair;
private byte[] secret; private byte[] secret;
private byte[] pairingSecret; private byte[] pairingSecret;
@ -45,7 +45,8 @@ public class SecureChannel {
*/ */
public SecureChannel(byte pairingLimit, byte[] aPairingSecret, short off) { public SecureChannel(byte pairingLimit, byte[] aPairingSecret, short off) {
scCipher = Cipher.getInstance(Cipher.ALG_AES_CBC_ISO9797_M2,false); scCipher = Cipher.getInstance(Cipher.ALG_AES_CBC_ISO9797_M2,false);
scSignature = Signature.getInstance(Signature.ALG_AES_MAC_128_NOPAD, false);
scMac = Signature.getInstance(Signature.ALG_AES_MAC_128_NOPAD, false);
scEncKey = (AESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_AES_TRANSIENT_DESELECT, KeyBuilder.LENGTH_AES_256, false); scEncKey = (AESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_AES_TRANSIENT_DESELECT, KeyBuilder.LENGTH_AES_256, false);
scMacKey = (AESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_AES_TRANSIENT_DESELECT, KeyBuilder.LENGTH_AES_256, false); scMacKey = (AESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_AES_TRANSIENT_DESELECT, KeyBuilder.LENGTH_AES_256, false);
@ -62,7 +63,6 @@ public class SecureChannel {
Util.arrayCopyNonAtomic(aPairingSecret, off, pairingSecret, (short) 0, SC_SECRET_LENGTH); Util.arrayCopyNonAtomic(aPairingSecret, off, pairingSecret, (short) 0, SC_SECRET_LENGTH);
} }
/** /**
* Processes the OPEN SECURE CHANNEL command. * Processes the OPEN SECURE CHANNEL command.
* *
@ -99,7 +99,7 @@ public class SecureChannel {
scEncKey.setKey(secret, (short) 0); scEncKey.setKey(secret, (short) 0);
scMacKey.setKey(secret, SC_SECRET_LENGTH); scMacKey.setKey(secret, SC_SECRET_LENGTH);
Util.arrayCopyNonAtomic(apduBuffer, SC_SECRET_LENGTH, secret, (short) 0, SC_BLOCK_SIZE); Util.arrayCopyNonAtomic(apduBuffer, SC_SECRET_LENGTH, secret, (short) 0, SC_BLOCK_SIZE);
Util.arrayFillNonAtomic(secret, SC_BLOCK_SIZE, SC_SECRET_LENGTH, (byte) 0); Util.arrayFillNonAtomic(secret, SC_BLOCK_SIZE, (short) (secret.length - SC_BLOCK_SIZE), (byte) 0);
apdu.setOutgoingAndSend((short) 0, (short) (SC_SECRET_LENGTH + SC_BLOCK_SIZE)); apdu.setOutgoingAndSend((short) 0, (short) (SC_SECRET_LENGTH + SC_BLOCK_SIZE));
} }
@ -109,29 +109,26 @@ public class SecureChannel {
* @param apdu the JCRE-owned APDU object. * @param apdu the JCRE-owned APDU object.
*/ */
public void mutuallyAuthenticate(APDU apdu) { public void mutuallyAuthenticate(APDU apdu) {
if (!scEncKey.isInitialized() || mutuallyAuthenticated) { if (!scEncKey.isInitialized()) {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
} }
byte[] apduBuffer = apdu.getBuffer(); boolean oldMutuallyAuthenticated = mutuallyAuthenticated;
mutuallyAuthenticated = true;
byte[] apduBuffer = apdu.getBuffer();
short len = preprocessAPDU(apduBuffer); short len = preprocessAPDU(apduBuffer);
if (len != (short) (SC_SECRET_LENGTH * 2)) { if (oldMutuallyAuthenticated) {
ISOException.throwIt(ISO7816.SW_WRONG_DATA); ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
} }
Crypto.sha256.doFinal(apduBuffer, ISO7816.OFFSET_CDATA, SC_SECRET_LENGTH, apduBuffer, ISO7816.OFFSET_CDATA); if (len != SC_SECRET_LENGTH) {
if (Util.arrayCompare(apduBuffer, ISO7816.OFFSET_CDATA, apduBuffer, (short) (ISO7816.OFFSET_CDATA + SC_SECRET_LENGTH), SC_SECRET_LENGTH) != 0) {
reset(); reset();
ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED); ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
} }
mutuallyAuthenticated = true;
Crypto.random.generateData(apduBuffer, SC_OUT_OFFSET, SC_SECRET_LENGTH); Crypto.random.generateData(apduBuffer, SC_OUT_OFFSET, SC_SECRET_LENGTH);
Crypto.sha256.doFinal(apduBuffer, SC_OUT_OFFSET, SC_SECRET_LENGTH, apduBuffer, (short) (SC_OUT_OFFSET + SC_SECRET_LENGTH));
respond(apdu, len, ISO7816.SW_NO_ERROR); respond(apdu, len, ISO7816.SW_NO_ERROR);
} }
@ -249,11 +246,7 @@ public class SecureChannel {
short apduLen = (short)((short) apduBuffer[ISO7816.OFFSET_LC] & 0xff); short apduLen = (short)((short) apduBuffer[ISO7816.OFFSET_LC] & 0xff);
scSignature.init(scMacKey, Signature.MODE_VERIFY); if (!verifyAESMAC(apduBuffer, apduLen)) {
scSignature.update(apduBuffer, (short) 0, ISO7816.OFFSET_CDATA);
scSignature.update(secret, SC_BLOCK_SIZE, (short) (SC_BLOCK_SIZE - ISO7816.OFFSET_CDATA));
if (!scSignature.verify(apduBuffer, (short) (ISO7816.OFFSET_CDATA + SC_BLOCK_SIZE), (short) (apduLen - SC_BLOCK_SIZE), apduBuffer, ISO7816.OFFSET_CDATA, SC_BLOCK_SIZE)) {
reset(); reset();
ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED); ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
} }
@ -267,10 +260,18 @@ public class SecureChannel {
return len; return len;
} }
public boolean verifyAESMAC(byte[] apduBuffer, short apduLen) {
scMac.init(scMacKey, Signature.MODE_VERIFY);
scMac.update(apduBuffer, (short) 0, ISO7816.OFFSET_CDATA);
scMac.update(secret, SC_BLOCK_SIZE, (short) (SC_BLOCK_SIZE - ISO7816.OFFSET_CDATA));
return scMac.verify(apduBuffer, (short) (ISO7816.OFFSET_CDATA + SC_BLOCK_SIZE), (short) (apduLen - SC_BLOCK_SIZE), apduBuffer, ISO7816.OFFSET_CDATA, SC_BLOCK_SIZE);
}
/** /**
* Sends the response to the command. This method always throws an ISOException with the given SW, so nothing can be * Sends the response to the command. This the given SW is appended to the data automatically. The response data must
* called after its execution. The response data must be placed starting at the SecureChannel.SC_OUT_OFFSET offset, to * be placed starting at the SecureChannel.SC_OUT_OFFSET offset, to leave place for the SecureChannel-specific data at
* leave place for the SecureChannel-specific data at the beginning of the APDU. * the beginning of the APDU.
* *
* @param apdu the APDU object * @param apdu the APDU object
* @param len the length of the plaintext * @param len the length of the plaintext
@ -278,21 +279,27 @@ public class SecureChannel {
public void respond(APDU apdu, short len, short sw) { public void respond(APDU apdu, short len, short sw) {
byte[] apduBuffer = apdu.getBuffer(); byte[] apduBuffer = apdu.getBuffer();
Util.setShort(apduBuffer, (short) 0, sw); Util.setShort(apduBuffer, (short) (SC_OUT_OFFSET + len), sw);
len += 2;
scCipher.init(scEncKey, Cipher.MODE_ENCRYPT, secret, (short) 0, SC_BLOCK_SIZE); scCipher.init(scEncKey, Cipher.MODE_ENCRYPT, secret, (short) 0, SC_BLOCK_SIZE);
len = scCipher.doFinal(apduBuffer, SC_OUT_OFFSET, len, apduBuffer, (short)(ISO7816.OFFSET_CDATA + SC_BLOCK_SIZE)); len = scCipher.doFinal(apduBuffer, SC_OUT_OFFSET, len, apduBuffer, (short)(ISO7816.OFFSET_CDATA + SC_BLOCK_SIZE));
scSignature.init(scMacKey, Signature.MODE_SIGN); apduBuffer[0] = (byte) (len + SC_BLOCK_SIZE);
scSignature.update(apduBuffer, (short) 0, (short) 2);
scSignature.update(secret, SC_BLOCK_SIZE, (short)(SC_BLOCK_SIZE - 2)); computeAESMAC(len, apduBuffer);
scSignature.sign(apduBuffer, SC_OUT_OFFSET, len, apduBuffer, ISO7816.OFFSET_CDATA);
Util.arrayCopyNonAtomic(apduBuffer, ISO7816.OFFSET_CDATA, secret, (short) 0, SC_BLOCK_SIZE); Util.arrayCopyNonAtomic(apduBuffer, ISO7816.OFFSET_CDATA, secret, (short) 0, SC_BLOCK_SIZE);
len += SC_BLOCK_SIZE; len += SC_BLOCK_SIZE;
apdu.setOutgoingAndSend(ISO7816.OFFSET_CDATA, len); apdu.setOutgoingAndSend(ISO7816.OFFSET_CDATA, len);
ISOException.throwIt(sw); }
public void computeAESMAC(short len, byte[] apduBuffer) {
scMac.init(scMacKey, Signature.MODE_SIGN);
scMac.update(apduBuffer, (short) 0, (short) 1);
scMac.update(secret, SC_BLOCK_SIZE, (short)(SC_BLOCK_SIZE - 1));
scMac.sign(apduBuffer, (short)(ISO7816.OFFSET_CDATA + SC_BLOCK_SIZE), len, apduBuffer, ISO7816.OFFSET_CDATA);
} }
/** /**

View File

@ -231,18 +231,22 @@ public class WalletApplet extends Applet {
break; break;
} }
} catch(ISOException sw) { } catch(ISOException sw) {
if (secureChannel.isOpen() && apdu.getCurrentState() != APDU.STATE_FULL_OUTGOING) { if (shouldRespond(apdu) && (sw.getReason() != ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED)) {
secureChannel.respond(apdu, (short) 0, sw.getReason()); secureChannel.respond(apdu, (short) 0, sw.getReason());
} else { } else {
throw sw; throw sw;
} }
} }
if (secureChannel.isOpen()) { if (shouldRespond(apdu)) {
secureChannel.respond(apdu, (short) 0, ISO7816.SW_NO_ERROR); secureChannel.respond(apdu, (short) 0, ISO7816.SW_NO_ERROR);
} }
} }
private boolean shouldRespond(APDU apdu) {
return secureChannel.isOpen() && (apdu.getCurrentState() != APDU.STATE_FULL_OUTGOING);
}
/** /**
* Checks that the PIN is validated and if it is call the unpair method of the secure channel. If the PIN is not * Checks that the PIN is validated and if it is call the unpair method of the secure channel. If the PIN is not
* validated the 0x6985 exception is thrown. * validated the 0x6985 exception is thrown.
@ -292,11 +296,8 @@ public class WalletApplet extends Applet {
* @param apdu the JCRE-owned APDU object. * @param apdu the JCRE-owned APDU object.
*/ */
private void getStatus(APDU apdu) { private void getStatus(APDU apdu) {
if (!secureChannel.isOpen()) {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}
byte[] apduBuffer = apdu.getBuffer(); byte[] apduBuffer = apdu.getBuffer();
secureChannel.preprocessAPDU(apduBuffer);
short len; short len;
@ -367,10 +368,6 @@ public class WalletApplet extends Applet {
* @param apdu the JCRE-owned APDU object. * @param apdu the JCRE-owned APDU object.
*/ */
private void verifyPIN(APDU apdu) { private void verifyPIN(APDU apdu) {
if (!secureChannel.isOpen()) {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}
byte[] apduBuffer = apdu.getBuffer(); byte[] apduBuffer = apdu.getBuffer();
byte len = (byte) secureChannel.preprocessAPDU(apduBuffer); byte len = (byte) secureChannel.preprocessAPDU(apduBuffer);
@ -387,13 +384,13 @@ public class WalletApplet extends Applet {
* @param apdu the JCRE-owned APDU object. * @param apdu the JCRE-owned APDU object.
*/ */
private void changePIN(APDU apdu) { private void changePIN(APDU apdu) {
if (!(secureChannel.isOpen() && pin.isValidated())) {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}
byte[] apduBuffer = apdu.getBuffer(); byte[] apduBuffer = apdu.getBuffer();
byte len = (byte) secureChannel.preprocessAPDU(apduBuffer); byte len = (byte) secureChannel.preprocessAPDU(apduBuffer);
if (!pin.isValidated()) {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}
if (!(len == PIN_LENGTH && allDigits(apduBuffer, ISO7816.OFFSET_CDATA, len))) { if (!(len == PIN_LENGTH && allDigits(apduBuffer, ISO7816.OFFSET_CDATA, len))) {
ISOException.throwIt(ISO7816.SW_WRONG_DATA); ISOException.throwIt(ISO7816.SW_WRONG_DATA);
} }
@ -411,13 +408,13 @@ public class WalletApplet extends Applet {
* @param apdu the JCRE-owned APDU object. * @param apdu the JCRE-owned APDU object.
*/ */
private void unblockPIN(APDU apdu) { private void unblockPIN(APDU apdu) {
if (!(secureChannel.isOpen() && pin.getTriesRemaining() == 0)) {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}
byte[] apduBuffer = apdu.getBuffer(); byte[] apduBuffer = apdu.getBuffer();
byte len = (byte) secureChannel.preprocessAPDU(apduBuffer); byte len = (byte) secureChannel.preprocessAPDU(apduBuffer);
if (pin.getTriesRemaining() != 0) {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}
if (!(len == (PUK_LENGTH + PIN_LENGTH) && allDigits(apduBuffer, ISO7816.OFFSET_CDATA, len))) { if (!(len == (PUK_LENGTH + PIN_LENGTH) && allDigits(apduBuffer, ISO7816.OFFSET_CDATA, len))) {
ISOException.throwIt(ISO7816.SW_WRONG_DATA); ISOException.throwIt(ISO7816.SW_WRONG_DATA);
} }
@ -442,13 +439,13 @@ public class WalletApplet extends Applet {
* @param apdu the JCRE-owned APDU object. * @param apdu the JCRE-owned APDU object.
*/ */
private void loadKey(APDU apdu) { private void loadKey(APDU apdu) {
if (!(secureChannel.isOpen() && pin.isValidated())) { byte[] apduBuffer = apdu.getBuffer();
secureChannel.preprocessAPDU(apduBuffer);
if (!pin.isValidated()) {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
} }
byte[] apduBuffer = apdu.getBuffer();
secureChannel.preprocessAPDU(apduBuffer);
boolean newExtended = false; boolean newExtended = false;
switch (apduBuffer[ISO7816.OFFSET_P1]) { switch (apduBuffer[ISO7816.OFFSET_P1]) {
@ -586,14 +583,13 @@ public class WalletApplet extends Applet {
* @param apdu the JCRE-owned APDU object. * @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)) { byte[] apduBuffer = apdu.getBuffer();
short len = secureChannel.preprocessAPDU(apduBuffer);
if (!((pin.isValidated() || (pinlessPathLen > 0)) && isExtended)) {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
} }
byte[] apduBuffer = apdu.getBuffer();
short len = secureChannel.preprocessAPDU(apduBuffer);
boolean assistedDerivation = (apduBuffer[ISO7816.OFFSET_P1] & DERIVE_P1_ASSISTED_MASK) == DERIVE_P1_ASSISTED_MASK; boolean assistedDerivation = (apduBuffer[ISO7816.OFFSET_P1] & DERIVE_P1_ASSISTED_MASK) == DERIVE_P1_ASSISTED_MASK;
boolean isPublicKey = apduBuffer[ISO7816.OFFSET_P2] == DERIVE_P2_PUBLIC_KEY; boolean isPublicKey = apduBuffer[ISO7816.OFFSET_P2] == DERIVE_P2_PUBLIC_KEY;
boolean isReset = (apduBuffer[ISO7816.OFFSET_P1] & DERIVE_P1_APPEND_MASK) != DERIVE_P1_APPEND_MASK; boolean isReset = (apduBuffer[ISO7816.OFFSET_P1] & DERIVE_P1_APPEND_MASK) != DERIVE_P1_APPEND_MASK;
@ -704,11 +700,9 @@ public class WalletApplet extends Applet {
* @param apdu the JCRE-owned APDU object. * @param apdu the JCRE-owned APDU object.
*/ */
private void generateMnemonic(APDU apdu) { private void generateMnemonic(APDU apdu) {
if (!secureChannel.isOpen()) {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}
byte[] apduBuffer = apdu.getBuffer(); byte[] apduBuffer = apdu.getBuffer();
secureChannel.preprocessAPDU(apduBuffer);
short csLen = apduBuffer[ISO7816.OFFSET_P1]; short csLen = apduBuffer[ISO7816.OFFSET_P1];
if (csLen < GENERATE_MNEMONIC_P1_CS_MIN || csLen > GENERATE_MNEMONIC_P1_CS_MAX) { if (csLen < GENERATE_MNEMONIC_P1_CS_MIN || csLen > GENERATE_MNEMONIC_P1_CS_MAX) {
@ -785,12 +779,13 @@ public class WalletApplet extends Applet {
* @param apdu the JCRE-owned APDU object. * @param apdu the JCRE-owned APDU object.
*/ */
private void sign(APDU apdu) { private void sign(APDU apdu) {
if (!(secureChannel.isOpen() && (pin.isValidated() || isPinless()) && privateKey.isInitialized() && !expectPublicKey)) { byte[] apduBuffer = apdu.getBuffer();
short len = secureChannel.preprocessAPDU(apduBuffer);
if (!((pin.isValidated() || isPinless()) && privateKey.isInitialized() && !expectPublicKey)) {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
} }
byte[] apduBuffer = apdu.getBuffer();
if ((apduBuffer[ISO7816.OFFSET_P2] & SIGN_P2_FIRST_BLOCK_MASK) == SIGN_P2_FIRST_BLOCK_MASK) { if ((apduBuffer[ISO7816.OFFSET_P2] & SIGN_P2_FIRST_BLOCK_MASK) == SIGN_P2_FIRST_BLOCK_MASK) {
signInProgress = true; signInProgress = true;
signature.init(privateKey, Signature.MODE_SIGN); signature.init(privateKey, Signature.MODE_SIGN);
@ -798,8 +793,6 @@ public class WalletApplet extends Applet {
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2); ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
} }
short len = secureChannel.preprocessAPDU(apduBuffer);
if ((apduBuffer[ISO7816.OFFSET_P2] & SIGN_P2_LAST_BLOCK_MASK) == SIGN_P2_LAST_BLOCK_MASK) { if ((apduBuffer[ISO7816.OFFSET_P2] & SIGN_P2_LAST_BLOCK_MASK) == SIGN_P2_LAST_BLOCK_MASK) {
signInProgress = false; signInProgress = false;
@ -838,13 +831,13 @@ public class WalletApplet extends Applet {
* @param apdu the JCRE-owned APDU object. * @param apdu the JCRE-owned APDU object.
*/ */
private void setPinlessPath(APDU apdu) { private void setPinlessPath(APDU apdu) {
if (!(secureChannel.isOpen() && pin.isValidated())) {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}
byte[] apduBuffer = apdu.getBuffer(); byte[] apduBuffer = apdu.getBuffer();
short len = secureChannel.preprocessAPDU(apduBuffer); short len = secureChannel.preprocessAPDU(apduBuffer);
if (!pin.isValidated()) {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}
if (((short) (len % 4) != 0) || (len > pinlessPath.length)) { if (((short) (len % 4) != 0) || (len > pinlessPath.length)) {
ISOException.throwIt(ISO7816.SW_WRONG_DATA); ISOException.throwIt(ISO7816.SW_WRONG_DATA);
} }
@ -864,11 +857,13 @@ public class WalletApplet extends Applet {
* @param apdu the JCRE-owned APDU object. * @param apdu the JCRE-owned APDU object.
*/ */
private void exportKey(APDU apdu) { private void exportKey(APDU apdu) {
if (!(secureChannel.isOpen() && pin.isValidated())) { byte[] apduBuffer = apdu.getBuffer();
secureChannel.preprocessAPDU(apduBuffer);
if (!pin.isValidated()) {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
} }
byte[] apduBuffer = apdu.getBuffer();
byte[] toExport; byte[] toExport;
switch (apduBuffer[ISO7816.OFFSET_P1]) { switch (apduBuffer[ISO7816.OFFSET_P1]) {

View File

@ -1,5 +1,8 @@
package im.status.wallet; package im.status.wallet;
import org.bouncycastle.crypto.engines.AESEngine;
import org.bouncycastle.crypto.macs.CBCBlockCipherMac;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.jce.ECNamedCurveTable; import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.interfaces.ECPublicKey; import org.bouncycastle.jce.interfaces.ECPublicKey;
import org.bouncycastle.jce.spec.ECParameterSpec; import org.bouncycastle.jce.spec.ECParameterSpec;
@ -25,10 +28,14 @@ public class SecureChannelSession {
private byte[] secret; private byte[] secret;
private byte[] publicKey; private byte[] publicKey;
private byte[] pairingKey; private byte[] pairingKey;
private byte[] iv;
private byte pairingIndex; private byte pairingIndex;
private Cipher sessionCipher; private Cipher sessionCipher;
private SecretKeySpec sessionKey; private CBCBlockCipherMac sessionMac;
private SecretKeySpec sessionEncKey;
private KeyParameter sessionMacKey;
private SecureRandom random; private SecureRandom random;
private boolean open;
/** /**
* Constructs a SecureChannel session on the client. The client should generate a fresh key pair for each session. * Constructs a SecureChannel session on the client. The client should generate a fresh key pair for each session.
@ -55,6 +62,7 @@ public class SecureChannelSession {
keyAgreement.doPhase(cardKey, true); keyAgreement.doPhase(cardKey, true);
secret = keyAgreement.generateSecret(); secret = keyAgreement.generateSecret();
open = false;
} catch(Exception e) { } catch(Exception e) {
throw new RuntimeException("Is BouncyCastle in the classpath?", e); throw new RuntimeException("Is BouncyCastle in the classpath?", e);
} }
@ -78,10 +86,7 @@ public class SecureChannelSession {
/** /**
* Establishes a Secure Channel with the card. The command parameters are the public key generated in the first step. * Establishes a Secure Channel with the card. The command parameters are the public key generated in the first step.
* The card returns a secret value which must be appended to the secret previously generated through the EC-DH * Follows the specifications from the SECURE_CHANNEL.md document.
* algorithm. This entire value must be hashed using SHA-256. The hash will be used as the key for an AES CBC cipher
* using ISO9797-1 Method 2 padding. From this point all further APDU must be sent encrypted and all responses from
* the card must be decrypted using this secure channel.
* *
* @param apduChannel the apdu channel * @param apduChannel the apdu channel
* @return the card response * @return the card response
@ -108,17 +113,24 @@ public class SecureChannelSession {
} }
/** /**
* Processes the response from OPEN SECURE CHANNEL. This initialize the session key and cipher internally. * Processes the response from OPEN SECURE CHANNEL. This initialize the session keys, Cipher and MAC internally.
* *
* @param response the card response * @param response the card response
*/ */
public void processOpenSecureChannelResponse(ResponseAPDU response) { public void processOpenSecureChannelResponse(ResponseAPDU response) {
try { try {
MessageDigest md = MessageDigest.getInstance("SHA256", "BC"); MessageDigest md = MessageDigest.getInstance("SHA512", "BC");
md.update(secret); md.update(secret);
md.update(pairingKey); md.update(pairingKey);
sessionKey = new SecretKeySpec(md.digest(response.getData()), "AES"); byte[] data = response.getData();
byte[] keyData = md.digest(Arrays.copyOf(data, SecureChannel.SC_SECRET_LENGTH));
iv = Arrays.copyOfRange(data, SecureChannel.SC_SECRET_LENGTH, data.length);
sessionEncKey = new SecretKeySpec(Arrays.copyOf(keyData, SecureChannel.SC_SECRET_LENGTH), "AES");
sessionMacKey = new KeyParameter(keyData, SecureChannel.SC_SECRET_LENGTH, SecureChannel.SC_SECRET_LENGTH);
sessionCipher = Cipher.getInstance("AES/CBC/ISO7816-4Padding", "BC"); sessionCipher = Cipher.getInstance("AES/CBC/ISO7816-4Padding", "BC");
sessionMac = new CBCBlockCipherMac(new AESEngine(), 128, null);
open = true;
} catch(Exception e) { } catch(Exception e) {
throw new RuntimeException("Is BouncyCastle in the classpath?", e); throw new RuntimeException("Is BouncyCastle in the classpath?", e);
} }
@ -131,20 +143,7 @@ public class SecureChannelSession {
* @return true if response is correct, false otherwise * @return true if response is correct, false otherwise
*/ */
public boolean verifyMutuallyAuthenticateResponse(ResponseAPDU response) { public boolean verifyMutuallyAuthenticateResponse(ResponseAPDU response) {
MessageDigest md; return response.getNr() == SecureChannel.SC_SECRET_LENGTH;
try {
md = MessageDigest.getInstance("SHA256", "BC");
} catch (Exception e) {
throw new RuntimeException("Is BouncyCastle in the classpath?", e);
}
byte[] data = decryptAPDU(response.getData());
md.update(data, 0, SecureChannel.SC_SECRET_LENGTH);
byte[] digest = md.digest();
data = Arrays.copyOfRange(data, SecureChannel.SC_SECRET_LENGTH, data.length);
return Arrays.equals(data, digest);
} }
/** /**
@ -204,7 +203,7 @@ public class SecureChannelSession {
* @throws CardException communication error * @throws CardException communication error
*/ */
public void autoUnpair(CardChannel apduChannel) throws CardException { public void autoUnpair(CardChannel apduChannel) throws CardException {
ResponseAPDU resp = unpair(apduChannel, pairingIndex, new byte[] { pairingIndex }); ResponseAPDU resp = unpair(apduChannel, pairingIndex);
if (resp.getSW() != 0x9000) { if (resp.getSW() != 0x9000) {
throw new CardException("Unpairing failed"); throw new CardException("Unpairing failed");
@ -221,6 +220,7 @@ public class SecureChannelSession {
* @throws CardException communication error * @throws CardException communication error
*/ */
public ResponseAPDU openSecureChannel(CardChannel apduChannel, byte index, byte[] data) throws CardException { public ResponseAPDU openSecureChannel(CardChannel apduChannel, byte index, byte[] data) throws CardException {
open = false;
CommandAPDU openSecureChannel = new CommandAPDU(0x80, SecureChannel.INS_OPEN_SECURE_CHANNEL, index, 0, data); CommandAPDU openSecureChannel = new CommandAPDU(0x80, SecureChannel.INS_OPEN_SECURE_CHANNEL, index, 0, data);
return apduChannel.transmit(openSecureChannel); return apduChannel.transmit(openSecureChannel);
} }
@ -233,19 +233,8 @@ public class SecureChannelSession {
* @throws CardException communication error * @throws CardException communication error
*/ */
public ResponseAPDU mutuallyAuthenticate(CardChannel apduChannel) throws CardException { public ResponseAPDU mutuallyAuthenticate(CardChannel apduChannel) throws CardException {
MessageDigest md;
try {
md = MessageDigest.getInstance("SHA256", "BC");
} catch (Exception e) {
throw new RuntimeException("Is BouncyCastle in the classpath?", e);
}
byte[] data = new byte[SecureChannel.SC_SECRET_LENGTH]; byte[] data = new byte[SecureChannel.SC_SECRET_LENGTH];
random.nextBytes(data); random.nextBytes(data);
byte[] digest = md.digest(data);
data = Arrays.copyOf(data, SecureChannel.SC_SECRET_LENGTH * 2);
System.arraycopy(digest, 0, data, SecureChannel.SC_SECRET_LENGTH, SecureChannel.SC_SECRET_LENGTH);
return mutuallyAuthenticate(apduChannel, data); return mutuallyAuthenticate(apduChannel, data);
} }
@ -259,8 +248,8 @@ public class SecureChannelSession {
* @throws CardException communication error * @throws CardException communication error
*/ */
public ResponseAPDU mutuallyAuthenticate(CardChannel apduChannel, byte[] data) throws CardException { public ResponseAPDU mutuallyAuthenticate(CardChannel apduChannel, byte[] data) throws CardException {
CommandAPDU mutuallyAuthenticate = new CommandAPDU(0x80, SecureChannel.INS_MUTUALLY_AUTHENTICATE, 0, 0, encryptAPDU(data)); CommandAPDU mutuallyAuthenticate = protectedCommand(0x80, SecureChannel.INS_MUTUALLY_AUTHENTICATE, 0, 0, data);
return apduChannel.transmit(mutuallyAuthenticate); return transmit(apduChannel, mutuallyAuthenticate);
} }
/** /**
@ -274,7 +263,7 @@ public class SecureChannelSession {
*/ */
public ResponseAPDU pair(CardChannel apduChannel, byte p1, byte[] data) throws CardException { public ResponseAPDU pair(CardChannel apduChannel, byte p1, byte[] data) throws CardException {
CommandAPDU openSecureChannel = new CommandAPDU(0x80, SecureChannel.INS_PAIR, p1, 0, data); CommandAPDU openSecureChannel = new CommandAPDU(0x80, SecureChannel.INS_PAIR, p1, 0, data);
return apduChannel.transmit(openSecureChannel); return transmit(apduChannel, openSecureChannel);
} }
/** /**
@ -282,13 +271,12 @@ public class SecureChannelSession {
* *
* @param apduChannel the apdu channel * @param apduChannel the apdu channel
* @param p1 the P1 parameter * @param p1 the P1 parameter
* @param data the data
* @return the raw card response * @return the raw card response
* @throws CardException communication error * @throws CardException communication error
*/ */
public ResponseAPDU unpair(CardChannel apduChannel, byte p1, byte[] data) throws CardException { public ResponseAPDU unpair(CardChannel apduChannel, byte p1) throws CardException {
CommandAPDU openSecureChannel = new CommandAPDU(0x80, SecureChannel.INS_UNPAIR, p1, 0, encryptAPDU(data)); CommandAPDU openSecureChannel = protectedCommand(0x80, SecureChannel.INS_UNPAIR, p1, 0, new byte[0]);
return apduChannel.transmit(openSecureChannel); return transmit(apduChannel, openSecureChannel);
} }
/** /**
@ -299,27 +287,14 @@ public class SecureChannelSession {
* @param data the plaintext data * @param data the plaintext data
* @return the encrypted data * @return the encrypted data
*/ */
public byte[] encryptAPDU(byte[] data) { private byte[] encryptAPDU(byte[] data) {
assert data.length <= PAYLOAD_MAX_SIZE; assert data.length <= PAYLOAD_MAX_SIZE;
if (sessionKey == null) {
return data;
}
try { try {
int ivSize = sessionCipher.getBlockSize();
byte[] iv = new byte[ivSize];
random.nextBytes(iv);
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv); IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
sessionCipher.init(Cipher.ENCRYPT_MODE, sessionKey, ivParameterSpec); sessionCipher.init(Cipher.ENCRYPT_MODE, sessionEncKey, ivParameterSpec);
byte[] encrypted = sessionCipher.doFinal(data); return sessionCipher.doFinal(data);
byte[] result = new byte[ivSize + encrypted.length];
System.arraycopy(iv, 0, result, 0, ivSize);
System.arraycopy(encrypted, 0, result, ivSize, encrypted.length);
return result;
} catch(Exception e) { } catch(Exception e) {
throw new RuntimeException("Is BouncyCastle in the classpath?", e); throw new RuntimeException("Is BouncyCastle in the classpath?", e);
} }
@ -332,18 +307,75 @@ public class SecureChannelSession {
* @param data the ciphetext * @param data the ciphetext
* @return the plaintext * @return the plaintext
*/ */
public byte[] decryptAPDU(byte[] data) { private byte[] decryptAPDU(byte[] data) {
if (sessionKey == null) { try {
return data; IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
sessionCipher.init(Cipher.DECRYPT_MODE, sessionEncKey, ivParameterSpec);
return sessionCipher.doFinal(data);
} catch(Exception e) {
throw new RuntimeException("Is BouncyCastle in the classpath?", e);
}
} }
public CommandAPDU protectedCommand(int cla, int ins, int p1, int p2, byte[] data) {
byte[] finalData;
if (open) {
data = encryptAPDU(data);
byte[] meta = new byte[]{(byte) cla, (byte) ins, (byte) p1, (byte) p2, (byte) (data.length + SecureChannel.SC_BLOCK_SIZE), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
updateIV(meta, data);
finalData = Arrays.copyOf(iv, iv.length + data.length);
System.arraycopy(data, 0, finalData, iv.length, data.length);
} else {
finalData = data;
}
return new CommandAPDU(cla, ins, p1, p2, finalData);
}
public ResponseAPDU transmit(CardChannel apduChannel, CommandAPDU apdu) throws CardException {
ResponseAPDU resp = apduChannel.transmit(apdu);
if (resp.getSW() == 0x6982) {
open = false;
}
if (open) {
byte[] data = resp.getData();
byte[] meta = new byte[]{(byte) data.length, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
byte[] mac = Arrays.copyOf(data, iv.length);
data = Arrays.copyOfRange(data, iv.length, data.length);
byte[] plainData = decryptAPDU(data);
updateIV(meta, data);
if (!Arrays.equals(iv, mac)) {
throw new CardException("Invalid MAC");
}
return new ResponseAPDU(plainData);
} else {
return resp;
}
}
public void reset() {
open = false;
}
void setOpen() {
open = true;
}
private void updateIV(byte[] meta, byte[] data) {
try { try {
int ivSize = sessionCipher.getBlockSize(); sessionMac.init(sessionMacKey);
IvParameterSpec ivParameterSpec = new IvParameterSpec(data, 0, ivSize); sessionMac.update(meta, 0, meta.length);
sessionCipher.init(Cipher.DECRYPT_MODE, sessionKey, ivParameterSpec); sessionMac.update(data, 0, data.length);
return sessionCipher.doFinal(data, ivSize, data.length - ivSize); sessionMac.doFinal(iv, 0);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("Is BouncyCastle in the classpath?", e); throw new RuntimeException("Is BouncyCastle in the classpath?", e);
} }
} }

View File

@ -41,6 +41,10 @@ public class WalletAppletCommandSet {
* @throws CardException communication error * @throws CardException communication error
*/ */
public ResponseAPDU select() throws CardException { public ResponseAPDU select() throws CardException {
if (secureChannel != null) {
secureChannel.reset();
}
CommandAPDU selectApplet = new CommandAPDU(ISO7816.CLA_ISO7816, ISO7816.INS_SELECT, 4, 0, APPLET_AID_BYTES); CommandAPDU selectApplet = new CommandAPDU(ISO7816.CLA_ISO7816, ISO7816.INS_SELECT, 4, 0, APPLET_AID_BYTES);
return apduChannel.transmit(selectApplet); return apduChannel.transmit(selectApplet);
} }
@ -104,8 +108,8 @@ public class WalletAppletCommandSet {
/** /**
* Sends a UNPAIR APDU. Calls the corresponding method of the SecureChannel class. * Sends a UNPAIR APDU. Calls the corresponding method of the SecureChannel class.
*/ */
public ResponseAPDU unpair(byte p1, byte[] data) throws CardException { public ResponseAPDU unpair(byte p1) throws CardException {
return secureChannel.unpair(apduChannel, p1, data); return secureChannel.unpair(apduChannel, p1);
} }
/** /**
@ -117,8 +121,8 @@ public class WalletAppletCommandSet {
* @throws CardException communication error * @throws CardException communication error
*/ */
public ResponseAPDU getStatus(byte info) throws CardException { public ResponseAPDU getStatus(byte info) throws CardException {
CommandAPDU getStatus = new CommandAPDU(0x80, WalletApplet.INS_GET_STATUS, info, 0, 256); CommandAPDU getStatus = secureChannel.protectedCommand(0x80, WalletApplet.INS_GET_STATUS, info, 0, new byte[0]);
return apduChannel.transmit(getStatus); return secureChannel.transmit(apduChannel, getStatus);
} }
/** /**
@ -130,7 +134,7 @@ public class WalletAppletCommandSet {
*/ */
public boolean getPublicKeyDerivationSupport() throws CardException { public boolean getPublicKeyDerivationSupport() throws CardException {
ResponseAPDU resp = getStatus(WalletApplet.GET_STATUS_P1_APPLICATION); ResponseAPDU resp = getStatus(WalletApplet.GET_STATUS_P1_APPLICATION);
byte[] data = secureChannel.decryptAPDU(resp.getData()); byte[] data = resp.getData();
return data[data.length - 1] == 1; return data[data.length - 1] == 1;
} }
@ -143,8 +147,8 @@ public class WalletAppletCommandSet {
* @throws CardException communication error * @throws CardException communication error
*/ */
public ResponseAPDU verifyPIN(String pin) throws CardException { public ResponseAPDU verifyPIN(String pin) throws CardException {
CommandAPDU verifyPIN = new CommandAPDU(0x80, WalletApplet.INS_VERIFY_PIN, 0, 0, secureChannel.encryptAPDU(pin.getBytes())); CommandAPDU verifyPIN = secureChannel.protectedCommand(0x80, WalletApplet.INS_VERIFY_PIN, 0, 0, pin.getBytes());
return apduChannel.transmit(verifyPIN); return secureChannel.transmit(apduChannel, verifyPIN);
} }
/** /**
@ -156,8 +160,8 @@ public class WalletAppletCommandSet {
* @throws CardException communication error * @throws CardException communication error
*/ */
public ResponseAPDU changePIN(String pin) throws CardException { public ResponseAPDU changePIN(String pin) throws CardException {
CommandAPDU changePIN = new CommandAPDU(0x80, WalletApplet.INS_CHANGE_PIN, 0, 0, secureChannel.encryptAPDU(pin.getBytes())); CommandAPDU changePIN = secureChannel.protectedCommand(0x80, WalletApplet.INS_CHANGE_PIN, 0, 0, pin.getBytes());
return apduChannel.transmit(changePIN); return secureChannel.transmit(apduChannel, changePIN);
} }
/** /**
@ -168,8 +172,8 @@ public class WalletAppletCommandSet {
* @throws CardException communication error * @throws CardException communication error
*/ */
public ResponseAPDU unblockPIN(String puk, String newPin) throws CardException { public ResponseAPDU unblockPIN(String puk, String newPin) throws CardException {
CommandAPDU unblockPIN = new CommandAPDU(0x80, WalletApplet.INS_UNBLOCK_PIN, 0, 0, secureChannel.encryptAPDU((puk + newPin).getBytes())); CommandAPDU unblockPIN = secureChannel.protectedCommand(0x80, WalletApplet.INS_UNBLOCK_PIN, 0, 0, (puk + newPin).getBytes());
return apduChannel.transmit(unblockPIN); return secureChannel.transmit(apduChannel, unblockPIN);
} }
/** /**
@ -335,8 +339,8 @@ public class WalletAppletCommandSet {
* @throws CardException communication error * @throws CardException communication error
*/ */
public ResponseAPDU loadKey(byte[] data, byte keyType) throws CardException { public ResponseAPDU loadKey(byte[] data, byte keyType) throws CardException {
CommandAPDU loadKey = new CommandAPDU(0x80, WalletApplet.INS_LOAD_KEY, keyType, 0, secureChannel.encryptAPDU(data)); CommandAPDU loadKey = secureChannel.protectedCommand(0x80, WalletApplet.INS_LOAD_KEY, keyType, 0, data);
return apduChannel.transmit(loadKey); return secureChannel.transmit(apduChannel, loadKey);
} }
/** /**
@ -347,8 +351,8 @@ public class WalletAppletCommandSet {
* @throws CardException communication error * @throws CardException communication error
*/ */
public ResponseAPDU generateMnemonic(int cs) throws CardException { public ResponseAPDU generateMnemonic(int cs) throws CardException {
CommandAPDU generateMnemonic = new CommandAPDU(0x80, WalletApplet.INS_GENERATE_MNEMONIC, cs, 0, 256); CommandAPDU generateMnemonic = secureChannel.protectedCommand(0x80, WalletApplet.INS_GENERATE_MNEMONIC, cs, 0, new byte[0]);
return apduChannel.transmit(generateMnemonic); return secureChannel.transmit(apduChannel, generateMnemonic);
} }
/** /**
@ -366,8 +370,8 @@ public class WalletAppletCommandSet {
*/ */
public ResponseAPDU sign(byte[] data, byte dataType, boolean isFirst, boolean isLast) throws CardException { public ResponseAPDU sign(byte[] data, byte dataType, boolean isFirst, boolean isLast) throws CardException {
byte p2 = (byte) ((isFirst ? 0x01 : 0x00) | (isLast ? 0x80 : 0x00)); byte p2 = (byte) ((isFirst ? 0x01 : 0x00) | (isLast ? 0x80 : 0x00));
CommandAPDU sign = new CommandAPDU(0x80, WalletApplet.INS_SIGN, dataType, p2, secureChannel.encryptAPDU(data)); CommandAPDU sign = secureChannel.protectedCommand(0x80, WalletApplet.INS_SIGN, dataType, p2, data);
return apduChannel.transmit(sign); return secureChannel.transmit(apduChannel, sign);
} }
/** /**
@ -398,8 +402,8 @@ public class WalletAppletCommandSet {
p1 |= reset ? 0 : WalletApplet.DERIVE_P1_APPEND_MASK; p1 |= reset ? 0 : WalletApplet.DERIVE_P1_APPEND_MASK;
byte p2 = isPublicKey ? WalletApplet.DERIVE_P2_PUBLIC_KEY : WalletApplet.DERIVE_P2_KEY_PATH; byte p2 = isPublicKey ? WalletApplet.DERIVE_P2_PUBLIC_KEY : WalletApplet.DERIVE_P2_KEY_PATH;
CommandAPDU deriveKey = new CommandAPDU(0x80, WalletApplet.INS_DERIVE_KEY, p1, p2, secureChannel.encryptAPDU(data)); CommandAPDU deriveKey = secureChannel.protectedCommand(0x80, WalletApplet.INS_DERIVE_KEY, p1, p2, data);
return apduChannel.transmit(deriveKey); return secureChannel.transmit(apduChannel, deriveKey);
} }
/** /**
@ -410,8 +414,8 @@ public class WalletAppletCommandSet {
* @throws CardException communication error * @throws CardException communication error
*/ */
public ResponseAPDU setPinlessPath(byte [] data) throws CardException { public ResponseAPDU setPinlessPath(byte [] data) throws CardException {
CommandAPDU setPinlessPath = new CommandAPDU(0x80, WalletApplet.INS_SET_PINLESS_PATH, 0x00, 0x00, secureChannel.encryptAPDU(data)); CommandAPDU setPinlessPath = secureChannel.protectedCommand(0x80, WalletApplet.INS_SET_PINLESS_PATH, 0x00, 0x00, data);
return apduChannel.transmit(setPinlessPath); return secureChannel.transmit(apduChannel, setPinlessPath);
} }
/** /**
@ -422,7 +426,7 @@ public class WalletAppletCommandSet {
* @throws CardException communication error * @throws CardException communication error
*/ */
public ResponseAPDU exportKey(byte keyPathIndex) throws CardException { public ResponseAPDU exportKey(byte keyPathIndex) throws CardException {
CommandAPDU exportKey = new CommandAPDU(0x80, WalletApplet.INS_EXPORT_KEY, keyPathIndex, 0x00, 256); CommandAPDU exportKey = secureChannel.protectedCommand(0x80, WalletApplet.INS_EXPORT_KEY, keyPathIndex, 0x00, new byte[0]);
return apduChannel.transmit(exportKey); return secureChannel.transmit(apduChannel, exportKey);
} }
} }

View File

@ -128,14 +128,16 @@ public class WalletAppletTest {
// Good case // Good case
response = cmdSet.openSecureChannel(secureChannel.getPairingIndex(), secureChannel.getPublicKey()); response = cmdSet.openSecureChannel(secureChannel.getPairingIndex(), secureChannel.getPublicKey());
assertEquals(0x9000, response.getSW()); assertEquals(0x9000, response.getSW());
assertEquals(SecureChannel.SC_SECRET_LENGTH, response.getData().length); assertEquals(SecureChannel.SC_SECRET_LENGTH + SecureChannel.SC_BLOCK_SIZE, response.getData().length);
secureChannel.processOpenSecureChannelResponse(response); secureChannel.processOpenSecureChannelResponse(response);
// Send command before MUTUALLY AUTHENTICATE // Send command before MUTUALLY AUTHENTICATE
secureChannel.reset();
response = cmdSet.getStatus(WalletApplet.GET_STATUS_P1_APPLICATION); response = cmdSet.getStatus(WalletApplet.GET_STATUS_P1_APPLICATION);
assertEquals(0x6985, response.getSW()); assertEquals(0x6985, response.getSW());
// Perform mutual authentication // Perform mutual authentication
secureChannel.setOpen();
response = cmdSet.mutuallyAuthenticate(); response = cmdSet.mutuallyAuthenticate();
assertEquals(0x9000, response.getSW()); assertEquals(0x9000, response.getSW());
assertTrue(secureChannel.verifyMutuallyAuthenticateResponse(response)); assertTrue(secureChannel.verifyMutuallyAuthenticateResponse(response));
@ -157,17 +159,23 @@ public class WalletAppletTest {
secureChannel.processOpenSecureChannelResponse(response); secureChannel.processOpenSecureChannelResponse(response);
// Wrong data format // Wrong data format
response = cmdSet.mutuallyAuthenticate(new byte[63]); response = cmdSet.mutuallyAuthenticate(new byte[31]);
assertEquals(0x6A80, response.getSW());
// Wrong authentication data
response = cmdSet.mutuallyAuthenticate(new byte[64]);
assertEquals(0x6982, response.getSW()); assertEquals(0x6982, response.getSW());
// Verify that after wrong authentication, the command does not work // Verify that after wrong authentication, the command does not work
response = cmdSet.mutuallyAuthenticate(); response = cmdSet.mutuallyAuthenticate();
assertEquals(0x6985, response.getSW()); assertEquals(0x6985, response.getSW());
// Wrong authentication data
response = cmdSet.openSecureChannel(secureChannel.getPairingIndex(), secureChannel.getPublicKey());
assertEquals(0x9000, response.getSW());
secureChannel.processOpenSecureChannelResponse(response);
response = apduChannel.transmit(new CommandAPDU(0x80, SecureChannel.INS_MUTUALLY_AUTHENTICATE, 0, 0, new byte[48]));
assertEquals(0x6982, response.getSW());
secureChannel.reset();
response = cmdSet.mutuallyAuthenticate();
assertEquals(0x6985, response.getSW());
// Good case // Good case
cmdSet.autoOpenSecureChannel(); cmdSet.autoOpenSecureChannel();
@ -235,7 +243,7 @@ public class WalletAppletTest {
assertEquals(0x9000, response.getSW()); assertEquals(0x9000, response.getSW());
for (byte i = 0; i < 4; i++) { for (byte i = 0; i < 4; i++) {
response = cmdSet.unpair(i, new byte[] { i }); response = cmdSet.unpair(i);
assertEquals(0x9000, response.getSW()); assertEquals(0x9000, response.getSW());
} }
} }
@ -252,28 +260,22 @@ public class WalletAppletTest {
assertEquals(0x9000, response.getSW()); assertEquals(0x9000, response.getSW());
// Security condition violation: SecureChannel not open // Security condition violation: SecureChannel not open
response = cmdSet.unpair(sparePairingIndex, new byte[] { sparePairingIndex }); response = cmdSet.unpair(sparePairingIndex);
assertEquals(0x6985, response.getSW()); assertEquals(0x6985, response.getSW());
// Not authenticated // Not authenticated
cmdSet.autoOpenSecureChannel(); cmdSet.autoOpenSecureChannel();
response = cmdSet.unpair(sparePairingIndex, new byte[] { sparePairingIndex }); response = cmdSet.unpair(sparePairingIndex);
assertEquals(0x6985, response.getSW()); assertEquals(0x6985, response.getSW());
// Wrong P1 // Wrong P1
response = cmdSet.verifyPIN("000000"); response = cmdSet.verifyPIN("000000");
assertEquals(0x9000, response.getSW()); assertEquals(0x9000, response.getSW());
response = cmdSet.unpair((byte) 5, new byte[] { 5 }); response = cmdSet.unpair((byte) 5);
assertEquals(0x6A86, response.getSW()); assertEquals(0x6A86, response.getSW());
// Unmatching P1 and data
response = cmdSet.unpair(sparePairingIndex, new byte[] { (byte) (sparePairingIndex + 1) });
assertEquals(0x6985, response.getSW());
response = cmdSet.unpair((byte) (sparePairingIndex + 1), new byte[] { sparePairingIndex });
assertEquals(0x6985, response.getSW());
// Unpair spare keyset // Unpair spare keyset
response = cmdSet.unpair(sparePairingIndex, new byte[] { sparePairingIndex }); response = cmdSet.unpair(sparePairingIndex);
assertEquals(0x9000, response.getSW()); assertEquals(0x9000, response.getSW());
// Proof that unpaired is not usable // Proof that unpaired is not usable
@ -293,27 +295,27 @@ public class WalletAppletTest {
// Additionally, support for public key derivation is hw dependent. // Additionally, support for public key derivation is hw dependent.
response = cmdSet.getStatus(WalletApplet.GET_STATUS_P1_APPLICATION); response = cmdSet.getStatus(WalletApplet.GET_STATUS_P1_APPLICATION);
assertEquals(0x9000, response.getSW()); assertEquals(0x9000, response.getSW());
byte[] data = secureChannel.decryptAPDU(response.getData()); byte[] data = response.getData();
assertTrue(Hex.toHexString(data).matches("a309c00103c10105c2010[0-1]c3010[0-1]")); assertTrue(Hex.toHexString(data).matches("a309c00103c10105c2010[0-1]c3010[0-1]"));
response = cmdSet.verifyPIN("123456"); response = cmdSet.verifyPIN("123456");
assertEquals(0x63C2, response.getSW()); assertEquals(0x63C2, response.getSW());
response = cmdSet.getStatus(WalletApplet.GET_STATUS_P1_APPLICATION); response = cmdSet.getStatus(WalletApplet.GET_STATUS_P1_APPLICATION);
assertEquals(0x9000, response.getSW()); assertEquals(0x9000, response.getSW());
data = secureChannel.decryptAPDU(response.getData()); data = response.getData();
assertTrue(Hex.toHexString(data).matches("a309c00102c10105c2010[0-1]c3010[0-1]")); assertTrue(Hex.toHexString(data).matches("a309c00102c10105c2010[0-1]c3010[0-1]"));
response = cmdSet.verifyPIN("000000"); response = cmdSet.verifyPIN("000000");
assertEquals(0x9000, response.getSW()); assertEquals(0x9000, response.getSW());
response = cmdSet.getStatus(WalletApplet.GET_STATUS_P1_APPLICATION); response = cmdSet.getStatus(WalletApplet.GET_STATUS_P1_APPLICATION);
assertEquals(0x9000, response.getSW()); assertEquals(0x9000, response.getSW());
data = secureChannel.decryptAPDU(response.getData()); data = response.getData();
assertTrue(Hex.toHexString(data).matches("a309c00103c10105c2010[0-1]c3010[0-1]")); assertTrue(Hex.toHexString(data).matches("a309c00103c10105c2010[0-1]c3010[0-1]"));
// Check that key path is empty // Check that key path is empty
response = cmdSet.getStatus(WalletApplet.GET_STATUS_P1_KEY_PATH); response = cmdSet.getStatus(WalletApplet.GET_STATUS_P1_KEY_PATH);
assertEquals(0x9000, response.getSW()); assertEquals(0x9000, response.getSW());
data = secureChannel.decryptAPDU(response.getData()); data = response.getData();
assertEquals(0, data.length); assertEquals(0, data.length);
} }
@ -524,23 +526,23 @@ public class WalletAppletTest {
// Good cases // Good cases
response = cmdSet.generateMnemonic(4); response = cmdSet.generateMnemonic(4);
assertEquals(0x9000, response.getSW()); assertEquals(0x9000, response.getSW());
assertMnemonic(12, secureChannel.decryptAPDU(response.getData())); assertMnemonic(12, response.getData());
response = cmdSet.generateMnemonic(5); response = cmdSet.generateMnemonic(5);
assertEquals(0x9000, response.getSW()); assertEquals(0x9000, response.getSW());
assertMnemonic(15, secureChannel.decryptAPDU(response.getData())); assertMnemonic(15, response.getData());
response = cmdSet.generateMnemonic(6); response = cmdSet.generateMnemonic(6);
assertEquals(0x9000, response.getSW()); assertEquals(0x9000, response.getSW());
assertMnemonic(18, secureChannel.decryptAPDU(response.getData())); assertMnemonic(18, response.getData());
response = cmdSet.generateMnemonic(7); response = cmdSet.generateMnemonic(7);
assertEquals(0x9000, response.getSW()); assertEquals(0x9000, response.getSW());
assertMnemonic(21, secureChannel.decryptAPDU(response.getData())); assertMnemonic(21, response.getData());
response = cmdSet.generateMnemonic(8); response = cmdSet.generateMnemonic(8);
assertEquals(0x9000, response.getSW()); assertEquals(0x9000, response.getSW());
assertMnemonic(24, secureChannel.decryptAPDU(response.getData())); assertMnemonic(24, response.getData());
} }
@Test @Test
@ -621,13 +623,13 @@ public class WalletAppletTest {
// Assisted derivation // Assisted derivation
response = cmdSet.deriveKey(new byte[] {0x00, 0x00, 0x00, 0x01}, true, true, false); response = cmdSet.deriveKey(new byte[] {0x00, 0x00, 0x00, 0x01}, true, true, false);
assertEquals(0x9000, response.getSW()); assertEquals(0x9000, response.getSW());
response = cmdSet.deriveKey(derivePublicKey(secureChannel.decryptAPDU(response.getData())), false, true, true); response = cmdSet.deriveKey(derivePublicKey(response.getData()), false, true, true);
assertEquals(0x9000, response.getSW()); assertEquals(0x9000, response.getSW());
verifyKeyDerivation(keyPair, chainCode, new int[] { 1 }); verifyKeyDerivation(keyPair, chainCode, new int[] { 1 });
response = cmdSet.deriveKey(new byte[] {0x00, 0x00, 0x00, 0x02}, false, true, false); response = cmdSet.deriveKey(new byte[] {0x00, 0x00, 0x00, 0x02}, false, true, false);
assertEquals(0x9000, response.getSW()); assertEquals(0x9000, response.getSW());
response = cmdSet.deriveKey(derivePublicKey(secureChannel.decryptAPDU(response.getData())), false, true, true); response = cmdSet.deriveKey(derivePublicKey(response.getData()), false, true, true);
assertEquals(0x9000, response.getSW()); assertEquals(0x9000, response.getSW());
verifyKeyDerivation(keyPair, chainCode, new int[] { 1, 2 }); verifyKeyDerivation(keyPair, chainCode, new int[] { 1, 2 });
@ -645,7 +647,7 @@ public class WalletAppletTest {
// Try to sign and get key path before load public key, then resume loading public key // Try to sign and get key path before load public key, then resume loading public key
response = cmdSet.deriveKey(new byte[] {0x00, 0x00, 0x00, 0x02}, false, true, false); response = cmdSet.deriveKey(new byte[] {0x00, 0x00, 0x00, 0x02}, false, true, false);
assertEquals(0x9000, response.getSW()); assertEquals(0x9000, response.getSW());
byte[] key = derivePublicKey(secureChannel.decryptAPDU(response.getData())); byte[] key = derivePublicKey(response.getData());
response = cmdSet.sign(sha256("test".getBytes()), WalletApplet.SIGN_P1_PRECOMPUTED_HASH, true, true); response = cmdSet.sign(sha256("test".getBytes()), WalletApplet.SIGN_P1_PRECOMPUTED_HASH, true, true);
assertEquals(0x6985, response.getSW()); assertEquals(0x6985, response.getSW());
response = cmdSet.getStatus(WalletApplet.GET_STATUS_P1_KEY_PATH); response = cmdSet.getStatus(WalletApplet.GET_STATUS_P1_KEY_PATH);
@ -696,7 +698,7 @@ public class WalletAppletTest {
// Correctly sign a precomputed hash // Correctly sign a precomputed hash
response = cmdSet.sign(hash, WalletApplet.SIGN_P1_PRECOMPUTED_HASH,true, true); response = cmdSet.sign(hash, WalletApplet.SIGN_P1_PRECOMPUTED_HASH,true, true);
assertEquals(0x9000, response.getSW()); assertEquals(0x9000, response.getSW());
byte[] sig = secureChannel.decryptAPDU(response.getData()); byte[] sig = response.getData();
byte[] keyData = extractPublicKeyFromSignature(sig); byte[] keyData = extractPublicKeyFromSignature(sig);
sig = extractSignature(sig); sig = extractSignature(sig);
assertEquals((SecureChannel.SC_KEY_LENGTH * 2 / 8) + 1, keyData.length); assertEquals((SecureChannel.SC_KEY_LENGTH * 2 / 8) + 1, keyData.length);
@ -746,17 +748,17 @@ public class WalletAppletTest {
assertEquals(0x6985, response.getSW()); assertEquals(0x6985, response.getSW());
response = cmdSet.deriveKey(new byte[] {0x00, 0x00, 0x00, 0x02}, true, true, false); response = cmdSet.deriveKey(new byte[] {0x00, 0x00, 0x00, 0x02}, true, true, false);
assertEquals(0x9000, response.getSW()); assertEquals(0x9000, response.getSW());
response = cmdSet.deriveKey(derivePublicKey(secureChannel.decryptAPDU(response.getData())), false, true, true); response = cmdSet.deriveKey(derivePublicKey(response.getData()), false, true, true);
assertEquals(0x9000, response.getSW()); assertEquals(0x9000, response.getSW());
response = cmdSet.deriveKey(new byte[] {0x00, 0x00, 0x00, 0x01}, false, true, false); response = cmdSet.deriveKey(new byte[] {0x00, 0x00, 0x00, 0x01}, false, true, false);
assertEquals(0x9000, response.getSW()); assertEquals(0x9000, response.getSW());
response = cmdSet.deriveKey(derivePublicKey(secureChannel.decryptAPDU(response.getData())), false, true, true); response = cmdSet.deriveKey(derivePublicKey(response.getData()), false, true, true);
assertEquals(0x9000, response.getSW()); assertEquals(0x9000, response.getSW());
response = cmdSet.sign(hash, WalletApplet.SIGN_P1_PRECOMPUTED_HASH,true, true); response = cmdSet.sign(hash, WalletApplet.SIGN_P1_PRECOMPUTED_HASH,true, true);
assertEquals(0x6985, response.getSW()); assertEquals(0x6985, response.getSW());
response = cmdSet.deriveKey(new byte[] {0x00, 0x00, 0x00, 0x02}, false, true, false); response = cmdSet.deriveKey(new byte[] {0x00, 0x00, 0x00, 0x02}, false, true, false);
assertEquals(0x9000, response.getSW()); assertEquals(0x9000, response.getSW());
response = cmdSet.deriveKey(derivePublicKey(secureChannel.decryptAPDU(response.getData())), false, true, true); response = cmdSet.deriveKey(derivePublicKey(response.getData()), false, true, true);
assertEquals(0x9000, response.getSW()); assertEquals(0x9000, response.getSW());
response = cmdSet.sign(hash, WalletApplet.SIGN_P1_PRECOMPUTED_HASH,true, true); response = cmdSet.sign(hash, WalletApplet.SIGN_P1_PRECOMPUTED_HASH,true, true);
assertEquals(0x9000, response.getSW()); assertEquals(0x9000, response.getSW());
@ -772,11 +774,11 @@ public class WalletAppletTest {
assertEquals(0x6985, response.getSW()); assertEquals(0x6985, response.getSW());
response = cmdSet.deriveKey(new byte[] {0x00, 0x00, 0x00, 0x02}, true, true, false); response = cmdSet.deriveKey(new byte[] {0x00, 0x00, 0x00, 0x02}, true, true, false);
assertEquals(0x9000, response.getSW()); assertEquals(0x9000, response.getSW());
response = cmdSet.deriveKey(derivePublicKey(secureChannel.decryptAPDU(response.getData())), false, true, true); response = cmdSet.deriveKey(derivePublicKey(response.getData()), false, true, true);
assertEquals(0x9000, response.getSW()); assertEquals(0x9000, response.getSW());
response = cmdSet.deriveKey(new byte[] {0x00, 0x00, 0x00, 0x01}, false, true, false); response = cmdSet.deriveKey(new byte[] {0x00, 0x00, 0x00, 0x01}, false, true, false);
assertEquals(0x9000, response.getSW()); assertEquals(0x9000, response.getSW());
response = cmdSet.deriveKey(derivePublicKey(secureChannel.decryptAPDU(response.getData())), false, true, true); response = cmdSet.deriveKey(derivePublicKey(response.getData()), false, true, true);
assertEquals(0x9000, response.getSW()); assertEquals(0x9000, response.getSW());
response = cmdSet.sign(hash, WalletApplet.SIGN_P1_PRECOMPUTED_HASH,true, true); response = cmdSet.sign(hash, WalletApplet.SIGN_P1_PRECOMPUTED_HASH,true, true);
assertEquals(0x9000, response.getSW()); assertEquals(0x9000, response.getSW());
@ -822,13 +824,13 @@ public class WalletAppletTest {
response = cmdSet.deriveKey(new byte[] {0x00, 0x00, 0x00, 0x01}, true, true, false); response = cmdSet.deriveKey(new byte[] {0x00, 0x00, 0x00, 0x01}, true, true, false);
assertEquals(0x9000, response.getSW()); assertEquals(0x9000, response.getSW());
response = cmdSet.deriveKey(derivePublicKey(secureChannel.decryptAPDU(response.getData())), false, true, true); response = cmdSet.deriveKey(derivePublicKey(response.getData()), false, true, true);
assertEquals(0x9000, response.getSW()); assertEquals(0x9000, response.getSW());
response = cmdSet.exportKey(WalletApplet.EXPORT_KEY_P1_WHISPER); response = cmdSet.exportKey(WalletApplet.EXPORT_KEY_P1_WHISPER);
assertEquals(0x6985, response.getSW()); assertEquals(0x6985, response.getSW());
response = cmdSet.deriveKey(new byte[] {0x00, 0x00, 0x00, 0x01}, false, true, false); response = cmdSet.deriveKey(new byte[] {0x00, 0x00, 0x00, 0x01}, false, true, false);
assertEquals(0x9000, response.getSW()); assertEquals(0x9000, response.getSW());
response = cmdSet.deriveKey(derivePublicKey(secureChannel.decryptAPDU(response.getData())), false, true, true); response = cmdSet.deriveKey(derivePublicKey(response.getData()), false, true, true);
assertEquals(0x9000, response.getSW()); assertEquals(0x9000, response.getSW());
// Wrong P1 // Wrong P1
@ -840,7 +842,7 @@ public class WalletAppletTest {
// Correct // Correct
response = cmdSet.exportKey(WalletApplet.EXPORT_KEY_P1_WHISPER); response = cmdSet.exportKey(WalletApplet.EXPORT_KEY_P1_WHISPER);
assertEquals(0x9000, response.getSW()); assertEquals(0x9000, response.getSW());
byte[] keyTemplate = secureChannel.decryptAPDU(response.getData()); byte[] keyTemplate = response.getData();
verifyExportedKey(keyTemplate, keyPair, chainCode, new int[] { 1, 1 }); verifyExportedKey(keyTemplate, keyPair, chainCode, new int[] { 1, 1 });
// Reset // Reset
@ -881,7 +883,7 @@ public class WalletAppletTest {
// Correctly sign 1 block (P2: 0x81) // Correctly sign 1 block (P2: 0x81)
response = cmdSet.sign(smallData, WalletApplet.SIGN_P1_DATA,true, true); response = cmdSet.sign(smallData, WalletApplet.SIGN_P1_DATA,true, true);
assertEquals(0x9000, response.getSW()); assertEquals(0x9000, response.getSW());
byte[] sig = extractSignature(secureChannel.decryptAPDU(response.getData())); byte[] sig = extractSignature(response.getData());
signature.update(smallData); signature.update(smallData);
assertTrue(signature.verify(sig)); assertTrue(signature.verify(sig));
@ -890,7 +892,7 @@ public class WalletAppletTest {
assertEquals(0x9000, response.getSW()); assertEquals(0x9000, response.getSW());
response = cmdSet.sign(smallData, WalletApplet.SIGN_P1_DATA,false, true); response = cmdSet.sign(smallData, WalletApplet.SIGN_P1_DATA,false, true);
assertEquals(0x9000, response.getSW()); assertEquals(0x9000, response.getSW());
sig = extractSignature(secureChannel.decryptAPDU(response.getData())); sig = extractSignature(response.getData());
signature.update(data); signature.update(data);
signature.update(smallData); signature.update(smallData);
assertTrue(signature.verify(sig)); assertTrue(signature.verify(sig));
@ -902,7 +904,7 @@ public class WalletAppletTest {
assertEquals(0x9000, response.getSW()); assertEquals(0x9000, response.getSW());
response = cmdSet.sign(smallData, WalletApplet.SIGN_P1_DATA,false, true); response = cmdSet.sign(smallData, WalletApplet.SIGN_P1_DATA,false, true);
assertEquals(0x9000, response.getSW()); assertEquals(0x9000, response.getSW());
sig = extractSignature(secureChannel.decryptAPDU(response.getData())); sig = extractSignature(response.getData());
signature.update(data); signature.update(data);
signature.update(data); signature.update(data);
signature.update(smallData); signature.update(smallData);
@ -913,7 +915,7 @@ public class WalletAppletTest {
assertEquals(0x9000, response.getSW()); assertEquals(0x9000, response.getSW());
response = cmdSet.sign(smallData, WalletApplet.SIGN_P1_DATA,true, true); response = cmdSet.sign(smallData, WalletApplet.SIGN_P1_DATA,true, true);
assertEquals(0x9000, response.getSW()); assertEquals(0x9000, response.getSW());
sig = extractSignature(secureChannel.decryptAPDU(response.getData())); sig = extractSignature(response.getData());
signature.update(smallData); signature.update(smallData);
assertTrue(signature.verify(sig)); assertTrue(signature.verify(sig));
@ -943,7 +945,7 @@ public class WalletAppletTest {
assertEquals(0x9000, response.getSW()); assertEquals(0x9000, response.getSW());
response = cmdSet.sign(smallData, WalletApplet.SIGN_P1_DATA,false, true); response = cmdSet.sign(smallData, WalletApplet.SIGN_P1_DATA,false, true);
assertEquals(0x9000, response.getSW()); assertEquals(0x9000, response.getSW());
sig = extractSignature(secureChannel.decryptAPDU(response.getData())); sig = extractSignature(response.getData());
signature.update(data); signature.update(data);
signature.update(smallData); signature.update(smallData);
assertTrue(signature.verify(sig)); assertTrue(signature.verify(sig));
@ -1074,7 +1076,7 @@ public class WalletAppletTest {
byte[] hash = Hash.sha3(new byte[8]); byte[] hash = Hash.sha3(new byte[8]);
ResponseAPDU resp = cmdSet.sign(hash, WalletApplet.SIGN_P1_PRECOMPUTED_HASH, true, true); ResponseAPDU resp = cmdSet.sign(hash, WalletApplet.SIGN_P1_PRECOMPUTED_HASH, true, true);
assertEquals(0x9000, resp.getSW()); assertEquals(0x9000, resp.getSW());
byte[] sig = secureChannel.decryptAPDU(resp.getData()); byte[] sig = resp.getData();
byte[] publicKey = extractPublicKeyFromSignature(sig); byte[] publicKey = extractPublicKeyFromSignature(sig);
sig = extractSignature(sig); sig = extractSignature(sig);
@ -1083,7 +1085,7 @@ public class WalletAppletTest {
resp = cmdSet.getStatus(WalletApplet.GET_STATUS_P1_KEY_PATH); resp = cmdSet.getStatus(WalletApplet.GET_STATUS_P1_KEY_PATH);
assertEquals(0x9000, resp.getSW()); assertEquals(0x9000, resp.getSW());
byte[] rawPath = secureChannel.decryptAPDU(resp.getData()); byte[] rawPath = resp.getData();
assertEquals(path.length * 4, rawPath.length); assertEquals(path.length * 4, rawPath.length);
@ -1172,7 +1174,7 @@ public class WalletAppletTest {
ResponseAPDU response = cmdSet.sign(messageHash, WalletApplet.SIGN_P1_PRECOMPUTED_HASH,true, true); ResponseAPDU response = cmdSet.sign(messageHash, WalletApplet.SIGN_P1_PRECOMPUTED_HASH,true, true);
assertEquals(0x9000, response.getSW()); assertEquals(0x9000, response.getSW());
byte[] respData = secureChannel.decryptAPDU(response.getData()); byte[] respData = response.getData();
byte[] rawSig = extractSignature(respData); byte[] rawSig = extractSignature(respData);
int rLen = rawSig[3]; int rLen = rawSig[3];