Initial commit
All checks were successful
CI / build (push) Successful in 27s
CI / no-std (push) Successful in 25s
CI / clippy (push) Successful in 26s
CI / test (push) Successful in 43s

This commit is contained in:
2026-02-21 09:01:54 +00:00
committed by Kris Kwiatkowski
commit dac2ea8c95
80 changed files with 11796 additions and 0 deletions

49
.gitea/workflows/ci.yaml Normal file
View File

@@ -0,0 +1,49 @@
name: CI
on:
push:
branches: [main]
pull_request:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust
run: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable
- name: Build
run: . $HOME/.cargo/env && cargo build
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust
run: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable
- name: Test (default)
run: . $HOME/.cargo/env && cargo test
- name: Test (webpki)
run: . $HOME/.cargo/env && cargo test --features webpki
- name: Test (native-pki)
run: . $HOME/.cargo/env && cargo test --features native-pki
- name: Test (native-pki + rsa)
run: . $HOME/.cargo/env && cargo test --features native-pki,rsa
clippy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust
run: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable --component clippy
- name: Clippy
run: . $HOME/.cargo/env && cargo clippy -- -D warnings
no-std:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust
run: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable --target thumbv7em-none-eabi
- name: Build (no_std)
run: . $HOME/.cargo/env && cargo build --target thumbv7em-none-eabi --no-default-features

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
target/

1755
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

60
Cargo.toml Normal file
View File

@@ -0,0 +1,60 @@
[package]
name = "latls"
version = "0.1.0"
edition = "2024"
description = "TLS 1.3 client with no_std and no allocator support"
license = "Apache-2.0"
keywords = ["async", "tls", "no_std", "bare-metal", "network"]
[dependencies]
portable-atomic = { version = "1.6.0", default-features = false }
p256 = { version = "0.13", default-features = false, features = [ "ecdh", "ecdsa", "sha256" ] }
p384 = { version = "0.13", default-features = false, features = [ "ecdsa", "sha384" ], optional = true }
ed25519-dalek = { version = "2.2", default-features = false, optional = true }
rsa = { version = "0.9.9", default-features = false, features = ["sha2"], optional = true }
rand_core = { version = "0.6.3", default-features = false }
hkdf = "0.12.3"
hmac = "0.12.1"
sha2 = { version = "0.10.2", default-features = false }
aes-gcm = { version = "0.10.1", default-features = false, features = ["aes"] }
digest = { version = "0.10.3", default-features = false, features = [ "core-api" ] }
typenum = { version = "1.15.0", default-features = false }
heapless = { version = "0.9", default-features = false }
heapless_typenum = { package = "heapless", version = "0.6", default-features = false }
embedded-io = "0.7"
embedded-io-async = "0.7"
embedded-io-adapters = { version = "0.7", optional = true }
generic-array = { version = "0.14", default-features = false }
webpki = { package = "rustls-webpki", version = "0.101.7", default-features = false, optional = true }
const-oid = { version = "0.10.1", optional = true }
der = { version = "0.8.0-rc.2", features = ["derive", "oid", "time", "heapless"], optional = true }
signature = { version = "2.2", default-features = false }
ecdsa = { version = "0.16.9", default-features = false }
# Logging alternatives
log = { version = "0.4", optional = true }
defmt = { version = "1.0.1", optional = true }
[dev-dependencies]
env_logger = "0.11"
tokio = { version = "1", features = ["full"] }
mio = { version = "0.8.3", features = ["os-poll", "net"] }
rustls = "0.21.6"
rustls-pemfile = "1.0"
serde = { version = "1.0", features = ["derive"] }
rand = "0.8"
log = "0.4"
pem-parser = "0.1.1"
openssl = "0.10.44"
[features]
default = ["std", "log", "tokio"]
defmt = ["dep:defmt", "embedded-io/defmt", "heapless/defmt"]
std = ["embedded-io/std", "embedded-io-async/std"]
tokio = ["embedded-io-adapters/tokio-1"]
alloc = []
webpki = ["dep:webpki"]
native-pki = ["dep:der","dep:const-oid"]
rsa = ["dep:rsa", "native-pki", "alloc"]
ed25519 = ["dep:ed25519-dalek", "native-pki"]
p384 = ["dep:p384", "native-pki"]

174
LICENSE Normal file
View File

@@ -0,0 +1,174 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship made available under
the License, as indicated by a copyright notice that is included in
or attached to the work (an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other
transformations represent, as a whole, an original work of authorship.
For the purposes of this License, Derivative Works shall not include
works that remain separable from, or merely link (or bind by name)
to the interfaces of, the Work and Derivative Works thereof.
"Contribution" shall mean, as submitted to the Licensor for inclusion
in the Work by the copyright owner or by an individual or Legal Entity
authorized to submit on behalf of the copyright owner. For the
purposes of this definition, "submit" means any form of electronic,
verbal, or written communication sent to the Licensor or its
representatives, including but not limited to communication on
electronic mailing lists, source code control systems, and issue
tracking systems that is managed by, or on behalf of, the Licensor
for the purpose of discussing and improving the Work, but excluding
communication that is conspicuously marked or designated in writing
by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any Legal Entity on behalf of
whom a Contribution has been received by the Licensor and included
within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent contributions
Licensable by such Contributor that are necessarily infringed by
their Contribution(s) alone or by the combination of their
Contribution(s) with the Work to which such Contribution(s) was
submitted. If You institute patent litigation against any entity
(including a cross-claim or counterclaim in a lawsuit) alleging that
the Work or any such Contribution embodied within the Work constitutes
direct or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate as of
the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or Derivative
Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, You must include a readable copy of the
attribution notices contained within such NOTICE file, in
at least one of the following places: within a NOTICE text
file distributed as part of the Derivative Works; within
the Source form or documentation, if provided along with the
Derivative Works; or, within a display generated by the
Derivative Works, if and wherever such third-party notices
normally appear. The contents of the NOTICE file are for
informational purposes only and do not modify the License.
You may add Your own attribution notices within Derivative
Works that You distribute, alongside or in addition to the
NOTICE text from the Work, provided that such additional
attribution notices cannot be construed as modifying the
License.
You may add Your own license statement for Your modifications and
may provide additional grant of rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the
Derivative Works, as separate terms and conditions for use,
reproduction, or distribution of Your modifications, or for such
Derivative Works as a whole, provided Your use, reproduction, and
distribution of the Work otherwise complies with the conditions
stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any conditions of TITLE,
MERCHANTIBILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely
responsible for determining the appropriateness of using or
reproducing the Work and assume any risks associated with Your
exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or exemplary damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or all other
commercial damages or losses), even if such Contributor has been
advised of the possibility of such damages.
9. Accepting Warranty or Liability. While redistributing the Work or
Derivative Works thereof, You may choose to offer, and charge a fee
for, acceptance of support, warranty, indemnity, or other liability
obligations and/or rights consistent with this License. However, in
accepting such obligations, You may offer such obligations only on
Your own behalf and on Your sole responsibility, not on behalf of
any other Contributor, and only if You agree to indemnify, defend,
and hold each Contributor harmless for any liability incurred by,
or claims asserted against, such Contributor by reason of your
accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

8
README.md Normal file
View File

@@ -0,0 +1,8 @@
# latls
TLS 1.3 client with `no_std` and no allocator support.
## License
Apache-2.0

121
src/alert.rs Normal file
View File

@@ -0,0 +1,121 @@
use crate::ProtocolError;
use crate::buffer::CryptoBuffer;
use crate::parse_buffer::ParseBuffer;
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum AlertLevel {
Warning = 1,
Fatal = 2,
}
impl AlertLevel {
#[must_use]
pub fn of(num: u8) -> Option<Self> {
match num {
1 => Some(AlertLevel::Warning),
2 => Some(AlertLevel::Fatal),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum AlertDescription {
CloseNotify = 0,
UnexpectedMessage = 10,
BadRecordMac = 20,
RecordOverflow = 22,
HandshakeFailure = 40,
BadCertificate = 42,
UnsupportedCertificate = 43,
CertificateRevoked = 44,
CertificateExpired = 45,
CertificateUnknown = 46,
IllegalParameter = 47,
UnknownCa = 48,
AccessDenied = 49,
DecodeError = 50,
DecryptError = 51,
ProtocolVersion = 70,
InsufficientSecurity = 71,
InternalError = 80,
InappropriateFallback = 86,
UserCanceled = 90,
MissingExtension = 109,
UnsupportedExtension = 110,
UnrecognizedName = 112,
BadCertificateStatusResponse = 113,
UnknownPskIdentity = 115,
CertificateRequired = 116,
NoApplicationProtocol = 120,
}
impl AlertDescription {
#[must_use]
pub fn of(num: u8) -> Option<Self> {
match num {
0 => Some(AlertDescription::CloseNotify),
10 => Some(AlertDescription::UnexpectedMessage),
20 => Some(AlertDescription::BadRecordMac),
22 => Some(AlertDescription::RecordOverflow),
40 => Some(AlertDescription::HandshakeFailure),
42 => Some(AlertDescription::BadCertificate),
43 => Some(AlertDescription::UnsupportedCertificate),
44 => Some(AlertDescription::CertificateRevoked),
45 => Some(AlertDescription::CertificateExpired),
46 => Some(AlertDescription::CertificateUnknown),
47 => Some(AlertDescription::IllegalParameter),
48 => Some(AlertDescription::UnknownCa),
49 => Some(AlertDescription::AccessDenied),
50 => Some(AlertDescription::DecodeError),
51 => Some(AlertDescription::DecryptError),
70 => Some(AlertDescription::ProtocolVersion),
71 => Some(AlertDescription::InsufficientSecurity),
80 => Some(AlertDescription::InternalError),
86 => Some(AlertDescription::InappropriateFallback),
90 => Some(AlertDescription::UserCanceled),
109 => Some(AlertDescription::MissingExtension),
110 => Some(AlertDescription::UnsupportedExtension),
112 => Some(AlertDescription::UnrecognizedName),
113 => Some(AlertDescription::BadCertificateStatusResponse),
115 => Some(AlertDescription::UnknownPskIdentity),
116 => Some(AlertDescription::CertificateRequired),
120 => Some(AlertDescription::NoApplicationProtocol),
_ => None,
}
}
}
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct Alert {
pub(crate) level: AlertLevel,
pub(crate) description: AlertDescription,
}
impl Alert {
#[must_use]
pub fn new(level: AlertLevel, description: AlertDescription) -> Self {
Self { level, description }
}
pub fn parse(buf: &mut ParseBuffer<'_>) -> Result<Alert, ProtocolError> {
let level = buf.read_u8()?;
let desc = buf.read_u8()?;
Ok(Self {
level: AlertLevel::of(level).ok_or(ProtocolError::DecodeError)?,
description: AlertDescription::of(desc).ok_or(ProtocolError::DecodeError)?,
})
}
pub fn encode(&self, buf: &mut CryptoBuffer<'_>) -> Result<(), ProtocolError> {
buf.push(self.level as u8)
.map_err(|_| ProtocolError::EncodeError)?;
buf.push(self.description as u8)
.map_err(|_| ProtocolError::EncodeError)?;
Ok(())
}
}

30
src/application_data.rs Normal file
View File

@@ -0,0 +1,30 @@
use crate::buffer::CryptoBuffer;
use crate::record::RecordHeader;
use core::fmt::{Debug, Formatter};
pub struct ApplicationData<'a> {
pub(crate) header: RecordHeader,
pub(crate) data: CryptoBuffer<'a>,
}
impl Debug for ApplicationData<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
write!(f, "ApplicationData {:x?}", self.data.len())
}
}
#[cfg(feature = "defmt")]
impl<'a> defmt::Format for ApplicationData<'a> {
fn format(&self, f: defmt::Formatter<'_>) {
defmt::write!(f, "ApplicationData {}", self.data.len());
}
}
impl<'a> ApplicationData<'a> {
pub fn new(rx_buf: CryptoBuffer<'a>, header: RecordHeader) -> ApplicationData<'a> {
Self {
header,
data: rx_buf,
}
}
}

505
src/asynch.rs Normal file
View File

@@ -0,0 +1,505 @@
use core::sync::atomic::{AtomicBool, Ordering};
use crate::ProtocolError;
use crate::common::decrypted_buffer_info::DecryptedBufferInfo;
use crate::common::decrypted_read_handler::DecryptedReadHandler;
use crate::connection::{Handshake, State, decrypt_record};
use crate::key_schedule::KeySchedule;
use crate::key_schedule::{ReadKeySchedule, WriteKeySchedule};
use crate::read_buffer::ReadBuffer;
use crate::record::{ClientRecord, ClientRecordHeader};
use crate::record_reader::{RecordReader, RecordReaderBorrowMut};
use crate::send_policy::FlushPolicy;
use crate::write_buffer::{WriteBuffer, WriteBufferBorrowMut};
use embedded_io::Error as _;
use embedded_io::ErrorType;
use embedded_io_async::{BufRead, Read as AsyncRead, Write as AsyncWrite};
pub use crate::config::*;
/// An async TLS 1.3 client stream wrapping an underlying async transport.
///
/// Call [`open`](SecureStream::open) to perform the handshake before reading or writing.
pub struct SecureStream<'a, Socket, CipherSuite>
where
Socket: AsyncRead + AsyncWrite + 'a,
CipherSuite: TlsCipherSuite + 'static,
{
delegate: Socket,
opened: AtomicBool,
key_schedule: KeySchedule<CipherSuite>,
record_reader: RecordReader<'a>,
record_write_buf: WriteBuffer<'a>,
decrypted: DecryptedBufferInfo,
flush_policy: FlushPolicy,
}
impl<'a, Socket, CipherSuite> SecureStream<'a, Socket, CipherSuite>
where
Socket: AsyncRead + AsyncWrite + 'a,
CipherSuite: TlsCipherSuite + 'static,
{
pub fn is_opened(&mut self) -> bool {
*self.opened.get_mut()
}
pub fn new(
delegate: Socket,
record_read_buf: &'a mut [u8],
record_write_buf: &'a mut [u8],
) -> Self {
Self {
delegate,
opened: AtomicBool::new(false),
key_schedule: KeySchedule::new(),
record_reader: RecordReader::new(record_read_buf),
record_write_buf: WriteBuffer::new(record_write_buf),
decrypted: DecryptedBufferInfo::default(),
flush_policy: FlushPolicy::default(),
}
}
#[inline]
pub fn flush_policy(&self) -> FlushPolicy {
self.flush_policy
}
#[inline]
pub fn set_flush_policy(&mut self, policy: FlushPolicy) {
self.flush_policy = policy;
}
pub async fn open<CP>(
&mut self,
mut context: ConnectContext<'_, CP>,
) -> Result<(), ProtocolError>
where
CP: CryptoBackend<CipherSuite = CipherSuite>,
{
let mut handshake: Handshake<CipherSuite> = Handshake::new();
if let (Ok(verifier), Some(server_name)) = (
context.crypto_provider.verifier(),
context.config.server_name,
) {
verifier.set_hostname_verification(server_name)?;
}
let mut state = State::ClientHello;
while state != State::ApplicationData {
let next_state = state
.process(
&mut self.delegate,
&mut handshake,
&mut self.record_reader,
&mut self.record_write_buf,
&mut self.key_schedule,
context.config,
&mut context.crypto_provider,
)
.await?;
trace!("State {:?} -> {:?}", state, next_state);
state = next_state;
}
*self.opened.get_mut() = true;
Ok(())
}
pub async fn write(&mut self, buf: &[u8]) -> Result<usize, ProtocolError> {
if self.is_opened() {
// Start a new ApplicationData record if none is in progress
if !self
.record_write_buf
.contains(ClientRecordHeader::ApplicationData)
{
self.flush().await?;
self.record_write_buf
.start_record(ClientRecordHeader::ApplicationData)?;
}
let buffered = self.record_write_buf.append(buf);
if self.record_write_buf.is_full() {
self.flush().await?;
}
Ok(buffered)
} else {
Err(ProtocolError::MissingHandshake)
}
}
pub async fn flush(&mut self) -> Result<(), ProtocolError> {
if !self.record_write_buf.is_empty() {
let key_schedule = self.key_schedule.write_state();
let slice = self.record_write_buf.close_record(key_schedule)?;
self.delegate
.write_all(slice)
.await
.map_err(|e| ProtocolError::Io(e.kind()))?;
key_schedule.increment_counter();
if self.flush_policy.flush_transport() {
self.flush_transport().await?;
}
}
Ok(())
}
#[inline]
async fn flush_transport(&mut self) -> Result<(), ProtocolError> {
self.delegate
.flush()
.await
.map_err(|e| ProtocolError::Io(e.kind()))
}
fn create_read_buffer(&mut self) -> ReadBuffer<'_> {
self.decrypted.create_read_buffer(self.record_reader.buf)
}
pub async fn read(&mut self, buf: &mut [u8]) -> Result<usize, ProtocolError> {
if buf.is_empty() {
return Ok(0);
}
let mut buffer = self.read_buffered().await?;
let len = buffer.pop_into(buf);
trace!("Copied {} bytes", len);
Ok(len)
}
pub async fn read_buffered(&mut self) -> Result<ReadBuffer<'_>, ProtocolError> {
if self.is_opened() {
while self.decrypted.is_empty() {
self.read_application_data().await?;
}
Ok(self.create_read_buffer())
} else {
Err(ProtocolError::MissingHandshake)
}
}
async fn read_application_data(&mut self) -> Result<(), ProtocolError> {
let buf_ptr_range = self.record_reader.buf.as_ptr_range();
let record = self
.record_reader
.read(&mut self.delegate, self.key_schedule.read_state())
.await?;
let mut handler = DecryptedReadHandler {
source_buffer: buf_ptr_range,
buffer_info: &mut self.decrypted,
is_open: self.opened.get_mut(),
};
decrypt_record(
self.key_schedule.read_state(),
record,
|_key_schedule, record| handler.handle(record),
)?;
Ok(())
}
async fn close_internal(&mut self) -> Result<(), ProtocolError> {
self.flush().await?;
let is_opened = self.is_opened();
let (write_key_schedule, read_key_schedule) = self.key_schedule.as_split();
// Send a close_notify alert to signal clean shutdown (RFC 8446 §6.1)
let slice = self.record_write_buf.write_record(
&ClientRecord::close_notify(is_opened),
write_key_schedule,
Some(read_key_schedule),
)?;
self.delegate
.write_all(slice)
.await
.map_err(|e| ProtocolError::Io(e.kind()))?;
self.key_schedule.write_state().increment_counter();
self.flush_transport().await
}
pub async fn close(mut self) -> Result<Socket, (Socket, ProtocolError)> {
match self.close_internal().await {
Ok(()) => Ok(self.delegate),
Err(e) => Err((self.delegate, e)),
}
}
pub fn split(
&mut self,
) -> (
TlsReader<'_, Socket, CipherSuite>,
TlsWriter<'_, Socket, CipherSuite>,
)
where
Socket: Clone,
{
// Split requires a Clone socket so both halves can independently drive the same connection
let (wks, rks) = self.key_schedule.as_split();
let reader = TlsReader {
opened: &self.opened,
delegate: self.delegate.clone(),
key_schedule: rks,
record_reader: self.record_reader.reborrow_mut(),
decrypted: &mut self.decrypted,
};
let writer = TlsWriter {
opened: &self.opened,
delegate: self.delegate.clone(),
key_schedule: wks,
record_write_buf: self.record_write_buf.reborrow_mut(),
flush_policy: self.flush_policy,
};
(reader, writer)
}
}
impl<'a, Socket, CipherSuite> ErrorType for SecureStream<'a, Socket, CipherSuite>
where
Socket: AsyncRead + AsyncWrite + 'a,
CipherSuite: TlsCipherSuite + 'static,
{
type Error = ProtocolError;
}
impl<'a, Socket, CipherSuite> AsyncRead for SecureStream<'a, Socket, CipherSuite>
where
Socket: AsyncRead + AsyncWrite + 'a,
CipherSuite: TlsCipherSuite + 'static,
{
async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
SecureStream::read(self, buf).await
}
}
impl<'a, Socket, CipherSuite> BufRead for SecureStream<'a, Socket, CipherSuite>
where
Socket: AsyncRead + AsyncWrite + 'a,
CipherSuite: TlsCipherSuite + 'static,
{
async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> {
self.read_buffered().await.map(|mut buf| buf.peek_all())
}
fn consume(&mut self, amt: usize) {
self.create_read_buffer().pop(amt);
}
}
impl<'a, Socket, CipherSuite> AsyncWrite for SecureStream<'a, Socket, CipherSuite>
where
Socket: AsyncRead + AsyncWrite + 'a,
CipherSuite: TlsCipherSuite + 'static,
{
async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
SecureStream::write(self, buf).await
}
async fn flush(&mut self) -> Result<(), Self::Error> {
SecureStream::flush(self).await
}
}
pub struct TlsReader<'a, Socket, CipherSuite>
where
CipherSuite: TlsCipherSuite + 'static,
{
opened: &'a AtomicBool,
delegate: Socket,
key_schedule: &'a mut ReadKeySchedule<CipherSuite>,
record_reader: RecordReaderBorrowMut<'a>,
decrypted: &'a mut DecryptedBufferInfo,
}
impl<Socket, CipherSuite> AsRef<Socket> for TlsReader<'_, Socket, CipherSuite>
where
CipherSuite: TlsCipherSuite + 'static,
{
fn as_ref(&self) -> &Socket {
&self.delegate
}
}
impl<'a, Socket, CipherSuite> TlsReader<'a, Socket, CipherSuite>
where
Socket: AsyncRead + 'a,
CipherSuite: TlsCipherSuite + 'static,
{
fn create_read_buffer(&mut self) -> ReadBuffer<'_> {
self.decrypted.create_read_buffer(self.record_reader.buf)
}
pub async fn read_buffered(&mut self) -> Result<ReadBuffer<'_>, ProtocolError> {
if self.opened.load(Ordering::Acquire) {
while self.decrypted.is_empty() {
self.read_application_data().await?;
}
Ok(self.create_read_buffer())
} else {
Err(ProtocolError::MissingHandshake)
}
}
async fn read_application_data(&mut self) -> Result<(), ProtocolError> {
let buf_ptr_range = self.record_reader.buf.as_ptr_range();
let record = self
.record_reader
.read(&mut self.delegate, self.key_schedule)
.await?;
let mut opened = self.opened.load(Ordering::Acquire);
let mut handler = DecryptedReadHandler {
source_buffer: buf_ptr_range,
buffer_info: self.decrypted,
is_open: &mut opened,
};
let result = decrypt_record(self.key_schedule, record, |_key_schedule, record| {
handler.handle(record)
});
if !opened {
self.opened.store(false, Ordering::Release);
}
result
}
}
pub struct TlsWriter<'a, Socket, CipherSuite>
where
CipherSuite: TlsCipherSuite + 'static,
{
opened: &'a AtomicBool,
delegate: Socket,
key_schedule: &'a mut WriteKeySchedule<CipherSuite>,
record_write_buf: WriteBufferBorrowMut<'a>,
flush_policy: FlushPolicy,
}
impl<'a, Socket, CipherSuite> TlsWriter<'a, Socket, CipherSuite>
where
Socket: AsyncWrite + 'a,
CipherSuite: TlsCipherSuite + 'static,
{
#[inline]
async fn flush_transport(&mut self) -> Result<(), ProtocolError> {
self.delegate
.flush()
.await
.map_err(|e| ProtocolError::Io(e.kind()))
}
}
impl<Socket, CipherSuite> AsRef<Socket> for TlsWriter<'_, Socket, CipherSuite>
where
CipherSuite: TlsCipherSuite + 'static,
{
fn as_ref(&self) -> &Socket {
&self.delegate
}
}
impl<Socket, CipherSuite> ErrorType for TlsWriter<'_, Socket, CipherSuite>
where
CipherSuite: TlsCipherSuite + 'static,
{
type Error = ProtocolError;
}
impl<Socket, CipherSuite> ErrorType for TlsReader<'_, Socket, CipherSuite>
where
CipherSuite: TlsCipherSuite + 'static,
{
type Error = ProtocolError;
}
impl<'a, Socket, CipherSuite> AsyncRead for TlsReader<'a, Socket, CipherSuite>
where
Socket: AsyncRead + 'a,
CipherSuite: TlsCipherSuite + 'static,
{
async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
if buf.is_empty() {
return Ok(0);
}
let mut buffer = self.read_buffered().await?;
let len = buffer.pop_into(buf);
trace!("Copied {} bytes", len);
Ok(len)
}
}
impl<'a, Socket, CipherSuite> BufRead for TlsReader<'a, Socket, CipherSuite>
where
Socket: AsyncRead + 'a,
CipherSuite: TlsCipherSuite + 'static,
{
async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> {
self.read_buffered().await.map(|mut buf| buf.peek_all())
}
fn consume(&mut self, amt: usize) {
self.create_read_buffer().pop(amt);
}
}
impl<'a, Socket, CipherSuite> AsyncWrite for TlsWriter<'a, Socket, CipherSuite>
where
Socket: AsyncWrite + 'a,
CipherSuite: TlsCipherSuite + 'static,
{
async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
if self.opened.load(Ordering::Acquire) {
if !self
.record_write_buf
.contains(ClientRecordHeader::ApplicationData)
{
self.flush().await?;
self.record_write_buf
.start_record(ClientRecordHeader::ApplicationData)?;
}
let buffered = self.record_write_buf.append(buf);
if self.record_write_buf.is_full() {
self.flush().await?;
}
Ok(buffered)
} else {
Err(ProtocolError::MissingHandshake)
}
}
async fn flush(&mut self) -> Result<(), Self::Error> {
if !self.record_write_buf.is_empty() {
let slice = self.record_write_buf.close_record(self.key_schedule)?;
self.delegate
.write_all(slice)
.await
.map_err(|e| ProtocolError::Io(e.kind()))?;
self.key_schedule.increment_counter();
if self.flush_policy.flush_transport() {
self.flush_transport().await?;
}
}
Ok(())
}
}

493
src/blocking.rs Normal file
View File

@@ -0,0 +1,493 @@
use core::sync::atomic::Ordering;
use crate::common::decrypted_buffer_info::DecryptedBufferInfo;
use crate::common::decrypted_read_handler::DecryptedReadHandler;
use crate::connection::{Handshake, State, decrypt_record};
use crate::key_schedule::KeySchedule;
use crate::key_schedule::{ReadKeySchedule, WriteKeySchedule};
use crate::read_buffer::ReadBuffer;
use crate::record::{ClientRecord, ClientRecordHeader};
use crate::record_reader::{RecordReader, RecordReaderBorrowMut};
use crate::send_policy::FlushPolicy;
use crate::write_buffer::{WriteBuffer, WriteBufferBorrowMut};
use embedded_io::Error as _;
use embedded_io::{BufRead, ErrorType, Read, Write};
use portable_atomic::AtomicBool;
pub use crate::ProtocolError;
pub use crate::config::*;
/// Blocking TLS 1.3 client stream wrapping a synchronous transport.
///
/// Call [`open`](SecureStream::open) to perform the handshake before reading or writing.
pub struct SecureStream<'a, Socket, CipherSuite>
where
Socket: Read + Write + 'a,
CipherSuite: TlsCipherSuite + 'static,
{
delegate: Socket,
opened: AtomicBool,
key_schedule: KeySchedule<CipherSuite>,
record_reader: RecordReader<'a>,
record_write_buf: WriteBuffer<'a>,
decrypted: DecryptedBufferInfo,
flush_policy: FlushPolicy,
}
impl<'a, Socket, CipherSuite> SecureStream<'a, Socket, CipherSuite>
where
Socket: Read + Write + 'a,
CipherSuite: TlsCipherSuite + 'static,
{
fn is_opened(&mut self) -> bool {
*self.opened.get_mut()
}
pub fn new(
delegate: Socket,
record_read_buf: &'a mut [u8],
record_write_buf: &'a mut [u8],
) -> Self {
Self {
delegate,
opened: AtomicBool::new(false),
key_schedule: KeySchedule::new(),
record_reader: RecordReader::new(record_read_buf),
record_write_buf: WriteBuffer::new(record_write_buf),
decrypted: DecryptedBufferInfo::default(),
flush_policy: FlushPolicy::default(),
}
}
#[inline]
pub fn flush_policy(&self) -> FlushPolicy {
self.flush_policy
}
#[inline]
pub fn set_flush_policy(&mut self, policy: FlushPolicy) {
self.flush_policy = policy;
}
pub fn open<CP>(&mut self, mut context: ConnectContext<CP>) -> Result<(), ProtocolError>
where
CP: CryptoBackend<CipherSuite = CipherSuite>,
{
let mut handshake: Handshake<CipherSuite> = Handshake::new();
if let (Ok(verifier), Some(server_name)) = (
context.crypto_provider.verifier(),
context.config.server_name,
) {
verifier.set_hostname_verification(server_name)?;
}
let mut state = State::ClientHello;
while state != State::ApplicationData {
let next_state = state.process_blocking(
&mut self.delegate,
&mut handshake,
&mut self.record_reader,
&mut self.record_write_buf,
&mut self.key_schedule,
context.config,
&mut context.crypto_provider,
)?;
trace!("State {:?} -> {:?}", state, next_state);
state = next_state;
}
*self.opened.get_mut() = true;
Ok(())
}
pub fn write(&mut self, buf: &[u8]) -> Result<usize, ProtocolError> {
if self.is_opened() {
// Start a new ApplicationData record if none is in progress
if !self
.record_write_buf
.contains(ClientRecordHeader::ApplicationData)
{
self.flush()?;
self.record_write_buf
.start_record(ClientRecordHeader::ApplicationData)?;
}
let buffered = self.record_write_buf.append(buf);
if self.record_write_buf.is_full() {
self.flush()?;
}
Ok(buffered)
} else {
Err(ProtocolError::MissingHandshake)
}
}
pub fn flush(&mut self) -> Result<(), ProtocolError> {
if !self.record_write_buf.is_empty() {
let key_schedule = self.key_schedule.write_state();
let slice = self.record_write_buf.close_record(key_schedule)?;
self.delegate
.write_all(slice)
.map_err(|e| ProtocolError::Io(e.kind()))?;
key_schedule.increment_counter();
if self.flush_policy.flush_transport() {
self.flush_transport()?;
}
}
Ok(())
}
#[inline]
fn flush_transport(&mut self) -> Result<(), ProtocolError> {
self.delegate
.flush()
.map_err(|e| ProtocolError::Io(e.kind()))
}
fn create_read_buffer(&mut self) -> ReadBuffer<'_> {
self.decrypted.create_read_buffer(self.record_reader.buf)
}
pub fn read(&mut self, buf: &mut [u8]) -> Result<usize, ProtocolError> {
if buf.is_empty() {
return Ok(0);
}
let mut buffer = self.read_buffered()?;
let len = buffer.pop_into(buf);
trace!("Copied {} bytes", len);
Ok(len)
}
pub fn read_buffered(&mut self) -> Result<ReadBuffer<'_>, ProtocolError> {
if self.is_opened() {
while self.decrypted.is_empty() {
self.read_application_data()?;
}
Ok(self.create_read_buffer())
} else {
Err(ProtocolError::MissingHandshake)
}
}
fn read_application_data(&mut self) -> Result<(), ProtocolError> {
let buf_ptr_range = self.record_reader.buf.as_ptr_range();
let key_schedule = self.key_schedule.read_state();
let record = self
.record_reader
.read_blocking(&mut self.delegate, key_schedule)?;
let mut handler = DecryptedReadHandler {
source_buffer: buf_ptr_range,
buffer_info: &mut self.decrypted,
is_open: self.opened.get_mut(),
};
decrypt_record(key_schedule, record, |_key_schedule, record| {
handler.handle(record)
})?;
Ok(())
}
fn close_internal(&mut self) -> Result<(), ProtocolError> {
self.flush()?;
let is_opened = self.is_opened();
let (write_key_schedule, read_key_schedule) = self.key_schedule.as_split();
// Send a close_notify alert to signal clean shutdown (RFC 8446 §6.1)
let slice = self.record_write_buf.write_record(
&ClientRecord::close_notify(is_opened),
write_key_schedule,
Some(read_key_schedule),
)?;
self.delegate
.write_all(slice)
.map_err(|e| ProtocolError::Io(e.kind()))?;
self.key_schedule.write_state().increment_counter();
self.flush_transport()?;
Ok(())
}
pub fn close(mut self) -> Result<Socket, (Socket, ProtocolError)> {
match self.close_internal() {
Ok(()) => Ok(self.delegate),
Err(e) => Err((self.delegate, e)),
}
}
pub fn split(
&mut self,
) -> (
TlsReader<'_, Socket, CipherSuite>,
TlsWriter<'_, Socket, CipherSuite>,
)
where
Socket: Clone,
{
let (wks, rks) = self.key_schedule.as_split();
let reader = TlsReader {
opened: &self.opened,
delegate: self.delegate.clone(),
key_schedule: rks,
record_reader: self.record_reader.reborrow_mut(),
decrypted: &mut self.decrypted,
};
let writer = TlsWriter {
opened: &self.opened,
delegate: self.delegate.clone(),
key_schedule: wks,
record_write_buf: self.record_write_buf.reborrow_mut(),
flush_policy: self.flush_policy,
};
(reader, writer)
}
}
impl<'a, Socket, CipherSuite> ErrorType for SecureStream<'a, Socket, CipherSuite>
where
Socket: Read + Write + 'a,
CipherSuite: TlsCipherSuite + 'static,
{
type Error = ProtocolError;
}
impl<'a, Socket, CipherSuite> Read for SecureStream<'a, Socket, CipherSuite>
where
Socket: Read + Write + 'a,
CipherSuite: TlsCipherSuite + 'static,
{
fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
SecureStream::read(self, buf)
}
}
impl<'a, Socket, CipherSuite> BufRead for SecureStream<'a, Socket, CipherSuite>
where
Socket: Read + Write + 'a,
CipherSuite: TlsCipherSuite + 'static,
{
fn fill_buf(&mut self) -> Result<&[u8], Self::Error> {
self.read_buffered().map(|mut buf| buf.peek_all())
}
fn consume(&mut self, amt: usize) {
self.create_read_buffer().pop(amt);
}
}
impl<'a, Socket, CipherSuite> Write for SecureStream<'a, Socket, CipherSuite>
where
Socket: Read + Write + 'a,
CipherSuite: TlsCipherSuite + 'static,
{
fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
SecureStream::write(self, buf)
}
fn flush(&mut self) -> Result<(), Self::Error> {
SecureStream::flush(self)
}
}
pub struct TlsReader<'a, Socket, CipherSuite>
where
CipherSuite: TlsCipherSuite + 'static,
{
opened: &'a AtomicBool,
delegate: Socket,
key_schedule: &'a mut ReadKeySchedule<CipherSuite>,
record_reader: RecordReaderBorrowMut<'a>,
decrypted: &'a mut DecryptedBufferInfo,
}
impl<Socket, CipherSuite> AsRef<Socket> for TlsReader<'_, Socket, CipherSuite>
where
CipherSuite: TlsCipherSuite + 'static,
{
fn as_ref(&self) -> &Socket {
&self.delegate
}
}
impl<'a, Socket, CipherSuite> TlsReader<'a, Socket, CipherSuite>
where
Socket: Read + 'a,
CipherSuite: TlsCipherSuite + 'static,
{
fn create_read_buffer(&mut self) -> ReadBuffer<'_> {
self.decrypted.create_read_buffer(self.record_reader.buf)
}
pub fn read_buffered(&mut self) -> Result<ReadBuffer<'_>, ProtocolError> {
if self.opened.load(Ordering::Acquire) {
while self.decrypted.is_empty() {
self.read_application_data()?;
}
Ok(self.create_read_buffer())
} else {
Err(ProtocolError::MissingHandshake)
}
}
fn read_application_data(&mut self) -> Result<(), ProtocolError> {
let buf_ptr_range = self.record_reader.buf.as_ptr_range();
let record = self
.record_reader
.read_blocking(&mut self.delegate, self.key_schedule)?;
let mut opened = self.opened.load(Ordering::Acquire);
let mut handler = DecryptedReadHandler {
source_buffer: buf_ptr_range,
buffer_info: self.decrypted,
is_open: &mut opened,
};
let result = decrypt_record(self.key_schedule, record, |_key_schedule, record| {
handler.handle(record)
});
if !opened {
self.opened.store(false, Ordering::Release);
}
result
}
}
pub struct TlsWriter<'a, Socket, CipherSuite>
where
CipherSuite: TlsCipherSuite + 'static,
{
opened: &'a AtomicBool,
delegate: Socket,
key_schedule: &'a mut WriteKeySchedule<CipherSuite>,
record_write_buf: WriteBufferBorrowMut<'a>,
flush_policy: FlushPolicy,
}
impl<'a, Socket, CipherSuite> TlsWriter<'a, Socket, CipherSuite>
where
Socket: Write + 'a,
CipherSuite: TlsCipherSuite + 'static,
{
fn flush_transport(&mut self) -> Result<(), ProtocolError> {
self.delegate
.flush()
.map_err(|e| ProtocolError::Io(e.kind()))
}
}
impl<Socket, CipherSuite> AsRef<Socket> for TlsWriter<'_, Socket, CipherSuite>
where
CipherSuite: TlsCipherSuite + 'static,
{
fn as_ref(&self) -> &Socket {
&self.delegate
}
}
impl<Socket, CipherSuite> ErrorType for TlsWriter<'_, Socket, CipherSuite>
where
CipherSuite: TlsCipherSuite + 'static,
{
type Error = ProtocolError;
}
impl<Socket, CipherSuite> ErrorType for TlsReader<'_, Socket, CipherSuite>
where
CipherSuite: TlsCipherSuite + 'static,
{
type Error = ProtocolError;
}
impl<'a, Socket, CipherSuite> Read for TlsReader<'a, Socket, CipherSuite>
where
Socket: Read + 'a,
CipherSuite: TlsCipherSuite + 'static,
{
fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
if buf.is_empty() {
return Ok(0);
}
let mut buffer = self.read_buffered()?;
let len = buffer.pop_into(buf);
trace!("Copied {} bytes", len);
Ok(len)
}
}
impl<'a, Socket, CipherSuite> BufRead for TlsReader<'a, Socket, CipherSuite>
where
Socket: Read + 'a,
CipherSuite: TlsCipherSuite + 'static,
{
fn fill_buf(&mut self) -> Result<&[u8], Self::Error> {
self.read_buffered().map(|mut buf| buf.peek_all())
}
fn consume(&mut self, amt: usize) {
self.create_read_buffer().pop(amt);
}
}
impl<'a, Socket, CipherSuite> Write for TlsWriter<'a, Socket, CipherSuite>
where
Socket: Write + 'a,
CipherSuite: TlsCipherSuite + 'static,
{
fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
if self.opened.load(Ordering::Acquire) {
if !self
.record_write_buf
.contains(ClientRecordHeader::ApplicationData)
{
self.flush()?;
self.record_write_buf
.start_record(ClientRecordHeader::ApplicationData)?;
}
let buffered = self.record_write_buf.append(buf);
if self.record_write_buf.is_full() {
self.flush()?;
}
Ok(buffered)
} else {
Err(ProtocolError::MissingHandshake)
}
}
fn flush(&mut self) -> Result<(), Self::Error> {
if !self.record_write_buf.is_empty() {
let slice = self.record_write_buf.close_record(self.key_schedule)?;
self.delegate
.write_all(slice)
.map_err(|e| ProtocolError::Io(e.kind()))?;
self.key_schedule.increment_counter();
if self.flush_policy.flush_transport() {
self.flush_transport()?;
}
}
Ok(())
}
}

