1use web_time_compat::SystemTime;
4
5use derive_more::{AsRef, Deref, Into};
6use tor_bytes::Writer as _;
7use tor_llcrypto::pk::{ed25519, rsa};
8
9use crate::{CertEncodeError, EncodedCert, ExpiryHours};
10
11#[derive(Clone, Debug, PartialEq, Into, AsRef, Deref)]
24pub struct EncodedRsaCrosscert(Vec<u8>);
25
26impl EncodedRsaCrosscert {
27 pub fn encode_and_sign(
34 rsa_identity: &rsa::KeyPair,
35 ed_identity: &ed25519::Ed25519Identity,
36 expiration: SystemTime,
37 ) -> Result<Self, CertEncodeError> {
38 let mut cert = Vec::new();
39 cert.write(ed_identity)?;
40 let exp_hours = ExpiryHours::try_from_systemtime_ceil(expiration)?;
41 cert.write(&exp_hours)?;
42 {
43 let signature = rsa_identity
44 .sign(&super::compute_digest(&cert))
45 .map_err(|_| CertEncodeError::RsaSignatureFailed)?;
46 let mut inner = cert.write_nested_u8len();
47 inner.write_and_consume(signature)?;
48 inner.finish()?;
49 }
50
51 Ok(EncodedRsaCrosscert(cert))
52 }
53}
54
55impl EncodedCert for EncodedRsaCrosscert {
56 fn cert_type(&self) -> crate::CertType {
57 crate::CertType::RSA_ID_V_IDENTITY
58 }
59
60 fn encoded(&self) -> &[u8] {
61 &self.0
62 }
63}
64
65#[cfg(test)]
66mod test {
67 #![allow(clippy::bool_assert_comparison)]
69 #![allow(clippy::clone_on_copy)]
70 #![allow(clippy::dbg_macro)]
71 #![allow(clippy::mixed_attributes_style)]
72 #![allow(clippy::print_stderr)]
73 #![allow(clippy::print_stdout)]
74 #![allow(clippy::single_char_pattern)]
75 #![allow(clippy::unwrap_used)]
76 #![allow(clippy::unchecked_time_subtraction)]
77 #![allow(clippy::useless_vec)]
78 #![allow(clippy::needless_pass_by_value)]
79 #![allow(clippy::string_slice)] use std::time::Duration;
82
83 use tor_basic_utils::test_rng::testing_rng;
84 use tor_checkable::{ExternallySigned, Timebound};
85 use web_time_compat::SystemTimeExt;
86
87 use crate::SEC_PER_HOUR;
88 use crate::rsa::RsaCrosscert;
89
90 use super::*;
91
92 #[test]
93 fn generate() {
94 let mut rng = testing_rng();
95 let keypair = rsa::KeyPair::generate(&mut rng).unwrap();
96 let ed_id =
97 ed25519::Ed25519Identity::from_base64("dGhhdW1hdHVyZ3kgaXMgc3RvcmVkIGluIHRoZSBvcmI")
98 .unwrap();
99
100 let now = SystemTime::get();
101 let expiry = now + Duration::from_secs(24 * SEC_PER_HOUR);
102
103 let cert = EncodedRsaCrosscert::encode_and_sign(&keypair, &ed_id, expiry).unwrap();
104
105 let parsed = RsaCrosscert::decode(cert.as_ref()).unwrap();
106 let parsed = parsed
107 .check_signature(&keypair.to_public_key())
108 .unwrap()
109 .check_valid_at(&now)
110 .unwrap();
111
112 assert!(parsed.subject_key_matches(&ed_id));
113 assert_eq!(parsed.subject_key, ed_id);
114 let parsed_expiry = parsed.expiry();
115 assert!(parsed_expiry >= expiry);
116 assert!(parsed_expiry < expiry + Duration::new(3600, 0));
117 }
118}