use std::collections::HashSet;
use anyhow::Result;
use bc_components::{Digest, DigestProvider};
use bc_envelope::{
Envelope,
known_values::{KEY, PRIVATE_KEY, PROVENANCE, PROVENANCE_GENERATOR},
};
use bc_ur::prelude::*;
use bc_xid::{XIDDocument, XIDVerifySignature};
use clap::Args;
use crate::{
EnvelopeArgs, EnvelopeArgsLike,
xid::{
GeneratorOptions, OutputOptions, PrivateOptions, ReadWritePasswordArgs,
SigningArgs, SigningOption, VerifyArgs, XIDDocumentReadable,
xid_document_to_ur_string,
},
};
#[derive(Debug, Args)]
#[group(skip)]
pub struct CommandArgs {
#[command(flatten)]
output_opts: OutputOptions,
#[command(flatten)]
password_args: ReadWritePasswordArgs,
#[command(flatten)]
verify_args: VerifyArgs,
#[command(flatten)]
signing_args: SigningArgs,
#[command(flatten)]
envelope_args: EnvelopeArgs,
}
impl EnvelopeArgsLike for CommandArgs {
fn envelope(&self) -> Option<&str> { self.envelope_args.envelope() }
}
impl XIDDocumentReadable for CommandArgs {}
impl crate::Exec for CommandArgs {
fn exec(&self) -> Result<String> {
let private_opts = self.output_opts.private_opts();
let generator_opts = self.output_opts.generator_opts();
let can_use_envelope_elision = matches!(
(private_opts, generator_opts),
(PrivateOptions::Elide, GeneratorOptions::Elide)
| (PrivateOptions::Elide, GeneratorOptions::Include)
| (PrivateOptions::Include, GeneratorOptions::Elide)
| (PrivateOptions::Include, GeneratorOptions::Include)
);
if can_use_envelope_elision
&& self.signing_args.sign == SigningOption::None
&& self.signing_args.signing_key.is_none()
{
return self.elide_at_envelope_level(private_opts, generator_opts);
}
self.reconstruct_document()
}
}
impl CommandArgs {
fn elide_at_envelope_level(
&self,
private_opts: PrivateOptions,
generator_opts: GeneratorOptions,
) -> Result<String> {
let envelope = self.read_envelope()?;
if self.verify_args.verify_signature() == XIDVerifySignature::Inception
{
let _ = XIDDocument::from_envelope(
&envelope,
None,
XIDVerifySignature::Inception,
)?;
}
let mut digests_to_elide: HashSet<Digest> = HashSet::new();
let inner = if envelope.subject().is_wrapped() {
envelope.subject().try_unwrap()?
} else {
envelope.clone()
};
if private_opts == PrivateOptions::Elide {
collect_private_key_digests(&inner, &mut digests_to_elide)?;
}
if generator_opts == GeneratorOptions::Elide {
collect_generator_digests(&inner, &mut digests_to_elide)?;
}
let elided = if digests_to_elide.is_empty() {
envelope
} else {
envelope.elide_removing_set(&digests_to_elide)
};
Ok(UR::new("xid", elided.to_cbor())?.string())
}
fn reconstruct_document(&self) -> Result<String> {
let xid_document = self.read_xid_document_with_password_and_verify(
&self.password_args.read,
self.verify_args.verify_signature(),
)?;
let signing_options = self
.signing_args
.signing_options(Some(&self.password_args.read))?;
xid_document_to_ur_string(
&xid_document,
&self.output_opts,
Some(&self.password_args.write),
None,
signing_options,
)
}
}
fn collect_private_key_digests(
envelope: &Envelope,
digests: &mut HashSet<Digest>,
) -> Result<()> {
for key_assertion in envelope.assertions_with_predicate(KEY) {
let key_object = key_assertion.try_object()?;
if let Some(pk_assertion) =
key_object.optional_assertion_with_predicate(PRIVATE_KEY)?
{
digests.insert(pk_assertion.digest().to_owned());
}
}
Ok(())
}
fn collect_generator_digests(
envelope: &Envelope,
digests: &mut HashSet<Digest>,
) -> Result<()> {
if let Some(prov_assertion) =
envelope.optional_assertion_with_predicate(PROVENANCE)?
{
let prov_object = prov_assertion.try_object()?;
if let Some(gen_assertion) = prov_object
.optional_assertion_with_predicate(PROVENANCE_GENERATOR)?
{
digests.insert(gen_assertion.digest().to_owned());
}
}
Ok(())
}