mirror of
https://github.com/TanelOrumaa/Estonian-ID-card-mobile-authenticator-POC.git
synced 2024-12-22 04:20:16 +02:00
MOB-42 Fixed JWT generation issue.
This commit is contained in:
parent
c46c3082b7
commit
82b1538867
@ -1,6 +1,7 @@
|
||||
package com.tarkvaraprojekt.mobileauthapp
|
||||
|
||||
import android.content.Intent
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.nfc.NfcAdapter
|
||||
import android.nfc.tech.IsoDep
|
||||
import android.os.Bundle
|
||||
@ -9,16 +10,15 @@ import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.navigation.fragment.navArgs
|
||||
import com.tarkvaraprojekt.mobileauthapp.NFC.Comms
|
||||
import com.tarkvaraprojekt.mobileauthapp.auth.Authenticator
|
||||
import com.tarkvaraprojekt.mobileauthapp.databinding.FragmentAuthBinding
|
||||
import com.tarkvaraprojekt.mobileauthapp.model.SmartCardViewModel
|
||||
import java.lang.Exception
|
||||
import kotlin.concurrent.thread
|
||||
|
||||
/**
|
||||
* Fragment that asks the user to detect the ID card with mobile NFC chip.
|
||||
@ -31,8 +31,6 @@ class AuthFragment : Fragment() {
|
||||
|
||||
private var binding: FragmentAuthBinding? = null
|
||||
|
||||
private val args: CanFragmentArgs by navArgs()
|
||||
|
||||
private lateinit var timer: CountDownTimer
|
||||
|
||||
private var timeRemaining: Int = 90
|
||||
@ -71,66 +69,51 @@ class AuthFragment : Fragment() {
|
||||
}
|
||||
|
||||
private fun getInfoFromIdCard(adapter: NfcAdapter) {
|
||||
if (args.reading) {
|
||||
adapter.enableReaderMode(activity, { tag ->
|
||||
timer.cancel()
|
||||
requireActivity().runOnUiThread {
|
||||
binding!!.timeCounter.text = getString(R.string.card_detected)
|
||||
}
|
||||
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: Exception) {
|
||||
requireActivity().runOnUiThread {
|
||||
binding!!.timeCounter.text = getString(R.string.no_success)
|
||||
}
|
||||
// If the CAN is wrong we will also delete the saved CAN so that the user won't use it again.
|
||||
viewModel.deleteCan(requireContext())
|
||||
// Gives user some time to read the error message
|
||||
Thread.sleep(1000)
|
||||
goToTheStart()
|
||||
} finally {
|
||||
adapter.disableReaderMode(activity)
|
||||
adapter.enableReaderMode(activity, { tag ->
|
||||
timer.cancel()
|
||||
requireActivity().runOnUiThread {
|
||||
binding!!.timeCounter.text = getString(R.string.card_detected)
|
||||
}
|
||||
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: Exception) {
|
||||
requireActivity().runOnUiThread {
|
||||
binding!!.timeCounter.text = getString(R.string.no_success)
|
||||
}
|
||||
// If the CAN is wrong we will also delete the saved CAN so that the user won't use it again.
|
||||
viewModel.deleteCan(requireContext())
|
||||
// Gives user some time to read the error message
|
||||
Thread.sleep(1000)
|
||||
goToTheStart()
|
||||
} finally {
|
||||
adapter.disableReaderMode(activity)
|
||||
}
|
||||
}, NfcAdapter.FLAG_READER_NFC_A, null)
|
||||
} else { //We want to create a JWT instead of reading the info from the card.
|
||||
goToNextFragment()
|
||||
}
|
||||
}
|
||||
}, NfcAdapter.FLAG_READER_NFC_A, null)
|
||||
}
|
||||
|
||||
private fun goToNextFragment() {
|
||||
timer.cancel()
|
||||
if (args.auth) {
|
||||
val action = AuthFragmentDirections.actionAuthFragmentToResultFragment(mobile = args.mobile)
|
||||
findNavController().navigate(action)
|
||||
} else {
|
||||
findNavController().navigate(R.id.action_authFragment_to_userFragment)
|
||||
}
|
||||
findNavController().navigate(R.id.action_authFragment_to_userFragment)
|
||||
}
|
||||
|
||||
private fun goToTheStart() {
|
||||
viewModel.clearUserInfo()
|
||||
timer.cancel()
|
||||
if (args.reading) {
|
||||
findNavController().navigate(R.id.action_authFragment_to_homeFragment)
|
||||
} else {
|
||||
val resultIntent = Intent()
|
||||
requireActivity().setResult(AppCompatActivity.RESULT_CANCELED, resultIntent)
|
||||
requireActivity().finish()
|
||||
}
|
||||
findNavController().navigate(R.id.action_authFragment_to_homeFragment)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
|
@ -21,6 +21,7 @@ import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
|
@ -1,16 +1,12 @@
|
||||
package com.tarkvaraprojekt.mobileauthapp.auth
|
||||
|
||||
import android.os.Message
|
||||
import android.util.Log
|
||||
import com.tarkvaraprojekt.mobileauthapp.NFC.Comms
|
||||
import io.jsonwebtoken.SignatureAlgorithm
|
||||
import org.bouncycastle.util.encoders.Base64
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.security.MessageDigest
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneOffset
|
||||
import javax.crypto.Mac
|
||||
import kotlin.experimental.and
|
||||
|
||||
class Authenticator(val comms : Comms) {
|
||||
|
||||
@ -19,43 +15,45 @@ class Authenticator(val comms : Comms) {
|
||||
var iss = "https://self-issued.me" // Will be specified at a later date.
|
||||
val algorithmUsedForSigning = SignatureAlgorithm.ES384
|
||||
|
||||
fun authenticate(challenge: String, originUrl: String, pin1: String) : String {
|
||||
fun authenticate(challenge: String, originUrl: String, pin1: String): String {
|
||||
|
||||
// Ask PIN 1 from the user and get the authentication certificate from the ID card.
|
||||
val authenticationCertificate : ByteArray = comms.getCertificate(true);
|
||||
val authenticationCertificate: ByteArray = comms.getCertificate(true);
|
||||
|
||||
// Encode the certificate in base64.
|
||||
val base64cert = String(Base64.encode(authenticationCertificate))
|
||||
val base64cert = java.util.Base64.getEncoder().encodeToString(authenticationCertificate)
|
||||
|
||||
// Get current epoch time.
|
||||
val epoch = LocalDateTime.now(ZoneOffset.UTC).atZone(ZoneOffset.UTC).toEpochSecond()
|
||||
|
||||
// Get expiration time.
|
||||
val exp = LocalDateTime.now(ZoneOffset.UTC).plusSeconds(5 * 60L).atZone(ZoneOffset.UTC).toEpochSecond()
|
||||
val exp = LocalDateTime.now(ZoneOffset.UTC).plusSeconds(5 * 60L).atZone(ZoneOffset.UTC)
|
||||
.toEpochSecond()
|
||||
|
||||
// Get subject value.
|
||||
val sub = authenticationCertificate[0] // TODO:
|
||||
// TODO: Get subject value.
|
||||
val sub = "FAMILYNAME.NAME"
|
||||
|
||||
// Get header and claims.
|
||||
val header = """{"typ":"$type","alg":"$algorithm","x5c":"$base64cert"}"""
|
||||
val claims = """{"iat":"$epoch","exp":"$exp","aud":"$originUrl","iss":"$iss","sub":"$sub","nonce":"$challenge","cnf":{"tbh":""}}"""
|
||||
val header = """{"typ":"$type","alg":"$algorithm","x5c":["$base64cert"]}"""
|
||||
val claims =
|
||||
"""{"iat":"$epoch","exp":"$exp","aud":"$originUrl","iss":"$iss","sub":"$sub","nonce":"$challenge","cnf":{"tbh":""}}"""
|
||||
|
||||
var jwt = String(Base64.encode(header.toByteArray(Charsets.UTF_8))) + "." + String(Base64.encode(claims.toByteArray(Charsets.UTF_8)))
|
||||
jwt = jwt.replace("=", "")
|
||||
|
||||
Log.v("JWT", jwt)
|
||||
val jwt = base64Encode(header.toByteArray(Charsets.UTF_8)) + "." + base64Encode(
|
||||
claims.toByteArray(Charsets.UTF_8)
|
||||
)
|
||||
|
||||
// Send the authentication token hash to the ID card for signing and get signed authentication token as response.
|
||||
val encoded = MessageDigest.getInstance("SHA-384").digest(jwt.toByteArray())
|
||||
val encoded =
|
||||
MessageDigest.getInstance("SHA-384").digest(jwt.toByteArray(StandardCharsets.UTF_8))
|
||||
val signed = comms.authenticate(pin1, encoded)
|
||||
|
||||
val jws = jwt + "." + String(Base64.encode(signed))
|
||||
|
||||
Log.v("Token", jws)
|
||||
|
||||
// Return the signed authentication token.
|
||||
return jws
|
||||
return jwt + "." + base64Encode(signed)
|
||||
}
|
||||
|
||||
fun base64Encode(bytes: ByteArray) : String? {
|
||||
val encoded = java.util.Base64.getUrlEncoder().encodeToString(bytes)
|
||||
return encoded.replace("=", "")
|
||||
}
|
||||
|
||||
}
|
@ -45,7 +45,11 @@ function createParametrizedIntentUrl(challenge, action) {
|
||||
if (action == null) {
|
||||
console.error("There has to be an action for intent.")
|
||||
}
|
||||
return intentUrl + "?" + "action=" + action + (challenge != null ? "&challenge=" + challenge : "");
|
||||
else if (challenge == null) {
|
||||
console.error("Challenge missing, can't authenticate without it.")
|
||||
} else {
|
||||
return intentUrl + "?" + "action=" + action + "&challenge=" + challenge + "&authUrl=" + originUrl + authenticationRequestUrl;
|
||||
}
|
||||
}
|
||||
|
||||
function isAndroid() {
|
||||
|
Loading…
Reference in New Issue
Block a user