@doko/react-native-yubikit 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +20 -0
- package/README.md +126 -0
- package/RNYubikit.podspec +22 -0
- package/android/build.gradle +63 -0
- package/android/src/main/AndroidManifest.xml +9 -0
- package/android/src/main/java/com/yubikit/YubiKitManagerHolder.kt +330 -0
- package/android/src/main/java/com/yubikit/YubikitCoreModule.kt +142 -0
- package/android/src/main/java/com/yubikit/YubikitFidoModule.kt +418 -0
- package/android/src/main/java/com/yubikit/YubikitManagementModule.kt +132 -0
- package/android/src/main/java/com/yubikit/YubikitOathModule.kt +224 -0
- package/android/src/main/java/com/yubikit/YubikitOpenPgpModule.kt +317 -0
- package/android/src/main/java/com/yubikit/YubikitPackage.kt +44 -0
- package/android/src/main/java/com/yubikit/YubikitPivModule.kt +310 -0
- package/android/src/main/java/com/yubikit/YubikitSupportModule.kt +108 -0
- package/android/src/main/java/com/yubikit/YubikitYubiOtpModule.kt +248 -0
- package/android/src/main/java/com/yubikit/utils/YubikitUtils.kt +321 -0
- package/ios/YubikitCore.h +5 -0
- package/ios/YubikitCore.mm +101 -0
- package/ios/YubikitDeviceInfoHelper.h +13 -0
- package/ios/YubikitDeviceInfoHelper.mm +110 -0
- package/ios/YubikitFido.h +5 -0
- package/ios/YubikitFido.mm +373 -0
- package/ios/YubikitManagement.h +5 -0
- package/ios/YubikitManagement.mm +153 -0
- package/ios/YubikitManager.h +27 -0
- package/ios/YubikitManager.m +169 -0
- package/ios/YubikitOath.h +5 -0
- package/ios/YubikitOath.mm +583 -0
- package/ios/YubikitOpenPgp.h +5 -0
- package/ios/YubikitOpenPgp.mm +210 -0
- package/ios/YubikitPiv.h +5 -0
- package/ios/YubikitPiv.mm +813 -0
- package/ios/YubikitSupport.h +5 -0
- package/ios/YubikitSupport.mm +58 -0
- package/ios/YubikitYubiOtp.h +5 -0
- package/ios/YubikitYubiOtp.mm +120 -0
- package/lib/module/core.js +34 -0
- package/lib/module/core.js.map +1 -0
- package/lib/module/fido.js +40 -0
- package/lib/module/fido.js.map +1 -0
- package/lib/module/hooks/index.js +5 -0
- package/lib/module/hooks/index.js.map +1 -0
- package/lib/module/hooks/use-yubikey.js +12 -0
- package/lib/module/hooks/use-yubikey.js.map +1 -0
- package/lib/module/hooks/yubikey-provider.js +85 -0
- package/lib/module/hooks/yubikey-provider.js.map +1 -0
- package/lib/module/index.js +12 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/management.js +17 -0
- package/lib/module/management.js.map +1 -0
- package/lib/module/oath.js +53 -0
- package/lib/module/oath.js.map +1 -0
- package/lib/module/openpgp.js +89 -0
- package/lib/module/openpgp.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/piv.js +71 -0
- package/lib/module/piv.js.map +1 -0
- package/lib/module/specs/NativeYubikitCore.js +10 -0
- package/lib/module/specs/NativeYubikitCore.js.map +1 -0
- package/lib/module/specs/NativeYubikitFido.js +5 -0
- package/lib/module/specs/NativeYubikitFido.js.map +1 -0
- package/lib/module/specs/NativeYubikitManagement.js +5 -0
- package/lib/module/specs/NativeYubikitManagement.js.map +1 -0
- package/lib/module/specs/NativeYubikitOath.js +5 -0
- package/lib/module/specs/NativeYubikitOath.js.map +1 -0
- package/lib/module/specs/NativeYubikitOpenPgp.js +5 -0
- package/lib/module/specs/NativeYubikitOpenPgp.js.map +1 -0
- package/lib/module/specs/NativeYubikitPiv.js +5 -0
- package/lib/module/specs/NativeYubikitPiv.js.map +1 -0
- package/lib/module/specs/NativeYubikitSupport.js +5 -0
- package/lib/module/specs/NativeYubikitSupport.js.map +1 -0
- package/lib/module/specs/NativeYubikitYubiOtp.js +5 -0
- package/lib/module/specs/NativeYubikitYubiOtp.js.map +1 -0
- package/lib/module/support.js +11 -0
- package/lib/module/support.js.map +1 -0
- package/lib/module/types.js +2 -0
- package/lib/module/types.js.map +1 -0
- package/lib/module/yubiotp.js +32 -0
- package/lib/module/yubiotp.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/core.d.ts +14 -0
- package/lib/typescript/src/core.d.ts.map +1 -0
- package/lib/typescript/src/fido.d.ts +24 -0
- package/lib/typescript/src/fido.d.ts.map +1 -0
- package/lib/typescript/src/hooks/index.d.ts +3 -0
- package/lib/typescript/src/hooks/index.d.ts.map +1 -0
- package/lib/typescript/src/hooks/use-yubikey.d.ts +3 -0
- package/lib/typescript/src/hooks/use-yubikey.d.ts.map +1 -0
- package/lib/typescript/src/hooks/yubikey-provider.d.ts +23 -0
- package/lib/typescript/src/hooks/yubikey-provider.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +10 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/management.d.ts +8 -0
- package/lib/typescript/src/management.d.ts.map +1 -0
- package/lib/typescript/src/oath.d.ts +20 -0
- package/lib/typescript/src/oath.d.ts.map +1 -0
- package/lib/typescript/src/openpgp.d.ts +35 -0
- package/lib/typescript/src/openpgp.d.ts.map +1 -0
- package/lib/typescript/src/piv.d.ts +26 -0
- package/lib/typescript/src/piv.d.ts.map +1 -0
- package/lib/typescript/src/specs/NativeYubikitCore.d.ts +49 -0
- package/lib/typescript/src/specs/NativeYubikitCore.d.ts.map +1 -0
- package/lib/typescript/src/specs/NativeYubikitFido.d.ts +121 -0
- package/lib/typescript/src/specs/NativeYubikitFido.d.ts.map +1 -0
- package/lib/typescript/src/specs/NativeYubikitManagement.d.ts +53 -0
- package/lib/typescript/src/specs/NativeYubikitManagement.d.ts.map +1 -0
- package/lib/typescript/src/specs/NativeYubikitOath.d.ts +70 -0
- package/lib/typescript/src/specs/NativeYubikitOath.d.ts.map +1 -0
- package/lib/typescript/src/specs/NativeYubikitOpenPgp.d.ts +53 -0
- package/lib/typescript/src/specs/NativeYubikitOpenPgp.d.ts.map +1 -0
- package/lib/typescript/src/specs/NativeYubikitPiv.d.ts +47 -0
- package/lib/typescript/src/specs/NativeYubikitPiv.d.ts.map +1 -0
- package/lib/typescript/src/specs/NativeYubikitSupport.d.ts +49 -0
- package/lib/typescript/src/specs/NativeYubikitSupport.d.ts.map +1 -0
- package/lib/typescript/src/specs/NativeYubikitYubiOtp.d.ts +45 -0
- package/lib/typescript/src/specs/NativeYubikitYubiOtp.d.ts.map +1 -0
- package/lib/typescript/src/support.d.ts +6 -0
- package/lib/typescript/src/support.d.ts.map +1 -0
- package/lib/typescript/src/types.d.ts +293 -0
- package/lib/typescript/src/types.d.ts.map +1 -0
- package/lib/typescript/src/yubiotp.d.ts +13 -0
- package/lib/typescript/src/yubiotp.d.ts.map +1 -0
- package/package.json +181 -0
- package/src/core.ts +58 -0
- package/src/fido.ts +112 -0
- package/src/hooks/index.tsx +2 -0
- package/src/hooks/use-yubikey.tsx +10 -0
- package/src/hooks/yubikey-provider.tsx +146 -0
- package/src/index.tsx +10 -0
- package/src/management.ts +44 -0
- package/src/oath.ts +134 -0
- package/src/openpgp.ts +220 -0
- package/src/piv.ts +193 -0
- package/src/specs/NativeYubikitCore.ts +68 -0
- package/src/specs/NativeYubikitFido.ts +149 -0
- package/src/specs/NativeYubikitManagement.ts +78 -0
- package/src/specs/NativeYubikitOath.ts +110 -0
- package/src/specs/NativeYubikitOpenPgp.ts +148 -0
- package/src/specs/NativeYubikitPiv.ts +108 -0
- package/src/specs/NativeYubikitSupport.ts +61 -0
- package/src/specs/NativeYubikitYubiOtp.ts +82 -0
- package/src/support.ts +18 -0
- package/src/types.ts +404 -0
- package/src/yubiotp.ts +96 -0
|
@@ -0,0 +1,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 ¶m : 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,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
|