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 use std::time::Duration;
81
82 use tor_basic_utils::test_rng::testing_rng;
83 use tor_checkable::{ExternallySigned, Timebound};
84 use web_time_compat::SystemTimeExt;
85
86 use crate::SEC_PER_HOUR;
87 use crate::rsa::RsaCrosscert;
88
89 use super::*;
90
91 #[test]
92 fn generate() {
93 let mut rng = testing_rng();
94 let keypair = rsa::KeyPair::generate(&mut rng).unwrap();
95 let ed_id =
96 ed25519::Ed25519Identity::from_base64("dGhhdW1hdHVyZ3kgaXMgc3RvcmVkIGluIHRoZSBvcmI")
97 .unwrap();
98
99 let now = SystemTime::get();
100 let expiry = now + Duration::from_secs(24 * SEC_PER_HOUR);
101
102 let cert = EncodedRsaCrosscert::encode_and_sign(&keypair, &ed_id, expiry).unwrap();
103
104 let parsed = RsaCrosscert::decode(cert.as_ref()).unwrap();
105 let parsed = parsed
106 .check_signature(&keypair.to_public_key())
107 .unwrap()
108 .check_valid_at(&now)
109 .unwrap();
110
111 assert!(parsed.subject_key_matches(&ed_id));
112 assert_eq!(parsed.subject_key, ed_id);
113 let parsed_expiry = parsed.expiry();
114 assert!(parsed_expiry >= expiry);
115 assert!(parsed_expiry < expiry + Duration::new(3600, 0));
116 }
117}