From b45a7c129dbd6c9c9dcdb37a5cf217f30837e0d0 Mon Sep 17 00:00:00 2001 From: Daniel Bevenius Date: Fri, 25 Nov 2022 14:49:14 +0100 Subject: [PATCH] Rename Bundle to RekorBundle and modify Bundle This commit renames the Bundle struct to RekorBundle and modifies the existing Bundle struct to contain the rekor_bundle, in addition to a base64_signature, and cert field. The motivation for change comes from trying to implement an example that verifies a blob using a bundle. For example, first a blob is signed using the following command: cosign sign-blob --bundle=artifact.bundle artifact.txt The `artifact.bundle` file generated by the above command will look something like this (shortened to fit the commit message format): { "base64Signature": "...", "cert": "...", "rekorBundle": { "SignedEntryTimestamp": "...", "Payload": { "body": "...", "integratedTime": 1669361833, "logIndex": 7810348, "logID": "..." } } } Currently, to create Bundle (which is called RekorBundle in this commit) from this, one would have to parse the string as json, and then access the `rekorBundle` element, and then serialize it so that it can be passed to `Bundle::new_verified` (again RekorBundle in this commit). With the changes in this commit it will be possible to call `Bundle::new_verified` and pass in the contents for the bundle file directly. Refs: https://github.com/sigstore/sigstore-rs/issues/117 Signed-off-by: Daniel Bevenius --- src/cosign/bundle.rs | 68 +++++++++++++++---- src/cosign/signature_layers.rs | 23 ++++--- .../certificate_verifier.rs | 7 +- 3 files changed, 72 insertions(+), 26 deletions(-) diff --git a/src/cosign/bundle.rs b/src/cosign/bundle.rs index 4d661a94fb..80d66cc395 100644 --- a/src/cosign/bundle.rs +++ b/src/cosign/bundle.rs @@ -20,23 +20,52 @@ use std::cmp::PartialEq; use crate::crypto::{CosignVerificationKey, Signature}; use crate::errors::{Result, SigstoreError}; +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)] +pub struct Bundle { + #[serde(rename(deserialize = "base64Signature"))] + pub base64_signature: String, + pub cert: String, + #[serde(rename(deserialize = "rekorBundle"))] + pub rekor_bundle: RekorBundle, +} + +impl Bundle { + #[allow(dead_code)] + pub(crate) fn new_verified(raw: &str, rekor_pub_key: &CosignVerificationKey) -> Result { + let bundle: Bundle = serde_json::from_str(raw).map_err(|e| { + SigstoreError::UnexpectedError(format!("Cannot parse bundle |{}|: {:?}", raw, e)) + })?; + RekorBundle::verify_bundle(&bundle.rekor_bundle, rekor_pub_key).map(|_| bundle) + } +} + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "PascalCase")] -pub struct Bundle { +pub struct RekorBundle { pub signed_entry_timestamp: String, pub payload: Payload, } -impl Bundle { - /// Create a new verified `Bundle` +impl RekorBundle { + /// Create a new verified `RekorBundle` /// /// **Note well:** The bundle will be returned only if it can be verified /// using the supplied `rekor_pub_key` public key. pub(crate) fn new_verified(raw: &str, rekor_pub_key: &CosignVerificationKey) -> Result { - let bundle: Bundle = serde_json::from_str(raw).map_err(|e| { + let bundle: RekorBundle = serde_json::from_str(raw).map_err(|e| { SigstoreError::UnexpectedError(format!("Cannot parse bundle |{}|: {:?}", raw, e)) })?; + Self::verify_bundle(&bundle, rekor_pub_key).map(|_| bundle) + } + /// Verify a `RekorBundle`. + /// + /// **Note well:** The bundle will be returned only if it can be verified + /// using the supplied `rekor_pub_key` public key. + pub(crate) fn verify_bundle( + bundle: &RekorBundle, + 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| { @@ -50,7 +79,7 @@ impl Bundle { Signature::Base64Encoded(bundle.signed_entry_timestamp.as_bytes()), &buf, )?; - Ok(bundle) + Ok(()) } } @@ -72,7 +101,7 @@ mod tests { use crate::cosign::tests::get_rekor_public_key; use crate::crypto::SigningScheme; - fn build_correct_bundle() -> String { + fn build_correct_rekor_bundle() -> String { let bundle_json = json!({ "SignedEntryTimestamp": "MEUCIDx9M+yRpD0O47/Mzm8NAPCbtqy4uiTkLWWexW0bo4jZAiEA1wwueIW8XzJWNkut5y9snYj7UOfbMmUXp7fH3CzJmWg=", "Payload": { @@ -86,17 +115,17 @@ mod tests { } #[test] - fn bundle_new_verified_success() { + fn rekor_bundle_new_verified_success() { let rekor_pub_key = get_rekor_public_key(); - let bundle_json = build_correct_bundle(); - let bundle = Bundle::new_verified(&bundle_json, &rekor_pub_key); + let bundle_json = build_correct_rekor_bundle(); + let bundle = RekorBundle::new_verified(&bundle_json, &rekor_pub_key); assert!(bundle.is_ok()); } #[test] - fn bundle_new_verified_failure() { + fn rekor_bundle_new_verified_failure() { let public_key = r#"-----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAENptdY/l3nB0yqkXLBWkZWQwo6+cu OSWS1X9vPavpiQOoTTGC0xX57OojUadxF1cdQmrsiReWg2Wn4FneJfa8xw== @@ -105,9 +134,24 @@ OSWS1X9vPavpiQOoTTGC0xX57OojUadxF1cdQmrsiReWg2Wn4FneJfa8xw== CosignVerificationKey::from_pem(public_key.as_bytes(), &SigningScheme::default()) .expect("Cannot create CosignVerificationKey"); - let bundle_json = build_correct_bundle(); - let bundle = Bundle::new_verified(&bundle_json, ¬_rekor_pub_key); + let bundle_json = build_correct_rekor_bundle(); + let bundle = RekorBundle::new_verified(&bundle_json, ¬_rekor_pub_key); assert!(bundle.is_err()); } + + #[test] + fn bundle_new_verified_success() { + // Bundle as generated by running the following command, and taking the + // content from the generated 'artifact.bundle` file: + // cosign sign-blob --bundle=artifact.bundle artifact.txt + let bundle_raw = r#" +{"base64Signature":"MEQCIGp1XZP5zaImosrBhDPCdXn3f8xI9FHGLsGVx6UeRPCgAiAt5GrsdQhOKnZcA3EWecvgJSHzCIjWifFBQkD7Hdsymg==","cert":"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNxRENDQWkrZ0F3SUJBZ0lVVFBXVGZPLzFOUmFTRmRlY2FBUS9wQkRHSnA4d0NnWUlLb1pJemowRUF3TXcKTnpFVk1CTUdBMVVFQ2hNTWMybG5jM1J2Y21VdVpHVjJNUjR3SEFZRFZRUURFeFZ6YVdkemRHOXlaUzFwYm5SbApjbTFsWkdsaGRHVXdIaGNOTWpJeE1USTFNRGN6TnpFeVdoY05Nakl4TVRJMU1EYzBOekV5V2pBQU1Ga3dFd1lICktvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUVKUVE0Vy81WFA5bTRZYldSQlF0SEdXd245dVVoYWUzOFVwY0oKcEVNM0RPczR6VzRNSXJNZlc0V1FEMGZ3cDhQVVVSRFh2UTM5NHBvcWdHRW1Ta3J1THFPQ0FVNHdnZ0ZLTUE0RwpBMVVkRHdFQi93UUVBd0lIZ0RBVEJnTlZIU1VFRERBS0JnZ3JCZ0VGQlFjREF6QWRCZ05WSFE0RUZnUVVvM0tuCmpKUVowWGZpZ2JENWIwT1ZOTjB4cVNvd0h3WURWUjBqQkJnd0ZvQVUzOVBwejFZa0VaYjVxTmpwS0ZXaXhpNFkKWkQ4d0p3WURWUjBSQVFIL0JCMHdHNEVaWkdGdWFXVnNMbUpsZG1WdWFYVnpRR2R0WVdsc0xtTnZiVEFzQmdvcgpCZ0VFQVlPL01BRUJCQjVvZEhSd2N6b3ZMMmRwZEdoMVlpNWpiMjB2Ykc5bmFXNHZiMkYxZEdnd2dZc0dDaXNHCkFRUUIxbmtDQkFJRWZRUjdBSGtBZHdEZFBUQnF4c2NSTW1NWkhoeVpaemNDb2twZXVONDhyZitIaW5LQUx5bnUKamdBQUFZU3R1Qkh5QUFBRUF3QklNRVlDSVFETTVZU1EvR0w2S0k1UjlPZGNuL3BTaytxVkQ2YnNMODMrRXA5UgoyaFdUYXdJaEFLMWppMWxaNTZEc2Z1TGZYN2JCQzluYlIzRWx4YWxCaHYxelFYTVU3dGx3TUFvR0NDcUdTTTQ5CkJBTURBMmNBTUdRQ01CSzh0c2dIZWd1aCtZaGVsM1BpakhRbHlKMVE1SzY0cDB4cURkbzdXNGZ4Zm9BUzl4clAKczJQS1FjZG9EOWJYd2dJd1g2ekxqeWJaa05IUDV4dEJwN3ZLMkZZZVp0ME9XTFJsVWxsY1VETDNULzdKUWZ3YwpHU3E2dlZCTndKMDB3OUhSCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K","rekorBundle":{"SignedEntryTimestamp":"MEUCIC3c+21v9pk6o4BpB/dRAM9lGnyWLi3Xnc+i8LmnNJmeAiEAiqZJbZHx3Idnw+zXv6yM0ipPw/p16R28YGuCJFQ1u8U=","Payload":{"body":"eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiI0YmM0NTNiNTNjYjNkOTE0YjQ1ZjRiMjUwMjk0MjM2YWRiYTJjMGUwOWZmNmYwMzc5Mzk0OWU3ZTM5ZmQ0Y2MxIn19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FUUNJR3AxWFpQNXphSW1vc3JCaERQQ2RYbjNmOHhJOUZIR0xzR1Z4NlVlUlBDZ0FpQXQ1R3JzZFFoT0tuWmNBM0VXZWN2Z0pTSHpDSWpXaWZGQlFrRDdIZHN5bWc9PSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVTnhSRU5EUVdrclowRjNTVUpCWjBsVlZGQlhWR1pQTHpGT1VtRlRSbVJsWTJGQlVTOXdRa1JIU25BNGQwTm5XVWxMYjFwSmVtb3dSVUYzVFhjS1RucEZWazFDVFVkQk1WVkZRMmhOVFdNeWJHNWpNMUoyWTIxVmRWcEhWakpOVWpSM1NFRlpSRlpSVVVSRmVGWjZZVmRrZW1SSE9YbGFVekZ3WW01U2JBcGpiVEZzV2tkc2FHUkhWWGRJYUdOT1RXcEplRTFVU1RGTlJHTjZUbnBGZVZkb1kwNU5ha2w0VFZSSk1VMUVZekJPZWtWNVYycEJRVTFHYTNkRmQxbElDa3R2V2tsNmFqQkRRVkZaU1V0dldrbDZhakJFUVZGalJGRm5RVVZLVVZFMFZ5ODFXRkE1YlRSWllsZFNRbEYwU0VkWGQyNDVkVlZvWVdVek9GVndZMG9LY0VWTk0wUlBjelI2VnpSTlNYSk5abGMwVjFGRU1HWjNjRGhRVlZWU1JGaDJVVE01TkhCdmNXZEhSVzFUYTNKMVRIRlBRMEZWTkhkblowWkxUVUUwUndwQk1WVmtSSGRGUWk5M1VVVkJkMGxJWjBSQlZFSm5UbFpJVTFWRlJFUkJTMEpuWjNKQ1owVkdRbEZqUkVGNlFXUkNaMDVXU0ZFMFJVWm5VVlZ2TTB0dUNtcEtVVm93V0dacFoySkVOV0l3VDFaT1RqQjRjVk52ZDBoM1dVUldVakJxUWtKbmQwWnZRVlV6T1ZCd2VqRlphMFZhWWpWeFRtcHdTMFpYYVhocE5Ga0tXa1E0ZDBwM1dVUldVakJTUVZGSUwwSkNNSGRITkVWYVdrZEdkV0ZYVm5OTWJVcHNaRzFXZFdGWVZucFJSMlIwV1Zkc2MweHRUblppVkVGelFtZHZjZ3BDWjBWRlFWbFBMMDFCUlVKQ1FqVnZaRWhTZDJONmIzWk1NbVJ3WkVkb01WbHBOV3BpTWpCMllrYzVibUZYTkhaaU1rWXhaRWRuZDJkWmMwZERhWE5IQ2tGUlVVSXhibXREUWtGSlJXWlJVamRCU0d0QlpIZEVaRkJVUW5GNGMyTlNUVzFOV2tob2VWcGFlbU5EYjJ0d1pYVk9ORGh5Wml0SWFXNUxRVXg1Ym5VS2FtZEJRVUZaVTNSMVFraDVRVUZCUlVGM1FrbE5SVmxEU1ZGRVRUVlpVMUV2UjB3MlMwazFVamxQWkdOdUwzQlRheXR4VmtRMlluTk1PRE1yUlhBNVVnb3lhRmRVWVhkSmFFRkxNV3BwTVd4YU5UWkVjMloxVEdaWU4ySkNRemx1WWxJelJXeDRZV3hDYUhZeGVsRllUVlUzZEd4M1RVRnZSME5EY1VkVFRUUTVDa0pCVFVSQk1tTkJUVWRSUTAxQ1N6aDBjMmRJWldkMWFDdFphR1ZzTTFCcGFraFJiSGxLTVZFMVN6WTBjREI0Y1VSa2J6ZFhOR1o0Wm05QlV6bDRjbEFLY3pKUVMxRmpaRzlFT1dKWWQyZEpkMWcyZWt4cWVXSmFhMDVJVURWNGRFSndOM1pMTWtaWlpWcDBNRTlYVEZKc1ZXeHNZMVZFVEROVUx6ZEtVV1ozWXdwSFUzRTJkbFpDVG5kS01EQjNPVWhTQ2kwdExTMHRSVTVFSUVORlVsUkpSa2xEUVZSRkxTMHRMUzBLIn19fX0=","integratedTime":1669361833,"logIndex":7810348,"logID":"c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d"}}} + "#; + let rekor_pub_key = get_rekor_public_key(); + let result = Bundle::new_verified(&bundle_raw, &rekor_pub_key); + assert!(result.is_ok()); + let bundle = result.unwrap(); + assert_eq!(bundle.rekor_bundle.payload.log_index, 7810348); + } } diff --git a/src/cosign/signature_layers.rs b/src/cosign/signature_layers.rs index 5488fd6235..f6a24c370f 100644 --- a/src/cosign/signature_layers.rs +++ b/src/cosign/signature_layers.rs @@ -23,7 +23,7 @@ use x509_parser::{ parse_x509_certificate, pem::parse_x509_pem, }; -use super::bundle::Bundle; +use super::bundle::RekorBundle; use super::constants::{ SIGSTORE_BUNDLE_ANNOTATION, SIGSTORE_CERT_ANNOTATION, SIGSTORE_GITHUB_WORKFLOW_NAME_OID, SIGSTORE_GITHUB_WORKFLOW_REF_OID, SIGSTORE_GITHUB_WORKFLOW_REPOSITORY_OID, @@ -137,7 +137,7 @@ pub struct SignatureLayer { /// signature time. pub certificate_signature: Option, /// The bundle produced by Rekor. - pub bundle: Option, + pub bundle: Option, #[serde(skip_serializing)] pub signature: Option, #[serde(skip_serializing)] @@ -298,10 +298,10 @@ impl SignatureLayer { fn get_bundle_from_annotations( annotations: &HashMap, rekor_pub_key: Option<&CosignVerificationKey>, - ) -> Result> { + ) -> Result> { let bundle = match annotations.get(SIGSTORE_BUNDLE_ANNOTATION) { Some(value) => match rekor_pub_key { - Some(key) => Some(Bundle::new_verified(value, key)?), + Some(key) => Some(RekorBundle::new_verified(value, key)?), None => { info!(bundle = ?value, "Ignoring bundle, rekor public key not provided to verification client"); None @@ -315,7 +315,7 @@ impl SignatureLayer { fn get_certificate_signature_from_annotations( annotations: &HashMap, fulcio_cert_pool: Option<&CertificatePool>, - bundle: Option<&Bundle>, + bundle: Option<&RekorBundle>, ) -> Option { let cert_raw = match annotations.get(SIGSTORE_CERT_ANNOTATION) { Some(value) => value, @@ -427,7 +427,7 @@ impl CertificateSignature { pub(crate) fn from_certificate( cert_raw: &[u8], fulcio_cert_pool: &CertificatePool, - trusted_bundle: &Bundle, + trusted_bundle: &RekorBundle, ) -> Result { let (_, pem) = parse_x509_pem(cert_raw)?; let (_, cert) = parse_x509_certificate(&pem.contents)?; @@ -573,7 +573,7 @@ OSWS1X9vPavpiQOoTTGC0xX57OojUadxF1cdQmrsiReWg2Wn4FneJfa8xw== ) } - pub(crate) fn build_bundle() -> Bundle { + pub(crate) fn build_bundle() -> RekorBundle { let bundle_value = json!({ "SignedEntryTimestamp": "MEUCIDBGJijj2FqU25yRWzlEWHqE64XKwUvychBs1bSM1PaKAiEAwcR2u81c42TLBk3lWJqhtB7SnM7Lh0OYEl6Bfa7ZA4s=", "Payload": { @@ -583,7 +583,8 @@ OSWS1X9vPavpiQOoTTGC0xX57OojUadxF1cdQmrsiReWg2Wn4FneJfa8xw== "logID": "c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d" } }); - let bundle: Bundle = serde_json::from_value(bundle_value).expect("Cannot parse bundle"); + let bundle: RekorBundle = + serde_json::from_value(bundle_value).expect("Cannot parse bundle"); bundle } @@ -887,7 +888,7 @@ JsB89BPhZYch0U0hKANx5TY+ncrm0s8bfJxxHoenAEFhwhuXeb4PqIrtoQ== let cert_pool = CertificatePool::from_certificates(&certs).unwrap(); let integrated_time = Utc::now().checked_sub_signed(Duration::minutes(1)).unwrap(); - let bundle = Bundle { + let bundle = RekorBundle { signed_entry_timestamp: "not relevant".to_string(), payload: Payload { body: "not relevant".to_string(), @@ -934,7 +935,7 @@ JsB89BPhZYch0U0hKANx5TY+ncrm0s8bfJxxHoenAEFhwhuXeb4PqIrtoQ== let cert_pool = CertificatePool::from_certificates(&certs).unwrap(); let integrated_time = Utc::now().checked_sub_signed(Duration::minutes(1)).unwrap(); - let bundle = Bundle { + let bundle = RekorBundle { signed_entry_timestamp: "not relevant".to_string(), payload: Payload { body: "not relevant".to_string(), @@ -980,7 +981,7 @@ JsB89BPhZYch0U0hKANx5TY+ncrm0s8bfJxxHoenAEFhwhuXeb4PqIrtoQ== let cert_pool = CertificatePool::from_certificates(&certs).unwrap(); let integrated_time = Utc::now().checked_sub_signed(Duration::minutes(1)).unwrap(); - let bundle = Bundle { + let bundle = RekorBundle { signed_entry_timestamp: "not relevant".to_string(), payload: Payload { body: "not relevant".to_string(), diff --git a/src/cosign/verification_constraint/certificate_verifier.rs b/src/cosign/verification_constraint/certificate_verifier.rs index 49dc3816ec..d4708a9e0a 100644 --- a/src/cosign/verification_constraint/certificate_verifier.rs +++ b/src/cosign/verification_constraint/certificate_verifier.rs @@ -119,7 +119,7 @@ impl VerificationConstraint for CertificateVerifier { #[cfg(test)] mod tests { use super::*; - use crate::cosign::bundle::Bundle; + use crate::cosign::bundle::RekorBundle; use crate::crypto::tests::*; use crate::registry; @@ -207,7 +207,7 @@ RAIgPixAn47x4qLpu7Y/d0oyvbnOGtD5cY7rywdMOO7LYRsCIDsCyGUZIYMFfSrt (signature_layer, cert_pem_raw) } - fn build_bundle() -> Bundle { + fn build_bundle() -> RekorBundle { let bundle_value = json!({ "SignedEntryTimestamp": "MEUCIG5TYOXkiPm7RGYgDIPHwRQW5NyoSPuwxvJe4ByB9c37AiEAyD0dVcsiJ5Lp+QY5SL80jDxfc75BtjRnticVf7SiFD0=", "Payload": { @@ -217,7 +217,8 @@ RAIgPixAn47x4qLpu7Y/d0oyvbnOGtD5cY7rywdMOO7LYRsCIDsCyGUZIYMFfSrt "logID": "c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d" } }); - let bundle: Bundle = serde_json::from_value(bundle_value).expect("Cannot parse bundle"); + let bundle: RekorBundle = + serde_json::from_value(bundle_value).expect("Cannot parse bundle"); bundle }