6 Commits

Author SHA1 Message Date
stargateprovider
1c7e51d69a Merge remote-tracking branch 'origin/MOB-24-user_notifications' into MOB-24-user_notifications
# Conflicts:
#	MobileAuthApp/app/src/main/java/com/tarkvaraprojekt/mobileauthapp/AuthFragment.kt
#	MobileAuthApp/app/src/main/java/com/tarkvaraprojekt/mobileauthapp/CanFragment.kt
#	MobileAuthApp/app/src/main/java/com/tarkvaraprojekt/mobileauthapp/NFC/Comms.java
#	MobileAuthApp/app/src/main/res/values-en/strings.xml
#	MobileAuthApp/app/src/main/res/values-et/strings.xml
#	MobileAuthApp/app/src/main/res/values/strings.xml
2021-12-06 23:26:14 +02:00
stargateprovider
b623dadff5 Rebase and/or merge with main 2021-12-06 23:18:01 +02:00
stargateprovider
4fcdccfb5e More specific errors and notifications 2021-12-06 22:48:08 +02:00
stargateprovider
70991ae682 Added basic notifications to the user
For when exceptions occur when communicating with the ID-card
2021-12-06 22:47:57 +02:00
stargateprovider
dbc758fb14 More specific errors and notifications 2021-12-04 21:22:54 +02:00
stargateprovider
1037d672d5 Merge branch 'main' into MOB-24-user_notifications
# Conflicts:
#	MobileAuthApp/app/src/main/java/com/tarkvaraprojekt/mobileauthapp/AuthFragment.kt
#	MobileAuthApp/app/src/main/java/com/tarkvaraprojekt/mobileauthapp/NFC/Comms.java
2021-12-04 19:12:25 +02:00
8 changed files with 466 additions and 360 deletions

View File

