diff --git a/MobileAuthApp/app/src/main/java/com/tarkvaraprojekt/mobileauthapp/AuthFragment.kt b/MobileAuthApp/app/src/main/java/com/tarkvaraprojekt/mobileauthapp/AuthFragment.kt index 4d3d678..55f82f3 100644 --- a/MobileAuthApp/app/src/main/java/com/tarkvaraprojekt/mobileauthapp/AuthFragment.kt +++ b/MobileAuthApp/app/src/main/java/com/tarkvaraprojekt/mobileauthapp/AuthFragment.kt @@ -1,14 +1,10 @@ package com.tarkvaraprojekt.mobileauthapp -import android.app.Activity -import android.content.Context import android.content.Intent import android.nfc.NfcAdapter -import android.nfc.TagLostException import android.nfc.tech.IsoDep import android.os.Bundle import android.os.CountDownTimer -import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -18,11 +14,14 @@ import androidx.fragment.app.activityViewModels import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs import com.tarkvaraprojekt.mobileauthapp.NFC.Comms -import com.tarkvaraprojekt.mobileauthapp.auth.Authenticator +import com.tarkvaraprojekt.mobileauthapp.auth.AuthAppException +import com.tarkvaraprojekt.mobileauthapp.auth.InvalidCANException import com.tarkvaraprojekt.mobileauthapp.databinding.FragmentAuthBinding import com.tarkvaraprojekt.mobileauthapp.model.ParametersViewModel import com.tarkvaraprojekt.mobileauthapp.model.SmartCardViewModel +import java.io.IOException import java.lang.Exception +import java.security.GeneralSecurityException import kotlin.system.exitProcess /** @@ -34,10 +33,9 @@ class AuthFragment : Fragment() { private val viewModel: SmartCardViewModel by activityViewModels() - private val paramsModel: ParametersViewModel by activityViewModels() + private val intentParameters: ParametersViewModel by activityViewModels() - private var _binding: FragmentAuthBinding? = null - private val binding get() = _binding!! + private var binding: FragmentAuthBinding? = null private val args: CanFragmentArgs by navArgs() @@ -50,8 +48,8 @@ class AuthFragment : Fragment() { container: ViewGroup?, savedInstanceState: Bundle? ): View? { - _binding = FragmentAuthBinding.inflate(inflater, container, false) - return binding.root + binding = FragmentAuthBinding.inflate(inflater, container, false) + return binding!!.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -60,96 +58,116 @@ class AuthFragment : Fragment() { override fun onTick(p0: Long) { timeRemaining-- if (timeRemaining == 0) { - binding.timeCounter.text = getString(R.string.no_time) + binding?.timeCounter?.text = getString(R.string.no_time) } else { - binding.timeCounter.text = getString(R.string.time_left, timeRemaining) + binding?.timeCounter?.text = getString(R.string.time_left, timeRemaining) } } override fun onFinish() { Thread.sleep(750) - cancelAuth() + goToTheStart() } }.start() - // The button exists in code for testing reasons, but not visible to the user anymore unless visibility is changed in the code. - binding.nextButton.visibility = View.GONE - binding.nextButton.setOnClickListener { goToNextFragment() } - binding.cancelButton.setOnClickListener { cancelAuth() } + binding!!.nextButton.setOnClickListener { goToNextFragment() } + binding!!.cancelButton.setOnClickListener { goToTheStart() } val adapter = NfcAdapter.getDefaultAdapter(activity) if (adapter != null) getInfoFromIdCard(adapter) - else { // If NFC adapter can not be detected then end the auth process as it is not possible to read an ID card - cancelAuth() // It would be a good idea to show user some notification as it might be confusing if the app suddenly closes + } + + private fun getInfoFromIdCard(adapter: NfcAdapter) { + if (args.reading) { + adapter.enableReaderMode(activity, { tag -> + timer.cancel() + requireActivity().runOnUiThread { + binding!!.timeCounter.text = getString(R.string.card_detected) + } + var msgCode = 0 + + val card = IsoDep.get(tag) + card.timeout = 32768 + card.use { + try { + val comms = Comms(it, viewModel.userCan) + val response = comms.readPersonalData(byteArrayOf(1, 2, 6, 3, 4, 8)) + viewModel.setUserFirstName(response[1]) + viewModel.setUserLastName(response[0]) + viewModel.setUserIdentificationNumber(response[2]) + viewModel.setGender(response[3]) + viewModel.setCitizenship(response[4]) + viewModel.setExpiration(response[5]) + requireActivity().runOnUiThread { + binding!!.timeCounter.text = getString(R.string.data_read) + } + + } catch (e: android.nfc.TagLostException) { + msgCode = R.string.tag_lost + } catch (e: InvalidCANException) { + msgCode = R.string.invalid_can + // If the CAN is wrong we will also delete the saved CAN so that the user won't use it again. + viewModel.deleteCan(requireContext()) + } catch (e: AuthAppException) { + msgCode = when (e.code) { + 448 -> R.string.err_bad_data + 500 -> R.string.err_internal + else -> R.string.err_unknown + } + } catch (e: GeneralSecurityException) { + msgCode = R.string.err_internal + } catch (e: IOException) { + msgCode = R.string.err_reading_card + } catch (e: Exception) { + msgCode = R.string.err_unknown + } finally { + adapter.disableReaderMode(activity) + } + + if (msgCode != 0) { + requireActivity().runOnUiThread { + binding!!.timeCounter.text = getString(msgCode) + } + // Gives user some time to read the error message + Thread.sleep(1000) + goToTheStart() + } + } + }, NfcAdapter.FLAG_READER_NFC_A, null) + } else { //We want to create a JWT instead of reading the info from the card. + goToNextFragment() } } private fun goToNextFragment() { timer.cancel() - val action = AuthFragmentDirections.actionAuthFragmentToResultFragment(mobile = args.mobile) - findNavController().navigate(action) - } - - private fun cancelAuth() { - viewModel.clearUserInfo() - timer.cancel() - if (args.mobile) { - val resultIntent = Intent() - requireActivity().setResult(AppCompatActivity.RESULT_CANCELED, resultIntent) - requireActivity().finish() + if (args.auth) { + val action = AuthFragmentDirections.actionAuthFragmentToResultFragment(mobile = args.mobile) + findNavController().navigate(action) } else { - requireActivity().finishAndRemoveTask() + findNavController().navigate(R.id.action_authFragment_to_userFragment) } } - private fun getInfoFromIdCard(adapter: NfcAdapter) { - adapter.enableReaderMode(activity, { tag -> - timer.cancel() - requireActivity().runOnUiThread { - binding.timeCounter.text = getString(R.string.card_detected) + private fun goToTheStart() { + viewModel.clearUserInfo() + timer.cancel() + if (args.reading) { + findNavController().navigate(R.id.action_authFragment_to_homeFragment) + } else { + if (!args.mobile) { + //Currently for some reason the activity is not killed entirely. Must be looked into further. + requireActivity().finish() + exitProcess(0) + } else { + val resultIntent = Intent() + requireActivity().setResult(AppCompatActivity.RESULT_CANCELED, resultIntent) + requireActivity().finish() } - val card = IsoDep.get(tag) - card.timeout = 32768 - card.use { - try { - val comms = Comms(it, viewModel.userCan) - val jws = Authenticator(comms).authenticate( - paramsModel.challenge, - paramsModel.origin, - viewModel.userPin - ) - paramsModel.setToken(jws) - requireActivity().runOnUiThread { - goToNextFragment() - } - } catch (e: Exception) { - when(e) { - is TagLostException -> requireActivity().runOnUiThread { binding!!.timeCounter.text = getString(R.string.id_card_removed_early) } - else -> { - when ("invalid pin") { - in e.message.toString().lowercase() -> requireActivity().runOnUiThread { - val messagePieces = e.message.toString().split(" ") - binding.timeCounter.text = getString(R.string.wrong_pin, messagePieces[messagePieces.size - 1]) - viewModel.deletePin(requireContext()) - } - else -> requireActivity().runOnUiThread { - binding.timeCounter.text = getString(R.string.wrong_can_text) - viewModel.deleteCan(requireContext()) - } - } - } - } - // Give user some time to read the error message - Thread.sleep(2000) - cancelAuth() - } finally { - adapter.disableReaderMode(activity) - } - } - }, NfcAdapter.FLAG_READER_NFC_A, null) + } } override fun onDestroy() { super.onDestroy() - _binding = null + binding = null } } \ No newline at end of file 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 bccb6ee..76b8c4f 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 @@ -3,6 +3,9 @@ package com.tarkvaraprojekt.mobileauthapp.NFC; import android.nfc.tech.IsoDep; import android.util.Log; +import com.tarkvaraprojekt.mobileauthapp.auth.AuthAppException; +import com.tarkvaraprojekt.mobileauthapp.auth.InvalidCANException; + import org.bouncycastle.crypto.BlockCipher; import org.bouncycastle.crypto.engines.AESEngine; import org.bouncycastle.crypto.macs.CMac; @@ -21,7 +24,6 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.Arrays; -import java.util.Base64; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; @@ -31,43 +33,47 @@ import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; public class Comms { + private static final byte[] master = { // select Main AID + 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[] selectMaster = Hex.decode("00a4040c10a000000077010800070000fe00000100"); + 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[] MSESetAT = Hex.decode("0022c1a40f800a04007f0007020204020483010200"); + private static final byte[] GAGetNonce = { // general authenticate: get nonce + 16, -122, 0, 0, 2, 124, 0, 0 + }; - private static final byte[] GAGetNonce = Hex.decode("10860000027c0000"); + private static final byte[] GAMapNonceIncomplete = { + 16, -122, 0, 0, 69, 124, 67, -127, 65 + }; - private static final byte[] GAMapNonceIncomplete = Hex.decode("10860000457c438141"); + private static final byte[] GAKeyAgreementIncomplete = { + 16, -122, 0, 0, 69, 124, 67, -125, 65 + }; - private static final byte[] GAKeyAgreementIncomplete = Hex.decode("10860000457c438341"); + private static final byte[] GAMutualAuthenticationIncomplete = { + 0, -122, 0, 0, 12, 124, 10, -123, 8 + }; - private static final byte[] GAMutualAuthenticationIncomplete = Hex.decode("008600000c7c0a8508"); + 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[] dataForMACIncomplete = Hex.decode("7f494f060a04007f000702020402048641"); + private static final byte[] masterSec = { + 12, -92, 4, 12, 45, -121, 33, 1 + }; - private static final byte[] selectFile = Hex.decode("0ca4010c1d871101"); + private static final byte[] personal = { // select personal data DF + 12, -92, 1, 12, 29, -121, 17, 1 + }; - private static final byte[] readFile = Hex.decode("0cb000000d970100"); + private static final byte[] read = { // read binary + 12, -80, 0, 0, 13, -105, 1, 0 + }; - private static final byte[] verifyPIN1 = Hex.decode("0c2000011d871101"); - - 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}; - 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 IsoDep idCard; private final byte[] keyEnc; private final byte[] keyMAC; private byte ssc; // Send sequence counter. @@ -81,12 +87,21 @@ public class Comms { public Comms(IsoDep idCard, String CAN) throws IOException, NoSuchPaddingException, InvalidKeyException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException { idCard.connect(); + this.idCard = idCard; - byte[][] keys = PACE(CAN.getBytes(StandardCharsets.UTF_8)); + + long start = System.currentTimeMillis(); + byte[][] keys = PACE(CAN); + Log.i("Pace duration", String.valueOf(System.currentTimeMillis() - start)); + keyEnc = keys[0]; keyMAC = keys[1]; } + public byte[] getAuthenticationCertificate() { + return new byte[0]; + } + /** * Calculates the message authentication code * @@ -139,51 +154,40 @@ public class Comms { * @param CAN the card access number provided by the user * @return the decrypted nonce */ - private byte[] decryptNonce(byte[] encryptedNonce, byte[] CAN) throws NoSuchPaddingException, InvalidKeyException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException { - byte[] decryptionKey = createKey(CAN, (byte) 3); + private byte[] decryptNonce(byte[] encryptedNonce, String CAN) throws NoSuchPaddingException, InvalidKeyException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException { + byte[] decryptionKey = createKey(CAN.getBytes(StandardCharsets.UTF_8), (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(byte[] 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 - getResponse(selectMaster, "Select the master application"); + // select the ECC applet on the chip + byte[] response = idCard.transceive(master); + Log.i("Select applet", Hex.toHexString(response)); // initiate PACE - getResponse(MSESetAT, "Set authentication template"); + response = idCard.transceive(MSESetAT); + Log.i("Authentication template", Hex.toHexString(response)); // get nonce - byte[] response = getResponse(GAGetNonce, "Get nonce"); + response = idCard.transceive(GAGetNonce); + Log.i("Get nonce", Hex.toHexString(response)); 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(); - response = getResponse(createAPDU(GAMapNonceIncomplete, publicKey.getEncoded(false), 66), "Map nonce"); + byte[] APDU = createAPDU(GAMapNonceIncomplete, publicKey.getEncoded(false), 66); + response = idCard.transceive(APDU); + Log.i("Map nonce", Hex.toHexString(response)); 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 @@ -191,41 +195,35 @@ 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(); - response = getResponse(createAPDU(GAKeyAgreementIncomplete, publicKey.getEncoded(false), 66), "Key agreement"); + APDU = createAPDU(GAKeyAgreementIncomplete, publicKey.getEncoded(false), 66); + response = idCard.transceive(APDU); + Log.i("Key agreement", Hex.toHexString(response)); cardPublicKey = spec.getCurve().decodePoint(Arrays.copyOfRange(response, 4, 69)); // generate the session keys and exchange MACs to verify them - 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"); + 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)); - // verify chip's MAC and return session keys - MAC = getMAC(createAPDU(dataForMACIncomplete, publicKey.getEncoded(false), 65), keyMAC); + // if the chip-side verification fails, crash and burn + if (response.length == 2) throw new InvalidCANException(); + + // otherwise verify chip's MAC and return session keys + 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 AuthAppException("Could not verify chip's MAC.", 448); // Should never happen. } return new byte[][]{keyEnc, keyMAC}; } - /** - * 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[] readFile(byte[] FID, String info) throws NoSuchPaddingException, InvalidKeyException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException, IOException { - selectFile(FID, info); - 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); - } - /** * Encrypts or decrypts the APDU data * @@ -260,182 +258,74 @@ 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] = (byte) 0x80; // elements are terminated by 0x80 and zero-padded to the next block + macData[20] = -128; // 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] = (byte) 0x80; + paddedData[data.length] = -128; encryptedData = encryptDecryptData(paddedData, Cipher.ENCRYPT_MODE); System.arraycopy(encryptedData, 0, macData, 35, encryptedData.length); } - macData[35 + encryptedData.length] = (byte) 0x80; + macData[35 + encryptedData.length] = -128; byte[] MAC = getMAC(macData, keyMAC); // construct the APDU using the encrypted data and the MAC - byte[] APDU = Arrays.copyOf(incomplete, incomplete.length + encryptedData.length + MAC.length + 3); + byte[] APDU = new byte[incomplete.length + encryptedData.length + MAC.length + 3]; + System.arraycopy(incomplete, 0, APDU, 0, incomplete.length); if (encryptedData.length > 0) { System.arraycopy(encryptedData, 0, APDU, incomplete.length, encryptedData.length); } - System.arraycopy(new byte[]{(byte) 0x8E, 0x08}, 0, APDU, incomplete.length + encryptedData.length, 2); // MAC is encapsulated using the tag 0x8E + 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; } - /** - * Selects a FILE by its identifier - * - */ - private void selectFile(byte[] FID, String info) throws NoSuchPaddingException, InvalidKeyException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException, IOException { - 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)); - } - } - /** * Gets the contents of the personal data dedicated file * - * @param lastBytes the last bytes of the personal data file identifiers (0 < x < 16) - * @return array containing the corresponding data strings + * @param FID the last bytes of file identifiers being requested + * @return array containing the data strings + * */ - public String[] readPersonalData(byte[] lastBytes) throws NoSuchPaddingException, InvalidKeyException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException, IOException { + public String[] readPersonalData(byte[] FID) throws NoSuchPaddingException, InvalidKeyException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException, IOException { - String[] personalData = new String[lastBytes.length]; - int stringIndex = 0; - - // select the master application - selectFile(IASECCFID, "the master application"); + String[] personalData = new String[FID.length]; + byte[] data; + byte[] APDU; + byte[] response; // select the personal data dedicated file - selectFile(personalDF, "the personal data DF"); + data = new byte[]{80, 0}; // personal data DF FID + APDU = createSecureAPDU(data, personal); + 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 - for (byte index : lastBytes) { + // select and read the first 8 elementary files in the DF + for (int i = 0; i < FID.length; i++) { - if (index > 15 || index < 1) throw new RuntimeException("Invalid personal data FID."); - FID[1] = index; + byte index = FID[i]; + if (index > 15 || index < 1) throw new AuthAppException("Invalid personal data FID.", 500); + + 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)); // store the decrypted datum - 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)); + 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; } - - /** - * Attempts to verify the selected PIN - * - * @param PIN user-provided PIN - * @param oneOrTwo true for PIN1, false for PIN2 - */ - private void verifyPIN(byte[] PIN, boolean oneOrTwo) throws NoSuchPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException, IOException { - - selectFile(IASECCFID, "the master application"); - if (!oneOrTwo) { - selectFile(QSCD, "the application"); - } - - // pad the PIN and use the chip for verification - byte[] paddedPIN = Hex.decode("ffffffffffffffffffffffff"); - System.arraycopy(PIN, 0, paddedPIN, 0, PIN.length); - byte[] response = getResponse(paddedPIN, oneOrTwo ? verifyPIN1 : verifyPIN2, "PIN verification"); - - 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 PIN. Attempts left: %d.", response[response.length - 1] + 64)); - } - } - } - - /** - * Retrieves the authentication or signature certificate from the chip - * - * @param authOrSign true for auth, false for sign cert - * @return the requested certificate - */ - public byte[] getCertificate(boolean authOrSign) throws NoSuchPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException, IOException { - - selectFile(IASECCFID, "the master application"); - - selectFile(authOrSign ? AWP : QSCD, "the application"); - - selectFile(authOrSign ? authCert : signCert, "the certificate"); - - byte[] certificate = new byte[0]; - 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++) { - - // Set the P1/P2 values to incrementally read the certificate - readCert[2] = (byte) (certificate.length / 256); - readCert[3] = (byte) (certificate.length % 256); - 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."); - } - - // 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 (response[response.length - 2] == (byte) 0x90 && response[response.length - 1] == 0x00) { - break; - } - } - - 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; - } - } diff --git a/MobileAuthApp/app/src/main/res/values-en/strings.xml b/MobileAuthApp/app/src/main/res/values-en/strings.xml index d0770eb..69af501 100644 --- a/MobileAuthApp/app/src/main/res/values-en/strings.xml +++ b/MobileAuthApp/app/src/main/res/values-en/strings.xml @@ -1,46 +1,29 @@ - NFC authenticator - - - CANCEL - BACK - ADD CAN - TRY AGAIN - CONTINUE - - - Card detected. Hold it against the phone. - Data read. You can continue. - Wrong CAN - Put the ID card against the phone to detect it - CAN must be added before ID card can be detected - NFC is not turned on or is not supported by the phone - The provided CAN does not match the ID card - ID card was removed too early - Wrong PIN 1. Tries on the card left %s + NFC authentication + Work in progress PIN 1 saved PIN 1 not saved CAN saved CAN not saved - HELP - What is CAN? - CAN is a 6 digit code that is needed to communicate with an ID card. It can be found on the ID card under the card holder\'s picture with a title KASUTAJA ALLKIRI/HOLDER\'S SIGNATURE. - Problem with parameters - Challenge is missing - AuthUrl is missing - OriginUrl is missing - Unspecified problem with parameters + + READ ID CARD + NEXT + CANCEL + SAVE + NO + BACK + - Please enter PIN 1 - PIN 1 - PIN 1 must be 4–12 digits long - Save PIN 1 - On - Off + Please enter PIN 1 + PIN 1 + Example. 1234 + Allowed length for PIN 1 is 4..12 + PIN 1 is currently not saved. Do you wish to save the entered PIN 1? Saved PIN 1 will be entered automatically in the future. Saved PIN 1 can be changed and deleted in the settings menu. + Save PIN 1 Please enter PIN 2 @@ -49,14 +32,21 @@ Allowed length for PIN 2 is 5..12 - Please enter CAN - CAN - CAN must be 6 digits long + Example. 123456 + CAN + Enter ID card\'s CAN (Card Access Number) + Length of the CAN is wrong + Card detected. Hold it against the phone. + Data read. You can continue. + Save CAN - Put the ID card against the phone + Put the ID card against the phone to establish connection Time left %d sek No time left + Unknown error + Wrong CAN + Connection between device and ID-card lost NAME @@ -65,14 +55,18 @@ DATE OF EXPIRY CITIZENSHIP SEX + FORGET Controlling the created token - The app will close automatically + Wait for the app to close Settings + Language + Currently unavailable CAN: %s + Add CAN Delete CAN PIN1: %s Add PIN1 @@ -81,7 +75,9 @@ SHOW HIDE **** - Settings are currently unavailable - CAN deleted - PIN 1 deleted + Settings currently unavailabe + CAN is currently not saved. Do you wish to save the CAN? Saved CAN will be entered automatically in the future. Saved CAN can be changed and deleted in the settings menu. + Failed to read data from the ID-card + Internal error + Read bad data from the ID-card, try using the card again \ No newline at end of file diff --git a/MobileAuthApp/app/src/main/res/values-et/strings.xml b/MobileAuthApp/app/src/main/res/values-et/strings.xml index 7b782cd..1dedbd0 100644 --- a/MobileAuthApp/app/src/main/res/values-et/strings.xml +++ b/MobileAuthApp/app/src/main/res/values-et/strings.xml @@ -1,45 +1,28 @@ - NFC autentija + NFC authentication + Work in progress - + LOE ID KAARTI + EDASI KATKESTA + SALVESTA + EI TAGASI - LISA CAN - ÜRITA UUESTI - JÄTKA - - - Kaart tuvastatud. Hoia kaarti vastu telefoni. - Andmed loetud, võid jätkata. - Vale CAN - ID kaardi tuvastamiseks pane kaart vastu telefoni - ID kaardi tuvastamiseks peab olema CAN lisatud - NFC ei ole sisse lülitatud või puudub telefonil NFC võimekus - Sisestatud CAN ei ole vastavuses ID kaardiga - ID kaart eemaldati liiga vara - Vale PIN 1. ID kaardil PIN 1 sisetamise kordi alles: %s PIN 1 on salvestatud PIN 1 ei ole salvestatud CAN on salvestatud CAN ei ole salvestatud - INFO - Mis on CAN? - CAN on 6 kohaline numbritest koosnev kood, mida on vaja ID kaardiga suhtlemiseks. CAN-i leiab ID kaardilt omaniku pildi alt pealkirjaga KASUTAJA ALLKIRI/HOLDER\'S SIGNATURE. - Probleem parameetritega - Puudub challenge parameeter - Puudub AuthUrl parameeter - Puudub OriginUrl parameeter - Täpsustamata probleem parameetritega + - Palun sisesta PIN 1 - PIN 1 - PIN 1 lubatud pikkus on 4..12 - Save PIN 1 - On - Off + Palun sisesta PIN 1 + PIN 1 + Näide. 1234 + PIN 1 lubatud pikkus on 4..12 + Praegu ei ole rakenduses PIN 1 salvestatud. Kas sa soovid sisestatud PIN 1-te salvestada? Sellisel juhul sisestatakse see järgmisel korral automaatselt. Salvestatud PIN 1-te saab alati menüüs muuta ja kustutada. + Salvesta PIN 1 Palun sisesta PIN 2 @@ -48,19 +31,27 @@ PIN 2 lubatud pikkus on 5..12 - Please enter CAN - CAN - CAN must be 6 digits long + Näide. 123456 + CAN + Sisesta ID kaardi CAN (Card Access Number) + CANi pikkus on vale + Kaart on tuvastatud. Hoia kaarti vastu telefoni. + Andmed loetud. Võid edasi minna. + Praegu ei ole rakenduses CAN salvestatud. Kas sa soovid sisestatud CANi salvestada? Sellisel juhul sisestatakse see järgmisel korral automaatselt. Salvestatud CANi saab alati menüüs muuta ja kustutada. Salvesta CAN - Pane ID kaart vastu telefoni + ID kaardiga ühenduse loomiseks pane kaart vastu telefoni Aega on jäänud %d sek Aeg on otsas + Tundmatu viga + Vale CAN + Ühendus seadme ja kaardi vahel katkes NIMI %1$s %2$s ISIKUKOOD + UNUSTA KEHTIV KUNI KODAKONDSUS SUGU @@ -71,7 +62,10 @@ Seaded + Keel + Toiming pole hetkel saadaval CAN: %s + Lisa CAN Kustuta CAN PIN1: %s Lisa PIN1 @@ -80,7 +74,8 @@ NÄITA PEIDA **** - Seaded pole hetkel saadaval - CAN kustatud - PIN 1 kustatud + Seaded pole hetkel saadaval + Ei saanud ID-kaardilt andmeid lugeda + Rakendusesisene viga + ID-kaardilt loeti vigased andmed, proovi uuesti kaarti kasutada \ No newline at end of file diff --git a/MobileAuthApp/app/src/main/res/values/strings.xml b/MobileAuthApp/app/src/main/res/values/strings.xml index af966a3..ac0d78b 100644 --- a/MobileAuthApp/app/src/main/res/values/strings.xml +++ b/MobileAuthApp/app/src/main/res/values/strings.xml @@ -1,44 +1,27 @@ - NFC authenticator + NFC authentication + Work in progress - + READ ID CARD + NEXT CANCEL + SAVE + NO BACK - ADD CAN - TRY AGAIN - CONTINUE - - - Card detected. Hold it against the phone. - Data read. You can continue. - Wrong CAN - Put the ID card against the phone to detect it - CAN must be added before ID card can be detected - NFC is not turned on or is not supported by the phone - The provided CAN does not match the ID card - ID card was removed too early - Wrong PIN 1. Tries on the card left %s PIN 1 saved PIN 1 not saved CAN saved CAN not saved - HELP - What is CAN? - CAN is a 6 digit code that is needed to communicate with an ID card. It can be found on the ID card under the card holder\'s picture with a title KASUTAJA ALLKIRI/HOLDER\'S SIGNATURE. - Problem with parameters - Challenge is missing - AuthUrl is missing - OriginUrl is missing - Unspecified problem with parameters + - Please enter PIN 1 - PIN 1 - PIN 1 must be 4–12 digits long - Save PIN 1 - On - Off + Please enter PIN 1 + PIN 1 + Example. 1234 + Allowed length for PIN 1 is 4..12 + PIN 1 is currently not saved. Do you wish to save the entered PIN 1? Saved PIN 1 will be entered automatically in the future. Saved PIN 1 can be changed and deleted in the settings menu. + Save PIN 1 Please enter PIN 2 @@ -47,14 +30,22 @@ Allowed length for PIN 2 is 5..12 - Please enter CAN - CAN - CAN must be 6 digits long + Example. 123456 + CAN + Enter ID card\'s CAN (Card Access Number) + Length of the CAN is wrong + Card detected. Hold it against the phone. + Data read. You can continue. + CAN is currently not saved. Do you wish to save the CAN? Saved CAN will be entered automatically in the future. Saved CAN can be changed and deleted in the settings menu. + Save CAN - Put the ID card against the phone + Put the ID card against the phone to establish connection Time left %d sek No time left + Unknown error + Wrong CAN + Connection between device and ID-card lost NAME @@ -63,14 +54,18 @@ DATE OF EXPIRY CITIZENSHIP SEX + FORGET Controlling the created token - The app will close automatically + Wait for the app to close Settings + Language + Currently unavailable CAN: %s + Add CAN Delete CAN PIN1: %s Add PIN 1 @@ -79,7 +74,8 @@ SHOW HIDE **** - Settings are currently unavailable - CAN deleted - PIN 1 deleted + Settings currently unavailable + Failed to read data from the ID-card + Internal error + Read bad data from the ID-card, try using the card again \ No newline at end of file