Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
0d3fc02
Added implementations for Merkle proofs.
vembacher Jul 19, 2023
7dd4224
Added merkle proof examples, fixed some bugs, reduced repetition and …
vembacher Jul 21, 2023
73dd6e8
consolidated methods
vembacher Jul 26, 2023
d936abd
Added doctests to LogInfo and LogEntry merkle proof verification func…
vembacher Jul 26, 2023
6d51377
Rebased to main.
vembacher Jul 27, 2023
e45c952
Refactored to use a single library for JSON canonicalization.
vembacher Mar 20, 2024
4398466
changed usize to u64 for compatibility
vembacher Aug 19, 2024
c935f04
Added tests to `LogEntry` struct in order to additionally test inclus…
vembacher Sep 13, 2024
41a9976
added an additional test for consistency proofs and changes to data t…
vembacher Sep 13, 2024
d068671
fixed formatting error
vembacher Sep 13, 2024
c459c6a
Merge branch 'main' into feature/merkle-proofs
vembacher Sep 13, 2024
38b0829
fixed inclusion proof bug, caused by using the incorrect root hash
vembacher Sep 13, 2024
333d686
replaced ToString/FromStr trait implemenations with encode/decode fun…
vembacher Sep 13, 2024
31f0dac
added more cases to the consistency proof test suite
vembacher Sep 16, 2024
484468e
Add comprehensive test suite using transparency-dev test vectors
wolfv Nov 17, 2025
b1beb04
Merge main into feature/merkle-proofs
wolfv Nov 17, 2025
9c38592
fix merge
wolfv Nov 17, 2025
01cecd1
fix formatting of toml
wolfv Nov 17, 2025
8a887ca
run fmt
wolfv Nov 17, 2025
dc450da
use verify feature where needed
wolfv Nov 17, 2025
a7c37fd
fix cargo hack check
wolfv Nov 17, 2025
1d02a43
fmt
wolfv Nov 17, 2025
f5f38c4
merge main and move to canonicalizer
wolfv Nov 18, 2025
96f6707
fix up features
wolfv Nov 18, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,15 @@ jobs:
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
with:
persist-credentials: false

# Checkout transparency-dev/merkle test vectors for Merkle tree validation
- name: Checkout transparency-dev/merkle test vectors
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with:
repository: transparency-dev/merkle
path: tests/data/merkle
persist-credentials: false

- uses: dtolnay/rust-toolchain@351f82a4dc29e4159746a068ed925da17341219f # 1.89.0
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
- run: |
Expand All @@ -76,6 +85,15 @@ jobs:
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
with:
persist-credentials: false

# Checkout transparency-dev/merkle test vectors for Merkle tree validation
- name: Checkout transparency-dev/merkle test vectors
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with:
repository: transparency-dev/merkle
path: tests/data/merkle
persist-credentials: false

