key.rs
  1  use std::os::unix::fs::OpenOptionsExt;
  2  
  3  use tokio::io::AsyncWriteExt;
  4  
  5  use crate::config::Config;
  6  use crate::util::FileOrStdout;
  7  
  8  #[derive(Debug, clap::Subcommand)]
  9  pub enum KeyCommand {
 10      /// Generate a new private key
 11      Generate {},
 12  
 13      /// Generate a public key from a private key
 14      Public {
 15          #[clap(long = "output", short = 'o')]
 16          output_path: crate::util::FileOrStdout,
 17      },
 18  }
 19  
 20  impl KeyCommand {
 21      pub async fn run(self, config: &Config) -> Result<(), KeyCommandError> {
 22          match self {
 23              KeyCommand::Generate {} => {
 24                  let secret_key_path = config.secret_key_path()?;
 25                  if secret_key_path.exists() {
 26                      tracing::error!(path = ?secret_key_path, "Path exists");
 27                      return Err(KeyCommandError::PathExists(secret_key_path.to_path_buf()));
 28                  }
 29  
 30                  // Create the private key file with permissions so that only the current user
 31                  // can read it
 32                  let mut private_key_file = std::fs::OpenOptions::new()
 33                      .mode(0o600)
 34                      .create(true)
 35                      .truncate(true)
 36                      .create_new(true)
 37                      .write(true)
 38                      .open(&secret_key_path)
 39                      .map(tokio::fs::File::from_std)
 40                      .map_err(|source| KeyCommandError::CreatingPrivateKeyFile {
 41                          path: secret_key_path.clone(),
 42                          source,
 43                      })?;
 44  
 45                  let mut rng = rand::rng();
 46                  let private_key = iroh::SecretKey::generate(&mut rng);
 47                  let private_key_bytes = private_key.to_bytes();
 48  
 49                  private_key_file
 50                      .write_all(&private_key_bytes)
 51                      .await
 52                      .map_err(|source| KeyCommandError::WritingKey {
 53                          path: secret_key_path.clone().to_string(),
 54                          source,
 55                      })?;
 56  
 57                  private_key_file
 58                      .flush()
 59                      .await
 60                      .map_err(|source| KeyCommandError::WritingKey {
 61                          path: secret_key_path.clone().to_string(),
 62                          source,
 63                      })?;
 64  
 65                  tracing::info!(path = %secret_key_path, "Written private key file");
 66                  Ok(())
 67              }
 68  
 69              KeyCommand::Public { output_path } => {
 70                  let from = config.secret_key_path()?;
 71  
 72                  if !from.exists() {
 73                      tracing::error!(path = ?from, "Path does not exist");
 74                      return Err(KeyCommandError::FileMissing(from));
 75                  }
 76  
 77                  let mut buf = [0; 32];
 78                  {
 79                      let bytes = tokio::fs::read(&from).await.map_err(|source| {
 80                          KeyCommandError::ReadingPrivateKeyFile { path: from, source }
 81                      })?;
 82                      if bytes.len() < 32 {
 83                          todo!()
 84                      }
 85                      buf.copy_from_slice(&bytes);
 86                  }
 87                  let public_key = iroh::SecretKey::from_bytes(&buf).public();
 88  
 89                  if output_path.is_stdout() {
 90                      println!("{}", public_key);
 91                  } else {
 92                      let path = camino::Utf8PathBuf::from(output_path.filename().to_string());
 93  
 94                      let mut output = std::fs::OpenOptions::new()
 95                          .create(true)
 96                          .create_new(true)
 97                          .truncate(true)
 98                          .write(true)
 99                          .open(&path)
100                          .map(tokio::fs::File::from_std)
101                          .map_err(|source| KeyCommandError::CreatingPublicKeyFile {
102                              path: output_path.clone(),
103                              source,
104                          })?;
105  
106                      output
107                          // Use Display impl so we get the base32 encoding of the public key
108                          .write_all(public_key.to_string().as_bytes())
109                          .await
110                          .map_err(|source| KeyCommandError::WritingKey {
111                              path: output_path.filename().to_string(),
112                              source,
113                          })?;
114                      output
115                          .flush()
116                          .await
117                          .map_err(|source| KeyCommandError::WritingKey {
118                              path: output_path.filename().to_string(),
119                              source,
120                          })?;
121                  }
122  
123                  tracing::info!(output = %output_path.filename(), "Written public key file");
124                  Ok(())
125              }
126          }
127      }
128  }
129  
130  #[derive(Debug, thiserror::Error)]
131  pub enum KeyCommandError {
132      #[error(transparent)]
133      Config(#[from] crate::config::ConfigError),
134  
135      #[error("Path exists: {}", .0)]
136      PathExists(camino::Utf8PathBuf),
137  
138      #[error("Path does not exist: {}", .0)]
139      FileMissing(camino::Utf8PathBuf),
140  
141      #[error("Failed to read private key file at: {}", .path)]
142      ReadingPrivateKeyFile {
143          path: camino::Utf8PathBuf,
144          #[source]
145          source: std::io::Error,
146      },
147  
148      #[error("Failed to create private key file at: {}", .path)]
149      CreatingPrivateKeyFile {
150          path: camino::Utf8PathBuf,
151          #[source]
152          source: std::io::Error,
153      },
154  
155      #[error("Failed to create public key file at: {}", .path.filename())]
156      CreatingPublicKeyFile {
157          path: FileOrStdout,
158          #[source]
159          source: std::io::Error,
160      },
161  
162      #[error("Writing key file: {}", .path)]
163      WritingKey {
164          path: String,
165          #[source]
166          source: std::io::Error,
167      },
168  }