Skip to content

Commit

Permalink
Merge pull request #24 from flavio/better-error-handling
Browse files Browse the repository at this point in the history
Better error handling
  • Loading branch information
flavio committed Feb 1, 2022
2 parents 7c5feb0 + 505fe2e commit 99ee52f
Show file tree
Hide file tree
Showing 14 changed files with 440 additions and 313 deletions.
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ native-tls = ["oci-distribution/native-tls"]
rustls-tls = ["oci-distribution/rustls-tls"]

[dependencies]
anyhow = "1.0.44"
async-trait = "0.1.51"
base64 = "0.13.0"
ecdsa = { version = "0.12.4", features = ["verify", "pem", "der", "pkcs8"] }
Expand All @@ -23,13 +22,15 @@ p256 = {version = "0.9.0", features = ["ecdsa-core"]}
sha2 = "0.10.1"
serde_json = "1.0.68"
serde = {version = "1.0.130", features = ["derive"]}
thiserror = "1.0.30"
tokio = { version = "1.12.0", features = ["full"]}
tough = { version = "0.12.1", features = [ "http" ] }
tracing = "0.1.29"
url = "2.2.2"
x509-parser = { version = "0.12.0", features = ["verify"]}

[dev-dependencies]
anyhow = "1.0.44"
chrono = "0.4.19"
clap = "2.33.3"
openssl = "0.10.38"
Expand Down
14 changes: 8 additions & 6 deletions examples/verify/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use sigstore::simple_signing::SimpleSigning;
use sigstore::tuf::SigstoreRepository;

extern crate anyhow;
use anyhow::{anyhow, Result};
use anyhow::anyhow;

extern crate clap;
use clap::{App, Arg};
Expand Down Expand Up @@ -104,7 +104,7 @@ fn cli() -> App<'static, 'static> {
)
}

