so many lines of totally untested code

This commit is contained in:
c0repwn3r 2023-02-26 21:58:45 -05:00
parent bba39afc6b
commit 641852481f
Signed by: core
GPG Key ID: FDBF740DADDCEECF
8 changed files with 1126 additions and 28 deletions

303
Cargo.lock generated
View File

@ -49,7 +49,7 @@ version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
dependencies = [
"getrandom",
"getrandom 0.2.8",
"once_cell",
"version_check",
]
@ -154,6 +154,12 @@ version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a"
[[package]]
name = "base64ct"
version = "1.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b645a089122eccb6111b4f81cbc1a49f5900ac4666bb93ac027feaecf15607bf"
[[package]]
name = "binascii"
version = "0.1.4"
@ -166,6 +172,15 @@ version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "block-buffer"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
dependencies = [
"generic-array",
]
[[package]]
name = "block-buffer"
version = "0.10.3"
@ -252,6 +267,12 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
[[package]]
name = "const-oid"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cec318a675afcb6a1ea1d4340e2d377e56e47c266f28043ceccbf4412ddfdd3b"
[[package]]
name = "constant_time_eq"
version = "0.2.4"
@ -270,7 +291,7 @@ dependencies = [
"hmac",
"percent-encoding",
"rand",
"sha2",
"sha2 0.10.6",
"subtle",
"time 0.3.17",
"version_check",
@ -351,7 +372,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
"generic-array",
"rand_core",
"rand_core 0.6.4",
"typenum",
]
@ -364,6 +385,34 @@ dependencies = [
"cipher",
]
[[package]]
name = "curve25519-dalek"
version = "3.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61"
dependencies = [
"byteorder",
"digest 0.9.0",
"rand_core 0.5.1",
"subtle",
"zeroize",
]
[[package]]
name = "curve25519-dalek"
version = "4.0.0-rc.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d4ba9852b42210c7538b75484f9daa0655e9a3ac04f693747bb0f02cf3cfe16"
dependencies = [
"cfg-if",
"digest 0.10.6",
"fiat-crypto",
"packed_simd_2",
"platforms",
"subtle",
"zeroize",
]
[[package]]
name = "cxx"
version = "1.0.91"
@ -408,6 +457,16 @@ dependencies = [
"syn",
]
[[package]]
name = "der"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de"
dependencies = [
"const-oid",
"zeroize",
]
[[package]]
name = "devise"
version = "0.3.1"
@ -441,13 +500,22 @@ dependencies = [
"syn",
]
[[package]]
name = "digest"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
dependencies = [
"generic-array",
]
[[package]]
name = "digest"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f"
dependencies = [
"block-buffer",
"block-buffer 0.10.3",
"crypto-common",
"subtle",
]
@ -478,6 +546,29 @@ version = "0.15.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03d8c417d7a8cb362e0c37e5d815f5eb7c37f79ff93707329d5a194e42e54ca0"
[[package]]
name = "ed25519"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3cf420a7ec85d98495b0c34aa4a58ca117f982ffbece111aeb545160148d7010"
dependencies = [
"pkcs8",
"signature",
]
[[package]]
name = "ed25519-dalek"
version = "2.0.0-pre.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7bd577ba9d4bcab443cac60003d8fd32c638e7024a3ec92c200d7af5d2c397ed"
dependencies = [
"curve25519-dalek 4.0.0-rc.1",
"ed25519",
"serde",
"sha2 0.10.6",
"zeroize",
]
[[package]]
name = "either"
version = "1.8.1"
@ -508,6 +599,12 @@ dependencies = [
"instant",
]
[[package]]
name = "fiat-crypto"
version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a214f5bb88731d436478f3ae1f8a277b62124089ba9fb67f4f93fb100ef73c90"
[[package]]
name = "figment"
version = "0.10.8"
@ -661,6 +758,17 @@ dependencies = [
"version_check",
]
[[package]]
name = "getrandom"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
dependencies = [
"cfg-if",
"libc",
"wasi 0.9.0+wasi-snapshot-preview1",
]
[[package]]
name = "getrandom"
version = "0.2.8"
@ -773,7 +881,7 @@ version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
dependencies = [
"digest",
"digest 0.10.6",
]
[[package]]
@ -917,6 +1025,12 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "ipnet"
version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30e22bd8629359895450b59ea7a776c850561b96a3b1d31321c1949d9e6c9146"
[[package]]
name = "itertools"
version = "0.10.5"
@ -953,6 +1067,12 @@ version = "0.2.139"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
[[package]]
name = "libm"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fc7aa29613bd6a620df431842069224d8bc9011086b1db4c0e0cd47fa03ec9a"
[[package]]
name = "link-cplusplus"
version = "1.0.8"
@ -1011,7 +1131,7 @@ version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6365506850d44bff6e2fbcb5176cf63650e48bd45ef2fe2665ae1570e0f4b9ca"
dependencies = [
"digest",
"digest 0.10.6",
]
[[package]]
@ -1223,6 +1343,16 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]]
name = "packed_simd_2"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1914cd452d8fccd6f9db48147b29fd4ae05bea9dc5d9ad578509f72415de282"
dependencies = [
"cfg-if",
"libm",
]
[[package]]
name = "parking_lot"
version = "0.11.2"
@ -1300,6 +1430,15 @@ dependencies = [
"syn",
]
[[package]]
name = "pem"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8835c273a76a90455d7344889b0964598e3316e2a79ede8e36f16bdcf2228b8"
dependencies = [
"base64 0.13.1",
]
[[package]]
name = "percent-encoding"
version = "2.2.0"
@ -1318,12 +1457,28 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "pkcs8"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba"
dependencies = [
"der",
"spki",
]
[[package]]
name = "pkg-config"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160"
[[package]]
name = "platforms"
version = "3.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3d7ddaed09e0eb771a79ab0fd64609ba0afb0a8366421957936ad14cbd13630"
[[package]]
name = "png"
version = "0.17.7"
@ -1382,6 +1537,15 @@ version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4339fc7a1021c9c1621d87f5e3505f2805c8c105420ba2f2a4df86814590c142"
[[package]]
name = "quick-protobuf"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d6da84cc204722a989e01ba2f6e1e276e190f22263d0cb6ce8526fcdb0d2e1f"
dependencies = [
"byteorder",
]
[[package]]
name = "quote"
version = "1.0.23"
@ -1399,7 +1563,7 @@ checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
"rand_core 0.6.4",
]
[[package]]
@ -1409,7 +1573,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
"rand_core 0.6.4",
]
[[package]]
name = "rand_core"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
dependencies = [
"getrandom 0.1.16",
]
[[package]]
@ -1418,7 +1591,7 @@ version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom",
"getrandom 0.2.8",
]
[[package]]
@ -1436,7 +1609,7 @@ version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b"
dependencies = [
"getrandom",
"getrandom 0.2.8",
"redox_syscall",
"thiserror",
]
@ -1686,7 +1859,20 @@ checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
"digest 0.10.6",
]
[[package]]
name = "sha2"
version = "0.9.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800"
dependencies = [
"block-buffer 0.9.0",
"cfg-if",
"cpufeatures",
"digest 0.9.0",
"opaque-debug",
]
[[package]]
@ -1697,7 +1883,17 @@ checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
"digest 0.10.6",
]
[[package]]
name = "sha256"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "328169f167261957e83d82be47f9e36629e257c62308129033d7f7e7c173d180"
dependencies = [
"hex",
"sha2 0.9.9",
]
[[package]]
@ -1718,6 +1914,12 @@ dependencies = [
"libc",
]
[[package]]
name = "signature"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fe458c98333f9c8152221191a77e2a44e8325d0193484af2e9421a53019e57d"
[[package]]
name = "slab"
version = "0.4.7"
@ -1749,6 +1951,16 @@ version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f6002a767bff9e83f8eeecf883ecb8011875a21ae8da43bffb817a57e78cc09"
[[package]]
name = "spki"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b"
dependencies = [
"base64ct",
"der",
]
[[package]]
name = "sqlformat"
version = "0.2.1"
@ -1809,7 +2021,7 @@ dependencies = [
"serde",
"serde_json",
"sha1",
"sha2",
"sha2 0.10.6",
"smallvec",
"sqlformat",
"sqlx-rt",
@ -1832,7 +2044,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"sha2",
"sha2 0.10.6",
"sqlx-core",
"sqlx-rt",
"syn",
@ -1896,6 +2108,18 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "synstructure"
version = "0.12.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f"
dependencies = [
"proc-macro2",
"quote",
"syn",
"unicode-xid",
]
[[package]]
name = "tempfile"
version = "3.3.0"
@ -2128,7 +2352,7 @@ dependencies = [
"qrcodegen",
"rand",
"sha1",
"sha2",
"sha2 0.10.6",
"url",
"urlencoding",
]
@ -2227,6 +2451,15 @@ dependencies = [
[[package]]
name = "trifid-pki"
version = "0.1.0"
dependencies = [
"ed25519-dalek",
"hex",
"ipnet",
"pem",
"quick-protobuf",
"sha256",
"x25519-dalek",
]
[[package]]
name = "try-lock"
@ -2338,7 +2571,7 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1674845326ee10d37ca60470760d4288a6f80f304007d92e5c53bab78c9cfd79"
dependencies = [
"getrandom",
"getrandom 0.2.8",
"rand",
"uuid-macro-internal",
]
@ -2382,6 +2615,12 @@ dependencies = [
"try-lock",
]
[[package]]
name = "wasi"
version = "0.9.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
[[package]]
name = "wasi"
version = "0.10.0+wasi-snapshot-preview1"
@ -2623,8 +2862,40 @@ version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"
[[package]]
name = "x25519-dalek"
version = "2.0.0-pre.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5da623d8af10a62342bcbbb230e33e58a63255a58012f8653c578e54bab48df"
dependencies = [
"curve25519-dalek 3.2.0",
"rand_core 0.6.4",
"zeroize",
]
[[package]]
name = "yansi"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec"
[[package]]
name = "zeroize"
version = "1.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f"
dependencies = [
"zeroize_derive",
]
[[package]]
name = "zeroize_derive"
version = "1.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44bf07cb3e50ea2003396695d58bf46bc9887a1f362260446fad6bc4e79bd36c"
dependencies = [
"proc-macro2",
"quote",
"syn",
"synstructure",
]

View File

@ -7,3 +7,10 @@ description = "A rust implementation of the Nebula PKI system"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
pem = "1.1.1"
x25519-dalek = "2.0.0-pre.1"
ed25519-dalek = "2.0.0-pre.0"
ipnet = "2.7.1"
quick-protobuf = "0.8.1"
hex = "0.4.3"
sha256 = "1.1.2"

101
trifid-pki/src/ca.rs Normal file
View File

@ -0,0 +1,101 @@
//! Structs to represent a pool of CA's and blacklisted certificates
use std::collections::HashMap;
use std::error::Error;
use std::fmt::{Display, Formatter};
use ed25519_dalek::VerifyingKey;
use crate::cert::{deserialize_nebula_certificate_from_pem, NebulaCertificate};
/// A pool of trusted CA certificates, and certificates that should be blocked.
/// This is equivalent to the `pki` section in a typical Nebula config.yml.
#[derive(Default)]
pub struct NebulaCAPool {
/// The list of CA root certificates that should be trusted.
pub cas: HashMap<String, NebulaCertificate>,
/// The list of blocklisted certificate fingerprints
pub cert_blocklist: Vec<String>,
/// True if any of the member CAs certificates are expired. Must be handled.
pub expired: bool
}
impl NebulaCAPool {
/// Create a new, blank CA pool
pub fn new() -> Self {
Self::default()
}
/// Create a new CA pool from a set of PEM encoded CA certificates.
/// If any of the certificates are expired, the pool will **still be returned**, with the expired flag set.
/// This must be handled properly.
/// # Errors
/// This function will return an error if PEM data provided was invalid.
pub fn new_from_pem(bytes: &[u8]) -> Result<Self, Box<dyn Error>> {
let pems = pem::parse_many(bytes)?;
let mut pool = Self::new();
for cert in pems {
match pool.add_ca_certificate(pem::encode(&cert).as_bytes()) {
Ok(did_expire) => if did_expire { pool.expired = true },
Err(e) => return Err(e)
}
}
Ok(pool)
}
/// Add a given CA certificate to the CA pool. If the certificate is expired, it will **still be added** - the return value will be `true` instead of `false`
/// # Errors
/// This function will return an error if the certificate is invalid in any way.
pub fn add_ca_certificate(&mut self, bytes: &[u8]) -> Result<bool, Box<dyn Error>> {
let cert = deserialize_nebula_certificate_from_pem(bytes)?;
if !cert.details.is_ca {
return Err(CaPoolError::NotACA.into())
}
if !cert.check_signature(&VerifyingKey::from_bytes(&cert.details.public_key)?)? {
}
Ok(false)
}
/// Checks if the given certificate is blocklisted
pub fn is_blocklisted(&self, cert: &NebulaCertificate) -> bool {
let Ok(h) = cert.sha256sum() else { return false };
self.cert_blocklist.contains(&h)
}
/// Gets the CA certificate used to sign the given certificate
/// # Errors
/// This function will return an error if the certificate does not have an issuer attached (it is self-signed)
pub fn get_ca_for_cert(&self, cert: &NebulaCertificate) -> Result<Option<&NebulaCertificate>, Box<dyn Error>> {
if cert.details.issuer == String::new() {
return Err(CaPoolError::NoIssuer.into())
}
Ok(self.cas.get(&cert.details.issuer))
}
}
#[derive(Debug)]
/// A list of errors that can happen when working with a CA Pool
pub enum CaPoolError {
/// Tried to add a non-CA cert to the CA pool
NotACA,
/// Tried to add a non-self-signed cert to the CA pool (all CAs must be root certificates)
NotSelfSigned,
/// Tried to look up a certificate that does not have an issuer field
NoIssuer
}
impl Error for CaPoolError {}
impl Display for CaPoolError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::NotACA => write!(f, "Tried to add a non-CA cert to the CA pool"),
Self::NotSelfSigned => write!(f, "Tried to add a non-self-signed cert to the CA pool (all CAs must be root certificates)"),
Self::NoIssuer => write!(f, "Tried to look up a certificate with a null issuer field")
}
}
}

