Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions ssh-encoding/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
[package]
name = "ssh-encoding"
version = "0.3.0-rc.8"
description = """
Pure Rust implementation of SSH data type decoders/encoders as described
in RFC4251
"""
description = "Pure Rust implementation of SSH data type decoders/encoders as described in RFC4251"
authors = ["RustCrypto Developers"]
license = "Apache-2.0 OR MIT"
homepage = "https://github.com/RustCrypto/SSH/tree/master/ssh-encoding"
Expand Down Expand Up @@ -37,6 +34,8 @@ bytes = ["alloc", "dep:bytes"]
pem = ["base64", "dep:pem-rfc7468"]
derive = ["ssh-derive"]

[lints]
workspace = true

[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
2 changes: 1 addition & 1 deletion ssh-encoding/LICENSE-MIT
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Copyright (c) 2021-2024 The RustCrypto Project Developers
Copyright (c) 2021-2026 The RustCrypto Project Developers

Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
Expand Down
7 changes: 0 additions & 7 deletions ssh-encoding/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,6 @@
Pure Rust implementation of SSH data type decoders/encoders as described
in [RFC4251].

## Minimum Supported Rust Version

This crate requires **Rust 1.85** at a minimum.

We may change the MSRV in the future, but it will be accompanied by a minor
version bump.

## License

Licensed under either of:
Expand Down
14 changes: 11 additions & 3 deletions ssh-encoding/src/base64/reader.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! Base64 reader support (constant-time).

use crate::{Decode, Error, Reader, Result};
use core::fmt::{self, Debug};

/// Inner constant-time Base64 reader type from the `base64ct` crate.
type Inner<'i> = base64ct::Decoder<'i, base64ct::Base64>;
Expand All @@ -18,9 +19,8 @@ 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.
/// # Errors
/// Returns [`Error::Base64`] if the `input` buffer is empty.
pub fn new(input: &'i [u8]) -> Result<Self> {
let inner = Inner::new(input)?;
let remaining_len = inner.remaining_len();
Expand All @@ -32,4 +32,12 @@ impl<'i> Base64Reader<'i> {
}
}

impl Debug for Base64Reader<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Base64Reader")
.field("remaining_len", &self.remaining_len)
.finish_non_exhaustive()
}
}

impl_reader_for_newtype!(Base64Reader<'_>);
16 changes: 16 additions & 0 deletions ssh-encoding/src/base64/writer.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
//! Base64 writer support (constant-time).

use crate::{Result, Writer};
use core::fmt::{self, Debug};

#[cfg(doc)]
use crate::Error;

/// Inner constant-time Base64 reader type from the `base64ct` crate.
type Inner<'o> = base64ct::Encoder<'o, base64ct::Base64>;
Expand All @@ -14,18 +18,30 @@ 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.
///
/// # Errors
/// Returns [`Error::Base64`] if the `output` buffer is empty.
pub fn new(output: &'o mut [u8]) -> Result<Self> {
Ok(Self {
inner: Inner::new(output)?,
})
}

/// Finish encoding data, returning the resulting Base64 as a `str`.
///
/// # Errors
/// Returns [`Error::Base64`] if there is insufficient space in the output buffer.
pub fn finish(self) -> Result<&'o str> {
Ok(self.inner.finish()?)
}
}

impl Debug for Base64Writer<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Base64Writer").finish_non_exhaustive()
}
}

