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
2 changes: 2 additions & 0 deletions Cargo.lock

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

56 changes: 27 additions & 29 deletions pem-rfc7468/src/encoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,31 @@ use alloc::string::String;
#[cfg(feature = "std")]
use std::io;

/// Compute the length of a PEM encoded document which encapsulates a
/// Base64-encoded body of the given length.
///
/// The `base64_len` value does *NOT* include the trailing newline's length.
pub fn encapsulated_len(label: &str, line_ending: LineEnding, base64_len: usize) -> usize {
// TODO(tarcieri): use checked arithmetic
PRE_ENCAPSULATION_BOUNDARY.len()
+ label.as_bytes().len()
+ ENCAPSULATION_BOUNDARY_DELIMITER.len()
+ line_ending.len()
+ base64_len
+ line_ending.len()
+ POST_ENCAPSULATION_BOUNDARY.len()
+ label.as_bytes().len()
+ ENCAPSULATION_BOUNDARY_DELIMITER.len()
+ line_ending.len()
}

/// Get the length of a PEM encoded document with the given bytes and label.
pub fn encoded_len(label: &str, line_ending: LineEnding, input: &[u8]) -> usize {
let mut base64_len = Base64::encoded_len(input);
base64_len += (base64_len.saturating_sub(1) / BASE64_WRAP_WIDTH) * line_ending.len();
encapsulated_len(label, line_ending, base64_len)
}

/// Encode a PEM document according to RFC 7468's "Strict" grammar.
pub fn encode<'o>(
type_label: &str,
Expand All @@ -26,18 +51,6 @@ pub fn encode<'o>(
Ok(str::from_utf8(&buf[..encoded_len])?)
}

/// Get the length of a PEM encoded document with the given bytes and label.
pub fn encoded_len(label: &str, line_ending: LineEnding, input: &[u8]) -> usize {
// TODO(tarcieri): use checked arithmetic
let base64_len = input
.chunks((BASE64_WRAP_WIDTH * 3) / 4)
.fold(0, |acc, chunk| {
acc + Base64::encoded_len(chunk) + line_ending.len()
});

encoded_len_inner(label, line_ending, base64_len)
}

/// Encode a PEM document according to RFC 7468's "Strict" grammar, returning
/// the result as a [`String`].
#[cfg(feature = "alloc")]
Expand Down Expand Up @@ -153,10 +166,10 @@ impl<'l, 'o> Encoder<'l, 'o> {
part.copy_from_slice(boundary_part);
}

Ok(encoded_len_inner(
Ok(encapsulated_len(
self.type_label,
self.line_ending,
base64.len() + self.line_ending.len(),
base64.len(),
))
}
}
Expand All @@ -174,18 +187,3 @@ impl<'l, 'o> io::Write for Encoder<'l, 'o> {
Ok(())
}
}

/// Compute the length of a PEM encoded document with a Base64-encoded body of
/// the given length.
fn encoded_len_inner(label: &str, line_ending: LineEnding, base64_len: usize) -> usize {
// TODO(tarcieri): use checked arithmetic
PRE_ENCAPSULATION_BOUNDARY.len()
+ label.as_bytes().len()
+ ENCAPSULATION_BOUNDARY_DELIMITER.len()
+ line_ending.len()
+ base64_len
+ POST_ENCAPSULATION_BOUNDARY.len()
+ label.as_bytes().len()
+ ENCAPSULATION_BOUNDARY_DELIMITER.len()
+ line_ending.len()
}
9 changes: 9 additions & 0 deletions pem-rfc7468/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ pub enum Error {

/// Errors in the post-encapsulation boundary.
PostEncapsulationBoundary,

/// Unexpected PEM type label.
UnexpectedTypeLabel {
/// Type label that was expected.
expected: &'static str,
},
}

impl fmt::Display for Error {
Expand All @@ -53,6 +59,9 @@ impl fmt::Display for Error {
Error::PostEncapsulationBoundary => {
f.write_str("PEM error in post-encapsulation boundary")
}
Error::UnexpectedTypeLabel { expected } => {
write!(f, "unexpected PEM type label: expecting \"{}\"", expected)
}
}
}
}
Expand Down
13 changes: 12 additions & 1 deletion pem-rfc7468/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ mod grammar;

pub use crate::{
decoder::{decode, decode_label, Decoder},
encoder::{encode, encoded_len, Encoder},
encoder::{encapsulated_len, encode, encoded_len, Encoder},
error::{Error, Result},
};
pub use base64ct::LineEnding;
Expand Down Expand Up @@ -97,4 +97,15 @@ pub type Base64Encoder<'o> = base64ct::Encoder<'o, base64ct::Base64>;
pub trait PemLabel {
/// Expected PEM type label for a given document, e.g. `"PRIVATE KEY"`
const TYPE_LABEL: &'static str;

/// Validate that a given label matches the expected label.
fn validate_pem_label(actual: &str) -> Result<()> {
if Self::TYPE_LABEL == actual {
Ok(())
} else {
Err(Error::UnexpectedTypeLabel {
expected: Self::TYPE_LABEL,
})
}
}
}
4 changes: 3 additions & 1 deletion ssh-key/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,15 @@ zeroize = { version = "1", default-features = false }

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

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

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

Expand Down
100 changes: 54 additions & 46 deletions ssh-key/src/algorithm.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Algorithm support.

