use bytes::Bytes;
use hashiverse_lib::protocol::peer::Peer;
use hashiverse_lib::tools::cert_validation::is_cert_valid;
use hashiverse_lib::tools::time::TimeMillis;
use hashiverse_lib::transport::transport_ownership_proof::TransportOwnershipProof;
use parking_lot::{Mutex, RwLock};
use rustls::sign::CertifiedKey;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
#[derive(Serialize, Deserialize)]
struct HttpsAcmeProof {
chain_der: Vec<Vec<u8>>,
}
struct CachedProofPayload {
certified_key: Arc<CertifiedKey>,
payload_bytes: Bytes,
}
pub struct HttpsTransportOwnershipProof {
base_cert: Arc<RwLock<Option<Arc<CertifiedKey>>>>,
cached_payload: Mutex<Option<CachedProofPayload>>,
}
impl HttpsTransportOwnershipProof {
pub fn new(base_cert: Arc<RwLock<Option<Arc<CertifiedKey>>>>) -> Self {
Self { base_cert, cached_payload: Mutex::new(None) }
}
}
impl TransportOwnershipProof for HttpsTransportOwnershipProof {
fn make_ownership_proof_payload(&self) -> Option<Bytes> {
let current_certified_key: Arc<CertifiedKey> = {
let base_cert_guard = self.base_cert.read();
base_cert_guard.as_ref()?.clone()
};
{
let cache_guard = self.cached_payload.lock();
if let Some(cached) = cache_guard.as_ref() {
if Arc::ptr_eq(&cached.certified_key, ¤t_certified_key) {
return Some(cached.payload_bytes.clone());
}
}
}
let chain_der: Vec<Vec<u8>> = current_certified_key.cert.iter().map(|der| der.to_vec()).collect();
if chain_der.is_empty() {
return None;
}
let proof: HttpsAcmeProof = HttpsAcmeProof { chain_der };
let encoded: Vec<u8> = postcard::to_allocvec(&proof).ok()?;
let payload_bytes: Bytes = Bytes::from(encoded);
*self.cached_payload.lock() = Some(CachedProofPayload {
certified_key: current_certified_key,
payload_bytes: payload_bytes.clone(),
});
Some(payload_bytes)
}
fn prove(&self, peer: &Peer, proof_payload: &[u8], now: TimeMillis) -> bool {
let proof: HttpsAcmeProof = match postcard::from_bytes(proof_payload) {
Ok(p) => p,
Err(_) => return false,
};
is_cert_valid(&proof.chain_der, &peer.address, now)
}
}
#[cfg(test)]
mod tests {
use super::*;
use hashiverse_lib::tools::types::Id;
fn peer_at(address: &str) -> Peer {
let mut peer: Peer = Peer::zero();
peer.address = address.to_string();
peer.id = Id([42u8; 32]);
peer
}
fn empty_base_cert_lock() -> Arc<RwLock<Option<Arc<CertifiedKey>>>> {
Arc::new(RwLock::new(None))
}
#[test]
fn make_returns_none_when_no_cert_loaded_yet() {
let proof: HttpsTransportOwnershipProof = HttpsTransportOwnershipProof::new(empty_base_cert_lock());
assert!(proof.make_ownership_proof_payload().is_none());
}
#[test]
fn prove_rejects_garbage_bytes() {
let proof: HttpsTransportOwnershipProof = HttpsTransportOwnershipProof::new(empty_base_cert_lock());
let peer: Peer = peer_at("1.2.3.4:443");
let now: TimeMillis = TimeMillis(1_700_000_000_000);
assert!(!proof.prove(&peer, &[0xff, 0xfe, 0xfd], now));
}
#[test]
fn prove_rejects_empty_bytes() {
let proof: HttpsTransportOwnershipProof = HttpsTransportOwnershipProof::new(empty_base_cert_lock());
let peer: Peer = peer_at("1.2.3.4:443");
let now: TimeMillis = TimeMillis(1_700_000_000_000);
assert!(!proof.prove(&peer, &[], now));
}
fn placeholder_certified_key(der_bytes: Vec<u8>) -> Arc<CertifiedKey> {
use rustls_pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer};
let _ = rustls::crypto::ring::default_provider().install_default();
let cert_chain: Vec<CertificateDer<'static>> = vec![CertificateDer::from(der_bytes)];
let key_pair: rcgen::KeyPair = rcgen::KeyPair::generate().expect("rcgen key gen never fails on supported algorithms");
let pkcs8: Vec<u8> = key_pair.serialize_der();
let key_der: PrivateKeyDer<'static> = PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from(pkcs8));
let signing: Arc<dyn rustls::sign::SigningKey> = rustls::crypto::ring::sign::any_supported_type(&key_der).expect("rcgen key is a supported type");
Arc::new(CertifiedKey { cert: cert_chain, key: signing, ocsp: None })
}
#[test]
fn cache_hit_returns_identical_bytes_when_cert_unchanged() {
let base_cert: Arc<RwLock<Option<Arc<CertifiedKey>>>> = Arc::new(RwLock::new(Some(placeholder_certified_key(vec![0x30, 0x82, 0x01, 0x00, 0xAA]))));
let proof: HttpsTransportOwnershipProof = HttpsTransportOwnershipProof::new(base_cert);
let first_bytes: Bytes = proof.make_ownership_proof_payload().expect("placeholder cert is non-empty");
let second_bytes: Bytes = proof.make_ownership_proof_payload().expect("placeholder cert is non-empty");
assert_eq!(first_bytes.as_ptr(), second_bytes.as_ptr());
assert_eq!(first_bytes, second_bytes);
}
#[test]
fn cache_miss_when_cert_rotates() {
let base_cert: Arc<RwLock<Option<Arc<CertifiedKey>>>> = Arc::new(RwLock::new(Some(placeholder_certified_key(vec![0x01, 0x02, 0x03]))));
let proof: HttpsTransportOwnershipProof = HttpsTransportOwnershipProof::new(base_cert.clone());
let first_bytes: Bytes = proof.make_ownership_proof_payload().expect("placeholder cert is non-empty");
*base_cert.write() = Some(placeholder_certified_key(vec![0x04, 0x05, 0x06]));
let second_bytes: Bytes = proof.make_ownership_proof_payload().expect("placeholder cert is non-empty");
assert_ne!(first_bytes.as_ptr(), second_bytes.as_ptr());
assert_ne!(first_bytes, second_bytes);
}
}