655 lines
23 KiB
Rust
655 lines
23 KiB
Rust
//! Manage Nebula PKI Certificates
|
|
//! This is pretty much a direct port of nebula/cert/cert.go
|
|
|
|
use crate::ca::NebulaCAPool;
|
|
use crate::cert_codec::{RawNebulaCertificate, RawNebulaCertificateDetails};
|
|
use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
|
|
use ipnet::Ipv4Net;
|
|
use pem::Pem;
|
|
use quick_protobuf::{BytesReader, MessageRead, MessageWrite, Writer};
|
|
use sha2::Digest;
|
|
use sha2::Sha256;
|
|
use std::error::Error;
|
|
use std::fmt::{Display, Formatter};
|
|
use std::net::Ipv4Addr;
|
|
use std::ops::Add;
|
|
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
|
|
|
#[cfg(feature = "serde_derive")]
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
/// 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
|
|
#[derive(Debug, Clone)]
|
|
#[cfg_attr(feature = "serde_derive", derive(Serialize, Deserialize))]
|
|
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
|
|
#[derive(Debug, Clone)]
|
|
#[cfg_attr(feature = "serde_derive", derive(Serialize, Deserialize))]
|
|
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)]
|
|
#[cfg_attr(feature = "serde_derive", derive(Serialize, Deserialize))]
|
|
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,
|
|
}
|
|
#[cfg(not(tarpaulin_include))]
|
|
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)
|
|
}
|
|
|
|
#[cfg(not(tarpaulin_include))]
|
|
impl Display for NebulaCertificate {
|
|
#[allow(clippy::unwrap_used)]
|
|
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.
|
|
/// # 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)]
|
|
#[cfg_attr(feature = "serde_derive", derive(Serialize, Deserialize))]
|
|
pub enum KeyError {
|
|
/// Keys should have their associated PEM tags but this had the wrong one
|
|
WrongPemTag,
|
|
/// Ed25519 private keys are 64 bytes
|
|
Not64Bytes,
|
|
/// X25519 private keys are 32 bytes
|
|
Not32Bytes,
|
|
}
|
|
#[cfg(not(tarpaulin_include))]
|
|
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"
|
|
),
|
|
Self::Not64Bytes => write!(f, "Ed25519 private keys are 64 bytes"),
|
|
Self::Not32Bytes => write!(f, "X25519 private keys are 32 bytes"),
|
|
}
|
|
}
|
|
}
|
|
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());
|
|
}
|
|
if pem.contents.len() != 32 {
|
|
return Err(KeyError::Not32Bytes.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());
|
|
}
|
|
if pem.contents.len() != 32 {
|
|
return Err(KeyError::Not32Bytes.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());
|
|
}
|
|
if pem.contents.len() != 64 {
|
|
return Err(KeyError::Not64Bytes.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());
|
|
}
|
|
if pem.contents.len() != 32 {
|
|
return Err(KeyError::Not32Bytes.into());
|
|
}
|
|
Ok(pem.contents)
|
|
}
|
|
|
|
/// Attempt to deserialize multiple PEM encoded Ed25519 public keys
|
|
/// # Errors
|
|
/// This function will return an error if the PEM data is invalid or has the wrong tag
|
|
pub fn deserialize_ed25519_public_many(bytes: &[u8]) -> Result<Vec<Vec<u8>>, Box<dyn Error>> {
|
|
let mut keys = vec![];
|
|
let pems = pem::parse_many(bytes)?;
|
|
|
|
for pem in pems {
|
|
if pem.tag != ED25519_PUBLIC_KEY_BANNER {
|
|
return Err(KeyError::WrongPemTag.into());
|
|
}
|
|
if pem.contents.len() != 32 {
|
|
return Err(KeyError::Not32Bytes.into());
|
|
}
|
|
keys.push(pem.contents);
|
|
}
|
|
|
|
Ok(keys)
|
|
}
|
|
|
|
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);
|
|
self.get_raw_details().write_message(&mut writer)?;
|
|
|
|
self.signature = key.sign(&out).to_vec();
|
|
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);
|
|
self.get_raw_details().write_message(&mut writer)?;
|
|
|
|
let sig = Signature::from_slice(&self.signature)?;
|
|
|
|
Ok(key.verify(&out, &sig).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
|
|
println!(
|
|
"{:?} {:?}",
|
|
signer.details.not_before, self.details.not_before
|
|
);
|
|
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() {
|
|
println!(
|
|
"root groups: {:?}, child groups: {:?}",
|
|
signer.details.groups, self.details.groups
|
|
);
|
|
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 contained in 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 secret = SigningKey::from_keypair_bytes(key.try_into().unwrap())?;
|
|
let pub_key = secret.verifying_key().to_bytes();
|
|
if pub_key != 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_raw = SigningKey::from_bytes(key.try_into()?).verifying_key();
|
|
let pubkey = pubkey_raw.as_bytes();
|
|
|
|
println!(
|
|
"{} {}",
|
|
hex::encode(pubkey),
|
|
hex::encode(self.details.public_key)
|
|
);
|
|
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);
|
|
raw_cert.write_message(&mut writer)?;
|
|
|
|
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()?;
|
|
|
|
let mut hasher = Sha256::new();
|
|
hasher.update(pbuf_bytes);
|
|
|
|
Ok(hex::encode(hasher.finalize()))
|
|
}
|
|
}
|
|
|
|
/// A list of possible errors that can happen validating a certificate
|
|
#[derive(Eq, PartialEq, Debug)]
|
|
#[cfg_attr(feature = "serde_derive", derive(Serialize, Deserialize))]
|
|
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
|
|
}
|