update test to use pairing/unpairing
This commit is contained in:
parent
e11d817c64
commit
a61369d1bc
|
@ -80,7 +80,7 @@ public class SecureChannel {
|
|||
short len = Crypto.ecdh.generateSecret(apduBuffer, ISO7816.OFFSET_CDATA, apduBuffer[ISO7816.OFFSET_LC], secret, (short) 0);
|
||||
Crypto.random.generateData(apduBuffer, (short) 0, SC_SECRET_LENGTH);
|
||||
Crypto.sha256.update(secret, (short) 0, len);
|
||||
Crypto.sha256.update(pairingSecret, pairingKeyOff, SC_SECRET_LENGTH);
|
||||
Crypto.sha256.update(pairingKeys, pairingKeyOff, SC_SECRET_LENGTH);
|
||||
Crypto.sha256.doFinal(apduBuffer, (short) 0, SC_SECRET_LENGTH, secret, (short) 0);
|
||||
scKey.setKey(secret, (short) 0);
|
||||
apdu.setOutgoingAndSend((short) 0, SC_SECRET_LENGTH);
|
||||
|
|
|
@ -14,6 +14,7 @@ import javax.smartcardio.CardException;
|
|||
import javax.smartcardio.CommandAPDU;
|
||||
import javax.smartcardio.ResponseAPDU;
|
||||
import java.security.*;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Handles a SecureChannel session with the card.
|
||||
|
@ -23,6 +24,8 @@ public class SecureChannelSession {
|
|||
|
||||
private byte[] secret;
|
||||
private byte[] publicKey;
|
||||
private byte[] pairingKey;
|
||||
private byte pairingIndex;
|
||||
private Cipher sessionCipher;
|
||||
private SecretKeySpec sessionKey;
|
||||
private SecureRandom random;
|
||||
|
@ -69,13 +72,14 @@ public class SecureChannelSession {
|
|||
* @throws CardException communication error
|
||||
*/
|
||||
public ResponseAPDU openSecureChannel(CardChannel apduChannel) throws CardException {
|
||||
CommandAPDU openSecureChannel = new CommandAPDU(0x80, SecureChannel.INS_OPEN_SECURE_CHANNEL, 0, 0, publicKey);
|
||||
CommandAPDU openSecureChannel = new CommandAPDU(0x80, SecureChannel.INS_OPEN_SECURE_CHANNEL, pairingIndex, 0, publicKey);
|
||||
ResponseAPDU response = apduChannel.transmit(openSecureChannel);
|
||||
byte[] salt = response.getData();
|
||||
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("SHA256", "BC");
|
||||
md.update(secret);
|
||||
md.update(pairingKey);
|
||||
sessionKey = new SecretKeySpec(md.digest(salt), "AES");
|
||||
sessionCipher = Cipher.getInstance("AES/CBC/ISO7816-4Padding", "BC");
|
||||
} catch(Exception e) {
|
||||
|
@ -85,6 +89,98 @@ public class SecureChannelSession {
|
|||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the entire pairing procedure in order to be able to use the secure channel
|
||||
*
|
||||
* @param apduChannel the apdu channel
|
||||
* @throws CardException communication error
|
||||
*/
|
||||
public void autoPair(CardChannel apduChannel, byte[] sharedSecret) throws CardException {
|
||||
byte[] challenge = new byte[32];
|
||||
random.nextBytes(challenge);
|
||||
ResponseAPDU resp = pair(apduChannel, SecureChannel.PAIR_P1_FIRST_STEP, challenge);
|
||||
|
||||
if (resp.getSW() != 0x9000) {
|
||||
throw new CardException("Pairing failed on step 1");
|
||||
}
|
||||
|
||||
byte[] respData = resp.getData();
|
||||
byte[] cardCryptogram = Arrays.copyOf(respData, 32);
|
||||
byte[] cardChallenge = Arrays.copyOfRange(respData, 32, respData.length);
|
||||
byte[] checkCryptogram;
|
||||
|
||||
MessageDigest md;
|
||||
|
||||
try {
|
||||
md = MessageDigest.getInstance("SHA256", "BC");
|
||||
} catch(Exception e) {
|
||||
throw new RuntimeException("Is BouncyCastle in the classpath?", e);
|
||||
}
|
||||
|
||||
md.update(sharedSecret);
|
||||
checkCryptogram = md.digest(challenge);
|
||||
|
||||
if (!Arrays.equals(checkCryptogram, cardCryptogram)) {
|
||||
throw new CardException("Invalid card cryptogram");
|
||||
}
|
||||
|
||||
md.update(sharedSecret);
|
||||
checkCryptogram = md.digest(cardChallenge);
|
||||
|
||||
resp = pair(apduChannel, SecureChannel.PAIR_P1_LAST_STEP, checkCryptogram);
|
||||
|
||||
if (resp.getSW() != 0x9000) {
|
||||
throw new CardException("Pairing failed on step 2");
|
||||
}
|
||||
|
||||
respData = resp.getData();
|
||||
md.update(sharedSecret);
|
||||
pairingKey = md.digest(Arrays.copyOfRange(respData, 1, respData.length));
|
||||
pairingIndex = respData[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Unpairs the current paired key
|
||||
*
|
||||
* @param apduChannel the apdu channel
|
||||
* @throws CardException communication error
|
||||
*/
|
||||
public void autoUnpair(CardChannel apduChannel) throws CardException {
|
||||
ResponseAPDU resp = unpair(apduChannel, pairingIndex, new byte[] { pairingIndex });
|
||||
|
||||
if (resp.getSW() != 0x9000) {
|
||||
throw new CardException("Unpairing failed");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a PAIR APDU.
|
||||
*
|
||||
* @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 pair(CardChannel apduChannel, byte p1, byte[] data) throws CardException {
|
||||
CommandAPDU openSecureChannel = new CommandAPDU(0x80, SecureChannel.INS_PAIR, p1, 0, data);
|
||||
return apduChannel.transmit(openSecureChannel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a UNPAIR APDU.
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypts the plaintext data using the session key. The maximum plaintext size is 223 bytes. The returned ciphertext
|
||||
* already includes the IV and padding and can be sent as-is in the APDU payload. If the input is an empty byte array
|
||||
|
|
|
@ -55,6 +55,38 @@ public class WalletAppletCommandSet {
|
|||
return secureChannel.openSecureChannel(apduChannel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically pairs. Calls the corresponding method of the SecureChannel class.
|
||||
*
|
||||
* @throws CardException communication error
|
||||
*/
|
||||
public void autoPair(byte[] sharedSecret) throws CardException {
|
||||
secureChannel.autoPair(apduChannel, sharedSecret);
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically unpairs. Calls the corresponding method of the SecureChannel class.
|
||||
*
|
||||
* @throws CardException communication error
|
||||
*/
|
||||
public void autoUnpair() throws CardException {
|
||||
secureChannel.autoUnpair(apduChannel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a PAIR APDU. Calls the corresponding method of the SecureChannel class.
|
||||
*/
|
||||
public ResponseAPDU pair(byte p1, byte[] data) throws CardException {
|
||||
return secureChannel.pair(apduChannel, p1, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a GET STATUS APDU. The info byte is the P1 parameter of the command, valid constants are defined in the applet
|
||||
* class itself.
|
||||
|
|
|
@ -87,10 +87,15 @@ public class WalletAppletTest {
|
|||
byte[] keyData = cmdSet.select().getData();
|
||||
secureChannel = new SecureChannelSession(keyData);
|
||||
cmdSet.setSecureChannel(secureChannel);
|
||||
cmdSet.autoPair(sha256("123456789012".getBytes()));
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
void tearDown() throws CardException {
|
||||
resetAndSelectAndOpenSC();
|
||||
ResponseAPDU response = cmdSet.verifyPIN("000000");
|
||||
assertEquals(0x9000, response.getSW());
|
||||
cmdSet.autoUnpair();
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
|
|
Loading…
Reference in New Issue