Skip to content
Draft
Show file tree
Hide file tree
Changes from 133 commits
Commits
Show all changes
134 commits
Select commit Hold shift + click to select a range
f2b7e42
implement poc
AdamVe Apr 15, 2025
b26ccc7
add support for passing in extensions
AdamVe May 6, 2025
7fc9ed1
use M3X
AdamVe May 23, 2025
8d0b829
add nfc guide screen
AdamVe Jun 10, 2025
38b5a98
add icon effects
AdamVe Jun 10, 2025
4bb8425
Merge branch 'main' into main-private
AdamVe Jun 25, 2025
b989e19
Add WebView support and demo
AdamVe Jun 25, 2025
028fc39
Merge branch main into adamve/fido-android-poc
AdamVe Jun 26, 2025
6a89ba7
add NFC guide UI
AdamVe Jun 26, 2025
9d12246
remove tint from icon
AdamVe Jun 26, 2025
65ddf97
bump deps
AdamVe Jun 26, 2025
e35fe29
simplify API
AdamVe Jun 26, 2025
aa58d8b
demo UI updates
AdamVe Jun 27, 2025
81e5148
Merge branch 'origin/main' into adamve/fido-android-poc
AdamVe Aug 6, 2025
71e8585
Merge branch 'merge/release/2.8.2' into adamve/fido-android-poc
AdamVe Aug 6, 2025
24895ab
bump version, update deps
AdamVe Aug 6, 2025
da5007b
version 2.8.2_1-fa
AdamVe Aug 6, 2025
390cd75
reformat
AdamVe Aug 6, 2025
a673bbb
fix test dependencies
AdamVe Aug 6, 2025
5a168ce
fix :fido-android:javadocJar
AdamVe Aug 6, 2025
ffb7cbb
fix publishing warning
AdamVe Aug 6, 2025
154ac8f
enable artifacts
AdamVe Aug 6, 2025
928490a
improve FIDO Web demo
AdamVe Aug 6, 2025
93784af
minify release builds
AdamVe Aug 11, 2025
48121dc
don't send transports to authenticator
AdamVe Aug 11, 2025
81d037f
prevent crashes when NFC not available
AdamVe Aug 11, 2025
534e115
support for FIDO security keys
AdamVe Aug 12, 2025
795ed5e
bump version
AdamVe Aug 12, 2025
b60ca64
consider maxMsgSize before sending CBOR
AdamVe Aug 15, 2025
fb6739a
formatting
AdamVe Aug 15, 2025
6242b2f
Merge branch 'adamve/fix_pre-flight' into adamve/fido-android-poc
AdamVe Aug 15, 2025
c9f6b9d
v2.8.2_3-fa
AdamVe Aug 15, 2025
c167e77
fix apdu formatter
AdamVe Aug 25, 2025
fa1b82b
Merge branch 'main' into adamve/fido-android-poc
AdamVe Aug 26, 2025
22cf6ba
bump version to 2.8.2_4-fa
AdamVe Aug 26, 2025
36a05b9
bump dependencies
AdamVe Sep 19, 2025
28e3397
Merge main, bump version to 2.9.0_1-fa
AdamVe Sep 19, 2025
42c3eb5
fix format
AdamVe Sep 19, 2025
6da1f41
Add proof of concept fido-android module
AdamVe Sep 19, 2025
d91bd4a
set version to 2.9.0_1-fa
AdamVe Sep 19, 2025
fe2acd9
fix fido-android signing
AdamVe Sep 19, 2025
6bdbdf6
add fido-android-provider-service module
AdamVe Sep 22, 2025
2942270
don't export activity
AdamVe Sep 26, 2025
2cd8c1a
fix nfc guide and styling
AdamVe Sep 26, 2025
83bd409
rethink provider service icons
AdamVe Sep 26, 2025
4f01d0e
update service dependencies
AdamVe Sep 26, 2025
98bccaf
refactor activity
AdamVe Sep 26, 2025
d9b6d26
refactor
AdamVe Sep 26, 2025
b5bdbbb
fix: Correct visibility of WebView extension
AdamVe Sep 29, 2025
62be364
Support multiple assertions
AdamVe Sep 30, 2025
277b5bb
Use more descriptive name for operation error state
AdamVe Sep 30, 2025
3d02a01
open software keyboard when PIN is needed
AdamVe Sep 30, 2025
08471af
Handle DEVICE_INELIGIBLE
AdamVe Sep 30, 2025
c44051c
use string resources
AdamVe Sep 30, 2025
f34be9f
version 2.9.0_2-fa
AdamVe Sep 30, 2025
8e7abad
implement Set PIN
AdamVe Oct 2, 2025
1cf887b
fix extractOrigin method
AdamVe Oct 3, 2025
d503ad5
bump version
AdamVe Oct 3, 2025
0dddc49
support more CTAP errors
AdamVe Oct 6, 2025
a62e8f2
add support for BIO in BasicWebAuthnClient
AdamVe Oct 8, 2025
5ef0888
Merge PR #224
AdamVe Oct 8, 2025
dd4d09e
improve UV
AdamVe Oct 9, 2025
0db5c06
correctly handle KEY_STORE_FULL
AdamVe Oct 10, 2025
6977bd2
refactor client screen
AdamVe Oct 10, 2025
16dcc96
fix UV UI flows
AdamVe Oct 13, 2025
132a3b4
simple PIN validation
AdamVe Oct 13, 2025
0f52dae
increase default height of sheet content
AdamVe Oct 13, 2025
73207b0
proper operation cancellation
AdamVe Oct 13, 2025
e8bf7fc
implement NfcAntennaHint
AdamVe Oct 13, 2025
20f7d2a
remove NfcUsageGuide
AdamVe Oct 13, 2025
83db1e5
change name of provider service in Demo App
AdamVe Oct 13, 2025
e73d204
handle key removal
AdamVe Oct 15, 2025
05e73b8
provider service fixes
AdamVe Oct 16, 2025
2598bc4
correct retry after error
AdamVe Oct 16, 2025
ef9c2bf
allow unsupported key types
AdamVe Oct 16, 2025
854be45
bump version to 2.9.0_4-fa
AdamVe Oct 16, 2025
2b129dc
change name of Provider Service in DemoApp
AdamVe Oct 16, 2025
8ac5fcc
refactor pin entry operations
AdamVe Oct 17, 2025
848e2cc
feat: added Brave Browser (Google Play Store) SHA256 cert hash to all…
DennisDyallo Nov 27, 2025
63fa59a
Merge PR #242
AdamVe Nov 28, 2025
bf59f4c
Merge branch 'main' into adamve/fido-android-poc
AdamVe Dec 8, 2025
ff6a27e
revert 48121dc, this is now handled in client
AdamVe Dec 8, 2025
7a2474e
fix formatting
AdamVe Dec 8, 2025
9ad1447
support customPom
AdamVe Dec 8, 2025
e5523e6
fix FIDO device detection
AdamVe Dec 8, 2025
ab3d024
bump poc version
AdamVe Dec 8, 2025
0f22e83
fix deprecations
AdamVe Dec 8, 2025
d319e61
code style fixes
AdamVe Dec 8, 2025
21ebef7
fix fido-android
AdamVe Dec 8, 2025
1e9f1ef
fix version string
AdamVe Dec 8, 2025
ba7e28b
add com.vivaldi.browser
AdamVe Dec 8, 2025
4b86026
add samsung and perplexity browsers
AdamVe Dec 8, 2025
379e4f1
feat: support forceChangePin
AdamVe Dec 18, 2025
de544ee
add spotless kotlin support + reformat
AdamVe Dec 18, 2025
ebf1fc3
support forcePinChange for ga
AdamVe Dec 18, 2025
902d125
v3.0.0_2-fa
AdamVe Dec 18, 2025
3ed0a28
Merge branch 'main' into adamve/fido-android-poc
AdamVe Jan 13, 2026
03b3d53
bump dependencies
AdamVe Jan 13, 2026
760ff37
use display name for multiple assertions
AdamVe Jan 13, 2026
a3386e8
Fix composable naming style
AdamVe Jan 13, 2026
0a6b488
use plural string resources
AdamVe Jan 13, 2026
01d5a58
add comments
AdamVe Jan 13, 2026
881051d
fix package name
AdamVe Jan 13, 2026
200f419
Implement Setting Activity and prioritizePin
AdamVe Jan 14, 2026
15da6da
update PinEntry screen keyboardOptions
AdamVe Jan 14, 2026
eddcab3
update ktlint, pre-commit, reformat
AdamVe Jan 14, 2026
7ab24fc
Replace allow list
AdamVe Jan 14, 2026
3430d23
add support for Related Origin Requests
AdamVe Jan 14, 2026
11a6e2f
update proguard-rules
AdamVe Jan 15, 2026
80eed2d
version 3.0.0_3-fa
AdamVe Jan 15, 2026
4035b6e
fix ktlint error
AdamVe Jan 15, 2026
f873132
fix ktlint errors
AdamVe Jan 15, 2026
d4db108
add unit tests
AdamVe Jan 21, 2026
f3ff210
fix lint issues
AdamVe Jan 21, 2026
5333bf4
update tests
AdamVe Jan 26, 2026
4021cbb
dont minify libs
AdamVe Jan 26, 2026
3bd8b88
use explicit api mode
AdamVe Jan 26, 2026
d73fd01
downgrade kotlin to 2.2.21
AdamVe Jan 27, 2026
219980c
rename top source directory
AdamVe Jan 27, 2026
58cb1f5
expose public API, hide impl in internal package
AdamVe Jan 27, 2026
5f76603
expose custom theme use through settings
AdamVe Jan 27, 2026
a3d084c
add prefix to string resources
AdamVe Jan 27, 2026
1f98180
improve provider settings activity
AdamVe Jan 27, 2026
16eb600
improve UX for create and chage FIDO PIN
AdamVe Jan 27, 2026
097766d
optimize spotless pre-commit script
AdamVe Jan 27, 2026
54c3262
version 3.0.0_4-fa
AdamVe Jan 27, 2026
601c581
provider-service: expose public API, hide impl
AdamVe Jan 28, 2026
0f033a7
fix formatting
AdamVe Jan 28, 2026
29b76d1
remove provider service submodule
AdamVe Jan 29, 2026
f81d547
update dependencies
AdamVe Jan 29, 2026
ca05f3a
rename to fido-android-ui
AdamVe Jan 29, 2026
8d01d99
add initial readme
AdamVe Jan 29, 2026
3fe80df
rename package to match module
AdamVe Jan 29, 2026
caaf05f
Merge release 3.0.1 into this
AdamVe Feb 10, 2026
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
2 changes: 1 addition & 1 deletion .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ version: 2
updates:
# Keep Gradle dependencies up to date
- package-ecosystem: "gradle"
directory: "/" # Root where build.gradle/settings.gradle reside
directory: "/" # Root where build.gradle.kts/settings.gradle.kts reside
schedule:
interval: "daily" # Adjust to "weekly" if preferred
open-pull-requests-limit: 5
Expand Down
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ repos:
- repo: local
hooks:
- id: spotless
name: reformat Java
name: reformat sources
language: script
entry: scripts/spotless.py
always_run: true
4 changes: 4 additions & 0 deletions AndroidDemo/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlinx.serialization)
id("yubikit-common")
}

