Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Android] NFC overlay for 7.1 #1686

Merged
merged 65 commits into from
Sep 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
ec87865
add widget
AdamVe Jun 8, 2023
3a0972e
Merge branch 'main' into adamve/nfc_activity_widget
AdamVe Sep 26, 2023
44844c7
add widgets for different nfc states
AdamVe Sep 26, 2023
39dfa17
add and use fade_in_out widget
AdamVe Sep 26, 2023
325a0be
Merge branch 'main' into adamve/nfc_activity_widget
AdamVe Sep 27, 2023
16e3d32
Merge branch 'main' into adamve/nfc_activity_widget
AdamVe Nov 15, 2023
9a7a1e7
Merge branch 'main' into adamve/nfc_activity_widget
AdamVe Jul 31, 2024
4ec2698
fix formatting
AdamVe Jul 31, 2024
69ff029
revert naming
AdamVe Jul 31, 2024
a82bcce
Merge branch main into adamve/nfc_activity_widget
AdamVe Aug 25, 2024
b0599ec
Merge branch main into adamve/nfc_activity_widget
AdamVe Aug 26, 2024
32d9cb1
Merge branch main into adamve/nfc_activity_widget
AdamVe Aug 28, 2024
d8a55a0
first version of the feature, wip still
AdamVe Aug 28, 2024
0cf46fd
shorten localization key names
AdamVe Aug 28, 2024
3ef1276
fix strings errors
AdamVe Aug 29, 2024
8b2126d
unfocus correctly to hide sw keyboard
AdamVe Aug 29, 2024
7924a3c
review and improve unfocus changes
AdamVe Aug 29, 2024
7c9beca
Merge 'main' into adamve/nfc_activity_widget
AdamVe Aug 30, 2024
a21691c
update class names, cleanup
AdamVe Aug 30, 2024
075ce16
replace pulsing widget with nfc progressbar
AdamVe Aug 30, 2024
4534120
remove unused widgets
AdamVe Aug 30, 2024
cc761c3
update naming style
AdamVe Aug 30, 2024
44e3bc7
refactor
AdamVe Aug 30, 2024
dd1c52f
add helper methods for commands
AdamVe Aug 30, 2024
f99b0b1
update dialog titles
AdamVe Aug 30, 2024
f500932
provide icon widget to nfc content vidget
AdamVe Aug 30, 2024
c3b0c79
unify dialog title, add success icon
AdamVe Aug 30, 2024
afaab49
add close button to the bottom sheet
AdamVe Aug 30, 2024
34f78d2
support for FIDO, conditional messages
AdamVe Aug 31, 2024
9cbe8c0
Merge branch main into adamve/nfc_activity_widget
AdamVe Sep 3, 2024
f98e34b
support retries on NFC failure
AdamVe Sep 4, 2024
3873ec4
Merge branch main into adamve/nfc_activity_widget
AdamVe Sep 4, 2024
fb2bec0
schedule device info update in fido and oath
AdamVe Sep 5, 2024
516d776
fix pop stack on device change, support nfc retry
AdamVe Sep 5, 2024
4e1abf2
handle app context changes
AdamVe Sep 5, 2024
d677dfe
improve exception handling
AdamVe Sep 5, 2024
2de217d
refactor autoclose views
AdamVe Sep 5, 2024
9f183bc
minor nfc views refactor
AdamVe Sep 6, 2024
673f5e1
remove unused c_ prefix
AdamVe Sep 6, 2024
abde264
UX updates
AdamVe Sep 6, 2024
63a1d8d
cleanup localizations
AdamVe Sep 6, 2024
b2a183e
Merge branch 'main' into adamve/nfc_activity_widget
AdamVe Sep 6, 2024
f989d38
Merge branch 'main' into adamve/nfc_activity_widget
AdamVe Sep 6, 2024
454d364
Use correct l10n key
AdamVe Sep 6, 2024
75073c1
refactor
AdamVe Sep 6, 2024
f14f16e
refactor
AdamVe Sep 6, 2024
ec42889
remove explicitAction variable
AdamVe Sep 9, 2024
937893a
update flutter file structure
AdamVe Sep 9, 2024
fea3d4b
cleanup
AdamVe Sep 9, 2024
58552c0
simplify method channel calls
AdamVe Sep 9, 2024
17e3837
shorten names, refactor
AdamVe Sep 9, 2024
dc8822d
update class name
AdamVe Sep 9, 2024
e5e6164
update names for overlay in native code
AdamVe Sep 9, 2024
7b971f1
remove nfc retries
AdamVe Sep 10, 2024
5441c95
Merge branch 'main' into adamve/nfc_activity_widget
AdamVe Sep 10, 2024
58167e6
minor fixes
AdamVe Sep 10, 2024
a6038ca
emit nfc overlay messages after FIDO reset
AdamVe Sep 10, 2024
827c95f
handle IO exceptions correctly
AdamVe Sep 10, 2024
90cd67a
preserve connections in addToAny methods
AdamVe Sep 10, 2024
3432835
use correct context/section for AddToAny
AdamVe Sep 10, 2024
769aeda
improve comparison to handle exception states
AdamVe Sep 11, 2024
a79e2da
don't swallow context disposed exceptions
AdamVe Sep 11, 2024
96d366e
Merge branch 'main' into adamve/nfc_activity_widget
AdamVe Sep 11, 2024
835749d
Merge branch 'adamve/nfc_activity_widget'
AdamVe Sep 11, 2024
2d394d8
fix copyright year in changed files
AdamVe Sep 11, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,13 @@ import com.yubico.yubikit.core.YubiKeyDevice
* Provides behavior to run when a YubiKey is inserted/tapped for a specific view of the app.
*/
abstract class AppContextManager {
abstract suspend fun processYubiKey(device: YubiKeyDevice)
abstract suspend fun processYubiKey(device: YubiKeyDevice): Boolean

open fun dispose() {}

open fun onPause() {}
}

