This commit is contained in:
Adam Velebil 2023-04-18 13:21:21 +02:00
commit 4be6ee4804
No known key found for this signature in database
GPG Key ID: C9B1E4A3CBBD2E10

View File

@ -63,6 +63,7 @@ import kotlinx.coroutines.*
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import java.net.URI import java.net.URI
import java.util.concurrent.Executors import java.util.concurrent.Executors
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.coroutines.suspendCoroutine import kotlin.coroutines.suspendCoroutine
typealias OathAction = (Result<YubiKitOathSession, Exception>) -> Unit typealias OathAction = (Result<YubiKitOathSession, Exception>) -> Unit
@ -100,6 +101,7 @@ class OathManager(
@TargetApi(Build.VERSION_CODES.M) @TargetApi(Build.VERSION_CODES.M)
private fun createKeyStoreProviderM(): KeyProvider = KeyStoreProvider() private fun createKeyStoreProviderM(): KeyProvider = KeyStoreProvider()
private val unlockOnConnect = AtomicBoolean(true)
private var pendingAction: OathAction? = null private var pendingAction: OathAction? = null
private var refreshJob: Job? = null private var refreshJob: Job? = null
private var addToAny = false private var addToAny = false
@ -241,8 +243,7 @@ class OathManager(
override suspend fun processYubiKey(device: YubiKeyDevice) { override suspend fun processYubiKey(device: YubiKeyDevice) {
try { try {
device.withConnection<SmartCardConnection, Unit> { connection -> device.withConnection<SmartCardConnection, Unit> { connection ->
val session = YubiKitOathSession(connection) val session = getOathSession(connection)
val previousId = oathViewModel.sessionState.value?.deviceId val previousId = oathViewModel.sessionState.value?.deviceId
if (session.deviceId == previousId && device is NfcYubiKeyDevice) { if (session.deviceId == previousId && device is NfcYubiKeyDevice) {
// Run any pending action // Run any pending action
@ -414,7 +415,7 @@ class OathManager(
currentPassword: String?, currentPassword: String?,
newPassword: String, newPassword: String,
): String = ): String =
useOathSession("Set password") { session -> useOathSession("Set password", unlock = false) { session ->
if (session.isAccessKeySet) { if (session.isAccessKeySet) {
if (currentPassword == null) { if (currentPassword == null) {
throw Exception("Must provide current password to be able to change it") throw Exception("Must provide current password to be able to change it")
@ -599,6 +600,29 @@ class OathManager(
return false // the unlock did not work, session is locked return false // the unlock did not work, session is locked
} }
/**
* Returns a [YubiKitOathSession] for the [connection].
* The session will be unlocked if [unlockOnConnect] is true.
*
* Generally we always want to try to unlock the session and that is why the variable
* [unlockOnConnect] is also reset to true.
*
* Currently, only setPassword and unsetPassword will not unlock the session.
*
* @param connection the device SmartCard connection
* @return a [YubiKitOathSession] which is unlocked or locked based on an internal parameter
*/
private fun getOathSession(connection: SmartCardConnection) : YubiKitOathSession {
val session = YubiKitOathSession(connection)
if (!unlockOnConnect.compareAndSet(false, true)) {
tryToUnlockOathSession(session)
}
return session
}
private fun calculateOathCodes(session: YubiKitOathSession): Map<Credential, Code?> { private fun calculateOathCodes(session: YubiKitOathSession): Map<Credential, Code?> {
val isUsbKey = appViewModel.connectedYubiKey.value != null val isUsbKey = appViewModel.connectedYubiKey.value != null
var timestamp = System.currentTimeMillis() var timestamp = System.currentTimeMillis()
@ -626,37 +650,30 @@ class OathManager(
unlock: Boolean = true, unlock: Boolean = true,
action: (YubiKitOathSession) -> T action: (YubiKitOathSession) -> T
): T { ): T {
// callers can decide whether the session should be unlocked first
unlockOnConnect.set(unlock)
return appViewModel.connectedYubiKey.value?.let { return appViewModel.connectedYubiKey.value?.let {
useOathSessionUsb(it, unlock, action) useOathSessionUsb(it, action)
} ?: useOathSessionNfc(title, unlock, action) } ?: useOathSessionNfc(title, action)
} }
private suspend fun <T> useOathSessionUsb( private suspend fun <T> useOathSessionUsb(
device: UsbYubiKeyDevice, device: UsbYubiKeyDevice,
unlock: Boolean = true,
block: (YubiKitOathSession) -> T block: (YubiKitOathSession) -> T
): T = device.withConnection<SmartCardConnection, T> { ): T = device.withConnection<SmartCardConnection, T> {
val session = YubiKitOathSession(it) block(getOathSession(it))
if (unlock) {
tryToUnlockOathSession(session)
}
block(session)
} }
private suspend fun <T> useOathSessionNfc( private suspend fun <T> useOathSessionNfc(
title: String, title: String,
unlock: Boolean = true,
block: (YubiKitOathSession) -> T block: (YubiKitOathSession) -> T
): T { ): T {
try { try {
val result = suspendCoroutine { outer -> val result = suspendCoroutine { outer ->
pendingAction = { pendingAction = {
outer.resumeWith(runCatching { outer.resumeWith(runCatching {
val session = it.value block.invoke(it.value)
if (unlock) {
tryToUnlockOathSession(session)
}
block.invoke(session)
}) })
} }
dialogManager.showDialog(Icon.NFC, "Tap your key", title) { dialogManager.showDialog(Icon.NFC, "Tap your key", title) {