diff --git a/CHANGELOG.md b/CHANGELOG.md index e4d7876..527ae19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 5.1.0 +- Added `rustls` backend support via two new feature flags: `client-rustls` and `async-client-rustls` +- Only `async-client-tls` feature is enabled by default + ## 5.0.5 - Added IPP attributes required by Windows Print Protection mode diff --git a/Cargo.toml b/Cargo.toml index d1c0b39..5ba3790 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ members = [ "ipp", "util", "examples" ] resolver = "2" [workspace.package] -version = "5.0.5" +version = "5.1.0" authors = ["Dmitry Pankratov "] license = "MIT/Apache-2.0" repository = "https://github.com/ancwrd1/ipp.rs" diff --git a/examples/Cargo.toml b/examples/Cargo.toml index fcf1142..e403801 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -36,7 +36,6 @@ path = "src/print-job.rs" name = "print-job" [dependencies] -ipp = { path = "../ipp", version = "5.0.5" } +ipp = { path = "../ipp", version = "5.1.0", features = ["client-tls"] } tokio = { version = "1", features = ["macros", "fs", "rt-multi-thread"] } tokio-util = { version = "0.7", features = ["compat"] } - diff --git a/ipp/Cargo.toml b/ipp/Cargo.toml index 9b7b632..a111902 100644 --- a/ipp/Cargo.toml +++ b/ipp/Cargo.toml @@ -26,6 +26,7 @@ serde = { version = "1", optional = true, features = ["derive"] } ureq = { version = "2", default-features = false, optional = true } native-tls = { version = "0.2", optional = true } base64 = { version = "0.22", optional = true } +rustls-native-certs = { version = "0.8", optional = true } [dependencies.futures-util] version = "0.3" @@ -39,6 +40,13 @@ optional = true default-features = false features = ["stream"] +[dependencies.rustls] +version = "0.23" +optional = true +default-features = false +features = ["ring", "log", "tls12", "std"] + + [dependencies.tokio-util] version = "0.7" optional = true @@ -48,10 +56,12 @@ features = ["io", "compat"] tokio = { version = "1", features = ["macros", "rt-multi-thread"] } [features] -default = ["async-client", "client", "async-client-tls", "client-tls"] +default = ["async-client-tls"] serde = ["dep:serde", "bytes/serde"] async = ["futures-util", "futures-executor"] async-client = ["async", "reqwest", "tokio-util", "base64"] client = ["ureq", "base64"] async-client-tls = ["async-client", "native-tls", "reqwest/native-tls"] client-tls = ["client", "native-tls", "ureq/native-tls"] +async-client-rustls = ["async-client", "rustls", "reqwest/rustls-tls-native-roots"] +client-rustls = ["client", "rustls", "rustls-native-certs", "ureq/tls"] diff --git a/ipp/README.md b/ipp/README.md index 68d5895..c28a79a 100644 --- a/ipp/README.md +++ b/ipp/README.md @@ -13,15 +13,18 @@ It supports both synchronous and asynchronous operations (requests and responses The following build-time features are supported: -* `async` - enables asynchronous APIs -* `async-client` - enables asynchronous IPP client based on reqwest crate, implies `async` feature -* `client` - enables blocking IPP client based on ureq crate -* `async-client-tls` - enables asynchronous IPP client with TLS -* `client-tls` - enables blocking IPP client with TLS - -By default, all features are enabled. Use `default-features=false` dependency option to disable them. - -[Documentation](https://ancwrd1.github.io/ipp.rs/doc/ipp/) +* `async` - enables asynchronous APIs. +* `async-client` - enables asynchronous IPP client based on `reqwest` crate, implies `async` feature. +* `client` - enables blocking IPP client based on `ureq` crate. +* `async-client-tls` - enables asynchronous IPP client with TLS, using native-tls backend. Implies `async-client` feature. +* `client-tls` - enables blocking IPP client with TLS, using native-tls backend. Implies `client` feature. +* `async-client-rustls` - enables asynchronous IPP client with TLS, using rustls backend. Implies `async-client` feature. +* `client-rustls` - enables blocking IPP client with TLS, using rustls backend. Implies `client` feature. + +By default, the following features are enabled: `async-client-tls`. +Use `default-features=false` dependency option to disable them. + +[Documentation](https://docs.rs/ipp/latest/ipp/) Usage example for async client: diff --git a/ipp/src/client.rs b/ipp/src/client.rs index 6bcea1e..e75acc8 100644 --- a/ipp/src/client.rs +++ b/ipp/src/client.rs @@ -158,7 +158,7 @@ pub mod non_blocking { builder = builder.timeout(timeout); } - #[cfg(feature = "async-client-tls")] + #[cfg(any(feature = "async-client-tls", feature = "async-client-rustls"))] { if self.0.ignore_tls_errors { builder = builder @@ -172,6 +172,11 @@ pub mod non_blocking { } } + #[cfg(feature = "async-client-rustls")] + { + builder = builder.use_rustls_tls(); + } + let mut req_builder = builder .user_agent(USER_AGENT) .build()? @@ -265,6 +270,37 @@ pub mod blocking { builder = builder.tls_connector(std::sync::Arc::new(tls_connector)); } + #[cfg(feature = "client-rustls")] + { + use rustls::pki_types::pem::PemObject; + let mut root_store = rustls::RootCertStore::empty(); + let certs = rustls_native_certs::load_native_certs(); + root_store.add_parsable_certificates(certs.certs); + + for data in &self.0.ca_certs { + let cert = rustls::pki_types::CertificateDer::<'static>::from_pem_slice(data) + .unwrap_or_else(|_| rustls::pki_types::CertificateDer::from_slice(data)); + root_store.add(cert)?; + } + + let secure_config = rustls::ClientConfig::builder() + .with_root_certificates(root_store) + .with_no_client_auth(); + + let config = if self.0.ignore_tls_errors { + rustls::ClientConfig::builder() + .dangerous() + .with_custom_certificate_verifier(std::sync::Arc::new(verifiers::NoVerifier( + secure_config.crypto_provider().clone(), + ))) + .with_no_client_auth() + } else { + secure_config + }; + + builder = builder.tls_config(std::sync::Arc::new(config)); + } + let agent = builder.user_agent(USER_AGENT).build(); let mut req = agent @@ -282,6 +318,52 @@ pub mod blocking { parser.parse().map_err(IppError::from) } } + + #[cfg(feature = "client-rustls")] + mod verifiers { + use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}; + use rustls::pki_types::{CertificateDer, ServerName, UnixTime}; + use rustls::{crypto::CryptoProvider, DigitallySignedStruct, Error, SignatureScheme}; + use std::sync::Arc; + + #[derive(Debug)] + pub struct NoVerifier(pub Arc); + + impl ServerCertVerifier for NoVerifier { + fn verify_server_cert( + &self, + _end_entity: &CertificateDer, + _intermediates: &[CertificateDer], + _server_name: &ServerName, + _ocsp_response: &[u8], + _now: UnixTime, + ) -> Result { + Ok(ServerCertVerified::assertion()) + } + + fn verify_tls12_signature( + &self, + _message: &[u8], + _cert: &CertificateDer, + _dss: &DigitallySignedStruct, + ) -> Result { + Ok(HandshakeSignatureValid::assertion()) + } + + fn verify_tls13_signature( + &self, + _message: &[u8], + _cert: &CertificateDer, + _dss: &DigitallySignedStruct, + ) -> Result { + Ok(HandshakeSignatureValid::assertion()) + } + + fn supported_verify_schemes(&self) -> Vec { + self.0.signature_verification_algorithms.supported_schemes() + } + } + } } #[cfg(test)] diff --git a/ipp/src/error.rs b/ipp/src/error.rs index 7cdae23..071d624 100644 --- a/ipp/src/error.rs +++ b/ipp/src/error.rs @@ -60,4 +60,9 @@ pub enum IppError { #[cfg(any(feature = "async-client-tls", feature = "client-tls"))] /// TLS error TlsError(#[from] native_tls::Error), + + #[error(transparent)] + #[cfg(feature = "client-rustls")] + /// TLS error + RustlsError(#[from] rustls::Error), } diff --git a/ipp/src/lib.rs b/ipp/src/lib.rs index fe30ac5..911cecf 100644 --- a/ipp/src/lib.rs +++ b/ipp/src/lib.rs @@ -6,19 +6,19 @@ //! * using any third-party HTTP client and send the serialized request manually. //! //! This crate supports both synchronous and asynchronous operations. The following feature flags are supported: -//! * `async` - enables asynchronous APIs -//! * `async-client` - enables asynchronous IPP client based on `reqwest` crate, implies `async` feature -//! * `client` - enables blocking IPP client based on `ureq` crate -//! * `async-client-tls` - enables asynchronous IPP client with TLS support -//! * `client-tls` - enables blocking IPP client with TLS support -//! -//! By default, all features are enabled. +//! * `async` - enables asynchronous APIs. +//! * `async-client` - enables asynchronous IPP client based on `reqwest` crate, implies `async` feature. +//! * `client` - enables blocking IPP client based on `ureq` crate. +//! * `async-client-tls` - enables asynchronous IPP client with TLS, using native-tls backend. Implies `async-client` feature. +//! * `client-tls` - enables blocking IPP client with TLS, using native-tls backend. Implies `client` feature. +//! * `async-client-rustls` - enables asynchronous IPP client with TLS, using rustls backend. Implies `async-client` feature. +//! * `client-rustls` - enables blocking IPP client with TLS, using rustls backend. Implies `client` feature. //! +//! By default, the following feature is enabled: `async-client-tls`. //! //! Implementation notes: //! * all RFC IPP values are supported including arrays and collections, for both de- and serialization. //! * this crate is also suitable for building IPP servers, however the example is not provided yet. -//! * some operations (e.g. CUPS-specific) require authorization which can be supplied in the printer URI. //! //! Usage examples: //! diff --git a/util/Cargo.toml b/util/Cargo.toml index 8038269..e909fb1 100644 --- a/util/Cargo.toml +++ b/util/Cargo.toml @@ -15,5 +15,5 @@ name = "ipputil" path = "src/main.rs" [dependencies] -ipp = { path = "../ipp", version = "5.0.5", default-features = false, features = ["client-tls"] } +ipp = { path = "../ipp", version = "5.1.0", default-features = false, features = ["client-tls"] } clap = { version = "4", features = ["derive"] }