commit d51f33cd96f2c7ab4e5b9d9c1804cb2213cfaee1
parent acb7a9d6c7554fc8f4cb0c0884c471473b095a6e
Author: mauricefisher64 <92736594+mauricefisher64@users.noreply.github.com>
Date: Mon, 12 Jun 2023 10:42:03 -0400
Update Timestamp message imprint to include entire protected header (#264)
* Initial box hash support
* Full box hash implementation for JPG
* Box hash fixes
* More unit tests and safety checks
* Formatting fix
* Another format fix
* More error checking
* Add API to get embeddable manifest
* PR feedback
* Add missing test file
* Change timestamp message imprint to include entire protected header
* Format fixes
* Fix for mixing cbor and json in a single object
* cleanup
* Simplify fix to use existing code
Diffstat:
7 files changed, 73 insertions(+), 66 deletions(-)
diff --git a/sdk/src/assertion.rs b/sdk/src/assertion.rs
@@ -323,6 +323,7 @@ impl Assertion {
/// Return assertion as serde_json Object
/// this may have loss of cbor structure if unsupported in conversion to json
+ /// It should always do the correct thing when using the correct tagged CBOR types
pub(crate) fn as_json_object(&self) -> AssertionDecodeResult<Value> {
match self.decode_data() {
AssertionData::Json(x) => serde_json::from_str(x)
diff --git a/sdk/src/assertions/actions.rs b/sdk/src/assertions/actions.rs
@@ -14,7 +14,7 @@
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
-use serde_json::Value;
+use serde_cbor::Value;
use crate::{
assertion::{Assertion, AssertionBase, AssertionCbor},
@@ -22,7 +22,7 @@ use crate::{
error::Result,
resource_store::UriOrResource,
utils::cbor_types::DateT,
- ClaimGeneratorInfo, Error,
+ ClaimGeneratorInfo,
};
const ASSERTION_CREATION_VERSION: usize = 2;
@@ -263,7 +263,9 @@ impl Action {
key: S,
value: T,
) -> Result<Self> {
- let value = serde_json::to_value(value).map_err(|_| Error::AssertionEncoding)?;
+ let value_bytes = serde_cbor::ser::to_vec(&value)?;
+ let value = serde_cbor::from_slice(&value_bytes)?;
+
self.parameters = Some(match self.parameters {
Some(mut parameters) => {
parameters.insert(key.into(), value);
@@ -422,9 +424,17 @@ impl Actions {
self
}
- /// Creates an [`Actions`] assertion from a compatible JSON value.
+ /// Creates a CBOR [`Actions`] assertion from a compatible JSON value.
pub fn from_json_value(json: &serde_json::Value) -> Result<Self> {
- let actions: Actions = serde_json::from_value(json.clone())?;
+ let buf: Vec<u8> = Vec::new();
+ let json_str = json.to_string();
+ let mut from = serde_json::Deserializer::from_str(&json_str);
+ let mut to = serde_cbor::Serializer::new(buf);
+
+ serde_transcode::transcode(&mut from, &mut to)?;
+ let buf2 = to.into_inner();
+
+ let actions: Actions = serde_cbor::from_slice(&buf2)?;
Ok(actions)
}
}
diff --git a/sdk/src/assertions/box_hash.rs b/sdk/src/assertions/box_hash.rs
@@ -17,12 +17,11 @@ use serde::{Deserialize, Serialize};
use serde_bytes::ByteBuf;
use crate::{
- assertion::{Assertion, AssertionBase, AssertionCbor},
+ assertion::{Assertion, AssertionBase, AssertionCbor, AssertionJson},
assertions::labels,
asset_io::{AssetBoxHash, CAIRead},
error::{Error, Result},
utils::hash_utils::{hash_stream_by_alg, verify_stream_by_alg, HashRange},
- AssertionJson,
};
const ASSERTION_CREATION_VERSION: usize = 1;
@@ -291,8 +290,6 @@ impl AssertionBase for BoxHash {
const LABEL: &'static str = Self::LABEL;
const VERSION: Option<usize> = Some(ASSERTION_CREATION_VERSION);
- // todo: this mechanism needs to change since a struct could support different versions
-
fn to_assertion(&self) -> crate::error::Result<Assertion> {
Self::to_cbor_assertion(self)
}
@@ -416,7 +413,7 @@ mod tests {
bh.generate_box_hash_from_stream(&mut input, "sha256", bhp, true)
.unwrap();
- // save and reload JSOH
+ // save and reload JSON
let bh_json_assertion = bh.to_json_assertion().unwrap();
println!("Box hash json: {:?}", bh_json_assertion.decode_data());
@@ -427,4 +424,33 @@ mod tests {
.verify_stream_hash(&mut input, Some("sha256"), bhp)
.unwrap();
}
+
+ #[test]
+ fn test_cbor_round_trop() {
+ let ap = fixture_path("CA.jpg");
+
+ let bhp = get_assetio_handler_from_path(&ap)
+ .unwrap()
+ .asset_box_hash_ref()
+ .unwrap();
+
+ let mut input = File::open(&ap).unwrap();
+
+ let mut bh = BoxHash { boxes: Vec::new() };
+
+ // generate box hashes
+ bh.generate_box_hash_from_stream(&mut input, "sha256", bhp, true)
+ .unwrap();
+
+ // save and reload CBOR
+ let bh_cbor_assertion = bh.to_cbor_assertion().unwrap();
+ println!("Box hash cbor: {:?}", bh_cbor_assertion.decode_data());
+
+ let reloaded_bh = BoxHash::from_cbor_assertion(&bh_cbor_assertion).unwrap();
+
+ // see if they match reading
+ reloaded_bh
+ .verify_stream_hash(&mut input, Some("sha256"), bhp)
+ .unwrap();
+ }
}
diff --git a/sdk/src/cose_sign.rs b/sdk/src/cose_sign.rs
@@ -18,7 +18,8 @@
use ciborium::value::Value;
use coset::{
iana::{self, EnumI64},
- CoseSign1, CoseSign1Builder, Header, HeaderBuilder, Label, TaggedCborSerializable,
+ CoseSign1, CoseSign1Builder, Header, HeaderBuilder, Label, ProtectedHeader,
+ TaggedCborSerializable,
};
use crate::{
@@ -222,9 +223,17 @@ fn build_headers(
iana::HeaderParameter::X5Chain.to_i64(),
sc_der_array_or_bytes,
);
+ let protected_header = protected_h.build();
let mut unprotected_h = if let Some(url) = ta_url {
- let cts = cose_timestamp_countersign(data, alg, &url)?;
+ let cts = cose_timestamp_countersign(
+ data,
+ &ProtectedHeader {
+ original_data: None,
+ header: protected_header.clone(),
+ },
+ &url,
+ )?;
let sigtst_vec = serde_cbor::to_vec(&make_cose_timestamp(&cts))?;
let sigtst_cbor = serde_cbor::from_slice(&sigtst_vec)?;
@@ -245,7 +254,6 @@ fn build_headers(
}
// build complete header
- let protected_header = protected_h.build();
let unprotected_header = unprotected_h.build();
Ok((protected_header, unprotected_header))
diff --git a/sdk/src/cose_validator.rs b/sdk/src/cose_validator.rs
@@ -696,9 +696,9 @@ fn get_timestamp_info(sign1: &coset::CoseSign1, data: &[u8]) -> Result<TstInfo>
}
})
{
- let alg = get_signing_alg(sign1)?;
let time_cbor = serde_cbor::to_vec(t)?;
- let tst_infos = crate::time_stamp::cose_sigtst_to_tstinfos(&time_cbor, data, alg)?;
+ let tst_infos =
+ crate::time_stamp::cose_sigtst_to_tstinfos(&time_cbor, data, &sign1.protected)?;
// there should only be one but consider handling more in the future since it is technically ok
if !tst_infos.is_empty() {
diff --git a/sdk/src/manifest.rs b/sdk/src/manifest.rs
@@ -25,7 +25,7 @@ use uuid::Uuid;
#[cfg(feature = "file_io")]
use crate::AsyncSigner;
use crate::{
- assertion::{AssertionBase, AssertionData, AssertionDecodeError},
+ assertion::{AssertionBase, AssertionData},
assertions::{labels, Actions, CreativeWork, Exif, SoftwareAgent, Thumbnail, User, UserCbor},
asset_io::CAIRead,
claim::{Claim, RemoteManifest},
@@ -638,7 +638,7 @@ impl Manifest {
)?;
manifest.add_ingredient(ingredient);
}
- labels::DATA_HASH | labels::BMFF_HASH => {
+ labels::DATA_HASH | labels::BMFF_HASH | labels::BOX_HASH => {
// do not include data hash when reading manifests
}
label if label.starts_with(labels::CLAIM_THUMBNAIL) => {
@@ -648,24 +648,15 @@ impl Manifest {
_ => {
// inject assertions for all other assertions
match assertion.decode_data() {
- AssertionData::Json(x) => {
- let value = serde_json::from_str(x).map_err(|e| {
- AssertionDecodeError::from_assertion_and_json_err(assertion, e)
- })?;
+ AssertionData::Json(_) | AssertionData::Cbor(_) => {
+ let value = assertion.as_json_object()?;
let ma = ManifestAssertion::new(base_label, value)
.set_instance(claim_assertion.instance())
.set_kind(ManifestAssertionKind::Json);
+
manifest.assertions.push(ma);
}
- AssertionData::Cbor(x) => {
- let value: Value =
- serde_cbor::from_slice(x.as_slice()).map_err(|e| {
- AssertionDecodeError::from_assertion_and_cbor_err(assertion, e)
- })?;
- let ma = ManifestAssertion::new(base_label, value)
- .set_instance(claim_assertion.instance());
- manifest.assertions.push(ma);
- }
+
// todo: support binary forms
AssertionData::Binary(_x) => {}
AssertionData::Uuid(_, _) => {}
diff --git a/sdk/src/time_stamp.rs b/sdk/src/time_stamp.rs
@@ -14,7 +14,7 @@
use std::convert::TryFrom;
use bcder::decode::Constructed;
-use coset::{iana, sig_structure_data, HeaderBuilder, ProtectedHeader};
+use coset::{sig_structure_data, ProtectedHeader};
use serde::{Deserialize, Serialize};
use x509_certificate::DigestAlgorithm::{self};
@@ -27,45 +27,16 @@ use crate::{
rfc5652::{CertificateChoices::Certificate, SignedData, OID_ID_SIGNED_DATA},
},
hash_utils::vec_compare,
- SigningAlg,
};
#[allow(dead_code)]
-pub(crate) fn cose_countersign_data(data: &[u8], alg: SigningAlg) -> Vec<u8> {
- let alg_id = match alg {
- SigningAlg::Ps256 => HeaderBuilder::new()
- .algorithm(iana::Algorithm::PS256)
- .build(),
- SigningAlg::Ps384 => HeaderBuilder::new()
- .algorithm(iana::Algorithm::PS384)
- .build(),
- SigningAlg::Ps512 => HeaderBuilder::new()
- .algorithm(iana::Algorithm::PS512)
- .build(),
- SigningAlg::Es256 => HeaderBuilder::new()
- .algorithm(iana::Algorithm::ES256)
- .build(),
- SigningAlg::Es384 => HeaderBuilder::new()
- .algorithm(iana::Algorithm::ES384)
- .build(),
- SigningAlg::Es512 => HeaderBuilder::new()
- .algorithm(iana::Algorithm::ES512)
- .build(),
- SigningAlg::Ed25519 => HeaderBuilder::new()
- .algorithm(iana::Algorithm::EdDSA)
- .build(),
- };
-
- let p_header = ProtectedHeader {
- original_data: None,
- header: alg_id,
- };
+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,
+ p_header.clone(),
None,
&aad,
data,
@@ -75,7 +46,7 @@ pub(crate) fn cose_countersign_data(data: &[u8], alg: SigningAlg) -> Vec<u8> {
#[allow(dead_code)]
pub(crate) fn cose_timestamp_countersign(
data: &[u8],
- alg: SigningAlg,
+ p_header: &ProtectedHeader,
tsa_url: &str,
) -> Result<Vec<u8>> {
// create countersignature with TimeStampReq parameters
@@ -85,7 +56,7 @@ pub(crate) fn cose_timestamp_countersign(
// algorithm sha256
// create sig data structure to be time stamped
- let sd = cose_countersign_data(data, alg);
+ let sd = cose_countersign_data(data, p_header);
timestamp_data(tsa_url, &sd)
}
@@ -94,7 +65,7 @@ pub(crate) fn cose_timestamp_countersign(
pub(crate) fn cose_sigtst_to_tstinfos(
sigtst_cbor: &[u8],
data: &[u8],
- alg: SigningAlg,
+ p_header: &ProtectedHeader,
) -> Result<Vec<TstInfo>> {
let tst_container: TstContainer =
serde_cbor::from_slice(sigtst_cbor).map_err(|_err| Error::CoseTimeStampGeneration)?;
@@ -102,7 +73,7 @@ pub(crate) fn cose_sigtst_to_tstinfos(
let mut tstinfos: Vec<TstInfo> = Vec::new();
for token in &tst_container.tst_tokens {
- let tbs = cose_countersign_data(data, alg);
+ let tbs = cose_countersign_data(data, p_header);
let tst_info = verify_timestamp(&token.val, &tbs)?;
tstinfos.push(tst_info);
}