use pyo3::exceptions::PyValueError;
use pyo3::prelude::*;
use pyo3::types::PyBytes;
use synta::traits::{Decode, Encode};
use super::proof::PyHashAlgorithm;
use synta_python_common::SyntaErr;
#[pyclass(name = "ValidationPolicy")]
pub struct PyValidationPolicy {
allowed_hash_algorithm_oids: Vec<String>,
min_cosignatures: usize,
max_cosignatures: usize,
allow_duplicate_cosigners: bool,
require_valid_timestamps: bool,
check_expiration: bool,
max_certificate_age: Option<u64>,
}
impl PyValidationPolicy {
fn to_rust(&self) -> synta_mtc::config::ValidationPolicy {
use std::str::FromStr;
use synta_mtc::config::ValidationPolicy;
let mut policy = ValidationPolicy::new();
policy.allowed_hash_algorithms.clear();
for oid_str in &self.allowed_hash_algorithm_oids {
if let Ok(oid) = synta::ObjectIdentifier::from_str(oid_str) {
policy.allowed_hash_algorithms.push(oid);
}
}
policy.cosignature_policy.min_cosignatures = self.min_cosignatures;
policy.cosignature_policy.max_cosignatures = self.max_cosignatures;
policy.cosignature_policy.allow_duplicate_cosigners = self.allow_duplicate_cosigners;
policy.require_valid_timestamps = self.require_valid_timestamps;
policy.check_expiration = self.check_expiration;
policy.max_certificate_age = self.max_certificate_age;
policy
}
}
#[pymethods]
impl PyValidationPolicy {
#[new]
fn new() -> Self {
use synta_mtc::config::ValidationPolicy;
let default = ValidationPolicy::default();
PyValidationPolicy {
allowed_hash_algorithm_oids: default
.allowed_hash_algorithms
.iter()
.map(|o| o.to_string())
.collect(),
min_cosignatures: default.cosignature_policy.min_cosignatures,
max_cosignatures: default.cosignature_policy.max_cosignatures,
allow_duplicate_cosigners: default.cosignature_policy.allow_duplicate_cosigners,
require_valid_timestamps: default.require_valid_timestamps,
check_expiration: default.check_expiration,
max_certificate_age: default.max_certificate_age,
}
}
#[staticmethod]
fn permissive() -> Self {
use synta_mtc::config::ValidationPolicy;
let p = ValidationPolicy::permissive();
PyValidationPolicy {
allowed_hash_algorithm_oids: p
.allowed_hash_algorithms
.iter()
.map(|o| o.to_string())
.collect(),
min_cosignatures: p.cosignature_policy.min_cosignatures,
max_cosignatures: p.cosignature_policy.max_cosignatures,
allow_duplicate_cosigners: p.cosignature_policy.allow_duplicate_cosigners,
require_valid_timestamps: p.require_valid_timestamps,
check_expiration: p.check_expiration,
max_certificate_age: p.max_certificate_age,
}
}
fn allow_hash_algorithm(&mut self, algo: &PyHashAlgorithm) {
let oid_str = algo.inner.to_oid().to_string();
if !self.allowed_hash_algorithm_oids.contains(&oid_str) {
self.allowed_hash_algorithm_oids.push(oid_str);
}
}
fn set_min_cosignatures(&mut self, n: usize) {
self.min_cosignatures = n;
}
fn set_max_cosignatures(&mut self, n: usize) {
self.max_cosignatures = n;
}
fn set_allow_duplicate_cosigners(&mut self, allow: bool) {
self.allow_duplicate_cosigners = allow;
}
fn set_require_valid_timestamps(&mut self, require: bool) {
self.require_valid_timestamps = require;
}
fn set_check_expiration(&mut self, check: bool) {
self.check_expiration = check;
}
#[getter]
fn allowed_hash_algorithm_oids(&self) -> Vec<String> {
self.allowed_hash_algorithm_oids.clone()
}
#[getter]
fn min_cosignatures(&self) -> usize {
self.min_cosignatures
}
#[getter]
fn max_cosignatures(&self) -> usize {
self.max_cosignatures
}
#[getter]
fn allow_duplicate_cosigners(&self) -> bool {
self.allow_duplicate_cosigners
}
#[getter]
fn require_valid_timestamps(&self) -> bool {
self.require_valid_timestamps
}
#[getter]
fn check_expiration(&self) -> bool {
self.check_expiration
}
fn __repr__(&self) -> String {
format!(
"ValidationPolicy(min_cosignatures={}, algorithms={})",
self.min_cosignatures,
self.allowed_hash_algorithm_oids.len()
)
}
}
#[pyclass(name = "TrustAnchor")]
pub struct PyTrustAnchor {
pub(super) log_id_der: Box<[u8]>,
pub(super) name: String,
pub(super) active: bool,
pub(super) revoked_ranges: synta_mtc::revocation::RevokedRanges,
}
#[pymethods]
impl PyTrustAnchor {
#[staticmethod]
fn from_log_id_der(log_id_der: &[u8], name: String) -> PyResult<Self> {
let validated = {
let mut dec = synta::Decoder::new(log_id_der, synta::Encoding::Der);
synta_mtc::types::LogID::decode(&mut dec).map_err(SyntaErr)?
};
let mut enc = synta::Encoder::new(synta::Encoding::Der);
validated.encode(&mut enc).map_err(SyntaErr)?;
let owned: Box<[u8]> = enc.finish().map_err(SyntaErr)?.into_boxed_slice();
Ok(PyTrustAnchor {
log_id_der: owned,
name,
active: true,
revoked_ranges: synta_mtc::revocation::RevokedRanges::new(),
})
}
#[getter]
fn log_id_der<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
PyBytes::new(py, &self.log_id_der)
}
#[getter]
fn name(&self) -> &str {
&self.name
}
#[getter]
fn active(&self) -> bool {
self.active
}
fn activate(&mut self) {
self.active = true;
}
fn deactivate(&mut self) {
self.active = false;
}
fn is_revoked(&self, entry_index: u64) -> bool {
self.revoked_ranges.is_revoked(entry_index)
}
fn revoke_range(&mut self, start: u64, end: u64) -> PyResult<()> {
self.revoked_ranges
.add(start, end)
.map_err(|e| PyValueError::new_err(e.to_string()))
}
fn __repr__(&self) -> String {
format!("TrustAnchor(name='{}', active={})", self.name, self.active)
}
}
#[pyclass(name = "CertificateValidator")]
pub struct PyCertificateValidator {
anchor_log_id_bytes: Vec<Box<[u8]>>,
anchor_meta: Vec<(String, bool, synta_mtc::revocation::RevokedRanges)>,
policy: PyValidationPolicy,
}
impl PyCertificateValidator {
fn build_rust_validator(
&self,
) -> PyResult<synta_mtc::validator::CertificateValidator<'static>> {
use synta_mtc::config::{TrustAnchor, ValidatorConfig};
use synta_mtc::types::LogID;
let policy = self.policy.to_rust();
let mut config = ValidatorConfig::with_policy(policy);
for (der_box, (name, active, revoked)) in
self.anchor_log_id_bytes.iter().zip(self.anchor_meta.iter())
{
let static_bytes: &'static [u8] = unsafe { &*(der_box.as_ref() as *const [u8]) };
let mut dec = synta::Decoder::new(static_bytes, synta::Encoding::Der);
let log_id_decoded = LogID::decode(&mut dec).map_err(SyntaErr)?;
let log_id: LogID<'static> = unsafe { std::mem::transmute(log_id_decoded) };
let mut anchor = TrustAnchor::new(log_id, name.clone());
anchor.active = *active;
anchor.revoked_ranges = revoked.clone();
config.add_trust_anchor(anchor);
}
Ok(synta_mtc::validator::CertificateValidator::new(config))
}
}
#[pymethods]
impl PyCertificateValidator {
#[new]
#[pyo3(signature = (policy=None))]
fn new(policy: Option<&PyValidationPolicy>) -> Self {
let policy = match policy {
Some(p) => PyValidationPolicy {
allowed_hash_algorithm_oids: p.allowed_hash_algorithm_oids.clone(),
min_cosignatures: p.min_cosignatures,
max_cosignatures: p.max_cosignatures,
allow_duplicate_cosigners: p.allow_duplicate_cosigners,
require_valid_timestamps: p.require_valid_timestamps,
check_expiration: p.check_expiration,
max_certificate_age: p.max_certificate_age,
},
None => PyValidationPolicy::new(),
};
PyCertificateValidator {
anchor_log_id_bytes: Vec::new(),
anchor_meta: Vec::new(),
policy,
}
}
fn add_trust_anchor(&mut self, anchor: &PyTrustAnchor) {
self.anchor_log_id_bytes.push(anchor.log_id_der.clone());
self.anchor_meta.push((
anchor.name.clone(),
anchor.active,
anchor.revoked_ranges.clone(),
));
}
fn trust_anchor_count(&self) -> usize {
self.anchor_log_id_bytes.len()
}
fn validate_standalone(&self, cert_der: &[u8]) -> PyResult<()> {
use synta_mtc::types::StandaloneCertificate;
let mut dec = synta::Decoder::new(cert_der, synta::Encoding::Der);
let cert = StandaloneCertificate::decode(&mut dec).map_err(SyntaErr)?;
let validator = self.build_rust_validator()?;
validator
.validate_standalone(&cert)
.map_err(|e| PyValueError::new_err(e.to_string()))
}
fn validate_landmark(&self, cert_der: &[u8], checkpoint_der: &[u8]) -> PyResult<()> {
use synta_mtc::types::{Checkpoint, LandmarkCertificate};
let cert = {
let mut dec = synta::Decoder::new(cert_der, synta::Encoding::Der);
LandmarkCertificate::decode(&mut dec).map_err(SyntaErr)?
};
let checkpoint = {
let mut dec = synta::Decoder::new(checkpoint_der, synta::Encoding::Der);
Checkpoint::decode(&mut dec).map_err(SyntaErr)?
};
let validator = self.build_rust_validator()?;
validator
.validate_landmark(&cert, &checkpoint)
.map_err(|e| PyValueError::new_err(e.to_string()))
}
fn __repr__(&self) -> String {
format!(
"CertificateValidator(trust_anchors={}, min_cosignatures={})",
self.anchor_log_id_bytes.len(),
self.policy.min_cosignatures
)
}
}