505
trifid-pki/src/cert.rs Normal file
View File

@ -0,0 +1,505 @@
//! Manage Nebula PKI Certificates
//! This is pretty much a direct port of nebula/cert/cert.go
use std::error::Error;
use std::fmt::{Display, Formatter};
use std::net::Ipv4Addr;
use std::ops::Add;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
use ed25519_dalek::ed25519::pkcs8::spki::der::Encode;
use ipnet::{Ipv4Net};
use pem::Pem;
use quick_protobuf::{BytesReader, MessageRead, Writer};
use sha256::digest;
use x25519_dalek::{PublicKey, StaticSecret};
use crate::ca::NebulaCAPool;
use crate::cert_codec::{RawNebulaCertificate, RawNebulaCertificateDetails};
/// The length, in bytes, of public keys
pub const PUBLIC_KEY_LENGTH: i32 = 32;
/// The PEM banner for certificates
pub const CERT_BANNER: &str = "NEBULA CERTIFICATE";
/// The PEM banner for X25519 private keys
pub const X25519_PRIVATE_KEY_BANNER: &str = "NEBULA X25519 PRIVATE KEY";
/// The PEM banner for X25519 public keys
pub const X25519_PUBLIC_KEY_BANNER: &str = "NEBULA X25519 PUBLIC KEY";
/// The PEM banner for Ed25519 private keys
pub const ED25519_PRIVATE_KEY_BANNER: &str = "NEBULA ED25519 PRIVATE KEY";
/// The PEM banner for Ed25519 public keys
pub const ED25519_PUBLIC_KEY_BANNER: &str = "NEBULA ED25519 PUBLIC KEY";
/// A Nebula PKI certificate
pub struct NebulaCertificate {
/// The signed data of this certificate
pub details: NebulaCertificateDetails,
/// The Ed25519 signature of this certificate
pub signature: Vec<u8>
}
/// The signed details contained in a Nebula PKI certificate
pub struct NebulaCertificateDetails {
/// The name of the identity this certificate was issued for
pub name: String,
/// The IPv4 addresses issued to this node
pub ips: Vec<Ipv4Net>,
/// The IPv4 subnets this node is responsible for routing
pub subnets: Vec<Ipv4Net>,
/// The groups this node is a part of
pub groups: Vec<String>,
/// Certificate start date and time
pub not_before: SystemTime,
/// Certificate expiry date and time
pub not_after: SystemTime,
/// Public key
pub public_key: [u8; PUBLIC_KEY_LENGTH as usize],
/// Is this node a CA?
pub is_ca: bool,
/// SHA256 of issuer certificate. If blank, this cert is self-signed.
pub issuer: String
}
/// A list of errors that can occur parsing certificates
#[derive(Debug)]
pub enum CertificateError {
/// Attempted to deserialize a certificate from an empty byte array
EmptyByteArray,
/// The encoded Details field is null
NilDetails,
/// The encoded Ips field is not formatted correctly
IpsNotPairs,
/// The encoded Subnets field is not formatted correctly
SubnetsNotPairs,
/// Signatures are expected to be 64 bytes but the signature on the certificate was a different length
WrongSigLength,
/// Public keys are expected to be 32 bytes but the public key on this cert is not
WrongKeyLength,
/// Certificates should have the PEM tag `NEBULA CERTIFICATE`, but this block did not
WrongPemTag,
/// This certificate either is not yet valid or has already expired
Expired,
/// The public key does not match the expected value
KeyMismatch
}
impl Display for CertificateError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::EmptyByteArray => write!(f, "Certificate bytearray is empty"),
Self::NilDetails => write!(f, "The encoded Details field is null"),
Self::IpsNotPairs => write!(f, "encoded IPs should be in pairs, an odd number was found"),
Self::SubnetsNotPairs => write!(f, "encoded subnets should be in pairs, an odd number was found"),
Self::WrongSigLength => write!(f, "Signature should be 64 bytes but is a different size"),
Self::WrongKeyLength => write!(f, "Public keys are expected to be 32 bytes but the public key on this cert is not"),
Self::WrongPemTag => write!(f, "Certificates should have the PEM tag `NEBULA CERTIFICATE`, but this block did not"),
Self::Expired => write!(f, "This certificate either is not yet valid or has already expired"),
Self::KeyMismatch => write!(f, "Key does not match expected value")
}
}
}
impl Error for CertificateError {}
fn map_cidr_pairs(pairs: &[u32]) -> Result<Vec<Ipv4Net>, Box<dyn Error>> {
let mut res_vec = vec![];
for pair in pairs.chunks(2) {
res_vec.push(Ipv4Net::with_netmask(Ipv4Addr::from(pair[0]), Ipv4Addr::from(pair[1]))?);
}
Ok(res_vec)
}
/// Given a protobuf-encoded certificate bytearray, deserialize it into a `NebulaCertificate` object.
/// # Errors
/// This function will return an error if there is a protobuf parsing error, or if the certificate data is invalid.
/// # Panics
pub fn deserialize_nebula_certificate(bytes: &[u8]) -> Result<NebulaCertificate, Box<dyn Error>> {
if bytes.is_empty() {
return Err(CertificateError::EmptyByteArray.into())
}
let mut reader = BytesReader::from_bytes(bytes);
let raw_cert = RawNebulaCertificate::from_reader(&mut reader, bytes)?;
let details = raw_cert.Details.ok_or(CertificateError::NilDetails)?;
if details.Ips.len() % 2 != 0 {
return Err(CertificateError::IpsNotPairs.into())
}
if details.Subnets.len() % 2 != 0 {
return Err(CertificateError::SubnetsNotPairs.into())
}
let mut nebula_cert;
#[allow(clippy::cast_sign_loss)]
{
nebula_cert = NebulaCertificate {
details: NebulaCertificateDetails {
name: details.Name.to_string(),
ips: map_cidr_pairs(&details.Ips)?,
subnets: map_cidr_pairs(&details.Subnets)?,
groups: details.Groups.iter().map(std::string::ToString::to_string).collect(),
not_before: SystemTime::UNIX_EPOCH.add(Duration::from_secs(details.NotBefore as u64)),
not_after: SystemTime::UNIX_EPOCH.add(Duration::from_secs(details.NotAfter as u64)),
public_key: [0u8; 32],
is_ca: details.IsCA,
issuer: hex::encode(details.Issuer),
},
signature: vec![],
};
}
nebula_cert.signature = raw_cert.Signature;
if details.PublicKey.len() != 32 {
return Err(CertificateError::WrongKeyLength.into())
}
#[allow(clippy::unwrap_used)] { nebula_cert.details.public_key = details.PublicKey.try_into().unwrap(); }
Ok(nebula_cert)
}
/// A list of errors that can occur parsing keys
#[derive(Debug)]
pub enum KeyError {
/// Keys should have their associated PEM tags but this had the wrong one
WrongPemTag
}
impl Display for KeyError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::WrongPemTag => write!(f, "Keys should have their associated PEM tags but this had the wrong one")
}
}
}
impl Error for KeyError {}
/// Deserialize the first PEM block in the given byte array into a `NebulaCertificate`
/// # Errors
/// This function will return an error if the PEM data is invalid, or if there is an error parsing the certificate (see `deserialize_nebula_certificate`)
pub fn deserialize_nebula_certificate_from_pem(bytes: &[u8]) -> Result<NebulaCertificate, Box<dyn Error>> {
let pem = pem::parse(bytes)?;
if pem.tag != CERT_BANNER {
return Err(CertificateError::WrongPemTag.into())
}
deserialize_nebula_certificate(&pem.contents)
}
/// Simple helper to PEM encode an X25519 private key
pub fn serialize_x25519_private(bytes: &[u8]) -> Vec<u8> {
pem::encode(&Pem {
tag: X25519_PRIVATE_KEY_BANNER.to_string(),
contents: bytes.to_vec(),
}).as_bytes().to_vec()
}
/// Simple helper to PEM encode an X25519 public key
pub fn serialize_x25519_public(bytes: &[u8]) -> Vec<u8> {
pem::encode(&Pem {
tag: X25519_PUBLIC_KEY_BANNER.to_string(),
contents: bytes.to_vec(),
}).as_bytes().to_vec()
}
/// Attempt to deserialize a PEM encoded X25519 private key
/// # Errors
/// This function will return an error if the PEM data is invalid or has the wrong tag
pub fn deserialize_x25519_private(bytes: &[u8]) -> Result<Vec<u8>, Box<dyn Error>> {
let pem = pem::parse(bytes)?;
if pem.tag != X25519_PRIVATE_KEY_BANNER {
return Err(KeyError::WrongPemTag.into())
}
Ok(pem.contents)
}
/// Attempt to deserialize a PEM encoded X25519 public key
/// # Errors
/// This function will return an error if the PEM data is invalid or has the wrong tag
pub fn deserialize_x25519_public(bytes: &[u8]) -> Result<Vec<u8>, Box<dyn Error>> {
let pem = pem::parse(bytes)?;
if pem.tag != X25519_PUBLIC_KEY_BANNER {
return Err(KeyError::WrongPemTag.into())
}
Ok(pem.contents)
}
/// Simple helper to PEM encode an Ed25519 private key
pub fn serialize_ed25519_private(bytes: &[u8]) -> Vec<u8> {
pem::encode(&Pem {
tag: ED25519_PRIVATE_KEY_BANNER.to_string(),
contents: bytes.to_vec(),
}).as_bytes().to_vec()
}
/// Simple helper to PEM encode an Ed25519 public key
pub fn serialize_ed25519_public(bytes: &[u8]) -> Vec<u8> {
pem::encode(&Pem {
tag: ED25519_PUBLIC_KEY_BANNER.to_string(),
contents: bytes.to_vec(),
}).as_bytes().to_vec()
}
/// Attempt to deserialize a PEM encoded Ed25519 private key
/// # Errors
/// This function will return an error if the PEM data is invalid or has the wrong tag
pub fn deserialize_ed25519_private(bytes: &[u8]) -> Result<Vec<u8>, Box<dyn Error>> {
let pem = pem::parse(bytes)?;
if pem.tag != ED25519_PRIVATE_KEY_BANNER {
return Err(KeyError::WrongPemTag.into())
}
Ok(pem.contents)
}
/// Attempt to deserialize a PEM encoded Ed25519 public key
/// # Errors
/// This function will return an error if the PEM data is invalid or has the wrong tag
pub fn deserialize_ed25519_public(bytes: &[u8]) -> Result<Vec<u8>, Box<dyn Error>> {
let pem = pem::parse(bytes)?;
if pem.tag != ED25519_PUBLIC_KEY_BANNER {
return Err(KeyError::WrongPemTag.into())
}
Ok(pem.contents)
}
impl NebulaCertificate {
/// Sign a nebula certificate with the provided private key
/// # Errors
/// This function will return an error if the certificate could not be serialized or signed.
pub fn sign(&mut self, key: &SigningKey) -> Result<(), Box<dyn Error>> {
let mut out = Vec::new();
let mut writer = Writer::new(&mut out);
writer.write_message(&self.get_raw_details())?;
let sig = key.sign(&out).to_bytes();
self.signature = match sig.to_vec() {
Ok(v) => v,
Err(_) => return Err("signature error".into())
};
Ok(())
}
/// Verify the signature on a certificate with the provided public key
/// # Errors
/// This function will return an error if the certificate could not be serialized or the signature could not be checked.
pub fn check_signature(&self, key: &VerifyingKey) -> Result<bool, Box<dyn Error>> {
let mut out = Vec::new();
let mut writer = Writer::new(&mut out);
writer.write_message(&self.get_raw_details())?;
Ok(key.verify(&out, &Signature::try_from(&*self.signature)?).is_ok())
}
/// Returns true if the signature is too young or too old compared to the provided time
pub fn expired(&self, time: SystemTime) -> bool {
self.details.not_before > time || self.details.not_after < time
}
/// Verify will ensure a certificate is good in all respects (expiry, group membership, signature, cert blocklist, etc)
/// # Errors
/// This function will return an error if there is an error parsing the cert or the CA pool.
pub fn verify(&self, time: SystemTime, ca_pool: &NebulaCAPool) -> Result<CertificateValidity, Box<dyn Error>> {
if ca_pool.is_blocklisted(self) {
return Ok(CertificateValidity::Blocklisted);
}
let Some(signer) = ca_pool.get_ca_for_cert(self)? else { return Ok(CertificateValidity::NotSignedByThisCAPool) };
if signer.expired(time) {
return Ok(CertificateValidity::RootCertExpired)
}
if self.expired(time) {
return Ok(CertificateValidity::CertExpired)
}
if !self.check_signature(&VerifyingKey::from_bytes(&signer.details.public_key)?)? {
return Ok(CertificateValidity::BadSignature)
}
Ok(self.check_root_constraints(signer))
}
/// Make sure that this certificate does not break any of the constraints set by the signing certificate
pub fn check_root_constraints(&self, signer: &Self) -> CertificateValidity {
// Make sure this cert doesn't expire after the signer
if signer.details.not_before < self.details.not_before {
return CertificateValidity::CertExpiresAfterSigner;
}
// Make sure this cert doesn't come into validity before the root
if signer.details.not_before > self.details.not_before {
return CertificateValidity::CertValidBeforeSigner;
}
// If the signer contains a limited set of groups, make sure this cert only has a subset of them
if !signer.details.groups.is_empty() {
for group in &self.details.groups {
if !signer.details.groups.contains(group) {
return CertificateValidity::GroupNotPresentOnSigner;
}
}
}
// If the signer contains a limited set of IP ranges, make sure the cert only contains a subset
if !signer.details.ips.is_empty() {
for ip in &self.details.ips {
if !net_match(*ip, &signer.details.ips) {
return CertificateValidity::IPNotPresentOnSigner;
}
}
}
// If the signer contains a limited set of subnets, make sure the cert only contains a subset
if !signer.details.subnets.is_empty() {
for subnet in &self.details.subnets {
if !net_match(*subnet, &signer.details.subnets) {
return CertificateValidity::SubnetNotPresentOnSigner;
}
}
}
CertificateValidity::Ok
}
#[allow(clippy::unwrap_used)]
/// Verify if the given private key corresponds to the public key used to sign this certificate
/// # Errors
/// This function will return an error if either keys are invalid.
/// # Panics
/// This function, while containing calls to unwrap, has proper bounds checking and will not panic.
pub fn verify_private_key(&self, key: &[u8]) -> Result<(), Box<dyn Error>> {
if self.details.is_ca {
// convert the keys
if key.len() != 64 {
return Err("key not 64-bytes long".into())
}
let actual_private_key: [u8; 32] = (&key[..32]).try_into().unwrap();
if PublicKey::from(&StaticSecret::from(actual_private_key)) != PublicKey::from(self.details.public_key) {
return Err(CertificateError::KeyMismatch.into());
}
return Ok(());
}
if key.len() != 32 {
return Err("key not 32-bytes long".into())
}
let pubkey = x25519_dalek::x25519(key.try_into().unwrap(), x25519_dalek::X25519_BASEPOINT_BYTES);
if pubkey != self.details.public_key {
return Err(CertificateError::KeyMismatch.into());
}
Ok(())
}
/// Get a protobuf-ready raw struct, ready for serialization
#[allow(clippy::expect_used)]
#[allow(clippy::cast_possible_wrap)]
/// # Panics
/// This function will panic if time went backwards, or if the certificate contains extremely invalid data.
pub fn get_raw_details(&self) -> RawNebulaCertificateDetails {
let mut raw = RawNebulaCertificateDetails {
Name: self.details.name.clone(),
Ips: vec![],
Subnets: vec![],
Groups: self.details.groups.iter().map(std::convert::Into::into).collect(),
NotBefore: self.details.not_before.duration_since(UNIX_EPOCH).expect("Time went backwards").as_secs() as i64,
NotAfter: self.details.not_after.duration_since(UNIX_EPOCH).expect("Time went backwards").as_secs() as i64,
PublicKey: self.details.public_key.into(),
IsCA: self.details.is_ca,
Issuer: hex::decode(&self.details.issuer).expect("Issuer was not a hex-encoded value"),
};
for ip_net in &self.details.ips {
raw.Ips.push(ip_net.addr().into());
raw.Ips.push(ip_net.netmask().into());
}
for subnet in &self.details.subnets {
raw.Subnets.push(subnet.addr().into());
raw.Subnets.push(subnet.netmask().into());
}
raw
}
/// Will serialize this cert into a protobuf byte array.
/// # Errors
/// This function will return an error if protobuf was unable to serialize the data.
pub fn serialize(&self) -> Result<Vec<u8>, Box<dyn Error>> {
let raw_cert = RawNebulaCertificate {
Details: Some(self.get_raw_details()),
Signature: self.signature.clone(),
};
let mut out = vec![];
let mut writer = Writer::new(&mut out);
writer.write_message(&raw_cert)?;
Ok(out)
}
/// Will serialize this cert into a PEM byte array.
/// # Errors
/// This function will return an error if protobuf was unable to serialize the data.
pub fn serialize_to_pem(&self) -> Result<Vec<u8>, Box<dyn Error>> {
let pbuf_bytes = self.serialize()?;
Ok(pem::encode(&Pem {
tag: CERT_BANNER.to_string(),
contents: pbuf_bytes,
}).as_bytes().to_vec())
}
/// Get the fingerprint of this certificate
/// # Errors
/// This functiom will return an error if protobuf was unable to serialize the cert.
pub fn sha256sum(&self) -> Result<String, Box<dyn Error>> {
let pbuf_bytes = self.serialize()?;
Ok(digest(&pbuf_bytes[..]))
}
}
/// A list of possible errors that can happen validating a certificate
pub enum CertificateValidity {
/// There are no issues with this certificate
Ok,
/// This cert has been blocklisted in the given CA pool
Blocklisted,
/// The certificate that signed this cert is expired
RootCertExpired,
/// This cert is expired
CertExpired,
/// This cert's signature is invalid
BadSignature,
/// This cert was not signed by any CAs in the CA pool
NotSignedByThisCAPool,
/// This cert expires after the signer's cert expires
CertExpiresAfterSigner,
/// This cert enters validity before the signer's cert does
CertValidBeforeSigner,
/// A group present on this certificate is not present on the signer's certificate
GroupNotPresentOnSigner,
/// An IP present on this certificate is not present on the signer's certificate
IPNotPresentOnSigner,
/// A subnet on this certificate is not present on the signer's certificate
SubnetNotPresentOnSigner
}
fn net_match(cert_ip: Ipv4Net, root_ips: &Vec<Ipv4Net>) -> bool {
for net in root_ips {
if net.contains(&cert_ip) {
return true;
}
}
false
}

View File

@ -0,0 +1,27 @@
// Copied from https://github.com/slackhq/nebula/blob/master/cert/cert.proto
// with some minimal changes for trifid-pki.
syntax = "proto3";
package cert_codec;
message RawNebulaCertificate {
RawNebulaCertificateDetails Details = 1;
bytes Signature = 2;
}
message RawNebulaCertificateDetails {
string Name = 1;
// Ips and Subnets are in big endian 32 bit pairs, 1st the ip, 2nd the mask
repeated uint32 Ips = 2;
repeated uint32 Subnets = 3;
repeated string Groups = 4;
int64 NotBefore = 5;
int64 NotAfter = 6;
bytes PublicKey = 7;
bool IsCA = 8;
// sha-256 of the issuer certificate, if this field is blank the cert is self-signed
bytes Issuer = 9;
}

View File

@ -0,0 +1,116 @@
// Automatically generated rust module for 'cert_codec.proto' file
#![allow(non_snake_case)]
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(unused_imports)]
#![allow(unknown_lints)]
#![allow(clippy::all)]
#![cfg_attr(rustfmt, rustfmt_skip)]
#![allow(clippy::wildcard_imports)]
use quick_protobuf::{MessageInfo, MessageRead, MessageWrite, BytesReader, Writer, WriterBackend, Result};
use quick_protobuf::sizeofs::*;
use super::*;
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Debug, Default, PartialEq, Clone)]
pub struct RawNebulaCertificate {
pub Details: Option<cert_codec::RawNebulaCertificateDetails>,
pub Signature: Vec<u8>,
}
impl<'a> MessageRead<'a> for RawNebulaCertificate {
fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result<Self> {
let mut msg = Self::default();
while !r.is_eof() {
match r.next_tag(bytes) {
Ok(10) => msg.Details = Some(r.read_message::<cert_codec::RawNebulaCertificateDetails>(bytes)?),
Ok(18) => msg.Signature = r.read_bytes(bytes)?.to_owned(),
Ok(t) => { r.read_unknown(bytes, t)?; }
Err(e) => return Err(e),
}
}
Ok(msg)
}
}
impl MessageWrite for RawNebulaCertificate {
fn write_message<W: WriterBackend>(&self, w: &mut Writer<W>) -> Result<()> {
if let Some(ref s) = self.Details { w.write_with_tag(10, |w| w.write_message(s))?; }
if !self.Signature.is_empty() { w.write_with_tag(18, |w| w.write_bytes(&**&self.Signature))?; }
Ok(())
}
fn get_size(&self) -> usize {
0
+ self.Details.as_ref().map_or(0, |m| 1 + sizeof_len((m).get_size()))
+ if self.Signature.is_empty() { 0 } else { 1 + sizeof_len((&self.Signature).len()) }
}
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Debug, Default, PartialEq, Clone)]
pub struct RawNebulaCertificateDetails {
pub Name: String,
pub Ips: Vec<u32>,
pub Subnets: Vec<u32>,
pub Groups: Vec<String>,
pub NotBefore: i64,
pub NotAfter: i64,
pub PublicKey: Vec<u8>,
pub IsCA: bool,
pub Issuer: Vec<u8>,
}
impl<'a> MessageRead<'a> for RawNebulaCertificateDetails {
fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result<Self> {
let mut msg = Self::default();
while !r.is_eof() {
match r.next_tag(bytes) {
Ok(10) => msg.Name = r.read_string(bytes)?.to_owned(),
Ok(18) => msg.Ips = r.read_packed(bytes, |r, bytes| Ok(r.read_uint32(bytes)?))?,
Ok(26) => msg.Subnets = r.read_packed(bytes, |r, bytes| Ok(r.read_uint32(bytes)?))?,
Ok(34) => msg.Groups.push(r.read_string(bytes)?.to_owned()),
Ok(40) => msg.NotBefore = r.read_int64(bytes)?,
Ok(48) => msg.NotAfter = r.read_int64(bytes)?,
Ok(58) => msg.PublicKey = r.read_bytes(bytes)?.to_owned(),
Ok(64) => msg.IsCA = r.read_bool(bytes)?,
Ok(74) => msg.Issuer = r.read_bytes(bytes)?.to_owned(),
Ok(t) => { r.read_unknown(bytes, t)?; }
Err(e) => return Err(e),
}
}
Ok(msg)
}
}
#[allow(clippy::cast_sign_loss)]
impl MessageWrite for RawNebulaCertificateDetails {
fn write_message<W: WriterBackend>(&self, w: &mut Writer<W>) -> Result<()> {
if self.Name != String::default() { w.write_with_tag(10, |w| w.write_string(&**&self.Name))?; }
w.write_packed_with_tag(18, &self.Ips, |w, m| w.write_uint32(*m), &|m| sizeof_varint(u64::from(*(m))))?;
w.write_packed_with_tag(26, &self.Subnets, |w, m| w.write_uint32(*m), &|m| sizeof_varint(u64::from(*(m))))?;
for s in &self.Groups { w.write_with_tag(34, |w| w.write_string(&**s))?; }
if self.NotBefore != 0i64 { w.write_with_tag(40, |w| w.write_int64(*&self.NotBefore))?; }
if self.NotAfter != 0i64 { w.write_with_tag(48, |w| w.write_int64(*&self.NotAfter))?; }
if !self.PublicKey.is_empty() { w.write_with_tag(58, |w| w.write_bytes(&**&self.PublicKey))?; }
if self.IsCA != false { w.write_with_tag(64, |w| w.write_bool(*&self.IsCA))?; }
if !self.Issuer.is_empty() { w.write_with_tag(74, |w| w.write_bytes(&**&self.Issuer))?; }
Ok(())
}
fn get_size(&self) -> usize {
0
+ if self.Name == String::default() { 0 } else { 1 + sizeof_len((&self.Name).len()) }
+ if self.Ips.is_empty() { 0 } else { 1 + sizeof_len(self.Ips.iter().map(|s| sizeof_varint(u64::from(*(s)))).sum::<usize>()) }
+ if self.Subnets.is_empty() { 0 } else { 1 + sizeof_len(self.Subnets.iter().map(|s| sizeof_varint(u64::from(*(s)))).sum::<usize>()) }
+ self.Groups.iter().map(|s| 1 + sizeof_len((s).len())).sum::<usize>()
+ if self.NotBefore == 0i64 { 0 } else { 1 + sizeof_varint(*(&self.NotBefore) as u64) }
+ if self.NotAfter == 0i64 { 0 } else { 1 + sizeof_varint(*(&self.NotAfter) as u64) }
+ if self.PublicKey.is_empty() { 0 } else { 1 + sizeof_len((&self.PublicKey).len()) }
+ if self.IsCA == false { 0 } else { 1 + sizeof_varint(u64::from(*(&self.IsCA))) }
+ if self.Issuer.is_empty() { 0 } else { 1 + sizeof_len((&self.Issuer).len()) }
}
}

