Skip to content

Commit

Permalink
Merge branch 'main' into jokkon/keyless_tests
Browse files Browse the repository at this point in the history
  • Loading branch information
jokkon committed Aug 27, 2024
2 parents bbd8b94 + 848605c commit 63906da
Show file tree
Hide file tree
Showing 185 changed files with 6,427 additions and 3,552 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.5
PYVER=3.12.2
FLUTTER=3.24.1
PYVER=3.12.5
8 changes: 7 additions & 1 deletion .github/workflows/macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,17 @@ jobs:
- uses: subosito/flutter-action@v2
with:
channel: 'stable'
architecture: 'x64'
flutter-version: ${{ env.FLUTTER }}
- 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
20 changes: 20 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
@@ -1,3 +1,23 @@
* Version 7.0.1 (released 2024-05-30) Android only release
** Fix: Opening the app by NFC tap needs another tap to reveal accounts.
** Fix: NFC devices attached to mobile phone prevent usage of USB YubiKeys.
** Fix: Invalid colors shown in customization views for Android Dynamic color.
** Fix: Fingerprints are shown in random order.

* Version 7.0.0 (released 2024-05-06)
** UI: Add home screen with device information, customization options, and factory reset.
** UI: Add search filtering to Passkeys and display more information.
** Localization: Add official support for French and Japanese.
** PIV (desktop): Support managing retired key slots.
** Management (desktop): Support toggling applications when Configuration Lock code is set.
** OTP (desktop): Support managing slots when OTP Access Code is set.
** Android: Support for FIDO (managing PIN, Passkeys, Fingerprints, and factory reset).
** Linux: Add ability to use external program for copying to clipboard (see README).
** Additional features/support for YubiKey 5.7:
*** Handling of PIN complexity.
*** PIV: New key algorithms: RSA3072, RSA4096, Ed25519, and X25519.
*** PIV: The ability to move and delete keys.

* Version 6.4.0 (released 2024-02-20)
** UI: Major UI overhaul, with improvements including:
*** Add new UI layouts for wider windows to better utilize screen space.
Expand Down
52 changes: 23 additions & 29 deletions README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -2,39 +2,28 @@

image:splash.png[]

Store your unique credential on a hardware-backed security key and take it
wherever you go from mobile to desktop. No more storing sensitive secrets on
your mobile phone, leaving your account vulnerable to takeovers. With the
Yubico Authenticator you can raise the bar for security.

* The Yubico Authenticator will work with any USB or NFC-enabled YubiKeys
The Yubico Authenticator securely generates a code used to verify your identity
as you are logging into various services. No connectivity needed!
Manage your YubiKey and access one-time passwords with this full-featured
companion app to the YubiKey.

=== Features include
* Secure - Hardware-backed strong two-factor authentication with secret stored
on the YubiKey, not on your phone or computer
* Portable - Get the same set of codes across our other Yubico Authenticator
apps for desktops as well as for all leading mobile platforms
* Flexible - Support for time-based and counter-based code generation
* USB or NFC usage - Insert the YubiKey into the USB port, or use the YubiKey
with NFC with a mobile phone that is NFC-enabled or a desktop NFC reader to
store your credential on the YubiKey
* Easy Setup - QR codes available from the services you wish to protect with
strong authentication
* User Presence - Require a touch on the YubiKey sensor to generate new codes
for sensitive accounts
* Compatible - Secure all the services currently compatible with other
Authenticator apps
* Versatile - Support for multiple work and personal accounts
* Display information about your YubiKey such as serial number, firmware version,
and supported capabilities
* Manage and access OATH one-time passwords stored securely on your YubiKey
* Configure PIN, fingerprints, and manage passkeys for WebAuthn/FIDO
* Configure PIN/PUK/Management key, and manage private keys and certificates for PIV
* Provision Yubico OTP, static passwords, and other YubiKey slot-based credentials
* Configure enabled features, and factory reset YubiKey data
* Compatible with any USB or NFC-enabled YubiKey

Store your unique credential on a hardware-backed security key and take it
wherever you go from mobile to desktop. No more storing sensitive secrets on
your mobile phone, leaving your accounts vulnerable to takeovers. With the
YubiKey and Yubico Authenticator you can raise the bar for security. No
connectivity needed!

Experience security the modern way with the Yubico Authenticator.
Visit https://yubico.com to learn more.

NOTE: Yubico Authenticator 6 uses a new codebase built using the Flutter
framework. The previous Qt codebase can be found in the `legacy` branch.

=== Supported platforms

*Supported* - these are platforms we build and test on and commit to supporting.
Expand Down Expand Up @@ -74,8 +63,13 @@ https://developers.yubico.com/yubikey-manager/Device_Permissions.html[here].

