Skip to content

Commit

Permalink
ssh-key: use pem-rfc7468 for private key PEM parser
Browse files Browse the repository at this point in the history
Now that `pem-rfc7468` supports a buffered Base64 decoder (#406), it's
possible to use it for parsing PEM-formatted OpenSSH private keys.

This commit switches to using `pem-rfc7468` for PEM parsing.
  • Loading branch information
tarcieri committed Feb 9, 2022
1 parent cd23518 commit 54689d5
Show file tree
Hide file tree
Showing 6 changed files with 41 additions and 100 deletions.
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.

1 change: 1 addition & 0 deletions ssh-key/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ rust-version = "1.57"

[dependencies]
base64ct = { version = "=1.4.0-pre.0", path = "../base64ct" }
pem-rfc7468 = { version = "0.4.0-pre.0", path = "../pem-rfc7468" }
zeroize = { version = "1", default-features = false }

# optional dependencies
Expand Down
14 changes: 6 additions & 8 deletions ssh-key/src/base64.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,6 @@ impl<'i> Decoder<'i> {
})
}

/// Create a new decoder for a byte slice containing Base64 which
/// line wraps at the given line length.
pub fn new_wrapped(input: &'i [u8], line_width: usize) -> Result<Self> {
Ok(Self {
inner: base64ct::Decoder::new_wrapped(input, line_width)?,
})
}

/// Decode as much Base64 as is needed to exactly fill `out`.
///
/// # Returns
Expand Down Expand Up @@ -146,6 +138,12 @@ impl<'i> Decoder<'i> {
}
}

impl<'i> From<base64ct::Decoder<'i, base64ct::Base64>> for Decoder<'i> {
fn from(decoder: base64ct::Decoder<'i, base64ct::Base64>) -> Decoder<'i> {
Decoder { inner: decoder }
}
}

/// Encoder trait.
pub(crate) trait Encode: Sized {
/// Get the length of this type encoded in bytes, prior to Base64 encoding.
Expand Down
6 changes: 6 additions & 0 deletions ssh-key/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,12 @@ impl From<core::str::Utf8Error> for Error {
}
}

impl From<pem_rfc7468::Error> for Error {
fn from(_: pem_rfc7468::Error) -> Error {
Error::Pem
}
}

#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
impl From<alloc::string::FromUtf8Error> for Error {
Expand Down
45 changes: 27 additions & 18 deletions ssh-key/src/private.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ mod dsa;
#[cfg(feature = "ecdsa")]
mod ecdsa;
mod ed25519;
mod openssh;
#[cfg(feature = "alloc")]
mod rsa;

Expand All @@ -27,10 +26,14 @@ use crate::{
public, Algorithm, CipherAlg, Error, KdfAlg, KdfOptions, Result,
};
use core::str::FromStr;
use pem_rfc7468::{self as pem, PemLabel};

#[cfg(feature = "alloc")]
use alloc::string::String;

/// Line width used by the PEM encoding of OpenSSH private keys
const PEM_LINE_WIDTH: usize = 70;

/// SSH private key.
#[derive(Clone, Debug)]
pub struct PrivateKey {
Expand Down Expand Up @@ -64,23 +67,25 @@ impl PrivateKey {
/// -----BEGIN OPENSSH PRIVATE KEY-----
/// ```
pub fn from_openssh(input: impl AsRef<[u8]>) -> Result<Self> {
let encapsulation = openssh::Encapsulation::decode(input.as_ref())?;
let mut decoder = base64::Decoder::new_wrapped(
encapsulation.base64_data,
openssh::Encapsulation::LINE_WIDTH,
)?;
let pem_decoder = pem::Decoder::new_wrapped(input.as_ref(), PEM_LINE_WIDTH)?;

if pem_decoder.type_label() != Self::TYPE_LABEL {
return Err(Error::Pem);
}

let mut base64_decoder = base64::Decoder::from(pem_decoder.into_base64_decoder());

let mut auth_magic = [0u8; Self::AUTH_MAGIC.len()];
decoder.decode_into(&mut auth_magic)?;
base64_decoder.decode_into(&mut auth_magic)?;

if auth_magic != Self::AUTH_MAGIC {
return Err(Error::FormatEncoding);
}

let cipher_alg = CipherAlg::decode(&mut decoder)?;
let kdf_alg = KdfAlg::decode(&mut decoder)?;
let kdf_options = KdfOptions::decode(&mut decoder)?;
let nkeys = decoder.decode_u32()? as usize;
let cipher_alg = CipherAlg::decode(&mut base64_decoder)?;
let kdf_alg = KdfAlg::decode(&mut base64_decoder)?;
let kdf_options = KdfOptions::decode(&mut base64_decoder)?;
let nkeys = base64_decoder.decode_u32()? as usize;

// TODO(tarcieri): support more than one key?
if nkeys != 1 {
Expand All @@ -89,26 +94,26 @@ impl PrivateKey {

for _ in 0..nkeys {
// TODO(tarcieri): validate decoded length
let _len = decoder.decode_u32()? as usize;
let _pubkey = public::KeyData::decode(&mut decoder)?;
let _len = base64_decoder.decode_u32()? as usize;
let _pubkey = public::KeyData::decode(&mut base64_decoder)?;
}

// Begin decoding unencrypted list of N private keys
// See OpenSSH PROTOCOL.key § 3
// TODO(tarcieri): validate decoded length
let _len = decoder.decode_u32()? as usize;
let checkint1 = decoder.decode_u32()?;
let checkint2 = decoder.decode_u32()?;
let _len = base64_decoder.decode_u32()? as usize;
let checkint1 = base64_decoder.decode_u32()?;
let checkint2 = base64_decoder.decode_u32()?;

if checkint1 != checkint2 {
// TODO(tarcieri): treat this as a cryptographic error?
return Err(Error::FormatEncoding);
}

let key_data = KeypairData::decode(&mut decoder)?;
let key_data = KeypairData::decode(&mut base64_decoder)?;

#[cfg(feature = "alloc")]
let comment = decoder.decode_string()?;
let comment = base64_decoder.decode_string()?;

// TODO(tarcieri): parse/validate padding bytes?
Ok(Self {
Expand All @@ -135,6 +140,10 @@ impl FromStr for PrivateKey {
}
}

impl PemLabel for PrivateKey {
const TYPE_LABEL: &'static str = "OPENSSH PRIVATE KEY";
}

/// Private key data.
#[derive(Clone, Debug)]
#[non_exhaustive]
Expand Down
74 changes: 0 additions & 74 deletions ssh-key/src/private/openssh.rs

This file was deleted.

0 comments on commit 54689d5

Please sign in to comment.