use crate::{
base64::{Decode, DecoderExt, Encode, EncoderExt},
base64::{Decode, DecoderExt, Encode, EncoderExt, StrField},
Error, Result,
};
use core::{fmt, str};
Expand Down Expand Up @@ -45,9 +45,6 @@ pub enum Algorithm {
}

impl Algorithm {
/// Maximum size of algorithms known to this crate in bytes.
const MAX_SIZE: usize = 20;

/// Decode algorithm from the given string identifier.
///
/// # Supported algorithms
Expand Down Expand Up @@ -102,21 +99,14 @@ impl Algorithm {
}
}

impl Decode for Algorithm {
fn decode(decoder: &mut impl DecoderExt) -> Result<Self> {
let mut buf = [0u8; Self::MAX_SIZE];
Self::new(decoder.decode_str(&mut buf)?)
impl AsRef<str> for Algorithm {
fn as_ref(&self) -> &str {
self.as_str()
}
}

impl Encode for Algorithm {
fn encoded_len(&self) -> Result<usize> {
Ok(4 + self.as_str().len())
}

fn encode(&self, encoder: &mut impl EncoderExt) -> Result<()> {
encoder.encode_str(self.as_str())
}
impl StrField for Algorithm {
type DecodeBuf = [u8; 20]; // max length: "ecdsa-sha2-nistpXXX"
}

impl fmt::Display for Algorithm {
Expand All @@ -142,9 +132,6 @@ pub enum CipherAlg {
}

impl CipherAlg {
/// Maximum size of cipher algorithms known to this crate in bytes.
const MAX_SIZE: usize = 4;

/// Decode cipher algorithm from the given `ciphername`.
///
/// # Supported ciphernames
Expand All @@ -164,13 +151,16 @@ impl CipherAlg {
}
}

impl Decode for CipherAlg {
fn decode(decoder: &mut impl DecoderExt) -> Result<Self> {
let mut buf = [0u8; Self::MAX_SIZE];
Self::new(decoder.decode_str(&mut buf)?)
impl AsRef<str> for CipherAlg {
fn as_ref(&self) -> &str {
self.as_str()
}
}

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

impl fmt::Display for CipherAlg {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
Expand Down Expand Up @@ -199,9 +189,6 @@ pub enum EcdsaCurve {
}

impl EcdsaCurve {
/// Maximum size of a curve identifier known to this crate in bytes.
const MAX_SIZE: usize = 8;

/// Decode elliptic curve from the given string identifier.
///
/// # Supported curves
Expand All @@ -228,21 +215,14 @@ impl EcdsaCurve {
}
}

impl Decode for EcdsaCurve {
fn decode(decoder: &mut impl DecoderExt) -> Result<Self> {
let mut buf = [0u8; Self::MAX_SIZE];
Self::new(decoder.decode_str(&mut buf)?)
impl AsRef<str> for EcdsaCurve {
fn as_ref(&self) -> &str {
self.as_str()
}
}

impl Encode for EcdsaCurve {
fn encoded_len(&self) -> Result<usize> {
Ok(4 + self.as_str().len())
}

fn encode(&self, encoder: &mut impl EncoderExt) -> Result<()> {
encoder.encode_str(self.as_str())
}
impl StrField for EcdsaCurve {
type DecodeBuf = [u8; 8]; // max length: 'nistpXXX'
}

impl fmt::Display for EcdsaCurve {
Expand All @@ -268,9 +248,6 @@ pub enum KdfAlg {
}

impl KdfAlg {
/// Maximum size of KDF algorithms known to this crate in bytes.
const MAX_SIZE: usize = 4;

/// Decode KDF algorithm from the given `kdfname`.
///
/// # Supported kdfnames
Expand All @@ -290,13 +267,16 @@ impl KdfAlg {
}
}

impl Decode for KdfAlg {
fn decode(decoder: &mut impl DecoderExt) -> Result<Self> {
let mut buf = [0u8; Self::MAX_SIZE];
Self::new(decoder.decode_str(&mut buf)?)
impl AsRef<str> for KdfAlg {
fn as_ref(&self) -> &str {
self.as_str()
}
}

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

impl fmt::Display for KdfAlg {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
Expand All @@ -313,7 +293,7 @@ impl str::FromStr for KdfAlg {

/// Key Derivation Function (KDF) options.
// TODO(tarcieri): stub!
#[derive(Clone, Debug, Eq, PartialEq)]
#[derive(Clone, Debug, Default, Eq, PartialEq)]
#[non_exhaustive]
pub struct KdfOptions {}

Expand All @@ -331,7 +311,35 @@ impl KdfOptions {

impl Decode for KdfOptions {
fn decode(decoder: &mut impl DecoderExt) -> Result<Self> {
// TODO(tarcieri): stub!
let mut buf = [0u8; 0];
Self::new(decoder.decode_str(&mut buf)?)
}
}

impl Encode for KdfOptions {
fn encoded_len(&self) -> Result<usize> {
Ok(4)
}

fn encode(&self, encoder: &mut impl EncoderExt) -> Result<()> {
// TODO(tarcieri): stub!
encoder.encode_str("")
}
}

impl fmt::Display for KdfOptions {
fn fmt(&self, _: &mut fmt::Formatter<'_>) -> fmt::Result {
// TODO(tarcieri): stub!
Ok(())
}
}

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

fn from_str(id: &str) -> Result<Self> {
// TODO(tarcieri): stub!
Self::new(id)
}
}
Loading