@doko/react-native-yubikit 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +20 -0
- package/README.md +126 -0
- package/RNYubikit.podspec +22 -0
- package/android/build.gradle +63 -0
- package/android/src/main/AndroidManifest.xml +9 -0
- package/android/src/main/java/com/yubikit/YubiKitManagerHolder.kt +330 -0
- package/android/src/main/java/com/yubikit/YubikitCoreModule.kt +142 -0
- package/android/src/main/java/com/yubikit/YubikitFidoModule.kt +418 -0
- package/android/src/main/java/com/yubikit/YubikitManagementModule.kt +132 -0
- package/android/src/main/java/com/yubikit/YubikitOathModule.kt +224 -0
- package/android/src/main/java/com/yubikit/YubikitOpenPgpModule.kt +317 -0
- package/android/src/main/java/com/yubikit/YubikitPackage.kt +44 -0
- package/android/src/main/java/com/yubikit/YubikitPivModule.kt +310 -0
- package/android/src/main/java/com/yubikit/YubikitSupportModule.kt +108 -0
- package/android/src/main/java/com/yubikit/YubikitYubiOtpModule.kt +248 -0
- package/android/src/main/java/com/yubikit/utils/YubikitUtils.kt +321 -0
- package/ios/YubikitCore.h +5 -0
- package/ios/YubikitCore.mm +101 -0
- package/ios/YubikitDeviceInfoHelper.h +13 -0
- package/ios/YubikitDeviceInfoHelper.mm +110 -0
- package/ios/YubikitFido.h +5 -0
- package/ios/YubikitFido.mm +373 -0
- package/ios/YubikitManagement.h +5 -0
- package/ios/YubikitManagement.mm +153 -0
- package/ios/YubikitManager.h +27 -0
- package/ios/YubikitManager.m +169 -0
- package/ios/YubikitOath.h +5 -0
- package/ios/YubikitOath.mm +583 -0
- package/ios/YubikitOpenPgp.h +5 -0
- package/ios/YubikitOpenPgp.mm +210 -0
- package/ios/YubikitPiv.h +5 -0
- package/ios/YubikitPiv.mm +813 -0
- package/ios/YubikitSupport.h +5 -0
- package/ios/YubikitSupport.mm +58 -0
- package/ios/YubikitYubiOtp.h +5 -0
- package/ios/YubikitYubiOtp.mm +120 -0
- package/lib/module/core.js +34 -0
- package/lib/module/core.js.map +1 -0
- package/lib/module/fido.js +40 -0
- package/lib/module/fido.js.map +1 -0
- package/lib/module/hooks/index.js +5 -0
- package/lib/module/hooks/index.js.map +1 -0
- package/lib/module/hooks/use-yubikey.js +12 -0
- package/lib/module/hooks/use-yubikey.js.map +1 -0
- package/lib/module/hooks/yubikey-provider.js +85 -0
- package/lib/module/hooks/yubikey-provider.js.map +1 -0
- package/lib/module/index.js +12 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/management.js +17 -0
- package/lib/module/management.js.map +1 -0
- package/lib/module/oath.js +53 -0
- package/lib/module/oath.js.map +1 -0
- package/lib/module/openpgp.js +89 -0
- package/lib/module/openpgp.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/piv.js +71 -0
- package/lib/module/piv.js.map +1 -0
- package/lib/module/specs/NativeYubikitCore.js +10 -0
- package/lib/module/specs/NativeYubikitCore.js.map +1 -0
- package/lib/module/specs/NativeYubikitFido.js +5 -0
- package/lib/module/specs/NativeYubikitFido.js.map +1 -0
- package/lib/module/specs/NativeYubikitManagement.js +5 -0
- package/lib/module/specs/NativeYubikitManagement.js.map +1 -0
- package/lib/module/specs/NativeYubikitOath.js +5 -0
- package/lib/module/specs/NativeYubikitOath.js.map +1 -0
- package/lib/module/specs/NativeYubikitOpenPgp.js +5 -0
- package/lib/module/specs/NativeYubikitOpenPgp.js.map +1 -0
- package/lib/module/specs/NativeYubikitPiv.js +5 -0
- package/lib/module/specs/NativeYubikitPiv.js.map +1 -0
- package/lib/module/specs/NativeYubikitSupport.js +5 -0
- package/lib/module/specs/NativeYubikitSupport.js.map +1 -0
- package/lib/module/specs/NativeYubikitYubiOtp.js +5 -0
- package/lib/module/specs/NativeYubikitYubiOtp.js.map +1 -0
- package/lib/module/support.js +11 -0
- package/lib/module/support.js.map +1 -0
- package/lib/module/types.js +2 -0
- package/lib/module/types.js.map +1 -0
- package/lib/module/yubiotp.js +32 -0
- package/lib/module/yubiotp.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/core.d.ts +14 -0
- package/lib/typescript/src/core.d.ts.map +1 -0
- package/lib/typescript/src/fido.d.ts +24 -0
- package/lib/typescript/src/fido.d.ts.map +1 -0
- package/lib/typescript/src/hooks/index.d.ts +3 -0
- package/lib/typescript/src/hooks/index.d.ts.map +1 -0
- package/lib/typescript/src/hooks/use-yubikey.d.ts +3 -0
- package/lib/typescript/src/hooks/use-yubikey.d.ts.map +1 -0
- package/lib/typescript/src/hooks/yubikey-provider.d.ts +23 -0
- package/lib/typescript/src/hooks/yubikey-provider.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +10 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/management.d.ts +8 -0
- package/lib/typescript/src/management.d.ts.map +1 -0
- package/lib/typescript/src/oath.d.ts +20 -0
- package/lib/typescript/src/oath.d.ts.map +1 -0
- package/lib/typescript/src/openpgp.d.ts +35 -0
- package/lib/typescript/src/openpgp.d.ts.map +1 -0
- package/lib/typescript/src/piv.d.ts +26 -0
- package/lib/typescript/src/piv.d.ts.map +1 -0
- package/lib/typescript/src/specs/NativeYubikitCore.d.ts +49 -0
- package/lib/typescript/src/specs/NativeYubikitCore.d.ts.map +1 -0
- package/lib/typescript/src/specs/NativeYubikitFido.d.ts +121 -0
- package/lib/typescript/src/specs/NativeYubikitFido.d.ts.map +1 -0
- package/lib/typescript/src/specs/NativeYubikitManagement.d.ts +53 -0
- package/lib/typescript/src/specs/NativeYubikitManagement.d.ts.map +1 -0
- package/lib/typescript/src/specs/NativeYubikitOath.d.ts +70 -0
- package/lib/typescript/src/specs/NativeYubikitOath.d.ts.map +1 -0
- package/lib/typescript/src/specs/NativeYubikitOpenPgp.d.ts +53 -0
- package/lib/typescript/src/specs/NativeYubikitOpenPgp.d.ts.map +1 -0
- package/lib/typescript/src/specs/NativeYubikitPiv.d.ts +47 -0
- package/lib/typescript/src/specs/NativeYubikitPiv.d.ts.map +1 -0
- package/lib/typescript/src/specs/NativeYubikitSupport.d.ts +49 -0
- package/lib/typescript/src/specs/NativeYubikitSupport.d.ts.map +1 -0
- package/lib/typescript/src/specs/NativeYubikitYubiOtp.d.ts +45 -0
- package/lib/typescript/src/specs/NativeYubikitYubiOtp.d.ts.map +1 -0
- package/lib/typescript/src/support.d.ts +6 -0
- package/lib/typescript/src/support.d.ts.map +1 -0
- package/lib/typescript/src/types.d.ts +293 -0
- package/lib/typescript/src/types.d.ts.map +1 -0
- package/lib/typescript/src/yubiotp.d.ts +13 -0
- package/lib/typescript/src/yubiotp.d.ts.map +1 -0
- package/package.json +181 -0
- package/src/core.ts +58 -0
- package/src/fido.ts +112 -0
- package/src/hooks/index.tsx +2 -0
- package/src/hooks/use-yubikey.tsx +10 -0
- package/src/hooks/yubikey-provider.tsx +146 -0
- package/src/index.tsx +10 -0
- package/src/management.ts +44 -0
- package/src/oath.ts +134 -0
- package/src/openpgp.ts +220 -0
- package/src/piv.ts +193 -0
- package/src/specs/NativeYubikitCore.ts +68 -0
- package/src/specs/NativeYubikitFido.ts +149 -0
- package/src/specs/NativeYubikitManagement.ts +78 -0
- package/src/specs/NativeYubikitOath.ts +110 -0
- package/src/specs/NativeYubikitOpenPgp.ts +148 -0
- package/src/specs/NativeYubikitPiv.ts +108 -0
- package/src/specs/NativeYubikitSupport.ts +61 -0
- package/src/specs/NativeYubikitYubiOtp.ts +82 -0
- package/src/support.ts +18 -0
- package/src/types.ts +404 -0
- package/src/yubiotp.ts +96 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Quan Pham
|
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
6
|
+
in the Software without restriction, including without limitation the rights
|
|
7
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
furnished to do so, subject to the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be included in all
|
|
12
|
+
copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
react-native-yubikit
|
|
2
|
+
|
|
3
|
+
A React Native TurboModule wrapper around Yubico's native [YubiKit Android](https://developers.yubico.com/yubikit-android/) and [YubiKit iOS](https://developers.yubico.com/yubikit-ios/) SDKs, for talking to YubiKey hardware over USB and NFC.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@doko/react-native-yubikit)
|
|
6
|
+
[](https://www.npmjs.com/package/@doko/react-native-yubikit)
|
|
7
|
+
[](https://reactnative.dev/docs/the-new-architecture/landing-page)
|
|
8
|
+
[](https://www.typescriptlang.org/)
|
|
9
|
+
[](LICENSE)
|
|
10
|
+
[](https://developer.apple.com/ios/)
|
|
11
|
+
[](https://developer.android.com/)
|
|
12
|
+
|
|
13
|
+
📖 **Full documentation:** [doko.aniviet.com/oss/react-native-yubikey](https://doko.aniviet.com/oss/react-native-yubikey)
|
|
14
|
+
|
|
15
|
+
The docs site covers requirements, installation (including the iOS Podfile override you need), usage examples for every module, security notes, advanced patterns, and troubleshooting. This README is deliberately short - start there for anything beyond a quick look.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Features
|
|
20
|
+
|
|
21
|
+
- 🔌 **USB + NFC discovery** - attach/detach events via a single `YubiKeyEvent` stream
|
|
22
|
+
- 🧩 **8 native modules**, exposed as namespaces: `Core`, `Support`, `Management`, `Oath`, `Piv`, `OpenPgp`, `YubiOtp`, `Fido`
|
|
23
|
+
- 🪝 **`YubiKeyProvider` + `useYubiKey()`** - drop-in device discovery/selection state, no boilerplate required
|
|
24
|
+
- 🔐 **PIV** - PIN/PUK, slot metadata, certificates, key generation, raw sign/decrypt
|
|
25
|
+
- 🔑 **FIDO2/WebAuthn** - authenticator info, registration, authentication
|
|
26
|
+
- ⏱️ **OATH** - TOTP/HOTP credential management and code calculation
|
|
27
|
+
- 🆕 **New Architecture only** - built as Fabric/TurboModules with Codegen, no old-bridge fallback
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Platform support
|
|
32
|
+
|
|
33
|
+
Android has full parity across every module. iOS is missing OpenPGP entirely, YubiOTP slot programming, and FIDO2 resident-credential management - these gaps come from the underlying YubiKit iOS SDK itself, not from this wrapper. See the [full platform support table](https://doko.aniviet.com/oss/react-native-yubikey) on the docs site for the exact per-method breakdown.
|
|
34
|
+
|
|
35
|
+
| Module | Android | iOS |
|
|
36
|
+
| -------------- | ------- | --------------------------------------- |
|
|
37
|
+
| Core / Support | Full | Full |
|
|
38
|
+
| Management | Full | Partial |
|
|
39
|
+
| OATH | Full | Full |
|
|
40
|
+
| PIV | Full | Full (no RSA3072/4096 raw sign/decrypt) |
|
|
41
|
+
| OpenPGP | Full | Not available |
|
|
42
|
+
| YubiOTP | Full | Partial (HMAC-SHA1 only) |
|
|
43
|
+
| FIDO2 | Full | Partial (no credential management) |
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Requirements
|
|
48
|
+
|
|
49
|
+
| | Minimum |
|
|
50
|
+
| ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
51
|
+
| React Native | 0.74+ with the **New Architecture enabled** |
|
|
52
|
+
| iOS | 16.4+ recommended ([Podfile override required](https://doko.aniviet.com/oss/react-native-yubikey/installation) - the CocoaPods-trunk `YubiKit` pod is too old) |
|
|
53
|
+
| Android | `minSdkVersion` 24 |
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Installation
|
|
58
|
+
|
|
59
|
+
```sh
|
|
60
|
+
npm install @doko/react-native-yubikit
|
|
61
|
+
# or
|
|
62
|
+
yarn add @doko/react-native-yubikit
|
|
63
|
+
# or
|
|
64
|
+
pnpm add @doko/react-native-yubikit
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
iOS needs a Podfile override and a few entitlements depending on which transports you use; Android needs USB/NFC manifest entries. See [Installation](https://doko.aniviet.com/oss/react-native-yubikey/installation) for the exact steps.
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Quick start
|
|
72
|
+
|
|
73
|
+
```tsx
|
|
74
|
+
import { YubiKeyProvider, useYubiKey } from '@doko/react-native-yubikit';
|
|
75
|
+
|
|
76
|
+
export default function App() {
|
|
77
|
+
return (
|
|
78
|
+
<YubiKeyProvider>
|
|
79
|
+
<DeviceScreen />
|
|
80
|
+
</YubiKeyProvider>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function DeviceScreen() {
|
|
85
|
+
const { devices, selectedDevice, startUsbDiscovery, stopUsbDiscovery } =
|
|
86
|
+
useYubiKey();
|
|
87
|
+
|
|
88
|
+
// startUsbDiscovery({ handlePermissions: true }) to begin listening,
|
|
89
|
+
// then read/write against selectedDevice.handle with any module below.
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
import { Management, Oath, Piv } from '@doko/react-native-yubikit';
|
|
95
|
+
|
|
96
|
+
const info = await Management.getDeviceInfo(deviceHandle);
|
|
97
|
+
await Piv.verifyPin(deviceHandle, '123456');
|
|
98
|
+
const { credentials } = await Oath.getCredentials(deviceHandle);
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
More examples for every module are in [Usage Examples](https://doko.aniviet.com/oss/react-native-yubikey/usage).
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Example app
|
|
106
|
+
|
|
107
|
+
A runnable example app is included in [`example/`](./example) (Expo Router, demonstrates every supported module).
|
|
108
|
+
|
|
109
|
+
```sh
|
|
110
|
+
pnpm install
|
|
111
|
+
pnpm example start
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Then press `a` for Android or `i` for iOS.
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Contributing
|
|
119
|
+
|
|
120
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for the development workflow, build instructions, and how to send a pull request.
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## License
|
|
125
|
+
|
|
126
|
+
MIT
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
package = JSON.parse(File.read(File.join(__dir__, "package.json")))
|
|
4
|
+
|
|
5
|
+
Pod::Spec.new do |s|
|
|
6
|
+
s.name = "RNYubikit"
|
|
7
|
+
s.version = package["version"]
|
|
8
|
+
s.summary = package["description"]
|
|
9
|
+
s.homepage = package["homepage"]
|
|
10
|
+
s.license = package["license"]
|
|
11
|
+
s.authors = package["author"]
|
|
12
|
+
|
|
13
|
+
s.platforms = { :ios => min_ios_version_supported }
|
|
14
|
+
s.source = { :git => "https://github.com/Doko-Demo-Doa/react-native-yubikit.git", :tag => "#{s.version}" }
|
|
15
|
+
|
|
16
|
+
s.source_files = "ios/**/*.{h,m,mm,swift,cpp}"
|
|
17
|
+
s.private_header_files = "ios/**/*.h"
|
|
18
|
+
|
|
19
|
+
s.dependency "YubiKit"
|
|
20
|
+
|
|
21
|
+
install_modules_dependencies(s)
|
|
22
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
buildscript {
|
|
2
|
+
ext.Yubikit = [
|
|
3
|
+
kotlinVersion: "2.0.21",
|
|
4
|
+
minSdkVersion: 24,
|
|
5
|
+
compileSdkVersion: 36
|
|
6
|
+
]
|
|
7
|
+
|
|
8
|
+
ext.getExtOrDefault = { prop ->
|
|
9
|
+
if (rootProject.ext.has(prop)) {
|
|
10
|
+
return rootProject.ext.get(prop)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return Yubikit[prop]
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
repositories {
|
|
17
|
+
google()
|
|
18
|
+
mavenCentral()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
dependencies {
|
|
22
|
+
classpath "com.android.tools.build:gradle:8.7.2"
|
|
23
|
+
// noinspection DifferentKotlinGradleVersion
|
|
24
|
+
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${getExtOrDefault('kotlinVersion')}"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
apply plugin: "com.android.library"
|
|
30
|
+
apply plugin: "kotlin-android"
|
|
31
|
+
|
|
32
|
+
apply plugin: "com.facebook.react"
|
|
33
|
+
|
|
34
|
+
android {
|
|
35
|
+
namespace "com.yubikit"
|
|
36
|
+
|
|
37
|
+
compileSdkVersion getExtOrDefault("compileSdkVersion")
|
|
38
|
+
|
|
39
|
+
defaultConfig {
|
|
40
|
+
minSdkVersion getExtOrDefault("minSdkVersion")
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
compileOptions {
|
|
44
|
+
sourceCompatibility JavaVersion.VERSION_17
|
|
45
|
+
targetCompatibility JavaVersion.VERSION_17
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
dependencies {
|
|
50
|
+
implementation "com.facebook.react:react-android"
|
|
51
|
+
|
|
52
|
+
def yubikitVersion = "3.1.0"
|
|
53
|
+
implementation "com.yubico.yubikit:android:${yubikitVersion}"
|
|
54
|
+
implementation "com.yubico.yubikit:core:${yubikitVersion}"
|
|
55
|
+
implementation "com.yubico.yubikit:fido:${yubikitVersion}"
|
|
56
|
+
implementation "com.yubico.yubikit:fido-android-ui:${yubikitVersion}"
|
|
57
|
+
implementation "com.yubico.yubikit:management:${yubikitVersion}"
|
|
58
|
+
implementation "com.yubico.yubikit:yubiotp:${yubikitVersion}"
|
|
59
|
+
implementation "com.yubico.yubikit:openpgp:${yubikitVersion}"
|
|
60
|
+
implementation "com.yubico.yubikit:oath:${yubikitVersion}"
|
|
61
|
+
implementation "com.yubico.yubikit:piv:${yubikitVersion}"
|
|
62
|
+
implementation "com.yubico.yubikit:support:${yubikitVersion}"
|
|
63
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
|
2
|
+
<!-- YubiKey USB support -->
|
|
3
|
+
<uses-feature android:name="android.hardware.usb.host" android:required="false" />
|
|
4
|
+
<uses-permission android:name="android.permission.NFC" />
|
|
5
|
+
|
|
6
|
+
<application>
|
|
7
|
+
<!-- YubiKit uses USB device detection via the host APIs; no manifest receivers are required. -->
|
|
8
|
+
</application>
|
|
9
|
+
</manifest>
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
package com.yubikit
|
|
2
|
+
|
|
3
|
+
import android.app.Activity
|
|
4
|
+
import android.content.Context
|
|
5
|
+
import com.facebook.react.bridge.Arguments
|
|
6
|
+
import com.facebook.react.bridge.WritableMap
|
|
7
|
+
import com.yubico.yubikit.android.YubiKitManager
|
|
8
|
+
import com.yubico.yubikit.android.transport.nfc.NfcConfiguration
|
|
9
|
+
import com.yubico.yubikit.android.transport.nfc.NfcNotAvailable
|
|
10
|
+
import com.yubico.yubikit.android.transport.nfc.NfcYubiKeyDevice
|
|
11
|
+
import com.yubico.yubikit.android.transport.usb.UsbConfiguration
|
|
12
|
+
import com.yubico.yubikit.android.transport.usb.UsbYubiKeyDevice
|
|
13
|
+
import com.yubico.yubikit.core.YubiKeyConnection
|
|
14
|
+
import com.yubico.yubikit.core.YubiKeyDevice
|
|
15
|
+
import com.yubico.yubikit.core.fido.FidoConnection
|
|
16
|
+
import com.yubico.yubikit.core.otp.OtpConnection
|
|
17
|
+
import com.yubico.yubikit.core.smartcard.SmartCardConnection
|
|
18
|
+
import com.yubikit.utils.YubikitUtils.toWritableMap
|
|
19
|
+
import java.io.IOException
|
|
20
|
+
import java.util.UUID
|
|
21
|
+
import java.util.concurrent.ConcurrentHashMap
|
|
22
|
+
import java.util.concurrent.CountDownLatch
|
|
23
|
+
import java.util.concurrent.TimeUnit
|
|
24
|
+
import java.util.concurrent.atomic.AtomicReference
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Singleton holder for the YubiKitManager and discovered devices/connections.
|
|
28
|
+
*/
|
|
29
|
+
object YubiKitManagerHolder {
|
|
30
|
+
@Volatile
|
|
31
|
+
private var manager: YubiKitManager? = null
|
|
32
|
+
|
|
33
|
+
private val devices = ConcurrentHashMap<String, YubiKeyDevice>()
|
|
34
|
+
private val connections = ConcurrentHashMap<String, YubiKeyConnection>()
|
|
35
|
+
private val connectionReleaseLatches = ConcurrentHashMap<String, CountDownLatch>()
|
|
36
|
+
|
|
37
|
+
private var usbConfiguration = UsbConfiguration()
|
|
38
|
+
private var nfcConfiguration = NfcConfiguration()
|
|
39
|
+
|
|
40
|
+
@JvmStatic
|
|
41
|
+
fun initialize(context: Context) {
|
|
42
|
+
if (manager == null) {
|
|
43
|
+
synchronized(this) {
|
|
44
|
+
if (manager == null) {
|
|
45
|
+
manager = YubiKitManager(context.applicationContext)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
@JvmStatic
|
|
52
|
+
fun getManager(): YubiKitManager {
|
|
53
|
+
return manager ?: throw IllegalStateException("YubiKitManager not initialized")
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
@JvmStatic
|
|
57
|
+
fun putDevice(device: YubiKeyDevice): String {
|
|
58
|
+
val handle = UUID.randomUUID().toString()
|
|
59
|
+
devices[handle] = device
|
|
60
|
+
return handle
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@JvmStatic
|
|
64
|
+
fun getDevice(handle: String): YubiKeyDevice {
|
|
65
|
+
return devices[handle] ?: throw IllegalArgumentException("Unknown device handle: $handle")
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
@JvmStatic
|
|
69
|
+
fun removeDevice(handle: String) {
|
|
70
|
+
devices.remove(handle)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
@JvmStatic
|
|
74
|
+
fun listDevices(): List<Pair<String, YubiKeyDevice>> {
|
|
75
|
+
return devices.toList()
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
@JvmStatic
|
|
79
|
+
fun putConnection(connection: YubiKeyConnection): String {
|
|
80
|
+
val handle = UUID.randomUUID().toString()
|
|
81
|
+
connections[handle] = connection
|
|
82
|
+
return handle
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
@JvmStatic
|
|
86
|
+
fun getConnection(handle: String): YubiKeyConnection {
|
|
87
|
+
return connections[handle]
|
|
88
|
+
?: throw IllegalArgumentException("Unknown connection handle: $handle")
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
@JvmStatic
|
|
92
|
+
fun removeConnection(handle: String) {
|
|
93
|
+
connections.remove(handle)?.close()
|
|
94
|
+
// Wakes up the requestConnection() call still blocked on this handle's
|
|
95
|
+
// latch below, letting its try-with-resources finally close.
|
|
96
|
+
connectionReleaseLatches.remove(handle)?.countDown()
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
@JvmStatic
|
|
100
|
+
fun closeAllConnections() {
|
|
101
|
+
connections.values.forEach { try { it.close() } catch (_: IOException) {} }
|
|
102
|
+
connections.clear()
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
@JvmStatic
|
|
106
|
+
fun setUsbConfiguration(config: UsbConfiguration) {
|
|
107
|
+
usbConfiguration = config
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
@JvmStatic
|
|
111
|
+
fun getUsbConfiguration(): UsbConfiguration = usbConfiguration
|
|
112
|
+
|
|
113
|
+
@JvmStatic
|
|
114
|
+
fun setNfcConfiguration(config: NfcConfiguration) {
|
|
115
|
+
nfcConfiguration = config
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
@JvmStatic
|
|
119
|
+
fun getNfcConfiguration(): NfcConfiguration = nfcConfiguration
|
|
120
|
+
|
|
121
|
+
// Set by YubikitCoreModule while the TurboModule instance is alive, so device
|
|
122
|
+
// attach/detach/failure can be routed to its generated emitOnYubiKeyEvent(...).
|
|
123
|
+
@Volatile
|
|
124
|
+
private var eventHandler: ((WritableMap) -> Unit)? = null
|
|
125
|
+
|
|
126
|
+
@JvmStatic
|
|
127
|
+
fun setEventHandler(handler: ((WritableMap) -> Unit)?) {
|
|
128
|
+
eventHandler = handler
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
@JvmStatic
|
|
132
|
+
fun emitDeviceAttached(handle: String, device: YubiKeyDevice) {
|
|
133
|
+
val params = Arguments.createMap().apply {
|
|
134
|
+
putString("type", "attached")
|
|
135
|
+
putMap("device", deviceToBundle(handle, device).toWritableMap())
|
|
136
|
+
}
|
|
137
|
+
eventHandler?.invoke(params)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
@JvmStatic
|
|
141
|
+
fun emitDeviceDetached(handle: String) {
|
|
142
|
+
removeDevice(handle)
|
|
143
|
+
val params = Arguments.createMap().apply {
|
|
144
|
+
putString("type", "detached")
|
|
145
|
+
putString("handle", handle)
|
|
146
|
+
}
|
|
147
|
+
eventHandler?.invoke(params)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
@JvmStatic
|
|
151
|
+
fun emitError(error: String) {
|
|
152
|
+
val params = Arguments.createMap().apply {
|
|
153
|
+
putString("type", "error")
|
|
154
|
+
putString("error", error)
|
|
155
|
+
}
|
|
156
|
+
eventHandler?.invoke(params)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
@JvmStatic
|
|
160
|
+
fun deviceToBundle(handle: String, device: YubiKeyDevice): android.os.Bundle {
|
|
161
|
+
return android.os.Bundle().apply {
|
|
162
|
+
putString("handle", handle)
|
|
163
|
+
putString("transport", device.transport.name.lowercase())
|
|
164
|
+
putStringArrayList(
|
|
165
|
+
"supportedConnections",
|
|
166
|
+
ArrayList(
|
|
167
|
+
listOfNotNull(
|
|
168
|
+
if (device.supportsConnection(SmartCardConnection::class.java)) "SmartCardConnection" else null,
|
|
169
|
+
if (device.supportsConnection(OtpConnection::class.java)) "OtpConnection" else null,
|
|
170
|
+
if (device.supportsConnection(FidoConnection::class.java)) "FidoConnection" else null
|
|
171
|
+
)
|
|
172
|
+
)
|
|
173
|
+
)
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
@JvmStatic
|
|
178
|
+
fun startUsbDiscovery() {
|
|
179
|
+
getManager().startUsbDiscovery(usbConfiguration) { device: UsbYubiKeyDevice ->
|
|
180
|
+
val handle = putDevice(device)
|
|
181
|
+
device.setOnClosed {
|
|
182
|
+
emitDeviceDetached(handle)
|
|
183
|
+
}
|
|
184
|
+
emitDeviceAttached(handle, device)
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
@JvmStatic
|
|
189
|
+
fun stopUsbDiscovery() {
|
|
190
|
+
getManager().stopUsbDiscovery()
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
@JvmStatic
|
|
194
|
+
@Throws(NfcNotAvailable::class)
|
|
195
|
+
fun startNfcDiscovery(activity: Activity) {
|
|
196
|
+
getManager().startNfcDiscovery(nfcConfiguration, activity) { device: NfcYubiKeyDevice ->
|
|
197
|
+
val handle = putDevice(device)
|
|
198
|
+
emitDeviceAttached(handle, device)
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
@JvmStatic
|
|
203
|
+
fun stopNfcDiscovery(activity: Activity) {
|
|
204
|
+
getManager().stopNfcDiscovery(activity)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Open a connection synchronously. Must not be called on the UI thread.
|
|
209
|
+
*/
|
|
210
|
+
@JvmStatic
|
|
211
|
+
fun <T : YubiKeyConnection> openConnection(
|
|
212
|
+
deviceHandle: String,
|
|
213
|
+
connectionType: Class<T>
|
|
214
|
+
): Pair<String, T> {
|
|
215
|
+
val device = getDevice(deviceHandle)
|
|
216
|
+
val connection = device.openConnection(connectionType)
|
|
217
|
+
val handle = putConnection(connection)
|
|
218
|
+
return handle to connection
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Non-OTP connections are opened by the SDK inside a try-with-resources block that
|
|
222
|
+
// closes the connection the instant device.requestConnection()'s callback returns
|
|
223
|
+
// (see UsbYubiKeyDevice.requestConnection). Since this module's requestConnection/
|
|
224
|
+
// sendApdu/closeConnection are exposed as three separate, independently-timed JS
|
|
225
|
+
// calls, the callback below is kept alive on a latch until closeConnection() (or
|
|
226
|
+
// this timeout) releases it, so the connection stays open across those calls.
|
|
227
|
+
private const val CONNECTION_HOLD_TIMEOUT_MS = 120_000L
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Open a connection asynchronously and return the handle.
|
|
231
|
+
*/
|
|
232
|
+
@JvmStatic
|
|
233
|
+
fun requestConnection(
|
|
234
|
+
deviceHandle: String,
|
|
235
|
+
connectionType: Class<out YubiKeyConnection>,
|
|
236
|
+
callback: (Result<Pair<String, YubiKeyConnection>>) -> Unit
|
|
237
|
+
) {
|
|
238
|
+
val device = getDevice(deviceHandle)
|
|
239
|
+
device.requestConnection(connectionType) { result ->
|
|
240
|
+
var handle: String? = null
|
|
241
|
+
try {
|
|
242
|
+
val connection = result.value
|
|
243
|
+
handle = putConnection(connection)
|
|
244
|
+
val releaseLatch = CountDownLatch(1)
|
|
245
|
+
connectionReleaseLatches[handle] = releaseLatch
|
|
246
|
+
callback(Result.success(handle to connection))
|
|
247
|
+
releaseLatch.await(CONNECTION_HOLD_TIMEOUT_MS, TimeUnit.MILLISECONDS)
|
|
248
|
+
} catch (e: Exception) {
|
|
249
|
+
callback(Result.failure(e))
|
|
250
|
+
} finally {
|
|
251
|
+
// No-op if closeConnection() already removed it; guards against the
|
|
252
|
+
// timeout case where the JS side never explicitly released it.
|
|
253
|
+
handle?.let { connections.remove(it)?.close(); connectionReleaseLatches.remove(it) }
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Run a synchronous operation on a connection inside a requestConnection callback.
|
|
260
|
+
* This is the preferred pattern because it automatically closes the connection.
|
|
261
|
+
*/
|
|
262
|
+
@JvmStatic
|
|
263
|
+
fun <T : YubiKeyConnection, R> withConnection(
|
|
264
|
+
deviceHandle: String,
|
|
265
|
+
connectionType: Class<T>,
|
|
266
|
+
timeoutMs: Long = 30000,
|
|
267
|
+
block: (T) -> R
|
|
268
|
+
): R {
|
|
269
|
+
val device = getDevice(deviceHandle)
|
|
270
|
+
val latch = CountDownLatch(1)
|
|
271
|
+
val resultRef = AtomicReference<Result<R>>()
|
|
272
|
+
|
|
273
|
+
device.requestConnection(connectionType) { result ->
|
|
274
|
+
try {
|
|
275
|
+
val connection = result.value
|
|
276
|
+
connection.use {
|
|
277
|
+
resultRef.set(Result.success(block(connection)))
|
|
278
|
+
}
|
|
279
|
+
} catch (e: Exception) {
|
|
280
|
+
resultRef.set(Result.failure(e))
|
|
281
|
+
} finally {
|
|
282
|
+
latch.countDown()
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (!latch.await(timeoutMs, TimeUnit.MILLISECONDS)) {
|
|
287
|
+
throw IOException("Timeout waiting for YubiKey connection")
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return resultRef.get().getOrThrow()
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
@JvmStatic
|
|
294
|
+
fun <R> withSmartCard(deviceHandle: String, block: (SmartCardConnection) -> R): R {
|
|
295
|
+
return withConnection(deviceHandle, SmartCardConnection::class.java, block = block)
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
@JvmStatic
|
|
299
|
+
fun <R> withOtp(deviceHandle: String, block: (OtpConnection) -> R): R {
|
|
300
|
+
return withConnection(deviceHandle, OtpConnection::class.java, block = block)
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
@JvmStatic
|
|
304
|
+
fun <R> withFido(deviceHandle: String, block: (FidoConnection) -> R): R {
|
|
305
|
+
return withConnection(deviceHandle, FidoConnection::class.java, block = block)
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Opens whichever of SmartCard/Otp/Fido the device actually supports, in that
|
|
310
|
+
* preference order. Several sessions (ManagementSession, Ctap2Session) accept more
|
|
311
|
+
* than one connection type, and hardcoding a single type breaks devices/transports
|
|
312
|
+
* that don't support it (e.g. NFC devices only ever support SmartCardConnection).
|
|
313
|
+
*/
|
|
314
|
+
@JvmStatic
|
|
315
|
+
fun <R> withAnyConnection(
|
|
316
|
+
deviceHandle: String,
|
|
317
|
+
preferredOrder: List<Class<out YubiKeyConnection>>,
|
|
318
|
+
block: (YubiKeyConnection) -> R
|
|
319
|
+
): R {
|
|
320
|
+
val device = getDevice(deviceHandle)
|
|
321
|
+
val connectionType = preferredOrder.firstOrNull { device.supportsConnection(it) }
|
|
322
|
+
?: throw IllegalArgumentException("Device does not support any of: ${preferredOrder.map { it.simpleName }}")
|
|
323
|
+
return when (connectionType) {
|
|
324
|
+
SmartCardConnection::class.java -> withConnection(deviceHandle, SmartCardConnection::class.java) { block(it) }
|
|
325
|
+
OtpConnection::class.java -> withConnection(deviceHandle, OtpConnection::class.java) { block(it) }
|
|
326
|
+
FidoConnection::class.java -> withConnection(deviceHandle, FidoConnection::class.java) { block(it) }
|
|
327
|
+
else -> throw IllegalArgumentException("Unsupported connection type: $connectionType")
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|