From d71b352b54b32f396d3971c13928c15f618c1179 Mon Sep 17 00:00:00 2001 From: Thomas Gatzweiler Date: Tue, 18 Jul 2017 13:36:37 +0200 Subject: [PATCH] Complete Curve25519 implementation --- README.md | 4 +- src/connection.rs | 226 ++++++++++++++++++++---------- src/error.rs | 2 + src/key_exchange/curve25519.rs | 131 ++++++++++++++--- src/key_exchange/dh_group_sha1.rs | 23 ++- src/key_exchange/mod.rs | 14 +- src/packet.rs | 4 + src/public_key/ed25519.rs | 21 ++- src/server.rs | 13 +- 9 files changed, 326 insertions(+), 112 deletions(-) diff --git a/README.md b/README.md index 0a515c2..3821fd8 100644 --- a/README.md +++ b/README.md @@ -10,10 +10,10 @@ Currently implemented features, ordered by priority: - [ ] SSH Client - [ ] Key Exchange algorithms - [ ] `diffie-hellman-group-exchange-sha1` - - [ ] `curve25519-sha256` + - [x] `curve25519-sha256` - [ ] Public Key algorithms - [ ] `ssh-rsa` - - [ ] `ssh-ed25519` + - [x] `ssh-ed25519` - [ ] Encryption algorithms - [ ] `aes256-ctr` - [ ] `aes256-gcm` diff --git a/src/connection.rs b/src/connection.rs index aa13c60..dbd27d6 100644 --- a/src/connection.rs +++ b/src/connection.rs @@ -1,9 +1,11 @@ use std::io::{self, BufRead, BufReader, Read, Write}; +use std::sync::Arc; use error::{ConnectionError, ConnectionResult}; -use key_exchange::{self, KeyExchange, KeyExchangeResult}; +use key_exchange::{self, KexResult, KeyExchange}; use message::MessageType; use packet::{Packet, ReadPacketExt, WritePacketExt}; +use server::ServerConfig; #[derive(PartialEq)] enum ConnectionState { @@ -12,55 +14,72 @@ enum ConnectionState { Established, } -#[derive(PartialEq)] +#[derive(Clone)] pub enum ConnectionType { - Server, + Server(Arc), Client, } -pub struct Connection { - ctype: ConnectionType, - state: ConnectionState, - key_exchange: Option>, - stream: W, - my_id: String, - peer_id: Option, +#[derive(Default, Debug)] +pub struct HashData { + pub client_id: Option, + pub server_id: Option, + pub client_kexinit: Option>, + pub server_kexinit: Option>, } -impl Connection { - pub fn new(ctype: ConnectionType, stream: W) -> Connection { +pub struct Connection { + pub conn_type: ConnectionType, + pub hash_data: HashData, + state: ConnectionState, + key_exchange: Option>, + stream: Box, + session_id: Option>, +} + +impl<'a> Connection { + pub fn new(conn_type: ConnectionType, stream: Box) -> Connection { Connection { - ctype: ctype, + conn_type: conn_type, + hash_data: HashData::default(), state: ConnectionState::Initial, key_exchange: None, - stream: stream, - my_id: format!( - "SSH-2.0-RedoxSSH_{}\r\n", - env!("CARGO_PKG_VERSION") - ), - peer_id: None, + stream: Box::new(stream), + session_id: None, } } pub fn run(&mut self, stream: &mut Read) -> ConnectionResult<()> { - self.stream.write(self.my_id.as_bytes())?; - self.stream.flush()?; + let mut reader = BufReader::new(stream); - let mut stream = BufReader::new(stream); - self.peer_id = Some(self.read_id(&mut stream)?); - - if let Some(ref peer_id) = self.peer_id { - println!("Identifies as {:?}", peer_id); - } + self.send_id()?; + self.read_id(&mut reader)?; loop { - let packet = Packet::read_from(&mut stream)?; - println!("packet: {:?}", packet); - self.process(&packet)?; + let packet = Packet::read_from(&mut reader)?; + trace!("Packet received: {:?}", packet); + self.process(packet)?; } } - fn read_id(&mut self, mut reader: &mut BufRead) -> io::Result { + pub fn send(&mut self, packet: &Packet) -> io::Result<()> { + packet.write_to(&mut self.stream) + } + + fn send_id(&mut self) -> io::Result<()> { + let id = format!("SSH-2.0-RedoxSSH_{}", env!("CARGO_PKG_VERSION")); + info!("Identifying as {:?}", id); + + self.stream.write(id.as_bytes())?; + self.stream.write(b"\r\n")?; + self.stream.flush()?; + + self.hash_data.server_id = Some(id); + + Ok(()) + } + + fn read_id(&mut self, mut reader: &mut BufRead) -> io::Result<()> { // The identification string has a maximum length of 255 bytes // TODO: Make sure to stop reading if the client sends too much @@ -70,38 +89,88 @@ impl Connection { reader.read_line(&mut id)?; } - Ok(id.trim_right().to_owned()) + let peer_id = id.trim_right().to_owned(); + info!("Peer identifies as {:?}", peer_id); + self.hash_data.client_id = Some(peer_id); + + Ok(()) } - pub fn process(&mut self, packet: &Packet) -> ConnectionResult<()> { + fn generate_key( + &mut self, + id: &[u8], + len: usize, + ) -> ConnectionResult> { + use self::ConnectionError::KeyGenerationError; + + let kex = self.key_exchange.take().ok_or(KeyGenerationError)?; + + let key = kex.hash( + &[ + kex.shared_secret().ok_or(KeyGenerationError)?, + kex.exchange_hash().ok_or(KeyGenerationError)?, + id, + self.session_id + .as_ref() + .ok_or(KeyGenerationError)? + .as_slice(), + ], + ); + + self.key_exchange = Some(kex); + + Ok(key) + } + + pub fn process(&mut self, packet: Packet) -> ConnectionResult<()> { match packet.msg_type() { MessageType::KexInit => { println!("Starting Key Exchange!"); self.kex_init(packet) } + MessageType::NewKeys => { + println!("Switching to new Keys"); + + let iv_c2s = self.generate_key(b"A", 256)?; + let iv_s2c = self.generate_key(b"B", 256)?; + let enc_c2s = self.generate_key(b"C", 256)?; + let enc_s2c = self.generate_key(b"D", 256)?; + let int_c2s = self.generate_key(b"E", 256)?; + let int_s2c = self.generate_key(b"F", 256)?; + + println!("c2s enc key: {:?}", enc_c2s); + + Ok(()) + } MessageType::KeyExchange(_) => { - let ref mut kex = self.key_exchange.as_mut().ok_or( + let mut kex = self.key_exchange.take().ok_or( ConnectionError::KeyExchangeError, )?; - match kex.process(packet) + match kex.process(self, packet) { - KeyExchangeResult::Ok(Some(packet)) => { - packet.write_to(&mut self.stream)?; - } - KeyExchangeResult::Error(Some(packet)) => { - packet.write_to(&mut self.stream)?; - } - KeyExchangeResult::Done(packet) => { - if let Some(packet) = packet { - packet.write_to(&mut self.stream)?; - } + KexResult::Done(packet) => { self.state = ConnectionState::Established; + self.send(&packet)?; + + if self.session_id.is_none() { + self.session_id = + kex.exchange_hash().map(|h| h.to_vec()); + } + + let packet = Packet::new(MessageType::NewKeys); + self.send(&packet)?; + Ok(()) } - KeyExchangeResult::Ok(None) | - KeyExchangeResult::Error(None) => {} - }; + KexResult::Ok(packet) => { + self.send(&packet)?; + Ok(()) + } + KexResult::Error => Err(ConnectionError::KeyExchangeError), + }?; + + self.key_exchange = Some(kex); Ok(()) } _ => { @@ -111,35 +180,43 @@ impl Connection { } } - pub fn kex_init(&mut self, packet: &Packet) -> ConnectionResult<()> { + pub fn kex_init(&mut self, packet: Packet) -> ConnectionResult<()> { use algorithm::*; - let mut reader = packet.reader(); + { + let mut reader = packet.reader(); + let _ = reader.read_bytes(16)?; // Cookie. Throw it away. + let kex_algos = reader.read_enum_list::()?; + let srv_host_key_algos = + reader.read_enum_list::()?; + let enc_algos_c2s = reader.read_enum_list::()?; + let enc_algos_s2c = reader.read_enum_list::()?; + let mac_algos_c2s = reader.read_enum_list::()?; + let mac_algos_s2c = reader.read_enum_list::()?; + let comp_algos_c2s = reader + .read_enum_list::()?; + let comp_algos_s2c = reader + .read_enum_list::()?; - let _ = reader.read_bytes(16)?; // Cookie. Throw it away. - let kex_algos = reader.read_enum_list::()?; - let srv_host_key_algos = reader.read_enum_list::()?; - let enc_algos_c2s = reader.read_enum_list::()?; - let enc_algos_s2c = reader.read_enum_list::()?; - let mac_algos_c2s = reader.read_enum_list::()?; - let mac_algos_s2c = reader.read_enum_list::()?; - let comp_algos_c2s = reader.read_enum_list::()?; - let comp_algos_s2c = reader.read_enum_list::()?; + let kex_algo = negotiate(KEY_EXCHANGE, kex_algos.as_slice())?; + let srv_host_key_algo = + negotiate(HOST_KEY, srv_host_key_algos.as_slice())?; + let enc_algo = negotiate(ENCRYPTION, enc_algos_s2c.as_slice())?; + let mac_algo = negotiate(MAC, mac_algos_s2c.as_slice())?; + let comp_algo = negotiate(COMPRESSION, comp_algos_s2c.as_slice())?; - let kex_algo = negotiate(KEY_EXCHANGE, kex_algos.as_slice())?; - let srv_host_key_algo = - negotiate(HOST_KEY, srv_host_key_algos.as_slice())?; - let enc_algo = negotiate(ENCRYPTION, enc_algos_s2c.as_slice())?; - let mac_algo = negotiate(MAC, mac_algos_s2c.as_slice())?; - let comp_algo = negotiate(COMPRESSION, comp_algos_s2c.as_slice())?; + println!("Negotiated Kex Algorithm: {:?}", kex_algo); + println!("Negotiated Host Key Algorithm: {:?}", srv_host_key_algo); + println!("Negotiated Encryption Algorithm: {:?}", enc_algo); + println!("Negotiated Mac Algorithm: {:?}", mac_algo); + println!("Negotiated Comp Algorithm: {:?}", comp_algo); + } - println!("Negotiated Kex Algorithm: {:?}", kex_algo); - println!("Negotiated Host Key Algorithm: {:?}", srv_host_key_algo); - println!("Negotiated Encryption Algorithm: {:?}", enc_algo); - println!("Negotiated Mac Algorithm: {:?}", mac_algo); - println!("Negotiated Comp Algorithm: {:?}", comp_algo); + // Save payload for hash generation + self.hash_data.client_kexinit = Some(packet.payload()); - use rand::{OsRng, Rng}; - let mut rng = OsRng::new()?; + // Create a random 16 byte cookie + use rand::{self, Rng}; + let mut rng = rand::thread_rng(); let cookie: Vec = rng.gen_iter::().take(16).collect(); let mut packet = Packet::new(MessageType::KexInit); @@ -162,7 +239,12 @@ impl Connection { self.state = ConnectionState::KeyExchange; self.key_exchange = Some(Box::new(key_exchange::Curve25519::new())); + packet.write_to(&mut self.stream)?; + + // Save payload for hash generation + self.hash_data.server_kexinit = Some(packet.payload()); + Ok(()) } } diff --git a/src/error.rs b/src/error.rs index 845a13b..9ee5d45 100644 --- a/src/error.rs +++ b/src/error.rs @@ -11,6 +11,7 @@ pub enum ConnectionError { ProtocolError, NegotiationError, KeyExchangeError, + KeyGenerationError, } impl fmt::Display for ConnectionError { @@ -28,6 +29,7 @@ impl Error for ConnectionError { &ProtocolError => "protocol error", &NegotiationError => "negotiation error", &KeyExchangeError => "key exchange error", + &KeyGenerationError => "key generation error", } } } diff --git a/src/key_exchange/curve25519.rs b/src/key_exchange/curve25519.rs index 8db11ca..3decc81 100644 --- a/src/key_exchange/curve25519.rs +++ b/src/key_exchange/curve25519.rs @@ -1,48 +1,141 @@ -use crypto::curve25519::curve25519; -use key_exchange::{KeyExchange, KeyExchangeResult}; +use connection::{Connection, ConnectionType}; +use crypto::curve25519; +use crypto::digest::Digest; +use crypto::sha2::Sha256; +use key_exchange::{KexResult, KeyExchange}; use message::MessageType; +use num_bigint::{BigInt, Sign}; use packet::{Packet, ReadPacketExt, WritePacketExt}; -use public_key::ED25519; +use rand::{self, Rng}; const ECDH_KEX_INIT: u8 = 30; const ECDH_KEX_REPLY: u8 = 31; -pub struct Curve25519 {} +pub struct Curve25519 { + shared_secret: Option<[u8; 32]>, + exchange_hash: Option>, +} impl Curve25519 { pub fn new() -> Curve25519 { - Curve25519 {} + Curve25519 { + shared_secret: None, + exchange_hash: None, + } } } impl KeyExchange for Curve25519 { - fn process(&mut self, packet: &Packet) -> KeyExchangeResult { + fn shared_secret<'a>(&'a self) -> Option<&'a [u8]> { + self.shared_secret.as_ref().map(|x| x as &[u8]) + } + + fn exchange_hash<'a>(&'a self) -> Option<&'a [u8]> { + self.exchange_hash.as_ref().map(|x| x.as_slice()) + } + + fn hash(&self, data: &[&[u8]]) -> Vec { + let mut hash = [0; 32]; + let mut hasher = Sha256::new(); + + for item in data { + hasher.input(item); + } + + hasher.result(&mut hash); + hash.to_vec() + } + + fn process(&mut self, conn: &mut Connection, packet: Packet) -> KexResult { match packet.msg_type() { MessageType::KeyExchange(ECDH_KEX_INIT) => { let mut reader = packet.reader(); - let qc = reader.read_string().unwrap(); + let client_public = reader.read_string().unwrap(); - let keypair = (ED25519.generate_key_pair)(None); - let mut public_key = Vec::new(); - keypair.write_public(&mut public_key); + let config = match &conn.conn_type + { + &ConnectionType::Server(ref config) => config.clone(), + _ => return KexResult::Error, + }; - println!("Received qc: {:?}", qc); + let public_key = { + let mut key = Vec::new(); + config.as_ref().key.write_public(&mut key).unwrap(); + key + }; + + println!("Received qc: {:?}", client_public); let mut packet = Packet::new(MessageType::KeyExchange(ECDH_KEX_REPLY)); - packet.with_writer(&|w| { - w.write_bytes(public_key.as_slice())?; - w.write_bytes(qc.as_slice())?; - w.write_bytes(&[0; 256])?; - Ok(()) - }); + let server_secret = { + let mut secret = [0; 32]; + let mut rng = rand::thread_rng(); + rng.fill_bytes(&mut secret); - KeyExchangeResult::Ok(Some(packet)) + secret[0] &= 248; + secret[31] &= 127; + secret[31] |= 64; + + secret + }; + + let server_public = curve25519::curve25519_base(&server_secret); + let shared_secret = + curve25519::curve25519(&server_secret, &client_public); + + let hash_data = { + let mut buf = Vec::new(); + let data = &conn.hash_data; + + let items = + [ + data.client_id.as_ref().unwrap().as_bytes(), + data.server_id.as_ref().unwrap().as_bytes(), + data.client_kexinit.as_ref().unwrap().as_slice(), + data.server_kexinit.as_ref().unwrap().as_slice(), + public_key.as_slice(), + client_public.as_slice(), + &server_public, + ]; + + for item in items.iter() { + buf.write_bytes(item); + } + + buf.write_mpint( + BigInt::from_bytes_be(Sign::Plus, &shared_secret), + ); + + buf + }; + + // Calculate hash + let hash = self.hash(&[hash_data.as_slice()]); + let signature = config.as_ref().key.sign(&hash).unwrap(); + + println!("Hash: {:?}", hash); + println!("Public Key: {:?}", public_key); + println!("Signature: {:?}", signature); + + packet + .with_writer(&|w| { + w.write_bytes(public_key.as_slice())?; + w.write_bytes(&server_public)?; + w.write_bytes(signature.as_slice())?; // Signature + Ok(()) + }) + .unwrap(); + + self.exchange_hash = Some(hash); + self.shared_secret = Some(shared_secret); + + KexResult::Done(packet) } _ => { debug!("Unhandled key exchange packet: {:?}", packet); - KeyExchangeResult::Error(None) + KexResult::Error } } } diff --git a/src/key_exchange/dh_group_sha1.rs b/src/key_exchange/dh_group_sha1.rs index c6c0993..4005c2a 100644 --- a/src/key_exchange/dh_group_sha1.rs +++ b/src/key_exchange/dh_group_sha1.rs @@ -1,4 +1,5 @@ -use key_exchange::{KeyExchange, KeyExchangeResult}; +use connection::Connection; +use key_exchange::{KexResult, KeyExchange}; use message::MessageType; use num_bigint::{BigInt, RandBigInt, ToBigInt}; use packet::{Packet, ReadPacketExt, WritePacketExt}; @@ -38,7 +39,19 @@ impl DhGroupSha1 { } impl KeyExchange for DhGroupSha1 { - fn process(&mut self, packet: &Packet) -> KeyExchangeResult { + fn shared_secret<'a>(&'a self) -> Option<&'a [u8]> { + Some(&[]) + } + + fn exchange_hash<'a>(&'a self) -> Option<&'a [u8]> { + Some(&[]) + } + + fn hash(&self, data: &[&[u8]]) -> Vec { + vec![] + } + + fn process(&mut self, conn: &mut Connection, packet: Packet) -> KexResult { match packet.msg_type() { MessageType::KeyExchange(DH_GEX_REQUEST) => { @@ -64,7 +77,7 @@ impl KeyExchange for DhGroupSha1 { self.g = Some(g); self.p = Some(p); - KeyExchangeResult::Ok(Some(packet)) + KexResult::Ok(packet) } MessageType::KeyExchange(DH_GEX_INIT) => { let mut reader = packet.reader(); @@ -83,11 +96,11 @@ impl KeyExchange for DhGroupSha1 { self.e = Some(e); - KeyExchangeResult::Ok(Some(packet)) + KexResult::Done(packet) } _ => { debug!("Unhandled key exchange packet: {:?}", packet); - KeyExchangeResult::Error(None) + KexResult::Error } } } diff --git a/src/key_exchange/mod.rs b/src/key_exchange/mod.rs index 5c1a077..f6c27e2 100644 --- a/src/key_exchange/mod.rs +++ b/src/key_exchange/mod.rs @@ -4,14 +4,18 @@ mod dh_group_sha1; pub use self::curve25519::Curve25519; pub use self::dh_group_sha1::DhGroupSha1; +use connection::Connection; use packet::Packet; -pub enum KeyExchangeResult { - Ok(Option), - Done(Option), - Error(Option), +pub enum KexResult { + Ok(Packet), + Done(Packet), + Error, } pub trait KeyExchange { - fn process(&mut self, packet: &Packet) -> KeyExchangeResult; + fn process(&mut self, conn: &mut Connection, packet: Packet) -> KexResult; + fn shared_secret<'a>(&'a self) -> Option<&'a [u8]>; + fn exchange_hash<'a>(&'a self) -> Option<&'a [u8]>; + fn hash(&self, data: &[&[u8]]) -> Vec; } diff --git a/src/packet.rs b/src/packet.rs index b688e8c..3ee9076 100644 --- a/src/packet.rs +++ b/src/packet.rs @@ -21,6 +21,10 @@ impl Packet { self.payload[0].into() } + pub fn payload(self) -> Vec { + self.payload + } + pub fn read_from(stream: &mut R) -> Result { let mac_len = 0; diff --git a/src/public_key/ed25519.rs b/src/public_key/ed25519.rs index 3f11d49..8430ed0 100644 --- a/src/public_key/ed25519.rs +++ b/src/public_key/ed25519.rs @@ -84,13 +84,28 @@ impl KeyPair for Ed25519KeyPair { } fn verify(&self, data: &[u8], signature: &[u8]) -> Result { - Ok(ed25519::verify(data, &self.public, signature)) + use packet::ReadPacketExt; + use std::io::Cursor; + + let mut reader = Cursor::new(signature); + let id = reader.read_string().unwrap_or(vec![]); + + if id == b"ssh-ed25519" { + if let Ok(sig) = reader.read_string() { + return Ok(ed25519::verify(data, &self.public, sig.as_slice())); + } + } + Err(()) } fn sign(&self, data: &[u8]) -> Result, ()> { + use packet::WritePacketExt; if let Some(private_key) = self.private { - let signature = ed25519::signature(data, &private_key); - Ok(signature.to_vec()) + let mut result = Vec::new(); + let sig = ed25519::signature(data, &private_key); + result.write_string("ssh-ed25519").or(Err(())); + result.write_bytes(&sig).or(Err(())); + Ok(result) } else { Err(()) diff --git a/src/server.rs b/src/server.rs index 67c9b82..e459f4f 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,5 +1,6 @@ use std::io; use std::net::TcpListener; +use std::sync::Arc; use connection::{Connection, ConnectionType}; use public_key::KeyPair; @@ -11,12 +12,12 @@ pub struct ServerConfig { } pub struct Server { - config: ServerConfig, + config: Arc, } impl Server { pub fn with_config(config: ServerConfig) -> Server { - Server { config: config } + Server { config: Arc::new(config) } } pub fn run(&self) -> io::Result<()> { @@ -24,8 +25,8 @@ impl Server { (&*self.config.host, self.config.port), ).expect(&*format!( "sshd: failed to bind to {}:{}", - self.config.host, - self.config.port + self.config.as_ref().host, + self.config.as_ref().port )); loop { @@ -36,8 +37,8 @@ impl Server { println!("Incoming connection from {}", addr); let mut connection = Connection::new( - ConnectionType::Server, - stream.try_clone().unwrap(), + ConnectionType::Server(self.config.clone()), + Box::new(stream.try_clone().unwrap()), ); let result = connection.run(&mut stream);