@@ -44,9 +44,7 @@ dependencies {
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test:rules:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
debugImplementation 'androidx.fragment:fragment-testing:1.4.0'
//To use activityViewModels
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"

View File

@@ -1,62 +0,0 @@
package com.tarkvaraprojekt.mobileauthapp
//import androidx.fragment.app.testing.launchFragmentInContainer
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.IdlingPolicies
import androidx.test.espresso.NoMatchingViewException
import androidx.test.espresso.action.ViewActions.*
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.*
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.rule.ActivityTestRule
import org.junit.*
import org.junit.runner.RunWith
import java.util.concurrent.TimeUnit
@RunWith(AndroidJUnit4::class)
class UC4Test {
@get:Rule
var activityActivityTestRule: ActivityTestRule<MainActivity> = ActivityTestRule(
MainActivity::class.java
)
@Before
fun setUp() {
IdlingPolicies.setMasterPolicyTimeout(3, TimeUnit.SECONDS)
IdlingPolicies.setIdlingResourceTimeout(3, TimeUnit.SECONDS)
activityActivityTestRule.activity
.supportFragmentManager.beginTransaction()
}
@After
fun tearDown() {
}
fun navigateToCANView() {
onView(withId(R.id.menu_settings_option)).perform(click())
try {
// Delete existing CAN
onView(withText(R.string.can_delete)).perform(click())
} catch (ignore: NoMatchingViewException) {}
onView(withId(R.id.can_menu_action)).perform(click())
}
@Test
fun validCAN() {
navigateToCANView()
onView(withText(R.string.can_helper_text)).check(matches(isDisplayed()))
onView(supportsInputMethods()).perform(typeText("123456"))
onView(withText(R.string.can_delete)).perform(closeSoftKeyboard())
onView(withText(R.string.can_status_saved)).check(matches(isDisplayed()))
}
@Test
fun invalidCAN() {
navigateToCANView()
onView(supportsInputMethods()).perform(typeText("12345"))
onView(withText(R.string.can_helper_text)).check(matches(isDisplayed()))
}
}

View File

@@ -1,10 +1,13 @@
package com.tarkvaraprojekt.mobileauthapp
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.nfc.NfcAdapter
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
@@ -15,7 +18,9 @@ import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import com.tarkvaraprojekt.mobileauthapp.NFC.Comms
import com.tarkvaraprojekt.mobileauthapp.auth.AuthAppException
import com.tarkvaraprojekt.mobileauthapp.auth.Authenticator
import com.tarkvaraprojekt.mobileauthapp.auth.InvalidCANException
import com.tarkvaraprojekt.mobileauthapp.auth.InvalidPINException
import com.tarkvaraprojekt.mobileauthapp.databinding.FragmentAuthBinding
import com.tarkvaraprojekt.mobileauthapp.model.ParametersViewModel
import com.tarkvaraprojekt.mobileauthapp.model.SmartCardViewModel
@@ -33,9 +38,10 @@ class AuthFragment : Fragment() {
private val viewModel: SmartCardViewModel by activityViewModels()
private val intentParameters: ParametersViewModel by activityViewModels()
private val paramsModel: ParametersViewModel by activityViewModels()
private var binding: FragmentAuthBinding? = null
private var _binding: FragmentAuthBinding? = null
private val binding get() = _binding!!
private val args: CanFragmentArgs by navArgs()
@@ -48,8 +54,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?) {
@@ -58,116 +64,117 @@ 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)
goToTheStart()
cancelAuth()
}
}.start()
binding!!.nextButton.setOnClickListener { goToNextFragment() }
binding!!.cancelButton.setOnClickListener { goToTheStart() }
// 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() }
val adapter = NfcAdapter.getDefaultAdapter(activity)
if (adapter != null)
getInfoFromIdCard(adapter)
}
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()
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 goToNextFragment() {
timer.cancel()
if (args.auth) {
val action = AuthFragmentDirections.actionAuthFragmentToResultFragment(mobile = args.mobile)
findNavController().navigate(action)
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()
} else {
findNavController().navigate(R.id.action_authFragment_to_userFragment)
requireActivity().finishAndRemoveTask()
}
}
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()
private fun getInfoFromIdCard(adapter: NfcAdapter) {
adapter.enableReaderMode(activity, { tag ->
timer.cancel()
requireActivity().runOnUiThread {
binding.timeCounter.text = getString(R.string.card_detected)
}
}
var msgCode = 0
var msgArg : Int? = null
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 {
binding.timeCounter.text = getString(R.string.data_read)
goToNextFragment()
}
} catch (e: android.nfc.TagLostException) {
msgCode = R.string.tag_lost
} catch (e: InvalidCANException) {
msgCode = R.string.wrong_can_text
// 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: InvalidPINException) {
msgCode = R.string.wrong_pin
msgArg = e.remainingAttempts
viewModel.deletePin(requireContext())
} catch (e: AuthAppException) {
msgCode = when (e.code) {
400 -> R.string.err_parameter
401 -> R.string.err_authentication
446 -> R.string.err_card_locked
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 {
var msg = getString(msgCode)
if (msgArg != null)
msg = String.format(msg, msgArg)
binding.timeCounter.text = msg
}
// Gives user some time to read the error message
Thread.sleep(2000)
cancelAuth()
}
}
}, NfcAdapter.FLAG_READER_NFC_A, null)
}
override fun onDestroy() {
super.onDestroy()
binding = null
_binding = null
}
}

View File

