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);
|
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.random.generateData(apduBuffer, (short) 0, SC_SECRET_LENGTH);
|
||||||
Crypto.sha256.update(secret, (short) 0, len);
|
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);
|
Crypto.sha256.doFinal(apduBuffer, (short) 0, SC_SECRET_LENGTH, secret, (short) 0);
|
||||||
scKey.setKey(secret, (short) 0);
|
scKey.setKey(secret, (short) 0);
|
||||||
apdu.setOutgoingAndSend((short) 0, SC_SECRET_LENGTH);
|
apdu.setOutgoingAndSend((short) 0, SC_SECRET_LENGTH);
|
||||||
|
|
|
@ -14,6 +14,7 @@ import javax.smartcardio.CardException;
|
||||||
import javax.smartcardio.CommandAPDU;
|
import javax.smartcardio.CommandAPDU;
|
||||||
import javax.smartcardio.ResponseAPDU;
|
import javax.smartcardio.ResponseAPDU;
|
||||||
import java.security.*;
|
import java.security.*;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles a SecureChannel session with the card.
|
* Handles a SecureChannel session with the card.
|
||||||
|
@ -23,6 +24,8 @@ public class SecureChannelSession {
|
||||||
|
|
||||||
private byte[] secret;
|
private byte[] secret;
|
||||||
private byte[] publicKey;
|
private byte[] publicKey;
|
||||||
|
private byte[] pairingKey;
|
||||||
|
private byte pairingIndex;
|
||||||
private Cipher sessionCipher;
|
private Cipher sessionCipher;
|
||||||
private SecretKeySpec sessionKey;
|
private SecretKeySpec sessionKey;
|
||||||
private SecureRandom random;
|
private SecureRandom random;
|
||||||
|
@ -69,13 +72,14 @@ public class SecureChannelSession {
|
||||||
* @throws CardException communication error
|
* @throws CardException communication error
|
||||||
*/
|
*/
|
||||||
public ResponseAPDU openSecureChannel(CardChannel apduChannel) throws CardException {
|
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);
|
ResponseAPDU response = apduChannel.transmit(openSecureChannel);
|
||||||
byte[] salt = response.getData();
|
byte[] salt = response.getData();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
MessageDigest md = MessageDigest.getInstance("SHA256", "BC");
|
MessageDigest md = MessageDigest.getInstance("SHA256", "BC");
|
||||||
md.update(secret);
|
md.update(secret);
|
||||||
|
md.update(pairingKey);
|
||||||
sessionKey = new SecretKeySpec(md.digest(salt), "AES");
|
sessionKey = new SecretKeySpec(md.digest(salt), "AES");
|
||||||
sessionCipher = Cipher.getInstance("AES/CBC/ISO7816-4Padding", "BC");
|
sessionCipher = Cipher.getInstance("AES/CBC/ISO7816-4Padding", "BC");
|
||||||
} catch(Exception e) {
|
} catch(Exception e) {
|
||||||
|
@ -85,6 +89,98 @@ public class SecureChannelSession {
|
||||||
return response;
|
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
|
* 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
|
* 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);
|
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
|
* Sends a GET STATUS APDU. The info byte is the P1 parameter of the command, valid constants are defined in the applet
|
||||||
* class itself.
|
* class itself.
|
||||||
|
|
|
@ -87,10 +87,15 @@ public class WalletAppletTest {
|
||||||
byte[] keyData = cmdSet.select().getData();
|
byte[] keyData = cmdSet.select().getData();
|
||||||
secureChannel = new SecureChannelSession(keyData);
|
secureChannel = new SecureChannelSession(keyData);
|
||||||
cmdSet.setSecureChannel(secureChannel);
|
cmdSet.setSecureChannel(secureChannel);
|
||||||
|
cmdSet.autoPair(sha256("123456789012".getBytes()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@AfterEach
|
@AfterEach
|
||||||
void tearDown() {
|
void tearDown() throws CardException {
|
||||||
|
resetAndSelectAndOpenSC();
|
||||||
|
ResponseAPDU response = cmdSet.verifyPIN("000000");
|
||||||
|
assertEquals(0x9000, response.getSW());
|
||||||
|
cmdSet.autoUnpair();
|
||||||
}
|
}
|
||||||
|
|
||||||
@AfterAll
|
@AfterAll
|
||||||
|
|
Loading…
Reference in New Issue