From 1c27302d191bddef390a3d5cc52661d1ae30c397 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Mon, 19 Jun 2023 21:17:12 -0600 Subject: [PATCH] ssh-cipher: extract from `ssh-key` Extracts symmetric encryption support into its own `ssh-cipher` crate which can be used independently from `ssh-key`. --- .github/workflows/ssh-cipher.yml | 58 +++++ .github/workflows/ssh-encoding.yml | 2 +- .github/workflows/ssh-key.yml | 5 +- Cargo.lock | 24 +- Cargo.toml | 1 + ssh-cipher/CHANGELOG.md | 5 + ssh-cipher/Cargo.toml | 45 ++++ ssh-cipher/LICENSE-APACHE | 201 ++++++++++++++++ ssh-cipher/LICENSE-MIT | 25 ++ ssh-cipher/README.md | 60 +++++ .../src/cipher.rs => ssh-cipher/src/lib.rs | 220 ++++++++++++------ ssh-encoding/LICENSE-MIT | 2 +- ssh-key/Cargo.toml | 25 +- ssh-key/LICENSE-MIT | 2 +- ssh-key/src/error.rs | 6 + ssh-key/src/lib.rs | 3 +- ssh-key/src/private.rs | 7 +- ssh-key/tests/encrypted_private_key.rs | 40 ++-- 18 files changed, 603 insertions(+), 128 deletions(-) create mode 100644 .github/workflows/ssh-cipher.yml create mode 100644 ssh-cipher/CHANGELOG.md create mode 100644 ssh-cipher/Cargo.toml create mode 100644 ssh-cipher/LICENSE-APACHE create mode 100644 ssh-cipher/LICENSE-MIT create mode 100644 ssh-cipher/README.md rename ssh-key/src/cipher.rs => ssh-cipher/src/lib.rs (72%) diff --git a/.github/workflows/ssh-cipher.yml b/.github/workflows/ssh-cipher.yml new file mode 100644 index 00000000..b48a2f79 --- /dev/null +++ b/.github/workflows/ssh-cipher.yml @@ -0,0 +1,58 @@ +name: ssh-cipher + +on: + pull_request: + paths: + - ".github/workflows/ssh-cipher.yml" + - "ssh-cipher/**" + - "ssh-encoding/**" + - "Cargo.*" + push: + branches: master + +defaults: + run: + working-directory: ssh-cipher + +env: + RUSTFLAGS: "-Dwarnings" + +jobs: + minimal-versions: + uses: RustCrypto/actions/.github/workflows/minimal-versions.yml@master + with: + working-directory: ${{ github.workflow }} + + no_std: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - 1.60.0 # MSRV + - stable + target: + - thumbv7em-none-eabi + - wasm32-unknown-unknown + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@stable + with: + toolchain: ${{ matrix.rust }} + target: ${{ matrix.target }} + - uses: RustCrypto/actions/cargo-hack-install@master + - run: cargo hack build --target ${{ matrix.target }} --feature-powerset --exclude-features default,std + + test: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - 1.60.0 # MSRV + - stable + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@stable + with: + toolchain: ${{ matrix.rust }} + - uses: RustCrypto/actions/cargo-hack-install@master + - run: cargo hack test --feature-powerset diff --git a/.github/workflows/ssh-encoding.yml b/.github/workflows/ssh-encoding.yml index 9dd6e17d..deff0283 100644 --- a/.github/workflows/ssh-encoding.yml +++ b/.github/workflows/ssh-encoding.yml @@ -39,7 +39,7 @@ jobs: toolchain: ${{ matrix.rust }} target: ${{ matrix.target }} - uses: RustCrypto/actions/cargo-hack-install@master - - run: cargo hack build --target ${{ matrix.target }} --feature-powerset --exclude-features default,getrandom,std + - run: cargo hack build --target ${{ matrix.target }} --feature-powerset --exclude-features default,std test: runs-on: ubuntu-latest diff --git a/.github/workflows/ssh-key.yml b/.github/workflows/ssh-key.yml index 565b9ad2..aeac708a 100644 --- a/.github/workflows/ssh-key.yml +++ b/.github/workflows/ssh-key.yml @@ -4,6 +4,8 @@ on: pull_request: paths: - ".github/workflows/ssh-key.yml" + - "ssh-cipher/**" + - "ssh-encoding/**" - "ssh-key/**" - "Cargo.*" push: @@ -84,9 +86,8 @@ jobs: with: toolchain: ${{ matrix.rust }} - uses: RustCrypto/actions/cargo-hack-install@master - - run: cargo hack test --feature-powerset --exclude-features aes-gcm,crypto,default,encryption,getrandom,std --release + - run: cargo hack test --feature-powerset --exclude-features crypto,default,encryption,getrandom,std --release - run: cargo test --release - - run: cargo test --release --features aes-gcm - run: cargo test --release --features crypto - run: cargo test --release --features encryption - run: cargo test --release --features getrandom diff --git a/Cargo.lock b/Cargo.lock index d298651a..c26c3237 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -714,6 +714,22 @@ dependencies = [ "der", ] +[[package]] +name = "ssh-cipher" +version = "0.1.0-pre" +dependencies = [ + "aes", + "aes-gcm", + "cbc", + "chacha20", + "cipher", + "ctr", + "des", + "poly1305", + "ssh-encoding", + "subtle", +] + [[package]] name = "ssh-encoding" version = "0.2.0-pre" @@ -728,20 +744,13 @@ dependencies = [ name = "ssh-key" version = "0.6.0-rc.0" dependencies = [ - "aes", - "aes-gcm", "bcrypt-pbkdf", - "cbc", - "chacha20", - "ctr", - "des", "dsa", "ed25519-dalek", "hex-literal", "num-bigint-dig", "p256", "p384", - "poly1305", "rand_chacha", "rand_core", "rsa", @@ -750,6 +759,7 @@ dependencies = [ "sha1", "sha2", "signature", + "ssh-cipher", "ssh-encoding", "subtle", "zeroize", diff --git a/Cargo.toml b/Cargo.toml index dcae887c..e0ac07a3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [workspace] resolver = "2" members = [ + "ssh-cipher", "ssh-encoding", "ssh-key" ] diff --git a/ssh-cipher/CHANGELOG.md b/ssh-cipher/CHANGELOG.md new file mode 100644 index 00000000..d6637e04 --- /dev/null +++ b/ssh-cipher/CHANGELOG.md @@ -0,0 +1,5 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). diff --git a/ssh-cipher/Cargo.toml b/ssh-cipher/Cargo.toml new file mode 100644 index 00000000..a7f67c82 --- /dev/null +++ b/ssh-cipher/Cargo.toml @@ -0,0 +1,45 @@ +[package] +name = "ssh-cipher" +version = "0.1.0-pre" +description = """ +Pure Rust implementation of SSH symmetric encryption including support for the +modern aes128-gcm@openssh.com/aes256-gcm@openssh.com and +chacha20-poly1305@openssh.com algorithms as well as legacy support for older +ciphers. Built on the pure Rust cryptography implementations maintained by the +RustCrypto organization. +""" +authors = ["RustCrypto Developers"] +license = "Apache-2.0 OR MIT" +repository = "https://github.com/RustCrypto/SSH/tree/master/ssh-cipher" +categories = ["cryptography", "no-std"] +keywords = ["crypto", "encryption", "openssh", "ssh"] +readme = "README.md" +edition = "2021" +rust-version = "1.60" + +[dependencies] +cipher = "0.4" +encoding = { package = "ssh-encoding", version = "=0.2.0-pre", path = "../ssh-encoding" } + +# optional dependencies +aes = { version = "0.8", optional = true, default-features = false } +aes-gcm = { version = "0.10", optional = true, default-features = false, features = ["aes"] } +cbc = { version = "0.1", optional = true } +ctr = { version = "0.9", optional = true, default-features = false } +chacha20 = { version = "0.9", optional = true, default-features = false } +des = { version = "0.8", optional = true, default-features = false } +poly1305 = { version = "0.8", optional = true, default-features = false } +subtle = { version = "2", optional = true, default-features = false } + +[features] +std = [] + +aes-cbc = ["dep:aes", "dep:cbc"] +aes-ctr = ["dep:aes", "dep:ctr"] +aes-gcm = ["dep:aes", "dep:aes-gcm"] +chacha20poly1305 = ["dep:chacha20", "dep:poly1305", "dep:subtle"] +tdes = ["dep:des", "dep:cbc"] + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] diff --git a/ssh-cipher/LICENSE-APACHE b/ssh-cipher/LICENSE-APACHE new file mode 100644 index 00000000..78173fa2 --- /dev/null +++ b/ssh-cipher/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/ssh-cipher/LICENSE-MIT b/ssh-cipher/LICENSE-MIT new file mode 100644 index 00000000..50c61807 --- /dev/null +++ b/ssh-cipher/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2023 The RustCrypto Project Developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/ssh-cipher/README.md b/ssh-cipher/README.md new file mode 100644 index 00000000..45773e3d --- /dev/null +++ b/ssh-cipher/README.md @@ -0,0 +1,60 @@ +# [RustCrypto]: SSH Symmetric Ciphers + +[![crate][crate-image]][crate-link] +[![Docs][docs-image]][docs-link] +[![Build Status][build-image]][build-link] +![Apache2/MIT licensed][license-image] +![Rust Version][rustc-image] +[![Project Chat][chat-image]][chat-link] + +[Documentation][docs-link] + +## About + +Pure Rust implementation of SSH symmetric encryption including support for the +modern `aes128-gcm@openssh.com`/`aes256-gcm@openssh.com` and +`chacha20-poly1305@openssh.com` algorithms as well as legacy support for older +ciphers. + +Built on the pure Rust cryptography implementations maintained by the +[RustCrypto] organization. + +## Minimum Supported Rust Version + +This crate requires **Rust 1.60** at a minimum. + +We may change the MSRV in the future, but it will be accompanied by a minor +version bump. + +## License + +Licensed under either of: + + * [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) + * [MIT license](http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. + +[//]: # (badges) + +[crate-image]: https://buildstats.info/crate/ssh-cipher +[crate-link]: https://crates.io/crates/ssh-cipher +[docs-image]: https://docs.rs/ssh-cipher/badge.svg +[docs-link]: https://docs.rs/ssh-cipher/ +[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg +[rustc-image]: https://img.shields.io/badge/rustc-1.60+-blue.svg +[chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg +[chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/346919-SSH +[build-image]: https://github.com/RustCrypto/SSH/actions/workflows/ssh-cipher.yml/badge.svg +[build-link]: https://github.com/RustCrypto/SSH/actions/workflows/ssh-cipher.yml + +[//]: # (links) + +[RustCrypto]: https://github.com/rustcrypto +[RFC4251]: https://datatracker.ietf.org/doc/html/rfc4251 diff --git a/ssh-key/src/cipher.rs b/ssh-cipher/src/lib.rs similarity index 72% rename from ssh-key/src/cipher.rs rename to ssh-cipher/src/lib.rs index 72586918..b7fa115a 100644 --- a/ssh-key/src/cipher.rs +++ b/ssh-cipher/src/lib.rs @@ -1,26 +1,59 @@ -//! Symmetric encryption ciphers. -//! -//! These are used for encrypting private keys. +#![no_std] +#![cfg_attr(docsrs, feature(doc_auto_cfg))] +#![doc = include_str!("../README.md")] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg", + html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg" +)] +#![forbid(unsafe_code)] +#![warn( + clippy::integer_arithmetic, + clippy::panic, + clippy::panic_in_result_fn, + clippy::unwrap_used, + missing_docs, + rust_2018_idioms, + unused_lifetimes, + unused_qualifications +)] + +#[cfg(feature = "std")] +extern crate std; -use crate::Result; use core::{fmt, str}; use encoding::{Label, LabelError}; -#[cfg(feature = "encryption")] -use { - crate::Error, - aes::{ - cipher::{ - BlockCipher, BlockDecryptMut, BlockEncryptMut, KeyInit, KeyIvInit, StreamCipherCore, - }, - Aes128, Aes192, Aes256, - }, - cbc::{cipher::block_padding::NoPadding, Decryptor, Encryptor}, -}; +#[cfg(feature = "aes-ctr")] +use cipher::StreamCipherCore; #[cfg(feature = "aes-gcm")] use aes_gcm::{aead::AeadInPlace, Aes128Gcm, Aes256Gcm}; +#[cfg(any(feature = "aes-cbc", feature = "aes-ctr"))] +use aes::{Aes128, Aes192, Aes256}; + +#[cfg(any(feature = "aes-cbc", feature = "tdes"))] +use { + cbc::{Decryptor, Encryptor}, + cipher::{block_padding::NoPadding, BlockCipher, BlockDecryptMut, BlockEncryptMut}, +}; + +#[cfg(any( + feature = "aes-cbc", + feature = "aes-gcm", + feature = "chacha20poly1305", + feature = "tdes" +))] +use cipher::KeyInit; + +#[cfg(any( + feature = "aes-cbc", + feature = "aes-ctr", + feature = "chacha20poly1305", + feature = "tdes" +))] +use cipher::KeyIvInit; + #[cfg(feature = "tdes")] use des::TdesEde3; @@ -54,33 +87,55 @@ const CHACHA20_POLY1305: &str = "chacha20-poly1305@openssh.com"; /// Triple-DES in block chaining (CBC) mode const TDES_CBC: &str = "3des-cbc"; -/// Nonces for AEAD modes. -#[cfg(any(feature = "aes-gcm", feature = "chacha20poly1305"))] -type AeadNonce = [u8; 12]; +/// Deliberately opaque error type. +/// +/// This type is deliberately opaque because revealing information about what +/// went wrong with symmetric encryption has enabled past sidechannel attacks. +#[derive(Clone, Copy, Default, Debug, Eq, PartialEq)] +pub struct Error; + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("Error") + } +} + +#[cfg(feature = "std")] +impl std::error::Error for Error {} + +/// Result type with [`Error`] as the error type. +pub type Result = core::result::Result; + +/// Nonce for AEAD modes. +/// +/// This is used by e.g. `aes128-gcm@openssh.com`/`aes256-gcm@openssh.com` and +/// `chacha20-poly1305@openssh.com`. +pub type Nonce = [u8; 12]; /// Authentication tag for ciphertext data. /// -/// This is used by e.g. `aes256-gcm@openssh.com` -pub(crate) type Tag = [u8; 16]; +/// This is used by e.g. `aes128-gcm@openssh.com`/`aes256-gcm@openssh.com` and +/// `chacha20-poly1305@openssh.com`. +pub type Tag = [u8; 16]; /// Counter mode with a 32-bit big endian counter. -#[cfg(feature = "encryption")] +#[cfg(feature = "aes-ctr")] type Ctr128BE = ctr::CtrCore; /// Cipher algorithms. -#[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq, PartialOrd, Ord)] +#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] #[non_exhaustive] pub enum Cipher { - /// No cipher (unencrypted key). + /// No cipher. None, - /// AES-128 in block chaining (CBC) mode. + /// AES-128 in cipher block chaining (CBC) mode. Aes128Cbc, - /// AES-192 in block chaining (CBC) mode. + /// AES-192 in cipher block chaining (CBC) mode. Aes192Cbc, - /// AES-256 in block chaining (CBC) mode. + /// AES-256 in cipher block chaining (CBC) mode. Aes256Cbc, /// AES-128 in counter (CTR) mode. @@ -90,7 +145,6 @@ pub enum Cipher { Aes192Ctr, /// AES-256 in counter (CTR) mode. - #[default] Aes256Ctr, /// AES-128 in Galois/Counter Mode (GCM). @@ -111,8 +165,8 @@ impl Cipher { /// /// # Supported cipher names /// - `aes256-ctr` - pub fn new(ciphername: &str) -> Result { - Ok(ciphername.parse()?) + pub fn new(ciphername: &str) -> core::result::Result { + ciphername.parse() } /// Get the string identifier which corresponds to this algorithm. @@ -193,30 +247,33 @@ impl Cipher { } /// Decrypt the ciphertext in the `buffer` in-place using this cipher. - #[cfg(feature = "encryption")] pub fn decrypt(self, key: &[u8], iv: &[u8], buffer: &mut [u8], tag: Option) -> Result<()> { match self { + #[cfg(feature = "aes-cbc")] Self::Aes128Cbc => { if tag.is_some() { - return Err(Error::Crypto); + return Err(Error); } cbc_decrypt::(key, iv, buffer) } + #[cfg(feature = "aes-cbc")] Self::Aes192Cbc => { if tag.is_some() { - return Err(Error::Crypto); + return Err(Error); } cbc_decrypt::(key, iv, buffer) } + #[cfg(feature = "aes-cbc")] Self::Aes256Cbc => { if tag.is_some() { - return Err(Error::Crypto); + return Err(Error); } cbc_decrypt::(key, iv, buffer) } + #[cfg(feature = "aes-ctr")] Self::Aes128Ctr | Self::Aes192Ctr | Self::Aes256Ctr => { if tag.is_some() { - return Err(Error::Crypto); + return Err(Error); } // Counter mode encryption and decryption are the same operation @@ -225,23 +282,23 @@ impl Cipher { } #[cfg(feature = "aes-gcm")] Self::Aes128Gcm => { - let cipher = Aes128Gcm::new_from_slice(key).map_err(|_| Error::Crypto)?; - let nonce = AeadNonce::try_from(iv).map_err(|_| Error::Crypto)?; - let tag = tag.ok_or(Error::Crypto)?; + let cipher = Aes128Gcm::new_from_slice(key).map_err(|_| Error)?; + let nonce = Nonce::try_from(iv).map_err(|_| Error)?; + let tag = tag.ok_or(Error)?; cipher .decrypt_in_place_detached(&nonce.into(), &[], buffer, &tag.into()) - .map_err(|_| Error::Crypto)?; + .map_err(|_| Error)?; Ok(()) } #[cfg(feature = "aes-gcm")] Self::Aes256Gcm => { - let cipher = Aes256Gcm::new_from_slice(key).map_err(|_| Error::Crypto)?; - let nonce = AeadNonce::try_from(iv).map_err(|_| Error::Crypto)?; - let tag = tag.ok_or(Error::Crypto)?; + let cipher = Aes256Gcm::new_from_slice(key).map_err(|_| Error)?; + let nonce = Nonce::try_from(iv).map_err(|_| Error)?; + let tag = tag.ok_or(Error)?; cipher .decrypt_in_place_detached(&nonce.into(), &[], buffer, &tag.into()) - .map_err(|_| Error::Crypto)?; + .map_err(|_| Error)?; Ok(()) } @@ -252,59 +309,68 @@ impl Cipher { #[cfg(feature = "tdes")] Self::TDesCbc => { if tag.is_some() { - return Err(Error::Crypto); + return Err(Error); } cbc_decrypt::(key, iv, buffer) } - _ => Err(Error::Crypto), + _ => { + // Suppress unused variable warnings. + let (_, _, _, _) = (key, iv, buffer, tag); + Err(Error) + } } } /// Encrypt the ciphertext in the `buffer` in-place using this cipher. - #[cfg(feature = "encryption")] pub fn encrypt(self, key: &[u8], iv: &[u8], buffer: &mut [u8]) -> Result> { match self { + #[cfg(feature = "aes-cbc")] Self::Aes128Cbc => { cbc_encrypt::(key, iv, buffer)?; Ok(None) } + #[cfg(feature = "aes-cbc")] Self::Aes192Cbc => { cbc_encrypt::(key, iv, buffer)?; Ok(None) } + #[cfg(feature = "aes-cbc")] Self::Aes256Cbc => { cbc_encrypt::(key, iv, buffer)?; Ok(None) } + #[cfg(feature = "aes-ctr")] Self::Aes128Ctr => { ctr_encrypt::>(key, iv, buffer)?; Ok(None) } + #[cfg(feature = "aes-ctr")] Self::Aes192Ctr => { ctr_encrypt::>(key, iv, buffer)?; Ok(None) } + #[cfg(feature = "aes-ctr")] Self::Aes256Ctr => { ctr_encrypt::>(key, iv, buffer)?; Ok(None) } #[cfg(feature = "aes-gcm")] Self::Aes128Gcm => { - let cipher = Aes128Gcm::new_from_slice(key).map_err(|_| Error::Crypto)?; - let nonce = AeadNonce::try_from(iv).map_err(|_| Error::Crypto)?; + let cipher = Aes128Gcm::new_from_slice(key).map_err(|_| Error)?; + let nonce = Nonce::try_from(iv).map_err(|_| Error)?; let tag = cipher .encrypt_in_place_detached(&nonce.into(), &[], buffer) - .map_err(|_| Error::Crypto)?; + .map_err(|_| Error)?; Ok(Some(tag.into())) } #[cfg(feature = "aes-gcm")] Self::Aes256Gcm => { - let cipher = Aes256Gcm::new_from_slice(key).map_err(|_| Error::Crypto)?; - let nonce = AeadNonce::try_from(iv).map_err(|_| Error::Crypto)?; + let cipher = Aes256Gcm::new_from_slice(key).map_err(|_| Error)?; + let nonce = Nonce::try_from(iv).map_err(|_| Error)?; let tag = cipher .encrypt_in_place_detached(&nonce.into(), &[], buffer) - .map_err(|_| Error::Crypto)?; + .map_err(|_| Error)?; Ok(Some(tag.into())) } @@ -317,7 +383,11 @@ impl Cipher { cbc_encrypt::(key, iv, buffer)?; Ok(None) } - _ => Err(Error::Crypto), + _ => { + // Suppress unused variable warnings. + let (_, _, _) = (key, iv, buffer); + Err(Error) + } } } } @@ -357,44 +427,44 @@ impl str::FromStr for Cipher { } } -#[cfg(feature = "encryption")] +#[cfg(any(feature = "aes-cbc", feature = "tdes"))] fn cbc_encrypt(key: &[u8], iv: &[u8], buffer: &mut [u8]) -> Result<()> where C: BlockEncryptMut + BlockCipher + KeyInit, { - let cipher = Encryptor::::new_from_slices(key, iv).map_err(|_| Error::Crypto)?; + let cipher = Encryptor::::new_from_slices(key, iv).map_err(|_| Error)?; // Since the passed in buffer is already padded, using NoPadding here cipher .encrypt_padded_mut::(buffer, buffer.len()) - .map_err(|_| Error::Crypto)?; + .map_err(|_| Error)?; Ok(()) } -#[cfg(feature = "encryption")] +#[cfg(any(feature = "aes-cbc", feature = "tdes"))] fn cbc_decrypt(key: &[u8], iv: &[u8], buffer: &mut [u8]) -> Result<()> where C: BlockDecryptMut + BlockCipher + KeyInit, { - let cipher = Decryptor::::new_from_slices(key, iv).map_err(|_| Error::Crypto)?; + let cipher = Decryptor::::new_from_slices(key, iv).map_err(|_| Error)?; // Since the passed in buffer is already padded, using NoPadding here cipher .decrypt_padded_mut::(buffer) - .map_err(|_| Error::Crypto)?; + .map_err(|_| Error)?; Ok(()) } -#[cfg(feature = "encryption")] +#[cfg(feature = "aes-ctr")] fn ctr_encrypt(key: &[u8], iv: &[u8], buffer: &mut [u8]) -> Result<()> where C: StreamCipherCore + KeyIvInit, { - let cipher = C::new_from_slices(key, iv).map_err(|_| Error::Crypto)?; + let cipher = C::new_from_slices(key, iv).map_err(|_| Error)?; cipher .try_apply_keystream_partial(buffer.into()) - .map_err(|_| Error::Crypto)?; + .map_err(|_| Error)?; Ok(()) } @@ -411,10 +481,8 @@ where #[cfg(feature = "chacha20poly1305")] mod chacha20_poly1305_openssh { use super::*; - - #[cfg(feature = "encryption")] - use aes::cipher::{StreamCipher, StreamCipherSeek}; use chacha20::ChaCha20; + use cipher::{StreamCipher, StreamCipherSeek}; use poly1305::Poly1305; use subtle::ConstantTimeEq; @@ -422,19 +490,18 @@ mod chacha20_poly1305_openssh { #[inline] fn chacha20poly1305_init(key: &[u8]) -> Result<(ChaCha20, Poly1305)> { - // The key here is actually concatenation of two chacha20 keys. if key.len() != 64 { - return Err(Error::Crypto); + return Err(Error); } - let k_main = ChaCha20Key::try_from(&key[..32]).map_err(|_| Error::Crypto)?; - let _k_header = ChaCha20Key::try_from(&key[32..]).map_err(|_| Error::Crypto)?; - // The nonce is from packet seq, but the value is alway 0 in sshkey. - let nonce: AeadNonce = [0u8; 12]; - let mut main_cipher = ChaCha20::new(&k_main.into(), &nonce.into()); + let k_main = ChaCha20Key::try_from(&key[..32]).map_err(|_| Error)?; + let _k_header = ChaCha20Key::try_from(&key[32..]).map_err(|_| Error)?; + let mut main_cipher = ChaCha20::new(&k_main.into(), &Nonce::default().into()); let mut poly1305_key = poly1305::Key::default(); main_cipher.apply_keystream(&mut poly1305_key); + let poly1305 = Poly1305::new(&poly1305_key); + // Seek to block 1 main_cipher.seek(64); @@ -444,24 +511,23 @@ mod chacha20_poly1305_openssh { #[inline] pub fn chacha20poly1305_encrypt(key: &[u8], buffer: &mut [u8]) -> Result { let (mut cipher, poly1305) = chacha20poly1305_init(key)?; - cipher.apply_keystream(buffer); - let tag = poly1305.compute_unpadded(buffer); + let tag = poly1305.compute_unpadded(buffer); Ok(tag.into()) } #[inline] pub fn chacha20poly1305_decrypt(key: &[u8], buffer: &mut [u8], tag: Option) -> Result<()> { let (mut cipher, poly1305) = chacha20poly1305_init(key)?; - let tag = tag.ok_or(Error::Crypto)?; + let tag = tag.ok_or(Error)?; - let expect_tag = poly1305.compute_unpadded(buffer); - if expect_tag.ct_eq(&tag).into() { + let expected_tag = poly1305.compute_unpadded(buffer); + if expected_tag.ct_eq(&tag).into() { cipher.apply_keystream(buffer); Ok(()) } else { - Err(Error::Crypto) + Err(Error) } } } diff --git a/ssh-encoding/LICENSE-MIT b/ssh-encoding/LICENSE-MIT index c869ada5..3294d743 100644 --- a/ssh-encoding/LICENSE-MIT +++ b/ssh-encoding/LICENSE-MIT @@ -1,4 +1,4 @@ -Copyright (c) 2021 The RustCrypto Project Developers +Copyright (c) 2021-2023 The RustCrypto Project Developers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated diff --git a/ssh-key/Cargo.toml b/ssh-key/Cargo.toml index 336e4a06..27f20896 100644 --- a/ssh-key/Cargo.toml +++ b/ssh-key/Cargo.toml @@ -17,6 +17,7 @@ edition = "2021" rust-version = "1.65" [dependencies] +cipher = { package = "ssh-cipher", version = "=0.1.0-pre", path = "../ssh-cipher" } encoding = { package = "ssh-encoding", version = "=0.2.0-pre", features = ["base64", "pem", "sha2"], path = "../ssh-encoding" } sha2 = { version = "0.10.7", default-features = false } signature = { version = "2", default-features = false } @@ -24,16 +25,9 @@ subtle = { version = "2", default-features = false } zeroize = { version = "1", default-features = false } # optional dependencies -aes = { version = "0.8", optional = true, default-features = false } -aes-gcm = { version = "0.10", optional = true, default-features = false, features = ["aes"] } -cbc = { version = "0.1.2", optional = true } -ctr = { version = "0.9", optional = true, default-features = false } -chacha20 = { version = "0.9.1", optional = true, default-features = false } -poly1305 = { version = "0.8.0", optional = true, default-features = false } bcrypt-pbkdf = { version = "0.10", optional = true, default-features = false, features = ["alloc"] } bigint = { package = "num-bigint-dig", version = "0.8", optional = true, default-features = false } dsa = { version = "0.6", optional = true, default-features = false } -des = { version = "0.8.1", optional = true, default-features = false } ed25519-dalek = { version = "=2.0.0-rc.2", optional = true, default-features = false } p256 = { version = "0.13", optional = true, default-features = false, features = ["ecdsa"] } p384 = { version = "0.13", optional = true, default-features = false, features = ["ecdsa"] } @@ -64,19 +58,24 @@ std = [ "signature/std" ] -aes-cbc = ["dep:cbc", "encryption"] -aes-gcm = ["dep:aes-gcm", "encryption"] -chacha20poly1305 = ["dep:chacha20", "dep:poly1305", "encryption"] -crypto = ["aes-gcm", "ed25519", "p256", "p384", "rsa"] # NOTE: `dsa` is obsolete/weak +crypto = ["ed25519", "p256", "p384", "rsa"] # NOTE: `dsa` is obsolete/weak dsa = ["dep:bigint", "dep:dsa", "dep:sha1", "alloc", "signature/rand_core"] ecdsa = ["dep:sec1"] ed25519 = ["dep:ed25519-dalek", "rand_core"] -encryption = [ "alloc", "dep:aes", "dep:bcrypt-pbkdf", "dep:cbc", "dep:ctr", "rand_core"] +encryption = [ + "dep:bcrypt-pbkdf", + "alloc", + "cipher/aes-cbc", + "cipher/aes-ctr", + "cipher/aes-gcm", + "cipher/chacha20poly1305", + "rand_core" +] getrandom = ["rand_core/getrandom"] p256 = ["dep:p256", "ecdsa"] p384 = ["dep:p384", "ecdsa"] rsa = ["dep:bigint", "dep:rsa", "alloc", "rand_core"] -tdes = ["dep:des", "encryption"] +tdes = ["cipher/tdes", "encryption"] [package.metadata.docs.rs] all-features = true diff --git a/ssh-key/LICENSE-MIT b/ssh-key/LICENSE-MIT index c869ada5..3294d743 100644 --- a/ssh-key/LICENSE-MIT +++ b/ssh-key/LICENSE-MIT @@ -1,4 +1,4 @@ -Copyright (c) 2021 The RustCrypto Project Developers +Copyright (c) 2021-2023 The RustCrypto Project Developers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated diff --git a/ssh-key/src/error.rs b/ssh-key/src/error.rs index 54978a57..1428db43 100644 --- a/ssh-key/src/error.rs +++ b/ssh-key/src/error.rs @@ -115,6 +115,12 @@ impl fmt::Display for Error { } } +impl From for Error { + fn from(_: cipher::Error) -> Error { + Error::Crypto + } +} + impl From for Error { fn from(_: core::array::TryFromSliceError) -> Error { Error::Encoding(encoding::Error::Length) diff --git a/ssh-key/src/lib.rs b/ssh-key/src/lib.rs index 1374c5a3..424030e1 100644 --- a/ssh-key/src/lib.rs +++ b/ssh-key/src/lib.rs @@ -148,7 +148,6 @@ pub mod certificate; pub mod known_hosts; mod algorithm; -mod cipher; mod error; mod fingerprint; mod kdf; @@ -163,13 +162,13 @@ mod sshsig; pub use crate::{ algorithm::{Algorithm, EcdsaCurve, HashAlg, KdfAlg}, authorized_keys::AuthorizedKeys, - cipher::Cipher, error::{Error, Result}, fingerprint::Fingerprint, kdf::Kdf, private::PrivateKey, public::PublicKey, }; +pub use cipher::Cipher; pub use encoding::LineEnding; pub use sha2; diff --git a/ssh-key/src/private.rs b/ssh-key/src/private.rs index 5088daac..ed32c8b0 100644 --- a/ssh-key/src/private.rs +++ b/ssh-key/src/private.rs @@ -136,9 +136,8 @@ pub use self::ecdsa::{EcdsaKeypair, EcdsaPrivateKey}; #[cfg(all(feature = "alloc", feature = "ecdsa"))] pub use self::sk::SkEcdsaSha2NistP256; -use crate::{ - cipher::Tag, public, Algorithm, Cipher, Error, Fingerprint, HashAlg, Kdf, PublicKey, Result, -}; +use crate::{public, Algorithm, Cipher, Error, Fingerprint, HashAlg, Kdf, PublicKey, Result}; +use cipher::Tag; use core::str; use encoding::{ pem::{LineEnding, PemLabel}, @@ -339,7 +338,7 @@ impl PrivateKey { rng: &mut impl CryptoRngCore, password: impl AsRef<[u8]>, ) -> Result { - self.encrypt_with_cipher(rng, Cipher::default(), password) + self.encrypt_with_cipher(rng, Cipher::Aes256Ctr, password) } /// Encrypt an unencrypted private key using the provided password to diff --git a/ssh-key/tests/encrypted_private_key.rs b/ssh-key/tests/encrypted_private_key.rs index b6e4aa39..b2413126 100644 --- a/ssh-key/tests/encrypted_private_key.rs +++ b/ssh-key/tests/encrypted_private_key.rs @@ -6,37 +6,37 @@ use hex_literal::hex; use ssh_key::{Algorithm, Cipher, Kdf, KdfAlg, PrivateKey}; /// Unencrypted Ed25519 OpenSSH-formatted private key. -#[cfg(all(feature = "encryption"))] +#[cfg(feature = "encryption")] const OPENSSH_ED25519_EXAMPLE: &str = include_str!("examples/id_ed25519"); /// AES128-CBC encrypted Ed25519 OpenSSH-formatted private key. /// /// Plaintext is `OPENSSH_ED25519_EXAMPLE`. -#[cfg(all(feature = "encryption"))] +#[cfg(feature = "encryption")] const OPENSSH_AES128_CBC_ED25519_EXAMPLE: &str = include_str!("examples/id_ed25519.aes128-cbc.enc"); /// AES192-CBC encrypted Ed25519 OpenSSH-formatted private key. /// /// Plaintext is `OPENSSH_ED25519_EXAMPLE`. -#[cfg(all(feature = "encryption"))] +#[cfg(feature = "encryption")] const OPENSSH_AES192_CBC_ED25519_EXAMPLE: &str = include_str!("examples/id_ed25519.aes192-cbc.enc"); /// AES256-CBC encrypted Ed25519 OpenSSH-formatted private key. /// /// Plaintext is `OPENSSH_ED25519_EXAMPLE`. -#[cfg(all(feature = "encryption"))] +#[cfg(feature = "encryption")] const OPENSSH_AES256_CBC_ED25519_EXAMPLE: &str = include_str!("examples/id_ed25519.aes256-cbc.enc"); /// AES128-CTR encrypted Ed25519 OpenSSH-formatted private key. /// /// Plaintext is `OPENSSH_ED25519_EXAMPLE`. -#[cfg(all(feature = "encryption"))] +#[cfg(feature = "encryption")] const OPENSSH_AES128_CTR_ED25519_EXAMPLE: &str = include_str!("examples/id_ed25519.aes128-ctr.enc"); /// AES192-CTR encrypted Ed25519 OpenSSH-formatted private key. /// /// Plaintext is `OPENSSH_ED25519_EXAMPLE`. -#[cfg(all(feature = "encryption"))] +#[cfg(feature = "encryption")] const OPENSSH_AES192_CTR_ED25519_EXAMPLE: &str = include_str!("examples/id_ed25519.aes192-ctr.enc"); /// AES256-CTR encrypted Ed25519 OpenSSH-formatted private key. @@ -47,7 +47,7 @@ const OPENSSH_AES256_CTR_ED25519_EXAMPLE: &str = include_str!("examples/id_ed255 /// AES256-GCM encrypted Ed25519 OpenSSH-formatted private key. /// /// Plaintext is `OPENSSH_ED25519_EXAMPLE`. -#[cfg(all(feature = "aes-gcm"))] +#[cfg(feature = "encryption")] const OPENSSH_AES128_GCM_ED25519_EXAMPLE: &str = include_str!("examples/id_ed25519.aes128-gcm.enc"); /// AES256-GCM encrypted Ed25519 OpenSSH-formatted private key. @@ -67,7 +67,7 @@ const OPENSSH_CHACHA20_POLY1305_ED25519_EXAMPLE: &str = const OPENSSH_3DES_CBC_ED25519_EXAMPLE: &str = include_str!("examples/id_ed25519.3des-cbc.enc"); /// Bad password; don't actually use outside tests! -#[cfg(all(feature = "encryption"))] +#[cfg(feature = "encryption")] const PASSWORD: &[u8] = b"hunter42"; #[test] @@ -154,7 +154,7 @@ fn decode_openssh_3des_cbc() { ); } -#[cfg(all(feature = "encryption"))] +#[cfg(feature = "encryption")] #[test] fn decrypt_openssh_aes128_ctr() { let key_enc = PrivateKey::from_openssh(OPENSSH_AES128_CTR_ED25519_EXAMPLE).unwrap(); @@ -166,7 +166,7 @@ fn decrypt_openssh_aes128_ctr() { ); } -#[cfg(all(feature = "encryption"))] +#[cfg(feature = "encryption")] #[test] fn decrypt_openssh_aes192_ctr() { let key_enc = PrivateKey::from_openssh(OPENSSH_AES192_CTR_ED25519_EXAMPLE).unwrap(); @@ -178,7 +178,7 @@ fn decrypt_openssh_aes192_ctr() { ); } -#[cfg(all(feature = "encryption"))] +#[cfg(feature = "encryption")] #[test] fn decrypt_openssh_aes256_ctr() { let key_enc = PrivateKey::from_openssh(OPENSSH_AES256_CTR_ED25519_EXAMPLE).unwrap(); @@ -190,7 +190,7 @@ fn decrypt_openssh_aes256_ctr() { ); } -#[cfg(all(feature = "encryption"))] +#[cfg(feature = "encryption")] #[test] fn decrypt_openssh_aes128_cbc() { let key_enc = PrivateKey::from_openssh(OPENSSH_AES128_CBC_ED25519_EXAMPLE).unwrap(); @@ -202,7 +202,7 @@ fn decrypt_openssh_aes128_cbc() { ); } -#[cfg(all(feature = "encryption"))] +#[cfg(feature = "encryption")] #[test] fn decrypt_openssh_aes192_cbc() { let key_enc = PrivateKey::from_openssh(OPENSSH_AES192_CBC_ED25519_EXAMPLE).unwrap(); @@ -214,7 +214,7 @@ fn decrypt_openssh_aes192_cbc() { ); } -#[cfg(all(feature = "encryption"))] +#[cfg(feature = "encryption")] #[test] fn decrypt_openssh_aes256_cbc() { let key_enc = PrivateKey::from_openssh(OPENSSH_AES256_CBC_ED25519_EXAMPLE).unwrap(); @@ -226,7 +226,7 @@ fn decrypt_openssh_aes256_cbc() { ); } -#[cfg(all(feature = "aes-gcm"))] +#[cfg(feature = "encryption")] #[test] fn decrypt_openssh_aes128_gcm() { let key_enc = PrivateKey::from_openssh(OPENSSH_AES128_GCM_ED25519_EXAMPLE).unwrap(); @@ -238,7 +238,7 @@ fn decrypt_openssh_aes128_gcm() { ); } -#[cfg(all(feature = "aes-gcm"))] +#[cfg(feature = "encryption")] #[test] fn decrypt_openssh_aes256_gcm() { let key_enc = PrivateKey::from_openssh(OPENSSH_AES256_GCM_ED25519_EXAMPLE).unwrap(); @@ -250,7 +250,7 @@ fn decrypt_openssh_aes256_gcm() { ); } -#[cfg(all(feature = "chacha20poly1305"))] +#[cfg(feature = "chacha20poly1305")] #[test] fn decrypt_openssh_chacha20_poly1305() { let key_enc = PrivateKey::from_openssh(OPENSSH_CHACHA20_POLY1305_ED25519_EXAMPLE).unwrap(); @@ -262,7 +262,7 @@ fn decrypt_openssh_chacha20_poly1305() { ); } -#[cfg(all(feature = "tdes"))] +#[cfg(feature = "tdes")] #[test] fn decrypt_openssh_3des() { let key_enc = PrivateKey::from_openssh(OPENSSH_3DES_CBC_ED25519_EXAMPLE).unwrap(); @@ -410,7 +410,7 @@ fn encrypt_openssh_aes256_ctr() { assert_eq!(key_dec, key_dec2); } -#[cfg(all(feature = "aes-gcm", feature = "getrandom"))] +#[cfg(all(feature = "encryption", feature = "getrandom"))] #[test] fn encrypt_openssh_aes128_gcm() { use rand_core::OsRng; @@ -431,7 +431,7 @@ fn encrypt_openssh_aes128_gcm() { assert_eq!(key_dec, key_dec2); } -#[cfg(all(feature = "aes-gcm", feature = "getrandom"))] +#[cfg(all(feature = "encryption", feature = "getrandom"))] #[test] fn encrypt_openssh_aes256_gcm() { use rand_core::OsRng;