From f193d7d6ccb8e1176723448d17dda91fc2a584ad Mon Sep 17 00:00:00 2001 From: Alexis Sellier Date: Mon, 26 Dec 2022 15:17:27 +0100 Subject: [PATCH] Improvements to keystore * Use the correct passphrase type everywhere * Allow conversion of secret key * Extract logic for storing a key in keystore Signed-off-by: Alexis Sellier --- radicle-cli/src/commands/auth.rs | 2 +- radicle-cli/tests/commands.rs | 2 +- radicle-crypto/src/lib.rs | 6 ++++++ radicle-crypto/src/ssh.rs | 2 ++ radicle-crypto/src/ssh/keystore.rs | 29 +++++++++++++++++++++-------- radicle-tools/src/rad-auth.rs | 2 +- radicle/src/profile.rs | 4 ++-- 7 files changed, 34 insertions(+), 13 deletions(-) diff --git a/radicle-cli/src/commands/auth.rs b/radicle-cli/src/commands/auth.rs index 4bb5de1a..710ee9aa 100644 --- a/radicle-cli/src/commands/auth.rs +++ b/radicle-cli/src/commands/auth.rs @@ -80,7 +80,7 @@ pub fn init(options: Options) -> anyhow::Result<()> { let home = profile::home()?; let passphrase = term::read_passphrase(options.stdin, true)?; let spinner = term::spinner("Creating your 🌱 Ed25519 keypair..."); - let profile = Profile::init(home, passphrase.as_str())?; + let profile = Profile::init(home, passphrase)?; spinner.finish(); term::success!( diff --git a/radicle-cli/tests/commands.rs b/radicle-cli/tests/commands.rs index 04dc5d0c..06124950 100644 --- a/radicle-cli/tests/commands.rs +++ b/radicle-cli/tests/commands.rs @@ -37,7 +37,7 @@ fn profile(home: &Path) -> Profile { // Set debug mode, to make test output more predictable. env::set_var("RAD_DEBUG", "1"); // Setup a new user. - Profile::init(home, "radicle").unwrap() + Profile::init(home, "radicle".to_owned()).unwrap() } #[test] diff --git a/radicle-crypto/src/lib.rs b/radicle-crypto/src/lib.rs index 085ff60a..5f3f31e7 100644 --- a/radicle-crypto/src/lib.rs +++ b/radicle-crypto/src/lib.rs @@ -220,6 +220,12 @@ impl From for SecretKey { } } +impl From for ed25519::SecretKey { + fn from(other: SecretKey) -> Self { + other.0 + } +} + impl Deref for SecretKey { type Target = ed25519::SecretKey; diff --git a/radicle-crypto/src/ssh.rs b/radicle-crypto/src/ssh.rs index f6dc63a2..4de281c5 100644 --- a/radicle-crypto/src/ssh.rs +++ b/radicle-crypto/src/ssh.rs @@ -13,6 +13,8 @@ use radicle_ssh::encoding::Reader; use crate as crypto; use crate::PublicKey; +pub use keystore::{Keystore, Passphrase}; + pub mod fmt { use radicle_ssh::encoding::Encoding as _; diff --git a/radicle-crypto/src/ssh/keystore.rs b/radicle-crypto/src/ssh/keystore.rs index aaf4cef6..ccede19e 100644 --- a/radicle-crypto/src/ssh/keystore.rs +++ b/radicle-crypto/src/ssh/keystore.rs @@ -5,7 +5,7 @@ use std::{fs, io}; use thiserror::Error; use zeroize::Zeroizing; -use crate::{keypair, PublicKey, SecretKey, SharedSecret, Signature, Signer, SignerError}; +use crate::{keypair, KeyPair, PublicKey, SecretKey, SharedSecret, Signature, Signer, SignerError}; /// A secret key passphrase. pub type Passphrase = Zeroizing; @@ -46,12 +46,25 @@ impl Keystore { /// /// The `comment` is associated with the private key. /// The `passphrase` is used to encrypt the private key. - pub fn init(&self, comment: &str, passphrase: &str) -> Result { - let pair = keypair::generate(); - let ssh_pair = ssh_key::private::Ed25519Keypair::from_bytes(&pair)?; + pub fn init( + &self, + comment: &str, + passphrase: impl Into, + ) -> Result { + self.store(keypair::generate(), comment, passphrase) + } + + /// Store a keypair on disk. Returns an error if the key already exists. + pub fn store( + &self, + keypair: KeyPair, + comment: &str, + passphrase: impl Into, + ) -> Result { + let ssh_pair = ssh_key::private::Ed25519Keypair::from_bytes(&keypair)?; let ssh_pair = ssh_key::private::KeypairData::Ed25519(ssh_pair); let secret = ssh_key::PrivateKey::new(ssh_pair, comment)?; - let secret = secret.encrypt(ssh_key::rand_core::OsRng, passphrase)?; + let secret = secret.encrypt(ssh_key::rand_core::OsRng, passphrase.into())?; let public = secret.public_key(); let path = self.path.join("radicle"); @@ -67,7 +80,7 @@ impl Keystore { secret.write_openssh_file(&path, ssh_key::LineEnding::default())?; public.write_openssh_file(&path.with_extension("pub"))?; - Ok(pair.pk.into()) + Ok(keypair.pk.into()) } /// Load the public key from the store. Returns `None` if it wasn't found. @@ -195,7 +208,7 @@ mod tests { let tmp = tempfile::tempdir().unwrap(); let store = Keystore::new(&tmp.path()); - let public = store.init("test", "hunter").unwrap(); + let public = store.init("test", "hunter".to_owned()).unwrap(); assert_eq!(public, store.public_key().unwrap().unwrap()); let secret = store @@ -212,7 +225,7 @@ mod tests { let tmp = tempfile::tempdir().unwrap(); let store = Keystore::new(&tmp.path()); - let public = store.init("test", "hunter").unwrap(); + let public = store.init("test", "hunter".to_owned()).unwrap(); let signer = MemorySigner::load(&store, "hunter".to_owned().into()).unwrap(); assert_eq!(public, *signer.public_key()); diff --git a/radicle-tools/src/rad-auth.rs b/radicle-tools/src/rad-auth.rs index 2de1c390..3c2e2eb3 100644 --- a/radicle-tools/src/rad-auth.rs +++ b/radicle-tools/src/rad-auth.rs @@ -4,7 +4,7 @@ use radicle::profile::{Error, Profile}; fn main() -> anyhow::Result<()> { let profile = match Profile::load() { Ok(profile) => profile, - Err(Error::NotFound(_)) => Profile::init(profile::home()?, "radicle")?, + Err(Error::NotFound(_)) => Profile::init(profile::home()?, "radicle".to_owned())?, Err(err) => anyhow::bail!(err), }; diff --git a/radicle/src/profile.rs b/radicle/src/profile.rs index ef775250..afa07abf 100644 --- a/radicle/src/profile.rs +++ b/radicle/src/profile.rs @@ -16,7 +16,7 @@ use std::path::{Path, PathBuf}; use thiserror::Error; use crate::crypto::ssh::agent::{Agent, AgentSigner}; -use crate::crypto::ssh::keystore::Keystore; +use crate::crypto::ssh::{Keystore, Passphrase}; use crate::crypto::PublicKey; use crate::node; use crate::storage::git::transport; @@ -57,7 +57,7 @@ pub struct Profile { } impl Profile { - pub fn init(home: impl AsRef, passphrase: &str) -> Result { + pub fn init(home: impl AsRef, passphrase: impl Into) -> Result { let home = home.as_ref().to_path_buf(); let storage = Storage::open(home.join("storage"))?; let keystore = Keystore::new(&home.join("keys"));