Skip to main content

tor_cert/
rsa.rs

1//! RSA->Ed25519 cross-certificates
2//!
3//! These are used in the Tor link handshake to prove that a given ed25519
4//! key speaks for a given (deprecated) RSA identity.
5
6use tor_bytes::Reader;
7use tor_checkable::{ExternallySigned, timed::TimerangeBound};
8use tor_llcrypto as ll;
9
10use digest::Digest;
11
12use crate::CertType;
13
14#[cfg(feature = "encode")]
15mod encode;
16#[cfg(feature = "encode")]
17pub use encode::EncodedRsaCrosscert;
18
19/// A RSA->Ed25519 cross-certificate
20///
21/// This kind of certificate is used in the channel handshake to prove
22/// that the Ed25519 identity key speaks on behalf of the RSA identity key.
23///
24/// (There is no converse type for certifying Ed25519 identity keys with
25/// RSA identity keys, since the RSA identity keys are too weak to trust.)
26#[must_use]
27pub struct RsaCrosscert {
28    /// The key that is being certified
29    subject_key: ll::pk::ed25519::Ed25519Identity,
30    /// The expiration time of this certificate, in hours since the
31    /// unix epoch.
32    exp_hours: u32,
33    /// The digest of the signed part of the certificate (for checking)
34    digest: [u8; 32],
35    /// The (alleged) signature on the certificate.
36    signature: Vec<u8>,
37}
38
39/// Number of seconds in an hour.
40const SECS_PER_HOUR: u64 = 3600;
41
42/// Prefix appended when generating a digest for an RsaCrosscert
43const PREFIX: &[u8] = b"Tor TLS RSA/Ed25519 cross-certificate";
44
45/// Compute the SHA256 digest of `c`, prefixed with PREFIX.
46fn compute_digest(c: &[u8]) -> [u8; 32] {
47    let mut d = ll::d::Sha256::new();
48    d.update(PREFIX);
49    d.update(c);
50    d.finalize().into()
51}
52
53impl RsaCrosscert {
54    /// Return the time at which this certificate becomes expired
55    pub fn expiry(&self) -> std::time::SystemTime {
56        let d = std::time::Duration::new(u64::from(self.exp_hours) * SECS_PER_HOUR, 0);
57        std::time::SystemTime::UNIX_EPOCH + d
58    }
59
60    /// Return a reference to the digest.
61    pub fn digest(&self) -> &[u8; 32] {
62        &self.digest
63    }
64
65    /// Return true if the subject key in this certificate matches `other`
66    pub fn subject_key_matches(&self, other: &ll::pk::ed25519::Ed25519Identity) -> bool {
67        other == &self.subject_key
68    }
69
70    /// Return this certificate cert type.
71    pub fn cert_type(&self) -> CertType {
72        CertType::RSA_ID_V_IDENTITY
73    }
74
75    /// Decode a slice of bytes into an RSA crosscert.
76    pub fn decode(bytes: &[u8]) -> tor_bytes::Result<UncheckedRsaCrosscert> {
77        let mut r = Reader::from_slice(bytes);
78        let signed_portion = r.peek(36)?; // TODO(nickm): a bit ugly.
79        let subject_key = r.extract()?;
80        let exp_hours = r.take_u32()?;
81        let siglen = r.take_u8()?;
82        let signature = r.take(siglen as usize)?.into();
83
84        let digest = compute_digest(signed_portion);
85
86        let cc = RsaCrosscert {
87            subject_key,
88            exp_hours,
89            digest,
90            signature,
91        };
92
93        Ok(UncheckedRsaCrosscert(cc))
94    }
95}
96
97/// An RsaCrosscert whose signature has not been checked.
98pub struct UncheckedRsaCrosscert(RsaCrosscert);
99
100impl ExternallySigned<TimerangeBound<RsaCrosscert>> for UncheckedRsaCrosscert {
101    type Key = ll::pk::rsa::PublicKey;
102    type KeyHint = ();
103    type Error = tor_bytes::Error;
104
105    fn key_is_correct(&self, _k: &Self::Key) -> Result<(), Self::KeyHint> {
106        // there is no way to check except for trying to verify the signature
107        Ok(())
108    }
109
110    fn is_well_signed(&self, k: &Self::Key) -> Result<(), Self::Error> {
111        k.verify(&self.0.digest[..], &self.0.signature[..])
112            .map_err(|_| {
113                tor_bytes::Error::InvalidMessage(
114                    "Invalid signature on RSA->Ed identity crosscert".into(),
115                )
116            })?;
117        Ok(())
118    }
119
120    fn dangerously_assume_wellsigned(self) -> TimerangeBound<RsaCrosscert> {
121        let expiration = self.0.expiry();
122        TimerangeBound::new(self.0, ..expiration)
123    }
124}