├── .github ├── dependabot.yml └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── SECURITY.md ├── benches └── benches.rs ├── build.rs ├── ci-tests.sh ├── clippy.toml ├── docs ├── CNAME └── index.html ├── examples ├── oneway.rs └── simple.rs ├── hfuzz ├── Cargo.toml └── src │ └── bin │ ├── handshake_reader.rs │ ├── handshake_writer.rs │ ├── params.rs │ └── transport.rs ├── rustfmt.toml ├── src ├── builder.rs ├── cipherstate.rs ├── constants.rs ├── error.rs ├── handshakestate.rs ├── lib.rs ├── params │ ├── mod.rs │ └── patterns.rs ├── resolvers │ ├── default.rs │ ├── mod.rs │ └── ring.rs ├── stateless_transportstate.rs ├── symmetricstate.rs ├── transportstate.rs ├── types.rs └── utils.rs └── tests ├── general.rs ├── vectors.rs └── vectors ├── cacophony.txt ├── snow-extended.txt └── snow.txt /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "cargo" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: CI Tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - "*" 7 | pull_request: 8 | branches: 9 | - "*" 10 | 11 | env: 12 | CARGO_TERM_COLOR: always 13 | 14 | jobs: 15 | ci-tests: 16 | runs-on: ${{ matrix.os }} 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | os: [ubuntu-latest, macos-latest, windows-latest] 21 | rust: [stable, beta, nightly, 1.81.0] 22 | 23 | steps: 24 | - uses: actions/checkout@v4 25 | - uses: dtolnay/rust-toolchain@master 26 | with: 27 | toolchain: ${{ matrix.rust }} 28 | components: clippy 29 | - uses: taiki-e/install-action@nextest 30 | - uses: Swatinem/rust-cache@v2 31 | - name: Install LLVM and Clang 32 | if: startsWith(matrix.os, 'windows') 33 | uses: KyleMayes/install-llvm-action@v2 34 | with: 35 | version: "11.0" 36 | directory: ${{ runner.temp }}/llvm 37 | - name: Set LIBCLANG_PATH 38 | if: startsWith(matrix.os, 'windows') 39 | run: echo "LIBCLANG_PATH=$((gcm clang).source -replace "clang.exe")" >> $env:GITHUB_ENV 40 | - name: Run tests 41 | run: bash ./ci-tests.sh 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Cargo.lock 2 | .idea/ 3 | *.iml 4 | target/ 5 | .DS_Store 6 | .cargo/ 7 | vendor/ 8 | hfuzz_*/ 9 | .vscode/ 10 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "snow" 3 | description = "A pure-rust implementation of the Noise Protocol Framework" 4 | homepage = "https://github.com/mcginty/snow" 5 | documentation = "https://docs.rs/snow/" 6 | repository = "https://github.com/mcginty/snow" 7 | version = "0.10.0-alpha.1" 8 | authors = ["Jake McGinty ", "trevp"] 9 | license = "Apache-2.0 OR MIT" 10 | categories = ["cryptography"] 11 | readme = "README.md" 12 | keywords = ["noise", "protocol", "crypto"] 13 | edition = "2021" 14 | rust-version = "1.81.0" 15 | 16 | # This is slightly mumbo-jumboey, but in short: 17 | # Features with a -resolver suffix simply enables the existence of a specific resolver, 18 | # and -accelerated suffix means that this resolver will be the default used by the Builder. 19 | # 20 | # If default features are disabled and default-resolver is used, required crypto primitives 21 | # must be enabled individually. 22 | [features] 23 | default = ["default-resolver", "default-resolver-crypto", "std"] 24 | default-resolver = [] 25 | default-resolver-crypto = [ 26 | "use-aes-gcm", 27 | "use-chacha20poly1305", 28 | "use-blake2", 29 | "use-sha2", 30 | "use-curve25519", 31 | ] 32 | nightly = ["blake2/simd_opt", "subtle/nightly"] 33 | ring-resolver = ["ring", "std"] 34 | ring-accelerated = ["ring-resolver", "default-resolver", "std"] 35 | vector-tests = [] 36 | hfs = [] 37 | risky-raw-split = [] 38 | 39 | # Backwards-compatibility aliases 40 | pqclean_kyber1024 = ["use-pqcrypto-kyber1024"] 41 | xchachapoly = ["use-xchacha20poly1305"] 42 | 43 | # Enable std features on dependencies if possible. 44 | std = [ 45 | "rand_core/std", 46 | "subtle/std", 47 | "ring/std", 48 | "blake2/std", 49 | "sha2/std", 50 | "byteorder/std", 51 | ] 52 | 53 | # Crypto primitives for default-resolver. 54 | use-curve25519 = ["curve25519-dalek", "default-resolver"] 55 | use-chacha20poly1305 = ["chacha20poly1305", "default-resolver"] 56 | use-xchacha20poly1305 = ["chacha20poly1305", "default-resolver"] 57 | use-blake2 = ["blake2", "default-resolver"] 58 | use-sha2 = ["sha2", "default-resolver"] 59 | use-aes-gcm = ["aes-gcm", "default-resolver"] 60 | use-pqcrypto-kyber1024 = [ 61 | "pqcrypto-kyber", 62 | "pqcrypto-traits", 63 | "hfs", 64 | "default-resolver", 65 | ] 66 | use-p256 = ["p256", "default-resolver"] 67 | 68 | [[bench]] 69 | name = "benches" 70 | harness = false 71 | 72 | [badges] 73 | travis-ci = { repository = "mcginty/snow", branch = "master" } 74 | 75 | [dependencies] 76 | # TODO: Waiting on https://github.com/RustCrypto/traits/issues/1642 77 | rand_core = { version = "0.6", default-features = false, features = [ 78 | "getrandom", 79 | ] } 80 | subtle = { version = "2.4", default-features = false } 81 | 82 | # default crypto provider 83 | aes-gcm = { version = "0.10", optional = true, default-features = false, features = [ 84 | "aes", 85 | ] } 86 | chacha20poly1305 = { version = "0.10", optional = true, default-features = false } 87 | blake2 = { version = "0.10", optional = true, default-features = false } 88 | sha2 = { version = "0.10", optional = true, default-features = false } 89 | curve25519-dalek = { version = "4", optional = true, default-features = false } 90 | p256 = { version = "0.13.2", features = ["ecdh"], optional = true } 91 | 92 | pqcrypto-kyber = { version = "0.8", optional = true } 93 | pqcrypto-traits = { version = "0.3", optional = true } 94 | 95 | # ring crypto provider 96 | ring = { version = "0.17", optional = true } 97 | byteorder = { version = "1.4", optional = true, default-features = false } 98 | 99 | [dev-dependencies] 100 | criterion = "0.5" 101 | serde = "1.0" 102 | serde_json = "1.0" 103 | serde_derive = "1.0" 104 | hex = "0.4" 105 | x25519-dalek = "2.0" 106 | # TODO: Waiting on https://github.com/RustCrypto/traits/issues/1642 107 | rand = "0.8" 108 | 109 | [build-dependencies] 110 | rustc_version = "0.4" 111 | 112 | [profile.test] 113 | opt-level = 1 114 | 115 | [package.metadata.docs.rs] 116 | features = ["ring-resolver"] 117 | all-features = false 118 | no-default-features = false 119 | 120 | [lints.rust] 121 | unsafe_code = "forbid" 122 | 123 | [lints.clippy] 124 | unseparated_literal_suffix = "warn" 125 | std_instead_of_core = "warn" 126 | as_conversions = "warn" 127 | shadow_reuse = "warn" 128 | missing_asserts_for_indexing = "warn" 129 | missing_assert_message = "warn" 130 | 131 | pedantic = { level = "warn", priority = -1 } 132 | doc_markdown = "allow" 133 | used_underscore_items = "allow" 134 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | https://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Jake McGinty 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Snow 2 | 3 | [![Crates.io](https://img.shields.io/crates/v/snow.svg)](https://crates.io/crates/snow) 4 | [![Docs.rs](https://docs.rs/snow/badge.svg)](https://docs.rs/snow) 5 | [![Build Status](https://github.com/mcginty/snow/workflows/Build/badge.svg)](https://github.com/mcginty/snow/actions) 6 | [![dependency status](https://deps.rs/repo/github/mcginty/snow/status.svg)](https://deps.rs/repo/github/mcginty/snow) 7 | 8 | ![totally official snow logo](https://i.imgur.com/gFgvo49.jpg?1) 9 | 10 | An implementation of Trevor Perrin's [Noise Protocol](https://noiseprotocol.org/) that 11 | is designed to be Hard To Fuck Up™. 12 | 13 | 🔥 **Warning** 🔥 This library has not received any formal audit. 14 | 15 | ## What's it look like? 16 | 17 | See `examples/simple.rs` for a more complete TCP client/server example. 18 | 19 | ```rust 20 | let mut noise = snow::Builder::new("Noise_NN_25519_ChaChaPoly_BLAKE2s".parse()?) 21 | .build_initiator()?; 22 | 23 | let mut buf = [0u8; 65535]; 24 | 25 | // write first handshake message 26 | noise.write_message(&[], &mut buf)?; 27 | 28 | // receive response message 29 | let incoming = receive_message_from_the_mysterious_ether(); 30 | noise.read_message(&incoming, &mut buf)?; 31 | 32 | // complete handshake, and transition the state machine into transport mode 33 | let mut noise = noise.into_transport_mode()?; 34 | ``` 35 | 36 | See the full documentation at [https://docs.rs/snow](https://docs.rs/snow). 37 | 38 | ## Implemented 39 | 40 | Snow is currently tracking against [Noise spec revision 34](https://noiseprotocol.org/noise_rev34.html). 41 | 42 | However, a not all features have been implemented yet (pull requests welcome): 43 | 44 | - [ ] [The `fallback` modifier](https://noiseprotocol.org/noise_rev34.html#the-fallback-modifier) 45 | 46 | ## Crypto 47 | 48 | Cryptographic providers are swappable through `Builder::with_resolver()`, but by default 49 | it chooses select, artisanal pure-Rust implementations (see `Cargo.toml` for a quick 50 | overview). 51 | 52 | ### Other Providers 53 | 54 | #### ring 55 | 56 | [ring](https://github.com/briansmith/ring) is a crypto library based off of BoringSSL 57 | and is significantly faster than most of the pure-Rust implementations. 58 | 59 | If you enable the `ring-resolver` feature, Snow will include a `resolvers::ring` module 60 | as well as a `RingAcceleratedResolver` available to be used with 61 | `Builder::with_resolver()`. 62 | 63 | If you enable the `ring-accelerated` feature, Snow will default to choosing `ring`'s 64 | crypto implementations when available. 65 | 66 | ### Resolver primitives supported 67 | 68 | | | default | ring | 69 | | -------------------------------------: | :----------------: | :----------------: | 70 | | CSPRNG | :heavy_check_mark: | :heavy_check_mark: | 71 | | 25519 | :heavy_check_mark: | :heavy_check_mark: | 72 | | 448 | | | 73 | | P-256:checkered_flag: | :heavy_check_mark: | | 74 | | AESGCM | :heavy_check_mark: | :heavy_check_mark: | 75 | | ChaChaPoly | :heavy_check_mark: | :heavy_check_mark: | 76 | | XChaChaPoly:checkered_flag: | :heavy_check_mark: | | 77 | | SHA256 | :heavy_check_mark: | :heavy_check_mark: | 78 | | SHA512 | :heavy_check_mark: | :heavy_check_mark: | 79 | | BLAKE2s | :heavy_check_mark: | | 80 | | BLAKE2b | :heavy_check_mark: | | 81 | 82 | > [!Note] 83 | > :checkered_flag: P-256 and XChaChaPoly are not in the official specification of Noise, and thus need to be enabled 84 | via the feature flags `use-p256` and `use-xchacha20poly1305`, respectively. 85 | 86 | ## `no_std` support and feature selection 87 | 88 | Snow can be used in `no_std` environments if `alloc` is provided. 89 | 90 | By default, Snow uses the standard library, default crypto resolver and a selected collection 91 | of crypto primitives. To use Snow in `no_std` environments or make other kinds of customized 92 | setups, use Snow with `default-features = false`. This way you will individually select 93 | the components you wish to use. `default-resolver` is the only built-in resolver that 94 | currently supports `no_std`. 95 | 96 | To use a custom setup with `default-resolver`, enable your desired selection of cryptographic primitives: 97 | 98 | | | Primitive | Feature flag | 99 | | ----------: | :------------------------------------- | :--------------------- | 100 | | **DHs** | Curve25519 | `use-curve25519` | 101 | | | P-256:checkered_flag: | `use-p256` | 102 | | **Ciphers** | AES-GCM | `use-aes-gcm` | 103 | | | ChaChaPoly | `use-chacha20poly1305` | 104 | | | XChaChaPoly:checkered_flag: | `use-xchacha20poly1305`| 105 | | **Hashes** | SHA-256 | `use-sha2` | 106 | | | SHA-512 | `use-sha2` | 107 | | | BLAKE2s | `use-blake2` | 108 | | | BLAKE2b | `use-blake2` | 109 | 110 | > [!Note] 111 | > :checkered_flag: XChaChaPoly and P-256 are not in the official specification of Noise, but they are supported 112 | by Snow. 113 | 114 | ### Example configurations 115 | 116 | **Curve25519 + AES-GCM + SHA-2** with standard library features. 117 | ```toml 118 | default-features = false 119 | features = [ 120 | "use-curve25519", 121 | "use-aes-gcm", 122 | "use-sha2", 123 | "std", 124 | ] 125 | ``` 126 | 127 | **Curve25519 + ChaChaPoly + BLAKE2** without standard library. 128 | ```toml 129 | default-features = false 130 | features = [ 131 | "use-curve25519", 132 | "use-chacha20poly1305", 133 | "use-blake2", 134 | ] 135 | ``` 136 | 137 | ### `getrandom` support 138 | 139 | Most crypto implementations supported by `default-resolver` will require 140 | [`getrandom`](getrandom). 141 | 142 | If your target platform is not directly supported 143 | you might have to provide a custom implementation in your crate root. 144 | Check out their [documentation](getrandom-custom) for details. 145 | 146 | [getrandom]: https://crates.io/crates/getrandom 147 | [getrandom-custom]: https://docs.rs/getrandom/0.2.15/getrandom/macro.register_custom_getrandom.html 148 | 149 | ## License 150 | 151 | `snow` is offered with a dual choice-of-license between: 152 | 153 | - [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0) 154 | - [MIT license](https://opensource.org/license/mit/) 155 | 156 | where you may choose either of these licenses to follow for this work. 157 | 158 | ### Contribution 159 | 160 | Unless you explicitly state otherwise, any contribution intentionally submitted 161 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 162 | dual licensed as above, without any additional terms or conditions. 163 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Reporting a Vulnerability 4 | 5 | If you have discovered a security vulnerability in this project, please report it privately. **Do not disclose it as a public issue.** This gives us time to work with you to fix the issue before public exposure, reducing the chance that the exploit will be used before a patch is released. 6 | 7 | Please disclose it at [security advisory](https://github.com/mcginty/snow/security/advisories/new). 8 | 9 | This project is maintained by volunteers on a reasonable-effort basis. As such, please give us at least 90 days to work on a fix before public exposure. 10 | -------------------------------------------------------------------------------- /benches/benches.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::shadow_reuse)] 2 | #[macro_use] 3 | extern crate criterion; 4 | 5 | use criterion::{Criterion, Throughput}; 6 | use snow::{params::NoiseParams, Builder}; 7 | 8 | const MSG_SIZE: usize = 4096; 9 | 10 | fn benchmarks(c: &mut Criterion) { 11 | let mut builder_group = c.benchmark_group("builder"); 12 | builder_group.throughput(Throughput::Elements(1)); 13 | builder_group.bench_function("skeleton", |b| { 14 | b.iter(move || { 15 | Builder::new("Noise_NN_25519_ChaChaPoly_SHA256".parse().unwrap()) 16 | .build_initiator() 17 | .unwrap(); 18 | }); 19 | }); 20 | 21 | builder_group.bench_function("withkey", |b| { 22 | b.iter(move || { 23 | Builder::new("Noise_XX_25519_ChaChaPoly_SHA256".parse().unwrap()) 24 | .local_private_key(&[1_u8; 32]) 25 | .unwrap() 26 | .build_initiator() 27 | .unwrap(); 28 | }); 29 | }); 30 | builder_group.finish(); 31 | 32 | let mut handshake_group = c.benchmark_group("handshake"); 33 | handshake_group.throughput(Throughput::Elements(1)); 34 | handshake_group.bench_function("xx", |b| { 35 | b.iter(move || { 36 | let pattern: NoiseParams = "Noise_XX_25519_ChaChaPoly_BLAKE2b".parse().unwrap(); 37 | let mut h_i = Builder::new(pattern.clone()) 38 | .local_private_key(&[1_u8; 32]) 39 | .unwrap() 40 | .build_initiator() 41 | .unwrap(); 42 | let mut h_r = Builder::new(pattern) 43 | .local_private_key(&[2_u8; 32]) 44 | .unwrap() 45 | .build_responder() 46 | .unwrap(); 47 | 48 | let mut buffer_msg = [0_u8; MSG_SIZE * 2]; 49 | let mut buffer_out = [0_u8; MSG_SIZE * 2]; 50 | 51 | // get the handshaking out of the way for even testing 52 | let len = h_i.write_message(&[0_u8; 0], &mut buffer_msg).unwrap(); 53 | h_r.read_message(&buffer_msg[..len], &mut buffer_out).unwrap(); 54 | let len = h_r.write_message(&[0_u8; 0], &mut buffer_msg).unwrap(); 55 | h_i.read_message(&buffer_msg[..len], &mut buffer_out).unwrap(); 56 | let len = h_i.write_message(&[0_u8; 0], &mut buffer_msg).unwrap(); 57 | h_r.read_message(&buffer_msg[..len], &mut buffer_out).unwrap(); 58 | }); 59 | }); 60 | 61 | handshake_group.bench_function("nn", |b| { 62 | b.iter(move || { 63 | let pattern = "Noise_NN_25519_ChaChaPoly_BLAKE2b"; 64 | let mut h_i = Builder::new(pattern.parse().unwrap()).build_initiator().unwrap(); 65 | let mut h_r = Builder::new(pattern.parse().unwrap()).build_responder().unwrap(); 66 | 67 | let mut buffer_msg = [0_u8; MSG_SIZE * 2]; 68 | let mut buffer_out = [0_u8; MSG_SIZE * 2]; 69 | 70 | // get the handshaking out of the way for even testing 71 | let len = h_i.write_message(&[0_u8; 0], &mut buffer_msg).unwrap(); 72 | h_r.read_message(&buffer_msg[..len], &mut buffer_out).unwrap(); 73 | let len = h_r.write_message(&[0_u8; 0], &mut buffer_msg).unwrap(); 74 | h_i.read_message(&buffer_msg[..len], &mut buffer_out).unwrap(); 75 | }); 76 | }); 77 | handshake_group.finish(); 78 | 79 | let mut transport_group = c.benchmark_group("transport"); 80 | transport_group.throughput(Throughput::Bytes(u64::try_from(MSG_SIZE * 2).unwrap())); 81 | if cfg!(feature = "ring-accelerated") { 82 | transport_group.bench_function("AESGCM_SHA256 throughput", |b| { 83 | static PATTERN: &str = "Noise_NN_25519_AESGCM_SHA256"; 84 | 85 | let mut h_i = Builder::new(PATTERN.parse().unwrap()).build_initiator().unwrap(); 86 | let mut h_r = Builder::new(PATTERN.parse().unwrap()).build_responder().unwrap(); 87 | 88 | let mut buffer_msg = [0_u8; MSG_SIZE * 2]; 89 | let mut buffer_out = [0_u8; MSG_SIZE * 2]; 90 | 91 | // get the handshaking out of the way for even testing 92 | let len = h_i.write_message(&[0_u8; 0], &mut buffer_msg).unwrap(); 93 | h_r.read_message(&buffer_msg[..len], &mut buffer_out).unwrap(); 94 | let len = h_r.write_message(&[0_u8; 0], &mut buffer_msg).unwrap(); 95 | h_i.read_message(&buffer_msg[..len], &mut buffer_out).unwrap(); 96 | 97 | let mut h_i = h_i.into_transport_mode().unwrap(); 98 | let mut h_r = h_r.into_transport_mode().unwrap(); 99 | 100 | b.iter(move || { 101 | let len = h_i.write_message(&buffer_msg[..MSG_SIZE], &mut buffer_out).unwrap(); 102 | let _ = h_r.read_message(&buffer_out[..len], &mut buffer_msg).unwrap(); 103 | }); 104 | }); 105 | } 106 | 107 | transport_group.bench_function("ChaChaPoly_BLAKE2s throughput", |b| { 108 | static PATTERN: &str = "Noise_NN_25519_ChaChaPoly_BLAKE2s"; 109 | 110 | let mut h_i = Builder::new(PATTERN.parse().unwrap()).build_initiator().unwrap(); 111 | let mut h_r = Builder::new(PATTERN.parse().unwrap()).build_responder().unwrap(); 112 | 113 | let mut buffer_msg = [0_u8; MSG_SIZE * 2]; 114 | let mut buffer_out = [0_u8; MSG_SIZE * 2]; 115 | 116 | // get the handshaking out of the way for even testing 117 | let len = h_i.write_message(&[0_u8; 0], &mut buffer_msg).unwrap(); 118 | h_r.read_message(&buffer_msg[..len], &mut buffer_out).unwrap(); 119 | let len = h_r.write_message(&[0_u8; 0], &mut buffer_msg).unwrap(); 120 | h_i.read_message(&buffer_msg[..len], &mut buffer_out).unwrap(); 121 | 122 | let mut h_i = h_i.into_transport_mode().unwrap(); 123 | let mut h_r = h_r.into_transport_mode().unwrap(); 124 | 125 | b.iter(move || { 126 | let len = h_i.write_message(&buffer_msg[..MSG_SIZE], &mut buffer_out).unwrap(); 127 | let _ = h_r.read_message(&buffer_out[..len], &mut buffer_msg).unwrap(); 128 | }); 129 | }); 130 | } 131 | 132 | criterion_group!(benches, benchmarks); 133 | criterion_main!(benches); 134 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | use rustc_version::{version_meta, Channel}; 2 | use std::env; 3 | 4 | fn main() { 5 | if env::var("CARGO_FEATURE_SODIUMOXIDE").is_ok() { 6 | println!( 7 | "cargo:warning=Use of the sodiumoxide backend is deprecated, as it is no longer \ 8 | maintained; please consider switching to another resolver." 9 | ); 10 | } 11 | if version_meta().unwrap().channel == Channel::Nightly { 12 | println!("cargo:rustc-cfg=feature=\"nightly\""); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /ci-tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | TARGET="$([ -n "$1" ] && echo "--target $1" || echo "")" 4 | MSRV="$(cargo metadata --no-deps | jq -r .packages[0].rust_version)" 5 | RUSTC_VERSION="$(rustc --version | cut -d' ' -f2)" 6 | TEST="cargo nextest run" 7 | 8 | COMMON_FEATURES="use-p256 use-xchacha20poly1305 vector-tests" 9 | if ! rustc -vV | grep 'host: .*windows' &> /dev/null; then 10 | COMMON_FEATURES="hfs use-pqcrypto-kyber1024 $COMMON_FEATURES" 11 | fi 12 | 13 | set -x 14 | cargo check --benches --tests --examples 15 | if [[ "$CI" != "true" || "$RUSTC_VERSION" == "$MSRV" ]]; then 16 | cargo clippy --features "$COMMON_FEATURES" --tests --benches --examples -- -Dwarnings 17 | else 18 | echo "skipping cargo clippy on non-MSRV CI run. MSRV: $MSRV, rustc: $RUSTC_VERSION" 19 | fi 20 | $TEST $TARGET --no-default-features 21 | # Custom set of crypto without std 22 | $TEST $TARGET --no-default-features --features "default-resolver use-curve25519 use-blake2 use-chacha20poly1305" 23 | # Custom set of crypto with std 24 | $TEST $TARGET --no-default-features --features "default-resolver use-curve25519 use-sha2 use-chacha20poly1305" 25 | $TEST $TARGET --features "ring-resolver $COMMON_FEATURES" 26 | $TEST $TARGET --features "ring-accelerated $COMMON_FEATURES" 27 | -------------------------------------------------------------------------------- /clippy.toml: -------------------------------------------------------------------------------- 1 | msrv = "1.81.0" 2 | -------------------------------------------------------------------------------- /docs/CNAME: -------------------------------------------------------------------------------- 1 | snow.rs 2 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /examples/oneway.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr( 2 | not(any(feature = "default-resolver", feature = "ring-accelerated",)), 3 | allow(dead_code, unused_extern_crates, unused_imports) 4 | )] 5 | //! This is a barebones TCP Client/Server that establishes a `Noise_X` session, and sends 6 | //! an important message across the wire. 7 | //! 8 | //! # Usage 9 | //! Run the server a-like-a-so `cargo run --example oneway -- -s`, then run the client 10 | //! as `cargo run --example oneway` to see the magic happen. 11 | 12 | use hex::FromHex; 13 | use snow::{params::NoiseParams, Builder, Keypair}; 14 | use std::{ 15 | io::{self, Read, Write}, 16 | net::{TcpListener, TcpStream}, 17 | sync::LazyLock, 18 | }; 19 | 20 | static SECRET: &[u8; 32] = b"i don't care for fidget spinners"; 21 | static PARAMS: LazyLock = 22 | LazyLock::new(|| "Noise_Xpsk1_25519_ChaChaPoly_BLAKE2s".parse().unwrap()); 23 | // The responder key is static in this example because the X pattern means 24 | // the initiator has pre-handshake knowledge of the responder's public key 25 | // (and of course both share the same psk `SECRET`) 26 | static RESPONDER: LazyLock = LazyLock::new(|| Keypair { 27 | private: Vec::from_hex("52fbe3721d1adbe312d270ca2db5ce5bd39ddc206075f3a8f06d422619c8eb5d") 28 | .expect("valid hex"), 29 | public: Vec::from_hex("435ce8a8415ccd44de5e207581ac7207b416683028bcaecc9eb38d944e6f900c") 30 | .expect("valid hex"), 31 | }); 32 | 33 | #[cfg(any(feature = "default-resolver", feature = "ring-accelerated"))] 34 | fn main() { 35 | let server_mode = 36 | std::env::args().next_back().map_or(true, |arg| arg == "-s" || arg == "--server"); 37 | 38 | if server_mode { 39 | run_server(&RESPONDER.private, SECRET); 40 | } else { 41 | run_client(&RESPONDER.public, SECRET); 42 | } 43 | println!("all done."); 44 | } 45 | 46 | #[cfg(any(feature = "default-resolver", feature = "ring-accelerated"))] 47 | fn run_server(private_key: &[u8], psk: &[u8; 32]) { 48 | let mut buf = vec![0_u8; 65535]; 49 | 50 | // Initialize our responder using a builder. 51 | let builder = Builder::new(PARAMS.clone()); 52 | let mut noise = builder 53 | .local_private_key(private_key) 54 | .unwrap() 55 | .psk(1, psk) 56 | .unwrap() 57 | .build_responder() 58 | .unwrap(); 59 | 60 | // Wait on our client's arrival... 61 | println!("listening on 127.0.0.1:9999"); 62 | let (mut stream, _) = TcpListener::bind("127.0.0.1:9999").unwrap().accept().unwrap(); 63 | 64 | // <- e, es, s, ss 65 | noise.read_message(&recv(&mut stream).unwrap(), &mut buf).unwrap(); 66 | 67 | // This is a oneway handshake - the server does not have to send anything 68 | 69 | // The remote static key (of the initiator) is now known 70 | let client = hex::encode(noise.get_remote_static().unwrap()); 71 | 72 | // Transition the state machine into transport mode now that the handshake is complete. 73 | let mut transport = noise.into_transport_mode().unwrap(); 74 | 75 | while let Ok(msg) = recv(&mut stream) { 76 | let len = transport.read_message(&msg, &mut buf).unwrap(); 77 | println!("{client} said: {}", String::from_utf8_lossy(&buf[..len])); 78 | } 79 | println!("connection closed."); 80 | } 81 | 82 | #[cfg(any(feature = "default-resolver", feature = "ring-accelerated"))] 83 | fn run_client(responder_public_key: &[u8], psk: &[u8; 32]) { 84 | let mut buf = vec![0_u8; 65535]; 85 | 86 | // Initialize our initiator using a builder. 87 | let builder = Builder::new(PARAMS.clone()); 88 | let private_key = &builder.generate_keypair().unwrap().private; 89 | let mut noise = builder 90 | .local_private_key(private_key) 91 | .unwrap() 92 | .remote_public_key(responder_public_key) 93 | .unwrap() 94 | .psk(1, psk) 95 | .unwrap() 96 | .build_initiator() 97 | .unwrap(); 98 | 99 | // Connect to our server, which is hopefully listening. 100 | let mut stream = TcpStream::connect("127.0.0.1:9999").unwrap(); 101 | println!("connected..."); 102 | 103 | // -> e, es, s, ss 104 | let len = noise.write_message(&[], &mut buf).unwrap(); 105 | send(&mut stream, &buf[..len]); 106 | 107 | // This is a oneway handshake - the respnder must not send anything 108 | 109 | let mut transport = noise.into_transport_mode().unwrap(); 110 | println!("session established..."); 111 | 112 | // Get to the important business of sending secured data. 113 | for _ in 0..10 { 114 | let len = transport.write_message(b"HACK THE PLANET", &mut buf).unwrap(); 115 | send(&mut stream, &buf[..len]); 116 | } 117 | println!("notified server of intent to hack planet."); 118 | } 119 | 120 | /// Hyper-basic stream transport receiver. 16-bit BE size followed by payload. 121 | fn recv(stream: &mut TcpStream) -> io::Result> { 122 | let mut msg_len_buf = [0_u8; 2]; 123 | stream.read_exact(&mut msg_len_buf)?; 124 | let msg_len = usize::from(u16::from_be_bytes(msg_len_buf)); 125 | let mut msg = vec![0_u8; msg_len]; 126 | stream.read_exact(&mut msg[..])?; 127 | Ok(msg) 128 | } 129 | 130 | /// Hyper-basic stream transport sender. 16-bit BE size followed by payload. 131 | fn send(stream: &mut TcpStream, buf: &[u8]) { 132 | let len = u16::try_from(buf.len()).expect("message too large"); 133 | stream.write_all(&len.to_be_bytes()).unwrap(); 134 | stream.write_all(buf).unwrap(); 135 | } 136 | 137 | #[cfg(not(any(feature = "default-resolver", feature = "ring-accelerated")))] 138 | fn main() { 139 | panic!("Example must be compiled with some cryptographic provider."); 140 | } 141 | -------------------------------------------------------------------------------- /examples/simple.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr( 2 | not(any(feature = "default-resolver", feature = "ring-accelerated",)), 3 | allow(dead_code, unused_extern_crates, unused_imports) 4 | )] 5 | //! This is a barebones TCP Client/Server that establishes a `Noise_XX` session, and sends 6 | //! an important message across the wire. 7 | //! 8 | //! # Usage 9 | //! Run the server a-like-a-so `cargo run --example simple -- -s`, then run the client 10 | //! as `cargo run --example simple` to see the magic happen. 11 | 12 | use snow::{params::NoiseParams, Builder}; 13 | use std::{ 14 | io::{self, Read, Write}, 15 | net::{TcpListener, TcpStream}, 16 | sync::LazyLock, 17 | }; 18 | 19 | static SECRET: &[u8; 32] = b"i don't care for fidget spinners"; 20 | static PARAMS: LazyLock = 21 | LazyLock::new(|| "Noise_XXpsk3_25519_ChaChaPoly_BLAKE2s".parse().unwrap()); 22 | 23 | #[cfg(any(feature = "default-resolver", feature = "ring-accelerated"))] 24 | fn main() { 25 | let server_mode = 26 | std::env::args().next_back().map_or(true, |arg| arg == "-s" || arg == "--server"); 27 | 28 | if server_mode { 29 | run_server(); 30 | } else { 31 | run_client(); 32 | } 33 | println!("all done."); 34 | } 35 | 36 | #[cfg(any(feature = "default-resolver", feature = "ring-accelerated"))] 37 | fn run_server() { 38 | let mut buf = vec![0_u8; 65535]; 39 | 40 | // Initialize our responder using a builder. 41 | let builder = Builder::new(PARAMS.clone()); 42 | let static_key = builder.generate_keypair().unwrap().private; 43 | let mut noise = builder 44 | .local_private_key(&static_key) 45 | .unwrap() 46 | .psk(3, SECRET) 47 | .unwrap() 48 | .build_responder() 49 | .unwrap(); 50 | 51 | // Wait on our client's arrival... 52 | println!("listening on 127.0.0.1:9999"); 53 | let (mut stream, _) = TcpListener::bind("127.0.0.1:9999").unwrap().accept().unwrap(); 54 | 55 | // <- e 56 | noise.read_message(&recv(&mut stream).unwrap(), &mut buf).unwrap(); 57 | 58 | // -> e, ee, s, es 59 | let len = noise.write_message(&[], &mut buf).unwrap(); 60 | send(&mut stream, &buf[..len]); 61 | 62 | // <- s, se 63 | noise.read_message(&recv(&mut stream).unwrap(), &mut buf).unwrap(); 64 | 65 | // Transition the state machine into transport mode now that the handshake is complete. 66 | let mut transport = noise.into_transport_mode().unwrap(); 67 | 68 | while let Ok(msg) = recv(&mut stream) { 69 | let len = transport.read_message(&msg, &mut buf).unwrap(); 70 | println!("client said: {}", String::from_utf8_lossy(&buf[..len])); 71 | } 72 | println!("connection closed."); 73 | } 74 | 75 | #[cfg(any(feature = "default-resolver", feature = "ring-accelerated"))] 76 | fn run_client() { 77 | let mut buf = vec![0_u8; 65535]; 78 | 79 | // Initialize our initiator using a builder. 80 | let builder = Builder::new(PARAMS.clone()); 81 | let static_key = builder.generate_keypair().unwrap().private; 82 | let mut noise = builder 83 | .local_private_key(&static_key) 84 | .unwrap() 85 | .psk(3, SECRET) 86 | .unwrap() 87 | .build_initiator() 88 | .unwrap(); 89 | 90 | // Connect to our server, which is hopefully listening. 91 | let mut stream = TcpStream::connect("127.0.0.1:9999").unwrap(); 92 | println!("connected..."); 93 | 94 | // -> e 95 | let len = noise.write_message(&[], &mut buf).unwrap(); 96 | send(&mut stream, &buf[..len]); 97 | 98 | // <- e, ee, s, es 99 | noise.read_message(&recv(&mut stream).unwrap(), &mut buf).unwrap(); 100 | 101 | // -> s, se 102 | let len = noise.write_message(&[], &mut buf).unwrap(); 103 | send(&mut stream, &buf[..len]); 104 | 105 | let mut transport = noise.into_transport_mode().unwrap(); 106 | println!("session established..."); 107 | 108 | // Get to the important business of sending secured data. 109 | for _ in 0..10 { 110 | let len = transport.write_message(b"HACK THE PLANET", &mut buf).unwrap(); 111 | send(&mut stream, &buf[..len]); 112 | } 113 | println!("notified server of intent to hack planet."); 114 | } 115 | 116 | /// Hyper-basic stream transport receiver. 16-bit BE size followed by payload. 117 | fn recv(stream: &mut TcpStream) -> io::Result> { 118 | let mut msg_len_buf = [0_u8; 2]; 119 | stream.read_exact(&mut msg_len_buf)?; 120 | let msg_len = usize::from(u16::from_be_bytes(msg_len_buf)); 121 | let mut msg = vec![0_u8; msg_len]; 122 | stream.read_exact(&mut msg[..])?; 123 | Ok(msg) 124 | } 125 | 126 | /// Hyper-basic stream transport sender. 16-bit BE size followed by payload. 127 | fn send(stream: &mut TcpStream, buf: &[u8]) { 128 | let len = u16::try_from(buf.len()).expect("message too large"); 129 | stream.write_all(&len.to_be_bytes()).unwrap(); 130 | stream.write_all(buf).unwrap(); 131 | } 132 | 133 | #[cfg(not(any(feature = "default-resolver", feature = "ring-accelerated")))] 134 | fn main() { 135 | panic!("Example must be compiled with some cryptographic provider."); 136 | } 137 | -------------------------------------------------------------------------------- /hfuzz/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fuzz" 3 | version = "0.1.0" 4 | authors = ["Jake McGinty "] 5 | 6 | [dependencies] 7 | honggfuzz = "0.5" 8 | snow = { path = "../" } 9 | lazy_static = "1.3" 10 | -------------------------------------------------------------------------------- /hfuzz/src/bin/handshake_reader.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] extern crate honggfuzz; 2 | #[macro_use] extern crate lazy_static; 3 | extern crate snow; 4 | 5 | use snow::params::NoiseParams; 6 | 7 | static SECRET: &'static [u8] = b"i don't care for fidget spinners"; 8 | lazy_static! { 9 | static ref PARAMS: NoiseParams = "Noise_NN_25519_ChaChaPoly_BLAKE2s".parse().unwrap(); 10 | } 11 | 12 | fn main() { 13 | let mut out_buf = vec![0u8; 128 * 1024 * 1024]; 14 | loop { 15 | fuzz!(|data: &[u8]| { 16 | let builder = snow::Builder::new(PARAMS.clone()); 17 | let mut noise = builder.build_responder().unwrap(); 18 | 19 | let _ = noise.read_message(data, &mut out_buf); 20 | }); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /hfuzz/src/bin/handshake_writer.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] extern crate honggfuzz; 2 | #[macro_use] extern crate lazy_static; 3 | extern crate snow; 4 | 5 | use snow::params::NoiseParams; 6 | 7 | static SECRET: &'static [u8] = b"i don't care for fidget spinners"; 8 | lazy_static! { 9 | static ref PARAMS: NoiseParams = "Noise_NN_25519_ChaChaPoly_BLAKE2s".parse().unwrap(); 10 | } 11 | 12 | fn main() { 13 | let mut out_buf = vec![0u8; 128 * 1024 * 1024]; 14 | loop { 15 | fuzz!(|data: &[u8]| { 16 | let builder = snow::Builder::new(PARAMS.clone()); 17 | let mut noise = builder.build_initiator().unwrap(); 18 | 19 | let _ = noise.write_message(data, &mut out_buf); 20 | }); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /hfuzz/src/bin/params.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] extern crate honggfuzz; 2 | extern crate snow; 3 | 4 | fn main() { 5 | loop { 6 | fuzz!(|data: &[u8]| { 7 | if let Ok(s) = String::from_utf8(data.to_vec()){ 8 | if let Ok(p) = s.parse() { 9 | let builder = snow::Builder::new(p); 10 | let _ = builder.build_initiator(); 11 | } 12 | } 13 | }); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /hfuzz/src/bin/transport.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] extern crate honggfuzz; 2 | #[macro_use] extern crate lazy_static; 3 | extern crate snow; 4 | 5 | use snow::params::NoiseParams; 6 | 7 | static SECRET: &'static [u8] = b"i don't care for fidget spinners"; 8 | lazy_static! { 9 | static ref PARAMS: NoiseParams = "Noise_NN_25519_ChaChaPoly_BLAKE2s".parse().unwrap(); 10 | } 11 | 12 | fn main() { 13 | let mut out_buf = vec![0u8; 128 * 1024 * 1024]; 14 | let mut initiator = snow::Builder::new(PARAMS.clone()).build_initiator().unwrap(); 15 | let mut responder = snow::Builder::new(PARAMS.clone()).build_responder().unwrap(); 16 | 17 | let len = initiator.write_message(&[], &mut out_buf).unwrap(); 18 | responder.read_message(&out_buf[..len], &mut []); 19 | let len = responder.write_message(&[], &mut out_buf).unwrap(); 20 | initiator.read_message(&out_buf[..len], &mut []); 21 | 22 | let mut responder = responder.into_transport_mode().unwrap(); 23 | let mut initiator = initiator.into_transport_mode().unwrap(); 24 | 25 | loop { 26 | fuzz!(|data: &[u8]| { 27 | let _ = initiator.write_message(data, &mut out_buf); 28 | let _ = initiator.read_message(data, &mut out_buf); 29 | }); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | indent_style = "Block" 2 | use_small_heuristics="Max" 3 | imports_granularity="Crate" 4 | match_block_trailing_comma = true 5 | reorder_impl_items = true 6 | reorder_imports = true 7 | use_field_init_shorthand = true 8 | use_try_shorthand = true 9 | normalize_doc_attributes = true 10 | enum_discrim_align_threshold = 40 11 | struct_field_align_threshold = 40 12 | -------------------------------------------------------------------------------- /src/builder.rs: -------------------------------------------------------------------------------- 1 | use core::fmt::Debug; 2 | 3 | #[cfg(not(feature = "std"))] 4 | use alloc::{boxed::Box, vec, vec::Vec}; 5 | 6 | #[cfg(feature = "hfs")] 7 | use crate::params::HandshakeModifier; 8 | use crate::{ 9 | cipherstate::{CipherState, CipherStates}, 10 | constants::{MAXDHLEN, PSKLEN}, 11 | error::{Error, InitStage, Prerequisite}, 12 | handshakestate::HandshakeState, 13 | params::NoiseParams, 14 | resolvers::{BoxedCryptoResolver, CryptoResolver}, 15 | utils::Toggle, 16 | }; 17 | use subtle::ConstantTimeEq; 18 | 19 | /// The maximum number of PSKs we will allocate for. 20 | const MAX_PSKS: usize = 10; 21 | 22 | /// A keypair object returned by [`Builder::generate_keypair()`] 23 | /// 24 | /// [`generate_keypair()`]: #method.generate_keypair 25 | pub struct Keypair { 26 | /// The private asymmetric key 27 | pub private: Vec, 28 | /// The public asymmetric key 29 | pub public: Vec, 30 | } 31 | 32 | impl PartialEq for Keypair { 33 | fn eq(&self, other: &Keypair) -> bool { 34 | let priv_eq = self.private.ct_eq(&other.private); 35 | let pub_eq = self.public.ct_eq(&other.public); 36 | 37 | (priv_eq & pub_eq).into() 38 | } 39 | } 40 | 41 | /// Generates a [`HandshakeState`] and also validates that all the prerequisites for 42 | /// the given parameters are satisfied. 43 | /// 44 | /// # Examples 45 | /// 46 | /// ``` 47 | /// # fn main() -> Result<(), Box> { 48 | /// # use snow::Builder; 49 | /// # let my_long_term_key = [0u8; 32]; 50 | /// # let their_pub_key = [0u8; 32]; 51 | /// # #[cfg(any(feature = "default-resolver-crypto", feature = "ring-accelerated"))] 52 | /// let noise = Builder::new("Noise_XX_25519_ChaChaPoly_BLAKE2s".parse()?) 53 | /// .local_private_key(&my_long_term_key)? 54 | /// .remote_public_key(&their_pub_key)? 55 | /// .prologue("noise is just swell".as_bytes())? 56 | /// .build_initiator()?; 57 | /// # Ok(()) 58 | /// # } 59 | /// ``` 60 | pub struct Builder<'builder> { 61 | params: NoiseParams, 62 | resolver: BoxedCryptoResolver, 63 | s: Option<&'builder [u8]>, 64 | e_fixed: Option<&'builder [u8]>, 65 | rs: Option<&'builder [u8]>, 66 | psks: [Option<&'builder [u8; 32]>; MAX_PSKS], 67 | plog: Option<&'builder [u8]>, 68 | } 69 | 70 | impl Debug for Builder<'_> { 71 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 72 | f.debug_struct("Builder").field("params", &self.params.name).finish_non_exhaustive() 73 | } 74 | } 75 | 76 | impl<'builder> Builder<'builder> { 77 | /// Create a Builder with the default crypto resolver. 78 | #[cfg(all(feature = "default-resolver", not(feature = "ring-accelerated")))] 79 | #[must_use] 80 | pub fn new(params: NoiseParams) -> Self { 81 | use crate::resolvers::DefaultResolver; 82 | 83 | Self::with_resolver(params, Box::new(DefaultResolver)) 84 | } 85 | 86 | /// Create a Builder with the ring resolver and default resolver as a fallback. 87 | #[cfg(feature = "ring-accelerated")] 88 | pub fn new(params: NoiseParams) -> Self { 89 | use crate::resolvers::{DefaultResolver, FallbackResolver, RingResolver}; 90 | 91 | Self::with_resolver( 92 | params, 93 | Box::new(FallbackResolver::new(Box::new(RingResolver), Box::new(DefaultResolver))), 94 | ) 95 | } 96 | 97 | /// Create a Builder with a custom crypto resolver. 98 | #[must_use] 99 | pub fn with_resolver(params: NoiseParams, resolver: BoxedCryptoResolver) -> Self { 100 | Builder { params, resolver, s: None, e_fixed: None, rs: None, plog: None, psks: [None; 10] } 101 | } 102 | 103 | /// Specify a PSK (only used with `NoisePSK` base parameter) 104 | /// 105 | /// # Errors 106 | /// * `InitError(InitStage::ValidatePskPosition)` if the location is a number larger than 107 | /// allowed. 108 | /// * `InitError(InitStage::ParameterOverwrite)` if this method has been called previously. 109 | pub fn psk(mut self, location: u8, key: &'builder [u8; PSKLEN]) -> Result { 110 | let index = usize::from(location); 111 | if index >= MAX_PSKS { 112 | Err(InitStage::ValidatePskPosition.into()) 113 | } else if self.psks[index].is_some() { 114 | Err(InitStage::ParameterOverwrite.into()) 115 | } else { 116 | self.psks[index] = Some(key); 117 | Ok(self) 118 | } 119 | } 120 | 121 | /// Your static private key (can be generated with [`generate_keypair()`]). 122 | /// 123 | /// [`generate_keypair()`]: #method.generate_keypair 124 | /// 125 | /// # Errors 126 | /// * `InitError(InitStage::ParameterOverwrite)` if this method has been called previously. 127 | pub fn local_private_key(mut self, key: &'builder [u8]) -> Result { 128 | if self.s.is_some() { 129 | Err(InitStage::ParameterOverwrite.into()) 130 | } else { 131 | self.s = Some(key); 132 | Ok(self) 133 | } 134 | } 135 | 136 | #[doc(hidden)] 137 | #[must_use] 138 | pub fn fixed_ephemeral_key_for_testing_only(mut self, key: &'builder [u8]) -> Self { 139 | self.e_fixed = Some(key); 140 | self 141 | } 142 | 143 | /// Arbitrary data to be hashed in to the handshake hash value. 144 | /// 145 | /// This may only be set once 146 | /// 147 | /// # Errors 148 | /// * `InitError(InitStage::ParameterOverwrite)` if this method has been called previously. 149 | pub fn prologue(mut self, key: &'builder [u8]) -> Result { 150 | if self.plog.is_some() { 151 | Err(InitStage::ParameterOverwrite.into()) 152 | } else { 153 | self.plog = Some(key); 154 | Ok(self) 155 | } 156 | } 157 | 158 | /// The responder's static public key. 159 | /// 160 | /// # Errors 161 | /// * `InitError(InitStage::ParameterOverwrite)` if this method has been called previously. 162 | pub fn remote_public_key(mut self, pub_key: &'builder [u8]) -> Result { 163 | if self.rs.is_some() { 164 | Err(InitStage::ParameterOverwrite.into()) 165 | } else { 166 | self.rs = Some(pub_key); 167 | Ok(self) 168 | } 169 | } 170 | 171 | // TODO: performance issue w/ creating a new RNG and DH instance per call. 172 | /// Generate a new asymmetric keypair (for use as a static key). 173 | /// 174 | /// # Errors 175 | /// * `InitError(InitStage::GetRngImpl)` if the RNG implementation failed to resolve. 176 | /// * `InitError(InitStage::GetDhImpl)` if the DH implementation failed to resolve. 177 | pub fn generate_keypair(&self) -> Result { 178 | let mut rng = self.resolver.resolve_rng().ok_or(InitStage::GetRngImpl)?; 179 | let mut dh = self.resolver.resolve_dh(&self.params.dh).ok_or(InitStage::GetDhImpl)?; 180 | let mut private = vec![0_u8; dh.priv_len()]; 181 | let mut public = vec![0_u8; dh.pub_len()]; 182 | dh.generate(&mut *rng); 183 | 184 | private.copy_from_slice(dh.privkey()); 185 | public.copy_from_slice(dh.pubkey()); 186 | 187 | Ok(Keypair { private, public }) 188 | } 189 | 190 | /// Build a [`HandshakeState`] for the side who will initiate the handshake (send the first message) 191 | /// 192 | /// # Errors 193 | /// * `InitError(InitStage::GetRngImpl)` if the RNG implementation failed to resolve. 194 | /// * `InitError(InitStage::GetDhImpl)` if the DH implementation failed to resolve. 195 | pub fn build_initiator(self) -> Result { 196 | self.build(true) 197 | } 198 | 199 | /// Build a [`HandshakeState`] for the side who will be responder (receive the first message) 200 | /// 201 | /// # Errors 202 | /// An `InitError(InitStage)` variant will be returned for various issues in the building of a 203 | /// usable `HandshakeState`. See `InitStage` for further details. 204 | pub fn build_responder(self) -> Result { 205 | self.build(false) 206 | } 207 | 208 | fn build(self, initiator: bool) -> Result { 209 | if self.s.is_none() && self.params.handshake.pattern.needs_local_static_key(initiator) { 210 | return Err(Prerequisite::LocalPrivateKey.into()); 211 | } 212 | 213 | if self.rs.is_none() && self.params.handshake.pattern.need_known_remote_pubkey(initiator) { 214 | return Err(Prerequisite::RemotePublicKey.into()); 215 | } 216 | 217 | let rng = self.resolver.resolve_rng().ok_or(InitStage::GetRngImpl)?; 218 | let cipher = 219 | self.resolver.resolve_cipher(&self.params.cipher).ok_or(InitStage::GetCipherImpl)?; 220 | let hash = self.resolver.resolve_hash(&self.params.hash).ok_or(InitStage::GetHashImpl)?; 221 | let mut s_dh = self.resolver.resolve_dh(&self.params.dh).ok_or(InitStage::GetDhImpl)?; 222 | let mut e_dh = self.resolver.resolve_dh(&self.params.dh).ok_or(InitStage::GetDhImpl)?; 223 | let cipher1 = 224 | self.resolver.resolve_cipher(&self.params.cipher).ok_or(InitStage::GetCipherImpl)?; 225 | let cipher2 = 226 | self.resolver.resolve_cipher(&self.params.cipher).ok_or(InitStage::GetCipherImpl)?; 227 | let handshake_cipherstate = CipherState::new(cipher); 228 | let cipherstates = CipherStates::new(CipherState::new(cipher1), CipherState::new(cipher2))?; 229 | 230 | let s = match self.s { 231 | Some(k) => { 232 | (*s_dh).set(k); 233 | Toggle::on(s_dh) 234 | }, 235 | None => Toggle::off(s_dh), 236 | }; 237 | 238 | if let Some(fixed_k) = self.e_fixed { 239 | (*e_dh).set(fixed_k); 240 | } 241 | let e = Toggle::off(e_dh); 242 | 243 | let mut rs_buf = [0_u8; MAXDHLEN]; 244 | let rs = match self.rs { 245 | Some(v) => { 246 | rs_buf[..v.len()].copy_from_slice(v); 247 | Toggle::on(rs_buf) 248 | }, 249 | None => Toggle::off(rs_buf), 250 | }; 251 | 252 | let re = Toggle::off([0_u8; MAXDHLEN]); 253 | 254 | let mut psks = [None::<[u8; PSKLEN]>; 10]; 255 | for (i, psk) in self.psks.iter().enumerate() { 256 | if let Some(key) = *psk { 257 | if key.len() != PSKLEN { 258 | return Err(InitStage::ValidatePskLengths.into()); 259 | } 260 | let mut k = [0_u8; PSKLEN]; 261 | k.copy_from_slice(key); 262 | psks[i] = Some(k); 263 | } 264 | } 265 | 266 | let mut hs = HandshakeState::new( 267 | rng, 268 | handshake_cipherstate, 269 | hash, 270 | s, 271 | e, 272 | self.e_fixed.is_some(), 273 | rs, 274 | re, 275 | initiator, 276 | self.params, 277 | &psks, 278 | self.plog.unwrap_or(&[]), 279 | cipherstates, 280 | )?; 281 | Self::resolve_kem(self.resolver, &mut hs)?; 282 | Ok(hs) 283 | } 284 | 285 | #[cfg(not(feature = "hfs"))] 286 | #[allow(clippy::unnecessary_wraps)] 287 | #[allow(clippy::needless_pass_by_value)] 288 | fn resolve_kem(_: Box, _: &mut HandshakeState) -> Result<(), Error> { 289 | // HFS is disabled, return nothing 290 | Ok(()) 291 | } 292 | 293 | #[cfg(feature = "hfs")] 294 | #[allow(clippy::needless_pass_by_value)] 295 | fn resolve_kem( 296 | resolver: Box, 297 | hs: &mut HandshakeState, 298 | ) -> Result<(), Error> { 299 | if hs.params.handshake.modifiers.list.contains(&HandshakeModifier::Hfs) { 300 | if let Some(kem_choice) = hs.params.kem { 301 | let kem = resolver.resolve_kem(&kem_choice).ok_or(InitStage::GetKemImpl)?; 302 | hs.set_kem(kem); 303 | } else { 304 | return Err(InitStage::GetKemImpl.into()); 305 | } 306 | } 307 | Ok(()) 308 | } 309 | } 310 | 311 | #[cfg(test)] 312 | #[cfg(all( 313 | feature = "std", 314 | any(feature = "default-resolver-crypto", feature = "ring-accelerated") 315 | ))] 316 | mod tests { 317 | use super::*; 318 | type TestResult = Result<(), Box>; 319 | 320 | #[test] 321 | fn test_builder() -> TestResult { 322 | let _noise = Builder::new("Noise_NN_25519_ChaChaPoly_SHA256".parse()?) 323 | .prologue(&[2, 2, 2, 2, 2, 2, 2, 2])? 324 | .local_private_key(&[0_u8; 32])? 325 | .build_initiator()?; 326 | Ok(()) 327 | } 328 | 329 | #[test] 330 | fn test_builder_keygen() -> TestResult { 331 | let builder = Builder::new("Noise_NN_25519_ChaChaPoly_SHA256".parse()?); 332 | let key1 = builder.generate_keypair(); 333 | let key2 = builder.generate_keypair(); 334 | assert!(key1? != key2?); 335 | Ok(()) 336 | } 337 | 338 | #[test] 339 | fn test_builder_bad_spec() { 340 | let params: ::core::result::Result = 341 | "Noise_NK_25519_ChaChaPoly_BLAH256".parse(); 342 | 343 | assert!(params.is_err(), "NoiseParams should have failed"); 344 | } 345 | 346 | #[test] 347 | fn test_builder_missing_prereqs() -> TestResult { 348 | let noise = Builder::new("Noise_NK_25519_ChaChaPoly_SHA256".parse()?) 349 | .prologue(&[2, 2, 2, 2, 2, 2, 2, 2])? 350 | .local_private_key(&[0_u8; 32])? 351 | .build_initiator(); // missing remote key, should result in Err 352 | 353 | assert!(noise.is_err(), "builder should have failed on build"); 354 | Ok(()) 355 | } 356 | 357 | #[test] 358 | fn test_builder_param_overwrite() -> TestResult { 359 | fn build_builder<'a>() -> Result, Error> { 360 | Builder::new("Noise_NNpsk0_25519_ChaChaPoly_SHA256".parse()?) 361 | .prologue(&[2_u8; 10])? 362 | .psk(0, &[0_u8; 32])? 363 | .local_private_key(&[0_u8; 32])? 364 | .remote_public_key(&[1_u8; 32]) 365 | } 366 | 367 | assert_eq!( 368 | build_builder()?.prologue(&[1_u8; 10]).unwrap_err(), 369 | Error::Init(InitStage::ParameterOverwrite) 370 | ); 371 | assert!(build_builder()?.psk(1, &[1_u8; 32]).is_ok()); 372 | assert_eq!( 373 | build_builder()?.psk(0, &[1_u8; 32]).unwrap_err(), 374 | Error::Init(InitStage::ParameterOverwrite) 375 | ); 376 | assert_eq!( 377 | build_builder()?.local_private_key(&[1_u8; 32]).unwrap_err(), 378 | Error::Init(InitStage::ParameterOverwrite) 379 | ); 380 | assert_eq!( 381 | build_builder()?.remote_public_key(&[1_u8; 32]).unwrap_err(), 382 | Error::Init(InitStage::ParameterOverwrite) 383 | ); 384 | Ok(()) 385 | } 386 | 387 | #[test] 388 | fn test_partialeq_impl() { 389 | let keypair_1 = Keypair { private: vec![0x01; 32], public: vec![0x01; 32] }; 390 | 391 | let mut keypair_2 = Keypair { private: vec![0x01; 32], public: vec![0x01; 32] }; 392 | 393 | // If both private and public are the same, return true 394 | assert!(keypair_1 == keypair_2); 395 | 396 | // If either public or private are different, return false 397 | 398 | // Wrong private 399 | keypair_2.private = vec![0x50; 32]; 400 | assert!(keypair_1 != keypair_2); 401 | // Reset to original 402 | keypair_2.private = vec![0x01; 32]; 403 | // Wrong public 404 | keypair_2.public = vec![0x50; 32]; 405 | assert!(keypair_1 != keypair_2); 406 | } 407 | } 408 | -------------------------------------------------------------------------------- /src/cipherstate.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(feature = "std"))] 2 | use alloc::boxed::Box; 3 | 4 | use crate::{ 5 | constants::{CIPHERKEYLEN, TAGLEN}, 6 | error::{Error, InitStage, StateProblem}, 7 | types::Cipher, 8 | }; 9 | 10 | pub(crate) struct CipherState { 11 | cipher: Box, 12 | n: u64, 13 | has_key: bool, 14 | } 15 | 16 | impl CipherState { 17 | pub fn new(cipher: Box) -> Self { 18 | Self { cipher, n: 0, has_key: false } 19 | } 20 | 21 | pub fn name(&self) -> &'static str { 22 | self.cipher.name() 23 | } 24 | 25 | pub fn set(&mut self, key: &[u8; CIPHERKEYLEN], n: u64) { 26 | self.cipher.set(key); 27 | self.n = n; 28 | self.has_key = true; 29 | } 30 | 31 | pub fn encrypt_ad( 32 | &mut self, 33 | authtext: &[u8], 34 | plaintext: &[u8], 35 | out: &mut [u8], 36 | ) -> Result { 37 | if !self.has_key { 38 | return Err(StateProblem::MissingKeyMaterial.into()); 39 | } 40 | 41 | validate_nonce(self.n)?; 42 | let len = self.cipher.encrypt(self.n, authtext, plaintext, out); 43 | 44 | // We have validated this will not wrap around. 45 | self.n += 1; 46 | 47 | Ok(len) 48 | } 49 | 50 | pub fn decrypt_ad( 51 | &mut self, 52 | authtext: &[u8], 53 | ciphertext: &[u8], 54 | out: &mut [u8], 55 | ) -> Result { 56 | if (ciphertext.len() < TAGLEN) || out.len() < (ciphertext.len() - TAGLEN) { 57 | return Err(Error::Decrypt); 58 | } 59 | 60 | if !self.has_key { 61 | return Err(StateProblem::MissingKeyMaterial.into()); 62 | } 63 | 64 | validate_nonce(self.n)?; 65 | let len = self.cipher.decrypt(self.n, authtext, ciphertext, out)?; 66 | 67 | // We have validated this will not wrap around. 68 | self.n += 1; 69 | 70 | Ok(len) 71 | } 72 | 73 | pub fn encrypt(&mut self, plaintext: &[u8], out: &mut [u8]) -> Result { 74 | self.encrypt_ad(&[0_u8; 0], plaintext, out) 75 | } 76 | 77 | pub fn decrypt(&mut self, ciphertext: &[u8], out: &mut [u8]) -> Result { 78 | self.decrypt_ad(&[0_u8; 0], ciphertext, out) 79 | } 80 | 81 | pub fn rekey(&mut self) { 82 | self.cipher.rekey(); 83 | } 84 | 85 | pub fn rekey_manually(&mut self, key: &[u8; CIPHERKEYLEN]) { 86 | self.cipher.set(key); 87 | } 88 | 89 | pub fn nonce(&self) -> u64 { 90 | self.n 91 | } 92 | 93 | pub fn set_nonce(&mut self, nonce: u64) { 94 | self.n = nonce; 95 | } 96 | } 97 | 98 | pub(crate) struct CipherStates(pub CipherState, pub CipherState); 99 | 100 | impl CipherStates { 101 | pub fn new(initiator: CipherState, responder: CipherState) -> Result { 102 | if initiator.name() != responder.name() { 103 | return Err(InitStage::ValidateCipherTypes.into()); 104 | } 105 | 106 | Ok(CipherStates(initiator, responder)) 107 | } 108 | 109 | pub fn rekey_initiator(&mut self) { 110 | self.0.rekey(); 111 | } 112 | 113 | pub fn rekey_initiator_manually(&mut self, key: &[u8; CIPHERKEYLEN]) { 114 | self.0.rekey_manually(key); 115 | } 116 | 117 | pub fn rekey_responder(&mut self) { 118 | self.1.rekey(); 119 | } 120 | 121 | pub fn rekey_responder_manually(&mut self, key: &[u8; CIPHERKEYLEN]) { 122 | self.1.rekey_manually(key); 123 | } 124 | } 125 | 126 | pub(crate) struct StatelessCipherState { 127 | cipher: Box, 128 | has_key: bool, 129 | } 130 | 131 | impl StatelessCipherState { 132 | pub fn encrypt_ad( 133 | &self, 134 | nonce: u64, 135 | authtext: &[u8], 136 | plaintext: &[u8], 137 | out: &mut [u8], 138 | ) -> Result { 139 | if !self.has_key { 140 | return Err(StateProblem::MissingKeyMaterial.into()); 141 | } 142 | 143 | validate_nonce(nonce)?; 144 | 145 | Ok(self.cipher.encrypt(nonce, authtext, plaintext, out)) 146 | } 147 | 148 | pub fn decrypt_ad( 149 | &self, 150 | nonce: u64, 151 | authtext: &[u8], 152 | ciphertext: &[u8], 153 | out: &mut [u8], 154 | ) -> Result { 155 | if (ciphertext.len() < TAGLEN) || out.len() < (ciphertext.len() - TAGLEN) { 156 | return Err(Error::Decrypt); 157 | } 158 | 159 | if !self.has_key { 160 | return Err(StateProblem::MissingKeyMaterial.into()); 161 | } 162 | 163 | validate_nonce(nonce)?; 164 | 165 | self.cipher.decrypt(nonce, authtext, ciphertext, out) 166 | } 167 | 168 | pub fn encrypt(&self, nonce: u64, plaintext: &[u8], out: &mut [u8]) -> Result { 169 | self.encrypt_ad(nonce, &[], plaintext, out) 170 | } 171 | 172 | pub fn decrypt(&self, nonce: u64, ciphertext: &[u8], out: &mut [u8]) -> Result { 173 | self.decrypt_ad(nonce, &[], ciphertext, out) 174 | } 175 | 176 | pub fn rekey(&mut self) { 177 | self.cipher.rekey(); 178 | } 179 | 180 | pub fn rekey_manually(&mut self, key: &[u8; CIPHERKEYLEN]) { 181 | self.cipher.set(key); 182 | } 183 | } 184 | 185 | /// Validates that a nonce value has not exceeded the maximum 186 | /// defined by the Noise spec. 187 | fn validate_nonce(current: u64) -> Result<(), Error> { 188 | // 2^64-1 is reserved and may not be used in the state machine (5.1). 189 | // 190 | // It is used by the default cipher rekey function (4.2). 191 | if current == u64::MAX { 192 | Err(Error::State(StateProblem::Exhausted)) 193 | } else { 194 | Ok(()) 195 | } 196 | } 197 | 198 | impl From for StatelessCipherState { 199 | fn from(other: CipherState) -> Self { 200 | Self { cipher: other.cipher, has_key: other.has_key } 201 | } 202 | } 203 | 204 | pub(crate) struct StatelessCipherStates(pub StatelessCipherState, pub StatelessCipherState); 205 | 206 | impl From for StatelessCipherStates { 207 | fn from(other: CipherStates) -> Self { 208 | StatelessCipherStates(other.0.into(), other.1.into()) 209 | } 210 | } 211 | 212 | impl StatelessCipherStates { 213 | pub fn rekey_initiator(&mut self) { 214 | self.0.rekey(); 215 | } 216 | 217 | pub fn rekey_initiator_manually(&mut self, key: &[u8; CIPHERKEYLEN]) { 218 | self.0.rekey_manually(key); 219 | } 220 | 221 | pub fn rekey_responder(&mut self) { 222 | self.1.rekey(); 223 | } 224 | 225 | pub fn rekey_responder_manually(&mut self, key: &[u8; CIPHERKEYLEN]) { 226 | self.1.rekey_manually(key); 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /src/constants.rs: -------------------------------------------------------------------------------- 1 | pub const PSKLEN: usize = 32; 2 | pub const CIPHERKEYLEN: usize = 32; 3 | pub const TAGLEN: usize = 16; 4 | 5 | pub const MAXHASHLEN: usize = 64; 6 | pub const MAXBLOCKLEN: usize = 128; 7 | pub const MAXMSGLEN: usize = 65535; 8 | 9 | // P-256 uncompressed SEC-1 encodings are 65 bytes long, larger 10 | // than the `MAXDHLEN` in the official Noise spec. 11 | #[cfg(feature = "p256")] 12 | pub const MAXDHLEN: usize = 65; 13 | // Curve448 keys are the largest in the official Noise spec. 14 | #[cfg(not(feature = "p256"))] 15 | pub const MAXDHLEN: usize = 56; 16 | 17 | #[cfg(feature = "hfs")] 18 | pub const MAXKEMPUBLEN: usize = 4096; 19 | #[cfg(feature = "hfs")] 20 | pub const MAXKEMCTLEN: usize = 4096; 21 | #[cfg(feature = "hfs")] 22 | pub const MAXKEMSSLEN: usize = 32; 23 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | //! All error types used by Snow operations. 2 | 3 | use core::fmt; 4 | 5 | /// `snow` provides decently detailed errors, exposed as the [`Error`] enum, 6 | /// to allow developers to react to errors in a more actionable way. 7 | /// 8 | /// *With that said*, security vulnerabilities *can* be introduced by passing 9 | /// along detailed failure information to an attacker. While an effort was 10 | /// made to not make any particularly foolish choices in this regard, we strongly 11 | /// recommend you don't dump the `Debug` output to a user, for example. 12 | /// 13 | /// This enum is intentionally non-exhasutive to allow new error types to be 14 | /// introduced without causing a breaking API change. 15 | /// 16 | /// `snow` may eventually add a feature flag and enum variant to only return 17 | /// an "unspecified" error for those who would prefer safety over observability. 18 | #[derive(Debug, PartialEq)] 19 | #[non_exhaustive] 20 | pub enum Error { 21 | /// The noise pattern failed to parse. 22 | Pattern(PatternProblem), 23 | 24 | /// Initialization failure, at a provided stage. 25 | Init(InitStage), 26 | 27 | /// Missing prerequisite material. 28 | Prereq(Prerequisite), 29 | 30 | /// An error in `snow`'s internal state. 31 | State(StateProblem), 32 | 33 | /// Invalid input. 34 | Input, 35 | 36 | /// Diffie-Hellman agreement failed. 37 | Dh, 38 | 39 | /// Decryption failed. 40 | Decrypt, 41 | 42 | /// Key-encapsulation failed 43 | #[cfg(feature = "hfs")] 44 | Kem, 45 | } 46 | 47 | /// The various stages of initialization used to help identify 48 | /// the specific cause of an `Init` error. 49 | #[derive(Debug, PartialEq)] 50 | pub enum PatternProblem { 51 | /// Caused by a pattern string that is too short and malformed (e.g. `Noise_NN_25519`). 52 | TooFewParameters, 53 | /// Caused by a pattern string that is too long (e.g. `Noise_NN_25519_SHA256_SomeOtherThing`). 54 | TooManyParameters, 55 | /// The handshake section of the string (e.g. `XXpsk3`) isn't supported. Check for typos 56 | /// and necessary feature flags. 57 | UnsupportedHandshakeType, 58 | /// This was a trick choice -- an illusion. The correct answer was `Noise`. 59 | UnsupportedBaseType, 60 | /// Invalid hash type (e.g. `BLAKE2s`). 61 | /// Check that there are no typos and that any feature flags you might need are toggled 62 | UnsupportedHashType, 63 | /// Invalid DH type (e.g. `25519`). 64 | /// Check that there are no typos and that any feature flags you might need are toggled 65 | UnsupportedDhType, 66 | /// Invalid cipher type (e.g. `ChaChaPoly`). 67 | /// Check that there are no typos and that any feature flags you might need are toggled 68 | UnsupportedCipherType, 69 | /// The PSK position must be a number, and a pretty small one at that. 70 | InvalidPsk, 71 | /// Duplicate modifiers must not be used in the same pattern. 72 | DuplicateModifier, 73 | /// Invalid modifier (e.g. `fallback`). 74 | /// Check that there are no typos and that any feature flags you might need are toggled 75 | UnsupportedModifier, 76 | #[cfg(feature = "hfs")] 77 | /// Invalid KEM type. 78 | /// Check that there are no typos and that any feature flags you might need are toggled 79 | UnsupportedKemType, 80 | } 81 | 82 | impl From for Error { 83 | fn from(reason: PatternProblem) -> Self { 84 | Error::Pattern(reason) 85 | } 86 | } 87 | 88 | /// The various stages of initialization used to help identify 89 | /// the specific cause of an `Init` error. 90 | #[derive(Debug, PartialEq)] 91 | pub enum InitStage { 92 | /// Provided and received key lengths were not equal. 93 | ValidateKeyLengths, 94 | /// Provided and received preshared key lengths were not equal. 95 | ValidatePskLengths, 96 | /// Two separate cipher algorithms were initialized. 97 | ValidateCipherTypes, 98 | /// The RNG couldn't be initialized. 99 | GetRngImpl, 100 | /// The DH implementation couldn't be initialized. 101 | GetDhImpl, 102 | /// The cipher implementation couldn't be initialized. 103 | GetCipherImpl, 104 | /// The hash implementation couldn't be initialized. 105 | GetHashImpl, 106 | #[cfg(feature = "hfs")] 107 | /// The KEM implementation couldn't be initialized. 108 | GetKemImpl, 109 | /// The PSK position (specified in the pattern string) isn't valid for the given handshake type. 110 | ValidatePskPosition, 111 | /// The Builder already has set a value for this parameter, and overwrites are not allowed as 112 | /// they can introduce subtle security issues. 113 | ParameterOverwrite, 114 | } 115 | 116 | impl From for Error { 117 | fn from(reason: InitStage) -> Self { 118 | Error::Init(reason) 119 | } 120 | } 121 | 122 | /// A prerequisite that may be missing. 123 | #[derive(Debug, PartialEq)] 124 | pub enum Prerequisite { 125 | /// A local private key wasn't provided when it was needed by the selected pattern. 126 | LocalPrivateKey, 127 | /// A remote public key wasn't provided when it was needed by the selected pattern. 128 | RemotePublicKey, 129 | } 130 | 131 | impl From for Error { 132 | fn from(reason: Prerequisite) -> Self { 133 | Error::Prereq(reason) 134 | } 135 | } 136 | 137 | /// Specific errors in the state machine. 138 | #[derive(Debug, PartialEq)] 139 | pub enum StateProblem { 140 | /// Missing key material in the internal handshake state. 141 | MissingKeyMaterial, 142 | /// Preshared key missing in the internal handshake state. 143 | MissingPsk, 144 | /// You attempted to write a message when it's our turn to read. 145 | NotTurnToWrite, 146 | /// You attempted to read a message when it's our turn to write. 147 | NotTurnToRead, 148 | /// You tried to go into transport mode before the handshake was done. 149 | HandshakeNotFinished, 150 | /// You tried to continue the handshake when it was already done. 151 | HandshakeAlreadyFinished, 152 | /// You called a method that is only valid if this weren't a one-way handshake. 153 | OneWay, 154 | /// The nonce counter attempted to go higher than (2^64) - 1 155 | Exhausted, 156 | } 157 | 158 | impl From for Error { 159 | fn from(reason: StateProblem) -> Self { 160 | Error::State(reason) 161 | } 162 | } 163 | 164 | impl fmt::Display for Error { 165 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 166 | match self { 167 | Error::Pattern(reason) => write!(f, "pattern error: {reason:?}"), 168 | Error::Init(reason) => { 169 | write!(f, "initialization error: {reason:?}") 170 | }, 171 | Error::Prereq(reason) => { 172 | write!(f, "prerequisite error: {reason:?}") 173 | }, 174 | Error::State(reason) => write!(f, "state error: {reason:?}"), 175 | Error::Input => write!(f, "input error"), 176 | Error::Dh => write!(f, "diffie-hellman error"), 177 | Error::Decrypt => write!(f, "decrypt error"), 178 | #[cfg(feature = "hfs")] 179 | Error::Kem => write!(f, "kem error"), 180 | } 181 | } 182 | } 183 | 184 | impl core::error::Error for Error {} 185 | -------------------------------------------------------------------------------- /src/handshakestate.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "risky-raw-split")] 2 | use crate::constants::{CIPHERKEYLEN, MAXHASHLEN}; 3 | #[cfg(feature = "hfs")] 4 | use crate::constants::{MAXKEMCTLEN, MAXKEMPUBLEN, MAXKEMSSLEN}; 5 | #[cfg(feature = "hfs")] 6 | use crate::types::Kem; 7 | use crate::{ 8 | cipherstate::{CipherState, CipherStates}, 9 | constants::{MAXDHLEN, MAXMSGLEN, PSKLEN, TAGLEN}, 10 | error::{Error, InitStage, StateProblem}, 11 | params::{DhToken, HandshakeTokens, MessagePatterns, NoiseParams, Token}, 12 | stateless_transportstate::StatelessTransportState, 13 | symmetricstate::SymmetricState, 14 | transportstate::TransportState, 15 | types::{Dh, Hash, Random}, 16 | utils::Toggle, 17 | }; 18 | #[cfg(not(feature = "std"))] 19 | use alloc::boxed::Box; 20 | use core::{ 21 | convert::{TryFrom, TryInto}, 22 | fmt, 23 | }; 24 | 25 | /// A state machine encompassing the handshake phase of a Noise session. 26 | /// 27 | /// **Note:** you are probably looking for [`Builder`](struct.Builder.html) to 28 | /// get started. 29 | /// 30 | /// See: 31 | pub struct HandshakeState { 32 | pub(crate) rng: Box, 33 | pub(crate) symmetricstate: SymmetricState, 34 | pub(crate) cipherstates: CipherStates, 35 | pub(crate) s: Toggle>, 36 | pub(crate) e: Toggle>, 37 | pub(crate) fixed_ephemeral: bool, 38 | pub(crate) rs: Toggle<[u8; MAXDHLEN]>, 39 | pub(crate) re: Toggle<[u8; MAXDHLEN]>, 40 | pub(crate) initiator: bool, 41 | pub(crate) params: NoiseParams, 42 | pub(crate) psks: [Option<[u8; PSKLEN]>; 10], 43 | #[cfg(feature = "hfs")] 44 | pub(crate) kem: Option>, 45 | #[cfg(feature = "hfs")] 46 | pub(crate) kem_re: Option<[u8; MAXKEMPUBLEN]>, 47 | pub(crate) my_turn: bool, 48 | pub(crate) message_patterns: MessagePatterns, 49 | pub(crate) pattern_position: usize, 50 | } 51 | 52 | impl HandshakeState { 53 | #[allow(clippy::too_many_arguments)] 54 | pub(crate) fn new( 55 | rng: Box, 56 | cipherstate: CipherState, 57 | hasher: Box, 58 | s: Toggle>, 59 | e: Toggle>, 60 | fixed_ephemeral: bool, 61 | rs: Toggle<[u8; MAXDHLEN]>, 62 | re: Toggle<[u8; MAXDHLEN]>, 63 | initiator: bool, 64 | params: NoiseParams, 65 | psks: &[Option<[u8; PSKLEN]>; 10], 66 | prologue: &[u8], 67 | cipherstates: CipherStates, 68 | ) -> Result { 69 | if (s.is_on() && e.is_on() && s.pub_len() != e.pub_len()) 70 | || (s.is_on() && rs.is_on() && s.pub_len() > rs.len()) 71 | || (s.is_on() && re.is_on() && s.pub_len() > re.len()) 72 | { 73 | return Err(InitStage::ValidateKeyLengths.into()); 74 | } 75 | 76 | let tokens = HandshakeTokens::try_from(¶ms.handshake)?; 77 | 78 | let mut symmetricstate = SymmetricState::new(cipherstate, hasher); 79 | 80 | symmetricstate.initialize(¶ms.name); 81 | symmetricstate.mix_hash(prologue); 82 | 83 | let pub_len = s.pub_len(); 84 | if initiator { 85 | for token in tokens.premsg_pattern_i { 86 | symmetricstate.mix_hash( 87 | match *token { 88 | Token::S => &s, 89 | Token::E => &e, 90 | _ => unreachable!(), 91 | } 92 | .get() 93 | .ok_or(StateProblem::MissingKeyMaterial)? 94 | .pubkey(), 95 | ); 96 | } 97 | for token in tokens.premsg_pattern_r { 98 | symmetricstate.mix_hash( 99 | &match *token { 100 | Token::S => &rs, 101 | Token::E => &re, 102 | _ => unreachable!(), 103 | } 104 | .get() 105 | .ok_or(StateProblem::MissingKeyMaterial)?[..pub_len], 106 | ); 107 | } 108 | } else { 109 | for token in tokens.premsg_pattern_i { 110 | symmetricstate.mix_hash( 111 | &match *token { 112 | Token::S => &rs, 113 | Token::E => &re, 114 | _ => unreachable!(), 115 | } 116 | .get() 117 | .ok_or(StateProblem::MissingKeyMaterial)?[..pub_len], 118 | ); 119 | } 120 | for token in tokens.premsg_pattern_r { 121 | symmetricstate.mix_hash( 122 | match *token { 123 | Token::S => &s, 124 | Token::E => &e, 125 | _ => unreachable!(), 126 | } 127 | .get() 128 | .ok_or(StateProblem::MissingKeyMaterial)? 129 | .pubkey(), 130 | ); 131 | } 132 | } 133 | 134 | Ok(HandshakeState { 135 | rng, 136 | symmetricstate, 137 | cipherstates, 138 | s, 139 | e, 140 | fixed_ephemeral, 141 | rs, 142 | re, 143 | initiator, 144 | params, 145 | psks: *psks, 146 | #[cfg(feature = "hfs")] 147 | kem: None, 148 | #[cfg(feature = "hfs")] 149 | kem_re: None, 150 | my_turn: initiator, 151 | message_patterns: tokens.msg_patterns, 152 | pattern_position: 0, 153 | }) 154 | } 155 | 156 | pub(crate) fn dh_len(&self) -> usize { 157 | self.s.dh_len() 158 | } 159 | 160 | #[cfg(feature = "hfs")] 161 | pub(crate) fn set_kem(&mut self, kem: Box) { 162 | self.kem = Some(kem); 163 | } 164 | 165 | fn dh(&self, token: DhToken) -> Result<[u8; MAXDHLEN], Error> { 166 | let mut dh_out = [0_u8; MAXDHLEN]; 167 | let (dh, key) = match (token, self.is_initiator()) { 168 | (DhToken::Ee, _) => (&self.e, &self.re), 169 | (DhToken::Ss, _) => (&self.s, &self.rs), 170 | (DhToken::Se, true) | (DhToken::Es, false) => (&self.s, &self.re), 171 | (DhToken::Es, true) | (DhToken::Se, false) => (&self.e, &self.rs), 172 | }; 173 | if !(dh.is_on() && key.is_on()) { 174 | return Err(StateProblem::MissingKeyMaterial.into()); 175 | } 176 | dh.dh(&**key, &mut dh_out)?; 177 | Ok(dh_out) 178 | } 179 | 180 | /// This method will return `true` if the *previous* write payload was encrypted. 181 | /// 182 | /// See [Payload Security Properties](https://noiseprotocol.org/noise.html#payload-security-properties) 183 | /// for more information on the specific properties of your chosen handshake pattern. 184 | /// 185 | /// # Examples 186 | /// 187 | /// ```rust,ignore 188 | /// let mut session = Builder::new("Noise_NN_25519_AESGCM_SHA256".parse()?) 189 | /// .build_initiator()?; 190 | /// 191 | /// // write message... 192 | /// 193 | /// assert!(session.was_write_payload_encrypted()); 194 | /// ``` 195 | #[must_use] 196 | pub fn was_write_payload_encrypted(&self) -> bool { 197 | self.symmetricstate.has_key() 198 | } 199 | 200 | /// Construct a message from `payload` (and pending handshake tokens if in handshake state), 201 | /// and write it to the `message` buffer. 202 | /// 203 | /// Returns the number of bytes written to `message`. 204 | /// 205 | /// # Errors 206 | /// 207 | /// Will result in `Error::Input` if the size of the output exceeds the max message 208 | /// length in the Noise Protocol (65535 bytes). 209 | pub fn write_message(&mut self, payload: &[u8], message: &mut [u8]) -> Result { 210 | let checkpoint = self.symmetricstate.checkpoint(); 211 | match self._write_message(payload, message) { 212 | Ok(res) => { 213 | self.pattern_position += 1; 214 | self.my_turn = false; 215 | Ok(res) 216 | }, 217 | Err(err) => { 218 | self.symmetricstate.restore(checkpoint); 219 | Err(err) 220 | }, 221 | } 222 | } 223 | 224 | fn _write_message(&mut self, payload: &[u8], message: &mut [u8]) -> Result { 225 | if !self.my_turn { 226 | return Err(StateProblem::NotTurnToWrite.into()); 227 | } else if self.pattern_position >= self.message_patterns.len() { 228 | return Err(StateProblem::HandshakeAlreadyFinished.into()); 229 | } 230 | 231 | let mut byte_index = 0; 232 | for token in &self.message_patterns[self.pattern_position] { 233 | match *token { 234 | Token::E => { 235 | if byte_index + self.e.pub_len() > message.len() { 236 | return Err(Error::Input); 237 | } 238 | 239 | if !self.fixed_ephemeral { 240 | self.e.generate(&mut *self.rng); 241 | } 242 | let pubkey = self.e.pubkey(); 243 | message[byte_index..byte_index + pubkey.len()].copy_from_slice(pubkey); 244 | byte_index += pubkey.len(); 245 | self.symmetricstate.mix_hash(pubkey); 246 | if self.params.handshake.is_psk() { 247 | self.symmetricstate.mix_key(pubkey); 248 | } 249 | self.e.enable(); 250 | }, 251 | Token::S => { 252 | if !self.s.is_on() { 253 | return Err(StateProblem::MissingKeyMaterial.into()); 254 | } else if byte_index + self.s.pub_len() > message.len() { 255 | return Err(Error::Input); 256 | } 257 | 258 | byte_index += self 259 | .symmetricstate 260 | .encrypt_and_mix_hash(self.s.pubkey(), &mut message[byte_index..])?; 261 | }, 262 | Token::Psk(n) => match self.psks[usize::from(n)] { 263 | Some(psk) => { 264 | self.symmetricstate.mix_key_and_hash(&psk); 265 | }, 266 | None => { 267 | return Err(StateProblem::MissingPsk.into()); 268 | }, 269 | }, 270 | Token::Dh(t) => { 271 | let dh_out = self.dh(t)?; 272 | self.symmetricstate.mix_key(&dh_out[..self.dh_len()]); 273 | }, 274 | #[cfg(feature = "hfs")] 275 | Token::E1 => { 276 | let kem = self.kem.as_mut().ok_or(Error::Input)?; 277 | if kem.pub_len() > message.len() { 278 | return Err(Error::Input); 279 | } 280 | 281 | kem.generate(&mut *self.rng); 282 | byte_index += self 283 | .symmetricstate 284 | .encrypt_and_mix_hash(kem.pubkey(), &mut message[byte_index..])?; 285 | }, 286 | #[cfg(feature = "hfs")] 287 | Token::Ekem1 => { 288 | let kem = self.kem.as_mut().unwrap(); 289 | let mut kem_output_buf = [0; MAXKEMSSLEN]; 290 | let mut ciphertext_buf = [0; MAXKEMCTLEN]; 291 | 292 | if kem.ciphertext_len() > message.len() { 293 | return Err(Error::Input); 294 | } 295 | 296 | let kem_output = &mut kem_output_buf[..kem.shared_secret_len()]; 297 | let ciphertext = &mut ciphertext_buf[..kem.ciphertext_len()]; 298 | let pubkey = &self.kem_re.as_ref().unwrap()[..kem.pub_len()]; 299 | if kem.encapsulate(pubkey, kem_output, ciphertext).is_err() { 300 | return Err(Error::Kem); 301 | } 302 | 303 | byte_index += self.symmetricstate.encrypt_and_mix_hash( 304 | &ciphertext[..kem.ciphertext_len()], 305 | &mut message[byte_index..], 306 | )?; 307 | self.symmetricstate.mix_key(&kem_output[..kem.shared_secret_len()]); 308 | }, 309 | } 310 | } 311 | 312 | if byte_index + payload.len() + TAGLEN > message.len() { 313 | return Err(Error::Input); 314 | } 315 | byte_index += 316 | self.symmetricstate.encrypt_and_mix_hash(payload, &mut message[byte_index..])?; 317 | if byte_index > MAXMSGLEN { 318 | return Err(Error::Input); 319 | } 320 | if self.pattern_position == (self.message_patterns.len() - 1) { 321 | self.symmetricstate.split(&mut self.cipherstates.0, &mut self.cipherstates.1); 322 | } 323 | Ok(byte_index) 324 | } 325 | 326 | /// Read a noise message from `message` and write the payload to the `payload` buffer. 327 | /// 328 | /// Returns the number of bytes written to `payload`. 329 | /// 330 | /// # Errors 331 | /// 332 | /// Will result in `Error::Decrypt` if the contents couldn't be decrypted and/or the 333 | /// authentication tag didn't verify. 334 | /// 335 | /// Will result in `StateProblem::Exhausted` if the max nonce count overflows. 336 | pub fn read_message(&mut self, message: &[u8], payload: &mut [u8]) -> Result { 337 | let checkpoint = self.symmetricstate.checkpoint(); 338 | match self._read_message(message, payload) { 339 | Ok(res) => { 340 | self.pattern_position += 1; 341 | self.my_turn = true; 342 | Ok(res) 343 | }, 344 | Err(err) => { 345 | self.symmetricstate.restore(checkpoint); 346 | Err(err) 347 | }, 348 | } 349 | } 350 | 351 | fn _read_message(&mut self, message: &[u8], payload: &mut [u8]) -> Result { 352 | if message.len() > MAXMSGLEN { 353 | return Err(Error::Input); 354 | } else if self.my_turn { 355 | return Err(StateProblem::NotTurnToRead.into()); 356 | } else if self.pattern_position >= self.message_patterns.len() { 357 | return Err(StateProblem::HandshakeAlreadyFinished.into()); 358 | } 359 | let last = self.pattern_position == (self.message_patterns.len() - 1); 360 | 361 | let pub_len = self.e.pub_len(); 362 | let mut ptr = message; 363 | for token in &self.message_patterns[self.pattern_position] { 364 | match *token { 365 | Token::E => { 366 | if ptr.len() < pub_len { 367 | return Err(Error::Input); 368 | } 369 | self.re[..pub_len].copy_from_slice(&ptr[..pub_len]); 370 | ptr = &ptr[pub_len..]; 371 | self.symmetricstate.mix_hash(&self.re[..pub_len]); 372 | if self.params.handshake.is_psk() { 373 | self.symmetricstate.mix_key(&self.re[..pub_len]); 374 | } 375 | self.re.enable(); 376 | }, 377 | Token::S => { 378 | let data = if self.symmetricstate.has_key() { 379 | if ptr.len() < pub_len + TAGLEN { 380 | return Err(Error::Input); 381 | } 382 | let temp = &ptr[..pub_len + TAGLEN]; 383 | ptr = &ptr[pub_len + TAGLEN..]; 384 | temp 385 | } else { 386 | if ptr.len() < pub_len { 387 | return Err(Error::Input); 388 | } 389 | let temp = &ptr[..pub_len]; 390 | ptr = &ptr[pub_len..]; 391 | temp 392 | }; 393 | self.symmetricstate.decrypt_and_mix_hash(data, &mut self.rs[..pub_len])?; 394 | self.rs.enable(); 395 | }, 396 | Token::Psk(n) => match self.psks[usize::from(n)] { 397 | Some(psk) => { 398 | self.symmetricstate.mix_key_and_hash(&psk); 399 | }, 400 | None => { 401 | return Err(StateProblem::MissingPsk.into()); 402 | }, 403 | }, 404 | Token::Dh(t) => { 405 | let dh_out = self.dh(t)?; 406 | self.symmetricstate.mix_key(&dh_out[..self.dh_len()]); 407 | }, 408 | #[cfg(feature = "hfs")] 409 | Token::E1 => { 410 | let kem = self.kem.as_ref().ok_or(Error::Kem)?; 411 | let read_len = 412 | kem.pub_len() + if self.symmetricstate.has_key() { TAGLEN } else { 0 }; 413 | if ptr.len() < read_len { 414 | return Err(Error::Input); 415 | } 416 | let mut kem_re = [0; MAXKEMPUBLEN]; 417 | self.symmetricstate 418 | .decrypt_and_mix_hash(&ptr[..read_len], &mut kem_re[..kem.pub_len()])?; 419 | self.kem_re = Some(kem_re); 420 | ptr = &ptr[read_len..]; 421 | }, 422 | #[cfg(feature = "hfs")] 423 | Token::Ekem1 => { 424 | let kem = self.kem.as_ref().unwrap(); 425 | let read_len = kem.ciphertext_len() 426 | + if self.symmetricstate.has_key() { TAGLEN } else { 0 }; 427 | if ptr.len() < read_len { 428 | return Err(Error::Input); 429 | } 430 | let mut ciphertext_buf = [0; MAXKEMCTLEN]; 431 | let ciphertext = &mut ciphertext_buf[..kem.ciphertext_len()]; 432 | self.symmetricstate.decrypt_and_mix_hash(&ptr[..read_len], ciphertext)?; 433 | let mut kem_output_buf = [0; MAXKEMSSLEN]; 434 | let kem_output = &mut kem_output_buf[..kem.shared_secret_len()]; 435 | kem.decapsulate(ciphertext, kem_output)?; 436 | self.symmetricstate.mix_key(&kem_output[..kem.shared_secret_len()]); 437 | ptr = &ptr[read_len..]; 438 | }, 439 | } 440 | } 441 | 442 | self.symmetricstate.decrypt_and_mix_hash(ptr, payload)?; 443 | if last { 444 | self.symmetricstate.split(&mut self.cipherstates.0, &mut self.cipherstates.1); 445 | } 446 | let payload_len = ptr.len() - if self.symmetricstate.has_key() { TAGLEN } else { 0 }; 447 | Ok(payload_len) 448 | } 449 | 450 | /// Set the preshared key at the specified location. It is up to the caller 451 | /// to correctly set the location based on the specified handshake - Snow 452 | /// won't stop you from placing a PSK in an unused slot. 453 | /// 454 | /// # Errors 455 | /// 456 | /// Will result in `Error::Input` if the PSK is not the right length or the location is out of bounds. 457 | pub fn set_psk(&mut self, location: usize, key: &[u8]) -> Result<(), Error> { 458 | if key.len() != PSKLEN || self.psks.len() <= location { 459 | return Err(Error::Input); 460 | } 461 | 462 | let mut new_psk = [0_u8; PSKLEN]; 463 | new_psk.copy_from_slice(key); 464 | self.psks[location] = Some(new_psk); 465 | 466 | Ok(()) 467 | } 468 | 469 | /// Get the remote party's static public key, if available. 470 | /// 471 | /// Note: will return `None` if either the chosen Noise pattern 472 | /// doesn't necessitate a remote static key, *or* if the remote 473 | /// static key is not yet known (as can be the case in the `XX` 474 | /// pattern, for example). 475 | #[must_use] 476 | pub fn get_remote_static(&self) -> Option<&[u8]> { 477 | self.rs.get().map(|rs| &rs[..self.s.pub_len()]) 478 | } 479 | 480 | /// Get the handshake hash. 481 | /// 482 | /// Returns a slice of length `Hasher.hash_len()` (i.e. HASHLEN for the chosen Hash function). 483 | #[must_use] 484 | pub fn get_handshake_hash(&self) -> &[u8] { 485 | self.symmetricstate.handshake_hash() 486 | } 487 | 488 | /// Check if this session was started with the "initiator" role. 489 | #[must_use] 490 | pub fn is_initiator(&self) -> bool { 491 | self.initiator 492 | } 493 | 494 | /// Check if the handshake is finished and `into_transport_mode()` can now be called. 495 | #[must_use] 496 | pub fn is_handshake_finished(&self) -> bool { 497 | self.pattern_position == self.message_patterns.len() 498 | } 499 | 500 | /// Check whether it is our turn to send in the handshake state machine 501 | #[must_use] 502 | pub fn is_my_turn(&self) -> bool { 503 | self.my_turn 504 | } 505 | 506 | /// Perform the split calculation and return the resulting keys. 507 | /// 508 | /// This returns raw key material so it should be used with care. The "risky-raw-split" 509 | /// feature has to be enabled to use this function. 510 | #[cfg(feature = "risky-raw-split")] 511 | pub fn dangerously_get_raw_split(&mut self) -> ([u8; CIPHERKEYLEN], [u8; CIPHERKEYLEN]) { 512 | let mut output = ([0u8; MAXHASHLEN], [0u8; MAXHASHLEN]); 513 | self.symmetricstate.split_raw(&mut output.0, &mut output.1); 514 | (output.0[..CIPHERKEYLEN].try_into().unwrap(), output.1[..CIPHERKEYLEN].try_into().unwrap()) 515 | } 516 | 517 | /// Convert this `HandshakeState` into a `TransportState` with an internally stored nonce. 518 | /// 519 | /// # Errors 520 | /// A `State(StateProblem)` variant will be returned for various issues in the building of a 521 | /// usable `TransportState`. See `StateProblem` for further details. 522 | pub fn into_transport_mode(self) -> Result { 523 | self.try_into() 524 | } 525 | 526 | /// Convert this `HandshakeState` into a `StatelessTransportState` without an internally stored nonce. 527 | /// 528 | /// # Errors 529 | /// A `State(StateProblem)` variant will be returned for various issues in the building of a 530 | /// usable `StatelessTransportState`. See `StateProblem` for further details. 531 | pub fn into_stateless_transport_mode(self) -> Result { 532 | self.try_into() 533 | } 534 | } 535 | 536 | impl fmt::Debug for HandshakeState { 537 | fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { 538 | fmt.debug_struct("HandshakeState").finish() 539 | } 540 | } 541 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! The `snow` crate aims to be a straightforward Noise Protocol implementation. See the 2 | //! [Noise Protocol Framework Spec](https://noiseprotocol.org/noise.html) for more 3 | //! information. 4 | //! 5 | //! The typical usage flow is to use [`Builder`] to construct a [`HandshakeState`], where you 6 | //! will complete the handshake phase and convert into either a [`TransportState`] (typically 7 | //! when done over a reliable transport where the internal message counter can be used) or 8 | //! [`StatelessTransportState`] (when you control the message counter for unreliable transports 9 | //! like UDP). 10 | //! 11 | //! # Example 12 | //! 13 | //! ``` 14 | //! # use snow::Error; 15 | //! # 16 | //! # #[cfg(any(feature = "default-resolver-crypto", feature = "ring-accelerated"))] 17 | //! # fn try_main() -> Result<(), Error> { 18 | //! static PATTERN: &'static str = "Noise_NN_25519_ChaChaPoly_BLAKE2s"; 19 | //! 20 | //! let mut initiator = snow::Builder::new(PATTERN.parse()?) 21 | //! .build_initiator()?; 22 | //! let mut responder = snow::Builder::new(PATTERN.parse()?) 23 | //! .build_responder()?; 24 | //! 25 | //! let (mut read_buf, mut first_msg, mut second_msg) = 26 | //! ([0u8; 1024], [0u8; 1024], [0u8; 1024]); 27 | //! 28 | //! // -> e 29 | //! let len = initiator.write_message(&[], &mut first_msg)?; 30 | //! 31 | //! // responder processes the first message... 32 | //! responder.read_message(&first_msg[..len], &mut read_buf)?; 33 | //! 34 | //! // <- e, ee 35 | //! let len = responder.write_message(&[], &mut second_msg)?; 36 | //! 37 | //! // initiator processes the response... 38 | //! initiator.read_message(&second_msg[..len], &mut read_buf)?; 39 | //! 40 | //! // NN handshake complete, transition into transport mode. 41 | //! let initiator = initiator.into_transport_mode(); 42 | //! let responder = responder.into_transport_mode(); 43 | //! # Ok(()) 44 | //! # } 45 | //! # 46 | //! # #[cfg(not(any(feature = "default-resolver-crypto", feature = "ring-accelerated")))] 47 | //! # fn try_main() -> Result<(), ()> { Ok(()) } 48 | //! # 49 | //! # fn main() { 50 | //! # try_main().unwrap(); 51 | //! # } 52 | //! ``` 53 | //! 54 | //! See `examples/simple.rs` for a more complete TCP client/server example with static keys. 55 | //! # Crypto 56 | //! 57 | //! Cryptographic providers are swappable through `Builder::with_resolver()`, but by default 58 | //! it chooses select, artisanal pure-Rust implementations (see `Cargo.toml` for a quick 59 | //! overview). 60 | //! 61 | //! ### Other Providers 62 | //! 63 | //! #### ring 64 | //! 65 | //! [ring](https://github.com/briansmith/ring) is a crypto library based off of BoringSSL 66 | //! and is significantly faster than most of the pure-Rust implementations. 67 | //! 68 | //! If you enable the `ring-resolver` feature, Snow will include a `resolvers::ring` module 69 | //! as well as a `RingAcceleratedResolver` available to be used with 70 | //! `Builder::with_resolver()`. 71 | //! 72 | //! If you enable the `ring-accelerated` feature, Snow will default to choosing `ring`'s 73 | //! crypto implementations when available. 74 | //! 75 | //! ### Resolver primitives supported 76 | //! 77 | //! | | default | ring | 78 | //! | -----------------------: | :--------------: | :----------------: | 79 | //! | CSPRNG | ✔️ | ✔️ | 80 | //! | 25519 | ✔️ | ✔️ | 81 | //! | 448 | | | 82 | //! | P-256🏁 | ✔️ | | 83 | //! | AESGCM | ✔️ | ✔️ | 84 | //! | ChaChaPoly | ✔️ | ✔️ | 85 | //! | XChaChaPoly🏁 | ✔️ | | 86 | //! | SHA256 | ✔️ | ✔️ | 87 | //! | SHA512 | ✔️ | ✔️ | 88 | //! | BLAKE2s | ✔️ | | 89 | //! | BLAKE2b | ✔️ | | 90 | //! 91 | //! 🏁 P-256 and XChaChaPoly are not in the official specification of Noise, and thus need to be enabled 92 | //! via the feature flags `use-p256` and `use-xchacha20poly1305`, respectively. 93 | //! 94 | //! ## `no_std` support and feature selection 95 | //! 96 | //! Snow can be used in `no_std` environments if `alloc` is provided. 97 | //! 98 | //! By default, Snow uses the standard library, default crypto resolver and a selected collection 99 | //! of crypto primitives. To use Snow in `no_std` environments or make other kinds of customized 100 | //! setups, use Snow with `default-features = false`. This way you will individually select 101 | //! the components you wish to use. `default-resolver` is the only built-in resolver that 102 | //! currently supports `no_std`. 103 | //! 104 | //! To use a custom setup with `default-resolver`, enable your desired selection of cryptographic primitives: 105 | //! 106 | //! | | Primitive | Feature flag | 107 | //! | ----------: | :------------------------- | :--------------------- | 108 | //! | **DHs** | Curve25519 | `use-curve25519` | 109 | //! | | P-256:🏁: | `use-p256` | 110 | //! | **Ciphers** | AES-GCM | `use-aes-gcm` | 111 | //! | | ChaChaPoly | `use-chacha20poly1305` | 112 | //! | | XChaChaPoly:🏁: | `use-xchacha20poly1305`| 113 | //! | **Hashes** | SHA-256 | `use-sha2` | 114 | //! | | SHA-512 | `use-sha2` | 115 | //! | | BLAKE2s | `use-blake2` | 116 | //! | | BLAKE2b | `use-blake2` | 117 | //! 118 | //! 🏁 XChaChaPoly and P-256 are not in the official specification of Noise, but they are supported 119 | //! by Snow. 120 | 121 | #![warn(missing_docs)] 122 | #![cfg_attr(not(feature = "std"), no_std)] 123 | 124 | #[cfg(not(feature = "std"))] 125 | extern crate alloc; 126 | 127 | // Make sure the user is running a supported configuration. 128 | #[cfg(feature = "default-resolver")] 129 | #[cfg(any( 130 | not(any(feature = "use-curve25519")), 131 | not(any( 132 | feature = "use-aes-gcm", 133 | feature = "use-chacha20poly1305", 134 | feature = "use-xchacha20poly1305" 135 | )), 136 | not(any(feature = "use-sha2", feature = "use-blake2")) 137 | ))] 138 | compile_error!( 139 | "Valid selection of crypto primitived must be enabled when using feature 'default-resolver'. 140 | Enable at least one DH feature, one Cipher feature and one Hash feature. Check README.md for details." 141 | ); 142 | 143 | macro_rules! copy_slices { 144 | ($inslice:expr, $outslice:expr) => { 145 | $outslice[..$inslice.len()].copy_from_slice(&$inslice[..]) 146 | }; 147 | } 148 | 149 | macro_rules! static_slice { 150 | ($_type:ty: $($item:expr),*) => ({ 151 | static STATIC_SLICE: &'static [$_type] = &[$($item),*]; 152 | STATIC_SLICE 153 | }); 154 | } 155 | 156 | mod builder; 157 | mod cipherstate; 158 | mod constants; 159 | pub mod error; 160 | mod handshakestate; 161 | mod stateless_transportstate; 162 | mod symmetricstate; 163 | mod transportstate; 164 | mod utils; 165 | 166 | pub mod params; 167 | pub mod resolvers; 168 | pub mod types; 169 | 170 | pub use crate::{ 171 | builder::{Builder, Keypair}, 172 | error::Error, 173 | handshakestate::HandshakeState, 174 | stateless_transportstate::StatelessTransportState, 175 | transportstate::TransportState, 176 | }; 177 | -------------------------------------------------------------------------------- /src/params/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::match_on_vec_items)] 2 | #![allow(clippy::enum_glob_use)] 3 | 4 | //! All structures related to Noise parameter definitions (cryptographic primitive choices, protocol 5 | //! patterns/names) 6 | 7 | #[cfg(not(feature = "std"))] 8 | use alloc::{borrow::ToOwned, string::String}; 9 | 10 | use crate::error::{Error, PatternProblem}; 11 | use core::str::FromStr; 12 | mod patterns; 13 | 14 | pub use self::patterns::{ 15 | HandshakeChoice, HandshakeModifier, HandshakeModifierList, HandshakePattern, 16 | SUPPORTED_HANDSHAKE_PATTERNS, 17 | }; 18 | 19 | pub(crate) use self::patterns::{DhToken, HandshakeTokens, MessagePatterns, Token}; 20 | 21 | /// I recommend you choose `Noise`. 22 | #[derive(PartialEq, Copy, Clone, Debug)] 23 | pub enum BaseChoice { 24 | /// Ole' faithful. 25 | Noise, 26 | } 27 | 28 | impl FromStr for BaseChoice { 29 | type Err = Error; 30 | 31 | fn from_str(s: &str) -> Result { 32 | use self::BaseChoice::*; 33 | match s { 34 | "Noise" => Ok(Noise), 35 | _ => Err(PatternProblem::UnsupportedBaseType.into()), 36 | } 37 | } 38 | } 39 | 40 | /// Which Diffie-Hellman primitive to use. One of `25519` or `448`, per the spec. 41 | #[derive(PartialEq, Copy, Clone, Debug)] 42 | pub enum DHChoice { 43 | /// The Curve25519 elliptic curve. 44 | Curve25519, 45 | /// The Curve448 elliptic curve. 46 | Curve448, 47 | #[cfg(feature = "p256")] 48 | /// The P-256 elliptic curve. 49 | P256, 50 | } 51 | 52 | impl FromStr for DHChoice { 53 | type Err = Error; 54 | 55 | fn from_str(s: &str) -> Result { 56 | use self::DHChoice::*; 57 | match s { 58 | "25519" => Ok(Curve25519), 59 | "448" => Ok(Curve448), 60 | #[cfg(feature = "p256")] 61 | "P256" => Ok(P256), 62 | _ => Err(PatternProblem::UnsupportedDhType.into()), 63 | } 64 | } 65 | } 66 | 67 | /// One of `ChaChaPoly` or `AESGCM`, per the spec. 68 | #[derive(PartialEq, Copy, Clone, Debug)] 69 | pub enum CipherChoice { 70 | /// The ChaCha20Poly1305 AEAD. 71 | ChaChaPoly, 72 | #[cfg(feature = "use-xchacha20poly1305")] 73 | /// The XChaCha20Poly1305 AEAD, an extended nonce variant of ChaCha20Poly1305. 74 | /// This variant is hidden behind a feature flag to highlight that it is not in the 75 | /// official specification of the Noise Protocol. 76 | XChaChaPoly, 77 | /// The AES-GCM AEAD. 78 | AESGCM, 79 | } 80 | 81 | impl FromStr for CipherChoice { 82 | type Err = Error; 83 | 84 | fn from_str(s: &str) -> Result { 85 | use self::CipherChoice::*; 86 | match s { 87 | "ChaChaPoly" => Ok(ChaChaPoly), 88 | #[cfg(feature = "use-xchacha20poly1305")] 89 | "XChaChaPoly" => Ok(XChaChaPoly), 90 | "AESGCM" => Ok(AESGCM), 91 | _ => Err(PatternProblem::UnsupportedCipherType.into()), 92 | } 93 | } 94 | } 95 | 96 | /// One of the supported SHA-family or BLAKE-family hash choices, per the spec. 97 | #[derive(PartialEq, Copy, Clone, Debug)] 98 | pub enum HashChoice { 99 | /// The SHA-256 hash function. 100 | SHA256, 101 | /// The SHA-512 hash function. 102 | SHA512, 103 | /// The BLAKE2s hash function, designed to be more efficient on 8-bit to 32-bit 104 | /// architectures. 105 | Blake2s, 106 | /// The BLAKE2b hash function, designed to be more efficient on 64-bit architectures. 107 | Blake2b, 108 | } 109 | 110 | impl FromStr for HashChoice { 111 | type Err = Error; 112 | 113 | fn from_str(s: &str) -> Result { 114 | use self::HashChoice::*; 115 | match s { 116 | "SHA256" => Ok(SHA256), 117 | "SHA512" => Ok(SHA512), 118 | "BLAKE2s" => Ok(Blake2s), 119 | "BLAKE2b" => Ok(Blake2b), 120 | _ => Err(PatternProblem::UnsupportedHashType.into()), 121 | } 122 | } 123 | } 124 | 125 | /// One of the supported Kems provided for unstable HFS extension. 126 | #[cfg(feature = "hfs")] 127 | #[derive(PartialEq, Copy, Clone, Debug)] 128 | pub enum KemChoice { 129 | /// The 1024-bit Kyber variant. 130 | Kyber1024, 131 | } 132 | 133 | #[cfg(feature = "hfs")] 134 | impl FromStr for KemChoice { 135 | type Err = Error; 136 | 137 | fn from_str(s: &str) -> Result { 138 | use self::KemChoice::*; 139 | match s { 140 | "Kyber1024" => Ok(Kyber1024), 141 | _ => Err(PatternProblem::UnsupportedKemType.into()), 142 | } 143 | } 144 | } 145 | 146 | /// The set of choices (as specified in the Noise spec) that constitute a full protocol definition. 147 | /// 148 | /// See: [Chapter 8: Protocol names and modifiers](https://noiseprotocol.org/noise.html#protocol-names-and-modifiers). 149 | /// 150 | /// # Examples 151 | /// 152 | /// From a string definition: 153 | /// 154 | /// ``` 155 | /// # use snow::params::*; 156 | /// 157 | /// let params: NoiseParams = "Noise_XX_25519_AESGCM_SHA256".parse().unwrap(); 158 | /// ``` 159 | #[derive(PartialEq, Clone, Debug)] 160 | #[allow(clippy::module_name_repetitions)] 161 | pub struct NoiseParams { 162 | /// The full pattern string. 163 | pub name: String, 164 | /// In this case, always `Noise`. 165 | pub base: BaseChoice, 166 | /// The pattern's handshake choice (e.g. `XX`). 167 | pub handshake: HandshakeChoice, 168 | /// The pattern's DH choice (e.g. `25519`). 169 | pub dh: DHChoice, 170 | #[cfg(feature = "hfs")] 171 | /// The pattern's KEM choice (e.g. `Kyber1024`). 172 | pub kem: Option, 173 | /// The pattern's cipher choice (e.g. `AESGCM`). 174 | pub cipher: CipherChoice, 175 | /// The pattern's hash choice (e.g. `SHA256`). 176 | pub hash: HashChoice, 177 | } 178 | 179 | impl NoiseParams { 180 | #[cfg(not(feature = "hfs"))] 181 | /// Construct a new `NoiseParams` via specifying enums directly. 182 | #[must_use] 183 | pub fn new( 184 | name: String, 185 | base: BaseChoice, 186 | handshake: HandshakeChoice, 187 | dh: DHChoice, 188 | cipher: CipherChoice, 189 | hash: HashChoice, 190 | ) -> Self { 191 | NoiseParams { name, base, handshake, dh, cipher, hash } 192 | } 193 | 194 | #[cfg(feature = "hfs")] 195 | /// Construct a new NoiseParams via specifying enums directly. 196 | #[must_use] pub fn new( 197 | name: String, 198 | base: BaseChoice, 199 | handshake: HandshakeChoice, 200 | dh: DHChoice, 201 | kem: Option, 202 | cipher: CipherChoice, 203 | hash: HashChoice, 204 | ) -> Self { 205 | NoiseParams { name, base, handshake, dh, kem, cipher, hash } 206 | } 207 | } 208 | 209 | impl FromStr for NoiseParams { 210 | type Err = Error; 211 | 212 | #[cfg(not(feature = "hfs"))] 213 | fn from_str(s: &str) -> Result { 214 | let mut split = s.split('_'); 215 | let params = NoiseParams::new( 216 | s.to_owned(), 217 | split.next().ok_or(PatternProblem::TooFewParameters)?.parse()?, 218 | split.next().ok_or(PatternProblem::TooFewParameters)?.parse()?, 219 | split.next().ok_or(PatternProblem::TooFewParameters)?.parse()?, 220 | split.next().ok_or(PatternProblem::TooFewParameters)?.parse()?, 221 | split.next().ok_or(PatternProblem::TooFewParameters)?.parse()?, 222 | ); 223 | if split.next().is_some() { 224 | return Err(PatternProblem::TooManyParameters.into()); 225 | } 226 | Ok(params) 227 | } 228 | 229 | #[cfg(feature = "hfs")] 230 | fn from_str(s: &str) -> Result { 231 | let mut split = s.split('_').peekable(); 232 | let p = NoiseParams::new( 233 | s.to_owned(), 234 | split.next().ok_or(PatternProblem::TooFewParameters)?.parse()?, 235 | split.next().ok_or(PatternProblem::TooFewParameters)?.parse()?, 236 | split 237 | .peek() 238 | .ok_or(PatternProblem::TooFewParameters)?.split('+') 239 | .nth(0) 240 | .ok_or(PatternProblem::TooFewParameters)? 241 | .parse()?, 242 | split 243 | .next() 244 | .ok_or(PatternProblem::TooFewParameters)?.split_once('+').map(|x| x.1) 245 | .map(str::parse) 246 | .transpose()?, 247 | split.next().ok_or(PatternProblem::TooFewParameters)?.parse()?, 248 | split.next().ok_or(PatternProblem::TooFewParameters)?.parse()?, 249 | ); 250 | if split.next().is_some() { 251 | return Err(PatternProblem::TooManyParameters.into()); 252 | } 253 | 254 | // Validate that a KEM is specified iff the hfs modifier is present 255 | if p.handshake.is_hfs() != p.kem.is_some() { 256 | return Err(PatternProblem::TooFewParameters.into()); 257 | } 258 | Ok(p) 259 | } 260 | } 261 | 262 | #[cfg(test)] 263 | mod tests { 264 | use super::*; 265 | use core::convert::TryFrom; 266 | 267 | #[test] 268 | fn test_simple_handshake() { 269 | let _: HandshakePattern = "XX".parse().unwrap(); 270 | } 271 | 272 | #[test] 273 | fn test_basic() { 274 | let p: NoiseParams = "Noise_XX_25519_AESGCM_SHA256".parse().unwrap(); 275 | assert!(p.handshake.modifiers.list.is_empty()); 276 | } 277 | 278 | #[test] 279 | #[cfg(feature = "p256")] 280 | fn test_p256() { 281 | let p: NoiseParams = "Noise_XX_P256_AESGCM_SHA256".parse().unwrap(); 282 | assert_eq!(p.dh, DHChoice::P256); 283 | } 284 | 285 | #[test] 286 | fn test_basic_deferred() { 287 | let p: NoiseParams = "Noise_X1X1_25519_AESGCM_SHA256".parse().unwrap(); 288 | assert!(p.handshake.modifiers.list.is_empty()); 289 | } 290 | 291 | #[test] 292 | fn test_fallback_mod() { 293 | let p: NoiseParams = "Noise_XXfallback_25519_AESGCM_SHA256".parse().unwrap(); 294 | assert!(p.handshake.modifiers.list[0] == HandshakeModifier::Fallback); 295 | } 296 | 297 | #[test] 298 | fn test_psk_fallback_mod() { 299 | let p: NoiseParams = "Noise_XXfallback+psk0_25519_AESGCM_SHA256".parse().unwrap(); 300 | assert!(p.handshake.modifiers.list.len() == 2); 301 | } 302 | 303 | #[test] 304 | fn test_single_psk_mod() { 305 | let p: NoiseParams = "Noise_XXpsk0_25519_AESGCM_SHA256".parse().unwrap(); 306 | match p.handshake.modifiers.list[0] { 307 | HandshakeModifier::Psk(0) => {}, 308 | _ => panic!("modifier isn't as expected!"), 309 | } 310 | } 311 | 312 | #[test] 313 | fn test_multi_psk_mod() { 314 | use self::HandshakeModifier::*; 315 | 316 | let p: NoiseParams = "Noise_XXpsk0+psk1+psk2_25519_AESGCM_SHA256".parse().unwrap(); 317 | let mods = p.handshake.modifiers.list; 318 | match (mods[0], mods[1], mods[2]) { 319 | (Psk(0), Psk(1), Psk(2)) => {}, 320 | _ => panic!("modifiers weren't as expected! actual: {mods:?}"), 321 | } 322 | } 323 | 324 | #[test] 325 | fn test_duplicate_psk_mod() { 326 | assert!("Noise_XXfallback+psk1_25519_AESGCM_SHA256".parse::().is_ok()); 327 | assert_eq!( 328 | Error::Pattern(PatternProblem::DuplicateModifier), 329 | "Noise_XXfallback+fallback_25519_AESGCM_SHA256".parse::().unwrap_err() 330 | ); 331 | assert_eq!( 332 | Error::Pattern(PatternProblem::DuplicateModifier), 333 | "Noise_XXpsk1+psk1_25519_AESGCM_SHA256".parse::().unwrap_err() 334 | ); 335 | } 336 | 337 | #[test] 338 | fn test_modified_psk_handshake() { 339 | let p: NoiseParams = "Noise_XXpsk0_25519_AESGCM_SHA256".parse().unwrap(); 340 | let tokens = HandshakeTokens::try_from(&p.handshake).unwrap(); 341 | match tokens.msg_patterns[0][0] { 342 | Token::Psk(_) => {}, 343 | _ => panic!("missing token!"), 344 | } 345 | } 346 | 347 | #[test] 348 | fn test_modified_multi_psk_handshake() { 349 | let p: NoiseParams = "Noise_XXpsk0+psk2_25519_AESGCM_SHA256".parse().unwrap(); 350 | 351 | let tokens = HandshakeTokens::try_from(&p.handshake).unwrap(); 352 | 353 | match tokens.msg_patterns[0][0] { 354 | Token::Psk(_) => {}, 355 | _ => panic!("missing token!"), 356 | } 357 | 358 | let second = &tokens.msg_patterns[1]; 359 | match second[second.len() - 1] { 360 | Token::Psk(_) => {}, 361 | _ => panic!("missing token!"), 362 | } 363 | } 364 | 365 | #[test] 366 | fn test_invalid_psk_handshake() { 367 | let p: NoiseParams = "Noise_XXpsk9_25519_AESGCM_SHA256".parse().unwrap(); 368 | 369 | assert_eq!( 370 | Error::Pattern(PatternProblem::InvalidPsk), 371 | HandshakeTokens::try_from(&p.handshake).unwrap_err() 372 | ); 373 | } 374 | 375 | #[test] 376 | fn test_extraneous_string_data() { 377 | assert_eq!( 378 | Error::Pattern(PatternProblem::TooManyParameters), 379 | "Noise_XXpsk0_25519_AESGCM_SHA256_HackThePlanet".parse::().unwrap_err() 380 | ); 381 | } 382 | } 383 | -------------------------------------------------------------------------------- /src/params/patterns.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::enum_glob_use)] 2 | 3 | #[cfg(not(feature = "std"))] 4 | use alloc::{vec, vec::Vec}; 5 | 6 | use crate::error::{Error, PatternProblem}; 7 | use core::{convert::TryFrom, str::FromStr}; 8 | 9 | /// A small helper macro that behaves similar to the `vec![]` standard macro, 10 | /// except it allocates a bit extra to avoid resizing. 11 | macro_rules! message_vec { 12 | ($($item:expr),*) => ({ 13 | let token_groups: &[&[Token]] = &[$($item),*]; 14 | let mut vec: MessagePatterns = Vec::with_capacity(10); 15 | for group in token_groups { 16 | let mut inner = Vec::with_capacity(10); 17 | inner.extend_from_slice(group); 18 | vec.push(inner); 19 | } 20 | vec 21 | }); 22 | } 23 | 24 | /// This macro is specifically a helper to generate the enum of all handshake 25 | /// patterns in a less error-prone way. 26 | /// 27 | /// While rust macros can be really difficult to read, it felt too sketchy to hand- 28 | /// write a growing list of str -> enum variant match statements. 29 | macro_rules! pattern_enum { 30 | // NOTE: see https://danielkeep.github.io/tlborm/book/mbe-macro-rules.html and 31 | // https://doc.rust-lang.org/rust-by-example/macros.html for a great overview 32 | // of `macro_rules!`. 33 | ($name:ident { 34 | $($variant:ident),* $(,)* 35 | }) => { 36 | /// One of the patterns as defined in the 37 | /// [Handshake Pattern](https://noiseprotocol.org/noise.html#handshake-patterns) 38 | /// section. 39 | #[allow(missing_docs)] 40 | #[derive(Copy, Clone, PartialEq, Debug)] 41 | pub enum $name { 42 | $($variant),*, 43 | } 44 | 45 | impl FromStr for $name { 46 | type Err = Error; 47 | fn from_str(s: &str) -> Result { 48 | use self::$name::*; 49 | match s { 50 | $( 51 | stringify!($variant) => Ok($variant) 52 | ), 53 | *, 54 | _ => return Err(PatternProblem::UnsupportedHandshakeType.into()) 55 | } 56 | } 57 | } 58 | 59 | impl $name { 60 | /// The equivalent of the `ToString` trait, but for `&'static str`. 61 | #[must_use] pub fn as_str(self) -> &'static str { 62 | use self::$name::*; 63 | match self { 64 | $( 65 | $variant => stringify!($variant) 66 | ), 67 | * 68 | } 69 | } 70 | } 71 | 72 | #[doc(hidden)] 73 | pub const SUPPORTED_HANDSHAKE_PATTERNS: &'static [$name] = &[$($name::$variant),*]; 74 | } 75 | } 76 | 77 | /// The tokens which describe patterns involving DH calculations. 78 | /// 79 | /// See: 80 | #[derive(Copy, Clone, PartialEq, Debug)] 81 | pub(crate) enum DhToken { 82 | Ee, 83 | Es, 84 | Se, 85 | Ss, 86 | } 87 | 88 | /// The tokens which describe message patterns. 89 | /// 90 | /// See: 91 | #[derive(Copy, Clone, PartialEq, Debug)] 92 | pub(crate) enum Token { 93 | E, 94 | S, 95 | Dh(DhToken), 96 | Psk(u8), 97 | #[cfg(feature = "hfs")] 98 | E1, 99 | #[cfg(feature = "hfs")] 100 | Ekem1, 101 | } 102 | 103 | #[cfg(feature = "hfs")] 104 | impl Token { 105 | fn is_dh(self) -> bool { 106 | matches!(self, Dh(_)) 107 | } 108 | } 109 | 110 | // See the documentation in the macro above. 111 | pattern_enum! { 112 | HandshakePattern { 113 | // 7.4. One-way handshake patterns 114 | N, X, K, 115 | 116 | // 7.5. Interactive handshake patterns (fundamental) 117 | NN, NK, NX, XN, XK, XX, KN, KK, KX, IN, IK, IX, 118 | 119 | // 7.6. Interactive handshake patterns (deferred) 120 | NK1, NX1, X1N, X1K, XK1, X1K1, X1X, XX1, X1X1, K1N, K1K, KK1, K1K1, K1X, 121 | KX1, K1X1, I1N, I1K, IK1, I1K1, I1X, IX1, I1X1 122 | } 123 | } 124 | 125 | impl HandshakePattern { 126 | /// If the protocol is one-way only 127 | /// 128 | /// See: 129 | #[must_use] 130 | pub fn is_oneway(self) -> bool { 131 | matches!(self, N | X | K) 132 | } 133 | 134 | /// Whether this pattern requires a long-term static key. 135 | #[must_use] 136 | pub fn needs_local_static_key(self, initiator: bool) -> bool { 137 | if initiator { 138 | !matches!(self, N | NN | NK | NX | NK1 | NX1) 139 | } else { 140 | !matches!(self, NN | XN | KN | IN | X1N | K1N | I1N) 141 | } 142 | } 143 | 144 | /// Whether this pattern demands a remote public key pre-message. 145 | #[rustfmt::skip] 146 | #[must_use] pub fn need_known_remote_pubkey(self, initiator: bool) -> bool { 147 | if initiator { 148 | matches!( 149 | self, 150 | N | K | X | NK | XK | KK | IK | NK1 | X1K | XK1 | X1K1 | K1K | KK1 | K1K1 | I1K | IK1 | I1K1 151 | ) 152 | } else { 153 | matches!( 154 | self, 155 | K | KN | KK | KX | K1N | K1K | KK1 | K1K1 | K1X | KX1 | K1X1 156 | ) 157 | } 158 | } 159 | } 160 | 161 | /// A modifier applied to the base pattern as defined in the Noise spec. 162 | #[derive(Copy, Clone, PartialEq, Debug)] 163 | pub enum HandshakeModifier { 164 | /// Insert a PSK to mix at the associated position 165 | Psk(u8), 166 | 167 | /// Modify the base pattern to its "fallback" form 168 | Fallback, 169 | 170 | #[cfg(feature = "hfs")] 171 | /// Modify the base pattern to use Hybrid-Forward-Secrecy 172 | Hfs, 173 | } 174 | 175 | impl FromStr for HandshakeModifier { 176 | type Err = Error; 177 | 178 | fn from_str(s: &str) -> Result { 179 | match s { 180 | s if s.starts_with("psk") => { 181 | Ok(HandshakeModifier::Psk(s[3..].parse().map_err(|_| PatternProblem::InvalidPsk)?)) 182 | }, 183 | "fallback" => Ok(HandshakeModifier::Fallback), 184 | #[cfg(feature = "hfs")] 185 | "hfs" => Ok(HandshakeModifier::Hfs), 186 | _ => Err(PatternProblem::UnsupportedModifier.into()), 187 | } 188 | } 189 | } 190 | 191 | /// Handshake modifiers that will be used during key exchange handshake. 192 | #[derive(Clone, PartialEq, Debug)] 193 | pub struct HandshakeModifierList { 194 | /// List of parsed modifiers. 195 | pub list: Vec, 196 | } 197 | 198 | impl FromStr for HandshakeModifierList { 199 | type Err = Error; 200 | 201 | fn from_str(s: &str) -> Result { 202 | if s.is_empty() { 203 | Ok(HandshakeModifierList { list: vec![] }) 204 | } else { 205 | let modifier_names = s.split('+'); 206 | let mut modifiers = vec![]; 207 | for modifier_name in modifier_names { 208 | let modifier: HandshakeModifier = modifier_name.parse()?; 209 | if modifiers.contains(&modifier) { 210 | return Err(Error::Pattern(PatternProblem::DuplicateModifier)); 211 | } 212 | modifiers.push(modifier); 213 | } 214 | Ok(HandshakeModifierList { list: modifiers }) 215 | } 216 | } 217 | } 218 | 219 | /// The pattern/modifier combination choice (no primitives specified) 220 | /// for a full noise protocol definition. 221 | #[derive(Clone, PartialEq, Debug)] 222 | pub struct HandshakeChoice { 223 | /// The base pattern itself 224 | pub pattern: HandshakePattern, 225 | 226 | /// The modifier(s) requested for the base pattern 227 | pub modifiers: HandshakeModifierList, 228 | } 229 | 230 | impl HandshakeChoice { 231 | /// Whether the handshake choice includes one or more PSK modifiers. 232 | #[must_use] 233 | pub fn is_psk(&self) -> bool { 234 | for modifier in &self.modifiers.list { 235 | if let HandshakeModifier::Psk(_) = *modifier { 236 | return true; 237 | } 238 | } 239 | false 240 | } 241 | 242 | /// Whether the handshake choice includes the fallback modifier. 243 | #[must_use] 244 | pub fn is_fallback(&self) -> bool { 245 | self.modifiers.list.contains(&HandshakeModifier::Fallback) 246 | } 247 | 248 | /// Whether the handshake choice includes the hfs modifier. 249 | #[cfg(feature = "hfs")] 250 | #[must_use] 251 | pub fn is_hfs(&self) -> bool { 252 | self.modifiers.list.contains(&HandshakeModifier::Hfs) 253 | } 254 | 255 | /// Parse and split a base `HandshakePattern` from its optional modifiers 256 | fn parse_pattern_and_modifier(s: &str) -> Result<(HandshakePattern, &str), Error> { 257 | for i in (1..=4).rev() { 258 | if s.len() > i - 1 && s.is_char_boundary(i) { 259 | if let Ok(p) = s[..i].parse() { 260 | return Ok((p, &s[i..])); 261 | } 262 | } 263 | } 264 | 265 | Err(PatternProblem::UnsupportedHandshakeType.into()) 266 | } 267 | } 268 | 269 | impl FromStr for HandshakeChoice { 270 | type Err = Error; 271 | 272 | fn from_str(s: &str) -> Result { 273 | let (pattern, remainder) = Self::parse_pattern_and_modifier(s)?; 274 | let modifiers = remainder.parse()?; 275 | 276 | Ok(HandshakeChoice { pattern, modifiers }) 277 | } 278 | } 279 | 280 | type PremessagePatterns = &'static [Token]; 281 | pub(crate) type MessagePatterns = Vec>; 282 | 283 | /// The defined token patterns for a given handshake. 284 | /// 285 | /// See: 286 | #[derive(Debug)] 287 | pub(crate) struct HandshakeTokens { 288 | pub premsg_pattern_i: PremessagePatterns, 289 | pub premsg_pattern_r: PremessagePatterns, 290 | pub msg_patterns: MessagePatterns, 291 | } 292 | 293 | use self::{DhToken::*, HandshakePattern::*, Token::*}; 294 | 295 | type Patterns = (PremessagePatterns, PremessagePatterns, MessagePatterns); 296 | 297 | impl<'a> TryFrom<&'a HandshakeChoice> for HandshakeTokens { 298 | type Error = Error; 299 | 300 | // We're going to ignore the clippy warnings here about this function being too long because 301 | // it's essentially a lookup table and not problematic complex logic. 302 | #[allow(clippy::cognitive_complexity)] 303 | #[allow(clippy::too_many_lines)] 304 | fn try_from(handshake: &'a HandshakeChoice) -> Result { 305 | // Hfs cannot be combined with one-way handshake patterns 306 | #[cfg(feature = "hfs")] 307 | check_hfs_and_oneway_conflict(handshake)?; 308 | 309 | #[rustfmt::skip] 310 | let mut patterns: Patterns = match handshake.pattern { 311 | N => ( 312 | static_slice![Token: ], 313 | static_slice![Token: S], 314 | message_vec![&[E, Dh(Es)]] 315 | ), 316 | K => ( 317 | static_slice![Token: S], 318 | static_slice![Token: S], 319 | message_vec![&[E, Dh(Es), Dh(Ss)]] 320 | ), 321 | X => ( 322 | static_slice![Token: ], 323 | static_slice![Token: S], 324 | message_vec![&[E, Dh(Es), S, Dh(Ss)]] 325 | ), 326 | NN => ( 327 | static_slice![Token: ], 328 | static_slice![Token: ], 329 | message_vec![&[E], &[E, Dh(Ee)]] 330 | ), 331 | NK => ( 332 | static_slice![Token: ], 333 | static_slice![Token: S], 334 | message_vec![&[E, Dh(Es)], &[E, Dh(Ee)]] 335 | ), 336 | NX => ( 337 | static_slice![Token: ], 338 | static_slice![Token: ], 339 | message_vec![&[E], &[E, Dh(Ee), S, Dh(Es)]] 340 | ), 341 | XN => ( 342 | static_slice![Token: ], 343 | static_slice![Token: ], 344 | message_vec![&[E], &[E, Dh(Ee)], &[S, Dh(Se)]] 345 | ), 346 | XK => ( 347 | static_slice![Token: ], 348 | static_slice![Token: S], 349 | message_vec![&[E, Dh(Es)], &[E, Dh(Ee)], &[S, Dh(Se)]] 350 | ), 351 | XX => ( 352 | static_slice![Token: ], 353 | static_slice![Token: ], 354 | message_vec![&[E], &[E, Dh(Ee), S, Dh(Es)], &[S, Dh(Se)]], 355 | ), 356 | KN => ( 357 | static_slice![Token: S], 358 | static_slice![Token: ], 359 | message_vec![&[E], &[E, Dh(Ee), Dh(Se)]], 360 | ), 361 | KK => ( 362 | static_slice![Token: S], 363 | static_slice![Token: S], 364 | message_vec![&[E, Dh(Es), Dh(Ss)], &[E, Dh(Ee), Dh(Se)]], 365 | ), 366 | KX => ( 367 | static_slice![Token: S], 368 | static_slice![Token: ], 369 | message_vec![&[E], &[E, Dh(Ee), Dh(Se), S, Dh(Es)]], 370 | ), 371 | IN => ( 372 | static_slice![Token: ], 373 | static_slice![Token: ], 374 | message_vec![&[E, S], &[E, Dh(Ee), Dh(Se)]], 375 | ), 376 | IK => ( 377 | static_slice![Token: ], 378 | static_slice![Token: S], 379 | message_vec![&[E, Dh(Es), S, Dh(Ss)], &[E, Dh(Ee), Dh(Se)]], 380 | ), 381 | IX => ( 382 | static_slice![Token: ], 383 | static_slice![Token: ], 384 | message_vec![&[E, S], &[E, Dh(Ee), Dh(Se), S, Dh(Es)]], 385 | ), 386 | NK1 => ( 387 | static_slice![Token: ], 388 | static_slice![Token: S], 389 | message_vec![&[E], &[E, Dh(Ee), Dh(Es)]], 390 | ), 391 | NX1 => ( 392 | static_slice![Token: ], 393 | static_slice![Token: ], 394 | message_vec![&[E], &[E, Dh(Ee), S], &[Dh(Es)]] 395 | ), 396 | X1N => ( 397 | static_slice![Token: ], 398 | static_slice![Token: ], 399 | message_vec![&[E], &[E, Dh(Ee)], &[S], &[Dh(Se)]] 400 | ), 401 | X1K => ( 402 | static_slice![Token: ], 403 | static_slice![Token: S], 404 | message_vec![&[E, Dh(Es)], &[E, Dh(Ee)], &[S], &[Dh(Se)]] 405 | ), 406 | XK1 => ( 407 | static_slice![Token: ], 408 | static_slice![Token: S], 409 | message_vec![&[E], &[E, Dh(Ee), Dh(Es)], &[S, Dh(Se)]] 410 | ), 411 | X1K1 => ( 412 | static_slice![Token: ], 413 | static_slice![Token: S], 414 | message_vec![&[E], &[E, Dh(Ee), Dh(Es)], &[S], &[Dh(Se)]] 415 | ), 416 | X1X => ( 417 | static_slice![Token: ], 418 | static_slice![Token: ], 419 | message_vec![&[E], &[E, Dh(Ee), S, Dh(Es)], &[S], &[Dh(Se)]], 420 | ), 421 | XX1 => ( 422 | static_slice![Token: ], 423 | static_slice![Token: ], 424 | message_vec![&[E], &[E, Dh(Ee), S], &[Dh(Es), S, Dh(Se)]], 425 | ), 426 | X1X1 => ( 427 | static_slice![Token: ], 428 | static_slice![Token: ], 429 | message_vec![&[E], &[E, Dh(Ee), S], &[Dh(Es), S], &[Dh(Se)]], 430 | ), 431 | K1N => ( 432 | static_slice![Token: S], 433 | static_slice![Token: ], 434 | message_vec![&[E], &[E, Dh(Ee)], &[Dh(Se)]], 435 | ), 436 | K1K => ( 437 | static_slice![Token: S], 438 | static_slice![Token: S], 439 | message_vec![&[E, Dh(Es)], &[E, Dh(Ee)], &[Dh(Se)]], 440 | ), 441 | KK1 => ( 442 | static_slice![Token: S], 443 | static_slice![Token: S], 444 | message_vec![&[E], &[E, Dh(Ee), Dh(Se), Dh(Es)]], 445 | ), 446 | K1K1 => ( 447 | static_slice![Token: S], 448 | static_slice![Token: S], 449 | message_vec![&[E], &[E, Dh(Ee), Dh(Es)], &[Dh(Se)]], 450 | ), 451 | K1X => ( 452 | static_slice![Token: S], 453 | static_slice![Token: ], 454 | message_vec![&[E], &[E, Dh(Ee), S, Dh(Es)], &[Dh(Se)]], 455 | ), 456 | KX1 => ( 457 | static_slice![Token: S], 458 | static_slice![Token: ], 459 | message_vec![&[E], &[E, Dh(Ee), Dh(Se), S], &[Dh(Es)]], 460 | ), 461 | K1X1 => ( 462 | static_slice![Token: S], 463 | static_slice![Token: ], 464 | message_vec![&[E], &[E, Dh(Ee), S], &[Dh(Se), Dh(Es)]], 465 | ), 466 | I1N => ( 467 | static_slice![Token: ], 468 | static_slice![Token: ], 469 | message_vec![&[E, S], &[E, Dh(Ee)], &[Dh(Se)]], 470 | ), 471 | I1K => ( 472 | static_slice![Token: ], 473 | static_slice![Token: S], 474 | message_vec![&[E, Dh(Es), S], &[E, Dh(Ee)], &[Dh(Se)]], 475 | ), 476 | IK1 => ( 477 | static_slice![Token: ], 478 | static_slice![Token: S], 479 | message_vec![&[E, S], &[E, Dh(Ee), Dh(Se), Dh(Es)]], 480 | ), 481 | I1K1 => ( 482 | static_slice![Token: ], 483 | static_slice![Token: S], 484 | message_vec![&[E, S], &[E, Dh(Ee), Dh(Es)], &[Dh(Se)]], 485 | ), 486 | I1X => ( 487 | static_slice![Token: ], 488 | static_slice![Token: ], 489 | message_vec![&[E, S], &[E, Dh(Ee), S, Dh(Es)], &[Dh(Se)]], 490 | ), 491 | IX1 => ( 492 | static_slice![Token: ], 493 | static_slice![Token: ], 494 | message_vec![&[E, S], &[E, Dh(Ee), Dh(Se), S], &[Dh(Es)]], 495 | ), 496 | I1X1 => ( 497 | static_slice![Token: ], 498 | static_slice![Token: ], 499 | message_vec![&[E, S], &[E, Dh(Ee), S], &[Dh(Se), Dh(Es)]], 500 | ), 501 | }; 502 | 503 | for modifier in &handshake.modifiers.list { 504 | match modifier { 505 | HandshakeModifier::Psk(n) => apply_psk_modifier(&mut patterns, *n)?, 506 | #[cfg(feature = "hfs")] 507 | HandshakeModifier::Hfs => apply_hfs_modifier(&mut patterns), 508 | _ => return Err(PatternProblem::UnsupportedModifier.into()), 509 | } 510 | } 511 | 512 | Ok(HandshakeTokens { 513 | premsg_pattern_i: patterns.0, 514 | premsg_pattern_r: patterns.1, 515 | msg_patterns: patterns.2, 516 | }) 517 | } 518 | } 519 | 520 | #[cfg(feature = "hfs")] 521 | /// Check that this handshake is not HFS *and* one-way. 522 | /// 523 | /// Usage of HFS in conjuction with a oneway pattern is invalid. This function returns an error 524 | /// if `handshake` is invalid because of this. Otherwise it will return `()`. 525 | fn check_hfs_and_oneway_conflict(handshake: &HandshakeChoice) -> Result<(), Error> { 526 | if handshake.is_hfs() && handshake.pattern.is_oneway() { 527 | Err(PatternProblem::UnsupportedModifier.into()) 528 | } else { 529 | Ok(()) 530 | } 531 | } 532 | 533 | /// Given our PSK modifier, we inject the token at the appropriate place. 534 | fn apply_psk_modifier(patterns: &mut Patterns, n: u8) -> Result<(), Error> { 535 | let tokens = patterns 536 | .2 537 | .get_mut(usize::from(n).saturating_sub(1)) 538 | .ok_or(Error::Pattern(PatternProblem::InvalidPsk))?; 539 | if n == 0 { 540 | tokens.insert(0, Token::Psk(n)); 541 | } else { 542 | tokens.push(Token::Psk(n)); 543 | } 544 | Ok(()) 545 | } 546 | 547 | #[cfg(feature = "hfs")] 548 | fn apply_hfs_modifier(patterns: &mut Patterns) { 549 | // From the HFS spec, Section 5: 550 | // 551 | // Add an "e1" token directly following the first occurence of "e", 552 | // unless there is a DH operation in this same message, in which case 553 | // the "hfs" [should be "e1"?] token is placed directly after this DH 554 | // (so that the public key will be encrypted). 555 | // 556 | // The "hfs" modifier also adds an "ekem1" token directly following the 557 | // first occurrence of "ee". 558 | 559 | // Add the e1 token 560 | let mut e1_insert_idx = None; 561 | for msg in &mut patterns.2 { 562 | if let Some(e_idx) = msg.iter().position(|x| *x == Token::E) { 563 | if let Some(dh_idx) = msg.iter().copied().position(Token::is_dh) { 564 | e1_insert_idx = Some(dh_idx + 1); 565 | } else { 566 | e1_insert_idx = Some(e_idx + 1); 567 | } 568 | } 569 | if let Some(idx) = e1_insert_idx { 570 | msg.insert(idx, Token::E1); 571 | break; 572 | } 573 | } 574 | 575 | // Add the ekem1 token 576 | let mut ekem1_insert_idx = None; 577 | for msg in &mut patterns.2 { 578 | if let Some(ee_idx) = msg.iter().position(|x| *x == Token::Dh(Ee)) { 579 | ekem1_insert_idx = Some(ee_idx + 1); 580 | } 581 | if let Some(idx) = ekem1_insert_idx { 582 | msg.insert(idx, Token::Ekem1); 583 | break; 584 | } 585 | } 586 | 587 | // This should not be possible, because the caller verified that the 588 | // HandshakePattern is not one-way. 589 | assert!( 590 | !(e1_insert_idx.is_some() ^ ekem1_insert_idx.is_some()), 591 | "handshake messages contain one of the ['e1', 'ekem1'] tokens, but not the other", 592 | ); 593 | } 594 | -------------------------------------------------------------------------------- /src/resolvers/mod.rs: -------------------------------------------------------------------------------- 1 | //! The wrappers around the default collection of cryptography and entropy providers. 2 | 3 | #[cfg(not(feature = "std"))] 4 | use alloc::boxed::Box; 5 | 6 | /// The default primitive resolver. 7 | #[cfg(feature = "default-resolver")] 8 | mod default; 9 | /// A ring primitive resolver. 10 | #[cfg(feature = "ring-resolver")] 11 | mod ring; 12 | 13 | #[cfg(feature = "hfs")] 14 | use crate::params::KemChoice; 15 | #[cfg(feature = "hfs")] 16 | use crate::types::Kem; 17 | use crate::{ 18 | params::{CipherChoice, DHChoice, HashChoice}, 19 | types::{Cipher, Dh, Hash, Random}, 20 | }; 21 | 22 | #[cfg(feature = "default-resolver")] 23 | pub use self::default::DefaultResolver; 24 | #[cfg(feature = "ring-resolver")] 25 | pub use self::ring::RingResolver; 26 | 27 | /// Boxed `CryptoResolver` 28 | pub type BoxedCryptoResolver = Box; 29 | 30 | /// An object that resolves the providers of Noise crypto choices 31 | pub trait CryptoResolver { 32 | /// Provide an implementation of the Random trait or None if none available. 33 | fn resolve_rng(&self) -> Option>; 34 | 35 | /// Provide an implementation of the Dh trait for the given `DHChoice` or None if unavailable. 36 | fn resolve_dh(&self, choice: &DHChoice) -> Option>; 37 | 38 | /// Provide an implementation of the Hash trait for the given `HashChoice` or None if unavailable. 39 | fn resolve_hash(&self, choice: &HashChoice) -> Option>; 40 | 41 | /// Provide an implementation of the Cipher trait for the given `CipherChoice` or None if unavailable. 42 | fn resolve_cipher(&self, choice: &CipherChoice) -> Option>; 43 | 44 | /// Provide an implementation of the Kem trait for the given KemChoice or None if unavailable 45 | #[cfg(feature = "hfs")] 46 | fn resolve_kem(&self, _choice: &KemChoice) -> Option> { 47 | None 48 | } 49 | } 50 | 51 | /// A helper resolver that can opportunistically use one resolver, but 52 | /// can fallback to another if the first didn't have an implementation for 53 | /// a given primitive. 54 | pub struct FallbackResolver { 55 | preferred: BoxedCryptoResolver, 56 | fallback: BoxedCryptoResolver, 57 | } 58 | 59 | impl FallbackResolver { 60 | /// Create a new `FallbackResolver` that holds the primary and secondary resolver. 61 | #[must_use] 62 | pub fn new(preferred: BoxedCryptoResolver, fallback: BoxedCryptoResolver) -> Self { 63 | Self { preferred, fallback } 64 | } 65 | } 66 | 67 | impl CryptoResolver for FallbackResolver { 68 | fn resolve_rng(&self) -> Option> { 69 | self.preferred.resolve_rng().or_else(|| self.fallback.resolve_rng()) 70 | } 71 | 72 | fn resolve_dh(&self, choice: &DHChoice) -> Option> { 73 | self.preferred.resolve_dh(choice).or_else(|| self.fallback.resolve_dh(choice)) 74 | } 75 | 76 | fn resolve_hash(&self, choice: &HashChoice) -> Option> { 77 | self.preferred.resolve_hash(choice).or_else(|| self.fallback.resolve_hash(choice)) 78 | } 79 | 80 | fn resolve_cipher(&self, choice: &CipherChoice) -> Option> { 81 | self.preferred.resolve_cipher(choice).or_else(|| self.fallback.resolve_cipher(choice)) 82 | } 83 | 84 | #[cfg(feature = "hfs")] 85 | fn resolve_kem(&self, choice: &KemChoice) -> Option> { 86 | self.preferred.resolve_kem(choice).or_else(|| self.fallback.resolve_kem(choice)) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/resolvers/ring.rs: -------------------------------------------------------------------------------- 1 | use super::CryptoResolver; 2 | use crate::{ 3 | constants::{CIPHERKEYLEN, TAGLEN}, 4 | params::{CipherChoice, DHChoice, HashChoice}, 5 | types::{Cipher, Dh, Hash, Random}, 6 | Error, 7 | }; 8 | use ring::{ 9 | aead::{self, LessSafeKey, UnboundKey}, 10 | digest, 11 | rand::{SecureRandom, SystemRandom}, 12 | }; 13 | 14 | /// A resolver that chooses [ring](https://github.com/briansmith/ring)-backed 15 | /// primitives when available. 16 | #[allow(clippy::module_name_repetitions)] 17 | #[derive(Default)] 18 | pub struct RingResolver; 19 | 20 | #[cfg(feature = "ring")] 21 | impl CryptoResolver for RingResolver { 22 | fn resolve_rng(&self) -> Option> { 23 | Some(Box::new(RingRng::default())) 24 | } 25 | 26 | fn resolve_dh(&self, _choice: &DHChoice) -> Option> { 27 | None 28 | } 29 | 30 | fn resolve_hash(&self, choice: &HashChoice) -> Option> { 31 | match *choice { 32 | HashChoice::SHA256 => Some(Box::new(HashSHA256::default())), 33 | HashChoice::SHA512 => Some(Box::new(HashSHA512::default())), 34 | _ => None, 35 | } 36 | } 37 | 38 | fn resolve_cipher(&self, choice: &CipherChoice) -> Option> { 39 | match *choice { 40 | CipherChoice::AESGCM => Some(Box::new(CipherAESGCM::default())), 41 | CipherChoice::ChaChaPoly => Some(Box::new(CipherChaChaPoly::default())), 42 | #[cfg(feature = "use-xchacha20poly1305")] 43 | CipherChoice::XChaChaPoly => None, 44 | } 45 | } 46 | } 47 | 48 | struct RingRng { 49 | rng: SystemRandom, 50 | } 51 | 52 | impl Default for RingRng { 53 | fn default() -> Self { 54 | Self { rng: SystemRandom::new() } 55 | } 56 | } 57 | 58 | impl rand_core::RngCore for RingRng { 59 | fn next_u32(&mut self) -> u32 { 60 | rand_core::impls::next_u32_via_fill(self) 61 | } 62 | 63 | fn next_u64(&mut self) -> u64 { 64 | rand_core::impls::next_u64_via_fill(self) 65 | } 66 | 67 | fn fill_bytes(&mut self, dest: &mut [u8]) { 68 | self.try_fill_bytes(dest).unwrap(); 69 | } 70 | 71 | fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> { 72 | self.rng.fill(dest).map_err(|e| rand_core::Error::new(e)) 73 | } 74 | } 75 | 76 | impl rand_core::CryptoRng for RingRng {} 77 | 78 | impl Random for RingRng {} 79 | 80 | struct CipherAESGCM { 81 | // NOTE: LessSafeKey is chosen here because nonce atomicity is handled outside of this structure. 82 | // See ring documentation for more details on the naming choices. 83 | key: LessSafeKey, 84 | } 85 | 86 | impl Default for CipherAESGCM { 87 | fn default() -> Self { 88 | CipherAESGCM { 89 | key: LessSafeKey::new(UnboundKey::new(&aead::AES_256_GCM, &[0u8; 32]).unwrap()), 90 | } 91 | } 92 | } 93 | 94 | impl Cipher for CipherAESGCM { 95 | fn name(&self) -> &'static str { 96 | "AESGCM" 97 | } 98 | 99 | fn set(&mut self, key: &[u8; CIPHERKEYLEN]) { 100 | self.key = aead::LessSafeKey::new(UnboundKey::new(&aead::AES_256_GCM, key).unwrap()); 101 | } 102 | 103 | fn encrypt(&self, nonce: u64, authtext: &[u8], plaintext: &[u8], out: &mut [u8]) -> usize { 104 | let mut nonce_bytes = [0u8; 12]; 105 | copy_slices!(&nonce.to_be_bytes(), &mut nonce_bytes[4..]); 106 | 107 | out[..plaintext.len()].copy_from_slice(plaintext); 108 | 109 | let nonce = aead::Nonce::assume_unique_for_key(nonce_bytes); 110 | 111 | let tag = self 112 | .key 113 | .seal_in_place_separate_tag( 114 | nonce, 115 | aead::Aad::from(authtext), 116 | &mut out[..plaintext.len()], 117 | ) 118 | .unwrap(); 119 | out[plaintext.len()..plaintext.len() + TAGLEN].copy_from_slice(tag.as_ref()); 120 | 121 | plaintext.len() + TAGLEN 122 | } 123 | 124 | fn decrypt( 125 | &self, 126 | nonce: u64, 127 | authtext: &[u8], 128 | ciphertext: &[u8], 129 | out: &mut [u8], 130 | ) -> Result { 131 | let mut nonce_bytes = [0u8; 12]; 132 | copy_slices!(&nonce.to_be_bytes(), &mut nonce_bytes[4..]); 133 | let nonce = aead::Nonce::assume_unique_for_key(nonce_bytes); 134 | 135 | if out.len() >= ciphertext.len() { 136 | let in_out = &mut out[..ciphertext.len()]; 137 | in_out.copy_from_slice(ciphertext); 138 | 139 | let len = self 140 | .key 141 | .open_in_place(nonce, aead::Aad::from(authtext), in_out) 142 | .map_err(|_| Error::Decrypt)? 143 | .len(); 144 | 145 | Ok(len) 146 | } else { 147 | let mut in_out = ciphertext.to_vec(); 148 | 149 | let out0 = self 150 | .key 151 | .open_in_place(nonce, aead::Aad::from(authtext), &mut in_out) 152 | .map_err(|_| Error::Decrypt)?; 153 | 154 | out[..out0.len()].copy_from_slice(out0); 155 | Ok(out0.len()) 156 | } 157 | } 158 | } 159 | 160 | struct CipherChaChaPoly { 161 | // NOTE: LessSafeKey is chosen here because nonce atomicity is to be ensured outside of this structure. 162 | // See ring documentation for more details on the naming choices. 163 | key: aead::LessSafeKey, 164 | } 165 | 166 | impl Default for CipherChaChaPoly { 167 | fn default() -> Self { 168 | Self { 169 | key: LessSafeKey::new(UnboundKey::new(&aead::CHACHA20_POLY1305, &[0u8; 32]).unwrap()), 170 | } 171 | } 172 | } 173 | 174 | impl Cipher for CipherChaChaPoly { 175 | fn name(&self) -> &'static str { 176 | "ChaChaPoly" 177 | } 178 | 179 | fn set(&mut self, key: &[u8; CIPHERKEYLEN]) { 180 | self.key = LessSafeKey::new(UnboundKey::new(&aead::CHACHA20_POLY1305, key).unwrap()); 181 | } 182 | 183 | fn encrypt(&self, nonce: u64, authtext: &[u8], plaintext: &[u8], out: &mut [u8]) -> usize { 184 | let mut nonce_bytes = [0u8; 12]; 185 | copy_slices!(&nonce.to_le_bytes(), &mut nonce_bytes[4..]); 186 | let nonce = aead::Nonce::assume_unique_for_key(nonce_bytes); 187 | 188 | out[..plaintext.len()].copy_from_slice(plaintext); 189 | 190 | let tag = self 191 | .key 192 | .seal_in_place_separate_tag( 193 | nonce, 194 | aead::Aad::from(authtext), 195 | &mut out[..plaintext.len()], 196 | ) 197 | .unwrap(); 198 | out[plaintext.len()..plaintext.len() + TAGLEN].copy_from_slice(tag.as_ref()); 199 | 200 | plaintext.len() + TAGLEN 201 | } 202 | 203 | fn decrypt( 204 | &self, 205 | nonce: u64, 206 | authtext: &[u8], 207 | ciphertext: &[u8], 208 | out: &mut [u8], 209 | ) -> Result { 210 | let mut nonce_bytes = [0u8; 12]; 211 | copy_slices!(&nonce.to_le_bytes(), &mut nonce_bytes[4..]); 212 | let nonce = aead::Nonce::assume_unique_for_key(nonce_bytes); 213 | 214 | if out.len() >= ciphertext.len() { 215 | let in_out = &mut out[..ciphertext.len()]; 216 | in_out.copy_from_slice(ciphertext); 217 | 218 | let len = self 219 | .key 220 | .open_in_place(nonce, aead::Aad::from(authtext), in_out) 221 | .map_err(|_| Error::Decrypt)? 222 | .len(); 223 | 224 | Ok(len) 225 | } else { 226 | let mut in_out = ciphertext.to_vec(); 227 | 228 | let out0 = self 229 | .key 230 | .open_in_place(nonce, aead::Aad::from(authtext), &mut in_out) 231 | .map_err(|_| Error::Decrypt)?; 232 | 233 | out[..out0.len()].copy_from_slice(out0); 234 | Ok(out0.len()) 235 | } 236 | } 237 | } 238 | struct HashSHA256 { 239 | context: digest::Context, 240 | } 241 | 242 | impl Default for HashSHA256 { 243 | fn default() -> Self { 244 | Self { context: digest::Context::new(&digest::SHA256) } 245 | } 246 | } 247 | 248 | impl Hash for HashSHA256 { 249 | fn name(&self) -> &'static str { 250 | "SHA256" 251 | } 252 | 253 | fn block_len(&self) -> usize { 254 | 64 255 | } 256 | 257 | fn hash_len(&self) -> usize { 258 | 32 259 | } 260 | 261 | fn reset(&mut self) { 262 | self.context = digest::Context::new(&digest::SHA256); 263 | } 264 | 265 | fn input(&mut self, data: &[u8]) { 266 | self.context.update(data); 267 | } 268 | 269 | fn result(&mut self, out: &mut [u8]) { 270 | out[..32].copy_from_slice(self.context.clone().finish().as_ref()); 271 | } 272 | } 273 | 274 | struct HashSHA512 { 275 | context: digest::Context, 276 | } 277 | 278 | impl Default for HashSHA512 { 279 | fn default() -> Self { 280 | Self { context: digest::Context::new(&digest::SHA512) } 281 | } 282 | } 283 | 284 | impl Hash for HashSHA512 { 285 | fn name(&self) -> &'static str { 286 | "SHA512" 287 | } 288 | 289 | fn block_len(&self) -> usize { 290 | 128 291 | } 292 | 293 | fn hash_len(&self) -> usize { 294 | 64 295 | } 296 | 297 | fn reset(&mut self) { 298 | self.context = digest::Context::new(&digest::SHA512); 299 | } 300 | 301 | fn input(&mut self, data: &[u8]) { 302 | self.context.update(data); 303 | } 304 | 305 | fn result(&mut self, out: &mut [u8]) { 306 | out[..64].copy_from_slice(self.context.clone().finish().as_ref()); 307 | } 308 | } 309 | 310 | #[cfg(test)] 311 | mod tests { 312 | use super::*; 313 | use rand_core::RngCore; 314 | 315 | #[test] 316 | fn test_randomness_sanity() { 317 | use std::collections::HashSet; 318 | 319 | let mut samples = HashSet::new(); 320 | let mut rng = RingRng::default(); 321 | for _ in 0..100_000 { 322 | let mut buf = vec![0u8; 128]; 323 | rng.fill_bytes(&mut buf); 324 | assert!(samples.insert(buf)); 325 | } 326 | } 327 | } 328 | -------------------------------------------------------------------------------- /src/stateless_transportstate.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | cipherstate::StatelessCipherStates, 3 | constants::{CIPHERKEYLEN, MAXDHLEN, MAXMSGLEN, TAGLEN}, 4 | error::{Error, StateProblem}, 5 | handshakestate::HandshakeState, 6 | params::HandshakePattern, 7 | utils::Toggle, 8 | }; 9 | use core::{convert::TryFrom, fmt}; 10 | 11 | /// A state machine encompassing the transport phase of a Noise session, using the two 12 | /// `CipherState`s (for sending and receiving) that were spawned from the `SymmetricState`'s 13 | /// `Split()` method, called after a handshake has been finished. 14 | /// 15 | /// See: 16 | pub struct StatelessTransportState { 17 | cipherstates: StatelessCipherStates, 18 | pattern: HandshakePattern, 19 | dh_len: usize, 20 | rs: Toggle<[u8; MAXDHLEN]>, 21 | initiator: bool, 22 | } 23 | 24 | impl StatelessTransportState { 25 | pub(crate) fn new(handshake: HandshakeState) -> Result { 26 | if !handshake.is_handshake_finished() { 27 | return Err(StateProblem::HandshakeNotFinished.into()); 28 | } 29 | 30 | let dh_len = handshake.dh_len(); 31 | let HandshakeState { cipherstates, params, rs, initiator, .. } = handshake; 32 | let pattern = params.handshake.pattern; 33 | 34 | Ok(Self { cipherstates: cipherstates.into(), pattern, dh_len, rs, initiator }) 35 | } 36 | 37 | /// Get the remote party's static public key, if available. 38 | /// 39 | /// Note: will return `None` if either the chosen Noise pattern 40 | /// doesn't necessitate a remote static key, *or* if the remote 41 | /// static key is not yet known (as can be the case in the `XX` 42 | /// pattern, for example). 43 | #[must_use] 44 | pub fn get_remote_static(&self) -> Option<&[u8]> { 45 | self.rs.get().map(|rs| &rs[..self.dh_len]) 46 | } 47 | 48 | /// Construct a message from `payload` (and pending handshake tokens if in handshake state), 49 | /// and write it to the `message` buffer. 50 | /// 51 | /// Returns the number of bytes written to `message`. 52 | /// 53 | /// # Errors 54 | /// 55 | /// Will result in `Error::Input` if the size of the output exceeds the max message 56 | /// length in the Noise Protocol (65535 bytes). 57 | pub fn write_message( 58 | &self, 59 | nonce: u64, 60 | payload: &[u8], 61 | message: &mut [u8], 62 | ) -> Result { 63 | if !self.initiator && self.pattern.is_oneway() { 64 | return Err(StateProblem::OneWay.into()); 65 | } else if payload.len() + TAGLEN > MAXMSGLEN || payload.len() + TAGLEN > message.len() { 66 | return Err(Error::Input); 67 | } 68 | 69 | let cipher = if self.initiator { &self.cipherstates.0 } else { &self.cipherstates.1 }; 70 | cipher.encrypt(nonce, payload, message) 71 | } 72 | 73 | /// Read a noise message from `message` and write the payload to the `payload` buffer. 74 | /// 75 | /// Returns the number of bytes written to `payload`. 76 | /// 77 | /// # Errors 78 | /// Will result in `Error::Input` if the message is more than 65535 bytes. 79 | /// 80 | /// Will result in `Error::Decrypt` if the contents couldn't be decrypted and/or the 81 | /// authentication tag didn't verify. 82 | /// 83 | /// Will result in `StateProblem::Exhausted` if the max nonce overflows. 84 | pub fn read_message( 85 | &self, 86 | nonce: u64, 87 | payload: &[u8], 88 | message: &mut [u8], 89 | ) -> Result { 90 | if payload.len() > MAXMSGLEN { 91 | Err(Error::Input) 92 | } else if self.initiator && self.pattern.is_oneway() { 93 | Err(StateProblem::OneWay.into()) 94 | } else { 95 | let cipher = if self.initiator { &self.cipherstates.1 } else { &self.cipherstates.0 }; 96 | cipher.decrypt(nonce, payload, message) 97 | } 98 | } 99 | 100 | /// Generate a new key for the egress symmetric cipher according to Section 4.2 101 | /// of the Noise Specification. Synchronizing timing of rekey between initiator and 102 | /// responder is the responsibility of the application, as described in Section 11.3 103 | /// of the Noise Specification. 104 | pub fn rekey_outgoing(&mut self) { 105 | if self.initiator { 106 | self.cipherstates.rekey_initiator(); 107 | } else { 108 | self.cipherstates.rekey_responder(); 109 | } 110 | } 111 | 112 | /// Generate a new key for the ingress symmetric cipher according to Section 4.2 113 | /// of the Noise Specification. Synchronizing timing of rekey between initiator and 114 | /// responder is the responsibility of the application, as described in Section 11.3 115 | /// of the Noise Specification. 116 | pub fn rekey_incoming(&mut self) { 117 | if self.initiator { 118 | self.cipherstates.rekey_responder(); 119 | } else { 120 | self.cipherstates.rekey_initiator(); 121 | } 122 | } 123 | 124 | /// Set a new key for the one or both of the initiator-egress and responder-egress symmetric ciphers. 125 | pub fn rekey_manually( 126 | &mut self, 127 | initiator: Option<&[u8; CIPHERKEYLEN]>, 128 | responder: Option<&[u8; CIPHERKEYLEN]>, 129 | ) { 130 | if let Some(key) = initiator { 131 | self.rekey_initiator_manually(key); 132 | } 133 | if let Some(key) = responder { 134 | self.rekey_responder_manually(key); 135 | } 136 | } 137 | 138 | /// Set a new key for the initiator-egress symmetric cipher. 139 | pub fn rekey_initiator_manually(&mut self, key: &[u8; CIPHERKEYLEN]) { 140 | self.cipherstates.rekey_initiator_manually(key); 141 | } 142 | 143 | /// Set a new key for the responder-egress symmetric cipher. 144 | pub fn rekey_responder_manually(&mut self, key: &[u8; CIPHERKEYLEN]) { 145 | self.cipherstates.rekey_responder_manually(key); 146 | } 147 | 148 | /// Check if this session was started with the "initiator" role. 149 | #[must_use] 150 | pub fn is_initiator(&self) -> bool { 151 | self.initiator 152 | } 153 | } 154 | 155 | impl fmt::Debug for StatelessTransportState { 156 | fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { 157 | fmt.debug_struct("StatelessTransportState").finish() 158 | } 159 | } 160 | 161 | impl TryFrom for StatelessTransportState { 162 | type Error = Error; 163 | 164 | fn try_from(old: HandshakeState) -> Result { 165 | StatelessTransportState::new(old) 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/symmetricstate.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(feature = "std"))] 2 | use alloc::boxed::Box; 3 | 4 | use crate::{ 5 | cipherstate::CipherState, 6 | constants::{CIPHERKEYLEN, MAXHASHLEN}, 7 | error::Error, 8 | types::Hash, 9 | }; 10 | 11 | #[derive(Copy, Clone)] 12 | pub(crate) struct SymmetricStateData { 13 | h: [u8; MAXHASHLEN], 14 | ck: [u8; MAXHASHLEN], 15 | has_key: bool, 16 | } 17 | 18 | impl Default for SymmetricStateData { 19 | fn default() -> Self { 20 | SymmetricStateData { 21 | h: [0_u8; MAXHASHLEN], 22 | ck: [0_u8; MAXHASHLEN], 23 | has_key: false, 24 | } 25 | } 26 | } 27 | 28 | pub(crate) struct SymmetricState { 29 | cipherstate: CipherState, 30 | hasher: Box, 31 | inner: SymmetricStateData, 32 | } 33 | 34 | impl SymmetricState { 35 | pub fn new(cipherstate: CipherState, hasher: Box) -> SymmetricState { 36 | SymmetricState { cipherstate, hasher, inner: SymmetricStateData::default() } 37 | } 38 | 39 | pub fn initialize(&mut self, handshake_name: &str) { 40 | if handshake_name.len() <= self.hasher.hash_len() { 41 | copy_slices!(handshake_name.as_bytes(), self.inner.h); 42 | } else { 43 | self.hasher.reset(); 44 | self.hasher.input(handshake_name.as_bytes()); 45 | self.hasher.result(&mut self.inner.h); 46 | } 47 | copy_slices!(self.inner.h, &mut self.inner.ck); 48 | self.inner.has_key = false; 49 | } 50 | 51 | pub fn mix_key(&mut self, data: &[u8]) { 52 | let hash_len = self.hasher.hash_len(); 53 | let mut hkdf_output = ([0_u8; MAXHASHLEN], [0_u8; MAXHASHLEN]); 54 | self.hasher.hkdf( 55 | &self.inner.ck[..hash_len], 56 | data, 57 | 2, 58 | &mut hkdf_output.0, 59 | &mut hkdf_output.1, 60 | &mut [], 61 | ); 62 | 63 | // TODO(mcginty): use `split_array_ref` once stable to avoid memory inefficiency 64 | let mut cipher_key = [0_u8; CIPHERKEYLEN]; 65 | cipher_key.copy_from_slice(&hkdf_output.1[..CIPHERKEYLEN]); 66 | 67 | self.inner.ck = hkdf_output.0; 68 | self.cipherstate.set(&cipher_key, 0); 69 | self.inner.has_key = true; 70 | } 71 | 72 | pub fn mix_hash(&mut self, data: &[u8]) { 73 | let hash_len = self.hasher.hash_len(); 74 | self.hasher.reset(); 75 | self.hasher.input(&self.inner.h[..hash_len]); 76 | self.hasher.input(data); 77 | self.hasher.result(&mut self.inner.h); 78 | } 79 | 80 | pub fn mix_key_and_hash(&mut self, data: &[u8]) { 81 | let hash_len = self.hasher.hash_len(); 82 | let mut hkdf_output = ([0_u8; MAXHASHLEN], [0_u8; MAXHASHLEN], [0_u8; MAXHASHLEN]); 83 | self.hasher.hkdf( 84 | &self.inner.ck[..hash_len], 85 | data, 86 | 3, 87 | &mut hkdf_output.0, 88 | &mut hkdf_output.1, 89 | &mut hkdf_output.2, 90 | ); 91 | self.inner.ck = hkdf_output.0; 92 | self.mix_hash(&hkdf_output.1[..hash_len]); 93 | 94 | // TODO(mcginty): use `split_array_ref` once stable to avoid memory inefficiency 95 | let mut cipher_key = [0_u8; CIPHERKEYLEN]; 96 | cipher_key.copy_from_slice(&hkdf_output.2[..CIPHERKEYLEN]); 97 | self.cipherstate.set(&cipher_key, 0); 98 | } 99 | 100 | pub fn has_key(&self) -> bool { 101 | self.inner.has_key 102 | } 103 | 104 | /// Encrypt a message and mixes in the hash of the output 105 | pub fn encrypt_and_mix_hash( 106 | &mut self, 107 | plaintext: &[u8], 108 | out: &mut [u8], 109 | ) -> Result { 110 | let hash_len = self.hasher.hash_len(); 111 | let output_len = if self.inner.has_key { 112 | self.cipherstate.encrypt_ad(&self.inner.h[..hash_len], plaintext, out)? 113 | } else { 114 | copy_slices!(plaintext, out); 115 | plaintext.len() 116 | }; 117 | self.mix_hash(&out[..output_len]); 118 | Ok(output_len) 119 | } 120 | 121 | pub fn decrypt_and_mix_hash(&mut self, data: &[u8], out: &mut [u8]) -> Result { 122 | let hash_len = self.hasher.hash_len(); 123 | let payload_len = if self.inner.has_key { 124 | self.cipherstate.decrypt_ad(&self.inner.h[..hash_len], data, out)? 125 | } else { 126 | if out.len() < data.len() { 127 | return Err(Error::Decrypt); 128 | } 129 | copy_slices!(data, out); 130 | data.len() 131 | }; 132 | self.mix_hash(data); 133 | Ok(payload_len) 134 | } 135 | 136 | pub fn split(&mut self, child1: &mut CipherState, child2: &mut CipherState) { 137 | let mut hkdf_output = ([0_u8; MAXHASHLEN], [0_u8; MAXHASHLEN]); 138 | self.split_raw(&mut hkdf_output.0, &mut hkdf_output.1); 139 | 140 | // TODO(mcginty): use `split_array_ref` once stable to avoid memory inefficiency 141 | let mut cipher_keys = ([0_u8; CIPHERKEYLEN], [0_u8; CIPHERKEYLEN]); 142 | cipher_keys.0.copy_from_slice(&hkdf_output.0[..CIPHERKEYLEN]); 143 | cipher_keys.1.copy_from_slice(&hkdf_output.1[..CIPHERKEYLEN]); 144 | child1.set(&cipher_keys.0, 0); 145 | child2.set(&cipher_keys.1, 0); 146 | } 147 | 148 | pub fn split_raw(&mut self, out1: &mut [u8], out2: &mut [u8]) { 149 | let hash_len = self.hasher.hash_len(); 150 | self.hasher.hkdf(&self.inner.ck[..hash_len], &[0_u8; 0], 2, out1, out2, &mut []); 151 | } 152 | 153 | pub(crate) fn checkpoint(&mut self) -> SymmetricStateData { 154 | self.inner 155 | } 156 | 157 | pub(crate) fn restore(&mut self, checkpoint: SymmetricStateData) { 158 | self.inner = checkpoint; 159 | } 160 | 161 | pub fn handshake_hash(&self) -> &[u8] { 162 | let hash_len = self.hasher.hash_len(); 163 | &self.inner.h[..hash_len] 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/transportstate.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | cipherstate::CipherStates, 3 | constants::{CIPHERKEYLEN, MAXDHLEN, MAXMSGLEN, TAGLEN}, 4 | error::{Error, StateProblem}, 5 | handshakestate::HandshakeState, 6 | params::HandshakePattern, 7 | utils::Toggle, 8 | }; 9 | use core::{convert::TryFrom, fmt}; 10 | 11 | /// A state machine encompassing the transport phase of a Noise session, using the two 12 | /// `CipherState`s (for sending and receiving) that were spawned from the `SymmetricState`'s 13 | /// `Split()` method, called after a handshake has been finished. 14 | /// 15 | /// Also see: [the relevant Noise spec section](https://noiseprotocol.org/noise.html#the-handshakestate-object). 16 | pub struct TransportState { 17 | cipherstates: CipherStates, 18 | pattern: HandshakePattern, 19 | dh_len: usize, 20 | rs: Toggle<[u8; MAXDHLEN]>, 21 | initiator: bool, 22 | } 23 | 24 | impl TransportState { 25 | pub(crate) fn new(handshake: HandshakeState) -> Result { 26 | if !handshake.is_handshake_finished() { 27 | return Err(StateProblem::HandshakeNotFinished.into()); 28 | } 29 | 30 | let dh_len = handshake.dh_len(); 31 | let HandshakeState { cipherstates, params, rs, initiator, .. } = handshake; 32 | let pattern = params.handshake.pattern; 33 | 34 | Ok(TransportState { cipherstates, pattern, dh_len, rs, initiator }) 35 | } 36 | 37 | /// Get the remote party's static public key, if available. 38 | /// 39 | /// Note: will return `None` if either the chosen Noise pattern 40 | /// doesn't necessitate a remote static key, *or* if the remote 41 | /// static key is not yet known (as can be the case in the `XX` 42 | /// pattern, for example). 43 | #[must_use] 44 | pub fn get_remote_static(&self) -> Option<&[u8]> { 45 | self.rs.get().map(|rs| &rs[..self.dh_len]) 46 | } 47 | 48 | /// Construct a message from `payload` (and pending handshake tokens if in handshake state), 49 | /// and write it to the `message` buffer. 50 | /// 51 | /// Returns the number of bytes written to `message`. 52 | /// 53 | /// # Errors 54 | /// 55 | /// Will result in `Error::Input` if the size of the output exceeds the max message 56 | /// length in the Noise Protocol (65535 bytes). 57 | pub fn write_message(&mut self, payload: &[u8], message: &mut [u8]) -> Result { 58 | if !self.initiator && self.pattern.is_oneway() { 59 | return Err(StateProblem::OneWay.into()); 60 | } else if payload.len() + TAGLEN > MAXMSGLEN || payload.len() + TAGLEN > message.len() { 61 | return Err(Error::Input); 62 | } 63 | 64 | let cipher = 65 | if self.initiator { &mut self.cipherstates.0 } else { &mut self.cipherstates.1 }; 66 | cipher.encrypt(payload, message) 67 | } 68 | 69 | /// Read a noise message from `message` and write the payload to the `payload` buffer. 70 | /// 71 | /// Returns the number of bytes written to `payload`. 72 | /// 73 | /// # Errors 74 | /// Will result in `Error::Input` if the message is more than 65535 bytes. 75 | /// 76 | /// Will result in `Error::Decrypt` if the contents couldn't be decrypted and/or the 77 | /// authentication tag didn't verify. 78 | /// 79 | /// Will result in `StateProblem::Exhausted` if the max nonce overflows. 80 | pub fn read_message(&mut self, message: &[u8], payload: &mut [u8]) -> Result { 81 | if message.len() > MAXMSGLEN { 82 | Err(Error::Input) 83 | } else if self.initiator && self.pattern.is_oneway() { 84 | Err(StateProblem::OneWay.into()) 85 | } else { 86 | let cipher = 87 | if self.initiator { &mut self.cipherstates.1 } else { &mut self.cipherstates.0 }; 88 | cipher.decrypt(message, payload) 89 | } 90 | } 91 | 92 | /// Generate a new key for the egress symmetric cipher according to Section 4.2 93 | /// of the Noise Specification. Synchronizing timing of rekey between initiator and 94 | /// responder is the responsibility of the application, as described in Section 11.3 95 | /// of the Noise Specification. 96 | pub fn rekey_outgoing(&mut self) { 97 | if self.initiator { 98 | self.cipherstates.rekey_initiator(); 99 | } else { 100 | self.cipherstates.rekey_responder(); 101 | } 102 | } 103 | 104 | /// Generate a new key for the ingress symmetric cipher according to Section 4.2 105 | /// of the Noise Specification. Synchronizing timing of rekey between initiator and 106 | /// responder is the responsibility of the application, as described in Section 11.3 107 | /// of the Noise Specification. 108 | pub fn rekey_incoming(&mut self) { 109 | if self.initiator { 110 | self.cipherstates.rekey_responder(); 111 | } else { 112 | self.cipherstates.rekey_initiator(); 113 | } 114 | } 115 | 116 | /// Set a new key for the one or both of the initiator-egress and responder-egress symmetric ciphers. 117 | pub fn rekey_manually( 118 | &mut self, 119 | initiator: Option<&[u8; CIPHERKEYLEN]>, 120 | responder: Option<&[u8; CIPHERKEYLEN]>, 121 | ) { 122 | if let Some(key) = initiator { 123 | self.rekey_initiator_manually(key); 124 | } 125 | if let Some(key) = responder { 126 | self.rekey_responder_manually(key); 127 | } 128 | } 129 | 130 | /// Set a new key for the initiator-egress symmetric cipher. 131 | pub fn rekey_initiator_manually(&mut self, key: &[u8; CIPHERKEYLEN]) { 132 | self.cipherstates.rekey_initiator_manually(key); 133 | } 134 | 135 | /// Set a new key for the responder-egress symmetric cipher. 136 | pub fn rekey_responder_manually(&mut self, key: &[u8; CIPHERKEYLEN]) { 137 | self.cipherstates.rekey_responder_manually(key); 138 | } 139 | 140 | /// Set the forthcoming *inbound* nonce value. Useful for using noise on lossy transports. 141 | pub fn set_receiving_nonce(&mut self, nonce: u64) { 142 | if self.initiator { 143 | self.cipherstates.1.set_nonce(nonce); 144 | } else { 145 | self.cipherstates.0.set_nonce(nonce); 146 | } 147 | } 148 | 149 | /// Get the forthcoming inbound nonce value. 150 | /// 151 | /// # Errors 152 | /// 153 | /// Will result in `Error::State` if not in transport mode. 154 | #[must_use] 155 | pub fn receiving_nonce(&self) -> u64 { 156 | if self.initiator { 157 | self.cipherstates.1.nonce() 158 | } else { 159 | self.cipherstates.0.nonce() 160 | } 161 | } 162 | 163 | /// Get the forthcoming outbound nonce value. 164 | /// 165 | /// # Errors 166 | /// 167 | /// Will result in `Error::State` if not in transport mode. 168 | #[must_use] 169 | pub fn sending_nonce(&self) -> u64 { 170 | if self.initiator { 171 | self.cipherstates.0.nonce() 172 | } else { 173 | self.cipherstates.1.nonce() 174 | } 175 | } 176 | 177 | /// Check if this session was started with the "initiator" role. 178 | #[must_use] 179 | pub fn is_initiator(&self) -> bool { 180 | self.initiator 181 | } 182 | } 183 | 184 | impl fmt::Debug for TransportState { 185 | fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { 186 | fmt.debug_struct("TransportState").finish() 187 | } 188 | } 189 | 190 | impl TryFrom for TransportState { 191 | type Error = Error; 192 | 193 | fn try_from(old: HandshakeState) -> Result { 194 | TransportState::new(old) 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /src/types.rs: -------------------------------------------------------------------------------- 1 | //! The traits for cryptographic implementations that can be used by Noise. 2 | 3 | use crate::{ 4 | constants::{CIPHERKEYLEN, MAXBLOCKLEN, MAXHASHLEN, TAGLEN}, 5 | Error, 6 | }; 7 | use rand_core::{CryptoRng, RngCore}; 8 | 9 | /// CSPRNG operations 10 | pub trait Random: CryptoRng + RngCore + Send + Sync {} 11 | 12 | /// Diffie-Hellman operations 13 | pub trait Dh: Send + Sync { 14 | /// The string that the Noise spec defines for the primitive 15 | fn name(&self) -> &'static str; 16 | 17 | /// The length in bytes of a public key for this primitive 18 | fn pub_len(&self) -> usize; 19 | 20 | /// The length in bytes of a private key for this primitive 21 | fn priv_len(&self) -> usize; 22 | 23 | /// Set the private key 24 | fn set(&mut self, privkey: &[u8]); 25 | 26 | /// Generate a new private key 27 | fn generate(&mut self, rng: &mut dyn Random); 28 | 29 | /// Get the public key 30 | fn pubkey(&self) -> &[u8]; 31 | 32 | /// Get the private key 33 | fn privkey(&self) -> &[u8]; 34 | 35 | /// Calculate a Diffie-Hellman exchange. 36 | /// 37 | /// # Errors 38 | /// Returns `Error::Dh` in the event that the Diffie-Hellman failed. 39 | fn dh(&self, pubkey: &[u8], out: &mut [u8]) -> Result<(), Error>; 40 | 41 | /// The lenght in bytes of of the DH key exchange. Defaults to the public key. 42 | fn dh_len(&self) -> usize { 43 | self.pub_len() 44 | } 45 | } 46 | 47 | /// Cipher operations 48 | pub trait Cipher: Send + Sync { 49 | /// The string that the Noise spec defines for the primitive 50 | fn name(&self) -> &'static str; 51 | 52 | /// Set the key 53 | fn set(&mut self, key: &[u8; CIPHERKEYLEN]); 54 | 55 | /// Encrypt (with associated data) a given plaintext. 56 | fn encrypt(&self, nonce: u64, authtext: &[u8], plaintext: &[u8], out: &mut [u8]) -> usize; 57 | 58 | /// Decrypt (with associated data) a given ciphertext. 59 | /// 60 | /// # Errors 61 | /// Returns `Error::Decrypt` in the event that the decryption failed. 62 | fn decrypt( 63 | &self, 64 | nonce: u64, 65 | authtext: &[u8], 66 | ciphertext: &[u8], 67 | out: &mut [u8], 68 | ) -> Result; 69 | 70 | /// Rekey according to Section 4.2 of the Noise Specification, with a default 71 | /// implementation guaranteed to be secure for all ciphers. 72 | fn rekey(&mut self) { 73 | let mut ciphertext = [0; CIPHERKEYLEN + TAGLEN]; 74 | let ciphertext_len = self.encrypt(u64::MAX, &[], &[0; CIPHERKEYLEN], &mut ciphertext); 75 | assert_eq!(ciphertext_len, ciphertext.len(), "unexpected ciphertext length for rekey"); 76 | 77 | // TODO(mcginty): use `split_array_ref` once stable to avoid memory inefficiency 78 | let mut key = [0_u8; CIPHERKEYLEN]; 79 | key.copy_from_slice(&ciphertext[..CIPHERKEYLEN]); 80 | 81 | self.set(&key); 82 | } 83 | } 84 | 85 | /// Hashing operations 86 | pub trait Hash: Send + Sync { 87 | /// The string that the Noise spec defines for the primitive 88 | fn name(&self) -> &'static str; 89 | 90 | /// The block length for the primitive 91 | fn block_len(&self) -> usize; 92 | 93 | /// The final hash digest length for the primitive 94 | fn hash_len(&self) -> usize; 95 | 96 | /// Reset the internal state 97 | fn reset(&mut self); 98 | 99 | /// Provide input to the internal state 100 | fn input(&mut self, data: &[u8]); 101 | 102 | /// Get the resulting hash 103 | fn result(&mut self, out: &mut [u8]); 104 | 105 | /// Calculate HMAC, as specified in the Noise spec. 106 | /// 107 | /// NOTE: This method clobbers the existing internal state 108 | fn hmac(&mut self, key: &[u8], data: &[u8], out: &mut [u8]) { 109 | assert!(key.len() <= self.block_len(), "unexpectedly large key length for hmac"); 110 | let block_len = self.block_len(); 111 | let hash_len = self.hash_len(); 112 | let mut ipad = [0x36_u8; MAXBLOCKLEN]; 113 | let mut opad = [0x5c_u8; MAXBLOCKLEN]; 114 | for count in 0..key.len() { 115 | ipad[count] ^= key[count]; 116 | opad[count] ^= key[count]; 117 | } 118 | self.reset(); 119 | self.input(&ipad[..block_len]); 120 | self.input(data); 121 | let mut inner_output = [0_u8; MAXHASHLEN]; 122 | self.result(&mut inner_output); 123 | self.reset(); 124 | self.input(&opad[..block_len]); 125 | self.input(&inner_output[..hash_len]); 126 | self.result(out); 127 | } 128 | 129 | /// Derive keys as specified in the Noise spec. 130 | /// 131 | /// NOTE: This method clobbers the existing internal state 132 | fn hkdf( 133 | &mut self, 134 | chaining_key: &[u8], 135 | input_key_material: &[u8], 136 | outputs: usize, 137 | out1: &mut [u8], 138 | out2: &mut [u8], 139 | out3: &mut [u8], 140 | ) { 141 | let hash_len = self.hash_len(); 142 | let mut temp_key = [0_u8; MAXHASHLEN]; 143 | self.hmac(chaining_key, input_key_material, &mut temp_key); 144 | self.hmac(&temp_key, &[1_u8], out1); 145 | if outputs == 1 { 146 | return; 147 | } 148 | 149 | let mut in2 = [0_u8; MAXHASHLEN + 1]; 150 | copy_slices!(out1[0..hash_len], &mut in2); 151 | in2[hash_len] = 2; 152 | self.hmac(&temp_key, &in2[..=hash_len], out2); 153 | if outputs == 2 { 154 | return; 155 | } 156 | 157 | let mut in3 = [0_u8; MAXHASHLEN + 1]; 158 | copy_slices!(out2[0..hash_len], &mut in3); 159 | in3[hash_len] = 3; 160 | self.hmac(&temp_key, &in3[..=hash_len], out3); 161 | } 162 | } 163 | 164 | /// Kem operations. 165 | #[cfg(feature = "hfs")] 166 | pub trait Kem: Send + Sync { 167 | /// The string that the Noise spec defines for the primitive. 168 | fn name(&self) -> &'static str; 169 | 170 | /// The length in bytes of a public key for this primitive. 171 | fn pub_len(&self) -> usize; 172 | 173 | /// The length in bytes the Kem cipherthext for this primitive. 174 | fn ciphertext_len(&self) -> usize; 175 | 176 | /// Shared secret length in bytes that this Kem encapsulates. 177 | fn shared_secret_len(&self) -> usize; 178 | 179 | /// Generate a new private key. 180 | fn generate(&mut self, rng: &mut dyn Random); 181 | 182 | /// Get the public key 183 | fn pubkey(&self) -> &[u8]; 184 | 185 | /// Generate a shared secret and encapsulate it using this Kem. 186 | /// 187 | /// # Errors 188 | /// Returns `Error::Kem` if the public key is invalid. 189 | #[must_use = "returned value includes needed length values for output slices"] 190 | fn encapsulate( 191 | &self, 192 | pubkey: &[u8], 193 | shared_secret_out: &mut [u8], 194 | ciphertext_out: &mut [u8], 195 | ) -> Result<(usize, usize), Error>; 196 | 197 | /// Decapsulate a ciphertext producing a shared secret. 198 | /// 199 | /// # Errors 200 | /// Returns `Error::Kem` if the ciphertext is invalid. 201 | #[must_use = "returned value includes needed length value for output slice"] 202 | fn decapsulate(&self, ciphertext: &[u8], shared_secret_out: &mut [u8]) -> Result; 203 | } 204 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | use core::ops::{Deref, DerefMut}; 2 | 3 | /// Toggle is similar to Option, except that even in the Off/"None" case, there is still 4 | /// an owned allocated inner object. This is useful for holding onto pre-allocated objects 5 | /// that can be toggled as enabled. 6 | pub struct Toggle { 7 | inner: T, 8 | on: bool, 9 | } 10 | 11 | impl Toggle { 12 | pub fn on(inner: T) -> Self { 13 | Self { inner, on: true } 14 | } 15 | 16 | pub fn off(inner: T) -> Self { 17 | Self { inner, on: false } 18 | } 19 | 20 | pub fn enable(&mut self) { 21 | self.on = true; 22 | } 23 | 24 | pub fn is_on(&self) -> bool { 25 | self.on 26 | } 27 | 28 | pub fn get(&self) -> Option<&T> { 29 | if self.on { 30 | Some(&self.inner) 31 | } else { 32 | None 33 | } 34 | } 35 | } 36 | 37 | impl Deref for Toggle { 38 | type Target = T; 39 | 40 | fn deref(&self) -> &T { 41 | &self.inner 42 | } 43 | } 44 | 45 | impl DerefMut for Toggle { 46 | fn deref_mut(&mut self) -> &mut T { 47 | &mut self.inner 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /tests/vectors.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "vector-tests")] 2 | #![allow(clippy::std_instead_of_core)] 3 | #[macro_use] 4 | extern crate serde_derive; 5 | 6 | use hex::FromHex; 7 | use rand::RngCore; 8 | use serde::{ 9 | de::{self, Deserialize, Deserializer, Unexpected, Visitor}, 10 | ser::{Serialize, Serializer}, 11 | }; 12 | use snow::{params::*, Builder, HandshakeState}; 13 | use std::{ 14 | fmt, 15 | fmt::Write as _, 16 | fs::{File, OpenOptions}, 17 | io::Read, 18 | marker::PhantomData, 19 | ops::Deref, 20 | }; 21 | 22 | #[derive(Clone)] 23 | struct HexBytes { 24 | original: String, 25 | payload: T, 26 | } 27 | 28 | impl> From for HexBytes { 29 | fn from(payload: T) -> Self { 30 | Self { original: hex::encode(&payload), payload } 31 | } 32 | } 33 | 34 | impl Deref for HexBytes { 35 | type Target = T; 36 | 37 | fn deref(&self) -> &Self::Target { 38 | &self.payload 39 | } 40 | } 41 | 42 | impl fmt::Debug for HexBytes { 43 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 44 | write!(f, "{:?}", self.original) 45 | } 46 | } 47 | 48 | struct HexBytesVisitor>(PhantomData); 49 | impl + FromHex> Visitor<'_> for HexBytesVisitor { 50 | type Value = HexBytes; 51 | 52 | fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 53 | write!(formatter, "a hex string") 54 | } 55 | 56 | fn visit_str(self, s: &str) -> Result 57 | where 58 | E: de::Error, 59 | { 60 | let bytes = 61 | T::from_hex(s).map_err(|_| de::Error::invalid_value(Unexpected::Str(s), &self))?; 62 | Ok(HexBytes { original: s.to_owned(), payload: bytes }) 63 | } 64 | } 65 | 66 | impl<'de, T: AsRef<[u8]> + FromHex> Deserialize<'de> for HexBytes { 67 | fn deserialize(deserializer: D) -> Result, D::Error> 68 | where 69 | D: Deserializer<'de>, 70 | { 71 | deserializer.deserialize_str(HexBytesVisitor(PhantomData)) 72 | } 73 | } 74 | 75 | impl> Serialize for HexBytes { 76 | fn serialize(&self, serializer: S) -> Result 77 | where 78 | S: Serializer, 79 | { 80 | serializer.serialize_str(&hex::encode(&self.payload)) 81 | } 82 | } 83 | 84 | #[derive(Serialize, Deserialize)] 85 | struct TestMessage { 86 | payload: HexBytes>, 87 | ciphertext: HexBytes>, 88 | } 89 | 90 | impl fmt::Debug for TestMessage { 91 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 92 | write!(f, "Message") 93 | } 94 | } 95 | 96 | #[derive(Serialize, Deserialize, Debug)] 97 | struct TestVector { 98 | #[serde(skip_serializing_if = "Option::is_none")] 99 | name: Option, 100 | 101 | protocol_name: String, 102 | 103 | #[serde(skip_serializing_if = "Option::is_none")] 104 | hybrid: Option, 105 | 106 | #[serde(skip_serializing_if = "Option::is_none")] 107 | fail: Option, 108 | 109 | #[serde(skip_serializing_if = "Option::is_none")] 110 | fallback: Option, 111 | 112 | #[serde(skip_serializing_if = "Option::is_none")] 113 | fallback_pattern: Option, 114 | 115 | init_prologue: HexBytes>, 116 | 117 | #[serde(skip_serializing_if = "Option::is_none")] 118 | init_psks: Option>>, 119 | 120 | #[serde(skip_serializing_if = "Option::is_none")] 121 | init_static: Option>>, 122 | 123 | #[serde(skip_serializing_if = "Option::is_none")] 124 | init_ephemeral: Option>>, 125 | 126 | #[serde(skip_serializing_if = "Option::is_none")] 127 | init_remote_static: Option>>, 128 | 129 | resp_prologue: HexBytes>, 130 | #[serde(skip_serializing_if = "Option::is_none")] 131 | resp_psks: Option>>, 132 | #[serde(skip_serializing_if = "Option::is_none")] 133 | resp_static: Option>>, 134 | #[serde(skip_serializing_if = "Option::is_none")] 135 | resp_ephemeral: Option>>, 136 | #[serde(skip_serializing_if = "Option::is_none")] 137 | resp_remote_static: Option>>, 138 | 139 | messages: Vec, 140 | } 141 | 142 | #[derive(Serialize, Deserialize)] 143 | struct TestVectors { 144 | vectors: Vec, 145 | } 146 | 147 | fn build_session_pair(vector: &TestVector) -> Result<(HandshakeState, HandshakeState), String> { 148 | let params: NoiseParams = vector.protocol_name.parse().unwrap(); 149 | let mut init_builder = Builder::new(params.clone()); 150 | let mut resp_builder = Builder::new(params.clone()); 151 | 152 | if params.handshake.is_psk() { 153 | let mut psk_index = 0; 154 | if let (Some(ipsks), Some(rpsks)) = (&vector.init_psks, &vector.resp_psks) { 155 | for modifier in params.handshake.modifiers.list { 156 | if let HandshakeModifier::Psk(n) = modifier { 157 | init_builder = init_builder.psk(n, &ipsks[psk_index]).unwrap(); 158 | resp_builder = resp_builder.psk(n, &rpsks[psk_index]).unwrap(); 159 | psk_index += 1; 160 | } 161 | } 162 | } else { 163 | return Err("missing PSKs for a PSK-mode handshake".into()); 164 | } 165 | } 166 | 167 | if let Some(ref init_s) = vector.init_static { 168 | init_builder = init_builder.local_private_key(init_s).unwrap(); 169 | } 170 | if let Some(ref resp_s) = vector.resp_static { 171 | resp_builder = resp_builder.local_private_key(resp_s).unwrap(); 172 | } 173 | if let Some(ref init_remote_static) = vector.init_remote_static { 174 | init_builder = init_builder.remote_public_key(init_remote_static).unwrap(); 175 | } 176 | if let Some(ref resp_remote_static) = vector.resp_remote_static { 177 | resp_builder = resp_builder.remote_public_key(resp_remote_static).unwrap(); 178 | } 179 | if let Some(ref init_e) = vector.init_ephemeral { 180 | init_builder = init_builder.fixed_ephemeral_key_for_testing_only(init_e); 181 | } 182 | if let Some(ref resp_e) = vector.resp_ephemeral { 183 | resp_builder = resp_builder.fixed_ephemeral_key_for_testing_only(resp_e); 184 | } 185 | 186 | let init = init_builder 187 | .prologue(&vector.init_prologue) 188 | .unwrap() 189 | .build_initiator() 190 | .map_err(|e| format!("{e:?}"))?; 191 | let resp = resp_builder 192 | .prologue(&vector.resp_prologue) 193 | .unwrap() 194 | .build_responder() 195 | .map_err(|e| format!("{e:?}"))?; 196 | 197 | Ok((init, resp)) 198 | } 199 | 200 | fn confirm_message_vectors( 201 | mut init_hs: HandshakeState, 202 | mut resp_hs: HandshakeState, 203 | messages: &[TestMessage], 204 | is_oneway: bool, 205 | ) -> Result<(), String> { 206 | let (mut sendbuf, mut recvbuf) = 207 | (vec![0_u8; 65535].into_boxed_slice(), vec![0_u8; 65535].into_boxed_slice()); 208 | let mut messages_iter = messages.iter().enumerate(); 209 | while !init_hs.is_handshake_finished() { 210 | let (i, message) = messages_iter.next().unwrap(); 211 | let (send, recv) = 212 | if i % 2 == 0 { (&mut init_hs, &mut resp_hs) } else { (&mut resp_hs, &mut init_hs) }; 213 | 214 | let len = send 215 | .write_message(&message.payload, &mut sendbuf) 216 | .map_err(|_| format!("write_message failed on message {i}"))?; 217 | let recv_len = recv 218 | .read_message(&sendbuf[..len], &mut recvbuf) 219 | .map_err(|_| format!("read_message failed on message {i}"))?; 220 | if sendbuf[..len] != (*message.ciphertext)[..] || *message.payload != recvbuf[..recv_len] { 221 | let mut s = String::new(); 222 | writeln!(&mut s, "message {i}").unwrap(); 223 | writeln!(&mut s, "plaintext: {}", hex::encode(&*message.payload)).unwrap(); 224 | writeln!(&mut s, "expected: {}", hex::encode(&*message.ciphertext)).unwrap(); 225 | writeln!(&mut s, "actual: {}", hex::encode(&sendbuf[..len])).unwrap(); 226 | return Err(s); 227 | } 228 | } 229 | 230 | let (mut init, mut resp) = 231 | (init_hs.into_transport_mode().unwrap(), resp_hs.into_transport_mode().unwrap()); 232 | for (i, message) in messages_iter { 233 | let (send, recv) = 234 | if is_oneway || i % 2 == 0 { (&mut init, &mut resp) } else { (&mut resp, &mut init) }; 235 | 236 | let len = send.write_message(&message.payload, &mut sendbuf).unwrap(); 237 | let recv_len = recv.read_message(&sendbuf[..len], &mut recvbuf).unwrap(); 238 | if sendbuf[..len] != (*message.ciphertext)[..] || *message.payload != recvbuf[..recv_len] { 239 | let mut s = String::new(); 240 | writeln!(&mut s, "message {i}").unwrap(); 241 | writeln!(&mut s, "plaintext : {}", hex::encode(&*message.payload)).unwrap(); 242 | writeln!(&mut s, "expected ciphertext: {}", hex::encode(&*message.ciphertext)).unwrap(); 243 | writeln!( 244 | &mut s, 245 | "actual ciphertext : {}", 246 | hex::encode(&sendbuf[..message.ciphertext.len()]) 247 | ) 248 | .unwrap(); 249 | writeln!(&mut s, "actual plaintext : {}", hex::encode(&recvbuf[..recv_len])).unwrap(); 250 | return Err(s); 251 | } 252 | } 253 | Ok(()) 254 | } 255 | 256 | fn test_vectors_from_json(json: &str) { 257 | let test_vectors: TestVectors = serde_json::from_str(json).unwrap(); 258 | 259 | let mut passes = 0; 260 | let mut fails = 0; 261 | let mut ignored = 0; 262 | 263 | for vector in test_vectors.vectors { 264 | let Ok(params) = vector.protocol_name.parse::() else { 265 | ignored += 1; 266 | continue; 267 | }; 268 | if params.dh == DHChoice::Curve448 { 269 | ignored += 1; 270 | continue; 271 | } 272 | 273 | let (init, resp) = match build_session_pair(&vector) { 274 | Ok((init, resp)) => (init, resp), 275 | Err(s) => { 276 | fails += 1; 277 | println!("FAIL"); 278 | println!("{s}"); 279 | println!("{vector:?}"); 280 | continue; 281 | }, 282 | }; 283 | 284 | match confirm_message_vectors( 285 | init, 286 | resp, 287 | &vector.messages, 288 | params.handshake.pattern.is_oneway(), 289 | ) { 290 | Ok(()) => { 291 | passes += 1; 292 | }, 293 | Err(s) => { 294 | fails += 1; 295 | println!("FAIL"); 296 | println!("{s}"); 297 | println!("{vector:?}"); 298 | }, 299 | } 300 | } 301 | 302 | println!("\n{}/{} passed", passes, passes + fails); 303 | println!("* ignored {ignored} unsupported variants"); 304 | assert!(fails <= 0, "at least one vector failed."); 305 | } 306 | 307 | fn random_slice() -> [u8; N] { 308 | let mut v = [0_u8; N]; 309 | let mut rng = rand::thread_rng(); 310 | rng.fill_bytes(&mut v); 311 | v 312 | } 313 | 314 | fn random_vec(size: usize) -> Vec { 315 | let mut v = vec![0_u8; size]; 316 | let mut rng = rand::thread_rng(); 317 | rng.fill_bytes(&mut v); 318 | v 319 | } 320 | 321 | fn get_psks_count(params: &NoiseParams) -> usize { 322 | params 323 | .handshake 324 | .modifiers 325 | .list 326 | .iter() 327 | .filter(|m| matches!(m, HandshakeModifier::Psk(_))) 328 | .count() 329 | } 330 | 331 | #[allow(clippy::too_many_lines)] 332 | fn generate_vector(params: NoiseParams) -> TestVector { 333 | let prologue = b"There is no right and wrong. There's only fun and boring.".to_vec(); 334 | let (mut psks, mut psks_hex) = (vec![], vec![]); 335 | let mut init_b: Builder<'_> = Builder::new(params.clone()); 336 | let mut resp_b: Builder<'_> = Builder::new(params.clone()); 337 | let is = init_b.generate_keypair().unwrap(); 338 | let ie = init_b.generate_keypair().unwrap(); 339 | let rs = resp_b.generate_keypair().unwrap(); 340 | let re = resp_b.generate_keypair().unwrap(); 341 | 342 | for _ in 0..get_psks_count(¶ms) { 343 | let v = random_slice::<32>(); 344 | psks_hex.push(v.into()); 345 | psks.push(v); 346 | } 347 | 348 | let mut psk_index = 0; 349 | for modifier in params.handshake.modifiers.list { 350 | if let HandshakeModifier::Psk(n) = modifier { 351 | init_b = init_b.psk(n, &psks[psk_index]).unwrap(); 352 | resp_b = resp_b.psk(n, &psks[psk_index]).unwrap(); 353 | psk_index += 1; 354 | } 355 | } 356 | init_b = init_b.fixed_ephemeral_key_for_testing_only(&ie.private); 357 | init_b = init_b.prologue(&prologue).unwrap(); 358 | if params.handshake.pattern.needs_local_static_key(true) { 359 | init_b = init_b.local_private_key(&is.private).unwrap(); 360 | } 361 | if params.handshake.pattern.need_known_remote_pubkey(true) { 362 | init_b = init_b.remote_public_key(&rs.public).unwrap(); 363 | } 364 | 365 | resp_b = resp_b.fixed_ephemeral_key_for_testing_only(&re.private); 366 | resp_b = resp_b.prologue(&prologue).unwrap(); 367 | if params.handshake.pattern.needs_local_static_key(false) { 368 | resp_b = resp_b.local_private_key(&rs.private).unwrap(); 369 | } 370 | if params.handshake.pattern.need_known_remote_pubkey(false) { 371 | resp_b = resp_b.remote_public_key(&is.public).unwrap(); 372 | } 373 | 374 | let mut init = init_b.build_initiator().unwrap(); 375 | let mut resp = resp_b.build_responder().unwrap(); 376 | 377 | let (mut ibuf, mut obuf) = 378 | (vec![0_u8; 65535].into_boxed_slice(), vec![0_u8; 65535].into_boxed_slice()); 379 | let mut messages = vec![]; 380 | let mut i = 0; 381 | while !(init.is_handshake_finished() && resp.is_handshake_finished()) { 382 | let payload = random_vec(32); 383 | let len = init.write_message(&payload, &mut ibuf).unwrap(); 384 | messages.push(TestMessage { 385 | payload: payload.clone().into(), 386 | ciphertext: ibuf[..len].to_vec().into(), 387 | }); 388 | i += 1; 389 | let _ = resp.read_message(&ibuf[..len], &mut obuf).unwrap(); 390 | 391 | if init.is_handshake_finished() && resp.is_handshake_finished() { 392 | break; 393 | } 394 | 395 | let payload = random_vec(32); 396 | let len = resp.write_message(&payload, &mut ibuf).unwrap(); 397 | messages.push(TestMessage { 398 | payload: payload.clone().into(), 399 | ciphertext: ibuf[..len].to_vec().into(), 400 | }); 401 | i += 1; 402 | let _ = init.read_message(&ibuf[..len], &mut obuf).unwrap(); 403 | } 404 | 405 | let (mut init_tr, mut resp_tr) = 406 | (init.into_transport_mode().unwrap(), resp.into_transport_mode().unwrap()); 407 | 408 | let (init, resp) = if params.handshake.pattern.is_oneway() || i % 2 == 0 { 409 | (&mut init_tr, &mut resp_tr) 410 | } else { 411 | (&mut resp_tr, &mut init_tr) 412 | }; 413 | 414 | let payload = random_vec(32); 415 | let len = init.write_message(&payload, &mut ibuf).unwrap(); 416 | messages.push(TestMessage { 417 | payload: payload.clone().into(), 418 | ciphertext: ibuf[..len].to_vec().into(), 419 | }); 420 | 421 | if !params.handshake.pattern.is_oneway() { 422 | let payload = random_vec(32); 423 | let len = resp.write_message(&payload, &mut obuf).unwrap(); 424 | messages.push(TestMessage { 425 | payload: payload.clone().into(), 426 | ciphertext: obuf[..len].to_vec().into(), 427 | }); 428 | } 429 | 430 | let init_static = if params.handshake.pattern.needs_local_static_key(true) { 431 | Some(is.private.clone().into()) 432 | } else { 433 | None 434 | }; 435 | let resp_static = if params.handshake.pattern.needs_local_static_key(false) { 436 | Some(rs.private.clone().into()) 437 | } else { 438 | None 439 | }; 440 | let init_remote_static = if params.handshake.pattern.need_known_remote_pubkey(true) { 441 | Some(rs.public.clone().into()) 442 | } else { 443 | None 444 | }; 445 | let resp_remote_static = if params.handshake.pattern.need_known_remote_pubkey(false) { 446 | Some(is.public.clone().into()) 447 | } else { 448 | None 449 | }; 450 | 451 | TestVector { 452 | name: None, 453 | protocol_name: params.name, 454 | hybrid: None, 455 | fail: None, 456 | fallback: None, 457 | fallback_pattern: None, 458 | init_prologue: prologue.clone().into(), 459 | init_psks: Some(psks_hex.clone()), 460 | init_static, 461 | init_ephemeral: Some(ie.private.clone().into()), 462 | init_remote_static, 463 | resp_prologue: prologue.clone().into(), 464 | resp_psks: Some(psks_hex.clone()), 465 | resp_static, 466 | resp_ephemeral: Some(re.private.clone().into()), 467 | resp_remote_static, 468 | messages, 469 | } 470 | } 471 | 472 | fn generate_vector_set(official: bool) -> TestVectors { 473 | let mut handshakes = 474 | SUPPORTED_HANDSHAKE_PATTERNS.iter().map(|p| p.as_str()).collect::>(); 475 | handshakes.extend_from_slice(&[ 476 | "NNpsk0+psk2", 477 | "NXpsk0+psk1+psk2", 478 | "XNpsk1+psk3", 479 | "XKpsk0+psk3", 480 | "KNpsk1+psk2", 481 | "KKpsk0+psk2", 482 | "INpsk1+psk2", 483 | "IKpsk0+psk2", 484 | "IXpsk0+psk2", 485 | "XXpsk0+psk1", 486 | "XXpsk0+psk2", 487 | "XXpsk0+psk3", 488 | "XXpsk0+psk1+psk2+psk3", 489 | ]); 490 | let dhs = if official { vec!["25519"] } else { vec!["P256"] }; 491 | let ciphers = if official { vec!["ChaChaPoly", "AESGCM"] } else { vec!["XChaChaPoly"] }; 492 | let hashes = vec!["BLAKE2s", "BLAKE2b", "SHA256", "SHA512"]; 493 | 494 | let mut vectors = vec![]; 495 | 496 | for handshake in &handshakes { 497 | for dh in &dhs { 498 | for cipher in &ciphers { 499 | for hash in &hashes { 500 | let protocol_name = format!("Noise_{handshake}_{dh}_{cipher}_{hash}"); 501 | let protocol = protocol_name.parse().unwrap(); 502 | vectors.push(generate_vector(protocol)); 503 | } 504 | } 505 | } 506 | } 507 | TestVectors { vectors } 508 | } 509 | 510 | #[test] 511 | fn test_vectors_cacophony() { 512 | test_vectors_from_json(include_str!("vectors/cacophony.txt")); 513 | } 514 | 515 | /// These are the test vectors for all the official "spec 34" features. 516 | #[test] 517 | fn test_vectors_snow() { 518 | let file_res = OpenOptions::new().write(true).create_new(true).open("tests/vectors/snow.txt"); 519 | if let Ok(mut file) = file_res { 520 | serde_json::to_writer_pretty(&mut file, &generate_vector_set(true)).unwrap(); 521 | } 522 | let mut file = File::open("tests/vectors/snow.txt").unwrap(); 523 | let mut contents = String::new(); 524 | file.read_to_string(&mut contents).unwrap(); 525 | test_vectors_from_json(&contents); 526 | } 527 | 528 | /// These are the test vectors for non-standard features. 529 | #[test] 530 | fn test_vectors_snow_extended() { 531 | let file_res = 532 | OpenOptions::new().write(true).create_new(true).open("tests/vectors/snow-extended.txt"); 533 | if let Ok(mut file) = file_res { 534 | serde_json::to_writer_pretty(&mut file, &generate_vector_set(false)).unwrap(); 535 | } 536 | let mut file = File::open("tests/vectors/snow-extended.txt").unwrap(); 537 | let mut contents = String::new(); 538 | file.read_to_string(&mut contents).unwrap(); 539 | test_vectors_from_json(&contents); 540 | } 541 | --------------------------------------------------------------------------------