@attest-it/cli 0.6.1 → 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/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");
@@ -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 {
@@ -1790,7 +1884,7 @@ function KeygenInteractive(props) {
1790
1884
  completionResult.account = selectedAccount;
1791
1885
  }
1792
1886
  onComplete(completionResult);
1793
- } else {
1887
+ } else if (provider === "macos-keychain") {
1794
1888
  if (!keychainItemName) {
1795
1889
  throw new Error("Item name is required for macOS Keychain");
1796
1890
  }
@@ -1809,6 +1903,33 @@ function KeygenInteractive(props) {
1809
1903
  storageDescription: result.storageDescription,
1810
1904
  itemName: keychainItemName
1811
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);
1812
1933
  }
1813
1934
  setStep("done");
1814
1935
  } catch (err) {
@@ -1834,6 +1955,12 @@ function KeygenInteractive(props) {
1834
1955
  value: "macos-keychain"
1835
1956
  });
1836
1957
  }
1958
+ if (yubiKeyAvailable) {
1959
+ options.push({
1960
+ label: "YubiKey (hardware security key)",
1961
+ value: "yubikey"
1962
+ });
1963
+ }
1837
1964
  if (opAvailable) {
1838
1965
  options.push({
1839
1966
  label: "1Password (requires op CLI)",
@@ -1848,7 +1975,7 @@ function KeygenInteractive(props) {
1848
1975
  }
1849
1976
  if (step === "select-account") {
1850
1977
  const options = accounts.map((account) => ({
1851
- label: account.email,
1978
+ label: account.name ? `${account.name} (${account.email})` : account.email,
1852
1979
  value: account.user_uuid
1853
1980
  }));
1854
1981
  return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", children: [
@@ -1890,6 +2017,65 @@ function KeygenInteractive(props) {
1890
2017
  /* @__PURE__ */ jsxRuntime.jsx(ui.TextInput, { defaultValue: keychainItemName, onSubmit: handleKeychainItemNameSubmit })
1891
2018
  ] });
1892
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
+ }
1893
2079
  if (step === "generating") {
1894
2080
  return /* @__PURE__ */ jsxRuntime.jsx(ink.Box, { flexDirection: "column", children: /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "row", gap: 1, children: [
1895
2081
  /* @__PURE__ */ jsxRuntime.jsx(ui.Spinner, {}),
@@ -2974,6 +3160,7 @@ async function runCreate() {
2974
3160
  };
2975
3161
  }
2976
3162
  await core.saveLocalConfig(newConfig);
3163
+ const publicKeyResult = await core.savePublicKey(slug, keyPair.publicKey);
2977
3164
  log("");
2978
3165
  success("Identity created successfully");
2979
3166
  log("");
@@ -2988,6 +3175,12 @@ async function runCreate() {
2988
3175
  log(` Public Key: ${keyPair.publicKey.slice(0, 32)}...`);
2989
3176
  log(` Private Key: ${keyStorageDescription}`);
2990
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("");
2991
3184
  if (!existingConfig) {
2992
3185
  success(`Set as active identity`);
2993
3186
  log("");