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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion ssh-key/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,18 @@ zeroize = { version = "1", default-features = false }

# optional dependencies
sec1 = { version = "=0.3.0-pre.1", optional = true, default-features = false, path = "../sec1" }
sha2 = { version = "0.10", optional = true, default-features = false }
subtle = { version = "2", optional = true, default-features = false }

[dev-dependencies]
hex-literal = "0.3"
tempfile = "3"

[features]
default = ["ecdsa", "std"]
default = ["ecdsa", "fingerprint", "std"]
alloc = ["zeroize/alloc"]
ecdsa = ["sec1"]
fingerprint = ["sha2"]
std = ["alloc", "base64ct/std"]

[package.metadata.docs.rs]
Expand Down
75 changes: 66 additions & 9 deletions ssh-key/src/algorithm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ use crate::{
};
use core::{fmt, str};

/// SHA-256 hash function.
const SHA256: &str = "SHA256";

/// ECDSA with SHA-256 + NIST P-256
const ECDSA_SHA2_P256: &str = "ecdsa-sha2-nistp256";

Expand All @@ -25,25 +28,25 @@ const SSH_ED25519: &str = "ssh-ed25519";
/// RSA
const SSH_RSA: &str = "ssh-rsa";

/// String-like fields.
/// String identifiers for cryptographic algorithms.
///
/// These fields receive a blanket impl of [`Decode`] and [`Encode`].
pub(crate) trait StrField: AsRef<str> + str::FromStr<Err = Error> {
/// Receives a blanket impl of [`Decode`] and [`Encode`].
pub(crate) trait AlgString: AsRef<str> + str::FromStr<Err = Error> {
/// Decoding buffer type.
///
/// This needs to be a byte array large enough to fit the largest
/// possible value of this type.
type DecodeBuf: AsMut<[u8]> + Default;
}

impl<T: StrField> Decode for T {
impl<T: AlgString> Decode for T {
fn decode(decoder: &mut impl Decoder) -> Result<Self> {
let mut buf = T::DecodeBuf::default();
decoder.decode_str(buf.as_mut())?.parse()
}
}

impl<T: StrField> Encode for T {
impl<T: AlgString> Encode for T {
fn encoded_len(&self) -> Result<usize> {
Ok(4 + self.as_ref().len())
}
Expand Down Expand Up @@ -134,7 +137,7 @@ impl AsRef<str> for Algorithm {
}
}

impl StrField for Algorithm {
impl AlgString for Algorithm {
type DecodeBuf = [u8; 20]; // max length: "ecdsa-sha2-nistpXXX"
}

Expand Down Expand Up @@ -186,7 +189,7 @@ impl AsRef<str> for CipherAlg {
}
}

impl StrField for CipherAlg {
impl AlgString for CipherAlg {
type DecodeBuf = [u8; 4]; // max length: 'none'
}

Expand Down Expand Up @@ -250,7 +253,7 @@ impl AsRef<str> for EcdsaCurve {
}
}

impl StrField for EcdsaCurve {
impl AlgString for EcdsaCurve {
type DecodeBuf = [u8; 8]; // max length: 'nistpXXX'
}

Expand All @@ -268,6 +271,60 @@ impl str::FromStr for EcdsaCurve {
}
}

/// Hashing algorithms a.k.a. digest functions.
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub enum HashAlg {
/// SHA-256
Sha256,
}

impl HashAlg {
/// Decode elliptic curve from the given string identifier.
///
/// # Supported hash algorithms
///
/// - `SHA256`
pub fn new(id: &str) -> Result<Self> {
match id {
SHA256 => Ok(HashAlg::Sha256),
_ => Err(Error::Algorithm),
}
}

/// Get the string identifier for this hash algorithm.
pub fn as_str(self) -> &'static str {
match self {
HashAlg::Sha256 => SHA256,
}
}
}

impl AsRef<str> for HashAlg {
fn as_ref(&self) -> &str {
self.as_str()
}
}

impl Default for HashAlg {
fn default() -> Self {
HashAlg::Sha256
}
}

impl fmt::Display for HashAlg {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}

impl str::FromStr for HashAlg {
type Err = Error;

fn from_str(id: &str) -> Result<Self> {
HashAlg::new(id)
}
}

/// Key Derivation Function (KDF) algorithms.
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
#[non_exhaustive]
Expand Down Expand Up @@ -302,7 +359,7 @@ impl AsRef<str> for KdfAlg {
}
}

impl StrField for KdfAlg {
impl AlgString for KdfAlg {
type DecodeBuf = [u8; 4]; // max length: 'none'
}

Expand Down
18 changes: 9 additions & 9 deletions ssh-key/src/decoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,15 @@ pub(crate) trait Decode: Sized {

/// Decoder extension trait.
pub(crate) trait Decoder {
/// Decode as much Base64 as is needed to exactly fill `out`.
/// Decode 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
/// - `Ok(bytes)` if the expected amount of data was read
/// - `Err(Error::Length)` if the exact amount of data couldn't be read
fn decode_base64<'o>(&mut self, out: &'o mut [u8]) -> Result<&'o [u8]>;
fn decode_raw<'o>(&mut self, out: &'o mut [u8]) -> Result<&'o [u8]>;

/// Get the length of the remaining data after Base64 decoding.
fn decoded_len(&self) -> usize;
Expand All @@ -43,7 +43,7 @@ pub(crate) trait Decoder {
#[cfg(feature = "ecdsa")]
fn decode_u8(&mut self) -> Result<u8> {
let mut buf = [0];
self.decode_base64(&mut buf)?;
self.decode_raw(&mut buf)?;
Ok(buf[0])
}

Expand All @@ -56,7 +56,7 @@ pub(crate) trait Decoder {
/// [RFC4251 § 5]: https://datatracker.ietf.org/doc/html/rfc4251#section-5
fn decode_u32(&mut self) -> Result<u32> {
let mut bytes = [0u8; 4];
self.decode_base64(&mut bytes)?;
self.decode_raw(&mut bytes)?;
Ok(u32::from_be_bytes(bytes))
}

Expand Down Expand Up @@ -84,7 +84,7 @@ pub(crate) trait Decoder {
fn decode_byte_slice<'o>(&mut self, out: &'o mut [u8]) -> Result<&'o [u8]> {
let len = self.decode_usize()?;
let result = out.get_mut(..len).ok_or(Error::Length)?;
self.decode_base64(result)?;
self.decode_raw(result)?;
Ok(result)
}

Expand All @@ -99,7 +99,7 @@ pub(crate) trait Decoder {
fn decode_byte_vec(&mut self) -> Result<Vec<u8>> {
let len = self.decode_usize()?;
let mut result = vec![0u8; len];
self.decode_base64(&mut result)?;
self.decode_raw(&mut result)?;
Ok(result)
}

Expand Down Expand Up @@ -134,7 +134,7 @@ pub(crate) trait Decoder {
fn drain(&mut self, n_bytes: usize) -> Result<()> {
let mut byte = [0];
for _ in 0..n_bytes {
self.decode_base64(&mut byte)?;
self.decode_raw(&mut byte)?;
}
Ok(())
}
Expand All @@ -147,7 +147,7 @@ pub(crate) trait Decoder {
}

impl Decoder for Base64Decoder<'_> {
fn decode_base64<'o>(&mut self, out: &'o mut [u8]) -> Result<&'o [u8]> {
fn decode_raw<'o>(&mut self, out: &'o mut [u8]) -> Result<&'o [u8]> {
Ok(self.decode(out)?)
}

Expand All @@ -161,7 +161,7 @@ impl Decoder for Base64Decoder<'_> {
}

impl Decoder for pem::Decoder<'_> {
fn decode_base64<'o>(&mut self, out: &'o mut [u8]) -> Result<&'o [u8]> {
fn decode_raw<'o>(&mut self, out: &'o mut [u8]) -> Result<&'o [u8]> {
Ok(self.decode(out)?)
}

