diff --git a/Cargo.lock b/Cargo.lock index cbb2a1e..0d95187 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -172,15 +172,6 @@ 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" @@ -291,7 +282,7 @@ dependencies = [ "hmac", "percent-encoding", "rand", - "sha2 0.10.6", + "sha2", "subtle", "time 0.3.17", "version_check", @@ -515,7 +506,7 @@ version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" dependencies = [ - "block-buffer 0.10.3", + "block-buffer", "crypto-common", "subtle", ] @@ -565,7 +556,7 @@ dependencies = [ "curve25519-dalek 4.0.0-rc.1", "ed25519", "serde", - "sha2 0.10.6", + "sha2", "zeroize", ] @@ -1862,19 +1853,6 @@ dependencies = [ "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]] name = "sha2" version = "0.10.6" @@ -1886,16 +1864,6 @@ dependencies = [ "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]] name = "sharded-slab" version = "0.1.4" @@ -2021,7 +1989,7 @@ dependencies = [ "serde", "serde_json", "sha1", - "sha2 0.10.6", + "sha2", "smallvec", "sqlformat", "sqlx-rt", @@ -2044,7 +2012,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "sha2 0.10.6", + "sha2", "sqlx-core", "sqlx-rt", "syn", @@ -2352,7 +2320,7 @@ dependencies = [ "qrcodegen", "rand", "sha1", - "sha2 0.10.6", + "sha2", "url", "urlencoding", ] @@ -2457,7 +2425,7 @@ dependencies = [ "ipnet", "pem", "quick-protobuf", - "sha256", + "sha2", "x25519-dalek", ] diff --git a/trifid-pki/Cargo.toml b/trifid-pki/Cargo.toml index c66566f..ed990d7 100644 --- a/trifid-pki/Cargo.toml +++ b/trifid-pki/Cargo.toml @@ -13,4 +13,4 @@ ed25519-dalek = "2.0.0-pre.0" ipnet = "2.7.1" quick-protobuf = "0.8.1" hex = "0.4.3" -sha256 = "1.1.2" \ No newline at end of file +sha2 = "0.10.6" \ No newline at end of file diff --git a/trifid-pki/bad.hex.crt b/trifid-pki/bad.hex.crt new file mode 100644 index 0000000..a491a28 --- /dev/null +++ b/trifid-pki/bad.hex.crt @@ -0,0 +1 @@ +0aaa010a0774657374696e67121b8182845080feffff0f828284508080fcff0f83828450808080f80f1a1b8182844880ffffff0f8282844880feffff0f838284488080fcff0f220b746573742d67726f757031220b746573742d67726f757032220b746573742d67726f75703328888cf39f0630808df39f063a20313233343536373839306162636564666768696a3132333435363738393061624a101234567890abcedfabcd1234567890ab1220313233343536373839306162636564666768696a313233343536373839306162 \ No newline at end of file diff --git a/trifid-pki/hex.crt b/trifid-pki/hex.crt new file mode 100644 index 0000000..7fbc9ac --- /dev/null +++ b/trifid-pki/hex.crt @@ -0,0 +1 @@ +0a490a1765337465616d20496e7465726e616c204e6574776f726b28959ebf9c06309585c4ab063a20afa70a07c8a639f10e7ed97c438eda027ffce5358fc451d07c6f05d04b0128e740011240740f1efa96432a7372321f9fa697674a8d3caf078262c3ac7769b7f961971ff2b0ee544810a6f15d266a37c49c886f70bdbca67a917b14c63c3ab3525d5a0900 \ No newline at end of file diff --git a/trifid-pki/known_good.crt b/trifid-pki/known_good.crt new file mode 100644 index 0000000..3969915 --- /dev/null +++ b/trifid-pki/known_good.crt @@ -0,0 +1,5 @@ +-----BEGIN NEBULA CERTIFICATE----- +CkkKF2UzdGVhbSBJbnRlcm5hbCBOZXR3b3JrKJWev5wGMJWFxKsGOiCvpwoHyKY5 +8Q5+2XxDjtoCf/zlNY/EUdB8bwXQSwEo50ABEkB0Dx76lkMqc3IyH5+ml2dKjTyv +B4Jiw6x3abf5YZcf8rDuVEgQpvFdJmo3xJyIb3C9vKZ6kXsUxjw6s1JdWgkA +-----END NEBULA CERTIFICATE----- \ No newline at end of file diff --git a/trifid-pki/src/cert.rs b/trifid-pki/src/cert.rs index ee164bc..257715f 100644 --- a/trifid-pki/src/cert.rs +++ b/trifid-pki/src/cert.rs @@ -10,11 +10,12 @@ 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 quick_protobuf::{BytesReader, MessageRead, MessageWrite, Writer}; +use sha2::Sha256; use x25519_dalek::{PublicKey, StaticSecret}; use crate::ca::NebulaCAPool; use crate::cert_codec::{RawNebulaCertificate, RawNebulaCertificateDetails}; +use sha2::Digest; /// The length, in bytes, of public keys pub const PUBLIC_KEY_LENGTH: i32 = 32; @@ -31,6 +32,7 @@ pub const ED25519_PRIVATE_KEY_BANNER: &str = "NEBULA ED25519 PRIVATE KEY"; pub const ED25519_PUBLIC_KEY_BANNER: &str = "NEBULA ED25519 PUBLIC KEY"; /// A Nebula PKI certificate +#[derive(Debug)] pub struct NebulaCertificate { /// The signed data of this certificate pub details: NebulaCertificateDetails, @@ -39,6 +41,7 @@ pub struct NebulaCertificate { } /// The signed details contained in a Nebula PKI certificate +#[derive(Debug)] pub struct NebulaCertificateDetails { /// The name of the identity this certificate was issued for pub name: String, @@ -107,6 +110,26 @@ fn map_cidr_pairs(pairs: &[u32]) -> Result, Box> { Ok(res_vec) } +impl Display for NebulaCertificate { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + writeln!(f, "NebulaCertificate {{")?; + writeln!(f, " Details {{")?; + writeln!(f, " Name: {}", self.details.name)?; + writeln!(f, " Ips: {:?}", self.details.ips)?; + writeln!(f, " Subnets: {:?}", self.details.subnets)?; + writeln!(f, " Groups: {:?}", self.details.groups)?; + writeln!(f, " Not before: {:?}", self.details.not_before)?; + writeln!(f, " Not after: {:?}", self.details.not_after)?; + writeln!(f, " Is CA: {}", self.details.is_ca)?; + writeln!(f, " Issuer: {}", self.details.issuer)?; + writeln!(f, " Public key: {}", hex::encode(self.details.public_key))?; + writeln!(f, " }}")?; + writeln!(f, " Fingerprint: {}", self.sha256sum().unwrap())?; + writeln!(f, " Signature: {}", hex::encode(self.signature.clone()))?; + writeln!(f, "}}") + } +} + /// 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. @@ -442,7 +465,9 @@ impl NebulaCertificate { let mut out = vec![]; let mut writer = Writer::new(&mut out); - writer.write_message(&raw_cert)?; + raw_cert.write_message(&mut writer)?; + + println!("{:?}", hex::encode(out.clone())); Ok(out) } @@ -465,7 +490,10 @@ impl NebulaCertificate { pub fn sha256sum(&self) -> Result> { let pbuf_bytes = self.serialize()?; - Ok(digest(&pbuf_bytes[..])) + let mut hasher = Sha256::new(); + hasher.update(pbuf_bytes); + + Ok(hex::encode(hasher.finalize())) } } diff --git a/trifid-pki/src/cert_codec.rs b/trifid-pki/src/cert_codec.rs index 7710326..2924b53 100644 --- a/trifid-pki/src/cert_codec.rs +++ b/trifid-pki/src/cert_codec.rs @@ -7,7 +7,7 @@ #![allow(unknown_lints)] #![allow(clippy::all)] #![cfg_attr(rustfmt, rustfmt_skip)] -#![allow(clippy::wildcard_imports)] +#![allow(clippy::pedantic)] use quick_protobuf::{MessageInfo, MessageRead, MessageWrite, BytesReader, Writer, WriterBackend, Result}; use quick_protobuf::sizeofs::*; @@ -36,17 +36,17 @@ impl<'a> MessageRead<'a> for RawNebulaCertificate { } impl MessageWrite for RawNebulaCertificate { - fn write_message(&self, w: &mut Writer) -> 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()) } } + + fn write_message(&self, w: &mut Writer) -> 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(()) + } } #[allow(clippy::derive_partial_eq_without_eq)] @@ -85,12 +85,24 @@ impl<'a> MessageRead<'a> for RawNebulaCertificateDetails { } } -#[allow(clippy::cast_sign_loss)] impl MessageWrite for RawNebulaCertificateDetails { + 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(*(s) as u64)).sum::()) } + + if self.Subnets.is_empty() { 0 } else { 1 + sizeof_len(self.Subnets.iter().map(|s| sizeof_varint(*(s) as u64)).sum::()) } + + self.Groups.iter().map(|s| 1 + sizeof_len((s).len())).sum::() + + 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(*(&self.IsCA) as u64) } + + if self.Issuer.is_empty() { 0 } else { 1 + sizeof_len((&self.Issuer).len()) } + } + fn write_message(&self, w: &mut Writer) -> 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))))?; + w.write_packed_with_tag(18, &self.Ips, |w, m| w.write_uint32(*m), &|m| sizeof_varint(*(m) as u64))?; + w.write_packed_with_tag(26, &self.Subnets, |w, m| w.write_uint32(*m), &|m| sizeof_varint(*(m) as u64))?; 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))?; } @@ -99,18 +111,5 @@ impl MessageWrite for RawNebulaCertificateDetails { 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::()) } - + if self.Subnets.is_empty() { 0 } else { 1 + sizeof_len(self.Subnets.iter().map(|s| sizeof_varint(u64::from(*(s)))).sum::()) } - + self.Groups.iter().map(|s| 1 + sizeof_len((s).len())).sum::() - + 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()) } - } } diff --git a/trifid-pki/src/lib.rs b/trifid-pki/src/lib.rs index 2a2454c..c388d11 100644 --- a/trifid-pki/src/lib.rs +++ b/trifid-pki/src/lib.rs @@ -17,6 +17,8 @@ #![allow(clippy::module_name_repetitions)] +extern crate core; + pub mod ca; pub mod cert; pub(crate) mod cert_codec; diff --git a/trifid-pki/src/test.rs b/trifid-pki/src/test.rs index 8ac70df..d24127f 100644 --- a/trifid-pki/src/test.rs +++ b/trifid-pki/src/test.rs @@ -1,17 +1,22 @@ #![allow(clippy::unwrap_used)] #![allow(clippy::expect_used)] +use std::fs; use crate::netmask; use std::net::Ipv4Addr; -use std::time::{Duration, SystemTime}; +use std::ops::Add; +use std::time::{Duration, SystemTime, SystemTimeError, UNIX_EPOCH}; use ipnet::Ipv4Net; use crate::cert::{deserialize_nebula_certificate, NebulaCertificate, NebulaCertificateDetails}; use std::str::FromStr; +/// 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 = SystemTime::now() - Duration::from_secs(60); - let after = SystemTime::now() + Duration::from_secs(60); + 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 { @@ -20,10 +25,10 @@ fn certificate_serialization() { 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") + netmask!("10.1.1.3", "255.0.0.0") ], subnets: vec![ - netmask!("9.1.1.1", "255.0.255.0"), + 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") ], @@ -32,13 +37,15 @@ fn certificate_serialization() { not_after: after, public_key: *pub_key, is_ca: false, - issuer: "1234567890abcedfghij1234567890ab".to_string(), + issuer: "1234567890abcedfabcd1234567890ab".to_string(), }, signature: b"1234567890abcedfghij1234567890ab".to_vec(), }; let bytes = cert.serialize().unwrap(); + fs::write("bad.hex.crt", hex::encode(bytes.clone())).unwrap(); + let deserialized = deserialize_nebula_certificate(&bytes).unwrap(); /* assert.Equal(t, nc.Details.Name, nc2.Details.Name) @@ -49,7 +56,9 @@ assert.Equal(t, nc.Details.Name, nc2.Details.Name) */ assert_eq!(cert.signature, deserialized.signature); assert_eq!(cert.details.name, deserialized.details.name); - assert_eq!(cert.details.not_before, deserialized.details.not_after); + 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); } #[macro_export] @@ -57,4 +66,9 @@ 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))) } \ No newline at end of file diff --git a/trifid-pki/test_cert.crt b/trifid-pki/test_cert.crt new file mode 100644 index 0000000..451ee14 --- /dev/null +++ b/trifid-pki/test_cert.crt @@ -0,0 +1,7 @@ +-----BEGIN NEBULA CERTIFICATE----- +zwEKqgEKB3Rlc3RpbmcSG4GChFCA/v//D4KChFCAgPz/D4OChFCAgID4DxobgYKE +SID///8PgoKESID+//8Pg4KESICA/P8PIgt0ZXN0LWdyb3VwMSILdGVzdC1ncm91 +cDIiC3Rlc3QtZ3JvdXAzKLDU8p8GMKjV8p8GOiAxMjM0NTY3ODkwYWJjZWRmZ2hp +ajEyMzQ1Njc4OTBhYkoQEjRWeJCrzt+rzRI0VniQqxIgMTIzNDU2Nzg5MGFiY2Vk +ZmdoaWoxMjM0NTY3ODkwYWI= +-----END NEBULA CERTIFICATE-----