Refactor (generalise selecting and reading a file, PIN verification and certificate retrieval).

This commit is contained in:
Lemmo Lavonen 2021-10-14 03:58:49 +03:00
parent 29c7ecfa12
commit ef7015abb8
3 changed files with 62 additions and 52 deletions

View File

@ -20,7 +20,6 @@ import java.security.InvalidKeyException;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.util.Arrays; import java.util.Arrays;
import javax.crypto.BadPaddingException; import javax.crypto.BadPaddingException;
@ -31,7 +30,7 @@ import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.SecretKeySpec;
public class Comms { 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 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 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 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 -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 = { // private static final byte[] selectFile = { //
12, -92, 1, 12, 29, -121, 17, 1 12, -92, 1, 12, 29, -121, 17, 1
}; };
@ -83,6 +78,18 @@ public class Comms {
12, 32, 0, 1, 29, -121, 17, 1 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 IsoDep idCard;
private final byte[] keyEnc; private final byte[] keyEnc;
private final byte[] keyMAC; private final byte[] keyMAC;
@ -170,8 +177,8 @@ public class Comms {
private byte[][] PACE(String CAN) throws IOException, NoSuchPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException { private byte[][] PACE(String CAN) throws IOException, NoSuchPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException {
// select the IAS-ECC application on the chip // select the IAS-ECC application on the chip
byte[] response = idCard.transceive(selectMain); byte[] response = idCard.transceive(selectMaster);
Log.i("Select applet", Hex.toHexString(response)); Log.i("Select the master application", Hex.toHexString(response));
// initiate PACE // initiate PACE
response = idCard.transceive(MSESetAT); response = idCard.transceive(MSESetAT);
@ -229,15 +236,17 @@ public class Comms {
* Selects a file and reads its contents * Selects a file and reads its contents
* *
* @param FID file identifier of the required file * @param FID file identifier of the required file
* @param info string for logging
* @return decrypted file contents * @return decrypted file contents
*/ */
private byte[] readFileByFID(byte[] FID) throws NoSuchPaddingException, InvalidKeyException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException, IOException { private byte[] readFile(byte[] FID, String info) throws NoSuchPaddingException, InvalidKeyException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException, IOException {
byte [] APDU = createSecureAPDU(FID, selectFile); selectFile(FID, info);
byte [] response = idCard.transceive(APDU); byte[] APDU = createSecureAPDU(new byte[0], read);
Log.i(String.format("Select FID %s", Hex.toHexString(FID)), Hex.toHexString(response)); byte[] response = idCard.transceive(APDU);
APDU = createSecureAPDU(new byte[0], read);
response = idCard.transceive(APDU);
Log.i("Read binary", Hex.toHexString(response)); 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); 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 { private void selectFile(byte[] FID, String info) throws NoSuchPaddingException, InvalidKeyException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException, IOException {
byte[] APDU = createSecureAPDU(IASECCAID, selectAppSecure); byte[] APDU = createSecureAPDU(FID, selectFile);
byte[] response = idCard.transceive(APDU); byte[] response = idCard.transceive(APDU);
Log.i("Select the main application", Hex.toHexString(response)); Log.i(String.format("Select %s", info), Hex.toHexString(response));
if (!Hex.toHexString(response, response.length - 2, 2).equals("9000")) { if (response[response.length - 2] != -112 || response[response.length - 1] != 0) {
throw new RuntimeException("Could not select IAS-ECC."); // *Should* never happen. throw new RuntimeException(String.format("Could not select %s", info));
} }
} }
@ -321,15 +331,13 @@ public class Comms {
String[] personalData = new String[lastBytes.length]; String[] personalData = new String[lastBytes.length];
int stringIndex = 0; int stringIndex = 0;
// select the application // select the master application
selectIASECCApplication(); selectFile(IASECCFID, "the master application");
// select the personal data dedicated file // select the personal data dedicated file
byte[] FID = new byte[]{80, 0}; // personal data dedicated file FID selectFile(personalDF, "the personal data DF");
byte[] APDU = createSecureAPDU(FID, selectFile);
byte[] response = idCard.transceive(APDU);
Log.i("Select personal data DF", Hex.toHexString(response));
byte[] FID = Arrays.copyOf(personalDF, personalDF.length);
// select and read the personal data elementary files // select and read the personal data elementary files
for (byte index : lastBytes) { for (byte index : lastBytes) {
@ -337,7 +345,7 @@ public class Comms {
FID[1] = index; FID[1] = index;
// store the decrypted datum // store the decrypted datum
response = readFileByFID(FID); byte[] response = readFile(FID, "a personal data EF");
int indexOfTerminator = Hex.toHexString(response).lastIndexOf("80") / 2; int indexOfTerminator = Hex.toHexString(response).lastIndexOf("80") / 2;
personalData[stringIndex++] = new String(Arrays.copyOfRange(response, 0, indexOfTerminator)); 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}; byte[] paddedPIN1 = new byte[]{-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1};
System.arraycopy(PIN1, 0, paddedPIN1, 0, PIN1.length); System.arraycopy(PIN, 0, paddedPIN1, 0, PIN.length);
byte[] APDU = createSecureAPDU(paddedPIN1, verifyPIN1); byte[] APDU = createSecureAPDU(paddedPIN1, oneOrTwo ? verifyPIN1 : verifyPIN2);
byte[] response = idCard.transceive(APDU); 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 sw1 = response[response.length - 2];
byte sw2 = response[response.length - 1]; byte sw2 = response[response.length - 1];
if (sw1 != -112 || sw2 != 0) { if (sw1 != -112 || sw2 != 0) {
if (sw1 == 105 && sw2 == -125) { if (sw1 == 105 && sw2 == -125) {
throw new RuntimeException("Invalid PIN1. Authentication method blocked."); throw new RuntimeException("Invalid PIN. Authentication method blocked.");
} else { } else {
throw new RuntimeException(String.format("Invalid PIN1. %d attempt%s left.", sw2 + 64, sw2 == -63 ? "" : "s")); 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); selectFile(authOrSign ? AWP : QSCD, "the application");
byte[] response = idCard.transceive(APDU);
Log.i("Select AWP application", Hex.toHexString(response));
APDU = createSecureAPDU(new byte[]{52, 1}, selectFile); selectFile(authOrSign ? authCert : signCert, "the certificate");
response = idCard.transceive(APDU);
Log.i("Select certificate", Hex.toHexString(response));
byte[] certificate = new byte[0]; byte[] certificate = new byte[0];
byte[] readCert = Arrays.copyOf(read, read.length); byte[] readCert = Arrays.copyOf(read, read.length);
@ -397,9 +407,9 @@ public class Comms {
// Set the P1/P2 values to incrementally read the certificate // Set the P1/P2 values to incrementally read the certificate
readCert[2] = (byte) (certificate.length / 256); readCert[2] = (byte) (certificate.length / 256);
readCert[3] = (byte) (certificate.length % 256); readCert[3] = (byte) (certificate.length % 256);
APDU = createSecureAPDU(new byte[0], readCert); byte[] APDU = createSecureAPDU(new byte[0], readCert);
response = idCard.transceive(APDU); byte[] response = idCard.transceive(APDU);
Log.i("Read certificate", Hex.toHexString(response)); Log.i("Read the certificate", Hex.toHexString(response));
byte sw1 = response[response.length - 2]; byte sw1 = response[response.length - 2];
byte sw2 = response[response.length - 1]; 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 // For debugging, ascertain that the byte array corresponds to a valid certificate
// CertificateFactory certificateFactory = CertificateFactory.getInstance("X509"); // CertificateFactory certificateFactory = CertificateFactory.getInstance("X509");
// X509Certificate x509Certificate = (X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(certificate)); // 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; return certificate;

View File

@ -9,7 +9,7 @@ class Authenticator(val comms : Comms) {
public fun authenticate(nonce: BigInteger, challengeUrl: String, pin1: String) { public fun authenticate(nonce: BigInteger, challengeUrl: String, pin1: String) {
// Ask PIN 1 from the user and get the authentication certificate from the ID card. // 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) // Create the authentication token (OpenID X509)

View File

@ -9,7 +9,7 @@ buildscript {
kotlin_version = "1.4.30" kotlin_version = "1.4.30"
} }
dependencies { 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 "org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.20"
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version" classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong