@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.
Files changed (144) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +126 -0
  3. package/RNYubikit.podspec +22 -0
  4. package/android/build.gradle +63 -0
  5. package/android/src/main/AndroidManifest.xml +9 -0
  6. package/android/src/main/java/com/yubikit/YubiKitManagerHolder.kt +330 -0
  7. package/android/src/main/java/com/yubikit/YubikitCoreModule.kt +142 -0
  8. package/android/src/main/java/com/yubikit/YubikitFidoModule.kt +418 -0
  9. package/android/src/main/java/com/yubikit/YubikitManagementModule.kt +132 -0
  10. package/android/src/main/java/com/yubikit/YubikitOathModule.kt +224 -0
  11. package/android/src/main/java/com/yubikit/YubikitOpenPgpModule.kt +317 -0
  12. package/android/src/main/java/com/yubikit/YubikitPackage.kt +44 -0
  13. package/android/src/main/java/com/yubikit/YubikitPivModule.kt +310 -0
  14. package/android/src/main/java/com/yubikit/YubikitSupportModule.kt +108 -0
  15. package/android/src/main/java/com/yubikit/YubikitYubiOtpModule.kt +248 -0
  16. package/android/src/main/java/com/yubikit/utils/YubikitUtils.kt +321 -0
  17. package/ios/YubikitCore.h +5 -0
  18. package/ios/YubikitCore.mm +101 -0
  19. package/ios/YubikitDeviceInfoHelper.h +13 -0
  20. package/ios/YubikitDeviceInfoHelper.mm +110 -0
  21. package/ios/YubikitFido.h +5 -0
  22. package/ios/YubikitFido.mm +373 -0
  23. package/ios/YubikitManagement.h +5 -0
  24. package/ios/YubikitManagement.mm +153 -0
  25. package/ios/YubikitManager.h +27 -0
  26. package/ios/YubikitManager.m +169 -0
  27. package/ios/YubikitOath.h +5 -0
  28. package/ios/YubikitOath.mm +583 -0
  29. package/ios/YubikitOpenPgp.h +5 -0
  30. package/ios/YubikitOpenPgp.mm +210 -0
  31. package/ios/YubikitPiv.h +5 -0
  32. package/ios/YubikitPiv.mm +813 -0
  33. package/ios/YubikitSupport.h +5 -0
  34. package/ios/YubikitSupport.mm +58 -0
  35. package/ios/YubikitYubiOtp.h +5 -0
  36. package/ios/YubikitYubiOtp.mm +120 -0
  37. package/lib/module/core.js +34 -0
  38. package/lib/module/core.js.map +1 -0
  39. package/lib/module/fido.js +40 -0
  40. package/lib/module/fido.js.map +1 -0
  41. package/lib/module/hooks/index.js +5 -0
  42. package/lib/module/hooks/index.js.map +1 -0
  43. package/lib/module/hooks/use-yubikey.js +12 -0
  44. package/lib/module/hooks/use-yubikey.js.map +1 -0
  45. package/lib/module/hooks/yubikey-provider.js +85 -0
  46. package/lib/module/hooks/yubikey-provider.js.map +1 -0
  47. package/lib/module/index.js +12 -0
  48. package/lib/module/index.js.map +1 -0
  49. package/lib/module/management.js +17 -0
  50. package/lib/module/management.js.map +1 -0
  51. package/lib/module/oath.js +53 -0
  52. package/lib/module/oath.js.map +1 -0
  53. package/lib/module/openpgp.js +89 -0
  54. package/lib/module/openpgp.js.map +1 -0
  55. package/lib/module/package.json +1 -0
  56. package/lib/module/piv.js +71 -0
  57. package/lib/module/piv.js.map +1 -0
  58. package/lib/module/specs/NativeYubikitCore.js +10 -0
  59. package/lib/module/specs/NativeYubikitCore.js.map +1 -0
  60. package/lib/module/specs/NativeYubikitFido.js +5 -0
  61. package/lib/module/specs/NativeYubikitFido.js.map +1 -0
  62. package/lib/module/specs/NativeYubikitManagement.js +5 -0
  63. package/lib/module/specs/NativeYubikitManagement.js.map +1 -0
  64. package/lib/module/specs/NativeYubikitOath.js +5 -0
  65. package/lib/module/specs/NativeYubikitOath.js.map +1 -0
  66. package/lib/module/specs/NativeYubikitOpenPgp.js +5 -0
  67. package/lib/module/specs/NativeYubikitOpenPgp.js.map +1 -0
  68. package/lib/module/specs/NativeYubikitPiv.js +5 -0
  69. package/lib/module/specs/NativeYubikitPiv.js.map +1 -0
  70. package/lib/module/specs/NativeYubikitSupport.js +5 -0
  71. package/lib/module/specs/NativeYubikitSupport.js.map +1 -0
  72. package/lib/module/specs/NativeYubikitYubiOtp.js +5 -0
  73. package/lib/module/specs/NativeYubikitYubiOtp.js.map +1 -0
  74. package/lib/module/support.js +11 -0
  75. package/lib/module/support.js.map +1 -0
  76. package/lib/module/types.js +2 -0
  77. package/lib/module/types.js.map +1 -0
  78. package/lib/module/yubiotp.js +32 -0
  79. package/lib/module/yubiotp.js.map +1 -0
  80. package/lib/typescript/package.json +1 -0
  81. package/lib/typescript/src/core.d.ts +14 -0
  82. package/lib/typescript/src/core.d.ts.map +1 -0
  83. package/lib/typescript/src/fido.d.ts +24 -0
  84. package/lib/typescript/src/fido.d.ts.map +1 -0
  85. package/lib/typescript/src/hooks/index.d.ts +3 -0
  86. package/lib/typescript/src/hooks/index.d.ts.map +1 -0
  87. package/lib/typescript/src/hooks/use-yubikey.d.ts +3 -0
  88. package/lib/typescript/src/hooks/use-yubikey.d.ts.map +1 -0
  89. package/lib/typescript/src/hooks/yubikey-provider.d.ts +23 -0
  90. package/lib/typescript/src/hooks/yubikey-provider.d.ts.map +1 -0
  91. package/lib/typescript/src/index.d.ts +10 -0
  92. package/lib/typescript/src/index.d.ts.map +1 -0
  93. package/lib/typescript/src/management.d.ts +8 -0
  94. package/lib/typescript/src/management.d.ts.map +1 -0
  95. package/lib/typescript/src/oath.d.ts +20 -0
  96. package/lib/typescript/src/oath.d.ts.map +1 -0
  97. package/lib/typescript/src/openpgp.d.ts +35 -0
  98. package/lib/typescript/src/openpgp.d.ts.map +1 -0
  99. package/lib/typescript/src/piv.d.ts +26 -0
  100. package/lib/typescript/src/piv.d.ts.map +1 -0
  101. package/lib/typescript/src/specs/NativeYubikitCore.d.ts +49 -0
  102. package/lib/typescript/src/specs/NativeYubikitCore.d.ts.map +1 -0
  103. package/lib/typescript/src/specs/NativeYubikitFido.d.ts +121 -0
  104. package/lib/typescript/src/specs/NativeYubikitFido.d.ts.map +1 -0
  105. package/lib/typescript/src/specs/NativeYubikitManagement.d.ts +53 -0
  106. package/lib/typescript/src/specs/NativeYubikitManagement.d.ts.map +1 -0
  107. package/lib/typescript/src/specs/NativeYubikitOath.d.ts +70 -0
  108. package/lib/typescript/src/specs/NativeYubikitOath.d.ts.map +1 -0
  109. package/lib/typescript/src/specs/NativeYubikitOpenPgp.d.ts +53 -0
  110. package/lib/typescript/src/specs/NativeYubikitOpenPgp.d.ts.map +1 -0
  111. package/lib/typescript/src/specs/NativeYubikitPiv.d.ts +47 -0
  112. package/lib/typescript/src/specs/NativeYubikitPiv.d.ts.map +1 -0
  113. package/lib/typescript/src/specs/NativeYubikitSupport.d.ts +49 -0
  114. package/lib/typescript/src/specs/NativeYubikitSupport.d.ts.map +1 -0
  115. package/lib/typescript/src/specs/NativeYubikitYubiOtp.d.ts +45 -0
  116. package/lib/typescript/src/specs/NativeYubikitYubiOtp.d.ts.map +1 -0
  117. package/lib/typescript/src/support.d.ts +6 -0
  118. package/lib/typescript/src/support.d.ts.map +1 -0
  119. package/lib/typescript/src/types.d.ts +293 -0
  120. package/lib/typescript/src/types.d.ts.map +1 -0
  121. package/lib/typescript/src/yubiotp.d.ts +13 -0
  122. package/lib/typescript/src/yubiotp.d.ts.map +1 -0
  123. package/package.json +181 -0
  124. package/src/core.ts +58 -0
  125. package/src/fido.ts +112 -0
  126. package/src/hooks/index.tsx +2 -0
  127. package/src/hooks/use-yubikey.tsx +10 -0
  128. package/src/hooks/yubikey-provider.tsx +146 -0
  129. package/src/index.tsx +10 -0
  130. package/src/management.ts +44 -0
  131. package/src/oath.ts +134 -0
  132. package/src/openpgp.ts +220 -0
  133. package/src/piv.ts +193 -0
  134. package/src/specs/NativeYubikitCore.ts +68 -0
  135. package/src/specs/NativeYubikitFido.ts +149 -0
  136. package/src/specs/NativeYubikitManagement.ts +78 -0
  137. package/src/specs/NativeYubikitOath.ts +110 -0
  138. package/src/specs/NativeYubikitOpenPgp.ts +148 -0
  139. package/src/specs/NativeYubikitPiv.ts +108 -0
  140. package/src/specs/NativeYubikitSupport.ts +61 -0
  141. package/src/specs/NativeYubikitYubiOtp.ts +82 -0
  142. package/src/support.ts +18 -0
  143. package/src/types.ts +404 -0
  144. 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
+ }