@visa/cli 5.0.0-rc.364 → 5.0.0-rc.366
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/README.md +1 -1
- package/dist/cli.js +248 -248
- package/dist/mcp-server/index.js +203 -203
- package/dist/merchant-ucp-mcp/index.js +6 -6
- package/dist/skills/pair-visa-agent/RUNTIMES.md +22 -28
- package/dist/skills/pair-visa-agent/SKILL.md +56 -73
- package/dist/skills/pair-visa-agent/scripts/__tests__/setup.test.mjs +62 -43
- package/dist/skills/pair-visa-agent/scripts/setup.mjs +11 -11
- package/install.ps1 +1 -1
- package/install.sh +1 -1
- package/native/bin/darwin-arm64/visa-runtime-signer +0 -0
- package/native/bin/darwin-x64/visa-runtime-signer +0 -0
- package/native/bin/linux-arm64/visa-runtime-signer +0 -0
- package/native/bin/linux-x64/visa-runtime-signer +0 -0
- package/native/bin/win32-arm64/visa-runtime-signer.exe +0 -0
- package/native/bin/win32-x64/visa-runtime-signer.exe +0 -0
- package/package.json +1 -5
- package/server.json +2 -2
- package/native/bin/win32-x64/visa-keychain-win.exe +0 -0
- package/native/build-win.bat +0 -42
- package/native/visa-keychain-win.cpp +0 -481
- package/native/visa-keychain.m +0 -367
package/native/visa-keychain.m
DELETED
|
@@ -1,367 +0,0 @@
|
|
|
1
|
-
#import <Foundation/Foundation.h>
|
|
2
|
-
#import <Security/Security.h>
|
|
3
|
-
#import <LocalAuthentication/LocalAuthentication.h>
|
|
4
|
-
#import <AppKit/AppKit.h>
|
|
5
|
-
|
|
6
|
-
// SPKI DER header for P-256 (prime256v1) uncompressed public key
|
|
7
|
-
static const unsigned char SPKI_HEADER[] = {
|
|
8
|
-
0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2A, 0x86,
|
|
9
|
-
0x48, 0xCE, 0x3D, 0x02, 0x01, 0x06, 0x08, 0x2A,
|
|
10
|
-
0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07, 0x03,
|
|
11
|
-
0x42, 0x00
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
static NSString *kService = @"visa-cli";
|
|
15
|
-
static NSString *kKeyAcct = @"attestation-key";
|
|
16
|
-
static NSString *kTokenAcct = @"session-token";
|
|
17
|
-
|
|
18
|
-
static NSString *spkiPublicKeyB64(SecKeyRef key) {
|
|
19
|
-
SecKeyRef pubKey = SecKeyCopyPublicKey(key);
|
|
20
|
-
if (!pubKey) return nil;
|
|
21
|
-
|
|
22
|
-
CFErrorRef pubErr = NULL;
|
|
23
|
-
CFDataRef rawPub = SecKeyCopyExternalRepresentation(pubKey, &pubErr);
|
|
24
|
-
CFRelease(pubKey);
|
|
25
|
-
if (!rawPub) {
|
|
26
|
-
if (pubErr) CFRelease(pubErr);
|
|
27
|
-
return nil;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
NSMutableData *spki = [NSMutableData dataWithBytes:SPKI_HEADER length:sizeof(SPKI_HEADER)];
|
|
31
|
-
[spki appendData:(__bridge NSData *)rawPub];
|
|
32
|
-
CFRelease(rawPub);
|
|
33
|
-
return [spki base64EncodedStringWithOptions:0];
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// ---------------------------------------------------------------------------
|
|
37
|
-
// Keychain helpers for generic password items
|
|
38
|
-
// ---------------------------------------------------------------------------
|
|
39
|
-
|
|
40
|
-
static OSStatus storeItem(NSString *account, NSData *data) {
|
|
41
|
-
NSDictionary *delQ = @{
|
|
42
|
-
(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
|
|
43
|
-
(__bridge id)kSecAttrService: kService,
|
|
44
|
-
(__bridge id)kSecAttrAccount: account,
|
|
45
|
-
};
|
|
46
|
-
SecItemDelete((__bridge CFDictionaryRef)delQ);
|
|
47
|
-
|
|
48
|
-
NSDictionary *addQ = @{
|
|
49
|
-
(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
|
|
50
|
-
(__bridge id)kSecAttrService: kService,
|
|
51
|
-
(__bridge id)kSecAttrAccount: account,
|
|
52
|
-
(__bridge id)kSecValueData: data,
|
|
53
|
-
};
|
|
54
|
-
return SecItemAdd((__bridge CFDictionaryRef)addQ, NULL);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
static NSData *retrieveItem(NSString *account) {
|
|
58
|
-
NSDictionary *q = @{
|
|
59
|
-
(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
|
|
60
|
-
(__bridge id)kSecAttrService: kService,
|
|
61
|
-
(__bridge id)kSecAttrAccount: account,
|
|
62
|
-
(__bridge id)kSecReturnData: @YES,
|
|
63
|
-
};
|
|
64
|
-
CFTypeRef result = NULL;
|
|
65
|
-
OSStatus st = SecItemCopyMatching((__bridge CFDictionaryRef)q, &result);
|
|
66
|
-
if (st != errSecSuccess) return nil;
|
|
67
|
-
return (__bridge NSData *)result;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
static void deleteItem(NSString *account) {
|
|
71
|
-
NSDictionary *q = @{
|
|
72
|
-
(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
|
|
73
|
-
(__bridge id)kSecAttrService: kService,
|
|
74
|
-
(__bridge id)kSecAttrAccount: account,
|
|
75
|
-
};
|
|
76
|
-
SecItemDelete((__bridge CFDictionaryRef)q);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// ---------------------------------------------------------------------------
|
|
80
|
-
// Key operations — raw EC key bytes stored as Keychain generic password,
|
|
81
|
-
// imported as in-memory SecKeyRef for signing (avoids CDSA limitations)
|
|
82
|
-
// ---------------------------------------------------------------------------
|
|
83
|
-
|
|
84
|
-
int cmd_generate_key(void) {
|
|
85
|
-
// Generate P-256 key pair in memory
|
|
86
|
-
NSDictionary *attrs = @{
|
|
87
|
-
(__bridge id)kSecAttrKeyType: (__bridge id)kSecAttrKeyTypeECSECPrimeRandom,
|
|
88
|
-
(__bridge id)kSecAttrKeySizeInBits: @256,
|
|
89
|
-
(__bridge id)kSecAttrIsPermanent: @NO,
|
|
90
|
-
};
|
|
91
|
-
CFErrorRef keyErr = NULL;
|
|
92
|
-
SecKeyRef privKey = SecKeyCreateRandomKey((__bridge CFDictionaryRef)attrs, &keyErr);
|
|
93
|
-
if (!privKey) {
|
|
94
|
-
if (keyErr) CFRelease(keyErr);
|
|
95
|
-
printf("ERROR:Key generation failed\n");
|
|
96
|
-
return 1;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Export raw private key bytes (caller stores via `security` CLI)
|
|
100
|
-
CFErrorRef expErr = NULL;
|
|
101
|
-
CFDataRef rawPriv = SecKeyCopyExternalRepresentation(privKey, &expErr);
|
|
102
|
-
if (!rawPriv) {
|
|
103
|
-
CFRelease(privKey);
|
|
104
|
-
if (expErr) CFRelease(expErr);
|
|
105
|
-
printf("ERROR:Failed to export private key\n");
|
|
106
|
-
return 1;
|
|
107
|
-
}
|
|
108
|
-
NSString *privB64 = [(__bridge NSData *)rawPriv base64EncodedStringWithOptions:0];
|
|
109
|
-
CFRelease(rawPriv);
|
|
110
|
-
|
|
111
|
-
NSString *pubB64 = spkiPublicKeyB64(privKey);
|
|
112
|
-
CFRelease(privKey);
|
|
113
|
-
if (!pubB64) {
|
|
114
|
-
printf("ERROR:Failed to export public key\n");
|
|
115
|
-
return 1;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Output both keys: OK:<private-b64>:<public-spki-b64>
|
|
119
|
-
printf("OK:%s:%s\n", privB64.UTF8String, pubB64.UTF8String);
|
|
120
|
-
return 0;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
int cmd_public_key(void) {
|
|
124
|
-
NSFileHandle *input = [NSFileHandle fileHandleWithStandardInput];
|
|
125
|
-
NSData *stdinData = [input readDataToEndOfFile];
|
|
126
|
-
NSString *keyB64 = [[NSString alloc] initWithData:stdinData encoding:NSUTF8StringEncoding];
|
|
127
|
-
keyB64 = [keyB64 stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
|
128
|
-
|
|
129
|
-
if (!keyB64 || keyB64.length == 0) {
|
|
130
|
-
printf("ERROR:No key provided on stdin\n");
|
|
131
|
-
return 1;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
NSData *rawKey = [[NSData alloc] initWithBase64EncodedString:keyB64 options:0];
|
|
135
|
-
if (!rawKey) {
|
|
136
|
-
printf("ERROR:Invalid base64 key data\n");
|
|
137
|
-
return 1;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
NSDictionary *keyAttrs = @{
|
|
141
|
-
(__bridge id)kSecAttrKeyType: (__bridge id)kSecAttrKeyTypeECSECPrimeRandom,
|
|
142
|
-
(__bridge id)kSecAttrKeyClass: (__bridge id)kSecAttrKeyClassPrivate,
|
|
143
|
-
(__bridge id)kSecAttrKeySizeInBits: @256,
|
|
144
|
-
};
|
|
145
|
-
CFErrorRef importErr = NULL;
|
|
146
|
-
SecKeyRef privKey = SecKeyCreateWithData(
|
|
147
|
-
(__bridge CFDataRef)rawKey,
|
|
148
|
-
(__bridge CFDictionaryRef)keyAttrs,
|
|
149
|
-
&importErr);
|
|
150
|
-
if (!privKey) {
|
|
151
|
-
if (importErr) CFRelease(importErr);
|
|
152
|
-
printf("ERROR:Failed to import key for public export\n");
|
|
153
|
-
return 1;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
NSString *pubB64 = spkiPublicKeyB64(privKey);
|
|
157
|
-
CFRelease(privKey);
|
|
158
|
-
if (!pubB64) {
|
|
159
|
-
printf("ERROR:Failed to export public key\n");
|
|
160
|
-
return 1;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
printf("OK:%s\n", pubB64.UTF8String);
|
|
164
|
-
return 0;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
int cmd_sign(const char *challenge, const char *reason) {
|
|
168
|
-
// Read base64-encoded private key from stdin (avoids Keychain ACL issues
|
|
169
|
-
// and keeps the key out of the process argument list visible in `ps`)
|
|
170
|
-
NSFileHandle *input = [NSFileHandle fileHandleWithStandardInput];
|
|
171
|
-
NSData *stdinData = [input readDataToEndOfFile];
|
|
172
|
-
NSString *keyB64 = [[NSString alloc] initWithData:stdinData encoding:NSUTF8StringEncoding];
|
|
173
|
-
keyB64 = [keyB64 stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
|
174
|
-
|
|
175
|
-
if (!keyB64 || keyB64.length == 0) {
|
|
176
|
-
printf("ERROR:No key provided on stdin\n");
|
|
177
|
-
return 1;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
NSData *rawKey = [[NSData alloc] initWithBase64EncodedString:keyB64 options:0];
|
|
181
|
-
if (!rawKey) {
|
|
182
|
-
printf("ERROR:Invalid base64 key data\n");
|
|
183
|
-
return 1;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// Authentication gate (Touch ID OR password — whichever the user has).
|
|
187
|
-
//
|
|
188
|
-
// We use LAPolicyDeviceOwnerAuthentication, which presents a single
|
|
189
|
-
// system dialog: Touch ID on enrolled hardware, password fallback
|
|
190
|
-
// everywhere else (including users who skipped biometric setup and
|
|
191
|
-
// Macs without a Touch ID sensor). This front-focus fix applies to
|
|
192
|
-
// BOTH dialogs — they are the same system-level window, just with
|
|
193
|
-
// different input modes. If we only fixed the Touch ID path we would
|
|
194
|
-
// still be burying the password prompt behind the terminal for every
|
|
195
|
-
// non-biometric user.
|
|
196
|
-
//
|
|
197
|
-
// Front-focus fix: bare CLI binaries have no NSApplication, so the
|
|
198
|
-
// system LAContext dialog gets parented to whichever process happens to
|
|
199
|
-
// be frontmost (usually the terminal or Claude Code), which puts the
|
|
200
|
-
// prompt *behind* the user's current window — users never see it, time
|
|
201
|
-
// out, and their spend gets cancelled.
|
|
202
|
-
//
|
|
203
|
-
// Making the binary an Accessory-policy NSApplication and explicitly
|
|
204
|
-
// calling activateIgnoringOtherApps: before evaluatePolicy tells the
|
|
205
|
-
// window server this process is the new frontmost app, so the auth
|
|
206
|
-
// prompt (biometric or password) is raised on top of all other
|
|
207
|
-
// windows. Accessory policy means no Dock icon, no menu bar — the
|
|
208
|
-
// binary stays invisible except for the dialog itself.
|
|
209
|
-
[NSApplication sharedApplication];
|
|
210
|
-
[NSApp setActivationPolicy:NSApplicationActivationPolicyAccessory];
|
|
211
|
-
[NSApp activateIgnoringOtherApps:YES];
|
|
212
|
-
|
|
213
|
-
LAContext *ctx = [[LAContext alloc] init];
|
|
214
|
-
NSString *reasonStr = reason
|
|
215
|
-
? [NSString stringWithUTF8String:reason]
|
|
216
|
-
: @"approve payment";
|
|
217
|
-
|
|
218
|
-
__block BOOL done = NO;
|
|
219
|
-
__block BOOL authOk = NO;
|
|
220
|
-
[ctx evaluatePolicy:LAPolicyDeviceOwnerAuthentication
|
|
221
|
-
localizedReason:reasonStr
|
|
222
|
-
reply:^(BOOL success, NSError *err) {
|
|
223
|
-
authOk = success;
|
|
224
|
-
done = YES;
|
|
225
|
-
}];
|
|
226
|
-
|
|
227
|
-
// Pump the main run loop while waiting for the reply block. Using
|
|
228
|
-
// dispatch_semaphore_wait here would freeze the main thread and
|
|
229
|
-
// prevent the WindowServer from delivering the activation we just
|
|
230
|
-
// requested — the dialog would come up, but not on top.
|
|
231
|
-
NSDate *timeout = [NSDate dateWithTimeIntervalSinceNow:55.0];
|
|
232
|
-
while (!done) {
|
|
233
|
-
@autoreleasepool {
|
|
234
|
-
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
|
|
235
|
-
beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.05]];
|
|
236
|
-
if ([[NSDate date] compare:timeout] == NSOrderedDescending) {
|
|
237
|
-
printf("ERROR:Authentication timed out\n");
|
|
238
|
-
return 1;
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
if (!authOk) {
|
|
244
|
-
printf("ERROR:Authentication cancelled by user\n");
|
|
245
|
-
return 1;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
// Import as in-memory SecKeyRef (modern API, not CDSA)
|
|
249
|
-
NSDictionary *keyAttrs = @{
|
|
250
|
-
(__bridge id)kSecAttrKeyType: (__bridge id)kSecAttrKeyTypeECSECPrimeRandom,
|
|
251
|
-
(__bridge id)kSecAttrKeyClass: (__bridge id)kSecAttrKeyClassPrivate,
|
|
252
|
-
(__bridge id)kSecAttrKeySizeInBits: @256,
|
|
253
|
-
};
|
|
254
|
-
CFErrorRef importErr = NULL;
|
|
255
|
-
SecKeyRef privKey = SecKeyCreateWithData(
|
|
256
|
-
(__bridge CFDataRef)rawKey,
|
|
257
|
-
(__bridge CFDictionaryRef)keyAttrs,
|
|
258
|
-
&importErr);
|
|
259
|
-
if (!privKey) {
|
|
260
|
-
if (importErr) CFRelease(importErr);
|
|
261
|
-
printf("ERROR:Failed to import key for signing\n");
|
|
262
|
-
return 1;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// Sign (ECDSA-SHA256, same format as Node.js crypto.createSign('SHA256'))
|
|
266
|
-
NSData *data = [[NSString stringWithUTF8String:challenge] dataUsingEncoding:NSUTF8StringEncoding];
|
|
267
|
-
CFErrorRef sigErr = NULL;
|
|
268
|
-
CFDataRef sig = SecKeyCreateSignature(
|
|
269
|
-
privKey,
|
|
270
|
-
kSecKeyAlgorithmECDSASignatureMessageX962SHA256,
|
|
271
|
-
(__bridge CFDataRef)data,
|
|
272
|
-
&sigErr);
|
|
273
|
-
CFRelease(privKey);
|
|
274
|
-
|
|
275
|
-
if (!sig) {
|
|
276
|
-
if (sigErr) CFRelease(sigErr);
|
|
277
|
-
printf("ERROR:Signing failed\n");
|
|
278
|
-
return 1;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
printf("OK:%s\n", [(__bridge NSData *)sig base64EncodedStringWithOptions:0].UTF8String);
|
|
282
|
-
CFRelease(sig);
|
|
283
|
-
return 0;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
int cmd_delete_key(void) {
|
|
287
|
-
// Legacy: also clean up any old keychain item from previous versions
|
|
288
|
-
deleteItem(kKeyAcct);
|
|
289
|
-
printf("OK\n");
|
|
290
|
-
return 0;
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
// ---------------------------------------------------------------------------
|
|
294
|
-
// Session token operations
|
|
295
|
-
// ---------------------------------------------------------------------------
|
|
296
|
-
|
|
297
|
-
int cmd_store_token(const char *token) {
|
|
298
|
-
NSData *val = [[NSString stringWithUTF8String:token] dataUsingEncoding:NSUTF8StringEncoding];
|
|
299
|
-
OSStatus st = storeItem(kTokenAcct, val);
|
|
300
|
-
if (st != errSecSuccess) {
|
|
301
|
-
printf("ERROR:Failed to store token (status %d)\n", (int)st);
|
|
302
|
-
return 1;
|
|
303
|
-
}
|
|
304
|
-
printf("OK\n");
|
|
305
|
-
return 0;
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
int cmd_get_token(void) {
|
|
309
|
-
NSData *d = retrieveItem(kTokenAcct);
|
|
310
|
-
if (!d) {
|
|
311
|
-
printf("ERROR:not_found\n");
|
|
312
|
-
return 1;
|
|
313
|
-
}
|
|
314
|
-
NSString *tok = [[NSString alloc] initWithData:d encoding:NSUTF8StringEncoding];
|
|
315
|
-
printf("OK:%s\n", tok.UTF8String);
|
|
316
|
-
return 0;
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
int cmd_delete_token(void) {
|
|
320
|
-
deleteItem(kTokenAcct);
|
|
321
|
-
printf("OK\n");
|
|
322
|
-
return 0;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
// ---------------------------------------------------------------------------
|
|
326
|
-
// Check
|
|
327
|
-
// ---------------------------------------------------------------------------
|
|
328
|
-
|
|
329
|
-
int cmd_check(void) {
|
|
330
|
-
LAContext *ctx = [[LAContext alloc] init];
|
|
331
|
-
NSError *err = nil;
|
|
332
|
-
if (![ctx canEvaluatePolicy:LAPolicyDeviceOwnerAuthentication error:&err]) {
|
|
333
|
-
printf("ERROR:Touch ID unavailable\n");
|
|
334
|
-
return 1;
|
|
335
|
-
}
|
|
336
|
-
printf("OK\n");
|
|
337
|
-
return 0;
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
// ---------------------------------------------------------------------------
|
|
341
|
-
// Main
|
|
342
|
-
// ---------------------------------------------------------------------------
|
|
343
|
-
|
|
344
|
-
int main(int argc, const char *argv[]) {
|
|
345
|
-
@autoreleasepool {
|
|
346
|
-
if (argc < 2) {
|
|
347
|
-
fprintf(stderr, "Usage: visa-keychain <command> [args...]\n");
|
|
348
|
-
return 1;
|
|
349
|
-
}
|
|
350
|
-
const char *cmd = argv[1];
|
|
351
|
-
if (strcmp(cmd, "generate-key") == 0) return cmd_generate_key();
|
|
352
|
-
else if (strcmp(cmd, "public-key") == 0) return cmd_public_key();
|
|
353
|
-
else if (strcmp(cmd, "sign") == 0) {
|
|
354
|
-
if (argc < 3) { fprintf(stderr, "Usage: visa-keychain sign <challenge> [reason]\n"); return 1; }
|
|
355
|
-
return cmd_sign(argv[2], argc >= 4 ? argv[3] : NULL);
|
|
356
|
-
}
|
|
357
|
-
else if (strcmp(cmd, "delete-key") == 0) return cmd_delete_key();
|
|
358
|
-
else if (strcmp(cmd, "store-token") == 0) {
|
|
359
|
-
if (argc < 3) { fprintf(stderr, "Usage: visa-keychain store-token <token>\n"); return 1; }
|
|
360
|
-
return cmd_store_token(argv[2]);
|
|
361
|
-
}
|
|
362
|
-
else if (strcmp(cmd, "get-token") == 0) return cmd_get_token();
|
|
363
|
-
else if (strcmp(cmd, "delete-token") == 0) return cmd_delete_token();
|
|
364
|
-
else if (strcmp(cmd, "check") == 0) return cmd_check();
|
|
365
|
-
else { fprintf(stderr, "Unknown command: %s\n", cmd); return 1; }
|
|
366
|
-
}
|
|
367
|
-
}
|