Skip to content

Commit

Permalink
refactor: replace x509-parser with x509-cert
Browse files Browse the repository at this point in the history
x509-cert is from RustCrypto, this replacement will
help to get rid of `ring` dependency.

Signed-off-by: Xynnn007 <xynnn@linux.alibaba.com>
  • Loading branch information
Xynnn007 committed Feb 10, 2023
1 parent 836f567 commit 02d0b3a
Show file tree
Hide file tree
Showing 15 changed files with 304 additions and 178 deletions.
7 changes: 5 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ async-trait = "0.1.52"
base64 = "0.21.0"
cached = { version = "0.42.0", optional = true }
cfg-if = "1.0.0"
chrono = { version = "0.4.23", feature = "clock" }
const-oid = "0.9.1"
der = "0.6.1"
digest = "0.10.3"
ecdsa = { version = "0.15", features = [ "pkcs8", "digest", "der" ] }
ed25519 = { version = "2", features = [ "alloc" ] }
Expand All @@ -71,7 +74,7 @@ pkcs8 = { version = "0.9.0", features = ["pem", "alloc", "pkcs5", "encryption"]
rand = { version = "0.8.5", features = [ "getrandom", "std" ] }
regex = { version = "1.5.5", optional = true }
reqwest = { version = "0.11", default-features = false, features = ["json", "multipart"], optional = true}
rsa = "0.8"
rsa = "0.8.0"
scrypt = "0.10.0"
serde = { version = "1.0.136", features = ["derive"] }
serde_json = "1.0.79"
Expand All @@ -82,7 +85,7 @@ tokio = { version = "1.17.0", features = ["full"] }
tough = { version = "0.12", features = [ "http" ], optional = true }
tracing = "0.1.31"
url = "2.2.2"
x509-parser = { version = "0.14.0", features = ["verify"] }
x509-cert = { version = "0.1.1", features = [ "pem", "std" ] }
xsalsa20poly1305 = "0.9.0"
zeroize = "1.5.7"

Expand Down
40 changes: 27 additions & 13 deletions examples/fulcio/cert/main.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
use const_oid::db::rfc5912::ID_CE_SUBJECT_ALT_NAME;
use pkcs8::der::{Decode, SliceReader};
use sigstore::crypto::SigningScheme;
use sigstore::fulcio::oauth::OauthTokenProvider;
use sigstore::fulcio::{FulcioClient, TokenProvider, FULCIO_ROOT};
use url::Url;
use x509_parser::pem::Pem;
use x509_cert::ext::pkix::SubjectAltName;
use x509_cert::Certificate;

#[tokio::main]
async fn main() {
Expand All @@ -17,18 +20,29 @@ async fn main() {
{
println!("Received certificate chain");

for cert in Pem::iter_from_buffer(cert.as_ref()) {
if let Ok(cert) = cert {
if let Ok(result) = cert.parse_x509() {
if let Ok(san) = result.subject_alternative_name() {
if let Some(san) = san {
let san = san.value;
for name in &san.general_names {
println!("SAN: {}", name);
}
}
}
}
let pems = pem::parse_many(cert.as_ref()).expect("parse pem failed");
for pem in &pems {
let cert = Certificate::from_der(&pem.contents).expect("parse certificate from der");

let subject_alternative_name_slice = cert
.tbs_certificate
.extensions
.as_ref()
.expect("no extension found")
.iter()
.find(|ext| ext.extn_id == ID_CE_SUBJECT_ALT_NAME)
.expect("no subject alternative name found")
.extn_value;

let mut subject_alternative_name_reader =
SliceReader::new(subject_alternative_name_slice)
.expect("read subject alternative name failed");
let subject_alternative_name =
SubjectAltName::decode(&mut subject_alternative_name_reader)
.expect("decode subject alternative name failed");

for name in &subject_alternative_name.0 {
println!("SAN: {name:?}");
}
}
}
Expand Down
13 changes: 8 additions & 5 deletions src/cosign/bundle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@

use base64::{engine::general_purpose::STANDARD as BASE64_STD_ENGINE, Engine as _};
use olpc_cjson::CanonicalFormatter;
use pkcs8::der::Decode;
use serde::{Deserialize, Serialize};
use std::cmp::PartialEq;
use std::convert::TryFrom;
use x509_cert::Certificate;

use crate::crypto::{CosignVerificationKey, Signature};
use crate::errors::{Result, SigstoreError};
Expand Down Expand Up @@ -56,11 +58,12 @@ impl SignedArtifactBundle {
/// SignedArtifactBundle.
pub fn verify_blob(&self, blob: &[u8]) -> Result<()> {
let cert = BASE64_STD_ENGINE.decode(&self.cert)?;
let (_, pem) = x509_parser::pem::parse_x509_pem(&cert)?;
let (_, cert) = x509_parser::parse_x509_certificate(&pem.contents)?;
let subject_public_key = cert.public_key();
let ver_key =
CosignVerificationKey::try_from(subject_public_key).expect("conversion failed");
let pem = pem::parse(cert)?;
let cert = Certificate::from_der(&pem.contents).map_err(|e| {
SigstoreError::PKCS8SpkiError(format!("parse der into cert failed: {e}"))
})?;
let spki = cert.tbs_certificate.subject_public_key_info;
let ver_key = CosignVerificationKey::try_from(&spki).expect("conversion failed");
let signature = Signature::Base64Encoded(self.base64_signature.as_bytes());
ver_key.verify_signature(signature, blob)?;
Ok(())
Expand Down
25 changes: 15 additions & 10 deletions src/cosign/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,22 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use x509_parser::der_parser::{oid, oid::Oid};
use const_oid::ObjectIdentifier;

pub(crate) const SIGSTORE_ISSUER_OID: Oid<'static> = oid!(1.3.6 .1 .4 .1 .57264 .1 .1);
pub(crate) const SIGSTORE_GITHUB_WORKFLOW_TRIGGER_OID: Oid<'static> =
oid!(1.3.6 .1 .4 .1 .57264 .1 .2);
pub(crate) const SIGSTORE_GITHUB_WORKFLOW_SHA_OID: Oid<'static> = oid!(1.3.6 .1 .4 .1 .57264 .1 .3);
pub(crate) const SIGSTORE_GITHUB_WORKFLOW_NAME_OID: Oid<'static> =
oid!(1.3.6 .1 .4 .1 .57264 .1 .4);
pub(crate) const SIGSTORE_GITHUB_WORKFLOW_REPOSITORY_OID: Oid<'static> =
oid!(1.3.6 .1 .4 .1 .57264 .1 .5);
pub(crate) const SIGSTORE_GITHUB_WORKFLOW_REF_OID: Oid<'static> = oid!(1.3.6 .1 .4 .1 .57264 .1 .6);
pub(crate) const SIGSTORE_ISSUER_OID: ObjectIdentifier =
ObjectIdentifier::new_unwrap("1.3.6.1.4.1.57264.1.1");
pub(crate) const SIGSTORE_GITHUB_WORKFLOW_TRIGGER_OID: ObjectIdentifier =
ObjectIdentifier::new_unwrap("1.3.6.1.4.1.57264.1.2");
pub(crate) const SIGSTORE_GITHUB_WORKFLOW_SHA_OID: ObjectIdentifier =
ObjectIdentifier::new_unwrap("1.3.6.1.4.1.57264.1.3");
pub(crate) const SIGSTORE_GITHUB_WORKFLOW_NAME_OID: ObjectIdentifier =
ObjectIdentifier::new_unwrap("1.3.6.1.4.1.57264.1.4");
pub(crate) const SIGSTORE_GITHUB_WORKFLOW_REPOSITORY_OID: ObjectIdentifier =
ObjectIdentifier::new_unwrap("1.3.6.1.4.1.57264.1.5");
pub(crate) const SIGSTORE_GITHUB_WORKFLOW_REF_OID: ObjectIdentifier =
ObjectIdentifier::new_unwrap("1.3.6.1.4.1.57264.1.6");
/// OID of Ed25519, which is not included in the RustCrypto repo yet.
pub(crate) const ED25519: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.3.101.112");

pub(crate) const SIGSTORE_OCI_MEDIA_TYPE: &str = "application/vnd.dev.cosign.simplesigning.v1+json";
pub(crate) const SIGSTORE_SIGNATURE_ANNOTATION: &str = "dev.cosignproject.cosign/signature";
Expand Down
53 changes: 32 additions & 21 deletions src/cosign/signature_layers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,17 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use const_oid::ObjectIdentifier;
use digest::Digest;
use oci_distribution::client::ImageLayer;
use pkcs8::der::Decode;
use serde::Serialize;
use std::convert::TryFrom;
use std::{collections::HashMap, fmt};
use tracing::{debug, info, warn};
use x509_parser::{
certificate::X509Certificate, der_parser::oid::Oid, extensions::GeneralName,
parse_x509_certificate, pem::parse_x509_pem,
};
use x509_cert::ext::pkix::name::GeneralName;
use x509_cert::ext::pkix::SubjectAltName;
use x509_cert::Certificate;

use super::bundle::Bundle;
use super::constants::{
Expand All @@ -33,7 +35,7 @@ use super::constants::{
use crate::crypto::certificate_pool::CertificatePool;
use crate::{
cosign::simple_signing::SimpleSigning,
crypto::{self, CosignVerificationKey, Signature, SigningScheme},
crypto::{self, CosignVerificationKey, Signature},
errors::{Result, SigstoreError},
};

Expand Down Expand Up @@ -428,8 +430,9 @@ impl CertificateSignature {
fulcio_cert_pool: &CertificatePool,
trusted_bundle: &Bundle,
) -> Result<Self> {
let (_, pem) = parse_x509_pem(cert_raw)?;
let (_, cert) = parse_x509_certificate(&pem.contents)?;
let pem = pem::parse(cert_raw)?;
let cert = Certificate::from_der(&pem.contents)
.map_err(|e| SigstoreError::X509Error(format!("parse from der: {e}")))?;
let integrated_time = trusted_bundle.payload.integrated_time;

// ensure the certificate has been issued by Fulcio
Expand All @@ -439,7 +442,7 @@ impl CertificateSignature {

let subject = CertificateSubject::from_certificate(&cert)?;
let verification_key =
CosignVerificationKey::from_der(cert.public_key().raw, &SigningScheme::default())?;
CosignVerificationKey::try_from(&cert.tbs_certificate.subject_public_key_info)?;

let issuer = get_cert_extension_by_oid(&cert, SIGSTORE_ISSUER_OID, "Issuer")?;

Expand Down Expand Up @@ -487,15 +490,21 @@ impl CertificateSignature {
}

fn get_cert_extension_by_oid(
cert: &X509Certificate,
ext_oid: Oid,
cert: &Certificate,
ext_oid: ObjectIdentifier,
ext_oid_name: &str,
) -> Result<Option<String>> {
let extension = cert.tbs_certificate.get_extension_unique(&ext_oid)?;
extension
cert.tbs_certificate
.extensions
.as_ref()
.ok_or(SigstoreError::X509Error(
"Certificate's extension is empty".to_string(),
))?
.iter()
.find(|ext| ext.extn_id == ext_oid)
.map(|ext| {
String::from_utf8(ext.value.to_vec()).map_err(|_| {
SigstoreError::UnexpectedError(format!(
String::from_utf8(ext.extn_value.to_vec()).map_err(|_| {
SigstoreError::X509Error(format!(
"Certificate's extension Sigstore {ext_oid_name} is not UTF8 compatible"
))
})
Expand All @@ -504,18 +513,19 @@ fn get_cert_extension_by_oid(
}

impl CertificateSubject {
pub fn from_certificate(certificate: &X509Certificate) -> Result<CertificateSubject> {
let subject_alternative_name = certificate
pub fn from_certificate(certificate: &Certificate) -> Result<CertificateSubject> {
let (_, san) = certificate
.tbs_certificate
.subject_alternative_name()?
.ok_or(SigstoreError::CertificateWithoutSubjectAlternativeName)?;
.get::<SubjectAltName>()
.map_err(|e| SigstoreError::PKCS8Error(format!("get SAN ext failed: {e}")))?
.ok_or(SigstoreError::PKCS8Error("No SAN ext found".to_string()))?;

for general_name in &subject_alternative_name.value.general_names {
if let GeneralName::RFC822Name(name) = general_name {
for general_name in &san.0 {
if let GeneralName::Rfc822Name(name) = general_name {
return Ok(CertificateSubject::Email(name.to_string()));
}

if let GeneralName::URI(uri) = general_name {
if let GeneralName::UniformResourceIdentifier(uri) = general_name {
return Ok(CertificateSubject::Uri(uri.to_string()));
}
}
Expand Down Expand Up @@ -854,6 +864,7 @@ JsB89BPhZYch0U0hKANx5TY+ncrm0s8bfJxxHoenAEFhwhuXeb4PqIrtoQ==
// Testing CertificateSignature
use crate::cosign::bundle::Payload;
use crate::crypto::tests::{generate_certificate, CertGenerationOptions};
use crate::crypto::SigningScheme;
use chrono::{Duration, Utc};

impl TryFrom<X509> for crate::registry::Certificate {
Expand Down
62 changes: 40 additions & 22 deletions src/cosign/verification_constraint/certificate_verifier.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
use chrono::{DateTime, NaiveDateTime, Utc};
use pkcs8::der::Decode;
use std::convert::TryFrom;
use tracing::warn;
use x509_parser::{
certificate::X509Certificate,
pem::parse_x509_pem,
prelude::{ASN1Time, FromDer},
};
use x509_cert::Certificate;

use super::VerificationConstraint;
use crate::cosign::signature_layers::SignatureLayer;
use crate::crypto::{certificate_pool::CertificatePool, CosignVerificationKey};
use crate::errors::Result;
use crate::errors::{Result, SigstoreError};

/// Verify signature layers using the public key defined inside of a x509 certificate
#[derive(Debug)]
pub struct CertificateVerifier {
cert_verification_key: CosignVerificationKey,
cert_validity: x509_parser::certificate::Validity,
cert_validity: x509_cert::time::Validity,
require_rekor_bundle: bool,
}

Expand All @@ -36,7 +34,7 @@ impl CertificateVerifier {
require_rekor_bundle: bool,
cert_chain: Option<&[crate::registry::Certificate]>,
) -> Result<Self> {
let (_, pem) = parse_x509_pem(cert_bytes)?;
let pem = pem::parse(cert_bytes)?;
Self::from_der(&pem.contents, require_rekor_bundle, cert_chain)
}

Expand All @@ -56,7 +54,8 @@ impl CertificateVerifier {
require_rekor_bundle: bool,
cert_chain: Option<&[crate::registry::Certificate]>,
) -> Result<Self> {
let (_, cert) = X509Certificate::from_der(cert_bytes)?;
let cert = Certificate::from_der(cert_bytes)
.map_err(|e| SigstoreError::X509Error(format!("parse from der {e}")))?;
crate::crypto::certificate::verify_key_usages(&cert)?;
crate::crypto::certificate::verify_has_san(&cert)?;
crate::crypto::certificate::verify_validity(&cert)?;
Expand All @@ -66,12 +65,12 @@ impl CertificateVerifier {
cert_pool.verify_der_cert(cert_bytes)?;
}

let subject_public_key_info = cert.public_key();
let subject_public_key_info = &cert.tbs_certificate.subject_public_key_info;
let cosign_verification_key = CosignVerificationKey::try_from(subject_public_key_info)?;

Ok(Self {
cert_verification_key: cosign_verification_key,
cert_validity: cert.validity().to_owned(),
cert_validity: cert.tbs_certificate.validity,
require_rekor_bundle,
})
}
Expand All @@ -84,8 +83,15 @@ impl VerificationConstraint for CertificateVerifier {
}
match &signature_layer.bundle {
Some(bundle) => {
let it = ASN1Time::from_timestamp(bundle.payload.integrated_time)?;
if it < self.cert_validity.not_before {
let it = DateTime::<Utc>::from_utc(
NaiveDateTime::from_timestamp_opt(bundle.payload.integrated_time, 0).ok_or(
SigstoreError::UnexpectedError("timestamp is not legal".into()),
)?,
Utc,
);
let not_before: DateTime<Utc> =
self.cert_validity.not_before.to_system_time().into();
if it < not_before {
warn!(
integrated_time = it.to_string(),
not_before = self.cert_validity.not_before.to_string(),
Expand All @@ -94,7 +100,8 @@ impl VerificationConstraint for CertificateVerifier {
return Ok(false);
}

if it > self.cert_validity.not_after {
let not_after: DateTime<Utc> = self.cert_validity.not_after.to_system_time().into();
if it > not_after {
warn!(
integrated_time = it.to_string(),
not_after = self.cert_validity.not_after.to_string(),
Expand All @@ -118,12 +125,16 @@ impl VerificationConstraint for CertificateVerifier {

#[cfg(test)]
mod tests {
use std::time::{Duration, SystemTime};

use super::*;
use crate::cosign::bundle::Bundle;
use crate::crypto::tests::*;
use crate::registry;

use pkcs8::der::asn1::UtcTime;
use serde_json::json;
use x509_cert::time::{Time, Validity};

#[test]
fn verify_certificate_() -> anyhow::Result<()> {
Expand Down Expand Up @@ -262,14 +273,21 @@ RAIgPixAn47x4qLpu7Y/d0oyvbnOGtD5cY7rywdMOO7LYRsCIDsCyGUZIYMFfSrt

let mut vc = CertificateVerifier::from_pem(cert_pem_raw.as_bytes(), true, None)
.expect("cannot create verification constraint");
let now = ASN1Time::now().timestamp();
let not_before =
ASN1Time::from_timestamp(now - 60).expect("cannot create not_before timestamp");
let not_after =
ASN1Time::from_timestamp(now + 60).expect("cannot create not_after timestamp");
let validity = x509_parser::certificate::Validity {
not_before,
not_after,
let not_before = UtcTime::from_system_time(
SystemTime::now()
.checked_sub(Duration::from_secs(60))
.expect("cannot sub time by 60 seconds"),
)
.expect("cannot create not_before timestamp");
let not_after = UtcTime::from_system_time(
SystemTime::now()
.checked_add(Duration::from_secs(60))
.expect("cannot add time by 60 seconds"),
)
.expect("cannot create not_after timestamp");
let validity = Validity {
not_before: Time::UtcTime(not_before),
not_after: Time::UtcTime(not_after),
};
vc.cert_validity = validity;
assert!(!vc.verify(&signature_layer).expect("error while verifying"));
Expand Down
Loading

0 comments on commit 02d0b3a

Please sign in to comment.