Skip to content

Commit

Permalink
Merge branch 'main' into jokkon/linux-arm64
Browse files Browse the repository at this point in the history
  • Loading branch information
jokkon committed Sep 18, 2024
2 parents 0af9df8 + 76f60a9 commit 2630665
Show file tree
Hide file tree
Showing 154 changed files with 6,960 additions and 2,877 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/android.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ jobs:
runs-on: ubuntu-latest

steps:
- name: set up JDK 17
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
distribution: 'temurin'
Expand Down
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.19.6
PYVER=3.12.4
FLUTTER=3.24.2
PYVER=3.12.6
1 change: 0 additions & 1 deletion .github/workflows/macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ jobs:
- uses: subosito/flutter-action@v2
with:
channel: 'stable'
architecture: 'x64'
flutter-version: ${{ env.FLUTTER }}
- run: flutter config --enable-macos-desktop
- run: flutter --version
Expand Down
17 changes: 5 additions & 12 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -91,26 +91,19 @@ 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.7.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.5'

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

implementation 'com.google.android.material:material:1.11.0'
implementation 'com.google.android.material:material:1.12.0'

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

implementation('commons-codec:commons-codec') {
version {
// use version 1.15 for compatibility reasons
strictly '1.15'
}
}

// testing dependencies
testImplementation "junit:junit:$project.junitVersion"
testImplementation "org.mockito:mockito-core:$project.mockitoVersion"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import android.os.Build
* @param sdkVersion the version this instance uses for compatibility checking. The release app
* uses `Build.VERSION.SDK_INT`, tests use appropriate other values.
*/
@Suppress("MemberVisibilityCanBePrivate", "unused")
@Suppress("MemberVisibilityCanBePrivate")
class CompatUtil(private val sdkVersion: Int) {
/**
* Wrapper class holding values computed by [CompatUtil]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,20 +40,28 @@ 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
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.getDeviceInfo
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.usb.UsbConfiguration
import com.yubico.yubikit.core.Transport
import com.yubico.yubikit.core.YubiKeyDevice
import com.yubico.yubikit.core.smartcard.SmartCardConnection
import com.yubico.yubikit.core.smartcard.scp.Scp11KeyParams
import com.yubico.yubikit.core.smartcard.scp.ScpKeyParams
import com.yubico.yubikit.core.smartcard.scp.ScpKid
import com.yubico.yubikit.core.smartcard.scp.SecurityDomainSession
import io.flutter.embedding.android.FlutterFragmentActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.BinaryMessenger
Expand All @@ -62,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 @@ -116,7 +126,7 @@ class MainActivity : FlutterFragmentActivity() {
}

hasNfc = true
} catch (e: NfcNotAvailable) {
} catch (_: NfcNotAvailable) {
hasNfc = false
}

Expand Down Expand Up @@ -231,7 +241,7 @@ class MainActivity : FlutterFragmentActivity() {
startNfcDiscovery()
}

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

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

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

// 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 =
device.withConnection<SmartCardConnection, ScpKeyParams?> { connection ->
val scp = SecurityDomainSession(connection)
val keyRef = scp.keyInformation.keys.firstOrNull { it.kid == ScpKid.SCP11b }
keyRef?.let {
val certs = scp.getCertificateBundle(it)
if (certs.isNotEmpty()) Scp11KeyParams(
keyRef,
certs[certs.size - 1].publicKey
) else null
}?.also {
logger.debug("Found SCP11b key: {}", keyRef)
}
}
}

// 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 All @@ -293,7 +329,7 @@ class MainActivity : FlutterFragmentActivity() {
switchContext(preferredContext)
}

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

Expand Down Expand Up @@ -406,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 All @@ -427,7 +469,7 @@ class MainActivity : FlutterFragmentActivity() {
}

private val sharedPreferencesListener = OnSharedPreferenceChangeListener { _, key ->
if ( AppPreferences.PREF_NFC_SILENCE_SOUNDS == key) {
if (AppPreferences.PREF_NFC_SILENCE_SOUNDS == key) {
stopNfcDiscovery()
startNfcDiscovery()
}
Expand Down Expand Up @@ -493,26 +535,30 @@ class MainActivity : FlutterFragmentActivity() {
}
result.success(true)
}

"hasCamera" -> {
val cameraService =
getSystemService(Context.CAMERA_SERVICE) as CameraManager
getSystemService(CAMERA_SERVICE) as CameraManager
result.success(
cameraService.cameraIdList.any {
cameraService.getCameraCharacteristics(it)
.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_BACK
}
)
}

"hasNfc" -> result.success(
packageManager.hasSystemFeature(PackageManager.FEATURE_NFC)
)

"isNfcEnabled" -> {
val nfcAdapter = NfcAdapter.getDefaultAdapter(this@MainActivity)

result.success(
nfcAdapter != null && nfcAdapter.isEnabled
)
}

"openNfcSettings" -> {
startActivity(Intent(ACTION_NFC_SETTINGS))
result.success(true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import com.yubico.authenticator.MainViewModel
import com.yubico.authenticator.OperationContext
import com.yubico.yubikit.android.transport.usb.UsbYubiKeyDevice
import com.yubico.yubikit.core.YubiKeyDevice
import com.yubico.yubikit.core.smartcard.scp.ScpKeyParams
import com.yubico.yubikit.management.Capability
import org.slf4j.LoggerFactory

Expand All @@ -46,6 +47,15 @@ class DeviceManager(

private val deviceListeners = HashSet<DeviceListener>()

val deviceInfo: Info?
get() = appViewModel.deviceInfo.value

var scpKeyParams: ScpKeyParams? = null
set(value) {
field = value
logger.debug("SCP params set to {}", value)
}

fun addDeviceListener(listener: DeviceListener) {
deviceListeners.add(listener)
}
Expand Down Expand Up @@ -157,6 +167,7 @@ class DeviceManager(

fun setDeviceInfo(deviceInfo: Info?) {
appViewModel.setDeviceInfo(deviceInfo)
scpKeyParams = null
}

fun isUsbKeyConnected(): Boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import com.yubico.yubikit.management.DeviceInfo
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

private fun DeviceInfo.capabilitiesFor(transport: Transport) : Int? =
private fun DeviceInfo.capabilitiesFor(transport: Transport): Int? =
when {
hasTransport(transport) -> getSupportedCapabilities(transport)
else -> null
Expand All @@ -30,7 +30,7 @@ private fun DeviceInfo.capabilitiesFor(transport: Transport) : Int? =
@Serializable
data class Info(
@SerialName("config")
val config : Config,
val config: Config,
@SerialName("serial")
val serialNumber: Int?,
@SerialName("version")
Expand All @@ -53,11 +53,21 @@ data class Info(
val pinComplexity: Boolean,
@SerialName("supported_capabilities")
val supportedCapabilities: Capabilities,
@SerialName("fips_capable")
val fipsCapable: Int,
@SerialName("fips_approved")
val fipsApproved: Int,
@SerialName("reset_blocked")
val resetBlocked: Int,
) {
constructor(name: String, isNfc: Boolean, usbPid: Int?, deviceInfo: DeviceInfo) : this(
config = Config(deviceInfo.config),
serialNumber = deviceInfo.serialNumber,
version = Version(deviceInfo.version.major, deviceInfo.version.minor, deviceInfo.version.micro),
version = Version(
deviceInfo.version.major,
deviceInfo.version.minor,
deviceInfo.version.micro
),
formFactor = deviceInfo.formFactor.value,
isLocked = deviceInfo.isLocked,
isSky = deviceInfo.isSky,
Expand All @@ -69,6 +79,9 @@ data class Info(
supportedCapabilities = Capabilities(
nfc = deviceInfo.capabilitiesFor(Transport.NFC),
usb = deviceInfo.capabilitiesFor(Transport.USB),
)
),
fipsCapable = deviceInfo.fipsCapable,
fipsApproved = deviceInfo.fipsApproved,
resetBlocked = deviceInfo.resetBlocked,
)
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
/*
* Copyright (C) 2023-2024 Yubico.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.yubico.authenticator.device

import com.yubico.yubikit.core.Transport
Expand All @@ -17,11 +33,14 @@ val UnknownDevice = Info(
isLocked = false,
isSky = false,
isFips = false,
name = "Unrecognized device",
name = "unknown-device",
isNfc = false,
usbPid = null,
pinComplexity = false,
supportedCapabilities = Capabilities()
supportedCapabilities = Capabilities(),
fipsCapable = 0,
fipsApproved = 0,
resetBlocked = 0
)

fun unknownDeviceWithCapability(transport: Transport, bit: Int = 0) : Info {
Expand All @@ -47,4 +66,21 @@ fun unknownFido2DeviceInfo(transport: Transport) : Info {
return unknownDeviceWithCapability(transport, Capability.FIDO2.bit).copy(
name = "FIDO2 device"
)
}
}

fun restrictedNfcDeviceInfo(transport: Transport) : Info {
if (transport != Transport.NFC) {
return UnknownDevice
}

return UnknownDevice.copy(
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 @@ -26,7 +26,8 @@ enum class FidoActionDescription(private val value: Int) {
DeleteFingerprint(4),
RenameFingerprint(5),
RegisterFingerprint(6),
ActionFailure(7);
EnableEnterpriseAttestation(7),
ActionFailure(8);

val id: Int
get() = value + dialogDescriptionFidoIndex
Expand Down
Loading

0 comments on commit 2630665

Please sign in to comment.