Skip to main content

wapm_cli/commands/
keys.rs

1//! Subcommand to deal with keys for signing wapm packages
2
3use crate::database;
4use crate::graphql::{self, DateTime};
5use crate::keys::*;
6use crate::util;
7
8use graphql_client::*;
9use prettytable::{format, Table};
10use rusqlite::Connection;
11use std::path::PathBuf;
12use structopt::StructOpt;
13
14#[derive(StructOpt, Debug)]
15pub enum KeyOpt {
16    #[structopt(name = "list")]
17    /// List keys registered with wapm
18    List(List),
19
20    #[structopt(name = "register")]
21    /// Register a personal key pair with wapm
22    Register(Register),
23
24    #[structopt(name = "import")]
25    /// Import a public key from somewhere
26    Import(Import),
27
28    #[structopt(name = "delete")]
29    /// Delete a keypair from wapm
30    Delete(Delete),
31
32    #[structopt(name = "generate")]
33    /// Generate a keypair for use with package signing
34    Generate(Generate),
35}
36
37/// Print the keys wapm knows about in a table
38#[derive(StructOpt, Debug)]
39pub struct List {
40    #[structopt(long = "all", short = "a")]
41    /// Show keys downloaded from WAPM too
42    all: bool,
43}
44
45/// Adds a key to wapm
46#[derive(StructOpt, Debug)]
47pub struct Register {
48    /// The location of the public key to add
49    #[structopt(long = "public")]
50    pub public_key_location: String,
51
52    /// The location of the private key to add
53    #[structopt(long = "private")]
54    pub private_key_location: String,
55}
56
57#[derive(GraphQLQuery)]
58#[graphql(
59    schema_path = "graphql/schema.graphql",
60    query_path = "graphql/queries/publish_public_key.graphql",
61    response_derives = "Debug"
62)]
63struct PublishPublicKeyMutation;
64
65/// Deletes a key to wapm
66#[derive(StructOpt, Debug)]
67pub struct Delete {
68    /// The identifier of the public key
69    public_key_id: String,
70}
71
72/// Generates a key pair for signing
73#[derive(StructOpt, Debug)]
74pub struct Generate {
75    /// Where the keys should be stored
76    key_path: PathBuf,
77
78    #[structopt(long = "force", short = "f")]
79    /// Overwrite keys if they exist
80    force: bool,
81}
82
83/// Import a public key from somewhere else
84#[derive(StructOpt, Debug)]
85pub struct Import {
86    #[structopt(long = "user-name")]
87    user_name: String,
88    public_key_value: String,
89}
90
91fn add_key_pair_from_fs_to_database(
92    key_db: &mut Connection,
93    public_key_location: String,
94    private_key_location: String,
95) -> anyhow::Result<()> {
96    let (pk_id, pk_v, tx) =
97        add_personal_key_pair_to_database(key_db, public_key_location, private_key_location)?;
98    let q = PublishPublicKeyMutation::build_query(publish_public_key_mutation::Variables {
99        key_id: pk_id,
100        key: pk_v,
101        verifying_signature_id: None,
102    });
103    let response_or_err: Result<publish_public_key_mutation::ResponseData, _> =
104        graphql::execute_query(&q);
105    match response_or_err {
106        Ok(_) => {
107            tx.commit().map_err(|e| {
108                anyhow!(
109                    "Failed to store key pair in local database: {}",
110                    e.to_string()
111                )
112            })?;
113            println!("Key pair successfully added!")
114        }
115        Err(e) => {
116            error!("Failed to upload public key to server: {}", e);
117            #[cfg(feature = "telemetry")]
118            sentry::integrations::anyhow::capture_anyhow(&e);
119        }
120    };
121    Ok(())
122}
123
124pub fn keys(options: KeyOpt) -> anyhow::Result<()> {
125    let mut key_db = database::open_db()?;
126    match options {
127        KeyOpt::List(List { all }) => {
128            // query server?
129            let keys = get_personal_keys_from_database(&key_db)?;
130            if all {
131                let wapm_public_keys = get_wapm_public_keys_from_database(&key_db)?;
132                match (keys.is_empty(), wapm_public_keys.is_empty()) {
133                    (true, true) => println!("No keys found"),
134                    (true, false) => {
135                        println!("{}", create_wapm_public_key_table(wapm_public_keys)?);
136                    }
137                    (false, true) => {
138                        println!("{}", create_personal_key_table(keys)?);
139                    }
140                    (false, false) => {
141                        println!("PERSONAL KEYS:\n{}", create_personal_key_table(keys)?);
142                        println!(
143                            "\nWAPM PUBLIC KEYS:\n{}",
144                            create_wapm_public_key_table(wapm_public_keys)?
145                        );
146                    }
147                }
148            } else if keys.is_empty() {
149                println!("No personal keys found");
150            } else {
151                println!("{}", create_personal_key_table(keys)?);
152            }
153        }
154        KeyOpt::Register(Register {
155            public_key_location,
156            private_key_location,
157        }) => {
158            add_key_pair_from_fs_to_database(
159                &mut key_db,
160                public_key_location,
161                private_key_location,
162            )?;
163        }
164        KeyOpt::Delete(Delete { public_key_id }) => {
165            let full_public_key = get_full_personal_public_key_by_id(&key_db, public_key_id)?;
166            warn!(
167                "You are about to delete the key pair associated with {:?} from wapm.\nThis cannot be undone.",
168                &full_public_key
169            );
170            let user_confirmed_key_deletion = util::prompt_user_for_yes(
171                "Please confirm that you want to permanently delete this key pair from wapm:",
172            )?;
173            if user_confirmed_key_deletion {
174                delete_key_pair(&mut key_db, full_public_key)?;
175            } else {
176                println!("Aborting");
177            }
178        }
179        KeyOpt::Import(Import {
180            user_name,
181            public_key_value,
182        }) => {
183            let user_name = user_name.trim().to_string();
184            let (pk_id, pkv) = normalize_public_key(public_key_value)?;
185            import_public_key(&mut key_db, &pk_id, &pkv, user_name)?;
186        }
187        KeyOpt::Generate(Generate { key_path, force }) => {
188            let private_key_path = key_path.join("minisign.key");
189            let public_key_path = key_path.join("minisign.pub");
190
191            if !key_path.exists() {
192                return Err(anyhow!(
193                    "Path {} does not exist!",
194                    &key_path.as_os_str().to_string_lossy()
195                ));
196            }
197            if !force {
198                if private_key_path.exists() {
199                    return Err(anyhow!(
200                        "Private key file, {}, exists",
201                        &private_key_path.as_os_str().to_string_lossy()
202                    ));
203                }
204
205                if public_key_path.exists() {
206                    return Err(anyhow!(
207                        "Public key file, {}, exists",
208                        &public_key_path.as_os_str().to_string_lossy()
209                    ));
210                }
211            }
212
213            let private_key_file = std::fs::File::create(&private_key_path)?;
214            let public_key_file = std::fs::File::create(&public_key_path)?;
215
216            info!("Generating key pair!");
217
218            let keypair = minisign::KeyPair::generate_and_write_encrypted_keypair(
219                public_key_file,
220                private_key_file,
221                None,
222                // None causes minisign to prompt for the password
223                None,
224            )?;
225
226            info!(
227                "Key pair successfully generated! Public key is: {}",
228                keypair.pk.to_base64()
229            );
230
231            debug!("Adding key pair to database");
232
233            add_key_pair_from_fs_to_database(
234                &mut key_db,
235                public_key_path.to_string_lossy().to_string(),
236                private_key_path.to_string_lossy().to_string(),
237            )?;
238        }
239    }
240
241    Ok(())
242}
243
244pub fn create_personal_key_table(keys: Vec<PersonalKey>) -> anyhow::Result<String> {
245    let mut table = Table::new();
246    table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
247    table.add_row(row![
248        "TAG",
249        "ACTIVE",
250        "KEY",
251        "PRIVATE KEY LOCATION",
252        "DATE ADDED"
253    ]);
254    for key in keys {
255        table.add_row(row![
256            key.public_key_id,
257            key.active,
258            key.public_key_value,
259            key.private_key_location
260                .unwrap_or_else(|| "None".to_string()),
261            time::strftime("%Y-%m-%d", &time::at(key.date_created))?
262        ]);
263    }
264    Ok(format!("{}", table))
265}
266
267pub fn create_wapm_public_key_table(keys: Vec<WapmPublicKey>) -> anyhow::Result<String> {
268    let mut table = Table::new();
269    table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
270    table.add_row(row!["USER", "TAG", "KEY", "DATE ADDED"]);
271    for key in keys {
272        table.add_row(row![
273            key.user_name,
274            key.public_key_id,
275            key.public_key_value,
276            time::strftime("%Y-%m-%d", &time::at(key.date_created))?
277        ]);
278    }
279    Ok(format!("{}", table))
280}