MORE TESTS! 95% pki coverage

This commit is contained in:
core 2023-02-27 18:12:24 -05:00
parent 7d5234b003
commit b0872780dd
Signed by: core
GPG Key ID: FDBF740DADDCEECF
8 changed files with 188 additions and 10 deletions

2
Cargo.lock generated
View File

@ -2419,7 +2419,7 @@ dependencies = [
[[package]] [[package]]
name = "trifid-pki" name = "trifid-pki"
version = "0.1.0" version = "0.1.2"
dependencies = [ dependencies = [
"ed25519-dalek", "ed25519-dalek",
"hex", "hex",

View File

@ -1,6 +1,6 @@
[package] [package]
name = "trifid-pki" name = "trifid-pki"
version = "0.1.0" version = "0.1.2"
edition = "2021" edition = "2021"
description = "A rust implementation of the Nebula PKI system" description = "A rust implementation of the Nebula PKI system"
license = "GPL-3.0-or-later" license = "GPL-3.0-or-later"

8
trifid-pki/README.md Normal file
View File

@ -0,0 +1,8 @@
# trifid-pki
trifid-pki is a crate for interacting with the Nebula PKI system. It was created to prevent the need to make constant CLI calls for signing operations in Nebula. Is is designed to be interoperable with the original Go implementation and as such has some oddities with key management to ensure compatability.
This crate has not received any formal security audits, however the underlying crates used for actual cryptographic operations (ed25519-dalek and curve25519-dalek) have been audited, finding no major issues.
# Examples
See [the documentation](https://docs.rs/trifid-pki) for examples.

View File

@ -113,6 +113,7 @@ pub enum CaPoolError {
NoIssuer NoIssuer
} }
impl Error for CaPoolError {} impl Error for CaPoolError {}
#[cfg(not(tarpaulin_include))]
impl Display for CaPoolError { impl Display for CaPoolError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self { match self {

View File

@ -83,6 +83,7 @@ pub enum CertificateError {
/// The public key does not match the expected value /// The public key does not match the expected value
KeyMismatch KeyMismatch
} }
#[cfg(not(tarpaulin_include))]
impl Display for CertificateError { impl Display for CertificateError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self { match self {
@ -108,6 +109,7 @@ fn map_cidr_pairs(pairs: &[u32]) -> Result<Vec<Ipv4Net>, Box<dyn Error>> {
Ok(res_vec) Ok(res_vec)
} }
#[cfg(not(tarpaulin_include))]
impl Display for NebulaCertificate { impl Display for NebulaCertificate {
#[allow(clippy::unwrap_used)] #[allow(clippy::unwrap_used)]
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
@ -186,12 +188,19 @@ pub fn deserialize_nebula_certificate(bytes: &[u8]) -> Result<NebulaCertificate,
#[derive(Debug)] #[derive(Debug)]
pub enum KeyError { pub enum KeyError {
/// Keys should have their associated PEM tags but this had the wrong one /// Keys should have their associated PEM tags but this had the wrong one
WrongPemTag WrongPemTag,
/// Ed25519 private keys are 64 bytes
Not64Bytes,
/// X25519 private keys are 32 bytes
Not32Bytes
} }
#[cfg(not(tarpaulin_include))]
impl Display for KeyError { impl Display for KeyError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self { match self {
Self::WrongPemTag => write!(f, "Keys should have their associated PEM tags but this had the wrong one") Self::WrongPemTag => write!(f, "Keys should have their associated PEM tags but this had the wrong one"),
Self::Not64Bytes => write!(f, "Ed25519 private keys are 64 bytes"),
Self::Not32Bytes => write!(f, "X25519 private keys are 32 bytes")
} }
} }
} }
@ -233,6 +242,9 @@ pub fn deserialize_x25519_private(bytes: &[u8]) -> Result<Vec<u8>, Box<dyn Error
if pem.tag != X25519_PRIVATE_KEY_BANNER { if pem.tag != X25519_PRIVATE_KEY_BANNER {
return Err(KeyError::WrongPemTag.into()) return Err(KeyError::WrongPemTag.into())
} }
if pem.contents.len() != 32 {
return Err(KeyError::Not32Bytes.into())
}
Ok(pem.contents) Ok(pem.contents)
} }
@ -244,6 +256,9 @@ pub fn deserialize_x25519_public(bytes: &[u8]) -> Result<Vec<u8>, Box<dyn Error>
if pem.tag != X25519_PUBLIC_KEY_BANNER { if pem.tag != X25519_PUBLIC_KEY_BANNER {
return Err(KeyError::WrongPemTag.into()) return Err(KeyError::WrongPemTag.into())
} }
if pem.contents.len() != 32 {
return Err(KeyError::Not32Bytes.into())
}
Ok(pem.contents) Ok(pem.contents)
} }
@ -271,6 +286,9 @@ pub fn deserialize_ed25519_private(bytes: &[u8]) -> Result<Vec<u8>, Box<dyn Erro
if pem.tag != ED25519_PRIVATE_KEY_BANNER { if pem.tag != ED25519_PRIVATE_KEY_BANNER {
return Err(KeyError::WrongPemTag.into()) return Err(KeyError::WrongPemTag.into())
} }
if pem.contents.len() != 64 {
return Err(KeyError::Not64Bytes.into())
}
Ok(pem.contents) Ok(pem.contents)
} }
@ -282,6 +300,9 @@ pub fn deserialize_ed25519_public(bytes: &[u8]) -> Result<Vec<u8>, Box<dyn Error
if pem.tag != ED25519_PUBLIC_KEY_BANNER { if pem.tag != ED25519_PUBLIC_KEY_BANNER {
return Err(KeyError::WrongPemTag.into()) return Err(KeyError::WrongPemTag.into())
} }
if pem.contents.len() != 64 {
return Err(KeyError::Not64Bytes.into())
}
Ok(pem.contents) Ok(pem.contents)
} }

