Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merkle tree proof implementation #285

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 10 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,8 @@ tempfile = "3.12"
testcontainers = "0.22"
tokio = { version = "1", features = ["rt", "rt-multi-thread"] }
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
hex = "0.4.3"
hex-literal = "0.4"

# cosign example mappings

Expand Down Expand Up @@ -244,3 +246,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 @@ -63,7 +63,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
14 changes: 6 additions & 8 deletions src/cosign/bundle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use olpc_cjson::CanonicalFormatter;
use json_syntax::Print;
use serde::{Deserialize, Serialize};
use std::cmp::PartialEq;

Expand Down Expand Up @@ -78,17 +78,15 @@ impl Bundle {
bundle: &Bundle,
rekor_pub_key: &CosignVerificationKey,
) -> Result<()> {
let mut buf = Vec::new();
let mut ser = serde_json::Serializer::with_formatter(&mut buf, CanonicalFormatter::new());
bundle.payload.serialize(&mut ser).map_err(|e| {
SigstoreError::UnexpectedError(format!(
"Cannot create canonical JSON representation of bundle: {e:?}"
))
let mut body = json_syntax::to_value(&bundle.payload).map_err(|_| {
SigstoreError::UnexpectedError("failed to serialize with json_syntax".to_string())
})?;
body.canonicalize();
let encoded = body.compact_print().to_string();

rekor_pub_key.verify_signature(
Signature::Base64Encoded(bundle.signed_entry_timestamp.as_bytes()),
&buf,
encoded.as_bytes(),
)?;
Ok(())
}
Expand Down
21 changes: 21 additions & 0 deletions src/crypto/merkle/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
pub mod proof_verification;
pub mod rfc6962;

use crate::errors::SigstoreError;
use crate::errors::SigstoreError::UnexpectedError;
use digest::Output;
pub use proof_verification::MerkleProofError;
pub(crate) use proof_verification::MerkleProofVerifier;

Check warning on line 8 in src/crypto/merkle/mod.rs

View workflow job for this annotation

GitHub Actions / Check WASM

unused import: `proof_verification::MerkleProofVerifier`
pub(crate) use rfc6962::{Rfc6269Default, Rfc6269HasherTrait};

Check warning on line 9 in src/crypto/merkle/mod.rs

View workflow job for this annotation

GitHub Actions / Check WASM

unused import: `Rfc6269HasherTrait`

/// Many rekor models have hex-encoded hashes, this functions helps to avoid repetition.
pub(crate) fn hex_to_hash_output(

Check warning on line 12 in src/crypto/merkle/mod.rs

View workflow job for this annotation

GitHub Actions / Check WASM

function `hex_to_hash_output` is never used
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
Loading