From bff59eebdd1ac3c0fc94fa84e155297b120e2cf1 Mon Sep 17 00:00:00 2001 From: Eugene Date: Fri, 27 Dec 2024 22:51:35 +0100 Subject: [PATCH] fixed #316 - correctly parse OpenSSH keys generated by PuTTYgen --- .gitignore | 1 + ssh-key/src/private.rs | 27 ++++++++++++++++++++++++--- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 312e97d5..6129446b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /target/ **/*.rs.bk ssh-key/tests/scratch/ +.vscode diff --git a/ssh-key/src/private.rs b/ssh-key/src/private.rs index 311a1fe2..0f338c29 100644 --- a/ssh-key/src/private.rs +++ b/ssh-key/src/private.rs @@ -176,7 +176,7 @@ const DEFAULT_RSA_KEY_SIZE: usize = 4096; const MAX_BLOCK_SIZE: usize = 16; /// Padding bytes to use. -const PADDING_BYTES: [u8; MAX_BLOCK_SIZE - 1] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; +const PADDING_BYTES: [u8; MAX_BLOCK_SIZE] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]; /// Unix file permissions for SSH private keys. #[cfg(all(unix, feature = "std"))] @@ -358,6 +358,7 @@ impl PrivateKey { &mut &**buffer, self.public_key.key_data.clone(), self.cipher.block_size(), + self.cipher.block_size() - 1, ) } @@ -548,8 +549,10 @@ impl PrivateKey { reader: &mut impl Reader, public_key: public::KeyData, block_size: usize, + max_padding_size: usize, ) -> Result { debug_assert!(block_size <= MAX_BLOCK_SIZE); + debug_assert!(max_padding_size <= MAX_BLOCK_SIZE); // Ensure input data is padding-aligned if reader.remaining_len().checked_rem(block_size) != Some(0) { @@ -575,7 +578,7 @@ impl PrivateKey { let padding_len = reader.remaining_len(); - if padding_len >= block_size { + if padding_len > max_padding_size { return Err(encoding::Error::Length.into()); } @@ -733,7 +736,25 @@ impl Decode for PrivateKey { } reader.read_prefixed(|reader| { - Self::decode_privatekey_comment_pair(reader, public_key, cipher.block_size()) + // PuTTYgen uses a non-standard block size of 16 + // and _always_ adds a padding even if data length + // is divisible by 16 - for unencrypted keys + // in the OpenSSH format. + // We're only relaxing the exact length check, but will + // still validate that the contents of the padding area. + // In all other cases there can be up to (but not including) + // `block_size` padding bytes as per `PROTOCOL.key`. + let max_padding_size = match cipher { + Cipher::None => 16, + #[allow(clippy::arithmetic_side_effects)] // block sizes are constants + _ => cipher.block_size() - 1, + }; + Self::decode_privatekey_comment_pair( + reader, + public_key, + cipher.block_size(), + max_padding_size, + ) }) } }