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

Add support for all Diffie-Hellman Key Exchange protocols. #5

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
Binary file removed assets/dh_init.raw
Binary file not shown.
Binary file removed assets/dh_reply.raw
Binary file not shown.
Binary file added assets/kex/dh-kex-gex/client_kex_init.raw
Binary file not shown.
Binary file added assets/kex/dh-kex-gex/group.raw
Binary file not shown.
Binary file added assets/kex/dh-kex-gex/init.raw
Binary file not shown.
Binary file added assets/kex/dh-kex-gex/reply.raw
Binary file not shown.
Binary file added assets/kex/dh-kex-gex/request.raw
Binary file not shown.
Binary file added assets/kex/dh-kex-gex/server_kex_init.raw
Binary file not shown.
Binary file added assets/kex/dh/client_kex_init.raw
Binary file not shown.
Binary file added assets/kex/dh/init.raw
Binary file not shown.
Binary file added assets/kex/dh/reply.raw
Binary file not shown.
Binary file added assets/kex/dh/server_kex_init.raw
Binary file not shown.
Binary file added assets/kex/ecdh/client_kex_init.raw
Binary file not shown.
Binary file added assets/kex/ecdh/init.raw
Binary file not shown.
Binary file added assets/kex/ecdh/reply.raw
Binary file not shown.
Binary file added assets/kex/ecdh/server_kex_init.raw
Binary file not shown.
703 changes: 703 additions & 0 deletions src/kex.rs

Large diffs are not rendered by default.

10 changes: 8 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,19 @@
//! The code is available on [GitHub](https://github.com/rusticata/ssh-parser)
//! and is part of the [Rusticata](https://github.com/rusticata) project.

pub mod kex;
#[cfg(feature = "integers")]
pub mod mpint;
#[cfg(feature = "serialize")]
/// SSH packet crafting functions
pub mod serialize;
mod ssh;
#[cfg(test)]
mod tests;

pub use kex::{
ssh_kex_negociate_algorithm, ECDSASignature, SshKEX, SshKEXDiffieHellman,
SshKEXDiffieHellmanKEXGEX, SshKEXECDiffieHellman, SshKEXError, SshPacketDHKEXInit,
SshPacketDHKEXReply, SshPacketDhKEXGEXGroup, SshPacketDhKEXGEXInit, SshPacketDhKEXGEXReply,
SshPacketDhKEXGEXRequest, SshPacketDhKEXGEXRequestOld, SshPacketECDHKEXInit,
SshPacketECDHKEXReply,
};
pub use ssh::*;
20 changes: 3 additions & 17 deletions src/serialize.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
use crate::ssh::{
SshPacket, SshPacketDebug, SshPacketDhReply, SshPacketDisconnect, SshPacketKeyExchange,
};
use super::{SshPacket, SshPacketDebug, SshPacketDisconnect, SshPacketKeyExchange};
use cookie_factory::gen::{set_be_u32, set_be_u8};
use cookie_factory::*;
use std::iter::repeat;
Expand Down Expand Up @@ -31,16 +29,6 @@ fn gen_packet_key_exchange<'a>(
)
}

fn gen_packet_dh_reply<'a>(
x: (&'a mut [u8], usize),
p: &SshPacketDhReply,
) -> Result<(&'a mut [u8], usize), GenError> {
do_gen!(
x,
gen_string(p.pubkey_and_cert) >> gen_string(p.f) >> gen_string(p.signature)
)
}

fn gen_packet_disconnect<'a>(
x: (&'a mut [u8], usize),
p: &SshPacketDisconnect,
Expand Down Expand Up @@ -73,8 +61,7 @@ fn packet_payload_type(p: &SshPacket) -> u8 {
SshPacket::ServiceAccept(_) => 6,
SshPacket::KeyExchange(_) => 20,
SshPacket::NewKeys => 21,
SshPacket::DiffieHellmanInit(_) => 30,
SshPacket::DiffieHellmanReply(_) => 31,
SshPacket::DiffieHellmanKEX(ref p) => p.0.message_code,
}
}

Expand All @@ -91,8 +78,7 @@ fn gen_packet_payload<'a>(
SshPacket::ServiceAccept(p) => gen_string(x, p),
SshPacket::KeyExchange(ref p) => gen_packet_key_exchange(x, p),
SshPacket::NewKeys => Ok(x),
SshPacket::DiffieHellmanInit(ref p) => gen_string(x, p.e),
SshPacket::DiffieHellmanReply(ref p) => gen_packet_dh_reply(x, p),
SshPacket::DiffieHellmanKEX(ref p) => gen_string(x, p.0.payload),
}
}