open fun onError() {}
}

class ContextDisposedException : Exception()
105 changes: 0 additions & 105 deletions android/app/src/main/kotlin/com/yubico/authenticator/DialogManager.kt

This file was deleted.

103 changes: 85 additions & 18 deletions android/app/src/main/kotlin/com/yubico/authenticator/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,11 @@

package com.yubico.authenticator

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.annotation.SuppressLint
import android.content.*
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
import android.content.pm.ActivityInfo
import android.content.pm.PackageManager
Expand Down Expand Up @@ -48,13 +51,18 @@ import com.yubico.authenticator.management.ManagementHandler
import com.yubico.authenticator.oath.AppLinkMethodChannel
import com.yubico.authenticator.oath.OathManager
import com.yubico.authenticator.oath.OathViewModel
import com.yubico.authenticator.yubikit.NfcStateDispatcher
import com.yubico.authenticator.yubikit.NfcStateListener
import com.yubico.authenticator.yubikit.NfcState
import com.yubico.authenticator.yubikit.DeviceInfoHelper.Companion.getDeviceInfo
import com.yubico.authenticator.yubikit.withConnection
import com.yubico.yubikit.android.YubiKitManager
import com.yubico.yubikit.android.transport.nfc.NfcConfiguration
import com.yubico.yubikit.android.transport.nfc.NfcNotAvailable
import com.yubico.yubikit.android.transport.nfc.NfcYubiKeyDevice
import com.yubico.yubikit.android.transport.nfc.NfcYubiKeyManager
import com.yubico.yubikit.android.transport.usb.UsbConfiguration
import com.yubico.yubikit.android.transport.usb.UsbYubiKeyManager
import com.yubico.yubikit.core.Transport
import com.yubico.yubikit.core.YubiKeyDevice
import com.yubico.yubikit.core.smartcard.SmartCardConnection
Expand All @@ -66,6 +74,7 @@ import io.flutter.embedding.android.FlutterFragmentActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.MethodChannel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.json.JSONObject
import org.slf4j.LoggerFactory
Expand Down Expand Up @@ -94,6 +103,20 @@ class MainActivity : FlutterFragmentActivity() {

private val logger = LoggerFactory.getLogger(MainActivity::class.java)

private val nfcStateListener = object : NfcStateListener {

var appMethodChannel : AppMethodChannel? = null

override fun onChange(newState: NfcState) {
appMethodChannel?.let {
logger.debug("set nfc state to ${newState.name}")
it.nfcStateChanged(newState)
} ?: {
logger.warn("failed set nfc state to ${newState.name} - no method channel")
}
}
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

Expand All @@ -105,7 +128,10 @@ class MainActivity : FlutterFragmentActivity() {

allowScreenshots(false)

yubikit = YubiKitManager(this)
yubikit = YubiKitManager(
UsbYubiKeyManager(this),
NfcYubiKeyManager(this, NfcStateDispatcher(nfcStateListener))
)
}

override fun onNewIntent(intent: Intent) {
Expand Down Expand Up @@ -291,10 +317,15 @@ class MainActivity : FlutterFragmentActivity() {
return
}

if (device is NfcYubiKeyDevice) {
appMethodChannel.nfcStateChanged(NfcState.ONGOING)
}

deviceManager.scpKeyParams = null
// If NFC and FIPS check for SCP11b key
if (device.transport == Transport.NFC && deviceInfo.fipsCapable != 0) {
logger.debug("Checking for usable SCP11b key...")
deviceManager.scpKeyParams =
deviceManager.scpKeyParams = try {
device.withConnection<SmartCardConnection, ScpKeyParams?> { connection ->
val scp = SecurityDomainSession(connection)
val keyRef = scp.keyInformation.keys.firstOrNull { it.kid == ScpKid.SCP11b }
Expand All @@ -308,6 +339,14 @@ class MainActivity : FlutterFragmentActivity() {
logger.debug("Found SCP11b key: {}", keyRef)
}
}
} catch (e: Exception) {
logger.debug("Exception while getting scp keys: ", e)
contextManager?.onError()
if (device is NfcYubiKeyDevice) {
appMethodChannel.nfcStateChanged(NfcState.FAILURE)
}
null
}
}

// this YubiKey provides SCP11b key but the phone cannot perform AESCMAC
Expand All @@ -319,25 +358,36 @@ class MainActivity : FlutterFragmentActivity() {
deviceManager.setDeviceInfo(deviceInfo)
val supportedContexts = DeviceManager.getSupportedContexts(deviceInfo)
logger.debug("Connected key supports: {}", supportedContexts)
var switchedContext: Boolean = false
if (!supportedContexts.contains(viewModel.appContext.value)) {
val preferredContext = DeviceManager.getPreferredContext(supportedContexts)
logger.debug(
"Current context ({}) is not supported by the key. Using preferred context {}",
viewModel.appContext.value,
preferredContext
)
switchContext(preferredContext)
switchedContext = switchContext(preferredContext)
}

if (contextManager == null && supportedContexts.isNotEmpty()) {
switchContext(DeviceManager.getPreferredContext(supportedContexts))
switchedContext = switchContext(DeviceManager.getPreferredContext(supportedContexts))
}

contextManager?.let {
try {
it.processYubiKey(device)
} catch (e: Throwable) {
logger.error("Error processing YubiKey in AppContextManager", e)
val requestHandled = it.processYubiKey(device)
if (requestHandled) {
appMethodChannel.nfcStateChanged(NfcState.SUCCESS)
}
if (!switchedContext && device is NfcYubiKeyDevice) {

device.remove {
appMethodChannel.nfcStateChanged(NfcState.IDLE)
}
}
} catch (e: Exception) {
logger.debug("Caught Exception during YubiKey processing: ", e)
appMethodChannel.nfcStateChanged(NfcState.FAILURE)
}
}
}
Expand All @@ -351,7 +401,7 @@ class MainActivity : FlutterFragmentActivity() {
private var contextManager: AppContextManager? = null
private lateinit var deviceManager: DeviceManager
private lateinit var appContext: AppContext
private lateinit var dialogManager: DialogManager
private lateinit var nfcOverlayManager: NfcOverlayManager
private lateinit var appPreferences: AppPreferences
private lateinit var flutterLog: FlutterLog
private lateinit var flutterStreams: List<Closeable>
Expand All @@ -365,13 +415,16 @@ class MainActivity : FlutterFragmentActivity() {

messenger = flutterEngine.dartExecutor.binaryMessenger
flutterLog = FlutterLog(messenger)
deviceManager = DeviceManager(this, viewModel)
appMethodChannel = AppMethodChannel(messenger)
nfcOverlayManager = NfcOverlayManager(messenger, this.lifecycleScope)
deviceManager = DeviceManager(this, viewModel,appMethodChannel, nfcOverlayManager)
appContext = AppContext(messenger, this.lifecycleScope, viewModel)
dialogManager = DialogManager(messenger, this.lifecycleScope)

appPreferences = AppPreferences(this)
appMethodChannel = AppMethodChannel(messenger)
appLinkMethodChannel = AppLinkMethodChannel(messenger)
managementHandler = ManagementHandler(messenger, deviceManager, dialogManager)
managementHandler = ManagementHandler(messenger, deviceManager)

nfcStateListener.appMethodChannel = appMethodChannel

flutterStreams = listOf(
viewModel.deviceInfo.streamTo(this, messenger, "android.devices.deviceInfo"),
Expand All @@ -390,7 +443,8 @@ class MainActivity : FlutterFragmentActivity() {
}
}

private fun switchContext(appContext: OperationContext) {
private fun switchContext(appContext: OperationContext) : Boolean {
var switchHappened = false
// TODO: refactor this when more OperationContext are handled
// only recreate the contextManager object if it cannot be reused
if (appContext == OperationContext.Home ||
Expand All @@ -404,6 +458,7 @@ class MainActivity : FlutterFragmentActivity() {
} else {
contextManager?.dispose()
contextManager = null
switchHappened = true
}

if (contextManager == null) {
Expand All @@ -413,7 +468,7 @@ class MainActivity : FlutterFragmentActivity() {
messenger,
deviceManager,
oathViewModel,
dialogManager,
nfcOverlayManager,
appPreferences
)

Expand All @@ -422,17 +477,20 @@ class MainActivity : FlutterFragmentActivity() {
messenger,
this,
deviceManager,
appMethodChannel,
nfcOverlayManager,
fidoViewModel,
viewModel,
dialogManager
viewModel
)

else -> null
}
}
return switchHappened
}

override fun cleanUpFlutterEngine(flutterEngine: FlutterEngine) {
nfcStateListener.appMethodChannel = null
flutterStreams.forEach { it.close() }
contextManager?.dispose()
deviceManager.dispose()
Expand Down Expand Up @@ -572,9 +630,18 @@ class MainActivity : FlutterFragmentActivity() {
fun nfcAdapterStateChanged(value: Boolean) {
methodChannel.invokeMethod(
"nfcAdapterStateChanged",
JSONObject(mapOf("nfcEnabled" to value)).toString()
JSONObject(mapOf("enabled" to value)).toString()
)
}

fun nfcStateChanged(activityState: NfcState) {
lifecycleScope.launch(Dispatchers.Main) {
methodChannel.invokeMethod(
"nfcStateChanged",
JSONObject(mapOf("state" to activityState.value)).toString()
)
}
}
}

private fun allowScreenshots(value: Boolean): Boolean {
Expand Down
Loading
Loading