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    #![allow(clippy::string_slice)] // See arti#2571
80    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
81    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}