native-tls-0.2.11/.cargo_vcs_info.json0000644000000001360000000000100132030ustar { "git": { "sha1": "774a19cecb694dc6e414c2ada9bd5f768a0b9b75" }, "path_in_vcs": "" }native-tls-0.2.11/.github/workflows/ci.yml000064400000000000000000000027621046102023000165150ustar 00000000000000name: CI on: pull_request: branches: - master push: branches: - master env: RUSTFLAGS: -Dwarnings RUST_BACKTRACE: 1 jobs: rustfmt: name: rustfmt runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: sfackler/actions/rustup@master - uses: sfackler/actions/rustfmt@master windows: strategy: fail-fast: false matrix: os: - ubuntu-latest - windows-latest - macos-latest name: test-${{ matrix.os }} runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v2 - uses: sfackler/actions/rustup@master with: version: 1.53.0 - run: echo "::set-output name=version::$(rustc --version)" id: rust-version - uses: actions/cache@v1 with: path: ~/.cargo/registry/index key: index-${{ runner.os }}-${{ github.run_number }} restore-keys: | index-${{ runner.os }}- - run: cargo generate-lockfile - uses: actions/cache@v1 with: path: ~/.cargo/registry/cache key: registry-${{ runner.os }}-${{ steps.rust-version.outputs.version }}-${{ hashFiles('Cargo.lock') }} - run: cargo fetch - uses: actions/cache@v1 with: path: target key: target-${{ runner.os }}-${{ steps.rust-version.outputs.version }}-${{ hashFiles('Cargo.lock') }} - run: cargo test --features vendored - run: cargo test --features vendored native-tls-0.2.11/.gitignore000064400000000000000000000000461046102023000137630ustar 00000000000000target Cargo.lock .idea *.iml .vscode native-tls-0.2.11/CHANGELOG.md000064400000000000000000000104601046102023000136050ustar 00000000000000# Change Log ## [Unreleased] ## [v0.2.11] ### Fixed * Removed an unused build dependency. ## [v0.2.10] ### Fixed * Fixed the build for iOS. ## [v0.2.9] ### Added * Added `Identity::from_pkcs8`. ## [v0.2.8] ### Fixed * Fixed an off by one error in the schannel backend's handling of max_protocol_version. ## [v0.2.7] ### Added * Added support for ALPN in client APIs flagged under the `alpn` Cargo feature. ## [v0.2.6] ### Fixed * Fixed compilation on iOS. ## [v0.2.5] ### Added * Added `TlsConnectorBuilder::disable_built_in_roots` to only trust root certificates explicitly added to the builder. ### Updated * Updated security-framework to 2.0. ## [v0.2.4] ### Added * Added a `Clone` implementation for `Identity`. ### Updated * Updated security-framework to 0.4. ## [v0.2.3] ### Fixed * Adding an already-trusted certificate to the root certificate set no longer triggers an error with OpenSSL. ### Updated * Updated security-framework to 0.3. ## [v0.2.2] ### Fixed * Failure to load a root certificate on Android now logs a message rather than producing an error. * Fixed ordering of the certificate chain in the OpenSSL backend. ## [v0.2.1] ### Added * The `vendored` Cargo feature will cause the crate to compile and statically link to a vendored copy of OpenSSL on platforms that use that backend. ## [v0.2.0] ### Added * The `openssl_probe` crate is now used with the OpenSSL backend so that trusted root certificates will automatically be detected when statically linking to OpenSSL. * Root certificates are now automatically loaded from the Android trust root. * Added `Certificate::to_der` to serialize an X509 certificate to DER. * Added `TlsConnectorBuilder::danger_accept_invalid_certs` to disable certificate verification. * Added `TlsAcceptor::new` and `TlsConnector::new` to easily create an acceptor/connector with default settings. * Added `TlsStream::peer_certificate` to obtain the peer's leaf certificate. * Added `TlsStream::tls_server_end_point` to retrieve RFC 5929 tls-server-end-point channel binding data. ### Changed * Upgraded to `openssl` 0.10 and `security-framework` 0.2. * `Pkcs12` has been renamed to `Identity`, and `Pkcs12::from_der` has been renamed to `Identity::from_pkcs12`. * `HandshakeError::Interrupted` has been renamed to `HandshakeError::WouldBlock`. * `TlsConnectorBuilder` and `TlsAcceptorBuilder` are now "traditional"-style builders. Their methods are now infallible and return `&mut Self` to allow them to be chained together. * `supported_protocols` has been replaced by `min_protocol_version` and `max_protocol_version` on `TlsConnectorBuilder` and `TlsAcceptorBuilder`. * SNI and hostname verification are now configured separately via `TlsConnectorBuilder::use_sni` and `TlsConnectorBuilder::danger_accept_invalid_hostnames`. They replace the `TlsConnector::danger_connect_without_providing_domain_for_certificate_verification_and_server_name_indication` method, which has been removed. ### Removed * The backend-specific extension traits have been removed. We want to avoid exposing the specific version of the backend library in the public API to provide more flexibility. ## Older Look at the [release tags] for information about older releases. [Unreleased]: https://github.com/sfackler/rust-native-tls/compare/v0.2.11...master [v0.2.11]: https://github.com/sfackler/rust-native-tls/compare/v0.2.10...v0.2.11 [v0.2.10]: https://github.com/sfackler/rust-native-tls/compare/v0.2.9...v0.2.10 [v0.2.9]: https://github.com/sfackler/rust-native-tls/compare/v0.2.8...v0.2.9 [v0.2.8]: https://github.com/sfackler/rust-native-tls/compare/v0.2.7...v0.2.8 [v0.2.7]: https://github.com/sfackler/rust-native-tls/compare/v0.2.6...v0.2.7 [v0.2.6]: https://github.com/sfackler/rust-native-tls/compare/v0.2.5...v0.2.6 [v0.2.5]: https://github.com/sfackler/rust-native-tls/compare/v0.2.4...v0.2.5 [v0.2.4]: https://github.com/sfackler/rust-native-tls/compare/v0.2.3...v0.2.4 [v0.2.3]: https://github.com/sfackler/rust-native-tls/compare/v0.2.2...v0.2.3 [v0.2.2]: https://github.com/sfackler/rust-native-tls/compare/v0.2.1...v0.2.2 [v0.2.1]: https://github.com/sfackler/rust-native-tls/compare/v0.2.0...v0.2.1 [v0.2.0]: https://github.com/sfackler/rust-native-tls/compare/v0.1.5...v0.2.0 [release tags]: https://github.com/sfackler/rust-native-tls/releases native-tls-0.2.11/Cargo.lock0000644000000271440000000000100111660ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "base64" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "cc" version = "1.0.74" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "581f5dba903aac52ea3feb5ec4810848460ee833876f1f9b0fdeab1f19091574" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "core-foundation" version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" dependencies = [ "core-foundation-sys", "libc", ] [[package]] name = "core-foundation-sys" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" [[package]] name = "fastrand" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" dependencies = [ "instant", ] [[package]] name = "foreign-types" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" dependencies = [ "foreign-types-shared", ] [[package]] name = "foreign-types-shared" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "fuchsia-cprng" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" [[package]] name = "instant" version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ "cfg-if", ] [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" version = "0.2.137" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" [[package]] name = "log" version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ "cfg-if", ] [[package]] name = "native-tls" version = "0.2.11" dependencies = [ "lazy_static", "libc", "log", "openssl", "openssl-probe", "openssl-sys", "schannel", "security-framework", "security-framework-sys", "tempfile", "test-cert-gen", ] [[package]] name = "once_cell" version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" [[package]] name = "openssl" version = "0.10.42" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12fc0523e3bd51a692c8850d075d74dc062ccf251c0110668cbd921917118a13" dependencies = [ "bitflags", "cfg-if", "foreign-types", "libc", "once_cell", "openssl-macros", "openssl-sys", ] [[package]] name = "openssl-macros" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "openssl-probe" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-src" version = "111.24.0+1.1.1s" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3498f259dab01178c6228c6b00dcef0ed2a2d5e20d648c017861227773ea4abd" dependencies = [ "cc", ] [[package]] name = "openssl-sys" version = "0.9.77" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b03b84c3b2d099b81f0953422b4d4ad58761589d0229b5506356afca05a3670a" dependencies = [ "autocfg", "cc", "libc", "openssl-src", "pkg-config", "vcpkg", ] [[package]] name = "pem" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd56cbd21fea48d0c440b41cd69c589faacade08c992d9a54e471b79d0fd13eb" dependencies = [ "base64", "once_cell", "regex", ] [[package]] name = "pkg-config" version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" [[package]] name = "proc-macro2" version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" dependencies = [ "proc-macro2", ] [[package]] name = "rand" version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" dependencies = [ "fuchsia-cprng", "libc", "rand_core 0.3.1", "rdrand", "winapi", ] [[package]] name = "rand_core" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" dependencies = [ "rand_core 0.4.2", ] [[package]] name = "rand_core" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" [[package]] name = "rdrand" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" dependencies = [ "rand_core 0.3.1", ] [[package]] name = "redox_syscall" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ "bitflags", ] [[package]] name = "regex" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" dependencies = [ "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.6.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" [[package]] name = "remove_dir_all" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" dependencies = [ "winapi", ] [[package]] name = "schannel" version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" dependencies = [ "lazy_static", "windows-sys", ] [[package]] name = "security-framework" version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bc1bb97804af6631813c55739f771071e0f2ed33ee20b68c86ec505d906356c" dependencies = [ "bitflags", "core-foundation", "core-foundation-sys", "libc", "security-framework-sys", ] [[package]] name = "security-framework-sys" version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" dependencies = [ "core-foundation-sys", "libc", ] [[package]] name = "syn" version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "tempdir" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" dependencies = [ "rand", "remove_dir_all", ] [[package]] name = "tempfile" version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" dependencies = [ "cfg-if", "fastrand", "libc", "redox_syscall", "remove_dir_all", "winapi", ] [[package]] name = "test-cert-gen" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3208d0ae2e3736d4ac2f6ba2229c4d9bbd54080e228e662a7684eabcf13ff419" dependencies = [ "pem", "tempdir", ] [[package]] name = "unicode-ident" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" [[package]] name = "vcpkg" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-sys" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" dependencies = [ "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_msvc" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" [[package]] name = "windows_i686_gnu" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" [[package]] name = "windows_i686_msvc" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" [[package]] name = "windows_x86_64_gnu" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" [[package]] name = "windows_x86_64_msvc" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" native-tls-0.2.11/Cargo.toml0000644000000042060000000000100112030ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] name = "native-tls" version = "0.2.11" authors = ["Steven Fackler "] description = "A wrapper over a platform's native TLS implementation" readme = "README.md" license = "MIT/Apache-2.0" repository = "https://github.com/sfackler/rust-native-tls" [package.metadata.docs.rs] features = ["alpn"] rustdoc-args = [ "--cfg", "docsrs", ] [dev-dependencies.tempfile] version = "3.0" [dev-dependencies.test-cert-gen] version = "0.7" [features] alpn = ["security-framework/alpn"] vendored = ["openssl/vendored"] [target."cfg(any(target_os = \"macos\", target_os = \"ios\"))".dependencies.lazy_static] version = "1.4.0" [target."cfg(any(target_os = \"macos\", target_os = \"ios\"))".dependencies.libc] version = "0.2" [target."cfg(any(target_os = \"macos\", target_os = \"ios\"))".dependencies.security-framework] version = "2.0.0" [target."cfg(any(target_os = \"macos\", target_os = \"ios\"))".dependencies.security-framework-sys] version = "2.0.0" [target."cfg(any(target_os = \"macos\", target_os = \"ios\"))".dependencies.tempfile] version = "3.1.0" [target."cfg(not(any(target_os = \"windows\", target_os = \"macos\", target_os = \"ios\")))".dependencies.log] version = "0.4.5" [target."cfg(not(any(target_os = \"windows\", target_os = \"macos\", target_os = \"ios\")))".dependencies.openssl] version = "0.10.29" [target."cfg(not(any(target_os = \"windows\", target_os = \"macos\", target_os = \"ios\")))".dependencies.openssl-probe] version = "0.1" [target."cfg(not(any(target_os = \"windows\", target_os = \"macos\", target_os = \"ios\")))".dependencies.openssl-sys] version = "0.9.55" [target."cfg(target_os = \"windows\")".dependencies.schannel] version = "0.1.17" native-tls-0.2.11/Cargo.toml.orig000064400000000000000000000016541046102023000146700ustar 00000000000000[package] name = "native-tls" version = "0.2.11" authors = ["Steven Fackler "] license = "MIT/Apache-2.0" description = "A wrapper over a platform's native TLS implementation" repository = "https://github.com/sfackler/rust-native-tls" readme = "README.md" [package.metadata.docs.rs] features = ["alpn"] rustdoc-args = ["--cfg", "docsrs"] [features] vendored = ["openssl/vendored"] alpn = ["security-framework/alpn"] [target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies] security-framework = "2.0.0" security-framework-sys = "2.0.0" lazy_static = "1.4.0" libc = "0.2" tempfile = "3.1.0" [target.'cfg(target_os = "windows")'.dependencies] schannel = "0.1.17" [target.'cfg(not(any(target_os = "windows", target_os = "macos", target_os = "ios")))'.dependencies] log = "0.4.5" openssl = "0.10.29" openssl-sys = "0.9.55" openssl-probe = "0.1" [dev-dependencies] tempfile = "3.0" test-cert-gen = "0.7" native-tls-0.2.11/LICENSE-APACHE000064400000000000000000000261361046102023000137270ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright {yyyy} {name of copyright owner} Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. native-tls-0.2.11/LICENSE-MIT000064400000000000000000000020621046102023000134270ustar 00000000000000Copyright (c) 2016 The rust-native-tls Developers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. native-tls-0.2.11/README.md000064400000000000000000000046551046102023000132640ustar 00000000000000# rust-native-tls [Documentation](https://docs.rs/native-tls) An abstraction over platform-specific TLS implementations. Specifically, this crate uses SChannel on Windows (via the [`schannel`] crate), Secure Transport on macOS (via the [`security-framework`] crate), and OpenSSL (via the [`openssl`] crate) on all other platforms. [`schannel`]: https://crates.io/crates/schannel [`security-framework`]: https://crates.io/crates/security-framework [`openssl`]: https://crates.io/crates/openssl ## Installation ```toml # Cargo.toml [dependencies] native-tls = "0.2" ``` ## Usage An example client looks like: ```rust,ignore extern crate native_tls; use native_tls::TlsConnector; use std::io::{Read, Write}; use std::net::TcpStream; fn main() { let connector = TlsConnector::new().unwrap(); let stream = TcpStream::connect("google.com:443").unwrap(); let mut stream = connector.connect("google.com", stream).unwrap(); stream.write_all(b"GET / HTTP/1.0\r\n\r\n").unwrap(); let mut res = vec![]; stream.read_to_end(&mut res).unwrap(); println!("{}", String::from_utf8_lossy(&res)); } ``` To accept connections as a server from remote clients: ```rust,ignore extern crate native_tls; use native_tls::{Identity, TlsAcceptor, TlsStream}; use std::fs::File; use std::io::{Read}; use std::net::{TcpListener, TcpStream}; use std::sync::Arc; use std::thread; fn main() { let mut file = File::open("identity.pfx").unwrap(); let mut identity = vec![]; file.read_to_end(&mut identity).unwrap(); let identity = Identity::from_pkcs12(&identity, "hunter2").unwrap(); let acceptor = TlsAcceptor::new(identity).unwrap(); let acceptor = Arc::new(acceptor); let listener = TcpListener::bind("0.0.0.0:8443").unwrap(); fn handle_client(stream: TlsStream) { // ... } for stream in listener.incoming() { match stream { Ok(stream) => { let acceptor = acceptor.clone(); thread::spawn(move || { let stream = acceptor.accept(stream).unwrap(); handle_client(stream); }); } Err(e) => { /* connection failed */ } } } } ``` # License `rust-native-tls` is primarily distributed under the terms of both the MIT license and the Apache License (Version 2.0), with portions covered by various BSD-like licenses. See LICENSE-APACHE, and LICENSE-MIT for details. native-tls-0.2.11/build.rs000064400000000000000000000010331046102023000134350ustar 00000000000000use std::env; fn main() { if let Ok(version) = env::var("DEP_OPENSSL_VERSION_NUMBER") { let version = u64::from_str_radix(&version, 16).unwrap(); if version >= 0x1_01_00_00_0 { println!("cargo:rustc-cfg=have_min_max_version"); } } if let Ok(version) = env::var("DEP_OPENSSL_LIBRESSL_VERSION_NUMBER") { let version = u64::from_str_radix(&version, 16).unwrap(); if version >= 0x2_06_01_00_0 { println!("cargo:rustc-cfg=have_min_max_version"); } } } native-tls-0.2.11/examples/google-connect.rs000064400000000000000000000007511046102023000170650ustar 00000000000000extern crate native_tls; use native_tls::TlsConnector; use std::io::{Read, Write}; use std::net::TcpStream; fn main() { let connector = TlsConnector::new().unwrap(); let stream = TcpStream::connect("google.com:443").unwrap(); let mut stream = connector.connect("google.com", stream).unwrap(); stream.write_all(b"GET / HTTP/1.0\r\n\r\n").unwrap(); let mut res = vec![]; stream.read_to_end(&mut res).unwrap(); println!("{}", String::from_utf8_lossy(&res)); } native-tls-0.2.11/examples/simple-server-pkcs8.rs000064400000000000000000000026141046102023000200050ustar 00000000000000extern crate native_tls; use native_tls::{Identity, TlsAcceptor, TlsStream}; use std::fs::File; use std::io::{Read, Write}; use std::net::{TcpListener, TcpStream}; use std::sync::Arc; use std::thread; fn main() { let mut cert_file = File::open("test/cert.pem").unwrap(); let mut certs = vec![]; cert_file.read_to_end(&mut certs).unwrap(); let mut key_file = File::open("test/key.pem").unwrap(); let mut key = vec![]; key_file.read_to_end(&mut key).unwrap(); let pkcs8 = Identity::from_pkcs8(&certs, &key).unwrap(); let acceptor = TlsAcceptor::new(pkcs8).unwrap(); let acceptor = Arc::new(acceptor); let listener = TcpListener::bind("0.0.0.0:8443").unwrap(); fn handle_client(mut stream: TlsStream) { let mut buf = [0; 1024]; let read = stream.read(&mut buf).unwrap(); let received = std::str::from_utf8(&buf[0..read]).unwrap(); stream .write_all(format!("received '{}'", received).as_bytes()) .unwrap(); } for stream in listener.incoming() { match stream { Ok(stream) => { let acceptor = acceptor.clone(); thread::spawn(move || { let stream = acceptor.accept(stream).unwrap(); handle_client(stream); }); } Err(_e) => { /* connection failed */ } } } } native-tls-0.2.11/examples/simple-server.rs000064400000000000000000000020101046102023000167450ustar 00000000000000extern crate native_tls; use native_tls::{Identity, TlsAcceptor, TlsStream}; use std::fs::File; use std::io::Read; use std::net::{TcpListener, TcpStream}; use std::sync::Arc; use std::thread; fn main() { let mut file = File::open("identity.pfx").unwrap(); let mut pkcs12 = vec![]; file.read_to_end(&mut pkcs12).unwrap(); let pkcs12 = Identity::from_pkcs12(&pkcs12, "hunter2").unwrap(); let acceptor = TlsAcceptor::new(pkcs12).unwrap(); let acceptor = Arc::new(acceptor); let listener = TcpListener::bind("0.0.0.0:8443").unwrap(); fn handle_client(_stream: TlsStream) { // ... } for stream in listener.incoming() { match stream { Ok(stream) => { let acceptor = acceptor.clone(); thread::spawn(move || { let stream = acceptor.accept(stream).unwrap(); handle_client(stream); }); } Err(_e) => { /* connection failed */ } } } } native-tls-0.2.11/src/imp/openssl.rs000064400000000000000000000350251046102023000154050ustar 00000000000000extern crate openssl; extern crate openssl_probe; use self::openssl::error::ErrorStack; use self::openssl::hash::MessageDigest; use self::openssl::nid::Nid; use self::openssl::pkcs12::Pkcs12; use self::openssl::pkey::{PKey, Private}; use self::openssl::ssl::{ self, MidHandshakeSslStream, SslAcceptor, SslConnector, SslContextBuilder, SslMethod, SslVerifyMode, }; use self::openssl::x509::{store::X509StoreBuilder, X509VerifyResult, X509}; use std::error; use std::fmt; use std::io; use std::sync::Once; use {Protocol, TlsAcceptorBuilder, TlsConnectorBuilder}; #[cfg(have_min_max_version)] fn supported_protocols( min: Option, max: Option, ctx: &mut SslContextBuilder, ) -> Result<(), ErrorStack> { use self::openssl::ssl::SslVersion; fn cvt(p: Protocol) -> SslVersion { match p { Protocol::Sslv3 => SslVersion::SSL3, Protocol::Tlsv10 => SslVersion::TLS1, Protocol::Tlsv11 => SslVersion::TLS1_1, Protocol::Tlsv12 => SslVersion::TLS1_2, Protocol::__NonExhaustive => unreachable!(), } } ctx.set_min_proto_version(min.map(cvt))?; ctx.set_max_proto_version(max.map(cvt))?; Ok(()) } #[cfg(not(have_min_max_version))] fn supported_protocols( min: Option, max: Option, ctx: &mut SslContextBuilder, ) -> Result<(), ErrorStack> { use self::openssl::ssl::SslOptions; let no_ssl_mask = SslOptions::NO_SSLV2 | SslOptions::NO_SSLV3 | SslOptions::NO_TLSV1 | SslOptions::NO_TLSV1_1 | SslOptions::NO_TLSV1_2; ctx.clear_options(no_ssl_mask); let mut options = SslOptions::empty(); options |= match min { None => SslOptions::empty(), Some(Protocol::Sslv3) => SslOptions::NO_SSLV2, Some(Protocol::Tlsv10) => SslOptions::NO_SSLV2 | SslOptions::NO_SSLV3, Some(Protocol::Tlsv11) => { SslOptions::NO_SSLV2 | SslOptions::NO_SSLV3 | SslOptions::NO_TLSV1 } Some(Protocol::Tlsv12) => { SslOptions::NO_SSLV2 | SslOptions::NO_SSLV3 | SslOptions::NO_TLSV1 | SslOptions::NO_TLSV1_1 } Some(Protocol::__NonExhaustive) => unreachable!(), }; options |= match max { None | Some(Protocol::Tlsv12) => SslOptions::empty(), Some(Protocol::Tlsv11) => SslOptions::NO_TLSV1_2, Some(Protocol::Tlsv10) => SslOptions::NO_TLSV1_1 | SslOptions::NO_TLSV1_2, Some(Protocol::Sslv3) => { SslOptions::NO_TLSV1 | SslOptions::NO_TLSV1_1 | SslOptions::NO_TLSV1_2 } Some(Protocol::__NonExhaustive) => unreachable!(), }; ctx.set_options(options); Ok(()) } fn init_trust() { static ONCE: Once = Once::new(); ONCE.call_once(openssl_probe::init_ssl_cert_env_vars); } #[cfg(target_os = "android")] fn load_android_root_certs(connector: &mut SslContextBuilder) -> Result<(), Error> { use std::fs; if let Ok(dir) = fs::read_dir("/system/etc/security/cacerts") { let certs = dir .filter_map(|r| r.ok()) .filter_map(|e| fs::read(e.path()).ok()) .filter_map(|b| X509::from_pem(&b).ok()); for cert in certs { if let Err(err) = connector.cert_store_mut().add_cert(cert) { debug!("load_android_root_certs error: {:?}", err); } } } Ok(()) } #[derive(Debug)] pub enum Error { Normal(ErrorStack), Ssl(ssl::Error, X509VerifyResult), EmptyChain, NotPkcs8, } impl error::Error for Error { fn source(&self) -> Option<&(dyn error::Error + 'static)> { match *self { Error::Normal(ref e) => error::Error::source(e), Error::Ssl(ref e, _) => error::Error::source(e), Error::EmptyChain => None, Error::NotPkcs8 => None, } } } impl fmt::Display for Error { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { match *self { Error::Normal(ref e) => fmt::Display::fmt(e, fmt), Error::Ssl(ref e, X509VerifyResult::OK) => fmt::Display::fmt(e, fmt), Error::Ssl(ref e, v) => write!(fmt, "{} ({})", e, v), Error::EmptyChain => write!( fmt, "at least one certificate must be provided to create an identity" ), Error::NotPkcs8 => write!(fmt, "expected PKCS#8 PEM"), } } } impl From for Error { fn from(err: ErrorStack) -> Error { Error::Normal(err) } } #[derive(Clone)] pub struct Identity { pkey: PKey, cert: X509, chain: Vec, } impl Identity { pub fn from_pkcs12(buf: &[u8], pass: &str) -> Result { let pkcs12 = Pkcs12::from_der(buf)?; let parsed = pkcs12.parse(pass)?; Ok(Identity { pkey: parsed.pkey, cert: parsed.cert, // > The stack is the reverse of what you might expect due to the way // > PKCS12_parse is implemented, so we need to load it backwards. // > https://github.com/sfackler/rust-native-tls/commit/05fb5e583be589ab63d9f83d986d095639f8ec44 chain: parsed.chain.into_iter().flatten().rev().collect(), }) } pub fn from_pkcs8(buf: &[u8], key: &[u8]) -> Result { if !key.starts_with(b"-----BEGIN PRIVATE KEY-----") { return Err(Error::NotPkcs8); } let pkey = PKey::private_key_from_pem(key)?; let mut cert_chain = X509::stack_from_pem(buf)?.into_iter(); let cert = cert_chain.next().ok_or(Error::EmptyChain)?; let chain = cert_chain.collect(); Ok(Identity { pkey, cert, chain }) } } #[derive(Clone)] pub struct Certificate(X509); impl Certificate { pub fn from_der(buf: &[u8]) -> Result { let cert = X509::from_der(buf)?; Ok(Certificate(cert)) } pub fn from_pem(buf: &[u8]) -> Result { let cert = X509::from_pem(buf)?; Ok(Certificate(cert)) } pub fn to_der(&self) -> Result, Error> { let der = self.0.to_der()?; Ok(der) } } pub struct MidHandshakeTlsStream(MidHandshakeSslStream); impl fmt::Debug for MidHandshakeTlsStream where S: fmt::Debug, { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt::Debug::fmt(&self.0, fmt) } } impl MidHandshakeTlsStream { pub fn get_ref(&self) -> &S { self.0.get_ref() } pub fn get_mut(&mut self) -> &mut S { self.0.get_mut() } } impl MidHandshakeTlsStream where S: io::Read + io::Write, { pub fn handshake(self) -> Result, HandshakeError> { match self.0.handshake() { Ok(s) => Ok(TlsStream(s)), Err(e) => Err(e.into()), } } } pub enum HandshakeError { Failure(Error), WouldBlock(MidHandshakeTlsStream), } impl From> for HandshakeError { fn from(e: ssl::HandshakeError) -> HandshakeError { match e { ssl::HandshakeError::SetupFailure(e) => HandshakeError::Failure(e.into()), ssl::HandshakeError::Failure(e) => { let v = e.ssl().verify_result(); HandshakeError::Failure(Error::Ssl(e.into_error(), v)) } ssl::HandshakeError::WouldBlock(s) => { HandshakeError::WouldBlock(MidHandshakeTlsStream(s)) } } } } impl From for HandshakeError { fn from(e: ErrorStack) -> HandshakeError { HandshakeError::Failure(e.into()) } } #[derive(Clone)] pub struct TlsConnector { connector: SslConnector, use_sni: bool, accept_invalid_hostnames: bool, accept_invalid_certs: bool, } impl TlsConnector { pub fn new(builder: &TlsConnectorBuilder) -> Result { init_trust(); let mut connector = SslConnector::builder(SslMethod::tls())?; if let Some(ref identity) = builder.identity { connector.set_certificate(&identity.0.cert)?; connector.set_private_key(&identity.0.pkey)?; for cert in identity.0.chain.iter() { // https://www.openssl.org/docs/manmaster/man3/SSL_CTX_add_extra_chain_cert.html // specifies that "When sending a certificate chain, extra chain certificates are // sent in order following the end entity certificate." connector.add_extra_chain_cert(cert.to_owned())?; } } supported_protocols(builder.min_protocol, builder.max_protocol, &mut connector)?; if builder.disable_built_in_roots { connector.set_cert_store(X509StoreBuilder::new()?.build()); } for cert in &builder.root_certificates { if let Err(err) = connector.cert_store_mut().add_cert((cert.0).0.clone()) { debug!("add_cert error: {:?}", err); } } #[cfg(feature = "alpn")] { if !builder.alpn.is_empty() { // Wire format is each alpn preceded by its length as a byte. let mut alpn_wire_format = Vec::with_capacity( builder .alpn .iter() .map(|s| s.as_bytes().len()) .sum::() + builder.alpn.len(), ); for alpn in builder.alpn.iter().map(|s| s.as_bytes()) { alpn_wire_format.push(alpn.len() as u8); alpn_wire_format.extend(alpn); } connector.set_alpn_protos(&alpn_wire_format)?; } } #[cfg(target_os = "android")] load_android_root_certs(&mut connector)?; Ok(TlsConnector { connector: connector.build(), use_sni: builder.use_sni, accept_invalid_hostnames: builder.accept_invalid_hostnames, accept_invalid_certs: builder.accept_invalid_certs, }) } pub fn connect(&self, domain: &str, stream: S) -> Result, HandshakeError> where S: io::Read + io::Write, { let mut ssl = self .connector .configure()? .use_server_name_indication(self.use_sni) .verify_hostname(!self.accept_invalid_hostnames); if self.accept_invalid_certs { ssl.set_verify(SslVerifyMode::NONE); } let s = ssl.connect(domain, stream)?; Ok(TlsStream(s)) } } impl fmt::Debug for TlsConnector { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt.debug_struct("TlsConnector") // n.b. SslConnector is a newtype on SslContext which implements a noop Debug so it's omitted .field("use_sni", &self.use_sni) .field("accept_invalid_hostnames", &self.accept_invalid_hostnames) .field("accept_invalid_certs", &self.accept_invalid_certs) .finish() } } #[derive(Clone)] pub struct TlsAcceptor(SslAcceptor); impl TlsAcceptor { pub fn new(builder: &TlsAcceptorBuilder) -> Result { let mut acceptor = SslAcceptor::mozilla_intermediate(SslMethod::tls())?; acceptor.set_private_key(&builder.identity.0.pkey)?; acceptor.set_certificate(&builder.identity.0.cert)?; for cert in builder.identity.0.chain.iter() { // https://www.openssl.org/docs/manmaster/man3/SSL_CTX_add_extra_chain_cert.html // specifies that "When sending a certificate chain, extra chain certificates are // sent in order following the end entity certificate." acceptor.add_extra_chain_cert(cert.to_owned())?; } supported_protocols(builder.min_protocol, builder.max_protocol, &mut acceptor)?; Ok(TlsAcceptor(acceptor.build())) } pub fn accept(&self, stream: S) -> Result, HandshakeError> where S: io::Read + io::Write, { let s = self.0.accept(stream)?; Ok(TlsStream(s)) } } pub struct TlsStream(ssl::SslStream); impl fmt::Debug for TlsStream { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt::Debug::fmt(&self.0, fmt) } } impl TlsStream { pub fn get_ref(&self) -> &S { self.0.get_ref() } pub fn get_mut(&mut self) -> &mut S { self.0.get_mut() } } impl TlsStream { pub fn buffered_read_size(&self) -> Result { Ok(self.0.ssl().pending()) } pub fn peer_certificate(&self) -> Result, Error> { Ok(self.0.ssl().peer_certificate().map(Certificate)) } #[cfg(feature = "alpn")] pub fn negotiated_alpn(&self) -> Result>, Error> { Ok(self .0 .ssl() .selected_alpn_protocol() .map(|alpn| alpn.to_vec())) } pub fn tls_server_end_point(&self) -> Result>, Error> { let cert = if self.0.ssl().is_server() { self.0.ssl().certificate().map(|x| x.to_owned()) } else { self.0.ssl().peer_certificate() }; let cert = match cert { Some(cert) => cert, None => return Ok(None), }; let algo_nid = cert.signature_algorithm().object().nid(); let signature_algorithms = match algo_nid.signature_algorithms() { Some(algs) => algs, None => return Ok(None), }; let md = match signature_algorithms.digest { Nid::MD5 | Nid::SHA1 => MessageDigest::sha256(), nid => match MessageDigest::from_nid(nid) { Some(md) => md, None => return Ok(None), }, }; let digest = cert.digest(md)?; Ok(Some(digest.to_vec())) } pub fn shutdown(&mut self) -> io::Result<()> { match self.0.shutdown() { Ok(_) => Ok(()), Err(ref e) if e.code() == ssl::ErrorCode::ZERO_RETURN => Ok(()), Err(e) => Err(e .into_io_error() .unwrap_or_else(|e| io::Error::new(io::ErrorKind::Other, e))), } } } impl io::Read for TlsStream { fn read(&mut self, buf: &mut [u8]) -> io::Result { self.0.read(buf) } } impl io::Write for TlsStream { fn write(&mut self, buf: &[u8]) -> io::Result { self.0.write(buf) } fn flush(&mut self) -> io::Result<()> { self.0.flush() } } native-tls-0.2.11/src/imp/schannel.rs000064400000000000000000000423471046102023000155220ustar 00000000000000extern crate schannel; use self::schannel::cert_context::{CertContext, HashAlgorithm, KeySpec}; use self::schannel::cert_store::{CertAdd, CertStore, Memory, PfxImportOptions}; use self::schannel::crypt_prov::{AcquireOptions, ProviderType}; use self::schannel::schannel_cred::{Direction, Protocol, SchannelCred}; use self::schannel::tls_stream; use std::error; use std::fmt; use std::io; use std::str; use {TlsAcceptorBuilder, TlsConnectorBuilder}; const SEC_E_NO_CREDENTIALS: u32 = 0x8009030E; static PROTOCOLS: &'static [Protocol] = &[ Protocol::Ssl3, Protocol::Tls10, Protocol::Tls11, Protocol::Tls12, ]; fn convert_protocols(min: Option<::Protocol>, max: Option<::Protocol>) -> &'static [Protocol] { let mut protocols = PROTOCOLS; if let Some(p) = max.and_then(|max| protocols.get(..=max as usize)) { protocols = p; } if let Some(p) = min.and_then(|min| protocols.get(min as usize..)) { protocols = p; } protocols } pub struct Error(io::Error); impl error::Error for Error { fn source(&self) -> Option<&(dyn error::Error + 'static)> { error::Error::source(&self.0) } } impl fmt::Display for Error { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, fmt) } } impl fmt::Debug for Error { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt::Debug::fmt(&self.0, fmt) } } impl From for Error { fn from(error: io::Error) -> Error { Error(error) } } #[derive(Clone)] pub struct Identity { cert: CertContext, } impl Identity { pub fn from_pkcs12(buf: &[u8], pass: &str) -> Result { let store = PfxImportOptions::new().password(pass).import(buf)?; let mut identity = None; for cert in store.certs() { if cert .private_key() .silent(true) .compare_key(true) .acquire() .is_ok() { identity = Some(cert); break; } } let identity = match identity { Some(identity) => identity, None => { return Err(io::Error::new( io::ErrorKind::InvalidInput, "No identity found in PKCS #12 archive", ) .into()); } }; Ok(Identity { cert: identity }) } pub fn from_pkcs8(pem: &[u8], key: &[u8]) -> Result { if !key.starts_with(b"-----BEGIN PRIVATE KEY-----") { return Err(io::Error::new(io::ErrorKind::InvalidInput, "not a PKCS#8 key").into()); } let mut store = Memory::new()?.into_store(); let mut cert_iter = pem::PemBlock::new(pem).into_iter(); let leaf = cert_iter.next().ok_or_else(|| { io::Error::new( io::ErrorKind::InvalidInput, "at least one certificate must be provided to create an identity", ) })?; let cert = CertContext::from_pem(std::str::from_utf8(leaf).map_err(|_| { io::Error::new( io::ErrorKind::InvalidInput, "leaf cert contains invalid utf8", ) })?)?; let name = gen_container_name(); let mut options = AcquireOptions::new(); options.container(&name); let type_ = ProviderType::rsa_full(); let mut container = match options.acquire(type_) { Ok(container) => container, Err(_) => options.new_keyset(true).acquire(type_)?, }; container.import().import_pkcs8_pem(&key)?; cert.set_key_prov_info() .container(&name) .type_(type_) .keep_open(true) .key_spec(KeySpec::key_exchange()) .set()?; let mut context = store.add_cert(&cert, CertAdd::Always)?; for int_cert in cert_iter { let certificate = Certificate::from_pem(int_cert)?; context = store.add_cert(&certificate.0, CertAdd::Always)?; } Ok(Identity { cert: context }) } } // The name of the container must be unique to have multiple active keys. fn gen_container_name() -> String { use std::sync::atomic::{AtomicUsize, Ordering}; static COUNTER: AtomicUsize = AtomicUsize::new(0); format!("native-tls-{}", COUNTER.fetch_add(1, Ordering::Relaxed)) } #[derive(Clone)] pub struct Certificate(CertContext); impl Certificate { pub fn from_der(buf: &[u8]) -> Result { let cert = CertContext::new(buf)?; Ok(Certificate(cert)) } pub fn from_pem(buf: &[u8]) -> Result { match str::from_utf8(buf) { Ok(s) => { let cert = CertContext::from_pem(s)?; Ok(Certificate(cert)) } Err(_) => Err(io::Error::new( io::ErrorKind::InvalidInput, "PEM representation contains non-UTF-8 bytes", ) .into()), } } pub fn to_der(&self) -> Result, Error> { Ok(self.0.to_der().to_vec()) } } pub struct MidHandshakeTlsStream(tls_stream::MidHandshakeTlsStream); impl fmt::Debug for MidHandshakeTlsStream where S: fmt::Debug, { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt::Debug::fmt(&self.0, fmt) } } impl MidHandshakeTlsStream { pub fn get_ref(&self) -> &S { self.0.get_ref() } pub fn get_mut(&mut self) -> &mut S { self.0.get_mut() } } impl MidHandshakeTlsStream where S: io::Read + io::Write, { pub fn handshake(self) -> Result, HandshakeError> { match self.0.handshake() { Ok(s) => Ok(TlsStream(s)), Err(e) => Err(e.into()), } } } pub enum HandshakeError { Failure(Error), WouldBlock(MidHandshakeTlsStream), } impl From> for HandshakeError { fn from(e: tls_stream::HandshakeError) -> HandshakeError { match e { tls_stream::HandshakeError::Failure(e) => HandshakeError::Failure(e.into()), tls_stream::HandshakeError::Interrupted(s) => { HandshakeError::WouldBlock(MidHandshakeTlsStream(s)) } } } } impl From for HandshakeError { fn from(e: io::Error) -> HandshakeError { HandshakeError::Failure(e.into()) } } #[derive(Clone, Debug)] pub struct TlsConnector { cert: Option, roots: CertStore, min_protocol: Option<::Protocol>, max_protocol: Option<::Protocol>, use_sni: bool, accept_invalid_hostnames: bool, accept_invalid_certs: bool, disable_built_in_roots: bool, #[cfg(feature = "alpn")] alpn: Vec, } impl TlsConnector { pub fn new(builder: &TlsConnectorBuilder) -> Result { let cert = builder.identity.as_ref().map(|i| i.0.cert.clone()); let mut roots = Memory::new()?.into_store(); for cert in &builder.root_certificates { roots.add_cert(&(cert.0).0, CertAdd::ReplaceExisting)?; } Ok(TlsConnector { cert, roots, min_protocol: builder.min_protocol, max_protocol: builder.max_protocol, use_sni: builder.use_sni, accept_invalid_hostnames: builder.accept_invalid_hostnames, accept_invalid_certs: builder.accept_invalid_certs, disable_built_in_roots: builder.disable_built_in_roots, #[cfg(feature = "alpn")] alpn: builder.alpn.clone(), }) } pub fn connect(&self, domain: &str, stream: S) -> Result, HandshakeError> where S: io::Read + io::Write, { let mut builder = SchannelCred::builder(); builder.enabled_protocols(convert_protocols(self.min_protocol, self.max_protocol)); if let Some(cert) = self.cert.as_ref() { builder.cert(cert.clone()); } let cred = builder.acquire(Direction::Outbound)?; let mut builder = tls_stream::Builder::new(); builder .cert_store(self.roots.clone()) .domain(domain) .use_sni(self.use_sni) .accept_invalid_hostnames(self.accept_invalid_hostnames); if self.accept_invalid_certs { builder.verify_callback(|_| Ok(())); } else if self.disable_built_in_roots { let roots_copy = self.roots.clone(); builder.verify_callback(move |res| { if let Err(err) = res.result() { // Propagate previous error encountered during normal cert validation. return Err(err); } if let Some(chain) = res.chain() { if chain .certificates() .any(|cert| roots_copy.certs().any(|root_cert| root_cert == cert)) { return Ok(()); } } Err(io::Error::new( io::ErrorKind::Other, "unable to find any user-specified roots in the final cert chain", )) }); } #[cfg(feature = "alpn")] { if !self.alpn.is_empty() { builder.request_application_protocols( &self.alpn.iter().map(|s| s.as_bytes()).collect::>(), ); } } match builder.connect(cred, stream) { Ok(s) => Ok(TlsStream(s)), Err(e) => Err(e.into()), } } } #[derive(Clone)] pub struct TlsAcceptor { cert: CertContext, min_protocol: Option<::Protocol>, max_protocol: Option<::Protocol>, } impl TlsAcceptor { pub fn new(builder: &TlsAcceptorBuilder) -> Result { Ok(TlsAcceptor { cert: builder.identity.0.cert.clone(), min_protocol: builder.min_protocol, max_protocol: builder.max_protocol, }) } pub fn accept(&self, stream: S) -> Result, HandshakeError> where S: io::Read + io::Write, { let mut builder = SchannelCred::builder(); builder.enabled_protocols(convert_protocols(self.min_protocol, self.max_protocol)); builder.cert(self.cert.clone()); // FIXME we're probably missing the certificate chain? let cred = builder.acquire(Direction::Inbound)?; match tls_stream::Builder::new().accept(cred, stream) { Ok(s) => Ok(TlsStream(s)), Err(e) => Err(e.into()), } } } pub struct TlsStream(tls_stream::TlsStream); impl fmt::Debug for TlsStream { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt::Debug::fmt(&self.0, fmt) } } impl TlsStream { pub fn get_ref(&self) -> &S { self.0.get_ref() } pub fn get_mut(&mut self) -> &mut S { self.0.get_mut() } } impl TlsStream { pub fn buffered_read_size(&self) -> Result { Ok(self.0.get_buf().len()) } pub fn peer_certificate(&self) -> Result, Error> { match self.0.peer_certificate() { Ok(cert) => Ok(Some(Certificate(cert))), Err(ref e) if e.raw_os_error() == Some(SEC_E_NO_CREDENTIALS as i32) => Ok(None), Err(e) => Err(Error(e)), } } #[cfg(feature = "alpn")] pub fn negotiated_alpn(&self) -> Result>, Error> { Ok(self.0.negotiated_application_protocol()?) } pub fn tls_server_end_point(&self) -> Result>, Error> { let cert = if self.0.is_server() { self.0.certificate() } else { self.0.peer_certificate() }; let cert = match cert { Ok(cert) => cert, Err(ref e) if e.raw_os_error() == Some(SEC_E_NO_CREDENTIALS as i32) => return Ok(None), Err(e) => return Err(Error(e)), }; let signature_algorithms = cert.sign_hash_algorithms()?; let hash = match signature_algorithms.rsplit('/').next().unwrap() { "MD5" | "SHA1" | "SHA256" => HashAlgorithm::sha256(), "SHA384" => HashAlgorithm::sha384(), "SHA512" => HashAlgorithm::sha512(), _ => return Ok(None), }; let digest = cert.fingerprint(hash)?; Ok(Some(digest)) } pub fn shutdown(&mut self) -> io::Result<()> { self.0.shutdown()?; Ok(()) } } impl io::Read for TlsStream { fn read(&mut self, buf: &mut [u8]) -> io::Result { self.0.read(buf) } } impl io::Write for TlsStream { fn write(&mut self, buf: &[u8]) -> io::Result { self.0.write(buf) } fn flush(&mut self) -> io::Result<()> { self.0.flush() } } mod pem { /// Split data by PEM guard lines pub struct PemBlock<'a> { pem_block: &'a str, cur_end: usize, } impl<'a> PemBlock<'a> { pub fn new(data: &'a [u8]) -> PemBlock<'a> { let s = ::std::str::from_utf8(data).unwrap(); PemBlock { pem_block: s, cur_end: s.find("-----BEGIN").unwrap_or(s.len()), } } } impl<'a> Iterator for PemBlock<'a> { type Item = &'a [u8]; fn next(&mut self) -> Option { let last = self.pem_block.len(); if self.cur_end >= last { return None; } let begin = self.cur_end; let pos = self.pem_block[begin + 1..].find("-----BEGIN"); self.cur_end = match pos { Some(end) => end + begin + 1, None => last, }; return Some(&self.pem_block[begin..self.cur_end].as_bytes()); } } #[test] fn test_split() { // Split three certs, CRLF line terminators. assert_eq!( PemBlock::new( b"-----BEGIN FIRST-----\r\n-----END FIRST-----\r\n\ -----BEGIN SECOND-----\r\n-----END SECOND\r\n\ -----BEGIN THIRD-----\r\n-----END THIRD\r\n" ) .collect::>(), vec![ b"-----BEGIN FIRST-----\r\n-----END FIRST-----\r\n" as &[u8], b"-----BEGIN SECOND-----\r\n-----END SECOND\r\n", b"-----BEGIN THIRD-----\r\n-----END THIRD\r\n" ] ); // Split three certs, CRLF line terminators except at EOF. assert_eq!( PemBlock::new( b"-----BEGIN FIRST-----\r\n-----END FIRST-----\r\n\ -----BEGIN SECOND-----\r\n-----END SECOND-----\r\n\ -----BEGIN THIRD-----\r\n-----END THIRD-----" ) .collect::>(), vec![ b"-----BEGIN FIRST-----\r\n-----END FIRST-----\r\n" as &[u8], b"-----BEGIN SECOND-----\r\n-----END SECOND-----\r\n", b"-----BEGIN THIRD-----\r\n-----END THIRD-----" ] ); // Split two certs, LF line terminators. assert_eq!( PemBlock::new( b"-----BEGIN FIRST-----\n-----END FIRST-----\n\ -----BEGIN SECOND-----\n-----END SECOND\n" ) .collect::>(), vec![ b"-----BEGIN FIRST-----\n-----END FIRST-----\n" as &[u8], b"-----BEGIN SECOND-----\n-----END SECOND\n" ] ); // Split two certs, CR line terminators. assert_eq!( PemBlock::new( b"-----BEGIN FIRST-----\r-----END FIRST-----\r\ -----BEGIN SECOND-----\r-----END SECOND\r" ) .collect::>(), vec![ b"-----BEGIN FIRST-----\r-----END FIRST-----\r" as &[u8], b"-----BEGIN SECOND-----\r-----END SECOND\r" ] ); // Split two certs, LF line terminators except at EOF. assert_eq!( PemBlock::new( b"-----BEGIN FIRST-----\n-----END FIRST-----\n\ -----BEGIN SECOND-----\n-----END SECOND" ) .collect::>(), vec![ b"-----BEGIN FIRST-----\n-----END FIRST-----\n" as &[u8], b"-----BEGIN SECOND-----\n-----END SECOND" ] ); // Split a single cert, LF line terminators. assert_eq!( PemBlock::new(b"-----BEGIN FIRST-----\n-----END FIRST-----\n").collect::>(), vec![b"-----BEGIN FIRST-----\n-----END FIRST-----\n" as &[u8]] ); // Split a single cert, LF line terminators except at EOF. assert_eq!( PemBlock::new(b"-----BEGIN FIRST-----\n-----END FIRST-----").collect::>(), vec![b"-----BEGIN FIRST-----\n-----END FIRST-----" as &[u8]] ); // (Don't) split garbage. assert_eq!( PemBlock::new(b"junk").collect::>(), Vec::<&[u8]>::new() ); assert_eq!( PemBlock::new(b"junk-----BEGIN garbage").collect::>(), vec![b"-----BEGIN garbage" as &[u8]] ); } } native-tls-0.2.11/src/imp/security_framework.rs000064400000000000000000000500171046102023000176440ustar 00000000000000extern crate libc; extern crate security_framework; extern crate security_framework_sys; extern crate tempfile; use self::security_framework::base; use self::security_framework::certificate::SecCertificate; use self::security_framework::identity::SecIdentity; use self::security_framework::import_export::{ImportedIdentity, Pkcs12ImportOptions}; use self::security_framework::random::SecRandom; use self::security_framework::secure_transport::{ self, ClientBuilder, SslConnectionType, SslContext, SslProtocol, SslProtocolSide, }; use self::security_framework_sys::base::{errSecIO, errSecParam}; use self::tempfile::TempDir; use std::error; use std::fmt; use std::io; use std::str; use std::sync::Mutex; use std::sync::Once; #[cfg(not(target_os = "ios"))] use self::security_framework::os::macos::certificate::{PropertyType, SecCertificateExt}; #[cfg(not(target_os = "ios"))] use self::security_framework::os::macos::certificate_oids::CertificateOid; #[cfg(not(target_os = "ios"))] use self::security_framework::os::macos::identity::SecIdentityExt; #[cfg(not(target_os = "ios"))] use self::security_framework::os::macos::import_export::{ ImportOptions, Pkcs12ImportOptionsExt, SecItems, }; #[cfg(not(target_os = "ios"))] use self::security_framework::os::macos::keychain::{self, KeychainSettings, SecKeychain}; use {Protocol, TlsAcceptorBuilder, TlsConnectorBuilder}; static SET_AT_EXIT: Once = Once::new(); #[cfg(not(target_os = "ios"))] lazy_static! { static ref TEMP_KEYCHAIN: Mutex> = Mutex::new(None); } fn convert_protocol(protocol: Protocol) -> SslProtocol { match protocol { Protocol::Sslv3 => SslProtocol::SSL3, Protocol::Tlsv10 => SslProtocol::TLS1, Protocol::Tlsv11 => SslProtocol::TLS11, Protocol::Tlsv12 => SslProtocol::TLS12, Protocol::__NonExhaustive => unreachable!(), } } pub struct Error(base::Error); impl error::Error for Error { fn source(&self) -> Option<&(dyn error::Error + 'static)> { error::Error::source(&self.0) } } impl fmt::Display for Error { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, fmt) } } impl fmt::Debug for Error { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt::Debug::fmt(&self.0, fmt) } } impl From for Error { fn from(error: base::Error) -> Error { Error(error) } } #[derive(Clone, Debug)] pub struct Identity { identity: SecIdentity, chain: Vec, } impl Identity { #[cfg(target_os = "ios")] pub fn from_pkcs8(_: &[u8], _: &[u8]) -> Result { panic!("Not implemented on iOS"); } #[cfg(not(target_os = "ios"))] pub fn from_pkcs8(pem: &[u8], key: &[u8]) -> Result { if !key.starts_with(b"-----BEGIN PRIVATE KEY-----") { return Err(Error(base::Error::from(errSecParam))); } let dir = TempDir::new().map_err(|_| Error(base::Error::from(errSecIO)))?; let keychain = keychain::CreateOptions::new() .password(&random_password()?) .create(dir.path().join("identity.keychain"))?; let mut items = SecItems::default(); ImportOptions::new() .filename("key.pem") .items(&mut items) .keychain(&keychain) .import(&key)?; ImportOptions::new() .filename("chain.pem") .items(&mut items) .keychain(&keychain) .import(&pem)?; let cert = items .certificates .get(0) .ok_or_else(|| Error(base::Error::from(errSecParam)))?; let ident = SecIdentity::with_certificate(&[keychain], cert)?; Ok(Identity { identity: ident, chain: items.certificates, }) } pub fn from_pkcs12(buf: &[u8], pass: &str) -> Result { let mut imports = Identity::import_options(buf, pass)?; let import = imports.pop().unwrap(); let identity = import .identity .expect("Pkcs12 files must include an identity"); // FIXME: Compare the certificates for equality using CFEqual let identity_cert = identity.certificate()?.to_der(); Ok(Identity { identity, chain: import .cert_chain .unwrap_or(vec![]) .into_iter() .filter(|c| c.to_der() != identity_cert) .collect(), }) } #[cfg(not(target_os = "ios"))] fn import_options(buf: &[u8], pass: &str) -> Result, Error> { SET_AT_EXIT.call_once(|| { extern "C" fn atexit() { *TEMP_KEYCHAIN.lock().unwrap() = None; } unsafe { libc::atexit(atexit); } }); let keychain = match *TEMP_KEYCHAIN.lock().unwrap() { Some((ref keychain, _)) => keychain.clone(), ref mut lock @ None => { let dir = TempDir::new().map_err(|_| Error(base::Error::from(errSecIO)))?; let mut keychain = keychain::CreateOptions::new() .password(pass) .create(dir.path().join("tmp.keychain"))?; keychain.set_settings(&KeychainSettings::new())?; *lock = Some((keychain.clone(), dir)); keychain } }; let mut import_opts = Pkcs12ImportOptions::new(); // Method shadowed by deprecated method. ::keychain(&mut import_opts, keychain); let imports = import_opts.passphrase(pass).import(buf)?; Ok(imports) } #[cfg(target_os = "ios")] fn import_options(buf: &[u8], pass: &str) -> Result, Error> { let imports = Pkcs12ImportOptions::new().passphrase(pass).import(buf)?; Ok(imports) } } fn random_password() -> Result { use std::fmt::Write; let mut bytes = [0_u8; 10]; SecRandom::default() .copy_bytes(&mut bytes) .map_err(|_| Error(base::Error::from(errSecIO)))?; let mut s = String::with_capacity(2 * bytes.len()); for byte in bytes { write!(s, "{:02X}", byte).map_err(|_| Error(base::Error::from(errSecIO)))?; } Ok(s) } #[derive(Clone)] pub struct Certificate(SecCertificate); impl Certificate { pub fn from_der(buf: &[u8]) -> Result { let cert = SecCertificate::from_der(buf)?; Ok(Certificate(cert)) } #[cfg(not(target_os = "ios"))] pub fn from_pem(buf: &[u8]) -> Result { let mut items = SecItems::default(); ImportOptions::new().items(&mut items).import(buf)?; if items.certificates.len() == 1 && items.identities.is_empty() && items.keys.is_empty() { Ok(Certificate(items.certificates.pop().unwrap())) } else { Err(Error(base::Error::from(errSecParam))) } } #[cfg(target_os = "ios")] pub fn from_pem(_: &[u8]) -> Result { panic!("Not implemented on iOS"); } pub fn to_der(&self) -> Result, Error> { Ok(self.0.to_der()) } } pub enum HandshakeError { WouldBlock(MidHandshakeTlsStream), Failure(Error), } impl From> for HandshakeError { fn from(e: secure_transport::ClientHandshakeError) -> HandshakeError { match e { secure_transport::ClientHandshakeError::Failure(e) => HandshakeError::Failure(e.into()), secure_transport::ClientHandshakeError::Interrupted(s) => { HandshakeError::WouldBlock(MidHandshakeTlsStream::Client(s)) } } } } impl From for HandshakeError { fn from(e: base::Error) -> HandshakeError { HandshakeError::Failure(e.into()) } } pub enum MidHandshakeTlsStream { Server( secure_transport::MidHandshakeSslStream, Option, ), Client(secure_transport::MidHandshakeClientBuilder), } impl fmt::Debug for MidHandshakeTlsStream where S: fmt::Debug, { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { match *self { MidHandshakeTlsStream::Server(ref s, _) => s.fmt(fmt), MidHandshakeTlsStream::Client(ref s) => s.fmt(fmt), } } } impl MidHandshakeTlsStream { pub fn get_ref(&self) -> &S { match *self { MidHandshakeTlsStream::Server(ref s, _) => s.get_ref(), MidHandshakeTlsStream::Client(ref s) => s.get_ref(), } } pub fn get_mut(&mut self) -> &mut S { match *self { MidHandshakeTlsStream::Server(ref mut s, _) => s.get_mut(), MidHandshakeTlsStream::Client(ref mut s) => s.get_mut(), } } } impl MidHandshakeTlsStream where S: io::Read + io::Write, { pub fn handshake(self) -> Result, HandshakeError> { match self { MidHandshakeTlsStream::Server(s, cert) => match s.handshake() { Ok(stream) => Ok(TlsStream { stream, cert }), Err(secure_transport::HandshakeError::Failure(e)) => { Err(HandshakeError::Failure(Error(e))) } Err(secure_transport::HandshakeError::Interrupted(s)) => Err( HandshakeError::WouldBlock(MidHandshakeTlsStream::Server(s, cert)), ), }, MidHandshakeTlsStream::Client(s) => match s.handshake() { Ok(stream) => Ok(TlsStream { stream, cert: None }), Err(e) => Err(e.into()), }, } } } #[derive(Clone, Debug)] pub struct TlsConnector { identity: Option, min_protocol: Option, max_protocol: Option, roots: Vec, use_sni: bool, danger_accept_invalid_hostnames: bool, danger_accept_invalid_certs: bool, disable_built_in_roots: bool, #[cfg(feature = "alpn")] alpn: Vec, } impl TlsConnector { pub fn new(builder: &TlsConnectorBuilder) -> Result { Ok(TlsConnector { identity: builder.identity.as_ref().map(|i| i.0.clone()), min_protocol: builder.min_protocol, max_protocol: builder.max_protocol, roots: builder .root_certificates .iter() .map(|c| (c.0).0.clone()) .collect(), use_sni: builder.use_sni, danger_accept_invalid_hostnames: builder.accept_invalid_hostnames, danger_accept_invalid_certs: builder.accept_invalid_certs, disable_built_in_roots: builder.disable_built_in_roots, #[cfg(feature = "alpn")] alpn: builder.alpn.clone(), }) } pub fn connect(&self, domain: &str, stream: S) -> Result, HandshakeError> where S: io::Read + io::Write, { let mut builder = ClientBuilder::new(); if let Some(min) = self.min_protocol { builder.protocol_min(convert_protocol(min)); } if let Some(max) = self.max_protocol { builder.protocol_max(convert_protocol(max)); } if let Some(identity) = self.identity.as_ref() { builder.identity(&identity.identity, &identity.chain); } builder.anchor_certificates(&self.roots); builder.use_sni(self.use_sni); builder.danger_accept_invalid_hostnames(self.danger_accept_invalid_hostnames); builder.danger_accept_invalid_certs(self.danger_accept_invalid_certs); builder.trust_anchor_certificates_only(self.disable_built_in_roots); #[cfg(feature = "alpn")] { if !self.alpn.is_empty() { builder.alpn_protocols(&self.alpn.iter().map(String::as_str).collect::>()); } } match builder.handshake(domain, stream) { Ok(stream) => Ok(TlsStream { stream, cert: None }), Err(e) => Err(e.into()), } } } #[derive(Clone)] pub struct TlsAcceptor { identity: Identity, min_protocol: Option, max_protocol: Option, } impl TlsAcceptor { pub fn new(builder: &TlsAcceptorBuilder) -> Result { Ok(TlsAcceptor { identity: builder.identity.0.clone(), min_protocol: builder.min_protocol, max_protocol: builder.max_protocol, }) } pub fn accept(&self, stream: S) -> Result, HandshakeError> where S: io::Read + io::Write, { let mut ctx = SslContext::new(SslProtocolSide::SERVER, SslConnectionType::STREAM)?; if let Some(min) = self.min_protocol { ctx.set_protocol_version_min(convert_protocol(min))?; } if let Some(max) = self.max_protocol { ctx.set_protocol_version_max(convert_protocol(max))?; } ctx.set_certificate(&self.identity.identity, &self.identity.chain)?; let cert = Some(self.identity.identity.certificate()?); match ctx.handshake(stream) { Ok(stream) => Ok(TlsStream { stream, cert }), Err(secure_transport::HandshakeError::Failure(e)) => { Err(HandshakeError::Failure(Error(e))) } Err(secure_transport::HandshakeError::Interrupted(s)) => Err( HandshakeError::WouldBlock(MidHandshakeTlsStream::Server(s, cert)), ), } } } pub struct TlsStream { stream: secure_transport::SslStream, cert: Option, } impl fmt::Debug for TlsStream { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt::Debug::fmt(&self.stream, fmt) } } impl TlsStream { pub fn get_ref(&self) -> &S { self.stream.get_ref() } pub fn get_mut(&mut self) -> &mut S { self.stream.get_mut() } } impl TlsStream { pub fn buffered_read_size(&self) -> Result { Ok(self.stream.context().buffered_read_size()?) } #[allow(deprecated)] pub fn peer_certificate(&self) -> Result, Error> { let trust = match self.stream.context().peer_trust2()? { Some(trust) => trust, None => return Ok(None), }; trust.evaluate()?; Ok(trust.certificate_at_index(0).map(Certificate)) } #[cfg(feature = "alpn")] pub fn negotiated_alpn(&self) -> Result>, Error> { match self.stream.context().alpn_protocols() { Ok(protocols) => { // Per RFC7301, "ProtocolNameList" MUST contain exactly one "ProtocolName". assert!(protocols.len() < 2); if protocols.is_empty() { // Not sure this is actually possible. Ok(None) } else { Ok(Some(protocols.into_iter().next().unwrap().into_bytes())) } } // The macOS API appears to return `errSecParam` whenever no ALPN was negotiated, both // when it isn't attempted and when it isn't successful. Err(e) if e.code() == errSecParam => Ok(None), Err(other) => Err(Error::from(other)), } } #[cfg(target_os = "ios")] pub fn tls_server_end_point(&self) -> Result>, Error> { Ok(None) } #[cfg(not(target_os = "ios"))] pub fn tls_server_end_point(&self) -> Result>, Error> { let cert = match self.cert { Some(ref cert) => cert.clone(), None => match self.peer_certificate()? { Some(cert) => cert.0, None => return Ok(None), }, }; let property = match cert .properties(Some(&[CertificateOid::x509_v1_signature_algorithm()])) .ok() .and_then(|p| p.get(CertificateOid::x509_v1_signature_algorithm())) { Some(property) => property, None => return Ok(None), }; let section = match property.get() { PropertyType::Section(section) => section, _ => return Ok(None), }; let algorithm = match section .iter() .filter(|p| p.label().to_string() == "Algorithm") .next() { Some(property) => property, None => return Ok(None), }; let algorithm = match algorithm.get() { PropertyType::String(algorithm) => algorithm, _ => return Ok(None), }; let digest = match &*algorithm.to_string() { // MD5 "1.2.840.113549.2.5" | "1.2.840.113549.1.1.4" | "1.3.14.3.2.3" => Digest::Sha256, // SHA-1 "1.3.14.3.2.26" | "1.3.14.3.2.15" | "1.2.840.113549.1.1.5" | "1.3.14.3.2.29" | "1.2.840.10040.4.3" | "1.3.14.3.2.13" | "1.2.840.10045.4.1" => Digest::Sha256, // SHA-224 "2.16.840.1.101.3.4.2.4" | "1.2.840.113549.1.1.14" | "2.16.840.1.101.3.4.3.1" | "1.2.840.10045.4.3.1" => Digest::Sha224, // SHA-256 "2.16.840.1.101.3.4.2.1" | "1.2.840.113549.1.1.11" | "1.2.840.10045.4.3.2" => { Digest::Sha256 } // SHA-384 "2.16.840.1.101.3.4.2.2" | "1.2.840.113549.1.1.12" | "1.2.840.10045.4.3.3" => { Digest::Sha384 } // SHA-512 "2.16.840.1.101.3.4.2.3" | "1.2.840.113549.1.1.13" | "1.2.840.10045.4.3.4" => { Digest::Sha512 } _ => return Ok(None), }; let der = cert.to_der(); Ok(Some(digest.hash(&der))) } pub fn shutdown(&mut self) -> io::Result<()> { self.stream.close()?; Ok(()) } } impl io::Read for TlsStream { fn read(&mut self, buf: &mut [u8]) -> io::Result { self.stream.read(buf) } } impl io::Write for TlsStream { fn write(&mut self, buf: &[u8]) -> io::Result { self.stream.write(buf) } fn flush(&mut self) -> io::Result<()> { self.stream.flush() } } enum Digest { Sha224, Sha256, Sha384, Sha512, } impl Digest { fn hash(&self, data: &[u8]) -> Vec { unsafe { assert!(data.len() <= CC_LONG::max_value() as usize); match *self { Digest::Sha224 => { let mut buf = [0; CC_SHA224_DIGEST_LENGTH]; CC_SHA224(data.as_ptr(), data.len() as CC_LONG, buf.as_mut_ptr()); buf.to_vec() } Digest::Sha256 => { let mut buf = [0; CC_SHA256_DIGEST_LENGTH]; CC_SHA256(data.as_ptr(), data.len() as CC_LONG, buf.as_mut_ptr()); buf.to_vec() } Digest::Sha384 => { let mut buf = [0; CC_SHA384_DIGEST_LENGTH]; CC_SHA384(data.as_ptr(), data.len() as CC_LONG, buf.as_mut_ptr()); buf.to_vec() } Digest::Sha512 => { let mut buf = [0; CC_SHA512_DIGEST_LENGTH]; CC_SHA512(data.as_ptr(), data.len() as CC_LONG, buf.as_mut_ptr()); buf.to_vec() } } } } } // FIXME ideally we'd pull these in from elsewhere const CC_SHA224_DIGEST_LENGTH: usize = 28; const CC_SHA256_DIGEST_LENGTH: usize = 32; const CC_SHA384_DIGEST_LENGTH: usize = 48; const CC_SHA512_DIGEST_LENGTH: usize = 64; #[allow(non_camel_case_types)] type CC_LONG = u32; extern "C" { fn CC_SHA224(data: *const u8, len: CC_LONG, md: *mut u8) -> *mut u8; fn CC_SHA256(data: *const u8, len: CC_LONG, md: *mut u8) -> *mut u8; fn CC_SHA384(data: *const u8, len: CC_LONG, md: *mut u8) -> *mut u8; fn CC_SHA512(data: *const u8, len: CC_LONG, md: *mut u8) -> *mut u8; } native-tls-0.2.11/src/lib.rs000064400000000000000000000546301046102023000137060ustar 00000000000000//! An abstraction over platform-specific TLS implementations. //! //! Many applications require TLS/SSL communication in one form or another as //! part of their implementation, but finding a library for this isn't always //! trivial! The purpose of this crate is to provide a seamless integration //! experience on all platforms with a cross-platform API that deals with all //! the underlying details for you. //! //! # How is this implemented? //! //! This crate uses SChannel on Windows (via the `schannel` crate), Secure //! Transport on OSX (via the `security-framework` crate), and OpenSSL (via the //! `openssl` crate) on all other platforms. Future features may also enable //! other TLS frameworks as well, but these initial libraries are likely to //! remain as the defaults. //! //! Note that this crate also strives to be secure-by-default. For example when //! using OpenSSL it will configure validation callbacks to ensure that //! hostnames match certificates, use strong ciphers, etc. This implies that //! this crate is *not* just a thin abstraction around the underlying libraries, //! but also an implementation that strives to strike reasonable defaults. //! //! # Supported features //! //! This crate supports the following features out of the box: //! //! * TLS/SSL client communication //! * TLS/SSL server communication //! * PKCS#12 encoded identities //! * X.509/PKCS#8 encoded identities //! * Secure-by-default for client and server //! * Includes hostname verification for clients //! * Supports asynchronous I/O for both the server and the client //! //! # Cargo Features //! //! * `vendored` - If enabled, the crate will compile and statically link to a //! vendored copy of OpenSSL. This feature has no effect on Windows and //! macOS, where OpenSSL is not used. //! //! # Examples //! //! To connect as a client to a remote server: //! //! ```rust //! use native_tls::TlsConnector; //! use std::io::{Read, Write}; //! use std::net::TcpStream; //! //! let connector = TlsConnector::new().unwrap(); //! //! let stream = TcpStream::connect("google.com:443").unwrap(); //! let mut stream = connector.connect("google.com", stream).unwrap(); //! //! stream.write_all(b"GET / HTTP/1.0\r\n\r\n").unwrap(); //! let mut res = vec![]; //! stream.read_to_end(&mut res).unwrap(); //! println!("{}", String::from_utf8_lossy(&res)); //! ``` //! //! To accept connections as a server from remote clients: //! //! ```rust,no_run //! use native_tls::{Identity, TlsAcceptor, TlsStream}; //! use std::fs::File; //! use std::io::{Read}; //! use std::net::{TcpListener, TcpStream}; //! use std::sync::Arc; //! use std::thread; //! //! let mut file = File::open("identity.pfx").unwrap(); //! let mut identity = vec![]; //! file.read_to_end(&mut identity).unwrap(); //! let identity = Identity::from_pkcs12(&identity, "hunter2").unwrap(); //! //! let listener = TcpListener::bind("0.0.0.0:8443").unwrap(); //! let acceptor = TlsAcceptor::new(identity).unwrap(); //! let acceptor = Arc::new(acceptor); //! //! fn handle_client(stream: TlsStream) { //! // ... //! } //! //! for stream in listener.incoming() { //! match stream { //! Ok(stream) => { //! let acceptor = acceptor.clone(); //! thread::spawn(move || { //! let stream = acceptor.accept(stream).unwrap(); //! handle_client(stream); //! }); //! } //! Err(e) => { /* connection failed */ } //! } //! } //! ``` #![doc(html_root_url = "https://docs.rs/native-tls/0.2")] #![warn(missing_docs)] #![cfg_attr(docsrs, feature(doc_cfg))] #[macro_use] #[cfg(any(target_os = "macos", target_os = "ios"))] extern crate lazy_static; use std::any::Any; use std::error; use std::fmt; use std::io; use std::result; #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] #[macro_use] extern crate log; #[cfg(any(target_os = "macos", target_os = "ios"))] #[path = "imp/security_framework.rs"] mod imp; #[cfg(target_os = "windows")] #[path = "imp/schannel.rs"] mod imp; #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] #[path = "imp/openssl.rs"] mod imp; #[cfg(test)] mod test; /// A typedef of the result-type returned by many methods. pub type Result = result::Result; /// An error returned from the TLS implementation. pub struct Error(imp::Error); impl error::Error for Error { fn source(&self) -> Option<&(dyn error::Error + 'static)> { error::Error::source(&self.0) } } impl fmt::Display for Error { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, fmt) } } impl fmt::Debug for Error { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt::Debug::fmt(&self.0, fmt) } } impl From for Error { fn from(err: imp::Error) -> Error { Error(err) } } /// A cryptographic identity. /// /// An identity is an X509 certificate along with its corresponding private key and chain of certificates to a trusted /// root. #[derive(Clone)] pub struct Identity(imp::Identity); impl Identity { /// Parses a DER-formatted PKCS #12 archive, using the specified password to decrypt the key. /// /// The archive should contain a leaf certificate and its private key, as well any intermediate /// certificates that should be sent to clients to allow them to build a chain to a trusted /// root. The chain certificates should be in order from the leaf certificate towards the root. /// /// PKCS #12 archives typically have the file extension `.p12` or `.pfx`, and can be created /// with the OpenSSL `pkcs12` tool: /// /// ```bash /// openssl pkcs12 -export -out identity.pfx -inkey key.pem -in cert.pem -certfile chain_certs.pem /// ``` pub fn from_pkcs12(der: &[u8], password: &str) -> Result { let identity = imp::Identity::from_pkcs12(der, password)?; Ok(Identity(identity)) } /// Parses a chain of PEM encoded X509 certificates, with the leaf certificate first. /// `key` is a PEM encoded PKCS #8 formatted private key for the leaf certificate. /// /// The certificate chain should contain any intermediate cerficates that should be sent to /// clients to allow them to build a chain to a trusted root. /// /// A certificate chain here means a series of PEM encoded certificates concatenated together. pub fn from_pkcs8(pem: &[u8], key: &[u8]) -> Result { let identity = imp::Identity::from_pkcs8(pem, key)?; Ok(Identity(identity)) } } /// An X509 certificate. #[derive(Clone)] pub struct Certificate(imp::Certificate); impl Certificate { /// Parses a DER-formatted X509 certificate. pub fn from_der(der: &[u8]) -> Result { let cert = imp::Certificate::from_der(der)?; Ok(Certificate(cert)) } /// Parses a PEM-formatted X509 certificate. pub fn from_pem(pem: &[u8]) -> Result { let cert = imp::Certificate::from_pem(pem)?; Ok(Certificate(cert)) } /// Returns the DER-encoded representation of this certificate. pub fn to_der(&self) -> Result> { let der = self.0.to_der()?; Ok(der) } } /// A TLS stream which has been interrupted midway through the handshake process. pub struct MidHandshakeTlsStream(imp::MidHandshakeTlsStream); impl fmt::Debug for MidHandshakeTlsStream where S: fmt::Debug, { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt::Debug::fmt(&self.0, fmt) } } impl MidHandshakeTlsStream { /// Returns a shared reference to the inner stream. pub fn get_ref(&self) -> &S { self.0.get_ref() } /// Returns a mutable reference to the inner stream. pub fn get_mut(&mut self) -> &mut S { self.0.get_mut() } } impl MidHandshakeTlsStream where S: io::Read + io::Write, { /// Restarts the handshake process. /// /// If the handshake completes successfully then the negotiated stream is /// returned. If there is a problem, however, then an error is returned. /// Note that the error may not be fatal. For example if the underlying /// stream is an asynchronous one then `HandshakeError::WouldBlock` may /// just mean to wait for more I/O to happen later. pub fn handshake(self) -> result::Result, HandshakeError> { match self.0.handshake() { Ok(s) => Ok(TlsStream(s)), Err(e) => Err(e.into()), } } } /// An error returned from `ClientBuilder::handshake`. #[derive(Debug)] pub enum HandshakeError { /// A fatal error. Failure(Error), /// A stream interrupted midway through the handshake process due to a /// `WouldBlock` error. /// /// Note that this is not a fatal error and it should be safe to call /// `handshake` at a later time once the stream is ready to perform I/O /// again. WouldBlock(MidHandshakeTlsStream), } impl error::Error for HandshakeError where S: Any + fmt::Debug, { fn source(&self) -> Option<&(dyn error::Error + 'static)> { match *self { HandshakeError::Failure(ref e) => Some(e), HandshakeError::WouldBlock(_) => None, } } } impl fmt::Display for HandshakeError where S: Any + fmt::Debug, { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { match *self { HandshakeError::Failure(ref e) => fmt::Display::fmt(e, fmt), HandshakeError::WouldBlock(_) => fmt.write_str("the handshake process was interrupted"), } } } impl From> for HandshakeError { fn from(e: imp::HandshakeError) -> HandshakeError { match e { imp::HandshakeError::Failure(e) => HandshakeError::Failure(Error(e)), imp::HandshakeError::WouldBlock(s) => { HandshakeError::WouldBlock(MidHandshakeTlsStream(s)) } } } } /// SSL/TLS protocol versions. #[derive(Debug, Copy, Clone)] pub enum Protocol { /// The SSL 3.0 protocol. /// /// # Warning /// /// SSL 3.0 has severe security flaws, and should not be used unless absolutely necessary. If /// you are not sure if you need to enable this protocol, you should not. Sslv3, /// The TLS 1.0 protocol. Tlsv10, /// The TLS 1.1 protocol. Tlsv11, /// The TLS 1.2 protocol. Tlsv12, #[doc(hidden)] __NonExhaustive, } /// A builder for `TlsConnector`s. pub struct TlsConnectorBuilder { identity: Option, min_protocol: Option, max_protocol: Option, root_certificates: Vec, accept_invalid_certs: bool, accept_invalid_hostnames: bool, use_sni: bool, disable_built_in_roots: bool, #[cfg(feature = "alpn")] alpn: Vec, } impl TlsConnectorBuilder { /// Sets the identity to be used for client certificate authentication. pub fn identity(&mut self, identity: Identity) -> &mut TlsConnectorBuilder { self.identity = Some(identity); self } /// Sets the minimum supported protocol version. /// /// A value of `None` enables support for the oldest protocols supported by the implementation. /// /// Defaults to `Some(Protocol::Tlsv10)`. pub fn min_protocol_version(&mut self, protocol: Option) -> &mut TlsConnectorBuilder { self.min_protocol = protocol; self } /// Sets the maximum supported protocol version. /// /// A value of `None` enables support for the newest protocols supported by the implementation. /// /// Defaults to `None`. pub fn max_protocol_version(&mut self, protocol: Option) -> &mut TlsConnectorBuilder { self.max_protocol = protocol; self } /// Adds a certificate to the set of roots that the connector will trust. /// /// The connector will use the system's trust root by default. This method can be used to add /// to that set when communicating with servers not trusted by the system. /// /// Defaults to an empty set. pub fn add_root_certificate(&mut self, cert: Certificate) -> &mut TlsConnectorBuilder { self.root_certificates.push(cert); self } /// Controls the use of built-in system certificates during certificate validation. /// /// Defaults to `false` -- built-in system certs will be used. pub fn disable_built_in_roots(&mut self, disable: bool) -> &mut TlsConnectorBuilder { self.disable_built_in_roots = disable; self } /// Request specific protocols through ALPN (Application-Layer Protocol Negotiation). /// /// Defaults to no protocols. #[cfg(feature = "alpn")] #[cfg_attr(docsrs, doc(cfg(feature = "alpn")))] pub fn request_alpns(&mut self, protocols: &[&str]) -> &mut TlsConnectorBuilder { self.alpn = protocols.iter().map(|s| (*s).to_owned()).collect(); self } /// Controls the use of certificate validation. /// /// Defaults to `false`. /// /// # Warning /// /// You should think very carefully before using this method. If invalid certificates are trusted, *any* /// certificate for *any* site will be trusted for use. This includes expired certificates. This introduces /// significant vulnerabilities, and should only be used as a last resort. pub fn danger_accept_invalid_certs( &mut self, accept_invalid_certs: bool, ) -> &mut TlsConnectorBuilder { self.accept_invalid_certs = accept_invalid_certs; self } /// Controls the use of Server Name Indication (SNI). /// /// Defaults to `true`. pub fn use_sni(&mut self, use_sni: bool) -> &mut TlsConnectorBuilder { self.use_sni = use_sni; self } /// Controls the use of hostname verification. /// /// Defaults to `false`. /// /// # Warning /// /// You should think very carefully before using this method. If invalid hostnames are trusted, *any* valid /// certificate for *any* site will be trusted for use. This introduces significant vulnerabilities, and should /// only be used as a last resort. pub fn danger_accept_invalid_hostnames( &mut self, accept_invalid_hostnames: bool, ) -> &mut TlsConnectorBuilder { self.accept_invalid_hostnames = accept_invalid_hostnames; self } /// Creates a new `TlsConnector`. pub fn build(&self) -> Result { let connector = imp::TlsConnector::new(self)?; Ok(TlsConnector(connector)) } } /// A builder for client-side TLS connections. /// /// # Examples /// /// ```rust /// use native_tls::TlsConnector; /// use std::io::{Read, Write}; /// use std::net::TcpStream; /// /// let connector = TlsConnector::new().unwrap(); /// /// let stream = TcpStream::connect("google.com:443").unwrap(); /// let mut stream = connector.connect("google.com", stream).unwrap(); /// /// stream.write_all(b"GET / HTTP/1.0\r\n\r\n").unwrap(); /// let mut res = vec![]; /// stream.read_to_end(&mut res).unwrap(); /// println!("{}", String::from_utf8_lossy(&res)); /// ``` #[derive(Clone, Debug)] pub struct TlsConnector(imp::TlsConnector); impl TlsConnector { /// Returns a new connector with default settings. pub fn new() -> Result { TlsConnector::builder().build() } /// Returns a new builder for a `TlsConnector`. pub fn builder() -> TlsConnectorBuilder { TlsConnectorBuilder { identity: None, min_protocol: Some(Protocol::Tlsv10), max_protocol: None, root_certificates: vec![], use_sni: true, accept_invalid_certs: false, accept_invalid_hostnames: false, disable_built_in_roots: false, #[cfg(feature = "alpn")] alpn: vec![], } } /// Initiates a TLS handshake. /// /// The provided domain will be used for both SNI and certificate hostname /// validation. /// /// If the socket is nonblocking and a `WouldBlock` error is returned during /// the handshake, a `HandshakeError::WouldBlock` error will be returned /// which can be used to restart the handshake when the socket is ready /// again. /// /// The domain is ignored if both SNI and hostname verification are /// disabled. pub fn connect( &self, domain: &str, stream: S, ) -> result::Result, HandshakeError> where S: io::Read + io::Write, { let s = self.0.connect(domain, stream)?; Ok(TlsStream(s)) } } /// A builder for `TlsAcceptor`s. pub struct TlsAcceptorBuilder { identity: Identity, min_protocol: Option, max_protocol: Option, } impl TlsAcceptorBuilder { /// Sets the minimum supported protocol version. /// /// A value of `None` enables support for the oldest protocols supported by the implementation. /// /// Defaults to `Some(Protocol::Tlsv10)`. pub fn min_protocol_version(&mut self, protocol: Option) -> &mut TlsAcceptorBuilder { self.min_protocol = protocol; self } /// Sets the maximum supported protocol version. /// /// A value of `None` enables support for the newest protocols supported by the implementation. /// /// Defaults to `None`. pub fn max_protocol_version(&mut self, protocol: Option) -> &mut TlsAcceptorBuilder { self.max_protocol = protocol; self } /// Creates a new `TlsAcceptor`. pub fn build(&self) -> Result { let acceptor = imp::TlsAcceptor::new(self)?; Ok(TlsAcceptor(acceptor)) } } /// A builder for server-side TLS connections. /// /// # Examples /// /// ```rust,no_run /// use native_tls::{Identity, TlsAcceptor, TlsStream}; /// use std::fs::File; /// use std::io::{Read}; /// use std::net::{TcpListener, TcpStream}; /// use std::sync::Arc; /// use std::thread; /// /// let mut file = File::open("identity.pfx").unwrap(); /// let mut identity = vec![]; /// file.read_to_end(&mut identity).unwrap(); /// let identity = Identity::from_pkcs12(&identity, "hunter2").unwrap(); /// /// let listener = TcpListener::bind("0.0.0.0:8443").unwrap(); /// let acceptor = TlsAcceptor::new(identity).unwrap(); /// let acceptor = Arc::new(acceptor); /// /// fn handle_client(stream: TlsStream) { /// // ... /// } /// /// for stream in listener.incoming() { /// match stream { /// Ok(stream) => { /// let acceptor = acceptor.clone(); /// thread::spawn(move || { /// let stream = acceptor.accept(stream).unwrap(); /// handle_client(stream); /// }); /// } /// Err(e) => { /* connection failed */ } /// } /// } /// ``` #[derive(Clone)] pub struct TlsAcceptor(imp::TlsAcceptor); impl TlsAcceptor { /// Creates a acceptor with default settings. /// /// The identity acts as the server's private key/certificate chain. pub fn new(identity: Identity) -> Result { TlsAcceptor::builder(identity).build() } /// Returns a new builder for a `TlsAcceptor`. /// /// The identity acts as the server's private key/certificate chain. pub fn builder(identity: Identity) -> TlsAcceptorBuilder { TlsAcceptorBuilder { identity, min_protocol: Some(Protocol::Tlsv10), max_protocol: None, } } /// Initiates a TLS handshake. /// /// If the socket is nonblocking and a `WouldBlock` error is returned during /// the handshake, a `HandshakeError::WouldBlock` error will be returned /// which can be used to restart the handshake when the socket is ready /// again. pub fn accept(&self, stream: S) -> result::Result, HandshakeError> where S: io::Read + io::Write, { match self.0.accept(stream) { Ok(s) => Ok(TlsStream(s)), Err(e) => Err(e.into()), } } } /// A stream managing a TLS session. pub struct TlsStream(imp::TlsStream); impl fmt::Debug for TlsStream { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt::Debug::fmt(&self.0, fmt) } } impl TlsStream { /// Returns a shared reference to the inner stream. pub fn get_ref(&self) -> &S { self.0.get_ref() } /// Returns a mutable reference to the inner stream. pub fn get_mut(&mut self) -> &mut S { self.0.get_mut() } } impl TlsStream { /// Returns the number of bytes that can be read without resulting in any /// network calls. pub fn buffered_read_size(&self) -> Result { Ok(self.0.buffered_read_size()?) } /// Returns the peer's leaf certificate, if available. pub fn peer_certificate(&self) -> Result> { Ok(self.0.peer_certificate()?.map(Certificate)) } /// Returns the tls-server-end-point channel binding data as defined in [RFC 5929]. /// /// [RFC 5929]: https://tools.ietf.org/html/rfc5929 pub fn tls_server_end_point(&self) -> Result>> { Ok(self.0.tls_server_end_point()?) } /// Returns the negotiated ALPN protocol. #[cfg(feature = "alpn")] #[cfg_attr(docsrs, doc(cfg(feature = "alpn")))] pub fn negotiated_alpn(&self) -> Result>> { Ok(self.0.negotiated_alpn()?) } /// Shuts down the TLS session. pub fn shutdown(&mut self) -> io::Result<()> { self.0.shutdown()?; Ok(()) } } impl io::Read for TlsStream { fn read(&mut self, buf: &mut [u8]) -> io::Result { self.0.read(buf) } } impl io::Write for TlsStream { fn write(&mut self, buf: &[u8]) -> io::Result { self.0.write(buf) } fn flush(&mut self) -> io::Result<()> { self.0.flush() } } fn _check_kinds() { use std::net::TcpStream; fn is_sync() {} fn is_send() {} is_sync::(); is_send::(); is_sync::(); is_send::(); is_sync::(); is_send::(); is_sync::(); is_send::(); is_sync::(); is_send::(); is_sync::>(); is_send::>(); is_sync::>(); is_send::>(); } native-tls-0.2.11/src/test.rs000064400000000000000000000417371046102023000141230ustar 00000000000000use std::fs; use std::io::{Read, Write}; use std::net::{TcpListener, TcpStream}; use std::process::{Command, Stdio}; use std::string::String; use std::thread; use super::*; macro_rules! p { ($e:expr) => { match $e { Ok(r) => r, Err(e) => panic!("{:?}", e), } }; } #[test] fn connect_google() { let builder = p!(TlsConnector::new()); let s = p!(TcpStream::connect("google.com:443")); let mut socket = p!(builder.connect("google.com", s)); p!(socket.write_all(b"GET / HTTP/1.0\r\n\r\n")); let mut result = vec![]; p!(socket.read_to_end(&mut result)); println!("{}", String::from_utf8_lossy(&result)); assert!(result.starts_with(b"HTTP/1.0")); assert!(result.ends_with(b"\r\n") || result.ends_with(b"")); } #[test] fn connect_bad_hostname() { let builder = p!(TlsConnector::new()); let s = p!(TcpStream::connect("google.com:443")); builder.connect("goggle.com", s).unwrap_err(); } #[test] fn connect_bad_hostname_ignored() { let builder = p!(TlsConnector::builder() .danger_accept_invalid_hostnames(true) .build()); let s = p!(TcpStream::connect("google.com:443")); builder.connect("goggle.com", s).unwrap(); } #[test] fn connect_no_root_certs() { let builder = p!(TlsConnector::builder().disable_built_in_roots(true).build()); let s = p!(TcpStream::connect("google.com:443")); assert!(builder.connect("google.com", s).is_err()); } #[test] fn server_no_root_certs() { let keys = test_cert_gen::keys(); let identity = p!(Identity::from_pkcs12( &keys.server.cert_and_key_pkcs12.pkcs12.0, &keys.server.cert_and_key_pkcs12.password )); let builder = p!(TlsAcceptor::new(identity)); let listener = p!(TcpListener::bind("0.0.0.0:0")); let port = p!(listener.local_addr()).port(); let j = thread::spawn(move || { let socket = p!(listener.accept()).0; let mut socket = p!(builder.accept(socket)); let mut buf = [0; 5]; p!(socket.read_exact(&mut buf)); assert_eq!(&buf, b"hello"); p!(socket.write_all(b"world")); }); let root_ca = Certificate::from_der(keys.client.ca.get_der()).unwrap(); let socket = p!(TcpStream::connect(("localhost", port))); let builder = p!(TlsConnector::builder() .disable_built_in_roots(true) .add_root_certificate(root_ca) .build()); let mut socket = p!(builder.connect("localhost", socket)); p!(socket.write_all(b"hello")); let mut buf = vec![]; p!(socket.read_to_end(&mut buf)); assert_eq!(buf, b"world"); p!(j.join()); } #[test] fn server() { let keys = test_cert_gen::keys(); let identity = p!(Identity::from_pkcs12( &keys.server.cert_and_key_pkcs12.pkcs12.0, &keys.server.cert_and_key_pkcs12.password )); let builder = p!(TlsAcceptor::new(identity)); let listener = p!(TcpListener::bind("0.0.0.0:0")); let port = p!(listener.local_addr()).port(); let j = thread::spawn(move || { let socket = p!(listener.accept()).0; let mut socket = p!(builder.accept(socket)); let mut buf = [0; 5]; p!(socket.read_exact(&mut buf)); assert_eq!(&buf, b"hello"); p!(socket.write_all(b"world")); }); let root_ca = Certificate::from_der(keys.client.ca.get_der()).unwrap(); let socket = p!(TcpStream::connect(("localhost", port))); let builder = p!(TlsConnector::builder() .add_root_certificate(root_ca) .build()); let mut socket = p!(builder.connect("localhost", socket)); p!(socket.write_all(b"hello")); let mut buf = vec![]; p!(socket.read_to_end(&mut buf)); assert_eq!(buf, b"world"); p!(j.join()); } #[test] fn certificate_from_pem() { let dir = tempfile::tempdir().unwrap(); let keys = test_cert_gen::keys(); let der_path = dir.path().join("cert.der"); fs::write(&der_path, &keys.client.ca.get_der()).unwrap(); let output = Command::new("openssl") .arg("x509") .arg("-in") .arg(der_path) .arg("-inform") .arg("der") .stderr(Stdio::piped()) .output() .unwrap(); assert!(output.status.success()); let cert = Certificate::from_pem(&output.stdout).unwrap(); assert_eq!(cert.to_der().unwrap(), keys.client.ca.get_der()); } #[test] fn peer_certificate() { let keys = test_cert_gen::keys(); let identity = p!(Identity::from_pkcs12( &keys.server.cert_and_key_pkcs12.pkcs12.0, &keys.server.cert_and_key_pkcs12.password )); let builder = p!(TlsAcceptor::new(identity)); let listener = p!(TcpListener::bind("0.0.0.0:0")); let port = p!(listener.local_addr()).port(); let j = thread::spawn(move || { let socket = p!(listener.accept()).0; let socket = p!(builder.accept(socket)); assert!(socket.peer_certificate().unwrap().is_none()); }); let root_ca = Certificate::from_der(keys.client.ca.get_der()).unwrap(); let socket = p!(TcpStream::connect(("localhost", port))); let builder = p!(TlsConnector::builder() .add_root_certificate(root_ca) .build()); let socket = p!(builder.connect("localhost", socket)); let cert = socket.peer_certificate().unwrap().unwrap(); assert_eq!( cert.to_der().unwrap(), keys.server.cert_and_key.cert.get_der() ); p!(j.join()); } #[test] fn server_tls11_only() { let keys = test_cert_gen::keys(); let identity = p!(Identity::from_pkcs12( &keys.server.cert_and_key_pkcs12.pkcs12.0, &keys.server.cert_and_key_pkcs12.password )); let builder = p!(TlsAcceptor::builder(identity) .min_protocol_version(Some(Protocol::Tlsv12)) .max_protocol_version(Some(Protocol::Tlsv12)) .build()); let listener = p!(TcpListener::bind("0.0.0.0:0")); let port = p!(listener.local_addr()).port(); let j = thread::spawn(move || { let socket = p!(listener.accept()).0; let mut socket = p!(builder.accept(socket)); let mut buf = [0; 5]; p!(socket.read_exact(&mut buf)); assert_eq!(&buf, b"hello"); p!(socket.write_all(b"world")); }); let root_ca = Certificate::from_der(keys.client.ca.get_der()).unwrap(); let socket = p!(TcpStream::connect(("localhost", port))); let builder = p!(TlsConnector::builder() .add_root_certificate(root_ca) .min_protocol_version(Some(Protocol::Tlsv12)) .max_protocol_version(Some(Protocol::Tlsv12)) .build()); let mut socket = p!(builder.connect("localhost", socket)); p!(socket.write_all(b"hello")); let mut buf = vec![]; p!(socket.read_to_end(&mut buf)); assert_eq!(buf, b"world"); p!(j.join()); } #[test] fn server_no_shared_protocol() { let keys = test_cert_gen::keys(); let identity = p!(Identity::from_pkcs12( &keys.server.cert_and_key_pkcs12.pkcs12.0, &keys.server.cert_and_key_pkcs12.password )); let builder = p!(TlsAcceptor::builder(identity) .min_protocol_version(Some(Protocol::Tlsv12)) .build()); let listener = p!(TcpListener::bind("0.0.0.0:0")); let port = p!(listener.local_addr()).port(); let j = thread::spawn(move || { let socket = p!(listener.accept()).0; assert!(builder.accept(socket).is_err()); }); let root_ca = Certificate::from_der(keys.client.ca.get_der()).unwrap(); let socket = p!(TcpStream::connect(("localhost", port))); let builder = p!(TlsConnector::builder() .add_root_certificate(root_ca) .min_protocol_version(Some(Protocol::Tlsv11)) .max_protocol_version(Some(Protocol::Tlsv11)) .build()); assert!(builder.connect("localhost", socket).is_err()); p!(j.join()); } #[test] fn server_untrusted() { let keys = test_cert_gen::keys(); let identity = p!(Identity::from_pkcs12( &keys.server.cert_and_key_pkcs12.pkcs12.0, &keys.server.cert_and_key_pkcs12.password )); let builder = p!(TlsAcceptor::new(identity)); let listener = p!(TcpListener::bind("0.0.0.0:0")); let port = p!(listener.local_addr()).port(); let j = thread::spawn(move || { let socket = p!(listener.accept()).0; // FIXME should assert error // https://github.com/steffengy/schannel-rs/issues/20 let _ = builder.accept(socket); }); let socket = p!(TcpStream::connect(("localhost", port))); let builder = p!(TlsConnector::new()); builder.connect("localhost", socket).unwrap_err(); p!(j.join()); } #[test] fn server_untrusted_unverified() { let keys = test_cert_gen::keys(); let identity = p!(Identity::from_pkcs12( &keys.server.cert_and_key_pkcs12.pkcs12.0, &keys.server.cert_and_key_pkcs12.password )); let builder = p!(TlsAcceptor::new(identity)); let listener = p!(TcpListener::bind("0.0.0.0:0")); let port = p!(listener.local_addr()).port(); let j = thread::spawn(move || { let socket = p!(listener.accept()).0; let mut socket = p!(builder.accept(socket)); let mut buf = [0; 5]; p!(socket.read_exact(&mut buf)); assert_eq!(&buf, b"hello"); p!(socket.write_all(b"world")); }); let socket = p!(TcpStream::connect(("localhost", port))); let builder = p!(TlsConnector::builder() .danger_accept_invalid_certs(true) .build()); let mut socket = p!(builder.connect("localhost", socket)); p!(socket.write_all(b"hello")); let mut buf = vec![]; p!(socket.read_to_end(&mut buf)); assert_eq!(buf, b"world"); p!(j.join()); } #[test] fn import_same_identity_multiple_times() { let keys = test_cert_gen::keys(); let _ = p!(Identity::from_pkcs12( &keys.server.cert_and_key_pkcs12.pkcs12.0, &keys.server.cert_and_key_pkcs12.password )); let _ = p!(Identity::from_pkcs12( &keys.server.cert_and_key_pkcs12.pkcs12.0, &keys.server.cert_and_key_pkcs12.password )); let cert = keys.server.cert_and_key.cert.to_pem().into_bytes(); let key = rsa_to_pkcs8(&keys.server.cert_and_key.key.to_pem_incorrect()).into_bytes(); let _ = p!(Identity::from_pkcs8(&cert, &key)); let _ = p!(Identity::from_pkcs8(&cert, &key)); } #[test] fn from_pkcs8_rejects_rsa_key() { let keys = test_cert_gen::keys(); let cert = keys.server.cert_and_key.cert.to_pem().into_bytes(); let rsa_key = keys.server.cert_and_key.key.to_pem_incorrect(); assert!(Identity::from_pkcs8(&cert, rsa_key.as_bytes()).is_err()); let pkcs8_key = rsa_to_pkcs8(&rsa_key); assert!(Identity::from_pkcs8(&cert, pkcs8_key.as_bytes()).is_ok()); } #[test] fn shutdown() { let keys = test_cert_gen::keys(); let identity = p!(Identity::from_pkcs12( &keys.server.cert_and_key_pkcs12.pkcs12.0, &keys.server.cert_and_key_pkcs12.password )); let builder = p!(TlsAcceptor::new(identity)); let listener = p!(TcpListener::bind("0.0.0.0:0")); let port = p!(listener.local_addr()).port(); let j = thread::spawn(move || { let socket = p!(listener.accept()).0; let mut socket = p!(builder.accept(socket)); let mut buf = [0; 5]; p!(socket.read_exact(&mut buf)); assert_eq!(&buf, b"hello"); assert_eq!(p!(socket.read(&mut buf)), 0); p!(socket.shutdown()); }); let root_ca = Certificate::from_der(keys.client.ca.get_der()).unwrap(); let socket = p!(TcpStream::connect(("localhost", port))); let builder = p!(TlsConnector::builder() .add_root_certificate(root_ca) .build()); let mut socket = p!(builder.connect("localhost", socket)); p!(socket.write_all(b"hello")); p!(socket.shutdown()); p!(j.join()); } #[test] #[cfg(feature = "alpn")] fn alpn_google_h2() { let builder = p!(TlsConnector::builder().request_alpns(&["h2"]).build()); let s = p!(TcpStream::connect("google.com:443")); let socket = p!(builder.connect("google.com", s)); let alpn = p!(socket.negotiated_alpn()); assert_eq!(alpn, Some(b"h2".to_vec())); } #[test] #[cfg(feature = "alpn")] fn alpn_google_invalid() { let builder = p!(TlsConnector::builder().request_alpns(&["h2c"]).build()); let s = p!(TcpStream::connect("google.com:443")); let socket = p!(builder.connect("google.com", s)); let alpn = p!(socket.negotiated_alpn()); assert_eq!(alpn, None); } #[test] #[cfg(feature = "alpn")] fn alpn_google_none() { let builder = p!(TlsConnector::new()); let s = p!(TcpStream::connect("google.com:443")); let socket = p!(builder.connect("google.com", s)); let alpn = p!(socket.negotiated_alpn()); assert_eq!(alpn, None); } #[test] fn server_pkcs8() { let keys = test_cert_gen::keys(); let cert = keys.server.cert_and_key.cert.to_pem().into_bytes(); let key = rsa_to_pkcs8(&keys.server.cert_and_key.key.to_pem_incorrect()).into_bytes(); let ident = Identity::from_pkcs8(&cert, &key).unwrap(); let ident2 = ident.clone(); let builder = p!(TlsAcceptor::new(ident)); let listener = p!(TcpListener::bind("0.0.0.0:0")); let port = p!(listener.local_addr()).port(); let j = thread::spawn(move || { let socket = p!(listener.accept()).0; let mut socket = p!(builder.accept(socket)); let mut buf = [0; 5]; p!(socket.read_exact(&mut buf)); assert_eq!(&buf, b"hello"); p!(socket.write_all(b"world")); }); let root_ca = Certificate::from_der(keys.client.ca.get_der()).unwrap(); let socket = p!(TcpStream::connect(("localhost", port))); let mut builder = TlsConnector::builder(); // FIXME // This checks that we can successfully add a certificate on the client side. // Unfortunately, we can not request client certificates through the API of this library, // otherwise we could check in the server thread that // socket.peer_certificate().unwrap().is_some() builder.identity(ident2); builder.add_root_certificate(root_ca); let builder = p!(builder.build()); let mut socket = p!(builder.connect("localhost", socket)); p!(socket.write_all(b"hello")); let mut buf = vec![]; p!(socket.read_to_end(&mut buf)); assert_eq!(buf, b"world"); p!(j.join()); } #[test] fn two_servers() { let keys1 = test_cert_gen::gen_keys(); let cert = keys1.server.cert_and_key.cert.to_pem().into_bytes(); let key = rsa_to_pkcs8(&keys1.server.cert_and_key.key.to_pem_incorrect()).into_bytes(); let identity = p!(Identity::from_pkcs8(&cert, &key)); let builder = TlsAcceptor::builder(identity); let builder = p!(builder.build()); let listener = p!(TcpListener::bind("0.0.0.0:0")); let port = p!(listener.local_addr()).port(); let j = thread::spawn(move || { let socket = p!(listener.accept()).0; let mut socket = p!(builder.accept(socket)); let mut buf = [0; 5]; p!(socket.read_exact(&mut buf)); assert_eq!(&buf, b"hello"); p!(socket.write_all(b"world")); }); let keys2 = test_cert_gen::gen_keys(); let cert = keys2.server.cert_and_key.cert.to_pem().into_bytes(); let key = rsa_to_pkcs8(&keys2.server.cert_and_key.key.to_pem_incorrect()).into_bytes(); let identity = p!(Identity::from_pkcs8(&cert, &key)); let builder = TlsAcceptor::builder(identity); let builder = p!(builder.build()); let listener = p!(TcpListener::bind("0.0.0.0:0")); let port2 = p!(listener.local_addr()).port(); let j2 = thread::spawn(move || { let socket = p!(listener.accept()).0; let mut socket = p!(builder.accept(socket)); let mut buf = [0; 5]; p!(socket.read_exact(&mut buf)); assert_eq!(&buf, b"hello"); p!(socket.write_all(b"world")); }); let root_ca = Certificate::from_der(keys1.client.ca.get_der()).unwrap(); let socket = p!(TcpStream::connect(("localhost", port))); let mut builder = TlsConnector::builder(); builder.add_root_certificate(root_ca); let builder = p!(builder.build()); let mut socket = p!(builder.connect("localhost", socket)); p!(socket.write_all(b"hello")); let mut buf = vec![]; p!(socket.read_to_end(&mut buf)); assert_eq!(buf, b"world"); let root_ca = Certificate::from_der(keys2.client.ca.get_der()).unwrap(); let socket = p!(TcpStream::connect(("localhost", port2))); let mut builder = TlsConnector::builder(); builder.add_root_certificate(root_ca); let builder = p!(builder.build()); let mut socket = p!(builder.connect("localhost", socket)); p!(socket.write_all(b"hello")); let mut buf = vec![]; p!(socket.read_to_end(&mut buf)); assert_eq!(buf, b"world"); p!(j.join()); p!(j2.join()); } fn rsa_to_pkcs8(pem: &str) -> String { let mut child = Command::new("openssl") .arg("pkcs8") .arg("-topk8") .arg("-nocrypt") .stdin(Stdio::piped()) .stdout(Stdio::piped()) .spawn() .unwrap(); { let child_stdin = child.stdin.as_mut().unwrap(); child_stdin.write_all(pem.as_bytes()).unwrap(); } String::from_utf8(child.wait_with_output().unwrap().stdout).unwrap() }