use anyhow::{Result, bail};
use bc_components::{
PrivateKeyBase, PrivateKeys, Signer, SigningOptions, SigningPrivateKey,
};
use bc_envelope::prelude::*;
use clap::Args;
use known_values::NOTE;
use super::generate::HashType;
use crate::{EnvelopeArgs, EnvelopeArgsLike};
#[derive(Debug, Args)]
#[group(skip)]
pub struct CommandArgs {
#[arg(long, short)]
signer: Vec<String>,
#[arg(long)]
note: Option<String>,
#[arg(long, default_value = "envelope")]
namespace: String,
#[arg(long, default_value = "sha256")]
hash_type: HashType,
#[command(flatten)]
envelope_args: EnvelopeArgs,
}
impl EnvelopeArgsLike for CommandArgs {
fn envelope(&self) -> Option<&str> { self.envelope_args.envelope() }
}
impl crate::Exec for CommandArgs {
fn exec(&self) -> Result<String> {
if self.signer.is_empty() {
if let Some(envelope_arg) = self.envelope()
&& looks_like_signer(envelope_arg)
{
bail!(
"signer keys must be passed with --signer/-s; did you mean: envelope sign --signer <key> [ENVELOPE]?"
);
}
bail!("at least one signer must be provided");
}
let envelope = self.read_envelope()?;
let mut private_key_bases: Vec<PrivateKeyBase> = Vec::new();
let mut private_keys: Vec<PrivateKeys> = Vec::new();
let mut signing_private_keys: Vec<SigningPrivateKey> = Vec::new();
let mut signing_options: Vec<Option<SigningOptions>> = Vec::new();
for s in &self.signer {
if let Ok(key) = PrivateKeyBase::from_ur_string(s) {
private_key_bases.push(key);
} else if let Ok(key) = PrivateKeys::from_ur_string(s) {
private_keys.push(key);
} else if let Ok(key) = SigningPrivateKey::from_ur_string(s) {
if key.is_ssh() {
let namespace = self.namespace.clone();
let hash_alg = self.hash_type.to_ssh_hash_alg();
signing_options.push(Some(SigningOptions::Ssh {
namespace,
hash_alg,
}));
} else {
signing_options.push(None);
}
signing_private_keys.push(key);
} else {
bail!("invalid signer: {}", s);
}
}
let mut signers: Vec<(
&dyn Signer,
Option<SigningOptions>,
Option<SignatureMetadata>,
)> = Vec::new();
for key in private_key_bases.iter() {
signers.push((key as &dyn Signer, None, None));
}
for key in private_keys.iter() {
let options = if key.signing_private_key().is_ssh() {
let namespace = self.namespace.clone();
let hash_alg = self.hash_type.to_ssh_hash_alg();
Some(SigningOptions::Ssh { namespace, hash_alg })
} else {
None
};
signers.push((key as &dyn Signer, options, None));
}
for i in 0..signing_private_keys.len() {
signers.push((
&signing_private_keys[i] as &dyn Signer,
signing_options[i].clone(),
None,
));
}
if let Some(note) = &self.note {
if signers.len() != 1 {
bail!("can only add a note on a single signature");
}
let metadata =
SignatureMetadata::new().with_assertion(NOTE, note.clone());
Ok(envelope
.add_signature_opt(
signers[0].0,
signers[0].1.clone(),
Some(metadata),
)
.ur_string())
} else {
Ok(envelope.add_signatures_opt(&signers).ur_string())
}
}
}
fn looks_like_signer(value: &str) -> bool {
PrivateKeyBase::from_ur_string(value).is_ok()
|| PrivateKeys::from_ur_string(value).is_ok()
|| SigningPrivateKey::from_ur_string(value).is_ok()
}