From db0299b3bb201eb058ca93d73d8ae37d0ad8e9e1 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Mon, 9 Nov 2026 22:08:45 -0700 Subject: [PATCH] ssh-encoding: extract a shared `Decoder` [BREAKING] Move nested decoding, length bounds, completion checks, and failure state into a shared `Decoder` type which owns the underlying reader. This removes considerable complexity from the reader types, including their individual `Reader::read_prefixed` implementations and the supporting macro, leaving them responsible only for supplying bytes and reporting the remaining length. Nested decoding now uses a common `read_nested` implementation which temporarily narrows the decoder's bounds. Each nesting level uses the same `Decoder` type, preserving `no_alloc` support without the recursively nested generic reader types that previously caused monomorphization bloat. This is a breaking change: `Decode` implementations now accept `&mut Decoder`, which is slightly uglier from an API perspective but enables a much better underlying implementation. A blanket `From` implementation keeps call sites concise by allowing `Reader`s to be converted into a `Decoder` with `into()`.. We are also motivated by #615 to make another breaking `ssh-encoding` release before the next ssh-key release: making the remaining infallible `Mpint` conversions fallible would allow its size limits to be enforced consistently. Because this is a breaking change, the versions of `ssh-cipher`, `ssh-derive`, and `ssh-encoding` have all been bumped to v0.4.0-pre. --- Cargo.lock | 6 +- ssh-cipher/Cargo.toml | 4 +- ssh-derive/Cargo.toml | 2 +- ssh-derive/src/decode.rs | 8 +- ssh-encoding/Cargo.toml | 4 +- ssh-encoding/README.md | 25 +++ ssh-encoding/src/base64/reader.rs | 25 ++- ssh-encoding/src/decode.rs | 26 +-- ssh-encoding/src/decoder.rs | 294 +++++++++++++++++++++++++ ssh-encoding/src/error.rs | 4 + ssh-encoding/src/label.rs | 4 +- ssh-encoding/src/lib.rs | 5 +- ssh-encoding/src/mpint.rs | 37 ++-- ssh-encoding/src/pem/decode.rs | 9 +- ssh-encoding/src/pem/reader.rs | 21 +- ssh-encoding/src/reader.rs | 212 ++---------------- ssh-encoding/tests/decode.rs | 28 +-- ssh-encoding/tests/decoder.rs | 254 +++++++++++++++++++++ ssh-encoding/tests/derive.rs | 14 +- ssh-key/Cargo.toml | 4 +- ssh-key/src/certificate.rs | 14 +- ssh-key/src/certificate/cert_type.rs | 4 +- ssh-key/src/certificate/options_map.rs | 4 +- ssh-key/src/certificate/unix_time.rs | 4 +- ssh-key/src/comment.rs | 4 +- ssh-key/src/kdf.rs | 4 +- ssh-key/src/ppk.rs | 6 +- ssh-key/src/private.rs | 14 +- ssh-key/src/private/dsa.rs | 6 +- ssh-key/src/private/ecdsa.rs | 6 +- ssh-key/src/private/ed25519.rs | 4 +- ssh-key/src/private/keypair.rs | 6 +- ssh-key/src/private/opaque.rs | 11 +- ssh-key/src/private/rsa.rs | 6 +- ssh-key/src/private/sk.rs | 6 +- ssh-key/src/public.rs | 14 +- ssh-key/src/public/dsa.rs | 4 +- ssh-key/src/public/ecdsa.rs | 4 +- ssh-key/src/public/ed25519.rs | 4 +- ssh-key/src/public/key_data.rs | 11 +- ssh-key/src/public/opaque.rs | 9 +- ssh-key/src/public/rsa.rs | 4 +- ssh-key/src/public/sk.rs | 6 +- ssh-key/src/signature.rs | 27 +-- ssh-key/src/sshsig.rs | 6 +- ssh-key/tests/certificate.rs | 4 +- ssh-key/tests/sshsig.rs | 13 +- ssh-protocol/Cargo.toml | 4 +- ssh-protocol/src/lib.rs | 2 +- 49 files changed, 814 insertions(+), 383 deletions(-) create mode 100644 ssh-encoding/src/decoder.rs create mode 100644 ssh-encoding/tests/decoder.rs diff --git a/Cargo.lock b/Cargo.lock index d6141ad6..55fd845b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -755,7 +755,7 @@ dependencies = [ [[package]] name = "ssh-cipher" -version = "0.3.0" +version = "0.4.0-pre" dependencies = [ "aead", "aes", @@ -772,7 +772,7 @@ dependencies = [ [[package]] name = "ssh-derive" -version = "0.3.0" +version = "0.4.0-pre" dependencies = [ "proc-macro2", "quote", @@ -781,7 +781,7 @@ dependencies = [ [[package]] name = "ssh-encoding" -version = "0.3.0" +version = "0.4.0-pre" dependencies = [ "base64ct", "bytes", diff --git a/ssh-cipher/Cargo.toml b/ssh-cipher/Cargo.toml index 011f087f..8e22cd1d 100644 --- a/ssh-cipher/Cargo.toml +++ b/ssh-cipher/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ssh-cipher" -version = "0.3.0" +version = "0.4.0-pre" description = """ Pure Rust implementation of SSH symmetric encryption including support for the modern aes128-gcm@openssh.com/aes256-gcm@openssh.com and chacha20-poly1305@openssh.com algorithms as well @@ -27,7 +27,7 @@ aes-gcm = { version = "0.11", optional = true, default-features = false, feature ctutils = { version = "0.4.8", optional = true, default-features = false } chacha20 = { version = "0.10", optional = true, default-features = false, features = ["cipher", "legacy"] } des = { version = "0.9", optional = true, default-features = false } -encoding = { package = "ssh-encoding", version = "0.3", optional = true } +encoding = { package = "ssh-encoding", version = "0.4.0-pre", optional = true } poly1305 = { version = "0.9", optional = true, default-features = false } zeroize = { version = "1", optional = true, default-features = false } diff --git a/ssh-derive/Cargo.toml b/ssh-derive/Cargo.toml index 215573a2..cb6f0158 100644 --- a/ssh-derive/Cargo.toml +++ b/ssh-derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ssh-derive" -version = "0.3.0" +version = "0.4.0-pre" description = "Custom derive support for the `ssh-encoding` crate" authors = ["RustCrypto Developers"] license = "Apache-2.0 OR MIT" diff --git a/ssh-derive/src/decode.rs b/ssh-derive/src/decode.rs index e73b8e98..3745d35f 100644 --- a/ssh-derive/src/decode.rs +++ b/ssh-derive/src/decode.rs @@ -29,7 +29,7 @@ fn try_derive_decode_for_struct( impl #impl_generics ::ssh_encoding::Decode for #struct_name #type_generics #where_clause { type Error = ::ssh_encoding::Error; - fn decode(reader: &mut impl ::ssh_encoding::Reader) -> ::core::result::Result { + fn decode(reader: &mut ::ssh_encoding::Decoder) -> ::core::result::Result { #body } } @@ -85,7 +85,7 @@ fn try_derive_decode_for_enum( impl #impl_generics ::ssh_encoding::Decode for #enum_name #type_generics #where_clause { type Error = ::ssh_encoding::Error; - fn decode(reader: &mut impl ::ssh_encoding::Reader) -> ::core::result::Result { + fn decode(reader: &mut ::ssh_encoding::Decoder) -> ::core::result::Result { #body } } @@ -245,7 +245,7 @@ mod tests { impl ::ssh_encoding::Decode for Foo { type Error = ::ssh_encoding::Error; - fn decode(reader: &mut impl ::ssh_encoding::Reader) -> ::core::result::Result { + fn decode(reader: &mut ::ssh_encoding::Decoder) -> ::core::result::Result { Ok({ Self { a: reader.read_prefixed(|reader| ::decode(reader))?, @@ -274,7 +274,7 @@ mod tests { impl ::ssh_encoding::Decode for Foo { type Error = ::ssh_encoding::Error; - fn decode(reader: &mut impl ::ssh_encoding::Reader) -> ::core::result::Result { + fn decode(reader: &mut ::ssh_encoding::Decoder) -> ::core::result::Result { reader.read_prefixed(|reader| { Ok::<_, ::ssh_encoding::Error>({ let discriminant = ::decode(reader)?; diff --git a/ssh-encoding/Cargo.toml b/ssh-encoding/Cargo.toml index ceaeae77..0a36b2a9 100644 --- a/ssh-encoding/Cargo.toml +++ b/ssh-encoding/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ssh-encoding" -version = "0.3.0" +version = "0.4.0-pre" description = "Pure Rust implementation of SSH data type decoders/encoders as described in RFC4251" authors = ["RustCrypto Developers"] license = "Apache-2.0 OR MIT" @@ -19,7 +19,7 @@ bytes = { version = "1.11", optional = true, default-features = false } ctutils = { version = "0.4.8", optional = true, default-features = false } digest = { version = "0.11", optional = true, default-features = false } pem-rfc7468 = { version = "1", optional = true } -ssh-derive = { version = "0.3", optional = true } +ssh-derive = { version = "0.4.0-pre", optional = true } zeroize = { version = "1.8", optional = true, default-features = false } [dev-dependencies] diff --git a/ssh-encoding/README.md b/ssh-encoding/README.md index aa202867..b58fb80e 100644 --- a/ssh-encoding/README.md +++ b/ssh-encoding/README.md @@ -14,6 +14,31 @@ Pure Rust implementation of SSH data type decoders/encoders as described in [RFC4251]. +## Decoding + +Convert a reader into a `Decoder` to start decoding SSH data types: + +```rust +use ssh_encoding::{Decode, Reader}; + +let mut decoder = [0, 0, 0, 4, 0, 0, 0, 42].as_slice().into_decoder(); +let value = decoder.read_prefixed(u32::decode).expect("invalid u32"); +assert_eq!(decoder.finish(value).expect("remaining data"), 42); +``` + +`Reader` supplies bytes and their remaining length. `Decoder` owns the reader and +handles nested bounds, complete consumption, and failure state. Nested callbacks use +the same decoder type and store enclosing bounds on the call stack without allocation. +`Decode` implementations accept `&mut Decoder`. + +The blanket `From` implementation supports `.into()` wherever a decoder is +expected, for example `Message::decode(&mut bytes.into())`. For any `R: Reader`, +`Reader::into_decoder` also constructs a decoder without naming its type. + +Raw byte slices implement `Reader`. With the corresponding features enabled, +`Base64Reader` decodes Base64 incrementally, and `DecodePem` wraps a PEM reader in +the same decoder internally. + ## License Licensed under either of: diff --git a/ssh-encoding/src/base64/reader.rs b/ssh-encoding/src/base64/reader.rs index ef58fba5..e30d8ebe 100644 --- a/ssh-encoding/src/base64/reader.rs +++ b/ssh-encoding/src/base64/reader.rs @@ -1,6 +1,6 @@ //! Base64 reader support (constant-time). -use crate::{Decode, Error, Reader, Result}; +use crate::{Reader, Result}; use core::fmt::{self, Debug}; /// Inner constant-time Base64 reader type from the `base64ct` crate. @@ -10,9 +10,6 @@ type Inner<'i> = base64ct::Decoder<'i, base64ct::Base64>; pub struct Base64Reader<'i> { /// Inner Base64 reader. inner: Inner<'i>, - - /// Custom length of remaining data, used for nested length-prefixed reading. - remaining_len: usize, } impl<'i> Base64Reader<'i> { @@ -20,24 +17,28 @@ impl<'i> Base64Reader<'i> { /// Base64-encoded data. /// /// # Errors - /// Returns [`Error::Base64`] if the `input` buffer is empty. + /// Returns [`crate::Error::Base64`] if the `input` buffer is empty. pub fn new(input: &'i [u8]) -> Result { let inner = Inner::new(input)?; - let remaining_len = inner.remaining_len(); - Ok(Self { - inner, - remaining_len, - }) + Ok(Self { inner }) } } impl Debug for Base64Reader<'_> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("Base64Reader") - .field("remaining_len", &self.remaining_len) + .field("remaining_len", &self.remaining_len()) .finish_non_exhaustive() } } -impl_reader_for_newtype!(Base64Reader<'_>); +impl Reader for Base64Reader<'_> { + fn read<'o>(&mut self, out: &'o mut [u8]) -> Result<&'o [u8]> { + Ok(self.inner.decode(out)?) + } + + fn remaining_len(&self) -> usize { + self.inner.remaining_len() + } +} diff --git a/ssh-encoding/src/decode.rs b/ssh-encoding/src/decode.rs index 85e14a2c..b74b8a56 100644 --- a/ssh-encoding/src/decode.rs +++ b/ssh-encoding/src/decode.rs @@ -3,7 +3,7 @@ //! //! [RFC4251 § 5]: https://datatracker.ietf.org/doc/html/rfc4251#section-5 -use crate::{Error, Result, reader::Reader}; +use crate::{Decoder, Error, Reader, Result}; #[cfg(feature = "alloc")] use alloc::{string::String, vec::Vec}; @@ -21,18 +21,18 @@ 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`]. + /// Attempt to decode a value of this type using the provided [`Decoder`]. /// /// # Errors /// Returns errors specific to the concrete implementation of this trait. - fn decode(reader: &mut impl Reader) -> core::result::Result; + fn decode(reader: &mut Decoder) -> 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 { + fn decode(reader: &mut Decoder) -> Result { let mut buf = [0]; reader.read(&mut buf)?; Ok(buf[0]) @@ -50,7 +50,7 @@ impl Decode for u8 { impl Decode for bool { type Error = Error; - fn decode(reader: &mut impl Reader) -> Result { + fn decode(reader: &mut Decoder) -> Result { let byte = u8::decode(reader)?; match byte { 0 => Ok(false), @@ -69,7 +69,7 @@ impl Decode for bool { impl Decode for u32 { type Error = Error; - fn decode(reader: &mut impl Reader) -> Result { + fn decode(reader: &mut Decoder) -> Result { let mut bytes = [0u8; 4]; reader.read(&mut bytes)?; Ok(u32::from_be_bytes(bytes)) @@ -85,7 +85,7 @@ impl Decode for u32 { impl Decode for u64 { type Error = Error; - fn decode(reader: &mut impl Reader) -> Result { + fn decode(reader: &mut Decoder) -> Result { let mut bytes = [0u8; 8]; reader.read(&mut bytes)?; Ok(u64::from_be_bytes(bytes)) @@ -102,7 +102,7 @@ impl Decode for u64 { impl Decode for usize { type Error = Error; - fn decode(reader: &mut impl Reader) -> Result { + fn decode(reader: &mut Decoder) -> Result { let n = usize::try_from(u32::decode(reader)?)?; if n <= MAX_USIZE { @@ -126,7 +126,7 @@ impl Decode for usize { impl Decode for [u8; N] { type Error = Error; - fn decode(reader: &mut impl Reader) -> Result { + fn decode(reader: &mut Decoder) -> Result { let mut result = [0u8; N]; reader.read(&mut result)?; Ok(result) @@ -147,7 +147,7 @@ impl Decode for [u8; N] { impl Decode for Vec { type Error = Error; - fn decode(reader: &mut impl Reader) -> Result { + fn decode(reader: &mut Decoder) -> Result { reader.read_prefixed(|reader| { let mut result = vec![0u8; reader.remaining_len()]; reader.read(&mut result)?; @@ -160,7 +160,7 @@ impl Decode for Vec { impl Decode for String { type Error = Error; - fn decode(reader: &mut impl Reader) -> Result { + fn decode(reader: &mut Decoder) -> Result { String::from_utf8(Vec::decode(reader)?).map_err(|_| Error::CharacterEncoding) } } @@ -187,7 +187,7 @@ impl Decode for String { impl Decode for Vec { type Error = Error; - fn decode(reader: &mut impl Reader) -> Result { + fn decode(reader: &mut Decoder) -> Result { reader.read_prefixed(|reader| { let mut entries = Self::new(); @@ -214,7 +214,7 @@ impl Decode for Vec { impl Decode for Bytes { type Error = Error; - fn decode(reader: &mut impl Reader) -> Result { + fn decode(reader: &mut Decoder) -> Result { Vec::::decode(reader).map(Into::into) } } diff --git a/ssh-encoding/src/decoder.rs b/ssh-encoding/src/decoder.rs new file mode 100644 index 00000000..031d2429 --- /dev/null +++ b/ssh-encoding/src/decoder.rs @@ -0,0 +1,294 @@ +//! Shared decoder for SSH protocol data types. + +use crate::{Decode, Error, Reader, Result}; +use core::{fmt, str}; + +/// Decoder for SSH protocol data types over a byte [`Reader`]. +/// +/// Construct a decoder with [`Reader::into_decoder`] or the [`From`] conversion. +/// +/// All nested decoding uses the same decoder type, with enclosing bounds stored on the call +/// stack. Neither nesting nor decoding from Base64 or PEM requires a heap allocation. +/// +/// A failed read or nested decoding operation invalidates the decoder. Subsequent reads and +/// completion checks return [`Error::Failed`]. The underlying reader is not rewound on errors. +/// +/// # Example +/// ``` +/// use ssh_encoding::{Decode, Decoder}; +/// +/// let mut decoder: Decoder<_> = [0, 0, 0, 42].as_slice().into(); +/// let value = u32::decode(&mut decoder)?; +/// assert_eq!(decoder.finish(value)?, 42); +/// # Ok::<(), ssh_encoding::Error>(()) +/// ``` +pub struct Decoder { + /// Underlying byte reader. + reader: R, + + /// Position in the decoded input. + position: usize, + + /// End of the currently active decoding scope. + end: usize, + + /// Whether a read or nested decoding operation failed. + failed: bool, +} + +impl Decoder { + /// Create a decoder which owns the given reader. + #[must_use] + pub fn new(reader: R) -> Self { + let end = reader.remaining_len(); + Self { + reader, + position: 0, + end, + failed: false, + } + } + + /// Read enough bytes to exactly fill `out`, without crossing the active scope's boundary. + /// + /// # Errors + /// - Returns [`Error::Failed`] if the decoder has already failed. + /// - Returns [`Error::Length`] if `out` exceeds the remaining length. + /// - Propagates errors returned from [`Reader::read`]. + pub fn read<'o>(&mut self, out: &'o mut [u8]) -> Result<&'o [u8]> { + if self.failed { + return Err(Error::Failed); + } + + if out.len() > self.remaining_len() { + self.failed = true; + return Err(Error::Length); + } + + // Avoid calling streaming decoders for empty reads. + if out.is_empty() { + return Ok(out); + } + + let len = out.len(); + match self.reader.read(out) { + Ok(bytes) => { + self.position += len; + Ok(bytes) + } + Err(err) => { + self.failed = true; + Err(err) + } + } + } + + /// Get the number of decoded bytes remaining in the active scope. + #[must_use] + pub fn remaining_len(&self) -> usize { + self.end - self.position + } + + /// Have all bytes in the active scope been consumed? + #[must_use] + pub fn is_finished(&self) -> bool { + self.remaining_len() == 0 + } + + /// Decode exactly `len` bytes within a nested scope. + /// + /// The callback receives the same decoder type with its bounds temporarily narrowed. + /// It must consume the entire scope before returning successfully. Enclosing bounds are + /// restored on success, error, or unwinding, without rewinding the underlying reader. + /// + /// # Errors + /// - Returns [`Error::Failed`] if the decoder has already failed. + /// - Returns [`Error::Length`] if `len` exceeds the remaining length. + /// - Returns [`Error::TrailingData`] if the callback leaves unread bytes. + /// - Propagates errors returned from the callback. + pub fn read_nested(&mut self, len: usize, f: F) -> core::result::Result + where + E: From, + F: FnOnce(&mut Self) -> core::result::Result, + { + let mut scope = self.enter_nested(len)?; + let value = f(scope.decoder)?; + scope.decoder.ensure_finished()?; + scope.completed = true; + Ok(value) + } + + /// Decode a `uint32` length prefix, then decode its body in a nested scope. + /// + /// # Errors + /// Propagates errors decoding the prefix or returned by [`Decoder::read_nested`]. + pub fn read_prefixed(&mut self, f: F) -> core::result::Result + where + E: From, + F: FnOnce(&mut Self) -> core::result::Result, + { + let len = usize::decode(self).inspect_err(|_| self.failed = true)?; + self.read_nested(len, f) + } + + /// Decodes `[u8]` from `byte[n]` as described in [RFC4251 § 5]: + /// + /// > 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. + /// + /// Storage for the byte array must be provided as mutable byte slice in order to accommodate + /// `no_alloc` use cases. + /// + /// 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 + /// + /// # Errors + /// - Returns [`Error::Length`] if `out` is too small to accommodate the full `byte[n]`. + /// - Propagates errors returned from [`Decoder::read`]. + pub fn read_byten<'o>(&mut self, out: &'o mut [u8]) -> Result<&'o [u8]> { + self.read_prefixed(|reader| { + let slice = out.get_mut(..reader.remaining_len()).ok_or(Error::Length)?; + reader.read(slice)?; + Ok(&*slice) + }) + } + + /// Decode a `string` as described in [RFC4251 § 5]: + /// + /// > Arbitrary length binary string. Strings are allowed to contain + /// > arbitrary binary data, including null characters and 8-bit + /// > characters. They are stored as a uint32 containing its length + /// > (number of bytes that follow) and zero (= empty string) or more + /// > bytes that are the value of the string. Terminating null + /// > characters are not used. + /// > + /// > Strings are also used to store text. In that case, US-ASCII is + /// > used for internal names, and ISO-10646 UTF-8 for text that might + /// > be displayed to the user. The terminating null character SHOULD + /// > NOT normally be stored in the string. For example: the US-ASCII + /// > string "testing" is represented as 00 00 00 07 t e s t i n g. The + /// > UTF-8 mapping does not alter the encoding of US-ASCII characters. + /// + /// Storage for the string data must be provided as mutable byte slice in order to accommodate + /// `no_alloc` use cases. + /// + /// 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 + /// + /// # Errors + /// - Propagates errors returned from [`Decoder::read_byten`]. + /// - Returns [`Error::CharacterEncoding`] if the data is not valid UTF-8. + pub fn read_string<'o>(&mut self, buf: &'o mut [u8]) -> Result<&'o str> { + Ok(str::from_utf8(self.read_byten(buf)?)?) + } + + /// Drain the given number of bytes from the reader, discarding them. + /// + /// # Errors + /// Propagates errors returned from [`Decoder::read`]. + pub fn drain(&mut self, n_bytes: usize) -> Result<()> { + let mut byte = [0]; + for _ in 0..n_bytes { + self.read(&mut byte)?; + } + Ok(()) + } + + /// Decode a `u32` length prefix, and then drain the length of the body. + /// + /// Upon success, returns the number of bytes drained sans the length of the `u32` length prefix + /// (4-bytes). + /// + /// # Errors + /// - Propagates errors returned from [`Decoder::read_prefixed`]. + /// - Propagates errors returned from [`Decoder::drain`]. + pub fn drain_prefixed(&mut self) -> Result { + self.read_prefixed(|reader| { + let len = reader.remaining_len(); + reader.drain(len)?; + Ok(len) + }) + } + + /// Ensure that decoding is finished. + /// + /// # Errors + /// - Returns [`Error::Failed`] if the decoder has already failed. + /// - Returns [`Error::TrailingData`] if there is data remaining in the decoder. + pub fn ensure_finished(&self) -> Result<()> { + if self.failed { + Err(Error::Failed) + } else if self.is_finished() { + Ok(()) + } else { + Err(Error::TrailingData { + remaining: self.remaining_len(), + }) + } + } + + /// Finish decoding, returning the given value if there is no remaining data. + /// + /// # Errors + /// - Returns [`Error::Failed`] if the decoder has already failed. + /// - Returns [`Error::TrailingData`] if there is data remaining in the decoder. + pub fn finish(self, value: T) -> Result { + self.ensure_finished()?; + Ok(value) + } + + /// Enter a nested scope, keeping setup independent of the callback type. + fn enter_nested(&mut self, len: usize) -> Result> { + if self.failed { + return Err(Error::Failed); + } + + if len > self.remaining_len() { + self.failed = true; + return Err(Error::Length); + } + + let outer_end = self.end; + self.end = self.position + len; + Ok(NestedScope { + decoder: self, + outer_end, + completed: false, + }) + } +} + +impl From for Decoder { + fn from(reader: R) -> Self { + Self::new(reader) + } +} + +impl fmt::Debug for Decoder { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Decoder") + .field("position", &self.position) + .field("remaining_len", &self.remaining_len()) + .field("failed", &self.failed) + .finish_non_exhaustive() + } +} + +/// Restores enclosing bounds on every exit path without introducing another reader type. +struct NestedScope<'a, R> { + decoder: &'a mut Decoder, + outer_end: usize, + completed: bool, +} + +impl Drop for NestedScope<'_, R> { + fn drop(&mut self) { + self.decoder.end = self.outer_end; + if !self.completed { + self.decoder.failed = true; + } + } +} diff --git a/ssh-encoding/src/error.rs b/ssh-encoding/src/error.rs index 6e3c110f..940c10f0 100644 --- a/ssh-encoding/src/error.rs +++ b/ssh-encoding/src/error.rs @@ -17,6 +17,9 @@ pub enum Error { /// Character encoding-related errors. CharacterEncoding, + /// A previous read or nested decoding operation failed. + Failed, + /// Invalid label. Label(LabelError), @@ -62,6 +65,7 @@ impl fmt::Display for Error { #[cfg(feature = "base64")] Error::Base64(err) => write!(f, "Base64 encoding error: {err}"), Error::CharacterEncoding => write!(f, "character encoding invalid"), + Error::Failed => write!(f, "decoder previously failed"), Error::Label(err) => write!(f, "{}", err), Error::Length => write!(f, "length invalid"), #[cfg(feature = "alloc")] diff --git a/ssh-encoding/src/label.rs b/ssh-encoding/src/label.rs index 3b2a2c77..c6a2da3b 100644 --- a/ssh-encoding/src/label.rs +++ b/ssh-encoding/src/label.rs @@ -1,6 +1,6 @@ //! Convenience trait for decoding/encoding string labels. -use crate::{Decode, Encode, Error, Reader, Writer}; +use crate::{Decode, Decoder, Encode, Error, Reader, Writer}; use core::{fmt, str::FromStr}; #[cfg(feature = "alloc")] @@ -17,7 +17,7 @@ pub trait Label: AsRef + FromStr {} impl Decode for T { type Error = Error; - fn decode(reader: &mut impl Reader) -> Result { + fn decode(reader: &mut Decoder) -> Result { let mut buf = [0u8; MAX_LABEL_SIZE]; Ok(reader.read_string(buf.as_mut())?.parse()?) } diff --git a/ssh-encoding/src/lib.rs b/ssh-encoding/src/lib.rs index dd8903e3..e85eeb2b 100644 --- a/ssh-encoding/src/lib.rs +++ b/ssh-encoding/src/lib.rs @@ -187,7 +187,7 @@ //! //! let encoded = message.encode_vec().unwrap(); //! assert_eq!(&encoded[..13], &[31, 0, 0, 0, 42, 0, 0, 0, 19, 101, 99, 100, 115]); -//! let decoded = Message::decode(&mut &encoded[..]).unwrap(); +//! let decoded = Message::decode(&mut encoded.as_slice().into()).unwrap(); //! assert_eq!(message, decoded); //! ``` @@ -197,12 +197,12 @@ extern crate alloc; mod checked; mod decode; +mod decoder; mod encode; mod error; mod label; #[cfg(feature = "alloc")] mod mpint; -#[macro_use] mod reader; mod writer; @@ -214,6 +214,7 @@ pub mod pem; pub use crate::{ checked::CheckedSum, decode::Decode, + decoder::Decoder, encode::Encode, error::{Error, Result}, label::{Label, LabelError}, diff --git a/ssh-encoding/src/mpint.rs b/ssh-encoding/src/mpint.rs index 82cb9429..8e5fe63c 100644 --- a/ssh-encoding/src/mpint.rs +++ b/ssh-encoding/src/mpint.rs @@ -1,6 +1,6 @@ //! Multiple precision integer. -use crate::{CheckedSum, Decode, Encode, Error, Reader, Result, Writer}; +use crate::{CheckedSum, Decode, Decoder, Encode, Error, Reader, Result, Writer}; use alloc::{boxed::Box, vec::Vec}; use core::fmt; @@ -172,7 +172,7 @@ impl PartialEq for Mpint { impl Decode for Mpint { type Error = Error; - fn decode(reader: &mut impl Reader) -> Result { + fn decode(reader: &mut Decoder) -> Result { reader.read_prefixed(|reader| { let len = reader.remaining_len(); if len > Self::MAX_SIZE + 1 { @@ -320,7 +320,7 @@ mod tests { let owned = Mpint::try_from(bytes.clone().into_boxed_slice()); let mut encoded = Vec::new(); bytes.encode(&mut encoded).unwrap(); - let decoded = Mpint::decode(&mut encoded.as_slice()); + let decoded = Mpint::decode(&mut encoded.as_slice().into()); for result in [borrowed, owned, decoded] { if valid { @@ -335,27 +335,32 @@ mod tests { #[test] fn reject_oversized_before_reading_body() { // A reader whose body cannot be read demonstrates rejection at the length check. - struct Unreadable; + struct Unreadable { + prefix: Option<[u8; 4]>, + } impl Reader for Unreadable { - fn read<'o>(&mut self, _: &'o mut [u8]) -> crate::Result<&'o [u8]> { - panic!("oversized mpint body must not be read"); + fn read<'o>(&mut self, out: &'o mut [u8]) -> crate::Result<&'o [u8]> { + let prefix = self + .prefix + .take() + .expect("oversized mpint body must not be read"); + out.copy_from_slice(&prefix); + Ok(out) } fn remaining_len(&self) -> usize { - Mpint::MAX_SIZE + 2 - } - - fn read_prefixed(&mut self, f: F) -> Result - where - E: From, - F: FnOnce(&mut Self) -> Result, - { - f(self) + Mpint::MAX_SIZE + 2 + if self.prefix.is_some() { 4 } else { 0 } } } - assert!(matches!(Mpint::decode(&mut Unreadable), Err(Error::Length))); + let reader = Unreadable { + prefix: Some(u32::try_from(Mpint::MAX_SIZE + 2).unwrap().to_be_bytes()), + }; + assert!(matches!( + Mpint::decode(&mut reader.into()), + Err(Error::Length) + )); } #[cfg(feature = "bigint")] diff --git a/ssh-encoding/src/pem/decode.rs b/ssh-encoding/src/pem/decode.rs index 3c4a866b..e8a5d3fa 100644 --- a/ssh-encoding/src/pem/decode.rs +++ b/ssh-encoding/src/pem/decode.rs @@ -1,5 +1,5 @@ use super::{PemLabel, reader::PemReader}; -use crate::{Decode, Error, Reader}; +use crate::{Decode, Decoder, Error}; /// Decoding trait for PEM documents. /// @@ -20,10 +20,11 @@ pub trait DecodePem: Decode + PemLabel + Sized { impl DecodePem for T { fn decode_pem(pem: impl AsRef<[u8]>) -> Result { - let mut reader = PemReader::new(pem.as_ref())?; + let reader = PemReader::new(pem.as_ref())?; Self::validate_pem_label(reader.type_label()).map_err(Error::from)?; - let ret = Self::decode(&mut reader)?; - Ok(reader.finish(ret)?) + let mut decoder = Decoder::from(reader); + let ret = Self::decode(&mut decoder)?; + Ok(decoder.finish(ret)?) } } diff --git a/ssh-encoding/src/pem/reader.rs b/ssh-encoding/src/pem/reader.rs index e51f67b5..30ec5601 100644 --- a/ssh-encoding/src/pem/reader.rs +++ b/ssh-encoding/src/pem/reader.rs @@ -1,4 +1,4 @@ -use crate::{Decode, Error, Reader, Result}; +use crate::{Reader, Result}; /// Inner PEM decoder. type Inner<'i> = pem_rfc7468::Decoder<'i>; @@ -7,21 +7,14 @@ type Inner<'i> = pem_rfc7468::Decoder<'i>; pub(crate) struct PemReader<'i> { /// Inner PEM reader. inner: Inner<'i>, - - /// Custom length of remaining data, used for nested length-prefixed reading. - remaining_len: usize, } impl<'i> PemReader<'i> { /// Create a new PEM reader which autodetects the line width of the input. pub(crate) fn new(pem: &'i [u8]) -> Result { let inner = Inner::new_detect_wrap(pem)?; - let remaining_len = inner.remaining_len(); - Ok(Self { - inner, - remaining_len, - }) + Ok(Self { inner }) } /// Get the PEM type label for the input document. @@ -30,4 +23,12 @@ impl<'i> PemReader<'i> { } } -impl_reader_for_newtype!(PemReader<'_>); +impl Reader for PemReader<'_> { + fn read<'o>(&mut self, out: &'o mut [u8]) -> Result<&'o [u8]> { + Ok(self.inner.decode(out)?) + } + + fn remaining_len(&self) -> usize { + self.inner.remaining_len() + } +} diff --git a/ssh-encoding/src/reader.rs b/ssh-encoding/src/reader.rs index e5fa710c..ae343e42 100644 --- a/ssh-encoding/src/reader.rs +++ b/ssh-encoding/src/reader.rs @@ -1,144 +1,29 @@ //! Reader trait and associated implementations. -use crate::{Error, Result, decode::Decode}; -use core::str; +use crate::{Decoder, Error, Result}; -/// Reader trait which decodes the binary SSH protocol serialization from -/// various inputs. +/// Byte reader for raw or incrementally decoded input. +/// +/// Use [`Reader::into_decoder`] to decode SSH protocol data types. Readers only supply bytes; +/// the decoder enforces nested length boundaries and complete consumption. 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 implemented in terms of. - /// - /// Returns `out` reborrowed as an immutable byte slice. + /// Returns `out` reborrowed as an immutable byte slice. On success, [`Reader::remaining_len`] + /// must decrease by exactly `out.len()` bytes. A read error may leave the reader partially + /// advanced; the enclosing decoder will reject subsequent reads. /// /// # Errors - /// Returns `Err(Error::Length)` if the exact amount of data couldn't be read. + /// Returns an error if the exact amount of data could not be read or decoded. fn read<'o>(&mut self, out: &'o mut [u8]) -> Result<&'o [u8]>; - /// Get the length of the remaining data after Base64 decoding. + /// Get the length of the remaining decoded data. fn remaining_len(&self) -> usize; - /// Is decoding finished? - fn is_finished(&self) -> bool { - self.remaining_len() == 0 - } - - /// Decode length-prefixed data. - /// - /// Decodes a `uint32` which identifies the length of some encapsulated data, then calls the - /// given reader function with the length of the remaining data. - /// - /// # Errors - /// Propagates errors returned from `F`. - fn read_prefixed(&mut self, f: F) -> core::result::Result - where - E: From, - F: FnOnce(&mut Self) -> core::result::Result; - - /// Decodes `[u8]` from `byte[n]` as described in [RFC4251 § 5]: - /// - /// > 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. - /// - /// Storage for the byte array must be provided as mutable byte slice in order to accommodate - /// `no_alloc` use cases. - /// - /// 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 - /// - /// # Errors - /// - Returns [`Error::Length`] if `out` is too small to accommodate the full `byte[n]`. - /// - Propagates errors returned from [`Reader::read`]. - fn read_byten<'o>(&mut self, out: &'o mut [u8]) -> Result<&'o [u8]> { - self.read_prefixed(|reader| { - let slice = out.get_mut(..reader.remaining_len()).ok_or(Error::Length)?; - reader.read(slice)?; - Ok(&*slice) - }) - } - - /// Decode a `string` as described in [RFC4251 § 5]: - /// - /// > Arbitrary length binary string. Strings are allowed to contain - /// > arbitrary binary data, including null characters and 8-bit - /// > characters. They are stored as a uint32 containing its length - /// > (number of bytes that follow) and zero (= empty string) or more - /// > bytes that are the value of the string. Terminating null - /// > characters are not used. - /// > - /// > Strings are also used to store text. In that case, US-ASCII is - /// > used for internal names, and ISO-10646 UTF-8 for text that might - /// > be displayed to the user. The terminating null character SHOULD - /// > NOT normally be stored in the string. For example: the US-ASCII - /// > string "testing" is represented as 00 00 00 07 t e s t i n g. The - /// > UTF-8 mapping does not alter the encoding of US-ASCII characters. - /// - /// Storage for the string data must be provided as mutable byte slice in order to accommodate - /// `no_alloc` use cases. - /// - /// 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 - /// - /// # Errors - /// - Propagates errors returned from [`Reader::read_byten`]. - /// - Returns [`Error::CharacterEncoding`] if the data is not valid UTF-8. - fn read_string<'o>(&mut self, buf: &'o mut [u8]) -> Result<&'o str> { - Ok(str::from_utf8(self.read_byten(buf)?)?) - } - - /// Drain the given number of bytes from the reader, discarding them. - /// - /// # Errors - /// Propagates errors returned from [`Reader::read`]. - fn drain(&mut self, n_bytes: usize) -> Result<()> { - let mut byte = [0]; - for _ in 0..n_bytes { - self.read(&mut byte)?; - } - Ok(()) - } - - /// Decode a `u32` length prefix, and then drain the length of the body. - /// - /// Upon success, returns the number of bytes drained sans the length of the `u32` length prefix - /// (4-bytes). - /// - /// # Errors - /// - Propagates errors returned from [`Reader::read_prefixed`]. - /// - Propagates errors returned from [`Reader::drain`]. - fn drain_prefixed(&mut self) -> Result { - self.read_prefixed(|reader| { - let len = reader.remaining_len(); - reader.drain(len)?; - Ok(len) - }) - } - - /// Ensure that decoding is finished. - /// - /// # Errors - /// Returns [`Error::TrailingData`] if there is data remaining in the encoder. - fn ensure_finished(&self) -> Result<()> { - if self.is_finished() { - Ok(()) - } else { - Err(Error::TrailingData { - remaining: self.remaining_len(), - }) - } - } - - /// Finish decoding, returning the given value if there is no remaining data. - /// - /// # Errors - /// Returns [`Error::TrailingData`] if there is data remaining in the encoder. - fn finish(self, value: T) -> Result { - self.ensure_finished()?; - Ok(value) + /// Consume this reader, wrapping it in a decoder for SSH protocol data types. + #[must_use] + fn into_decoder(self) -> Decoder { + self.into() } } @@ -154,72 +39,17 @@ impl Reader for &[u8] { } } - fn read_prefixed(&mut self, f: F) -> core::result::Result - where - E: From, - F: FnOnce(&mut Self) -> core::result::Result, - { - let prefix_len = usize::decode(self)?; - - if self.len() < prefix_len { - return Err(Error::Length.into()); - } - - let (mut prefix, remaining) = self.split_at(prefix_len); - let ret = f(&mut prefix)?; - prefix.ensure_finished()?; - *self = remaining; - Ok(ret) - } - fn remaining_len(&self) -> usize { self.len() } } -/// Writes a `Reader` impl for the given newtype with a `remaining_len` field. -// TODO(tarcieri): non-macro abstraction over `Base64Reader` and `PemReader` -#[cfg(any(feature = "base64", feature = "pem"))] -macro_rules! impl_reader_for_newtype { - ($type:ty) => { - impl Reader for $type { - fn read<'o>(&mut self, out: &'o mut [u8]) -> Result<&'o [u8]> { - if out.is_empty() { - return Ok(out); - } - - let remaining_len = self - .remaining_len - .checked_sub(out.len()) - .ok_or(Error::Length)?; - - let ret = self.inner.decode(out)?; - self.remaining_len = remaining_len; - Ok(ret) - } - - fn read_prefixed(&mut self, f: F) -> core::result::Result - where - E: From, - F: FnOnce(&mut Self) -> core::result::Result, - { - let prefix_len = usize::decode(self)?; - let new_remaining_len = self - .remaining_len - .checked_sub(prefix_len) - .ok_or(Error::Length)?; - - self.remaining_len = prefix_len; - let ret = f(self)?; - self.ensure_finished()?; - - self.remaining_len = new_remaining_len; - Ok(ret) - } +impl Reader for &mut R { + fn read<'o>(&mut self, out: &'o mut [u8]) -> Result<&'o [u8]> { + (**self).read(out) + } - fn remaining_len(&self) -> usize { - self.remaining_len - } - } - }; + fn remaining_len(&self) -> usize { + (**self).remaining_len() + } } diff --git a/ssh-encoding/tests/decode.rs b/ssh-encoding/tests/decode.rs index 1bfff77a..190da9f0 100644 --- a/ssh-encoding/tests/decode.rs +++ b/ssh-encoding/tests/decode.rs @@ -1,44 +1,44 @@ //! Tests for the `Decode` trait. use hex_literal::hex; -use ssh_encoding::{Decode, Error, Reader}; +use ssh_encoding::{Decode, Decoder, Error}; #[test] fn decode_u8() { - let mut bytes = hex!("42").as_slice(); + let mut bytes = Decoder::from(hex!("42").as_slice()); let ret = u8::decode(&mut bytes).unwrap(); assert_eq!(ret, 0x42u8); } #[test] fn decode_boolean() { - let mut bytes = hex!("01").as_slice(); + let mut bytes = Decoder::from(hex!("01").as_slice()); let ret = bool::decode(&mut bytes).unwrap(); assert!(ret); // "All non-zero values MUST be interpreted as TRUE" - let mut bytes = hex!("FF").as_slice(); + let mut bytes = Decoder::from(hex!("FF").as_slice()); let ret = bool::decode(&mut bytes).unwrap(); assert!(ret); } #[test] fn decode_u32() { - let mut bytes = hex!("DEADBEEF").as_slice(); + let mut bytes = Decoder::from(hex!("DEADBEEF").as_slice()); let ret = u32::decode(&mut bytes).unwrap(); assert_eq!(ret, 0xDEADBEEFu32); } #[test] fn decode_u64() { - let mut bytes = hex!("0000DEADBEEFCAFE").as_slice(); + let mut bytes = Decoder::from(hex!("0000DEADBEEFCAFE").as_slice()); let ret = u64::decode(&mut bytes).unwrap(); assert_eq!(ret, 0xDEADBEEFCAFEu64); } #[test] fn decode_usize() { - let mut bytes = hex!("000FFFFF").as_slice(); + let mut bytes = Decoder::from(hex!("000FFFFF").as_slice()); let ret = usize::decode(&mut bytes).unwrap(); assert_eq!(ret, 0xFFFFFusize); } @@ -46,14 +46,14 @@ fn decode_usize() { /// `usize` decoder has a sanity limit of 0xFFFFF. #[test] fn reject_oversize_usize() { - let mut bytes = hex!("00100000").as_slice(); + let mut bytes = Decoder::from(hex!("00100000").as_slice()); let err = usize::decode(&mut bytes).err().unwrap(); assert_eq!(err, Error::Overflow); } #[test] fn decode_byte_array() { - let mut bytes = hex!("6578616d706c65").as_slice(); + let mut bytes = Decoder::from(hex!("6578616d706c65").as_slice()); let ret = <[u8; 7]>::decode(&mut bytes).unwrap(); assert_eq!(&ret, b"example"); } @@ -61,7 +61,7 @@ fn decode_byte_array() { #[cfg(feature = "alloc")] #[test] fn decode_byte_vec() { - let mut bytes = hex!("000000076578616d706c65").as_slice(); + let mut bytes = Decoder::from(hex!("000000076578616d706c65").as_slice()); let ret = Vec::::decode(&mut bytes).unwrap(); assert_eq!(&ret, b"example"); } @@ -69,7 +69,7 @@ fn decode_byte_vec() { #[cfg(feature = "alloc")] #[test] fn decode_string() { - let mut bytes = hex!("000000076578616d706c65").as_slice(); + let mut bytes = Decoder::from(hex!("000000076578616d706c65").as_slice()); let ret = String::decode(&mut bytes).unwrap(); assert_eq!(&ret, "example"); } @@ -77,7 +77,9 @@ fn decode_string() { #[cfg(feature = "alloc")] #[test] fn decode_string_vec() { - let mut bytes = hex!("0000001500000003666f6f000000036261720000000362617a").as_slice(); + let mut bytes = hex!("0000001500000003666f6f000000036261720000000362617a") + .as_slice() + .into(); let ret = Vec::::decode(&mut bytes).unwrap(); assert_eq!(ret.len(), 3); assert_eq!(ret[0], "foo"); @@ -87,7 +89,7 @@ fn decode_string_vec() { #[test] fn read_prefixed_rejects_unread_bytes_in_slice() { - let mut reader: &[u8] = &[0, 0, 0, 2, 0xAA, 0xBB]; + let mut reader = Decoder::from([0, 0, 0, 2, 0xAA, 0xBB].as_slice()); let result: Result = reader.read_prefixed(u8::decode); assert_eq!(result, Err(Error::TrailingData { remaining: 1 })); } diff --git a/ssh-encoding/tests/decoder.rs b/ssh-encoding/tests/decoder.rs new file mode 100644 index 00000000..81f096bc --- /dev/null +++ b/ssh-encoding/tests/decoder.rs @@ -0,0 +1,254 @@ +//! Shared decoder behavior across byte, Base64, and PEM readers. + +use hex_literal::hex; +use ssh_encoding::{Decode, Decoder, Error, Reader}; + +/// Run the same parser and completion check through every enabled input representation. +fn check_backends(bytes: &[u8], expected: Result) +where + T: Decode + core::fmt::Debug + Eq, +{ + let mut decoder = Decoder::from(bytes); + let result = T::decode(&mut decoder).and_then(|value| decoder.finish(value)); + assert_eq!(result, expected); + + #[cfg(feature = "base64")] + { + use base64ct::{Base64, Encoding}; + use ssh_encoding::Base64Reader; + + let mut buffer = [0; 256]; + let base64 = Base64::encode(bytes, &mut buffer).expect("test input fits in buffer"); + let reader = Base64Reader::new(base64.as_bytes()).expect("valid Base64 test input"); + let mut decoder: Decoder<_> = reader.into(); + let result = T::decode(&mut decoder).and_then(|value| decoder.finish(value)); + assert_eq!(result, expected); + } + + #[cfg(feature = "pem")] + { + use ssh_encoding::{ + DecodePem, + pem::{LineEnding, PemLabel}, + }; + + struct PemValue(T); + + impl PemLabel for PemValue { + const PEM_LABEL: &'static str = "TEST"; + } + + impl Decode for PemValue { + type Error = T::Error; + + fn decode(decoder: &mut Decoder) -> Result { + T::decode(decoder).map(Self) + } + } + + let mut buffer = [0; 512]; + let pem = pem_rfc7468::encode("TEST", LineEnding::LF, bytes, &mut buffer) + .expect("test input fits in buffer"); + let result = PemValue::::decode_pem(pem).map(|value| value.0); + assert_eq!(result, expected); + } +} + +#[test] +fn nested_scopes_and_siblings() { + #[derive(Debug, Eq, PartialEq)] + struct Value; + + impl Decode for Value { + type Error = Error; + + fn decode(decoder: &mut Decoder) -> Result { + decoder.read_prefixed(|decoder| { + assert_eq!(decoder.remaining_len(), 9); + assert_eq!(decoder.read_prefixed(u8::decode)?, 0xaa); + assert_eq!(decoder.remaining_len(), 4); + decoder.read_prefixed(|decoder| { + assert!(decoder.is_finished()); + decoder.read(&mut [])?; + Ok::<_, Error>(()) + }) + })?; + assert_eq!(decoder.remaining_len(), 1); + assert_eq!(u8::decode(decoder)?, 0xbb); + Ok(Self) + } + } + + check_backends(&hex!("00000009 00000001 aa 00000000 bb"), Ok(Value)); +} + +#[test] +fn unread_body_is_rejected() { + #[derive(Debug, Eq, PartialEq)] + struct Value; + + impl Decode for Value { + type Error = Error; + + fn decode(decoder: &mut Decoder) -> Result { + decoder.read_prefixed(u8::decode)?; + Ok(Self) + } + } + + check_backends::( + &hex!("00000002 aabb cc"), + Err(Error::TrailingData { remaining: 1 }), + ); +} + +#[test] +fn reads_cannot_consume_sibling_bytes() { + #[derive(Debug, Eq, PartialEq)] + struct Value; + + impl Decode for Value { + type Error = Error; + + fn decode(decoder: &mut Decoder) -> Result { + decoder.read_prefixed(<[u8; 3]>::decode)?; + Ok(Self) + } + } + + check_backends::(&hex!("00000002 aabb cc"), Err(Error::Length)); +} + +#[test] +fn nested_length_cannot_extend_into_sibling() { + #[derive(Debug, Eq, PartialEq)] + struct Value; + + impl Decode for Value { + type Error = Error; + + fn decode(decoder: &mut Decoder) -> Result { + decoder.read_prefixed(|decoder| decoder.read_prefixed(<[u8; 2]>::decode))?; + Ok(Self) + } + } + + check_backends::(&hex!("00000005 00000002 aa bb"), Err(Error::Length)); +} + +#[test] +fn top_level_trailing_data_is_rejected() { + check_backends::(&hex!("aabb"), Err(Error::TrailingData { remaining: 1 })); +} + +#[test] +fn recursive_decoding_keeps_the_same_reader_type() { + #[derive(Debug, Eq, PartialEq)] + struct Depth(usize); + + impl Decode for Depth { + type Error = Error; + + fn decode(decoder: &mut Decoder) -> Result { + if decoder.remaining_len() == 1 { + assert_eq!(u8::decode(decoder)?, 0x42); + Ok(Self(0)) + } else { + // This must compile without recursively instantiating nested reader types. + decoder + .read_prefixed(Self::decode) + .map(|depth| Self(depth.0 + 1)) + } + } + } + + check_backends(&hex!("00000009 00000005 00000001 42"), Ok(Depth(3))); +} + +#[test] +fn callback_error_restores_scope_and_invalidates_decoder() { + #[derive(Debug, Eq, PartialEq)] + struct Value; + + #[derive(Debug, Eq, PartialEq)] + enum CustomError { + Encoding(Error), + Callback, + } + + impl From for CustomError { + fn from(error: Error) -> Self { + Self::Encoding(error) + } + } + + impl Decode for Value { + type Error = Error; + + fn decode(decoder: &mut Decoder) -> Result { + let result = decoder.read_prefixed(|decoder| { + assert_eq!(u8::decode(decoder)?, 0xaa); + Err::<(), _>(CustomError::Callback) + }); + assert_eq!(result, Err(CustomError::Callback)); + assert_eq!(decoder.remaining_len(), 2); + assert_eq!(u8::decode(decoder), Err(Error::Failed)); + assert_eq!(decoder.ensure_finished(), Err(Error::Failed)); + Err(Error::Failed) + } + } + + check_backends::(&hex!("00000002 aabb cc"), Err(Error::Failed)); +} + +#[test] +fn ignored_read_error_cannot_make_scope_succeed() { + let mut decoder = Decoder::from([0xaa, 0xbb].as_slice()); + let result = decoder.read_nested(1, |decoder| { + assert_eq!(u8::decode(decoder)?, 0xaa); + assert_eq!(u8::decode(decoder), Err(Error::Length)); + Ok::<_, Error>(()) + }); + assert_eq!(result, Err(Error::Failed)); + assert_eq!(decoder.remaining_len(), 1); +} + +#[test] +fn unwinding_restores_scope_and_invalidates_decoder() { + use core::panic::AssertUnwindSafe; + use std::panic::catch_unwind; + + let mut decoder = Decoder::from([0xaa, 0xbb, 0xcc].as_slice()); + let result = catch_unwind(AssertUnwindSafe(|| { + decoder.read_nested::<(), Error, _>(2, |decoder| { + assert_eq!(u8::decode(decoder)?, 0xaa); + panic!("callback failed"); + }) + })); + assert!(result.is_err()); + assert_eq!(decoder.remaining_len(), 2); + assert_eq!(u8::decode(&mut decoder), Err(Error::Failed)); +} + +#[test] +fn partially_advanced_reader_cannot_be_reused_after_error() { + struct PartialReader<'a>(&'a [u8]); + + impl Reader for PartialReader<'_> { + fn read<'o>(&mut self, _: &'o mut [u8]) -> Result<&'o [u8], Error> { + self.0 = &self.0[1..]; + Err(Error::CharacterEncoding) + } + + fn remaining_len(&self) -> usize { + self.0.len() + } + } + + let mut reader = PartialReader(&[0xaa, 0xbb]); + let mut decoder = Decoder::from(&mut reader); + assert_eq!(u8::decode(&mut decoder), Err(Error::CharacterEncoding)); + assert_eq!(u8::decode(&mut decoder), Err(Error::Failed)); + assert_eq!(decoder.finish(()), Err(Error::Failed)); + assert_eq!(reader.remaining_len(), 1); +} diff --git a/ssh-encoding/tests/derive.rs b/ssh-encoding/tests/derive.rs index 9e034b43..f6d66426 100644 --- a/ssh-encoding/tests/derive.rs +++ b/ssh-encoding/tests/derive.rs @@ -89,7 +89,7 @@ fn derive_encode_decode_roundtrip_most_types() { i: 0x20u8, }; assert_eq!(&data, expected.encode_vec().unwrap().as_slice()); - let most_types = MostTypes::::decode(&mut &data[..]).unwrap(); + let most_types = MostTypes::::decode(&mut data.as_slice().into()).unwrap(); assert_eq!(most_types, expected); } @@ -113,7 +113,7 @@ fn derive_encode_decode_roundtrip_length_prefixed() { b: "hello".to_string(), }; assert_eq!(&data, expected.encode_vec().unwrap().as_slice()); - let length_prefixed = LengthPrefixed::decode(&mut &data[..]).unwrap(); + let length_prefixed = LengthPrefixed::decode(&mut data.as_slice().into()).unwrap(); assert_eq!(length_prefixed, expected); } @@ -122,7 +122,7 @@ fn derive_encode_decode_empty() { let data = [0u8; 0]; let expected = Empty; assert_eq!(data, expected.encode_vec().unwrap().as_slice()); - let actual = Empty::decode(&mut &data[..]).unwrap(); + let actual = Empty::decode(&mut data.as_slice().into()).unwrap(); assert_eq!(actual, expected); } @@ -135,7 +135,7 @@ fn derive_encode_decode_enum_unit() { ]; let expected = ComplexEnum::Bar; assert_eq!(data, expected.encode_vec().unwrap().as_slice()); - let actual = ComplexEnum::decode(&mut &data[..]).unwrap(); + let actual = ComplexEnum::decode(&mut data.as_slice().into()).unwrap(); assert_eq!(actual, expected); } @@ -151,7 +151,7 @@ fn derive_encode_decode_enum_struct() { ]; let expected = ComplexEnum::Baz { a: 1, b: 2 }; assert_eq!(data, expected.encode_vec().unwrap().as_slice()); - let actual = ComplexEnum::decode(&mut &data[..]).unwrap(); + let actual = ComplexEnum::decode(&mut data.as_slice().into()).unwrap(); assert_eq!(actual, expected); } @@ -167,7 +167,7 @@ fn derive_encode_decode_enum_tuple() { ]; let expected = ComplexEnum::Fiz(1, 2); assert_eq!(data, expected.encode_vec().unwrap().as_slice()); - let actual = ComplexEnum::decode(&mut &data[..]).unwrap(); + let actual = ComplexEnum::decode(&mut data.as_slice().into()).unwrap(); assert_eq!(actual, expected); } @@ -179,6 +179,6 @@ fn derive_encode_decode_enum_no_prefix_u32_repr() { ]; let expected = SimpleEnum::A; assert_eq!(data, expected.encode_vec().unwrap().as_slice()); - let actual = SimpleEnum::decode(&mut &data[..]).unwrap(); + let actual = SimpleEnum::decode(&mut data.as_slice().into()).unwrap(); assert_eq!(actual, expected); } diff --git a/ssh-key/Cargo.toml b/ssh-key/Cargo.toml index e2b9037a..dd5f90d5 100644 --- a/ssh-key/Cargo.toml +++ b/ssh-key/Cargo.toml @@ -18,9 +18,9 @@ edition = "2024" rust-version = "1.87" [dependencies] -cipher = { package = "ssh-cipher", version = "0.3", features = ["encoding", "zeroize"] } +cipher = { package = "ssh-cipher", version = "0.4.0-pre", features = ["encoding", "zeroize"] } ctutils = { version = "0.4.8", default-features = false } -encoding = { package = "ssh-encoding", version = "0.3", features = ["base64", "digest", "pem", "ctutils", "zeroize"] } +encoding = { package = "ssh-encoding", version = "0.4.0-pre", features = ["base64", "digest", "pem", "ctutils", "zeroize"] } sha2 = { version = "0.11", default-features = false } signature = { version = "3", default-features = false } zeroize = { version = "1.9", default-features = false } diff --git a/ssh-key/src/certificate.rs b/ssh-key/src/certificate.rs index b2c49613..c35eff0e 100644 --- a/ssh-key/src/certificate.rs +++ b/ssh-key/src/certificate.rs @@ -17,7 +17,7 @@ use alloc::{ vec::Vec, }; use core::str::FromStr; -use encoding::{Base64Reader, CheckedSum, Decode, Encode, Reader, Writer}; +use encoding::{Base64Reader, CheckedSum, Decode, Decoder, Encode, Reader, Writer}; use signature::Verifier; #[cfg(feature = "serde")] @@ -175,7 +175,7 @@ impl Certificate { /// - Returns [`Error::Encoding`] in the event of an encoding error. pub fn from_openssh(certificate_str: &str) -> Result { let encapsulation = SshFormat::decode(certificate_str.trim_end().as_bytes())?; - let mut reader = Base64Reader::new(encapsulation.base64_data)?; + let mut reader = Decoder::from(Base64Reader::new(encapsulation.base64_data)?); let mut cert = Certificate::decode(&mut reader)?; // Verify that the algorithm in the Base64-encoded data matches the text @@ -191,9 +191,9 @@ impl Certificate { /// /// # Errors /// Returns [`Error::Encoding`] in the event of an encoding error. - pub fn from_bytes(mut bytes: &[u8]) -> Result { - let reader = &mut bytes; - let cert = Certificate::decode(reader)?; + pub fn from_bytes(bytes: &[u8]) -> Result { + let mut reader = Decoder::from(bytes); + let cert = Certificate::decode(&mut reader)?; Ok(reader.finish(cert)?) } @@ -499,7 +499,7 @@ impl Certificate { /// /// # Errors /// Returns [`Error::Encoding`] in the event of an encoding error. - pub fn decode_as(reader: &mut impl Reader, algorithm: Algorithm) -> Result { + pub fn decode_as(reader: &mut Decoder, algorithm: Algorithm) -> Result { Ok(Self { nonce: Vec::decode(reader)?, public_key: KeyData::decode_as(reader, algorithm)?, @@ -533,7 +533,7 @@ impl Certificate { impl Decode for Certificate { type Error = Error; - fn decode(reader: &mut impl Reader) -> Result { + fn decode(reader: &mut Decoder) -> Result { let algorithm = Algorithm::new_certificate(&String::decode(reader)?)?; Self::decode_as(reader, algorithm) } diff --git a/ssh-key/src/certificate/cert_type.rs b/ssh-key/src/certificate/cert_type.rs index 485aeb2c..d830f12e 100644 --- a/ssh-key/src/certificate/cert_type.rs +++ b/ssh-key/src/certificate/cert_type.rs @@ -1,7 +1,7 @@ //! OpenSSH certificate types. use crate::{Error, Result}; -use encoding::{Decode, Encode, Reader, Writer}; +use encoding::{Decode, Decoder, Encode, Reader, Writer}; /// Types of OpenSSH certificates: user or host. #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] @@ -33,7 +33,7 @@ impl CertType { impl Decode for CertType { type Error = Error; - fn decode(reader: &mut impl Reader) -> Result { + fn decode(reader: &mut Decoder) -> Result { u32::decode(reader)?.try_into() } } diff --git a/ssh-key/src/certificate/options_map.rs b/ssh-key/src/certificate/options_map.rs index 3fc43bc5..80aab4d1 100644 --- a/ssh-key/src/certificate/options_map.rs +++ b/ssh-key/src/certificate/options_map.rs @@ -6,7 +6,7 @@ use core::{ cmp::Ordering, ops::{Deref, DerefMut}, }; -use encoding::{CheckedSum, Decode, Encode, Reader, Writer}; +use encoding::{CheckedSum, Decode, Decoder, Encode, Reader, Writer}; /// Key/value map type used for certificate's critical options and extensions. #[derive(Clone, Debug, Default, Eq, Hash, PartialEq, PartialOrd, Ord)] @@ -37,7 +37,7 @@ impl DerefMut for OptionsMap { impl Decode for OptionsMap { type Error = Error; - fn decode(reader: &mut impl Reader) -> Result { + fn decode(reader: &mut Decoder) -> Result { reader.read_prefixed(|reader| { let mut entries = Vec::<(String, String)>::new(); diff --git a/ssh-key/src/certificate/unix_time.rs b/ssh-key/src/certificate/unix_time.rs index dd8c9385..8ac584bf 100644 --- a/ssh-key/src/certificate/unix_time.rs +++ b/ssh-key/src/certificate/unix_time.rs @@ -3,7 +3,7 @@ use crate::{Error, Result}; use core::fmt; use core::fmt::Formatter; -use encoding::{Decode, Encode, Reader, Writer}; +use encoding::{Decode, Decoder, Encode, Reader, Writer}; #[cfg(feature = "std")] use { @@ -82,7 +82,7 @@ impl UnixTime { impl Decode for UnixTime { type Error = Error; - fn decode(reader: &mut impl Reader) -> Result { + fn decode(reader: &mut Decoder) -> Result { u64::decode(reader)?.try_into() } } diff --git a/ssh-key/src/comment.rs b/ssh-key/src/comment.rs index b6b71a52..18a524a8 100644 --- a/ssh-key/src/comment.rs +++ b/ssh-key/src/comment.rs @@ -6,7 +6,7 @@ use core::{ fmt, str::{self, FromStr}, }; -use encoding::{Decode, Encode, Error, Reader, Writer}; +use encoding::{Decode, Decoder, Encode, Error, Reader, Writer}; /// SSH key comment (e.g. email address of owner) /// @@ -40,7 +40,7 @@ impl AsRef for Comment { impl Decode for Comment { type Error = Error; - fn decode(reader: &mut impl Reader) -> encoding::Result { + fn decode(reader: &mut Decoder) -> encoding::Result { Vec::::decode(reader).map(Into::into) } } diff --git a/ssh-key/src/kdf.rs b/ssh-key/src/kdf.rs index 5a66a8c9..2347d610 100644 --- a/ssh-key/src/kdf.rs +++ b/ssh-key/src/kdf.rs @@ -3,7 +3,7 @@ //! These are used for deriving an encryption key from a password. use crate::{Error, KdfAlg, Result}; -use encoding::{CheckedSum, Decode, Encode, Reader, Writer}; +use encoding::{CheckedSum, Decode, Decoder, Encode, Reader, Writer}; #[cfg(feature = "alloc")] use alloc::vec::Vec; @@ -144,7 +144,7 @@ impl Kdf { impl Decode for Kdf { type Error = Error; - fn decode(reader: &mut impl Reader) -> Result { + fn decode(reader: &mut Decoder) -> Result { match KdfAlg::decode(reader)? { KdfAlg::None => { if usize::decode(reader)? == 0 { diff --git a/ssh-key/src/ppk.rs b/ssh-key/src/ppk.rs index a7efd6f1..835bc30e 100644 --- a/ssh-key/src/ppk.rs +++ b/ssh-key/src/ppk.rs @@ -19,7 +19,7 @@ use crate::public::KeyData; use crate::{Algorithm, Error, PublicKey}; use ctutils::CtEq; use encoding::base64::{self, Base64, Encoding}; -use encoding::{Decode, Encode, LabelError, Reader}; +use encoding::{Decode, Decoder, Encode, LabelError, Reader}; #[derive(Debug)] pub(crate) enum Kdf { @@ -442,7 +442,7 @@ impl PpkContainer { } let mut public_key = PublicKey::from_bytes(&public_key)?; - let mut private_key_cursor = &private_key[..]; + let mut private_key_cursor = Decoder::from(private_key.as_slice()); let keypair_data = decode_private_key_as(&mut private_key_cursor, public_key.clone(), ppk.algorithm)?; @@ -456,7 +456,7 @@ impl PpkContainer { } fn decode_private_key_as( - reader: &mut impl Reader, + reader: &mut Decoder, public: PublicKey, algorithm: Algorithm, ) -> Result { diff --git a/ssh-key/src/private.rs b/ssh-key/src/private.rs index a7bf4a2a..c4501df3 100644 --- a/ssh-key/src/private.rs +++ b/ssh-key/src/private.rs @@ -134,7 +134,7 @@ use cipher::Tag; use core::str; use ctutils::{Choice, CtEq}; use encoding::{ - CheckedSum, Decode, DecodePem, Encode, EncodePem, Reader, Writer, + CheckedSum, Decode, DecodePem, Decoder, Encode, EncodePem, Reader, Writer, pem::{LineEnding, PemLabel}, }; @@ -266,9 +266,9 @@ impl PrivateKey { /// /// # Errors /// Returns [`Error::Encoding`] in the event of an encoding error. - pub fn from_bytes(mut bytes: &[u8]) -> Result { - let reader = &mut bytes; - let private_key = Self::decode(reader)?; + pub fn from_bytes(bytes: &[u8]) -> Result { + let mut reader = Decoder::from(bytes); + let private_key = Self::decode(&mut reader)?; Ok(reader.finish(private_key)?) } @@ -465,7 +465,7 @@ impl PrivateKey { #[allow(clippy::arithmetic_side_effects)] // block sizes are constants Self::decode_privatekey_comment_pair( - &mut &**buffer, + &mut (&**buffer).into(), self.public_key.key_data.clone(), self.cipher.block_size(), self.cipher.block_size() - 1, @@ -669,7 +669,7 @@ impl PrivateKey { /// /// [PROTOCOL.key]: https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.key?annotate=HEAD fn decode_privatekey_comment_pair( - reader: &mut impl Reader, + reader: &mut Decoder, public_key: public::KeyData, block_size: usize, max_padding_size: usize, @@ -807,7 +807,7 @@ impl PartialEq for PrivateKey { impl Decode for PrivateKey { type Error = Error; - fn decode(reader: &mut impl Reader) -> Result { + fn decode(reader: &mut Decoder) -> Result { let mut auth_magic = [0u8; Self::AUTH_MAGIC.len()]; reader.read(&mut auth_magic)?; diff --git a/ssh-key/src/private/dsa.rs b/ssh-key/src/private/dsa.rs index 186b34ee..7a440fe2 100644 --- a/ssh-key/src/private/dsa.rs +++ b/ssh-key/src/private/dsa.rs @@ -3,7 +3,7 @@ use crate::{Error, Mpint, Result, public::DsaPublicKey}; use core::fmt; use ctutils::{Choice, CtEq}; -use encoding::{CheckedSum, Decode, Encode, Reader, Writer}; +use encoding::{CheckedSum, Decode, Decoder, Encode, Reader, Writer}; use zeroize::Zeroize; #[cfg(feature = "dsa")] @@ -80,7 +80,7 @@ impl TryFrom for DsaPrivateKey { impl Decode for DsaPrivateKey { type Error = Error; - fn decode(reader: &mut impl Reader) -> Result { + fn decode(reader: &mut Decoder) -> Result { Self::new(Mpint::decode(reader)?) } } @@ -206,7 +206,7 @@ impl Eq for DsaKeypair {} impl Decode for DsaKeypair { type Error = Error; - fn decode(reader: &mut impl Reader) -> Result { + fn decode(reader: &mut Decoder) -> Result { let public = DsaPublicKey::decode(reader)?; let private = DsaPrivateKey::decode(reader)?; DsaKeypair::new(public, private) diff --git a/ssh-key/src/private/ecdsa.rs b/ssh-key/src/private/ecdsa.rs index 7e0ea8d9..563168bb 100644 --- a/ssh-key/src/private/ecdsa.rs +++ b/ssh-key/src/private/ecdsa.rs @@ -3,7 +3,7 @@ use crate::{Algorithm, EcdsaCurve, Error, Result, public::EcdsaPublicKey}; use core::fmt; use ctutils::{Choice, CtEq}; -use encoding::{CheckedSum, Decode, Encode, Reader, Writer}; +use encoding::{CheckedSum, Decode, Decoder, Encode, Reader, Writer}; use sec1::consts::{U32, U48, U66}; use zeroize::Zeroize; @@ -45,7 +45,7 @@ impl EcdsaPrivateKey { impl Decode for EcdsaPrivateKey { type Error = Error; - fn decode(reader: &mut impl Reader) -> Result { + fn decode(reader: &mut Decoder) -> Result { reader.read_prefixed(|reader| { let mut len = reader.remaining_len(); @@ -327,7 +327,7 @@ impl PartialEq for EcdsaKeypair { impl Decode for EcdsaKeypair { type Error = Error; - fn decode(reader: &mut impl Reader) -> Result { + fn decode(reader: &mut Decoder) -> Result { match EcdsaPublicKey::decode(reader)? { EcdsaPublicKey::NistP256(public) => { let private = EcdsaPrivateKey::<32>::decode(reader)?; diff --git a/ssh-key/src/private/ed25519.rs b/ssh-key/src/private/ed25519.rs index 56400ef4..29662ecb 100644 --- a/ssh-key/src/private/ed25519.rs +++ b/ssh-key/src/private/ed25519.rs @@ -5,7 +5,7 @@ use crate::{Error, Result, public::Ed25519PublicKey}; use core::fmt; use ctutils::{Choice, CtEq}; -use encoding::{CheckedSum, Decode, Encode, Reader, Writer}; +use encoding::{CheckedSum, Decode, Decoder, Encode, Reader, Writer}; use zeroize::{Zeroize, Zeroizing}; #[cfg(feature = "rand_core")] @@ -216,7 +216,7 @@ impl PartialEq for Ed25519Keypair { impl Decode for Ed25519Keypair { type Error = Error; - fn decode(reader: &mut impl Reader) -> Result { + fn decode(reader: &mut Decoder) -> Result { // Decode private key let public = Ed25519PublicKey::decode(reader)?; diff --git a/ssh-key/src/private/keypair.rs b/ssh-key/src/private/keypair.rs index f187bbaf..5ac7a15a 100644 --- a/ssh-key/src/private/keypair.rs +++ b/ssh-key/src/private/keypair.rs @@ -3,7 +3,7 @@ use super::ed25519::Ed25519Keypair; use crate::{Algorithm, Error, Result, public}; use ctutils::{Choice, CtEq}; -use encoding::{CheckedSum, Decode, Encode, Reader, Writer}; +use encoding::{CheckedSum, Decode, Decoder, Encode, Reader, Writer}; #[cfg(feature = "alloc")] use { @@ -266,7 +266,7 @@ impl KeypairData { /// - Returns [`Error::AlgorithmUnknown`] if the provided `algorithm` is unknown or unsupported /// by this library. /// - Returns [`Error::Encoding`] in the event of an encoding error. - pub fn decode_as(reader: &mut impl Reader, algorithm: Algorithm) -> Result { + pub fn decode_as(reader: &mut Decoder, algorithm: Algorithm) -> Result { match algorithm { #[cfg(feature = "alloc")] Algorithm::Dsa => DsaKeypair::decode(reader).map(Self::Dsa), @@ -338,7 +338,7 @@ impl PartialEq for KeypairData { impl Decode for KeypairData { type Error = Error; - fn decode(reader: &mut impl Reader) -> Result { + fn decode(reader: &mut Decoder) -> Result { let algorithm = Algorithm::decode(reader)?; Self::decode_as(reader, algorithm) } diff --git a/ssh-key/src/private/opaque.rs b/ssh-key/src/private/opaque.rs index 2b059e4f..d85ee602 100644 --- a/ssh-key/src/private/opaque.rs +++ b/ssh-key/src/private/opaque.rs @@ -15,7 +15,7 @@ use crate::{ use alloc::vec::Vec; use core::fmt::{self, Debug}; use ctutils::{Choice, CtEq}; -use encoding::{CheckedSum, Decode, Encode, Reader, Writer}; +use encoding::{CheckedSum, Decode, Decoder, Encode, Reader, Writer}; /// An opaque keypair. /// @@ -46,7 +46,10 @@ impl OpaqueKeypair { } /// Decode [`OpaqueKeypair`] for the specified algorithm. - pub(super) fn decode_as(reader: &mut impl Reader, algorithm: Algorithm) -> Result { + pub(super) fn decode_as( + reader: &mut Decoder, + algorithm: Algorithm, + ) -> Result { let key = OpaqueKeypairBytes::decode(reader)?; let public = OpaquePublicKey { algorithm, @@ -116,7 +119,7 @@ impl Debug for OpaqueKeypairBytes { impl Decode for OpaqueKeypairBytes { type Error = Error; - fn decode(reader: &mut impl Reader) -> Result { + fn decode(reader: &mut Decoder) -> Result { let public = OpaquePublicKeyBytes::decode(reader)?; let private = OpaquePrivateKeyBytes::decode(reader)?; @@ -163,7 +166,7 @@ impl Debug for OpaquePrivateKeyBytes { impl Decode for OpaquePrivateKeyBytes { type Error = Error; - fn decode(reader: &mut impl Reader) -> Result { + fn decode(reader: &mut Decoder) -> Result { Ok(Self(Vec::decode(reader)?)) } } diff --git a/ssh-key/src/private/rsa.rs b/ssh-key/src/private/rsa.rs index ae0f686d..f87f0e21 100644 --- a/ssh-key/src/private/rsa.rs +++ b/ssh-key/src/private/rsa.rs @@ -3,7 +3,7 @@ use crate::{Error, Mpint, Result, public::RsaPublicKey}; use core::fmt::{self, Debug}; use ctutils::{Choice, CtEq}; -use encoding::{CheckedSum, Decode, Encode, Reader, Writer}; +use encoding::{CheckedSum, Decode, Decoder, Encode, Reader, Writer}; use zeroize::Zeroize; #[cfg(feature = "rsa")] @@ -100,7 +100,7 @@ impl Drop for RsaPrivateKey { impl Decode for RsaPrivateKey { type Error = Error; - fn decode(reader: &mut impl Reader) -> Result { + fn decode(reader: &mut Decoder) -> Result { let d = Mpint::decode(reader)?; let iqmp = Mpint::decode(reader)?; let p = Mpint::decode(reader)?; @@ -199,7 +199,7 @@ impl Debug for RsaKeypair { impl Decode for RsaKeypair { type Error = Error; - fn decode(reader: &mut impl Reader) -> Result { + fn decode(reader: &mut Decoder) -> Result { let n = Mpint::decode(reader)?; let e = Mpint::decode(reader)?; let public = RsaPublicKey::new(e, n)?; diff --git a/ssh-key/src/private/sk.rs b/ssh-key/src/private/sk.rs index 2a580296..7fc4fb90 100644 --- a/ssh-key/src/private/sk.rs +++ b/ssh-key/src/private/sk.rs @@ -4,7 +4,7 @@ use crate::{Error, Result, public}; use alloc::vec::Vec; -use encoding::{CheckedSum, Decode, Encode, Reader, Writer}; +use encoding::{CheckedSum, Decode, Decoder, 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). @@ -73,7 +73,7 @@ impl SkEcdsaSha2NistP256 { impl Decode for SkEcdsaSha2NistP256 { type Error = Error; - fn decode(reader: &mut impl Reader) -> Result { + fn decode(reader: &mut Decoder) -> Result { Ok(Self { public: public::SkEcdsaSha2NistP256::decode(reader)?, flags: u8::decode(reader)?, @@ -169,7 +169,7 @@ impl SkEd25519 { impl Decode for SkEd25519 { type Error = Error; - fn decode(reader: &mut impl Reader) -> Result { + fn decode(reader: &mut Decoder) -> Result { Ok(Self { public: public::SkEd25519::decode(reader)?, flags: u8::decode(reader)?, diff --git a/ssh-key/src/public.rs b/ssh-key/src/public.rs index 9dd0b106..c46e72e4 100644 --- a/ssh-key/src/public.rs +++ b/ssh-key/src/public.rs @@ -31,7 +31,7 @@ pub(crate) use self::ssh_format::SshFormat; use crate::{Algorithm, Error, Fingerprint, HashAlg, Result}; use core::str::{self, FromStr}; -use encoding::{Base64Reader, Decode, Reader}; +use encoding::{Base64Reader, Decode, Decoder, Reader}; #[cfg(feature = "alloc")] use { @@ -130,7 +130,7 @@ impl PublicKey { /// Returns [`Error::Encoding`] in the event of an encoding error. pub fn from_openssh(public_key: &str) -> Result { let encapsulation = SshFormat::decode(public_key.trim_end().as_bytes())?; - let mut reader = Base64Reader::new(encapsulation.base64_data)?; + let mut reader = Decoder::from(Base64Reader::new(encapsulation.base64_data)?); let key_data = KeyData::decode(&mut reader)?; // Verify that the algorithm in the Base64-encoded data matches the text @@ -151,9 +151,9 @@ impl PublicKey { /// /// # Errors /// Returns [`Error::Encoding`] in the event of an encoding error. - pub fn from_bytes(mut bytes: &[u8]) -> Result { - let reader = &mut bytes; - let key_data = KeyData::decode(reader)?; + pub fn from_bytes(bytes: &[u8]) -> Result { + let mut reader = Decoder::from(bytes); + let key_data = KeyData::decode(&mut reader)?; Ok(reader.finish(key_data.into())?) } @@ -393,14 +393,14 @@ impl PublicKey { /// /// This is a stub implementation that ignores the comment. #[cfg(not(feature = "alloc"))] - pub(crate) fn decode_comment(&mut self, reader: &mut impl Reader) -> Result<()> { + pub(crate) fn decode_comment(&mut self, reader: &mut Decoder) -> Result<()> { reader.drain_prefixed()?; Ok(()) } /// Decode comment (e.g. email address) #[cfg(feature = "alloc")] - pub(crate) fn decode_comment(&mut self, reader: &mut impl Reader) -> Result<()> { + pub(crate) fn decode_comment(&mut self, reader: &mut Decoder) -> Result<()> { self.comment = Comment::decode(reader)?; Ok(()) } diff --git a/ssh-key/src/public/dsa.rs b/ssh-key/src/public/dsa.rs index d34bb65a..4a5d3d30 100644 --- a/ssh-key/src/public/dsa.rs +++ b/ssh-key/src/public/dsa.rs @@ -2,7 +2,7 @@ use crate::{Error, Mpint, Result}; use core::hash::{Hash, Hasher}; -use encoding::{CheckedSum, Decode, Encode, Reader, Writer}; +use encoding::{CheckedSum, Decode, Decoder, Encode, Reader, Writer}; #[cfg(feature = "dsa")] use encoding::Uint; @@ -74,7 +74,7 @@ impl DsaPublicKey { impl Decode for DsaPublicKey { type Error = Error; - fn decode(reader: &mut impl Reader) -> Result { + fn decode(reader: &mut Decoder) -> Result { let p = Mpint::decode(reader)?; let q = Mpint::decode(reader)?; let g = Mpint::decode(reader)?; diff --git a/ssh-key/src/public/ecdsa.rs b/ssh-key/src/public/ecdsa.rs index f79bf1d6..0e67d449 100644 --- a/ssh-key/src/public/ecdsa.rs +++ b/ssh-key/src/public/ecdsa.rs @@ -2,7 +2,7 @@ use crate::{Algorithm, EcdsaCurve, Error, Result}; use core::fmt; -use encoding::{CheckedSum, Decode, Encode, Reader, Writer}; +use encoding::{CheckedSum, Decode, Decoder, Encode, Reader, Writer}; use sec1::consts::{U32, U48, U66}; /// ECDSA/NIST P-256 public key. @@ -107,7 +107,7 @@ impl AsRef<[u8]> for EcdsaPublicKey { impl Decode for EcdsaPublicKey { type Error = Error; - fn decode(reader: &mut impl Reader) -> Result { + fn decode(reader: &mut Decoder) -> Result { let curve = EcdsaCurve::decode(reader)?; let mut buf = [0u8; Self::MAX_SIZE]; diff --git a/ssh-key/src/public/ed25519.rs b/ssh-key/src/public/ed25519.rs index f09f7e72..34cabab7 100644 --- a/ssh-key/src/public/ed25519.rs +++ b/ssh-key/src/public/ed25519.rs @@ -4,7 +4,7 @@ use crate::{Error, Result}; use core::fmt; -use encoding::{CheckedSum, Decode, Encode, Reader, Writer}; +use encoding::{CheckedSum, Decode, Decoder, Encode, Reader, Writer}; /// Ed25519 public key. /// @@ -36,7 +36,7 @@ impl AsRef<[u8; Self::BYTE_SIZE]> for Ed25519PublicKey { impl Decode for Ed25519PublicKey { type Error = Error; - fn decode(reader: &mut impl Reader) -> Result { + fn decode(reader: &mut Decoder) -> Result { let mut bytes = [0u8; Self::BYTE_SIZE]; reader.read_prefixed(|reader| reader.read(&mut bytes))?; Ok(Self(bytes)) diff --git a/ssh-key/src/public/key_data.rs b/ssh-key/src/public/key_data.rs index 88ce6d7f..6e0c76c7 100644 --- a/ssh-key/src/public/key_data.rs +++ b/ssh-key/src/public/key_data.rs @@ -2,7 +2,7 @@ use super::{Ed25519PublicKey, SkEd25519}; use crate::{Algorithm, Error, Fingerprint, HashAlg, Result}; -use encoding::{CheckedSum, Decode, Encode, Reader, Writer}; +use encoding::{CheckedSum, Decode, Decoder, Encode, Reader, Writer}; #[cfg(feature = "alloc")] use { @@ -234,7 +234,7 @@ impl KeyData { /// - Returns [`Error::AlgorithmUnknown`] if the provided `algorithm` is unknown or unsupported /// by this library. /// - Returns [`Error::Encoding`] in the event of an encoding error. - pub fn decode_as(reader: &mut impl Reader, algorithm: Algorithm) -> Result { + pub fn decode_as(reader: &mut Decoder, algorithm: Algorithm) -> Result { match algorithm { #[cfg(feature = "alloc")] Algorithm::Dsa => DsaPublicKey::decode(reader).map(Self::Dsa), @@ -263,7 +263,10 @@ impl KeyData { /// # Errors /// Propagates errors from [`Certificate::decode_as`]. #[cfg(feature = "alloc")] - pub fn decode_as_certificate(reader: &mut impl Reader, algorithm: Algorithm) -> Result { + pub fn decode_as_certificate( + reader: &mut Decoder, + algorithm: Algorithm, + ) -> Result { Certificate::decode_as(reader, algorithm).map(|cert| Self::Certificate(Box::new(cert))) } @@ -312,7 +315,7 @@ impl KeyData { impl Decode for KeyData { type Error = Error; - fn decode(reader: &mut impl Reader) -> Result { + fn decode(reader: &mut Decoder) -> Result { let algorithm = Algorithm::decode(reader)?; #[cfg(feature = "alloc")] if let Algorithm::Other(name) = &algorithm { diff --git a/ssh-key/src/public/opaque.rs b/ssh-key/src/public/opaque.rs index 4cd58cf0..4336af8d 100644 --- a/ssh-key/src/public/opaque.rs +++ b/ssh-key/src/public/opaque.rs @@ -10,7 +10,7 @@ use crate::{Algorithm, Error, Result}; use alloc::vec::Vec; -use encoding::{Decode, Encode, Reader, Writer}; +use encoding::{Decode, Decoder, Encode, Reader, Writer}; /// An opaque public key with a custom algorithm name. /// @@ -41,7 +41,10 @@ impl OpaquePublicKey { } /// Decode [`OpaquePublicKey`] for the specified algorithm. - pub(super) fn decode_as(reader: &mut impl Reader, algorithm: Algorithm) -> Result { + pub(super) fn decode_as( + reader: &mut Decoder, + algorithm: Algorithm, + ) -> Result { Ok(Self { algorithm, key: OpaquePublicKeyBytes::decode(reader)?, @@ -75,7 +78,7 @@ pub struct OpaquePublicKeyBytes(Vec); impl Decode for OpaquePublicKeyBytes { type Error = Error; - fn decode(reader: &mut impl Reader) -> Result { + fn decode(reader: &mut Decoder) -> Result { Ok(Self(Vec::decode(reader)?)) } } diff --git a/ssh-key/src/public/rsa.rs b/ssh-key/src/public/rsa.rs index 9c854d4d..d1444d90 100644 --- a/ssh-key/src/public/rsa.rs +++ b/ssh-key/src/public/rsa.rs @@ -2,7 +2,7 @@ use crate::{Error, Mpint, Result}; use core::hash::{Hash, Hasher}; -use encoding::{CheckedSum, Decode, Encode, Reader, Writer}; +use encoding::{CheckedSum, Decode, Decoder, Encode, Reader, Writer}; #[cfg(feature = "rsa")] use { @@ -73,7 +73,7 @@ impl RsaPublicKey { impl Decode for RsaPublicKey { type Error = Error; - fn decode(reader: &mut impl Reader) -> Result { + fn decode(reader: &mut Decoder) -> Result { let e = Mpint::decode(reader)?; let n = Mpint::decode(reader)?; Self::new(e, n) diff --git a/ssh-key/src/public/sk.rs b/ssh-key/src/public/sk.rs index c1ca4fb8..04a34a74 100644 --- a/ssh-key/src/public/sk.rs +++ b/ssh-key/src/public/sk.rs @@ -4,7 +4,7 @@ use super::Ed25519PublicKey; use crate::{Error, Result}; -use encoding::{CheckedSum, Decode, Encode, Reader, Writer}; +use encoding::{CheckedSum, Decode, Decoder, Encode, Reader, Writer}; #[cfg(feature = "alloc")] use alloc::{borrow::ToOwned, string::String}; @@ -64,7 +64,7 @@ impl SkEcdsaSha2NistP256 { impl Decode for SkEcdsaSha2NistP256 { type Error = Error; - fn decode(reader: &mut impl Reader) -> Result { + fn decode(reader: &mut Decoder) -> Result { if EcdsaCurve::decode(reader)? != EcdsaCurve::NistP256 { return Err(Error::Crypto); } @@ -169,7 +169,7 @@ impl SkEd25519 { impl Decode for SkEd25519 { type Error = Error; - fn decode(reader: &mut impl Reader) -> Result { + fn decode(reader: &mut Decoder) -> Result { let public_key = Ed25519PublicKey::decode(reader)?; // application string (e.g. `ssh:`) diff --git a/ssh-key/src/signature.rs b/ssh-key/src/signature.rs index 98e6244a..72ba0537 100644 --- a/ssh-key/src/signature.rs +++ b/ssh-key/src/signature.rs @@ -3,7 +3,7 @@ use crate::{Algorithm, EcdsaCurve, Error, Mpint, PrivateKey, PublicKey, Result, private, public}; use alloc::vec::Vec; use core::fmt; -use encoding::{CheckedSum, Decode, Encode, Reader, Writer}; +use encoding::{CheckedSum, Decode, Decoder, Encode, Reader, Writer}; use signature::{SignatureEncoding, Signer, Verifier}; #[cfg(feature = "ed25519")] @@ -149,11 +149,11 @@ impl Signature { /// Returns Ok() if data holds an ecdsa signature with components of appropriate size /// according to curve. -fn ecdsa_sig_size(mut data: &[u8], curve: EcdsaCurve, sk_trailer: bool) -> Result<()> { - let reader = &mut data; +fn ecdsa_sig_size(data: &[u8], curve: EcdsaCurve, sk_trailer: bool) -> Result<()> { + let mut reader = Decoder::from(data); for _ in 0..2 { - let component = Mpint::decode(reader)?; + let component = Mpint::decode(&mut reader)?; let bytes = component.as_positive_bytes().ok_or(Error::FormatEncoding)?; if bytes.len() > curve.field_size() { return Err(encoding::Error::Length.into()); @@ -176,7 +176,7 @@ impl AsRef<[u8]> for Signature { impl Decode for Signature { type Error = Error; - fn decode(reader: &mut impl Reader) -> Result { + fn decode(reader: &mut Decoder) -> Result { let algorithm = Algorithm::decode(reader)?; let mut data = Vec::decode(reader)?; @@ -234,8 +234,8 @@ impl SignatureEncoding for Signature { impl TryFrom<&[u8]> for Signature { type Error = Error; - fn try_from(mut bytes: &[u8]) -> Result { - Self::decode(&mut bytes) + fn try_from(bytes: &[u8]) -> Result { + Self::decode(&mut bytes.into()) } } @@ -585,8 +585,8 @@ impl TryFrom<&Signature> for p256::ecdsa::Signature { } } #[cfg(feature = "p256")] -fn p256_signature_from_openssh_bytes(mut signature_bytes: &[u8]) -> Result { - let reader = &mut signature_bytes; +fn p256_signature_from_openssh_bytes(signature_bytes: &[u8]) -> Result { + let reader = &mut Decoder::from(signature_bytes); let r = Mpint::decode(reader)?; let s = Mpint::decode(reader)?; @@ -608,7 +608,7 @@ impl TryFrom<&Signature> for p384::ecdsa::Signature { Algorithm::Ecdsa { curve: EcdsaCurve::NistP384, } => { - let reader = &mut signature.as_bytes(); + let reader = &mut Decoder::from(signature.as_bytes()); let r = Mpint::decode(reader)?; let s = Mpint::decode(reader)?; @@ -634,7 +634,7 @@ impl TryFrom<&Signature> for p521::ecdsa::Signature { Algorithm::Ecdsa { curve: EcdsaCurve::NistP521, } => { - let reader = &mut signature.as_bytes(); + let reader = &mut Decoder::from(signature.as_bytes()); let r = Mpint::decode(reader)?; let s = Mpint::decode(reader)?; @@ -962,7 +962,8 @@ mod tests { let keypair = hex!( "0000008100c161fb30c9e4e3602c8510f93bbd48d813da845dfcc75f3696e440cd019d609809608cd592b8430db901d7b43740740045b547c60fb035d69f9c64d3dfbfb13bb3edd8ccfdd44705739a639eb70f4aed16b0b8355de1b21cd9d442eff250895573a8af7ce2fb71fb062e887482dab5c68139845fb8afafc5f3819dc782920d510000001500f3fb6762430332bd5950edc5cd1ae6f17b88514f0000008061ef1394d864905e8efec3b610b7288a6522893af2a475f910796e0de47c8b065d365e942e80e471d1e6d4abdee1d3d3ede7103c6996432f1a9f9a671a31388672d63555077911fc69e641a997087260d22cdbf4965aa64bb382204f88987890ec225a5a7723a977dc1ecc5e04cf678f994692b20470adbf697489f800817b920000008100a9a6f1b65fc724d65df7441908b34af66489a4a3872cbbba25ea1bcfc83f25c4af1a62e339eefc814907cfaf0cb6d2d16996212a32a27a63013f01c57d0630f0be16c8c69d16fc25438e613b904b98aeb3e7c356fa8e75ee1d474c9f82f1280c5a6c18e9e607fcf7586eefb75ea9399da893b807375ac1396fd586bf277161980000001500ced95f1c7bbb39be4987837ad1f71be31bb7b0d9" ); - let keypair = DsaKeypair::decode(&mut &keypair[..]).expect("properly encoded DSA keypair"); + let keypair = DsaKeypair::decode(&mut keypair.as_slice().into()) + .expect("properly encoded DSA keypair"); let data = hex!( "F0000040713d5f6fffe0000e6421ab0b3a69774d3da02fd72b107d6b32b6dad7c1660bbf507bf3eac3304cc5058f7e6f81b04239b8471459b1f3b387e2626f7eb8f6bcdd3200000006626c616465320000000e7373682d636f6e6e656374696f6e00000009686f73746261736564000000077373682d647373000001b2000000077373682d6473730000008100c161fb30c9e4e3602c8510f93bbd48d813da845dfcc75f3696e440cd019d609809608cd592b8430db901d7b43740740045b547c60fb035d69f9c64d3dfbfb13bb3edd8ccfdd44705739a639eb70f4aed16b0b8355de1b21cd9d442eff250895573a8af7ce2fb71fb062e887482dab5c68139845fb8afafc5f3819dc782920d510000001500f3fb6762430332bd5950edc5cd1ae6f17b88514f0000008061ef1394d864905e8efec3b610b7288a6522893af2a475f910796e0de47c8b065d365e942e80e471d1e6d4abdee1d3d3ede7103c6996432f1a9f9a671a31388672d63555077911fc69e641a997087260d22cdbf4965aa64bb382204f88987890ec225a5a7723a977dc1ecc5e04cf678f994692b20470adbf697489f800817b920000008100a9a6f1b65fc724d65df7441908b34af66489a4a3872cbbba25ea1bcfc83f25c4af1a62e339eefc814907cfaf0cb6d2d16996212a32a27a63013f01c57d0630f0be16c8c69d16fc25438e613b904b98aeb3e7c356fa8e75ee1d474c9f82f1280c5a6c18e9e607fcf7586eefb75ea9399da893b807375ac1396fd586bf2771619800000015746f6d61746f7373682e6c6f63616c646f6d61696e00000009746f6d61746f737368" @@ -1032,7 +1033,7 @@ mod tests { "000000077373682d727361000001809485247d72bf853272c86dd8c1c3fa0d2bebcdea9d91a376525a4bcc4a9ca2b19d31af48cfc07da086b244c65b37f3eb8fcab9661ccf777ed2f45404dd602b405526e19323f065b44d19f1bbda3eaf87b922b01049fcd8b82f08ffab6582e8427b0af3305f32961816d499d7b4925c1293b2d658dc6ca7cfb2d47c203c7d9512c0ee33e3d74f362d339a112fc94a74e8f388fc7fd1e9b95c7dd94e62ff16c9463476b7cf0e42af0f17fd2b9e325a50fc40ffd02b4a39e692727186b47c8ce9d7037de7e94615966df462238e214e7440bedabc5fbf79cfa93b96be5f27268da7c1ae2246bcabcc18a0d2c507be8727d04e41ed38686e5c455c159ee371f477668e89720191a72fbdb4eef86f1aa5c3596cefad12b20b1a1220accf6145f8583d7559751b2d0445e2e8a8fda85bf30f24b446ac6d0b943f7c519e5a021b1468cf120ed565d95ed8ddf022f97537ec5491226198ec58dd96c6bd218ddb237aa80785ceafa7722f1d2ba3e39dce2a9fdb0038f4124e2aa27d28eef927d87c8708f6" ); - let decoded = Signature::decode(&mut &encoded[..]).unwrap(); + let decoded = Signature::decode(&mut encoded.as_slice().into()).unwrap(); assert!(Verifier::verify(key.public(), EXAMPLE_MSG, &decoded).is_ok()); } diff --git a/ssh-key/src/sshsig.rs b/ssh-key/src/sshsig.rs index 4cdf59b8..325168cd 100644 --- a/ssh-key/src/sshsig.rs +++ b/ssh-key/src/sshsig.rs @@ -4,7 +4,7 @@ use crate::{Algorithm, AssociatedHashAlg, Error, HashAlg, Result, Signature, Sig use alloc::{string::String, string::ToString, vec::Vec}; use core::str::FromStr; use encoding::{ - CheckedSum, Decode, DecodePem, Encode, EncodePem, Reader, Writer, + CheckedSum, Decode, DecodePem, Decoder, Encode, EncodePem, Reader, Writer, pem::{LineEnding, PemLabel}, }; use sha2::Digest; @@ -320,7 +320,7 @@ impl SshSig { impl Decode for SshSig { type Error = Error; - fn decode(reader: &mut impl Reader) -> Result { + fn decode(reader: &mut Decoder) -> Result { let mut magic_preamble = [0u8; Self::MAGIC_PREAMBLE.len()]; reader.read(&mut magic_preamble)?; @@ -452,7 +452,7 @@ impl<'de> Deserialize<'de> for SshSig { string.parse::().map_err(de::Error::custom) } else { let bytes = Vec::::deserialize(deserializer)?; - Self::decode(&mut bytes.as_slice()).map_err(de::Error::custom) + Self::decode(&mut bytes.as_slice().into()).map_err(de::Error::custom) } } } diff --git a/ssh-key/tests/certificate.rs b/ssh-key/tests/certificate.rs index f83c164f..c6f3d797 100644 --- a/ssh-key/tests/certificate.rs +++ b/ssh-key/tests/certificate.rs @@ -4,7 +4,7 @@ #![allow(clippy::unwrap_used, reason = "tests")] use core::str::FromStr; -use encoding::{Base64Reader, Decode, Encode, Reader}; +use encoding::{Base64Reader, Decode, Decoder, Encode, Reader}; use hex_literal::hex; use ssh_key::{Algorithm, Certificate, public::KeyData}; @@ -270,7 +270,7 @@ fn decode_keydata(certificate_str: &str) { let cert_encoded = cert.to_bytes().unwrap(); // Decode bytes-on-wire to KeyData - let mut reader = &cert_encoded[..]; + let mut reader = Decoder::from(cert_encoded.as_slice()); let key_data = KeyData::decode(&mut reader).unwrap(); // Convert KeyData to Certificate and override comment from diff --git a/ssh-key/tests/sshsig.rs b/ssh-key/tests/sshsig.rs index 2b0ae637..6e37fbd5 100644 --- a/ssh-key/tests/sshsig.rs +++ b/ssh-key/tests/sshsig.rs @@ -190,7 +190,8 @@ fn sign_dsa() { #[test] #[cfg(feature = "p256")] fn verify_sk_ecdsa_openssh_wire_format() { - let signature = Signature::decode(&mut SK_ECDSA_SIGNATURE_OPENSSH_WIRE.as_ref()).unwrap(); + let signature = + Signature::decode(&mut SK_ECDSA_SIGNATURE_OPENSSH_WIRE.as_slice().into()).unwrap(); let verifying_key = SK_ECDSA_P256_PUBLIC_KEY.parse::().unwrap(); verifying_key .key_data() @@ -206,7 +207,7 @@ fn verify_sk_ecdsa_openssh_wire_format() { #[test] #[cfg(feature = "dsa")] fn verify_dsa_openssh_wire_format() { - let signature = Signature::decode(&mut DSA_SIGNATURE_OPENSSH_WIRE.as_ref()).unwrap(); + let signature = Signature::decode(&mut DSA_SIGNATURE_OPENSSH_WIRE.as_slice().into()).unwrap(); let verifying_key = DSA_PUBLIC_KEY.parse::().unwrap(); verifying_key .key_data() @@ -233,7 +234,8 @@ fn sign_ecdsa_p256() { #[test] #[cfg(feature = "p256")] fn verify_ecdsa_p256_openssh_wire_format() { - let signature = Signature::decode(&mut ECDSA_P256_SIGNATURE_OPENSSH_WIRE.as_ref()).unwrap(); + let signature = + Signature::decode(&mut ECDSA_P256_SIGNATURE_OPENSSH_WIRE.as_slice().into()).unwrap(); let verifying_key = ECDSA_P256_PUBLIC_KEY.parse::().unwrap(); verifying_key .key_data() @@ -255,7 +257,8 @@ fn sign_ed25519() { #[test] #[cfg(feature = "ed25519")] fn verify_ed25519_openssh_wire_format() { - let signature = Signature::decode(&mut ED25519_SIGNATURE_OPENSSH_WIRE.as_ref()).unwrap(); + let signature = + Signature::decode(&mut ED25519_SIGNATURE_OPENSSH_WIRE.as_slice().into()).unwrap(); let verifying_key = ED25519_PUBLIC_KEY.parse::().unwrap(); verifying_key .key_data() @@ -282,7 +285,7 @@ fn sign_rsa() { #[test] #[cfg(feature = "rsa")] fn verify_rsa_openssh_wire_format() { - let signature = Signature::decode(&mut RSA_SIGNATURE_OPENSSH_WIRE.as_ref()).unwrap(); + let signature = Signature::decode(&mut RSA_SIGNATURE_OPENSSH_WIRE.as_slice().into()).unwrap(); let verifying_key = RSA_PUBLIC_KEY.parse::().unwrap(); verifying_key .key_data() diff --git a/ssh-protocol/Cargo.toml b/ssh-protocol/Cargo.toml index eb3930e2..b755fb4c 100644 --- a/ssh-protocol/Cargo.toml +++ b/ssh-protocol/Cargo.toml @@ -16,8 +16,8 @@ edition = "2024" rust-version = "1.87" [dependencies] -cipher = { package = "ssh-cipher", version = "0.3.0-rc.10", default-features = false } -encoding = { package = "ssh-encoding", version = "0.3", default-features = false } +cipher = { package = "ssh-cipher", version = "0.4.0-pre", default-features = false } +encoding = { package = "ssh-encoding", version = "0.4.0-pre", default-features = false } key = { package = "ssh-key", version = "0.7.0-rc.8", default-features = false } [features] diff --git a/ssh-protocol/src/lib.rs b/ssh-protocol/src/lib.rs index d201045d..f91ff7e7 100644 --- a/ssh-protocol/src/lib.rs +++ b/ssh-protocol/src/lib.rs @@ -7,7 +7,7 @@ )] pub use cipher::{self, Cipher}; -pub use encoding::{self, Decode, Encode, Reader, Writer}; +pub use encoding::{self, Decode, Decoder, Encode, Reader, Writer}; pub use key::{ self, Algorithm, Fingerprint, HashAlg, Kdf, KdfAlg, private::PrivateKey, public::PublicKey, };