300
src/buffer.rs Normal file
View File

@@ -0,0 +1,300 @@
use crate::ProtocolError;
use aes_gcm::Error;
use aes_gcm::aead::Buffer;
pub struct CryptoBuffer<'b> {
buf: &'b mut [u8],
offset: usize,
len: usize,
}
impl<'b> CryptoBuffer<'b> {
#[allow(dead_code)]
pub(crate) fn empty() -> Self {
Self {
buf: &mut [],
offset: 0,
len: 0,
}
}
pub(crate) fn wrap(buf: &'b mut [u8]) -> Self {
Self {
buf,
offset: 0,
len: 0,
}
}
pub(crate) fn wrap_with_pos(buf: &'b mut [u8], pos: usize) -> Self {
Self {
buf,
offset: 0,
len: pos,
}
}
pub fn push(&mut self, b: u8) -> Result<(), ProtocolError> {
if self.space() > 0 {
self.buf[self.offset + self.len] = b;
self.len += 1;
Ok(())
} else {
error!("Failed to push byte");
Err(ProtocolError::InsufficientSpace)
}
}
pub fn push_u16(&mut self, num: u16) -> Result<(), ProtocolError> {
let data = num.to_be_bytes();
self.extend_from_slice(&data)
}
pub fn push_u24(&mut self, num: u32) -> Result<(), ProtocolError> {
let data = num.to_be_bytes();
self.extend_from_slice(&[data[1], data[2], data[3]])
}
pub fn push_u32(&mut self, num: u32) -> Result<(), ProtocolError> {
let data = num.to_be_bytes();
self.extend_from_slice(&data)
}
fn set(&mut self, idx: usize, val: u8) -> Result<(), ProtocolError> {
if idx < self.len {
self.buf[self.offset + idx] = val;
Ok(())
} else {
error!(
"Failed to set byte: index {} is out of range for {} elements",
idx, self.len
);
Err(ProtocolError::InsufficientSpace)
}
}
fn set_u16(&mut self, idx: usize, val: u16) -> Result<(), ProtocolError> {
let [upper, lower] = val.to_be_bytes();
self.set(idx, upper)?;
self.set(idx + 1, lower)?;
Ok(())
}
fn set_u24(&mut self, idx: usize, val: u32) -> Result<(), ProtocolError> {
let [_, upper, mid, lower] = val.to_be_bytes();
self.set(idx, upper)?;
self.set(idx + 1, mid)?;
self.set(idx + 2, lower)?;
Ok(())
}
pub fn as_mut_slice(&mut self) -> &mut [u8] {
&mut self.buf[self.offset..self.offset + self.len]
}
pub fn as_slice(&self) -> &[u8] {
&self.buf[self.offset..self.offset + self.len]
}
fn extend_internal(&mut self, other: &[u8]) -> Result<(), ProtocolError> {
if self.space() < other.len() {
error!(
"Failed to extend buffer. Space: {} required: {}",
self.space(),
other.len()
);
Err(ProtocolError::InsufficientSpace)
} else {
let start = self.offset + self.len;
self.buf[start..start + other.len()].clone_from_slice(other);
self.len += other.len();
Ok(())
}
}
fn space(&self) -> usize {
self.capacity() - (self.offset + self.len)
}
pub fn extend_from_slice(&mut self, other: &[u8]) -> Result<(), ProtocolError> {
self.extend_internal(other)
}
fn truncate_internal(&mut self, len: usize) {
if len <= self.capacity() - self.offset {
self.len = len;
}
}
pub fn truncate(&mut self, len: usize) {
self.truncate_internal(len);
}
pub fn len(&self) -> usize {
self.len
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
pub fn release(self) -> (&'b mut [u8], usize, usize) {
(self.buf, self.offset, self.len)
}
pub fn capacity(&self) -> usize {
self.buf.len()
}
pub fn forward(self) -> CryptoBuffer<'b> {
let len = self.len;
self.offset(len)
}
pub fn rewind(self) -> CryptoBuffer<'b> {
self.offset(0)
}
pub(crate) fn offset(self, offset: usize) -> CryptoBuffer<'b> {
let new_len = self.len + self.offset - offset;
CryptoBuffer {
buf: self.buf,
len: new_len,
offset,
}
}
pub fn with_u8_length<R>(
&mut self,
op: impl FnOnce(&mut Self) -> Result<R, ProtocolError>,
) -> Result<R, ProtocolError> {
let len_pos = self.len;
self.push(0)?;
let start = self.len;
let r = op(self)?;
let len = (self.len() - start) as u8;
self.set(len_pos, len)?;
Ok(r)
}
pub fn with_u16_length<R>(
&mut self,
op: impl FnOnce(&mut Self) -> Result<R, ProtocolError>,
) -> Result<R, ProtocolError> {
let len_pos = self.len;
self.push_u16(0)?;
let start = self.len;
let r = op(self)?;
let len = (self.len() - start) as u16;
self.set_u16(len_pos, len)?;
Ok(r)
}
pub fn with_u24_length<R>(
&mut self,
op: impl FnOnce(&mut Self) -> Result<R, ProtocolError>,
) -> Result<R, ProtocolError> {
let len_pos = self.len;
self.push_u24(0)?;
let start = self.len;
let r = op(self)?;
let len = (self.len() - start) as u32;
self.set_u24(len_pos, len)?;
Ok(r)
}
}
impl AsRef<[u8]> for CryptoBuffer<'_> {
fn as_ref(&self) -> &[u8] {
self.as_slice()
}
}
impl AsMut<[u8]> for CryptoBuffer<'_> {
fn as_mut(&mut self) -> &mut [u8] {
self.as_mut_slice()
}
}
impl Buffer for CryptoBuffer<'_> {
fn extend_from_slice(&mut self, other: &[u8]) -> Result<(), Error> {
self.extend_internal(other).map_err(|_| Error)
}
fn truncate(&mut self, len: usize) {
self.truncate_internal(len);
}
}
#[cfg(test)]
mod test {
use super::CryptoBuffer;
#[test]
fn encode() {
let mut buf1 = [0; 4];
let mut c = CryptoBuffer::wrap(&mut buf1);
c.push_u24(1027).unwrap();
let mut buf2 = [0; 4];
let mut c = CryptoBuffer::wrap(&mut buf2);
c.push_u24(0).unwrap();
c.set_u24(0, 1027).unwrap();
assert_eq!(buf1, buf2);
let decoded = u32::from_be_bytes([0, buf1[0], buf1[1], buf1[2]]);
assert_eq!(1027, decoded);
}
#[test]
fn offset_calc() {
let mut buf = [0; 8];
let mut c = CryptoBuffer::wrap(&mut buf);
c.push(1).unwrap();
c.push(2).unwrap();
c.push(3).unwrap();
assert_eq!(&[1, 2, 3], c.as_slice());
let l = c.len();
let mut c = c.offset(l);
c.push(4).unwrap();
c.push(5).unwrap();
c.push(6).unwrap();
assert_eq!(&[4, 5, 6], c.as_slice());
let mut c = c.offset(0);
c.push(7).unwrap();
c.push(8).unwrap();
assert_eq!(&[1, 2, 3, 4, 5, 6, 7, 8], c.as_slice());
let mut c = c.offset(6);
c.set(0, 14).unwrap();
c.set(1, 15).unwrap();
let c = c.offset(0);
assert_eq!(&[1, 2, 3, 4, 5, 6, 14, 15], c.as_slice());
let mut c = c.offset(4);
c.truncate(0);
c.extend_from_slice(&[10, 11, 12, 13]).unwrap();
assert_eq!(&[10, 11, 12, 13], c.as_slice());
let c = c.offset(0);
assert_eq!(&[1, 2, 3, 4, 10, 11, 12, 13], c.as_slice());
}
}

279
src/cert_verify.rs Normal file
View File

@@ -0,0 +1,279 @@
use crate::ProtocolError;
use crate::config::{Certificate, TlsCipherSuite, TlsClock, Verifier};
use crate::extensions::extension_data::signature_algorithms::SignatureScheme;
use crate::handshake::{
certificate::{
Certificate as OwnedCertificate, CertificateEntryRef, CertificateRef as ServerCertificate,
},
certificate_verify::HandshakeVerifyRef,
};
use core::marker::PhantomData;
use digest::Digest;
use heapless::Vec;
#[cfg(all(not(feature = "alloc"), feature = "webpki"))]
impl TryInto<&'static webpki::SignatureAlgorithm> for SignatureScheme {
type Error = ProtocolError;
fn try_into(self) -> Result<&'static webpki::SignatureAlgorithm, Self::Error> {
#[allow(clippy::match_same_arms)]
match self {
SignatureScheme::RsaPkcs1Sha256
| SignatureScheme::RsaPkcs1Sha384
| SignatureScheme::RsaPkcs1Sha512 => Err(ProtocolError::InvalidSignatureScheme),
SignatureScheme::EcdsaSecp256r1Sha256 => Ok(&webpki::ECDSA_P256_SHA256),
SignatureScheme::EcdsaSecp384r1Sha384 => Ok(&webpki::ECDSA_P384_SHA384),
SignatureScheme::EcdsaSecp521r1Sha512 => Err(ProtocolError::InvalidSignatureScheme),
SignatureScheme::RsaPssRsaeSha256
| SignatureScheme::RsaPssRsaeSha384
| SignatureScheme::RsaPssRsaeSha512 => Err(ProtocolError::InvalidSignatureScheme),
SignatureScheme::Ed25519 => Ok(&webpki::ED25519),
SignatureScheme::Ed448
| SignatureScheme::Sha224Ecdsa
| SignatureScheme::Sha224Rsa
| SignatureScheme::Sha224Dsa => Err(ProtocolError::InvalidSignatureScheme),
SignatureScheme::RsaPssPssSha256
| SignatureScheme::RsaPssPssSha384
| SignatureScheme::RsaPssPssSha512 => Err(ProtocolError::InvalidSignatureScheme),
SignatureScheme::RsaPkcs1Sha1 | SignatureScheme::EcdsaSha1 => {
Err(ProtocolError::InvalidSignatureScheme)
}
SignatureScheme::MlDsa44 | SignatureScheme::MlDsa65 | SignatureScheme::MlDsa87 => {
Err(ProtocolError::InvalidSignatureScheme)
}
SignatureScheme::Sha256BrainpoolP256r1
| SignatureScheme::Sha384BrainpoolP384r1
| SignatureScheme::Sha512BrainpoolP512r1 => Err(ProtocolError::InvalidSignatureScheme),
}
}
}
#[cfg(all(feature = "alloc", feature = "webpki"))]
impl TryInto<&'static webpki::SignatureAlgorithm> for SignatureScheme {
type Error = ProtocolError;
fn try_into(self) -> Result<&'static webpki::SignatureAlgorithm, Self::Error> {
match self {
SignatureScheme::RsaPkcs1Sha256 => Ok(&webpki::RSA_PKCS1_2048_8192_SHA256),
SignatureScheme::RsaPkcs1Sha384 => Ok(&webpki::RSA_PKCS1_2048_8192_SHA384),
SignatureScheme::RsaPkcs1Sha512 => Ok(&webpki::RSA_PKCS1_2048_8192_SHA512),
SignatureScheme::EcdsaSecp256r1Sha256 => Ok(&webpki::ECDSA_P256_SHA256),
SignatureScheme::EcdsaSecp384r1Sha384 => Ok(&webpki::ECDSA_P384_SHA384),
SignatureScheme::EcdsaSecp521r1Sha512 => Err(ProtocolError::InvalidSignatureScheme),
SignatureScheme::RsaPssRsaeSha256 => Ok(&webpki::RSA_PSS_2048_8192_SHA256_LEGACY_KEY),
SignatureScheme::RsaPssRsaeSha384 => Ok(&webpki::RSA_PSS_2048_8192_SHA384_LEGACY_KEY),
SignatureScheme::RsaPssRsaeSha512 => Ok(&webpki::RSA_PSS_2048_8192_SHA512_LEGACY_KEY),
SignatureScheme::Ed25519 => Ok(&webpki::ED25519),
SignatureScheme::Ed448 => Err(ProtocolError::InvalidSignatureScheme),
SignatureScheme::Sha224Ecdsa => Err(ProtocolError::InvalidSignatureScheme),
SignatureScheme::Sha224Rsa => Err(ProtocolError::InvalidSignatureScheme),
SignatureScheme::Sha224Dsa => Err(ProtocolError::InvalidSignatureScheme),
SignatureScheme::RsaPssPssSha256 => Err(ProtocolError::InvalidSignatureScheme),
SignatureScheme::RsaPssPssSha384 => Err(ProtocolError::InvalidSignatureScheme),
SignatureScheme::RsaPssPssSha512 => Err(ProtocolError::InvalidSignatureScheme),
SignatureScheme::RsaPkcs1Sha1 => Err(ProtocolError::InvalidSignatureScheme),
SignatureScheme::EcdsaSha1 => Err(ProtocolError::InvalidSignatureScheme),
SignatureScheme::MlDsa44 => Err(ProtocolError::InvalidSignatureScheme),
SignatureScheme::MlDsa65 => Err(ProtocolError::InvalidSignatureScheme),
SignatureScheme::MlDsa87 => Err(ProtocolError::InvalidSignatureScheme),
SignatureScheme::Sha256BrainpoolP256r1 => Err(ProtocolError::InvalidSignatureScheme),
SignatureScheme::Sha384BrainpoolP384r1 => Err(ProtocolError::InvalidSignatureScheme),
SignatureScheme::Sha512BrainpoolP512r1 => Err(ProtocolError::InvalidSignatureScheme),
}
}
}
static ALL_SIGALGS: &[&webpki::SignatureAlgorithm] = &[
&webpki::ECDSA_P256_SHA256,
&webpki::ECDSA_P256_SHA384,
&webpki::ECDSA_P384_SHA256,
&webpki::ECDSA_P384_SHA384,
&webpki::ED25519,
];
pub struct CertVerifier<'a, CipherSuite, Clock, const CERT_SIZE: usize>
where
Clock: TlsClock,
CipherSuite: TlsCipherSuite,
{
ca: Certificate<&'a [u8]>,
host: Option<heapless::String<64>>,
certificate_transcript: Option<CipherSuite::Hash>,
certificate: Option<OwnedCertificate<CERT_SIZE>>,
_clock: PhantomData<Clock>,
}
impl<'a, CipherSuite, Clock, const CERT_SIZE: usize> CertVerifier<'a, CipherSuite, Clock, CERT_SIZE>
where
Clock: TlsClock,
CipherSuite: TlsCipherSuite,
{
#[must_use]
pub fn new(ca: Certificate<&'a [u8]>) -> Self {
Self {
ca,
host: None,
certificate_transcript: None,
certificate: None,
_clock: PhantomData,
}
}
}
impl<CipherSuite, Clock, const CERT_SIZE: usize> Verifier<CipherSuite>
for CertVerifier<'_, CipherSuite, Clock, CERT_SIZE>
where
CipherSuite: TlsCipherSuite,
Clock: TlsClock,
{
fn set_hostname_verification(&mut self, hostname: &str) -> Result<(), ProtocolError> {
self.host.replace(
heapless::String::try_from(hostname).map_err(|_| ProtocolError::InsufficientSpace)?,
);
Ok(())
}
fn verify_certificate(
&mut self,
transcript: &CipherSuite::Hash,
cert: ServerCertificate,
) -> Result<(), ProtocolError> {
verify_certificate(self.host.as_deref(), &self.ca, &cert, Clock::now())?;
self.certificate.replace(cert.try_into()?);
self.certificate_transcript.replace(transcript.clone());
Ok(())
}
fn verify_signature(&mut self, verify: HandshakeVerifyRef) -> Result<(), ProtocolError> {
let handshake_hash = unwrap!(self.certificate_transcript.take());
let ctx_str = b"TLS 1.3, server CertificateVerify\x00";
let mut msg: Vec<u8, 130> = Vec::new();
msg.resize(64, 0x20)
.map_err(|_| ProtocolError::EncodeError)?;
msg.extend_from_slice(ctx_str)
.map_err(|_| ProtocolError::EncodeError)?;
msg.extend_from_slice(&handshake_hash.finalize())
.map_err(|_| ProtocolError::EncodeError)?;
let certificate = unwrap!(self.certificate.as_ref()).try_into()?;
verify_signature(&msg[..], &certificate, &verify)?;
Ok(())
}
}
fn verify_signature(
message: &[u8],
certificate: &ServerCertificate,
verify: &HandshakeVerifyRef,
) -> Result<(), ProtocolError> {
let mut verified = false;
if !certificate.entries.is_empty() {
if let CertificateEntryRef::X509(certificate) = certificate.entries[0] {
let cert = webpki::EndEntityCert::try_from(certificate).map_err(|e| {
warn!("ProtocolError loading cert: {:?}", e);
ProtocolError::DecodeError
})?;
trace!(
"Verifying with signature scheme {:?}",
verify.signature_scheme
);
info!("Signature: {:x?}", verify.signature);
let pkisig = verify.signature_scheme.try_into()?;
match cert.verify_signature(pkisig, message, verify.signature) {
Ok(()) => {
verified = true;
}
Err(e) => {
info!("ProtocolError verifying signature: {:?}", e);
}
}
}
}
if !verified {
return Err(ProtocolError::InvalidSignature);
}
Ok(())
}
fn verify_certificate(
verify_host: Option<&str>,
ca: &Certificate<&[u8]>,
certificate: &ServerCertificate,
now: Option<u64>,
) -> Result<(), ProtocolError> {
let mut verified = false;
let mut host_verified = false;
if let Certificate::X509(ca) = ca {
let trust = webpki::TrustAnchor::try_from_cert_der(ca).map_err(|e| {
warn!("ProtocolError loading CA: {:?}", e);
ProtocolError::DecodeError
})?;
trace!("We got {} certificate entries", certificate.entries.len());
if !certificate.entries.is_empty() {
if let CertificateEntryRef::X509(certificate) = certificate.entries[0] {
let cert = webpki::EndEntityCert::try_from(certificate).map_err(|e| {
warn!("ProtocolError loading cert: {:?}", e);
ProtocolError::DecodeError
})?;
let time = if let Some(now) = now {
webpki::Time::from_seconds_since_unix_epoch(now)
} else {
webpki::Time::from_seconds_since_unix_epoch(0)
};
info!("Certificate is loaded!");
match cert.verify_for_usage(
ALL_SIGALGS,
&[trust],
&[],
time,
webpki::KeyUsage::server_auth(),
&[],
) {
Ok(()) => verified = true,
Err(e) => {
warn!("ProtocolError verifying certificate: {:?}", e);
}
}
if let Some(server_name) = verify_host {
match webpki::SubjectNameRef::try_from_ascii(server_name.as_bytes()) {
Ok(subject) => match cert.verify_is_valid_for_subject_name(subject) {
Ok(()) => host_verified = true,
Err(e) => {
warn!("ProtocolError verifying host: {:?}", e);
}
},
Err(e) => {
warn!("ProtocolError verifying host: {:?}", e);
}
}
}
}
}
}
if !verified {
return Err(ProtocolError::InvalidCertificate);
}
if !host_verified && verify_host.is_some() {
return Err(ProtocolError::InvalidCertificate);
}
Ok(())
}

157
src/certificate.rs Normal file
View File

@@ -0,0 +1,157 @@
use core::cmp::Ordering;
use der::asn1::{
BitStringRef, GeneralizedTime, IntRef, ObjectIdentifier, SequenceOf, SetOf, UtcTime,
};
use der::{AnyRef, Choice, Enumerated, Sequence, ValueOrd};
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Sequence, ValueOrd)]
pub struct AlgorithmIdentifier<'a> {
pub oid: ObjectIdentifier,
pub parameters: Option<AnyRef<'a>>,
}
#[cfg(feature = "defmt")]
impl<'a> defmt::Format for AlgorithmIdentifier<'a> {
fn format(&self, fmt: defmt::Formatter) {
defmt::write!(fmt, "AlgorithmIdentifier:{}", &self.oid.as_bytes())
}
}
pub const ECDSA_SHA256: AlgorithmIdentifier = AlgorithmIdentifier {
oid: ObjectIdentifier::new_unwrap("1.2.840.10045.4.3.2"),
parameters: None,
};
#[cfg(feature = "p384")]
pub const ECDSA_SHA384: AlgorithmIdentifier = AlgorithmIdentifier {
oid: ObjectIdentifier::new_unwrap("1.2.840.10045.4.3.3"),
parameters: None,
};
#[cfg(feature = "ed25519")]
pub const ED25519: AlgorithmIdentifier = AlgorithmIdentifier {
oid: ObjectIdentifier::new_unwrap("1.3.101.112"),
parameters: None,
};
#[cfg(feature = "rsa")]
pub const RSA_PKCS1_SHA256: AlgorithmIdentifier = AlgorithmIdentifier {
oid: ObjectIdentifier::new_unwrap("1.2.840.113549.1.1.11"),
parameters: Some(AnyRef::NULL),
};
#[cfg(feature = "rsa")]
pub const RSA_PKCS1_SHA384: AlgorithmIdentifier = AlgorithmIdentifier {
oid: ObjectIdentifier::new_unwrap("1.2.840.113549.1.1.12"),
parameters: Some(AnyRef::NULL),
};
#[cfg(feature = "rsa")]
pub const RSA_PKCS1_SHA512: AlgorithmIdentifier = AlgorithmIdentifier {
oid: ObjectIdentifier::new_unwrap("1.2.840.113549.1.1.13"),
parameters: Some(AnyRef::NULL),
};
#[derive(Debug, Clone, PartialEq, Eq, Copy, Enumerated)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[asn1(type = "INTEGER")]
#[repr(u8)]
pub enum Version {
V1 = 0,
V2 = 1,
V3 = 2,
}
impl ValueOrd for Version {
fn value_cmp(&self, other: &Self) -> der::Result<Ordering> {
(*self as u8).value_cmp(&(*other as u8))
}
}
impl Default for Version {
fn default() -> Self {
Self::V1
}
}
#[derive(Sequence, ValueOrd)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct DecodedCertificate<'a> {
pub tbs_certificate: TbsCertificate<'a>,
pub signature_algorithm: AlgorithmIdentifier<'a>,
pub signature: BitStringRef<'a>,
}
#[derive(Debug, Sequence, ValueOrd)]
pub struct AttributeTypeAndValue<'a> {
pub oid: ObjectIdentifier,
pub value: AnyRef<'a>,
}
#[cfg(feature = "defmt")]
impl<'a> defmt::Format for AttributeTypeAndValue<'a> {
fn format(&self, fmt: defmt::Formatter) {
defmt::write!(
fmt,
"Attribute:{} Value:{}",
&self.oid.as_bytes(),
&self.value.value()
)
}
}
#[derive(Debug, Choice, ValueOrd)]
pub enum Time {
#[asn1(type = "UTCTime")]
UtcTime(UtcTime),
#[asn1(type = "GeneralizedTime")]
GeneralTime(GeneralizedTime),
}
#[cfg(feature = "defmt")]
impl defmt::Format for Time {
fn format(&self, fmt: defmt::Formatter) {
match self {
Time::UtcTime(utc_time) => {
defmt::write!(fmt, "UtcTime:{}", utc_time.to_unix_duration())
}
Time::GeneralTime(generalized_time) => {
defmt::write!(fmt, "GeneralTime:{}", generalized_time.to_unix_duration())
}
}
}
}
#[derive(Debug, Sequence, ValueOrd)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct Validity {
pub not_before: Time,
pub not_after: Time,
}
#[derive(Debug, Sequence, ValueOrd)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct SubjectPublicKeyInfoRef<'a> {
pub algorithm: AlgorithmIdentifier<'a>,
pub public_key: BitStringRef<'a>,
}
#[derive(Debug, Sequence, ValueOrd)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct TbsCertificate<'a> {
#[asn1(context_specific = "0", default = "Default::default")]
pub version: Version,
pub serial_number: IntRef<'a>,
pub signature: AlgorithmIdentifier<'a>,
pub issuer: SequenceOf<SetOf<AttributeTypeAndValue<'a>, 1>, 7>,
pub validity: Validity,
pub subject: SequenceOf<SetOf<AttributeTypeAndValue<'a>, 1>, 7>,
pub subject_public_key_info: SubjectPublicKeyInfoRef<'a>,
#[asn1(context_specific = "1", tag_mode = "IMPLICIT", optional = "true")]
pub issuer_unique_id: Option<BitStringRef<'a>>,
#[asn1(context_specific = "2", tag_mode = "IMPLICIT", optional = "true")]
pub subject_unique_id: Option<BitStringRef<'a>>,
#[asn1(context_specific = "3", tag_mode = "EXPLICIT", optional = "true")]
pub extensions: Option<AnyRef<'a>>,
}

35
src/change_cipher_spec.rs Normal file
View File

@@ -0,0 +1,35 @@
use crate::ProtocolError;
use crate::buffer::CryptoBuffer;
use crate::parse_buffer::ParseBuffer;
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct ChangeCipherSpec {}
#[allow(clippy::unnecessary_wraps)]
impl ChangeCipherSpec {
pub fn new() -> Self {
Self {}
}
pub fn read(_rx_buf: &mut [u8]) -> Result<Self, ProtocolError> {
Ok(Self {})
}
#[allow(dead_code)]
pub fn parse(_: &mut ParseBuffer) -> Result<Self, ProtocolError> {
Ok(Self {})
}
#[allow(dead_code, clippy::unused_self)]
pub(crate) fn encode(self, buf: &mut CryptoBuffer<'_>) -> Result<(), ProtocolError> {
buf.push(1).map_err(|_| ProtocolError::EncodeError)?;
Ok(())
}
}
impl Default for ChangeCipherSpec {
fn default() -> Self {
ChangeCipherSpec::new()
}
}

15
src/cipher.rs Normal file
View File

@@ -0,0 +1,15 @@
use crate::application_data::ApplicationData;
use crate::extensions::extension_data::supported_groups::NamedGroup;
use p256::ecdh::SharedSecret;
pub struct CryptoEngine {}
#[allow(clippy::unused_self, clippy::needless_pass_by_value)]
impl CryptoEngine {
pub fn new(_group: NamedGroup, _shared: SharedSecret) -> Self {
Self {}
}
#[allow(dead_code)]
pub fn decrypt(&self, _: &ApplicationData) {}
}

26
src/cipher_suites.rs Normal file
View File

@@ -0,0 +1,26 @@
use crate::parse_buffer::{ParseBuffer, ParseError};
#[derive(Copy, Clone, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum CipherSuite {
TlsAes128GcmSha256 = 0x1301,
TlsAes256GcmSha384 = 0x1302,
TlsChacha20Poly1305Sha256 = 0x1303,
TlsAes128CcmSha256 = 0x1304,
TlsAes128Ccm8Sha256 = 0x1305,
TlsPskAes128GcmSha256 = 0x00A8,
}
impl CipherSuite {
pub fn parse(buf: &mut ParseBuffer) -> Result<Self, ParseError> {
match buf.read_u16()? {
v if v == Self::TlsAes128GcmSha256 as u16 => Ok(Self::TlsAes128GcmSha256),
v if v == Self::TlsAes256GcmSha384 as u16 => Ok(Self::TlsAes256GcmSha384),
v if v == Self::TlsChacha20Poly1305Sha256 as u16 => Ok(Self::TlsChacha20Poly1305Sha256),
v if v == Self::TlsAes128CcmSha256 as u16 => Ok(Self::TlsAes128CcmSha256),
v if v == Self::TlsAes128Ccm8Sha256 as u16 => Ok(Self::TlsAes128Ccm8Sha256),
v if v == Self::TlsPskAes128GcmSha256 as u16 => Ok(Self::TlsPskAes128GcmSha256),
_ => Err(ParseError::InvalidData),
}
}
}

View File

@@ -0,0 +1,24 @@
use crate::read_buffer::ReadBuffer;
#[derive(Default)]
pub struct DecryptedBufferInfo {
pub offset: usize,
pub len: usize,
pub consumed: usize,
}
impl DecryptedBufferInfo {
pub fn create_read_buffer<'b>(&'b mut self, buffer: &'b [u8]) -> ReadBuffer<'b> {
let offset = self.offset + self.consumed;
let end = self.offset + self.len;
ReadBuffer::new(&buffer[offset..end], &mut self.consumed)
}
pub fn len(&self) -> usize {
self.len - self.consumed
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}

View File

@@ -0,0 +1,52 @@
use core::ops::Range;
use crate::{
ProtocolError, alert::AlertDescription, common::decrypted_buffer_info::DecryptedBufferInfo,
config::TlsCipherSuite, handshake::ServerHandshake, record::ServerRecord,
};
pub struct DecryptedReadHandler<'a> {
pub source_buffer: Range<*const u8>,
pub buffer_info: &'a mut DecryptedBufferInfo,
pub is_open: &'a mut bool,
}
impl DecryptedReadHandler<'_> {
pub fn handle<CipherSuite: TlsCipherSuite>(
&mut self,
record: ServerRecord<'_, CipherSuite>,
) -> Result<(), ProtocolError> {
match record {
ServerRecord::ApplicationData(data) => {
let slice = data.data.as_slice();
let slice_ptrs = slice.as_ptr_range();
debug_assert!(
self.source_buffer.contains(&slice_ptrs.start)
&& self.source_buffer.contains(&slice_ptrs.end)
);
let offset =
unsafe { slice_ptrs.start.offset_from(self.source_buffer.start) as usize };
self.buffer_info.offset = offset;
self.buffer_info.len = slice.len();
self.buffer_info.consumed = 0;
Ok(())
}
ServerRecord::Alert(alert) => {
if let AlertDescription::CloseNotify = alert.description {
*self.is_open = false;
Err(ProtocolError::ConnectionClosed)
} else {
Err(ProtocolError::InternalError)
}
}
ServerRecord::ChangeCipherSpec(_) => Err(ProtocolError::InternalError),
ServerRecord::Handshake(ServerHandshake::NewSessionTicket(_)) => Ok(()),
ServerRecord::Handshake(_) => {
unimplemented!()
}
}
}
}

2
src/common/mod.rs Normal file
View File

@@ -0,0 +1,2 @@
pub mod decrypted_buffer_info;
pub mod decrypted_read_handler;

382
src/config.rs Normal file
View File

