├── .github ├── dependabot.yml └── workflows │ └── ci.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── build.rs ├── examples ├── google-connect.rs ├── simple-server-pkcs8.rs └── simple-server.rs └── src ├── imp ├── openssl.rs ├── schannel.rs └── security_framework.rs ├── lib.rs └── test.rs /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "13:00" 8 | open-pull-requests-limit: 10 9 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - master 7 | push: 8 | branches: 9 | - master 10 | 11 | env: 12 | RUSTFLAGS: -Dwarnings 13 | RUST_BACKTRACE: 1 14 | 15 | jobs: 16 | rustfmt: 17 | name: rustfmt 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: actions/checkout@v2 21 | - uses: sfackler/actions/rustup@master 22 | - uses: sfackler/actions/rustfmt@master 23 | 24 | windows: 25 | strategy: 26 | fail-fast: false 27 | matrix: 28 | os: 29 | - ubuntu-latest 30 | - windows-latest 31 | - macos-latest 32 | name: test-${{ matrix.os }} 33 | runs-on: ${{ matrix.os }} 34 | steps: 35 | - uses: actions/checkout@v2 36 | - uses: sfackler/actions/rustup@master 37 | with: 38 | version: 1.80.0 39 | - run: echo "::set-output name=version::$(rustc --version)" 40 | id: rust-version 41 | - uses: actions/cache@v1 42 | with: 43 | path: ~/.cargo/registry/index 44 | key: index-${{ runner.os }}-${{ github.run_number }} 45 | restore-keys: | 46 | index-${{ runner.os }}- 47 | - run: cargo generate-lockfile 48 | - uses: actions/cache@v1 49 | with: 50 | path: ~/.cargo/registry/cache 51 | key: registry-${{ runner.os }}-${{ steps.rust-version.outputs.version }}-${{ hashFiles('Cargo.lock') }} 52 | - run: cargo fetch 53 | - uses: actions/cache@v1 54 | with: 55 | path: target 56 | key: target-${{ runner.os }}-${{ steps.rust-version.outputs.version }}-${{ hashFiles('Cargo.lock') }} 57 | - run: cargo test --features vendored 58 | - run: cargo test --features vendored 59 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | .idea 4 | *.iml 5 | .vscode 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## [Unreleased] 4 | 5 | ## [v0.2.12] 6 | 7 | ### Fixed 8 | 9 | * Stopped using a deprecated openssl-probe API. 10 | 11 | ## [v0.2.11] 12 | 13 | ### Fixed 14 | 15 | * Removed an unused build dependency. 16 | 17 | ## [v0.2.10] 18 | 19 | ### Fixed 20 | 21 | * Fixed the build for iOS. 22 | 23 | ## [v0.2.9] 24 | 25 | ### Added 26 | 27 | * Added `Identity::from_pkcs8`. 28 | 29 | ## [v0.2.8] 30 | 31 | ### Fixed 32 | 33 | * Fixed an off by one error in the schannel backend's handling of max_protocol_version. 34 | 35 | ## [v0.2.7] 36 | 37 | ### Added 38 | 39 | * Added support for ALPN in client APIs flagged under the `alpn` Cargo feature. 40 | 41 | ## [v0.2.6] 42 | 43 | ### Fixed 44 | 45 | * Fixed compilation on iOS. 46 | 47 | ## [v0.2.5] 48 | 49 | ### Added 50 | 51 | * Added `TlsConnectorBuilder::disable_built_in_roots` to only trust root certificates explicitly 52 | added to the builder. 53 | 54 | ### Updated 55 | 56 | * Updated security-framework to 2.0. 57 | 58 | ## [v0.2.4] 59 | 60 | ### Added 61 | 62 | * Added a `Clone` implementation for `Identity`. 63 | 64 | ### Updated 65 | 66 | * Updated security-framework to 0.4. 67 | 68 | ## [v0.2.3] 69 | 70 | ### Fixed 71 | 72 | * Adding an already-trusted certificate to the root certificate set no longer triggers an error 73 | with OpenSSL. 74 | 75 | ### Updated 76 | 77 | * Updated security-framework to 0.3. 78 | 79 | ## [v0.2.2] 80 | 81 | ### Fixed 82 | 83 | * Failure to load a root certificate on Android now logs a message rather than producing an error. 84 | * Fixed ordering of the certificate chain in the OpenSSL backend. 85 | 86 | ## [v0.2.1] 87 | 88 | ### Added 89 | 90 | * The `vendored` Cargo feature will cause the crate to compile and statically link to a vendored 91 | copy of OpenSSL on platforms that use that backend. 92 | 93 | ## [v0.2.0] 94 | 95 | ### Added 96 | 97 | * The `openssl_probe` crate is now used with the OpenSSL backend so that trusted root certificates 98 | will automatically be detected when statically linking to OpenSSL. 99 | * Root certificates are now automatically loaded from the Android trust root. 100 | * Added `Certificate::to_der` to serialize an X509 certificate to DER. 101 | * Added `TlsConnectorBuilder::danger_accept_invalid_certs` to disable certificate verification. 102 | * Added `TlsAcceptor::new` and `TlsConnector::new` to easily create an acceptor/connector with 103 | default settings. 104 | * Added `TlsStream::peer_certificate` to obtain the peer's leaf certificate. 105 | * Added `TlsStream::tls_server_end_point` to retrieve RFC 5929 tls-server-end-point channel binding 106 | data. 107 | 108 | ### Changed 109 | 110 | * Upgraded to `openssl` 0.10 and `security-framework` 0.2. 111 | * `Pkcs12` has been renamed to `Identity`, and `Pkcs12::from_der` has been renamed to 112 | `Identity::from_pkcs12`. 113 | * `HandshakeError::Interrupted` has been renamed to `HandshakeError::WouldBlock`. 114 | * `TlsConnectorBuilder` and `TlsAcceptorBuilder` are now "traditional"-style builders. Their methods 115 | are now infallible and return `&mut Self` to allow them to be chained together. 116 | * `supported_protocols` has been replaced by `min_protocol_version` and `max_protocol_version` on 117 | `TlsConnectorBuilder` and `TlsAcceptorBuilder`. 118 | * SNI and hostname verification are now configured separately via `TlsConnectorBuilder::use_sni` and 119 | `TlsConnectorBuilder::danger_accept_invalid_hostnames`. They replace the 120 | `TlsConnector::danger_connect_without_providing_domain_for_certificate_verification_and_server_name_indication` 121 | method, which has been removed. 122 | 123 | ### Removed 124 | 125 | * The backend-specific extension traits have been removed. We want to avoid exposing the specific 126 | version of the backend library in the public API to provide more flexibility. 127 | 128 | ## Older 129 | 130 | Look at the [release tags] for information about older releases. 131 | 132 | [Unreleased]: https://github.com/sfackler/rust-native-tls/compare/v0.2.11...master 133 | [v0.2.11]: https://github.com/sfackler/rust-native-tls/compare/v0.2.10...v0.2.11 134 | [v0.2.10]: https://github.com/sfackler/rust-native-tls/compare/v0.2.9...v0.2.10 135 | [v0.2.9]: https://github.com/sfackler/rust-native-tls/compare/v0.2.8...v0.2.9 136 | [v0.2.8]: https://github.com/sfackler/rust-native-tls/compare/v0.2.7...v0.2.8 137 | [v0.2.7]: https://github.com/sfackler/rust-native-tls/compare/v0.2.6...v0.2.7 138 | [v0.2.6]: https://github.com/sfackler/rust-native-tls/compare/v0.2.5...v0.2.6 139 | [v0.2.5]: https://github.com/sfackler/rust-native-tls/compare/v0.2.4...v0.2.5 140 | [v0.2.4]: https://github.com/sfackler/rust-native-tls/compare/v0.2.3...v0.2.4 141 | [v0.2.3]: https://github.com/sfackler/rust-native-tls/compare/v0.2.2...v0.2.3 142 | [v0.2.2]: https://github.com/sfackler/rust-native-tls/compare/v0.2.1...v0.2.2 143 | [v0.2.1]: https://github.com/sfackler/rust-native-tls/compare/v0.2.0...v0.2.1 144 | [v0.2.0]: https://github.com/sfackler/rust-native-tls/compare/v0.1.5...v0.2.0 145 | [release tags]: https://github.com/sfackler/rust-native-tls/releases 146 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "native-tls" 3 | version = "0.2.14" 4 | authors = ["Steven Fackler "] 5 | license = "MIT OR Apache-2.0" 6 | description = "A wrapper over a platform's native TLS implementation" 7 | repository = "https://github.com/sfackler/rust-native-tls" 8 | readme = "README.md" 9 | rust-version = "1.80.0" 10 | 11 | [package.metadata.docs.rs] 12 | features = ["alpn"] 13 | rustdoc-args = ["--cfg", "docsrs"] 14 | 15 | [features] 16 | vendored = ["openssl/vendored"] 17 | alpn = ["security-framework/alpn"] 18 | 19 | [target.'cfg(target_vendor = "apple")'.dependencies] 20 | security-framework = "2.0.0" 21 | security-framework-sys = "2.0.0" 22 | libc = "0.2" 23 | 24 | [target.'cfg(target_os = "macos")'.dependencies] 25 | tempfile = "3.1.0" 26 | 27 | [target.'cfg(target_os = "windows")'.dependencies] 28 | schannel = "0.1.17" 29 | 30 | [target.'cfg(not(any(target_os = "windows", target_vendor = "apple")))'.dependencies] 31 | log = "0.4.5" 32 | openssl = "0.10.69" 33 | openssl-sys = "0.9.81" 34 | openssl-probe = "0.1" 35 | 36 | [dev-dependencies] 37 | tempfile = "3.0" 38 | test-cert-gen = "0.9" 39 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 The rust-native-tls Developers 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rust-native-tls 2 | 3 | [Documentation](https://docs.rs/native-tls) 4 | 5 | An abstraction over platform-specific TLS implementations. 6 | 7 | Specifically, this crate uses SChannel on Windows (via the [`schannel`] crate), 8 | Secure Transport on macOS (via the [`security-framework`] crate), and OpenSSL (via 9 | the [`openssl`] crate) on all other platforms. 10 | 11 | [`schannel`]: https://crates.io/crates/schannel 12 | [`security-framework`]: https://crates.io/crates/security-framework 13 | [`openssl`]: https://crates.io/crates/openssl 14 | 15 | ## Installation 16 | 17 | ```toml 18 | # Cargo.toml 19 | [dependencies] 20 | native-tls = "0.2" 21 | ``` 22 | 23 | ## Usage 24 | 25 | An example client looks like: 26 | 27 | ```rust,ignore 28 | extern crate native_tls; 29 | 30 | use native_tls::TlsConnector; 31 | use std::io::{Read, Write}; 32 | use std::net::TcpStream; 33 | 34 | fn main() { 35 | let connector = TlsConnector::new().unwrap(); 36 | 37 | let stream = TcpStream::connect("google.com:443").unwrap(); 38 | let mut stream = connector.connect("google.com", stream).unwrap(); 39 | 40 | stream.write_all(b"GET / HTTP/1.0\r\n\r\n").unwrap(); 41 | let mut res = vec![]; 42 | stream.read_to_end(&mut res).unwrap(); 43 | println!("{}", String::from_utf8_lossy(&res)); 44 | } 45 | ``` 46 | 47 | To accept connections as a server from remote clients: 48 | 49 | ```rust,ignore 50 | extern crate native_tls; 51 | 52 | use native_tls::{Identity, TlsAcceptor, TlsStream}; 53 | use std::fs::File; 54 | use std::io::{Read}; 55 | use std::net::{TcpListener, TcpStream}; 56 | use std::sync::Arc; 57 | use std::thread; 58 | 59 | fn main() { 60 | let mut file = File::open("identity.pfx").unwrap(); 61 | let mut identity = vec![]; 62 | file.read_to_end(&mut identity).unwrap(); 63 | let identity = Identity::from_pkcs12(&identity, "hunter2").unwrap(); 64 | 65 | let acceptor = TlsAcceptor::new(identity).unwrap(); 66 | let acceptor = Arc::new(acceptor); 67 | 68 | let listener = TcpListener::bind("0.0.0.0:8443").unwrap(); 69 | 70 | fn handle_client(stream: TlsStream) { 71 | // ... 72 | } 73 | 74 | for stream in listener.incoming() { 75 | match stream { 76 | Ok(stream) => { 77 | let acceptor = acceptor.clone(); 78 | thread::spawn(move || { 79 | let stream = acceptor.accept(stream).unwrap(); 80 | handle_client(stream); 81 | }); 82 | } 83 | Err(e) => { /* connection failed */ } 84 | } 85 | } 86 | } 87 | ``` 88 | 89 | # License 90 | 91 | `rust-native-tls` is primarily distributed under the terms of both the MIT 92 | license and the Apache License (Version 2.0), with portions covered by various 93 | BSD-like licenses. 94 | 95 | See LICENSE-APACHE, and LICENSE-MIT for details. 96 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::unusual_byte_groupings)] 2 | use std::env; 3 | 4 | fn main() { 5 | if let Ok(version) = env::var("DEP_OPENSSL_VERSION_NUMBER") { 6 | let version = u64::from_str_radix(&version, 16).unwrap(); 7 | 8 | if version >= 0x1_01_00_00_0 { 9 | println!("cargo:rustc-cfg=have_min_max_version"); 10 | } 11 | } 12 | 13 | if let Ok(version) = env::var("DEP_OPENSSL_LIBRESSL_VERSION_NUMBER") { 14 | let version = u64::from_str_radix(&version, 16).unwrap(); 15 | 16 | if version >= 0x2_06_01_00_0 { 17 | println!("cargo:rustc-cfg=have_min_max_version"); 18 | } 19 | } 20 | 21 | println!("cargo::rustc-check-cfg=cfg(have_min_max_version)") 22 | } 23 | -------------------------------------------------------------------------------- /examples/google-connect.rs: -------------------------------------------------------------------------------- 1 | extern crate native_tls; 2 | 3 | use native_tls::TlsConnector; 4 | use std::io::{Read, Write}; 5 | use std::net::TcpStream; 6 | 7 | fn main() { 8 | let connector = TlsConnector::new().unwrap(); 9 | 10 | let stream = TcpStream::connect("google.com:443").unwrap(); 11 | let mut stream = connector.connect("google.com", stream).unwrap(); 12 | 13 | stream.write_all(b"GET / HTTP/1.0\r\n\r\n").unwrap(); 14 | let mut res = vec![]; 15 | stream.read_to_end(&mut res).unwrap(); 16 | println!("{}", String::from_utf8_lossy(&res)); 17 | } 18 | -------------------------------------------------------------------------------- /examples/simple-server-pkcs8.rs: -------------------------------------------------------------------------------- 1 | extern crate native_tls; 2 | 3 | use native_tls::{Identity, TlsAcceptor, TlsStream}; 4 | use std::fs::File; 5 | use std::io::{Read, Write}; 6 | use std::net::{TcpListener, TcpStream}; 7 | use std::sync::Arc; 8 | use std::thread; 9 | 10 | fn main() { 11 | let mut cert_file = File::open("test/cert.pem").unwrap(); 12 | let mut certs = vec![]; 13 | cert_file.read_to_end(&mut certs).unwrap(); 14 | let mut key_file = File::open("test/key.pem").unwrap(); 15 | let mut key = vec![]; 16 | key_file.read_to_end(&mut key).unwrap(); 17 | let pkcs8 = Identity::from_pkcs8(&certs, &key).unwrap(); 18 | 19 | let acceptor = TlsAcceptor::new(pkcs8).unwrap(); 20 | let acceptor = Arc::new(acceptor); 21 | 22 | let listener = TcpListener::bind("0.0.0.0:8443").unwrap(); 23 | 24 | fn handle_client(mut stream: TlsStream) { 25 | let mut buf = [0; 1024]; 26 | let read = stream.read(&mut buf).unwrap(); 27 | let received = std::str::from_utf8(&buf[0..read]).unwrap(); 28 | stream 29 | .write_all(format!("received '{}'", received).as_bytes()) 30 | .unwrap(); 31 | } 32 | 33 | for stream in listener.incoming() { 34 | match stream { 35 | Ok(stream) => { 36 | let acceptor = acceptor.clone(); 37 | thread::spawn(move || { 38 | let stream = acceptor.accept(stream).unwrap(); 39 | handle_client(stream); 40 | }); 41 | } 42 | Err(_e) => { /* connection failed */ } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /examples/simple-server.rs: -------------------------------------------------------------------------------- 1 | extern crate native_tls; 2 | 3 | use native_tls::{Identity, TlsAcceptor, TlsStream}; 4 | use std::fs::File; 5 | use std::io::Read; 6 | use std::net::{TcpListener, TcpStream}; 7 | use std::sync::Arc; 8 | use std::thread; 9 | 10 | fn main() { 11 | let mut file = File::open("identity.pfx").unwrap(); 12 | let mut pkcs12 = vec![]; 13 | file.read_to_end(&mut pkcs12).unwrap(); 14 | let pkcs12 = Identity::from_pkcs12(&pkcs12, "hunter2").unwrap(); 15 | 16 | let acceptor = TlsAcceptor::new(pkcs12).unwrap(); 17 | let acceptor = Arc::new(acceptor); 18 | 19 | let listener = TcpListener::bind("0.0.0.0:8443").unwrap(); 20 | 21 | fn handle_client(_stream: TlsStream) { 22 | // ... 23 | } 24 | 25 | for stream in listener.incoming() { 26 | match stream { 27 | Ok(stream) => { 28 | let acceptor = acceptor.clone(); 29 | thread::spawn(move || { 30 | let stream = acceptor.accept(stream).unwrap(); 31 | handle_client(stream); 32 | }); 33 | } 34 | Err(_e) => { /* connection failed */ } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/imp/openssl.rs: -------------------------------------------------------------------------------- 1 | extern crate openssl; 2 | extern crate openssl_probe; 3 | 4 | use self::openssl::error::ErrorStack; 5 | use self::openssl::hash::MessageDigest; 6 | use self::openssl::nid::Nid; 7 | use self::openssl::pkcs12::Pkcs12; 8 | use self::openssl::pkey::{PKey, Private}; 9 | use self::openssl::ssl::{ 10 | self, MidHandshakeSslStream, SslAcceptor, SslConnector, SslContextBuilder, SslMethod, 11 | SslVerifyMode, 12 | }; 13 | use self::openssl::x509::{store::X509StoreBuilder, X509VerifyResult, X509}; 14 | use self::openssl_probe::ProbeResult; 15 | use std::error; 16 | use std::fmt; 17 | use std::io; 18 | use std::sync::LazyLock; 19 | 20 | use {Protocol, TlsAcceptorBuilder, TlsConnectorBuilder}; 21 | 22 | static PROBE_RESULT: LazyLock = LazyLock::new(openssl_probe::probe); 23 | 24 | #[cfg(have_min_max_version)] 25 | fn supported_protocols( 26 | min: Option, 27 | max: Option, 28 | ctx: &mut SslContextBuilder, 29 | ) -> Result<(), ErrorStack> { 30 | use self::openssl::ssl::SslVersion; 31 | 32 | fn cvt(p: Protocol) -> SslVersion { 33 | match p { 34 | Protocol::Sslv3 => SslVersion::SSL3, 35 | Protocol::Tlsv10 => SslVersion::TLS1, 36 | Protocol::Tlsv11 => SslVersion::TLS1_1, 37 | Protocol::Tlsv12 => SslVersion::TLS1_2, 38 | } 39 | } 40 | 41 | ctx.set_min_proto_version(min.map(cvt))?; 42 | ctx.set_max_proto_version(max.map(cvt))?; 43 | 44 | Ok(()) 45 | } 46 | 47 | #[cfg(not(have_min_max_version))] 48 | fn supported_protocols( 49 | min: Option, 50 | max: Option, 51 | ctx: &mut SslContextBuilder, 52 | ) -> Result<(), ErrorStack> { 53 | use self::openssl::ssl::SslOptions; 54 | 55 | let no_ssl_mask = SslOptions::NO_SSLV2 56 | | SslOptions::NO_SSLV3 57 | | SslOptions::NO_TLSV1 58 | | SslOptions::NO_TLSV1_1 59 | | SslOptions::NO_TLSV1_2; 60 | 61 | ctx.clear_options(no_ssl_mask); 62 | let mut options = SslOptions::empty(); 63 | options |= match min { 64 | None => SslOptions::empty(), 65 | Some(Protocol::Sslv3) => SslOptions::NO_SSLV2, 66 | Some(Protocol::Tlsv10) => SslOptions::NO_SSLV2 | SslOptions::NO_SSLV3, 67 | Some(Protocol::Tlsv11) => { 68 | SslOptions::NO_SSLV2 | SslOptions::NO_SSLV3 | SslOptions::NO_TLSV1 69 | } 70 | Some(Protocol::Tlsv12) => { 71 | SslOptions::NO_SSLV2 72 | | SslOptions::NO_SSLV3 73 | | SslOptions::NO_TLSV1 74 | | SslOptions::NO_TLSV1_1 75 | } 76 | }; 77 | options |= match max { 78 | None | Some(Protocol::Tlsv12) => SslOptions::empty(), 79 | Some(Protocol::Tlsv11) => SslOptions::NO_TLSV1_2, 80 | Some(Protocol::Tlsv10) => SslOptions::NO_TLSV1_1 | SslOptions::NO_TLSV1_2, 81 | Some(Protocol::Sslv3) => { 82 | SslOptions::NO_TLSV1 | SslOptions::NO_TLSV1_1 | SslOptions::NO_TLSV1_2 83 | } 84 | }; 85 | 86 | ctx.set_options(options); 87 | 88 | Ok(()) 89 | } 90 | 91 | #[cfg(target_os = "android")] 92 | fn load_android_root_certs(connector: &mut SslContextBuilder) -> Result<(), Error> { 93 | use std::fs; 94 | 95 | if let Ok(dir) = fs::read_dir("/system/etc/security/cacerts") { 96 | let certs = dir 97 | .filter_map(|r| r.ok()) 98 | .filter_map(|e| fs::read(e.path()).ok()) 99 | .filter_map(|b| X509::from_pem(&b).ok()); 100 | for cert in certs { 101 | if let Err(err) = connector.cert_store_mut().add_cert(cert) { 102 | debug!("load_android_root_certs error: {:?}", err); 103 | } 104 | } 105 | } 106 | 107 | Ok(()) 108 | } 109 | 110 | #[derive(Debug)] 111 | pub enum Error { 112 | Normal(ErrorStack), 113 | Ssl(ssl::Error, X509VerifyResult), 114 | EmptyChain, 115 | NotPkcs8, 116 | } 117 | 118 | impl error::Error for Error { 119 | fn source(&self) -> Option<&(dyn error::Error + 'static)> { 120 | match *self { 121 | Error::Normal(ref e) => error::Error::source(e), 122 | Error::Ssl(ref e, _) => error::Error::source(e), 123 | Error::EmptyChain => None, 124 | Error::NotPkcs8 => None, 125 | } 126 | } 127 | } 128 | 129 | impl fmt::Display for Error { 130 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 131 | match *self { 132 | Error::Normal(ref e) => fmt::Display::fmt(e, fmt), 133 | Error::Ssl(ref e, X509VerifyResult::OK) => fmt::Display::fmt(e, fmt), 134 | Error::Ssl(ref e, v) => write!(fmt, "{} ({})", e, v), 135 | Error::EmptyChain => write!( 136 | fmt, 137 | "at least one certificate must be provided to create an identity" 138 | ), 139 | Error::NotPkcs8 => write!(fmt, "expected PKCS#8 PEM"), 140 | } 141 | } 142 | } 143 | 144 | impl From for Error { 145 | fn from(err: ErrorStack) -> Error { 146 | Error::Normal(err) 147 | } 148 | } 149 | 150 | #[derive(Clone)] 151 | pub struct Identity { 152 | pkey: PKey, 153 | cert: X509, 154 | chain: Vec, 155 | } 156 | 157 | impl Identity { 158 | pub fn from_pkcs12(buf: &[u8], pass: &str) -> Result { 159 | let pkcs12 = Pkcs12::from_der(buf)?; 160 | let parsed = pkcs12.parse2(pass)?; 161 | Ok(Identity { 162 | pkey: parsed.pkey.ok_or_else(|| Error::EmptyChain)?, 163 | cert: parsed.cert.ok_or_else(|| Error::EmptyChain)?, 164 | // > The stack is the reverse of what you might expect due to the way 165 | // > PKCS12_parse is implemented, so we need to load it backwards. 166 | // > https://github.com/sfackler/rust-native-tls/commit/05fb5e583be589ab63d9f83d986d095639f8ec44 167 | chain: parsed.ca.into_iter().flatten().rev().collect(), 168 | }) 169 | } 170 | 171 | pub fn from_pkcs8(buf: &[u8], key: &[u8]) -> Result { 172 | if !key.starts_with(b"-----BEGIN PRIVATE KEY-----") { 173 | return Err(Error::NotPkcs8); 174 | } 175 | 176 | let pkey = PKey::private_key_from_pem(key)?; 177 | let mut cert_chain = X509::stack_from_pem(buf)?.into_iter(); 178 | let cert = cert_chain.next().ok_or(Error::EmptyChain)?; 179 | let chain = cert_chain.collect(); 180 | Ok(Identity { pkey, cert, chain }) 181 | } 182 | } 183 | 184 | #[derive(Clone)] 185 | pub struct Certificate(X509); 186 | 187 | impl Certificate { 188 | pub fn from_der(buf: &[u8]) -> Result { 189 | let cert = X509::from_der(buf)?; 190 | Ok(Certificate(cert)) 191 | } 192 | 193 | pub fn from_pem(buf: &[u8]) -> Result { 194 | let cert = X509::from_pem(buf)?; 195 | Ok(Certificate(cert)) 196 | } 197 | 198 | pub fn to_der(&self) -> Result, Error> { 199 | let der = self.0.to_der()?; 200 | Ok(der) 201 | } 202 | } 203 | 204 | pub struct MidHandshakeTlsStream(MidHandshakeSslStream); 205 | 206 | impl fmt::Debug for MidHandshakeTlsStream 207 | where 208 | S: fmt::Debug, 209 | { 210 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 211 | fmt::Debug::fmt(&self.0, fmt) 212 | } 213 | } 214 | 215 | impl MidHandshakeTlsStream { 216 | pub fn get_ref(&self) -> &S { 217 | self.0.get_ref() 218 | } 219 | 220 | pub fn get_mut(&mut self) -> &mut S { 221 | self.0.get_mut() 222 | } 223 | } 224 | 225 | impl MidHandshakeTlsStream 226 | where 227 | S: io::Read + io::Write, 228 | { 229 | pub fn handshake(self) -> Result, HandshakeError> { 230 | match self.0.handshake() { 231 | Ok(s) => Ok(TlsStream(s)), 232 | Err(e) => Err(e.into()), 233 | } 234 | } 235 | } 236 | 237 | pub enum HandshakeError { 238 | Failure(Error), 239 | WouldBlock(MidHandshakeTlsStream), 240 | } 241 | 242 | impl From> for HandshakeError { 243 | fn from(e: ssl::HandshakeError) -> HandshakeError { 244 | match e { 245 | ssl::HandshakeError::SetupFailure(e) => HandshakeError::Failure(e.into()), 246 | ssl::HandshakeError::Failure(e) => { 247 | let v = e.ssl().verify_result(); 248 | HandshakeError::Failure(Error::Ssl(e.into_error(), v)) 249 | } 250 | ssl::HandshakeError::WouldBlock(s) => { 251 | HandshakeError::WouldBlock(MidHandshakeTlsStream(s)) 252 | } 253 | } 254 | } 255 | } 256 | 257 | impl From for HandshakeError { 258 | fn from(e: ErrorStack) -> HandshakeError { 259 | HandshakeError::Failure(e.into()) 260 | } 261 | } 262 | 263 | #[derive(Clone)] 264 | pub struct TlsConnector { 265 | connector: SslConnector, 266 | use_sni: bool, 267 | accept_invalid_hostnames: bool, 268 | accept_invalid_certs: bool, 269 | } 270 | 271 | impl TlsConnector { 272 | pub fn new(builder: &TlsConnectorBuilder) -> Result { 273 | let mut connector = SslConnector::builder(SslMethod::tls())?; 274 | 275 | // We need to load these separately so an error on one doesn't prevent the other from loading. 276 | if let Some(cert_file) = &PROBE_RESULT.cert_file { 277 | if let Err(e) = connector.load_verify_locations(Some(cert_file), None) { 278 | debug!("load_verify_locations cert file error: {:?}", e); 279 | } 280 | } 281 | if let Some(cert_dir) = &PROBE_RESULT.cert_dir { 282 | if let Err(e) = connector.load_verify_locations(None, Some(cert_dir)) { 283 | debug!("load_verify_locations cert dir error: {:?}", e); 284 | } 285 | } 286 | 287 | if let Some(ref identity) = builder.identity { 288 | connector.set_certificate(&identity.0.cert)?; 289 | connector.set_private_key(&identity.0.pkey)?; 290 | for cert in identity.0.chain.iter() { 291 | // https://www.openssl.org/docs/manmaster/man3/SSL_CTX_add_extra_chain_cert.html 292 | // specifies that "When sending a certificate chain, extra chain certificates are 293 | // sent in order following the end entity certificate." 294 | connector.add_extra_chain_cert(cert.to_owned())?; 295 | } 296 | } 297 | supported_protocols(builder.min_protocol, builder.max_protocol, &mut connector)?; 298 | 299 | if builder.disable_built_in_roots { 300 | connector.set_cert_store(X509StoreBuilder::new()?.build()); 301 | } 302 | 303 | for cert in &builder.root_certificates { 304 | if let Err(err) = connector.cert_store_mut().add_cert((cert.0).0.clone()) { 305 | debug!("add_cert error: {:?}", err); 306 | } 307 | } 308 | 309 | #[cfg(feature = "alpn")] 310 | { 311 | if !builder.alpn.is_empty() { 312 | // Wire format is each alpn preceded by its length as a byte. 313 | let mut alpn_wire_format = Vec::with_capacity( 314 | builder 315 | .alpn 316 | .iter() 317 | .map(|s| s.as_bytes().len()) 318 | .sum::() 319 | + builder.alpn.len(), 320 | ); 321 | for alpn in builder.alpn.iter().map(|s| s.as_bytes()) { 322 | alpn_wire_format.push(alpn.len() as u8); 323 | alpn_wire_format.extend(alpn); 324 | } 325 | connector.set_alpn_protos(&alpn_wire_format)?; 326 | } 327 | } 328 | 329 | #[cfg(target_os = "android")] 330 | load_android_root_certs(&mut connector)?; 331 | 332 | Ok(TlsConnector { 333 | connector: connector.build(), 334 | use_sni: builder.use_sni, 335 | accept_invalid_hostnames: builder.accept_invalid_hostnames, 336 | accept_invalid_certs: builder.accept_invalid_certs, 337 | }) 338 | } 339 | 340 | pub fn connect(&self, domain: &str, stream: S) -> Result, HandshakeError> 341 | where 342 | S: io::Read + io::Write, 343 | { 344 | let mut ssl = self 345 | .connector 346 | .configure()? 347 | .use_server_name_indication(self.use_sni) 348 | .verify_hostname(!self.accept_invalid_hostnames); 349 | if self.accept_invalid_certs { 350 | ssl.set_verify(SslVerifyMode::NONE); 351 | } 352 | 353 | let s = ssl.connect(domain, stream)?; 354 | Ok(TlsStream(s)) 355 | } 356 | } 357 | 358 | impl fmt::Debug for TlsConnector { 359 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 360 | fmt.debug_struct("TlsConnector") 361 | // n.b. SslConnector is a newtype on SslContext which implements a noop Debug so it's omitted 362 | .field("use_sni", &self.use_sni) 363 | .field("accept_invalid_hostnames", &self.accept_invalid_hostnames) 364 | .field("accept_invalid_certs", &self.accept_invalid_certs) 365 | .finish() 366 | } 367 | } 368 | 369 | #[derive(Clone)] 370 | pub struct TlsAcceptor(SslAcceptor); 371 | 372 | impl TlsAcceptor { 373 | pub fn new(builder: &TlsAcceptorBuilder) -> Result { 374 | let mut acceptor = SslAcceptor::mozilla_intermediate(SslMethod::tls())?; 375 | acceptor.set_private_key(&builder.identity.0.pkey)?; 376 | acceptor.set_certificate(&builder.identity.0.cert)?; 377 | for cert in builder.identity.0.chain.iter() { 378 | // https://www.openssl.org/docs/manmaster/man3/SSL_CTX_add_extra_chain_cert.html 379 | // specifies that "When sending a certificate chain, extra chain certificates are 380 | // sent in order following the end entity certificate." 381 | acceptor.add_extra_chain_cert(cert.to_owned())?; 382 | } 383 | supported_protocols(builder.min_protocol, builder.max_protocol, &mut acceptor)?; 384 | 385 | Ok(TlsAcceptor(acceptor.build())) 386 | } 387 | 388 | pub fn accept(&self, stream: S) -> Result, HandshakeError> 389 | where 390 | S: io::Read + io::Write, 391 | { 392 | let s = self.0.accept(stream)?; 393 | Ok(TlsStream(s)) 394 | } 395 | } 396 | 397 | pub struct TlsStream(ssl::SslStream); 398 | 399 | impl fmt::Debug for TlsStream { 400 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 401 | fmt::Debug::fmt(&self.0, fmt) 402 | } 403 | } 404 | 405 | impl TlsStream { 406 | pub fn get_ref(&self) -> &S { 407 | self.0.get_ref() 408 | } 409 | 410 | pub fn get_mut(&mut self) -> &mut S { 411 | self.0.get_mut() 412 | } 413 | } 414 | 415 | impl TlsStream { 416 | pub fn buffered_read_size(&self) -> Result { 417 | Ok(self.0.ssl().pending()) 418 | } 419 | 420 | pub fn peer_certificate(&self) -> Result, Error> { 421 | Ok(self.0.ssl().peer_certificate().map(Certificate)) 422 | } 423 | 424 | #[cfg(feature = "alpn")] 425 | pub fn negotiated_alpn(&self) -> Result>, Error> { 426 | Ok(self 427 | .0 428 | .ssl() 429 | .selected_alpn_protocol() 430 | .map(|alpn| alpn.to_vec())) 431 | } 432 | 433 | pub fn tls_server_end_point(&self) -> Result>, Error> { 434 | let cert = if self.0.ssl().is_server() { 435 | self.0.ssl().certificate().map(|x| x.to_owned()) 436 | } else { 437 | self.0.ssl().peer_certificate() 438 | }; 439 | 440 | let cert = match cert { 441 | Some(cert) => cert, 442 | None => return Ok(None), 443 | }; 444 | 445 | let algo_nid = cert.signature_algorithm().object().nid(); 446 | let signature_algorithms = match algo_nid.signature_algorithms() { 447 | Some(algs) => algs, 448 | None => return Ok(None), 449 | }; 450 | 451 | let md = match signature_algorithms.digest { 452 | Nid::MD5 | Nid::SHA1 => MessageDigest::sha256(), 453 | nid => match MessageDigest::from_nid(nid) { 454 | Some(md) => md, 455 | None => return Ok(None), 456 | }, 457 | }; 458 | 459 | let digest = cert.digest(md)?; 460 | 461 | Ok(Some(digest.to_vec())) 462 | } 463 | 464 | pub fn shutdown(&mut self) -> io::Result<()> { 465 | match self.0.shutdown() { 466 | Ok(_) => Ok(()), 467 | Err(ref e) if e.code() == ssl::ErrorCode::ZERO_RETURN => Ok(()), 468 | Err(e) => Err(e 469 | .into_io_error() 470 | .unwrap_or_else(|e| io::Error::new(io::ErrorKind::Other, e))), 471 | } 472 | } 473 | } 474 | 475 | impl io::Read for TlsStream { 476 | fn read(&mut self, buf: &mut [u8]) -> io::Result { 477 | self.0.read(buf) 478 | } 479 | } 480 | 481 | impl io::Write for TlsStream { 482 | fn write(&mut self, buf: &[u8]) -> io::Result { 483 | self.0.write(buf) 484 | } 485 | 486 | fn flush(&mut self) -> io::Result<()> { 487 | self.0.flush() 488 | } 489 | } 490 | -------------------------------------------------------------------------------- /src/imp/schannel.rs: -------------------------------------------------------------------------------- 1 | extern crate schannel; 2 | 3 | use self::schannel::cert_context::{CertContext, HashAlgorithm, KeySpec}; 4 | use self::schannel::cert_store::{CertAdd, CertStore, Memory, PfxImportOptions}; 5 | use self::schannel::crypt_prov::{AcquireOptions, ProviderType}; 6 | use self::schannel::schannel_cred::{Direction, Protocol, SchannelCred}; 7 | use self::schannel::tls_stream; 8 | use std::error; 9 | use std::fmt; 10 | use std::io; 11 | use std::str; 12 | 13 | use {TlsAcceptorBuilder, TlsConnectorBuilder}; 14 | 15 | const SEC_E_NO_CREDENTIALS: u32 = 0x8009030E; 16 | 17 | static PROTOCOLS: &'static [Protocol] = &[ 18 | Protocol::Ssl3, 19 | Protocol::Tls10, 20 | Protocol::Tls11, 21 | Protocol::Tls12, 22 | ]; 23 | 24 | fn convert_protocols(min: Option<::Protocol>, max: Option<::Protocol>) -> &'static [Protocol] { 25 | let mut protocols = PROTOCOLS; 26 | if let Some(p) = max.and_then(|max| protocols.get(..=max as usize)) { 27 | protocols = p; 28 | } 29 | if let Some(p) = min.and_then(|min| protocols.get(min as usize..)) { 30 | protocols = p; 31 | } 32 | protocols 33 | } 34 | 35 | pub struct Error(io::Error); 36 | 37 | impl error::Error for Error { 38 | fn source(&self) -> Option<&(dyn error::Error + 'static)> { 39 | error::Error::source(&self.0) 40 | } 41 | } 42 | 43 | impl fmt::Display for Error { 44 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 45 | fmt::Display::fmt(&self.0, fmt) 46 | } 47 | } 48 | 49 | impl fmt::Debug for Error { 50 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 51 | fmt::Debug::fmt(&self.0, fmt) 52 | } 53 | } 54 | 55 | impl From for Error { 56 | fn from(error: io::Error) -> Error { 57 | Error(error) 58 | } 59 | } 60 | 61 | #[derive(Clone)] 62 | pub struct Identity { 63 | cert: CertContext, 64 | } 65 | 66 | impl Identity { 67 | pub fn from_pkcs12(buf: &[u8], pass: &str) -> Result { 68 | let store = PfxImportOptions::new().password(pass).import(buf)?; 69 | let mut identity = None; 70 | 71 | for cert in store.certs() { 72 | if cert 73 | .private_key() 74 | .silent(true) 75 | .compare_key(true) 76 | .acquire() 77 | .is_ok() 78 | { 79 | identity = Some(cert); 80 | break; 81 | } 82 | } 83 | 84 | let identity = match identity { 85 | Some(identity) => identity, 86 | None => { 87 | return Err(io::Error::new( 88 | io::ErrorKind::InvalidInput, 89 | "No identity found in PKCS #12 archive", 90 | ) 91 | .into()); 92 | } 93 | }; 94 | 95 | Ok(Identity { cert: identity }) 96 | } 97 | 98 | pub fn from_pkcs8(pem: &[u8], key: &[u8]) -> Result { 99 | if !key.starts_with(b"-----BEGIN PRIVATE KEY-----") { 100 | return Err(io::Error::new(io::ErrorKind::InvalidInput, "not a PKCS#8 key").into()); 101 | } 102 | 103 | let mut store = Memory::new()?.into_store(); 104 | let mut cert_iter = pem::PemBlock::new(pem).into_iter(); 105 | let leaf = cert_iter.next().ok_or_else(|| { 106 | io::Error::new( 107 | io::ErrorKind::InvalidInput, 108 | "at least one certificate must be provided to create an identity", 109 | ) 110 | })?; 111 | let cert = CertContext::from_pem(std::str::from_utf8(leaf).map_err(|_| { 112 | io::Error::new( 113 | io::ErrorKind::InvalidInput, 114 | "leaf cert contains invalid utf8", 115 | ) 116 | })?)?; 117 | 118 | let name = gen_container_name(); 119 | let mut options = AcquireOptions::new(); 120 | options.container(&name); 121 | let type_ = ProviderType::rsa_full(); 122 | 123 | let mut container = match options.acquire(type_) { 124 | Ok(container) => container, 125 | Err(_) => options.new_keyset(true).acquire(type_)?, 126 | }; 127 | container.import().import_pkcs8_pem(&key)?; 128 | 129 | cert.set_key_prov_info() 130 | .container(&name) 131 | .type_(type_) 132 | .keep_open(true) 133 | .key_spec(KeySpec::key_exchange()) 134 | .set()?; 135 | let mut context = store.add_cert(&cert, CertAdd::Always)?; 136 | 137 | for int_cert in cert_iter { 138 | let certificate = Certificate::from_pem(int_cert)?; 139 | context = store.add_cert(&certificate.0, CertAdd::Always)?; 140 | } 141 | Ok(Identity { cert: context }) 142 | } 143 | } 144 | 145 | // The name of the container must be unique to have multiple active keys. 146 | fn gen_container_name() -> String { 147 | use std::sync::atomic::{AtomicUsize, Ordering}; 148 | static COUNTER: AtomicUsize = AtomicUsize::new(0); 149 | format!("native-tls-{}", COUNTER.fetch_add(1, Ordering::Relaxed)) 150 | } 151 | 152 | #[derive(Clone)] 153 | pub struct Certificate(CertContext); 154 | 155 | impl Certificate { 156 | pub fn from_der(buf: &[u8]) -> Result { 157 | let cert = CertContext::new(buf)?; 158 | Ok(Certificate(cert)) 159 | } 160 | 161 | pub fn from_pem(buf: &[u8]) -> Result { 162 | match str::from_utf8(buf) { 163 | Ok(s) => { 164 | let cert = CertContext::from_pem(s)?; 165 | Ok(Certificate(cert)) 166 | } 167 | Err(_) => Err(io::Error::new( 168 | io::ErrorKind::InvalidInput, 169 | "PEM representation contains non-UTF-8 bytes", 170 | ) 171 | .into()), 172 | } 173 | } 174 | 175 | pub fn to_der(&self) -> Result, Error> { 176 | Ok(self.0.to_der().to_vec()) 177 | } 178 | } 179 | 180 | pub struct MidHandshakeTlsStream(tls_stream::MidHandshakeTlsStream); 181 | 182 | impl fmt::Debug for MidHandshakeTlsStream 183 | where 184 | S: fmt::Debug, 185 | { 186 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 187 | fmt::Debug::fmt(&self.0, fmt) 188 | } 189 | } 190 | 191 | impl MidHandshakeTlsStream { 192 | pub fn get_ref(&self) -> &S { 193 | self.0.get_ref() 194 | } 195 | 196 | pub fn get_mut(&mut self) -> &mut S { 197 | self.0.get_mut() 198 | } 199 | } 200 | 201 | impl MidHandshakeTlsStream 202 | where 203 | S: io::Read + io::Write, 204 | { 205 | pub fn handshake(self) -> Result, HandshakeError> { 206 | match self.0.handshake() { 207 | Ok(s) => Ok(TlsStream(s)), 208 | Err(e) => Err(e.into()), 209 | } 210 | } 211 | } 212 | 213 | pub enum HandshakeError { 214 | Failure(Error), 215 | WouldBlock(MidHandshakeTlsStream), 216 | } 217 | 218 | impl From> for HandshakeError { 219 | fn from(e: tls_stream::HandshakeError) -> HandshakeError { 220 | match e { 221 | tls_stream::HandshakeError::Failure(e) => HandshakeError::Failure(e.into()), 222 | tls_stream::HandshakeError::Interrupted(s) => { 223 | HandshakeError::WouldBlock(MidHandshakeTlsStream(s)) 224 | } 225 | } 226 | } 227 | } 228 | 229 | impl From for HandshakeError { 230 | fn from(e: io::Error) -> HandshakeError { 231 | HandshakeError::Failure(e.into()) 232 | } 233 | } 234 | 235 | #[derive(Clone, Debug)] 236 | pub struct TlsConnector { 237 | cert: Option, 238 | roots: CertStore, 239 | min_protocol: Option<::Protocol>, 240 | max_protocol: Option<::Protocol>, 241 | use_sni: bool, 242 | accept_invalid_hostnames: bool, 243 | accept_invalid_certs: bool, 244 | disable_built_in_roots: bool, 245 | #[cfg(feature = "alpn")] 246 | alpn: Vec, 247 | } 248 | 249 | impl TlsConnector { 250 | pub fn new(builder: &TlsConnectorBuilder) -> Result { 251 | let cert = builder.identity.as_ref().map(|i| i.0.cert.clone()); 252 | let mut roots = Memory::new()?.into_store(); 253 | for cert in &builder.root_certificates { 254 | roots.add_cert(&(cert.0).0, CertAdd::ReplaceExisting)?; 255 | } 256 | 257 | Ok(TlsConnector { 258 | cert, 259 | roots, 260 | min_protocol: builder.min_protocol, 261 | max_protocol: builder.max_protocol, 262 | use_sni: builder.use_sni, 263 | accept_invalid_hostnames: builder.accept_invalid_hostnames, 264 | accept_invalid_certs: builder.accept_invalid_certs, 265 | disable_built_in_roots: builder.disable_built_in_roots, 266 | #[cfg(feature = "alpn")] 267 | alpn: builder.alpn.clone(), 268 | }) 269 | } 270 | 271 | pub fn connect(&self, domain: &str, stream: S) -> Result, HandshakeError> 272 | where 273 | S: io::Read + io::Write, 274 | { 275 | let mut builder = SchannelCred::builder(); 276 | builder.enabled_protocols(convert_protocols(self.min_protocol, self.max_protocol)); 277 | if let Some(cert) = self.cert.as_ref() { 278 | builder.cert(cert.clone()); 279 | } 280 | let cred = builder.acquire(Direction::Outbound)?; 281 | let mut builder = tls_stream::Builder::new(); 282 | builder 283 | .cert_store(self.roots.clone()) 284 | .domain(domain) 285 | .use_sni(self.use_sni) 286 | .accept_invalid_hostnames(self.accept_invalid_hostnames); 287 | if self.accept_invalid_certs { 288 | builder.verify_callback(|_| Ok(())); 289 | } else if self.disable_built_in_roots { 290 | let roots_copy = self.roots.clone(); 291 | builder.verify_callback(move |res| { 292 | if let Err(err) = res.result() { 293 | // Propagate previous error encountered during normal cert validation. 294 | return Err(err); 295 | } 296 | 297 | if let Some(chain) = res.chain() { 298 | if chain 299 | .certificates() 300 | .any(|cert| roots_copy.certs().any(|root_cert| root_cert == cert)) 301 | { 302 | return Ok(()); 303 | } 304 | } 305 | 306 | Err(io::Error::new( 307 | io::ErrorKind::Other, 308 | "unable to find any user-specified roots in the final cert chain", 309 | )) 310 | }); 311 | } 312 | #[cfg(feature = "alpn")] 313 | { 314 | if !self.alpn.is_empty() { 315 | builder.request_application_protocols( 316 | &self.alpn.iter().map(|s| s.as_bytes()).collect::>(), 317 | ); 318 | } 319 | } 320 | match builder.connect(cred, stream) { 321 | Ok(s) => Ok(TlsStream(s)), 322 | Err(e) => Err(e.into()), 323 | } 324 | } 325 | } 326 | 327 | #[derive(Clone)] 328 | pub struct TlsAcceptor { 329 | cert: CertContext, 330 | min_protocol: Option<::Protocol>, 331 | max_protocol: Option<::Protocol>, 332 | } 333 | 334 | impl TlsAcceptor { 335 | pub fn new(builder: &TlsAcceptorBuilder) -> Result { 336 | Ok(TlsAcceptor { 337 | cert: builder.identity.0.cert.clone(), 338 | min_protocol: builder.min_protocol, 339 | max_protocol: builder.max_protocol, 340 | }) 341 | } 342 | 343 | pub fn accept(&self, stream: S) -> Result, HandshakeError> 344 | where 345 | S: io::Read + io::Write, 346 | { 347 | let mut builder = SchannelCred::builder(); 348 | builder.enabled_protocols(convert_protocols(self.min_protocol, self.max_protocol)); 349 | builder.cert(self.cert.clone()); 350 | // FIXME we're probably missing the certificate chain? 351 | let cred = builder.acquire(Direction::Inbound)?; 352 | match tls_stream::Builder::new().accept(cred, stream) { 353 | Ok(s) => Ok(TlsStream(s)), 354 | Err(e) => Err(e.into()), 355 | } 356 | } 357 | } 358 | 359 | pub struct TlsStream(tls_stream::TlsStream); 360 | 361 | impl fmt::Debug for TlsStream { 362 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 363 | fmt::Debug::fmt(&self.0, fmt) 364 | } 365 | } 366 | 367 | impl TlsStream { 368 | pub fn get_ref(&self) -> &S { 369 | self.0.get_ref() 370 | } 371 | 372 | pub fn get_mut(&mut self) -> &mut S { 373 | self.0.get_mut() 374 | } 375 | } 376 | 377 | impl TlsStream { 378 | pub fn buffered_read_size(&self) -> Result { 379 | Ok(self.0.get_buf().len()) 380 | } 381 | 382 | pub fn peer_certificate(&self) -> Result, Error> { 383 | match self.0.peer_certificate() { 384 | Ok(cert) => Ok(Some(Certificate(cert))), 385 | Err(ref e) if e.raw_os_error() == Some(SEC_E_NO_CREDENTIALS as i32) => Ok(None), 386 | Err(e) => Err(Error(e)), 387 | } 388 | } 389 | 390 | #[cfg(feature = "alpn")] 391 | pub fn negotiated_alpn(&self) -> Result>, Error> { 392 | Ok(self.0.negotiated_application_protocol()?) 393 | } 394 | 395 | pub fn tls_server_end_point(&self) -> Result>, Error> { 396 | let cert = if self.0.is_server() { 397 | self.0.certificate() 398 | } else { 399 | self.0.peer_certificate() 400 | }; 401 | 402 | let cert = match cert { 403 | Ok(cert) => cert, 404 | Err(ref e) if e.raw_os_error() == Some(SEC_E_NO_CREDENTIALS as i32) => return Ok(None), 405 | Err(e) => return Err(Error(e)), 406 | }; 407 | 408 | let signature_algorithms = cert.sign_hash_algorithms()?; 409 | let hash = match signature_algorithms.rsplit('/').next().unwrap() { 410 | "MD5" | "SHA1" | "SHA256" => HashAlgorithm::sha256(), 411 | "SHA384" => HashAlgorithm::sha384(), 412 | "SHA512" => HashAlgorithm::sha512(), 413 | _ => return Ok(None), 414 | }; 415 | 416 | let digest = cert.fingerprint(hash)?; 417 | Ok(Some(digest)) 418 | } 419 | 420 | pub fn shutdown(&mut self) -> io::Result<()> { 421 | self.0.shutdown()?; 422 | Ok(()) 423 | } 424 | } 425 | 426 | impl io::Read for TlsStream { 427 | fn read(&mut self, buf: &mut [u8]) -> io::Result { 428 | self.0.read(buf) 429 | } 430 | } 431 | 432 | impl io::Write for TlsStream { 433 | fn write(&mut self, buf: &[u8]) -> io::Result { 434 | self.0.write(buf) 435 | } 436 | 437 | fn flush(&mut self) -> io::Result<()> { 438 | self.0.flush() 439 | } 440 | } 441 | 442 | mod pem { 443 | /// Split data by PEM guard lines 444 | pub struct PemBlock<'a> { 445 | pem_block: &'a str, 446 | cur_end: usize, 447 | } 448 | 449 | impl<'a> PemBlock<'a> { 450 | pub fn new(data: &'a [u8]) -> PemBlock<'a> { 451 | let s = ::std::str::from_utf8(data).unwrap(); 452 | PemBlock { 453 | pem_block: s, 454 | cur_end: s.find("-----BEGIN").unwrap_or(s.len()), 455 | } 456 | } 457 | } 458 | 459 | impl<'a> Iterator for PemBlock<'a> { 460 | type Item = &'a [u8]; 461 | fn next(&mut self) -> Option { 462 | let last = self.pem_block.len(); 463 | if self.cur_end >= last { 464 | return None; 465 | } 466 | let begin = self.cur_end; 467 | let pos = self.pem_block[begin + 1..].find("-----BEGIN"); 468 | self.cur_end = match pos { 469 | Some(end) => end + begin + 1, 470 | None => last, 471 | }; 472 | return Some(&self.pem_block[begin..self.cur_end].as_bytes()); 473 | } 474 | } 475 | 476 | #[test] 477 | fn test_split() { 478 | // Split three certs, CRLF line terminators. 479 | assert_eq!( 480 | PemBlock::new( 481 | b"-----BEGIN FIRST-----\r\n-----END FIRST-----\r\n\ 482 | -----BEGIN SECOND-----\r\n-----END SECOND\r\n\ 483 | -----BEGIN THIRD-----\r\n-----END THIRD\r\n" 484 | ) 485 | .collect::>(), 486 | vec![ 487 | b"-----BEGIN FIRST-----\r\n-----END FIRST-----\r\n" as &[u8], 488 | b"-----BEGIN SECOND-----\r\n-----END SECOND\r\n", 489 | b"-----BEGIN THIRD-----\r\n-----END THIRD\r\n" 490 | ] 491 | ); 492 | // Split three certs, CRLF line terminators except at EOF. 493 | assert_eq!( 494 | PemBlock::new( 495 | b"-----BEGIN FIRST-----\r\n-----END FIRST-----\r\n\ 496 | -----BEGIN SECOND-----\r\n-----END SECOND-----\r\n\ 497 | -----BEGIN THIRD-----\r\n-----END THIRD-----" 498 | ) 499 | .collect::>(), 500 | vec![ 501 | b"-----BEGIN FIRST-----\r\n-----END FIRST-----\r\n" as &[u8], 502 | b"-----BEGIN SECOND-----\r\n-----END SECOND-----\r\n", 503 | b"-----BEGIN THIRD-----\r\n-----END THIRD-----" 504 | ] 505 | ); 506 | // Split two certs, LF line terminators. 507 | assert_eq!( 508 | PemBlock::new( 509 | b"-----BEGIN FIRST-----\n-----END FIRST-----\n\ 510 | -----BEGIN SECOND-----\n-----END SECOND\n" 511 | ) 512 | .collect::>(), 513 | vec![ 514 | b"-----BEGIN FIRST-----\n-----END FIRST-----\n" as &[u8], 515 | b"-----BEGIN SECOND-----\n-----END SECOND\n" 516 | ] 517 | ); 518 | // Split two certs, CR line terminators. 519 | assert_eq!( 520 | PemBlock::new( 521 | b"-----BEGIN FIRST-----\r-----END FIRST-----\r\ 522 | -----BEGIN SECOND-----\r-----END SECOND\r" 523 | ) 524 | .collect::>(), 525 | vec![ 526 | b"-----BEGIN FIRST-----\r-----END FIRST-----\r" as &[u8], 527 | b"-----BEGIN SECOND-----\r-----END SECOND\r" 528 | ] 529 | ); 530 | // Split two certs, LF line terminators except at EOF. 531 | assert_eq!( 532 | PemBlock::new( 533 | b"-----BEGIN FIRST-----\n-----END FIRST-----\n\ 534 | -----BEGIN SECOND-----\n-----END SECOND" 535 | ) 536 | .collect::>(), 537 | vec![ 538 | b"-----BEGIN FIRST-----\n-----END FIRST-----\n" as &[u8], 539 | b"-----BEGIN SECOND-----\n-----END SECOND" 540 | ] 541 | ); 542 | // Split a single cert, LF line terminators. 543 | assert_eq!( 544 | PemBlock::new(b"-----BEGIN FIRST-----\n-----END FIRST-----\n").collect::>(), 545 | vec![b"-----BEGIN FIRST-----\n-----END FIRST-----\n" as &[u8]] 546 | ); 547 | // Split a single cert, LF line terminators except at EOF. 548 | assert_eq!( 549 | PemBlock::new(b"-----BEGIN FIRST-----\n-----END FIRST-----").collect::>(), 550 | vec![b"-----BEGIN FIRST-----\n-----END FIRST-----" as &[u8]] 551 | ); 552 | // (Don't) split garbage. 553 | assert_eq!( 554 | PemBlock::new(b"junk").collect::>(), 555 | Vec::<&[u8]>::new() 556 | ); 557 | assert_eq!( 558 | PemBlock::new(b"junk-----BEGIN garbage").collect::>(), 559 | vec![b"-----BEGIN garbage" as &[u8]] 560 | ); 561 | } 562 | } 563 | -------------------------------------------------------------------------------- /src/imp/security_framework.rs: -------------------------------------------------------------------------------- 1 | extern crate libc; 2 | extern crate security_framework; 3 | extern crate security_framework_sys; 4 | 5 | use self::security_framework::base; 6 | use self::security_framework::certificate::SecCertificate; 7 | use self::security_framework::identity::SecIdentity; 8 | use self::security_framework::import_export::{ImportedIdentity, Pkcs12ImportOptions}; 9 | use self::security_framework::random::SecRandom; 10 | use self::security_framework::secure_transport::{ 11 | self, ClientBuilder, SslConnectionType, SslContext, SslProtocol, SslProtocolSide, 12 | }; 13 | use self::security_framework_sys::base::{errSecIO, errSecParam}; 14 | use std::error; 15 | use std::fmt; 16 | use std::io; 17 | use std::str; 18 | use std::sync::Mutex; 19 | use std::sync::Once; 20 | 21 | #[cfg(not(any( 22 | target_os = "ios", 23 | target_os = "watchos", 24 | target_os = "tvos", 25 | target_os = "visionos" 26 | )))] 27 | use self::security_framework::os::macos::certificate::{PropertyType, SecCertificateExt}; 28 | #[cfg(not(any( 29 | target_os = "ios", 30 | target_os = "watchos", 31 | target_os = "tvos", 32 | target_os = "visionos" 33 | )))] 34 | use self::security_framework::os::macos::certificate_oids::CertificateOid; 35 | #[cfg(not(any( 36 | target_os = "ios", 37 | target_os = "watchos", 38 | target_os = "tvos", 39 | target_os = "visionos" 40 | )))] 41 | use self::security_framework::os::macos::identity::SecIdentityExt; 42 | #[cfg(not(any( 43 | target_os = "ios", 44 | target_os = "watchos", 45 | target_os = "tvos", 46 | target_os = "visionos" 47 | )))] 48 | use self::security_framework::os::macos::import_export::{ 49 | ImportOptions, Pkcs12ImportOptionsExt, SecItems, 50 | }; 51 | #[cfg(not(any( 52 | target_os = "ios", 53 | target_os = "watchos", 54 | target_os = "tvos", 55 | target_os = "visionos" 56 | )))] 57 | use self::security_framework::os::macos::keychain::{self, KeychainSettings, SecKeychain}; 58 | 59 | use {Protocol, TlsAcceptorBuilder, TlsConnectorBuilder}; 60 | 61 | static SET_AT_EXIT: Once = Once::new(); 62 | 63 | #[cfg(not(any( 64 | target_os = "ios", 65 | target_os = "watchos", 66 | target_os = "tvos", 67 | target_os = "visionos" 68 | )))] 69 | static TEMP_KEYCHAIN: Mutex> = Mutex::new(None); 70 | 71 | fn convert_protocol(protocol: Protocol) -> SslProtocol { 72 | match protocol { 73 | Protocol::Sslv3 => SslProtocol::SSL3, 74 | Protocol::Tlsv10 => SslProtocol::TLS1, 75 | Protocol::Tlsv11 => SslProtocol::TLS11, 76 | Protocol::Tlsv12 => SslProtocol::TLS12, 77 | } 78 | } 79 | 80 | pub struct Error(base::Error); 81 | 82 | impl error::Error for Error { 83 | fn source(&self) -> Option<&(dyn error::Error + 'static)> { 84 | error::Error::source(&self.0) 85 | } 86 | } 87 | 88 | impl fmt::Display for Error { 89 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 90 | fmt::Display::fmt(&self.0, fmt) 91 | } 92 | } 93 | 94 | impl fmt::Debug for Error { 95 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 96 | fmt::Debug::fmt(&self.0, fmt) 97 | } 98 | } 99 | 100 | impl From for Error { 101 | fn from(error: base::Error) -> Error { 102 | Error(error) 103 | } 104 | } 105 | 106 | #[derive(Clone, Debug)] 107 | pub struct Identity { 108 | identity: SecIdentity, 109 | chain: Vec, 110 | } 111 | 112 | impl Identity { 113 | #[cfg(any( 114 | target_os = "ios", 115 | target_os = "watchos", 116 | target_os = "tvos", 117 | target_os = "visionos" 118 | ))] 119 | pub fn from_pkcs8(_: &[u8], _: &[u8]) -> Result { 120 | panic!("Not implemented on iOS"); 121 | } 122 | 123 | #[cfg(not(any( 124 | target_os = "ios", 125 | target_os = "watchos", 126 | target_os = "tvos", 127 | target_os = "visionos" 128 | )))] 129 | pub fn from_pkcs8(pem: &[u8], key: &[u8]) -> Result { 130 | if !key.starts_with(b"-----BEGIN PRIVATE KEY-----") { 131 | return Err(Error(base::Error::from(errSecParam))); 132 | } 133 | 134 | let dir = tempfile::TempDir::new().map_err(|_| Error(base::Error::from(errSecIO)))?; 135 | let keychain = keychain::CreateOptions::new() 136 | .password(&random_password()?) 137 | .create(dir.path().join("identity.keychain"))?; 138 | 139 | let mut items = SecItems::default(); 140 | 141 | ImportOptions::new() 142 | .filename("key.pem") 143 | .items(&mut items) 144 | .keychain(&keychain) 145 | .import(key)?; 146 | 147 | ImportOptions::new() 148 | .filename("chain.pem") 149 | .items(&mut items) 150 | .keychain(&keychain) 151 | .import(pem)?; 152 | 153 | let cert = items 154 | .certificates 155 | .get(0) 156 | .ok_or_else(|| Error(base::Error::from(errSecParam)))?; 157 | let ident = SecIdentity::with_certificate(&[keychain], cert)?; 158 | Ok(Identity { 159 | identity: ident, 160 | chain: items.certificates.into_iter().skip(1).collect(), 161 | }) 162 | } 163 | 164 | pub fn from_pkcs12(buf: &[u8], pass: &str) -> Result { 165 | let mut imports = Identity::import_options(buf, pass)?; 166 | let import = imports.pop().unwrap(); 167 | 168 | let identity = import 169 | .identity 170 | .expect("Pkcs12 files must include an identity"); 171 | 172 | // FIXME: Compare the certificates for equality using CFEqual 173 | let identity_cert = identity.certificate()?.to_der(); 174 | 175 | Ok(Identity { 176 | identity, 177 | chain: import 178 | .cert_chain 179 | .unwrap_or(vec![]) 180 | .into_iter() 181 | .filter(|c| c.to_der() != identity_cert) 182 | .collect(), 183 | }) 184 | } 185 | 186 | #[cfg(not(any( 187 | target_os = "ios", 188 | target_os = "watchos", 189 | target_os = "tvos", 190 | target_os = "visionos" 191 | )))] 192 | fn import_options(buf: &[u8], pass: &str) -> Result, Error> { 193 | SET_AT_EXIT.call_once(|| { 194 | extern "C" fn atexit() { 195 | *TEMP_KEYCHAIN.lock().unwrap() = None; 196 | } 197 | unsafe { 198 | libc::atexit(atexit); 199 | } 200 | }); 201 | 202 | let keychain = match *TEMP_KEYCHAIN.lock().unwrap() { 203 | Some((ref keychain, _)) => keychain.clone(), 204 | ref mut lock @ None => { 205 | let dir = 206 | tempfile::TempDir::new().map_err(|_| Error(base::Error::from(errSecIO)))?; 207 | 208 | let mut keychain = keychain::CreateOptions::new() 209 | .password(pass) 210 | .create(dir.path().join("tmp.keychain"))?; 211 | keychain.set_settings(&KeychainSettings::new())?; 212 | 213 | *lock = Some((keychain.clone(), dir)); 214 | keychain 215 | } 216 | }; 217 | let mut import_opts = Pkcs12ImportOptions::new(); 218 | // Method shadowed by deprecated method. 219 | ::keychain(&mut import_opts, keychain); 220 | let imports = import_opts.passphrase(pass).import(buf)?; 221 | Ok(imports) 222 | } 223 | 224 | #[cfg(any( 225 | target_os = "ios", 226 | target_os = "watchos", 227 | target_os = "tvos", 228 | target_os = "visionos" 229 | ))] 230 | fn import_options(buf: &[u8], pass: &str) -> Result, Error> { 231 | let imports = Pkcs12ImportOptions::new().passphrase(pass).import(buf)?; 232 | Ok(imports) 233 | } 234 | } 235 | 236 | fn random_password() -> Result { 237 | use std::fmt::Write; 238 | let mut bytes = [0_u8; 10]; 239 | SecRandom::default() 240 | .copy_bytes(&mut bytes) 241 | .map_err(|_| Error(base::Error::from(errSecIO)))?; 242 | let mut s = String::with_capacity(2 * bytes.len()); 243 | for byte in bytes { 244 | write!(s, "{:02X}", byte).map_err(|_| Error(base::Error::from(errSecIO)))?; 245 | } 246 | Ok(s) 247 | } 248 | 249 | #[derive(Clone)] 250 | pub struct Certificate(SecCertificate); 251 | 252 | impl Certificate { 253 | pub fn from_der(buf: &[u8]) -> Result { 254 | let cert = SecCertificate::from_der(buf)?; 255 | Ok(Certificate(cert)) 256 | } 257 | 258 | #[cfg(not(any( 259 | target_os = "ios", 260 | target_os = "watchos", 261 | target_os = "tvos", 262 | target_os = "visionos" 263 | )))] 264 | pub fn from_pem(buf: &[u8]) -> Result { 265 | let mut items = SecItems::default(); 266 | ImportOptions::new().items(&mut items).import(buf)?; 267 | if items.certificates.len() == 1 && items.identities.is_empty() && items.keys.is_empty() { 268 | Ok(Certificate(items.certificates.pop().unwrap())) 269 | } else { 270 | Err(Error(base::Error::from(errSecParam))) 271 | } 272 | } 273 | 274 | #[cfg(any( 275 | target_os = "ios", 276 | target_os = "watchos", 277 | target_os = "tvos", 278 | target_os = "visionos" 279 | ))] 280 | pub fn from_pem(_: &[u8]) -> Result { 281 | panic!("Not implemented on iOS, tvOS, watchOS or visionOS"); 282 | } 283 | 284 | pub fn to_der(&self) -> Result, Error> { 285 | Ok(self.0.to_der()) 286 | } 287 | } 288 | 289 | pub enum HandshakeError { 290 | WouldBlock(MidHandshakeTlsStream), 291 | Failure(Error), 292 | } 293 | 294 | impl From> for HandshakeError { 295 | fn from(e: secure_transport::ClientHandshakeError) -> HandshakeError { 296 | match e { 297 | secure_transport::ClientHandshakeError::Failure(e) => HandshakeError::Failure(e.into()), 298 | secure_transport::ClientHandshakeError::Interrupted(s) => { 299 | HandshakeError::WouldBlock(MidHandshakeTlsStream::Client(s)) 300 | } 301 | } 302 | } 303 | } 304 | 305 | impl From for HandshakeError { 306 | fn from(e: base::Error) -> HandshakeError { 307 | HandshakeError::Failure(e.into()) 308 | } 309 | } 310 | 311 | pub enum MidHandshakeTlsStream { 312 | Server( 313 | secure_transport::MidHandshakeSslStream, 314 | Option, 315 | ), 316 | Client(secure_transport::MidHandshakeClientBuilder), 317 | } 318 | 319 | impl fmt::Debug for MidHandshakeTlsStream 320 | where 321 | S: fmt::Debug, 322 | { 323 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 324 | match *self { 325 | MidHandshakeTlsStream::Server(ref s, _) => s.fmt(fmt), 326 | MidHandshakeTlsStream::Client(ref s) => s.fmt(fmt), 327 | } 328 | } 329 | } 330 | 331 | impl MidHandshakeTlsStream { 332 | pub fn get_ref(&self) -> &S { 333 | match *self { 334 | MidHandshakeTlsStream::Server(ref s, _) => s.get_ref(), 335 | MidHandshakeTlsStream::Client(ref s) => s.get_ref(), 336 | } 337 | } 338 | 339 | pub fn get_mut(&mut self) -> &mut S { 340 | match *self { 341 | MidHandshakeTlsStream::Server(ref mut s, _) => s.get_mut(), 342 | MidHandshakeTlsStream::Client(ref mut s) => s.get_mut(), 343 | } 344 | } 345 | } 346 | 347 | impl MidHandshakeTlsStream 348 | where 349 | S: io::Read + io::Write, 350 | { 351 | pub fn handshake(self) -> Result, HandshakeError> { 352 | match self { 353 | MidHandshakeTlsStream::Server(s, cert) => match s.handshake() { 354 | Ok(stream) => Ok(TlsStream { stream, cert }), 355 | Err(secure_transport::HandshakeError::Failure(e)) => { 356 | Err(HandshakeError::Failure(Error(e))) 357 | } 358 | Err(secure_transport::HandshakeError::Interrupted(s)) => Err( 359 | HandshakeError::WouldBlock(MidHandshakeTlsStream::Server(s, cert)), 360 | ), 361 | }, 362 | MidHandshakeTlsStream::Client(s) => match s.handshake() { 363 | Ok(stream) => Ok(TlsStream { stream, cert: None }), 364 | Err(e) => Err(e.into()), 365 | }, 366 | } 367 | } 368 | } 369 | 370 | #[derive(Clone, Debug)] 371 | pub struct TlsConnector { 372 | identity: Option, 373 | min_protocol: Option, 374 | max_protocol: Option, 375 | roots: Vec, 376 | use_sni: bool, 377 | danger_accept_invalid_hostnames: bool, 378 | danger_accept_invalid_certs: bool, 379 | disable_built_in_roots: bool, 380 | #[cfg(feature = "alpn")] 381 | alpn: Vec, 382 | } 383 | 384 | impl TlsConnector { 385 | pub fn new(builder: &TlsConnectorBuilder) -> Result { 386 | Ok(TlsConnector { 387 | identity: builder.identity.as_ref().map(|i| i.0.clone()), 388 | min_protocol: builder.min_protocol, 389 | max_protocol: builder.max_protocol, 390 | roots: builder 391 | .root_certificates 392 | .iter() 393 | .map(|c| (c.0).0.clone()) 394 | .collect(), 395 | use_sni: builder.use_sni, 396 | danger_accept_invalid_hostnames: builder.accept_invalid_hostnames, 397 | danger_accept_invalid_certs: builder.accept_invalid_certs, 398 | disable_built_in_roots: builder.disable_built_in_roots, 399 | #[cfg(feature = "alpn")] 400 | alpn: builder.alpn.clone(), 401 | }) 402 | } 403 | 404 | pub fn connect(&self, domain: &str, stream: S) -> Result, HandshakeError> 405 | where 406 | S: io::Read + io::Write, 407 | { 408 | let mut builder = ClientBuilder::new(); 409 | if let Some(min) = self.min_protocol { 410 | builder.protocol_min(convert_protocol(min)); 411 | } 412 | if let Some(max) = self.max_protocol { 413 | builder.protocol_max(convert_protocol(max)); 414 | } 415 | if let Some(identity) = self.identity.as_ref() { 416 | builder.identity(&identity.identity, &identity.chain); 417 | } 418 | builder.anchor_certificates(&self.roots); 419 | builder.use_sni(self.use_sni); 420 | builder.danger_accept_invalid_hostnames(self.danger_accept_invalid_hostnames); 421 | builder.danger_accept_invalid_certs(self.danger_accept_invalid_certs); 422 | builder.trust_anchor_certificates_only(self.disable_built_in_roots); 423 | 424 | #[cfg(feature = "alpn")] 425 | { 426 | if !self.alpn.is_empty() { 427 | builder.alpn_protocols(&self.alpn.iter().map(String::as_str).collect::>()); 428 | } 429 | } 430 | 431 | match builder.handshake(domain, stream) { 432 | Ok(stream) => Ok(TlsStream { stream, cert: None }), 433 | Err(e) => Err(e.into()), 434 | } 435 | } 436 | } 437 | 438 | #[derive(Clone)] 439 | pub struct TlsAcceptor { 440 | identity: Identity, 441 | min_protocol: Option, 442 | max_protocol: Option, 443 | } 444 | 445 | impl TlsAcceptor { 446 | pub fn new(builder: &TlsAcceptorBuilder) -> Result { 447 | Ok(TlsAcceptor { 448 | identity: builder.identity.0.clone(), 449 | min_protocol: builder.min_protocol, 450 | max_protocol: builder.max_protocol, 451 | }) 452 | } 453 | 454 | pub fn accept(&self, stream: S) -> Result, HandshakeError> 455 | where 456 | S: io::Read + io::Write, 457 | { 458 | let mut ctx = SslContext::new(SslProtocolSide::SERVER, SslConnectionType::STREAM)?; 459 | 460 | if let Some(min) = self.min_protocol { 461 | ctx.set_protocol_version_min(convert_protocol(min))?; 462 | } 463 | if let Some(max) = self.max_protocol { 464 | ctx.set_protocol_version_max(convert_protocol(max))?; 465 | } 466 | ctx.set_certificate(&self.identity.identity, &self.identity.chain)?; 467 | let cert = Some(self.identity.identity.certificate()?); 468 | match ctx.handshake(stream) { 469 | Ok(stream) => Ok(TlsStream { stream, cert }), 470 | Err(secure_transport::HandshakeError::Failure(e)) => { 471 | Err(HandshakeError::Failure(Error(e))) 472 | } 473 | Err(secure_transport::HandshakeError::Interrupted(s)) => Err( 474 | HandshakeError::WouldBlock(MidHandshakeTlsStream::Server(s, cert)), 475 | ), 476 | } 477 | } 478 | } 479 | 480 | pub struct TlsStream { 481 | stream: secure_transport::SslStream, 482 | cert: Option, 483 | } 484 | 485 | impl fmt::Debug for TlsStream { 486 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 487 | fmt::Debug::fmt(&self.stream, fmt) 488 | } 489 | } 490 | 491 | impl TlsStream { 492 | pub fn get_ref(&self) -> &S { 493 | self.stream.get_ref() 494 | } 495 | 496 | pub fn get_mut(&mut self) -> &mut S { 497 | self.stream.get_mut() 498 | } 499 | } 500 | 501 | impl TlsStream { 502 | pub fn buffered_read_size(&self) -> Result { 503 | Ok(self.stream.context().buffered_read_size()?) 504 | } 505 | 506 | #[allow(deprecated)] 507 | pub fn peer_certificate(&self) -> Result, Error> { 508 | let trust = match self.stream.context().peer_trust2()? { 509 | Some(trust) => trust, 510 | None => return Ok(None), 511 | }; 512 | trust.evaluate()?; 513 | 514 | Ok(trust.certificate_at_index(0).map(Certificate)) 515 | } 516 | 517 | #[cfg(feature = "alpn")] 518 | pub fn negotiated_alpn(&self) -> Result>, Error> { 519 | match self.stream.context().alpn_protocols() { 520 | Ok(protocols) => { 521 | // Per RFC7301, "ProtocolNameList" MUST contain exactly one "ProtocolName". 522 | assert!(protocols.len() < 2); 523 | 524 | if protocols.is_empty() { 525 | // Not sure this is actually possible. 526 | Ok(None) 527 | } else { 528 | Ok(Some(protocols.into_iter().next().unwrap().into_bytes())) 529 | } 530 | } 531 | // The macOS API appears to return `errSecParam` whenever no ALPN was negotiated, both 532 | // when it isn't attempted and when it isn't successful. 533 | Err(e) if e.code() == errSecParam => Ok(None), 534 | Err(other) => Err(Error::from(other)), 535 | } 536 | } 537 | 538 | #[cfg(any( 539 | target_os = "ios", 540 | target_os = "watchos", 541 | target_os = "tvos", 542 | target_os = "visionos" 543 | ))] 544 | pub fn tls_server_end_point(&self) -> Result>, Error> { 545 | Ok(None) 546 | } 547 | 548 | #[cfg(not(any( 549 | target_os = "ios", 550 | target_os = "watchos", 551 | target_os = "tvos", 552 | target_os = "visionos" 553 | )))] 554 | pub fn tls_server_end_point(&self) -> Result>, Error> { 555 | let cert = match self.cert { 556 | Some(ref cert) => cert.clone(), 557 | None => match self.peer_certificate()? { 558 | Some(cert) => cert.0, 559 | None => return Ok(None), 560 | }, 561 | }; 562 | 563 | let property = match cert 564 | .properties(Some(&[CertificateOid::x509_v1_signature_algorithm()])) 565 | .ok() 566 | .and_then(|p| p.get(CertificateOid::x509_v1_signature_algorithm())) 567 | { 568 | Some(property) => property, 569 | None => return Ok(None), 570 | }; 571 | 572 | let section = match property.get() { 573 | PropertyType::Section(section) => section, 574 | _ => return Ok(None), 575 | }; 576 | 577 | let algorithm = match section.iter().find(|p| p.label() == "Algorithm") { 578 | Some(property) => property, 579 | None => return Ok(None), 580 | }; 581 | 582 | let algorithm = match algorithm.get() { 583 | PropertyType::String(algorithm) => algorithm, 584 | _ => return Ok(None), 585 | }; 586 | 587 | let digest = match &*algorithm.to_string() { 588 | // MD5 589 | "1.2.840.113549.2.5" | "1.2.840.113549.1.1.4" | "1.3.14.3.2.3" => Digest::Sha256, 590 | // SHA-1 591 | "1.3.14.3.2.26" 592 | | "1.3.14.3.2.15" 593 | | "1.2.840.113549.1.1.5" 594 | | "1.3.14.3.2.29" 595 | | "1.2.840.10040.4.3" 596 | | "1.3.14.3.2.13" 597 | | "1.2.840.10045.4.1" => Digest::Sha256, 598 | // SHA-224 599 | "2.16.840.1.101.3.4.2.4" 600 | | "1.2.840.113549.1.1.14" 601 | | "2.16.840.1.101.3.4.3.1" 602 | | "1.2.840.10045.4.3.1" => Digest::Sha224, 603 | // SHA-256 604 | "2.16.840.1.101.3.4.2.1" | "1.2.840.113549.1.1.11" | "1.2.840.10045.4.3.2" => { 605 | Digest::Sha256 606 | } 607 | // SHA-384 608 | "2.16.840.1.101.3.4.2.2" | "1.2.840.113549.1.1.12" | "1.2.840.10045.4.3.3" => { 609 | Digest::Sha384 610 | } 611 | // SHA-512 612 | "2.16.840.1.101.3.4.2.3" | "1.2.840.113549.1.1.13" | "1.2.840.10045.4.3.4" => { 613 | Digest::Sha512 614 | } 615 | _ => return Ok(None), 616 | }; 617 | 618 | let der = cert.to_der(); 619 | Ok(Some(digest.hash(&der))) 620 | } 621 | 622 | pub fn shutdown(&mut self) -> io::Result<()> { 623 | self.stream.close()?; 624 | Ok(()) 625 | } 626 | } 627 | 628 | impl io::Read for TlsStream { 629 | fn read(&mut self, buf: &mut [u8]) -> io::Result { 630 | self.stream.read(buf) 631 | } 632 | } 633 | 634 | impl io::Write for TlsStream { 635 | fn write(&mut self, buf: &[u8]) -> io::Result { 636 | self.stream.write(buf) 637 | } 638 | 639 | fn flush(&mut self) -> io::Result<()> { 640 | self.stream.flush() 641 | } 642 | } 643 | 644 | enum Digest { 645 | Sha224, 646 | Sha256, 647 | Sha384, 648 | Sha512, 649 | } 650 | 651 | impl Digest { 652 | fn hash(&self, data: &[u8]) -> Vec { 653 | unsafe { 654 | assert!(data.len() <= CC_LONG::max_value() as usize); 655 | match *self { 656 | Digest::Sha224 => { 657 | let mut buf = [0; CC_SHA224_DIGEST_LENGTH]; 658 | CC_SHA224(data.as_ptr(), data.len() as CC_LONG, buf.as_mut_ptr()); 659 | buf.to_vec() 660 | } 661 | Digest::Sha256 => { 662 | let mut buf = [0; CC_SHA256_DIGEST_LENGTH]; 663 | CC_SHA256(data.as_ptr(), data.len() as CC_LONG, buf.as_mut_ptr()); 664 | buf.to_vec() 665 | } 666 | Digest::Sha384 => { 667 | let mut buf = [0; CC_SHA384_DIGEST_LENGTH]; 668 | CC_SHA384(data.as_ptr(), data.len() as CC_LONG, buf.as_mut_ptr()); 669 | buf.to_vec() 670 | } 671 | Digest::Sha512 => { 672 | let mut buf = [0; CC_SHA512_DIGEST_LENGTH]; 673 | CC_SHA512(data.as_ptr(), data.len() as CC_LONG, buf.as_mut_ptr()); 674 | buf.to_vec() 675 | } 676 | } 677 | } 678 | } 679 | } 680 | 681 | // FIXME ideally we'd pull these in from elsewhere 682 | const CC_SHA224_DIGEST_LENGTH: usize = 28; 683 | const CC_SHA256_DIGEST_LENGTH: usize = 32; 684 | const CC_SHA384_DIGEST_LENGTH: usize = 48; 685 | const CC_SHA512_DIGEST_LENGTH: usize = 64; 686 | #[allow(non_camel_case_types)] 687 | type CC_LONG = u32; 688 | 689 | extern "C" { 690 | fn CC_SHA224(data: *const u8, len: CC_LONG, md: *mut u8) -> *mut u8; 691 | fn CC_SHA256(data: *const u8, len: CC_LONG, md: *mut u8) -> *mut u8; 692 | fn CC_SHA384(data: *const u8, len: CC_LONG, md: *mut u8) -> *mut u8; 693 | fn CC_SHA512(data: *const u8, len: CC_LONG, md: *mut u8) -> *mut u8; 694 | } 695 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! An abstraction over platform-specific TLS implementations. 2 | //! 3 | //! Many applications require TLS/SSL communication in one form or another as 4 | //! part of their implementation, but finding a library for this isn't always 5 | //! trivial! The purpose of this crate is to provide a seamless integration 6 | //! experience on all platforms with a cross-platform API that deals with all 7 | //! the underlying details for you. 8 | //! 9 | //! # How is this implemented? 10 | //! 11 | //! This crate uses SChannel on Windows (via the `schannel` crate), Secure 12 | //! Transport on OSX (via the `security-framework` crate), and OpenSSL (via the 13 | //! `openssl` crate) on all other platforms. Future features may also enable 14 | //! other TLS frameworks as well, but these initial libraries are likely to 15 | //! remain as the defaults. 16 | //! 17 | //! Note that this crate also strives to be secure-by-default. For example when 18 | //! using OpenSSL it will configure validation callbacks to ensure that 19 | //! hostnames match certificates, use strong ciphers, etc. This implies that 20 | //! this crate is *not* just a thin abstraction around the underlying libraries, 21 | //! but also an implementation that strives to strike reasonable defaults. 22 | //! 23 | //! # Supported features 24 | //! 25 | //! This crate supports the following features out of the box: 26 | //! 27 | //! * TLS/SSL client communication 28 | //! * TLS/SSL server communication 29 | //! * PKCS#12 encoded identities 30 | //! * X.509/PKCS#8 encoded identities 31 | //! * Secure-by-default for client and server 32 | //! * Includes hostname verification for clients 33 | //! * Supports asynchronous I/O for both the server and the client 34 | //! 35 | //! # Cargo Features 36 | //! 37 | //! * `vendored` - If enabled, the crate will compile and statically link to a 38 | //! vendored copy of OpenSSL. This feature has no effect on Windows and 39 | //! macOS, where OpenSSL is not used. 40 | //! 41 | //! # Examples 42 | //! 43 | //! To connect as a client to a remote server: 44 | //! 45 | //! ```rust 46 | //! use native_tls::TlsConnector; 47 | //! use std::io::{Read, Write}; 48 | //! use std::net::TcpStream; 49 | //! 50 | //! let connector = TlsConnector::new().unwrap(); 51 | //! 52 | //! let stream = TcpStream::connect("google.com:443").unwrap(); 53 | //! let mut stream = connector.connect("google.com", stream).unwrap(); 54 | //! 55 | //! stream.write_all(b"GET / HTTP/1.0\r\n\r\n").unwrap(); 56 | //! let mut res = vec![]; 57 | //! stream.read_to_end(&mut res).unwrap(); 58 | //! println!("{}", String::from_utf8_lossy(&res)); 59 | //! ``` 60 | //! 61 | //! To accept connections as a server from remote clients: 62 | //! 63 | //! ```rust,no_run 64 | //! use native_tls::{Identity, TlsAcceptor, TlsStream}; 65 | //! use std::fs::File; 66 | //! use std::io::{Read}; 67 | //! use std::net::{TcpListener, TcpStream}; 68 | //! use std::sync::Arc; 69 | //! use std::thread; 70 | //! 71 | //! let mut file = File::open("identity.pfx").unwrap(); 72 | //! let mut identity = vec![]; 73 | //! file.read_to_end(&mut identity).unwrap(); 74 | //! let identity = Identity::from_pkcs12(&identity, "hunter2").unwrap(); 75 | //! 76 | //! let listener = TcpListener::bind("0.0.0.0:8443").unwrap(); 77 | //! let acceptor = TlsAcceptor::new(identity).unwrap(); 78 | //! let acceptor = Arc::new(acceptor); 79 | //! 80 | //! fn handle_client(stream: TlsStream) { 81 | //! // ... 82 | //! } 83 | //! 84 | //! for stream in listener.incoming() { 85 | //! match stream { 86 | //! Ok(stream) => { 87 | //! let acceptor = acceptor.clone(); 88 | //! thread::spawn(move || { 89 | //! let stream = acceptor.accept(stream).unwrap(); 90 | //! handle_client(stream); 91 | //! }); 92 | //! } 93 | //! Err(e) => { /* connection failed */ } 94 | //! } 95 | //! } 96 | //! ``` 97 | #![warn(missing_docs)] 98 | #![cfg_attr(docsrs, feature(doc_cfg))] 99 | 100 | use std::any::Any; 101 | use std::error; 102 | use std::fmt; 103 | use std::io; 104 | use std::result; 105 | 106 | #[cfg(not(any(target_os = "windows", target_vendor = "apple")))] 107 | #[macro_use] 108 | extern crate log; 109 | #[cfg(target_vendor = "apple")] 110 | #[path = "imp/security_framework.rs"] 111 | mod imp; 112 | #[cfg(target_os = "windows")] 113 | #[path = "imp/schannel.rs"] 114 | mod imp; 115 | #[cfg(not(any(target_vendor = "apple", target_os = "windows")))] 116 | #[path = "imp/openssl.rs"] 117 | mod imp; 118 | 119 | #[cfg(test)] 120 | mod test; 121 | 122 | /// A typedef of the result-type returned by many methods. 123 | pub type Result = result::Result; 124 | 125 | /// An error returned from the TLS implementation. 126 | pub struct Error(imp::Error); 127 | 128 | impl error::Error for Error { 129 | fn source(&self) -> Option<&(dyn error::Error + 'static)> { 130 | error::Error::source(&self.0) 131 | } 132 | } 133 | 134 | impl fmt::Display for Error { 135 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 136 | fmt::Display::fmt(&self.0, fmt) 137 | } 138 | } 139 | 140 | impl fmt::Debug for Error { 141 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 142 | fmt::Debug::fmt(&self.0, fmt) 143 | } 144 | } 145 | 146 | impl From for Error { 147 | fn from(err: imp::Error) -> Error { 148 | Error(err) 149 | } 150 | } 151 | 152 | /// A cryptographic identity. 153 | /// 154 | /// An identity is an X509 certificate along with its corresponding private key and chain of certificates to a trusted 155 | /// root. 156 | #[derive(Clone)] 157 | pub struct Identity(imp::Identity); 158 | 159 | impl Identity { 160 | /// Parses a DER-formatted PKCS #12 archive, using the specified password to decrypt the key. 161 | /// 162 | /// The archive should contain a leaf certificate and its private key, as well any intermediate 163 | /// certificates that should be sent to clients to allow them to build a chain to a trusted 164 | /// root. The chain certificates should be in order from the leaf certificate towards the root. 165 | /// 166 | /// PKCS #12 archives typically have the file extension `.p12` or `.pfx`, and can be created 167 | /// with the OpenSSL `pkcs12` tool: 168 | /// 169 | /// ```bash 170 | /// openssl pkcs12 -export -out identity.pfx -inkey key.pem -in cert.pem -certfile chain_certs.pem 171 | /// ``` 172 | pub fn from_pkcs12(der: &[u8], password: &str) -> Result { 173 | let identity = imp::Identity::from_pkcs12(der, password)?; 174 | Ok(Identity(identity)) 175 | } 176 | 177 | /// Parses a chain of PEM encoded X509 certificates, with the leaf certificate first. 178 | /// `key` is a PEM encoded PKCS #8 formatted private key for the leaf certificate. 179 | /// 180 | /// The certificate chain should contain any intermediate cerficates that should be sent to 181 | /// clients to allow them to build a chain to a trusted root. 182 | /// 183 | /// A certificate chain here means a series of PEM encoded certificates concatenated together. 184 | pub fn from_pkcs8(pem: &[u8], key: &[u8]) -> Result { 185 | let identity = imp::Identity::from_pkcs8(pem, key)?; 186 | Ok(Identity(identity)) 187 | } 188 | } 189 | 190 | /// An X509 certificate. 191 | #[derive(Clone)] 192 | pub struct Certificate(imp::Certificate); 193 | 194 | impl Certificate { 195 | /// Parses a DER-formatted X509 certificate. 196 | pub fn from_der(der: &[u8]) -> Result { 197 | let cert = imp::Certificate::from_der(der)?; 198 | Ok(Certificate(cert)) 199 | } 200 | 201 | /// Parses a PEM-formatted X509 certificate. 202 | pub fn from_pem(pem: &[u8]) -> Result { 203 | let cert = imp::Certificate::from_pem(pem)?; 204 | Ok(Certificate(cert)) 205 | } 206 | 207 | /// Returns the DER-encoded representation of this certificate. 208 | pub fn to_der(&self) -> Result> { 209 | let der = self.0.to_der()?; 210 | Ok(der) 211 | } 212 | } 213 | 214 | /// A TLS stream which has been interrupted midway through the handshake process. 215 | pub struct MidHandshakeTlsStream(imp::MidHandshakeTlsStream); 216 | 217 | impl fmt::Debug for MidHandshakeTlsStream 218 | where 219 | S: fmt::Debug, 220 | { 221 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 222 | fmt::Debug::fmt(&self.0, fmt) 223 | } 224 | } 225 | 226 | impl MidHandshakeTlsStream { 227 | /// Returns a shared reference to the inner stream. 228 | pub fn get_ref(&self) -> &S { 229 | self.0.get_ref() 230 | } 231 | 232 | /// Returns a mutable reference to the inner stream. 233 | pub fn get_mut(&mut self) -> &mut S { 234 | self.0.get_mut() 235 | } 236 | } 237 | 238 | impl MidHandshakeTlsStream 239 | where 240 | S: io::Read + io::Write, 241 | { 242 | /// Restarts the handshake process. 243 | /// 244 | /// If the handshake completes successfully then the negotiated stream is 245 | /// returned. If there is a problem, however, then an error is returned. 246 | /// Note that the error may not be fatal. For example if the underlying 247 | /// stream is an asynchronous one then `HandshakeError::WouldBlock` may 248 | /// just mean to wait for more I/O to happen later. 249 | pub fn handshake(self) -> result::Result, HandshakeError> { 250 | match self.0.handshake() { 251 | Ok(s) => Ok(TlsStream(s)), 252 | Err(e) => Err(e.into()), 253 | } 254 | } 255 | } 256 | 257 | /// An error returned from `ClientBuilder::handshake`. 258 | #[derive(Debug)] 259 | pub enum HandshakeError { 260 | /// A fatal error. 261 | Failure(Error), 262 | 263 | /// A stream interrupted midway through the handshake process due to a 264 | /// `WouldBlock` error. 265 | /// 266 | /// Note that this is not a fatal error and it should be safe to call 267 | /// `handshake` at a later time once the stream is ready to perform I/O 268 | /// again. 269 | WouldBlock(MidHandshakeTlsStream), 270 | } 271 | 272 | impl error::Error for HandshakeError 273 | where 274 | S: Any + fmt::Debug, 275 | { 276 | fn source(&self) -> Option<&(dyn error::Error + 'static)> { 277 | match *self { 278 | HandshakeError::Failure(ref e) => Some(e), 279 | HandshakeError::WouldBlock(_) => None, 280 | } 281 | } 282 | } 283 | 284 | impl fmt::Display for HandshakeError 285 | where 286 | S: Any + fmt::Debug, 287 | { 288 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 289 | match *self { 290 | HandshakeError::Failure(ref e) => fmt::Display::fmt(e, fmt), 291 | HandshakeError::WouldBlock(_) => fmt.write_str("the handshake process was interrupted"), 292 | } 293 | } 294 | } 295 | 296 | impl From> for HandshakeError { 297 | fn from(e: imp::HandshakeError) -> HandshakeError { 298 | match e { 299 | imp::HandshakeError::Failure(e) => HandshakeError::Failure(Error(e)), 300 | imp::HandshakeError::WouldBlock(s) => { 301 | HandshakeError::WouldBlock(MidHandshakeTlsStream(s)) 302 | } 303 | } 304 | } 305 | } 306 | 307 | /// SSL/TLS protocol versions. 308 | #[derive(Debug, Copy, Clone)] 309 | #[non_exhaustive] 310 | pub enum Protocol { 311 | /// The SSL 3.0 protocol. 312 | /// 313 | /// # Warning 314 | /// 315 | /// SSL 3.0 has severe security flaws, and should not be used unless absolutely necessary. If 316 | /// you are not sure if you need to enable this protocol, you should not. 317 | Sslv3, 318 | /// The TLS 1.0 protocol. 319 | Tlsv10, 320 | /// The TLS 1.1 protocol. 321 | Tlsv11, 322 | /// The TLS 1.2 protocol. 323 | Tlsv12, 324 | } 325 | 326 | /// A builder for `TlsConnector`s. 327 | /// 328 | /// You can get one from [`TlsConnector::builder()`](TlsConnector::builder) 329 | pub struct TlsConnectorBuilder { 330 | identity: Option, 331 | min_protocol: Option, 332 | max_protocol: Option, 333 | root_certificates: Vec, 334 | accept_invalid_certs: bool, 335 | accept_invalid_hostnames: bool, 336 | use_sni: bool, 337 | disable_built_in_roots: bool, 338 | #[cfg(feature = "alpn")] 339 | alpn: Vec, 340 | } 341 | 342 | impl TlsConnectorBuilder { 343 | /// Sets the identity to be used for client certificate authentication. 344 | pub fn identity(&mut self, identity: Identity) -> &mut TlsConnectorBuilder { 345 | self.identity = Some(identity); 346 | self 347 | } 348 | 349 | /// Sets the minimum supported protocol version. 350 | /// 351 | /// A value of `None` enables support for the oldest protocols supported by the implementation. 352 | /// 353 | /// Defaults to `Some(Protocol::Tlsv10)`. 354 | pub fn min_protocol_version(&mut self, protocol: Option) -> &mut TlsConnectorBuilder { 355 | self.min_protocol = protocol; 356 | self 357 | } 358 | 359 | /// Sets the maximum supported protocol version. 360 | /// 361 | /// A value of `None` enables support for the newest protocols supported by the implementation. 362 | /// 363 | /// Defaults to `None`. 364 | pub fn max_protocol_version(&mut self, protocol: Option) -> &mut TlsConnectorBuilder { 365 | self.max_protocol = protocol; 366 | self 367 | } 368 | 369 | /// Adds a certificate to the set of roots that the connector will trust. 370 | /// 371 | /// The connector will use the system's trust root by default. This method can be used to add 372 | /// to that set when communicating with servers not trusted by the system. 373 | /// 374 | /// Defaults to an empty set. 375 | pub fn add_root_certificate(&mut self, cert: Certificate) -> &mut TlsConnectorBuilder { 376 | self.root_certificates.push(cert); 377 | self 378 | } 379 | 380 | /// Controls the use of built-in system certificates during certificate validation. 381 | /// 382 | /// Defaults to `false` -- built-in system certs will be used. 383 | pub fn disable_built_in_roots(&mut self, disable: bool) -> &mut TlsConnectorBuilder { 384 | self.disable_built_in_roots = disable; 385 | self 386 | } 387 | 388 | /// Request specific protocols through ALPN (Application-Layer Protocol Negotiation). 389 | /// 390 | /// Defaults to no protocols. 391 | #[cfg(feature = "alpn")] 392 | #[cfg_attr(docsrs, doc(cfg(feature = "alpn")))] 393 | pub fn request_alpns(&mut self, protocols: &[&str]) -> &mut TlsConnectorBuilder { 394 | self.alpn = protocols.iter().map(|s| (*s).to_owned()).collect(); 395 | self 396 | } 397 | 398 | /// Controls the use of certificate validation. 399 | /// 400 | /// Defaults to `false`. 401 | /// 402 | /// # Warning 403 | /// 404 | /// You should think very carefully before using this method. If invalid certificates are trusted, *any* 405 | /// certificate for *any* site will be trusted for use. This includes expired certificates. This introduces 406 | /// significant vulnerabilities, and should only be used as a last resort. 407 | pub fn danger_accept_invalid_certs( 408 | &mut self, 409 | accept_invalid_certs: bool, 410 | ) -> &mut TlsConnectorBuilder { 411 | self.accept_invalid_certs = accept_invalid_certs; 412 | self 413 | } 414 | 415 | /// Controls the use of Server Name Indication (SNI). 416 | /// 417 | /// Defaults to `true`. 418 | pub fn use_sni(&mut self, use_sni: bool) -> &mut TlsConnectorBuilder { 419 | self.use_sni = use_sni; 420 | self 421 | } 422 | 423 | /// Controls the use of hostname verification. 424 | /// 425 | /// Defaults to `false`. 426 | /// 427 | /// # Warning 428 | /// 429 | /// You should think very carefully before using this method. If invalid hostnames are trusted, *any* valid 430 | /// certificate for *any* site will be trusted for use. This introduces significant vulnerabilities, and should 431 | /// only be used as a last resort. 432 | pub fn danger_accept_invalid_hostnames( 433 | &mut self, 434 | accept_invalid_hostnames: bool, 435 | ) -> &mut TlsConnectorBuilder { 436 | self.accept_invalid_hostnames = accept_invalid_hostnames; 437 | self 438 | } 439 | 440 | /// Creates a new `TlsConnector`. 441 | pub fn build(&self) -> Result { 442 | let connector = imp::TlsConnector::new(self)?; 443 | Ok(TlsConnector(connector)) 444 | } 445 | } 446 | 447 | /// A builder for client-side TLS connections. 448 | /// 449 | /// # Examples 450 | /// 451 | /// ```rust 452 | /// use native_tls::TlsConnector; 453 | /// use std::io::{Read, Write}; 454 | /// use std::net::TcpStream; 455 | /// 456 | /// let connector = TlsConnector::new().unwrap(); 457 | /// 458 | /// let stream = TcpStream::connect("google.com:443").unwrap(); 459 | /// let mut stream = connector.connect("google.com", stream).unwrap(); 460 | /// 461 | /// stream.write_all(b"GET / HTTP/1.0\r\n\r\n").unwrap(); 462 | /// let mut res = vec![]; 463 | /// stream.read_to_end(&mut res).unwrap(); 464 | /// println!("{}", String::from_utf8_lossy(&res)); 465 | /// ``` 466 | #[derive(Clone, Debug)] 467 | pub struct TlsConnector(imp::TlsConnector); 468 | 469 | impl TlsConnector { 470 | /// Returns a new connector with default settings. 471 | pub fn new() -> Result { 472 | TlsConnector::builder().build() 473 | } 474 | 475 | /// Returns a new builder for a `TlsConnector`. 476 | pub fn builder() -> TlsConnectorBuilder { 477 | TlsConnectorBuilder { 478 | identity: None, 479 | min_protocol: Some(Protocol::Tlsv10), 480 | max_protocol: None, 481 | root_certificates: vec![], 482 | use_sni: true, 483 | accept_invalid_certs: false, 484 | accept_invalid_hostnames: false, 485 | disable_built_in_roots: false, 486 | #[cfg(feature = "alpn")] 487 | alpn: vec![], 488 | } 489 | } 490 | 491 | /// Initiates a TLS handshake. 492 | /// 493 | /// The provided domain will be used for both SNI and certificate hostname 494 | /// validation. 495 | /// 496 | /// If the socket is nonblocking and a `WouldBlock` error is returned during 497 | /// the handshake, a `HandshakeError::WouldBlock` error will be returned 498 | /// which can be used to restart the handshake when the socket is ready 499 | /// again. 500 | /// 501 | /// The domain is ignored if both SNI and hostname verification are 502 | /// disabled. 503 | pub fn connect( 504 | &self, 505 | domain: &str, 506 | stream: S, 507 | ) -> result::Result, HandshakeError> 508 | where 509 | S: io::Read + io::Write, 510 | { 511 | let s = self.0.connect(domain, stream)?; 512 | Ok(TlsStream(s)) 513 | } 514 | } 515 | 516 | /// A builder for `TlsAcceptor`s. 517 | /// 518 | /// You can get one from [`TlsAcceptor::builder()`](TlsAcceptor::builder) 519 | pub struct TlsAcceptorBuilder { 520 | identity: Identity, 521 | min_protocol: Option, 522 | max_protocol: Option, 523 | } 524 | 525 | impl TlsAcceptorBuilder { 526 | /// Sets the minimum supported protocol version. 527 | /// 528 | /// A value of `None` enables support for the oldest protocols supported by the implementation. 529 | /// 530 | /// Defaults to `Some(Protocol::Tlsv10)`. 531 | pub fn min_protocol_version(&mut self, protocol: Option) -> &mut TlsAcceptorBuilder { 532 | self.min_protocol = protocol; 533 | self 534 | } 535 | 536 | /// Sets the maximum supported protocol version. 537 | /// 538 | /// A value of `None` enables support for the newest protocols supported by the implementation. 539 | /// 540 | /// Defaults to `None`. 541 | pub fn max_protocol_version(&mut self, protocol: Option) -> &mut TlsAcceptorBuilder { 542 | self.max_protocol = protocol; 543 | self 544 | } 545 | 546 | /// Creates a new `TlsAcceptor`. 547 | pub fn build(&self) -> Result { 548 | let acceptor = imp::TlsAcceptor::new(self)?; 549 | Ok(TlsAcceptor(acceptor)) 550 | } 551 | } 552 | 553 | /// A builder for server-side TLS connections. 554 | /// 555 | /// # Examples 556 | /// 557 | /// ```rust,no_run 558 | /// use native_tls::{Identity, TlsAcceptor, TlsStream}; 559 | /// use std::fs::File; 560 | /// use std::io::{Read}; 561 | /// use std::net::{TcpListener, TcpStream}; 562 | /// use std::sync::Arc; 563 | /// use std::thread; 564 | /// 565 | /// let mut file = File::open("identity.pfx").unwrap(); 566 | /// let mut identity = vec![]; 567 | /// file.read_to_end(&mut identity).unwrap(); 568 | /// let identity = Identity::from_pkcs12(&identity, "hunter2").unwrap(); 569 | /// 570 | /// let listener = TcpListener::bind("0.0.0.0:8443").unwrap(); 571 | /// let acceptor = TlsAcceptor::new(identity).unwrap(); 572 | /// let acceptor = Arc::new(acceptor); 573 | /// 574 | /// fn handle_client(stream: TlsStream) { 575 | /// // ... 576 | /// } 577 | /// 578 | /// for stream in listener.incoming() { 579 | /// match stream { 580 | /// Ok(stream) => { 581 | /// let acceptor = acceptor.clone(); 582 | /// thread::spawn(move || { 583 | /// let stream = acceptor.accept(stream).unwrap(); 584 | /// handle_client(stream); 585 | /// }); 586 | /// } 587 | /// Err(e) => { /* connection failed */ } 588 | /// } 589 | /// } 590 | /// ``` 591 | #[derive(Clone)] 592 | pub struct TlsAcceptor(imp::TlsAcceptor); 593 | 594 | impl TlsAcceptor { 595 | /// Creates a acceptor with default settings. 596 | /// 597 | /// The identity acts as the server's private key/certificate chain. 598 | pub fn new(identity: Identity) -> Result { 599 | TlsAcceptor::builder(identity).build() 600 | } 601 | 602 | /// Returns a new builder for a `TlsAcceptor`. 603 | /// 604 | /// The identity acts as the server's private key/certificate chain. 605 | pub fn builder(identity: Identity) -> TlsAcceptorBuilder { 606 | TlsAcceptorBuilder { 607 | identity, 608 | min_protocol: Some(Protocol::Tlsv10), 609 | max_protocol: None, 610 | } 611 | } 612 | 613 | /// Initiates a TLS handshake. 614 | /// 615 | /// If the socket is nonblocking and a `WouldBlock` error is returned during 616 | /// the handshake, a `HandshakeError::WouldBlock` error will be returned 617 | /// which can be used to restart the handshake when the socket is ready 618 | /// again. 619 | pub fn accept(&self, stream: S) -> result::Result, HandshakeError> 620 | where 621 | S: io::Read + io::Write, 622 | { 623 | match self.0.accept(stream) { 624 | Ok(s) => Ok(TlsStream(s)), 625 | Err(e) => Err(e.into()), 626 | } 627 | } 628 | } 629 | 630 | /// A stream managing a TLS session. 631 | pub struct TlsStream(imp::TlsStream); 632 | 633 | impl fmt::Debug for TlsStream { 634 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 635 | fmt::Debug::fmt(&self.0, fmt) 636 | } 637 | } 638 | 639 | impl TlsStream { 640 | /// Returns a shared reference to the inner stream. 641 | pub fn get_ref(&self) -> &S { 642 | self.0.get_ref() 643 | } 644 | 645 | /// Returns a mutable reference to the inner stream. 646 | pub fn get_mut(&mut self) -> &mut S { 647 | self.0.get_mut() 648 | } 649 | } 650 | 651 | impl TlsStream { 652 | /// Returns the number of bytes that can be read without resulting in any 653 | /// network calls. 654 | pub fn buffered_read_size(&self) -> Result { 655 | Ok(self.0.buffered_read_size()?) 656 | } 657 | 658 | /// Returns the peer's leaf certificate, if available. 659 | pub fn peer_certificate(&self) -> Result> { 660 | Ok(self.0.peer_certificate()?.map(Certificate)) 661 | } 662 | 663 | /// Returns the tls-server-end-point channel binding data as defined in [RFC 5929]. 664 | /// 665 | /// [RFC 5929]: https://tools.ietf.org/html/rfc5929 666 | pub fn tls_server_end_point(&self) -> Result>> { 667 | Ok(self.0.tls_server_end_point()?) 668 | } 669 | 670 | /// Returns the negotiated ALPN protocol. 671 | #[cfg(feature = "alpn")] 672 | #[cfg_attr(docsrs, doc(cfg(feature = "alpn")))] 673 | pub fn negotiated_alpn(&self) -> Result>> { 674 | Ok(self.0.negotiated_alpn()?) 675 | } 676 | 677 | /// Shuts down the TLS session. 678 | pub fn shutdown(&mut self) -> io::Result<()> { 679 | self.0.shutdown()?; 680 | Ok(()) 681 | } 682 | } 683 | 684 | impl io::Read for TlsStream { 685 | fn read(&mut self, buf: &mut [u8]) -> io::Result { 686 | self.0.read(buf) 687 | } 688 | } 689 | 690 | impl io::Write for TlsStream { 691 | fn write(&mut self, buf: &[u8]) -> io::Result { 692 | self.0.write(buf) 693 | } 694 | 695 | fn flush(&mut self) -> io::Result<()> { 696 | self.0.flush() 697 | } 698 | } 699 | 700 | fn _check_kinds() { 701 | use std::net::TcpStream; 702 | 703 | fn is_sync() {} 704 | fn is_send() {} 705 | is_sync::(); 706 | is_send::(); 707 | is_sync::(); 708 | is_send::(); 709 | is_sync::(); 710 | is_send::(); 711 | is_sync::(); 712 | is_send::(); 713 | is_sync::(); 714 | is_send::(); 715 | is_sync::>(); 716 | is_send::>(); 717 | is_sync::>(); 718 | is_send::>(); 719 | } 720 | -------------------------------------------------------------------------------- /src/test.rs: -------------------------------------------------------------------------------- 1 | use std::fs; 2 | use std::io::{Read, Write}; 3 | use std::net::{TcpListener, TcpStream}; 4 | use std::process::{Command, Stdio}; 5 | use std::string::String; 6 | use std::thread; 7 | 8 | use super::*; 9 | 10 | macro_rules! p { 11 | ($e:expr) => { 12 | match $e { 13 | Ok(r) => r, 14 | Err(e) => panic!("{:?}", e), 15 | } 16 | }; 17 | } 18 | 19 | #[test] 20 | fn connect_google() { 21 | let builder = p!(TlsConnector::new()); 22 | let s = p!(TcpStream::connect("google.com:443")); 23 | let mut socket = p!(builder.connect("google.com", s)); 24 | 25 | p!(socket.write_all(b"GET / HTTP/1.0\r\n\r\n")); 26 | let mut result = vec![]; 27 | p!(socket.read_to_end(&mut result)); 28 | 29 | println!("{}", String::from_utf8_lossy(&result)); 30 | assert!(result.starts_with(b"HTTP/1.0")); 31 | assert!(result.ends_with(b"\r\n") || result.ends_with(b"")); 32 | } 33 | 34 | #[test] 35 | fn connect_bad_hostname() { 36 | let builder = p!(TlsConnector::new()); 37 | let s = p!(TcpStream::connect("google.com:443")); 38 | builder.connect("goggle.com", s).unwrap_err(); 39 | } 40 | 41 | #[test] 42 | fn connect_bad_hostname_ignored() { 43 | let builder = p!(TlsConnector::builder() 44 | .danger_accept_invalid_hostnames(true) 45 | .build()); 46 | let s = p!(TcpStream::connect("google.com:443")); 47 | builder.connect("goggle.com", s).unwrap(); 48 | } 49 | 50 | #[test] 51 | fn connect_no_root_certs() { 52 | let builder = p!(TlsConnector::builder().disable_built_in_roots(true).build()); 53 | let s = p!(TcpStream::connect("google.com:443")); 54 | assert!(builder.connect("google.com", s).is_err()); 55 | } 56 | 57 | #[test] 58 | fn server_no_root_certs() { 59 | let keys = test_cert_gen::keys(); 60 | 61 | let identity = p!(Identity::from_pkcs12( 62 | &keys.server.cert_and_key_pkcs12.pkcs12.0, 63 | &keys.server.cert_and_key_pkcs12.password 64 | )); 65 | let builder = p!(TlsAcceptor::new(identity)); 66 | 67 | let listener = p!(TcpListener::bind("0.0.0.0:0")); 68 | let port = p!(listener.local_addr()).port(); 69 | 70 | let j = thread::spawn(move || { 71 | let socket = p!(listener.accept()).0; 72 | let mut socket = p!(builder.accept(socket)); 73 | 74 | let mut buf = [0; 5]; 75 | p!(socket.read_exact(&mut buf)); 76 | assert_eq!(&buf, b"hello"); 77 | 78 | p!(socket.write_all(b"world")); 79 | }); 80 | 81 | let root_ca = Certificate::from_der(keys.client.ca.get_der()).unwrap(); 82 | 83 | let socket = p!(TcpStream::connect(("localhost", port))); 84 | let builder = p!(TlsConnector::builder() 85 | .disable_built_in_roots(true) 86 | .add_root_certificate(root_ca) 87 | .build()); 88 | let mut socket = p!(builder.connect("localhost", socket)); 89 | 90 | p!(socket.write_all(b"hello")); 91 | let mut buf = vec![]; 92 | p!(socket.read_to_end(&mut buf)); 93 | assert_eq!(buf, b"world"); 94 | 95 | p!(j.join()); 96 | } 97 | 98 | #[test] 99 | fn server() { 100 | let keys = test_cert_gen::keys(); 101 | 102 | let identity = p!(Identity::from_pkcs12( 103 | &keys.server.cert_and_key_pkcs12.pkcs12.0, 104 | &keys.server.cert_and_key_pkcs12.password 105 | )); 106 | let builder = p!(TlsAcceptor::new(identity)); 107 | 108 | let listener = p!(TcpListener::bind("0.0.0.0:0")); 109 | let port = p!(listener.local_addr()).port(); 110 | 111 | let j = thread::spawn(move || { 112 | let socket = p!(listener.accept()).0; 113 | let mut socket = p!(builder.accept(socket)); 114 | 115 | let mut buf = [0; 5]; 116 | p!(socket.read_exact(&mut buf)); 117 | assert_eq!(&buf, b"hello"); 118 | 119 | p!(socket.write_all(b"world")); 120 | }); 121 | 122 | let root_ca = Certificate::from_der(keys.client.ca.get_der()).unwrap(); 123 | 124 | let socket = p!(TcpStream::connect(("localhost", port))); 125 | let builder = p!(TlsConnector::builder() 126 | .add_root_certificate(root_ca) 127 | .build()); 128 | let mut socket = p!(builder.connect("localhost", socket)); 129 | 130 | p!(socket.write_all(b"hello")); 131 | let mut buf = vec![]; 132 | p!(socket.read_to_end(&mut buf)); 133 | assert_eq!(buf, b"world"); 134 | 135 | p!(j.join()); 136 | } 137 | 138 | #[test] 139 | fn certificate_from_pem() { 140 | let dir = tempfile::tempdir().unwrap(); 141 | let keys = test_cert_gen::keys(); 142 | 143 | let der_path = dir.path().join("cert.der"); 144 | fs::write(&der_path, keys.client.ca.get_der()).unwrap(); 145 | let output = Command::new("openssl") 146 | .arg("x509") 147 | .arg("-in") 148 | .arg(der_path) 149 | .arg("-inform") 150 | .arg("der") 151 | .stderr(Stdio::piped()) 152 | .output() 153 | .unwrap(); 154 | 155 | assert!(output.status.success()); 156 | 157 | let cert = Certificate::from_pem(&output.stdout).unwrap(); 158 | assert_eq!(cert.to_der().unwrap(), keys.client.ca.get_der()); 159 | } 160 | 161 | #[test] 162 | fn peer_certificate() { 163 | let keys = test_cert_gen::keys(); 164 | 165 | let identity = p!(Identity::from_pkcs12( 166 | &keys.server.cert_and_key_pkcs12.pkcs12.0, 167 | &keys.server.cert_and_key_pkcs12.password 168 | )); 169 | let builder = p!(TlsAcceptor::new(identity)); 170 | 171 | let listener = p!(TcpListener::bind("0.0.0.0:0")); 172 | let port = p!(listener.local_addr()).port(); 173 | 174 | let j = thread::spawn(move || { 175 | let socket = p!(listener.accept()).0; 176 | let socket = p!(builder.accept(socket)); 177 | assert!(socket.peer_certificate().unwrap().is_none()); 178 | }); 179 | 180 | let root_ca = Certificate::from_der(keys.client.ca.get_der()).unwrap(); 181 | 182 | let socket = p!(TcpStream::connect(("localhost", port))); 183 | let builder = p!(TlsConnector::builder() 184 | .add_root_certificate(root_ca) 185 | .build()); 186 | let socket = p!(builder.connect("localhost", socket)); 187 | 188 | let cert = socket.peer_certificate().unwrap().unwrap(); 189 | assert_eq!( 190 | cert.to_der().unwrap(), 191 | keys.server.cert_and_key.cert.get_der() 192 | ); 193 | 194 | p!(j.join()); 195 | } 196 | 197 | #[test] 198 | fn server_tls11_only() { 199 | let keys = test_cert_gen::keys(); 200 | 201 | let identity = p!(Identity::from_pkcs12( 202 | &keys.server.cert_and_key_pkcs12.pkcs12.0, 203 | &keys.server.cert_and_key_pkcs12.password 204 | )); 205 | let builder = p!(TlsAcceptor::builder(identity) 206 | .min_protocol_version(Some(Protocol::Tlsv12)) 207 | .max_protocol_version(Some(Protocol::Tlsv12)) 208 | .build()); 209 | 210 | let listener = p!(TcpListener::bind("0.0.0.0:0")); 211 | let port = p!(listener.local_addr()).port(); 212 | 213 | let j = thread::spawn(move || { 214 | let socket = p!(listener.accept()).0; 215 | let mut socket = p!(builder.accept(socket)); 216 | 217 | let mut buf = [0; 5]; 218 | p!(socket.read_exact(&mut buf)); 219 | assert_eq!(&buf, b"hello"); 220 | 221 | p!(socket.write_all(b"world")); 222 | }); 223 | 224 | let root_ca = Certificate::from_der(keys.client.ca.get_der()).unwrap(); 225 | 226 | let socket = p!(TcpStream::connect(("localhost", port))); 227 | let builder = p!(TlsConnector::builder() 228 | .add_root_certificate(root_ca) 229 | .min_protocol_version(Some(Protocol::Tlsv12)) 230 | .max_protocol_version(Some(Protocol::Tlsv12)) 231 | .build()); 232 | let mut socket = p!(builder.connect("localhost", socket)); 233 | 234 | p!(socket.write_all(b"hello")); 235 | let mut buf = vec![]; 236 | p!(socket.read_to_end(&mut buf)); 237 | assert_eq!(buf, b"world"); 238 | 239 | p!(j.join()); 240 | } 241 | 242 | #[test] 243 | fn server_no_shared_protocol() { 244 | let keys = test_cert_gen::keys(); 245 | 246 | let identity = p!(Identity::from_pkcs12( 247 | &keys.server.cert_and_key_pkcs12.pkcs12.0, 248 | &keys.server.cert_and_key_pkcs12.password 249 | )); 250 | let builder = p!(TlsAcceptor::builder(identity) 251 | .min_protocol_version(Some(Protocol::Tlsv12)) 252 | .build()); 253 | 254 | let listener = p!(TcpListener::bind("0.0.0.0:0")); 255 | let port = p!(listener.local_addr()).port(); 256 | 257 | let j = thread::spawn(move || { 258 | let socket = p!(listener.accept()).0; 259 | assert!(builder.accept(socket).is_err()); 260 | }); 261 | 262 | let root_ca = Certificate::from_der(keys.client.ca.get_der()).unwrap(); 263 | 264 | let socket = p!(TcpStream::connect(("localhost", port))); 265 | let builder = p!(TlsConnector::builder() 266 | .add_root_certificate(root_ca) 267 | .min_protocol_version(Some(Protocol::Tlsv11)) 268 | .max_protocol_version(Some(Protocol::Tlsv11)) 269 | .build()); 270 | assert!(builder.connect("localhost", socket).is_err()); 271 | 272 | p!(j.join()); 273 | } 274 | 275 | #[test] 276 | fn server_untrusted() { 277 | let keys = test_cert_gen::keys(); 278 | 279 | let identity = p!(Identity::from_pkcs12( 280 | &keys.server.cert_and_key_pkcs12.pkcs12.0, 281 | &keys.server.cert_and_key_pkcs12.password 282 | )); 283 | let builder = p!(TlsAcceptor::new(identity)); 284 | 285 | let listener = p!(TcpListener::bind("0.0.0.0:0")); 286 | let port = p!(listener.local_addr()).port(); 287 | 288 | let j = thread::spawn(move || { 289 | let socket = p!(listener.accept()).0; 290 | // FIXME should assert error 291 | // https://github.com/steffengy/schannel-rs/issues/20 292 | let _ = builder.accept(socket); 293 | }); 294 | 295 | let socket = p!(TcpStream::connect(("localhost", port))); 296 | let builder = p!(TlsConnector::new()); 297 | builder.connect("localhost", socket).unwrap_err(); 298 | 299 | p!(j.join()); 300 | } 301 | 302 | #[test] 303 | fn server_untrusted_unverified() { 304 | let keys = test_cert_gen::keys(); 305 | 306 | let identity = p!(Identity::from_pkcs12( 307 | &keys.server.cert_and_key_pkcs12.pkcs12.0, 308 | &keys.server.cert_and_key_pkcs12.password 309 | )); 310 | let builder = p!(TlsAcceptor::new(identity)); 311 | 312 | let listener = p!(TcpListener::bind("0.0.0.0:0")); 313 | let port = p!(listener.local_addr()).port(); 314 | 315 | let j = thread::spawn(move || { 316 | let socket = p!(listener.accept()).0; 317 | let mut socket = p!(builder.accept(socket)); 318 | 319 | let mut buf = [0; 5]; 320 | p!(socket.read_exact(&mut buf)); 321 | assert_eq!(&buf, b"hello"); 322 | 323 | p!(socket.write_all(b"world")); 324 | }); 325 | 326 | let socket = p!(TcpStream::connect(("localhost", port))); 327 | let builder = p!(TlsConnector::builder() 328 | .danger_accept_invalid_certs(true) 329 | .build()); 330 | let mut socket = p!(builder.connect("localhost", socket)); 331 | 332 | p!(socket.write_all(b"hello")); 333 | let mut buf = vec![]; 334 | p!(socket.read_to_end(&mut buf)); 335 | assert_eq!(buf, b"world"); 336 | 337 | p!(j.join()); 338 | } 339 | 340 | #[test] 341 | fn import_same_identity_multiple_times() { 342 | let keys = test_cert_gen::keys(); 343 | 344 | let _ = p!(Identity::from_pkcs12( 345 | &keys.server.cert_and_key_pkcs12.pkcs12.0, 346 | &keys.server.cert_and_key_pkcs12.password 347 | )); 348 | let _ = p!(Identity::from_pkcs12( 349 | &keys.server.cert_and_key_pkcs12.pkcs12.0, 350 | &keys.server.cert_and_key_pkcs12.password 351 | )); 352 | 353 | let cert = keys.server.cert_and_key.cert.to_pem().into_bytes(); 354 | let key = rsa_to_pkcs8(&keys.server.cert_and_key.key.to_pem_incorrect()).into_bytes(); 355 | let _ = p!(Identity::from_pkcs8(&cert, &key)); 356 | let _ = p!(Identity::from_pkcs8(&cert, &key)); 357 | } 358 | 359 | #[test] 360 | fn from_pkcs8_rejects_rsa_key() { 361 | let keys = test_cert_gen::keys(); 362 | let cert = keys.server.cert_and_key.cert.to_pem().into_bytes(); 363 | let rsa_key = keys.server.cert_and_key.key.to_pem_incorrect(); 364 | assert!(Identity::from_pkcs8(&cert, rsa_key.as_bytes()).is_err()); 365 | let pkcs8_key = rsa_to_pkcs8(&rsa_key); 366 | assert!(Identity::from_pkcs8(&cert, pkcs8_key.as_bytes()).is_ok()); 367 | } 368 | 369 | #[test] 370 | fn shutdown() { 371 | let keys = test_cert_gen::keys(); 372 | 373 | let identity = p!(Identity::from_pkcs12( 374 | &keys.server.cert_and_key_pkcs12.pkcs12.0, 375 | &keys.server.cert_and_key_pkcs12.password 376 | )); 377 | let builder = p!(TlsAcceptor::new(identity)); 378 | 379 | let listener = p!(TcpListener::bind("0.0.0.0:0")); 380 | let port = p!(listener.local_addr()).port(); 381 | 382 | let j = thread::spawn(move || { 383 | let socket = p!(listener.accept()).0; 384 | let mut socket = p!(builder.accept(socket)); 385 | 386 | let mut buf = [0; 5]; 387 | p!(socket.read_exact(&mut buf)); 388 | assert_eq!(&buf, b"hello"); 389 | 390 | assert_eq!(p!(socket.read(&mut buf)), 0); 391 | p!(socket.shutdown()); 392 | }); 393 | 394 | let root_ca = Certificate::from_der(keys.client.ca.get_der()).unwrap(); 395 | 396 | let socket = p!(TcpStream::connect(("localhost", port))); 397 | let builder = p!(TlsConnector::builder() 398 | .add_root_certificate(root_ca) 399 | .build()); 400 | let mut socket = p!(builder.connect("localhost", socket)); 401 | 402 | p!(socket.write_all(b"hello")); 403 | p!(socket.shutdown()); 404 | 405 | p!(j.join()); 406 | } 407 | 408 | #[test] 409 | #[cfg(feature = "alpn")] 410 | fn alpn_google_h2() { 411 | let builder = p!(TlsConnector::builder().request_alpns(&["h2"]).build()); 412 | let s = p!(TcpStream::connect("google.com:443")); 413 | let socket = p!(builder.connect("google.com", s)); 414 | let alpn = p!(socket.negotiated_alpn()); 415 | assert_eq!(alpn, Some(b"h2".to_vec())); 416 | } 417 | 418 | #[test] 419 | #[cfg(feature = "alpn")] 420 | fn alpn_google_invalid() { 421 | let builder = p!(TlsConnector::builder().request_alpns(&["h2c"]).build()); 422 | let s = p!(TcpStream::connect("google.com:443")); 423 | let socket = p!(builder.connect("google.com", s)); 424 | let alpn = p!(socket.negotiated_alpn()); 425 | assert_eq!(alpn, None); 426 | } 427 | 428 | #[test] 429 | #[cfg(feature = "alpn")] 430 | fn alpn_google_none() { 431 | let builder = p!(TlsConnector::new()); 432 | let s = p!(TcpStream::connect("google.com:443")); 433 | let socket = p!(builder.connect("google.com", s)); 434 | let alpn = p!(socket.negotiated_alpn()); 435 | assert_eq!(alpn, None); 436 | } 437 | 438 | #[test] 439 | fn server_pkcs8() { 440 | let keys = test_cert_gen::keys(); 441 | let cert = keys.server.cert_and_key.cert.to_pem().into_bytes(); 442 | let key = rsa_to_pkcs8(&keys.server.cert_and_key.key.to_pem_incorrect()).into_bytes(); 443 | 444 | let ident = Identity::from_pkcs8(&cert, &key).unwrap(); 445 | let ident2 = ident.clone(); 446 | let builder = p!(TlsAcceptor::new(ident)); 447 | 448 | let listener = p!(TcpListener::bind("0.0.0.0:0")); 449 | let port = p!(listener.local_addr()).port(); 450 | 451 | let j = thread::spawn(move || { 452 | let socket = p!(listener.accept()).0; 453 | let mut socket = p!(builder.accept(socket)); 454 | 455 | let mut buf = [0; 5]; 456 | p!(socket.read_exact(&mut buf)); 457 | assert_eq!(&buf, b"hello"); 458 | 459 | p!(socket.write_all(b"world")); 460 | }); 461 | 462 | let root_ca = Certificate::from_der(keys.client.ca.get_der()).unwrap(); 463 | 464 | let socket = p!(TcpStream::connect(("localhost", port))); 465 | let mut builder = TlsConnector::builder(); 466 | // FIXME 467 | // This checks that we can successfully add a certificate on the client side. 468 | // Unfortunately, we can not request client certificates through the API of this library, 469 | // otherwise we could check in the server thread that 470 | // socket.peer_certificate().unwrap().is_some() 471 | builder.identity(ident2); 472 | 473 | builder.add_root_certificate(root_ca); 474 | let builder = p!(builder.build()); 475 | let mut socket = p!(builder.connect("localhost", socket)); 476 | 477 | p!(socket.write_all(b"hello")); 478 | let mut buf = vec![]; 479 | p!(socket.read_to_end(&mut buf)); 480 | assert_eq!(buf, b"world"); 481 | 482 | p!(j.join()); 483 | } 484 | 485 | #[test] 486 | fn two_servers() { 487 | let keys1 = test_cert_gen::gen_keys(); 488 | let cert = keys1.server.cert_and_key.cert.to_pem().into_bytes(); 489 | let key = rsa_to_pkcs8(&keys1.server.cert_and_key.key.to_pem_incorrect()).into_bytes(); 490 | let identity = p!(Identity::from_pkcs8(&cert, &key)); 491 | let builder = TlsAcceptor::builder(identity); 492 | let builder = p!(builder.build()); 493 | 494 | let listener = p!(TcpListener::bind("0.0.0.0:0")); 495 | let port = p!(listener.local_addr()).port(); 496 | 497 | let j = thread::spawn(move || { 498 | let socket = p!(listener.accept()).0; 499 | let mut socket = p!(builder.accept(socket)); 500 | 501 | let mut buf = [0; 5]; 502 | p!(socket.read_exact(&mut buf)); 503 | assert_eq!(&buf, b"hello"); 504 | 505 | p!(socket.write_all(b"world")); 506 | }); 507 | 508 | let keys2 = test_cert_gen::gen_keys(); 509 | let cert = keys2.server.cert_and_key.cert.to_pem().into_bytes(); 510 | let key = rsa_to_pkcs8(&keys2.server.cert_and_key.key.to_pem_incorrect()).into_bytes(); 511 | let identity = p!(Identity::from_pkcs8(&cert, &key)); 512 | let builder = TlsAcceptor::builder(identity); 513 | let builder = p!(builder.build()); 514 | 515 | let listener = p!(TcpListener::bind("0.0.0.0:0")); 516 | let port2 = p!(listener.local_addr()).port(); 517 | 518 | let j2 = thread::spawn(move || { 519 | let socket = p!(listener.accept()).0; 520 | let mut socket = p!(builder.accept(socket)); 521 | 522 | let mut buf = [0; 5]; 523 | p!(socket.read_exact(&mut buf)); 524 | assert_eq!(&buf, b"hello"); 525 | 526 | p!(socket.write_all(b"world")); 527 | }); 528 | 529 | let root_ca = Certificate::from_der(keys1.client.ca.get_der()).unwrap(); 530 | 531 | let socket = p!(TcpStream::connect(("localhost", port))); 532 | let mut builder = TlsConnector::builder(); 533 | builder.add_root_certificate(root_ca); 534 | let builder = p!(builder.build()); 535 | let mut socket = p!(builder.connect("localhost", socket)); 536 | 537 | p!(socket.write_all(b"hello")); 538 | let mut buf = vec![]; 539 | p!(socket.read_to_end(&mut buf)); 540 | assert_eq!(buf, b"world"); 541 | 542 | let root_ca = Certificate::from_der(keys2.client.ca.get_der()).unwrap(); 543 | 544 | let socket = p!(TcpStream::connect(("localhost", port2))); 545 | let mut builder = TlsConnector::builder(); 546 | builder.add_root_certificate(root_ca); 547 | let builder = p!(builder.build()); 548 | let mut socket = p!(builder.connect("localhost", socket)); 549 | 550 | p!(socket.write_all(b"hello")); 551 | let mut buf = vec![]; 552 | p!(socket.read_to_end(&mut buf)); 553 | assert_eq!(buf, b"world"); 554 | 555 | p!(j.join()); 556 | p!(j2.join()); 557 | } 558 | 559 | fn rsa_to_pkcs8(pem: &str) -> String { 560 | let mut child = Command::new("openssl") 561 | .arg("pkcs8") 562 | .arg("-topk8") 563 | .arg("-nocrypt") 564 | .stdin(Stdio::piped()) 565 | .stdout(Stdio::piped()) 566 | .spawn() 567 | .unwrap(); 568 | { 569 | let child_stdin = child.stdin.as_mut().unwrap(); 570 | child_stdin.write_all(pem.as_bytes()).unwrap(); 571 | } 572 | String::from_utf8(child.wait_with_output().unwrap().stdout).unwrap() 573 | } 574 | --------------------------------------------------------------------------------