1use 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#[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 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 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 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 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 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); 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
187pub trait EncodedCert {
189 fn cert_type(&self) -> CertType;
191 fn encoded(&self) -> &[u8];
193}
194
195impl EncodedCert for EncodedEd25519Cert {
196 fn cert_type(&self) -> CertType {
197 self.0[1].into()
199 }
200
201 fn encoded(&self) -> &[u8] {
202 &self.0
203 }
204}
205
206#[cfg(test)]
207mod test {
208 #![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 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(); let validated = decoded
243 .should_be_signed_with(&keypair.verifying_key().into())
244 .unwrap()
245 .check_signature()
246 .unwrap(); 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}