From 150aea97963b6f58795cfe756d11950640417900 Mon Sep 17 00:00:00 2001 From: c0repwn3r Date: Mon, 27 Feb 2023 13:45:02 -0500 Subject: [PATCH] more tests --- trifid-pki/src/cert.rs | 22 +- trifid-pki/src/lib.rs | 1 + trifid-pki/src/test.rs | 446 ++++++++++++++++++++- trifid-pki/tarpaulin-report.html | 660 +++++++++++++++++++++++++++++++ 4 files changed, 1118 insertions(+), 11 deletions(-) create mode 100644 trifid-pki/tarpaulin-report.html diff --git a/trifid-pki/src/cert.rs b/trifid-pki/src/cert.rs index 6c7dbd8..fd1faef 100644 --- a/trifid-pki/src/cert.rs +++ b/trifid-pki/src/cert.rs @@ -11,7 +11,6 @@ use ipnet::{Ipv4Net}; use pem::Pem; use quick_protobuf::{BytesReader, MessageRead, MessageWrite, Writer}; use sha2::Sha256; -use x25519_dalek::{PublicKey, StaticSecret}; use crate::ca::NebulaCAPool; use crate::cert_codec::{RawNebulaCertificate, RawNebulaCertificateDetails}; use sha2::Digest; @@ -31,7 +30,7 @@ pub const ED25519_PRIVATE_KEY_BANNER: &str = "NEBULA ED25519 PRIVATE KEY"; pub const ED25519_PUBLIC_KEY_BANNER: &str = "NEBULA ED25519 PUBLIC KEY"; /// A Nebula PKI certificate -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct NebulaCertificate { /// The signed data of this certificate pub details: NebulaCertificateDetails, @@ -40,7 +39,7 @@ pub struct NebulaCertificate { } /// The signed details contained in a Nebula PKI certificate -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct NebulaCertificateDetails { /// The name of the identity this certificate was issued for pub name: String, @@ -345,6 +344,7 @@ impl NebulaCertificate { /// Make sure that this certificate does not break any of the constraints set by the signing certificate pub fn check_root_constraints(&self, signer: &Self) -> CertificateValidity { // Make sure this cert doesn't expire after the signer + println!("{:?} {:?}", signer.details.not_before, self.details.not_before); if signer.details.not_before < self.details.not_before { return CertificateValidity::CertExpiresAfterSigner; } @@ -356,6 +356,7 @@ impl NebulaCertificate { // If the signer contains a limited set of groups, make sure this cert only has a subset of them if !signer.details.groups.is_empty() { + println!("root groups: {:?}, child groups: {:?}", signer.details.groups, self.details.groups); for group in &self.details.groups { if !signer.details.groups.contains(group) { return CertificateValidity::GroupNotPresentOnSigner; @@ -385,7 +386,7 @@ impl NebulaCertificate { } #[allow(clippy::unwrap_used)] - /// Verify if the given private key corresponds to the public key used to sign this certificate + /// Verify if the given private key corresponds to the public key contained in this certificate /// # Errors /// This function will return an error if either keys are invalid. /// # Panics @@ -397,9 +398,10 @@ impl NebulaCertificate { return Err("key not 64-bytes long".into()) } - let actual_private_key: [u8; 32] = (&key[..32]).try_into().unwrap(); - if PublicKey::from(&StaticSecret::from(actual_private_key)) != PublicKey::from(self.details.public_key) { + let secret = SigningKey::from_keypair_bytes(key.try_into().unwrap())?; + let pub_key = secret.verifying_key().to_bytes(); + if pub_key != self.details.public_key { return Err(CertificateError::KeyMismatch.into()); } @@ -410,8 +412,11 @@ impl NebulaCertificate { return Err("key not 32-bytes long".into()) } - let pubkey = x25519_dalek::x25519(key.try_into().unwrap(), x25519_dalek::X25519_BASEPOINT_BYTES); - if pubkey != self.details.public_key { + let pubkey_raw = SigningKey::from_bytes(key.try_into()?).verifying_key(); + let pubkey = pubkey_raw.as_bytes(); + + println!("{} {}", hex::encode(pubkey), hex::encode(self.details.public_key)); + if *pubkey != self.details.public_key { return Err(CertificateError::KeyMismatch.into()); } @@ -493,6 +498,7 @@ impl NebulaCertificate { } /// A list of possible errors that can happen validating a certificate +#[derive(Eq, PartialEq, Debug)] pub enum CertificateValidity { /// There are no issues with this certificate Ok, diff --git a/trifid-pki/src/lib.rs b/trifid-pki/src/lib.rs index c388d11..600d63f 100644 --- a/trifid-pki/src/lib.rs +++ b/trifid-pki/src/lib.rs @@ -21,6 +21,7 @@ extern crate core; pub mod ca; pub mod cert; +#[cfg(not(tarpaulin_include))] pub(crate) mod cert_codec; #[cfg(test)] #[macro_use] diff --git a/trifid-pki/src/test.rs b/trifid-pki/src/test.rs index 17b1283..a614583 100644 --- a/trifid-pki/src/test.rs +++ b/trifid-pki/src/test.rs @@ -3,13 +3,16 @@ use crate::netmask; use std::net::Ipv4Addr; -use std::ops::Add; +use std::ops::{Add, Sub}; use std::time::{Duration, SystemTime, SystemTimeError, UNIX_EPOCH}; use ipnet::Ipv4Net; -use crate::cert::{deserialize_nebula_certificate, NebulaCertificate, NebulaCertificateDetails}; +use crate::cert::{CertificateValidity, deserialize_ed25519_private, deserialize_ed25519_public, deserialize_nebula_certificate, deserialize_nebula_certificate_from_pem, deserialize_x25519_private, deserialize_x25519_public, NebulaCertificate, NebulaCertificateDetails, serialize_ed25519_private, serialize_ed25519_public, serialize_x25519_private, serialize_x25519_public}; use std::str::FromStr; -use ed25519_dalek::{SigningKey}; +use ed25519_dalek::{SigningKey, VerifyingKey}; +use quick_protobuf::{MessageWrite, Writer}; use rand::rngs::OsRng; +use crate::ca::NebulaCAPool; +use crate::cert_codec::{RawNebulaCertificate, RawNebulaCertificateDetails}; /// This is a cert that we (e3team) actually use in production, and it's a known-good certificate. pub const KNOWN_GOOD_CERT: &[u8; 258] = b"-----BEGIN NEBULA CERTIFICATE-----\nCkkKF2UzdGVhbSBJbnRlcm5hbCBOZXR3b3JrKJWev5wGMJWFxKsGOiCvpwoHyKY5\n8Q5+2XxDjtoCf/zlNY/EUdB8bwXQSwEo50ABEkB0Dx76lkMqc3IyH5+ml2dKjTyv\nB4Jiw6x3abf5YZcf8rDuVEgQpvFdJmo3xJyIb3C9vKZ6kXsUxjw6s1JdWgkA\n-----END NEBULA CERTIFICATE-----"; @@ -70,6 +73,62 @@ fn certificate_serialization() { } } +#[test] +fn certificate_serialization_pem() { + let before = round_systime_to_secs(SystemTime::now() - Duration::from_secs(60)).unwrap(); + let after = round_systime_to_secs(SystemTime::now() + Duration::from_secs(60)).unwrap(); + let pub_key = b"1234567890abcedfghij1234567890ab"; + + let cert = NebulaCertificate { + details: NebulaCertificateDetails { + name: "testing".to_string(), + ips: vec![ + netmask!("10.1.1.1", "255.255.255.0"), + netmask!("10.1.1.2", "255.255.0.0"), + netmask!("10.1.1.3", "255.0.0.0") + ], + subnets: vec![ + netmask!("9.1.1.1", "255.255.255.128"), + netmask!("9.1.1.2", "255.255.255.0"), + netmask!("9.1.1.3", "255.255.0.0") + ], + groups: vec!["test-group1".to_string(), "test-group2".to_string(), "test-group3".to_string()], + not_before: before, + not_after: after, + public_key: *pub_key, + is_ca: false, + issuer: "1234567890abcedfabcd1234567890ab".to_string(), + }, + signature: b"1234567890abcedfghij1234567890ab".to_vec(), + }; + + let bytes = cert.serialize_to_pem().unwrap(); + + let deserialized = deserialize_nebula_certificate_from_pem(&bytes).unwrap(); + + assert_eq!(cert.signature, deserialized.signature); + assert_eq!(cert.details.name, deserialized.details.name); + assert_eq!(cert.details.not_before, deserialized.details.not_before); + assert_eq!(cert.details.not_after, deserialized.details.not_after); + assert_eq!(cert.details.public_key, deserialized.details.public_key); + assert_eq!(cert.details.is_ca, deserialized.details.is_ca); + + assert_eq!(cert.details.ips.len(), deserialized.details.ips.len()); + for item in &cert.details.ips { + assert!(deserialized.details.ips.contains(item), "deserialized does not contain from source"); + } + + assert_eq!(cert.details.subnets.len(), deserialized.details.subnets.len()); + for item in &cert.details.subnets { + assert!(deserialized.details.subnets.contains(item), "deserialized does not contain from source"); + } + + assert_eq!(cert.details.groups.len(), deserialized.details.groups.len()); + for item in &cert.details.groups { + assert!(deserialized.details.groups.contains(item), "deserialized does not contain from source"); + } +} + #[test] fn cert_signing() { let before = round_systime_to_secs(SystemTime::now() - Duration::from_secs(60)).unwrap(); @@ -107,6 +166,304 @@ fn cert_signing() { assert!(cert.check_signature(&key.verifying_key()).unwrap()); } +#[test] +fn cert_expiry() { + let cert = NebulaCertificate { + details: NebulaCertificateDetails { + name: String::new(), + ips: vec![], + subnets: vec![], + groups: vec![], + not_before: SystemTime::now().sub(Duration::from_secs(60)), + not_after: SystemTime::now().add(Duration::from_secs(60)), + public_key: [0u8; 32], + is_ca: false, + issuer: String::new(), + }, + signature: vec![], + }; + + assert!(cert.expired(SystemTime::now().add(Duration::from_secs(60 * 60 * 60)))); + assert!(cert.expired(SystemTime::now().sub(Duration::from_secs(60 * 60 * 60)))); + assert!(!cert.expired(SystemTime::now())); +} + +#[test] +fn cert_display() { + let cert = NebulaCertificate { + details: NebulaCertificateDetails { + name: String::new(), + ips: vec![], + subnets: vec![], + groups: vec![], + not_before: SystemTime::now().sub(Duration::from_secs(60)), + not_after: SystemTime::now().add(Duration::from_secs(60)), + public_key: [0u8; 32], + is_ca: false, + issuer: String::new(), + }, + signature: vec![], + }; + println!("{cert}"); +} + +#[test] +#[should_panic] +fn cert_deserialize_empty_bytes() { + deserialize_nebula_certificate(&[]).unwrap(); +} + +#[test] +fn cert_deserialize_unpaired_ips() { + let broken_cert = RawNebulaCertificate { + Details: Some(RawNebulaCertificateDetails { + Name: String::new(), + Ips: vec![0], + Subnets: vec![], + Groups: vec![], + NotBefore: 0, + NotAfter: 0, + PublicKey: vec![], + IsCA: false, + Issuer: vec![], + }), + Signature: vec![], + }; + let mut bytes = vec![]; + let mut writer = Writer::new(&mut bytes); + broken_cert.write_message(&mut writer).unwrap(); + + deserialize_nebula_certificate(&bytes).unwrap_err(); +} + +#[test] +fn cert_deserialize_unpaired_subnets() { + let broken_cert = RawNebulaCertificate { + Details: Some(RawNebulaCertificateDetails { + Name: String::new(), + Ips: vec![], + Subnets: vec![0], + Groups: vec![], + NotBefore: 0, + NotAfter: 0, + PublicKey: vec![], + IsCA: false, + Issuer: vec![], + }), + Signature: vec![], + }; + let mut bytes = vec![]; + let mut writer = Writer::new(&mut bytes); + broken_cert.write_message(&mut writer).unwrap(); + + deserialize_nebula_certificate(&bytes).unwrap_err(); +} + +#[test] +fn cert_deserialize_wrong_pubkey_len() { + let broken_cert = RawNebulaCertificate { + Details: Some(RawNebulaCertificateDetails { + Name: String::new(), + Ips: vec![], + Subnets: vec![], + Groups: vec![], + NotBefore: 0, + NotAfter: 0, + PublicKey: vec![0u8; 31], + IsCA: false, + Issuer: vec![], + }), + Signature: vec![], + }; + let mut bytes = vec![]; + let mut writer = Writer::new(&mut bytes); + broken_cert.write_message(&mut writer).unwrap(); + + deserialize_nebula_certificate(&bytes).unwrap_err(); + + assert!(deserialize_nebula_certificate_from_pem(&[0u8; 32]).is_err()); +} + +#[test] +fn x25519_serialization() { + let bytes = [0u8; 32]; + assert_eq!(deserialize_x25519_private(&serialize_x25519_private(&bytes)).unwrap(), bytes); + assert!(deserialize_x25519_private(&[0u8; 32]).is_err()); + assert_eq!(deserialize_x25519_public(&serialize_x25519_public(&bytes)).unwrap(), bytes); + assert!(deserialize_x25519_public(&[0u8; 32]).is_err()); +} + +#[test] +fn ed25519_serialization() { + let bytes = [0u8; 32]; + assert_eq!(deserialize_ed25519_private(&serialize_ed25519_private(&bytes)).unwrap(), bytes); + assert!(deserialize_ed25519_private(&[0u8; 32]).is_err()); + assert_eq!(deserialize_ed25519_public(&serialize_ed25519_public(&bytes)).unwrap(), bytes); + assert!(deserialize_ed25519_public(&[0u8; 32]).is_err()); +} + +#[test] +fn cert_verify() { + let (ca_cert, ca_key, _ca_pub) = test_ca_cert(round_systime_to_secs(SystemTime::now()).unwrap(), round_systime_to_secs(SystemTime::now() + Duration::from_secs(60 * 60 * 10)).unwrap(), vec![], vec![], vec!["groupa".to_string()]); + + let (cert, _, _) = test_cert(&ca_cert, &ca_key, SystemTime::now(), round_systime_to_secs(SystemTime::now() + Duration::from_secs(60 * 60 * 5)).unwrap(), vec![], vec![], vec![]); + + let mut ca_pool = NebulaCAPool::new(); + ca_pool.add_ca_certificate(&ca_cert.serialize_to_pem().unwrap()).unwrap(); + + let fingerprint = cert.sha256sum().unwrap(); + ca_pool.blocklist_fingerprint(&fingerprint); + + assert!(matches!(cert.verify(SystemTime::now(), &ca_pool).unwrap(), CertificateValidity::Blocklisted)); + + ca_pool.reset_blocklist(); + + assert!(matches!(cert.verify(SystemTime::now() + Duration::from_secs(60 * 60 * 60), &ca_pool).unwrap(), CertificateValidity::RootCertExpired)); + assert!(matches!(cert.verify(SystemTime::now() + Duration::from_secs(60 * 60 * 6), &ca_pool).unwrap(), CertificateValidity::CertExpired)); + + let (cert_with_bad_group, _, _) = test_cert(&ca_cert, &ca_key, round_systime_to_secs(SystemTime::now()).unwrap(), in_a_minute(), vec![], vec![], vec!["group-not-present on parent".to_string()]); + assert_eq!(cert_with_bad_group.verify(SystemTime::now(), &ca_pool).unwrap(), CertificateValidity::GroupNotPresentOnSigner); + + let (cert_with_good_group, _, _) = test_cert(&ca_cert, &ca_key, round_systime_to_secs(SystemTime::now()).unwrap(), in_a_minute(), vec![], vec![], vec!["groupa".to_string()]); + assert_eq!(cert_with_good_group.verify(SystemTime::now(), &ca_pool).unwrap(), CertificateValidity::Ok); + +} + +#[test] +fn cert_verify_ip() { + let ca_ip_1 = Ipv4Net::from_str("10.0.0.0/16").unwrap(); + let ca_ip_2 = Ipv4Net::from_str("192.168.0.0/24").unwrap(); + + let (ca, ca_key, _ca_pub) = test_ca_cert(round_systime_to_secs(SystemTime::now()).unwrap(), round_systime_to_secs(SystemTime::now() + Duration::from_secs(60 * 60 * 10)).unwrap(), vec![ca_ip_1, ca_ip_2], vec![], vec![]); + + let ca_pem = ca.serialize_to_pem().unwrap(); + + let mut ca_pool = NebulaCAPool::new(); + ca_pool.add_ca_certificate(&ca_pem).unwrap(); + + // ip is outside the network + let cip1 = netmask!("10.1.0.0", "255.255.255.0"); + let cip2 = netmask!("192.198.0.1", "255.255.0.0"); + let (cert, _, _) = test_cert(&ca, &ca_key, round_systime_to_secs(SystemTime::now()).unwrap(), round_systime_to_secs(SystemTime::now() + Duration::from_secs(60 * 60 * 5)).unwrap(), vec![cip1, cip2], vec![], vec![]); + + assert_eq!(cert.verify(SystemTime::now(), &ca_pool).unwrap(), CertificateValidity::IPNotPresentOnSigner); + + // ip is outside the network - reversed order from above + let cip1 = netmask!("192.198.0.1", "255.255.255.0"); + let cip2 = netmask!("10.1.0.0", "255.255.255.0"); + let (cert, _, _) = test_cert(&ca, &ca_key, round_systime_to_secs(SystemTime::now()).unwrap(), round_systime_to_secs(SystemTime::now() + Duration::from_secs(60 * 60 * 5)).unwrap(), vec![cip1, cip2], vec![], vec![]); + + assert_eq!(cert.verify(SystemTime::now(), &ca_pool).unwrap(), CertificateValidity::IPNotPresentOnSigner); + + // ip is within the network but mask is outside + let cip1 = netmask!("10.0.1.0", "255.254.0.0"); + let cip2 = netmask!("192.168.0.1", "255.255.255.0"); + let (cert, _, _) = test_cert(&ca, &ca_key, round_systime_to_secs(SystemTime::now()).unwrap(), round_systime_to_secs(SystemTime::now() + Duration::from_secs(60 * 60 * 5)).unwrap(), vec![cip1, cip2], vec![], vec![]); + + assert_eq!(cert.verify(SystemTime::now(), &ca_pool).unwrap(), CertificateValidity::IPNotPresentOnSigner); + + // ip is within the network but mask is outside - reversed order from above + let cip1 = netmask!("192.168.0.1", "255.255.255.0"); + let cip2 = netmask!("10.0.1.0", "255.254.0.0"); + let (cert, _, _) = test_cert(&ca, &ca_key, round_systime_to_secs(SystemTime::now()).unwrap(), round_systime_to_secs(SystemTime::now() + Duration::from_secs(60 * 60 * 5)).unwrap(), vec![cip1, cip2], vec![], vec![]); + + assert_eq!(cert.verify(SystemTime::now(), &ca_pool).unwrap(), CertificateValidity::IPNotPresentOnSigner); + + // ip and mask are within the network + let cip1 = netmask!("10.0.1.0", "255.255.0.0"); + let cip2 = netmask!("192.168.0.1", "255.255.255.128"); + let (cert, _, _) = test_cert(&ca, &ca_key, round_systime_to_secs(SystemTime::now()).unwrap(), round_systime_to_secs(SystemTime::now() + Duration::from_secs(60 * 60 * 5)).unwrap(), vec![cip1, cip2], vec![], vec![]); + + assert_eq!(cert.verify(SystemTime::now(), &ca_pool).unwrap(), CertificateValidity::Ok); + + // Exact matches + let (cert, _, _) = test_cert(&ca, &ca_key, round_systime_to_secs(SystemTime::now()).unwrap(), round_systime_to_secs(SystemTime::now() + Duration::from_secs(60 * 60 * 5)).unwrap(), vec![ca_ip_1, ca_ip_2], vec![], vec![]); + assert_eq!(cert.verify(SystemTime::now(), &ca_pool).unwrap(), CertificateValidity::Ok); + + // Exact matches reversed + let (cert, _, _) = test_cert(&ca, &ca_key, round_systime_to_secs(SystemTime::now()).unwrap(), round_systime_to_secs(SystemTime::now() + Duration::from_secs(60 * 60 * 5)).unwrap(), vec![ca_ip_2, ca_ip_1], vec![], vec![]); + assert_eq!(cert.verify(SystemTime::now(), &ca_pool).unwrap(), CertificateValidity::Ok); + + // Exact matches reversed with just one + let (cert, _, _) = test_cert(&ca, &ca_key, round_systime_to_secs(SystemTime::now()).unwrap(), round_systime_to_secs(SystemTime::now() + Duration::from_secs(60 * 60 * 5)).unwrap(), vec![ca_ip_2], vec![], vec![]); + assert_eq!(cert.verify(SystemTime::now(), &ca_pool).unwrap(), CertificateValidity::Ok); +} + +#[test] +fn cert_verify_subnet() { + let ca_ip_1 = Ipv4Net::from_str("10.0.0.0/16").unwrap(); + let ca_ip_2 = Ipv4Net::from_str("192.168.0.0/24").unwrap(); + + let (ca, ca_key, _ca_pub) = test_ca_cert(round_systime_to_secs(SystemTime::now()).unwrap(), round_systime_to_secs(SystemTime::now() + Duration::from_secs(60 * 60 * 10)).unwrap(), vec![],vec![ca_ip_1, ca_ip_2], vec![]); + + let ca_pem = ca.serialize_to_pem().unwrap(); + + let mut ca_pool = NebulaCAPool::new(); + ca_pool.add_ca_certificate(&ca_pem).unwrap(); + + // ip is outside the network + let cip1 = netmask!("10.1.0.0", "255.255.255.0"); + let cip2 = netmask!("192.198.0.1", "255.255.0.0"); + let (cert, _, _) = test_cert(&ca, &ca_key, round_systime_to_secs(SystemTime::now()).unwrap(), round_systime_to_secs(SystemTime::now() + Duration::from_secs(60 * 60 * 5)).unwrap(), vec![],vec![cip1, cip2], vec![]); + + assert_eq!(cert.verify(SystemTime::now(), &ca_pool).unwrap(), CertificateValidity::SubnetNotPresentOnSigner); + + // ip is outside the network - reversed order from above + let cip1 = netmask!("192.198.0.1", "255.255.255.0"); + let cip2 = netmask!("10.1.0.0", "255.255.255.0"); + let (cert, _, _) = test_cert(&ca, &ca_key, round_systime_to_secs(SystemTime::now()).unwrap(), round_systime_to_secs(SystemTime::now() + Duration::from_secs(60 * 60 * 5)).unwrap(), vec![],vec![cip1, cip2], vec![]); + + assert_eq!(cert.verify(SystemTime::now(), &ca_pool).unwrap(), CertificateValidity::SubnetNotPresentOnSigner); + + // ip is within the network but mask is outside + let cip1 = netmask!("10.0.1.0", "255.254.0.0"); + let cip2 = netmask!("192.168.0.1", "255.255.255.0"); + let (cert, _, _) = test_cert(&ca, &ca_key, round_systime_to_secs(SystemTime::now()).unwrap(), round_systime_to_secs(SystemTime::now() + Duration::from_secs(60 * 60 * 5)).unwrap(), vec![],vec![cip1, cip2], vec![]); + + assert_eq!(cert.verify(SystemTime::now(), &ca_pool).unwrap(), CertificateValidity::SubnetNotPresentOnSigner); + + // ip is within the network but mask is outside - reversed order from above + let cip1 = netmask!("192.168.0.1", "255.255.255.0"); + let cip2 = netmask!("10.0.1.0", "255.254.0.0"); + let (cert, _, _) = test_cert(&ca, &ca_key, round_systime_to_secs(SystemTime::now()).unwrap(), round_systime_to_secs(SystemTime::now() + Duration::from_secs(60 * 60 * 5)).unwrap(), vec![cip1, cip2], vec![], vec![]); + + assert_eq!(cert.verify(SystemTime::now(), &ca_pool).unwrap(), CertificateValidity::SubnetNotPresentOnSigner); + + // ip and mask are within the network + let cip1 = netmask!("10.0.1.0", "255.255.0.0"); + let cip2 = netmask!("192.168.0.1", "255.255.255.128"); + let (cert, _, _) = test_cert(&ca, &ca_key, round_systime_to_secs(SystemTime::now()).unwrap(), round_systime_to_secs(SystemTime::now() + Duration::from_secs(60 * 60 * 5)).unwrap(), vec![], vec![cip1, cip2], vec![]); + + assert_eq!(cert.verify(SystemTime::now(), &ca_pool).unwrap(), CertificateValidity::Ok); + + // Exact matches + let (cert, _, _) = test_cert(&ca, &ca_key, round_systime_to_secs(SystemTime::now()).unwrap(), round_systime_to_secs(SystemTime::now() + Duration::from_secs(60 * 60 * 5)).unwrap(), vec![],vec![ca_ip_1, ca_ip_2], vec![]); + assert_eq!(cert.verify(SystemTime::now(), &ca_pool).unwrap(), CertificateValidity::Ok); + + // Exact matches reversed + let (cert, _, _) = test_cert(&ca, &ca_key, round_systime_to_secs(SystemTime::now()).unwrap(), round_systime_to_secs(SystemTime::now() + Duration::from_secs(60 * 60 * 5)).unwrap(), vec![], vec![ca_ip_2, ca_ip_1], vec![]); + assert_eq!(cert.verify(SystemTime::now(), &ca_pool).unwrap(), CertificateValidity::Ok); + + // Exact matches reversed with just one + let (cert, _, _) = test_cert(&ca, &ca_key, round_systime_to_secs(SystemTime::now()).unwrap(), round_systime_to_secs(SystemTime::now() + Duration::from_secs(60 * 60 * 5)).unwrap(), vec![], vec![ca_ip_2], vec![]); + assert_eq!(cert.verify(SystemTime::now(), &ca_pool).unwrap(), CertificateValidity::Ok); +} + +#[test] +fn cert_private_key() { + let (ca, ca_key, _) = test_ca_cert(SystemTime::now(), SystemTime::now(), vec![], vec![], vec![]); + ca.verify_private_key(&ca_key.to_keypair_bytes()).unwrap(); + + let (_, ca_key2, _) = test_ca_cert(SystemTime::now(), SystemTime::now(), vec![], vec![], vec![]); + ca.verify_private_key(&ca_key2.to_keypair_bytes()).unwrap_err(); + + let (cert, priv_key, _) = test_cert(&ca, &ca_key, SystemTime::now(), SystemTime::now(), vec![], vec![], vec![]); + cert.verify_private_key(&priv_key.to_bytes()).unwrap(); + + let (cert2, _, _) = test_cert(&ca, &ca_key, SystemTime::now(), SystemTime::now(), vec![], vec![], vec![]); + cert2.verify_private_key(&priv_key.to_bytes()).unwrap_err(); +} + #[macro_export] macro_rules! netmask { ($ip:expr,$mask:expr) => { @@ -117,4 +474,87 @@ macro_rules! netmask { fn round_systime_to_secs(time: SystemTime) -> Result { let secs = time.duration_since(UNIX_EPOCH)?.as_secs(); Ok(SystemTime::UNIX_EPOCH.add(Duration::from_secs(secs))) +} + +fn test_ca_cert(before: SystemTime, after: SystemTime, ips: Vec, subnets: Vec, groups: Vec) -> (NebulaCertificate, SigningKey, VerifyingKey) { + let mut csprng = OsRng; + let key = SigningKey::generate(&mut csprng); + let pub_key = key.verifying_key(); + + let mut cert = NebulaCertificate { + details: NebulaCertificateDetails { + name: "TEST_CA".to_string(), + ips, + subnets, + groups, + not_before: before, + not_after: after, + public_key: pub_key.to_bytes(), + is_ca: true, + issuer: "".to_string(), + }, + signature: vec![], + }; + cert.sign(&key).unwrap(); + (cert, key, pub_key) +} + +fn test_cert(ca: &NebulaCertificate, key: &SigningKey, before: SystemTime, after: SystemTime, ips: Vec, subnets: Vec, groups: Vec) -> (NebulaCertificate, SigningKey, VerifyingKey) { + let issuer = ca.sha256sum().unwrap(); + + let real_groups = if groups.is_empty() { + vec!["test-group1".to_string(), "test-group2".to_string(), "test-group3".to_string()] + } else { + groups + }; + + let real_ips = if ips.is_empty() { + vec![ + netmask!("10.1.1.1", "255.255.255.0"), + netmask!("10.1.1.2", "255.255.0.0"), + netmask!("10.1.1.3", "255.0.0.0") + ] + } else { + ips + }; + + let real_subnets = if subnets.is_empty() { + vec![ + netmask!("9.1.1.1", "255.255.255.128"), + netmask!("9.1.1.2", "255.255.255.0"), + netmask!("9.1.1.3", "255.255.0.0") + ] + } else { + subnets + }; + + let mut csprng = OsRng; + let cert_key = SigningKey::generate(&mut csprng); + let pub_key = cert_key.verifying_key(); + + let mut cert = NebulaCertificate { + details: NebulaCertificateDetails { + name: "TEST_CA".to_string(), + ips: real_ips, + subnets: real_subnets, + groups: real_groups, + not_before: before, + not_after: after, + public_key: pub_key.to_bytes(), + is_ca: false, + issuer, + }, + signature: vec![], + }; + cert.sign(key).unwrap(); + (cert, cert_key, pub_key) +} + +#[allow(dead_code)] +fn in_a_minute() -> SystemTime { + round_systime_to_secs(SystemTime::now().add(Duration::from_secs(60))).unwrap() +} +#[allow(dead_code)] +fn a_minute_ago() -> SystemTime { + round_systime_to_secs(SystemTime::now().sub(Duration::from_secs(60))).unwrap() } \ No newline at end of file diff --git a/trifid-pki/tarpaulin-report.html b/trifid-pki/tarpaulin-report.html new file mode 100644 index 0000000..72179ec --- /dev/null +++ b/trifid-pki/tarpaulin-report.html @@ -0,0 +1,660 @@ + + + + + + + +
+ + + + + + \ No newline at end of file