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,15 +55,14 @@ The seed IV is used by the client as the IV for the next encrypted APDU.
* 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
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
guarantee authentication of the counterpart. The data sent by both parties is a 256-bit random number The APDU data is
sent encrypted with the keys generated in the OPEN SECURE CHANNEL step. Each party must verify the MAC of the received
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
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.
the card must respond with 0x6982. In this case the OPEN SECURE CHANNEL command must be repeated to generate new keys.
### PAIR
@ -123,9 +122,15 @@ happen.
### Encrypted APDUs
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
are plaintext. This means no sensitive data should be sent in these parameters. Additionally a MAC is calculated for the
entire APDU, including the unencrypted fields.
the data fields of C-APDU are encrypted, which means that CLA, INS, P1, P2 for C-APDU are plaintext. This means no
sensitive data should be sent in these parameters. Additionally a MAC is calculated for the entire APDU, including
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:
@ -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
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
the encrypted data field. The 13-byte long padding does not become part of the response field. Lr is the length of the
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 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.
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 scMacKey;
private Cipher scCipher;
private Signature scSignature;
private Signature scMac;
private KeyPair scKeypair;
private byte[] secret;
private byte[] pairingSecret;
@ -45,7 +45,8 @@ public class SecureChannel {
*/
public SecureChannel(byte pairingLimit, byte[] aPairingSecret, short off) {
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);
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);
}
/**
* Processes the OPEN SECURE CHANNEL command.
*
@ -99,7 +99,7 @@ public class SecureChannel {
scEncKey.setKey(secret, (short) 0);
scMacKey.setKey(secret, SC_SECRET_LENGTH);
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));
}
@ -109,29 +109,26 @@ public class SecureChannel {
* @param apdu the JCRE-owned APDU object.
*/
public void mutuallyAuthenticate(APDU apdu) {
if (!scEncKey.isInitialized() || mutuallyAuthenticated) {
if (!scEncKey.isInitialized()) {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}
byte[] apduBuffer = apdu.getBuffer();
boolean oldMutuallyAuthenticated = mutuallyAuthenticated;
mutuallyAuthenticated = true;
byte[] apduBuffer = apdu.getBuffer();
short len = preprocessAPDU(apduBuffer);
if (len != (short) (SC_SECRET_LENGTH * 2)) {
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
if (oldMutuallyAuthenticated) {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}
Crypto.sha256.doFinal(apduBuffer, ISO7816.OFFSET_CDATA, SC_SECRET_LENGTH, apduBuffer, ISO7816.OFFSET_CDATA);
if (Util.arrayCompare(apduBuffer, ISO7816.OFFSET_CDATA, apduBuffer, (short) (ISO7816.OFFSET_CDATA + SC_SECRET_LENGTH), SC_SECRET_LENGTH) != 0) {
if (len != SC_SECRET_LENGTH) {
reset();
ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
}
mutuallyAuthenticated = true;
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);
}
@ -249,11 +246,7 @@ public class SecureChannel {
short apduLen = (short)((short) apduBuffer[ISO7816.OFFSET_LC] & 0xff);
scSignature.init(scMacKey, Signature.MODE_VERIFY);
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)) {
if (!verifyAESMAC(apduBuffer, apduLen)) {
reset();
ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
}
@ -267,10 +260,18 @@ public class SecureChannel {
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
* called after its execution. The response data must be placed starting at the SecureChannel.SC_OUT_OFFSET offset, to
* leave place for the SecureChannel-specific data at the beginning of the APDU.
* Sends the response to the command. This the given SW is appended to the data automatically. The response data must
* be placed starting at the SecureChannel.SC_OUT_OFFSET offset, to leave place for the SecureChannel-specific data at
* the beginning of the APDU.
*
* @param apdu the APDU object
* @param len the length of the plaintext
@ -278,21 +279,27 @@ public class SecureChannel {
public void respond(APDU apdu, short len, short sw) {
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);
len = scCipher.doFinal(apduBuffer, SC_OUT_OFFSET, len, apduBuffer, (short)(ISO7816.OFFSET_CDATA + SC_BLOCK_SIZE));
scSignature.init(scMacKey, Signature.MODE_SIGN);
scSignature.update(apduBuffer, (short) 0, (short) 2);
scSignature.update(secret, SC_BLOCK_SIZE, (short)(SC_BLOCK_SIZE - 2));
scSignature.sign(apduBuffer, SC_OUT_OFFSET, len, apduBuffer, ISO7816.OFFSET_CDATA);
apduBuffer[0] = (byte) (len + SC_BLOCK_SIZE);
computeAESMAC(len, apduBuffer);
Util.arrayCopyNonAtomic(apduBuffer, ISO7816.OFFSET_CDATA, secret, (short) 0, SC_BLOCK_SIZE);
len += SC_BLOCK_SIZE;
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;
}
} 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());
} else {
throw sw;
}
}
if (secureChannel.isOpen()) {
if (shouldRespond(apdu)) {
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
* validated the 0x6985 exception is thrown.
@ -292,11 +296,8 @@ public class WalletApplet extends Applet {
* @param apdu the JCRE-owned APDU object.
*/
private void getStatus(APDU apdu) {
if (!secureChannel.isOpen()) {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}
byte[] apduBuffer = apdu.getBuffer();
secureChannel.preprocessAPDU(apduBuffer);
short len;
@ -367,10 +368,6 @@ public class WalletApplet extends Applet {
* @param apdu the JCRE-owned APDU object.
*/
private void verifyPIN(APDU apdu) {
if (!secureChannel.isOpen()) {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}
byte[] apduBuffer = apdu.getBuffer();
byte len = (byte) secureChannel.preprocessAPDU(apduBuffer);
@ -387,13 +384,13 @@ public class WalletApplet extends Applet {
* @param apdu the JCRE-owned APDU object.
*/
private void changePIN(APDU apdu) {
if (!(secureChannel.isOpen() && pin.isValidated())) {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}
byte[] apduBuffer = apdu.getBuffer();
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))) {
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
}
@ -411,13 +408,13 @@ public class WalletApplet extends Applet {
* @param apdu the JCRE-owned APDU object.
*/
private void unblockPIN(APDU apdu) {
if (!(secureChannel.isOpen() && pin.getTriesRemaining() == 0)) {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}
byte[] apduBuffer = apdu.getBuffer();
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))) {
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
}
@ -442,13 +439,13 @@ public class WalletApplet extends Applet {
* @param apdu the JCRE-owned APDU object.
*/
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);
}
byte[] apduBuffer = apdu.getBuffer();
secureChannel.preprocessAPDU(apduBuffer);
boolean newExtended = false;
switch (apduBuffer[ISO7816.OFFSET_P1]) {
@ -586,14 +583,13 @@ public class WalletApplet extends Applet {
* @param apdu the JCRE-owned APDU object.
*/
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);
}
byte[] apduBuffer = apdu.getBuffer();
short len = secureChannel.preprocessAPDU(apduBuffer);
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 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.
*/
private void generateMnemonic(APDU apdu) {
if (!secureChannel.isOpen()) {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}
byte[] apduBuffer = apdu.getBuffer();
secureChannel.preprocessAPDU(apduBuffer);
short csLen = apduBuffer[ISO7816.OFFSET_P1];
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.
*/
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);
}
byte[] apduBuffer = apdu.getBuffer();
if ((apduBuffer[ISO7816.OFFSET_P2] & SIGN_P2_FIRST_BLOCK_MASK) == SIGN_P2_FIRST_BLOCK_MASK) {
signInProgress = true;
signature.init(privateKey, Signature.MODE_SIGN);
@ -798,8 +793,6 @@ public class WalletApplet extends Applet {
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) {
signInProgress = false;
@ -838,13 +831,13 @@ public class WalletApplet extends Applet {
* @param apdu the JCRE-owned APDU object.
*/
private void setPinlessPath(APDU apdu) {
if (!(secureChannel.isOpen() && pin.isValidated())) {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}
byte[] apduBuffer = apdu.getBuffer();
short len = secureChannel.preprocessAPDU(apduBuffer);
if (!pin.isValidated()) {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}
if (((short) (len % 4) != 0) || (len > pinlessPath.length)) {
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
}
@ -864,11 +857,13 @@ public class WalletApplet extends Applet {
* @param apdu the JCRE-owned APDU object.
*/
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);
}
byte[] apduBuffer = apdu.getBuffer();
byte[] toExport;
switch (apduBuffer[ISO7816.OFFSET_P1]) {

View File

@ -1,5 +1,8 @@
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.interfaces.ECPublicKey;
import org.bouncycastle.jce.spec.ECParameterSpec;
@ -25,10 +28,14 @@ public class SecureChannelSession {
private byte[] secret;
private byte[] publicKey;
private byte[] pairingKey;
private byte[] iv;
private byte pairingIndex;
private Cipher sessionCipher;
private SecretKeySpec sessionKey;
private CBCBlockCipherMac sessionMac;
private SecretKeySpec sessionEncKey;
private KeyParameter sessionMacKey;
private SecureRandom random;
private boolean open;
/**
* 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);
secret = keyAgreement.generateSecret();
open = false;
} catch(Exception 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.
* The card returns a secret value which must be appended to the secret previously generated through the EC-DH
* 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.
* Follows the specifications from the SECURE_CHANNEL.md document.
*
* @param apduChannel the apdu channel
* @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
*/
public void processOpenSecureChannelResponse(ResponseAPDU response) {
try {
MessageDigest md = MessageDigest.getInstance("SHA256", "BC");
MessageDigest md = MessageDigest.getInstance("SHA512", "BC");
md.update(secret);
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");
sessionMac = new CBCBlockCipherMac(new AESEngine(), 128, null);
open = true;
} catch(Exception 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
*/
public boolean verifyMutuallyAuthenticateResponse(ResponseAPDU response) {
MessageDigest md;
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);
return response.getNr() == SecureChannel.SC_SECRET_LENGTH;
}
/**
@ -204,7 +203,7 @@ public class SecureChannelSession {
* @throws CardException communication error
*/
public void autoUnpair(CardChannel apduChannel) throws CardException {
ResponseAPDU resp = unpair(apduChannel, pairingIndex, new byte[] { pairingIndex });
ResponseAPDU resp = unpair(apduChannel, pairingIndex);
if (resp.getSW() != 0x9000) {
throw new CardException("Unpairing failed");
@ -221,6 +220,7 @@ public class SecureChannelSession {
* @throws CardException communication error
*/
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);
return apduChannel.transmit(openSecureChannel);
}
@ -233,19 +233,8 @@ public class SecureChannelSession {
* @throws CardException communication error
*/
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];
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);
}
@ -259,8 +248,8 @@ public class SecureChannelSession {
* @throws CardException communication error
*/
public ResponseAPDU mutuallyAuthenticate(CardChannel apduChannel, byte[] data) throws CardException {
CommandAPDU mutuallyAuthenticate = new CommandAPDU(0x80, SecureChannel.INS_MUTUALLY_AUTHENTICATE, 0, 0, encryptAPDU(data));
return apduChannel.transmit(mutuallyAuthenticate);
CommandAPDU mutuallyAuthenticate = protectedCommand(0x80, SecureChannel.INS_MUTUALLY_AUTHENTICATE, 0, 0, data);
return transmit(apduChannel, mutuallyAuthenticate);
}
/**
@ -274,7 +263,7 @@ public class SecureChannelSession {
*/
public ResponseAPDU pair(CardChannel apduChannel, byte p1, byte[] data) throws CardException {
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 p1 the P1 parameter
* @param data the data
* @return the raw card response
* @throws CardException communication error
*/
public ResponseAPDU unpair(CardChannel apduChannel, byte p1, byte[] data) throws CardException {
CommandAPDU openSecureChannel = new CommandAPDU(0x80, SecureChannel.INS_UNPAIR, p1, 0, encryptAPDU(data));
return apduChannel.transmit(openSecureChannel);
public ResponseAPDU unpair(CardChannel apduChannel, byte p1) throws CardException {
CommandAPDU openSecureChannel = protectedCommand(0x80, SecureChannel.INS_UNPAIR, p1, 0, new byte[0]);
return transmit(apduChannel, openSecureChannel);
}
/**
@ -299,27 +287,14 @@ public class SecureChannelSession {
* @param data the plaintext data
* @return the encrypted data
*/
public byte[] encryptAPDU(byte[] data) {
private byte[] encryptAPDU(byte[] data) {
assert data.length <= PAYLOAD_MAX_SIZE;
if (sessionKey == null) {
return data;
}
try {
int ivSize = sessionCipher.getBlockSize();
byte[] iv = new byte[ivSize];
random.nextBytes(iv);
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
sessionCipher.init(Cipher.ENCRYPT_MODE, sessionKey, ivParameterSpec);
byte[] encrypted = 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;
sessionCipher.init(Cipher.ENCRYPT_MODE, sessionEncKey, ivParameterSpec);
return sessionCipher.doFinal(data);
} catch(Exception e) {
throw new RuntimeException("Is BouncyCastle in the classpath?", e);
}
@ -332,18 +307,75 @@ public class SecureChannelSession {
* @param data the ciphetext
* @return the plaintext
*/
public byte[] decryptAPDU(byte[] data) {
if (sessionKey == null) {
return data;
private byte[] decryptAPDU(byte[] data) {
try {
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 {
int ivSize = sessionCipher.getBlockSize();
IvParameterSpec ivParameterSpec = new IvParameterSpec(data, 0, ivSize);
sessionCipher.init(Cipher.DECRYPT_MODE, sessionKey, ivParameterSpec);
return sessionCipher.doFinal(data, ivSize, data.length - ivSize);
} catch(Exception e) {
e.printStackTrace();
sessionMac.init(sessionMacKey);
sessionMac.update(meta, 0, meta.length);
sessionMac.update(data, 0, data.length);
sessionMac.doFinal(iv, 0);
} catch (Exception e) {
throw new RuntimeException("Is BouncyCastle in the classpath?", e);
}
}

View File

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

View File

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