mirror of
https://github.com/TanelOrumaa/Estonian-ID-card-mobile-authenticator-POC.git
synced 2025-08-30 07:10:59 +03:00
Compare commits
33 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
1cd86cc4f8 | ||
|
634672db0f | ||
|
be5c0568f8 | ||
|
b8c2da95a2 | ||
|
624ebf3999 | ||
|
7dde3239a0 | ||
|
0dfeb798e3 | ||
|
371a871f87 | ||
|
8793ed9179 | ||
|
0c205eea8d | ||
|
8fe7aed941 | ||
|
57de1bf979 | ||
|
5c1f141405 | ||
|
a61ea0b6cc | ||
|
8d5a55c00e | ||
|
bf87eb1c07 | ||
|
7e14bc289e | ||
|
f274b48d68 | ||
|
515eea14bb | ||
|
e4a06b4fc9 | ||
|
24980b3253 | ||
|
6ddfe9af46 | ||
|
60207319b7 | ||
|
67ba0ed764 | ||
|
da2ba0b0da | ||
|
73b94adcd3 | ||
|
339fa0a378 | ||
|
e5300dfa5e | ||
|
d4c2a11521 | ||
|
0e15bede78 | ||
|
09c4fa6be3 | ||
|
63bc89b0e4 | ||
|
73efb00368 |
@@ -44,7 +44,9 @@ dependencies {
|
|||||||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||||
testImplementation 'junit:junit:4.+'
|
testImplementation 'junit:junit:4.+'
|
||||||
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
||||||
|
androidTestImplementation 'androidx.test:rules:1.2.0'
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
||||||
|
debugImplementation 'androidx.fragment:fragment-testing:1.4.0'
|
||||||
|
|
||||||
//To use activityViewModels
|
//To use activityViewModels
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||||
|
@@ -0,0 +1,62 @@
|
|||||||
|
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()))
|
||||||
|
}
|
||||||
|
}
|
@@ -1,14 +1,10 @@
|
|||||||
package com.tarkvaraprojekt.mobileauthapp
|
package com.tarkvaraprojekt.mobileauthapp
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.nfc.NfcAdapter
|
import android.nfc.NfcAdapter
|
||||||
import android.nfc.TagLostException
|
|
||||||
import android.nfc.tech.IsoDep
|
import android.nfc.tech.IsoDep
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.CountDownTimer
|
import android.os.CountDownTimer
|
||||||
import android.util.Log
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
@@ -18,11 +14,14 @@ import androidx.fragment.app.activityViewModels
|
|||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
import androidx.navigation.fragment.navArgs
|
import androidx.navigation.fragment.navArgs
|
||||||
import com.tarkvaraprojekt.mobileauthapp.NFC.Comms
|
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.databinding.FragmentAuthBinding
|
||||||
import com.tarkvaraprojekt.mobileauthapp.model.ParametersViewModel
|
import com.tarkvaraprojekt.mobileauthapp.model.ParametersViewModel
|
||||||
import com.tarkvaraprojekt.mobileauthapp.model.SmartCardViewModel
|
import com.tarkvaraprojekt.mobileauthapp.model.SmartCardViewModel
|
||||||
|
import java.io.IOException
|
||||||
import java.lang.Exception
|
import java.lang.Exception
|
||||||
|
import java.security.GeneralSecurityException
|
||||||
import kotlin.system.exitProcess
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -34,7 +33,7 @@ class AuthFragment : Fragment() {
|
|||||||
|
|
||||||
private val viewModel: SmartCardViewModel by activityViewModels()
|
private val viewModel: SmartCardViewModel by activityViewModels()
|
||||||
|
|
||||||
private val paramsModel: ParametersViewModel by activityViewModels()
|
private val intentParameters: ParametersViewModel by activityViewModels()
|
||||||
|
|
||||||
private var binding: FragmentAuthBinding? = null
|
private var binding: FragmentAuthBinding? = null
|
||||||
|
|
||||||
@@ -67,84 +66,104 @@ class AuthFragment : Fragment() {
|
|||||||
|
|
||||||
override fun onFinish() {
|
override fun onFinish() {
|
||||||
Thread.sleep(750)
|
Thread.sleep(750)
|
||||||
cancelAuth()
|
goToTheStart()
|
||||||
}
|
}
|
||||||
}.start()
|
}.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!!.nextButton.setOnClickListener { goToNextFragment() }
|
||||||
binding!!.cancelButton.setOnClickListener { cancelAuth() }
|
binding!!.cancelButton.setOnClickListener { goToTheStart() }
|
||||||
val adapter = NfcAdapter.getDefaultAdapter(activity)
|
val adapter = NfcAdapter.getDefaultAdapter(activity)
|
||||||
if (adapter != null)
|
if (adapter != null)
|
||||||
getInfoFromIdCard(adapter)
|
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() {
|
private fun goToNextFragment() {
|
||||||
timer.cancel()
|
timer.cancel()
|
||||||
val action = AuthFragmentDirections.actionAuthFragmentToResultFragment(mobile = args.mobile)
|
if (args.auth) {
|
||||||
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 {
|
} else {
|
||||||
requireActivity().finishAndRemoveTask()
|
findNavController().navigate(R.id.action_authFragment_to_userFragment)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getInfoFromIdCard(adapter: NfcAdapter) {
|
private fun goToTheStart() {
|
||||||
adapter.enableReaderMode(activity, { tag ->
|
viewModel.clearUserInfo()
|
||||||
timer.cancel()
|
timer.cancel()
|
||||||
requireActivity().runOnUiThread {
|
if (args.reading) {
|
||||||
binding!!.timeCounter.text = getString(R.string.card_detected)
|
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() {
|
override fun onDestroy() {
|
||||||
|
@@ -29,7 +29,8 @@ class CanFragment : Fragment() {
|
|||||||
|
|
||||||
private val viewModel: SmartCardViewModel by activityViewModels()
|
private val viewModel: SmartCardViewModel by activityViewModels()
|
||||||
|
|
||||||
private var binding: FragmentCanBinding? = null
|
private var _binding: FragmentCanBinding? = null
|
||||||
|
private val binding get() = _binding!!
|
||||||
|
|
||||||
// Navigation arguments:
|
// Navigation arguments:
|
||||||
// saving = true means that we are navigating here from the settings menu and must return to the settings menu.
|
// saving = true means that we are navigating here from the settings menu and must return to the settings menu.
|
||||||
@@ -40,17 +41,17 @@ class CanFragment : Fragment() {
|
|||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View? {
|
): View? {
|
||||||
binding = FragmentCanBinding.inflate(inflater, container, false)
|
_binding = FragmentCanBinding.inflate(inflater, container, false)
|
||||||
return binding!!.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
checkIfSkip()
|
checkIfSkip()
|
||||||
binding!!.canTextField.editText?.addTextChangedListener {
|
binding.canTextField.editText?.addTextChangedListener {
|
||||||
checkEnteredCan()
|
checkEnteredCan()
|
||||||
}
|
}
|
||||||
binding!!.buttonCancel.setOnClickListener { goToTheStart() }
|
binding.buttonCancel.setOnClickListener { goToTheStart() }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -112,7 +113,7 @@ class CanFragment : Fragment() {
|
|||||||
* allowed to modify the entered can.
|
* allowed to modify the entered can.
|
||||||
*/
|
*/
|
||||||
private fun checkEnteredCan() {
|
private fun checkEnteredCan() {
|
||||||
val enteredCan = binding!!.canTextField.editText?.text.toString()
|
val enteredCan = binding.canTextField.editText?.text.toString()
|
||||||
if (enteredCan.length == 6) {
|
if (enteredCan.length == 6) {
|
||||||
viewModel.setUserCan(enteredCan)
|
viewModel.setUserCan(enteredCan)
|
||||||
viewModel.storeCan(requireContext()) //Maybe storeCan should always automatically call setUserCan method as well because these methods usually are used together
|
viewModel.storeCan(requireContext()) //Maybe storeCan should always automatically call setUserCan method as well because these methods usually are used together
|
||||||
@@ -127,6 +128,6 @@ class CanFragment : Fragment() {
|
|||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
binding = null
|
_binding = null
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -39,7 +39,8 @@ class HomeFragment : Fragment() {
|
|||||||
|
|
||||||
private val intentParams: ParametersViewModel by activityViewModels()
|
private val intentParams: ParametersViewModel by activityViewModels()
|
||||||
|
|
||||||
private var binding: FragmentHomeBinding? = null
|
private var _binding: FragmentHomeBinding? = null
|
||||||
|
private val binding get() = _binding!!
|
||||||
|
|
||||||
// The ID card reader mode is enabled on the home fragment when can is saved.
|
// The ID card reader mode is enabled on the home fragment when can is saved.
|
||||||
private var canSaved: Boolean = false
|
private var canSaved: Boolean = false
|
||||||
@@ -54,10 +55,10 @@ class HomeFragment : Fragment() {
|
|||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View? {
|
): View? {
|
||||||
binding = FragmentHomeBinding.inflate(inflater, container, false)
|
_binding = FragmentHomeBinding.inflate(inflater, container, false)
|
||||||
// Making settings menu active again
|
// Making settings menu active again
|
||||||
(activity as MainActivity).menuAvailable = true
|
(activity as MainActivity).menuAvailable = true
|
||||||
return binding!!.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
@@ -111,15 +112,29 @@ class HomeFragment : Fragment() {
|
|||||||
intentParams.setAuthUrl(requireActivity().intent.data!!.getQueryParameter("authUrl")!!)
|
intentParams.setAuthUrl(requireActivity().intent.data!!.getQueryParameter("authUrl")!!)
|
||||||
intentParams.setOrigin(requireActivity().intent.data!!.getQueryParameter("originUrl")!!)
|
intentParams.setOrigin(requireActivity().intent.data!!.getQueryParameter("originUrl")!!)
|
||||||
}
|
}
|
||||||
|
goToTheNextFragment(mobile)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
// There was a problem with parameters, which means that authentication is not possible.
|
// There was a problem with parameters, which means that authentication is not possible.
|
||||||
// In that case we will cancel the authentication immediately as it would be waste of the user's time to carry on
|
// In that case we will cancel the authentication immediately as it would be waste of the user's time to carry on
|
||||||
// before getting an inevitable error.
|
// before getting an inevitable error.
|
||||||
val resultIntent = Intent()
|
val message = MaterialAlertDialogBuilder(requireContext())
|
||||||
requireActivity().setResult(AppCompatActivity.RESULT_CANCELED, resultIntent)
|
message.setTitle(getString(R.string.problem_parameters))
|
||||||
requireActivity().finish()
|
if (intentParams.challenge == "") {
|
||||||
|
message.setMessage(getString(R.string.problem_challenge))
|
||||||
|
} else if (intentParams.authUrl == "") {
|
||||||
|
message.setMessage(getString(R.string.problem_authurl))
|
||||||
|
} else if (intentParams.origin == "") {
|
||||||
|
message.setMessage(getString(R.string.problem_originurl))
|
||||||
|
} else {
|
||||||
|
message.setMessage(getString(R.string.problem_other))
|
||||||
|
}
|
||||||
|
message.setPositiveButton(getString(R.string.continue_button)) {_, _ ->
|
||||||
|
val resultIntent = Intent()
|
||||||
|
requireActivity().setResult(AppCompatActivity.RESULT_CANCELED, resultIntent)
|
||||||
|
requireActivity().finish()
|
||||||
|
}
|
||||||
|
message.show()
|
||||||
}
|
}
|
||||||
goToTheNextFragment(mobile)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -127,12 +142,12 @@ class HomeFragment : Fragment() {
|
|||||||
*/
|
*/
|
||||||
private fun canState() {
|
private fun canState() {
|
||||||
if (viewModel.userCan.length == 6) {
|
if (viewModel.userCan.length == 6) {
|
||||||
binding!!.canStatusText.text = getString(R.string.can_status_saved)
|
binding.canStatusText.text = getString(R.string.can_status_saved)
|
||||||
binding!!.canStatusLogo.setImageResource(R.drawable.ic_check_logo)
|
binding.canStatusLogo.setImageResource(R.drawable.ic_check_logo)
|
||||||
canSaved = true
|
canSaved = true
|
||||||
} else {
|
} else {
|
||||||
binding!!.canStatusText.text = getString(R.string.can_status_negative)
|
binding.canStatusText.text = getString(R.string.can_status_negative)
|
||||||
binding!!.canStatusLogo.setImageResource(R.drawable.ic_info_logo)
|
binding.canStatusLogo.setImageResource(R.drawable.ic_info_logo)
|
||||||
canSaved = false
|
canSaved = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -142,11 +157,11 @@ class HomeFragment : Fragment() {
|
|||||||
*/
|
*/
|
||||||
private fun pinState() {
|
private fun pinState() {
|
||||||
if (viewModel.userPin.length in 4..12) {
|
if (viewModel.userPin.length in 4..12) {
|
||||||
binding!!.pinStatusText.text = getString(R.string.pin_status_saved)
|
binding.pinStatusText.text = getString(R.string.pin_status_saved)
|
||||||
binding!!.pinStatusLogo.setImageResource(R.drawable.ic_check_logo)
|
binding.pinStatusLogo.setImageResource(R.drawable.ic_check_logo)
|
||||||
} else {
|
} else {
|
||||||
binding!!.pinStatusText.text = getString(R.string.pin_status_negative)
|
binding.pinStatusText.text = getString(R.string.pin_status_negative)
|
||||||
binding!!.pinStatusLogo.setImageResource(R.drawable.ic_info_logo)
|
binding.pinStatusLogo.setImageResource(R.drawable.ic_info_logo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,10 +187,10 @@ class HomeFragment : Fragment() {
|
|||||||
/**
|
/**
|
||||||
* Displays a help message to the user explaining what the CAN is
|
* Displays a help message to the user explaining what the CAN is
|
||||||
*/
|
*/
|
||||||
private fun displayMessage() {
|
private fun displayMessage(title: String, message: String) {
|
||||||
val dialog = MaterialAlertDialogBuilder(requireContext())
|
val dialog = MaterialAlertDialogBuilder(requireContext())
|
||||||
.setTitle(getString(R.string.can_question))
|
.setTitle(title)
|
||||||
.setMessage(getString(R.string.can_explanation))
|
.setMessage(message)
|
||||||
.setPositiveButton(R.string.return_text){_, _ -> }
|
.setPositiveButton(R.string.return_text){_, _ -> }
|
||||||
.show()
|
.show()
|
||||||
val title = dialog.findViewById<TextView>(R.id.alertTitle)
|
val title = dialog.findViewById<TextView>(R.id.alertTitle)
|
||||||
@@ -188,22 +203,22 @@ class HomeFragment : Fragment() {
|
|||||||
*/
|
*/
|
||||||
private fun updateAction(canIsSaved: Boolean) {
|
private fun updateAction(canIsSaved: Boolean) {
|
||||||
if (canIsSaved) {
|
if (canIsSaved) {
|
||||||
binding!!.detectionActionText.text = getString(R.string.action_detect)
|
binding.detectionActionText.text = getString(R.string.action_detect)
|
||||||
enableReaderMode()
|
enableReaderMode()
|
||||||
binding!!.homeActionButton.visibility = View.GONE
|
binding.homeActionButton.visibility = View.GONE
|
||||||
binding!!.homeHelpButton.visibility = View.GONE
|
binding.homeHelpButton.visibility = View.GONE
|
||||||
} else {
|
} else {
|
||||||
binding!!.detectionActionText.text = getString(R.string.action_detect_unavailable)
|
binding.detectionActionText.text = getString(R.string.action_detect_unavailable)
|
||||||
binding!!.homeActionButton.text = getString(R.string.add_can_text)
|
binding.homeActionButton.text = getString(R.string.add_can_text)
|
||||||
binding!!.homeActionButton.setOnClickListener {
|
binding.homeActionButton.setOnClickListener {
|
||||||
val action = HomeFragmentDirections.actionHomeFragmentToCanFragment(saving = true, fromhome = true)
|
val action = HomeFragmentDirections.actionHomeFragmentToCanFragment(saving = true, fromhome = true)
|
||||||
findNavController().navigate(action)
|
findNavController().navigate(action)
|
||||||
}
|
}
|
||||||
binding!!.homeHelpButton.setOnClickListener {
|
binding.homeHelpButton.setOnClickListener {
|
||||||
displayMessage()
|
displayMessage(getString(R.string.can_question), getString(R.string.can_explanation))
|
||||||
}
|
}
|
||||||
binding!!.homeActionButton.visibility = View.VISIBLE
|
binding.homeActionButton.visibility = View.VISIBLE
|
||||||
binding!!.homeHelpButton.visibility = View.VISIBLE
|
binding.homeHelpButton.visibility = View.VISIBLE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,11 +226,11 @@ class HomeFragment : Fragment() {
|
|||||||
* Resets the error message and allows the user to try again
|
* Resets the error message and allows the user to try again
|
||||||
*/
|
*/
|
||||||
private fun reset() {
|
private fun reset() {
|
||||||
binding!!.homeActionButton.text = getString(R.string.try_again_text)
|
binding.homeActionButton.text = getString(R.string.try_again_text)
|
||||||
binding!!.homeActionButton.setOnClickListener {
|
binding.homeActionButton.setOnClickListener {
|
||||||
updateAction(canSaved)
|
updateAction(canSaved)
|
||||||
}
|
}
|
||||||
binding!!.homeActionButton.visibility = View.VISIBLE
|
binding.homeActionButton.visibility = View.VISIBLE
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -224,11 +239,11 @@ class HomeFragment : Fragment() {
|
|||||||
private fun enableReaderMode() {
|
private fun enableReaderMode() {
|
||||||
val adapter = NfcAdapter.getDefaultAdapter(activity)
|
val adapter = NfcAdapter.getDefaultAdapter(activity)
|
||||||
if (adapter == null || !adapter.isEnabled) {
|
if (adapter == null || !adapter.isEnabled) {
|
||||||
binding!!.detectionActionText.text = getString(R.string.nfc_not_available)
|
binding.detectionActionText.text = getString(R.string.nfc_not_available)
|
||||||
} else {
|
} else {
|
||||||
adapter.enableReaderMode(activity, { tag ->
|
adapter.enableReaderMode(activity, { tag ->
|
||||||
requireActivity().runOnUiThread {
|
requireActivity().runOnUiThread {
|
||||||
binding!!.detectionActionText.text = getString(R.string.card_detected)
|
binding.detectionActionText.text = getString(R.string.card_detected)
|
||||||
}
|
}
|
||||||
val card = IsoDep.get(tag)
|
val card = IsoDep.get(tag)
|
||||||
card.timeout = 32768
|
card.timeout = 32768
|
||||||
@@ -249,11 +264,11 @@ class HomeFragment : Fragment() {
|
|||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
when(e) {
|
when(e) {
|
||||||
is TagLostException -> requireActivity().runOnUiThread {
|
is TagLostException -> requireActivity().runOnUiThread {
|
||||||
binding!!.detectionActionText.text = getString(R.string.id_card_removed_early)
|
binding.detectionActionText.text = getString(R.string.id_card_removed_early)
|
||||||
reset()
|
reset()
|
||||||
}
|
}
|
||||||
else -> requireActivity().runOnUiThread {
|
else -> requireActivity().runOnUiThread {
|
||||||
binding!!.detectionActionText.text = getString(R.string.nfc_reading_error)
|
binding.detectionActionText.text = getString(R.string.nfc_reading_error)
|
||||||
viewModel.deleteCan(requireContext())
|
viewModel.deleteCan(requireContext())
|
||||||
canState()
|
canState()
|
||||||
reset()
|
reset()
|
||||||
@@ -269,7 +284,9 @@ class HomeFragment : Fragment() {
|
|||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
requireActivity().unregisterReceiver(receiver)
|
if (receiver != null) {
|
||||||
binding = null
|
requireActivity().unregisterReceiver(receiver)
|
||||||
|
}
|
||||||
|
_binding = null
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -3,6 +3,9 @@ package com.tarkvaraprojekt.mobileauthapp.NFC;
|
|||||||
import android.nfc.tech.IsoDep;
|
import android.nfc.tech.IsoDep;
|
||||||
import android.util.Log;
|
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.BlockCipher;
|
||||||
import org.bouncycastle.crypto.engines.AESEngine;
|
import org.bouncycastle.crypto.engines.AESEngine;
|
||||||
import org.bouncycastle.crypto.macs.CMac;
|
import org.bouncycastle.crypto.macs.CMac;
|
||||||
@@ -21,7 +24,6 @@ import java.security.MessageDigest;
|
|||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Base64;
|
|
||||||
|
|
||||||
import javax.crypto.BadPaddingException;
|
import javax.crypto.BadPaddingException;
|
||||||
import javax.crypto.Cipher;
|
import javax.crypto.Cipher;
|
||||||
@@ -31,43 +33,47 @@ 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[] 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 IsoDep idCard;
|
||||||
|
|
||||||
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[] keyEnc;
|
||||||
private final byte[] keyMAC;
|
private final byte[] keyMAC;
|
||||||
private byte ssc; // Send sequence counter.
|
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 {
|
public Comms(IsoDep idCard, String CAN) throws IOException, NoSuchPaddingException, InvalidKeyException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException {
|
||||||
|
|
||||||
idCard.connect();
|
idCard.connect();
|
||||||
|
|
||||||
this.idCard = idCard;
|
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];
|
keyEnc = keys[0];
|
||||||
keyMAC = keys[1];
|
keyMAC = keys[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public byte[] getAuthenticationCertificate() {
|
||||||
|
return new byte[0];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculates the message authentication code
|
* Calculates the message authentication code
|
||||||
*
|
*
|
||||||
@@ -139,51 +154,40 @@ public class Comms {
|
|||||||
* @param CAN the card access number provided by the user
|
* @param CAN the card access number provided by the user
|
||||||
* @return the decrypted nonce
|
* @return the decrypted nonce
|
||||||
*/
|
*/
|
||||||
private byte[] decryptNonce(byte[] encryptedNonce, byte[] CAN) throws NoSuchPaddingException, InvalidKeyException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException {
|
private byte[] decryptNonce(byte[] encryptedNonce, String CAN) throws NoSuchPaddingException, InvalidKeyException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException {
|
||||||
byte[] decryptionKey = createKey(CAN, (byte) 3);
|
byte[] decryptionKey = createKey(CAN.getBytes(StandardCharsets.UTF_8), (byte) 3);
|
||||||
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
|
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
|
||||||
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(decryptionKey, "AES"), new IvParameterSpec(new byte[16]));
|
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(decryptionKey, "AES"), new IvParameterSpec(new byte[16]));
|
||||||
return cipher.doFinal(encryptedNonce);
|
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
|
* Attempts to use the PACE protocol to create a secure channel with an Estonian ID-card
|
||||||
*
|
*
|
||||||
* @param CAN the card access number
|
* @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
|
// select the ECC applet on the chip
|
||||||
getResponse(selectMaster, "Select the master application");
|
byte[] response = idCard.transceive(master);
|
||||||
|
Log.i("Select applet", Hex.toHexString(response));
|
||||||
|
|
||||||
// initiate PACE
|
// initiate PACE
|
||||||
getResponse(MSESetAT, "Set authentication template");
|
response = idCard.transceive(MSESetAT);
|
||||||
|
Log.i("Authentication template", Hex.toHexString(response));
|
||||||
|
|
||||||
// get nonce
|
// 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);
|
byte[] decryptedNonce = decryptNonce(Arrays.copyOfRange(response, 4, response.length - 2), CAN);
|
||||||
|
|
||||||
// generate an EC keypair and exchange public keys with the chip
|
// generate an EC keypair and exchange public keys with the chip
|
||||||
ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("secp256r1");
|
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
|
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();
|
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));
|
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
|
// 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();
|
ECPoint mappedECBasePoint = spec.getG().multiply(new BigInteger(1, decryptedNonce)).add(sharedSecret).normalize();
|
||||||
privateKey = new BigInteger(255, new SecureRandom()).add(BigInteger.ONE);
|
privateKey = new BigInteger(255, new SecureRandom()).add(BigInteger.ONE);
|
||||||
publicKey = mappedECBasePoint.multiply(privateKey).normalize();
|
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));
|
cardPublicKey = spec.getCurve().decodePoint(Arrays.copyOfRange(response, 4, 69));
|
||||||
|
|
||||||
// generate the session keys and exchange MACs to verify them
|
// generate the session keys and exchange MACs to verify them
|
||||||
byte[] secret = cardPublicKey.multiply(privateKey).normalize().getAffineXCoord().getEncoded();
|
sharedSecret = cardPublicKey.multiply(privateKey).normalize();
|
||||||
byte[] keyEnc = createKey(secret, (byte) 1);
|
byte[] encodedSecret = sharedSecret.getAffineXCoord().getEncoded();
|
||||||
byte[] keyMAC = createKey(secret, (byte) 2);
|
byte[] keyEnc = createKey(encodedSecret, (byte) 1);
|
||||||
byte[] MAC = getMAC(createAPDU(dataForMACIncomplete, cardPublicKey.getEncoded(false), 65), keyMAC);
|
byte[] keyMAC = createKey(encodedSecret, (byte) 2);
|
||||||
response = getResponse(createAPDU(GAMutualAuthenticationIncomplete, MAC, 9), "Mutual authentication");
|
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
|
// if the chip-side verification fails, crash and burn
|
||||||
MAC = getMAC(createAPDU(dataForMACIncomplete, publicKey.getEncoded(false), 65), keyMAC);
|
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))) {
|
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};
|
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
|
* Encrypts or decrypts the APDU data
|
||||||
*
|
*
|
||||||
@@ -260,182 +258,74 @@ public class Comms {
|
|||||||
byte[] macData = new byte[data.length > 0 ? 48 + length : 48];
|
byte[] macData = new byte[data.length > 0 ? 48 + length : 48];
|
||||||
macData[15] = ssc; // first block contains the ssc
|
macData[15] = ssc; // first block contains the ssc
|
||||||
System.arraycopy(incomplete, 0, macData, 16, 4); // second block has the command
|
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
|
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
|
if (data.length > 0) { // if the APDU has data, add padding and encrypt it
|
||||||
byte[] paddedData = Arrays.copyOf(data, length);
|
byte[] paddedData = Arrays.copyOf(data, length);
|
||||||
paddedData[data.length] = (byte) 0x80;
|
paddedData[data.length] = -128;
|
||||||
encryptedData = encryptDecryptData(paddedData, Cipher.ENCRYPT_MODE);
|
encryptedData = encryptDecryptData(paddedData, Cipher.ENCRYPT_MODE);
|
||||||
System.arraycopy(encryptedData, 0, macData, 35, encryptedData.length);
|
System.arraycopy(encryptedData, 0, macData, 35, encryptedData.length);
|
||||||
}
|
}
|
||||||
macData[35 + encryptedData.length] = (byte) 0x80;
|
macData[35 + encryptedData.length] = -128;
|
||||||
byte[] MAC = getMAC(macData, keyMAC);
|
byte[] MAC = getMAC(macData, keyMAC);
|
||||||
|
|
||||||
// construct the APDU using the encrypted data and the MAC
|
// 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) {
|
if (encryptedData.length > 0) {
|
||||||
System.arraycopy(encryptedData, 0, APDU, incomplete.length, encryptedData.length);
|
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);
|
System.arraycopy(MAC, 0, APDU, incomplete.length + encryptedData.length + 2, MAC.length);
|
||||||
|
|
||||||
ssc++;
|
ssc++;
|
||||||
return APDU;
|
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
|
* Gets the contents of the personal data dedicated file
|
||||||
*
|
*
|
||||||
* @param lastBytes the last bytes of the personal data file identifiers (0 < x < 16)
|
* @param FID the last bytes of file identifiers being requested
|
||||||
* @return array containing the corresponding data strings
|
* @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];
|
String[] personalData = new String[FID.length];
|
||||||
int stringIndex = 0;
|
byte[] data;
|
||||||
|
byte[] APDU;
|
||||||
// select the master application
|
byte[] response;
|
||||||
selectFile(IASECCFID, "the master application");
|
|
||||||
|
|
||||||
// select the personal data dedicated file
|
// 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 first 8 elementary files in the DF
|
||||||
// select and read the personal data elementary files
|
for (int i = 0; i < FID.length; i++) {
|
||||||
for (byte index : lastBytes) {
|
|
||||||
|
|
||||||
if (index > 15 || index < 1) throw new RuntimeException("Invalid personal data FID.");
|
byte index = FID[i];
|
||||||
FID[1] = index;
|
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
|
// store the decrypted datum
|
||||||
byte[] response = readFile(FID, "a personal data EF");
|
byte[] raw = encryptDecryptData(Arrays.copyOfRange(response, 3, 19), Cipher.DECRYPT_MODE);
|
||||||
int indexOfTerminator = Hex.toHexString(response).lastIndexOf("80") / 2;
|
int indexOfTerminator = Hex.toHexString(raw).lastIndexOf("80") / 2;
|
||||||
personalData[stringIndex++] = new String(Arrays.copyOfRange(response, 0, indexOfTerminator));
|
personalData[i] = new String(Arrays.copyOfRange(raw, 0, indexOfTerminator));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return personalData;
|
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -19,23 +19,24 @@ class Pin2Fragment : Fragment() {
|
|||||||
|
|
||||||
private val viewModel: SmartCardViewModel by activityViewModels()
|
private val viewModel: SmartCardViewModel by activityViewModels()
|
||||||
|
|
||||||
private var binding: FragmentPin2Binding? = null
|
private var _binding: FragmentPin2Binding? = null
|
||||||
|
private val binding get() = _binding!!
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View? {
|
): View? {
|
||||||
binding = FragmentPin2Binding.inflate(inflater, container, false)
|
_binding = FragmentPin2Binding.inflate(inflater, container, false)
|
||||||
return binding!!.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
binding!!.nextButton.setOnClickListener {
|
binding.nextButton.setOnClickListener {
|
||||||
checkPin2Length()
|
checkPin2Length()
|
||||||
}
|
}
|
||||||
binding!!.cancelButton.setOnClickListener {
|
binding.cancelButton.setOnClickListener {
|
||||||
cancel()
|
cancel()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -45,7 +46,7 @@ class Pin2Fragment : Fragment() {
|
|||||||
* then it is saved to the viewModel.
|
* then it is saved to the viewModel.
|
||||||
*/
|
*/
|
||||||
private fun checkPin2Length() {
|
private fun checkPin2Length() {
|
||||||
val enteredPin2 = binding!!.pin2EditText.editText?.text.toString()
|
val enteredPin2 = binding.pin2EditText.editText?.text.toString()
|
||||||
if (enteredPin2.length in 5..12) {
|
if (enteredPin2.length in 5..12) {
|
||||||
viewModel.setUserPin2(enteredPin2)
|
viewModel.setUserPin2(enteredPin2)
|
||||||
} else {
|
} else {
|
||||||
@@ -66,7 +67,7 @@ class Pin2Fragment : Fragment() {
|
|||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
binding = null
|
_binding = null
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@@ -29,7 +29,8 @@ class PinFragment : Fragment() {
|
|||||||
|
|
||||||
private val viewModel: SmartCardViewModel by activityViewModels()
|
private val viewModel: SmartCardViewModel by activityViewModels()
|
||||||
|
|
||||||
private var binding: FragmentPinBinding? = null
|
private var _binding: FragmentPinBinding? = null
|
||||||
|
private val binding get() = _binding!!
|
||||||
|
|
||||||
// Navigation arguments:
|
// Navigation arguments:
|
||||||
// saving = true means that the user must be returned to the settings menu
|
// saving = true means that the user must be returned to the settings menu
|
||||||
@@ -42,8 +43,8 @@ class PinFragment : Fragment() {
|
|||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View? {
|
): View? {
|
||||||
binding = FragmentPinBinding.inflate(inflater, container, false)
|
_binding = FragmentPinBinding.inflate(inflater, container, false)
|
||||||
return binding!!.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
@@ -51,28 +52,27 @@ class PinFragment : Fragment() {
|
|||||||
checkIfSkip()
|
checkIfSkip()
|
||||||
// Switch should be not visible when user is in savings mode
|
// Switch should be not visible when user is in savings mode
|
||||||
if (args.saving) {
|
if (args.saving) {
|
||||||
binding!!.savePinQuestion.visibility = View.GONE
|
binding.savePinQuestion.visibility = View.GONE
|
||||||
binding!!.saveLayout.visibility = View.GONE
|
binding.saveLayout.visibility = View.GONE
|
||||||
} else {
|
} else {
|
||||||
saveToggle =
|
saveToggle =
|
||||||
activity?.getPreferences(Context.MODE_PRIVATE)?.getBoolean("saveToggle", true) == true //Android Studio recommendation to get rid of Boolean?.
|
activity?.getPreferences(Context.MODE_PRIVATE)?.getBoolean("saveToggle", true) == true //Android Studio recommendation to get rid of Boolean?.
|
||||||
Log.i("myLogging", activity?.getPreferences(Context.MODE_PRIVATE)?.getBoolean("saveToggle", true).toString())
|
|
||||||
if (!saveToggle) {
|
if (!saveToggle) {
|
||||||
binding!!.saveSwitch.isChecked = false
|
binding.saveSwitch.isChecked = false
|
||||||
}
|
}
|
||||||
binding!!.saveSwitch.setOnCheckedChangeListener { _, isChecked ->
|
binding.saveSwitch.setOnCheckedChangeListener { _, isChecked ->
|
||||||
if (isChecked) {
|
if (isChecked) {
|
||||||
binding!!.saveStatus.text = getString(R.string.pin_save_on)
|
binding.saveStatus.text = getString(R.string.pin_save_on)
|
||||||
activity?.getPreferences(Context.MODE_PRIVATE)?.edit()?.putBoolean("saveToggle", true)?.apply()
|
activity?.getPreferences(Context.MODE_PRIVATE)?.edit()?.putBoolean("saveToggle", true)?.apply()
|
||||||
} else {
|
} else {
|
||||||
binding!!.saveStatus.text = getString(R.string.pin_save_off)
|
binding.saveStatus.text = getString(R.string.pin_save_off)
|
||||||
activity?.getPreferences(Context.MODE_PRIVATE)?.edit()?.putBoolean("saveToggle", false)?.apply()
|
activity?.getPreferences(Context.MODE_PRIVATE)?.edit()?.putBoolean("saveToggle", false)?.apply()
|
||||||
}
|
}
|
||||||
saveToggle = !saveToggle
|
saveToggle = !saveToggle
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
binding!!.buttonContinue.setOnClickListener { checkEnteredPin() }
|
binding.buttonContinue.setOnClickListener { checkEnteredPin() }
|
||||||
binding!!.buttonCancel.setOnClickListener { goToTheStart() }
|
binding.buttonCancel.setOnClickListener { goToTheStart() }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -130,7 +130,7 @@ class PinFragment : Fragment() {
|
|||||||
* allowed to modify the entered PIN 1.
|
* allowed to modify the entered PIN 1.
|
||||||
*/
|
*/
|
||||||
private fun checkEnteredPin() {
|
private fun checkEnteredPin() {
|
||||||
val enteredPin = binding!!.pinTextField.editText?.text.toString()
|
val enteredPin = binding.pinTextField.editText?.text.toString()
|
||||||
if (enteredPin.length in 4..12) {
|
if (enteredPin.length in 4..12) {
|
||||||
viewModel.setUserPin(enteredPin)
|
viewModel.setUserPin(enteredPin)
|
||||||
if (args.saving) {
|
if (args.saving) {
|
||||||
@@ -152,6 +152,6 @@ class PinFragment : Fragment() {
|
|||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
binding = null
|
_binding = null
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -24,7 +24,8 @@ class ResultFragment : Fragment() {
|
|||||||
|
|
||||||
private val paramsModel: ParametersViewModel by activityViewModels()
|
private val paramsModel: ParametersViewModel by activityViewModels()
|
||||||
|
|
||||||
private var binding: FragmentResultBinding? = null
|
private var _binding: FragmentResultBinding? = null
|
||||||
|
private val binding get() = _binding!!
|
||||||
|
|
||||||
private val args: ResultFragmentArgs by navArgs()
|
private val args: ResultFragmentArgs by navArgs()
|
||||||
|
|
||||||
@@ -33,8 +34,8 @@ class ResultFragment : Fragment() {
|
|||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View? {
|
): View? {
|
||||||
binding = FragmentResultBinding.inflate(inflater, container, false)
|
_binding = FragmentResultBinding.inflate(inflater, container, false)
|
||||||
return binding!!.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
@@ -86,7 +87,7 @@ class ResultFragment : Fragment() {
|
|||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
binding = null
|
_binding = null
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@@ -20,33 +20,34 @@ class UserFragment : Fragment() {
|
|||||||
|
|
||||||
private val viewModel: SmartCardViewModel by activityViewModels()
|
private val viewModel: SmartCardViewModel by activityViewModels()
|
||||||
|
|
||||||
private var binding: FragmentUserBinding? = null
|
private var _binding: FragmentUserBinding? = null
|
||||||
|
private val binding get() = _binding!!
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View? {
|
): View? {
|
||||||
binding = FragmentUserBinding.inflate(inflater, container, false)
|
_binding = FragmentUserBinding.inflate(inflater, container, false)
|
||||||
return binding!!.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
displayInformation()
|
displayInformation()
|
||||||
binding!!.clearButton.setOnClickListener { goToTheStart() }
|
binding.clearButton.setOnClickListener { goToTheStart() }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assigns text values to the fields in order to display user information.
|
* Assigns text values to the fields in order to display user information.
|
||||||
*/
|
*/
|
||||||
private fun displayInformation() {
|
private fun displayInformation() {
|
||||||
binding!!.userName.text =
|
binding.userName.text =
|
||||||
getString(R.string.user_name, viewModel.userFirstName, viewModel.userLastName)
|
getString(R.string.user_name, viewModel.userFirstName, viewModel.userLastName)
|
||||||
binding!!.identificationNumber.text = viewModel.userIdentificationNumber
|
binding.identificationNumber.text = viewModel.userIdentificationNumber
|
||||||
binding!!.gender.text = viewModel.gender
|
binding.gender.text = viewModel.gender
|
||||||
binding!!.expiration.text = viewModel.expiration.replace(" ", "/")
|
binding.expiration.text = viewModel.expiration.replace(" ", "/")
|
||||||
binding!!.citizenship.text = viewModel.citizenship
|
binding.citizenship.text = viewModel.citizenship
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -59,6 +60,6 @@ class UserFragment : Fragment() {
|
|||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
binding = null
|
_binding = null
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -0,0 +1,10 @@
|
|||||||
|
package com.tarkvaraprojekt.mobileauthapp.auth
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A specialised RuntimeException class for exceptions related to the mobile authentication app.
|
||||||
|
* Possible error codes can be found at
|
||||||
|
* https://github.com/TanelOrumaa/Estonian-ID-card-mobile-authenticator-POC/wiki/Error-codes
|
||||||
|
* @param message Error message
|
||||||
|
* @param code An error code defined in the project wiki
|
||||||
|
*/
|
||||||
|
open class AuthAppException(message: String, var code: Int) : RuntimeException(message)
|
@@ -0,0 +1,7 @@
|
|||||||
|
package com.tarkvaraprojekt.mobileauthapp.auth
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An AuthAppException for when the user entered CAN does not match the one read from the ID-card
|
||||||
|
* @see AuthAppException
|
||||||
|
*/
|
||||||
|
class InvalidCANException : AuthAppException("Invalid CAN", 400)
|
@@ -25,7 +25,8 @@ class SettingsFragment : Fragment() {
|
|||||||
|
|
||||||
private val viewModel: SmartCardViewModel by activityViewModels()
|
private val viewModel: SmartCardViewModel by activityViewModels()
|
||||||
|
|
||||||
private var binding: FragmentSettingsBinding? = null
|
private var _binding: FragmentSettingsBinding? = null
|
||||||
|
private val binding get() = _binding!!
|
||||||
|
|
||||||
private var showPin: Boolean = false
|
private var showPin: Boolean = false
|
||||||
|
|
||||||
@@ -34,8 +35,8 @@ class SettingsFragment : Fragment() {
|
|||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View? {
|
): View? {
|
||||||
binding = FragmentSettingsBinding.inflate(inflater, container, false)
|
_binding = FragmentSettingsBinding.inflate(inflater, container, false)
|
||||||
return binding!!.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
@@ -43,10 +44,10 @@ class SettingsFragment : Fragment() {
|
|||||||
showCanField()
|
showCanField()
|
||||||
showPinField()
|
showPinField()
|
||||||
togglePinButton()
|
togglePinButton()
|
||||||
binding!!.canMenuAction.setOnClickListener { canAction() }
|
binding.canMenuAction.setOnClickListener { canAction() }
|
||||||
binding!!.pinMenuAction.setOnClickListener { pinAction() }
|
binding.pinMenuAction.setOnClickListener { pinAction() }
|
||||||
binding!!.pinMenuShow.setOnClickListener { togglePin() }
|
binding.pinMenuShow.setOnClickListener { togglePin() }
|
||||||
binding!!.returnButton.setOnClickListener { backToHome() }
|
binding.returnButton.setOnClickListener { backToHome() }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -64,11 +65,11 @@ class SettingsFragment : Fragment() {
|
|||||||
*/
|
*/
|
||||||
private fun showCanField() {
|
private fun showCanField() {
|
||||||
if (viewModel.userCan.length == 6) {
|
if (viewModel.userCan.length == 6) {
|
||||||
binding!!.canSaved.text = getString(R.string.saved_can, viewModel.userCan)
|
binding.canSaved.text = getString(R.string.saved_can, viewModel.userCan)
|
||||||
binding!!.canMenuAction.text = getString(R.string.can_delete)
|
binding.canMenuAction.text = getString(R.string.can_delete)
|
||||||
} else {
|
} else {
|
||||||
binding!!.canSaved.text = getString(R.string.saved_can, getString(R.string.missing))
|
binding.canSaved.text = getString(R.string.saved_can, getString(R.string.missing))
|
||||||
binding!!.canMenuAction.text = getString(R.string.add_can_text)
|
binding.canMenuAction.text = getString(R.string.add_can_text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,16 +96,16 @@ class SettingsFragment : Fragment() {
|
|||||||
*/
|
*/
|
||||||
private fun showPinField() {
|
private fun showPinField() {
|
||||||
if (viewModel.userPin.length in 4..12) {
|
if (viewModel.userPin.length in 4..12) {
|
||||||
binding!!.pinMenuShow.visibility = Button.VISIBLE
|
binding.pinMenuShow.visibility = Button.VISIBLE
|
||||||
if (showPin)
|
if (showPin)
|
||||||
binding!!.pinSaved.text = getString(R.string.saved_pin, viewModel.userPin)
|
binding.pinSaved.text = getString(R.string.saved_pin, viewModel.userPin)
|
||||||
else
|
else
|
||||||
binding!!.pinSaved.text = getString(R.string.saved_pin, getString(R.string.hidden_pin))
|
binding.pinSaved.text = getString(R.string.saved_pin, getString(R.string.hidden_pin))
|
||||||
binding!!.pinMenuAction.text = getString(R.string.pin1_delete)
|
binding.pinMenuAction.text = getString(R.string.pin1_delete)
|
||||||
} else {
|
} else {
|
||||||
binding!!.pinMenuShow.visibility = Button.GONE
|
binding.pinMenuShow.visibility = Button.GONE
|
||||||
binding!!.pinSaved.text = getString(R.string.saved_pin, getString(R.string.missing))
|
binding.pinSaved.text = getString(R.string.saved_pin, getString(R.string.missing))
|
||||||
binding!!.pinMenuAction.text = getString(R.string.pin1_add)
|
binding.pinMenuAction.text = getString(R.string.pin1_add)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,9 +139,9 @@ class SettingsFragment : Fragment() {
|
|||||||
*/
|
*/
|
||||||
private fun togglePinButton() {
|
private fun togglePinButton() {
|
||||||
if (showPin) {
|
if (showPin) {
|
||||||
binding!!.pinMenuShow.text = getString(R.string.hide)
|
binding.pinMenuShow.text = getString(R.string.hide)
|
||||||
} else {
|
} else {
|
||||||
binding!!.pinMenuShow.text = getString(R.string.show)
|
binding.pinMenuShow.text = getString(R.string.show)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,7 +155,7 @@ class SettingsFragment : Fragment() {
|
|||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
binding = null
|
_binding = null
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@@ -1,42 +1,29 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<!-- Must translate to English, but should work now -->
|
<!-- Must translate to English, but should work now -->
|
||||||
<string name="app_name">NFC authenticator</string>
|
<string name="app_name">NFC authentication</string>
|
||||||
|
<string name="home_fragment">Work in progress</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. Tries on the card left %s</string>
|
|
||||||
|
|
||||||
<!-- string resources for HomeFragment -->
|
<!-- string resources for HomeFragment -->
|
||||||
<string name="pin_status_saved">PIN 1 saved</string>
|
<string name="pin_status_saved">PIN 1 saved</string>
|
||||||
<string name="pin_status_negative">PIN 1 not saved</string>
|
<string name="pin_status_negative">PIN 1 not saved</string>
|
||||||
<string name="can_status_saved">CAN saved</string>
|
<string name="can_status_saved">CAN saved</string>
|
||||||
<string name="can_status_negative">CAN not 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="begin_text">READ ID CARD</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="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 resources for PinFragment -->
|
<!-- string resources for PinFragment -->
|
||||||
<string name="pin_view">Please enter PIN 1</string>
|
<string name="pin_fragment">Please enter PIN 1</string>
|
||||||
<string name="hint_pin">PIN 1</string>
|
<string name="enter_pin">PIN 1</string>
|
||||||
<string name="pin_helper_text">PIN 1 must be 4–12 digits long</string>
|
<string name="example_pin">Example. 1234</string>
|
||||||
<string name="save_pin">Save PIN 1</string>
|
<string name="length_pin">Allowed length for PIN 1 is 4..12</string>
|
||||||
<string name="pin_save_on">On</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="pin_save_off">Off</string>
|
<string name="save_pin_title">Save PIN 1</string>
|
||||||
|
|
||||||
<!-- string resources for Pin2Fragment -->
|
<!-- string resources for Pin2Fragment -->
|
||||||
<string name="pin2_fragment">Please enter PIN 2</string>
|
<string name="pin2_fragment">Please enter PIN 2</string>
|
||||||
@@ -45,14 +32,21 @@
|
|||||||
<string name="length_pin2">Allowed length for PIN 2 is 5..12</string>
|
<string name="length_pin2">Allowed length for PIN 2 is 5..12</string>
|
||||||
|
|
||||||
<!-- string resources for CanFragment -->
|
<!-- string resources for CanFragment -->
|
||||||
<string name="can_view">Please enter CAN</string>
|
<string name="example_can">Example. 123456</string>
|
||||||
<string name="can_text">CAN</string>
|
<string name="text_can">CAN</string>
|
||||||
<string name="can_helper_text">CAN must be 6 digits long</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 resources for AuthFragment layout -->
|
<!-- string resources for AuthFragment layout -->
|
||||||
<string name="auth_instruction_text">Put the ID card against the phone</string>
|
<string name="auth_instruction_text">Put the ID card against the phone to establish connection</string>
|
||||||
<string name="time_left">Time left %d sek</string>
|
<string name="time_left">Time left %d sek</string>
|
||||||
<string name="no_time">No time left</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 resources for UserFragment layout -->
|
||||||
<string name="user_name_label">NAME</string>
|
<string name="user_name_label">NAME</string>
|
||||||
@@ -61,14 +55,18 @@
|
|||||||
<string name="expiration_label">DATE OF EXPIRY</string>
|
<string name="expiration_label">DATE OF EXPIRY</string>
|
||||||
<string name="citizenship_label">CITIZENSHIP</string>
|
<string name="citizenship_label">CITIZENSHIP</string>
|
||||||
<string name="gender_label">SEX</string>
|
<string name="gender_label">SEX</string>
|
||||||
|
<string name="clear_button">FORGET</string>
|
||||||
|
|
||||||
<!-- string resources for ResultFragment layout-->
|
<!-- string resources for ResultFragment layout-->
|
||||||
<string name="result_text">Controlling the created token</string>
|
<string name="result_text">Controlling the created token</string>
|
||||||
<string name="result_info">The app will close automatically</string>
|
<string name="result_info">Wait for the app to close</string>
|
||||||
|
|
||||||
<!-- menu -->
|
<!-- menu -->
|
||||||
<string name="menu_settings_title">Settings</string>
|
<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="saved_can">CAN: %s</string>
|
||||||
|
<string name="can_add">Add CAN</string>
|
||||||
<string name="can_delete">Delete CAN</string>
|
<string name="can_delete">Delete CAN</string>
|
||||||
<string name="saved_pin">PIN1: %s</string>
|
<string name="saved_pin">PIN1: %s</string>
|
||||||
<string name="pin1_add">Add PIN1</string>
|
<string name="pin1_add">Add PIN1</string>
|
||||||
@@ -77,7 +75,9 @@
|
|||||||
<string name="show">SHOW</string>
|
<string name="show">SHOW</string>
|
||||||
<string name="hide">HIDE</string>
|
<string name="hide">HIDE</string>
|
||||||
<string name="hidden_pin">****</string>
|
<string name="hidden_pin">****</string>
|
||||||
<string name="menu_unavailable_message">Settings are currently unavailable</string>
|
<string name="unavailable">Settings currently unavailabe</string>
|
||||||
<string name="can_deleted">CAN deleted</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="pin_deleted">PIN 1 deleted</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>
|
||||||
</resources>
|
</resources>
|
@@ -1,41 +1,28 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">NFC autentija</string>
|
<string name="app_name">NFC authentication</string>
|
||||||
|
<string name="home_fragment">Work in progress</string>
|
||||||
|
|
||||||
<!-- Buttons -->
|
<string name="begin_text">LOE ID KAARTI</string>
|
||||||
|
<string name="next_text">EDASI</string>
|
||||||
<string name="cancel_text">KATKESTA</string>
|
<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="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 kordi alles: %s</string>
|
|
||||||
|
|
||||||
<!-- string resources for HomeFragment -->
|
<!-- string resources for HomeFragment -->
|
||||||
<string name="pin_status_saved">PIN 1 on salvestatud</string>
|
<string name="pin_status_saved">PIN 1 on salvestatud</string>
|
||||||
<string name="pin_status_negative">PIN 1 ei ole 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_saved">CAN on salvestatud</string>
|
||||||
<string name="can_status_negative">CAN ei ole 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 resources for PinFragment -->
|
<!-- string resources for PinFragment -->
|
||||||
<string name="pin_view">Palun sisesta PIN 1</string>
|
<string name="pin_fragment">Palun sisesta PIN 1</string>
|
||||||
<string name="hint_pin">PIN 1</string>
|
<string name="enter_pin">PIN 1</string>
|
||||||
<string name="pin_helper_text">PIN 1 lubatud pikkus on 4..12</string>
|
<string name="example_pin">Näide. 1234</string>
|
||||||
<string name="save_pin">Save PIN 1</string>
|
<string name="length_pin">PIN 1 lubatud pikkus on 4..12</string>
|
||||||
<string name="pin_save_on">On</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="pin_save_off">Off</string>
|
<string name="save_pin_title">Salvesta PIN 1</string>
|
||||||
|
|
||||||
<!-- string resources for Pin2Fragment -->
|
<!-- string resources for Pin2Fragment -->
|
||||||
<string name="pin2_fragment">Palun sisesta PIN 2</string>
|
<string name="pin2_fragment">Palun sisesta PIN 2</string>
|
||||||
@@ -44,19 +31,27 @@
|
|||||||
<string name="length_pin2">PIN 2 lubatud pikkus on 5..12</string>
|
<string name="length_pin2">PIN 2 lubatud pikkus on 5..12</string>
|
||||||
|
|
||||||
<!-- string resources for CanFragment -->
|
<!-- string resources for CanFragment -->
|
||||||
<string name="can_view">Please enter CAN</string>
|
<string name="example_can">Näide. 123456</string>
|
||||||
<string name="can_text">CAN</string>
|
<string name="text_can">CAN</string>
|
||||||
<string name="can_helper_text">CAN must be 6 digits long</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 resources for AuthFragment layout -->
|
<!-- string resources for AuthFragment layout -->
|
||||||
<string name="auth_instruction_text">Pane ID kaart vastu telefoni</string>
|
<string name="auth_instruction_text">ID kaardiga ühenduse loomiseks pane kaart vastu telefoni</string>
|
||||||
<string name="time_left">Aega on jäänud %d sek</string>
|
<string name="time_left">Aega on jäänud %d sek</string>
|
||||||
<string name="no_time">Aeg on otsas</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 resources for UserFragment layout -->
|
||||||
<string name="user_name_label">NIMI</string>
|
<string name="user_name_label">NIMI</string>
|
||||||
<string name="user_name">%1$s %2$s</string>
|
<string name="user_name">%1$s %2$s</string>
|
||||||
<string name="identification_number_label">ISIKUKOOD</string>
|
<string name="identification_number_label">ISIKUKOOD</string>
|
||||||
|
<string name="clear_button">UNUSTA</string>
|
||||||
<string name="expiration_label">KEHTIV KUNI</string>
|
<string name="expiration_label">KEHTIV KUNI</string>
|
||||||
<string name="citizenship_label">KODAKONDSUS</string>
|
<string name="citizenship_label">KODAKONDSUS</string>
|
||||||
<string name="gender_label">SUGU</string>
|
<string name="gender_label">SUGU</string>
|
||||||
@@ -67,7 +62,10 @@
|
|||||||
|
|
||||||
<!-- menu -->
|
<!-- menu -->
|
||||||
<string name="menu_settings_title">Seaded</string>
|
<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="saved_can">CAN: %s</string>
|
||||||
|
<string name="can_add">Lisa CAN</string>
|
||||||
<string name="can_delete">Kustuta CAN</string>
|
<string name="can_delete">Kustuta CAN</string>
|
||||||
<string name="saved_pin">PIN1: %s</string>
|
<string name="saved_pin">PIN1: %s</string>
|
||||||
<string name="pin1_add">Lisa PIN1</string>
|
<string name="pin1_add">Lisa PIN1</string>
|
||||||
@@ -76,7 +74,8 @@
|
|||||||
<string name="show">NÄITA</string>
|
<string name="show">NÄITA</string>
|
||||||
<string name="hide">PEIDA</string>
|
<string name="hide">PEIDA</string>
|
||||||
<string name="hidden_pin">****</string>
|
<string name="hidden_pin">****</string>
|
||||||
<string name="menu_unavailable_message">Seaded pole hetkel saadaval</string>
|
<string name="unavailable">Seaded pole hetkel saadaval</string>
|
||||||
<string name="can_deleted">CAN kustatud</string>
|
<string name="err_reading_card">Ei saanud ID-kaardilt andmeid lugeda</string>
|
||||||
<string name="pin_deleted">PIN 1 kustatud</string>
|
<string name="err_internal">Rakendusesisene viga</string>
|
||||||
|
<string name="err_bad_data">ID-kaardilt loeti vigased andmed, proovi uuesti kaarti kasutada</string>
|
||||||
</resources>
|
</resources>
|
@@ -1,40 +1,27 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">NFC authenticator</string>
|
<string name="app_name">NFC authentication</string>
|
||||||
|
<string name="home_fragment">Work in progress</string>
|
||||||
|
|
||||||
<!-- BUTTONS -->
|
<string name="begin_text">READ ID CARD</string>
|
||||||
|
<string name="next_text">NEXT</string>
|
||||||
<string name="cancel_text">CANCEL</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="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. Tries on the card left %s</string>
|
|
||||||
|
|
||||||
<!-- string resources for HomeFragment -->
|
<!-- string resources for HomeFragment -->
|
||||||
<string name="pin_status_saved">PIN 1 saved</string>
|
<string name="pin_status_saved">PIN 1 saved</string>
|
||||||
<string name="pin_status_negative">PIN 1 not saved</string>
|
<string name="pin_status_negative">PIN 1 not saved</string>
|
||||||
<string name="can_status_saved">CAN saved</string>
|
<string name="can_status_saved">CAN saved</string>
|
||||||
<string name="can_status_negative">CAN not 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 resources for PinFragment -->
|
<!-- string resources for PinFragment -->
|
||||||
<string name="pin_view">Please enter PIN 1</string>
|
<string name="pin_fragment">Please enter PIN 1</string>
|
||||||
<string name="hint_pin">PIN 1</string>
|
<string name="enter_pin">PIN 1</string>
|
||||||
<string name="pin_helper_text">PIN 1 must be 4–12 digits long</string>
|
<string name="example_pin">Example. 1234</string>
|
||||||
<string name="save_pin">Save PIN 1</string>
|
<string name="length_pin">Allowed length for PIN 1 is 4..12</string>
|
||||||
<string name="pin_save_on">On</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="pin_save_off">Off</string>
|
<string name="save_pin_title">Save PIN 1</string>
|
||||||
|
|
||||||
<!-- string resources for Pin2Fragment -->
|
<!-- string resources for Pin2Fragment -->
|
||||||
<string name="pin2_fragment">Please enter PIN 2</string>
|
<string name="pin2_fragment">Please enter PIN 2</string>
|
||||||
@@ -43,14 +30,22 @@
|
|||||||
<string name="length_pin2">Allowed length for PIN 2 is 5..12</string>
|
<string name="length_pin2">Allowed length for PIN 2 is 5..12</string>
|
||||||
|
|
||||||
<!-- string resources for CanFragment -->
|
<!-- string resources for CanFragment -->
|
||||||
<string name="can_view">Please enter CAN</string>
|
<string name="example_can">Example. 123456</string>
|
||||||
<string name="can_text">CAN</string>
|
<string name="text_can">CAN</string>
|
||||||
<string name="can_helper_text">CAN must be 6 digits long</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 resources for AuthFragment layout -->
|
<!-- string resources for AuthFragment layout -->
|
||||||
<string name="auth_instruction_text">Put the ID card against the phone</string>
|
<string name="auth_instruction_text">Put the ID card against the phone to establish connection</string>
|
||||||
<string name="time_left">Time left %d sek</string>
|
<string name="time_left">Time left %d sek</string>
|
||||||
<string name="no_time">No time left</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 resources for UserFragment layout -->
|
||||||
<string name="user_name_label">NAME</string>
|
<string name="user_name_label">NAME</string>
|
||||||
@@ -59,14 +54,18 @@
|
|||||||
<string name="expiration_label">DATE OF EXPIRY</string>
|
<string name="expiration_label">DATE OF EXPIRY</string>
|
||||||
<string name="citizenship_label">CITIZENSHIP</string>
|
<string name="citizenship_label">CITIZENSHIP</string>
|
||||||
<string name="gender_label">SEX</string>
|
<string name="gender_label">SEX</string>
|
||||||
|
<string name="clear_button">FORGET</string>
|
||||||
|
|
||||||
<!-- string resources for ResultFragment layout-->
|
<!-- string resources for ResultFragment layout-->
|
||||||
<string name="result_text">Controlling the created token</string>
|
<string name="result_text">Controlling the created token</string>
|
||||||
<string name="result_info">The app will close automatically</string>
|
<string name="result_info">Wait for the app to close</string>
|
||||||
|
|
||||||
<!-- menu -->
|
<!-- menu -->
|
||||||
<string name="menu_settings_title">Settings</string>
|
<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="saved_can">CAN: %s</string>
|
||||||
|
<string name="can_add">Add CAN</string>
|
||||||
<string name="can_delete">Delete CAN</string>
|
<string name="can_delete">Delete CAN</string>
|
||||||
<string name="saved_pin">PIN1: %s</string>
|
<string name="saved_pin">PIN1: %s</string>
|
||||||
<string name="pin1_add">Add PIN 1</string>
|
<string name="pin1_add">Add PIN 1</string>
|
||||||
@@ -75,7 +74,8 @@
|
|||||||
<string name="show">SHOW</string>
|
<string name="show">SHOW</string>
|
||||||
<string name="hide">HIDE</string>
|
<string name="hide">HIDE</string>
|
||||||
<string name="hidden_pin">****</string>
|
<string name="hidden_pin">****</string>
|
||||||
<string name="menu_unavailable_message">Settings are currently unavailable</string>
|
<string name="unavailable">Settings currently unavailable</string>
|
||||||
<string name="can_deleted">CAN deleted</string>
|
<string name="err_reading_card">Failed to read data from the ID-card</string>
|
||||||
<string name="pin_deleted">PIN 1 deleted</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>
|
||||||
</resources>
|
</resources>
|
@@ -9,7 +9,6 @@ import android.view.View
|
|||||||
import androidx.activity.result.ActivityResultLauncher
|
import androidx.activity.result.ActivityResultLauncher
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import com.example.testmobileapp.databinding.ActivityMainBinding
|
import com.example.testmobileapp.databinding.ActivityMainBinding
|
||||||
import com.google.gson.JsonObject
|
|
||||||
import com.koushikdutta.ion.Ion
|
import com.koushikdutta.ion.Ion
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
|
|
||||||
@@ -35,9 +34,8 @@ class MainActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
authLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { response ->
|
authLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { response ->
|
||||||
if (response.resultCode == Activity.RESULT_OK) {
|
if (response.resultCode == Activity.RESULT_OK) {
|
||||||
// Currently we are not actually checking whether we get a valid token.
|
|
||||||
// For testing purposes only, to make sure that we are able to get a response at all.
|
|
||||||
binding.loginTextView.text = getString(R.string.auth_success)
|
binding.loginTextView.text = getString(R.string.auth_success)
|
||||||
|
// Logs are used to show what information can be retrieved from the mobileauthapp.
|
||||||
Log.i("getResult", response.data?.getStringExtra("token").toString())
|
Log.i("getResult", response.data?.getStringExtra("token").toString())
|
||||||
Log.i("getResult", response.data?.getStringExtra("result").toString())
|
Log.i("getResult", response.data?.getStringExtra("result").toString())
|
||||||
var user = ""
|
var user = ""
|
||||||
@@ -48,14 +46,6 @@ class MainActivity : AppCompatActivity() {
|
|||||||
Log.i("getResult", "unable to retrieve name from principal")
|
Log.i("getResult", "unable to retrieve name from principal")
|
||||||
}
|
}
|
||||||
showResult(user)
|
showResult(user)
|
||||||
/*
|
|
||||||
binding.loginOptionNfcButton.text = "Log Out"
|
|
||||||
binding.loginOptionNfcButton.setOnClickListener {
|
|
||||||
binding.loginOptionNfcButton.text = "NFC auth"
|
|
||||||
binding.loginOptionNfcButton.setOnClickListener { getData() }
|
|
||||||
}
|
|
||||||
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
if (response.resultCode == Activity.RESULT_CANCELED) {
|
if (response.resultCode == Activity.RESULT_CANCELED) {
|
||||||
binding.loginTextView.text = getString(R.string.auth_failure)
|
binding.loginTextView.text = getString(R.string.auth_failure)
|
||||||
@@ -114,6 +104,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
binding.resultLayout.visibility = View.VISIBLE
|
binding.resultLayout.visibility = View.VISIBLE
|
||||||
binding.resultObject.text = getString(R.string.hello, user)
|
binding.resultObject.text = getString(R.string.hello, user)
|
||||||
binding.buttonForget.setOnClickListener {
|
binding.buttonForget.setOnClickListener {
|
||||||
|
binding.loginTextView.text = getString(R.string.login_text)
|
||||||
binding.resultObject.text = ""
|
binding.resultObject.text = ""
|
||||||
binding.resultLayout.visibility = View.GONE
|
binding.resultLayout.visibility = View.GONE
|
||||||
binding.loginOptions.visibility = View.VISIBLE
|
binding.loginOptions.visibility = View.VISIBLE
|
||||||
|
Reference in New Issue
Block a user