Skip to content

Commit

Permalink
Update create_log_entry example to create key pair.
Browse files Browse the repository at this point in the history
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 <jguilhermevanz@suse.com>
  • Loading branch information
jvanz authored and flavio committed May 30, 2023
1 parent 2500875 commit 3d6e5c7
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 22 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ serial_test = "2.0.0"
tempfile = "3.3.0"
testcontainers = "0.14"
tracing-subscriber = { version = "0.3.9", features = ["env-filter"] }
hex = "0.4.3"

# cosign example mappings

Expand Down
113 changes: 91 additions & 22 deletions examples/rekor/create_log_entry/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand All @@ -21,20 +26,38 @@ use sigstore::rekor::models::{

use clap::{Arg, Command};

async fn create_signer() -> SigStoreSigner {
SigningScheme::ECDSA_P256_SHA256_ASN1
.create_signer()
.expect("cannot create sigstore signer")
}

// 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<u8>, 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 URL constant, 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 -- \
--hash c7ead87fa5c82d2b17feece1c2ee1bda8e94788f4b208de5057b3617a42b7413\
--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:
Expand All @@ -43,15 +66,15 @@ async fn main() {
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 {
code: Some(
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",
),
},
),
Expand All @@ -61,11 +84,58 @@ 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:
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 data_job = get_file_sha256sum(URL.to_string());
let signer_job = create_signer();

let matches = Command::new("cmd")
.arg(Arg::new("hash")
.long("hash")
Expand All @@ -79,10 +149,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")
Expand All @@ -96,31 +162,34 @@ 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 PUBLIC_KEY: &str = "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFeEhUTWRSQk80ZThCcGZ3cG5KMlozT2JMRlVrVQpaUVp6WGxtKzdyd1lZKzhSMUZpRWhmS0JZclZraGpHL2lCUjZac2s3Z01iYWZPOG9FM01lUEVvWU93PT0KLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg==";
const SIGNATURE: &str = "MEUCIHWACbBnw+YkJCy2tVQd5i7VH6HgkdVBdP7HRV1IEsDuAiEA19iJNvmkE6We7iZGjHsTkjXV8QhK9iXu0ArUxvJF1N8=";
const API_VERSION: &str = "0.0.1";
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::<String>("hash")
.unwrap_or(&HASH.to_string())
.unwrap_or(&digest)
.to_owned(),
);
let data = Data::new(hash);
let public_key = PublicKey::new(
flags
.get_one::<String>("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);
Expand Down

0 comments on commit 3d6e5c7

Please sign in to comment.