@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.js
CHANGED
|
@@ -2273,10 +2273,19 @@ async function runCreate() {
|
|
|
2273
2273
|
default: ""
|
|
2274
2274
|
});
|
|
2275
2275
|
info("Checking available key storage providers...");
|
|
2276
|
+
info(
|
|
2277
|
+
"You may see authentication prompts from 1Password, macOS Keychain, or other security tools."
|
|
2278
|
+
);
|
|
2276
2279
|
const opAvailable = await OnePasswordKeyProvider.isInstalled();
|
|
2280
|
+
verbose(` 1Password CLI (op): ${opAvailable ? "found" : "not found"}`);
|
|
2277
2281
|
const keychainAvailable = MacOSKeychainKeyProvider.isAvailable();
|
|
2282
|
+
verbose(` macOS Keychain: ${keychainAvailable ? "available" : "not available (not macOS)"}`);
|
|
2278
2283
|
const yubikeyInstalled = await YubiKeyProvider.isInstalled();
|
|
2284
|
+
verbose(` YubiKey CLI (ykman): ${yubikeyInstalled ? "found" : "not found"}`);
|
|
2279
2285
|
const yubikeyConnected = yubikeyInstalled ? await YubiKeyProvider.isConnected() : false;
|
|
2286
|
+
if (yubikeyInstalled) {
|
|
2287
|
+
verbose(` YubiKey device: ${yubikeyConnected ? "connected" : "not connected"}`);
|
|
2288
|
+
}
|
|
2280
2289
|
const configDir = getAttestItConfigDir();
|
|
2281
2290
|
const storageChoices = [
|
|
2282
2291
|
{ name: `File system (${join(configDir, "keys")})`, value: "file" }
|
|
@@ -2290,24 +2299,24 @@ async function runCreate() {
|
|
|
2290
2299
|
if (yubikeyInstalled) {
|
|
2291
2300
|
const yubikeyLabel = yubikeyConnected ? "YubiKey (encrypted with challenge-response)" : "YubiKey (not connected - insert YubiKey first)";
|
|
2292
2301
|
storageChoices.push({ name: yubikeyLabel, value: "yubikey" });
|
|
2302
|
+
} else {
|
|
2303
|
+
storageChoices.push({
|
|
2304
|
+
name: theme3.muted("YubiKey (install ykman CLI to enable)"),
|
|
2305
|
+
value: "yubikey-disabled",
|
|
2306
|
+
// @ts-expect-error -- @inquirer/prompts supports disabled property but types may not reflect it
|
|
2307
|
+
disabled: true
|
|
2308
|
+
});
|
|
2293
2309
|
}
|
|
2294
2310
|
const keyStorageType = await select({
|
|
2295
2311
|
message: "Where should the private key be stored?",
|
|
2296
2312
|
choices: storageChoices
|
|
2297
2313
|
});
|
|
2298
|
-
|
|
2299
|
-
log("Generating Ed25519 keypair...");
|
|
2300
|
-
const keyPair = generateEd25519KeyPair();
|
|
2301
|
-
let privateKeyRef;
|
|
2302
|
-
let keyStorageDescription;
|
|
2314
|
+
let storageConfig;
|
|
2303
2315
|
switch (keyStorageType) {
|
|
2304
2316
|
case "file": {
|
|
2305
2317
|
const keysDir = join(getAttestItConfigDir(), "keys");
|
|
2306
|
-
await mkdir(keysDir, { recursive: true });
|
|
2307
2318
|
const keyPath = join(keysDir, `${slug}.pem`);
|
|
2308
|
-
|
|
2309
|
-
privateKeyRef = { type: "file", path: keyPath };
|
|
2310
|
-
keyStorageDescription = keyPath;
|
|
2319
|
+
storageConfig = { type: "file", keyPath };
|
|
2311
2320
|
break;
|
|
2312
2321
|
}
|
|
2313
2322
|
case "keychain": {
|
|
@@ -2315,6 +2324,9 @@ async function runCreate() {
|
|
|
2315
2324
|
error("macOS Keychain is not available on this system");
|
|
2316
2325
|
process.exit(ExitCode.CONFIG_ERROR);
|
|
2317
2326
|
}
|
|
2327
|
+
log("");
|
|
2328
|
+
info("Accessing macOS Keychain to list available keychains...");
|
|
2329
|
+
info("You may be prompted to allow access or enter your password.");
|
|
2318
2330
|
const keychains = await MacOSKeychainKeyProvider.listKeychains();
|
|
2319
2331
|
if (keychains.length === 0) {
|
|
2320
2332
|
throw new Error("No keychains found on this system");
|
|
@@ -2350,38 +2362,15 @@ async function runCreate() {
|
|
|
2350
2362
|
return true;
|
|
2351
2363
|
}
|
|
2352
2364
|
});
|
|
2353
|
-
|
|
2354
|
-
const { promisify } = await import('util');
|
|
2355
|
-
const execFileAsync = promisify(execFile);
|
|
2356
|
-
const encodedKey = Buffer.from(keyPair.privateKey).toString("base64");
|
|
2357
|
-
try {
|
|
2358
|
-
const addArgs = [
|
|
2359
|
-
"add-generic-password",
|
|
2360
|
-
"-a",
|
|
2361
|
-
"attest-it",
|
|
2362
|
-
"-s",
|
|
2363
|
-
keychainItemName,
|
|
2364
|
-
"-w",
|
|
2365
|
-
encodedKey,
|
|
2366
|
-
"-U",
|
|
2367
|
-
selectedKeychain.path
|
|
2368
|
-
];
|
|
2369
|
-
await execFileAsync("security", addArgs);
|
|
2370
|
-
} catch (err) {
|
|
2371
|
-
throw new Error(
|
|
2372
|
-
`Failed to store key in macOS Keychain: ${err instanceof Error ? err.message : String(err)}`
|
|
2373
|
-
);
|
|
2374
|
-
}
|
|
2375
|
-
privateKeyRef = {
|
|
2376
|
-
type: "keychain",
|
|
2377
|
-
service: keychainItemName,
|
|
2378
|
-
account: "attest-it",
|
|
2379
|
-
keychain: selectedKeychain.path
|
|
2380
|
-
};
|
|
2381
|
-
keyStorageDescription = `macOS Keychain: ${selectedKeychain.name}/${keychainItemName}`;
|
|
2365
|
+
storageConfig = { type: "keychain", selectedKeychain, keychainItemName };
|
|
2382
2366
|
break;
|
|
2383
2367
|
}
|
|
2384
2368
|
case "1password": {
|
|
2369
|
+
log("");
|
|
2370
|
+
info("Accessing 1Password to list your accounts and vaults...");
|
|
2371
|
+
info(
|
|
2372
|
+
"You may see biometric prompts or be asked to unlock 1Password for each configured account."
|
|
2373
|
+
);
|
|
2385
2374
|
const accounts = await OnePasswordKeyProvider.listAccounts();
|
|
2386
2375
|
if (accounts.length === 0) {
|
|
2387
2376
|
throw new Error(
|
|
@@ -2402,7 +2391,7 @@ async function runCreate() {
|
|
|
2402
2391
|
"--format=json"
|
|
2403
2392
|
]);
|
|
2404
2393
|
const details = JSON.parse(stdout);
|
|
2405
|
-
const name2 = details !== null && typeof details === "object" && "name" in details && typeof details.name === "string" ? details.name :
|
|
2394
|
+
const name2 = details !== null && typeof details === "object" && "name" in details && typeof details.name === "string" ? details.name : "[Could not read account name]";
|
|
2406
2395
|
return {
|
|
2407
2396
|
url: acc.url,
|
|
2408
2397
|
email: acc.email,
|
|
@@ -2412,7 +2401,7 @@ async function runCreate() {
|
|
|
2412
2401
|
return {
|
|
2413
2402
|
url: acc.url,
|
|
2414
2403
|
email: acc.email,
|
|
2415
|
-
name:
|
|
2404
|
+
name: "[Could not read account name]"
|
|
2416
2405
|
};
|
|
2417
2406
|
}
|
|
2418
2407
|
})
|
|
@@ -2454,43 +2443,21 @@ async function runCreate() {
|
|
|
2454
2443
|
return true;
|
|
2455
2444
|
}
|
|
2456
2445
|
});
|
|
2457
|
-
const
|
|
2458
|
-
const
|
|
2459
|
-
|
|
2460
|
-
const tempPrivatePath = join(tempDir, "private.pem");
|
|
2461
|
-
try {
|
|
2462
|
-
await writeFile(tempPrivatePath, keyPair.privateKey, { mode: 384 });
|
|
2463
|
-
const { execFile: execFile2 } = await import('child_process');
|
|
2464
|
-
const { promisify: promisify2 } = await import('util');
|
|
2465
|
-
const execFileAsync2 = promisify2(execFile2);
|
|
2466
|
-
const opArgs = [
|
|
2467
|
-
"document",
|
|
2468
|
-
"create",
|
|
2469
|
-
tempPrivatePath,
|
|
2470
|
-
"--title",
|
|
2471
|
-
item,
|
|
2472
|
-
"--vault",
|
|
2473
|
-
selectedVault
|
|
2474
|
-
];
|
|
2475
|
-
if (selectedAccount) {
|
|
2476
|
-
opArgs.push("--account", selectedAccount);
|
|
2477
|
-
}
|
|
2478
|
-
await execFileAsync2("op", opArgs);
|
|
2479
|
-
} finally {
|
|
2480
|
-
const { rm } = await import('fs/promises');
|
|
2481
|
-
await rm(tempDir, { recursive: true, force: true }).catch(() => {
|
|
2482
|
-
});
|
|
2483
|
-
}
|
|
2484
|
-
privateKeyRef = {
|
|
2446
|
+
const selectedAccountDetails = accountDetails.find((acc) => acc.url === selectedAccount);
|
|
2447
|
+
const accountDisplayName = selectedAccountDetails?.name ?? selectedAccount;
|
|
2448
|
+
storageConfig = {
|
|
2485
2449
|
type: "1password",
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2450
|
+
selectedAccount,
|
|
2451
|
+
accountDisplayName,
|
|
2452
|
+
selectedVault,
|
|
2453
|
+
item
|
|
2489
2454
|
};
|
|
2490
|
-
keyStorageDescription = `1Password (${selectedVault}/${item})`;
|
|
2491
2455
|
break;
|
|
2492
2456
|
}
|
|
2493
2457
|
case "yubikey": {
|
|
2458
|
+
log("");
|
|
2459
|
+
info("Accessing YubiKey to detect connected devices...");
|
|
2460
|
+
info("Your private key will be encrypted using HMAC challenge-response from the YubiKey.");
|
|
2494
2461
|
if (!await YubiKeyProvider.isConnected()) {
|
|
2495
2462
|
error("No YubiKey detected. Please insert your YubiKey and try again.");
|
|
2496
2463
|
process.exit(ExitCode.CONFIG_ERROR);
|
|
@@ -2542,25 +2509,115 @@ async function runCreate() {
|
|
|
2542
2509
|
}
|
|
2543
2510
|
});
|
|
2544
2511
|
const keysDir = join(getAttestItConfigDir(), "keys");
|
|
2545
|
-
await mkdir(keysDir, { recursive: true });
|
|
2546
2512
|
const encryptedKeyPath = join(keysDir, encryptedKeyName);
|
|
2513
|
+
storageConfig = { type: "yubikey", selectedSerial, slot, encryptedKeyPath };
|
|
2514
|
+
break;
|
|
2515
|
+
}
|
|
2516
|
+
default:
|
|
2517
|
+
throw new Error(`Unknown key storage type: ${keyStorageType}`);
|
|
2518
|
+
}
|
|
2519
|
+
log("");
|
|
2520
|
+
log("Generating Ed25519 keypair...");
|
|
2521
|
+
const keyPair = generateEd25519KeyPair();
|
|
2522
|
+
let privateKeyRef;
|
|
2523
|
+
let keyStorageDescription;
|
|
2524
|
+
switch (storageConfig.type) {
|
|
2525
|
+
case "file": {
|
|
2526
|
+
log("");
|
|
2527
|
+
info("Creating private key file on disk...");
|
|
2528
|
+
const keysDir = join(getAttestItConfigDir(), "keys");
|
|
2529
|
+
await mkdir(keysDir, { recursive: true });
|
|
2530
|
+
await writeFile(storageConfig.keyPath, keyPair.privateKey, { mode: 384 });
|
|
2531
|
+
privateKeyRef = { type: "file", path: storageConfig.keyPath };
|
|
2532
|
+
keyStorageDescription = storageConfig.keyPath;
|
|
2533
|
+
break;
|
|
2534
|
+
}
|
|
2535
|
+
case "keychain": {
|
|
2536
|
+
const { execFile } = await import('child_process');
|
|
2537
|
+
const { promisify } = await import('util');
|
|
2538
|
+
const execFileAsync = promisify(execFile);
|
|
2539
|
+
const encodedKey = Buffer.from(keyPair.privateKey).toString("base64");
|
|
2540
|
+
try {
|
|
2541
|
+
const addArgs = [
|
|
2542
|
+
"add-generic-password",
|
|
2543
|
+
"-a",
|
|
2544
|
+
"attest-it",
|
|
2545
|
+
"-s",
|
|
2546
|
+
storageConfig.keychainItemName,
|
|
2547
|
+
"-w",
|
|
2548
|
+
encodedKey,
|
|
2549
|
+
"-U",
|
|
2550
|
+
storageConfig.selectedKeychain.path
|
|
2551
|
+
];
|
|
2552
|
+
await execFileAsync("security", addArgs);
|
|
2553
|
+
} catch (err) {
|
|
2554
|
+
throw new Error(
|
|
2555
|
+
`Failed to store key in macOS Keychain: ${err instanceof Error ? err.message : String(err)}`
|
|
2556
|
+
);
|
|
2557
|
+
}
|
|
2558
|
+
privateKeyRef = {
|
|
2559
|
+
type: "keychain",
|
|
2560
|
+
service: storageConfig.keychainItemName,
|
|
2561
|
+
account: "attest-it",
|
|
2562
|
+
keychain: storageConfig.selectedKeychain.path
|
|
2563
|
+
};
|
|
2564
|
+
keyStorageDescription = `macOS Keychain: ${storageConfig.selectedKeychain.name}/${storageConfig.keychainItemName}`;
|
|
2565
|
+
break;
|
|
2566
|
+
}
|
|
2567
|
+
case "1password": {
|
|
2568
|
+
const { tmpdir } = await import('os');
|
|
2569
|
+
const tempDir = join(tmpdir(), `attest-it-${String(Date.now())}`);
|
|
2570
|
+
await mkdir(tempDir, { recursive: true });
|
|
2571
|
+
const tempPrivatePath = join(tempDir, "private.pem");
|
|
2572
|
+
try {
|
|
2573
|
+
await writeFile(tempPrivatePath, keyPair.privateKey, { mode: 384 });
|
|
2574
|
+
const { execFile } = await import('child_process');
|
|
2575
|
+
const { promisify } = await import('util');
|
|
2576
|
+
const execFileAsync = promisify(execFile);
|
|
2577
|
+
const opArgs = [
|
|
2578
|
+
"document",
|
|
2579
|
+
"create",
|
|
2580
|
+
tempPrivatePath,
|
|
2581
|
+
"--title",
|
|
2582
|
+
storageConfig.item,
|
|
2583
|
+
"--vault",
|
|
2584
|
+
storageConfig.selectedVault,
|
|
2585
|
+
"--account",
|
|
2586
|
+
storageConfig.selectedAccount
|
|
2587
|
+
];
|
|
2588
|
+
await execFileAsync("op", opArgs);
|
|
2589
|
+
} finally {
|
|
2590
|
+
const { rm } = await import('fs/promises');
|
|
2591
|
+
await rm(tempDir, { recursive: true, force: true }).catch(() => {
|
|
2592
|
+
});
|
|
2593
|
+
}
|
|
2594
|
+
privateKeyRef = {
|
|
2595
|
+
type: "1password",
|
|
2596
|
+
vault: storageConfig.selectedVault,
|
|
2597
|
+
item: storageConfig.item,
|
|
2598
|
+
account: storageConfig.selectedAccount
|
|
2599
|
+
};
|
|
2600
|
+
keyStorageDescription = `1Password (${storageConfig.accountDisplayName}/${storageConfig.selectedVault}/${storageConfig.item})`;
|
|
2601
|
+
break;
|
|
2602
|
+
}
|
|
2603
|
+
case "yubikey": {
|
|
2604
|
+
const keysDir = join(getAttestItConfigDir(), "keys");
|
|
2605
|
+
await mkdir(keysDir, { recursive: true });
|
|
2547
2606
|
const result = await YubiKeyProvider.encryptPrivateKey({
|
|
2548
2607
|
privateKey: keyPair.privateKey,
|
|
2549
|
-
encryptedKeyPath,
|
|
2550
|
-
slot,
|
|
2551
|
-
serial: selectedSerial
|
|
2608
|
+
encryptedKeyPath: storageConfig.encryptedKeyPath,
|
|
2609
|
+
slot: storageConfig.slot,
|
|
2610
|
+
serial: storageConfig.selectedSerial
|
|
2552
2611
|
});
|
|
2553
2612
|
privateKeyRef = {
|
|
2554
2613
|
type: "yubikey",
|
|
2555
2614
|
encryptedKeyPath: result.encryptedKeyPath,
|
|
2556
|
-
slot,
|
|
2557
|
-
serial: selectedSerial
|
|
2615
|
+
slot: storageConfig.slot,
|
|
2616
|
+
serial: storageConfig.selectedSerial
|
|
2558
2617
|
};
|
|
2559
2618
|
keyStorageDescription = result.storageDescription;
|
|
2560
2619
|
break;
|
|
2561
2620
|
}
|
|
2562
|
-
default:
|
|
2563
|
-
throw new Error(`Unknown key storage type: ${keyStorageType}`);
|
|
2564
2621
|
}
|
|
2565
2622
|
const identity = {
|
|
2566
2623
|
name,
|