1
//! Code for constructing and signing certificates.
2
//!
3
//! Only available when the crate is built with the `encode` feature.
4

            
5
use crate::{
6
    CertEncodeError, CertExt, CertType, Ed25519Cert, Ed25519CertConstructor, ExpiryHours, ExtType,
7
    SignedWithEd25519Ext, UnrecognizedExt,
8
};
9
use std::time::SystemTime;
10
use tor_bytes::{EncodeResult, Writeable, Writer};
11
use tor_llcrypto::pk::ed25519::{self, Ed25519PublicKey, Ed25519SigningKey};
12

            
13
use 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)]
23
pub struct EncodedEd25519Cert(Vec<u8>);
24

            
25
impl Ed25519Cert {
26
    /// Return a new `Ed25519CertConstructor` to create and return a new signed
27
    /// `Ed25519Cert`.
28
34571
    pub fn constructor() -> Ed25519CertConstructor {
29
34571
        Default::default()
30
34571
    }
31
}
32

            
33
impl 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
61
    pub fn dangerously_from_bytes(cert: &[u8]) -> Self {
46
61
        Self(cert.into())
47
61
    }
48
}
49

            
50
impl Writeable for CertExt {
51
    /// As Writeable::WriteOnto, but may return an error.
52
    ///
53
    /// TODO: Migrate Writeable to provide this interface.
54
23966
    fn write_onto<B: Writer + ?Sized>(&self, w: &mut B) -> EncodeResult<()> {
55
23966
        match self {
56
23964
            CertExt::SignedWithEd25519(pk) => pk.write_onto(w),
57
2
            CertExt::Unrecognized(u) => u.write_onto(w),
58
        }
59
23966
    }
60
}
61

            
62
impl Writeable for SignedWithEd25519Ext {
63
    /// As Writeable::WriteOnto, but may return an error.
64
23964
    fn write_onto<B: Writer + ?Sized>(&self, w: &mut B) -> EncodeResult<()> {
65
        // body length
66
23964
        w.write_u16(32);
67
        // Signed-with-ed25519-key-extension
68
23964
        w.write_u8(ExtType::SIGNED_WITH_ED25519_KEY.into());
69
        // flags = 0.
70
23964
        w.write_u8(0);
71
        // body
72
23964
        w.write_all(self.pk.as_bytes());
73
23964
        Ok(())
74
23964
    }
75
}
76

            
77
impl Writeable for UnrecognizedExt {
78
    /// As Writeable::WriteOnto, but may return an error.
79
2
    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
2
        w.write_u16(
83
2
            self.body
84
2
                .len()
85
2
                .try_into()
86
2
                .map_err(|_| tor_bytes::EncodeError::BadLengthValue)?,
87
        );
88
2
        w.write_u8(self.ext_type.into());
89
2
        let flags = u8::from(self.affects_validation);
90
2
        w.write_u8(flags);
91
2
        w.write_all(&self.body[..]);
92
2
        Ok(())
93
2
    }
94
}
95

            
96
impl 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
34571
    pub fn expiration(&mut self, expiration: SystemTime) -> &mut Self {
101
34571
        let exp_hours = ExpiryHours::try_from_systemtime_ceil(expiration)
102
34571
            .unwrap_or_else(|_| ExpiryHours::max());
103
34571
        self.exp_hours = Some(exp_hours);
104
34571
        self
105
34571
    }
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
34569
    pub fn signing_key(&mut self, key: ed25519::Ed25519Identity) -> &mut Self {
113
34569
        self.clear_signing_key();
114
34569
        self.signed_with = Some(Some(key));
115
34569
        self.extensions
116
34569
            .get_or_insert_with(Vec::new)
117
34569
            .push(CertExt::SignedWithEd25519(SignedWithEd25519Ext { pk: key }));
118

            
119
34569
        self
120
34569
    }
121

            
122
    /// Remove any signing key previously set on this Ed25519CertConstructor.
123
34569
    pub fn clear_signing_key(&mut self) -> &mut Self {
124
34569
        self.signed_with = None;
125
34569
        self.extensions
126
34569
            .get_or_insert_with(Vec::new)
127
34569
            .retain(|ext| !matches!(ext, CertExt::SignedWithEd25519(_)));
128
34569
        self
129
34569
    }
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
5150
    pub fn encode_and_sign<S>(&self, skey: &S) -> Result<EncodedEd25519Cert, CertEncodeError>
138
5150
    where
139
5150
        S: Ed25519PublicKey + Ed25519SigningKey,
140
    {
141
        let Ed25519CertConstructor {
142
5150
            exp_hours,
143
5150
            cert_type,
144
5150
            cert_key,
145
5150
            extensions,
146
5150
            signed_with,
147
5150
        } = self;
148

            
149
        // As long as there is no setter for Ed25519Cert::signed_with, this is unreachable
150
5148
        if let Some(Some(signer)) = &signed_with {
151
5148
            if *signer != skey.public_key().into() {
152
                return Err(CertEncodeError::KeyMismatch);
153
5148
            }
154
2
        }
155

            
156
5150
        let mut w = Vec::new();
157
5150
        w.write_u8(1); // Version
158
5150
        w.write_u8(
159
5150
            cert_type
160
5150
                .ok_or(CertEncodeError::MissingField("cert_type"))?
161
5150
                .into(),
162
        );
163
5150
        w.write(&exp_hours.ok_or(CertEncodeError::MissingField("expiration"))?)?;
164
5150
        let cert_key = cert_key
165
5150
            .clone()
166
5150
            .ok_or(CertEncodeError::MissingField("cert_key"))?;
167
5150
        w.write_u8(cert_key.key_type().into());
168
5150
        w.write_all(cert_key.as_bytes());
169
5150
        let extensions = extensions.as_ref().map(Vec::as_slice).unwrap_or(&[]);
170
5150
        w.write_u8(
171
5150
            extensions
172
5150
                .len()
173
5150
                .try_into()
174
5150
                .map_err(|_| CertEncodeError::TooManyExtensions)?,
175
        );
176

            
177
5150
        for e in extensions.iter() {
178
5148
            e.write_onto(&mut w)?;
179
        }
180

            
181
5150
        let signature = skey.sign(&w[..]);
182
5150
        w.write(&signature)?;
183
5150
        Ok(EncodedEd25519Cert(w))
184
5150
    }
185
}
186

            
187
/// A certificate with an encoded representation.
188
pub 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

            
195
impl EncodedCert for EncodedEd25519Cert {
196
2
    fn cert_type(&self) -> CertType {
197
        // The cert type is the second byte of the certificate.
198
2
        self.0[1].into()
199
2
    }
200

            
201
    fn encoded(&self) -> &[u8] {
202
        &self.0
203
    }
204
}
205

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