#![deny(missing_docs)]
use std::io::Cursor;
use async_generic::async_generic;
use ciborium::value::Value;
use coset::{
iana::{self, EnumI64},
CoseSign1, CoseSign1Builder, Header, HeaderBuilder, Label, ProtectedHeader,
TaggedCborSerializable,
};
use crate::{
claim::Claim,
cose_validator::{check_cert, verify_cose},
settings::get_settings_value,
status_tracker::OneShotStatusTracker,
time_stamp::{
cose_timestamp_countersign, cose_timestamp_countersign_async, make_cose_timestamp,
},
trust_handler::TrustHandlerConfig,
AsyncSigner, Error, Result, Signer, SigningAlg,
};
#[async_generic(async_signature(
claim_bytes: &[u8],
signer: &dyn AsyncSigner,
box_size: usize
))]
pub fn sign_claim(claim_bytes: &[u8], signer: &dyn Signer, box_size: usize) -> Result<Vec<u8>> {
let label = "dummy_label";
let _claim = Claim::from_data(label, claim_bytes)?;
let signed_bytes = if _sync {
cose_sign(signer, claim_bytes, box_size)
} else {
cose_sign_async(signer, claim_bytes, box_size).await
};
match signed_bytes {
Ok(signed_bytes) => {
let mut cose_log = OneShotStatusTracker::new();
let passthrough_tb = crate::trust_handler::TrustPassThrough::new();
match verify_cose(
&signed_bytes,
claim_bytes,
b"",
true,
&passthrough_tb,
&mut cose_log,
) {
Ok(r) => {
if !r.validated {
Err(Error::CoseSignature)
} else {
Ok(signed_bytes)
}
}
Err(err) => Err(err),
}
}
Err(err) => Err(err),
}
}
fn signing_cert_valid(signing_cert: &[u8]) -> Result<()> {
let mut cose_log = OneShotStatusTracker::default();
let mut passthrough_tb = crate::trust_handler::TrustPassThrough::new();
if let Ok(Some(trust_config)) = get_settings_value::<Option<String>>("trust.trust_config") {
let mut reader = Cursor::new(trust_config.as_bytes());
passthrough_tb.load_configuration(&mut reader)?;
}
check_cert(signing_cert, &passthrough_tb, &mut cose_log, None)
}
#[async_generic(async_signature(
signer: &dyn AsyncSigner,
data: &[u8],
box_size: usize
))]
pub(crate) fn cose_sign(signer: &dyn Signer, data: &[u8], box_size: usize) -> Result<Vec<u8>> {
let certs = signer.certs()?;
if let Some(signing_cert) = certs.first() {
signing_cert_valid(signing_cert)?;
} else {
return Err(Error::CoseNoCerts);
}
let alg = signer.alg();
let (protected_header, unprotected_header) = if _sync {
build_headers(signer, data, alg)?
} else {
build_headers_async(signer, data, alg).await?
};
let aad: &[u8; 0] = b""; let sign1_builder = CoseSign1Builder::new()
.protected(protected_header)
.unprotected(unprotected_header)
.payload(data.to_vec());
let mut sign1 = sign1_builder.build();
let tbs = coset::sig_structure_data(
coset::SignatureContext::CoseSign1,
sign1.protected.clone(),
None,
aad,
sign1.payload.as_ref().unwrap_or(&vec![]),
);
if _sync {
sign1.signature = signer.sign(&tbs)?;
} else {
sign1.signature = signer.sign(tbs).await?;
}
sign1.payload = None; let c2pa_sig_data = pad_cose_sig(&mut sign1, box_size)?;
Ok(c2pa_sig_data)
}
#[async_generic(async_signature(signer: &dyn AsyncSigner, data: &[u8], alg: SigningAlg))]
fn build_headers(signer: &dyn Signer, data: &[u8], alg: SigningAlg) -> Result<(Header, Header)> {
let mut protected_h = match alg {
SigningAlg::Ps256 => HeaderBuilder::new().algorithm(iana::Algorithm::PS256),
SigningAlg::Ps384 => HeaderBuilder::new().algorithm(iana::Algorithm::PS384),
SigningAlg::Ps512 => HeaderBuilder::new().algorithm(iana::Algorithm::PS512),
SigningAlg::Es256 => HeaderBuilder::new().algorithm(iana::Algorithm::ES256),
SigningAlg::Es384 => HeaderBuilder::new().algorithm(iana::Algorithm::ES384),
SigningAlg::Es512 => HeaderBuilder::new().algorithm(iana::Algorithm::ES512),
SigningAlg::Ed25519 => HeaderBuilder::new().algorithm(iana::Algorithm::EdDSA),
};
let certs = signer.certs()?;
let ocsp_val = if _sync {
signer.ocsp_val()
} else {
signer.ocsp_val().await
};
let sc_der_array_or_bytes = match certs.len() {
1 => Value::Bytes(certs[0].clone()), _ => {
let mut sc_der_array: Vec<Value> = Vec::new();
for cert in certs {
sc_der_array.push(Value::Bytes(cert));
}
Value::Array(sc_der_array) }
};
protected_h = protected_h.value(
iana::HeaderParameter::X5Chain.to_i64(),
sc_der_array_or_bytes.clone(),
);
let protected_header = protected_h.build();
let ph2 = ProtectedHeader {
original_data: None,
header: protected_header.clone(),
};
let maybe_cts = if _sync {
cose_timestamp_countersign(signer, data, &ph2)
} else {
cose_timestamp_countersign_async(signer, data, &ph2).await
};
let mut unprotected_h = if let Some(cts) = maybe_cts {
let cts = cts?;
let sigtst_vec = serde_cbor::to_vec(&make_cose_timestamp(&cts))?;
let sigtst_cbor = serde_cbor::from_slice(&sigtst_vec)?;
HeaderBuilder::new().text_value("sigTst".to_string(), sigtst_cbor)
} else {
HeaderBuilder::new()
};
if let Some(ocsp) = ocsp_val {
let mut ocsp_vec: Vec<Value> = Vec::new();
let mut r_vals: Vec<(Value, Value)> = vec![];
ocsp_vec.push(Value::Bytes(ocsp));
r_vals.push((Value::Text("ocspVals".to_string()), Value::Array(ocsp_vec)));
unprotected_h = unprotected_h.text_value("rVals".to_string(), Value::Map(r_vals));
}
let unprotected_header = unprotected_h.build();
Ok((protected_header, unprotected_header))
}
const PAD: &str = "pad";
const PAD2: &str = "pad2";
const PAD_OFFSET: usize = 7;
fn pad_cose_sig(sign1: &mut CoseSign1, end_size: usize) -> Result<Vec<u8>> {
let mut sign1_clone = sign1.clone();
let cur_vec = sign1_clone
.to_tagged_vec()
.map_err(|_e| Error::CoseSignature)?;
let cur_size = cur_vec.len();
if cur_size == end_size {
return Ok(cur_vec);
}
if cur_size + PAD_OFFSET > end_size {
return Err(Error::CoseSigboxTooSmall);
}
let mut padding_found = false;
let mut last_pad = 0;
let mut target_guess = end_size - cur_size - PAD_OFFSET; loop {
sign1_clone = sign1.clone();
for header_pair in &mut sign1_clone.unprotected.rest {
if header_pair.0 == Label::Text("pad".to_string()) {
if let Value::Bytes(b) = &header_pair.1 {
last_pad = b.len();
}
header_pair.1 = Value::Bytes(vec![0u8; target_guess]);
padding_found = true;
break;
}
}
if !padding_found {
sign1_clone.unprotected.rest.push((
Label::Text(PAD.to_string()),
Value::Bytes(vec![0u8; target_guess]),
));
return pad_cose_sig(&mut sign1_clone, end_size);
}
let new_cbor = sign1_clone
.to_tagged_vec()
.map_err(|_e| Error::CoseSignature)?;
match new_cbor.len() < end_size {
true => target_guess += 1,
false if new_cbor.len() == end_size => return Ok(new_cbor),
false => break, }
}
sign1.unprotected.rest.push((
Label::Text(PAD2.to_string()),
Value::Bytes(vec![0u8; last_pad - 10]),
));
pad_cose_sig(sign1, end_size)
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used)]
use super::sign_claim;
use crate::{claim::Claim, utils::test::temp_signer};
#[test]
fn test_sign_claim() {
let mut claim = Claim::new("extern_sign_test", Some("contentauth"));
claim.build().unwrap();
let claim_bytes = claim.data().unwrap();
let signer = temp_signer();
let box_size = signer.reserve_size();
let cose_sign1 = sign_claim(&claim_bytes, signer.as_ref(), box_size).unwrap();
assert_eq!(cose_sign1.len(), box_size);
}
#[cfg(not(target_arch = "wasm32"))]
#[cfg(feature = "openssl")]
#[actix::test]
async fn test_sign_claim_async() {
use crate::{
cose_sign::sign_claim_async, openssl::AsyncSignerAdapter, AsyncSigner, SigningAlg,
};
let mut claim = Claim::new("extern_sign_test", Some("contentauth"));
claim.build().unwrap();
let claim_bytes = claim.data().unwrap();
let signer = AsyncSignerAdapter::new(SigningAlg::Ps256);
let box_size = signer.reserve_size();
let cose_sign1 = sign_claim_async(&claim_bytes, &signer, box_size)
.await
.unwrap();
assert_eq!(cose_sign1.len(), box_size);
}
struct BogusSigner {}
impl BogusSigner {
pub fn new() -> Self {
BogusSigner {}
}
}
impl crate::Signer for BogusSigner {
fn sign(&self, _data: &[u8]) -> crate::error::Result<Vec<u8>> {
eprintln!("Canary, canary, please cause this deploy to fail!");
Ok(b"totally bogus signature".to_vec())
}
fn alg(&self) -> crate::SigningAlg {
crate::SigningAlg::Ps256
}
fn certs(&self) -> crate::error::Result<Vec<Vec<u8>>> {
let cert_vec: Vec<u8> = Vec::new();
let certs = vec![cert_vec];
Ok(certs)
}
fn reserve_size(&self) -> usize {
1024
}
fn send_timestamp_request(&self, _message: &[u8]) -> Option<crate::error::Result<Vec<u8>>> {
Some(Ok(Vec::new()))
}
}
#[test]
fn test_bogus_signer() {
let mut claim = Claim::new("bogus_sign_test", Some("contentauth"));
claim.build().unwrap();
let claim_bytes = claim.data().unwrap();
let box_size = 10000;
let signer = BogusSigner::new();
let _cose_sign1 = sign_claim(&claim_bytes, &signer, box_size);
#[cfg(feature = "openssl")] assert!(_cose_sign1.is_err());
}
}