@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,373 @@
1
+ #import "YubikitFido.h"
2
+ #import "YubikitManager.h"
3
+ #import <YubiKit/YubiKit.h>
4
+
5
+ @implementation YubikitFido
6
+
7
+ RCT_EXPORT_MODULE(YubikitFido)
8
+
9
+ #pragma mark - Helpers
10
+
11
+ - (id<YKFConnectionProtocol>)connectionForHandle:(NSString *)deviceHandle
12
+ reject:(RCTPromiseRejectBlock)reject {
13
+ id<YKFConnectionProtocol> connection = [[YubikitManager shared] connectionForHandle:deviceHandle];
14
+ if (connection == nil) {
15
+ reject(@"CONNECTION_ERROR", @"No device found for handle", nil);
16
+ }
17
+ return connection;
18
+ }
19
+
20
+ - (nullable NSData *)base64Decode:(nullable NSString *)value {
21
+ if (value == nil) return nil;
22
+ return [[NSData alloc] initWithBase64EncodedString:value options:0];
23
+ }
24
+
25
+ - (YKFFIDO2PublicKeyCredentialDescriptor *)descriptorFromId:(NSString *)identifier
26
+ type:(NSString *)type {
27
+ YKFFIDO2PublicKeyCredentialType *credentialType = [[YKFFIDO2PublicKeyCredentialType alloc] init];
28
+ credentialType.name = type ?: @"public-key";
29
+
30
+ YKFFIDO2PublicKeyCredentialDescriptor *descriptor = [[YKFFIDO2PublicKeyCredentialDescriptor alloc] init];
31
+ descriptor.credentialId = [self base64Decode:identifier] ?: [NSData data];
32
+ descriptor.credentialType = credentialType;
33
+ return descriptor;
34
+ }
35
+
36
+ // Requirement levels other than "discouraged" are treated as required, since this
37
+ // SDK's options dictionary only accepts a plain boolean, not a three-way preference.
38
+ - (BOOL)isRequiredPreference:(nullable NSString *)preference {
39
+ return preference != nil && ![preference isEqualToString:@"discouraged"];
40
+ }
41
+
42
+ #pragma mark - Methods
43
+
44
+ - (void)getInfo:(NSString *)deviceHandle
45
+ resolve:(RCTPromiseResolveBlock)resolve
46
+ reject:(RCTPromiseRejectBlock)reject {
47
+ id<YKFConnectionProtocol> connection = [self connectionForHandle:deviceHandle reject:reject];
48
+ if (!connection) return;
49
+
50
+ [connection fido2Session:^(YKFFIDO2Session *_Nullable session, NSError *_Nullable error) {
51
+ if (error || session == nil) {
52
+ reject(@"FIDO_ERROR", error ? error.localizedDescription : @"FIDO2 session not available", error);
53
+ return;
54
+ }
55
+ [session getInfoWithCompletion:^(YKFFIDO2GetInfoResponse *_Nullable response, NSError *_Nullable error) {
56
+ if (error || response == nil) {
57
+ reject(@"FIDO_ERROR", error ? error.localizedDescription : @"Get info failed", error);
58
+ return;
59
+ }
60
+
61
+ NSMutableDictionary *result = [NSMutableDictionary dictionary];
62
+ result[@"versions"] = response.versions ?: @[];
63
+ if (response.extensions != nil) {
64
+ result[@"extensions"] = response.extensions;
65
+ }
66
+ if (response.aaguid != nil) {
67
+ result[@"aaguid"] = [response.aaguid base64EncodedStringWithOptions:0];
68
+ }
69
+ if (response.options != nil) {
70
+ NSMutableDictionary *options = [NSMutableDictionary dictionary];
71
+ [response.options enumerateKeysAndObjectsUsingBlock:^(id key, id value, BOOL *stop) {
72
+ if ([value isKindOfClass:[NSNumber class]]) {
73
+ options[key] = value;
74
+ }
75
+ }];
76
+ result[@"options"] = options;
77
+ }
78
+ result[@"maxMsgSize"] = @(response.maxMsgSize);
79
+ if (response.pinProtocols != nil) {
80
+ result[@"pinUvAuthProtocols"] = response.pinProtocols;
81
+ }
82
+ result[@"minPinLength"] = @(response.minPinLength);
83
+
84
+ resolve(result);
85
+ }];
86
+ }];
87
+ }
88
+
89
+ - (void)makeCredential:(NSString *)deviceHandle
90
+ options:(JS::NativeYubikitFido::PublicKeyCredentialCreationOptions &)options
91
+ effectiveDomain:(NSString *)effectiveDomain
92
+ pin:(NSString *)pin
93
+ enterpriseAttestation:(NSNumber *)enterpriseAttestation
94
+ resolve:(RCTPromiseResolveBlock)resolve
95
+ reject:(RCTPromiseRejectBlock)reject {
96
+ id<YKFConnectionProtocol> connection = [self connectionForHandle:deviceHandle reject:reject];
97
+ if (!connection) return;
98
+
99
+ // YKFFIDO2Session has no enterprise-attestation option in this SDK version (only
100
+ // YKFFIDO2OptionRK/UV/UP exist) - reject explicitly rather than silently proceeding
101
+ // as a normal, non-enterprise attestation request.
102
+ if (enterpriseAttestation != nil) {
103
+ reject(@"FIDO_ERROR", @"enterpriseAttestation is not supported by the YubiKit iOS SDK", nil);
104
+ return;
105
+ }
106
+
107
+ NSData *challengeData = [self base64Decode:options.challenge()];
108
+ if (challengeData == nil) {
109
+ reject(@"FIDO_ERROR", @"Invalid base64 challenge", nil);
110
+ return;
111
+ }
112
+
113
+ NSString *origin = [NSString stringWithFormat:@"https://%@", effectiveDomain];
114
+ YKFWebAuthnClientData *clientData = [[YKFWebAuthnClientData alloc] initWithType:YKFWebAuthnClientDataTypeCreate
115
+ challenge:challengeData
116
+ origin:origin];
117
+ if (clientData == nil || clientData.clientDataHash == nil || clientData.jsonData == nil) {
118
+ reject(@"FIDO_ERROR", @"Failed to build client data", nil);
119
+ return;
120
+ }
121
+
122
+ JS::NativeYubikitFido::PublicKeyCredentialRpEntity rpOptions = options.rp();
123
+ YKFFIDO2PublicKeyCredentialRpEntity *rp = [[YKFFIDO2PublicKeyCredentialRpEntity alloc] init];
124
+ rp.rpId = rpOptions.id_() ?: effectiveDomain;
125
+ rp.rpName = rpOptions.name() ?: rp.rpId;
126
+
127
+ JS::NativeYubikitFido::PublicKeyCredentialUserEntity userOptions = options.user();
128
+ YKFFIDO2PublicKeyCredentialUserEntity *user = [[YKFFIDO2PublicKeyCredentialUserEntity alloc] init];
129
+ user.userId = [self base64Decode:userOptions.id_()] ?: [NSData data];
130
+ user.userName = userOptions.name() ?: @"";
131
+ user.userDisplayName = userOptions.displayName() ?: @"";
132
+
133
+ NSMutableArray<YKFFIDO2PublicKeyCredentialParam *> *pubKeyCredParams = [NSMutableArray array];
134
+ auto params = options.pubKeyCredParams();
135
+ for (const auto &param : params) {
136
+ YKFFIDO2PublicKeyCredentialParam *credParam = [[YKFFIDO2PublicKeyCredentialParam alloc] init];
137
+ credParam.alg = (NSInteger)param.alg();
138
+ [pubKeyCredParams addObject:credParam];
139
+ }
140
+
141
+ NSMutableArray<YKFFIDO2PublicKeyCredentialDescriptor *> *excludeList = nil;
142
+ auto excludeCredentials = options.excludeCredentials();
143
+ if (excludeCredentials.has_value()) {
144
+ excludeList = [NSMutableArray array];
145
+ for (const auto &credential : excludeCredentials.value()) {
146
+ [excludeList addObject:[self descriptorFromId:credential.id_() type:credential.type()]];
147
+ }
148
+ }
149
+
150
+ NSMutableDictionary *fidoOptions = [NSMutableDictionary dictionary];
151
+ auto authenticatorSelection = options.authenticatorSelection();
152
+ if (authenticatorSelection.has_value()) {
153
+ NSString *residentKey = authenticatorSelection.value().residentKey();
154
+ if (residentKey != nil) {
155
+ fidoOptions[YKFFIDO2OptionRK] = @([self isRequiredPreference:residentKey]);
156
+ }
157
+ NSString *userVerification = authenticatorSelection.value().userVerification();
158
+ if (userVerification != nil) {
159
+ fidoOptions[YKFFIDO2OptionUV] = @([self isRequiredPreference:userVerification]);
160
+ }
161
+ }
162
+
163
+ [connection fido2Session:^(YKFFIDO2Session *_Nullable session, NSError *_Nullable error) {
164
+ if (error || session == nil) {
165
+ reject(@"FIDO_ERROR", error ? error.localizedDescription : @"FIDO2 session not available", error);
166
+ return;
167
+ }
168
+
169
+ void (^performMakeCredential)(void) = ^{
170
+ [session makeCredentialWithClientDataHash:clientData.clientDataHash
171
+ rp:rp
172
+ user:user
173
+ pubKeyCredParams:pubKeyCredParams
174
+ excludeList:excludeList
175
+ options:fidoOptions.count > 0 ? fidoOptions : nil
176
+ completion:^(YKFFIDO2MakeCredentialResponse *_Nullable response, NSError *_Nullable error) {
177
+ if (error || response == nil) {
178
+ reject(@"FIDO_ERROR", error ? error.localizedDescription : @"Make credential failed", error);
179
+ return;
180
+ }
181
+
182
+ NSData *credentialId = response.authenticatorData.credentialId ?: [NSData data];
183
+ NSString *credentialIdBase64 = [credentialId base64EncodedStringWithOptions:0];
184
+
185
+ resolve(@{
186
+ @"id": credentialIdBase64,
187
+ @"rawId": credentialIdBase64,
188
+ @"type": @"public-key",
189
+ @"response": @{
190
+ @"clientDataJSON": [clientData.jsonData base64EncodedStringWithOptions:0],
191
+ @"authenticatorData": [response.authData base64EncodedStringWithOptions:0],
192
+ @"attestationObject": [response.webauthnAttestationObject base64EncodedStringWithOptions:0]
193
+ }
194
+ });
195
+ }];
196
+ };
197
+
198
+ if (pin != nil && pin.length > 0) {
199
+ [session verifyPin:pin completion:^(NSError *_Nullable error) {
200
+ if (error) {
201
+ reject(@"FIDO_ERROR", error.localizedDescription, error);
202
+ return;
203
+ }
204
+ performMakeCredential();
205
+ }];
206
+ } else {
207
+ performMakeCredential();
208
+ }
209
+ }];
210
+ }
211
+
212
+ - (void)getAssertion:(NSString *)deviceHandle
213
+ options:(JS::NativeYubikitFido::PublicKeyCredentialRequestOptions &)options
214
+ effectiveDomain:(NSString *)effectiveDomain
215
+ pin:(NSString *)pin
216
+ resolve:(RCTPromiseResolveBlock)resolve
217
+ reject:(RCTPromiseRejectBlock)reject {
218
+ id<YKFConnectionProtocol> connection = [self connectionForHandle:deviceHandle reject:reject];
219
+ if (!connection) return;
220
+
221
+ NSData *challengeData = [self base64Decode:options.challenge()];
222
+ if (challengeData == nil) {
223
+ reject(@"FIDO_ERROR", @"Invalid base64 challenge", nil);
224
+ return;
225
+ }
226
+
227
+ NSString *origin = [NSString stringWithFormat:@"https://%@", effectiveDomain];
228
+ YKFWebAuthnClientData *clientData = [[YKFWebAuthnClientData alloc] initWithType:YKFWebAuthnClientDataTypeGet
229
+ challenge:challengeData
230
+ origin:origin];
231
+ if (clientData == nil || clientData.clientDataHash == nil || clientData.jsonData == nil) {
232
+ reject(@"FIDO_ERROR", @"Failed to build client data", nil);
233
+ return;
234
+ }
235
+
236
+ NSString *rpId = options.rpId() ?: effectiveDomain;
237
+
238
+ NSMutableArray<YKFFIDO2PublicKeyCredentialDescriptor *> *allowList = nil;
239
+ auto allowCredentials = options.allowCredentials();
240
+ if (allowCredentials.has_value()) {
241
+ allowList = [NSMutableArray array];
242
+ for (const auto &credential : allowCredentials.value()) {
243
+ [allowList addObject:[self descriptorFromId:credential.id_() type:credential.type()]];
244
+ }
245
+ }
246
+
247
+ NSMutableDictionary *fidoOptions = [NSMutableDictionary dictionary];
248
+ NSString *userVerification = options.userVerification();
249
+ if (userVerification != nil) {
250
+ fidoOptions[YKFFIDO2OptionUV] = @([self isRequiredPreference:userVerification]);
251
+ }
252
+
253
+ NSArray<YKFFIDO2PublicKeyCredentialDescriptor *> *finalAllowList = allowList;
254
+
255
+ [connection fido2Session:^(YKFFIDO2Session *_Nullable session, NSError *_Nullable error) {
256
+ if (error || session == nil) {
257
+ reject(@"FIDO_ERROR", error ? error.localizedDescription : @"FIDO2 session not available", error);
258
+ return;
259
+ }
260
+
261
+ void (^performGetAssertion)(void) = ^{
262
+ [session getAssertionWithClientDataHash:clientData.clientDataHash
263
+ rpId:rpId
264
+ allowList:finalAllowList
265
+ options:fidoOptions.count > 0 ? fidoOptions : nil
266
+ completion:^(YKFFIDO2GetAssertionResponse *_Nullable response, NSError *_Nullable error) {
267
+ if (error || response == nil) {
268
+ reject(@"FIDO_ERROR", error ? error.localizedDescription : @"Get assertion failed", error);
269
+ return;
270
+ }
271
+
272
+ NSData *credentialId = response.credential.credentialId;
273
+ if (credentialId == nil && finalAllowList.count == 1) {
274
+ credentialId = finalAllowList.firstObject.credentialId;
275
+ }
276
+ NSString *credentialIdBase64 = [(credentialId ?: [NSData data]) base64EncodedStringWithOptions:0];
277
+
278
+ NSMutableDictionary *responseDict = [NSMutableDictionary dictionary];
279
+ responseDict[@"clientDataJSON"] = [clientData.jsonData base64EncodedStringWithOptions:0];
280
+ responseDict[@"authenticatorData"] = [response.authData base64EncodedStringWithOptions:0];
281
+ responseDict[@"signature"] = [response.signature base64EncodedStringWithOptions:0];
282
+ if (response.user != nil && response.user.userId != nil) {
283
+ responseDict[@"userHandle"] = [response.user.userId base64EncodedStringWithOptions:0];
284
+ }
285
+
286
+ resolve(@{
287
+ @"id": credentialIdBase64,
288
+ @"rawId": credentialIdBase64,
289
+ @"type": @"public-key",
290
+ @"response": responseDict
291
+ });
292
+ }];
293
+ };
294
+
295
+ if (pin != nil && pin.length > 0) {
296
+ [session verifyPin:pin completion:^(NSError *_Nullable error) {
297
+ if (error) {
298
+ reject(@"FIDO_ERROR", error.localizedDescription, error);
299
+ return;
300
+ }
301
+ performGetAssertion();
302
+ }];
303
+ } else {
304
+ performGetAssertion();
305
+ }
306
+ }];
307
+ }
308
+
309
+ - (void)reset:(NSString *)deviceHandle
310
+ resolve:(RCTPromiseResolveBlock)resolve
311
+ reject:(RCTPromiseRejectBlock)reject {
312
+ id<YKFConnectionProtocol> connection = [self connectionForHandle:deviceHandle reject:reject];
313
+ if (!connection) return;
314
+
315
+ [connection fido2Session:^(YKFFIDO2Session *_Nullable session, NSError *_Nullable error) {
316
+ if (error || session == nil) {
317
+ reject(@"FIDO_ERROR", error ? error.localizedDescription : @"FIDO2 session not available", error);
318
+ return;
319
+ }
320
+ [session resetWithCompletion:^(NSError *_Nullable error) {
321
+ if (error) {
322
+ reject(@"FIDO_ERROR", error.localizedDescription, error);
323
+ return;
324
+ }
325
+ resolve(nil);
326
+ }];
327
+ }];
328
+ }
329
+
330
+ - (void)getCredentialCount:(NSString *)deviceHandle
331
+ pin:(NSString *)pin
332
+ resolve:(RCTPromiseResolveBlock)resolve
333
+ reject:(RCTPromiseRejectBlock)reject {
334
+ reject(@"FIDO_ERROR", @"Not implemented on iOS", nil);
335
+ }
336
+
337
+ - (void)getRpIdList:(NSString *)deviceHandle
338
+ pin:(NSString *)pin
339
+ resolve:(RCTPromiseResolveBlock)resolve
340
+ reject:(RCTPromiseRejectBlock)reject {
341
+ reject(@"FIDO_ERROR", @"Not implemented on iOS", nil);
342
+ }
343
+
344
+ - (void)getCredentials:(NSString *)deviceHandle
345
+ rpId:(NSString *)rpId
346
+ pin:(NSString *)pin
347
+ resolve:(RCTPromiseResolveBlock)resolve
348
+ reject:(RCTPromiseRejectBlock)reject {
349
+ reject(@"FIDO_ERROR", @"Not implemented on iOS", nil);
350
+ }
351
+
352
+ - (void)deleteCredential:(NSString *)deviceHandle
353
+ credential:(JS::NativeYubikitFido::FidoCredentialDescriptor &)credential
354
+ pin:(NSString *)pin
355
+ resolve:(RCTPromiseResolveBlock)resolve
356
+ reject:(RCTPromiseRejectBlock)reject {
357
+ reject(@"FIDO_ERROR", @"Not implemented on iOS", nil);
358
+ }
359
+
360
+ - (void)updateUserInformation:(NSString *)deviceHandle
361
+ credential:(JS::NativeYubikitFido::FidoCredentialDescriptor &)credential
362
+ user:(JS::NativeYubikitFido::FidoCredentialUserEntity &)user
363
+ pin:(NSString *)pin
364
+ resolve:(RCTPromiseResolveBlock)resolve
365
+ reject:(RCTPromiseRejectBlock)reject {
366
+ reject(@"FIDO_ERROR", @"Not implemented on iOS", nil);
367
+ }
368
+
369
+ - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const facebook::react::ObjCTurboModule::InitParams &)params {
370
+ return std::make_shared<facebook::react::NativeYubikitFidoSpecJSI>(params);
371
+ }
372
+
373
+ @end
@@ -0,0 +1,5 @@
1
+ #import <YubikitSpec/YubikitSpec.h>
2
+
3
+ @interface YubikitManagement : NSObject <NativeYubikitManagementSpec>
4
+
5
+ @end
@@ -0,0 +1,153 @@
1
+ #import "YubikitManagement.h"
2
+ #import "YubikitManager.h"
3
+ #import "YubikitDeviceInfoHelper.h"
4
+
5
+ @implementation YubikitManagement
6
+
7
+ RCT_EXPORT_MODULE(YubikitManagement)
8
+
9
+ - (void)getDeviceInfo:(NSString *)deviceHandle
10
+ resolve:(RCTPromiseResolveBlock)resolve
11
+ reject:(RCTPromiseRejectBlock)reject {
12
+ id<YKFConnectionProtocol> connection = [[YubikitManager shared] connectionForHandle:deviceHandle];
13
+ if (connection == nil) {
14
+ reject(@"CONNECTION_ERROR", @"No device found for handle", nil);
15
+ return;
16
+ }
17
+
18
+ [connection managementSession:^(YKFManagementSession *_Nullable session, NSError *_Nullable error) {
19
+ if (error || session == nil) {
20
+ reject(@"MANAGEMENT_ERROR", error ? error.localizedDescription : @"Management session not available", error);
21
+ return;
22
+ }
23
+
24
+ [session getDeviceInfoWithCompletion:^(YKFManagementDeviceInfo *_Nullable deviceInfo, NSError *_Nullable error) {
25
+ if (error || deviceInfo == nil) {
26
+ reject(@"MANAGEMENT_ERROR", error ? error.localizedDescription : @"Failed to read device info", error);
27
+ return;
28
+ }
29
+ resolve([YubikitDeviceInfoHelper dictionaryFromDeviceInfo:deviceInfo]);
30
+ }];
31
+ }];
32
+ }
33
+
34
+ - (void)updateDeviceConfig:(NSString *)deviceHandle
35
+ config:(JS::NativeYubikitManagement::DeviceConfig &)config
36
+ reboot:(BOOL)reboot
37
+ currentLockCode:(NSString *)currentLockCode
38
+ newLockCode:(NSString *)newLockCode
39
+ resolve:(RCTPromiseResolveBlock)resolve
40
+ reject:(RCTPromiseRejectBlock)reject {
41
+ id<YKFConnectionProtocol> connection = [[YubikitManager shared] connectionForHandle:deviceHandle];
42
+ if (connection == nil) {
43
+ reject(@"CONNECTION_ERROR", @"No device found for handle", nil);
44
+ return;
45
+ }
46
+
47
+ [connection managementSession:^(YKFManagementSession *_Nullable session, NSError *_Nullable error) {
48
+ if (error || session == nil) {
49
+ reject(@"MANAGEMENT_ERROR", error ? error.localizedDescription : @"Management session not available", error);
50
+ return;
51
+ }
52
+
53
+ [session getDeviceInfoWithCompletion:^(YKFManagementDeviceInfo *_Nullable deviceInfo, NSError *_Nullable error) {
54
+ if (error || deviceInfo == nil || deviceInfo.configuration == nil) {
55
+ reject(@"MANAGEMENT_ERROR", error ? error.localizedDescription : @"Device configuration not available", error);
56
+ return;
57
+ }
58
+
59
+ YKFManagementInterfaceConfiguration *interfaceConfig = deviceInfo.configuration;
60
+ std::optional<JS::NativeYubikitManagement::DeviceConfigEnabledCapabilities> enabledCaps = config.enabledCapabilities();
61
+ if (enabledCaps.has_value()) {
62
+ std::optional<double> usbCaps = enabledCaps.value().usb();
63
+ std::optional<double> nfcCaps = enabledCaps.value().nfc();
64
+ // Assign the raw bitmask directly - iterating a hardcoded application list (as
65
+ // done previously) drops any bit not enumerated there (e.g. HSMAUTH), silently
66
+ // failing to apply part of what the caller asked for.
67
+ if (usbCaps.has_value()) {
68
+ interfaceConfig.usbEnabledMask = (NSUInteger)usbCaps.value();
69
+ }
70
+ if (nfcCaps.has_value()) {
71
+ interfaceConfig.nfcEnabledMask = (NSUInteger)nfcCaps.value();
72
+ }
73
+ }
74
+
75
+ std::optional<double> autoEjectTimeout = config.autoEjectTimeout();
76
+ if (autoEjectTimeout.has_value()) {
77
+ interfaceConfig.autoEjectTimeout = autoEjectTimeout.value();
78
+ }
79
+ std::optional<double> challengeResponseTimeout = config.challengeResponseTimeout();
80
+ if (challengeResponseTimeout.has_value()) {
81
+ interfaceConfig.challengeResponseTimeout = challengeResponseTimeout.value();
82
+ }
83
+ std::optional<bool> nfcRestricted = config.nfcRestricted();
84
+ if (nfcRestricted.has_value()) {
85
+ interfaceConfig.isNFCRestricted = nfcRestricted.value();
86
+ }
87
+
88
+ NSData *currentLockCodeData = currentLockCode != nil
89
+ ? [[NSData alloc] initWithBase64EncodedString:currentLockCode options:0]
90
+ : nil;
91
+ NSData *newLockCodeData = newLockCode != nil
92
+ ? [[NSData alloc] initWithBase64EncodedString:newLockCode options:0]
93
+ : nil;
94
+
95
+ YKFManagementSessionWriteCompletionBlock completion = ^(NSError *_Nullable error) {
96
+ if (error) {
97
+ reject(@"MANAGEMENT_ERROR", error.localizedDescription, error);
98
+ return;
99
+ }
100
+ resolve(nil);
101
+ };
102
+
103
+ if (currentLockCodeData != nil || newLockCodeData != nil) {
104
+ [session writeConfiguration:interfaceConfig reboot:reboot lockCode:currentLockCodeData newLockCode:newLockCodeData completion:completion];
105
+ } else {
106
+ [session writeConfiguration:interfaceConfig reboot:reboot completion:completion];
107
+ }
108
+ }];
109
+ }];
110
+ }
111
+
112
+ // The iOS SDK has no dedicated "mode" setter; the same per-transport interface
113
+ // enable/disable behavior is available through updateDeviceConfig instead.
114
+ - (void)setMode:(NSString *)deviceHandle
115
+ mode:(NSString *)mode
116
+ chalrespTimeout:(double)chalrespTimeout
117
+ autoejectTimeout:(double)autoejectTimeout
118
+ resolve:(RCTPromiseResolveBlock)resolve
119
+ reject:(RCTPromiseRejectBlock)reject {
120
+ reject(@"MANAGEMENT_ERROR", @"Not implemented on iOS; use updateDeviceConfig instead", nil);
121
+ }
122
+
123
+ // Only supported by YubiKey Bio - Multi-protocol Edition devices on firmware 5.6+;
124
+ // the SDK surfaces an error for any other device rather than a universal factory reset.
125
+ - (void)deviceReset:(NSString *)deviceHandle
126
+ resolve:(RCTPromiseResolveBlock)resolve
127
+ reject:(RCTPromiseRejectBlock)reject {
128
+ id<YKFConnectionProtocol> connection = [[YubikitManager shared] connectionForHandle:deviceHandle];
129
+ if (connection == nil) {
130
+ reject(@"CONNECTION_ERROR", @"No device found for handle", nil);
131
+ return;
132
+ }
133
+
134
+ [connection managementSession:^(YKFManagementSession *_Nullable session, NSError *_Nullable error) {
135
+ if (error || session == nil) {
136
+ reject(@"MANAGEMENT_ERROR", error ? error.localizedDescription : @"Management session not available", error);
137
+ return;
138
+ }
139
+ [session deviceReset:^(NSError *_Nullable error) {
140
+ if (error) {
141
+ reject(@"MANAGEMENT_ERROR", error.localizedDescription, error);
142
+ return;
143
+ }
144
+ resolve(nil);
145
+ }];
146
+ }];
147
+ }
148
+
149
+ - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const facebook::react::ObjCTurboModule::InitParams &)params {
150
+ return std::make_shared<facebook::react::NativeYubikitManagementSpecJSI>(params);
151
+ }
152
+
153
+ @end
@@ -0,0 +1,27 @@
1
+ #import <Foundation/Foundation.h>
2
+ #import <YubiKit/YubiKit.h>
3
+
4
+ NS_ASSUME_NONNULL_BEGIN
5
+
6
+ typedef void (^YubikitManagerEventHandler)(NSString *type, NSDictionary * _Nullable payload);
7
+
8
+ @interface YubikitManager : NSObject <YKFManagerDelegate>
9
+
10
+ /// Set by YubikitCore while it has JS-side "YubiKeyEvent" listeners registered.
11
+ /// Invoked on device attach/detach/connection-failure so it can be re-emitted to JS.
12
+ @property (nonatomic, copy, nullable) YubikitManagerEventHandler eventHandler;
13
+
14
+ + (instancetype)shared;
15
+
16
+ - (void)startUsbDiscovery;
17
+ - (void)stopUsbDiscovery;
18
+ - (void)startNfcDiscovery;
19
+ - (void)stopNfcDiscovery;
20
+
21
+ - (nullable id<YKFConnectionProtocol>)connectionForHandle:(NSString *)handle;
22
+ - (void)removeConnectionForHandle:(NSString *)handle;
23
+ - (NSArray<NSDictionary *> *)listDevices;
24
+
25
+ @end
26
+
27
+ NS_ASSUME_NONNULL_END