From 70f9dc32abce5b43df8bc076a299d4314329d6de Mon Sep 17 00:00:00 2001 From: Daniel Bevenius Date: Wed, 11 Jan 2023 15:01:13 +0100 Subject: [PATCH 1/2] Add cosign verify-bundle example This commit adds an example that uses cosign sign-blob with the --bundle option so that a bundle is generated. This bundle will then be used as an argument to verify-bundle, and it will be used to verify a blob. Signed-off-by: Daniel Bevenius --- Cargo.toml | 4 ++ examples/cosign/verify-bundle/.gitignore | 2 + examples/cosign/verify-bundle/README.md | 20 +++++++ examples/cosign/verify-bundle/main.rs | 67 ++++++++++++++++++++++++ examples/cosign/verify-bundle/run.sh | 17 ++++++ src/cosign/bundle.rs | 16 +++++- src/cosign/mod.rs | 2 +- 7 files changed, 126 insertions(+), 2 deletions(-) create mode 100644 examples/cosign/verify-bundle/.gitignore create mode 100644 examples/cosign/verify-bundle/README.md create mode 100644 examples/cosign/verify-bundle/main.rs create mode 100755 examples/cosign/verify-bundle/run.sh diff --git a/Cargo.toml b/Cargo.toml index 8ccda903c0..b362bca52c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -73,6 +73,10 @@ serial_test = "0.10.0" name = "verify" path = "examples/cosign/verify/main.rs" +[[example]] +name = "verify-bundle" +path = "examples/cosign/verify-bundle/main.rs" + [[example]] name = "sign" path = "examples/cosign/sign/main.rs" diff --git a/examples/cosign/verify-bundle/.gitignore b/examples/cosign/verify-bundle/.gitignore new file mode 100644 index 0000000000..4f525aecd1 --- /dev/null +++ b/examples/cosign/verify-bundle/.gitignore @@ -0,0 +1,2 @@ +artifact.bundle +artifact.txt diff --git a/examples/cosign/verify-bundle/README.md b/examples/cosign/verify-bundle/README.md new file mode 100644 index 0000000000..221f269862 --- /dev/null +++ b/examples/cosign/verify-bundle/README.md @@ -0,0 +1,20 @@ +This is a simple example that shows how perform cosign verification +using a bundle which contains everything required to verify a blob. + +### Create the artifact to be signed. +```console +echo something > artifact.txt +``` + +### Sign the artifact.txt file using cosign +``` +COSIGN_EXPERIMENTAL=1 cosign sign-blob --bundle=artifact.bundle artifact.txt +``` + +### Verify using sigstore-rs: +```console +cargo run --example verify-bundle -- \ + --rekor-pub-key ~/.sigstore/root/targets/rekor.pub \ + --bundle artifact.bundle \ + artifact.txt +``` diff --git a/examples/cosign/verify-bundle/main.rs b/examples/cosign/verify-bundle/main.rs new file mode 100644 index 0000000000..ddee7fcbc3 --- /dev/null +++ b/examples/cosign/verify-bundle/main.rs @@ -0,0 +1,67 @@ +// +// Copyright 2021 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use clap::Parser; +use sigstore::cosign::bundle::SignedArtifactBundle; +use sigstore::crypto::{CosignVerificationKey, SigningScheme}; +use std::fs; +use tracing_subscriber::prelude::*; +use tracing_subscriber::{fmt, EnvFilter}; + +#[derive(Parser, Debug)] +#[clap(author, version, about, long_about = None)] +struct Cli { + /// Path to bundle file + #[clap(short, long)] + bundle: String, + + /// Path to artifact to be verify + blob: String, + + /// File containing Rekor's public key (e.g.: ~/.sigstore/root/targets/rekor.pub) + #[clap(long, required(false))] + rekor_pub_key: String, + + /// Enable verbose mode + #[clap(short, long)] + verbose: bool, +} + +#[tokio::main] +pub async fn main() { + let cli = Cli::parse(); + + // setup logging + let level_filter = if cli.verbose { "debug" } else { "info" }; + let filter_layer = EnvFilter::new(level_filter); + tracing_subscriber::registry() + .with(filter_layer) + .with(fmt::layer().with_writer(std::io::stderr)) + .init(); + + let rekor_pub_pem = fs::read_to_string(&cli.rekor_pub_key).unwrap(); + let rekor_pub_key = + CosignVerificationKey::from_pem(rekor_pub_pem.as_bytes(), &SigningScheme::default()) + .expect("Cannot create Rekor verification key"); + let bundle_json = fs::read_to_string(&cli.bundle).unwrap(); + let blob = fs::read(&cli.blob.as_str()).unwrap(); + + let bundle = SignedArtifactBundle::new_verified(&bundle_json, &rekor_pub_key).unwrap(); + if bundle.verify_blob(&blob).is_ok() { + println!("Verification succeeded"); + } else { + println!("Verification failed"); + } +} diff --git a/examples/cosign/verify-bundle/run.sh b/examples/cosign/verify-bundle/run.sh new file mode 100755 index 0000000000..37f037a4b7 --- /dev/null +++ b/examples/cosign/verify-bundle/run.sh @@ -0,0 +1,17 @@ +BLOB="artifact.txt" +BUNDLE="artifact.bundle" + +echo -e "\nGenerate the blob to be signed" +echo something > $BLOB + +echo -e "\nSign the artifact.txt file using sign-blob" +COSIGN_EXPERIMENTAL=1 cosign sign-blob --bundle=$BUNDLE $BLOB + +echo -e "\nVerify using cosign. TODO: remove this later" +cosign verify-blob --bundle=$BUNDLE $BLOB + +echo -e "\nRun examples/cosign/verify-bundle" +cargo run --example verify-bundle -- \ + --rekor-pub-key ~/.sigstore/root/targets/rekor.pub \ + --bundle $BUNDLE \ + $BLOB diff --git a/src/cosign/bundle.rs b/src/cosign/bundle.rs index ac3de5618d..5ea8cae02a 100644 --- a/src/cosign/bundle.rs +++ b/src/cosign/bundle.rs @@ -13,9 +13,11 @@ // See the License for the specific language governing permissions and // limitations under the License. +use base64::{engine::general_purpose, Engine as _}; use olpc_cjson::CanonicalFormatter; use serde::{Deserialize, Serialize}; use std::cmp::PartialEq; +use std::convert::TryFrom; use crate::crypto::{CosignVerificationKey, Signature}; use crate::errors::{Result, SigstoreError}; @@ -44,12 +46,24 @@ impl SignedArtifactBundle { /// **Note well:** The bundle will be returned only if it can be verified /// using the supplied `rekor_pub_key` public key. #[allow(dead_code)] - pub(crate) fn new_verified(raw: &str, rekor_pub_key: &CosignVerificationKey) -> Result { + pub fn new_verified(raw: &str, rekor_pub_key: &CosignVerificationKey) -> Result { let bundle: SignedArtifactBundle = serde_json::from_str(raw).map_err(|e| { SigstoreError::UnexpectedError(format!("Cannot parse bundle |{}|: {:?}", raw, e)) })?; Bundle::verify_bundle(&bundle.rekor_bundle, rekor_pub_key).map(|_| bundle) } + + pub fn verify_blob(&self, blob: &[u8]) -> Result<()> { + let cert = general_purpose::STANDARD.decode(&self.cert)?; + let (_, pem) = x509_parser::pem::parse_x509_pem(&cert)?; + let (_, cert) = x509_parser::parse_x509_certificate(&pem.contents)?; + let subject_public_key = cert.public_key(); + let ver_key = + CosignVerificationKey::try_from(subject_public_key).expect("conversion failed"); + let signature = Signature::Base64Encoded(self.base64_signature.as_bytes()); + ver_key.verify_signature(signature, blob)?; + Ok(()) + } } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] diff --git a/src/cosign/mod.rs b/src/cosign/mod.rs index fb8ac0174d..d555249794 100644 --- a/src/cosign/mod.rs +++ b/src/cosign/mod.rs @@ -44,7 +44,7 @@ use tracing::warn; use crate::errors::{Result, SigstoreApplicationConstraintsError, SigstoreVerifyConstraintsError}; use crate::registry::{Auth, PushResponse}; -mod bundle; +pub mod bundle; pub(crate) mod constants; pub mod signature_layers; pub use signature_layers::SignatureLayer; From 126b28613c2965b5e6f0316ac50ef848ddafa98c Mon Sep 17 00:00:00 2001 From: Daniel Bevenius Date: Thu, 12 Jan 2023 15:40:21 +0100 Subject: [PATCH 2/2] squash! Add cosign verify-bundle example Address PR review feedback. Signed-off-by: Daniel Bevenius --- examples/cosign/verify-bundle/README.md | 4 ++-- examples/cosign/verify-bundle/main.rs | 11 ++++++----- src/cosign/bundle.rs | 7 ++++--- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/examples/cosign/verify-bundle/README.md b/examples/cosign/verify-bundle/README.md index 221f269862..423affcbed 100644 --- a/examples/cosign/verify-bundle/README.md +++ b/examples/cosign/verify-bundle/README.md @@ -1,5 +1,5 @@ -This is a simple example that shows how perform cosign verification -using a bundle which contains everything required to verify a blob. +This example shows how to verify a blob, using a bundle that was created by the +`cosign sign-blob` command. ### Create the artifact to be signed. ```console diff --git a/examples/cosign/verify-bundle/main.rs b/examples/cosign/verify-bundle/main.rs index ddee7fcbc3..2c49602b53 100644 --- a/examples/cosign/verify-bundle/main.rs +++ b/examples/cosign/verify-bundle/main.rs @@ -27,7 +27,7 @@ struct Cli { #[clap(short, long)] bundle: String, - /// Path to artifact to be verify + /// Path to artifact to be verified blob: String, /// File containing Rekor's public key (e.g.: ~/.sigstore/root/targets/rekor.pub) @@ -51,17 +51,18 @@ pub async fn main() { .with(fmt::layer().with_writer(std::io::stderr)) .init(); - let rekor_pub_pem = fs::read_to_string(&cli.rekor_pub_key).unwrap(); + let rekor_pub_pem = + fs::read_to_string(&cli.rekor_pub_key).expect("error reading rekor's public key"); let rekor_pub_key = CosignVerificationKey::from_pem(rekor_pub_pem.as_bytes(), &SigningScheme::default()) .expect("Cannot create Rekor verification key"); - let bundle_json = fs::read_to_string(&cli.bundle).unwrap(); - let blob = fs::read(&cli.blob.as_str()).unwrap(); + let bundle_json = fs::read_to_string(&cli.bundle).expect("error reading bundle json file"); + let blob = fs::read(&cli.blob.as_str()).expect("error reading blob file"); let bundle = SignedArtifactBundle::new_verified(&bundle_json, &rekor_pub_key).unwrap(); if bundle.verify_blob(&blob).is_ok() { println!("Verification succeeded"); } else { - println!("Verification failed"); + eprintln!("Verification failed"); } } diff --git a/src/cosign/bundle.rs b/src/cosign/bundle.rs index 5ea8cae02a..8de3e02c19 100644 --- a/src/cosign/bundle.rs +++ b/src/cosign/bundle.rs @@ -13,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use base64::{engine::general_purpose, Engine as _}; +use base64::{engine::general_purpose::STANDARD as BASE64_STD_ENGINE, Engine as _}; use olpc_cjson::CanonicalFormatter; use serde::{Deserialize, Serialize}; use std::cmp::PartialEq; @@ -45,7 +45,6 @@ impl SignedArtifactBundle { /// /// **Note well:** The bundle will be returned only if it can be verified /// using the supplied `rekor_pub_key` public key. - #[allow(dead_code)] pub fn new_verified(raw: &str, rekor_pub_key: &CosignVerificationKey) -> Result { let bundle: SignedArtifactBundle = serde_json::from_str(raw).map_err(|e| { SigstoreError::UnexpectedError(format!("Cannot parse bundle |{}|: {:?}", raw, e)) @@ -53,8 +52,10 @@ impl SignedArtifactBundle { Bundle::verify_bundle(&bundle.rekor_bundle, rekor_pub_key).map(|_| bundle) } + /// Verifies the passed-in blob against the signature in this + /// SignedArtifactBundle. pub fn verify_blob(&self, blob: &[u8]) -> Result<()> { - let cert = general_purpose::STANDARD.decode(&self.cert)?; + let cert = BASE64_STD_ENGINE.decode(&self.cert)?; let (_, pem) = x509_parser::pem::parse_x509_pem(&cert)?; let (_, cert) = x509_parser::parse_x509_certificate(&pem.contents)?; let subject_public_key = cert.public_key();