Expand Down
146 changes: 57 additions & 89 deletions src/ssh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@

use nom::bytes::streaming::{is_not, tag, take, take_until};
use nom::character::streaming::{crlf, line_ending, not_line_ending};
use nom::combinator::{complete, map, map_parser, map_res, opt};
use nom::combinator::{complete, map, map_res, opt};
use nom::error::{make_error, Error, ErrorKind};
use nom::multi::{length_data, many_till, separated_list1};
use nom::number::streaming::{be_u32, be_u8};
use nom::sequence::{delimited, pair, terminated};
use nom::sequence::{delimited, terminated};
use nom::{Err, IResult};
use rusticata_macros::newtype_enum;
use std::str;
Expand Down Expand Up @@ -61,7 +61,7 @@ pub fn parse_ssh_identification(i: &[u8]) -> IResult<&[u8], (Vec<&[u8]>, SshVers
}

#[inline]
fn parse_string(i: &[u8]) -> IResult<&[u8], &[u8]> {
pub(super) fn parse_string(i: &[u8]) -> IResult<&[u8], &[u8]> {
length_data(be_u32)(i)
}

Expand Down Expand Up @@ -189,73 +189,6 @@ impl<'a> SshPacketKeyExchange<'a> {
}
}

/// SSH Key Exchange Client Packet
///
/// Defined in [RFC4253 section 8](https://tools.ietf.org/html/rfc4253#section-8) and [errata](https://www.rfc-editor.org/errata_search.php?rfc=4253).
///
/// The single field e is left unparsed because its representation depends on
/// the negotiated key exchange algorithm:
///
/// - with a diffie hellman exchange on multiplicative group of integers modulo
/// p, such as defined in [RFC4253](https://tools.ietf.org/html/rfc4253), the
/// field is a multiple precision integer (defined in [RFC4251 section 5](https://tools.ietf.org/html/rfc4251#section-5)).
/// - with a DH on elliptic curves, such as defined in [RFC6239](https://tools.ietf.org/html/rfc6239), the field is an octet string.
///
/// TODO: add accessors for the different representations
#[derive(Debug, PartialEq)]
pub struct SshPacketDhInit<'a> {
pub e: &'a [u8],
}

fn parse_packet_dh_init(i: &[u8]) -> IResult<&[u8], SshPacket> {
map(parse_string, |e| {
SshPacket::DiffieHellmanInit(SshPacketDhInit { e })
})(i)
}

/// SSH Key Exchange Server Packet
///
/// Defined in [RFC4253 section 8](https://tools.ietf.org/html/rfc4253#section-8) and [errata](https://www.rfc-editor.org/errata_search.php?rfc=4253).
///
/// Like the client packet, the fields depend on the algorithm negotiated during
/// the previous packet exchange.
#[derive(Debug, PartialEq)]
pub struct SshPacketDhReply<'a> {
pub pubkey_and_cert: &'a [u8],
pub f: &'a [u8],
pub signature: &'a [u8],
}

fn parse_packet_dh_reply(i: &[u8]) -> IResult<&[u8], SshPacket> {
let (i, pubkey_and_cert) = parse_string(i)?;
let (i, f) = parse_string(i)?;
let (i, signature) = parse_string(i)?;
let reply = SshPacketDhReply {
pubkey_and_cert,
f,
signature,
};
Ok((i, SshPacket::DiffieHellmanReply(reply)))
}

impl<'a> SshPacketDhReply<'a> {
/// Parse the ECDSA server signature.
///
/// Defined in [RFC5656 Section 3.1.2](https://tools.ietf.org/html/rfc5656#section-3.1.2).
#[allow(clippy::type_complexity)]
pub fn get_ecdsa_signature(&self) -> Result<(&str, Vec<u8>), nom::Err<Error<&[u8]>>> {
let (i, identifier) = map_res(parse_string, str::from_utf8)(self.signature)?;
let (_, blob) = map_parser(parse_string, pair(parse_string, parse_string))(i)?;

let mut rs = Vec::new();

rs.extend_from_slice(blob.0);
rs.extend_from_slice(blob.1);

Ok((identifier, rs))
}
}

/// SSH Disconnection Message
///
/// Defined in [RFC4253 Section 11.1](https://tools.ietf.org/html/rfc4253#section-11.1).
Expand Down Expand Up @@ -345,6 +278,11 @@ impl<'a> SshPacketDebug<'a> {
}
}