Expand Down Expand Up @@ -70,10 +71,12 @@ dependencies {
implementation(project(":yubiotp"))
implementation(project(":oath"))
implementation(project(":piv"))
implementation(project(":fido-android-ui"))
implementation(project(":support"))

coreLibraryDesugaring(libs.desugar.jdk.libs)
implementation(libs.kotlinx.coroutines.android)
implementation(libs.kotlinx.serialization.json)

implementation(libs.androidx.core.ktx)
implementation(libs.androidx.fragment.ktx)
Expand All @@ -89,6 +92,7 @@ dependencies {
implementation(libs.androidx.navigation.fragment.ktx)
implementation(libs.androidx.navigation.ui.ktx)
implementation(libs.androidx.navigation.dynamic.features.fragment)
implementation(libs.androidx.webkit)

implementation(libs.bcpkix.jdk15to18)
implementation(libs.logback.android)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,4 @@ class ExampleInstrumentedTest {
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.yubico.yubikit.android.app.test", appContext.packageName)
}
}
}
2 changes: 1 addition & 1 deletion AndroidDemo/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<application
android:name="androidx.multidex.MultiDexApplication"
android:allowBackup="true"
android:label="@string/app_name"
android:label="@string/yk_fido_app_name"
android:supportsRtl="true"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,16 @@ class MainActivity : AppCompatActivity() {
navController = findNavController(R.id.nav_host_fragment)
// Passing each menu ID as a set of Ids because each
// menu should be considered as top level destinations.
appBarConfiguration = AppBarConfiguration(setOf(
R.id.nav_management, R.id.nav_yubiotp, R.id.nav_piv, R.id.nav_oath), drawerLayout)
appBarConfiguration =
AppBarConfiguration(
setOf(
R.id.nav_management,
R.id.nav_yubiotp,
R.id.nav_piv,
R.id.nav_oath,
),
drawerLayout,
)
setupActionBarWithNavController(navController, appBarConfiguration)
navView.setupWithNavController(navController)

Expand Down Expand Up @@ -123,12 +131,17 @@ class MainActivity : AppCompatActivity() {
R.id.action_about -> {
val binding = DialogAboutBinding.inflate(LayoutInflater.from(this))
AlertDialog.Builder(this)
.setView(binding.root)
.create().apply {
setOnShowListener {
binding.version.text = String.format(Locale.getDefault(), getString(R.string.version), BuildConfig.VERSION_NAME)
}
}.show()
.setView(binding.root)
.create().apply {
setOnShowListener {
binding.version.text =
String.format(
Locale.getDefault(),
getString(R.string.yk_provider_service_version_label),
BuildConfig.VERSION_NAME,
)
}
}.show()
}
}
return super.onOptionsItemSelected(item)
Expand Down Expand Up @@ -168,4 +181,4 @@ class MainActivity : AppCompatActivity() {
yubikit.stopUsbDiscovery()
super.onDestroy()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,4 @@ class MainViewModel : ViewModel() {
}

val yubiKey = MutableLiveData<YubiKeyDevice?>()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,17 @@ import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine

@UiThread
suspend fun getSecret(context: Context, @StringRes title: Int, @StringRes hint: Int = R.string.pin) = suspendCoroutine { cont ->
val view = LayoutInflater.from(context).inflate(R.layout.dialog_pin, null).apply {
findViewById<TextInputLayout>(R.id.dialog_pin_textinputlayout).hint = context.getString(hint)
}
val dialog = AlertDialog.Builder(context)
suspend fun getSecret(
context: Context,
@StringRes title: Int,
@StringRes hint: Int = R.string.pin,
) = suspendCoroutine { cont ->
val view =
LayoutInflater.from(context).inflate(R.layout.dialog_pin, null).apply {
findViewById<TextInputLayout>(R.id.dialog_pin_textinputlayout).hint = context.getString(hint)
}
val dialog =
AlertDialog.Builder(context)
.setTitle(title)
.setView(view)
.setPositiveButton(android.R.string.ok) { _, _ ->
Expand All @@ -46,4 +52,4 @@ suspend fun getSecret(context: Context, @StringRes title: Int, @StringRes hint:
}
.create()
dialog.show()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,27 +21,21 @@ import android.os.Bundle
import android.view.View
import android.widget.TextView
import android.widget.Toast

import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.lifecycleScope

import com.yubico.yubikit.android.app.MainViewModel
import com.yubico.yubikit.android.app.R
import com.yubico.yubikit.android.transport.nfc.NfcYubiKeyDevice
import com.yubico.yubikit.core.YubiKeyDevice
import com.yubico.yubikit.core.application.ApplicationNotAvailableException

import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

import org.slf4j.LoggerFactory

import java.io.Closeable

abstract class YubiKeyFragment<App : Closeable, VM : YubiKeyViewModel<App>> : Fragment() {

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

private val activityViewModel: MainViewModel by activityViewModels()
Expand All @@ -50,11 +44,15 @@ abstract class YubiKeyFragment<App : Closeable, VM : YubiKeyViewModel<App>> : Fr
private lateinit var yubiKeyPrompt: AlertDialog
private lateinit var emptyText: TextView

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
override fun onViewCreated(
view: View,
savedInstanceState: Bundle?,
) {
emptyText = view.findViewById(R.id.empty_view)
emptyText.visibility = View.VISIBLE

yubiKeyPrompt = AlertDialog.Builder(context)
yubiKeyPrompt =
AlertDialog.Builder(context)
.setTitle("Insert YubiKey")
.setMessage(R.string.need_yubikey)
.setOnCancelListener { viewModel.pendingAction.value = null }
Expand Down Expand Up @@ -125,4 +123,4 @@ abstract class YubiKeyFragment<App : Closeable, VM : YubiKeyViewModel<App>> : Fr
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,12 @@ abstract class YubiKeyViewModel<Session : Closeable> : ViewModel() {

val pendingAction = MutableLiveData<(Session.() -> String?)?>()

abstract fun getSession(device: YubiKeyDevice, onError: (Throwable) -> Unit, callback: (Session) -> Unit)
abstract fun getSession(
device: YubiKeyDevice,
onError: (Throwable) -> Unit,
callback: (Session) -> Unit,
)

abstract fun Session.updateState()

fun onYubiKeyDevice(device: YubiKeyDevice) {
Expand All @@ -53,4 +58,4 @@ abstract class YubiKeyViewModel<Session : Closeable> : ViewModel() {
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/*
* Copyright (C) 2025-2026 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.yubikit.android.app.ui.fido

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import com.yubico.yubikit.android.app.databinding.FragmentFidoAppLocalBinding
import com.yubico.yubikit.core.internal.codec.Base64
import com.yubico.yubikit.fido.android.ui.FidoClient
import com.yubico.yubikit.fido.android.ui.Origin
import kotlinx.coroutines.launch
import kotlinx.serialization.json.Json
import org.slf4j.LoggerFactory
import kotlin.random.Random

class FidoAppLocalFragment : Fragment() {
private val logger = LoggerFactory.getLogger(FidoAppLocalFragment::class.java)
private var _binding: FragmentFidoAppLocalBinding? = null
val binding get() = _binding!!
private lateinit var fidoClient: FidoClient

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?,
): View? {
fidoClient = FidoClient(this)
_binding = FragmentFidoAppLocalBinding.inflate(inflater, container, false)
return binding.root
}

override fun onDestroyView() {
super.onDestroyView()
_binding = null
}

private fun buildMcRequest(
userName: String,
userDisplayName: String = userName,
): String {
val challenge = ByteArray(16).also { Random.nextBytes(it) }
val userId = ByteArray(32).also { Random.nextBytes(it) }
val request =
McRequest(
challenge = Base64.toUrlSafeString(challenge),
rp = Rp(RP_ID, RP_ID),
user = User(Base64.toUrlSafeString(userId), userName, userDisplayName),
)

return json.encodeToString(request)
}

private fun buildGaRequest(): String {
val challenge = ByteArray(16).also { Random.nextBytes(it) }
val request =
GaRequest(
challenge = Base64.toUrlSafeString(challenge),
rpId = RP_ID,
)
return json.encodeToString(request)
}

override fun onViewCreated(
view: View,
savedInstanceState: Bundle?,
) {
super.onViewCreated(view, savedInstanceState)

binding.btnMc.setOnClickListener {
lifecycleScope.launch {
val request = buildMcRequest("App test user")
logger.debug("Make credential request: {}", request)

fidoClient.makeCredential(Origin(ORIGIN), request, null)
.onSuccess { logger.debug("Successful MC: {}", it) }
.onFailure { logger.error("Error during MC: ", it) }
}
}

binding.btnGa.setOnClickListener {
lifecycleScope.launch {
val request = buildGaRequest()
logger.debug("Get assertions request: {}", request)

fidoClient.getAssertion(Origin(ORIGIN), request, null)
.onSuccess { logger.debug("Successful GA: {}", it) }
.onFailure { logger.error("Error during GA:", it) }
}
}
}

companion object {
const val ORIGIN = "https://demo.yubico.app"
const val RP_ID = "demo.yubico.app"
val json = Json { encodeDefaults = true }
}
}
Loading
Loading