Skip to main content

opcua_crypto/
x509.rs

1// OPCUA for Rust
2// SPDX-License-Identifier: MPL-2.0
3// Copyright (C) 2017-2024 Adam Lock
4
5//! Wrapper for X509 certificates, and related tooling.
6
7use std::{
8    self,
9    collections::HashSet,
10    fmt::{self, Debug, Formatter},
11    net::{Ipv4Addr, Ipv6Addr, SocketAddr, ToSocketAddrs},
12    result::Result,
13};
14
15use chrono::{DateTime, Utc};
16use tracing::{error, info, trace, warn};
17type ChronoUtc = DateTime<Utc>;
18
19use rsa::pkcs1v15;
20use rsa::RsaPublicKey;
21use x509_cert::{
22    self as x509,
23    der::asn1::{Ia5String, OctetString},
24    ext::pkix::name::GeneralName,
25};
26
27use x509::builder::Error as BuilderError;
28use x509::ext::pkix::name as xname;
29
30use opcua_types::{status_code::StatusCode, ApplicationDescription, ByteString, Error};
31
32use crate::{KeySize, PrivateKey, PublicKey};
33
34use super::{hostname, thumbprint::Thumbprint};
35
36const DEFAULT_KEYSIZE: u32 = 2048;
37const DEFAULT_COUNTRY: &str = "IE";
38const DEFAULT_STATE: &str = "Dublin";
39
40#[derive(Debug, Default)]
41/// Alternate names for an X509 certificate.
42pub struct AlternateNames {
43    /// List of alternative names.
44    pub names: x509::ext::pkix::SubjectAltName,
45}
46
47impl AlternateNames {
48    /// Create a new `AlternateNames` struct with no contents.
49    pub fn new() -> Self {
50        use x509::ext::pkix::SubjectAltName;
51        Self {
52            names: SubjectAltName(xname::GeneralNames::new()),
53        }
54    }
55
56    /// Create a new list of alternate names from a list of addresses.
57    pub fn new_from_addresses(ads: Vec<String>) -> Self {
58        let mut result = Self::new();
59        result.add_addresses(&ads);
60        result
61    }
62
63    /// `true` if no alternate names are added.
64    pub fn is_empty(&self) -> bool {
65        self.names.0.is_empty()
66    }
67
68    /// Number of alternate names added.
69    pub fn len(&self) -> usize {
70        self.names.0.len()
71    }
72
73    /// Add an IPV4 address as alternate name.
74    pub fn add_ipv4(&mut self, ad: &std::net::Ipv4Addr) {
75        if let Ok(v) = x509::der::asn1::OctetString::new(ad.octets()) {
76            self.names.0.push(xname::GeneralName::IpAddress(v));
77        }
78    }
79
80    /// Add an IPV6 address as alternate name.
81    pub fn add_ipv6(&mut self, ad: &std::net::Ipv6Addr) {
82        if let Ok(v) = x509::der::asn1::OctetString::new(ad.octets()) {
83            self.names.0.push(xname::GeneralName::IpAddress(v))
84        }
85    }
86
87    /// Add a DNS name as alternate name.
88    pub fn add_dns(&mut self, v: impl AsRef<str>) {
89        if let Ok(v) = x509::der::asn1::Ia5String::new(v.as_ref()) {
90            self.names.0.push(xname::GeneralName::DnsName(v));
91        }
92    }
93
94    /// Add an IP or hostname.
95    pub fn add_address(&mut self, v: impl AsRef<str>) {
96        let v = v.as_ref();
97        {
98            if let Ok(ip) = v.parse::<std::net::Ipv4Addr>() {
99                self.add_ipv4(&ip);
100                return;
101            }
102        }
103        {
104            if let Ok(r) = v.parse::<std::net::Ipv6Addr>() {
105                self.add_ipv6(&r);
106                return;
107            }
108        }
109        self.add_dns(v);
110    }
111
112    /// Add a URI.
113    pub fn add_uri(&mut self, v: &str) {
114        if let Ok(uri) = Ia5String::new(v) {
115            self.names
116                .0
117                .push(xname::GeneralName::UniformResourceIdentifier(uri));
118        }
119    }
120
121    /// Add a list of addresses.
122    pub fn add_addresses(&mut self, ads: &[String]) {
123        ads.iter().for_each(|h| {
124            self.add_address(h);
125        })
126    }
127
128    fn convert_name(name: &x509::ext::pkix::name::GeneralName) -> Option<String> {
129        match name {
130            GeneralName::DnsName(val) => Some(val.to_string()),
131            GeneralName::DirectoryName(val) => Some(val.to_string()),
132            GeneralName::Rfc822Name(val) => Some(val.to_string()),
133            GeneralName::UniformResourceIdentifier(val) => Some(val.to_string()),
134            GeneralName::IpAddress(val) => {
135                let bytes = val.as_bytes();
136                match bytes.len() {
137                    4 => Some(Ipv4Addr::new(bytes[0], bytes[1], bytes[2], bytes[3]).to_string()),
138
139                    16 => {
140                        let a = ((bytes[0] as u16) << 8) | bytes[1] as u16;
141                        let b = ((bytes[2] as u16) << 8) | bytes[3] as u16;
142                        let c = ((bytes[4] as u16) << 8) | bytes[5] as u16;
143                        let d = ((bytes[6] as u16) << 8) | bytes[7] as u16;
144                        let e = ((bytes[8] as u16) << 8) | bytes[9] as u16;
145                        let f = ((bytes[10] as u16) << 8) | bytes[11] as u16;
146                        let g = ((bytes[12] as u16) << 8) | bytes[13] as u16;
147                        let h = ((bytes[14] as u16) << 8) | bytes[15] as u16;
148                        Some(Ipv6Addr::new(a, b, c, d, e, f, g, h).to_string())
149                    }
150                    _ => None,
151                }
152            }
153
154            _ => None,
155        }
156    }
157
158    /// Iterate over all the registered names.
159    pub fn iter(&self) -> impl Iterator<Item = String> + '_ {
160        AlternateNamesStringIterator {
161            source: &self.names.0,
162            index: 0,
163        }
164    }
165}
166
167struct AlternateNamesStringIterator<'a> {
168    source: &'a xname::GeneralNames,
169    index: usize,
170}
171
172impl Iterator for AlternateNamesStringIterator<'_> {
173    type Item = String;
174
175    fn next(&mut self) -> Option<Self::Item> {
176        if self.index < self.source.len() {
177            let converted = AlternateNames::convert_name(&self.source[self.index]);
178            self.index += 1;
179
180            match converted {
181                None => Some("".to_string()),
182                Some(val) => Some(val),
183            }
184        } else {
185            None
186        }
187    }
188}
189
190impl From<Vec<String>> for AlternateNames {
191    fn from(source: Vec<String>) -> Self {
192        Self::new_from_addresses(source)
193    }
194}
195
196/// Data for constructing an X509 certificate.
197pub struct X509Data {
198    /// Requested key size.
199    pub key_size: u32,
200    /// Certificate CN.
201    pub common_name: String,
202    /// Certificate organization.
203    pub organization: String,
204    /// Certificate organizational unit.
205    pub organizational_unit: String,
206    /// Certificate country.
207    pub country: String,
208    /// Certificate state.
209    pub state: String,
210    /// A list of alternate host names as text. The first entry is expected to be the application uri.
211    /// The remainder are treated as IP addresses or DNS names depending on whether they parse as IPv4, IPv6 or neither.
212    /// IP addresses are expected to be in their canonical form and you will run into trouble
213    /// especially in IPv6 if they are not because string comparison may be used during validation.
214    /// e.g. IPv6 canonical format shortens addresses by stripping leading zeros, sequences of zeros
215    /// and using lowercase hex.
216    pub alt_host_names: AlternateNames,
217    /// The number of days the certificate is valid for, i.e. it will be valid from now until now + duration_days.
218    pub certificate_duration_days: u32,
219}
220
221impl From<(ApplicationDescription, Option<Vec<String>>)> for X509Data {
222    fn from(v: (ApplicationDescription, Option<Vec<String>>)) -> Self {
223        let (application_description, addresses) = v;
224        let application_uri = application_description.application_uri.as_ref();
225        let mut alt_host_names = AlternateNames::new();
226        Self::compute_alt_host_names(
227            &mut alt_host_names,
228            application_uri,
229            addresses,
230            true,
231            true,
232            true,
233        );
234        X509Data {
235            key_size: DEFAULT_KEYSIZE,
236            common_name: application_description.application_name.to_string(),
237            organization: application_description.application_name.to_string(),
238            organizational_unit: application_description.application_name.to_string(),
239            country: DEFAULT_COUNTRY.to_string(),
240            state: DEFAULT_STATE.to_string(),
241            alt_host_names,
242            certificate_duration_days: 365,
243        }
244    }
245}
246
247impl From<ApplicationDescription> for X509Data {
248    fn from(v: ApplicationDescription) -> Self {
249        X509Data::from((v, None))
250    }
251}
252
253impl X509Data {
254    /// Gets a list of possible dns hostnames for this device
255    pub fn computer_hostnames() -> Vec<String> {
256        let mut result = Vec::with_capacity(2);
257
258        if let Ok(hostname) = hostname() {
259            if !hostname.is_empty() {
260                result.push(hostname);
261            }
262        }
263        if result.is_empty() {
264            // Look for environment vars
265            if let Ok(machine_name) = std::env::var("COMPUTERNAME") {
266                result.push(machine_name);
267            }
268            if let Ok(machine_name) = std::env::var("NAME") {
269                result.push(machine_name);
270            }
271        }
272
273        result
274    }
275
276    /// Create `AlternateNames` from the current host and application URI, with
277    /// an optional extra list of addresses.
278    pub fn alt_host_names(
279        application_uri: &str,
280        addresses: Option<Vec<String>>,
281        add_localhost: bool,
282        add_computer_name: bool,
283        add_ip_addresses: bool,
284    ) -> AlternateNames {
285        let mut result = AlternateNames::new();
286        Self::compute_alt_host_names(
287            &mut result,
288            application_uri,
289            addresses,
290            add_localhost,
291            add_computer_name,
292            add_ip_addresses,
293        );
294        result
295    }
296
297    /// Creates a list of uri + DNS hostnames using the supplied arguments
298    fn compute_alt_host_names(
299        result: &mut AlternateNames,
300        application_uri: &str,
301        addresses: Option<Vec<String>>,
302        add_localhost: bool,
303        add_computer_name: bool,
304        add_ip_addresses: bool,
305    ) {
306        // The first name is the application uri
307
308        result.add_uri(application_uri);
309
310        // Addresses supplied by caller
311        if let Some(addresses) = addresses {
312            result.add_addresses(&addresses);
313        }
314
315        // The remainder are alternative IP/DNS entries
316        if add_localhost {
317            result.add_address("localhost");
318            if add_ip_addresses {
319                result.add_address("127.0.0.1");
320                result.add_address("::1");
321            }
322        }
323        // Get the machine name / ip address
324        if add_computer_name {
325            let computer_hostnames = Self::computer_hostnames();
326            if add_ip_addresses {
327                let mut ipaddresses = HashSet::new();
328                // Iterate hostnames, produce a set of ip addresses from lookup, using set to eliminate duplicates
329                computer_hostnames.iter().for_each(|h| {
330                    ipaddresses.extend(Self::ipaddresses_from_hostname(h));
331                });
332                result.add_addresses(&computer_hostnames);
333                ipaddresses.iter().for_each(|v| {
334                    result.add_address(v);
335                });
336            } else {
337                result.add_addresses(&computer_hostnames);
338            }
339        }
340    }
341
342    /// Do a hostname lookup, find matching IP addresses
343    fn ipaddresses_from_hostname(hostname: &str) -> Vec<String> {
344        // Get ip addresses
345        if let Ok(addresses) = (hostname, 0u16).to_socket_addrs() {
346            addresses
347                .map(|addr| match addr {
348                    SocketAddr::V4(addr) => addr.ip().to_string(),
349                    SocketAddr::V6(addr) => addr.ip().to_string(),
350                })
351                .collect()
352        } else {
353            Vec::new()
354        }
355    }
356
357    /// Creates a sample certificate for testing, sample purposes only
358    pub fn sample_cert() -> X509Data {
359        let mut alt_host_names = AlternateNames::new();
360        Self::compute_alt_host_names(&mut alt_host_names, "urn:OPCUADemo", None, true, true, true);
361        X509Data {
362            key_size: 2048,
363            common_name: "OPC UA Demo Key".to_string(),
364            organization: "OPC UA for Rust".to_string(),
365            organizational_unit: "OPC UA for Rust".to_string(),
366            country: DEFAULT_COUNTRY.to_string(),
367            state: DEFAULT_STATE.to_string(),
368            alt_host_names,
369            certificate_duration_days: 365,
370        }
371    }
372}
373
374#[derive(Debug)]
375/// Error returned when handling X509 certificates.
376pub struct X509Error;
377
378impl fmt::Display for X509Error {
379    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
380        write!(f, "X509Error")
381    }
382}
383
384impl std::error::Error for X509Error {}
385
386impl From<x509::der::Error> for X509Error {
387    fn from(_err: x509::der::Error) -> Self {
388        X509Error
389    }
390}
391
392#[derive(Clone)]
393/// Wrapper around an X509 certificate.
394pub struct X509 {
395    value: x509::certificate::Certificate,
396}
397
398impl Debug for X509 {
399    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
400        // This impl will not write out the cert, and exists to keep derive happy
401        // on structs that contain an X509 instance
402        write!(f, "[x509]")
403    }
404}
405
406impl X509 {
407    /// Load an X509 certificate from a pem file.
408    pub fn from_pem(data: &[u8]) -> Result<Self, X509Error> {
409        use der::Decode;
410        use der::Reader;
411        use x509::der;
412
413        let mut reader = der::PemReader::new(data)?;
414        let val = x509::certificate::Certificate::decode(&mut reader)?;
415        let valf = reader.finish(val)?;
416        Ok(X509 { value: valf })
417
418        //keep certificate chain for another story
419        //let r = x509::certificate::Certificate::load_pem_chain(data);
420    }
421
422    /// Load an X509 certificate from a der file.
423    pub fn from_der(data: &[u8]) -> Result<Self, X509Error> {
424        use x509::der::Decode;
425
426        let val = x509::certificate::Certificate::from_der(data)?;
427        Ok(X509 { value: val })
428    }
429
430    /// Serialize the X509 file to a der file.
431    pub fn to_der(&self) -> Result<Vec<u8>, X509Error> {
432        use x509_cert::der::Encode;
433        let data = self.value.to_der()?;
434        Ok(data)
435
436        /*
437        let length = self.value.encoded_len()?;
438        let size : u32 = length.into();
439
440                let mut data: Vec<u8> = vec![0;size as usize];
441                let mut slice =  x509::der::SliceWriter::new(&mut data);
442                self.value.encode(&mut slice)?;
443                Ok(data)
444        */
445    }
446
447    /// Creates a self-signed X509v3 certificate and public/private key from the supplied creation args.
448    /// The certificate identifies an instance of the application running on a host as well
449    /// as the public key. The PKey holds the corresponding public/private key. Note that if
450    /// the pkey is stored by cert store, then only the private key will be written. The public key
451    /// is only ever stored with the cert.
452    ///
453    /// See Part 6 Table 23 for full set of requirements
454    ///
455    /// In particular, application instance cert requires subjectAltName to specify alternate
456    /// hostnames / ip addresses that the host runs on.
457    pub fn cert_and_pkey(x509_data: &X509Data) -> Result<(Self, PrivateKey), String> {
458        // Create a key pair
459
460        let pkey = PrivateKey::new(x509_data.key_size)
461            .map_err(|e| format!("Failed to generate RSA private key: {e}"))?;
462
463        // Create an X509 cert to hold the public key
464        let cert = Self::from_pkey(&pkey, x509_data)?;
465
466        Ok((cert, pkey))
467    }
468
469    fn append_to_name(name: &mut String, param: &str, data: &str) {
470        if !data.is_empty() {
471            if !name.is_empty() {
472                name.push(',');
473            }
474            name.push_str(param);
475            name.push('=');
476            name.push_str(data);
477        }
478    }
479
480    /// Create a certificate from a private key and certificate description.
481    pub fn from_pkey(pkey: &PrivateKey, x509_data: &X509Data) -> Result<Self, String> {
482        let result = Self::create_from_pkey(pkey, x509_data);
483
484        match result {
485            Ok(val) => Ok(val),
486            Err(e) => match e {
487                BuilderError::Asn1(_) => Err("Invalid der".to_string()),
488                BuilderError::PublicKey(_) => Err("Invalid public key".to_string()),
489                BuilderError::Signature(_) => Err("Invalid signature".to_string()),
490                _ => Err("Invalid".to_string()),
491            },
492        }
493    }
494
495    fn create_from_pkey(pkey: &PrivateKey, x509_data: &X509Data) -> Result<Self, BuilderError> {
496        use std::time::Duration;
497        use x509_cert::builder::{CertificateBuilder, Profile};
498        use x509_cert::name::Name;
499        use x509_cert::serial_number::SerialNumber;
500        use x509_cert::time::Validity;
501
502        let pub_key;
503        {
504            let r = pkey.public_key_to_info();
505            match r {
506                Err(e) => return Err(BuilderError::PublicKey(e)),
507                Ok(v) => pub_key = v,
508            }
509        }
510
511        let validity = Validity::from_now(Duration::new(
512            86400 * x509_data.certificate_duration_days as u64,
513            0,
514        ))
515        .unwrap();
516
517        let signing_key = pkcs1v15::SigningKey::<sha2::Sha256>::new(pkey.value.clone());
518
519        let serial_number = SerialNumber::from(42u32);
520
521        let subject;
522
523        {
524            let mut issuer = String::new();
525            Self::append_to_name(&mut issuer, "CN", &x509_data.common_name);
526            Self::append_to_name(&mut issuer, "O", &x509_data.organization);
527            Self::append_to_name(&mut issuer, "OU", &x509_data.organizational_unit);
528            Self::append_to_name(&mut issuer, "C", &x509_data.country);
529            Self::append_to_name(&mut issuer, "ST", &x509_data.state);
530
531            use std::str::FromStr;
532            subject = Name::from_str(&issuer)?;
533        }
534
535        // Issuer and subject shall be the same for self-signed cert
536        let profile = Profile::Manual {
537            issuer: Some(subject.clone()),
538        };
539
540        // Generate a SKI, and set it as the AKI for the certificate according to Part 6, 6.2.2
541        // Generation is as suggested in RFC3280, 4.2.1.2. A 160-bit SHA-1 hash of the public key bitstring.
542        use sha1::Digest;
543        let mut hasher = sha1::Sha1::new();
544        hasher.update(
545            pub_key
546                .subject_public_key
547                .as_bytes()
548                .expect("Invalid public key"),
549        );
550        let ski = hasher.finalize();
551
552        let mut builder = CertificateBuilder::new(
553            profile,
554            serial_number.clone(),
555            validity,
556            subject.clone(),
557            pub_key,
558            &signing_key,
559        )?;
560
561        builder.add_extension(&x509::ext::pkix::SubjectKeyIdentifier(
562            OctetString::new(ski.as_slice()).unwrap(),
563        ))?;
564        builder.add_extension(&x509::ext::pkix::AuthorityKeyIdentifier {
565            authority_cert_issuer: Some(vec![GeneralName::DirectoryName(subject)]),
566            key_identifier: Some(OctetString::new(ski.as_slice()).unwrap()),
567            authority_cert_serial_number: Some(serial_number),
568        })?;
569        builder.add_extension(&x509::ext::pkix::BasicConstraints {
570            ca: false,
571            path_len_constraint: None,
572        })?;
573
574        {
575            use x509::ext::pkix::KeyUsage;
576            use x509::ext::pkix::KeyUsages;
577
578            let key_usage = KeyUsages::DigitalSignature
579                | KeyUsages::NonRepudiation
580                | KeyUsages::KeyEncipherment
581                | KeyUsages::DataEncipherment
582                | KeyUsages::KeyCertSign;
583            builder.add_extension(&KeyUsage(key_usage))?;
584        }
585
586        {
587            use x509::ext::pkix::ExtendedKeyUsage;
588            let usage = vec![
589                const_oid::db::rfc5280::ID_KP_CLIENT_AUTH,
590                const_oid::db::rfc5280::ID_KP_SERVER_AUTH,
591            ];
592            builder.add_extension(&ExtendedKeyUsage(usage))?;
593        }
594
595        {
596            if !x509_data.alt_host_names.is_empty() {
597                builder.add_extension(&x509_data.alt_host_names.names)?;
598            }
599        }
600
601        use x509_cert::builder::Builder;
602        let built = builder.build()?;
603
604        Ok(X509 { value: built })
605    }
606
607    /// Load a certificate from a der byte string.
608    pub fn from_byte_string(data: &ByteString) -> Result<X509, Error> {
609        if data.is_null_or_empty() {
610            Err(Error::new(
611                StatusCode::BadCertificateInvalid,
612                "Cannot make certificate from null bytestring",
613            ))
614        } else {
615            let r = Self::from_der(data.value.as_ref().unwrap());
616            match r {
617                Err(e) => Err(Error::new(StatusCode::BadCertificateInvalid, e)),
618                Ok(cert) => Ok(cert),
619            }
620        }
621    }
622
623    /// Returns a ByteString representation of the cert which is DER encoded form of X509v3
624    pub fn as_byte_string(&self) -> ByteString {
625        let der = self.to_der().unwrap();
626        ByteString::from(&der)
627    }
628
629    /// Try to get the public key from this certificate.
630    pub fn public_key(&self) -> Result<PublicKey, Error> {
631        use x509_cert::der::referenced::OwnedToRef;
632
633        let r = RsaPublicKey::try_from(
634            self.value
635                .tbs_certificate
636                .subject_public_key_info
637                .owned_to_ref(),
638        );
639        match r {
640            Err(e) => Err(Error::new(StatusCode::BadCertificateInvalid, e)),
641            Ok(v) => Ok(PublicKey { value: v }),
642        }
643    }
644
645    /// Returns the key length in bits (if possible)
646    pub fn key_length(&self) -> Result<usize, X509Error> {
647        let r = self.public_key();
648        match r {
649            Err(_) => Err(X509Error),
650            Ok(v) => Ok(v.bit_length()),
651        }
652    }
653
654    fn get_subject_entry(&self, nid: const_oid::ObjectIdentifier) -> Result<String, X509Error> {
655        for dn in self.value.tbs_certificate.subject.0.iter() {
656            for tv in dn.0.iter() {
657                if tv.oid == nid {
658                    return Ok(tv.to_string());
659                }
660            }
661        }
662
663        Err(X509Error)
664    }
665
666    /// Produces a subject name string such as "CN=foo/C=IE"
667    pub fn subject_name(&self) -> String {
668        let r = self.value.tbs_certificate.subject.to_string();
669        r.replace(";", "/")
670    }
671
672    /// Gets the common name out of the cert
673    pub fn common_name(&self) -> Result<String, X509Error> {
674        self.get_subject_entry(const_oid::db::rfc4519::COMMON_NAME)
675    }
676
677    /// Tests if the certificate is valid for the supplied time using the not before and not
678    /// after values on the cert.
679    pub fn is_time_valid(&self, now: &DateTime<Utc>) -> Result<(), StatusCode> {
680        // Issuer time
681        let not_before = self.not_before();
682        if let Ok(not_before) = not_before {
683            if now.lt(&not_before) {
684                error!("Certificate < before date)");
685                return Err(StatusCode::BadCertificateTimeInvalid);
686            }
687        } else {
688            // No before time
689            error!("Certificate has no before date");
690            return Err(StatusCode::BadCertificateInvalid);
691        }
692
693        // Expiration time
694        let not_after = self.not_after();
695        if let Ok(not_after) = not_after {
696            if now.gt(&not_after) {
697                error!("Certificate has expired (> after date)");
698                return Err(StatusCode::BadCertificateTimeInvalid);
699            }
700        } else {
701            // No after time
702            error!("Certificate has no after date");
703            return Err(StatusCode::BadCertificateInvalid);
704        }
705
706        trace!("Certificate is valid for this time");
707        Ok(())
708    }
709
710    fn get_alternate_names(&self) -> Option<x509::ext::pkix::name::GeneralNames> {
711        use x509::ext::pkix::SubjectAltName;
712
713        let r: Result<Option<(bool, SubjectAltName)>, _> = self.value.tbs_certificate.get();
714        match r {
715            Err(_) => None,
716            Ok(option) => match option {
717                None => None,
718                Some(v) => {
719                    Some(v.1 .0) //the second field of option (ie SubjectAltName) then the first field
720                }
721            },
722        }
723    }
724
725    /// Tests if the supplied hostname matches any of the dns alt subject name entries on the cert
726    pub fn is_hostname_valid(&self, hostname: &str) -> Result<(), StatusCode> {
727        trace!("is_hostname_valid against {} on cert", hostname);
728        // Look through alt subject names for a matching entry
729        if hostname.is_empty() {
730            error!("Hostname is empty");
731            Err(StatusCode::BadCertificateHostNameInvalid)
732        } else if let Some(subject_alt_names) = self.get_alternate_names() {
733            let found = subject_alt_names
734                .iter()
735                .skip(1) //skip the application uri
736                .any(|n| {
737                    let name = AlternateNames::convert_name(n);
738                    match name {
739                        Some(val) => val.eq_ignore_ascii_case(hostname),
740                        _ => false,
741                    }
742                });
743            if found {
744                info!("Certificate host name {} is good", hostname);
745                Ok(())
746            } else {
747                warn!("Did not find hostname {hostname} in alt names {subject_alt_names:?}");
748                Err(StatusCode::BadCertificateHostNameInvalid)
749            }
750        } else {
751            // No alt names
752            error!("Cert has no subject alt names at all");
753            Err(StatusCode::BadCertificateHostNameInvalid)
754        }
755    }
756
757    /// Tests if the supplied application uri matches the uri alt subject name entry on the cert
758    pub fn is_application_uri_valid(&self, application_uri: &str) -> Result<(), StatusCode> {
759        // Expecting the first subject alternative name to be a uri that matches with the supplied
760        // application uri
761        if let Some(alt_names) = self.get_alternate_names() {
762            if !alt_names.is_empty() {
763                match AlternateNames::convert_name(&alt_names[0]) {
764                    Some(val) => {
765                        if val == application_uri {
766                            Ok(())
767                        } else {
768                            error!(
769                                "Application uri {} does not match first alt name {}",
770                                application_uri, val
771                            );
772                            Err(StatusCode::BadCertificateUriInvalid)
773                        }
774                    }
775
776                    _ => {
777                        error!("Alternate name {:?} cannot be converted", alt_names[0]);
778                        Err(StatusCode::BadCertificateUriInvalid)
779                    }
780                }
781            } else {
782                error!("Cert has zero subject alt names");
783                Err(StatusCode::BadCertificateUriInvalid)
784            }
785        } else {
786            error!("Cert has no subject alt names at all");
787            // No alt names
788            Err(StatusCode::BadCertificateUriInvalid)
789        }
790    }
791
792    /// OPC UA Part 6 MessageChunk structure
793    ///
794    /// The thumbprint is the SHA1 digest of the DER form of the certificate. The hash is 160 bits
795    /// (20 bytes) in length and is sent in some secure conversation headers.
796    ///
797    /// The thumbprint might be used by the server / client for look-up purposes.
798    pub fn thumbprint(&self) -> Thumbprint {
799        use sha1::Digest;
800        use x509_cert::der::Encode;
801
802        let der = self.value.to_der().unwrap();
803
804        let mut hasher = sha1::Sha1::new();
805        hasher.update(&der);
806        let digest = hasher.finalize();
807        Thumbprint::new(&digest)
808    }
809
810    /// Turn the Asn1 values into useful portable types
811    pub fn not_before(&self) -> Result<ChronoUtc, X509Error> {
812        let dur = self
813            .value
814            .tbs_certificate
815            .validity
816            .not_before
817            .to_unix_duration();
818        let r = ChronoUtc::from_timestamp_micros(dur.as_micros() as i64);
819        match r {
820            None => Err(X509Error),
821            Some(val) => Ok(val),
822        }
823    }
824
825    /// Turn the Asn1 values into useful portable types
826    pub fn not_after(&self) -> Result<ChronoUtc, X509Error> {
827        let dur = self
828            .value
829            .tbs_certificate
830            .validity
831            .not_after
832            .to_unix_duration();
833        let r = ChronoUtc::from_timestamp_micros(dur.as_micros() as i64);
834        match r {
835            None => Err(X509Error),
836            Some(val) => Ok(val),
837        }
838    }
839}
840
841#[cfg(test)]
842mod tests {
843    use super::*;
844
845    /*
846        #[test]
847        fn parse_asn1_date_test() {
848            use chrono::{Datelike, Timelike};
849
850            assert!(X509::parse_asn1_date("").is_err());
851            assert!(X509::parse_asn1_date("Jan 69 00:00:00 1970").is_err());
852            assert!(X509::parse_asn1_date("Feb 21 00:00:00 1970").is_ok());
853            assert!(X509::parse_asn1_date("Feb 21 00:00:00 1970 GMT").is_ok());
854
855            let dt: DateTime<Utc> = X509::parse_asn1_date("Feb 21 12:45:30 1999 GMT").unwrap();
856            assert_eq!(dt.month(), 2);
857            assert_eq!(dt.day(), 21);
858            assert_eq!(dt.hour(), 12);
859            assert_eq!(dt.minute(), 45);
860            assert_eq!(dt.second(), 30);
861            assert_eq!(dt.year(), 1999);
862        }
863    */
864
865    /// This test checks that a cert will validate dns or ip entries in the subject alt host names
866    #[test]
867    fn alt_hostnames() {
868        let mut alt_host_names = AlternateNames::new();
869        alt_host_names.add_dns("uri:foo"); //the application uri
870        alt_host_names.add_address("host2");
871        alt_host_names.add_address("www.google.com");
872        alt_host_names.add_address("192.168.1.1");
873        alt_host_names.add_address("::1");
874
875        // Create a cert with alt hostnames which are both IP and DNS entries
876        let args = X509Data {
877            key_size: 2048,
878            common_name: "x".to_string(),
879            organization: "x.org".to_string(),
880            organizational_unit: "x.org ops".to_string(),
881            country: "EN".to_string(),
882            state: "London".to_string(),
883            alt_host_names,
884            certificate_duration_days: 60,
885        };
886
887        let (x509, _pkey) = X509::cert_and_pkey(&args).unwrap();
888
889        assert!(x509.is_hostname_valid("").is_err());
890        assert!(x509.is_hostname_valid("uri:foo").is_err()); // The application uri should not be valid
891        assert!(x509.is_hostname_valid("192.168.1.0").is_err());
892        assert!(x509.is_hostname_valid("www.cnn.com").is_err());
893        assert!(x509.is_hostname_valid("host1").is_err());
894
895        args.alt_host_names.iter().skip(1).for_each(|n| {
896            assert!(x509.is_hostname_valid(n.as_str()).is_ok());
897        })
898    }
899}