Skip to main content

tor_cert/rsa/
encode.rs

1//! RSA cross-cert generation
2
3use 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/// An RSA cross certificate certificate,
12/// created using [`EncodedRsaCrosscert::encode_and_sign`].
13///
14/// It corresponds to the type of certificate parsed with
15/// [`RsaCrosscert`](super::RsaCrosscert).
16/// It is used to prove that an Ed25519 identity speaks
17/// on behalf of an RSA identity.
18///
19/// The certificate is encoded in the format specified
20/// in Tor's [certificate specification](https://spec.torproject.org/cert-spec.html#rsa-cross-cert)
21///
22/// This certificate has already been validated.
23#[derive(Clone, Debug, PartialEq, Into, AsRef, Deref)]
24pub struct EncodedRsaCrosscert(Vec<u8>);
25
26impl EncodedRsaCrosscert {
27    /// Create a new [`EncodedRsaCrosscert`] certifying `ed_identity` as
28    /// speaking on behalf of `rsa_identity`.
29    ///
30    /// The certificate will expire no earlier than `expiration`,
31    /// and no more than one hour later.
32    /// (Expiration times in these certificates have a one-hour granularity.)
33    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    // @@ begin test lint list maintained by maint/add_warning @@
68    #![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    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
80    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}