View File

@ -1,14 +1,25 @@
pub fn add(left: usize, right: usize) -> usize {
left + right
}
//! # 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.
//! 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.
#![warn(clippy::pedantic)]
#![warn(clippy::nursery)]
#![deny(clippy::unwrap_used)]
#![deny(clippy::expect_used)]
#![deny(missing_docs)]
#![deny(clippy::missing_errors_doc)]
#![deny(clippy::missing_panics_doc)]
#![deny(clippy::missing_safety_doc)]
#![allow(clippy::must_use_candidate)]
#![allow(clippy::too_many_lines)]
#![allow(clippy::module_name_repetitions)]
pub mod ca;
pub mod cert;
pub(crate) mod cert_codec;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}
#[macro_use]
pub mod test;

60
trifid-pki/src/test.rs Normal file
View File

@ -0,0 +1,60 @@
#![allow(clippy::unwrap_used)]
#![allow(clippy::expect_used)]
use crate::netmask;
use std::net::Ipv4Addr;
use std::time::{Duration, SystemTime};
use ipnet::Ipv4Net;
use crate::cert::{deserialize_nebula_certificate, NebulaCertificate, NebulaCertificateDetails};
use std::str::FromStr;
#[test]
fn certificate_serialization() {
let before = SystemTime::now() - Duration::from_secs(60);
let after = SystemTime::now() + Duration::from_secs(60);
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.255.0")
],
subnets: vec![
netmask!("9.1.1.1", "255.0.255.0"),
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: "1234567890abcedfghij1234567890ab".to_string(),
},
signature: hex::decode("1234567890abcedfghij1234567890ab").unwrap(),
};
let bytes = cert.serialize().unwrap();
let deserialized = deserialize_nebula_certificate(&bytes).unwrap();
/*
assert.Equal(t, nc.Details.Name, nc2.Details.Name)
assert.Equal(t, nc.Details.NotBefore, nc2.Details.NotBefore)
assert.Equal(t, nc.Details.NotAfter, nc2.Details.NotAfter)
assert.Equal(t, nc.Details.PublicKey, nc2.Details.PublicKey)
assert.Equal(t, nc.Details.IsCA, nc2.Details.IsCA)
*/
assert_eq!(cert.signature, deserialized.signature);
assert_eq!(cert.details.name, deserialized.details.name);
assert_eq!(cert.details.not_before, deserialized.details.not_after);
}
#[macro_export]
macro_rules! netmask {
($ip:expr,$mask:expr) => {
Ipv4Net::with_netmask(Ipv4Addr::from_str($ip).unwrap(), Ipv4Addr::from_str($mask).unwrap()).unwrap()
};
}