@@ -0,0 +1,382 @@
use core::marker::PhantomData;
use crate::ProtocolError;
use crate::cipher_suites::CipherSuite;
use crate::extensions::extension_data::signature_algorithms::SignatureScheme;
use crate::extensions::extension_data::supported_groups::NamedGroup;
pub use crate::handshake::certificate::{CertificateEntryRef, CertificateRef};
pub use crate::handshake::certificate_verify::HandshakeVerifyRef;
use aes_gcm::{AeadInPlace, Aes128Gcm, Aes256Gcm, KeyInit};
use digest::core_api::BlockSizeUser;
use digest::{Digest, FixedOutput, OutputSizeUser, Reset};
use ecdsa::elliptic_curve::SecretKey;
use generic_array::ArrayLength;
use heapless::Vec;
use p256::ecdsa::SigningKey;
use rand_core::CryptoRngCore;
pub use sha2::{Sha256, Sha384};
use typenum::{Sum, U10, U12, U16, U32};
pub use crate::extensions::extension_data::max_fragment_length::MaxFragmentLength;
/// Extra bytes required per record for the TLS 1.3 header, authentication tag, and inner content type.
pub const TLS_RECORD_OVERHEAD: usize = 128;
type LongestLabel = U12;
type LabelOverhead = U10;
type LabelBuffer<CipherSuite> = Sum<
<<CipherSuite as TlsCipherSuite>::Hash as OutputSizeUser>::OutputSize,
Sum<LongestLabel, LabelOverhead>,
>;
/// Associates a cipher, key/IV lengths, hash algorithm, and label buffer size for a TLS 1.3 cipher suite.
pub trait TlsCipherSuite {
const CODE_POINT: u16;
type Cipher: KeyInit<KeySize = Self::KeyLen> + AeadInPlace<NonceSize = Self::IvLen>;
type KeyLen: ArrayLength<u8>;
type IvLen: ArrayLength<u8>;
type Hash: Digest + Reset + Clone + OutputSizeUser + BlockSizeUser + FixedOutput;
type LabelBufferSize: ArrayLength<u8>;
}
pub struct Aes128GcmSha256;
impl TlsCipherSuite for Aes128GcmSha256 {
const CODE_POINT: u16 = CipherSuite::TlsAes128GcmSha256 as u16;
type Cipher = Aes128Gcm;
type KeyLen = U16;
type IvLen = U12;
type Hash = Sha256;
type LabelBufferSize = LabelBuffer<Self>;
}
pub struct Aes256GcmSha384;
impl TlsCipherSuite for Aes256GcmSha384 {
const CODE_POINT: u16 = CipherSuite::TlsAes256GcmSha384 as u16;
type Cipher = Aes256Gcm;
type KeyLen = U32;
type IvLen = U12;
type Hash = Sha384;
type LabelBufferSize = LabelBuffer<Self>;
}
/// Certificate and server-identity verification interface. Implement to enforce PKI validation.
pub trait Verifier<CipherSuite>
where
CipherSuite: TlsCipherSuite,
{
fn set_hostname_verification(&mut self, hostname: &str) -> Result<(), crate::ProtocolError>;
fn verify_certificate(
&mut self,
transcript: &CipherSuite::Hash,
cert: CertificateRef,
) -> Result<(), ProtocolError>;
fn verify_signature(&mut self, verify: HandshakeVerifyRef) -> Result<(), crate::ProtocolError>;
}
/// A [`Verifier`] that accepts any certificate without validation. Useful for testing only.
pub struct NoVerify;
impl<CipherSuite> Verifier<CipherSuite> for NoVerify
where
CipherSuite: TlsCipherSuite,
{
fn set_hostname_verification(&mut self, _hostname: &str) -> Result<(), crate::ProtocolError> {
Ok(())
}
fn verify_certificate(
&mut self,
_transcript: &CipherSuite::Hash,
_cert: CertificateRef,
) -> Result<(), ProtocolError> {
Ok(())
}
fn verify_signature(
&mut self,
_verify: HandshakeVerifyRef,
) -> Result<(), crate::ProtocolError> {
Ok(())
}
}
/// Configuration for a single TLS client connection: server name, PSK, cipher preferences, etc.
#[derive(Debug, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[must_use = "ConnectConfig does nothing unless consumed"]
pub struct ConnectConfig<'a> {
pub(crate) server_name: Option<&'a str>,
pub(crate) alpn_protocols: Option<&'a [&'a [u8]]>,
// PSK value and the list of identity labels to offer in the ClientHello
pub(crate) psk: Option<(&'a [u8], Vec<&'a [u8], 4>)>,
pub(crate) signature_schemes: Vec<SignatureScheme, 25>,
pub(crate) named_groups: Vec<NamedGroup, 13>,
pub(crate) max_fragment_length: Option<MaxFragmentLength>,
}
pub trait TlsClock {
fn now() -> Option<u64>;
}
pub struct NoClock;
impl TlsClock for NoClock {
fn now() -> Option<u64> {
None
}
}
/// Provides the RNG, cipher suite, optional certificate verifier, and optional client signing key.
pub trait CryptoBackend {
type CipherSuite: TlsCipherSuite;
type Signature: AsRef<[u8]>;
fn rng(&mut self) -> impl CryptoRngCore;
fn verifier(&mut self) -> Result<&mut impl Verifier<Self::CipherSuite>, crate::ProtocolError> {
Err::<&mut NoVerify, _>(crate::ProtocolError::Unimplemented)
}
fn signer(
&mut self,
) -> Result<(impl signature::SignerMut<Self::Signature>, SignatureScheme), crate::ProtocolError>
{
Err::<(NoSign, _), crate::ProtocolError>(crate::ProtocolError::Unimplemented)
}
fn client_cert(&mut self) -> Option<Certificate<impl AsRef<[u8]>>> {
None::<Certificate<&[u8]>>
}
}
impl<T: CryptoBackend> CryptoBackend for &mut T {
type CipherSuite = T::CipherSuite;
type Signature = T::Signature;
fn rng(&mut self) -> impl CryptoRngCore {
T::rng(self)
}
fn verifier(&mut self) -> Result<&mut impl Verifier<Self::CipherSuite>, crate::ProtocolError> {
T::verifier(self)
}
fn signer(
&mut self,
) -> Result<(impl signature::SignerMut<Self::Signature>, SignatureScheme), crate::ProtocolError>
{
T::signer(self)
}
fn client_cert(&mut self) -> Option<Certificate<impl AsRef<[u8]>>> {
T::client_cert(self)
}
}
pub struct NoSign;
impl<S> signature::Signer<S> for NoSign {
fn try_sign(&self, _msg: &[u8]) -> Result<S, signature::Error> {
unimplemented!()
}
}
/// A [`CryptoBackend`] that skips certificate verification. Suitable for testing or constrained environments.
pub struct SkipVerifyProvider<'a, CipherSuite, RNG> {
rng: RNG,
priv_key: Option<&'a [u8]>,
client_cert: Option<Certificate<&'a [u8]>>,
_marker: PhantomData<CipherSuite>,
}
impl<RNG: CryptoRngCore> SkipVerifyProvider<'_, (), RNG> {
pub fn new<CipherSuite: TlsCipherSuite>(
rng: RNG,
) -> SkipVerifyProvider<'static, CipherSuite, RNG> {
SkipVerifyProvider {
rng,
priv_key: None,
client_cert: None,
_marker: PhantomData,
}
}
}
impl<'a, CipherSuite: TlsCipherSuite, RNG: CryptoRngCore> SkipVerifyProvider<'a, CipherSuite, RNG> {
#[must_use]
pub fn with_priv_key(mut self, priv_key: &'a [u8]) -> Self {
self.priv_key = Some(priv_key);
self
}
#[must_use]
pub fn with_cert(mut self, cert: Certificate<&'a [u8]>) -> Self {
self.client_cert = Some(cert);
self
}
}
impl<CipherSuite: TlsCipherSuite, RNG: CryptoRngCore> CryptoBackend
for SkipVerifyProvider<'_, CipherSuite, RNG>
{
type CipherSuite = CipherSuite;
type Signature = p256::ecdsa::DerSignature;
fn rng(&mut self) -> impl CryptoRngCore {
&mut self.rng
}
fn signer(
&mut self,
) -> Result<(impl signature::SignerMut<Self::Signature>, SignatureScheme), crate::ProtocolError>
{
let key_der = self.priv_key.ok_or(ProtocolError::InvalidPrivateKey)?;
let secret_key =
SecretKey::from_sec1_der(key_der).map_err(|_| ProtocolError::InvalidPrivateKey)?;
Ok((
SigningKey::from(&secret_key),
SignatureScheme::EcdsaSecp256r1Sha256,
))
}
fn client_cert(&mut self) -> Option<Certificate<impl AsRef<[u8]>>> {
self.client_cert.clone()
}
}
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct ConnectContext<'a, CP>
where
CP: CryptoBackend,
{
pub(crate) config: &'a ConnectConfig<'a>,
pub(crate) crypto_provider: CP,
}
impl<'a, CP> ConnectContext<'a, CP>
where
CP: CryptoBackend,
{
pub fn new(config: &'a ConnectConfig<'a>, crypto_provider: CP) -> Self {
Self {
config,
crypto_provider,
}
}
}
impl<'a> ConnectConfig<'a> {
pub fn new() -> Self {
let mut config = Self {
signature_schemes: Vec::new(),
named_groups: Vec::new(),
max_fragment_length: None,
psk: None,
server_name: None,
alpn_protocols: None,
};
// RSA signature schemes are disabled by default to save code size; opt in via `alloc` feature
if cfg!(feature = "alloc") {
config = config.enable_rsa_signatures();
}
unwrap!(
config
.signature_schemes
.push(SignatureScheme::EcdsaSecp256r1Sha256)
.ok()
);
unwrap!(
config
.signature_schemes
.push(SignatureScheme::EcdsaSecp384r1Sha384)
.ok()
);
unwrap!(config.signature_schemes.push(SignatureScheme::Ed25519).ok());
unwrap!(config.named_groups.push(NamedGroup::Secp256r1));
config
}
pub fn enable_rsa_signatures(mut self) -> Self {
unwrap!(
self.signature_schemes
.push(SignatureScheme::RsaPkcs1Sha256)
.ok()
);
unwrap!(
self.signature_schemes
.push(SignatureScheme::RsaPkcs1Sha384)
.ok()
);
unwrap!(
self.signature_schemes
.push(SignatureScheme::RsaPkcs1Sha512)
.ok()
);
unwrap!(
self.signature_schemes
.push(SignatureScheme::RsaPssRsaeSha256)
.ok()
);
unwrap!(
self.signature_schemes
.push(SignatureScheme::RsaPssRsaeSha384)
.ok()
);
unwrap!(
self.signature_schemes
.push(SignatureScheme::RsaPssRsaeSha512)
.ok()
);
self
}
pub fn with_server_name(mut self, server_name: &'a str) -> Self {
self.server_name = Some(server_name);
self
}
pub fn with_alpn(mut self, protocols: &'a [&'a [u8]]) -> Self {
self.alpn_protocols = Some(protocols);
self
}
pub fn with_max_fragment_length(mut self, max_fragment_length: MaxFragmentLength) -> Self {
self.max_fragment_length = Some(max_fragment_length);
self
}
pub fn reset_max_fragment_length(mut self) -> Self {
self.max_fragment_length = None;
self
}
pub fn with_psk(mut self, psk: &'a [u8], identities: &[&'a [u8]]) -> Self {
self.psk = Some((psk, unwrap!(Vec::from_slice(identities).ok())));
self
}
}
impl Default for ConnectConfig<'_> {
fn default() -> Self {
ConnectConfig::new()
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Certificate<D> {
X509(D),
RawPublicKey(D),
}

643
src/connection.rs Normal file
View File

@@ -0,0 +1,643 @@
use crate::config::{ConnectConfig, TlsCipherSuite};
use crate::handshake::{ClientHandshake, ServerHandshake};
use crate::key_schedule::{KeySchedule, ReadKeySchedule, WriteKeySchedule};
use crate::record::{ClientRecord, ServerRecord};
use crate::record_reader::RecordReader;
use crate::write_buffer::WriteBuffer;
use crate::{CryptoBackend, HandshakeVerify, ProtocolError, Verifier};
use crate::{
alert::{Alert, AlertDescription, AlertLevel},
handshake::{certificate::CertificateRef, certificate_request::CertificateRequest},
};
use core::fmt::Debug;
use digest::Digest;
use embedded_io::Error as _;
use embedded_io::{Read as BlockingRead, Write as BlockingWrite};
use embedded_io_async::{Read as AsyncRead, Write as AsyncWrite};
use crate::application_data::ApplicationData;
use crate::buffer::CryptoBuffer;
use digest::generic_array::typenum::Unsigned;
use p256::ecdh::EphemeralSecret;
use signature::SignerMut;
use crate::content_types::ContentType;
use crate::parse_buffer::ParseBuffer;
use aes_gcm::aead::{AeadCore, AeadInPlace, KeyInit};
// Decrypts an ApplicationData record in-place, then dispatches the inner content type to `cb`.
// Plaintext records (Handshake, ChangeCipherSpec) are forwarded to `cb` without decryption.
pub(crate) fn decrypt_record<CipherSuite>(
key_schedule: &mut ReadKeySchedule<CipherSuite>,
record: ServerRecord<'_, CipherSuite>,
mut cb: impl FnMut(
&mut ReadKeySchedule<CipherSuite>,
ServerRecord<'_, CipherSuite>,
) -> Result<(), ProtocolError>,
) -> Result<(), ProtocolError>
where
CipherSuite: TlsCipherSuite,
{
if let ServerRecord::ApplicationData(ApplicationData {
header,
data: mut app_data,
}) = record
{
let server_key = key_schedule.get_key();
let nonce = key_schedule.get_nonce();
let crypto = <CipherSuite::Cipher as KeyInit>::new(server_key);
crypto
.decrypt_in_place(&nonce, header.data(), &mut app_data)
.map_err(|_| ProtocolError::CryptoError)?;
// Strip TLS 1.3 inner-content padding: trailing zero bytes before the real content type
let padding = app_data
.as_slice()
.iter()
.enumerate()
.rfind(|(_, b)| **b != 0);
if let Some((index, _)) = padding {
app_data.truncate(index + 1);
}
// The last byte of the decrypted payload is the actual ContentType (RFC 8446 §5.4)
let content_type = ContentType::of(*app_data.as_slice().last().unwrap())
.ok_or(ProtocolError::InvalidRecord)?;
trace!("Decrypting: content type = {:?}", content_type);
app_data.truncate(app_data.len() - 1);
let mut buf = ParseBuffer::new(app_data.as_slice());
match content_type {
ContentType::Handshake => {
while buf.remaining() > 0 {
let inner = ServerHandshake::read(&mut buf, key_schedule.transcript_hash())?;
cb(key_schedule, ServerRecord::Handshake(inner))?;
}
}
ContentType::ApplicationData => {
let inner = ApplicationData::new(app_data, header);
cb(key_schedule, ServerRecord::ApplicationData(inner))?;
}
ContentType::Alert => {
let alert = Alert::parse(&mut buf)?;
cb(key_schedule, ServerRecord::Alert(alert))?;
}
_ => return Err(ProtocolError::Unimplemented),
}
key_schedule.increment_counter();
} else {
trace!("Not decrypting: content_type = {:?}", record.content_type());
cb(key_schedule, record)?;
}
Ok(())
}
pub(crate) fn encrypt<CipherSuite>(
key_schedule: &WriteKeySchedule<CipherSuite>,
buf: &mut CryptoBuffer<'_>,
) -> Result<(), ProtocolError>
where
CipherSuite: TlsCipherSuite,
{
let client_key = key_schedule.get_key();
let nonce = key_schedule.get_nonce();
let crypto = <CipherSuite::Cipher as KeyInit>::new(client_key);
let len = buf.len() + <CipherSuite::Cipher as AeadCore>::TagSize::to_usize();
if len > buf.capacity() {
return Err(ProtocolError::InsufficientSpace);
}
trace!("output size {}", len);
let len_bytes = (len as u16).to_be_bytes();
// Additional data is the TLS record header (type=ApplicationData, legacy version 0x0303, length)
let additional_data = [
ContentType::ApplicationData as u8,
0x03,
0x03,
len_bytes[0],
len_bytes[1],
];
crypto
.encrypt_in_place(&nonce, &additional_data, buf)
.map_err(|_| ProtocolError::InvalidApplicationData)
}
/// Ephemeral state held between handshake steps — discarded once the handshake completes.
pub struct Handshake<CipherSuite>
where
CipherSuite: TlsCipherSuite,
{
// Saved pre-master transcript hash used for Finished after a certificate exchange
traffic_hash: Option<CipherSuite::Hash>,
secret: Option<EphemeralSecret>,
certificate_request: Option<CertificateRequest>,
}
impl<CipherSuite> Handshake<CipherSuite>
where
CipherSuite: TlsCipherSuite,
{
pub fn new() -> Handshake<CipherSuite> {
Handshake {
traffic_hash: None,
secret: None,
certificate_request: None,
}
}
}
/// TLS handshake state machine. Drives the client through all stages of the TLS 1.3 handshake.
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum State {
ClientHello,
ServerHello,
ServerVerify,
ClientCert,
ClientCertVerify,
ClientFinished,
ApplicationData,
}
impl<'a> State {
#[allow(clippy::too_many_arguments)]
pub async fn process<'v, Transport, CP>(
self,
transport: &mut Transport,
handshake: &mut Handshake<CP::CipherSuite>,
record_reader: &mut RecordReader<'_>,
tx_buf: &mut WriteBuffer<'_>,
key_schedule: &mut KeySchedule<CP::CipherSuite>,
config: &ConnectConfig<'a>,
crypto_provider: &mut CP,
) -> Result<State, ProtocolError>
where
Transport: AsyncRead + AsyncWrite + 'a,
CP: CryptoBackend,
{
match self {
State::ClientHello => {
let (state, tx) =
client_hello(key_schedule, config, crypto_provider, tx_buf, handshake)?;
respond(tx, transport, key_schedule).await?;
Ok(state)
}
State::ServerHello => {
let record = record_reader
.read(transport, key_schedule.read_state())
.await?;
let result = process_server_hello(handshake, key_schedule, record);
handle_processing_error(result, transport, key_schedule, tx_buf).await
}
State::ServerVerify => {
let record = record_reader
.read(transport, key_schedule.read_state())
.await?;
let result =
process_server_verify(handshake, key_schedule, crypto_provider, record);
handle_processing_error(result, transport, key_schedule, tx_buf).await
}
State::ClientCert => {
let (state, tx) = client_cert(handshake, key_schedule, crypto_provider, tx_buf)?;
respond(tx, transport, key_schedule).await?;
Ok(state)
}
State::ClientCertVerify => {
let (result, tx) = client_cert_verify(key_schedule, crypto_provider, tx_buf)?;
respond(tx, transport, key_schedule).await?;
result
}
State::ClientFinished => {
let tx = client_finished(key_schedule, tx_buf)?;
respond(tx, transport, key_schedule).await?;
client_finished_finalize(key_schedule, handshake)
}
State::ApplicationData => Ok(State::ApplicationData),
}
}
#[allow(clippy::too_many_arguments)]
pub fn process_blocking<'v, Transport, CP>(
self,
transport: &mut Transport,
handshake: &mut Handshake<CP::CipherSuite>,
record_reader: &mut RecordReader<'_>,
tx_buf: &mut WriteBuffer,
key_schedule: &mut KeySchedule<CP::CipherSuite>,
config: &ConnectConfig<'a>,
crypto_provider: &mut CP,
) -> Result<State, ProtocolError>
where
Transport: BlockingRead + BlockingWrite + 'a,
CP: CryptoBackend,
{
match self {
State::ClientHello => {
let (state, tx) =
client_hello(key_schedule, config, crypto_provider, tx_buf, handshake)?;
respond_blocking(tx, transport, key_schedule)?;
Ok(state)
}
State::ServerHello => {
let record = record_reader.read_blocking(transport, key_schedule.read_state())?;
let result = process_server_hello(handshake, key_schedule, record);
handle_processing_error_blocking(result, transport, key_schedule, tx_buf)
}
State::ServerVerify => {
let record = record_reader.read_blocking(transport, key_schedule.read_state())?;
let result =
process_server_verify(handshake, key_schedule, crypto_provider, record);
handle_processing_error_blocking(result, transport, key_schedule, tx_buf)
}
State::ClientCert => {
let (state, tx) = client_cert(handshake, key_schedule, crypto_provider, tx_buf)?;
respond_blocking(tx, transport, key_schedule)?;
Ok(state)
}
State::ClientCertVerify => {
let (result, tx) = client_cert_verify(key_schedule, crypto_provider, tx_buf)?;
respond_blocking(tx, transport, key_schedule)?;
result
}
State::ClientFinished => {
let tx = client_finished(key_schedule, tx_buf)?;
respond_blocking(tx, transport, key_schedule)?;
client_finished_finalize(key_schedule, handshake)
}
State::ApplicationData => Ok(State::ApplicationData),
}
}
}
fn handle_processing_error_blocking<CipherSuite>(
result: Result<State, ProtocolError>,
transport: &mut impl BlockingWrite,
key_schedule: &mut KeySchedule<CipherSuite>,
tx_buf: &mut WriteBuffer,
) -> Result<State, ProtocolError>
where
CipherSuite: TlsCipherSuite,
{
if let Err(ProtocolError::AbortHandshake(level, description)) = result {
let (write_key_schedule, read_key_schedule) = key_schedule.as_split();
let tx = tx_buf.write_record(
&ClientRecord::Alert(Alert { level, description }, false),
write_key_schedule,
Some(read_key_schedule),
)?;
respond_blocking(tx, transport, key_schedule)?;
}
result
}
fn respond_blocking<CipherSuite>(
tx: &[u8],
transport: &mut impl BlockingWrite,
key_schedule: &mut KeySchedule<CipherSuite>,
) -> Result<(), ProtocolError>
where
CipherSuite: TlsCipherSuite,
{
transport
.write_all(tx)
.map_err(|e| ProtocolError::Io(e.kind()))?;
key_schedule.write_state().increment_counter();
transport.flush().map_err(|e| ProtocolError::Io(e.kind()))?;
Ok(())
}
async fn handle_processing_error<CipherSuite>(
result: Result<State, ProtocolError>,
transport: &mut impl AsyncWrite,
key_schedule: &mut KeySchedule<CipherSuite>,
tx_buf: &mut WriteBuffer<'_>,
) -> Result<State, ProtocolError>
where
CipherSuite: TlsCipherSuite,
{
if let Err(ProtocolError::AbortHandshake(level, description)) = result {
let (write_key_schedule, read_key_schedule) = key_schedule.as_split();
let tx = tx_buf.write_record(
&ClientRecord::Alert(Alert { level, description }, false),
write_key_schedule,
Some(read_key_schedule),
)?;
respond(tx, transport, key_schedule).await?;
}
result
}
async fn respond<CipherSuite>(
tx: &[u8],
transport: &mut impl AsyncWrite,
key_schedule: &mut KeySchedule<CipherSuite>,
) -> Result<(), ProtocolError>
where
CipherSuite: TlsCipherSuite,
{
transport
.write_all(tx)
.await
.map_err(|e| ProtocolError::Io(e.kind()))?;
key_schedule.write_state().increment_counter();
transport
.flush()
.await
.map_err(|e| ProtocolError::Io(e.kind()))?;
Ok(())
}
fn client_hello<'r, CP>(
key_schedule: &mut KeySchedule<CP::CipherSuite>,
config: &ConnectConfig,
crypto_provider: &mut CP,
tx_buf: &'r mut WriteBuffer,
handshake: &mut Handshake<CP::CipherSuite>,
) -> Result<(State, &'r [u8]), ProtocolError>
where
CP: CryptoBackend,
{
key_schedule.initialize_early_secret(config.psk.as_ref().map(|p| p.0))?;
let (write_key_schedule, read_key_schedule) = key_schedule.as_split();
let client_hello = ClientRecord::client_hello(config, crypto_provider);
let slice = tx_buf.write_record(&client_hello, write_key_schedule, Some(read_key_schedule))?;
if let ClientRecord::Handshake(ClientHandshake::ClientHello(client_hello), _) = client_hello {
handshake.secret.replace(client_hello.secret);
Ok((State::ServerHello, slice))
} else {
Err(ProtocolError::EncodeError)
}
}
fn process_server_hello<CipherSuite>(
handshake: &mut Handshake<CipherSuite>,
key_schedule: &mut KeySchedule<CipherSuite>,
record: ServerRecord<'_, CipherSuite>,
) -> Result<State, ProtocolError>
where
CipherSuite: TlsCipherSuite,
{
match record {
ServerRecord::Handshake(server_handshake) => match server_handshake {
ServerHandshake::ServerHello(server_hello) => {
trace!("********* ServerHello");
let secret = handshake
.secret
.take()
.ok_or(ProtocolError::InvalidHandshake)?;
let shared = server_hello
.calculate_shared_secret(&secret)
.ok_or(ProtocolError::InvalidKeyShare)?;
key_schedule.initialize_handshake_secret(shared.raw_secret_bytes())?;
Ok(State::ServerVerify)
}
_ => Err(ProtocolError::InvalidHandshake),
},
ServerRecord::Alert(alert) => Err(ProtocolError::HandshakeAborted(
alert.level,
alert.description,
)),
_ => Err(ProtocolError::InvalidRecord),
}
}
fn process_server_verify<CP>(
handshake: &mut Handshake<CP::CipherSuite>,
key_schedule: &mut KeySchedule<CP::CipherSuite>,
crypto_provider: &mut CP,
record: ServerRecord<'_, CP::CipherSuite>,
) -> Result<State, ProtocolError>
where
CP: CryptoBackend,
{
let mut state = State::ServerVerify;
decrypt_record(key_schedule.read_state(), record, |key_schedule, record| {
match record {
ServerRecord::Handshake(server_handshake) => {
match server_handshake {
ServerHandshake::EncryptedExtensions(_) => {}
ServerHandshake::Certificate(certificate) => {
let transcript = key_schedule.transcript_hash();
if let Ok(verifier) = crypto_provider.verifier() {
verifier.verify_certificate(transcript, certificate)?;
debug!("Certificate verified!");
} else {
debug!("Certificate verification skipped due to no verifier!");
}
}
ServerHandshake::HandshakeVerify(verify) => {
if let Ok(verifier) = crypto_provider.verifier() {
verifier.verify_signature(verify)?;
debug!("Signature verified!");
} else {
debug!("Signature verification skipped due to no verifier!");
}
}
ServerHandshake::CertificateRequest(request) => {
handshake.certificate_request.replace(request.try_into()?);
}
ServerHandshake::Finished(finished) => {
if !key_schedule.verify_server_finished(&finished)? {
warn!("Server signature verification failed");
return Err(ProtocolError::InvalidSignature);
}
// If the server sent a CertificateRequest we must respond with a cert before Finished
state = if handshake.certificate_request.is_some() {
State::ClientCert
} else {
handshake
.traffic_hash
.replace(key_schedule.transcript_hash().clone());
State::ClientFinished
};
}
_ => return Err(ProtocolError::InvalidHandshake),
}
}
ServerRecord::ChangeCipherSpec(_) => {}
_ => return Err(ProtocolError::InvalidRecord),
}
Ok(())
})?;
Ok(state)
}
fn client_cert<'r, CP>(
handshake: &mut Handshake<CP::CipherSuite>,
key_schedule: &mut KeySchedule<CP::CipherSuite>,
crypto_provider: &mut CP,
buffer: &'r mut WriteBuffer,
) -> Result<(State, &'r [u8]), ProtocolError>
where
CP: CryptoBackend,
{
handshake
.traffic_hash
.replace(key_schedule.transcript_hash().clone());
let request_context = &handshake
.certificate_request
.as_ref()
.ok_or(ProtocolError::InvalidHandshake)?
.request_context;
let cert = crypto_provider.client_cert();
let mut certificate = CertificateRef::with_context(request_context);
let next_state = if let Some(ref cert) = cert {
certificate.add(cert.into())?;
State::ClientCertVerify
} else {
State::ClientFinished
};
let (write_key_schedule, read_key_schedule) = key_schedule.as_split();
buffer
.write_record(
&ClientRecord::Handshake(ClientHandshake::ClientCert(certificate), true),
write_key_schedule,
Some(read_key_schedule),
)
.map(|slice| (next_state, slice))
}
fn client_cert_verify<'r, CP>(
key_schedule: &mut KeySchedule<CP::CipherSuite>,
crypto_provider: &mut CP,
buffer: &'r mut WriteBuffer,
) -> Result<(Result<State, ProtocolError>, &'r [u8]), ProtocolError>
where
CP: CryptoBackend,
{
let (result, record) = match crypto_provider.signer() {
Ok((mut signing_key, signature_scheme)) => {
// CertificateVerify message format: 64 spaces + context string + \0 + transcript hash (RFC 8446 §4.4.3)
let ctx_str = b"TLS 1.3, client CertificateVerify\x00";
let mut msg: heapless::Vec<u8, 146> = heapless::Vec::new();
msg.resize(64, 0x20)
.map_err(|_| ProtocolError::EncodeError)?;
msg.extend_from_slice(ctx_str)
.map_err(|_| ProtocolError::EncodeError)?;
msg.extend_from_slice(&key_schedule.transcript_hash().clone().finalize())
.map_err(|_| ProtocolError::EncodeError)?;
let signature = signing_key.sign(&msg);
trace!(
"Signature: {:?} ({})",
signature.as_ref(),
signature.as_ref().len()
);
let certificate_verify = HandshakeVerify {
signature_scheme,
signature: heapless::Vec::from_slice(signature.as_ref()).unwrap(),
};
(
Ok(State::ClientFinished),
ClientRecord::Handshake(
ClientHandshake::ClientCertVerify(certificate_verify),
true,
),
)
}
Err(e) => {
error!("Failed to obtain signing key: {:?}", e);
(
Err(e),
ClientRecord::Alert(
Alert::new(AlertLevel::Warning, AlertDescription::CloseNotify),
true,
),
)
}
};
let (write_key_schedule, read_key_schedule) = key_schedule.as_split();
buffer
.write_record(&record, write_key_schedule, Some(read_key_schedule))
.map(|slice| (result, slice))
}
fn client_finished<'r, CipherSuite>(
key_schedule: &mut KeySchedule<CipherSuite>,
buffer: &'r mut WriteBuffer,
) -> Result<&'r [u8], ProtocolError>
where
CipherSuite: TlsCipherSuite,
{
let client_finished = key_schedule
.create_client_finished()
.map_err(|_| ProtocolError::InvalidHandshake)?;
let (write_key_schedule, read_key_schedule) = key_schedule.as_split();
buffer.write_record(
&ClientRecord::Handshake(ClientHandshake::Finished(client_finished), true),
write_key_schedule,
Some(read_key_schedule),
)
}
fn client_finished_finalize<CipherSuite>(
key_schedule: &mut KeySchedule<CipherSuite>,
handshake: &mut Handshake<CipherSuite>,
) -> Result<State, ProtocolError>
where
CipherSuite: TlsCipherSuite,
{
// Restore the transcript hash captured before the client cert exchange, then derive app traffic secrets
key_schedule.replace_transcript_hash(
handshake
.traffic_hash
.take()
.ok_or(ProtocolError::InvalidHandshake)?,
);
key_schedule.initialize_master_secret()?;
Ok(State::ApplicationData)
}

22
src/content_types.rs Normal file
View File

@@ -0,0 +1,22 @@
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum ContentType {
Invalid = 0,
ChangeCipherSpec = 20,
Alert = 21,
Handshake = 22,
ApplicationData = 23,
}
impl ContentType {
pub fn of(num: u8) -> Option<Self> {
match num {
0 => Some(Self::Invalid),
20 => Some(Self::ChangeCipherSpec),
21 => Some(Self::Alert),
22 => Some(Self::Handshake),
23 => Some(Self::ApplicationData),
_ => None,
}
}
}

View File

@@ -0,0 +1,39 @@
use crate::{
ProtocolError,
buffer::CryptoBuffer,
parse_buffer::{ParseBuffer, ParseError},
};
#[derive(Debug, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct AlpnProtocolNameList<'a> {
pub protocols: &'a [&'a [u8]],
}
impl<'a> AlpnProtocolNameList<'a> {
pub fn parse(buf: &mut ParseBuffer<'a>) -> Result<Self, ParseError> {
let list_len = buf.read_u16()? as usize;
let mut list_buf = buf.slice(list_len)?;
while !list_buf.is_empty() {
let name_len = list_buf.read_u8()? as usize;
if name_len == 0 {
return Err(ParseError::InvalidData);
}
let _name = list_buf.slice(name_len)?;
}
Ok(Self { protocols: &[] })
}
pub fn encode(&self, buf: &mut CryptoBuffer) -> Result<(), ProtocolError> {
buf.with_u16_length(|buf| {
for protocol in self.protocols {
buf.push(protocol.len() as u8)
.map_err(|_| ProtocolError::EncodeError)?;
buf.extend_from_slice(protocol)?;
}
Ok(())
})
}
}

View File

