mirror of
https://github.com/Noratrieb/tls.git
synced 2026-01-16 01:25:02 +01:00
make progress
This commit is contained in:
parent
b4faf77116
commit
eea717987d
11 changed files with 574 additions and 343 deletions
448
src/lib.rs
448
src/lib.rs
|
|
@ -2,10 +2,14 @@ mod crypt;
|
|||
pub mod proto;
|
||||
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
fmt::Debug,
|
||||
io::{self, Read, Write},
|
||||
};
|
||||
|
||||
use crypt::{SeqId, SeqIdGen};
|
||||
use proto::CipherSuite;
|
||||
|
||||
use crate::proto::TLSPlaintext;
|
||||
|
||||
type Result<T, E = Error> = std::result::Result<T, E>;
|
||||
|
|
@ -16,172 +20,324 @@ pub struct ClientConnection<W> {
|
|||
|
||||
impl<W: Read + Write> ClientConnection<W> {
|
||||
pub fn establish(w: W, host: &str) -> Result<Self> {
|
||||
let _setup = ClientSetupConnection::establish(w, host)?;
|
||||
let mut setup = ClientSetupConnection {
|
||||
stream: StreamState::new(w),
|
||||
};
|
||||
let _setup = setup.establish(host)?;
|
||||
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
struct ClientSetupConnection<W> {
|
||||
_w: W,
|
||||
stream: StreamState<W>,
|
||||
}
|
||||
|
||||
mod stream_state {
|
||||
use std::io::{Read, Write};
|
||||
|
||||
use crate::crypt::{SeqId, SeqIdGen};
|
||||
use crate::proto::{self, TLSPlaintext};
|
||||
use crate::Result;
|
||||
|
||||
pub struct StreamState<W> {
|
||||
stream: W,
|
||||
read_seq_id: SeqIdGen,
|
||||
write_seq_id: SeqIdGen,
|
||||
}
|
||||
impl<W: Write + Read> StreamState<W> {
|
||||
pub fn new(stream: W) -> Self {
|
||||
Self {
|
||||
stream,
|
||||
read_seq_id: SeqIdGen::new(),
|
||||
write_seq_id: SeqIdGen::new(),
|
||||
}
|
||||
}
|
||||
pub fn write_flush_record(&mut self, plaintext: TLSPlaintext) -> Result<()> {
|
||||
self.write_record(plaintext)?;
|
||||
self.stream.flush()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_record(&mut self, plaintext: TLSPlaintext) -> Result<SeqId> {
|
||||
plaintext.write(&mut self.stream)?;
|
||||
self.stream.flush()?;
|
||||
Ok(self.write_seq_id.next())
|
||||
}
|
||||
|
||||
pub fn read_record(&mut self) -> Result<(TLSPlaintext, SeqId)> {
|
||||
let seq_id = self.read_seq_id.next();
|
||||
let frame = proto::TLSPlaintext::read(&mut self.stream)?;
|
||||
Ok((frame, seq_id))
|
||||
}
|
||||
}
|
||||
}
|
||||
use stream_state::StreamState;
|
||||
|
||||
macro_rules! unexpected_message {
|
||||
($($tt:tt)*) => {
|
||||
Err(ErrorKind::UnexpectedMessage(format!($($tt)*)).into())
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
https://datatracker.ietf.org/doc/html/rfc8446#appendix-A.1
|
||||
|
||||
```text
|
||||
START <----+
|
||||
Send ClientHello | | Recv HelloRetryRequest
|
||||
[K_send = early data] | |
|
||||
v |
|
||||
/ WAIT_SH ----+
|
||||
| | Recv ServerHello
|
||||
| | K_recv = handshake
|
||||
Can | V
|
||||
send | WAIT_EE
|
||||
early | | Recv EncryptedExtensions
|
||||
data | +--------+--------+
|
||||
| Using | | Using certificate
|
||||
| PSK | v
|
||||
| | WAIT_CERT_CR
|
||||
| | Recv | | Recv CertificateRequest
|
||||
| | Certificate | v
|
||||
| | | WAIT_CERT
|
||||
| | | | Recv Certificate
|
||||
| | v v
|
||||
| | WAIT_CV
|
||||
| | | Recv CertificateVerify
|
||||
| +> WAIT_FINISHED <+
|
||||
| | Recv Finished
|
||||
\ | [Send EndOfEarlyData]
|
||||
| K_send = handshake
|
||||
| [Send Certificate [+ CertificateVerify]]
|
||||
Can send | Send Finished
|
||||
app data --> | K_send = K_recv = application
|
||||
after here v
|
||||
CONNECTED
|
||||
```
|
||||
*/
|
||||
enum ConnectState {
|
||||
Start,
|
||||
WaitServerHello {
|
||||
legacy_session_id: [u8; 32],
|
||||
secret: RefCell<Option<x25519_dalek::EphemeralSecret>>,
|
||||
cipher_suites: Vec<CipherSuite>,
|
||||
},
|
||||
WaitEncryptedExtensions,
|
||||
WaitCertificateRequest,
|
||||
WaitCertificate,
|
||||
WaitCertificateVerify,
|
||||
WaitFinished,
|
||||
Connected,
|
||||
}
|
||||
|
||||
impl<W: Read + Write> ClientSetupConnection<W> {
|
||||
fn establish(mut stream: W, host: &str) -> Result<Self> {
|
||||
let secret = x25519_dalek::EphemeralSecret::random_from_rng(rand::thread_rng());
|
||||
let public = x25519_dalek::PublicKey::from(&secret);
|
||||
fn establish(&mut self, host: &str) -> Result<Self> {
|
||||
let mut state = ConnectState::Start;
|
||||
|
||||
let legacy_session_id = rand::random::<[u8; 32]>();
|
||||
let cipher_suites = vec![proto::CipherSuite::TLS_AES_128_GCM_SHA256];
|
||||
loop {
|
||||
let next_state = match &state {
|
||||
ConnectState::Start => {
|
||||
// https://datatracker.ietf.org/doc/html/rfc8446#section-2
|
||||
let secret = x25519_dalek::EphemeralSecret::random_from_rng(rand::thread_rng());
|
||||
let public = x25519_dalek::PublicKey::from(&secret);
|
||||
|
||||
let handshake = proto::Handshake::ClientHello {
|
||||
legacy_version: proto::LEGACY_TLSV12,
|
||||
random: rand::random(),
|
||||
legacy_session_id: legacy_session_id.to_vec().into(),
|
||||
cipher_suites: cipher_suites.clone().into(),
|
||||
legacy_compressions_methods: vec![0].into(),
|
||||
extensions: vec![
|
||||
proto::ExtensionCH::ServerName {
|
||||
server_name: vec![proto::ServerName::HostName {
|
||||
host_name: host.as_bytes().to_vec().into(),
|
||||
}]
|
||||
.into(),
|
||||
},
|
||||
proto::ExtensionCH::ECPointFormat {
|
||||
formats: vec![proto::ECPointFormat::Uncompressed].into(),
|
||||
},
|
||||
proto::ExtensionCH::SupportedGroups {
|
||||
groups: vec![proto::NamedGroup::X25519].into(),
|
||||
},
|
||||
proto::ExtensionCH::KeyShare {
|
||||
entries: vec![proto::KeyShareEntry::X25519 {
|
||||
len: public.as_bytes().len().try_into().unwrap(),
|
||||
key_exchange: *public.as_bytes(),
|
||||
}]
|
||||
.into(),
|
||||
},
|
||||
proto::ExtensionCH::SignatureAlgorithms {
|
||||
supported_signature_algorithms: vec![
|
||||
proto::SignatureScheme::ED25519,
|
||||
proto::SignatureScheme::ED448,
|
||||
proto::SignatureScheme::ECDSA_SECP256R1_SHA256,
|
||||
proto::SignatureScheme::ECDSA_SECP384R1_SHA384,
|
||||
proto::SignatureScheme::ECDSA_SECP521R1_SHA512,
|
||||
proto::SignatureScheme::RSA_PSS_PSS_SHA256,
|
||||
proto::SignatureScheme::RSA_PSS_PSS_SHA384,
|
||||
proto::SignatureScheme::RSA_PSS_PSS_SHA512,
|
||||
proto::SignatureScheme::RSA_PSS_RSAE_SHA256,
|
||||
proto::SignatureScheme::RSA_PSS_RSAE_SHA384,
|
||||
proto::SignatureScheme::RSA_PSS_RSAE_SHA512,
|
||||
]
|
||||
.into(),
|
||||
},
|
||||
proto::ExtensionCH::SupportedVersions {
|
||||
versions: vec![proto::TLSV13].into(),
|
||||
},
|
||||
]
|
||||
.into(),
|
||||
};
|
||||
let plaintext = proto::TLSPlaintext::Handshake { handshake };
|
||||
plaintext.write(&mut stream)?;
|
||||
stream.flush()?;
|
||||
let legacy_session_id = rand::random::<[u8; 32]>();
|
||||
let cipher_suites = vec![proto::CipherSuite::TLS_AES_128_GCM_SHA256];
|
||||
|
||||
let out = proto::TLSPlaintext::read(&mut stream)?;
|
||||
dbg!(&out);
|
||||
|
||||
let proto::TLSPlaintext::Handshake {
|
||||
handshake:
|
||||
proto::Handshake::ServerHello {
|
||||
legacy_version,
|
||||
random,
|
||||
legacy_session_id_echo,
|
||||
cipher_suite,
|
||||
legacy_compression_method,
|
||||
extensions,
|
||||
},
|
||||
} = out
|
||||
else {
|
||||
return Err(
|
||||
ErrorKind::UnexpectedMessage(format!("expected ServerHello, got {out:?}")).into(),
|
||||
);
|
||||
};
|
||||
|
||||
if random.is_hello_retry_request() {
|
||||
return Err(ErrorKind::HelloRetryRequest.into());
|
||||
}
|
||||
|
||||
if legacy_version != proto::LEGACY_TLSV12 {
|
||||
return unexpected_message!(
|
||||
"unexpected TLS version in legacy_version field: {legacy_version:x?}"
|
||||
);
|
||||
}
|
||||
|
||||
if legacy_session_id_echo.as_ref() != legacy_session_id {
|
||||
return unexpected_message!(
|
||||
"server did not echo the legacy_session_id: {legacy_session_id_echo:?}"
|
||||
);
|
||||
}
|
||||
|
||||
if !cipher_suites.contains(&cipher_suite) {
|
||||
return unexpected_message!(
|
||||
"cipher suite from server not sent in client hello: {cipher_suite:?}"
|
||||
);
|
||||
}
|
||||
|
||||
if legacy_compression_method != 0 {
|
||||
return unexpected_message!(
|
||||
"legacy compression method MUST be zero: {legacy_compression_method}"
|
||||
);
|
||||
}
|
||||
|
||||
let mut supported_versions = false;
|
||||
let mut server_key = None;
|
||||
|
||||
for ext in extensions.as_ref() {
|
||||
match ext {
|
||||
proto::ExtensionSH::PreSharedKey => todo!(),
|
||||
proto::ExtensionSH::SupportedVersions { selected_version } => {
|
||||
if *selected_version != proto::TLSV13 {
|
||||
return unexpected_message!("server returned non-TLS 1.3 version: {selected_version}");
|
||||
let handshake = proto::Handshake::ClientHello {
|
||||
legacy_version: proto::LEGACY_TLSV12,
|
||||
random: rand::random(),
|
||||
legacy_session_id: legacy_session_id.to_vec().into(),
|
||||
cipher_suites: cipher_suites.clone().into(),
|
||||
legacy_compressions_methods: vec![0].into(),
|
||||
extensions: vec![
|
||||
proto::ExtensionCH::ServerName {
|
||||
server_name: vec![proto::ServerName::HostName {
|
||||
host_name: host.as_bytes().to_vec().into(),
|
||||
}]
|
||||
.into(),
|
||||
},
|
||||
proto::ExtensionCH::ECPointFormat {
|
||||
formats: vec![proto::ECPointFormat::Uncompressed].into(),
|
||||
},
|
||||
proto::ExtensionCH::SupportedGroups {
|
||||
groups: vec![proto::NamedGroup::X25519].into(),
|
||||
},
|
||||
proto::ExtensionCH::KeyShare {
|
||||
entries: vec![proto::KeyShareEntry::X25519 {
|
||||
len: public.as_bytes().len().try_into().unwrap(),
|
||||
key_exchange: *public.as_bytes(),
|
||||
}]
|
||||
.into(),
|
||||
},
|
||||
proto::ExtensionCH::SignatureAlgorithms {
|
||||
supported_signature_algorithms: vec![
|
||||
proto::SignatureScheme::ED25519,
|
||||
proto::SignatureScheme::ED448,
|
||||
proto::SignatureScheme::ECDSA_SECP256R1_SHA256,
|
||||
proto::SignatureScheme::ECDSA_SECP384R1_SHA384,
|
||||
proto::SignatureScheme::ECDSA_SECP521R1_SHA512,
|
||||
proto::SignatureScheme::RSA_PSS_PSS_SHA256,
|
||||
proto::SignatureScheme::RSA_PSS_PSS_SHA384,
|
||||
proto::SignatureScheme::RSA_PSS_PSS_SHA512,
|
||||
proto::SignatureScheme::RSA_PSS_RSAE_SHA256,
|
||||
proto::SignatureScheme::RSA_PSS_RSAE_SHA384,
|
||||
proto::SignatureScheme::RSA_PSS_RSAE_SHA512,
|
||||
]
|
||||
.into(),
|
||||
},
|
||||
proto::ExtensionCH::SupportedVersions {
|
||||
versions: vec![proto::TLSV13].into(),
|
||||
},
|
||||
]
|
||||
.into(),
|
||||
};
|
||||
let plaintext = proto::TLSPlaintext::Handshake { handshake };
|
||||
self.stream.write_flush_record(plaintext)?;
|
||||
ConnectState::WaitServerHello {
|
||||
legacy_session_id,
|
||||
secret: RefCell::new(Some(secret)),
|
||||
cipher_suites,
|
||||
}
|
||||
supported_versions = true;
|
||||
},
|
||||
proto::ExtensionSH::Cookie { .. } => todo!(),
|
||||
proto::ExtensionSH::KeyShare { key_share } => {
|
||||
let entry = key_share.unwrap_server_hello();
|
||||
match entry {
|
||||
proto::KeyShareEntry::X25519 { len, key_exchange } => {
|
||||
if *len != 32 {
|
||||
return unexpected_message!("key length for X25519 key share must be 32: {len}");
|
||||
}
|
||||
ConnectState::WaitServerHello {
|
||||
legacy_session_id,
|
||||
secret,
|
||||
cipher_suites,
|
||||
} => {
|
||||
let (frame, seq_id) = self.stream.read_record()?;
|
||||
if frame.should_drop() {
|
||||
continue;
|
||||
}
|
||||
let proto::TLSPlaintext::Handshake {
|
||||
handshake:
|
||||
proto::Handshake::ServerHello {
|
||||
legacy_version,
|
||||
random,
|
||||
legacy_session_id_echo,
|
||||
cipher_suite,
|
||||
legacy_compression_method,
|
||||
extensions,
|
||||
},
|
||||
} = frame
|
||||
else {
|
||||
return unexpected_message!("expected ServerHello, got {frame:?}");
|
||||
};
|
||||
|
||||
if random.is_hello_retry_request() {
|
||||
return Err(ErrorKind::HelloRetryRequest.into());
|
||||
}
|
||||
|
||||
if legacy_version != proto::LEGACY_TLSV12 {
|
||||
return unexpected_message!(
|
||||
"unexpected TLS version in legacy_version field: {legacy_version:x?}"
|
||||
);
|
||||
}
|
||||
|
||||
if legacy_session_id_echo.as_ref() != legacy_session_id {
|
||||
return unexpected_message!(
|
||||
"server did not echo the legacy_session_id: {legacy_session_id_echo:?}"
|
||||
);
|
||||
}
|
||||
|
||||
if !cipher_suites.contains(&cipher_suite) {
|
||||
return unexpected_message!(
|
||||
"cipher suite from server not sent in client hello: {cipher_suite:?}"
|
||||
);
|
||||
}
|
||||
|
||||
if legacy_compression_method != 0 {
|
||||
return unexpected_message!(
|
||||
"legacy compression method MUST be zero: {legacy_compression_method}"
|
||||
);
|
||||
}
|
||||
|
||||
let mut supported_versions = false;
|
||||
let mut server_key = None;
|
||||
|
||||
for ext in extensions.as_ref() {
|
||||
match ext {
|
||||
proto::ExtensionSH::PreSharedKey => todo!(),
|
||||
proto::ExtensionSH::SupportedVersions { selected_version } => {
|
||||
if *selected_version != proto::TLSV13 {
|
||||
return unexpected_message!(
|
||||
"server returned non-TLS 1.3 version: {selected_version}"
|
||||
);
|
||||
}
|
||||
supported_versions = true;
|
||||
}
|
||||
server_key = Some(key_exchange);
|
||||
},
|
||||
proto::ExtensionSH::Cookie { .. } => todo!(),
|
||||
proto::ExtensionSH::KeyShare { key_share } => {
|
||||
let entry = key_share.unwrap_server_hello();
|
||||
match entry {
|
||||
proto::KeyShareEntry::X25519 { len, key_exchange } => {
|
||||
if *len != 32 {
|
||||
return unexpected_message!(
|
||||
"key length for X25519 key share must be 32: {len}"
|
||||
);
|
||||
}
|
||||
server_key = Some(key_exchange);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
if !supported_versions {
|
||||
return unexpected_message!(
|
||||
"server did not send supported_versions extension"
|
||||
);
|
||||
}
|
||||
|
||||
let Some(server_key) = server_key else {
|
||||
return unexpected_message!("server did not send its key");
|
||||
};
|
||||
let server_key = x25519_dalek::PublicKey::from(*server_key);
|
||||
let dh_shared_secret = secret
|
||||
.borrow_mut()
|
||||
.take()
|
||||
.unwrap()
|
||||
.diffie_hellman(&server_key);
|
||||
|
||||
println!(
|
||||
"we have established a shared secret. dont leak it!! anyways here is it: {:x?}",
|
||||
dh_shared_secret.as_bytes()
|
||||
);
|
||||
|
||||
crypt::compute_keys(dh_shared_secret);
|
||||
|
||||
ConnectState::WaitEncryptedExtensions
|
||||
}
|
||||
ConnectState::WaitEncryptedExtensions => {
|
||||
let (frame, seq_id) = self.stream.read_record()?;
|
||||
if frame.should_drop() {
|
||||
continue;
|
||||
}
|
||||
let proto::TLSPlaintext::ApplicationData { data } = frame else {
|
||||
return unexpected_message!("expected ApplicationData, got {frame:?}");
|
||||
};
|
||||
//crypt::TlsCiphertext::from(data).decrypt(key, seq_id.to_nonce());
|
||||
|
||||
todo!()
|
||||
}
|
||||
ConnectState::WaitCertificateRequest => todo!(),
|
||||
ConnectState::WaitCertificate => todo!(),
|
||||
ConnectState::WaitCertificateVerify => todo!(),
|
||||
ConnectState::WaitFinished => todo!(),
|
||||
ConnectState::Connected => todo!(),
|
||||
};
|
||||
state = next_state;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !supported_versions {
|
||||
return unexpected_message!("server did not send supported_versions extension");
|
||||
impl TLSPlaintext {
|
||||
fn should_drop(&self) -> bool {
|
||||
match self {
|
||||
TLSPlaintext::ChangeCipherSpec { data: body } if body.as_ref() == &[0x01] => true,
|
||||
_ => false,
|
||||
}
|
||||
|
||||
let Some(server_key) = server_key else {
|
||||
return unexpected_message!("server did not send its key");
|
||||
};
|
||||
let server_key = x25519_dalek::PublicKey::from(*server_key);
|
||||
let dh_shared_secret = secret.diffie_hellman(&server_key);
|
||||
|
||||
println!("we have established a shared secret. dont leak it!! anywhere here is it: {:x?}", dh_shared_secret.as_bytes());
|
||||
|
||||
dbg!(proto::TLSPlaintext::read(&mut stream))?;
|
||||
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -201,16 +357,16 @@ pub enum ErrorKind {
|
|||
impl From<io::Error> for Error {
|
||||
fn from(value: io::Error) -> Self {
|
||||
panic!("error: {value}");
|
||||
Self {
|
||||
kind: ErrorKind::Io(value),
|
||||
}
|
||||
//Self {
|
||||
// kind: ErrorKind::Io(value),
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ErrorKind> for Error {
|
||||
fn from(value: ErrorKind) -> Self {
|
||||
panic!("error:{value:?}");
|
||||
Self { kind: value }
|
||||
//Self { kind: value }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue