Skip to content

Commit 2630665

Browse files
committed
Merge branch 'main' into jokkon/linux-arm64
2 parents 0af9df8 + 76f60a9 commit 2630665

File tree

154 files changed

+6960
-2877
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

154 files changed

+6960
-2877
lines changed

.github/workflows/android.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ jobs:
77
runs-on: ubuntu-latest
88

99
steps:
10-
- name: set up JDK 17
10+
- name: Set up JDK 17
1111
uses: actions/setup-java@v4
1212
with:
1313
distribution: 'temurin'

.github/workflows/env

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
FLUTTER=3.19.6
2-
PYVER=3.12.4
1+
FLUTTER=3.24.2
2+
PYVER=3.12.6

.github/workflows/macos.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ jobs:
5252
- uses: subosito/flutter-action@v2
5353
with:
5454
channel: 'stable'
55-
architecture: 'x64'
5655
flutter-version: ${{ env.FLUTTER }}
5756
- run: flutter config --enable-macos-desktop
5857
- run: flutter --version

android/app/build.gradle

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -91,26 +91,19 @@ dependencies {
9191
api "com.yubico.yubikit:fido:$project.yubiKitVersion"
9292
api "com.yubico.yubikit:support:$project.yubiKitVersion"
9393

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

9696
// Lifecycle
97-
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0'
97+
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.5'
9898

99-
implementation "androidx.core:core-ktx:1.13.0"
100-
implementation 'androidx.fragment:fragment-ktx:1.6.2'
99+
implementation "androidx.core:core-ktx:1.13.1"
100+
implementation 'androidx.fragment:fragment-ktx:1.8.3'
101101
implementation 'androidx.preference:preference-ktx:1.2.1'
102102

103-
implementation 'com.google.android.material:material:1.11.0'
103+
implementation 'com.google.android.material:material:1.12.0'
104104

105105
implementation 'com.github.tony19:logback-android:3.0.0'
106106

107-
implementation('commons-codec:commons-codec') {
108-
version {
109-
// use version 1.15 for compatibility reasons
110-
strictly '1.15'
111-
}
112-
}
113-
114107
// testing dependencies
115108
testImplementation "junit:junit:$project.junitVersion"
116109
testImplementation "org.mockito:mockito-core:$project.mockitoVersion"

android/app/src/main/kotlin/com/yubico/authenticator/CompatUtil.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ import android.os.Build
3737
* @param sdkVersion the version this instance uses for compatibility checking. The release app
3838
* uses `Build.VERSION.SDK_INT`, tests use appropriate other values.
3939
*/
40-
@Suppress("MemberVisibilityCanBePrivate", "unused")
40+
@Suppress("MemberVisibilityCanBePrivate")
4141
class CompatUtil(private val sdkVersion: Int) {
4242
/**
4343
* Wrapper class holding values computed by [CompatUtil]

android/app/src/main/kotlin/com/yubico/authenticator/MainActivity.kt

Lines changed: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,20 +40,28 @@ import androidx.core.view.WindowCompat
4040
import androidx.lifecycle.lifecycleScope
4141
import com.google.android.material.color.DynamicColors
4242
import com.yubico.authenticator.device.DeviceManager
43+
import com.yubico.authenticator.device.noScp11bNfcSupport
4344
import com.yubico.authenticator.fido.FidoManager
4445
import com.yubico.authenticator.fido.FidoViewModel
4546
import com.yubico.authenticator.logging.FlutterLog
4647
import com.yubico.authenticator.management.ManagementHandler
4748
import com.yubico.authenticator.oath.AppLinkMethodChannel
4849
import com.yubico.authenticator.oath.OathManager
4950
import com.yubico.authenticator.oath.OathViewModel
50-
import com.yubico.authenticator.yubikit.getDeviceInfo
51+
import com.yubico.authenticator.yubikit.DeviceInfoHelper.Companion.getDeviceInfo
52+
import com.yubico.authenticator.yubikit.withConnection
5153
import com.yubico.yubikit.android.YubiKitManager
5254
import com.yubico.yubikit.android.transport.nfc.NfcConfiguration
5355
import com.yubico.yubikit.android.transport.nfc.NfcNotAvailable
5456
import com.yubico.yubikit.android.transport.nfc.NfcYubiKeyDevice
5557
import com.yubico.yubikit.android.transport.usb.UsbConfiguration
58+
import com.yubico.yubikit.core.Transport
5659
import com.yubico.yubikit.core.YubiKeyDevice
60+
import com.yubico.yubikit.core.smartcard.SmartCardConnection
61+
import com.yubico.yubikit.core.smartcard.scp.Scp11KeyParams
62+
import com.yubico.yubikit.core.smartcard.scp.ScpKeyParams
63+
import com.yubico.yubikit.core.smartcard.scp.ScpKid
64+
import com.yubico.yubikit.core.smartcard.scp.SecurityDomainSession
5765
import io.flutter.embedding.android.FlutterFragmentActivity
5866
import io.flutter.embedding.engine.FlutterEngine
5967
import io.flutter.plugin.common.BinaryMessenger
@@ -62,7 +70,9 @@ import kotlinx.coroutines.launch
6270
import org.json.JSONObject
6371
import org.slf4j.LoggerFactory
6472
import java.io.Closeable
73+
import java.security.NoSuchAlgorithmException
6574
import java.util.concurrent.Executors
75+
import javax.crypto.Mac
6676

6777
class MainActivity : FlutterFragmentActivity() {
6878
private val viewModel: MainViewModel by viewModels()
@@ -116,7 +126,7 @@ class MainActivity : FlutterFragmentActivity() {
116126
}
117127

118128
hasNfc = true
119-
} catch (e: NfcNotAvailable) {
129+
} catch (_: NfcNotAvailable) {
120130
hasNfc = false
121131
}
122132

@@ -231,7 +241,7 @@ class MainActivity : FlutterFragmentActivity() {
231241
startNfcDiscovery()
232242
}
233243

234-
val usbManager = getSystemService(Context.USB_SERVICE) as UsbManager
244+
val usbManager = getSystemService(USB_SERVICE) as UsbManager
235245
if (UsbManager.ACTION_USB_DEVICE_ATTACHED == intent.action) {
236246
val device = intent.parcelableExtra<UsbDevice>(UsbManager.EXTRA_DEVICE)
237247
if (device != null) {
@@ -275,12 +285,38 @@ class MainActivity : FlutterFragmentActivity() {
275285

276286
private suspend fun processYubiKey(device: YubiKeyDevice) {
277287
val deviceInfo = getDeviceInfo(device)
278-
deviceManager.setDeviceInfo(deviceInfo)
279288

280289
if (deviceInfo == null) {
290+
deviceManager.setDeviceInfo(null)
291+
return
292+
}
293+
294+
// If NFC and FIPS check for SCP11b key
295+
if (device.transport == Transport.NFC && deviceInfo.fipsCapable != 0) {
296+
logger.debug("Checking for usable SCP11b key...")
297+
deviceManager.scpKeyParams =
298+
device.withConnection<SmartCardConnection, ScpKeyParams?> { connection ->
299+
val scp = SecurityDomainSession(connection)
300+
val keyRef = scp.keyInformation.keys.firstOrNull { it.kid == ScpKid.SCP11b }
301+
keyRef?.let {
302+
val certs = scp.getCertificateBundle(it)
303+
if (certs.isNotEmpty()) Scp11KeyParams(
304+
keyRef,
305+
certs[certs.size - 1].publicKey
306+
) else null
307+
}?.also {
308+
logger.debug("Found SCP11b key: {}", keyRef)
309+
}
310+
}
311+
}
312+
313+
// this YubiKey provides SCP11b key but the phone cannot perform AESCMAC
314+
if (deviceManager.scpKeyParams != null && !supportsScp11b) {
315+
deviceManager.setDeviceInfo(noScp11bNfcSupport)
281316
return
282317
}
283318

319+
deviceManager.setDeviceInfo(deviceInfo)
284320
val supportedContexts = DeviceManager.getSupportedContexts(deviceInfo)
285321
logger.debug("Connected key supports: {}", supportedContexts)
286322
if (!supportedContexts.contains(viewModel.appContext.value)) {
@@ -293,7 +329,7 @@ class MainActivity : FlutterFragmentActivity() {
293329
switchContext(preferredContext)
294330
}
295331

296-
if (contextManager == null) {
332+
if (contextManager == null && supportedContexts.isNotEmpty()) {
297333
switchContext(DeviceManager.getPreferredContext(supportedContexts))
298334
}
299335

@@ -406,6 +442,12 @@ class MainActivity : FlutterFragmentActivity() {
406442
companion object {
407443
const val YUBICO_VENDOR_ID = 4176
408444
const val FLAG_SECURE = WindowManager.LayoutParams.FLAG_SECURE
445+
val supportsScp11b = try {
446+
Mac.getInstance("AESCMAC");
447+
true
448+
} catch (_: NoSuchAlgorithmException) {
449+
false
450+
}
409451
}
410452

411453
/** We observed that some devices (Pixel 2, OnePlus 6) automatically end NFC discovery
@@ -427,7 +469,7 @@ class MainActivity : FlutterFragmentActivity() {
427469
}
428470

429471
private val sharedPreferencesListener = OnSharedPreferenceChangeListener { _, key ->
430-
if ( AppPreferences.PREF_NFC_SILENCE_SOUNDS == key) {
472+
if (AppPreferences.PREF_NFC_SILENCE_SOUNDS == key) {
431473
stopNfcDiscovery()
432474
startNfcDiscovery()
433475
}
@@ -493,26 +535,30 @@ class MainActivity : FlutterFragmentActivity() {
493535
}
494536
result.success(true)
495537
}
538+
496539
"hasCamera" -> {
497540
val cameraService =
498-
getSystemService(Context.CAMERA_SERVICE) as CameraManager
541+
getSystemService(CAMERA_SERVICE) as CameraManager
499542
result.success(
500543
cameraService.cameraIdList.any {
501544
cameraService.getCameraCharacteristics(it)
502545
.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_BACK
503546
}
504547
)
505548
}
549+
506550
"hasNfc" -> result.success(
507551
packageManager.hasSystemFeature(PackageManager.FEATURE_NFC)
508552
)
553+
509554
"isNfcEnabled" -> {
510555
val nfcAdapter = NfcAdapter.getDefaultAdapter(this@MainActivity)
511556

512557
result.success(
513558
nfcAdapter != null && nfcAdapter.isEnabled
514559
)
515560
}
561+
516562
"openNfcSettings" -> {
517563
startActivity(Intent(ACTION_NFC_SETTINGS))
518564
result.success(true)

android/app/src/main/kotlin/com/yubico/authenticator/device/DeviceManager.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import com.yubico.authenticator.MainViewModel
2424
import com.yubico.authenticator.OperationContext
2525
import com.yubico.yubikit.android.transport.usb.UsbYubiKeyDevice
2626
import com.yubico.yubikit.core.YubiKeyDevice
27+
import com.yubico.yubikit.core.smartcard.scp.ScpKeyParams
2728
import com.yubico.yubikit.management.Capability
2829
import org.slf4j.LoggerFactory
2930

@@ -46,6 +47,15 @@ class DeviceManager(
4647

4748
private val deviceListeners = HashSet<DeviceListener>()
4849

50+
val deviceInfo: Info?
51+
get() = appViewModel.deviceInfo.value
52+
53+
var scpKeyParams: ScpKeyParams? = null
54+
set(value) {
55+
field = value
56+
logger.debug("SCP params set to {}", value)
57+
}
58+
4959
fun addDeviceListener(listener: DeviceListener) {
5060
deviceListeners.add(listener)
5161
}
@@ -157,6 +167,7 @@ class DeviceManager(
157167

158168
fun setDeviceInfo(deviceInfo: Info?) {
159169
appViewModel.setDeviceInfo(deviceInfo)
170+
scpKeyParams = null
160171
}
161172

162173
fun isUsbKeyConnected(): Boolean {

android/app/src/main/kotlin/com/yubico/authenticator/device/Info.kt

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import com.yubico.yubikit.management.DeviceInfo
2121
import kotlinx.serialization.SerialName
2222
import kotlinx.serialization.Serializable
2323

24-
private fun DeviceInfo.capabilitiesFor(transport: Transport) : Int? =
24+
private fun DeviceInfo.capabilitiesFor(transport: Transport): Int? =
2525
when {
2626
hasTransport(transport) -> getSupportedCapabilities(transport)
2727
else -> null
@@ -30,7 +30,7 @@ private fun DeviceInfo.capabilitiesFor(transport: Transport) : Int? =
3030
@Serializable
3131
data class Info(
3232
@SerialName("config")
33-
val config : Config,
33+
val config: Config,
3434
@SerialName("serial")
3535
val serialNumber: Int?,
3636
@SerialName("version")
@@ -53,11 +53,21 @@ data class Info(
5353
val pinComplexity: Boolean,
5454
@SerialName("supported_capabilities")
5555
val supportedCapabilities: Capabilities,
56+
@SerialName("fips_capable")
57+
val fipsCapable: Int,
58+
@SerialName("fips_approved")
59+
val fipsApproved: Int,
60+
@SerialName("reset_blocked")
61+
val resetBlocked: Int,
5662
) {
5763
constructor(name: String, isNfc: Boolean, usbPid: Int?, deviceInfo: DeviceInfo) : this(
5864
config = Config(deviceInfo.config),
5965
serialNumber = deviceInfo.serialNumber,
60-
version = Version(deviceInfo.version.major, deviceInfo.version.minor, deviceInfo.version.micro),
66+
version = Version(
67+
deviceInfo.version.major,
68+
deviceInfo.version.minor,
69+
deviceInfo.version.micro
70+
),
6171
formFactor = deviceInfo.formFactor.value,
6272
isLocked = deviceInfo.isLocked,
6373
isSky = deviceInfo.isSky,
@@ -69,6 +79,9 @@ data class Info(
6979
supportedCapabilities = Capabilities(
7080
nfc = deviceInfo.capabilitiesFor(Transport.NFC),
7181
usb = deviceInfo.capabilitiesFor(Transport.USB),
72-
)
82+
),
83+
fipsCapable = deviceInfo.fipsCapable,
84+
fipsApproved = deviceInfo.fipsApproved,
85+
resetBlocked = deviceInfo.resetBlocked,
7386
)
7487
}

android/app/src/main/kotlin/com/yubico/authenticator/device/UnknownDevice.kt

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,19 @@
1+
/*
2+
* Copyright (C) 2023-2024 Yubico.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
117
package com.yubico.authenticator.device
218

319
import com.yubico.yubikit.core.Transport
@@ -17,11 +33,14 @@ val UnknownDevice = Info(
1733
isLocked = false,
1834
isSky = false,
1935
isFips = false,
20-
name = "Unrecognized device",
36+
name = "unknown-device",
2137
isNfc = false,
2238
usbPid = null,
2339
pinComplexity = false,
24-
supportedCapabilities = Capabilities()
40+
supportedCapabilities = Capabilities(),
41+
fipsCapable = 0,
42+
fipsApproved = 0,
43+
resetBlocked = 0
2544
)
2645

2746
fun unknownDeviceWithCapability(transport: Transport, bit: Int = 0) : Info {
@@ -47,4 +66,21 @@ fun unknownFido2DeviceInfo(transport: Transport) : Info {
4766
return unknownDeviceWithCapability(transport, Capability.FIDO2.bit).copy(
4867
name = "FIDO2 device"
4968
)
50-
}
69+
}
70+
71+
fun restrictedNfcDeviceInfo(transport: Transport) : Info {
72+
if (transport != Transport.NFC) {
73+
return UnknownDevice
74+
}
75+
76+
return UnknownDevice.copy(
77+
isNfc = true,
78+
name = "restricted-nfc"
79+
)
80+
}
81+
82+
// the YubiKey requires SCP11b communication but the phone cannot handle it
83+
val noScp11bNfcSupport = UnknownDevice.copy(
84+
isNfc = true,
85+
name = "no-scp11b-nfc-support"
86+
)

android/app/src/main/kotlin/com/yubico/authenticator/fido/FidoActionDescription.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ enum class FidoActionDescription(private val value: Int) {
2626
DeleteFingerprint(4),
2727
RenameFingerprint(5),
2828
RegisterFingerprint(6),
29-
ActionFailure(7);
29+
EnableEnterpriseAttestation(7),
30+
ActionFailure(8);
3031

3132
val id: Int
3233
get() = value + dialogDescriptionFidoIndex

0 commit comments

Comments
 (0)