From c30fff7a6f948cb8305d17dd2c8ca4134e69f5a5 Mon Sep 17 00:00:00 2001 From: Lemmo Lavonen Date: Tue, 5 Oct 2021 19:58:27 +0300 Subject: [PATCH] Adds a constructor for convenience in setting up Comms. --- .../mobileauthapp/NFC/Comms.java | 208 +++++++++--------- 1 file changed, 105 insertions(+), 103 deletions(-) diff --git a/MobileAuthApp/app/src/main/java/com/tarkvaraprojekt/mobileauthapp/NFC/Comms.java b/MobileAuthApp/app/src/main/java/com/tarkvaraprojekt/mobileauthapp/NFC/Comms.java index 9ef2b37..c434a80 100644 --- a/MobileAuthApp/app/src/main/java/com/tarkvaraprojekt/mobileauthapp/NFC/Comms.java +++ b/MobileAuthApp/app/src/main/java/com/tarkvaraprojekt/mobileauthapp/NFC/Comms.java @@ -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 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,9 +114,10 @@ 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 + * @param data the data necessary for completing the APDU + * @param extra the missing length of the APDU being created * @return the complete APDU */ private byte[] createAPDU(byte[] template, byte[] data, int extra) { @@ -121,8 +128,9 @@ 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 + * @param last the last byte in the appended padding * @return the constructed key */ private byte[] createKey(byte[] unpadded, byte last) throws NoSuchAlgorithmException { @@ -134,8 +142,9 @@ 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 + * @param CAN the card access number provided by the user * @return the decrypted nonce */ 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 - * @param idCard the IsoDep link to the card - * @param CAN the card access number - * @return session keys if authentication succeeds, otherwise null + * + * @param CAN the card access number */ - 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 + * + * @param data the array containing the data to be processed + * @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 data the data to be encrypted * @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;