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 }