1
0
Fork 0
mirror of https://gitlab.redox-os.org/CoffeeCode/redox-ssh.git synced 2025-12-28 18:42:18 +01:00

Complete Curve25519 implementation

This commit is contained in:
Thomas Gatzweiler 2017-07-18 13:36:37 +02:00
parent c44b63c32c
commit d71b352b54
9 changed files with 326 additions and 112 deletions

View file

@ -10,10 +10,10 @@ Currently implemented features, ordered by priority:
- [ ] SSH Client - [ ] SSH Client
- [ ] Key Exchange algorithms - [ ] Key Exchange algorithms
- [ ] `diffie-hellman-group-exchange-sha1` - [ ] `diffie-hellman-group-exchange-sha1`
- [ ] `curve25519-sha256` - [x] `curve25519-sha256`
- [ ] Public Key algorithms - [ ] Public Key algorithms
- [ ] `ssh-rsa` - [ ] `ssh-rsa`
- [ ] `ssh-ed25519` - [x] `ssh-ed25519`
- [ ] Encryption algorithms - [ ] Encryption algorithms
- [ ] `aes256-ctr` - [ ] `aes256-ctr`
- [ ] `aes256-gcm` - [ ] `aes256-gcm`

View file

@ -1,9 +1,11 @@
use std::io::{self, BufRead, BufReader, Read, Write}; use std::io::{self, BufRead, BufReader, Read, Write};
use std::sync::Arc;
use error::{ConnectionError, ConnectionResult}; use error::{ConnectionError, ConnectionResult};
use key_exchange::{self, KeyExchange, KeyExchangeResult}; use key_exchange::{self, KexResult, KeyExchange};
use message::MessageType; use message::MessageType;
use packet::{Packet, ReadPacketExt, WritePacketExt}; use packet::{Packet, ReadPacketExt, WritePacketExt};
use server::ServerConfig;
#[derive(PartialEq)] #[derive(PartialEq)]
enum ConnectionState { enum ConnectionState {
@ -12,55 +14,72 @@ enum ConnectionState {
Established, Established,
} }
#[derive(PartialEq)] #[derive(Clone)]
pub enum ConnectionType { pub enum ConnectionType {
Server, Server(Arc<ServerConfig>),
Client, Client,
} }
pub struct Connection<W: Write> { #[derive(Default, Debug)]
ctype: ConnectionType, pub struct HashData {
state: ConnectionState, pub client_id: Option<String>,
key_exchange: Option<Box<KeyExchange>>, pub server_id: Option<String>,
stream: W, pub client_kexinit: Option<Vec<u8>>,
my_id: String, pub server_kexinit: Option<Vec<u8>>,
peer_id: Option<String>,
} }
impl<W: Write> Connection<W> { pub struct Connection {
pub fn new(ctype: ConnectionType, stream: W) -> Connection<W> { pub conn_type: ConnectionType,
pub hash_data: HashData,
state: ConnectionState,
key_exchange: Option<Box<KeyExchange>>,
stream: Box<Write>,
session_id: Option<Vec<u8>>,
}
impl<'a> Connection {
pub fn new(conn_type: ConnectionType, stream: Box<Write>) -> Connection {
Connection { Connection {
ctype: ctype, conn_type: conn_type,
hash_data: HashData::default(),
state: ConnectionState::Initial, state: ConnectionState::Initial,
key_exchange: None, key_exchange: None,
stream: stream, stream: Box::new(stream),
my_id: format!( session_id: None,
"SSH-2.0-RedoxSSH_{}\r\n",
env!("CARGO_PKG_VERSION")
),
peer_id: None,
} }
} }
pub fn run(&mut self, stream: &mut Read) -> ConnectionResult<()> { pub fn run(&mut self, stream: &mut Read) -> ConnectionResult<()> {
self.stream.write(self.my_id.as_bytes())?; let mut reader = BufReader::new(stream);
self.stream.flush()?;
let mut stream = BufReader::new(stream); self.send_id()?;
self.peer_id = Some(self.read_id(&mut stream)?); self.read_id(&mut reader)?;
if let Some(ref peer_id) = self.peer_id {
println!("Identifies as {:?}", peer_id);
}
loop { loop {
let packet = Packet::read_from(&mut stream)?; let packet = Packet::read_from(&mut reader)?;
println!("packet: {:?}", packet); trace!("Packet received: {:?}", packet);
self.process(&packet)?; self.process(packet)?;
} }
} }
fn read_id(&mut self, mut reader: &mut BufRead) -> io::Result<String> { 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 // The identification string has a maximum length of 255 bytes
// TODO: Make sure to stop reading if the client sends too much // TODO: Make sure to stop reading if the client sends too much
@ -70,38 +89,88 @@ impl<W: Write> Connection<W> {
reader.read_line(&mut id)?; 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<Vec<u8>> {
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() match packet.msg_type()
{ {
MessageType::KexInit => { MessageType::KexInit => {
println!("Starting Key Exchange!"); println!("Starting Key Exchange!");
self.kex_init(packet) 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(_) => { MessageType::KeyExchange(_) => {
let ref mut kex = self.key_exchange.as_mut().ok_or( let mut kex = self.key_exchange.take().ok_or(
ConnectionError::KeyExchangeError, ConnectionError::KeyExchangeError,
)?; )?;
match kex.process(packet) match kex.process(self, packet)
{ {
KeyExchangeResult::Ok(Some(packet)) => { KexResult::Done(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)?;
}
self.state = ConnectionState::Established; 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) | KexResult::Ok(packet) => {
KeyExchangeResult::Error(None) => {} self.send(&packet)?;
}; Ok(())
}
KexResult::Error => Err(ConnectionError::KeyExchangeError),
}?;
self.key_exchange = Some(kex);
Ok(()) Ok(())
} }
_ => { _ => {
@ -111,35 +180,43 @@ impl<W: Write> Connection<W> {
} }
} }
pub fn kex_init(&mut self, packet: &Packet) -> ConnectionResult<()> { pub fn kex_init(&mut self, packet: Packet) -> ConnectionResult<()> {
use algorithm::*; 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::<KeyExchangeAlgorithm>()?;
let srv_host_key_algos =
reader.read_enum_list::<PublicKeyAlgorithm>()?;
let enc_algos_c2s = reader.read_enum_list::<EncryptionAlgorithm>()?;
let enc_algos_s2c = reader.read_enum_list::<EncryptionAlgorithm>()?;
let mac_algos_c2s = reader.read_enum_list::<MacAlgorithm>()?;
let mac_algos_s2c = reader.read_enum_list::<MacAlgorithm>()?;
let comp_algos_c2s = reader
.read_enum_list::<CompressionAlgorithm>()?;
let comp_algos_s2c = reader
.read_enum_list::<CompressionAlgorithm>()?;
let _ = reader.read_bytes(16)?; // Cookie. Throw it away. let kex_algo = negotiate(KEY_EXCHANGE, kex_algos.as_slice())?;
let kex_algos = reader.read_enum_list::<KeyExchangeAlgorithm>()?; let srv_host_key_algo =
let srv_host_key_algos = reader.read_enum_list::<PublicKeyAlgorithm>()?; negotiate(HOST_KEY, srv_host_key_algos.as_slice())?;
let enc_algos_c2s = reader.read_enum_list::<EncryptionAlgorithm>()?; let enc_algo = negotiate(ENCRYPTION, enc_algos_s2c.as_slice())?;
let enc_algos_s2c = reader.read_enum_list::<EncryptionAlgorithm>()?; let mac_algo = negotiate(MAC, mac_algos_s2c.as_slice())?;
let mac_algos_c2s = reader.read_enum_list::<MacAlgorithm>()?; let comp_algo = negotiate(COMPRESSION, comp_algos_s2c.as_slice())?;
let mac_algos_s2c = reader.read_enum_list::<MacAlgorithm>()?;
let comp_algos_c2s = reader.read_enum_list::<CompressionAlgorithm>()?;
let comp_algos_s2c = reader.read_enum_list::<CompressionAlgorithm>()?;
let kex_algo = negotiate(KEY_EXCHANGE, kex_algos.as_slice())?; println!("Negotiated Kex Algorithm: {:?}", kex_algo);
let srv_host_key_algo = println!("Negotiated Host Key Algorithm: {:?}", srv_host_key_algo);
negotiate(HOST_KEY, srv_host_key_algos.as_slice())?; println!("Negotiated Encryption Algorithm: {:?}", enc_algo);
let enc_algo = negotiate(ENCRYPTION, enc_algos_s2c.as_slice())?; println!("Negotiated Mac Algorithm: {:?}", mac_algo);
let mac_algo = negotiate(MAC, mac_algos_s2c.as_slice())?; println!("Negotiated Comp Algorithm: {:?}", comp_algo);
let comp_algo = negotiate(COMPRESSION, comp_algos_s2c.as_slice())?; }
println!("Negotiated Kex Algorithm: {:?}", kex_algo); // Save payload for hash generation
println!("Negotiated Host Key Algorithm: {:?}", srv_host_key_algo); self.hash_data.client_kexinit = Some(packet.payload());
println!("Negotiated Encryption Algorithm: {:?}", enc_algo);
println!("Negotiated Mac Algorithm: {:?}", mac_algo);
println!("Negotiated Comp Algorithm: {:?}", comp_algo);
use rand::{OsRng, Rng}; // Create a random 16 byte cookie
let mut rng = OsRng::new()?; use rand::{self, Rng};
let mut rng = rand::thread_rng();
let cookie: Vec<u8> = rng.gen_iter::<u8>().take(16).collect(); let cookie: Vec<u8> = rng.gen_iter::<u8>().take(16).collect();
let mut packet = Packet::new(MessageType::KexInit); let mut packet = Packet::new(MessageType::KexInit);
@ -162,7 +239,12 @@ impl<W: Write> Connection<W> {
self.state = ConnectionState::KeyExchange; self.state = ConnectionState::KeyExchange;
self.key_exchange = Some(Box::new(key_exchange::Curve25519::new())); self.key_exchange = Some(Box::new(key_exchange::Curve25519::new()));
packet.write_to(&mut self.stream)?; packet.write_to(&mut self.stream)?;
// Save payload for hash generation
self.hash_data.server_kexinit = Some(packet.payload());
Ok(()) Ok(())
} }
} }

View file

@ -11,6 +11,7 @@ pub enum ConnectionError {
ProtocolError, ProtocolError,
NegotiationError, NegotiationError,
KeyExchangeError, KeyExchangeError,
KeyGenerationError,
} }
impl fmt::Display for ConnectionError { impl fmt::Display for ConnectionError {
@ -28,6 +29,7 @@ impl Error for ConnectionError {
&ProtocolError => "protocol error", &ProtocolError => "protocol error",
&NegotiationError => "negotiation error", &NegotiationError => "negotiation error",
&KeyExchangeError => "key exchange error", &KeyExchangeError => "key exchange error",
&KeyGenerationError => "key generation error",
} }
} }
} }

View file

@ -1,48 +1,141 @@
use crypto::curve25519::curve25519; use connection::{Connection, ConnectionType};
use key_exchange::{KeyExchange, KeyExchangeResult}; use crypto::curve25519;
use crypto::digest::Digest;
use crypto::sha2::Sha256;
use key_exchange::{KexResult, KeyExchange};
use message::MessageType; use message::MessageType;
use num_bigint::{BigInt, Sign};
use packet::{Packet, ReadPacketExt, WritePacketExt}; use packet::{Packet, ReadPacketExt, WritePacketExt};
use public_key::ED25519; use rand::{self, Rng};
const ECDH_KEX_INIT: u8 = 30; const ECDH_KEX_INIT: u8 = 30;
const ECDH_KEX_REPLY: u8 = 31; const ECDH_KEX_REPLY: u8 = 31;
pub struct Curve25519 {} pub struct Curve25519 {
shared_secret: Option<[u8; 32]>,
exchange_hash: Option<Vec<u8>>,
}
impl Curve25519 { impl Curve25519 {
pub fn new() -> Curve25519 { pub fn new() -> Curve25519 {
Curve25519 {} Curve25519 {
shared_secret: None,
exchange_hash: None,
}
} }
} }
impl KeyExchange for Curve25519 { 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<u8> {
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() match packet.msg_type()
{ {
MessageType::KeyExchange(ECDH_KEX_INIT) => { MessageType::KeyExchange(ECDH_KEX_INIT) => {
let mut reader = packet.reader(); 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 config = match &conn.conn_type
let mut public_key = Vec::new(); {
keypair.write_public(&mut public_key); &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 = let mut packet =
Packet::new(MessageType::KeyExchange(ECDH_KEX_REPLY)); Packet::new(MessageType::KeyExchange(ECDH_KEX_REPLY));
packet.with_writer(&|w| { let server_secret = {
w.write_bytes(public_key.as_slice())?; let mut secret = [0; 32];
w.write_bytes(qc.as_slice())?; let mut rng = rand::thread_rng();
w.write_bytes(&[0; 256])?; rng.fill_bytes(&mut secret);
Ok(())
});
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); debug!("Unhandled key exchange packet: {:?}", packet);
KeyExchangeResult::Error(None) KexResult::Error
} }
} }
} }

View file

@ -1,4 +1,5 @@
use key_exchange::{KeyExchange, KeyExchangeResult}; use connection::Connection;
use key_exchange::{KexResult, KeyExchange};
use message::MessageType; use message::MessageType;
use num_bigint::{BigInt, RandBigInt, ToBigInt}; use num_bigint::{BigInt, RandBigInt, ToBigInt};
use packet::{Packet, ReadPacketExt, WritePacketExt}; use packet::{Packet, ReadPacketExt, WritePacketExt};
@ -38,7 +39,19 @@ impl DhGroupSha1 {
} }
impl KeyExchange for 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<u8> {
vec![]
}
fn process(&mut self, conn: &mut Connection, packet: Packet) -> KexResult {
match packet.msg_type() match packet.msg_type()
{ {
MessageType::KeyExchange(DH_GEX_REQUEST) => { MessageType::KeyExchange(DH_GEX_REQUEST) => {
@ -64,7 +77,7 @@ impl KeyExchange for DhGroupSha1 {
self.g = Some(g); self.g = Some(g);
self.p = Some(p); self.p = Some(p);
KeyExchangeResult::Ok(Some(packet)) KexResult::Ok(packet)
} }
MessageType::KeyExchange(DH_GEX_INIT) => { MessageType::KeyExchange(DH_GEX_INIT) => {
let mut reader = packet.reader(); let mut reader = packet.reader();
@ -83,11 +96,11 @@ impl KeyExchange for DhGroupSha1 {
self.e = Some(e); self.e = Some(e);
KeyExchangeResult::Ok(Some(packet)) KexResult::Done(packet)
} }
_ => { _ => {
debug!("Unhandled key exchange packet: {:?}", packet); debug!("Unhandled key exchange packet: {:?}", packet);
KeyExchangeResult::Error(None) KexResult::Error
} }
} }
} }

View file

@ -4,14 +4,18 @@ mod dh_group_sha1;
pub use self::curve25519::Curve25519; pub use self::curve25519::Curve25519;
pub use self::dh_group_sha1::DhGroupSha1; pub use self::dh_group_sha1::DhGroupSha1;
use connection::Connection;
use packet::Packet; use packet::Packet;
pub enum KeyExchangeResult { pub enum KexResult {
Ok(Option<Packet>), Ok(Packet),
Done(Option<Packet>), Done(Packet),
Error(Option<Packet>), Error,
} }
pub trait KeyExchange { 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<u8>;
} }

View file

@ -21,6 +21,10 @@ impl Packet {
self.payload[0].into() self.payload[0].into()
} }
pub fn payload(self) -> Vec<u8> {
self.payload
}
pub fn read_from<R: io::Read>(stream: &mut R) -> Result<Packet> { pub fn read_from<R: io::Read>(stream: &mut R) -> Result<Packet> {
let mac_len = 0; let mac_len = 0;

View file

@ -84,13 +84,28 @@ impl KeyPair for Ed25519KeyPair {
} }
fn verify(&self, data: &[u8], signature: &[u8]) -> Result<bool, ()> { fn verify(&self, data: &[u8], signature: &[u8]) -> Result<bool, ()> {
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<Vec<u8>, ()> { fn sign(&self, data: &[u8]) -> Result<Vec<u8>, ()> {
use packet::WritePacketExt;
if let Some(private_key) = self.private { if let Some(private_key) = self.private {
let signature = ed25519::signature(data, &private_key); let mut result = Vec::new();
Ok(signature.to_vec()) let sig = ed25519::signature(data, &private_key);
result.write_string("ssh-ed25519").or(Err(()));
result.write_bytes(&sig).or(Err(()));
Ok(result)
} }
else { else {
Err(()) Err(())

View file

@ -1,5 +1,6 @@
use std::io; use std::io;
use std::net::TcpListener; use std::net::TcpListener;
use std::sync::Arc;
use connection::{Connection, ConnectionType}; use connection::{Connection, ConnectionType};
use public_key::KeyPair; use public_key::KeyPair;
@ -11,12 +12,12 @@ pub struct ServerConfig {
} }
pub struct Server { pub struct Server {
config: ServerConfig, config: Arc<ServerConfig>,
} }
impl Server { impl Server {
pub fn with_config(config: ServerConfig) -> Server { pub fn with_config(config: ServerConfig) -> Server {
Server { config: config } Server { config: Arc::new(config) }
} }
pub fn run(&self) -> io::Result<()> { pub fn run(&self) -> io::Result<()> {
@ -24,8 +25,8 @@ impl Server {
(&*self.config.host, self.config.port), (&*self.config.host, self.config.port),
).expect(&*format!( ).expect(&*format!(
"sshd: failed to bind to {}:{}", "sshd: failed to bind to {}:{}",
self.config.host, self.config.as_ref().host,
self.config.port self.config.as_ref().port
)); ));
loop { loop {
@ -36,8 +37,8 @@ impl Server {
println!("Incoming connection from {}", addr); println!("Incoming connection from {}", addr);
let mut connection = Connection::new( let mut connection = Connection::new(
ConnectionType::Server, ConnectionType::Server(self.config.clone()),
stream.try_clone().unwrap(), Box::new(stream.try_clone().unwrap()),
); );
let result = connection.run(&mut stream); let result = connection.run(&mut stream);