View File

@ -2,7 +2,7 @@
//! trifid-pki is a crate for interacting with the Nebula PKI system. It was created to prevent the need to make constant CLI calls for signing operations in Nebula. //! trifid-pki is a crate for interacting with the Nebula PKI system. It was created to prevent the need to make constant CLI calls for signing operations in Nebula.
//! It is designed to be interoperable with the original Go implementation and as such has some oddities with key management to ensure compatability. //! It is designed to be interoperable with the original Go implementation and as such has some oddities with key management to ensure compatability.
//! //!
//! This crate has not received any format security audits, however the underlying crates used for actual cryptographic operations (ed25519-dalek and curve25519-dalek) have been audited with no major issues. //! This crate has not received any formal security audits, however the underlying crates used for actual cryptographic operations (ed25519-dalek and curve25519-dalek) have been audited with no major issues.
#![warn(clippy::pedantic)] #![warn(clippy::pedantic)]
#![warn(clippy::nursery)] #![warn(clippy::nursery)]

View File

@ -295,7 +295,7 @@ fn x25519_serialization() {
#[test] #[test]
fn ed25519_serialization() { fn ed25519_serialization() {
let bytes = [0u8; 32]; let bytes = [0u8; 64];
assert_eq!(deserialize_ed25519_private(&serialize_ed25519_private(&bytes)).unwrap(), bytes); assert_eq!(deserialize_ed25519_private(&serialize_ed25519_private(&bytes)).unwrap(), bytes);
assert!(deserialize_ed25519_private(&[0u8; 32]).is_err()); assert!(deserialize_ed25519_private(&[0u8; 32]).is_err());
assert_eq!(deserialize_ed25519_public(&serialize_ed25519_public(&bytes)).unwrap(), bytes); assert_eq!(deserialize_ed25519_public(&serialize_ed25519_public(&bytes)).unwrap(), bytes);
@ -549,7 +549,7 @@ fn test_ca_cert(before: SystemTime, after: SystemTime, ips: Vec<Ipv4Net>, subnet
not_after: after, not_after: after,
public_key: pub_key.to_bytes(), public_key: pub_key.to_bytes(),
is_ca: true, is_ca: true,
issuer: "".to_string(), issuer: String::new(),
}, },
signature: vec![], signature: vec![],
}; };
@ -557,6 +557,154 @@ fn test_ca_cert(before: SystemTime, after: SystemTime, ips: Vec<Ipv4Net>, subnet
(cert, key, pub_key) (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 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();
}
fn test_cert(ca: &NebulaCertificate, key: &SigningKey, before: SystemTime, after: SystemTime, ips: Vec<Ipv4Net>, subnets: Vec<Ipv4Net>, groups: Vec<String>) -> (NebulaCertificate, SigningKey, VerifyingKey) { 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 issuer = ca.sha256sum().unwrap();

File diff suppressed because one or more lines are too long