diff --git a/.github/workflows/ssh-encoding.yml b/.github/workflows/ssh-encoding.yml
new file mode 100644
index 00000000..86dab636
--- /dev/null
+++ b/.github/workflows/ssh-encoding.yml
@@ -0,0 +1,61 @@
+name: ssh-encoding
+
+on:
+ pull_request:
+ paths:
+ - ".github/workflows/ssh-encoding.yml"
+ - "ssh-encoding/**"
+ - "Cargo.*"
+ push:
+ branches: master
+
+defaults:
+ run:
+ working-directory: ssh-encoding
+
+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: actions-rs/toolchain@v1
+ with:
+ profile: minimal
+ toolchain: ${{ matrix.rust }}
+ target: ${{ matrix.target }}
+ override: true
+ - uses: RustCrypto/actions/cargo-hack-install@master
+ - run: cargo hack build --target ${{ matrix.target }} --feature-powerset --exclude-features default,getrandom,std
+
+ test:
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ rust:
+ - 1.60.0 # MSRV
+ - stable
+ steps:
+ - uses: actions/checkout@v3
+ - uses: actions-rs/toolchain@v1
+ with:
+ profile: minimal
+ toolchain: ${{ matrix.rust }}
+ override: true
+ - uses: RustCrypto/actions/cargo-hack-install@master
+ - run: cargo hack test --feature-powerset
diff --git a/Cargo.lock b/Cargo.lock
index 71ea0703..b30061f0 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -724,12 +724,20 @@ dependencies = [
"der",
]
+[[package]]
+name = "ssh-encoding"
+version = "0.0.0"
+dependencies = [
+ "base64ct",
+ "pem-rfc7468",
+ "sha2 0.10.6",
+]
+
[[package]]
name = "ssh-key"
version = "0.5.0-pre.0"
dependencies = [
"aes",
- "base64ct",
"bcrypt-pbkdf",
"ctr",
"dsa",
@@ -738,7 +746,6 @@ dependencies = [
"num-bigint-dig",
"p256",
"p384",
- "pem-rfc7468",
"rand_chacha 0.3.1",
"rand_core 0.6.4",
"rsa",
@@ -747,6 +754,7 @@ dependencies = [
"sha1",
"sha2 0.10.6",
"signature",
+ "ssh-encoding",
"subtle",
"tempfile",
"zeroize",
diff --git a/Cargo.toml b/Cargo.toml
index 73ae08df..dcae887c 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,9 @@
[workspace]
-members = ["ssh-key"]
resolver = "2"
+members = [
+ "ssh-encoding",
+ "ssh-key"
+]
[profile.dev]
opt-level = 2
diff --git a/ssh-encoding/CHANGELOG.md b/ssh-encoding/CHANGELOG.md
new file mode 100644
index 00000000..d6637e04
--- /dev/null
+++ b/ssh-encoding/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-encoding/Cargo.toml b/ssh-encoding/Cargo.toml
new file mode 100644
index 00000000..3534024d
--- /dev/null
+++ b/ssh-encoding/Cargo.toml
@@ -0,0 +1,30 @@
+[package]
+name = "ssh-encoding"
+version = "0.0.0"
+description = """
+Pure Rust implementation of SSH data type decoders/encoders as described
+in RFC4251
+"""
+authors = ["RustCrypto Developers"]
+license = "Apache-2.0 OR MIT"
+repository = "https://github.com/RustCrypto/SSH/tree/master/ssh-encoding"
+categories = ["authentication", "cryptography", "encoding", "no-std", "parser-implementations"]
+keywords = ["crypto", "certificate", "key", "openssh", "ssh"]
+readme = "README.md"
+edition = "2021"
+rust-version = "1.60"
+
+[dependencies]
+base64 = { package = "base64ct", version = "1.4", optional = true }
+pem = { package = "pem-rfc7468", version = "0.6", optional = true }
+sha2 = { version = "0.10", optional = true, default-features = false }
+
+[features]
+alloc = ["base64?/alloc", "pem?/alloc"]
+std = ["alloc", "base64?/std", "pem?/std", "sha2?/std"]
+
+pem = ["base64", "dep:pem"]
+
+[package.metadata.docs.rs]
+all-features = true
+rustdoc-args = ["--cfg", "docsrs"]
diff --git a/ssh-encoding/LICENSE-APACHE b/ssh-encoding/LICENSE-APACHE
new file mode 100644
index 00000000..78173fa2
--- /dev/null
+++ b/ssh-encoding/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-encoding/LICENSE-MIT b/ssh-encoding/LICENSE-MIT
new file mode 100644
index 00000000..c869ada5
--- /dev/null
+++ b/ssh-encoding/LICENSE-MIT
@@ -0,0 +1,25 @@
+Copyright (c) 2021 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-encoding/README.md b/ssh-encoding/README.md
new file mode 100644
index 00000000..3251474d
--- /dev/null
+++ b/ssh-encoding/README.md
@@ -0,0 +1,55 @@
+# [RustCrypto]: SSH Encoding
+
+[![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 data type decoders/encoders as described
+in [RFC4251].
+
+## 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-encoding
+[crate-link]: https://crates.io/crates/ssh-encoding
+[docs-image]: https://docs.rs/ssh-encoding/badge.svg
+[docs-link]: https://docs.rs/ssh-encoding/
+[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-encoding.yml/badge.svg
+[build-link]: https://github.com/RustCrypto/SSH/actions/workflows/ssh-encoding.yml
+
+[//]: # (links)
+
+[RustCrypto]: https://github.com/rustcrypto
+[RFC4251]: https://datatracker.ietf.org/doc/html/rfc4251
diff --git a/ssh-key/src/checked.rs b/ssh-encoding/src/checked.rs
similarity index 93%
rename from ssh-key/src/checked.rs
rename to ssh-encoding/src/checked.rs
index 6e6db30f..013574da 100644
--- a/ssh-key/src/checked.rs
+++ b/ssh-encoding/src/checked.rs
@@ -3,7 +3,7 @@
use crate::{Error, Result};
/// Extension trait for providing checked [`Iterator::sum`]-like functionality.
-pub(crate) trait CheckedSum: Sized {
+pub trait CheckedSum: Sized {
/// Iterate over the values of this type, computing a checked sum.
///
/// Returns [`Error::Length`] on overflow.
diff --git a/ssh-key/src/decode.rs b/ssh-encoding/src/decode.rs
similarity index 88%
rename from ssh-key/src/decode.rs
rename to ssh-encoding/src/decode.rs
index de0f6da8..332c0a7d 100644
--- a/ssh-key/src/decode.rs
+++ b/ssh-encoding/src/decode.rs
@@ -14,13 +14,18 @@ const MAX_SIZE: usize = 0xFFFFF;
/// Decoder trait.
///
/// This trait describes how to decode a given type.
-pub(crate) trait Decode: Sized {
- /// Attempt to decode a value of this type using the provided [`Decoder`].
- fn decode(reader: &mut impl Reader) -> Result;
+pub trait Decode: Sized {
+ /// Type returned in the event of a decoding error.
+ type Error: From;
+
+ /// Attempt to decode a value of this type using the provided [`Reader`].
+ fn decode(reader: &mut impl Reader) -> core::result::Result;
}
/// Decode a single `byte` from the input data.
impl Decode for u8 {
+ type Error = Error;
+
fn decode(reader: &mut impl Reader) -> Result {
let mut buf = [0];
reader.read(&mut buf)?;
@@ -36,6 +41,8 @@ impl Decode for u8 {
///
/// [RFC4251 § 5]: https://datatracker.ietf.org/doc/html/rfc4251#section-5
impl Decode for u32 {
+ type Error = Error;
+
fn decode(reader: &mut impl Reader) -> Result {
let mut bytes = [0u8; 4];
reader.read(&mut bytes)?;
@@ -50,6 +57,8 @@ impl Decode for u32 {
///
/// [RFC4251 § 5]: https://datatracker.ietf.org/doc/html/rfc4251#section-5
impl Decode for u64 {
+ type Error = Error;
+
fn decode(reader: &mut impl Reader) -> Result {
let mut bytes = [0u8; 8];
reader.read(&mut bytes)?;
@@ -65,6 +74,8 @@ impl Decode for u64 {
/// Enforces a library-internal limit of 1048575, as the main use case for
/// `usize` is length prefixes.
impl Decode for usize {
+ type Error = Error;
+
fn decode(reader: &mut impl Reader) -> Result {
let n = usize::try_from(u32::decode(reader)?)?;
@@ -80,10 +91,12 @@ impl Decode for usize {
///
/// > A byte represents an arbitrary 8-bit value (octet). Fixed length
/// > data is sometimes represented as an array of bytes, written
-/// > byte[n], where n is the number of bytes in the array.
+/// > `byte[n]`, where n is the number of bytes in the array.
///
/// [RFC4251 § 5]: https://datatracker.ietf.org/doc/html/rfc4251#section-5
impl Decode for [u8; N] {
+ type Error = Error;
+
fn decode(reader: &mut impl Reader) -> Result {
reader.read_nested(|reader| {
let mut result = [(); N].map(|_| 0);
@@ -97,12 +110,14 @@ impl Decode for [u8; N] {
///
/// > A byte represents an arbitrary 8-bit value (octet). Fixed length
/// > data is sometimes represented as an array of bytes, written
-/// > byte[n], where n is the number of bytes in the array.
+/// > `byte[n]`, where n is the number of bytes in the array.
///
/// [RFC4251 § 5]: https://datatracker.ietf.org/doc/html/rfc4251#section-5
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
impl Decode for Vec {
+ type Error = Error;
+
fn decode(reader: &mut impl Reader) -> Result {
reader.read_nested(|reader| {
let mut result = vec![0u8; reader.remaining_len()];
@@ -115,6 +130,8 @@ impl Decode for Vec {
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
impl Decode for String {
+ type Error = Error;
+
fn decode(reader: &mut impl Reader) -> Result {
String::from_utf8(Vec::decode(reader)?).map_err(|_| Error::CharacterEncoding)
}
@@ -123,6 +140,8 @@ impl Decode for String {
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
impl Decode for Vec {
+ type Error = Error;
+
fn decode(reader: &mut impl Reader) -> Result {
reader.read_nested(|reader| {
let mut entries = Self::new();
diff --git a/ssh-key/src/encode.rs b/ssh-encoding/src/encode.rs
similarity index 83%
rename from ssh-key/src/encode.rs
rename to ssh-encoding/src/encode.rs
index bf7f1d16..90a83697 100644
--- a/ssh-key/src/encode.rs
+++ b/ssh-encoding/src/encode.rs
@@ -3,27 +3,33 @@
//!
//! [RFC4251 § 5]: https://datatracker.ietf.org/doc/html/rfc4251#section-5
-use crate::{checked::CheckedSum, writer::Writer, Result};
+use crate::{checked::CheckedSum, writer::Writer, Error, Result};
#[cfg(feature = "alloc")]
-use {
- crate::Error,
- alloc::{string::String, vec::Vec},
-};
+use alloc::{string::String, vec::Vec};
/// Encoding trait.
///
/// This trait describes how to encode a given type.
-pub(crate) trait Encode {
+pub trait Encode {
+ /// Type returned in the event of an encoding error.
+ type Error: From;
+
/// Get the length of this type encoded in bytes, prior to Base64 encoding.
- fn encoded_len(&self) -> Result;
+ fn encoded_len(&self) -> core::result::Result;
+
+ /// Encode this value using the provided [`Writer`].
+ fn encode(&self, writer: &mut impl Writer) -> core::result::Result<(), Self::Error>;
- /// Encode this value using the provided [`Encoder`].
- fn encode(&self, writer: &mut impl Writer) -> Result<()>;
+ /// Return the length of this type after encoding when prepended with a
+ /// `uint32` length prefix.
+ fn encoded_len_nested(&self) -> core::result::Result {
+ Ok([4, self.encoded_len()?].checked_sum()?)
+ }
/// Encode this value, first prepending a `uint32` length prefix
/// set to [`Encode::encoded_len`].
- fn encode_nested(&self, writer: &mut impl Writer) -> Result<()> {
+ fn encode_nested(&self, writer: &mut impl Writer) -> core::result::Result<(), Self::Error> {
self.encoded_len()?.encode(writer)?;
self.encode(writer)
}
@@ -31,6 +37,8 @@ pub(crate) trait Encode {
/// Encode a single `byte` to the writer.
impl Encode for u8 {
+ type Error = Error;
+
fn encoded_len(&self) -> Result {
Ok(1)
}
@@ -48,6 +56,8 @@ impl Encode for u8 {
///
/// [RFC4251 § 5]: https://datatracker.ietf.org/doc/html/rfc4251#section-5
impl Encode for u32 {
+ type Error = Error;
+
fn encoded_len(&self) -> Result {
Ok(4)
}
@@ -64,6 +74,8 @@ impl Encode for u32 {
///
/// [RFC4251 § 5]: https://datatracker.ietf.org/doc/html/rfc4251#section-5
impl Encode for u64 {
+ type Error = Error;
+
fn encoded_len(&self) -> Result {
Ok(8)
}
@@ -80,6 +92,8 @@ impl Encode for u64 {
///
/// [RFC4251 § 5]: https://datatracker.ietf.org/doc/html/rfc4251#section-5
impl Encode for usize {
+ type Error = Error;
+
fn encoded_len(&self) -> Result {
Ok(4)
}
@@ -93,10 +107,12 @@ impl Encode for usize {
///
/// > A byte represents an arbitrary 8-bit value (octet). Fixed length
/// > data is sometimes represented as an array of bytes, written
-/// > byte[n], where n is the number of bytes in the array.
+/// > `byte[n]`, where n is the number of bytes in the array.
///
/// [RFC4251 § 5]: https://datatracker.ietf.org/doc/html/rfc4251#section-5
impl Encode for [u8] {
+ type Error = Error;
+
fn encoded_len(&self) -> Result {
[4, self.len()].checked_sum()
}
@@ -111,10 +127,12 @@ impl Encode for [u8] {
///
/// > A byte represents an arbitrary 8-bit value (octet). Fixed length
/// > data is sometimes represented as an array of bytes, written
-/// > byte[n], where n is the number of bytes in the array.
+/// > `byte[n]`, where n is the number of bytes in the array.
///
/// [RFC4251 § 5]: https://datatracker.ietf.org/doc/html/rfc4251#section-5
impl Encode for [u8; N] {
+ type Error = Error;
+
fn encoded_len(&self) -> Result {
self.as_slice().encoded_len()
}
@@ -142,6 +160,8 @@ impl Encode for [u8; N] {
///
/// [RFC4251 § 5]: https://datatracker.ietf.org/doc/html/rfc4251#section-5
impl Encode for &str {
+ type Error = Error;
+
fn encoded_len(&self) -> Result {
self.as_bytes().encoded_len()
}
@@ -154,6 +174,8 @@ impl Encode for &str {
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
impl Encode for Vec {
+ type Error = Error;
+
fn encoded_len(&self) -> Result {
self.as_slice().encoded_len()
}
@@ -166,6 +188,8 @@ impl Encode for Vec {
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
impl Encode for String {
+ type Error = Error;
+
fn encoded_len(&self) -> Result {
self.as_str().encoded_len()
}
@@ -178,6 +202,8 @@ impl Encode for String {
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
impl Encode for Vec {
+ type Error = Error;
+
fn encoded_len(&self) -> Result {
self.iter().try_fold(4usize, |acc, string| {
acc.checked_add(string.encoded_len()?).ok_or(Error::Length)
diff --git a/ssh-encoding/src/error.rs b/ssh-encoding/src/error.rs
new file mode 100644
index 00000000..93bacbf4
--- /dev/null
+++ b/ssh-encoding/src/error.rs
@@ -0,0 +1,102 @@
+//! Error types
+
+use core::fmt;
+
+/// Result type with `ssh-encoding` crate's [`Error`] as the error type.
+pub type Result = core::result::Result;
+
+/// Error type.
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+#[non_exhaustive]
+pub enum Error {
+ #[cfg(feature = "base64")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "base64")))]
+ /// Base64-related errors.
+ Base64(base64::Error),
+
+ /// Character encoding-related errors.
+ CharacterEncoding,
+
+ /// Invalid length.
+ Length,
+
+ /// Overflow errors.
+ Overflow,
+
+ /// PEM encoding errors.
+ #[cfg(feature = "pem")]
+ Pem(pem::Error),
+
+ /// Unexpected trailing data at end of message.
+ TrailingData {
+ /// Number of bytes of remaining data at end of message.
+ remaining: usize,
+ },
+}
+
+impl fmt::Display for Error {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ #[cfg(feature = "base64")]
+ Error::Base64(err) => write!(f, "Base64 encoding error: {}", err),
+ Error::CharacterEncoding => write!(f, "character encoding invalid"),
+ Error::Length => write!(f, "length invalid"),
+ Error::Overflow => write!(f, "internal overflow error"),
+ #[cfg(feature = "pem")]
+ Error::Pem(err) => write!(f, "{}", err),
+ Error::TrailingData { remaining } => write!(
+ f,
+ "unexpected trailing data at end of message ({} bytes)",
+ remaining
+ ),
+ }
+ }
+}
+
+impl From for Error {
+ fn from(_: core::num::TryFromIntError) -> Error {
+ Error::Overflow
+ }
+}
+
+impl From for Error {
+ fn from(_: core::str::Utf8Error) -> Error {
+ Error::CharacterEncoding
+ }
+}
+
+#[cfg(feature = "alloc")]
+#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
+impl From for Error {
+ fn from(_: alloc::string::FromUtf8Error) -> Error {
+ Error::CharacterEncoding
+ }
+}
+
+#[cfg(feature = "base64")]
+#[cfg_attr(docsrs, doc(cfg(feature = "base64")))]
+impl From for Error {
+ fn from(err: base64::Error) -> Error {
+ Error::Base64(err)
+ }
+}
+
+#[cfg(feature = "base64")]
+#[cfg_attr(docsrs, doc(cfg(feature = "base64")))]
+impl From for Error {
+ fn from(_: base64::InvalidLengthError) -> Error {
+ Error::Length
+ }
+}
+
+#[cfg(feature = "pem")]
+#[cfg_attr(docsrs, doc(cfg(feature = "pem")))]
+impl From for Error {
+ fn from(err: pem::Error) -> Error {
+ Error::Pem(err)
+ }
+}
+
+#[cfg(feature = "std")]
+#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
+impl std::error::Error for Error {}
diff --git a/ssh-encoding/src/label.rs b/ssh-encoding/src/label.rs
new file mode 100644
index 00000000..b952769f
--- /dev/null
+++ b/ssh-encoding/src/label.rs
@@ -0,0 +1,36 @@
+//! Convenience trait for decoding/encoding string labels.
+
+use crate::{Decode, Encode, Reader, Writer};
+use core::str::FromStr;
+
+/// Maximum size of any algorithm name/identifier.
+const MAX_LABEL_SIZE: usize = 48;
+
+/// Labels for e.g. cryptographic algorithms.
+///
+/// Receives a blanket impl of [`Decode`] and [`Encode`].
+pub trait Label: AsRef + FromStr {
+ /// Type returned in the event of an encoding error.
+ type Error: From;
+}
+
+impl Decode for T {
+ type Error = T::Error;
+
+ fn decode(reader: &mut impl Reader) -> Result {
+ let mut buf = [0u8; MAX_LABEL_SIZE];
+ reader.read_string(buf.as_mut())?.parse()
+ }
+}
+
+impl Encode for T {
+ type Error = T::Error;
+
+ fn encoded_len(&self) -> Result {
+ Ok(self.as_ref().encoded_len()?)
+ }
+
+ fn encode(&self, writer: &mut impl Writer) -> Result<(), T::Error> {
+ Ok(self.as_ref().encode(writer)?)
+ }
+}
diff --git a/ssh-encoding/src/lib.rs b/ssh-encoding/src/lib.rs
new file mode 100644
index 00000000..148c89a0
--- /dev/null
+++ b/ssh-encoding/src/lib.rs
@@ -0,0 +1,57 @@
+#![no_std]
+#![cfg_attr(docsrs, feature(doc_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 = "alloc")]
+#[macro_use]
+extern crate alloc;
+#[cfg(feature = "std")]
+extern crate std;
+
+mod checked;
+mod decode;
+mod encode;
+mod error;
+mod label;
+mod reader;
+mod writer;
+
+pub use crate::{
+ checked::CheckedSum,
+ decode::Decode,
+ encode::Encode,
+ error::{Error, Result},
+ label::Label,
+ reader::{NestedReader, Reader},
+ writer::Writer,
+};
+
+#[cfg(feature = "base64")]
+pub use {
+ crate::{
+ reader::Base64Reader,
+ writer::{base64_len_approx, Base64Writer},
+ },
+ base64,
+};
+
+#[cfg(feature = "pem")]
+pub use pem::{self, LineEnding};
+
+/// Line width used by the PEM encoding of OpenSSH documents.
+pub const PEM_LINE_WIDTH: usize = 70;
diff --git a/ssh-key/src/reader.rs b/ssh-encoding/src/reader.rs
similarity index 88%
rename from ssh-key/src/reader.rs
rename to ssh-encoding/src/reader.rs
index d5e3f0f5..d2302f36 100644
--- a/ssh-key/src/reader.rs
+++ b/ssh-encoding/src/reader.rs
@@ -2,14 +2,15 @@
use crate::{decode::Decode, Error, Result};
use core::str;
-use pem_rfc7468 as pem;
/// Constant-time Base64 reader implementation.
-pub(crate) type Base64Reader<'i> = base64ct::Decoder<'i, base64ct::Base64>;
+#[cfg(feature = "base64")]
+#[cfg_attr(docsrs, doc(cfg(feature = "base64")))]
+pub type Base64Reader<'i> = base64::Decoder<'i, base64::Base64>;
/// Reader trait which decodes the binary SSH protocol serialization from
/// various inputs.
-pub(crate) trait Reader: Sized {
+pub trait Reader: Sized {
/// Read as much data as is needed to exactly fill `out`.
///
/// This is the base decoding method on which the rest of the trait is
@@ -33,9 +34,10 @@ pub(crate) trait Reader: Sized {
/// Decodes a `uint32` which identifies the length of some encapsulated
/// data, then calls the given nested reader function with the length of
/// the remaining data.
- fn read_nested<'r, T, F>(&'r mut self, f: F) -> Result
+ fn read_nested<'r, T, E, F>(&'r mut self, f: F) -> core::result::Result
where
- F: FnOnce(&mut NestedReader<'r, Self>) -> Result,
+ E: From,
+ F: FnOnce(&mut NestedReader<'r, Self>) -> core::result::Result,
{
let len = usize::decode(self)?;
@@ -49,12 +51,12 @@ pub(crate) trait Reader: Sized {
///
/// > A byte represents an arbitrary 8-bit value (octet). Fixed length
/// > data is sometimes represented as an array of bytes, written
- /// > byte[n], where n is the number of bytes in the array.
+ /// > `byte[n]`, where n is the number of bytes in the array.
///
/// Storage for the byte array must be provided as mutable byte slice in
/// order to accommodate `no_std` use cases.
///
- /// The [`Decode`] impl on [`Vec`] can be used to allocate a buffer for
+ /// The [`Decode`] impl on `Vec` can be used to allocate a buffer for
/// the result.
///
/// [RFC4251 § 5]: https://datatracker.ietf.org/doc/html/rfc4251#section-5
@@ -85,7 +87,7 @@ pub(crate) trait Reader: Sized {
/// Storage for the string data must be provided as mutable byte slice in
/// order to accommodate `no_std` use cases.
///
- /// The [`Decode`] impl on [`String`] can be used to allocate a buffer for
+ /// The [`Decode`] impl on `String` can be used to allocate a buffer for
/// the result.
///
/// [RFC4251 § 5]: https://datatracker.ietf.org/doc/html/rfc4251#section-5
@@ -127,17 +129,26 @@ pub(crate) trait Reader: Sized {
}
}
-impl Reader for Base64Reader<'_> {
+impl Reader for &[u8] {
fn read<'o>(&mut self, out: &'o mut [u8]) -> Result<&'o [u8]> {
- Ok(self.decode(out)?)
+ if self.len() >= out.len() {
+ let (head, tail) = self.split_at(out.len());
+ *self = tail;
+ out.copy_from_slice(head);
+ Ok(out)
+ } else {
+ Err(Error::Length)
+ }
}
fn remaining_len(&self) -> usize {
- self.remaining_len()
+ self.len()
}
}
-impl Reader for pem::Decoder<'_> {
+#[cfg(feature = "base64")]
+#[cfg_attr(docsrs, doc(cfg(feature = "base64")))]
+impl Reader for Base64Reader<'_> {
fn read<'o>(&mut self, out: &'o mut [u8]) -> Result<&'o [u8]> {
Ok(self.decode(out)?)
}
@@ -147,25 +158,20 @@ impl Reader for pem::Decoder<'_> {
}
}
-impl Reader for &[u8] {
+#[cfg(feature = "pem")]
+#[cfg_attr(docsrs, doc(cfg(feature = "pem")))]
+impl Reader for pem::Decoder<'_> {
fn read<'o>(&mut self, out: &'o mut [u8]) -> Result<&'o [u8]> {
- if self.len() >= out.len() {
- let (head, tail) = self.split_at(out.len());
- *self = tail;
- out.copy_from_slice(head);
- Ok(out)
- } else {
- Err(Error::Length)
- }
+ Ok(self.decode(out)?)
}
fn remaining_len(&self) -> usize {
- self.len()
+ self.remaining_len()
}
}
/// Reader type used by [`Reader::read_nested`].
-pub(crate) struct NestedReader<'r, R: Reader> {
+pub struct NestedReader<'r, R: Reader> {
/// Inner reader type.
inner: &'r mut R,
diff --git a/ssh-key/src/writer.rs b/ssh-encoding/src/writer.rs
similarity index 64%
rename from ssh-key/src/writer.rs
rename to ssh-encoding/src/writer.rs
index cc26ea4b..2454ebc4 100644
--- a/ssh-key/src/writer.rs
+++ b/ssh-encoding/src/writer.rs
@@ -1,53 +1,65 @@
//! Writer trait and associated implementations.
use crate::Result;
-use pem_rfc7468 as pem;
-use sha2::{Digest, Sha256, Sha512};
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
+#[cfg(feature = "sha2")]
+use sha2::{Digest, Sha256, Sha512};
+
/// Get the estimated length of data when encoded as Base64.
///
-/// This is an upper bound where the actual length might be slightly shorter.
-#[cfg(feature = "alloc")]
+/// This is an upper bound where the actual length might be slightly shorter,
+/// and can be used to estimate the capacity of an output buffer. However, the
+/// final result may need to be sliced and should use the actual encoded length
+/// rather than this estimate.
+#[cfg(feature = "base64")]
#[allow(clippy::integer_arithmetic)]
-pub(crate) fn base64_len(input_len: usize) -> usize {
+pub fn base64_len_approx(input_len: usize) -> usize {
// TODO(tarcieri): checked arithmetic
(((input_len * 4) / 3) + 3) & !3
}
/// Constant-time Base64 writer implementation.
-pub(crate) type Base64Writer<'o> = base64ct::Encoder<'o, base64ct::Base64>;
+#[cfg(feature = "base64")]
+#[cfg_attr(docsrs, doc(cfg(feature = "base64")))]
+pub type Base64Writer<'o> = base64::Encoder<'o, base64::Base64>;
/// Writer trait which encodes the SSH binary format to various output
/// encodings.
-pub(crate) trait Writer: Sized {
+pub trait Writer: Sized {
/// Write the given bytes to the writer.
fn write(&mut self, bytes: &[u8]) -> Result<()>;
}
-impl Writer for Base64Writer<'_> {
+#[cfg(feature = "alloc")]
+#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
+impl Writer for Vec {
fn write(&mut self, bytes: &[u8]) -> Result<()> {
- Ok(self.encode(bytes)?)
+ self.extend_from_slice(bytes);
+ Ok(())
}
}
-impl Writer for pem::Encoder<'_, '_> {
+#[cfg(feature = "base64")]
+#[cfg_attr(docsrs, doc(cfg(feature = "base64")))]
+impl Writer for Base64Writer<'_> {
fn write(&mut self, bytes: &[u8]) -> Result<()> {
Ok(self.encode(bytes)?)
}
}
-#[cfg(feature = "alloc")]
-#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
-impl Writer for Vec {
+#[cfg(feature = "pem")]
+#[cfg_attr(docsrs, doc(cfg(feature = "pem")))]
+impl Writer for pem::Encoder<'_, '_> {
fn write(&mut self, bytes: &[u8]) -> Result<()> {
- self.extend_from_slice(bytes);
- Ok(())
+ Ok(self.encode(bytes)?)
}
}
+#[cfg(feature = "sha2")]
+#[cfg_attr(docsrs, doc(cfg(feature = "sha2")))]
impl Writer for Sha256 {
fn write(&mut self, bytes: &[u8]) -> Result<()> {
self.update(bytes);
@@ -55,6 +67,8 @@ impl Writer for Sha256 {
}
}
+#[cfg(feature = "sha2")]
+#[cfg_attr(docsrs, doc(cfg(feature = "sha2")))]
impl Writer for Sha512 {
fn write(&mut self, bytes: &[u8]) -> Result<()> {
self.update(bytes);
diff --git a/ssh-key/Cargo.toml b/ssh-key/Cargo.toml
index faf0e2e8..1b7a0bf5 100644
--- a/ssh-key/Cargo.toml
+++ b/ssh-key/Cargo.toml
@@ -18,8 +18,7 @@ edition = "2021"
rust-version = "1.60"
[dependencies]
-base64ct = "1.4"
-pem-rfc7468 = "0.6"
+encoding = { package = "ssh-encoding", version = "0", features = ["base64", "pem", "sha2"], path = "../ssh-encoding" }
sha2 = { version = "0.10.6", default-features = false }
zeroize = { version = "1", default-features = false }
@@ -48,14 +47,17 @@ zeroize_derive = "1.3" # hack to make minimal-versions lint happy (pulled in by
[features]
default = ["ecdsa", "rand_core", "std"]
-alloc = ["base64ct/alloc", "signature/hazmat-preview", "zeroize/alloc"]
+alloc = [
+ "encoding/alloc",
+ "signature/hazmat-preview",
+ "zeroize/alloc"
+]
std = [
"alloc",
- "base64ct/std",
"ed25519-dalek?/std",
+ "encoding/std",
"p256?/std",
"p384?/std",
- "pem-rfc7468/std",
"rsa?/std",
"sec1?/std",
"signature?/std"
diff --git a/ssh-key/src/algorithm.rs b/ssh-key/src/algorithm.rs
index 2465cd91..ca211614 100644
--- a/ssh-key/src/algorithm.rs
+++ b/ssh-key/src/algorithm.rs
@@ -1,7 +1,8 @@
//! Algorithm support.
-use crate::{decode::Decode, encode::Encode, reader::Reader, writer::Writer, Error, Result};
+use crate::{Error, Result};
use core::{fmt, str};
+use encoding::Label;
#[cfg(feature = "alloc")]
use {
@@ -75,34 +76,6 @@ const SK_ECDSA_SHA2_P256: &str = "sk-ecdsa-sha2-nistp256@openssh.com";
/// U2F/FIDO security key with Ed25519
const SK_SSH_ED25519: &str = "sk-ssh-ed25519@openssh.com";
-/// Maximum size of any algorithm name/identifier.
-const MAX_ALG_NAME_SIZE: usize = 48;
-
-/// String identifiers for cryptographic algorithms.
-///
-/// Receives a blanket impl of [`Decode`] and [`Encode`].
-pub(crate) trait AlgString: AsRef + str::FromStr {}
-
-impl Decode for T {
- fn decode(reader: &mut impl Reader) -> Result {
- let mut buf = [0u8; MAX_ALG_NAME_SIZE];
- reader
- .read_string(buf.as_mut())
- .map_err(|_| Error::Algorithm)?
- .parse()
- }
-}
-
-impl Encode for T {
- fn encoded_len(&self) -> Result {
- self.as_ref().encoded_len()
- }
-
- fn encode(&self, writer: &mut impl Writer) -> Result<()> {
- self.as_ref().encode(writer)
- }
-}
-
/// SSH key algorithms.
///
/// This type provides a registry of supported digital signature algorithms
@@ -285,7 +258,9 @@ impl AsRef for Algorithm {
}
}
-impl AlgString for Algorithm {}
+impl Label for Algorithm {
+ type Error = Error;
+}
impl Default for Algorithm {
fn default() -> Algorithm {
@@ -363,7 +338,9 @@ impl AsRef for EcdsaCurve {
}
}
-impl AlgString for EcdsaCurve {}
+impl Label for EcdsaCurve {
+ type Error = Error;
+}
impl fmt::Display for EcdsaCurve {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
@@ -432,7 +409,9 @@ impl HashAlg {
}
}
-impl AlgString for HashAlg {}
+impl Label for HashAlg {
+ type Error = Error;
+}
impl AsRef for HashAlg {
fn as_ref(&self) -> &str {
@@ -498,7 +477,9 @@ impl KdfAlg {
}
}
-impl AlgString for KdfAlg {}
+impl Label for KdfAlg {
+ type Error = Error;
+}
impl AsRef for KdfAlg {
fn as_ref(&self) -> &str {
diff --git a/ssh-key/src/authorized_keys.rs b/ssh-key/src/authorized_keys.rs
index c24ab055..d449a136 100644
--- a/ssh-key/src/authorized_keys.rs
+++ b/ssh-key/src/authorized_keys.rs
@@ -293,10 +293,10 @@ impl<'a> ConfigOptsIter<'a> {
| b'}'
| b'|'
| b'~' => (),
- _ => return Err(Error::CharacterEncoding),
+ _ => return Err(encoding::Error::CharacterEncoding.into()),
}
- index = index.checked_add(1).ok_or(Error::Length)?;
+ index = index.checked_add(1).ok_or(encoding::Error::Length)?;
}
let remaining = self.0;
@@ -317,7 +317,6 @@ impl<'a> Iterator for ConfigOptsIter<'a> {
#[cfg(all(test, feature = "alloc"))]
mod tests {
use super::ConfigOptsIter;
- use crate::Error;
#[test]
fn options_empty() {
@@ -362,10 +361,16 @@ mod tests {
#[test]
fn options_invalid_character() {
let mut opts = ConfigOptsIter("❌");
- assert_eq!(opts.try_next(), Err(Error::CharacterEncoding));
+ assert_eq!(
+ opts.try_next(),
+ Err(encoding::Error::CharacterEncoding.into())
+ );
let mut opts = ConfigOptsIter("x,❌");
assert_eq!(opts.try_next(), Ok(Some("x")));
- assert_eq!(opts.try_next(), Err(Error::CharacterEncoding));
+ assert_eq!(
+ opts.try_next(),
+ Err(encoding::Error::CharacterEncoding.into())
+ );
}
}
diff --git a/ssh-key/src/certificate.rs b/ssh-key/src/certificate.rs
index 26e99e05..a1530246 100644
--- a/ssh-key/src/certificate.rs
+++ b/ssh-key/src/certificate.rs
@@ -10,12 +10,7 @@ pub use self::{builder::Builder, cert_type::CertType, field::Field, options_map:
use self::unix_time::UnixTime;
use crate::{
- checked::CheckedSum,
- decode::Decode,
- encode::Encode,
public::{Encapsulation, KeyData},
- reader::{Base64Reader, Reader},
- writer::{base64_len, Writer},
Algorithm, Error, Fingerprint, HashAlg, Result, Signature,
};
use alloc::{
@@ -24,6 +19,7 @@ use alloc::{
vec::Vec,
};
use core::str::FromStr;
+use encoding::{base64_len_approx, Base64Reader, CheckedSum, Decode, Encode, Reader, Writer};
use signature::Verifier;
#[cfg(feature = "serde")]
@@ -185,14 +181,14 @@ impl Certificate {
}
cert.comment = encapsulation.comment.to_owned();
- reader.finish(cert)
+ Ok(reader.finish(cert)?)
}
/// Parse a raw binary OpenSSH certificate.
pub fn from_bytes(mut bytes: &[u8]) -> Result {
let reader = &mut bytes;
let cert = Certificate::decode(reader)?;
- reader.finish(cert)
+ Ok(reader.finish(cert)?)
}
/// Encode OpenSSH certificate to a [`String`].
@@ -200,7 +196,7 @@ impl Certificate {
let encoded_len = [
2, // interstitial spaces
self.algorithm().as_certificate_str().len(),
- base64_len(self.encoded_len()?),
+ base64_len_approx(self.encoded_len()?),
self.comment.len(),
]
.checked_sum()?;
@@ -472,6 +468,8 @@ impl Certificate {
}
impl Decode for Certificate {
+ type Error = Error;
+
fn decode(reader: &mut impl Reader) -> Result {
let algorithm = Algorithm::new_certificate(&String::decode(reader)?)?;
@@ -495,8 +493,10 @@ impl Decode for Certificate {
}
impl Encode for Certificate {
+ type Error = Error;
+
fn encoded_len(&self) -> Result {
- [
+ Ok([
self.algorithm().as_certificate_str().encoded_len()?,
self.nonce.encoded_len()?,
self.public_key.encoded_key_data_len()?,
@@ -514,7 +514,7 @@ impl Encode for Certificate {
4, // signature length prefix (uint32)
self.signature.encoded_len()?,
]
- .checked_sum()
+ .checked_sum()?)
}
fn encode(&self, writer: &mut impl Writer) -> Result<()> {
diff --git a/ssh-key/src/certificate/cert_type.rs b/ssh-key/src/certificate/cert_type.rs
index 9ba47d3b..38f040f7 100644
--- a/ssh-key/src/certificate/cert_type.rs
+++ b/ssh-key/src/certificate/cert_type.rs
@@ -1,5 +1,7 @@
-/// OpenSSH certificate types.
-use crate::{decode::Decode, encode::Encode, reader::Reader, writer::Writer, Error, Result};
+//! OpenSSH certificate types.
+
+use crate::{Error, Result};
+use encoding::{Decode, Encode, Reader, Writer};
/// Types of OpenSSH certificates: user or host.
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
@@ -24,25 +26,30 @@ impl CertType {
}
}
-impl Decode for CertType {
- fn decode(reader: &mut impl Reader) -> Result {
- u32::decode(reader)?.try_into()
- }
-}
-
impl Default for CertType {
fn default() -> Self {
Self::User
}
}
+impl Decode for CertType {
+ type Error = Error;
+
+ fn decode(reader: &mut impl Reader) -> Result {
+ u32::decode(reader)?.try_into()
+ }
+}
+
impl Encode for CertType {
+ type Error = Error;
+
fn encoded_len(&self) -> Result {
Ok(4)
}
fn encode(&self, writer: &mut impl Writer) -> Result<()> {
- u32::from(*self).encode(writer)
+ u32::from(*self).encode(writer)?;
+ Ok(())
}
}
diff --git a/ssh-key/src/certificate/options_map.rs b/ssh-key/src/certificate/options_map.rs
index ff9461b5..7bf639c4 100644
--- a/ssh-key/src/certificate/options_map.rs
+++ b/ssh-key/src/certificate/options_map.rs
@@ -1,16 +1,41 @@
//! OpenSSH certificate options used by critical options and extensions.
-use crate::{
- checked::CheckedSum, decode::Decode, encode::Encode, reader::Reader, writer::Writer, Error,
- Result,
+use crate::{Error, Result};
+use alloc::{collections::BTreeMap, string::String, vec::Vec};
+use core::{
+ cmp::Ordering,
+ ops::{Deref, DerefMut},
};
-use alloc::{string::String, vec::Vec};
-use core::cmp::Ordering;
+use encoding::{CheckedSum, Decode, Encode, Reader, Writer};
/// Key/value map type used for certificate's critical options and extensions.
-pub type OptionsMap = alloc::collections::BTreeMap;
+#[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Ord)]
+pub struct OptionsMap(pub BTreeMap);
+
+impl OptionsMap {
+ /// Create a new [`OptionsMap`].
+ pub fn new() -> Self {
+ Self::default()
+ }
+}
+
+impl Deref for OptionsMap {
+ type Target = BTreeMap;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+
+impl DerefMut for OptionsMap {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.0
+ }
+}
impl Decode for OptionsMap {
+ type Error = Error;
+
fn decode(reader: &mut impl Reader) -> Result {
reader.read_nested(|reader| {
let mut entries = Vec::<(String, String)>::new();
@@ -37,19 +62,23 @@ impl Decode for OptionsMap {
}
impl Encode for OptionsMap {
+ type Error = Error;
+
fn encoded_len(&self) -> Result {
- self.iter().try_fold(4, |acc, (name, data)| {
- [acc, 4, name.len(), 4, data.len()].checked_sum()
- })
+ self.iter()
+ .try_fold(4, |acc, (name, data)| {
+ [acc, 4, name.len(), 4, data.len()].checked_sum()
+ })
+ .map_err(Into::into)
}
fn encode(&self, writer: &mut impl Writer) -> Result<()> {
self.encoded_len()?
.checked_sub(4)
- .ok_or(Error::Length)?
+ .ok_or(encoding::Error::Length)?
.encode(writer)?;
- for (name, data) in self {
+ for (name, data) in self.iter() {
name.encode(writer)?;
data.encode(writer)?;
}
@@ -57,3 +86,24 @@ impl Encode for OptionsMap {
Ok(())
}
}
+
+impl From> for OptionsMap {
+ fn from(map: BTreeMap) -> OptionsMap {
+ OptionsMap(map)
+ }
+}
+
+impl From for BTreeMap {
+ fn from(map: OptionsMap) -> BTreeMap {
+ map.0
+ }
+}
+
+impl FromIterator<(String, String)> for OptionsMap {
+ fn from_iter(iter: T) -> Self
+ where
+ T: IntoIterator- ,
+ {
+ BTreeMap::from_iter(iter).into()
+ }
+}
diff --git a/ssh-key/src/certificate/unix_time.rs b/ssh-key/src/certificate/unix_time.rs
index f2190c49..847ed924 100644
--- a/ssh-key/src/certificate/unix_time.rs
+++ b/ssh-key/src/certificate/unix_time.rs
@@ -1,8 +1,9 @@
//! Unix timestamps.
-use crate::{decode::Decode, encode::Encode, reader::Reader, writer::Writer, Error, Result};
+use crate::{Error, Result};
use core::fmt;
use core::fmt::Formatter;
+use encoding::{Decode, Encode, Reader, Writer};
#[cfg(feature = "std")]
use std::time::{Duration, SystemTime, UNIX_EPOCH};
@@ -65,18 +66,23 @@ impl UnixTime {
}
impl Decode for UnixTime {
+ type Error = Error;
+
fn decode(reader: &mut impl Reader) -> Result {
u64::decode(reader)?.try_into()
}
}
impl Encode for UnixTime {
+ type Error = Error;
+
fn encoded_len(&self) -> Result {
- self.secs.encoded_len()
+ Ok(self.secs.encoded_len()?)
}
fn encode(&self, writer: &mut impl Writer) -> Result<()> {
- self.secs.encode(writer)
+ self.secs.encode(writer)?;
+ Ok(())
}
}
diff --git a/ssh-key/src/cipher.rs b/ssh-key/src/cipher.rs
index d4a74526..df3e80b4 100644
--- a/ssh-key/src/cipher.rs
+++ b/ssh-key/src/cipher.rs
@@ -2,8 +2,9 @@
//!
//! These are used for encrypting private keys.
-use crate::{algorithm::AlgString, Error, Result};
+use crate::{Error, Result};
use core::{fmt, str};
+use encoding::Label;
#[cfg(feature = "encryption")]
use aes::{
@@ -126,7 +127,9 @@ impl AsRef for Cipher {
}
}
-impl AlgString for Cipher {}
+impl Label for Cipher {
+ type Error = Error;
+}
impl Default for Cipher {
fn default() -> Cipher {
diff --git a/ssh-key/src/error.rs b/ssh-key/src/error.rs
index 4436e791..257b5ee4 100644
--- a/ssh-key/src/error.rs
+++ b/ssh-key/src/error.rs
@@ -1,6 +1,5 @@
//! Error types
-use crate::pem;
use core::fmt;
#[cfg(feature = "alloc")]
@@ -16,9 +15,6 @@ pub enum Error {
/// Algorithm-related errors.
Algorithm,
- /// Base64-related errors.
- Base64(base64ct::Error),
-
/// Certificate field is invalid or already set.
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
@@ -27,9 +23,6 @@ pub enum Error {
/// Certificate validation failed.
CertificateValidation,
- /// Character encoding-related errors.
- CharacterEncoding,
-
/// Cryptographic errors.
Crypto,
@@ -41,6 +34,9 @@ pub enum Error {
#[cfg_attr(docsrs, doc(cfg(feature = "ecdsa")))]
Ecdsa(sec1::Error),
+ /// Encoding errors.
+ Encoding(encoding::Error),
+
/// Cannot perform operation on encrypted private key.
Encrypted,
@@ -52,18 +48,9 @@ pub enum Error {
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
Io(std::io::ErrorKind),
- /// Invalid length.
- Length,
-
/// Namespace invalid.
Namespace,
- /// Overflow errors.
- Overflow,
-
- /// PEM encoding errors.
- Pem(pem::Error),
-
/// Public key is incorrect.
PublicKey,
@@ -87,25 +74,21 @@ impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::Algorithm => write!(f, "unknown or unsupported algorithm"),
- Error::Base64(err) => write!(f, "Base64 encoding error: {}", err),
#[cfg(feature = "alloc")]
Error::CertificateFieldInvalid(field) => {
write!(f, "certificate field invalid: {}", field)
}
Error::CertificateValidation => write!(f, "certificate validation failed"),
- Error::CharacterEncoding => write!(f, "character encoding invalid"),
Error::Crypto => write!(f, "cryptographic error"),
Error::Decrypted => write!(f, "private key is already decrypted"),
#[cfg(feature = "ecdsa")]
Error::Ecdsa(err) => write!(f, "ECDSA encoding error: {}", err),
+ Error::Encoding(err) => write!(f, "{}", err),
Error::Encrypted => write!(f, "private key is encrypted"),
Error::FormatEncoding => write!(f, "format encoding error"),
#[cfg(feature = "std")]
Error::Io(err) => write!(f, "I/O error: {}", std::io::Error::from(*err)),
- Error::Length => write!(f, "length invalid"),
Error::Namespace => write!(f, "namespace invalid"),
- Error::Overflow => write!(f, "internal overflow error"),
- Error::Pem(err) => write!(f, "{}", err),
Error::PublicKey => write!(f, "public key is incorrect"),
Error::Time => write!(f, "invalid time"),
Error::TrailingData { remaining } => write!(
@@ -118,47 +101,41 @@ impl fmt::Display for Error {
}
}
-impl From for Error {
- fn from(err: base64ct::Error) -> Error {
- Error::Base64(err)
- }
-}
-
-impl From for Error {
- fn from(_: base64ct::InvalidLengthError) -> Error {
- Error::Length
+impl From for Error {
+ fn from(_: core::array::TryFromSliceError) -> Error {
+ Error::Encoding(encoding::Error::Length)
}
}
-impl From for Error {
- fn from(_: core::array::TryFromSliceError) -> Error {
- Error::Length
+impl From for Error {
+ fn from(err: core::str::Utf8Error) -> Error {
+ Error::Encoding(err.into())
}
}
-impl From for Error {
- fn from(_: core::num::TryFromIntError) -> Error {
- Error::Overflow
+impl From for Error {
+ fn from(err: encoding::Error) -> Error {
+ Error::Encoding(err)
}
}
-impl From for Error {
- fn from(_: core::str::Utf8Error) -> Error {
- Error::CharacterEncoding
+impl From for Error {
+ fn from(err: encoding::base64::Error) -> Error {
+ Error::Encoding(err.into())
}
}
-impl From for Error {
- fn from(err: pem_rfc7468::Error) -> Error {
- Error::Pem(err)
+impl From for Error {
+ fn from(err: encoding::pem::Error) -> Error {
+ Error::Encoding(err.into())
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
impl From for Error {
- fn from(_: alloc::string::FromUtf8Error) -> Error {
- Error::CharacterEncoding
+ fn from(err: alloc::string::FromUtf8Error) -> Error {
+ Error::Encoding(err.into())
}
}
@@ -203,6 +180,7 @@ impl From for Error {
}
#[cfg(feature = "std")]
+#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
impl From for Error {
fn from(_: std::time::SystemTimeError) -> Error {
Error::Time
@@ -210,4 +188,5 @@ impl From for Error {
}
#[cfg(feature = "std")]
+#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
impl std::error::Error for Error {}
diff --git a/ssh-key/src/fingerprint.rs b/ssh-key/src/fingerprint.rs
index 7231e439..563b701d 100644
--- a/ssh-key/src/fingerprint.rs
+++ b/ssh-key/src/fingerprint.rs
@@ -1,11 +1,14 @@
//! SSH public key fingerprints.
-use crate::{encode::Encode, public, Error, HashAlg, Result};
-use base64ct::{Base64Unpadded, Encoding};
+use crate::{public, Error, HashAlg, Result};
use core::{
fmt::{self, Display},
str::{self, FromStr},
};
+use encoding::{
+ base64::{Base64Unpadded, Encoding},
+ Encode,
+};
use sha2::{Digest, Sha256, Sha512};
/// Fingerprint encoding error message.
diff --git a/ssh-key/src/kdf.rs b/ssh-key/src/kdf.rs
index 5b4a49bb..488a5a87 100644
--- a/ssh-key/src/kdf.rs
+++ b/ssh-key/src/kdf.rs
@@ -2,10 +2,8 @@
//!
//! These are used for deriving an encryption key from a password.
-use crate::{
- checked::CheckedSum, decode::Decode, encode::Encode, reader::Reader, writer::Writer, Error,
- KdfAlg, Result,
-};
+use crate::{Error, KdfAlg, Result};
+use encoding::{CheckedSum, Decode, Encode, Reader, Writer};
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
@@ -98,7 +96,9 @@ impl Kdf {
password: impl AsRef<[u8]>,
) -> Result<(Zeroizing>, Vec)> {
let (key_size, iv_size) = cipher.key_and_iv_size().ok_or(Error::Decrypted)?;
- let okm_size = key_size.checked_add(iv_size).ok_or(Error::Length)?;
+ let okm_size = key_size
+ .checked_add(iv_size)
+ .ok_or(encoding::Error::Length)?;
let mut okm = Zeroizing::new(vec![0u8; okm_size]);
self.derive(password, &mut okm)?;
@@ -131,6 +131,8 @@ impl Default for Kdf {
}
impl Decode for Kdf {
+ type Error = Error;
+
fn decode(reader: &mut impl Reader) -> Result {
match KdfAlg::decode(reader)? {
KdfAlg::None => {
@@ -157,6 +159,8 @@ impl Decode for Kdf {
}
impl Encode for Kdf {
+ type Error = Error;
+
fn encoded_len(&self) -> Result {
let kdfopts_len = match self {
Self::None => 0,
@@ -164,25 +168,27 @@ impl Encode for Kdf {
Self::Bcrypt { salt, .. } => [8, salt.len()].checked_sum()?,
};
- [
+ Ok([
self.algorithm().encoded_len()?,
4, // kdfopts length prefix (uint32)
kdfopts_len,
]
- .checked_sum()
+ .checked_sum()?)
}
fn encode(&self, writer: &mut impl Writer) -> Result<()> {
self.algorithm().encode(writer)?;
match self {
- Self::None => 0usize.encode(writer),
+ Self::None => 0usize.encode(writer)?,
#[cfg(feature = "alloc")]
Self::Bcrypt { salt, rounds } => {
[8, salt.len()].checked_sum()?.encode(writer)?;
salt.encode(writer)?;
- rounds.encode(writer)
+ rounds.encode(writer)?
}
}
+
+ Ok(())
}
}
diff --git a/ssh-key/src/known_hosts.rs b/ssh-key/src/known_hosts.rs
index 0ba12ac1..197ec214 100644
--- a/ssh-key/src/known_hosts.rs
+++ b/ssh-key/src/known_hosts.rs
@@ -1,9 +1,8 @@
//! Parser for `KnownHostsFile`-formatted data.
-use base64ct::{Base64, Encoding};
-
use crate::{Error, PublicKey, Result};
use core::str;
+use encoding::base64::{Base64, Encoding};
use {
alloc::string::{String, ToString},
diff --git a/ssh-key/src/lib.rs b/ssh-key/src/lib.rs
index 6b8621ad..638ad48e 100644
--- a/ssh-key/src/lib.rs
+++ b/ssh-key/src/lib.rs
@@ -149,15 +149,10 @@ pub mod public;
pub mod certificate;
mod algorithm;
-mod checked;
mod cipher;
-mod decode;
-mod encode;
mod error;
mod fingerprint;
mod kdf;
-mod reader;
-mod writer;
#[cfg(feature = "alloc")]
mod mpint;
@@ -176,8 +171,7 @@ pub use crate::{
private::PrivateKey,
public::PublicKey,
};
-pub use base64ct::LineEnding;
-pub use pem_rfc7468 as pem;
+pub use encoding::LineEnding;
pub use sha2;
#[cfg(feature = "alloc")]
@@ -194,6 +188,3 @@ pub use sec1;
#[cfg(feature = "rand_core")]
pub use rand_core;
-
-/// Line width used by the PEM encoding of OpenSSH documents.
-const PEM_LINE_WIDTH: usize = 70;
diff --git a/ssh-key/src/mpint.rs b/ssh-key/src/mpint.rs
index eb925a92..149e40ed 100644
--- a/ssh-key/src/mpint.rs
+++ b/ssh-key/src/mpint.rs
@@ -1,11 +1,9 @@
//! Multiple precision integer
-use crate::{
- checked::CheckedSum, decode::Decode, encode::Encode, reader::Reader, writer::Writer, Error,
- Result,
-};
+use crate::{Error, Result};
use alloc::vec::Vec;
use core::fmt;
+use encoding::{CheckedSum, Decode, Encode, Reader, Writer};
use zeroize::Zeroize;
#[cfg(any(feature = "dsa", feature = "rsa"))]
@@ -107,18 +105,23 @@ impl AsRef<[u8]> for MPInt {
}
impl Decode for MPInt {
+ type Error = Error;
+
fn decode(reader: &mut impl Reader) -> Result {
Vec::decode(reader)?.try_into()
}
}
impl Encode for MPInt {
+ type Error = Error;
+
fn encoded_len(&self) -> Result {
- [4, self.as_bytes().len()].checked_sum()
+ Ok([4, self.as_bytes().len()].checked_sum()?)
}
fn encode(&self, writer: &mut impl Writer) -> Result<()> {
- self.as_bytes().encode(writer)
+ self.as_bytes().encode(writer)?;
+ Ok(())
}
}
diff --git a/ssh-key/src/private.rs b/ssh-key/src/private.rs
index 85bc843a..b3d9cfcc 100644
--- a/ssh-key/src/private.rs
+++ b/ssh-key/src/private.rs
@@ -115,8 +115,10 @@ mod rsa;
#[cfg(feature = "alloc")]
mod sk;
-pub use self::ed25519::{Ed25519Keypair, Ed25519PrivateKey};
-pub use self::keypair::KeypairData;
+pub use self::{
+ ed25519::{Ed25519Keypair, Ed25519PrivateKey},
+ keypair::KeypairData,
+};
#[cfg(feature = "alloc")]
pub use crate::{
@@ -134,17 +136,12 @@ pub use self::ecdsa::{EcdsaKeypair, EcdsaPrivateKey};
#[cfg(all(feature = "alloc", feature = "ecdsa"))]
pub use self::sk::SkEcdsaSha2NistP256;
-use crate::{
- checked::CheckedSum,
- decode::Decode,
- encode::Encode,
+use crate::{public, Algorithm, Cipher, Error, Fingerprint, HashAlg, Kdf, PublicKey, Result};
+use core::str;
+use encoding::{
pem::{self, LineEnding, PemLabel},
- public,
- reader::Reader,
- writer::Writer,
- Algorithm, Cipher, Error, Fingerprint, HashAlg, Kdf, PublicKey, Result, PEM_LINE_WIDTH,
+ CheckedSum, Decode, Encode, Reader, Writer, PEM_LINE_WIDTH,
};
-use core::str;
#[cfg(feature = "alloc")]
use {
@@ -232,14 +229,14 @@ impl PrivateKey {
let mut reader = pem::Decoder::new_wrapped(input.as_ref(), PEM_LINE_WIDTH)?;
Self::validate_pem_label(reader.type_label())?;
let private_key = Self::decode(&mut reader)?;
- reader.finish(private_key)
+ Ok(reader.finish(private_key)?)
}
/// Parse a raw binary SSH private key.
pub fn from_bytes(mut bytes: &[u8]) -> Result {
let reader = &mut bytes;
let private_key = Self::decode(reader)?;
- reader.finish(private_key)
+ Ok(reader.finish(private_key)?)
}
/// Encode OpenSSH-formatted (PEM) private key.
@@ -530,7 +527,7 @@ impl PrivateKey {
// Ensure input data is padding-aligned
if reader.remaining_len().checked_rem(block_size) != Some(0) {
- return Err(Error::Length);
+ return Err(encoding::Error::Length.into());
}
let checkint1 = u32::decode(reader)?;
@@ -553,7 +550,7 @@ impl PrivateKey {
let padding_len = reader.remaining_len();
if padding_len >= block_size {
- return Err(Error::Length);
+ return Err(encoding::Error::Length.into());
}
if padding_len != 0 {
@@ -603,7 +600,7 @@ impl PrivateKey {
/// and padded using the padding size for the given cipher.
fn encoded_privatekey_comment_pair_len(&self, cipher: Cipher) -> Result {
let len = self.unpadded_privatekey_comment_pair_len()?;
- [len, cipher.padding_len(len)].checked_sum()
+ Ok([len, cipher.padding_len(len)].checked_sum()?)
}
/// Get the length of this private key when encoded with the given comment.
@@ -616,16 +613,18 @@ impl PrivateKey {
return Err(Error::Encrypted);
}
- [
+ Ok([
8, // 2 x uint32 checkints,
self.key_data.encoded_len()?,
self.comment().encoded_len()?,
]
- .checked_sum()
+ .checked_sum()?)
}
}
impl Decode for PrivateKey {
+ type Error = Error;
+
fn decode(reader: &mut impl Reader) -> Result {
let mut auth_magic = [0u8; Self::AUTH_MAGIC.len()];
reader.read(&mut auth_magic)?;
@@ -640,7 +639,7 @@ impl Decode for PrivateKey {
// TODO(tarcieri): support more than one key?
if nkeys != 1 {
- return Err(Error::Length);
+ return Err(encoding::Error::Length.into());
}
let public_key = reader.read_nested(public::KeyData::decode)?;
@@ -660,7 +659,7 @@ impl Decode for PrivateKey {
}
if !reader.is_finished() {
- return Err(Error::Length);
+ return Err(encoding::Error::Length.into());
}
return Ok(Self {
@@ -684,6 +683,8 @@ impl Decode for PrivateKey {
}
impl Encode for PrivateKey {
+ type Error = Error;
+
fn encoded_len(&self) -> Result {
let private_key_len = if self.is_encrypted() {
self.key_data.encoded_len()?
@@ -691,7 +692,7 @@ impl Encode for PrivateKey {
self.encoded_privatekey_comment_pair_len(Cipher::None)?
};
- [
+ Ok([
Self::AUTH_MAGIC.len(),
self.cipher.encoded_len()?,
self.kdf.encoded_len()?,
@@ -701,7 +702,7 @@ impl Encode for PrivateKey {
4, // private key length prefix (uint32)
private_key_len,
]
- .checked_sum()
+ .checked_sum()?)
}
fn encode(&self, writer: &mut impl Writer) -> Result<()> {
diff --git a/ssh-key/src/private/dsa.rs b/ssh-key/src/private/dsa.rs
index 34370e04..f9aa8920 100644
--- a/ssh-key/src/private/dsa.rs
+++ b/ssh-key/src/private/dsa.rs
@@ -1,15 +1,10 @@
//! Digital Signature Algorithm (DSA) private keys.
-use crate::{
- checked::CheckedSum, decode::Decode, encode::Encode, public::DsaPublicKey, reader::Reader,
- writer::Writer, MPInt, Result,
-};
+use crate::{public::DsaPublicKey, Error, MPInt, Result};
use core::fmt;
+use encoding::{CheckedSum, Decode, Encode, Reader, Writer};
use zeroize::Zeroize;
-#[cfg(feature = "dsa")]
-use crate::Error;
-
#[cfg(feature = "subtle")]
use subtle::{Choice, ConstantTimeEq};
@@ -48,6 +43,8 @@ impl AsRef<[u8]> for DsaPrivateKey {
}
impl Decode for DsaPrivateKey {
+ type Error = Error;
+
fn decode(reader: &mut impl Reader) -> Result {
Ok(Self {
inner: MPInt::decode(reader)?,
@@ -62,6 +59,8 @@ impl Drop for DsaPrivateKey {
}
impl Encode for DsaPrivateKey {
+ type Error = Error;
+
fn encoded_len(&self) -> Result {
self.inner.encoded_len()
}
@@ -166,6 +165,8 @@ impl DsaKeypair {
}
impl Decode for DsaKeypair {
+ type Error = Error;
+
fn decode(reader: &mut impl Reader) -> Result {
let public = DsaPublicKey::decode(reader)?;
let private = DsaPrivateKey::decode(reader)?;
@@ -174,8 +175,10 @@ impl Decode for DsaKeypair {
}
impl Encode for DsaKeypair {
+ type Error = Error;
+
fn encoded_len(&self) -> Result {
- [self.public.encoded_len()?, self.private.encoded_len()?].checked_sum()
+ Ok([self.public.encoded_len()?, self.private.encoded_len()?].checked_sum()?)
}
fn encode(&self, writer: &mut impl Writer) -> Result<()> {
diff --git a/ssh-key/src/private/ecdsa.rs b/ssh-key/src/private/ecdsa.rs
index c1f520da..16b15bc1 100644
--- a/ssh-key/src/private/ecdsa.rs
+++ b/ssh-key/src/private/ecdsa.rs
@@ -1,10 +1,8 @@
//! Elliptic Curve Digital Signature Algorithm (ECDSA) private keys.
-use crate::{
- checked::CheckedSum, decode::Decode, encode::Encode, public::EcdsaPublicKey, reader::Reader,
- writer::Writer, Algorithm, EcdsaCurve, Error, Result,
-};
+use crate::{public::EcdsaPublicKey, Algorithm, EcdsaCurve, Error, Result};
use core::fmt;
+use encoding::{CheckedSum, Decode, Encode, Reader, Writer};
use sec1::consts::{U32, U48, U66};
use zeroize::Zeroize;
@@ -33,10 +31,18 @@ impl EcdsaPrivateKey {
self.bytes
}
- /// Decode ECDSA private key using the provided Base64 reader.
+ /// Does this private key need to be prefixed with a leading zero?
+ fn needs_leading_zero(&self) -> bool {
+ self.bytes[0] >= 0x80
+ }
+}
+
+impl Decode for EcdsaPrivateKey {
+ type Error = Error;
+
fn decode(reader: &mut impl Reader) -> Result {
reader.read_nested(|reader| {
- if reader.remaining_len() == SIZE.checked_add(1).ok_or(Error::Length)? {
+ if reader.remaining_len() == SIZE.checked_add(1).ok_or(encoding::Error::Length)? {
// Strip leading zero
// TODO(tarcieri): make sure leading zero was necessary
if u8::decode(reader)? != 0 {
@@ -49,16 +55,13 @@ impl EcdsaPrivateKey {
Ok(Self { bytes })
})
}
-
- /// Does this private key need to be prefixed with a leading zero?
- fn needs_leading_zero(&self) -> bool {
- self.bytes[0] >= 0x80
- }
}
impl Encode for EcdsaPrivateKey {
+ type Error = Error;
+
fn encoded_len(&self) -> Result {
- [4, self.needs_leading_zero().into(), SIZE].checked_sum()
+ Ok([4, self.needs_leading_zero().into(), SIZE].checked_sum()?)
}
fn encode(&self, writer: &mut impl Writer) -> Result<()> {
@@ -70,7 +73,8 @@ impl Encode for EcdsaPrivateKey {
writer.write(&[0])?;
}
- writer.write(&self.bytes)
+ writer.write(&self.bytes)?;
+ Ok(())
}
}
@@ -247,6 +251,8 @@ impl EcdsaKeypair {
}
impl Decode for EcdsaKeypair {
+ type Error = Error;
+
fn decode(reader: &mut impl Reader) -> Result {
match EcdsaPublicKey::decode(reader)? {
EcdsaPublicKey::NistP256(public) => {
@@ -266,6 +272,8 @@ impl Decode for EcdsaKeypair {
}
impl Encode for EcdsaKeypair {
+ type Error = Error;
+
fn encoded_len(&self) -> Result {
let public_len = EcdsaPublicKey::from(self).encoded_len()?;
@@ -275,7 +283,7 @@ impl Encode for EcdsaKeypair {
Self::NistP521 { private, .. } => private.encoded_len()?,
};
- [public_len, private_len].checked_sum()
+ Ok([public_len, private_len].checked_sum()?)
}
fn encode(&self, writer: &mut impl Writer) -> Result<()> {
diff --git a/ssh-key/src/private/ed25519.rs b/ssh-key/src/private/ed25519.rs
index a6078598..2babdb35 100644
--- a/ssh-key/src/private/ed25519.rs
+++ b/ssh-key/src/private/ed25519.rs
@@ -2,11 +2,9 @@
//!
//! Edwards Digital Signature Algorithm (EdDSA) over Curve25519.
-use crate::{
- checked::CheckedSum, decode::Decode, encode::Encode, public::Ed25519PublicKey, reader::Reader,
- writer::Writer, Error, Result,
-};
+use crate::{public::Ed25519PublicKey, Error, Result};
use core::fmt;
+use encoding::{CheckedSum, Decode, Encode, Reader, Writer};
use zeroize::{Zeroize, Zeroizing};
#[cfg(feature = "rand_core")]
@@ -211,6 +209,8 @@ impl Ed25519Keypair {
}
impl Decode for Ed25519Keypair {
+ type Error = Error;
+
fn decode(reader: &mut impl Reader) -> Result {
// Decode private key
let public = Ed25519PublicKey::decode(reader)?;
@@ -233,13 +233,16 @@ impl Decode for Ed25519Keypair {
}
impl Encode for Ed25519Keypair {
+ type Error = Error;
+
fn encoded_len(&self) -> Result {
- [4, self.public.encoded_len()?, Self::BYTE_SIZE].checked_sum()
+ Ok([4, self.public.encoded_len()?, Self::BYTE_SIZE].checked_sum()?)
}
fn encode(&self, writer: &mut impl Writer) -> Result<()> {
self.public.encode(writer)?;
- Zeroizing::new(self.to_bytes()).as_ref().encode(writer)
+ Zeroizing::new(self.to_bytes()).as_ref().encode(writer)?;
+ Ok(())
}
}
diff --git a/ssh-key/src/private/keypair.rs b/ssh-key/src/private/keypair.rs
index 3f9d000b..892551c4 100644
--- a/ssh-key/src/private/keypair.rs
+++ b/ssh-key/src/private/keypair.rs
@@ -1,10 +1,8 @@
//! Private key pairs.
use super::ed25519::Ed25519Keypair;
-use crate::{
- checked::CheckedSum, decode::Decode, encode::Encode, public, reader::Reader, writer::Writer,
- Algorithm, Error, Result,
-};
+use crate::{public, Algorithm, Error, Result};
+use encoding::{CheckedSum, Decode, Encode, Reader, Writer};
#[cfg(feature = "alloc")]
use {
@@ -240,6 +238,8 @@ impl KeypairData {
}
impl Decode for KeypairData {
+ type Error = Error;
+
fn decode(reader: &mut impl Reader) -> Result {
match Algorithm::decode(reader)? {
#[cfg(feature = "alloc")]
@@ -265,6 +265,8 @@ impl Decode for KeypairData {
}
impl Encode for KeypairData {
+ type Error = Error;
+
fn encoded_len(&self) -> Result {
let key_len = match self {
#[cfg(feature = "alloc")]
@@ -282,7 +284,7 @@ impl Encode for KeypairData {
Self::SkEd25519(sk) => sk.encoded_len()?,
};
- [self.algorithm()?.encoded_len()?, key_len].checked_sum()
+ Ok([self.algorithm()?.encoded_len()?, key_len].checked_sum()?)
}
fn encode(&self, writer: &mut impl Writer) -> Result<()> {
@@ -292,19 +294,21 @@ impl Encode for KeypairData {
match self {
#[cfg(feature = "alloc")]
- Self::Dsa(key) => key.encode(writer),
+ Self::Dsa(key) => key.encode(writer)?,
#[cfg(feature = "ecdsa")]
- Self::Ecdsa(key) => key.encode(writer),
- Self::Ed25519(key) => key.encode(writer),
+ Self::Ecdsa(key) => key.encode(writer)?,
+ Self::Ed25519(key) => key.encode(writer)?,
#[cfg(feature = "alloc")]
- Self::Encrypted(ciphertext) => writer.write(ciphertext),
+ Self::Encrypted(ciphertext) => writer.write(ciphertext)?,
#[cfg(feature = "alloc")]
- Self::Rsa(key) => key.encode(writer),
+ Self::Rsa(key) => key.encode(writer)?,
#[cfg(all(feature = "alloc", feature = "ecdsa"))]
- Self::SkEcdsaSha2NistP256(sk) => sk.encode(writer),
+ Self::SkEcdsaSha2NistP256(sk) => sk.encode(writer)?,
#[cfg(feature = "alloc")]
- Self::SkEd25519(sk) => sk.encode(writer),
+ Self::SkEd25519(sk) => sk.encode(writer)?,
}
+
+ Ok(())
}
}
diff --git a/ssh-key/src/private/rsa.rs b/ssh-key/src/private/rsa.rs
index 36536e8d..19bb4cd0 100644
--- a/ssh-key/src/private/rsa.rs
+++ b/ssh-key/src/private/rsa.rs
@@ -1,15 +1,12 @@
//! Rivest–Shamir–Adleman (RSA) private keys.
-use crate::{
- checked::CheckedSum, decode::Decode, encode::Encode, public::RsaPublicKey, reader::Reader,
- writer::Writer, MPInt, Result,
-};
+use crate::{public::RsaPublicKey, Error, MPInt, Result};
use core::fmt;
+use encoding::{CheckedSum, Decode, Encode, Reader, Writer};
use zeroize::Zeroize;
#[cfg(feature = "rsa")]
use {
- crate::Error,
rand_core::{CryptoRng, RngCore},
rsa::{pkcs1v15, PublicKeyParts},
sha2::{digest::const_oid::AssociatedOid, Digest},
@@ -36,6 +33,8 @@ pub struct RsaPrivateKey {
}
impl Decode for RsaPrivateKey {
+ type Error = Error;
+
fn decode(reader: &mut impl Reader) -> Result {
let d = MPInt::decode(reader)?;
let iqmp = MPInt::decode(reader)?;
@@ -46,21 +45,24 @@ impl Decode for RsaPrivateKey {
}
impl Encode for RsaPrivateKey {
+ type Error = Error;
+
fn encoded_len(&self) -> Result {
- [
+ Ok([
self.d.encoded_len()?,
self.iqmp.encoded_len()?,
self.p.encoded_len()?,
self.q.encoded_len()?,
]
- .checked_sum()
+ .checked_sum()?)
}
fn encode(&self, writer: &mut impl Writer) -> Result<()> {
self.d.encode(writer)?;
self.iqmp.encode(writer)?;
self.p.encode(writer)?;
- self.q.encode(writer)
+ self.q.encode(writer)?;
+ Ok(())
}
}
@@ -125,6 +127,8 @@ impl RsaKeypair {
}
impl Decode for RsaKeypair {
+ type Error = Error;
+
fn decode(reader: &mut impl Reader) -> Result {
let n = MPInt::decode(reader)?;
let e = MPInt::decode(reader)?;
@@ -135,13 +139,15 @@ impl Decode for RsaKeypair {
}
impl Encode for RsaKeypair {
+ type Error = Error;
+
fn encoded_len(&self) -> Result {
- [
+ Ok([
self.public.n.encoded_len()?,
self.public.e.encoded_len()?,
self.private.encoded_len()?,
]
- .checked_sum()
+ .checked_sum()?)
}
fn encode(&self, writer: &mut impl Writer) -> Result<()> {
diff --git a/ssh-key/src/private/sk.rs b/ssh-key/src/private/sk.rs
index 95016a7e..0b7e9910 100644
--- a/ssh-key/src/private/sk.rs
+++ b/ssh-key/src/private/sk.rs
@@ -2,11 +2,9 @@
//!
//! [PROTOCOL.u2f]: https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.u2f?annotate=HEAD
-use crate::{
- checked::CheckedSum, decode::Decode, encode::Encode, public, reader::Reader, writer::Writer,
- Result,
-};
+use crate::{public, Error, Result};
use alloc::vec::Vec;
+use encoding::{CheckedSum, Decode, Encode, Reader, Writer};
/// Security Key (FIDO/U2F) ECDSA/NIST P-256 private key as specified in
/// [PROTOCOL.u2f](https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.u2f?annotate=HEAD).
@@ -47,6 +45,8 @@ impl SkEcdsaSha2NistP256 {
#[cfg(feature = "ecdsa")]
impl Decode for SkEcdsaSha2NistP256 {
+ type Error = Error;
+
fn decode(reader: &mut impl Reader) -> Result {
Ok(Self {
public: public::SkEcdsaSha2NistP256::decode(reader)?,
@@ -59,21 +59,24 @@ impl Decode for SkEcdsaSha2NistP256 {
#[cfg(feature = "ecdsa")]
impl Encode for SkEcdsaSha2NistP256 {
+ type Error = Error;
+
fn encoded_len(&self) -> Result {
- [
+ Ok([
self.public.encoded_len()?,
self.flags.encoded_len()?,
self.key_handle.encoded_len()?,
self.reserved.encoded_len()?,
]
- .checked_sum()
+ .checked_sum()?)
}
fn encode(&self, writer: &mut impl Writer) -> Result<()> {
self.public.encode(writer)?;
self.flags.encode(writer)?;
self.key_handle.encode(writer)?;
- self.reserved.encode(writer)
+ self.reserved.encode(writer)?;
+ Ok(())
}
}
@@ -114,6 +117,8 @@ impl SkEd25519 {
}
impl Decode for SkEd25519 {
+ type Error = Error;
+
fn decode(reader: &mut impl Reader) -> Result {
Ok(Self {
public: public::SkEd25519::decode(reader)?,
@@ -125,20 +130,23 @@ impl Decode for SkEd25519 {
}
impl Encode for SkEd25519 {
+ type Error = Error;
+
fn encoded_len(&self) -> Result {
- [
+ Ok([
self.public.encoded_len()?,
self.flags.encoded_len()?,
self.key_handle.encoded_len()?,
self.reserved.encoded_len()?,
]
- .checked_sum()
+ .checked_sum()?)
}
fn encode(&self, writer: &mut impl Writer) -> Result<()> {
self.public.encode(writer)?;
self.flags.encode(writer)?;
self.key_handle.encode(writer)?;
- self.reserved.encode(writer)
+ self.reserved.encode(writer)?;
+ Ok(())
}
}
diff --git a/ssh-key/src/public.rs b/ssh-key/src/public.rs
index 60446269..0f5df429 100644
--- a/ssh-key/src/public.rs
+++ b/ssh-key/src/public.rs
@@ -23,22 +23,19 @@ pub use self::{ecdsa::EcdsaPublicKey, sk::SkEcdsaSha2NistP256};
pub(crate) use self::openssh::Encapsulation;
-use crate::{
- decode::Decode,
- encode::Encode,
- reader::{Base64Reader, Reader},
- Algorithm, Error, Fingerprint, HashAlg, Result,
-};
+use crate::{Algorithm, Error, Fingerprint, HashAlg, Result};
use core::str::FromStr;
+use encoding::{Base64Reader, Decode, Encode, Reader};
#[cfg(feature = "alloc")]
use {
- crate::{checked::CheckedSum, writer::base64_len, SshSig},
+ crate::SshSig,
alloc::{
borrow::ToOwned,
string::{String, ToString},
vec::Vec,
},
+ encoding::{base64_len_approx, CheckedSum},
};
#[cfg(all(feature = "alloc", feature = "serde"))]
@@ -121,14 +118,14 @@ impl PublicKey {
comment: encapsulation.comment.to_owned(),
};
- reader.finish(public_key)
+ Ok(reader.finish(public_key)?)
}
/// Parse a raw binary SSH public key.
pub fn from_bytes(mut bytes: &[u8]) -> Result {
let reader = &mut bytes;
let key_data = KeyData::decode(reader)?;
- reader.finish(key_data.into())
+ Ok(reader.finish(key_data.into())?)
}
/// Encode OpenSSH-formatted public key.
@@ -146,7 +143,7 @@ impl PublicKey {
let encoded_len = [
2, // interstitial spaces
self.algorithm().as_str().len(),
- base64_len(self.key_data.encoded_len()?),
+ base64_len_approx(self.key_data.encoded_len()?),
self.comment.len(),
]
.checked_sum()?;
diff --git a/ssh-key/src/public/dsa.rs b/ssh-key/src/public/dsa.rs
index c5563152..00aa5cf4 100644
--- a/ssh-key/src/public/dsa.rs
+++ b/ssh-key/src/public/dsa.rs
@@ -1,12 +1,7 @@
//! Digital Signature Algorithm (DSA) public keys.
-use crate::{
- checked::CheckedSum, decode::Decode, encode::Encode, reader::Reader, writer::Writer, MPInt,
- Result,
-};
-
-#[cfg(feature = "dsa")]
-use crate::Error;
+use crate::{Error, MPInt, Result};
+use encoding::{CheckedSum, Decode, Encode, Reader, Writer};
/// Digital Signature Algorithm (DSA) public key.
///
@@ -29,6 +24,8 @@ pub struct DsaPublicKey {
}
impl Decode for DsaPublicKey {
+ type Error = Error;
+
fn decode(reader: &mut impl Reader) -> Result {
let p = MPInt::decode(reader)?;
let q = MPInt::decode(reader)?;
@@ -39,14 +36,16 @@ impl Decode for DsaPublicKey {
}
impl Encode for DsaPublicKey {
+ type Error = Error;
+
fn encoded_len(&self) -> Result {
- [
+ Ok([
self.p.encoded_len()?,
self.q.encoded_len()?,
self.g.encoded_len()?,
self.y.encoded_len()?,
]
- .checked_sum()
+ .checked_sum()?)
}
fn encode(&self, writer: &mut impl Writer) -> Result<()> {
diff --git a/ssh-key/src/public/ecdsa.rs b/ssh-key/src/public/ecdsa.rs
index 7990c2f6..9c8684b8 100644
--- a/ssh-key/src/public/ecdsa.rs
+++ b/ssh-key/src/public/ecdsa.rs
@@ -1,10 +1,8 @@
//! Elliptic Curve Digital Signature Algorithm (ECDSA) public keys.
-use crate::{
- checked::CheckedSum, decode::Decode, encode::Encode, reader::Reader, writer::Writer, Algorithm,
- EcdsaCurve, Error, Result,
-};
+use crate::{Algorithm, EcdsaCurve, Error, Result};
use core::fmt;
+use encoding::{CheckedSum, Decode, Encode, Reader, Writer};
use sec1::consts::{U32, U48, U66};
/// ECDSA/NIST P-256 public key.
@@ -61,10 +59,10 @@ impl EcdsaPublicKey {
32 => Ok(Self::NistP256(EcdsaNistP256PublicKey::from_bytes(bytes)?)),
48 => Ok(Self::NistP384(EcdsaNistP384PublicKey::from_bytes(bytes)?)),
66 => Ok(Self::NistP521(EcdsaNistP521PublicKey::from_bytes(bytes)?)),
- _ => Err(Error::Length),
+ _ => Err(encoding::Error::Length.into()),
}
}
- _ => Err(Error::Length),
+ _ => Err(encoding::Error::Length.into()),
}
}
@@ -101,6 +99,8 @@ impl AsRef<[u8]> for EcdsaPublicKey {
}
impl Decode for EcdsaPublicKey {
+ type Error = Error;
+
fn decode(reader: &mut impl Reader) -> Result {
let curve = EcdsaCurve::decode(reader)?;
@@ -116,18 +116,21 @@ impl Decode for EcdsaPublicKey {
}
impl Encode for EcdsaPublicKey {
+ type Error = Error;
+
fn encoded_len(&self) -> Result {
- [
+ Ok([
self.curve().encoded_len()?,
4, // uint32 length prefix
self.as_ref().len(),
]
- .checked_sum()
+ .checked_sum()?)
}
fn encode(&self, writer: &mut impl Writer) -> Result<()> {
self.curve().encode(writer)?;
- self.as_ref().encode(writer)
+ self.as_ref().encode(writer)?;
+ Ok(())
}
}
diff --git a/ssh-key/src/public/ed25519.rs b/ssh-key/src/public/ed25519.rs
index 5197a426..cea82e9b 100644
--- a/ssh-key/src/public/ed25519.rs
+++ b/ssh-key/src/public/ed25519.rs
@@ -2,11 +2,9 @@
//!
//! Edwards Digital Signature Algorithm (EdDSA) over Curve25519.
-use crate::{
- checked::CheckedSum, decode::Decode, encode::Encode, reader::Reader, writer::Writer, Error,
- Result,
-};
+use crate::{Error, Result};
use core::fmt;
+use encoding::{CheckedSum, Decode, Encode, Reader, Writer};
/// Ed25519 public key.
// TODO(tarcieri): use `ed25519::PublicKey`? (doesn't exist yet)
@@ -25,6 +23,8 @@ impl AsRef<[u8; Self::BYTE_SIZE]> for Ed25519PublicKey {
}
impl Decode for Ed25519PublicKey {
+ type Error = Error;
+
fn decode(reader: &mut impl Reader) -> Result {
let mut bytes = [0u8; Self::BYTE_SIZE];
reader.read_nested(|reader| reader.read(&mut bytes))?;
@@ -33,12 +33,15 @@ impl Decode for Ed25519PublicKey {
}
impl Encode for Ed25519PublicKey {
+ type Error = Error;
+
fn encoded_len(&self) -> Result {
- [4, Self::BYTE_SIZE].checked_sum()
+ Ok([4, Self::BYTE_SIZE].checked_sum()?)
}
fn encode(&self, writer: &mut impl Writer) -> Result<()> {
- self.0.encode(writer)
+ self.0.encode(writer)?;
+ Ok(())
}
}
diff --git a/ssh-key/src/public/key_data.rs b/ssh-key/src/public/key_data.rs
index 09cae902..2ecf94d9 100644
--- a/ssh-key/src/public/key_data.rs
+++ b/ssh-key/src/public/key_data.rs
@@ -1,10 +1,8 @@
//! Public key data.
use super::{Ed25519PublicKey, SkEd25519};
-use crate::{
- checked::CheckedSum, decode::Decode, encode::Encode, reader::Reader, writer::Writer, Algorithm,
- Error, Fingerprint, HashAlg, Result,
-};
+use crate::{Algorithm, Error, Fingerprint, HashAlg, Result};
+use encoding::{CheckedSum, Decode, Encode, Reader, Writer};
#[cfg(feature = "alloc")]
use super::{DsaPublicKey, RsaPublicKey};
@@ -224,6 +222,8 @@ impl KeyData {
}
impl Decode for KeyData {
+ type Error = Error;
+
fn decode(reader: &mut impl Reader) -> Result {
let algorithm = Algorithm::decode(reader)?;
Self::decode_as(reader, algorithm)
@@ -231,12 +231,14 @@ impl Decode for KeyData {
}
impl Encode for KeyData {
+ type Error = Error;
+
fn encoded_len(&self) -> Result {
- [
+ Ok([
self.algorithm().encoded_len()?,
self.encoded_key_data_len()?,
]
- .checked_sum()
+ .checked_sum()?)
}
fn encode(&self, writer: &mut impl Writer) -> Result<()> {
diff --git a/ssh-key/src/public/openssh.rs b/ssh-key/src/public/openssh.rs
index 96871f57..a55ba9c5 100644
--- a/ssh-key/src/public/openssh.rs
+++ b/ssh-key/src/public/openssh.rs
@@ -12,8 +12,9 @@
//! ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILM+rvN+ot98qgEN796jTiQfZfG1KaT0PtFDJ/XFSqti user@example.com
//! ```
-use crate::{writer::Base64Writer, Error, Result};
+use crate::Result;
use core::str;
+use encoding::{Base64Writer, Error};
/// OpenSSH public key encapsulation parser.
#[derive(Clone, Debug, Eq, PartialEq)]
@@ -37,10 +38,12 @@ impl<'a> Encapsulation<'a> {
let comment = str::from_utf8(bytes)
.map_err(|_| Error::CharacterEncoding)?
.trim_end();
+
if algorithm_id.is_empty() || base64_data.is_empty() {
// TODO(tarcieri): better errors for these cases?
- return Err(Error::Length);
+ return Err(Error::Length.into());
}
+
Ok(Self {
algorithm_id,
base64_data,
@@ -64,13 +67,16 @@ impl<'a> Encapsulation<'a> {
let mut writer = Base64Writer::new(&mut out[offset..])?;
f(&mut writer)?;
+
let base64_len = writer.finish()?.len();
offset = offset.checked_add(base64_len).ok_or(Error::Length)?;
+
if !comment.is_empty() {
encode_str(out, &mut offset, " ")?;
encode_str(out, &mut offset, comment)?;
}
+
Ok(str::from_utf8(&out[..offset])?)
}
}
@@ -91,15 +97,15 @@ fn decode_segment<'a>(bytes: &mut &'a [u8]) -> Result<&'a [u8]> {
[b' ', rest @ ..] => {
// Encountered space; we're done
*bytes = rest;
- return start.get(..len).ok_or(Error::Length);
+ return start.get(..len).ok_or_else(|| Error::Length.into());
}
[_, ..] => {
// Invalid character
- return Err(Error::CharacterEncoding);
+ return Err(Error::CharacterEncoding.into());
}
[] => {
// End of input, could be truncated or could be no comment
- return start.get(..len).ok_or(Error::Length);
+ return start.get(..len).ok_or_else(|| Error::Length.into());
}
}
}
@@ -107,7 +113,7 @@ fn decode_segment<'a>(bytes: &mut &'a [u8]) -> Result<&'a [u8]> {
/// Parse a segment of the public key as a `&str`.
fn decode_segment_str<'a>(bytes: &mut &'a [u8]) -> Result<&'a str> {
- str::from_utf8(decode_segment(bytes)?).map_err(|_| Error::CharacterEncoding)
+ str::from_utf8(decode_segment(bytes)?).map_err(|_| Error::CharacterEncoding.into())
}
/// Encode a segment of the public key.
@@ -115,11 +121,12 @@ fn encode_str(out: &mut [u8], offset: &mut usize, s: &str) -> Result<()> {
let bytes = s.as_bytes();
if out.len() < offset.checked_add(bytes.len()).ok_or(Error::Length)? {
- return Err(Error::Length);
+ return Err(Error::Length.into());
}
out[*offset..][..bytes.len()].copy_from_slice(bytes);
*offset = offset.checked_add(bytes.len()).ok_or(Error::Length)?;
+
Ok(())
}
diff --git a/ssh-key/src/public/rsa.rs b/ssh-key/src/public/rsa.rs
index e7d92068..f6e77e90 100644
--- a/ssh-key/src/public/rsa.rs
+++ b/ssh-key/src/public/rsa.rs
@@ -1,13 +1,11 @@
//! Rivest–Shamir–Adleman (RSA) public keys.
-use crate::{
- checked::CheckedSum, decode::Decode, encode::Encode, reader::Reader, writer::Writer, MPInt,
- Result,
-};
+use crate::{Error, MPInt, Result};
+use encoding::{CheckedSum, Decode, Encode, Reader, Writer};
#[cfg(feature = "rsa")]
use {
- crate::{private::RsaKeypair, Error},
+ crate::private::RsaKeypair,
rsa::{pkcs1v15, PublicKeyParts},
sha2::{digest::const_oid::AssociatedOid, Digest},
};
@@ -32,6 +30,8 @@ impl RsaPublicKey {
}
impl Decode for RsaPublicKey {
+ type Error = Error;
+
fn decode(reader: &mut impl Reader) -> Result {
let e = MPInt::decode(reader)?;
let n = MPInt::decode(reader)?;
@@ -40,8 +40,10 @@ impl Decode for RsaPublicKey {
}
impl Encode for RsaPublicKey {
+ type Error = Error;
+
fn encoded_len(&self) -> Result {
- [self.e.encoded_len()?, self.n.encoded_len()?].checked_sum()
+ Ok([self.e.encoded_len()?, self.n.encoded_len()?].checked_sum()?)
}
fn encode(&self, writer: &mut impl Writer) -> Result<()> {
diff --git a/ssh-key/src/public/sk.rs b/ssh-key/src/public/sk.rs
index 12329ae0..d34b1e36 100644
--- a/ssh-key/src/public/sk.rs
+++ b/ssh-key/src/public/sk.rs
@@ -3,18 +3,14 @@
//! [PROTOCOL.u2f]: https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.u2f?annotate=HEAD
use super::Ed25519PublicKey;
-use crate::{
- checked::CheckedSum, decode::Decode, encode::Encode, reader::Reader, writer::Writer, Result,
-};
+use crate::{Error, Result};
+use encoding::{CheckedSum, Decode, Encode, Reader, Writer};
#[cfg(feature = "alloc")]
use alloc::{borrow::ToOwned, string::String};
#[cfg(feature = "ecdsa")]
-use {
- super::ecdsa::EcdsaNistP256PublicKey,
- crate::{EcdsaCurve, Error},
-};
+use crate::{public::ecdsa::EcdsaNistP256PublicKey, EcdsaCurve};
/// Default FIDO/U2F Security Key application string.
const DEFAULT_APPLICATION_STRING: &str = "ssh:";
@@ -55,6 +51,8 @@ impl SkEcdsaSha2NistP256 {
#[cfg(feature = "ecdsa")]
impl Decode for SkEcdsaSha2NistP256 {
+ type Error = Error;
+
fn decode(reader: &mut impl Reader) -> Result {
if EcdsaCurve::decode(reader)? != EcdsaCurve::NistP256 {
return Err(Error::Crypto);
@@ -78,19 +76,22 @@ impl Decode for SkEcdsaSha2NistP256 {
#[cfg(feature = "ecdsa")]
impl Encode for SkEcdsaSha2NistP256 {
+ type Error = Error;
+
fn encoded_len(&self) -> Result {
- [
+ Ok([
EcdsaCurve::NistP256.encoded_len()?,
self.ec_point.as_bytes().encoded_len()?,
self.application().encoded_len()?,
]
- .checked_sum()
+ .checked_sum()?)
}
fn encode(&self, writer: &mut impl Writer) -> Result<()> {
EcdsaCurve::NistP256.encode(writer)?;
self.ec_point.as_bytes().encode(writer)?;
- self.application().encode(writer)
+ self.application().encode(writer)?;
+ Ok(())
}
}
@@ -144,6 +145,8 @@ impl SkEd25519 {
}
impl Decode for SkEd25519 {
+ type Error = Error;
+
fn decode(reader: &mut impl Reader) -> Result {
let public_key = Ed25519PublicKey::decode(reader)?;
@@ -161,17 +164,20 @@ impl Decode for SkEd25519 {
}
impl Encode for SkEd25519 {
+ type Error = Error;
+
fn encoded_len(&self) -> Result {
- [
+ Ok([
self.public_key.encoded_len()?,
self.application().encoded_len()?,
]
- .checked_sum()
+ .checked_sum()?)
}
fn encode(&self, writer: &mut impl Writer) -> Result<()> {
self.public_key.encode(writer)?;
- self.application().encode(writer)
+ self.application().encode(writer)?;
+ Ok(())
}
}
diff --git a/ssh-key/src/signature.rs b/ssh-key/src/signature.rs
index adec815f..e70457f0 100644
--- a/ssh-key/src/signature.rs
+++ b/ssh-key/src/signature.rs
@@ -1,11 +1,9 @@
//! Signatures (e.g. CA signatures over SSH certificates)
-use crate::{
- checked::CheckedSum, decode::Decode, encode::Encode, private, public, reader::Reader,
- writer::Writer, Algorithm, Error, MPInt, PrivateKey, PublicKey, Result,
-};
+use crate::{private, public, Algorithm, Error, MPInt, PrivateKey, PublicKey, Result};
use alloc::vec::Vec;
use core::fmt;
+use encoding::{CheckedSum, Decode, Encode, Reader, Writer};
use signature::{Signer, Verifier};
#[cfg(feature = "ed25519")]
@@ -92,7 +90,7 @@ impl Signature {
/// format the raw signature data for a given algorithm.
///
/// # Returns
- /// - [`Error::Length`] if the signature is not the correct length.
+ /// - [`Error::Encoding`] if the signature is not the correct length.
pub fn new(algorithm: Algorithm, data: impl Into>) -> Result {
let data = data.into();
@@ -108,17 +106,17 @@ impl Signature {
if component.as_positive_bytes().ok_or(Error::Crypto)?.len()
!= curve.field_size()
{
- return Err(Error::Length);
+ return Err(encoding::Error::Length.into());
}
}
if !reader.is_finished() {
- return Err(Error::Length);
+ return Err(encoding::Error::Length.into());
}
}
Algorithm::Ed25519 if data.len() == ED25519_SIGNATURE_SIZE => (),
Algorithm::Rsa { hash: Some(_) } => (),
- _ => return Err(Error::Length),
+ _ => return Err(encoding::Error::Length.into()),
}
Ok(Self { algorithm, data })
@@ -157,6 +155,8 @@ impl AsRef<[u8]> for Signature {
}
impl Decode for Signature {
+ type Error = Error;
+
fn decode(reader: &mut impl Reader) -> Result {
let algorithm = Algorithm::decode(reader)?;
let data = Vec::decode(reader)?;
@@ -165,22 +165,25 @@ impl Decode for Signature {
}
impl Encode for Signature {
+ type Error = Error;
+
fn encoded_len(&self) -> Result {
- [
+ Ok([
self.algorithm().encoded_len()?,
4, // signature data length prefix (uint32)
self.as_bytes().len(),
]
- .checked_sum()
+ .checked_sum()?)
}
fn encode(&self, writer: &mut impl Writer) -> Result<()> {
if self.is_placeholder() {
- return Err(Error::Length);
+ return Err(encoding::Error::Length.into());
}
self.algorithm().encode(writer)?;
- self.as_bytes().encode(writer)
+ self.as_bytes().encode(writer)?;
+ Ok(())
}
}
@@ -602,8 +605,9 @@ impl Verifier for RsaPublicKey {
#[cfg(test)]
mod tests {
use super::Signature;
- use crate::{encode::Encode, Algorithm, EcdsaCurve, Error, HashAlg};
+ use crate::{Algorithm, EcdsaCurve, HashAlg};
use alloc::vec::Vec;
+ use encoding::Encode;
use hex_literal::hex;
#[cfg(feature = "ed25519")]
@@ -700,6 +704,9 @@ mod tests {
assert!(placeholder.is_placeholder());
let mut writer = Vec::new();
- assert_eq!(placeholder.encode(&mut writer), Err(Error::Length));
+ assert_eq!(
+ placeholder.encode(&mut writer),
+ Err(encoding::Error::Length.into())
+ );
}
}
diff --git a/ssh-key/src/sshsig.rs b/ssh-key/src/sshsig.rs
index 9024379e..d9c377b3 100644
--- a/ssh-key/src/sshsig.rs
+++ b/ssh-key/src/sshsig.rs
@@ -1,18 +1,12 @@
//! `sshsig` implementation.
-use crate::{
- checked::CheckedSum,
- decode::Decode,
- encode::Encode,
- pem::{self, PemLabel},
- public,
- reader::Reader,
- writer::Writer,
- Algorithm, Error, HashAlg, Result, Signature, SigningKey, PEM_LINE_WIDTH,
-};
+use crate::{public, Algorithm, Error, HashAlg, Result, Signature, SigningKey};
use alloc::{borrow::ToOwned, string::String, string::ToString, vec::Vec};
-use base64ct::LineEnding;
use core::str::FromStr;
+use encoding::{
+ pem::{self, LineEnding, PemLabel},
+ CheckedSum, Decode, Encode, Reader, Writer, PEM_LINE_WIDTH,
+};
use signature::{Signer, Verifier};
type Version = u32;
@@ -59,7 +53,7 @@ impl SshSig {
let mut reader = pem::Decoder::new_wrapped(pem.as_ref(), PEM_LINE_WIDTH)?;
Self::validate_pem_label(reader.type_label())?;
let signature = Self::decode(&mut reader)?;
- reader.finish(signature)
+ Ok(reader.finish(signature)?)
}
/// Encode signature as PEM which begins with the following:
@@ -198,6 +192,8 @@ impl SshSig {
}
impl Decode for SshSig {
+ type Error = Error;
+
fn decode(reader: &mut impl Reader) -> Result {
let mut magic_preamble = [0u8; Self::MAGIC_PREAMBLE.len()];
reader.read(&mut magic_preamble)?;
@@ -235,8 +231,10 @@ impl Decode for SshSig {
}
impl Encode for SshSig {
+ type Error = Error;
+
fn encoded_len(&self) -> Result {
- [
+ Ok([
Self::MAGIC_PREAMBLE.len(),
self.version.encoded_len()?,
4, // public key length prefix (uint32)
@@ -247,7 +245,7 @@ impl Encode for SshSig {
4, // signature length prefix (uint32)
self.signature.encoded_len()?,
]
- .checked_sum()
+ .checked_sum()?)
}
fn encode(&self, writer: &mut impl Writer) -> Result<()> {
@@ -306,15 +304,17 @@ impl<'a> SignedData<'a> {
}
impl<'a> Encode for SignedData<'a> {
+ type Error = Error;
+
fn encoded_len(&self) -> Result {
- [
+ Ok([
SshSig::MAGIC_PREAMBLE.len(),
self.namespace.encoded_len()?,
self.reserved.encoded_len()?,
self.hash_alg.encoded_len()?,
self.hash.encoded_len()?,
]
- .checked_sum()
+ .checked_sum()?)
}
fn encode(&self, writer: &mut impl Writer) -> Result<()> {