use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use pyo3::prelude::*;
use pyo3::types::PyBytes;
use synta::OctetString;
use synta_certificate::acme_types::Authorization;
use crate::error::SyntaErr;
#[pyclass(frozen, name = "AcmeAuthorization", module = "synta.acme")]
pub struct PyAcmeAuthorization {
digest: [u8; 32],
}
#[pymethods]
impl PyAcmeAuthorization {
#[new]
fn new(digest: &[u8]) -> PyResult<Self> {
if digest.len() != 32 {
return Err(pyo3::exceptions::PyValueError::new_err(format!(
"AcmeAuthorization requires exactly 32 bytes, got {}",
digest.len()
)));
}
let mut arr = [0u8; 32];
arr.copy_from_slice(digest);
Ok(Self { digest: arr })
}
#[staticmethod]
fn from_der(data: &[u8]) -> PyResult<Self> {
if data.len() != 34 {
return Err(pyo3::exceptions::PyValueError::new_err(format!(
"AcmeAuthorization.from_der: expected 34-byte DER \
(04 20 <32-byte SHA-256 digest>), got {} bytes. \
Pass the raw extnValue contents, not a wrapped SEQUENCE.",
data.len()
)));
}
let auth = Authorization::from_der(data).map_err(SyntaErr)?;
let bytes = auth.get().as_bytes();
let mut arr = [0u8; 32];
arr.copy_from_slice(bytes);
Ok(Self { digest: arr })
}
fn to_der<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
let auth = Authorization::new_unchecked(OctetString::new(self.digest.to_vec()));
auth.to_der()
.map(|bytes| PyBytes::new(py, &bytes))
.map_err(|e| {
pyo3::exceptions::PyRuntimeError::new_err(format!(
"AcmeAuthorization.to_der: DER encoder error: {:?}",
e
))
})
}
#[getter]
fn digest<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
PyBytes::new(py, &self.digest)
}
#[getter]
fn hex_digest(&self) -> String {
synta::bytes_to_hex(&self.digest)
}
fn __bytes__<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
PyBytes::new(py, &self.digest)
}
fn __repr__(&self) -> String {
format!("AcmeAuthorization(digest={})", self.hex_digest())
}
fn __eq__(&self, other: &Self) -> bool {
self.digest == other.digest
}
fn __hash__(&self) -> u64 {
let mut h = DefaultHasher::new();
self.digest.hash(&mut h);
h.finish()
}
fn __len__(&self) -> usize {
self.digest.len()
}
}
pub(super) fn register_acme_submodule(parent: &Bound<'_, PyModule>) -> PyResult<()> {
let py = parent.py();
let m = PyModule::new(py, "acme")?;
m.add_class::<PyAcmeAuthorization>()?;
m.add(
"ID_PE_ACME_IDENTIFIER",
super::oid_const(py, synta_certificate::oids::PE_ACME_IDENTIFIER),
)?;
crate::install_submodule(
parent,
&m,
"synta.acme",
Some(concat!(
"synta.acme — RFC 8737 ACME TLS-ALPN-01 extension types.\n\n",
"Provides AcmeAuthorization for the id-pe-acmeIdentifier extension\n",
"(OID 1.3.6.1.5.5.7.1.31) used during ACME TLS-ALPN-01 domain\n",
"validation. The extension value is a 32-byte SHA-256 digest of\n",
"the ACME key authorization string: SHA-256(token + '.' + base64url(thumbprint)).",
)),
)
}