Skip to content

Commit

Permalink
Rename Bundle to RekorBundle and modify Bundle
Browse files Browse the repository at this point in the history
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: sigstore#117

Signed-off-by: Daniel Bevenius <daniel.bevenius@gmail.com>
  • Loading branch information
danbev committed Nov 25, 2022
1 parent 37b9e42 commit b45a7c1
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 26 deletions.
68 changes: 56 additions & 12 deletions src/cosign/bundle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Self> {
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<Self> {
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| {
Expand All @@ -50,7 +79,7 @@ impl Bundle {
Signature::Base64Encoded(bundle.signed_entry_timestamp.as_bytes()),
&buf,
)?;
Ok(bundle)
Ok(())
}
}

Expand All @@ -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": {
Expand All @@ -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==
Expand All @@ -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, &not_rekor_pub_key);
let bundle_json = build_correct_rekor_bundle();
let bundle = RekorBundle::new_verified(&bundle_json, &not_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);
}
}
23 changes: 12 additions & 11 deletions src/cosign/signature_layers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -137,7 +137,7 @@ pub struct SignatureLayer {
/// signature time.
pub certificate_signature: Option<CertificateSignature>,
/// The bundle produced by Rekor.
pub bundle: Option<Bundle>,
pub bundle: Option<RekorBundle>,
#[serde(skip_serializing)]
pub signature: Option<String>,
#[serde(skip_serializing)]
Expand Down Expand Up @@ -298,10 +298,10 @@ impl SignatureLayer {
fn get_bundle_from_annotations(
annotations: &HashMap<String, String>,
rekor_pub_key: Option<&CosignVerificationKey>,
) -> Result<Option<Bundle>> {
) -> Result<Option<RekorBundle>> {
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
Expand All @@ -315,7 +315,7 @@ impl SignatureLayer {
fn get_certificate_signature_from_annotations(
annotations: &HashMap<String, String>,
fulcio_cert_pool: Option<&CertificatePool>,
bundle: Option<&Bundle>,
bundle: Option<&RekorBundle>,
) -> Option<CertificateSignature> {
let cert_raw = match annotations.get(SIGSTORE_CERT_ANNOTATION) {
Some(value) => value,
Expand Down Expand Up @@ -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<Self> {
let (_, pem) = parse_x509_pem(cert_raw)?;
let (_, cert) = parse_x509_certificate(&pem.contents)?;
Expand Down Expand Up @@ -573,7 +573,7 @@ OSWS1X9vPavpiQOoTTGC0xX57OojUadxF1cdQmrsiReWg2Wn4FneJfa8xw==
)
}

pub(crate) fn build_bundle() -> Bundle {
pub(crate) fn build_bundle() -> RekorBundle {
let bundle_value = json!({
"SignedEntryTimestamp": "MEUCIDBGJijj2FqU25yRWzlEWHqE64XKwUvychBs1bSM1PaKAiEAwcR2u81c42TLBk3lWJqhtB7SnM7Lh0OYEl6Bfa7ZA4s=",
"Payload": {
Expand All @@ -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
}

Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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(),
Expand Down
7 changes: 4 additions & 3 deletions src/cosign/verification_constraint/certificate_verifier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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": {
Expand All @@ -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
}

Expand Down

0 comments on commit b45a7c1

Please sign in to comment.