Skip to content

Commit

Permalink
Merge branch 'main' into jokkon/tests-7.1
Browse files Browse the repository at this point in the history
  • Loading branch information
dainnilsson committed Sep 10, 2024
2 parents 630d32c + 3dee5fd commit 43ea6d3
Show file tree
Hide file tree
Showing 38 changed files with 1,521 additions and 301 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/env
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
FLUTTER=3.24.1
PYVER=3.12.5
FLUTTER=3.24.2
PYVER=3.12.6
7 changes: 0 additions & 7 deletions .github/workflows/macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,6 @@ jobs:
- run: flutter config --enable-macos-desktop
- run: flutter --version

- name: Apply Flutter Patch
run: |
cd $FLUTTER_ROOT
git apply $GITHUB_WORKSPACE/macos_assemble.patch
env:
GITHUB_WORKSPACE: ${{ github.workspace }}

- name: Run lints/tests
env:
SKIP: ${{ steps.cache-helper.outputs.cache-hit == 'true' && 'mypy,flake8,black,bandit' || ''}}
Expand Down
6 changes: 3 additions & 3 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -91,13 +91,13 @@ dependencies {
api "com.yubico.yubikit:fido:$project.yubiKitVersion"
api "com.yubico.yubikit:support:$project.yubiKitVersion"

implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3'
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.2'

// Lifecycle
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.3'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.5'

implementation "androidx.core:core-ktx:1.13.1"
implementation 'androidx.fragment:fragment-ktx:1.8.1'
implementation 'androidx.fragment:fragment-ktx:1.8.3'
implementation 'androidx.preference:preference-ktx:1.2.1'

implementation 'com.google.android.material:material:1.12.0'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import androidx.core.view.WindowCompat
import androidx.lifecycle.lifecycleScope
import com.google.android.material.color.DynamicColors
import com.yubico.authenticator.device.DeviceManager
import com.yubico.authenticator.device.noScp11bNfcSupport
import com.yubico.authenticator.fido.FidoManager
import com.yubico.authenticator.fido.FidoViewModel
import com.yubico.authenticator.logging.FlutterLog
Expand Down Expand Up @@ -69,7 +70,9 @@ import kotlinx.coroutines.launch
import org.json.JSONObject
import org.slf4j.LoggerFactory
import java.io.Closeable
import java.security.NoSuchAlgorithmException
import java.util.concurrent.Executors
import javax.crypto.Mac

class MainActivity : FlutterFragmentActivity() {
private val viewModel: MainViewModel by viewModels()
Expand Down Expand Up @@ -282,9 +285,9 @@ class MainActivity : FlutterFragmentActivity() {

private suspend fun processYubiKey(device: YubiKeyDevice) {
val deviceInfo = getDeviceInfo(device)
deviceManager.setDeviceInfo(deviceInfo)

if (deviceInfo == null) {
deviceManager.setDeviceInfo(null)
return
}

Expand All @@ -307,6 +310,13 @@ class MainActivity : FlutterFragmentActivity() {
}
}

// this YubiKey provides SCP11b key but the phone cannot perform AESCMAC
if (deviceManager.scpKeyParams != null && !supportsScp11b) {
deviceManager.setDeviceInfo(noScp11bNfcSupport)
return
}

deviceManager.setDeviceInfo(deviceInfo)
val supportedContexts = DeviceManager.getSupportedContexts(deviceInfo)
logger.debug("Connected key supports: {}", supportedContexts)
if (!supportedContexts.contains(viewModel.appContext.value)) {
Expand Down Expand Up @@ -432,6 +442,12 @@ class MainActivity : FlutterFragmentActivity() {
companion object {
const val YUBICO_VENDOR_ID = 4176
const val FLAG_SECURE = WindowManager.LayoutParams.FLAG_SECURE
val supportsScp11b = try {
Mac.getInstance("AESCMAC");
true
} catch (_: NoSuchAlgorithmException) {
false
}
}

/** We observed that some devices (Pixel 2, OnePlus 6) automatically end NFC discovery
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,10 @@ fun restrictedNfcDeviceInfo(transport: Transport) : Info {
isNfc = true,
name = "restricted-nfc"
)
}
}

// the YubiKey requires SCP11b communication but the phone cannot handle it
val noScp11bNfcSupport = UnknownDevice.copy(
isNfc = true,
name = "no-scp11b-nfc-support"
)
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import com.yubico.authenticator.DialogManager
import com.yubico.authenticator.DialogTitle
import com.yubico.authenticator.device.DeviceManager
import com.yubico.authenticator.fido.data.YubiKitFidoSession
import com.yubico.authenticator.yubikit.DeviceInfoHelper.Companion.getDeviceInfo
import com.yubico.authenticator.yubikit.withConnection
import com.yubico.yubikit.android.transport.usb.UsbYubiKeyDevice
import com.yubico.yubikit.core.fido.FidoConnection
Expand Down Expand Up @@ -51,18 +52,25 @@ class FidoConnectionHelper(

suspend fun <T> useSession(
actionDescription: FidoActionDescription,
updateDeviceInfo: Boolean = false,
action: (YubiKitFidoSession) -> T
): T {
FidoManager.updateDeviceInfo.set(updateDeviceInfo)
return deviceManager.withKey(
onNfc = { useSessionNfc(actionDescription,action) },
onUsb = { useSessionUsb(it, action) })
onUsb = { useSessionUsb(it, updateDeviceInfo, action) })
}

suspend fun <T> useSessionUsb(
device: UsbYubiKeyDevice,
updateDeviceInfo: Boolean = false,
block: (YubiKitFidoSession) -> T
): T = device.withConnection<FidoConnection, T> {
block(YubiKitFidoSession(it))
}.also {
if (updateDeviceInfo) {
deviceManager.setDeviceInfo(getDeviceInfo(device))
}
}

suspend fun <T> useSessionNfc(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import com.yubico.authenticator.fido.data.Session
import com.yubico.authenticator.fido.data.SessionInfo
import com.yubico.authenticator.fido.data.YubiKitFidoSession
import com.yubico.authenticator.setHandler
import com.yubico.authenticator.yubikit.DeviceInfoHelper.Companion.getDeviceInfo
import com.yubico.authenticator.yubikit.withConnection
import com.yubico.yubikit.android.transport.nfc.NfcYubiKeyDevice
import com.yubico.yubikit.core.YubiKeyConnection
Expand Down Expand Up @@ -61,6 +62,7 @@ import org.slf4j.LoggerFactory
import java.io.IOException
import java.util.Arrays
import java.util.concurrent.Executors
import java.util.concurrent.atomic.AtomicBoolean

typealias FidoAction = (Result<YubiKitFidoSession, Exception>) -> Unit

Expand All @@ -80,6 +82,7 @@ class FidoManager(
}

companion object {
val updateDeviceInfo = AtomicBoolean(false)
fun getPreferredPinUvAuthProtocol(infoData: InfoData): PinUvAuthProtocol {
val pinUvAuthProtocols = infoData.pinUvAuthProtocols
val pinSupported = infoData.options["clientPin"] != null
Expand Down Expand Up @@ -120,6 +123,8 @@ class FidoManager(
pinStore
)



init {
pinRetries = null

Expand Down Expand Up @@ -173,6 +178,7 @@ class FidoManager(
fidoChannel.setMethodCallHandler(null)
fidoViewModel.clearSessionState()
fidoViewModel.updateCredentials(null)
connectionHelper.cancelPending()
coroutineScope.cancel()
}

Expand All @@ -187,6 +193,10 @@ class FidoManager(
processYubiKey(connection, device)
}
}

if (updateDeviceInfo.getAndSet(false)) {
deviceManager.setDeviceInfo(getDeviceInfo(device))
}
} catch (e: Exception) {
// something went wrong, try to get DeviceInfo from any available connection type
logger.error("Failure when processing YubiKey: ", e)
Expand Down Expand Up @@ -380,7 +390,7 @@ class FidoManager(
}

private suspend fun setPin(pin: CharArray?, newPin: CharArray): String =
connectionHelper.useSession(FidoActionDescription.SetPin) { fidoSession ->
connectionHelper.useSession(FidoActionDescription.SetPin, updateDeviceInfo = true) { fidoSession ->
try {
val clientPin =
ClientPin(fidoSession, getPreferredPinUvAuthProtocol(fidoSession.cachedInfo))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,8 +162,9 @@ class FidoResetHelper(
coroutineScope.launch(Dispatchers.Main) {
fidoViewModel.updateResetState(FidoResetState.Touch)
logger.debug("Waiting for touch")
deviceManager.withKey { usbYubiKeyDevice ->
connectionHelper.useSessionUsb(usbYubiKeyDevice) { fidoSession ->
deviceManager.withKey {
usbYubiKeyDevice ->
connectionHelper.useSessionUsb(usbYubiKeyDevice, updateDeviceInfo = true) { fidoSession ->
resetCommandState = CommandState()
try {
if (cancelReset) {
Expand Down Expand Up @@ -211,6 +212,7 @@ class FidoResetHelper(
coroutineScope.launch {
fidoViewModel.updateResetState(FidoResetState.Touch)
try {
FidoManager.updateDeviceInfo.set(true)
connectionHelper.useSessionNfc(FidoActionDescription.Reset) { fidoSession ->
doReset(fidoSession)
continuation.resume(Unit)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ class OathManager(
oathChannel.setMethodCallHandler(null)
oathViewModel.clearSession()
oathViewModel.updateCredentials(mapOf())
pendingAction?.invoke(Result.failure(Exception()))
coroutineScope.cancel()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@ import com.yubico.authenticator.device.unknownFido2DeviceInfo
import com.yubico.authenticator.device.unknownOathDeviceInfo
import com.yubico.yubikit.android.transport.nfc.NfcYubiKeyDevice
import com.yubico.yubikit.android.transport.usb.UsbYubiKeyDevice
import com.yubico.yubikit.core.Version
import com.yubico.yubikit.core.YubiKeyDevice
import com.yubico.yubikit.core.application.ApplicationNotAvailableException
import com.yubico.yubikit.core.application.SessionVersionOverride
import com.yubico.yubikit.core.fido.FidoConnection
import com.yubico.yubikit.core.otp.OtpConnection
import com.yubico.yubikit.core.smartcard.Apdu
Expand All @@ -46,8 +48,17 @@ class DeviceInfoHelper {
byteArrayOf(0x00, 0x1F, 0xD1.toByte(), 0x01, 0x1b, 0x55, 0x04) + uri

suspend fun getDeviceInfo(device: YubiKeyDevice): Info? {
val pid = (device as? UsbYubiKeyDevice)?.pid
SessionVersionOverride.set(null)
var deviceInfo = readDeviceInfo(device)
if (deviceInfo?.version?.major == 0.toByte()) {
SessionVersionOverride.set(Version(5, 7, 2))
deviceInfo = readDeviceInfo(device)
}
return deviceInfo
}

private suspend fun readDeviceInfo(device: YubiKeyDevice): Info? {
val pid = (device as? UsbYubiKeyDevice)?.pid

val deviceInfo = runCatching {
device.withConnection<SmartCardConnection, DeviceInfo> {
Expand Down
7 changes: 7 additions & 0 deletions android/app/src/main/res/values-vi/strings.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version='1.0' encoding='utf-8'?>
<resources>
<string name="p_ndef_set_otp">Đã sao chép mã OTP từ YubiKey vào clipboard.</string>
<string name="p_ndef_set_password">Đã sao chép mật khẩu từ YubiKey vào clipboard.</string>
<string name="p_ndef_parse_failure">Không thể phân tích mã OTP từ YubiKey.</string>
<string name="p_ndef_set_clip_failure">Không thể truy cập clipboard khi cố gắng sao chép mã OTP từ YubiKey.</string>
</resources>
4 changes: 2 additions & 2 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ allprojects {
targetSdkVersion = 34
compileSdkVersion = 34

yubiKitVersion = "2.7.0-alpha01"
yubiKitVersion = "2.7.0"
junitVersion = "4.13.2"
mockitoVersion = "5.12.0"
mockitoVersion = "5.13.0"
}
}

Expand Down
4 changes: 2 additions & 2 deletions android/flutter_plugins/qrscanner_zxing/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@ group 'com.yubico.authenticator.flutter_plugins.qrscanner_zxing'
version '1.0'

buildscript {
ext.kotlin_version = '2.0.0'
ext.kotlin_version = '2.0.20'
repositories {
google()
mavenCentral()
}

dependencies {
classpath 'com.android.tools.build:gradle:8.5.0'
classpath 'com.android.tools.build:gradle:8.6.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
Expand Down
6 changes: 3 additions & 3 deletions android/settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ pluginManagement {

plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version "8.5.0" apply false
id "org.jetbrains.kotlin.android" version "2.0.0" apply false
id "org.jetbrains.kotlin.plugin.serialization" version "2.0.0" apply false
id "com.android.application" version "8.6.0" apply false
id "org.jetbrains.kotlin.android" version "2.0.20" apply false
id "org.jetbrains.kotlin.plugin.serialization" version "2.0.20" apply false
id "com.google.android.gms.oss-licenses-plugin" version "0.10.6" apply false
}

Expand Down
6 changes: 2 additions & 4 deletions build-helper.sh
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,8 @@ if [ "$OS" = "macos" ]; then
poetry run pip download -r $HELPER/pillow.txt --platform macosx_10_10_x86_64 --only-binary :all: --no-deps --dest $HELPER
poetry run pip download -r $HELPER/pillow.txt --platform macosx_11_0_arm64 --only-binary :all: --no-deps --dest $HELPER
poetry run pip install delocate
poetry run delocate-fuse $HELPER/pillow*.whl
WHL=$(ls $HELPER/pillow*x86_64.whl)
UNIVERSAL_WHL=${WHL//x86_64/universal2}
mv $WHL $UNIVERSAL_WHL
poetry run delocate-merge $HELPER/pillow*.whl
UNIVERSAL_WHL=$(ls $HELPER/pillow*universal2.whl)
poetry run pip install --upgrade $UNIVERSAL_WHL
fi
fi
Expand Down
18 changes: 14 additions & 4 deletions helper/helper/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -274,9 +274,6 @@ def __call__(self, *args, **kwargs):
# Clear DeviceInfo cache
self._info = None
self._data = None
# Make sure any child node is re-opened after this,
# as enabled applications may have changed
super().close()

return response

Expand Down Expand Up @@ -465,7 +462,20 @@ def __init__(self, device, connection, info):

def __call__(self, *args, **kwargs):
try:
return super().__call__(*args, **kwargs)
response = super().__call__(*args, **kwargs)
if "device_info" in response.flags:
# Refresh DeviceInfo
info = read_info(self._connection, self._device.pid)
if self._info != info:
self._info = info
# Make sure any child node is re-opened after this,
# as enabled applications may have changed
self.close()
else:
# No change to DeviceInfo, further propagation not needed.
response.flags.remove("device_info")

return response
except (SmartcardException, OSError) as e:
logger.exception("Connection error")
raise ChildResetException(f"{e}")
Expand Down
8 changes: 7 additions & 1 deletion helper/helper/fido.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,8 +186,13 @@ def reset(self, params, event, signal):
try:
self.ctap.reset(event=event)
except CtapError as e:
if e.code == CtapError.ERR.USER_ACTION_TIMEOUT:
if e.code in (
# Different keys respond with different errors here
CtapError.ERR.USER_ACTION_TIMEOUT,
CtapError.ERR.ACTION_TIMEOUT,
):
raise InactivityException()
raise
self._info = self.ctap.get_info()
self._token = None
return RpcResponse(dict(), ["device_info"])
Expand Down Expand Up @@ -333,6 +338,7 @@ def get_data(self):
def delete(self, params, event, signal):
self.credman.delete_cred(self.data["credential_id"])
self.refresh_rps()
return dict()


class FingerprintsNode(RpcNode):
Expand Down
Loading

0 comments on commit 43ea6d3

Please sign in to comment.