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, Ed25519Cert, Ed25519CertConstructor, ExtType, SignedWithEd25519Ext,
7
    UnrecognizedExt,
8
};
9
use std::time::{Duration, 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
#[cfg_attr(docsrs, doc(cfg(feature = "encode")))]
24
pub struct EncodedEd25519Cert(Vec<u8>);
25

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

            
34
impl EncodedEd25519Cert {
35
    /// Create an `EncodedEd25519Cert` from a byte slice.
36
    ///
37
    /// **Important**: generally you should not use this function.
38
    /// Instead, prefer using [`Ed25519CertConstructor::encode_and_sign`]
39
    /// to encode certificates.
40
    ///
41
    /// This function should only be used if `cert`
42
    /// is known to be the byte representation of a valid `EncodedEd25519Cert`
43
    /// (for example, after parsing the byte slice using [`Ed25519Cert::decode`],
44
    /// and validating its signature and timeliness).
45
    #[cfg(feature = "experimental-api")]
46
    pub fn dangerously_from_bytes(cert: &[u8]) -> Self {
47
        Self(cert.into())
48
    }
49
}
50

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

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

            
78
impl Writeable for UnrecognizedExt {
79
    /// As Writeable::WriteOnto, but may return an error.
80
2
    fn write_onto<B: Writer + ?Sized>(&self, w: &mut B) -> EncodeResult<()> {
81
2
        // We can't use Writer::write_nested_u16len here, since the length field
82
2
        // doesn't include the type or the flags.
83
2
        w.write_u16(
84
2
            self.body
85
2
                .len()
86
2
                .try_into()
87
2
                .map_err(|_| tor_bytes::EncodeError::BadLengthValue)?,
88
        );
89
2
        w.write_u8(self.ext_type.into());
90
2
        let flags = u8::from(self.affects_validation);
91
2
        w.write_u8(flags);
92
2
        w.write_all(&self.body[..]);
93
2
        Ok(())
94
2
    }
95
}
96

            
97
impl Ed25519CertConstructor {
98
    /// Set the approximate expiration time for this certificate.
99
    ///
100
    /// (The time will be rounded forward to the nearest hour after the epoch.)
101
32800
    pub fn expiration(&mut self, expiration: SystemTime) -> &mut Self {
102
        /// The number of seconds in an hour.
103
        const SEC_PER_HOUR: u64 = 3600;
104
32800
        let duration = expiration
105
32800
            .duration_since(SystemTime::UNIX_EPOCH)
106
32800
            .unwrap_or(Duration::from_secs(0));
107
32800
        let exp_hours = duration.as_secs().saturating_add(SEC_PER_HOUR - 1) / SEC_PER_HOUR;
108
32800
        self.exp_hours = Some(exp_hours.try_into().unwrap_or(u32::MAX));
109
32800
        self
110
32800
    }
111

            
112
    /// Set the signing key to be included with this certificate.
113
    ///
114
    /// This is optional: you don't need to include the signing key at all.  If
115
    /// you do, it must match the key that you actually use to sign the
116
    /// certificate.
117
32798
    pub fn signing_key(&mut self, key: ed25519::Ed25519Identity) -> &mut Self {
118
32798
        self.clear_signing_key();
119
32798
        self.signed_with = Some(Some(key));
120
32798
        self.extensions
121
32798
            .get_or_insert_with(Vec::new)
122
32798
            .push(CertExt::SignedWithEd25519(SignedWithEd25519Ext { pk: key }));
123
32798

            
124
32798
        self
125
32798
    }
126

            
127
    /// Remove any signing key previously set on this Ed25519CertConstructor.
128
32798
    pub fn clear_signing_key(&mut self) -> &mut Self {
129
32798
        self.signed_with = None;
130
32798
        self.extensions
131
32798
            .get_or_insert_with(Vec::new)
132
32798
            .retain(|ext| !matches!(ext, CertExt::SignedWithEd25519(_)));
133
32798
        self
134
32798
    }
135

            
136
    /// Encode a certificate into a new vector, signing the result
137
    /// with `skey`.
138
    ///
139
    /// This function exists in lieu of a `build()` function, since we have a rule that
140
    /// we don't produce an `Ed25519Cert` except if the certificate is known to be
141
    /// valid.
142
4300
    pub fn encode_and_sign<S>(&self, skey: &S) -> Result<EncodedEd25519Cert, CertEncodeError>
143
4300
    where
144
4300
        S: Ed25519PublicKey + Ed25519SigningKey,
145
4300
    {
146
4300
        let Ed25519CertConstructor {
147
4300
            exp_hours,
148
4300
            cert_type,
149
4300
            cert_key,
150
4300
            extensions,
151
4300
            signed_with,
152
4300
        } = self;
153

            
154
        // As long as there is no setter for Ed25519Cert::signed_with, this is unreachable
155
4298
        if let Some(Some(signer)) = &signed_with {
156
4298
            if *signer != skey.public_key().into() {
157
                return Err(CertEncodeError::KeyMismatch);
158
4298
            }
159
2
        }
160

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

            
182
4300
        for e in extensions.iter() {
183
4298
            e.write_onto(&mut w)?;
184
        }
185

            
186
4300
        let signature = skey.sign(&w[..]);
187
4300
        w.write(&signature)?;
188
4300
        Ok(EncodedEd25519Cert(w))
189
4300
    }
190
}
191

            
192
#[cfg(test)]
193
#[allow(clippy::unwrap_used)]
194
mod test {
195
    // @@ begin test lint list maintained by maint/add_warning @@
196
    #![allow(clippy::bool_assert_comparison)]
197
    #![allow(clippy::clone_on_copy)]
198
    #![allow(clippy::dbg_macro)]
199
    #![allow(clippy::mixed_attributes_style)]
200
    #![allow(clippy::print_stderr)]
201
    #![allow(clippy::print_stdout)]
202
    #![allow(clippy::single_char_pattern)]
203
    #![allow(clippy::unwrap_used)]
204
    #![allow(clippy::unchecked_duration_subtraction)]
205
    #![allow(clippy::useless_vec)]
206
    #![allow(clippy::needless_pass_by_value)]
207
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
208
    use super::*;
209
    use crate::CertifiedKey;
210
    use tor_checkable::{SelfSigned, Timebound};
211

            
212
    #[test]
213
    fn signed_cert_without_key() {
214
        let mut rng = rand::rng();
215
        let keypair = ed25519::Keypair::generate(&mut rng);
216
        let now = SystemTime::now();
217
        let day = Duration::from_secs(86400);
218
        let encoded = Ed25519Cert::constructor()
219
            .expiration(now + day * 30)
220
            .cert_key(CertifiedKey::Ed25519(keypair.verifying_key().into()))
221
            .cert_type(7.into())
222
            .encode_and_sign(&keypair)
223
            .unwrap();
224

            
225
        let decoded = Ed25519Cert::decode(&encoded).unwrap(); // Well-formed?
226
        let validated = decoded
227
            .should_be_signed_with(&keypair.verifying_key().into())
228
            .unwrap()
229
            .check_signature()
230
            .unwrap(); // Well-signed?
231
        let cert = validated.check_valid_at(&(now + day * 20)).unwrap();
232
        assert_eq!(cert.cert_type(), 7.into());
233
        if let CertifiedKey::Ed25519(found) = cert.subject_key() {
234
            assert_eq!(found, &keypair.verifying_key().into());
235
        } else {
236
            panic!("wrong key type");
237
        }
238
        assert!(cert.signing_key() == Some(&keypair.verifying_key().into()));
239
    }
240

            
241
    #[test]
242
    fn unrecognized_ext() {
243
        use hex_literal::hex;
244
        use tor_bytes::Reader;
245

            
246
        let mut reader = Reader::from_slice(&hex!("0001 2A 00 2A"));
247
        let ext: CertExt = reader.extract().unwrap();
248

            
249
        let mut encoded: Vec<u8> = Vec::new();
250
        encoded.write(&ext).unwrap();
251

            
252
        assert_eq!(encoded, hex!("0001 2A 00 2A"));
253
    }
254
}