trifid/trifid-pki/src/test.rs

766 lines
34 KiB
Rust
Raw Normal View History

2023-02-27 02:58:45 +00:00
#![allow(clippy::unwrap_used)]
#![allow(clippy::expect_used)]
use crate::netmask;
use std::net::Ipv4Addr;
2023-02-27 18:45:02 +00:00
use std::ops::{Add, Sub};
2023-02-27 15:04:10 +00:00
use std::time::{Duration, SystemTime, SystemTimeError, UNIX_EPOCH};
2023-02-27 02:58:45 +00:00
use ipnet::Ipv4Net;
2023-02-27 18:45:02 +00:00
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};
2023-02-27 02:58:45 +00:00
use std::str::FromStr;
2023-02-27 18:45:02 +00:00
use ed25519_dalek::{SigningKey, VerifyingKey};
use quick_protobuf::{MessageWrite, Writer};
2023-02-27 15:55:53 +00:00
use rand::rngs::OsRng;
2023-02-27 19:42:49 +00:00
use crate::ca::{NebulaCAPool};
2023-02-27 18:45:02 +00:00
use crate::cert_codec::{RawNebulaCertificate, RawNebulaCertificateDetails};
2023-02-27 02:58:45 +00:00
2023-02-27 15:04:10 +00:00
/// 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-----";
2023-02-27 02:58:45 +00:00
#[test]
fn certificate_serialization() {
2023-02-27 15:04:10 +00:00
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();
2023-02-27 02:58:45 +00:00
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"),
2023-02-27 15:04:10 +00:00
netmask!("10.1.1.3", "255.0.0.0")
2023-02-27 02:58:45 +00:00
],
subnets: vec![
2023-02-27 15:04:10 +00:00
netmask!("9.1.1.1", "255.255.255.128"),
2023-02-27 02:58:45 +00:00
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,
2023-02-27 15:04:10 +00:00
issuer: "1234567890abcedfabcd1234567890ab".to_string(),
2023-02-27 02:58:45 +00:00
},
2023-02-27 02:59:46 +00:00
signature: b"1234567890abcedfghij1234567890ab".to_vec(),
2023-02-27 02:58:45 +00:00
};
let bytes = cert.serialize().unwrap();
let deserialized = deserialize_nebula_certificate(&bytes).unwrap();
2023-02-27 15:24:30 +00:00
2023-02-27 02:58:45 +00:00
assert_eq!(cert.signature, deserialized.signature);
assert_eq!(cert.details.name, deserialized.details.name);
2023-02-27 15:04:10 +00:00
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);
2023-02-27 15:24:30 +00:00
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");
}
2023-02-27 02:58:45 +00:00
}
2023-02-27 18:45:02 +00:00
#[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");
}
}
2023-02-27 15:55:53 +00:00
#[test]
fn cert_signing() {
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 mut 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 mut csprng = OsRng;
let key = SigningKey::generate(&mut csprng);
assert!(cert.check_signature(&key.verifying_key()).is_err());
cert.sign(&key).unwrap();
assert!(cert.check_signature(&key.verifying_key()).unwrap());
}
2023-02-27 18:45:02 +00:00
#[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() {
2023-02-27 23:12:24 +00:00
let bytes = [0u8; 64];
2023-02-27 18:45:02 +00:00
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();
}
2023-02-27 19:42:49 +00:00
#[test]
fn capool_from_pem() {
let no_newlines = b"# Current provisional, Remove once everything moves over to the real root.
-----BEGIN NEBULA CERTIFICATE-----
CkAKDm5lYnVsYSByb290IGNhKJfap9AFMJfg1+YGOiCUQGByMuNRhIlQBOyzXWbL
vcKBwDhov900phEfJ5DN3kABEkDCq5R8qBiu8sl54yVfgRcQXEDt3cHr8UTSLszv
bzBEr00kERQxxTzTsH8cpYEgRoipvmExvg8WP8NdAJEYJosB
-----END NEBULA CERTIFICATE-----
# root-ca01
-----BEGIN NEBULA CERTIFICATE-----
CkMKEW5lYnVsYSByb290IGNhIDAxKJL2u9EFMJL86+cGOiDPXMH4oU6HZTk/CqTG
BVG+oJpAoqokUBbI4U0N8CSfpUABEkB/Pm5A2xyH/nc8mg/wvGUWG3pZ7nHzaDMf
8/phAUt+FLzqTECzQKisYswKvE3pl9mbEYKbOdIHrxdIp95mo4sF
-----END NEBULA CERTIFICATE-----";
let with_newlines = b"# Current provisional, Remove once everything moves over to the real root.
-----BEGIN NEBULA CERTIFICATE-----
CkAKDm5lYnVsYSByb290IGNhKJfap9AFMJfg1+YGOiCUQGByMuNRhIlQBOyzXWbL
vcKBwDhov900phEfJ5DN3kABEkDCq5R8qBiu8sl54yVfgRcQXEDt3cHr8UTSLszv
bzBEr00kERQxxTzTsH8cpYEgRoipvmExvg8WP8NdAJEYJosB
-----END NEBULA CERTIFICATE-----
# root-ca01
-----BEGIN NEBULA CERTIFICATE-----
CkMKEW5lYnVsYSByb290IGNhIDAxKJL2u9EFMJL86+cGOiDPXMH4oU6HZTk/CqTG
BVG+oJpAoqokUBbI4U0N8CSfpUABEkB/Pm5A2xyH/nc8mg/wvGUWG3pZ7nHzaDMf
8/phAUt+FLzqTECzQKisYswKvE3pl9mbEYKbOdIHrxdIp95mo4sF
-----END NEBULA CERTIFICATE-----
";
let expired = b"# expired certificate
-----BEGIN NEBULA CERTIFICATE-----
CjkKB2V4cGlyZWQouPmWjQYwufmWjQY6ILCRaoCkJlqHgv5jfDN4lzLHBvDzaQm4
vZxfu144hmgjQAESQG4qlnZi8DncvD/LDZnLgJHOaX1DWCHHEh59epVsC+BNgTie
WH1M9n4O7cFtGlM6sJJOS+rCVVEJ3ABS7+MPdQs=
-----END NEBULA CERTIFICATE-----";
let pool_a = NebulaCAPool::new_from_pem(no_newlines).unwrap();
assert_eq!(pool_a.cas["c9bfaf7ce8e84b2eeda2e27b469f4b9617bde192efd214b68891ecda6ed49522"].details.name, "nebula root ca".to_string());
assert_eq!(pool_a.cas["5c9c3f23e7ee7fe97637cbd3a0a5b854154d1d9aaaf7b566a51f4a88f76b64cd"].details.name, "nebula root ca 01".to_string());
assert!(!pool_a.expired);
let pool_b = NebulaCAPool::new_from_pem(with_newlines).unwrap();
assert_eq!(pool_b.cas["c9bfaf7ce8e84b2eeda2e27b469f4b9617bde192efd214b68891ecda6ed49522"].details.name, "nebula root ca".to_string());
assert_eq!(pool_b.cas["5c9c3f23e7ee7fe97637cbd3a0a5b854154d1d9aaaf7b566a51f4a88f76b64cd"].details.name, "nebula root ca 01".to_string());
assert!(!pool_b.expired);
let pool_c = NebulaCAPool::new_from_pem(expired).unwrap();
assert!(pool_c.expired);
assert_eq!(pool_c.cas["152070be6bb19bc9e3bde4c2f0e7d8f4ff5448b4c9856b8eccb314fade0229b0"].details.name, "expired");
let mut pool_d = NebulaCAPool::new_from_pem(with_newlines).unwrap();
pool_d.add_ca_certificate(expired).unwrap();
assert_eq!(pool_d.cas["c9bfaf7ce8e84b2eeda2e27b469f4b9617bde192efd214b68891ecda6ed49522"].details.name, "nebula root ca".to_string());
assert_eq!(pool_d.cas["5c9c3f23e7ee7fe97637cbd3a0a5b854154d1d9aaaf7b566a51f4a88f76b64cd"].details.name, "nebula root ca 01".to_string());
assert_eq!(pool_d.cas["152070be6bb19bc9e3bde4c2f0e7d8f4ff5448b4c9856b8eccb314fade0229b0"].details.name, "expired");
assert!(pool_d.expired);
assert_eq!(pool_d.get_fingerprints().len(), 3);
}
2023-02-27 02:58:45 +00:00
#[macro_export]
macro_rules! netmask {
($ip:expr,$mask:expr) => {
Ipv4Net::with_netmask(Ipv4Addr::from_str($ip).unwrap(), Ipv4Addr::from_str($mask).unwrap()).unwrap()
};
2023-02-27 15:04:10 +00:00
}
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)))
2023-02-27 18:45:02 +00:00
}
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,
2023-02-27 23:12:24 +00:00
issuer: String::new(),
2023-02-27 18:45:02 +00:00
},
signature: vec![],
};
cert.sign(&key).unwrap();
(cert, key, pub_key)
}
2023-02-27 23:12:24 +00:00
#[test]
fn test_deserialize_ed25519_private() {
let priv_key = b"-----BEGIN NEBULA ED25519 PRIVATE KEY-----
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
-----END NEBULA ED25519 PRIVATE KEY-----";
let short_key = b"-----BEGIN NEBULA ED25519 PRIVATE KEY-----
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-----END NEBULA ED25519 PRIVATE KEY-----";
let invalid_banner = b"-----BEGIN NOT A NEBULA PRIVATE KEY-----
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
-----END NOT A NEBULA PRIVATE KEY-----";
let invalid_pem = b"-BEGIN NEBULA ED25519 PRIVATE KEY-----
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
-END NEBULA ED25519 PRIVATE KEY-----";
deserialize_ed25519_private(priv_key).unwrap();
deserialize_ed25519_private(short_key).unwrap_err();
deserialize_ed25519_private(invalid_banner).unwrap_err();
deserialize_ed25519_private(invalid_pem).unwrap_err();
}
#[test]
fn test_deserialize_x25519_private() {
let priv_key = b"-----BEGIN NEBULA X25519 PRIVATE KEY-----
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
-----END NEBULA X25519 PRIVATE KEY-----";
let short_key = b"-----BEGIN NEBULA X25519 PRIVATE KEY-----
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
-----END NEBULA X25519 PRIVATE KEY-----";
let invalid_banner = b"-----BEGIN NOT A NEBULA PRIVATE KEY-----
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
-----END NOT A NEBULA PRIVATE KEY-----";
let invalid_pem = b"-BEGIN NEBULA X25519 PRIVATE KEY-----
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
-END NEBULA X25519 PRIVATE KEY-----";
deserialize_x25519_private(priv_key).unwrap();
deserialize_x25519_private(short_key).unwrap_err();
deserialize_x25519_private(invalid_banner).unwrap_err();
deserialize_x25519_private(invalid_pem).unwrap_err();
}
#[test]
fn test_pem_deserialization() {
let good_cert = b"# A good cert
-----BEGIN NEBULA CERTIFICATE-----
CkAKDm5lYnVsYSByb290IGNhKJfap9AFMJfg1+YGOiCUQGByMuNRhIlQBOyzXWbL
vcKBwDhov900phEfJ5DN3kABEkDCq5R8qBiu8sl54yVfgRcQXEDt3cHr8UTSLszv
bzBEr00kERQxxTzTsH8cpYEgRoipvmExvg8WP8NdAJEYJosB
-----END NEBULA CERTIFICATE-----";
let bad_banner = b"-----BEGIN NOT A NEBULA CERTIFICATE-----
CkAKDm5lYnVsYSByb290IGNhKJfap9AFMJfg1+YGOiCUQGByMuNRhIlQBOyzXWbL
vcKBwDhov900phEfJ5DN3kABEkDCq5R8qBiu8sl54yVfgRcQXEDt3cHr8UTSLszv
bzBEr00kERQxxTzTsH8cpYEgRoipvmExvg8WP8NdAJEYJosB
-----END NOT A NEBULA CERTIFICATE-----";
let invalid_pem = b"# Not a valid PEM format
-BEGIN NEBULA CERTIFICATE-----
CkAKDm5lYnVsYSByb290IGNhKJfap9AFMJfg1+YGOiCUQGByMuNRhIlQBOyzXWbL
vcKBwDhov900phEfJ5DN3kABEkDCq5R8qBiu8sl54yVfgRcQXEDt3cHr8UTSLszv
bzBEr00kERQxxTzTsH8cpYEgRoipvmExvg8WP8NdAJEYJosB
-END NEBULA CERTIFICATE----";
// success
deserialize_nebula_certificate_from_pem(good_cert).unwrap();
// fail because invalid banner
deserialize_nebula_certificate_from_pem(bad_banner).unwrap_err();
// fail because nonsense pem
deserialize_nebula_certificate_from_pem(invalid_pem).unwrap_err();
}
#[test]
fn test_deserialize_ed25519_public() {
let priv_key = b"-----BEGIN NEBULA ED25519 PUBLIC KEY-----
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
-----END NEBULA ED25519 PUBLIC KEY-----";
let short_key = b"-----BEGIN NEBULA ED25519 PUBLIC KEY-----
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-----END NEBULA ED25519 PUBLIC KEY-----";
let invalid_banner = b"-----BEGIN NOT A NEBULA PUBLIC KEY-----
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
-----END NOT A NEBULA PUBLIC KEY-----";
let invalid_pem = b"-BEGIN NEBULA ED25519 PUBLIC KEY-----
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
-END NEBULA ED25519 PUBLIC KEY-----";
deserialize_ed25519_public(priv_key).unwrap();
deserialize_ed25519_public(short_key).unwrap_err();
deserialize_ed25519_public(invalid_banner).unwrap_err();
deserialize_ed25519_public(invalid_pem).unwrap_err();
}
#[test]
// Ensure that trifid-pki produces the *exact same* bit-for-bit representation as nebula does
// to ensure signature interoperability
// We use an e3team production certificate for this as it is a known-good certificate.
fn test_serialization_interoperability() {
let known_good_cert = hex::decode("0a650a08636f72652d74777212098184c4508080f8ff0f28ae9fbf9c06309485c4ab063a20304e6279d8722f0fd8d966faa70adaeec08c4649d486457bb038be243753a7474a2056860ded3d14b7f3b77ca2a062ee5d683dd06b35f87446d8d6a7e923b6a7783d1240eb5d6b688eda0d36e925219c098ff2799e42207a093f7d9b7d875823ca05b2e0d3a749dd2fc9cec811ed9a2865d71c4d53cfdfd1bf7e6d5058bd9ecd388ddf0d").unwrap();
let cert = deserialize_nebula_certificate(&known_good_cert).unwrap();
let reserialized_cert_pem = cert.serialize().unwrap();
assert_eq!(known_good_cert, reserialized_cert_pem);
}
#[test]
fn test_deserialize_x25519_public() {
let priv_key = b"-----BEGIN NEBULA X25519 PUBLIC KEY-----
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
-----END NEBULA X25519 PUBLIC KEY-----";
let short_key = b"-----BEGIN NEBULA X25519 PUBLIC KEY-----
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
-----END NEBULA X25519 PUBLIC KEY-----";
let invalid_banner = b"-----BEGIN NOT A NEBULA PUBLIC KEY-----
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
-----END NOT A NEBULA PUBLIC KEY-----";
let invalid_pem = b"-BEGIN NEBULA X25519 PUBLIC KEY-----
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
-END NEBULA X25519 PUBLIC KEY-----";
deserialize_x25519_public(priv_key).unwrap();
deserialize_x25519_public(short_key).unwrap_err();
deserialize_x25519_public(invalid_banner).unwrap_err();
deserialize_x25519_public(invalid_pem).unwrap_err();
}
#[test]
fn ca_pool_add_non_ca() {
let mut ca_pool = NebulaCAPool::new();
let (ca, ca_key, _) = test_ca_cert(SystemTime::now(), SystemTime::now() + Duration::from_secs(3600), vec![], vec![], vec![]);
let (cert, _, _) = test_cert(&ca, &ca_key, SystemTime::now(), SystemTime::now(), vec![], vec![], vec![]);
ca_pool.add_ca_certificate(&cert.serialize_to_pem().unwrap()).unwrap_err();
}
2023-02-27 18:45:02 +00:00
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()
2023-02-27 02:58:45 +00:00
}