//! RSA cross-cert generation use web_time_compat::SystemTime; use derive_more::{AsRef, Deref, Into}; use tor_bytes::Writer as _; use tor_llcrypto::pk::{ed25519, rsa}; use crate::{CertEncodeError, EncodedCert, ExpiryHours}; /// An RSA cross certificate certificate, /// created using [`EncodedRsaCrosscert::encode_and_sign`]. /// /// It corresponds to the type of certificate parsed with /// [`RsaCrosscert`](super::RsaCrosscert). /// It is used to prove that an Ed25519 identity speaks /// on behalf of an RSA identity. /// /// The certificate is encoded in the format specified /// in Tor's [certificate specification](https://spec.torproject.org/cert-spec.html#rsa-cross-cert) /// /// This certificate has already been validated. #[derive(Clone, Debug, PartialEq, Into, AsRef, Deref)] pub struct EncodedRsaCrosscert(Vec); impl EncodedRsaCrosscert { /// Create a new [`EncodedRsaCrosscert`] certifying `ed_identity` as /// speaking on behalf of `rsa_identity`. /// /// The certificate will expire no earlier than `expiration`, /// and no more than one hour later. /// (Expiration times in these certificates have a one-hour granularity.) pub fn encode_and_sign( rsa_identity: &rsa::KeyPair, ed_identity: &ed25519::Ed25519Identity, expiration: SystemTime, ) -> Result { let mut cert = Vec::new(); cert.write(ed_identity)?; let exp_hours = ExpiryHours::try_from_systemtime_ceil(expiration)?; cert.write(&exp_hours)?; { let signature = rsa_identity .sign(&super::compute_digest(&cert)) .map_err(|_| CertEncodeError::RsaSignatureFailed)?; let mut inner = cert.write_nested_u8len(); inner.write_and_consume(signature)?; inner.finish()?; } Ok(EncodedRsaCrosscert(cert)) } } impl EncodedCert for EncodedRsaCrosscert { fn cert_type(&self) -> crate::CertType { crate::CertType::RSA_ID_V_IDENTITY } fn encoded(&self) -> &[u8] { &self.0 } } #[cfg(test)] mod test { // @@ begin test lint list maintained by maint/add_warning @@ #![allow(clippy::bool_assert_comparison)] #![allow(clippy::clone_on_copy)] #![allow(clippy::dbg_macro)] #![allow(clippy::mixed_attributes_style)] #![allow(clippy::print_stderr)] #![allow(clippy::print_stdout)] #![allow(clippy::single_char_pattern)] #![allow(clippy::unwrap_used)] #![allow(clippy::unchecked_time_subtraction)] #![allow(clippy::useless_vec)] #![allow(clippy::needless_pass_by_value)] //! use std::time::Duration; use tor_basic_utils::test_rng::testing_rng; use tor_checkable::{ExternallySigned, Timebound}; use web_time_compat::SystemTimeExt; use crate::SEC_PER_HOUR; use crate::rsa::RsaCrosscert; use super::*; #[test] fn generate() { let mut rng = testing_rng(); let keypair = rsa::KeyPair::generate(&mut rng).unwrap(); let ed_id = ed25519::Ed25519Identity::from_base64("dGhhdW1hdHVyZ3kgaXMgc3RvcmVkIGluIHRoZSBvcmI") .unwrap(); let now = SystemTime::get(); let expiry = now + Duration::from_secs(24 * SEC_PER_HOUR); let cert = EncodedRsaCrosscert::encode_and_sign(&keypair, &ed_id, expiry).unwrap(); let parsed = RsaCrosscert::decode(cert.as_ref()).unwrap(); let parsed = parsed .check_signature(&keypair.to_public_key()) .unwrap() .check_valid_at(&now) .unwrap(); assert!(parsed.subject_key_matches(&ed_id)); assert_eq!(parsed.subject_key, ed_id); let parsed_expiry = parsed.expiry(); assert!(parsed_expiry >= expiry); assert!(parsed_expiry < expiry + Duration::new(3600, 0)); } }