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

cosign: Allow use of regex in CertSubjectEmailVerifier #300

Merged
merged 1 commit into from
Sep 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions examples/cosign/verify/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
// limitations under the License.

extern crate sigstore;
use sigstore::cosign::verification_constraint::cert_subject_email_verifier::StringVerifier;
use sigstore::cosign::verification_constraint::{
AnnotationVerifier, CertSubjectEmailVerifier, CertSubjectUrlVerifier, CertificateVerifier,
PublicKeyVerifier, VerificationConstraintVec,
Expand Down Expand Up @@ -146,10 +147,13 @@ async fn run_app(
// Build verification constraints
let mut verification_constraints: VerificationConstraintVec = Vec::new();
if let Some(cert_email) = cli.cert_email.as_ref() {
let issuer = cli.cert_issuer.as_ref().map(|i| i.to_string());
let issuer = cli
.cert_issuer
.as_ref()
.map(|i| StringVerifier::ExactMatch(i.to_string()));

verification_constraints.push(Box::new(CertSubjectEmailVerifier {
email: cert_email.to_string(),
email: StringVerifier::ExactMatch(cert_email.to_string()),
issuer,
}));
}
Expand Down
19 changes: 10 additions & 9 deletions src/cosign/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,7 @@ mod tests {
use webpki::types::CertificateDer;

use super::constraint::{AnnotationMarker, PrivateKeySigner};
use super::verification_constraint::cert_subject_email_verifier::StringVerifier;
use super::*;
use crate::cosign::signature_layers::tests::build_correct_signature_layer_with_certificate;
use crate::cosign::signature_layers::CertificateSubject;
Expand Down Expand Up @@ -389,13 +390,13 @@ TNMea7Ix/stJ5TfcLLeABLE4BNJOsQ4vnBHJ

let mut constraints: VerificationConstraintVec = Vec::new();
let vc = CertSubjectEmailVerifier {
email: email.clone(),
issuer: Some(issuer),
email: StringVerifier::ExactMatch(email.clone()),
issuer: Some(StringVerifier::ExactMatch(issuer)),
};
constraints.push(Box::new(vc));

let vc = CertSubjectEmailVerifier {
email,
email: StringVerifier::ExactMatch(email),
issuer: None,
};
constraints.push(Box::new(vc));
Expand Down Expand Up @@ -438,13 +439,13 @@ TNMea7Ix/stJ5TfcLLeABLE4BNJOsQ4vnBHJ

let mut constraints: VerificationConstraintVec = Vec::new();
let vc = CertSubjectEmailVerifier {
email: wrong_email.clone(),
issuer: Some(issuer), // correct issuer
email: StringVerifier::ExactMatch(wrong_email.clone()),
issuer: Some(StringVerifier::ExactMatch(issuer)), // correct issuer
};
constraints.push(Box::new(vc));

let vc = CertSubjectEmailVerifier {
email: wrong_email,
email: StringVerifier::ExactMatch(wrong_email),
issuer: None, // missing issuer, more relaxed
};
constraints.push(Box::new(vc));
Expand Down Expand Up @@ -486,13 +487,13 @@ TNMea7Ix/stJ5TfcLLeABLE4BNJOsQ4vnBHJ

let mut constraints: VerificationConstraintVec = Vec::new();
let satisfied_constraint = CertSubjectEmailVerifier {
email,
issuer: Some(issuer),
email: StringVerifier::ExactMatch(email),
issuer: Some(StringVerifier::ExactMatch(issuer)),
};
constraints.push(Box::new(satisfied_constraint));

let unsatisfied_constraint = CertSubjectEmailVerifier {
email: email_incorrect,
email: StringVerifier::ExactMatch(email_incorrect),
issuer: None,
};
constraints.push(Box::new(unsatisfied_constraint));
Expand Down
214 changes: 192 additions & 22 deletions src/cosign/verification_constraint/cert_subject_email_verifier.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
use regex::Regex;
use std::fmt::Debug;

use super::VerificationConstraint;
use crate::cosign::signature_layers::{CertificateSubject, SignatureLayer};
use crate::errors::Result;
Expand Down Expand Up @@ -33,19 +36,35 @@ use crate::errors::Result;
/// found:
///
/// ```rust
/// use regex::Regex;
/// use sigstore::cosign::verification_constraint::CertSubjectEmailVerifier;
/// use sigstore::cosign::verification_constraint::cert_subject_email_verifier::StringVerifier;
///
/// // This looks only for the email address of the trusted user
/// let vc_email = CertSubjectEmailVerifier{
/// email: String::from("[email protected]"),
/// ..Default::default()
/// email: StringVerifier::ExactMatch("[email protected]".to_string()),
/// issuer: None,
/// };
///
/// // This looks only for emails matching the a pattern
/// let vc_email_regex = CertSubjectEmailVerifier{
/// email: StringVerifier::Regex(Regex::new(".*@example.com").unwrap()),
/// issuer: None,
/// };
///
/// // This ensures the user authenticated via GitHub (see the issuer value),
/// // plus the email associated to his GitHub account must be the one specified.
/// let vc_email_and_issuer = CertSubjectEmailVerifier{
/// email: String::from("[email protected]"),
/// issuer: Some(String::from("https://github.com/login/oauth")),
/// email: StringVerifier::ExactMatch("[email protected]".to_string()),
/// issuer: Some(StringVerifier::ExactMatch("https://github.com/login/oauth".to_string())),
/// };
///
/// // This ensures the user authenticated via a service that has a domain
/// // matching the regex, plus the email associated to account also matches
/// // the regex.
/// let vc_email_and_issuer_regex = CertSubjectEmailVerifier{
/// email: StringVerifier::Regex(Regex::new(".*@example.com").unwrap()),
/// issuer: Some(StringVerifier::Regex(Regex::new(r"https://github\.com/login/oauth|https://google\.com").unwrap())),
/// };
/// ```
///
Expand All @@ -55,10 +74,11 @@ use crate::errors::Result;
/// For example, given the following constraint:
/// ```rust
/// use sigstore::cosign::verification_constraint::CertSubjectEmailVerifier;
/// use sigstore::cosign::verification_constraint::cert_subject_email_verifier::StringVerifier;
///
/// let constraint = CertSubjectEmailVerifier{
/// email: String::from("[email protected]"),
/// ..Default::default()
/// email: StringVerifier::ExactMatch("[email protected]".to_string()),
/// issuer: None,
/// };
/// ```
///
Expand Down Expand Up @@ -91,23 +111,69 @@ use crate::errors::Result;
/// }
/// ]
/// ```
#[derive(Default, Debug)]
pub struct CertSubjectEmailVerifier {
pub email: String,
pub issuer: Option<String>,
pub email: StringVerifier,
pub issuer: Option<StringVerifier>,
}

impl Debug for CertSubjectEmailVerifier {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut issuer_str = String::new();
if let Some(issuer) = &self.issuer {
issuer_str.push_str(&format!(" and {}", issuer));
}
f.write_fmt(format_args!(
"email {}{}",
&self.email.to_string(),
issuer_str
))
}
}

pub enum StringVerifier {
ExactMatch(String),
Regex(Regex),
}
flavio marked this conversation as resolved.
Show resolved Hide resolved

impl StringVerifier {
fn verify(&self, s: &str) -> bool {
match self {
StringVerifier::ExactMatch(s2) => s == *s2,
StringVerifier::Regex(r) => r.is_match(s),
}
}
}

impl std::fmt::Display for StringVerifier {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
StringVerifier::ExactMatch(s) => f.write_fmt(format_args!("is exactly {}", s)),
StringVerifier::Regex(r) => {
f.write_fmt(format_args!("matches regular expression {}", r))
}
}
}
}

