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