more tests
This commit is contained in:
parent
33ba674073
commit
150aea9796
|
@ -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,
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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<SystemTime, SystemTimeError> {
|
||||
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<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
Loading…
Reference in New Issue