diff --git a/Cargo.lock b/Cargo.lock index 4723dc5..a14c4b2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2419,7 +2419,7 @@ dependencies = [ [[package]] name = "trifid-pki" -version = "0.1.0" +version = "0.1.2" dependencies = [ "ed25519-dalek", "hex", diff --git a/trifid-pki/Cargo.toml b/trifid-pki/Cargo.toml index 780d5d4..2c69fe6 100644 --- a/trifid-pki/Cargo.toml +++ b/trifid-pki/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "trifid-pki" -version = "0.1.0" +version = "0.1.2" edition = "2021" description = "A rust implementation of the Nebula PKI system" license = "GPL-3.0-or-later" diff --git a/trifid-pki/README.md b/trifid-pki/README.md new file mode 100644 index 0000000..8e3892d --- /dev/null +++ b/trifid-pki/README.md @@ -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. \ No newline at end of file diff --git a/trifid-pki/src/ca.rs b/trifid-pki/src/ca.rs index a589600..66dc20a 100644 --- a/trifid-pki/src/ca.rs +++ b/trifid-pki/src/ca.rs @@ -113,6 +113,7 @@ pub enum CaPoolError { NoIssuer } impl Error for CaPoolError {} +#[cfg(not(tarpaulin_include))] impl Display for CaPoolError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { diff --git a/trifid-pki/src/cert.rs b/trifid-pki/src/cert.rs index fd1faef..e328307 100644 --- a/trifid-pki/src/cert.rs +++ b/trifid-pki/src/cert.rs @@ -83,6 +83,7 @@ pub enum CertificateError { /// The public key does not match the expected value KeyMismatch } +#[cfg(not(tarpaulin_include))] impl Display for CertificateError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { @@ -108,6 +109,7 @@ fn map_cidr_pairs(pairs: &[u32]) -> Result, Box> { Ok(res_vec) } +#[cfg(not(tarpaulin_include))] impl Display for NebulaCertificate { #[allow(clippy::unwrap_used)] fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { @@ -186,12 +188,19 @@ pub fn deserialize_nebula_certificate(bytes: &[u8]) -> Result) -> std::fmt::Result { 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, Box Result, Box if pem.tag != X25519_PUBLIC_KEY_BANNER { return Err(KeyError::WrongPemTag.into()) } + if pem.contents.len() != 32 { + return Err(KeyError::Not32Bytes.into()) + } Ok(pem.contents) } @@ -271,6 +286,9 @@ pub fn deserialize_ed25519_private(bytes: &[u8]) -> Result, Box Result, Box, subnet not_after: after, public_key: pub_key.to_bytes(), is_ca: true, - issuer: "".to_string(), + issuer: String::new(), }, signature: vec![], }; @@ -557,6 +557,154 @@ fn test_ca_cert(before: SystemTime, after: SystemTime, ips: Vec, subnet (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, subnets: Vec, groups: Vec) -> (NebulaCertificate, SigningKey, VerifyingKey) { let issuer = ca.sha256sum().unwrap(); diff --git a/trifid-pki/tarpaulin-report.html b/trifid-pki/tarpaulin-report.html index 14a8594..15d7ef7 100644 --- a/trifid-pki/tarpaulin-report.html +++ b/trifid-pki/tarpaulin-report.html @@ -107,8 +107,8 @@