impl VerificationConstraint for CertSubjectEmailVerifier {
fn verify(&self, signature_layer: &SignatureLayer) -> Result<bool> {
let verified = match &signature_layer.certificate_signature {
Some(signature) => {
let email_matches = match &signature.subject {
CertificateSubject::Email(e) => e == &self.email,
CertificateSubject::Email(e) => self.email.verify(e),
_ => false,
};

let issuer_matches = match self.issuer {
Some(_) => self.issuer == signature.issuer,
let issuer_matches = match &self.issuer {
Some(issuer) => {
if let Some(signature_issuer) = &signature.issuer {
issuer.verify(signature_issuer)
} else {
// if the issuer is not present in the signature, we
// consider it as a failed constriant
false
}
}
None => true,
};

Expand All @@ -133,19 +199,19 @@ mod tests {
let email = "[email protected]".to_string();
let mut sl = build_correct_signature_layer_with_certificate();
let mut cert_signature = sl.certificate_signature.unwrap();
let cert_subj = CertificateSubject::Email(email.clone());
let cert_subj = CertificateSubject::Email(email.to_string());
cert_signature.issuer = None;
cert_signature.subject = cert_subj;
sl.certificate_signature = Some(cert_signature);

let vc = CertSubjectEmailVerifier {
email,
email: StringVerifier::ExactMatch(email),
issuer: None,
};
assert!(vc.verify(&sl).unwrap());

let vc = CertSubjectEmailVerifier {
email: "[email protected]".to_string(),
email: StringVerifier::ExactMatch("[email protected]".to_string()),
issuer: None,
};
assert!(!vc.verify(&sl).unwrap());
Expand All @@ -165,8 +231,8 @@ mod tests {

// fail because the issuer we want doesn't exist
let vc = CertSubjectEmailVerifier {
email: email.clone(),
issuer: Some("an issuer".to_string()),
email: StringVerifier::ExactMatch(email.clone()),
issuer: Some(StringVerifier::ExactMatch("an issuer".to_string())),
};
assert!(!vc.verify(&sl).unwrap());

Expand All @@ -178,14 +244,14 @@ mod tests {
sl.certificate_signature = Some(cert_signature);

let vc = CertSubjectEmailVerifier {
email: email.clone(),
issuer: Some(issuer.clone()),
email: StringVerifier::ExactMatch(email.clone()),
issuer: Some(StringVerifier::ExactMatch(issuer.clone())),
};
assert!(vc.verify(&sl).unwrap());

let vc = CertSubjectEmailVerifier {
email,
issuer: Some("another issuer".to_string()),
email: StringVerifier::ExactMatch(email),
issuer: Some(StringVerifier::ExactMatch("another issuer".to_string())),
};
assert!(!vc.verify(&sl).unwrap());

Expand All @@ -202,9 +268,113 @@ mod tests {
let (sl, _) = build_correct_signature_layer_without_bundle();

let vc = CertSubjectEmailVerifier {
email: "[email protected]".to_string(),
email: StringVerifier::ExactMatch("[email protected]".to_string()),
issuer: None,
};
assert!(!vc.verify(&sl).unwrap());
}

#[test]
fn cert_email_verifier_only_email_regex() {
flavio marked this conversation as resolved.
Show resolved Hide resolved
let mut sl = build_correct_signature_layer_with_certificate();
let mut cert_signature = sl.certificate_signature.unwrap();
let cert_subj = CertificateSubject::Email("[email protected]".to_string());
cert_signature.issuer = None;
cert_signature.subject = cert_subj;
sl.certificate_signature = Some(cert_signature);

let vc = CertSubjectEmailVerifier {
email: StringVerifier::Regex(Regex::new(".*@example.com").unwrap()),
issuer: None,
};
assert!(vc.verify(&sl).unwrap());

let mut sl = build_correct_signature_layer_with_certificate();
let mut cert_signature = sl.certificate_signature.unwrap();
let cert_subj = CertificateSubject::Email("[email protected]".to_string());
cert_signature.issuer = None;
cert_signature.subject = cert_subj;
sl.certificate_signature = Some(cert_signature);
assert!(vc.verify(&sl).unwrap());

let vc = CertSubjectEmailVerifier {
email: StringVerifier::ExactMatch("[email protected]".to_string()),
issuer: None,
};
assert!(!vc.verify(&sl).unwrap());
}

#[test]
fn cert_email_verifier_email_and_issuer_regex() {
flavio marked this conversation as resolved.
Show resolved Hide resolved
// The cerificate subject doesn't have an issuer
let mut sl = build_correct_signature_layer_with_certificate();
let mut cert_signature = sl.certificate_signature.unwrap();
let cert_subj = CertificateSubject::Email("[email protected]".to_string());
cert_signature.issuer = None;
cert_signature.subject = cert_subj;
sl.certificate_signature = Some(cert_signature.clone());

// fail because the issuer we want doesn't exist
let vc = CertSubjectEmailVerifier {
email: StringVerifier::Regex(Regex::new(".*@example.com").unwrap()),
issuer: Some(StringVerifier::Regex(
Regex::new(r#".*\.github.com"#).unwrap(),
)),
};
assert!(!vc.verify(&sl).unwrap());

// The cerificate subject has an issuer
let mut sl = build_correct_signature_layer_with_certificate();
let mut cert_signature = sl.certificate_signature.unwrap();
let issuer = "some-action.github.com".to_string();
let cert_subj = CertificateSubject::Email("[email protected]".to_string());
cert_signature.issuer = Some(issuer.clone());
cert_signature.subject = cert_subj;
sl.certificate_signature = Some(cert_signature);

// pass because the issuer matches the regex
let vc = CertSubjectEmailVerifier {
email: StringVerifier::Regex(Regex::new(".*@example.com").unwrap()),
issuer: Some(StringVerifier::Regex(
Regex::new(r#".*\.github.com"#).unwrap(),
)),
};
assert!(vc.verify(&sl).unwrap());

// The cerificate subject has an incorrect issuer
let mut sl = build_correct_signature_layer_with_certificate();
let mut cert_signature = sl.certificate_signature.unwrap();
let issuer = "invalid issuer".to_string();
let cert_subj = CertificateSubject::Email("[email protected]".to_string());
cert_signature.issuer = Some(issuer.clone());
cert_signature.subject = cert_subj;
sl.certificate_signature = Some(cert_signature);

// fail because the issuer doesn't matches the regex
let vc = CertSubjectEmailVerifier {
email: StringVerifier::Regex(Regex::new(".*@example.com").unwrap()),
issuer: Some(StringVerifier::Regex(
Regex::new(r#".*\.github.com"#).unwrap(),
)),
};
assert!(!vc.verify(&sl).unwrap());

// The cerificate subject has an invalid email
let mut sl = build_correct_signature_layer_with_certificate();
let mut cert_signature = sl.certificate_signature.unwrap();
let issuer = "some-action.github.com".to_string();
let cert_subj = CertificateSubject::Email("[email protected]".to_string());
cert_signature.issuer = Some(issuer.clone());
cert_signature.subject = cert_subj;
sl.certificate_signature = Some(cert_signature);

// fail because the email doesn't matches the regex
let vc = CertSubjectEmailVerifier {
email: StringVerifier::Regex(Regex::new(".*@example.com").unwrap()),
issuer: Some(StringVerifier::Regex(
Regex::new(r#".*\.github.com"#).unwrap(),
)),
};
assert!(!vc.verify(&sl).unwrap());
}
}
Loading
Loading