@@ -0,0 +1,128 @@
use heapless::Vec;
use crate::buffer::CryptoBuffer;
use crate::extensions::extension_data::supported_groups::NamedGroup;
use crate::ProtocolError;
use crate::parse_buffer::{ParseBuffer, ParseError};
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct KeyShareServerHello<'a>(pub KeyShareEntry<'a>);
impl<'a> KeyShareServerHello<'a> {
pub fn parse(buf: &mut ParseBuffer<'a>) -> Result<Self, ParseError> {
Ok(KeyShareServerHello(KeyShareEntry::parse(buf)?))
}
pub fn encode(&self, buf: &mut CryptoBuffer) -> Result<(), ProtocolError> {
self.0.encode(buf)
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct KeyShareClientHello<'a, const N: usize> {
pub client_shares: Vec<KeyShareEntry<'a>, N>,
}
impl<'a, const N: usize> KeyShareClientHello<'a, N> {
pub fn parse(buf: &mut ParseBuffer<'a>) -> Result<Self, ParseError> {
let len = buf.read_u16()? as usize;
Ok(KeyShareClientHello {
client_shares: buf.read_list(len, KeyShareEntry::parse)?,
})
}
pub fn encode(&self, buf: &mut CryptoBuffer) -> Result<(), ProtocolError> {
buf.with_u16_length(|buf| {
for client_share in &self.client_shares {
client_share.encode(buf)?;
}
Ok(())
})
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct KeyShareHelloRetryRequest {
pub selected_group: NamedGroup,
}
#[allow(dead_code)]
impl KeyShareHelloRetryRequest {
pub fn parse(buf: &mut ParseBuffer) -> Result<Self, ParseError> {
Ok(Self {
selected_group: NamedGroup::parse(buf)?,
})
}
pub fn encode(&self, buf: &mut CryptoBuffer) -> Result<(), ProtocolError> {
self.selected_group.encode(buf)
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct KeyShareEntry<'a> {
pub(crate) group: NamedGroup,
pub(crate) opaque: &'a [u8],
}
impl<'a> KeyShareEntry<'a> {
pub fn parse(buf: &mut ParseBuffer<'a>) -> Result<Self, ParseError> {
let group = NamedGroup::parse(buf)?;
let opaque_len = buf.read_u16()?;
let opaque = buf.slice(opaque_len as usize)?;
Ok(Self {
group,
opaque: opaque.as_slice(),
})
}
pub fn encode(&self, buf: &mut CryptoBuffer) -> Result<(), ProtocolError> {
self.group.encode(buf)?;
buf.with_u16_length(|buf| buf.extend_from_slice(self.opaque))
.map_err(|_| ProtocolError::EncodeError)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Once;
static INIT: Once = Once::new();
fn setup() {
INIT.call_once(|| {
env_logger::init();
});
}
#[test]
fn test_parse_empty() {
setup();
let buffer = [0x00, 0x17, 0x00, 0x00];
let result = KeyShareEntry::parse(&mut ParseBuffer::new(&buffer)).unwrap();
assert_eq!(NamedGroup::Secp256r1, result.group);
assert_eq!(0, result.opaque.len());
}
#[test]
fn test_parse() {
setup();
let buffer = [0x00, 0x17, 0x00, 0x02, 0xAA, 0xBB];
let result = KeyShareEntry::parse(&mut ParseBuffer::new(&buffer)).unwrap();
assert_eq!(NamedGroup::Secp256r1, result.group);
assert_eq!(2, result.opaque.len());
assert_eq!([0xAA, 0xBB], result.opaque);
}
}

View File

@@ -0,0 +1,34 @@
use crate::{
ProtocolError,
buffer::CryptoBuffer,
parse_buffer::{ParseBuffer, ParseError},
};
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum MaxFragmentLength {
Bits9 = 1,
Bits10 = 2,
Bits11 = 3,
Bits12 = 4,
}
impl MaxFragmentLength {
pub fn parse(buf: &mut ParseBuffer) -> Result<Self, ParseError> {
match buf.read_u8()? {
1 => Ok(Self::Bits9),
2 => Ok(Self::Bits10),
3 => Ok(Self::Bits11),
4 => Ok(Self::Bits12),
other => {
warn!("Read unknown MaxFragmentLength: {}", other);
Err(ParseError::InvalidData)
}
}
}
pub fn encode(&self, buf: &mut CryptoBuffer) -> Result<(), ProtocolError> {
buf.push(*self as u8)
.map_err(|_| ProtocolError::EncodeError)
}
}

View File

@@ -0,0 +1,11 @@
pub mod alpn;
pub mod key_share;
pub mod max_fragment_length;
pub mod pre_shared_key;
pub mod psk_key_exchange_modes;
pub mod server_name;
pub mod signature_algorithms;
pub mod signature_algorithms_cert;
pub mod supported_groups;
pub mod supported_versions;
pub mod unimplemented;

View File

@@ -0,0 +1,61 @@
use crate::buffer::CryptoBuffer;
use crate::ProtocolError;
use crate::parse_buffer::{ParseBuffer, ParseError};
use heapless::Vec;
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct PreSharedKeyClientHello<'a, const N: usize> {
pub identities: Vec<&'a [u8], N>,
pub hash_size: usize,
}
impl<const N: usize> PreSharedKeyClientHello<'_, N> {
pub fn parse(_buf: &mut ParseBuffer) -> Result<Self, ParseError> {
unimplemented!()
}
pub fn encode(&self, buf: &mut CryptoBuffer) -> Result<(), ProtocolError> {
buf.with_u16_length(|buf| {
for identity in &self.identities {
buf.with_u16_length(|buf| buf.extend_from_slice(identity))
.map_err(|_| ProtocolError::EncodeError)?;
buf.push_u32(0).map_err(|_| ProtocolError::EncodeError)?;
}
Ok(())
})
.map_err(|_| ProtocolError::EncodeError)?;
let binders_len = (1 + self.hash_size) * self.identities.len();
buf.push_u16(binders_len as u16)
.map_err(|_| ProtocolError::EncodeError)?;
for _ in 0..binders_len {
buf.push(0).map_err(|_| ProtocolError::EncodeError)?;
}
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct PreSharedKeyServerHello {
pub selected_identity: u16,
}
impl PreSharedKeyServerHello {
pub fn parse(buf: &mut ParseBuffer) -> Result<Self, ParseError> {
Ok(Self {
selected_identity: buf.read_u16()?,
})
}
pub fn encode(self, buf: &mut CryptoBuffer) -> Result<(), ProtocolError> {
buf.push_u16(self.selected_identity)
.map_err(|_| ProtocolError::EncodeError)
}
}

View File

@@ -0,0 +1,53 @@
use crate::buffer::CryptoBuffer;
use crate::ProtocolError;
use crate::parse_buffer::{ParseBuffer, ParseError};
use heapless::Vec;
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum PskKeyExchangeMode {
PskKe = 0,
PskDheKe = 1,
}
impl PskKeyExchangeMode {
fn parse(buf: &mut ParseBuffer) -> Result<Self, ParseError> {
match buf.read_u8()? {
0 => Ok(Self::PskKe),
1 => Ok(Self::PskDheKe),
other => {
warn!("Read unknown PskKeyExchangeMode: {}", other);
Err(ParseError::InvalidData)
}
}
}
fn encode(self, buf: &mut CryptoBuffer) -> Result<(), ProtocolError> {
buf.push(self as u8).map_err(|_| ProtocolError::EncodeError)
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct PskKeyExchangeModes<const N: usize> {
pub modes: Vec<PskKeyExchangeMode, N>,
}
impl<const N: usize> PskKeyExchangeModes<N> {
pub fn parse(buf: &mut ParseBuffer) -> Result<Self, ParseError> {
let data_length = buf.read_u8()? as usize;
Ok(Self {
modes: buf.read_list::<_, N>(data_length, PskKeyExchangeMode::parse)?,
})
}
pub fn encode(&self, buf: &mut CryptoBuffer) -> Result<(), ProtocolError> {
buf.with_u8_length(|buf| {
for mode in &self.modes {
mode.encode(buf)?;
}
Ok(())
})
}
}

View File

@@ -0,0 +1,124 @@
use heapless::Vec;
use crate::{
ProtocolError,
buffer::CryptoBuffer,
extensions::ExtensionType,
parse_buffer::{ParseBuffer, ParseError},
};
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum NameType {
HostName = 0,
}
impl NameType {
pub fn parse(buf: &mut ParseBuffer) -> Result<Self, ParseError> {
match buf.read_u8()? {
0 => Ok(Self::HostName),
other => {
warn!("Read unknown NameType: {}", other);
Err(ParseError::InvalidData)
}
}
}
pub fn encode(self, buf: &mut CryptoBuffer) -> Result<(), ProtocolError> {
buf.push(self as u8).map_err(|_| ProtocolError::EncodeError)
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct ServerName<'a> {
pub name_type: NameType,
pub name: &'a str,
}
impl<'a> ServerName<'a> {
pub fn parse(buf: &mut ParseBuffer<'a>) -> Result<ServerName<'a>, ParseError> {
let name_type = NameType::parse(buf)?;
let name_len = buf.read_u16()?;
let name = buf.slice(name_len as usize)?.as_slice();
if name.is_ascii() {
Ok(ServerName {
name_type,
name: core::str::from_utf8(name).map_err(|_| ParseError::InvalidData)?,
})
} else {
Err(ParseError::InvalidData)
}
}
pub fn encode(&self, buf: &mut CryptoBuffer) -> Result<(), ProtocolError> {
self.name_type.encode(buf)?;
buf.with_u16_length(|buf| buf.extend_from_slice(self.name.as_bytes()))
.map_err(|_| ProtocolError::EncodeError)
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct ServerNameList<'a, const N: usize> {
pub names: Vec<ServerName<'a>, N>,
}
impl<'a> ServerNameList<'a, 1> {
pub fn single(server_name: &'a str) -> Self {
let mut names = Vec::<_, 1>::new();
names
.push(ServerName {
name_type: NameType::HostName,
name: server_name,
})
.unwrap();
ServerNameList { names }
}
}
impl<'a, const N: usize> ServerNameList<'a, N> {
#[allow(dead_code)]
pub const EXTENSION_TYPE: ExtensionType = ExtensionType::ServerName;
pub fn parse(buf: &mut ParseBuffer<'a>) -> Result<ServerNameList<'a, N>, ParseError> {
let data_length = buf.read_u16()? as usize;
Ok(Self {
names: buf.read_list::<_, N>(data_length, ServerName::parse)?,
})
}
pub fn encode(&self, buf: &mut CryptoBuffer) -> Result<(), ProtocolError> {
buf.with_u16_length(|buf| {
for name in &self.names {
name.encode(buf)?;
}
Ok(())
})
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct ServerNameResponse;
impl ServerNameResponse {
pub fn parse(buf: &mut ParseBuffer) -> Result<Self, ParseError> {
if buf.is_empty() {
Ok(Self)
} else {
Err(ParseError::InvalidData)
}
}
#[allow(clippy::unused_self, clippy::unnecessary_wraps)]
pub fn encode(&self, _buf: &mut CryptoBuffer) -> Result<(), ProtocolError> {
Ok(())
}
}

View File

@@ -0,0 +1,153 @@
use crate::{
ProtocolError,
buffer::CryptoBuffer,
parse_buffer::{ParseBuffer, ParseError},
};
use heapless::Vec;
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum SignatureScheme {
RsaPkcs1Sha256,
RsaPkcs1Sha384,
RsaPkcs1Sha512,
EcdsaSecp256r1Sha256,
EcdsaSecp384r1Sha384,
EcdsaSecp521r1Sha512,
RsaPssRsaeSha256,
RsaPssRsaeSha384,
RsaPssRsaeSha512,
Ed25519,
Ed448,
RsaPssPssSha256,
RsaPssPssSha384,
RsaPssPssSha512,
Sha224Ecdsa,
Sha224Rsa,
Sha224Dsa,
RsaPkcs1Sha1,
EcdsaSha1,
Sha256BrainpoolP256r1,
Sha384BrainpoolP384r1,
Sha512BrainpoolP512r1,
MlDsa44,
MlDsa65,
MlDsa87,
}
impl SignatureScheme {
pub fn parse(buf: &mut ParseBuffer) -> Result<Self, ParseError> {
match buf.read_u16()? {
0x0401 => Ok(Self::RsaPkcs1Sha256),
0x0501 => Ok(Self::RsaPkcs1Sha384),
0x0601 => Ok(Self::RsaPkcs1Sha512),
0x0403 => Ok(Self::EcdsaSecp256r1Sha256),
0x0503 => Ok(Self::EcdsaSecp384r1Sha384),
0x0603 => Ok(Self::EcdsaSecp521r1Sha512),
0x0804 => Ok(Self::RsaPssRsaeSha256),
0x0805 => Ok(Self::RsaPssRsaeSha384),
0x0806 => Ok(Self::RsaPssRsaeSha512),
0x0807 => Ok(Self::Ed25519),
0x0808 => Ok(Self::Ed448),
0x0809 => Ok(Self::RsaPssPssSha256),
0x080a => Ok(Self::RsaPssPssSha384),
0x080b => Ok(Self::RsaPssPssSha512),
0x0303 => Ok(Self::Sha224Ecdsa),
0x0301 => Ok(Self::Sha224Rsa),
0x0302 => Ok(Self::Sha224Dsa),
0x0201 => Ok(Self::RsaPkcs1Sha1),
0x0203 => Ok(Self::EcdsaSha1),
0x081A => Ok(Self::Sha256BrainpoolP256r1),
0x081B => Ok(Self::Sha384BrainpoolP384r1),
0x081C => Ok(Self::Sha512BrainpoolP512r1),
0x0904 => Ok(Self::MlDsa44),
0x0905 => Ok(Self::MlDsa65),
0x0906 => Ok(Self::MlDsa87),
_ => Err(ParseError::InvalidData),
}
}
#[must_use]
pub fn as_u16(self) -> u16 {
match self {
Self::RsaPkcs1Sha256 => 0x0401,
Self::RsaPkcs1Sha384 => 0x0501,
Self::RsaPkcs1Sha512 => 0x0601,
Self::EcdsaSecp256r1Sha256 => 0x0403,
Self::EcdsaSecp384r1Sha384 => 0x0503,
Self::EcdsaSecp521r1Sha512 => 0x0603,
Self::RsaPssRsaeSha256 => 0x0804,
Self::RsaPssRsaeSha384 => 0x0805,
Self::RsaPssRsaeSha512 => 0x0806,
Self::Ed25519 => 0x0807,
Self::Ed448 => 0x0808,
Self::RsaPssPssSha256 => 0x0809,
Self::RsaPssPssSha384 => 0x080a,
Self::RsaPssPssSha512 => 0x080b,
Self::Sha224Ecdsa => 0x0303,
Self::Sha224Rsa => 0x0301,
Self::Sha224Dsa => 0x0302,
Self::RsaPkcs1Sha1 => 0x0201,
Self::EcdsaSha1 => 0x0203,
Self::Sha256BrainpoolP256r1 => 0x081A,
Self::Sha384BrainpoolP384r1 => 0x081B,
Self::Sha512BrainpoolP512r1 => 0x081C,
Self::MlDsa44 => 0x0904,
Self::MlDsa65 => 0x0905,
Self::MlDsa87 => 0x0906,
}
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct SignatureAlgorithms<const N: usize> {
pub supported_signature_algorithms: Vec<SignatureScheme, N>,
}
impl<const N: usize> SignatureAlgorithms<N> {
pub fn parse(buf: &mut ParseBuffer) -> Result<Self, ParseError> {
let data_length = buf.read_u16()? as usize;
Ok(Self {
supported_signature_algorithms: buf
.read_list::<_, N>(data_length, SignatureScheme::parse)?,
})
}
pub fn encode(&self, buf: &mut CryptoBuffer) -> Result<(), ProtocolError> {
buf.with_u16_length(|buf| {
for &a in &self.supported_signature_algorithms {
buf.push_u16(a.as_u16())
.map_err(|_| ProtocolError::EncodeError)?;
}
Ok(())
})
}
}

View File

@@ -0,0 +1,34 @@
use crate::buffer::CryptoBuffer;
use crate::extensions::extension_data::signature_algorithms::SignatureScheme;
use crate::ProtocolError;
use crate::parse_buffer::{ParseBuffer, ParseError};
use heapless::Vec;
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct SignatureAlgorithmsCert<const N: usize> {
pub supported_signature_algorithms: Vec<SignatureScheme, N>,
}
impl<const N: usize> SignatureAlgorithmsCert<N> {
pub fn parse(buf: &mut ParseBuffer) -> Result<Self, ParseError> {
let data_length = buf.read_u16()? as usize;
Ok(Self {
supported_signature_algorithms: buf
.read_list::<_, N>(data_length, SignatureScheme::parse)?,
})
}
pub fn encode(&self, buf: &mut CryptoBuffer) -> Result<(), ProtocolError> {
buf.with_u16_length(|buf| {
for &a in &self.supported_signature_algorithms {
buf.push_u16(a.as_u16())
.map_err(|_| ProtocolError::EncodeError)?;
}
Ok(())
})
}
}

View File

@@ -0,0 +1,101 @@
use heapless::Vec;
use crate::{
ProtocolError,
buffer::CryptoBuffer,
parse_buffer::{ParseBuffer, ParseError},
};
#[derive(Copy, Clone, Debug, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum NamedGroup {
Secp256r1,
Secp384r1,
Secp521r1,
X25519,
X448,
Ffdhe2048,
Ffdhe3072,
Ffdhe4096,
Ffdhe6144,
Ffdhe8192,
X25519MLKEM768,
SecP256r1MLKEM768,
SecP384r1MLKEM1024,
}
impl NamedGroup {
pub fn parse(buf: &mut ParseBuffer) -> Result<Self, ParseError> {
match buf.read_u16()? {
0x0017 => Ok(Self::Secp256r1),
0x0018 => Ok(Self::Secp384r1),
0x0019 => Ok(Self::Secp521r1),
0x001D => Ok(Self::X25519),
0x001E => Ok(Self::X448),
0x0100 => Ok(Self::Ffdhe2048),
0x0101 => Ok(Self::Ffdhe3072),
0x0102 => Ok(Self::Ffdhe4096),
0x0103 => Ok(Self::Ffdhe6144),
0x0104 => Ok(Self::Ffdhe8192),
0x11EB => Ok(Self::SecP256r1MLKEM768),
0x11EC => Ok(Self::X25519MLKEM768),
0x11ED => Ok(Self::SecP384r1MLKEM1024),
_ => Err(ParseError::InvalidData),
}
}
pub fn as_u16(self) -> u16 {
match self {
Self::Secp256r1 => 0x0017,
Self::Secp384r1 => 0x0018,
Self::Secp521r1 => 0x0019,
Self::X25519 => 0x001D,
Self::X448 => 0x001E,
Self::Ffdhe2048 => 0x0100,
Self::Ffdhe3072 => 0x0101,
Self::Ffdhe4096 => 0x0102,
Self::Ffdhe6144 => 0x0103,
Self::Ffdhe8192 => 0x0104,
Self::SecP256r1MLKEM768 => 0x11EB,
Self::X25519MLKEM768 => 0x11EC,
Self::SecP384r1MLKEM1024 => 0x11ED,
}
}
pub fn encode(self, buf: &mut CryptoBuffer) -> Result<(), ProtocolError> {
buf.push_u16(self.as_u16())
.map_err(|_| ProtocolError::EncodeError)
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct SupportedGroups<const N: usize> {
pub supported_groups: Vec<NamedGroup, N>,
}
impl<const N: usize> SupportedGroups<N> {
pub fn parse(buf: &mut ParseBuffer) -> Result<Self, ParseError> {
let data_length = buf.read_u16()? as usize;
Ok(Self {
supported_groups: buf.read_list::<_, N>(data_length, NamedGroup::parse)?,
})
}
pub fn encode(&self, buf: &mut CryptoBuffer) -> Result<(), ProtocolError> {
buf.with_u16_length(|buf| {
for g in &self.supported_groups {
g.encode(buf)?;
}
Ok(())
})
}
}

View File

@@ -0,0 +1,65 @@
use crate::{
ProtocolError,
buffer::CryptoBuffer,
parse_buffer::{ParseBuffer, ParseError},
};
use heapless::Vec;
#[derive(Clone, Copy, PartialEq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct ProtocolVersion(u16);
impl ProtocolVersion {
pub fn encode(self, buf: &mut CryptoBuffer) -> Result<(), ProtocolError> {
buf.push_u16(self.0).map_err(|_| ProtocolError::EncodeError)
}
pub fn parse(buf: &mut ParseBuffer) -> Result<Self, ParseError> {
buf.read_u16().map(Self)
}
}
pub const TLS13: ProtocolVersion = ProtocolVersion(0x0304);
#[derive(Debug, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct SupportedVersionsClientHello<const N: usize> {
pub versions: Vec<ProtocolVersion, N>,
}
impl<const N: usize> SupportedVersionsClientHello<N> {
pub fn parse(buf: &mut ParseBuffer) -> Result<Self, ParseError> {
let data_length = buf.read_u8()? as usize;
Ok(Self {
versions: buf.read_list::<_, N>(data_length, ProtocolVersion::parse)?,
})
}
pub fn encode(&self, buf: &mut CryptoBuffer) -> Result<(), ProtocolError> {
buf.with_u8_length(|buf| {
for v in &self.versions {
v.encode(buf)?;
}
Ok(())
})
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct SupportedVersionsServerHello {
pub selected_version: ProtocolVersion,
}
impl SupportedVersionsServerHello {
pub fn parse(buf: &mut ParseBuffer) -> Result<Self, ParseError> {
Ok(Self {
selected_version: ProtocolVersion::parse(buf)?,
})
}
pub fn encode(self, buf: &mut CryptoBuffer) -> Result<(), ProtocolError> {
self.selected_version.encode(buf)
}
}

View File

@@ -0,0 +1,24 @@
use crate::{
ProtocolError,
buffer::CryptoBuffer,
parse_buffer::{ParseBuffer, ParseError},
};
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct Unimplemented<'a> {
pub data: &'a [u8],
}
impl<'a> Unimplemented<'a> {
#[allow(clippy::unnecessary_wraps)]
pub fn parse(buf: &mut ParseBuffer<'a>) -> Result<Self, ParseError> {
Ok(Self {
data: buf.as_slice(),
})
}
pub fn encode(&self, buf: &mut CryptoBuffer) -> Result<(), ProtocolError> {
buf.extend_from_slice(self.data)
}
}

View File

@@ -0,0 +1,93 @@
macro_rules! extension_group {
(pub enum $name:ident$(<$lt:lifetime>)? {
$($extension:ident($extension_data:ty)),+
}) => {
#[derive(Debug, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[allow(dead_code)]
pub enum $name$(<$lt>)? {
$($extension($extension_data)),+
}
#[allow(dead_code)]
impl$(<$lt>)? $name$(<$lt>)? {
pub fn extension_type(&self) -> crate::extensions::ExtensionType {
match self {
$(Self::$extension(_) => crate::extensions::ExtensionType::$extension),+
}
}
pub fn encode(&self, buf: &mut crate::buffer::CryptoBuffer) -> Result<(), crate::ProtocolError> {
self.extension_type().encode(buf)?;
buf.with_u16_length(|buf| match self {
$(Self::$extension(ext_data) => ext_data.encode(buf)),+
})
}
pub fn parse(buf: &mut crate::parse_buffer::ParseBuffer$(<$lt>)?) -> Result<Self, crate::ProtocolError> {
let extension_type = crate::extensions::ExtensionType::parse(buf);
let data_len = buf.read_u16().map_err(|_| crate::ProtocolError::DecodeError)? as usize;
let mut ext_data = buf.slice(data_len).map_err(|_| crate::ProtocolError::DecodeError)?;
let ext_type = extension_type.map_err(|err| {
warn!("Failed to read extension type: {:?}", err);
match err {
crate::parse_buffer::ParseError::InvalidData => crate::ProtocolError::UnknownExtensionType,
_ => crate::ProtocolError::DecodeError,
}
})?;
debug!("Read extension type {:?}", ext_type);
trace!("Extension data length: {}", data_len);
match ext_type {
$(crate::extensions::ExtensionType::$extension => Ok(Self::$extension(<$extension_data>::parse(&mut ext_data).map_err(|err| {
warn!("Failed to parse extension data: {:?}", err);
crate::ProtocolError::DecodeError
})?)),)+
#[allow(unreachable_patterns)]
other => {
warn!("Read unexpected ExtensionType: {:?}", other);
Err(crate::ProtocolError::AbortHandshake(
crate::alert::AlertLevel::Fatal,
crate::alert::AlertDescription::IllegalParameter,
))
}
}
}
pub fn parse_vector<const N: usize>(
buf: &mut crate::parse_buffer::ParseBuffer$(<$lt>)?,
) -> Result<heapless::Vec<Self, N>, crate::ProtocolError> {
let extensions_len = buf
.read_u16()
.map_err(|_| crate::ProtocolError::InvalidExtensionsLength)?;
let mut ext_buf = buf.slice(extensions_len as usize)?;
let mut extensions = heapless::Vec::new();
while !ext_buf.is_empty() {
trace!("Extension buffer: {}", ext_buf.remaining());
match Self::parse(&mut ext_buf) {
Ok(extension) => {
extensions
.push(extension)
.map_err(|_| crate::ProtocolError::DecodeError)?;
}
Err(crate::ProtocolError::UnknownExtensionType) => {
}
Err(err) => return Err(err),
}
}
trace!("Read {} extensions", extensions.len());
Ok(extensions)
}
}
};
}
pub(crate) use extension_group;

View File

@@ -0,0 +1,99 @@
use crate::extensions::{
extension_data::{
alpn::AlpnProtocolNameList,
key_share::{KeyShareClientHello, KeyShareServerHello},
max_fragment_length::MaxFragmentLength,
pre_shared_key::{PreSharedKeyClientHello, PreSharedKeyServerHello},
psk_key_exchange_modes::PskKeyExchangeModes,
server_name::{ServerNameList, ServerNameResponse},
signature_algorithms::SignatureAlgorithms,
signature_algorithms_cert::SignatureAlgorithmsCert,
supported_groups::SupportedGroups,
supported_versions::{SupportedVersionsClientHello, SupportedVersionsServerHello},
unimplemented::Unimplemented,
},
extension_group_macro::extension_group,
};
extension_group! {
pub enum ClientHelloExtension<'a> {
ServerName(ServerNameList<'a, 1>),
SupportedVersions(SupportedVersionsClientHello<1>),
SignatureAlgorithms(SignatureAlgorithms<25>),
SupportedGroups(SupportedGroups<13>),
KeyShare(KeyShareClientHello<'a, 1>),
PreSharedKey(PreSharedKeyClientHello<'a, 4>),
PskKeyExchangeModes(PskKeyExchangeModes<4>),
SignatureAlgorithmsCert(SignatureAlgorithmsCert<25>),
MaxFragmentLength(MaxFragmentLength),
StatusRequest(Unimplemented<'a>),
UseSrtp(Unimplemented<'a>),
Heartbeat(Unimplemented<'a>),
ApplicationLayerProtocolNegotiation(AlpnProtocolNameList<'a>),
SignedCertificateTimestamp(Unimplemented<'a>),
ClientCertificateType(Unimplemented<'a>),
ServerCertificateType(Unimplemented<'a>),
Padding(Unimplemented<'a>),
EarlyData(Unimplemented<'a>),
Cookie(Unimplemented<'a>),
CertificateAuthorities(Unimplemented<'a>),
OidFilters(Unimplemented<'a>),
PostHandshakeAuth(Unimplemented<'a>)
}
}
extension_group! {
pub enum ServerHelloExtension<'a> {
KeyShare(KeyShareServerHello<'a>),
PreSharedKey(PreSharedKeyServerHello),
Cookie(Unimplemented<'a>),
SupportedVersions(SupportedVersionsServerHello)
}
}
extension_group! {
pub enum EncryptedExtensionsExtension<'a> {
ServerName(ServerNameResponse),
MaxFragmentLength(MaxFragmentLength),
SupportedGroups(SupportedGroups<13>),
UseSrtp(Unimplemented<'a>),
Heartbeat(Unimplemented<'a>),
ApplicationLayerProtocolNegotiation(AlpnProtocolNameList<'a>),
ClientCertificateType(Unimplemented<'a>),
ServerCertificateType(Unimplemented<'a>),
EarlyData(Unimplemented<'a>)
}
}
extension_group! {
pub enum CertificateRequestExtension<'a> {
StatusRequest(Unimplemented<'a>),
SignatureAlgorithms(SignatureAlgorithms<25>),
SignedCertificateTimestamp(Unimplemented<'a>),
CertificateAuthorities(Unimplemented<'a>),
OidFilters(Unimplemented<'a>),
SignatureAlgorithmsCert(Unimplemented<'a>),
CompressCertificate(Unimplemented<'a>)
}
}
extension_group! {
pub enum CertificateExtension<'a> {
StatusRequest(Unimplemented<'a>),
SignedCertificateTimestamp(Unimplemented<'a>)
}
}
extension_group! {
pub enum NewSessionTicketExtension<'a> {
EarlyData(Unimplemented<'a>)
}
}
extension_group! {
pub enum HelloRetryRequestExtension<'a> {
KeyShare(Unimplemented<'a>),
Cookie(Unimplemented<'a>),
SupportedVersions(Unimplemented<'a>)
}
}

81
src/extensions/mod.rs Normal file
View File

@@ -0,0 +1,81 @@
use crate::{
ProtocolError,
buffer::CryptoBuffer,
parse_buffer::{ParseBuffer, ParseError},
};
mod extension_group_macro;
pub mod extension_data;
pub mod messages;
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum ExtensionType {
ServerName = 0,
MaxFragmentLength = 1,
StatusRequest = 5,
SupportedGroups = 10,
SignatureAlgorithms = 13,
UseSrtp = 14,
Heartbeat = 15,
ApplicationLayerProtocolNegotiation = 16,
SignedCertificateTimestamp = 18,
ClientCertificateType = 19,
ServerCertificateType = 20,
Padding = 21,
CompressCertificate = 27,
PreSharedKey = 41,
EarlyData = 42,
SupportedVersions = 43,
Cookie = 44,
PskKeyExchangeModes = 45,
CertificateAuthorities = 47,
OidFilters = 48,
PostHandshakeAuth = 49,
SignatureAlgorithmsCert = 50,
KeyShare = 51,
}
impl ExtensionType {
pub fn parse(buf: &mut ParseBuffer) -> Result<Self, ParseError> {
match buf.read_u16()? {
v if v == Self::ServerName as u16 => Ok(Self::ServerName),
v if v == Self::MaxFragmentLength as u16 => Ok(Self::MaxFragmentLength),
v if v == Self::StatusRequest as u16 => Ok(Self::StatusRequest),
v if v == Self::SupportedGroups as u16 => Ok(Self::SupportedGroups),
v if v == Self::SignatureAlgorithms as u16 => Ok(Self::SignatureAlgorithms),
v if v == Self::UseSrtp as u16 => Ok(Self::UseSrtp),
v if v == Self::Heartbeat as u16 => Ok(Self::Heartbeat),
v if v == Self::ApplicationLayerProtocolNegotiation as u16 => {
Ok(Self::ApplicationLayerProtocolNegotiation)
}
v if v == Self::SignedCertificateTimestamp as u16 => {
Ok(Self::SignedCertificateTimestamp)
}
v if v == Self::ClientCertificateType as u16 => Ok(Self::ClientCertificateType),
v if v == Self::ServerCertificateType as u16 => Ok(Self::ServerCertificateType),
v if v == Self::Padding as u16 => Ok(Self::Padding),
v if v == Self::CompressCertificate as u16 => Ok(Self::CompressCertificate),
v if v == Self::PreSharedKey as u16 => Ok(Self::PreSharedKey),
v if v == Self::EarlyData as u16 => Ok(Self::EarlyData),
v if v == Self::SupportedVersions as u16 => Ok(Self::SupportedVersions),
v if v == Self::Cookie as u16 => Ok(Self::Cookie),
v if v == Self::PskKeyExchangeModes as u16 => Ok(Self::PskKeyExchangeModes),
v if v == Self::CertificateAuthorities as u16 => Ok(Self::CertificateAuthorities),
v if v == Self::OidFilters as u16 => Ok(Self::OidFilters),
v if v == Self::PostHandshakeAuth as u16 => Ok(Self::PostHandshakeAuth),
v if v == Self::SignatureAlgorithmsCert as u16 => Ok(Self::SignatureAlgorithmsCert),
v if v == Self::KeyShare as u16 => Ok(Self::KeyShare),
other => {
warn!("Read unknown ExtensionType: {}", other);
Err(ParseError::InvalidData)
}
}
}
pub fn encode(self, buf: &mut CryptoBuffer) -> Result<(), ProtocolError> {
buf.push_u16(self as u16)
.map_err(|_| ProtocolError::EncodeError)
}
}

223
src/fmt.rs Normal file
View File

@@ -0,0 +1,223 @@
#![macro_use]
#![allow(unused_macros)]
macro_rules! assert {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::assert!($($x)*);
#[cfg(feature = "defmt")]
::defmt::assert!($($x)*);
}
};
}
macro_rules! assert_eq {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::assert_eq!($($x)*);
#[cfg(feature = "defmt")]
::defmt::assert_eq!($($x)*);
}
};
}
macro_rules! assert_ne {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::assert_ne!($($x)*);
#[cfg(feature = "defmt")]
::defmt::assert_ne!($($x)*);
}
};
}
macro_rules! debug_assert {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::debug_assert!($($x)*);
#[cfg(feature = "defmt")]
::defmt::debug_assert!($($x)*);
}
};
}
macro_rules! debug_assert_eq {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::debug_assert_eq!($($x)*);
#[cfg(feature = "defmt")]
::defmt::debug_assert_eq!($($x)*);
}
};
}
macro_rules! debug_assert_ne {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::debug_assert_ne!($($x)*);
#[cfg(feature = "defmt")]
::defmt::debug_assert_ne!($($x)*);
}
};
}
macro_rules! todo {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::todo!($($x)*);
#[cfg(feature = "defmt")]
::defmt::todo!($($x)*);
}
};
}
macro_rules! unreachable {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::unreachable!($($x)*);
#[cfg(feature = "defmt")]
::defmt::unreachable!($($x)*);
}
};
}
macro_rules! panic {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::panic!($($x)*);
#[cfg(feature = "defmt")]
::defmt::panic!($($x)*);
}
};
}
macro_rules! trace {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::trace!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::trace!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ = ($( & $x ),*);
}
};
}
macro_rules! debug {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::debug!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::debug!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ = ($( & $x ),*);
}
};
}
macro_rules! info {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::info!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::info!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ = ($( & $x ),*);
}
};
}
macro_rules! warn {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::warn!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::warn!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ = ($( & $x ),*);
}
};
}
macro_rules! error {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::error!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::error!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ = ($( & $x ),*);
}
};
}
#[cfg(feature = "defmt")]
macro_rules! unwrap {
($($x:tt)*) => {
::defmt::unwrap!($($x)*)
};
}
#[cfg(not(feature = "defmt"))]
macro_rules! unwrap {
($arg:expr) => {
match $crate::fmt::Try::into_result($arg) {
::core::result::Result::Ok(t) => t,
::core::result::Result::Err(e) => {
::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e);
}
}
};
($arg:expr, $($msg:expr),+ $(,)? ) => {
match $crate::fmt::Try::into_result($arg) {
::core::result::Result::Ok(t) => t,
::core::result::Result::Err(e) => {
::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e);
}
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct NoneError;
pub trait Try {
type Ok;
type Error;
fn into_result(self) -> Result<Self::Ok, Self::Error>;
}
impl<T> Try for Option<T> {
type Ok = T;
type Error = NoneError;
#[inline]
fn into_result(self) -> Result<T, NoneError> {
self.ok_or(NoneError)
}
}
impl<T, E> Try for Result<T, E> {
type Ok = T;
type Error = E;
#[inline]
fn into_result(self) -> Self {
self
}
}

36
src/handshake/binder.rs Normal file
View File

@@ -0,0 +1,36 @@
use crate::ProtocolError;
use crate::buffer::CryptoBuffer;
use core::fmt::{Debug, Formatter};
use generic_array::{ArrayLength, GenericArray};
pub struct PskBinder<N: ArrayLength<u8>> {
pub verify: GenericArray<u8, N>,
}
#[cfg(feature = "defmt")]
impl<N: ArrayLength<u8>> defmt::Format for PskBinder<N> {
fn format(&self, f: defmt::Formatter<'_>) {
defmt::write!(f, "verify length:{}", &self.verify.len());
}
}
impl<N: ArrayLength<u8>> Debug for PskBinder<N> {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
f.debug_struct("PskBinder").finish()
}
}
impl<N: ArrayLength<u8>> PskBinder<N> {
pub(crate) fn encode(&self, buf: &mut CryptoBuffer<'_>) -> Result<(), ProtocolError> {
let len = self.verify.len() as u8;
buf.push(len).map_err(|_| ProtocolError::EncodeError)?;
buf.extend_from_slice(&self.verify[..self.verify.len()])
.map_err(|_| ProtocolError::EncodeError)?;
Ok(())
}
#[allow(dead_code)]
pub fn len() -> usize {
N::to_usize()
}
}

View File

@@ -0,0 +1,176 @@
use crate::ProtocolError;
use crate::buffer::CryptoBuffer;
use crate::extensions::messages::CertificateExtension;
use crate::parse_buffer::ParseBuffer;
use heapless::Vec;
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct CertificateRef<'a> {
raw_entries: &'a [u8],
request_context: &'a [u8],
pub entries: Vec<CertificateEntryRef<'a>, 16>,
}
impl<'a> CertificateRef<'a> {
#[must_use]
pub fn with_context(request_context: &'a [u8]) -> Self {
Self {
raw_entries: &[],
request_context,
entries: Vec::new(),
}
}
pub fn add(&mut self, entry: CertificateEntryRef<'a>) -> Result<(), ProtocolError> {
self.entries.push(entry).map_err(|_| {
error!("CertificateRef: InsufficientSpace");
ProtocolError::InsufficientSpace
})
}
pub fn parse(buf: &mut ParseBuffer<'a>) -> Result<Self, ProtocolError> {
let request_context_len = buf
.read_u8()
.map_err(|_| ProtocolError::InvalidCertificate)?;
let request_context = buf
.slice(request_context_len as usize)
.map_err(|_| ProtocolError::InvalidCertificate)?;
let entries_len = buf
.read_u24()
.map_err(|_| ProtocolError::InvalidCertificate)?;
let mut raw_entries = buf
.slice(entries_len as usize)
.map_err(|_| ProtocolError::InvalidCertificate)?;
let entries = CertificateEntryRef::parse_vector(&mut raw_entries)?;
Ok(Self {
raw_entries: raw_entries.as_slice(),
request_context: request_context.as_slice(),
entries,
})
}
pub(crate) fn encode(&self, buf: &mut CryptoBuffer<'_>) -> Result<(), ProtocolError> {
buf.with_u8_length(|buf| buf.extend_from_slice(self.request_context))?;
buf.with_u24_length(|buf| {
for entry in &self.entries {
entry.encode(buf)?;
}
Ok(())
})?;
Ok(())
}
}
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum CertificateEntryRef<'a> {
X509(&'a [u8]),
RawPublicKey(&'a [u8]),
}
impl<'a> CertificateEntryRef<'a> {
pub fn parse(buf: &mut ParseBuffer<'a>) -> Result<Self, ProtocolError> {
let entry_len = buf
.read_u24()
.map_err(|_| ProtocolError::InvalidCertificateEntry)?;
let cert = buf
.slice(entry_len as usize)
.map_err(|_| ProtocolError::InvalidCertificateEntry)?;
let entry = CertificateEntryRef::X509(cert.as_slice());
CertificateExtension::parse_vector::<2>(buf)?;
Ok(entry)
}
pub fn parse_vector<const N: usize>(
buf: &mut ParseBuffer<'a>,
) -> Result<Vec<Self, N>, ProtocolError> {
let mut result = Vec::new();
while !buf.is_empty() {
result
.push(Self::parse(buf)?)
.map_err(|_| ProtocolError::DecodeError)?;
}
Ok(result)
}
pub(crate) fn encode(&self, buf: &mut CryptoBuffer<'_>) -> Result<(), ProtocolError> {
match *self {
CertificateEntryRef::RawPublicKey(_key) => {
todo!("ASN1_subjectPublicKeyInfo encoding?");
}
CertificateEntryRef::X509(cert) => {
buf.with_u24_length(|buf| buf.extend_from_slice(cert))?;
}
}
buf.push_u16(0)?;
Ok(())
}
}
impl<'a, D: AsRef<[u8]>> From<&'a crate::config::Certificate<D>> for CertificateEntryRef<'a> {
fn from(cert: &'a crate::config::Certificate<D>) -> Self {
match cert {
crate::config::Certificate::X509(data) => CertificateEntryRef::X509(data.as_ref()),
crate::config::Certificate::RawPublicKey(data) => {
CertificateEntryRef::RawPublicKey(data.as_ref())
}
}
}
}
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct Certificate<const N: usize> {
request_context: Vec<u8, 256>,
entries_data: Vec<u8, N>,
}
impl<const N: usize> Certificate<N> {
pub fn request_context(&self) -> &[u8] {
&self.request_context[..]
}
}
impl<'a, const N: usize> TryFrom<CertificateRef<'a>> for Certificate<N> {
type Error = ProtocolError;
fn try_from(cert: CertificateRef<'a>) -> Result<Self, Self::Error> {
let mut request_context = Vec::new();
request_context
.extend_from_slice(cert.request_context)
.map_err(|_| ProtocolError::OutOfMemory)?;
let mut entries_data = Vec::new();
entries_data
.extend_from_slice(cert.raw_entries)
.map_err(|_| ProtocolError::OutOfMemory)?;
Ok(Self {
request_context,
entries_data,
})
}
}
impl<'a, const N: usize> TryFrom<&'a Certificate<N>> for CertificateRef<'a> {
type Error = ProtocolError;
fn try_from(cert: &'a Certificate<N>) -> Result<Self, Self::Error> {
let request_context = cert.request_context();
let entries =
CertificateEntryRef::parse_vector(&mut ParseBuffer::from(&cert.entries_data[..]))?;
Ok(Self {
raw_entries: &cert.entries_data[..],
request_context,
entries,
})
}
}

View File

@@ -0,0 +1,49 @@
use crate::extensions::messages::CertificateRequestExtension;
use crate::parse_buffer::ParseBuffer;
use crate::{ProtocolError, unused};
use heapless::Vec;
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct CertificateRequestRef<'a> {
pub(crate) request_context: &'a [u8],
}
impl<'a> CertificateRequestRef<'a> {
pub fn parse(buf: &mut ParseBuffer<'a>) -> Result<CertificateRequestRef<'a>, ProtocolError> {
let request_context_len = buf
.read_u8()
.map_err(|_| ProtocolError::InvalidCertificateRequest)?;
let request_context = buf
.slice(request_context_len as usize)
.map_err(|_| ProtocolError::InvalidCertificateRequest)?;
let extensions = CertificateRequestExtension::parse_vector::<6>(buf)?;
unused(extensions);
Ok(Self {
request_context: request_context.as_slice(),
})
}
}
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct CertificateRequest {
pub(crate) request_context: Vec<u8, 256>,
}
impl<'a> TryFrom<CertificateRequestRef<'a>> for CertificateRequest {
type Error = ProtocolError;
fn try_from(cert: CertificateRequestRef<'a>) -> Result<Self, Self::Error> {
let mut request_context = Vec::new();
request_context
.extend_from_slice(cert.request_context)
.map_err(|_| {
error!("CertificateRequest: InsufficientSpace");
ProtocolError::InsufficientSpace
})?;
Ok(Self { request_context })
}
}

View File

@@ -0,0 +1,51 @@
use crate::ProtocolError;
use crate::extensions::extension_data::signature_algorithms::SignatureScheme;
use crate::parse_buffer::ParseBuffer;
use super::CryptoBuffer;
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct HandshakeVerifyRef<'a> {
pub signature_scheme: SignatureScheme,
pub signature: &'a [u8],
}
impl<'a> HandshakeVerifyRef<'a> {
pub fn parse(buf: &mut ParseBuffer<'a>) -> Result<HandshakeVerifyRef<'a>, ProtocolError> {
let signature_scheme =
SignatureScheme::parse(buf).map_err(|_| ProtocolError::InvalidSignatureScheme)?;
let len = buf
.read_u16()
.map_err(|_| ProtocolError::InvalidSignature)?;
let signature = buf
.slice(len as usize)
.map_err(|_| ProtocolError::InvalidSignature)?;
Ok(Self {
signature_scheme,
signature: signature.as_slice(),
})
}
}
#[cfg(feature = "rsa")]
const SIGNATURE_SIZE: usize = 512;
#[cfg(not(feature = "rsa"))]
const SIGNATURE_SIZE: usize = 104;
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct HandshakeVerify {
pub(crate) signature_scheme: SignatureScheme,
pub(crate) signature: heapless::Vec<u8, SIGNATURE_SIZE>,
}
impl HandshakeVerify {
pub(crate) fn encode(&self, buf: &mut CryptoBuffer<'_>) -> Result<(), ProtocolError> {
buf.push_u16(self.signature_scheme.as_u16())?;
buf.with_u16_length(|buf| buf.extend_from_slice(self.signature.as_slice()))?;
Ok(())
}
}

View File

@@ -0,0 +1,165 @@
use core::marker::PhantomData;
use digest::{Digest, OutputSizeUser};
use heapless::Vec;
use p256::EncodedPoint;
use p256::ecdh::EphemeralSecret;
use p256::elliptic_curve::rand_core::RngCore;
use typenum::Unsigned;
use crate::ProtocolError;
use crate::config::{ConnectConfig, TlsCipherSuite};
use crate::extensions::extension_data::alpn::AlpnProtocolNameList;
use crate::extensions::extension_data::key_share::{KeyShareClientHello, KeyShareEntry};
use crate::extensions::extension_data::pre_shared_key::PreSharedKeyClientHello;
use crate::extensions::extension_data::psk_key_exchange_modes::{
PskKeyExchangeMode, PskKeyExchangeModes,
};
use crate::extensions::extension_data::server_name::ServerNameList;
use crate::extensions::extension_data::signature_algorithms::SignatureAlgorithms;
use crate::extensions::extension_data::supported_groups::{NamedGroup, SupportedGroups};
use crate::extensions::extension_data::supported_versions::{SupportedVersionsClientHello, TLS13};
use crate::extensions::messages::ClientHelloExtension;
use crate::handshake::{LEGACY_VERSION, Random};
use crate::key_schedule::{HashOutputSize, WriteKeySchedule};
use crate::{CryptoBackend, buffer::CryptoBuffer};
pub struct ClientHello<'config, CipherSuite>
where
CipherSuite: TlsCipherSuite,
{
pub(crate) config: &'config ConnectConfig<'config>,
random: Random,
cipher_suite: PhantomData<CipherSuite>,
pub(crate) secret: EphemeralSecret,
}
impl<'config, CipherSuite> ClientHello<'config, CipherSuite>
where
CipherSuite: TlsCipherSuite,
{
pub fn new<CP>(config: &'config ConnectConfig<'config>, mut provider: CP) -> Self
where
CP: CryptoBackend,
{
let mut random = [0; 32];
provider.rng().fill_bytes(&mut random);
Self {
config,
random,
cipher_suite: PhantomData,
secret: EphemeralSecret::random(&mut provider.rng()),
}
}
pub(crate) fn encode(&self, buf: &mut CryptoBuffer<'_>) -> Result<(), ProtocolError> {
let public_key = EncodedPoint::from(&self.secret.public_key());
let public_key = public_key.as_ref();
buf.push_u16(LEGACY_VERSION)
.map_err(|_| ProtocolError::EncodeError)?;
buf.extend_from_slice(&self.random)
.map_err(|_| ProtocolError::EncodeError)?;
// Empty legacy session ID — TLS 1.3 doesn't use it, but the field must be present
buf.push(0).map_err(|_| ProtocolError::EncodeError)?;
// Exactly one cipher suite entry (2-byte length prefix + 2-byte code point)
buf.push_u16(2).map_err(|_| ProtocolError::EncodeError)?;
buf.push_u16(CipherSuite::CODE_POINT)
.map_err(|_| ProtocolError::EncodeError)?;
// Legacy compression methods: one entry, 0x00 = no compression
buf.push(1).map_err(|_| ProtocolError::EncodeError)?;
buf.push(0).map_err(|_| ProtocolError::EncodeError)?;
buf.with_u16_length(|buf| {
ClientHelloExtension::SupportedVersions(SupportedVersionsClientHello {
versions: Vec::from_slice(&[TLS13]).unwrap(),
})
.encode(buf)?;
ClientHelloExtension::SignatureAlgorithms(SignatureAlgorithms {
supported_signature_algorithms: self.config.signature_schemes.clone(),
})
.encode(buf)?;
if let Some(max_fragment_length) = self.config.max_fragment_length {
ClientHelloExtension::MaxFragmentLength(max_fragment_length).encode(buf)?;
}
ClientHelloExtension::SupportedGroups(SupportedGroups {
supported_groups: self.config.named_groups.clone(),
})
.encode(buf)?;
ClientHelloExtension::PskKeyExchangeModes(PskKeyExchangeModes {
modes: Vec::from_slice(&[PskKeyExchangeMode::PskDheKe]).unwrap(),
})
.encode(buf)?;
ClientHelloExtension::KeyShare(KeyShareClientHello {
client_shares: Vec::from_slice(&[KeyShareEntry {
group: NamedGroup::Secp256r1,
opaque: public_key,
}])
.unwrap(),
})
.encode(buf)?;
if let Some(server_name) = self.config.server_name {
ClientHelloExtension::ServerName(ServerNameList::single(server_name))
.encode(buf)?;
}
if let Some(alpn_protocols) = self.config.alpn_protocols {
ClientHelloExtension::ApplicationLayerProtocolNegotiation(AlpnProtocolNameList {
protocols: alpn_protocols,
})
.encode(buf)?;
}
if let Some((_, identities)) = &self.config.psk {
ClientHelloExtension::PreSharedKey(PreSharedKeyClientHello {
identities: identities.clone(),
hash_size: <CipherSuite::Hash as OutputSizeUser>::output_size(),
})
.encode(buf)?;
}
Ok(())
})?;
Ok(())
}
pub fn finalize(
&self,
enc_buf: &mut [u8],
transcript: &mut CipherSuite::Hash,
write_key_schedule: &mut WriteKeySchedule<CipherSuite>,
) -> Result<(), ProtocolError> {
if let Some((_, identities)) = &self.config.psk {
// PSK binders depend on the transcript up to (but not including) the binder values,
// so we hash the partial message, compute binders, then hash the remainder (RFC 8446 §4.2.11.2)
let binders_len = identities.len() * (1 + HashOutputSize::<CipherSuite>::to_usize());
let binders_pos = enc_buf.len() - binders_len;
transcript.update(&enc_buf[0..binders_pos - 2]);
let mut buf = CryptoBuffer::wrap(&mut enc_buf[binders_pos..]);
for _id in identities {
let binder = write_key_schedule.create_psk_binder(transcript)?;
binder.encode(&mut buf)?;
}
transcript.update(&enc_buf[binders_pos - 2..]);
} else {
transcript.update(enc_buf);
}
Ok(())
}
}

View File

@@ -0,0 +1,19 @@
use core::marker::PhantomData;
use crate::extensions::messages::EncryptedExtensionsExtension;
use crate::ProtocolError;
use crate::parse_buffer::ParseBuffer;
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct EncryptedExtensions<'a> {
_todo: PhantomData<&'a ()>,
}
impl<'a> EncryptedExtensions<'a> {
pub fn parse(buf: &mut ParseBuffer<'a>) -> Result<EncryptedExtensions<'a>, ProtocolError> {
EncryptedExtensionsExtension::parse_vector::<16>(buf)?;
Ok(EncryptedExtensions { _todo: PhantomData })
}
}

43
src/handshake/finished.rs Normal file
View File

@@ -0,0 +1,43 @@
use crate::ProtocolError;
use crate::buffer::CryptoBuffer;
use crate::parse_buffer::ParseBuffer;
use core::fmt::{Debug, Formatter};
use generic_array::{ArrayLength, GenericArray};
/// TLS Finished message: contains an HMAC over the handshake transcript (RFC 8446 §4.4.4).
///
/// `hash` holds the transcript hash snapshot taken just before this message was received;
/// it is `None` when the struct is used for a locally-generated Finished message.
pub struct Finished<N: ArrayLength<u8>> {
pub verify: GenericArray<u8, N>,
pub hash: Option<GenericArray<u8, N>>,
}
#[cfg(feature = "defmt")]
impl<N: ArrayLength<u8>> defmt::Format for Finished<N> {
fn format(&self, f: defmt::Formatter<'_>) {
defmt::write!(f, "verify length:{}", &self.verify.len());
}
}
impl<N: ArrayLength<u8>> Debug for Finished<N> {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Finished")
.field("verify", &self.hash)
.finish()
}
}
impl<N: ArrayLength<u8>> Finished<N> {
pub fn parse(buf: &mut ParseBuffer, _len: u32) -> Result<Self, ProtocolError> {
let mut verify = GenericArray::default();
buf.fill(&mut verify)?;
Ok(Self { verify, hash: None })
}
pub(crate) fn encode(&self, buf: &mut CryptoBuffer<'_>) -> Result<(), ProtocolError> {
buf.extend_from_slice(&self.verify[..self.verify.len()])
.map_err(|_| ProtocolError::EncodeError)?;
Ok(())
}
}

242
src/handshake/mod.rs Normal file
View File

@@ -0,0 +1,242 @@
use crate::ProtocolError;
use crate::config::TlsCipherSuite;
use crate::handshake::certificate::CertificateRef;
use crate::handshake::certificate_request::CertificateRequestRef;
use crate::handshake::certificate_verify::{HandshakeVerify, HandshakeVerifyRef};
use crate::handshake::client_hello::ClientHello;
use crate::handshake::encrypted_extensions::EncryptedExtensions;
use crate::handshake::finished::Finished;
use crate::handshake::new_session_ticket::NewSessionTicket;
use crate::handshake::server_hello::ServerHello;
use crate::key_schedule::HashOutputSize;
use crate::parse_buffer::{ParseBuffer, ParseError};
use crate::{buffer::CryptoBuffer, key_schedule::WriteKeySchedule};
use core::fmt::{Debug, Formatter};
use sha2::Digest;
pub mod binder;
pub mod certificate;
pub mod certificate_request;
pub mod certificate_verify;
pub mod client_hello;
pub mod encrypted_extensions;
pub mod finished;
pub mod new_session_ticket;
pub mod server_hello;
// TLS legacy_record_version field — always 0x0303 for TLS 1.3 compatibility (RFC 8446 §5.1)
const LEGACY_VERSION: u16 = 0x0303;
type Random = [u8; 32];
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum HandshakeType {
ClientHello = 1,
ServerHello = 2,
NewSessionTicket = 4,
EndOfEarlyData = 5,
EncryptedExtensions = 8,
Certificate = 11,
CertificateRequest = 13,
HandshakeVerify = 15,
Finished = 20,
KeyUpdate = 24,
MessageHash = 254,
}
impl HandshakeType {
pub fn parse(buf: &mut ParseBuffer) -> Result<Self, ParseError> {
match buf.read_u8()? {
1 => Ok(HandshakeType::ClientHello),
2 => Ok(HandshakeType::ServerHello),
4 => Ok(HandshakeType::NewSessionTicket),
5 => Ok(HandshakeType::EndOfEarlyData),
8 => Ok(HandshakeType::EncryptedExtensions),
11 => Ok(HandshakeType::Certificate),
13 => Ok(HandshakeType::CertificateRequest),
15 => Ok(HandshakeType::HandshakeVerify),
20 => Ok(HandshakeType::Finished),
24 => Ok(HandshakeType::KeyUpdate),
254 => Ok(HandshakeType::MessageHash),
_ => Err(ParseError::InvalidData),
}
}
}
#[allow(clippy::large_enum_variant)]
pub enum ClientHandshake<'config, 'a, CipherSuite>
where
CipherSuite: TlsCipherSuite,
{
ClientCert(CertificateRef<'a>),
ClientCertVerify(HandshakeVerify),
ClientHello(ClientHello<'config, CipherSuite>),
Finished(Finished<HashOutputSize<CipherSuite>>),
}
impl<CipherSuite> ClientHandshake<'_, '_, CipherSuite>
where
CipherSuite: TlsCipherSuite,
{
fn handshake_type(&self) -> HandshakeType {
match self {
ClientHandshake::ClientHello(_) => HandshakeType::ClientHello,
ClientHandshake::Finished(_) => HandshakeType::Finished,
ClientHandshake::ClientCert(_) => HandshakeType::Certificate,
ClientHandshake::ClientCertVerify(_) => HandshakeType::HandshakeVerify,
}
}
fn encode_inner(&self, buf: &mut CryptoBuffer<'_>) -> Result<(), ProtocolError> {
match self {
ClientHandshake::ClientHello(inner) => inner.encode(buf),
ClientHandshake::Finished(inner) => inner.encode(buf),
ClientHandshake::ClientCert(inner) => inner.encode(buf),
ClientHandshake::ClientCertVerify(inner) => inner.encode(buf),
}
}
pub(crate) fn encode(&self, buf: &mut CryptoBuffer<'_>) -> Result<(), ProtocolError> {
buf.push(self.handshake_type() as u8)
.map_err(|_| ProtocolError::EncodeError)?;
// Handshake message body is preceded by a 3-byte (u24) length (RFC 8446 §4)
buf.with_u24_length(|buf| self.encode_inner(buf))
}
pub fn finalize(
&self,
buf: &mut CryptoBuffer,
transcript: &mut CipherSuite::Hash,
write_key_schedule: &mut WriteKeySchedule<CipherSuite>,
) -> Result<(), ProtocolError> {
let enc_buf = buf.as_mut_slice();
if let ClientHandshake::ClientHello(hello) = self {
hello.finalize(enc_buf, transcript, write_key_schedule)
} else {
transcript.update(enc_buf);
Ok(())
}
}
pub fn finalize_encrypted(buf: &mut CryptoBuffer, transcript: &mut CipherSuite::Hash) {
let enc_buf = buf.as_slice();
let end = enc_buf.len();
transcript.update(&enc_buf[0..end]);
}
}
#[allow(clippy::large_enum_variant)]
pub enum ServerHandshake<'a, CipherSuite: TlsCipherSuite> {
ServerHello(ServerHello<'a>),
EncryptedExtensions(EncryptedExtensions<'a>),
NewSessionTicket(NewSessionTicket<'a>),
Certificate(CertificateRef<'a>),
CertificateRequest(CertificateRequestRef<'a>),
HandshakeVerify(HandshakeVerifyRef<'a>),
Finished(Finished<HashOutputSize<CipherSuite>>),
}
impl<CipherSuite: TlsCipherSuite> ServerHandshake<'_, CipherSuite> {
#[allow(dead_code)]
pub fn handshake_type(&self) -> HandshakeType {
match self {
ServerHandshake::ServerHello(_) => HandshakeType::ServerHello,
ServerHandshake::EncryptedExtensions(_) => HandshakeType::EncryptedExtensions,
ServerHandshake::NewSessionTicket(_) => HandshakeType::NewSessionTicket,
ServerHandshake::Certificate(_) => HandshakeType::Certificate,
ServerHandshake::CertificateRequest(_) => HandshakeType::CertificateRequest,
ServerHandshake::HandshakeVerify(_) => HandshakeType::HandshakeVerify,
ServerHandshake::Finished(_) => HandshakeType::Finished,
}
}
}
impl<CipherSuite: TlsCipherSuite> Debug for ServerHandshake<'_, CipherSuite> {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
match self {
ServerHandshake::ServerHello(inner) => Debug::fmt(inner, f),
ServerHandshake::EncryptedExtensions(inner) => Debug::fmt(inner, f),
ServerHandshake::Certificate(inner) => Debug::fmt(inner, f),
ServerHandshake::CertificateRequest(inner) => Debug::fmt(inner, f),
ServerHandshake::HandshakeVerify(inner) => Debug::fmt(inner, f),
ServerHandshake::Finished(inner) => Debug::fmt(inner, f),
ServerHandshake::NewSessionTicket(inner) => Debug::fmt(inner, f),
}
}
}
#[cfg(feature = "defmt")]
impl<'a, CipherSuite: TlsCipherSuite> defmt::Format for ServerHandshake<'a, CipherSuite> {
fn format(&self, f: defmt::Formatter<'_>) {
match self {
ServerHandshake::ServerHello(inner) => defmt::write!(f, "{}", inner),
ServerHandshake::EncryptedExtensions(inner) => defmt::write!(f, "{}", inner),
ServerHandshake::Certificate(inner) => defmt::write!(f, "{}", inner),
ServerHandshake::CertificateRequest(inner) => defmt::write!(f, "{}", inner),
ServerHandshake::HandshakeVerify(inner) => defmt::write!(f, "{}", inner),
ServerHandshake::Finished(inner) => defmt::write!(f, "{}", inner),
ServerHandshake::NewSessionTicket(inner) => defmt::write!(f, "{}", inner),
}
}
}
impl<'a, CipherSuite: TlsCipherSuite> ServerHandshake<'a, CipherSuite> {
pub fn read(
buf: &mut ParseBuffer<'a>,
digest: &mut CipherSuite::Hash,
) -> Result<Self, ProtocolError> {
let handshake_start = buf.offset();
let mut handshake = Self::parse(buf)?;
let handshake_end = buf.offset();
// Capture the current transcript hash into Finished before we update it with this message
if let ServerHandshake::Finished(finished) = &mut handshake {
finished.hash.replace(digest.clone().finalize());
}
digest.update(&buf.as_slice()[handshake_start..handshake_end]);
Ok(handshake)
}
fn parse(buf: &mut ParseBuffer<'a>) -> Result<Self, ProtocolError> {
let handshake_type =
HandshakeType::parse(buf).map_err(|_| ProtocolError::InvalidHandshake)?;
trace!("handshake = {:?}", handshake_type);
let content_len = buf
.read_u24()
.map_err(|_| ProtocolError::InvalidHandshake)?;
let handshake = match handshake_type {
HandshakeType::ServerHello => ServerHandshake::ServerHello(ServerHello::parse(buf)?),
HandshakeType::NewSessionTicket => {
ServerHandshake::NewSessionTicket(NewSessionTicket::parse(buf)?)
}
HandshakeType::EncryptedExtensions => {
ServerHandshake::EncryptedExtensions(EncryptedExtensions::parse(buf)?)
}
HandshakeType::Certificate => ServerHandshake::Certificate(CertificateRef::parse(buf)?),
HandshakeType::CertificateRequest => {
ServerHandshake::CertificateRequest(CertificateRequestRef::parse(buf)?)
}
HandshakeType::HandshakeVerify => {
ServerHandshake::HandshakeVerify(HandshakeVerifyRef::parse(buf)?)
}
HandshakeType::Finished => {
ServerHandshake::Finished(Finished::parse(buf, content_len)?)
}
t => {
warn!("Unimplemented handshake type: {:?}", t);
return Err(ProtocolError::Unimplemented);
}
};
Ok(handshake)
}
}

View File

@@ -0,0 +1,33 @@
use core::marker::PhantomData;
use crate::extensions::messages::NewSessionTicketExtension;
use crate::parse_buffer::ParseBuffer;
use crate::{ProtocolError, unused};
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct NewSessionTicket<'a> {
_todo: PhantomData<&'a ()>,
}
impl<'a> NewSessionTicket<'a> {
pub fn parse(buf: &mut ParseBuffer<'a>) -> Result<NewSessionTicket<'a>, ProtocolError> {
let lifetime = buf.read_u32()?;
let age_add = buf.read_u32()?;
let nonce_length = buf.read_u8()?;
let nonce = buf
.slice(nonce_length as usize)
.map_err(|_| ProtocolError::InvalidNonceLength)?;
let ticket_length = buf.read_u16()?;
let ticket = buf
.slice(ticket_length as usize)
.map_err(|_| ProtocolError::InvalidTicketLength)?;
let extensions = NewSessionTicketExtension::parse_vector::<1>(buf)?;
unused((lifetime, age_add, nonce, ticket, extensions));
Ok(Self { _todo: PhantomData })
}
}

View File

@@ -0,0 +1,80 @@
use heapless::Vec;
use crate::cipher::CryptoEngine;
use crate::cipher_suites::CipherSuite;
use crate::extensions::extension_data::key_share::KeyShareEntry;
use crate::extensions::messages::ServerHelloExtension;
use crate::parse_buffer::ParseBuffer;
use crate::{ProtocolError, unused};
use p256::PublicKey;
use p256::ecdh::{EphemeralSecret, SharedSecret};
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct ServerHello<'a> {
extensions: Vec<ServerHelloExtension<'a>, 4>,
}
impl<'a> ServerHello<'a> {
pub fn parse(buf: &mut ParseBuffer<'a>) -> Result<ServerHello<'a>, ProtocolError> {
// legacy_version is always 0x0303 in TLS 1.3; actual version is negotiated via extensions
let _version = buf
.read_u16()
.map_err(|_| ProtocolError::InvalidHandshake)?;
let mut random = [0; 32];
buf.fill(&mut random)?;
let session_id_length = buf
.read_u8()
.map_err(|_| ProtocolError::InvalidSessionIdLength)?;
// Legacy session ID echo: TLS 1.3 servers echo the client's session ID for middlebox compatibility
let session_id = buf
.slice(session_id_length as usize)
.map_err(|_| ProtocolError::InvalidSessionIdLength)?;
let cipher_suite =
CipherSuite::parse(buf).map_err(|_| ProtocolError::InvalidCipherSuite)?;
// compression_method: always 0x00 in TLS 1.3
buf.read_u8()?;
let extensions = ServerHelloExtension::parse_vector(buf)?;
debug!("server cipher_suite {:?}", cipher_suite);
debug!("server extensions {:?}", extensions);
unused(session_id);
Ok(Self { extensions })
}
pub fn key_share(&self) -> Option<&KeyShareEntry<'_>> {
self.extensions.iter().find_map(|e| {
if let ServerHelloExtension::KeyShare(entry) = e {
Some(&entry.0)
} else {
None
}
})
}
/// Performs ECDH with the server's key share to derive the shared secret used in the handshake.
pub fn calculate_shared_secret(&self, secret: &EphemeralSecret) -> Option<SharedSecret> {
let server_key_share = self.key_share()?;
let server_public_key = PublicKey::from_sec1_bytes(server_key_share.opaque).ok()?;
Some(secret.diffie_hellman(&server_public_key))
}
#[allow(dead_code)]
pub fn initialize_crypto_engine(&self, secret: &EphemeralSecret) -> Option<CryptoEngine> {
let server_key_share = self.key_share()?;
let group = server_key_share.group;
let server_public_key = PublicKey::from_sec1_bytes(server_key_share.opaque).ok()?;
let shared = secret.diffie_hellman(&server_public_key);
Some(CryptoEngine::new(group, shared))
}
}

485
src/key_schedule.rs Normal file
View File

@@ -0,0 +1,485 @@
use crate::handshake::binder::PskBinder;
use crate::handshake::finished::Finished;
use crate::{ProtocolError, config::TlsCipherSuite};
use digest::OutputSizeUser;
use digest::generic_array::ArrayLength;
use hmac::{Mac, SimpleHmac};
use sha2::Digest;
use sha2::digest::generic_array::{GenericArray, typenum::Unsigned};
pub type HashOutputSize<CipherSuite> =
<<CipherSuite as TlsCipherSuite>::Hash as OutputSizeUser>::OutputSize;
pub type LabelBufferSize<CipherSuite> = <CipherSuite as TlsCipherSuite>::LabelBufferSize;
pub type IvArray<CipherSuite> = GenericArray<u8, <CipherSuite as TlsCipherSuite>::IvLen>;
pub type KeyArray<CipherSuite> = GenericArray<u8, <CipherSuite as TlsCipherSuite>::KeyLen>;
/// Hash-sized byte array, used as the HKDF secret at each key schedule stage.
pub type HashArray<CipherSuite> = GenericArray<u8, HashOutputSize<CipherSuite>>;
type Hkdf<CipherSuite> = hkdf::Hkdf<
<CipherSuite as TlsCipherSuite>::Hash,
SimpleHmac<<CipherSuite as TlsCipherSuite>::Hash>,
>;
enum Secret<CipherSuite>
where
CipherSuite: TlsCipherSuite,
{
Uninitialized,
Initialized(Hkdf<CipherSuite>),
}
impl<CipherSuite> Secret<CipherSuite>
where
CipherSuite: TlsCipherSuite,
{
fn replace(&mut self, secret: Hkdf<CipherSuite>) {
*self = Self::Initialized(secret);
}
fn as_ref(&self) -> Result<&Hkdf<CipherSuite>, ProtocolError> {
match self {
Secret::Initialized(secret) => Ok(secret),
Secret::Uninitialized => Err(ProtocolError::InternalError),
}
}
// HKDF-Expand-Label as defined in RFC 8446 §7.1
fn make_expanded_hkdf_label<N: ArrayLength<u8>>(
&self,
label: &[u8],
context_type: ContextType<CipherSuite>,
) -> Result<GenericArray<u8, N>, ProtocolError> {
let mut hkdf_label = heapless_typenum::Vec::<u8, LabelBufferSize<CipherSuite>>::new();
// Length field: desired output length as u16 big-endian
hkdf_label
.extend_from_slice(&N::to_u16().to_be_bytes())
.map_err(|()| ProtocolError::InternalError)?;
// TLS 1.3 labels are prefixed with "tls13 " (6 bytes)
let label_len = 6 + label.len() as u8;
hkdf_label
.extend_from_slice(&label_len.to_be_bytes())
.map_err(|()| ProtocolError::InternalError)?;
hkdf_label
.extend_from_slice(b"tls13 ")
.map_err(|()| ProtocolError::InternalError)?;
hkdf_label
.extend_from_slice(label)
.map_err(|()| ProtocolError::InternalError)?;
match context_type {
ContextType::None => {
hkdf_label
.push(0)
.map_err(|_| ProtocolError::InternalError)?;
}
ContextType::Hash(context) => {
hkdf_label
.extend_from_slice(&(context.len() as u8).to_be_bytes())
.map_err(|()| ProtocolError::InternalError)?;
hkdf_label
.extend_from_slice(&context)
.map_err(|()| ProtocolError::InternalError)?;
}
}
let mut okm = GenericArray::default();
self.as_ref()?
.expand(&hkdf_label, &mut okm)
.map_err(|_| ProtocolError::CryptoError)?;
Ok(okm)
}
}
pub struct SharedState<CipherSuite>
where
CipherSuite: TlsCipherSuite,
{
secret: HashArray<CipherSuite>,
hkdf: Secret<CipherSuite>,
}
impl<CipherSuite> SharedState<CipherSuite>
where
CipherSuite: TlsCipherSuite,
{
fn new() -> Self {
Self {
secret: GenericArray::default(),
hkdf: Secret::Uninitialized,
}
}
// HKDF-Extract, chaining the previous stage's secret as the salt (RFC 8446 §7.1)
fn initialize(&mut self, ikm: &[u8]) {
let (secret, hkdf) = Hkdf::<CipherSuite>::extract(Some(self.secret.as_ref()), ikm);
self.hkdf.replace(hkdf);
self.secret = secret;
}
fn derive_secret(
&mut self,
label: &[u8],
context_type: ContextType<CipherSuite>,
) -> Result<HashArray<CipherSuite>, ProtocolError> {
self.hkdf
.make_expanded_hkdf_label::<HashOutputSize<CipherSuite>>(label, context_type)
}
// "Derive-Secret(Secret, "derived", "")" — used to chain stages per RFC 8446 §7.1
fn derived(&mut self) -> Result<(), ProtocolError> {
self.secret = self.derive_secret(b"derived", ContextType::empty_hash())?;
Ok(())
}
}
pub(crate) struct KeyScheduleState<CipherSuite>
where
CipherSuite: TlsCipherSuite,
{
traffic_secret: Secret<CipherSuite>,
counter: u64,
key: KeyArray<CipherSuite>,
iv: IvArray<CipherSuite>,
}
impl<CipherSuite> KeyScheduleState<CipherSuite>
where
CipherSuite: TlsCipherSuite,
{
fn new() -> Self {
Self {
traffic_secret: Secret::Uninitialized,
counter: 0,
key: KeyArray::<CipherSuite>::default(),
iv: IvArray::<CipherSuite>::default(),
}
}
#[inline]
pub fn get_key(&self) -> &KeyArray<CipherSuite> {
&self.key
}
#[inline]
pub fn get_iv(&self) -> &IvArray<CipherSuite> {
&self.iv
}
pub fn get_nonce(&self) -> IvArray<CipherSuite> {
let iv = self.get_iv();
KeySchedule::<CipherSuite>::get_nonce(self.counter, iv)
}
fn calculate_traffic_secret(
&mut self,
label: &[u8],
shared: &mut SharedState<CipherSuite>,
transcript_hash: &CipherSuite::Hash,
) -> Result<(), ProtocolError> {
let secret = shared.derive_secret(label, ContextType::transcript_hash(transcript_hash))?;
let traffic_secret =
Hkdf::<CipherSuite>::from_prk(&secret).map_err(|_| ProtocolError::InternalError)?;
self.traffic_secret.replace(traffic_secret);
// Derive per-record key and IV from the traffic secret (RFC 8446 §7.3)
self.key = self
.traffic_secret
.make_expanded_hkdf_label(b"key", ContextType::None)?;
self.iv = self
.traffic_secret
.make_expanded_hkdf_label(b"iv", ContextType::None)?;
self.counter = 0;
Ok(())
}
pub fn increment_counter(&mut self) {
self.counter = unwrap!(self.counter.checked_add(1));
}
}
enum ContextType<CipherSuite>
where
CipherSuite: TlsCipherSuite,
{
None,
Hash(HashArray<CipherSuite>),
}
impl<CipherSuite> ContextType<CipherSuite>
where
CipherSuite: TlsCipherSuite,
{
fn transcript_hash(hash: &CipherSuite::Hash) -> Self {
Self::Hash(hash.clone().finalize())
}
fn empty_hash() -> Self {
Self::Hash(
<CipherSuite::Hash as Digest>::new()
.chain_update([])
.finalize(),
)
}
}
pub struct KeySchedule<CipherSuite>
where
CipherSuite: TlsCipherSuite,
{
shared: SharedState<CipherSuite>,
client_state: WriteKeySchedule<CipherSuite>,
server_state: ReadKeySchedule<CipherSuite>,
}
impl<CipherSuite> KeySchedule<CipherSuite>
where
CipherSuite: TlsCipherSuite,
{
pub fn new() -> Self {
Self {
shared: SharedState::new(),
client_state: WriteKeySchedule {
state: KeyScheduleState::new(),
binder_key: Secret::Uninitialized,
},
server_state: ReadKeySchedule {
state: KeyScheduleState::new(),
transcript_hash: <CipherSuite::Hash as Digest>::new(),
},
}
}
pub(crate) fn transcript_hash(&mut self) -> &mut CipherSuite::Hash {
&mut self.server_state.transcript_hash
}
pub(crate) fn replace_transcript_hash(&mut self, hash: CipherSuite::Hash) {
self.server_state.transcript_hash = hash;
}
pub fn as_split(
&mut self,
) -> (
&mut WriteKeySchedule<CipherSuite>,
&mut ReadKeySchedule<CipherSuite>,
) {
(&mut self.client_state, &mut self.server_state)
}
pub(crate) fn write_state(&mut self) -> &mut WriteKeySchedule<CipherSuite> {
&mut self.client_state
}
pub(crate) fn read_state(&mut self) -> &mut ReadKeySchedule<CipherSuite> {
&mut self.server_state
}
pub fn create_client_finished(
&self,
) -> Result<Finished<HashOutputSize<CipherSuite>>, ProtocolError> {
let key = self
.client_state
.state
.traffic_secret
.make_expanded_hkdf_label::<HashOutputSize<CipherSuite>>(
b"finished",
ContextType::None,
)?;
let mut hmac = SimpleHmac::<CipherSuite::Hash>::new_from_slice(&key)
.map_err(|_| ProtocolError::CryptoError)?;
Mac::update(
&mut hmac,
&self.server_state.transcript_hash.clone().finalize(),
);
let verify = hmac.finalize().into_bytes();
Ok(Finished { verify, hash: None })
}
fn get_nonce(counter: u64, iv: &IvArray<CipherSuite>) -> IvArray<CipherSuite> {
// Per-record nonce: XOR the static IV with the zero-padded sequence counter (RFC 8446 §5.3)
let counter = Self::pad::<CipherSuite::IvLen>(&counter.to_be_bytes());
let mut nonce = GenericArray::default();
for (index, (l, r)) in iv[0..CipherSuite::IvLen::to_usize()]
.iter()
.zip(counter.iter())
.enumerate()
{
nonce[index] = l ^ r;
}
nonce
}
// Right-aligns `input` bytes in a zero-padded array of length N (big-endian padding)
fn pad<N: ArrayLength<u8>>(input: &[u8]) -> GenericArray<u8, N> {
let mut padded = GenericArray::default();
for (index, byte) in input.iter().rev().enumerate() {
padded[(N::to_usize() - index) - 1] = *byte;
}
padded
}
fn zero() -> HashArray<CipherSuite> {
GenericArray::default()
}
pub fn initialize_early_secret(&mut self, psk: Option<&[u8]>) -> Result<(), ProtocolError> {
// IKM is 0-bytes when no PSK is used — still required to derive the binder key
self.shared.initialize(
#[allow(clippy::or_fun_call)]
psk.unwrap_or(Self::zero().as_slice()),
);
let binder_key = self
.shared
.derive_secret(b"ext binder", ContextType::empty_hash())?;
self.client_state.binder_key.replace(
Hkdf::<CipherSuite>::from_prk(&binder_key).map_err(|_| ProtocolError::InternalError)?,
);
self.shared.derived()
}
pub fn initialize_handshake_secret(&mut self, ikm: &[u8]) -> Result<(), ProtocolError> {
self.shared.initialize(ikm);
self.calculate_traffic_secrets(b"c hs traffic", b"s hs traffic")?;
self.shared.derived()
}
pub fn initialize_master_secret(&mut self) -> Result<(), ProtocolError> {
// IKM is all-zeros at the master secret stage (RFC 8446 §7.1)
self.shared.initialize(Self::zero().as_slice());
self.calculate_traffic_secrets(b"c ap traffic", b"s ap traffic")?;
self.shared.derived()
}
fn calculate_traffic_secrets(
&mut self,
client_label: &[u8],
server_label: &[u8],
) -> Result<(), ProtocolError> {
self.client_state.state.calculate_traffic_secret(
client_label,
&mut self.shared,
&self.server_state.transcript_hash,
)?;
self.server_state.state.calculate_traffic_secret(
server_label,
&mut self.shared,
&self.server_state.transcript_hash,
)?;
Ok(())
}
}
impl<CipherSuite> Default for KeySchedule<CipherSuite>
where
CipherSuite: TlsCipherSuite,
{
fn default() -> Self {
KeySchedule::new()
}
}
pub struct WriteKeySchedule<CipherSuite>
where
CipherSuite: TlsCipherSuite,
{
state: KeyScheduleState<CipherSuite>,
binder_key: Secret<CipherSuite>,
}
impl<CipherSuite> WriteKeySchedule<CipherSuite>
where
CipherSuite: TlsCipherSuite,
{
pub(crate) fn increment_counter(&mut self) {
self.state.increment_counter();
}
pub(crate) fn get_key(&self) -> &KeyArray<CipherSuite> {
self.state.get_key()
}
pub(crate) fn get_nonce(&self) -> IvArray<CipherSuite> {
self.state.get_nonce()
}
pub fn create_psk_binder(
&self,
transcript_hash: &CipherSuite::Hash,
) -> Result<PskBinder<HashOutputSize<CipherSuite>>, ProtocolError> {
let key = self
.binder_key
.make_expanded_hkdf_label::<HashOutputSize<CipherSuite>>(
b"finished",
ContextType::None,
)?;
let mut hmac = SimpleHmac::<CipherSuite::Hash>::new_from_slice(&key)
.map_err(|_| ProtocolError::CryptoError)?;
Mac::update(&mut hmac, &transcript_hash.clone().finalize());
let verify = hmac.finalize().into_bytes();
Ok(PskBinder { verify })
}
}
pub struct ReadKeySchedule<CipherSuite>
where
CipherSuite: TlsCipherSuite,
{
state: KeyScheduleState<CipherSuite>,
transcript_hash: CipherSuite::Hash,
}
impl<CipherSuite> ReadKeySchedule<CipherSuite>
where
CipherSuite: TlsCipherSuite,
{
pub(crate) fn increment_counter(&mut self) {
self.state.increment_counter();
}
pub(crate) fn transcript_hash(&mut self) -> &mut CipherSuite::Hash {
&mut self.transcript_hash
}
pub(crate) fn get_key(&self) -> &KeyArray<CipherSuite> {
self.state.get_key()
}
pub(crate) fn get_nonce(&self) -> IvArray<CipherSuite> {
self.state.get_nonce()
}
pub fn verify_server_finished(
&self,
finished: &Finished<HashOutputSize<CipherSuite>>,
) -> Result<bool, ProtocolError> {
let key = self
.state
.traffic_secret
.make_expanded_hkdf_label::<HashOutputSize<CipherSuite>>(
b"finished",
ContextType::None,
)?;
let mut hmac = SimpleHmac::<CipherSuite::Hash>::new_from_slice(&key)
.map_err(|_| ProtocolError::InternalError)?;
Mac::update(
&mut hmac,
finished.hash.as_ref().ok_or_else(|| {
warn!("No hash in Finished");
ProtocolError::InternalError
})?,
);
Ok(hmac.verify(&finished.verify).is_ok())
}
}

126
src/lib.rs Normal file
View File

@@ -0,0 +1,126 @@
#![cfg_attr(not(any(test, feature = "std")), no_std)]
#![warn(clippy::pedantic)]
#![allow(
clippy::module_name_repetitions,
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
clippy::missing_errors_doc
)]
pub(crate) mod fmt;
use parse_buffer::ParseError;
pub mod alert;
mod application_data;
pub mod blocking;
mod buffer;
mod change_cipher_spec;
mod cipher;
mod cipher_suites;
mod common;
mod config;
mod connection;
mod content_types;
mod extensions;
mod handshake;
mod key_schedule;
mod parse_buffer;
pub mod read_buffer;
mod record;
mod record_reader;
pub mod send_policy;
mod write_buffer;
pub use config::SkipVerifyProvider;
pub use extensions::extension_data::signature_algorithms::SignatureScheme;
pub use handshake::certificate_verify::HandshakeVerify;
pub use rand_core::{CryptoRng, CryptoRngCore};
#[cfg(feature = "webpki")]
pub mod cert_verify;
#[cfg(feature = "native-pki")]
mod certificate;
#[cfg(feature = "native-pki")]
pub mod native_pki;
mod asynch;
pub use asynch::*;
pub use send_policy::*;
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum ProtocolError {
ConnectionClosed,
Unimplemented,
MissingHandshake,
HandshakeAborted(alert::AlertLevel, alert::AlertDescription),
AbortHandshake(alert::AlertLevel, alert::AlertDescription),
IoError,
InternalError,
InvalidRecord,
UnknownContentType,
InvalidNonceLength,
InvalidTicketLength,
UnknownExtensionType,
InsufficientSpace,
InvalidHandshake,
InvalidCipherSuite,
InvalidSignatureScheme,
InvalidSignature,
InvalidExtensionsLength,
InvalidSessionIdLength,
InvalidSupportedVersions,
InvalidApplicationData,
InvalidKeyShare,
InvalidCertificate,
InvalidCertificateEntry,
InvalidCertificateRequest,
InvalidPrivateKey,
UnableToInitializeCryptoEngine,
ParseError(ParseError),
OutOfMemory,
CryptoError,
EncodeError,
DecodeError,
Io(embedded_io::ErrorKind),
}
impl embedded_io::Error for ProtocolError {
fn kind(&self) -> embedded_io::ErrorKind {
if let Self::Io(k) = self {
*k
} else {
error!("TLS error: {:?}", self);
embedded_io::ErrorKind::Other
}
}
}
impl core::fmt::Display for ProtocolError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{self:?}")
}
}
impl core::error::Error for ProtocolError {}
#[cfg(feature = "std")]
mod stdlib {
use crate::config::TlsClock;
use std::time::SystemTime;
impl TlsClock for SystemTime {
fn now() -> Option<u64> {
Some(
SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.as_secs(),
)
}
}
}
fn unused<T>(_: T) {}

468
src/native_pki.rs Normal file
View File

@@ -0,0 +1,468 @@
use crate::ProtocolError;
#[cfg(feature = "p384")]
use crate::certificate::ECDSA_SHA384;
#[cfg(feature = "ed25519")]
use crate::certificate::ED25519;
use crate::certificate::{DecodedCertificate, ECDSA_SHA256, Time};
#[cfg(feature = "rsa")]
use crate::certificate::{RSA_PKCS1_SHA256, RSA_PKCS1_SHA384, RSA_PKCS1_SHA512};
use crate::config::{Certificate, TlsCipherSuite, TlsClock, Verifier};
use crate::extensions::extension_data::signature_algorithms::SignatureScheme;
use crate::handshake::{
certificate::{
Certificate as OwnedCertificate, CertificateEntryRef, CertificateRef as ServerCertificate,
},
certificate_verify::HandshakeVerifyRef,
};
use crate::parse_buffer::ParseError;
use const_oid::ObjectIdentifier;
use core::marker::PhantomData;
use der::Decode;
use digest::Digest;
use heapless::{String, Vec};
const HOSTNAME_MAXLEN: usize = 64;
const COMMON_NAME_OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("2.5.4.3");
pub struct CertificateChain<'a> {
prev: Option<&'a CertificateEntryRef<'a>>,
chain: &'a ServerCertificate<'a>,
idx: isize,
}
impl<'a> CertificateChain<'a> {
pub fn new(ca: &'a CertificateEntryRef, chain: &'a ServerCertificate<'a>) -> Self {
Self {
prev: Some(ca),
chain,
idx: chain.entries.len() as isize - 1,
}
}
}
impl<'a> Iterator for CertificateChain<'a> {
type Item = (&'a CertificateEntryRef<'a>, &'a CertificateEntryRef<'a>);
fn next(&mut self) -> Option<Self::Item> {
if self.idx < 0 {
return None;
}
let cur = &self.chain.entries[self.idx as usize];
let out = (self.prev.unwrap(), cur);
self.prev = Some(cur);
self.idx -= 1;
Some(out)
}
}
pub struct CertVerifier<'a, CipherSuite, Clock, const CERT_SIZE: usize>
where
Clock: TlsClock,
CipherSuite: TlsCipherSuite,
{
ca: Certificate<&'a [u8]>,
host: Option<heapless::String<64>>,
certificate_transcript: Option<CipherSuite::Hash>,
certificate: Option<OwnedCertificate<CERT_SIZE>>,
_clock: PhantomData<Clock>,
}
impl<'a, CipherSuite, Clock, const CERT_SIZE: usize> CertVerifier<'a, CipherSuite, Clock, CERT_SIZE>
where
Clock: TlsClock,
CipherSuite: TlsCipherSuite,
{
#[must_use]
pub fn new(ca: Certificate<&'a [u8]>) -> Self {
Self {
ca,
host: None,
certificate_transcript: None,
certificate: None,
_clock: PhantomData,
}
}
}
impl<CipherSuite, Clock, const CERT_SIZE: usize> Verifier<CipherSuite>
for CertVerifier<'_, CipherSuite, Clock, CERT_SIZE>
where
CipherSuite: TlsCipherSuite,
Clock: TlsClock,
{
fn set_hostname_verification(&mut self, hostname: &str) -> Result<(), ProtocolError> {
self.host.replace(
heapless::String::try_from(hostname).map_err(|_| ProtocolError::InsufficientSpace)?,
);
Ok(())
}
fn verify_certificate(
&mut self,
transcript: &CipherSuite::Hash,
cert: ServerCertificate,
) -> Result<(), ProtocolError> {
let mut cn = None;
for (p, q) in CertificateChain::new(&(&self.ca).into(), &cert) {
cn = verify_certificate(p, q, Clock::now())?;
}
if self.host.ne(&cn) {
error!(
"Hostname ({:?}) does not match CommonName ({:?})",
self.host, cn
);
return Err(ProtocolError::InvalidCertificate);
}
self.certificate.replace(cert.try_into()?);
self.certificate_transcript.replace(transcript.clone());
Ok(())
}
fn verify_signature(&mut self, verify: HandshakeVerifyRef) -> Result<(), ProtocolError> {
let handshake_hash = unwrap!(self.certificate_transcript.take());
let ctx_str = b"TLS 1.3, server CertificateVerify\x00";
let mut msg: Vec<u8, 146> = Vec::new();
msg.resize(64, 0x20)
.map_err(|_| ProtocolError::EncodeError)?;
msg.extend_from_slice(ctx_str)
.map_err(|_| ProtocolError::EncodeError)?;
msg.extend_from_slice(&handshake_hash.finalize())
.map_err(|_| ProtocolError::EncodeError)?;
let certificate = unwrap!(self.certificate.as_ref()).try_into()?;
verify_signature(&msg[..], &certificate, &verify)?;
Ok(())
}
}
fn verify_signature(
message: &[u8],
certificate: &ServerCertificate,
verify: &HandshakeVerifyRef,
) -> Result<(), ProtocolError> {
let verified;
let certificate =
if let Some(CertificateEntryRef::X509(certificate)) = certificate.entries.first() {
certificate
} else {
return Err(ProtocolError::DecodeError);
};
let certificate =
DecodedCertificate::from_der(certificate).map_err(|_| ProtocolError::DecodeError)?;
let public_key = certificate
.tbs_certificate
.subject_public_key_info
.public_key
.as_bytes()
.ok_or(ProtocolError::DecodeError)?;
match verify.signature_scheme {
SignatureScheme::EcdsaSecp256r1Sha256 => {
use p256::ecdsa::{Signature, VerifyingKey, signature::Verifier};
let verifying_key = VerifyingKey::from_sec1_bytes(public_key)
.map_err(|_| ProtocolError::DecodeError)?;
let signature =
Signature::from_der(&verify.signature).map_err(|_| ProtocolError::DecodeError)?;
verified = verifying_key.verify(message, &signature).is_ok();
}
#[cfg(feature = "p384")]
SignatureScheme::EcdsaSecp384r1Sha384 => {
use p384::ecdsa::{Signature, VerifyingKey, signature::Verifier};
let verifying_key = VerifyingKey::from_sec1_bytes(public_key)
.map_err(|_| ProtocolError::DecodeError)?;
let signature =
Signature::from_der(&verify.signature).map_err(|_| ProtocolError::DecodeError)?;
verified = verifying_key.verify(message, &signature).is_ok();
}
#[cfg(feature = "ed25519")]
SignatureScheme::Ed25519 => {
use ed25519_dalek::{Signature, Verifier, VerifyingKey};
let verifying_key: VerifyingKey =
VerifyingKey::from_bytes(public_key.try_into().unwrap())
.map_err(|_| ProtocolError::DecodeError)?;
let signature =
Signature::try_from(verify.signature).map_err(|_| ProtocolError::DecodeError)?;
verified = verifying_key.verify(message, &signature).is_ok();
}
#[cfg(feature = "rsa")]
SignatureScheme::RsaPssRsaeSha256 => {
use rsa::{
RsaPublicKey,
pkcs1::DecodeRsaPublicKey,
pss::{Signature, VerifyingKey},
signature::Verifier,
};
use sha2::Sha256;
let der_pubkey = RsaPublicKey::from_pkcs1_der(public_key).unwrap();
let verifying_key = VerifyingKey::<Sha256>::from(der_pubkey);
let signature =
Signature::try_from(verify.signature).map_err(|_| ProtocolError::DecodeError)?;
verified = verifying_key.verify(message, &signature).is_ok();
}
#[cfg(feature = "rsa")]
SignatureScheme::RsaPssRsaeSha384 => {
use rsa::{
RsaPublicKey,
pkcs1::DecodeRsaPublicKey,
pss::{Signature, VerifyingKey},
signature::Verifier,
};
use sha2::Sha384;
let der_pubkey =
RsaPublicKey::from_pkcs1_der(public_key).map_err(|_| ProtocolError::DecodeError)?;
let verifying_key = VerifyingKey::<Sha384>::from(der_pubkey);
let signature =
Signature::try_from(verify.signature).map_err(|_| ProtocolError::DecodeError)?;
verified = verifying_key.verify(message, &signature).is_ok();
}
#[cfg(feature = "rsa")]
SignatureScheme::RsaPssRsaeSha512 => {
use rsa::{
RsaPublicKey,
pkcs1::DecodeRsaPublicKey,
pss::{Signature, VerifyingKey},
signature::Verifier,
};
use sha2::Sha512;
let der_pubkey =
RsaPublicKey::from_pkcs1_der(public_key).map_err(|_| ProtocolError::DecodeError)?;
let verifying_key = VerifyingKey::<Sha512>::from(der_pubkey);
let signature =
Signature::try_from(verify.signature).map_err(|_| ProtocolError::DecodeError)?;
verified = verifying_key.verify(message, &signature).is_ok();
}
_ => {
error!(
"InvalidSignatureScheme: {:?} Are you missing a feature?",
verify.signature_scheme
);
return Err(ProtocolError::InvalidSignatureScheme);
}
}
if !verified {
return Err(ProtocolError::InvalidSignature);
}
Ok(())
}
fn get_certificate_tlv_bytes<'a>(input: &[u8]) -> der::Result<&[u8]> {
use der::{Decode, Reader, SliceReader};
let mut reader = SliceReader::new(input)?;
let top_header = der::Header::decode(&mut reader)?;
top_header.tag().assert_eq(der::Tag::Sequence)?;
let header = der::Header::peek(&mut reader)?;
header.tag().assert_eq(der::Tag::Sequence)?;
reader.tlv_bytes()
}
fn get_cert_time(time: Time) -> u64 {
match time {
Time::UtcTime(utc_time) => utc_time.to_unix_duration().as_secs(),
Time::GeneralTime(generalized_time) => generalized_time.to_unix_duration().as_secs(),
}
}
fn verify_certificate(
verifier: &CertificateEntryRef,
certificate: &CertificateEntryRef,
now: Option<u64>,
) -> Result<Option<heapless::String<HOSTNAME_MAXLEN>>, ProtocolError> {
let mut verified = false;
let mut common_name = None;
let ca_certificate = if let CertificateEntryRef::X509(verifier) = verifier {
DecodedCertificate::from_der(verifier).map_err(|_| ProtocolError::DecodeError)?
} else {
return Err(ProtocolError::DecodeError);
};
if let CertificateEntryRef::X509(certificate) = certificate {
let parsed_certificate =
DecodedCertificate::from_der(certificate).map_err(|_| ProtocolError::DecodeError)?;
let ca_public_key = ca_certificate
.tbs_certificate
.subject_public_key_info
.public_key
.as_bytes()
.ok_or(ProtocolError::DecodeError)?;
for elems in parsed_certificate.tbs_certificate.subject.iter() {
let attrs = elems
.get(0)
.ok_or(ProtocolError::ParseError(ParseError::InvalidData))?;
if attrs.oid == COMMON_NAME_OID {
let mut v: Vec<u8, HOSTNAME_MAXLEN> = Vec::new();
v.extend_from_slice(attrs.value.value())
.map_err(|_| ProtocolError::ParseError(ParseError::InvalidData))?;
common_name = String::from_utf8(v).ok();
debug!("CommonName: {:?}", common_name);
}
}
if let Some(now) = now {
if get_cert_time(parsed_certificate.tbs_certificate.validity.not_before) > now
|| get_cert_time(parsed_certificate.tbs_certificate.validity.not_after) < now
{
return Err(ProtocolError::InvalidCertificate);
}
debug!("Epoch is {} and certificate is valid!", now)
}
let certificate_data =
get_certificate_tlv_bytes(certificate).map_err(|_| ProtocolError::DecodeError)?;
match parsed_certificate.signature_algorithm {
ECDSA_SHA256 => {
use p256::ecdsa::{Signature, VerifyingKey, signature::Verifier};
let verifying_key = VerifyingKey::from_sec1_bytes(ca_public_key)
.map_err(|_| ProtocolError::DecodeError)?;
let signature = Signature::from_der(
parsed_certificate
.signature
.as_bytes()
.ok_or(ProtocolError::ParseError(ParseError::InvalidData))?,
)
.map_err(|_| ProtocolError::ParseError(ParseError::InvalidData))?;
verified = verifying_key.verify(&certificate_data, &signature).is_ok();
}
#[cfg(feature = "p384")]
ECDSA_SHA384 => {
use p384::ecdsa::{Signature, VerifyingKey, signature::Verifier};
let verifying_key = VerifyingKey::from_sec1_bytes(ca_public_key)
.map_err(|_| ProtocolError::DecodeError)?;
let signature = Signature::from_der(
parsed_certificate
.signature
.as_bytes()
.ok_or(ProtocolError::ParseError(ParseError::InvalidData))?,
)
.map_err(|_| ProtocolError::ParseError(ParseError::InvalidData))?;
verified = verifying_key.verify(&certificate_data, &signature).is_ok();
}
#[cfg(feature = "ed25519")]
ED25519 => {
use ed25519_dalek::{Signature, Verifier, VerifyingKey};
let verifying_key: VerifyingKey =
VerifyingKey::from_bytes(ca_public_key.try_into().unwrap())
.map_err(|_| ProtocolError::DecodeError)?;
let signature = Signature::try_from(
parsed_certificate
.signature
.as_bytes()
.ok_or(ProtocolError::ParseError(ParseError::InvalidData))?,
)
.map_err(|_| ProtocolError::ParseError(ParseError::InvalidData))?;
verified = verifying_key.verify(certificate_data, &signature).is_ok();
}
#[cfg(feature = "rsa")]
a if a == RSA_PKCS1_SHA256 => {
use rsa::{
pkcs1::DecodeRsaPublicKey,
pkcs1v15::{Signature, VerifyingKey},
signature::Verifier,
};
use sha2::Sha256;
let verifying_key =
VerifyingKey::<Sha256>::from_pkcs1_der(ca_public_key).map_err(|e| {
error!("VerifyingKey: {}", e);
ProtocolError::DecodeError
})?;
let signature = Signature::try_from(
parsed_certificate
.signature
.as_bytes()
.ok_or(ProtocolError::ParseError(ParseError::InvalidData))?,
)
.map_err(|e| {
error!("Signature: {}", e);
ProtocolError::ParseError(ParseError::InvalidData)
})?;
verified = verifying_key.verify(certificate_data, &signature).is_ok();
}
#[cfg(feature = "rsa")]
a if a == RSA_PKCS1_SHA384 => {
use rsa::{
pkcs1::DecodeRsaPublicKey,
pkcs1v15::{Signature, VerifyingKey},
signature::Verifier,
};
use sha2::Sha384;
let verifying_key = VerifyingKey::<Sha384>::from_pkcs1_der(ca_public_key)
.map_err(|_| ProtocolError::DecodeError)?;
let signature = Signature::try_from(
parsed_certificate
.signature
.as_bytes()
.ok_or(ProtocolError::ParseError(ParseError::InvalidData))?,
)
.map_err(|_| ProtocolError::ParseError(ParseError::InvalidData))?;
verified = verifying_key.verify(certificate_data, &signature).is_ok();
}
#[cfg(feature = "rsa")]
a if a == RSA_PKCS1_SHA512 => {
use rsa::{
pkcs1::DecodeRsaPublicKey,
pkcs1v15::{Signature, VerifyingKey},
signature::Verifier,
};
use sha2::Sha512;
let verifying_key = VerifyingKey::<Sha512>::from_pkcs1_der(ca_public_key)
.map_err(|_| ProtocolError::DecodeError)?;
let signature = Signature::try_from(
parsed_certificate
.signature
.as_bytes()
.ok_or(ProtocolError::ParseError(ParseError::InvalidData))?,
)
.map_err(|_| ProtocolError::ParseError(ParseError::InvalidData))?;
verified = verifying_key.verify(certificate_data, &signature).is_ok();
}
_ => {
error!(
"Unsupported signature alg: {:?}",
parsed_certificate.signature_algorithm
);
return Err(ProtocolError::InvalidSignatureScheme);
}
}
}
if !verified {
return Err(ProtocolError::InvalidCertificate);
}
Ok(common_name)
}

171
src/parse_buffer.rs Normal file
View File

@@ -0,0 +1,171 @@
use crate::ProtocolError;
use heapless::{CapacityError, Vec};
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum ParseError {
InsufficientBytes,
InsufficientSpace,
InvalidData,
}
pub struct ParseBuffer<'b> {
pos: usize,
buffer: &'b [u8],
}
impl<'b> From<&'b [u8]> for ParseBuffer<'b> {
fn from(val: &'b [u8]) -> Self {
ParseBuffer::new(val)
}
}
impl<'b, const N: usize> From<ParseBuffer<'b>> for Result<Vec<u8, N>, CapacityError> {
fn from(val: ParseBuffer<'b>) -> Self {
Vec::from_slice(&val.buffer[val.pos..])
}
}
impl<'b> ParseBuffer<'b> {
pub fn new(buffer: &'b [u8]) -> Self {
Self { pos: 0, buffer }
}
pub fn is_empty(&self) -> bool {
self.pos == self.buffer.len()
}
pub fn remaining(&self) -> usize {
self.buffer.len() - self.pos
}
pub fn offset(&self) -> usize {
self.pos
}
pub fn as_slice(&self) -> &'b [u8] {
self.buffer
}
pub fn slice(&mut self, len: usize) -> Result<ParseBuffer<'b>, ParseError> {
if self.pos + len <= self.buffer.len() {
let slice = ParseBuffer::new(&self.buffer[self.pos..self.pos + len]);
self.pos += len;
Ok(slice)
} else {
Err(ParseError::InsufficientBytes)
}
}
pub fn read_u8(&mut self) -> Result<u8, ParseError> {
if self.pos < self.buffer.len() {
let value = self.buffer[self.pos];
self.pos += 1;
Ok(value)
} else {
Err(ParseError::InsufficientBytes)
}
}
pub fn read_u16(&mut self) -> Result<u16, ParseError> {
if self.pos + 2 <= self.buffer.len() {
let value = u16::from_be_bytes([self.buffer[self.pos], self.buffer[self.pos + 1]]);
self.pos += 2;
Ok(value)
} else {
Err(ParseError::InsufficientBytes)
}
}
pub fn read_u24(&mut self) -> Result<u32, ParseError> {
if self.pos + 3 <= self.buffer.len() {
let value = u32::from_be_bytes([
0,
self.buffer[self.pos],
self.buffer[self.pos + 1],
self.buffer[self.pos + 2],
]);
self.pos += 3;
Ok(value)
} else {
Err(ParseError::InsufficientBytes)
}
}
pub fn read_u32(&mut self) -> Result<u32, ParseError> {
if self.pos + 4 <= self.buffer.len() {
let value = u32::from_be_bytes([
self.buffer[self.pos],
self.buffer[self.pos + 1],
self.buffer[self.pos + 2],
self.buffer[self.pos + 3],
]);
self.pos += 4;
Ok(value)
} else {
Err(ParseError::InsufficientBytes)
}
}
pub fn fill(&mut self, dest: &mut [u8]) -> Result<(), ParseError> {
if self.pos + dest.len() <= self.buffer.len() {
dest.copy_from_slice(&self.buffer[self.pos..self.pos + dest.len()]);
self.pos += dest.len();
Ok(())
} else {
Err(ParseError::InsufficientBytes)
}
}
pub fn copy<const N: usize>(
&mut self,
dest: &mut Vec<u8, N>,
num_bytes: usize,
) -> Result<(), ParseError> {
let space = dest.capacity() - dest.len();
if space < num_bytes {
error!(
"Insufficient space in destination buffer. Space: {} required: {}",
space, num_bytes
);
Err(ParseError::InsufficientSpace)
} else if self.pos + num_bytes <= self.buffer.len() {
dest.extend_from_slice(&self.buffer[self.pos..self.pos + num_bytes])
.map_err(|_| {
error!(
"Failed to extend destination buffer. Space: {} required: {}",
space, num_bytes
);
ParseError::InsufficientSpace
})?;
self.pos += num_bytes;
Ok(())
} else {
Err(ParseError::InsufficientBytes)
}
}
pub fn read_list<T, const N: usize>(
&mut self,
data_length: usize,
read: impl Fn(&mut ParseBuffer<'b>) -> Result<T, ParseError>,
) -> Result<Vec<T, N>, ParseError> {
let mut result = Vec::new();
let mut data = self.slice(data_length)?;
while !data.is_empty() {
result.push(read(&mut data)?).map_err(|_| {
error!("Failed to store parse result");
ParseError::InsufficientSpace
})?;
}
Ok(result)
}
}
impl From<ParseError> for ProtocolError {
fn from(e: ParseError) -> Self {
ProtocolError::ParseError(e)
}
}

171
src/read_buffer.rs Normal file
View File

@@ -0,0 +1,171 @@
#[must_use]
pub struct ReadBuffer<'a> {
data: &'a [u8],
consumed: usize,
used: bool,
decrypted_consumed: &'a mut usize,
}
impl<'a> ReadBuffer<'a> {
#[inline]
pub(crate) fn new(buffer: &'a [u8], decrypted_consumed: &'a mut usize) -> Self {
Self {
data: buffer,
consumed: 0,
used: false,
decrypted_consumed,
}
}
#[inline]
#[must_use]
pub fn len(&self) -> usize {
self.data.len() - self.consumed
}
#[inline]
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
#[inline]
pub fn peek(&mut self, count: usize) -> &'a [u8] {
let count = self.len().min(count);
let start = self.consumed;
self.used = true;
&self.data[start..start + count]
}
#[inline]
pub fn peek_all(&mut self) -> &'a [u8] {
self.peek(self.len())
}
#[inline]
pub fn pop(&mut self, count: usize) -> &'a [u8] {
let count = self.len().min(count);
let start = self.consumed;
self.consumed += count;
self.used = true;
&self.data[start..start + count]
}
#[inline]
pub fn pop_all(&mut self) -> &'a [u8] {
self.pop(self.len())
}
#[inline]
pub fn revert(self) {
core::mem::forget(self);
}
#[inline]
pub fn pop_into(&mut self, buf: &mut [u8]) -> usize {
let to_copy = self.pop(buf.len());
buf[..to_copy.len()].copy_from_slice(to_copy);
to_copy.len()
}
}
impl Drop for ReadBuffer<'_> {
#[inline]
fn drop(&mut self) {
*self.decrypted_consumed += if self.used {
self.consumed
} else {
self.data.len()
};
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn dropping_unused_buffer_consumes_all() {
let mut consumed = 1000;
let buffer = [0, 1, 2, 3];
_ = ReadBuffer::new(&buffer, &mut consumed);
assert_eq!(consumed, 1004);
}
#[test]
fn pop_moves_internal_cursor() {
let mut consumed = 0;
let mut buffer = ReadBuffer::new(&[0, 1, 2, 3], &mut consumed);
assert_eq!(buffer.pop(1), &[0]);
assert_eq!(buffer.pop(1), &[1]);
assert_eq!(buffer.pop(1), &[2]);
}
#[test]
fn dropping_consumes_as_many_bytes_as_used() {
let mut consumed = 0;
let mut buffer = ReadBuffer::new(&[0, 1, 2, 3], &mut consumed);
assert_eq!(buffer.pop(1), &[0]);
assert_eq!(buffer.pop(1), &[1]);
assert_eq!(buffer.pop(1), &[2]);
core::mem::drop(buffer);
assert_eq!(consumed, 3);
}
#[test]
fn pop_returns_fewer_bytes_if_requested_more_than_what_it_has() {
let mut consumed = 0;
let mut buffer = ReadBuffer::new(&[0, 1, 2, 3], &mut consumed);
assert_eq!(buffer.pop(1), &[0]);
assert_eq!(buffer.pop(1), &[1]);
assert_eq!(buffer.pop(4), &[2, 3]);
assert_eq!(buffer.pop(1), &[]);
core::mem::drop(buffer);
assert_eq!(consumed, 4);
}
#[test]
fn peek_does_not_consume() {
let mut consumed = 0;
let mut buffer = ReadBuffer::new(&[0, 1, 2, 3], &mut consumed);
assert_eq!(buffer.peek(1), &[0]);
assert_eq!(buffer.peek(1), &[0]);
core::mem::drop(buffer);
assert_eq!(consumed, 0);
}
#[test]
fn revert_undoes_pop() {
let mut consumed = 0;
let mut buffer = ReadBuffer::new(&[0, 1, 2, 3], &mut consumed);
assert_eq!(buffer.pop(4), &[0, 1, 2, 3]);
buffer.revert();
assert_eq!(consumed, 0);
}
}

215
src/record.rs Normal file
View File

@@ -0,0 +1,215 @@
use crate::ProtocolError;
use crate::application_data::ApplicationData;
use crate::change_cipher_spec::ChangeCipherSpec;
use crate::config::{ConnectConfig, TlsCipherSuite};
use crate::content_types::ContentType;
use crate::handshake::client_hello::ClientHello;
use crate::handshake::{ClientHandshake, ServerHandshake};
use crate::key_schedule::WriteKeySchedule;
use crate::{CryptoBackend, buffer::CryptoBuffer};
use crate::{
alert::{Alert, AlertDescription, AlertLevel},
parse_buffer::ParseBuffer,
};
use core::fmt::Debug;
pub type Encrypted = bool;
#[allow(clippy::large_enum_variant)]
pub enum ClientRecord<'config, 'a, CipherSuite>
where
CipherSuite: TlsCipherSuite,
{
Handshake(ClientHandshake<'config, 'a, CipherSuite>, Encrypted),
Alert(Alert, Encrypted),
}
#[derive(Clone, Copy, PartialEq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum ClientRecordHeader {
Handshake(Encrypted),
Alert(Encrypted),
ApplicationData,
}
impl ClientRecordHeader {
pub fn is_encrypted(self) -> bool {
match self {
ClientRecordHeader::Handshake(encrypted) | ClientRecordHeader::Alert(encrypted) => {
encrypted
}
ClientRecordHeader::ApplicationData => true,
}
}
pub fn header_content_type(self) -> ContentType {
match self {
Self::Handshake(false) => ContentType::Handshake,
Self::Alert(false) => ContentType::ChangeCipherSpec,
Self::Handshake(true) | Self::Alert(true) | Self::ApplicationData => {
ContentType::ApplicationData
}
}
}
pub fn trailer_content_type(self) -> ContentType {
match self {
Self::Handshake(_) => ContentType::Handshake,
Self::Alert(_) => ContentType::Alert,
Self::ApplicationData => ContentType::ApplicationData,
}
}
pub fn version(self) -> [u8; 2] {
match self {
Self::Handshake(true) | Self::Alert(true) | Self::ApplicationData => [0x03, 0x03],
Self::Handshake(false) | Self::Alert(false) => [0x03, 0x01],
}
}
pub fn encode(self, buf: &mut CryptoBuffer) -> Result<(), ProtocolError> {
buf.push(self.header_content_type() as u8)
.map_err(|_| ProtocolError::EncodeError)?;
buf.extend_from_slice(&self.version())
.map_err(|_| ProtocolError::EncodeError)?;
Ok(())
}
}
impl<'config, CipherSuite> ClientRecord<'config, '_, CipherSuite>
where
CipherSuite: TlsCipherSuite,
{
pub fn header(&self) -> ClientRecordHeader {
match self {
ClientRecord::Handshake(_, encrypted) => ClientRecordHeader::Handshake(*encrypted),
ClientRecord::Alert(_, encrypted) => ClientRecordHeader::Alert(*encrypted),
}
}
pub fn client_hello<CP>(config: &'config ConnectConfig<'config>, provider: &mut CP) -> Self
where
CP: CryptoBackend,
{
ClientRecord::Handshake(
ClientHandshake::ClientHello(ClientHello::new(config, provider)),
false,
)
}
pub fn close_notify(opened: bool) -> Self {
ClientRecord::Alert(
Alert::new(AlertLevel::Warning, AlertDescription::CloseNotify),
opened,
)
}
pub(crate) fn encode_payload(&self, buf: &mut CryptoBuffer) -> Result<usize, ProtocolError> {
let record_length_marker = buf.len();
match self {
ClientRecord::Handshake(handshake, _) => handshake.encode(buf)?,
ClientRecord::Alert(alert, _) => alert.encode(buf)?,
}
Ok(buf.len() - record_length_marker)
}
pub fn finish_record(
&self,
buf: &mut CryptoBuffer,
transcript: &mut CipherSuite::Hash,
write_key_schedule: &mut WriteKeySchedule<CipherSuite>,
) -> Result<(), ProtocolError> {
match self {
ClientRecord::Handshake(handshake, false) => {
handshake.finalize(buf, transcript, write_key_schedule)
}
ClientRecord::Handshake(_, true) => {
ClientHandshake::<CipherSuite>::finalize_encrypted(buf, transcript);
Ok(())
}
_ => Ok(()),
}
}
}
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[allow(clippy::large_enum_variant)]
pub enum ServerRecord<'a, CipherSuite: TlsCipherSuite> {
Handshake(ServerHandshake<'a, CipherSuite>),
ChangeCipherSpec(ChangeCipherSpec),
Alert(Alert),
ApplicationData(ApplicationData<'a>),
}
pub struct RecordHeader {
header: [u8; 5],
}
impl RecordHeader {
pub const LEN: usize = 5;
pub fn content_type(&self) -> ContentType {
unwrap!(ContentType::of(self.header[0]))
}
pub fn content_length(&self) -> usize {
u16::from_be_bytes([self.header[3], self.header[4]]) as usize
}
pub fn data(&self) -> &[u8; 5] {
&self.header
}
pub fn decode(header: [u8; 5]) -> Result<RecordHeader, ProtocolError> {
match ContentType::of(header[0]) {
None => Err(ProtocolError::InvalidRecord),
Some(_) => Ok(RecordHeader { header }),
}
}
}
impl<'a, CipherSuite: TlsCipherSuite> ServerRecord<'a, CipherSuite> {
pub fn content_type(&self) -> ContentType {
match self {
ServerRecord::Handshake(_) => ContentType::Handshake,
ServerRecord::ChangeCipherSpec(_) => ContentType::ChangeCipherSpec,
ServerRecord::Alert(_) => ContentType::Alert,
ServerRecord::ApplicationData(_) => ContentType::ApplicationData,
}
}
pub fn decode(
header: RecordHeader,
data: &'a mut [u8],
digest: &mut CipherSuite::Hash,
) -> Result<ServerRecord<'a, CipherSuite>, ProtocolError> {
assert_eq!(header.content_length(), data.len());
match header.content_type() {
ContentType::Invalid => Err(ProtocolError::Unimplemented),
ContentType::ChangeCipherSpec => Ok(ServerRecord::ChangeCipherSpec(
ChangeCipherSpec::read(data)?,
)),
ContentType::Alert => {
let mut parse = ParseBuffer::new(data);
let alert = Alert::parse(&mut parse)?;
Ok(ServerRecord::Alert(alert))
}
ContentType::Handshake => {
let mut parse = ParseBuffer::new(data);
Ok(ServerRecord::Handshake(ServerHandshake::read(
&mut parse, digest,
)?))
}
ContentType::ApplicationData => {
let buf = CryptoBuffer::wrap_with_pos(data, data.len());
Ok(ServerRecord::ApplicationData(ApplicationData::new(
buf, header,
)))
}
}
}
}

471
src/record_reader.rs Normal file
View File

@@ -0,0 +1,471 @@
use crate::key_schedule::ReadKeySchedule;
use embedded_io::{Error as _, Read as BlockingRead};
use embedded_io_async::Read as AsyncRead;
use crate::{
ProtocolError,
config::TlsCipherSuite,
record::{RecordHeader, ServerRecord},
};
/// Stateful reader that reassembles TLS records from a byte stream into the shared receive buffer.
///
/// `decoded` tracks how many bytes at the start of `buf` have already been handed to the caller;
/// `pending` tracks bytes that have been read from the transport but not yet consumed as a record.
pub struct RecordReader<'a> {
pub(crate) buf: &'a mut [u8],
decoded: usize,
pending: usize,
}
pub struct RecordReaderBorrowMut<'a> {
pub(crate) buf: &'a mut [u8],
decoded: &'a mut usize,
pending: &'a mut usize,
}
impl<'a> RecordReader<'a> {
pub fn new(buf: &'a mut [u8]) -> Self {
// TLS 1.3 max plaintext record is 16 384 bytes + 256 bytes overhead = 16 640 bytes
if buf.len() < 16640 {
warn!("Read buffer is smaller than 16640 bytes, which may cause problems!");
}
Self {
buf,
decoded: 0,
pending: 0,
}
}
pub fn reborrow_mut(&mut self) -> RecordReaderBorrowMut<'_> {
RecordReaderBorrowMut {
buf: self.buf,
decoded: &mut self.decoded,
pending: &mut self.pending,
}
}
pub async fn read<'m, CipherSuite: TlsCipherSuite>(
&'m mut self,
transport: &mut impl AsyncRead,
key_schedule: &mut ReadKeySchedule<CipherSuite>,
) -> Result<ServerRecord<'m, CipherSuite>, ProtocolError> {
read(
self.buf,
&mut self.decoded,
&mut self.pending,
transport,
key_schedule,
)
.await
}
pub fn read_blocking<'m, CipherSuite: TlsCipherSuite>(
&'m mut self,
transport: &mut impl BlockingRead,
key_schedule: &mut ReadKeySchedule<CipherSuite>,
) -> Result<ServerRecord<'m, CipherSuite>, ProtocolError> {
read_blocking(
self.buf,
&mut self.decoded,
&mut self.pending,
transport,
key_schedule,
)
}
}
impl RecordReaderBorrowMut<'_> {
pub async fn read<'m, CipherSuite: TlsCipherSuite>(
&'m mut self,
transport: &mut impl AsyncRead,
key_schedule: &mut ReadKeySchedule<CipherSuite>,
) -> Result<ServerRecord<'m, CipherSuite>, ProtocolError> {
read(
self.buf,
self.decoded,
self.pending,
transport,
key_schedule,
)
.await
}
pub fn read_blocking<'m, CipherSuite: TlsCipherSuite>(
&'m mut self,
transport: &mut impl BlockingRead,
key_schedule: &mut ReadKeySchedule<CipherSuite>,
) -> Result<ServerRecord<'m, CipherSuite>, ProtocolError> {
read_blocking(
self.buf,
self.decoded,
self.pending,
transport,
key_schedule,
)
}
}
pub async fn read<'m, CipherSuite: TlsCipherSuite>(
buf: &'m mut [u8],
decoded: &mut usize,
pending: &mut usize,
transport: &mut impl AsyncRead,
key_schedule: &mut ReadKeySchedule<CipherSuite>,
) -> Result<ServerRecord<'m, CipherSuite>, ProtocolError> {
let header: RecordHeader = next_record_header(transport).await?;
advance(buf, decoded, pending, transport, header.content_length()).await?;
consume(
buf,
decoded,
pending,
header,
key_schedule.transcript_hash(),
)
}
pub fn read_blocking<'m, CipherSuite: TlsCipherSuite>(
buf: &'m mut [u8],
decoded: &mut usize,
pending: &mut usize,
transport: &mut impl BlockingRead,
key_schedule: &mut ReadKeySchedule<CipherSuite>,
) -> Result<ServerRecord<'m, CipherSuite>, ProtocolError> {
let header: RecordHeader = next_record_header_blocking(transport)?;
advance_blocking(buf, decoded, pending, transport, header.content_length())?;
consume(
buf,
decoded,
pending,
header,
key_schedule.transcript_hash(),
)
}
async fn next_record_header(transport: &mut impl AsyncRead) -> Result<RecordHeader, ProtocolError> {
let mut buf: [u8; RecordHeader::LEN] = [0; RecordHeader::LEN];
let mut total_read: usize = 0;
while total_read != RecordHeader::LEN {
let read: usize = transport
.read(&mut buf[total_read..])
.await
.map_err(|e| ProtocolError::Io(e.kind()))?;
if read == 0 {
return Err(ProtocolError::IoError);
}
total_read += read;
}
RecordHeader::decode(buf)
}
fn next_record_header_blocking(
transport: &mut impl BlockingRead,
) -> Result<RecordHeader, ProtocolError> {
let mut buf: [u8; RecordHeader::LEN] = [0; RecordHeader::LEN];
let mut total_read: usize = 0;
while total_read != RecordHeader::LEN {
let read: usize = transport
.read(&mut buf[total_read..])
.map_err(|e| ProtocolError::Io(e.kind()))?;
if read == 0 {
return Err(ProtocolError::IoError);
}
total_read += read;
}
RecordHeader::decode(buf)
}
async fn advance(
buf: &mut [u8],
decoded: &mut usize,
pending: &mut usize,
transport: &mut impl AsyncRead,
amount: usize,
) -> Result<(), ProtocolError> {
ensure_contiguous(buf, decoded, pending, amount)?;
let mut remain: usize = amount;
while *pending < amount {
let read = transport
.read(&mut buf[*decoded + *pending..][..remain])
.await
.map_err(|e| ProtocolError::Io(e.kind()))?;
if read == 0 {
return Err(ProtocolError::IoError);
}
remain -= read;
*pending += read;
}
Ok(())
}
fn advance_blocking(
buf: &mut [u8],
decoded: &mut usize,
pending: &mut usize,
transport: &mut impl BlockingRead,
amount: usize,
) -> Result<(), ProtocolError> {
ensure_contiguous(buf, decoded, pending, amount)?;
let mut remain: usize = amount;
while *pending < amount {
let read = transport
.read(&mut buf[*decoded + *pending..][..remain])
.map_err(|e| ProtocolError::Io(e.kind()))?;
if read == 0 {
return Err(ProtocolError::IoError);
}
remain -= read;
*pending += read;
}
Ok(())
}
fn consume<'m, CipherSuite: TlsCipherSuite>(
buf: &'m mut [u8],
decoded: &mut usize,
pending: &mut usize,
header: RecordHeader,
digest: &mut CipherSuite::Hash,
) -> Result<ServerRecord<'m, CipherSuite>, ProtocolError> {
let content_len = header.content_length();
let slice = &mut buf[*decoded..][..content_len];
*decoded += content_len;
*pending -= content_len;
ServerRecord::decode(header, slice, digest)
}
fn ensure_contiguous(
buf: &mut [u8],
decoded: &mut usize,
pending: &mut usize,
len: usize,
) -> Result<(), ProtocolError> {
// If the next record would overflow the end of the buffer, rotate unconsumed bytes to the front
if *decoded + len > buf.len() {
if len > buf.len() {
error!(
"Record too large for buffer. Size: {} Buffer size: {}",
len,
buf.len()
);
return Err(ProtocolError::InsufficientSpace);
}
buf.copy_within(*decoded..*decoded + *pending, 0);
*decoded = 0;
}
Ok(())
}
#[cfg(test)]
mod tests {
use core::convert::Infallible;
use super::*;
use crate::{Aes128GcmSha256, content_types::ContentType, key_schedule::KeySchedule};
struct ChunkRead<'a>(&'a [u8], usize);
impl embedded_io::ErrorType for ChunkRead<'_> {
type Error = Infallible;
}
impl BlockingRead for ChunkRead<'_> {
fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
let len = usize::min(self.1, buf.len());
let len = usize::min(len, self.0.len());
buf[..len].copy_from_slice(&self.0[..len]);
self.0 = &self.0[len..];
Ok(len)
}
}
#[test]
fn can_read_blocking() {
can_read_blocking_case(1);
can_read_blocking_case(2);
can_read_blocking_case(3);
can_read_blocking_case(4);
can_read_blocking_case(5);
can_read_blocking_case(6);
can_read_blocking_case(7);
can_read_blocking_case(8);
can_read_blocking_case(9);
can_read_blocking_case(10);
can_read_blocking_case(11);
can_read_blocking_case(12);
can_read_blocking_case(13);
can_read_blocking_case(14);
can_read_blocking_case(15);
can_read_blocking_case(16);
}
fn can_read_blocking_case(chunk_size: usize) {
let mut transport = ChunkRead(
&[
ContentType::ApplicationData as u8,
0x03,
0x03,
0x00,
0x04,
0xde,
0xad,
0xbe,
0xef,
ContentType::ApplicationData as u8,
0x03,
0x03,
0x00,
0x02,
0xaa,
0xbb,
],
chunk_size,
);
let mut buf = [0; 32];
let mut reader = RecordReader::new(&mut buf);
let mut key_schedule = KeySchedule::<Aes128GcmSha256>::new();
{
if let ServerRecord::ApplicationData(data) = reader
.read_blocking(&mut transport, key_schedule.read_state())
.unwrap()
{
assert_eq!([0xde, 0xad, 0xbe, 0xef], data.data.as_slice());
} else {
panic!("Wrong server record");
}
assert_eq!(4, reader.decoded);
assert_eq!(0, reader.pending);
}
{
if let ServerRecord::ApplicationData(data) = reader
.read_blocking(&mut transport, key_schedule.read_state())
.unwrap()
{
assert_eq!([0xaa, 0xbb], data.data.as_slice());
} else {
panic!("Wrong server record");
}
assert_eq!(6, reader.decoded);
assert_eq!(0, reader.pending);
}
}
#[test]
fn can_read_blocking_must_rotate_buffer() {
let mut transport = [
ContentType::ApplicationData as u8,
0x03,
0x03,
0x00,
0x04,
0xde,
0xad,
0xbe,
0xef,
ContentType::ApplicationData as u8,
0x03,
0x03,
0x00,
0x02,
0xaa,
0xbb,
]
.as_slice();
let mut buf = [0; 4];
let mut reader = RecordReader::new(&mut buf);
let mut key_schedule = KeySchedule::<Aes128GcmSha256>::new();
{
if let ServerRecord::ApplicationData(data) = reader
.read_blocking(&mut transport, key_schedule.read_state())
.unwrap()
{
assert_eq!([0xde, 0xad, 0xbe, 0xef], data.data.as_slice());
} else {
panic!("Wrong server record");
}
assert_eq!(4, reader.decoded);
assert_eq!(0, reader.pending);
}
{
if let ServerRecord::ApplicationData(data) = reader
.read_blocking(&mut transport, key_schedule.read_state())
.unwrap()
{
assert_eq!([0xaa, 0xbb], data.data.as_slice());
} else {
panic!("Wrong server record");
}
assert_eq!(2, reader.decoded);
assert_eq!(0, reader.pending);
}
}
#[test]
fn can_read_empty_record() {
let mut transport = [
ContentType::ApplicationData as u8,
0x03,
0x03,
0x00,
0x00,
ContentType::ApplicationData as u8,
0x03,
0x03,
0x00,
0x00,
]
.as_slice();
let mut buf = [0; 32];
let mut reader = RecordReader::new(&mut buf);
let mut key_schedule = KeySchedule::<Aes128GcmSha256>::new();
{
if let ServerRecord::ApplicationData(data) = reader
.read_blocking(&mut transport, key_schedule.read_state())
.unwrap()
{
assert!(data.data.is_empty());
} else {
panic!("Wrong server record");
}
assert_eq!(0, reader.decoded);
assert_eq!(0, reader.pending);
}
{
if let ServerRecord::ApplicationData(data) = reader
.read_blocking(&mut transport, key_schedule.read_state())
.unwrap()
{
assert!(data.data.is_empty());
} else {
panic!("Wrong server record");
}
assert_eq!(0, reader.decoded);
assert_eq!(0, reader.pending);
}
}
}

