diff --git a/ssh-encoding/Cargo.toml b/ssh-encoding/Cargo.toml index 8d715209..05df652b 100644 --- a/ssh-encoding/Cargo.toml +++ b/ssh-encoding/Cargo.toml @@ -15,7 +15,7 @@ edition = "2021" rust-version = "1.60" [dependencies] -base64 = { package = "base64ct", version = "1.4", optional = true } +base64ct = { version = "1.4", optional = true } bytes = { version = "1", optional = true, default-features = false } pem = { package = "pem-rfc7468", version = "=1.0.0-pre.0", optional = true } sha2 = { version = "=0.11.0-pre.3", optional = true, default-features = false } @@ -24,9 +24,10 @@ sha2 = { version = "=0.11.0-pre.3", optional = true, default-features = false } hex-literal = "0.4.1" [features] -alloc = ["base64?/alloc", "pem?/alloc"] -std = ["alloc", "base64?/std", "pem?/std", "sha2?/std"] +alloc = ["base64ct?/alloc", "pem?/alloc"] +std = ["alloc", "base64ct?/std", "pem?/std", "sha2?/std"] +base64 = ["dep:base64ct"] bytes = ["alloc", "dep:bytes"] pem = ["base64", "dep:pem"] diff --git a/ssh-encoding/src/error.rs b/ssh-encoding/src/error.rs index d60fb9ce..76892f73 100644 --- a/ssh-encoding/src/error.rs +++ b/ssh-encoding/src/error.rs @@ -12,7 +12,7 @@ pub type Result = core::result::Result; pub enum Error { /// Base64-related errors. #[cfg(feature = "base64")] - Base64(base64::Error), + Base64(base64ct::Error), /// Character encoding-related errors. CharacterEncoding, @@ -82,15 +82,15 @@ impl From for Error { } #[cfg(feature = "base64")] -impl From for Error { - fn from(err: base64::Error) -> Error { +impl From for Error { + fn from(err: base64ct::Error) -> Error { Error::Base64(err) } } #[cfg(feature = "base64")] -impl From for Error { - fn from(_: base64::InvalidLengthError) -> Error { +impl From for Error { + fn from(_: base64ct::InvalidLengthError) -> Error { Error::Length } } diff --git a/ssh-encoding/src/lib.rs b/ssh-encoding/src/lib.rs index 08b7aa5f..91864d6e 100644 --- a/ssh-encoding/src/lib.rs +++ b/ssh-encoding/src/lib.rs @@ -43,8 +43,8 @@ pub use crate::{ #[cfg(feature = "base64")] pub use { - crate::{reader::Base64Reader, writer::Base64Writer}, - base64, + crate::{reader::base64::Base64Reader, writer::base64::Base64Writer}, + base64ct as base64, }; #[cfg(feature = "pem")] diff --git a/ssh-encoding/src/reader.rs b/ssh-encoding/src/reader.rs index a88450b9..180bba57 100644 --- a/ssh-encoding/src/reader.rs +++ b/ssh-encoding/src/reader.rs @@ -1,12 +1,11 @@ //! Reader trait and associated implementations. +#[cfg(feature = "base64")] +pub(crate) mod base64; + use crate::{decode::Decode, Error, Result}; use core::str; -/// Constant-time Base64 reader implementation. -#[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 trait Reader: Sized { @@ -145,17 +144,6 @@ impl Reader for &[u8] { } } -#[cfg(feature = "base64")] -impl Reader for Base64Reader<'_> { - fn read<'o>(&mut self, out: &'o mut [u8]) -> Result<&'o [u8]> { - Ok(self.decode(out)?) - } - - fn remaining_len(&self) -> usize { - self.remaining_len() - } -} - #[cfg(feature = "pem")] impl Reader for pem::Decoder<'_> { fn read<'o>(&mut self, out: &'o mut [u8]) -> Result<&'o [u8]> { diff --git a/ssh-encoding/src/reader/base64.rs b/ssh-encoding/src/reader/base64.rs new file mode 100644 index 00000000..e486b206 --- /dev/null +++ b/ssh-encoding/src/reader/base64.rs @@ -0,0 +1,36 @@ +//! Base64 reader support (constant-time). + +use super::Reader; +use crate::Result; + +/// Inner constant-time Base64 reader type from the `base64ct` crate. +type Inner<'i> = base64ct::Decoder<'i, base64ct::Base64>; + +/// Constant-time Base64 reader implementation. +pub struct Base64Reader<'i> { + inner: Inner<'i>, +} + +impl<'i> Base64Reader<'i> { + /// Create a new Base64 reader for a byte slice containing contiguous (non-newline-delimited) + /// Base64-encoded data. + /// + /// # Returns + /// - `Ok(reader)` on success. + /// - `Err(Error::Base64)` if the input buffer is empty. + pub fn new(input: &'i [u8]) -> Result { + Ok(Self { + inner: Inner::new(input)?, + }) + } +} + +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/writer.rs b/ssh-encoding/src/writer.rs index 177aaf10..c6094eee 100644 --- a/ssh-encoding/src/writer.rs +++ b/ssh-encoding/src/writer.rs @@ -1,5 +1,8 @@ //! Writer trait and associated implementations. +#[cfg(feature = "base64")] +pub(crate) mod base64; + use crate::Result; #[cfg(feature = "alloc")] @@ -8,10 +11,6 @@ use alloc::vec::Vec; #[cfg(feature = "sha2")] use sha2::{Digest, Sha256, Sha512}; -/// Constant-time Base64 writer implementation. -#[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 trait Writer: Sized { @@ -27,13 +26,6 @@ impl Writer for Vec { } } -#[cfg(feature = "base64")] -impl Writer for Base64Writer<'_> { - fn write(&mut self, bytes: &[u8]) -> Result<()> { - Ok(self.encode(bytes)?) - } -} - #[cfg(feature = "pem")] impl Writer for pem::Encoder<'_, '_> { fn write(&mut self, bytes: &[u8]) -> Result<()> { diff --git a/ssh-encoding/src/writer/base64.rs b/ssh-encoding/src/writer/base64.rs new file mode 100644 index 00000000..40be6e70 --- /dev/null +++ b/ssh-encoding/src/writer/base64.rs @@ -0,0 +1,34 @@ +//! Base64 writer support (constant-time). + +use super::Writer; +use crate::Result; + +/// Inner constant-time Base64 reader type from the `base64ct` crate. +type Inner<'o> = base64ct::Encoder<'o, base64ct::Base64>; + +/// Constant-time Base64 writer implementation. +pub struct Base64Writer<'o> { + inner: Inner<'o>, +} + +impl<'o> Base64Writer<'o> { + /// Create a new Base64 writer which writes output to the given byte slice. + /// + /// Output constructed using this method is not line-wrapped. + pub fn new(output: &'o mut [u8]) -> Result { + Ok(Self { + inner: Inner::new(output)?, + }) + } + + /// Finish encoding data, returning the resulting Base64 as a `str`. + pub fn finish(self) -> Result<&'o str> { + Ok(self.inner.finish()?) + } +} + +impl Writer for Base64Writer<'_> { + fn write(&mut self, bytes: &[u8]) -> Result<()> { + Ok(self.inner.encode(bytes)?) + } +}