Skip to main content

tor_cert/
encode.rs

1//! Code for constructing and signing certificates.
2//!
3//! Only available when the crate is built with the `encode` feature.
4
5use crate::{
6    CertEncodeError, CertExt, CertType, Ed25519Cert, Ed25519CertConstructor, ExpiryHours, ExtType,
7    SignedWithEd25519Ext, UnrecognizedExt,
8};
9use std::time::SystemTime;
10use tor_bytes::{EncodeResult, Writeable, Writer};
11use tor_llcrypto::pk::ed25519::{self, Ed25519PublicKey, Ed25519SigningKey};
12
13use derive_more::{AsRef, Deref, Into};
14
15/// An encoded ed25519 certificate,
16/// created using [`Ed25519CertConstructor::encode_and_sign`].
17///
18/// The certificate is encoded in the format specified
19/// in Tor's cert-spec.txt.
20///
21/// This certificate has already been validated.
22#[derive(Clone, Debug, PartialEq, Into, AsRef, Deref)]
23pub struct EncodedEd25519Cert(Vec<u8>);
24
25impl Ed25519Cert {
26    /// Return a new `Ed25519CertConstructor` to create and return a new signed
27    /// `Ed25519Cert`.
28    pub fn constructor() -> Ed25519CertConstructor {
29        Default::default()
30    }
31}
32
33impl EncodedEd25519Cert {
34    /// Create an `EncodedEd25519Cert` from a byte slice.
35    ///
36    /// **Important**: generally you should not use this function.
37    /// Instead, prefer using [`Ed25519CertConstructor::encode_and_sign`]
38    /// to encode certificates.
39    ///
40    /// This function should only be used if `cert`
41    /// is known to be the byte representation of a valid `EncodedEd25519Cert`
42    /// (for example, after parsing the byte slice using [`Ed25519Cert::decode`],
43    /// and validating its signature and timeliness).
44    #[cfg(feature = "experimental-api")]
45    pub fn dangerously_from_bytes(cert: &[u8]) -> Self {
46        Self(cert.into())
47    }
48}
49
50impl Writeable for CertExt {
51    /// As Writeable::WriteOnto, but may return an error.
52    ///
53    /// TODO: Migrate Writeable to provide this interface.
54    fn write_onto<B: Writer + ?Sized>(&self, w: &mut B) -> EncodeResult<()> {
55        match self {
56            CertExt::SignedWithEd25519(pk) => pk.write_onto(w),
57            CertExt::Unrecognized(u) => u.write_onto(w),
58        }
59    }
60}
61
62impl Writeable for SignedWithEd25519Ext {
63    /// As Writeable::WriteOnto, but may return an error.
64    fn write_onto<B: Writer + ?Sized>(&self, w: &mut B) -> EncodeResult<()> {
65        // body length
66        w.write_u16(32);
67        // Signed-with-ed25519-key-extension
68        w.write_u8(ExtType::SIGNED_WITH_ED25519_KEY.into());
69        // flags = 0.
70        w.write_u8(0);
71        // body
72        w.write_all(self.pk.as_bytes());
73        Ok(())
74    }
75}
76
77impl Writeable for UnrecognizedExt {
78    /// As Writeable::WriteOnto, but may return an error.
79    fn write_onto<B: Writer + ?Sized>(&self, w: &mut B) -> EncodeResult<()> {
80        // We can't use Writer::write_nested_u16len here, since the length field
81        // doesn't include the type or the flags.
82        w.write_u16(
83            self.body
84                .len()
85                .try_into()
86                .map_err(|_| tor_bytes::EncodeError::BadLengthValue)?,
87        );
88        w.write_u8(self.ext_type.into());
89        let flags = u8::from(self.affects_validation);
90        w.write_u8(flags);
91        w.write_all(&self.body[..]);
92        Ok(())
93    }
94}
95
96impl Ed25519CertConstructor {
97    /// Set the approximate expiration time for this certificate.
98    ///
99    /// (The time will be rounded forward to the nearest hour after the epoch.)
100    pub fn expiration(&mut self, expiration: SystemTime) -> &mut Self {
101        let exp_hours = ExpiryHours::try_from_systemtime_ceil(expiration)
102            .unwrap_or_else(|_| ExpiryHours::max());
103        self.exp_hours = Some(exp_hours);
104        self
105    }
106
107    /// Set the signing key to be included with this certificate.
108    ///
109    /// This is optional: you don't need to include the signing key at all.  If
110    /// you do, it must match the key that you actually use to sign the
111    /// certificate.
112    pub fn signing_key(&mut self, key: ed25519::Ed25519Identity) -> &mut Self {
113        self.clear_signing_key();
114        self.signed_with = Some(Some(key));
115        self.extensions
116            .get_or_insert_with(Vec::new)
117            .push(CertExt::SignedWithEd25519(SignedWithEd25519Ext { pk: key }));
118
119        self
120    }
121
122    /// Remove any signing key previously set on this Ed25519CertConstructor.
123    pub fn clear_signing_key(&mut self) -> &mut Self {
124        self.signed_with = None;
125        self.extensions
126            .get_or_insert_with(Vec::new)
127            .retain(|ext| !matches!(ext, CertExt::SignedWithEd25519(_)));
128        self
129    }
130
131    /// Encode a certificate into a new vector, signing the result
132    /// with `skey`.
133    ///
134    /// This function exists in lieu of a `build()` function, since we have a rule that
135    /// we don't produce an `Ed25519Cert` except if the certificate is known to be
136    /// valid.
137    pub fn encode_and_sign<S>(&self, skey: &S) -> Result<EncodedEd25519Cert, CertEncodeError>
138    where
139        S: Ed25519PublicKey + Ed25519SigningKey,
140    {
141        let Ed25519CertConstructor {
142            exp_hours,
143            cert_type,
144            cert_key,
145            extensions,
146            signed_with,
147        } = self;
148
149        // As long as there is no setter for Ed25519Cert::signed_with, this is unreachable
150        if let Some(Some(signer)) = &signed_with {
151            if *signer != skey.public_key().into() {
152                return Err(CertEncodeError::KeyMismatch);
153            }
154        }
155
156        let mut w = Vec::new();
157        w.write_u8(1); // Version
158        w.write_u8(
159            cert_type
160                .ok_or(CertEncodeError::MissingField("cert_type"))?
161                .into(),
162        );
163        w.write(&exp_hours.ok_or(CertEncodeError::MissingField("expiration"))?)?;
164        let cert_key = cert_key
165            .clone()
166            .ok_or(CertEncodeError::MissingField("cert_key"))?;
167        w.write_u8(cert_key.key_type().into());
168        w.write_all(cert_key.as_bytes());
169        let extensions = extensions.as_ref().map(Vec::as_slice).unwrap_or(&[]);
170        w.write_u8(
171            extensions
172                .len()
173                .try_into()
174                .map_err(|_| CertEncodeError::TooManyExtensions)?,
175        );
176
177        for e in extensions.iter() {
178            e.write_onto(&mut w)?;
179        }
180
181        let signature = skey.sign(&w[..]);
182        w.write(&signature)?;
183        Ok(EncodedEd25519Cert(w))
184    }
185}
186
187/// A certificate with an encoded representation.
188pub trait EncodedCert {
189    /// Return the type of this certificate.
190    fn cert_type(&self) -> CertType;
191    /// Return the encoding of this certificate.
192    fn encoded(&self) -> &[u8];
193}
194
195impl EncodedCert for EncodedEd25519Cert {
196    fn cert_type(&self) -> CertType {
197        // The cert type is the second byte of the certificate.
198        self.0[1].into()
199    }
200
201    fn encoded(&self) -> &[u8] {
202        &self.0
203    }
204}
205
206#[cfg(test)]
207mod test {
208    // @@ begin test lint list maintained by maint/add_warning @@
209    #![allow(clippy::bool_assert_comparison)]
210    #![allow(clippy::clone_on_copy)]
211    #![allow(clippy::dbg_macro)]
212    #![allow(clippy::mixed_attributes_style)]
213    #![allow(clippy::print_stderr)]
214    #![allow(clippy::print_stdout)]
215    #![allow(clippy::single_char_pattern)]
216    #![allow(clippy::unwrap_used)]
217    #![allow(clippy::unchecked_time_subtraction)]
218    #![allow(clippy::useless_vec)]
219    #![allow(clippy::needless_pass_by_value)]
220    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
221    use super::*;
222    use crate::CertifiedKey;
223    use tor_checkable::{SelfSigned, Timebound};
224    use web_time_compat::{Duration, SystemTimeExt};
225
226    #[test]
227    fn signed_cert_without_key() {
228        let mut rng = rand::rng();
229        let keypair = ed25519::Keypair::generate(&mut rng);
230        let now = SystemTime::get();
231        let day = Duration::from_secs(86400);
232        let encoded = Ed25519Cert::constructor()
233            .expiration(now + day * 30)
234            .cert_key(CertifiedKey::Ed25519(keypair.verifying_key().into()))
235            .cert_type(7.into())
236            .encode_and_sign(&keypair)
237            .unwrap();
238
239        assert_eq!(encoded.cert_type(), 7.into());
240
241        let decoded = Ed25519Cert::decode(&encoded).unwrap(); // Well-formed?
242        let validated = decoded
243            .should_be_signed_with(&keypair.verifying_key().into())
244            .unwrap()
245            .check_signature()
246            .unwrap(); // Well-signed?
247        let cert = validated.check_valid_at(&(now + day * 20)).unwrap();
248        assert_eq!(cert.cert_type(), 7.into());
249        if let CertifiedKey::Ed25519(found) = cert.subject_key() {
250            assert_eq!(found, &keypair.verifying_key().into());
251        } else {
252            panic!("wrong key type");
253        }
254        assert!(cert.signing_key() == Some(&keypair.verifying_key().into()));
255    }
256
257    #[test]
258    fn unrecognized_ext() {
259        use hex_literal::hex;
260        use tor_bytes::Reader;
261
262        let mut reader = Reader::from_slice(&hex!("0001 2A 00 2A"));
263        let ext: CertExt = reader.extract().unwrap();
264
265        let mut encoded: Vec<u8> = Vec::new();
266        encoded.write(&ext).unwrap();
267
268        assert_eq!(encoded, hex!("0001 2A 00 2A"));
269    }
270}