20
src/send_policy.rs Normal file
View File

@@ -0,0 +1,20 @@
/// Controls whether `flush()` calls also flush the underlying transport.
///
/// `Strict` (the default) ensures bytes reach the network immediately after every record.
/// `Relaxed` leaves transport flushing to the caller, which can reduce syscall overhead.
#[derive(Default, Clone, Copy, Debug, PartialEq, Eq)]
pub enum FlushPolicy {
/// Only encrypt and hand bytes to the transport; do not call `transport.flush()`.
Relaxed,
/// Call `transport.flush()` after writing each TLS record.
#[default]
Strict,
}
impl FlushPolicy {
#[must_use]
pub fn flush_transport(&self) -> bool {
matches!(self, Self::Strict)
}
}

287
src/write_buffer.rs Normal file
View File

@@ -0,0 +1,287 @@
use crate::{
ProtocolError,
buffer::CryptoBuffer,
config::{TLS_RECORD_OVERHEAD, TlsCipherSuite},
connection::encrypt,
key_schedule::{ReadKeySchedule, WriteKeySchedule},
record::{ClientRecord, ClientRecordHeader},
};
pub struct WriteBuffer<'a> {
buffer: &'a mut [u8],
pos: usize,
current_header: Option<ClientRecordHeader>,
}
pub(crate) struct WriteBufferBorrow<'a> {
buffer: &'a [u8],
pos: &'a usize,
current_header: &'a Option<ClientRecordHeader>,
}
pub(crate) struct WriteBufferBorrowMut<'a> {
buffer: &'a mut [u8],
pos: &'a mut usize,
current_header: &'a mut Option<ClientRecordHeader>,
}
impl<'a> WriteBuffer<'a> {
pub fn new(buffer: &'a mut [u8]) -> Self {
debug_assert!(
buffer.len() > TLS_RECORD_OVERHEAD,
"The write buffer must be sufficiently large to include the tls record overhead"
);
Self {
buffer,
pos: 0,
current_header: None,
}
}
pub(crate) fn reborrow_mut(&mut self) -> WriteBufferBorrowMut<'_> {
WriteBufferBorrowMut {
buffer: self.buffer,
pos: &mut self.pos,
current_header: &mut self.current_header,
}
}
pub(crate) fn reborrow(&self) -> WriteBufferBorrow<'_> {
WriteBufferBorrow {
buffer: self.buffer,
pos: &self.pos,
current_header: &self.current_header,
}
}
pub fn is_full(&self) -> bool {
self.reborrow().is_full()
}
pub fn append(&mut self, buf: &[u8]) -> usize {
self.reborrow_mut().append(buf)
}
pub fn is_empty(&self) -> bool {
self.reborrow().is_empty()
}
pub fn contains(&self, header: ClientRecordHeader) -> bool {
self.reborrow().contains(header)
}
pub(crate) fn start_record(&mut self, header: ClientRecordHeader) -> Result<(), ProtocolError> {
self.reborrow_mut().start_record(header)
}
pub(crate) fn close_record<CipherSuite>(
&mut self,
write_key_schedule: &mut WriteKeySchedule<CipherSuite>,
) -> Result<&[u8], ProtocolError>
where
CipherSuite: TlsCipherSuite,
{
close_record(
self.buffer,
&mut self.pos,
&mut self.current_header,
write_key_schedule,
)
}
pub fn write_record<CipherSuite>(
&mut self,
record: &ClientRecord<CipherSuite>,
write_key_schedule: &mut WriteKeySchedule<CipherSuite>,
read_key_schedule: Option<&mut ReadKeySchedule<CipherSuite>>,
) -> Result<&[u8], ProtocolError>
where
CipherSuite: TlsCipherSuite,
{
write_record(
self.buffer,
&mut self.pos,
&mut self.current_header,
record,
write_key_schedule,
read_key_schedule,
)
}
}
impl WriteBufferBorrow<'_> {
fn max_block_size(&self) -> usize {
self.buffer.len() - TLS_RECORD_OVERHEAD
}
pub fn is_full(&self) -> bool {
*self.pos == self.max_block_size()
}
pub fn len(&self) -> usize {
*self.pos
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn space(&self) -> usize {
self.max_block_size() - *self.pos
}
pub fn contains(&self, header: ClientRecordHeader) -> bool {
self.current_header.as_ref() == Some(&header)
}
}
impl WriteBufferBorrowMut<'_> {
fn reborrow(&self) -> WriteBufferBorrow<'_> {
WriteBufferBorrow {
buffer: self.buffer,
pos: self.pos,
current_header: self.current_header,
}
}
pub fn is_full(&self) -> bool {
self.reborrow().is_full()
}
pub fn is_empty(&self) -> bool {
self.reborrow().is_empty()
}
pub fn contains(&self, header: ClientRecordHeader) -> bool {
self.reborrow().contains(header)
}
pub fn append(&mut self, buf: &[u8]) -> usize {
let buffered = usize::min(buf.len(), self.reborrow().space());
if buffered > 0 {
self.buffer[*self.pos..*self.pos + buffered].copy_from_slice(&buf[..buffered]);
*self.pos += buffered;
}
buffered
}
pub(crate) fn start_record(&mut self, header: ClientRecordHeader) -> Result<(), ProtocolError> {
start_record(self.buffer, self.pos, self.current_header, header)
}
pub fn close_record<CipherSuite>(
&mut self,
write_key_schedule: &mut WriteKeySchedule<CipherSuite>,
) -> Result<&[u8], ProtocolError>
where
CipherSuite: TlsCipherSuite,
{
close_record(
self.buffer,
self.pos,
self.current_header,
write_key_schedule,
)
}
}
fn start_record(
buffer: &mut [u8],
pos: &mut usize,
current_header: &mut Option<ClientRecordHeader>,
header: ClientRecordHeader,
) -> Result<(), ProtocolError> {
debug_assert!(current_header.is_none());
debug!("start_record({:?})", header);
*current_header = Some(header);
with_buffer(buffer, pos, |mut buf| {
header.encode(&mut buf)?;
buf.push_u16(0)?;
Ok(buf.rewind())
})
}
fn with_buffer(
buffer: &mut [u8],
pos: &mut usize,
op: impl FnOnce(CryptoBuffer) -> Result<CryptoBuffer, ProtocolError>,
) -> Result<(), ProtocolError> {
let buf = CryptoBuffer::wrap_with_pos(buffer, *pos);
match op(buf) {
Ok(buf) => {
*pos = buf.len();
Ok(())
}
Err(err) => Err(err),
}
}
fn close_record<'a, CipherSuite>(
buffer: &'a mut [u8],
pos: &mut usize,
current_header: &mut Option<ClientRecordHeader>,
write_key_schedule: &mut WriteKeySchedule<CipherSuite>,
) -> Result<&'a [u8], ProtocolError>
where
CipherSuite: TlsCipherSuite,
{
const HEADER_SIZE: usize = 5;
let header = current_header.take().unwrap();
with_buffer(buffer, pos, |mut buf| {
if !header.is_encrypted() {
return Ok(buf);
}
buf.push(header.trailer_content_type() as u8)
.map_err(|_| ProtocolError::EncodeError)?;
let mut buf = buf.offset(HEADER_SIZE);
encrypt(write_key_schedule, &mut buf)?;
Ok(buf.rewind())
})?;
let [upper, lower] = ((*pos - HEADER_SIZE) as u16).to_be_bytes();
buffer[3] = upper;
buffer[4] = lower;
let slice = &buffer[..*pos];
*pos = 0;
*current_header = None;
Ok(slice)
}
fn write_record<'a, CipherSuite>(
buffer: &'a mut [u8],
pos: &mut usize,
current_header: &mut Option<ClientRecordHeader>,
record: &ClientRecord<CipherSuite>,
write_key_schedule: &mut WriteKeySchedule<CipherSuite>,
read_key_schedule: Option<&mut ReadKeySchedule<CipherSuite>>,
) -> Result<&'a [u8], ProtocolError>
where
CipherSuite: TlsCipherSuite,
{
if current_header.is_some() {
return Err(ProtocolError::InternalError);
}
start_record(buffer, pos, current_header, record.header())?;
with_buffer(buffer, pos, |buf| {
let mut buf = buf.forward();
record.encode_payload(&mut buf)?;
let transcript = read_key_schedule
.ok_or(ProtocolError::InternalError)?
.transcript_hash();
record.finish_record(&mut buf, transcript, write_key_schedule)?;
Ok(buf.rewind())
})?;
close_record(buffer, pos, current_header, write_key_schedule)
}

