use super::{OID, PUBLIC_KEY_SIZE, PublicKey, SecretKey};
use crate::pem;
use crate::x509::{self, ValidityCheck};
use der::Encode;
use x509_cert::ext::pkix::{KeyUsage, KeyUsages};
pub fn issue_cert_der(
subject: &PublicKey,
issuer: &SecretKey,
template: &x509::Certificate,
) -> x509::Result<Vec<u8>> {
let default_key_usage = match template.role {
x509::Role::Authority { .. } => KeyUsage(KeyUsages::KeyCertSign | KeyUsages::CRLSign),
x509::Role::Leaf => KeyUsage(KeyUsages::DigitalSignature.into()),
};
Ok(x509::issue_cert(
&subject.to_bytes(),
OID,
default_key_usage,
issuer,
template,
)?
.to_der()?)
}
pub fn issue_cert_pem(
subject: &PublicKey,
issuer: &SecretKey,
template: &x509::Certificate,
) -> x509::Result<String> {
let der = issue_cert_der(subject, issuer, template)?;
Ok(pem::encode("CERTIFICATE", &der))
}
pub fn verify_cert_der(
der: &[u8],
issuer: &PublicKey,
validity: ValidityCheck,
) -> x509::Result<x509::Verified<PublicKey>> {
let (key_bytes, cert, key_usage) = x509::verify_cert::<PUBLIC_KEY_SIZE>(der, issuer, validity)?;
let required: u16 = match cert.role {
x509::Role::Authority { .. } => (1 << 5) | (1 << 6), x509::Role::Leaf => 1 << 0, };
if key_usage & required != required {
return Err(x509::Error::InvalidKeyUsage {
details: "xDSA key usage does not match certificate role",
});
}
Ok(x509::Verified {
public_key: PublicKey::from_bytes(&key_bytes)?,
cert,
})
}
pub fn verify_cert_pem(
pem_data: &str,
issuer: &PublicKey,
validity: ValidityCheck,
) -> x509::Result<x509::Verified<PublicKey>> {
let (tag, der) = pem::decode(pem_data.as_bytes())?;
if tag != "CERTIFICATE" {
return Err(x509::Error::External(
format!("invalid PEM tag {}", tag).into(),
));
}
verify_cert_der(&der, issuer, validity)
}
pub fn verify_cert_der_with_issuer(
der: &[u8],
issuer_cert: &x509::Verified<PublicKey>,
validity: ValidityCheck,
) -> x509::Result<x509::Verified<PublicKey>> {
let cert = verify_cert_der(der, &issuer_cert.public_key, validity)?;
enforce_issuer_chaining(&cert, issuer_cert)?;
Ok(cert)
}
pub fn verify_cert_pem_with_issuer(
pem_data: &str,
issuer_cert: &x509::Verified<PublicKey>,
validity: ValidityCheck,
) -> x509::Result<x509::Verified<PublicKey>> {
let cert = verify_cert_pem(pem_data, &issuer_cert.public_key, validity)?;
enforce_issuer_chaining(&cert, issuer_cert)?;
Ok(cert)
}
fn enforce_issuer_chaining(
cert: &x509::Verified<PublicKey>,
issuer_cert: &x509::Verified<PublicKey>,
) -> x509::Result<()> {
let path_len = match &issuer_cert.cert.role {
x509::Role::Authority { path_len } => path_len,
x509::Role::Leaf => {
return Err(x509::Error::InvalidIssuer {
details: "not a CA",
});
}
};
if matches!(cert.cert.role, x509::Role::Authority { .. }) && *path_len == Some(0) {
return Err(x509::Error::InvalidIssuer {
details: "pathLenConstraint forbids CA certificates",
});
}
if cert.cert.issuer != issuer_cert.cert.subject {
return Err(x509::Error::InvalidIssuer {
details: "issuer DN does not match",
});
}
Ok(())
}