use color_eyre::eyre::eyre;
use log::{debug, info, warn};
use once_cell::sync::OnceCell;
use serde_cose::sig::Sig;
use std::{convert::TryFrom, fmt};
use structopt::StructOpt;
use x509_parser::{der_parser::oid, oid_registry::OidRegistry, prelude::*};
use crate::{
cert::{Algorithm, Prime, TrustList},
dcc::{
load_sign1,
valuesets::{EhnData, ValueSet},
CertPayload,
},
json::Loadable,
};
pub mod b45;
pub mod cert;
pub mod cwt;
pub mod dcc;
pub mod json;
static OID_REGISTRY: OnceCell<OidRegistry> = OnceCell::new();
static EHN_DATA: OnceCell<EhnData> = OnceCell::new();
static TRUSTLIST: OnceCell<TrustList> = OnceCell::new();
#[derive(Debug, StructOpt)]
struct Args {
#[structopt(long)]
json: bool,
#[structopt(default_value = "-")]
file: String,
}
struct CertSubject<'a>(&'a X509Name<'a>);
impl fmt::Debug for CertSubject<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let oid_registry = OID_REGISTRY.get().unwrap();
let mut debug = f.debug_struct("X509Name");
for key in self.0.iter_attributes() {
match (oid_registry.get(&key.attr_type), key.attr_value.as_str()) {
(Some(name), Ok(value)) => {
debug.field(name.sn(), &value);
}
(Some(name), Err(_)) => {
debug.field(name.sn(), &"???");
}
(None, Ok(value)) => {
let name = format!("oid_{}", key.attr_type);
debug.field(&name, &value);
}
(None, Err(_)) => {
let name = format!("oid_{}", key.attr_type);
debug.field(&name, &"???");
}
}
}
debug.finish()
}
}
fn main() -> color_eyre::Result<()> {
color_eyre::install()?;
pretty_env_logger::formatted_builder()
.filter(Some("dcc_decode"), log::LevelFilter::Debug)
.init();
let args = Args::from_args();
let mut oid_registry = OidRegistry::default().with_crypto().with_x509();
oid_registry.insert(oid!(2.5.4 .97), ("organizationIdentifier", ""));
oid_registry.insert(
oid!(2.5.4 .5),
("serialNumber", "Serial number attribute type"),
);
OID_REGISTRY.set(oid_registry).unwrap();
let ehn_data = EhnData {
vaccine_prophylaxis: ValueSet::load("ehn-dcc-valuesets/vaccine-prophylaxis.json"),
disease_agent_targeted: ValueSet::load("ehn-dcc-valuesets/disease-agent-targeted.json"),
vaccine_mah_manf: ValueSet::load("ehn-dcc-valuesets/vaccine-mah-manf.json"),
vaccine_medicinal_product: ValueSet::load(
"ehn-dcc-valuesets/vaccine-medicinal-product.json",
),
};
EHN_DATA.set(ehn_data).unwrap();
if let Some(trustlist) = TrustList::load("trustlist.json") {
TRUSTLIST.set(trustlist).unwrap();
}
let mut buf = String::new();
if args.file == "-" {
std::io::stdin().read_line(&mut buf)?;
} else {
buf = std::fs::read_to_string(&args.file)?;
}
let sign1 = load_sign1(&buf)?;
let b64_kid = base64::encode(sign1.kid());
info!("Well-formed COSE certificate (kid='{}')", b64_kid);
let v = CertPayload::try_from(&sign1)?;
info!("Well-formed Digital-Covid-Certificate");
if args.json {
let jout = serde_json::to_string(&v.health_claim.cert)?;
println!("{}", jout);
} else {
println!("{:#?}", v);
}
let signature = sign1.signature.clone();
if let Some(cert) = TRUSTLIST
.get()
.and_then(|t| t.certificates.iter().find(|&c| c.kid == b64_kid))
{
info!("Found certificate with matching kid in trustlist");
let sig = Sig::from(sign1);
let message = serde_cbor::to_vec(&sig)?;
debug!("Signature1 encoding successful");
let sigbytes = base64::decode(&cert.raw_data)?;
let (_, sigcert) = parse_x509_certificate(&sigbytes)?;
debug!("Loaded issuer X.509 certificate");
let subject = &sigcert.tbs_certificate.subject;
if let Some(name) = subject
.iter_common_name()
.next()
.and_then(|name| name.attr_value.as_str().ok())
{
info!("subject common name: {:?}", name);
}
let sigpki = &sigcert.tbs_certificate.subject_pki;
let alg = cert::get_pk_sig_algorithm(sigpki)?;
debug!("found signature algorithm: {:?}", alg);
if Algorithm::IdEcPublicKey(Prime::Prime256v1) == alg {
let pubkey = ring::signature::UnparsedPublicKey::new(
&ring::signature::ECDSA_P256_SHA256_FIXED,
&sigpki.subject_public_key.data,
);
pubkey
.verify(&message, &signature)
.map_err(|_e| eyre!("Verification failed"))?;
info!("Verified OK");
} else {
warn!("Unknown signature algorithm");
}
} else {
warn!("Did not find certificate with matching kid")
}
Ok(())
}