/// A SSH message that may belong to the KEX stage.
/// use [`super::SshKEX`] to parse this message.
#[derive(Debug, PartialEq)]
pub struct MaybeDiffieHellmanKEX<'a>(pub SshPacketUnparsed<'a>);

/// SSH Packet Enumeration
#[derive(Debug, PartialEq)]
pub enum SshPacket<'a> {
Expand All @@ -356,38 +294,68 @@ pub enum SshPacket<'a> {
ServiceAccept(&'a [u8]),
KeyExchange(SshPacketKeyExchange<'a>),
NewKeys,
DiffieHellmanInit(SshPacketDhInit<'a>),
DiffieHellmanReply(SshPacketDhReply<'a>),
DiffieHellmanKEX(MaybeDiffieHellmanKEX<'a>),
}

/// Parse a plaintext SSH packet with its padding.
///
/// Packet structure is defined in [RFC4253 Section 6](https://tools.ietf.org/html/rfc4253#section-6) and
/// message codes are defined in [RFC4253 Section 12](https://tools.ietf.org/html/rfc4253#section-12).
pub fn parse_ssh_packet(i: &[u8]) -> IResult<&[u8], (SshPacket, &[u8])> {
let (i, unparsed_ssh_packet) = parse_ssh_packet_with_message_code(i)?;
let padding = unparsed_ssh_packet.padding;
let d = unparsed_ssh_packet.payload;
let (_, msg) = match unparsed_ssh_packet.message_code {
1 => parse_packet_disconnect(d),
2 => map(parse_string, SshPacket::Ignore)(d),
3 => map(be_u32, SshPacket::Unimplemented)(d),
4 => parse_packet_debug(d),
5 => map(parse_string, SshPacket::ServiceRequest)(d),
6 => map(parse_string, SshPacket::ServiceAccept)(d),
20 => parse_packet_key_exchange(d),
21 => Ok((d, SshPacket::NewKeys)),
30..=34 => Ok((
i,
SshPacket::DiffieHellmanKEX(MaybeDiffieHellmanKEX(unparsed_ssh_packet)),
)),
_ => Err(Err::Error(make_error(d, ErrorKind::Switch))),
}?;
Ok((i, (msg, padding)))
}

/// A plaintext SSH packet in raw format, with the message code.
#[derive(Debug, PartialEq)]
pub struct SshPacketUnparsed<'a> {
/// The payload, **without** the message code byte.
pub payload: &'a [u8],

/// The padding.
pub padding: &'a [u8],

/// The message code.
pub message_code: u8,
}

/// Parse a plaintext SSH packet header with its message code.
///
/// Packet structure is defined in [RFC4253 Section 6](https://tools.ietf.org/html/rfc4253#section-6) and
pub fn parse_ssh_packet_with_message_code(i: &[u8]) -> IResult<&[u8], SshPacketUnparsed> {
let (i, packet_length) = be_u32(i)?;
let (i, padding_length) = be_u8(i)?;
if padding_length as u32 + 1 > packet_length {
return Err(Err::Error(make_error(i, ErrorKind::LengthValue)));
}
let (i, payload) = map_parser(take(packet_length - padding_length as u32 - 1), |d| {
let (d, msg_type) = be_u8(d)?;
match msg_type {
1 => parse_packet_disconnect(d),
2 => map(parse_string, SshPacket::Ignore)(d),
3 => map(be_u32, SshPacket::Unimplemented)(d),
4 => parse_packet_debug(d),
5 => map(parse_string, SshPacket::ServiceRequest)(d),
6 => map(parse_string, SshPacket::ServiceAccept)(d),
20 => parse_packet_key_exchange(d),
21 => Ok((d, SshPacket::NewKeys)),
30 => parse_packet_dh_init(d),
31 => parse_packet_dh_reply(d),
_ => Err(Err::Error(make_error(d, ErrorKind::Switch))),
}
})(i)?;
let (i, payload) = take(packet_length - padding_length as u32 - 1)(i)?;
let (payload_without_message_code, message_code) = be_u8(payload)?;
let (i, padding) = take(padding_length)(i)?;
Ok((i, (payload, padding)))
Ok((
i,
SshPacketUnparsed {
payload: payload_without_message_code,
padding,
message_code,
},
))
}

#[cfg(test)]
Expand Down
Loading
Loading