cli: Make key encryption optional
By leaving the passphrase prompt blank, users can create unencrypted keys.
This commit is contained in:
parent
e593191ae2
commit
f92308cf25
|
|
@ -1,6 +1,7 @@
|
||||||
#![allow(clippy::or_fun_call)]
|
#![allow(clippy::or_fun_call)]
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::ffi::OsString;
|
use std::ffi::OsString;
|
||||||
|
use std::ops::Not as _;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
|
|
@ -109,22 +110,25 @@ pub fn init(options: Options) -> anyhow::Result<()> {
|
||||||
} else {
|
} else {
|
||||||
term::passphrase_confirm("Enter a passphrase:", RAD_PASSPHRASE)
|
term::passphrase_confirm("Enter a passphrase:", RAD_PASSPHRASE)
|
||||||
}?;
|
}?;
|
||||||
|
let passphrase = passphrase.trim().is_empty().not().then_some(passphrase);
|
||||||
let spinner = term::spinner("Creating your Ed25519 keypair...");
|
let spinner = term::spinner("Creating your Ed25519 keypair...");
|
||||||
let profile = Profile::init(home, alias, passphrase.clone())?;
|
let profile = Profile::init(home, alias, passphrase.clone())?;
|
||||||
spinner.finish();
|
spinner.finish();
|
||||||
|
|
||||||
match ssh::agent::Agent::connect() {
|
if let Some(passphrase) = passphrase {
|
||||||
Ok(mut agent) => {
|
match ssh::agent::Agent::connect() {
|
||||||
let mut spinner = term::spinner("Adding your radicle key to ssh-agent...");
|
Ok(mut agent) => {
|
||||||
if register(&mut agent, &profile, passphrase).is_ok() {
|
let mut spinner = term::spinner("Adding your radicle key to ssh-agent...");
|
||||||
spinner.finish();
|
if register(&mut agent, &profile, passphrase).is_ok() {
|
||||||
} else {
|
spinner.finish();
|
||||||
spinner.message("Could not register radicle key in ssh-agent.");
|
} else {
|
||||||
spinner.warn();
|
spinner.message("Could not register radicle key in ssh-agent.");
|
||||||
|
spinner.warn();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Err(e) if e.is_not_running() => {}
|
||||||
|
Err(e) => Err(e)?,
|
||||||
}
|
}
|
||||||
Err(e) if e.is_not_running() => {}
|
|
||||||
Err(e) => Err(e)?,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
term::success!(
|
term::success!(
|
||||||
|
|
@ -144,7 +148,13 @@ pub fn init(options: Options) -> anyhow::Result<()> {
|
||||||
/// Try loading the identity's key into SSH Agent, falling back to verifying `RAD_PASSPHRASE` for
|
/// Try loading the identity's key into SSH Agent, falling back to verifying `RAD_PASSPHRASE` for
|
||||||
/// use.
|
/// use.
|
||||||
pub fn authenticate(options: Options, profile: &Profile) -> anyhow::Result<()> {
|
pub fn authenticate(options: Options, profile: &Profile) -> anyhow::Result<()> {
|
||||||
// Authenticate with SSH Agent only if it is running.
|
if !profile.keystore.is_encrypted()? {
|
||||||
|
term::success!("Authenticated as {}", term::format::tertiary(profile.id()));
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// If our key is encrypted, we try to authenticate with SSH Agent and
|
||||||
|
// register it; only if it is running.
|
||||||
match ssh::agent::Agent::connect() {
|
match ssh::agent::Agent::connect() {
|
||||||
Ok(mut agent) => {
|
Ok(mut agent) => {
|
||||||
if agent.request_identities()?.contains(&profile.public_key) {
|
if agent.request_identities()?.contains(&profile.public_key) {
|
||||||
|
|
@ -174,7 +184,7 @@ pub fn authenticate(options: Options, profile: &Profile) -> anyhow::Result<()> {
|
||||||
|
|
||||||
// Try RAD_PASSPHRASE fallback.
|
// Try RAD_PASSPHRASE fallback.
|
||||||
if let Some(passphrase) = profile::env::passphrase() {
|
if let Some(passphrase) = profile::env::passphrase() {
|
||||||
ssh::keystore::MemorySigner::load(&profile.keystore, passphrase)
|
ssh::keystore::MemorySigner::load(&profile.keystore, Some(passphrase))
|
||||||
.map_err(|_| anyhow!("RAD_PASSPHRASE failed"))?;
|
.map_err(|_| anyhow!("RAD_PASSPHRASE failed"))?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
|
@ -191,7 +201,7 @@ pub fn register(
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let secret = profile
|
let secret = profile
|
||||||
.keystore
|
.keystore
|
||||||
.secret_key(passphrase)
|
.secret_key(Some(passphrase))
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
if e.is_crypto_err() {
|
if e.is_crypto_err() {
|
||||||
anyhow!("could not decrypt secret key: invalid passphrase")
|
anyhow!("could not decrypt secret key: invalid passphrase")
|
||||||
|
|
|
||||||
|
|
@ -24,10 +24,16 @@ pub fn start(
|
||||||
term::success!("Node is already running");
|
term::success!("Node is already running");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
// Ask passphrase here, otherwise it'll be a fatal error when running the daemon
|
let envs = if profile.keystore.is_encrypted()? {
|
||||||
// without `RAD_PASSPHRASE`. To keep things consistent, we also use this in foreground mode.
|
// Ask passphrase here, otherwise it'll be a fatal error when running the daemon
|
||||||
let passphrase = term::io::passphrase(profile::env::RAD_PASSPHRASE)
|
// without `RAD_PASSPHRASE`. To keep things consistent, we also use this in foreground mode.
|
||||||
.context(format!("`{}` must be set", profile::env::RAD_PASSPHRASE))?;
|
let passphrase = term::io::passphrase(profile::env::RAD_PASSPHRASE)
|
||||||
|
.context(format!("`{}` must be set", profile::env::RAD_PASSPHRASE))?;
|
||||||
|
|
||||||
|
Some((profile::env::RAD_PASSPHRASE, passphrase))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
// Since we checked that the node is not running, it's safe to use `--force`
|
// Since we checked that the node is not running, it's safe to use `--force`
|
||||||
// here.
|
// here.
|
||||||
|
|
@ -42,7 +48,7 @@ pub fn start(
|
||||||
|
|
||||||
process::Command::new("radicle-node")
|
process::Command::new("radicle-node")
|
||||||
.args(options)
|
.args(options)
|
||||||
.env(profile::env::RAD_PASSPHRASE, passphrase)
|
.envs(envs)
|
||||||
.stdin(process::Stdio::null())
|
.stdin(process::Stdio::null())
|
||||||
.stdout(process::Stdio::from(log))
|
.stdout(process::Stdio::from(log))
|
||||||
.stderr(process::Stdio::null())
|
.stderr(process::Stdio::null())
|
||||||
|
|
@ -52,7 +58,7 @@ pub fn start(
|
||||||
} else {
|
} else {
|
||||||
let mut child = process::Command::new("radicle-node")
|
let mut child = process::Command::new("radicle-node")
|
||||||
.args(options)
|
.args(options)
|
||||||
.env(profile::env::RAD_PASSPHRASE, passphrase)
|
.envs(envs)
|
||||||
.spawn()?;
|
.spawn()?;
|
||||||
|
|
||||||
child.wait()?;
|
child.wait()?;
|
||||||
|
|
@ -139,6 +145,7 @@ pub fn status(node: &Node, profile: &Profile) -> anyhow::Result<()> {
|
||||||
term::success!("Node is {}", term::format::positive("running"));
|
term::success!("Node is {}", term::format::positive("running"));
|
||||||
} else {
|
} else {
|
||||||
term::info!("Node is {}", term::format::negative("stopped"));
|
term::info!("Node is {}", term::format::negative("stopped"));
|
||||||
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let sessions = sessions(node)?;
|
let sessions = sessions(node)?;
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ pub fn signer(profile: &Profile) -> anyhow::Result<Box<dyn Signer>> {
|
||||||
}
|
}
|
||||||
let passphrase = passphrase(RAD_PASSPHRASE)?;
|
let passphrase = passphrase(RAD_PASSPHRASE)?;
|
||||||
let spinner = spinner("Unsealing key...");
|
let spinner = spinner("Unsealing key...");
|
||||||
let signer = MemorySigner::load(&profile.keystore, passphrase)?;
|
let signer = MemorySigner::load(&profile.keystore, Some(passphrase))?;
|
||||||
|
|
||||||
spinner.finish();
|
spinner.finish();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ ssh = ["radicle-ssh", "ssh-key"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
amplify = { version = "4.0.0" }
|
amplify = { version = "4.0.0" }
|
||||||
cyphernet = { version = "0.2.0", optional = true }
|
cyphernet = { version = "0.2.0", optional = true, features = ["ed25519"] }
|
||||||
multibase = { version = "0.9.1" }
|
multibase = { version = "0.9.1" }
|
||||||
ec25519 = { version = "0.1.0", features = [] }
|
ec25519 = { version = "0.1.0", features = [] }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
|
|
||||||
|
|
@ -56,11 +56,9 @@ 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,
|
/// If `passphrase` is `None`, the key is not encrypted.
|
||||||
comment: &str,
|
pub fn init(&self, comment: &str, passphrase: Option<Passphrase>) -> Result<PublicKey, Error> {
|
||||||
passphrase: impl Into<Passphrase>,
|
|
||||||
) -> Result<PublicKey, Error> {
|
|
||||||
self.store(keypair::generate(), comment, passphrase)
|
self.store(keypair::generate(), comment, passphrase)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -69,12 +67,16 @@ impl Keystore {
|
||||||
&self,
|
&self,
|
||||||
keypair: KeyPair,
|
keypair: KeyPair,
|
||||||
comment: &str,
|
comment: &str,
|
||||||
passphrase: impl Into<Passphrase>,
|
passphrase: Option<Passphrase>,
|
||||||
) -> Result<PublicKey, Error> {
|
) -> Result<PublicKey, Error> {
|
||||||
let ssh_pair = ssh_key::private::Ed25519Keypair::from_bytes(&keypair)?;
|
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.into())?;
|
let secret = if let Some(p) = passphrase {
|
||||||
|
secret.encrypt(ssh_key::rand_core::OsRng, p)?
|
||||||
|
} else {
|
||||||
|
secret
|
||||||
|
};
|
||||||
let public = secret.public_key();
|
let public = secret.public_key();
|
||||||
let path = self.path.join("radicle");
|
let path = self.path.join("radicle");
|
||||||
|
|
||||||
|
|
@ -111,16 +113,19 @@ impl Keystore {
|
||||||
/// Returns `None` if it wasn't found.
|
/// Returns `None` if it wasn't found.
|
||||||
pub fn secret_key(
|
pub fn secret_key(
|
||||||
&self,
|
&self,
|
||||||
passphrase: Passphrase,
|
passphrase: Option<Passphrase>,
|
||||||
) -> Result<Option<Zeroizing<SecretKey>>, Error> {
|
) -> Result<Option<Zeroizing<SecretKey>>, Error> {
|
||||||
let path = self.path.join("radicle");
|
let path = self.path.join("radicle");
|
||||||
if !path.exists() {
|
if !path.exists() {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
let encrypted = ssh_key::PrivateKey::read_openssh_file(&path)?;
|
let secret = ssh_key::PrivateKey::read_openssh_file(&path)?;
|
||||||
let secret = encrypted.decrypt(passphrase)?;
|
let secret = if let Some(p) = passphrase {
|
||||||
|
secret.decrypt(p)?
|
||||||
|
} else {
|
||||||
|
secret
|
||||||
|
};
|
||||||
match secret.key_data() {
|
match secret.key_data() {
|
||||||
ssh_key::private::KeypairData::Ed25519(pair) => {
|
ssh_key::private::KeypairData::Ed25519(pair) => {
|
||||||
Ok(Some(SecretKey::from(pair.to_bytes()).into()))
|
Ok(Some(SecretKey::from(pair.to_bytes()).into()))
|
||||||
|
|
@ -128,6 +133,14 @@ impl Keystore {
|
||||||
_ => Err(Error::InvalidKeyType),
|
_ => Err(Error::InvalidKeyType),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check whether the secret key is encrypted.
|
||||||
|
pub fn is_encrypted(&self) -> Result<bool, Error> {
|
||||||
|
let path = self.path.join("radicle");
|
||||||
|
let secret = ssh_key::PrivateKey::read_openssh_file(&path)?;
|
||||||
|
|
||||||
|
Ok(secret.is_encrypted())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
|
|
@ -192,7 +205,10 @@ impl Ecdh for MemorySigner {
|
||||||
|
|
||||||
impl MemorySigner {
|
impl MemorySigner {
|
||||||
/// Load this signer from a keystore, given a secret key passphrase.
|
/// Load this signer from a keystore, given a secret key passphrase.
|
||||||
pub fn load(keystore: &Keystore, passphrase: Passphrase) -> Result<Self, MemorySignerError> {
|
pub fn load(
|
||||||
|
keystore: &Keystore,
|
||||||
|
passphrase: Option<Passphrase>,
|
||||||
|
) -> Result<Self, MemorySignerError> {
|
||||||
let public = keystore
|
let public = keystore
|
||||||
.public_key()?
|
.public_key()?
|
||||||
.ok_or_else(|| MemorySignerError::NotFound(keystore.path().to_path_buf()))?;
|
.ok_or_else(|| MemorySignerError::NotFound(keystore.path().to_path_buf()))?;
|
||||||
|
|
@ -238,20 +254,38 @@ mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_init() {
|
fn test_init_passphrase() {
|
||||||
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".to_owned()).unwrap();
|
let public = store
|
||||||
|
.init("test", Some("hunter".to_owned().into()))
|
||||||
|
.unwrap();
|
||||||
assert_eq!(public, store.public_key().unwrap().unwrap());
|
assert_eq!(public, store.public_key().unwrap().unwrap());
|
||||||
|
assert!(store.is_encrypted().unwrap());
|
||||||
|
|
||||||
let secret = store
|
let secret = store
|
||||||
.secret_key("hunter".to_owned().into())
|
.secret_key(Some("hunter".to_owned().into()))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(PublicKey::from(secret.public_key()), public);
|
assert_eq!(PublicKey::from(secret.public_key()), public);
|
||||||
|
|
||||||
store.secret_key("blunder".to_owned().into()).unwrap_err(); // Wrong passphrase.
|
store
|
||||||
|
.secret_key(Some("blunder".to_owned().into()))
|
||||||
|
.unwrap_err(); // Wrong passphrase.
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_init_no_passphrase() {
|
||||||
|
let tmp = tempfile::tempdir().unwrap();
|
||||||
|
let store = Keystore::new(&tmp.path());
|
||||||
|
|
||||||
|
let public = store.init("test", None).unwrap();
|
||||||
|
assert_eq!(public, store.public_key().unwrap().unwrap());
|
||||||
|
assert!(!store.is_encrypted().unwrap());
|
||||||
|
|
||||||
|
let secret = store.secret_key(None).unwrap().unwrap();
|
||||||
|
assert_eq!(PublicKey::from(secret.public_key()), public);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -259,8 +293,10 @@ 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".to_owned()).unwrap();
|
let public = store
|
||||||
let signer = MemorySigner::load(&store, "hunter".to_owned().into()).unwrap();
|
.init("test", Some("hunter".to_owned().into()))
|
||||||
|
.unwrap();
|
||||||
|
let signer = MemorySigner::load(&store, Some("hunter".to_owned().into())).unwrap();
|
||||||
|
|
||||||
assert_eq!(public, *signer.public_key());
|
assert_eq!(public, *signer.public_key());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -46,8 +46,6 @@ pub const CONTRIBUTOR_PATCH_ID: &str = "044b577cc7551cd09d4b2f03566a553762180de4
|
||||||
pub const CONTRIBUTOR_COMMENT_1: &str = "92aab76516ae7f60a9b2952043ba578383de7d46";
|
pub const CONTRIBUTOR_COMMENT_1: &str = "92aab76516ae7f60a9b2952043ba578383de7d46";
|
||||||
pub const CONTRIBUTOR_COMMENT_2: &str = "cb360eee0ec70563d5c4c3613fdc076648523248";
|
pub const CONTRIBUTOR_COMMENT_2: &str = "cb360eee0ec70563d5c4c3613fdc076648523248";
|
||||||
|
|
||||||
const PASSWORD: &str = "radicle";
|
|
||||||
|
|
||||||
/// Create a new profile.
|
/// Create a new profile.
|
||||||
pub fn profile(home: &Path, seed: [u8; 32]) -> radicle::Profile {
|
pub fn profile(home: &Path, seed: [u8; 32]) -> radicle::Profile {
|
||||||
let home = Home::new(home).unwrap();
|
let home = Home::new(home).unwrap();
|
||||||
|
|
@ -56,9 +54,7 @@ pub fn profile(home: &Path, seed: [u8; 32]) -> radicle::Profile {
|
||||||
let keypair = KeyPair::from_seed(Seed::from(seed));
|
let keypair = KeyPair::from_seed(Seed::from(seed));
|
||||||
|
|
||||||
radicle::storage::git::transport::local::register(storage.clone());
|
radicle::storage::git::transport::local::register(storage.clone());
|
||||||
keystore
|
keystore.store(keypair.clone(), "radicle", None).unwrap();
|
||||||
.store(keypair.clone(), "radicle", PASSWORD.to_owned())
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
radicle::Profile {
|
radicle::Profile {
|
||||||
home,
|
home,
|
||||||
|
|
@ -85,7 +81,7 @@ pub fn contributor(dir: &Path) -> Context {
|
||||||
|
|
||||||
let home = dir.join("radicle");
|
let home = dir.join("radicle");
|
||||||
let profile = profile(home.as_path(), seed);
|
let profile = profile(home.as_path(), seed);
|
||||||
let signer = MemorySigner::load(&profile.keystore, PASSWORD.to_owned().into()).unwrap();
|
let signer = MemorySigner::load(&profile.keystore, None).unwrap();
|
||||||
|
|
||||||
seed_with_signer(dir, profile, &signer)
|
seed_with_signer(dir, profile, &signer)
|
||||||
}
|
}
|
||||||
|
|
@ -102,7 +98,6 @@ fn seed_with_signer<G: Signer>(dir: &Path, profile: radicle::Profile, signer: &G
|
||||||
let workdir = dir.join("hello-world");
|
let workdir = dir.join("hello-world");
|
||||||
|
|
||||||
env::set_var("RAD_COMMIT_TIME", TIMESTAMP.to_string());
|
env::set_var("RAD_COMMIT_TIME", TIMESTAMP.to_string());
|
||||||
env::set_var("RAD_PASSPHRASE", PASSWORD);
|
|
||||||
|
|
||||||
fs::create_dir_all(&workdir).unwrap();
|
fs::create_dir_all(&workdir).unwrap();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
use std::{env, fs, net, process};
|
use std::{env, fs, net, process};
|
||||||
|
|
||||||
use anyhow::{anyhow, Context as _};
|
use anyhow::anyhow;
|
||||||
use crossbeam_channel as chan;
|
use crossbeam_channel as chan;
|
||||||
use cyphernet::addr::PeerAddr;
|
use cyphernet::addr::PeerAddr;
|
||||||
use localtime::LocalDuration;
|
use localtime::LocalDuration;
|
||||||
|
|
||||||
use radicle::crypto;
|
|
||||||
use radicle::node;
|
use radicle::node;
|
||||||
use radicle::prelude::Signer;
|
use radicle::prelude::Signer;
|
||||||
use radicle::profile;
|
use radicle::profile;
|
||||||
|
|
@ -126,9 +125,7 @@ fn execute() -> anyhow::Result<()> {
|
||||||
|
|
||||||
log::info!(target: "node", "Unlocking node keystore..");
|
log::info!(target: "node", "Unlocking node keystore..");
|
||||||
|
|
||||||
let passphrase = env::var(profile::env::RAD_PASSPHRASE)
|
let passphrase = profile::env::passphrase();
|
||||||
.map(crypto::ssh::Passphrase::from)
|
|
||||||
.context(format!("`{}` must be set", profile::env::RAD_PASSPHRASE))?;
|
|
||||||
let keystore = Keystore::new(&home.keys());
|
let keystore = Keystore::new(&home.keys());
|
||||||
let signer = MemorySigner::load(&keystore, passphrase)?;
|
let signer = MemorySigner::load(&keystore, passphrase)?;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,7 @@ impl Environment {
|
||||||
/// is required. Use [`Environment::profile`] otherwise.
|
/// is required. Use [`Environment::profile`] otherwise.
|
||||||
pub fn node(&mut self, config: Config) -> Node<MemorySigner> {
|
pub fn node(&mut self, config: Config) -> Node<MemorySigner> {
|
||||||
let profile = self.profile(&config.alias);
|
let profile = self.profile(&config.alias);
|
||||||
let signer = MemorySigner::load(&profile.keystore, "radicle".to_owned().into()).unwrap();
|
let signer = MemorySigner::load(&profile.keystore, None).unwrap();
|
||||||
let tracking_db = profile.home.node().join(TRACKING_DB_FILE);
|
let tracking_db = profile.home.node().join(TRACKING_DB_FILE);
|
||||||
TrackingStore::Config::open(tracking_db).unwrap();
|
TrackingStore::Config::open(tracking_db).unwrap();
|
||||||
let addresses_db = profile.home.node().join(ADDRESS_DB_FILE);
|
let addresses_db = profile.home.node().join(ADDRESS_DB_FILE);
|
||||||
|
|
@ -109,9 +109,7 @@ impl Environment {
|
||||||
Book::open(addresses_db).unwrap();
|
Book::open(addresses_db).unwrap();
|
||||||
|
|
||||||
transport::local::register(storage.clone());
|
transport::local::register(storage.clone());
|
||||||
keystore
|
keystore.store(keypair.clone(), "radicle", None).unwrap();
|
||||||
.store(keypair.clone(), "radicle", "radicle".to_owned())
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Ensures that each user has a unique but deterministic public key.
|
// Ensures that each user has a unique but deterministic public key.
|
||||||
self.users += 1;
|
self.users += 1;
|
||||||
|
|
|
||||||
|
|
@ -216,7 +216,7 @@ pub fn passphrase_confirm<K: AsRef<OsStr>>(
|
||||||
.with_display_mode(inquire::PasswordDisplayMode::Masked)
|
.with_display_mode(inquire::PasswordDisplayMode::Masked)
|
||||||
.with_custom_confirmation_message("Repeat passphrase:")
|
.with_custom_confirmation_message("Repeat passphrase:")
|
||||||
.with_custom_confirmation_error_message("The passphrases don't match.")
|
.with_custom_confirmation_error_message("The passphrases don't match.")
|
||||||
.with_help_message("This passphrase protects your radicle identity")
|
.with_help_message("Leave this blank to keep your radicle key unencrypted")
|
||||||
.prompt()?,
|
.prompt()?,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ fn main() -> anyhow::Result<()> {
|
||||||
let passphrase = passphrase.trim().to_owned().into();
|
let passphrase = passphrase.trim().to_owned().into();
|
||||||
let secret = profile
|
let secret = profile
|
||||||
.keystore
|
.keystore
|
||||||
.secret_key(passphrase)?
|
.secret_key(Some(passphrase))?
|
||||||
.ok_or_else(|| anyhow!("Key not found in {:?}", profile.keystore.path()))?;
|
.ok_or_else(|| anyhow!("Key not found in {:?}", profile.keystore.path()))?;
|
||||||
|
|
||||||
agent.register(&secret)?;
|
agent.register(&secret)?;
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ pub mod env {
|
||||||
/// Passphrase for the encrypted radicle secret key.
|
/// Passphrase for the encrypted radicle secret key.
|
||||||
pub const RAD_PASSPHRASE: &str = "RAD_PASSPHRASE";
|
pub const RAD_PASSPHRASE: &str = "RAD_PASSPHRASE";
|
||||||
|
|
||||||
|
/// Get the radicle passphrase from the environment.
|
||||||
pub fn passphrase() -> Option<super::Passphrase> {
|
pub fn passphrase() -> Option<super::Passphrase> {
|
||||||
let Ok(passphrase) = std::env::var(RAD_PASSPHRASE) else {
|
let Ok(passphrase) = std::env::var(RAD_PASSPHRASE) else {
|
||||||
return None;
|
return None;
|
||||||
|
|
@ -139,11 +140,7 @@ pub struct Profile {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Profile {
|
impl Profile {
|
||||||
pub fn init(
|
pub fn init(home: Home, alias: Alias, passphrase: Option<Passphrase>) -> Result<Self, Error> {
|
||||||
home: Home,
|
|
||||||
alias: Alias,
|
|
||||||
passphrase: impl Into<Passphrase>,
|
|
||||||
) -> Result<Self, Error> {
|
|
||||||
let storage = Storage::open(home.storage())?;
|
let storage = Storage::open(home.storage())?;
|
||||||
let keystore = Keystore::new(&home.keys());
|
let keystore = Keystore::new(&home.keys());
|
||||||
let public_key = keystore.init("radicle", passphrase)?;
|
let public_key = keystore.init("radicle", passphrase)?;
|
||||||
|
|
@ -189,8 +186,13 @@ impl Profile {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn signer(&self) -> Result<Box<dyn Signer>, Error> {
|
pub fn signer(&self) -> Result<Box<dyn Signer>, Error> {
|
||||||
|
if !self.keystore.is_encrypted()? {
|
||||||
|
let signer = keystore::MemorySigner::load(&self.keystore, None)?;
|
||||||
|
return Ok(signer.boxed());
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(passphrase) = env::passphrase() {
|
if let Some(passphrase) = env::passphrase() {
|
||||||
let signer = keystore::MemorySigner::load(&self.keystore, passphrase)?;
|
let signer = keystore::MemorySigner::load(&self.keystore, Some(passphrase))?;
|
||||||
return Ok(signer.boxed());
|
return Ok(signer.boxed());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue