@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/index.cjs
CHANGED
|
@@ -1665,17 +1665,28 @@ function getKeyRefFromIdentity(identity) {
|
|
|
1665
1665
|
}
|
|
1666
1666
|
}
|
|
1667
1667
|
function KeygenInteractive(props) {
|
|
1668
|
-
const { onComplete, onError } = props;
|
|
1668
|
+
const { onComplete, onCancel, onError } = props;
|
|
1669
1669
|
const [step, setStep] = React7.useState("checking-providers");
|
|
1670
1670
|
const [opAvailable, setOpAvailable] = React7.useState(false);
|
|
1671
1671
|
const [keychainAvailable, setKeychainAvailable] = React7.useState(false);
|
|
1672
|
+
const [yubiKeyAvailable, setYubiKeyAvailable] = React7.useState(false);
|
|
1672
1673
|
const [accounts, setAccounts] = React7.useState([]);
|
|
1673
1674
|
const [vaults, setVaults] = React7.useState([]);
|
|
1675
|
+
const [yubiKeyDevices, setYubiKeyDevices] = React7.useState([]);
|
|
1674
1676
|
const [_selectedProvider, setSelectedProvider] = React7.useState();
|
|
1675
1677
|
const [selectedAccount, setSelectedAccount] = React7.useState();
|
|
1676
1678
|
const [selectedVault, setSelectedVault] = React7.useState();
|
|
1677
1679
|
const [itemName, setItemName] = React7.useState("attest-it-private-key");
|
|
1678
1680
|
const [keychainItemName, setKeychainItemName] = React7.useState("attest-it-private-key");
|
|
1681
|
+
const [selectedYubiKeySerial, setSelectedYubiKeySerial] = React7.useState();
|
|
1682
|
+
const [selectedYubiKeySlot, setSelectedYubiKeySlot] = React7.useState(2);
|
|
1683
|
+
const [slot1Configured, setSlot1Configured] = React7.useState(false);
|
|
1684
|
+
const [slot2Configured, setSlot2Configured] = React7.useState(false);
|
|
1685
|
+
ink.useInput((_input, key) => {
|
|
1686
|
+
if (key.escape) {
|
|
1687
|
+
onCancel();
|
|
1688
|
+
}
|
|
1689
|
+
});
|
|
1679
1690
|
React7.useEffect(() => {
|
|
1680
1691
|
const checkProviders = async () => {
|
|
1681
1692
|
try {
|
|
@@ -1690,6 +1701,19 @@ function KeygenInteractive(props) {
|
|
|
1690
1701
|
}
|
|
1691
1702
|
const isKeychainAvailable = core.MacOSKeychainKeyProvider.isAvailable();
|
|
1692
1703
|
setKeychainAvailable(isKeychainAvailable);
|
|
1704
|
+
try {
|
|
1705
|
+
const isInstalled = await core.YubiKeyProvider.isInstalled();
|
|
1706
|
+
if (isInstalled) {
|
|
1707
|
+
const isConnected = await core.YubiKeyProvider.isConnected();
|
|
1708
|
+
if (isConnected) {
|
|
1709
|
+
const devices = await core.YubiKeyProvider.listDevices();
|
|
1710
|
+
setYubiKeyDevices(devices);
|
|
1711
|
+
setYubiKeyAvailable(devices.length > 0);
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
} catch {
|
|
1715
|
+
setYubiKeyAvailable(false);
|
|
1716
|
+
}
|
|
1693
1717
|
setStep("select-provider");
|
|
1694
1718
|
};
|
|
1695
1719
|
void checkProviders();
|
|
@@ -1707,6 +1731,27 @@ function KeygenInteractive(props) {
|
|
|
1707
1731
|
void fetchVaults();
|
|
1708
1732
|
}
|
|
1709
1733
|
}, [step, selectedAccount, onError]);
|
|
1734
|
+
const checkYubiKeySlots = async (serial) => {
|
|
1735
|
+
try {
|
|
1736
|
+
const slot1 = await core.YubiKeyProvider.isChallengeResponseConfigured(1, serial);
|
|
1737
|
+
const slot2 = await core.YubiKeyProvider.isChallengeResponseConfigured(2, serial);
|
|
1738
|
+
setSlot1Configured(slot1);
|
|
1739
|
+
setSlot2Configured(slot2);
|
|
1740
|
+
if (slot1 && slot2) {
|
|
1741
|
+
setStep("select-yubikey-slot");
|
|
1742
|
+
} else if (slot2) {
|
|
1743
|
+
setSelectedYubiKeySlot(2);
|
|
1744
|
+
void generateKeys("yubikey");
|
|
1745
|
+
} else if (slot1) {
|
|
1746
|
+
setSelectedYubiKeySlot(1);
|
|
1747
|
+
void generateKeys("yubikey");
|
|
1748
|
+
} else {
|
|
1749
|
+
setStep("yubikey-offer-setup");
|
|
1750
|
+
}
|
|
1751
|
+
} catch (err) {
|
|
1752
|
+
onError(err instanceof Error ? err : new Error("Failed to check YubiKey slots"));
|
|
1753
|
+
}
|
|
1754
|
+
};
|
|
1710
1755
|
const handleProviderSelect = (value) => {
|
|
1711
1756
|
if (value === "filesystem") {
|
|
1712
1757
|
setSelectedProvider("filesystem");
|
|
@@ -1714,7 +1759,7 @@ function KeygenInteractive(props) {
|
|
|
1714
1759
|
} else if (value === "1password") {
|
|
1715
1760
|
setSelectedProvider("1password");
|
|
1716
1761
|
if (accounts.length === 1 && accounts[0]) {
|
|
1717
|
-
setSelectedAccount(accounts[0].
|
|
1762
|
+
setSelectedAccount(accounts[0].user_uuid);
|
|
1718
1763
|
setStep("select-vault");
|
|
1719
1764
|
} else {
|
|
1720
1765
|
setStep("select-account");
|
|
@@ -1722,6 +1767,14 @@ function KeygenInteractive(props) {
|
|
|
1722
1767
|
} else if (value === "macos-keychain") {
|
|
1723
1768
|
setSelectedProvider("macos-keychain");
|
|
1724
1769
|
setStep("enter-keychain-item-name");
|
|
1770
|
+
} else if (value === "yubikey") {
|
|
1771
|
+
setSelectedProvider("yubikey");
|
|
1772
|
+
if (yubiKeyDevices.length > 1) {
|
|
1773
|
+
setStep("select-yubikey-device");
|
|
1774
|
+
} else if (yubiKeyDevices.length === 1 && yubiKeyDevices[0]) {
|
|
1775
|
+
setSelectedYubiKeySerial(yubiKeyDevices[0].serial);
|
|
1776
|
+
void checkYubiKeySlots(yubiKeyDevices[0].serial);
|
|
1777
|
+
}
|
|
1725
1778
|
}
|
|
1726
1779
|
};
|
|
1727
1780
|
const handleAccountSelect = (value) => {
|
|
@@ -1740,6 +1793,47 @@ function KeygenInteractive(props) {
|
|
|
1740
1793
|
setKeychainItemName(value);
|
|
1741
1794
|
void generateKeys("macos-keychain");
|
|
1742
1795
|
};
|
|
1796
|
+
const handleYubiKeyDeviceSelect = (value) => {
|
|
1797
|
+
setSelectedYubiKeySerial(value);
|
|
1798
|
+
void checkYubiKeySlots(value);
|
|
1799
|
+
};
|
|
1800
|
+
const handleYubiKeySlotSelect = (value) => {
|
|
1801
|
+
const slot = value === "1" ? 1 : 2;
|
|
1802
|
+
setSelectedYubiKeySlot(slot);
|
|
1803
|
+
void generateKeys("yubikey");
|
|
1804
|
+
};
|
|
1805
|
+
const handleYubiKeySetupConfirm = (value) => {
|
|
1806
|
+
if (value === "yes") {
|
|
1807
|
+
void setupYubiKeySlot();
|
|
1808
|
+
} else {
|
|
1809
|
+
onError(new Error("YubiKey setup cancelled"));
|
|
1810
|
+
}
|
|
1811
|
+
};
|
|
1812
|
+
const setupYubiKeySlot = async () => {
|
|
1813
|
+
setStep("yubikey-configuring");
|
|
1814
|
+
try {
|
|
1815
|
+
const { spawn: spawn3 } = await import('child_process');
|
|
1816
|
+
const args = ["otp", "chalresp", "--touch", "--generate", "2"];
|
|
1817
|
+
if (selectedYubiKeySerial) {
|
|
1818
|
+
args.unshift("--device", selectedYubiKeySerial);
|
|
1819
|
+
}
|
|
1820
|
+
await new Promise((resolve2, reject) => {
|
|
1821
|
+
const proc = spawn3("ykman", args, { stdio: "inherit" });
|
|
1822
|
+
proc.on("close", (code) => {
|
|
1823
|
+
if (code === 0) {
|
|
1824
|
+
resolve2();
|
|
1825
|
+
} else {
|
|
1826
|
+
reject(new Error(`ykman exited with code ${String(code)}`));
|
|
1827
|
+
}
|
|
1828
|
+
});
|
|
1829
|
+
proc.on("error", reject);
|
|
1830
|
+
});
|
|
1831
|
+
setSelectedYubiKeySlot(2);
|
|
1832
|
+
void generateKeys("yubikey");
|
|
1833
|
+
} catch (err) {
|
|
1834
|
+
onError(err instanceof Error ? err : new Error("Failed to configure YubiKey"));
|
|
1835
|
+
}
|
|
1836
|
+
};
|
|
1743
1837
|
const generateKeys = async (provider) => {
|
|
1744
1838
|
setStep("generating");
|
|
1745
1839
|
try {
|
|
@@ -1761,8 +1855,12 @@ function KeygenInteractive(props) {
|
|
|
1761
1855
|
if (!selectedVault || !itemName) {
|
|
1762
1856
|
throw new Error("Vault and item name are required for 1Password");
|
|
1763
1857
|
}
|
|
1858
|
+
const vault = vaults.find((v) => v.id === selectedVault);
|
|
1859
|
+
if (!vault) {
|
|
1860
|
+
throw new Error("Selected vault not found");
|
|
1861
|
+
}
|
|
1764
1862
|
const providerOptions = {
|
|
1765
|
-
vault:
|
|
1863
|
+
vault: vault.name,
|
|
1766
1864
|
itemName
|
|
1767
1865
|
};
|
|
1768
1866
|
if (selectedAccount !== void 0) {
|
|
@@ -1779,14 +1877,14 @@ function KeygenInteractive(props) {
|
|
|
1779
1877
|
publicKeyPath: result.publicKeyPath,
|
|
1780
1878
|
privateKeyRef: result.privateKeyRef,
|
|
1781
1879
|
storageDescription: result.storageDescription,
|
|
1782
|
-
vault:
|
|
1880
|
+
vault: vault.name,
|
|
1783
1881
|
itemName
|
|
1784
1882
|
};
|
|
1785
1883
|
if (selectedAccount !== void 0) {
|
|
1786
1884
|
completionResult.account = selectedAccount;
|
|
1787
1885
|
}
|
|
1788
1886
|
onComplete(completionResult);
|
|
1789
|
-
} else {
|
|
1887
|
+
} else if (provider === "macos-keychain") {
|
|
1790
1888
|
if (!keychainItemName) {
|
|
1791
1889
|
throw new Error("Item name is required for macOS Keychain");
|
|
1792
1890
|
}
|
|
@@ -1805,6 +1903,33 @@ function KeygenInteractive(props) {
|
|
|
1805
1903
|
storageDescription: result.storageDescription,
|
|
1806
1904
|
itemName: keychainItemName
|
|
1807
1905
|
});
|
|
1906
|
+
} else {
|
|
1907
|
+
const encryptedKeyPath = core.getDefaultYubiKeyEncryptedKeyPath();
|
|
1908
|
+
const providerOptions = {
|
|
1909
|
+
encryptedKeyPath,
|
|
1910
|
+
slot: selectedYubiKeySlot
|
|
1911
|
+
};
|
|
1912
|
+
if (selectedYubiKeySerial !== void 0) {
|
|
1913
|
+
providerOptions.serial = selectedYubiKeySerial;
|
|
1914
|
+
}
|
|
1915
|
+
const ykProvider = new core.YubiKeyProvider(providerOptions);
|
|
1916
|
+
const genOptions = { publicKeyPath };
|
|
1917
|
+
if (props.force !== void 0) {
|
|
1918
|
+
genOptions.force = props.force;
|
|
1919
|
+
}
|
|
1920
|
+
const result = await ykProvider.generateKeyPair(genOptions);
|
|
1921
|
+
const completionResult = {
|
|
1922
|
+
provider: "yubikey",
|
|
1923
|
+
publicKeyPath: result.publicKeyPath,
|
|
1924
|
+
privateKeyRef: result.privateKeyRef,
|
|
1925
|
+
storageDescription: result.storageDescription,
|
|
1926
|
+
slot: selectedYubiKeySlot,
|
|
1927
|
+
encryptedKeyPath
|
|
1928
|
+
};
|
|
1929
|
+
if (selectedYubiKeySerial !== void 0) {
|
|
1930
|
+
completionResult.serial = selectedYubiKeySerial;
|
|
1931
|
+
}
|
|
1932
|
+
onComplete(completionResult);
|
|
1808
1933
|
}
|
|
1809
1934
|
setStep("done");
|
|
1810
1935
|
} catch (err) {
|
|
@@ -1830,6 +1955,12 @@ function KeygenInteractive(props) {
|
|
|
1830
1955
|
value: "macos-keychain"
|
|
1831
1956
|
});
|
|
1832
1957
|
}
|
|
1958
|
+
if (yubiKeyAvailable) {
|
|
1959
|
+
options.push({
|
|
1960
|
+
label: "YubiKey (hardware security key)",
|
|
1961
|
+
value: "yubikey"
|
|
1962
|
+
});
|
|
1963
|
+
}
|
|
1833
1964
|
if (opAvailable) {
|
|
1834
1965
|
options.push({
|
|
1835
1966
|
label: "1Password (requires op CLI)",
|
|
@@ -1844,8 +1975,8 @@ function KeygenInteractive(props) {
|
|
|
1844
1975
|
}
|
|
1845
1976
|
if (step === "select-account") {
|
|
1846
1977
|
const options = accounts.map((account) => ({
|
|
1847
|
-
label: account.email,
|
|
1848
|
-
value: account.
|
|
1978
|
+
label: account.name ? `${account.name} (${account.email})` : account.email,
|
|
1979
|
+
value: account.user_uuid
|
|
1849
1980
|
}));
|
|
1850
1981
|
return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", children: [
|
|
1851
1982
|
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { bold: true, children: "Select 1Password account:" }),
|
|
@@ -1862,7 +1993,7 @@ function KeygenInteractive(props) {
|
|
|
1862
1993
|
}
|
|
1863
1994
|
const options = vaults.map((vault) => ({
|
|
1864
1995
|
label: vault.name,
|
|
1865
|
-
value: vault.
|
|
1996
|
+
value: vault.id
|
|
1866
1997
|
}));
|
|
1867
1998
|
return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", children: [
|
|
1868
1999
|
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { bold: true, children: "Select vault for private key storage:" }),
|
|
@@ -1886,6 +2017,65 @@ function KeygenInteractive(props) {
|
|
|
1886
2017
|
/* @__PURE__ */ jsxRuntime.jsx(ui.TextInput, { defaultValue: keychainItemName, onSubmit: handleKeychainItemNameSubmit })
|
|
1887
2018
|
] });
|
|
1888
2019
|
}
|
|
2020
|
+
if (step === "select-yubikey-device") {
|
|
2021
|
+
const options = yubiKeyDevices.map((device) => ({
|
|
2022
|
+
label: `${device.type} (Serial: ${device.serial})`,
|
|
2023
|
+
value: device.serial
|
|
2024
|
+
}));
|
|
2025
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", children: [
|
|
2026
|
+
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { bold: true, children: "Select YubiKey device:" }),
|
|
2027
|
+
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "" }),
|
|
2028
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Select, { options, onChange: handleYubiKeyDeviceSelect })
|
|
2029
|
+
] });
|
|
2030
|
+
}
|
|
2031
|
+
if (step === "select-yubikey-slot") {
|
|
2032
|
+
const options = [];
|
|
2033
|
+
if (slot2Configured) {
|
|
2034
|
+
options.push({
|
|
2035
|
+
label: "Slot 2 (recommended)",
|
|
2036
|
+
value: "2"
|
|
2037
|
+
});
|
|
2038
|
+
}
|
|
2039
|
+
if (slot1Configured) {
|
|
2040
|
+
options.push({
|
|
2041
|
+
label: "Slot 1",
|
|
2042
|
+
value: "1"
|
|
2043
|
+
});
|
|
2044
|
+
}
|
|
2045
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", children: [
|
|
2046
|
+
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { bold: true, children: "Select YubiKey slot for challenge-response:" }),
|
|
2047
|
+
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "" }),
|
|
2048
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Select, { options, onChange: handleYubiKeySlotSelect })
|
|
2049
|
+
] });
|
|
2050
|
+
}
|
|
2051
|
+
if (step === "yubikey-offer-setup") {
|
|
2052
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", children: [
|
|
2053
|
+
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { bold: true, children: "Your YubiKey is not configured for challenge-response." }),
|
|
2054
|
+
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "" }),
|
|
2055
|
+
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { children: "Would you like to configure slot 2 for challenge-response now?" }),
|
|
2056
|
+
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "This will enable touch-to-sign functionality." }),
|
|
2057
|
+
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "" }),
|
|
2058
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2059
|
+
ui.Select,
|
|
2060
|
+
{
|
|
2061
|
+
options: [
|
|
2062
|
+
{ label: "Yes, configure my YubiKey", value: "yes" },
|
|
2063
|
+
{ label: "No, cancel", value: "no" }
|
|
2064
|
+
],
|
|
2065
|
+
onChange: handleYubiKeySetupConfirm
|
|
2066
|
+
}
|
|
2067
|
+
)
|
|
2068
|
+
] });
|
|
2069
|
+
}
|
|
2070
|
+
if (step === "yubikey-configuring") {
|
|
2071
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", children: [
|
|
2072
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "row", gap: 1, children: [
|
|
2073
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Spinner, {}),
|
|
2074
|
+
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { children: "Configuring YubiKey slot 2 for challenge-response..." })
|
|
2075
|
+
] }),
|
|
2076
|
+
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "Touch your YubiKey when it flashes." })
|
|
2077
|
+
] });
|
|
2078
|
+
}
|
|
1889
2079
|
if (step === "generating") {
|
|
1890
2080
|
return /* @__PURE__ */ jsxRuntime.jsx(ink.Box, { flexDirection: "column", children: /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "row", gap: 1, children: [
|
|
1891
2081
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Spinner, {}),
|
|
@@ -2970,6 +3160,7 @@ async function runCreate() {
|
|
|
2970
3160
|
};
|
|
2971
3161
|
}
|
|
2972
3162
|
await core.saveLocalConfig(newConfig);
|
|
3163
|
+
const publicKeyResult = await core.savePublicKey(slug, keyPair.publicKey);
|
|
2973
3164
|
log("");
|
|
2974
3165
|
success("Identity created successfully");
|
|
2975
3166
|
log("");
|
|
@@ -2984,6 +3175,12 @@ async function runCreate() {
|
|
|
2984
3175
|
log(` Public Key: ${keyPair.publicKey.slice(0, 32)}...`);
|
|
2985
3176
|
log(` Private Key: ${keyStorageDescription}`);
|
|
2986
3177
|
log("");
|
|
3178
|
+
log(theme3.blue.bold()("Public key saved to:"));
|
|
3179
|
+
log(` ${publicKeyResult.homePath}`);
|
|
3180
|
+
if (publicKeyResult.projectPath) {
|
|
3181
|
+
log(` ${publicKeyResult.projectPath}`);
|
|
3182
|
+
}
|
|
3183
|
+
log("");
|
|
2987
3184
|
if (!existingConfig) {
|
|
2988
3185
|
success(`Set as active identity`);
|
|
2989
3186
|
log("");
|