use pyo3::exceptions::PyOverflowError;
use pyo3::prelude::*;
use pyo3::types::{PyBytes, PyList};
use crate::error::SyntaErr;
use crate::types::PyObjectIdentifier;
use synta::traits::Encode;
use synta::{Decoder, Encoding, ObjectIdentifier};
fn encode_element_to_vec(elem: &synta::Element<'_>) -> PyResult<Vec<u8>> {
let mut encoder = synta::Encoder::new(Encoding::Der);
elem.encode(&mut encoder).map_err(SyntaErr)?;
Ok(encoder.finish().map_err(SyntaErr)?)
}
#[pyclass(frozen, name = "EncryptionKey")]
pub struct PyEncryptionKey {
keytype: i64,
keyvalue: Vec<u8>,
}
#[pymethods]
impl PyEncryptionKey {
#[staticmethod]
fn from_der(data: &[u8]) -> PyResult<Self> {
let mut dec = Decoder::new(data, Encoding::Der);
let parsed: synta_krb5::pkinit::EncryptionKey<'_> = dec.decode().map_err(SyntaErr)?;
Ok(Self {
keytype: parsed
.keytype
.as_i64()
.map_err(|_| PyOverflowError::new_err("keytype out of i64 range"))?,
keyvalue: parsed.keyvalue.as_bytes().to_vec(),
})
}
#[getter]
fn keytype(&self) -> i64 {
self.keytype
}
#[getter]
fn keyvalue<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
PyBytes::new(py, &self.keyvalue)
}
fn __repr__(&self) -> String {
format!(
"EncryptionKey(keytype={}, keyvalue=<{} bytes>)",
self.keytype,
self.keyvalue.len()
)
}
}
#[pyclass(frozen, name = "Checksum")]
pub struct PyChecksum {
cksumtype: i64,
checksum: Vec<u8>,
}
#[pymethods]
impl PyChecksum {
#[staticmethod]
fn from_der(data: &[u8]) -> PyResult<Self> {
let mut dec = Decoder::new(data, Encoding::Der);
let parsed: synta_krb5::pkinit::Checksum<'_> = dec.decode().map_err(SyntaErr)?;
Ok(Self {
cksumtype: parsed
.cksumtype
.as_i64()
.map_err(|_| PyOverflowError::new_err("cksumtype out of i64 range"))?,
checksum: parsed.checksum.as_bytes().to_vec(),
})
}
#[getter]
fn cksumtype(&self) -> i64 {
self.cksumtype
}
#[getter]
fn checksum<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
PyBytes::new(py, &self.checksum)
}
fn __repr__(&self) -> String {
format!(
"Checksum(cksumtype={}, checksum=<{} bytes>)",
self.cksumtype,
self.checksum.len()
)
}
}
#[pyclass(frozen, name = "KDFAlgorithmId")]
pub struct PyKDFAlgorithmId {
kdf_id: ObjectIdentifier,
}
#[pymethods]
impl PyKDFAlgorithmId {
#[staticmethod]
fn from_der(data: &[u8]) -> PyResult<Self> {
let mut dec = Decoder::new(data, Encoding::Der);
let parsed: synta_krb5::pkinit::KDFAlgorithmId = dec.decode().map_err(SyntaErr)?;
Ok(Self {
kdf_id: parsed.kdf_id,
})
}
#[getter]
fn kdf_id(&self, py: Python<'_>) -> PyResult<Py<PyObjectIdentifier>> {
Py::new(py, PyObjectIdentifier::from_oid(self.kdf_id.clone()))
}
fn __repr__(&self) -> String {
format!("KDFAlgorithmId(kdf_id='{}')", self.kdf_id)
}
}
#[pyclass(frozen, name = "IssuerAndSerialNumber")]
pub struct PyIssuerAndSerialNumber {
issuer: Vec<u8>,
serial_number_bytes: Vec<u8>,
}
#[pymethods]
impl PyIssuerAndSerialNumber {
#[staticmethod]
fn from_der(data: &[u8]) -> PyResult<Self> {
let mut dec = Decoder::new(data, Encoding::Der);
let parsed: synta_krb5::pkinit::IssuerAndSerialNumber<'_> =
dec.decode().map_err(SyntaErr)?;
Ok(Self {
issuer: parsed.issuer.as_bytes().to_vec(),
serial_number_bytes: parsed.serial_number.as_bytes().to_vec(),
})
}
#[getter]
fn issuer<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
PyBytes::new(py, &self.issuer)
}
#[getter]
fn serial_number<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
let bytes_obj = PyBytes::new(py, &self.serial_number_bytes);
let kwargs = pyo3::types::PyDict::new(py);
kwargs.set_item(pyo3::intern!(py, "signed"), true)?;
py.get_type::<pyo3::types::PyInt>().call_method(
"from_bytes",
(bytes_obj, "big"),
Some(&kwargs),
)
}
fn __repr__(&self) -> String {
format!(
"IssuerAndSerialNumber(issuer=<{} bytes>, serial=<{} bytes>)",
self.issuer.len(),
self.serial_number_bytes.len(),
)
}
}
#[pyclass(frozen, name = "PKAuthenticator")]
pub struct PyPKAuthenticator {
cusec: i64,
ctime: String,
nonce: i64,
pa_checksum: Option<Vec<u8>>,
freshness_token: Option<Vec<u8>>,
}
#[pymethods]
impl PyPKAuthenticator {
#[staticmethod]
fn from_der(data: &[u8]) -> PyResult<Self> {
let mut dec = Decoder::new(data, Encoding::Der);
let parsed: synta_krb5::pkinit::PKAuthenticator<'_> = dec.decode().map_err(SyntaErr)?;
Ok(Self {
cusec: parsed
.cusec
.as_i64()
.map_err(|_| PyOverflowError::new_err("cusec out of i64 range"))?,
ctime: parsed.ctime.to_string(),
nonce: parsed
.nonce
.as_i64()
.map_err(|_| PyOverflowError::new_err("nonce out of i64 range"))?,
pa_checksum: parsed.pa_checksum.map(|s| s.as_bytes().to_vec()),
freshness_token: parsed.freshness_token.map(|s| s.as_bytes().to_vec()),
})
}
#[getter]
fn cusec(&self) -> i64 {
self.cusec
}
#[getter]
fn ctime(&self) -> &str {
&self.ctime
}
#[getter]
fn nonce(&self) -> i64 {
self.nonce
}
#[getter]
fn pa_checksum<'py>(&self, py: Python<'py>) -> Option<Bound<'py, PyBytes>> {
self.pa_checksum.as_deref().map(|b| PyBytes::new(py, b))
}
#[getter]
fn freshness_token<'py>(&self, py: Python<'py>) -> Option<Bound<'py, PyBytes>> {
self.freshness_token.as_deref().map(|b| PyBytes::new(py, b))
}
fn __repr__(&self) -> String {
format!(
"PKAuthenticator(ctime='{}', nonce={})",
self.ctime, self.nonce
)
}
}
#[pyclass(frozen, name = "DHRepInfo")]
pub struct PyDHRepInfo {
dh_signed_data: Vec<u8>,
server_dhnonce: Option<Vec<u8>>,
}
#[pymethods]
impl PyDHRepInfo {
#[staticmethod]
fn from_der(data: &[u8]) -> PyResult<Self> {
let mut dec = Decoder::new(data, Encoding::Der);
let parsed: synta_krb5::pkinit::DHRepInfo<'_> = dec.decode().map_err(SyntaErr)?;
Ok(Self {
dh_signed_data: parsed.dh_signed_data.as_bytes().to_vec(),
server_dhnonce: parsed.server_dhnonce.map(|s| s.as_bytes().to_vec()),
})
}
#[getter]
fn dh_signed_data<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
PyBytes::new(py, &self.dh_signed_data)
}
#[getter]
fn server_dhnonce<'py>(&self, py: Python<'py>) -> Option<Bound<'py, PyBytes>> {
self.server_dhnonce.as_deref().map(|b| PyBytes::new(py, b))
}
fn __repr__(&self) -> String {
format!(
"DHRepInfo(dh_signed_data=<{} bytes>)",
self.dh_signed_data.len()
)
}
}
#[pyclass(frozen, name = "KDCDHKeyInfo")]
pub struct PyKDCDHKeyInfo {
subject_public_key: Vec<u8>,
nonce: i64,
dh_key_expiration: Option<String>,
}
#[pymethods]
impl PyKDCDHKeyInfo {
#[staticmethod]
fn from_der(data: &[u8]) -> PyResult<Self> {
let mut dec = Decoder::new(data, Encoding::Der);
let parsed: synta_krb5::pkinit::KDCDHKeyInfo<'_> = dec.decode().map_err(SyntaErr)?;
Ok(Self {
subject_public_key: parsed.subject_public_key.as_bytes().to_vec(),
nonce: parsed
.nonce
.as_i64()
.map_err(|_| PyOverflowError::new_err("nonce out of i64 range"))?,
dh_key_expiration: parsed.dh_key_expiration.map(|t| t.to_string()),
})
}
#[getter]
fn subject_public_key<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
PyBytes::new(py, &self.subject_public_key)
}
#[getter]
fn nonce(&self) -> i64 {
self.nonce
}
#[getter]
fn dh_key_expiration(&self) -> Option<&str> {
self.dh_key_expiration.as_deref()
}
fn __repr__(&self) -> String {
format!(
"KDCDHKeyInfo(nonce={}, subject_public_key=<{} bytes>)",
self.nonce,
self.subject_public_key.len()
)
}
}
#[pyclass(frozen, name = "ExternalPrincipalIdentifier")]
pub struct PyExternalPrincipalIdentifier {
subject_name: Option<Vec<u8>>,
issuer_and_serial_number: Option<Py<PyIssuerAndSerialNumber>>,
subject_key_identifier: Option<Vec<u8>>,
}
#[pymethods]
impl PyExternalPrincipalIdentifier {
#[staticmethod]
fn from_der(py: Python<'_>, data: &[u8]) -> PyResult<Self> {
let mut dec = Decoder::new(data, Encoding::Der);
let parsed: synta_krb5::pkinit::ExternalPrincipalIdentifier<'_> =
dec.decode().map_err(SyntaErr)?;
let issuer_and_serial_number = parsed
.issuer_and_serial_number
.map(|isn| {
Py::new(
py,
PyIssuerAndSerialNumber {
issuer: isn.issuer.as_bytes().to_vec(),
serial_number_bytes: isn.serial_number.as_bytes().to_vec(),
},
)
})
.transpose()?;
Ok(Self {
subject_name: parsed.subject_name.map(|s| s.as_bytes().to_vec()),
issuer_and_serial_number,
subject_key_identifier: parsed.subject_key_identifier.map(|s| s.as_bytes().to_vec()),
})
}
#[getter]
fn subject_name<'py>(&self, py: Python<'py>) -> Option<Bound<'py, PyBytes>> {
self.subject_name.as_deref().map(|b| PyBytes::new(py, b))
}
#[getter]
fn issuer_and_serial_number<'py>(
&self,
py: Python<'py>,
) -> Option<Bound<'py, PyIssuerAndSerialNumber>> {
self.issuer_and_serial_number
.as_ref()
.map(|x| x.clone_ref(py).into_bound(py))
}
#[getter]
fn subject_key_identifier<'py>(&self, py: Python<'py>) -> Option<Bound<'py, PyBytes>> {
self.subject_key_identifier
.as_deref()
.map(|b| PyBytes::new(py, b))
}
}
#[pyclass(frozen, name = "ReplyKeyPack")]
pub struct PyReplyKeyPack {
reply_key: Py<PyEncryptionKey>,
as_checksum: Py<PyChecksum>,
}
#[pymethods]
impl PyReplyKeyPack {
#[staticmethod]
fn from_der(py: Python<'_>, data: &[u8]) -> PyResult<Self> {
let mut dec = Decoder::new(data, Encoding::Der);
let parsed: synta_krb5::pkinit::ReplyKeyPack<'_> = dec.decode().map_err(SyntaErr)?;
let reply_key = Py::new(
py,
PyEncryptionKey {
keytype: parsed
.reply_key
.keytype
.as_i64()
.map_err(|_| PyOverflowError::new_err("keytype out of i64 range"))?,
keyvalue: parsed.reply_key.keyvalue.as_bytes().to_vec(),
},
)?;
let as_checksum = Py::new(
py,
PyChecksum {
cksumtype: parsed
.as_checksum
.cksumtype
.as_i64()
.map_err(|_| PyOverflowError::new_err("cksumtype out of i64 range"))?,
checksum: parsed.as_checksum.checksum.as_bytes().to_vec(),
},
)?;
Ok(Self {
reply_key,
as_checksum,
})
}
#[getter]
fn reply_key<'py>(&self, py: Python<'py>) -> Bound<'py, PyEncryptionKey> {
self.reply_key.clone_ref(py).into_bound(py)
}
#[getter]
fn as_checksum<'py>(&self, py: Python<'py>) -> Bound<'py, PyChecksum> {
self.as_checksum.clone_ref(py).into_bound(py)
}
fn __repr__(&self) -> String {
String::from("ReplyKeyPack(...)")
}
}
#[pyclass(frozen, name = "PaPkAsRep")]
pub struct PyPaPkAsRep {
variant: &'static str,
dh_info: Option<Py<PyDHRepInfo>>,
enc_key_pack: Option<Vec<u8>>,
}
#[pymethods]
impl PyPaPkAsRep {
#[staticmethod]
fn from_der(py: Python<'_>, data: &[u8]) -> PyResult<Self> {
let mut dec = Decoder::new(data, Encoding::Der);
let parsed: synta_krb5::pkinit::PaPkAsRep<'_> = dec.decode().map_err(SyntaErr)?;
match parsed {
synta_krb5::pkinit::PaPkAsRep::DhInfo(dh) => {
let dh_info = Py::new(
py,
PyDHRepInfo {
dh_signed_data: dh.dh_signed_data.as_bytes().to_vec(),
server_dhnonce: dh.server_dhnonce.map(|s| s.as_bytes().to_vec()),
},
)?;
Ok(Self {
variant: "DhInfo",
dh_info: Some(dh_info),
enc_key_pack: None,
})
}
synta_krb5::pkinit::PaPkAsRep::EncKeyPack(pack) => Ok(Self {
variant: "EncKeyPack",
dh_info: None,
enc_key_pack: Some(pack.as_bytes().to_vec()),
}),
}
}
#[getter]
fn variant(&self) -> &'static str {
self.variant
}
#[getter]
fn dh_info<'py>(&self, py: Python<'py>) -> Option<Bound<'py, PyDHRepInfo>> {
self.dh_info
.as_ref()
.map(|x| x.clone_ref(py).into_bound(py))
}
#[getter]
fn enc_key_pack<'py>(&self, py: Python<'py>) -> Option<Bound<'py, PyBytes>> {
self.enc_key_pack.as_deref().map(|b| PyBytes::new(py, b))
}
fn __repr__(&self) -> String {
format!("PaPkAsRep(variant='{}')", self.variant)
}
}
#[pyclass(frozen, name = "AuthPack")]
pub struct PyAuthPack {
pk_authenticator: Py<PyPKAuthenticator>,
client_public_value: Option<Vec<u8>>,
supported_cmstypes: Option<Vec<u8>>,
client_dhnonce: Option<Vec<u8>>,
supported_kdfs: Option<Vec<Py<PyKDFAlgorithmId>>>,
}
#[pymethods]
impl PyAuthPack {
#[staticmethod]
fn from_der(py: Python<'_>, data: &[u8]) -> PyResult<Self> {
let mut dec = Decoder::new(data, Encoding::Der);
let parsed: synta_krb5::pkinit::AuthPack<'_> = dec.decode().map_err(SyntaErr)?;
let pka = parsed.pk_authenticator;
let pk_authenticator = Py::new(
py,
PyPKAuthenticator {
cusec: pka
.cusec
.as_i64()
.map_err(|_| PyOverflowError::new_err("cusec out of i64 range"))?,
ctime: pka.ctime.to_string(),
nonce: pka
.nonce
.as_i64()
.map_err(|_| PyOverflowError::new_err("nonce out of i64 range"))?,
pa_checksum: pka.pa_checksum.map(|s| s.as_bytes().to_vec()),
freshness_token: pka.freshness_token.map(|s| s.as_bytes().to_vec()),
},
)?;
let client_public_value = parsed
.client_public_value
.as_ref()
.map(encode_element_to_vec)
.transpose()?;
let supported_cmstypes = parsed
.supported_cmstypes
.as_ref()
.map(encode_element_to_vec)
.transpose()?;
let client_dhnonce = parsed.client_dhnonce.map(|s| s.as_bytes().to_vec());
let supported_kdfs = parsed
.supported_kdfs
.map(|kdfs| {
kdfs.into_iter()
.map(|kdf| Py::new(py, PyKDFAlgorithmId { kdf_id: kdf.kdf_id }))
.collect::<PyResult<Vec<_>>>()
})
.transpose()?;
Ok(Self {
pk_authenticator,
client_public_value,
supported_cmstypes,
client_dhnonce,
supported_kdfs,
})
}
#[getter]
fn pk_authenticator<'py>(&self, py: Python<'py>) -> Bound<'py, PyPKAuthenticator> {
self.pk_authenticator.clone_ref(py).into_bound(py)
}
#[getter]
fn client_public_value<'py>(&self, py: Python<'py>) -> Option<Bound<'py, PyBytes>> {
self.client_public_value
.as_deref()
.map(|b| PyBytes::new(py, b))
}
#[getter]
fn supported_cmstypes<'py>(&self, py: Python<'py>) -> Option<Bound<'py, PyBytes>> {
self.supported_cmstypes
.as_deref()
.map(|b| PyBytes::new(py, b))
}
#[getter]
fn client_dhnonce<'py>(&self, py: Python<'py>) -> Option<Bound<'py, PyBytes>> {
self.client_dhnonce.as_deref().map(|b| PyBytes::new(py, b))
}
#[getter]
fn supported_kdfs<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyList>>> {
self.supported_kdfs
.as_ref()
.map(|kdfs| {
let list = PyList::empty(py);
for kdf in kdfs {
list.append(kdf.clone_ref(py).into_bound(py))?;
}
Ok(list)
})
.transpose()
}
fn __repr__(&self) -> String {
String::from("AuthPack(...)")
}
}
#[pyclass(frozen, name = "PaPkAsReq")]
pub struct PyPaPkAsReq {
signed_auth_pack: Vec<u8>,
trusted_certifiers: Option<Vec<Py<PyExternalPrincipalIdentifier>>>,
kdc_pk_id: Option<Vec<u8>>,
}
#[pymethods]
impl PyPaPkAsReq {
#[staticmethod]
fn from_der(py: Python<'_>, data: &[u8]) -> PyResult<Self> {
let mut dec = Decoder::new(data, Encoding::Der);
let parsed: synta_krb5::pkinit::PaPkAsReq<'_> = dec.decode().map_err(SyntaErr)?;
let signed_auth_pack = parsed.signed_auth_pack.as_bytes().to_vec();
let trusted_certifiers = parsed
.trusted_certifiers
.map(|certs| {
certs
.into_iter()
.map(|epi| {
let isn = epi
.issuer_and_serial_number
.map(|isn| {
Py::new(
py,
PyIssuerAndSerialNumber {
issuer: isn.issuer.as_bytes().to_vec(),
serial_number_bytes: isn.serial_number.as_bytes().to_vec(),
},
)
})
.transpose()?;
Py::new(
py,
PyExternalPrincipalIdentifier {
subject_name: epi.subject_name.map(|s| s.as_bytes().to_vec()),
issuer_and_serial_number: isn,
subject_key_identifier: epi
.subject_key_identifier
.map(|s| s.as_bytes().to_vec()),
},
)
})
.collect::<PyResult<Vec<_>>>()
})
.transpose()?;
let kdc_pk_id = parsed.kdc_pk_id.map(|s| s.as_bytes().to_vec());
Ok(Self {
signed_auth_pack,
trusted_certifiers,
kdc_pk_id,
})
}
#[getter]
fn signed_auth_pack<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
PyBytes::new(py, &self.signed_auth_pack)
}
#[getter]
fn trusted_certifiers<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyList>>> {
self.trusted_certifiers
.as_ref()
.map(|certs| {
let list = PyList::empty(py);
for cert in certs {
list.append(cert.clone_ref(py).into_bound(py))?;
}
Ok(list)
})
.transpose()
}
#[getter]
fn kdc_pk_id<'py>(&self, py: Python<'py>) -> Option<Bound<'py, PyBytes>> {
self.kdc_pk_id.as_deref().map(|b| PyBytes::new(py, b))
}
fn __repr__(&self) -> String {
format!(
"PaPkAsReq(signed_auth_pack=<{} bytes>)",
self.signed_auth_pack.len()
)
}
}
pub fn register_pkinit_classes(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<PyEncryptionKey>()?;
m.add_class::<PyChecksum>()?;
m.add_class::<PyKDFAlgorithmId>()?;
m.add_class::<PyIssuerAndSerialNumber>()?;
m.add_class::<PyPKAuthenticator>()?;
m.add_class::<PyDHRepInfo>()?;
m.add_class::<PyKDCDHKeyInfo>()?;
m.add_class::<PyExternalPrincipalIdentifier>()?;
m.add_class::<PyReplyKeyPack>()?;
m.add_class::<PyPaPkAsRep>()?;
m.add_class::<PyAuthPack>()?;
m.add_class::<PyPaPkAsReq>()?;
Ok(())
}