Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: bundles + conformance suite #315

Draft
wants to merge 14 commits into
base: main
Choose a base branch
from
Draft
18 changes: 12 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ readme = "README.md"
repository = "https://github.com/sigstore/sigstore-rs"

[features]
default = ["full-native-tls", "cached-client", "tuf"]
default = ["full-native-tls", "cached-client", "tuf", "sign", "verify"]
wasm = ["getrandom/js"]

full-native-tls = [
Expand Down Expand Up @@ -42,6 +42,9 @@ rekor = ["reqwest"]

tuf = ["tough", "regex"]

sign = []
verify = []

cosign-native-tls = [
"oci-distribution/native-tls",
"cert",
Expand Down Expand Up @@ -72,7 +75,7 @@ async-trait = "0.1.52"
base64 = "0.21.0"
cached = { version = "0.46.0", optional = true, features = ["async"] }
cfg-if = "1.0.0"
chrono = { version = "0.4.27", default-features = false }
chrono = { version = "0.4.27", default-features = false, features = ["serde"] }
const-oid = "0.9.1"
digest = { version = "0.10.3", default-features = false }
ecdsa = { version = "0.16.7", features = ["pkcs8", "digest", "der", "signing"] }
Expand All @@ -88,7 +91,7 @@ openidconnect = { version = "3.0", default-features = false, features = [
p256 = "0.13.2"
p384 = "0.13"
webbrowser = "0.8.4"
pem = "3.0"
pem = { version = "3.0", features = ["serde"] }
pkcs1 = { version = "0.7.5", features = ["std"] }
pkcs8 = { version = "0.10.2", features = [
"pem",
Expand All @@ -110,17 +113,21 @@ serde_json = "1.0.79"
serde_with = { version = "3.4.0", features = ["base64"] }
sha2 = { version = "0.10.6", features = ["oid"] }
signature = { version = "2.0" }
sigstore_protobuf_specs = "0.1.0-rc.2"
thiserror = "1.0.30"
tokio = { version = "1.17.0", features = ["rt"] }
tokio-util = { version = "0.7.10", features = ["io-util"] }
tough = { version = "0.14", features = ["http"], optional = true }
tracing = "0.1.31"
url = "2.2.2"
x509-cert = { version = "0.2.2", features = ["pem", "std"] }
x509-cert = { version = "0.2.2", features = ["builder", "pem", "std"] }
crypto_secretbox = "0.1.1"
zeroize = "1.5.7"
rustls-webpki = { version = "0.102.0", features = ["alloc"] }
rustls-webpki = { version = "0.102.0-alpha.7", features = ["alloc"] }
rustls-pki-types = { version = "1.0.0", features = ["std"] }
serde_repr = "0.1.16"
hex = "0.4.3"
json-syntax = { version = "0.9.6", features = ["canonicalize", "serde"] }

[dev-dependencies]
anyhow = { version = "1.0", features = ["backtrace"] }
Expand All @@ -134,7 +141,6 @@ serial_test = "2.0.0"
tempfile = "3.3.0"
testcontainers = "0.15"
tracing-subscriber = { version = "0.3.9", features = ["env-filter"] }
hex = "0.4.3"

# cosign example mappings

Expand Down
59 changes: 59 additions & 0 deletions src/bundle/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Copyright 2023 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.

//! Useful types for Sigstore bundles.

use std::fmt::Display;
use std::str::FromStr;

pub use sigstore_protobuf_specs::Bundle;

macro_rules! required {
($($base:expr )? ; $first_attr:ident $( . $rest_attrs:ident)* $( , $else_err:expr)?) => {
$( $base . )? $first_attr.as_ref()
$(
.and_then(|v| v.$rest_attrs.as_ref())
)*
$( .ok_or($else_err) )?
}
}
pub(crate) use required;

// Known Sigstore bundle media types.
#[derive(Clone, Copy, Debug)]
pub enum Version {
Bundle0_1,
Bundle0_2,
}

impl Display for Version {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match &self {
Version::Bundle0_1 => "application/vnd.dev.sigstore.bundle+json;version=0.1",
Version::Bundle0_2 => "application/vnd.dev.sigstore.bundle+json;version=0.2",
})
}
}

impl FromStr for Version {
type Err = ();

fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"application/vnd.dev.sigstore.bundle+json;version=0.1" => Ok(Version::Bundle0_1),
"application/vnd.dev.sigstore.bundle+json;version=0.2" => Ok(Version::Bundle0_2),
_ => Err(()),
}
}
}
2 changes: 1 addition & 1 deletion src/cosign/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,7 @@ TNMea7Ix/stJ5TfcLLeABLE4BNJOsQ4vnBHJ
const SIGNED_IMAGE: &str = "busybox:1.34";

pub(crate) fn get_fulcio_cert_pool() -> CertificatePool<'static> {
fn pem_to_der<'a>(input: &'a str) -> CertificateDer<'a> {
fn pem_to_der(input: &str) -> CertificateDer<'_> {
let pem_cert = pem::parse(input).unwrap();
assert_eq!(pem_cert.tag(), "CERTIFICATE");
CertificateDer::from(pem_cert.into_contents())
Expand Down
30 changes: 30 additions & 0 deletions src/crypto/certificate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ use x509_cert::{

use crate::errors::{Result, SigstoreError};

pub type DERCert = Vec<u8>;

/// Ensure the given certificate can be trusted for verifying cosign
/// signatures.
///
Expand Down Expand Up @@ -120,6 +122,34 @@ fn verify_expiration(certificate: &Certificate, integrated_time: i64) -> Result<
Ok(())
}

/// Check if the given certificate is a leaf in the context of the Sigstore profile.
///
/// * It is not a root or intermediate CA;
/// * It has `keyUsage.digitalSignature`
/// * It has `CODE_SIGNING` as an `ExtendedKeyUsage`.
///
/// This function does not evaluate the trustworthiness of the certificate.
pub(crate) fn is_leaf(certificate: &Certificate) -> Result<()> {
let tbs = &certificate.tbs_certificate;

// Only V3 certificates should appear in the context of Sigstore; earlier versions of X.509 lack
// extensions and have ambiguous CA behavior.
if tbs.version != x509_cert::Version::V3 {
return Err(SigstoreError::CertificateUnsupportedVersionError);
}

// TODO(tnytown): cert_is_ca

verify_key_usages(certificate)?;

Ok(())
}

pub(crate) fn is_root_ca(_certificate: &Certificate) -> Result<()> {
// TODO(tnytown)
Ok(())
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
2 changes: 2 additions & 0 deletions src/crypto/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,8 @@ pub enum Signature<'a> {
pub(crate) mod certificate;
#[cfg(feature = "cert")]
pub(crate) mod certificate_pool;
#[cfg(feature = "cert")]
pub(crate) use certificate_pool::CertificatePool;

pub mod verification_key;

Expand Down
12 changes: 12 additions & 0 deletions src/crypto/verification.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
use rustls_pki_types::CertificateDer;
use webpki::TrustAnchor;

/// Machinery for Sigstore end entity certificate verification.
struct CertificateVerificationContext<'a> {
pub trust_anchors: Vec<TrustAnchor<'a>>,
pub intermediate_certs: Vec<CertificateDer<'a>>,
}

impl CertificateVerificationContext<'_> {
pub fn new() {}
}
64 changes: 63 additions & 1 deletion src/crypto/verification_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use const_oid::db::rfc5912::{ID_EC_PUBLIC_KEY, RSA_ENCRYPTION};
use ed25519::pkcs8::DecodePublicKey as ED25519DecodePublicKey;
use rsa::{pkcs1v15, pss};
use sha2::{Digest, Sha256, Sha384};
use signature::{DigestVerifier, Verifier};
use signature::{hazmat::PrehashVerifier, DigestVerifier, Verifier};
use std::convert::TryFrom;
use x509_cert::{der::referenced::OwnedToRef, spki::SubjectPublicKeyInfoOwned};

