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 <self@cloudhead.io>
This commit is contained in:
Alexis Sellier 2022-12-26 15:17:27 +01:00
parent 2f1ea7f8da
commit f193d7d6cc
No known key found for this signature in database
7 changed files with 34 additions and 13 deletions

View File

@ -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!(

View File

@ -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]

View File

@ -220,6 +220,12 @@ impl From<ed25519::SecretKey> for SecretKey {
}
}
impl From<SecretKey> for ed25519::SecretKey {
fn from(other: SecretKey) -> Self {
other.0
}
}
impl Deref for SecretKey {
type Target = ed25519::SecretKey;

View File

@ -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 _;

View File

@ -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<String>;
@ -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<PublicKey, Error> {
let pair = keypair::generate();
let ssh_pair = ssh_key::private::Ed25519Keypair::from_bytes(&pair)?;
pub fn init(
&self,
comment: &str,
passphrase: impl Into<Passphrase>,
) -> Result<PublicKey, Error> {
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<Passphrase>,
) -> Result<PublicKey, Error> {
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());

View File

@ -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),
};

View File

@ -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<Path>, passphrase: &str) -> Result<Self, Error> {
pub fn init(home: impl AsRef<Path>, passphrase: impl Into<Passphrase>) -> Result<Self, Error> {
let home = home.as_ref().to_path_buf();
let storage = Storage::open(home.join("storage"))?;
let keystore = Keystore::new(&home.join("keys"));