use anyhow::{Result, bail};
use bc_components::{PrivateKeys, URI, XID};
use bc_envelope::{Envelope, PrivateKeyBase, PublicKeys};
use bc_ur::prelude::*;
use bc_xid::{
HasNickname, HasPermissions, Key, XIDDocument, XIDPrivateKeyOptions,
XIDSigningOptions, XIDVerifySignature,
};
use super::{OutputOptions, ReadPasswordArgs, WritePasswordArgs, XIDPrivilege};
use crate::EnvelopeArgsLike;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum InputKey {
Public(PublicKeys),
PrivateBase(PrivateKeyBase),
PrivateKeys(PrivateKeys),
PrivateAndPublicKeys(PrivateKeys, PublicKeys),
}
pub fn read_key(key: Option<&str>) -> Result<InputKey> {
let key_string = if let Some(key_str) = key {
key_str.to_string()
} else {
let mut input = String::new();
std::io::stdin().read_line(&mut input)?;
input.trim().to_string()
};
if key_string.is_empty() {
bail!("No key provided");
}
let parts: Vec<&str> = key_string.split_whitespace().collect();
if parts.len() == 2 {
if let (Ok(key1), Ok(key2)) =
(parse_single_key(parts[0]), parse_single_key(parts[1]))
{
match (&key1, &key2) {
(InputKey::PrivateKeys(prv), InputKey::Public(pub_keys))
| (InputKey::Public(pub_keys), InputKey::PrivateKeys(prv)) => {
return Ok(InputKey::PrivateAndPublicKeys(
prv.clone(),
pub_keys.clone(),
));
}
_ => {
bail!(
"When providing two keys, one must be crypto-prvkeys and one must be crypto-pubkeys"
)
}
}
}
}
parse_single_key(&key_string)
}
fn parse_single_key(key_string: &str) -> Result<InputKey> {
if let Ok(public_keys) = PublicKeys::from_ur_string(key_string) {
Ok(InputKey::Public(public_keys))
} else if let Ok(private_key_base) =
PrivateKeyBase::from_ur_string(key_string)
{
Ok(InputKey::PrivateBase(private_key_base))
} else if let Ok(private_keys) = PrivateKeys::from_ur_string(key_string) {
Ok(InputKey::PrivateKeys(private_keys))
} else {
bail!("Invalid public keys, private key base, or private keys")
}
}
pub fn read_public_key(key: Option<&str>) -> Result<PublicKeys> {
let key = read_key(key)?;
match key {
InputKey::Public(public_keys) => Ok(public_keys),
_ => bail!("Expected a public key, but found a private key."),
}
}
pub fn update_key(
key: &mut Key,
nickname: &str,
endpoints: &[URI],
permissions: &[XIDPrivilege],
) {
if !nickname.is_empty() {
key.set_nickname(nickname);
}
if !endpoints.is_empty() {
for uri in endpoints {
key.add_endpoint(uri.clone());
}
}
if !permissions.is_empty() {
key.clear_all_permissions();
for privilege in permissions {
key.add_permission((*privilege).into());
}
}
}
pub trait XIDDocumentReadable: EnvelopeArgsLike {
fn read_xid_document(&self) -> Result<XIDDocument> {
self.read_xid_document_with_verify(XIDVerifySignature::None)
}
fn read_xid_document_with_verify(
&self,
verify: XIDVerifySignature,
) -> Result<XIDDocument> {
let envelope = self.read_envelope()?;
Ok(XIDDocument::from_envelope(&envelope, None, verify)?)
}
fn read_xid_document_with_password_and_verify(
&self,
password_args: &ReadPasswordArgs,
verify: XIDVerifySignature,
) -> Result<XIDDocument> {
let envelope = self.read_envelope()?;
let password = password_args.read_password("Decryption password:")?;
Ok(XIDDocument::from_envelope(
&envelope,
password.as_deref().map(|s| s.as_bytes()),
verify,
)?)
}
}
pub fn xid_document_envelope(envelope: &Envelope) -> Result<Envelope> {
if envelope.subject().is_wrapped() {
envelope.subject().try_unwrap().map_err(Into::into)
} else {
Ok(envelope.clone())
}
}
pub fn xid_from_document_envelope(envelope: &Envelope) -> Result<XID> {
let envelope = xid_document_envelope(envelope)?;
Ok(envelope.subject().try_leaf()?.try_into()?)
}
pub fn get_private_key_ur(
key: &Key,
password_args: &ReadPasswordArgs,
) -> Result<String> {
use bc_components::PrivateKeys;
let password = password_args.read_password("Decryption password:")?;
match key.private_key_envelope(password.as_deref())? {
None => bail!("No private key present in this key"),
Some(envelope) => {
match PrivateKeys::try_from(envelope.subject()) {
Ok(private_keys) => {
Ok(private_keys.ur_string())
}
Err(_) => {
Ok(envelope.ur_string())
}
}
}
}
}
pub fn read_uri(uri: Option<&URI>) -> Result<URI> {
let uri_string = if let Some(u) = uri {
u.to_string()
} else {
let mut input = String::new();
std::io::stdin().read_line(&mut input)?;
input
};
if uri_string.is_empty() {
bail!("No URI provided");
}
Ok(URI::new(uri_string.trim())?)
}
pub fn envelope_to_xid_ur_string(envelope: &Envelope) -> String {
UR::new("xid", envelope.to_cbor()).unwrap().string()
}
pub fn xid_document_to_ur_string(
xid_document: &XIDDocument,
output_opts: &OutputOptions,
password_args: Option<&WritePasswordArgs>,
shared_password: Option<String>,
signing_options: XIDSigningOptions,
) -> Result<String> {
use bc_xid::XIDGeneratorOptions;
let private_opts = output_opts.private_opts();
let generator_opts = output_opts.generator_opts();
let private_key_options = if private_opts.is_encrypt() {
let password_args = password_args.ok_or_else(|| {
anyhow::anyhow!("Password args required for encryption")
})?;
let password = if let Some(ref pwd) = shared_password {
pwd.clone()
} else {
password_args.read_password("Encryption password:")?
};
XIDPrivateKeyOptions::Encrypt {
method: password_args.method(),
password: password.into_bytes(),
}
} else {
XIDPrivateKeyOptions::from(private_opts)
};
let generator_options = if generator_opts.is_encrypt() {
let password_args = password_args.ok_or_else(|| {
anyhow::anyhow!("Password args required for encryption")
})?;
let password = if let Some(ref pwd) = shared_password {
pwd.clone()
} else {
password_args.read_password("Generator password:")?
};
XIDGeneratorOptions::Encrypt {
method: password_args.method(),
password: password.into_bytes(),
}
} else {
XIDGeneratorOptions::from(generator_opts)
};
if matches!(signing_options, XIDSigningOptions::Inception)
&& let Some(inception_key) = xid_document.inception_key()
&& inception_key.private_keys().is_none()
&& inception_key.has_encrypted_private_keys()
{
bail!(
"could not decrypt the inception key; check the decryption password"
);
}
let envelope = xid_document.to_envelope(
private_key_options,
generator_options,
signing_options,
)?;
Ok(envelope_to_xid_ur_string(&envelope))
}