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.util.Log;
@ -31,72 +31,78 @@ import javax.crypto.spec.SecretKeySpec;
public class Comms {
private static final byte[] master = { // select Main AID
(byte) 0x00, (byte) 0xA4, (byte) 0x04, (byte) 0x0C, (byte) 0x10,
(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,
0, -92, 4, 12, 16, -96, 0, 0, 0, 119, 1, 8, 0, 7, 0, 0, -2, 0, 0, 1, 0
};
private static final byte[] MSESetAT = { // manage security environment: set authentication template
(byte) 0x00, (byte) 0x22, (byte) 0xC1, (byte) 0xA4, (byte) 0x0F,
(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,
0, 34, -63, -92, 15, -128, 10, 4, 0, 127, 0, 7, 2, 2, 4, 2, 4, -125, 1, 2, 0
};
private static final byte[] GAGetNonce = { // general authenticate: get nonce
(byte) 0x10, (byte) 0x86, (byte) 0x00, (byte) 0x00, (byte) 0x02,
(byte) 0x7C, (byte) 0x00, (byte) 0x00,
16, -122, 0, 0, 2, 124, 0, 0
};
private static final byte[] GAMapNonceIncomplete = {
(byte) 0x10, (byte) 0x86, (byte) 0x00, (byte) 0x00, (byte) 0x45,
(byte) 0x7C, (byte) 0x43, (byte) 0x81, (byte) 0x41,
16, -122, 0, 0, 69, 124, 67, -127, 65
};
private static final byte[] GAKeyAgreementIncomplete = {
(byte) 0x10, (byte) 0x86, (byte) 0x00, (byte) 0x00, (byte) 0x45,
(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,
16, -122, 0, 0, 69, 124, 67, -125, 65
};
private static final byte[] GAMutualAuthenticationIncomplete = {
(byte) 0x00, (byte) 0x86, (byte) 0x00, (byte) 0x00, (byte) 0x0C,
(byte) 0x7C, (byte) 0x0A, (byte) 0x85, (byte) 0x08,
0, -122, 0, 0, 12, 124, 10, -123, 8
};
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
(byte) 0x0C, (byte) 0xA4, (byte) 0x01, (byte) 0x0C, (byte) 0x1D,
(byte) 0x87, (byte) 0x11, (byte) 0x01,
12, -92, 1, 12, 29, -121, 17, 1
};
private static final byte[] read = { // read binary
(byte) 0x0C, (byte) 0xB0, (byte) 0x00, (byte) 0x00, (byte) 0x0D,
(byte) 0x97, (byte) 0x01, (byte) 0x00,
12, -80, 0, 0, 13, -105, 1, 0
};
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
* @param keyMAC the cipher key
*
* @param APDU the byte array on which the CMAC algorithm is performed
* @param keyMAC the key for performing CMAC
* @return MAC
*/
private byte[] getMAC(byte[] keyMAC, byte[] APDU) {
private byte[] getMAC(byte[] APDU, byte[] keyMAC) {
BlockCipher blockCipher = new AESEngine();
CMac cmac = new CMac(blockCipher);
cmac.init(new KeyParameter(keyMAC));
@ -108,6 +114,7 @@ public class Comms {
/**
* Creates an application protocol data unit
*
* @param template the byte array to be used as a template
* @param data the data necessary for completing the APDU
* @param extra the missing length of the APDU being created
@ -121,6 +128,7 @@ public class Comms {
/**
* Creates a cipher key
*
* @param unpadded the array to be used as the basis for the key
* @param last the last byte in the appended padding
* @return the constructed key
@ -134,6 +142,7 @@ public class Comms {
/**
* Decrypts the nonce
*
* @param encryptedNonce the encrypted nonce received from the chip
* @param CAN the card access number provided by the user
* @return the decrypted nonce
@ -147,28 +156,13 @@ public class Comms {
/**
* 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
* @return session keys if authentication succeeds, otherwise null
*/
public byte[][] PACE(IsoDep idCard, 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");
private byte[][] PACE(String CAN) throws IOException, NoSuchPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException {
// select the ECC applet on the chip
response = idCard.transceive(master);
byte[] response = idCard.transceive(master);
Log.i("Select applet", Hex.toHexString(response));
// initiate PACE
@ -178,19 +172,20 @@ public class Comms {
// get nonce
response = idCard.transceive(GAGetNonce);
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
privateKey = new BigInteger(255, new SecureRandom()).add(BigInteger.ONE); // should be in [1, spec.getN()-1], but this is good enough for this application
publicKey = spec.getG().multiply(privateKey).normalize();
APDU = createAPDU(GAMapNonceIncomplete, publicKey.getEncoded(false), 66);
ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("secp256r1");
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
ECPoint publicKey = spec.getG().multiply(privateKey).normalize();
byte[] APDU = createAPDU(GAMapNonceIncomplete, publicKey.getEncoded(false), 66);
response = idCard.transceive(APDU);
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
sharedSecret = cardPublicKey.multiply(privateKey);
mappedECBasePoint = spec.getG().multiply(new BigInteger(1, decryptedNonce)).add(sharedSecret).normalize();
ECPoint sharedSecret = cardPublicKey.multiply(privateKey);
ECPoint mappedECBasePoint = spec.getG().multiply(new BigInteger(1, decryptedNonce)).add(sharedSecret).normalize();
privateKey = new BigInteger(255, new SecureRandom()).add(BigInteger.ONE);
publicKey = mappedECBasePoint.multiply(privateKey).normalize();
APDU = createAPDU(GAKeyAgreementIncomplete, publicKey.getEncoded(false), 66);
@ -200,34 +195,36 @@ public class Comms {
// generate the session keys and exchange MACs to verify them
sharedSecret = cardPublicKey.multiply(privateKey).normalize();
encodedSecret = sharedSecret.getAffineXCoord().getEncoded();
keyEnc = createKey(encodedSecret, (byte) 1);
keyMAC = createKey(encodedSecret, (byte) 2);
byte[] encodedSecret = sharedSecret.getAffineXCoord().getEncoded();
byte[] keyEnc = createKey(encodedSecret, (byte) 1);
byte[] keyMAC = createKey(encodedSecret, (byte) 2);
APDU = createAPDU(dataForMACIncomplete, cardPublicKey.getEncoded(false), 65);
MAC = getMAC(keyMAC, APDU);
byte[] MAC = getMAC(APDU, keyMAC);
APDU = createAPDU(GAMutualAuthenticationIncomplete, MAC, 9);
response = idCard.transceive(APDU);
Log.i("Mutual authentication", Hex.toHexString(response));
// if the chip-side verification fails, return null
if (response.length == 2) return null;
// if the chip-side verification fails, crash and burn
if (response.length == 2) throw new RuntimeException("Invalid CAN.");
// otherwise verify chip's MAC and return session keys
APDU = createAPDU(dataForMACIncomplete, publicKey.getEncoded(false), 65);
MAC = getMAC(keyMAC, APDU);
assert (Hex.toHexString(response, 4, 8).equals(Hex.toHexString(MAC)));
MAC = getMAC(APDU, keyMAC);
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};
}
/**
* Encrypts or decrypts the APDU data
*
* @param data the array containing the data to be processed
* @param keyEnc the cipher key
* @param mode indicates whether to en- or decrypt the data
* @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");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
@ -239,13 +236,12 @@ public class Comms {
/**
* Constructs APDUs suitable for the secure channel.
*
* @param data the data to be encrypted
* @param keyEnc the encryption key
* @param keyMAC the MAC key
* @param incomplete the array to be used as a template
* @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++;
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
byte[] paddedData = Arrays.copyOf(data, length);
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);
}
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
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
* @param idCard link to the ID-card
* @param keyEnc the encryption key
* @param keyMAC the MAC key
* @return an array containing personal data
* Gets the contents of the personal data dedicated file
*
* @param FID the last bytes of file identifiers being requested
* @return array containing the data strings
*
*/
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[] APDU;
byte[] response;
// select the personal data dedicated file
data = new byte[]{80, 0}; // personal data DF FID
APDU = createSecureAPDU(data, keyEnc, keyMAC, personal);
APDU = createSecureAPDU(data, personal);
response = idCard.transceive(APDU);
Log.i("Select personal data DF", Hex.toHexString(response));
// 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);
APDU = createSecureAPDU(data, keyEnc, keyMAC, personal);
response = idCard.transceive(APDU);
Log.i(String.format("Select EF 500%d", i + 1), Hex.toHexString(response));
byte index = FID[i];
if (index > 15 || index < 1) throw new RuntimeException("Invalid personal data FID.");
APDU = createSecureAPDU(new byte[0], keyEnc, keyMAC, read);
data[1] = index;
APDU = createSecureAPDU(data, personal);
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
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;