1use 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)]
41pub struct AlternateNames {
43 pub names: x509::ext::pkix::SubjectAltName,
45}
46
47impl AlternateNames {
48 pub fn new() -> Self {
50 use x509::ext::pkix::SubjectAltName;
51 Self {
52 names: SubjectAltName(xname::GeneralNames::new()),
53 }
54 }
55
56 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 pub fn is_empty(&self) -> bool {
65 self.names.0.is_empty()
66 }
67
68 pub fn len(&self) -> usize {
70 self.names.0.len()
71 }
72
73 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 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 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 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 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 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 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
196pub struct X509Data {
198 pub key_size: u32,
200 pub common_name: String,
202 pub organization: String,
204 pub organizational_unit: String,
206 pub country: String,
208 pub state: String,
210 pub alt_host_names: AlternateNames,
217 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 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 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 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 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 result.add_uri(application_uri);
309
310 if let Some(addresses) = addresses {
312 result.add_addresses(&addresses);
313 }
314
315 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 if add_computer_name {
325 let computer_hostnames = Self::computer_hostnames();
326 if add_ip_addresses {
327 let mut ipaddresses = HashSet::new();
328 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 fn ipaddresses_from_hostname(hostname: &str) -> Vec<String> {
344 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 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)]
375pub 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)]
393pub struct X509 {
395 value: x509::certificate::Certificate,
396}
397
398impl Debug for X509 {
399 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
400 write!(f, "[x509]")
403 }
404}
405
406impl X509 {
407 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 }
421
422 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 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 }
446
447 pub fn cert_and_pkey(x509_data: &X509Data) -> Result<(Self, PrivateKey), String> {
458 let pkey = PrivateKey::new(x509_data.key_size)
461 .map_err(|e| format!("Failed to generate RSA private key: {e}"))?;
462
463 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 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 let profile = Profile::Manual {
537 issuer: Some(subject.clone()),
538 };
539
540 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 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 pub fn as_byte_string(&self) -> ByteString {
625 let der = self.to_der().unwrap();
626 ByteString::from(&der)
627 }
628
629 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 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 pub fn subject_name(&self) -> String {
668 let r = self.value.tbs_certificate.subject.to_string();
669 r.replace(";", "/")
670 }
671
672 pub fn common_name(&self) -> Result<String, X509Error> {
674 self.get_subject_entry(const_oid::db::rfc4519::COMMON_NAME)
675 }
676
677 pub fn is_time_valid(&self, now: &DateTime<Utc>) -> Result<(), StatusCode> {
680 let not_before = self.not_before();
682 if let Ok(not_before) = not_before {
683 if now.lt(¬_before) {
684 error!("Certificate < before date)");
685 return Err(StatusCode::BadCertificateTimeInvalid);
686 }
687 } else {
688 error!("Certificate has no before date");
690 return Err(StatusCode::BadCertificateInvalid);
691 }
692
693 let not_after = self.not_after();
695 if let Ok(not_after) = not_after {
696 if now.gt(¬_after) {
697 error!("Certificate has expired (> after date)");
698 return Err(StatusCode::BadCertificateTimeInvalid);
699 }
700 } else {
701 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) }
721 },
722 }
723 }
724
725 pub fn is_hostname_valid(&self, hostname: &str) -> Result<(), StatusCode> {
727 trace!("is_hostname_valid against {} on cert", hostname);
728 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) .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 error!("Cert has no subject alt names at all");
753 Err(StatusCode::BadCertificateHostNameInvalid)
754 }
755 }
756
757 pub fn is_application_uri_valid(&self, application_uri: &str) -> Result<(), StatusCode> {
759 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 Err(StatusCode::BadCertificateUriInvalid)
789 }
790 }
791
792 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 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 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 #[test]
867 fn alt_hostnames() {
868 let mut alt_host_names = AlternateNames::new();
869 alt_host_names.add_dns("uri:foo"); 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 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()); 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}