aditjind_crate/
cert.rs

1/*
2 * Licensed to Elasticsearch B.V. under one or more contributor
3 * license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright
5 * ownership. Elasticsearch B.V. licenses this file to you under
6 * the Apache License, Version 2.0 (the "License"); you may
7 * not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 *	http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied.  See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19
20/*
21 * SPDX-License-Identifier: Apache-2.0
22 *
23 * The OpenSearch Contributors require contributions made to
24 * this file be licensed under the Apache-2.0 license or a
25 * compatible open source license.
26 *
27 * Modifications Copyright OpenSearch Contributors. See
28 * GitHub history for details.
29 */
30
31//! Certificate components
32
33use crate::error::Error;
34use std::{
35    io::{BufRead, BufReader, Cursor},
36    ops::Deref,
37    vec,
38};
39
40/// Validation applied to a SSL/TLS certificate, to establish a HTTPS connection.
41///
42/// This requires the `native-tls`, or `rustls-tls` feature to be enabled. `native-tls` is
43/// configured by default.
44///
45/// # Examples
46///
47/// ## Default
48///
49/// The client is configured by default to validate that a certificate used to establish a
50/// HTTPS connection is one that is signed by a trusted Certificate Authority (CA) and passes
51/// hostname verification. [CertificateValidation::Default] is a provided variant only to
52/// be able to change from another validation mode back to the default.
53///
54/// ## Full validation
55///
56/// With OpenSearch running at `https://example.com`, configured to use a certificate generated
57/// with your own Certificate Authority (CA), and where the certificate contains a CommonName (CN)
58/// or Subject Alternative Name (SAN) that matches the hostname of OpenSearch
59#[cfg_attr(
60    any(feature = "native-tls", feature = "rustls-tls"),
61    doc = r##"
62```rust,no_run
63# use opensearch::{
64#     auth::Credentials,
65#     cert::{Certificate,CertificateValidation},
66#     Error, OpenSearch,
67#     http::transport::{TransportBuilder,SingleNodeConnectionPool},
68# };
69# use std::fs::File;
70# use std::io::Read;
71# use url::Url;
72# async fn doc() -> Result<(), Box<dyn std::error::Error>> {
73let url = Url::parse("https://example.com")?;
74let conn_pool = SingleNodeConnectionPool::new(url);
75
76// load the CA certificate
77let mut buf = Vec::new();
78File::open("my_ca_cert.pem")?
79    .read_to_end(&mut buf)?;
80let cert = Certificate::from_pem(&buf)?;
81
82let transport = TransportBuilder::new(conn_pool)
83    .cert_validation(CertificateValidation::Full(cert))
84    .build()?;
85let client = OpenSearch::new(transport);
86let _response = client.ping().send().await?;
87# Ok(())
88# }
89```
90"##
91)]
92/// ## Certificate validation
93///
94/// This requires the `native-tls` feature to be enabled.
95///
96/// With OpenSearch running at `https://example.com`, configured to use a certificate generated
97/// with your own Certificate Authority (CA)
98#[cfg_attr(
99    feature = "native-tls",
100    doc = r##"
101```rust,no_run
102# use opensearch::{
103#     auth::Credentials,
104#     cert::{Certificate,CertificateValidation},
105#     Error, OpenSearch,
106#     http::transport::{TransportBuilder,SingleNodeConnectionPool},
107# };
108# use std::fs::File;
109# use std::io::Read;
110# use url::Url;
111# async fn doc() -> Result<(), Box<dyn std::error::Error>> {
112let url = Url::parse("https://example.com")?;
113let conn_pool = SingleNodeConnectionPool::new(url);
114
115// load the CA certificate
116let mut buf = Vec::new();
117File::open("my_ca_cert.pem")?
118    .read_to_end(&mut buf)?;
119let cert = Certificate::from_pem(&buf)?;
120let transport = TransportBuilder::new(conn_pool)
121    .cert_validation(CertificateValidation::Certificate(cert))
122    .build()?;
123let client = OpenSearch::new(transport);
124let _response = client.ping().send().await?;
125# Ok(())
126# }
127```
128"##
129)]
130/// ## No validation
131///
132/// No validation is performed on the certificate provided by the server.
133/// **Use on production clusters is strongly discouraged**
134///
135/// ```rust,no_run
136/// # use opensearch::{
137/// #     auth::Credentials,
138/// #     cert::{Certificate,CertificateValidation},
139/// #     Error, OpenSearch,
140/// #     http::transport::{TransportBuilder,SingleNodeConnectionPool},
141/// # };
142/// # use std::fs::File;
143/// # use std::io::Read;
144/// # use url::Url;
145/// # async fn doc() -> Result<(), Box<dyn std::error::Error>> {
146/// let url = Url::parse("https://example.com")?;
147/// let conn_pool = SingleNodeConnectionPool::new(url);
148/// let transport = TransportBuilder::new(conn_pool)
149///     .cert_validation(CertificateValidation::None)
150///     .build()?;
151/// let client = OpenSearch::new(transport);
152/// let _response = client.ping().send().await?;
153/// # Ok(())
154/// # }
155/// ```
156#[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
157pub enum CertificateValidation {
158    /// Default validation of the certificate, which validates that the certificate provided by the
159    /// server is signed by a trusted Certificate Authority (CA) and also verifies that the server’s hostname
160    /// (or IP address) matches the names identified by the CommonName (CN) or Subject Alternative
161    /// Name (SAN) within the certificate.
162    ///
163    /// A trusted CA is one that is trusted by the operating system on which the client is running,
164    /// which typically means that the CA certificate is in the certificate/truststore of the
165    /// operating system. This is the default mode of operation.
166    Default,
167    /// Full validation of the certificate, which validates that the certificate provided by the
168    /// server is signed by a trusted Certificate Authority (CA) and also verifies that the server’s hostname
169    /// (or IP address) matches the names identified by the CommonName (CN) or Subject Alternative
170    /// Name (SAN) within the certificate.
171    ///
172    /// This is useful for self-signed certificates generated by your own CA,
173    /// where the certificate contains the CommonName (CN) or a Subject Alternative Name (SAN)
174    /// that matches the server hostname.
175    ///
176    /// Typically, the certificate provided to the client is the Certificate Authority (CA)
177    /// used to sign the certificate used by the server.
178    Full(Certificate),
179    /// Validates that the certificate provided by the server is signed by a trusted
180    /// Certificate Authority (CA), but does not perform hostname verification.
181    ///
182    /// This is useful for self-signed certificates generated by your own CA
183    /// that **do not** contain the CommonName (CN) or a Subject Alternative Name (SAN)
184    /// that matches the server hostname.
185    ///
186    /// Typically, the certificate provided to the client will be the Certificate Authority (CA)
187    /// used to sign the certificate used by the server.
188    ///
189    /// # Optional
190    ///
191    /// This requires the `native-tls` feature to be enabled.
192    #[cfg(feature = "native-tls")]
193    Certificate(Certificate),
194    /// No validation is performed on the certificate provided by the server.
195    ///
196    /// This disables many of the security benefits of SSL/TLS and should only be used after very
197    /// careful consideration. It is primarily intended as a temporary diagnostic mechanism when
198    /// attempting to resolve TLS errors, and **its use on production clusters is strongly discouraged**.
199    None,
200}
201
202/// Start marker for PEM encoded certificates.
203const BEGIN_CERTIFICATE: &str = "-----BEGIN CERTIFICATE-----";
204
205/// End marker for PEM encoded certificates.
206const END_CERTIFICATE: &str = "-----END CERTIFICATE-----";
207
208/// Represents a server X509 certificate chain.
209#[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
210pub struct Certificate(Vec<reqwest::Certificate>);
211
212#[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
213impl Certificate {
214    /// Create a `Certificate` chain from PEM encoded certificates.
215    ///
216    /// The `pem` input data may contain one or more PEM encoded CA certificates.
217    pub fn from_pem(pem: &[u8]) -> Result<Self, Error> {
218        let reader = BufReader::new(Cursor::new(pem));
219
220        // Split the PEM cert into parts without validating the
221        // contents as this will be done by the
222        // `reqwest::Certificate::from_pem` call itself.
223        let mut certs = Vec::new();
224        let mut cert = Vec::new();
225        let mut begin = false;
226        for line in reader.lines() {
227            let line = line?;
228            match line.as_ref() {
229                BEGIN_CERTIFICATE if !begin => {
230                    begin = true;
231                    cert.push(line);
232                }
233                END_CERTIFICATE if begin => {
234                    begin = false;
235                    cert.push(line);
236                    certs.push(reqwest::Certificate::from_pem(cert.join("\n").as_bytes())?);
237                    cert = Vec::new();
238                }
239                _ if begin => cert.push(line),
240                _ => {}
241            }
242        }
243
244        if certs.is_empty() {
245            Err(crate::error::lib(
246                "could not find PEM certificate in input data",
247            ))
248        } else {
249            Ok(Self(certs))
250        }
251    }
252
253    /// Create a `Certificate` from a binary DER encoded certificate.
254    pub fn from_der(der: &[u8]) -> Result<Self, Error> {
255        Ok(Self(vec![reqwest::Certificate::from_der(der)?]))
256    }
257
258    /// Append a `Certificate` to the chain.
259    pub fn append(&mut self, mut cert: Self) {
260        self.0.append(&mut cert.0);
261    }
262}
263
264#[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
265impl IntoIterator for Certificate {
266    type Item = reqwest::Certificate;
267    type IntoIter = vec::IntoIter<Self::Item>;
268
269    fn into_iter(self) -> Self::IntoIter {
270        self.0.into_iter()
271    }
272}
273
274#[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
275impl Deref for Certificate {
276    type Target = Vec<reqwest::Certificate>;
277
278    fn deref(&self) -> &Self::Target {
279        &self.0
280    }
281}