373
tests/common/mod.rs Normal file
View File

@@ -0,0 +1,373 @@
use std::{path::PathBuf, sync::Arc};
use mio::net::{TcpListener, TcpStream};
use std::collections::HashMap;
use std::fs;
use std::io;
use std::io::{BufReader, Read, Write};
use std::net;
// Token for our listening socket.
pub const LISTENER: mio::Token = mio::Token(0);
// Which mode the server operates in.
#[derive(Clone)]
pub enum ServerMode {
/// Write back received bytes
Echo,
}
/// This binds together a TCP listening socket, some outstanding
/// connections, and a TLS server configuration.
pub struct EchoServer {
server: TcpListener,
connections: HashMap<mio::Token, Connection>,
next_id: usize,
tls_config: Arc<rustls::ServerConfig>,
mode: ServerMode,
}
impl EchoServer {
pub fn new(
server: TcpListener,
mode: ServerMode,
cfg: Arc<rustls::ServerConfig>,
) -> EchoServer {
EchoServer {
server,
connections: HashMap::new(),
next_id: 2,
tls_config: cfg,
mode,
}
}
pub fn accept(&mut self, registry: &mio::Registry) -> Result<(), io::Error> {
loop {
match self.server.accept() {
Ok((socket, addr)) => {
log::debug!("Accepting new connection from {:?}", addr);
let tls_session =
rustls::ServerConnection::new(self.tls_config.clone()).unwrap();
let mode = self.mode.clone();
let token = mio::Token(self.next_id);
self.next_id += 1;
let mut connection = Connection::new(socket, token, mode, tls_session);
connection.register(registry);
self.connections.insert(token, connection);
}
Err(ref err) if err.kind() == io::ErrorKind::WouldBlock => return Ok(()),
Err(err) => {
println!(
"encountered error while accepting connection; err={:?}",
err
);
return Err(err);
}
}
}
}
pub fn conn_event(&mut self, registry: &mio::Registry, event: &mio::event::Event) {
let token = event.token();
if self.connections.contains_key(&token) {
self.connections
.get_mut(&token)
.unwrap()
.ready(registry, event);
if self.connections[&token].is_closed() {
self.connections.remove(&token);
}
}
}
}
/// This is a connection which has been accepted by the server,
/// and is currently being served.
///
/// It has a TCP-level stream, a TLS-level session, and some
/// other state/metadata.
struct Connection {
socket: TcpStream,
token: mio::Token,
closing: bool,
closed: bool,
mode: ServerMode,
tls_session: rustls::ServerConnection,
back: Option<TcpStream>,
}
/// Open a plaintext TCP-level connection for forwarded connections.
fn open_back(_mode: &ServerMode) -> Option<TcpStream> {
None
}
/// This used to be conveniently exposed by mio: map EWOULDBLOCK
/// errors to something less-errory.
fn try_read(r: io::Result<usize>) -> io::Result<Option<usize>> {
match r {
Ok(len) => Ok(Some(len)),
Err(e) if e.kind() == io::ErrorKind::WouldBlock => Ok(None),
Err(e) => Err(e),
}
}
impl Connection {
fn new(
socket: TcpStream,
token: mio::Token,
mode: ServerMode,
tls_session: rustls::ServerConnection,
) -> Connection {
let back = open_back(&mode);
Connection {
socket,
token,
closing: false,
closed: false,
mode,
tls_session,
back,
}
}
/// We're a connection, and we have something to do.
fn ready(&mut self, registry: &mio::Registry, ev: &mio::event::Event) {
if ev.is_readable() {
self.do_tls_read();
self.try_plain_read();
self.try_back_read();
}
if ev.is_writable() {
self.do_tls_write_and_handle_error();
}
if self.closing {
let _ = self.socket.shutdown(net::Shutdown::Both);
self.close_back();
self.closed = true;
self.deregister(registry);
} else {
self.reregister(registry);
}
}
fn close_back(&mut self) {
if self.back.is_some() {
let back = self.back.as_mut().unwrap();
back.shutdown(net::Shutdown::Both).unwrap();
}
self.back = None;
}
fn do_tls_read(&mut self) {
let rc = self.tls_session.read_tls(&mut self.socket);
if rc.is_err() {
let err = rc.unwrap_err();
if let io::ErrorKind::WouldBlock = err.kind() {
return;
}
log::warn!("read error {:?}", err);
self.closing = true;
return;
}
if rc.unwrap() == 0 {
log::debug!("eof");
self.closing = true;
return;
}
let processed = self.tls_session.process_new_packets();
if processed.is_err() {
log::warn!("cannot process packet: {:?}", processed);
self.do_tls_write_and_handle_error();
self.closing = true;
}
}
fn try_plain_read(&mut self) {
let mut buf = Vec::new();
let rc = self.tls_session.reader().read_to_end(&mut buf);
if let Err(ref e) = rc {
if e.kind() != io::ErrorKind::WouldBlock {
log::warn!("plaintext read failed: {:?}", rc);
self.closing = true;
return;
}
}
if !buf.is_empty() {
log::debug!("plaintext read {:?}", buf.len());
self.incoming_plaintext(&buf);
}
}
fn try_back_read(&mut self) {
if self.back.is_none() {
return;
}
let mut buf = [0u8; 1024];
let back = self.back.as_mut().unwrap();
let rc = try_read(back.read(&mut buf));
if rc.is_err() {
log::warn!("backend read failed: {:?}", rc);
self.closing = true;
return;
}
let maybe_len = rc.unwrap();
match maybe_len {
Some(0) => {
log::debug!("back eof");
self.closing = true;
}
Some(len) => {
self.tls_session.writer().write_all(&buf[..len]).unwrap();
}
None => {}
};
}
fn incoming_plaintext(&mut self, buf: &[u8]) {
match self.mode {
ServerMode::Echo => {
self.tls_session.writer().write_all(buf).unwrap();
}
}
}
fn tls_write(&mut self) -> io::Result<usize> {
self.tls_session.write_tls(&mut self.socket)
}
fn do_tls_write_and_handle_error(&mut self) {
let rc = self.tls_write();
if rc.is_err() {
log::warn!("write failed {:?}", rc);
self.closing = true;
}
}
fn register(&mut self, registry: &mio::Registry) {
let event_set = self.event_set();
registry
.register(&mut self.socket, self.token, event_set)
.unwrap();
if self.back.is_some() {
registry
.register(
self.back.as_mut().unwrap(),
self.token,
mio::Interest::READABLE,
)
.unwrap();
}
}
fn reregister(&mut self, registry: &mio::Registry) {
let event_set = self.event_set();
registry
.reregister(&mut self.socket, self.token, event_set)
.unwrap();
}
fn deregister(&mut self, registry: &mio::Registry) {
registry.deregister(&mut self.socket).unwrap();
if self.back.is_some() {
registry.deregister(self.back.as_mut().unwrap()).unwrap();
}
}
fn event_set(&self) -> mio::Interest {
let rd = self.tls_session.wants_read();
let wr = self.tls_session.wants_write();
if rd && wr {
mio::Interest::READABLE | mio::Interest::WRITABLE
} else if wr {
mio::Interest::WRITABLE
} else {
mio::Interest::READABLE
}
}
fn is_closed(&self) -> bool {
self.closed
}
}
pub fn load_certs(filename: &PathBuf) -> Vec<rustls::Certificate> {
let certfile = fs::File::open(filename).expect("cannot open certificate file");
let mut reader = BufReader::new(certfile);
rustls_pemfile::certs(&mut reader)
.unwrap()
.iter()
.map(|v| rustls::Certificate(v.clone()))
.collect()
}
pub fn load_private_key(filename: &PathBuf) -> rustls::PrivateKey {
let keyfile = fs::File::open(filename).expect("cannot open private key file");
let mut reader = BufReader::new(keyfile);
loop {
match rustls_pemfile::read_one(&mut reader).expect("cannot parse private key .pem file") {
Some(rustls_pemfile::Item::RSAKey(key)) => return rustls::PrivateKey(key),
Some(rustls_pemfile::Item::PKCS8Key(key)) => return rustls::PrivateKey(key),
Some(rustls_pemfile::Item::ECKey(key)) => return rustls::PrivateKey(key),
None => break,
_ => {}
}
}
panic!(
"no keys found in {:?} (encrypted keys not supported)",
filename
);
}
#[allow(dead_code)]
pub fn run(listener: TcpListener) {
let versions = &[&rustls::version::TLS13];
let test_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests");
let certs = load_certs(&test_dir.join("fixtures").join("leaf-server.pem"));
let privkey = load_private_key(&test_dir.join("fixtures").join("leaf-server-key.pem"));
let config = rustls::ServerConfig::builder()
.with_cipher_suites(rustls::ALL_CIPHER_SUITES)
.with_kx_groups(&rustls::ALL_KX_GROUPS)
.with_protocol_versions(versions)
.unwrap()
.with_no_client_auth()
.with_single_cert(certs, privkey)
.unwrap();
run_with_config(listener, config)
}
pub fn run_with_config(mut listener: TcpListener, config: rustls::ServerConfig) {
let mut poll = mio::Poll::new().unwrap();
poll.registry()
.register(&mut listener, LISTENER, mio::Interest::READABLE)
.unwrap();
let mut tlsserv = EchoServer::new(listener, ServerMode::Echo, Arc::new(config));
let mut events = mio::Events::with_capacity(256);
loop {
if let Err(e) = poll.poll(&mut events, None) {
if e.kind() == std::io::ErrorKind::Interrupted {
log::debug!("I/O error {:?}", e);
continue;
}
panic!("I/O error {:?}", e);
}
for event in events.iter() {
match event.token() {
LISTENER => {
tlsserv
.accept(poll.registry())
.expect("error accepting socket");
}
_ => tlsserv.conn_event(poll.registry(), event),
}
}
}
}

