From a1dbcad15ce0a24dd2fab33df8f446406d7be122 Mon Sep 17 00:00:00 2001 From: Jov Mit Date: Fri, 25 Aug 2023 12:05:29 +0200 Subject: [PATCH 1/3] AndroidSecureArea keys creation enhancements --- .../android/mdl/app/util/ProvisioningUtil.kt | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/appholder/src/main/java/com/android/mdl/app/util/ProvisioningUtil.kt b/appholder/src/main/java/com/android/mdl/app/util/ProvisioningUtil.kt index 861eb6122..3cd749340 100644 --- a/appholder/src/main/java/com/android/mdl/app/util/ProvisioningUtil.kt +++ b/appholder/src/main/java/com/android/mdl/app/util/ProvisioningUtil.kt @@ -14,6 +14,7 @@ import com.android.identity.mdoc.mso.StaticAuthDataGenerator import com.android.identity.mdoc.util.MdocUtil import com.android.identity.securearea.BouncyCastleSecureArea import com.android.identity.securearea.SecureArea +import com.android.identity.securearea.SecureArea.KeyPurpose import com.android.identity.securearea.SecureAreaRepository import com.android.identity.util.Timestamp import com.android.mdl.app.document.DocumentInformation @@ -242,13 +243,8 @@ class ProvisioningUtil private constructor( ecCurve: Int, validUntil: Timestamp ): AndroidKeystoreSecureArea.CreateKeySettings { - val keyPurpose = if (mDocAuthOption == AddSelfSignedScreenState.MdocAuthStateOption.ECDSA) { - SecureArea.KEY_PURPOSE_SIGN - } else { - SecureArea.KEY_PURPOSE_AGREE_KEY - } return AndroidKeystoreSecureArea.CreateKeySettings.Builder(CHALLENGE) - .setKeyPurposes(keyPurpose) + .setKeyPurposes(mDocAuthOption.toKeyPurpose()) .setUseStrongBox(useStrongBox) .setEcCurve(ecCurve) .setValidityPeriod(Timestamp.now(), validUntil) @@ -264,11 +260,20 @@ class ProvisioningUtil private constructor( passphrase: String? = null ): BouncyCastleSecureArea.CreateKeySettings { return BouncyCastleSecureArea.CreateKeySettings.Builder() - .setPassphraseRequired(passphrase != null, passphrase) .setKeyPurposes(SecureArea.KEY_PURPOSE_SIGN or SecureArea.KEY_PURPOSE_AGREE_KEY) + .setPassphraseRequired(passphrase != null, passphrase) .build() } + @KeyPurpose + private fun AddSelfSignedScreenState.MdocAuthStateOption.toKeyPurpose(): Int { + return if (this == AddSelfSignedScreenState.MdocAuthStateOption.ECDSA) { + SecureArea.KEY_PURPOSE_SIGN + } else { + SecureArea.KEY_PURPOSE_AGREE_KEY + } + } + companion object { private const val AUTH_KEY_DOMAIN = "some_hardcoded_string" From 0c91366dc9f8562ec2199a8bd3cdf56e4b83cd48 Mon Sep 17 00:00:00 2001 From: Jov Mit Date: Mon, 18 Sep 2023 08:54:33 +0200 Subject: [PATCH 2/3] Add support for BoucyCastle SecureArea when creating self signed doc --- .../AuthConfirmationFragment.kt | 9 +- .../mdl/app/document/DocumentInformation.kt | 1 + .../app/documentinfo/DocumentInfoViewModel.kt | 1 + .../selfsigned/AddSelfSignedDocumentScreen.kt | 128 ++++++++++++++---- .../app/selfsigned/AddSelfSignedFragment.kt | 2 +- .../selfsigned/AddSelfSignedScreenState.kt | 53 +++++++- .../app/selfsigned/AddSelfSignedViewModel.kt | 20 ++- .../app/selfsigned/SelfSignedDocumentData.kt | 3 +- .../mdl/app/transfer/TransferManager.kt | 10 -- .../android/mdl/app/util/ProvisioningUtil.kt | 31 ++++- .../viewmodel/TransferDocumentViewModel.kt | 2 - appholder/src/main/res/values/strings.xml | 8 ++ 12 files changed, 211 insertions(+), 57 deletions(-) diff --git a/appholder/src/main/java/com/android/mdl/app/authconfirmation/AuthConfirmationFragment.kt b/appholder/src/main/java/com/android/mdl/app/authconfirmation/AuthConfirmationFragment.kt index 8a4985e80..7d377f408 100644 --- a/appholder/src/main/java/com/android/mdl/app/authconfirmation/AuthConfirmationFragment.kt +++ b/appholder/src/main/java/com/android/mdl/app/authconfirmation/AuthConfirmationFragment.kt @@ -20,6 +20,7 @@ import androidx.lifecycle.repeatOnLifecycle import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs import com.android.identity.android.securearea.AndroidKeystoreSecureArea +import com.android.identity.securearea.BouncyCastleSecureArea import com.android.identity.securearea.SecureArea.ALGORITHM_ES256 import com.android.mdl.app.R import com.android.mdl.app.authprompt.UserAuthPromptBuilder @@ -161,8 +162,9 @@ class AuthConfirmationFragment : BottomSheetDialogFragment() { } private fun authenticationSucceeded(passphrase: String) { - viewModel.sendResponseForSelection() - findNavController().navigateUp() + val unlockData = BouncyCastleSecureArea.KeyUnlockData(passphrase) + val result = viewModel.sendResponseForSelection(unlockData) + onSendResponseResult(result) } private fun authenticationSucceeded() { @@ -211,8 +213,7 @@ class AuthConfirmationFragment : BottomSheetDialogFragment() { } private fun requestPassphrase() { - val destination = AuthConfirmationFragmentDirections - .openPassphrasePrompt() + val destination = AuthConfirmationFragmentDirections.openPassphrasePrompt() findNavController().navigate(destination) } diff --git a/appholder/src/main/java/com/android/mdl/app/document/DocumentInformation.kt b/appholder/src/main/java/com/android/mdl/app/document/DocumentInformation.kt index e71d38858..88759a93d 100644 --- a/appholder/src/main/java/com/android/mdl/app/document/DocumentInformation.kt +++ b/appholder/src/main/java/com/android/mdl/app/document/DocumentInformation.kt @@ -10,6 +10,7 @@ data class DocumentInformation( val maxUsagesPerKey: Int, val lastTimeUsed: String, val mDocAuthOption: String, + val secureAreaImplementationState: SecureAreaImplementationState, val authKeys: List ) { diff --git a/appholder/src/main/java/com/android/mdl/app/documentinfo/DocumentInfoViewModel.kt b/appholder/src/main/java/com/android/mdl/app/documentinfo/DocumentInfoViewModel.kt index 2621e9f57..078db8347 100644 --- a/appholder/src/main/java/com/android/mdl/app/documentinfo/DocumentInfoViewModel.kt +++ b/appholder/src/main/java/com/android/mdl/app/documentinfo/DocumentInfoViewModel.kt @@ -70,6 +70,7 @@ class DocumentInfoViewModel( documentType = documentInformation.docType, documentColor = documentInformation.documentColor.toCardArt(), provisioningDate = documentInformation.dateProvisioned, + secureAreaImplementationState = documentInformation.secureAreaImplementationState, isSelfSigned = documentInformation.selfSigned, lastTimeUsedDate = documentInformation.lastTimeUsed, authKeys = documentInformation.authKeys.asScreenStateKeys() diff --git a/appholder/src/main/java/com/android/mdl/app/selfsigned/AddSelfSignedDocumentScreen.kt b/appholder/src/main/java/com/android/mdl/app/selfsigned/AddSelfSignedDocumentScreen.kt index be2750fe7..1d7788e86 100644 --- a/appholder/src/main/java/com/android/mdl/app/selfsigned/AddSelfSignedDocumentScreen.kt +++ b/appholder/src/main/java/com/android/mdl/app/selfsigned/AddSelfSignedDocumentScreen.kt @@ -59,6 +59,7 @@ import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import com.android.identity.securearea.SecureArea import com.android.mdl.app.R import com.android.mdl.app.composables.PreviewLightDark import com.android.mdl.app.composables.gradientFor @@ -66,8 +67,7 @@ import com.android.mdl.app.composables.keystoreNameFor import com.android.mdl.app.document.DocumentColor import com.android.mdl.app.document.DocumentType import com.android.mdl.app.document.SecureAreaImplementationState -import com.android.mdl.app.selfsigned.AddSelfSignedScreenState.AndroidAuthKeyCurveOption -import com.android.mdl.app.selfsigned.AddSelfSignedScreenState.MdocAuthStateOption +import com.android.mdl.app.selfsigned.AddSelfSignedScreenState.* import com.android.mdl.app.theme.HolderAppTheme @Composable @@ -94,6 +94,7 @@ fun AddSelfSignedDocumentScreen( onBiometricAuthChanged = viewModel::updateBiometricUnlocking, onMdocAuthOptionChange = viewModel::updateMdocAuthOption, onAndroidAuthKeyCurveChanged = viewModel::updateAndroidAuthKeyCurve, + onBouncyCastleAuthKeyCurveChanged = viewModel::updateBouncyCastleAuthKeyCurve, onStrongBoxChanged = viewModel::updateStrongBox, onPassphraseChanged = viewModel::updatePassphrase, onNumberOfMsoChanged = viewModel::updateNumberOfMso, @@ -119,6 +120,7 @@ private fun AddSelfSignedDocumentScreenContent( onBiometricAuthChanged: (newValue: Boolean) -> Unit, onMdocAuthOptionChange: (newValue: MdocAuthStateOption) -> Unit, onAndroidAuthKeyCurveChanged: (newValue: AndroidAuthKeyCurveOption) -> Unit, + onBouncyCastleAuthKeyCurveChanged: (newValue: BouncyCastleAuthKeyCurveOption) -> Unit, onPassphraseChanged: (newValue: String) -> Unit, onNumberOfMsoChanged: (newValue: Int) -> Unit, onMaxUseOfMsoChanged: (newValue: Int) -> Unit, @@ -179,7 +181,7 @@ private fun AddSelfSignedDocumentScreenContent( onBiometricAuthChanged = onBiometricAuthChanged, onStrongBoxChanged = onStrongBoxChanged, ) - MdocAuthenticationAndroid( + MdocAuthentication( modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp), @@ -202,6 +204,21 @@ private fun AddSelfSignedDocumentScreenContent( state = screenState, onPassphraseChanged = onPassphraseChanged ) + MdocAuthentication( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + state = screenState.androidMdocAuthState, + onMdocAuthOptionChange = onMdocAuthOptionChange + ) + AuthenticationKeyCurveBouncyCastle( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + state = screenState.bouncyCastleAuthKeyCurveState, + mDocAuthState = screenState.androidMdocAuthState, + onBouncyCastleAuthKeyCurveChanged = onBouncyCastleAuthKeyCurveChanged + ) } CounterInput( modifier = Modifier @@ -501,9 +518,9 @@ private fun AndroidSetupContainer( modifier: Modifier = Modifier, isOn: Boolean, timeoutSeconds: Int, - lskfAuthTypeState: AddSelfSignedScreenState.AuthTypeState, - biometricAuthTypeState: AddSelfSignedScreenState.AuthTypeState, - useStrongBox: AddSelfSignedScreenState.AuthTypeState, + lskfAuthTypeState: AuthTypeState, + biometricAuthTypeState: AuthTypeState, + useStrongBox: AuthTypeState, onUserAuthenticationChanged: (isOn: Boolean) -> Unit, onAuthTimeoutChanged: (authTimeout: Int) -> Unit, onLskfAuthChanged: (isOn: Boolean) -> Unit, @@ -606,9 +623,9 @@ private fun AndroidSetupContainer( @Composable -private fun MdocAuthenticationAndroid( +private fun MdocAuthentication( modifier: Modifier = Modifier, - state: AddSelfSignedScreenState.MdocAuthOptionState, + state: MdocAuthOptionState, onMdocAuthOptionChange: (newValue: MdocAuthStateOption) -> Unit ) { LabeledUserInput( @@ -657,12 +674,11 @@ private fun MdocAuthenticationAndroid( } } - @Composable private fun AuthenticationKeyCurveAndroid( modifier: Modifier = Modifier, - state: AddSelfSignedScreenState.AndroidAuthKeyCurveState, - mDocAuthState: AddSelfSignedScreenState.MdocAuthOptionState, + state: AndroidAuthKeyCurveState, + mDocAuthState: MdocAuthOptionState, onAndroidAuthKeyCurveChanged: (newValue: AndroidAuthKeyCurveOption) -> Unit ) { LabeledUserInput( @@ -684,7 +700,7 @@ private fun AuthenticationKeyCurveAndroid( ) { ValueLabel( modifier = Modifier.weight(1f), - label = curveLabelFor(state.authCurve) + label = curveLabelFor(state.authCurve.toEcCurve()) ) DropDownIndicator() } @@ -698,14 +714,14 @@ private fun AuthenticationKeyCurveAndroid( AndroidAuthKeyCurveOption.X25519 } TextDropDownRow( - label = curveLabelFor(curveOption = AndroidAuthKeyCurveOption.P_256), + label = curveLabelFor(curveOption = AndroidAuthKeyCurveOption.P_256.toEcCurve()), onSelected = { onAndroidAuthKeyCurveChanged(AndroidAuthKeyCurveOption.P_256) keyCurveDropDownExpanded = false } ) TextDropDownRow( - label = curveLabelFor(curveOption = ecCurveOption), + label = curveLabelFor(curveOption = ecCurveOption.toEcCurve()), onSelected = { onAndroidAuthKeyCurveChanged(ecCurveOption) keyCurveDropDownExpanded = false @@ -715,6 +731,61 @@ private fun AuthenticationKeyCurveAndroid( } } +@Composable +private fun AuthenticationKeyCurveBouncyCastle( + modifier: Modifier = Modifier, + state: BouncyCastleAuthKeyCurveState, + mDocAuthState: MdocAuthOptionState, + onBouncyCastleAuthKeyCurveChanged: (newValue: BouncyCastleAuthKeyCurveOption) -> Unit +) { + LabeledUserInput( + modifier = modifier, + label = stringResource(id = R.string.authentication_key_curve_label) + ) { + var keyCurveDropDownExpanded by remember { mutableStateOf(false) } + val clickModifier = if (state.isEnabled) { + Modifier.clickable { keyCurveDropDownExpanded = true } + } else { + Modifier + } + val alpha = if (state.isEnabled) 1f else .5f + OutlinedContainerHorizontal( + modifier = Modifier + .fillMaxWidth() + .alpha(alpha) + .then(clickModifier) + ) { + ValueLabel( + modifier = Modifier.weight(1f), + label = curveLabelFor(state.authCurve.toEcCurve()) + ) + DropDownIndicator() + } + val entries = BouncyCastleAuthKeyCurveOption.values().toMutableList() + if (mDocAuthState.mDocAuthentication == MdocAuthStateOption.ECDSA) { + entries.remove(BouncyCastleAuthKeyCurveOption.X448) + entries.remove(BouncyCastleAuthKeyCurveOption.X25519) + } else { + entries.remove(BouncyCastleAuthKeyCurveOption.Ed448) + entries.remove(BouncyCastleAuthKeyCurveOption.Ed25519) + } + DropdownMenu( + expanded = keyCurveDropDownExpanded, + onDismissRequest = { keyCurveDropDownExpanded = false } + ) { + for (entry in entries) { + TextDropDownRow( + label = curveLabelFor(curveOption = entry.toEcCurve()), + onSelected = { + onBouncyCastleAuthKeyCurveChanged(entry) + keyCurveDropDownExpanded = false + } + ) + } + } + } +} + @Composable private fun CounterInput( modifier: Modifier = Modifier, @@ -922,17 +993,21 @@ private fun mdocAuthOptionLabelFor( @Composable private fun curveLabelFor( - curveOption: AndroidAuthKeyCurveOption + @SecureArea.EcCurve curveOption: Int ): String { return when (curveOption) { - AndroidAuthKeyCurveOption.P_256 -> - stringResource(id = R.string.curve_p_256) - - AndroidAuthKeyCurveOption.Ed25519 -> - stringResource(id = R.string.curve_ed25519) - - AndroidAuthKeyCurveOption.X25519 -> - stringResource(id = R.string.curve_x25519) + SecureArea.EC_CURVE_P256 -> stringResource(id = R.string.curve_p_256) + SecureArea.EC_CURVE_P384 -> stringResource(id = R.string.curve_p_384) + SecureArea.EC_CURVE_P521 -> stringResource(id = R.string.curve_p_521) + SecureArea.EC_CURVE_BRAINPOOLP256R1 -> stringResource(id = R.string.curve_brain_pool_p_256R1) + SecureArea.EC_CURVE_BRAINPOOLP320R1 -> stringResource(id = R.string.curve_brain_pool_p_320R1) + SecureArea.EC_CURVE_BRAINPOOLP384R1 -> stringResource(id = R.string.curve_brain_pool_p_384R1) + SecureArea.EC_CURVE_BRAINPOOLP512R1 -> stringResource(id = R.string.curve_brain_pool_p_512R1) + SecureArea.EC_CURVE_ED25519 -> stringResource(id = R.string.curve_ed25519) + SecureArea.EC_CURVE_X25519 -> stringResource(id = R.string.curve_x25519) + SecureArea.EC_CURVE_ED448 -> stringResource(id = R.string.curve_ed448) + SecureArea.EC_CURVE_X448 -> stringResource(id = R.string.curve_X448) + else -> "" } } @@ -974,6 +1049,7 @@ private fun PreviewAddSelfSignedDocumentScreenAndroidKeystore() { onStrongBoxChanged = {}, onMdocAuthOptionChange = {}, onAndroidAuthKeyCurveChanged = {}, + onBouncyCastleAuthKeyCurveChanged = {}, onPassphraseChanged = {}, onNumberOfMsoChanged = {}, onMaxUseOfMsoChanged = {}, @@ -992,11 +1068,11 @@ private fun PreviewAddSelfSignedDocumentScreenAndroidKeystoreAuthOn() { modifier = Modifier.fillMaxSize(), screenState = AddSelfSignedScreenState( userAuthentication = true, - allowLSKFUnlocking = AddSelfSignedScreenState.AuthTypeState( + allowLSKFUnlocking = AuthTypeState( isEnabled = true, canBeModified = true ), - allowBiometricUnlocking = AddSelfSignedScreenState.AuthTypeState( + allowBiometricUnlocking = AuthTypeState( isEnabled = true, canBeModified = false ), @@ -1012,6 +1088,7 @@ private fun PreviewAddSelfSignedDocumentScreenAndroidKeystoreAuthOn() { onStrongBoxChanged = {}, onMdocAuthOptionChange = {}, onAndroidAuthKeyCurveChanged = {}, + onBouncyCastleAuthKeyCurveChanged = {}, onPassphraseChanged = {}, onNumberOfMsoChanged = {}, onMaxUseOfMsoChanged = {}, @@ -1042,6 +1119,7 @@ private fun PreviewAddSelfSignedDocumentScreenBouncyCastleKeystore() { onStrongBoxChanged = {}, onMdocAuthOptionChange = {}, onAndroidAuthKeyCurveChanged = {}, + onBouncyCastleAuthKeyCurveChanged = {}, onPassphraseChanged = {}, onNumberOfMsoChanged = {}, onMaxUseOfMsoChanged = {}, diff --git a/appholder/src/main/java/com/android/mdl/app/selfsigned/AddSelfSignedFragment.kt b/appholder/src/main/java/com/android/mdl/app/selfsigned/AddSelfSignedFragment.kt index d613add69..25486bbaa 100644 --- a/appholder/src/main/java/com/android/mdl/app/selfsigned/AddSelfSignedFragment.kt +++ b/appholder/src/main/java/com/android/mdl/app/selfsigned/AddSelfSignedFragment.kt @@ -44,7 +44,7 @@ class AddSelfSignedFragment : Fragment() { allowBiometricUnlocking = state.allowBiometricUnlocking.isEnabled, useStrongBox = state.useStrongBox.isEnabled, mDocAuthenticationOption = state.androidMdocAuthState.mDocAuthentication, - androidAuthKeyCurveOption = state.androidAuthKeyCurveState.authCurve, + authKeyCurve = state.ecCurve, validityInDays = state.validityInDays, minValidityInDays = state.minValidityInDays, passphrase = state.passphrase.ifBlank { null }, diff --git a/appholder/src/main/java/com/android/mdl/app/selfsigned/AddSelfSignedScreenState.kt b/appholder/src/main/java/com/android/mdl/app/selfsigned/AddSelfSignedScreenState.kt index 40d788303..8b6354ef5 100644 --- a/appholder/src/main/java/com/android/mdl/app/selfsigned/AddSelfSignedScreenState.kt +++ b/appholder/src/main/java/com/android/mdl/app/selfsigned/AddSelfSignedScreenState.kt @@ -1,7 +1,7 @@ package com.android.mdl.app.selfsigned import android.os.Parcelable -import com.android.identity.android.securearea.AndroidKeystoreSecureArea +import com.android.identity.securearea.SecureArea import com.android.mdl.app.document.DocumentColor import com.android.mdl.app.document.DocumentType import com.android.mdl.app.document.SecureAreaImplementationState @@ -29,6 +29,7 @@ data class AddSelfSignedScreenState( ), val androidMdocAuthState: MdocAuthOptionState = MdocAuthOptionState(), val androidAuthKeyCurveState: AndroidAuthKeyCurveState = AndroidAuthKeyCurveState(), + val bouncyCastleAuthKeyCurveState: BouncyCastleAuthKeyCurveState = BouncyCastleAuthKeyCurveState(), val passphrase: String = "", val numberOfMso: Int = 10, val maxUseOfMso: Int = 1, @@ -39,6 +40,13 @@ data class AddSelfSignedScreenState( val isAndroidKeystoreSelected: Boolean get() = secureAreaImplementationState == SecureAreaImplementationState.Android + val ecCurve: Int + get() = if (secureAreaImplementationState == SecureAreaImplementationState.Android) { + androidAuthKeyCurveState.authCurve.toEcCurve() + } else { + bouncyCastleAuthKeyCurveState.authCurve.toEcCurve() + } + @Parcelize data class AuthTypeState( val isEnabled: Boolean = true, @@ -57,6 +65,12 @@ data class AddSelfSignedScreenState( val authCurve: AndroidAuthKeyCurveOption = AndroidAuthKeyCurveOption.P_256 ) : Parcelable + @Parcelize + data class BouncyCastleAuthKeyCurveState( + val isEnabled: Boolean = true, + val authCurve: BouncyCastleAuthKeyCurveOption = BouncyCastleAuthKeyCurveOption.P256 + ) : Parcelable + @Parcelize enum class MdocAuthStateOption : Parcelable { ECDSA, MAC @@ -68,9 +82,40 @@ data class AddSelfSignedScreenState( fun toEcCurve(): Int { return when (this) { - P_256 -> AndroidKeystoreSecureArea.EC_CURVE_P256 - Ed25519 -> AndroidKeystoreSecureArea.EC_CURVE_ED25519 - X25519 -> AndroidKeystoreSecureArea.EC_CURVE_X25519 + P_256 -> SecureArea.EC_CURVE_P256 + Ed25519 -> SecureArea.EC_CURVE_ED25519 + X25519 -> SecureArea.EC_CURVE_X25519 + } + } + } + + @Parcelize + enum class BouncyCastleAuthKeyCurveOption : Parcelable { + P256, + P384, + P521, + BrainPoolP256R1, + BrainPoolP320R1, + BrainPoolP384R1, + BrainPoolP512R1, + Ed25519, + Ed448, + X25519, + X448; + + fun toEcCurve(): Int { + return when (this) { + P256 -> SecureArea.EC_CURVE_P256 + P384 -> SecureArea.EC_CURVE_P384 + P521 -> SecureArea.EC_CURVE_P521 + BrainPoolP256R1 -> SecureArea.EC_CURVE_BRAINPOOLP256R1 + BrainPoolP320R1 -> SecureArea.EC_CURVE_BRAINPOOLP320R1 + BrainPoolP384R1 -> SecureArea.EC_CURVE_BRAINPOOLP384R1 + BrainPoolP512R1 -> SecureArea.EC_CURVE_BRAINPOOLP512R1 + Ed25519 -> SecureArea.EC_CURVE_ED25519 + Ed448 -> SecureArea.EC_CURVE_ED448 + X25519 -> SecureArea.EC_CURVE_X25519 + X448 -> SecureArea.EC_CURVE_X448 } } } diff --git a/appholder/src/main/java/com/android/mdl/app/selfsigned/AddSelfSignedViewModel.kt b/appholder/src/main/java/com/android/mdl/app/selfsigned/AddSelfSignedViewModel.kt index cc033707f..5afe82269 100644 --- a/appholder/src/main/java/com/android/mdl/app/selfsigned/AddSelfSignedViewModel.kt +++ b/appholder/src/main/java/com/android/mdl/app/selfsigned/AddSelfSignedViewModel.kt @@ -10,6 +10,7 @@ import com.android.mdl.app.document.SecureAreaImplementationState import com.android.mdl.app.selfsigned.AddSelfSignedScreenState.AndroidAuthKeyCurveOption import com.android.mdl.app.selfsigned.AddSelfSignedScreenState.AndroidAuthKeyCurveState import com.android.mdl.app.selfsigned.AddSelfSignedScreenState.AuthTypeState +import com.android.mdl.app.selfsigned.AddSelfSignedScreenState.BouncyCastleAuthKeyCurveOption import com.android.mdl.app.selfsigned.AddSelfSignedScreenState.MdocAuthOptionState import com.android.mdl.app.selfsigned.AddSelfSignedScreenState.MdocAuthStateOption import com.android.mdl.app.util.getState @@ -31,8 +32,14 @@ class AddSelfSignedViewModel( capabilities = KeystoreUtil(context).getDeviceCapabilities() savedStateHandle.updateState { it.copy( - allowLSKFUnlocking = AuthTypeState(true, capabilities.configureUserAuthenticationType), - allowBiometricUnlocking = AuthTypeState(true, capabilities.configureUserAuthenticationType), + allowLSKFUnlocking = AuthTypeState( + true, + capabilities.configureUserAuthenticationType + ), + allowBiometricUnlocking = AuthTypeState( + true, + capabilities.configureUserAuthenticationType + ), useStrongBox = AuthTypeState(false, capabilities.strongBox), androidMdocAuthState = MdocAuthOptionState( isEnabled = if (it.useStrongBox.isEnabled) capabilities.strongBoxEcdh else capabilities.ecdh @@ -113,7 +120,8 @@ class AddSelfSignedViewModel( savedStateHandle.updateState { it.copy( androidMdocAuthState = it.androidMdocAuthState.copy(mDocAuthentication = newValue), - androidAuthKeyCurveState = it.androidAuthKeyCurveState.copy(authCurve = AndroidAuthKeyCurveOption.P_256) + androidAuthKeyCurveState = it.androidAuthKeyCurveState.copy(authCurve = AndroidAuthKeyCurveOption.P_256), + bouncyCastleAuthKeyCurveState = it.bouncyCastleAuthKeyCurveState.copy(authCurve = BouncyCastleAuthKeyCurveOption.P256) ) } } @@ -124,6 +132,12 @@ class AddSelfSignedViewModel( } } + fun updateBouncyCastleAuthKeyCurve(newValue: BouncyCastleAuthKeyCurveOption) { + savedStateHandle.updateState { + it.copy(bouncyCastleAuthKeyCurveState = it.bouncyCastleAuthKeyCurveState.copy(authCurve = newValue)) + } + } + fun updateValidityInDays(newValue: Int) { val state = savedStateHandle.getState(AddSelfSignedScreenState()) if (newValue < state.value.minValidityInDays) return diff --git a/appholder/src/main/java/com/android/mdl/app/selfsigned/SelfSignedDocumentData.kt b/appholder/src/main/java/com/android/mdl/app/selfsigned/SelfSignedDocumentData.kt index 898f5bbe2..0aa434588 100644 --- a/appholder/src/main/java/com/android/mdl/app/selfsigned/SelfSignedDocumentData.kt +++ b/appholder/src/main/java/com/android/mdl/app/selfsigned/SelfSignedDocumentData.kt @@ -3,7 +3,6 @@ package com.android.mdl.app.selfsigned import android.graphics.Bitmap import android.os.Parcelable import com.android.mdl.app.document.SecureAreaImplementationState -import com.android.mdl.app.selfsigned.AddSelfSignedScreenState.AndroidAuthKeyCurveOption import com.android.mdl.app.selfsigned.AddSelfSignedScreenState.MdocAuthStateOption import com.android.mdl.app.util.Field import kotlinx.parcelize.Parcelize @@ -45,7 +44,7 @@ data class ProvisionInfo( val allowBiometricUnlocking: Boolean, val useStrongBox: Boolean, val mDocAuthenticationOption: MdocAuthStateOption, - val androidAuthKeyCurveOption: AndroidAuthKeyCurveOption, + val authKeyCurve: Int, val validityInDays: Int, val minValidityInDays: Int, val passphrase: String?, diff --git a/appholder/src/main/java/com/android/mdl/app/transfer/TransferManager.kt b/appholder/src/main/java/com/android/mdl/app/transfer/TransferManager.kt index 7174ac887..f6441a138 100644 --- a/appholder/src/main/java/com/android/mdl/app/transfer/TransferManager.kt +++ b/appholder/src/main/java/com/android/mdl/app/transfer/TransferManager.kt @@ -278,16 +278,6 @@ class TransferManager private constructor(private val context: Context) { hasStarted = false } - fun getCryptoObject(): BiometricPrompt.CryptoObject? { - try { - return session?.cryptoObject - } catch (e: RuntimeException) { - // Error when device doesn't have secure unlock - log("getCryptoObject: ${e.message}", e) - } - return null - } - fun sendResponse(deviceResponse: ByteArray, closeAfterSending: Boolean) { communication.sendResponse(deviceResponse, closeAfterSending) if (closeAfterSending) { diff --git a/appholder/src/main/java/com/android/mdl/app/util/ProvisioningUtil.kt b/appholder/src/main/java/com/android/mdl/app/util/ProvisioningUtil.kt index 3cd749340..309f6ec4f 100644 --- a/appholder/src/main/java/com/android/mdl/app/util/ProvisioningUtil.kt +++ b/appholder/src/main/java/com/android/mdl/app/util/ProvisioningUtil.kt @@ -66,12 +66,14 @@ class ProvisioningUtil private constructor( authTimeoutMillis = provisionInfo.userAuthenticationTimeoutSeconds * 1000L, userAuthenticationType = provisionInfo.userAuthType(), useStrongBox = provisionInfo.useStrongBox, - ecCurve = provisionInfo.androidAuthKeyCurveOption.toEcCurve(), + ecCurve = provisionInfo.authKeyCurve, validUntil = provisionInfo.validityInDays.toTimestampFromNow() ) SecureAreaImplementationState.BouncyCastle -> createBouncyCastleKeystoreSettings( - passphrase = provisionInfo.passphrase + passphrase = provisionInfo.passphrase, + mDocAuthOption = provisionInfo.mDocAuthenticationOption, + ecCurve = provisionInfo.authKeyCurve ) } @@ -200,9 +202,9 @@ class ProvisioningUtil private constructor( } private fun manageKeysFor(credential: Credential): Int { + val mDocAuthOption = credential.applicationData.getString(MDOC_AUTHENTICATION) val settings = when (credential.credentialSecureArea) { is AndroidKeystoreSecureArea -> { - val mDocAuthOption = credential.applicationData.getString(MDOC_AUTHENTICATION) val keyInfo = credential.credentialSecureArea.getKeyInfo(credential.credentialKeyAlias) as AndroidKeystoreSecureArea.KeyInfo createAndroidKeystoreSettings( keyInfo.isUserAuthenticationRequired, @@ -216,7 +218,11 @@ class ProvisioningUtil private constructor( } is BouncyCastleSecureArea -> { - createBouncyCastleKeystoreSettings() + val keyInfo = credential.credentialSecureArea.getKeyInfo(credential.credentialKeyAlias) as BouncyCastleSecureArea.KeyInfo + createBouncyCastleKeystoreSettings( + mDocAuthOption = AddSelfSignedScreenState.MdocAuthStateOption.valueOf(mDocAuthOption), + ecCurve = keyInfo.ecCurve + ) } else -> throw IllegalStateException("Unknown keystore secure area implementation") @@ -257,11 +263,15 @@ class ProvisioningUtil private constructor( } private fun createBouncyCastleKeystoreSettings( - passphrase: String? = null + passphrase: String? = null, + mDocAuthOption: AddSelfSignedScreenState.MdocAuthStateOption, + ecCurve: Int ): BouncyCastleSecureArea.CreateKeySettings { + val keyPurpose = mDocAuthOption.toKeyPurpose() return BouncyCastleSecureArea.CreateKeySettings.Builder() - .setKeyPurposes(SecureArea.KEY_PURPOSE_SIGN or SecureArea.KEY_PURPOSE_AGREE_KEY) .setPassphraseRequired(passphrase != null, passphrase) + .setKeyPurposes(keyPurpose) + .setEcCurve(ecCurve) .build() } @@ -330,12 +340,21 @@ class ProvisioningUtil private constructor( selfSigned = it.applicationData.getBoolean(IS_SELF_SIGNED), maxUsagesPerKey = it.applicationData.getNumber(MAX_USAGES_PER_KEY).toInt(), mDocAuthOption = it.applicationData.getString(MDOC_AUTHENTICATION), + secureAreaImplementationState = it.credentialSecureArea.toSecureAreaState(), lastTimeUsed = lastTimeUsed, authKeys = authKeys ) } } + private fun SecureArea.toSecureAreaState(): SecureAreaImplementationState { + return when (this) { + is AndroidKeystoreSecureArea -> SecureAreaImplementationState.Android + is BouncyCastleSecureArea -> SecureAreaImplementationState.BouncyCastle + else -> throw IllegalStateException("Unknown Secure Area Implementation") + } + } + private fun Timestamp.formatted(): String { val instant = Instant.ofEpochMilli(this.toEpochMilli()) val dateTime = ZonedDateTime.ofInstant(instant, ZoneId.systemDefault()) diff --git a/appholder/src/main/java/com/android/mdl/app/viewmodel/TransferDocumentViewModel.kt b/appholder/src/main/java/com/android/mdl/app/viewmodel/TransferDocumentViewModel.kt index 486ee97c8..f93a3aba1 100644 --- a/appholder/src/main/java/com/android/mdl/app/viewmodel/TransferDocumentViewModel.kt +++ b/appholder/src/main/java/com/android/mdl/app/viewmodel/TransferDocumentViewModel.kt @@ -59,8 +59,6 @@ class TransferDocumentViewModel(val app: Application) : AndroidViewModel(app) { fun getSelectedDocuments() = selectedDocuments - fun getCryptoObject() = transferManager.getCryptoObject() - fun requestedElements() = requestedElements fun closeConnection() { diff --git a/appholder/src/main/res/values/strings.xml b/appholder/src/main/res/values/strings.xml index 539b642a0..31fa9e792 100644 --- a/appholder/src/main/res/values/strings.xml +++ b/appholder/src/main/res/values/strings.xml @@ -193,8 +193,16 @@ mdoc ECDSA authentication mdoc MAC authentication P-256 + P-384 + P-521 + BrainPoolP256R1 + BrainPoolP320R1 + BrainPoolP384R1 + BrainPoolP512R1 Ed25519 + Ed448 X25519 + X448 Validity time in days Minimum validity in days From f727f61ba965df68c8479551694a2792cebdbf3d Mon Sep 17 00:00:00 2001 From: Jov Mit Date: Sat, 23 Sep 2023 12:45:05 +0200 Subject: [PATCH 3/3] Default auth timeout to 0sec, fix presentation bug with keys with 0 timeout --- .../AuthConfirmationFragment.kt | 22 +-- .../app/authconfirmation/PassphrasePrompt.kt | 42 +++++- .../selfsigned/AddSelfSignedScreenState.kt | 2 +- .../transfer/AddDocumentToResponseResult.kt | 4 +- .../mdl/app/transfer/TransferManager.kt | 137 ++++++++---------- .../android/mdl/app/util/ProvisioningUtil.kt | 5 +- .../main/res/navigation/navigation_graph.xml | 9 +- appholder/src/main/res/values/strings.xml | 2 + 8 files changed, 132 insertions(+), 91 deletions(-) diff --git a/appholder/src/main/java/com/android/mdl/app/authconfirmation/AuthConfirmationFragment.kt b/appholder/src/main/java/com/android/mdl/app/authconfirmation/AuthConfirmationFragment.kt index 7d377f408..9802289d1 100644 --- a/appholder/src/main/java/com/android/mdl/app/authconfirmation/AuthConfirmationFragment.kt +++ b/appholder/src/main/java/com/android/mdl/app/authconfirmation/AuthConfirmationFragment.kt @@ -38,6 +38,7 @@ class AuthConfirmationFragment : BottomSheetDialogFragment() { private val passphraseViewModel: PassphrasePromptViewModel by activityViewModels() private val arguments by navArgs() private var isSendingInProgress = mutableStateOf(false) + private var androidKeyUnlockData: AndroidKeystoreSecureArea.KeyUnlockData? = null override fun onCreateView( inflater: LayoutInflater, @@ -73,7 +74,7 @@ class AuthConfirmationFragment : BottomSheetDialogFragment() { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.CREATED) { passphraseViewModel.authorizationState.collect { value -> if (value is PassphraseAuthResult.Success) { - authenticationSucceeded(value.userPassphrase) + onPassphraseProvided(value.userPassphrase) passphraseViewModel.reset() } } @@ -145,8 +146,6 @@ class AuthConfirmationFragment : BottomSheetDialogFragment() { userAuthRequest.build().authenticate(cryptoObject) } - private var androidKeyUnlockData: AndroidKeystoreSecureArea.KeyUnlockData? = null - private fun getSubtitle(): String { val readerCommonName = arguments.readerCommonName val readerIsTrusted = arguments.readerIsTrusted @@ -161,7 +160,7 @@ class AuthConfirmationFragment : BottomSheetDialogFragment() { } } - private fun authenticationSucceeded(passphrase: String) { + private fun onPassphraseProvided(passphrase: String) { val unlockData = BouncyCastleSecureArea.KeyUnlockData(passphrase) val result = viewModel.sendResponseForSelection(unlockData) onSendResponseResult(result) @@ -181,8 +180,7 @@ class AuthConfirmationFragment : BottomSheetDialogFragment() { private fun onSendResponseResult(result: AddDocumentToResponseResult) { when (result) { is AddDocumentToResponseResult.UserAuthRequired -> { - val keyUnlockData = AndroidKeystoreSecureArea.KeyUnlockData(result.keyAlias) - androidKeyUnlockData = keyUnlockData + androidKeyUnlockData = AndroidKeystoreSecureArea.KeyUnlockData(result.keyAlias) requestUserAuth( result.allowLSKFUnlocking, result.allowBiometricUnlocking @@ -190,7 +188,7 @@ class AuthConfirmationFragment : BottomSheetDialogFragment() { } is AddDocumentToResponseResult.PassphraseRequired -> { - requestPassphrase() + requestPassphrase(result.attemptedWithIncorrectPassword) } is AddDocumentToResponseResult.DocumentAdded -> { @@ -212,9 +210,13 @@ class AuthConfirmationFragment : BottomSheetDialogFragment() { viewModel.closeConnection() } - private fun requestPassphrase() { - val destination = AuthConfirmationFragmentDirections.openPassphrasePrompt() - findNavController().navigate(destination) + private fun requestPassphrase(attemptedWithIncorrectPassword: Boolean) { + val destination = AuthConfirmationFragmentDirections.openPassphrasePrompt( + showIncorrectPassword = attemptedWithIncorrectPassword + ) + val runnable = { findNavController().navigate(destination) } + // The system needs a little time to get back to this screen + Handler(Looper.getMainLooper()).postDelayed(runnable, 500) } private fun toast(message: String, duration: Int = Toast.LENGTH_SHORT) { diff --git a/appholder/src/main/java/com/android/mdl/app/authconfirmation/PassphrasePrompt.kt b/appholder/src/main/java/com/android/mdl/app/authconfirmation/PassphrasePrompt.kt index de35e6fb4..18d774bb2 100644 --- a/appholder/src/main/java/com/android/mdl/app/authconfirmation/PassphrasePrompt.kt +++ b/appholder/src/main/java/com/android/mdl/app/authconfirmation/PassphrasePrompt.kt @@ -8,6 +8,7 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.MaterialTheme @@ -23,6 +24,8 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.PasswordVisualTransformation +import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.fragment.app.DialogFragment @@ -30,10 +33,12 @@ import androidx.fragment.app.activityViewModels import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs import com.android.mdl.app.R +import com.android.mdl.app.composables.PreviewLightDark import com.android.mdl.app.theme.HolderAppTheme class PassphrasePrompt : DialogFragment() { + private val args by navArgs() private val viewModel by activityViewModels() override fun onCreateView( @@ -45,6 +50,7 @@ class PassphrasePrompt : DialogFragment() { setContent { HolderAppTheme { PassphrasePromptUI( + showIncorrectPassword = args.showIncorrectPassword, onDone = { passphrase -> viewModel.authorize(userPassphrase = passphrase) findNavController().navigateUp() @@ -58,6 +64,7 @@ class PassphrasePrompt : DialogFragment() { @Composable private fun PassphrasePromptUI( + showIncorrectPassword: Boolean, onDone: (passphrase: String) -> Unit ) { var value by remember { mutableStateOf("") } @@ -82,8 +89,23 @@ private fun PassphrasePromptUI( modifier = Modifier.fillMaxWidth(), value = value, onValueChange = { value = it }, - textStyle = MaterialTheme.typography.bodyMedium + textStyle = MaterialTheme.typography.bodyMedium, + visualTransformation = PasswordVisualTransformation(), + placeholder = { + Text( + text = stringResource(id = R.string.passphrase_prompt_hint), + style = MaterialTheme.typography.titleSmall, + color = MaterialTheme.colorScheme.onBackground.copy(alpha = .5f) + ) + } ) + if (showIncorrectPassword) { + Text( + text = stringResource(id = R.string.passphrase_prompt_incorrect_passphrase), + style = MaterialTheme.typography.labelSmall, + color = MaterialTheme.colorScheme.error + ) + } TextButton( modifier = Modifier .align(Alignment.End), @@ -95,9 +117,23 @@ private fun PassphrasePromptUI( } @Composable -@Preview +@PreviewLightDark private fun PreviewPassphrasePrompt() { HolderAppTheme { - PassphrasePromptUI(onDone = {}) + PassphrasePromptUI( + showIncorrectPassword = false, + onDone = {} + ) + } +} + +@Composable +@PreviewLightDark +private fun PreviewPassphrasePromptWithIncorrectPassword() { + HolderAppTheme { + PassphrasePromptUI( + showIncorrectPassword = true, + onDone = {} + ) } } \ No newline at end of file diff --git a/appholder/src/main/java/com/android/mdl/app/selfsigned/AddSelfSignedScreenState.kt b/appholder/src/main/java/com/android/mdl/app/selfsigned/AddSelfSignedScreenState.kt index 8b6354ef5..eaa2802f4 100644 --- a/appholder/src/main/java/com/android/mdl/app/selfsigned/AddSelfSignedScreenState.kt +++ b/appholder/src/main/java/com/android/mdl/app/selfsigned/AddSelfSignedScreenState.kt @@ -14,7 +14,7 @@ data class AddSelfSignedScreenState( val documentName: String = "Driving License", val secureAreaImplementationState: SecureAreaImplementationState = SecureAreaImplementationState.Android, val userAuthentication: Boolean = true, - val userAuthenticationTimeoutSeconds: Int = 10, + val userAuthenticationTimeoutSeconds: Int = 0, val allowLSKFUnlocking: AuthTypeState = AuthTypeState( isEnabled = true, canBeModified = false diff --git a/appholder/src/main/java/com/android/mdl/app/transfer/AddDocumentToResponseResult.kt b/appholder/src/main/java/com/android/mdl/app/transfer/AddDocumentToResponseResult.kt index 78573f47f..798c9216b 100644 --- a/appholder/src/main/java/com/android/mdl/app/transfer/AddDocumentToResponseResult.kt +++ b/appholder/src/main/java/com/android/mdl/app/transfer/AddDocumentToResponseResult.kt @@ -12,5 +12,7 @@ sealed class AddDocumentToResponseResult { val allowBiometricUnlocking: Boolean ) : AddDocumentToResponseResult() - object PassphraseRequired : AddDocumentToResponseResult() + data class PassphraseRequired( + val attemptedWithIncorrectPassword: Boolean = false + ) : AddDocumentToResponseResult() } \ No newline at end of file diff --git a/appholder/src/main/java/com/android/mdl/app/transfer/TransferManager.kt b/appholder/src/main/java/com/android/mdl/app/transfer/TransferManager.kt index f6441a138..ec30c58b1 100644 --- a/appholder/src/main/java/com/android/mdl/app/transfer/TransferManager.kt +++ b/appholder/src/main/java/com/android/mdl/app/transfer/TransferManager.kt @@ -8,7 +8,6 @@ import android.graphics.Color.WHITE import android.nfc.cardemulation.HostApduService import android.view.View import android.widget.ImageView -import androidx.biometric.BiometricPrompt import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import com.android.identity.* @@ -40,8 +39,6 @@ import java.util.* class TransferManager private constructor(private val context: Context) { companion object { - private const val LOG_TAG = "TransferManager" - @SuppressLint("StaticFieldLeak") @Volatile private var instance: TransferManager? = null @@ -55,7 +52,6 @@ class TransferManager private constructor(private val context: Context) { private var reversedQrCommunicationSetup: ReverseQrCommunicationSetup? = null private var qrCommunicationSetup: QrCommunicationSetup? = null private var hostApduService: HostApduService? = null - private var session: PresentationSession? = null private var hasStarted = false private lateinit var communication: Communication @@ -63,7 +59,6 @@ class TransferManager private constructor(private val context: Context) { private var transferStatusLd = MutableLiveData() fun setCommunication(session: PresentationSession, communication: Communication) { - this.session = session this.communication = communication } @@ -88,7 +83,6 @@ class TransferManager private constructor(private val context: Context) { reversedQrCommunicationSetup = ReverseQrCommunicationSetup( context = context, onPresentationReady = { session, presentation -> - this.session = session communication.setupPresentation(presentation) }, onNewRequest = { request -> @@ -116,7 +110,6 @@ class TransferManager private constructor(private val context: Context) { onConnecting = { transferStatusLd.value = TransferStatus.CONNECTING }, onQrEngagementReady = { transferStatusLd.value = TransferStatus.QR_ENGAGEMENT_READY }, onDeviceRetrievalHelperReady = { session, deviceRetrievalHelper -> - this.session = session communication.setupPresentation(deviceRetrievalHelper) transferStatusLd.value = TransferStatus.CONNECTED }, @@ -178,76 +171,75 @@ class TransferManager private constructor(private val context: Context) { keyUnlockData: SecureArea.KeyUnlockData? ): AddDocumentToResponseResult { var signingKeyUsageLimitPassed = false - session?.let { - val documentManager = DocumentManager.getInstance(context) - val documentInformation = documentManager.getDocumentInformation(credentialName) - requireValidProperty(documentInformation) { "Document not found!" } + val documentManager = DocumentManager.getInstance(context) + val documentInformation = documentManager.getDocumentInformation(credentialName) + requireValidProperty(documentInformation) { "Document not found!" } - val credential = requireNotNull(documentManager.getCredentialByName(credentialName)) - val dataElements = issuerSignedEntriesToRequest.keys.flatMap { key -> - issuerSignedEntriesToRequest.getOrDefault(key, emptyList()).map { value -> - CredentialRequest.DataElement(key, value, false) - } + val credential = requireNotNull(documentManager.getCredentialByName(credentialName)) + val dataElements = issuerSignedEntriesToRequest.keys.flatMap { key -> + issuerSignedEntriesToRequest.getOrDefault(key, emptyList()).map { value -> + CredentialRequest.DataElement(key, value, false) } + } - val request = CredentialRequest(dataElements) - val authKey = credential.findAuthenticationKey(Timestamp.now()) - ?: throw IllegalStateException("No auth key available") - if (authKey.usageCount >= documentInformation.maxUsagesPerKey) { - logWarning("Using Auth Key previously used ${authKey.usageCount} times, and maxUsagesPerKey is ${documentInformation.maxUsagesPerKey}") - signingKeyUsageLimitPassed = true - } + val request = CredentialRequest(dataElements) + val authKey = credential.findAuthenticationKey(Timestamp.now()) + ?: throw IllegalStateException("No auth key available") + if (authKey.usageCount >= documentInformation.maxUsagesPerKey) { + logWarning("Using Auth Key previously used ${authKey.usageCount} times, and maxUsagesPerKey is ${documentInformation.maxUsagesPerKey}") + signingKeyUsageLimitPassed = true + } - val staticAuthData = StaticAuthDataParser(authKey.issuerProvidedData).parse() - val mergedIssuerNamespaces = MdocUtil.mergeIssuerNamesSpaces( - request, credential.nameSpacedData, staticAuthData - ) + val staticAuthData = StaticAuthDataParser(authKey.issuerProvidedData).parse() + val mergedIssuerNamespaces = MdocUtil.mergeIssuerNamesSpaces( + request, credential.nameSpacedData, staticAuthData + ) - val transcript = communication.getSessionTranscript() ?: byteArrayOf() - val authOption = AddSelfSignedScreenState.MdocAuthStateOption.valueOf(documentInformation.mDocAuthOption) - try { - val generator = DocumentGenerator(docType, staticAuthData.issuerAuth, transcript) - .setIssuerNamespaces(mergedIssuerNamespaces) - if (authOption == AddSelfSignedScreenState.MdocAuthStateOption.ECDSA) { - generator.setDeviceNamespacesSignature(NameSpacedData.Builder().build(), - authKey.secureArea, - authKey.alias, - keyUnlockData, - SecureArea.ALGORITHM_ES256 - ) - } else { - generator.setDeviceNamespacesMac(NameSpacedData.Builder().build(), - authKey.secureArea, - authKey.alias, - keyUnlockData, - authKey.attestation.first().publicKey - ) - } - val data = generator.generate() - keyUnlockData?.let { - if (authOption == AddSelfSignedScreenState.MdocAuthStateOption.ECDSA) { - credential.credentialSecureArea.sign(authKey.alias, SecureArea.ALGORITHM_ES256, data, it) - } else { - credential.credentialSecureArea.keyAgreement(authKey.alias, authKey.attestation.first().publicKey, it) - } - } - deviceResponseGenerator.addDocument(data) - authKey.increaseUsageCount() - ProvisioningUtil.getInstance(context).trackUsageTimestamp(credential) - } catch (lockedException: SecureArea.KeyLockedException) { - return if (credential.credentialSecureArea is AndroidKeystoreSecureArea) { - val keyInfo = credential.credentialSecureArea.getKeyInfo(authKey.alias) as AndroidKeystoreSecureArea.KeyInfo - val allowLskf = keyInfo.userAuthenticationType == USER_AUTHENTICATION_TYPE_LSKF - val allowBiometric = keyInfo.userAuthenticationType == USER_AUTHENTICATION_TYPE_BIOMETRIC - val allowBoth = keyInfo.userAuthenticationType == USER_AUTHENTICATION_TYPE_LSKF or USER_AUTHENTICATION_TYPE_BIOMETRIC - AddDocumentToResponseResult.UserAuthRequired( - keyAlias = authKey.alias, - allowLSKFUnlocking = allowLskf || allowBoth, - allowBiometricUnlocking = allowBiometric || allowBoth - ) - } else { - AddDocumentToResponseResult.PassphraseRequired - } + val transcript = communication.getSessionTranscript() ?: byteArrayOf() + val authOption = + AddSelfSignedScreenState.MdocAuthStateOption.valueOf(documentInformation.mDocAuthOption) + try { + val generator = DocumentGenerator(docType, staticAuthData.issuerAuth, transcript) + .setIssuerNamespaces(mergedIssuerNamespaces) + if (authOption == AddSelfSignedScreenState.MdocAuthStateOption.ECDSA) { + generator.setDeviceNamespacesSignature( + NameSpacedData.Builder().build(), + authKey.secureArea, + authKey.alias, + keyUnlockData, + SecureArea.ALGORITHM_ES256 + ) + } else { + generator.setDeviceNamespacesMac( + NameSpacedData.Builder().build(), + authKey.secureArea, + authKey.alias, + keyUnlockData, + authKey.attestation.first().publicKey + ) + } + val data = generator.generate() + deviceResponseGenerator.addDocument(data) + authKey.increaseUsageCount() + ProvisioningUtil.getInstance(context).trackUsageTimestamp(credential) + } catch (lockedException: SecureArea.KeyLockedException) { + return if (credential.credentialSecureArea is AndroidKeystoreSecureArea) { + val keyInfo = + credential.credentialSecureArea.getKeyInfo(authKey.alias) as AndroidKeystoreSecureArea.KeyInfo + val allowLskf = keyInfo.userAuthenticationType == USER_AUTHENTICATION_TYPE_LSKF + val allowBiometric = + keyInfo.userAuthenticationType == USER_AUTHENTICATION_TYPE_BIOMETRIC + val allowBoth = + keyInfo.userAuthenticationType == USER_AUTHENTICATION_TYPE_LSKF or USER_AUTHENTICATION_TYPE_BIOMETRIC + AddDocumentToResponseResult.UserAuthRequired( + keyAlias = authKey.alias, + allowLSKFUnlocking = allowLskf || allowBoth, + allowBiometricUnlocking = allowBiometric || allowBoth + ) + } else { + AddDocumentToResponseResult.PassphraseRequired( + attemptedWithIncorrectPassword = keyUnlockData != null + ) } } return AddDocumentToResponseResult.DocumentAdded(signingKeyUsageLimitPassed) @@ -274,7 +266,6 @@ class TransferManager private constructor(private val context: Context) { fun destroy() { qrCommunicationSetup = null reversedQrCommunicationSetup = null - session = null hasStarted = false } diff --git a/appholder/src/main/java/com/android/mdl/app/util/ProvisioningUtil.kt b/appholder/src/main/java/com/android/mdl/app/util/ProvisioningUtil.kt index 309f6ec4f..afa5b3c04 100644 --- a/appholder/src/main/java/com/android/mdl/app/util/ProvisioningUtil.kt +++ b/appholder/src/main/java/com/android/mdl/app/util/ProvisioningUtil.kt @@ -268,11 +268,12 @@ class ProvisioningUtil private constructor( ecCurve: Int ): BouncyCastleSecureArea.CreateKeySettings { val keyPurpose = mDocAuthOption.toKeyPurpose() - return BouncyCastleSecureArea.CreateKeySettings.Builder() + val builder = BouncyCastleSecureArea.CreateKeySettings.Builder() .setPassphraseRequired(passphrase != null, passphrase) .setKeyPurposes(keyPurpose) .setEcCurve(ecCurve) - .build() + .setKeyPurposes(SecureArea.KEY_PURPOSE_SIGN or SecureArea.KEY_PURPOSE_AGREE_KEY) + return builder.build() } @KeyPurpose diff --git a/appholder/src/main/res/navigation/navigation_graph.xml b/appholder/src/main/res/navigation/navigation_graph.xml index 3f9974246..3ff2bc163 100644 --- a/appholder/src/main/res/navigation/navigation_graph.xml +++ b/appholder/src/main/res/navigation/navigation_graph.xml @@ -144,5 +144,12 @@ + android:name="com.android.mdl.app.authconfirmation.PassphrasePrompt"> + + + + \ No newline at end of file diff --git a/appholder/src/main/res/values/strings.xml b/appholder/src/main/res/values/strings.xml index 31fa9e792..79c26490c 100644 --- a/appholder/src/main/res/values/strings.xml +++ b/appholder/src/main/res/values/strings.xml @@ -189,6 +189,8 @@ Document Deleted! Passphrase + Type Passphrase + Incorrect Password. Try again! We need your consent to show the document data mdoc ECDSA authentication mdoc MAC authentication