@attest-it/cli 0.9.0 → 0.9.1
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/dist/bin/attest-it.js +207 -87
- package/dist/bin/attest-it.js.map +1 -1
- package/dist/index.cjs +137 -80
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +137 -80
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -2300,10 +2300,19 @@ async function runCreate() {
|
|
|
2300
2300
|
default: ""
|
|
2301
2301
|
});
|
|
2302
2302
|
info("Checking available key storage providers...");
|
|
2303
|
+
info(
|
|
2304
|
+
"You may see authentication prompts from 1Password, macOS Keychain, or other security tools."
|
|
2305
|
+
);
|
|
2303
2306
|
const opAvailable = await core.OnePasswordKeyProvider.isInstalled();
|
|
2307
|
+
verbose(` 1Password CLI (op): ${opAvailable ? "found" : "not found"}`);
|
|
2304
2308
|
const keychainAvailable = core.MacOSKeychainKeyProvider.isAvailable();
|
|
2309
|
+
verbose(` macOS Keychain: ${keychainAvailable ? "available" : "not available (not macOS)"}`);
|
|
2305
2310
|
const yubikeyInstalled = await core.YubiKeyProvider.isInstalled();
|
|
2311
|
+
verbose(` YubiKey CLI (ykman): ${yubikeyInstalled ? "found" : "not found"}`);
|
|
2306
2312
|
const yubikeyConnected = yubikeyInstalled ? await core.YubiKeyProvider.isConnected() : false;
|
|
2313
|
+
if (yubikeyInstalled) {
|
|
2314
|
+
verbose(` YubiKey device: ${yubikeyConnected ? "connected" : "not connected"}`);
|
|
2315
|
+
}
|
|
2307
2316
|
const configDir = core.getAttestItConfigDir();
|
|
2308
2317
|
const storageChoices = [
|
|
2309
2318
|
{ name: `File system (${path.join(configDir, "keys")})`, value: "file" }
|
|
@@ -2317,24 +2326,24 @@ async function runCreate() {
|
|
|
2317
2326
|
if (yubikeyInstalled) {
|
|
2318
2327
|
const yubikeyLabel = yubikeyConnected ? "YubiKey (encrypted with challenge-response)" : "YubiKey (not connected - insert YubiKey first)";
|
|
2319
2328
|
storageChoices.push({ name: yubikeyLabel, value: "yubikey" });
|
|
2329
|
+
} else {
|
|
2330
|
+
storageChoices.push({
|
|
2331
|
+
name: theme3.muted("YubiKey (install ykman CLI to enable)"),
|
|
2332
|
+
value: "yubikey-disabled",
|
|
2333
|
+
// @ts-expect-error -- @inquirer/prompts supports disabled property but types may not reflect it
|
|
2334
|
+
disabled: true
|
|
2335
|
+
});
|
|
2320
2336
|
}
|
|
2321
2337
|
const keyStorageType = await prompts.select({
|
|
2322
2338
|
message: "Where should the private key be stored?",
|
|
2323
2339
|
choices: storageChoices
|
|
2324
2340
|
});
|
|
2325
|
-
|
|
2326
|
-
log("Generating Ed25519 keypair...");
|
|
2327
|
-
const keyPair = core.generateEd25519KeyPair();
|
|
2328
|
-
let privateKeyRef;
|
|
2329
|
-
let keyStorageDescription;
|
|
2341
|
+
let storageConfig;
|
|
2330
2342
|
switch (keyStorageType) {
|
|
2331
2343
|
case "file": {
|
|
2332
2344
|
const keysDir = path.join(core.getAttestItConfigDir(), "keys");
|
|
2333
|
-
await promises.mkdir(keysDir, { recursive: true });
|
|
2334
2345
|
const keyPath = path.join(keysDir, `${slug}.pem`);
|
|
2335
|
-
|
|
2336
|
-
privateKeyRef = { type: "file", path: keyPath };
|
|
2337
|
-
keyStorageDescription = keyPath;
|
|
2346
|
+
storageConfig = { type: "file", keyPath };
|
|
2338
2347
|
break;
|
|
2339
2348
|
}
|
|
2340
2349
|
case "keychain": {
|
|
@@ -2342,6 +2351,9 @@ async function runCreate() {
|
|
|
2342
2351
|
error("macOS Keychain is not available on this system");
|
|
2343
2352
|
process.exit(ExitCode.CONFIG_ERROR);
|
|
2344
2353
|
}
|
|
2354
|
+
log("");
|
|
2355
|
+
info("Accessing macOS Keychain to list available keychains...");
|
|
2356
|
+
info("You may be prompted to allow access or enter your password.");
|
|
2345
2357
|
const keychains = await core.MacOSKeychainKeyProvider.listKeychains();
|
|
2346
2358
|
if (keychains.length === 0) {
|
|
2347
2359
|
throw new Error("No keychains found on this system");
|
|
@@ -2377,38 +2389,15 @@ async function runCreate() {
|
|
|
2377
2389
|
return true;
|
|
2378
2390
|
}
|
|
2379
2391
|
});
|
|
2380
|
-
|
|
2381
|
-
const { promisify } = await import('util');
|
|
2382
|
-
const execFileAsync = promisify(execFile);
|
|
2383
|
-
const encodedKey = Buffer.from(keyPair.privateKey).toString("base64");
|
|
2384
|
-
try {
|
|
2385
|
-
const addArgs = [
|
|
2386
|
-
"add-generic-password",
|
|
2387
|
-
"-a",
|
|
2388
|
-
"attest-it",
|
|
2389
|
-
"-s",
|
|
2390
|
-
keychainItemName,
|
|
2391
|
-
"-w",
|
|
2392
|
-
encodedKey,
|
|
2393
|
-
"-U",
|
|
2394
|
-
selectedKeychain.path
|
|
2395
|
-
];
|
|
2396
|
-
await execFileAsync("security", addArgs);
|
|
2397
|
-
} catch (err) {
|
|
2398
|
-
throw new Error(
|
|
2399
|
-
`Failed to store key in macOS Keychain: ${err instanceof Error ? err.message : String(err)}`
|
|
2400
|
-
);
|
|
2401
|
-
}
|
|
2402
|
-
privateKeyRef = {
|
|
2403
|
-
type: "keychain",
|
|
2404
|
-
service: keychainItemName,
|
|
2405
|
-
account: "attest-it",
|
|
2406
|
-
keychain: selectedKeychain.path
|
|
2407
|
-
};
|
|
2408
|
-
keyStorageDescription = `macOS Keychain: ${selectedKeychain.name}/${keychainItemName}`;
|
|
2392
|
+
storageConfig = { type: "keychain", selectedKeychain, keychainItemName };
|
|
2409
2393
|
break;
|
|
2410
2394
|
}
|
|
2411
2395
|
case "1password": {
|
|
2396
|
+
log("");
|
|
2397
|
+
info("Accessing 1Password to list your accounts and vaults...");
|
|
2398
|
+
info(
|
|
2399
|
+
"You may see biometric prompts or be asked to unlock 1Password for each configured account."
|
|
2400
|
+
);
|
|
2412
2401
|
const accounts = await core.OnePasswordKeyProvider.listAccounts();
|
|
2413
2402
|
if (accounts.length === 0) {
|
|
2414
2403
|
throw new Error(
|
|
@@ -2429,7 +2418,7 @@ async function runCreate() {
|
|
|
2429
2418
|
"--format=json"
|
|
2430
2419
|
]);
|
|
2431
2420
|
const details = JSON.parse(stdout);
|
|
2432
|
-
const name2 = details !== null && typeof details === "object" && "name" in details && typeof details.name === "string" ? details.name :
|
|
2421
|
+
const name2 = details !== null && typeof details === "object" && "name" in details && typeof details.name === "string" ? details.name : "[Could not read account name]";
|
|
2433
2422
|
return {
|
|
2434
2423
|
url: acc.url,
|
|
2435
2424
|
email: acc.email,
|
|
@@ -2439,7 +2428,7 @@ async function runCreate() {
|
|
|
2439
2428
|
return {
|
|
2440
2429
|
url: acc.url,
|
|
2441
2430
|
email: acc.email,
|
|
2442
|
-
name:
|
|
2431
|
+
name: "[Could not read account name]"
|
|
2443
2432
|
};
|
|
2444
2433
|
}
|
|
2445
2434
|
})
|
|
@@ -2481,43 +2470,21 @@ async function runCreate() {
|
|
|
2481
2470
|
return true;
|
|
2482
2471
|
}
|
|
2483
2472
|
});
|
|
2484
|
-
const
|
|
2485
|
-
const
|
|
2486
|
-
|
|
2487
|
-
const tempPrivatePath = path.join(tempDir, "private.pem");
|
|
2488
|
-
try {
|
|
2489
|
-
await promises.writeFile(tempPrivatePath, keyPair.privateKey, { mode: 384 });
|
|
2490
|
-
const { execFile: execFile2 } = await import('child_process');
|
|
2491
|
-
const { promisify: promisify2 } = await import('util');
|
|
2492
|
-
const execFileAsync2 = promisify2(execFile2);
|
|
2493
|
-
const opArgs = [
|
|
2494
|
-
"document",
|
|
2495
|
-
"create",
|
|
2496
|
-
tempPrivatePath,
|
|
2497
|
-
"--title",
|
|
2498
|
-
item,
|
|
2499
|
-
"--vault",
|
|
2500
|
-
selectedVault
|
|
2501
|
-
];
|
|
2502
|
-
if (selectedAccount) {
|
|
2503
|
-
opArgs.push("--account", selectedAccount);
|
|
2504
|
-
}
|
|
2505
|
-
await execFileAsync2("op", opArgs);
|
|
2506
|
-
} finally {
|
|
2507
|
-
const { rm } = await import('fs/promises');
|
|
2508
|
-
await rm(tempDir, { recursive: true, force: true }).catch(() => {
|
|
2509
|
-
});
|
|
2510
|
-
}
|
|
2511
|
-
privateKeyRef = {
|
|
2473
|
+
const selectedAccountDetails = accountDetails.find((acc) => acc.url === selectedAccount);
|
|
2474
|
+
const accountDisplayName = selectedAccountDetails?.name ?? selectedAccount;
|
|
2475
|
+
storageConfig = {
|
|
2512
2476
|
type: "1password",
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2477
|
+
selectedAccount,
|
|
2478
|
+
accountDisplayName,
|
|
2479
|
+
selectedVault,
|
|
2480
|
+
item
|
|
2516
2481
|
};
|
|
2517
|
-
keyStorageDescription = `1Password (${selectedVault}/${item})`;
|
|
2518
2482
|
break;
|
|
2519
2483
|
}
|
|
2520
2484
|
case "yubikey": {
|
|
2485
|
+
log("");
|
|
2486
|
+
info("Accessing YubiKey to detect connected devices...");
|
|
2487
|
+
info("Your private key will be encrypted using HMAC challenge-response from the YubiKey.");
|
|
2521
2488
|
if (!await core.YubiKeyProvider.isConnected()) {
|
|
2522
2489
|
error("No YubiKey detected. Please insert your YubiKey and try again.");
|
|
2523
2490
|
process.exit(ExitCode.CONFIG_ERROR);
|
|
@@ -2569,25 +2536,115 @@ async function runCreate() {
|
|
|
2569
2536
|
}
|
|
2570
2537
|
});
|
|
2571
2538
|
const keysDir = path.join(core.getAttestItConfigDir(), "keys");
|
|
2572
|
-
await promises.mkdir(keysDir, { recursive: true });
|
|
2573
2539
|
const encryptedKeyPath = path.join(keysDir, encryptedKeyName);
|
|
2540
|
+
storageConfig = { type: "yubikey", selectedSerial, slot, encryptedKeyPath };
|
|
2541
|
+
break;
|
|
2542
|
+
}
|
|
2543
|
+
default:
|
|
2544
|
+
throw new Error(`Unknown key storage type: ${keyStorageType}`);
|
|
2545
|
+
}
|
|
2546
|
+
log("");
|
|
2547
|
+
log("Generating Ed25519 keypair...");
|
|
2548
|
+
const keyPair = core.generateEd25519KeyPair();
|
|
2549
|
+
let privateKeyRef;
|
|
2550
|
+
let keyStorageDescription;
|
|
2551
|
+
switch (storageConfig.type) {
|
|
2552
|
+
case "file": {
|
|
2553
|
+
log("");
|
|
2554
|
+
info("Creating private key file on disk...");
|
|
2555
|
+
const keysDir = path.join(core.getAttestItConfigDir(), "keys");
|
|
2556
|
+
await promises.mkdir(keysDir, { recursive: true });
|
|
2557
|
+
await promises.writeFile(storageConfig.keyPath, keyPair.privateKey, { mode: 384 });
|
|
2558
|
+
privateKeyRef = { type: "file", path: storageConfig.keyPath };
|
|
2559
|
+
keyStorageDescription = storageConfig.keyPath;
|
|
2560
|
+
break;
|
|
2561
|
+
}
|
|
2562
|
+
case "keychain": {
|
|
2563
|
+
const { execFile } = await import('child_process');
|
|
2564
|
+
const { promisify } = await import('util');
|
|
2565
|
+
const execFileAsync = promisify(execFile);
|
|
2566
|
+
const encodedKey = Buffer.from(keyPair.privateKey).toString("base64");
|
|
2567
|
+
try {
|
|
2568
|
+
const addArgs = [
|
|
2569
|
+
"add-generic-password",
|
|
2570
|
+
"-a",
|
|
2571
|
+
"attest-it",
|
|
2572
|
+
"-s",
|
|
2573
|
+
storageConfig.keychainItemName,
|
|
2574
|
+
"-w",
|
|
2575
|
+
encodedKey,
|
|
2576
|
+
"-U",
|
|
2577
|
+
storageConfig.selectedKeychain.path
|
|
2578
|
+
];
|
|
2579
|
+
await execFileAsync("security", addArgs);
|
|
2580
|
+
} catch (err) {
|
|
2581
|
+
throw new Error(
|
|
2582
|
+
`Failed to store key in macOS Keychain: ${err instanceof Error ? err.message : String(err)}`
|
|
2583
|
+
);
|
|
2584
|
+
}
|
|
2585
|
+
privateKeyRef = {
|
|
2586
|
+
type: "keychain",
|
|
2587
|
+
service: storageConfig.keychainItemName,
|
|
2588
|
+
account: "attest-it",
|
|
2589
|
+
keychain: storageConfig.selectedKeychain.path
|
|
2590
|
+
};
|
|
2591
|
+
keyStorageDescription = `macOS Keychain: ${storageConfig.selectedKeychain.name}/${storageConfig.keychainItemName}`;
|
|
2592
|
+
break;
|
|
2593
|
+
}
|
|
2594
|
+
case "1password": {
|
|
2595
|
+
const { tmpdir } = await import('os');
|
|
2596
|
+
const tempDir = path.join(tmpdir(), `attest-it-${String(Date.now())}`);
|
|
2597
|
+
await promises.mkdir(tempDir, { recursive: true });
|
|
2598
|
+
const tempPrivatePath = path.join(tempDir, "private.pem");
|
|
2599
|
+
try {
|
|
2600
|
+
await promises.writeFile(tempPrivatePath, keyPair.privateKey, { mode: 384 });
|
|
2601
|
+
const { execFile } = await import('child_process');
|
|
2602
|
+
const { promisify } = await import('util');
|
|
2603
|
+
const execFileAsync = promisify(execFile);
|
|
2604
|
+
const opArgs = [
|
|
2605
|
+
"document",
|
|
2606
|
+
"create",
|
|
2607
|
+
tempPrivatePath,
|
|
2608
|
+
"--title",
|
|
2609
|
+
storageConfig.item,
|
|
2610
|
+
"--vault",
|
|
2611
|
+
storageConfig.selectedVault,
|
|
2612
|
+
"--account",
|
|
2613
|
+
storageConfig.selectedAccount
|
|
2614
|
+
];
|
|
2615
|
+
await execFileAsync("op", opArgs);
|
|
2616
|
+
} finally {
|
|
2617
|
+
const { rm } = await import('fs/promises');
|
|
2618
|
+
await rm(tempDir, { recursive: true, force: true }).catch(() => {
|
|
2619
|
+
});
|
|
2620
|
+
}
|
|
2621
|
+
privateKeyRef = {
|
|
2622
|
+
type: "1password",
|
|
2623
|
+
vault: storageConfig.selectedVault,
|
|
2624
|
+
item: storageConfig.item,
|
|
2625
|
+
account: storageConfig.selectedAccount
|
|
2626
|
+
};
|
|
2627
|
+
keyStorageDescription = `1Password (${storageConfig.accountDisplayName}/${storageConfig.selectedVault}/${storageConfig.item})`;
|
|
2628
|
+
break;
|
|
2629
|
+
}
|
|
2630
|
+
case "yubikey": {
|
|
2631
|
+
const keysDir = path.join(core.getAttestItConfigDir(), "keys");
|
|
2632
|
+
await promises.mkdir(keysDir, { recursive: true });
|
|
2574
2633
|
const result = await core.YubiKeyProvider.encryptPrivateKey({
|
|
2575
2634
|
privateKey: keyPair.privateKey,
|
|
2576
|
-
encryptedKeyPath,
|
|
2577
|
-
slot,
|
|
2578
|
-
serial: selectedSerial
|
|
2635
|
+
encryptedKeyPath: storageConfig.encryptedKeyPath,
|
|
2636
|
+
slot: storageConfig.slot,
|
|
2637
|
+
serial: storageConfig.selectedSerial
|
|
2579
2638
|
});
|
|
2580
2639
|
privateKeyRef = {
|
|
2581
2640
|
type: "yubikey",
|
|
2582
2641
|
encryptedKeyPath: result.encryptedKeyPath,
|
|
2583
|
-
slot,
|
|
2584
|
-
serial: selectedSerial
|
|
2642
|
+
slot: storageConfig.slot,
|
|
2643
|
+
serial: storageConfig.selectedSerial
|
|
2585
2644
|
};
|
|
2586
2645
|
keyStorageDescription = result.storageDescription;
|
|
2587
2646
|
break;
|
|
2588
2647
|
}
|
|
2589
|
-
default:
|
|
2590
|
-
throw new Error(`Unknown key storage type: ${keyStorageType}`);
|
|
2591
2648
|
}
|
|
2592
2649
|
const identity = {
|
|
2593
2650
|
name,
|