For some configurations running Wayland, copying an OTP to clipboard only works
when the app has focus. If you are unable to reliably copy to clipboard from
the systray icon, you can set the environment variable `_YA_WL_CLIPFIX=1` to
enable a workaround that attempts to give the window focus before copying.
the systray icon, you can use a separate binary which take the payload to stdin
by defining the environment variable `_YA_TRAY_CLIPBOARD`. Note that this must
be an absolute path to a binary owned by root:root, and should not be
world-writable.
For example: `_YA_TRAY_CLIPBOARD=/usr/bin/wl-copy`.

NOTE: Only use a trusted binary, OTPs will be sent to this when copied to clipboard from the systray!

=== Command line interface
Looking for a command line option? Try our
Expand Down
15 changes: 4 additions & 11 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -94,23 +94,16 @@ dependencies {
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3'

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

implementation "androidx.core:core-ktx:1.12.0"
implementation 'androidx.fragment:fragment-ktx:1.6.2'
implementation "androidx.core:core-ktx:1.13.1"
implementation 'androidx.fragment:fragment-ktx:1.8.1'
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
120 changes: 73 additions & 47 deletions android/app/src/main/kotlin/com/yubico/authenticator/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -40,22 +40,27 @@ 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.UnknownDevice
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 Down Expand Up @@ -110,11 +115,15 @@ class MainActivity : FlutterFragmentActivity() {
logger.debug("Starting nfc discovery")
yubikit.startNfcDiscovery(
nfcConfiguration.disableNfcDiscoverySound(appPreferences.silenceNfcSounds),
this,
::processYubiKey
)
this
) { nfcYubiKeyDevice ->
if (!deviceManager.isUsbKeyConnected()) {
launchProcessYubiKey(nfcYubiKeyDevice)
}
}

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

Expand All @@ -133,7 +142,7 @@ class MainActivity : FlutterFragmentActivity() {
logger.debug("YubiKey was disconnected, stopping usb discovery")
stopUsbDiscovery()
}
processYubiKey(device)
launchProcessYubiKey(device)
}
}

Expand Down Expand Up @@ -216,7 +225,7 @@ class MainActivity : FlutterFragmentActivity() {
val device = NfcYubiKeyDevice(tag, nfcConfiguration.timeout, executor)
lifecycleScope.launch {
try {
contextManager?.processYubiKey(device)
processYubiKey(device)
device.remove {
executor.shutdown()
startNfcDiscovery()
Expand All @@ -229,7 +238,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 @@ -271,48 +280,61 @@ class MainActivity : FlutterFragmentActivity() {
}
}

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

val deviceInfo = try {
getDeviceInfo(device)
} catch (e: IllegalArgumentException) {
logger.debug("Device was not recognized")
UnknownDevice.copy(isNfc = device.transport == Transport.NFC)
} catch (e: Exception) {
logger.error("Failure getting device info", e)
null
}
if (deviceInfo == null) {
return
}

deviceManager.setDeviceInfo(deviceInfo)
// 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)
}
}
}

if (deviceInfo == null) {
return@launch
}
val supportedContexts = DeviceManager.getSupportedContexts(deviceInfo)
logger.debug("Connected key supports: {}", supportedContexts)
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)
}

val supportedContexts = DeviceManager.getSupportedContexts(deviceInfo)
logger.debug("Connected key supports: {}", supportedContexts)
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)
}
if (contextManager == null && supportedContexts.isNotEmpty()) {
switchContext(DeviceManager.getPreferredContext(supportedContexts))
}

if (contextManager == null) {
switchContext(DeviceManager.getPreferredContext(supportedContexts))
contextManager?.let {
try {
it.processYubiKey(device)
} catch (e: Throwable) {
logger.error("Error processing YubiKey in AppContextManager", e)
}
}
}

contextManager?.let {
try {
it.processYubiKey(device)
} catch (e: Throwable) {
logger.error("Error processing YubiKey in AppContextManager", e)
}
}
private fun launchProcessYubiKey(device: YubiKeyDevice) {
lifecycleScope.launch {
processYubiKey(device)
}
}

Expand Down Expand Up @@ -354,7 +376,7 @@ class MainActivity : FlutterFragmentActivity() {

viewModel.appContext.observe(this) {
switchContext(it)
viewModel.connectedYubiKey.value?.let(::processYubiKey)
viewModel.connectedYubiKey.value?.let(::launchProcessYubiKey)
}
}

Expand Down Expand Up @@ -431,7 +453,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 @@ -497,26 +519,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
Loading

0 comments on commit 63906da

Please sign in to comment.