trifid_pki/
cert.rs

1//! Manage Nebula PKI Certificates
2//! This is pretty much a direct port of nebula/cert/cert.go
3
4use crate::ca::NebulaCAPool;
5use crate::cert_codec::{RawNebulaCertificate, RawNebulaCertificateDetails};
6use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
7use ipnet::Ipv4Net;
8use pem::Pem;
9use quick_protobuf::{BytesReader, MessageRead, MessageWrite, Writer};
10use sha2::Digest;
11use sha2::Sha256;
12use std::error::Error;
13use std::fmt::{Display, Formatter};
14use std::net::Ipv4Addr;
15use std::ops::Add;
16use std::time::{Duration, SystemTime, UNIX_EPOCH};
17
18#[cfg(feature = "serde_derive")]
19use serde::{Deserialize, Serialize};
20
21/// The length, in bytes, of public keys
22pub const PUBLIC_KEY_LENGTH: i32 = 32;
23
24/// The PEM banner for certificates
25pub const CERT_BANNER: &str = "NEBULA CERTIFICATE";
26/// The PEM banner for X25519 private keys
27pub const X25519_PRIVATE_KEY_BANNER: &str = "NEBULA X25519 PRIVATE KEY";
28/// The PEM banner for X25519 public keys
29pub const X25519_PUBLIC_KEY_BANNER: &str = "NEBULA X25519 PUBLIC KEY";
30/// The PEM banner for Ed25519 private keys
31pub const ED25519_PRIVATE_KEY_BANNER: &str = "NEBULA ED25519 PRIVATE KEY";
32/// The PEM banner for Ed25519 public keys
33pub const ED25519_PUBLIC_KEY_BANNER: &str = "NEBULA ED25519 PUBLIC KEY";
34
35/// A Nebula PKI certificate
36#[derive(Debug, Clone)]
37#[cfg_attr(feature = "serde_derive", derive(Serialize, Deserialize))]
38pub struct NebulaCertificate {
39    /// The signed data of this certificate
40    pub details: NebulaCertificateDetails,
41    /// The Ed25519 signature of this certificate
42    pub signature: Vec<u8>,
43}
44
45/// The signed details contained in a Nebula PKI certificate
46#[derive(Debug, Clone)]
47#[cfg_attr(feature = "serde_derive", derive(Serialize, Deserialize))]
48pub struct NebulaCertificateDetails {
49    /// The name of the identity this certificate was issued for
50    pub name: String,
51    /// The IPv4 addresses issued to this node
52    pub ips: Vec<Ipv4Net>,
53    /// The IPv4 subnets this node is responsible for routing
54    pub subnets: Vec<Ipv4Net>,
55    /// The groups this node is a part of
56    pub groups: Vec<String>,
57    /// Certificate start date and time
58    pub not_before: SystemTime,
59    /// Certificate expiry date and time
60    pub not_after: SystemTime,
61    /// Public key
62    pub public_key: [u8; PUBLIC_KEY_LENGTH as usize],
63    /// Is this node a CA?
64    pub is_ca: bool,
65    /// SHA256 of issuer certificate. If blank, this cert is self-signed.
66    pub issuer: String,
67}
68
69/// A list of errors that can occur parsing certificates
70#[derive(Debug)]
71#[cfg_attr(feature = "serde_derive", derive(Serialize, Deserialize))]
72pub enum CertificateError {
73    /// Attempted to deserialize a certificate from an empty byte array
74    EmptyByteArray,
75    /// The encoded Details field is null
76    NilDetails,
77    /// The encoded Ips field is not formatted correctly
78    IpsNotPairs,
79    /// The encoded Subnets field is not formatted correctly
80    SubnetsNotPairs,
81    /// Signatures are expected to be 64 bytes but the signature on the certificate was a different length
82    WrongSigLength,
83    /// Public keys are expected to be 32 bytes but the public key on this cert is not
84    WrongKeyLength,
85    /// Certificates should have the PEM tag `NEBULA CERTIFICATE`, but this block did not
86    WrongPemTag,
87    /// This certificate either is not yet valid or has already expired
88    Expired,
89    /// The public key does not match the expected value
90    KeyMismatch,
91}
92#[cfg(not(tarpaulin_include))]
93impl Display for CertificateError {
94    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
95        match self {
96            Self::EmptyByteArray => write!(f, "Certificate bytearray is empty"),
97            Self::NilDetails => write!(f, "The encoded Details field is null"),
98            Self::IpsNotPairs => {
99                write!(f, "encoded IPs should be in pairs, an odd number was found")
100            }
101            Self::SubnetsNotPairs => write!(
102                f,
103                "encoded subnets should be in pairs, an odd number was found"
104            ),
105            Self::WrongSigLength => {
106                write!(f, "Signature should be 64 bytes but is a different size")
107            }
108            Self::WrongKeyLength => write!(
109                f,
110                "Public keys are expected to be 32 bytes but the public key on this cert is not"
111            ),
112            Self::WrongPemTag => write!(
113                f,
114                "Certificates should have the PEM tag `NEBULA CERTIFICATE`, but this block did not"
115            ),
116            Self::Expired => write!(
117                f,
118                "This certificate either is not yet valid or has already expired"
119            ),
120            Self::KeyMismatch => write!(f, "Key does not match expected value"),
121        }
122    }
123}
124impl Error for CertificateError {}
125
126fn map_cidr_pairs(pairs: &[u32]) -> Result<Vec<Ipv4Net>, Box<dyn Error>> {
127    let mut res_vec = vec![];
128    for pair in pairs.chunks(2) {
129        res_vec.push(Ipv4Net::with_netmask(
130            Ipv4Addr::from(pair[0]),
131            Ipv4Addr::from(pair[1]),
132        )?);
133    }
134    Ok(res_vec)
135}
136
137#[cfg(not(tarpaulin_include))]
138impl Display for NebulaCertificate {
139    #[allow(clippy::unwrap_used)]
140    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
141        writeln!(f, "NebulaCertificate {{")?;
142        writeln!(f, "   Details {{")?;
143        writeln!(f, "       Name: {}", self.details.name)?;
144        writeln!(f, "       Ips: {:?}", self.details.ips)?;
145        writeln!(f, "       Subnets: {:?}", self.details.subnets)?;
146        writeln!(f, "       Groups: {:?}", self.details.groups)?;
147        writeln!(f, "       Not before: {:?}", self.details.not_before)?;
148        writeln!(f, "       Not after: {:?}", self.details.not_after)?;
149        writeln!(f, "       Is CA: {}", self.details.is_ca)?;
150        writeln!(f, "       Issuer: {}", self.details.issuer)?;
151        writeln!(
152            f,
153            "       Public key: {}",
154            hex::encode(self.details.public_key)
155        )?;
156        writeln!(f, "   }}")?;
157        writeln!(f, "   Fingerprint: {}", self.sha256sum().unwrap())?;
158        writeln!(f, "   Signature: {}", hex::encode(self.signature.clone()))?;
159        writeln!(f, "}}")
160    }
161}
162
163/// Given a protobuf-encoded certificate bytearray, deserialize it into a `NebulaCertificate` object.
164/// # Errors
165/// This function will return an error if there is a protobuf parsing error, or if the certificate data is invalid.
166/// # Panics
167pub fn deserialize_nebula_certificate(bytes: &[u8]) -> Result<NebulaCertificate, Box<dyn Error>> {
168    if bytes.is_empty() {
169        return Err(CertificateError::EmptyByteArray.into());
170    }
171
172    let mut reader = BytesReader::from_bytes(bytes);
173
174    let raw_cert = RawNebulaCertificate::from_reader(&mut reader, bytes)?;
175
176    let details = raw_cert.Details.ok_or(CertificateError::NilDetails)?;
177
178    if details.Ips.len() % 2 != 0 {
179        return Err(CertificateError::IpsNotPairs.into());
180    }
181
182    if details.Subnets.len() % 2 != 0 {
183        return Err(CertificateError::SubnetsNotPairs.into());
184    }
185
186    let mut nebula_cert;
187    #[allow(clippy::cast_sign_loss)]
188    {
189        nebula_cert = NebulaCertificate {
190            details: NebulaCertificateDetails {
191                name: details.Name.to_string(),
192                ips: map_cidr_pairs(&details.Ips)?,
193                subnets: map_cidr_pairs(&details.Subnets)?,
194                groups: details
195                    .Groups
196                    .iter()
197                    .map(std::string::ToString::to_string)
198                    .collect(),
199                not_before: SystemTime::UNIX_EPOCH
200                    .add(Duration::from_secs(details.NotBefore as u64)),
201                not_after: SystemTime::UNIX_EPOCH.add(Duration::from_secs(details.NotAfter as u64)),
202                public_key: [0u8; 32],
203                is_ca: details.IsCA,
204                issuer: hex::encode(details.Issuer),
205            },
206            signature: vec![],
207        };
208    }
209
210    nebula_cert.signature = raw_cert.Signature;
211
212    if details.PublicKey.len() != 32 {
213        return Err(CertificateError::WrongKeyLength.into());
214    }
215
216    #[allow(clippy::unwrap_used)]
217    {
218        nebula_cert.details.public_key = details.PublicKey.try_into().unwrap();
219    }
220
221    Ok(nebula_cert)
222}
223
224/// A list of errors that can occur parsing keys
225#[derive(Debug)]
226#[cfg_attr(feature = "serde_derive", derive(Serialize, Deserialize))]
227pub enum KeyError {
228    /// Keys should have their associated PEM tags but this had the wrong one
229    WrongPemTag,
230    /// Ed25519 private keys are 64 bytes
231    Not64Bytes,
232    /// X25519 private keys are 32 bytes
233    Not32Bytes,
234}
235#[cfg(not(tarpaulin_include))]
236impl Display for KeyError {
237    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
238        match self {
239            Self::WrongPemTag => write!(
240                f,
241                "Keys should have their associated PEM tags but this had the wrong one"
242            ),
243            Self::Not64Bytes => write!(f, "Ed25519 private keys are 64 bytes"),
244            Self::Not32Bytes => write!(f, "X25519 private keys are 32 bytes"),
245        }
246    }
247}
248impl Error for KeyError {}
249
250/// Deserialize the first PEM block in the given byte array into a `NebulaCertificate`
251/// # Errors
252/// 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`)
253pub fn deserialize_nebula_certificate_from_pem(
254    bytes: &[u8],
255) -> Result<NebulaCertificate, Box<dyn Error>> {
256    let pem = pem::parse(bytes)?;
257    if pem.tag != CERT_BANNER {
258        return Err(CertificateError::WrongPemTag.into());
259    }
260    deserialize_nebula_certificate(&pem.contents)
261}
262
263/// Simple helper to PEM encode an X25519 private key
264pub fn serialize_x25519_private(bytes: &[u8]) -> Vec<u8> {
265    pem::encode(&Pem {
266        tag: X25519_PRIVATE_KEY_BANNER.to_string(),
267        contents: bytes.to_vec(),
268    })
269    .as_bytes()
270    .to_vec()
271}
272
273/// Simple helper to PEM encode an X25519 public key
274pub fn serialize_x25519_public(bytes: &[u8]) -> Vec<u8> {
275    pem::encode(&Pem {
276        tag: X25519_PUBLIC_KEY_BANNER.to_string(),
277        contents: bytes.to_vec(),
278    })
279    .as_bytes()
280    .to_vec()
281}
282
283/// Attempt to deserialize a PEM encoded X25519 private key
284/// # Errors
285/// This function will return an error if the PEM data is invalid or has the wrong tag
286pub fn deserialize_x25519_private(bytes: &[u8]) -> Result<Vec<u8>, Box<dyn Error>> {
287    let pem = pem::parse(bytes)?;
288    if pem.tag != X25519_PRIVATE_KEY_BANNER {
289        return Err(KeyError::WrongPemTag.into());
290    }
291    if pem.contents.len() != 32 {
292        return Err(KeyError::Not32Bytes.into());
293    }
294    Ok(pem.contents)
295}
296
297/// Attempt to deserialize a PEM encoded X25519 public key
298/// # Errors
299/// This function will return an error if the PEM data is invalid or has the wrong tag
300pub fn deserialize_x25519_public(bytes: &[u8]) -> Result<Vec<u8>, Box<dyn Error>> {
301    let pem = pem::parse(bytes)?;
302    if pem.tag != X25519_PUBLIC_KEY_BANNER {
303        return Err(KeyError::WrongPemTag.into());
304    }
305    if pem.contents.len() != 32 {
306        return Err(KeyError::Not32Bytes.into());
307    }
308    Ok(pem.contents)
309}
310
311/// Simple helper to PEM encode an Ed25519 private key
312pub fn serialize_ed25519_private(bytes: &[u8]) -> Vec<u8> {
313    pem::encode(&Pem {
314        tag: ED25519_PRIVATE_KEY_BANNER.to_string(),
315        contents: bytes.to_vec(),
316    })
317    .as_bytes()
318    .to_vec()
319}
320
321/// Simple helper to PEM encode an Ed25519 public key
322pub fn serialize_ed25519_public(bytes: &[u8]) -> Vec<u8> {
323    pem::encode(&Pem {
324        tag: ED25519_PUBLIC_KEY_BANNER.to_string(),
325        contents: bytes.to_vec(),
326    })
327    .as_bytes()
328    .to_vec()
329}
330
331/// Attempt to deserialize a PEM encoded Ed25519 private key
332/// # Errors
333/// This function will return an error if the PEM data is invalid or has the wrong tag
334pub fn deserialize_ed25519_private(bytes: &[u8]) -> Result<Vec<u8>, Box<dyn Error>> {
335    let pem = pem::parse(bytes)?;
336    if pem.tag != ED25519_PRIVATE_KEY_BANNER {
337        return Err(KeyError::WrongPemTag.into());
338    }
339    if pem.contents.len() != 64 {
340        return Err(KeyError::Not64Bytes.into());
341    }
342    Ok(pem.contents)
343}
344
345/// Attempt to deserialize a PEM encoded Ed25519 public key
346/// # Errors
347/// This function will return an error if the PEM data is invalid or has the wrong tag
348pub fn deserialize_ed25519_public(bytes: &[u8]) -> Result<Vec<u8>, Box<dyn Error>> {
349    let pem = pem::parse(bytes)?;
350    if pem.tag != ED25519_PUBLIC_KEY_BANNER {
351        return Err(KeyError::WrongPemTag.into());
352    }
353    if pem.contents.len() != 32 {
354        return Err(KeyError::Not32Bytes.into());
355    }
356    Ok(pem.contents)
357}
358
359/// Attempt to deserialize multiple PEM encoded Ed25519 public keys
360/// # Errors
361/// This function will return an error if the PEM data is invalid or has the wrong tag
362pub fn deserialize_ed25519_public_many(bytes: &[u8]) -> Result<Vec<Vec<u8>>, Box<dyn Error>> {
363    let mut keys = vec![];
364    let pems = pem::parse_many(bytes)?;
365
366    for pem in pems {
367        if pem.tag != ED25519_PUBLIC_KEY_BANNER {
368            return Err(KeyError::WrongPemTag.into());
369        }
370        if pem.contents.len() != 32 {
371            return Err(KeyError::Not32Bytes.into());
372        }
373        keys.push(pem.contents);
374    }
375
376    Ok(keys)
377}
378
379impl NebulaCertificate {
380    /// Sign a nebula certificate with the provided private key
381    /// # Errors
382    /// This function will return an error if the certificate could not be serialized or signed.
383    pub fn sign(&mut self, key: &SigningKey) -> Result<(), Box<dyn Error>> {
384        let mut out = Vec::new();
385        let mut writer = Writer::new(&mut out);
386        self.get_raw_details().write_message(&mut writer)?;
387
388        self.signature = key.sign(&out).to_vec();
389        Ok(())
390    }
391
392    /// Verify the signature on a certificate with the provided public key
393    /// # Errors
394    /// This function will return an error if the certificate could not be serialized or the signature could not be checked.
395    pub fn check_signature(&self, key: &VerifyingKey) -> Result<bool, Box<dyn Error>> {
396        let mut out = Vec::new();
397        let mut writer = Writer::new(&mut out);
398        self.get_raw_details().write_message(&mut writer)?;
399
400        let sig = Signature::from_slice(&self.signature)?;
401
402        Ok(key.verify(&out, &sig).is_ok())
403    }
404
405    /// Returns true if the signature is too young or too old compared to the provided time
406    pub fn expired(&self, time: SystemTime) -> bool {
407        self.details.not_before > time || self.details.not_after < time
408    }
409
410    /// Verify will ensure a certificate is good in all respects (expiry, group membership, signature, cert blocklist, etc)
411    /// # Errors
412    /// This function will return an error if there is an error parsing the cert or the CA pool.
413    pub fn verify(
414        &self,
415        time: SystemTime,
416        ca_pool: &NebulaCAPool,
417    ) -> Result<CertificateValidity, Box<dyn Error>> {
418        if ca_pool.is_blocklisted(self) {
419            return Ok(CertificateValidity::Blocklisted);
420        }
421
422        let Some(signer) = ca_pool.get_ca_for_cert(self)? else { return Ok(CertificateValidity::NotSignedByThisCAPool) };
423
424        if signer.expired(time) {
425            return Ok(CertificateValidity::RootCertExpired);
426        }
427
428        if self.expired(time) {
429            return Ok(CertificateValidity::CertExpired);
430        }
431
432        if !self.check_signature(&VerifyingKey::from_bytes(&signer.details.public_key)?)? {
433            return Ok(CertificateValidity::BadSignature);
434        }
435
436        Ok(self.check_root_constraints(signer))
437    }
438
439    /// Make sure that this certificate does not break any of the constraints set by the signing certificate
440    pub fn check_root_constraints(&self, signer: &Self) -> CertificateValidity {
441        // Make sure this cert doesn't expire after the signer
442        println!(
443            "{:?} {:?}",
444            signer.details.not_before, self.details.not_before
445        );
446        if signer.details.not_before < self.details.not_before {
447            return CertificateValidity::CertExpiresAfterSigner;
448        }
449
450        // Make sure this cert doesn't come into validity before the root
451        if signer.details.not_before > self.details.not_before {
452            return CertificateValidity::CertValidBeforeSigner;
453        }
454
455        // If the signer contains a limited set of groups, make sure this cert only has a subset of them
456        if !signer.details.groups.is_empty() {
457            println!(
458                "root groups: {:?}, child groups: {:?}",
459                signer.details.groups, self.details.groups
460            );
461            for group in &self.details.groups {
462                if !signer.details.groups.contains(group) {
463                    return CertificateValidity::GroupNotPresentOnSigner;
464                }
465            }
466        }
467
468        // If the signer contains a limited set of IP ranges, make sure the cert only contains a subset
469        if !signer.details.ips.is_empty() {
470            for ip in &self.details.ips {
471                if !net_match(*ip, &signer.details.ips) {
472                    return CertificateValidity::IPNotPresentOnSigner;
473                }
474            }
475        }
476
477        // If the signer contains a limited set of subnets, make sure the cert only contains a subset
478        if !signer.details.subnets.is_empty() {
479            for subnet in &self.details.subnets {
480                if !net_match(*subnet, &signer.details.subnets) {
481                    return CertificateValidity::SubnetNotPresentOnSigner;
482                }
483            }
484        }
485
486        CertificateValidity::Ok
487    }
488
489    #[allow(clippy::unwrap_used)]
490    /// Verify if the given private key corresponds to the public key contained in this certificate
491    /// # Errors
492    /// This function will return an error if either keys are invalid.
493    /// # Panics
494    /// This function, while containing calls to unwrap, has proper bounds checking and will not panic.
495    pub fn verify_private_key(&self, key: &[u8]) -> Result<(), Box<dyn Error>> {
496        if self.details.is_ca {
497            // convert the keys
498            if key.len() != 64 {
499                return Err("key not 64-bytes long".into());
500            }
501
502            let secret = SigningKey::from_keypair_bytes(key.try_into().unwrap())?;
503            let pub_key = secret.verifying_key().to_bytes();
504            if pub_key != self.details.public_key {
505                return Err(CertificateError::KeyMismatch.into());
506            }
507
508            return Ok(());
509        }
510
511        if key.len() != 32 {
512            return Err("key not 32-bytes long".into());
513        }
514
515        let pubkey_raw = SigningKey::from_bytes(key.try_into()?).verifying_key();
516        let pubkey = pubkey_raw.as_bytes();
517
518        println!(
519            "{} {}",
520            hex::encode(pubkey),
521            hex::encode(self.details.public_key)
522        );
523        if *pubkey != self.details.public_key {
524            return Err(CertificateError::KeyMismatch.into());
525        }
526
527        Ok(())
528    }
529
530    /// Get a protobuf-ready raw struct, ready for serialization
531    #[allow(clippy::expect_used)]
532    #[allow(clippy::cast_possible_wrap)]
533    /// # Panics
534    /// This function will panic if time went backwards, or if the certificate contains extremely invalid data.
535    pub fn get_raw_details(&self) -> RawNebulaCertificateDetails {
536        let mut raw = RawNebulaCertificateDetails {
537            Name: self.details.name.clone(),
538            Ips: vec![],
539            Subnets: vec![],
540            Groups: self
541                .details
542                .groups
543                .iter()
544                .map(std::convert::Into::into)
545                .collect(),
546            NotBefore: self
547                .details
548                .not_before
549                .duration_since(UNIX_EPOCH)
550                .expect("Time went backwards")
551                .as_secs() as i64,
552            NotAfter: self
553                .details
554                .not_after
555                .duration_since(UNIX_EPOCH)
556                .expect("Time went backwards")
557                .as_secs() as i64,
558            PublicKey: self.details.public_key.into(),
559            IsCA: self.details.is_ca,
560            Issuer: hex::decode(&self.details.issuer).expect("Issuer was not a hex-encoded value"),
561        };
562
563        for ip_net in &self.details.ips {
564            raw.Ips.push(ip_net.addr().into());
565            raw.Ips.push(ip_net.netmask().into());
566        }
567
568        for subnet in &self.details.subnets {
569            raw.Subnets.push(subnet.addr().into());
570            raw.Subnets.push(subnet.netmask().into());
571        }
572
573        raw
574    }
575
576    /// Will serialize this cert into a protobuf byte array.
577    /// # Errors
578    /// This function will return an error if protobuf was unable to serialize the data.
579    pub fn serialize(&self) -> Result<Vec<u8>, Box<dyn Error>> {
580        let raw_cert = RawNebulaCertificate {
581            Details: Some(self.get_raw_details()),
582            Signature: self.signature.clone(),
583        };
584
585        let mut out = vec![];
586        let mut writer = Writer::new(&mut out);
587        raw_cert.write_message(&mut writer)?;
588
589        Ok(out)
590    }
591
592    /// Will serialize this cert into a PEM byte array.
593    /// # Errors
594    /// This function will return an error if protobuf was unable to serialize the data.
595    pub fn serialize_to_pem(&self) -> Result<Vec<u8>, Box<dyn Error>> {
596        let pbuf_bytes = self.serialize()?;
597
598        Ok(pem::encode(&Pem {
599            tag: CERT_BANNER.to_string(),
600            contents: pbuf_bytes,
601        })
602        .as_bytes()
603        .to_vec())
604    }
605
606    /// Get the fingerprint of this certificate
607    /// # Errors
608    /// This functiom will return an error if protobuf was unable to serialize the cert.
609    pub fn sha256sum(&self) -> Result<String, Box<dyn Error>> {
610        let pbuf_bytes = self.serialize()?;
611
612        let mut hasher = Sha256::new();
613        hasher.update(pbuf_bytes);
614
615        Ok(hex::encode(hasher.finalize()))
616    }
617}
618
619/// A list of possible errors that can happen validating a certificate
620#[derive(Eq, PartialEq, Debug)]
621#[cfg_attr(feature = "serde_derive", derive(Serialize, Deserialize))]
622pub enum CertificateValidity {
623    /// There are no issues with this certificate
624    Ok,
625    /// This cert has been blocklisted in the given CA pool
626    Blocklisted,
627    /// The certificate that signed this cert is expired
628    RootCertExpired,
629    /// This cert is expired
630    CertExpired,
631    /// This cert's signature is invalid
632    BadSignature,
633    /// This cert was not signed by any CAs in the CA pool
634    NotSignedByThisCAPool,
635    /// This cert expires after the signer's cert expires
636    CertExpiresAfterSigner,
637    /// This cert enters validity before the signer's cert does
638    CertValidBeforeSigner,
639    /// A group present on this certificate is not present on the signer's certificate
640    GroupNotPresentOnSigner,
641    /// An IP present on this certificate is not present on the signer's certificate
642    IPNotPresentOnSigner,
643    /// A subnet on this certificate is not present on the signer's certificate
644    SubnetNotPresentOnSigner,
645}
646
647fn net_match(cert_ip: Ipv4Net, root_ips: &Vec<Ipv4Net>) -> bool {
648    for net in root_ips {
649        if net.contains(&cert_ip) {
650            return true;
651        }
652    }
653    false
654}