@@ -4,7 +4,7 @@ import android.nfc.tech.IsoDep;
import android.util.Log;
import com.tarkvaraprojekt.mobileauthapp.auth.AuthAppException;
import com.tarkvaraprojekt.mobileauthapp.auth.InvalidCANException;
import com.tarkvaraprojekt.mobileauthapp.auth.InvalidPINException;
import org.bouncycastle.crypto.BlockCipher;
import org.bouncycastle.crypto.engines.AESEngine;
@@ -24,6 +24,7 @@ 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;
@@ -33,47 +34,43 @@ 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[] 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[] masterSec = {
12, -92, 4, 12, 45, -121, 33, 1
};
private static final byte[] dataForMACIncomplete = Hex.decode("7f494f060a04007f000702020402048641");
private static final byte[] personal = { // select personal data DF
12, -92, 1, 12, 29, -121, 17, 1
};
private static final byte[] selectFile = Hex.decode("0ca4010c1d871101");
private static final byte[] read = { // read binary
12, -80, 0, 0, 13, -105, 1, 0
};
private static final byte[] readFile = Hex.decode("0cb000000d970100");
private IsoDep idCard;
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 final byte[] keyEnc;
private final byte[] keyMAC;
private byte ssc; // Send sequence counter.
@@ -87,21 +84,12 @@ 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));
byte[][] keys = PACE(CAN.getBytes(StandardCharsets.UTF_8));
keyEnc = keys[0];
keyMAC = keys[1];
}
public byte[] getAuthenticationCertificate() {
return new byte[0];
}
/**
* Calculates the message authentication code
*
@@ -154,40 +142,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 AuthAppException(String.format("%s failed.", log), 500);
}
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 ECC applet on the chip
byte[] response = idCard.transceive(master);
Log.i("Select applet", Hex.toHexString(response));
// select the IAS-ECC application on the chip
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
@@ -195,35 +194,41 @@ 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 InvalidCANException();
// 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 AuthAppException("Could not verify chip's MAC.", 448); // 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 AuthAppException(String.format("Could not read %s", info), 500);
}
return encryptDecryptData(Arrays.copyOfRange(response, 3, 19), Cipher.DECRYPT_MODE);
}
/**
* Encrypts or decrypts the APDU data
*
@@ -258,74 +263,182 @@ 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
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);
}
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;
}
/**
* 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
* Selects a FILE by its identifier
*
*/
public String[] readPersonalData(byte[] FID) throws NoSuchPaddingException, InvalidKeyException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException, IOException {
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 AuthAppException(String.format("Could not select %s", info), 500);
}
}
String[] personalData = new String[FID.length];
byte[] data;
byte[] APDU;
byte[] response;
/**
* 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
*/
public String[] readPersonalData(byte[] lastBytes) 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");
// select the personal data dedicated file
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));
selectFile(personalDF, "the personal data DF");
// select and read the first 8 elementary files in the DF
for (int i = 0; i < FID.length; i++) {
byte[] FID = Arrays.copyOf(personalDF, personalDF.length);
// select and read the personal data elementary files
for (byte index : lastBytes) {
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));
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));
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));
}
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 AuthAppException("Invalid PIN. Authentication method blocked.", 446);
} else {
throw new InvalidPINException(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 AuthAppException("Wrong read parameters.", 400);
}
// 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 AuthAppException("Setting the environment failed.", 500);
}
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 AuthAppException("Signing the token failed.", 500);
}
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;
}
}

View File

@@ -0,0 +1,10 @@
package com.tarkvaraprojekt.mobileauthapp.auth
/**
* An AuthAppException for when the user entered PIN is not correct
* @see AuthAppException
*/
class InvalidPINException(val remainingAttempts: Int) : AuthAppException(
"Invalid PIN" + (if (remainingAttempts>0) "" else ". Authentication method blocked."),
if (remainingAttempts>0) 401 else 446
)

View File

