From b7c0930f224bfb0fb8cb43630fb1f3b34e5c9e9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Guilherme=20Vanz?= Date: Mon, 30 Jan 2023 15:22:04 -0300 Subject: [PATCH] Update create_log_entry example to create key pair. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updates the example program create_log_entry to create the key pair used to sign and upload the data file into the Rekor transparency log. Therefore, the users do not need to create the files and generate the hashes required to create an transparency log entry. Signed-off-by: José Guilherme Vanz --- Cargo.toml | 1 + examples/rekor/create_log_entry/main.rs | 130 +++++++++++++++++++----- 2 files changed, 107 insertions(+), 24 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b8ad03584a..2a1d8fef7b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,6 +66,7 @@ tempfile = "3.3.0" tracing-subscriber = { version = "0.3.9", features = ["env-filter"] } testcontainers = "0.14" serial_test = "1.0.0" +hex = "0.4.3" # cosign example mappings diff --git a/examples/rekor/create_log_entry/main.rs b/examples/rekor/create_log_entry/main.rs index e14f210417..31512191d6 100644 --- a/examples/rekor/create_log_entry/main.rs +++ b/examples/rekor/create_log_entry/main.rs @@ -13,6 +13,11 @@ // See the License for the specific language governing permissions and // limitations under the License. +use base64::{engine::general_purpose, Engine as _}; +use sha2::Digest; +use sha2::Sha256; +use sigstore::crypto::signing_key::SigStoreSigner; +use sigstore::crypto::SigningScheme; use sigstore::rekor::apis::{configuration::Configuration, entries_api}; use sigstore::rekor::models::{ hashedrekord::{AlgorithmKind, Data, Hash, PublicKey, Signature, Spec}, @@ -21,12 +26,31 @@ use sigstore::rekor::models::{ use clap::{Arg, Command}; +async fn create_signer() -> SigStoreSigner { + SigningScheme::ECDSA_P256_SHA256_ASN1 + .create_signer() + .unwrap() +} + +// function to fetch data and generate the hash of it to be signed and upload to the transparency log +async fn get_file_sha256sum(url: String) -> Result<(Vec, String), reqwest::Error> { + let body = reqwest::get(&url).await?.bytes().await?; + let mut digester = Sha256::new(); + digester.update(body.clone()); + let digest = format!("{:x}", digester.finalize()); + Ok((body.to_vec(), digest)) +} + #[tokio::main] async fn main() { /* - Creates an entry in the transparency log for a detached signature, public key, and content. - Items can be included in the request or fetched by the server when URLs are specified. + Creates an entry in the transparency log. If no command line arguments is provided, + the pram will generate a key pair, download the file available at [TODO], sign it + and create an entry in the transparency log. In the other hand, if the user sets the + command line flags, the program will use that info to create the entry. Therefore, + if the user use information of an entry already present in the transparency log, this + program can print an error. See an example: Example command : cargo run --example create_log_entry -- \ @@ -34,16 +58,20 @@ async fn main() { --url https://raw.githubusercontent.com/jyotsna-penumaka/rekor-rs/rekor-functionality/test_data/data\ --public_key LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFeEhUTWRSQk80ZThCcGZ3cG5KMlozT2JMRlVrVQpaUVp6WGxtKzdyd1lZKzhSMUZpRWhmS0JZclZraGpHL2lCUjZac2s3Z01iYWZPOG9FM01lUEVvWU93PT0KLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg==\ --signature MEUCIHWACbBnw+YkJCy2tVQd5i7VH6HgkdVBdP7HRV1IEsDuAiEA19iJNvmkE6We7iZGjHsTkjXV8QhK9iXu0ArUxvJF1N8=\ - --key_format x509\ --api_version 0.0.1 When the example code is run with the default values, the following error message gets returned: + Data: b"This file will be used to test create_log_entry\nChange 1\n" + Data hash: c7ead87fa5c82d2b17feece1c2ee1bda8e94788f4b208de5057b3617a42b7413 + Public key base 64: LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFbjNJeUh4SDVzbDFFczVXcmFQNSs3aFUxejNLbgpGUzlOL3FQam80YVpPeW1ob29IUnBscUxCS0JQS3QvN2RoS0ZEZHBnUkJhb0ZaZXVHQ3JnUllNVU9BPT0KLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg== + Signature: 30460221008b1b96b32f8b9d6c2ae789cb692a95cd72fb59502e79899360b23d379e23c8cd02210098a045432a057a671ec21038b65be7b1e29188ccd37950abc904b832268dfcf5 + Signature base64: MEYCIQCLG5azL4udbCrnictpKpXNcvtZUC55iZNgsj03niPIzQIhAJigRUMqBXpnHsIQOLZb57HikYjM03lQq8kEuDImjfz1 Err( ResponseError( ResponseContent { status: 409, - content: "{\"code\":409,\"message\":\"An equivalent entry already exists in the transparency log with UUID aa7ba9f2eae2a0c9a40dab33c160ba41c80da877b0207b8f3243fdb4db5cf30c\"}\n", + content: "{\"code\":409,\"message\":\"An equivalent entry already exists in the transparency log with UUID 1377da9d9dbad451a5a8acdd28add750815d34e8205f1b8a35a67b8a27dae9bf\"}\n", entity: Some( Status400( Error { @@ -51,7 +79,7 @@ async fn main() { 409, ), message: Some( - "An equivalent entry already exists in the transparency log with UUID aa7ba9f2eae2a0c9a40dab33c160ba41c80da877b0207b8f3243fdb4db5cf30c", + "An equivalent entry already exists in the transparency log with UUID 1377da9d9dbad451a5a8acdd28add750815d34e8205f1b8a35a67b8a27dae9bf", ), }, ), @@ -61,11 +89,64 @@ async fn main() { ) This is because an equivalent entry with the provided meta data already exists in the transparency log. - When you use the example code to create a new entry with fresh set of input values, - you should be able to run the code without any errors. + When you use the example code to create a new entry with fresh set of input values or levaing the program + to generate the required data, you should be able to run the code without any errors. See an example: + + Example command : + cargo run --example create_log_entry -- + + The expected output will be something similar to: + Data: b"This file will be used to test create_log_entry\nChange 1\n" + Data hash: c7ead87fa5c82d2b17feece1c2ee1bda8e94788f4b208de5057b3617a42b7413 + Public key base 64: LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFb3BFbFNReldSMC9JNkhBYnorWmlValVXVGNuZQovU0RwVWVNUHFGN0QyYmNqWkJFV0xhbjNWN0wreG5ncTRGampDemtyK0Y0bW9lSDhRSmhMaEtJT3ZRPT0KLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg== + Signature: 304502200871cd755fb6bd63aca23bb489d89db199334eab957882613d78225584935c41022100e0789ede2c6ae2bf289945f4ad30968fb4609ea5e33c060d89d87d3d562402a5 + Signature base64: MEUCIAhxzXVftr1jrKI7tInYnbGZM06rlXiCYT14IlWEk1xBAiEA4Hie3ixq4r8omUX0rTCWj7RgnqXjPAYNidh9PVYkAqU= + Ok( + LogEntry { + uuid: "24296fb24b8ad77afa01e2c1f5555326e4fc32a942b40a2d798ae72a8f10c801f6e8dee771dfbacc", + attestation: None, + body: "eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiJjN2VhZDg3ZmE1YzgyZDJiMTdmZWVjZTFjMmVlMWJkYThlOTQ3ODhmNGIyMDhkZTUwNTdiMzYxN2E0MmI3NDEzIn19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FVUNJQWh4elhWZnRyMWpyS0k3dEluWW5iR1pNMDZybFhpQ1lUMTRJbFdFazF4QkFpRUE0SGllM2l4cTRyOG9tVVgwclRDV2o3UmducVhqUEFZTmlkaDlQVllrQXFVPSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCUVZVSk1TVU1nUzBWWkxTMHRMUzBLVFVacmQwVjNXVWhMYjFwSmVtb3dRMEZSV1VsTGIxcEplbW93UkVGUlkwUlJaMEZGYjNCRmJGTlJlbGRTTUM5Sk5raEJZbm9yV21sVmFsVlhWR051WlFvdlUwUndWV1ZOVUhGR04wUXlZbU5xV2tKRlYweGhiak5XTjB3cmVHNW5jVFJHYW1wRGVtdHlLMFkwYlc5bFNEaFJTbWhNYUV0SlQzWlJQVDBLTFMwdExTMUZUa1FnVUZWQ1RFbERJRXRGV1MwdExTMHRDZz09In19fX0=", + integrated_time: 1675277501, + log_i_d: "c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d", + log_index: 12425816, + verification: Verification { + inclusion_proof: Some( + InclusionProof { + hashes: [ + "a0b75928818e5fa302c3690a895e0385803a079391a79cf9f25b08a51eebc338", + "3a39532ac61bf4d3f9a982e38f1b3166a3222e9d8a081d31f67d0da745117dc5", + "96ff049c2d122233d7e44b49d5df16b0901dbf85523d90bd739a3a25d26a974c", + "09805d1e21e395d8b82e7269c7ddff2941564f925145196273a993c452059a85", + "37ca98bdb80bdc45768539d15117b2b57531b3ad1f051aaa4f58d030f868f86e", + "e81e08afa83961c36e2a6961f66859620c9ee4ed9be5631ace9f3c27c72f66fb", + "2f01e165e3758aba5fd53d0c03c88b84ccbed7334173d9159d87fed5930bfe03", + "d0509b1c0bde3ba0517efcc5d3e8d2007fe86e5e055cebd5e94307cd0394c22d", + "8a84151f9b8fbbf7b3e77cb658535ec46d27c1cdd1cab714558dc51114922e7a", + "5eb43eca7a763e2eaafb2bc2fc963e7802283cf1a9076638242177b1669942c0", + "5523cd019fea93d01834fc429f708b700aeb72c835a73161cdb9003f8f4e8072", + "4b6df664d9552bc24d48a4c7d5659a8270065e1fedbc39103b010ab235a87850", + "616429db6c7d20c5b0eff1a6e512ea57a0734b94ae0bc7c914679463e01a7fba", + "5a4ad1534b1e770f02bfde0de15008a6971cf1ffbfa963fc9c2a644973a8d2d1", + ], + log_index: 8262385, + root_hash: "41b3e1294d122b2190396de7de92731a378378ac2d7f620eb01d653838e88219", + tree_size: 8262387, + }, + ), + signed_entry_timestamp: "MEUCIG/vIwjuQoiVZtxw48KSMYyxXlpHA/y8kxYTJh46qbejAiEAyFAP5oQjxT6xFK7wKYW33sa/5wFQvqtKsdTLnitrzWA=", + }, + }, + ) */ + const URL: &str = "https://raw.githubusercontent.com/jyotsna-penumaka/rekor-rs/rekor-functionality/test_data/data"; + const API_VERSION: &str = "0.0.1"; + + //let keys_job = generate_keys(); + let data_job = get_file_sha256sum(URL.to_string()); + let signer_job = create_signer(); + let matches = Command::new("cmd") .arg(Arg::new("hash") .long("hash") @@ -79,10 +160,6 @@ async fn main() { .long("public_key") .value_name("PUBLIC_KEY") .help("base64 encoded public_key. Look at https://raw.githubusercontent.com/jyotsna-penumaka/rekor-rs/rekor-functionality/test_data/create_log_entry.md for more details on generating keys.")) - .arg(Arg::new("key_format") - .long("key_format") - .value_name("KEY_FORMAT") - .help("Accepted formats are : pgp / x509 / minsign / ssh / tuf")) .arg(Arg::new("signature") .long("signature") .value_name("SIGNATURE") @@ -96,33 +173,38 @@ async fn main() { let configuration = Configuration::default(); - // The following default values will be used if the user does not input values using cli flags - const HASH: &str = "c7ead87fa5c82d2b17feece1c2ee1bda8e94788f4b208de5057b3617a42b7413"; - const URL: &str = "https://raw.githubusercontent.com/jyotsna-penumaka/rekor-rs/rekor-functionality/test_data/data"; - const PUBLIC_KEY: &str = "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFeEhUTWRSQk80ZThCcGZ3cG5KMlozT2JMRlVrVQpaUVp6WGxtKzdyd1lZKzhSMUZpRWhmS0JZclZraGpHL2lCUjZac2s3Z01iYWZPOG9FM01lUEVvWU93PT0KLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg=="; - const SIGNATURE: &str = "MEUCIHWACbBnw+YkJCy2tVQd5i7VH6HgkdVBdP7HRV1IEsDuAiEA19iJNvmkE6We7iZGjHsTkjXV8QhK9iXu0ArUxvJF1N8="; - const KEY_FORMAT: &str = "x509"; - const API_VERSION: &str = "0.0.1"; + //let (key, public_key_base64) = keys_job.await.unwrap(); + // + // I want to panic if something went wrong. Therefore, I'll use unwrap() to + // get the job output + let (data_bytes, digest) = data_job.await.expect("Cannot get data digest"); + let signer = signer_job.await; + let public_key_base64 = general_purpose::STANDARD.encode( + signer + .to_sigstore_keypair() + .expect("Cannot get sigstore keypair") + .public_key_to_pem() + .expect("Cannot set public key"), + ); + + let sig = general_purpose::STANDARD.encode(signer.sign(&data_bytes).expect("Cannot sign data")); let hash = Hash::new( AlgorithmKind::sha256, flags .get_one::("hash") - .unwrap_or(&HASH.to_string()) + .unwrap_or(&digest) .to_owned(), ); let data = Data::new(hash); let public_key = PublicKey::new( flags .get_one::("public_key") - .unwrap_or(&PUBLIC_KEY.to_string()) + .unwrap_or(&public_key_base64) .to_owned(), ); let signature = Signature::new( - flags - .get_one("signature") - .unwrap_or(&SIGNATURE.to_string()) - .to_owned(), + flags.get_one("signature").unwrap_or(&sig).to_owned(), public_key, ); let spec = Spec::new(signature, data);