Skip to main content

tor_cert/rsa/
encode.rs

1//! RSA cross-cert generation
2
3use 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/// 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 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    // @@ begin test lint list maintained by maint/add_warning @@
64    #![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    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
76    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}