Skip to content

Commit

Permalink
Extract SecureArea-specific UI into an interface
Browse files Browse the repository at this point in the history
  • Loading branch information
mitrejcevski committed Oct 24, 2023
1 parent 9b37ddc commit a20eed1
Show file tree
Hide file tree
Showing 37 changed files with 1,152 additions and 1,017 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ import androidx.lifecycle.lifecycleScope
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.identity.android.securearea.KeystoreUtil
import com.android.identity.wallet.R
import com.android.identity.wallet.authprompt.UserAuthPromptBuilder
import com.android.identity.wallet.theme.HolderAppTheme
import com.android.identity.wallet.transfer.AddDocumentToResponseResult
import com.android.identity.wallet.support.AndroidSecureAreaSupport
import com.android.identity.wallet.support.BouncyCastleSecureAreaSupport
import com.android.identity.wallet.util.DocumentData
import com.android.identity.wallet.util.log
import com.android.identity.wallet.viewmodel.TransferDocumentViewModel
Expand All @@ -38,7 +38,10 @@ class AuthConfirmationFragment : BottomSheetDialogFragment() {
private val passphraseViewModel: PassphrasePromptViewModel by activityViewModels()
private val arguments by navArgs<AuthConfirmationFragmentArgs>()
private var isSendingInProgress = mutableStateOf(false)
private var androidKeyUnlockData: AndroidKeystoreSecureArea.KeyUnlockData? = null

private var capabilities = KeystoreUtil(requireContext()).getDeviceCapabilities()
private val androidSecureAreaSupport = AndroidSecureAreaSupport(capabilities)
private val bouncyCastleSecureAreaSupport = BouncyCastleSecureAreaSupport()

override fun onCreateView(
inflater: LayoutInflater,
Expand Down Expand Up @@ -142,7 +145,7 @@ class AuthConfirmationFragment : BottomSheetDialogFragment() {
} else {
userAuthRequest.withNegativeButton("Cancel")
}
val cryptoObject = androidKeyUnlockData?.getCryptoObjectForSigning(ALGORITHM_ES256)
val cryptoObject = androidSecureAreaSupport.getCryptoObjectForSigning()
userAuthRequest.build().authenticate(cryptoObject)
}

Expand All @@ -161,14 +164,15 @@ class AuthConfirmationFragment : BottomSheetDialogFragment() {
}

private fun onPassphraseProvided(passphrase: String) {
val unlockData = BouncyCastleSecureArea.KeyUnlockData(passphrase)
val unlockData = bouncyCastleSecureAreaSupport.createKeyUnlockData(passphrase)
val result = viewModel.sendResponseForSelection(unlockData)
onSendResponseResult(result)
}

private fun authenticationSucceeded() {
try {
val result = viewModel.sendResponseForSelection(keyUnlockData = androidKeyUnlockData)
val result =
viewModel.sendResponseForSelection(keyUnlockData = androidSecureAreaSupport.lastKeyUnlockData())
onSendResponseResult(result)
} catch (e: Exception) {
val message = "Send response error: ${e.message}"
Expand All @@ -180,7 +184,7 @@ class AuthConfirmationFragment : BottomSheetDialogFragment() {
private fun onSendResponseResult(result: AddDocumentToResponseResult) {
when (result) {
is AddDocumentToResponseResult.UserAuthRequired -> {
androidKeyUnlockData = AndroidKeystoreSecureArea.KeyUnlockData(result.keyAlias)
androidSecureAreaSupport.createKeyUnlockData(result.keyAlias)
requestUserAuth(
result.allowLSKFUnlocking,
result.allowBiometricUnlocking
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package com.android.identity.wallet.composables

import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Checkbox
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Switch
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.android.identity.wallet.R
import com.android.identity.wallet.composables.state.AuthTypeState
import com.android.identity.wallet.selfsigned.OutlinedContainerVertical

@Composable
fun AndroidSetupContainer(
modifier: Modifier = Modifier,
isOn: Boolean,
timeoutSeconds: Int,
lskfAuthTypeState: AuthTypeState,
biometricAuthTypeState: AuthTypeState,
useStrongBox: AuthTypeState,
onUserAuthenticationChanged: (isOn: Boolean) -> Unit,
onAuthTimeoutChanged: (authTimeout: Int) -> Unit,
onLskfAuthChanged: (isOn: Boolean) -> Unit,
onBiometricAuthChanged: (isOn: Boolean) -> Unit,
onStrongBoxChanged: (isOn: Boolean) -> Unit
) {
Column(modifier = modifier) {
OutlinedContainerVertical(modifier = Modifier.fillMaxWidth()) {
val labelOn = stringResource(id = R.string.user_authentication_on)
val labelOff = stringResource(id = R.string.user_authentication_off)
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
ValueLabel(
modifier = Modifier.weight(1f),
label = if (isOn) labelOn else labelOff,
)
Switch(
modifier = Modifier.padding(start = 8.dp),
checked = isOn,
onCheckedChange = onUserAuthenticationChanged
)
}
AnimatedVisibility(
modifier = Modifier.fillMaxWidth(),
visible = isOn
) {
Column(modifier = Modifier.fillMaxWidth()) {
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
ValueLabel(
modifier = Modifier.weight(1f),
label = stringResource(id = R.string.keystore_android_user_auth_timeout)
)
NumberChanger(
number = timeoutSeconds,
onNumberChanged = onAuthTimeoutChanged,
counterTextStyle = MaterialTheme.typography.titleLarge
)
}
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
val alpha = if (lskfAuthTypeState.canBeModified) 1f else .5f
ValueLabel(
modifier = Modifier
.weight(1f)
.alpha(alpha),
label = stringResource(id = R.string.user_auth_type_allow_lskf)
)
Checkbox(
checked = lskfAuthTypeState.isEnabled,
onCheckedChange = onLskfAuthChanged,
enabled = lskfAuthTypeState.canBeModified
)
}
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
val alpha = if (biometricAuthTypeState.canBeModified) 1f else .5f
ValueLabel(
modifier = Modifier
.weight(1f)
.alpha(alpha),
label = stringResource(id = R.string.user_auth_type_allow_biometric)
)
Checkbox(
checked = biometricAuthTypeState.isEnabled,
onCheckedChange = onBiometricAuthChanged,
enabled = biometricAuthTypeState.canBeModified
)
}
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
val alpha = if (useStrongBox.canBeModified) 1f else .5f
ValueLabel(
modifier = Modifier
.weight(1f)
.alpha(alpha),
label = stringResource(id = R.string.user_auth_use_strong_box)
)
Checkbox(
checked = useStrongBox.isEnabled,
onCheckedChange = onStrongBoxChanged,
enabled = useStrongBox.canBeModified
)
}
}
}
}
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package com.android.identity.wallet.composables

import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material3.DropdownMenu
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.res.stringResource
import com.android.identity.wallet.R
import com.android.identity.wallet.composables.state.AndroidAuthKeyCurveOption
import com.android.identity.wallet.composables.state.MdocAuthOption
import com.android.identity.wallet.composables.state.MdocAuthStateOption
import com.android.identity.wallet.composables.state.AndroidAuthKeyCurveState

@Composable
fun AuthenticationKeyCurveAndroid(
modifier: Modifier = Modifier,
state: AndroidAuthKeyCurveState,
mDocAuthState: MdocAuthOption,
onAndroidAuthKeyCurveChanged: (newValue: AndroidAuthKeyCurveOption) -> 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()
}
DropdownMenu(
expanded = keyCurveDropDownExpanded,
onDismissRequest = { keyCurveDropDownExpanded = false }
) {
val ecCurveOption =
if (mDocAuthState.mDocAuthentication == MdocAuthStateOption.ECDSA) {
AndroidAuthKeyCurveOption.Ed25519
} else {
AndroidAuthKeyCurveOption.X25519
}
TextDropDownRow(
label = curveLabelFor(curveOption = AndroidAuthKeyCurveOption.P_256.toEcCurve()),
onSelected = {
onAndroidAuthKeyCurveChanged(AndroidAuthKeyCurveOption.P_256)
keyCurveDropDownExpanded = false
}
)
TextDropDownRow(
label = curveLabelFor(curveOption = ecCurveOption.toEcCurve()),
onSelected = {
onAndroidAuthKeyCurveChanged(ecCurveOption)
keyCurveDropDownExpanded = false
}
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package com.android.identity.wallet.composables

import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material3.DropdownMenu
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.res.stringResource
import com.android.identity.wallet.R
import com.android.identity.wallet.composables.state.BouncyCastleAuthKeyCurveOption
import com.android.identity.wallet.composables.state.BouncyCastleAuthKeyCurveState
import com.android.identity.wallet.composables.state.MdocAuthOption
import com.android.identity.wallet.composables.state.MdocAuthStateOption

@Composable
fun AuthenticationKeyCurveBouncyCastle(
modifier: Modifier = Modifier,
state: BouncyCastleAuthKeyCurveState,
mDocAuthState: MdocAuthOption,
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
}
)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.android.identity.wallet.composables

import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.android.identity.wallet.R

@Composable
fun BouncyCastlePassphraseInput(
modifier: Modifier = Modifier,
value: String,
onValueChanged: (newValue: String) -> Unit
) {
OutlinedContainerHorizontal(modifier = modifier) {
Box(contentAlignment = Alignment.CenterStart) {
BasicTextField(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 10.dp),
textStyle = MaterialTheme.typography.labelMedium.copy(
color = MaterialTheme.colorScheme.onSurface,
),
value = value,
onValueChange = onValueChanged,
cursorBrush = SolidColor(MaterialTheme.colorScheme.onSurface)
)
if (value.isEmpty()) {
Text(
text = stringResource(id = R.string.keystore_bouncy_castle_passphrase_hint),
style = MaterialTheme.typography.labelMedium.copy(
color = MaterialTheme.colorScheme.onSurface.copy(alpha = .5f)
),
)
}
}
}
}
Loading

0 comments on commit a20eed1

Please sign in to comment.