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}