@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,813 @@
1
+ #import "YubikitPiv.h"
2
+ #import "YubikitManager.h"
3
+ #import <YubiKit/YubiKit.h>
4
+
5
+ @implementation YubikitPiv
6
+
7
+ RCT_EXPORT_MODULE(YubikitPiv)
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
+ - (YKFPIVSlot)slotFromString:(NSString *)slot {
21
+ if ([slot isEqualToString:@"SIGNATURE"]) return YKFPIVSlotSignature;
22
+ if ([slot isEqualToString:@"KEY_MANAGEMENT"]) return YKFPIVSlotKeyManagement;
23
+ if ([slot isEqualToString:@"CARD_AUTH"]) return YKFPIVSlotCardAuth;
24
+ if ([slot isEqualToString:@"ATTESTATION"]) return YKFPIVSlotAttestation;
25
+ if ([slot hasPrefix:@"RETIRED"]) {
26
+ NSInteger index = [[slot substringFromIndex:7] integerValue];
27
+ if (index >= 1 && index <= 20) {
28
+ return (YKFPIVSlot)(0x82 + (index - 1));
29
+ }
30
+ }
31
+ return YKFPIVSlotAuthentication;
32
+ }
33
+
34
+ - (YKFPIVKeyType)keyTypeFromString:(NSString *)keyType {
35
+ if ([keyType isEqualToString:@"RSA1024"]) return YKFPIVKeyTypeRSA1024;
36
+ if ([keyType isEqualToString:@"RSA2048"]) return YKFPIVKeyTypeRSA2048;
37
+ if ([keyType isEqualToString:@"RSA3072"]) return YKFPIVKeyTypeRSA3072;
38
+ if ([keyType isEqualToString:@"RSA4096"]) return YKFPIVKeyTypeRSA4096;
39
+ if ([keyType isEqualToString:@"ECCP256"]) return YKFPIVKeyTypeECCP256;
40
+ if ([keyType isEqualToString:@"ECCP384"]) return YKFPIVKeyTypeECCP384;
41
+ return YKFPIVKeyTypeUnknown;
42
+ }
43
+
44
+ - (NSString *)stringFromKeyType:(YKFPIVKeyType)keyType {
45
+ switch (keyType) {
46
+ case YKFPIVKeyTypeRSA1024: return @"RSA1024";
47
+ case YKFPIVKeyTypeRSA2048: return @"RSA2048";
48
+ case YKFPIVKeyTypeRSA3072: return @"RSA3072";
49
+ case YKFPIVKeyTypeRSA4096: return @"RSA4096";
50
+ case YKFPIVKeyTypeECCP256: return @"ECCP256";
51
+ case YKFPIVKeyTypeECCP384: return @"ECCP384";
52
+ default: return @"";
53
+ }
54
+ }
55
+
56
+ - (YKFPIVPinPolicy)pinPolicyFromString:(NSString *)policy {
57
+ if ([policy isEqualToString:@"NEVER"]) return YKFPIVPinPolicyNever;
58
+ if ([policy isEqualToString:@"ONCE"]) return YKFPIVPinPolicyOnce;
59
+ if ([policy isEqualToString:@"ALWAYS"]) return YKFPIVPinPolicyAlways;
60
+ if ([policy isEqualToString:@"MATCH_ONCE"]) return YKFPIVPinPolicyMatchOnce;
61
+ if ([policy isEqualToString:@"MATCH_ALWAYS"]) return YKFPIVPinPolicyMatchAlways;
62
+ return YKFPIVPinPolicyDefault;
63
+ }
64
+
65
+ - (NSString *)stringFromPinPolicy:(YKFPIVPinPolicy)policy {
66
+ switch (policy) {
67
+ case YKFPIVPinPolicyNever: return @"NEVER";
68
+ case YKFPIVPinPolicyOnce: return @"ONCE";
69
+ case YKFPIVPinPolicyAlways: return @"ALWAYS";
70
+ case YKFPIVPinPolicyMatchOnce: return @"MATCH_ONCE";
71
+ case YKFPIVPinPolicyMatchAlways: return @"MATCH_ALWAYS";
72
+ default: return @"DEFAULT";
73
+ }
74
+ }
75
+
76
+ - (YKFPIVTouchPolicy)touchPolicyFromString:(NSString *)policy {
77
+ if ([policy isEqualToString:@"NEVER"]) return YKFPIVTouchPolicyNever;
78
+ if ([policy isEqualToString:@"ALWAYS"]) return YKFPIVTouchPolicyAlways;
79
+ if ([policy isEqualToString:@"CACHED"]) return YKFPIVTouchPolicyCached;
80
+ return YKFPIVTouchPolicyDefault;
81
+ }
82
+
83
+ - (NSString *)stringFromTouchPolicy:(YKFPIVTouchPolicy)policy {
84
+ switch (policy) {
85
+ case YKFPIVTouchPolicyNever: return @"NEVER";
86
+ case YKFPIVTouchPolicyAlways: return @"ALWAYS";
87
+ case YKFPIVTouchPolicyCached: return @"CACHED";
88
+ default: return @"DEFAULT";
89
+ }
90
+ }
91
+
92
+ - (YKFPIVManagementKeyType *)managementKeyTypeFromString:(NSString *)keyType {
93
+ if ([keyType isEqualToString:@"AES128"]) return [YKFPIVManagementKeyType AES128];
94
+ if ([keyType isEqualToString:@"AES192"]) return [YKFPIVManagementKeyType AES192];
95
+ if ([keyType isEqualToString:@"AES256"]) return [YKFPIVManagementKeyType AES256];
96
+ return [YKFPIVManagementKeyType TripleDES];
97
+ }
98
+
99
+ // The "authenticate" method doesn't receive an explicit key type from JS, so the
100
+ // management key type is inferred from its length. 24-byte keys default to TDES
101
+ // since that's the YubiKey factory-default management key type.
102
+ - (YKFPIVManagementKeyType *)managementKeyTypeFromKeyData:(NSData *)keyData {
103
+ switch (keyData.length) {
104
+ case 16: return [YKFPIVManagementKeyType AES128];
105
+ case 32: return [YKFPIVManagementKeyType AES256];
106
+ default: return [YKFPIVManagementKeyType TripleDES];
107
+ }
108
+ }
109
+
110
+ - (NSString *)stringFromManagementKeyType:(YKFPIVManagementKeyType *)keyType {
111
+ if ([keyType.name isEqualToString:YKFPIVManagementKeyTypeAES]) {
112
+ switch (keyType.keyLenght) {
113
+ case 16: return @"AES128";
114
+ case 24: return @"AES192";
115
+ case 32: return @"AES256";
116
+ default: return @"TDES";
117
+ }
118
+ }
119
+ return @"TDES";
120
+ }
121
+
122
+ - (NSString *)base64FromSecCertificate:(SecCertificateRef)certificate {
123
+ if (certificate == NULL) return nil;
124
+ NSData *data = (__bridge_transfer NSData *)SecCertificateCopyData(certificate);
125
+ return [data base64EncodedStringWithOptions:0];
126
+ }
127
+
128
+ - (NSString *)base64FromSecKey:(SecKeyRef)key {
129
+ if (key == NULL) return nil;
130
+ CFErrorRef error = NULL;
131
+ NSData *data = (__bridge_transfer NSData *)SecKeyCopyExternalRepresentation(key, &error);
132
+ if (error || data == nil) return nil;
133
+ return [data base64EncodedStringWithOptions:0];
134
+ }
135
+
136
+ // The SDK's rawSignOrDecrypt is exposed as two distinct methods (sign / decrypt),
137
+ // while the JS API takes no explicit intent. Decryption is only meaningful for RSA
138
+ // keys in the KEY_MANAGEMENT slot on this SDK (EC keys use ECDH instead), so that
139
+ // combination is treated as decrypt; everything else is treated as sign. (The two
140
+ // paths are mathematically equivalent raw RSA operations when using the "Raw"
141
+ // algorithm variants below, so this choice mainly preserves intent for callers.)
142
+ - (BOOL)isDecryptOperationForSlot:(YKFPIVSlot)pivSlot keyType:(YKFPIVKeyType)pivKeyType {
143
+ BOOL isRsa = pivKeyType == YKFPIVKeyTypeRSA1024 || pivKeyType == YKFPIVKeyTypeRSA2048;
144
+ return isRsa && pivSlot == YKFPIVSlotKeyManagement;
145
+ }
146
+
147
+ // "Raw"/plain-digest algorithm variants make YKFPIVPadding treat the payload as an
148
+ // already-formed, pass-through block (no extra hashing or padding added), matching
149
+ // the Android SDK's rawSignOrDecrypt, which sends the payload to the card unmodified
150
+ // (aside from the length normalization in normalizedPayload:forKeyType:error: below).
151
+ // Note: YKFPIVPadding only recognizes the SHA-suffixed "Digest" EC constants (they're
152
+ // all treated identically, as pure pass-through) — the unsuffixed
153
+ // kSecKeyAlgorithmECDSASignatureDigestX962 is NOT one of them and would silently
154
+ // zero-pad the payload instead of forwarding it, so it must not be used here.
155
+ - (SecKeyAlgorithm)signAlgorithmForKeyType:(YKFPIVKeyType)pivKeyType {
156
+ switch (pivKeyType) {
157
+ case YKFPIVKeyTypeECCP256:
158
+ return kSecKeyAlgorithmECDSASignatureDigestX962SHA256;
159
+ case YKFPIVKeyTypeECCP384:
160
+ return kSecKeyAlgorithmECDSASignatureDigestX962SHA384;
161
+ default:
162
+ return kSecKeyAlgorithmRSASignatureRaw;
163
+ }
164
+ }
165
+
166
+ // YKFPIVKeyType.h declares this as a plain C function with no `extern "C"` guard,
167
+ // so calling it from Objective-C++ (.mm) applies C++ name mangling that doesn't
168
+ // match the C-linkage symbol the SDK actually exports, causing an undefined-symbol
169
+ // link error. Reimplement it locally instead (values match YKFPIVSizeFromKeyType).
170
+ - (NSInteger)byteLengthForKeyType:(YKFPIVKeyType)pivKeyType {
171
+ switch (pivKeyType) {
172
+ case YKFPIVKeyTypeECCP256: return 256 / 8;
173
+ case YKFPIVKeyTypeECCP384: return 384 / 8;
174
+ case YKFPIVKeyTypeRSA1024: return 1024 / 8;
175
+ case YKFPIVKeyTypeRSA2048: return 2048 / 8;
176
+ case YKFPIVKeyTypeRSA3072: return 3072 / 8;
177
+ case YKFPIVKeyTypeRSA4096: return 4096 / 8;
178
+ default: return 0;
179
+ }
180
+ }
181
+
182
+ // Mirrors PivSession.rawSignOrDecrypt on Android: left-pad short payloads with
183
+ // leading zero bytes, truncate long EC payloads, and reject long RSA payloads,
184
+ // before handing off to the card's raw private-key operation.
185
+ - (nullable NSData *)normalizedPayload:(NSData *)payload
186
+ forKeyType:(YKFPIVKeyType)pivKeyType
187
+ error:(NSError **)error {
188
+ NSInteger byteLength = [self byteLengthForKeyType:pivKeyType];
189
+ if (byteLength <= 0 || payload.length == (NSUInteger)byteLength) {
190
+ return payload;
191
+ }
192
+
193
+ BOOL isEC = pivKeyType == YKFPIVKeyTypeECCP256 || pivKeyType == YKFPIVKeyTypeECCP384;
194
+
195
+ if (payload.length > (NSUInteger)byteLength) {
196
+ if (isEC) {
197
+ return [payload subdataWithRange:NSMakeRange(0, (NSUInteger)byteLength)];
198
+ }
199
+ if (error) {
200
+ *error = [NSError errorWithDomain:@"YubikitPiv" code:0
201
+ userInfo:@{NSLocalizedDescriptionKey: @"Payload too large for key"}];
202
+ }
203
+ return nil;
204
+ }
205
+
206
+ NSMutableData *padded = [NSMutableData dataWithLength:(NSUInteger)byteLength];
207
+ [payload getBytes:(uint8_t *)padded.mutableBytes + (byteLength - payload.length) length:payload.length];
208
+ return padded;
209
+ }
210
+
211
+ #pragma mark - Methods
212
+
213
+ - (void)reset:(NSString *)deviceHandle
214
+ resolve:(RCTPromiseResolveBlock)resolve
215
+ reject:(RCTPromiseRejectBlock)reject {
216
+ id<YKFConnectionProtocol> connection = [self connectionForHandle:deviceHandle reject:reject];
217
+ if (!connection) return;
218
+
219
+ [connection pivSession:^(YKFPIVSession *_Nullable session, NSError *_Nullable error) {
220
+ if (error || session == nil) {
221
+ reject(@"PIV_ERROR", error ? error.localizedDescription : @"PIV session not available", error);
222
+ return;
223
+ }
224
+ [session resetWithCompletion:^(NSError *_Nullable error) {
225
+ if (error) {
226
+ reject(@"PIV_ERROR", error.localizedDescription, error);
227
+ return;
228
+ }
229
+ resolve(nil);
230
+ }];
231
+ }];
232
+ }
233
+
234
+ - (void)getSerialNumber:(NSString *)deviceHandle
235
+ resolve:(RCTPromiseResolveBlock)resolve
236
+ reject:(RCTPromiseRejectBlock)reject {
237
+ id<YKFConnectionProtocol> connection = [self connectionForHandle:deviceHandle reject:reject];
238
+ if (!connection) return;
239
+
240
+ [connection pivSession:^(YKFPIVSession *_Nullable session, NSError *_Nullable error) {
241
+ if (error || session == nil) {
242
+ reject(@"PIV_ERROR", error ? error.localizedDescription : @"PIV session not available", error);
243
+ return;
244
+ }
245
+ [session getSerialNumberWithCompletion:^(int serialNumber, NSError *_Nullable error) {
246
+ if (error) {
247
+ reject(@"PIV_ERROR", error.localizedDescription, error);
248
+ return;
249
+ }
250
+ resolve(@(serialNumber));
251
+ }];
252
+ }];
253
+ }
254
+
255
+ - (void)authenticate:(NSString *)deviceHandle
256
+ managementKey:(NSString *)managementKey
257
+ resolve:(RCTPromiseResolveBlock)resolve
258
+ reject:(RCTPromiseRejectBlock)reject {
259
+ id<YKFConnectionProtocol> connection = [self connectionForHandle:deviceHandle reject:reject];
260
+ if (!connection) return;
261
+
262
+ NSData *keyData = [[NSData alloc] initWithBase64EncodedString:managementKey options:0];
263
+ if (keyData == nil) {
264
+ reject(@"PIV_ERROR", @"Invalid base64 management key", nil);
265
+ return;
266
+ }
267
+
268
+ [connection pivSession:^(YKFPIVSession *_Nullable session, NSError *_Nullable error) {
269
+ if (error || session == nil) {
270
+ reject(@"PIV_ERROR", error ? error.localizedDescription : @"PIV session not available", error);
271
+ return;
272
+ }
273
+ [session authenticateWithManagementKey:keyData
274
+ type:[self managementKeyTypeFromKeyData:keyData]
275
+ completion:^(NSError *_Nullable error) {
276
+ if (error) {
277
+ reject(@"PIV_ERROR", error.localizedDescription, error);
278
+ return;
279
+ }
280
+ resolve(nil);
281
+ }];
282
+ }];
283
+ }
284
+
285
+ - (void)setManagementKey:(NSString *)deviceHandle
286
+ keyType:(NSString *)keyType
287
+ managementKey:(NSString *)managementKey
288
+ requireTouch:(BOOL)requireTouch
289
+ resolve:(RCTPromiseResolveBlock)resolve
290
+ reject:(RCTPromiseRejectBlock)reject {
291
+ id<YKFConnectionProtocol> connection = [self connectionForHandle:deviceHandle reject:reject];
292
+ if (!connection) return;
293
+
294
+ NSData *keyData = [[NSData alloc] initWithBase64EncodedString:managementKey options:0];
295
+ if (keyData == nil) {
296
+ reject(@"PIV_ERROR", @"Invalid base64 management key", nil);
297
+ return;
298
+ }
299
+
300
+ [connection pivSession:^(YKFPIVSession *_Nullable session, NSError *_Nullable error) {
301
+ if (error || session == nil) {
302
+ reject(@"PIV_ERROR", error ? error.localizedDescription : @"PIV session not available", error);
303
+ return;
304
+ }
305
+ [session setManagementKey:keyData
306
+ type:[self managementKeyTypeFromString:keyType]
307
+ requiresTouch:requireTouch
308
+ completion:^(NSError *_Nullable error) {
309
+ if (error) {
310
+ reject(@"PIV_ERROR", error.localizedDescription, error);
311
+ return;
312
+ }
313
+ resolve(nil);
314
+ }];
315
+ }];
316
+ }
317
+
318
+ - (void)verifyPin:(NSString *)deviceHandle
319
+ pin:(NSString *)pin
320
+ resolve:(RCTPromiseResolveBlock)resolve
321
+ reject:(RCTPromiseRejectBlock)reject {
322
+ id<YKFConnectionProtocol> connection = [self connectionForHandle:deviceHandle reject:reject];
323
+ if (!connection) return;
324
+
325
+ [connection pivSession:^(YKFPIVSession *_Nullable session, NSError *_Nullable error) {
326
+ if (error || session == nil) {
327
+ reject(@"PIV_ERROR", error ? error.localizedDescription : @"PIV session not available", error);
328
+ return;
329
+ }
330
+ [session verifyPin:pin completion:^(int retries, NSError *_Nullable error) {
331
+ if (error) {
332
+ NSString *message = retries > 0
333
+ ? [NSString stringWithFormat:@"%@ (%d attempt(s) remaining)", error.localizedDescription, retries]
334
+ : error.localizedDescription;
335
+ reject(@"PIV_ERROR", message, error);
336
+ return;
337
+ }
338
+ resolve(nil);
339
+ }];
340
+ }];
341
+ }
342
+
343
+ - (void)getPinAttempts:(NSString *)deviceHandle
344
+ resolve:(RCTPromiseResolveBlock)resolve
345
+ reject:(RCTPromiseRejectBlock)reject {
346
+ id<YKFConnectionProtocol> connection = [self connectionForHandle:deviceHandle reject:reject];
347
+ if (!connection) return;
348
+
349
+ [connection pivSession:^(YKFPIVSession *_Nullable session, NSError *_Nullable error) {
350
+ if (error || session == nil) {
351
+ reject(@"PIV_ERROR", error ? error.localizedDescription : @"PIV session not available", error);
352
+ return;
353
+ }
354
+ [session getPinAttemptsWithCompletion:^(int retriesRemaining, NSError *_Nullable error) {
355
+ if (error) {
356
+ reject(@"PIV_ERROR", error.localizedDescription, error);
357
+ return;
358
+ }
359
+ resolve(@(retriesRemaining));
360
+ }];
361
+ }];
362
+ }
363
+
364
+ - (void)changePin:(NSString *)deviceHandle
365
+ oldPin:(NSString *)oldPin
366
+ newPin:(NSString *)newPin
367
+ resolve:(RCTPromiseResolveBlock)resolve
368
+ reject:(RCTPromiseRejectBlock)reject {
369
+ id<YKFConnectionProtocol> connection = [self connectionForHandle:deviceHandle reject:reject];
370
+ if (!connection) return;
371
+
372
+ [connection pivSession:^(YKFPIVSession *_Nullable session, NSError *_Nullable error) {
373
+ if (error || session == nil) {
374
+ reject(@"PIV_ERROR", error ? error.localizedDescription : @"PIV session not available", error);
375
+ return;
376
+ }
377
+ [session setPin:newPin oldPin:oldPin completion:^(NSError *_Nullable error) {
378
+ if (error) {
379
+ reject(@"PIV_ERROR", error.localizedDescription, error);
380
+ return;
381
+ }
382
+ resolve(nil);
383
+ }];
384
+ }];
385
+ }
386
+
387
+ - (void)changePuk:(NSString *)deviceHandle
388
+ oldPuk:(NSString *)oldPuk
389
+ newPuk:(NSString *)newPuk
390
+ resolve:(RCTPromiseResolveBlock)resolve
391
+ reject:(RCTPromiseRejectBlock)reject {
392
+ id<YKFConnectionProtocol> connection = [self connectionForHandle:deviceHandle reject:reject];
393
+ if (!connection) return;
394
+
395
+ [connection pivSession:^(YKFPIVSession *_Nullable session, NSError *_Nullable error) {
396
+ if (error || session == nil) {
397
+ reject(@"PIV_ERROR", error ? error.localizedDescription : @"PIV session not available", error);
398
+ return;
399
+ }
400
+ [session setPuk:newPuk oldPuk:oldPuk completion:^(NSError *_Nullable error) {
401
+ if (error) {
402
+ reject(@"PIV_ERROR", error.localizedDescription, error);
403
+ return;
404
+ }
405
+ resolve(nil);
406
+ }];
407
+ }];
408
+ }
409
+
410
+ - (void)unblockPin:(NSString *)deviceHandle
411
+ puk:(NSString *)puk
412
+ newPin:(NSString *)newPin
413
+ resolve:(RCTPromiseResolveBlock)resolve
414
+ reject:(RCTPromiseRejectBlock)reject {
415
+ id<YKFConnectionProtocol> connection = [self connectionForHandle:deviceHandle reject:reject];
416
+ if (!connection) return;
417
+
418
+ [connection pivSession:^(YKFPIVSession *_Nullable session, NSError *_Nullable error) {
419
+ if (error || session == nil) {
420
+ reject(@"PIV_ERROR", error ? error.localizedDescription : @"PIV session not available", error);
421
+ return;
422
+ }
423
+ [session unblockPinWithPuk:puk newPin:newPin completion:^(NSError *_Nullable error) {
424
+ if (error) {
425
+ reject(@"PIV_ERROR", error.localizedDescription, error);
426
+ return;
427
+ }
428
+ resolve(nil);
429
+ }];
430
+ }];
431
+ }
432
+
433
+ - (void)setPinAttempts:(NSString *)deviceHandle
434
+ pinAttempts:(double)pinAttempts
435
+ pukAttempts:(double)pukAttempts
436
+ resolve:(RCTPromiseResolveBlock)resolve
437
+ reject:(RCTPromiseRejectBlock)reject {
438
+ id<YKFConnectionProtocol> connection = [self connectionForHandle:deviceHandle reject:reject];
439
+ if (!connection) return;
440
+
441
+ [connection pivSession:^(YKFPIVSession *_Nullable session, NSError *_Nullable error) {
442
+ if (error || session == nil) {
443
+ reject(@"PIV_ERROR", error ? error.localizedDescription : @"PIV session not available", error);
444
+ return;
445
+ }
446
+ [session setPinAttempts:(int)pinAttempts
447
+ pukAttempts:(int)pukAttempts
448
+ completion:^(NSError *_Nullable error) {
449
+ if (error) {
450
+ reject(@"PIV_ERROR", error.localizedDescription, error);
451
+ return;
452
+ }
453
+ resolve(nil);
454
+ }];
455
+ }];
456
+ }
457
+
458
+ - (void)getPinMetadata:(NSString *)deviceHandle
459
+ resolve:(RCTPromiseResolveBlock)resolve
460
+ reject:(RCTPromiseRejectBlock)reject {
461
+ id<YKFConnectionProtocol> connection = [self connectionForHandle:deviceHandle reject:reject];
462
+ if (!connection) return;
463
+
464
+ [connection pivSession:^(YKFPIVSession *_Nullable session, NSError *_Nullable error) {
465
+ if (error || session == nil) {
466
+ reject(@"PIV_ERROR", error ? error.localizedDescription : @"PIV session not available", error);
467
+ return;
468
+ }
469
+ [session getPinMetadataWithCompletion:^(bool isDefault, int retriesTotal, int retriesRemaining, NSError *_Nullable error) {
470
+ if (error) {
471
+ reject(@"PIV_ERROR", error.localizedDescription, error);
472
+ return;
473
+ }
474
+ resolve(@{@"attemptsRemaining": @(retriesRemaining)});
475
+ }];
476
+ }];
477
+ }
478
+
479
+ - (void)getPukMetadata:(NSString *)deviceHandle
480
+ resolve:(RCTPromiseResolveBlock)resolve
481
+ reject:(RCTPromiseRejectBlock)reject {
482
+ id<YKFConnectionProtocol> connection = [self connectionForHandle:deviceHandle reject:reject];
483
+ if (!connection) return;
484
+
485
+ [connection pivSession:^(YKFPIVSession *_Nullable session, NSError *_Nullable error) {
486
+ if (error || session == nil) {
487
+ reject(@"PIV_ERROR", error ? error.localizedDescription : @"PIV session not available", error);
488
+ return;
489
+ }
490
+ [session getPukMetadataWithCompletion:^(bool isDefault, int retriesTotal, int retriesRemaining, NSError *_Nullable error) {
491
+ if (error) {
492
+ reject(@"PIV_ERROR", error.localizedDescription, error);
493
+ return;
494
+ }
495
+ resolve(@{@"attemptsRemaining": @(retriesRemaining)});
496
+ }];
497
+ }];
498
+ }
499
+
500
+ - (void)getManagementKeyMetadata:(NSString *)deviceHandle
501
+ resolve:(RCTPromiseResolveBlock)resolve
502
+ reject:(RCTPromiseRejectBlock)reject {
503
+ id<YKFConnectionProtocol> connection = [self connectionForHandle:deviceHandle reject:reject];
504
+ if (!connection) return;
505
+
506
+ [connection pivSession:^(YKFPIVSession *_Nullable session, NSError *_Nullable error) {
507
+ if (error || session == nil) {
508
+ reject(@"PIV_ERROR", error ? error.localizedDescription : @"PIV session not available", error);
509
+ return;
510
+ }
511
+ [session getManagementKeyMetadataWithCompletion:^(YKFPIVManagementKeyMetadata *_Nullable metaData, NSError *_Nullable error) {
512
+ if (error || metaData == nil) {
513
+ reject(@"PIV_ERROR", error ? error.localizedDescription : @"Management key metadata not available", error);
514
+ return;
515
+ }
516
+ BOOL touchRequired = metaData.touchPolicy == YKFPIVTouchPolicyAlways || metaData.touchPolicy == YKFPIVTouchPolicyCached;
517
+ resolve(@{
518
+ @"keyType": [self stringFromManagementKeyType:metaData.keyType],
519
+ @"defaultValue": @(metaData.isDefault),
520
+ @"touchRequired": @(touchRequired)
521
+ });
522
+ }];
523
+ }];
524
+ }
525
+
526
+ - (void)getSlotMetadata:(NSString *)deviceHandle
527
+ slot:(NSString *)slot
528
+ resolve:(RCTPromiseResolveBlock)resolve
529
+ reject:(RCTPromiseRejectBlock)reject {
530
+ id<YKFConnectionProtocol> connection = [self connectionForHandle:deviceHandle reject:reject];
531
+ if (!connection) return;
532
+
533
+ [connection pivSession:^(YKFPIVSession *_Nullable session, NSError *_Nullable error) {
534
+ if (error || session == nil) {
535
+ reject(@"PIV_ERROR", error ? error.localizedDescription : @"PIV session not available", error);
536
+ return;
537
+ }
538
+ [session getMetadataForSlot:[self slotFromString:slot]
539
+ completion:^(YKFPIVSlotMetadata *_Nullable metaData, NSError *_Nullable error) {
540
+ if (error || metaData == nil) {
541
+ reject(@"PIV_ERROR", error ? error.localizedDescription : @"Slot metadata not available", error);
542
+ return;
543
+ }
544
+ NSMutableDictionary *result = [NSMutableDictionary dictionary];
545
+ result[@"keyType"] = [self stringFromKeyType:metaData.keyType];
546
+ result[@"pinPolicy"] = [self stringFromPinPolicy:metaData.pinPolicy];
547
+ result[@"touchPolicy"] = [self stringFromTouchPolicy:metaData.touchPolicy];
548
+ result[@"generated"] = @(metaData.generated);
549
+ NSString *publicKey = [self base64FromSecKey:metaData.publicKey];
550
+ if (publicKey != nil) {
551
+ result[@"publicKey"] = publicKey;
552
+ }
553
+ resolve(result);
554
+ }];
555
+ }];
556
+ }
557
+
558
+ - (void)getCertificate:(NSString *)deviceHandle
559
+ slot:(NSString *)slot
560
+ resolve:(RCTPromiseResolveBlock)resolve
561
+ reject:(RCTPromiseRejectBlock)reject {
562
+ id<YKFConnectionProtocol> connection = [self connectionForHandle:deviceHandle reject:reject];
563
+ if (!connection) return;
564
+
565
+ [connection pivSession:^(YKFPIVSession *_Nullable session, NSError *_Nullable error) {
566
+ if (error || session == nil) {
567
+ reject(@"PIV_ERROR", error ? error.localizedDescription : @"PIV session not available", error);
568
+ return;
569
+ }
570
+ [session getCertificateInSlot:[self slotFromString:slot]
571
+ completion:^(SecCertificateRef _Nullable certificate, NSError *_Nullable error) {
572
+ if (error || certificate == NULL) {
573
+ reject(@"PIV_ERROR", error ? error.localizedDescription : @"No certificate in slot", error);
574
+ return;
575
+ }
576
+ resolve([self base64FromSecCertificate:certificate]);
577
+ }];
578
+ }];
579
+ }
580
+
581
+ - (void)putCertificate:(NSString *)deviceHandle
582
+ slot:(NSString *)slot
583
+ certificate:(NSString *)certificate
584
+ compress:(NSNumber *)compress
585
+ resolve:(RCTPromiseResolveBlock)resolve
586
+ reject:(RCTPromiseRejectBlock)reject {
587
+ id<YKFConnectionProtocol> connection = [self connectionForHandle:deviceHandle reject:reject];
588
+ if (!connection) return;
589
+
590
+ NSData *certData = [[NSData alloc] initWithBase64EncodedString:certificate options:0];
591
+ if (certData == nil) {
592
+ reject(@"PIV_ERROR", @"Invalid base64 certificate", nil);
593
+ return;
594
+ }
595
+
596
+ SecCertificateRef certRef = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certData);
597
+ if (certRef == NULL) {
598
+ reject(@"PIV_ERROR", @"Invalid certificate data", nil);
599
+ return;
600
+ }
601
+
602
+ [connection pivSession:^(YKFPIVSession *_Nullable session, NSError *_Nullable error) {
603
+ if (error || session == nil) {
604
+ reject(@"PIV_ERROR", error ? error.localizedDescription : @"PIV session not available", error);
605
+ CFRelease(certRef);
606
+ return;
607
+ }
608
+ [session putCertificate:certRef
609
+ inSlot:[self slotFromString:slot]
610
+ compress:compress != nil && compress.boolValue
611
+ completion:^(NSError *_Nullable error) {
612
+ if (error) {
613
+ reject(@"PIV_ERROR", error.localizedDescription, error);
614
+ return;
615
+ }
616
+ resolve(nil);
617
+ }];
618
+ CFRelease(certRef);
619
+ }];
620
+ }
621
+
622
+ - (void)deleteCertificate:(NSString *)deviceHandle
623
+ slot:(NSString *)slot
624
+ resolve:(RCTPromiseResolveBlock)resolve
625
+ reject:(RCTPromiseRejectBlock)reject {
626
+ id<YKFConnectionProtocol> connection = [self connectionForHandle:deviceHandle reject:reject];
627
+ if (!connection) return;
628
+
629
+ [connection pivSession:^(YKFPIVSession *_Nullable session, NSError *_Nullable error) {
630
+ if (error || session == nil) {
631
+ reject(@"PIV_ERROR", error ? error.localizedDescription : @"PIV session not available", error);
632
+ return;
633
+ }
634
+ [session deleteCertificateInSlot:[self slotFromString:slot] completion:^(NSError *_Nullable error) {
635
+ if (error) {
636
+ reject(@"PIV_ERROR", error.localizedDescription, error);
637
+ return;
638
+ }
639
+ resolve(nil);
640
+ }];
641
+ }];
642
+ }
643
+
644
+ - (void)attestKey:(NSString *)deviceHandle
645
+ slot:(NSString *)slot
646
+ resolve:(RCTPromiseResolveBlock)resolve
647
+ reject:(RCTPromiseRejectBlock)reject {
648
+ id<YKFConnectionProtocol> connection = [self connectionForHandle:deviceHandle reject:reject];
649
+ if (!connection) return;
650
+
651
+ [connection pivSession:^(YKFPIVSession *_Nullable session, NSError *_Nullable error) {
652
+ if (error || session == nil) {
653
+ reject(@"PIV_ERROR", error ? error.localizedDescription : @"PIV session not available", error);
654
+ return;
655
+ }
656
+ [session attestKeyInSlot:[self slotFromString:slot]
657
+ completion:^(SecCertificateRef _Nullable certificate, NSError *_Nullable error) {
658
+ if (error || certificate == NULL) {
659
+ reject(@"PIV_ERROR", error ? error.localizedDescription : @"Attestation failed", error);
660
+ return;
661
+ }
662
+ resolve([self base64FromSecCertificate:certificate]);
663
+ }];
664
+ }];
665
+ }
666
+
667
+ - (void)generateKey:(NSString *)deviceHandle
668
+ slot:(NSString *)slot
669
+ keyType:(NSString *)keyType
670
+ pinPolicy:(NSString *)pinPolicy
671
+ touchPolicy:(NSString *)touchPolicy
672
+ resolve:(RCTPromiseResolveBlock)resolve
673
+ reject:(RCTPromiseRejectBlock)reject {
674
+ id<YKFConnectionProtocol> connection = [self connectionForHandle:deviceHandle reject:reject];
675
+ if (!connection) return;
676
+
677
+ [connection pivSession:^(YKFPIVSession *_Nullable session, NSError *_Nullable error) {
678
+ if (error || session == nil) {
679
+ reject(@"PIV_ERROR", error ? error.localizedDescription : @"PIV session not available", error);
680
+ return;
681
+ }
682
+ [session generateKeyInSlot:[self slotFromString:slot]
683
+ type:[self keyTypeFromString:keyType]
684
+ pinPolicy:[self pinPolicyFromString:pinPolicy]
685
+ touchPolicy:[self touchPolicyFromString:touchPolicy]
686
+ completion:^(SecKeyRef _Nullable key, NSError *_Nullable error) {
687
+ if (error || key == NULL) {
688
+ reject(@"PIV_ERROR", error ? error.localizedDescription : @"Key generation failed", error);
689
+ return;
690
+ }
691
+ resolve([self base64FromSecKey:key]);
692
+ }];
693
+ }];
694
+ }
695
+
696
+ - (void)deleteKey:(NSString *)deviceHandle
697
+ slot:(NSString *)slot
698
+ resolve:(RCTPromiseResolveBlock)resolve
699
+ reject:(RCTPromiseRejectBlock)reject {
700
+ id<YKFConnectionProtocol> connection = [self connectionForHandle:deviceHandle reject:reject];
701
+ if (!connection) return;
702
+
703
+ [connection pivSession:^(YKFPIVSession *_Nullable session, NSError *_Nullable error) {
704
+ if (error || session == nil) {
705
+ reject(@"PIV_ERROR", error ? error.localizedDescription : @"PIV session not available", error);
706
+ return;
707
+ }
708
+ [session deleteKeyInSlot:[self slotFromString:slot] completion:^(NSError *_Nullable error) {
709
+ if (error) {
710
+ reject(@"PIV_ERROR", error.localizedDescription, error);
711
+ return;
712
+ }
713
+ resolve(nil);
714
+ }];
715
+ }];
716
+ }
717
+
718
+ - (void)rawSignOrDecrypt:(NSString *)deviceHandle
719
+ slot:(NSString *)slot
720
+ keyType:(NSString *)keyType
721
+ payload:(NSString *)payload
722
+ resolve:(RCTPromiseResolveBlock)resolve
723
+ reject:(RCTPromiseRejectBlock)reject {
724
+ id<YKFConnectionProtocol> connection = [self connectionForHandle:deviceHandle reject:reject];
725
+ if (!connection) return;
726
+
727
+ NSData *payloadData = [[NSData alloc] initWithBase64EncodedString:payload options:0];
728
+ if (payloadData == nil) {
729
+ reject(@"PIV_ERROR", @"Invalid base64 payload", nil);
730
+ return;
731
+ }
732
+
733
+ YKFPIVSlot pivSlot = [self slotFromString:slot];
734
+ YKFPIVKeyType pivKeyType = [self keyTypeFromString:keyType];
735
+
736
+ // YKFPIVPadding (used internally by signWithKeyInSlot:/decryptWithKeyInSlot:) only
737
+ // handles RSA1024/RSA2048 — RSA3072/RSA4096 keys can be generated on iOS but can't
738
+ // be used for raw sign/decrypt via this SDK version, so fail fast with a clear
739
+ // message instead of a cryptic Security-framework error.
740
+ if (pivKeyType == YKFPIVKeyTypeRSA3072 || pivKeyType == YKFPIVKeyTypeRSA4096) {
741
+ reject(@"PIV_ERROR", @"rawSignOrDecrypt for RSA3072/RSA4096 is not supported by the YubiKit iOS SDK", nil);
742
+ return;
743
+ }
744
+
745
+ NSError *normalizeError = nil;
746
+ NSData *normalizedPayload = [self normalizedPayload:payloadData forKeyType:pivKeyType error:&normalizeError];
747
+ if (normalizedPayload == nil) {
748
+ reject(@"PIV_ERROR", normalizeError ? normalizeError.localizedDescription : @"Invalid payload for key type", normalizeError);
749
+ return;
750
+ }
751
+
752
+ [connection pivSession:^(YKFPIVSession *_Nullable session, NSError *_Nullable error) {
753
+ if (error || session == nil) {
754
+ reject(@"PIV_ERROR", error ? error.localizedDescription : @"PIV session not available", error);
755
+ return;
756
+ }
757
+
758
+ if ([self isDecryptOperationForSlot:pivSlot keyType:pivKeyType]) {
759
+ [session decryptWithKeyInSlot:pivSlot
760
+ algorithm:kSecKeyAlgorithmRSAEncryptionRaw
761
+ encrypted:normalizedPayload
762
+ completion:^(NSData *_Nullable decrypted, NSError *_Nullable error) {
763
+ if (error || decrypted == nil) {
764
+ reject(@"PIV_ERROR", error ? error.localizedDescription : @"Decryption failed", error);
765
+ return;
766
+ }
767
+ resolve([decrypted base64EncodedStringWithOptions:0]);
768
+ }];
769
+ } else {
770
+ [session signWithKeyInSlot:pivSlot
771
+ type:pivKeyType
772
+ algorithm:[self signAlgorithmForKeyType:pivKeyType]
773
+ message:normalizedPayload
774
+ completion:^(NSData *_Nullable signature, NSError *_Nullable error) {
775
+ if (error || signature == nil) {
776
+ reject(@"PIV_ERROR", error ? error.localizedDescription : @"Signing failed", error);
777
+ return;
778
+ }
779
+ resolve([signature base64EncodedStringWithOptions:0]);
780
+ }];
781
+ }
782
+ }];
783
+ }
784
+
785
+ - (void)getBioMetadata:(NSString *)deviceHandle
786
+ resolve:(RCTPromiseResolveBlock)resolve
787
+ reject:(RCTPromiseRejectBlock)reject {
788
+ id<YKFConnectionProtocol> connection = [self connectionForHandle:deviceHandle reject:reject];
789
+ if (!connection) return;
790
+
791
+ [connection pivSession:^(YKFPIVSession *_Nullable session, NSError *_Nullable error) {
792
+ if (error || session == nil) {
793
+ reject(@"PIV_ERROR", error ? error.localizedDescription : @"PIV session not available", error);
794
+ return;
795
+ }
796
+ [session getBioMetadataWithCompletion:^(YKFPIVBioMetadata *_Nullable metaData, NSError *_Nullable error) {
797
+ if (error || metaData == nil) {
798
+ reject(@"PIV_ERROR", error ? error.localizedDescription : @"Bio metadata not available", error);
799
+ return;
800
+ }
801
+ resolve(@{
802
+ @"attemptsRemaining": @(metaData.attemptsRemaining),
803
+ @"temporaryPin": @(metaData.temporaryPin)
804
+ });
805
+ }];
806
+ }];
807
+ }
808
+
809
+ - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const facebook::react::ObjCTurboModule::InitParams &)params {
810
+ return std::make_shared<facebook::react::NativeYubikitPivSpecJSI>(params);
811
+ }
812
+
813
+ @end