commit f7c044f3566bfbf90c9bc091501cef696488910c
parent d2946dc71fcb9006222b056c1261db85e15939cc
Author: Eric Scouten <scouten@adobe.com>
Date: Thu, 28 Sep 2023 11:09:56 -0700
(MINOR) Signer can call timestamp authority directly (#311)
Diffstat:
6 files changed, 99 insertions(+), 104 deletions(-)
diff --git a/export_schema/Cargo.toml b/export_schema/Cargo.toml
@@ -4,7 +4,7 @@ version = "0.26.0"
authors = ["Dave Kozma <dkozma@adobe.com>"]
license = "MIT OR Apache-2.0"
edition = "2018"
-rust-version = "1.65.0"
+rust-version = "1.70.0"
[dependencies]
anyhow = "1.0.40"
diff --git a/make_test_images/Cargo.toml b/make_test_images/Cargo.toml
@@ -4,7 +4,7 @@ version = "0.26.0"
authors = ["Gavin Peacock <gpeacock@adobe.com>"]
license = "MIT OR Apache-2.0"
edition = "2021"
-rust-version = "1.65.0"
+rust-version = "1.70.0"
[dependencies]
anyhow = "1.0.40"
diff --git a/sdk/Cargo.toml b/sdk/Cargo.toml
@@ -17,7 +17,7 @@ readme = "../README.md"
keywords = ["xmp", "metadata"]
categories = ["api-bindings"]
edition = "2021"
-rust-version = "1.65.0"
+rust-version = "1.70.0"
exclude = ["tests/fixtures"]
[package.metadata.docs.rs]
@@ -57,6 +57,7 @@ crate-type = ["lib"]
[dependencies]
asn1-rs = "0.5.2"
+async-generic = "0.1.2"
async-trait = { version = "0.1.48" }
atree = "0.5.2"
base64 = "0.21.2"
diff --git a/sdk/src/cose_sign.rs b/sdk/src/cose_sign.rs
@@ -15,6 +15,7 @@
#![deny(missing_docs)]
+use async_generic::async_generic;
use ciborium::value::Value;
use coset::{
iana::{self},
@@ -26,8 +27,10 @@ use crate::{
claim::Claim,
cose_validator::verify_cose,
status_tracker::OneShotStatusTracker,
- time_stamp::{cose_timestamp_countersign, make_cose_timestamp},
- Error, Result, Signer, SigningAlg,
+ time_stamp::{
+ cose_timestamp_countersign, cose_timestamp_countersign_async, make_cose_timestamp,
+ },
+ AsyncSigner, Error, Result, Signer, SigningAlg,
};
/// Generate a COSE signature for a block of bytes which must be a valid C2PA
@@ -72,64 +75,12 @@ pub fn sign_claim(claim_bytes: &[u8], signer: &dyn Signer, box_size: usize) -> R
/// Returns signed Cose_Sign1 bytes for `data`.
/// The Cose_Sign1 will be signed with the algorithm from [`Signer`].
-pub(crate) fn cose_sign(signer: &dyn Signer, data: &[u8], box_size: usize) -> Result<Vec<u8>> {
- // 13.2.1. X.509 Certificates
- //
- // X.509 Certificates are stored in a header named x5chain draft-ietf-cose-x509.
- // The value is a CBOR array of byte strings, each of which contains the certificate
- // encoded as ASN.1 distinguished encoding rules (DER). This array must contain at
- // least one element. The first element of the array must be the certificate of
- // the signer, and the subjectPublicKeyInfo element of the certificate will be the
- // public key used to validate the signature. The Validity member of the TBSCertificate
- // sequence provides the time validity period of the certificate.
-
- /*
- This header parameter allows for a single X.509 certificate or a
- chain of X.509 certificates to be carried in the message.
-
- * If a single certificate is conveyed, it is placed in a CBOR
- byte string.
-
- * If multiple certificates are conveyed, a CBOR array of byte
- strings is used, with each certificate being in its own byte
- string.
- */
-
- let alg = signer.alg();
-
- // build complete header
- let (protected_header, unprotected_header) = build_headers(
- data,
- alg,
- signer.certs()?,
- signer.time_authority_url(),
- signer.ocsp_val(),
- )?;
-
- let aad = b""; // no additional data required here
-
- let sign1_builder = CoseSign1Builder::new()
- .protected(protected_header)
- .unprotected(unprotected_header)
- .payload(data.to_vec())
- .try_create_signature(aad, |bytes| signer.sign(bytes))?;
-
- let mut sign1 = sign1_builder.build();
- sign1.payload = None; // clear the payload since it is known
-
- let c2pa_sig_data = pad_cose_sig(&mut sign1, box_size)?;
-
- // println!("sig: {}", Hexlify(&c2pa_sig_data));
-
- Ok(c2pa_sig_data)
-}
-
-/// Returns signed Cose_Sign1 bytes for "data". The Cose_Sign1 will be signed with the algorithm from `Signer`.
-pub async fn cose_sign_async(
- signer: &dyn crate::AsyncSigner,
+#[async_generic(async_signature(
+ signer: &dyn AsyncSigner,
data: &[u8],
- box_size: usize,
-) -> Result<Vec<u8>> {
+ box_size: usize
+))]
+pub(crate) fn cose_sign(signer: &dyn Signer, data: &[u8], box_size: usize) -> Result<Vec<u8>> {
// 13.2.1. X.509 Certificates
//
// X.509 Certificates are stored in a header named x5chain draft-ietf-cose-x509.
@@ -155,15 +106,13 @@ pub async fn cose_sign_async(
let alg = signer.alg();
// build complete header
- let (protected_header, unprotected_header) = build_headers(
- data,
- alg,
- signer.certs()?,
- signer.time_authority_url(),
- signer.ocsp_val(),
- )?;
+ let (protected_header, unprotected_header) = if _sync {
+ build_headers(signer, data, alg)?
+ } else {
+ build_headers_async(signer, data, alg).await?
+ };
- let aad = b""; // no additional data required here
+ let aad: &[u8; 0] = b""; // no additional data required here
let sign1_builder = CoseSign1Builder::new()
.protected(protected_header)
@@ -179,7 +128,12 @@ pub async fn cose_sign_async(
aad,
sign1.payload.as_ref().unwrap_or(&vec![]),
);
- sign1.signature = signer.sign(tbs).await?;
+
+ if _sync {
+ sign1.signature = signer.sign(&tbs)?;
+ } else {
+ sign1.signature = signer.sign(tbs).await?;
+ }
sign1.payload = None; // clear the payload since it is known
@@ -190,13 +144,8 @@ pub async fn cose_sign_async(
Ok(c2pa_sig_data)
}
-fn build_headers(
- data: &[u8],
- alg: SigningAlg,
- certs: Vec<Vec<u8>>,
- ta_url: Option<String>,
- ocsp_val: Option<Vec<u8>>,
-) -> Result<(Header, Header)> {
+#[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 protected_h = match alg {
SigningAlg::Ps256 => HeaderBuilder::new().algorithm(iana::Algorithm::PS256),
SigningAlg::Ps384 => HeaderBuilder::new().algorithm(iana::Algorithm::PS384),
@@ -207,6 +156,9 @@ fn build_headers(
SigningAlg::Ed25519 => HeaderBuilder::new().algorithm(iana::Algorithm::EdDSA),
};
+ let certs = signer.certs()?;
+ let ocsp_val = signer.ocsp_val();
+
let sc_der_array_or_bytes = match certs.len() {
1 => Value::Bytes(certs[0].clone()), // single cert
_ => {
@@ -228,16 +180,19 @@ fn build_headers(
*/
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(url) = ta_url {
- let cts = cose_timestamp_countersign(
- data,
- &ProtectedHeader {
- original_data: None,
- header: protected_header.clone(),
- },
- &url,
- )?;
+ 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)?;
diff --git a/sdk/src/signer.rs b/sdk/src/signer.rs
@@ -34,6 +34,18 @@ pub trait Signer {
None
}
+ /// Request RFC 3161 timestamp to be included in the manifest data
+ /// structure.
+ ///
+ /// `message` is a preliminary hash of the claim
+ ///
+ /// The default implementation will send the request to the URL
+ /// provided by [`Self::time_authority_url()`], if any.
+ fn send_timestamp_request(&self, message: &[u8]) -> Option<Result<Vec<u8>>> {
+ self.time_authority_url()
+ .map(|url| crate::time_stamp::default_rfc3161_request(&url, message))
+ }
+
/// OCSP response for the signing cert if available
/// This is the only C2PA supported cert revocation method.
/// By pre-querying the value for a your signing cert the value can
@@ -99,6 +111,20 @@ pub trait AsyncSigner: Sync {
None
}
+ /// Request RFC 3161 timestamp to be included in the manifest data
+ /// structure.
+ ///
+ /// `message` is a preliminary hash of the claim
+ ///
+ /// The default implementation will send the request to the URL
+ /// provided by [`Self::time_authority_url()`], if any.
+ async fn send_timestamp_request(&self, message: &[u8]) -> Option<Result<Vec<u8>>> {
+ // NOTE: This is currently synchronous, but may become
+ // async in the future.
+ self.time_authority_url()
+ .map(|url| crate::time_stamp::default_rfc3161_request(&url, message))
+ }
+
/// OCSP response for the signing cert if available
/// This is the only C2PA supported cert revocation method.
/// By pre-querying the value for a your signing cert the value can
diff --git a/sdk/src/time_stamp.rs b/sdk/src/time_stamp.rs
@@ -13,6 +13,7 @@
use std::convert::TryFrom;
+use async_generic::async_generic;
use bcder::decode::Constructed;
use coset::{sig_structure_data, ProtectedHeader};
use serde::{Deserialize, Serialize};
@@ -27,6 +28,7 @@ use crate::{
rfc5652::{CertificateChoices::Certificate, SignedData, OID_ID_SIGNED_DATA},
},
hash_utils::vec_compare,
+ AsyncSigner, Signer,
};
#[allow(dead_code)]
@@ -43,12 +45,17 @@ pub(crate) fn cose_countersign_data(data: &[u8], p_header: &ProtectedHeader) ->
)
}
-#[allow(dead_code)]
+#[async_generic(
+ async_signature(
+ signer: &dyn AsyncSigner,
+ data: &[u8],
+ p_header: &ProtectedHeader,
+ ))]
pub(crate) fn cose_timestamp_countersign(
+ signer: &dyn Signer,
data: &[u8],
p_header: &ProtectedHeader,
- tsa_url: &str,
-) -> Result<Vec<u8>> {
+) -> Option<Result<Vec<u8>>> {
// create countersignature with TimeStampReq parameters
// payload: data
// context "CounterSigner"
@@ -58,7 +65,11 @@ pub(crate) fn cose_timestamp_countersign(
// create sig data structure to be time stamped
let sd = cose_countersign_data(data, p_header);
- timestamp_data(tsa_url, &sd)
+ if _sync {
+ timestamp_data(signer, &sd)
+ } else {
+ timestamp_data_async(signer, &sd).await
+ }
}
#[allow(dead_code)]
@@ -85,17 +96,6 @@ pub(crate) fn cose_sigtst_to_tstinfos(
}
}
-/// Get URL to Time Authority to use
-#[allow(dead_code)] // in case we make use of this later
-pub fn get_ta_url() -> Option<String> {
- //const TA_URL: &str = "http://timestamp.digicert.com";
-
- match std::env::var("CAI_TA_URL") {
- Ok(url) => Some(url),
- Err(_) => None,
- }
-}
-
/// internal only function to work around bug in serialization of TimeStampResponse
/// so we just return the data directly
#[cfg(feature = "openssl_sign")]
@@ -167,7 +167,7 @@ fn time_stamp_request_http(
/// ASN.1 request object with reasonable defaults.
#[cfg(feature = "openssl_sign")]
-fn time_stamp_message_http(
+pub(crate) fn time_stamp_message_http(
url: &str,
message: &[u8],
digest_algorithm: DigestAlgorithm,
@@ -258,9 +258,21 @@ impl TimeStampResponse {
}
}
}
+
/// Generate TimeStamp based on rfc3161 using "data" as MessageImprint and return raw TimeStampRsp bytes
+#[async_generic(async_signature(signer: &dyn AsyncSigner, data: &[u8]))]
+pub fn timestamp_data(signer: &dyn Signer, data: &[u8]) -> Option<Result<Vec<u8>>> {
+ if _sync {
+ signer.send_timestamp_request(data)
+ } else {
+ signer.send_timestamp_request(data).await
+ // TO DO: Fix bug in async_generic. This .await
+ // should be automatically removed.
+ }
+}
+
#[allow(unused_variables)]
-pub fn timestamp_data(url: &str, data: &[u8]) -> Result<Vec<u8>> {
+pub fn default_rfc3161_request(url: &str, data: &[u8]) -> Result<Vec<u8>> {
#[cfg(feature = "openssl_sign")]
{
let ts = time_stamp_message_http(url, data, x509_certificate::DigestAlgorithm::Sha256)?;
@@ -275,6 +287,7 @@ pub fn timestamp_data(url: &str, data: &[u8]) -> Result<Vec<u8>> {
Err(Error::WasmNoCrypto)
}
}
+
pub fn gt_to_datetime(
gt: x509_certificate::asn1time::GeneralizedTime,
) -> chrono::DateTime<chrono::Utc> {