1use std::time::SystemTime;
4
5use derive_more::{AsRef, Deref, Into};
6use tor_bytes::Writer as _;
7use tor_llcrypto::pk::{ed25519, rsa};
8
9use crate::CertEncodeError;
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 hours_since_epoch: u32 = expiration
41 .duration_since(SystemTime::UNIX_EPOCH)
42 .map_err(|_| CertEncodeError::InvalidExpiration)?
43 .as_secs()
44 .div_ceil(super::SECS_PER_HOUR)
45 .try_into()
46 .map_err(|_| CertEncodeError::InvalidExpiration)?;
47 cert.write_u32(hours_since_epoch);
48 {
49 let signature = rsa_identity
50 .sign(&super::compute_digest(&cert))
51 .map_err(|_| CertEncodeError::RsaSignatureFailed)?;
52 let mut inner = cert.write_nested_u8len();
53 inner.write_and_consume(signature)?;
54 inner.finish()?;
55 }
56
57 Ok(EncodedRsaCrosscert(cert))
58 }
59}
60
61#[cfg(test)]
62mod test {
63 #![allow(clippy::bool_assert_comparison)]
65 #![allow(clippy::clone_on_copy)]
66 #![allow(clippy::dbg_macro)]
67 #![allow(clippy::mixed_attributes_style)]
68 #![allow(clippy::print_stderr)]
69 #![allow(clippy::print_stdout)]
70 #![allow(clippy::single_char_pattern)]
71 #![allow(clippy::unwrap_used)]
72 #![allow(clippy::unchecked_time_subtraction)]
73 #![allow(clippy::useless_vec)]
74 #![allow(clippy::needless_pass_by_value)]
75 use std::time::Duration;
77
78 use tor_basic_utils::test_rng::testing_rng;
79 use tor_checkable::{ExternallySigned, Timebound};
80
81 use crate::rsa::{RsaCrosscert, SECS_PER_HOUR};
82
83 use super::*;
84
85 #[test]
86 fn generate() {
87 let mut rng = testing_rng();
88 let keypair = rsa::KeyPair::generate(&mut rng).unwrap();
89 let ed_id =
90 ed25519::Ed25519Identity::from_base64("dGhhdW1hdHVyZ3kgaXMgc3RvcmVkIGluIHRoZSBvcmI")
91 .unwrap();
92
93 let now = SystemTime::now();
94 let expiry = now + Duration::from_secs(24 * SECS_PER_HOUR);
95
96 let cert = EncodedRsaCrosscert::encode_and_sign(&keypair, &ed_id, expiry).unwrap();
97
98 let parsed = RsaCrosscert::decode(cert.as_ref()).unwrap();
99 let parsed = parsed
100 .check_signature(&keypair.to_public_key())
101 .unwrap()
102 .check_valid_at(&now)
103 .unwrap();
104
105 assert!(parsed.subject_key_matches(&ed_id));
106 assert_eq!(parsed.subject_key, ed_id);
107 let parsed_expiry = parsed.expiry();
108 assert!(parsed_expiry >= expiry);
109 assert!(parsed_expiry < expiry + Duration::new(3600, 0));
110 }
111}