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:
parent
2f1ea7f8da
commit
f193d7d6cc
|
|
@ -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!(
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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 _;
|
||||
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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"));
|
||||
|
|
|
|||
Loading…
Reference in New Issue