Initial commit
Some checks failed
CI / build (push) Successful in 27s
CI / no-std (push) Successful in 26s
CI / clippy (push) Successful in 26s
CI / test (push) Failing after 39s

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
2026-02-21 08:32:22 +00:00
commit bd970016e5
80 changed files with 11783 additions and 0 deletions

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

@@ -0,0 +1,42 @@
name: CI
on:
push:
branches: [main]
pull_request:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- run: cargo build
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- run: cargo test
- run: cargo test --features webpki
- run: cargo test --features native-pki
- run: cargo test --features native-pki,rsa
clippy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- run: cargo clippy -- -D warnings
no-std:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
targets: thumbv7em-none-eabi
- run: 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 = "mote-tls"
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

9
README.md Normal file
View File

@@ -0,0 +1,9 @@
# mote-tls
TLS 1.3 client with `no_std` and no allocator support.
Based on commit [426f327](https://github.com/drogue-iot/embedded-tls/commit/426f327) from [drogue-iot/embedded-tls](https://github.com/drogue-iot/embedded-tls).
## 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 HandshakeVerify\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-----
MIIBvjCCAWSgAwIBAgIUXXNrQ1jydxm9uhK7n0OmDUOPiCQwCgYIKoZIzj0EAwIw
WzELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVN0YXRlMQ0wCwYDVQQHDARDaXR5MRAw
DgYDVQQKDAdUZXN0T3JnMRswGQYDVQQDDBJUZXN0SW50ZXJtZWRpYXRlQ0EwHhcN
MjYwMjIxMDgzMDE1WhcNMzYwMjE5MDgzMDE1WjAUMRIwEAYDVQQDDAlsb2NhbGhv
c3QwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQljcc4CF/2x0n5T7EdvNZaBOkE
pM1bdrp7Zc+rUwvZlKbs6vUTQBD7GFoPoTtx7KmcPF4yC+m2Wbps97fjuf2po00w
SzAJBgNVHRMEAjAAMB0GA1UdDgQWBBQWI63NaIN1ycxYkF07VcNN/68BrjAfBgNV
HSMEGDAWgBSZHKZMU0B62yamD8iYJYZijS5saTAKBggqhkjOPQQDAgNIADBFAiBM
deIDUWrrDndB13EBwhqQPNq0WdnbeP5ETTFzllfGewIhAL1kU5S4/ofLnvNCqlh1
gYyZOvfQBq2c3HJQ7LFZz2E4
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIICBDCCAamgAwIBAgIUaNQANP0JbqEIRANchWyHObCtwyQwCgYIKoZIzj0EAwIw
UzELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVN0YXRlMQ0wCwYDVQQHDARDaXR5MRAw
DgYDVQQKDAdUZXN0T3JnMRMwEQYDVQQDDApUZXN0Um9vdENBMB4XDTI2MDIyMTA4
MzAxNVoXDTM2MDIxOTA4MzAxNVowWzELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVN0
YXRlMQ0wCwYDVQQHDARDaXR5MRAwDgYDVQQKDAdUZXN0T3JnMRswGQYDVQQDDBJU
ZXN0SW50ZXJtZWRpYXRlQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARAblvA
Lgu+51BzDdUoT4p8vCgLvd9kCiGsHoa0S2sphCy2DbZguJar3h7WeFLa72EvoYwo
JGMo1CF3VJ/IU2ugo1MwUTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSZHKZM
U0B62yamD8iYJYZijS5saTAfBgNVHSMEGDAWgBTz3CbeFHL4OAweOp0osfWlJvyF
ETAKBggqhkjOPQQDAgNJADBGAiEA/4feZ//so7F5N3HvrjVOfCaYeY2zuOciJhBq
D1AJLc0CIQDfVNyECBECAK6Cd/dZkLMf5M8IggLqfQMLzB25vp2K6Q==
-----END CERTIFICATE-----

View File

@@ -0,0 +1,5 @@
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIEbKGg3eb4qwtVUemTHUWkxAJfNyoCeJ4GvJLMPdvLAmoAoGCCqGSM49
AwEHoUQDQgAEQG5bwC4LvudQcw3VKE+KfLwoC73fZAohrB6GtEtrKYQstg22YLiW
q94e1nhS2u9hL6GMKCRjKNQhd1SfyFNroA==
-----END EC PRIVATE KEY-----

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

@@ -0,0 +1,13 @@
-----BEGIN CERTIFICATE-----
MIICBDCCAamgAwIBAgIUaNQANP0JbqEIRANchWyHObCtwyQwCgYIKoZIzj0EAwIw
UzELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVN0YXRlMQ0wCwYDVQQHDARDaXR5MRAw
DgYDVQQKDAdUZXN0T3JnMRMwEQYDVQQDDApUZXN0Um9vdENBMB4XDTI2MDIyMTA4
MzAxNVoXDTM2MDIxOTA4MzAxNVowWzELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVN0
YXRlMQ0wCwYDVQQHDARDaXR5MRAwDgYDVQQKDAdUZXN0T3JnMRswGQYDVQQDDBJU
ZXN0SW50ZXJtZWRpYXRlQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARAblvA
Lgu+51BzDdUoT4p8vCgLvd9kCiGsHoa0S2sphCy2DbZguJar3h7WeFLa72EvoYwo
JGMo1CF3VJ/IU2ugo1MwUTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSZHKZM
U0B62yamD8iYJYZijS5saTAfBgNVHSMEGDAWgBTz3CbeFHL4OAweOp0osfWlJvyF
ETAKBggqhkjOPQQDAgNJADBGAiEA/4feZ//so7F5N3HvrjVOfCaYeY2zuOciJhBq
D1AJLc0CIQDfVNyECBECAK6Cd/dZkLMf5M8IggLqfQMLzB25vp2K6Q==
-----END CERTIFICATE-----

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

@@ -0,0 +1 @@
5D736B4358F27719BDBA12BB9F43A60D438F8824

View File

@@ -0,0 +1,5 @@
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIB/4vLEx10l/AmZYZVvzZZQRrEWu95QKF0Q8yfjSjn/ZoAoGCCqGSM49
AwEHoUQDQgAEJY3HOAhf9sdJ+U+xHbzWWgTpBKTNW3a6e2XPq1ML2ZSm7Or1E0AQ
+xhaD6E7ceypnDxeMgvptlm6bPe347n9qQ==
-----END EC PRIVATE KEY-----

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

@@ -0,0 +1,12 @@
-----BEGIN CERTIFICATE-----
MIIBvjCCAWSgAwIBAgIUXXNrQ1jydxm9uhK7n0OmDUOPiCQwCgYIKoZIzj0EAwIw
WzELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVN0YXRlMQ0wCwYDVQQHDARDaXR5MRAw
DgYDVQQKDAdUZXN0T3JnMRswGQYDVQQDDBJUZXN0SW50ZXJtZWRpYXRlQ0EwHhcN
MjYwMjIxMDgzMDE1WhcNMzYwMjE5MDgzMDE1WjAUMRIwEAYDVQQDDAlsb2NhbGhv
c3QwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQljcc4CF/2x0n5T7EdvNZaBOkE
pM1bdrp7Zc+rUwvZlKbs6vUTQBD7GFoPoTtx7KmcPF4yC+m2Wbps97fjuf2po00w
SzAJBgNVHRMEAjAAMB0GA1UdDgQWBBQWI63NaIN1ycxYkF07VcNN/68BrjAfBgNV
HSMEGDAWgBSZHKZMU0B62yamD8iYJYZijS5saTAKBggqhkjOPQQDAgNIADBFAiBM
deIDUWrrDndB13EBwhqQPNq0WdnbeP5ETTFzllfGewIhAL1kU5S4/ofLnvNCqlh1
gYyZOvfQBq2c3HJQ7LFZz2E4
-----END CERTIFICATE-----

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

@@ -0,0 +1,5 @@
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIG1y//8RDl0lkBt2PjMzfDzWsBzda/3ZZ32sz6EJiKi3oAoGCCqGSM49
AwEHoUQDQgAEh9c2BC3Y2CoL1ZBg5P+ySkXqlzSFB91uywNWpys/STUWvDV2+OHY
ad2BdDt6PhxjkkRIgVgvqkkdKbB72uvThQ==
-----END EC PRIVATE KEY-----

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

@@ -0,0 +1,13 @@
-----BEGIN CERTIFICATE-----
MIIB9TCCAZugAwIBAgIUaNQANP0JbqEIRANchWyHObCtwyYwCgYIKoZIzj0EAwIw
UzELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVN0YXRlMQ0wCwYDVQQHDARDaXR5MRAw
DgYDVQQKDAdUZXN0T3JnMRMwEQYDVQQDDApUZXN0Um9vdENBMB4XDTI2MDIyMTA4
MzAxNVoXDTM2MDIxOTA4MzAxNVowUzELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVN0
YXRlMQ0wCwYDVQQHDARDaXR5MRAwDgYDVQQKDAdUZXN0T3JnMRMwEQYDVQQDDApU
ZXN0Q2xpZW50MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEh9c2BC3Y2CoL1ZBg
5P+ySkXqlzSFB91uywNWpys/STUWvDV2+OHYad2BdDt6PhxjkkRIgVgvqkkdKbB7
2uvThaNNMEswCQYDVR0TBAIwADAdBgNVHQ4EFgQUZbrJ1ksnEO1+12ipJ4wSHCvm
sFwwHwYDVR0jBBgwFoAU89wm3hRy+DgMHjqdKLH1pSb8hREwCgYIKoZIzj0EAwID
SAAwRQIgYyIsX1E8PWh4ZIX7KM+gg5NZwY2a9UHioWSzMGqvPikCIQCQDy87OFxc
1PR/eZZ1KFmRUYuxyghLtxIi1/+BgPE+2Q==
-----END CERTIFICATE-----

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

@@ -0,0 +1,5 @@
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIC3yo4TLklV3vUpwnchiCn13F9hABtjU3tLw2TQGACWXoAoGCCqGSM49
AwEHoUQDQgAEDkIgrLrKWlGqoSVWlmsnyyTJateL/+OHBQBQcNA8z3yAlLZ1W2VV
STVmFj7i2zN4jKo8IfYzAxpIAjKHcwNaKA==
-----END EC PRIVATE KEY-----

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

@@ -0,0 +1,12 @@
-----BEGIN CERTIFICATE-----
MIIBtzCCAVygAwIBAgIUaNQANP0JbqEIRANchWyHObCtwyUwCgYIKoZIzj0EAwIw
UzELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVN0YXRlMQ0wCwYDVQQHDARDaXR5MRAw
DgYDVQQKDAdUZXN0T3JnMRMwEQYDVQQDDApUZXN0Um9vdENBMB4XDTI2MDIyMTA4
MzAxNVoXDTM2MDIxOTA4MzAxNVowFDESMBAGA1UEAwwJbG9jYWxob3N0MFkwEwYH
KoZIzj0CAQYIKoZIzj0DAQcDQgAEDkIgrLrKWlGqoSVWlmsnyyTJateL/+OHBQBQ
cNA8z3yAlLZ1W2VVSTVmFj7i2zN4jKo8IfYzAxpIAjKHcwNaKKNNMEswCQYDVR0T
BAIwADAdBgNVHQ4EFgQUy0tDB5Mr0y4OUgKgHgf0OCOUsyswHwYDVR0jBBgwFoAU
89wm3hRy+DgMHjqdKLH1pSb8hREwCgYIKoZIzj0EAwIDSQAwRgIhAN2o1Otq0pJW
D7X7foM/qGgr9L4wcr94KKnlJ/s55ODCAiEAvxeNd8OSMzuSf8AT3FbiZskPq9qw
otekC0SmFtHVsu4=
-----END CERTIFICATE-----

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

@@ -0,0 +1,5 @@
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIPxaAHVgFs4Mf3NxaEJWGcr843M3JnCInDWbQwEcBVV7oAoGCCqGSM49
AwEHoUQDQgAE498nsz667F1s+6cboTTXB/qiHxyd4a/ELpetMB6VVX2M2zbzbq2Z
3ts8yycHe2XIw5LxM0Ezl8xa97BLzjkh8A==
-----END EC PRIVATE KEY-----

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

@@ -0,0 +1,13 @@
-----BEGIN CERTIFICATE-----
MIIB/DCCAaGgAwIBAgIUdfWaj8vluUGRp2GfaHEfSWlO/tMwCgYIKoZIzj0EAwIw
UzELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVN0YXRlMQ0wCwYDVQQHDARDaXR5MRAw
DgYDVQQKDAdUZXN0T3JnMRMwEQYDVQQDDApUZXN0Um9vdENBMB4XDTI2MDIyMTA4
MzAxNFoXDTM2MDIxOTA4MzAxNFowUzELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVN0
YXRlMQ0wCwYDVQQHDARDaXR5MRAwDgYDVQQKDAdUZXN0T3JnMRMwEQYDVQQDDApU
ZXN0Um9vdENBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE498nsz667F1s+6cb
oTTXB/qiHxyd4a/ELpetMB6VVX2M2zbzbq2Z3ts8yycHe2XIw5LxM0Ezl8xa97BL
zjkh8KNTMFEwHQYDVR0OBBYEFPPcJt4Ucvg4DB46nSix9aUm/IURMB8GA1UdIwQY
MBaAFPPcJt4Ucvg4DB46nSix9aUm/IURMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZI
zj0EAwIDSQAwRgIhANfBHCD2cUfM79I6VFWzJWMRp4SE+gNJW4VRXHgjZthqAiEA
ywMIQhLsVlfgcoG77/OIoMuyvwb+elvkSz2qNTh+ue4=
-----END CERTIFICATE-----

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

@@ -0,0 +1 @@
68D40034FD096EA10844035C856C8739B0ADC326

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

@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCPtbfD6hP/fTBm
3ggp+8CC5VV0tQVou5U1aYsNlm01YsdCIRBPg+jsIySeVmvCM7Mx3YJ8gcxud0ef
9oPqArxNKq9HLEsnFOJx2tg405MEMExKhyv1gn8ax/Qzav1jVUBg9l5mTrGNCera
nZawpeVHh8Pc2seKG9pawc0x1kCPeTVt0poT+VVfrNiyB1ksdxn9Sq8RhYDnSLFd
ZZWKfJxNQWnwk3U1yZp1KsBpVPqR02vTdFilnfJ3hS3ExUIy6zDnYbOTm9Kn0DHT
dNSLuAHifu6LMOzekLRrTrV/LbZlpb2Q9Jemt+Mrd0ehfjw2haC78PbW0GLY+e1K
9tAJ4zb7AgMBAAECggEAEWFSxjjrDj0fu3Ei36D42VvPB/xUmSQGmZ0YGh6VOp2l
p5PEGznC07w0U4n0IlqKU3+Mpm40QS/f07LGpWiLSXHeHOd4d1OJR2fGOqkr4JfO
wjyyzlBn+t8v44APARJwZMmnBQyDYKFQa/wfG2IU5p++ylkTRNq1f8Bshph42mEJ
9hiZ2lt2wsdmh91UJm/XWoeXDCqNx62WLPTUboVHwxYv195gkH/abUUK9kWv8i4R
P7EUk9ITVfBYD/shwr6m6oBS1l9vkamALa33zWRyn2mmIn6UJp1jlyXSmstNsPnS
JISfafXzYvIVli9dTtHQrpYEFDe8qIftRApImDmlEQKBgQDAbEmZXNLcUbzt6K5d
KJs2FhU+tslrcBpS8L9GyAHl8W0aP6JTdolWIMZ4WsIzQT3hsZPNalca1zGC1SrH
nuvGhiENjaQPd1OjD6F3YvR4s+WMtXmZVmGu83UyryEh+Zjenat81Ce4j59D4sO3
7We9n3smfA4ky5m8XlTM/qDLTwKBgQC/MSAZGjNKqmMd6CpHqpXfkCpXfgSQYxyb
9+Ox8BgxJZGuY60YvV09xN+GrI7UwV75HmDTbCftZSp0Vy3mEofry8xk83vuThou
SGACa506os0jr/fWoFum0ME69dd3BWup+CuV/bwdJL45GAlHCozRNGdCCeN7WL3+
R+x46wR+lQKBgFD+A2iTAooLuYsmEiZSDiqQ1vZjt5oKQHpCrTAcxXfEy9htimS5
Ewt2ljNYeD9cqbBSr1SZ+vnoNAXOtcf6I2GXWVg8Ex8TadfLn3oB48beabN1Oy6I
hms+PElOH5MOXQLuuJy9K87qXO4VB65mNfFBrHNBai6gqB/6UJVMY9/PAoGAG8dV
dJA795M+B3BeBD+iuvLFVCT5IMlltLuVl5rcyPc+bWoKElghHgJmv7h+oCbgV620
P8OtIW7bdj/caVsz6GyZ6+j8jqlGYIcfe/qKw6Q3zgGZLtPpvRkDmj9x6Ncex3lJ
S+er10gpYz48ytebkiHdBtlM9fT01ec5UnBDHOkCgYBJ5KwdsFl5omwUvNFljEov
92wcP5VDVVaBK4ZmqRWfTMWF3TvJVcByujuVLeOXTiQuvp5HkaLINtnkbWASC/cw
AdBrvZJ1jsmRFnpAEgXLMSe0wazMXycN6+phdF25OmNuNGReE5q11t+7oaN0/9V1
i5CiwYXqnGcHqPuE/bvGvw==
-----END PRIVATE KEY-----

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

@@ -0,0 +1,20 @@
-----BEGIN CERTIFICATE-----
MIIDMDCCAhgCFDy9mhbPptRzCcvT9c3gkR7IAf3TMA0GCSqGSIb3DQEBCwUAMFYx
CzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVTdGF0ZTENMAsGA1UEBwwEQ2l0eTEQMA4G
A1UECgwHVGVzdE9yZzEWMBQGA1UEAwwNVGVzdFJzYVJvb3RDQTAeFw0yNjAyMjEw
ODMwMTdaFw0zNjAyMTkwODMwMTdaMFMxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVT
dGF0ZTENMAsGA1UEBwwEQ2l0eTEQMA4GA1UECgwHVGVzdE9yZzETMBEGA1UEAwwK
VGVzdENsaWVudDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAI+1t8Pq
E/99MGbeCCn7wILlVXS1BWi7lTVpiw2WbTVix0IhEE+D6OwjJJ5Wa8IzszHdgnyB
zG53R5/2g+oCvE0qr0csSycU4nHa2DjTkwQwTEqHK/WCfxrH9DNq/WNVQGD2XmZO
sY0J6tqdlrCl5UeHw9zax4ob2lrBzTHWQI95NW3SmhP5VV+s2LIHWSx3Gf1KrxGF
gOdIsV1llYp8nE1BafCTdTXJmnUqwGlU+pHTa9N0WKWd8neFLcTFQjLrMOdhs5Ob
0qfQMdN01Iu4AeJ+7osw7N6QtGtOtX8ttmWlvZD0l6a34yt3R6F+PDaFoLvw9tbQ
Ytj57Ur20AnjNvsCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEACMujqep7PRc5eOIo
K15+67kr+nu4AScGHZbDP5Rr69bG34azzrCK4s0JqAVLDk3NyHDieFbW5Xi7zmzw
Eav5FjiPEDHx5zZ/xh8W9TsbPoNw7E3H1qOUMfpyWQuUtQyUahdCufuMU8doGv8B
R6rxk2OnbaITFLeZ9OfIG+qZ6/dvFvlFdpHsTRvGF3bfSD6+637KsmOQXwOm+577
D6cPBFa0x0HxJxp1R1nwJ3o1opsD3gWJO/+HiLRQbTcjsF1ZIs+9tuaL90L17d6+
1VRR5Xz2xCoOqE6RQ8mmXFwiDoPDiLzGcv+GdYSPoDpe/TGKf0LHubGd0Kcb5f2H
nJePMg==
-----END CERTIFICATE-----

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

@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC+AmLMV0jpmLyD
bZ3rMWGj4XstfjT2LPJfRwYPbyPD+kgUHMotgIteNgbbxs2ZbyEtRHB1StMevgP5
jROI+BUYHmHQwe5KQPdqgEkQTMhnBXTWyhk3AzAd509VzJwEDnIG414J7yntbVdL
CZVrbocXV5Eqj6NtnpUUj39gOkaZ/L+KpFFbPhHLFDUfu+2n65KrSCw/ut2FGtbd
M/WbytBxjBUyr4jcUxDtN6AX68K0DATGePxrBJEgYOYmTS8tzazTgqf23riWxINh
L5eVuzEM9X/Je/ILjhjTvtgXBFx8ECnROnEJeuwfmrUOO/ieFNWlEO2V4xuTyWOD
mpEkZcPLAgMBAAECggEAFOY9SL2XCv6eSZR+iLaTR0vPWScnGybBeAJfY8Y1OGKo
l/G8uTmI2tF1eqoIxkYnb4u2FOiohRqgZEwEqI2lAFSjLKuQEsHHlQLMRoszhcVf
Xxq2oErs+XUOUGEjjfyqxSAwIUaZpYBf2CSTN1Beco5SrMxEzRNi0XKhL4vpZRlr
ie3dYU4oXQ/zw3+wOaQssq0CHmKwMeKtmnMp+fFInyqPC08xgzLmntwEIXKutMhh
R+Q0DFBMEQ55GJ714gL36CFuYD/P7mbF43zUBUz5LgAyuk8zExG277eR63lX0AD4
aFLEtHLU5B6OTEQxDjZ1413zP4jw4iOWJr1GxUi/AQKBgQD1heFXPCIB0Bgg8f1G
+3hMATnW14VqM3Spn2ZLV510aPw0aqlKk37LUT5lTTloMWeKQgzzpF3F/+Wcbcdz
+yJJMxNVmgvGGwQfLgjzT+3r2JfpCGTCQbcY6hb2LlOieptxM1dh5pTNpiDLUSRd
jvMEJ12GScYGlEEsLhmHUURCywKBgQDGHhGKOtOgWm8ho91hUlH82sFvp8j4om3s
bKwANJzsXNRXQm5yVWLmrA5ugDszKZbJK5pdLGvc4456SEeQgvQNjk+XDqtqA8Lw
JlcwQ0fUHMDb7z4878ssZV4G6Z9C7xXZgntgRvq9irPEhtPnwRKUUy3QnQ9phIyp
rSLx1UdjAQKBgBQgEUSRTUhQwmmQ7G3xFv9D6nXN3MXDygBNbJkoaWOtZ170j0Pp
qy8HTdIH7ni39ADFQUKHaphcTXnxFbRQFZNieGc/5U8rz76ui1VGa/41Ft6nLXsu
389PAOrVCU38Ntmkl7kSqYfh4jZIRG7W1Ny2TVhAm9bWODFi5fzNkIbZAoGBAIeV
fmV+WuRr35BDJ7d58fg88ZLrXeOird3WhWPind44rW1GXnXKr9OzvnCrO1iJRtNI
Du1jADJ8XT6chrWEmWdJlHiJpo/4IQnfA15ZPSgRwX8C3TIw3Xf1q4LJkZ/qJabk
4HCEQwdCjEKcDxuVC5UM09boFesdtnJMthSQ5LkBAoGBAMBB5naBf3ASfAPefKP/
F2AUH5IgTc6WhfKA1HCbQWC4PZmS9R30T7QpDy0JVn4gwnJ37TwI7u1PN+//+veV
P0kbTNwVvzzCYDEcsvqEtqwXZZ7WZACREQWf6Su5V/OIi9dq7h2XmyY9WtJ71Mgu
Z/F0hK9uRzN1YGrhO4MsVXNA
-----END PRIVATE KEY-----

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

@@ -0,0 +1,18 @@
-----BEGIN CERTIFICATE-----
MIIC8TCCAdkCFDy9mhbPptRzCcvT9c3gkR7IAf3SMA0GCSqGSIb3DQEBCwUAMFYx
CzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVTdGF0ZTENMAsGA1UEBwwEQ2l0eTEQMA4G
A1UECgwHVGVzdE9yZzEWMBQGA1UEAwwNVGVzdFJzYVJvb3RDQTAeFw0yNjAyMjEw
ODMwMTZaFw0zNjAyMTkwODMwMTZaMBQxEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIw
DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL4CYsxXSOmYvINtnesxYaPhey1+
NPYs8l9HBg9vI8P6SBQcyi2Ai142BtvGzZlvIS1EcHVK0x6+A/mNE4j4FRgeYdDB
7kpA92qASRBMyGcFdNbKGTcDMB3nT1XMnAQOcgbjXgnvKe1tV0sJlWtuhxdXkSqP
o22elRSPf2A6Rpn8v4qkUVs+EcsUNR+77afrkqtILD+63YUa1t0z9ZvK0HGMFTKv
iNxTEO03oBfrwrQMBMZ4/GsEkSBg5iZNLy3NrNOCp/beuJbEg2Evl5W7MQz1f8l7
8guOGNO+2BcEXHwQKdE6cQl67B+atQ47+J4U1aUQ7ZXjG5PJY4OakSRlw8sCAwEA
ATANBgkqhkiG9w0BAQsFAAOCAQEAIYaOr2AXKtDfSDbSuucOoiTj8LyRbsnNz62z
+dFpi0R/vICrFYfjUOTUtHfxTZOe9q5/Vs3PJBwHdLaUKZhtZRDoicw4SkcxFGj+
Gm/tWghkquY40ihLvEefOptQz4W1gukU+ppFHXsOeCN8EVHX9OrcegW5+bt4B4OR
9p87B/YASjVgA23ZWJKIdJ09RY01wolRll7S8j52J3PeVFlTLiH8wCifdmglNjmX
nCAOUnzpD5qkxfXzvgvgS7iQr09AstWjwEuV01++VBUDN1YW/wCN7itUnIcO5K+T
GUkZyvdqNi8Z0V39PbPtN2YtLk3ylISa8ZdUC7+l1JkuSPqt5g==
-----END CERTIFICATE-----

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

@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDCmnCyEFiU6DbC
rOxjl36iO/665dpWE3AVqmY7pmRv8TyhS7LpOh0ekRieBTDO8pPrT2Z/z+BEJwny
U2oUJv01uX4rhLq/vcbN3OO+AQHcG5ELKrZtiWy87t2amoitpsj1xWEWS2L3Rk5w
CmZTofXBLNyMOMddfuKRU+JLW0WnyAZ6O+mmW/dy9HqT0fYvwb0ucWC2w3jvVNyi
uBMkD0IdCpectHAFEGJgJUWiLUIG+ml2vt0C6OWUK3BT2RPo51B4e6tV+eL+s+AL
NvXeTlo0Z7gyK6s2EPZrleFAFagbRVaOerNruhLZ3KTdx9u4Oj0z32rg/XX12/js
rW2zS5hfAgMBAAECggEALPfTItJ0JbSjMskSttDDCmoiqr2CfnFvbRI7HX2Np/Bu
ujbr2Mj9AZs1vQ4mASw80hs+7Dn08Mq0mbJ8yLtZt5htd1DsdnI8BkYMulMxQtN7
6MCt9xHSdrYbryYWf0oTFffOP4CcFdfBOFKu4pSCXWkobZ6RMyGm6T4hBJjKt8Ix
zY6UMJlmLlcHSRNPm1RNPsWWe6CNNOtJxyKQ/P8JyHu+9WW0ld7azfbIOLoAKbPV
tY8+F3TVz0FcG9piD+6nhJL0uLn6wJJnMQVGAoJFau3CpedKSm3jbIOznaXUVei9
yRv1M24kdkZc6OhPhihGYzHiKfBKxlrHVXHkh+lIwQKBgQDkxcm0TEU5gxJegKHc
yVRKDIyW2/D/YgtOu8ZUtd2QVXQzO436kl4cjEW4ipS6isCLC1ENtJ5JuKnkwZH0
T7hnRnzxHcyE91XYIDeEldBv1Cv5QYjJpJt6y+rFD1alSivvv/DqB/y5nOKuYOyH
1z6gMUrXZ6uwApB/Df+cw68HDQKBgQDZw5YEI+Z7z76VPmtSFirE5c0rNBf4tzOr
EmqkVmRtoRZVe/uw5Bm3V/iZrWYSTyAMguBKzCVUhPg5h+PlaHyLjtwV2LkkPYJp
xTEXFcYh3p/cW/rqdvMnD6hnORs1Mj9XFpbMPWlekqruhWxtdtOkY6yxS9bDzzp5
uxdmvvbCGwKBgQDEbIZJBguR29ZTycIwXbS/d5LmnKWJwNbQnS0m4pgAKz8AFixL
bozbmhzq3CHjIOs891R6nhAiYCmPPhxhVmmQUtdH9zv5FpxgWxkP/8ndmqC+/OPD
rk/I2XkUBZ1xggPDcFwbtQvrGqcO7i1oXQlqZahK1rp0/16tmIlWQjXvqQKBgQDA
dg5mRlx0XN1yBiLP/+t47ilw36+4ECLINZSu/fPwuIiGsPd4FYFs+4EqQYiO8gO/
SwR01wy/MG46WpHetYQty+tUG6E2GG7kkHWck4/za1EabujKxKqOgVYzNNOJJom4
rKxGgphYD4SnHqD/9h+DkNyWLhL4KHTkFajPFEP+tQKBgQCfaogtT5Uv1wzQgs6c
/ATSSCw4RMFPQA702KWquMcydGyyA5WiE2n7S5J8Gl2BhdGRdVBqoM2RBqQs9diw
m8Da0SJkPjwgXda6FOmm4krQIowXO2bLTkDo0jEAWW3PmrF53MmmAGhBT7cB/8sK
8C5KH7rGwI9dW5OxbbCxA6g2mw==
-----END PRIVATE KEY-----

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

@@ -0,0 +1,22 @@
-----BEGIN CERTIFICATE-----
MIIDjTCCAnWgAwIBAgIULqYXD4Y/S1pjcF50lTIKVEMHaqYwDQYJKoZIhvcNAQEL
BQAwVjELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVN0YXRlMQ0wCwYDVQQHDARDaXR5
MRAwDgYDVQQKDAdUZXN0T3JnMRYwFAYDVQQDDA1UZXN0UnNhUm9vdENBMB4XDTI2
MDIyMTA4MzAxNVoXDTM2MDIxOTA4MzAxNVowVjELMAkGA1UEBhMCVVMxDjAMBgNV
BAgMBVN0YXRlMQ0wCwYDVQQHDARDaXR5MRAwDgYDVQQKDAdUZXN0T3JnMRYwFAYD
VQQDDA1UZXN0UnNhUm9vdENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
AQEAwppwshBYlOg2wqzsY5d+ojv+uuXaVhNwFapmO6Zkb/E8oUuy6TodHpEYngUw
zvKT609mf8/gRCcJ8lNqFCb9Nbl+K4S6v73GzdzjvgEB3BuRCyq2bYlsvO7dmpqI
rabI9cVhFkti90ZOcApmU6H1wSzcjDjHXX7ikVPiS1tFp8gGejvpplv3cvR6k9H2
L8G9LnFgtsN471TcorgTJA9CHQqXnLRwBRBiYCVFoi1CBvppdr7dAujllCtwU9kT
6OdQeHurVfni/rPgCzb13k5aNGe4MiurNhD2a5XhQBWoG0VWjnqza7oS2dyk3cfb
uDo9M99q4P119dv47K1ts0uYXwIDAQABo1MwUTAdBgNVHQ4EFgQUJYIW2GVN+Z9d
TiXBmXZWrpRO0u0wHwYDVR0jBBgwFoAUJYIW2GVN+Z9dTiXBmXZWrpRO0u0wDwYD
VR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAMekKKzOrLtMUUeUDhT7z
ZyNpdZQypboXH4jFZgnnSDQ4nhyTBQ1tydRsxKe/ySev2Ug4qqYeUUvoHVRe33bp
HpQDlowRx0SYMY3QHZPNd4Bhi24N4L7Xv2ncfmhiUaECT9m6F3ZMBKhY0GgD6rzE
vSBGAA/V59qI3FaREY2UmxRctDZx1ZdZyKw14rdrPHfxLZmCMJUtgxGdKcItyDjn
pd4q6O7nf4fO0Cv8UDe035Jx5jQwpKw2nY7GcP8YHaLv/rzOT5Uog0UQ720GLjyV
uGKfeXeH6Tw6yPjy/DNvjZKFG+SILJwdtW1m7w/NrWKiylhrDQqQamhetTquCmVf
og==
-----END CERTIFICATE-----

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

@@ -0,0 +1 @@
3CBD9A16CFA6D47309CBD3F5CDE0911EC801FDD3

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

@@ -0,0 +1,66 @@
#!/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"
# 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 -CAcreateserial
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 -CAcreateserial
rm _rsacli.csr

1326
tests/integration.rs Normal file

File diff suppressed because it is too large Load Diff