From 71370a0cf338f511eec94e6d399aab060f48c183 Mon Sep 17 00:00:00 2001 From: Sebastian Goll Date: Fri, 6 Dec 2024 16:32:46 +0100 Subject: [PATCH] Refactor code to fetch certificate from server --- CHANGELOG.md | 2 +- Cargo.lock | 339 ++++++++++++++++++++++++++++- Cargo.toml | 7 +- examples/ssl_create_certificate.rs | 34 ++- examples/ssl_fetch_certificate.rs | 69 +++++- src/client.rs | 52 ++++- src/lib.rs | 2 +- src/ssl.rs | 88 ++------ 8 files changed, 508 insertions(+), 85 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ad7d10e..561a09f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,8 +10,8 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ### Added - Add type `PrivateKey` to wrap private keys when using Mbed TLS. -- Add ability to fetch remote server certificate with `fetch_server_certificate()`. - Add types `ua::SecurityLevel`, `ua::EndpointDescription`, `ua::MessageSecurityMode`. +- Add ability to get remote server endpoints with `ua::ClientBuilder::get_endpoints()`. ### Changed diff --git a/Cargo.lock b/Cargo.lock index 3a71f47..c5e6b5d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,6 +26,21 @@ dependencies = [ "memchr", ] +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "annotate-snippets" version = "0.9.2" @@ -111,6 +126,28 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bcder" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c627747a6774aab38beb35990d88309481378558875a41da1a4b2e373c906ef0" +dependencies = [ + "bytes", + "smallvec", +] + [[package]] name = "bindgen" version = "0.70.1" @@ -121,7 +158,7 @@ dependencies = [ "bitflags", "cexpr", "clang-sys", - "itertools", + "itertools 0.12.1", "log", "prettyplease", "proc-macro2", @@ -138,6 +175,18 @@ version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "bytes" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" + [[package]] name = "cc" version = "1.0.83" @@ -162,6 +211,18 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "num-traits", + "windows-targets 0.52.0", +] + [[package]] name = "clang-sys" version = "1.7.0" @@ -188,6 +249,28 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "der" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +dependencies = [ + "const-oid", + "zeroize", +] + [[package]] name = "deranged" version = "0.3.11" @@ -345,12 +428,41 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "humantime" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +[[package]] +name = "iana-time-zone" +version = "0.1.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "itertools" version = "0.12.1" @@ -360,12 +472,31 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +[[package]] +name = "js-sys" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a865e038f7f6ed956f788f0d7d60c541fff74c7bd74272c5d4cf15c63743e705" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + [[package]] name = "libc" version = "0.2.153" @@ -425,6 +556,15 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "num_cpus" version = "1.16.0" @@ -444,6 +584,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + [[package]] name = "open62541" version = "0.6.6" @@ -454,15 +600,17 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", + "itertools 0.13.0", "log", "open62541-sys", "paste", "rand", "serde", "serde_json", - "thiserror", + "thiserror 2.0.3", "time", "tokio", + "x509-certificate", ] [[package]] @@ -483,6 +631,16 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +[[package]] +name = "pem" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae" +dependencies = [ + "base64", + "serde", +] + [[package]] name = "pin-project-lite" version = "0.2.13" @@ -594,6 +752,21 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys 0.52.0", +] + [[package]] name = "rustc-demangle" version = "0.1.23" @@ -649,6 +822,15 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "rand_core", +] + [[package]] name = "slab" version = "0.4.9" @@ -658,6 +840,28 @@ dependencies = [ "autocfg", ] +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + [[package]] name = "syn" version = "2.0.87" @@ -669,13 +873,33 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + [[package]] name = "thiserror" version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa" dependencies = [ - "thiserror-impl", + "thiserror-impl 2.0.3", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -755,6 +979,12 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "utf8parse" version = "0.2.1" @@ -773,6 +1003,61 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasm-bindgen" +version = "0.2.97" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d15e63b4482863c109d70a7b8706c1e364eb6ea449b201a76c5b89cedcec2d5c" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.97" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d36ef12e3aaca16ddd3f67922bc63e48e953f126de60bd33ccc0101ef9998cd" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.97" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "705440e08b42d3e4b36de7d66c944be628d579796b8090bfa3471478a2260051" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.97" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98c9ae5a76e46f4deecd0f0255cc223cfa18dc9b261213b8aa0c7b36f61b3f1d" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.97" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ee99da9c5ba11bd675621338ef6fa52296b76b83305e9b6e5c77d4c286d6d49" + [[package]] name = "winapi" version = "0.3.9" @@ -795,6 +1080,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.0", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -927,6 +1221,25 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +[[package]] +name = "x509-certificate" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57b9f8bcae7c1f36479821ae826d75050c60ce55146fd86d3553ed2573e2762" +dependencies = [ + "bcder", + "bytes", + "chrono", + "der", + "hex", + "pem", + "ring", + "signature", + "spki", + "thiserror 1.0.69", + "zeroize", +] + [[package]] name = "yansi-term" version = "0.1.2" @@ -935,3 +1248,23 @@ checksum = "fe5c30ade05e61656247b2e334a031dfd0cc466fadef865bdcdea8d537951bf1" dependencies = [ "winapi", ] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml index cc76c28..a1b4b37 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,7 @@ tokio = { version = "1.35.1", optional = true, features = [ "sync", "time", ] } +x509-certificate = { version = "0.24.0", optional = true } [dev-dependencies] anyhow = "1.0.79" @@ -42,6 +43,7 @@ time = { version = "0.3.31", features = ["macros"] } # Enable multi-threaded runtime in examples to increase the chances of finding # problems with our use of open62541. tokio = { version = "1.35.1", features = ["macros", "rt-multi-thread"] } +itertools = "0.13.0" [features] default = ["serde", "time", "tokio"] @@ -49,6 +51,7 @@ mbedtls = ["open62541-sys/mbedtls"] serde = ["dep:serde", "dep:serde_json", "time?/formatting", "time?/serde"] time = ["dep:time"] tokio = ["dep:tokio"] +x509 = ["dep:x509-certificate"] [lints.rust] future_incompatible = { level = "warn", priority = -1 } @@ -185,8 +188,8 @@ name = "server_method_callback" [[example]] name = "ssl_create_certificate" -required-features = ["mbedtls"] +required-features = ["mbedtls", "x509"] [[example]] name = "ssl_fetch_certificate" -required-features = ["mbedtls"] +required-features = ["mbedtls", "x509"] diff --git a/examples/ssl_create_certificate.rs b/examples/ssl_create_certificate.rs index cea32c9..3e239ae 100644 --- a/examples/ssl_create_certificate.rs +++ b/examples/ssl_create_certificate.rs @@ -34,7 +34,39 @@ fn main() -> anyhow::Result<()> { ) .context("create certificate")?; - println!("Certificate: {certificate:?}"); + let certificate = certificate.into_x509().context("parse certificate")?; + + println!( + "Subject common name: {:?}", + certificate.subject_common_name() + ); + println!("Key algorithm: {:?}", certificate.key_algorithm()); + println!( + "Signature algorithm: {:?}", + certificate.signature_algorithm() + ); + println!( + "Validity not before: {:?}", + certificate.validity_not_before() + ); + println!("Validity not after: {:?}", certificate.validity_not_after()); + println!( + "Fingerprint (SHA-1): {:?}", + certificate + .sha1_fingerprint() + .context("SHA-1 fingerprint")? + ); + println!( + "Fingerprint (SHA-256): {:?}", + certificate + .sha256_fingerprint() + .context("SHA-256 fingerprint")? + ); + println!(); + println!( + "{}", + certificate.encode_pem().context("encode certificate")? + ); Ok(()) } diff --git a/examples/ssl_fetch_certificate.rs b/examples/ssl_fetch_certificate.rs index e64a107..c95471e 100644 --- a/examples/ssl_fetch_certificate.rs +++ b/examples/ssl_fetch_certificate.rs @@ -1,21 +1,68 @@ use anyhow::Context as _; -use open62541::{Certificate, PrivateKey}; - -// These files have been created with `client_ssl.sh`. -const CERTIFICATE_PEM: &[u8] = include_bytes!("client_certificate.pem"); -const PRIVATE_KEY_PEM: &[u8] = include_bytes!("client_private_key.pem"); +use itertools::Itertools as _; +use open62541::{Certificate, ClientBuilder}; fn main() -> anyhow::Result<()> { env_logger::init(); - let certificate = Certificate::from_bytes(CERTIFICATE_PEM); - let private_key = PrivateKey::from_bytes(PRIVATE_KEY_PEM); + let endpoint_descriptions = ClientBuilder::default() + .get_endpoints("opc.tcp://localhost") + .context("get endpoints")?; + + let server_certificates = endpoint_descriptions + .iter() + .filter_map(|endpoint_description| { + endpoint_description + .server_certificate() + .as_bytes() + .map(|bytes| Certificate::from_bytes(bytes).into_x509()) + }) + .collect::, _>>() + .context("parse certificates")?; + + // Include consecutive (!) identical certificates only once. + let unique_certificates = server_certificates + .into_iter() + .dedup_by(|a, b| a.serial_number_asn1() == b.serial_number_asn1()) + .collect::>(); - let certificate = - open62541::fetch_server_certificate(&certificate, &private_key, "opc.tcp://localhost") - .context("fetch certificate")?; + println!("Found {} server certificate(s)", unique_certificates.len()); - println!("Certificate: {certificate:?}"); + for (index, certificate) in unique_certificates.iter().enumerate() { + println!(); + println!("# Certificate {}", index + 1); + println!( + "Subject common name: {:?}", + certificate.subject_common_name() + ); + println!("Key algorithm: {:?}", certificate.key_algorithm()); + println!( + "Signature algorithm: {:?}", + certificate.signature_algorithm() + ); + println!( + "Validity not before: {:?}", + certificate.validity_not_before() + ); + println!("Validity not after: {:?}", certificate.validity_not_after()); + println!( + "Fingerprint (SHA-1): {:?}", + certificate + .sha1_fingerprint() + .context("SHA-1 fingerprint")? + ); + println!( + "Fingerprint (SHA-256): {:?}", + certificate + .sha256_fingerprint() + .context("SHA-256 fingerprint")? + ); + println!(); + println!( + "{}", + certificate.encode_pem().context("encode certificate")? + ); + } Ok(()) } diff --git a/src/client.rs b/src/client.rs index 5bf0898..daf9cea 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,6 +1,9 @@ -use std::{ffi::CString, time::Duration}; +use std::{ffi::CString, ptr, time::Duration}; -use open62541_sys::{UA_CertificateVerification_AcceptAll, UA_ClientConfig, UA_Client_connect}; +use open62541_sys::{ + UA_CertificateVerification_AcceptAll, UA_ClientConfig, UA_Client_connect, + UA_Client_getEndpoints, +}; use crate::{ua, DataType as _, Error, Result}; @@ -199,6 +202,51 @@ impl ClientBuilder { Ok(client) } + /// Connects to OPC UA server and returns endpoints. + /// + /// # Errors + /// + /// This fails when the target server is not reachable. + /// + /// # Panics + /// + /// The server URL must not contain any NUL bytes. + pub fn get_endpoints(self, server_url: &str) -> Result> { + log::info!("Getting endpoints of server {server_url}"); + + let server_url = CString::new(server_url).expect("server URL does not contain NUL bytes"); + + let mut client = self.build(); + let endpoint_descriptions: Option>; + + let status_code = ua::StatusCode::new({ + let mut endpoint_descriptions_size = 0; + let mut endpoint_descriptions_ptr = ptr::null_mut(); + let result = unsafe { + UA_Client_getEndpoints( + client.0.as_mut_ptr(), + server_url.as_ptr(), + &mut endpoint_descriptions_size, + &mut endpoint_descriptions_ptr, + ) + }; + // Wrap array result immediately to not leak memory when leaving function early as with + // `?` below. + endpoint_descriptions = ua::Array::::from_raw_parts( + endpoint_descriptions_size, + endpoint_descriptions_ptr, + ); + result + }); + Error::verify_good(&status_code)?; + + let Some(endpoint_descriptions) = endpoint_descriptions else { + return Err(Error::internal("expected array of endpoint descriptions")); + }; + + Ok(endpoint_descriptions) + } + /// Builds OPC UA client. #[must_use] fn build(self) -> Client { diff --git a/src/lib.rs b/src/lib.rs index 6b617c8..58cc018 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -232,7 +232,7 @@ mod userdata; mod value; #[cfg(feature = "mbedtls")] -pub use self::ssl::{create_certificate, fetch_server_certificate, Certificate, PrivateKey}; +pub use self::ssl::{create_certificate, Certificate, PrivateKey}; #[cfg(feature = "tokio")] pub use self::{ async_client::AsyncClient, diff --git a/src/ssl.rs b/src/ssl.rs index 721fe00..a75bf41 100644 --- a/src/ssl.rs +++ b/src/ssl.rs @@ -1,8 +1,8 @@ -use std::{cell::RefCell, fmt, ptr, rc::Rc}; +use std::{fmt, ptr}; use open62541_sys::UA_CreateCertificate; -use crate::{ua, ClientBuilder, CustomCertificateVerification, DataType, Error, Result}; +use crate::{ua, DataType, Error}; /// Certificate in [DER] or [PEM] format. /// @@ -36,6 +36,27 @@ impl Certificate { unsafe { self.0.as_bytes_unchecked() } } + /// Parses certificate. + /// + /// # Errors + /// + /// This fails when the certificate cannot be parsed or is invalid. + #[cfg(feature = "x509")] + pub fn into_x509( + self, + ) -> Result { + use x509_certificate::{X509Certificate, X509CertificateError}; + + // Apply heuristic to get certificates from both DER and PEM format. Try PEM first because + // the implementation first extracts DER data from PEM and can tell us whether this failed + // (or the certificate itself was invalid). + match X509Certificate::from_pem(self.as_bytes()) { + Ok(certificate) => Ok(certificate), + Err(X509CertificateError::PemDecode(_)) => X509Certificate::from_der(self.as_bytes()), + Err(err) => Err(err), + } + } + pub(crate) const fn as_byte_string(&self) -> &ua::ByteString { &self.0 } @@ -117,7 +138,7 @@ pub fn create_certificate( subject_alt_name: &ua::Array, cert_format: &ua::CertificateFormat, params: Option<&ua::KeyValueMap>, -) -> Result<(Certificate, PrivateKey)> { +) -> crate::Result<(Certificate, PrivateKey)> { // Create logger that forwards to Rust `log`. It is only used for the function call below and it // will be cleaned up at the end of the function. let mut logger = ua::Logger::rust_log(); @@ -156,64 +177,3 @@ pub fn create_certificate( Ok((certificate, private_key)) } - -/// Connects to remote server and fetches certificate -/// -/// # Errors -/// -/// This fails when the connection cannot be established at all or the server does not offer secure -/// communication. -pub fn fetch_server_certificate( - local_certificate: &Certificate, - private_key: &PrivateKey, - endpoint_url: &str, -) -> Result { - let (fetch_server_certificate_verification, certificate) = - FetchServerCertificateVerification::new(); - - let certificate_verification = - ua::CertificateVerification::custom(fetch_server_certificate_verification); - - let _unused = ClientBuilder::default_encryption(local_certificate, private_key)? - .certificate_verification(certificate_verification) - .connect(endpoint_url)?; - - certificate - .take() - .ok_or(Error::internal("did not receive certificate")) -} - -#[derive(Debug)] -struct FetchServerCertificateVerification { - certificate: Rc>>, -} - -impl FetchServerCertificateVerification { - fn new() -> (Self, Rc>>) { - let certificate = Rc::new(RefCell::new(None)); - - ( - Self { - certificate: Rc::clone(&certificate), - }, - certificate, - ) - } -} - -impl CustomCertificateVerification for FetchServerCertificateVerification { - fn verify_certificate(&self, certificate: &ua::ByteString) -> ua::StatusCode { - self.certificate - .replace(Certificate::from_byte_string(certificate.clone())); - - ua::StatusCode::GOOD - } - - fn verify_application_uri( - &self, - _certificate: &ua::ByteString, - _application_uri: &ua::String, - ) -> ua::StatusCode { - ua::StatusCode::GOOD - } -}