Skip to content

Commit

Permalink
chore: Move sigTst handling into c2pa-crypto (#715)
Browse files Browse the repository at this point in the history
  • Loading branch information
scouten-adobe authored Dec 5, 2024
1 parent b738cc0 commit a254623
Show file tree
Hide file tree
Showing 10 changed files with 182 additions and 92 deletions.
3 changes: 3 additions & 0 deletions internal/crypto/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ base64 = "0.22.1"
bcder = "0.7.3"
bytes = "1.7.2"
c2pa-status-tracker = { path = "../status-tracker", version = "0.1.0" }
ciborium = "0.2.2"
coset = "0.3.1"
getrandom = { version = "0.2.7", features = ["js"] }
hex = "0.4.3"
rand = "0.8.5"
Expand All @@ -44,6 +46,7 @@ rasn-ocsp = "0.18.0"
rasn-pkix = "0.18.0"
schemars = { version = "0.8.21", optional = true }
serde = { version = "1.0.197", features = ["derive"] }
serde_bytes = "0.11.5"
sha1 = "0.10.6"
sha2 = "0.10.6"
thiserror = "1.0.61"
Expand Down
35 changes: 35 additions & 0 deletions internal/crypto/src/cose/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright 2024 Adobe. All rights reserved.
// This file is licensed to you under the Apache License,
// Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
// or the MIT license (http://opensource.org/licenses/MIT),
// at your option.

// Unless required by applicable law or agreed to in writing,
// this software is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or
// implied. See the LICENSE-MIT and LICENSE-APACHE files for the
// specific language governing permissions and limitations under
// each license.

use thiserror::Error;

use crate::time_stamp::TimeStampError;

/// Describes errors that can occur when processing or generating [COSE]
/// signatures.
///
/// [COSE]: https://datatracker.ietf.org/doc/rfc9052/
#[derive(Debug, Error)]
pub enum CoseError {
/// No time stamp token found.
#[error("no time stamp token found in sigTst or sigTst2 header")]
NoTimeStampToken,

/// An error occurred while parsing CBOR.
#[error("error while parsing CBOR ({0})")]
CborParsingError(String),

/// An error occurred while parsing a time stamp.
#[error(transparent)]
TimeStampError(#[from] TimeStampError),
}
24 changes: 24 additions & 0 deletions internal/crypto/src/cose/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Copyright 2024 Adobe. All rights reserved.
// This file is licensed to you under the Apache License,
// Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
// or the MIT license (http://opensource.org/licenses/MIT),
// at your option.

// Unless required by applicable law or agreed to in writing,
// this software is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or
// implied. See the LICENSE-MIT and LICENSE-APACHE files for the
// specific language governing permissions and limitations under
// each license.

//! This module provides functions for working with [COSE] signatures.
//!
//! [COSE]: https://datatracker.ietf.org/doc/rfc9052/
mod error;
pub use error::CoseError;

mod sigtst;
pub use sigtst::{
cose_countersign_data, parse_and_validate_sigtst, parse_and_validate_sigtst_async, TstToken,
};
87 changes: 87 additions & 0 deletions internal/crypto/src/cose/sigtst.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Copyright 2022 Adobe. All rights reserved.
// This file is licensed to you under the Apache License,
// Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
// or the MIT license (http://opensource.org/licenses/MIT),
// at your option.

// Unless required by applicable law or agreed to in writing,
// this software is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or
// implied. See the LICENSE-MIT and LICENSE-APACHE files for the
// specific language governing permissions and limitations under
// each license.

use async_generic::async_generic;
use coset::{sig_structure_data, ProtectedHeader, SignatureContext};
use serde::{Deserialize, Serialize};

use crate::{
asn1::rfc3161::TstInfo,
cose::CoseError,
time_stamp::{verify_time_stamp, verify_time_stamp_async},
};

/// Parse the `sigTst` header from a COSE signature, which should contain one or
/// more `TstInfo` structures ([RFC 3161] time stamps).
///
/// Validate each time stamp and return them if valid.
///
/// [RFC 3161]: https://datatracker.ietf.org/doc/html/rfc3161
#[async_generic]
pub fn parse_and_validate_sigtst(
sigtst_cbor: &[u8],
data: &[u8],
p_header: &ProtectedHeader,
) -> Result<Vec<TstInfo>, CoseError> {
let tst_container: TstContainer = ciborium::from_reader(sigtst_cbor)
.map_err(|err| CoseError::CborParsingError(err.to_string()))?;

let mut tstinfos: Vec<TstInfo> = vec![];

for token in &tst_container.tst_tokens {
let tbs = cose_countersign_data(data, p_header);
let tst_info = if _sync {
verify_time_stamp(&token.val, &tbs)?
} else {
verify_time_stamp_async(&token.val, &tbs).await?
};

tstinfos.push(tst_info);
}

if tstinfos.is_empty() {
Err(CoseError::NoTimeStampToken)
} else {
Ok(tstinfos)
}
}

/// Given an arbitrary message and a COSE protected header, generate the binary
/// blob to be signed as part of the COSE signature.
pub fn cose_countersign_data(data: &[u8], p_header: &ProtectedHeader) -> Vec<u8> {
let aad: Vec<u8> = vec![];

sig_structure_data(
SignatureContext::CounterSignature,
p_header.clone(),
None,
&aad,
data,
)
}

/// Raw contents of an [RFC 3161] time stamp.
///
/// [RFC 3161]: https://datatracker.ietf.org/doc/html/rfc3161
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct TstToken {
#[allow(missing_docs)]
#[serde(with = "serde_bytes")]
pub val: Vec<u8>,
}

#[derive(Debug, Deserialize, Eq, PartialEq, Serialize)]
struct TstContainer {
#[serde(rename = "tstTokens")]
tst_tokens: Vec<TstToken>,
}
1 change: 1 addition & 0 deletions internal/crypto/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

pub mod asn1;
pub mod base64;
pub mod cose;
pub mod hash;
pub(crate) mod internal;
pub mod ocsp;
Expand Down
2 changes: 1 addition & 1 deletion internal/crypto/src/time_stamp/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ pub enum TimeStampError {
#[error("unable to complete HTTP request ({0})")]
HttpConnectionError(String),

/// An unexpected internal error occured whiel requesting the time stamp
/// An unexpected internal error occured while requesting the time stamp
/// response.
#[error("internal error ({0})")]
InternalError(String),
Expand Down
2 changes: 1 addition & 1 deletion sdk/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ chrono = { version = "0.4.38", default-features = false, features = [
"serde",
"wasmbind",
] }
ciborium = "0.2.0"
ciborium = "0.2.2"
config = { version = "0.14.0", default-features = false, features = [
"json",
"json5",
Expand Down
6 changes: 3 additions & 3 deletions sdk/src/cose_validator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use asn1_rs::{Any, Class, Header, Tag};
use async_generic::async_generic;
use c2pa_crypto::{
asn1::rfc3161::TstInfo,
cose::{parse_and_validate_sigtst, parse_and_validate_sigtst_async},
ocsp::OcspResponse,
p1363::parse_ec_der_sig,
raw_signature::{validator_for_signing_alg, RawSignatureValidator},
Expand Down Expand Up @@ -811,10 +812,9 @@ fn get_timestamp_info(sign1: &coset::CoseSign1, data: &[u8]) -> Result<TstInfo>
{
let time_cbor = serde_cbor::to_vec(t)?;
let tst_infos = if _sync {
crate::time_stamp::cose_sigtst_to_tstinfos(&time_cbor, data, &sign1.protected)?
parse_and_validate_sigtst(&time_cbor, data, &sign1.protected)?
} else {
crate::time_stamp::cose_sigtst_to_tstinfos_async(&time_cbor, data, &sign1.protected)
.await?
parse_and_validate_sigtst_async(&time_cbor, data, &sign1.protected).await?
};

// there should only be one but consider handling more in the future since it is technically ok
Expand Down
10 changes: 10 additions & 0 deletions sdk/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -327,3 +327,13 @@ impl From<c2pa_crypto::webcrypto::WasmCryptoError> for Error {
}
}
}

impl From<c2pa_crypto::cose::CoseError> for Error {
fn from(err: c2pa_crypto::cose::CoseError) -> Self {
match err {
c2pa_crypto::cose::CoseError::NoTimeStampToken => Self::NotFound,
c2pa_crypto::cose::CoseError::CborParsingError(_) => Self::CoseTimeStampGeneration,
c2pa_crypto::cose::CoseError::TimeStampError(e) => e.into(),
}
}
}
104 changes: 17 additions & 87 deletions sdk/src/time_stamp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,34 +12,11 @@
// each license.

use async_generic::async_generic;
use c2pa_crypto::{
asn1::rfc3161::TstInfo,
time_stamp::{verify_time_stamp, verify_time_stamp_async},
};
use coset::{sig_structure_data, ProtectedHeader};
use c2pa_crypto::cose::{cose_countersign_data, TstToken};
use coset::ProtectedHeader;
use serde::{Deserialize, Serialize};

use crate::{
error::{Error, Result},
AsyncSigner, Signer,
};

// Generate TimeStamp signature according to https://datatracker.ietf.org/doc/html/rfc3161
// using the specified Time Authority

#[allow(dead_code)]
pub(crate) fn cose_countersign_data(data: &[u8], p_header: &ProtectedHeader) -> Vec<u8> {
let aad: Vec<u8> = Vec::new();

// create sig_structure_data to be signed
sig_structure_data(
coset::SignatureContext::CounterSignature,
p_header.clone(),
None,
&aad,
data,
)
}
use crate::{error::Result, AsyncSigner, Signer};

#[async_generic(
async_signature(
Expand Down Expand Up @@ -68,38 +45,9 @@ pub(crate) fn cose_timestamp_countersign(
}
}

#[async_generic]
pub(crate) fn cose_sigtst_to_tstinfos(
sigtst_cbor: &[u8],
data: &[u8],
p_header: &ProtectedHeader,
) -> Result<Vec<TstInfo>> {
let tst_container: TstContainer =
serde_cbor::from_slice(sigtst_cbor).map_err(|_err| Error::CoseTimeStampGeneration)?;

let mut tstinfos: Vec<TstInfo> = Vec::new();

for token in &tst_container.tst_tokens {
let tbs = cose_countersign_data(data, p_header);
let tst_info = if _sync {
verify_time_stamp(&token.val, &tbs)?
} else {
verify_time_stamp_async(&token.val, &tbs).await?
};

tstinfos.push(tst_info);
}

if tstinfos.is_empty() {
Err(Error::NotFound)
} else {
Ok(tstinfos)
}
}

// 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>>> {
fn timestamp_data(signer: &dyn Signer, data: &[u8]) -> Option<Result<Vec<u8>>> {
if _sync {
signer
.send_time_stamp_request(data)
Expand All @@ -114,44 +62,26 @@ pub fn timestamp_data(signer: &dyn Signer, data: &[u8]) -> Option<Result<Vec<u8>
}
}

#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone)]
pub struct TstToken {
#[serde(with = "serde_bytes")]
pub val: Vec<u8>,
}

#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone)]
pub struct TstContainer {
#[serde(rename = "tstTokens")]
pub tst_tokens: Vec<TstToken>,
}

impl TstContainer {
pub fn new() -> Self {
TstContainer {
tst_tokens: Vec::new(),
}
}

pub fn add_token(&mut self, token: TstToken) {
self.tst_tokens.push(token);
}
}

impl Default for TstContainer {
fn default() -> Self {
Self::new()
}
}

// Wrap rfc3161 TimeStampRsp in COSE sigTst object
pub(crate) fn make_cose_timestamp(ts_data: &[u8]) -> TstContainer {
let token = TstToken {
val: ts_data.to_vec(),
};

let mut container = TstContainer::new();
let mut container = TstContainer::default();
container.add_token(token);

container
}

#[derive(Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
pub(crate) struct TstContainer {
#[serde(rename = "tstTokens")]
pub(crate) tst_tokens: Vec<TstToken>,
}

impl TstContainer {
pub fn add_token(&mut self, token: TstToken) {
self.tst_tokens.push(token);
}
}

0 comments on commit a254623

Please sign in to comment.