more tests

This commit is contained in:
c0repwn3r 2023-02-27 13:45:02 -05:00
parent 33ba674073
commit 150aea9796
Signed by: core
GPG Key ID: FDBF740DADDCEECF
4 changed files with 1118 additions and 11 deletions

View File

@ -11,7 +11,6 @@ use ipnet::{Ipv4Net};
use pem::Pem; use pem::Pem;
use quick_protobuf::{BytesReader, MessageRead, MessageWrite, Writer}; use quick_protobuf::{BytesReader, MessageRead, MessageWrite, Writer};
use sha2::Sha256; use sha2::Sha256;
use x25519_dalek::{PublicKey, StaticSecret};
use crate::ca::NebulaCAPool; use crate::ca::NebulaCAPool;
use crate::cert_codec::{RawNebulaCertificate, RawNebulaCertificateDetails}; use crate::cert_codec::{RawNebulaCertificate, RawNebulaCertificateDetails};
use sha2::Digest; 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"; pub const ED25519_PUBLIC_KEY_BANNER: &str = "NEBULA ED25519 PUBLIC KEY";
/// A Nebula PKI certificate /// A Nebula PKI certificate
#[derive(Debug)] #[derive(Debug, Clone)]
pub struct NebulaCertificate { pub struct NebulaCertificate {
/// The signed data of this certificate /// The signed data of this certificate
pub details: NebulaCertificateDetails, pub details: NebulaCertificateDetails,
@ -40,7 +39,7 @@ pub struct NebulaCertificate {
} }
/// The signed details contained in a Nebula PKI certificate /// The signed details contained in a Nebula PKI certificate
#[derive(Debug)] #[derive(Debug, Clone)]
pub struct NebulaCertificateDetails { pub struct NebulaCertificateDetails {
/// The name of the identity this certificate was issued for /// The name of the identity this certificate was issued for
pub name: String, 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 /// 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 { pub fn check_root_constraints(&self, signer: &Self) -> CertificateValidity {
// Make sure this cert doesn't expire after the signer // 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 { if signer.details.not_before < self.details.not_before {
return CertificateValidity::CertExpiresAfterSigner; 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 the signer contains a limited set of groups, make sure this cert only has a subset of them
if !signer.details.groups.is_empty() { if !signer.details.groups.is_empty() {
println!("root groups: {:?}, child groups: {:?}", signer.details.groups, self.details.groups);
for group in &self.details.groups { for group in &self.details.groups {
if !signer.details.groups.contains(group) { if !signer.details.groups.contains(group) {
return CertificateValidity::GroupNotPresentOnSigner; return CertificateValidity::GroupNotPresentOnSigner;
@ -385,7 +386,7 @@ impl NebulaCertificate {
} }
#[allow(clippy::unwrap_used)] #[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 /// # Errors
/// This function will return an error if either keys are invalid. /// This function will return an error if either keys are invalid.
/// # Panics /// # Panics
@ -397,9 +398,10 @@ impl NebulaCertificate {
return Err("key not 64-bytes long".into()) 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()); return Err(CertificateError::KeyMismatch.into());
} }
@ -410,8 +412,11 @@ impl NebulaCertificate {
return Err("key not 32-bytes long".into()) return Err("key not 32-bytes long".into())
} }
let pubkey = x25519_dalek::x25519(key.try_into().unwrap(), x25519_dalek::X25519_BASEPOINT_BYTES); let pubkey_raw = SigningKey::from_bytes(key.try_into()?).verifying_key();
if pubkey != self.details.public_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()); return Err(CertificateError::KeyMismatch.into());
} }
@ -493,6 +498,7 @@ impl NebulaCertificate {
} }
/// A list of possible errors that can happen validating a certificate /// A list of possible errors that can happen validating a certificate
#[derive(Eq, PartialEq, Debug)]
pub enum CertificateValidity { pub enum CertificateValidity {
/// There are no issues with this certificate /// There are no issues with this certificate
Ok, Ok,

View File

@ -21,6 +21,7 @@ extern crate core;
pub mod ca; pub mod ca;
pub mod cert; pub mod cert;
#[cfg(not(tarpaulin_include))]
pub(crate) mod cert_codec; pub(crate) mod cert_codec;
#[cfg(test)] #[cfg(test)]
#[macro_use] #[macro_use]

View File

@ -3,13 +3,16 @@
use crate::netmask; use crate::netmask;
use std::net::Ipv4Addr; use std::net::Ipv4Addr;
use std::ops::Add; use std::ops::{Add, Sub};
use std::time::{Duration, SystemTime, SystemTimeError, UNIX_EPOCH}; use std::time::{Duration, SystemTime, SystemTimeError, UNIX_EPOCH};
use ipnet::Ipv4Net; 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 std::str::FromStr;
use ed25519_dalek::{SigningKey}; use ed25519_dalek::{SigningKey, VerifyingKey};
use quick_protobuf::{MessageWrite, Writer};
use rand::rngs::OsRng; 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. /// 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-----"; 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] #[test]
fn cert_signing() { fn cert_signing() {
let before = round_systime_to_secs(SystemTime::now() - Duration::from_secs(60)).unwrap(); 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()); 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_export]
macro_rules! netmask { macro_rules! netmask {
($ip:expr,$mask:expr) => { ($ip:expr,$mask:expr) => {
@ -117,4 +474,87 @@ macro_rules! netmask {
fn round_systime_to_secs(time: SystemTime) -> Result<SystemTime, SystemTimeError> { fn round_systime_to_secs(time: SystemTime) -> Result<SystemTime, SystemTimeError> {
let secs = time.duration_since(UNIX_EPOCH)?.as_secs(); let secs = time.duration_since(UNIX_EPOCH)?.as_secs();
Ok(SystemTime::UNIX_EPOCH.add(Duration::from_secs(secs))) Ok(SystemTime::UNIX_EPOCH.add(Duration::from_secs(secs)))
}
fn test_ca_cert(before: SystemTime, after: SystemTime, ips: Vec<Ipv4Net>, subnets: Vec<Ipv4Net>, groups: Vec<String>) -> (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<Ipv4Net>, subnets: Vec<Ipv4Net>, groups: Vec<String>) -> (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()
} }

File diff suppressed because one or more lines are too long