-
Notifications
You must be signed in to change notification settings - Fork 56
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
cosign: Allow use of regex in CertSubjectEmailVerifier
This allows for either an exact match [StringVerifier::ExactMatch] or it allows for a regular expression [StringVerifier::Regex] This supports the use case of trusting signatures from a collection of email addresses e.g .*@redhat.com and or from a collection of issuers. Fixes: #299 Signed-off-by: Dave Tucker <[email protected]>
- Loading branch information
1 parent
7960dff
commit c67f5cb
Showing
4 changed files
with
211 additions
and
35 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
|
@@ -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())), | ||
/// }; | ||
/// ``` | ||
/// | ||
|
@@ -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, | ||
/// }; | ||
/// ``` | ||
/// | ||
|
@@ -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), | ||
} | ||
|
||
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, | ||
}; | ||
|
||
|
@@ -134,19 +200,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()); | ||
|
@@ -166,8 +232,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()); | ||
|
||
|
@@ -179,14 +245,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()); | ||
|
||
|
@@ -203,9 +269,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() { | ||
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() { | ||
// 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()); | ||
} | ||
} |
Oops, something went wrong.