use std::net::IpAddr;
use std::time::{SystemTime, UNIX_EPOCH};
use pyo3::exceptions::PyValueError;
use pyo3::prelude::*;
use pyo3::types::PyBytes;
use synta::{Decoder, Encoding};
use synta_certificate::{default_signature_verifier, Certificate, ErasedSignatureVerifier};
use synta_x509_verification::{
ocsp::OcspStore,
ops::VerificationCertificate,
policy::{NameMatchMode, PolicyDefinition, Subject, ValidationProfile, VerificationPolicy},
revocation::CrlStore,
trust_store::OwnedStore,
types::{DNSName, IPAddress},
verify, RevocationChecks,
};
use crate::error::SyntaErr;
use crate::install_submodule;
pyo3::create_exception!(
synta.x509,
X509VerificationError,
pyo3::exceptions::PyException
);
#[pyclass(frozen, name = "TrustStore")]
pub struct PyTrustStore {
store: OwnedStore,
}
#[pymethods]
impl PyTrustStore {
#[new]
fn new(certs_der: Vec<Vec<u8>>) -> PyResult<Self> {
let store =
OwnedStore::try_new(certs_der.iter().map(|v| v.as_slice())).map_err(SyntaErr)?;
Ok(PyTrustStore { store })
}
fn __repr__(&self) -> String {
format!("TrustStore(<{} certificate(s)>)", self.store.len())
}
#[getter]
fn len(&self) -> usize {
self.store.len()
}
}
#[pyclass(frozen, name = "CrlStore")]
pub struct PyCrlStore {
crl_ders: Vec<Vec<u8>>,
}
#[pymethods]
impl PyCrlStore {
#[new]
fn new(crl_ders: Vec<Vec<u8>>) -> Self {
PyCrlStore { crl_ders }
}
fn __repr__(&self) -> String {
format!("CrlStore(<{} CRL(s)>)", self.crl_ders.len())
}
#[getter]
fn len(&self) -> usize {
self.crl_ders.len()
}
}
#[pyclass(frozen, name = "OcspStore")]
pub struct PyOcspStore {
ocsp_ders: Vec<Vec<u8>>,
}
#[pymethods]
impl PyOcspStore {
#[new]
fn new(ocsp_ders: Vec<Vec<u8>>) -> Self {
PyOcspStore { ocsp_ders }
}
fn __repr__(&self) -> String {
format!("OcspStore(<{} OCSP response(s)>)", self.ocsp_ders.len())
}
#[getter]
fn len(&self) -> usize {
self.ocsp_ders.len()
}
}
#[pyclass(name = "VerificationPolicy")]
pub struct PyVerificationPolicy {
inner: VerificationPolicy,
}
#[pymethods]
impl PyVerificationPolicy {
#[new]
#[pyo3(
signature = (*, server_names=None, name_match=None, validation_time=None, max_chain_depth=8, profile=None)
)]
fn new(
server_names: Option<Vec<String>>,
name_match: Option<String>,
validation_time: Option<i64>,
max_chain_depth: u8,
profile: Option<String>,
) -> PyResult<Self> {
let vprofile = match profile.as_deref() {
None | Some("webpki") => ValidationProfile::WebPki,
Some("rfc5280") => ValidationProfile::Rfc5280,
Some(other) => {
return Err(PyValueError::new_err(format!(
"unknown validation profile {other:?}; expected \"webpki\" or \"rfc5280\""
)));
}
};
let vmatch = match name_match.as_deref() {
None | Some("any") => NameMatchMode::Any,
Some("all") => NameMatchMode::All,
Some(other) => {
return Err(PyValueError::new_err(format!(
"unknown name_match {other:?}; expected \"any\" or \"all\""
)));
}
};
Ok(PyVerificationPolicy {
inner: VerificationPolicy {
server_names: server_names.unwrap_or_default(),
name_match: vmatch,
validation_time,
max_chain_depth,
profile: vprofile,
},
})
}
fn __repr__(&self) -> String {
let profile = match self.inner.profile {
ValidationProfile::WebPki => "webpki",
ValidationProfile::Rfc5280 => "rfc5280",
};
let name_match = match self.inner.name_match {
NameMatchMode::Any => "any",
NameMatchMode::All => "all",
};
format!(
"VerificationPolicy(server_names={:?}, name_match={:?}, profile={:?}, \
max_chain_depth={}, validation_time={:?})",
self.inner.server_names,
name_match,
profile,
self.inner.max_chain_depth,
self.inner.validation_time,
)
}
}
fn now_unix() -> i64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs() as i64)
.unwrap_or(0)
}
fn parse_vcert(der: &[u8]) -> PyResult<Certificate<'_>> {
Ok(Decoder::new(der, Encoding::Der)
.decode::<Certificate>()
.map_err(SyntaErr)?)
}
fn run_verify<'a>(
leaf_der: &'a [u8],
intermediate_ders: &'a [Vec<u8>],
trust_store: &'a PyTrustStore,
policy: PolicyDefinition<'a, Box<dyn ErasedSignatureVerifier>>,
crls: Option<&'a CrlStore>,
ocsp: Option<&'a OcspStore>,
) -> PyResult<Vec<Vec<u8>>> {
let leaf_cert = parse_vcert(leaf_der)?;
let leaf_vcert = VerificationCertificate::new(leaf_cert, leaf_der);
let mut intermediate_vcerts = Vec::with_capacity(intermediate_ders.len());
for der in intermediate_ders {
let cert = parse_vcert(der.as_slice())?;
intermediate_vcerts.push(VerificationCertificate::new(cert, der.as_slice()));
}
let store = trust_store.store.as_store();
verify(
&leaf_vcert,
&intermediate_vcerts,
&policy,
store,
RevocationChecks { crls, ocsp },
)
.map(|chain| chain.into_iter().map(|vc| vc.der().to_vec()).collect())
.map_err(|e| X509VerificationError::new_err(e.to_string()))
}
fn parse_subject(name: &str) -> PyResult<Subject<'_>> {
if let Ok(ip) = name.parse::<IpAddr>() {
let addr = match ip {
IpAddr::V4(a) => IPAddress::from_bytes(&a.octets()),
IpAddr::V6(a) => IPAddress::from_bytes(&a.octets()),
};
Ok(Subject::Ip(addr.ok_or_else(|| {
PyValueError::new_err(format!("invalid IP address for server name: {name}"))
})?))
} else {
Ok(Subject::Dns(DNSName::new(name).ok_or_else(|| {
PyValueError::new_err(format!("invalid DNS name for server name: {name:?}"))
})?))
}
}
#[pyfunction]
#[pyo3(signature = (leaf_der, intermediates_der, trust_store, policy=None, crls=None, ocsp=None))]
fn verify_server_certificate<'py>(
py: Python<'py>,
leaf_der: Vec<u8>,
intermediates_der: Vec<Vec<u8>>,
trust_store: &PyTrustStore,
policy: Option<&PyVerificationPolicy>,
crls: Option<&PyCrlStore>,
ocsp: Option<&PyOcspStore>,
) -> PyResult<Vec<Bound<'py, PyBytes>>> {
let default_policy;
let vp: &VerificationPolicy = match policy {
Some(p) => &p.inner,
None => {
default_policy = VerificationPolicy::new_client();
&default_policy
}
};
let now = vp.validation_time.unwrap_or_else(now_unix);
let subjects = vp
.server_names
.iter()
.map(|name| parse_subject(name))
.collect::<PyResult<Vec<_>>>()?;
let mut pd = PolicyDefinition::new_server(default_signature_verifier(), subjects, now);
pd.profile = vp.profile;
pd.max_chain_depth = vp.max_chain_depth;
pd.name_match = vp.name_match;
let mut crl_store = CrlStore::new();
if let Some(py_crls) = crls {
for der in &py_crls.crl_ders {
crl_store.add_der(der.clone());
}
}
let crl_opt = crls.map(|_| &crl_store);
let mut ocsp_store = OcspStore::new();
if let Some(py_ocsp) = ocsp {
for der in &py_ocsp.ocsp_ders {
ocsp_store.add_der(der.clone());
}
}
let ocsp_opt = ocsp.map(|_| &ocsp_store);
run_verify(
leaf_der.as_slice(),
&intermediates_der,
trust_store,
pd,
crl_opt,
ocsp_opt,
)?
.into_iter()
.map(|der| Ok(PyBytes::new(py, &der)))
.collect()
}
#[pyfunction]
#[pyo3(signature = (leaf_der, intermediates_der, trust_store, policy=None, crls=None, ocsp=None))]
fn verify_client_certificate<'py>(
py: Python<'py>,
leaf_der: Vec<u8>,
intermediates_der: Vec<Vec<u8>>,
trust_store: &PyTrustStore,
policy: Option<&PyVerificationPolicy>,
crls: Option<&PyCrlStore>,
ocsp: Option<&PyOcspStore>,
) -> PyResult<Vec<Bound<'py, PyBytes>>> {
let default_policy;
let vp: &VerificationPolicy = match policy {
Some(p) => &p.inner,
None => {
default_policy = VerificationPolicy::new_client();
&default_policy
}
};
let now = vp.validation_time.unwrap_or_else(now_unix);
let mut pd = PolicyDefinition::new_client(default_signature_verifier(), now);
pd.profile = vp.profile;
pd.max_chain_depth = vp.max_chain_depth;
let mut crl_store = CrlStore::new();
if let Some(py_crls) = crls {
for der in &py_crls.crl_ders {
crl_store.add_der(der.clone());
}
}
let crl_opt = crls.map(|_| &crl_store);
let mut ocsp_store = OcspStore::new();
if let Some(py_ocsp) = ocsp {
for der in &py_ocsp.ocsp_ders {
ocsp_store.add_der(der.clone());
}
}
let ocsp_opt = ocsp.map(|_| &ocsp_store);
run_verify(
leaf_der.as_slice(),
&intermediates_der,
trust_store,
pd,
crl_opt,
ocsp_opt,
)?
.into_iter()
.map(|der| Ok(PyBytes::new(py, &der)))
.collect()
}
pub fn register_x509_module(parent: &Bound<'_, PyModule>) -> PyResult<()> {
let py = parent.py();
let m = PyModule::new(py, "x509")?;
m.add(
"X509VerificationError",
py.get_type::<X509VerificationError>(),
)?;
m.add_class::<PyTrustStore>()?;
m.add_class::<PyCrlStore>()?;
m.add_class::<PyOcspStore>()?;
m.add_class::<PyVerificationPolicy>()?;
m.add_function(wrap_pyfunction!(verify_server_certificate, &m)?)?;
m.add_function(wrap_pyfunction!(verify_client_certificate, &m)?)?;
install_submodule(
parent,
&m,
"synta.x509",
Some("RFC 5280 X.509 certificate chain verification."),
)?;
Ok(())
}