1use crate::ca::NebulaCAPool;
5use crate::cert_codec::{RawNebulaCertificate, RawNebulaCertificateDetails};
6use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
7use ipnet::Ipv4Net;
8use pem::Pem;
9use quick_protobuf::{BytesReader, MessageRead, MessageWrite, Writer};
10use sha2::Digest;
11use sha2::Sha256;
12use std::error::Error;
13use std::fmt::{Display, Formatter};
14use std::net::Ipv4Addr;
15use std::ops::Add;
16use std::time::{Duration, SystemTime, UNIX_EPOCH};
17
18#[cfg(feature = "serde_derive")]
19use serde::{Deserialize, Serialize};
20
21pub const PUBLIC_KEY_LENGTH: i32 = 32;
23
24pub const CERT_BANNER: &str = "NEBULA CERTIFICATE";
26pub const X25519_PRIVATE_KEY_BANNER: &str = "NEBULA X25519 PRIVATE KEY";
28pub const X25519_PUBLIC_KEY_BANNER: &str = "NEBULA X25519 PUBLIC KEY";
30pub const ED25519_PRIVATE_KEY_BANNER: &str = "NEBULA ED25519 PRIVATE KEY";
32pub const ED25519_PUBLIC_KEY_BANNER: &str = "NEBULA ED25519 PUBLIC KEY";
34
35#[derive(Debug, Clone)]
37#[cfg_attr(feature = "serde_derive", derive(Serialize, Deserialize))]
38pub struct NebulaCertificate {
39 pub details: NebulaCertificateDetails,
41 pub signature: Vec<u8>,
43}
44
45#[derive(Debug, Clone)]
47#[cfg_attr(feature = "serde_derive", derive(Serialize, Deserialize))]
48pub struct NebulaCertificateDetails {
49 pub name: String,
51 pub ips: Vec<Ipv4Net>,
53 pub subnets: Vec<Ipv4Net>,
55 pub groups: Vec<String>,
57 pub not_before: SystemTime,
59 pub not_after: SystemTime,
61 pub public_key: [u8; PUBLIC_KEY_LENGTH as usize],
63 pub is_ca: bool,
65 pub issuer: String,
67}
68
69#[derive(Debug)]
71#[cfg_attr(feature = "serde_derive", derive(Serialize, Deserialize))]
72pub enum CertificateError {
73 EmptyByteArray,
75 NilDetails,
77 IpsNotPairs,
79 SubnetsNotPairs,
81 WrongSigLength,
83 WrongKeyLength,
85 WrongPemTag,
87 Expired,
89 KeyMismatch,
91}
92#[cfg(not(tarpaulin_include))]
93impl Display for CertificateError {
94 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
95 match self {
96 Self::EmptyByteArray => write!(f, "Certificate bytearray is empty"),
97 Self::NilDetails => write!(f, "The encoded Details field is null"),
98 Self::IpsNotPairs => {
99 write!(f, "encoded IPs should be in pairs, an odd number was found")
100 }
101 Self::SubnetsNotPairs => write!(
102 f,
103 "encoded subnets should be in pairs, an odd number was found"
104 ),
105 Self::WrongSigLength => {
106 write!(f, "Signature should be 64 bytes but is a different size")
107 }
108 Self::WrongKeyLength => write!(
109 f,
110 "Public keys are expected to be 32 bytes but the public key on this cert is not"
111 ),
112 Self::WrongPemTag => write!(
113 f,
114 "Certificates should have the PEM tag `NEBULA CERTIFICATE`, but this block did not"
115 ),
116 Self::Expired => write!(
117 f,
118 "This certificate either is not yet valid or has already expired"
119 ),
120 Self::KeyMismatch => write!(f, "Key does not match expected value"),
121 }
122 }
123}
124impl Error for CertificateError {}
125
126fn map_cidr_pairs(pairs: &[u32]) -> Result<Vec<Ipv4Net>, Box<dyn Error>> {
127 let mut res_vec = vec![];
128 for pair in pairs.chunks(2) {
129 res_vec.push(Ipv4Net::with_netmask(
130 Ipv4Addr::from(pair[0]),
131 Ipv4Addr::from(pair[1]),
132 )?);
133 }
134 Ok(res_vec)
135}
136
137#[cfg(not(tarpaulin_include))]
138impl Display for NebulaCertificate {
139 #[allow(clippy::unwrap_used)]
140 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
141 writeln!(f, "NebulaCertificate {{")?;
142 writeln!(f, " Details {{")?;
143 writeln!(f, " Name: {}", self.details.name)?;
144 writeln!(f, " Ips: {:?}", self.details.ips)?;
145 writeln!(f, " Subnets: {:?}", self.details.subnets)?;
146 writeln!(f, " Groups: {:?}", self.details.groups)?;
147 writeln!(f, " Not before: {:?}", self.details.not_before)?;
148 writeln!(f, " Not after: {:?}", self.details.not_after)?;
149 writeln!(f, " Is CA: {}", self.details.is_ca)?;
150 writeln!(f, " Issuer: {}", self.details.issuer)?;
151 writeln!(
152 f,
153 " Public key: {}",
154 hex::encode(self.details.public_key)
155 )?;
156 writeln!(f, " }}")?;
157 writeln!(f, " Fingerprint: {}", self.sha256sum().unwrap())?;
158 writeln!(f, " Signature: {}", hex::encode(self.signature.clone()))?;
159 writeln!(f, "}}")
160 }
161}
162
163pub fn deserialize_nebula_certificate(bytes: &[u8]) -> Result<NebulaCertificate, Box<dyn Error>> {
168 if bytes.is_empty() {
169 return Err(CertificateError::EmptyByteArray.into());
170 }
171
172 let mut reader = BytesReader::from_bytes(bytes);
173
174 let raw_cert = RawNebulaCertificate::from_reader(&mut reader, bytes)?;
175
176 let details = raw_cert.Details.ok_or(CertificateError::NilDetails)?;
177
178 if details.Ips.len() % 2 != 0 {
179 return Err(CertificateError::IpsNotPairs.into());
180 }
181
182 if details.Subnets.len() % 2 != 0 {
183 return Err(CertificateError::SubnetsNotPairs.into());
184 }
185
186 let mut nebula_cert;
187 #[allow(clippy::cast_sign_loss)]
188 {
189 nebula_cert = NebulaCertificate {
190 details: NebulaCertificateDetails {
191 name: details.Name.to_string(),
192 ips: map_cidr_pairs(&details.Ips)?,
193 subnets: map_cidr_pairs(&details.Subnets)?,
194 groups: details
195 .Groups
196 .iter()
197 .map(std::string::ToString::to_string)
198 .collect(),
199 not_before: SystemTime::UNIX_EPOCH
200 .add(Duration::from_secs(details.NotBefore as u64)),
201 not_after: SystemTime::UNIX_EPOCH.add(Duration::from_secs(details.NotAfter as u64)),
202 public_key: [0u8; 32],
203 is_ca: details.IsCA,
204 issuer: hex::encode(details.Issuer),
205 },
206 signature: vec![],
207 };
208 }
209
210 nebula_cert.signature = raw_cert.Signature;
211
212 if details.PublicKey.len() != 32 {
213 return Err(CertificateError::WrongKeyLength.into());
214 }
215
216 #[allow(clippy::unwrap_used)]
217 {
218 nebula_cert.details.public_key = details.PublicKey.try_into().unwrap();
219 }
220
221 Ok(nebula_cert)
222}
223
224#[derive(Debug)]
226#[cfg_attr(feature = "serde_derive", derive(Serialize, Deserialize))]
227pub enum KeyError {
228 WrongPemTag,
230 Not64Bytes,
232 Not32Bytes,
234}
235#[cfg(not(tarpaulin_include))]
236impl Display for KeyError {
237 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
238 match self {
239 Self::WrongPemTag => write!(
240 f,
241 "Keys should have their associated PEM tags but this had the wrong one"
242 ),
243 Self::Not64Bytes => write!(f, "Ed25519 private keys are 64 bytes"),
244 Self::Not32Bytes => write!(f, "X25519 private keys are 32 bytes"),
245 }
246 }
247}
248impl Error for KeyError {}
249
250pub fn deserialize_nebula_certificate_from_pem(
254 bytes: &[u8],
255) -> Result<NebulaCertificate, Box<dyn Error>> {
256 let pem = pem::parse(bytes)?;
257 if pem.tag != CERT_BANNER {
258 return Err(CertificateError::WrongPemTag.into());
259 }
260 deserialize_nebula_certificate(&pem.contents)
261}
262
263pub fn serialize_x25519_private(bytes: &[u8]) -> Vec<u8> {
265 pem::encode(&Pem {
266 tag: X25519_PRIVATE_KEY_BANNER.to_string(),
267 contents: bytes.to_vec(),
268 })
269 .as_bytes()
270 .to_vec()
271}
272
273pub fn serialize_x25519_public(bytes: &[u8]) -> Vec<u8> {
275 pem::encode(&Pem {
276 tag: X25519_PUBLIC_KEY_BANNER.to_string(),
277 contents: bytes.to_vec(),
278 })
279 .as_bytes()
280 .to_vec()
281}
282
283pub fn deserialize_x25519_private(bytes: &[u8]) -> Result<Vec<u8>, Box<dyn Error>> {
287 let pem = pem::parse(bytes)?;
288 if pem.tag != X25519_PRIVATE_KEY_BANNER {
289 return Err(KeyError::WrongPemTag.into());
290 }
291 if pem.contents.len() != 32 {
292 return Err(KeyError::Not32Bytes.into());
293 }
294 Ok(pem.contents)
295}
296
297pub fn deserialize_x25519_public(bytes: &[u8]) -> Result<Vec<u8>, Box<dyn Error>> {
301 let pem = pem::parse(bytes)?;
302 if pem.tag != X25519_PUBLIC_KEY_BANNER {
303 return Err(KeyError::WrongPemTag.into());
304 }
305 if pem.contents.len() != 32 {
306 return Err(KeyError::Not32Bytes.into());
307 }
308 Ok(pem.contents)
309}
310
311pub fn serialize_ed25519_private(bytes: &[u8]) -> Vec<u8> {
313 pem::encode(&Pem {
314 tag: ED25519_PRIVATE_KEY_BANNER.to_string(),
315 contents: bytes.to_vec(),
316 })
317 .as_bytes()
318 .to_vec()
319}
320
321pub fn serialize_ed25519_public(bytes: &[u8]) -> Vec<u8> {
323 pem::encode(&Pem {
324 tag: ED25519_PUBLIC_KEY_BANNER.to_string(),
325 contents: bytes.to_vec(),
326 })
327 .as_bytes()
328 .to_vec()
329}
330
331pub fn deserialize_ed25519_private(bytes: &[u8]) -> Result<Vec<u8>, Box<dyn Error>> {
335 let pem = pem::parse(bytes)?;
336 if pem.tag != ED25519_PRIVATE_KEY_BANNER {
337 return Err(KeyError::WrongPemTag.into());
338 }
339 if pem.contents.len() != 64 {
340 return Err(KeyError::Not64Bytes.into());
341 }
342 Ok(pem.contents)
343}
344
345pub fn deserialize_ed25519_public(bytes: &[u8]) -> Result<Vec<u8>, Box<dyn Error>> {
349 let pem = pem::parse(bytes)?;
350 if pem.tag != ED25519_PUBLIC_KEY_BANNER {
351 return Err(KeyError::WrongPemTag.into());
352 }
353 if pem.contents.len() != 32 {
354 return Err(KeyError::Not32Bytes.into());
355 }
356 Ok(pem.contents)
357}
358
359pub fn deserialize_ed25519_public_many(bytes: &[u8]) -> Result<Vec<Vec<u8>>, Box<dyn Error>> {
363 let mut keys = vec![];
364 let pems = pem::parse_many(bytes)?;
365
366 for pem in pems {
367 if pem.tag != ED25519_PUBLIC_KEY_BANNER {
368 return Err(KeyError::WrongPemTag.into());
369 }
370 if pem.contents.len() != 32 {
371 return Err(KeyError::Not32Bytes.into());
372 }
373 keys.push(pem.contents);
374 }
375
376 Ok(keys)
377}
378
379impl NebulaCertificate {
380 pub fn sign(&mut self, key: &SigningKey) -> Result<(), Box<dyn Error>> {
384 let mut out = Vec::new();
385 let mut writer = Writer::new(&mut out);
386 self.get_raw_details().write_message(&mut writer)?;
387
388 self.signature = key.sign(&out).to_vec();
389 Ok(())
390 }
391
392 pub fn check_signature(&self, key: &VerifyingKey) -> Result<bool, Box<dyn Error>> {
396 let mut out = Vec::new();
397 let mut writer = Writer::new(&mut out);
398 self.get_raw_details().write_message(&mut writer)?;
399
400 let sig = Signature::from_slice(&self.signature)?;
401
402 Ok(key.verify(&out, &sig).is_ok())
403 }
404
405 pub fn expired(&self, time: SystemTime) -> bool {
407 self.details.not_before > time || self.details.not_after < time
408 }
409
410 pub fn verify(
414 &self,
415 time: SystemTime,
416 ca_pool: &NebulaCAPool,
417 ) -> Result<CertificateValidity, Box<dyn Error>> {
418 if ca_pool.is_blocklisted(self) {
419 return Ok(CertificateValidity::Blocklisted);
420 }
421
422 let Some(signer) = ca_pool.get_ca_for_cert(self)? else { return Ok(CertificateValidity::NotSignedByThisCAPool) };
423
424 if signer.expired(time) {
425 return Ok(CertificateValidity::RootCertExpired);
426 }
427
428 if self.expired(time) {
429 return Ok(CertificateValidity::CertExpired);
430 }
431
432 if !self.check_signature(&VerifyingKey::from_bytes(&signer.details.public_key)?)? {
433 return Ok(CertificateValidity::BadSignature);
434 }
435
436 Ok(self.check_root_constraints(signer))
437 }
438
439 pub fn check_root_constraints(&self, signer: &Self) -> CertificateValidity {
441 println!(
443 "{:?} {:?}",
444 signer.details.not_before, self.details.not_before
445 );
446 if signer.details.not_before < self.details.not_before {
447 return CertificateValidity::CertExpiresAfterSigner;
448 }
449
450 if signer.details.not_before > self.details.not_before {
452 return CertificateValidity::CertValidBeforeSigner;
453 }
454
455 if !signer.details.groups.is_empty() {
457 println!(
458 "root groups: {:?}, child groups: {:?}",
459 signer.details.groups, self.details.groups
460 );
461 for group in &self.details.groups {
462 if !signer.details.groups.contains(group) {
463 return CertificateValidity::GroupNotPresentOnSigner;
464 }
465 }
466 }
467
468 if !signer.details.ips.is_empty() {
470 for ip in &self.details.ips {
471 if !net_match(*ip, &signer.details.ips) {
472 return CertificateValidity::IPNotPresentOnSigner;
473 }
474 }
475 }
476
477 if !signer.details.subnets.is_empty() {
479 for subnet in &self.details.subnets {
480 if !net_match(*subnet, &signer.details.subnets) {
481 return CertificateValidity::SubnetNotPresentOnSigner;
482 }
483 }
484 }
485
486 CertificateValidity::Ok
487 }
488
489 #[allow(clippy::unwrap_used)]
490 pub fn verify_private_key(&self, key: &[u8]) -> Result<(), Box<dyn Error>> {
496 if self.details.is_ca {
497 if key.len() != 64 {
499 return Err("key not 64-bytes long".into());
500 }
501
502 let secret = SigningKey::from_keypair_bytes(key.try_into().unwrap())?;
503 let pub_key = secret.verifying_key().to_bytes();
504 if pub_key != self.details.public_key {
505 return Err(CertificateError::KeyMismatch.into());
506 }
507
508 return Ok(());
509 }
510
511 if key.len() != 32 {
512 return Err("key not 32-bytes long".into());
513 }
514
515 let pubkey_raw = SigningKey::from_bytes(key.try_into()?).verifying_key();
516 let pubkey = pubkey_raw.as_bytes();
517
518 println!(
519 "{} {}",
520 hex::encode(pubkey),
521 hex::encode(self.details.public_key)
522 );
523 if *pubkey != self.details.public_key {
524 return Err(CertificateError::KeyMismatch.into());
525 }
526
527 Ok(())
528 }
529
530 #[allow(clippy::expect_used)]
532 #[allow(clippy::cast_possible_wrap)]
533 pub fn get_raw_details(&self) -> RawNebulaCertificateDetails {
536 let mut raw = RawNebulaCertificateDetails {
537 Name: self.details.name.clone(),
538 Ips: vec![],
539 Subnets: vec![],
540 Groups: self
541 .details
542 .groups
543 .iter()
544 .map(std::convert::Into::into)
545 .collect(),
546 NotBefore: self
547 .details
548 .not_before
549 .duration_since(UNIX_EPOCH)
550 .expect("Time went backwards")
551 .as_secs() as i64,
552 NotAfter: self
553 .details
554 .not_after
555 .duration_since(UNIX_EPOCH)
556 .expect("Time went backwards")
557 .as_secs() as i64,
558 PublicKey: self.details.public_key.into(),
559 IsCA: self.details.is_ca,
560 Issuer: hex::decode(&self.details.issuer).expect("Issuer was not a hex-encoded value"),
561 };
562
563 for ip_net in &self.details.ips {
564 raw.Ips.push(ip_net.addr().into());
565 raw.Ips.push(ip_net.netmask().into());
566 }
567
568 for subnet in &self.details.subnets {
569 raw.Subnets.push(subnet.addr().into());
570 raw.Subnets.push(subnet.netmask().into());
571 }
572
573 raw
574 }
575
576 pub fn serialize(&self) -> Result<Vec<u8>, Box<dyn Error>> {
580 let raw_cert = RawNebulaCertificate {
581 Details: Some(self.get_raw_details()),
582 Signature: self.signature.clone(),
583 };
584
585 let mut out = vec![];
586 let mut writer = Writer::new(&mut out);
587 raw_cert.write_message(&mut writer)?;
588
589 Ok(out)
590 }
591
592 pub fn serialize_to_pem(&self) -> Result<Vec<u8>, Box<dyn Error>> {
596 let pbuf_bytes = self.serialize()?;
597
598 Ok(pem::encode(&Pem {
599 tag: CERT_BANNER.to_string(),
600 contents: pbuf_bytes,
601 })
602 .as_bytes()
603 .to_vec())
604 }
605
606 pub fn sha256sum(&self) -> Result<String, Box<dyn Error>> {
610 let pbuf_bytes = self.serialize()?;
611
612 let mut hasher = Sha256::new();
613 hasher.update(pbuf_bytes);
614
615 Ok(hex::encode(hasher.finalize()))
616 }
617}
618
619#[derive(Eq, PartialEq, Debug)]
621#[cfg_attr(feature = "serde_derive", derive(Serialize, Deserialize))]
622pub enum CertificateValidity {
623 Ok,
625 Blocklisted,
627 RootCertExpired,
629 CertExpired,
631 BadSignature,
633 NotSignedByThisCAPool,
635 CertExpiresAfterSigner,
637 CertValidBeforeSigner,
639 GroupNotPresentOnSigner,
641 IPNotPresentOnSigner,
643 SubnetNotPresentOnSigner,
645}
646
647fn net_match(cert_ip: Ipv4Net, root_ips: &Vec<Ipv4Net>) -> bool {
648 for net in root_ips {
649 if net.contains(&cert_ip) {
650 return true;
651 }
652 }
653 false
654}