@attest-it/cli 0.6.1 → 0.8.0
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 +310 -25
- package/dist/bin/attest-it.js.map +1 -1
- package/dist/index.cjs +308 -23
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +310 -25
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/bin/attest-it.js
CHANGED
|
@@ -6,7 +6,7 @@ import * as path from 'path';
|
|
|
6
6
|
import { join, dirname } from 'path';
|
|
7
7
|
import { detectTheme } from 'chromaterm';
|
|
8
8
|
import { input, select, confirm, checkbox } from '@inquirer/prompts';
|
|
9
|
-
import { loadConfig, toAttestItConfig, readSealsSync, computeFingerprintSync, verifyGateSeal, verifyAllSeals, computeFingerprint, createAttestation, readAttestations, upsertAttestation, KeyProviderRegistry, getDefaultPrivateKeyPath, FilesystemKeyProvider, writeSignedAttestations, loadLocalConfigSync, getActiveIdentity, isAuthorizedSigner, createSeal, writeSealsSync, checkOpenSSL, getDefaultPublicKeyPath, OnePasswordKeyProvider, MacOSKeychainKeyProvider, generateKeyPair, setKeyPermissions, getGate, loadLocalConfig, YubiKeyProvider, getAttestItConfigDir, generateEd25519KeyPair, saveLocalConfig, findConfigPath, loadPreferences, savePreferences, findAttestation, setAttestItHomeDir } from '@attest-it/core';
|
|
9
|
+
import { loadConfig, toAttestItConfig, readSealsSync, computeFingerprintSync, verifyGateSeal, verifyAllSeals, computeFingerprint, createAttestation, readAttestations, upsertAttestation, KeyProviderRegistry, getDefaultPrivateKeyPath, FilesystemKeyProvider, writeSignedAttestations, loadLocalConfigSync, getActiveIdentity, isAuthorizedSigner, createSeal, writeSealsSync, checkOpenSSL, getDefaultPublicKeyPath, OnePasswordKeyProvider, MacOSKeychainKeyProvider, generateKeyPair, setKeyPermissions, getGate, loadLocalConfig, YubiKeyProvider, getAttestItConfigDir, generateEd25519KeyPair, saveLocalConfig, savePublicKey, findConfigPath, loadPreferences, savePreferences, findAttestation, setAttestItHomeDir, getDefaultYubiKeyEncryptedKeyPath } from '@attest-it/core';
|
|
10
10
|
import tabtab2 from '@pnpm/tabtab';
|
|
11
11
|
import { spawn } from 'child_process';
|
|
12
12
|
import * as os from 'os';
|
|
@@ -16,7 +16,7 @@ import { useState, useEffect } from 'react';
|
|
|
16
16
|
import { render, useApp, Box, Text, useInput } from 'ink';
|
|
17
17
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
18
18
|
import { mkdir, writeFile, unlink, readFile } from 'fs/promises';
|
|
19
|
-
import { Spinner, Select, TextInput } from '@inkjs/ui';
|
|
19
|
+
import { Spinner, Select, PasswordInput, TextInput } from '@inkjs/ui';
|
|
20
20
|
import { stringify } from 'yaml';
|
|
21
21
|
import { fileURLToPath } from 'url';
|
|
22
22
|
|
|
@@ -337,7 +337,7 @@ async function runStatus(gates, options) {
|
|
|
337
337
|
process.exit(ExitCode.CONFIG_ERROR);
|
|
338
338
|
}
|
|
339
339
|
const projectRoot = process.cwd();
|
|
340
|
-
const sealsFile = readSealsSync(projectRoot);
|
|
340
|
+
const sealsFile = readSealsSync(projectRoot, attestItConfig.settings.sealsPath);
|
|
341
341
|
const gatesToCheck = gates.length > 0 ? gates : Object.keys(attestItConfig.gates);
|
|
342
342
|
for (const gateId of gatesToCheck) {
|
|
343
343
|
if (!attestItConfig.gates[gateId]) {
|
|
@@ -989,12 +989,24 @@ async function getAllSuiteStatuses(config) {
|
|
|
989
989
|
const attestations = attestationsFile?.attestations ?? [];
|
|
990
990
|
const results = [];
|
|
991
991
|
for (const [suiteName, suiteConfig] of Object.entries(config.suites)) {
|
|
992
|
-
|
|
992
|
+
let packages;
|
|
993
|
+
let ignore;
|
|
994
|
+
if (suiteConfig.gate && config.gates) {
|
|
995
|
+
const gateConfig = config.gates[suiteConfig.gate];
|
|
996
|
+
if (gateConfig) {
|
|
997
|
+
packages = gateConfig.fingerprint.paths;
|
|
998
|
+
ignore = gateConfig.fingerprint.exclude;
|
|
999
|
+
}
|
|
1000
|
+
} else if (suiteConfig.packages) {
|
|
1001
|
+
packages = suiteConfig.packages;
|
|
1002
|
+
ignore = suiteConfig.ignore;
|
|
1003
|
+
}
|
|
1004
|
+
if (!packages || packages.length === 0) {
|
|
993
1005
|
continue;
|
|
994
1006
|
}
|
|
995
1007
|
const fingerprintResult = await computeFingerprint({
|
|
996
|
-
packages
|
|
997
|
-
...
|
|
1008
|
+
packages,
|
|
1009
|
+
...ignore && { ignore }
|
|
998
1010
|
});
|
|
999
1011
|
const attestation = findAttestation(
|
|
1000
1012
|
{ schemaVersion: "1", attestations, signature: "" },
|
|
@@ -1559,18 +1571,19 @@ async function promptForSeal(suiteName, gateId, config) {
|
|
|
1559
1571
|
const fs4 = await import('fs/promises');
|
|
1560
1572
|
const privateKeyPem = await fs4.readFile(keyResult.keyPath, "utf8");
|
|
1561
1573
|
await keyResult.cleanup();
|
|
1574
|
+
const identitySlug = localConfig.activeIdentity;
|
|
1562
1575
|
const seal = createSeal({
|
|
1563
1576
|
gateId,
|
|
1564
1577
|
fingerprint: gateFingerprint.fingerprint,
|
|
1565
|
-
sealedBy:
|
|
1578
|
+
sealedBy: identitySlug,
|
|
1566
1579
|
privateKey: privateKeyPem
|
|
1567
1580
|
});
|
|
1568
1581
|
const projectRoot = process.cwd();
|
|
1569
|
-
const sealsFile = readSealsSync(projectRoot);
|
|
1582
|
+
const sealsFile = readSealsSync(projectRoot, attestItConfig.settings.sealsPath);
|
|
1570
1583
|
sealsFile.seals[gateId] = seal;
|
|
1571
|
-
writeSealsSync(projectRoot, sealsFile);
|
|
1584
|
+
writeSealsSync(projectRoot, sealsFile, attestItConfig.settings.sealsPath);
|
|
1572
1585
|
success(`Seal created for gate '${gateId}'`);
|
|
1573
|
-
log(` Sealed by: ${identity.name}`);
|
|
1586
|
+
log(` Sealed by: ${identitySlug} (${identity.name})`);
|
|
1574
1587
|
log(` Timestamp: ${seal.timestamp}`);
|
|
1575
1588
|
} catch (err) {
|
|
1576
1589
|
if (err instanceof Error) {
|
|
@@ -1638,18 +1651,31 @@ function getKeyRefFromIdentity(identity) {
|
|
|
1638
1651
|
}
|
|
1639
1652
|
}
|
|
1640
1653
|
}
|
|
1654
|
+
var MIN_PASSPHRASE_LENGTH = 8;
|
|
1641
1655
|
function KeygenInteractive(props) {
|
|
1642
|
-
const { onComplete, onError } = props;
|
|
1656
|
+
const { onComplete, onCancel, onError } = props;
|
|
1643
1657
|
const [step, setStep] = useState("checking-providers");
|
|
1644
1658
|
const [opAvailable, setOpAvailable] = useState(false);
|
|
1645
1659
|
const [keychainAvailable, setKeychainAvailable] = useState(false);
|
|
1660
|
+
const [yubiKeyAvailable, setYubiKeyAvailable] = useState(false);
|
|
1646
1661
|
const [accounts, setAccounts] = useState([]);
|
|
1647
1662
|
const [vaults, setVaults] = useState([]);
|
|
1663
|
+
const [yubiKeyDevices, setYubiKeyDevices] = useState([]);
|
|
1648
1664
|
const [_selectedProvider, setSelectedProvider] = useState();
|
|
1649
1665
|
const [selectedAccount, setSelectedAccount] = useState();
|
|
1650
1666
|
const [selectedVault, setSelectedVault] = useState();
|
|
1651
1667
|
const [itemName, setItemName] = useState("attest-it-private-key");
|
|
1652
1668
|
const [keychainItemName, setKeychainItemName] = useState("attest-it-private-key");
|
|
1669
|
+
const [selectedYubiKeySerial, setSelectedYubiKeySerial] = useState();
|
|
1670
|
+
const [selectedYubiKeySlot, setSelectedYubiKeySlot] = useState(2);
|
|
1671
|
+
const [slot1Configured, setSlot1Configured] = useState(false);
|
|
1672
|
+
const [slot2Configured, setSlot2Configured] = useState(false);
|
|
1673
|
+
const [encryptionPassphrase, setEncryptionPassphrase] = useState();
|
|
1674
|
+
useInput((_input, key) => {
|
|
1675
|
+
if (key.escape) {
|
|
1676
|
+
onCancel();
|
|
1677
|
+
}
|
|
1678
|
+
});
|
|
1653
1679
|
useEffect(() => {
|
|
1654
1680
|
const checkProviders = async () => {
|
|
1655
1681
|
try {
|
|
@@ -1664,6 +1690,19 @@ function KeygenInteractive(props) {
|
|
|
1664
1690
|
}
|
|
1665
1691
|
const isKeychainAvailable = MacOSKeychainKeyProvider.isAvailable();
|
|
1666
1692
|
setKeychainAvailable(isKeychainAvailable);
|
|
1693
|
+
try {
|
|
1694
|
+
const isInstalled = await YubiKeyProvider.isInstalled();
|
|
1695
|
+
if (isInstalled) {
|
|
1696
|
+
const isConnected = await YubiKeyProvider.isConnected();
|
|
1697
|
+
if (isConnected) {
|
|
1698
|
+
const devices = await YubiKeyProvider.listDevices();
|
|
1699
|
+
setYubiKeyDevices(devices);
|
|
1700
|
+
setYubiKeyAvailable(devices.length > 0);
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
} catch {
|
|
1704
|
+
setYubiKeyAvailable(false);
|
|
1705
|
+
}
|
|
1667
1706
|
setStep("select-provider");
|
|
1668
1707
|
};
|
|
1669
1708
|
void checkProviders();
|
|
@@ -1681,10 +1720,31 @@ function KeygenInteractive(props) {
|
|
|
1681
1720
|
void fetchVaults();
|
|
1682
1721
|
}
|
|
1683
1722
|
}, [step, selectedAccount, onError]);
|
|
1723
|
+
const checkYubiKeySlots = async (serial) => {
|
|
1724
|
+
try {
|
|
1725
|
+
const slot1 = await YubiKeyProvider.isChallengeResponseConfigured(1, serial);
|
|
1726
|
+
const slot2 = await YubiKeyProvider.isChallengeResponseConfigured(2, serial);
|
|
1727
|
+
setSlot1Configured(slot1);
|
|
1728
|
+
setSlot2Configured(slot2);
|
|
1729
|
+
if (slot1 && slot2) {
|
|
1730
|
+
setStep("select-yubikey-slot");
|
|
1731
|
+
} else if (slot2) {
|
|
1732
|
+
setSelectedYubiKeySlot(2);
|
|
1733
|
+
void generateKeys("yubikey");
|
|
1734
|
+
} else if (slot1) {
|
|
1735
|
+
setSelectedYubiKeySlot(1);
|
|
1736
|
+
void generateKeys("yubikey");
|
|
1737
|
+
} else {
|
|
1738
|
+
setStep("yubikey-offer-setup");
|
|
1739
|
+
}
|
|
1740
|
+
} catch (err) {
|
|
1741
|
+
onError(err instanceof Error ? err : new Error("Failed to check YubiKey slots"));
|
|
1742
|
+
}
|
|
1743
|
+
};
|
|
1684
1744
|
const handleProviderSelect = (value) => {
|
|
1685
1745
|
if (value === "filesystem") {
|
|
1686
1746
|
setSelectedProvider("filesystem");
|
|
1687
|
-
|
|
1747
|
+
setStep("select-filesystem-encryption");
|
|
1688
1748
|
} else if (value === "1password") {
|
|
1689
1749
|
setSelectedProvider("1password");
|
|
1690
1750
|
if (accounts.length === 1 && accounts[0]) {
|
|
@@ -1696,6 +1756,14 @@ function KeygenInteractive(props) {
|
|
|
1696
1756
|
} else if (value === "macos-keychain") {
|
|
1697
1757
|
setSelectedProvider("macos-keychain");
|
|
1698
1758
|
setStep("enter-keychain-item-name");
|
|
1759
|
+
} else if (value === "yubikey") {
|
|
1760
|
+
setSelectedProvider("yubikey");
|
|
1761
|
+
if (yubiKeyDevices.length > 1) {
|
|
1762
|
+
setStep("select-yubikey-device");
|
|
1763
|
+
} else if (yubiKeyDevices.length === 1 && yubiKeyDevices[0]) {
|
|
1764
|
+
setSelectedYubiKeySerial(yubiKeyDevices[0].serial);
|
|
1765
|
+
void checkYubiKeySlots(yubiKeyDevices[0].serial);
|
|
1766
|
+
}
|
|
1699
1767
|
}
|
|
1700
1768
|
};
|
|
1701
1769
|
const handleAccountSelect = (value) => {
|
|
@@ -1714,23 +1782,98 @@ function KeygenInteractive(props) {
|
|
|
1714
1782
|
setKeychainItemName(value);
|
|
1715
1783
|
void generateKeys("macos-keychain");
|
|
1716
1784
|
};
|
|
1785
|
+
const handleYubiKeyDeviceSelect = (value) => {
|
|
1786
|
+
setSelectedYubiKeySerial(value);
|
|
1787
|
+
void checkYubiKeySlots(value);
|
|
1788
|
+
};
|
|
1789
|
+
const handleYubiKeySlotSelect = (value) => {
|
|
1790
|
+
const slot = value === "1" ? 1 : 2;
|
|
1791
|
+
setSelectedYubiKeySlot(slot);
|
|
1792
|
+
void generateKeys("yubikey");
|
|
1793
|
+
};
|
|
1794
|
+
const handleYubiKeySetupConfirm = (value) => {
|
|
1795
|
+
if (value === "yes") {
|
|
1796
|
+
void setupYubiKeySlot();
|
|
1797
|
+
} else {
|
|
1798
|
+
onError(new Error("YubiKey setup cancelled"));
|
|
1799
|
+
}
|
|
1800
|
+
};
|
|
1801
|
+
const handleEncryptionMethodSelect = (value) => {
|
|
1802
|
+
if (value === "passphrase") {
|
|
1803
|
+
setStep("enter-encryption-passphrase");
|
|
1804
|
+
} else {
|
|
1805
|
+
setEncryptionPassphrase(void 0);
|
|
1806
|
+
void generateKeys("filesystem");
|
|
1807
|
+
}
|
|
1808
|
+
};
|
|
1809
|
+
const handleEncryptionPassphrase = (value) => {
|
|
1810
|
+
if (value.length < MIN_PASSPHRASE_LENGTH) {
|
|
1811
|
+
onError(new Error(`Passphrase must be at least ${String(MIN_PASSPHRASE_LENGTH)} characters`));
|
|
1812
|
+
return;
|
|
1813
|
+
}
|
|
1814
|
+
setEncryptionPassphrase(value);
|
|
1815
|
+
setStep("confirm-encryption-passphrase");
|
|
1816
|
+
};
|
|
1817
|
+
const handleConfirmPassphrase = (value) => {
|
|
1818
|
+
if (value !== encryptionPassphrase) {
|
|
1819
|
+
onError(new Error("Passphrases do not match. Please try again."));
|
|
1820
|
+
setEncryptionPassphrase(void 0);
|
|
1821
|
+
setStep("enter-encryption-passphrase");
|
|
1822
|
+
return;
|
|
1823
|
+
}
|
|
1824
|
+
void generateKeys("filesystem");
|
|
1825
|
+
};
|
|
1826
|
+
const setupYubiKeySlot = async () => {
|
|
1827
|
+
setStep("yubikey-configuring");
|
|
1828
|
+
try {
|
|
1829
|
+
const { spawn: spawn3 } = await import('child_process');
|
|
1830
|
+
const args = ["otp", "chalresp", "--touch", "--generate", "2"];
|
|
1831
|
+
if (selectedYubiKeySerial) {
|
|
1832
|
+
args.unshift("--device", selectedYubiKeySerial);
|
|
1833
|
+
}
|
|
1834
|
+
await new Promise((resolve2, reject) => {
|
|
1835
|
+
const proc = spawn3("ykman", args, { stdio: "inherit" });
|
|
1836
|
+
proc.on("close", (code) => {
|
|
1837
|
+
if (code === 0) {
|
|
1838
|
+
resolve2();
|
|
1839
|
+
} else {
|
|
1840
|
+
reject(new Error(`ykman exited with code ${String(code)}`));
|
|
1841
|
+
}
|
|
1842
|
+
});
|
|
1843
|
+
proc.on("error", reject);
|
|
1844
|
+
});
|
|
1845
|
+
setSelectedYubiKeySlot(2);
|
|
1846
|
+
void generateKeys("yubikey");
|
|
1847
|
+
} catch (err) {
|
|
1848
|
+
onError(err instanceof Error ? err : new Error("Failed to configure YubiKey"));
|
|
1849
|
+
}
|
|
1850
|
+
};
|
|
1717
1851
|
const generateKeys = async (provider) => {
|
|
1718
1852
|
setStep("generating");
|
|
1719
1853
|
try {
|
|
1720
1854
|
const publicKeyPath = props.publicKeyPath ?? getDefaultPublicKeyPath();
|
|
1721
1855
|
if (provider === "filesystem") {
|
|
1722
1856
|
const fsProvider = new FilesystemKeyProvider();
|
|
1723
|
-
const genOptions = {
|
|
1857
|
+
const genOptions = {
|
|
1858
|
+
publicKeyPath
|
|
1859
|
+
};
|
|
1724
1860
|
if (props.force !== void 0) {
|
|
1725
1861
|
genOptions.force = props.force;
|
|
1726
1862
|
}
|
|
1863
|
+
if (encryptionPassphrase !== void 0) {
|
|
1864
|
+
genOptions.passphrase = encryptionPassphrase;
|
|
1865
|
+
}
|
|
1727
1866
|
const result = await fsProvider.generateKeyPair(genOptions);
|
|
1728
|
-
|
|
1867
|
+
const completionResult = {
|
|
1729
1868
|
provider: "filesystem",
|
|
1730
1869
|
publicKeyPath: result.publicKeyPath,
|
|
1731
1870
|
privateKeyRef: result.privateKeyRef,
|
|
1732
1871
|
storageDescription: result.storageDescription
|
|
1733
|
-
}
|
|
1872
|
+
};
|
|
1873
|
+
if (result.encrypted) {
|
|
1874
|
+
completionResult.encrypted = result.encrypted;
|
|
1875
|
+
}
|
|
1876
|
+
onComplete(completionResult);
|
|
1734
1877
|
} else if (provider === "1password") {
|
|
1735
1878
|
if (!selectedVault || !itemName) {
|
|
1736
1879
|
throw new Error("Vault and item name are required for 1Password");
|
|
@@ -1764,7 +1907,7 @@ function KeygenInteractive(props) {
|
|
|
1764
1907
|
completionResult.account = selectedAccount;
|
|
1765
1908
|
}
|
|
1766
1909
|
onComplete(completionResult);
|
|
1767
|
-
} else {
|
|
1910
|
+
} else if (provider === "macos-keychain") {
|
|
1768
1911
|
if (!keychainItemName) {
|
|
1769
1912
|
throw new Error("Item name is required for macOS Keychain");
|
|
1770
1913
|
}
|
|
@@ -1783,10 +1926,39 @@ function KeygenInteractive(props) {
|
|
|
1783
1926
|
storageDescription: result.storageDescription,
|
|
1784
1927
|
itemName: keychainItemName
|
|
1785
1928
|
});
|
|
1929
|
+
} else {
|
|
1930
|
+
const encryptedKeyPath = getDefaultYubiKeyEncryptedKeyPath();
|
|
1931
|
+
const providerOptions = {
|
|
1932
|
+
encryptedKeyPath,
|
|
1933
|
+
slot: selectedYubiKeySlot
|
|
1934
|
+
};
|
|
1935
|
+
if (selectedYubiKeySerial !== void 0) {
|
|
1936
|
+
providerOptions.serial = selectedYubiKeySerial;
|
|
1937
|
+
}
|
|
1938
|
+
const ykProvider = new YubiKeyProvider(providerOptions);
|
|
1939
|
+
const genOptions = { publicKeyPath };
|
|
1940
|
+
if (props.force !== void 0) {
|
|
1941
|
+
genOptions.force = props.force;
|
|
1942
|
+
}
|
|
1943
|
+
const result = await ykProvider.generateKeyPair(genOptions);
|
|
1944
|
+
const completionResult = {
|
|
1945
|
+
provider: "yubikey",
|
|
1946
|
+
publicKeyPath: result.publicKeyPath,
|
|
1947
|
+
privateKeyRef: result.privateKeyRef,
|
|
1948
|
+
storageDescription: result.storageDescription,
|
|
1949
|
+
slot: selectedYubiKeySlot,
|
|
1950
|
+
encryptedKeyPath
|
|
1951
|
+
};
|
|
1952
|
+
if (selectedYubiKeySerial !== void 0) {
|
|
1953
|
+
completionResult.serial = selectedYubiKeySerial;
|
|
1954
|
+
}
|
|
1955
|
+
onComplete(completionResult);
|
|
1786
1956
|
}
|
|
1787
1957
|
setStep("done");
|
|
1788
1958
|
} catch (err) {
|
|
1789
1959
|
onError(err instanceof Error ? err : new Error("Key generation failed"));
|
|
1960
|
+
} finally {
|
|
1961
|
+
setEncryptionPassphrase(void 0);
|
|
1790
1962
|
}
|
|
1791
1963
|
};
|
|
1792
1964
|
if (step === "checking-providers") {
|
|
@@ -1808,6 +1980,12 @@ function KeygenInteractive(props) {
|
|
|
1808
1980
|
value: "macos-keychain"
|
|
1809
1981
|
});
|
|
1810
1982
|
}
|
|
1983
|
+
if (yubiKeyAvailable) {
|
|
1984
|
+
options.push({
|
|
1985
|
+
label: "YubiKey (hardware security key)",
|
|
1986
|
+
value: "yubikey"
|
|
1987
|
+
});
|
|
1988
|
+
}
|
|
1811
1989
|
if (opAvailable) {
|
|
1812
1990
|
options.push({
|
|
1813
1991
|
label: "1Password (requires op CLI)",
|
|
@@ -1820,9 +1998,42 @@ function KeygenInteractive(props) {
|
|
|
1820
1998
|
/* @__PURE__ */ jsx(Select, { options, onChange: handleProviderSelect })
|
|
1821
1999
|
] });
|
|
1822
2000
|
}
|
|
2001
|
+
if (step === "select-filesystem-encryption") {
|
|
2002
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
2003
|
+
/* @__PURE__ */ jsx(Text, { bold: true, children: "Would you like to encrypt your private key with a passphrase?" }),
|
|
2004
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "A passphrase adds extra security but must be entered each time you sign." }),
|
|
2005
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "" }),
|
|
2006
|
+
/* @__PURE__ */ jsx(
|
|
2007
|
+
Select,
|
|
2008
|
+
{
|
|
2009
|
+
options: [
|
|
2010
|
+
{ label: "No encryption (key protected by file permissions only)", value: "none" },
|
|
2011
|
+
{ label: "Passphrase protection (AES-256 encryption)", value: "passphrase" }
|
|
2012
|
+
],
|
|
2013
|
+
onChange: handleEncryptionMethodSelect
|
|
2014
|
+
}
|
|
2015
|
+
)
|
|
2016
|
+
] });
|
|
2017
|
+
}
|
|
2018
|
+
if (step === "enter-encryption-passphrase") {
|
|
2019
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
2020
|
+
/* @__PURE__ */ jsx(Text, { bold: true, children: "Enter a passphrase to encrypt your private key:" }),
|
|
2021
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: `(Minimum ${String(MIN_PASSPHRASE_LENGTH)} characters. You will need this passphrase each time you sign.)` }),
|
|
2022
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "" }),
|
|
2023
|
+
/* @__PURE__ */ jsx(PasswordInput, { onSubmit: handleEncryptionPassphrase })
|
|
2024
|
+
] });
|
|
2025
|
+
}
|
|
2026
|
+
if (step === "confirm-encryption-passphrase") {
|
|
2027
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
2028
|
+
/* @__PURE__ */ jsx(Text, { bold: true, children: "Confirm your passphrase:" }),
|
|
2029
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "(Enter the same passphrase again to confirm.)" }),
|
|
2030
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "" }),
|
|
2031
|
+
/* @__PURE__ */ jsx(PasswordInput, { onSubmit: handleConfirmPassphrase })
|
|
2032
|
+
] });
|
|
2033
|
+
}
|
|
1823
2034
|
if (step === "select-account") {
|
|
1824
2035
|
const options = accounts.map((account) => ({
|
|
1825
|
-
label: account.email,
|
|
2036
|
+
label: account.name ? `${account.name} (${account.email})` : account.email,
|
|
1826
2037
|
value: account.user_uuid
|
|
1827
2038
|
}));
|
|
1828
2039
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
@@ -1864,6 +2075,65 @@ function KeygenInteractive(props) {
|
|
|
1864
2075
|
/* @__PURE__ */ jsx(TextInput, { defaultValue: keychainItemName, onSubmit: handleKeychainItemNameSubmit })
|
|
1865
2076
|
] });
|
|
1866
2077
|
}
|
|
2078
|
+
if (step === "select-yubikey-device") {
|
|
2079
|
+
const options = yubiKeyDevices.map((device) => ({
|
|
2080
|
+
label: `${device.type} (Serial: ${device.serial})`,
|
|
2081
|
+
value: device.serial
|
|
2082
|
+
}));
|
|
2083
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
2084
|
+
/* @__PURE__ */ jsx(Text, { bold: true, children: "Select YubiKey device:" }),
|
|
2085
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "" }),
|
|
2086
|
+
/* @__PURE__ */ jsx(Select, { options, onChange: handleYubiKeyDeviceSelect })
|
|
2087
|
+
] });
|
|
2088
|
+
}
|
|
2089
|
+
if (step === "select-yubikey-slot") {
|
|
2090
|
+
const options = [];
|
|
2091
|
+
if (slot2Configured) {
|
|
2092
|
+
options.push({
|
|
2093
|
+
label: "Slot 2 (recommended)",
|
|
2094
|
+
value: "2"
|
|
2095
|
+
});
|
|
2096
|
+
}
|
|
2097
|
+
if (slot1Configured) {
|
|
2098
|
+
options.push({
|
|
2099
|
+
label: "Slot 1",
|
|
2100
|
+
value: "1"
|
|
2101
|
+
});
|
|
2102
|
+
}
|
|
2103
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
2104
|
+
/* @__PURE__ */ jsx(Text, { bold: true, children: "Select YubiKey slot for challenge-response:" }),
|
|
2105
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "" }),
|
|
2106
|
+
/* @__PURE__ */ jsx(Select, { options, onChange: handleYubiKeySlotSelect })
|
|
2107
|
+
] });
|
|
2108
|
+
}
|
|
2109
|
+
if (step === "yubikey-offer-setup") {
|
|
2110
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
2111
|
+
/* @__PURE__ */ jsx(Text, { bold: true, children: "Your YubiKey is not configured for challenge-response." }),
|
|
2112
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "" }),
|
|
2113
|
+
/* @__PURE__ */ jsx(Text, { children: "Would you like to configure slot 2 for challenge-response now?" }),
|
|
2114
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "This will enable touch-to-sign functionality." }),
|
|
2115
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "" }),
|
|
2116
|
+
/* @__PURE__ */ jsx(
|
|
2117
|
+
Select,
|
|
2118
|
+
{
|
|
2119
|
+
options: [
|
|
2120
|
+
{ label: "Yes, configure my YubiKey", value: "yes" },
|
|
2121
|
+
{ label: "No, cancel", value: "no" }
|
|
2122
|
+
],
|
|
2123
|
+
onChange: handleYubiKeySetupConfirm
|
|
2124
|
+
}
|
|
2125
|
+
)
|
|
2126
|
+
] });
|
|
2127
|
+
}
|
|
2128
|
+
if (step === "yubikey-configuring") {
|
|
2129
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
2130
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
|
|
2131
|
+
/* @__PURE__ */ jsx(Spinner, {}),
|
|
2132
|
+
/* @__PURE__ */ jsx(Text, { children: "Configuring YubiKey slot 2 for challenge-response..." })
|
|
2133
|
+
] }),
|
|
2134
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "Touch your YubiKey when it flashes." })
|
|
2135
|
+
] });
|
|
2136
|
+
}
|
|
1867
2137
|
if (step === "generating") {
|
|
1868
2138
|
return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
|
|
1869
2139
|
/* @__PURE__ */ jsx(Spinner, {}),
|
|
@@ -2176,7 +2446,7 @@ async function runVerify(gates, options) {
|
|
|
2176
2446
|
process.exit(ExitCode.CONFIG_ERROR);
|
|
2177
2447
|
}
|
|
2178
2448
|
const projectRoot = process.cwd();
|
|
2179
|
-
const sealsFile = readSealsSync(projectRoot);
|
|
2449
|
+
const sealsFile = readSealsSync(projectRoot, attestItConfig.settings.sealsPath);
|
|
2180
2450
|
const gatesToVerify = gates.length > 0 ? gates : Object.keys(attestItConfig.gates);
|
|
2181
2451
|
for (const gateId of gatesToVerify) {
|
|
2182
2452
|
if (!attestItConfig.gates[gateId]) {
|
|
@@ -2332,7 +2602,7 @@ async function runSeal(gates, options) {
|
|
|
2332
2602
|
process.exit(ExitCode.CONFIG_ERROR);
|
|
2333
2603
|
}
|
|
2334
2604
|
const projectRoot = process.cwd();
|
|
2335
|
-
const sealsFile = readSealsSync(projectRoot);
|
|
2605
|
+
const sealsFile = readSealsSync(projectRoot, attestItConfig.settings.sealsPath);
|
|
2336
2606
|
const gatesToSeal = gates.length > 0 ? gates : getAllGateIds(attestItConfig);
|
|
2337
2607
|
for (const gateId of gatesToSeal) {
|
|
2338
2608
|
if (!attestItConfig.gates[gateId]) {
|
|
@@ -2345,9 +2615,17 @@ async function runSeal(gates, options) {
|
|
|
2345
2615
|
skipped: [],
|
|
2346
2616
|
failed: []
|
|
2347
2617
|
};
|
|
2618
|
+
const identitySlug = localConfig.activeIdentity;
|
|
2348
2619
|
for (const gateId of gatesToSeal) {
|
|
2349
2620
|
try {
|
|
2350
|
-
const result = await processSingleGate(
|
|
2621
|
+
const result = await processSingleGate(
|
|
2622
|
+
gateId,
|
|
2623
|
+
attestItConfig,
|
|
2624
|
+
identity,
|
|
2625
|
+
identitySlug,
|
|
2626
|
+
sealsFile,
|
|
2627
|
+
options
|
|
2628
|
+
);
|
|
2351
2629
|
if (result.sealed) {
|
|
2352
2630
|
summary.sealed.push(gateId);
|
|
2353
2631
|
} else if (result.skipped) {
|
|
@@ -2359,7 +2637,7 @@ async function runSeal(gates, options) {
|
|
|
2359
2637
|
}
|
|
2360
2638
|
}
|
|
2361
2639
|
if (!options.dryRun && summary.sealed.length > 0) {
|
|
2362
|
-
writeSealsSync(projectRoot, sealsFile);
|
|
2640
|
+
writeSealsSync(projectRoot, sealsFile, attestItConfig.settings.sealsPath);
|
|
2363
2641
|
}
|
|
2364
2642
|
displaySummary(summary, options.dryRun);
|
|
2365
2643
|
if (summary.failed.length > 0) {
|
|
@@ -2378,7 +2656,7 @@ async function runSeal(gates, options) {
|
|
|
2378
2656
|
process.exit(ExitCode.CONFIG_ERROR);
|
|
2379
2657
|
}
|
|
2380
2658
|
}
|
|
2381
|
-
async function processSingleGate(gateId, config, identity, sealsFile, options) {
|
|
2659
|
+
async function processSingleGate(gateId, config, identity, identitySlug, sealsFile, options) {
|
|
2382
2660
|
verbose(`Processing gate: ${gateId}`);
|
|
2383
2661
|
const gate = getGate(config, gateId);
|
|
2384
2662
|
if (!gate) {
|
|
@@ -2418,12 +2696,12 @@ async function processSingleGate(gateId, config, identity, sealsFile, options) {
|
|
|
2418
2696
|
const seal = createSeal({
|
|
2419
2697
|
gateId,
|
|
2420
2698
|
fingerprint: fingerprintResult.fingerprint,
|
|
2421
|
-
sealedBy:
|
|
2699
|
+
sealedBy: identitySlug,
|
|
2422
2700
|
privateKey: privateKeyPem
|
|
2423
2701
|
});
|
|
2424
2702
|
sealsFile.seals[gateId] = seal;
|
|
2425
2703
|
log(` Sealed gate: ${gateId}`);
|
|
2426
|
-
verbose(` Sealed by: ${identity.name}`);
|
|
2704
|
+
verbose(` Sealed by: ${identitySlug} (${identity.name})`);
|
|
2427
2705
|
verbose(` Timestamp: ${seal.timestamp}`);
|
|
2428
2706
|
return { sealed: true, skipped: false };
|
|
2429
2707
|
}
|
|
@@ -2948,6 +3226,7 @@ async function runCreate() {
|
|
|
2948
3226
|
};
|
|
2949
3227
|
}
|
|
2950
3228
|
await saveLocalConfig(newConfig);
|
|
3229
|
+
const publicKeyResult = await savePublicKey(slug, keyPair.publicKey);
|
|
2951
3230
|
log("");
|
|
2952
3231
|
success("Identity created successfully");
|
|
2953
3232
|
log("");
|
|
@@ -2962,6 +3241,12 @@ async function runCreate() {
|
|
|
2962
3241
|
log(` Public Key: ${keyPair.publicKey.slice(0, 32)}...`);
|
|
2963
3242
|
log(` Private Key: ${keyStorageDescription}`);
|
|
2964
3243
|
log("");
|
|
3244
|
+
log(theme3.blue.bold()("Public key saved to:"));
|
|
3245
|
+
log(` ${publicKeyResult.homePath}`);
|
|
3246
|
+
if (publicKeyResult.projectPath) {
|
|
3247
|
+
log(` ${publicKeyResult.projectPath}`);
|
|
3248
|
+
}
|
|
3249
|
+
log("");
|
|
2965
3250
|
if (!existingConfig) {
|
|
2966
3251
|
success(`Set as active identity`);
|
|
2967
3252
|
log("");
|
|
@@ -3853,7 +4138,7 @@ async function runRemove2(slug, options) {
|
|
|
3853
4138
|
const projectRoot = process.cwd();
|
|
3854
4139
|
let sealsFile;
|
|
3855
4140
|
try {
|
|
3856
|
-
sealsFile = readSealsSync(projectRoot);
|
|
4141
|
+
sealsFile = readSealsSync(projectRoot, attestItConfig.settings.sealsPath);
|
|
3857
4142
|
} catch {
|
|
3858
4143
|
sealsFile = { version: 1, seals: {} };
|
|
3859
4144
|
}
|