Expand Down Expand Up @@ -329,6 +329,68 @@ impl CosignVerificationKey {
}
}
}

/// Verify the signature provided has been actually generated by the given key
/// when signing the provided prehashed message.
pub fn verify_prehash(&self, signature: Signature, msg: &[u8]) -> Result<()> {
let sig = match signature {
Signature::Raw(data) => data.to_owned(),
Signature::Base64Encoded(data) => BASE64_STD_ENGINE.decode(data)?,
};

match self {
CosignVerificationKey::RSA_PSS_SHA256(inner) => {
let sig = pss::Signature::try_from(sig.as_slice())?;
inner
.verify_prehash(msg, &sig)
.map_err(|_| SigstoreError::PublicKeyVerificationError)
}
CosignVerificationKey::RSA_PSS_SHA384(inner) => {
let sig = pss::Signature::try_from(sig.as_slice())?;
inner
.verify_prehash(msg, &sig)
.map_err(|_| SigstoreError::PublicKeyVerificationError)
}
CosignVerificationKey::RSA_PSS_SHA512(inner) => {
let sig = pss::Signature::try_from(sig.as_slice())?;
inner
.verify_prehash(msg, &sig)
.map_err(|_| SigstoreError::PublicKeyVerificationError)
}
CosignVerificationKey::RSA_PKCS1_SHA256(inner) => {
let sig = pkcs1v15::Signature::try_from(sig.as_slice())?;
inner
.verify_prehash(msg, &sig)
.map_err(|_| SigstoreError::PublicKeyVerificationError)
}
CosignVerificationKey::RSA_PKCS1_SHA384(inner) => {
let sig = pkcs1v15::Signature::try_from(sig.as_slice())?;
inner
.verify_prehash(msg, &sig)
.map_err(|_| SigstoreError::PublicKeyVerificationError)
}
CosignVerificationKey::RSA_PKCS1_SHA512(inner) => {
let sig = pkcs1v15::Signature::try_from(sig.as_slice())?;
inner
.verify_prehash(msg, &sig)
.map_err(|_| SigstoreError::PublicKeyVerificationError)
}
// ECDSA signatures are encoded in der.
CosignVerificationKey::ECDSA_P256_SHA256_ASN1(inner) => {
let sig = ecdsa::Signature::from_der(&sig)?;
inner
.verify_prehash(msg, &sig)
.map_err(|_| SigstoreError::PublicKeyVerificationError)
}
CosignVerificationKey::ECDSA_P384_SHA384_ASN1(inner) => {
let sig = ecdsa::Signature::from_der(&sig)?;
inner
.verify_prehash(msg, &sig)
.map_err(|_| SigstoreError::PublicKeyVerificationError)
}
_ => unimplemented!("Ed25519 doesn't implement verify_prehash"),
}
}
}