@@ -1,29 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Must translate to English, but should work now -->
<string name="app_name">NFC authentication</string>
<string name="home_fragment">Work in progress</string>
<string name="app_name">NFC authenticator</string>
<!-- BUTTONS -->
<string name="cancel_text">CANCEL</string>
<string name="return_text">BACK</string>
<string name="add_can_text">ADD CAN</string>
<string name="try_again_text">TRY AGAIN</string>
<string name="continue_button">CONTINUE</string>
<!-- Card Detection related -->
<string name="card_detected">Card detected. Hold it against the phone.</string>
<string name="data_read">Data read. You can continue.</string>
<string name="wrong_can_text">Wrong CAN</string>
<string name="action_detect">Put the ID card against the phone to detect it</string>
<string name="action_detect_unavailable">CAN must be added before ID card can be detected</string>
<string name="nfc_not_available">NFC is not turned on or is not supported by the phone</string>
<string name="nfc_reading_error">The provided CAN does not match the ID card</string>
<string name="id_card_removed_early">ID card was removed too early</string>
<string name="wrong_pin">Wrong PIN 1. %s tries left on the card</string>
<!-- string resources for HomeFragment -->
<string name="pin_status_saved">PIN 1 saved</string>
<string name="pin_status_negative">PIN 1 not saved</string>
<string name="can_status_saved">CAN saved</string>
<string name="can_status_negative">CAN not saved</string>
<string name="begin_text">READ ID CARD</string>
<string name="next_text">NEXT</string>
<string name="cancel_text">CANCEL</string>
<string name="save_text">SAVE</string>
<string name="deny_text">NO</string>
<string name="return_text">BACK</string>
<string name="help_text">HELP</string>
<string name="can_question">What is CAN?</string>
<string name="can_explanation">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.</string>
<string name="problem_parameters">Problem with parameters</string>
<string name="problem_challenge">Challenge is missing</string>
<string name="problem_authurl">AuthUrl is missing</string>
<string name="problem_originurl">OriginUrl is missing</string>
<string name="problem_other">Unspecified problem with parameters</string>
<!-- string resources for PinFragment -->
<string name="pin_fragment">Please enter PIN 1</string>
<string name="enter_pin">PIN 1</string>
<string name="example_pin">Example. 1234</string>
<string name="length_pin">Allowed length for PIN 1 is 4..12</string>
<string name="pin_save_request">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.</string>
<string name="save_pin_title">Save PIN 1</string>
<string name="pin_view">Please enter PIN 1</string>
<string name="hint_pin">PIN 1</string>
<string name="pin_helper_text">PIN 1 must be 412 digits long</string>
<string name="save_pin">Save PIN 1</string>
<string name="pin_save_on">On</string>
<string name="pin_save_off">Off</string>
<!-- string resources for Pin2Fragment -->
<string name="pin2_fragment">Please enter PIN 2</string>
@@ -32,21 +49,14 @@
<string name="length_pin2">Allowed length for PIN 2 is 5..12</string>
<!-- string resources for CanFragment -->
<string name="example_can">Example. 123456</string>
<string name="text_can">CAN</string>
<string name="enter_can">Enter ID card\'s CAN (Card Access Number)</string>
<string name="length_can">Length of the CAN is wrong</string>
<string name="card_detected">Card detected. Hold it against the phone.</string>
<string name="data_read">Data read. You can continue.</string>
<string name="save_can_title">Save CAN</string>
<string name="can_view">Please enter CAN</string>
<string name="can_text">CAN</string>
<string name="can_helper_text">CAN must be 6 digits long</string>
<!-- string resources for AuthFragment layout -->
<string name="auth_instruction_text">Put the ID card against the phone to establish connection</string>
<string name="auth_instruction_text">Put the ID card against the phone</string>
<string name="time_left">Time left %d sek</string>
<string name="no_time">No time left</string>
<string name="err_unknown">Unknown error</string>
<string name="invalid_can">Wrong CAN</string>
<string name="tag_lost">Connection between device and ID-card lost</string>
<!-- string resources for UserFragment layout -->
<string name="user_name_label">NAME</string>
@@ -55,29 +65,32 @@
<string name="expiration_label">DATE OF EXPIRY</string>
<string name="citizenship_label">CITIZENSHIP</string>
<string name="gender_label">SEX</string>
<string name="clear_button">FORGET</string>
<!-- string resources for ResultFragment layout-->
<string name="result_text">Controlling the created token</string>
<string name="result_info">Wait for the app to close</string>
<string name="result_info">The app will close automatically</string>
<!-- menu -->
<string name="menu_settings_title">Settings</string>
<string name="menu_language_title">Language</string>
<string name="menu_action_unavailable">Currently unavailable</string>
<string name="saved_can">CAN: %s</string>
<string name="can_add">Add CAN</string>
<string name="can_delete">Delete CAN</string>
<string name="saved_pin">PIN1: %s</string>
<string name="pin1_add">Add PIN1</string>
<string name="pin1_delete">Delete PIN1</string>
<string name="pin1_add">Add PIN 1</string>
<string name="pin1_delete">Delete PIN 1</string>
<string name="missing">not saved</string>
<string name="show">SHOW</string>
<string name="hide">HIDE</string>
<string name="hidden_pin">****</string>
<string name="unavailable">Settings currently unavailabe</string>
<string name="can_save_request">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.</string>
<string name="menu_unavailable_message">Settings are currently unavailable</string>
<string name="can_deleted">CAN deleted</string>
<string name="pin_deleted">PIN 1 deleted</string>
<string name="err_unknown">Unknown error</string>
<string name="tag_lost">Connection between device and ID-card lost</string>
<string name="err_reading_card">Failed to read data from the ID-card</string>
<string name="err_internal">Internal error</string>
<string name="err_bad_data">Read bad data from the ID-card, try using the card again</string>
<string name="err_parameter">Required parameter is missing or invalid</string>
<string name="err_authentication">Failed to authenticate</string>
<string name="err_card_locked">Card locked</string>
</resources>

