From d2ad8920a11589ffecfa20927eff96f41e159094 Mon Sep 17 00:00:00 2001 From: Lemmo Lavonen Date: Tue, 12 Oct 2021 00:34:06 +0300 Subject: [PATCH 1/8] Add a method for verifying PIN1. --- .../mobileauthapp/NFC/Comms.java | 130 +++++++++++++----- 1 file changed, 92 insertions(+), 38 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 a4d184b..a6782d4 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 @@ -21,6 +21,7 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.Arrays; +import java.util.Locale; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; @@ -30,7 +31,7 @@ import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; public class Comms { - private static final byte[] master = { // select Main AID + private static final byte[] selectMain = { // select IAS-ECC 0, -92, 4, 12, 16, -96, 0, 0, 0, 119, 1, 8, 0, 7, 0, 0, -2, 0, 0, 1, 0 }; @@ -58,19 +59,31 @@ public class Comms { 127, 73, 79, 6, 10, 4, 0, 127, 0, 7, 2, 2, 4, 2, 4, -122, 65 }; - private static final byte[] masterSec = { + private static final byte[] selectAppSecure = { 12, -92, 4, 12, 45, -121, 33, 1 }; - private static final byte[] personal = { // select personal data DF + private static final byte[] IASECCAID = { // Identification Authentication Signature - European Citizen Card Application Identifier + -96, 0, 0, 0, 119, 1, 8, 0, 7, 0, 0, -2, 0, 0, 1, 0 + }; + + private static final byte[] QSCDAID = { // Qualified Signature Creation Device Application Identifier + 81, 83, 67, 68, 32, 65, 112, 112, 108, 105, 99, 97, 116, 105, 111, 110 + }; + + private static final byte[] selectFile = { // 12, -92, 1, 12, 29, -121, 17, 1 }; - private static final byte[] read = { // read binary + private static final byte[] read = { 12, -80, 0, 0, 13, -105, 1, 0 }; - private IsoDep idCard; + private static final byte[] verifyPIN1 = { + 12, 32, 0, 1, 29, -121, 17, 1 + }; + + private final IsoDep idCard; private final byte[] keyEnc; private final byte[] keyMAC; private byte ssc; // Send sequence counter. @@ -95,10 +108,6 @@ public class Comms { keyMAC = keys[1]; } - public byte[] getAuthenticationCertificate() { - return new byte[0]; - } - /** * Calculates the message authentication code * @@ -165,8 +174,8 @@ public class Comms { */ private byte[][] PACE(String CAN) throws IOException, NoSuchPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException { - // select the ECC applet on the chip - byte[] response = idCard.transceive(master); + // select the IAS-ECC application on the chip + byte[] response = idCard.transceive(selectMain); Log.i("Select applet", Hex.toHexString(response)); // initiate PACE @@ -221,6 +230,22 @@ public class Comms { } + /** + * Selects a file and reads its contents + * + * @param FID file identifier of the required file + * @return decrypted file contents + */ + private byte[] readFileByFID(byte[] FID) throws NoSuchPaddingException, InvalidKeyException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException, IOException { + byte [] APDU = createSecureAPDU(FID, selectFile); + byte [] response = idCard.transceive(APDU); + Log.i(String.format("Select FID %s", Hex.toHexString(FID)), Hex.toHexString(response)); + APDU = createSecureAPDU(new byte[0], read); + response = idCard.transceive(APDU); + Log.i("Read binary", Hex.toHexString(response)); + return encryptDecryptData(Arrays.copyOfRange(response, 3, 19), Cipher.DECRYPT_MODE); + } + /** * Encrypts or decrypts the APDU data * @@ -267,8 +292,7 @@ public class Comms { 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]; - System.arraycopy(incomplete, 0, APDU, 0, incomplete.length); + byte[] APDU = Arrays.copyOf(incomplete, incomplete.length + encryptedData.length + MAC.length + 3); if (encryptedData.length > 0) { System.arraycopy(encryptedData, 0, APDU, incomplete.length, encryptedData.length); } @@ -280,49 +304,79 @@ public class Comms { } + /** + * Selects the IAS ECC application, which provides the PKI functionalities, after a successful PACE. + */ + private void selectIASECCApplication() throws NoSuchPaddingException, InvalidKeyException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException, IOException { + byte[] APDU = createSecureAPDU(IASECCAID, selectAppSecure); + byte[] response = idCard.transceive(APDU); + Log.i("Select the main application", Hex.toHexString(response)); + if (!Hex.toHexString(response, response.length - 2, 2).equals("9000")) { + throw new RuntimeException("Could not select IAS-ECC."); // *Should* never happen. + } + } + /** * 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 + * @param lastBytes the last bytes of the personal data file identifiers (0 < x < 16) + * @return array containing the corresponding data strings * */ - public String[] readPersonalData(byte[] FID) throws NoSuchPaddingException, InvalidKeyException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException, IOException { + public String[] readPersonalData(byte[] lastBytes) throws NoSuchPaddingException, InvalidKeyException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException, IOException { - String[] personalData = new String[FID.length]; - byte[] data; - byte[] APDU; - byte[] response; + String[] personalData = new String[lastBytes.length]; + int stringIndex = 0; + + // select the application + selectIASECCApplication(); // select the personal data dedicated file - data = new byte[]{80, 0}; // personal data DF FID - APDU = createSecureAPDU(data, personal); - response = idCard.transceive(APDU); + byte[] FID = new byte[]{80, 0}; // personal data dedicated file FID + byte[] APDU = createSecureAPDU(FID, selectFile); + byte[] 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 (int i = 0; i < FID.length; i++) { + // select and read the personal data elementary files + for (byte index : lastBytes) { - byte index = FID[i]; if (index > 15 || index < 1) throw new RuntimeException("Invalid personal data FID."); - - data[1] = index; - APDU = createSecureAPDU(data, personal); - response = idCard.transceive(APDU); - 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)); + FID[1] = index; // store the decrypted datum - 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)); + response = readFileByFID(FID); + int indexOfTerminator = Hex.toHexString(response).lastIndexOf("80") / 2; + personalData[stringIndex++] = new String(Arrays.copyOfRange(response, 0, indexOfTerminator)); } return personalData; } + + /** + * Attempts to verify the user-provided PIN1 + * + * @param PIN1 byte array containing the PIN1 + */ + private void verifyPIN1(byte[] PIN1) throws NoSuchPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException, IOException { + selectIASECCApplication(); + byte[] paddedPIN1 = new byte[]{-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}; + System.arraycopy(PIN1, 0, paddedPIN1, 0, PIN1.length); + byte[] APDU = createSecureAPDU(paddedPIN1, verifyPIN1); + byte[] response = idCard.transceive(APDU); + Log.i("PIN1 verification", Hex.toHexString(response)); + String sw1sw2 = Hex.toHexString(response, response.length - 2, 2); + if (!sw1sw2.equals("9000")) { + if (sw1sw2.equals("6983")) throw new RuntimeException("Invalid PIN1. Authentication method blocked."); + else throw new RuntimeException(String.format("Invalid PIN1. %c attempt%s left.", sw1sw2.charAt(sw1sw2.length() - 1), sw1sw2.endsWith("1") ? "" : "s")); + } + } + + public byte[] getAuthenticationCertificate(String PIN1) throws NoSuchPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException, IOException { + + return new byte[0]; + + } + } From 1c8a606376a1b3c80f1be9ab36a10b1237de49fc Mon Sep 17 00:00:00 2001 From: Lemmo Lavonen Date: Tue, 12 Oct 2021 00:36:08 +0300 Subject: [PATCH 2/8] Add a method for getting the authentication certificate (WIP). --- .../mobileauthapp/NFC/Comms.java | 41 +++++++++++++++++-- 1 file changed, 38 insertions(+), 3 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 a6782d4..da09127 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 @@ -373,10 +373,45 @@ public class Comms { } } - public byte[] getAuthenticationCertificate(String PIN1) throws NoSuchPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException, IOException { + /** + * Retrieves the authentication certificate from the chip + * + * @return authentication certificate + */ + public byte[] getAuthenticationCertificate() throws NoSuchPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException, IOException { - return new byte[0]; + selectIASECCApplication(); + + byte[] APDU = createSecureAPDU(new byte[]{-83, -15}, selectFile); + byte[] response = idCard.transceive(APDU); + Log.i("Select AWP Application", Hex.toHexString(response)); + + APDU = createSecureAPDU(new byte[]{52, 1}, selectFile); + response = idCard.transceive(APDU); + Log.i("Select certificate", Hex.toHexString(response)); + + byte[] responses = new byte[0]; + byte[] readCert = Arrays.copyOf(read, read.length); + for (int i = 0; i < 5; i++) { + + readCert[2] = (byte) i; + APDU = createSecureAPDU(new byte[0], readCert); + response = idCard.transceive(APDU); + Log.i("Read certificate", Hex.toHexString(response)); + + if (!Hex.toHexString(response).substring(response.length * 2 - 4).equals("6b00")) { + byte[] decrypted = encryptDecryptData(Arrays.copyOfRange(response, 4, 244), Cipher.DECRYPT_MODE); + responses = Arrays.copyOf(responses, responses.length + decrypted.length); + System.arraycopy(decrypted, 0, responses, responses.length - decrypted.length, decrypted.length); + } else { + break; + } + + } + + Log.i("Certificate", new String(responses, StandardCharsets.UTF_8)); + + return responses; } - } From 25c01803cb1f043834fba71fc4deac9a977eac78 Mon Sep 17 00:00:00 2001 From: Lemmo Lavonen Date: Tue, 12 Oct 2021 03:04:00 +0300 Subject: [PATCH 3/8] auth cert bug fix progress --- .../mobileauthapp/NFC/Comms.java | 33 +------------------ 1 file changed, 1 insertion(+), 32 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 da09127..3c65287 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 @@ -380,38 +380,7 @@ public class Comms { */ public byte[] getAuthenticationCertificate() throws NoSuchPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException, IOException { - selectIASECCApplication(); - - byte[] APDU = createSecureAPDU(new byte[]{-83, -15}, selectFile); - byte[] response = idCard.transceive(APDU); - Log.i("Select AWP Application", Hex.toHexString(response)); - - APDU = createSecureAPDU(new byte[]{52, 1}, selectFile); - response = idCard.transceive(APDU); - Log.i("Select certificate", Hex.toHexString(response)); - - byte[] responses = new byte[0]; - byte[] readCert = Arrays.copyOf(read, read.length); - for (int i = 0; i < 5; i++) { - - readCert[2] = (byte) i; - APDU = createSecureAPDU(new byte[0], readCert); - response = idCard.transceive(APDU); - Log.i("Read certificate", Hex.toHexString(response)); - - if (!Hex.toHexString(response).substring(response.length * 2 - 4).equals("6b00")) { - byte[] decrypted = encryptDecryptData(Arrays.copyOfRange(response, 4, 244), Cipher.DECRYPT_MODE); - responses = Arrays.copyOf(responses, responses.length + decrypted.length); - System.arraycopy(decrypted, 0, responses, responses.length - decrypted.length, decrypted.length); - } else { - break; - } - - } - - Log.i("Certificate", new String(responses, StandardCharsets.UTF_8)); - - return responses; + return new byte[0]; } } From 9c48cc9c1a523ce4d3ba7a91663ab0a9747a83d9 Mon Sep 17 00:00:00 2001 From: Lemmo Lavonen Date: Tue, 12 Oct 2021 12:18:06 +0300 Subject: [PATCH 4/8] Fix authentication certificate retrieval. --- .../mobileauthapp/NFC/Comms.java | 53 ++++++++++++++++++- 1 file changed, 51 insertions(+), 2 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 3c65287..7ddde80 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 @@ -12,6 +12,7 @@ import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec; import org.bouncycastle.math.ec.ECPoint; import org.bouncycastle.util.encoders.Hex; +import java.io.ByteArrayInputStream; import java.io.IOException; import java.math.BigInteger; import java.nio.charset.StandardCharsets; @@ -20,6 +21,9 @@ import java.security.InvalidKeyException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.Locale; @@ -378,9 +382,54 @@ public class Comms { * * @return authentication certificate */ - public byte[] getAuthenticationCertificate() throws NoSuchPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException, IOException { + public byte[] getAuthenticationCertificate() throws NoSuchPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException, IOException, CertificateException { - return new byte[0]; + selectIASECCApplication(); + + byte[] APDU = createSecureAPDU(new byte[]{-83, -15}, selectFile); + byte[] response = idCard.transceive(APDU); + Log.i("Select AWP Application", Hex.toHexString(response)); + + APDU = createSecureAPDU(new byte[]{52, 1}, selectFile); + response = idCard.transceive(APDU); + Log.i("Select certificate", Hex.toHexString(response)); + + byte[] responses = new byte[0]; + byte[] readCert = Arrays.copyOf(read, read.length); + int indexOfTerminator = 0; + for (int i = 0; i < 9; i++) { + + readCert[2] = (byte) ((byte) i / 2); + readCert[3] = (byte) ((byte) (i % 2) * 25); + APDU = createSecureAPDU(new byte[0], readCert); + response = idCard.transceive(APDU); + Log.i("Read certificate part " + i, Hex.toHexString(response)); + + if (!Hex.toHexString(response).substring(response.length * 2 - 4).equals("6b00")) { + byte[] decrypted = encryptDecryptData(Arrays.copyOfRange(response, 4, 244), Cipher.DECRYPT_MODE); + if (i % 2 == 0) { + indexOfTerminator = Hex.toHexString(decrypted).lastIndexOf("80") / 2; + responses = Arrays.copyOf(responses, responses.length + indexOfTerminator); + System.arraycopy(decrypted, 0, responses, responses.length - indexOfTerminator, indexOfTerminator); +// Log.i("Partial certificate #1", new String(Arrays.copyOf(decrypted, indexOfTerminator), StandardCharsets.ISO_8859_1)); + } else { + int newIndexOfTerminator = Hex.toHexString(decrypted).lastIndexOf("80") / 2; + responses = Arrays.copyOf(responses, responses.length + 25 - indexOfTerminator + newIndexOfTerminator); + System.arraycopy(decrypted, 0, responses, responses.length - newIndexOfTerminator, newIndexOfTerminator); +// Log.i("Partial certificate #2", new String(Arrays.copyOfRange(decrypted, newIndexOfTerminator - 25, newIndexOfTerminator), StandardCharsets.ISO_8859_1)); + } + } else { + break; + } + + } + + CertificateFactory certificateFactory = CertificateFactory.getInstance("X509"); + X509Certificate certificate = (X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(responses)); + + Log.i("Certificate subject", certificate.getSubjectX500Principal().getName()); + + return responses; } } From 29c7ecfa12fb4865526f71e7cea00d6dbfb55f66 Mon Sep 17 00:00:00 2001 From: Lemmo Lavonen Date: Wed, 13 Oct 2021 02:27:19 +0300 Subject: [PATCH 5/8] Refactor the method for authentication certificate retrieval. --- .../mobileauthapp/NFC/Comms.java | 84 +++++++++---------- 1 file changed, 40 insertions(+), 44 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 7ddde80..f548f93 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 @@ -12,7 +12,6 @@ import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec; import org.bouncycastle.math.ec.ECPoint; import org.bouncycastle.util.encoders.Hex; -import java.io.ByteArrayInputStream; import java.io.IOException; import java.math.BigInteger; import java.nio.charset.StandardCharsets; @@ -22,10 +21,7 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.security.cert.CertificateException; -import java.security.cert.CertificateFactory; -import java.security.cert.X509Certificate; import java.util.Arrays; -import java.util.Locale; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; @@ -101,13 +97,8 @@ public class Comms { 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]; } @@ -228,7 +219,7 @@ public class Comms { APDU = createAPDU(dataForMACIncomplete, publicKey.getEncoded(false), 65); 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. + throw new RuntimeException("Could not verify chip's MAC."); // *Should* never happen. } return new byte[][]{keyEnc, keyMAC}; @@ -302,7 +293,6 @@ public class Comms { } System.arraycopy(new byte[]{-114, 8}, 0, APDU, incomplete.length + encryptedData.length, 2); // MAC is encapsulated using the tag 0x8E System.arraycopy(MAC, 0, APDU, incomplete.length + encryptedData.length + 2, MAC.length); - ssc++; return APDU; @@ -325,7 +315,6 @@ public class Comms { * * @param lastBytes the last bytes of the personal data file identifiers (0 < x < 16) * @return array containing the corresponding data strings - * */ public String[] readPersonalData(byte[] lastBytes) throws NoSuchPaddingException, InvalidKeyException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException, IOException { @@ -353,7 +342,6 @@ public class Comms { personalData[stringIndex++] = new String(Arrays.copyOfRange(response, 0, indexOfTerminator)); } - return personalData; } @@ -364,16 +352,23 @@ public class Comms { * @param PIN1 byte array containing the PIN1 */ private void verifyPIN1(byte[] PIN1) throws NoSuchPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException, IOException { + selectIASECCApplication(); + byte[] paddedPIN1 = new byte[]{-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}; System.arraycopy(PIN1, 0, paddedPIN1, 0, PIN1.length); byte[] APDU = createSecureAPDU(paddedPIN1, verifyPIN1); byte[] response = idCard.transceive(APDU); Log.i("PIN1 verification", Hex.toHexString(response)); - String sw1sw2 = Hex.toHexString(response, response.length - 2, 2); - if (!sw1sw2.equals("9000")) { - if (sw1sw2.equals("6983")) throw new RuntimeException("Invalid PIN1. Authentication method blocked."); - else throw new RuntimeException(String.format("Invalid PIN1. %c attempt%s left.", sw1sw2.charAt(sw1sw2.length() - 1), sw1sw2.endsWith("1") ? "" : "s")); + + byte sw1 = response[response.length - 2]; + byte sw2 = response[response.length - 1]; + if (sw1 != -112 || sw2 != 0) { + if (sw1 == 105 && sw2 == -125) { + throw new RuntimeException("Invalid PIN1. Authentication method blocked."); + } else { + throw new RuntimeException(String.format("Invalid PIN1. %d attempt%s left.", sw2 + 64, sw2 == -63 ? "" : "s")); + } } } @@ -388,48 +383,49 @@ public class Comms { byte[] APDU = createSecureAPDU(new byte[]{-83, -15}, selectFile); byte[] response = idCard.transceive(APDU); - Log.i("Select AWP Application", Hex.toHexString(response)); + Log.i("Select AWP application", Hex.toHexString(response)); APDU = createSecureAPDU(new byte[]{52, 1}, selectFile); response = idCard.transceive(APDU); Log.i("Select certificate", Hex.toHexString(response)); - byte[] responses = new byte[0]; + byte[] certificate = new byte[0]; byte[] readCert = Arrays.copyOf(read, read.length); - int indexOfTerminator = 0; - for (int i = 0; i < 9; i++) { + // Construct the certificate byte array n=indexOfTerminator bytes at a time + for (int i = 0; i < 16; i++) { - readCert[2] = (byte) ((byte) i / 2); - readCert[3] = (byte) ((byte) (i % 2) * 25); + // Set the P1/P2 values to incrementally read the certificate + readCert[2] = (byte) (certificate.length / 256); + readCert[3] = (byte) (certificate.length % 256); APDU = createSecureAPDU(new byte[0], readCert); response = idCard.transceive(APDU); - Log.i("Read certificate part " + i, Hex.toHexString(response)); + Log.i("Read certificate", Hex.toHexString(response)); - if (!Hex.toHexString(response).substring(response.length * 2 - 4).equals("6b00")) { - byte[] decrypted = encryptDecryptData(Arrays.copyOfRange(response, 4, 244), Cipher.DECRYPT_MODE); - if (i % 2 == 0) { - indexOfTerminator = Hex.toHexString(decrypted).lastIndexOf("80") / 2; - responses = Arrays.copyOf(responses, responses.length + indexOfTerminator); - System.arraycopy(decrypted, 0, responses, responses.length - indexOfTerminator, indexOfTerminator); -// Log.i("Partial certificate #1", new String(Arrays.copyOf(decrypted, indexOfTerminator), StandardCharsets.ISO_8859_1)); - } else { - int newIndexOfTerminator = Hex.toHexString(decrypted).lastIndexOf("80") / 2; - responses = Arrays.copyOf(responses, responses.length + 25 - indexOfTerminator + newIndexOfTerminator); - System.arraycopy(decrypted, 0, responses, responses.length - newIndexOfTerminator, newIndexOfTerminator); -// Log.i("Partial certificate #2", new String(Arrays.copyOfRange(decrypted, newIndexOfTerminator - 25, newIndexOfTerminator), StandardCharsets.ISO_8859_1)); - } - } else { - break; + byte sw1 = response[response.length - 2]; + byte sw2 = response[response.length - 1]; + if (sw1 == 107 && sw2 == 0) { + throw new RuntimeException("Wrong read parameters."); } + // Set the range containing a portion of the certificate and decrypt it + int start = response[2] == 1 ? 3 : 4; + int end = start + (response[start - 2] + 256) % 256 - 1; + byte[] decrypted = encryptDecryptData(Arrays.copyOfRange(response, start, end), Cipher.DECRYPT_MODE); + int indexOfTerminator = Hex.toHexString(decrypted).lastIndexOf("80") / 2; + certificate = Arrays.copyOf(certificate, certificate.length + indexOfTerminator); + System.arraycopy(decrypted, 0, certificate, certificate.length - indexOfTerminator, indexOfTerminator); + + if (sw1 == -112 && sw2 == 0) { + break; + } } - CertificateFactory certificateFactory = CertificateFactory.getInstance("X509"); - X509Certificate certificate = (X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(responses)); + // For debugging, ascertain that the byte array corresponds to a valid certificate +// CertificateFactory certificateFactory = CertificateFactory.getInstance("X509"); +// X509Certificate x509Certificate = (X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(certificate)); +// Log.i("Certificate subject", x509Certificate.getSubjectX500Principal().getName()); - Log.i("Certificate subject", certificate.getSubjectX500Principal().getName()); - - return responses; + return certificate; } } From ef7015abb804928003decb138660d7be6298c2d8 Mon Sep 17 00:00:00 2001 From: Lemmo Lavonen Date: Thu, 14 Oct 2021 03:58:49 +0300 Subject: [PATCH 6/8] Refactor (generalise selecting and reading a file, PIN verification and certificate retrieval). --- .../mobileauthapp/NFC/Comms.java | 110 ++++++++++-------- .../mobileauthapp/auth/Authenticator.kt | 2 +- MobileAuthApp/build.gradle | 2 +- 3 files changed, 62 insertions(+), 52 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 f548f93..5fe496f 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 @@ -20,7 +20,6 @@ import java.security.InvalidKeyException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; -import java.security.cert.CertificateException; import java.util.Arrays; import javax.crypto.BadPaddingException; @@ -31,7 +30,7 @@ import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; public class Comms { - private static final byte[] selectMain = { // select IAS-ECC + private static final byte[] selectMaster = { // select IAS-ECC 0, -92, 4, 12, 16, -96, 0, 0, 0, 119, 1, 8, 0, 7, 0, 0, -2, 0, 0, 1, 0 }; @@ -59,7 +58,7 @@ public class Comms { 127, 73, 79, 6, 10, 4, 0, 127, 0, 7, 2, 2, 4, 2, 4, -122, 65 }; - private static final byte[] selectAppSecure = { + private static final byte[] selectMasterSecure = { 12, -92, 4, 12, 45, -121, 33, 1 }; @@ -67,10 +66,6 @@ public class Comms { -96, 0, 0, 0, 119, 1, 8, 0, 7, 0, 0, -2, 0, 0, 1, 0 }; - private static final byte[] QSCDAID = { // Qualified Signature Creation Device Application Identifier - 81, 83, 67, 68, 32, 65, 112, 112, 108, 105, 99, 97, 116, 105, 111, 110 - }; - private static final byte[] selectFile = { // 12, -92, 1, 12, 29, -121, 17, 1 }; @@ -83,6 +78,18 @@ public class Comms { 12, 32, 0, 1, 29, -121, 17, 1 }; + private static final byte[] verifyPIN2 = { + 12, 32, 0, -123, 29, -121, 17, 1 + }; + + private static final byte[] IASECCFID = {63, 0}; + private static final byte[] personalDF = {80, 0}; + private static final byte[] AWP = {-83, -15}; + private static final byte[] QSCD = {-83, -14}; + private static final byte[] authCert = {52, 1}; + private static final byte[] signCert = {52, 31}; + + private final IsoDep idCard; private final byte[] keyEnc; private final byte[] keyMAC; @@ -170,8 +177,8 @@ public class Comms { private byte[][] PACE(String CAN) throws IOException, NoSuchPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException { // select the IAS-ECC application on the chip - byte[] response = idCard.transceive(selectMain); - Log.i("Select applet", Hex.toHexString(response)); + byte[] response = idCard.transceive(selectMaster); + Log.i("Select the master application", Hex.toHexString(response)); // initiate PACE response = idCard.transceive(MSESetAT); @@ -229,15 +236,17 @@ public class Comms { * Selects a file and reads its contents * * @param FID file identifier of the required file + * @param info string for logging * @return decrypted file contents */ - private byte[] readFileByFID(byte[] FID) throws NoSuchPaddingException, InvalidKeyException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException, IOException { - byte [] APDU = createSecureAPDU(FID, selectFile); - byte [] response = idCard.transceive(APDU); - Log.i(String.format("Select FID %s", Hex.toHexString(FID)), Hex.toHexString(response)); - APDU = createSecureAPDU(new byte[0], read); - response = idCard.transceive(APDU); + private byte[] readFile(byte[] FID, String info) throws NoSuchPaddingException, InvalidKeyException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException, IOException { + selectFile(FID, info); + byte[] APDU = createSecureAPDU(new byte[0], read); + byte[] response = idCard.transceive(APDU); Log.i("Read binary", Hex.toHexString(response)); + if (response[response.length - 2] != -112 || response[response.length - 1] != 0) { + throw new RuntimeException(String.format("Could not read %s", info)); + } return encryptDecryptData(Arrays.copyOfRange(response, 3, 19), Cipher.DECRYPT_MODE); } @@ -299,14 +308,15 @@ public class Comms { } /** - * Selects the IAS ECC application, which provides the PKI functionalities, after a successful PACE. + * Selects a FILE by its identifier + * */ - private void selectIASECCApplication() throws NoSuchPaddingException, InvalidKeyException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException, IOException { - byte[] APDU = createSecureAPDU(IASECCAID, selectAppSecure); + private void selectFile(byte[] FID, String info) throws NoSuchPaddingException, InvalidKeyException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException, IOException { + byte[] APDU = createSecureAPDU(FID, selectFile); byte[] response = idCard.transceive(APDU); - Log.i("Select the main application", Hex.toHexString(response)); - if (!Hex.toHexString(response, response.length - 2, 2).equals("9000")) { - throw new RuntimeException("Could not select IAS-ECC."); // *Should* never happen. + Log.i(String.format("Select %s", info), Hex.toHexString(response)); + if (response[response.length - 2] != -112 || response[response.length - 1] != 0) { + throw new RuntimeException(String.format("Could not select %s", info)); } } @@ -321,15 +331,13 @@ public class Comms { String[] personalData = new String[lastBytes.length]; int stringIndex = 0; - // select the application - selectIASECCApplication(); + // select the master application + selectFile(IASECCFID, "the master application"); // select the personal data dedicated file - byte[] FID = new byte[]{80, 0}; // personal data dedicated file FID - byte[] APDU = createSecureAPDU(FID, selectFile); - byte[] response = idCard.transceive(APDU); - Log.i("Select personal data DF", Hex.toHexString(response)); + selectFile(personalDF, "the personal data DF"); + byte[] FID = Arrays.copyOf(personalDF, personalDF.length); // select and read the personal data elementary files for (byte index : lastBytes) { @@ -337,7 +345,7 @@ public class Comms { FID[1] = index; // store the decrypted datum - response = readFileByFID(FID); + byte[] response = readFile(FID, "a personal data EF"); int indexOfTerminator = Hex.toHexString(response).lastIndexOf("80") / 2; personalData[stringIndex++] = new String(Arrays.copyOfRange(response, 0, indexOfTerminator)); @@ -347,25 +355,30 @@ public class Comms { } /** - * Attempts to verify the user-provided PIN1 + * Attempts to verify the selected PIN * - * @param PIN1 byte array containing the PIN1 + * @param PIN user-provided PIN + * @param oneOrTwo true for PIN1, false for PIN2 */ - private void verifyPIN1(byte[] PIN1) throws NoSuchPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException, IOException { + public void verifyPIN(byte[] PIN, boolean oneOrTwo) throws NoSuchPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException, IOException { - selectIASECCApplication(); + selectFile(IASECCFID, "the master application"); + if (!oneOrTwo) { + selectFile(QSCD, "the application"); + } + // pad the PIN and use the chip for verification byte[] paddedPIN1 = new byte[]{-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}; - System.arraycopy(PIN1, 0, paddedPIN1, 0, PIN1.length); - byte[] APDU = createSecureAPDU(paddedPIN1, verifyPIN1); + System.arraycopy(PIN, 0, paddedPIN1, 0, PIN.length); + byte[] APDU = createSecureAPDU(paddedPIN1, oneOrTwo ? verifyPIN1 : verifyPIN2); byte[] response = idCard.transceive(APDU); - Log.i("PIN1 verification", Hex.toHexString(response)); + Log.i(String.format("PIN%d verification", oneOrTwo ? 1 : 2), Hex.toHexString(response)); byte sw1 = response[response.length - 2]; byte sw2 = response[response.length - 1]; if (sw1 != -112 || sw2 != 0) { if (sw1 == 105 && sw2 == -125) { - throw new RuntimeException("Invalid PIN1. Authentication method blocked."); + throw new RuntimeException("Invalid PIN. Authentication method blocked."); } else { throw new RuntimeException(String.format("Invalid PIN1. %d attempt%s left.", sw2 + 64, sw2 == -63 ? "" : "s")); } @@ -373,21 +386,18 @@ public class Comms { } /** - * Retrieves the authentication certificate from the chip + * Retrieves the authentication or signature certificate from the chip * - * @return authentication certificate + * @param authOrSign true for auth, false for sign cert + * @return the requested certificate */ - public byte[] getAuthenticationCertificate() throws NoSuchPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException, IOException, CertificateException { + public byte[] getCertificate(boolean authOrSign) throws NoSuchPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException, IOException { - selectIASECCApplication(); + selectFile(IASECCFID, "the master application"); - byte[] APDU = createSecureAPDU(new byte[]{-83, -15}, selectFile); - byte[] response = idCard.transceive(APDU); - Log.i("Select AWP application", Hex.toHexString(response)); + selectFile(authOrSign ? AWP : QSCD, "the application"); - APDU = createSecureAPDU(new byte[]{52, 1}, selectFile); - response = idCard.transceive(APDU); - Log.i("Select certificate", Hex.toHexString(response)); + selectFile(authOrSign ? authCert : signCert, "the certificate"); byte[] certificate = new byte[0]; byte[] readCert = Arrays.copyOf(read, read.length); @@ -397,9 +407,9 @@ public class Comms { // Set the P1/P2 values to incrementally read the certificate readCert[2] = (byte) (certificate.length / 256); readCert[3] = (byte) (certificate.length % 256); - APDU = createSecureAPDU(new byte[0], readCert); - response = idCard.transceive(APDU); - Log.i("Read certificate", Hex.toHexString(response)); + byte[] APDU = createSecureAPDU(new byte[0], readCert); + byte[] response = idCard.transceive(APDU); + Log.i("Read the certificate", Hex.toHexString(response)); byte sw1 = response[response.length - 2]; byte sw2 = response[response.length - 1]; @@ -423,7 +433,7 @@ public class Comms { // For debugging, ascertain that the byte array corresponds to a valid certificate // CertificateFactory certificateFactory = CertificateFactory.getInstance("X509"); // X509Certificate x509Certificate = (X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(certificate)); -// Log.i("Certificate subject", x509Certificate.getSubjectX500Principal().getName()); +// Log.i("Certificate serial number", String.valueOf(x509Certificate.getSerialNumber())); return certificate; diff --git a/MobileAuthApp/app/src/main/java/com/tarkvaraprojekt/mobileauthapp/auth/Authenticator.kt b/MobileAuthApp/app/src/main/java/com/tarkvaraprojekt/mobileauthapp/auth/Authenticator.kt index a92d716..e2f86fd 100644 --- a/MobileAuthApp/app/src/main/java/com/tarkvaraprojekt/mobileauthapp/auth/Authenticator.kt +++ b/MobileAuthApp/app/src/main/java/com/tarkvaraprojekt/mobileauthapp/auth/Authenticator.kt @@ -9,7 +9,7 @@ class Authenticator(val comms : Comms) { public fun authenticate(nonce: BigInteger, challengeUrl: String, pin1: String) { // Ask PIN 1 from the user and get the authentication certificate from the ID card. - val authenticationCertificate : ByteArray = comms.getAuthenticationCertificate(); + val authenticationCertificate : ByteArray = comms.getCertificate(true); // Create the authentication token (OpenID X509) diff --git a/MobileAuthApp/build.gradle b/MobileAuthApp/build.gradle index 0e1f251..0367823 100644 --- a/MobileAuthApp/build.gradle +++ b/MobileAuthApp/build.gradle @@ -9,7 +9,7 @@ buildscript { kotlin_version = "1.4.30" } dependencies { - classpath "com.android.tools.build:gradle:7.0.2" + classpath 'com.android.tools.build:gradle:7.0.3' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.20" classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version" // NOTE: Do not place your application dependencies here; they belong From 850ab8fc660afb6015dcc3fa7ad9e9cd48b44026 Mon Sep 17 00:00:00 2001 From: Lemmo Lavonen Date: Thu, 14 Oct 2021 19:50:53 +0300 Subject: [PATCH 7/8] Use hex to represent bytes. --- .../mobileauthapp/NFC/Comms.java | 72 ++++++------------- 1 file changed, 21 insertions(+), 51 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 5fe496f..39b04bf 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 @@ -30,65 +30,35 @@ import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; public class Comms { - private static final byte[] selectMaster = { // select IAS-ECC - 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 - 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[] selectMaster = Hex.decode("00a4040c10a000000077010800070000fe00000100"); - private static final byte[] GAGetNonce = { // general authenticate: get nonce - 16, -122, 0, 0, 2, 124, 0, 0 - }; + private static final byte[] MSESetAT = Hex.decode("0022c1a40f800a04007f0007020204020483010200"); - private static final byte[] GAMapNonceIncomplete = { - 16, -122, 0, 0, 69, 124, 67, -127, 65 - }; + private static final byte[] GAGetNonce = Hex.decode("10860000027c0000"); - private static final byte[] GAKeyAgreementIncomplete = { - 16, -122, 0, 0, 69, 124, 67, -125, 65 - }; + private static final byte[] GAMapNonceIncomplete = Hex.decode("10860000457c438141"); - private static final byte[] GAMutualAuthenticationIncomplete = { - 0, -122, 0, 0, 12, 124, 10, -123, 8 - }; + private static final byte[] GAKeyAgreementIncomplete = Hex.decode("10860000457c438341"); - 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[] GAMutualAuthenticationIncomplete = Hex.decode("008600000c7c0a8508"); - private static final byte[] selectMasterSecure = { - 12, -92, 4, 12, 45, -121, 33, 1 - }; + private static final byte[] dataForMACIncomplete = Hex.decode("7f494f060a04007f000702020402048641"); - private static final byte[] IASECCAID = { // Identification Authentication Signature - European Citizen Card Application Identifier - -96, 0, 0, 0, 119, 1, 8, 0, 7, 0, 0, -2, 0, 0, 1, 0 - }; + private static final byte[] selectFile = Hex.decode("0ca4010c1d871101"); - private static final byte[] selectFile = { // - 12, -92, 1, 12, 29, -121, 17, 1 - }; + private static final byte[] readFile = Hex.decode("0cb000000d970100"); - private static final byte[] read = { - 12, -80, 0, 0, 13, -105, 1, 0 - }; + private static final byte[] verifyPIN1 = Hex.decode("0c2000011d871101"); - private static final byte[] verifyPIN1 = { - 12, 32, 0, 1, 29, -121, 17, 1 - }; - - private static final byte[] verifyPIN2 = { - 12, 32, 0, -123, 29, -121, 17, 1 - }; - - private static final byte[] IASECCFID = {63, 0}; - private static final byte[] personalDF = {80, 0}; - private static final byte[] AWP = {-83, -15}; - private static final byte[] QSCD = {-83, -14}; - private static final byte[] authCert = {52, 1}; - private static final byte[] signCert = {52, 31}; + private static final byte[] verifyPIN2 = Hex.decode("0c2000851d871101"); + private static final byte[] IASECCFID = {0x3f, 0x00}; + private static final byte[] personalDF = {0x50, 0x00}; + private static final byte[] AWP = {(byte) 0xad, (byte) 0xf1}; + private static final byte[] QSCD = {(byte) 0xad, (byte) 0xf2}; + private static final byte[] authCert = {0x34, 0x01}; + private static final byte[] signCert = {0x34, 0x1f}; private final IsoDep idCard; private final byte[] keyEnc; @@ -241,7 +211,7 @@ public class Comms { */ private byte[] readFile(byte[] FID, String info) throws NoSuchPaddingException, InvalidKeyException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException, IOException { selectFile(FID, info); - byte[] APDU = createSecureAPDU(new byte[0], read); + byte[] APDU = createSecureAPDU(new byte[0], readFile); byte[] response = idCard.transceive(APDU); Log.i("Read binary", Hex.toHexString(response)); if (response[response.length - 2] != -112 || response[response.length - 1] != 0) { @@ -300,7 +270,7 @@ public class Comms { if (encryptedData.length > 0) { System.arraycopy(encryptedData, 0, APDU, incomplete.length, encryptedData.length); } - System.arraycopy(new byte[]{-114, 8}, 0, APDU, incomplete.length + encryptedData.length, 2); // MAC is encapsulated using the tag 0x8E + System.arraycopy(new byte[]{(byte) 0x8E, 0x08}, 0, APDU, incomplete.length + encryptedData.length, 2); // MAC is encapsulated using the tag 0x8E System.arraycopy(MAC, 0, APDU, incomplete.length + encryptedData.length + 2, MAC.length); ssc++; return APDU; @@ -368,7 +338,7 @@ public class Comms { } // pad the PIN and use the chip for verification - byte[] paddedPIN1 = new byte[]{-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}; + byte[] paddedPIN1 = Hex.decode("ffffffffffffffffffffffff"); System.arraycopy(PIN, 0, paddedPIN1, 0, PIN.length); byte[] APDU = createSecureAPDU(paddedPIN1, oneOrTwo ? verifyPIN1 : verifyPIN2); byte[] response = idCard.transceive(APDU); @@ -400,7 +370,7 @@ public class Comms { selectFile(authOrSign ? authCert : signCert, "the certificate"); byte[] certificate = new byte[0]; - byte[] readCert = Arrays.copyOf(read, read.length); + byte[] readCert = Arrays.copyOf(readFile, readFile.length); // Construct the certificate byte array n=indexOfTerminator bytes at a time for (int i = 0; i < 16; i++) { From 62888a72997fa5c2ca66ef49b4595365ed12e4ad Mon Sep 17 00:00:00 2001 From: Lemmo Lavonen Date: Tue, 19 Oct 2021 00:58:53 +0300 Subject: [PATCH 8/8] Add a method for signing the auth token hash. --- .../mobileauthapp/NFC/Comms.java | 159 ++++++++++-------- 1 file changed, 93 insertions(+), 66 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 39b04bf..928312b 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 @@ -53,6 +53,12 @@ public class Comms { private static final byte[] verifyPIN2 = Hex.decode("0c2000851d871101"); + private static final byte[] MSESetEnv = Hex.decode("0c2241A41d871101"); + + private static final byte[] Env = Hex.decode("8004FF200800840181"); + + private static final byte[] InternalAuthenticate = Hex.decode("0c8800001d871101"); + private static final byte[] IASECCFID = {0x3f, 0x00}; private static final byte[] personalDF = {0x50, 0x00}; private static final byte[] AWP = {(byte) 0xad, (byte) 0xf1}; @@ -75,7 +81,7 @@ public class Comms { idCard.connect(); this.idCard = idCard; - byte[][] keys = PACE(CAN); + byte[][] keys = PACE(CAN.getBytes(StandardCharsets.UTF_8)); keyEnc = keys[0]; keyMAC = keys[1]; } @@ -132,40 +138,51 @@ public class Comms { * @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 { - byte[] decryptionKey = createKey(CAN.getBytes(StandardCharsets.UTF_8), (byte) 3); + private byte[] decryptNonce(byte[] encryptedNonce, byte[] CAN) throws NoSuchPaddingException, InvalidKeyException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException { + byte[] decryptionKey = createKey(CAN, (byte) 3); Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding"); cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(decryptionKey, "AES"), new IvParameterSpec(new byte[16])); return cipher.doFinal(encryptedNonce); } + /** + * Communicates with the card and logs the response + * + * @param APDU The command + * @param log Information for logging + * @return The response + */ + private byte[] getResponse(byte[] APDU, String log) throws IOException { + byte[] response = idCard.transceive(APDU); + if (response[response.length - 2] != (byte) 0x90 || response[response.length - 1] != 0x00) { + throw new RuntimeException(String.format("%s failed.", log)); + } + Log.i(log, Hex.toHexString(response)); + return response; + } + /** * Attempts to use the PACE protocol to create a secure channel with an Estonian ID-card * * @param CAN the card access number */ - private byte[][] PACE(String CAN) throws IOException, NoSuchPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException { + private byte[][] PACE(byte[] CAN) throws IOException, NoSuchPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException { // select the IAS-ECC application on the chip - byte[] response = idCard.transceive(selectMaster); - Log.i("Select the master application", Hex.toHexString(response)); + getResponse(selectMaster, "Select the master application"); // initiate PACE - response = idCard.transceive(MSESetAT); - Log.i("Authentication template", Hex.toHexString(response)); + getResponse(MSESetAT, "Set authentication template"); // get nonce - response = idCard.transceive(GAGetNonce); - Log.i("Get nonce", Hex.toHexString(response)); + byte[] response = getResponse(GAGetNonce, "Get nonce"); byte[] decryptedNonce = decryptNonce(Arrays.copyOfRange(response, 4, response.length - 2), CAN); // generate an EC keypair and exchange public keys with the chip 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)); + response = getResponse(createAPDU(GAMapNonceIncomplete, publicKey.getEncoded(false), 66), "Map nonce"); 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 @@ -173,28 +190,18 @@ public class Comms { 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); - response = idCard.transceive(APDU); - Log.i("Key agreement", Hex.toHexString(response)); + response = getResponse(createAPDU(GAKeyAgreementIncomplete, publicKey.getEncoded(false), 66), "Key agreement"); cardPublicKey = spec.getCurve().decodePoint(Arrays.copyOfRange(response, 4, 69)); // generate the session keys and exchange MACs to verify them - sharedSecret = cardPublicKey.multiply(privateKey).normalize(); - byte[] encodedSecret = sharedSecret.getAffineXCoord().getEncoded(); - byte[] keyEnc = createKey(encodedSecret, (byte) 1); - byte[] keyMAC = createKey(encodedSecret, (byte) 2); - APDU = createAPDU(dataForMACIncomplete, cardPublicKey.getEncoded(false), 65); - byte[] MAC = getMAC(APDU, keyMAC); - APDU = createAPDU(GAMutualAuthenticationIncomplete, MAC, 9); - response = idCard.transceive(APDU); - Log.i("Mutual authentication", Hex.toHexString(response)); + byte[] secret = cardPublicKey.multiply(privateKey).normalize().getAffineXCoord().getEncoded(); + byte[] keyEnc = createKey(secret, (byte) 1); + byte[] keyMAC = createKey(secret, (byte) 2); + byte[] MAC = getMAC(createAPDU(dataForMACIncomplete, cardPublicKey.getEncoded(false), 65), keyMAC); + response = getResponse(createAPDU(GAMutualAuthenticationIncomplete, MAC, 9), "Mutual authentication"); - // 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(APDU, keyMAC); + // verify chip's MAC and return session keys + MAC = getMAC(createAPDU(dataForMACIncomplete, publicKey.getEncoded(false), 65), keyMAC); if (!Hex.toHexString(response, 4, 8).equals(Hex.toHexString(MAC))) { throw new RuntimeException("Could not verify chip's MAC."); // *Should* never happen. } @@ -211,10 +218,8 @@ public class Comms { */ private byte[] readFile(byte[] FID, String info) throws NoSuchPaddingException, InvalidKeyException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException, IOException { selectFile(FID, info); - byte[] APDU = createSecureAPDU(new byte[0], readFile); - byte[] response = idCard.transceive(APDU); - Log.i("Read binary", Hex.toHexString(response)); - if (response[response.length - 2] != -112 || response[response.length - 1] != 0) { + byte[] response = getResponse(new byte[0], readFile, "Read binary"); + if (response[response.length - 2] != (byte) 0x90 || response[response.length - 1] != 0x00) { throw new RuntimeException(String.format("Could not read %s", info)); } return encryptDecryptData(Arrays.copyOfRange(response, 3, 19), Cipher.DECRYPT_MODE); @@ -254,15 +259,15 @@ public class Comms { byte[] macData = new byte[data.length > 0 ? 48 + length : 48]; macData[15] = ssc; // first block contains the ssc System.arraycopy(incomplete, 0, macData, 16, 4); // second block has the command - macData[20] = -128; // elements are terminated by 0x80 and zero-padded to the next block + macData[20] = (byte) 0x80; // elements are terminated by 0x80 and zero-padded to the next block System.arraycopy(incomplete, 5, macData, 32, 3); // third block contains appropriately encapsulated data/Le if (data.length > 0) { // if the APDU has data, add padding and encrypt it byte[] paddedData = Arrays.copyOf(data, length); - paddedData[data.length] = -128; + paddedData[data.length] = (byte) 0x80; encryptedData = encryptDecryptData(paddedData, Cipher.ENCRYPT_MODE); System.arraycopy(encryptedData, 0, macData, 35, encryptedData.length); } - macData[35 + encryptedData.length] = -128; + macData[35 + encryptedData.length] = (byte) 0x80; byte[] MAC = getMAC(macData, keyMAC); // construct the APDU using the encrypted data and the MAC @@ -282,10 +287,8 @@ public class Comms { * */ private void selectFile(byte[] FID, String info) throws NoSuchPaddingException, InvalidKeyException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException, IOException { - byte[] APDU = createSecureAPDU(FID, selectFile); - byte[] response = idCard.transceive(APDU); - Log.i(String.format("Select %s", info), Hex.toHexString(response)); - if (response[response.length - 2] != -112 || response[response.length - 1] != 0) { + byte[] response = getResponse(FID, selectFile, String.format("Select %s", info)); + if (response[response.length - 2] != (byte) 0x90 || response[response.length - 1] != 0x00) { throw new RuntimeException(String.format("Could not select %s", info)); } } @@ -330,7 +333,7 @@ public class Comms { * @param PIN user-provided PIN * @param oneOrTwo true for PIN1, false for PIN2 */ - public void verifyPIN(byte[] PIN, boolean oneOrTwo) throws NoSuchPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException, IOException { + private void verifyPIN(byte[] PIN, boolean oneOrTwo) throws NoSuchPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException, IOException { selectFile(IASECCFID, "the master application"); if (!oneOrTwo) { @@ -338,19 +341,15 @@ public class Comms { } // pad the PIN and use the chip for verification - byte[] paddedPIN1 = Hex.decode("ffffffffffffffffffffffff"); - System.arraycopy(PIN, 0, paddedPIN1, 0, PIN.length); - byte[] APDU = createSecureAPDU(paddedPIN1, oneOrTwo ? verifyPIN1 : verifyPIN2); - byte[] response = idCard.transceive(APDU); - Log.i(String.format("PIN%d verification", oneOrTwo ? 1 : 2), Hex.toHexString(response)); + byte[] paddedPIN = Hex.decode("ffffffffffffffffffffffff"); + System.arraycopy(PIN, 0, paddedPIN, 0, PIN.length); + byte[] response = getResponse(paddedPIN, oneOrTwo ? verifyPIN1 : verifyPIN2, "PIN verification"); - byte sw1 = response[response.length - 2]; - byte sw2 = response[response.length - 1]; - if (sw1 != -112 || sw2 != 0) { - if (sw1 == 105 && sw2 == -125) { + if (response[response.length - 2] != (byte) 0x90 || response[response.length - 1] != 0x00) { + if (response[response.length - 2] == 0x69 && response[response.length - 1] == (byte) 0x83) { throw new RuntimeException("Invalid PIN. Authentication method blocked."); } else { - throw new RuntimeException(String.format("Invalid PIN1. %d attempt%s left.", sw2 + 64, sw2 == -63 ? "" : "s")); + throw new RuntimeException(String.format("Invalid PIN. Attempts left: %d.", response[response.length - 1] + 64)); } } } @@ -377,13 +376,8 @@ public class Comms { // Set the P1/P2 values to incrementally read the certificate readCert[2] = (byte) (certificate.length / 256); readCert[3] = (byte) (certificate.length % 256); - byte[] APDU = createSecureAPDU(new byte[0], readCert); - byte[] response = idCard.transceive(APDU); - Log.i("Read the certificate", Hex.toHexString(response)); - - byte sw1 = response[response.length - 2]; - byte sw2 = response[response.length - 1]; - if (sw1 == 107 && sw2 == 0) { + byte[] response = getResponse(new byte[0], readCert, "Read the certificate"); + if (response[response.length - 2] == 0x6b && response[response.length - 1] == 0x00) { throw new RuntimeException("Wrong read parameters."); } @@ -395,17 +389,50 @@ public class Comms { certificate = Arrays.copyOf(certificate, certificate.length + indexOfTerminator); System.arraycopy(decrypted, 0, certificate, certificate.length - indexOfTerminator, indexOfTerminator); - if (sw1 == -112 && sw2 == 0) { + if (response[response.length - 2] == (byte) 0x90 && response[response.length - 1] == 0x00) { break; } } - // For debugging, ascertain that the byte array corresponds to a valid certificate -// CertificateFactory certificateFactory = CertificateFactory.getInstance("X509"); -// X509Certificate x509Certificate = (X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(certificate)); -// Log.i("Certificate serial number", String.valueOf(x509Certificate.getSerialNumber())); - return certificate; } + + /** + * Signs the authentication token hash + * + * @param PIN1 PIN1 + * @param token the token hash to be signed + * @return authentication token hash signature + */ + public byte[] authenticate(String PIN1, byte[] token) throws NoSuchPaddingException, InvalidKeyException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException, IOException { + + verifyPIN(PIN1.getBytes(StandardCharsets.UTF_8), true); + + selectFile(AWP, "the AWP application"); + + byte[] response = getResponse(Env, MSESetEnv, "Set environment"); + if (response[response.length - 2] != (byte) 0x90 || response[response.length - 1] != 0x00) { + throw new RuntimeException("Setting the environment failed."); + } + + InternalAuthenticate[4] = (byte) (0x1d + 16 * (token.length / 16)); + InternalAuthenticate[6] = (byte) (0x11 + 16 * (token.length / 16)); + response = getResponse(token, InternalAuthenticate, "Internal Authenticate"); + if (response[response.length - 2] != (byte) 0x90 || response[response.length - 1] != 0x00) { + throw new RuntimeException("Signing the token failed."); + } + + byte[] signature = encryptDecryptData(Arrays.copyOfRange(response, 3, 115), Cipher.DECRYPT_MODE); + int indexOfTerminator = Hex.toHexString(signature).lastIndexOf("80") / 2; + + return Arrays.copyOf(signature, indexOfTerminator); + } + + private byte[] getResponse(byte[] data, byte[] command, String log) throws NoSuchPaddingException, InvalidKeyException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException, IOException { + byte[] response = idCard.transceive(createSecureAPDU(data, command)); + Log.i(log, Hex.toHexString(response)); + return response; + } + }