@attest-it/cli 0.6.0 → 0.7.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 +206 -9
- package/dist/bin/attest-it.js.map +1 -1
- package/dist/index.cjs +205 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +206 -9
- 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';
|
|
@@ -1639,17 +1639,28 @@ function getKeyRefFromIdentity(identity) {
|
|
|
1639
1639
|
}
|
|
1640
1640
|
}
|
|
1641
1641
|
function KeygenInteractive(props) {
|
|
1642
|
-
const { onComplete, onError } = props;
|
|
1642
|
+
const { onComplete, onCancel, onError } = props;
|
|
1643
1643
|
const [step, setStep] = useState("checking-providers");
|
|
1644
1644
|
const [opAvailable, setOpAvailable] = useState(false);
|
|
1645
1645
|
const [keychainAvailable, setKeychainAvailable] = useState(false);
|
|
1646
|
+
const [yubiKeyAvailable, setYubiKeyAvailable] = useState(false);
|
|
1646
1647
|
const [accounts, setAccounts] = useState([]);
|
|
1647
1648
|
const [vaults, setVaults] = useState([]);
|
|
1649
|
+
const [yubiKeyDevices, setYubiKeyDevices] = useState([]);
|
|
1648
1650
|
const [_selectedProvider, setSelectedProvider] = useState();
|
|
1649
1651
|
const [selectedAccount, setSelectedAccount] = useState();
|
|
1650
1652
|
const [selectedVault, setSelectedVault] = useState();
|
|
1651
1653
|
const [itemName, setItemName] = useState("attest-it-private-key");
|
|
1652
1654
|
const [keychainItemName, setKeychainItemName] = useState("attest-it-private-key");
|
|
1655
|
+
const [selectedYubiKeySerial, setSelectedYubiKeySerial] = useState();
|
|
1656
|
+
const [selectedYubiKeySlot, setSelectedYubiKeySlot] = useState(2);
|
|
1657
|
+
const [slot1Configured, setSlot1Configured] = useState(false);
|
|
1658
|
+
const [slot2Configured, setSlot2Configured] = useState(false);
|
|
1659
|
+
useInput((_input, key) => {
|
|
1660
|
+
if (key.escape) {
|
|
1661
|
+
onCancel();
|
|
1662
|
+
}
|
|
1663
|
+
});
|
|
1653
1664
|
useEffect(() => {
|
|
1654
1665
|
const checkProviders = async () => {
|
|
1655
1666
|
try {
|
|
@@ -1664,6 +1675,19 @@ function KeygenInteractive(props) {
|
|
|
1664
1675
|
}
|
|
1665
1676
|
const isKeychainAvailable = MacOSKeychainKeyProvider.isAvailable();
|
|
1666
1677
|
setKeychainAvailable(isKeychainAvailable);
|
|
1678
|
+
try {
|
|
1679
|
+
const isInstalled = await YubiKeyProvider.isInstalled();
|
|
1680
|
+
if (isInstalled) {
|
|
1681
|
+
const isConnected = await YubiKeyProvider.isConnected();
|
|
1682
|
+
if (isConnected) {
|
|
1683
|
+
const devices = await YubiKeyProvider.listDevices();
|
|
1684
|
+
setYubiKeyDevices(devices);
|
|
1685
|
+
setYubiKeyAvailable(devices.length > 0);
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
} catch {
|
|
1689
|
+
setYubiKeyAvailable(false);
|
|
1690
|
+
}
|
|
1667
1691
|
setStep("select-provider");
|
|
1668
1692
|
};
|
|
1669
1693
|
void checkProviders();
|
|
@@ -1681,6 +1705,27 @@ function KeygenInteractive(props) {
|
|
|
1681
1705
|
void fetchVaults();
|
|
1682
1706
|
}
|
|
1683
1707
|
}, [step, selectedAccount, onError]);
|
|
1708
|
+
const checkYubiKeySlots = async (serial) => {
|
|
1709
|
+
try {
|
|
1710
|
+
const slot1 = await YubiKeyProvider.isChallengeResponseConfigured(1, serial);
|
|
1711
|
+
const slot2 = await YubiKeyProvider.isChallengeResponseConfigured(2, serial);
|
|
1712
|
+
setSlot1Configured(slot1);
|
|
1713
|
+
setSlot2Configured(slot2);
|
|
1714
|
+
if (slot1 && slot2) {
|
|
1715
|
+
setStep("select-yubikey-slot");
|
|
1716
|
+
} else if (slot2) {
|
|
1717
|
+
setSelectedYubiKeySlot(2);
|
|
1718
|
+
void generateKeys("yubikey");
|
|
1719
|
+
} else if (slot1) {
|
|
1720
|
+
setSelectedYubiKeySlot(1);
|
|
1721
|
+
void generateKeys("yubikey");
|
|
1722
|
+
} else {
|
|
1723
|
+
setStep("yubikey-offer-setup");
|
|
1724
|
+
}
|
|
1725
|
+
} catch (err) {
|
|
1726
|
+
onError(err instanceof Error ? err : new Error("Failed to check YubiKey slots"));
|
|
1727
|
+
}
|
|
1728
|
+
};
|
|
1684
1729
|
const handleProviderSelect = (value) => {
|
|
1685
1730
|
if (value === "filesystem") {
|
|
1686
1731
|
setSelectedProvider("filesystem");
|
|
@@ -1688,7 +1733,7 @@ function KeygenInteractive(props) {
|
|
|
1688
1733
|
} else if (value === "1password") {
|
|
1689
1734
|
setSelectedProvider("1password");
|
|
1690
1735
|
if (accounts.length === 1 && accounts[0]) {
|
|
1691
|
-
setSelectedAccount(accounts[0].
|
|
1736
|
+
setSelectedAccount(accounts[0].user_uuid);
|
|
1692
1737
|
setStep("select-vault");
|
|
1693
1738
|
} else {
|
|
1694
1739
|
setStep("select-account");
|
|
@@ -1696,6 +1741,14 @@ function KeygenInteractive(props) {
|
|
|
1696
1741
|
} else if (value === "macos-keychain") {
|
|
1697
1742
|
setSelectedProvider("macos-keychain");
|
|
1698
1743
|
setStep("enter-keychain-item-name");
|
|
1744
|
+
} else if (value === "yubikey") {
|
|
1745
|
+
setSelectedProvider("yubikey");
|
|
1746
|
+
if (yubiKeyDevices.length > 1) {
|
|
1747
|
+
setStep("select-yubikey-device");
|
|
1748
|
+
} else if (yubiKeyDevices.length === 1 && yubiKeyDevices[0]) {
|
|
1749
|
+
setSelectedYubiKeySerial(yubiKeyDevices[0].serial);
|
|
1750
|
+
void checkYubiKeySlots(yubiKeyDevices[0].serial);
|
|
1751
|
+
}
|
|
1699
1752
|
}
|
|
1700
1753
|
};
|
|
1701
1754
|
const handleAccountSelect = (value) => {
|
|
@@ -1714,6 +1767,47 @@ function KeygenInteractive(props) {
|
|
|
1714
1767
|
setKeychainItemName(value);
|
|
1715
1768
|
void generateKeys("macos-keychain");
|
|
1716
1769
|
};
|
|
1770
|
+
const handleYubiKeyDeviceSelect = (value) => {
|
|
1771
|
+
setSelectedYubiKeySerial(value);
|
|
1772
|
+
void checkYubiKeySlots(value);
|
|
1773
|
+
};
|
|
1774
|
+
const handleYubiKeySlotSelect = (value) => {
|
|
1775
|
+
const slot = value === "1" ? 1 : 2;
|
|
1776
|
+
setSelectedYubiKeySlot(slot);
|
|
1777
|
+
void generateKeys("yubikey");
|
|
1778
|
+
};
|
|
1779
|
+
const handleYubiKeySetupConfirm = (value) => {
|
|
1780
|
+
if (value === "yes") {
|
|
1781
|
+
void setupYubiKeySlot();
|
|
1782
|
+
} else {
|
|
1783
|
+
onError(new Error("YubiKey setup cancelled"));
|
|
1784
|
+
}
|
|
1785
|
+
};
|
|
1786
|
+
const setupYubiKeySlot = async () => {
|
|
1787
|
+
setStep("yubikey-configuring");
|
|
1788
|
+
try {
|
|
1789
|
+
const { spawn: spawn3 } = await import('child_process');
|
|
1790
|
+
const args = ["otp", "chalresp", "--touch", "--generate", "2"];
|
|
1791
|
+
if (selectedYubiKeySerial) {
|
|
1792
|
+
args.unshift("--device", selectedYubiKeySerial);
|
|
1793
|
+
}
|
|
1794
|
+
await new Promise((resolve2, reject) => {
|
|
1795
|
+
const proc = spawn3("ykman", args, { stdio: "inherit" });
|
|
1796
|
+
proc.on("close", (code) => {
|
|
1797
|
+
if (code === 0) {
|
|
1798
|
+
resolve2();
|
|
1799
|
+
} else {
|
|
1800
|
+
reject(new Error(`ykman exited with code ${String(code)}`));
|
|
1801
|
+
}
|
|
1802
|
+
});
|
|
1803
|
+
proc.on("error", reject);
|
|
1804
|
+
});
|
|
1805
|
+
setSelectedYubiKeySlot(2);
|
|
1806
|
+
void generateKeys("yubikey");
|
|
1807
|
+
} catch (err) {
|
|
1808
|
+
onError(err instanceof Error ? err : new Error("Failed to configure YubiKey"));
|
|
1809
|
+
}
|
|
1810
|
+
};
|
|
1717
1811
|
const generateKeys = async (provider) => {
|
|
1718
1812
|
setStep("generating");
|
|
1719
1813
|
try {
|
|
@@ -1735,8 +1829,12 @@ function KeygenInteractive(props) {
|
|
|
1735
1829
|
if (!selectedVault || !itemName) {
|
|
1736
1830
|
throw new Error("Vault and item name are required for 1Password");
|
|
1737
1831
|
}
|
|
1832
|
+
const vault = vaults.find((v) => v.id === selectedVault);
|
|
1833
|
+
if (!vault) {
|
|
1834
|
+
throw new Error("Selected vault not found");
|
|
1835
|
+
}
|
|
1738
1836
|
const providerOptions = {
|
|
1739
|
-
vault:
|
|
1837
|
+
vault: vault.name,
|
|
1740
1838
|
itemName
|
|
1741
1839
|
};
|
|
1742
1840
|
if (selectedAccount !== void 0) {
|
|
@@ -1753,14 +1851,14 @@ function KeygenInteractive(props) {
|
|
|
1753
1851
|
publicKeyPath: result.publicKeyPath,
|
|
1754
1852
|
privateKeyRef: result.privateKeyRef,
|
|
1755
1853
|
storageDescription: result.storageDescription,
|
|
1756
|
-
vault:
|
|
1854
|
+
vault: vault.name,
|
|
1757
1855
|
itemName
|
|
1758
1856
|
};
|
|
1759
1857
|
if (selectedAccount !== void 0) {
|
|
1760
1858
|
completionResult.account = selectedAccount;
|
|
1761
1859
|
}
|
|
1762
1860
|
onComplete(completionResult);
|
|
1763
|
-
} else {
|
|
1861
|
+
} else if (provider === "macos-keychain") {
|
|
1764
1862
|
if (!keychainItemName) {
|
|
1765
1863
|
throw new Error("Item name is required for macOS Keychain");
|
|
1766
1864
|
}
|
|
@@ -1779,6 +1877,33 @@ function KeygenInteractive(props) {
|
|
|
1779
1877
|
storageDescription: result.storageDescription,
|
|
1780
1878
|
itemName: keychainItemName
|
|
1781
1879
|
});
|
|
1880
|
+
} else {
|
|
1881
|
+
const encryptedKeyPath = getDefaultYubiKeyEncryptedKeyPath();
|
|
1882
|
+
const providerOptions = {
|
|
1883
|
+
encryptedKeyPath,
|
|
1884
|
+
slot: selectedYubiKeySlot
|
|
1885
|
+
};
|
|
1886
|
+
if (selectedYubiKeySerial !== void 0) {
|
|
1887
|
+
providerOptions.serial = selectedYubiKeySerial;
|
|
1888
|
+
}
|
|
1889
|
+
const ykProvider = new YubiKeyProvider(providerOptions);
|
|
1890
|
+
const genOptions = { publicKeyPath };
|
|
1891
|
+
if (props.force !== void 0) {
|
|
1892
|
+
genOptions.force = props.force;
|
|
1893
|
+
}
|
|
1894
|
+
const result = await ykProvider.generateKeyPair(genOptions);
|
|
1895
|
+
const completionResult = {
|
|
1896
|
+
provider: "yubikey",
|
|
1897
|
+
publicKeyPath: result.publicKeyPath,
|
|
1898
|
+
privateKeyRef: result.privateKeyRef,
|
|
1899
|
+
storageDescription: result.storageDescription,
|
|
1900
|
+
slot: selectedYubiKeySlot,
|
|
1901
|
+
encryptedKeyPath
|
|
1902
|
+
};
|
|
1903
|
+
if (selectedYubiKeySerial !== void 0) {
|
|
1904
|
+
completionResult.serial = selectedYubiKeySerial;
|
|
1905
|
+
}
|
|
1906
|
+
onComplete(completionResult);
|
|
1782
1907
|
}
|
|
1783
1908
|
setStep("done");
|
|
1784
1909
|
} catch (err) {
|
|
@@ -1804,6 +1929,12 @@ function KeygenInteractive(props) {
|
|
|
1804
1929
|
value: "macos-keychain"
|
|
1805
1930
|
});
|
|
1806
1931
|
}
|
|
1932
|
+
if (yubiKeyAvailable) {
|
|
1933
|
+
options.push({
|
|
1934
|
+
label: "YubiKey (hardware security key)",
|
|
1935
|
+
value: "yubikey"
|
|
1936
|
+
});
|
|
1937
|
+
}
|
|
1807
1938
|
if (opAvailable) {
|
|
1808
1939
|
options.push({
|
|
1809
1940
|
label: "1Password (requires op CLI)",
|
|
@@ -1818,8 +1949,8 @@ function KeygenInteractive(props) {
|
|
|
1818
1949
|
}
|
|
1819
1950
|
if (step === "select-account") {
|
|
1820
1951
|
const options = accounts.map((account) => ({
|
|
1821
|
-
label: account.email,
|
|
1822
|
-
value: account.
|
|
1952
|
+
label: account.name ? `${account.name} (${account.email})` : account.email,
|
|
1953
|
+
value: account.user_uuid
|
|
1823
1954
|
}));
|
|
1824
1955
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
1825
1956
|
/* @__PURE__ */ jsx(Text, { bold: true, children: "Select 1Password account:" }),
|
|
@@ -1836,7 +1967,7 @@ function KeygenInteractive(props) {
|
|
|
1836
1967
|
}
|
|
1837
1968
|
const options = vaults.map((vault) => ({
|
|
1838
1969
|
label: vault.name,
|
|
1839
|
-
value: vault.
|
|
1970
|
+
value: vault.id
|
|
1840
1971
|
}));
|
|
1841
1972
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
1842
1973
|
/* @__PURE__ */ jsx(Text, { bold: true, children: "Select vault for private key storage:" }),
|
|
@@ -1860,6 +1991,65 @@ function KeygenInteractive(props) {
|
|
|
1860
1991
|
/* @__PURE__ */ jsx(TextInput, { defaultValue: keychainItemName, onSubmit: handleKeychainItemNameSubmit })
|
|
1861
1992
|
] });
|
|
1862
1993
|
}
|
|
1994
|
+
if (step === "select-yubikey-device") {
|
|
1995
|
+
const options = yubiKeyDevices.map((device) => ({
|
|
1996
|
+
label: `${device.type} (Serial: ${device.serial})`,
|
|
1997
|
+
value: device.serial
|
|
1998
|
+
}));
|
|
1999
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
2000
|
+
/* @__PURE__ */ jsx(Text, { bold: true, children: "Select YubiKey device:" }),
|
|
2001
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "" }),
|
|
2002
|
+
/* @__PURE__ */ jsx(Select, { options, onChange: handleYubiKeyDeviceSelect })
|
|
2003
|
+
] });
|
|
2004
|
+
}
|
|
2005
|
+
if (step === "select-yubikey-slot") {
|
|
2006
|
+
const options = [];
|
|
2007
|
+
if (slot2Configured) {
|
|
2008
|
+
options.push({
|
|
2009
|
+
label: "Slot 2 (recommended)",
|
|
2010
|
+
value: "2"
|
|
2011
|
+
});
|
|
2012
|
+
}
|
|
2013
|
+
if (slot1Configured) {
|
|
2014
|
+
options.push({
|
|
2015
|
+
label: "Slot 1",
|
|
2016
|
+
value: "1"
|
|
2017
|
+
});
|
|
2018
|
+
}
|
|
2019
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
2020
|
+
/* @__PURE__ */ jsx(Text, { bold: true, children: "Select YubiKey slot for challenge-response:" }),
|
|
2021
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "" }),
|
|
2022
|
+
/* @__PURE__ */ jsx(Select, { options, onChange: handleYubiKeySlotSelect })
|
|
2023
|
+
] });
|
|
2024
|
+
}
|
|
2025
|
+
if (step === "yubikey-offer-setup") {
|
|
2026
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
2027
|
+
/* @__PURE__ */ jsx(Text, { bold: true, children: "Your YubiKey is not configured for challenge-response." }),
|
|
2028
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "" }),
|
|
2029
|
+
/* @__PURE__ */ jsx(Text, { children: "Would you like to configure slot 2 for challenge-response now?" }),
|
|
2030
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "This will enable touch-to-sign functionality." }),
|
|
2031
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "" }),
|
|
2032
|
+
/* @__PURE__ */ jsx(
|
|
2033
|
+
Select,
|
|
2034
|
+
{
|
|
2035
|
+
options: [
|
|
2036
|
+
{ label: "Yes, configure my YubiKey", value: "yes" },
|
|
2037
|
+
{ label: "No, cancel", value: "no" }
|
|
2038
|
+
],
|
|
2039
|
+
onChange: handleYubiKeySetupConfirm
|
|
2040
|
+
}
|
|
2041
|
+
)
|
|
2042
|
+
] });
|
|
2043
|
+
}
|
|
2044
|
+
if (step === "yubikey-configuring") {
|
|
2045
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
2046
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
|
|
2047
|
+
/* @__PURE__ */ jsx(Spinner, {}),
|
|
2048
|
+
/* @__PURE__ */ jsx(Text, { children: "Configuring YubiKey slot 2 for challenge-response..." })
|
|
2049
|
+
] }),
|
|
2050
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "Touch your YubiKey when it flashes." })
|
|
2051
|
+
] });
|
|
2052
|
+
}
|
|
1863
2053
|
if (step === "generating") {
|
|
1864
2054
|
return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
|
|
1865
2055
|
/* @__PURE__ */ jsx(Spinner, {}),
|
|
@@ -2944,6 +3134,7 @@ async function runCreate() {
|
|
|
2944
3134
|
};
|
|
2945
3135
|
}
|
|
2946
3136
|
await saveLocalConfig(newConfig);
|
|
3137
|
+
const publicKeyResult = await savePublicKey(slug, keyPair.publicKey);
|
|
2947
3138
|
log("");
|
|
2948
3139
|
success("Identity created successfully");
|
|
2949
3140
|
log("");
|
|
@@ -2958,6 +3149,12 @@ async function runCreate() {
|
|
|
2958
3149
|
log(` Public Key: ${keyPair.publicKey.slice(0, 32)}...`);
|
|
2959
3150
|
log(` Private Key: ${keyStorageDescription}`);
|
|
2960
3151
|
log("");
|
|
3152
|
+
log(theme3.blue.bold()("Public key saved to:"));
|
|
3153
|
+
log(` ${publicKeyResult.homePath}`);
|
|
3154
|
+
if (publicKeyResult.projectPath) {
|
|
3155
|
+
log(` ${publicKeyResult.projectPath}`);
|
|
3156
|
+
}
|
|
3157
|
+
log("");
|
|
2961
3158
|
if (!existingConfig) {
|
|
2962
3159
|
success(`Set as active identity`);
|
|
2963
3160
|
log("");
|