1use crate::subcommands::prompt;
4use crate::{Result, TorClient};
5
6use anyhow::{Context, anyhow};
7use arti_client::{HsClientDescEncKey, HsId, InertTorClient, KeystoreSelector, TorClientConfig};
8use clap::{ArgMatches, Args, FromArgMatches, Parser, Subcommand, ValueEnum};
9use safelog::DisplayRedacted;
10use tor_rtcompat::Runtime;
11
12use std::fs::OpenOptions;
13use std::io::{self, Write};
14use std::str::FromStr;
15
16#[derive(Parser, Debug)]
18pub(crate) enum HscSubcommands {
19 #[command(subcommand)]
21 Hsc(HscSubcommand),
22}
23
24#[derive(Debug, Subcommand)]
25pub(crate) enum HscSubcommand {
26 #[command(arg_required_else_help = true)]
33 GetKey(GetKeyArgs),
34 #[command(subcommand)]
36 Key(KeySubcommand),
37}
38
39#[derive(Debug, Subcommand)]
40pub(crate) enum KeySubcommand {
41 #[command(arg_required_else_help = true)]
43 Get(GetKeyArgs),
44
45 #[command(arg_required_else_help = true)]
47 Rotate(RotateKeyArgs),
48
49 #[command(arg_required_else_help = true)]
51 Remove(RemoveKeyArgs),
52}
53
54#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, ValueEnum)]
56enum KeyType {
57 #[default]
60 ServiceDiscovery,
61}
62
63#[derive(Debug, Clone, Args)]
66pub(crate) struct GetKeyArgs {
67 #[command(flatten)]
69 common: CommonArgs,
70
71 #[command(flatten)]
73 keygen: KeygenArgs,
74
75 #[arg(
77 long,
78 default_value_t = GenerateKey::IfNeeded,
79 value_enum
80 )]
81 generate: GenerateKey,
82 }
84
85#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, ValueEnum)]
87enum GenerateKey {
88 No,
90 #[default]
92 IfNeeded,
93}
94
95#[derive(Debug, Clone, Args)]
97pub(crate) struct CommonArgs {
98 #[arg(
100 long,
101 default_value_t = KeyType::ServiceDiscovery,
102 value_enum
103 )]
104 key_type: KeyType,
105
106 #[arg(long, short, default_value_t = false)]
109 batch: bool,
110}
111
112#[derive(Debug, Clone, Args)]
114pub(crate) struct KeygenArgs {
115 #[arg(long, name = "FILE")]
117 output: String,
118
119 #[arg(long)]
121 overwrite: bool,
122}
123
124#[derive(Debug, Clone, Args)]
126pub(crate) struct RotateKeyArgs {
127 #[command(flatten)]
129 common: CommonArgs,
130
131 #[command(flatten)]
133 keygen: KeygenArgs,
134}
135
136#[derive(Debug, Clone, Args)]
138pub(crate) struct RemoveKeyArgs {
139 #[command(flatten)]
141 common: CommonArgs,
142}
143
144pub(crate) fn run<R: Runtime>(
146 runtime: R,
147 hsc_matches: &ArgMatches,
148 config: &TorClientConfig,
149) -> Result<()> {
150 use KeyType::*;
151
152 let subcommand =
153 HscSubcommand::from_arg_matches(hsc_matches).expect("Could not parse hsc subcommand");
154 let client = TorClient::with_runtime(runtime)
155 .config(config.clone())
156 .create_inert()?;
157
158 match subcommand {
159 HscSubcommand::GetKey(args) => {
160 eprintln!(
161 "warning: using deprecated command 'arti hsc key-get` (hint: use 'arti hsc key get' instead)"
162 );
163 match args.common.key_type {
164 ServiceDiscovery => prepare_service_discovery_key(&args, &client),
165 }
166 }
167 HscSubcommand::Key(subcommand) => run_key(subcommand, &client),
168 }
169}
170
171fn run_key(subcommand: KeySubcommand, client: &InertTorClient) -> Result<()> {
173 match subcommand {
174 KeySubcommand::Get(args) => prepare_service_discovery_key(&args, client),
175 KeySubcommand::Rotate(args) => rotate_service_discovery_key(&args, client),
176 KeySubcommand::Remove(args) => remove_service_discovery_key(&args, client),
177 }
178}
179
180fn prepare_service_discovery_key(args: &GetKeyArgs, client: &InertTorClient) -> Result<()> {
182 let addr = get_onion_address(&args.common)?;
183 let key = match args.generate {
184 GenerateKey::IfNeeded => {
185 client
187 .get_service_discovery_key(addr)?
188 .map(Ok)
189 .unwrap_or_else(|| {
190 client.generate_service_discovery_key(KeystoreSelector::Primary, addr)
191 })?
192 }
193 GenerateKey::No => match client.get_service_discovery_key(addr)? {
194 Some(key) => key,
195 None => {
196 return Err(anyhow!(
197 "Service discovery key not found. Rerun with --generate=if-needed to generate a new service discovery keypair"
198 ));
199 }
200 },
201 };
202
203 display_service_discovery_key(&args.keygen, &key)
204}
205
206fn display_service_discovery_key(args: &KeygenArgs, key: &HsClientDescEncKey) -> Result<()> {
212 match args.output.as_str() {
214 "-" => write_public_key(io::stdout(), key)?,
215 filename => {
216 let res = OpenOptions::new()
217 .create(true)
218 .create_new(!args.overwrite)
219 .write(true)
220 .truncate(true)
221 .open(filename)
222 .and_then(|f| write_public_key(f, key));
223
224 if let Err(e) = res {
225 match e.kind() {
226 io::ErrorKind::AlreadyExists => {
227 return Err(anyhow!(
228 "{filename} already exists. Move it, or rerun with --overwrite to overwrite it"
229 ));
230 }
231 _ => {
232 return Err(e)
233 .with_context(|| format!("could not write public key to {filename}"));
234 }
235 }
236 }
237 }
238 }
239
240 Ok(())
241}
242
243fn write_public_key(mut f: impl io::Write, key: &HsClientDescEncKey) -> io::Result<()> {
245 writeln!(f, "{}", key)?;
246 Ok(())
247}
248
249fn rotate_service_discovery_key(args: &RotateKeyArgs, client: &InertTorClient) -> Result<()> {
251 let addr = get_onion_address(&args.common)?;
252 let msg = format!(
253 "rotate client restricted discovery key for {}?",
254 addr.display_unredacted()
255 );
256 if !args.common.batch && !prompt(&msg)? {
257 return Ok(());
258 }
259
260 let key = client.rotate_service_discovery_key(KeystoreSelector::default(), addr)?;
261
262 display_service_discovery_key(&args.keygen, &key)
263}
264
265fn remove_service_discovery_key(args: &RemoveKeyArgs, client: &InertTorClient) -> Result<()> {
267 let addr = get_onion_address(&args.common)?;
268 let msg = format!(
269 "remove client restricted discovery key for {}?",
270 addr.display_unredacted()
271 );
272 if !args.common.batch && !prompt(&msg)? {
273 return Ok(());
274 }
275
276 let _key = client.remove_service_discovery_key(KeystoreSelector::default(), addr)?;
277
278 Ok(())
279}
280
281fn get_onion_address(args: &CommonArgs) -> Result<HsId, anyhow::Error> {
283 let mut addr = String::new();
284 if !args.batch {
285 print!("Enter an onion address: ");
286 io::stdout().flush().map_err(|e| anyhow!(e))?;
287 };
288 io::stdin().read_line(&mut addr).map_err(|e| anyhow!(e))?;
289
290 HsId::from_str(addr.trim_end()).map_err(|e| anyhow!(e))
291}