View File

@@ -1,28 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">NFC authentication</string>
<string name="home_fragment">Work in progress</string>
<string name="app_name">NFC autentija</string>
<string name="begin_text">LOE ID KAARTI</string>
<string name="next_text">EDASI</string>
<!-- Buttons -->
<string name="cancel_text">KATKESTA</string>
<string name="save_text">SALVESTA</string>
<string name="deny_text">EI</string>
<string name="return_text">TAGASI</string>
<string name="add_can_text">LISA CAN</string>
<string name="try_again_text">ÜRITA UUESTI</string>
<string name="continue_button">JÄTKA</string>
<!-- Card Detection related -->
<string name="card_detected">Kaart tuvastatud. Hoia kaarti vastu telefoni.</string>
<string name="data_read">Andmed loetud, võid jätkata.</string>
<string name="wrong_can_text">Vale CAN</string>
<string name="action_detect">ID kaardi tuvastamiseks pane kaart vastu telefoni</string>
<string name="action_detect_unavailable">ID kaardi tuvastamiseks peab olema CAN lisatud</string>
<string name="nfc_not_available">NFC ei ole sisse lülitatud või puudub telefonil NFC võimekus</string>
<string name="nfc_reading_error">Sisestatud CAN ei ole vastavuses ID kaardiga</string>
<string name="id_card_removed_early">ID kaart eemaldati liiga vara</string>
<string name="wrong_pin">Vale PIN 1. ID kaardil PIN 1 sisetamise katseid järel: %s</string>
<!-- string resources for HomeFragment -->
<string name="pin_status_saved">PIN 1 on salvestatud</string>
<string name="pin_status_negative">PIN 1 ei ole salvestatud</string>
<string name="can_status_saved">CAN on salvestatud</string>
<string name="can_status_negative">CAN ei ole salvestatud</string>
<string name="help_text">INFO</string>
<string name="can_question">Mis on CAN?</string>
<string name="can_explanation">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.</string>
<string name="problem_parameters">Probleem parameetritega</string>
<string name="problem_challenge">Puudub challenge parameeter</string>
<string name="problem_authurl">Puudub AuthUrl parameeter</string>
<string name="problem_originurl">Puudub OriginUrl parameeter</string>
<string name="problem_other">Täpsustamata probleem parameetritega</string>
<!-- string resources for PinFragment -->
<string name="pin_fragment">Palun sisesta PIN 1</string>
<string name="enter_pin">PIN 1</string>
<string name="example_pin">Näide. 1234</string>
<string name="length_pin">PIN 1 lubatud pikkus on 4..12</string>
<string name="pin_save_request">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.</string>
<string name="save_pin_title">Salvesta PIN 1</string>
<string name="pin_view">Palun sisesta PIN 1</string>
<string name="hint_pin">PIN 1</string>
<string name="pin_helper_text">PIN 1 lubatud pikkus on 4..12</string>
<string name="save_pin">Save PIN 1</string>
<string name="pin_save_on">On</string>
<string name="pin_save_off">Off</string>
<!-- string resources for Pin2Fragment -->
<string name="pin2_fragment">Palun sisesta PIN 2</string>
@@ -31,27 +48,19 @@
<string name="length_pin2">PIN 2 lubatud pikkus on 5..12</string>
<!-- string resources for CanFragment -->
<string name="example_can">Näide. 123456</string>
<string name="text_can">CAN</string>
<string name="enter_can">Sisesta ID kaardi CAN (Card Access Number)</string>
<string name="length_can">CANi pikkus on vale</string>
<string name="card_detected">Kaart on tuvastatud. Hoia kaarti vastu telefoni.</string>
<string name="data_read">Andmed loetud. Võid edasi minna.</string>
<string name="can_save_request">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.</string> <string name="save_can_title">Salvesta CAN</string>
<string name="can_view">Please enter CAN</string>
<string name="can_text">CAN</string>
<string name="can_helper_text">CAN must be 6 digits long</string>
<!-- string resources for AuthFragment layout -->
<string name="auth_instruction_text">ID kaardiga ühenduse loomiseks pane kaart vastu telefoni</string>
<string name="auth_instruction_text">Pane ID kaart vastu telefoni</string>
<string name="time_left">Aega on jäänud %d sek</string>
<string name="no_time">Aeg on otsas</string>
<string name="err_unknown">Tundmatu viga</string>
<string name="invalid_can">Vale CAN</string>
<string name="tag_lost">Ühendus seadme ja kaardi vahel katkes</string>
<!-- string resources for UserFragment layout -->
<string name="user_name_label">NIMI</string>
<string name="user_name">%1$s %2$s</string>
<string name="identification_number_label">ISIKUKOOD</string>
<string name="clear_button">UNUSTA</string>
<string name="expiration_label">KEHTIV KUNI</string>
<string name="citizenship_label">KODAKONDSUS</string>
<string name="gender_label">SUGU</string>
@@ -62,10 +71,7 @@
<!-- menu -->
<string name="menu_settings_title">Seaded</string>
<string name="menu_language_title">Keel</string>
<string name="menu_action_unavailable">Toiming pole hetkel saadaval</string>
<string name="saved_can">CAN: %s</string>
<string name="can_add">Lisa CAN</string>
<string name="can_delete">Kustuta CAN</string>
<string name="saved_pin">PIN1: %s</string>
<string name="pin1_add">Lisa PIN1</string>
@@ -74,8 +80,16 @@
<string name="show">NÄITA</string>
<string name="hide">PEIDA</string>
<string name="hidden_pin">****</string>
<string name="unavailable">Seaded pole hetkel saadaval</string>
<string name="menu_unavailable_message">Seaded pole hetkel saadaval</string>
<string name="can_deleted">CAN kustatud</string>
<string name="pin_deleted">PIN 1 kustatud</string>
<string name="err_unknown">Tundmatu viga</string>
<string name="tag_lost">Ühendus seadme ja kaardi vahel katkes</string>
<string name="err_reading_card">Ei saanud ID-kaardilt andmeid lugeda</string>
<string name="err_internal">Rakendusesisene viga</string>
<string name="err_bad_data">ID-kaardilt loeti vigased andmed, proovi uuesti kaarti kasutada</string>
<string name="err_parameter">Vigane või puuduv parameeter</string>
<string name="err_authentication">Autentimine ebaõnnestus</string>
<string name="err_card_locked">Kaart lukus</string>
</resources>

