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

Migrate PolicyBuilder to Rust #10069

Merged
merged 1 commit into from
Jan 3, 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
17 changes: 11 additions & 6 deletions src/cryptography/hazmat/bindings/_rust/x509.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -38,19 +38,21 @@ def create_x509_crl(
hash_algorithm: hashes.HashAlgorithm | None,
rsa_padding: PKCS1v15 | PSS | None,
) -> x509.CertificateRevocationList: ...
def create_server_verifier(
name: x509.verification.Subject,
store: Store,
time: datetime.datetime | None,
max_chain_depth: int | None,
) -> x509.verification.ServerVerifier: ...

class Sct: ...
class Certificate: ...
class RevokedCertificate: ...
class CertificateRevocationList: ...
class CertificateSigningRequest: ...

class PolicyBuilder:
def time(self, new_time: datetime.datetime) -> PolicyBuilder: ...
def store(self, new_store: Store) -> PolicyBuilder: ...
def max_chain_depth(self, new_max_chain_depth: int) -> PolicyBuilder: ...
def build_server_verifier(
self, subject: x509.verification.Subject
) -> ServerVerifier: ...

class ServerVerifier:
@property
def subject(self) -> x509.verification.Subject: ...
Expand All @@ -68,3 +70,6 @@ class ServerVerifier:

class Store:
def __init__(self, certs: list[x509.Certificate]) -> None: ...

class VerificationError(Exception):
pass
86 changes: 9 additions & 77 deletions src/cryptography/x509/verification.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,89 +4,21 @@

from __future__ import annotations

import datetime
import typing

from cryptography.hazmat.bindings._rust import x509 as rust_x509
from cryptography.x509.general_name import DNSName, IPAddress

__all__ = ["Store", "Subject", "ServerVerifier", "PolicyBuilder"]
__all__ = [
"Store",
"Subject",
"ServerVerifier",
"PolicyBuilder",
"VerificationError",
]

Store = rust_x509.Store

Subject = typing.Union[DNSName, IPAddress]

ServerVerifier = rust_x509.ServerVerifier


class VerificationError(Exception):
pass


class PolicyBuilder:
def __init__(
self,
*,
time: datetime.datetime | None = None,
store: Store | None = None,
max_chain_depth: int | None = None,
):
self._time = time
self._store = store
self._max_chain_depth = max_chain_depth

def time(self, new_time: datetime.datetime) -> PolicyBuilder:
"""
Sets the validation time.
"""
if self._time is not None:
raise ValueError("The validation time may only be set once.")

return PolicyBuilder(
time=new_time,
store=self._store,
max_chain_depth=self._max_chain_depth,
)

def store(self, new_store: Store) -> PolicyBuilder:
"""
Sets the trust store.
"""

if self._store is not None:
raise ValueError("The trust store may only be set once.")

return PolicyBuilder(
time=self._time,
store=new_store,
max_chain_depth=self._max_chain_depth,
)

def max_chain_depth(self, new_max_chain_depth: int) -> PolicyBuilder:
"""
Sets the maximum chain depth.
"""

if self._max_chain_depth is not None:
raise ValueError("The maximum chain depth may only be set once.")

return PolicyBuilder(
time=self._time,
store=self._store,
max_chain_depth=new_max_chain_depth,
)

def build_server_verifier(self, subject: Subject) -> ServerVerifier:
"""
Builds a verifier for verifying server certificates.
"""

if self._store is None:
raise ValueError("A server verifier must have a trust store")

return rust_x509.create_server_verifier(
subject,
self._store,
self._time,
self._max_chain_depth,
)
PolicyBuilder = rust_x509.PolicyBuilder
VerificationError = rust_x509.VerificationError
1 change: 0 additions & 1 deletion src/rust/src/exceptions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ pyo3::import_exception!(cryptography.x509, AttributeNotFound);
pyo3::import_exception!(cryptography.x509, DuplicateExtension);
pyo3::import_exception!(cryptography.x509, UnsupportedGeneralNameType);
pyo3::import_exception!(cryptography.x509, InvalidVersion);
pyo3::import_exception!(cryptography.x509.verification, VerificationError);

pub(crate) fn create_submodule(py: pyo3::Python<'_>) -> pyo3::PyResult<&pyo3::prelude::PyModule> {
let submod = pyo3::prelude::PyModule::new(py, "exceptions")?;
Expand Down
158 changes: 122 additions & 36 deletions src/rust/src/x509/verify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,11 @@ use cryptography_x509_verification::{
};

use crate::backend::keys;
use crate::error::{CryptographyError, CryptographyResult};
use crate::types;
use crate::x509::certificate::Certificate as PyCertificate;
use crate::x509::common::{datetime_now, datetime_to_py, py_to_datetime};
use crate::x509::sign;
use crate::{
error::{CryptographyError, CryptographyResult},
exceptions::VerificationError,
};

pub(crate) struct PyCryptoOps {}

Expand Down Expand Up @@ -46,6 +43,121 @@ impl CryptoOps for PyCryptoOps {
}
}

pyo3::create_exception!(
cryptography.hazmat.bindings._rust.x509,
VerificationError,
pyo3::exceptions::PyException
);

#[pyo3::pyclass(frozen, module = "cryptography.x509.verification")]
struct PolicyBuilder {
time: Option<asn1::DateTime>,
store: Option<pyo3::Py<PyStore>>,
max_chain_depth: Option<u8>,
}

#[pyo3::pymethods]
impl PolicyBuilder {
#[new]
fn new() -> PolicyBuilder {
PolicyBuilder {
time: None,
store: None,
max_chain_depth: None,
}
}

fn time(
&self,
py: pyo3::Python<'_>,
new_time: &pyo3::PyAny,
) -> CryptographyResult<PolicyBuilder> {
if self.time.is_some() {
return Err(CryptographyError::from(
pyo3::exceptions::PyValueError::new_err(
"The validation time may only be set once.",
),
));
}
Ok(PolicyBuilder {
time: Some(py_to_datetime(py, new_time)?),
store: self.store.as_ref().map(|s| s.clone_ref(py)),
max_chain_depth: self.max_chain_depth,
})
}

fn store(&self, new_store: pyo3::Py<PyStore>) -> CryptographyResult<PolicyBuilder> {
if self.store.is_some() {
return Err(CryptographyError::from(
pyo3::exceptions::PyValueError::new_err("The trust store may only be set once."),
));
}
Ok(PolicyBuilder {
time: self.time.clone(),
store: Some(new_store),
max_chain_depth: self.max_chain_depth,
})
}

fn max_chain_depth(
&self,
py: pyo3::Python<'_>,
new_max_chain_depth: u8,
) -> CryptographyResult<PolicyBuilder> {
if self.max_chain_depth.is_some() {
return Err(CryptographyError::from(
pyo3::exceptions::PyValueError::new_err(
"The maximum chain depth may only be set once.",
),
));
}
Ok(PolicyBuilder {
time: self.time.clone(),
store: self.store.as_ref().map(|s| s.clone_ref(py)),
max_chain_depth: Some(new_max_chain_depth),
})
}

fn build_server_verifier(
&self,
py: pyo3::Python<'_>,
subject: pyo3::PyObject,
) -> CryptographyResult<PyServerVerifier> {
let store = match self.store.as_ref() {
Some(s) => s.clone_ref(py),
None => {
return Err(CryptographyError::from(
pyo3::exceptions::PyValueError::new_err(
"A server verifier must have a trust store.",
),
));
}
};

let time = match self.time.as_ref() {
Some(t) => t.clone(),
None => datetime_now(py)?,
};
let subject_owner = build_subject_owner(py, &subject)?;

let policy = OwnedPolicy::try_new(subject_owner, |subject_owner| {
let subject = build_subject(py, subject_owner)?;
Ok::<PyCryptoPolicy<'_>, pyo3::PyErr>(PyCryptoPolicy(Policy::new(
PyCryptoOps {},
subject,
time,
self.max_chain_depth,
)))
})?;

Ok(PyServerVerifier {
py_subject: subject,
policy,
store,
})
}
}

struct PyCryptoPolicy<'a>(Policy<'a, PyCryptoOps>);

/// This enum exists solely to provide heterogeneously typed ownership for `OwnedPolicy`.
Expand All @@ -69,6 +181,7 @@ self_cell::self_cell!(
);

#[pyo3::pyclass(
frozen,
name = "ServerVerifier",
module = "cryptography.hazmat.bindings._rust.x509"
)]
Expand Down Expand Up @@ -173,37 +286,6 @@ fn build_subject<'a>(
}
}

#[pyo3::prelude::pyfunction]
fn create_server_verifier(
py: pyo3::Python<'_>,
subject: pyo3::Py<pyo3::PyAny>,
store: pyo3::Py<PyStore>,
time: Option<&pyo3::PyAny>,
max_chain_depth: Option<u8>,
) -> pyo3::PyResult<PyServerVerifier> {
let time = match time {
Some(time) => py_to_datetime(py, time)?,
None => datetime_now(py)?,
};

let subject_owner = build_subject_owner(py, &subject)?;
let policy = OwnedPolicy::try_new(subject_owner, |subject_owner| {
let subject = build_subject(py, subject_owner)?;
Ok::<PyCryptoPolicy<'_>, pyo3::PyErr>(PyCryptoPolicy(Policy::new(
PyCryptoOps {},
subject,
time,
max_chain_depth,
)))
})?;

Ok(PyServerVerifier {
py_subject: subject,
policy,
store,
})
}

type PyCryptoOpsStore<'a> = Store<'a, PyCryptoOps>;

self_cell::self_cell!(
Expand Down Expand Up @@ -249,7 +331,11 @@ impl PyStore {
pub(crate) fn add_to_module(module: &pyo3::prelude::PyModule) -> pyo3::PyResult<()> {
module.add_class::<PyServerVerifier>()?;
module.add_class::<PyStore>()?;
module.add_function(pyo3::wrap_pyfunction!(create_server_verifier, module)?)?;
module.add_class::<PolicyBuilder>()?;
module.add(
"VerificationError",
module.py().get_type::<VerificationError>(),
)?;

Ok(())
}
14 changes: 7 additions & 7 deletions tests/x509/verification/test_limbo.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,13 +113,13 @@ def _limbo_testcase(id_, testcase):
max_chain_depth = testcase["max_chain_depth"]
should_pass = testcase["expected_result"] == "SUCCESS"

verifier = (
PolicyBuilder()
.time(validation_time)
.store(Store(trusted_certs))
.max_chain_depth(max_chain_depth)
.build_server_verifier(peer_name)
)
builder = PolicyBuilder().store(Store(trusted_certs))
if validation_time is not None:
builder = builder.time(validation_time)
if max_chain_depth is not None:
builder = builder.max_chain_depth(max_chain_depth)

verifier = builder.build_server_verifier(peer_name)

if should_pass:
built_chain = verifier.verify(
Expand Down
Loading