25
tests/fixtures/chain.pem vendored Normal file
View File

@@ -0,0 +1,25 @@
-----BEGIN CERTIFICATE-----
MIIBvjCCAWSgAwIBAgIUXXNrQ1jydxm9uhK7n0OmDUOPiCUwCgYIKoZIzj0EAwIw
WzELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVN0YXRlMQ0wCwYDVQQHDARDaXR5MRAw
DgYDVQQKDAdUZXN0T3JnMRswGQYDVQQDDBJUZXN0SW50ZXJtZWRpYXRlQ0EwHhcN
MjYwMjIxMDgzODU4WhcNMzYwMjE5MDgzODU4WjAUMRIwEAYDVQQDDAlsb2NhbGhv
c3QwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAStUYteJnlqMQQaVt8Y3GY92A4E
/E4/9tB9Y5w9OniFssXUzubEhFyWRUMzF/0plx3Q1LJpEFi+PHOUOMZIUplHo00w
SzAJBgNVHRMEAjAAMB0GA1UdDgQWBBTmipGtVpxknBpCkG5VwqycrALvuzAfBgNV
HSMEGDAWgBTRmjFBdHm/8OSJJ4GuT+NLNY6LsDAKBggqhkjOPQQDAgNIADBFAiB2
ZslSroEj+F/JaLSbNNMTTiRzIeP8jz6Lw+18bWo0agIhAK6gPc06G4rghYixJrWI
348sgPEgLNyoDXPAIN+EabvM
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIICAjCCAamgAwIBAgIUaNQANP0JbqEIRANchWyHObCtwycwCgYIKoZIzj0EAwIw
UzELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVN0YXRlMQ0wCwYDVQQHDARDaXR5MRAw
DgYDVQQKDAdUZXN0T3JnMRMwEQYDVQQDDApUZXN0Um9vdENBMB4XDTI2MDIyMTA4
Mzg1N1oXDTM2MDIxOTA4Mzg1N1owWzELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVN0
YXRlMQ0wCwYDVQQHDARDaXR5MRAwDgYDVQQKDAdUZXN0T3JnMRswGQYDVQQDDBJU
ZXN0SW50ZXJtZWRpYXRlQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASrQQZ/
9Om99T1hcWWBLRceIiPBy5y8AwzeG/30E+CipqpXcGfJJj6b9riPDueOnTbMhRH8
BmNgJAZBXvKEQd4Po1MwUTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTRmjFB
dHm/8OSJJ4GuT+NLNY6LsDAfBgNVHSMEGDAWgBRkhR8ezN8mKycuLoqDnfSBhZid
mzAKBggqhkjOPQQDAgNHADBEAiB5ISXZkzQlbgZZ7q8vpWTb9Cfxx3rpKBg6IfIg
NKm5lgIgJhsTXYO4tWz3UfWDXub2NtXoDGXwMvTsE4UXDRDO15E=
-----END CERTIFICATE-----