View File

@@ -1,27 +1,44 @@
<resources>
<string name="app_name">NFC authentication</string>
<string name="home_fragment">Work in progress</string>
<string name="app_name">NFC authenticator</string>
<string name="begin_text">READ ID CARD</string>
<string name="next_text">NEXT</string>
<!-- BUTTONS -->
<string name="cancel_text">CANCEL</string>
<string name="save_text">SAVE</string>
<string name="deny_text">NO</string>
<string name="return_text">BACK</string>
<string name="add_can_text">ADD CAN</string>
<string name="try_again_text">TRY AGAIN</string>
<string name="continue_button">CONTINUE</string>
<!-- Card Detection related -->
<string name="card_detected">Card detected. Hold it against the phone.</string>
<string name="data_read">Data read. You can continue.</string>
<string name="wrong_can_text">Wrong CAN</string>
<string name="action_detect">Put the ID card against the phone to detect it</string>
<string name="action_detect_unavailable">CAN must be added before ID card can be detected</string>
<string name="nfc_not_available">NFC is not turned on or is not supported by the phone</string>
<string name="nfc_reading_error">The provided CAN does not match the ID card</string>
<string name="id_card_removed_early">ID card was removed too early</string>
<string name="wrong_pin">Wrong PIN 1. %s tries left on the card</string>
<!-- string resources for HomeFragment -->
<string name="pin_status_saved">PIN 1 saved</string>
<string name="pin_status_negative">PIN 1 not saved</string>
<string name="can_status_saved">CAN saved</string>
<string name="can_status_negative">CAN not saved</string>
<string name="help_text">HELP</string>
<string name="can_question">What is CAN?</string>
<string name="can_explanation">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.</string>
<string name="problem_parameters">Problem with parameters</string>
<string name="problem_challenge">Challenge is missing</string>
<string name="problem_authurl">AuthUrl is missing</string>
<string name="problem_originurl">OriginUrl is missing</string>
<string name="problem_other">Unspecified problem with parameters</string>
<!-- string resources for PinFragment -->
<string name="pin_fragment">Please enter PIN 1</string>
<string name="enter_pin">PIN 1</string>
<string name="example_pin">Example. 1234</string>
<string name="length_pin">Allowed length for PIN 1 is 4..12</string>
<string name="pin_save_request">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.</string>
<string name="save_pin_title">Save PIN 1</string>
<string name="pin_view">Please enter PIN 1</string>
<string name="hint_pin">PIN 1</string>
<string name="pin_helper_text">PIN 1 must be 412 digits long</string>
<string name="save_pin">Save PIN 1</string>
<string name="pin_save_on">On</string>
<string name="pin_save_off">Off</string>
<!-- string resources for Pin2Fragment -->
<string name="pin2_fragment">Please enter PIN 2</string>
@@ -30,22 +47,14 @@
<string name="length_pin2">Allowed length for PIN 2 is 5..12</string>
<!-- string resources for CanFragment -->
<string name="example_can">Example. 123456</string>
<string name="text_can">CAN</string>
<string name="enter_can">Enter ID card\'s CAN (Card Access Number)</string>
<string name="length_can">Length of the CAN is wrong</string>
<string name="card_detected">Card detected. Hold it against the phone.</string>
<string name="data_read">Data read. You can continue.</string>
<string name="can_save_request">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.</string>
<string name="save_can_title">Save CAN</string>
<string name="can_view">Please enter CAN</string>
<string name="can_text">CAN</string>
<string name="can_helper_text">CAN must be 6 digits long</string>
<!-- string resources for AuthFragment layout -->
<string name="auth_instruction_text">Put the ID card against the phone to establish connection</string>
<string name="auth_instruction_text">Put the ID card against the phone</string>
<string name="time_left">Time left %d sek</string>
<string name="no_time">No time left</string>
<string name="err_unknown">Unknown error</string>
<string name="invalid_can">Wrong CAN</string>
<string name="tag_lost">Connection between device and ID-card lost</string>
<!-- string resources for UserFragment layout -->
<string name="user_name_label">NAME</string>
@@ -54,18 +63,14 @@
<string name="expiration_label">DATE OF EXPIRY</string>
<string name="citizenship_label">CITIZENSHIP</string>
<string name="gender_label">SEX</string>
<string name="clear_button">FORGET</string>
<!-- string resources for ResultFragment layout-->
<string name="result_text">Controlling the created token</string>
<string name="result_info">Wait for the app to close</string>
<string name="result_info">The app will close automatically</string>
<!-- menu -->
<string name="menu_settings_title">Settings</string>
<string name="menu_language_title">Language</string>
<string name="menu_action_unavailable">Currently unavailable</string>
<string name="saved_can">CAN: %s</string>
<string name="can_add">Add CAN</string>
<string name="can_delete">Delete CAN</string>
<string name="saved_pin">PIN1: %s</string>
<string name="pin1_add">Add PIN 1</string>
@@ -74,8 +79,16 @@
<string name="show">SHOW</string>
<string name="hide">HIDE</string>
<string name="hidden_pin">****</string>
<string name="unavailable">Settings currently unavailable</string>
<string name="menu_unavailable_message">Settings are currently unavailable</string>
<string name="can_deleted">CAN deleted</string>
<string name="pin_deleted">PIN 1 deleted</string>
<string name="err_unknown">Unknown error</string>
<string name="tag_lost">Connection between device and ID-card lost</string>
<string name="err_reading_card">Failed to read data from the ID-card</string>
<string name="err_internal">Internal error</string>
<string name="err_bad_data">Read bad data from the ID-card, try using the card again</string>
<string name="err_parameter">Required parameter is missing or invalid</string>
<string name="err_authentication">Failed to authenticate</string>
<string name="err_card_locked">Card locked</string>
</resources>