c2pa-rs

A fork of https://github.com/contentauth/c2pa-rs/
git clone git://archive.git.mtrnord.blog/mtrnords-photography-manager/c2pa-rs.git
Log | Files | Refs | README

cose_sign.rs (14757B)


      1 // Copyright 2022 Adobe. All rights reserved.
      2 // This file is licensed to you under the Apache License,
      3 // Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
      4 // or the MIT license (http://opensource.org/licenses/MIT),
      5 // at your option.
      6 
      7 // Unless required by applicable law or agreed to in writing,
      8 // this software is distributed on an "AS IS" BASIS, WITHOUT
      9 // WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or
     10 // implied. See the LICENSE-MIT and LICENSE-APACHE files for the
     11 // specific language governing permissions and limitations under
     12 // each license.
     13 
     14 //! Provides access to COSE signature generation.
     15 
     16 #![deny(missing_docs)]
     17 
     18 use std::io::Cursor;
     19 
     20 use async_generic::async_generic;
     21 use ciborium::value::Value;
     22 use coset::{
     23     iana::{self, EnumI64},
     24     CoseSign1, CoseSign1Builder, Header, HeaderBuilder, Label, ProtectedHeader,
     25     TaggedCborSerializable,
     26 };
     27 
     28 use crate::{
     29     claim::Claim,
     30     cose_validator::{check_cert, verify_cose},
     31     settings::get_settings_value,
     32     status_tracker::OneShotStatusTracker,
     33     time_stamp::{
     34         cose_timestamp_countersign, cose_timestamp_countersign_async, make_cose_timestamp,
     35     },
     36     trust_handler::TrustHandlerConfig,
     37     AsyncSigner, Error, Result, Signer, SigningAlg,
     38 };
     39 
     40 /// Generate a COSE signature for a block of bytes which must be a valid C2PA
     41 /// claim structure.
     42 ///
     43 /// Should only be used when the underlying signature mechanism is detached
     44 /// from the generation of the C2PA manifest (and thus the claim embedded in it).
     45 ///
     46 /// ## Actions taken
     47 ///
     48 /// 1. Verifies that the data supplied is a valid C2PA claim. The function will
     49 ///    respond with [`Error::ClaimDecoding`] if not.
     50 /// 2. Signs the data using the provided [`Signer`] instance. Will ensure that
     51 ///    the signature is padded to match `box_size`, which should be the number of
     52 ///    bytes reserved for the `c2pa.signature` JUMBF box in this claim's manifest.
     53 ///    (If `box_size` is too small for the generated signature, this function
     54 ///    will respond with an error.)
     55 /// 3. Verifies that the signature is valid COSE. Will respond with an error
     56 ///    [`Error::CoseSignature`] if unable to validate.
     57 #[async_generic(async_signature(
     58     claim_bytes: &[u8],
     59     signer: &dyn AsyncSigner,
     60     box_size: usize
     61 ))]
     62 pub fn sign_claim(claim_bytes: &[u8], signer: &dyn Signer, box_size: usize) -> Result<Vec<u8>> {
     63     // Must be a valid claim.
     64     let label = "dummy_label";
     65     let _claim = Claim::from_data(label, claim_bytes)?;
     66 
     67     let signed_bytes = if _sync {
     68         cose_sign(signer, claim_bytes, box_size)
     69     } else {
     70         cose_sign_async(signer, claim_bytes, box_size).await
     71     };
     72 
     73     match signed_bytes {
     74         Ok(signed_bytes) => {
     75             // Sanity check: Ensure that this signature is valid.
     76             let mut cose_log = OneShotStatusTracker::new();
     77             let passthrough_tb = crate::trust_handler::TrustPassThrough::new();
     78 
     79             match verify_cose(
     80                 &signed_bytes,
     81                 claim_bytes,
     82                 b"",
     83                 true,
     84                 &passthrough_tb,
     85                 &mut cose_log,
     86             ) {
     87                 Ok(r) => {
     88                     if !r.validated {
     89                         Err(Error::CoseSignature)
     90                     } else {
     91                         Ok(signed_bytes)
     92                     }
     93                 }
     94                 Err(err) => Err(err),
     95             }
     96         }
     97         Err(err) => Err(err),
     98     }
     99 }
    100 
    101 fn signing_cert_valid(signing_cert: &[u8]) -> Result<()> {
    102     // make sure signer certs are valid
    103     let mut cose_log = OneShotStatusTracker::default();
    104     let mut passthrough_tb = crate::trust_handler::TrustPassThrough::new();
    105 
    106     // allow user EKUs through this check if configured
    107     if let Ok(Some(trust_config)) = get_settings_value::<Option<String>>("trust.trust_config") {
    108         let mut reader = Cursor::new(trust_config.as_bytes());
    109         passthrough_tb.load_configuration(&mut reader)?;
    110     }
    111 
    112     check_cert(signing_cert, &passthrough_tb, &mut cose_log, None)
    113 }
    114 
    115 /// Returns signed Cose_Sign1 bytes for `data`.
    116 /// The Cose_Sign1 will be signed with the algorithm from [`Signer`].
    117 #[async_generic(async_signature(
    118     signer: &dyn AsyncSigner,
    119     data: &[u8],
    120     box_size: usize
    121 ))]
    122 pub(crate) fn cose_sign(signer: &dyn Signer, data: &[u8], box_size: usize) -> Result<Vec<u8>> {
    123     // 13.2.1. X.509 Certificates
    124     //
    125     // X.509 Certificates are stored in a header named x5chain draft-ietf-cose-x509.
    126     // The value is a CBOR array of byte strings, each of which contains the certificate
    127     // encoded as ASN.1 distinguished encoding rules (DER). This array must contain at
    128     // least one element. The first element of the array must be the certificate of
    129     // the signer, and the subjectPublicKeyInfo element of the certificate will be the
    130     // public key used to validate the signature. The Validity member of the TBSCertificate
    131     // sequence provides the time validity period of the certificate.
    132 
    133     /*
    134        This header parameter allows for a single X.509 certificate or a
    135        chain of X.509 certificates to be carried in the message.
    136 
    137        *  If a single certificate is conveyed, it is placed in a CBOR
    138            byte string.
    139 
    140        *  If multiple certificates are conveyed, a CBOR array of byte
    141            strings is used, with each certificate being in its own byte
    142            string.
    143     */
    144 
    145     // make sure the signing cert is valid
    146     let certs = signer.certs()?;
    147     if let Some(signing_cert) = certs.first() {
    148         signing_cert_valid(signing_cert)?;
    149     } else {
    150         return Err(Error::CoseNoCerts);
    151     }
    152 
    153     let alg = signer.alg();
    154 
    155     // build complete header
    156     let (protected_header, unprotected_header) = if _sync {
    157         build_headers(signer, data, alg)?
    158     } else {
    159         build_headers_async(signer, data, alg).await?
    160     };
    161 
    162     let aad: &[u8; 0] = b""; // no additional data required here
    163 
    164     let sign1_builder = CoseSign1Builder::new()
    165         .protected(protected_header)
    166         .unprotected(unprotected_header)
    167         .payload(data.to_vec());
    168 
    169     let mut sign1 = sign1_builder.build();
    170 
    171     let tbs = coset::sig_structure_data(
    172         coset::SignatureContext::CoseSign1,
    173         sign1.protected.clone(),
    174         None,
    175         aad,
    176         sign1.payload.as_ref().unwrap_or(&vec![]),
    177     );
    178 
    179     if _sync {
    180         sign1.signature = signer.sign(&tbs)?;
    181     } else {
    182         sign1.signature = signer.sign(tbs).await?;
    183     }
    184 
    185     sign1.payload = None; // clear the payload since it is known
    186 
    187     let c2pa_sig_data = pad_cose_sig(&mut sign1, box_size)?;
    188 
    189     // println!("sig: {}", Hexlify(&c2pa_sig_data));
    190 
    191     Ok(c2pa_sig_data)
    192 }
    193 
    194 #[async_generic(async_signature(signer: &dyn AsyncSigner, data: &[u8], alg: SigningAlg))]
    195 fn build_headers(signer: &dyn Signer, data: &[u8], alg: SigningAlg) -> Result<(Header, Header)> {
    196     let mut protected_h = match alg {
    197         SigningAlg::Ps256 => HeaderBuilder::new().algorithm(iana::Algorithm::PS256),
    198         SigningAlg::Ps384 => HeaderBuilder::new().algorithm(iana::Algorithm::PS384),
    199         SigningAlg::Ps512 => HeaderBuilder::new().algorithm(iana::Algorithm::PS512),
    200         SigningAlg::Es256 => HeaderBuilder::new().algorithm(iana::Algorithm::ES256),
    201         SigningAlg::Es384 => HeaderBuilder::new().algorithm(iana::Algorithm::ES384),
    202         SigningAlg::Es512 => HeaderBuilder::new().algorithm(iana::Algorithm::ES512),
    203         SigningAlg::Ed25519 => HeaderBuilder::new().algorithm(iana::Algorithm::EdDSA),
    204     };
    205 
    206     let certs = signer.certs()?;
    207 
    208     let ocsp_val = if _sync {
    209         signer.ocsp_val()
    210     } else {
    211         signer.ocsp_val().await
    212     };
    213 
    214     let sc_der_array_or_bytes = match certs.len() {
    215         1 => Value::Bytes(certs[0].clone()), // single cert
    216         _ => {
    217             let mut sc_der_array: Vec<Value> = Vec::new();
    218             for cert in certs {
    219                 sc_der_array.push(Value::Bytes(cert));
    220             }
    221             Value::Array(sc_der_array) // provide vec of certs when required
    222         }
    223     };
    224 
    225     // add certs to protected header (spec 1.3 now requires integer 33(X5Chain) in favor of string "x5chain" going forward)
    226     protected_h = protected_h.value(
    227         iana::HeaderParameter::X5Chain.to_i64(),
    228         sc_der_array_or_bytes.clone(),
    229     );
    230 
    231     let protected_header = protected_h.build();
    232     let ph2 = ProtectedHeader {
    233         original_data: None,
    234         header: protected_header.clone(),
    235     };
    236 
    237     let maybe_cts = if _sync {
    238         cose_timestamp_countersign(signer, data, &ph2)
    239     } else {
    240         cose_timestamp_countersign_async(signer, data, &ph2).await
    241     };
    242 
    243     let mut unprotected_h = if let Some(cts) = maybe_cts {
    244         let cts = cts?;
    245         let sigtst_vec = serde_cbor::to_vec(&make_cose_timestamp(&cts))?;
    246         let sigtst_cbor = serde_cbor::from_slice(&sigtst_vec)?;
    247 
    248         HeaderBuilder::new().text_value("sigTst".to_string(), sigtst_cbor)
    249     } else {
    250         HeaderBuilder::new()
    251     };
    252 
    253     // set the ocsp responder response if available
    254     if let Some(ocsp) = ocsp_val {
    255         let mut ocsp_vec: Vec<Value> = Vec::new();
    256         let mut r_vals: Vec<(Value, Value)> = vec![];
    257 
    258         ocsp_vec.push(Value::Bytes(ocsp));
    259         r_vals.push((Value::Text("ocspVals".to_string()), Value::Array(ocsp_vec)));
    260 
    261         unprotected_h = unprotected_h.text_value("rVals".to_string(), Value::Map(r_vals));
    262     }
    263 
    264     // build complete header
    265     let unprotected_header = unprotected_h.build();
    266 
    267     Ok((protected_header, unprotected_header))
    268 }
    269 
    270 const PAD: &str = "pad";
    271 const PAD2: &str = "pad2";
    272 const PAD_OFFSET: usize = 7;
    273 
    274 // Pad the CoseSign1 structure with 0s to match the reserved box size.
    275 // There are some values lengths that are impossible to hit with a single padding so
    276 // when that happens a second padding is added to change the remaining needed padding.
    277 // The default initial guess works for almost all sizes, without the need for additional loops.
    278 fn pad_cose_sig(sign1: &mut CoseSign1, end_size: usize) -> Result<Vec<u8>> {
    279     let mut sign1_clone = sign1.clone();
    280     let cur_vec = sign1_clone
    281         .to_tagged_vec()
    282         .map_err(|_e| Error::CoseSignature)?;
    283     let cur_size = cur_vec.len();
    284 
    285     if cur_size == end_size {
    286         return Ok(cur_vec);
    287     }
    288 
    289     // check for box too small and matched size
    290     if cur_size + PAD_OFFSET > end_size {
    291         return Err(Error::CoseSigboxTooSmall);
    292     }
    293 
    294     let mut padding_found = false;
    295     let mut last_pad = 0;
    296     let mut target_guess = end_size - cur_size - PAD_OFFSET; // start close to desired end_size accounting for label
    297     loop {
    298         // clone to use
    299         sign1_clone = sign1.clone();
    300 
    301         // replace padding with new estimate
    302         for header_pair in &mut sign1_clone.unprotected.rest {
    303             if header_pair.0 == Label::Text("pad".to_string()) {
    304                 if let Value::Bytes(b) = &header_pair.1 {
    305                     last_pad = b.len();
    306                 }
    307                 header_pair.1 = Value::Bytes(vec![0u8; target_guess]);
    308                 padding_found = true;
    309                 break;
    310             }
    311         }
    312 
    313         // if there was no padding add it and call again
    314         if !padding_found {
    315             sign1_clone.unprotected.rest.push((
    316                 Label::Text(PAD.to_string()),
    317                 Value::Bytes(vec![0u8; target_guess]),
    318             ));
    319             return pad_cose_sig(&mut sign1_clone, end_size);
    320         }
    321 
    322         // get current cbor vec to size if we reached target size
    323         let new_cbor = sign1_clone
    324             .to_tagged_vec()
    325             .map_err(|_e| Error::CoseSignature)?;
    326 
    327         match new_cbor.len() < end_size {
    328             true => target_guess += 1,
    329             false if new_cbor.len() == end_size => return Ok(new_cbor),
    330             false => break, // we could not match end_size in a single pad so break and add a second
    331         }
    332     }
    333 
    334     // if we reach here we need a new second padding object to hit exact size
    335     sign1.unprotected.rest.push((
    336         Label::Text(PAD2.to_string()),
    337         Value::Bytes(vec![0u8; last_pad - 10]),
    338     ));
    339     pad_cose_sig(sign1, end_size)
    340 }
    341 
    342 #[cfg(test)]
    343 mod tests {
    344     #![allow(clippy::unwrap_used)]
    345 
    346     use super::sign_claim;
    347     use crate::{claim::Claim, utils::test::temp_signer};
    348 
    349     #[test]
    350     fn test_sign_claim() {
    351         let mut claim = Claim::new("extern_sign_test", Some("contentauth"));
    352         claim.build().unwrap();
    353 
    354         let claim_bytes = claim.data().unwrap();
    355 
    356         let signer = temp_signer();
    357         let box_size = signer.reserve_size();
    358 
    359         let cose_sign1 = sign_claim(&claim_bytes, signer.as_ref(), box_size).unwrap();
    360 
    361         assert_eq!(cose_sign1.len(), box_size);
    362     }
    363 
    364     #[cfg(not(target_arch = "wasm32"))]
    365     #[cfg(feature = "openssl")]
    366     #[actix::test]
    367     async fn test_sign_claim_async() {
    368         use crate::{
    369             cose_sign::sign_claim_async, openssl::AsyncSignerAdapter, AsyncSigner, SigningAlg,
    370         };
    371 
    372         let mut claim = Claim::new("extern_sign_test", Some("contentauth"));
    373         claim.build().unwrap();
    374 
    375         let claim_bytes = claim.data().unwrap();
    376 
    377         let signer = AsyncSignerAdapter::new(SigningAlg::Ps256);
    378         let box_size = signer.reserve_size();
    379 
    380         let cose_sign1 = sign_claim_async(&claim_bytes, &signer, box_size)
    381             .await
    382             .unwrap();
    383 
    384         assert_eq!(cose_sign1.len(), box_size);
    385     }
    386 
    387     struct BogusSigner {}
    388 
    389     impl BogusSigner {
    390         pub fn new() -> Self {
    391             BogusSigner {}
    392         }
    393     }
    394 
    395     impl crate::Signer for BogusSigner {
    396         fn sign(&self, _data: &[u8]) -> crate::error::Result<Vec<u8>> {
    397             eprintln!("Canary, canary, please cause this deploy to fail!");
    398             Ok(b"totally bogus signature".to_vec())
    399         }
    400 
    401         fn alg(&self) -> crate::SigningAlg {
    402             crate::SigningAlg::Ps256
    403         }
    404 
    405         fn certs(&self) -> crate::error::Result<Vec<Vec<u8>>> {
    406             let cert_vec: Vec<u8> = Vec::new();
    407             let certs = vec![cert_vec];
    408             Ok(certs)
    409         }
    410 
    411         fn reserve_size(&self) -> usize {
    412             1024
    413         }
    414 
    415         fn send_timestamp_request(&self, _message: &[u8]) -> Option<crate::error::Result<Vec<u8>>> {
    416             Some(Ok(Vec::new()))
    417         }
    418     }
    419 
    420     #[test]
    421     fn test_bogus_signer() {
    422         let mut claim = Claim::new("bogus_sign_test", Some("contentauth"));
    423         claim.build().unwrap();
    424 
    425         let claim_bytes = claim.data().unwrap();
    426 
    427         let box_size = 10000;
    428 
    429         let signer = BogusSigner::new();
    430 
    431         let _cose_sign1 = sign_claim(&claim_bytes, &signer, box_size);
    432 
    433         #[cfg(feature = "openssl")] // there is no verify on sign when openssl is disabled
    434         assert!(_cose_sign1.is_err());
    435     }
    436 }