#![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 { 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, subnets: Vec, groups: Vec, ) -> (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, subnets: Vec, groups: Vec, ) -> (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() }