async fn run_app() -> Result<Vec<SimpleSigning>> {
async fn run_app() -> anyhow::Result<Vec<SimpleSigning>> {
let matches = cli().get_matches();

// setup logging
Expand Down Expand Up @@ -137,7 +137,7 @@ async fn run_app() -> Result<Vec<SimpleSigning>> {
.transpose()?;

let sigstore_repo: Option<SigstoreRepository> = if matches.is_present("use-sigstore-tuf-data") {
let repo: Result<SigstoreRepository> = spawn_blocking(|| {
let repo: sigstore::errors::Result<SigstoreRepository> = spawn_blocking(|| {
info!("Downloading data from Sigstore TUF repository");
sigstore::tuf::SigstoreRepository::fetch(None)
})
Expand Down Expand Up @@ -201,20 +201,22 @@ async fn run_app() -> Result<Vec<SimpleSigning>> {
}
};

client
let matches = client
.verify(
auth,
&source_image_digest,
&cosign_signature_image,
&pub_key,
annotations,
)
.await
.await?;

Ok(matches)
}

#[tokio::main]
pub async fn main() {
let satistied_simple_signatures: Result<Vec<SimpleSigning>> = run_app().await;
let satistied_simple_signatures: anyhow::Result<Vec<SimpleSigning>> = run_app().await;

std::process::exit(match satistied_simple_signatures {
Ok(signatures) => {
Expand Down
20 changes: 8 additions & 12 deletions src/cosign/bundle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use anyhow::{anyhow, Result};
use olpc_cjson::CanonicalFormatter;
use serde::{Deserialize, Serialize};
use std::cmp::PartialEq;

use crate::crypto::{verify_signature, CosignVerificationKey};
use crate::errors::{Result, SigstoreError};

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
#[serde(rename_all = "PascalCase")]
Expand All @@ -33,25 +33,21 @@ impl Bundle {
/// **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| anyhow!("Cannot parse bundle |{}|: {:?}", raw, e))?;
let bundle: Bundle = serde_json::from_str(raw).map_err(|e| {
SigstoreError::UnexpectedError(format!("Cannot parse bundle |{}|: {:?}", raw, e))
})?;

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| {
anyhow!(
SigstoreError::UnexpectedError(format!(
"Cannot create canonical JSON representation of bundle: {:?}",
e
)
))
})?;

match verify_signature(rekor_pub_key, &bundle.signed_entry_timestamp, &buf) {
Ok(_) => Ok(bundle),
Err(e) => Err(anyhow!(
"Cannot verify bundle with the given rekor public key: {:?}",
e
)),
}
verify_signature(rekor_pub_key, &bundle.signed_entry_timestamp, &buf)?;
Ok(bundle)
}
}

Expand Down
55 changes: 21 additions & 34 deletions src/cosign/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use anyhow::{anyhow, Result};
use async_trait::async_trait;
use std::collections::HashMap;
use x509_parser::{traits::FromDer, x509::SubjectPublicKeyInfo};
Expand All @@ -24,6 +23,7 @@ use super::{
CosignCapabilities,
};
use crate::crypto::CosignVerificationKey;
use crate::errors::{Result, SigstoreError};
use crate::registry::Auth;
use crate::simple_signing::SimpleSigning;

Expand All @@ -40,21 +40,17 @@ pub struct Client {
#[async_trait]
impl CosignCapabilities for Client {
async fn triangulate(&mut self, image: &str, auth: &Auth) -> Result<(String, String)> {
let image_reference: oci_distribution::Reference = image
.parse()
.map_err(|e| anyhow!("Cannot parse image reference '{}': {:?}", image, e))?;
let image_reference: oci_distribution::Reference =
image
.parse()
.map_err(|_| SigstoreError::OciReferenceNotValidError {
reference: image.to_string(),
})?;

let manifest_digest = self
.registry_client
.fetch_manifest_digest(&image_reference, &auth.into())
.await
.map_err(|e| {
anyhow!(
"Cannot fetch manifest digest for {:?}: {:?}",
image_reference,
e
)
})?;
.await?;

let sign = format!(
"{}/{}:{}.sig",
Expand All @@ -64,7 +60,9 @@ impl CosignCapabilities for Client {
);
let reference = sign
.parse()
.map_err(|e| anyhow!("Cannot calculate signature object reference {:?}", e))?;
.map_err(|_| SigstoreError::OciReferenceNotValidError {
reference: image.to_string(),
})?;

Ok((reference, manifest_digest))
}
Expand All @@ -82,8 +80,7 @@ impl CosignCapabilities for Client {
let fulcio_pub_key = match &self.fulcio_pub_key_der {
None => None,
Some(der) => {
let (_, key) = SubjectPublicKeyInfo::from_der(der)
.map_err(|e| anyhow!("Cannot parse fulcio public key: {:?}", e))?;
let (_, key) = SubjectPublicKeyInfo::from_der(der)?;
Some(key)
}
};
Expand Down Expand Up @@ -121,37 +118,27 @@ impl Client {
oci_distribution::manifest::OciManifest,
Vec<oci_distribution::client::ImageLayer>,
)> {
let cosign_image_reference: oci_distribution::Reference = cosign_image
.parse()
.map_err(|e| anyhow!("Cannot parse image reference '{}': {:?}", cosign_image, e))?;
let cosign_image_reference: oci_distribution::Reference =
cosign_image
.parse()
.map_err(|_| SigstoreError::OciReferenceNotValidError {
reference: cosign_image.to_string(),
})?;

let oci_auth: oci_distribution::secrets::RegistryAuth = auth.into();

let (manifest, _) = self
.registry_client
.pull_manifest(&cosign_image_reference, &oci_auth)
.await
.map_err(|e| {
anyhow!(
"Cannot pull manifest for image {:?}: {:?}",
cosign_image_reference,
e
)
})?;
.await?;
let image_data = self
.registry_client
.pull(
&cosign_image_reference,
&oci_auth,
vec![SIGSTORE_OCI_MEDIA_TYPE],
)
.await
.map_err(|e| {
anyhow!(
"Cannot pull data for image {:?}: {:?}",
cosign_image_reference,
e
)
})?;
.await?;

Ok((manifest, image_data.layers))
}
Expand Down
2 changes: 1 addition & 1 deletion src/cosign/client_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use anyhow::Result;
use tracing::info;

use super::client::Client;
use crate::crypto;
use crate::errors::Result;
use crate::registry::ClientConfig;

/// A builder that generates Client objects.
Expand Down
2 changes: 1 addition & 1 deletion src/cosign/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,10 @@
//! In case you want to mock sigstore interactions inside of your own code, you
//! can implement the [`CosignCapabilities`] trait inside of your test suite.

use anyhow::Result;
use async_trait::async_trait;
use std::collections::HashMap;

use crate::errors::Result;
use crate::registry::Auth;
use crate::simple_signing::SimpleSigning;

Expand Down
43 changes: 19 additions & 24 deletions src/cosign/signature_layers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use anyhow::{anyhow, Result};
use oci_distribution::client::ImageLayer;
use std::{collections::HashMap, fmt};
use tracing::{debug, info, warn};
Expand All @@ -26,6 +25,7 @@ use super::constants::{
};
use crate::{
crypto::{verify_certificate_can_be_trusted, CosignVerificationKey},
errors::{Result, SigstoreError},
simple_signing::SimpleSigning,
};

Expand Down Expand Up @@ -92,23 +92,23 @@ impl SignatureLayer {
cert_email: Option<&String>,
) -> Result<SignatureLayer> {
if descriptor.media_type != SIGSTORE_OCI_MEDIA_TYPE {
return Err(anyhow!("layer doesn't have Sigstore media type"));
return Err(SigstoreError::SigstoreMediaTypeNotFoundError);
}

if layer.media_type != SIGSTORE_OCI_MEDIA_TYPE {
return Err(anyhow!("layer data doesn't have Sigstore media type"));
return Err(SigstoreError::SigstoreMediaTypeNotFoundError);
}

let layer_digest = layer.clone().sha256_digest();
if descriptor.digest != layer_digest {
return Err(anyhow!("layer digest is different from the layer data one"));
return Err(SigstoreError::SigstoreLayerDigestMismatchError);
}

let simple_signing: SimpleSigning = serde_json::from_slice(&layer.data).map_err(|e| {
anyhow!(
SigstoreError::UnexpectedError(format!(
"Cannot convert layer data into SimpleSigning object: {:?}",
e
)
))
})?;

let annotations = descriptor.annotations.clone().unwrap_or_default();
Expand Down Expand Up @@ -136,7 +136,7 @@ impl SignatureLayer {
let signature: String = annotations
.get(SIGSTORE_SIGNATURE_ANNOTATION)
.cloned()
.ok_or_else(|| anyhow!("Missing signature annotation"))?;
.ok_or(SigstoreError::SigstoreAnnotationNotFoundError)?;
Ok(signature)
}

Expand Down Expand Up @@ -271,19 +271,14 @@ fn verify_certificate_and_extract_public_key(
trusted_bundle: Option<&Bundle>,
) -> Result<CosignVerificationKey> {
if trusted_bundle.is_none() {
return Err(anyhow!(
"Cannot verify attached certificate because rekor bundle is missing"
));
return Err(SigstoreError::SigstoreRekorBundleNotFoundError);
}
let (_, pem) =
parse_x509_pem(cert_raw).map_err(|e| anyhow!("Error parsing PEM certificate: {:?}", e))?;
let (_, cert) = parse_x509_certificate(&pem.contents)
.map_err(|e| anyhow!("Error parsing bundled certificate: {:?}", e))?;
let (_, pem) = parse_x509_pem(cert_raw)?;
let (_, cert) = parse_x509_certificate(&pem.contents)?;
let integrated_time = trusted_bundle.unwrap().payload.integrated_time;
verify_certificate_can_be_trusted(&cert, fulcio_pub_key, integrated_time, cert_email)?;

let key = crate::crypto::new_verification_key_from_public_key_der(cert.public_key().raw)
.map_err(|e| anyhow!("Cannot parse key from bundled certificate: {:?}", e))?;
let key = crate::crypto::new_verification_key_from_public_key_der(cert.public_key().raw)?;
Ok(key)
}

Expand Down Expand Up @@ -550,14 +545,14 @@ JsB89BPhZYch0U0hKANx5TY+ncrm0s8bfJxxHoenAEFhwhuXeb4PqIrtoQ==

let actual = verify_certificate_and_extract_public_key(&cert, &fulcio_pub_key, None, None);

if let Err(e) = actual {
assert_eq!(
e.to_string(),
"Cannot verify attached certificate because rekor bundle is missing".to_string()
);
} else {
panic!("Didn't get an error as expected");
}
let found = match actual.expect_err("It was supposed to fail") {
SigstoreError::SigstoreRekorBundleNotFoundError => true,
_ => false,
};
assert!(
found,
"Was supposed to get SigstoreRekorBundleNotFoundError"
);
}

#[test]
Expand Down
Loading

0 comments on commit 99ee52f

Please sign in to comment.