@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
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
package com.yubikit
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.Arguments
|
|
4
|
+
import com.facebook.react.bridge.Promise
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.yubico.yubikit.piv.PivSession
|
|
7
|
+
import com.yubikit.utils.YubikitUtils.base64Decode
|
|
8
|
+
import com.yubikit.utils.YubikitUtils.base64Encode
|
|
9
|
+
import com.yubikit.utils.YubikitUtils.keyTypeFromString
|
|
10
|
+
import com.yubikit.utils.YubikitUtils.managementKeyTypeFromString
|
|
11
|
+
import com.yubikit.utils.YubikitUtils.pinPolicyFromString
|
|
12
|
+
import com.yubikit.utils.YubikitUtils.slotFromString
|
|
13
|
+
import com.yubikit.utils.YubikitUtils.touchPolicyFromString
|
|
14
|
+
import kotlinx.coroutines.CoroutineScope
|
|
15
|
+
import kotlinx.coroutines.Dispatchers
|
|
16
|
+
import kotlinx.coroutines.launch
|
|
17
|
+
|
|
18
|
+
@Suppress("unused")
|
|
19
|
+
class YubikitPivModule(reactContext: ReactApplicationContext) :
|
|
20
|
+
NativeYubikitPivSpec(reactContext) {
|
|
21
|
+
|
|
22
|
+
private val moduleScope = CoroutineScope(Dispatchers.IO)
|
|
23
|
+
|
|
24
|
+
override fun getName(): String = NAME
|
|
25
|
+
|
|
26
|
+
private inline fun <R> withPivSession(
|
|
27
|
+
deviceHandle: String,
|
|
28
|
+
promise: Promise,
|
|
29
|
+
crossinline block: (PivSession) -> R
|
|
30
|
+
) {
|
|
31
|
+
moduleScope.launch {
|
|
32
|
+
try {
|
|
33
|
+
val result = YubiKitManagerHolder.withSmartCard(deviceHandle) { connection ->
|
|
34
|
+
PivSession(connection).use { session ->
|
|
35
|
+
block(session)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
@Suppress("UNCHECKED_CAST")
|
|
39
|
+
when (result) {
|
|
40
|
+
is Unit -> promise.resolve(null)
|
|
41
|
+
else -> promise.resolve(result)
|
|
42
|
+
}
|
|
43
|
+
} catch (e: Exception) {
|
|
44
|
+
promise.reject("PIV_ERROR", e.message, e)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
override fun reset(deviceHandle: String, promise: Promise) {
|
|
50
|
+
withPivSession(deviceHandle, promise) { it.reset() }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
override fun getSerialNumber(deviceHandle: String, promise: Promise) {
|
|
54
|
+
withPivSession(deviceHandle, promise) { it.serialNumber }
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
override fun authenticate(deviceHandle: String, managementKey: String, promise: Promise) {
|
|
58
|
+
withPivSession(deviceHandle, promise) {
|
|
59
|
+
it.authenticate(base64Decode(managementKey) ?: byteArrayOf())
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
override fun setManagementKey(
|
|
64
|
+
deviceHandle: String,
|
|
65
|
+
keyType: String,
|
|
66
|
+
managementKey: String,
|
|
67
|
+
requireTouch: Boolean,
|
|
68
|
+
promise: Promise
|
|
69
|
+
) {
|
|
70
|
+
withPivSession(deviceHandle, promise) {
|
|
71
|
+
it.setManagementKey(
|
|
72
|
+
managementKeyTypeFromString(keyType),
|
|
73
|
+
base64Decode(managementKey) ?: byteArrayOf(),
|
|
74
|
+
requireTouch
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
override fun verifyPin(deviceHandle: String, pin: String, promise: Promise) {
|
|
80
|
+
withPivSession(deviceHandle, promise) { it.verifyPin(pin.toCharArray()) }
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
override fun getPinAttempts(deviceHandle: String, promise: Promise) {
|
|
84
|
+
withPivSession(deviceHandle, promise) { it.pinAttempts }
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
override fun changePin(
|
|
88
|
+
deviceHandle: String,
|
|
89
|
+
oldPin: String,
|
|
90
|
+
newPin: String,
|
|
91
|
+
promise: Promise
|
|
92
|
+
) {
|
|
93
|
+
withPivSession(deviceHandle, promise) {
|
|
94
|
+
it.changePin(oldPin.toCharArray(), newPin.toCharArray())
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
override fun changePuk(
|
|
99
|
+
deviceHandle: String,
|
|
100
|
+
oldPuk: String,
|
|
101
|
+
newPuk: String,
|
|
102
|
+
promise: Promise
|
|
103
|
+
) {
|
|
104
|
+
withPivSession(deviceHandle, promise) {
|
|
105
|
+
it.changePuk(oldPuk.toCharArray(), newPuk.toCharArray())
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
override fun unblockPin(
|
|
110
|
+
deviceHandle: String,
|
|
111
|
+
puk: String,
|
|
112
|
+
newPin: String,
|
|
113
|
+
promise: Promise
|
|
114
|
+
) {
|
|
115
|
+
withPivSession(deviceHandle, promise) {
|
|
116
|
+
it.unblockPin(puk.toCharArray(), newPin.toCharArray())
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
override fun setPinAttempts(
|
|
121
|
+
deviceHandle: String,
|
|
122
|
+
pinAttempts: Double,
|
|
123
|
+
pukAttempts: Double,
|
|
124
|
+
promise: Promise
|
|
125
|
+
) {
|
|
126
|
+
withPivSession(deviceHandle, promise) {
|
|
127
|
+
it.setPinAttempts(pinAttempts.toInt(), pukAttempts.toInt())
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
override fun getPinMetadata(deviceHandle: String, promise: Promise) {
|
|
132
|
+
moduleScope.launch {
|
|
133
|
+
try {
|
|
134
|
+
val meta = YubiKitManagerHolder.withSmartCard(deviceHandle) { connection ->
|
|
135
|
+
PivSession(connection).use { session ->
|
|
136
|
+
session.pinMetadata
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
promise.resolve(Arguments.createMap().apply {
|
|
140
|
+
putInt("attemptsRemaining", meta.attemptsRemaining)
|
|
141
|
+
})
|
|
142
|
+
} catch (e: Exception) {
|
|
143
|
+
promise.reject("PIV_ERROR", e.message, e)
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
override fun getPukMetadata(deviceHandle: String, promise: Promise) {
|
|
149
|
+
moduleScope.launch {
|
|
150
|
+
try {
|
|
151
|
+
val meta = YubiKitManagerHolder.withSmartCard(deviceHandle) { connection ->
|
|
152
|
+
PivSession(connection).use { session ->
|
|
153
|
+
session.pukMetadata
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
promise.resolve(Arguments.createMap().apply {
|
|
157
|
+
putInt("attemptsRemaining", meta.attemptsRemaining)
|
|
158
|
+
})
|
|
159
|
+
} catch (e: Exception) {
|
|
160
|
+
promise.reject("PIV_ERROR", e.message, e)
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
override fun getManagementKeyMetadata(deviceHandle: String, promise: Promise) {
|
|
166
|
+
moduleScope.launch {
|
|
167
|
+
try {
|
|
168
|
+
val meta = YubiKitManagerHolder.withSmartCard(deviceHandle) { connection ->
|
|
169
|
+
PivSession(connection).use { session ->
|
|
170
|
+
session.managementKeyMetadata
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
promise.resolve(Arguments.createMap().apply {
|
|
174
|
+
putString("keyType", com.yubikit.utils.YubikitUtils.managementKeyTypeToString(meta.keyType))
|
|
175
|
+
putBoolean("defaultValue", meta.isDefaultValue)
|
|
176
|
+
// The JS-facing type is `touchRequired: boolean`, not the raw TouchPolicy
|
|
177
|
+
// string used by getSlotMetadata - DEFAULT/NEVER both mean "not required".
|
|
178
|
+
putBoolean(
|
|
179
|
+
"touchRequired",
|
|
180
|
+
meta.touchPolicy != com.yubico.yubikit.piv.TouchPolicy.DEFAULT &&
|
|
181
|
+
meta.touchPolicy != com.yubico.yubikit.piv.TouchPolicy.NEVER
|
|
182
|
+
)
|
|
183
|
+
})
|
|
184
|
+
} catch (e: Exception) {
|
|
185
|
+
promise.reject("PIV_ERROR", e.message, e)
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
override fun getSlotMetadata(deviceHandle: String, slot: String, promise: Promise) {
|
|
191
|
+
moduleScope.launch {
|
|
192
|
+
try {
|
|
193
|
+
val meta = YubiKitManagerHolder.withSmartCard(deviceHandle) { connection ->
|
|
194
|
+
PivSession(connection).use { session ->
|
|
195
|
+
session.getSlotMetadata(slotFromString(slot))
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
promise.resolve(Arguments.createMap().apply {
|
|
199
|
+
putString("keyType", com.yubikit.utils.YubikitUtils.keyTypeToString(meta.keyType))
|
|
200
|
+
putString("pinPolicy", com.yubikit.utils.YubikitUtils.pinPolicyToString(meta.pinPolicy))
|
|
201
|
+
putString("touchPolicy", com.yubikit.utils.YubikitUtils.touchPolicyToString(meta.touchPolicy))
|
|
202
|
+
putBoolean("generated", meta.isGenerated)
|
|
203
|
+
meta.publicKeyValues?.let { putString("publicKey", base64Encode(it.encoded)) }
|
|
204
|
+
})
|
|
205
|
+
} catch (e: Exception) {
|
|
206
|
+
promise.reject("PIV_ERROR", e.message, e)
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
override fun getCertificate(deviceHandle: String, slot: String, promise: Promise) {
|
|
212
|
+
withPivSession(deviceHandle, promise) {
|
|
213
|
+
base64Encode(it.getCertificate(slotFromString(slot)).encoded)
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
override fun putCertificate(
|
|
218
|
+
deviceHandle: String,
|
|
219
|
+
slot: String,
|
|
220
|
+
certificate: String,
|
|
221
|
+
compress: Boolean?,
|
|
222
|
+
promise: Promise
|
|
223
|
+
) {
|
|
224
|
+
withPivSession(deviceHandle, promise) {
|
|
225
|
+
val cert = java.security.cert.CertificateFactory.getInstance("X.509")
|
|
226
|
+
.generateCertificate(
|
|
227
|
+
java.io.ByteArrayInputStream(base64Decode(certificate) ?: byteArrayOf())
|
|
228
|
+
) as java.security.cert.X509Certificate
|
|
229
|
+
if (compress == true) {
|
|
230
|
+
it.putCertificate(slotFromString(slot), cert, true)
|
|
231
|
+
} else {
|
|
232
|
+
it.putCertificate(slotFromString(slot), cert)
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
override fun deleteCertificate(deviceHandle: String, slot: String, promise: Promise) {
|
|
238
|
+
withPivSession(deviceHandle, promise) { it.deleteCertificate(slotFromString(slot)) }
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
override fun attestKey(deviceHandle: String, slot: String, promise: Promise) {
|
|
242
|
+
withPivSession(deviceHandle, promise) {
|
|
243
|
+
base64Encode(it.attestKey(slotFromString(slot)).encoded)
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
override fun generateKey(
|
|
248
|
+
deviceHandle: String,
|
|
249
|
+
slot: String,
|
|
250
|
+
keyType: String,
|
|
251
|
+
pinPolicy: String,
|
|
252
|
+
touchPolicy: String,
|
|
253
|
+
promise: Promise
|
|
254
|
+
) {
|
|
255
|
+
withPivSession(deviceHandle, promise) {
|
|
256
|
+
val publicKey = it.generateKey(
|
|
257
|
+
slotFromString(slot),
|
|
258
|
+
keyTypeFromString(keyType),
|
|
259
|
+
pinPolicyFromString(pinPolicy),
|
|
260
|
+
touchPolicyFromString(touchPolicy)
|
|
261
|
+
)
|
|
262
|
+
base64Encode(publicKey.encoded)
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
override fun deleteKey(deviceHandle: String, slot: String, promise: Promise) {
|
|
267
|
+
withPivSession(deviceHandle, promise) { it.deleteKey(slotFromString(slot)) }
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
override fun rawSignOrDecrypt(
|
|
271
|
+
deviceHandle: String,
|
|
272
|
+
slot: String,
|
|
273
|
+
keyType: String,
|
|
274
|
+
payload: String,
|
|
275
|
+
promise: Promise
|
|
276
|
+
) {
|
|
277
|
+
withPivSession(deviceHandle, promise) {
|
|
278
|
+
base64Encode(
|
|
279
|
+
it.rawSignOrDecrypt(
|
|
280
|
+
slotFromString(slot),
|
|
281
|
+
keyTypeFromString(keyType),
|
|
282
|
+
base64Decode(payload) ?: byteArrayOf()
|
|
283
|
+
)
|
|
284
|
+
)
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
override fun getBioMetadata(deviceHandle: String, promise: Promise) {
|
|
289
|
+
moduleScope.launch {
|
|
290
|
+
try {
|
|
291
|
+
val meta = YubiKitManagerHolder.withSmartCard(deviceHandle) { connection ->
|
|
292
|
+
PivSession(connection).use { session ->
|
|
293
|
+
session.bioMetadata
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
promise.resolve(Arguments.createMap().apply {
|
|
297
|
+
putInt("attemptsRemaining", meta.attemptsRemaining)
|
|
298
|
+
putBoolean("configured", meta.isConfigured)
|
|
299
|
+
putBoolean("temporaryPin", meta.hasTemporaryPin())
|
|
300
|
+
})
|
|
301
|
+
} catch (e: Exception) {
|
|
302
|
+
promise.reject("PIV_ERROR", e.message, e)
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
companion object {
|
|
308
|
+
const val NAME = "YubikitPiv"
|
|
309
|
+
}
|
|
310
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
package com.yubikit
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.Promise
|
|
4
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
5
|
+
import com.yubico.yubikit.android.transport.usb.UsbYubiKeyDevice
|
|
6
|
+
import com.yubico.yubikit.core.Transport
|
|
7
|
+
import com.yubico.yubikit.core.UsbPid
|
|
8
|
+
import com.yubico.yubikit.core.YubiKeyType
|
|
9
|
+
import com.yubico.yubikit.core.fido.FidoConnection
|
|
10
|
+
import com.yubico.yubikit.core.otp.OtpConnection
|
|
11
|
+
import com.yubico.yubikit.core.smartcard.SmartCardConnection
|
|
12
|
+
import com.yubico.yubikit.support.DeviceUtil
|
|
13
|
+
import com.yubikit.utils.YubikitUtils.deviceInfoToMap
|
|
14
|
+
import com.yubikit.utils.YubikitUtils.formFactorToString
|
|
15
|
+
import com.yubikit.utils.YubikitUtils.versionToMap
|
|
16
|
+
import kotlinx.coroutines.CoroutineScope
|
|
17
|
+
import kotlinx.coroutines.Dispatchers
|
|
18
|
+
import kotlinx.coroutines.launch
|
|
19
|
+
|
|
20
|
+
@Suppress("unused")
|
|
21
|
+
class YubikitSupportModule(reactContext: ReactApplicationContext) :
|
|
22
|
+
NativeYubikitSupportSpec(reactContext) {
|
|
23
|
+
|
|
24
|
+
private val moduleScope = CoroutineScope(Dispatchers.IO)
|
|
25
|
+
|
|
26
|
+
override fun getName(): String = NAME
|
|
27
|
+
|
|
28
|
+
override fun readInfo(deviceHandle: String, pid: Double?, promise: Promise) {
|
|
29
|
+
moduleScope.launch {
|
|
30
|
+
try {
|
|
31
|
+
val device = YubiKitManagerHolder.getDevice(deviceHandle)
|
|
32
|
+
// The USB PID is required by DeviceUtil.readInfo whenever the connection is
|
|
33
|
+
// over USB. Prefer an explicitly supplied pid, but fall back to the one the
|
|
34
|
+
// Android USB device itself already knows, since callers shouldn't need to
|
|
35
|
+
// plumb this Android-specific detail through the JS API.
|
|
36
|
+
val usbPid = pid?.toInt()?.let { UsbPid.fromValue(it) }
|
|
37
|
+
?: (device as? UsbYubiKeyDevice)?.pid
|
|
38
|
+
val info = when {
|
|
39
|
+
device.supportsConnection(SmartCardConnection::class.java) ->
|
|
40
|
+
YubiKitManagerHolder.withSmartCard(deviceHandle) { connection ->
|
|
41
|
+
DeviceUtil.readInfo(connection, usbPid)
|
|
42
|
+
}
|
|
43
|
+
device.supportsConnection(OtpConnection::class.java) ->
|
|
44
|
+
YubiKitManagerHolder.withOtp(deviceHandle) { connection ->
|
|
45
|
+
DeviceUtil.readInfo(connection, usbPid)
|
|
46
|
+
}
|
|
47
|
+
device.supportsConnection(FidoConnection::class.java) ->
|
|
48
|
+
YubiKitManagerHolder.withFido(deviceHandle) { connection ->
|
|
49
|
+
DeviceUtil.readInfo(connection, usbPid)
|
|
50
|
+
}
|
|
51
|
+
else -> throw IllegalArgumentException("Device does not support any known connection type")
|
|
52
|
+
}
|
|
53
|
+
promise.resolve(deviceInfoToMap(info))
|
|
54
|
+
} catch (e: Exception) {
|
|
55
|
+
promise.reject("SUPPORT_ERROR", e.message, e)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
override fun getName(info: com.facebook.react.bridge.ReadableMap, keyType: String?): String {
|
|
61
|
+
val deviceInfo = parseDeviceInfo(info)
|
|
62
|
+
val ykType = keyType?.let { YubiKeyType.valueOf(it) }
|
|
63
|
+
return DeviceUtil.getName(deviceInfo, ykType)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
private fun parseDeviceInfo(map: com.facebook.react.bridge.ReadableMap): com.yubico.yubikit.management.DeviceInfo {
|
|
67
|
+
val builder = com.yubico.yubikit.management.DeviceInfo.Builder()
|
|
68
|
+
builder.version(com.yubico.yubikit.core.Version(
|
|
69
|
+
map.getMap("version")!!.getInt("major"),
|
|
70
|
+
map.getMap("version")!!.getInt("minor"),
|
|
71
|
+
map.getMap("version")!!.getInt("micro")
|
|
72
|
+
))
|
|
73
|
+
map.getString("formFactor")?.let {
|
|
74
|
+
builder.formFactor(com.yubico.yubikit.management.FormFactor.valueOf(it))
|
|
75
|
+
}
|
|
76
|
+
if (map.hasKey("serialNumber") && !map.isNull("serialNumber")) {
|
|
77
|
+
builder.serialNumber(map.getInt("serialNumber"))
|
|
78
|
+
}
|
|
79
|
+
builder.isLocked(map.getBoolean("isLocked"))
|
|
80
|
+
builder.isFips(map.getBoolean("isFips"))
|
|
81
|
+
if (map.hasKey("isSky") && !map.isNull("isSky")) {
|
|
82
|
+
builder.isSky(map.getBoolean("isSky"))
|
|
83
|
+
}
|
|
84
|
+
if (map.hasKey("pinComplexity") && !map.isNull("pinComplexity")) {
|
|
85
|
+
builder.pinComplexity(map.getBoolean("pinComplexity"))
|
|
86
|
+
}
|
|
87
|
+
// getName() relies on hasTransport(Transport.NFC)/(USB), which in turn is just
|
|
88
|
+
// "does the map below contain this key" - if this isn't reconstructed here,
|
|
89
|
+
// hasTransport() is always false for every transport and getName() can never
|
|
90
|
+
// report NFC-capable devices correctly, regardless of the real hardware.
|
|
91
|
+
val supportedCapabilities = mutableMapOf<Transport, Int>()
|
|
92
|
+
map.getMap("supportedCapabilities")?.let { caps ->
|
|
93
|
+
if (caps.hasKey("usb") && !caps.isNull("usb")) {
|
|
94
|
+
supportedCapabilities[Transport.USB] = caps.getInt("usb")
|
|
95
|
+
}
|
|
96
|
+
if (caps.hasKey("nfc") && !caps.isNull("nfc")) {
|
|
97
|
+
supportedCapabilities[Transport.NFC] = caps.getInt("nfc")
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
builder.supportedCapabilities(supportedCapabilities)
|
|
101
|
+
// DeviceInfo.Builder may not expose all setters; this is a best-effort reconstruction.
|
|
102
|
+
return builder.build()
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
companion object {
|
|
106
|
+
const val NAME = "YubikitSupport"
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
package com.yubikit
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.Arguments
|
|
4
|
+
import com.facebook.react.bridge.Promise
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.bridge.ReadableMap
|
|
7
|
+
import com.yubico.yubikit.yubiotp.ConfigurationState
|
|
8
|
+
import com.yubico.yubikit.yubiotp.HmacSha1SlotConfiguration
|
|
9
|
+
import com.yubico.yubikit.yubiotp.HotpSlotConfiguration
|
|
10
|
+
import com.yubico.yubikit.yubiotp.Slot
|
|
11
|
+
import com.yubico.yubikit.yubiotp.SlotConfiguration
|
|
12
|
+
import com.yubico.yubikit.yubiotp.StaticPasswordSlotConfiguration
|
|
13
|
+
import com.yubico.yubikit.yubiotp.StaticTicketSlotConfiguration
|
|
14
|
+
import com.yubico.yubikit.yubiotp.UpdateConfiguration
|
|
15
|
+
import com.yubico.yubikit.yubiotp.YubiOtpSession
|
|
16
|
+
import com.yubico.yubikit.yubiotp.YubiOtpSlotConfiguration
|
|
17
|
+
import com.yubikit.utils.YubikitUtils.base64Decode
|
|
18
|
+
import com.yubikit.utils.YubikitUtils.base64Encode
|
|
19
|
+
import com.yubikit.utils.YubikitUtils.versionToMap
|
|
20
|
+
import kotlinx.coroutines.CoroutineScope
|
|
21
|
+
import kotlinx.coroutines.Dispatchers
|
|
22
|
+
import kotlinx.coroutines.launch
|
|
23
|
+
|
|
24
|
+
@Suppress("unused")
|
|
25
|
+
class YubikitYubiOtpModule(reactContext: ReactApplicationContext) :
|
|
26
|
+
NativeYubikitYubiOtpSpec(reactContext) {
|
|
27
|
+
|
|
28
|
+
private val moduleScope = CoroutineScope(Dispatchers.IO)
|
|
29
|
+
|
|
30
|
+
override fun getName(): String = NAME
|
|
31
|
+
|
|
32
|
+
private inline fun <R> withYubiOtpSession(
|
|
33
|
+
deviceHandle: String,
|
|
34
|
+
promise: Promise,
|
|
35
|
+
crossinline block: (YubiOtpSession) -> R
|
|
36
|
+
) {
|
|
37
|
+
moduleScope.launch {
|
|
38
|
+
try {
|
|
39
|
+
val device = YubiKitManagerHolder.getDevice(deviceHandle)
|
|
40
|
+
val result = when {
|
|
41
|
+
device.supportsConnection(com.yubico.yubikit.core.smartcard.SmartCardConnection::class.java) -> {
|
|
42
|
+
YubiKitManagerHolder.withSmartCard(deviceHandle) { connection ->
|
|
43
|
+
YubiOtpSession(connection).use { session -> block(session) }
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
device.supportsConnection(com.yubico.yubikit.core.otp.OtpConnection::class.java) -> {
|
|
47
|
+
YubiKitManagerHolder.withOtp(deviceHandle) { connection ->
|
|
48
|
+
YubiOtpSession(connection).use { session -> block(session) }
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
else -> throw IllegalArgumentException("Device does not support OTP")
|
|
52
|
+
}
|
|
53
|
+
@Suppress("UNCHECKED_CAST")
|
|
54
|
+
when (result) {
|
|
55
|
+
is Unit -> promise.resolve(null)
|
|
56
|
+
else -> promise.resolve(result)
|
|
57
|
+
}
|
|
58
|
+
} catch (e: Exception) {
|
|
59
|
+
promise.reject("YUBIOTP_ERROR", e.message, e)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
private fun slotFromString(value: String): Slot {
|
|
65
|
+
return Slot.valueOf(value)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private fun configurationStateToMap(state: ConfigurationState): com.facebook.react.bridge.WritableMap {
|
|
69
|
+
return Arguments.createMap().apply {
|
|
70
|
+
putBoolean("slot1Configured", state.isConfigured(Slot.ONE))
|
|
71
|
+
putBoolean("slot2Configured", state.isConfigured(Slot.TWO))
|
|
72
|
+
putBoolean("slot1TouchTriggered", state.isTouchTriggered(Slot.ONE))
|
|
73
|
+
putBoolean("slot2TouchTriggered", state.isTouchTriggered(Slot.TWO))
|
|
74
|
+
putBoolean("ledInverted", state.isLedInverted)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
private fun buildSlotConfiguration(map: ReadableMap): SlotConfiguration {
|
|
79
|
+
val type = map.getString("type") ?: throw IllegalArgumentException("configuration type is required")
|
|
80
|
+
return when (type) {
|
|
81
|
+
"HmacSha1" -> {
|
|
82
|
+
val cfg = HmacSha1SlotConfiguration(base64Decode(map.getString("secret")) ?: byteArrayOf())
|
|
83
|
+
if (map.hasKey("requireTouch") && !map.isNull("requireTouch")) {
|
|
84
|
+
cfg.requireTouch(map.getBoolean("requireTouch"))
|
|
85
|
+
}
|
|
86
|
+
if (map.hasKey("lt64") && !map.isNull("lt64")) {
|
|
87
|
+
cfg.lt64(map.getBoolean("lt64"))
|
|
88
|
+
}
|
|
89
|
+
cfg
|
|
90
|
+
}
|
|
91
|
+
"HOTP" -> {
|
|
92
|
+
val cfg = HotpSlotConfiguration(base64Decode(map.getString("secret")) ?: byteArrayOf())
|
|
93
|
+
if (map.hasKey("digits") && !map.isNull("digits")) {
|
|
94
|
+
if (map.getInt("digits") == 8) cfg.digits8(true)
|
|
95
|
+
}
|
|
96
|
+
if (map.hasKey("imf") && !map.isNull("imf")) {
|
|
97
|
+
cfg.imf(map.getInt("imf"))
|
|
98
|
+
}
|
|
99
|
+
cfg
|
|
100
|
+
}
|
|
101
|
+
"StaticPassword" -> {
|
|
102
|
+
StaticPasswordSlotConfiguration(base64Decode(map.getString("scanCodes")) ?: byteArrayOf())
|
|
103
|
+
}
|
|
104
|
+
"StaticTicket" -> {
|
|
105
|
+
StaticTicketSlotConfiguration(
|
|
106
|
+
base64Decode(map.getString("fixed")) ?: byteArrayOf(),
|
|
107
|
+
base64Decode(map.getString("uid")) ?: byteArrayOf(),
|
|
108
|
+
base64Decode(map.getString("key")) ?: byteArrayOf()
|
|
109
|
+
)
|
|
110
|
+
}
|
|
111
|
+
"YubiOtp" -> {
|
|
112
|
+
YubiOtpSlotConfiguration(
|
|
113
|
+
base64Decode(map.getString("fixed")) ?: byteArrayOf(),
|
|
114
|
+
base64Decode(map.getString("uid")) ?: byteArrayOf(),
|
|
115
|
+
base64Decode(map.getString("key")) ?: byteArrayOf()
|
|
116
|
+
)
|
|
117
|
+
}
|
|
118
|
+
else -> throw IllegalArgumentException("Slot configuration type not supported for putConfiguration: $type")
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
private fun buildUpdateConfiguration(map: ReadableMap): UpdateConfiguration {
|
|
123
|
+
val type = map.getString("type") ?: throw IllegalArgumentException("configuration type is required")
|
|
124
|
+
if (type != "Update") {
|
|
125
|
+
throw IllegalArgumentException("updateConfiguration only supports Update configuration type")
|
|
126
|
+
}
|
|
127
|
+
val cfg = UpdateConfiguration()
|
|
128
|
+
if (map.hasKey("appendCr") && !map.isNull("appendCr")) {
|
|
129
|
+
cfg.appendCr(map.getBoolean("appendCr"))
|
|
130
|
+
}
|
|
131
|
+
if (map.hasKey("serialApiVisible") && !map.isNull("serialApiVisible")) {
|
|
132
|
+
cfg.serialApiVisible(map.getBoolean("serialApiVisible"))
|
|
133
|
+
}
|
|
134
|
+
if (map.hasKey("serialUsbVisible") && !map.isNull("serialUsbVisible")) {
|
|
135
|
+
cfg.serialUsbVisible(map.getBoolean("serialUsbVisible"))
|
|
136
|
+
}
|
|
137
|
+
if (map.hasKey("allowUpdate") && !map.isNull("allowUpdate")) {
|
|
138
|
+
cfg.allowUpdate(map.getBoolean("allowUpdate"))
|
|
139
|
+
}
|
|
140
|
+
if (map.hasKey("dormant") && !map.isNull("dormant")) {
|
|
141
|
+
cfg.dormant(map.getBoolean("dormant"))
|
|
142
|
+
}
|
|
143
|
+
if (map.hasKey("invertLed") && !map.isNull("invertLed")) {
|
|
144
|
+
cfg.invertLed(map.getBoolean("invertLed"))
|
|
145
|
+
}
|
|
146
|
+
return cfg
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
override fun getConfigurationState(deviceHandle: String, promise: Promise) {
|
|
150
|
+
withYubiOtpSession(deviceHandle, promise) { configurationStateToMap(it.configurationState) }
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
override fun getVersion(deviceHandle: String, promise: Promise) {
|
|
154
|
+
withYubiOtpSession(deviceHandle, promise) { versionToMap(it.version) }
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
override fun getSerialNumber(deviceHandle: String, promise: Promise) {
|
|
158
|
+
withYubiOtpSession(deviceHandle, promise) { it.serialNumber }
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
override fun swapConfigurations(deviceHandle: String, promise: Promise) {
|
|
162
|
+
withYubiOtpSession(deviceHandle, promise) { it.swapConfigurations() }
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
override fun deleteConfiguration(
|
|
166
|
+
deviceHandle: String,
|
|
167
|
+
slot: String,
|
|
168
|
+
currentAccessCode: String?,
|
|
169
|
+
promise: Promise
|
|
170
|
+
) {
|
|
171
|
+
withYubiOtpSession(deviceHandle, promise) {
|
|
172
|
+
it.deleteConfiguration(slotFromString(slot), base64Decode(currentAccessCode))
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
override fun putConfiguration(
|
|
177
|
+
deviceHandle: String,
|
|
178
|
+
slot: String,
|
|
179
|
+
configuration: ReadableMap,
|
|
180
|
+
accessCode: String?,
|
|
181
|
+
currentAccessCode: String?,
|
|
182
|
+
promise: Promise
|
|
183
|
+
) {
|
|
184
|
+
withYubiOtpSession(deviceHandle, promise) {
|
|
185
|
+
it.putConfiguration(
|
|
186
|
+
slotFromString(slot),
|
|
187
|
+
buildSlotConfiguration(configuration),
|
|
188
|
+
base64Decode(accessCode),
|
|
189
|
+
base64Decode(currentAccessCode)
|
|
190
|
+
)
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
override fun updateConfiguration(
|
|
195
|
+
deviceHandle: String,
|
|
196
|
+
slot: String,
|
|
197
|
+
configuration: ReadableMap,
|
|
198
|
+
accessCode: String?,
|
|
199
|
+
currentAccessCode: String?,
|
|
200
|
+
promise: Promise
|
|
201
|
+
) {
|
|
202
|
+
withYubiOtpSession(deviceHandle, promise) {
|
|
203
|
+
it.updateConfiguration(
|
|
204
|
+
slotFromString(slot),
|
|
205
|
+
buildUpdateConfiguration(configuration),
|
|
206
|
+
base64Decode(accessCode),
|
|
207
|
+
base64Decode(currentAccessCode)
|
|
208
|
+
)
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
override fun setNdefConfiguration(
|
|
213
|
+
deviceHandle: String,
|
|
214
|
+
slot: String,
|
|
215
|
+
uri: String?,
|
|
216
|
+
currentAccessCode: String?,
|
|
217
|
+
promise: Promise
|
|
218
|
+
) {
|
|
219
|
+
withYubiOtpSession(deviceHandle, promise) {
|
|
220
|
+
it.setNdefConfiguration(
|
|
221
|
+
slotFromString(slot),
|
|
222
|
+
uri,
|
|
223
|
+
base64Decode(currentAccessCode)
|
|
224
|
+
)
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
override fun calculateHmacSha1(
|
|
229
|
+
deviceHandle: String,
|
|
230
|
+
slot: String,
|
|
231
|
+
challenge: String,
|
|
232
|
+
promise: Promise
|
|
233
|
+
) {
|
|
234
|
+
withYubiOtpSession(deviceHandle, promise) {
|
|
235
|
+
base64Encode(
|
|
236
|
+
it.calculateHmacSha1(
|
|
237
|
+
slotFromString(slot),
|
|
238
|
+
base64Decode(challenge) ?: byteArrayOf(),
|
|
239
|
+
null
|
|
240
|
+
)
|
|
241
|
+
)
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
companion object {
|
|
246
|
+
const val NAME = "YubikitYubiOtp"
|
|
247
|
+
}
|
|
248
|
+
}
|