1use 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};
12
13use derive_more::{AsRef, Deref, Into};
14
15#[derive(Clone, Debug, PartialEq, Into, AsRef, Deref)]
23pub struct EncodedEd25519Cert(Vec<u8>);
24
25impl Ed25519Cert {
26 pub fn constructor() -> Ed25519CertConstructor {
29 Default::default()
30 }
31}
32
33impl EncodedEd25519Cert {
34 #[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 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 fn write_onto<B: Writer + ?Sized>(&self, w: &mut B) -> EncodeResult<()> {
65 w.write_u16(32);
67 w.write_u8(ExtType::SIGNED_WITH_ED25519_KEY.into());
69 w.write_u8(0);
71 w.write_all(self.pk.as_bytes());
73 Ok(())
74 }
75}
76
77impl Writeable for UnrecognizedExt {
78 fn write_onto<B: Writer + ?Sized>(&self, w: &mut B) -> EncodeResult<()> {
80 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 pub fn expiration(&mut self, expiration: SystemTime) -> &mut Self {
101 const SEC_PER_HOUR: u64 = 3600;
103 let duration = expiration
104 .duration_since(SystemTime::UNIX_EPOCH)
105 .unwrap_or(Duration::from_secs(0));
106 let exp_hours = duration.as_secs().saturating_add(SEC_PER_HOUR - 1) / SEC_PER_HOUR;
107 self.exp_hours = Some(exp_hours.try_into().unwrap_or(u32::MAX));
108 self
109 }
110
111 pub fn signing_key(&mut self, key: ed25519::Ed25519Identity) -> &mut Self {
117 self.clear_signing_key();
118 self.signed_with = Some(Some(key));
119 self.extensions
120 .get_or_insert_with(Vec::new)
121 .push(CertExt::SignedWithEd25519(SignedWithEd25519Ext { pk: key }));
122
123 self
124 }
125
126 pub fn clear_signing_key(&mut self) -> &mut Self {
128 self.signed_with = None;
129 self.extensions
130 .get_or_insert_with(Vec::new)
131 .retain(|ext| !matches!(ext, CertExt::SignedWithEd25519(_)));
132 self
133 }
134
135 pub fn encode_and_sign<S>(&self, skey: &S) -> Result<EncodedEd25519Cert, CertEncodeError>
142 where
143 S: Ed25519PublicKey + Ed25519SigningKey,
144 {
145 let Ed25519CertConstructor {
146 exp_hours,
147 cert_type,
148 cert_key,
149 extensions,
150 signed_with,
151 } = self;
152
153 if let Some(Some(signer)) = &signed_with {
155 if *signer != skey.public_key().into() {
156 return Err(CertEncodeError::KeyMismatch);
157 }
158 }
159
160 let mut w = Vec::new();
161 w.write_u8(1); w.write_u8(
163 cert_type
164 .ok_or(CertEncodeError::MissingField("cert_type"))?
165 .into(),
166 );
167 w.write_u32(exp_hours.ok_or(CertEncodeError::MissingField("expiration"))?);
168 let cert_key = cert_key
169 .clone()
170 .ok_or(CertEncodeError::MissingField("cert_key"))?;
171 w.write_u8(cert_key.key_type().into());
172 w.write_all(cert_key.as_bytes());
173 let extensions = extensions.as_ref().map(Vec::as_slice).unwrap_or(&[]);
174 w.write_u8(
175 extensions
176 .len()
177 .try_into()
178 .map_err(|_| CertEncodeError::TooManyExtensions)?,
179 );
180
181 for e in extensions.iter() {
182 e.write_onto(&mut w)?;
183 }
184
185 let signature = skey.sign(&w[..]);
186 w.write(&signature)?;
187 Ok(EncodedEd25519Cert(w))
188 }
189}
190
191#[cfg(test)]
192mod test {
193 #![allow(clippy::bool_assert_comparison)]
195 #![allow(clippy::clone_on_copy)]
196 #![allow(clippy::dbg_macro)]
197 #![allow(clippy::mixed_attributes_style)]
198 #![allow(clippy::print_stderr)]
199 #![allow(clippy::print_stdout)]
200 #![allow(clippy::single_char_pattern)]
201 #![allow(clippy::unwrap_used)]
202 #![allow(clippy::unchecked_time_subtraction)]
203 #![allow(clippy::useless_vec)]
204 #![allow(clippy::needless_pass_by_value)]
205 use super::*;
207 use crate::CertifiedKey;
208 use tor_checkable::{SelfSigned, Timebound};
209
210 #[test]
211 fn signed_cert_without_key() {
212 let mut rng = rand::rng();
213 let keypair = ed25519::Keypair::generate(&mut rng);
214 let now = SystemTime::now();
215 let day = Duration::from_secs(86400);
216 let encoded = Ed25519Cert::constructor()
217 .expiration(now + day * 30)
218 .cert_key(CertifiedKey::Ed25519(keypair.verifying_key().into()))
219 .cert_type(7.into())
220 .encode_and_sign(&keypair)
221 .unwrap();
222
223 let decoded = Ed25519Cert::decode(&encoded).unwrap(); let validated = decoded
225 .should_be_signed_with(&keypair.verifying_key().into())
226 .unwrap()
227 .check_signature()
228 .unwrap(); let cert = validated.check_valid_at(&(now + day * 20)).unwrap();
230 assert_eq!(cert.cert_type(), 7.into());
231 if let CertifiedKey::Ed25519(found) = cert.subject_key() {
232 assert_eq!(found, &keypair.verifying_key().into());
233 } else {
234 panic!("wrong key type");
235 }
236 assert!(cert.signing_key() == Some(&keypair.verifying_key().into()));
237 }
238
239 #[test]
240 fn unrecognized_ext() {
241 use hex_literal::hex;
242 use tor_bytes::Reader;
243
244 let mut reader = Reader::from_slice(&hex!("0001 2A 00 2A"));
245 let ext: CertExt = reader.extract().unwrap();
246
247 let mut encoded: Vec<u8> = Vec::new();
248 encoded.write(&ext).unwrap();
249
250 assert_eq!(encoded, hex!("0001 2A 00 2A"));
251 }
252}