- uses: dtolnay/rust-toolchain@351f82a4dc29e4159746a068ed925da17341219f # 1.89.0
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
- run: |
Expand Down
11 changes: 10 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ native-tls = [
]
oauth = ["dep:openidconnect", "dep:reqwest"]
registry = ["dep:async-trait", "dep:oci-client", "dep:serde_json_canonicalizer"]
rekor = ["dep:reqwest"]
rekor = ["dep:hex", "dep:reqwest", "dep:serde_json_canonicalizer"]
rustls-tls = [
"oci-client?/rustls-tls",
"openidconnect?/rustls-tls",
Expand Down Expand Up @@ -217,6 +217,7 @@ clap = { version = "4.5", default-features = false, features = [
"usage",
] }
docker_credential = { version = "1.3", default-features = false }
hex-literal = { version = "0.4", default-features = false }
openssl = { version = "0.10", default-features = false }
rstest = { version = "0.26", default-features = false }
serial_test = { version = "3.1", default-features = false }
Expand Down Expand Up @@ -314,3 +315,11 @@ path = "examples/rekor/search_log_query/main.rs"
[[example]]
name = "fulcio_cert"
path = "examples/fulcio/cert/main.rs"

[[example]]
name = "inclusion_proof"
path = "examples/rekor/merkle_proofs/inclusion.rs"

[[example]]
name = "consistency_proof"
path = "examples/rekor/merkle_proofs/consistency.rs"
46 changes: 46 additions & 0 deletions examples/rekor/merkle_proofs/consistency.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
use clap::Parser;
use sigstore::crypto::CosignVerificationKey;
use sigstore::rekor::apis::configuration::Configuration;
use sigstore::rekor::apis::tlog_api::{get_log_info, get_log_proof};
use std::fs::read_to_string;
use std::path::PathBuf;

#[derive(Parser)]
struct Args {
#[arg(long, value_name = "REKOR PUBLIC KEY")]
rekor_key: PathBuf,
#[arg(long, value_name = "HEX ENCODED HASH")]
old_root: String,
#[arg(long)]
old_size: u64,
#[arg(long, value_name = "TREE ID")]
tree_id: Option<String>,
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let args = Args::parse();
let tree_id = args.tree_id.as_ref().map(|s| s.as_str());
// read verification key
let rekor_key = read_to_string(&args.rekor_key)
.map_err(Into::into)
.and_then(|k| CosignVerificationKey::from_pem(k.as_bytes(), &Default::default()))?;

// fetch log info
let rekor_config = Configuration::default();
let log_info = get_log_info(&rekor_config).await?;

let proof = get_log_proof(
&rekor_config,
log_info.tree_size as _,
Some(&args.old_size.to_string()),
tree_id,
)
.await?;

log_info
.verify_consistency(args.old_size, &args.old_root, &proof, &rekor_key)
.expect("failed to verify log consistency");
println!("Successfully verified consistency");
Ok(())
}
35 changes: 35 additions & 0 deletions examples/rekor/merkle_proofs/inclusion.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
use clap::Parser;
use sigstore::crypto::CosignVerificationKey;
use sigstore::rekor::apis::configuration::Configuration;
use sigstore::rekor::apis::entries_api::get_log_entry_by_index;
use std::fs::read_to_string;
use std::path::PathBuf;

#[derive(Parser)]
struct Args {
#[arg(long, value_name = "INDEX")]
log_index: usize,
#[arg(long, value_name = "REKOR PUBLIC KEY")]
rekor_key: PathBuf,
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let args = Args::parse();

// read verification key
let rekor_key = read_to_string(&args.rekor_key)
.map_err(Into::into)
.and_then(|k| CosignVerificationKey::from_pem(k.as_bytes(), &Default::default()))?;

// fetch entry from log
let rekor_config = Configuration::default();
let log_entry = get_log_entry_by_index(&rekor_config, args.log_index as i32).await?;

// verify inclusion with key
log_entry
.verify_inclusion(&rekor_key)
.expect("failed to verify log inclusion");
println!("Successfully verified inclusion.");
Ok(())
}
2 changes: 1 addition & 1 deletion src/bundle/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ impl TryFrom<RekorInclusionProof> for InclusionProof {
hashes,
log_index: value.log_index,
root_hash: decode_hex(value.root_hash)?,
tree_size: value.tree_size,
tree_size: value.tree_size as i64,
})
}
}
Expand Down
48 changes: 48 additions & 0 deletions src/crypto/merkle/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
pub mod proof_verification;
pub mod rfc6962;

#[cfg(test)]
mod testdata_tests;

#[cfg(any(
feature = "verify",
feature = "sign",
feature = "sigstore-trust-root",
feature = "rekor"
))]
use crate::errors::SigstoreError;
#[cfg(any(
feature = "verify",
feature = "sign",
feature = "sigstore-trust-root",
feature = "rekor"
))]
use crate::errors::SigstoreError::UnexpectedError;
#[cfg(any(
feature = "verify",
feature = "sign",
feature = "sigstore-trust-root",
feature = "rekor"
))]
use digest::Output;
pub use proof_verification::MerkleProofError;
pub(crate) use proof_verification::MerkleProofVerifier;
pub(crate) use rfc6962::{Rfc6269Default, Rfc6269HasherTrait};

/// Many rekor models have hex-encoded hashes, this functions helps to avoid repetition.
#[cfg(any(
feature = "verify",
feature = "sign",
feature = "sigstore-trust-root",
feature = "rekor"
))]
pub(crate) fn hex_to_hash_output(
h: impl AsRef<[u8]>,
) -> Result<Output<Rfc6269Default>, SigstoreError> {
hex::decode(h)
.map_err(Into::into)
.and_then(|h| {
<[u8; 32]>::try_from(h.as_slice()).map_err(|err| UnexpectedError(format!("{err:?}")))
})
.map(Into::into)
}
Loading