impl Writer for Base64Writer<'_> {
fn write(&mut self, bytes: &[u8]) -> Result<()> {
Ok(self.inner.encode(bytes)?)
Expand Down
1 change: 1 addition & 0 deletions ssh-encoding/src/checked.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::{Error, Result};
pub trait CheckedSum<A>: Sized {
/// Iterate over the values of this type, computing a checked sum.
///
/// # Errors
/// Returns [`Error::Length`] on overflow.
fn checked_sum(self) -> Result<A>;
}
Expand Down
3 changes: 3 additions & 0 deletions ssh-encoding/src/decode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ pub trait Decode: Sized {
type Error: From<Error>;

/// Attempt to decode a value of this type using the provided [`Reader`].
///
/// # Errors
/// Returns errors specific to the concrete implementation of this trait.
fn decode(reader: &mut impl Reader) -> core::result::Result<Self, Self::Error>;
}

Expand Down
24 changes: 20 additions & 4 deletions ssh-encoding/src/encode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,25 +17,38 @@ use bytes::{Bytes, BytesMut};
/// This trait describes how to encode a given type.
pub trait Encode {
/// Get the length of this type encoded in bytes, prior to Base64 encoding.
///
/// # Errors
/// Returns errors specific to the concrete implementation of this trait.
fn encoded_len(&self) -> Result<usize, Error>;

/// Encode this value using the provided [`Writer`].
///
/// # Errors
/// Returns errors specific to the concrete implementation of this trait.
fn encode(&self, writer: &mut impl Writer) -> Result<(), Error>;

/// Return the length of this type after encoding when prepended with a
/// `uint32` length prefix.
/// Return the length of this type after encoding when prepended with a `uint32` length prefix.
///
/// # Errors
/// Returns errors specific to the concrete implementation of this trait.
fn encoded_len_prefixed(&self) -> Result<usize, Error> {
[4, self.encoded_len()?].checked_sum()
}

/// Encode this value, first prepending a `uint32` length prefix
/// set to [`Encode::encoded_len`].
/// Encode this value, first prepending a `uint32` length prefix set to [`Encode::encoded_len`].
///
/// # Errors
/// Returns errors specific to the concrete implementation of this trait.
fn encode_prefixed(&self, writer: &mut impl Writer) -> Result<(), Error> {
self.encoded_len()?.encode(writer)?;
self.encode(writer)
}

/// Encode this value, returning a `Vec<u8>` containing the encoded message.
///
/// # Errors
/// Returns errors specific to the concrete implementation of this trait.
#[cfg(feature = "alloc")]
fn encode_vec(&self) -> Result<Vec<u8>, Error> {
let mut ret = Vec::with_capacity(self.encoded_len()?);
Expand All @@ -44,6 +57,9 @@ pub trait Encode {
}

/// Encode this value, returning a [`BytesMut`] containing the encoded message.
///
/// # Errors
/// Returns errors specific to the concrete implementation of this trait.
#[cfg(feature = "bytes")]
fn encode_bytes(&self) -> Result<BytesMut, Error> {
let mut ret = BytesMut::with_capacity(self.encoded_len()?);
Expand Down
2 changes: 2 additions & 0 deletions ssh-encoding/src/label.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ pub struct LabelError {
impl LabelError {
/// Create a new [`LabelError`] for the given invalid label.
#[cfg_attr(not(feature = "alloc"), allow(unused_variables))]
#[must_use]
pub fn new(label: &str) -> Self {
Self {
#[cfg(feature = "alloc")]
Expand All @@ -54,6 +55,7 @@ impl LabelError {

/// The invalid label string (if available).
#[inline]
#[must_use]
pub fn label(&self) -> &str {
#[cfg(not(feature = "alloc"))]
{
Expand Down
17 changes: 1 addition & 16 deletions ssh-encoding/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,10 @@
#![no_std]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![doc = include_str!("../README.md")]
#![doc(
html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg",
html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg"
)]
#![forbid(unsafe_code)]
#![warn(
clippy::alloc_instead_of_core,
clippy::arithmetic_side_effects,
clippy::mod_module_files,
clippy::panic,
clippy::panic_in_result_fn,
clippy::std_instead_of_alloc,
clippy::std_instead_of_core,
clippy::unwrap_used,
missing_docs,
rust_2018_idioms,
unused_lifetimes,
unused_qualifications
)]

//! ## Conventions used in this crate
//!
Expand Down
8 changes: 6 additions & 2 deletions ssh-encoding/src/mpint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ impl Mpint {
///
/// The input may begin with leading zeros, which will be stripped when converted to [`Mpint`]
/// encoding.
#[must_use]
pub fn from_positive_bytes(mut bytes: &[u8]) -> Self {
// Strip leading zeros
while bytes.first().copied() == Some(0) {
Expand Down Expand Up @@ -91,6 +92,7 @@ impl Mpint {
/// This slice will contain a leading zero if the value is positive but the
/// MSB is also set. Use [`Mpint::as_positive_bytes`] to ensure the number
/// is positive and strip the leading zero byte if it exists.
#[must_use]
pub fn as_bytes(&self) -> &[u8] {
&self.inner
}
Expand All @@ -100,6 +102,7 @@ impl Mpint {
/// # Returns
/// - `Some(bytes)` if the number is positive. The leading zero byte will be stripped.
/// - `None` if the value is negative
#[must_use]
pub fn as_positive_bytes(&self) -> Option<&[u8]> {
match self.as_bytes() {
[0x00, rest @ ..] => Some(rest),
Expand All @@ -109,6 +112,7 @@ impl Mpint {
}

/// Is this [`Mpint`] positive?
#[must_use]
pub fn is_positive(&self) -> bool {
self.as_positive_bytes().is_some()
}
Expand Down Expand Up @@ -258,7 +262,7 @@ mod tests {
#[test]
fn decode_0() {
let n = Mpint::from_bytes(b"").unwrap();
assert_eq!(b"", n.as_bytes())
assert_eq!(b"", n.as_bytes());
}

#[test]
Expand All @@ -278,7 +282,7 @@ mod tests {
let n = Mpint::from_bytes(&hex!("00 80")).unwrap();

// Leading zero stripped
assert_eq!(&hex!("80"), n.as_positive_bytes().unwrap())
assert_eq!(&hex!("80"), n.as_positive_bytes().unwrap());
}
#[test]
fn from_positive_bytes_strips_leading_zeroes() {
Expand Down
12 changes: 8 additions & 4 deletions ssh-encoding/src/pem/decode.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
use super::{PemLabel, reader::PemReader};
use crate::{Decode, Reader};
use crate::{Decode, Error, Reader};

/// Decoding trait for PEM documents.
///
/// This is an extension trait which is auto-impl'd for types which impl the
/// [`Decode`], [`PemLabel`], and [`Sized`] traits.
pub trait DecodePem: Decode + PemLabel + Sized {
/// Decode the provided PEM-encoded string, interpreting the Base64-encoded
/// body of the document using the [`Decode`] trait.
/// Decode the provided PEM-encoded string, interpreting the Base64-encoded body of the document
/// using the [`Decode`] trait.
///
/// # Errors
/// - Returns [`Error::Pem`] in the event of PEM decoding errors.
/// - Propagates errors returned from the [`Decode::decode`] method.
fn decode_pem(pem: impl AsRef<[u8]>) -> Result<Self, Self::Error>;
}

impl<T: Decode + PemLabel + Sized> DecodePem for T {
fn decode_pem(pem: impl AsRef<[u8]>) -> Result<Self, Self::Error> {
let mut reader = PemReader::new(pem.as_ref())?;
Self::validate_pem_label(reader.type_label()).map_err(crate::Error::from)?;
Self::validate_pem_label(reader.type_label()).map_err(Error::from)?;

let ret = Self::decode(&mut reader)?;
Ok(reader.finish(ret)?)
Expand Down
19 changes: 13 additions & 6 deletions ssh-encoding/src/pem/encode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,22 @@ use {super::LINE_WIDTH, alloc::string::String};

/// Encoding trait for PEM documents.
///
/// This is an extension trait which is auto-impl'd for types which impl the
/// [`Encode`] and [`PemLabel`] traits.
/// This is an extension trait which is auto-impl'd for types which impl the [`Encode`] and
/// [`PemLabel`] traits.
pub trait EncodePem: Encode + PemLabel {
/// Encode this type using the [`Encode`] trait, writing the resulting PEM
/// document into the provided `out` buffer.
/// Encode this type using the [`Encode`] trait, writing the resulting PEM document into the
/// provided `out` buffer.
///
/// # Errors
/// - Returns [`Error::Pem`] in the event of PEM encoding errors.
/// - Propagates errors returned from the [`Encode::encode`] method.
fn encode_pem<'o>(&self, line_ending: LineEnding, out: &'o mut [u8]) -> Result<&'o str, Error>;

/// Encode this type using the [`Encode`] trait, writing the resulting PEM
/// document to a returned [`String`].
/// Encode this type using the [`Encode`] trait, writing the resulting PEM document to a
/// returned [`String`].
///
/// # Errors
/// Propagates errors returned from [`EncodePem::encode_pem`].
#[cfg(feature = "alloc")]
fn encode_pem_string(&self, line_ending: LineEnding) -> Result<String, Error>;
}
Expand Down
6 changes: 3 additions & 3 deletions ssh-encoding/src/pem/reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::{Decode, Error, Reader, Result};
type Inner<'i> = pem_rfc7468::Decoder<'i>;

/// Constant-time PEM reader.
pub struct PemReader<'i> {
pub(crate) struct PemReader<'i> {
/// Inner PEM reader.
inner: Inner<'i>,

Expand All @@ -14,7 +14,7 @@ pub struct PemReader<'i> {

impl<'i> PemReader<'i> {
/// Create a new PEM reader which autodetects the line width of the input.
pub fn new(pem: &'i [u8]) -> Result<Self> {
pub(crate) fn new(pem: &'i [u8]) -> Result<Self> {
let inner = Inner::new_detect_wrap(pem)?;
let remaining_len = inner.remaining_len();

Expand All @@ -25,7 +25,7 @@ impl<'i> PemReader<'i> {
}

/// Get the PEM type label for the input document.
pub fn type_label(&self) -> &'i str {
pub(crate) fn type_label(&self) -> &'i str {
self.inner.type_label()
}
}
Expand Down
6 changes: 3 additions & 3 deletions ssh-encoding/src/pem/writer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::{Result, Writer};
type Inner<'o> = pem_rfc7468::Encoder<'static, 'o>;

/// Constant-time PEM writer.
pub struct PemWriter<'o> {
pub(crate) struct PemWriter<'o> {
inner: Inner<'o>,
}

Expand All @@ -14,7 +14,7 @@ impl<'o> PemWriter<'o> {
/// buffer.
///
/// Uses 70-character line wrapping to be equivalent to OpenSSH.
pub fn new(
pub(crate) fn new(
type_label: &'static str,
line_ending: LineEnding,
out: &'o mut [u8],
Expand All @@ -28,7 +28,7 @@ impl<'o> PemWriter<'o> {
///
/// On success, returns the total number of bytes written to the output
/// buffer.
pub fn finish(self) -> Result<usize> {
pub(crate) fn finish(self) -> Result<usize> {
Ok(self.inner.finish()?)
}
}
Expand Down
Loading
Loading