Skip to content

Commit ef9b10e

Browse files
Add support for embeddable manifests with RemoteSigner (#344)
* Add support for embeddable manifests with RemoteSigner * Fix clippy error
1 parent 02e237d commit ef9b10e

File tree

3 files changed

+163
-68
lines changed

3 files changed

+163
-68
lines changed

sdk/examples/data_hash.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ fn user_data_hash_with_sdk_hashing() {
108108

109109
// get the composed manifest ready to insert into a file (returns manifest of same length as finished manifest)
110110
let unfinished_manifest = manifest
111-
.data_hash_placeholder(signer.as_ref(), "jpg")
111+
.data_hash_placeholder(signer.reserve_size(), "jpg")
112112
.unwrap();
113113

114114
// Figure out where you want to put the manifest, let's put it at the beginning of the JPEG as first segment
@@ -225,7 +225,7 @@ fn user_data_hash_with_user_hashing() {
225225

226226
// get the composed manifest ready to insert into a file (returns manifest of same length as finished manifest)
227227
let unfinished_manifest = manifest
228-
.data_hash_placeholder(signer.as_ref(), "jpg")
228+
.data_hash_placeholder(signer.reserve_size(), "jpg")
229229
.unwrap();
230230

231231
// Figure out where you want to put the manifest, let's put it at the beginning of the JPEG as first segment

sdk/src/manifest.rs

Lines changed: 86 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1158,8 +1158,10 @@ impl Manifest {
11581158
///
11591159
/// The return value is pre-formatted for insertion into a file of the given format
11601160
/// For JPEG it is a series of App11 JPEG segments containing space for a manifest
1161-
/// This is used to create a properly formatted file ready for signing
1162-
pub fn data_hash_placeholder(&mut self, signer: &dyn Signer, format: &str) -> Result<Vec<u8>> {
1161+
/// This is used to create a properly formatted file ready for signing.
1162+
/// The reserve_size is the amount of space to reserve for the signature box. This
1163+
/// value is fixed once set and must be sufficient to hold the completed signature
1164+
pub fn data_hash_placeholder(&mut self, reserve_size: usize, format: &str) -> Result<Vec<u8>> {
11631165
let dh: Result<DataHash> = self.find_assertion(DataHash::LABEL);
11641166
if dh.is_err() {
11651167
let mut ph = DataHash::new("jumbf manifest", "sha256");
@@ -1170,8 +1172,7 @@ impl Manifest {
11701172
}
11711173

11721174
let mut store = self.to_store()?;
1173-
let placeholder =
1174-
store.get_data_hashed_manifest_placeholder(signer.reserve_size(), format)?;
1175+
let placeholder = store.get_data_hashed_manifest_placeholder(reserve_size, format)?;
11751176
Ok(placeholder)
11761177
}
11771178

@@ -1197,6 +1198,29 @@ impl Manifest {
11971198
Ok(cm)
11981199
}
11991200

1201+
/// Generates an data hashed embeddable manifest for a file
1202+
///
1203+
/// The return value is pre-formatted for insertion into a file of the given format
1204+
/// For JPEG it is a series of App11 JPEG segments containing a signed manifest
1205+
/// This can directly replace a placeholder manifest to create a properly signed asset
1206+
/// The data hash must contain exclusions and may contain pre-calculated hashes
1207+
/// if an asset reader is provided, it will be used to calculate the data hash
1208+
pub async fn data_hash_embeddable_manifest_remote(
1209+
&mut self,
1210+
dh: &DataHash,
1211+
signer: &dyn RemoteSigner,
1212+
format: &str,
1213+
mut asset_reader: Option<&mut dyn CAIRead>,
1214+
) -> Result<Vec<u8>> {
1215+
let mut store = self.to_store()?;
1216+
if let Some(asset_reader) = asset_reader.as_deref_mut() {
1217+
asset_reader.rewind()?;
1218+
}
1219+
store
1220+
.get_data_hashed_embeddable_manifest_remote(dh, signer, format, asset_reader)
1221+
.await
1222+
}
1223+
12001224
/// Generates a signed box hashed manifest, optionally preformatted for embedding
12011225
///
12021226
/// The manifest must include a box hash assertion with correct hashes
@@ -2264,7 +2288,7 @@ pub(crate) mod tests {
22642288

22652289
// get a placeholder the manifest
22662290
let placeholder = manifest
2267-
.data_hash_placeholder(signer.as_ref(), "jpeg")
2291+
.data_hash_placeholder(signer.reserve_size(), "jpeg")
22682292
.unwrap();
22692293

22702294
let temp_dir = tempfile::tempdir().unwrap();
@@ -2309,6 +2333,63 @@ pub(crate) mod tests {
23092333
assert!(manifest_store.validation_status().is_none());
23102334
}
23112335

2336+
#[cfg(all(feature = "file_io", feature = "openssl_sign"))]
2337+
#[actix::test]
2338+
async fn test_data_hash_embeddable_manifest_remote_signed() {
2339+
let ap = fixture_path("cloud.jpg");
2340+
2341+
let signer = temp_remote_signer();
2342+
2343+
let mut manifest = Manifest::new("claim_generator");
2344+
2345+
// get a placeholder the manifest
2346+
let placeholder = manifest
2347+
.data_hash_placeholder(signer.reserve_size(), "jpeg")
2348+
.unwrap();
2349+
2350+
let temp_dir = tempfile::tempdir().unwrap();
2351+
let output = temp_dir_path(&temp_dir, "boxhash-out.jpg");
2352+
let mut output_file = std::fs::OpenOptions::new()
2353+
.read(true)
2354+
.write(true)
2355+
.create(true)
2356+
.open(&output)
2357+
.unwrap();
2358+
2359+
// write a jpeg file with a placeholder for the manifest (returns offset of the placeholder)
2360+
let offset =
2361+
write_jpeg_placeholder_file(&placeholder, &ap, &mut output_file, None).unwrap();
2362+
2363+
// build manifest to insert in the hole
2364+
2365+
// create an hash exclusion for the manifest
2366+
let exclusion = HashRange::new(offset, placeholder.len());
2367+
let exclusions = vec![exclusion];
2368+
2369+
let mut dh = DataHash::new("source_hash", "sha256");
2370+
dh.exclusions = Some(exclusions);
2371+
2372+
let signed_manifest = manifest
2373+
.data_hash_embeddable_manifest_remote(
2374+
&dh,
2375+
signer.as_ref(),
2376+
"image/jpeg",
2377+
Some(&mut output_file),
2378+
)
2379+
.await
2380+
.unwrap();
2381+
2382+
use std::io::{Seek, SeekFrom, Write};
2383+
2384+
// path in new composed manifest
2385+
output_file.seek(SeekFrom::Start(offset as u64)).unwrap();
2386+
output_file.write_all(&signed_manifest).unwrap();
2387+
2388+
let manifest_store = crate::ManifestStore::from_file(&output).expect("from_file");
2389+
println!("{manifest_store}");
2390+
assert!(manifest_store.validation_status().is_none());
2391+
}
2392+
23122393
#[test]
23132394
#[cfg(feature = "file_io")]
23142395
fn test_box_hash_embeddable_manifest() {

sdk/src/store.rs

Lines changed: 75 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ use crate::{
5151
hash_utils::{hash256, HashRange},
5252
patch::patch_bytes,
5353
},
54-
validation_status, AsyncSigner, ManifestStoreReport, Signer,
54+
validation_status, AsyncSigner, ManifestStoreReport, RemoteSigner, Signer,
5555
};
5656
#[cfg(feature = "file_io")]
5757
use crate::{
@@ -1659,22 +1659,10 @@ impl Store {
16591659
Ok(composed)
16601660
}
16611661

1662-
/// Returns a finalized, signed manifest. The manfiest are only supported
1663-
/// for cases when the client has provided a data hash content hash binding. Note,
1664-
/// this function will not work for cases like BMFF where the position
1665-
/// of the content is also encoded. This function is not compatible with
1666-
/// BMFF hash binding. If a BMFF data hash or box hash is detected that is
1667-
/// an error. The DataHash placeholder assertion will be adjusted to the contain
1668-
/// the correct values. If the asset_reader value is supplied it will also perform
1669-
/// the hash calulations, otherwise the function uses the caller supplied values.
1670-
/// It is an error if `get_data_hashed_manifest_placeholder` was not called first
1671-
/// as this call inserts the DataHash placeholder assertion to reserve space for the
1672-
/// actual hash values not required when using BoxHashes.
1673-
pub fn get_data_hashed_embeddable_manifest(
1662+
fn prep_embeddable_store(
16741663
&mut self,
1664+
reserve_size: usize,
16751665
dh: &DataHash,
1676-
signer: &dyn Signer,
1677-
format: &str,
16781666
asset_reader: Option<&mut dyn CAIRead>,
16791667
) -> Result<Vec<u8>> {
16801668
let pc = self.provenance_claim_mut().ok_or(Error::ClaimEncoding)?;
@@ -1705,23 +1693,23 @@ impl Store {
17051693
// update the placeholder hash
17061694
pc.update_data_hash(adusted_dh)?;
17071695

1708-
// reborrow immuttable
1709-
let pc = self.provenance_claim().ok_or(Error::ClaimEncoding)?;
1710-
let mut jumbf_bytes = self.to_jumbf_internal(signer.reserve_size())?;
1711-
1712-
// sign contents
1713-
let sig = self.sign_claim(pc, signer, signer.reserve_size())?;
1714-
1715-
let sig_placeholder = Store::sign_claim_placeholder(pc, signer.reserve_size());
1696+
self.to_jumbf_internal(reserve_size)
1697+
}
17161698

1699+
fn finish_embeddable_store(
1700+
&mut self,
1701+
sig: &[u8],
1702+
sig_placeholder: &[u8],
1703+
jumbf_bytes: &mut Vec<u8>,
1704+
format: &str,
1705+
) -> Result<Vec<u8>> {
17171706
if sig_placeholder.len() != sig.len() {
17181707
return Err(Error::CoseSigboxTooSmall);
17191708
}
17201709

1721-
patch_bytes(&mut jumbf_bytes, &sig_placeholder, &sig)
1722-
.map_err(|_| Error::JumbfCreationError)?;
1710+
patch_bytes(jumbf_bytes, sig_placeholder, sig).map_err(|_| Error::JumbfCreationError)?;
17231711

1724-
self.get_composed_manifest(&jumbf_bytes, format)
1712+
self.get_composed_manifest(jumbf_bytes, format)
17251713
}
17261714

17271715
/// Returns a finalized, signed manifest. The manfiest are only supported
@@ -1735,60 +1723,86 @@ impl Store {
17351723
/// It is an error if `get_data_hashed_manifest_placeholder` was not called first
17361724
/// as this call inserts the DataHash placeholder assertion to reserve space for the
17371725
/// actual hash values not required when using BoxHashes.
1738-
pub async fn get_data_hashed_embeddable_manifest_async(
1726+
pub fn get_data_hashed_embeddable_manifest(
17391727
&mut self,
17401728
dh: &DataHash,
1741-
signer: &dyn AsyncSigner,
1729+
signer: &dyn Signer,
17421730
format: &str,
17431731
asset_reader: Option<&mut dyn CAIRead>,
17441732
) -> Result<Vec<u8>> {
1745-
let pc = self.provenance_claim_mut().ok_or(Error::ClaimEncoding)?;
1746-
1747-
// make sure there are data hashes present before generating
1748-
if pc.hash_assertions().is_empty() {
1749-
return Err(Error::BadParam(
1750-
"Claim must have hash binding assertion".to_string(),
1751-
));
1752-
}
1733+
let mut jumbf_bytes =
1734+
self.prep_embeddable_store(signer.reserve_size(), dh, asset_reader)?;
17531735

1754-
// don't allow BMFF assertions to be present
1755-
if !pc.bmff_hash_assertions().is_empty() {
1756-
return Err(Error::BadParam(
1757-
"BMFF assertions not supported in embeddable manifests".to_string(),
1758-
));
1759-
}
1760-
1761-
let mut adusted_dh = DataHash::new("jumbf manifest", pc.alg());
1762-
adusted_dh.exclusions = dh.exclusions.clone();
1763-
adusted_dh.hash = dh.hash.clone();
1736+
// sign contents
1737+
let pc = self.provenance_claim().ok_or(Error::ClaimEncoding)?;
1738+
let sig = self.sign_claim(pc, signer, signer.reserve_size())?;
17641739

1765-
if let Some(reader) = asset_reader {
1766-
// calc hashes
1767-
adusted_dh.gen_hash_from_stream(reader)?;
1768-
}
1740+
let sig_placeholder = Store::sign_claim_placeholder(pc, signer.reserve_size());
17691741

1770-
// update the placeholder hash
1771-
pc.update_data_hash(adusted_dh)?;
1742+
self.finish_embeddable_store(&sig, &sig_placeholder, &mut jumbf_bytes, format)
1743+
}
17721744

1773-
// reborrow immuttable
1774-
let pc = self.provenance_claim().ok_or(Error::ClaimEncoding)?;
1775-
let mut jumbf_bytes = self.to_jumbf_internal(signer.reserve_size())?;
1745+
/// Returns a finalized, signed manifest. The manfiest are only supported
1746+
/// for cases when the client has provided a data hash content hash binding. Note,
1747+
/// this function will not work for cases like BMFF where the position
1748+
/// of the content is also encoded. This function is not compatible with
1749+
/// BMFF hash binding. If a BMFF data hash or box hash is detected that is
1750+
/// an error. The DataHash placeholder assertion will be adjusted to the contain
1751+
/// the correct values. If the asset_reader value is supplied it will also perform
1752+
/// the hash calulations, otherwise the function uses the caller supplied values.
1753+
/// It is an error if `get_data_hashed_manifest_placeholder` was not called first
1754+
/// as this call inserts the DataHash placeholder assertion to reserve space for the
1755+
/// actual hash values not required when using BoxHashes.
1756+
pub async fn get_data_hashed_embeddable_manifest_async(
1757+
&mut self,
1758+
dh: &DataHash,
1759+
signer: &dyn AsyncSigner,
1760+
format: &str,
1761+
asset_reader: Option<&mut dyn CAIRead>,
1762+
) -> Result<Vec<u8>> {
1763+
let mut jumbf_bytes =
1764+
self.prep_embeddable_store(signer.reserve_size(), dh, asset_reader)?;
17761765

17771766
// sign contents
1767+
let pc = self.provenance_claim().ok_or(Error::ClaimEncoding)?;
17781768
let sig = self
17791769
.sign_claim_async(pc, signer, signer.reserve_size())
17801770
.await?;
17811771

17821772
let sig_placeholder = Store::sign_claim_placeholder(pc, signer.reserve_size());
17831773

1784-
if sig_placeholder.len() != sig.len() {
1785-
return Err(Error::CoseSigboxTooSmall);
1786-
}
1774+
self.finish_embeddable_store(&sig, &sig_placeholder, &mut jumbf_bytes, format)
1775+
}
17871776

1788-
patch_bytes(&mut jumbf_bytes, &sig_placeholder, &sig)
1789-
.map_err(|_| Error::JumbfCreationError)?;
1777+
/// Returns a finalized, signed manifest. The manfiest are only supported
1778+
/// for cases when the client has provided a data hash content hash binding. Note,
1779+
/// this function will not work for cases like BMFF where the position
1780+
/// of the content is also encoded. This function is not compatible with
1781+
/// BMFF hash binding. If a BMFF data hash or box hash is detected that is
1782+
/// an error. The DataHash placeholder assertion will be adjusted to the contain
1783+
/// the correct values. If the asset_reader value is supplied it will also perform
1784+
/// the hash calulations, otherwise the function uses the caller supplied values.
1785+
/// It is an error if `get_data_hashed_manifest_placeholder` was not called first
1786+
/// as this call inserts the DataHash placeholder assertion to reserve space for the
1787+
/// actual hash values not required when using BoxHashes.
1788+
pub async fn get_data_hashed_embeddable_manifest_remote(
1789+
&mut self,
1790+
dh: &DataHash,
1791+
signer: &dyn RemoteSigner,
1792+
format: &str,
1793+
asset_reader: Option<&mut dyn CAIRead>,
1794+
) -> Result<Vec<u8>> {
1795+
let mut jumbf_bytes =
1796+
self.prep_embeddable_store(signer.reserve_size(), dh, asset_reader)?;
1797+
1798+
// sign contents
1799+
let pc = self.provenance_claim().ok_or(Error::ClaimEncoding)?;
1800+
let claim_bytes = pc.data()?;
1801+
let sig = signer.sign_remote(&claim_bytes).await?;
1802+
1803+
let sig_placeholder = Store::sign_claim_placeholder(pc, signer.reserve_size());
17901804

1791-
self.get_composed_manifest(&jumbf_bytes, format)
1805+
self.finish_embeddable_store(&sig, &sig_placeholder, &mut jumbf_bytes, format)
17921806
}
17931807

17941808
/// Returns a finalized, signed manifest. The client is required to have

0 commit comments

Comments
 (0)