#[cfg(test)]
Expand Down
36 changes: 35 additions & 1 deletion src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ pub enum SigstoreError {
#[error("invalid key format: {error}")]
InvalidKeyFormat { error: String },

#[error("Unable to parse identity token: {0}")]
IdentityTokenError(String),

#[error("unmatched key type {key_typ} and signing scheme {scheme}")]
UnmatchedKeyAndSigningScheme { key_typ: String, scheme: String },

Expand All @@ -70,6 +73,9 @@ pub enum SigstoreError {
#[error("Public key verification error")]
PublicKeyVerificationError,

#[error("X.509 certificate version is not V3")]
CertificateUnsupportedVersionError,

#[error("Certificate validity check failed: cannot be used before {0}")]
CertificateValidityError(String),

Expand Down Expand Up @@ -103,6 +109,12 @@ pub enum SigstoreError {
#[error("Certificate pool error: {0}")]
CertificatePoolError(&'static str),

#[error("Signing session expired")]
ExpiredSigningSession(),

#[error("Fulcio request unsuccessful: {0}")]
FulcioClientError(String),

#[error("Cannot fetch manifest of {image}: {error}")]
RegistryFetchManifestError { image: String, error: String },

Expand All @@ -115,9 +127,22 @@ pub enum SigstoreError {
#[error("Cannot push {image}: {error}")]
RegistryPushError { image: String, error: String },

#[error("Rekor request unsuccessful: {0}")]
RekorClientError(String),

#[error(transparent)]
JoinError(#[from] tokio::task::JoinError),

#[cfg(feature = "sign")]
#[error(transparent)]
ReqwestError(#[from] reqwest::Error),

#[error("OCI reference not valid: {reference}")]
OciReferenceNotValidError { reference: String },

#[error("Sigstore bundle malformed: {0}")]
SigstoreBundleMalformedError(String),

#[error("Layer doesn't have Sigstore media type")]
SigstoreMediaTypeNotFoundError,

Expand All @@ -144,7 +169,7 @@ pub enum SigstoreError {
TufTargetNotFoundError(String),

#[error("{0}")]
TufMetadataError(&'static str),
TufMetadataError(String),

#[error(transparent)]
IOError(#[from] std::io::Error),
Expand All @@ -155,6 +180,9 @@ pub enum SigstoreError {
#[error("{0}")]
VerificationConstraintError(String),

#[error("{0}")]
VerificationMaterialError(String),

#[error("{0}")]
ApplyConstraintError(String),

Expand Down Expand Up @@ -214,4 +242,10 @@ pub enum SigstoreError {

#[error(transparent)]
Ed25519PKCS8Error(#[from] ed25519_dalek::pkcs8::spki::Error),

#[error(transparent)]
X509ParseError(#[from] x509_cert::der::Error),

#[error(transparent)]
X509BuilderError(#[from] x509_cert::builder::Error),
}
Loading