View File

@@ -0,0 +1,5 @@
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIC94ig/ME0X0FOyu8Byk4tDkAdd/LmuUk3KjalEk/7ImoAoGCCqGSM49
AwEHoUQDQgAEq0EGf/TpvfU9YXFlgS0XHiIjwcucvAMM3hv99BPgoqaqV3BnySY+
m/a4jw7njp02zIUR/AZjYCQGQV7yhEHeDw==
-----END EC PRIVATE KEY-----

13
tests/fixtures/intermediate-ca.pem vendored Normal file
View File

@@ -0,0 +1,13 @@
-----BEGIN CERTIFICATE-----
MIICAjCCAamgAwIBAgIUaNQANP0JbqEIRANchWyHObCtwycwCgYIKoZIzj0EAwIw
UzELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVN0YXRlMQ0wCwYDVQQHDARDaXR5MRAw
DgYDVQQKDAdUZXN0T3JnMRMwEQYDVQQDDApUZXN0Um9vdENBMB4XDTI2MDIyMTA4
Mzg1N1oXDTM2MDIxOTA4Mzg1N1owWzELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVN0
YXRlMQ0wCwYDVQQHDARDaXR5MRAwDgYDVQQKDAdUZXN0T3JnMRswGQYDVQQDDBJU
ZXN0SW50ZXJtZWRpYXRlQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASrQQZ/
9Om99T1hcWWBLRceIiPBy5y8AwzeG/30E+CipqpXcGfJJj6b9riPDueOnTbMhRH8
BmNgJAZBXvKEQd4Po1MwUTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTRmjFB
dHm/8OSJJ4GuT+NLNY6LsDAfBgNVHSMEGDAWgBRkhR8ezN8mKycuLoqDnfSBhZid
mzAKBggqhkjOPQQDAgNHADBEAiB5ISXZkzQlbgZZ7q8vpWTb9Cfxx3rpKBg6IfIg
NKm5lgIgJhsTXYO4tWz3UfWDXub2NtXoDGXwMvTsE4UXDRDO15E=
-----END CERTIFICATE-----

1
tests/fixtures/intermediate-ca.srl vendored Normal file
View File

@@ -0,0 +1 @@
5D736B4358F27719BDBA12BB9F43A60D438F8825

View File

@@ -0,0 +1,5 @@
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEINVUisAFH2zQI1zMfTTQ5Imx89obUgjLnhR1LNKR7xCGoAoGCCqGSM49
AwEHoUQDQgAErVGLXiZ5ajEEGlbfGNxmPdgOBPxOP/bQfWOcPTp4hbLF1M7mxIRc
lkVDMxf9KZcd0NSyaRBYvjxzlDjGSFKZRw==
-----END EC PRIVATE KEY-----

12
tests/fixtures/intermediate-server.pem vendored Normal file
View File

@@ -0,0 +1,12 @@
-----BEGIN CERTIFICATE-----
MIIBvjCCAWSgAwIBAgIUXXNrQ1jydxm9uhK7n0OmDUOPiCUwCgYIKoZIzj0EAwIw
WzELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVN0YXRlMQ0wCwYDVQQHDARDaXR5MRAw
DgYDVQQKDAdUZXN0T3JnMRswGQYDVQQDDBJUZXN0SW50ZXJtZWRpYXRlQ0EwHhcN
MjYwMjIxMDgzODU4WhcNMzYwMjE5MDgzODU4WjAUMRIwEAYDVQQDDAlsb2NhbGhv
c3QwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAStUYteJnlqMQQaVt8Y3GY92A4E
/E4/9tB9Y5w9OniFssXUzubEhFyWRUMzF/0plx3Q1LJpEFi+PHOUOMZIUplHo00w
SzAJBgNVHRMEAjAAMB0GA1UdDgQWBBTmipGtVpxknBpCkG5VwqycrALvuzAfBgNV
HSMEGDAWgBTRmjFBdHm/8OSJJ4GuT+NLNY6LsDAKBggqhkjOPQQDAgNIADBFAiB2
ZslSroEj+F/JaLSbNNMTTiRzIeP8jz6Lw+18bWo0agIhAK6gPc06G4rghYixJrWI
348sgPEgLNyoDXPAIN+EabvM
-----END CERTIFICATE-----

5
tests/fixtures/leaf-client-key.pem vendored Normal file
View File

@@ -0,0 +1,5 @@
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEICyVJZJWFmk9fLUocyUgG1Vy3DN0tEK2C2akRjxu/9uUoAoGCCqGSM49
AwEHoUQDQgAESmy9Dc5o67THYEEMOhf55AtrPfE/b9oECoHxsE08kAYiEhDNHF3b
fHAsG/8o8K0+D/nZBiHSVz7qOJEAYtI38g==
-----END EC PRIVATE KEY-----

13
tests/fixtures/leaf-client.pem vendored Normal file
View File

@@ -0,0 +1,13 @@
-----BEGIN CERTIFICATE-----
MIIB9DCCAZugAwIBAgIUaNQANP0JbqEIRANchWyHObCtwykwCgYIKoZIzj0EAwIw
UzELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVN0YXRlMQ0wCwYDVQQHDARDaXR5MRAw
DgYDVQQKDAdUZXN0T3JnMRMwEQYDVQQDDApUZXN0Um9vdENBMB4XDTI2MDIyMTA4
Mzg1OFoXDTM2MDIxOTA4Mzg1OFowUzELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVN0
YXRlMQ0wCwYDVQQHDARDaXR5MRAwDgYDVQQKDAdUZXN0T3JnMRMwEQYDVQQDDApU
ZXN0Q2xpZW50MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAESmy9Dc5o67THYEEM
Ohf55AtrPfE/b9oECoHxsE08kAYiEhDNHF3bfHAsG/8o8K0+D/nZBiHSVz7qOJEA
YtI38qNNMEswCQYDVR0TBAIwADAdBgNVHQ4EFgQUmnmdVS/JiP0+xsswPzbU0Q5T
qxYwHwYDVR0jBBgwFoAUZIUfHszfJisnLi6Kg530gYWYnZswCgYIKoZIzj0EAwID
RwAwRAIgfuTkH23Somj9TonLSQScjTlX3wQOmnmK0tnopK81SdQCIHwFLvOtatXW
oYwcJFlV9VnsdM1Kl+XqxWotTKNGgGHQ
-----END CERTIFICATE-----

5
tests/fixtures/leaf-server-key.pem vendored Normal file
View File

@@ -0,0 +1,5 @@
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEINmGVq3b5UlwxwkUApMi+qlCUXptr279ZWzkPuY06h8koAoGCCqGSM49
AwEHoUQDQgAEzZQXKPiuG7YT3j8zFOz4SNJqoKWw8gKivlVqVpBNLcOvcfkrzFvd
vvT4FiaTUWxjNsiXDT1mj35Gbeip1HLabw==
-----END EC PRIVATE KEY-----

12
tests/fixtures/leaf-server.pem vendored Normal file
View File

@@ -0,0 +1,12 @@
-----BEGIN CERTIFICATE-----
MIIBtjCCAVygAwIBAgIUaNQANP0JbqEIRANchWyHObCtwygwCgYIKoZIzj0EAwIw
UzELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVN0YXRlMQ0wCwYDVQQHDARDaXR5MRAw
DgYDVQQKDAdUZXN0T3JnMRMwEQYDVQQDDApUZXN0Um9vdENBMB4XDTI2MDIyMTA4
Mzg1OFoXDTM2MDIxOTA4Mzg1OFowFDESMBAGA1UEAwwJbG9jYWxob3N0MFkwEwYH
KoZIzj0CAQYIKoZIzj0DAQcDQgAEzZQXKPiuG7YT3j8zFOz4SNJqoKWw8gKivlVq
VpBNLcOvcfkrzFvdvvT4FiaTUWxjNsiXDT1mj35Gbeip1HLab6NNMEswCQYDVR0T
BAIwADAdBgNVHQ4EFgQUSvq+CjEju6CTX5ySu6YiKDTuCVswHwYDVR0jBBgwFoAU
ZIUfHszfJisnLi6Kg530gYWYnZswCgYIKoZIzj0EAwIDSAAwRQIgWzKWT9G3NwTM
wi0DJ9S+34oyL2WU7h+nPgJ0/ZFPR80CIQDx9pppEpipI83lB5A8wAB6Vi/Kugf9
ZL3JtaYEl1gJzQ==
-----END CERTIFICATE-----

5
tests/fixtures/root-ca-key.pem vendored Normal file
View File

@@ -0,0 +1,5 @@
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEILDaSJAPexCKKVxUwA2Obha08yO3FVDlzXwRPk+HDm7EoAoGCCqGSM49
AwEHoUQDQgAEddxFuwZN+JLJFTiSKPb9DJPOdbLFMPzz66JkcxB28da2r2DqQKTK
EmramKjIsI9WuXGY06XF1tYDxSfe7lTZZQ==
-----END EC PRIVATE KEY-----

13
tests/fixtures/root-ca.pem vendored Normal file
View File

@@ -0,0 +1,13 @@
-----BEGIN CERTIFICATE-----
MIIB+zCCAaGgAwIBAgIUfXCGhET97fQO0Q5r972wMTrwA3gwCgYIKoZIzj0EAwIw
UzELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVN0YXRlMQ0wCwYDVQQHDARDaXR5MRAw
DgYDVQQKDAdUZXN0T3JnMRMwEQYDVQQDDApUZXN0Um9vdENBMB4XDTI2MDIyMTA4
Mzg1N1oXDTM2MDIxOTA4Mzg1N1owUzELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVN0
YXRlMQ0wCwYDVQQHDARDaXR5MRAwDgYDVQQKDAdUZXN0T3JnMRMwEQYDVQQDDApU
ZXN0Um9vdENBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEddxFuwZN+JLJFTiS
KPb9DJPOdbLFMPzz66JkcxB28da2r2DqQKTKEmramKjIsI9WuXGY06XF1tYDxSfe
7lTZZaNTMFEwHQYDVR0OBBYEFGSFHx7M3yYrJy4uioOd9IGFmJ2bMB8GA1UdIwQY
MBaAFGSFHx7M3yYrJy4uioOd9IGFmJ2bMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZI
zj0EAwIDSAAwRQIhAJrkXZrhS2zAexShCh/Dh47d1LtOvMiq9uqbgP7xI10pAiAF
fMNptczj+DpN9nRTlELt8FxC8rTxGxjC/tL0aOomDw==
-----END CERTIFICATE-----

1
tests/fixtures/root-ca.srl vendored Normal file
View File

@@ -0,0 +1 @@
68D40034FD096EA10844035C856C8739B0ADC329

28
tests/fixtures/rsa-leaf-client-key.pem vendored Normal file
View File

@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCPYjGfNwKAhIsD
Fepzgc57jcHso5dO/hfs5k4JTRhACoGDA+6QFm9KAb8YC1NFbcJsExJAfvVlSr3S
82WJqvQOH5LQ7TXPRaG0tAIrWoB76jYb+TQhmi6o0WepOxqDR6w64O3bM6geFR7A
zbhl2bzOYlZwF+tdCn3L41LpjhzcEVUM8M/IUaCbzexq/jUsRUpFdpYSvEFCKMSR
BvbV/I0/zMWo+jZXG/YQqbOhNmgQugypPlVIYQLtAVye+92qpajLFlg5RCvXmAtE
0KGl6nhHPNutqOUJGgmPVmx0fAj1IIjTRws++kIuRYgncxS9qtcblFVy4Ji0NdKu
+Z6LeyLZAgMBAAECggEAA8wP8mtK9MUSFG+sLTxxfretka09IaqR128Q/2Ko+5Qe
2MdYk+eKD0joONvA/RaKd0FdX3muUHWzSbBuf7dT57mT/wDmgonef/3jL56qPjMt
rsqyu1ytObhrSR+oeOudSg/huXp3bmVQluC70JGewwkZDNiPc0Qo5OJo1mY6wxex
dps0A4fCmZtB9ZgmaejCiQaw7udgcpOGo5xdfVrgAtAqCBSaWOoZWNHzjsntSrHO
fRDa4ilLhwuWwrkIsg5om/zasfTDLM70YyXq4s0ccYyCEPkquPgN/mII/lK+78U6
AXYfsvSfyVNRoMzMXdTKC+G1QlYDGWeqAoVCS9TyawKBgQDC1ihsohh6PSjYHS7Y
+QODmDXEbwXU/V5iEnFKf4/9+QSW+g12mbckjHS+njvNV4wzdx/ph+bNDAdI8bQU
Dr5bJXtGbHy1r8/PtNUP4SJmoRvmMVrr34g7QjaHYJ8Jy2m9JHpuRnr38pzg6vP6
deDtxupT2xPTqV7ivwUDLK9SKwKBgQC8ZRGp2Ny17cH9aKnyjibXLKvtITdDc7H9
kqs+LM3bcEXpXs0o5ED7J0uhpbNkpplV95Lcm7zdDAkHHiek4yWBUohtgqfbZ76d
vA+BtnbjDdCyH8B5mN5IDc1zEIJZB1x9CpIhIsDRrBCH/NXl3ldJLBF/ZCJTXWCf
nqRgwllRCwKBgQC4k7W8JFvYAfSduBfXiSAhHKNjMmJuApHViu80ymAZFD2a4cy7
XKg5wa4fnzu8LoIth17+F7c47XpBSml0zvra0klU0BXc8W+HsCJgZsH2RA5wJrWh
2yPuL64E1i4UU1Yaz2IE8lQwbPDdyvfTgLTTzavUQSkpTb0MRjZzaXO1/QKBgG8X
0mCr5wrJF1nNfFnyBWlhiEifC62U7eKvuJdDaGj8Pd2t76EraD4yH+FEixLRQx50
jX/VvntC+5fc6lfLMnSeLKEXKNCyzq7JFQPSiyy9GtHO83tA7+LhcMNnetXxB1Md
BqrPiZCavGzUZXXVtPcLK45JiAxMxguaSyhbsrudAoGAMjK2bcu7buE13KM3wbte
lysax9gxKKNlqFX68/qx4SUrHxhz5ugF95zlkiDciq/jG5TH1IGNX3q0C7Eid4FY
0XHwJAqBG5wFWnjx3U+Ozk2QYVXK6rfyk7drAWO2XlsAZVHwf6DlVhYCOT+a9O6W
60KKqnpWzmBnrc2fzOJgJTM=
-----END PRIVATE KEY-----

21
tests/fixtures/rsa-leaf-client.pem vendored Normal file
View File

@@ -0,0 +1,21 @@
-----BEGIN CERTIFICATE-----
MIIDhDCCAmygAwIBAgIUPL2aFs+m1HMJy9P1zeCRHsgB/dUwDQYJKoZIhvcNAQEL
BQAwVjELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVN0YXRlMQ0wCwYDVQQHDARDaXR5
MRAwDgYDVQQKDAdUZXN0T3JnMRYwFAYDVQQDDA1UZXN0UnNhUm9vdENBMB4XDTI2
MDIyMTA4MzkwMFoXDTM2MDIxOTA4MzkwMFowUzELMAkGA1UEBhMCVVMxDjAMBgNV
BAgMBVN0YXRlMQ0wCwYDVQQHDARDaXR5MRAwDgYDVQQKDAdUZXN0T3JnMRMwEQYD
VQQDDApUZXN0Q2xpZW50MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
j2IxnzcCgISLAxXqc4HOe43B7KOXTv4X7OZOCU0YQAqBgwPukBZvSgG/GAtTRW3C
bBMSQH71ZUq90vNliar0Dh+S0O01z0WhtLQCK1qAe+o2G/k0IZouqNFnqTsag0es
OuDt2zOoHhUewM24Zdm8zmJWcBfrXQp9y+NS6Y4c3BFVDPDPyFGgm83sav41LEVK
RXaWErxBQijEkQb21fyNP8zFqPo2Vxv2EKmzoTZoELoMqT5VSGEC7QFcnvvdqqWo
yxZYOUQr15gLRNChpep4RzzbrajlCRoJj1ZsdHwI9SCI00cLPvpCLkWIJ3MUvarX
G5RVcuCYtDXSrvmei3si2QIDAQABo00wSzAJBgNVHRMEAjAAMB0GA1UdDgQWBBQV
mkgyHQUCPp0zo8903lXsOeKE5DAfBgNVHSMEGDAWgBRHYiOHw458Ka7uZ2w7wkbk
WsD5MTANBgkqhkiG9w0BAQsFAAOCAQEAhrtvIHbrRPFc5QbaT/eelrAqWkhwpxxL
X/gVwbqtP2+uU2xUVGsfRJV5EI27kzyysq+ySdQLq3j8oe0X6poqDJiE8/zUg7KO
OYCkH/UGhr+L0hk6Ibvc7izu/LSFT3K5Mo78aB/C4cjDrLgA/cbFKT+OVQW4iw49
2Pgw7+vuNLzYJSu2m5XzeR07FMcTpf2EAOGseDde6zFwduUBJtAbGj4YHkTQp+MD
dEE5ymyXl4+ehcafk0g4ZZARN5qVPMOqjRASWRhuZI+36Ihc6KBL22B1KzexyoYx
2sEyd04NU1PX9i5Zn7JXNbHIESNCkLk4IspSnk6DBEpNpQgEW6l3Cw==
-----END CERTIFICATE-----

28
tests/fixtures/rsa-leaf-server-key.pem vendored Normal file
View File

@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCjK6TkX5PuTApT
uL5Z0p6Iofo2zXHOPJVcDDeKzVW83KRwK9oFhjIT9HHKRFkar5ezKQuKY+xtsvmz
JX2STGwg1itPIcHpngwvmtAy4/2lBB0wL1wxc+2sqpWYGmqLm5iqiuaVQo2n0uiL
Nwd4aZzpjtRXWjyr9sjfBcXu6/m3bN6nYkmh613qu9BAo4L1YPy+NFtVhKkBzD9Q
qPDZv7mVoqe+Y3Cyf8873+5T6PVrpV7+YCpFJA46fsA/UrmsyQfQPCOL5fbbLXqs
V9yMf2/7JCCbrO+6mpIJaEWnciS51sEIh15oqH7IiF5+6az/Oe11X/LbWHkLRUzr
k4uYGV61AgMBAAECggEABZz2Pq71CVEPV+L2lWNz9bJQx7rYi+Y0oyZ+cKVwqh8S
/xLbHK6JoXsawQEJ6auZtd2XGosmcn2iLmH/SF2dqKGFeuLn50/7DlYujFmge6FB
Gcu/Sao5xmNV4xYhjSzsmw1NMlxIQDo2qrdZZ/CGJ9i0gE7H4IiMT3PE49u1SvSD
OkKmcZW3suPYyZGPZxl2h37cARIjqsmw9D3OmDxUA0hfZg6sGoABZeNrGi7IScZe
D26DAhPhIWa0sDVUTfCZkezXtH+mFptwN0zSIbMRWozx49jEZF9hBF4QBmnNnUPm
VTs+Z2AzeRQpUZPQ8rzWAbcf1oCwhPqSknXlcsxJnwKBgQDartbfUMkJ2LgBLTkl
BSPxI2ngvb0ySzdzxfsPKZZe4EAz4KFl+1DqQ4EZifhdhJiqV+PicNPKNFo2AK6S
3Wd0X4GAHZDAamiJces1WxqHigHXOrdVqu8/YSpVPEk/jdSYgHqwG7ji0IKEVtpw
EZJ+LX6BDOvDpbedBGcZT7Mm/wKBgQC/A75DSRxHK48AeuCiG4Q6G72IvOTYqK/2
hz45HgfUI22d41Bb/48ybH8xt/SDoJLibx8P88ej/81vvYg3eLIyH+QR0f18Ia2g
sZd4Lj+2zo1fJbmQKptTI+O8PqrZzx9UZ2UfGXxQ0sNSuF37hFw8hI8ALzk1+UW/
OoX90IUOSwKBgA0FJ+n352BctOftB1/65F7xGta0tVUPQWf1O7N1aGyRsYDlOPbX
dcPc7QzWOCFpSaWqwfizewipAU4B0GMSJ5y4Kv+zwvCR5VN5ouV0XSoAv4dPCadi
HAiMAnc8tafBDA1gaO2fWOy4OW0jtrHBehVlJAkO+eKWNU51+qV5J1OFAoGANS+N
op6QySBPyQpt0bVns+ZVd+VgsxMFK9esc6rw8xiKRRQuI++cp6WeJPHbm2ryeyoF
tCNkyz1Grn5Pl2J7+4j1sCCQPCgEeGH6kvQNuZD5vCx85q92YEf1+UxZthv91TqU
5XvrKXYF/NppEMdiB1fBmYOMooKt8PkSpgGRitECgYAkF8zcg/zNCueYezn6iW7q
PeO30XvLRnDbh4DnhkwoLbDR3vaxmmbphCzLe3tX8TDAA13QeWON/8JL7QU35HcY
lBlVJo2Mr0crROKElPicXXXSZWDxXFjcdVj+Oa4ni5wwKsppE18HNo9Vlj+RNgjw
dOfKqK1UP8tgaDa1W9ERDQ==
-----END PRIVATE KEY-----

20
tests/fixtures/rsa-leaf-server.pem vendored Normal file
View File

@@ -0,0 +1,20 @@
-----BEGIN CERTIFICATE-----
MIIDRTCCAi2gAwIBAgIUPL2aFs+m1HMJy9P1zeCRHsgB/dQwDQYJKoZIhvcNAQEL
BQAwVjELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVN0YXRlMQ0wCwYDVQQHDARDaXR5
MRAwDgYDVQQKDAdUZXN0T3JnMRYwFAYDVQQDDA1UZXN0UnNhUm9vdENBMB4XDTI2
MDIyMTA4Mzg1OVoXDTM2MDIxOTA4Mzg1OVowFDESMBAGA1UEAwwJbG9jYWxob3N0
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoyuk5F+T7kwKU7i+WdKe
iKH6Ns1xzjyVXAw3is1VvNykcCvaBYYyE/RxykRZGq+XsykLimPsbbL5syV9kkxs
INYrTyHB6Z4ML5rQMuP9pQQdMC9cMXPtrKqVmBpqi5uYqormlUKNp9LoizcHeGmc
6Y7UV1o8q/bI3wXF7uv5t2zep2JJoetd6rvQQKOC9WD8vjRbVYSpAcw/UKjw2b+5
laKnvmNwsn/PO9/uU+j1a6Ve/mAqRSQOOn7AP1K5rMkH0Dwji+X22y16rFfcjH9v
+yQgm6zvupqSCWhFp3IkudbBCIdeaKh+yIhefums/zntdV/y21h5C0VM65OLmBle
tQIDAQABo00wSzAJBgNVHRMEAjAAMB0GA1UdDgQWBBRsFCi7ZWnzarIhJkQzwWQm
7r27WTAfBgNVHSMEGDAWgBRHYiOHw458Ka7uZ2w7wkbkWsD5MTANBgkqhkiG9w0B
AQsFAAOCAQEAOuP/m6+3/XlGmospEkD3K8I3/zTNqYUQAeFaPcivdAO5WgqpBN7G
JCPVEY12XGHd8KNYe6h7J0w2weCAUONiaU/2bsXCcrOKNA2c4no1DY+NJR7YKJmi
QKHNXpw3qcDlqFzkz8/4GiIEf+NA+dtS6476iTr1d1OnJwYm9yjgVeWod/fp0kU/
ECF52TIbB3qJKKYiZwhdEQm1ddjfMJAaM+kRkCf+53UZG5R/Z9ApX1DHALbIPlNe
49s7ulKuNJs1ZfhjcgruJn4b6XTgsTZ/1KJv+Wgd31G6at9I/gRmRvGBOwUjAkyH
O3ZKdDXZfHvhfoHiiO9Bjs5Hxt0F7xJ88g==
-----END CERTIFICATE-----

28
tests/fixtures/rsa-root-ca-key.pem vendored Normal file
View File

@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEugIBADANBgkqhkiG9w0BAQEFAASCBKQwggSgAgEAAoIBAQCZKwbc7+1RFLyM
7sWOdHLHfGwyGQnD5zkYyGnMrHuewFgua6D52zoI3XmTEL4tdKgHLFIjOI3z/kQ3
78BX727vsGj+/uMUo64cnC6xJIOQKPUaNQPvHQfiUjwxqP3rV0Bf+bVj5D14rFFw
ghWnp4KJ13dw50qjV2hPcGz2rf7HJtv/rNiTAoiVt5nc6NhMKFzJXPOMIzYAgTDL
ISRtjw6LR2LOR+guSMvpEPe+Kgle44OSHjz+SLABNE9oXIdqzrmE1AStmMoNl9n9
FLQ3ci2yaEGMshYBB/AU3cwAXOgQvObDusmPp+hbcsGglauem/7WTZSWUXhdJG4M
mTzTf0GJAgMBAAECgf85I1PsF5TwKkwsRuZrvgUTZdb22WBLNHaYSCsvryhukFJU
/tGOY7nClNxFgHlxe5MzGdWKTg6mdrP8KfQW2bsIr0Z72ZncmTLaeWjxrC1oGd9V
Z3GQQcQvKX5LCD+xC1t4ci64lOxZl+7Jib2KTXLk+PwVojK1vGWtPMNpQn9IyyIz
b13zG/4cN7tHECjvNa3Xp0mJ9p7y34LiAl7YD+FGh8DHt+LG5Tcbr8fV3fzafNZK
S1cX8zBJlqPcmMuPxWwTEinW8Lo4A1P1E+8adxzDmIoD4VR3CR5zruzwGbAQv8GV
EGAQzbRSCNXprRi0vmDs2sJm465U5bPGNCimt7cCgYEA09KbmFD/GcUbTC9v/uvC
7xnbyMljwDFooxwYPLqe5FdCiBbLziY4U9D21BSvxZxDi11Ltbem1/kfj1Fpb5Xq
qP7bMqcCO/pwzXvb7px3QFBvp+jPDjKr3I3/c+rXu20Ua2sQl+8AyTqm1QpmP1w2
EvXkXFaH7aJ7gf8hGcrX8IcCgYEAuRzMtoPAkmou0mGf4sPt1hcoW9YuRyI2Jcsg
WgTg0i2O1IX4TlEHG5pgIkM3uR3VEkSN0qJWB6V+1FP4CKnSrxgVq2V4422mQNtG
d2Gd3gZFhpQE9B3McAq/2G3pXHI8ZQNd6Oc1z7ecmEnBff/PlE2xGhMoe2Srgwxh
h/IzEW8CgYBg57LjJfrusSvh2Lnl57nQZQYVf3yxCmmSZWH5Nm9Gi10WoUcv0nBm
d+zT7XrUbr6/3Tirs48SsxfrGxWfRPiLw7xIGft9sP82InnlWZN8ys+qA2nmVuwl
BJlfUIrNZgO3eM2olGDJrplwUUehqO/cEL4eOEALSRAz0qI0CIZttQKBgFRV3KZi
jD+ohMBwnclQfnEFh+ufPuJFoenCC3E3u73F58bHaoMzw0s+IAI8IY0DHGoANaT7
NLqzGX9e6if4RvZiwKyfxF3JPO9bd1U4chYPQWm40jDtypBZNWJDYQgvO3jB+ez8
ObXy7zMqly7ydv4YD1HT3KOrD8DaySyImd+dAoGAI8QU3UEdge0cY+UtD1vHLUb/
Z6rfcnoDL17azt33CooNH3lCVw+IqgE0t63lO6W/3DCi8A3D9PJsopnAdPK7rvdT
UrvmtretbgxNRGCBC8T2fVJV6Q9DGqmjMol2/rlNiYQNhgK8bEiAmPYw7GXH0LjO
PEayI0HRcYAWrpEwwvs=
-----END PRIVATE KEY-----

22
tests/fixtures/rsa-root-ca.pem vendored Normal file
View File

@@ -0,0 +1,22 @@
-----BEGIN CERTIFICATE-----
MIIDjTCCAnWgAwIBAgIUWjGv/hbVIjFa4t9qWs3sd5RCzDQwDQYJKoZIhvcNAQEL
BQAwVjELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVN0YXRlMQ0wCwYDVQQHDARDaXR5
MRAwDgYDVQQKDAdUZXN0T3JnMRYwFAYDVQQDDA1UZXN0UnNhUm9vdENBMB4XDTI2
MDIyMTA4Mzg1OVoXDTM2MDIxOTA4Mzg1OVowVjELMAkGA1UEBhMCVVMxDjAMBgNV
BAgMBVN0YXRlMQ0wCwYDVQQHDARDaXR5MRAwDgYDVQQKDAdUZXN0T3JnMRYwFAYD
VQQDDA1UZXN0UnNhUm9vdENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
AQEAmSsG3O/tURS8jO7FjnRyx3xsMhkJw+c5GMhpzKx7nsBYLmug+ds6CN15kxC+
LXSoByxSIziN8/5EN+/AV+9u77Bo/v7jFKOuHJwusSSDkCj1GjUD7x0H4lI8Maj9
61dAX/m1Y+Q9eKxRcIIVp6eCidd3cOdKo1doT3Bs9q3+xybb/6zYkwKIlbeZ3OjY
TChcyVzzjCM2AIEwyyEkbY8Oi0dizkfoLkjL6RD3vioJXuODkh48/kiwATRPaFyH
as65hNQErZjKDZfZ/RS0N3ItsmhBjLIWAQfwFN3MAFzoELzmw7rJj6foW3LBoJWr
npv+1k2UllF4XSRuDJk8039BiQIDAQABo1MwUTAfBgNVHSMEGDAWgBRHYiOHw458
Ka7uZ2w7wkbkWsD5MTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRHYiOHw458
Ka7uZ2w7wkbkWsD5MTANBgkqhkiG9w0BAQsFAAOCAQEAiHXmwDY+9t5cnFZcuxJl
3lQSOpHQMmSYe7FiTpI+GglGW+ugahdyqc/c2GbNKqB1oxGA+mEv+KeKYgtTfC8J
gTtBLVjhquUOY4K6AkU69i5e7g8/41TFuAF0nt8LnkAYZmo12kbYjX1gKTfkl9wl
hMY+OSnoIoxBrw1cmqRtOu+Hn2wY9dVCQJVIgc89WuQr8USn9JXzq1bUCzOk6a8/
dR6ZEPDVPo6RjEXuLNUuTOoTH1wCG3P1tHUs4Cwapr1EhgrEs7DC4uvB+9Y6EXqI
OLqn0V3FBsBPN19bdLWTtaOm5C0xbNmiXfJPBH9nF38Hpj4tk72GfxOzKI4J+rG7
Kg==
-----END CERTIFICATE-----

1
tests/fixtures/rsa-root-ca.srl vendored Normal file
View File

@@ -0,0 +1 @@
3CBD9A16CFA6D47309CBD3F5CDE0911EC801FDD5

70
tests/fixtures/setup_fixtures.sh vendored Executable file
View File

@@ -0,0 +1,70 @@
#!/usr/bin/env bash
set -e
SUBJ_CA="/C=US/ST=State/L=City/O=TestOrg/CN=TestRootCA"
SUBJ_IM="/C=US/ST=State/L=City/O=TestOrg/CN=TestIntermediateCA"
SUBJ_SRV="/CN=localhost"
SUBJ_CLI="/C=US/ST=State/L=City/O=TestOrg/CN=TestClient"
SUBJ_RSA_CA="/C=US/ST=State/L=City/O=TestOrg/CN=TestRsaRootCA"
EXT_CA="basicConstraints=critical,CA:TRUE\nsubjectKeyIdentifier=hash\nauthorityKeyIdentifier=keyid:always"
EXT_LEAF="basicConstraints=CA:FALSE\nsubjectKeyIdentifier=hash\nauthorityKeyIdentifier=keyid,issuer"
# Root CA
openssl ecparam -name prime256v1 -genkey -noout -out root-ca-key.pem
openssl req -new -x509 -sha256 -key root-ca-key.pem -days 3650 -out root-ca.pem -subj "$SUBJ_CA"
# Intermediate CA
openssl ecparam -name prime256v1 -genkey -noout -out intermediate-ca-key.pem
openssl req -new -sha256 -key intermediate-ca-key.pem -out _im.csr -subj "$SUBJ_IM"
openssl x509 -req -in _im.csr -CA root-ca.pem -CAkey root-ca-key.pem \
-CAcreateserial -out intermediate-ca.pem -days 3650 -sha256 \
-extfile <(printf "$EXT_CA")
rm _im.csr
# Server leaf cert (signed by root CA)
openssl ecparam -name prime256v1 -genkey -noout -out leaf-server-key.pem
openssl req -new -sha256 -key leaf-server-key.pem -out _srv.csr -subj "$SUBJ_SRV"
openssl x509 -req -in _srv.csr -CA root-ca.pem -CAkey root-ca-key.pem \
-CAcreateserial -out leaf-server.pem -days 3650 -sha256 \
-extfile <(printf "$EXT_LEAF")
rm _srv.csr
# Client leaf cert (signed by root CA)
openssl ecparam -name prime256v1 -genkey -noout -out leaf-client-key.pem
openssl req -new -sha256 -key leaf-client-key.pem -out _cli.csr -subj "$SUBJ_CLI"
openssl x509 -req -in _cli.csr -CA root-ca.pem -CAkey root-ca-key.pem \
-CAcreateserial -out leaf-client.pem -days 3650 -sha256 \
-extfile <(printf "$EXT_LEAF")
rm _cli.csr
# Intermediate server cert + chain
openssl ecparam -name prime256v1 -genkey -noout -out intermediate-server-key.pem
openssl req -new -sha256 -key intermediate-server-key.pem -out _imsrv.csr -subj "$SUBJ_SRV"
openssl x509 -req -in _imsrv.csr -CA intermediate-ca.pem -CAkey intermediate-ca-key.pem \
-CAcreateserial -out intermediate-server.pem -days 3650 -sha256 \
-extfile <(printf "$EXT_LEAF")
rm _imsrv.csr
cat intermediate-server.pem intermediate-ca.pem > chain.pem
# RSA root CA
openssl req -x509 -newkey rsa:2048 -keyout rsa-root-ca-key.pem -nodes \
-out rsa-root-ca.pem -sha256 -days 3650 -subj "$SUBJ_RSA_CA" \
-addext "basicConstraints=critical,CA:TRUE" \
-addext "subjectKeyIdentifier=hash"
# RSA server cert
openssl req -newkey rsa:2048 -keyout rsa-leaf-server-key.pem -nodes \
-out _rsasrv.csr -sha256 -subj "$SUBJ_SRV"
openssl x509 -req -CA rsa-root-ca.pem -CAkey rsa-root-ca-key.pem \
-in _rsasrv.csr -out rsa-leaf-server.pem -days 3650 -sha256 -CAcreateserial \
-extfile <(printf "$EXT_LEAF")
rm _rsasrv.csr
# RSA client cert
openssl req -newkey rsa:2048 -keyout rsa-leaf-client-key.pem -nodes \
-out _rsacli.csr -sha256 -subj "$SUBJ_CLI"
openssl x509 -req -CA rsa-root-ca.pem -CAkey rsa-root-ca-key.pem \
-in _rsacli.csr -out rsa-leaf-client.pem -days 3650 -sha256 -CAcreateserial \
-extfile <(printf "$EXT_LEAF")
rm _rsacli.csr

1326
tests/integration.rs Normal file

File diff suppressed because it is too large Load Diff