Expand Down
23 changes: 17 additions & 6 deletions ssh-key/src/encoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ use crate::Result;
use core::str;
use pem_rfc7468 as pem;

#[cfg(feature = "fingerprint")]
use sha2::{Digest, Sha256};

/// Get the estimated length of data when encoded as Base64.
///
/// This is an upper bound where the actual length might be slightly shorter.
Expand All @@ -28,11 +31,11 @@ pub(crate) trait Encode: Sized {

/// Encoder extension trait.
pub(crate) trait Encoder {
/// Encode the given byte slice as Base64.
/// Encode the given byte slice containing raw unstructured data.
///
/// This is the base encoding method on which the rest of the trait is
/// implemented in terms of.
fn encode_base64(&mut self, bytes: &[u8]) -> Result<()>;
fn encode_raw(&mut self, bytes: &[u8]) -> Result<()>;

/// Encode a `uint32` as described in [RFC4251 § 5]:
///
Expand All @@ -42,7 +45,7 @@ pub(crate) trait Encoder {
///
/// [RFC4251 § 5]: https://datatracker.ietf.org/doc/html/rfc4251#section-5
fn encode_u32(&mut self, num: u32) -> Result<()> {
self.encode_base64(&num.to_be_bytes())
self.encode_raw(&num.to_be_bytes())
}

/// Encode a `usize` as a `uint32` as described in [RFC4251 § 5].
Expand All @@ -64,7 +67,7 @@ pub(crate) trait Encoder {
/// [RFC4251 § 5]: https://datatracker.ietf.org/doc/html/rfc4251#section-5
fn encode_byte_slice(&mut self, bytes: &[u8]) -> Result<()> {
self.encode_usize(bytes.len())?;
self.encode_base64(bytes)
self.encode_raw(bytes)
}

/// Encode a `string` as described in [RFC4251 § 5]:
Expand All @@ -90,13 +93,21 @@ pub(crate) trait Encoder {
}

impl Encoder for Base64Encoder<'_> {
fn encode_base64(&mut self, bytes: &[u8]) -> Result<()> {
fn encode_raw(&mut self, bytes: &[u8]) -> Result<()> {
Ok(self.encode(bytes)?)
}
}

impl Encoder for pem::Encoder<'_, '_> {
fn encode_base64(&mut self, bytes: &[u8]) -> Result<()> {
fn encode_raw(&mut self, bytes: &[u8]) -> Result<()> {
Ok(self.encode(bytes)?)
}
}

#[cfg(feature = "fingerprint")]
impl Encoder for Sha256 {
fn encode_raw(&mut self, bytes: &[u8]) -> Result<()> {
self.update(bytes);
Ok(())
}
}
Loading