more tests
This commit is contained in:
parent
33ba674073
commit
150aea9796
|
@ -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,
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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) => {
|
||||||
|
@ -118,3 +475,86 @@ 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
Loading…
Reference in New Issue