1
//! RSA cross-cert generation
2

            
3
use std::time::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;
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
2
    pub fn encode_and_sign(
34
2
        rsa_identity: &rsa::KeyPair,
35
2
        ed_identity: &ed25519::Ed25519Identity,
36
2
        expiration: SystemTime,
37
2
    ) -> Result<Self, CertEncodeError> {
38
2
        let mut cert = Vec::new();
39
2
        cert.write(ed_identity)?;
40
2
        let hours_since_epoch: u32 = expiration
41
2
            .duration_since(SystemTime::UNIX_EPOCH)
42
2
            .map_err(|_| CertEncodeError::InvalidExpiration)?
43
2
            .as_secs()
44
2
            .div_ceil(super::SECS_PER_HOUR)
45
2
            .try_into()
46
2
            .map_err(|_| CertEncodeError::InvalidExpiration)?;
47
2
        cert.write_u32(hours_since_epoch);
48
        {
49
2
            let signature = rsa_identity
50
2
                .sign(&super::compute_digest(&cert))
51
2
                .map_err(|_| CertEncodeError::RsaSignatureFailed)?;
52
2
            let mut inner = cert.write_nested_u8len();
53
2
            inner.write_and_consume(signature)?;
54
2
            inner.finish()?;
55
        }
56

            
57
2
        Ok(EncodedRsaCrosscert(cert))
58
2
    }
59
}
60

            
61
#[cfg(test)]
62
mod 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
}