1
//! RSA cross-cert generation
2

            
3
use web_time_compat::SystemTime;
4

            
5
use derive_more::{AsRef, Deref, Into};
6
use tor_bytes::Writer as _;
7
use tor_llcrypto::pk::{ed25519, rsa};
8

            
9
use 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)]
24
pub struct EncodedRsaCrosscert(Vec<u8>);
25

            
26
impl 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
98
    pub fn encode_and_sign(
34
98
        rsa_identity: &rsa::KeyPair,
35
98
        ed_identity: &ed25519::Ed25519Identity,
36
98
        expiration: SystemTime,
37
98
    ) -> Result<Self, CertEncodeError> {
38
98
        let mut cert = Vec::new();
39
98
        cert.write(ed_identity)?;
40
98
        let exp_hours = ExpiryHours::try_from_systemtime_ceil(expiration)?;
41
98
        cert.write(&exp_hours)?;
42
        {
43
98
            let signature = rsa_identity
44
98
                .sign(&super::compute_digest(&cert))
45
98
                .map_err(|_| CertEncodeError::RsaSignatureFailed)?;
46
98
            let mut inner = cert.write_nested_u8len();
47
98
            inner.write_and_consume(signature)?;
48
98
            inner.finish()?;
49
        }
50

            
51
98
        Ok(EncodedRsaCrosscert(cert))
52
98
    }
53
}
54

            
55
impl 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)]
66
mod 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
}