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 }