Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ssh-key: use pem-rfc7468 for private key PEM parser #407

Merged
merged 1 commit into from
Feb 9, 2022
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.

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
16 changes: 8 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,14 @@ impl<'i> Decoder<'i> {
}
}

impl<'i> From<pem_rfc7468::Decoder<'i>> for Decoder<'i> {
fn from(decoder: pem_rfc7468::Decoder<'i>) -> Decoder<'i> {
Decoder {
inner: decoder.into(),
}
}
}

/// 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);

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.