1use std::{
8 self,
9 fmt::{self, Debug, Formatter},
10 net::{Ipv4Addr, Ipv6Addr},
11 result::Result,
12};
13
14use chrono::{DateTime, TimeZone, Utc};
15use openssl::{
16 asn1::*,
17 hash,
18 nid::Nid,
19 pkey,
20 rsa::*,
21 x509::{self, extension::*},
22};
23
24use opcua_types::{service_types::ApplicationDescription, status_code::StatusCode, ByteString};
25
26use crate::{
27 hostname,
28 pkey::{PrivateKey, PublicKey},
29 thumbprint::Thumbprint,
30};
31
32const DEFAULT_KEYSIZE: u32 = 2048;
33const DEFAULT_COUNTRY: &str = "IE";
34const DEFAULT_STATE: &str = "Dublin";
35
36#[derive(Debug)]
37pub struct X509Data {
39 pub key_size: u32,
40 pub common_name: String,
41 pub organization: String,
42 pub organizational_unit: String,
43 pub country: String,
44 pub state: String,
45 pub alt_host_names: Vec<String>,
52 pub certificate_duration_days: u32,
54}
55
56impl From<(ApplicationDescription, Option<Vec<String>>)> for X509Data {
57 fn from(v: (ApplicationDescription, Option<Vec<String>>)) -> Self {
58 let (application_description, addresses) = v;
59 let application_uri = application_description.application_uri.as_ref();
60 let alt_host_names = Self::alt_host_names(application_uri, addresses, false, true);
61 X509Data {
62 key_size: DEFAULT_KEYSIZE,
63 common_name: application_description.application_name.to_string(),
64 organization: application_description.application_name.to_string(),
65 organizational_unit: application_description.application_name.to_string(),
66 country: DEFAULT_COUNTRY.to_string(),
67 state: DEFAULT_STATE.to_string(),
68 alt_host_names,
69 certificate_duration_days: 365,
70 }
71 }
72}
73
74impl From<ApplicationDescription> for X509Data {
75 fn from(v: ApplicationDescription) -> Self {
76 X509Data::from((v, None))
77 }
78}
79
80impl X509Data {
81 pub fn computer_hostnames() -> Vec<String> {
83 let mut result = Vec::with_capacity(2);
84
85 if let Ok(hostname) = hostname() {
86 if !hostname.is_empty() {
87 result.push(hostname);
88 }
89 }
90 if result.is_empty() {
91 if let Ok(machine_name) = std::env::var("COMPUTERNAME") {
93 result.push(machine_name);
94 }
95 if let Ok(machine_name) = std::env::var("NAME") {
96 result.push(machine_name);
97 }
98 }
99
100 result
101 }
102
103 pub fn alt_host_names(
105 application_uri: &str,
106 addresses: Option<Vec<String>>,
107 add_localhost: bool,
108 add_computer_name: bool,
109 ) -> Vec<String> {
110 let mut result = vec![application_uri.to_string()];
112
113 if let Some(mut addresses) = addresses {
115 result.append(&mut addresses);
116 }
117
118 if add_localhost {
120 result.push("localhost".to_string());
121 result.push("127.0.0.1".to_string());
122 result.push("::1".to_string());
123 }
124 if add_computer_name {
126 result.extend(Self::computer_hostnames());
127 }
128 if result.len() == 1 {
129 panic!("Could not create any DNS alt host names");
130 }
131 result
132 }
133
134 pub fn sample_cert() -> X509Data {
136 let alt_host_names = Self::alt_host_names("urn:OPCUADemo", None, false, true);
137 X509Data {
138 key_size: 2048,
139 common_name: "OPC UA Demo Key".to_string(),
140 organization: "OPC UA for Rust".to_string(),
141 organizational_unit: "OPC UA for Rust".to_string(),
142 country: DEFAULT_COUNTRY.to_string(),
143 state: DEFAULT_STATE.to_string(),
144 alt_host_names,
145 certificate_duration_days: 365,
146 }
147 }
148}
149
150#[derive(Debug)]
151pub struct X509Error;
152
153impl fmt::Display for X509Error {
154 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
155 write!(f, "X509Error")
156 }
157}
158
159impl std::error::Error for X509Error {}
160
161#[derive(Clone)]
163pub struct X509 {
164 value: x509::X509,
165}
166
167impl Debug for X509 {
168 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
169 write!(f, "[x509]")
172 }
173}
174
175impl From<x509::X509> for X509 {
176 fn from(value: x509::X509) -> Self {
177 Self { value }
178 }
179}
180
181impl X509 {
182 pub fn from_der(der: &[u8]) -> Result<Self, X509Error> {
183 x509::X509::from_der(der).map(X509::from).map_err(|_| {
184 error!("Cannot produce an x509 cert from the data supplied");
185 X509Error
186 })
187 }
188
189 pub fn cert_and_pkey(x509_data: &X509Data) -> Result<(Self, PrivateKey), String> {
200 let rsa = Rsa::generate(x509_data.key_size).map_err(|err| {
202 format!(
203 "Cannot create key pair check error {} and key size {}",
204 err, x509_data.key_size
205 )
206 })?;
207 let pkey = pkey::PKey::from_rsa(rsa)
208 .map_err(|err| format!("Cannot create key pair check error {}", err))?;
209 let pkey = PrivateKey::wrap_private_key(pkey);
210
211 let cert = Self::from_pkey(&pkey, x509_data)?;
213
214 Ok((cert, pkey))
215 }
216
217 pub fn from_pkey(pkey: &PrivateKey, x509_data: &X509Data) -> Result<Self, String> {
218 let mut builder = x509::X509Builder::new().unwrap();
219 let _ = builder.set_version(2);
221 let issuer_name = {
222 let mut name = x509::X509NameBuilder::new().unwrap();
223 name.append_entry_by_text("CN", &x509_data.common_name)
225 .unwrap();
226 name.append_entry_by_text("O", &x509_data.organization)
228 .unwrap();
229 name.append_entry_by_text("OU", &x509_data.organizational_unit)
231 .unwrap();
232 name.append_entry_by_text("C", &x509_data.country).unwrap();
234 name.append_entry_by_text("ST", &x509_data.state).unwrap();
236 name.build()
237 };
238 let _ = builder.set_subject_name(&issuer_name);
240 let _ = builder.set_issuer_name(&issuer_name);
241
242 let key_usage = KeyUsage::new()
244 .digital_signature()
245 .non_repudiation()
246 .key_encipherment()
247 .data_encipherment()
248 .key_cert_sign()
249 .build()
250 .unwrap();
251 let _ = builder.append_extension(key_usage);
252 let extended_key_usage = ExtendedKeyUsage::new()
253 .client_auth()
254 .server_auth()
255 .build()
256 .unwrap();
257 let _ = builder.append_extension(extended_key_usage);
258
259 builder
260 .set_not_before(&Asn1Time::days_from_now(0).unwrap())
261 .unwrap();
262 builder
263 .set_not_after(&Asn1Time::days_from_now(x509_data.certificate_duration_days).unwrap())
264 .unwrap();
265 builder.set_pubkey(&pkey.value).unwrap();
266
267 {
269 use openssl::bn::BigNum;
270 use openssl::bn::MsbOption;
271 let mut serial = BigNum::new().unwrap();
272 serial.rand(128, MsbOption::MAYBE_ZERO, false).unwrap();
273 let serial = serial.to_asn1_integer().unwrap();
274 let _ = builder.set_serial_number(&serial);
275 }
276
277 if !x509_data.alt_host_names.is_empty() {
280 let subject_alternative_name = {
281 let mut subject_alternative_name = SubjectAlternativeName::new();
282 x509_data
283 .alt_host_names
284 .iter()
285 .enumerate()
286 .for_each(|(i, alt_host_name)| {
287 if !alt_host_name.is_empty() {
288 if i == 0 {
289 subject_alternative_name.uri(alt_host_name);
291 } else if alt_host_name.parse::<Ipv4Addr>().is_ok()
292 || alt_host_name.parse::<Ipv6Addr>().is_ok()
293 {
294 subject_alternative_name.ip(alt_host_name);
296 } else {
297 subject_alternative_name.dns(alt_host_name);
299 }
300 }
301 });
302 subject_alternative_name
303 .build(&builder.x509v3_context(None, None))
304 .unwrap()
305 };
306 builder.append_extension(subject_alternative_name).unwrap();
307 }
308
309 let _ = builder.sign(&pkey.value, hash::MessageDigest::sha256());
311
312 Ok(X509::from(builder.build()))
313 }
314
315 pub fn from_byte_string(data: &ByteString) -> Result<X509, StatusCode> {
316 if data.is_null() {
317 error!("Cannot make certificate from null bytestring");
318 Err(StatusCode::BadCertificateInvalid)
319 } else if let Ok(cert) = x509::X509::from_der(data.value.as_ref().unwrap()) {
320 Ok(X509::from(cert))
321 } else {
322 error!("Cannot make certificate, does bytestring contain .der?");
323 Err(StatusCode::BadCertificateInvalid)
324 }
325 }
326
327 pub fn as_byte_string(&self) -> ByteString {
329 let der = self.value.to_der().unwrap();
330 ByteString::from(&der)
331 }
332
333 pub fn public_key(&self) -> Result<PublicKey, StatusCode> {
334 self.value
335 .public_key()
336 .map(PublicKey::wrap_public_key)
337 .map_err(|_| {
338 error!("Cannot obtain public key from certificate");
339 StatusCode::BadCertificateInvalid
340 })
341 }
342
343 pub fn key_length(&self) -> Result<usize, X509Error> {
345 let pub_key = self.value.public_key().map_err(|_| X509Error)?;
346 Ok(pub_key.size() * 8)
347 }
348
349 fn get_subject_entry(&self, nid: Nid) -> Result<String, X509Error> {
350 let subject_name = self.value.subject_name();
351 let mut entries = subject_name.entries_by_nid(nid);
352 if let Some(entry) = entries.next() {
353 if let Ok(value) = entry.data().as_utf8() {
355 use std::ops::Deref;
356 Ok(value.deref().to_string())
358 } else {
359 Err(X509Error)
360 }
361 } else {
362 Err(X509Error)
363 }
364 }
365
366 pub fn subject_name(&self) -> String {
368 use std::ops::Deref;
369 self.value
370 .subject_name()
371 .entries()
372 .map(|e| {
373 let v = if let Ok(v) = e.data().as_utf8() {
374 v.deref().to_string()
375 } else {
376 "?".into()
377 };
378 format!("{}={}", e.object(), v)
379 })
380 .collect::<Vec<String>>()
381 .join("/")
382 }
383
384 pub fn common_name(&self) -> Result<String, X509Error> {
386 self.get_subject_entry(Nid::COMMONNAME)
387 }
388
389 pub fn is_time_valid(&self, now: &DateTime<Utc>) -> StatusCode {
392 let not_before = self.not_before();
394 if let Ok(not_before) = not_before {
395 if now.lt(¬_before) {
396 error!("Certificate < before date)");
397 return StatusCode::BadCertificateTimeInvalid;
398 }
399 } else {
400 error!("Certificate has no before date");
402 return StatusCode::BadCertificateInvalid;
403 }
404
405 let not_after = self.not_after();
407 if let Ok(not_after) = not_after {
408 if now.gt(¬_after) {
409 error!("Certificate has expired (> after date)");
410 return StatusCode::BadCertificateTimeInvalid;
411 }
412 } else {
413 error!("Certificate has no after date");
415 return StatusCode::BadCertificateInvalid;
416 }
417
418 info!("Certificate is valid for this time");
419 StatusCode::Good
420 }
421
422 fn subject_alt_names(&self) -> Option<Vec<String>> {
423 if let Some(ref alt_names) = self.value.subject_alt_names() {
424 let subject_alt_names = alt_names
426 .iter()
427 .skip(1)
428 .map(|n| {
429 if let Some(dnsname) = n.dnsname() {
430 dnsname.to_string()
431 } else if let Some(ip) = n.ipaddress() {
432 if ip.len() == 4 {
433 let mut addr = [0u8; 4];
434 addr[..].clone_from_slice(ip);
435 Ipv4Addr::from(addr).to_string()
436 } else if ip.len() == 16 {
437 let mut addr = [0u8; 16];
438 addr[..].clone_from_slice(ip);
439 Ipv6Addr::from(addr).to_string()
440 } else {
441 "".to_string()
442 }
443 } else {
444 "".to_string()
445 }
446 })
447 .collect();
448 Some(subject_alt_names)
449 } else {
450 None
451 }
452 }
453
454 pub fn is_hostname_valid(&self, hostname: &str) -> StatusCode {
456 trace!("is_hostname_valid against {} on cert", hostname);
457 if hostname.is_empty() {
459 error!("Hostname is empty");
460 StatusCode::BadCertificateHostNameInvalid
461 } else if let Some(subject_alt_names) = self.subject_alt_names() {
462 let found = subject_alt_names
463 .iter()
464 .any(|n| n.eq_ignore_ascii_case(hostname));
465 if found {
466 info!("Certificate host name {} is good", hostname);
467 StatusCode::Good
468 } else {
469 let alt_names = subject_alt_names
470 .iter()
471 .map(|n| n.as_ref())
472 .collect::<Vec<&str>>()
473 .join(", ");
474 error!(
475 "Cannot find a matching hostname for input {}, alt names = {}",
476 hostname, alt_names
477 );
478 StatusCode::BadCertificateHostNameInvalid
479 }
480 } else {
481 error!("Cert has no subject alt names at all");
483 StatusCode::BadCertificateHostNameInvalid
484 }
485 }
486
487 pub fn is_application_uri_valid(&self, application_uri: &str) -> StatusCode {
489 trace!(
490 "is_application_uri_valid against {} on cert",
491 application_uri
492 );
493 if let Some(ref alt_names) = self.value.subject_alt_names() {
496 if alt_names.len() > 0 {
497 if let Some(cert_application_uri) = alt_names[0].uri() {
498 if cert_application_uri == application_uri {
499 info!("Certificate application uri {} is good", application_uri);
500 StatusCode::Good
501 } else {
502 error!(
503 "Cert application uri {} does not match supplied uri {}",
504 cert_application_uri, application_uri
505 );
506 StatusCode::BadCertificateUriInvalid
507 }
508 } else {
509 error!("Cert's first subject alt name is not a uri and cannot be compared");
510 StatusCode::BadCertificateUriInvalid
511 }
512 } else {
513 error!("Cert has zero subject alt names");
514 StatusCode::BadCertificateUriInvalid
515 }
516 } else {
517 error!("Cert has no subject alt names at all");
518 StatusCode::BadCertificateUriInvalid
520 }
521 }
522
523 pub fn thumbprint(&self) -> Thumbprint {
530 use openssl::hash::{hash, MessageDigest};
531 let der = self.value.to_der().unwrap();
532 let digest = hash(MessageDigest::sha1(), &der).unwrap();
533 Thumbprint::new(&digest)
534 }
535
536 pub fn not_before(&self) -> Result<DateTime<Utc>, X509Error> {
538 let date = self.value.not_before().to_string();
539 Self::parse_asn1_date(&date)
540 }
541
542 pub fn not_after(&self) -> Result<DateTime<Utc>, X509Error> {
544 let date = self.value.not_after().to_string();
545 Self::parse_asn1_date(&date)
546 }
547
548 pub fn to_der(&self) -> Result<Vec<u8>, X509Error> {
549 self.value.to_der().map_err(|e| {
550 error!("Cannot turn X509 cert to DER, err = {:?}", e);
551 X509Error
552 })
553 }
554
555 fn parse_asn1_date(date: &str) -> Result<DateTime<Utc>, X509Error> {
556 const SUFFIX: &str = " GMT";
557 let date = if date.ends_with(SUFFIX) {
560 let end = date.len() - SUFFIX.len();
562 &date[..end]
563 } else {
564 date
565 };
566 Utc.datetime_from_str(date, "%b %d %H:%M:%S %Y")
567 .map_err(|e| {
568 error!("Cannot parse ASN1 date, err = {:?}", e);
569 X509Error
570 })
571 }
572}
573
574#[cfg(test)]
575mod tests {
576 use super::*;
577
578 #[test]
579 fn parse_asn1_date_test() {
580 use chrono::{Datelike, Timelike};
581
582 assert!(X509::parse_asn1_date("").is_err());
583 assert!(X509::parse_asn1_date("Jan 69 00:00:00 1970").is_err());
584 assert!(X509::parse_asn1_date("Feb 21 00:00:00 1970").is_ok());
585 assert!(X509::parse_asn1_date("Feb 21 00:00:00 1970 GMT").is_ok());
586
587 let dt: DateTime<Utc> = X509::parse_asn1_date("Feb 21 12:45:30 1999 GMT").unwrap();
588 assert_eq!(dt.month(), 2);
589 assert_eq!(dt.day(), 21);
590 assert_eq!(dt.hour(), 12);
591 assert_eq!(dt.minute(), 45);
592 assert_eq!(dt.second(), 30);
593 assert_eq!(dt.year(), 1999);
594 }
595
596 #[test]
598 fn alt_hostnames() {
599 opcua_console_logging::init();
600
601 let alt_host_names = ["uri:foo", "host2", "www.google.com", "192.168.1.1", "::1"];
602
603 let args = X509Data {
605 key_size: 2048,
606 common_name: "x".to_string(),
607 organization: "x.org".to_string(),
608 organizational_unit: "x.org ops".to_string(),
609 country: "EN".to_string(),
610 state: "London".to_string(),
611 alt_host_names: alt_host_names.iter().map(|h| h.to_string()).collect(),
612 certificate_duration_days: 60,
613 };
614
615 let (x509, _pkey) = X509::cert_and_pkey(&args).unwrap();
616
617 assert!(!x509.is_hostname_valid("").is_good());
618 assert!(!x509.is_hostname_valid("uri:foo").is_good()); assert!(!x509.is_hostname_valid("192.168.1.0").is_good());
620 assert!(!x509.is_hostname_valid("www.cnn.com").is_good());
621 assert!(!x509.is_hostname_valid("host1").is_good());
622
623 alt_host_names.iter().skip(1).for_each(|n| {
624 println!("Hostname {}", n);
625 assert!(x509.is_hostname_valid(n).is_good());
626 })
627 }
628}