trifid/trifid-pki/src/test.rs

1176 lines
37 KiB
Rust

#![allow(clippy::unwrap_used)]
#![allow(clippy::expect_used)]
use crate::ca::NebulaCAPool;
use crate::cert::{
deserialize_ed25519_private, deserialize_ed25519_public, deserialize_ed25519_public_many,
deserialize_nebula_certificate, deserialize_nebula_certificate_from_pem,
deserialize_x25519_private, deserialize_x25519_public, serialize_ed25519_private,
serialize_ed25519_public, serialize_x25519_private, serialize_x25519_public,
CertificateValidity, NebulaCertificate, NebulaCertificateDetails,
};
use crate::cert_codec::{RawNebulaCertificate, RawNebulaCertificateDetails};
use crate::netmask;
use ed25519_dalek::{SigningKey, VerifyingKey};
use ipnet::Ipv4Net;
use quick_protobuf::{MessageWrite, Writer};
use rand::rngs::OsRng;
use std::net::Ipv4Addr;
use std::ops::{Add, Sub};
use std::str::FromStr;
use std::time::{Duration, SystemTime, SystemTimeError, UNIX_EPOCH};
/// 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-----";
#[test]
fn certificate_serialization() {
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().unwrap();
let deserialized = deserialize_nebula_certificate(&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 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();
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());
}
#[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; 64];
let bytes2 = [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(&bytes2)).unwrap(),
bytes2
);
assert!(deserialize_ed25519_public(&[0u8; 64]).is_err());
let mut bytes = vec![];
bytes.append(&mut serialize_ed25519_public(&[0u8; 32]));
bytes.append(&mut serialize_ed25519_public(&[1u8; 32]));
let deser = deserialize_ed25519_public_many(&bytes).unwrap();
assert_eq!(deser[0], [0u8; 32]);
assert_eq!(deser[1], [1u8; 32]);
bytes.append(&mut serialize_ed25519_public(&[1u8; 33]));
deserialize_ed25519_public_many(&bytes).unwrap_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();
}
#[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);
}
#[macro_export]
macro_rules! netmask {
($ip:expr,$mask:expr) => {
Ipv4Net::with_netmask(
Ipv4Addr::from_str($ip).unwrap(),
Ipv4Addr::from_str($mask).unwrap(),
)
.unwrap()
};
}
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: String::new(),
},
signature: vec![],
};
cert.sign(&key).unwrap();
(cert, key, pub_key)
}
#[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 pub_key = b"-----BEGIN NEBULA ED25519 PUBLIC KEY-----
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
-----END NEBULA ED25519 PUBLIC KEY-----";
let short_key = b"-----BEGIN NEBULA ED25519 PUBLIC KEY-----
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
-----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(pub_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();
}
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()
}