From ba2ceee725b2f05ccd6e8f6e1ef1ebaa5b305c16 Mon Sep 17 00:00:00 2001 From: Adam Wick Date: Mon, 27 May 2019 20:28:34 -0700 Subject: [PATCH] Update the SSH documentation. --- src/ssh/errors.rs | 4 ++++ src/ssh/frame.rs | 19 +++++++++++++++++++ src/ssh/mod.rs | 28 +++++++++++++++++++++++++++- 3 files changed, 50 insertions(+), 1 deletion(-) diff --git a/src/ssh/errors.rs b/src/ssh/errors.rs index b50b9c7..5525dc3 100644 --- a/src/ssh/errors.rs +++ b/src/ssh/errors.rs @@ -2,6 +2,8 @@ use base64::DecodeError; use ed25519::ED25519PublicImportError; use std::io; +/// A whole pile of errors that you can get when parsing an SSH key from +/// disk or memory. #[derive(Debug)] pub enum SSHKeyParseError { @@ -49,6 +51,8 @@ impl From for SSHKeyParseError { } } +/// A much smaller set of errors you can get when rendering an SSH key into +/// a file or memory block. #[derive(Debug)] pub enum SSHKeyRenderError { IOError(io::Error), diff --git a/src/ssh/frame.rs b/src/ssh/frame.rs index df566d0..f7716ce 100644 --- a/src/ssh/frame.rs +++ b/src/ssh/frame.rs @@ -13,6 +13,9 @@ use std::iter::Iterator; const OPENER: &'static str = "-----BEGIN OPENSSH PRIVATE KEY-----\n"; const CLOSER: &'static str = "-----END OPENSSH PRIVATE KEY-----"; +/// Given a string defining an ASCII SSH key blob (one that starts with +/// "--BEGIN..."), decode the body of the blob and return it as binary +/// data. pub fn parse_ssh_private_key_data(s: &str) -> Result,SSHKeyParseError> { if s.starts_with(OPENER) { @@ -28,6 +31,8 @@ pub fn parse_ssh_private_key_data(s: &str) -> Result,SSHKeyParseError> } } +/// Once you've figured out the binary data you want to produce for an SSH key +/// blob, use this routine to render it into its ASCII encoding. pub fn render_ssh_private_key_data(bytes: &[u8]) -> String { let mut bytestr = encode(bytes); @@ -52,6 +57,7 @@ pub fn render_ssh_private_key_data(bytes: &[u8]) -> String const OPENSSH_MAGIC_HEADER: &'static str = "openssh-key-v1\0"; const OPENSSH_MAGIC_HEADER_LEN: usize = 15; +/// Parse the magic header in an SSH key file. pub fn parse_openssh_header(input: &mut R) -> Result<(),SSHKeyParseError> { let mut limited_input_header = input.take(OPENSSH_MAGIC_HEADER_LEN as u64); @@ -70,6 +76,7 @@ pub fn parse_openssh_header(input: &mut R) -> Result<(),SSHKeyParseErro Ok(()) } +/// Render the magic header in an SSH key file. pub fn render_openssh_header(output: &mut O) -> Result<(),SSHKeyRenderError> { Ok(output.write_all(OPENSSH_MAGIC_HEADER.as_bytes())?) @@ -77,6 +84,8 @@ pub fn render_openssh_header(output: &mut O) -> Result<(),SSHKeyRender //------------------------------------------------------------------------------ +/// Parse an unsigned u32 from the SSH key stream. (This does the appropriate +/// conversion from network order to native order.) pub fn parse_openssh_u32(input: &mut I) -> Result { let mut limited_input_header = input.take(4); @@ -84,6 +93,8 @@ pub fn parse_openssh_u32(input: &mut I) -> Result Ok(res) } +/// Render an unsigned u32 from the SSH key stream. (This does the appropriate +/// conversion from network order to native order.) pub fn render_openssh_u32(output: &mut O, val: u32) -> Result<(),SSHKeyRenderError> { Ok(output.write_u32::(val)?) @@ -91,6 +102,9 @@ pub fn render_openssh_u32(output: &mut O, val: u32) -> Result<(),SSHKe //------------------------------------------------------------------------------ +/// Parse a string from the SSH key stream. This does some validation to ensure +/// that the data being read is actually in a form that Rust will recognize as +/// being a valid string. pub fn parse_openssh_string(input: &mut I) -> Result { let length = parse_openssh_u32(input)?; @@ -100,6 +114,7 @@ pub fn parse_openssh_string(input: &mut I) -> Result(output: &mut O, v: &str) -> Result<(),SSHKeyRenderError> { let vbytes: Vec = v.bytes().collect(); @@ -116,6 +131,7 @@ pub fn render_openssh_string(output: &mut O, v: &str) -> Result<(),SSH //------------------------------------------------------------------------------ +/// Read a buffer from the SSH key stream. pub fn parse_openssh_buffer(input: &mut I) -> Result,SSHKeyParseError> { let length = parse_openssh_u32(input)?; @@ -125,6 +141,7 @@ pub fn parse_openssh_buffer(input: &mut I) -> Result,SSHKeyPars Ok(res) } +/// Render a buffer into the SSH key stream. pub fn render_openssh_buffer(output: &mut O, b: &[u8]) -> Result<(),SSHKeyRenderError> { if b.len() > 0xFFFFFFFF { @@ -141,6 +158,7 @@ pub fn render_openssh_buffer(output: &mut O, b: &[u8]) -> Result<(),SS //------------------------------------------------------------------------------ +/// Parse a fixed-width number from the SSH key stream and return it. pub fn parse_openssh_number(input: &mut I) -> Result where I: Read, @@ -151,6 +169,7 @@ pub fn parse_openssh_number(input: &mut I) -> Result Ok(D::from_bytes(&buffer)) } +/// Render a fixed-width number into the SSH key stream. pub fn render_openssh_number(output: &mut O, n: &D) -> Result<(),SSHKeyRenderError> where O: Write, diff --git a/src/ssh/mod.rs b/src/ssh/mod.rs index 82d213f..18987ab 100644 --- a/src/ssh/mod.rs +++ b/src/ssh/mod.rs @@ -2,7 +2,7 @@ mod dsa; mod ecdsa; mod ed25519; mod errors; -mod frame; +pub mod frame; mod rsa; pub use self::errors::{SSHKeyParseError,SSHKeyRenderError}; @@ -14,16 +14,35 @@ use std::io::{Cursor,Read,Write}; use std::path::Path; use super::KeyPair; +/// A trait defining keys that can be parsed / rendered by this library. Note +/// that you probably don't want to use these routines directly; they're mostly +/// used by the internal functions. Perhaps the only reason to use them is to +/// implement them, because you've got another kind of key you want to parse that +/// isn't already part of the library. (In that case, though ... maybe send a +/// patch?) pub trait SSHKey: Sized + KeyPair { + /// Return true if the given string is a valid key type identifier for this + /// key type. (i.e., "ssh-ed25519" is the identifier for ED25519, and "dss" + /// and "ssh-dss" are both valid identifiers for DSA keys.) fn valid_keytype(s: &str) -> bool; + /// Parse the public blob info within an SSH blob. I strongly recommend + /// using the functions in `ssh::frame` for this. fn parse_ssh_public_info(inp: &mut I) -> Result; + /// Parse the private blob info within an SSH blob. I strongly recommend + /// using the functions in `ssh::frame` for this. fn parse_ssh_private_info(inp: &mut I) -> Result<(Self::Private,String),SSHKeyParseError>; + /// Render the public blob info within an SSH blob. I strongly recommend + /// using the functions in `ssh::frame` for this. fn render_ssh_public_info(&self, out: &mut O) -> Result<(),SSHKeyRenderError>; + /// Render the private blob info within an SSH blob. I strongly recommend + /// using the functions in `ssh::frame` for this. fn render_ssh_private_info(&self, out: &mut O) -> Result<(),SSHKeyRenderError>; } +/// Decode a string containing a private key into the appropriate key type and +/// the comment associated with it, usually an email address or similar. pub fn decode_ssh(x: &str) -> Result<(KP, String),SSHKeyParseError> { let bytes = parse_ssh_private_key_data(x)?; @@ -61,6 +80,8 @@ pub fn decode_ssh(x: &str) -> Result<(KP, String),SSHKeyParseError> Ok((KP::new(public, private), comment)) } +/// Decode a string containing a public key into an appropriate key type and +/// the comment associated with it, usually an email address or similar. pub fn decode_ssh_pubkey(s: &str) -> Result<(KP::Public, String),SSHKeyParseError> { let mut splitter = s.split_whitespace(); @@ -82,6 +103,8 @@ pub fn decode_ssh_pubkey(s: &str) -> Result<(KP::Public, String),SSH } } +/// Load an SSH private key file, returning the appropriate key type and the +/// comment associated with it. pub fn load_ssh_keyfile(path: P) -> Result<(KP, String),SSHKeyParseError> where KP: SSHKey, @@ -93,6 +116,7 @@ pub fn load_ssh_keyfile(path: P) -> Result<(KP, String),SSHKeyParseError> decode_ssh(&contents) } +/// Load all the public keys from a file into memory. pub fn load_ssh_pubkeys(path: P) -> Result,SSHKeyParseError> where KP: SSHKey, @@ -110,6 +134,7 @@ pub fn load_ssh_pubkeys(path: P) -> Result,SSHKe Ok(result) } +/// Encode a supported key into its ASCII SSH format, with the given comment. pub fn encode_ssh(x: &KP, comment: &str) -> Result { let mut pubkeybin = Vec::with_capacity(8192); @@ -140,6 +165,7 @@ pub fn encode_ssh(x: &KP, comment: &str) -> Result(path: P, x: &KP, comment: &str) -> Result<(),SSHKeyRenderError> where KP: SSHKey,