Adds a constructor for convenience in setting up Comms.

This commit is contained in:
Lemmo Lavonen 2021-10-05 19:58:27 +03:00
parent 08006dc1ac
commit c30fff7a6f

View File

@ -1,4 +1,4 @@
package com.tarkvaraprojekt.mobileauthapp.NFC; package ee.ut.math.lemmo.readnfctag;
import android.nfc.tech.IsoDep; import android.nfc.tech.IsoDep;
import android.util.Log; import android.util.Log;
@ -31,72 +31,78 @@ import javax.crypto.spec.SecretKeySpec;
public class Comms { public class Comms {
private static final byte[] master = { // select Main AID private static final byte[] master = { // select Main AID
(byte) 0x00, (byte) 0xA4, (byte) 0x04, (byte) 0x0C, (byte) 0x10, 0, -92, 4, 12, 16, -96, 0, 0, 0, 119, 1, 8, 0, 7, 0, 0, -2, 0, 0, 1, 0
(byte) 0xA0, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x77,
(byte) 0x01, (byte) 0x08, (byte) 0x00, (byte) 0x07, (byte) 0x00,
(byte) 0x00, (byte) 0xFE, (byte) 0x00, (byte) 0x00, (byte) 0x01,
(byte) 0x00,
};
private static final byte[] masterSec = { // select Main AID
(byte) 0x0C, (byte) 0xA4, (byte) 0x04, (byte) 0x0C, (byte) 0x2D,
(byte) 0x87, (byte) 0x21, (byte) 0x01,
}; };
private static final byte[] MSESetAT = { // manage security environment: set authentication template private static final byte[] MSESetAT = { // manage security environment: set authentication template
(byte) 0x00, (byte) 0x22, (byte) 0xC1, (byte) 0xA4, (byte) 0x0F, 0, 34, -63, -92, 15, -128, 10, 4, 0, 127, 0, 7, 2, 2, 4, 2, 4, -125, 1, 2, 0
(byte) 0x80, (byte) 0x0A, (byte) 0x04, (byte) 0x00, (byte) 0x7F,
(byte) 0x00, (byte) 0x07, (byte) 0x02, (byte) 0x02, (byte) 0x04,
(byte) 0x02, (byte) 0x04, (byte) 0x83, (byte) 0x01, (byte) 0x02,
(byte) 0x00,
}; };
private static final byte[] GAGetNonce = { // general authenticate: get nonce private static final byte[] GAGetNonce = { // general authenticate: get nonce
(byte) 0x10, (byte) 0x86, (byte) 0x00, (byte) 0x00, (byte) 0x02, 16, -122, 0, 0, 2, 124, 0, 0
(byte) 0x7C, (byte) 0x00, (byte) 0x00,
}; };
private static final byte[] GAMapNonceIncomplete = { private static final byte[] GAMapNonceIncomplete = {
(byte) 0x10, (byte) 0x86, (byte) 0x00, (byte) 0x00, (byte) 0x45, 16, -122, 0, 0, 69, 124, 67, -127, 65
(byte) 0x7C, (byte) 0x43, (byte) 0x81, (byte) 0x41,
}; };
private static final byte[] GAKeyAgreementIncomplete = { private static final byte[] GAKeyAgreementIncomplete = {
(byte) 0x10, (byte) 0x86, (byte) 0x00, (byte) 0x00, (byte) 0x45, 16, -122, 0, 0, 69, 124, 67, -125, 65
(byte) 0x7C, (byte) 0x43, (byte) 0x83, (byte) 0x41,
};
private static final byte[] dataForMACIncomplete = {
(byte) 0x7F, (byte) 0x49, (byte) 0x4F, (byte) 0x06, (byte) 0x0A,
(byte) 0x04, (byte) 0x00, (byte) 0x7F, (byte) 0x00, (byte) 0x07,
(byte) 0x02, (byte) 0x02, (byte) 0x04, (byte) 0x02, (byte) 0x04,
(byte) 0x86, (byte) 0x41,
}; };
private static final byte[] GAMutualAuthenticationIncomplete = { private static final byte[] GAMutualAuthenticationIncomplete = {
(byte) 0x00, (byte) 0x86, (byte) 0x00, (byte) 0x00, (byte) 0x0C, 0, -122, 0, 0, 12, 124, 10, -123, 8
(byte) 0x7C, (byte) 0x0A, (byte) 0x85, (byte) 0x08, };
private static final byte[] dataForMACIncomplete = {
127, 73, 79, 6, 10, 4, 0, 127, 0, 7, 2, 2, 4, 2, 4, -122, 65
};
private static final byte[] masterSec = {
12, -92, 4, 12, 45, -121, 33, 1
}; };
private static final byte[] personal = { // select personal data DF private static final byte[] personal = { // select personal data DF
(byte) 0x0C, (byte) 0xA4, (byte) 0x01, (byte) 0x0C, (byte) 0x1D, 12, -92, 1, 12, 29, -121, 17, 1
(byte) 0x87, (byte) 0x11, (byte) 0x01,
}; };
private static final byte[] read = { // read binary private static final byte[] read = { // read binary
(byte) 0x0C, (byte) 0xB0, (byte) 0x00, (byte) 0x00, (byte) 0x0D, 12, -80, 0, 0, 13, -105, 1, 0
(byte) 0x97, (byte) 0x01, (byte) 0x00,
}; };
private byte ssc; // Send sequence counter. Ok as long as the number of sent and received APDUs is <128. private IsoDep idCard;
private final byte[] keyEnc;
private final byte[] keyMAC;
private byte ssc; // Send sequence counter.
/**
* The constructor performs PACE and stores the session keys
*
* @param idCard link to the card
* @param CAN the card authentication number
*/
public Comms(IsoDep idCard, String CAN) throws IOException, NoSuchPaddingException, InvalidKeyException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException {
idCard.connect();
this.idCard = idCard;
long start = System.currentTimeMillis();
byte[][] keys = PACE(CAN);
Log.i("Pace duration", String.valueOf(System.currentTimeMillis() - start));
keyEnc = keys[0];
keyMAC = keys[1];
}
/** /**
* Calculates the message authentication code * Calculates the message authentication code
* @param keyMAC the cipher key *
* @param APDU the byte array on which the CMAC algorithm is performed * @param APDU the byte array on which the CMAC algorithm is performed
* @param keyMAC the key for performing CMAC
* @return MAC * @return MAC
*/ */
private byte[] getMAC(byte[] keyMAC, byte[] APDU) { private byte[] getMAC(byte[] APDU, byte[] keyMAC) {
BlockCipher blockCipher = new AESEngine(); BlockCipher blockCipher = new AESEngine();
CMac cmac = new CMac(blockCipher); CMac cmac = new CMac(blockCipher);
cmac.init(new KeyParameter(keyMAC)); cmac.init(new KeyParameter(keyMAC));
@ -108,9 +114,10 @@ public class Comms {
/** /**
* Creates an application protocol data unit * Creates an application protocol data unit
*
* @param template the byte array to be used as a template * @param template the byte array to be used as a template
* @param data the data necessary for completing the APDU * @param data the data necessary for completing the APDU
* @param extra the missing length of the APDU being created * @param extra the missing length of the APDU being created
* @return the complete APDU * @return the complete APDU
*/ */
private byte[] createAPDU(byte[] template, byte[] data, int extra) { private byte[] createAPDU(byte[] template, byte[] data, int extra) {
@ -121,8 +128,9 @@ public class Comms {
/** /**
* Creates a cipher key * Creates a cipher key
*
* @param unpadded the array to be used as the basis for the key * @param unpadded the array to be used as the basis for the key
* @param last the last byte in the appended padding * @param last the last byte in the appended padding
* @return the constructed key * @return the constructed key
*/ */
private byte[] createKey(byte[] unpadded, byte last) throws NoSuchAlgorithmException { private byte[] createKey(byte[] unpadded, byte last) throws NoSuchAlgorithmException {
@ -134,8 +142,9 @@ public class Comms {
/** /**
* Decrypts the nonce * Decrypts the nonce
*
* @param encryptedNonce the encrypted nonce received from the chip * @param encryptedNonce the encrypted nonce received from the chip
* @param CAN the card access number provided by the user * @param CAN the card access number provided by the user
* @return the decrypted nonce * @return the decrypted nonce
*/ */
private byte[] decryptNonce(byte[] encryptedNonce, String CAN) throws NoSuchPaddingException, InvalidKeyException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException { private byte[] decryptNonce(byte[] encryptedNonce, String CAN) throws NoSuchPaddingException, InvalidKeyException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException {
@ -147,28 +156,13 @@ public class Comms {
/** /**
* Attempts to use the PACE protocol to create a secure channel with an Estonian ID-card * Attempts to use the PACE protocol to create a secure channel with an Estonian ID-card
* @param idCard the IsoDep link to the card *
* @param CAN the card access number * @param CAN the card access number
* @return session keys if authentication succeeds, otherwise null
*/ */
public byte[][] PACE(IsoDep idCard, String CAN) throws IOException, NoSuchPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException { private byte[][] PACE(String CAN) throws IOException, NoSuchPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException {
byte[] response;
byte[] APDU;
byte[] decryptedNonce;
byte[] encodedSecret;
byte[] keyEnc;
byte[] keyMAC;
byte[] MAC;
BigInteger privateKey;
ECPoint publicKey;
ECPoint cardPublicKey;
ECPoint sharedSecret;
ECPoint mappedECBasePoint;
ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("secp256r1");
// select the ECC applet on the chip // select the ECC applet on the chip
response = idCard.transceive(master); byte[] response = idCard.transceive(master);
Log.i("Select applet", Hex.toHexString(response)); Log.i("Select applet", Hex.toHexString(response));
// initiate PACE // initiate PACE
@ -178,19 +172,20 @@ public class Comms {
// get nonce // get nonce
response = idCard.transceive(GAGetNonce); response = idCard.transceive(GAGetNonce);
Log.i("Get nonce", Hex.toHexString(response)); Log.i("Get nonce", Hex.toHexString(response));
decryptedNonce = decryptNonce(Arrays.copyOfRange(response, 4, response.length - 2), CAN); byte[] decryptedNonce = decryptNonce(Arrays.copyOfRange(response, 4, response.length - 2), CAN);
// generate an EC keypair and exchange public keys with the chip // generate an EC keypair and exchange public keys with the chip
privateKey = new BigInteger(255, new SecureRandom()).add(BigInteger.ONE); // should be in [1, spec.getN()-1], but this is good enough for this application ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("secp256r1");
publicKey = spec.getG().multiply(privateKey).normalize(); BigInteger privateKey = new BigInteger(255, new SecureRandom()).add(BigInteger.ONE); // should be in [1, spec.getN()-1], but this is good enough for this application
APDU = createAPDU(GAMapNonceIncomplete, publicKey.getEncoded(false), 66); ECPoint publicKey = spec.getG().multiply(privateKey).normalize();
byte[] APDU = createAPDU(GAMapNonceIncomplete, publicKey.getEncoded(false), 66);
response = idCard.transceive(APDU); response = idCard.transceive(APDU);
Log.i("Map nonce", Hex.toHexString(response)); Log.i("Map nonce", Hex.toHexString(response));
cardPublicKey = spec.getCurve().decodePoint(Arrays.copyOfRange(response, 4, 69)); ECPoint cardPublicKey = spec.getCurve().decodePoint(Arrays.copyOfRange(response, 4, 69));
// calculate the new base point, use it to generate a new keypair, and exchange public keys // calculate the new base point, use it to generate a new keypair, and exchange public keys
sharedSecret = cardPublicKey.multiply(privateKey); ECPoint sharedSecret = cardPublicKey.multiply(privateKey);
mappedECBasePoint = spec.getG().multiply(new BigInteger(1, decryptedNonce)).add(sharedSecret).normalize(); ECPoint mappedECBasePoint = spec.getG().multiply(new BigInteger(1, decryptedNonce)).add(sharedSecret).normalize();
privateKey = new BigInteger(255, new SecureRandom()).add(BigInteger.ONE); privateKey = new BigInteger(255, new SecureRandom()).add(BigInteger.ONE);
publicKey = mappedECBasePoint.multiply(privateKey).normalize(); publicKey = mappedECBasePoint.multiply(privateKey).normalize();
APDU = createAPDU(GAKeyAgreementIncomplete, publicKey.getEncoded(false), 66); APDU = createAPDU(GAKeyAgreementIncomplete, publicKey.getEncoded(false), 66);
@ -200,34 +195,36 @@ public class Comms {
// generate the session keys and exchange MACs to verify them // generate the session keys and exchange MACs to verify them
sharedSecret = cardPublicKey.multiply(privateKey).normalize(); sharedSecret = cardPublicKey.multiply(privateKey).normalize();
encodedSecret = sharedSecret.getAffineXCoord().getEncoded(); byte[] encodedSecret = sharedSecret.getAffineXCoord().getEncoded();
keyEnc = createKey(encodedSecret, (byte) 1); byte[] keyEnc = createKey(encodedSecret, (byte) 1);
keyMAC = createKey(encodedSecret, (byte) 2); byte[] keyMAC = createKey(encodedSecret, (byte) 2);
APDU = createAPDU(dataForMACIncomplete, cardPublicKey.getEncoded(false), 65); APDU = createAPDU(dataForMACIncomplete, cardPublicKey.getEncoded(false), 65);
MAC = getMAC(keyMAC, APDU); byte[] MAC = getMAC(APDU, keyMAC);
APDU = createAPDU(GAMutualAuthenticationIncomplete, MAC, 9); APDU = createAPDU(GAMutualAuthenticationIncomplete, MAC, 9);
response = idCard.transceive(APDU); response = idCard.transceive(APDU);
Log.i("Mutual authentication", Hex.toHexString(response)); Log.i("Mutual authentication", Hex.toHexString(response));
// if the chip-side verification fails, return null // if the chip-side verification fails, crash and burn
if (response.length == 2) return null; if (response.length == 2) throw new RuntimeException("Invalid CAN.");
// otherwise verify chip's MAC and return session keys // otherwise verify chip's MAC and return session keys
APDU = createAPDU(dataForMACIncomplete, publicKey.getEncoded(false), 65); APDU = createAPDU(dataForMACIncomplete, publicKey.getEncoded(false), 65);
MAC = getMAC(keyMAC, APDU); MAC = getMAC(APDU, keyMAC);
assert (Hex.toHexString(response, 4, 8).equals(Hex.toHexString(MAC))); if (!Hex.toHexString(response, 4, 8).equals(Hex.toHexString(MAC))) {
throw new RuntimeException("Could not verify chip's MAC."); // Should never happen.
}
return new byte[][]{keyEnc, keyMAC}; return new byte[][]{keyEnc, keyMAC};
} }
/** /**
* Encrypts or decrypts the APDU data * Encrypts or decrypts the APDU data
* @param data the array containing the data to be processed *
* @param keyEnc the cipher key * @param data the array containing the data to be processed
* @param mode indicates whether to en- or decrypt the data * @param mode indicates whether to en- or decrypt the data
* @return the result of encryption or decryption * @return the result of encryption or decryption
*/ */
private byte[] encryptDecryptData(byte[] data, byte[] keyEnc, int mode) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, InvalidAlgorithmParameterException { private byte[] encryptDecryptData(byte[] data, int mode) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, InvalidAlgorithmParameterException {
SecretKeySpec secretKeySpec = new SecretKeySpec(keyEnc, "AES"); SecretKeySpec secretKeySpec = new SecretKeySpec(keyEnc, "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec); cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
@ -239,13 +236,12 @@ public class Comms {
/** /**
* Constructs APDUs suitable for the secure channel. * Constructs APDUs suitable for the secure channel.
* @param data the data to be encrypted *
* @param keyEnc the encryption key * @param data the data to be encrypted
* @param keyMAC the MAC key
* @param incomplete the array to be used as a template * @param incomplete the array to be used as a template
* @return the constructed APDU * @return the constructed APDU
*/ */
private byte[] createSecureAPDU(byte[] data, byte[] keyEnc, byte[] keyMAC, byte[] incomplete) throws NoSuchPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException { private byte[] createSecureAPDU(byte[] data, byte[] incomplete) throws NoSuchPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException {
ssc++; ssc++;
byte[] encryptedData = new byte[0]; byte[] encryptedData = new byte[0];
@ -260,11 +256,11 @@ public class Comms {
if (data.length > 0) { // if the APDU has data, add padding and encrypt it if (data.length > 0) { // if the APDU has data, add padding and encrypt it
byte[] paddedData = Arrays.copyOf(data, length); byte[] paddedData = Arrays.copyOf(data, length);
paddedData[data.length] = -128; paddedData[data.length] = -128;
encryptedData = encryptDecryptData(paddedData, keyEnc, Cipher.ENCRYPT_MODE); encryptedData = encryptDecryptData(paddedData, Cipher.ENCRYPT_MODE);
System.arraycopy(encryptedData, 0, macData, 35, encryptedData.length); System.arraycopy(encryptedData, 0, macData, 35, encryptedData.length);
} }
macData[35 + encryptedData.length] = -128; macData[35 + encryptedData.length] = -128;
byte[] MAC = getMAC(keyMAC, macData); byte[] MAC = getMAC(macData, keyMAC);
// construct the APDU using the encrypted data and the MAC // construct the APDU using the encrypted data and the MAC
byte[] APDU = new byte[incomplete.length + encryptedData.length + MAC.length + 3]; byte[] APDU = new byte[incomplete.length + encryptedData.length + MAC.length + 3];
@ -281,39 +277,45 @@ public class Comms {
} }
/** /**
* Gets contents of the personal data dedicated file * Gets the contents of the personal data dedicated file
* @param idCard link to the ID-card *
* @param keyEnc the encryption key * @param FID the last bytes of file identifiers being requested
* @param keyMAC the MAC key * @return array containing the data strings
* @return an array containing personal data *
*/ */
public byte[][] readPersonalData(IsoDep idCard, byte[] keyEnc, byte[] keyMAC) throws NoSuchPaddingException, InvalidKeyException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException, IOException { public String[] readPersonalData(byte[] FID) throws NoSuchPaddingException, InvalidKeyException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException, IOException {
byte[][] personalData = new byte[8][]; String[] personalData = new String[FID.length];
byte[] data; byte[] data;
byte[] APDU; byte[] APDU;
byte[] response; byte[] response;
// select the personal data dedicated file // select the personal data dedicated file
data = new byte[]{80, 0}; // personal data DF FID data = new byte[]{80, 0}; // personal data DF FID
APDU = createSecureAPDU(data, keyEnc, keyMAC, personal); APDU = createSecureAPDU(data, personal);
response = idCard.transceive(APDU); response = idCard.transceive(APDU);
Log.i("Select personal data DF", Hex.toHexString(response)); Log.i("Select personal data DF", Hex.toHexString(response));
// select and read the first 8 elementary files in the DF // select and read the first 8 elementary files in the DF
for (byte i = 0; i < 8; i++) { for (int i = 0; i < FID.length; i++) {
data[1] = (byte) (i + 1); byte index = FID[i];
APDU = createSecureAPDU(data, keyEnc, keyMAC, personal); if (index > 15 || index < 1) throw new RuntimeException("Invalid personal data FID.");
response = idCard.transceive(APDU);
Log.i(String.format("Select EF 500%d", i + 1), Hex.toHexString(response));
APDU = createSecureAPDU(new byte[0], keyEnc, keyMAC, read); data[1] = index;
APDU = createSecureAPDU(data, personal);
response = idCard.transceive(APDU); response = idCard.transceive(APDU);
Log.i(String.format("Read binary EF 500%d", i + 1), Hex.toHexString(response)); Log.i(String.format("Select EF 500%d", index), Hex.toHexString(response));
APDU = createSecureAPDU(new byte[0], read);
response = idCard.transceive(APDU);
Log.i(String.format("Read binary EF 500%d", index), Hex.toHexString(response));
// store the decrypted datum // store the decrypted datum
personalData[i] = encryptDecryptData(Arrays.copyOfRange(response, 3, 19), keyEnc, Cipher.DECRYPT_MODE); byte[] raw = encryptDecryptData(Arrays.copyOfRange(response, 3, 19), Cipher.DECRYPT_MODE);
int indexOfTerminator = Hex.toHexString(raw).lastIndexOf("80") / 2;
personalData[i] = new String(Arrays.copyOfRange(raw, 0, indexOfTerminator));
} }
return personalData; return personalData;