1use 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#[must_use]
25pub struct RsaCrosscert {
26 subject_key: ll::pk::ed25519::Ed25519Identity,
28 exp_hours: ExpiryHours,
31 digest: [u8; 32],
33 signature: Vec<u8>,
35}
36
37const PREFIX: &[u8] = b"Tor TLS RSA/Ed25519 cross-certificate";
39
40fn 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 pub fn expiry(&self) -> std::time::SystemTime {
51 self.exp_hours.into()
52 }
53
54 pub fn digest(&self) -> &[u8; 32] {
56 &self.digest
57 }
58
59 pub fn subject_key_matches(&self, other: &ll::pk::ed25519::Ed25519Identity) -> bool {
61 other == &self.subject_key
62 }
63
64 pub fn cert_type(&self) -> CertType {
66 CertType::RSA_ID_V_IDENTITY
67 }
68
69 pub fn decode(bytes: &[u8]) -> tor_bytes::Result<UncheckedRsaCrosscert> {
71 let mut r = Reader::from_slice(bytes);
72 let signed_portion = r.peek(36)?; 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
91pub 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 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}