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 home = profile::home()?;
|
||||||
let passphrase = term::read_passphrase(options.stdin, true)?;
|
let passphrase = term::read_passphrase(options.stdin, true)?;
|
||||||
let spinner = term::spinner("Creating your 🌱 Ed25519 keypair...");
|
let spinner = term::spinner("Creating your 🌱 Ed25519 keypair...");
|
||||||
let profile = Profile::init(home, passphrase.as_str())?;
|
let profile = Profile::init(home, passphrase)?;
|
||||||
spinner.finish();
|
spinner.finish();
|
||||||
|
|
||||||
term::success!(
|
term::success!(
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ fn profile(home: &Path) -> Profile {
|
||||||
// Set debug mode, to make test output more predictable.
|
// Set debug mode, to make test output more predictable.
|
||||||
env::set_var("RAD_DEBUG", "1");
|
env::set_var("RAD_DEBUG", "1");
|
||||||
// Setup a new user.
|
// Setup a new user.
|
||||||
Profile::init(home, "radicle").unwrap()
|
Profile::init(home, "radicle".to_owned()).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[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 {
|
impl Deref for SecretKey {
|
||||||
type Target = ed25519::SecretKey;
|
type Target = ed25519::SecretKey;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,8 @@ use radicle_ssh::encoding::Reader;
|
||||||
use crate as crypto;
|
use crate as crypto;
|
||||||
use crate::PublicKey;
|
use crate::PublicKey;
|
||||||
|
|
||||||
|
pub use keystore::{Keystore, Passphrase};
|
||||||
|
|
||||||
pub mod fmt {
|
pub mod fmt {
|
||||||
use radicle_ssh::encoding::Encoding as _;
|
use radicle_ssh::encoding::Encoding as _;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use std::{fs, io};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use zeroize::Zeroizing;
|
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.
|
/// A secret key passphrase.
|
||||||
pub type Passphrase = Zeroizing<String>;
|
pub type Passphrase = Zeroizing<String>;
|
||||||
|
|
@ -46,12 +46,25 @@ impl Keystore {
|
||||||
///
|
///
|
||||||
/// The `comment` is associated with the private key.
|
/// The `comment` is associated with the private key.
|
||||||
/// The `passphrase` is used to encrypt the private key.
|
/// The `passphrase` is used to encrypt the private key.
|
||||||
pub fn init(&self, comment: &str, passphrase: &str) -> Result<PublicKey, Error> {
|
pub fn init(
|
||||||
let pair = keypair::generate();
|
&self,
|
||||||
let ssh_pair = ssh_key::private::Ed25519Keypair::from_bytes(&pair)?;
|
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 ssh_pair = ssh_key::private::KeypairData::Ed25519(ssh_pair);
|
||||||
let secret = ssh_key::PrivateKey::new(ssh_pair, comment)?;
|
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 public = secret.public_key();
|
||||||
let path = self.path.join("radicle");
|
let path = self.path.join("radicle");
|
||||||
|
|
||||||
|
|
@ -67,7 +80,7 @@ impl Keystore {
|
||||||
secret.write_openssh_file(&path, ssh_key::LineEnding::default())?;
|
secret.write_openssh_file(&path, ssh_key::LineEnding::default())?;
|
||||||
public.write_openssh_file(&path.with_extension("pub"))?;
|
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.
|
/// 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 tmp = tempfile::tempdir().unwrap();
|
||||||
let store = Keystore::new(&tmp.path());
|
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());
|
assert_eq!(public, store.public_key().unwrap().unwrap());
|
||||||
|
|
||||||
let secret = store
|
let secret = store
|
||||||
|
|
@ -212,7 +225,7 @@ mod tests {
|
||||||
let tmp = tempfile::tempdir().unwrap();
|
let tmp = tempfile::tempdir().unwrap();
|
||||||
let store = Keystore::new(&tmp.path());
|
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();
|
let signer = MemorySigner::load(&store, "hunter".to_owned().into()).unwrap();
|
||||||
|
|
||||||
assert_eq!(public, *signer.public_key());
|
assert_eq!(public, *signer.public_key());
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ use radicle::profile::{Error, Profile};
|
||||||
fn main() -> anyhow::Result<()> {
|
fn main() -> anyhow::Result<()> {
|
||||||
let profile = match Profile::load() {
|
let profile = match Profile::load() {
|
||||||
Ok(profile) => profile,
|
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),
|
Err(err) => anyhow::bail!(err),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ use std::path::{Path, PathBuf};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::crypto::ssh::agent::{Agent, AgentSigner};
|
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::crypto::PublicKey;
|
||||||
use crate::node;
|
use crate::node;
|
||||||
use crate::storage::git::transport;
|
use crate::storage::git::transport;
|
||||||
|
|
@ -57,7 +57,7 @@ pub struct Profile {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl 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 home = home.as_ref().to_path_buf();
|
||||||
let storage = Storage::open(home.join("storage"))?;
|
let storage = Storage::open(home.join("storage"))?;
|
||||||
let keystore = Keystore::new(&home.join("keys"));
|
let keystore = Keystore::new(&home.join("keys"));
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue