summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAlex Gaynor <alex.gaynor@gmail.com>2021-10-24 20:19:53 -0400
committerGitHub <noreply@github.com>2021-10-25 08:19:53 +0800
commit16bfb57fc7ae67cab59c3dfb9338d33ba4f6e681 (patch)
tree8d5e8146e43f22bcaa9cdeb0c62c4806e88433be /src
parent6f20e0674a075930324fba012cf14c2a56fd742d (diff)
downloadcryptography-16bfb57fc7ae67cab59c3dfb9338d33ba4f6e681.tar.gz
Port OCSP Response generation to Rust (#6460)
* xxx * The rest * file * first milestone! * progress * Good progress * Aaaand, tests pass! * linter fixes * moar linting * moar linting * style on that coverage * Flesh this out * reformat * Remove RSA+DSA support, will be added back later * Refactor to avoid todo!() branch * sha384 support * Unused * Suggesting I learn to spell? It's a bold move cotton, let's see how it pays off
Diffstat (limited to 'src')
-rw-r--r--src/_cffi_src/build_openssl.py1
-rw-r--r--src/_cffi_src/openssl/ocsp.py40
-rw-r--r--src/cryptography/hazmat/backends/openssl/backend.py123
-rw-r--r--src/cryptography/hazmat/bindings/_rust/ocsp.pyi13
-rw-r--r--src/cryptography/x509/ocsp.py8
-rw-r--r--src/rust/Cargo.lock2
-rw-r--r--src/rust/Cargo.toml2
-rw-r--r--src/rust/src/x509/certificate.rs10
-rw-r--r--src/rust/src/x509/common.rs6
-rw-r--r--src/rust/src/x509/mod.rs1
-rw-r--r--src/rust/src/x509/ocsp_resp.rs222
-rw-r--r--src/rust/src/x509/sign.rs137
12 files changed, 368 insertions, 197 deletions
diff --git a/src/_cffi_src/build_openssl.py b/src/_cffi_src/build_openssl.py
index 2ee3f4bff..95e9d138e 100644
--- a/src/_cffi_src/build_openssl.py
+++ b/src/_cffi_src/build_openssl.py
@@ -99,7 +99,6 @@ ffi = build_ffi_for_binding(
"hmac",
"nid",
"objects",
- "ocsp",
"opensslv",
"osrandom_engine",
"pem",
diff --git a/src/_cffi_src/openssl/ocsp.py b/src/_cffi_src/openssl/ocsp.py
deleted file mode 100644
index 093805a1c..000000000
--- a/src/_cffi_src/openssl/ocsp.py
+++ /dev/null
@@ -1,40 +0,0 @@
-# This file is dual licensed under the terms of the Apache License, Version
-# 2.0, and the BSD License. See the LICENSE file in the root of this repository
-# for complete details.
-
-
-INCLUDES = """
-#include <openssl/ocsp.h>
-"""
-
-TYPES = """
-typedef ... OCSP_ONEREQ;
-typedef ... OCSP_RESPONSE;
-typedef ... OCSP_BASICRESP;
-typedef ... OCSP_SINGLERESP;
-typedef ... OCSP_CERTID;
-static const long OCSP_NOCERTS;
-static const long OCSP_RESPID_KEY;
-"""
-
-FUNCTIONS = """
-OCSP_CERTID *OCSP_cert_to_id(const EVP_MD *, const X509 *, const X509 *);
-void OCSP_CERTID_free(OCSP_CERTID *);
-
-OCSP_BASICRESP *OCSP_BASICRESP_new(void);
-void OCSP_BASICRESP_free(OCSP_BASICRESP *);
-OCSP_SINGLERESP *OCSP_basic_add1_status(OCSP_BASICRESP *, OCSP_CERTID *, int,
- int, ASN1_TIME *, ASN1_TIME *,
- ASN1_TIME *);
-int OCSP_basic_add1_cert(OCSP_BASICRESP *, X509 *);
-int OCSP_BASICRESP_add_ext(OCSP_BASICRESP *, X509_EXTENSION *, int);
-int OCSP_basic_sign(OCSP_BASICRESP *, X509 *, EVP_PKEY *, const EVP_MD *,
- Cryptography_STACK_OF_X509 *, unsigned long);
-OCSP_RESPONSE *OCSP_response_create(int, OCSP_BASICRESP *);
-void OCSP_RESPONSE_free(OCSP_RESPONSE *);
-
-int i2d_OCSP_RESPONSE_bio(BIO *, OCSP_RESPONSE *);
-"""
-
-CUSTOMIZATIONS = """
-"""
diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py
index 764f8b781..23cebc892 100644
--- a/src/cryptography/hazmat/backends/openssl/backend.py
+++ b/src/cryptography/hazmat/backends/openssl/backend.py
@@ -16,9 +16,6 @@ from cryptography.hazmat.backends.interfaces import Backend as BackendInterface
from cryptography.hazmat.backends.openssl import aead
from cryptography.hazmat.backends.openssl.ciphers import _CipherContext
from cryptography.hazmat.backends.openssl.cmac import _CMACContext
-from cryptography.hazmat.backends.openssl.decode_asn1 import (
- _CRL_ENTRY_REASON_ENUM_TO_CODE,
-)
from cryptography.hazmat.backends.openssl.dh import (
_DHParameters,
_DHPrivateKey,
@@ -71,7 +68,6 @@ from cryptography.hazmat.backends.openssl.x509 import (
_RawRevokedCertificate,
)
from cryptography.hazmat.bindings._rust import (
- ocsp as rust_ocsp,
x509 as rust_x509,
)
from cryptography.hazmat.bindings.openssl import binding
@@ -119,7 +115,6 @@ from cryptography.hazmat.primitives.serialization.pkcs12 import (
PKCS12Certificate,
PKCS12KeyAndCertificates,
)
-from cryptography.x509 import ocsp
from cryptography.x509.base import PUBLIC_KEY_TYPES
@@ -1380,6 +1375,10 @@ class Backend(BackendInterface):
return True
+ def _check_keys_correspond(self, key1, key2):
+ if self._lib.EVP_PKEY_cmp(key1._evp_pkey, key2._evp_pkey) != 1:
+ raise ValueError("Keys do not correspond")
+
def _load_key(self, openssl_read_func, convert_func, data, password):
mem_bio = self._bytes_to_bio(data)
@@ -1616,120 +1615,6 @@ class Backend(BackendInterface):
self.openssl_assert(ec_cdata != self._ffi.NULL)
return self._ffi.gc(ec_cdata, self._lib.EC_KEY_free)
- def _create_ocsp_basic_response(self, builder, private_key, algorithm):
- self._x509_check_signature_params(private_key, algorithm)
-
- basic = self._lib.OCSP_BASICRESP_new()
- self.openssl_assert(basic != self._ffi.NULL)
- basic = self._ffi.gc(basic, self._lib.OCSP_BASICRESP_free)
- evp_md = self._evp_md_non_null_from_algorithm(
- builder._response._algorithm
- )
- ossl_cert = self._cert2ossl(builder._response._cert)
- ossl_issuer = self._cert2ossl(builder._response._issuer)
- certid = self._lib.OCSP_cert_to_id(
- evp_md,
- ossl_cert,
- ossl_issuer,
- )
- self.openssl_assert(certid != self._ffi.NULL)
- certid = self._ffi.gc(certid, self._lib.OCSP_CERTID_free)
- if builder._response._revocation_reason is None:
- reason = -1
- else:
- reason = _CRL_ENTRY_REASON_ENUM_TO_CODE[
- builder._response._revocation_reason
- ]
- if builder._response._revocation_time is None:
- rev_time = self._ffi.NULL
- else:
- rev_time = self._create_asn1_time_gc(
- builder._response._revocation_time
- )
-
- next_update = self._ffi.NULL
- if builder._response._next_update is not None:
- next_update = self._create_asn1_time_gc(
- builder._response._next_update
- )
-
- this_update = self._create_asn1_time_gc(builder._response._this_update)
-
- res = self._lib.OCSP_basic_add1_status(
- basic,
- certid,
- builder._response._cert_status.value,
- reason,
- rev_time,
- this_update,
- next_update,
- )
- self.openssl_assert(res != self._ffi.NULL)
- # okay, now sign the basic structure
- evp_md = self._evp_md_x509_null_if_eddsa(private_key, algorithm)
- responder_cert, responder_encoding = builder._responder_id
- flags = self._lib.OCSP_NOCERTS
- if responder_encoding is ocsp.OCSPResponderEncoding.HASH:
- flags |= self._lib.OCSP_RESPID_KEY
-
- # This list is to keep the x509 values alive until end of function
- ossl_certs = []
- if builder._certs is not None:
- for cert in builder._certs:
- ossl_cert = self._cert2ossl(cert)
- ossl_certs.append(ossl_cert)
- res = self._lib.OCSP_basic_add1_cert(basic, ossl_cert)
- self.openssl_assert(res == 1)
-
- self._create_x509_extensions(
- extensions=builder._extensions,
- rust_handler=rust_ocsp.encode_ocsp_basic_response_extension,
- x509_obj=basic,
- add_func=self._lib.OCSP_BASICRESP_add_ext,
- gc=True,
- )
-
- ossl_cert = self._cert2ossl(responder_cert)
- res = self._lib.OCSP_basic_sign(
- basic,
- ossl_cert,
- private_key._evp_pkey,
- evp_md,
- self._ffi.NULL,
- flags,
- )
- if res != 1:
- errors = self._consume_errors_with_text()
- raise ValueError(
- "Error while signing. responder_cert must be signed "
- "by private_key",
- errors,
- )
-
- return basic
-
- def create_ocsp_response(
- self, response_status, builder, private_key, algorithm
- ):
- if response_status is ocsp.OCSPResponseStatus.SUCCESSFUL:
- basic = self._create_ocsp_basic_response(
- builder, private_key, algorithm
- )
- else:
- basic = self._ffi.NULL
-
- ocsp_resp = self._lib.OCSP_response_create(
- response_status.value, basic
- )
- self.openssl_assert(ocsp_resp != self._ffi.NULL)
- ocsp_resp = self._ffi.gc(ocsp_resp, self._lib.OCSP_RESPONSE_free)
-
- bio = self._create_mem_bio_gc()
- res = self._lib.i2d_OCSP_RESPONSE_bio(bio, ocsp_resp)
- self.openssl_assert(res > 0)
- data = self._read_mem_bio(bio)
- return ocsp.load_der_ocsp_response(data)
-
def elliptic_curve_exchange_algorithm_supported(self, algorithm, curve):
if self._fips_enabled and not isinstance(
curve, self._fips_ecdh_curves
diff --git a/src/cryptography/hazmat/bindings/_rust/ocsp.pyi b/src/cryptography/hazmat/bindings/_rust/ocsp.pyi
index 3ccb48e5e..91b84955f 100644
--- a/src/cryptography/hazmat/bindings/_rust/ocsp.pyi
+++ b/src/cryptography/hazmat/bindings/_rust/ocsp.pyi
@@ -1,11 +1,22 @@
+import typing
+
+from cryptography.hazmat.primitives.asymmetric.types import PRIVATE_KEY_TYPES
+from cryptography.hazmat.primitives import hashes
from cryptography.x509 import Extension
from cryptography.x509.ocsp import (
OCSPRequest,
OCSPRequestBuilder,
OCSPResponse,
+ OCSPResponseStatus,
+ OCSPResponseBuilder,
)
def load_der_ocsp_request(data: bytes) -> OCSPRequest: ...
def load_der_ocsp_response(data: bytes) -> OCSPResponse: ...
-def encode_ocsp_basic_response_extension(ext: Extension) -> bytes: ...
def create_ocsp_request(builder: OCSPRequestBuilder) -> OCSPRequest: ...
+def create_ocsp_response(
+ status: OCSPResponseStatus,
+ builder: typing.Optional[OCSPResponseBuilder],
+ private_key: typing.Optional[PRIVATE_KEY_TYPES],
+ hash_algorithm: typing.Optional[hashes.HashAlgorithm],
+) -> OCSPResponse: ...
diff --git a/src/cryptography/x509/ocsp.py b/src/cryptography/x509/ocsp.py
index 1c02c2945..8eec4c0c6 100644
--- a/src/cryptography/x509/ocsp.py
+++ b/src/cryptography/x509/ocsp.py
@@ -452,14 +452,12 @@ class OCSPResponseBuilder(object):
private_key: PRIVATE_KEY_TYPES,
algorithm: typing.Optional[hashes.HashAlgorithm],
) -> OCSPResponse:
- from cryptography.hazmat.backends.openssl.backend import backend
-
if self._response is None:
raise ValueError("You must add a response before signing")
if self._responder_id is None:
raise ValueError("You must add a responder_id before signing")
- return backend.create_ocsp_response(
+ return ocsp.create_ocsp_response(
OCSPResponseStatus.SUCCESSFUL, self, private_key, algorithm
)
@@ -467,8 +465,6 @@ class OCSPResponseBuilder(object):
def build_unsuccessful(
cls, response_status: OCSPResponseStatus
) -> OCSPResponse:
- from cryptography.hazmat.backends.openssl.backend import backend
-
if not isinstance(response_status, OCSPResponseStatus):
raise TypeError(
"response_status must be an item from OCSPResponseStatus"
@@ -476,7 +472,7 @@ class OCSPResponseBuilder(object):
if response_status is OCSPResponseStatus.SUCCESSFUL:
raise ValueError("response_status cannot be SUCCESSFUL")
- return backend.create_ocsp_response(response_status, None, None, None)
+ return ocsp.create_ocsp_response(response_status, None, None, None)
def load_der_ocsp_request(data: bytes) -> OCSPRequest:
diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock
index 5b75a3623..d695da4a5 100644
--- a/src/rust/Cargo.lock
+++ b/src/rust/Cargo.lock
@@ -65,8 +65,10 @@ version = "0.4.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
dependencies = [
+ "libc",
"num-integer",
"num-traits",
+ "winapi",
]
[[package]]
diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml
index 4dd1b429d..62bc96057 100644
--- a/src/rust/Cargo.toml
+++ b/src/rust/Cargo.toml
@@ -10,7 +10,7 @@ lazy_static = "1"
pyo3 = { version = "0.14.5" }
asn1 = { version = "0.8.4", default-features = false, features = ["derive"] }
pem = "1.0"
-chrono = { version = "0.4", default-features = false, features = ["alloc"] }
+chrono = { version = "0.4", default-features = false, features = ["alloc", "clock"] }
ouroboros = "0.13"
[features]
diff --git a/src/rust/src/x509/certificate.rs b/src/rust/src/x509/certificate.rs
index 8fe9b23ac..175dbb1ed 100644
--- a/src/rust/src/x509/certificate.rs
+++ b/src/rust/src/x509/certificate.rs
@@ -34,14 +34,14 @@ lazy_static::lazy_static! {
static ref NAME_CONSTRAINTS_OID: asn1::ObjectIdentifier<'static> = asn1::ObjectIdentifier::from_string("2.5.29.30").unwrap();
}
-#[derive(asn1::Asn1Read, asn1::Asn1Write, Hash, PartialEq)]
+#[derive(asn1::Asn1Read, asn1::Asn1Write, Hash, PartialEq, Clone)]
pub(crate) struct RawCertificate<'a> {
pub(crate) tbs_cert: TbsCertificate<'a>,
signature_alg: x509::AlgorithmIdentifier<'a>,
signature: asn1::BitString<'a>,
}
-#[derive(asn1::Asn1Read, asn1::Asn1Write, Hash, PartialEq)]
+#[derive(asn1::Asn1Read, asn1::Asn1Write, Hash, PartialEq, Clone)]
pub(crate) struct TbsCertificate<'a> {
#[explicit(0)]
#[default(0)]
@@ -51,7 +51,7 @@ pub(crate) struct TbsCertificate<'a> {
pub(crate) issuer: x509::Name<'a>,
validity: Validity,
- subject: x509::Name<'a>,
+ pub(crate) subject: x509::Name<'a>,
pub(crate) spki: SubjectPublicKeyInfo<'a>,
#[implicit(1)]
@@ -62,13 +62,13 @@ pub(crate) struct TbsCertificate<'a> {
extensions: Option<x509::Extensions<'a>>,
}
-#[derive(asn1::Asn1Read, asn1::Asn1Write, Hash, PartialEq)]
+#[derive(asn1::Asn1Read, asn1::Asn1Write, Hash, PartialEq, Clone)]
pub(crate) struct Validity {
not_before: x509::Time,
not_after: x509::Time,
}
-#[derive(asn1::Asn1Read, asn1::Asn1Write, Hash, PartialEq)]
+#[derive(asn1::Asn1Read, asn1::Asn1Write, Hash, PartialEq, Clone)]
pub(crate) struct SubjectPublicKeyInfo<'a> {
_algorithm: x509::AlgorithmIdentifier<'a>,
pub(crate) subject_public_key: asn1::BitString<'a>,
diff --git a/src/rust/src/x509/common.rs b/src/rust/src/x509/common.rs
index 3a071c65c..0dfb74cd9 100644
--- a/src/rust/src/x509/common.rs
+++ b/src/rust/src/x509/common.rs
@@ -43,7 +43,7 @@ pub(crate) type Name<'a> = Asn1ReadableOrWritable<
>,
>;
-#[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Hash)]
+#[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Hash, Clone)]
pub(crate) struct AttributeTypeValue<'a> {
pub(crate) type_id: asn1::ObjectIdentifier<'a>,
pub(crate) value: RawTlv<'a>,
@@ -51,7 +51,7 @@ pub(crate) struct AttributeTypeValue<'a> {
// Like `asn1::Tlv` but doesn't store `full_data` so it can be constucted from
// an un-encoded tag and value.
-#[derive(Hash, PartialEq)]
+#[derive(Hash, PartialEq, Clone)]
pub(crate) struct RawTlv<'a> {
tag: u8,
value: &'a [u8],
@@ -331,7 +331,7 @@ pub(crate) type Extensions<'a> = Asn1ReadableOrWritable<
asn1::SequenceOfWriter<'a, Extension<'a>, Vec<Extension<'a>>>,
>;
-#[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Hash)]
+#[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Hash, Clone)]
pub(crate) struct AlgorithmIdentifier<'a> {
pub(crate) oid: asn1::ObjectIdentifier<'a>,
pub(crate) params: Option<asn1::Tlv<'a>>,
diff --git a/src/rust/src/x509/mod.rs b/src/rust/src/x509/mod.rs
index 33136741d..f8876c003 100644
--- a/src/rust/src/x509/mod.rs
+++ b/src/rust/src/x509/mod.rs
@@ -10,6 +10,7 @@ mod ocsp;
pub(crate) mod ocsp_req;
pub(crate) mod ocsp_resp;
pub(crate) mod sct;
+pub(crate) mod sign;
pub(crate) use certificate::Certificate;
pub(crate) use common::{
diff --git a/src/rust/src/x509/ocsp_resp.rs b/src/rust/src/x509/ocsp_resp.rs
index d72b29b8a..0b7431ed5 100644
--- a/src/rust/src/x509/ocsp_resp.rs
+++ b/src/rust/src/x509/ocsp_resp.rs
@@ -4,7 +4,7 @@
use crate::asn1::{big_asn1_uint_to_py, PyAsn1Error, PyAsn1Result};
use crate::x509;
-use crate::x509::{certificate, crl, ocsp, sct};
+use crate::x509::{certificate, crl, ocsp, py_to_chrono, sct};
use std::sync::Arc;
lazy_static::lazy_static! {
@@ -45,7 +45,11 @@ fn load_der_ocsp_response(_py: pyo3::Python<'_>, data: &[u8]) -> Result<OCSPResp
)?;
if let Some(basic_response) = raw.borrow_basic_response() {
- let num_responses = basic_response.tbs_response_data.responses.len();
+ let num_responses = basic_response
+ .tbs_response_data
+ .responses
+ .unwrap_read()
+ .len();
if num_responses != 1 {
return Err(PyAsn1Error::from(
pyo3::exceptions::PyValueError::new_err(format!(
@@ -207,7 +211,7 @@ impl OCSPResponse {
let resp = self.requires_successful_response()?;
let py_certs = pyo3::types::PyList::empty(py);
let certs = match &resp.certs {
- Some(certs) => certs,
+ Some(certs) => certs.unwrap_read(),
None => return Ok(py_certs),
};
for i in 0..certs.len() {
@@ -219,6 +223,7 @@ impl OCSPResponse {
.certs
.as_ref()
.unwrap()
+ .unwrap_read()
.clone()
.nth(i)
.unwrap()
@@ -444,18 +449,35 @@ struct ResponseBytes<'a> {
response: &'a [u8],
}
-#[derive(asn1::Asn1Read)]
+type OCSPCerts<'a> = Option<
+ x509::Asn1ReadableOrWritable<
+ 'a,
+ asn1::SequenceOf<'a, certificate::RawCertificate<'a>>,
+ asn1::SequenceOfWriter<
+ 'a,
+ certificate::RawCertificate<'a>,
+ Vec<certificate::RawCertificate<'a>>,
+ >,
+ >,
+>;
+
+#[derive(asn1::Asn1Read, asn1::Asn1Write)]
struct BasicOCSPResponse<'a> {
tbs_response_data: ResponseData<'a>,
signature_algorithm: x509::AlgorithmIdentifier<'a>,
signature: asn1::BitString<'a>,
#[explicit(0)]
- certs: Option<asn1::SequenceOf<'a, certificate::RawCertificate<'a>>>,
+ certs: OCSPCerts<'a>,
}
impl BasicOCSPResponse<'_> {
fn single_response(&self) -> SingleResponse<'_> {
- self.tbs_response_data.responses.clone().next().unwrap()
+ self.tbs_response_data
+ .responses
+ .unwrap_read()
+ .clone()
+ .next()
+ .unwrap()
}
}
@@ -466,7 +488,11 @@ struct ResponseData<'a> {
version: u8,
responder_id: ResponderId<'a>,
produced_at: asn1::GeneralizedTime,
- responses: asn1::SequenceOf<'a, SingleResponse<'a>>,
+ responses: x509::Asn1ReadableOrWritable<
+ 'a,
+ asn1::SequenceOf<'a, SingleResponse<'a>>,
+ asn1::SequenceOfWriter<'a, SingleResponse<'a>, Vec<SingleResponse<'a>>>,
+ >,
#[explicit(1)]
response_extensions: Option<x509::Extensions<'a>>,
}
@@ -507,27 +533,181 @@ struct RevokedInfo {
revocation_reason: Option<crl::CRLReason>,
}
+fn create_ocsp_basic_response<'p>(
+ py: pyo3::Python<'p>,
+ builder: &'p pyo3::PyAny,
+ private_key: &'p pyo3::PyAny,
+ hash_algorithm: &'p pyo3::PyAny,
+) -> pyo3::PyResult<Vec<u8>> {
+ let ocsp_mod = py.import("cryptography.x509.ocsp")?;
+
+ let py_single_resp = builder.getattr("_response")?;
+ let py_cert: pyo3::PyRef<'_, x509::Certificate> = py_single_resp.getattr("_cert")?.extract()?;
+ let py_issuer: pyo3::PyRef<'_, x509::Certificate> =
+ py_single_resp.getattr("_issuer")?.extract()?;
+ let py_cert_hash_algorithm = py_single_resp.getattr("_algorithm")?;
+ let (responder_cert, responder_encoding): (&pyo3::PyCell<x509::Certificate>, &pyo3::PyAny) =
+ builder.getattr("_responder_id")?.extract()?;
+
+ let py_cert_status = py_single_resp.getattr("_cert_status")?;
+ let cert_status = if py_cert_status == ocsp_mod.getattr("OCSPCertStatus")?.getattr("GOOD")? {
+ CertStatus::Good(())
+ } else if py_cert_status == ocsp_mod.getattr("OCSPCertStatus")?.getattr("UNKNOWN")? {
+ CertStatus::Unknown(())
+ } else {
+ let revocation_reason = if !py_single_resp.getattr("_revocation_reason")?.is_none() {
+ let value = py
+ .import("cryptography.hazmat.backends.openssl.decode_asn1")?
+ .getattr("_CRL_ENTRY_REASON_ENUM_TO_CODE")?
+ .get_item(py_single_resp.getattr("_revocation_reason")?)?
+ .extract::<u32>()?;
+ Some(asn1::Enumerated::new(value))
+ } else {
+ None
+ };
+ // REVOKED
+ let revocation_time =
+ asn1::GeneralizedTime::new(py_to_chrono(py_single_resp.getattr("_revocation_time")?)?);
+ CertStatus::Revoked(RevokedInfo {
+ revocation_time,
+ revocation_reason,
+ })
+ };
+ let next_update = if !py_single_resp.getattr("_next_update")?.is_none() {
+ let py_next_update = py_single_resp.getattr("_next_update")?;
+ Some(asn1::GeneralizedTime::new(py_to_chrono(py_next_update)?))
+ } else {
+ None
+ };
+ let this_update =
+ asn1::GeneralizedTime::new(py_to_chrono(py_single_resp.getattr("_this_update")?)?);
+
+ let responses = vec![SingleResponse {
+ cert_id: ocsp::CertID::new(py, &py_cert, &py_issuer, py_cert_hash_algorithm)?,
+ cert_status,
+ next_update,
+ this_update,
+ single_extensions: None,
+ }];
+
+ let borrowed_cert = responder_cert.borrow();
+ let responder_id =
+ if responder_encoding == ocsp_mod.getattr("OCSPResponderEncoding")?.getattr("HASH")? {
+ let sha1 = py
+ .import("cryptography.hazmat.primitives.hashes")?
+ .getattr("SHA1")?
+ .call0()?;
+ ResponderId::ByKey(ocsp::hash_data(
+ py,
+ sha1,
+ borrowed_cert
+ .raw
+ .borrow_value_public()
+ .tbs_cert
+ .spki
+ .subject_public_key
+ .as_bytes(),
+ )?)
+ } else {
+ ResponderId::ByName(
+ borrowed_cert
+ .raw
+ .borrow_value_public()
+ .tbs_cert
+ .subject
+ .clone(),
+ )
+ };
+
+ let tbs_response_data = ResponseData {
+ version: 0,
+ produced_at: asn1::GeneralizedTime::new(chrono::Utc::now()),
+ responder_id,
+ responses: x509::Asn1ReadableOrWritable::new_write(asn1::SequenceOfWriter::new(responses)),
+ response_extensions: x509::common::encode_extensions(
+ py,
+ builder.getattr("_extensions")?,
+ encode_ocsp_basic_response_extension,
+ )?,
+ };
+
+ let sigalg = x509::sign::compute_signature_algorithm(py, private_key, hash_algorithm)?;
+ let tbs_bytes = asn1::write_single(&tbs_response_data);
+ let signature = x509::sign::sign_data(py, private_key, hash_algorithm, &tbs_bytes)?;
+
+ py.import("cryptography.hazmat.backends.openssl.backend")?
+ .getattr("backend")?
+ .call_method1(
+ "_check_keys_correspond",
+ (
+ responder_cert.call_method0("public_key")?,
+ private_key.call_method0("public_key")?,
+ ),
+ )?;
+
+ let py_certs: Option<Vec<pyo3::PyRef<'_, x509::Certificate>>> =
+ builder.getattr("_certs")?.extract()?;
+ let certs = py_certs.as_ref().map(|py_certs| {
+ x509::Asn1ReadableOrWritable::new_write(asn1::SequenceOfWriter::new(
+ py_certs
+ .iter()
+ .map(|c| c.raw.borrow_value_public().clone())
+ .collect(),
+ ))
+ });
+
+ let basic_resp = BasicOCSPResponse {
+ tbs_response_data,
+ signature: asn1::BitString::new(signature, 0).unwrap(),
+ signature_algorithm: sigalg,
+ certs,
+ };
+ Ok(asn1::write_single(&basic_resp))
+}
+
#[pyo3::prelude::pyfunction]
-fn encode_ocsp_basic_response_extension(ext: &pyo3::PyAny) -> pyo3::PyResult<&pyo3::PyAny> {
- let oid = asn1::ObjectIdentifier::from_string(
- ext.getattr("oid")?
- .getattr("dotted_string")?
- .extract::<&str>()?,
- )
- .unwrap();
- if oid == *ocsp::NONCE_OID {
- Ok(ext.getattr("value")?.getattr("nonce")?)
+fn create_ocsp_response(
+ py: pyo3::Python<'_>,
+ status: &pyo3::PyAny,
+ builder: &pyo3::PyAny,
+ private_key: &pyo3::PyAny,
+ hash_algorithm: &pyo3::PyAny,
+) -> PyAsn1Result<OCSPResponse> {
+ let response_status = status.getattr("value")?.extract::<u32>()?;
+ let basic_resp_bytes;
+ let response_bytes = if response_status == SUCCESSFUL_RESPONSE {
+ basic_resp_bytes = create_ocsp_basic_response(py, builder, private_key, hash_algorithm)?;
+ Some(ResponseBytes {
+ response_type: (*BASIC_RESPONSE_OID).clone(),
+ response: &basic_resp_bytes,
+ })
+ } else {
+ None
+ };
+
+ let resp = RawOCSPResponse {
+ response_status: asn1::Enumerated::new(response_status),
+ response_bytes,
+ };
+ let data = asn1::write_single(&resp);
+ // TODO: extra copy as we round-trip through a slice
+ load_der_ocsp_response(py, &data)
+}
+
+fn encode_ocsp_basic_response_extension(
+ oid: &asn1::ObjectIdentifier<'_>,
+ ext: &pyo3::PyAny,
+) -> pyo3::PyResult<Option<Vec<u8>>> {
+ if oid == &*ocsp::NONCE_OID {
+ Ok(Some(ext.getattr("nonce")?.extract::<&[u8]>()?.to_vec()))
} else {
- Err(pyo3::exceptions::PyNotImplementedError::new_err(format!(
- "Extension not supported: {}",
- oid
- )))
+ Ok(None)
}
}
pub(crate) fn add_to_module(module: &pyo3::prelude::PyModule) -> pyo3::PyResult<()> {
module.add_wrapped(pyo3::wrap_pyfunction!(load_der_ocsp_response))?;
- module.add_wrapped(pyo3::wrap_pyfunction!(encode_ocsp_basic_response_extension))?;
+ module.add_wrapped(pyo3::wrap_pyfunction!(create_ocsp_response))?;
Ok(())
}
diff --git a/src/rust/src/x509/sign.rs b/src/rust/src/x509/sign.rs
new file mode 100644
index 000000000..62230f7fa
--- /dev/null
+++ b/src/rust/src/x509/sign.rs
@@ -0,0 +1,137 @@
+// This file is dual licensed under the terms of the Apache License, Version
+// 2.0, and the BSD License. See the LICENSE file in the root of this repository
+// for complete details.
+
+use crate::x509;
+
+lazy_static::lazy_static! {
+ static ref ECDSA_WITH_SHA256_OID: asn1::ObjectIdentifier<'static> = asn1::ObjectIdentifier::from_string("1.2.840.10045.4.3.2").unwrap();
+ static ref ECDSA_WITH_SHA384_OID: asn1::ObjectIdentifier<'static> = asn1::ObjectIdentifier::from_string("1.2.840.10045.4.3.3").unwrap();
+
+ static ref ED25519_OID: asn1::ObjectIdentifier<'static> = asn1::ObjectIdentifier::from_string("1.3.101.112").unwrap();
+ static ref ED448_OID: asn1::ObjectIdentifier<'static> = asn1::ObjectIdentifier::from_string("1.3.101.113").unwrap();
+}
+
+enum KeyType {
+ Ec,
+ Ed25519,
+ Ed448,
+}
+
+enum HashType {
+ None,
+ Sha256,
+ Sha384,
+}
+
+fn identify_key_type(py: pyo3::Python<'_>, private_key: &pyo3::PyAny) -> pyo3::PyResult<KeyType> {
+ let ec_key_type: &pyo3::types::PyType = py
+ .import("cryptography.hazmat.primitives.asymmetric.ec")?
+ .getattr("EllipticCurvePrivateKey")?
+ .extract()?;
+ let ed25519_key_type: &pyo3::types::PyType = py
+ .import("cryptography.hazmat.primitives.asymmetric.ed25519")?
+ .getattr("Ed25519PrivateKey")?
+ .extract()?;
+ let ed448_key_type: &pyo3::types::PyType = py
+ .import("cryptography.hazmat.primitives.asymmetric.ed448")?
+ .getattr("Ed448PrivateKey")?
+ .extract()?;
+
+ if ec_key_type.is_instance(private_key)? {
+ Ok(KeyType::Ec)
+ } else if ed25519_key_type.is_instance(private_key)? {
+ Ok(KeyType::Ed25519)
+ } else if ed448_key_type.is_instance(private_key)? {
+ Ok(KeyType::Ed448)
+ } else {
+ Err(pyo3::exceptions::PyTypeError::new_err(
+ "Key must be an rsa, dsa, ec, ed25519, or ed448 private key.",
+ ))
+ }
+}
+
+fn identify_hash_type(
+ py: pyo3::Python<'_>,
+ hash_algorithm: &pyo3::PyAny,
+) -> pyo3::PyResult<HashType> {
+ if hash_algorithm.is_none() {
+ return Ok(HashType::None);
+ }
+
+ let hash_algorithm_type: &pyo3::types::PyType = py
+ .import("cryptography.hazmat.primitives.hashes")?
+ .getattr("HashAlgorithm")?
+ .extract()?;
+ if !hash_algorithm_type.is_instance(hash_algorithm)? {
+ return Err(pyo3::exceptions::PyTypeError::new_err(
+ "Algorithm must be a registered hash algorithm.",
+ ));
+ }
+
+ match hash_algorithm.getattr("name")?.extract()? {
+ "sha256" => Ok(HashType::Sha256),
+ "sha384" => Ok(HashType::Sha384),
+ name => Err(pyo3::exceptions::PyValueError::new_err(format!(
+ "Hash algorithm {:?} not supported for signatures",
+ name
+ ))),
+ }
+}
+
+pub(crate) fn compute_signature_algorithm<'p>(
+ py: pyo3::Python<'p>,
+ private_key: &'p pyo3::PyAny,
+ hash_algorithm: &'p pyo3::PyAny,
+) -> pyo3::PyResult<x509::AlgorithmIdentifier<'static>> {
+ let key_type = identify_key_type(py, private_key)?;
+ let hash_type = identify_hash_type(py, hash_algorithm)?;
+
+ match (key_type, hash_type) {
+ (KeyType::Ed25519, HashType::None) => Ok(x509::AlgorithmIdentifier {
+ oid: (*ED25519_OID).clone(),
+ params: None,
+ }),
+ (KeyType::Ed448, HashType::None) => Ok(x509::AlgorithmIdentifier {
+ oid: (*ED448_OID).clone(),
+ params: None,
+ }),
+ (KeyType::Ed25519, _) | (KeyType::Ed448, _) => {
+ Err(pyo3::exceptions::PyValueError::new_err(
+ "Algorithm must be None when signing via ed25519 or ed448",
+ ))
+ }
+
+ (KeyType::Ec, HashType::Sha256) => Ok(x509::AlgorithmIdentifier {
+ oid: (*ECDSA_WITH_SHA256_OID).clone(),
+ params: None,
+ }),
+ (KeyType::Ec, HashType::Sha384) => Ok(x509::AlgorithmIdentifier {
+ oid: (*ECDSA_WITH_SHA384_OID).clone(),
+ params: None,
+ }),
+
+ (_, HashType::None) => Err(pyo3::exceptions::PyTypeError::new_err(
+ "Algorithm must be a registered hash algorithm, not None.",
+ )),
+ }
+}
+
+pub(crate) fn sign_data<'p>(
+ py: pyo3::Python<'p>,
+ private_key: &'p pyo3::PyAny,
+ hash_algorithm: &'p pyo3::PyAny,
+ data: &[u8],
+) -> pyo3::PyResult<&'p [u8]> {
+ let key_type = identify_key_type(py, private_key)?;
+
+ let signature = match key_type {
+ KeyType::Ed25519 | KeyType::Ed448 => private_key.call_method1("sign", (data,))?,
+ KeyType::Ec => {
+ let ec_mod = py.import("cryptography.hazmat.primitives.asymmetric.ec")?;
+ let ecdsa = ec_mod.getattr("ECDSA")?.call1((hash_algorithm,))?;
+ private_key.call_method1("sign", (data, ecdsa))?
+ }
+ };
+ signature.extract()
+}