1//! Code for constructing and signing certificates.
2//!
3//! Only available when the crate is built with the `encode` feature.
45use crate::{
6 CertEncodeError, CertExt, Ed25519Cert, Ed25519CertConstructor, ExtType, SignedWithEd25519Ext,
7 UnrecognizedExt,
8};
9use std::time::{Duration, SystemTime};
10use tor_bytes::{EncodeResult, Writeable, Writer};
11use tor_llcrypto::pk::ed25519::{self, Ed25519PublicKey, Ed25519SigningKey};
1213use derive_more::{AsRef, Deref, Into};
1415/// 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")))]
24pub struct EncodedEd25519Cert(Vec<u8>);
2526impl Ed25519Cert {
27/// Return a new `Ed25519CertConstructor` to create and return a new signed
28 /// `Ed25519Cert`.
29pub fn constructor() -> Ed25519CertConstructor {
30 Default::default()
31 }
32}
3334impl 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")]
46pub fn dangerously_from_bytes(cert: &[u8]) -> Self {
47Self(cert.into())
48 }
49}
5051impl Writeable for CertExt {
52/// As Writeable::WriteOnto, but may return an error.
53 ///
54 /// TODO: Migrate Writeable to provide this interface.
55fn write_onto<B: Writer + ?Sized>(&self, w: &mut B) -> EncodeResult<()> {
56match self {
57 CertExt::SignedWithEd25519(pk) => pk.write_onto(w),
58 CertExt::Unrecognized(u) => u.write_onto(w),
59 }
60 }
61}
6263impl Writeable for SignedWithEd25519Ext {
64/// As Writeable::WriteOnto, but may return an error.
65fn write_onto<B: Writer + ?Sized>(&self, w: &mut B) -> EncodeResult<()> {
66// body length
67w.write_u16(32);
68// Signed-with-ed25519-key-extension
69w.write_u8(ExtType::SIGNED_WITH_ED25519_KEY.into());
70// flags = 0.
71w.write_u8(0);
72// body
73w.write_all(self.pk.as_bytes());
74Ok(())
75 }
76}
7778impl Writeable for UnrecognizedExt {
79/// As Writeable::WriteOnto, but may return an error.
80fn write_onto<B: Writer + ?Sized>(&self, w: &mut B) -> EncodeResult<()> {
81// We can't use Writer::write_nested_u16len here, since the length field
82 // doesn't include the type or the flags.
83w.write_u16(
84self.body
85 .len()
86 .try_into()
87 .map_err(|_| tor_bytes::EncodeError::BadLengthValue)?,
88 );
89 w.write_u8(self.ext_type.into());
90let flags = u8::from(self.affects_validation);
91 w.write_u8(flags);
92 w.write_all(&self.body[..]);
93Ok(())
94 }
95}
9697impl 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.)
101pub fn expiration(&mut self, expiration: SystemTime) -> &mut Self {
102/// The number of seconds in an hour.
103const SEC_PER_HOUR: u64 = 3600;
104let duration = expiration
105 .duration_since(SystemTime::UNIX_EPOCH)
106 .unwrap_or(Duration::from_secs(0));
107let exp_hours = duration.as_secs().saturating_add(SEC_PER_HOUR - 1) / SEC_PER_HOUR;
108self.exp_hours = Some(exp_hours.try_into().unwrap_or(u32::MAX));
109self
110}
111112/// 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.
117pub fn signing_key(&mut self, key: ed25519::Ed25519Identity) -> &mut Self {
118self.clear_signing_key();
119self.signed_with = Some(Some(key));
120self.extensions
121 .get_or_insert_with(Vec::new)
122 .push(CertExt::SignedWithEd25519(SignedWithEd25519Ext { pk: key }));
123124self
125}
126127/// Remove any signing key previously set on this Ed25519CertConstructor.
128pub fn clear_signing_key(&mut self) -> &mut Self {
129self.signed_with = None;
130self.extensions
131 .get_or_insert_with(Vec::new)
132 .retain(|ext| !matches!(ext, CertExt::SignedWithEd25519(_)));
133self
134}
135136/// 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.
142pub fn encode_and_sign<S>(&self, skey: &S) -> Result<EncodedEd25519Cert, CertEncodeError>
143where
144S: Ed25519PublicKey + Ed25519SigningKey,
145 {
146let Ed25519CertConstructor {
147 exp_hours,
148 cert_type,
149 cert_key,
150 extensions,
151 signed_with,
152 } = self;
153154// As long as there is no setter for Ed25519Cert::signed_with, this is unreachable
155if let Some(Some(signer)) = &signed_with {
156if *signer != skey.public_key().into() {
157return Err(CertEncodeError::KeyMismatch);
158 }
159 }
160161let mut w = Vec::new();
162 w.write_u8(1); // Version
163w.write_u8(
164 cert_type
165 .ok_or(CertEncodeError::MissingField("cert_type"))?
166.into(),
167 );
168 w.write_u32(exp_hours.ok_or(CertEncodeError::MissingField("expiration"))?);
169let cert_key = cert_key
170 .clone()
171 .ok_or(CertEncodeError::MissingField("cert_key"))?;
172 w.write_u8(cert_key.key_type().into());
173 w.write_all(cert_key.as_bytes());
174let extensions = extensions.as_ref().map(Vec::as_slice).unwrap_or(&[]);
175 w.write_u8(
176 extensions
177 .len()
178 .try_into()
179 .map_err(|_| CertEncodeError::TooManyExtensions)?,
180 );
181182for e in extensions.iter() {
183 e.write_onto(&mut w)?;
184 }
185186let signature = skey.sign(&w[..]);
187 w.write(&signature)?;
188Ok(EncodedEd25519Cert(w))
189 }
190}
191192#[cfg(test)]
193#[allow(clippy::unwrap_used)]
194mod 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 @@ -->
208use super::*;
209use crate::CertifiedKey;
210use tor_checkable::{SelfSigned, Timebound};
211212#[test]
213fn signed_cert_without_key() {
214let mut rng = rand::rng();
215let keypair = ed25519::Keypair::generate(&mut rng);
216let now = SystemTime::now();
217let day = Duration::from_secs(86400);
218let 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();
224225let decoded = Ed25519Cert::decode(&encoded).unwrap(); // Well-formed?
226let validated = decoded
227 .should_be_signed_with(&keypair.verifying_key().into())
228 .unwrap()
229 .check_signature()
230 .unwrap(); // Well-signed?
231let cert = validated.check_valid_at(&(now + day * 20)).unwrap();
232assert_eq!(cert.cert_type(), 7.into());
233if let CertifiedKey::Ed25519(found) = cert.subject_key() {
234assert_eq!(found, &keypair.verifying_key().into());
235 } else {
236panic!("wrong key type");
237 }
238assert!(cert.signing_key() == Some(&keypair.verifying_key().into()));
239 }
240241#[test]
242fn unrecognized_ext() {
243use hex_literal::hex;
244use tor_bytes::Reader;
245246let mut reader = Reader::from_slice(&hex!("0001 2A 00 2A"));
247let ext: CertExt = reader.extract().unwrap();
248249let mut encoded: Vec<u8> = Vec::new();
250 encoded.write(&ext).unwrap();
251252assert_eq!(encoded, hex!("0001 2A 00 2A"));
253 }
254}