Initial commit
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
49
.gitea/workflows/ci.yaml
Normal file
49
.gitea/workflows/ci.yaml
Normal file
@@ -0,0 +1,49 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust
|
||||
run: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable
|
||||
- name: Build
|
||||
run: . $HOME/.cargo/env && cargo build
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust
|
||||
run: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable
|
||||
- name: Test (default)
|
||||
run: . $HOME/.cargo/env && cargo test
|
||||
- name: Test (webpki)
|
||||
run: . $HOME/.cargo/env && cargo test --features webpki
|
||||
- name: Test (native-pki)
|
||||
run: . $HOME/.cargo/env && cargo test --features native-pki
|
||||
- name: Test (native-pki + rsa)
|
||||
run: . $HOME/.cargo/env && cargo test --features native-pki,rsa
|
||||
|
||||
clippy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust
|
||||
run: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable --component clippy
|
||||
- name: Clippy
|
||||
run: . $HOME/.cargo/env && cargo clippy -- -D warnings
|
||||
|
||||
no-std:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust
|
||||
run: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable --target thumbv7em-none-eabi
|
||||
- name: Build (no_std)
|
||||
run: . $HOME/.cargo/env && cargo build --target thumbv7em-none-eabi --no-default-features
|
||||
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
target/
|
||||
1755
Cargo.lock
generated
Normal file
1755
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
60
Cargo.toml
Normal file
60
Cargo.toml
Normal 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
174
LICENSE
Normal 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
9
README.md
Normal 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
121
src/alert.rs
Normal 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
30
src/application_data.rs
Normal 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
505
src/asynch.rs
Normal 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
493
src/blocking.rs
Normal 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
300
src/buffer.rs
Normal 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
279
src/cert_verify.rs
Normal 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
157
src/certificate.rs
Normal 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
35
src/change_cipher_spec.rs
Normal 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
15
src/cipher.rs
Normal 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
26
src/cipher_suites.rs
Normal 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),
|
||||
}
|
||||
}
|
||||
}
|
||||
24
src/common/decrypted_buffer_info.rs
Normal file
24
src/common/decrypted_buffer_info.rs
Normal 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
|
||||
}
|
||||
}
|
||||
52
src/common/decrypted_read_handler.rs
Normal file
52
src/common/decrypted_read_handler.rs
Normal 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
2
src/common/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
pub mod decrypted_buffer_info;
|
||||
pub mod decrypted_read_handler;
|
||||
382
src/config.rs
Normal file
382
src/config.rs
Normal 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
643
src/connection.rs
Normal 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
22
src/content_types.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
39
src/extensions/extension_data/alpn.rs
Normal file
39
src/extensions/extension_data/alpn.rs
Normal 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(())
|
||||
})
|
||||
}
|
||||
}
|
||||
128
src/extensions/extension_data/key_share.rs
Normal file
128
src/extensions/extension_data/key_share.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
34
src/extensions/extension_data/max_fragment_length.rs
Normal file
34
src/extensions/extension_data/max_fragment_length.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
11
src/extensions/extension_data/mod.rs
Normal file
11
src/extensions/extension_data/mod.rs
Normal 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;
|
||||
61
src/extensions/extension_data/pre_shared_key.rs
Normal file
61
src/extensions/extension_data/pre_shared_key.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
53
src/extensions/extension_data/psk_key_exchange_modes.rs
Normal file
53
src/extensions/extension_data/psk_key_exchange_modes.rs
Normal 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(())
|
||||
})
|
||||
}
|
||||
}
|
||||
124
src/extensions/extension_data/server_name.rs
Normal file
124
src/extensions/extension_data/server_name.rs
Normal 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(())
|
||||
}
|
||||
}
|
||||
153
src/extensions/extension_data/signature_algorithms.rs
Normal file
153
src/extensions/extension_data/signature_algorithms.rs
Normal 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(())
|
||||
})
|
||||
}
|
||||
}
|
||||
34
src/extensions/extension_data/signature_algorithms_cert.rs
Normal file
34
src/extensions/extension_data/signature_algorithms_cert.rs
Normal 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(())
|
||||
})
|
||||
}
|
||||
}
|
||||
101
src/extensions/extension_data/supported_groups.rs
Normal file
101
src/extensions/extension_data/supported_groups.rs
Normal 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(())
|
||||
})
|
||||
}
|
||||
}
|
||||
65
src/extensions/extension_data/supported_versions.rs
Normal file
65
src/extensions/extension_data/supported_versions.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
24
src/extensions/extension_data/unimplemented.rs
Normal file
24
src/extensions/extension_data/unimplemented.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
93
src/extensions/extension_group_macro.rs
Normal file
93
src/extensions/extension_group_macro.rs
Normal 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;
|
||||
99
src/extensions/messages.rs
Normal file
99
src/extensions/messages.rs
Normal 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
81
src/extensions/mod.rs
Normal 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
223
src/fmt.rs
Normal 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
36
src/handshake/binder.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
176
src/handshake/certificate.rs
Normal file
176
src/handshake/certificate.rs
Normal 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,
|
||||
})
|
||||
}
|
||||
}
|
||||
49
src/handshake/certificate_request.rs
Normal file
49
src/handshake/certificate_request.rs
Normal 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 })
|
||||
}
|
||||
}
|
||||
51
src/handshake/certificate_verify.rs
Normal file
51
src/handshake/certificate_verify.rs
Normal 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(())
|
||||
}
|
||||
}
|
||||
165
src/handshake/client_hello.rs
Normal file
165
src/handshake/client_hello.rs
Normal 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(())
|
||||
}
|
||||
}
|
||||
19
src/handshake/encrypted_extensions.rs
Normal file
19
src/handshake/encrypted_extensions.rs
Normal 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
43
src/handshake/finished.rs
Normal 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
242
src/handshake/mod.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
33
src/handshake/new_session_ticket.rs
Normal file
33
src/handshake/new_session_ticket.rs
Normal 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 })
|
||||
}
|
||||
}
|
||||
80
src/handshake/server_hello.rs
Normal file
80
src/handshake/server_hello.rs
Normal 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
485
src/key_schedule.rs
Normal 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
126
src/lib.rs
Normal 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
468
src/native_pki.rs
Normal file
@@ -0,0 +1,468 @@
|
||||
use crate::ProtocolError;
|
||||
#[cfg(feature = "p384")]
|
||||
use crate::certificate::ECDSA_SHA384;
|
||||
#[cfg(feature = "ed25519")]
|
||||
use crate::certificate::ED25519;
|
||||
use crate::certificate::{DecodedCertificate, ECDSA_SHA256, Time};
|
||||
#[cfg(feature = "rsa")]
|
||||
use crate::certificate::{RSA_PKCS1_SHA256, RSA_PKCS1_SHA384, RSA_PKCS1_SHA512};
|
||||
use crate::config::{Certificate, TlsCipherSuite, TlsClock, Verifier};
|
||||
use crate::extensions::extension_data::signature_algorithms::SignatureScheme;
|
||||
use crate::handshake::{
|
||||
certificate::{
|
||||
Certificate as OwnedCertificate, CertificateEntryRef, CertificateRef as ServerCertificate,
|
||||
},
|
||||
certificate_verify::HandshakeVerifyRef,
|
||||
};
|
||||
use crate::parse_buffer::ParseError;
|
||||
use const_oid::ObjectIdentifier;
|
||||
use core::marker::PhantomData;
|
||||
use der::Decode;
|
||||
use digest::Digest;
|
||||
use heapless::{String, Vec};
|
||||
|
||||
const HOSTNAME_MAXLEN: usize = 64;
|
||||
const COMMON_NAME_OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("2.5.4.3");
|
||||
|
||||
pub struct CertificateChain<'a> {
|
||||
prev: Option<&'a CertificateEntryRef<'a>>,
|
||||
chain: &'a ServerCertificate<'a>,
|
||||
idx: isize,
|
||||
}
|
||||
|
||||
impl<'a> CertificateChain<'a> {
|
||||
pub fn new(ca: &'a CertificateEntryRef, chain: &'a ServerCertificate<'a>) -> Self {
|
||||
Self {
|
||||
prev: Some(ca),
|
||||
chain,
|
||||
idx: chain.entries.len() as isize - 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for CertificateChain<'a> {
|
||||
type Item = (&'a CertificateEntryRef<'a>, &'a CertificateEntryRef<'a>);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.idx < 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let cur = &self.chain.entries[self.idx as usize];
|
||||
let out = (self.prev.unwrap(), cur);
|
||||
|
||||
self.prev = Some(cur);
|
||||
self.idx -= 1;
|
||||
|
||||
Some(out)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CertVerifier<'a, CipherSuite, Clock, const CERT_SIZE: usize>
|
||||
where
|
||||
Clock: TlsClock,
|
||||
CipherSuite: TlsCipherSuite,
|
||||
{
|
||||
ca: Certificate<&'a [u8]>,
|
||||
host: Option<heapless::String<64>>,
|
||||
certificate_transcript: Option<CipherSuite::Hash>,
|
||||
certificate: Option<OwnedCertificate<CERT_SIZE>>,
|
||||
_clock: PhantomData<Clock>,
|
||||
}
|
||||
|
||||
impl<'a, CipherSuite, Clock, const CERT_SIZE: usize> CertVerifier<'a, CipherSuite, Clock, CERT_SIZE>
|
||||
where
|
||||
Clock: TlsClock,
|
||||
CipherSuite: TlsCipherSuite,
|
||||
{
|
||||
#[must_use]
|
||||
pub fn new(ca: Certificate<&'a [u8]>) -> Self {
|
||||
Self {
|
||||
ca,
|
||||
host: None,
|
||||
certificate_transcript: None,
|
||||
certificate: None,
|
||||
_clock: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<CipherSuite, Clock, const CERT_SIZE: usize> Verifier<CipherSuite>
|
||||
for CertVerifier<'_, CipherSuite, Clock, CERT_SIZE>
|
||||
where
|
||||
CipherSuite: TlsCipherSuite,
|
||||
Clock: TlsClock,
|
||||
{
|
||||
fn set_hostname_verification(&mut self, hostname: &str) -> Result<(), ProtocolError> {
|
||||
self.host.replace(
|
||||
heapless::String::try_from(hostname).map_err(|_| ProtocolError::InsufficientSpace)?,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn verify_certificate(
|
||||
&mut self,
|
||||
transcript: &CipherSuite::Hash,
|
||||
cert: ServerCertificate,
|
||||
) -> Result<(), ProtocolError> {
|
||||
let mut cn = None;
|
||||
for (p, q) in CertificateChain::new(&(&self.ca).into(), &cert) {
|
||||
cn = verify_certificate(p, q, Clock::now())?;
|
||||
}
|
||||
if self.host.ne(&cn) {
|
||||
error!(
|
||||
"Hostname ({:?}) does not match CommonName ({:?})",
|
||||
self.host, cn
|
||||
);
|
||||
return Err(ProtocolError::InvalidCertificate);
|
||||
}
|
||||
|
||||
self.certificate.replace(cert.try_into()?);
|
||||
self.certificate_transcript.replace(transcript.clone());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn verify_signature(&mut self, verify: HandshakeVerifyRef) -> Result<(), ProtocolError> {
|
||||
let handshake_hash = unwrap!(self.certificate_transcript.take());
|
||||
let ctx_str = b"TLS 1.3, server CertificateVerify\x00";
|
||||
let mut msg: Vec<u8, 146> = Vec::new();
|
||||
msg.resize(64, 0x20)
|
||||
.map_err(|_| ProtocolError::EncodeError)?;
|
||||
msg.extend_from_slice(ctx_str)
|
||||
.map_err(|_| ProtocolError::EncodeError)?;
|
||||
msg.extend_from_slice(&handshake_hash.finalize())
|
||||
.map_err(|_| ProtocolError::EncodeError)?;
|
||||
|
||||
let certificate = unwrap!(self.certificate.as_ref()).try_into()?;
|
||||
verify_signature(&msg[..], &certificate, &verify)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn verify_signature(
|
||||
message: &[u8],
|
||||
certificate: &ServerCertificate,
|
||||
verify: &HandshakeVerifyRef,
|
||||
) -> Result<(), ProtocolError> {
|
||||
let verified;
|
||||
|
||||
let certificate =
|
||||
if let Some(CertificateEntryRef::X509(certificate)) = certificate.entries.first() {
|
||||
certificate
|
||||
} else {
|
||||
return Err(ProtocolError::DecodeError);
|
||||
};
|
||||
|
||||
let certificate =
|
||||
DecodedCertificate::from_der(certificate).map_err(|_| ProtocolError::DecodeError)?;
|
||||
|
||||
let public_key = certificate
|
||||
.tbs_certificate
|
||||
.subject_public_key_info
|
||||
.public_key
|
||||
.as_bytes()
|
||||
.ok_or(ProtocolError::DecodeError)?;
|
||||
|
||||
match verify.signature_scheme {
|
||||
SignatureScheme::EcdsaSecp256r1Sha256 => {
|
||||
use p256::ecdsa::{Signature, VerifyingKey, signature::Verifier};
|
||||
let verifying_key = VerifyingKey::from_sec1_bytes(public_key)
|
||||
.map_err(|_| ProtocolError::DecodeError)?;
|
||||
let signature =
|
||||
Signature::from_der(&verify.signature).map_err(|_| ProtocolError::DecodeError)?;
|
||||
verified = verifying_key.verify(message, &signature).is_ok();
|
||||
}
|
||||
#[cfg(feature = "p384")]
|
||||
SignatureScheme::EcdsaSecp384r1Sha384 => {
|
||||
use p384::ecdsa::{Signature, VerifyingKey, signature::Verifier};
|
||||
let verifying_key = VerifyingKey::from_sec1_bytes(public_key)
|
||||
.map_err(|_| ProtocolError::DecodeError)?;
|
||||
let signature =
|
||||
Signature::from_der(&verify.signature).map_err(|_| ProtocolError::DecodeError)?;
|
||||
verified = verifying_key.verify(message, &signature).is_ok();
|
||||
}
|
||||
#[cfg(feature = "ed25519")]
|
||||
SignatureScheme::Ed25519 => {
|
||||
use ed25519_dalek::{Signature, Verifier, VerifyingKey};
|
||||
let verifying_key: VerifyingKey =
|
||||
VerifyingKey::from_bytes(public_key.try_into().unwrap())
|
||||
.map_err(|_| ProtocolError::DecodeError)?;
|
||||
let signature =
|
||||
Signature::try_from(verify.signature).map_err(|_| ProtocolError::DecodeError)?;
|
||||
verified = verifying_key.verify(message, &signature).is_ok();
|
||||
}
|
||||
#[cfg(feature = "rsa")]
|
||||
SignatureScheme::RsaPssRsaeSha256 => {
|
||||
use rsa::{
|
||||
RsaPublicKey,
|
||||
pkcs1::DecodeRsaPublicKey,
|
||||
pss::{Signature, VerifyingKey},
|
||||
signature::Verifier,
|
||||
};
|
||||
use sha2::Sha256;
|
||||
|
||||
let der_pubkey = RsaPublicKey::from_pkcs1_der(public_key).unwrap();
|
||||
let verifying_key = VerifyingKey::<Sha256>::from(der_pubkey);
|
||||
|
||||
let signature =
|
||||
Signature::try_from(verify.signature).map_err(|_| ProtocolError::DecodeError)?;
|
||||
verified = verifying_key.verify(message, &signature).is_ok();
|
||||
}
|
||||
#[cfg(feature = "rsa")]
|
||||
SignatureScheme::RsaPssRsaeSha384 => {
|
||||
use rsa::{
|
||||
RsaPublicKey,
|
||||
pkcs1::DecodeRsaPublicKey,
|
||||
pss::{Signature, VerifyingKey},
|
||||
signature::Verifier,
|
||||
};
|
||||
use sha2::Sha384;
|
||||
|
||||
let der_pubkey =
|
||||
RsaPublicKey::from_pkcs1_der(public_key).map_err(|_| ProtocolError::DecodeError)?;
|
||||
let verifying_key = VerifyingKey::<Sha384>::from(der_pubkey);
|
||||
|
||||
let signature =
|
||||
Signature::try_from(verify.signature).map_err(|_| ProtocolError::DecodeError)?;
|
||||
verified = verifying_key.verify(message, &signature).is_ok();
|
||||
}
|
||||
#[cfg(feature = "rsa")]
|
||||
SignatureScheme::RsaPssRsaeSha512 => {
|
||||
use rsa::{
|
||||
RsaPublicKey,
|
||||
pkcs1::DecodeRsaPublicKey,
|
||||
pss::{Signature, VerifyingKey},
|
||||
signature::Verifier,
|
||||
};
|
||||
use sha2::Sha512;
|
||||
|
||||
let der_pubkey =
|
||||
RsaPublicKey::from_pkcs1_der(public_key).map_err(|_| ProtocolError::DecodeError)?;
|
||||
let verifying_key = VerifyingKey::<Sha512>::from(der_pubkey);
|
||||
|
||||
let signature =
|
||||
Signature::try_from(verify.signature).map_err(|_| ProtocolError::DecodeError)?;
|
||||
verified = verifying_key.verify(message, &signature).is_ok();
|
||||
}
|
||||
_ => {
|
||||
error!(
|
||||
"InvalidSignatureScheme: {:?} Are you missing a feature?",
|
||||
verify.signature_scheme
|
||||
);
|
||||
return Err(ProtocolError::InvalidSignatureScheme);
|
||||
}
|
||||
}
|
||||
|
||||
if !verified {
|
||||
return Err(ProtocolError::InvalidSignature);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_certificate_tlv_bytes<'a>(input: &[u8]) -> der::Result<&[u8]> {
|
||||
use der::{Decode, Reader, SliceReader};
|
||||
|
||||
let mut reader = SliceReader::new(input)?;
|
||||
let top_header = der::Header::decode(&mut reader)?;
|
||||
top_header.tag().assert_eq(der::Tag::Sequence)?;
|
||||
|
||||
let header = der::Header::peek(&mut reader)?;
|
||||
header.tag().assert_eq(der::Tag::Sequence)?;
|
||||
|
||||
reader.tlv_bytes()
|
||||
}
|
||||
|
||||
fn get_cert_time(time: Time) -> u64 {
|
||||
match time {
|
||||
Time::UtcTime(utc_time) => utc_time.to_unix_duration().as_secs(),
|
||||
Time::GeneralTime(generalized_time) => generalized_time.to_unix_duration().as_secs(),
|
||||
}
|
||||
}
|
||||
|
||||
fn verify_certificate(
|
||||
verifier: &CertificateEntryRef,
|
||||
certificate: &CertificateEntryRef,
|
||||
now: Option<u64>,
|
||||
) -> Result<Option<heapless::String<HOSTNAME_MAXLEN>>, ProtocolError> {
|
||||
let mut verified = false;
|
||||
let mut common_name = None;
|
||||
|
||||
let ca_certificate = if let CertificateEntryRef::X509(verifier) = verifier {
|
||||
DecodedCertificate::from_der(verifier).map_err(|_| ProtocolError::DecodeError)?
|
||||
} else {
|
||||
return Err(ProtocolError::DecodeError);
|
||||
};
|
||||
|
||||
if let CertificateEntryRef::X509(certificate) = certificate {
|
||||
let parsed_certificate =
|
||||
DecodedCertificate::from_der(certificate).map_err(|_| ProtocolError::DecodeError)?;
|
||||
|
||||
let ca_public_key = ca_certificate
|
||||
.tbs_certificate
|
||||
.subject_public_key_info
|
||||
.public_key
|
||||
.as_bytes()
|
||||
.ok_or(ProtocolError::DecodeError)?;
|
||||
|
||||
for elems in parsed_certificate.tbs_certificate.subject.iter() {
|
||||
let attrs = elems
|
||||
.get(0)
|
||||
.ok_or(ProtocolError::ParseError(ParseError::InvalidData))?;
|
||||
if attrs.oid == COMMON_NAME_OID {
|
||||
let mut v: Vec<u8, HOSTNAME_MAXLEN> = Vec::new();
|
||||
v.extend_from_slice(attrs.value.value())
|
||||
.map_err(|_| ProtocolError::ParseError(ParseError::InvalidData))?;
|
||||
common_name = String::from_utf8(v).ok();
|
||||
debug!("CommonName: {:?}", common_name);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(now) = now {
|
||||
if get_cert_time(parsed_certificate.tbs_certificate.validity.not_before) > now
|
||||
|| get_cert_time(parsed_certificate.tbs_certificate.validity.not_after) < now
|
||||
{
|
||||
return Err(ProtocolError::InvalidCertificate);
|
||||
}
|
||||
debug!("Epoch is {} and certificate is valid!", now)
|
||||
}
|
||||
|
||||
let certificate_data =
|
||||
get_certificate_tlv_bytes(certificate).map_err(|_| ProtocolError::DecodeError)?;
|
||||
|
||||
match parsed_certificate.signature_algorithm {
|
||||
ECDSA_SHA256 => {
|
||||
use p256::ecdsa::{Signature, VerifyingKey, signature::Verifier};
|
||||
let verifying_key = VerifyingKey::from_sec1_bytes(ca_public_key)
|
||||
.map_err(|_| ProtocolError::DecodeError)?;
|
||||
|
||||
let signature = Signature::from_der(
|
||||
parsed_certificate
|
||||
.signature
|
||||
.as_bytes()
|
||||
.ok_or(ProtocolError::ParseError(ParseError::InvalidData))?,
|
||||
)
|
||||
.map_err(|_| ProtocolError::ParseError(ParseError::InvalidData))?;
|
||||
|
||||
verified = verifying_key.verify(&certificate_data, &signature).is_ok();
|
||||
}
|
||||
#[cfg(feature = "p384")]
|
||||
ECDSA_SHA384 => {
|
||||
use p384::ecdsa::{Signature, VerifyingKey, signature::Verifier};
|
||||
let verifying_key = VerifyingKey::from_sec1_bytes(ca_public_key)
|
||||
.map_err(|_| ProtocolError::DecodeError)?;
|
||||
|
||||
let signature = Signature::from_der(
|
||||
parsed_certificate
|
||||
.signature
|
||||
.as_bytes()
|
||||
.ok_or(ProtocolError::ParseError(ParseError::InvalidData))?,
|
||||
)
|
||||
.map_err(|_| ProtocolError::ParseError(ParseError::InvalidData))?;
|
||||
|
||||
verified = verifying_key.verify(&certificate_data, &signature).is_ok();
|
||||
}
|
||||
#[cfg(feature = "ed25519")]
|
||||
ED25519 => {
|
||||
use ed25519_dalek::{Signature, Verifier, VerifyingKey};
|
||||
let verifying_key: VerifyingKey =
|
||||
VerifyingKey::from_bytes(ca_public_key.try_into().unwrap())
|
||||
.map_err(|_| ProtocolError::DecodeError)?;
|
||||
|
||||
let signature = Signature::try_from(
|
||||
parsed_certificate
|
||||
.signature
|
||||
.as_bytes()
|
||||
.ok_or(ProtocolError::ParseError(ParseError::InvalidData))?,
|
||||
)
|
||||
.map_err(|_| ProtocolError::ParseError(ParseError::InvalidData))?;
|
||||
|
||||
verified = verifying_key.verify(certificate_data, &signature).is_ok();
|
||||
}
|
||||
#[cfg(feature = "rsa")]
|
||||
a if a == RSA_PKCS1_SHA256 => {
|
||||
use rsa::{
|
||||
pkcs1::DecodeRsaPublicKey,
|
||||
pkcs1v15::{Signature, VerifyingKey},
|
||||
signature::Verifier,
|
||||
};
|
||||
use sha2::Sha256;
|
||||
|
||||
let verifying_key =
|
||||
VerifyingKey::<Sha256>::from_pkcs1_der(ca_public_key).map_err(|e| {
|
||||
error!("VerifyingKey: {}", e);
|
||||
ProtocolError::DecodeError
|
||||
})?;
|
||||
|
||||
let signature = Signature::try_from(
|
||||
parsed_certificate
|
||||
.signature
|
||||
.as_bytes()
|
||||
.ok_or(ProtocolError::ParseError(ParseError::InvalidData))?,
|
||||
)
|
||||
.map_err(|e| {
|
||||
error!("Signature: {}", e);
|
||||
ProtocolError::ParseError(ParseError::InvalidData)
|
||||
})?;
|
||||
|
||||
verified = verifying_key.verify(certificate_data, &signature).is_ok();
|
||||
}
|
||||
#[cfg(feature = "rsa")]
|
||||
a if a == RSA_PKCS1_SHA384 => {
|
||||
use rsa::{
|
||||
pkcs1::DecodeRsaPublicKey,
|
||||
pkcs1v15::{Signature, VerifyingKey},
|
||||
signature::Verifier,
|
||||
};
|
||||
use sha2::Sha384;
|
||||
|
||||
let verifying_key = VerifyingKey::<Sha384>::from_pkcs1_der(ca_public_key)
|
||||
.map_err(|_| ProtocolError::DecodeError)?;
|
||||
|
||||
let signature = Signature::try_from(
|
||||
parsed_certificate
|
||||
.signature
|
||||
.as_bytes()
|
||||
.ok_or(ProtocolError::ParseError(ParseError::InvalidData))?,
|
||||
)
|
||||
.map_err(|_| ProtocolError::ParseError(ParseError::InvalidData))?;
|
||||
|
||||
verified = verifying_key.verify(certificate_data, &signature).is_ok();
|
||||
}
|
||||
#[cfg(feature = "rsa")]
|
||||
a if a == RSA_PKCS1_SHA512 => {
|
||||
use rsa::{
|
||||
pkcs1::DecodeRsaPublicKey,
|
||||
pkcs1v15::{Signature, VerifyingKey},
|
||||
signature::Verifier,
|
||||
};
|
||||
use sha2::Sha512;
|
||||
|
||||
let verifying_key = VerifyingKey::<Sha512>::from_pkcs1_der(ca_public_key)
|
||||
.map_err(|_| ProtocolError::DecodeError)?;
|
||||
|
||||
let signature = Signature::try_from(
|
||||
parsed_certificate
|
||||
.signature
|
||||
.as_bytes()
|
||||
.ok_or(ProtocolError::ParseError(ParseError::InvalidData))?,
|
||||
)
|
||||
.map_err(|_| ProtocolError::ParseError(ParseError::InvalidData))?;
|
||||
|
||||
verified = verifying_key.verify(certificate_data, &signature).is_ok();
|
||||
}
|
||||
_ => {
|
||||
error!(
|
||||
"Unsupported signature alg: {:?}",
|
||||
parsed_certificate.signature_algorithm
|
||||
);
|
||||
return Err(ProtocolError::InvalidSignatureScheme);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !verified {
|
||||
return Err(ProtocolError::InvalidCertificate);
|
||||
}
|
||||
|
||||
Ok(common_name)
|
||||
}
|
||||
171
src/parse_buffer.rs
Normal file
171
src/parse_buffer.rs
Normal 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
171
src/read_buffer.rs
Normal 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
215
src/record.rs
Normal 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
471
src/record_reader.rs
Normal 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
20
src/send_policy.rs
Normal 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
287
src/write_buffer.rs
Normal 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
373
tests/common/mod.rs
Normal 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
25
tests/fixtures/chain.pem
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBvjCCAWSgAwIBAgIUXXNrQ1jydxm9uhK7n0OmDUOPiCUwCgYIKoZIzj0EAwIw
|
||||
WzELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVN0YXRlMQ0wCwYDVQQHDARDaXR5MRAw
|
||||
DgYDVQQKDAdUZXN0T3JnMRswGQYDVQQDDBJUZXN0SW50ZXJtZWRpYXRlQ0EwHhcN
|
||||
MjYwMjIxMDgzODU4WhcNMzYwMjE5MDgzODU4WjAUMRIwEAYDVQQDDAlsb2NhbGhv
|
||||
c3QwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAStUYteJnlqMQQaVt8Y3GY92A4E
|
||||
/E4/9tB9Y5w9OniFssXUzubEhFyWRUMzF/0plx3Q1LJpEFi+PHOUOMZIUplHo00w
|
||||
SzAJBgNVHRMEAjAAMB0GA1UdDgQWBBTmipGtVpxknBpCkG5VwqycrALvuzAfBgNV
|
||||
HSMEGDAWgBTRmjFBdHm/8OSJJ4GuT+NLNY6LsDAKBggqhkjOPQQDAgNIADBFAiB2
|
||||
ZslSroEj+F/JaLSbNNMTTiRzIeP8jz6Lw+18bWo0agIhAK6gPc06G4rghYixJrWI
|
||||
348sgPEgLNyoDXPAIN+EabvM
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICAjCCAamgAwIBAgIUaNQANP0JbqEIRANchWyHObCtwycwCgYIKoZIzj0EAwIw
|
||||
UzELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVN0YXRlMQ0wCwYDVQQHDARDaXR5MRAw
|
||||
DgYDVQQKDAdUZXN0T3JnMRMwEQYDVQQDDApUZXN0Um9vdENBMB4XDTI2MDIyMTA4
|
||||
Mzg1N1oXDTM2MDIxOTA4Mzg1N1owWzELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVN0
|
||||
YXRlMQ0wCwYDVQQHDARDaXR5MRAwDgYDVQQKDAdUZXN0T3JnMRswGQYDVQQDDBJU
|
||||
ZXN0SW50ZXJtZWRpYXRlQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASrQQZ/
|
||||
9Om99T1hcWWBLRceIiPBy5y8AwzeG/30E+CipqpXcGfJJj6b9riPDueOnTbMhRH8
|
||||
BmNgJAZBXvKEQd4Po1MwUTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTRmjFB
|
||||
dHm/8OSJJ4GuT+NLNY6LsDAfBgNVHSMEGDAWgBRkhR8ezN8mKycuLoqDnfSBhZid
|
||||
mzAKBggqhkjOPQQDAgNHADBEAiB5ISXZkzQlbgZZ7q8vpWTb9Cfxx3rpKBg6IfIg
|
||||
NKm5lgIgJhsTXYO4tWz3UfWDXub2NtXoDGXwMvTsE4UXDRDO15E=
|
||||
-----END CERTIFICATE-----
|
||||
5
tests/fixtures/intermediate-ca-key.pem
vendored
Normal file
5
tests/fixtures/intermediate-ca-key.pem
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
-----BEGIN EC PRIVATE KEY-----
|
||||
MHcCAQEEIC94ig/ME0X0FOyu8Byk4tDkAdd/LmuUk3KjalEk/7ImoAoGCCqGSM49
|
||||
AwEHoUQDQgAEq0EGf/TpvfU9YXFlgS0XHiIjwcucvAMM3hv99BPgoqaqV3BnySY+
|
||||
m/a4jw7njp02zIUR/AZjYCQGQV7yhEHeDw==
|
||||
-----END EC PRIVATE KEY-----
|
||||
13
tests/fixtures/intermediate-ca.pem
vendored
Normal file
13
tests/fixtures/intermediate-ca.pem
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICAjCCAamgAwIBAgIUaNQANP0JbqEIRANchWyHObCtwycwCgYIKoZIzj0EAwIw
|
||||
UzELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVN0YXRlMQ0wCwYDVQQHDARDaXR5MRAw
|
||||
DgYDVQQKDAdUZXN0T3JnMRMwEQYDVQQDDApUZXN0Um9vdENBMB4XDTI2MDIyMTA4
|
||||
Mzg1N1oXDTM2MDIxOTA4Mzg1N1owWzELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVN0
|
||||
YXRlMQ0wCwYDVQQHDARDaXR5MRAwDgYDVQQKDAdUZXN0T3JnMRswGQYDVQQDDBJU
|
||||
ZXN0SW50ZXJtZWRpYXRlQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASrQQZ/
|
||||
9Om99T1hcWWBLRceIiPBy5y8AwzeG/30E+CipqpXcGfJJj6b9riPDueOnTbMhRH8
|
||||
BmNgJAZBXvKEQd4Po1MwUTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTRmjFB
|
||||
dHm/8OSJJ4GuT+NLNY6LsDAfBgNVHSMEGDAWgBRkhR8ezN8mKycuLoqDnfSBhZid
|
||||
mzAKBggqhkjOPQQDAgNHADBEAiB5ISXZkzQlbgZZ7q8vpWTb9Cfxx3rpKBg6IfIg
|
||||
NKm5lgIgJhsTXYO4tWz3UfWDXub2NtXoDGXwMvTsE4UXDRDO15E=
|
||||
-----END CERTIFICATE-----
|
||||
1
tests/fixtures/intermediate-ca.srl
vendored
Normal file
1
tests/fixtures/intermediate-ca.srl
vendored
Normal file
@@ -0,0 +1 @@
|
||||
5D736B4358F27719BDBA12BB9F43A60D438F8825
|
||||
5
tests/fixtures/intermediate-server-key.pem
vendored
Normal file
5
tests/fixtures/intermediate-server-key.pem
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
-----BEGIN EC PRIVATE KEY-----
|
||||
MHcCAQEEINVUisAFH2zQI1zMfTTQ5Imx89obUgjLnhR1LNKR7xCGoAoGCCqGSM49
|
||||
AwEHoUQDQgAErVGLXiZ5ajEEGlbfGNxmPdgOBPxOP/bQfWOcPTp4hbLF1M7mxIRc
|
||||
lkVDMxf9KZcd0NSyaRBYvjxzlDjGSFKZRw==
|
||||
-----END EC PRIVATE KEY-----
|
||||
12
tests/fixtures/intermediate-server.pem
vendored
Normal file
12
tests/fixtures/intermediate-server.pem
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBvjCCAWSgAwIBAgIUXXNrQ1jydxm9uhK7n0OmDUOPiCUwCgYIKoZIzj0EAwIw
|
||||
WzELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVN0YXRlMQ0wCwYDVQQHDARDaXR5MRAw
|
||||
DgYDVQQKDAdUZXN0T3JnMRswGQYDVQQDDBJUZXN0SW50ZXJtZWRpYXRlQ0EwHhcN
|
||||
MjYwMjIxMDgzODU4WhcNMzYwMjE5MDgzODU4WjAUMRIwEAYDVQQDDAlsb2NhbGhv
|
||||
c3QwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAStUYteJnlqMQQaVt8Y3GY92A4E
|
||||
/E4/9tB9Y5w9OniFssXUzubEhFyWRUMzF/0plx3Q1LJpEFi+PHOUOMZIUplHo00w
|
||||
SzAJBgNVHRMEAjAAMB0GA1UdDgQWBBTmipGtVpxknBpCkG5VwqycrALvuzAfBgNV
|
||||
HSMEGDAWgBTRmjFBdHm/8OSJJ4GuT+NLNY6LsDAKBggqhkjOPQQDAgNIADBFAiB2
|
||||
ZslSroEj+F/JaLSbNNMTTiRzIeP8jz6Lw+18bWo0agIhAK6gPc06G4rghYixJrWI
|
||||
348sgPEgLNyoDXPAIN+EabvM
|
||||
-----END CERTIFICATE-----
|
||||
5
tests/fixtures/leaf-client-key.pem
vendored
Normal file
5
tests/fixtures/leaf-client-key.pem
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
-----BEGIN EC PRIVATE KEY-----
|
||||
MHcCAQEEICyVJZJWFmk9fLUocyUgG1Vy3DN0tEK2C2akRjxu/9uUoAoGCCqGSM49
|
||||
AwEHoUQDQgAESmy9Dc5o67THYEEMOhf55AtrPfE/b9oECoHxsE08kAYiEhDNHF3b
|
||||
fHAsG/8o8K0+D/nZBiHSVz7qOJEAYtI38g==
|
||||
-----END EC PRIVATE KEY-----
|
||||
13
tests/fixtures/leaf-client.pem
vendored
Normal file
13
tests/fixtures/leaf-client.pem
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIB9DCCAZugAwIBAgIUaNQANP0JbqEIRANchWyHObCtwykwCgYIKoZIzj0EAwIw
|
||||
UzELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVN0YXRlMQ0wCwYDVQQHDARDaXR5MRAw
|
||||
DgYDVQQKDAdUZXN0T3JnMRMwEQYDVQQDDApUZXN0Um9vdENBMB4XDTI2MDIyMTA4
|
||||
Mzg1OFoXDTM2MDIxOTA4Mzg1OFowUzELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVN0
|
||||
YXRlMQ0wCwYDVQQHDARDaXR5MRAwDgYDVQQKDAdUZXN0T3JnMRMwEQYDVQQDDApU
|
||||
ZXN0Q2xpZW50MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAESmy9Dc5o67THYEEM
|
||||
Ohf55AtrPfE/b9oECoHxsE08kAYiEhDNHF3bfHAsG/8o8K0+D/nZBiHSVz7qOJEA
|
||||
YtI38qNNMEswCQYDVR0TBAIwADAdBgNVHQ4EFgQUmnmdVS/JiP0+xsswPzbU0Q5T
|
||||
qxYwHwYDVR0jBBgwFoAUZIUfHszfJisnLi6Kg530gYWYnZswCgYIKoZIzj0EAwID
|
||||
RwAwRAIgfuTkH23Somj9TonLSQScjTlX3wQOmnmK0tnopK81SdQCIHwFLvOtatXW
|
||||
oYwcJFlV9VnsdM1Kl+XqxWotTKNGgGHQ
|
||||
-----END CERTIFICATE-----
|
||||
5
tests/fixtures/leaf-server-key.pem
vendored
Normal file
5
tests/fixtures/leaf-server-key.pem
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
-----BEGIN EC PRIVATE KEY-----
|
||||
MHcCAQEEINmGVq3b5UlwxwkUApMi+qlCUXptr279ZWzkPuY06h8koAoGCCqGSM49
|
||||
AwEHoUQDQgAEzZQXKPiuG7YT3j8zFOz4SNJqoKWw8gKivlVqVpBNLcOvcfkrzFvd
|
||||
vvT4FiaTUWxjNsiXDT1mj35Gbeip1HLabw==
|
||||
-----END EC PRIVATE KEY-----
|
||||
12
tests/fixtures/leaf-server.pem
vendored
Normal file
12
tests/fixtures/leaf-server.pem
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBtjCCAVygAwIBAgIUaNQANP0JbqEIRANchWyHObCtwygwCgYIKoZIzj0EAwIw
|
||||
UzELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVN0YXRlMQ0wCwYDVQQHDARDaXR5MRAw
|
||||
DgYDVQQKDAdUZXN0T3JnMRMwEQYDVQQDDApUZXN0Um9vdENBMB4XDTI2MDIyMTA4
|
||||
Mzg1OFoXDTM2MDIxOTA4Mzg1OFowFDESMBAGA1UEAwwJbG9jYWxob3N0MFkwEwYH
|
||||
KoZIzj0CAQYIKoZIzj0DAQcDQgAEzZQXKPiuG7YT3j8zFOz4SNJqoKWw8gKivlVq
|
||||
VpBNLcOvcfkrzFvdvvT4FiaTUWxjNsiXDT1mj35Gbeip1HLab6NNMEswCQYDVR0T
|
||||
BAIwADAdBgNVHQ4EFgQUSvq+CjEju6CTX5ySu6YiKDTuCVswHwYDVR0jBBgwFoAU
|
||||
ZIUfHszfJisnLi6Kg530gYWYnZswCgYIKoZIzj0EAwIDSAAwRQIgWzKWT9G3NwTM
|
||||
wi0DJ9S+34oyL2WU7h+nPgJ0/ZFPR80CIQDx9pppEpipI83lB5A8wAB6Vi/Kugf9
|
||||
ZL3JtaYEl1gJzQ==
|
||||
-----END CERTIFICATE-----
|
||||
5
tests/fixtures/root-ca-key.pem
vendored
Normal file
5
tests/fixtures/root-ca-key.pem
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
-----BEGIN EC PRIVATE KEY-----
|
||||
MHcCAQEEILDaSJAPexCKKVxUwA2Obha08yO3FVDlzXwRPk+HDm7EoAoGCCqGSM49
|
||||
AwEHoUQDQgAEddxFuwZN+JLJFTiSKPb9DJPOdbLFMPzz66JkcxB28da2r2DqQKTK
|
||||
EmramKjIsI9WuXGY06XF1tYDxSfe7lTZZQ==
|
||||
-----END EC PRIVATE KEY-----
|
||||
13
tests/fixtures/root-ca.pem
vendored
Normal file
13
tests/fixtures/root-ca.pem
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIB+zCCAaGgAwIBAgIUfXCGhET97fQO0Q5r972wMTrwA3gwCgYIKoZIzj0EAwIw
|
||||
UzELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVN0YXRlMQ0wCwYDVQQHDARDaXR5MRAw
|
||||
DgYDVQQKDAdUZXN0T3JnMRMwEQYDVQQDDApUZXN0Um9vdENBMB4XDTI2MDIyMTA4
|
||||
Mzg1N1oXDTM2MDIxOTA4Mzg1N1owUzELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVN0
|
||||
YXRlMQ0wCwYDVQQHDARDaXR5MRAwDgYDVQQKDAdUZXN0T3JnMRMwEQYDVQQDDApU
|
||||
ZXN0Um9vdENBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEddxFuwZN+JLJFTiS
|
||||
KPb9DJPOdbLFMPzz66JkcxB28da2r2DqQKTKEmramKjIsI9WuXGY06XF1tYDxSfe
|
||||
7lTZZaNTMFEwHQYDVR0OBBYEFGSFHx7M3yYrJy4uioOd9IGFmJ2bMB8GA1UdIwQY
|
||||
MBaAFGSFHx7M3yYrJy4uioOd9IGFmJ2bMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZI
|
||||
zj0EAwIDSAAwRQIhAJrkXZrhS2zAexShCh/Dh47d1LtOvMiq9uqbgP7xI10pAiAF
|
||||
fMNptczj+DpN9nRTlELt8FxC8rTxGxjC/tL0aOomDw==
|
||||
-----END CERTIFICATE-----
|
||||
1
tests/fixtures/root-ca.srl
vendored
Normal file
1
tests/fixtures/root-ca.srl
vendored
Normal file
@@ -0,0 +1 @@
|
||||
68D40034FD096EA10844035C856C8739B0ADC329
|
||||
28
tests/fixtures/rsa-leaf-client-key.pem
vendored
Normal file
28
tests/fixtures/rsa-leaf-client-key.pem
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCPYjGfNwKAhIsD
|
||||
Fepzgc57jcHso5dO/hfs5k4JTRhACoGDA+6QFm9KAb8YC1NFbcJsExJAfvVlSr3S
|
||||
82WJqvQOH5LQ7TXPRaG0tAIrWoB76jYb+TQhmi6o0WepOxqDR6w64O3bM6geFR7A
|
||||
zbhl2bzOYlZwF+tdCn3L41LpjhzcEVUM8M/IUaCbzexq/jUsRUpFdpYSvEFCKMSR
|
||||
BvbV/I0/zMWo+jZXG/YQqbOhNmgQugypPlVIYQLtAVye+92qpajLFlg5RCvXmAtE
|
||||
0KGl6nhHPNutqOUJGgmPVmx0fAj1IIjTRws++kIuRYgncxS9qtcblFVy4Ji0NdKu
|
||||
+Z6LeyLZAgMBAAECggEAA8wP8mtK9MUSFG+sLTxxfretka09IaqR128Q/2Ko+5Qe
|
||||
2MdYk+eKD0joONvA/RaKd0FdX3muUHWzSbBuf7dT57mT/wDmgonef/3jL56qPjMt
|
||||
rsqyu1ytObhrSR+oeOudSg/huXp3bmVQluC70JGewwkZDNiPc0Qo5OJo1mY6wxex
|
||||
dps0A4fCmZtB9ZgmaejCiQaw7udgcpOGo5xdfVrgAtAqCBSaWOoZWNHzjsntSrHO
|
||||
fRDa4ilLhwuWwrkIsg5om/zasfTDLM70YyXq4s0ccYyCEPkquPgN/mII/lK+78U6
|
||||
AXYfsvSfyVNRoMzMXdTKC+G1QlYDGWeqAoVCS9TyawKBgQDC1ihsohh6PSjYHS7Y
|
||||
+QODmDXEbwXU/V5iEnFKf4/9+QSW+g12mbckjHS+njvNV4wzdx/ph+bNDAdI8bQU
|
||||
Dr5bJXtGbHy1r8/PtNUP4SJmoRvmMVrr34g7QjaHYJ8Jy2m9JHpuRnr38pzg6vP6
|
||||
deDtxupT2xPTqV7ivwUDLK9SKwKBgQC8ZRGp2Ny17cH9aKnyjibXLKvtITdDc7H9
|
||||
kqs+LM3bcEXpXs0o5ED7J0uhpbNkpplV95Lcm7zdDAkHHiek4yWBUohtgqfbZ76d
|
||||
vA+BtnbjDdCyH8B5mN5IDc1zEIJZB1x9CpIhIsDRrBCH/NXl3ldJLBF/ZCJTXWCf
|
||||
nqRgwllRCwKBgQC4k7W8JFvYAfSduBfXiSAhHKNjMmJuApHViu80ymAZFD2a4cy7
|
||||
XKg5wa4fnzu8LoIth17+F7c47XpBSml0zvra0klU0BXc8W+HsCJgZsH2RA5wJrWh
|
||||
2yPuL64E1i4UU1Yaz2IE8lQwbPDdyvfTgLTTzavUQSkpTb0MRjZzaXO1/QKBgG8X
|
||||
0mCr5wrJF1nNfFnyBWlhiEifC62U7eKvuJdDaGj8Pd2t76EraD4yH+FEixLRQx50
|
||||
jX/VvntC+5fc6lfLMnSeLKEXKNCyzq7JFQPSiyy9GtHO83tA7+LhcMNnetXxB1Md
|
||||
BqrPiZCavGzUZXXVtPcLK45JiAxMxguaSyhbsrudAoGAMjK2bcu7buE13KM3wbte
|
||||
lysax9gxKKNlqFX68/qx4SUrHxhz5ugF95zlkiDciq/jG5TH1IGNX3q0C7Eid4FY
|
||||
0XHwJAqBG5wFWnjx3U+Ozk2QYVXK6rfyk7drAWO2XlsAZVHwf6DlVhYCOT+a9O6W
|
||||
60KKqnpWzmBnrc2fzOJgJTM=
|
||||
-----END PRIVATE KEY-----
|
||||
21
tests/fixtures/rsa-leaf-client.pem
vendored
Normal file
21
tests/fixtures/rsa-leaf-client.pem
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDhDCCAmygAwIBAgIUPL2aFs+m1HMJy9P1zeCRHsgB/dUwDQYJKoZIhvcNAQEL
|
||||
BQAwVjELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVN0YXRlMQ0wCwYDVQQHDARDaXR5
|
||||
MRAwDgYDVQQKDAdUZXN0T3JnMRYwFAYDVQQDDA1UZXN0UnNhUm9vdENBMB4XDTI2
|
||||
MDIyMTA4MzkwMFoXDTM2MDIxOTA4MzkwMFowUzELMAkGA1UEBhMCVVMxDjAMBgNV
|
||||
BAgMBVN0YXRlMQ0wCwYDVQQHDARDaXR5MRAwDgYDVQQKDAdUZXN0T3JnMRMwEQYD
|
||||
VQQDDApUZXN0Q2xpZW50MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
|
||||
j2IxnzcCgISLAxXqc4HOe43B7KOXTv4X7OZOCU0YQAqBgwPukBZvSgG/GAtTRW3C
|
||||
bBMSQH71ZUq90vNliar0Dh+S0O01z0WhtLQCK1qAe+o2G/k0IZouqNFnqTsag0es
|
||||
OuDt2zOoHhUewM24Zdm8zmJWcBfrXQp9y+NS6Y4c3BFVDPDPyFGgm83sav41LEVK
|
||||
RXaWErxBQijEkQb21fyNP8zFqPo2Vxv2EKmzoTZoELoMqT5VSGEC7QFcnvvdqqWo
|
||||
yxZYOUQr15gLRNChpep4RzzbrajlCRoJj1ZsdHwI9SCI00cLPvpCLkWIJ3MUvarX
|
||||
G5RVcuCYtDXSrvmei3si2QIDAQABo00wSzAJBgNVHRMEAjAAMB0GA1UdDgQWBBQV
|
||||
mkgyHQUCPp0zo8903lXsOeKE5DAfBgNVHSMEGDAWgBRHYiOHw458Ka7uZ2w7wkbk
|
||||
WsD5MTANBgkqhkiG9w0BAQsFAAOCAQEAhrtvIHbrRPFc5QbaT/eelrAqWkhwpxxL
|
||||
X/gVwbqtP2+uU2xUVGsfRJV5EI27kzyysq+ySdQLq3j8oe0X6poqDJiE8/zUg7KO
|
||||
OYCkH/UGhr+L0hk6Ibvc7izu/LSFT3K5Mo78aB/C4cjDrLgA/cbFKT+OVQW4iw49
|
||||
2Pgw7+vuNLzYJSu2m5XzeR07FMcTpf2EAOGseDde6zFwduUBJtAbGj4YHkTQp+MD
|
||||
dEE5ymyXl4+ehcafk0g4ZZARN5qVPMOqjRASWRhuZI+36Ihc6KBL22B1KzexyoYx
|
||||
2sEyd04NU1PX9i5Zn7JXNbHIESNCkLk4IspSnk6DBEpNpQgEW6l3Cw==
|
||||
-----END CERTIFICATE-----
|
||||
28
tests/fixtures/rsa-leaf-server-key.pem
vendored
Normal file
28
tests/fixtures/rsa-leaf-server-key.pem
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCjK6TkX5PuTApT
|
||||
uL5Z0p6Iofo2zXHOPJVcDDeKzVW83KRwK9oFhjIT9HHKRFkar5ezKQuKY+xtsvmz
|
||||
JX2STGwg1itPIcHpngwvmtAy4/2lBB0wL1wxc+2sqpWYGmqLm5iqiuaVQo2n0uiL
|
||||
Nwd4aZzpjtRXWjyr9sjfBcXu6/m3bN6nYkmh613qu9BAo4L1YPy+NFtVhKkBzD9Q
|
||||
qPDZv7mVoqe+Y3Cyf8873+5T6PVrpV7+YCpFJA46fsA/UrmsyQfQPCOL5fbbLXqs
|
||||
V9yMf2/7JCCbrO+6mpIJaEWnciS51sEIh15oqH7IiF5+6az/Oe11X/LbWHkLRUzr
|
||||
k4uYGV61AgMBAAECggEABZz2Pq71CVEPV+L2lWNz9bJQx7rYi+Y0oyZ+cKVwqh8S
|
||||
/xLbHK6JoXsawQEJ6auZtd2XGosmcn2iLmH/SF2dqKGFeuLn50/7DlYujFmge6FB
|
||||
Gcu/Sao5xmNV4xYhjSzsmw1NMlxIQDo2qrdZZ/CGJ9i0gE7H4IiMT3PE49u1SvSD
|
||||
OkKmcZW3suPYyZGPZxl2h37cARIjqsmw9D3OmDxUA0hfZg6sGoABZeNrGi7IScZe
|
||||
D26DAhPhIWa0sDVUTfCZkezXtH+mFptwN0zSIbMRWozx49jEZF9hBF4QBmnNnUPm
|
||||
VTs+Z2AzeRQpUZPQ8rzWAbcf1oCwhPqSknXlcsxJnwKBgQDartbfUMkJ2LgBLTkl
|
||||
BSPxI2ngvb0ySzdzxfsPKZZe4EAz4KFl+1DqQ4EZifhdhJiqV+PicNPKNFo2AK6S
|
||||
3Wd0X4GAHZDAamiJces1WxqHigHXOrdVqu8/YSpVPEk/jdSYgHqwG7ji0IKEVtpw
|
||||
EZJ+LX6BDOvDpbedBGcZT7Mm/wKBgQC/A75DSRxHK48AeuCiG4Q6G72IvOTYqK/2
|
||||
hz45HgfUI22d41Bb/48ybH8xt/SDoJLibx8P88ej/81vvYg3eLIyH+QR0f18Ia2g
|
||||
sZd4Lj+2zo1fJbmQKptTI+O8PqrZzx9UZ2UfGXxQ0sNSuF37hFw8hI8ALzk1+UW/
|
||||
OoX90IUOSwKBgA0FJ+n352BctOftB1/65F7xGta0tVUPQWf1O7N1aGyRsYDlOPbX
|
||||
dcPc7QzWOCFpSaWqwfizewipAU4B0GMSJ5y4Kv+zwvCR5VN5ouV0XSoAv4dPCadi
|
||||
HAiMAnc8tafBDA1gaO2fWOy4OW0jtrHBehVlJAkO+eKWNU51+qV5J1OFAoGANS+N
|
||||
op6QySBPyQpt0bVns+ZVd+VgsxMFK9esc6rw8xiKRRQuI++cp6WeJPHbm2ryeyoF
|
||||
tCNkyz1Grn5Pl2J7+4j1sCCQPCgEeGH6kvQNuZD5vCx85q92YEf1+UxZthv91TqU
|
||||
5XvrKXYF/NppEMdiB1fBmYOMooKt8PkSpgGRitECgYAkF8zcg/zNCueYezn6iW7q
|
||||
PeO30XvLRnDbh4DnhkwoLbDR3vaxmmbphCzLe3tX8TDAA13QeWON/8JL7QU35HcY
|
||||
lBlVJo2Mr0crROKElPicXXXSZWDxXFjcdVj+Oa4ni5wwKsppE18HNo9Vlj+RNgjw
|
||||
dOfKqK1UP8tgaDa1W9ERDQ==
|
||||
-----END PRIVATE KEY-----
|
||||
20
tests/fixtures/rsa-leaf-server.pem
vendored
Normal file
20
tests/fixtures/rsa-leaf-server.pem
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDRTCCAi2gAwIBAgIUPL2aFs+m1HMJy9P1zeCRHsgB/dQwDQYJKoZIhvcNAQEL
|
||||
BQAwVjELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVN0YXRlMQ0wCwYDVQQHDARDaXR5
|
||||
MRAwDgYDVQQKDAdUZXN0T3JnMRYwFAYDVQQDDA1UZXN0UnNhUm9vdENBMB4XDTI2
|
||||
MDIyMTA4Mzg1OVoXDTM2MDIxOTA4Mzg1OVowFDESMBAGA1UEAwwJbG9jYWxob3N0
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoyuk5F+T7kwKU7i+WdKe
|
||||
iKH6Ns1xzjyVXAw3is1VvNykcCvaBYYyE/RxykRZGq+XsykLimPsbbL5syV9kkxs
|
||||
INYrTyHB6Z4ML5rQMuP9pQQdMC9cMXPtrKqVmBpqi5uYqormlUKNp9LoizcHeGmc
|
||||
6Y7UV1o8q/bI3wXF7uv5t2zep2JJoetd6rvQQKOC9WD8vjRbVYSpAcw/UKjw2b+5
|
||||
laKnvmNwsn/PO9/uU+j1a6Ve/mAqRSQOOn7AP1K5rMkH0Dwji+X22y16rFfcjH9v
|
||||
+yQgm6zvupqSCWhFp3IkudbBCIdeaKh+yIhefums/zntdV/y21h5C0VM65OLmBle
|
||||
tQIDAQABo00wSzAJBgNVHRMEAjAAMB0GA1UdDgQWBBRsFCi7ZWnzarIhJkQzwWQm
|
||||
7r27WTAfBgNVHSMEGDAWgBRHYiOHw458Ka7uZ2w7wkbkWsD5MTANBgkqhkiG9w0B
|
||||
AQsFAAOCAQEAOuP/m6+3/XlGmospEkD3K8I3/zTNqYUQAeFaPcivdAO5WgqpBN7G
|
||||
JCPVEY12XGHd8KNYe6h7J0w2weCAUONiaU/2bsXCcrOKNA2c4no1DY+NJR7YKJmi
|
||||
QKHNXpw3qcDlqFzkz8/4GiIEf+NA+dtS6476iTr1d1OnJwYm9yjgVeWod/fp0kU/
|
||||
ECF52TIbB3qJKKYiZwhdEQm1ddjfMJAaM+kRkCf+53UZG5R/Z9ApX1DHALbIPlNe
|
||||
49s7ulKuNJs1ZfhjcgruJn4b6XTgsTZ/1KJv+Wgd31G6at9I/gRmRvGBOwUjAkyH
|
||||
O3ZKdDXZfHvhfoHiiO9Bjs5Hxt0F7xJ88g==
|
||||
-----END CERTIFICATE-----
|
||||
28
tests/fixtures/rsa-root-ca-key.pem
vendored
Normal file
28
tests/fixtures/rsa-root-ca-key.pem
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEugIBADANBgkqhkiG9w0BAQEFAASCBKQwggSgAgEAAoIBAQCZKwbc7+1RFLyM
|
||||
7sWOdHLHfGwyGQnD5zkYyGnMrHuewFgua6D52zoI3XmTEL4tdKgHLFIjOI3z/kQ3
|
||||
78BX727vsGj+/uMUo64cnC6xJIOQKPUaNQPvHQfiUjwxqP3rV0Bf+bVj5D14rFFw
|
||||
ghWnp4KJ13dw50qjV2hPcGz2rf7HJtv/rNiTAoiVt5nc6NhMKFzJXPOMIzYAgTDL
|
||||
ISRtjw6LR2LOR+guSMvpEPe+Kgle44OSHjz+SLABNE9oXIdqzrmE1AStmMoNl9n9
|
||||
FLQ3ci2yaEGMshYBB/AU3cwAXOgQvObDusmPp+hbcsGglauem/7WTZSWUXhdJG4M
|
||||
mTzTf0GJAgMBAAECgf85I1PsF5TwKkwsRuZrvgUTZdb22WBLNHaYSCsvryhukFJU
|
||||
/tGOY7nClNxFgHlxe5MzGdWKTg6mdrP8KfQW2bsIr0Z72ZncmTLaeWjxrC1oGd9V
|
||||
Z3GQQcQvKX5LCD+xC1t4ci64lOxZl+7Jib2KTXLk+PwVojK1vGWtPMNpQn9IyyIz
|
||||
b13zG/4cN7tHECjvNa3Xp0mJ9p7y34LiAl7YD+FGh8DHt+LG5Tcbr8fV3fzafNZK
|
||||
S1cX8zBJlqPcmMuPxWwTEinW8Lo4A1P1E+8adxzDmIoD4VR3CR5zruzwGbAQv8GV
|
||||
EGAQzbRSCNXprRi0vmDs2sJm465U5bPGNCimt7cCgYEA09KbmFD/GcUbTC9v/uvC
|
||||
7xnbyMljwDFooxwYPLqe5FdCiBbLziY4U9D21BSvxZxDi11Ltbem1/kfj1Fpb5Xq
|
||||
qP7bMqcCO/pwzXvb7px3QFBvp+jPDjKr3I3/c+rXu20Ua2sQl+8AyTqm1QpmP1w2
|
||||
EvXkXFaH7aJ7gf8hGcrX8IcCgYEAuRzMtoPAkmou0mGf4sPt1hcoW9YuRyI2Jcsg
|
||||
WgTg0i2O1IX4TlEHG5pgIkM3uR3VEkSN0qJWB6V+1FP4CKnSrxgVq2V4422mQNtG
|
||||
d2Gd3gZFhpQE9B3McAq/2G3pXHI8ZQNd6Oc1z7ecmEnBff/PlE2xGhMoe2Srgwxh
|
||||
h/IzEW8CgYBg57LjJfrusSvh2Lnl57nQZQYVf3yxCmmSZWH5Nm9Gi10WoUcv0nBm
|
||||
d+zT7XrUbr6/3Tirs48SsxfrGxWfRPiLw7xIGft9sP82InnlWZN8ys+qA2nmVuwl
|
||||
BJlfUIrNZgO3eM2olGDJrplwUUehqO/cEL4eOEALSRAz0qI0CIZttQKBgFRV3KZi
|
||||
jD+ohMBwnclQfnEFh+ufPuJFoenCC3E3u73F58bHaoMzw0s+IAI8IY0DHGoANaT7
|
||||
NLqzGX9e6if4RvZiwKyfxF3JPO9bd1U4chYPQWm40jDtypBZNWJDYQgvO3jB+ez8
|
||||
ObXy7zMqly7ydv4YD1HT3KOrD8DaySyImd+dAoGAI8QU3UEdge0cY+UtD1vHLUb/
|
||||
Z6rfcnoDL17azt33CooNH3lCVw+IqgE0t63lO6W/3DCi8A3D9PJsopnAdPK7rvdT
|
||||
UrvmtretbgxNRGCBC8T2fVJV6Q9DGqmjMol2/rlNiYQNhgK8bEiAmPYw7GXH0LjO
|
||||
PEayI0HRcYAWrpEwwvs=
|
||||
-----END PRIVATE KEY-----
|
||||
22
tests/fixtures/rsa-root-ca.pem
vendored
Normal file
22
tests/fixtures/rsa-root-ca.pem
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDjTCCAnWgAwIBAgIUWjGv/hbVIjFa4t9qWs3sd5RCzDQwDQYJKoZIhvcNAQEL
|
||||
BQAwVjELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVN0YXRlMQ0wCwYDVQQHDARDaXR5
|
||||
MRAwDgYDVQQKDAdUZXN0T3JnMRYwFAYDVQQDDA1UZXN0UnNhUm9vdENBMB4XDTI2
|
||||
MDIyMTA4Mzg1OVoXDTM2MDIxOTA4Mzg1OVowVjELMAkGA1UEBhMCVVMxDjAMBgNV
|
||||
BAgMBVN0YXRlMQ0wCwYDVQQHDARDaXR5MRAwDgYDVQQKDAdUZXN0T3JnMRYwFAYD
|
||||
VQQDDA1UZXN0UnNhUm9vdENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
|
||||
AQEAmSsG3O/tURS8jO7FjnRyx3xsMhkJw+c5GMhpzKx7nsBYLmug+ds6CN15kxC+
|
||||
LXSoByxSIziN8/5EN+/AV+9u77Bo/v7jFKOuHJwusSSDkCj1GjUD7x0H4lI8Maj9
|
||||
61dAX/m1Y+Q9eKxRcIIVp6eCidd3cOdKo1doT3Bs9q3+xybb/6zYkwKIlbeZ3OjY
|
||||
TChcyVzzjCM2AIEwyyEkbY8Oi0dizkfoLkjL6RD3vioJXuODkh48/kiwATRPaFyH
|
||||
as65hNQErZjKDZfZ/RS0N3ItsmhBjLIWAQfwFN3MAFzoELzmw7rJj6foW3LBoJWr
|
||||
npv+1k2UllF4XSRuDJk8039BiQIDAQABo1MwUTAfBgNVHSMEGDAWgBRHYiOHw458
|
||||
Ka7uZ2w7wkbkWsD5MTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRHYiOHw458
|
||||
Ka7uZ2w7wkbkWsD5MTANBgkqhkiG9w0BAQsFAAOCAQEAiHXmwDY+9t5cnFZcuxJl
|
||||
3lQSOpHQMmSYe7FiTpI+GglGW+ugahdyqc/c2GbNKqB1oxGA+mEv+KeKYgtTfC8J
|
||||
gTtBLVjhquUOY4K6AkU69i5e7g8/41TFuAF0nt8LnkAYZmo12kbYjX1gKTfkl9wl
|
||||
hMY+OSnoIoxBrw1cmqRtOu+Hn2wY9dVCQJVIgc89WuQr8USn9JXzq1bUCzOk6a8/
|
||||
dR6ZEPDVPo6RjEXuLNUuTOoTH1wCG3P1tHUs4Cwapr1EhgrEs7DC4uvB+9Y6EXqI
|
||||
OLqn0V3FBsBPN19bdLWTtaOm5C0xbNmiXfJPBH9nF38Hpj4tk72GfxOzKI4J+rG7
|
||||
Kg==
|
||||
-----END CERTIFICATE-----
|
||||
1
tests/fixtures/rsa-root-ca.srl
vendored
Normal file
1
tests/fixtures/rsa-root-ca.srl
vendored
Normal file
@@ -0,0 +1 @@
|
||||
3CBD9A16CFA6D47309CBD3F5CDE0911EC801FDD5
|
||||
70
tests/fixtures/setup_fixtures.sh
vendored
Executable file
70
tests/fixtures/setup_fixtures.sh
vendored
Executable file
@@ -0,0 +1,70 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
SUBJ_CA="/C=US/ST=State/L=City/O=TestOrg/CN=TestRootCA"
|
||||
SUBJ_IM="/C=US/ST=State/L=City/O=TestOrg/CN=TestIntermediateCA"
|
||||
SUBJ_SRV="/CN=localhost"
|
||||
SUBJ_CLI="/C=US/ST=State/L=City/O=TestOrg/CN=TestClient"
|
||||
SUBJ_RSA_CA="/C=US/ST=State/L=City/O=TestOrg/CN=TestRsaRootCA"
|
||||
|
||||
EXT_CA="basicConstraints=critical,CA:TRUE\nsubjectKeyIdentifier=hash\nauthorityKeyIdentifier=keyid:always"
|
||||
EXT_LEAF="basicConstraints=CA:FALSE\nsubjectKeyIdentifier=hash\nauthorityKeyIdentifier=keyid,issuer"
|
||||
|
||||
# Root CA
|
||||
openssl ecparam -name prime256v1 -genkey -noout -out root-ca-key.pem
|
||||
openssl req -new -x509 -sha256 -key root-ca-key.pem -days 3650 -out root-ca.pem -subj "$SUBJ_CA"
|
||||
|
||||
# Intermediate CA
|
||||
openssl ecparam -name prime256v1 -genkey -noout -out intermediate-ca-key.pem
|
||||
openssl req -new -sha256 -key intermediate-ca-key.pem -out _im.csr -subj "$SUBJ_IM"
|
||||
openssl x509 -req -in _im.csr -CA root-ca.pem -CAkey root-ca-key.pem \
|
||||
-CAcreateserial -out intermediate-ca.pem -days 3650 -sha256 \
|
||||
-extfile <(printf "$EXT_CA")
|
||||
rm _im.csr
|
||||
|
||||
# Server leaf cert (signed by root CA)
|
||||
openssl ecparam -name prime256v1 -genkey -noout -out leaf-server-key.pem
|
||||
openssl req -new -sha256 -key leaf-server-key.pem -out _srv.csr -subj "$SUBJ_SRV"
|
||||
openssl x509 -req -in _srv.csr -CA root-ca.pem -CAkey root-ca-key.pem \
|
||||
-CAcreateserial -out leaf-server.pem -days 3650 -sha256 \
|
||||
-extfile <(printf "$EXT_LEAF")
|
||||
rm _srv.csr
|
||||
|
||||
# Client leaf cert (signed by root CA)
|
||||
openssl ecparam -name prime256v1 -genkey -noout -out leaf-client-key.pem
|
||||
openssl req -new -sha256 -key leaf-client-key.pem -out _cli.csr -subj "$SUBJ_CLI"
|
||||
openssl x509 -req -in _cli.csr -CA root-ca.pem -CAkey root-ca-key.pem \
|
||||
-CAcreateserial -out leaf-client.pem -days 3650 -sha256 \
|
||||
-extfile <(printf "$EXT_LEAF")
|
||||
rm _cli.csr
|
||||
|
||||
# Intermediate server cert + chain
|
||||
openssl ecparam -name prime256v1 -genkey -noout -out intermediate-server-key.pem
|
||||
openssl req -new -sha256 -key intermediate-server-key.pem -out _imsrv.csr -subj "$SUBJ_SRV"
|
||||
openssl x509 -req -in _imsrv.csr -CA intermediate-ca.pem -CAkey intermediate-ca-key.pem \
|
||||
-CAcreateserial -out intermediate-server.pem -days 3650 -sha256 \
|
||||
-extfile <(printf "$EXT_LEAF")
|
||||
rm _imsrv.csr
|
||||
cat intermediate-server.pem intermediate-ca.pem > chain.pem
|
||||
|
||||
# RSA root CA
|
||||
openssl req -x509 -newkey rsa:2048 -keyout rsa-root-ca-key.pem -nodes \
|
||||
-out rsa-root-ca.pem -sha256 -days 3650 -subj "$SUBJ_RSA_CA" \
|
||||
-addext "basicConstraints=critical,CA:TRUE" \
|
||||
-addext "subjectKeyIdentifier=hash"
|
||||
|
||||
# RSA server cert
|
||||
openssl req -newkey rsa:2048 -keyout rsa-leaf-server-key.pem -nodes \
|
||||
-out _rsasrv.csr -sha256 -subj "$SUBJ_SRV"
|
||||
openssl x509 -req -CA rsa-root-ca.pem -CAkey rsa-root-ca-key.pem \
|
||||
-in _rsasrv.csr -out rsa-leaf-server.pem -days 3650 -sha256 -CAcreateserial \
|
||||
-extfile <(printf "$EXT_LEAF")
|
||||
rm _rsasrv.csr
|
||||
|
||||
# RSA client cert
|
||||
openssl req -newkey rsa:2048 -keyout rsa-leaf-client-key.pem -nodes \
|
||||
-out _rsacli.csr -sha256 -subj "$SUBJ_CLI"
|
||||
openssl x509 -req -CA rsa-root-ca.pem -CAkey rsa-root-ca-key.pem \
|
||||
-in _rsacli.csr -out rsa-leaf-client.pem -days 3650 -sha256 -CAcreateserial \
|
||||
-extfile <(printf "$EXT_LEAF")
|
||||
rm _rsacli.csr
|
||||
1326
tests/integration.rs
Normal file
1326
tests/integration.rs
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user