mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-10-28 00:10:50 +08:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b5d13ee16e | ||
|
|
66a13d3d8c | ||
|
|
8b4ca6d080 | ||
|
|
9a1f4ff4b5 | ||
|
|
3b8a4a6f34 | ||
|
|
4f59e78179 | ||
|
|
2f60e61dd5 | ||
|
|
318b58b26a | ||
|
|
48cd06c7a4 | ||
|
|
99da8a3c61 | ||
|
|
1e0b0407d3 | ||
|
|
7e6dc91095 | ||
|
|
d854b6a220 | ||
|
|
f8fb64ad8c | ||
|
|
0e8c323659 | ||
|
|
0956da4ae2 | ||
|
|
03cff9d9a8 | ||
|
|
0a041c2caa | ||
|
|
83cbfb6924 | ||
|
|
6010c39cd8 | ||
|
|
4cf3f4e252 | ||
|
|
3d3110c3d7 | ||
|
|
22912707b5 | ||
|
|
e85f0a9bfe |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,6 +1,5 @@
|
|||||||
# Rust
|
# Rust
|
||||||
target/
|
target/
|
||||||
Cargo.lock
|
|
||||||
|
|
||||||
# IDE
|
# IDE
|
||||||
.idea/
|
.idea/
|
||||||
|
|||||||
5652
Cargo.lock
generated
Normal file
5652
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
20
Cargo.toml
20
Cargo.toml
@@ -1,10 +1,10 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "one-kvm"
|
name = "one-kvm"
|
||||||
version = "0.2.7"
|
version = "0.2.8"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
authors = ["SilentWind"]
|
authors = ["SilentWind"]
|
||||||
description = "A open and lightweight IP-KVM solution written in Rust"
|
description = "A open and lightweight IP-KVM solution written in Rust"
|
||||||
license = "GPL-2.0"
|
license = "AGPL-3.0-only"
|
||||||
repository = "https://github.com/mofeng-git/One-KVM"
|
repository = "https://github.com/mofeng-git/One-KVM"
|
||||||
keywords = ["kvm", "ipkvm", "remote-management", "embedded"]
|
keywords = ["kvm", "ipkvm", "remote-management", "embedded"]
|
||||||
categories = ["embedded", "network-programming"]
|
categories = ["embedded", "network-programming"]
|
||||||
@@ -35,7 +35,7 @@ desktop = [
|
|||||||
"dep:urlencoding",
|
"dep:urlencoding",
|
||||||
"dep:rust-embed",
|
"dep:rust-embed",
|
||||||
"dep:mime_guess",
|
"dep:mime_guess",
|
||||||
"dep:rustls",
|
"dep:openssl",
|
||||||
"dep:rcgen",
|
"dep:rcgen",
|
||||||
"dep:axum-server",
|
"dep:axum-server",
|
||||||
"dep:clap",
|
"dep:clap",
|
||||||
@@ -56,7 +56,6 @@ desktop = [
|
|||||||
"dep:serialport",
|
"dep:serialport",
|
||||||
"dep:async-trait",
|
"dep:async-trait",
|
||||||
"dep:libc",
|
"dep:libc",
|
||||||
"dep:libloading",
|
|
||||||
"dep:ventoy-img",
|
"dep:ventoy-img",
|
||||||
"dep:protobuf",
|
"dep:protobuf",
|
||||||
"dep:sodiumoxide",
|
"dep:sodiumoxide",
|
||||||
@@ -111,8 +110,7 @@ base64 = { version = "0.22", optional = true }
|
|||||||
tempfile = { version = "3", optional = true }
|
tempfile = { version = "3", optional = true }
|
||||||
|
|
||||||
# HTTP client (for URL downloads)
|
# HTTP client (for URL downloads)
|
||||||
# Use rustls by default, but allow native-tls for systems with older GLIBC
|
reqwest = { version = "0.13", features = ["stream", "native-tls-vendored", "json"], default-features = false, optional = true }
|
||||||
reqwest = { version = "0.13", features = ["stream", "rustls-no-provider", "json"], default-features = false, optional = true }
|
|
||||||
urlencoding = { version = "2", optional = true }
|
urlencoding = { version = "2", optional = true }
|
||||||
|
|
||||||
# Static file embedding
|
# Static file embedding
|
||||||
@@ -120,9 +118,9 @@ rust-embed = { version = "8", features = ["compression", "debug-embed"], optiona
|
|||||||
mime_guess = { version = "2", optional = true }
|
mime_guess = { version = "2", optional = true }
|
||||||
|
|
||||||
# TLS/HTTPS
|
# TLS/HTTPS
|
||||||
rustls = { version = "0.23", default-features = false, features = ["ring", "std", "tls12"], optional = true }
|
openssl = { version = "0.10", features = ["vendored"], optional = true }
|
||||||
rcgen = { version = "0.14", optional = true }
|
rcgen = { version = "0.14", optional = true }
|
||||||
axum-server = { version = "0.8", features = ["tls-rustls-no-provider"], optional = true }
|
axum-server = { version = "0.8", features = ["tls-openssl"], optional = true }
|
||||||
|
|
||||||
# CLI argument parsing
|
# CLI argument parsing
|
||||||
clap = { version = "4", features = ["derive"], optional = true }
|
clap = { version = "4", features = ["derive"], optional = true }
|
||||||
@@ -149,7 +147,7 @@ parking_lot = { version = "0.12", optional = true }
|
|||||||
arc-swap = { version = "1.8", optional = true }
|
arc-swap = { version = "1.8", optional = true }
|
||||||
|
|
||||||
# WebRTC
|
# WebRTC
|
||||||
webrtc = { version = "0.17", optional = true }
|
webrtc = { version = "0.17", features = ["vendored-openssl"], optional = true }
|
||||||
rtp = { version = "0.17", optional = true }
|
rtp = { version = "0.17", optional = true }
|
||||||
rtsp-types = { version = "0.1", optional = true }
|
rtsp-types = { version = "0.1", optional = true }
|
||||||
sdp-types = { version = "0.1", optional = true }
|
sdp-types = { version = "0.1", optional = true }
|
||||||
@@ -158,7 +156,6 @@ sdp-types = { version = "0.1", optional = true }
|
|||||||
serialport = { version = "4", optional = true }
|
serialport = { version = "4", optional = true }
|
||||||
async-trait = { version = "0.1", optional = true }
|
async-trait = { version = "0.1", optional = true }
|
||||||
libc = { version = "0.2", optional = true }
|
libc = { version = "0.2", optional = true }
|
||||||
libloading = { version = "0.8", optional = true }
|
|
||||||
|
|
||||||
# Ventoy bootable image support
|
# Ventoy bootable image support
|
||||||
ventoy-img = { path = "libs/ventoy-img-rs", optional = true }
|
ventoy-img = { path = "libs/ventoy-img-rs", optional = true }
|
||||||
@@ -208,6 +205,9 @@ windows-sys = { version = "0.61", features = [
|
|||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
protobuf-codegen = "3.7"
|
protobuf-codegen = "3.7"
|
||||||
|
|
||||||
|
[patch.crates-io]
|
||||||
|
dtls = { path = "libs/dtls" }
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
opt-level = 3
|
opt-level = 3
|
||||||
lto = true
|
lto = true
|
||||||
|
|||||||
141
LICENSE
141
LICENSE
@@ -1,5 +1,5 @@
|
|||||||
GNU GENERAL PUBLIC LICENSE
|
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||||
Version 3, 29 June 2007
|
Version 3, 19 November 2007
|
||||||
|
|
||||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||||
Everyone is permitted to copy and distribute verbatim copies
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
@@ -7,17 +7,15 @@
|
|||||||
|
|
||||||
Preamble
|
Preamble
|
||||||
|
|
||||||
The GNU General Public License is a free, copyleft license for
|
The GNU Affero General Public License is a free, copyleft license for
|
||||||
software and other kinds of works.
|
software and other kinds of works, specifically designed to ensure
|
||||||
|
cooperation with the community in the case of network server software.
|
||||||
|
|
||||||
The licenses for most software and other practical works are designed
|
The licenses for most software and other practical works are designed
|
||||||
to take away your freedom to share and change the works. By contrast,
|
to take away your freedom to share and change the works. By contrast,
|
||||||
the GNU General Public License is intended to guarantee your freedom to
|
our General Public Licenses are intended to guarantee your freedom to
|
||||||
share and change all versions of a program--to make sure it remains free
|
share and change all versions of a program--to make sure it remains free
|
||||||
software for all its users. We, the Free Software Foundation, use the
|
software for all its users.
|
||||||
GNU General Public License for most of our software; it applies also to
|
|
||||||
any other work released this way by its authors. You can apply it to
|
|
||||||
your programs, too.
|
|
||||||
|
|
||||||
When we speak of free software, we are referring to freedom, not
|
When we speak of free software, we are referring to freedom, not
|
||||||
price. Our General Public Licenses are designed to make sure that you
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
@@ -26,44 +24,34 @@ them if you wish), that you receive source code or can get it if you
|
|||||||
want it, that you can change the software or use pieces of it in new
|
want it, that you can change the software or use pieces of it in new
|
||||||
free programs, and that you know you can do these things.
|
free programs, and that you know you can do these things.
|
||||||
|
|
||||||
To protect your rights, we need to prevent others from denying you
|
Developers that use our General Public Licenses protect your rights
|
||||||
these rights or asking you to surrender the rights. Therefore, you have
|
with two steps: (1) assert copyright on the software, and (2) offer
|
||||||
certain responsibilities if you distribute copies of the software, or if
|
you this License which gives you legal permission to copy, distribute
|
||||||
you modify it: responsibilities to respect the freedom of others.
|
and/or modify the software.
|
||||||
|
|
||||||
For example, if you distribute copies of such a program, whether
|
A secondary benefit of defending all users' freedom is that
|
||||||
gratis or for a fee, you must pass on to the recipients the same
|
improvements made in alternate versions of the program, if they
|
||||||
freedoms that you received. You must make sure that they, too, receive
|
receive widespread use, become available for other developers to
|
||||||
or can get the source code. And you must show them these terms so they
|
incorporate. Many developers of free software are heartened and
|
||||||
know their rights.
|
encouraged by the resulting cooperation. However, in the case of
|
||||||
|
software used on network servers, this result may fail to come about.
|
||||||
|
The GNU General Public License permits making a modified version and
|
||||||
|
letting the public access it on a server without ever releasing its
|
||||||
|
source code to the public.
|
||||||
|
|
||||||
Developers that use the GNU GPL protect your rights with two steps:
|
The GNU Affero General Public License is designed specifically to
|
||||||
(1) assert copyright on the software, and (2) offer you this License
|
ensure that, in such cases, the modified source code becomes available
|
||||||
giving you legal permission to copy, distribute and/or modify it.
|
to the community. It requires the operator of a network server to
|
||||||
|
provide the source code of the modified version running there to the
|
||||||
|
users of that server. Therefore, public use of a modified version, on
|
||||||
|
a publicly accessible server, gives the public access to the source
|
||||||
|
code of the modified version.
|
||||||
|
|
||||||
For the developers' and authors' protection, the GPL clearly explains
|
An older license, called the Affero General Public License and
|
||||||
that there is no warranty for this free software. For both users' and
|
published by Affero, was designed to accomplish similar goals. This is
|
||||||
authors' sake, the GPL requires that modified versions be marked as
|
a different license, not a version of the Affero GPL, but Affero has
|
||||||
changed, so that their problems will not be attributed erroneously to
|
released a new version of the Affero GPL which permits relicensing under
|
||||||
authors of previous versions.
|
this license.
|
||||||
|
|
||||||
Some devices are designed to deny users access to install or run
|
|
||||||
modified versions of the software inside them, although the manufacturer
|
|
||||||
can do so. This is fundamentally incompatible with the aim of
|
|
||||||
protecting users' freedom to change the software. The systematic
|
|
||||||
pattern of such abuse occurs in the area of products for individuals to
|
|
||||||
use, which is precisely where it is most unacceptable. Therefore, we
|
|
||||||
have designed this version of the GPL to prohibit the practice for those
|
|
||||||
products. If such problems arise substantially in other domains, we
|
|
||||||
stand ready to extend this provision to those domains in future versions
|
|
||||||
of the GPL, as needed to protect the freedom of users.
|
|
||||||
|
|
||||||
Finally, every program is threatened constantly by software patents.
|
|
||||||
States should not allow patents to restrict development and use of
|
|
||||||
software on general-purpose computers, but in those that do, we wish to
|
|
||||||
avoid the special danger that patents applied to a free program could
|
|
||||||
make it effectively proprietary. To prevent this, the GPL assures that
|
|
||||||
patents cannot be used to render the program non-free.
|
|
||||||
|
|
||||||
The precise terms and conditions for copying, distribution and
|
The precise terms and conditions for copying, distribution and
|
||||||
modification follow.
|
modification follow.
|
||||||
@@ -72,7 +60,7 @@ modification follow.
|
|||||||
|
|
||||||
0. Definitions.
|
0. Definitions.
|
||||||
|
|
||||||
"This License" refers to version 3 of the GNU General Public License.
|
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||||
|
|
||||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||||
works, such as semiconductor masks.
|
works, such as semiconductor masks.
|
||||||
@@ -549,35 +537,45 @@ to collect a royalty for further conveying from those to whom you convey
|
|||||||
the Program, the only way you could satisfy both those terms and this
|
the Program, the only way you could satisfy both those terms and this
|
||||||
License would be to refrain entirely from conveying the Program.
|
License would be to refrain entirely from conveying the Program.
|
||||||
|
|
||||||
13. Use with the GNU Affero General Public License.
|
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, if you modify the
|
||||||
|
Program, your modified version must prominently offer all users
|
||||||
|
interacting with it remotely through a computer network (if your version
|
||||||
|
supports such interaction) an opportunity to receive the Corresponding
|
||||||
|
Source of your version by providing access to the Corresponding Source
|
||||||
|
from a network server at no charge, through some standard or customary
|
||||||
|
means of facilitating copying of software. This Corresponding Source
|
||||||
|
shall include the Corresponding Source for any work covered by version 3
|
||||||
|
of the GNU General Public License that is incorporated pursuant to the
|
||||||
|
following paragraph.
|
||||||
|
|
||||||
Notwithstanding any other provision of this License, you have
|
Notwithstanding any other provision of this License, you have
|
||||||
permission to link or combine any covered work with a work licensed
|
permission to link or combine any covered work with a work licensed
|
||||||
under version 3 of the GNU Affero General Public License into a single
|
under version 3 of the GNU General Public License into a single
|
||||||
combined work, and to convey the resulting work. The terms of this
|
combined work, and to convey the resulting work. The terms of this
|
||||||
License will continue to apply to the part which is the covered work,
|
License will continue to apply to the part which is the covered work,
|
||||||
but the special requirements of the GNU Affero General Public License,
|
but the work with which it is combined will remain governed by version
|
||||||
section 13, concerning interaction through a network will apply to the
|
3 of the GNU General Public License.
|
||||||
combination as such.
|
|
||||||
|
|
||||||
14. Revised Versions of this License.
|
14. Revised Versions of this License.
|
||||||
|
|
||||||
The Free Software Foundation may publish revised and/or new versions of
|
The Free Software Foundation may publish revised and/or new versions of
|
||||||
the GNU General Public License from time to time. Such new versions will
|
the GNU Affero General Public License from time to time. Such new versions
|
||||||
be similar in spirit to the present version, but may differ in detail to
|
will be similar in spirit to the present version, but may differ in detail to
|
||||||
address new problems or concerns.
|
address new problems or concerns.
|
||||||
|
|
||||||
Each version is given a distinguishing version number. If the
|
Each version is given a distinguishing version number. If the
|
||||||
Program specifies that a certain numbered version of the GNU General
|
Program specifies that a certain numbered version of the GNU Affero General
|
||||||
Public License "or any later version" applies to it, you have the
|
Public License "or any later version" applies to it, you have the
|
||||||
option of following the terms and conditions either of that numbered
|
option of following the terms and conditions either of that numbered
|
||||||
version or of any later version published by the Free Software
|
version or of any later version published by the Free Software
|
||||||
Foundation. If the Program does not specify a version number of the
|
Foundation. If the Program does not specify a version number of the
|
||||||
GNU General Public License, you may choose any version ever published
|
GNU Affero General Public License, you may choose any version ever published
|
||||||
by the Free Software Foundation.
|
by the Free Software Foundation.
|
||||||
|
|
||||||
If the Program specifies that a proxy can decide which future
|
If the Program specifies that a proxy can decide which future
|
||||||
versions of the GNU General Public License can be used, that proxy's
|
versions of the GNU Affero General Public License can be used, that proxy's
|
||||||
public statement of acceptance of a version permanently authorizes you
|
public statement of acceptance of a version permanently authorizes you
|
||||||
to choose that version for the Program.
|
to choose that version for the Program.
|
||||||
|
|
||||||
@@ -635,40 +633,29 @@ the "copyright" line and a pointer to where the full notice is found.
|
|||||||
Copyright (C) <year> <name of author>
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
(at your option) any later version.
|
(at your option) any later version.
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
This program is distributed in the hope that it will be useful,
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
GNU General Public License for more details.
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU Affero General Public License
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
Also add information on how to contact you by electronic and paper mail.
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
If the program does terminal interaction, make it output a short
|
If your software can interact with users remotely through a computer
|
||||||
notice like this when it starts in an interactive mode:
|
network, you should also make sure that it provides a way for users to
|
||||||
|
get its source. For example, if your program is a web application, its
|
||||||
<program> Copyright (C) <year> <name of author>
|
interface could display a "Source" link that leads users to an archive
|
||||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
of the code. There are many ways you could offer source, and different
|
||||||
This is free software, and you are welcome to redistribute it
|
solutions will be better for different programs; see section 13 for the
|
||||||
under certain conditions; type `show c' for details.
|
specific requirements.
|
||||||
|
|
||||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
|
||||||
parts of the General Public License. Of course, your program's commands
|
|
||||||
might be different; for a GUI interface, you would use an "about box".
|
|
||||||
|
|
||||||
You should also get your employer (if you work as a programmer) or school,
|
You should also get your employer (if you work as a programmer) or school,
|
||||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||||
For more information on this, and how to apply and follow the GNU GPL, see
|
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||||
<https://www.gnu.org/licenses/>.
|
<https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
The GNU General Public License does not permit incorporating your program
|
|
||||||
into proprietary programs. If your program is a subroutine library, you
|
|
||||||
may consider it more useful to permit linking proprietary applications with
|
|
||||||
the library. If this is what you want to do, use the GNU Lesser General
|
|
||||||
Public License instead of this License. But first, please read
|
|
||||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
|
||||||
|
|||||||
@@ -8,11 +8,16 @@ ARG TARGETPLATFORM=linux/amd64
|
|||||||
FROM debian:12-slim
|
FROM debian:12-slim
|
||||||
|
|
||||||
ARG TARGETPLATFORM
|
ARG TARGETPLATFORM
|
||||||
|
ARG APT_MIRROR=
|
||||||
|
|
||||||
# Install runtime dependencies in a single layer
|
# Install runtime dependencies in a single layer
|
||||||
# All codec libraries (libx264, libx265, libopus) are now statically linked
|
# All codec libraries (libx264, libx265, libopus) are now statically linked
|
||||||
# Only hardware acceleration drivers and core system libraries remain dynamic
|
# Only hardware acceleration drivers and core system libraries remain dynamic
|
||||||
RUN sed -i 's/^Components: main$/Components: main contrib non-free non-free-firmware/' /etc/apt/sources.list.d/debian.sources && \
|
# Set APT_MIRROR=mirrors.tuna.tsinghua.edu.cn to speed up builds in China
|
||||||
|
RUN if [ -n "$APT_MIRROR" ]; then \
|
||||||
|
sed -i "s@deb.debian.org@${APT_MIRROR}@g" /etc/apt/sources.list.d/debian.sources; \
|
||||||
|
fi \
|
||||||
|
&& sed -i 's/^Components: main$/Components: main contrib non-free non-free-firmware/' /etc/apt/sources.list.d/debian.sources && \
|
||||||
apt-get update && \
|
apt-get update && \
|
||||||
apt-get install -y --no-install-recommends \
|
apt-get install -y --no-install-recommends \
|
||||||
# Core runtime (all platforms) - no codec libs needed
|
# Core runtime (all platforms) - no codec libs needed
|
||||||
|
|||||||
@@ -8,11 +8,16 @@ ARG TARGETPLATFORM=linux/amd64
|
|||||||
FROM debian:12-slim
|
FROM debian:12-slim
|
||||||
|
|
||||||
ARG TARGETPLATFORM
|
ARG TARGETPLATFORM
|
||||||
|
ARG APT_MIRROR=
|
||||||
|
|
||||||
# Install runtime dependencies in a single layer
|
# Install runtime dependencies in a single layer
|
||||||
# All codec libraries (libx264, libx265, libopus) are now statically linked
|
# All codec libraries (libx264, libx265, libopus) are now statically linked
|
||||||
# Only hardware acceleration drivers and core system libraries remain dynamic
|
# Only hardware acceleration drivers and core system libraries remain dynamic
|
||||||
RUN sed -i 's/^Components: main$/Components: main contrib non-free non-free-firmware/' /etc/apt/sources.list.d/debian.sources && \
|
# Set APT_MIRROR=mirrors.tuna.tsinghua.edu.cn to speed up builds in China
|
||||||
|
RUN if [ -n "$APT_MIRROR" ]; then \
|
||||||
|
sed -i "s@deb.debian.org@${APT_MIRROR}@g" /etc/apt/sources.list.d/debian.sources; \
|
||||||
|
fi \
|
||||||
|
&& sed -i 's/^Components: main$/Components: main contrib non-free non-free-firmware/' /etc/apt/sources.list.d/debian.sources && \
|
||||||
apt-get update && \
|
apt-get update && \
|
||||||
apt-get install -y --no-install-recommends \
|
apt-get install -y --no-install-recommends \
|
||||||
# Core runtime (all platforms) - no codec libs needed
|
# Core runtime (all platforms) - no codec libs needed
|
||||||
|
|||||||
@@ -21,21 +21,25 @@ build_arch() {
|
|||||||
|
|
||||||
case "${CHINAMIRRO:-}" in
|
case "${CHINAMIRRO:-}" in
|
||||||
1|true|TRUE|yes|YES|on|ON)
|
1|true|TRUE|yes|YES|on|ON)
|
||||||
local cross_build_opts="${CROSS_BUILD_OPTS:+$CROSS_BUILD_OPTS }--progress=plain --build-arg CHINAMIRRO=1 --build-arg GH_PROXY=${GH_PROXY:-https://gh-proxy.com/} --build-arg DEBIAN_IMAGE=${DEBIAN_IMAGE:-docker.1ms.run/library/debian:12}"
|
# Default Rust toolchain mirror is USTC (rsproxy.cn is unreachable via the
|
||||||
|
# Docker bridge on some networks because its CDN blocks the SNAT source IP).
|
||||||
|
local rust_dist="${RUSTUP_DIST_SERVER_MIRROR:-https://mirrors.ustc.edu.cn/rust-static}"
|
||||||
|
local rust_root="${RUSTUP_UPDATE_ROOT_MIRROR:-https://mirrors.ustc.edu.cn/rust-static/rustup}"
|
||||||
|
local crates_sparse="${CARGO_SPARSE_MIRROR:-sparse+https://mirrors.ustc.edu.cn/crates.io-index/}"
|
||||||
|
local crates_git="${CARGO_GIT_MIRROR:-https://mirrors.ustc.edu.cn/crates.io-index}"
|
||||||
|
local cross_build_opts="${CROSS_BUILD_OPTS:+$CROSS_BUILD_OPTS }--progress=plain --build-arg CHINAMIRRO=1 --build-arg GH_PROXY=${GH_PROXY:-https://ghfast.top/} --build-arg DEBIAN_IMAGE=${DEBIAN_IMAGE:-dockerproxy.net/library/debian:12}"
|
||||||
cross_build_opts="$cross_build_opts --build-arg HTTP_PROXY= --build-arg HTTPS_PROXY= --build-arg ALL_PROXY= --build-arg NO_PROXY="
|
cross_build_opts="$cross_build_opts --build-arg HTTP_PROXY= --build-arg HTTPS_PROXY= --build-arg ALL_PROXY= --build-arg NO_PROXY="
|
||||||
cross_build_opts="$cross_build_opts --build-arg http_proxy= --build-arg https_proxy= --build-arg all_proxy= --build-arg no_proxy="
|
cross_build_opts="$cross_build_opts --build-arg http_proxy= --build-arg https_proxy= --build-arg all_proxy= --build-arg no_proxy="
|
||||||
echo "=== China mirror acceleration: enabled ==="
|
echo "=== China mirror acceleration: enabled ==="
|
||||||
echo "=== Building: $rust_target (via cross with custom Dockerfile) ==="
|
echo "=== Building: $rust_target (via cross with custom Dockerfile) ==="
|
||||||
env \
|
env \
|
||||||
CROSS_BUILD_OPTS="$cross_build_opts" \
|
CROSS_BUILD_OPTS="$cross_build_opts" \
|
||||||
CARGO_SOURCE_CRATES_IO_REPLACE_WITH=rsproxy-sparse \
|
CARGO_SOURCE_CRATES_IO_REPLACE_WITH=cn-mirror \
|
||||||
CARGO_SOURCE_RSPROXY_REGISTRY=https://rsproxy.cn/crates.io-index \
|
CARGO_SOURCE_CN-MIRROR_REGISTRY="$crates_sparse" \
|
||||||
CARGO_SOURCE_RSPROXY_SPARSE_REGISTRY=sparse+https://rsproxy.cn/index/ \
|
|
||||||
CARGO_REGISTRIES_RSPROXY_INDEX=https://rsproxy.cn/crates.io-index \
|
|
||||||
CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse \
|
CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse \
|
||||||
CARGO_NET_GIT_FETCH_WITH_CLI=true \
|
CARGO_NET_GIT_FETCH_WITH_CLI=true \
|
||||||
RUSTUP_DIST_SERVER=https://rsproxy.cn \
|
RUSTUP_DIST_SERVER="$rust_dist" \
|
||||||
RUSTUP_UPDATE_ROOT=https://rsproxy.cn/rustup \
|
RUSTUP_UPDATE_ROOT="$rust_root" \
|
||||||
cross build --release --target "$rust_target"
|
cross build --release --target "$rust_target"
|
||||||
return
|
return
|
||||||
;;
|
;;
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ FROM ${DEBIAN_IMAGE}
|
|||||||
|
|
||||||
ARG CHINAMIRRO=0
|
ARG CHINAMIRRO=0
|
||||||
ARG GH_PROXY=https://gh-proxy.com/
|
ARG GH_PROXY=https://gh-proxy.com/
|
||||||
ARG RUSTUP_DIST_SERVER_CN=https://rsproxy.cn
|
ARG RUSTUP_DIST_SERVER_CN=https://mirrors.ustc.edu.cn/rust-static
|
||||||
ARG RUSTUP_UPDATE_ROOT_CN=https://rsproxy.cn/rustup
|
ARG RUSTUP_UPDATE_ROOT_CN=https://mirrors.ustc.edu.cn/rust-static/rustup
|
||||||
ARG CARGO_INDEX_CN=https://rsproxy.cn/crates.io-index
|
ARG CARGO_INDEX_CN=https://mirrors.ustc.edu.cn/crates.io-index
|
||||||
ARG CARGO_REGISTRY_CN=sparse+https://rsproxy.cn/index/
|
ARG CARGO_REGISTRY_CN=sparse+https://mirrors.ustc.edu.cn/crates.io-index/
|
||||||
ARG LIBJPEG_TURBO_VERSION=3.1.4.1
|
ARG LIBJPEG_TURBO_VERSION=3.1.4.1
|
||||||
ARG LIBYUV_REV=957f295ea946cbbd13fcfc46e7066f2efa801233
|
ARG LIBYUV_REV=957f295ea946cbbd13fcfc46e7066f2efa801233
|
||||||
ARG LIBVPX_VERSION=1.16.0
|
ARG LIBVPX_VERSION=1.16.0
|
||||||
@@ -39,12 +39,12 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|||||||
mkdir -p /root/.cargo; \
|
mkdir -p /root/.cargo; \
|
||||||
printf '%s\n' \
|
printf '%s\n' \
|
||||||
'[source.crates-io]' \
|
'[source.crates-io]' \
|
||||||
"replace-with = 'rsproxy-sparse'" \
|
"replace-with = 'cn-mirror-sparse'" \
|
||||||
'[source.rsproxy]' \
|
'[source.cn-mirror]' \
|
||||||
"registry = '${CARGO_INDEX_CN}'" \
|
"registry = '${CARGO_INDEX_CN}'" \
|
||||||
'[source.rsproxy-sparse]' \
|
'[source.cn-mirror-sparse]' \
|
||||||
"registry = '${CARGO_REGISTRY_CN}'" \
|
"registry = '${CARGO_REGISTRY_CN}'" \
|
||||||
'[registries.rsproxy]' \
|
'[registries.cn-mirror]' \
|
||||||
"index = '${CARGO_INDEX_CN}'" \
|
"index = '${CARGO_INDEX_CN}'" \
|
||||||
'[net]' \
|
'[net]' \
|
||||||
'git-fetch-with-cli = true' \
|
'git-fetch-with-cli = true' \
|
||||||
@@ -60,6 +60,7 @@ RUN dpkg --add-architecture arm64
|
|||||||
# Install cross-compiler and native build tools
|
# Install cross-compiler and native build tools
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
build-essential \
|
build-essential \
|
||||||
|
patch \
|
||||||
pkg-config \
|
pkg-config \
|
||||||
cmake \
|
cmake \
|
||||||
nasm \
|
nasm \
|
||||||
@@ -250,6 +251,9 @@ RUN github_prefix="" \
|
|||||||
&& make install \
|
&& make install \
|
||||||
&& rm -rf /tmp/opus /tmp/opus.tar.gz
|
&& rm -rf /tmp/opus /tmp/opus.tar.gz
|
||||||
|
|
||||||
|
# Reject raw formats smaller than the requested frame before any V4L2 upload.
|
||||||
|
COPY build/cross/patches/0001-v4l2-m2m-reject-undersized-encoder-format.patch /tmp/ffmpeg-v4l2-size.patch
|
||||||
|
|
||||||
# Download and build FFmpeg with RKMPP support
|
# Download and build FFmpeg with RKMPP support
|
||||||
RUN mkdir -p /tmp/ffmpeg-build && cd /tmp/ffmpeg-build \
|
RUN mkdir -p /tmp/ffmpeg-build && cd /tmp/ffmpeg-build \
|
||||||
&& github_prefix="" \
|
&& github_prefix="" \
|
||||||
@@ -261,6 +265,7 @@ RUN mkdir -p /tmp/ffmpeg-build && cd /tmp/ffmpeg-build \
|
|||||||
&& git remote add origin "${github_prefix}https://github.com/nyanmisaka/ffmpeg-rockchip.git" \
|
&& git remote add origin "${github_prefix}https://github.com/nyanmisaka/ffmpeg-rockchip.git" \
|
||||||
&& git fetch --depth 1 origin ${FFMPEG_ROCKCHIP_REV} \
|
&& git fetch --depth 1 origin ${FFMPEG_ROCKCHIP_REV} \
|
||||||
&& git checkout --detach FETCH_HEAD \
|
&& git checkout --detach FETCH_HEAD \
|
||||||
|
&& patch -p1 < /tmp/ffmpeg-v4l2-size.patch \
|
||||||
&& cd .. \
|
&& cd .. \
|
||||||
# Build RKMPP
|
# Build RKMPP
|
||||||
&& mkdir -p rkmpp/build && cd rkmpp/build \
|
&& mkdir -p rkmpp/build && cd rkmpp/build \
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ FROM ${DEBIAN_IMAGE}
|
|||||||
|
|
||||||
ARG CHINAMIRRO=0
|
ARG CHINAMIRRO=0
|
||||||
ARG GH_PROXY=https://gh-proxy.com/
|
ARG GH_PROXY=https://gh-proxy.com/
|
||||||
ARG RUSTUP_DIST_SERVER_CN=https://rsproxy.cn
|
ARG RUSTUP_DIST_SERVER_CN=https://mirrors.ustc.edu.cn/rust-static
|
||||||
ARG RUSTUP_UPDATE_ROOT_CN=https://rsproxy.cn/rustup
|
ARG RUSTUP_UPDATE_ROOT_CN=https://mirrors.ustc.edu.cn/rust-static/rustup
|
||||||
ARG CARGO_INDEX_CN=https://rsproxy.cn/crates.io-index
|
ARG CARGO_INDEX_CN=https://mirrors.ustc.edu.cn/crates.io-index
|
||||||
ARG CARGO_REGISTRY_CN=sparse+https://rsproxy.cn/index/
|
ARG CARGO_REGISTRY_CN=sparse+https://mirrors.ustc.edu.cn/crates.io-index/
|
||||||
ARG LIBJPEG_TURBO_VERSION=3.1.4.1
|
ARG LIBJPEG_TURBO_VERSION=3.1.4.1
|
||||||
ARG LIBYUV_REV=957f295ea946cbbd13fcfc46e7066f2efa801233
|
ARG LIBYUV_REV=957f295ea946cbbd13fcfc46e7066f2efa801233
|
||||||
ARG LIBVPX_VERSION=1.16.0
|
ARG LIBVPX_VERSION=1.16.0
|
||||||
@@ -39,12 +39,12 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|||||||
mkdir -p /root/.cargo; \
|
mkdir -p /root/.cargo; \
|
||||||
printf '%s\n' \
|
printf '%s\n' \
|
||||||
'[source.crates-io]' \
|
'[source.crates-io]' \
|
||||||
"replace-with = 'rsproxy-sparse'" \
|
"replace-with = 'cn-mirror-sparse'" \
|
||||||
'[source.rsproxy]' \
|
'[source.cn-mirror]' \
|
||||||
"registry = '${CARGO_INDEX_CN}'" \
|
"registry = '${CARGO_INDEX_CN}'" \
|
||||||
'[source.rsproxy-sparse]' \
|
'[source.cn-mirror-sparse]' \
|
||||||
"registry = '${CARGO_REGISTRY_CN}'" \
|
"registry = '${CARGO_REGISTRY_CN}'" \
|
||||||
'[registries.rsproxy]' \
|
'[registries.cn-mirror]' \
|
||||||
"index = '${CARGO_INDEX_CN}'" \
|
"index = '${CARGO_INDEX_CN}'" \
|
||||||
'[net]' \
|
'[net]' \
|
||||||
'git-fetch-with-cli = true' \
|
'git-fetch-with-cli = true' \
|
||||||
@@ -60,6 +60,7 @@ RUN dpkg --add-architecture armhf
|
|||||||
# Install cross-compiler and native build tools
|
# Install cross-compiler and native build tools
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
build-essential \
|
build-essential \
|
||||||
|
patch \
|
||||||
pkg-config \
|
pkg-config \
|
||||||
cmake \
|
cmake \
|
||||||
nasm \
|
nasm \
|
||||||
@@ -239,6 +240,9 @@ RUN github_prefix="" \
|
|||||||
&& make install \
|
&& make install \
|
||||||
&& rm -rf /tmp/opus /tmp/opus.tar.gz
|
&& rm -rf /tmp/opus /tmp/opus.tar.gz
|
||||||
|
|
||||||
|
# Reject raw formats smaller than the requested frame before any V4L2 upload.
|
||||||
|
COPY build/cross/patches/0001-v4l2-m2m-reject-undersized-encoder-format.patch /tmp/ffmpeg-v4l2-size.patch
|
||||||
|
|
||||||
# Download and build FFmpeg with RKMPP support
|
# Download and build FFmpeg with RKMPP support
|
||||||
RUN mkdir -p /tmp/ffmpeg-build && cd /tmp/ffmpeg-build \
|
RUN mkdir -p /tmp/ffmpeg-build && cd /tmp/ffmpeg-build \
|
||||||
&& github_prefix="" \
|
&& github_prefix="" \
|
||||||
@@ -250,6 +254,7 @@ RUN mkdir -p /tmp/ffmpeg-build && cd /tmp/ffmpeg-build \
|
|||||||
&& git remote add origin "${github_prefix}https://github.com/nyanmisaka/ffmpeg-rockchip.git" \
|
&& git remote add origin "${github_prefix}https://github.com/nyanmisaka/ffmpeg-rockchip.git" \
|
||||||
&& git fetch --depth 1 origin ${FFMPEG_ROCKCHIP_REV} \
|
&& git fetch --depth 1 origin ${FFMPEG_ROCKCHIP_REV} \
|
||||||
&& git checkout --detach FETCH_HEAD \
|
&& git checkout --detach FETCH_HEAD \
|
||||||
|
&& patch -p1 < /tmp/ffmpeg-v4l2-size.patch \
|
||||||
&& cd .. \
|
&& cd .. \
|
||||||
# Build RKMPP
|
# Build RKMPP
|
||||||
&& mkdir -p rkmpp/build && cd rkmpp/build \
|
&& mkdir -p rkmpp/build && cd rkmpp/build \
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ FROM ${DEBIAN_IMAGE}
|
|||||||
|
|
||||||
ARG CHINAMIRRO=0
|
ARG CHINAMIRRO=0
|
||||||
ARG GH_PROXY=https://gh-proxy.com/
|
ARG GH_PROXY=https://gh-proxy.com/
|
||||||
ARG RUSTUP_DIST_SERVER_CN=https://rsproxy.cn
|
ARG RUSTUP_DIST_SERVER_CN=https://mirrors.ustc.edu.cn/rust-static
|
||||||
ARG RUSTUP_UPDATE_ROOT_CN=https://rsproxy.cn/rustup
|
ARG RUSTUP_UPDATE_ROOT_CN=https://mirrors.ustc.edu.cn/rust-static/rustup
|
||||||
ARG CARGO_INDEX_CN=https://rsproxy.cn/crates.io-index
|
ARG CARGO_INDEX_CN=https://mirrors.ustc.edu.cn/crates.io-index
|
||||||
ARG CARGO_REGISTRY_CN=sparse+https://rsproxy.cn/index/
|
ARG CARGO_REGISTRY_CN=sparse+https://mirrors.ustc.edu.cn/crates.io-index/
|
||||||
ARG LIBJPEG_TURBO_VERSION=3.1.4.1
|
ARG LIBJPEG_TURBO_VERSION=3.1.4.1
|
||||||
ARG LIBYUV_REV=957f295ea946cbbd13fcfc46e7066f2efa801233
|
ARG LIBYUV_REV=957f295ea946cbbd13fcfc46e7066f2efa801233
|
||||||
ARG LIBVPX_VERSION=1.16.0
|
ARG LIBVPX_VERSION=1.16.0
|
||||||
@@ -37,12 +37,12 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|||||||
mkdir -p /root/.cargo; \
|
mkdir -p /root/.cargo; \
|
||||||
printf '%s\n' \
|
printf '%s\n' \
|
||||||
'[source.crates-io]' \
|
'[source.crates-io]' \
|
||||||
"replace-with = 'rsproxy-sparse'" \
|
"replace-with = 'cn-mirror-sparse'" \
|
||||||
'[source.rsproxy]' \
|
'[source.cn-mirror]' \
|
||||||
"registry = '${CARGO_INDEX_CN}'" \
|
"registry = '${CARGO_INDEX_CN}'" \
|
||||||
'[source.rsproxy-sparse]' \
|
'[source.cn-mirror-sparse]' \
|
||||||
"registry = '${CARGO_REGISTRY_CN}'" \
|
"registry = '${CARGO_REGISTRY_CN}'" \
|
||||||
'[registries.rsproxy]' \
|
'[registries.cn-mirror]' \
|
||||||
"index = '${CARGO_INDEX_CN}'" \
|
"index = '${CARGO_INDEX_CN}'" \
|
||||||
'[net]' \
|
'[net]' \
|
||||||
'git-fetch-with-cli = true' \
|
'git-fetch-with-cli = true' \
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
--- a/libavcodec/v4l2_m2m_enc.c
|
||||||
|
+++ b/libavcodec/v4l2_m2m_enc.c
|
||||||
|
@@ -338,6 +338,7 @@
|
||||||
|
V4L2m2mPriv *priv = avctx->priv_data;
|
||||||
|
enum AVPixelFormat pix_fmt_output;
|
||||||
|
uint32_t v4l2_fmt_output;
|
||||||
|
+ unsigned int output_width, output_height;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = ff_v4l2_m2m_create_context(priv, &s);
|
||||||
|
@@ -370,6 +371,25 @@
|
||||||
|
v4l2_fmt_output = output->format.fmt.pix_mp.pixelformat;
|
||||||
|
else
|
||||||
|
v4l2_fmt_output = output->format.fmt.pix.pixelformat;
|
||||||
|
+
|
||||||
|
+ /* Drivers may clamp the requested raw format to their supported size.
|
||||||
|
+ * The software-frame upload still uses avctx/frame dimensions, so a
|
||||||
|
+ * smaller negotiated format would overrun the mapped output buffers.
|
||||||
|
+ * Allow upward alignment, but reject frames that do not fit.
|
||||||
|
+ */
|
||||||
|
+ if (V4L2_TYPE_IS_MULTIPLANAR(output->type)) {
|
||||||
|
+ output_width = output->format.fmt.pix_mp.width;
|
||||||
|
+ output_height = output->format.fmt.pix_mp.height;
|
||||||
|
+ } else {
|
||||||
|
+ output_width = output->format.fmt.pix.width;
|
||||||
|
+ output_height = output->format.fmt.pix.height;
|
||||||
|
+ }
|
||||||
|
+ if (output_width < avctx->width || output_height < avctx->height) {
|
||||||
|
+ av_log(avctx, AV_LOG_ERROR,
|
||||||
|
+ "Encoder cannot fit requested %dx%d frame in negotiated %ux%u format\n",
|
||||||
|
+ avctx->width, avctx->height, output_width, output_height);
|
||||||
|
+ return AVERROR(EINVAL);
|
||||||
|
+ }
|
||||||
|
|
||||||
|
pix_fmt_output = ff_v4l2_format_v4l2_to_avfmt(v4l2_fmt_output, AV_CODEC_ID_RAWVIDEO);
|
||||||
|
if (pix_fmt_output != avctx->pix_fmt) {
|
||||||
@@ -1,16 +1,130 @@
|
|||||||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||||
Upstream-Name: one-kvm
|
Upstream-Name: one-kvm
|
||||||
Source: https://github.com/mofeng-git/one-KVM
|
Upstream-Contact: https://github.com/mofeng-git/One-KVM/issues
|
||||||
|
Source: https://github.com/mofeng-git/One-KVM
|
||||||
|
|
||||||
Files: *
|
Files: *
|
||||||
Copyright: 2025 One-KVM contributors
|
Copyright: 2025 One-KVM contributors
|
||||||
License: GPL-2.0
|
License: AGPL-3.0-only
|
||||||
|
|
||||||
|
Files: protos/*
|
||||||
|
Copyright: the RustDesk contributors
|
||||||
|
2025 One-KVM contributors
|
||||||
|
License: AGPL-3.0-only
|
||||||
|
Comment:
|
||||||
|
Protocol definition files derived from RustDesk
|
||||||
|
(https://github.com/rustdesk/rustdesk). Modified for One-KVM.
|
||||||
|
See protos/NOTICE for the full attribution.
|
||||||
|
|
||||||
|
Files: src/rustdesk/*
|
||||||
|
Copyright: the RustDesk contributors
|
||||||
|
2025 One-KVM contributors
|
||||||
|
License: AGPL-3.0-only
|
||||||
|
Comment:
|
||||||
|
Code derived from the RustDesk project, modified for One-KVM.
|
||||||
|
See src/rustdesk/NOTICE for the full attribution.
|
||||||
|
|
||||||
|
Files: libs/hwcodec/*
|
||||||
|
Copyright: the RustDesk contributors
|
||||||
|
2025 One-KVM contributors
|
||||||
|
License: AGPL-3.0-only
|
||||||
|
Comment:
|
||||||
|
Derived from the RustDesk hwcodec component. See libs/hwcodec/NOTICE.
|
||||||
|
|
||||||
|
Files: libs/v4l2r/*
|
||||||
|
Copyright: 2023-2024 Andrei Lytkin, Daniel Almeida, Collabora Ltd. and other v4l2r contributors
|
||||||
|
License: Expat
|
||||||
|
|
||||||
Files: libs/ventoy-img-rs/resources/*
|
Files: libs/ventoy-img-rs/resources/*
|
||||||
Copyright: Ventoy contributors
|
Copyright: Ventoy contributors
|
||||||
License: GPL-2.0
|
License: GPL-3.0+
|
||||||
|
Comment:
|
||||||
|
Pre-built Ventoy boot files (boot.img, core.img.xz, ventoy.disk.img.xz).
|
||||||
|
Ventoy upstream: https://github.com/ventoy/Ventoy, licensed GPL-3.0-or-later.
|
||||||
|
|
||||||
License: GPL-2.0
|
Files: res/vcpkg/libyuv/*
|
||||||
|
Copyright: The LibYuv Project Authors
|
||||||
|
License: BSD-3-Clause
|
||||||
|
|
||||||
|
Files: third-party/ffmpeg
|
||||||
|
Copyright: FFmpeg developers and contributors
|
||||||
|
License: GPL-2.0+
|
||||||
|
Comment:
|
||||||
|
FFmpeg is built with --enable-gpl --enable-version3 and linked against
|
||||||
|
x264/x265; the resulting build is GPL-compatible with the AGPL-3.0 main
|
||||||
|
program. Upstream: https://ffmpeg.org/
|
||||||
|
The Linux builds use nyanmisaka/ffmpeg-rockchip at the commit pinned by
|
||||||
|
FFMPEG_ROCKCHIP_REV in build/cross/Dockerfile.*.
|
||||||
|
|
||||||
|
Files: third-party/x264
|
||||||
|
Copyright: x264 LLC and x264 contributors
|
||||||
|
License: GPL-2.0+
|
||||||
|
Comment: Upstream: https://www.videolan.org/developers/x264.html
|
||||||
|
|
||||||
|
Files: third-party/x265
|
||||||
|
Copyright: MulticoreWare, Inc. and x265 contributors
|
||||||
|
License: GPL-2.0+
|
||||||
|
Comment: Upstream: https://www.x265.org/
|
||||||
|
|
||||||
|
Files: third-party/libvpx
|
||||||
|
Copyright: The WebM Project authors
|
||||||
|
License: BSD-3-Clause
|
||||||
|
Comment: Upstream: https://www.webmproject.org/code/
|
||||||
|
|
||||||
|
Files: third-party/libopus
|
||||||
|
Copyright: Opus contributors
|
||||||
|
License: BSD-3-Clause
|
||||||
|
Comment: Upstream: https://opus-codec.org/
|
||||||
|
|
||||||
|
Files: third-party/libjpeg-turbo
|
||||||
|
Copyright: D. R. Commander and other libjpeg-turbo contributors
|
||||||
|
License: BSD-3-Clause
|
||||||
|
Comment: Upstream: https://libjpeg-turbo.org/
|
||||||
|
|
||||||
|
Files: third-party/libsodium
|
||||||
|
Copyright: 2013-2025 Frank Denis
|
||||||
|
License: Expat
|
||||||
|
Comment: Upstream: https://libsodium.gitbook.io/
|
||||||
|
|
||||||
|
Files: third-party/sqlite
|
||||||
|
Copyright: 1994-2010 Hipp, Wyrick & Company, Inc.
|
||||||
|
License: SQLite-public-domain
|
||||||
|
Comment: Upstream: https://www.sqlite.org/
|
||||||
|
|
||||||
|
License: AGPL-3.0-only
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License.
|
||||||
|
.
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
.
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
.
|
||||||
|
On Debian systems, the full text of the GNU Affero General Public License
|
||||||
|
version 3 can be found in the file `/usr/share/common-licenses/AGPL-3'.
|
||||||
|
|
||||||
|
License: GPL-3.0+
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
.
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
.
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
.
|
||||||
|
On Debian systems, the full text of the GNU General Public License
|
||||||
|
version 3 can be found in the file `/usr/share/common-licenses/GPL-3'.
|
||||||
|
|
||||||
|
License: GPL-2.0+
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
the Free Software Foundation, either version 2 of the License, or
|
the Free Software Foundation, either version 2 of the License, or
|
||||||
@@ -22,8 +136,61 @@ License: GPL-2.0
|
|||||||
GNU General Public License for more details.
|
GNU General Public License for more details.
|
||||||
.
|
.
|
||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with this program. If not, write to the Free Software
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
.
|
||||||
|
On Debian systems, the full text of the GNU General Public License
|
||||||
|
version 2 can be found in the file `/usr/share/common-licenses/GPL-2'.
|
||||||
|
|
||||||
On Debian systems, the full text of the GNU General Public License
|
License: Expat
|
||||||
version 2 can be found in the file `/usr/share/common-licenses/GPL-2'.
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
copy of this software and associated documentation files (the "Software"),
|
||||||
|
to deal in the Software without restriction, including without limitation
|
||||||
|
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||||
|
and/or sell copies of the Software, and to permit persons to whom the
|
||||||
|
Software is furnished to do so, subject to the following conditions:
|
||||||
|
.
|
||||||
|
The above copyright notice and this permission notice shall be included
|
||||||
|
in all copies or substantial portions of the Software.
|
||||||
|
.
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||||
|
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
License: BSD-3-Clause
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
.
|
||||||
|
1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer.
|
||||||
|
.
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
.
|
||||||
|
3. Neither the name of the copyright holder nor the names of its contributors
|
||||||
|
may be used to endorse or promote products derived from this software
|
||||||
|
without specific prior written permission.
|
||||||
|
.
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||||
|
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
License: SQLite-public-domain
|
||||||
|
The author disclaims copyright to this source code. In place of a legal
|
||||||
|
notice, here is a blessing:
|
||||||
|
.
|
||||||
|
May you do good and not evil.
|
||||||
|
May you find forgiveness for yourself and understand yourself.
|
||||||
|
May you share freely, never taking more than you give.
|
||||||
|
|||||||
@@ -204,9 +204,21 @@ get_tool_urls() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Download tools for a platform
|
# Download tools for a platform
|
||||||
|
# Set CURL_PROXY=http://host:port to route GitHub downloads through an HTTP proxy
|
||||||
|
# Set GHPROXY_PREFIX=https://ghfast.top/ to prefix GitHub URLs instead
|
||||||
download_tools() {
|
download_tools() {
|
||||||
local platform="$1"
|
local platform="$1"
|
||||||
local staging="$2"
|
local staging="$2"
|
||||||
|
local curl_proxy=()
|
||||||
|
if [ -n "${CURL_PROXY:-}" ]; then
|
||||||
|
curl_proxy=(-x "$CURL_PROXY")
|
||||||
|
fi
|
||||||
|
dl() {
|
||||||
|
# dl <url> <output>
|
||||||
|
local url="$1"
|
||||||
|
[ -n "${GHPROXY_PREFIX:-}" ] && url="${GHPROXY_PREFIX%/}/$url"
|
||||||
|
curl -fsSL "${curl_proxy[@]}" "$url" -o "$2"
|
||||||
|
}
|
||||||
|
|
||||||
get_tool_urls "$platform"
|
get_tool_urls "$platform"
|
||||||
|
|
||||||
@@ -214,14 +226,14 @@ download_tools() {
|
|||||||
|
|
||||||
# ttyd
|
# ttyd
|
||||||
if [ ! -f "$staging/ttyd" ]; then
|
if [ ! -f "$staging/ttyd" ]; then
|
||||||
curl -fsSL "$TTYD_URL" -o "$staging/ttyd"
|
dl "$TTYD_URL" "$staging/ttyd"
|
||||||
chmod +x "$staging/ttyd"
|
chmod +x "$staging/ttyd"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$INCLUDE_THIRD_PARTY" = true ]; then
|
if [ "$INCLUDE_THIRD_PARTY" = true ]; then
|
||||||
# gostc
|
# gostc
|
||||||
if [ ! -f "$staging/gostc" ]; then
|
if [ ! -f "$staging/gostc" ]; then
|
||||||
curl -fsSL "$GOSTC_URL" -o /tmp/gostc.tar.gz
|
dl "$GOSTC_URL" /tmp/gostc.tar.gz
|
||||||
tar -xzf /tmp/gostc.tar.gz -C "$staging"
|
tar -xzf /tmp/gostc.tar.gz -C "$staging"
|
||||||
chmod +x "$staging/gostc"
|
chmod +x "$staging/gostc"
|
||||||
rm /tmp/gostc.tar.gz
|
rm /tmp/gostc.tar.gz
|
||||||
@@ -229,7 +241,7 @@ download_tools() {
|
|||||||
|
|
||||||
# easytier
|
# easytier
|
||||||
if [ ! -f "$staging/easytier-core" ]; then
|
if [ ! -f "$staging/easytier-core" ]; then
|
||||||
curl -fsSL "$EASYTIER_URL" -o /tmp/easytier.zip
|
dl "$EASYTIER_URL" /tmp/easytier.zip
|
||||||
unzip -o /tmp/easytier.zip -d /tmp/easytier
|
unzip -o /tmp/easytier.zip -d /tmp/easytier
|
||||||
cp "/tmp/easytier/$EASYTIER_DIR/easytier-core" "$staging/easytier-core"
|
cp "/tmp/easytier/$EASYTIER_DIR/easytier-core" "$staging/easytier-core"
|
||||||
chmod +x "$staging/easytier-core"
|
chmod +x "$staging/easytier-core"
|
||||||
@@ -311,6 +323,9 @@ build_for_platform() {
|
|||||||
|
|
||||||
local build_cmd="docker buildx build --platform $platform"
|
local build_cmd="docker buildx build --platform $platform"
|
||||||
build_cmd="$build_cmd --build-arg TARGETPLATFORM=$platform"
|
build_cmd="$build_cmd --build-arg TARGETPLATFORM=$platform"
|
||||||
|
if [ -n "${APT_MIRROR:-}" ]; then
|
||||||
|
build_cmd="$build_cmd --build-arg APT_MIRROR=$APT_MIRROR"
|
||||||
|
fi
|
||||||
|
|
||||||
if [ "$PUSH" = true ]; then
|
if [ "$PUSH" = true ]; then
|
||||||
build_cmd="$build_cmd --push"
|
build_cmd="$build_cmd --push"
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
name = "one-kvm-bluetooth-hid"
|
name = "one-kvm-bluetooth-hid"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "GPL-2.0"
|
license = "AGPL-3.0-only"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bluer = { version = "0.17", features = ["bluetoothd", "l2cap"] }
|
bluer = { version = "0.17", features = ["bluetoothd", "l2cap"] }
|
||||||
|
|||||||
155
libs/dtls/Cargo.toml
Normal file
155
libs/dtls/Cargo.toml
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
|
||||||
|
#
|
||||||
|
# When uploading crates to the registry Cargo will automatically
|
||||||
|
# "normalize" Cargo.toml files for maximal compatibility
|
||||||
|
# with all versions of Cargo and also rewrite `path` dependencies
|
||||||
|
# to registry (e.g., crates.io) dependencies.
|
||||||
|
#
|
||||||
|
# If you are reading this file be aware that the original Cargo.toml
|
||||||
|
# will likely look very different (and much more reasonable).
|
||||||
|
# See Cargo.toml.orig for the original contents.
|
||||||
|
|
||||||
|
[package]
|
||||||
|
edition = "2021"
|
||||||
|
name = "dtls"
|
||||||
|
version = "0.17.2"
|
||||||
|
authors = ["Rain Liu <yliu@webrtc.rs>"]
|
||||||
|
build = false
|
||||||
|
autolib = false
|
||||||
|
autobins = false
|
||||||
|
autoexamples = false
|
||||||
|
autotests = false
|
||||||
|
autobenches = false
|
||||||
|
description = "A pure Rust implementation of DTLS"
|
||||||
|
homepage = "https://webrtc.rs"
|
||||||
|
documentation = "https://docs.rs/dtls"
|
||||||
|
readme = "README.md"
|
||||||
|
license = "MIT/Apache-2.0"
|
||||||
|
repository = "https://github.com/webrtc-rs/webrtc/tree/master/dtls"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
pem = ["dep:pem"]
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "dtls"
|
||||||
|
path = "src/lib.rs"
|
||||||
|
|
||||||
|
[dependencies.aes]
|
||||||
|
version = "0.8"
|
||||||
|
|
||||||
|
[dependencies.aes-gcm]
|
||||||
|
version = "0.10"
|
||||||
|
|
||||||
|
[dependencies.async-trait]
|
||||||
|
version = "0.1"
|
||||||
|
|
||||||
|
[dependencies.bytecheck]
|
||||||
|
version = "0.8"
|
||||||
|
|
||||||
|
[dependencies.byteorder]
|
||||||
|
version = "1"
|
||||||
|
|
||||||
|
[dependencies.cbc]
|
||||||
|
version = "0.1"
|
||||||
|
features = [
|
||||||
|
"block-padding",
|
||||||
|
"alloc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[dependencies.ccm]
|
||||||
|
version = "0.5"
|
||||||
|
|
||||||
|
[dependencies.chacha20poly1305]
|
||||||
|
version = "0.10.1"
|
||||||
|
|
||||||
|
[dependencies.der-parser]
|
||||||
|
version = "9.0"
|
||||||
|
|
||||||
|
[dependencies.hmac]
|
||||||
|
version = "0.12"
|
||||||
|
|
||||||
|
[dependencies.log]
|
||||||
|
version = "0.4"
|
||||||
|
|
||||||
|
[dependencies.p256]
|
||||||
|
version = "0.13"
|
||||||
|
features = [
|
||||||
|
"default",
|
||||||
|
"ecdh",
|
||||||
|
"ecdsa",
|
||||||
|
]
|
||||||
|
|
||||||
|
[dependencies.p384]
|
||||||
|
version = "0.13"
|
||||||
|
|
||||||
|
[dependencies.pem]
|
||||||
|
version = "3"
|
||||||
|
optional = true
|
||||||
|
|
||||||
|
[dependencies.portable-atomic]
|
||||||
|
version = "1.6"
|
||||||
|
|
||||||
|
[dependencies.rand]
|
||||||
|
version = "0.9"
|
||||||
|
|
||||||
|
[dependencies.rand_core]
|
||||||
|
version = "0.6"
|
||||||
|
|
||||||
|
[dependencies.rcgen]
|
||||||
|
version = "0.13"
|
||||||
|
|
||||||
|
[dependencies.ring]
|
||||||
|
version = "0.17.14"
|
||||||
|
|
||||||
|
[dependencies.rkyv]
|
||||||
|
version = "0.8"
|
||||||
|
|
||||||
|
[dependencies.openssl]
|
||||||
|
version = "0.10"
|
||||||
|
features = ["vendored"]
|
||||||
|
|
||||||
|
[dependencies.rustls-pki-types]
|
||||||
|
version = "1"
|
||||||
|
|
||||||
|
[dependencies.sec1]
|
||||||
|
version = "0.7"
|
||||||
|
features = ["std"]
|
||||||
|
|
||||||
|
[dependencies.sha1]
|
||||||
|
version = "0.10"
|
||||||
|
|
||||||
|
[dependencies.sha2]
|
||||||
|
version = "0.10"
|
||||||
|
|
||||||
|
[dependencies.thiserror]
|
||||||
|
version = "1"
|
||||||
|
|
||||||
|
[dependencies.tokio]
|
||||||
|
version = "1.32.0"
|
||||||
|
features = [
|
||||||
|
"fs",
|
||||||
|
"io-util",
|
||||||
|
"io-std",
|
||||||
|
"macros",
|
||||||
|
"net",
|
||||||
|
"parking_lot",
|
||||||
|
"rt",
|
||||||
|
"rt-multi-thread",
|
||||||
|
"sync",
|
||||||
|
"time",
|
||||||
|
]
|
||||||
|
|
||||||
|
[dependencies.util]
|
||||||
|
version = "0.17.1"
|
||||||
|
features = ["conn"]
|
||||||
|
default-features = false
|
||||||
|
package = "webrtc-util"
|
||||||
|
|
||||||
|
[dependencies.x25519-dalek]
|
||||||
|
version = "2"
|
||||||
|
features = ["static_secrets"]
|
||||||
|
|
||||||
|
[dependencies.x509-parser]
|
||||||
|
version = "0.16"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
201
libs/dtls/LICENSE-APACHE
Normal file
201
libs/dtls/LICENSE-APACHE
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
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, whether in Source or
|
||||||
|
Object form, 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 modifications
|
||||||
|
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 any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to 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, "submitted"
|
||||||
|
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 are 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 otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated 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 claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by 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 a Contribution incorporated 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, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, 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 as an addendum 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 copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any 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 warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing 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 consequential 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 any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional 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 act 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
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
21
libs/dtls/LICENSE-MIT
Normal file
21
libs/dtls/LICENSE-MIT
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2021 WebRTC.rs
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
20
libs/dtls/README.md
Normal file
20
libs/dtls/README.md
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# DTLS certificate verification backend
|
||||||
|
|
||||||
|
This directory contains `dtls` 0.17.2 from the crates.io release of webrtc-rs.
|
||||||
|
The original MIT and Apache-2.0 licenses are preserved.
|
||||||
|
|
||||||
|
One-KVM patches only certificate-chain verification and its configuration to
|
||||||
|
use vendored OpenSSL instead of rustls/WebPKI. DTLS flights, record protection,
|
||||||
|
certificate signatures, SRTP negotiation and the application fingerprint check
|
||||||
|
remain unchanged. `rustls-pki-types` remains a certificate-data representation,
|
||||||
|
not a TLS implementation.
|
||||||
|
|
||||||
|
`certificate_verifier.rs` implements trust-anchor, purpose, certificate-validity
|
||||||
|
and DNS/IP identity checks. Server names do not fall back to the certificate
|
||||||
|
subject, and partial-label wildcards are rejected. No trust checks are skipped
|
||||||
|
by this backend. The upstream application-controlled `insecure_skip_verify`
|
||||||
|
policy is preserved, including WebRTC's separate certificate fingerprint check.
|
||||||
|
Authentication security level 2 rejects weak RSA keys and SHA-1 leaf signatures.
|
||||||
|
|
||||||
|
Run `cargo test --manifest-path libs/dtls/Cargo.toml --lib` after updating this
|
||||||
|
fork, and verify the application dependency tree contains no `rustls` engine.
|
||||||
50
libs/dtls/src/alert/alert_test.rs
Normal file
50
libs/dtls/src/alert/alert_test.rs
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
use std::io::{BufReader, BufWriter};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::error::Error;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_alert() -> Result<()> {
|
||||||
|
let tests = vec![
|
||||||
|
(
|
||||||
|
"Valid Alert",
|
||||||
|
vec![0x02, 0x0A],
|
||||||
|
Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::UnexpectedMessage,
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Invalid alert length",
|
||||||
|
vec![0x00],
|
||||||
|
Alert {
|
||||||
|
alert_level: AlertLevel::Invalid,
|
||||||
|
alert_description: AlertDescription::Invalid,
|
||||||
|
},
|
||||||
|
Some(Error::Other("io".to_owned())),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (name, data, wanted, unmarshal_error) in tests {
|
||||||
|
let mut reader = BufReader::new(data.as_slice());
|
||||||
|
let result = Alert::unmarshal(&mut reader);
|
||||||
|
|
||||||
|
if let Some(err) = unmarshal_error {
|
||||||
|
assert!(result.is_err(), "{name} expected error: {err}");
|
||||||
|
} else if let Ok(alert) = result {
|
||||||
|
assert_eq!(wanted, alert, "{name} expected {wanted}, but got {alert}");
|
||||||
|
|
||||||
|
let mut data2: Vec<u8> = vec![];
|
||||||
|
{
|
||||||
|
let mut writer = BufWriter::<&mut Vec<u8>>::new(data2.as_mut());
|
||||||
|
alert.marshal(&mut writer)?;
|
||||||
|
}
|
||||||
|
assert_eq!(data, data2, "{name} expected {data:?}, but got {data2:?}");
|
||||||
|
} else {
|
||||||
|
assert!(result.is_ok(), "{name} expected Ok, but has error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
189
libs/dtls/src/alert/mod.rs
Normal file
189
libs/dtls/src/alert/mod.rs
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod alert_test;
|
||||||
|
|
||||||
|
use std::fmt;
|
||||||
|
use std::io::{Read, Write};
|
||||||
|
|
||||||
|
use byteorder::{ReadBytesExt, WriteBytesExt};
|
||||||
|
|
||||||
|
use super::content::*;
|
||||||
|
use crate::error::Result;
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq, Debug)]
|
||||||
|
pub(crate) enum AlertLevel {
|
||||||
|
Warning = 1,
|
||||||
|
Fatal = 2,
|
||||||
|
Invalid,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for AlertLevel {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match *self {
|
||||||
|
AlertLevel::Warning => write!(f, "LevelWarning"),
|
||||||
|
AlertLevel::Fatal => write!(f, "LevelFatal"),
|
||||||
|
_ => write!(f, "Invalid alert level"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u8> for AlertLevel {
|
||||||
|
fn from(val: u8) -> Self {
|
||||||
|
match val {
|
||||||
|
1 => AlertLevel::Warning,
|
||||||
|
2 => AlertLevel::Fatal,
|
||||||
|
_ => AlertLevel::Invalid,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq, Debug)]
|
||||||
|
pub(crate) enum AlertDescription {
|
||||||
|
CloseNotify = 0,
|
||||||
|
UnexpectedMessage = 10,
|
||||||
|
BadRecordMac = 20,
|
||||||
|
DecryptionFailed = 21,
|
||||||
|
RecordOverflow = 22,
|
||||||
|
DecompressionFailure = 30,
|
||||||
|
HandshakeFailure = 40,
|
||||||
|
NoCertificate = 41,
|
||||||
|
BadCertificate = 42,
|
||||||
|
UnsupportedCertificate = 43,
|
||||||
|
CertificateRevoked = 44,
|
||||||
|
CertificateExpired = 45,
|
||||||
|
CertificateUnknown = 46,
|
||||||
|
IllegalParameter = 47,
|
||||||
|
UnknownCa = 48,
|
||||||
|
AccessDenied = 49,
|
||||||
|
DecodeError = 50,
|
||||||
|
DecryptError = 51,
|
||||||
|
ExportRestriction = 60,
|
||||||
|
ProtocolVersion = 70,
|
||||||
|
InsufficientSecurity = 71,
|
||||||
|
InternalError = 80,
|
||||||
|
UserCanceled = 90,
|
||||||
|
NoRenegotiation = 100,
|
||||||
|
UnsupportedExtension = 110,
|
||||||
|
UnknownPskIdentity = 115,
|
||||||
|
Invalid,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for AlertDescription {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match *self {
|
||||||
|
AlertDescription::CloseNotify => write!(f, "CloseNotify"),
|
||||||
|
AlertDescription::UnexpectedMessage => write!(f, "UnexpectedMessage"),
|
||||||
|
AlertDescription::BadRecordMac => write!(f, "BadRecordMac"),
|
||||||
|
AlertDescription::DecryptionFailed => write!(f, "DecryptionFailed"),
|
||||||
|
AlertDescription::RecordOverflow => write!(f, "RecordOverflow"),
|
||||||
|
AlertDescription::DecompressionFailure => write!(f, "DecompressionFailure"),
|
||||||
|
AlertDescription::HandshakeFailure => write!(f, "HandshakeFailure"),
|
||||||
|
AlertDescription::NoCertificate => write!(f, "NoCertificate"),
|
||||||
|
AlertDescription::BadCertificate => write!(f, "BadCertificate"),
|
||||||
|
AlertDescription::UnsupportedCertificate => write!(f, "UnsupportedCertificate"),
|
||||||
|
AlertDescription::CertificateRevoked => write!(f, "CertificateRevoked"),
|
||||||
|
AlertDescription::CertificateExpired => write!(f, "CertificateExpired"),
|
||||||
|
AlertDescription::CertificateUnknown => write!(f, "CertificateUnknown"),
|
||||||
|
AlertDescription::IllegalParameter => write!(f, "IllegalParameter"),
|
||||||
|
AlertDescription::UnknownCa => write!(f, "UnknownCA"),
|
||||||
|
AlertDescription::AccessDenied => write!(f, "AccessDenied"),
|
||||||
|
AlertDescription::DecodeError => write!(f, "DecodeError"),
|
||||||
|
AlertDescription::DecryptError => write!(f, "DecryptError"),
|
||||||
|
AlertDescription::ExportRestriction => write!(f, "ExportRestriction"),
|
||||||
|
AlertDescription::ProtocolVersion => write!(f, "ProtocolVersion"),
|
||||||
|
AlertDescription::InsufficientSecurity => write!(f, "InsufficientSecurity"),
|
||||||
|
AlertDescription::InternalError => write!(f, "InternalError"),
|
||||||
|
AlertDescription::UserCanceled => write!(f, "UserCanceled"),
|
||||||
|
AlertDescription::NoRenegotiation => write!(f, "NoRenegotiation"),
|
||||||
|
AlertDescription::UnsupportedExtension => write!(f, "UnsupportedExtension"),
|
||||||
|
AlertDescription::UnknownPskIdentity => write!(f, "UnknownPskIdentity"),
|
||||||
|
_ => write!(f, "Invalid alert description"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u8> for AlertDescription {
|
||||||
|
fn from(val: u8) -> Self {
|
||||||
|
match val {
|
||||||
|
0 => AlertDescription::CloseNotify,
|
||||||
|
10 => AlertDescription::UnexpectedMessage,
|
||||||
|
20 => AlertDescription::BadRecordMac,
|
||||||
|
21 => AlertDescription::DecryptionFailed,
|
||||||
|
22 => AlertDescription::RecordOverflow,
|
||||||
|
30 => AlertDescription::DecompressionFailure,
|
||||||
|
40 => AlertDescription::HandshakeFailure,
|
||||||
|
41 => AlertDescription::NoCertificate,
|
||||||
|
42 => AlertDescription::BadCertificate,
|
||||||
|
43 => AlertDescription::UnsupportedCertificate,
|
||||||
|
44 => AlertDescription::CertificateRevoked,
|
||||||
|
45 => AlertDescription::CertificateExpired,
|
||||||
|
46 => AlertDescription::CertificateUnknown,
|
||||||
|
47 => AlertDescription::IllegalParameter,
|
||||||
|
48 => AlertDescription::UnknownCa,
|
||||||
|
49 => AlertDescription::AccessDenied,
|
||||||
|
50 => AlertDescription::DecodeError,
|
||||||
|
51 => AlertDescription::DecryptError,
|
||||||
|
60 => AlertDescription::ExportRestriction,
|
||||||
|
70 => AlertDescription::ProtocolVersion,
|
||||||
|
71 => AlertDescription::InsufficientSecurity,
|
||||||
|
80 => AlertDescription::InternalError,
|
||||||
|
90 => AlertDescription::UserCanceled,
|
||||||
|
100 => AlertDescription::NoRenegotiation,
|
||||||
|
110 => AlertDescription::UnsupportedExtension,
|
||||||
|
115 => AlertDescription::UnknownPskIdentity,
|
||||||
|
_ => AlertDescription::Invalid,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// One of the content types supported by the TLS record layer is the
|
||||||
|
// alert type. Alert messages convey the severity of the message
|
||||||
|
// (warning or fatal) and a description of the alert. Alert messages
|
||||||
|
// with a level of fatal result in the immediate termination of the
|
||||||
|
// connection. In this case, other connections corresponding to the
|
||||||
|
// session may continue, but the session identifier MUST be invalidated,
|
||||||
|
// preventing the failed session from being used to establish new
|
||||||
|
// connections. Like other messages, alert messages are encrypted and
|
||||||
|
// compressed, as specified by the current connection state.
|
||||||
|
/// ## Specifications
|
||||||
|
///
|
||||||
|
/// * [RFC 5246 §7.2]
|
||||||
|
///
|
||||||
|
/// [RFC 5246 §7.2]: https://tools.ietf.org/html/rfc5246#section-7.2
|
||||||
|
#[derive(Copy, Clone, PartialEq, Debug)]
|
||||||
|
pub struct Alert {
|
||||||
|
pub(crate) alert_level: AlertLevel,
|
||||||
|
pub(crate) alert_description: AlertDescription,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Alert {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "Alert {}: {}", self.alert_level, self.alert_description)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Alert {
|
||||||
|
pub fn content_type(&self) -> ContentType {
|
||||||
|
ContentType::Alert
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn size(&self) -> usize {
|
||||||
|
2
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn marshal<W: Write>(&self, writer: &mut W) -> Result<()> {
|
||||||
|
writer.write_u8(self.alert_level as u8)?;
|
||||||
|
writer.write_u8(self.alert_description as u8)?;
|
||||||
|
|
||||||
|
Ok(writer.flush()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unmarshal<R: Read>(reader: &mut R) -> Result<Self> {
|
||||||
|
let alert_level = reader.read_u8()?.into();
|
||||||
|
let alert_description = reader.read_u8()?.into();
|
||||||
|
|
||||||
|
Ok(Alert {
|
||||||
|
alert_level,
|
||||||
|
alert_description,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
41
libs/dtls/src/application_data.rs
Normal file
41
libs/dtls/src/application_data.rs
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
use std::io::{Read, Write};
|
||||||
|
|
||||||
|
use super::content::*;
|
||||||
|
use crate::error::Result;
|
||||||
|
|
||||||
|
// Application data messages are carried by the record layer and are
|
||||||
|
// fragmented, compressed, and encrypted based on the current connection
|
||||||
|
// state. The messages are treated as transparent data to the record
|
||||||
|
// layer.
|
||||||
|
/// ## Specifications
|
||||||
|
///
|
||||||
|
/// * [RFC 5246 §10]
|
||||||
|
///
|
||||||
|
/// [RFC 5246 §10]: https://tools.ietf.org/html/rfc5246#section-10
|
||||||
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
|
pub struct ApplicationData {
|
||||||
|
pub data: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ApplicationData {
|
||||||
|
pub fn content_type(&self) -> ContentType {
|
||||||
|
ContentType::ApplicationData
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn size(&self) -> usize {
|
||||||
|
self.data.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn marshal<W: Write>(&self, writer: &mut W) -> Result<()> {
|
||||||
|
writer.write_all(&self.data)?;
|
||||||
|
|
||||||
|
Ok(writer.flush()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unmarshal<R: Read>(reader: &mut R) -> Result<Self> {
|
||||||
|
let mut data: Vec<u8> = vec![];
|
||||||
|
reader.read_to_end(&mut data)?;
|
||||||
|
|
||||||
|
Ok(ApplicationData { data })
|
||||||
|
}
|
||||||
|
}
|
||||||
378
libs/dtls/src/certificate_verifier.rs
Normal file
378
libs/dtls/src/certificate_verifier.rs
Normal file
@@ -0,0 +1,378 @@
|
|||||||
|
use openssl::stack::Stack;
|
||||||
|
use openssl::x509::store::X509StoreBuilder;
|
||||||
|
use openssl::x509::verify::{X509CheckFlags, X509VerifyParam};
|
||||||
|
use openssl::x509::{X509PurposeId, X509StoreContext, X509};
|
||||||
|
use rustls_pki_types::{CertificateDer, ServerName};
|
||||||
|
|
||||||
|
use crate::error::{Error, Result};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default)]
|
||||||
|
pub struct RootCertStore {
|
||||||
|
certificates: Vec<X509>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RootCertStore {
|
||||||
|
pub fn empty() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add(&mut self, certificate: CertificateDer<'_>) -> Result<()> {
|
||||||
|
self.certificates
|
||||||
|
.push(X509::from_der(certificate.as_ref()).map_err(openssl_error)?);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct CertificateVerifier {
|
||||||
|
roots: RootCertStore,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CertificateVerifier {
|
||||||
|
pub(crate) fn new(roots: RootCertStore) -> Self {
|
||||||
|
Self { roots }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn verify_client_cert(
|
||||||
|
&self,
|
||||||
|
end_entity: &CertificateDer<'_>,
|
||||||
|
intermediates: &[CertificateDer<'_>],
|
||||||
|
) -> Result<()> {
|
||||||
|
self.verify(end_entity, intermediates, X509PurposeId::SSL_CLIENT, None)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn verify_server_cert(
|
||||||
|
&self,
|
||||||
|
end_entity: &CertificateDer<'_>,
|
||||||
|
intermediates: &[CertificateDer<'_>],
|
||||||
|
server_name: &str,
|
||||||
|
) -> Result<()> {
|
||||||
|
ServerName::try_from(server_name).map_err(|error| Error::Other(error.to_string()))?;
|
||||||
|
self.verify(
|
||||||
|
end_entity,
|
||||||
|
intermediates,
|
||||||
|
X509PurposeId::SSL_SERVER,
|
||||||
|
Some(server_name),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn verify(
|
||||||
|
&self,
|
||||||
|
end_entity: &CertificateDer<'_>,
|
||||||
|
intermediates: &[CertificateDer<'_>],
|
||||||
|
purpose: X509PurposeId,
|
||||||
|
server_name: Option<&str>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let leaf = X509::from_der(end_entity.as_ref()).map_err(openssl_error)?;
|
||||||
|
let mut chain = Stack::new().map_err(openssl_error)?;
|
||||||
|
for certificate in intermediates {
|
||||||
|
chain
|
||||||
|
.push(X509::from_der(certificate.as_ref()).map_err(openssl_error)?)
|
||||||
|
.map_err(openssl_error)?;
|
||||||
|
}
|
||||||
|
let mut parameters = X509VerifyParam::new().map_err(openssl_error)?;
|
||||||
|
parameters.set_auth_level(2);
|
||||||
|
parameters.set_purpose(purpose).map_err(openssl_error)?;
|
||||||
|
parameters.set_hostflags(
|
||||||
|
X509CheckFlags::NO_PARTIAL_WILDCARDS | X509CheckFlags::NEVER_CHECK_SUBJECT,
|
||||||
|
);
|
||||||
|
if let Some(server_name) = server_name {
|
||||||
|
if let Ok(address) = server_name.parse() {
|
||||||
|
parameters.set_ip(address).map_err(openssl_error)?;
|
||||||
|
} else {
|
||||||
|
parameters.set_host(server_name).map_err(openssl_error)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut store = X509StoreBuilder::new().map_err(openssl_error)?;
|
||||||
|
store.set_param(¶meters).map_err(openssl_error)?;
|
||||||
|
for certificate in &self.roots.certificates {
|
||||||
|
store.add_cert(certificate.clone()).map_err(openssl_error)?;
|
||||||
|
}
|
||||||
|
let store = store.build();
|
||||||
|
let mut context = X509StoreContext::new().map_err(openssl_error)?;
|
||||||
|
context
|
||||||
|
.init(&store, &leaf, &chain, |context| {
|
||||||
|
if context.verify_cert()? {
|
||||||
|
Ok(None)
|
||||||
|
} else {
|
||||||
|
Ok(Some(context.error().to_string()))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.map_err(openssl_error)?
|
||||||
|
.map_or(Ok(()), |error| Err(Error::Other(error)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn openssl_error(error: openssl::error::ErrorStack) -> Error {
|
||||||
|
Error::Other(error.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use rcgen::{BasicConstraints, CertificateParams, ExtendedKeyUsagePurpose, IsCa, KeyPair};
|
||||||
|
|
||||||
|
fn signed_leaf(
|
||||||
|
parameters: CertificateParams,
|
||||||
|
) -> (CertificateVerifier, CertificateDer<'static>) {
|
||||||
|
let mut issuer_parameters = CertificateParams::new(Vec::<String>::new()).unwrap();
|
||||||
|
issuer_parameters
|
||||||
|
.distinguished_name
|
||||||
|
.push(rcgen::DnType::CommonName, "test root");
|
||||||
|
issuer_parameters.is_ca = IsCa::Ca(BasicConstraints::Unconstrained);
|
||||||
|
let issuer_key = KeyPair::generate().unwrap();
|
||||||
|
let issuer = issuer_parameters.self_signed(&issuer_key).unwrap();
|
||||||
|
let leaf_key = KeyPair::generate().unwrap();
|
||||||
|
let leaf = parameters
|
||||||
|
.signed_by(&leaf_key, &issuer, &issuer_key)
|
||||||
|
.unwrap();
|
||||||
|
let mut roots = RootCertStore::empty();
|
||||||
|
roots.add(issuer.der().clone()).unwrap();
|
||||||
|
(CertificateVerifier::new(roots), leaf.der().clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn trusted_server_matches_dns_and_ip_subject_alternative_names() {
|
||||||
|
let parameters =
|
||||||
|
CertificateParams::new(vec!["localhost".into(), "127.0.0.1".into()]).unwrap();
|
||||||
|
let (verifier, leaf) = signed_leaf(parameters);
|
||||||
|
verifier
|
||||||
|
.verify_server_cert(&leaf, &[], "localhost")
|
||||||
|
.unwrap();
|
||||||
|
assert!(verifier.verify_server_cert(&leaf, &[], "127.0.0.1").is_ok());
|
||||||
|
assert!(verifier
|
||||||
|
.verify_server_cert(&leaf, &[], "wrong.local")
|
||||||
|
.is_err());
|
||||||
|
assert!(verifier
|
||||||
|
.verify_server_cert(&leaf, &[], "127.0.0.2")
|
||||||
|
.is_err());
|
||||||
|
assert!(verifier.verify_server_cert(&leaf, &[], "").is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rejects_untrusted_and_tampered_certificates() {
|
||||||
|
let (verifier, leaf) =
|
||||||
|
signed_leaf(CertificateParams::new(vec!["localhost".into()]).unwrap());
|
||||||
|
let untrusted = CertificateVerifier::new(RootCertStore::empty());
|
||||||
|
assert!(untrusted
|
||||||
|
.verify_server_cert(&leaf, &[], "localhost")
|
||||||
|
.is_err());
|
||||||
|
let mut tampered = leaf.as_ref().to_vec();
|
||||||
|
let last = tampered.len() - 1;
|
||||||
|
tampered[last] ^= 1;
|
||||||
|
assert!(verifier
|
||||||
|
.verify_server_cert(&CertificateDer::from(tampered), &[], "localhost")
|
||||||
|
.is_err());
|
||||||
|
assert!(verifier
|
||||||
|
.verify_server_cert(&CertificateDer::from(vec![0]), &[], "localhost")
|
||||||
|
.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rejects_expired_certificate_and_wrong_extended_key_usage() {
|
||||||
|
let mut expired = CertificateParams::new(vec!["localhost".into()]).unwrap();
|
||||||
|
expired.not_before = rcgen::date_time_ymd(2019, 1, 1);
|
||||||
|
expired.not_after = rcgen::date_time_ymd(2020, 1, 1);
|
||||||
|
let (verifier, leaf) = signed_leaf(expired);
|
||||||
|
assert!(verifier
|
||||||
|
.verify_server_cert(&leaf, &[], "localhost")
|
||||||
|
.is_err());
|
||||||
|
let mut server_only = CertificateParams::new(vec!["localhost".into()]).unwrap();
|
||||||
|
server_only.extended_key_usages = vec![ExtendedKeyUsagePurpose::ServerAuth];
|
||||||
|
let (verifier, leaf) = signed_leaf(server_only);
|
||||||
|
verifier
|
||||||
|
.verify_server_cert(&leaf, &[], "localhost")
|
||||||
|
.unwrap();
|
||||||
|
assert!(verifier.verify_client_cert(&leaf, &[]).is_err());
|
||||||
|
let mut client_only = CertificateParams::new(vec!["localhost".into()]).unwrap();
|
||||||
|
client_only.extended_key_usages = vec![ExtendedKeyUsagePurpose::ClientAuth];
|
||||||
|
let (verifier, leaf) = signed_leaf(client_only);
|
||||||
|
assert!(verifier.verify_client_cert(&leaf, &[]).is_ok());
|
||||||
|
assert!(verifier
|
||||||
|
.verify_server_cert(&leaf, &[], "localhost")
|
||||||
|
.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn does_not_fall_back_to_common_name_or_partial_wildcard() {
|
||||||
|
let mut parameters = CertificateParams::new(Vec::<String>::new()).unwrap();
|
||||||
|
parameters
|
||||||
|
.distinguished_name
|
||||||
|
.push(rcgen::DnType::CommonName, "localhost");
|
||||||
|
let (verifier, leaf) = signed_leaf(parameters);
|
||||||
|
assert!(verifier
|
||||||
|
.verify_server_cert(&leaf, &[], "localhost")
|
||||||
|
.is_err());
|
||||||
|
let (verifier, leaf) =
|
||||||
|
signed_leaf(CertificateParams::new(vec!["local*.example".into()]).unwrap());
|
||||||
|
assert!(verifier
|
||||||
|
.verify_server_cert(&leaf, &[], "localhost.example")
|
||||||
|
.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rejects_weak_rsa_keys_and_sha1_leaf_signatures() {
|
||||||
|
use openssl::asn1::Asn1Time;
|
||||||
|
use openssl::hash::MessageDigest;
|
||||||
|
use openssl::pkey::PKey;
|
||||||
|
use openssl::rsa::Rsa;
|
||||||
|
use openssl::x509::extension::{BasicConstraints, SubjectAlternativeName};
|
||||||
|
use openssl::x509::X509NameBuilder;
|
||||||
|
|
||||||
|
let issuer_key = PKey::from_rsa(Rsa::generate(2048).unwrap()).unwrap();
|
||||||
|
let mut issuer_name = X509NameBuilder::new().unwrap();
|
||||||
|
issuer_name
|
||||||
|
.append_entry_by_text("CN", "strong root")
|
||||||
|
.unwrap();
|
||||||
|
let issuer_name = issuer_name.build();
|
||||||
|
let mut issuer = X509::builder().unwrap();
|
||||||
|
issuer.set_version(2).unwrap();
|
||||||
|
issuer.set_subject_name(&issuer_name).unwrap();
|
||||||
|
issuer.set_issuer_name(&issuer_name).unwrap();
|
||||||
|
issuer.set_pubkey(&issuer_key).unwrap();
|
||||||
|
issuer
|
||||||
|
.set_not_before(&Asn1Time::days_from_now(0).unwrap())
|
||||||
|
.unwrap();
|
||||||
|
issuer
|
||||||
|
.set_not_after(&Asn1Time::days_from_now(1).unwrap())
|
||||||
|
.unwrap();
|
||||||
|
issuer
|
||||||
|
.append_extension(BasicConstraints::new().critical().ca().build().unwrap())
|
||||||
|
.unwrap();
|
||||||
|
issuer.sign(&issuer_key, MessageDigest::sha256()).unwrap();
|
||||||
|
let issuer = issuer.build();
|
||||||
|
let mut roots = RootCertStore::empty();
|
||||||
|
roots
|
||||||
|
.add(CertificateDer::from(issuer.to_der().unwrap()))
|
||||||
|
.unwrap();
|
||||||
|
let verifier = CertificateVerifier::new(roots);
|
||||||
|
for (bits, digest, expected) in [
|
||||||
|
(2048, MessageDigest::sha256(), true),
|
||||||
|
(1024, MessageDigest::sha256(), false),
|
||||||
|
(2048, MessageDigest::sha1(), false),
|
||||||
|
] {
|
||||||
|
let leaf_key = PKey::from_rsa(Rsa::generate(bits).unwrap()).unwrap();
|
||||||
|
let mut leaf_name = X509NameBuilder::new().unwrap();
|
||||||
|
leaf_name.append_entry_by_text("CN", "localhost").unwrap();
|
||||||
|
let leaf_name = leaf_name.build();
|
||||||
|
let mut leaf = X509::builder().unwrap();
|
||||||
|
leaf.set_version(2).unwrap();
|
||||||
|
leaf.set_subject_name(&leaf_name).unwrap();
|
||||||
|
leaf.set_issuer_name(issuer.subject_name()).unwrap();
|
||||||
|
leaf.set_pubkey(&leaf_key).unwrap();
|
||||||
|
leaf.set_not_before(&Asn1Time::days_from_now(0).unwrap())
|
||||||
|
.unwrap();
|
||||||
|
leaf.set_not_after(&Asn1Time::days_from_now(1).unwrap())
|
||||||
|
.unwrap();
|
||||||
|
let names = SubjectAlternativeName::new()
|
||||||
|
.dns("localhost")
|
||||||
|
.build(&leaf.x509v3_context(Some(&issuer), None))
|
||||||
|
.unwrap();
|
||||||
|
leaf.append_extension(names).unwrap();
|
||||||
|
leaf.sign(&issuer_key, digest).unwrap();
|
||||||
|
let leaf = CertificateDer::from(leaf.build().to_der().unwrap());
|
||||||
|
assert_eq!(
|
||||||
|
verifier.verify_server_cert(&leaf, &[], "localhost").is_ok(),
|
||||||
|
expected
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn dtls_handshake_preserves_mutual_certificate_authentication() {
|
||||||
|
use crate::config::{ClientAuthType, Config};
|
||||||
|
use crate::conn::DTLSConn;
|
||||||
|
use crate::crypto::Certificate;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
let client_certificate =
|
||||||
|
Certificate::generate_self_signed(vec!["localhost".into()]).unwrap();
|
||||||
|
let server_certificate =
|
||||||
|
Certificate::generate_self_signed(vec!["localhost".into()]).unwrap();
|
||||||
|
let mut client_roots = RootCertStore::empty();
|
||||||
|
client_roots
|
||||||
|
.add(server_certificate.certificate[0].clone())
|
||||||
|
.unwrap();
|
||||||
|
let mut server_roots = RootCertStore::empty();
|
||||||
|
server_roots
|
||||||
|
.add(client_certificate.certificate[0].clone())
|
||||||
|
.unwrap();
|
||||||
|
let client_socket = Arc::new(tokio::net::UdpSocket::bind("127.0.0.1:0").await.unwrap());
|
||||||
|
let server_socket = Arc::new(tokio::net::UdpSocket::bind("127.0.0.1:0").await.unwrap());
|
||||||
|
client_socket
|
||||||
|
.connect(server_socket.local_addr().unwrap())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
server_socket
|
||||||
|
.connect(client_socket.local_addr().unwrap())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let client_config = Config {
|
||||||
|
certificates: vec![client_certificate],
|
||||||
|
roots_cas: client_roots,
|
||||||
|
server_name: "localhost".into(),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let server_config = Config {
|
||||||
|
certificates: vec![server_certificate],
|
||||||
|
client_cas: server_roots,
|
||||||
|
client_auth: ClientAuthType::RequireAndVerifyClientCert,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let (client, server) = tokio::time::timeout(Duration::from_secs(10), async {
|
||||||
|
tokio::try_join!(
|
||||||
|
DTLSConn::new(client_socket, client_config, true, None),
|
||||||
|
DTLSConn::new(server_socket, server_config, false, None),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
client
|
||||||
|
.write(b"authenticated", Some(Duration::from_secs(5)))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let mut buffer = [0; 64];
|
||||||
|
let count = server
|
||||||
|
.read(&mut buffer, Some(Duration::from_secs(5)))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(&buffer[..count], b"authenticated");
|
||||||
|
client.close().await.unwrap();
|
||||||
|
server.close().await.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn verifies_intermediate_chain_without_implicitly_trusting_it() {
|
||||||
|
let mut root_parameters = CertificateParams::new(Vec::<String>::new()).unwrap();
|
||||||
|
root_parameters
|
||||||
|
.distinguished_name
|
||||||
|
.push(rcgen::DnType::CommonName, "test root");
|
||||||
|
root_parameters.is_ca = IsCa::Ca(BasicConstraints::Unconstrained);
|
||||||
|
let root_key = KeyPair::generate().unwrap();
|
||||||
|
let root = root_parameters.self_signed(&root_key).unwrap();
|
||||||
|
let mut intermediate_parameters = CertificateParams::new(Vec::<String>::new()).unwrap();
|
||||||
|
intermediate_parameters
|
||||||
|
.distinguished_name
|
||||||
|
.push(rcgen::DnType::CommonName, "test intermediate");
|
||||||
|
intermediate_parameters.is_ca = IsCa::Ca(BasicConstraints::Constrained(0));
|
||||||
|
let intermediate_key = KeyPair::generate().unwrap();
|
||||||
|
let intermediate = intermediate_parameters
|
||||||
|
.signed_by(&intermediate_key, &root, &root_key)
|
||||||
|
.unwrap();
|
||||||
|
let leaf_key = KeyPair::generate().unwrap();
|
||||||
|
let leaf = CertificateParams::new(vec!["localhost".into()])
|
||||||
|
.unwrap()
|
||||||
|
.signed_by(&leaf_key, &intermediate, &intermediate_key)
|
||||||
|
.unwrap();
|
||||||
|
let mut roots = RootCertStore::empty();
|
||||||
|
roots.add(root.der().clone()).unwrap();
|
||||||
|
let verifier = CertificateVerifier::new(roots);
|
||||||
|
assert!(verifier
|
||||||
|
.verify_server_cert(leaf.der(), &[intermediate.der().clone()], "localhost")
|
||||||
|
.is_ok());
|
||||||
|
assert!(verifier
|
||||||
|
.verify_server_cert(leaf.der(), &[], "localhost")
|
||||||
|
.is_err());
|
||||||
|
}
|
||||||
|
}
|
||||||
37
libs/dtls/src/change_cipher_spec/change_cipher_spec_test.rs
Normal file
37
libs/dtls/src/change_cipher_spec/change_cipher_spec_test.rs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
use std::io::{BufReader, BufWriter};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_change_cipher_spec_round_trip() -> Result<()> {
|
||||||
|
let c = ChangeCipherSpec {};
|
||||||
|
let mut raw = vec![];
|
||||||
|
{
|
||||||
|
let mut writer = BufWriter::<&mut Vec<u8>>::new(raw.as_mut());
|
||||||
|
c.marshal(&mut writer)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut reader = BufReader::new(raw.as_slice());
|
||||||
|
let cnew = ChangeCipherSpec::unmarshal(&mut reader)?;
|
||||||
|
assert_eq!(
|
||||||
|
c, cnew,
|
||||||
|
"ChangeCipherSpec round trip: got {cnew:?}, want {c:?}"
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_change_cipher_spec_invalid() -> Result<()> {
|
||||||
|
let data = vec![0x00];
|
||||||
|
|
||||||
|
let mut reader = BufReader::new(data.as_slice());
|
||||||
|
let result = ChangeCipherSpec::unmarshal(&mut reader);
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(_) => panic!("must be error"),
|
||||||
|
Err(err) => assert_eq!(err.to_string(), Error::ErrInvalidCipherSpec.to_string()),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
46
libs/dtls/src/change_cipher_spec/mod.rs
Normal file
46
libs/dtls/src/change_cipher_spec/mod.rs
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod change_cipher_spec_test;
|
||||||
|
|
||||||
|
use std::io::{Read, Write};
|
||||||
|
|
||||||
|
use byteorder::{ReadBytesExt, WriteBytesExt};
|
||||||
|
|
||||||
|
use super::content::*;
|
||||||
|
use super::error::*;
|
||||||
|
|
||||||
|
// The change cipher spec protocol exists to signal transitions in
|
||||||
|
// ciphering strategies. The protocol consists of a single message,
|
||||||
|
// which is encrypted and compressed under the current (not the pending)
|
||||||
|
// connection state. The message consists of a single byte of value 1.
|
||||||
|
/// ## Specifications
|
||||||
|
///
|
||||||
|
/// * [RFC 5246 §7.1]
|
||||||
|
///
|
||||||
|
/// [RFC 5246 §7.1]: https://tools.ietf.org/html/rfc5246#section-7.1
|
||||||
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
|
pub struct ChangeCipherSpec;
|
||||||
|
|
||||||
|
impl ChangeCipherSpec {
|
||||||
|
pub fn content_type(&self) -> ContentType {
|
||||||
|
ContentType::ChangeCipherSpec
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn size(&self) -> usize {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn marshal<W: Write>(&self, writer: &mut W) -> Result<()> {
|
||||||
|
writer.write_u8(0x01)?;
|
||||||
|
|
||||||
|
Ok(writer.flush()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unmarshal<R: Read>(reader: &mut R) -> Result<Self> {
|
||||||
|
let data = reader.read_u8()?;
|
||||||
|
if data != 0x01 {
|
||||||
|
return Err(Error::ErrInvalidCipherSpec);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(ChangeCipherSpec {})
|
||||||
|
}
|
||||||
|
}
|
||||||
112
libs/dtls/src/cipher_suite/cipher_suite_aes_128_ccm.rs
Normal file
112
libs/dtls/src/cipher_suite/cipher_suite_aes_128_ccm.rs
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
use super::*;
|
||||||
|
use crate::client_certificate_type::ClientCertificateType;
|
||||||
|
use crate::crypto::crypto_ccm::{CryptoCcm, CryptoCcmTagLen};
|
||||||
|
use crate::prf::*;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct CipherSuiteAes128Ccm {
|
||||||
|
ccm: Option<CryptoCcm>,
|
||||||
|
client_certificate_type: ClientCertificateType,
|
||||||
|
id: CipherSuiteId,
|
||||||
|
psk: bool,
|
||||||
|
crypto_ccm_tag_len: CryptoCcmTagLen,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CipherSuiteAes128Ccm {
|
||||||
|
const PRF_MAC_LEN: usize = 0;
|
||||||
|
const PRF_KEY_LEN: usize = 16;
|
||||||
|
const PRF_IV_LEN: usize = 4;
|
||||||
|
|
||||||
|
pub fn new(
|
||||||
|
client_certificate_type: ClientCertificateType,
|
||||||
|
id: CipherSuiteId,
|
||||||
|
psk: bool,
|
||||||
|
crypto_ccm_tag_len: CryptoCcmTagLen,
|
||||||
|
) -> Self {
|
||||||
|
CipherSuiteAes128Ccm {
|
||||||
|
ccm: None,
|
||||||
|
client_certificate_type,
|
||||||
|
id,
|
||||||
|
psk,
|
||||||
|
crypto_ccm_tag_len,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CipherSuite for CipherSuiteAes128Ccm {
|
||||||
|
fn to_string(&self) -> String {
|
||||||
|
format!("{}", self.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn id(&self) -> CipherSuiteId {
|
||||||
|
self.id
|
||||||
|
}
|
||||||
|
|
||||||
|
fn certificate_type(&self) -> ClientCertificateType {
|
||||||
|
self.client_certificate_type
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hash_func(&self) -> CipherSuiteHash {
|
||||||
|
CipherSuiteHash::Sha256
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_psk(&self) -> bool {
|
||||||
|
self.psk
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_initialized(&self) -> bool {
|
||||||
|
self.ccm.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(
|
||||||
|
&mut self,
|
||||||
|
master_secret: &[u8],
|
||||||
|
client_random: &[u8],
|
||||||
|
server_random: &[u8],
|
||||||
|
is_client: bool,
|
||||||
|
) -> Result<()> {
|
||||||
|
let keys = prf_encryption_keys(
|
||||||
|
master_secret,
|
||||||
|
client_random,
|
||||||
|
server_random,
|
||||||
|
CipherSuiteAes128Ccm::PRF_MAC_LEN,
|
||||||
|
CipherSuiteAes128Ccm::PRF_KEY_LEN,
|
||||||
|
CipherSuiteAes128Ccm::PRF_IV_LEN,
|
||||||
|
self.hash_func(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
if is_client {
|
||||||
|
self.ccm = Some(CryptoCcm::new(
|
||||||
|
&self.crypto_ccm_tag_len,
|
||||||
|
&keys.client_write_key,
|
||||||
|
&keys.client_write_iv,
|
||||||
|
&keys.server_write_key,
|
||||||
|
&keys.server_write_iv,
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
self.ccm = Some(CryptoCcm::new(
|
||||||
|
&self.crypto_ccm_tag_len,
|
||||||
|
&keys.server_write_key,
|
||||||
|
&keys.server_write_iv,
|
||||||
|
&keys.client_write_key,
|
||||||
|
&keys.client_write_iv,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encrypt(&self, pkt_rlh: &RecordLayerHeader, raw: &[u8]) -> Result<Vec<u8>> {
|
||||||
|
let ccm = self.ccm.as_ref().ok_or(Error::Other(
|
||||||
|
"CipherSuite has not been initialized, unable to encrypt".to_owned(),
|
||||||
|
))?;
|
||||||
|
ccm.encrypt(pkt_rlh, raw)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decrypt(&self, input: &[u8]) -> Result<Vec<u8>> {
|
||||||
|
let ccm = self.ccm.as_ref().ok_or(Error::Other(
|
||||||
|
"CipherSuite has not been initialized, unable to decrypt".to_owned(),
|
||||||
|
))?;
|
||||||
|
ccm.decrypt(input)
|
||||||
|
}
|
||||||
|
}
|
||||||
107
libs/dtls/src/cipher_suite/cipher_suite_aes_128_gcm_sha256.rs
Normal file
107
libs/dtls/src/cipher_suite/cipher_suite_aes_128_gcm_sha256.rs
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
use super::*;
|
||||||
|
use crate::crypto::crypto_gcm::*;
|
||||||
|
use crate::prf::*;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct CipherSuiteAes128GcmSha256 {
|
||||||
|
gcm: Option<CryptoGcm>,
|
||||||
|
rsa: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CipherSuiteAes128GcmSha256 {
|
||||||
|
const PRF_MAC_LEN: usize = 0;
|
||||||
|
const PRF_KEY_LEN: usize = 16;
|
||||||
|
const PRF_IV_LEN: usize = 4;
|
||||||
|
|
||||||
|
pub fn new(rsa: bool) -> Self {
|
||||||
|
CipherSuiteAes128GcmSha256 { gcm: None, rsa }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CipherSuite for CipherSuiteAes128GcmSha256 {
|
||||||
|
fn to_string(&self) -> String {
|
||||||
|
if self.rsa {
|
||||||
|
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256".to_owned()
|
||||||
|
} else {
|
||||||
|
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256".to_owned()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn id(&self) -> CipherSuiteId {
|
||||||
|
if self.rsa {
|
||||||
|
CipherSuiteId::Tls_Ecdhe_Rsa_With_Aes_128_Gcm_Sha256
|
||||||
|
} else {
|
||||||
|
CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_128_Gcm_Sha256
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn certificate_type(&self) -> ClientCertificateType {
|
||||||
|
if self.rsa {
|
||||||
|
ClientCertificateType::RsaSign
|
||||||
|
} else {
|
||||||
|
ClientCertificateType::EcdsaSign
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hash_func(&self) -> CipherSuiteHash {
|
||||||
|
CipherSuiteHash::Sha256
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_psk(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_initialized(&self) -> bool {
|
||||||
|
self.gcm.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(
|
||||||
|
&mut self,
|
||||||
|
master_secret: &[u8],
|
||||||
|
client_random: &[u8],
|
||||||
|
server_random: &[u8],
|
||||||
|
is_client: bool,
|
||||||
|
) -> Result<()> {
|
||||||
|
let keys = prf_encryption_keys(
|
||||||
|
master_secret,
|
||||||
|
client_random,
|
||||||
|
server_random,
|
||||||
|
CipherSuiteAes128GcmSha256::PRF_MAC_LEN,
|
||||||
|
CipherSuiteAes128GcmSha256::PRF_KEY_LEN,
|
||||||
|
CipherSuiteAes128GcmSha256::PRF_IV_LEN,
|
||||||
|
self.hash_func(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
if is_client {
|
||||||
|
self.gcm = Some(CryptoGcm::new(
|
||||||
|
&keys.client_write_key,
|
||||||
|
&keys.client_write_iv,
|
||||||
|
&keys.server_write_key,
|
||||||
|
&keys.server_write_iv,
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
self.gcm = Some(CryptoGcm::new(
|
||||||
|
&keys.server_write_key,
|
||||||
|
&keys.server_write_iv,
|
||||||
|
&keys.client_write_key,
|
||||||
|
&keys.client_write_iv,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encrypt(&self, pkt_rlh: &RecordLayerHeader, raw: &[u8]) -> Result<Vec<u8>> {
|
||||||
|
let cg = self.gcm.as_ref().ok_or(Error::Other(
|
||||||
|
"CipherSuite has not been initialized, unable to encrypt".to_owned(),
|
||||||
|
))?;
|
||||||
|
cg.encrypt(pkt_rlh, raw)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decrypt(&self, input: &[u8]) -> Result<Vec<u8>> {
|
||||||
|
let cg = self.gcm.as_ref().ok_or(Error::Other(
|
||||||
|
"CipherSuite has not been initialized, unable to decrypt".to_owned(),
|
||||||
|
))?;
|
||||||
|
cg.decrypt(input)
|
||||||
|
}
|
||||||
|
}
|
||||||
107
libs/dtls/src/cipher_suite/cipher_suite_aes_256_cbc_sha.rs
Normal file
107
libs/dtls/src/cipher_suite/cipher_suite_aes_256_cbc_sha.rs
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
use super::*;
|
||||||
|
use crate::crypto::crypto_cbc::*;
|
||||||
|
use crate::prf::*;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct CipherSuiteAes256CbcSha {
|
||||||
|
cbc: Option<CryptoCbc>,
|
||||||
|
rsa: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CipherSuiteAes256CbcSha {
|
||||||
|
const PRF_MAC_LEN: usize = 20;
|
||||||
|
const PRF_KEY_LEN: usize = 32;
|
||||||
|
const PRF_IV_LEN: usize = 16;
|
||||||
|
|
||||||
|
pub fn new(rsa: bool) -> Self {
|
||||||
|
CipherSuiteAes256CbcSha { cbc: None, rsa }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CipherSuite for CipherSuiteAes256CbcSha {
|
||||||
|
fn to_string(&self) -> String {
|
||||||
|
if self.rsa {
|
||||||
|
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA".to_owned()
|
||||||
|
} else {
|
||||||
|
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA".to_owned()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn id(&self) -> CipherSuiteId {
|
||||||
|
if self.rsa {
|
||||||
|
CipherSuiteId::Tls_Ecdhe_Rsa_With_Aes_256_Cbc_Sha
|
||||||
|
} else {
|
||||||
|
CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_256_Cbc_Sha
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn certificate_type(&self) -> ClientCertificateType {
|
||||||
|
if self.rsa {
|
||||||
|
ClientCertificateType::RsaSign
|
||||||
|
} else {
|
||||||
|
ClientCertificateType::EcdsaSign
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hash_func(&self) -> CipherSuiteHash {
|
||||||
|
CipherSuiteHash::Sha256
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_psk(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_initialized(&self) -> bool {
|
||||||
|
self.cbc.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(
|
||||||
|
&mut self,
|
||||||
|
master_secret: &[u8],
|
||||||
|
client_random: &[u8],
|
||||||
|
server_random: &[u8],
|
||||||
|
is_client: bool,
|
||||||
|
) -> Result<()> {
|
||||||
|
let keys = prf_encryption_keys(
|
||||||
|
master_secret,
|
||||||
|
client_random,
|
||||||
|
server_random,
|
||||||
|
CipherSuiteAes256CbcSha::PRF_MAC_LEN,
|
||||||
|
CipherSuiteAes256CbcSha::PRF_KEY_LEN,
|
||||||
|
CipherSuiteAes256CbcSha::PRF_IV_LEN,
|
||||||
|
self.hash_func(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
if is_client {
|
||||||
|
self.cbc = Some(CryptoCbc::new(
|
||||||
|
&keys.client_write_key,
|
||||||
|
&keys.client_mac_key,
|
||||||
|
&keys.server_write_key,
|
||||||
|
&keys.server_mac_key,
|
||||||
|
)?);
|
||||||
|
} else {
|
||||||
|
self.cbc = Some(CryptoCbc::new(
|
||||||
|
&keys.server_write_key,
|
||||||
|
&keys.server_mac_key,
|
||||||
|
&keys.client_write_key,
|
||||||
|
&keys.client_mac_key,
|
||||||
|
)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encrypt(&self, pkt_rlh: &RecordLayerHeader, raw: &[u8]) -> Result<Vec<u8>> {
|
||||||
|
let cg = self.cbc.as_ref().ok_or(Error::Other(
|
||||||
|
"CipherSuite has not been initialized, unable to encrypt".to_owned(),
|
||||||
|
))?;
|
||||||
|
cg.encrypt(pkt_rlh, raw)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decrypt(&self, input: &[u8]) -> Result<Vec<u8>> {
|
||||||
|
let cg = self.cbc.as_ref().ok_or(Error::Other(
|
||||||
|
"CipherSuite has not been initialized, unable to decrypt".to_owned(),
|
||||||
|
))?;
|
||||||
|
cg.decrypt(input)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
use super::*;
|
||||||
|
use crate::crypto::crypto_chacha20::*;
|
||||||
|
use crate::prf::*;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct CipherSuiteChaCha20Poly1305Sha256 {
|
||||||
|
rsa: bool,
|
||||||
|
cipher: Option<CryptoChaCha20>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CipherSuiteChaCha20Poly1305Sha256 {
|
||||||
|
const PRF_MAC_LEN: usize = 0;
|
||||||
|
const PRF_KEY_LEN: usize = 32;
|
||||||
|
const PRF_IV_LEN: usize = 12;
|
||||||
|
|
||||||
|
pub fn new(rsa: bool) -> Self {
|
||||||
|
CipherSuiteChaCha20Poly1305Sha256 { rsa, cipher: None }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CipherSuite for CipherSuiteChaCha20Poly1305Sha256 {
|
||||||
|
fn to_string(&self) -> String {
|
||||||
|
if self.rsa {
|
||||||
|
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256".to_owned()
|
||||||
|
} else {
|
||||||
|
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256".to_owned()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn id(&self) -> CipherSuiteId {
|
||||||
|
if self.rsa {
|
||||||
|
CipherSuiteId::Tls_Ecdhe_Rsa_With_ChaCha20_Poly1305_Sha256
|
||||||
|
} else {
|
||||||
|
CipherSuiteId::Tls_Ecdhe_Ecdsa_With_ChaCha20_Poly1305_Sha256
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn certificate_type(&self) -> ClientCertificateType {
|
||||||
|
if self.rsa {
|
||||||
|
ClientCertificateType::RsaSign
|
||||||
|
} else {
|
||||||
|
ClientCertificateType::EcdsaSign
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hash_func(&self) -> CipherSuiteHash {
|
||||||
|
CipherSuiteHash::Sha256
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_psk(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_initialized(&self) -> bool {
|
||||||
|
self.cipher.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(
|
||||||
|
&mut self,
|
||||||
|
master_secret: &[u8],
|
||||||
|
client_random: &[u8],
|
||||||
|
server_random: &[u8],
|
||||||
|
is_client: bool,
|
||||||
|
) -> Result<()> {
|
||||||
|
let keys = prf_encryption_keys(
|
||||||
|
master_secret,
|
||||||
|
client_random,
|
||||||
|
server_random,
|
||||||
|
CipherSuiteChaCha20Poly1305Sha256::PRF_MAC_LEN,
|
||||||
|
CipherSuiteChaCha20Poly1305Sha256::PRF_KEY_LEN,
|
||||||
|
CipherSuiteChaCha20Poly1305Sha256::PRF_IV_LEN,
|
||||||
|
self.hash_func(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
if is_client {
|
||||||
|
self.cipher = Some(CryptoChaCha20::new(
|
||||||
|
&keys.client_write_key,
|
||||||
|
&keys.client_write_iv,
|
||||||
|
&keys.server_write_key,
|
||||||
|
&keys.server_write_iv,
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
self.cipher = Some(CryptoChaCha20::new(
|
||||||
|
&keys.server_write_key,
|
||||||
|
&keys.server_write_iv,
|
||||||
|
&keys.client_write_key,
|
||||||
|
&keys.client_write_iv,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encrypt(&self, pkt_rlh: &RecordLayerHeader, raw: &[u8]) -> Result<Vec<u8>> {
|
||||||
|
let cg = self.cipher.as_ref().ok_or(Error::Other(
|
||||||
|
"CipherSuite has not been initialized, unable to encrypt".to_owned(),
|
||||||
|
))?;
|
||||||
|
cg.encrypt(pkt_rlh, raw)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decrypt(&self, input: &[u8]) -> Result<Vec<u8>> {
|
||||||
|
let cg = self.cipher.as_ref().ok_or(Error::Other(
|
||||||
|
"CipherSuite has not been initialized, unable to decrypt".to_owned(),
|
||||||
|
))?;
|
||||||
|
cg.decrypt(input)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
use super::*;
|
||||||
|
use crate::cipher_suite::cipher_suite_aes_128_ccm::CipherSuiteAes128Ccm;
|
||||||
|
use crate::crypto::crypto_ccm::CryptoCcmTagLen;
|
||||||
|
|
||||||
|
pub fn new_cipher_suite_tls_ecdhe_ecdsa_with_aes_128_ccm() -> CipherSuiteAes128Ccm {
|
||||||
|
CipherSuiteAes128Ccm::new(
|
||||||
|
ClientCertificateType::EcdsaSign,
|
||||||
|
CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_128_Ccm,
|
||||||
|
false,
|
||||||
|
CryptoCcmTagLen::CryptoCcmTagLength,
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
use super::*;
|
||||||
|
use crate::cipher_suite::cipher_suite_aes_128_ccm::CipherSuiteAes128Ccm;
|
||||||
|
use crate::crypto::crypto_ccm::CryptoCcmTagLen;
|
||||||
|
|
||||||
|
pub fn new_cipher_suite_tls_ecdhe_ecdsa_with_aes_128_ccm8() -> CipherSuiteAes128Ccm {
|
||||||
|
CipherSuiteAes128Ccm::new(
|
||||||
|
ClientCertificateType::EcdsaSign,
|
||||||
|
CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_128_Ccm_8,
|
||||||
|
false,
|
||||||
|
CryptoCcmTagLen::CryptoCcm8TagLength,
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
use super::*;
|
||||||
|
use crate::cipher_suite::cipher_suite_aes_128_ccm::CipherSuiteAes128Ccm;
|
||||||
|
use crate::crypto::crypto_ccm::CryptoCcmTagLen;
|
||||||
|
|
||||||
|
pub fn new_cipher_suite_tls_psk_with_aes_128_ccm() -> CipherSuiteAes128Ccm {
|
||||||
|
CipherSuiteAes128Ccm::new(
|
||||||
|
ClientCertificateType::Unsupported,
|
||||||
|
CipherSuiteId::Tls_Psk_With_Aes_128_Ccm,
|
||||||
|
true,
|
||||||
|
CryptoCcmTagLen::CryptoCcmTagLength,
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
use super::*;
|
||||||
|
use crate::cipher_suite::cipher_suite_aes_128_ccm::CipherSuiteAes128Ccm;
|
||||||
|
use crate::crypto::crypto_ccm::CryptoCcmTagLen;
|
||||||
|
|
||||||
|
pub fn new_cipher_suite_tls_psk_with_aes_128_ccm8() -> CipherSuiteAes128Ccm {
|
||||||
|
CipherSuiteAes128Ccm::new(
|
||||||
|
ClientCertificateType::Unsupported,
|
||||||
|
CipherSuiteId::Tls_Psk_With_Aes_128_Ccm_8,
|
||||||
|
true,
|
||||||
|
CryptoCcmTagLen::CryptoCcm8TagLength,
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
use super::*;
|
||||||
|
use crate::crypto::crypto_gcm::*;
|
||||||
|
use crate::prf::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Default)]
|
||||||
|
pub struct CipherSuiteTlsPskWithAes128GcmSha256 {
|
||||||
|
gcm: Option<CryptoGcm>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CipherSuiteTlsPskWithAes128GcmSha256 {
|
||||||
|
const PRF_MAC_LEN: usize = 0;
|
||||||
|
const PRF_KEY_LEN: usize = 16;
|
||||||
|
const PRF_IV_LEN: usize = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CipherSuite for CipherSuiteTlsPskWithAes128GcmSha256 {
|
||||||
|
fn to_string(&self) -> String {
|
||||||
|
"TLS_PSK_WITH_AES_128_GCM_SHA256".to_owned()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn id(&self) -> CipherSuiteId {
|
||||||
|
CipherSuiteId::Tls_Psk_With_Aes_128_Gcm_Sha256
|
||||||
|
}
|
||||||
|
|
||||||
|
fn certificate_type(&self) -> ClientCertificateType {
|
||||||
|
ClientCertificateType::Unsupported
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hash_func(&self) -> CipherSuiteHash {
|
||||||
|
CipherSuiteHash::Sha256
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_psk(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_initialized(&self) -> bool {
|
||||||
|
self.gcm.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(
|
||||||
|
&mut self,
|
||||||
|
master_secret: &[u8],
|
||||||
|
client_random: &[u8],
|
||||||
|
server_random: &[u8],
|
||||||
|
is_client: bool,
|
||||||
|
) -> Result<()> {
|
||||||
|
let keys = prf_encryption_keys(
|
||||||
|
master_secret,
|
||||||
|
client_random,
|
||||||
|
server_random,
|
||||||
|
CipherSuiteTlsPskWithAes128GcmSha256::PRF_MAC_LEN,
|
||||||
|
CipherSuiteTlsPskWithAes128GcmSha256::PRF_KEY_LEN,
|
||||||
|
CipherSuiteTlsPskWithAes128GcmSha256::PRF_IV_LEN,
|
||||||
|
self.hash_func(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
if is_client {
|
||||||
|
self.gcm = Some(CryptoGcm::new(
|
||||||
|
&keys.client_write_key,
|
||||||
|
&keys.client_write_iv,
|
||||||
|
&keys.server_write_key,
|
||||||
|
&keys.server_write_iv,
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
self.gcm = Some(CryptoGcm::new(
|
||||||
|
&keys.server_write_key,
|
||||||
|
&keys.server_write_iv,
|
||||||
|
&keys.client_write_key,
|
||||||
|
&keys.client_write_iv,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encrypt(&self, pkt_rlh: &RecordLayerHeader, raw: &[u8]) -> Result<Vec<u8>> {
|
||||||
|
let cg = self.gcm.as_ref().ok_or(Error::Other(
|
||||||
|
"CipherSuite has not been initialized, unable to encrypt".to_owned(),
|
||||||
|
))?;
|
||||||
|
cg.encrypt(pkt_rlh, raw)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decrypt(&self, input: &[u8]) -> Result<Vec<u8>> {
|
||||||
|
let cg = self.gcm.as_ref().ok_or(Error::Other(
|
||||||
|
"CipherSuite has not been initialized, unable to decrypt".to_owned(),
|
||||||
|
))?;
|
||||||
|
cg.decrypt(input)
|
||||||
|
}
|
||||||
|
}
|
||||||
287
libs/dtls/src/cipher_suite/mod.rs
Normal file
287
libs/dtls/src/cipher_suite/mod.rs
Normal file
@@ -0,0 +1,287 @@
|
|||||||
|
pub mod cipher_suite_aes_128_ccm;
|
||||||
|
pub mod cipher_suite_aes_128_gcm_sha256;
|
||||||
|
pub mod cipher_suite_aes_256_cbc_sha;
|
||||||
|
pub mod cipher_suite_chacha20_poly1305_sha256;
|
||||||
|
pub mod cipher_suite_tls_ecdhe_ecdsa_with_aes_128_ccm;
|
||||||
|
pub mod cipher_suite_tls_ecdhe_ecdsa_with_aes_128_ccm8;
|
||||||
|
pub mod cipher_suite_tls_psk_with_aes_128_ccm;
|
||||||
|
pub mod cipher_suite_tls_psk_with_aes_128_ccm8;
|
||||||
|
pub mod cipher_suite_tls_psk_with_aes_128_gcm_sha256;
|
||||||
|
|
||||||
|
use std::fmt;
|
||||||
|
use std::marker::{Send, Sync};
|
||||||
|
|
||||||
|
use cipher_suite_aes_128_gcm_sha256::*;
|
||||||
|
use cipher_suite_aes_256_cbc_sha::*;
|
||||||
|
use cipher_suite_chacha20_poly1305_sha256::*;
|
||||||
|
use cipher_suite_tls_ecdhe_ecdsa_with_aes_128_ccm::*;
|
||||||
|
use cipher_suite_tls_ecdhe_ecdsa_with_aes_128_ccm8::*;
|
||||||
|
use cipher_suite_tls_psk_with_aes_128_ccm::*;
|
||||||
|
use cipher_suite_tls_psk_with_aes_128_ccm8::*;
|
||||||
|
use cipher_suite_tls_psk_with_aes_128_gcm_sha256::*;
|
||||||
|
|
||||||
|
use super::client_certificate_type::*;
|
||||||
|
use super::error::*;
|
||||||
|
use super::record_layer::record_layer_header::*;
|
||||||
|
|
||||||
|
// CipherSuiteID is an ID for our supported CipherSuites
|
||||||
|
// Supported Cipher Suites
|
||||||
|
#[allow(non_camel_case_types)]
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub enum CipherSuiteId {
|
||||||
|
// AES-128-CCM
|
||||||
|
Tls_Ecdhe_Ecdsa_With_Aes_128_Ccm = 0xc0ac,
|
||||||
|
Tls_Ecdhe_Ecdsa_With_Aes_128_Ccm_8 = 0xc0ae,
|
||||||
|
|
||||||
|
// AES-128-GCM-SHA256
|
||||||
|
Tls_Ecdhe_Ecdsa_With_Aes_128_Gcm_Sha256 = 0xc02b,
|
||||||
|
Tls_Ecdhe_Rsa_With_Aes_128_Gcm_Sha256 = 0xc02f,
|
||||||
|
|
||||||
|
// AES-256-CBC-SHA
|
||||||
|
Tls_Ecdhe_Ecdsa_With_Aes_256_Cbc_Sha = 0xc00a,
|
||||||
|
Tls_Ecdhe_Rsa_With_Aes_256_Cbc_Sha = 0xc014,
|
||||||
|
|
||||||
|
Tls_Psk_With_Aes_128_Ccm = 0xc0a4,
|
||||||
|
Tls_Psk_With_Aes_128_Ccm_8 = 0xc0a8,
|
||||||
|
Tls_Psk_With_Aes_128_Gcm_Sha256 = 0x00a8,
|
||||||
|
|
||||||
|
// CHACHA20_POLY1305_SHA256
|
||||||
|
Tls_Ecdhe_Rsa_With_ChaCha20_Poly1305_Sha256 = 0xcca8,
|
||||||
|
Tls_Ecdhe_Ecdsa_With_ChaCha20_Poly1305_Sha256 = 0xcca9,
|
||||||
|
|
||||||
|
Unsupported,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for CipherSuiteId {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match *self {
|
||||||
|
CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_128_Ccm => {
|
||||||
|
write!(f, "TLS_ECDHE_ECDSA_WITH_AES_128_CCM")
|
||||||
|
}
|
||||||
|
CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_128_Ccm_8 => {
|
||||||
|
write!(f, "TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8")
|
||||||
|
}
|
||||||
|
CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_128_Gcm_Sha256 => {
|
||||||
|
write!(f, "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256")
|
||||||
|
}
|
||||||
|
CipherSuiteId::Tls_Ecdhe_Rsa_With_Aes_128_Gcm_Sha256 => {
|
||||||
|
write!(f, "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256")
|
||||||
|
}
|
||||||
|
CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_256_Cbc_Sha => {
|
||||||
|
write!(f, "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA")
|
||||||
|
}
|
||||||
|
CipherSuiteId::Tls_Ecdhe_Rsa_With_Aes_256_Cbc_Sha => {
|
||||||
|
write!(f, "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA")
|
||||||
|
}
|
||||||
|
CipherSuiteId::Tls_Psk_With_Aes_128_Ccm => write!(f, "TLS_PSK_WITH_AES_128_CCM"),
|
||||||
|
CipherSuiteId::Tls_Psk_With_Aes_128_Ccm_8 => write!(f, "TLS_PSK_WITH_AES_128_CCM_8"),
|
||||||
|
CipherSuiteId::Tls_Psk_With_Aes_128_Gcm_Sha256 => {
|
||||||
|
write!(f, "TLS_PSK_WITH_AES_128_GCM_SHA256")
|
||||||
|
}
|
||||||
|
CipherSuiteId::Tls_Ecdhe_Rsa_With_ChaCha20_Poly1305_Sha256 => {
|
||||||
|
write!(f, "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256")
|
||||||
|
}
|
||||||
|
CipherSuiteId::Tls_Ecdhe_Ecdsa_With_ChaCha20_Poly1305_Sha256 => {
|
||||||
|
write!(f, "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256")
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => write!(f, "Unsupported CipherSuiteID"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u16> for CipherSuiteId {
|
||||||
|
fn from(val: u16) -> Self {
|
||||||
|
match val {
|
||||||
|
// AES-128-CCM
|
||||||
|
0xc0ac => CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_128_Ccm,
|
||||||
|
0xc0ae => CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_128_Ccm_8,
|
||||||
|
|
||||||
|
// AES-128-GCM-SHA256
|
||||||
|
0xc02b => CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_128_Gcm_Sha256,
|
||||||
|
0xc02f => CipherSuiteId::Tls_Ecdhe_Rsa_With_Aes_128_Gcm_Sha256,
|
||||||
|
|
||||||
|
// AES-256-CBC-SHA
|
||||||
|
0xc00a => CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_256_Cbc_Sha,
|
||||||
|
0xc014 => CipherSuiteId::Tls_Ecdhe_Rsa_With_Aes_256_Cbc_Sha,
|
||||||
|
|
||||||
|
0xc0a4 => CipherSuiteId::Tls_Psk_With_Aes_128_Ccm,
|
||||||
|
0xc0a8 => CipherSuiteId::Tls_Psk_With_Aes_128_Ccm_8,
|
||||||
|
0x00a8 => CipherSuiteId::Tls_Psk_With_Aes_128_Gcm_Sha256,
|
||||||
|
|
||||||
|
// CHACHA20_POLY1305_SHA256
|
||||||
|
0xcca8 => CipherSuiteId::Tls_Ecdhe_Rsa_With_ChaCha20_Poly1305_Sha256,
|
||||||
|
0xcca9 => CipherSuiteId::Tls_Ecdhe_Ecdsa_With_ChaCha20_Poly1305_Sha256,
|
||||||
|
|
||||||
|
_ => CipherSuiteId::Unsupported,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&str> for CipherSuiteId {
|
||||||
|
fn from(val: &str) -> Self {
|
||||||
|
match val {
|
||||||
|
"TLS_ECDHE_ECDSA_WITH_AES_128_CCM" => CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_128_Ccm,
|
||||||
|
"TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8" => {
|
||||||
|
CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_128_Ccm_8
|
||||||
|
}
|
||||||
|
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256" => {
|
||||||
|
CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_128_Gcm_Sha256
|
||||||
|
}
|
||||||
|
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256" => {
|
||||||
|
CipherSuiteId::Tls_Ecdhe_Rsa_With_Aes_128_Gcm_Sha256
|
||||||
|
}
|
||||||
|
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA" => {
|
||||||
|
CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_256_Cbc_Sha
|
||||||
|
}
|
||||||
|
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA" => {
|
||||||
|
CipherSuiteId::Tls_Ecdhe_Rsa_With_Aes_256_Cbc_Sha
|
||||||
|
}
|
||||||
|
"TLS_PSK_WITH_AES_128_CCM" => CipherSuiteId::Tls_Psk_With_Aes_128_Ccm,
|
||||||
|
"TLS_PSK_WITH_AES_128_CCM_8" => CipherSuiteId::Tls_Psk_With_Aes_128_Ccm_8,
|
||||||
|
"TLS_PSK_WITH_AES_128_GCM_SHA256" => CipherSuiteId::Tls_Psk_With_Aes_128_Gcm_Sha256,
|
||||||
|
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256" => {
|
||||||
|
CipherSuiteId::Tls_Ecdhe_Rsa_With_ChaCha20_Poly1305_Sha256
|
||||||
|
}
|
||||||
|
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256" => {
|
||||||
|
CipherSuiteId::Tls_Ecdhe_Ecdsa_With_ChaCha20_Poly1305_Sha256
|
||||||
|
}
|
||||||
|
_ => CipherSuiteId::Unsupported,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
pub enum CipherSuiteHash {
|
||||||
|
Sha256,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CipherSuiteHash {
|
||||||
|
pub(crate) fn size(&self) -> usize {
|
||||||
|
match *self {
|
||||||
|
CipherSuiteHash::Sha256 => 32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait CipherSuite {
|
||||||
|
fn to_string(&self) -> String;
|
||||||
|
fn id(&self) -> CipherSuiteId;
|
||||||
|
fn certificate_type(&self) -> ClientCertificateType;
|
||||||
|
fn hash_func(&self) -> CipherSuiteHash;
|
||||||
|
fn is_psk(&self) -> bool;
|
||||||
|
fn is_initialized(&self) -> bool;
|
||||||
|
|
||||||
|
// Generate the internal encryption state
|
||||||
|
fn init(
|
||||||
|
&mut self,
|
||||||
|
master_secret: &[u8],
|
||||||
|
client_random: &[u8],
|
||||||
|
server_random: &[u8],
|
||||||
|
is_client: bool,
|
||||||
|
) -> Result<()>;
|
||||||
|
|
||||||
|
fn encrypt(&self, pkt_rlh: &RecordLayerHeader, raw: &[u8]) -> Result<Vec<u8>>;
|
||||||
|
fn decrypt(&self, input: &[u8]) -> Result<Vec<u8>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Taken from https://www.iana.org/assignments/tls-parameters/tls-parameters.xml
|
||||||
|
// A cipher_suite is a specific combination of key agreement, cipher and MAC
|
||||||
|
// function.
|
||||||
|
pub fn cipher_suite_for_id(id: CipherSuiteId) -> Result<Box<dyn CipherSuite + Send + Sync>> {
|
||||||
|
match id {
|
||||||
|
CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_128_Ccm => {
|
||||||
|
Ok(Box::new(new_cipher_suite_tls_ecdhe_ecdsa_with_aes_128_ccm()))
|
||||||
|
}
|
||||||
|
CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_128_Ccm_8 => Ok(Box::new(
|
||||||
|
new_cipher_suite_tls_ecdhe_ecdsa_with_aes_128_ccm8(),
|
||||||
|
)),
|
||||||
|
CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_128_Gcm_Sha256 => {
|
||||||
|
Ok(Box::new(CipherSuiteAes128GcmSha256::new(false)))
|
||||||
|
}
|
||||||
|
CipherSuiteId::Tls_Ecdhe_Rsa_With_Aes_128_Gcm_Sha256 => {
|
||||||
|
Ok(Box::new(CipherSuiteAes128GcmSha256::new(true)))
|
||||||
|
}
|
||||||
|
CipherSuiteId::Tls_Ecdhe_Rsa_With_Aes_256_Cbc_Sha => {
|
||||||
|
Ok(Box::new(CipherSuiteAes256CbcSha::new(true)))
|
||||||
|
}
|
||||||
|
CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_256_Cbc_Sha => {
|
||||||
|
Ok(Box::new(CipherSuiteAes256CbcSha::new(false)))
|
||||||
|
}
|
||||||
|
CipherSuiteId::Tls_Psk_With_Aes_128_Ccm => {
|
||||||
|
Ok(Box::new(new_cipher_suite_tls_psk_with_aes_128_ccm()))
|
||||||
|
}
|
||||||
|
CipherSuiteId::Tls_Psk_With_Aes_128_Ccm_8 => {
|
||||||
|
Ok(Box::new(new_cipher_suite_tls_psk_with_aes_128_ccm8()))
|
||||||
|
}
|
||||||
|
CipherSuiteId::Tls_Psk_With_Aes_128_Gcm_Sha256 => {
|
||||||
|
Ok(Box::<CipherSuiteTlsPskWithAes128GcmSha256>::default())
|
||||||
|
}
|
||||||
|
CipherSuiteId::Tls_Ecdhe_Rsa_With_ChaCha20_Poly1305_Sha256 => {
|
||||||
|
Ok(Box::new(CipherSuiteChaCha20Poly1305Sha256::new(true)))
|
||||||
|
}
|
||||||
|
CipherSuiteId::Tls_Ecdhe_Ecdsa_With_ChaCha20_Poly1305_Sha256 => {
|
||||||
|
Ok(Box::new(CipherSuiteChaCha20Poly1305Sha256::new(false)))
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => Err(Error::ErrInvalidCipherSuite),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CipherSuites we support in order of preference
|
||||||
|
pub(crate) fn default_cipher_suites() -> Vec<Box<dyn CipherSuite + Send + Sync>> {
|
||||||
|
vec![
|
||||||
|
Box::new(CipherSuiteAes128GcmSha256::new(false)),
|
||||||
|
Box::new(CipherSuiteAes256CbcSha::new(false)),
|
||||||
|
Box::new(CipherSuiteAes128GcmSha256::new(true)),
|
||||||
|
Box::new(CipherSuiteAes256CbcSha::new(true)),
|
||||||
|
Box::new(CipherSuiteChaCha20Poly1305Sha256::new(false)),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn all_cipher_suites() -> Vec<Box<dyn CipherSuite + Send + Sync>> {
|
||||||
|
vec![
|
||||||
|
Box::new(new_cipher_suite_tls_ecdhe_ecdsa_with_aes_128_ccm()),
|
||||||
|
Box::new(new_cipher_suite_tls_ecdhe_ecdsa_with_aes_128_ccm8()),
|
||||||
|
Box::new(CipherSuiteAes128GcmSha256::new(false)),
|
||||||
|
Box::new(CipherSuiteAes128GcmSha256::new(true)),
|
||||||
|
Box::new(CipherSuiteAes256CbcSha::new(false)),
|
||||||
|
Box::new(CipherSuiteAes256CbcSha::new(true)),
|
||||||
|
Box::new(new_cipher_suite_tls_psk_with_aes_128_ccm()),
|
||||||
|
Box::new(new_cipher_suite_tls_psk_with_aes_128_ccm8()),
|
||||||
|
Box::<CipherSuiteTlsPskWithAes128GcmSha256>::default(),
|
||||||
|
Box::new(CipherSuiteChaCha20Poly1305Sha256::new(false)),
|
||||||
|
Box::new(CipherSuiteChaCha20Poly1305Sha256::new(true)),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cipher_suites_for_ids(ids: &[CipherSuiteId]) -> Result<Vec<Box<dyn CipherSuite + Send + Sync>>> {
|
||||||
|
let mut cipher_suites = vec![];
|
||||||
|
for id in ids {
|
||||||
|
cipher_suites.push(cipher_suite_for_id(*id)?);
|
||||||
|
}
|
||||||
|
Ok(cipher_suites)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn parse_cipher_suites(
|
||||||
|
user_selected_suites: &[CipherSuiteId],
|
||||||
|
exclude_psk: bool,
|
||||||
|
exclude_non_psk: bool,
|
||||||
|
) -> Result<Vec<Box<dyn CipherSuite + Send + Sync>>> {
|
||||||
|
let cipher_suites = if !user_selected_suites.is_empty() {
|
||||||
|
cipher_suites_for_ids(user_selected_suites)?
|
||||||
|
} else {
|
||||||
|
default_cipher_suites()
|
||||||
|
};
|
||||||
|
|
||||||
|
let filtered_cipher_suites: Vec<Box<dyn CipherSuite + Send + Sync>> = cipher_suites
|
||||||
|
.into_iter()
|
||||||
|
.filter(|c| !((exclude_psk && c.is_psk()) || (exclude_non_psk && !c.is_psk())))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if filtered_cipher_suites.is_empty() {
|
||||||
|
Err(Error::ErrNoAvailableCipherSuites)
|
||||||
|
} else {
|
||||||
|
Ok(filtered_cipher_suites)
|
||||||
|
}
|
||||||
|
}
|
||||||
16
libs/dtls/src/client_certificate_type.rs
Normal file
16
libs/dtls/src/client_certificate_type.rs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub enum ClientCertificateType {
|
||||||
|
RsaSign = 1,
|
||||||
|
EcdsaSign = 64,
|
||||||
|
Unsupported,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u8> for ClientCertificateType {
|
||||||
|
fn from(val: u8) -> Self {
|
||||||
|
match val {
|
||||||
|
1 => ClientCertificateType::RsaSign,
|
||||||
|
64 => ClientCertificateType::EcdsaSign,
|
||||||
|
_ => ClientCertificateType::Unsupported,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
60
libs/dtls/src/compression_methods.rs
Normal file
60
libs/dtls/src/compression_methods.rs
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
use std::io::{Read, Write};
|
||||||
|
|
||||||
|
use byteorder::{ReadBytesExt, WriteBytesExt};
|
||||||
|
|
||||||
|
use crate::error::Result;
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub enum CompressionMethodId {
|
||||||
|
Null = 0,
|
||||||
|
Unsupported,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u8> for CompressionMethodId {
|
||||||
|
fn from(val: u8) -> Self {
|
||||||
|
match val {
|
||||||
|
0 => CompressionMethodId::Null,
|
||||||
|
_ => CompressionMethodId::Unsupported,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct CompressionMethods {
|
||||||
|
pub ids: Vec<CompressionMethodId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CompressionMethods {
|
||||||
|
pub fn size(&self) -> usize {
|
||||||
|
1 + self.ids.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn marshal<W: Write>(&self, writer: &mut W) -> Result<()> {
|
||||||
|
writer.write_u8(self.ids.len() as u8)?;
|
||||||
|
|
||||||
|
for id in &self.ids {
|
||||||
|
writer.write_u8(*id as u8)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(writer.flush()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unmarshal<R: Read>(reader: &mut R) -> Result<Self> {
|
||||||
|
let compression_methods_count = reader.read_u8()? as usize;
|
||||||
|
let mut ids = vec![];
|
||||||
|
for _ in 0..compression_methods_count {
|
||||||
|
let id = reader.read_u8()?.into();
|
||||||
|
if id != CompressionMethodId::Unsupported {
|
||||||
|
ids.push(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(CompressionMethods { ids })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn default_compression_methods() -> CompressionMethods {
|
||||||
|
CompressionMethods {
|
||||||
|
ids: vec![CompressionMethodId::Null],
|
||||||
|
}
|
||||||
|
}
|
||||||
193
libs/dtls/src/config.rs
Normal file
193
libs/dtls/src/config.rs
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
use std::future::Future;
|
||||||
|
use std::pin::Pin;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use tokio::time::Duration;
|
||||||
|
|
||||||
|
use crate::cipher_suite::*;
|
||||||
|
use crate::crypto::*;
|
||||||
|
use crate::error::*;
|
||||||
|
use crate::extension::extension_use_srtp::SrtpProtectionProfile;
|
||||||
|
use crate::handshaker::VerifyPeerCertificateFn;
|
||||||
|
use crate::signature_hash_algorithm::SignatureScheme;
|
||||||
|
|
||||||
|
/// Config is used to configure a DTLS client or server.
|
||||||
|
/// After a Config is passed to a DTLS function it must not be modified.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Config {
|
||||||
|
/// certificates contains certificate chain to present to the other side of the connection.
|
||||||
|
/// Server MUST set this if psk is non-nil
|
||||||
|
/// client SHOULD sets this so CertificateRequests can be handled if psk is non-nil
|
||||||
|
pub certificates: Vec<Certificate>,
|
||||||
|
|
||||||
|
/// cipher_suites is a list of supported cipher suites.
|
||||||
|
/// If cipher_suites is nil, a default list is used
|
||||||
|
pub cipher_suites: Vec<CipherSuiteId>,
|
||||||
|
|
||||||
|
/// signature_schemes contains the signature and hash schemes that the peer requests to verify.
|
||||||
|
pub signature_schemes: Vec<SignatureScheme>,
|
||||||
|
|
||||||
|
/// srtp_protection_profiles are the supported protection profiles
|
||||||
|
/// Clients will send this via use_srtp and assert that the server properly responds
|
||||||
|
/// Servers will assert that clients send one of these profiles and will respond as needed
|
||||||
|
pub srtp_protection_profiles: Vec<SrtpProtectionProfile>,
|
||||||
|
|
||||||
|
/// client_auth determines the server's policy for
|
||||||
|
/// TLS Client Authentication. The default is NoClientCert.
|
||||||
|
pub client_auth: ClientAuthType,
|
||||||
|
|
||||||
|
/// extended_master_secret determines if the "Extended Master Secret" extension
|
||||||
|
/// should be disabled, requested, or required (default requested).
|
||||||
|
pub extended_master_secret: ExtendedMasterSecretType,
|
||||||
|
|
||||||
|
/// flight_interval controls how often we send outbound handshake messages
|
||||||
|
/// defaults to time.Second
|
||||||
|
pub flight_interval: Duration,
|
||||||
|
|
||||||
|
/// psk sets the pre-shared key used by this DTLS connection
|
||||||
|
/// If psk is non-nil only psk cipher_suites will be used
|
||||||
|
pub psk: Option<PskCallback>,
|
||||||
|
pub psk_identity_hint: Option<Vec<u8>>,
|
||||||
|
|
||||||
|
/// insecure_skip_verify controls whether a client verifies the
|
||||||
|
/// server's certificate chain and host name.
|
||||||
|
/// If insecure_skip_verify is true, TLS accepts any certificate
|
||||||
|
/// presented by the server and any host name in that certificate.
|
||||||
|
/// In this mode, TLS is susceptible to man-in-the-middle attacks.
|
||||||
|
/// This should be used only for testing.
|
||||||
|
pub insecure_skip_verify: bool,
|
||||||
|
|
||||||
|
/// insecure_hashes allows the use of hashing algorithms that are known
|
||||||
|
/// to be vulnerable.
|
||||||
|
pub insecure_hashes: bool,
|
||||||
|
|
||||||
|
/// insecure_verification allows the use of verification algorithms that are
|
||||||
|
/// known to be vulnerable or deprecated
|
||||||
|
pub insecure_verification: bool,
|
||||||
|
/// VerifyPeerCertificate, if not nil, is called after normal
|
||||||
|
/// certificate verification by either a client or server. It
|
||||||
|
/// receives the certificate provided by the peer and also a flag
|
||||||
|
/// that tells if normal verification has succeeded. If it returns a
|
||||||
|
/// non-nil error, the handshake is aborted and that error results.
|
||||||
|
///
|
||||||
|
/// If normal verification fails then the handshake will abort before
|
||||||
|
/// considering this callback. If normal verification is disabled by
|
||||||
|
/// setting insecure_skip_verify, or (for a server) when client_auth is
|
||||||
|
/// RequestClientCert or RequireAnyClientCert, then this callback will
|
||||||
|
/// be considered but the verifiedChains will always be nil.
|
||||||
|
pub verify_peer_certificate: Option<VerifyPeerCertificateFn>,
|
||||||
|
|
||||||
|
/// roots_cas defines the set of root certificate authorities
|
||||||
|
/// that one peer uses when verifying the other peer's certificates.
|
||||||
|
/// If RootCAs is nil, TLS uses the host's root CA set.
|
||||||
|
/// Used by Client to verify server's certificate
|
||||||
|
pub roots_cas: crate::certificate_verifier::RootCertStore,
|
||||||
|
|
||||||
|
/// client_cas defines the set of root certificate authorities
|
||||||
|
/// that servers use if required to verify a client certificate
|
||||||
|
/// by the policy in client_auth.
|
||||||
|
/// Used by Server to verify client's certificate
|
||||||
|
pub client_cas: crate::certificate_verifier::RootCertStore,
|
||||||
|
|
||||||
|
/// server_name is used to verify the hostname on the returned
|
||||||
|
/// certificates unless insecure_skip_verify is given.
|
||||||
|
pub server_name: String,
|
||||||
|
|
||||||
|
/// mtu is the length at which handshake messages will be fragmented to
|
||||||
|
/// fit within the maximum transmission unit (default is 1200 bytes)
|
||||||
|
pub mtu: usize,
|
||||||
|
|
||||||
|
/// replay_protection_window is the size of the replay attack protection window.
|
||||||
|
/// Duplication of the sequence number is checked in this window size.
|
||||||
|
/// Packet with sequence number older than this value compared to the latest
|
||||||
|
/// accepted packet will be discarded. (default is 64)
|
||||||
|
pub replay_protection_window: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Config {
|
||||||
|
fn default() -> Self {
|
||||||
|
Config {
|
||||||
|
certificates: vec![],
|
||||||
|
cipher_suites: vec![],
|
||||||
|
signature_schemes: vec![],
|
||||||
|
srtp_protection_profiles: vec![],
|
||||||
|
client_auth: ClientAuthType::default(),
|
||||||
|
extended_master_secret: ExtendedMasterSecretType::default(),
|
||||||
|
flight_interval: Duration::default(),
|
||||||
|
psk: None,
|
||||||
|
psk_identity_hint: None,
|
||||||
|
insecure_skip_verify: false,
|
||||||
|
insecure_hashes: false,
|
||||||
|
insecure_verification: false,
|
||||||
|
verify_peer_certificate: None,
|
||||||
|
roots_cas: crate::certificate_verifier::RootCertStore::empty(),
|
||||||
|
client_cas: crate::certificate_verifier::RootCertStore::empty(),
|
||||||
|
server_name: String::default(),
|
||||||
|
mtu: 0,
|
||||||
|
replay_protection_window: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) const DEFAULT_MTU: usize = 1200; // bytes
|
||||||
|
|
||||||
|
// PSKCallback is called once we have the remote's psk_identity_hint.
|
||||||
|
// If the remote provided none it will be nil
|
||||||
|
pub(crate) type PskCallback =
|
||||||
|
Arc<dyn (Fn(&[u8]) -> Pin<Box<dyn Future<Output = Result<Vec<u8>>> + Send>>) + Send + Sync>;
|
||||||
|
|
||||||
|
// ClientAuthType declares the policy the server will follow for
|
||||||
|
// TLS Client Authentication.
|
||||||
|
#[derive(Default, Copy, Clone, PartialEq, Eq)]
|
||||||
|
pub enum ClientAuthType {
|
||||||
|
#[default]
|
||||||
|
NoClientCert = 0,
|
||||||
|
RequestClientCert = 1,
|
||||||
|
RequireAnyClientCert = 2,
|
||||||
|
VerifyClientCertIfGiven = 3,
|
||||||
|
RequireAndVerifyClientCert = 4,
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtendedMasterSecretType declares the policy the client and server
|
||||||
|
// will follow for the Extended Master Secret extension
|
||||||
|
#[derive(Default, PartialEq, Eq, Copy, Clone)]
|
||||||
|
pub enum ExtendedMasterSecretType {
|
||||||
|
#[default]
|
||||||
|
Request = 0,
|
||||||
|
Require = 1,
|
||||||
|
Disable = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn validate_config(is_client: bool, config: &Config) -> Result<()> {
|
||||||
|
if is_client && config.psk.is_some() && config.psk_identity_hint.is_none() {
|
||||||
|
return Err(Error::ErrPskAndIdentityMustBeSetForClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !is_client && config.psk.is_none() && config.certificates.is_empty() {
|
||||||
|
return Err(Error::ErrServerMustHaveCertificate);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !config.certificates.is_empty() && config.psk.is_some() {
|
||||||
|
return Err(Error::ErrPskAndCertificate);
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.psk_identity_hint.is_some() && config.psk.is_none() {
|
||||||
|
return Err(Error::ErrIdentityNoPsk);
|
||||||
|
}
|
||||||
|
|
||||||
|
for cert in &config.certificates {
|
||||||
|
match cert.private_key.kind {
|
||||||
|
CryptoPrivateKeyKind::Ed25519(_) => {}
|
||||||
|
CryptoPrivateKeyKind::Ecdsa256(_) => {}
|
||||||
|
_ => return Err(Error::ErrInvalidPrivateKey),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_cipher_suites(
|
||||||
|
&config.cipher_suites,
|
||||||
|
config.psk.is_none(),
|
||||||
|
config.psk.is_some(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
2464
libs/dtls/src/conn/conn_test.rs
Normal file
2464
libs/dtls/src/conn/conn_test.rs
Normal file
File diff suppressed because it is too large
Load Diff
1212
libs/dtls/src/conn/mod.rs
Normal file
1212
libs/dtls/src/conn/mod.rs
Normal file
File diff suppressed because it is too large
Load Diff
85
libs/dtls/src/content.rs
Normal file
85
libs/dtls/src/content.rs
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
use std::io::{Read, Write};
|
||||||
|
|
||||||
|
use super::alert::*;
|
||||||
|
use super::application_data::*;
|
||||||
|
use super::change_cipher_spec::*;
|
||||||
|
use super::handshake::*;
|
||||||
|
use crate::error::*;
|
||||||
|
|
||||||
|
/// ## Specifications
|
||||||
|
///
|
||||||
|
/// * [RFC 4346 §6.2.1]
|
||||||
|
///
|
||||||
|
/// [RFC 4346 §6.2.1]: https://tools.ietf.org/html/rfc4346#section-6.2.1
|
||||||
|
#[derive(Default, Copy, Clone, PartialEq, Eq, Debug)]
|
||||||
|
pub enum ContentType {
|
||||||
|
ChangeCipherSpec = 20,
|
||||||
|
Alert = 21,
|
||||||
|
Handshake = 22,
|
||||||
|
ApplicationData = 23,
|
||||||
|
#[default]
|
||||||
|
Invalid,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u8> for ContentType {
|
||||||
|
fn from(val: u8) -> Self {
|
||||||
|
match val {
|
||||||
|
20 => ContentType::ChangeCipherSpec,
|
||||||
|
21 => ContentType::Alert,
|
||||||
|
22 => ContentType::Handshake,
|
||||||
|
23 => ContentType::ApplicationData,
|
||||||
|
_ => ContentType::Invalid,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Debug, Clone)]
|
||||||
|
pub enum Content {
|
||||||
|
ChangeCipherSpec(ChangeCipherSpec),
|
||||||
|
Alert(Alert),
|
||||||
|
Handshake(Handshake),
|
||||||
|
ApplicationData(ApplicationData),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Content {
|
||||||
|
pub fn content_type(&self) -> ContentType {
|
||||||
|
match self {
|
||||||
|
Content::ChangeCipherSpec(c) => c.content_type(),
|
||||||
|
Content::Alert(c) => c.content_type(),
|
||||||
|
Content::Handshake(c) => c.content_type(),
|
||||||
|
Content::ApplicationData(c) => c.content_type(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn size(&self) -> usize {
|
||||||
|
match self {
|
||||||
|
Content::ChangeCipherSpec(c) => c.size(),
|
||||||
|
Content::Alert(c) => c.size(),
|
||||||
|
Content::Handshake(c) => c.size(),
|
||||||
|
Content::ApplicationData(c) => c.size(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn marshal<W: Write>(&self, writer: &mut W) -> Result<()> {
|
||||||
|
match self {
|
||||||
|
Content::ChangeCipherSpec(c) => c.marshal(writer),
|
||||||
|
Content::Alert(c) => c.marshal(writer),
|
||||||
|
Content::Handshake(c) => c.marshal(writer),
|
||||||
|
Content::ApplicationData(c) => c.marshal(writer),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unmarshal<R: Read>(content_type: ContentType, reader: &mut R) -> Result<Self> {
|
||||||
|
match content_type {
|
||||||
|
ContentType::ChangeCipherSpec => Ok(Content::ChangeCipherSpec(
|
||||||
|
ChangeCipherSpec::unmarshal(reader)?,
|
||||||
|
)),
|
||||||
|
ContentType::Alert => Ok(Content::Alert(Alert::unmarshal(reader)?)),
|
||||||
|
ContentType::Handshake => Ok(Content::Handshake(Handshake::unmarshal(reader)?)),
|
||||||
|
ContentType::ApplicationData => Ok(Content::ApplicationData(
|
||||||
|
ApplicationData::unmarshal(reader)?,
|
||||||
|
)),
|
||||||
|
_ => Err(Error::ErrInvalidContentType),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
136
libs/dtls/src/crypto/crypto_cbc.rs
Normal file
136
libs/dtls/src/crypto/crypto_cbc.rs
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
// AES-CBC (Cipher Block Chaining)
|
||||||
|
// First historic block cipher for AES.
|
||||||
|
// CBC mode is insecure and must not be used. It’s been progressively deprecated and
|
||||||
|
// removed from SSL libraries.
|
||||||
|
// Introduced with TLS 1.0 year 2002. Superseded by GCM in TLS 1.2 year 2008.
|
||||||
|
// Removed in TLS 1.3 year 2018.
|
||||||
|
// RFC 3268 year 2002 https://tools.ietf.org/html/rfc3268
|
||||||
|
|
||||||
|
// https://github.com/RustCrypto/block-ciphers
|
||||||
|
|
||||||
|
use aes::cipher::{BlockDecryptMut, BlockEncryptMut, KeyIvInit};
|
||||||
|
use p256::elliptic_curve::subtle::ConstantTimeEq;
|
||||||
|
use rand::Rng;
|
||||||
|
use std::io::Cursor;
|
||||||
|
use std::ops::Not;
|
||||||
|
|
||||||
|
use super::padding::DtlsPadding;
|
||||||
|
use crate::content::*;
|
||||||
|
use crate::error::*;
|
||||||
|
use crate::prf::*;
|
||||||
|
use crate::record_layer::record_layer_header::*;
|
||||||
|
type Aes256CbcEnc = cbc::Encryptor<aes::Aes256>;
|
||||||
|
type Aes256CbcDec = cbc::Decryptor<aes::Aes256>;
|
||||||
|
|
||||||
|
// State needed to handle encrypted input/output
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct CryptoCbc {
|
||||||
|
local_key: Vec<u8>,
|
||||||
|
remote_key: Vec<u8>,
|
||||||
|
write_mac: Vec<u8>,
|
||||||
|
read_mac: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CryptoCbc {
|
||||||
|
const BLOCK_SIZE: usize = 16;
|
||||||
|
const MAC_SIZE: usize = 20;
|
||||||
|
|
||||||
|
pub fn new(
|
||||||
|
local_key: &[u8],
|
||||||
|
local_mac: &[u8],
|
||||||
|
remote_key: &[u8],
|
||||||
|
remote_mac: &[u8],
|
||||||
|
) -> Result<Self> {
|
||||||
|
Ok(CryptoCbc {
|
||||||
|
local_key: local_key.to_vec(),
|
||||||
|
write_mac: local_mac.to_vec(),
|
||||||
|
|
||||||
|
remote_key: remote_key.to_vec(),
|
||||||
|
read_mac: remote_mac.to_vec(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn encrypt(&self, pkt_rlh: &RecordLayerHeader, raw: &[u8]) -> Result<Vec<u8>> {
|
||||||
|
let mut payload = raw[RECORD_LAYER_HEADER_SIZE..].to_vec();
|
||||||
|
let raw = &raw[..RECORD_LAYER_HEADER_SIZE];
|
||||||
|
|
||||||
|
// Generate + Append MAC
|
||||||
|
let h = pkt_rlh;
|
||||||
|
|
||||||
|
let mac = prf_mac(
|
||||||
|
h.epoch,
|
||||||
|
h.sequence_number,
|
||||||
|
h.content_type,
|
||||||
|
h.protocol_version,
|
||||||
|
&payload,
|
||||||
|
&self.write_mac,
|
||||||
|
)?;
|
||||||
|
payload.extend_from_slice(&mac);
|
||||||
|
|
||||||
|
let mut iv: Vec<u8> = vec![0; Self::BLOCK_SIZE];
|
||||||
|
rand::rng().fill(iv.as_mut_slice());
|
||||||
|
|
||||||
|
let write_cbc = Aes256CbcEnc::new_from_slices(&self.local_key, &iv)?;
|
||||||
|
let encrypted = write_cbc.encrypt_padded_vec_mut::<DtlsPadding>(&payload);
|
||||||
|
|
||||||
|
// Prepend unencrypte header with encrypted payload
|
||||||
|
let mut r = vec![];
|
||||||
|
r.extend_from_slice(raw);
|
||||||
|
r.extend_from_slice(&iv);
|
||||||
|
r.extend_from_slice(&encrypted);
|
||||||
|
|
||||||
|
let r_len = (r.len() - RECORD_LAYER_HEADER_SIZE) as u16;
|
||||||
|
r[RECORD_LAYER_HEADER_SIZE - 2..RECORD_LAYER_HEADER_SIZE]
|
||||||
|
.copy_from_slice(&r_len.to_be_bytes());
|
||||||
|
|
||||||
|
Ok(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decrypt(&self, r: &[u8]) -> Result<Vec<u8>> {
|
||||||
|
let mut reader = Cursor::new(r);
|
||||||
|
let h = RecordLayerHeader::unmarshal(&mut reader)?;
|
||||||
|
if h.content_type == ContentType::ChangeCipherSpec {
|
||||||
|
// Nothing to encrypt with ChangeCipherSpec
|
||||||
|
return Ok(r.to_vec());
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.len() < RECORD_LAYER_HEADER_SIZE + Self::BLOCK_SIZE {
|
||||||
|
return Err(Error::ErrInvalidPacketLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
let body = &r[RECORD_LAYER_HEADER_SIZE..];
|
||||||
|
let iv = &body[0..Self::BLOCK_SIZE];
|
||||||
|
let body = &body[Self::BLOCK_SIZE..];
|
||||||
|
|
||||||
|
if body.is_empty() || !body.len().is_multiple_of(Self::BLOCK_SIZE) {
|
||||||
|
return Err(Error::ErrInvalidPacketLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
let read_cbc = Aes256CbcDec::new_from_slices(&self.remote_key, iv)?;
|
||||||
|
|
||||||
|
let decrypted = read_cbc
|
||||||
|
.decrypt_padded_vec_mut::<DtlsPadding>(body)
|
||||||
|
.map_err(|_| Error::ErrInvalidPacketLength)?;
|
||||||
|
|
||||||
|
let recv_mac = &decrypted[decrypted.len() - Self::MAC_SIZE..];
|
||||||
|
let decrypted = &decrypted[0..decrypted.len() - Self::MAC_SIZE];
|
||||||
|
let mac = prf_mac(
|
||||||
|
h.epoch,
|
||||||
|
h.sequence_number,
|
||||||
|
h.content_type,
|
||||||
|
h.protocol_version,
|
||||||
|
decrypted,
|
||||||
|
&self.read_mac,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
if recv_mac.ct_eq(&mac).not().into() {
|
||||||
|
return Err(Error::ErrInvalidMac);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut d = Vec::with_capacity(RECORD_LAYER_HEADER_SIZE + decrypted.len());
|
||||||
|
d.extend_from_slice(&r[..RECORD_LAYER_HEADER_SIZE]);
|
||||||
|
d.extend_from_slice(decrypted);
|
||||||
|
|
||||||
|
Ok(d)
|
||||||
|
}
|
||||||
|
}
|
||||||
188
libs/dtls/src/crypto/crypto_ccm.rs
Normal file
188
libs/dtls/src/crypto/crypto_ccm.rs
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
// AES-CCM (Counter with CBC-MAC)
|
||||||
|
// Alternative to GCM mode.
|
||||||
|
// Available in OpenSSL as of TLS 1.3 (2018), but disabled by default.
|
||||||
|
// Two AES computations per block, thus expected to be somewhat slower than AES-GCM.
|
||||||
|
// RFC 6655 year 2012 https://tools.ietf.org/html/rfc6655
|
||||||
|
// Much lower adoption, probably because it came after GCM and offer no significant benefit.
|
||||||
|
|
||||||
|
// https://github.com/RustCrypto/AEADs
|
||||||
|
// https://docs.rs/ccm/0.3.0/ccm/ Or https://crates.io/crates/aes-ccm?
|
||||||
|
|
||||||
|
use std::io::Cursor;
|
||||||
|
|
||||||
|
use aes::Aes128;
|
||||||
|
use ccm::aead::generic_array::GenericArray;
|
||||||
|
use ccm::aead::AeadInPlace;
|
||||||
|
use ccm::consts::{U12, U16, U8};
|
||||||
|
use ccm::Ccm;
|
||||||
|
use ccm::KeyInit;
|
||||||
|
use rand::Rng;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::content::*;
|
||||||
|
use crate::error::*;
|
||||||
|
use crate::record_layer::record_layer_header::*;
|
||||||
|
|
||||||
|
const CRYPTO_CCM_8_TAG_LENGTH: usize = 8;
|
||||||
|
const CRYPTO_CCM_TAG_LENGTH: usize = 16;
|
||||||
|
const CRYPTO_CCM_NONCE_LENGTH: usize = 12;
|
||||||
|
|
||||||
|
type AesCcm8 = Ccm<Aes128, U8, U12>;
|
||||||
|
type AesCcm = Ccm<Aes128, U16, U12>;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum CryptoCcmTagLen {
|
||||||
|
CryptoCcm8TagLength,
|
||||||
|
CryptoCcmTagLength,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum CryptoCcmType {
|
||||||
|
CryptoCcm8(AesCcm8),
|
||||||
|
CryptoCcm(AesCcm),
|
||||||
|
}
|
||||||
|
|
||||||
|
// State needed to handle encrypted input/output
|
||||||
|
pub struct CryptoCcm {
|
||||||
|
local_ccm: CryptoCcmType,
|
||||||
|
remote_ccm: CryptoCcmType,
|
||||||
|
local_write_iv: Vec<u8>,
|
||||||
|
remote_write_iv: Vec<u8>,
|
||||||
|
// used by clone()
|
||||||
|
local_write_key: Vec<u8>,
|
||||||
|
remote_write_key: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clone for CryptoCcm {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
match self.local_ccm {
|
||||||
|
CryptoCcmType::CryptoCcm(_) => Self::new(
|
||||||
|
&CryptoCcmTagLen::CryptoCcmTagLength,
|
||||||
|
&self.local_write_key,
|
||||||
|
&self.local_write_iv,
|
||||||
|
&self.remote_write_key,
|
||||||
|
&self.remote_write_iv,
|
||||||
|
),
|
||||||
|
CryptoCcmType::CryptoCcm8(_) => Self::new(
|
||||||
|
&CryptoCcmTagLen::CryptoCcm8TagLength,
|
||||||
|
&self.local_write_key,
|
||||||
|
&self.local_write_iv,
|
||||||
|
&self.remote_write_key,
|
||||||
|
&self.remote_write_iv,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CryptoCcm {
|
||||||
|
pub fn new(
|
||||||
|
tag_len: &CryptoCcmTagLen,
|
||||||
|
local_key: &[u8],
|
||||||
|
local_write_iv: &[u8],
|
||||||
|
remote_key: &[u8],
|
||||||
|
remote_write_iv: &[u8],
|
||||||
|
) -> Self {
|
||||||
|
let key = GenericArray::from_slice(local_key);
|
||||||
|
let local_ccm = match tag_len {
|
||||||
|
CryptoCcmTagLen::CryptoCcmTagLength => CryptoCcmType::CryptoCcm(AesCcm::new(key)),
|
||||||
|
CryptoCcmTagLen::CryptoCcm8TagLength => CryptoCcmType::CryptoCcm8(AesCcm8::new(key)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let key = GenericArray::from_slice(remote_key);
|
||||||
|
let remote_ccm = match tag_len {
|
||||||
|
CryptoCcmTagLen::CryptoCcmTagLength => CryptoCcmType::CryptoCcm(AesCcm::new(key)),
|
||||||
|
CryptoCcmTagLen::CryptoCcm8TagLength => CryptoCcmType::CryptoCcm8(AesCcm8::new(key)),
|
||||||
|
};
|
||||||
|
|
||||||
|
CryptoCcm {
|
||||||
|
local_ccm,
|
||||||
|
local_write_key: local_key.to_vec(),
|
||||||
|
local_write_iv: local_write_iv.to_vec(),
|
||||||
|
remote_ccm,
|
||||||
|
remote_write_key: remote_key.to_vec(),
|
||||||
|
remote_write_iv: remote_write_iv.to_vec(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn encrypt(&self, pkt_rlh: &RecordLayerHeader, raw: &[u8]) -> Result<Vec<u8>> {
|
||||||
|
let payload = &raw[RECORD_LAYER_HEADER_SIZE..];
|
||||||
|
let raw = &raw[..RECORD_LAYER_HEADER_SIZE];
|
||||||
|
|
||||||
|
let mut nonce = vec![0u8; CRYPTO_CCM_NONCE_LENGTH];
|
||||||
|
nonce[..4].copy_from_slice(&self.local_write_iv[..4]);
|
||||||
|
rand::rng().fill(&mut nonce[4..]);
|
||||||
|
let nonce = GenericArray::from_slice(&nonce);
|
||||||
|
|
||||||
|
let additional_data = generate_aead_additional_data(pkt_rlh, payload.len());
|
||||||
|
|
||||||
|
let mut buffer: Vec<u8> = Vec::new();
|
||||||
|
buffer.extend_from_slice(payload);
|
||||||
|
|
||||||
|
match &self.local_ccm {
|
||||||
|
CryptoCcmType::CryptoCcm(ccm) => {
|
||||||
|
ccm.encrypt_in_place(nonce, &additional_data, &mut buffer)
|
||||||
|
.map_err(|e| Error::Other(e.to_string()))?;
|
||||||
|
}
|
||||||
|
CryptoCcmType::CryptoCcm8(ccm8) => {
|
||||||
|
ccm8.encrypt_in_place(nonce, &additional_data, &mut buffer)
|
||||||
|
.map_err(|e| Error::Other(e.to_string()))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut r = Vec::with_capacity(raw.len() + nonce.len() + buffer.len());
|
||||||
|
|
||||||
|
r.extend_from_slice(raw);
|
||||||
|
r.extend_from_slice(&nonce[4..]);
|
||||||
|
r.extend_from_slice(&buffer);
|
||||||
|
|
||||||
|
// Update recordLayer size to include explicit nonce
|
||||||
|
let r_len = (r.len() - RECORD_LAYER_HEADER_SIZE) as u16;
|
||||||
|
r[RECORD_LAYER_HEADER_SIZE - 2..RECORD_LAYER_HEADER_SIZE]
|
||||||
|
.copy_from_slice(&r_len.to_be_bytes());
|
||||||
|
|
||||||
|
Ok(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decrypt(&self, r: &[u8]) -> Result<Vec<u8>> {
|
||||||
|
let mut reader = Cursor::new(r);
|
||||||
|
let h = RecordLayerHeader::unmarshal(&mut reader)?;
|
||||||
|
if h.content_type == ContentType::ChangeCipherSpec {
|
||||||
|
// Nothing to encrypt with ChangeCipherSpec
|
||||||
|
return Ok(r.to_vec());
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.len() <= (RECORD_LAYER_HEADER_SIZE + 8) {
|
||||||
|
return Err(Error::ErrNotEnoughRoomForNonce);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut nonce = vec![];
|
||||||
|
nonce.extend_from_slice(&self.remote_write_iv[..4]);
|
||||||
|
nonce.extend_from_slice(&r[RECORD_LAYER_HEADER_SIZE..RECORD_LAYER_HEADER_SIZE + 8]);
|
||||||
|
let nonce = GenericArray::from_slice(&nonce);
|
||||||
|
|
||||||
|
let out = &r[RECORD_LAYER_HEADER_SIZE + 8..];
|
||||||
|
|
||||||
|
let mut buffer: Vec<u8> = Vec::new();
|
||||||
|
buffer.extend_from_slice(out);
|
||||||
|
|
||||||
|
match &self.remote_ccm {
|
||||||
|
CryptoCcmType::CryptoCcm(ccm) => {
|
||||||
|
let additional_data =
|
||||||
|
generate_aead_additional_data(&h, out.len() - CRYPTO_CCM_TAG_LENGTH);
|
||||||
|
ccm.decrypt_in_place(nonce, &additional_data, &mut buffer)
|
||||||
|
.map_err(|e| Error::Other(e.to_string()))?;
|
||||||
|
}
|
||||||
|
CryptoCcmType::CryptoCcm8(ccm8) => {
|
||||||
|
let additional_data =
|
||||||
|
generate_aead_additional_data(&h, out.len() - CRYPTO_CCM_8_TAG_LENGTH);
|
||||||
|
ccm8.decrypt_in_place(nonce, &additional_data, &mut buffer)
|
||||||
|
.map_err(|e| Error::Other(e.to_string()))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut d = Vec::with_capacity(RECORD_LAYER_HEADER_SIZE + buffer.len());
|
||||||
|
d.extend_from_slice(&r[..RECORD_LAYER_HEADER_SIZE]);
|
||||||
|
d.extend_from_slice(&buffer);
|
||||||
|
|
||||||
|
Ok(d)
|
||||||
|
}
|
||||||
|
}
|
||||||
123
libs/dtls/src/crypto/crypto_chacha20.rs
Normal file
123
libs/dtls/src/crypto/crypto_chacha20.rs
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
use std::io::Cursor;
|
||||||
|
|
||||||
|
use chacha20poly1305::aead::generic_array::GenericArray;
|
||||||
|
use chacha20poly1305::aead::AeadInPlace;
|
||||||
|
use chacha20poly1305::{ChaCha20Poly1305, KeyInit};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::content::*;
|
||||||
|
use crate::error::*;
|
||||||
|
use crate::record_layer::record_layer_header::*; // what about Aes256Gcm?
|
||||||
|
|
||||||
|
const CRYPTO_CHACHA20_TAG_LENGTH: usize = 16;
|
||||||
|
const CRYPTO_CHACHA20_NONCE_LENGTH: usize = 12;
|
||||||
|
|
||||||
|
// State needed to handle encrypted input/output
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct CryptoChaCha20 {
|
||||||
|
local_cc: ChaCha20Poly1305,
|
||||||
|
remote_cc: ChaCha20Poly1305,
|
||||||
|
local_key: Vec<u8>,
|
||||||
|
remote_key: Vec<u8>,
|
||||||
|
local_write_iv: Vec<u8>,
|
||||||
|
remote_write_iv: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn noncegen(nonce: &mut [u8], epoch: u16, seqnum: u64) {
|
||||||
|
let epoch: u64 = epoch.into();
|
||||||
|
let seqnum = (seqnum & 0xFFFFFFFFFFFF) | (epoch << 48);
|
||||||
|
for i in 0..8 {
|
||||||
|
nonce[i + 4] ^= ((seqnum >> (8 * (7 - i))) & 0xFF) as u8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CryptoChaCha20 {
|
||||||
|
pub fn new(
|
||||||
|
local_key: &[u8],
|
||||||
|
local_write_iv: &[u8],
|
||||||
|
remote_key: &[u8],
|
||||||
|
remote_write_iv: &[u8],
|
||||||
|
) -> Self {
|
||||||
|
let key = GenericArray::from_slice(local_key);
|
||||||
|
let local_cc = ChaCha20Poly1305::new(key);
|
||||||
|
|
||||||
|
let key = GenericArray::from_slice(remote_key);
|
||||||
|
let remote_cc = ChaCha20Poly1305::new(key);
|
||||||
|
|
||||||
|
CryptoChaCha20 {
|
||||||
|
local_cc,
|
||||||
|
local_write_iv: local_write_iv.to_vec(),
|
||||||
|
remote_cc,
|
||||||
|
local_key: local_key.to_vec(),
|
||||||
|
remote_key: remote_key.to_vec(),
|
||||||
|
remote_write_iv: remote_write_iv.to_vec(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn encrypt(&self, pkt_rlh: &RecordLayerHeader, raw: &[u8]) -> Result<Vec<u8>> {
|
||||||
|
let payload = &raw[RECORD_LAYER_HEADER_SIZE..];
|
||||||
|
let raw = &raw[..RECORD_LAYER_HEADER_SIZE];
|
||||||
|
|
||||||
|
let mut nonce = vec![0u8; CRYPTO_CHACHA20_NONCE_LENGTH];
|
||||||
|
nonce[..CRYPTO_CHACHA20_NONCE_LENGTH]
|
||||||
|
.copy_from_slice(&self.local_write_iv[..CRYPTO_CHACHA20_NONCE_LENGTH]);
|
||||||
|
|
||||||
|
noncegen(&mut nonce[..], pkt_rlh.epoch, pkt_rlh.sequence_number);
|
||||||
|
let nonce = GenericArray::from_slice(&nonce);
|
||||||
|
|
||||||
|
let additional_data = generate_aead_additional_data(pkt_rlh, payload.len());
|
||||||
|
|
||||||
|
let mut buffer: Vec<u8> = Vec::new();
|
||||||
|
buffer.extend_from_slice(payload);
|
||||||
|
|
||||||
|
let tag = self
|
||||||
|
.local_cc
|
||||||
|
.encrypt_in_place_detached(nonce, &additional_data, &mut buffer)
|
||||||
|
.map_err(|e| Error::Other(e.to_string()))?;
|
||||||
|
|
||||||
|
let mut r = Vec::with_capacity(raw.len() + buffer.len() + tag.len());
|
||||||
|
r.extend_from_slice(raw);
|
||||||
|
r.extend_from_slice(&buffer);
|
||||||
|
r.extend_from_slice(&tag);
|
||||||
|
|
||||||
|
// Update recordLayer size to include explicit nonce
|
||||||
|
let r_len = (r.len() - RECORD_LAYER_HEADER_SIZE) as u16;
|
||||||
|
r[RECORD_LAYER_HEADER_SIZE - 2..RECORD_LAYER_HEADER_SIZE]
|
||||||
|
.copy_from_slice(&r_len.to_be_bytes());
|
||||||
|
|
||||||
|
Ok(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decrypt(&self, r: &[u8]) -> Result<Vec<u8>> {
|
||||||
|
let mut reader = Cursor::new(r);
|
||||||
|
let h = RecordLayerHeader::unmarshal(&mut reader)?;
|
||||||
|
if h.content_type == ContentType::ChangeCipherSpec {
|
||||||
|
// Nothing to encrypt with ChangeCipherSpec
|
||||||
|
return Ok(r.to_vec());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut nonce = vec![];
|
||||||
|
nonce.extend_from_slice(&self.remote_write_iv[..]);
|
||||||
|
|
||||||
|
noncegen(&mut nonce[..], h.epoch, h.sequence_number);
|
||||||
|
let nonce = GenericArray::from_slice(&nonce);
|
||||||
|
|
||||||
|
let out = &r[RECORD_LAYER_HEADER_SIZE..];
|
||||||
|
|
||||||
|
let additional_data =
|
||||||
|
generate_aead_additional_data(&h, out.len() - CRYPTO_CHACHA20_TAG_LENGTH);
|
||||||
|
|
||||||
|
let mut buffer: Vec<u8> = Vec::new();
|
||||||
|
buffer.extend_from_slice(out);
|
||||||
|
|
||||||
|
self.remote_cc
|
||||||
|
.decrypt_in_place(nonce, &additional_data, &mut buffer)
|
||||||
|
.map_err(|e| Error::Other(e.to_string()))?;
|
||||||
|
|
||||||
|
let mut d = Vec::with_capacity(RECORD_LAYER_HEADER_SIZE + buffer.len());
|
||||||
|
d.extend_from_slice(&r[..RECORD_LAYER_HEADER_SIZE]);
|
||||||
|
d.extend_from_slice(&buffer);
|
||||||
|
|
||||||
|
Ok(d)
|
||||||
|
}
|
||||||
|
}
|
||||||
119
libs/dtls/src/crypto/crypto_gcm.rs
Normal file
119
libs/dtls/src/crypto/crypto_gcm.rs
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
// AES-GCM (Galois Counter Mode)
|
||||||
|
// The most widely used block cipher worldwide.
|
||||||
|
// Mandatory as of TLS 1.2 (2008) and used by default by most clients.
|
||||||
|
// RFC 5288 year 2008 https://tools.ietf.org/html/rfc5288
|
||||||
|
|
||||||
|
// https://github.com/RustCrypto/AEADs
|
||||||
|
// https://docs.rs/aes-gcm/0.8.0/aes_gcm/
|
||||||
|
|
||||||
|
use std::io::Cursor;
|
||||||
|
|
||||||
|
use aes_gcm::aead::generic_array::GenericArray;
|
||||||
|
use aes_gcm::aead::AeadInPlace;
|
||||||
|
use aes_gcm::{Aes128Gcm, KeyInit};
|
||||||
|
use rand::Rng;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::content::*;
|
||||||
|
use crate::error::*;
|
||||||
|
use crate::record_layer::record_layer_header::*; // what about Aes256Gcm?
|
||||||
|
|
||||||
|
const CRYPTO_GCM_TAG_LENGTH: usize = 16;
|
||||||
|
const CRYPTO_GCM_NONCE_LENGTH: usize = 12;
|
||||||
|
|
||||||
|
// State needed to handle encrypted input/output
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct CryptoGcm {
|
||||||
|
local_gcm: Aes128Gcm,
|
||||||
|
remote_gcm: Aes128Gcm,
|
||||||
|
local_write_iv: Vec<u8>,
|
||||||
|
remote_write_iv: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CryptoGcm {
|
||||||
|
pub fn new(
|
||||||
|
local_key: &[u8],
|
||||||
|
local_write_iv: &[u8],
|
||||||
|
remote_key: &[u8],
|
||||||
|
remote_write_iv: &[u8],
|
||||||
|
) -> Self {
|
||||||
|
let key = GenericArray::from_slice(local_key);
|
||||||
|
let local_gcm = Aes128Gcm::new(key);
|
||||||
|
|
||||||
|
let key = GenericArray::from_slice(remote_key);
|
||||||
|
let remote_gcm = Aes128Gcm::new(key);
|
||||||
|
|
||||||
|
CryptoGcm {
|
||||||
|
local_gcm,
|
||||||
|
local_write_iv: local_write_iv.to_vec(),
|
||||||
|
remote_gcm,
|
||||||
|
remote_write_iv: remote_write_iv.to_vec(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn encrypt(&self, pkt_rlh: &RecordLayerHeader, raw: &[u8]) -> Result<Vec<u8>> {
|
||||||
|
let payload = &raw[RECORD_LAYER_HEADER_SIZE..];
|
||||||
|
let raw = &raw[..RECORD_LAYER_HEADER_SIZE];
|
||||||
|
|
||||||
|
let mut nonce = vec![0u8; CRYPTO_GCM_NONCE_LENGTH];
|
||||||
|
nonce[..4].copy_from_slice(&self.local_write_iv[..4]);
|
||||||
|
rand::rng().fill(&mut nonce[4..]);
|
||||||
|
let nonce = GenericArray::from_slice(&nonce);
|
||||||
|
|
||||||
|
let additional_data = generate_aead_additional_data(pkt_rlh, payload.len());
|
||||||
|
|
||||||
|
let mut buffer: Vec<u8> = Vec::new();
|
||||||
|
buffer.extend_from_slice(payload);
|
||||||
|
|
||||||
|
self.local_gcm
|
||||||
|
.encrypt_in_place(nonce, &additional_data, &mut buffer)
|
||||||
|
.map_err(|e| Error::Other(e.to_string()))?;
|
||||||
|
|
||||||
|
let mut r = Vec::with_capacity(raw.len() + nonce.len() + buffer.len());
|
||||||
|
r.extend_from_slice(raw);
|
||||||
|
r.extend_from_slice(&nonce[4..]);
|
||||||
|
r.extend_from_slice(&buffer);
|
||||||
|
|
||||||
|
// Update recordLayer size to include explicit nonce
|
||||||
|
let r_len = (r.len() - RECORD_LAYER_HEADER_SIZE) as u16;
|
||||||
|
r[RECORD_LAYER_HEADER_SIZE - 2..RECORD_LAYER_HEADER_SIZE]
|
||||||
|
.copy_from_slice(&r_len.to_be_bytes());
|
||||||
|
|
||||||
|
Ok(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decrypt(&self, r: &[u8]) -> Result<Vec<u8>> {
|
||||||
|
let mut reader = Cursor::new(r);
|
||||||
|
let h = RecordLayerHeader::unmarshal(&mut reader)?;
|
||||||
|
if h.content_type == ContentType::ChangeCipherSpec {
|
||||||
|
// Nothing to encrypt with ChangeCipherSpec
|
||||||
|
return Ok(r.to_vec());
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.len() <= (RECORD_LAYER_HEADER_SIZE + 8) {
|
||||||
|
return Err(Error::ErrNotEnoughRoomForNonce);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut nonce = vec![];
|
||||||
|
nonce.extend_from_slice(&self.remote_write_iv[..4]);
|
||||||
|
nonce.extend_from_slice(&r[RECORD_LAYER_HEADER_SIZE..RECORD_LAYER_HEADER_SIZE + 8]);
|
||||||
|
let nonce = GenericArray::from_slice(&nonce);
|
||||||
|
|
||||||
|
let out = &r[RECORD_LAYER_HEADER_SIZE + 8..];
|
||||||
|
|
||||||
|
let additional_data = generate_aead_additional_data(&h, out.len() - CRYPTO_GCM_TAG_LENGTH);
|
||||||
|
|
||||||
|
let mut buffer: Vec<u8> = Vec::new();
|
||||||
|
buffer.extend_from_slice(out);
|
||||||
|
|
||||||
|
self.remote_gcm
|
||||||
|
.decrypt_in_place(nonce, &additional_data, &mut buffer)
|
||||||
|
.map_err(|e| Error::Other(e.to_string()))?;
|
||||||
|
|
||||||
|
let mut d = Vec::with_capacity(RECORD_LAYER_HEADER_SIZE + buffer.len());
|
||||||
|
d.extend_from_slice(&r[..RECORD_LAYER_HEADER_SIZE]);
|
||||||
|
d.extend_from_slice(&buffer);
|
||||||
|
|
||||||
|
Ok(d)
|
||||||
|
}
|
||||||
|
}
|
||||||
221
libs/dtls/src/crypto/crypto_test.rs
Normal file
221
libs/dtls/src/crypto/crypto_test.rs
Normal file
@@ -0,0 +1,221 @@
|
|||||||
|
use std::io::Cursor;
|
||||||
|
|
||||||
|
use x509_parser::pem::Pem;
|
||||||
|
|
||||||
|
use super::crypto_ccm::*;
|
||||||
|
use super::*;
|
||||||
|
use crate::content::ContentType;
|
||||||
|
use crate::record_layer::record_layer_header::{ProtocolVersion, RECORD_LAYER_HEADER_SIZE};
|
||||||
|
|
||||||
|
const RAW_PRIVATE_KEY: &str = "
|
||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEowIBAAKCAQEAxIA2BrrnR2sIlATsp7aRBD/3krwZ7vt9dNeoDQAee0s6SuYP
|
||||||
|
6MBx/HPnAkwNvPS90R05a7pwRkoT6Ur4PfPhCVlUe8lV+0Eto3ZSEeHz3HdsqlM3
|
||||||
|
bso67L7Dqrc7MdVstlKcgJi8yeAoGOIL9/igOv0XBFCeznm9nznx6mnsR5cugw+1
|
||||||
|
ypXelaHmBCLV7r5SeVSh57+KhvZGbQ2fFpUaTPegRpJZXBNS8lSeWvtOv9d6N5UB
|
||||||
|
ROTAJodMZT5AfX0jB0QB9IT/0I96H6BSENH08NXOeXApMuLKvnAf361rS7cRAfRL
|
||||||
|
rWZqERMP4u6Cnk0Cnckc3WcW27kGGIbtwbqUIQIDAQABAoIBAGF7OVIdZp8Hejn0
|
||||||
|
N3L8HvT8xtUEe9kS6ioM0lGgvX5s035Uo4/T6LhUx0VcdXRH9eLHnLTUyN4V4cra
|
||||||
|
ZkxVsE3zAvZl60G6E+oDyLMWZOP6Wu4kWlub9597A5atT7BpMIVCdmFVZFLB4SJ3
|
||||||
|
AXkC3nplFAYP+Lh1rJxRIrIn2g+pEeBboWbYA++oDNuMQffDZaokTkJ8Bn1JZYh0
|
||||||
|
xEXKY8Bi2Egd5NMeZa1UFO6y8tUbZfwgVs6Enq5uOgtfayq79vZwyjj1kd29MBUD
|
||||||
|
8g8byV053ZKxbUOiOuUts97eb+fN3DIDRTcT2c+lXt/4C54M1FclJAbtYRK/qwsl
|
||||||
|
pYWKQAECgYEA4ZUbqQnTo1ICvj81ifGrz+H4LKQqe92Hbf/W51D/Umk2kP702W22
|
||||||
|
HP4CvrJRtALThJIG9m2TwUjl/WAuZIBrhSAbIvc3Fcoa2HjdRp+sO5U1ueDq7d/S
|
||||||
|
Z+PxRI8cbLbRpEdIaoR46qr/2uWZ943PHMv9h4VHPYn1w8b94hwD6vkCgYEA3v87
|
||||||
|
mFLzyM9ercnEv9zHMRlMZFQhlcUGQZvfb8BuJYl/WogyT6vRrUuM0QXULNEPlrin
|
||||||
|
mBQTqc1nCYbgkFFsD2VVt1qIyiAJsB9MD1LNV6YuvE7T2KOSadmsA4fa9PUqbr71
|
||||||
|
hf3lTTq+LeR09LebO7WgSGYY+5YKVOEGpYMR1GkCgYEAxPVQmk3HKHEhjgRYdaG5
|
||||||
|
lp9A9ZE8uruYVJWtiHgzBTxx9TV2iST+fd/We7PsHFTfY3+wbpcMDBXfIVRKDVwH
|
||||||
|
BMwchXH9+Ztlxx34bYJaegd0SmA0Hw9ugWEHNgoSEmWpM1s9wir5/ELjc7dGsFtz
|
||||||
|
uzvsl9fpdLSxDYgAAdzeGtkCgYBAzKIgrVox7DBzB8KojhtD5ToRnXD0+H/M6OKQ
|
||||||
|
srZPKhlb0V/tTtxrIx0UUEFLlKSXA6mPw6XDHfDnD86JoV9pSeUSlrhRI+Ysy6tq
|
||||||
|
eIE7CwthpPZiaYXORHZ7wCqcK/HcpJjsCs9rFbrV0yE5S3FMdIbTAvgXg44VBB7O
|
||||||
|
UbwIoQKBgDuY8gSrA5/A747wjjmsdRWK4DMTMEV4eCW1BEP7Tg7Cxd5n3xPJiYhr
|
||||||
|
nhLGN+mMnVIcv2zEMS0/eNZr1j/0BtEdx+3IC6Eq+ONY0anZ4Irt57/5QeKgKn/L
|
||||||
|
JPhfPySIPG4UmwE4gW8t79vfOKxnUu2fDD1ZXUYopan6EckACNH/
|
||||||
|
-----END RSA PRIVATE KEY-----
|
||||||
|
";
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_generate_key_signature() -> Result<()> {
|
||||||
|
let reader = Cursor::new(RAW_PRIVATE_KEY.as_bytes());
|
||||||
|
let pem = match Pem::read(reader) {
|
||||||
|
Ok((pem, _)) => pem,
|
||||||
|
Err(_) => return Err(Error::Other("Pem::read error".to_owned())),
|
||||||
|
};
|
||||||
|
//let private_key = rsa::RSAPrivateKey::from_pkcs1(&pem.contents)?;
|
||||||
|
|
||||||
|
let client_random = vec![
|
||||||
|
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
|
||||||
|
0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d,
|
||||||
|
0x1e, 0x1f,
|
||||||
|
];
|
||||||
|
let server_random = vec![
|
||||||
|
0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e,
|
||||||
|
0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d,
|
||||||
|
0x8e, 0x8f,
|
||||||
|
];
|
||||||
|
let public_key = vec![
|
||||||
|
0x20, 0x9f, 0xd7, 0xad, 0x6d, 0xcf, 0xf4, 0x29, 0x8d, 0xd3, 0xf9, 0x6d, 0x5b, 0x1b, 0x2a,
|
||||||
|
0xf9, 0x10, 0xa0, 0x53, 0x5b, 0x14, 0x88, 0xd7, 0xf8, 0xfa, 0xbb, 0x34, 0x9a, 0x98, 0x28,
|
||||||
|
0x80, 0xb6, 0x15,
|
||||||
|
];
|
||||||
|
let expected_signature = vec![
|
||||||
|
0x6f, 0x47, 0x97, 0x85, 0xcc, 0x76, 0x50, 0x93, 0xbd, 0xe2, 0x6a, 0x69, 0x0b, 0xc3, 0x03,
|
||||||
|
0xd1, 0xb7, 0xe4, 0xab, 0x88, 0x7b, 0xa6, 0x52, 0x80, 0xdf, 0xaa, 0x25, 0x7a, 0xdb, 0x29,
|
||||||
|
0x32, 0xe4, 0xd8, 0x28, 0x28, 0xb3, 0xe8, 0x04, 0x3c, 0x38, 0x16, 0xfc, 0x78, 0xe9, 0x15,
|
||||||
|
0x7b, 0xc5, 0xbd, 0x7d, 0xfc, 0xcd, 0x83, 0x00, 0x57, 0x4a, 0x3c, 0x23, 0x85, 0x75, 0x6b,
|
||||||
|
0x37, 0xd5, 0x89, 0x72, 0x73, 0xf0, 0x44, 0x8c, 0x00, 0x70, 0x1f, 0x6e, 0xa2, 0x81, 0xd0,
|
||||||
|
0x09, 0xc5, 0x20, 0x36, 0xab, 0x23, 0x09, 0x40, 0x1f, 0x4d, 0x45, 0x96, 0x62, 0xbb, 0x81,
|
||||||
|
0xb0, 0x30, 0x72, 0xad, 0x3a, 0x0a, 0xac, 0x31, 0x63, 0x40, 0x52, 0x0a, 0x27, 0xf3, 0x34,
|
||||||
|
0xde, 0x27, 0x7d, 0xb7, 0x54, 0xff, 0x0f, 0x9f, 0x5a, 0xfe, 0x07, 0x0f, 0x4e, 0x9f, 0x53,
|
||||||
|
0x04, 0x34, 0x62, 0xf4, 0x30, 0x74, 0x83, 0x35, 0xfc, 0xe4, 0x7e, 0xbf, 0x5a, 0xc4, 0x52,
|
||||||
|
0xd0, 0xea, 0xf9, 0x61, 0x4e, 0xf5, 0x1c, 0x0e, 0x58, 0x02, 0x71, 0xfb, 0x1f, 0x34, 0x55,
|
||||||
|
0xe8, 0x36, 0x70, 0x3c, 0xc1, 0xcb, 0xc9, 0xb7, 0xbb, 0xb5, 0x1c, 0x44, 0x9a, 0x6d, 0x88,
|
||||||
|
0x78, 0x98, 0xd4, 0x91, 0x2e, 0xeb, 0x98, 0x81, 0x23, 0x30, 0x73, 0x39, 0x43, 0xd5, 0xbb,
|
||||||
|
0x70, 0x39, 0xba, 0x1f, 0xdb, 0x70, 0x9f, 0x91, 0x83, 0x56, 0xc2, 0xde, 0xed, 0x17, 0x6d,
|
||||||
|
0x2c, 0x3e, 0x21, 0xea, 0x36, 0xb4, 0x91, 0xd8, 0x31, 0x05, 0x60, 0x90, 0xfd, 0xc6, 0x74,
|
||||||
|
0xa9, 0x7b, 0x18, 0xfc, 0x1c, 0x6a, 0x1c, 0x6e, 0xec, 0xd3, 0xc1, 0xc0, 0x0d, 0x11, 0x25,
|
||||||
|
0x48, 0x37, 0x3d, 0x45, 0x11, 0xa2, 0x31, 0x14, 0x0a, 0x66, 0x9f, 0xd8, 0xac, 0x74, 0xa2,
|
||||||
|
0xcd, 0xc8, 0x79, 0xb3, 0x9e, 0xc6, 0x66, 0x25, 0xcf, 0x2c, 0x87, 0x5e, 0x5c, 0x36, 0x75,
|
||||||
|
0x86,
|
||||||
|
];
|
||||||
|
|
||||||
|
let signature = generate_key_signature(
|
||||||
|
&client_random,
|
||||||
|
&server_random,
|
||||||
|
&public_key,
|
||||||
|
NamedCurve::X25519,
|
||||||
|
&CryptoPrivateKey {
|
||||||
|
kind: CryptoPrivateKeyKind::Rsa256(
|
||||||
|
ring::rsa::KeyPair::from_der(&pem.contents)
|
||||||
|
.map_err(|e| Error::Other(e.to_string()))?,
|
||||||
|
),
|
||||||
|
serialized_der: pem.contents.clone(),
|
||||||
|
}, //hashAlgorithmSHA256,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
signature, expected_signature,
|
||||||
|
"Signature generation failed \nexp {expected_signature:?} \nactual {signature:?} "
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_ccm_encryption_and_decryption() -> Result<()> {
|
||||||
|
let key = vec![
|
||||||
|
0x18, 0x78, 0xac, 0xc2, 0x2a, 0xd8, 0xbd, 0xd8, 0xc6, 0x01, 0xa6, 0x17, 0x12, 0x6f, 0x63,
|
||||||
|
0x54,
|
||||||
|
];
|
||||||
|
let iv = vec![0x0e, 0xb2, 0x09, 0x06];
|
||||||
|
|
||||||
|
let ccm = CryptoCcm::new(&CryptoCcmTagLen::CryptoCcmTagLength, &key, &iv, &key, &iv);
|
||||||
|
|
||||||
|
let rlh = RecordLayerHeader {
|
||||||
|
content_type: ContentType::ApplicationData,
|
||||||
|
protocol_version: ProtocolVersion {
|
||||||
|
major: 0xfe,
|
||||||
|
minor: 0xff,
|
||||||
|
},
|
||||||
|
epoch: 0,
|
||||||
|
sequence_number: 18,
|
||||||
|
content_len: 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
let raw = vec![
|
||||||
|
0x17, 0xfe, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x03, 0xff, 0xaa,
|
||||||
|
0xbb,
|
||||||
|
];
|
||||||
|
|
||||||
|
let cipher_text = ccm.encrypt(&rlh, &raw)?;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
&cipher_text[RECORD_LAYER_HEADER_SIZE - 2..RECORD_LAYER_HEADER_SIZE],
|
||||||
|
[0, 27],
|
||||||
|
"RecordLayer size updating failed \nexp: {:?} \nactual {:?} ",
|
||||||
|
[0, 27],
|
||||||
|
&cipher_text[RECORD_LAYER_HEADER_SIZE - 2..RECORD_LAYER_HEADER_SIZE]
|
||||||
|
);
|
||||||
|
|
||||||
|
let plain_text = ccm.decrypt(&cipher_text)?;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
raw[RECORD_LAYER_HEADER_SIZE..],
|
||||||
|
plain_text[RECORD_LAYER_HEADER_SIZE..],
|
||||||
|
"Decryption failed \nexp: {:?} \nactual {:?} ",
|
||||||
|
&raw[RECORD_LAYER_HEADER_SIZE..],
|
||||||
|
&plain_text[RECORD_LAYER_HEADER_SIZE..]
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_certificate_verify() -> Result<()> {
|
||||||
|
let plain_text: Vec<u8> = vec![
|
||||||
|
0x6f, 0x47, 0x97, 0x85, 0xcc, 0x76, 0x50, 0x93, 0xbd, 0xe2, 0x6a, 0x69, 0x0b, 0xc3, 0x03,
|
||||||
|
0xd1, 0xb7, 0xe4, 0xab, 0x88, 0x7b, 0xa6, 0x52, 0x80, 0xdf, 0xaa, 0x25, 0x7a, 0xdb, 0x29,
|
||||||
|
0x32, 0xe4, 0xd8, 0x28, 0x28, 0xb3, 0xe8, 0x04, 0x3c, 0x38, 0x16, 0xfc, 0x78, 0xe9, 0x15,
|
||||||
|
0x7b, 0xc5, 0xbd, 0x7d, 0xfc, 0xcd, 0x83, 0x00, 0x57, 0x4a, 0x3c, 0x23, 0x85, 0x75, 0x6b,
|
||||||
|
0x37, 0xd5, 0x89, 0x72, 0x73, 0xf0, 0x44, 0x8c, 0x00, 0x70, 0x1f, 0x6e, 0xa2, 0x81, 0xd0,
|
||||||
|
0x09, 0xc5, 0x20, 0x36, 0xab, 0x23, 0x09, 0x40, 0x1f, 0x4d, 0x45, 0x96, 0x62, 0xbb, 0x81,
|
||||||
|
0xb0, 0x30, 0x72, 0xad, 0x3a, 0x0a, 0xac, 0x31, 0x63, 0x40, 0x52, 0x0a, 0x27, 0xf3, 0x34,
|
||||||
|
0xde, 0x27, 0x7d, 0xb7, 0x54, 0xff, 0x0f, 0x9f, 0x5a, 0xfe, 0x07, 0x0f, 0x4e, 0x9f, 0x53,
|
||||||
|
0x04, 0x34, 0x62, 0xf4, 0x30, 0x74, 0x83, 0x35, 0xfc, 0xe4, 0x7e, 0xbf, 0x5a, 0xc4, 0x52,
|
||||||
|
0xd0, 0xea, 0xf9, 0x61, 0x4e, 0xf5, 0x1c, 0x0e, 0x58, 0x02, 0x71, 0xfb, 0x1f, 0x34, 0x55,
|
||||||
|
0xe8, 0x36, 0x70, 0x3c, 0xc1, 0xcb, 0xc9, 0xb7, 0xbb, 0xb5, 0x1c, 0x44, 0x9a, 0x6d, 0x88,
|
||||||
|
0x78, 0x98, 0xd4, 0x91, 0x2e, 0xeb, 0x98, 0x81, 0x23, 0x30, 0x73, 0x39, 0x43, 0xd5, 0xbb,
|
||||||
|
0x70, 0x39, 0xba, 0x1f, 0xdb, 0x70, 0x9f, 0x91, 0x83, 0x56, 0xc2, 0xde, 0xed, 0x17, 0x6d,
|
||||||
|
0x2c, 0x3e, 0x21, 0xea, 0x36, 0xb4, 0x91, 0xd8, 0x31, 0x05, 0x60, 0x90, 0xfd, 0xc6, 0x74,
|
||||||
|
0xa9, 0x7b, 0x18, 0xfc, 0x1c, 0x6a, 0x1c, 0x6e, 0xec, 0xd3, 0xc1, 0xc0, 0x0d, 0x11, 0x25,
|
||||||
|
0x48, 0x37, 0x3d, 0x45, 0x11, 0xa2, 0x31, 0x14, 0x0a, 0x66, 0x9f, 0xd8, 0xac, 0x74, 0xa2,
|
||||||
|
0xcd, 0xc8, 0x79, 0xb3, 0x9e, 0xc6, 0x66, 0x25, 0xcf, 0x2c, 0x87, 0x5e, 0x5c, 0x36, 0x75,
|
||||||
|
0x86,
|
||||||
|
];
|
||||||
|
|
||||||
|
//test ECDSA256
|
||||||
|
let certificate_ecdsa256 = Certificate::generate_self_signed(vec!["localhost".to_owned()])?;
|
||||||
|
let cert_verify_ecdsa256 =
|
||||||
|
generate_certificate_verify(&plain_text, &certificate_ecdsa256.private_key)?;
|
||||||
|
verify_certificate_verify(
|
||||||
|
&plain_text,
|
||||||
|
&SignatureHashAlgorithm {
|
||||||
|
hash: HashAlgorithm::Sha256,
|
||||||
|
signature: SignatureAlgorithm::Ecdsa,
|
||||||
|
},
|
||||||
|
&cert_verify_ecdsa256,
|
||||||
|
&certificate_ecdsa256
|
||||||
|
.certificate
|
||||||
|
.iter()
|
||||||
|
.map(|x| x.as_ref().to_owned())
|
||||||
|
.collect::<Vec<Vec<u8>>>(),
|
||||||
|
false,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
//test ED25519
|
||||||
|
let certificate_ed25519 = Certificate::generate_self_signed_with_alg(
|
||||||
|
vec!["localhost".to_owned()],
|
||||||
|
&rcgen::PKCS_ED25519,
|
||||||
|
)?;
|
||||||
|
let cert_verify_ed25519 =
|
||||||
|
generate_certificate_verify(&plain_text, &certificate_ed25519.private_key)?;
|
||||||
|
verify_certificate_verify(
|
||||||
|
&plain_text,
|
||||||
|
&SignatureHashAlgorithm {
|
||||||
|
hash: HashAlgorithm::Sha256,
|
||||||
|
signature: SignatureAlgorithm::Ed25519,
|
||||||
|
},
|
||||||
|
&cert_verify_ed25519,
|
||||||
|
&certificate_ed25519
|
||||||
|
.certificate
|
||||||
|
.iter()
|
||||||
|
.map(|x| x.as_ref().to_owned())
|
||||||
|
.collect::<Vec<Vec<u8>>>(),
|
||||||
|
false,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
514
libs/dtls/src/crypto/mod.rs
Normal file
514
libs/dtls/src/crypto/mod.rs
Normal file
@@ -0,0 +1,514 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod crypto_test;
|
||||||
|
|
||||||
|
pub mod crypto_cbc;
|
||||||
|
pub mod crypto_ccm;
|
||||||
|
pub mod crypto_chacha20;
|
||||||
|
pub mod crypto_gcm;
|
||||||
|
pub mod padding;
|
||||||
|
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use der_parser::oid;
|
||||||
|
use der_parser::oid::Oid;
|
||||||
|
|
||||||
|
use crate::certificate_verifier::CertificateVerifier;
|
||||||
|
use rustls_pki_types::CertificateDer;
|
||||||
|
|
||||||
|
use rcgen::{generate_simple_self_signed, CertifiedKey, KeyPair};
|
||||||
|
use ring::rand::SystemRandom;
|
||||||
|
use ring::signature::{EcdsaKeyPair, Ed25519KeyPair};
|
||||||
|
|
||||||
|
use crate::curve::named_curve::*;
|
||||||
|
use crate::error::*;
|
||||||
|
use crate::record_layer::record_layer_header::*;
|
||||||
|
use crate::signature_hash_algorithm::{HashAlgorithm, SignatureAlgorithm, SignatureHashAlgorithm};
|
||||||
|
|
||||||
|
/// A X.509 certificate(s) used to authenticate a DTLS connection.
|
||||||
|
#[derive(Clone, PartialEq, Debug)]
|
||||||
|
pub struct Certificate {
|
||||||
|
/// DER-encoded certificates.
|
||||||
|
pub certificate: Vec<CertificateDer<'static>>,
|
||||||
|
/// Private key.
|
||||||
|
pub private_key: CryptoPrivateKey,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Certificate {
|
||||||
|
/// Generate a self-signed certificate.
|
||||||
|
///
|
||||||
|
/// See [`rcgen::generate_simple_self_signed`].
|
||||||
|
pub fn generate_self_signed(subject_alt_names: impl Into<Vec<String>>) -> Result<Self> {
|
||||||
|
let CertifiedKey { cert, key_pair } =
|
||||||
|
generate_simple_self_signed(subject_alt_names).unwrap();
|
||||||
|
Ok(Certificate {
|
||||||
|
certificate: vec![cert.der().to_owned()],
|
||||||
|
private_key: CryptoPrivateKey::try_from(&key_pair)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate a self-signed certificate with the given algorithm.
|
||||||
|
///
|
||||||
|
/// See [`rcgen::Certificate::from_params`].
|
||||||
|
pub fn generate_self_signed_with_alg(
|
||||||
|
subject_alt_names: impl Into<Vec<String>>,
|
||||||
|
alg: &'static rcgen::SignatureAlgorithm,
|
||||||
|
) -> Result<Self> {
|
||||||
|
let params = rcgen::CertificateParams::new(subject_alt_names).unwrap();
|
||||||
|
let key_pair = rcgen::KeyPair::generate_for(alg).unwrap();
|
||||||
|
let cert = params.self_signed(&key_pair).unwrap();
|
||||||
|
|
||||||
|
Ok(Certificate {
|
||||||
|
certificate: vec![cert.der().to_owned()],
|
||||||
|
private_key: CryptoPrivateKey::try_from(&key_pair)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parses a certificate from the ASCII PEM format.
|
||||||
|
#[cfg(feature = "pem")]
|
||||||
|
pub fn from_pem(pem_str: &str) -> Result<Self> {
|
||||||
|
let mut pems = pem::parse_many(pem_str).map_err(|e| Error::InvalidPEM(e.to_string()))?;
|
||||||
|
if pems.len() < 2 {
|
||||||
|
return Err(Error::InvalidPEM(format!(
|
||||||
|
"expected at least two PEM blocks, got {}",
|
||||||
|
pems.len()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
if pems[0].tag() != "PRIVATE_KEY" {
|
||||||
|
return Err(Error::InvalidPEM(format!(
|
||||||
|
"invalid tag (expected: 'PRIVATE_KEY', got: '{}')",
|
||||||
|
pems[0].tag()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
let keypair = KeyPair::try_from(pems[0].contents())
|
||||||
|
.map_err(|e| Error::InvalidPEM(format!("can't decode keypair: {e}")))?;
|
||||||
|
|
||||||
|
let mut rustls_certs = Vec::new();
|
||||||
|
for p in pems.drain(1..) {
|
||||||
|
if p.tag() != "CERTIFICATE" {
|
||||||
|
return Err(Error::InvalidPEM(format!(
|
||||||
|
"invalid tag (expected: 'CERTIFICATE', got: '{}')",
|
||||||
|
p.tag()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
rustls_certs.push(CertificateDer::from(p.contents().to_vec()));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Certificate {
|
||||||
|
certificate: rustls_certs,
|
||||||
|
private_key: CryptoPrivateKey::try_from(&keypair)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Serializes the certificate (including the private key) in PKCS#8 format in PEM.
|
||||||
|
#[cfg(feature = "pem")]
|
||||||
|
pub fn serialize_pem(&self) -> String {
|
||||||
|
let mut data = vec![pem::Pem::new(
|
||||||
|
"PRIVATE_KEY".to_string(),
|
||||||
|
self.private_key.serialized_der.clone(),
|
||||||
|
)];
|
||||||
|
for rustls_cert in &self.certificate {
|
||||||
|
data.push(pem::Pem::new(
|
||||||
|
"CERTIFICATE".to_string(),
|
||||||
|
rustls_cert.as_ref(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
pem::encode_many(&data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn value_key_message(
|
||||||
|
client_random: &[u8],
|
||||||
|
server_random: &[u8],
|
||||||
|
public_key: &[u8],
|
||||||
|
named_curve: NamedCurve,
|
||||||
|
) -> Vec<u8> {
|
||||||
|
let mut server_ecdh_params = vec![0u8; 4];
|
||||||
|
server_ecdh_params[0] = 3; // named curve
|
||||||
|
server_ecdh_params[1..3].copy_from_slice(&(named_curve as u16).to_be_bytes());
|
||||||
|
server_ecdh_params[3] = public_key.len() as u8;
|
||||||
|
|
||||||
|
let mut plaintext = vec![];
|
||||||
|
plaintext.extend_from_slice(client_random);
|
||||||
|
plaintext.extend_from_slice(server_random);
|
||||||
|
plaintext.extend_from_slice(&server_ecdh_params);
|
||||||
|
plaintext.extend_from_slice(public_key);
|
||||||
|
|
||||||
|
plaintext
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Either ED25519, ECDSA or RSA keypair.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum CryptoPrivateKeyKind {
|
||||||
|
Ed25519(Ed25519KeyPair),
|
||||||
|
Ecdsa256(EcdsaKeyPair),
|
||||||
|
Rsa256(ring::rsa::KeyPair),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Private key.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct CryptoPrivateKey {
|
||||||
|
/// Keypair.
|
||||||
|
pub kind: CryptoPrivateKeyKind,
|
||||||
|
/// DER-encoded keypair.
|
||||||
|
pub serialized_der: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for CryptoPrivateKey {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
if self.serialized_der != other.serialized_der {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
matches!(
|
||||||
|
(&self.kind, &other.kind),
|
||||||
|
(
|
||||||
|
CryptoPrivateKeyKind::Rsa256(_),
|
||||||
|
CryptoPrivateKeyKind::Rsa256(_)
|
||||||
|
) | (
|
||||||
|
CryptoPrivateKeyKind::Ecdsa256(_),
|
||||||
|
CryptoPrivateKeyKind::Ecdsa256(_)
|
||||||
|
) | (
|
||||||
|
CryptoPrivateKeyKind::Ed25519(_),
|
||||||
|
CryptoPrivateKeyKind::Ed25519(_)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clone for CryptoPrivateKey {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
match self.kind {
|
||||||
|
CryptoPrivateKeyKind::Ed25519(_) => CryptoPrivateKey {
|
||||||
|
kind: CryptoPrivateKeyKind::Ed25519(
|
||||||
|
Ed25519KeyPair::from_pkcs8_maybe_unchecked(&self.serialized_der).unwrap(),
|
||||||
|
),
|
||||||
|
serialized_der: self.serialized_der.clone(),
|
||||||
|
},
|
||||||
|
CryptoPrivateKeyKind::Ecdsa256(_) => CryptoPrivateKey {
|
||||||
|
kind: CryptoPrivateKeyKind::Ecdsa256(
|
||||||
|
EcdsaKeyPair::from_pkcs8(
|
||||||
|
&ring::signature::ECDSA_P256_SHA256_ASN1_SIGNING,
|
||||||
|
&self.serialized_der,
|
||||||
|
&SystemRandom::new(),
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
serialized_der: self.serialized_der.clone(),
|
||||||
|
},
|
||||||
|
CryptoPrivateKeyKind::Rsa256(_) => CryptoPrivateKey {
|
||||||
|
kind: CryptoPrivateKeyKind::Rsa256(
|
||||||
|
ring::rsa::KeyPair::from_pkcs8(&self.serialized_der).unwrap(),
|
||||||
|
),
|
||||||
|
serialized_der: self.serialized_der.clone(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&KeyPair> for CryptoPrivateKey {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn try_from(key_pair: &KeyPair) -> Result<Self> {
|
||||||
|
Self::from_key_pair(key_pair)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CryptoPrivateKey {
|
||||||
|
pub fn from_key_pair(key_pair: &KeyPair) -> Result<Self> {
|
||||||
|
let serialized_der = key_pair.serialize_der();
|
||||||
|
if key_pair.is_compatible(&rcgen::PKCS_ED25519) {
|
||||||
|
Ok(CryptoPrivateKey {
|
||||||
|
kind: CryptoPrivateKeyKind::Ed25519(
|
||||||
|
Ed25519KeyPair::from_pkcs8_maybe_unchecked(&serialized_der)
|
||||||
|
.map_err(|e| Error::Other(e.to_string()))?,
|
||||||
|
),
|
||||||
|
serialized_der,
|
||||||
|
})
|
||||||
|
} else if key_pair.is_compatible(&rcgen::PKCS_ECDSA_P256_SHA256) {
|
||||||
|
Ok(CryptoPrivateKey {
|
||||||
|
kind: CryptoPrivateKeyKind::Ecdsa256(
|
||||||
|
EcdsaKeyPair::from_pkcs8(
|
||||||
|
&ring::signature::ECDSA_P256_SHA256_ASN1_SIGNING,
|
||||||
|
&serialized_der,
|
||||||
|
&SystemRandom::new(),
|
||||||
|
)
|
||||||
|
.map_err(|e| Error::Other(e.to_string()))?,
|
||||||
|
),
|
||||||
|
serialized_der,
|
||||||
|
})
|
||||||
|
} else if key_pair.is_compatible(&rcgen::PKCS_RSA_SHA256) {
|
||||||
|
Ok(CryptoPrivateKey {
|
||||||
|
kind: CryptoPrivateKeyKind::Rsa256(
|
||||||
|
ring::rsa::KeyPair::from_pkcs8(&serialized_der)
|
||||||
|
.map_err(|e| Error::Other(e.to_string()))?,
|
||||||
|
),
|
||||||
|
serialized_der,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(Error::Other("Unsupported key_pair".to_owned()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the client provided a "signature_algorithms" extension, then all
|
||||||
|
// certificates provided by the server MUST be signed by a
|
||||||
|
// hash/signature algorithm pair that appears in that extension
|
||||||
|
//
|
||||||
|
// https://tools.ietf.org/html/rfc5246#section-7.4.2
|
||||||
|
pub(crate) fn generate_key_signature(
|
||||||
|
client_random: &[u8],
|
||||||
|
server_random: &[u8],
|
||||||
|
public_key: &[u8],
|
||||||
|
named_curve: NamedCurve,
|
||||||
|
private_key: &CryptoPrivateKey, /*, hash_algorithm: HashAlgorithm*/
|
||||||
|
) -> Result<Vec<u8>> {
|
||||||
|
let msg = value_key_message(client_random, server_random, public_key, named_curve);
|
||||||
|
let signature = match &private_key.kind {
|
||||||
|
CryptoPrivateKeyKind::Ed25519(kp) => kp.sign(&msg).as_ref().to_vec(),
|
||||||
|
CryptoPrivateKeyKind::Ecdsa256(kp) => {
|
||||||
|
let system_random = SystemRandom::new();
|
||||||
|
kp.sign(&system_random, &msg)
|
||||||
|
.map_err(|e| Error::Other(e.to_string()))?
|
||||||
|
.as_ref()
|
||||||
|
.to_vec()
|
||||||
|
}
|
||||||
|
CryptoPrivateKeyKind::Rsa256(kp) => {
|
||||||
|
let system_random = SystemRandom::new();
|
||||||
|
let mut signature = vec![0; kp.public().modulus_len()];
|
||||||
|
kp.sign(
|
||||||
|
&ring::signature::RSA_PKCS1_SHA256,
|
||||||
|
&system_random,
|
||||||
|
&msg,
|
||||||
|
&mut signature,
|
||||||
|
)
|
||||||
|
.map_err(|e| Error::Other(e.to_string()))?;
|
||||||
|
|
||||||
|
signature
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(signature)
|
||||||
|
}
|
||||||
|
|
||||||
|
// add OID_ED25519 which is not defined in x509_parser
|
||||||
|
pub const OID_ED25519: Oid<'static> = oid!(1.3.101 .112);
|
||||||
|
pub const OID_ECDSA: Oid<'static> = oid!(1.2.840 .10045 .2 .1);
|
||||||
|
|
||||||
|
fn verify_signature(
|
||||||
|
message: &[u8],
|
||||||
|
hash_algorithm: &SignatureHashAlgorithm,
|
||||||
|
remote_key_signature: &[u8],
|
||||||
|
raw_certificates: &[Vec<u8>],
|
||||||
|
insecure_verification: bool,
|
||||||
|
) -> Result<()> {
|
||||||
|
if raw_certificates.is_empty() {
|
||||||
|
return Err(Error::ErrLengthMismatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
let (_, certificate) = x509_parser::parse_x509_certificate(&raw_certificates[0])
|
||||||
|
.map_err(|e| Error::Other(e.to_string()))?;
|
||||||
|
|
||||||
|
let verify_alg: &dyn ring::signature::VerificationAlgorithm = match hash_algorithm.signature {
|
||||||
|
SignatureAlgorithm::Ed25519 => &ring::signature::ED25519,
|
||||||
|
SignatureAlgorithm::Ecdsa if hash_algorithm.hash == HashAlgorithm::Sha256 => {
|
||||||
|
&ring::signature::ECDSA_P256_SHA256_ASN1
|
||||||
|
}
|
||||||
|
SignatureAlgorithm::Ecdsa if hash_algorithm.hash == HashAlgorithm::Sha384 => {
|
||||||
|
&ring::signature::ECDSA_P384_SHA384_ASN1
|
||||||
|
}
|
||||||
|
SignatureAlgorithm::Rsa if hash_algorithm.hash == HashAlgorithm::Sha1 => {
|
||||||
|
&ring::signature::RSA_PKCS1_1024_8192_SHA1_FOR_LEGACY_USE_ONLY
|
||||||
|
}
|
||||||
|
SignatureAlgorithm::Rsa if (hash_algorithm.hash == HashAlgorithm::Sha256) => {
|
||||||
|
if remote_key_signature.len() < 256 && insecure_verification {
|
||||||
|
&ring::signature::RSA_PKCS1_1024_8192_SHA256_FOR_LEGACY_USE_ONLY
|
||||||
|
} else {
|
||||||
|
&ring::signature::RSA_PKCS1_2048_8192_SHA256
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SignatureAlgorithm::Rsa if hash_algorithm.hash == HashAlgorithm::Sha384 => {
|
||||||
|
&ring::signature::RSA_PKCS1_2048_8192_SHA384
|
||||||
|
}
|
||||||
|
SignatureAlgorithm::Rsa if hash_algorithm.hash == HashAlgorithm::Sha512 => {
|
||||||
|
if remote_key_signature.len() < 256 && insecure_verification {
|
||||||
|
&ring::signature::RSA_PKCS1_1024_8192_SHA512_FOR_LEGACY_USE_ONLY
|
||||||
|
} else {
|
||||||
|
&ring::signature::RSA_PKCS1_2048_8192_SHA512
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => return Err(Error::ErrKeySignatureVerifyUnimplemented),
|
||||||
|
};
|
||||||
|
|
||||||
|
log::trace!("Picked an algorithm {verify_alg:?}");
|
||||||
|
|
||||||
|
let public_key = ring::signature::UnparsedPublicKey::new(
|
||||||
|
verify_alg,
|
||||||
|
certificate
|
||||||
|
.tbs_certificate
|
||||||
|
.subject_pki
|
||||||
|
.subject_public_key
|
||||||
|
.data,
|
||||||
|
);
|
||||||
|
|
||||||
|
public_key
|
||||||
|
.verify(message, remote_key_signature)
|
||||||
|
.map_err(|e| Error::Other(e.to_string()))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn verify_key_signature(
|
||||||
|
message: &[u8],
|
||||||
|
hash_algorithm: &SignatureHashAlgorithm,
|
||||||
|
remote_key_signature: &[u8],
|
||||||
|
raw_certificates: &[Vec<u8>],
|
||||||
|
insecure_verification: bool,
|
||||||
|
) -> Result<()> {
|
||||||
|
verify_signature(
|
||||||
|
message,
|
||||||
|
hash_algorithm,
|
||||||
|
remote_key_signature,
|
||||||
|
raw_certificates,
|
||||||
|
insecure_verification,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the server has sent a CertificateRequest message, the client MUST send the Certificate
|
||||||
|
// message. The ClientKeyExchange message is now sent, and the content
|
||||||
|
// of that message will depend on the public key algorithm selected
|
||||||
|
// between the ClientHello and the ServerHello. If the client has sent
|
||||||
|
// a certificate with signing ability, a digitally-signed
|
||||||
|
// CertificateVerify message is sent to explicitly verify possession of
|
||||||
|
// the private key in the certificate.
|
||||||
|
// https://tools.ietf.org/html/rfc5246#section-7.3
|
||||||
|
pub(crate) fn generate_certificate_verify(
|
||||||
|
handshake_bodies: &[u8],
|
||||||
|
private_key: &CryptoPrivateKey, /*, hashAlgorithm hashAlgorithm*/
|
||||||
|
) -> Result<Vec<u8>> {
|
||||||
|
let signature = match &private_key.kind {
|
||||||
|
CryptoPrivateKeyKind::Ed25519(kp) => kp.sign(handshake_bodies).as_ref().to_vec(),
|
||||||
|
CryptoPrivateKeyKind::Ecdsa256(kp) => {
|
||||||
|
let system_random = SystemRandom::new();
|
||||||
|
kp.sign(&system_random, handshake_bodies)
|
||||||
|
.map_err(|e| Error::Other(e.to_string()))?
|
||||||
|
.as_ref()
|
||||||
|
.to_vec()
|
||||||
|
}
|
||||||
|
CryptoPrivateKeyKind::Rsa256(kp) => {
|
||||||
|
let system_random = SystemRandom::new();
|
||||||
|
let mut signature = vec![0; kp.public().modulus_len()];
|
||||||
|
kp.sign(
|
||||||
|
&ring::signature::RSA_PKCS1_SHA256,
|
||||||
|
&system_random,
|
||||||
|
handshake_bodies,
|
||||||
|
&mut signature,
|
||||||
|
)
|
||||||
|
.map_err(|e| Error::Other(e.to_string()))?;
|
||||||
|
|
||||||
|
signature
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(signature)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn verify_certificate_verify(
|
||||||
|
handshake_bodies: &[u8],
|
||||||
|
hash_algorithm: &SignatureHashAlgorithm,
|
||||||
|
remote_key_signature: &[u8],
|
||||||
|
raw_certificates: &[Vec<u8>],
|
||||||
|
insecure_verification: bool,
|
||||||
|
) -> Result<()> {
|
||||||
|
verify_signature(
|
||||||
|
handshake_bodies,
|
||||||
|
hash_algorithm,
|
||||||
|
remote_key_signature,
|
||||||
|
raw_certificates,
|
||||||
|
insecure_verification,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn load_certs(raw_certificates: &[Vec<u8>]) -> Result<Vec<CertificateDer<'static>>> {
|
||||||
|
if raw_certificates.is_empty() {
|
||||||
|
return Err(Error::ErrLengthMismatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut certs = vec![];
|
||||||
|
for raw_cert in raw_certificates {
|
||||||
|
let cert = CertificateDer::from(raw_cert.to_vec());
|
||||||
|
certs.push(cert);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(certs)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn verify_client_cert(
|
||||||
|
raw_certificates: &[Vec<u8>],
|
||||||
|
cert_verifier: &Arc<CertificateVerifier>,
|
||||||
|
) -> Result<Vec<CertificateDer<'static>>> {
|
||||||
|
let chains = load_certs(raw_certificates)?;
|
||||||
|
|
||||||
|
let (end_entity, intermediates) = chains
|
||||||
|
.split_first()
|
||||||
|
.ok_or(Error::ErrClientCertificateRequired)?;
|
||||||
|
|
||||||
|
match cert_verifier.verify_client_cert(end_entity, intermediates) {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(err) => return Err(Error::Other(err.to_string())),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(chains)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn verify_server_cert(
|
||||||
|
raw_certificates: &[Vec<u8>],
|
||||||
|
cert_verifier: &Arc<CertificateVerifier>,
|
||||||
|
server_name: &str,
|
||||||
|
) -> Result<Vec<CertificateDer<'static>>> {
|
||||||
|
let chains = load_certs(raw_certificates)?;
|
||||||
|
|
||||||
|
let (end_entity, intermediates) = chains
|
||||||
|
.split_first()
|
||||||
|
.ok_or(Error::ErrServerMustHaveCertificate)?;
|
||||||
|
match cert_verifier.verify_server_cert(end_entity, intermediates, server_name) {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(err) => return Err(Error::Other(err.to_string())),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(chains)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn generate_aead_additional_data(h: &RecordLayerHeader, payload_len: usize) -> Vec<u8> {
|
||||||
|
let mut additional_data = vec![0u8; 13];
|
||||||
|
// SequenceNumber MUST be set first
|
||||||
|
// we only want uint48, clobbering an extra 2 (using uint64, rust doesn't have uint48)
|
||||||
|
additional_data[..8].copy_from_slice(&h.sequence_number.to_be_bytes());
|
||||||
|
additional_data[..2].copy_from_slice(&h.epoch.to_be_bytes());
|
||||||
|
additional_data[8] = h.content_type as u8;
|
||||||
|
additional_data[9] = h.protocol_version.major;
|
||||||
|
additional_data[10] = h.protocol_version.minor;
|
||||||
|
additional_data[11..].copy_from_slice(&(payload_len as u16).to_be_bytes());
|
||||||
|
|
||||||
|
additional_data
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
#[cfg(feature = "pem")]
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[cfg(feature = "pem")]
|
||||||
|
#[test]
|
||||||
|
fn test_certificate_serialize_pem_and_from_pem() -> crate::error::Result<()> {
|
||||||
|
let cert = Certificate::generate_self_signed(vec!["webrtc.rs".to_owned()])?;
|
||||||
|
|
||||||
|
let pem = cert.serialize_pem();
|
||||||
|
let loaded_cert = Certificate::from_pem(&pem)?;
|
||||||
|
|
||||||
|
assert_eq!(loaded_cert, cert);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
122
libs/dtls/src/crypto/padding.rs
Normal file
122
libs/dtls/src/crypto/padding.rs
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
use cbc::cipher::block_padding::{PadType, RawPadding, UnpadError};
|
||||||
|
use core::panic;
|
||||||
|
|
||||||
|
pub enum DtlsPadding {}
|
||||||
|
/// Reference: RFC5246, 6.2.3.2
|
||||||
|
impl RawPadding for DtlsPadding {
|
||||||
|
const TYPE: PadType = PadType::Reversible;
|
||||||
|
|
||||||
|
fn raw_pad(block: &mut [u8], pos: usize) {
|
||||||
|
if pos >= block.len() {
|
||||||
|
panic!("`pos` is bigger or equal to block size");
|
||||||
|
}
|
||||||
|
|
||||||
|
let padding_length = block.len() - pos - 1;
|
||||||
|
if padding_length > 255 {
|
||||||
|
panic!("block size is too big for DTLS");
|
||||||
|
}
|
||||||
|
|
||||||
|
set(&mut block[pos..], padding_length as u8);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn raw_unpad(data: &[u8]) -> Result<&[u8], UnpadError> {
|
||||||
|
let padding_length = data.last().copied().unwrap_or(1) as usize;
|
||||||
|
if padding_length + 1 > data.len() {
|
||||||
|
return Err(UnpadError);
|
||||||
|
}
|
||||||
|
|
||||||
|
let padding_begin = data.len() - padding_length - 1;
|
||||||
|
|
||||||
|
if data[padding_begin..data.len() - 1]
|
||||||
|
.iter()
|
||||||
|
.any(|&byte| byte as usize != padding_length)
|
||||||
|
{
|
||||||
|
return Err(UnpadError);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(&data[0..padding_begin])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets all bytes in `dst` equal to `value`
|
||||||
|
#[inline(always)]
|
||||||
|
fn set(dst: &mut [u8], value: u8) {
|
||||||
|
// SAFETY: we overwrite valid memory behind `dst`
|
||||||
|
// note: loop is not used here because it produces
|
||||||
|
// unnecessary branch which tests for zero-length slices
|
||||||
|
unsafe {
|
||||||
|
core::ptr::write_bytes(dst.as_mut_ptr(), value, dst.len());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub mod tests {
|
||||||
|
use rand::Rng;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn padding_length_is_amount_of_bytes_excluding_the_padding_length_itself() -> Result<(), ()> {
|
||||||
|
for original_length in 0..128 {
|
||||||
|
for padding_length in 0..(256 - original_length) {
|
||||||
|
let mut block = vec![0; original_length + padding_length + 1];
|
||||||
|
rand::rng().fill(&mut block[0..original_length]);
|
||||||
|
let original = block[0..original_length].to_vec();
|
||||||
|
DtlsPadding::raw_pad(&mut block, original_length);
|
||||||
|
|
||||||
|
for byte in block[original_length..].iter() {
|
||||||
|
assert_eq!(*byte as usize, padding_length);
|
||||||
|
}
|
||||||
|
assert_eq!(block[0..original_length], original);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic]
|
||||||
|
fn full_block_is_padding_error() {
|
||||||
|
for original_length in 0..256 {
|
||||||
|
let mut block = vec![0; original_length];
|
||||||
|
DtlsPadding::raw_pad(&mut block, original_length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic]
|
||||||
|
fn padding_length_bigger_than_255_is_a_pad_error() {
|
||||||
|
let padding_length = 256;
|
||||||
|
for original_length in 0..128 {
|
||||||
|
let mut block = vec![0; original_length + padding_length + 1];
|
||||||
|
DtlsPadding::raw_pad(&mut block, original_length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn empty_block_is_unpadding_error() {
|
||||||
|
let r = DtlsPadding::raw_unpad(&[]);
|
||||||
|
assert!(r.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn padding_too_big_for_block_is_unpadding_error() {
|
||||||
|
let r = DtlsPadding::raw_unpad(&[1]);
|
||||||
|
assert!(r.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn one_of_the_padding_bytes_with_value_different_than_padding_length_is_unpadding_error() {
|
||||||
|
for padding_length in 0..16 {
|
||||||
|
for invalid_byte in 0..padding_length {
|
||||||
|
let mut block = vec![0; padding_length + 1];
|
||||||
|
DtlsPadding::raw_pad(&mut block, 0);
|
||||||
|
|
||||||
|
assert_eq!(DtlsPadding::raw_unpad(&block).ok(), Some(&[][..]));
|
||||||
|
block[invalid_byte] = (padding_length - 1) as u8;
|
||||||
|
let r = DtlsPadding::raw_unpad(&block);
|
||||||
|
assert!(r.is_err());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
17
libs/dtls/src/curve/mod.rs
Normal file
17
libs/dtls/src/curve/mod.rs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
pub mod named_curve;
|
||||||
|
|
||||||
|
// https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-10
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||||
|
pub enum EllipticCurveType {
|
||||||
|
NamedCurve = 0x03,
|
||||||
|
Unsupported,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u8> for EllipticCurveType {
|
||||||
|
fn from(val: u8) -> Self {
|
||||||
|
match val {
|
||||||
|
0x03 => EllipticCurveType::NamedCurve,
|
||||||
|
_ => EllipticCurveType::Unsupported,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
83
libs/dtls/src/curve/named_curve.rs
Normal file
83
libs/dtls/src/curve/named_curve.rs
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
use rand_core::OsRng; // requires 'getrandom' feature
|
||||||
|
|
||||||
|
use crate::error::*;
|
||||||
|
|
||||||
|
// https://www.iana.org/assignments/tls-parameters/tls-parameters.xml#tls-parameters-8
|
||||||
|
#[repr(u16)]
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||||
|
pub enum NamedCurve {
|
||||||
|
Unsupported = 0x0000,
|
||||||
|
P256 = 0x0017,
|
||||||
|
P384 = 0x0018,
|
||||||
|
X25519 = 0x001d,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u16> for NamedCurve {
|
||||||
|
fn from(val: u16) -> Self {
|
||||||
|
match val {
|
||||||
|
0x0017 => NamedCurve::P256,
|
||||||
|
0x0018 => NamedCurve::P384,
|
||||||
|
0x001d => NamedCurve::X25519,
|
||||||
|
_ => NamedCurve::Unsupported,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) enum NamedCurvePrivateKey {
|
||||||
|
EphemeralSecretP256(p256::ecdh::EphemeralSecret),
|
||||||
|
EphemeralSecretP384(p384::ecdh::EphemeralSecret),
|
||||||
|
StaticSecretX25519(x25519_dalek::StaticSecret),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct NamedCurveKeypair {
|
||||||
|
pub(crate) curve: NamedCurve,
|
||||||
|
pub(crate) public_key: Vec<u8>,
|
||||||
|
pub(crate) private_key: NamedCurvePrivateKey,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn elliptic_curve_keypair(curve: NamedCurve) -> Result<NamedCurveKeypair> {
|
||||||
|
let (public_key, private_key) = match curve {
|
||||||
|
NamedCurve::P256 => {
|
||||||
|
let secret_key = p256::ecdh::EphemeralSecret::random(&mut OsRng);
|
||||||
|
let public_key = p256::EncodedPoint::from(secret_key.public_key());
|
||||||
|
(
|
||||||
|
public_key.as_bytes().to_vec(),
|
||||||
|
NamedCurvePrivateKey::EphemeralSecretP256(secret_key),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
NamedCurve::P384 => {
|
||||||
|
let secret_key = p384::ecdh::EphemeralSecret::random(&mut OsRng);
|
||||||
|
let public_key = p384::EncodedPoint::from(secret_key.public_key());
|
||||||
|
(
|
||||||
|
public_key.as_bytes().to_vec(),
|
||||||
|
NamedCurvePrivateKey::EphemeralSecretP384(secret_key),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
NamedCurve::X25519 => {
|
||||||
|
let secret_key = x25519_dalek::StaticSecret::random_from_rng(OsRng);
|
||||||
|
let public_key = x25519_dalek::PublicKey::from(&secret_key);
|
||||||
|
(
|
||||||
|
public_key.as_bytes().to_vec(),
|
||||||
|
NamedCurvePrivateKey::StaticSecretX25519(secret_key),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
_ => return Err(Error::ErrInvalidNamedCurve),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(NamedCurveKeypair {
|
||||||
|
curve,
|
||||||
|
public_key,
|
||||||
|
private_key,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NamedCurve {
|
||||||
|
pub fn generate_keypair(&self) -> Result<NamedCurveKeypair> {
|
||||||
|
match *self {
|
||||||
|
NamedCurve::X25519 => elliptic_curve_keypair(NamedCurve::X25519),
|
||||||
|
NamedCurve::P256 => elliptic_curve_keypair(NamedCurve::P256),
|
||||||
|
NamedCurve::P384 => elliptic_curve_keypair(NamedCurve::P384),
|
||||||
|
_ => Err(Error::ErrInvalidNamedCurve),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
223
libs/dtls/src/error.rs
Normal file
223
libs/dtls/src/error.rs
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
use std::io;
|
||||||
|
use std::string::FromUtf8Error;
|
||||||
|
|
||||||
|
use thiserror::Error;
|
||||||
|
use tokio::sync::mpsc::error::SendError as MpscSendError;
|
||||||
|
use util::KeyingMaterialExporterError;
|
||||||
|
|
||||||
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
|
#[derive(Debug, Error, PartialEq)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum Error {
|
||||||
|
#[error("conn is closed")]
|
||||||
|
ErrConnClosed,
|
||||||
|
#[error("read/write timeout")]
|
||||||
|
ErrDeadlineExceeded,
|
||||||
|
#[error("buffer is too small")]
|
||||||
|
ErrBufferTooSmall,
|
||||||
|
#[error("context is not supported for export_keying_material")]
|
||||||
|
ErrContextUnsupported,
|
||||||
|
#[error("packet is too short")]
|
||||||
|
ErrDtlspacketInvalidLength,
|
||||||
|
#[error("handshake is in progress")]
|
||||||
|
ErrHandshakeInProgress,
|
||||||
|
#[error("invalid content type")]
|
||||||
|
ErrInvalidContentType,
|
||||||
|
#[error("invalid mac")]
|
||||||
|
ErrInvalidMac,
|
||||||
|
#[error("packet length and declared length do not match")]
|
||||||
|
ErrInvalidPacketLength,
|
||||||
|
#[error("export_keying_material can not be used with a reserved label")]
|
||||||
|
ErrReservedExportKeyingMaterial,
|
||||||
|
#[error("client sent certificate verify but we have no certificate to verify")]
|
||||||
|
ErrCertificateVerifyNoCertificate,
|
||||||
|
#[error("client+server do not support any shared cipher suites")]
|
||||||
|
ErrCipherSuiteNoIntersection,
|
||||||
|
#[error("server hello can not be created without a cipher suite")]
|
||||||
|
ErrCipherSuiteUnset,
|
||||||
|
#[error("client sent certificate but did not verify it")]
|
||||||
|
ErrClientCertificateNotVerified,
|
||||||
|
#[error("server required client verification, but got none")]
|
||||||
|
ErrClientCertificateRequired,
|
||||||
|
#[error("server responded with SRTP Profile we do not support")]
|
||||||
|
ErrClientNoMatchingSrtpProfile,
|
||||||
|
#[error("client required Extended Master Secret extension, but server does not support it")]
|
||||||
|
ErrClientRequiredButNoServerEms,
|
||||||
|
#[error("server hello can not be created without a compression method")]
|
||||||
|
ErrCompressionMethodUnset,
|
||||||
|
#[error("client+server cookie does not match")]
|
||||||
|
ErrCookieMismatch,
|
||||||
|
#[error("cookie must not be longer then 255 bytes")]
|
||||||
|
ErrCookieTooLong,
|
||||||
|
#[error("PSK Identity Hint provided but PSK is nil")]
|
||||||
|
ErrIdentityNoPsk,
|
||||||
|
#[error("no certificate provided")]
|
||||||
|
ErrInvalidCertificate,
|
||||||
|
#[error("cipher spec invalid")]
|
||||||
|
ErrInvalidCipherSpec,
|
||||||
|
#[error("invalid or unknown cipher suite")]
|
||||||
|
ErrInvalidCipherSuite,
|
||||||
|
#[error("unable to determine if ClientKeyExchange is a public key or PSK Identity")]
|
||||||
|
ErrInvalidClientKeyExchange,
|
||||||
|
#[error("invalid or unknown compression method")]
|
||||||
|
ErrInvalidCompressionMethod,
|
||||||
|
#[error("ECDSA signature contained zero or negative values")]
|
||||||
|
ErrInvalidEcdsasignature,
|
||||||
|
#[error("invalid or unknown elliptic curve type")]
|
||||||
|
ErrInvalidEllipticCurveType,
|
||||||
|
#[error("invalid extension type")]
|
||||||
|
ErrInvalidExtensionType,
|
||||||
|
#[error("invalid hash algorithm")]
|
||||||
|
ErrInvalidHashAlgorithm,
|
||||||
|
#[error("invalid named curve")]
|
||||||
|
ErrInvalidNamedCurve,
|
||||||
|
#[error("invalid private key type")]
|
||||||
|
ErrInvalidPrivateKey,
|
||||||
|
#[error("named curve and private key type does not match")]
|
||||||
|
ErrNamedCurveAndPrivateKeyMismatch,
|
||||||
|
#[error("invalid server name format")]
|
||||||
|
ErrInvalidSniFormat,
|
||||||
|
#[error("invalid signature algorithm")]
|
||||||
|
ErrInvalidSignatureAlgorithm,
|
||||||
|
#[error("expected and actual key signature do not match")]
|
||||||
|
ErrKeySignatureMismatch,
|
||||||
|
#[error("Conn can not be created with a nil nextConn")]
|
||||||
|
ErrNilNextConn,
|
||||||
|
#[error("connection can not be created, no CipherSuites satisfy this Config")]
|
||||||
|
ErrNoAvailableCipherSuites,
|
||||||
|
#[error("connection can not be created, no SignatureScheme satisfy this Config")]
|
||||||
|
ErrNoAvailableSignatureSchemes,
|
||||||
|
#[error("no certificates configured")]
|
||||||
|
ErrNoCertificates,
|
||||||
|
#[error("no config provided")]
|
||||||
|
ErrNoConfigProvided,
|
||||||
|
#[error("client requested zero or more elliptic curves that are not supported by the server")]
|
||||||
|
ErrNoSupportedEllipticCurves,
|
||||||
|
#[error("unsupported protocol version")]
|
||||||
|
ErrUnsupportedProtocolVersion,
|
||||||
|
#[error("Certificate and PSK provided")]
|
||||||
|
ErrPskAndCertificate,
|
||||||
|
#[error("PSK and PSK Identity Hint must both be set for client")]
|
||||||
|
ErrPskAndIdentityMustBeSetForClient,
|
||||||
|
#[error("SRTP support was requested but server did not respond with use_srtp extension")]
|
||||||
|
ErrRequestedButNoSrtpExtension,
|
||||||
|
#[error("Certificate is mandatory for server")]
|
||||||
|
ErrServerMustHaveCertificate,
|
||||||
|
#[error("client requested SRTP but we have no matching profiles")]
|
||||||
|
ErrServerNoMatchingSrtpProfile,
|
||||||
|
#[error(
|
||||||
|
"server requires the Extended Master Secret extension, but the client does not support it"
|
||||||
|
)]
|
||||||
|
ErrServerRequiredButNoClientEms,
|
||||||
|
#[error("expected and actual verify data does not match")]
|
||||||
|
ErrVerifyDataMismatch,
|
||||||
|
#[error("handshake message unset, unable to marshal")]
|
||||||
|
ErrHandshakeMessageUnset,
|
||||||
|
#[error("invalid flight number")]
|
||||||
|
ErrInvalidFlight,
|
||||||
|
#[error("unable to generate key signature, unimplemented")]
|
||||||
|
ErrKeySignatureGenerateUnimplemented,
|
||||||
|
#[error("unable to verify key signature, unimplemented")]
|
||||||
|
ErrKeySignatureVerifyUnimplemented,
|
||||||
|
#[error("data length and declared length do not match")]
|
||||||
|
ErrLengthMismatch,
|
||||||
|
#[error("buffer not long enough to contain nonce")]
|
||||||
|
ErrNotEnoughRoomForNonce,
|
||||||
|
#[error("feature has not been implemented yet")]
|
||||||
|
ErrNotImplemented,
|
||||||
|
#[error("sequence number overflow")]
|
||||||
|
ErrSequenceNumberOverflow,
|
||||||
|
#[error("unable to marshal fragmented handshakes")]
|
||||||
|
ErrUnableToMarshalFragmented,
|
||||||
|
#[error("invalid state machine transition")]
|
||||||
|
ErrInvalidFsmTransition,
|
||||||
|
#[error("ApplicationData with epoch of 0")]
|
||||||
|
ErrApplicationDataEpochZero,
|
||||||
|
#[error("unhandled contentType")]
|
||||||
|
ErrUnhandledContextType,
|
||||||
|
#[error("context canceled")]
|
||||||
|
ErrContextCanceled,
|
||||||
|
#[error("empty fragment")]
|
||||||
|
ErrEmptyFragment,
|
||||||
|
#[error("Alert is Fatal or Close Notify")]
|
||||||
|
ErrAlertFatalOrClose,
|
||||||
|
|
||||||
|
#[error(
|
||||||
|
"Fragment buffer overflow. New size {new_size} is greater than specified max {max_size}"
|
||||||
|
)]
|
||||||
|
ErrFragmentBufferOverflow { new_size: usize, max_size: usize },
|
||||||
|
|
||||||
|
#[error("{0}")]
|
||||||
|
Io(#[source] IoError),
|
||||||
|
#[error("{0}")]
|
||||||
|
Util(#[from] util::Error),
|
||||||
|
#[error("utf8: {0}")]
|
||||||
|
Utf8(#[from] FromUtf8Error),
|
||||||
|
#[error("{0}")]
|
||||||
|
Sec1(#[source] sec1::Error),
|
||||||
|
#[error("{0}")]
|
||||||
|
Aes(#[from] aes::cipher::InvalidLength),
|
||||||
|
#[error("{0}")]
|
||||||
|
P256(#[source] P256Error),
|
||||||
|
#[error("{0}")]
|
||||||
|
RcGen(#[from] rcgen::Error),
|
||||||
|
#[error("mpsc send: {0}")]
|
||||||
|
MpscSend(String),
|
||||||
|
#[error("keying material: {0}")]
|
||||||
|
KeyingMaterial(#[from] KeyingMaterialExporterError),
|
||||||
|
|
||||||
|
/// Error parsing a given PEM string.
|
||||||
|
#[error("invalid PEM: {0}")]
|
||||||
|
InvalidPEM(String),
|
||||||
|
|
||||||
|
#[allow(non_camel_case_types)]
|
||||||
|
#[error("{0}")]
|
||||||
|
Other(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
#[error("io error: {0}")]
|
||||||
|
pub struct IoError(#[from] pub io::Error);
|
||||||
|
|
||||||
|
// Workaround for wanting PartialEq for io::Error.
|
||||||
|
impl PartialEq for IoError {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.0.kind() == other.0.kind()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<io::Error> for Error {
|
||||||
|
fn from(e: io::Error) -> Self {
|
||||||
|
Error::Io(IoError(e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<sec1::Error> for Error {
|
||||||
|
fn from(e: sec1::Error) -> Self {
|
||||||
|
Error::Sec1(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
#[error("{0}")]
|
||||||
|
pub struct P256Error(#[source] p256::elliptic_curve::Error);
|
||||||
|
|
||||||
|
impl PartialEq for P256Error {
|
||||||
|
fn eq(&self, _: &Self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<p256::elliptic_curve::Error> for Error {
|
||||||
|
fn from(e: p256::elliptic_curve::Error) -> Self {
|
||||||
|
Error::P256(P256Error(e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Because Tokio SendError is parameterized, we sadly lose the backtrace.
|
||||||
|
impl<T> From<MpscSendError<T>> for Error {
|
||||||
|
fn from(e: MpscSendError<T>) -> Self {
|
||||||
|
Error::MpscSend(e.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
56
libs/dtls/src/extension/extension_server_name.rs
Normal file
56
libs/dtls/src/extension/extension_server_name.rs
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod extension_server_name_test;
|
||||||
|
|
||||||
|
use std::io::{Read, Write};
|
||||||
|
|
||||||
|
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
const EXTENSION_SERVER_NAME_TYPE_DNSHOST_NAME: u8 = 0;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct ExtensionServerName {
|
||||||
|
pub(crate) server_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExtensionServerName {
|
||||||
|
pub fn extension_value(&self) -> ExtensionValue {
|
||||||
|
ExtensionValue::ServerName
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn size(&self) -> usize {
|
||||||
|
//TODO: check how to do cryptobyte?
|
||||||
|
2 + 2 + 1 + 2 + self.server_name.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn marshal<W: Write>(&self, writer: &mut W) -> Result<()> {
|
||||||
|
//TODO: check how to do cryptobyte?
|
||||||
|
writer.write_u16::<BigEndian>(2 + 1 + 2 + self.server_name.len() as u16)?;
|
||||||
|
writer.write_u16::<BigEndian>(1 + 2 + self.server_name.len() as u16)?;
|
||||||
|
writer.write_u8(EXTENSION_SERVER_NAME_TYPE_DNSHOST_NAME)?;
|
||||||
|
writer.write_u16::<BigEndian>(self.server_name.len() as u16)?;
|
||||||
|
writer.write_all(self.server_name.as_bytes())?;
|
||||||
|
|
||||||
|
Ok(writer.flush()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unmarshal<R: Read>(reader: &mut R) -> Result<Self> {
|
||||||
|
//TODO: check how to do cryptobyte?
|
||||||
|
let _ = reader.read_u16::<BigEndian>()? as usize;
|
||||||
|
let _ = reader.read_u16::<BigEndian>()? as usize;
|
||||||
|
|
||||||
|
let name_type = reader.read_u8()?;
|
||||||
|
if name_type != EXTENSION_SERVER_NAME_TYPE_DNSHOST_NAME {
|
||||||
|
return Err(Error::ErrInvalidSniFormat);
|
||||||
|
}
|
||||||
|
|
||||||
|
let buf_len = reader.read_u16::<BigEndian>()? as usize;
|
||||||
|
let mut buf: Vec<u8> = vec![0u8; buf_len];
|
||||||
|
reader.read_exact(&mut buf)?;
|
||||||
|
|
||||||
|
let server_name = String::from_utf8(buf)?;
|
||||||
|
|
||||||
|
Ok(ExtensionServerName { server_name })
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
use std::io::{BufReader, BufWriter};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_extension_server_name() -> Result<()> {
|
||||||
|
let extension = ExtensionServerName {
|
||||||
|
server_name: "test.domain".to_owned(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut raw = vec![];
|
||||||
|
{
|
||||||
|
let mut writer = BufWriter::<&mut Vec<u8>>::new(raw.as_mut());
|
||||||
|
extension.marshal(&mut writer)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut reader = BufReader::new(raw.as_slice());
|
||||||
|
let new_extension = ExtensionServerName::unmarshal(&mut reader)?;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
new_extension, extension,
|
||||||
|
"extensionServerName marshal: got {new_extension:?} expected {extension:?}",
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod extension_supported_elliptic_curves_test;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::curve::named_curve::*;
|
||||||
|
|
||||||
|
const EXTENSION_SUPPORTED_GROUPS_HEADER_SIZE: usize = 6;
|
||||||
|
|
||||||
|
/// ## Specifications
|
||||||
|
///
|
||||||
|
/// * [RFC 8422 §5.1.1]
|
||||||
|
///
|
||||||
|
/// [RFC 8422 §5.1.1]: https://tools.ietf.org/html/rfc8422#section-5.1.1
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct ExtensionSupportedEllipticCurves {
|
||||||
|
pub elliptic_curves: Vec<NamedCurve>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExtensionSupportedEllipticCurves {
|
||||||
|
pub fn extension_value(&self) -> ExtensionValue {
|
||||||
|
ExtensionValue::SupportedEllipticCurves
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn size(&self) -> usize {
|
||||||
|
2 + 2 + self.elliptic_curves.len() * 2
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn marshal<W: Write>(&self, writer: &mut W) -> Result<()> {
|
||||||
|
writer.write_u16::<BigEndian>(2 + 2 * self.elliptic_curves.len() as u16)?;
|
||||||
|
writer.write_u16::<BigEndian>(2 * self.elliptic_curves.len() as u16)?;
|
||||||
|
for v in &self.elliptic_curves {
|
||||||
|
writer.write_u16::<BigEndian>(*v as u16)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(writer.flush()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unmarshal<R: Read>(reader: &mut R) -> Result<Self> {
|
||||||
|
let _ = reader.read_u16::<BigEndian>()?;
|
||||||
|
|
||||||
|
let group_count = reader.read_u16::<BigEndian>()? as usize / 2;
|
||||||
|
let mut elliptic_curves = vec![];
|
||||||
|
for _ in 0..group_count {
|
||||||
|
let elliptic_curve = reader.read_u16::<BigEndian>()?.into();
|
||||||
|
elliptic_curves.push(elliptic_curve);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(ExtensionSupportedEllipticCurves { elliptic_curves })
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
use std::io::{BufReader, BufWriter};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_extension_supported_groups() -> Result<()> {
|
||||||
|
let raw_supported_groups = vec![0x0, 0x4, 0x0, 0x2, 0x0, 0x1d]; // 0x0, 0xa,
|
||||||
|
let parsed_supported_groups = ExtensionSupportedEllipticCurves {
|
||||||
|
elliptic_curves: vec![NamedCurve::X25519],
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut raw = vec![];
|
||||||
|
{
|
||||||
|
let mut writer = BufWriter::<&mut Vec<u8>>::new(raw.as_mut());
|
||||||
|
parsed_supported_groups.marshal(&mut writer)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
raw, raw_supported_groups,
|
||||||
|
"extensionSupportedGroups marshal: got {raw:?}, want {raw_supported_groups:?}"
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut reader = BufReader::new(raw.as_slice());
|
||||||
|
let new_supported_groups = ExtensionSupportedEllipticCurves::unmarshal(&mut reader)?;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
new_supported_groups, parsed_supported_groups,
|
||||||
|
"extensionSupportedGroups unmarshal: got {new_supported_groups:?}, want {parsed_supported_groups:?}"
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
53
libs/dtls/src/extension/extension_supported_point_formats.rs
Normal file
53
libs/dtls/src/extension/extension_supported_point_formats.rs
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod extension_supported_point_formats_test;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
const EXTENSION_SUPPORTED_POINT_FORMATS_SIZE: usize = 5;
|
||||||
|
|
||||||
|
pub type EllipticCurvePointFormat = u8;
|
||||||
|
|
||||||
|
pub const ELLIPTIC_CURVE_POINT_FORMAT_UNCOMPRESSED: EllipticCurvePointFormat = 0;
|
||||||
|
|
||||||
|
/// ## Specifications
|
||||||
|
///
|
||||||
|
/// * [RFC 4492 §5.1.2]
|
||||||
|
///
|
||||||
|
/// [RFC 4492 §5.1.2]: https://tools.ietf.org/html/rfc4492#section-5.1.2
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct ExtensionSupportedPointFormats {
|
||||||
|
pub(crate) point_formats: Vec<EllipticCurvePointFormat>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExtensionSupportedPointFormats {
|
||||||
|
pub fn extension_value(&self) -> ExtensionValue {
|
||||||
|
ExtensionValue::SupportedPointFormats
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn size(&self) -> usize {
|
||||||
|
2 + 1 + self.point_formats.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn marshal<W: Write>(&self, writer: &mut W) -> Result<()> {
|
||||||
|
writer.write_u16::<BigEndian>(1 + self.point_formats.len() as u16)?;
|
||||||
|
writer.write_u8(self.point_formats.len() as u8)?;
|
||||||
|
for v in &self.point_formats {
|
||||||
|
writer.write_u8(*v)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(writer.flush()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unmarshal<R: Read>(reader: &mut R) -> Result<Self> {
|
||||||
|
let _ = reader.read_u16::<BigEndian>()?;
|
||||||
|
|
||||||
|
let point_format_count = reader.read_u8()? as usize;
|
||||||
|
let mut point_formats = vec![];
|
||||||
|
for _ in 0..point_format_count {
|
||||||
|
let point_format = reader.read_u8()?;
|
||||||
|
point_formats.push(point_format);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(ExtensionSupportedPointFormats { point_formats })
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
use std::io::{BufReader, BufWriter};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_extension_supported_point_formats() -> Result<()> {
|
||||||
|
let raw_extension_supported_point_formats = vec![0x00, 0x02, 0x01, 0x00]; // 0x00, 0x0b,
|
||||||
|
let parsed_extension_supported_point_formats = ExtensionSupportedPointFormats {
|
||||||
|
point_formats: vec![ELLIPTIC_CURVE_POINT_FORMAT_UNCOMPRESSED],
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut raw = vec![];
|
||||||
|
{
|
||||||
|
let mut writer = BufWriter::<&mut Vec<u8>>::new(raw.as_mut());
|
||||||
|
parsed_extension_supported_point_formats.marshal(&mut writer)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
raw, raw_extension_supported_point_formats,
|
||||||
|
"extensionSupportedPointFormats marshal: got {raw:?}, want {raw_extension_supported_point_formats:?}"
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut reader = BufReader::new(raw.as_slice());
|
||||||
|
let new_extension_supported_point_formats =
|
||||||
|
ExtensionSupportedPointFormats::unmarshal(&mut reader)?;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
new_extension_supported_point_formats, parsed_extension_supported_point_formats,
|
||||||
|
"extensionSupportedPointFormats unmarshal: got {new_extension_supported_point_formats:?}, want {parsed_extension_supported_point_formats:?}"
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod extension_supported_signature_algorithms_test;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::signature_hash_algorithm::*;
|
||||||
|
|
||||||
|
const EXTENSION_SUPPORTED_SIGNATURE_ALGORITHMS_HEADER_SIZE: usize = 6;
|
||||||
|
|
||||||
|
/// ## Specifications
|
||||||
|
///
|
||||||
|
/// * [RFC 5246 §7.4.1.4.1]
|
||||||
|
///
|
||||||
|
/// [RFC 5246 §7.4.1.4.1]: https://tools.ietf.org/html/rfc5246#section-7.4.1.4.1
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct ExtensionSupportedSignatureAlgorithms {
|
||||||
|
pub(crate) signature_hash_algorithms: Vec<SignatureHashAlgorithm>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExtensionSupportedSignatureAlgorithms {
|
||||||
|
pub fn extension_value(&self) -> ExtensionValue {
|
||||||
|
ExtensionValue::SupportedSignatureAlgorithms
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn size(&self) -> usize {
|
||||||
|
2 + 2 + self.signature_hash_algorithms.len() * 2
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn marshal<W: Write>(&self, writer: &mut W) -> Result<()> {
|
||||||
|
writer.write_u16::<BigEndian>(2 + 2 * self.signature_hash_algorithms.len() as u16)?;
|
||||||
|
writer.write_u16::<BigEndian>(2 * self.signature_hash_algorithms.len() as u16)?;
|
||||||
|
for v in &self.signature_hash_algorithms {
|
||||||
|
writer.write_u8(v.hash as u8)?;
|
||||||
|
writer.write_u8(v.signature as u8)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(writer.flush()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unmarshal<R: Read>(reader: &mut R) -> Result<Self> {
|
||||||
|
let _ = reader.read_u16::<BigEndian>()?;
|
||||||
|
|
||||||
|
let algorithm_count = reader.read_u16::<BigEndian>()? as usize / 2;
|
||||||
|
let mut signature_hash_algorithms = vec![];
|
||||||
|
for _ in 0..algorithm_count {
|
||||||
|
let hash = reader.read_u8()?.into();
|
||||||
|
let signature = reader.read_u8()?.into();
|
||||||
|
signature_hash_algorithms.push(SignatureHashAlgorithm { hash, signature });
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(ExtensionSupportedSignatureAlgorithms {
|
||||||
|
signature_hash_algorithms,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
use std::io::{BufReader, BufWriter};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_extension_supported_signature_algorithms() -> Result<()> {
|
||||||
|
let raw_extension_supported_signature_algorithms =
|
||||||
|
vec![0x00, 0x08, 0x00, 0x06, 0x04, 0x03, 0x05, 0x03, 0x06, 0x03]; //0x00, 0x0d,
|
||||||
|
let parsed_extension_supported_signature_algorithms = ExtensionSupportedSignatureAlgorithms {
|
||||||
|
signature_hash_algorithms: vec![
|
||||||
|
SignatureHashAlgorithm {
|
||||||
|
hash: HashAlgorithm::Sha256,
|
||||||
|
signature: SignatureAlgorithm::Ecdsa,
|
||||||
|
},
|
||||||
|
SignatureHashAlgorithm {
|
||||||
|
hash: HashAlgorithm::Sha384,
|
||||||
|
signature: SignatureAlgorithm::Ecdsa,
|
||||||
|
},
|
||||||
|
SignatureHashAlgorithm {
|
||||||
|
hash: HashAlgorithm::Sha512,
|
||||||
|
signature: SignatureAlgorithm::Ecdsa,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut raw = vec![];
|
||||||
|
{
|
||||||
|
let mut writer = BufWriter::<&mut Vec<u8>>::new(raw.as_mut());
|
||||||
|
parsed_extension_supported_signature_algorithms.marshal(&mut writer)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
raw, raw_extension_supported_signature_algorithms,
|
||||||
|
"extensionSupportedSignatureAlgorithms marshal: got {raw:?}, want {raw_extension_supported_signature_algorithms:?}"
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut reader = BufReader::new(raw.as_slice());
|
||||||
|
let new_extension_supported_signature_algorithms =
|
||||||
|
ExtensionSupportedSignatureAlgorithms::unmarshal(&mut reader)?;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
new_extension_supported_signature_algorithms,
|
||||||
|
parsed_extension_supported_signature_algorithms,
|
||||||
|
"extensionSupportedSignatureAlgorithms unmarshal: got {new_extension_supported_signature_algorithms:?}, want {parsed_extension_supported_signature_algorithms:?}"
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod extension_use_extended_master_secret_test;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
const EXTENSION_USE_EXTENDED_MASTER_SECRET_HEADER_SIZE: usize = 4;
|
||||||
|
|
||||||
|
/// ## Specifications
|
||||||
|
///
|
||||||
|
/// * [RFC 8422]
|
||||||
|
///
|
||||||
|
/// [RFC 8422]: https://tools.ietf.org/html/rfc8422
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct ExtensionUseExtendedMasterSecret {
|
||||||
|
pub(crate) supported: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExtensionUseExtendedMasterSecret {
|
||||||
|
pub fn extension_value(&self) -> ExtensionValue {
|
||||||
|
ExtensionValue::UseExtendedMasterSecret
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn size(&self) -> usize {
|
||||||
|
2
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn marshal<W: Write>(&self, writer: &mut W) -> Result<()> {
|
||||||
|
// length
|
||||||
|
writer.write_u16::<BigEndian>(0)?;
|
||||||
|
|
||||||
|
Ok(writer.flush()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unmarshal<R: Read>(reader: &mut R) -> Result<Self> {
|
||||||
|
let _ = reader.read_u16::<BigEndian>()?;
|
||||||
|
|
||||||
|
Ok(ExtensionUseExtendedMasterSecret { supported: true })
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
use std::io::{BufReader, BufWriter};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_extension_use_extended_master_secret() -> Result<()> {
|
||||||
|
let raw_extension_use_extended_master_secret = vec![0x00, 0x00];
|
||||||
|
let parsed_extension_use_extended_master_secret =
|
||||||
|
ExtensionUseExtendedMasterSecret { supported: true };
|
||||||
|
|
||||||
|
let mut raw = vec![];
|
||||||
|
{
|
||||||
|
let mut writer = BufWriter::<&mut Vec<u8>>::new(raw.as_mut());
|
||||||
|
parsed_extension_use_extended_master_secret.marshal(&mut writer)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
raw, raw_extension_use_extended_master_secret,
|
||||||
|
"extension_use_extended_master_secret marshal: got {raw:?}, want {raw_extension_use_extended_master_secret:?}"
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut reader = BufReader::new(raw.as_slice());
|
||||||
|
let new_extension_use_extended_master_secret =
|
||||||
|
ExtensionUseExtendedMasterSecret::unmarshal(&mut reader)?;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
new_extension_use_extended_master_secret, parsed_extension_use_extended_master_secret,
|
||||||
|
"extension_use_extended_master_secret unmarshal: got {new_extension_use_extended_master_secret:?}, want {parsed_extension_use_extended_master_secret:?}"
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
88
libs/dtls/src/extension/extension_use_srtp.rs
Normal file
88
libs/dtls/src/extension/extension_use_srtp.rs
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod extension_use_srtp_test;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
// SRTPProtectionProfile defines the parameters and options that are in effect for the SRTP processing
|
||||||
|
/// ## Specifications
|
||||||
|
///
|
||||||
|
/// * [RFC 5764 §4.1.2]
|
||||||
|
///
|
||||||
|
/// [RFC 5764 §4.1.2]: https://tools.ietf.org/html/rfc5764#section-4.1.2
|
||||||
|
#[allow(non_camel_case_types)]
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub enum SrtpProtectionProfile {
|
||||||
|
Srtp_Aes128_Cm_Hmac_Sha1_80 = 0x0001,
|
||||||
|
Srtp_Aes128_Cm_Hmac_Sha1_32 = 0x0002,
|
||||||
|
Srtp_Aead_Aes_128_Gcm = 0x0007,
|
||||||
|
Srtp_Aead_Aes_256_Gcm = 0x0008,
|
||||||
|
Unsupported,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u16> for SrtpProtectionProfile {
|
||||||
|
fn from(val: u16) -> Self {
|
||||||
|
match val {
|
||||||
|
0x0001 => SrtpProtectionProfile::Srtp_Aes128_Cm_Hmac_Sha1_80,
|
||||||
|
0x0002 => SrtpProtectionProfile::Srtp_Aes128_Cm_Hmac_Sha1_32,
|
||||||
|
0x0007 => SrtpProtectionProfile::Srtp_Aead_Aes_128_Gcm,
|
||||||
|
0x0008 => SrtpProtectionProfile::Srtp_Aead_Aes_256_Gcm,
|
||||||
|
_ => SrtpProtectionProfile::Unsupported,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const EXTENSION_USE_SRTPHEADER_SIZE: usize = 6;
|
||||||
|
|
||||||
|
/// ## Specifications
|
||||||
|
///
|
||||||
|
/// * [RFC 8422]
|
||||||
|
///
|
||||||
|
/// [RFC 8422]: https://tools.ietf.org/html/rfc8422
|
||||||
|
#[allow(non_camel_case_types)]
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct ExtensionUseSrtp {
|
||||||
|
pub(crate) protection_profiles: Vec<SrtpProtectionProfile>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExtensionUseSrtp {
|
||||||
|
pub fn extension_value(&self) -> ExtensionValue {
|
||||||
|
ExtensionValue::UseSrtp
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn size(&self) -> usize {
|
||||||
|
2 + 2 + self.protection_profiles.len() * 2 + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn marshal<W: Write>(&self, writer: &mut W) -> Result<()> {
|
||||||
|
writer.write_u16::<BigEndian>(
|
||||||
|
2 + /* MKI Length */ 1 + 2 * self.protection_profiles.len() as u16,
|
||||||
|
)?;
|
||||||
|
writer.write_u16::<BigEndian>(2 * self.protection_profiles.len() as u16)?;
|
||||||
|
for v in &self.protection_profiles {
|
||||||
|
writer.write_u16::<BigEndian>(*v as u16)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* MKI Length */
|
||||||
|
writer.write_u8(0x00)?;
|
||||||
|
|
||||||
|
Ok(writer.flush()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unmarshal<R: Read>(reader: &mut R) -> Result<Self> {
|
||||||
|
let _ = reader.read_u16::<BigEndian>()?;
|
||||||
|
|
||||||
|
let profile_count = reader.read_u16::<BigEndian>()? as usize / 2;
|
||||||
|
let mut protection_profiles = vec![];
|
||||||
|
for _ in 0..profile_count {
|
||||||
|
let protection_profile = reader.read_u16::<BigEndian>()?.into();
|
||||||
|
protection_profiles.push(protection_profile);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* MKI Length */
|
||||||
|
let _ = reader.read_u8()?;
|
||||||
|
|
||||||
|
Ok(ExtensionUseSrtp {
|
||||||
|
protection_profiles,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
use std::io::{BufReader, BufWriter};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_extension_use_srtp() -> Result<()> {
|
||||||
|
let raw_use_srtp = vec![0x00, 0x05, 0x00, 0x02, 0x00, 0x01, 0x00]; //0x00, 0x0e,
|
||||||
|
let parsed_use_srtp = ExtensionUseSrtp {
|
||||||
|
protection_profiles: vec![SrtpProtectionProfile::Srtp_Aes128_Cm_Hmac_Sha1_80],
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut raw = vec![];
|
||||||
|
{
|
||||||
|
let mut writer = BufWriter::<&mut Vec<u8>>::new(raw.as_mut());
|
||||||
|
parsed_use_srtp.marshal(&mut writer)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
raw, raw_use_srtp,
|
||||||
|
"extensionUseSRTP marshal: got {raw:?}, want {raw_use_srtp:?}"
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut reader = BufReader::new(raw.as_slice());
|
||||||
|
let new_use_srtp = ExtensionUseSrtp::unmarshal(&mut reader)?;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
new_use_srtp, parsed_use_srtp,
|
||||||
|
"extensionUseSRTP unmarshal: got {new_use_srtp:?}, want {parsed_use_srtp:?}"
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
130
libs/dtls/src/extension/mod.rs
Normal file
130
libs/dtls/src/extension/mod.rs
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
pub mod extension_server_name;
|
||||||
|
pub mod extension_supported_elliptic_curves;
|
||||||
|
pub mod extension_supported_point_formats;
|
||||||
|
pub mod extension_supported_signature_algorithms;
|
||||||
|
pub mod extension_use_extended_master_secret;
|
||||||
|
pub mod extension_use_srtp;
|
||||||
|
pub mod renegotiation_info;
|
||||||
|
|
||||||
|
use std::io::{Read, Write};
|
||||||
|
|
||||||
|
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||||
|
use extension_server_name::*;
|
||||||
|
use extension_supported_elliptic_curves::*;
|
||||||
|
use extension_supported_point_formats::*;
|
||||||
|
use extension_supported_signature_algorithms::*;
|
||||||
|
use extension_use_extended_master_secret::*;
|
||||||
|
use extension_use_srtp::*;
|
||||||
|
|
||||||
|
use crate::error::*;
|
||||||
|
use crate::extension::renegotiation_info::ExtensionRenegotiationInfo;
|
||||||
|
|
||||||
|
// https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub enum ExtensionValue {
|
||||||
|
ServerName = 0,
|
||||||
|
SupportedEllipticCurves = 10,
|
||||||
|
SupportedPointFormats = 11,
|
||||||
|
SupportedSignatureAlgorithms = 13,
|
||||||
|
UseSrtp = 14,
|
||||||
|
UseExtendedMasterSecret = 23,
|
||||||
|
RenegotiationInfo = 65281,
|
||||||
|
Unsupported,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u16> for ExtensionValue {
|
||||||
|
fn from(val: u16) -> Self {
|
||||||
|
match val {
|
||||||
|
0 => ExtensionValue::ServerName,
|
||||||
|
10 => ExtensionValue::SupportedEllipticCurves,
|
||||||
|
11 => ExtensionValue::SupportedPointFormats,
|
||||||
|
13 => ExtensionValue::SupportedSignatureAlgorithms,
|
||||||
|
14 => ExtensionValue::UseSrtp,
|
||||||
|
23 => ExtensionValue::UseExtendedMasterSecret,
|
||||||
|
65281 => ExtensionValue::RenegotiationInfo,
|
||||||
|
_ => ExtensionValue::Unsupported,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Debug, Clone)]
|
||||||
|
pub enum Extension {
|
||||||
|
ServerName(ExtensionServerName),
|
||||||
|
SupportedEllipticCurves(ExtensionSupportedEllipticCurves),
|
||||||
|
SupportedPointFormats(ExtensionSupportedPointFormats),
|
||||||
|
SupportedSignatureAlgorithms(ExtensionSupportedSignatureAlgorithms),
|
||||||
|
UseSrtp(ExtensionUseSrtp),
|
||||||
|
UseExtendedMasterSecret(ExtensionUseExtendedMasterSecret),
|
||||||
|
RenegotiationInfo(ExtensionRenegotiationInfo),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Extension {
|
||||||
|
pub fn extension_value(&self) -> ExtensionValue {
|
||||||
|
match self {
|
||||||
|
Extension::ServerName(ext) => ext.extension_value(),
|
||||||
|
Extension::SupportedEllipticCurves(ext) => ext.extension_value(),
|
||||||
|
Extension::SupportedPointFormats(ext) => ext.extension_value(),
|
||||||
|
Extension::SupportedSignatureAlgorithms(ext) => ext.extension_value(),
|
||||||
|
Extension::UseSrtp(ext) => ext.extension_value(),
|
||||||
|
Extension::UseExtendedMasterSecret(ext) => ext.extension_value(),
|
||||||
|
Extension::RenegotiationInfo(ext) => ext.extension_value(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn size(&self) -> usize {
|
||||||
|
let mut len = 2;
|
||||||
|
|
||||||
|
len += match self {
|
||||||
|
Extension::ServerName(ext) => ext.size(),
|
||||||
|
Extension::SupportedEllipticCurves(ext) => ext.size(),
|
||||||
|
Extension::SupportedPointFormats(ext) => ext.size(),
|
||||||
|
Extension::SupportedSignatureAlgorithms(ext) => ext.size(),
|
||||||
|
Extension::UseSrtp(ext) => ext.size(),
|
||||||
|
Extension::UseExtendedMasterSecret(ext) => ext.size(),
|
||||||
|
Extension::RenegotiationInfo(ext) => ext.size(),
|
||||||
|
};
|
||||||
|
|
||||||
|
len
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn marshal<W: Write>(&self, writer: &mut W) -> Result<()> {
|
||||||
|
writer.write_u16::<BigEndian>(self.extension_value() as u16)?;
|
||||||
|
match self {
|
||||||
|
Extension::ServerName(ext) => ext.marshal(writer),
|
||||||
|
Extension::SupportedEllipticCurves(ext) => ext.marshal(writer),
|
||||||
|
Extension::SupportedPointFormats(ext) => ext.marshal(writer),
|
||||||
|
Extension::SupportedSignatureAlgorithms(ext) => ext.marshal(writer),
|
||||||
|
Extension::UseSrtp(ext) => ext.marshal(writer),
|
||||||
|
Extension::UseExtendedMasterSecret(ext) => ext.marshal(writer),
|
||||||
|
Extension::RenegotiationInfo(ext) => ext.marshal(writer),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unmarshal<R: Read>(reader: &mut R) -> Result<Self> {
|
||||||
|
let extension_value: ExtensionValue = reader.read_u16::<BigEndian>()?.into();
|
||||||
|
match extension_value {
|
||||||
|
ExtensionValue::ServerName => Ok(Extension::ServerName(
|
||||||
|
ExtensionServerName::unmarshal(reader)?,
|
||||||
|
)),
|
||||||
|
ExtensionValue::SupportedEllipticCurves => Ok(Extension::SupportedEllipticCurves(
|
||||||
|
ExtensionSupportedEllipticCurves::unmarshal(reader)?,
|
||||||
|
)),
|
||||||
|
ExtensionValue::SupportedPointFormats => Ok(Extension::SupportedPointFormats(
|
||||||
|
ExtensionSupportedPointFormats::unmarshal(reader)?,
|
||||||
|
)),
|
||||||
|
ExtensionValue::SupportedSignatureAlgorithms => {
|
||||||
|
Ok(Extension::SupportedSignatureAlgorithms(
|
||||||
|
ExtensionSupportedSignatureAlgorithms::unmarshal(reader)?,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
ExtensionValue::UseSrtp => Ok(Extension::UseSrtp(ExtensionUseSrtp::unmarshal(reader)?)),
|
||||||
|
ExtensionValue::UseExtendedMasterSecret => Ok(Extension::UseExtendedMasterSecret(
|
||||||
|
ExtensionUseExtendedMasterSecret::unmarshal(reader)?,
|
||||||
|
)),
|
||||||
|
ExtensionValue::RenegotiationInfo => Ok(Extension::RenegotiationInfo(
|
||||||
|
ExtensionRenegotiationInfo::unmarshal(reader)?,
|
||||||
|
)),
|
||||||
|
_ => Err(Error::ErrInvalidExtensionType),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
53
libs/dtls/src/extension/renegotiation_info.rs
Normal file
53
libs/dtls/src/extension/renegotiation_info.rs
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod renegotiation_info_test;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::error::Error::ErrInvalidPacketLength;
|
||||||
|
|
||||||
|
const RENEGOTIATION_INFO_HEADER_SIZE: usize = 5;
|
||||||
|
|
||||||
|
/// RenegotiationInfo allows a Client/Server to
|
||||||
|
/// communicate their renegotiation support
|
||||||
|
///
|
||||||
|
/// ## Specifications
|
||||||
|
///
|
||||||
|
/// * [RFC 5746]
|
||||||
|
///
|
||||||
|
/// [RFC 5746]: https://tools.ietf.org/html/rfc5746
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct ExtensionRenegotiationInfo {
|
||||||
|
pub(crate) renegotiated_connection: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExtensionRenegotiationInfo {
|
||||||
|
// TypeValue returns the extension TypeValue
|
||||||
|
pub fn extension_value(&self) -> ExtensionValue {
|
||||||
|
ExtensionValue::RenegotiationInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn size(&self) -> usize {
|
||||||
|
3
|
||||||
|
}
|
||||||
|
|
||||||
|
/// marshal encodes the extension
|
||||||
|
pub fn marshal<W: Write>(&self, writer: &mut W) -> Result<()> {
|
||||||
|
writer.write_u16::<BigEndian>(1)?; //length
|
||||||
|
writer.write_u8(self.renegotiated_connection)?;
|
||||||
|
|
||||||
|
Ok(writer.flush()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unmarshal populates the extension from encoded data
|
||||||
|
pub fn unmarshal<R: Read>(reader: &mut R) -> Result<Self> {
|
||||||
|
let l = reader.read_u16::<BigEndian>()?; //length
|
||||||
|
if l != 1 {
|
||||||
|
return Err(ErrInvalidPacketLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
let renegotiated_connection = reader.read_u8()?;
|
||||||
|
|
||||||
|
Ok(ExtensionRenegotiationInfo {
|
||||||
|
renegotiated_connection,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
use std::io::{BufReader, BufWriter};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_renegotiation_info() -> Result<()> {
|
||||||
|
let extension = ExtensionRenegotiationInfo {
|
||||||
|
renegotiated_connection: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut raw = vec![];
|
||||||
|
{
|
||||||
|
let mut writer = BufWriter::<&mut Vec<u8>>::new(raw.as_mut());
|
||||||
|
extension.marshal(&mut writer)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut reader = BufReader::new(raw.as_slice());
|
||||||
|
let new_extension = ExtensionRenegotiationInfo::unmarshal(&mut reader)?;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
new_extension.renegotiated_connection,
|
||||||
|
extension.renegotiated_connection
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
209
libs/dtls/src/flight/flight0.rs
Normal file
209
libs/dtls/src/flight/flight0.rs
Normal file
@@ -0,0 +1,209 @@
|
|||||||
|
use std::fmt;
|
||||||
|
use std::sync::atomic::Ordering;
|
||||||
|
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use rand::Rng;
|
||||||
|
|
||||||
|
use super::flight2::*;
|
||||||
|
use super::*;
|
||||||
|
use crate::config::*;
|
||||||
|
use crate::conn::*;
|
||||||
|
use crate::curve::named_curve::NamedCurve;
|
||||||
|
use crate::error::Error;
|
||||||
|
use crate::extension::*;
|
||||||
|
use crate::handshake::*;
|
||||||
|
use crate::record_layer::record_layer_header::*;
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub(crate) struct Flight0;
|
||||||
|
|
||||||
|
impl fmt::Display for Flight0 {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "Flight 0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Flight for Flight0 {
|
||||||
|
async fn parse(
|
||||||
|
&self,
|
||||||
|
_tx: &mut mpsc::Sender<mpsc::Sender<()>>,
|
||||||
|
state: &mut State,
|
||||||
|
cache: &HandshakeCache,
|
||||||
|
cfg: &HandshakeConfig,
|
||||||
|
) -> Result<Box<dyn Flight + Send + Sync>, (Option<Alert>, Option<Error>)> {
|
||||||
|
let (seq, msgs) = match cache
|
||||||
|
.full_pull_map(
|
||||||
|
0,
|
||||||
|
&[HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::ClientHello,
|
||||||
|
epoch: cfg.initial_epoch,
|
||||||
|
is_client: true,
|
||||||
|
optional: false,
|
||||||
|
}],
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok((seq, msgs)) => (seq, msgs),
|
||||||
|
Err(_) => return Err((None, None)),
|
||||||
|
};
|
||||||
|
|
||||||
|
state.handshake_recv_sequence = seq;
|
||||||
|
|
||||||
|
if let Some(message) = msgs.get(&HandshakeType::ClientHello) {
|
||||||
|
// Validate type
|
||||||
|
let client_hello = match message {
|
||||||
|
HandshakeMessage::ClientHello(client_hello) => client_hello,
|
||||||
|
_ => {
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::InternalError,
|
||||||
|
}),
|
||||||
|
None,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if client_hello.version != PROTOCOL_VERSION1_2 {
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::ProtocolVersion,
|
||||||
|
}),
|
||||||
|
Some(Error::ErrUnsupportedProtocolVersion),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
state.remote_random = client_hello.random.clone();
|
||||||
|
|
||||||
|
if let Ok(id) =
|
||||||
|
find_matching_cipher_suite(&client_hello.cipher_suites, &cfg.local_cipher_suites)
|
||||||
|
{
|
||||||
|
if let Ok(cipher_suite) = cipher_suite_for_id(id) {
|
||||||
|
log::debug!(
|
||||||
|
"[handshake:{}] use cipher suite: {}",
|
||||||
|
srv_cli_str(state.is_client),
|
||||||
|
cipher_suite.to_string()
|
||||||
|
);
|
||||||
|
let mut cs = state.cipher_suite.lock().await;
|
||||||
|
*cs = Some(cipher_suite);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::InsufficientSecurity,
|
||||||
|
}),
|
||||||
|
Some(Error::ErrCipherSuiteNoIntersection),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
for extension in &client_hello.extensions {
|
||||||
|
match extension {
|
||||||
|
Extension::SupportedEllipticCurves(e) => {
|
||||||
|
if e.elliptic_curves.is_empty() {
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::InsufficientSecurity,
|
||||||
|
}),
|
||||||
|
Some(Error::ErrNoSupportedEllipticCurves),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
for curve in e.elliptic_curves.iter() {
|
||||||
|
if curve != &NamedCurve::Unsupported {
|
||||||
|
state.named_curve = *curve;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Extension::UseSrtp(e) => {
|
||||||
|
if let Ok(profile) = find_matching_srtp_profile(
|
||||||
|
&e.protection_profiles,
|
||||||
|
&cfg.local_srtp_protection_profiles,
|
||||||
|
) {
|
||||||
|
state.srtp_protection_profile = profile;
|
||||||
|
} else {
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::InsufficientSecurity,
|
||||||
|
}),
|
||||||
|
Some(Error::ErrServerNoMatchingSrtpProfile),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Extension::UseExtendedMasterSecret(_) => {
|
||||||
|
if cfg.extended_master_secret != ExtendedMasterSecretType::Disable {
|
||||||
|
state.extended_master_secret = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Extension::ServerName(e) => {
|
||||||
|
state.server_name.clone_from(&e.server_name); // remote server name
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.extended_master_secret == ExtendedMasterSecretType::Require
|
||||||
|
&& !state.extended_master_secret
|
||||||
|
{
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::InsufficientSecurity,
|
||||||
|
}),
|
||||||
|
Some(Error::ErrServerRequiredButNoClientEms),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if state.local_keypair.is_none() {
|
||||||
|
state.local_keypair = match state.named_curve.generate_keypair() {
|
||||||
|
Ok(local_keypar) => Some(local_keypar),
|
||||||
|
Err(err) => {
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::IllegalParameter,
|
||||||
|
}),
|
||||||
|
Some(err),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Box::new(Flight2 {}))
|
||||||
|
} else {
|
||||||
|
Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::InternalError,
|
||||||
|
}),
|
||||||
|
None,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn generate(
|
||||||
|
&self,
|
||||||
|
state: &mut State,
|
||||||
|
_cache: &HandshakeCache,
|
||||||
|
_cfg: &HandshakeConfig,
|
||||||
|
) -> Result<Vec<Packet>, (Option<Alert>, Option<Error>)> {
|
||||||
|
// Initialize
|
||||||
|
state.cookie = vec![0; COOKIE_LENGTH];
|
||||||
|
rand::rng().fill(state.cookie.as_mut_slice());
|
||||||
|
|
||||||
|
//TODO: figure out difference between golang's atom store and rust atom store
|
||||||
|
let zero_epoch = 0;
|
||||||
|
state.local_epoch.store(zero_epoch, Ordering::SeqCst);
|
||||||
|
state.remote_epoch.store(zero_epoch, Ordering::SeqCst);
|
||||||
|
|
||||||
|
state.named_curve = DEFAULT_NAMED_CURVE;
|
||||||
|
state.local_random.populate();
|
||||||
|
|
||||||
|
Ok(vec![])
|
||||||
|
}
|
||||||
|
}
|
||||||
193
libs/dtls/src/flight/flight1.rs
Normal file
193
libs/dtls/src/flight/flight1.rs
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
use std::fmt;
|
||||||
|
use std::sync::atomic::Ordering;
|
||||||
|
|
||||||
|
use async_trait::async_trait;
|
||||||
|
|
||||||
|
use super::flight3::*;
|
||||||
|
use super::*;
|
||||||
|
use crate::compression_methods::*;
|
||||||
|
use crate::config::*;
|
||||||
|
use crate::conn::*;
|
||||||
|
use crate::content::*;
|
||||||
|
use crate::curve::named_curve::*;
|
||||||
|
use crate::error::Error;
|
||||||
|
use crate::extension::extension_server_name::*;
|
||||||
|
use crate::extension::extension_supported_elliptic_curves::*;
|
||||||
|
use crate::extension::extension_supported_point_formats::*;
|
||||||
|
use crate::extension::extension_supported_signature_algorithms::*;
|
||||||
|
use crate::extension::extension_use_extended_master_secret::*;
|
||||||
|
use crate::extension::extension_use_srtp::*;
|
||||||
|
use crate::extension::renegotiation_info::ExtensionRenegotiationInfo;
|
||||||
|
use crate::extension::*;
|
||||||
|
use crate::handshake::handshake_message_client_hello::*;
|
||||||
|
use crate::handshake::*;
|
||||||
|
use crate::record_layer::record_layer_header::*;
|
||||||
|
use crate::record_layer::*;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub(crate) struct Flight1;
|
||||||
|
|
||||||
|
impl fmt::Display for Flight1 {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "Flight 1")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Flight for Flight1 {
|
||||||
|
async fn parse(
|
||||||
|
&self,
|
||||||
|
tx: &mut mpsc::Sender<mpsc::Sender<()>>,
|
||||||
|
state: &mut State,
|
||||||
|
cache: &HandshakeCache,
|
||||||
|
cfg: &HandshakeConfig,
|
||||||
|
) -> Result<Box<dyn Flight + Send + Sync>, (Option<Alert>, Option<Error>)> {
|
||||||
|
// HelloVerifyRequest can be skipped by the server,
|
||||||
|
// so allow ServerHello during flight1 also
|
||||||
|
let (seq, msgs) = match cache
|
||||||
|
.full_pull_map(
|
||||||
|
state.handshake_recv_sequence,
|
||||||
|
&[
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::HelloVerifyRequest,
|
||||||
|
epoch: cfg.initial_epoch,
|
||||||
|
is_client: false,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::ServerHello,
|
||||||
|
epoch: cfg.initial_epoch,
|
||||||
|
is_client: false,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
// No valid message received. Keep reading
|
||||||
|
Ok((seq, msgs)) => (seq, msgs),
|
||||||
|
Err(_) => return Err((None, None)),
|
||||||
|
};
|
||||||
|
|
||||||
|
if msgs.contains_key(&HandshakeType::ServerHello) {
|
||||||
|
// Flight1 and flight2 were skipped.
|
||||||
|
// Parse as flight3.
|
||||||
|
let flight3 = Flight3 {};
|
||||||
|
return flight3.parse(tx, state, cache, cfg).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(message) = msgs.get(&HandshakeType::HelloVerifyRequest) {
|
||||||
|
// DTLS 1.2 clients must not assume that the server will use the protocol version
|
||||||
|
// specified in HelloVerifyRequest message. RFC 6347 Section 4.2.1
|
||||||
|
let h = match message {
|
||||||
|
HandshakeMessage::HelloVerifyRequest(h) => h,
|
||||||
|
_ => {
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::InternalError,
|
||||||
|
}),
|
||||||
|
None,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if h.version != PROTOCOL_VERSION1_0 && h.version != PROTOCOL_VERSION1_2 {
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::ProtocolVersion,
|
||||||
|
}),
|
||||||
|
Some(Error::ErrUnsupportedProtocolVersion),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
state.cookie.clone_from(&h.cookie);
|
||||||
|
state.handshake_recv_sequence = seq;
|
||||||
|
Ok(Box::new(Flight3 {}))
|
||||||
|
} else {
|
||||||
|
Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::InternalError,
|
||||||
|
}),
|
||||||
|
None,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn generate(
|
||||||
|
&self,
|
||||||
|
state: &mut State,
|
||||||
|
_cache: &HandshakeCache,
|
||||||
|
cfg: &HandshakeConfig,
|
||||||
|
) -> Result<Vec<Packet>, (Option<Alert>, Option<Error>)> {
|
||||||
|
let zero_epoch = 0;
|
||||||
|
state.local_epoch.store(zero_epoch, Ordering::SeqCst);
|
||||||
|
state.remote_epoch.store(zero_epoch, Ordering::SeqCst);
|
||||||
|
|
||||||
|
state.named_curve = DEFAULT_NAMED_CURVE;
|
||||||
|
state.cookie = vec![];
|
||||||
|
state.local_random.populate();
|
||||||
|
|
||||||
|
let mut extensions = vec![
|
||||||
|
Extension::SupportedSignatureAlgorithms(ExtensionSupportedSignatureAlgorithms {
|
||||||
|
signature_hash_algorithms: cfg.local_signature_schemes.clone(),
|
||||||
|
}),
|
||||||
|
Extension::RenegotiationInfo(ExtensionRenegotiationInfo {
|
||||||
|
renegotiated_connection: 0,
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
if cfg.local_psk_callback.is_none() {
|
||||||
|
extensions.extend_from_slice(&[
|
||||||
|
Extension::SupportedEllipticCurves(ExtensionSupportedEllipticCurves {
|
||||||
|
elliptic_curves: vec![NamedCurve::P256, NamedCurve::X25519, NamedCurve::P384],
|
||||||
|
}),
|
||||||
|
Extension::SupportedPointFormats(ExtensionSupportedPointFormats {
|
||||||
|
point_formats: vec![ELLIPTIC_CURVE_POINT_FORMAT_UNCOMPRESSED],
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !cfg.local_srtp_protection_profiles.is_empty() {
|
||||||
|
extensions.push(Extension::UseSrtp(ExtensionUseSrtp {
|
||||||
|
protection_profiles: cfg.local_srtp_protection_profiles.clone(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.extended_master_secret == ExtendedMasterSecretType::Request
|
||||||
|
|| cfg.extended_master_secret == ExtendedMasterSecretType::Require
|
||||||
|
{
|
||||||
|
extensions.push(Extension::UseExtendedMasterSecret(
|
||||||
|
ExtensionUseExtendedMasterSecret { supported: true },
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if !cfg.server_name.is_empty() {
|
||||||
|
extensions.push(Extension::ServerName(ExtensionServerName {
|
||||||
|
server_name: cfg.server_name.clone(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(vec![Packet {
|
||||||
|
record: RecordLayer::new(
|
||||||
|
PROTOCOL_VERSION1_2,
|
||||||
|
0,
|
||||||
|
Content::Handshake(Handshake::new(HandshakeMessage::ClientHello(
|
||||||
|
HandshakeMessageClientHello {
|
||||||
|
version: PROTOCOL_VERSION1_2,
|
||||||
|
random: state.local_random.clone(),
|
||||||
|
cookie: state.cookie.clone(),
|
||||||
|
|
||||||
|
cipher_suites: cfg.local_cipher_suites.clone(),
|
||||||
|
compression_methods: default_compression_methods(),
|
||||||
|
extensions,
|
||||||
|
},
|
||||||
|
))),
|
||||||
|
),
|
||||||
|
should_encrypt: false,
|
||||||
|
reset_local_sequence_number: false,
|
||||||
|
}])
|
||||||
|
}
|
||||||
|
}
|
||||||
131
libs/dtls/src/flight/flight2.rs
Normal file
131
libs/dtls/src/flight/flight2.rs
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
use async_trait::async_trait;
|
||||||
|
|
||||||
|
use super::flight0::*;
|
||||||
|
use super::flight4::*;
|
||||||
|
use super::*;
|
||||||
|
use crate::content::*;
|
||||||
|
use crate::error::Error;
|
||||||
|
use crate::handshake::handshake_message_hello_verify_request::*;
|
||||||
|
use crate::handshake::*;
|
||||||
|
use crate::record_layer::record_layer_header::*;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub(crate) struct Flight2;
|
||||||
|
|
||||||
|
impl fmt::Display for Flight2 {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "Flight 2")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Flight for Flight2 {
|
||||||
|
fn has_retransmit(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn parse(
|
||||||
|
&self,
|
||||||
|
tx: &mut mpsc::Sender<mpsc::Sender<()>>,
|
||||||
|
state: &mut State,
|
||||||
|
cache: &HandshakeCache,
|
||||||
|
cfg: &HandshakeConfig,
|
||||||
|
) -> Result<Box<dyn Flight + Send + Sync>, (Option<Alert>, Option<Error>)> {
|
||||||
|
let (seq, msgs) = match cache
|
||||||
|
.full_pull_map(
|
||||||
|
state.handshake_recv_sequence,
|
||||||
|
&[HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::ClientHello,
|
||||||
|
epoch: cfg.initial_epoch,
|
||||||
|
is_client: true,
|
||||||
|
optional: false,
|
||||||
|
}],
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
// No valid message received. Keep reading
|
||||||
|
Ok((seq, msgs)) => (seq, msgs),
|
||||||
|
|
||||||
|
// Client may retransmit the first ClientHello when HelloVerifyRequest is dropped.
|
||||||
|
// Parse as flight 0 in this case.
|
||||||
|
Err(_) => return Flight0 {}.parse(tx, state, cache, cfg).await,
|
||||||
|
};
|
||||||
|
|
||||||
|
state.handshake_recv_sequence = seq;
|
||||||
|
|
||||||
|
if let Some(message) = msgs.get(&HandshakeType::ClientHello) {
|
||||||
|
// Validate type
|
||||||
|
let client_hello = match message {
|
||||||
|
HandshakeMessage::ClientHello(client_hello) => client_hello,
|
||||||
|
_ => {
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::InternalError,
|
||||||
|
}),
|
||||||
|
None,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if client_hello.version != PROTOCOL_VERSION1_2 {
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::ProtocolVersion,
|
||||||
|
}),
|
||||||
|
Some(Error::ErrUnsupportedProtocolVersion),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if client_hello.cookie.is_empty() {
|
||||||
|
return Err((None, None));
|
||||||
|
}
|
||||||
|
|
||||||
|
if state.cookie != client_hello.cookie {
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::AccessDenied,
|
||||||
|
}),
|
||||||
|
Some(Error::ErrCookieMismatch),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Box::new(Flight4 {}))
|
||||||
|
} else {
|
||||||
|
Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::InternalError,
|
||||||
|
}),
|
||||||
|
None,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn generate(
|
||||||
|
&self,
|
||||||
|
state: &mut State,
|
||||||
|
_cache: &HandshakeCache,
|
||||||
|
_cfg: &HandshakeConfig,
|
||||||
|
) -> Result<Vec<Packet>, (Option<Alert>, Option<Error>)> {
|
||||||
|
state.handshake_send_sequence = 0;
|
||||||
|
Ok(vec![Packet {
|
||||||
|
record: RecordLayer::new(
|
||||||
|
PROTOCOL_VERSION1_2,
|
||||||
|
0,
|
||||||
|
Content::Handshake(Handshake::new(HandshakeMessage::HelloVerifyRequest(
|
||||||
|
HandshakeMessageHelloVerifyRequest {
|
||||||
|
version: PROTOCOL_VERSION1_2,
|
||||||
|
cookie: state.cookie.clone(),
|
||||||
|
},
|
||||||
|
))),
|
||||||
|
),
|
||||||
|
should_encrypt: false,
|
||||||
|
reset_local_sequence_number: false,
|
||||||
|
}])
|
||||||
|
}
|
||||||
|
}
|
||||||
470
libs/dtls/src/flight/flight3.rs
Normal file
470
libs/dtls/src/flight/flight3.rs
Normal file
@@ -0,0 +1,470 @@
|
|||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use log::*;
|
||||||
|
|
||||||
|
use super::flight5::*;
|
||||||
|
use super::*;
|
||||||
|
use crate::cipher_suite::cipher_suite_for_id;
|
||||||
|
use crate::compression_methods::*;
|
||||||
|
use crate::config::*;
|
||||||
|
use crate::content::*;
|
||||||
|
use crate::curve::named_curve::*;
|
||||||
|
use crate::error::Error;
|
||||||
|
use crate::extension::extension_server_name::*;
|
||||||
|
use crate::extension::extension_supported_elliptic_curves::*;
|
||||||
|
use crate::extension::extension_supported_point_formats::*;
|
||||||
|
use crate::extension::extension_supported_signature_algorithms::*;
|
||||||
|
use crate::extension::extension_use_extended_master_secret::*;
|
||||||
|
use crate::extension::extension_use_srtp::*;
|
||||||
|
use crate::extension::renegotiation_info::ExtensionRenegotiationInfo;
|
||||||
|
use crate::extension::*;
|
||||||
|
use crate::handshake::handshake_message_client_hello::*;
|
||||||
|
use crate::handshake::handshake_message_server_key_exchange::*;
|
||||||
|
use crate::handshake::*;
|
||||||
|
use crate::prf::{prf_pre_master_secret, prf_psk_pre_master_secret};
|
||||||
|
use crate::record_layer::record_layer_header::*;
|
||||||
|
use crate::record_layer::*;
|
||||||
|
use crate::{find_matching_cipher_suite, find_matching_srtp_profile};
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub(crate) struct Flight3;
|
||||||
|
|
||||||
|
impl fmt::Display for Flight3 {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "Flight 3")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Flight for Flight3 {
|
||||||
|
async fn parse(
|
||||||
|
&self,
|
||||||
|
_tx: &mut mpsc::Sender<mpsc::Sender<()>>,
|
||||||
|
state: &mut State,
|
||||||
|
cache: &HandshakeCache,
|
||||||
|
cfg: &HandshakeConfig,
|
||||||
|
) -> Result<Box<dyn Flight + Send + Sync>, (Option<Alert>, Option<Error>)> {
|
||||||
|
// Clients may receive multiple HelloVerifyRequest messages with different cookies.
|
||||||
|
// Clients SHOULD handle this by sending a new ClientHello with a cookie in response
|
||||||
|
// to the new HelloVerifyRequest. RFC 6347 Section 4.2.1
|
||||||
|
if let Ok((seq, msgs)) = cache
|
||||||
|
.full_pull_map(
|
||||||
|
state.handshake_recv_sequence,
|
||||||
|
&[HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::HelloVerifyRequest,
|
||||||
|
epoch: cfg.initial_epoch,
|
||||||
|
is_client: false,
|
||||||
|
optional: true,
|
||||||
|
}],
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
if let Some(message) = msgs.get(&HandshakeType::HelloVerifyRequest) {
|
||||||
|
// DTLS 1.2 clients must not assume that the server will use the protocol version
|
||||||
|
// specified in HelloVerifyRequest message. RFC 6347 Section 4.2.1
|
||||||
|
let h = match message {
|
||||||
|
HandshakeMessage::HelloVerifyRequest(h) => h,
|
||||||
|
_ => {
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::InternalError,
|
||||||
|
}),
|
||||||
|
None,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// DTLS 1.2 clients must not assume that the server will use the protocol version
|
||||||
|
// specified in HelloVerifyRequest message. RFC 6347 Section 4.2.1
|
||||||
|
if h.version != PROTOCOL_VERSION1_0 && h.version != PROTOCOL_VERSION1_2 {
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::ProtocolVersion,
|
||||||
|
}),
|
||||||
|
Some(Error::ErrUnsupportedProtocolVersion),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
state.cookie.clone_from(&h.cookie);
|
||||||
|
state.handshake_recv_sequence = seq;
|
||||||
|
return Ok(Box::new(Flight3 {}) as Box<dyn Flight + Send + Sync>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = if cfg.local_psk_callback.is_some() {
|
||||||
|
cache
|
||||||
|
.full_pull_map(
|
||||||
|
state.handshake_recv_sequence,
|
||||||
|
&[
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::ServerHello,
|
||||||
|
epoch: cfg.initial_epoch,
|
||||||
|
is_client: false,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::ServerKeyExchange,
|
||||||
|
epoch: cfg.initial_epoch,
|
||||||
|
is_client: false,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::ServerHelloDone,
|
||||||
|
epoch: cfg.initial_epoch,
|
||||||
|
is_client: false,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
} else {
|
||||||
|
cache
|
||||||
|
.full_pull_map(
|
||||||
|
state.handshake_recv_sequence,
|
||||||
|
&[
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::ServerHello,
|
||||||
|
epoch: cfg.initial_epoch,
|
||||||
|
is_client: false,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::Certificate,
|
||||||
|
epoch: cfg.initial_epoch,
|
||||||
|
is_client: false,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::ServerKeyExchange,
|
||||||
|
epoch: cfg.initial_epoch,
|
||||||
|
is_client: false,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::CertificateRequest,
|
||||||
|
epoch: cfg.initial_epoch,
|
||||||
|
is_client: false,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::ServerHelloDone,
|
||||||
|
epoch: cfg.initial_epoch,
|
||||||
|
is_client: false,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
};
|
||||||
|
|
||||||
|
let (seq, msgs) = match result {
|
||||||
|
Ok((seq, msgs)) => (seq, msgs),
|
||||||
|
Err(_) => return Err((None, None)),
|
||||||
|
};
|
||||||
|
|
||||||
|
state.handshake_recv_sequence = seq;
|
||||||
|
|
||||||
|
if let Some(message) = msgs.get(&HandshakeType::ServerHello) {
|
||||||
|
let h = match message {
|
||||||
|
HandshakeMessage::ServerHello(h) => h,
|
||||||
|
_ => {
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::InternalError,
|
||||||
|
}),
|
||||||
|
None,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if h.version != PROTOCOL_VERSION1_2 {
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::ProtocolVersion,
|
||||||
|
}),
|
||||||
|
Some(Error::ErrUnsupportedProtocolVersion),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
for extension in &h.extensions {
|
||||||
|
match extension {
|
||||||
|
Extension::UseSrtp(e) => {
|
||||||
|
let profile = match find_matching_srtp_profile(
|
||||||
|
&e.protection_profiles,
|
||||||
|
&cfg.local_srtp_protection_profiles,
|
||||||
|
) {
|
||||||
|
Ok(profile) => profile,
|
||||||
|
Err(_) => {
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::IllegalParameter,
|
||||||
|
}),
|
||||||
|
Some(Error::ErrClientNoMatchingSrtpProfile),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
state.srtp_protection_profile = profile;
|
||||||
|
}
|
||||||
|
Extension::UseExtendedMasterSecret(_) => {
|
||||||
|
if cfg.extended_master_secret != ExtendedMasterSecretType::Disable {
|
||||||
|
state.extended_master_secret = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.extended_master_secret == ExtendedMasterSecretType::Require
|
||||||
|
&& !state.extended_master_secret
|
||||||
|
{
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::InsufficientSecurity,
|
||||||
|
}),
|
||||||
|
Some(Error::ErrClientRequiredButNoServerEms),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if !cfg.local_srtp_protection_profiles.is_empty()
|
||||||
|
&& state.srtp_protection_profile == SrtpProtectionProfile::Unsupported
|
||||||
|
{
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::InsufficientSecurity,
|
||||||
|
}),
|
||||||
|
Some(Error::ErrRequestedButNoSrtpExtension),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if find_matching_cipher_suite(&[h.cipher_suite], &cfg.local_cipher_suites).is_err() {
|
||||||
|
debug!(
|
||||||
|
"[handshake:{}] use cipher suite: {}",
|
||||||
|
srv_cli_str(state.is_client),
|
||||||
|
h.cipher_suite
|
||||||
|
);
|
||||||
|
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::InsufficientSecurity,
|
||||||
|
}),
|
||||||
|
Some(Error::ErrCipherSuiteNoIntersection),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let cipher_suite = match cipher_suite_for_id(h.cipher_suite) {
|
||||||
|
Ok(cipher_suite) => cipher_suite,
|
||||||
|
Err(_) => {
|
||||||
|
debug!(
|
||||||
|
"[handshake:{}] use cipher suite: {}",
|
||||||
|
srv_cli_str(state.is_client),
|
||||||
|
h.cipher_suite
|
||||||
|
);
|
||||||
|
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::InsufficientSecurity,
|
||||||
|
}),
|
||||||
|
Some(Error::ErrInvalidCipherSuite),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
trace!(
|
||||||
|
"[handshake:{}] use cipher suite: {}",
|
||||||
|
srv_cli_str(state.is_client),
|
||||||
|
cipher_suite.to_string()
|
||||||
|
);
|
||||||
|
{
|
||||||
|
let mut cs = state.cipher_suite.lock().await;
|
||||||
|
*cs = Some(cipher_suite);
|
||||||
|
}
|
||||||
|
state.remote_random = h.random.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(message) = msgs.get(&HandshakeType::Certificate) {
|
||||||
|
let h = match message {
|
||||||
|
HandshakeMessage::Certificate(h) => h,
|
||||||
|
_ => {
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::InternalError,
|
||||||
|
}),
|
||||||
|
None,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
state.peer_certificates.clone_from(&h.certificate);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(message) = msgs.get(&HandshakeType::ServerKeyExchange) {
|
||||||
|
let h = match message {
|
||||||
|
HandshakeMessage::ServerKeyExchange(h) => h,
|
||||||
|
_ => {
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::InternalError,
|
||||||
|
}),
|
||||||
|
None,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err((alert, err)) = handle_server_key_exchange(state, cfg, h).await {
|
||||||
|
return Err((alert, err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(message) = msgs.get(&HandshakeType::CertificateRequest) {
|
||||||
|
match message {
|
||||||
|
HandshakeMessage::CertificateRequest(_) => {}
|
||||||
|
_ => {
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::InternalError,
|
||||||
|
}),
|
||||||
|
None,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
state.remote_requested_certificate = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Box::new(Flight5 {}) as Box<dyn Flight + Send + Sync>)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn generate(
|
||||||
|
&self,
|
||||||
|
state: &mut State,
|
||||||
|
_cache: &HandshakeCache,
|
||||||
|
cfg: &HandshakeConfig,
|
||||||
|
) -> Result<Vec<Packet>, (Option<Alert>, Option<Error>)> {
|
||||||
|
let mut extensions = vec![
|
||||||
|
Extension::SupportedSignatureAlgorithms(ExtensionSupportedSignatureAlgorithms {
|
||||||
|
signature_hash_algorithms: cfg.local_signature_schemes.clone(),
|
||||||
|
}),
|
||||||
|
Extension::RenegotiationInfo(ExtensionRenegotiationInfo {
|
||||||
|
renegotiated_connection: 0,
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
if cfg.local_psk_callback.is_none() {
|
||||||
|
extensions.extend_from_slice(&[
|
||||||
|
Extension::SupportedEllipticCurves(ExtensionSupportedEllipticCurves {
|
||||||
|
elliptic_curves: vec![NamedCurve::P256, NamedCurve::X25519, NamedCurve::P384],
|
||||||
|
}),
|
||||||
|
Extension::SupportedPointFormats(ExtensionSupportedPointFormats {
|
||||||
|
point_formats: vec![ELLIPTIC_CURVE_POINT_FORMAT_UNCOMPRESSED],
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !cfg.local_srtp_protection_profiles.is_empty() {
|
||||||
|
extensions.push(Extension::UseSrtp(ExtensionUseSrtp {
|
||||||
|
protection_profiles: cfg.local_srtp_protection_profiles.clone(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.extended_master_secret == ExtendedMasterSecretType::Request
|
||||||
|
|| cfg.extended_master_secret == ExtendedMasterSecretType::Require
|
||||||
|
{
|
||||||
|
extensions.push(Extension::UseExtendedMasterSecret(
|
||||||
|
ExtensionUseExtendedMasterSecret { supported: true },
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if !cfg.server_name.is_empty() {
|
||||||
|
extensions.push(Extension::ServerName(ExtensionServerName {
|
||||||
|
server_name: cfg.server_name.clone(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(vec![Packet {
|
||||||
|
record: RecordLayer::new(
|
||||||
|
PROTOCOL_VERSION1_2,
|
||||||
|
0,
|
||||||
|
Content::Handshake(Handshake::new(HandshakeMessage::ClientHello(
|
||||||
|
HandshakeMessageClientHello {
|
||||||
|
version: PROTOCOL_VERSION1_2,
|
||||||
|
random: state.local_random.clone(),
|
||||||
|
cookie: state.cookie.clone(),
|
||||||
|
|
||||||
|
cipher_suites: cfg.local_cipher_suites.clone(),
|
||||||
|
compression_methods: default_compression_methods(),
|
||||||
|
extensions,
|
||||||
|
},
|
||||||
|
))),
|
||||||
|
),
|
||||||
|
should_encrypt: false,
|
||||||
|
reset_local_sequence_number: false,
|
||||||
|
}])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn handle_server_key_exchange(
|
||||||
|
state: &mut State,
|
||||||
|
cfg: &HandshakeConfig,
|
||||||
|
h: &HandshakeMessageServerKeyExchange,
|
||||||
|
) -> Result<(), (Option<Alert>, Option<Error>)> {
|
||||||
|
if let Some(local_psk_callback) = &cfg.local_psk_callback {
|
||||||
|
let psk = match local_psk_callback(&h.identity_hint).await {
|
||||||
|
Ok(psk) => psk,
|
||||||
|
Err(err) => {
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::InternalError,
|
||||||
|
}),
|
||||||
|
Some(err),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
state.identity_hint.clone_from(&h.identity_hint);
|
||||||
|
state.pre_master_secret = prf_psk_pre_master_secret(&psk);
|
||||||
|
} else {
|
||||||
|
let local_keypair = match h.named_curve.generate_keypair() {
|
||||||
|
Ok(local_keypair) => local_keypair,
|
||||||
|
Err(err) => {
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::InternalError,
|
||||||
|
}),
|
||||||
|
Some(err),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
state.pre_master_secret = match prf_pre_master_secret(
|
||||||
|
&h.public_key,
|
||||||
|
&local_keypair.private_key,
|
||||||
|
local_keypair.curve,
|
||||||
|
) {
|
||||||
|
Ok(pre_master_secret) => pre_master_secret,
|
||||||
|
Err(err) => {
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::InternalError,
|
||||||
|
}),
|
||||||
|
Some(err),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
state.local_keypair = Some(local_keypair);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
853
libs/dtls/src/flight/flight4.rs
Normal file
853
libs/dtls/src/flight/flight4.rs
Normal file
@@ -0,0 +1,853 @@
|
|||||||
|
use std::fmt;
|
||||||
|
use std::io::BufWriter;
|
||||||
|
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use log::*;
|
||||||
|
|
||||||
|
use super::flight6::*;
|
||||||
|
use super::*;
|
||||||
|
use crate::cipher_suite::*;
|
||||||
|
use crate::client_certificate_type::*;
|
||||||
|
use crate::compression_methods::*;
|
||||||
|
use crate::config::*;
|
||||||
|
use crate::content::*;
|
||||||
|
use crate::crypto::*;
|
||||||
|
use crate::curve::named_curve::*;
|
||||||
|
use crate::curve::*;
|
||||||
|
use crate::error::Error;
|
||||||
|
use crate::extension::extension_supported_elliptic_curves::*;
|
||||||
|
use crate::extension::extension_supported_point_formats::*;
|
||||||
|
use crate::extension::extension_use_extended_master_secret::*;
|
||||||
|
use crate::extension::extension_use_srtp::*;
|
||||||
|
use crate::extension::renegotiation_info::ExtensionRenegotiationInfo;
|
||||||
|
use crate::extension::*;
|
||||||
|
use crate::handshake::handshake_message_certificate::*;
|
||||||
|
use crate::handshake::handshake_message_certificate_request::*;
|
||||||
|
use crate::handshake::handshake_message_server_hello::*;
|
||||||
|
use crate::handshake::handshake_message_server_hello_done::*;
|
||||||
|
use crate::handshake::handshake_message_server_key_exchange::*;
|
||||||
|
use crate::handshake::*;
|
||||||
|
use crate::prf::*;
|
||||||
|
use crate::record_layer::record_layer_header::*;
|
||||||
|
use crate::record_layer::*;
|
||||||
|
use crate::signature_hash_algorithm::*;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub(crate) struct Flight4;
|
||||||
|
|
||||||
|
impl fmt::Display for Flight4 {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "Flight 4")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Flight for Flight4 {
|
||||||
|
async fn parse(
|
||||||
|
&self,
|
||||||
|
tx: &mut mpsc::Sender<mpsc::Sender<()>>,
|
||||||
|
state: &mut State,
|
||||||
|
cache: &HandshakeCache,
|
||||||
|
cfg: &HandshakeConfig,
|
||||||
|
) -> Result<Box<dyn Flight + Send + Sync>, (Option<Alert>, Option<Error>)> {
|
||||||
|
let (seq, msgs) = match cache
|
||||||
|
.full_pull_map(
|
||||||
|
state.handshake_recv_sequence,
|
||||||
|
&[
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::Certificate,
|
||||||
|
epoch: cfg.initial_epoch,
|
||||||
|
is_client: true,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::ClientKeyExchange,
|
||||||
|
epoch: cfg.initial_epoch,
|
||||||
|
is_client: true,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::CertificateVerify,
|
||||||
|
epoch: cfg.initial_epoch,
|
||||||
|
is_client: true,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok((seq, msgs)) => (seq, msgs),
|
||||||
|
Err(_) => return Err((None, None)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let client_key_exchange = if let Some(HandshakeMessage::ClientKeyExchange(h)) =
|
||||||
|
msgs.get(&HandshakeType::ClientKeyExchange)
|
||||||
|
{
|
||||||
|
h
|
||||||
|
} else {
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::InternalError,
|
||||||
|
}),
|
||||||
|
None,
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(message) = msgs.get(&HandshakeType::Certificate) {
|
||||||
|
let h = match message {
|
||||||
|
HandshakeMessage::Certificate(h) => h,
|
||||||
|
_ => {
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::InternalError,
|
||||||
|
}),
|
||||||
|
None,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
state.peer_certificates.clone_from(&h.certificate);
|
||||||
|
trace!(
|
||||||
|
"[handshake] PeerCertificates4 {}",
|
||||||
|
state.peer_certificates.len()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(message) = msgs.get(&HandshakeType::CertificateVerify) {
|
||||||
|
let h = match message {
|
||||||
|
HandshakeMessage::CertificateVerify(h) => h,
|
||||||
|
_ => {
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::InternalError,
|
||||||
|
}),
|
||||||
|
None,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if state.peer_certificates.is_empty() {
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::NoCertificate,
|
||||||
|
}),
|
||||||
|
Some(Error::ErrCertificateVerifyNoCertificate),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let plain_text = cache
|
||||||
|
.pull_and_merge(&[
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::ClientHello,
|
||||||
|
epoch: cfg.initial_epoch,
|
||||||
|
is_client: true,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::ServerHello,
|
||||||
|
epoch: cfg.initial_epoch,
|
||||||
|
is_client: false,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::Certificate,
|
||||||
|
epoch: cfg.initial_epoch,
|
||||||
|
is_client: false,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::ServerKeyExchange,
|
||||||
|
epoch: cfg.initial_epoch,
|
||||||
|
is_client: false,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::CertificateRequest,
|
||||||
|
epoch: cfg.initial_epoch,
|
||||||
|
is_client: false,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::ServerHelloDone,
|
||||||
|
epoch: cfg.initial_epoch,
|
||||||
|
is_client: false,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::Certificate,
|
||||||
|
epoch: cfg.initial_epoch,
|
||||||
|
is_client: true,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::ClientKeyExchange,
|
||||||
|
epoch: cfg.initial_epoch,
|
||||||
|
is_client: true,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// Verify that the pair of hash algorithm and signature is listed.
|
||||||
|
let mut valid_signature_scheme = false;
|
||||||
|
for ss in &cfg.local_signature_schemes {
|
||||||
|
if ss.hash == h.algorithm.hash && ss.signature == h.algorithm.signature {
|
||||||
|
valid_signature_scheme = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !valid_signature_scheme {
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::InsufficientSecurity,
|
||||||
|
}),
|
||||||
|
Some(Error::ErrNoAvailableSignatureSchemes),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Err(err) = verify_certificate_verify(
|
||||||
|
&plain_text,
|
||||||
|
&h.algorithm,
|
||||||
|
&h.signature,
|
||||||
|
&state.peer_certificates,
|
||||||
|
cfg.insecure_verification,
|
||||||
|
) {
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::BadCertificate,
|
||||||
|
}),
|
||||||
|
Some(err),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut chains = vec![];
|
||||||
|
let mut verified = false;
|
||||||
|
if cfg.client_auth as u8 >= ClientAuthType::VerifyClientCertIfGiven as u8 {
|
||||||
|
if let Some(client_cert_verifier) = &cfg.client_cert_verifier {
|
||||||
|
chains =
|
||||||
|
match verify_client_cert(&state.peer_certificates, client_cert_verifier) {
|
||||||
|
Ok(chains) => chains,
|
||||||
|
Err(err) => {
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::BadCertificate,
|
||||||
|
}),
|
||||||
|
Some(err),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::BadCertificate,
|
||||||
|
}),
|
||||||
|
Some(Error::ErrInvalidCertificate),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
verified = true
|
||||||
|
}
|
||||||
|
if let Some(verify_peer_certificate) = &cfg.verify_peer_certificate {
|
||||||
|
if let Err(err) = verify_peer_certificate(&state.peer_certificates, &chains) {
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::BadCertificate,
|
||||||
|
}),
|
||||||
|
Some(err),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
state.peer_certificates_verified = verified
|
||||||
|
} else if !state.peer_certificates.is_empty() {
|
||||||
|
// A certificate was received, but we haven't seen a CertificateVerify
|
||||||
|
// keep reading until we receive one
|
||||||
|
return Err((None, None));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut cipher_suite = state.cipher_suite.lock().await;
|
||||||
|
if let Some(cipher_suite) = &mut *cipher_suite {
|
||||||
|
if !cipher_suite.is_initialized() {
|
||||||
|
let mut server_random = vec![];
|
||||||
|
{
|
||||||
|
let mut writer = BufWriter::<&mut Vec<u8>>::new(server_random.as_mut());
|
||||||
|
let _ = state.local_random.marshal(&mut writer);
|
||||||
|
}
|
||||||
|
let mut client_random = vec![];
|
||||||
|
{
|
||||||
|
let mut writer = BufWriter::<&mut Vec<u8>>::new(client_random.as_mut());
|
||||||
|
let _ = state.remote_random.marshal(&mut writer);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut pre_master_secret = vec![];
|
||||||
|
if let Some(local_psk_callback) = &cfg.local_psk_callback {
|
||||||
|
let psk = match local_psk_callback(&client_key_exchange.identity_hint).await
|
||||||
|
{
|
||||||
|
Ok(psk) => psk,
|
||||||
|
Err(err) => {
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::InternalError,
|
||||||
|
}),
|
||||||
|
Some(err),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
state
|
||||||
|
.identity_hint
|
||||||
|
.clone_from(&client_key_exchange.identity_hint);
|
||||||
|
pre_master_secret = prf_psk_pre_master_secret(&psk);
|
||||||
|
} else if let Some(local_keypair) = &state.local_keypair {
|
||||||
|
pre_master_secret = match prf_pre_master_secret(
|
||||||
|
&client_key_exchange.public_key,
|
||||||
|
&local_keypair.private_key,
|
||||||
|
local_keypair.curve,
|
||||||
|
) {
|
||||||
|
Ok(pre_master_secret) => pre_master_secret,
|
||||||
|
Err(err) => {
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::IllegalParameter,
|
||||||
|
}),
|
||||||
|
Some(err),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if state.extended_master_secret {
|
||||||
|
let hf = cipher_suite.hash_func();
|
||||||
|
let session_hash =
|
||||||
|
match cache.session_hash(hf, cfg.initial_epoch, &[]).await {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(err) => {
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::InternalError,
|
||||||
|
}),
|
||||||
|
Some(err),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
state.master_secret = match prf_extended_master_secret(
|
||||||
|
&pre_master_secret,
|
||||||
|
&session_hash,
|
||||||
|
cipher_suite.hash_func(),
|
||||||
|
) {
|
||||||
|
Ok(ms) => ms,
|
||||||
|
Err(err) => {
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::InternalError,
|
||||||
|
}),
|
||||||
|
Some(err),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
state.master_secret = match prf_master_secret(
|
||||||
|
&pre_master_secret,
|
||||||
|
&client_random,
|
||||||
|
&server_random,
|
||||||
|
cipher_suite.hash_func(),
|
||||||
|
) {
|
||||||
|
Ok(ms) => ms,
|
||||||
|
Err(err) => {
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::InternalError,
|
||||||
|
}),
|
||||||
|
Some(err),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Err(err) = cipher_suite.init(
|
||||||
|
&state.master_secret,
|
||||||
|
&client_random,
|
||||||
|
&server_random,
|
||||||
|
false,
|
||||||
|
) {
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::InternalError,
|
||||||
|
}),
|
||||||
|
Some(err),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now, encrypted packets can be handled
|
||||||
|
let (done_tx, mut done_rx) = mpsc::channel(1);
|
||||||
|
if let Err(err) = tx.send(done_tx).await {
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::InternalError,
|
||||||
|
}),
|
||||||
|
Some(Error::Other(err.to_string())),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
done_rx.recv().await;
|
||||||
|
|
||||||
|
let (seq, msgs) = match cache
|
||||||
|
.full_pull_map(
|
||||||
|
seq,
|
||||||
|
&[HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::Finished,
|
||||||
|
epoch: cfg.initial_epoch + 1,
|
||||||
|
is_client: true,
|
||||||
|
optional: false,
|
||||||
|
}],
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok((seq, msgs)) => (seq, msgs),
|
||||||
|
// No valid message received. Keep reading
|
||||||
|
Err(_) => return Err((None, None)),
|
||||||
|
};
|
||||||
|
|
||||||
|
state.handshake_recv_sequence = seq;
|
||||||
|
|
||||||
|
if let Some(HandshakeMessage::Finished(h)) = msgs.get(&HandshakeType::Finished) {
|
||||||
|
h
|
||||||
|
} else {
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::InternalError,
|
||||||
|
}),
|
||||||
|
None,
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
match cfg.client_auth {
|
||||||
|
ClientAuthType::RequireAnyClientCert => {
|
||||||
|
trace!(
|
||||||
|
"{} peer_certificates.len() {}",
|
||||||
|
srv_cli_str(state.is_client),
|
||||||
|
state.peer_certificates.len(),
|
||||||
|
);
|
||||||
|
if state.peer_certificates.is_empty() {
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::NoCertificate,
|
||||||
|
}),
|
||||||
|
Some(Error::ErrClientCertificateRequired),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ClientAuthType::VerifyClientCertIfGiven => {
|
||||||
|
if !state.peer_certificates.is_empty() && !state.peer_certificates_verified {
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::BadCertificate,
|
||||||
|
}),
|
||||||
|
Some(Error::ErrClientCertificateNotVerified),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ClientAuthType::RequireAndVerifyClientCert => {
|
||||||
|
if state.peer_certificates.is_empty() {
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::NoCertificate,
|
||||||
|
}),
|
||||||
|
Some(Error::ErrClientCertificateRequired),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if !state.peer_certificates_verified {
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::BadCertificate,
|
||||||
|
}),
|
||||||
|
Some(Error::ErrClientCertificateNotVerified),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ClientAuthType::NoClientCert | ClientAuthType::RequestClientCert => {
|
||||||
|
return Ok(Box::new(Flight6 {}) as Box<dyn Flight + Send + Sync>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Box::new(Flight6 {}) as Box<dyn Flight + Send + Sync>)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn generate(
|
||||||
|
&self,
|
||||||
|
state: &mut State,
|
||||||
|
_cache: &HandshakeCache,
|
||||||
|
cfg: &HandshakeConfig,
|
||||||
|
) -> Result<Vec<Packet>, (Option<Alert>, Option<Error>)> {
|
||||||
|
let mut extensions = vec![Extension::RenegotiationInfo(ExtensionRenegotiationInfo {
|
||||||
|
renegotiated_connection: 0,
|
||||||
|
})];
|
||||||
|
if (cfg.extended_master_secret == ExtendedMasterSecretType::Request
|
||||||
|
|| cfg.extended_master_secret == ExtendedMasterSecretType::Require)
|
||||||
|
&& state.extended_master_secret
|
||||||
|
{
|
||||||
|
extensions.push(Extension::UseExtendedMasterSecret(
|
||||||
|
ExtensionUseExtendedMasterSecret { supported: true },
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if state.srtp_protection_profile != SrtpProtectionProfile::Unsupported {
|
||||||
|
extensions.push(Extension::UseSrtp(ExtensionUseSrtp {
|
||||||
|
protection_profiles: vec![state.srtp_protection_profile],
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.local_psk_callback.is_none() {
|
||||||
|
extensions.extend_from_slice(&[
|
||||||
|
Extension::SupportedEllipticCurves(ExtensionSupportedEllipticCurves {
|
||||||
|
elliptic_curves: vec![NamedCurve::P256, NamedCurve::X25519, NamedCurve::P384],
|
||||||
|
}),
|
||||||
|
Extension::SupportedPointFormats(ExtensionSupportedPointFormats {
|
||||||
|
point_formats: vec![ELLIPTIC_CURVE_POINT_FORMAT_UNCOMPRESSED],
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut pkts = vec![Packet {
|
||||||
|
record: RecordLayer::new(
|
||||||
|
PROTOCOL_VERSION1_2,
|
||||||
|
0,
|
||||||
|
Content::Handshake(Handshake::new(HandshakeMessage::ServerHello(
|
||||||
|
HandshakeMessageServerHello {
|
||||||
|
version: PROTOCOL_VERSION1_2,
|
||||||
|
random: state.local_random.clone(),
|
||||||
|
cipher_suite: {
|
||||||
|
let cipher_suite = state.cipher_suite.lock().await;
|
||||||
|
if let Some(cipher_suite) = &*cipher_suite {
|
||||||
|
cipher_suite.id()
|
||||||
|
} else {
|
||||||
|
CipherSuiteId::Unsupported
|
||||||
|
}
|
||||||
|
},
|
||||||
|
compression_method: default_compression_methods().ids[0],
|
||||||
|
extensions,
|
||||||
|
},
|
||||||
|
))),
|
||||||
|
),
|
||||||
|
should_encrypt: false,
|
||||||
|
reset_local_sequence_number: false,
|
||||||
|
}];
|
||||||
|
|
||||||
|
if cfg.local_psk_callback.is_none() {
|
||||||
|
let certificate = match cfg.get_certificate(&cfg.server_name) {
|
||||||
|
Ok(cert) => cert,
|
||||||
|
Err(err) => {
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::HandshakeFailure,
|
||||||
|
}),
|
||||||
|
Some(err),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pkts.push(Packet {
|
||||||
|
record: RecordLayer::new(
|
||||||
|
PROTOCOL_VERSION1_2,
|
||||||
|
0,
|
||||||
|
Content::Handshake(Handshake::new(HandshakeMessage::Certificate(
|
||||||
|
HandshakeMessageCertificate {
|
||||||
|
certificate: certificate
|
||||||
|
.certificate
|
||||||
|
.iter()
|
||||||
|
.map(|x| x.as_ref().to_owned())
|
||||||
|
.collect(),
|
||||||
|
},
|
||||||
|
))),
|
||||||
|
),
|
||||||
|
should_encrypt: false,
|
||||||
|
reset_local_sequence_number: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut server_random = vec![];
|
||||||
|
{
|
||||||
|
let mut writer = BufWriter::<&mut Vec<u8>>::new(server_random.as_mut());
|
||||||
|
let _ = state.local_random.marshal(&mut writer);
|
||||||
|
}
|
||||||
|
let mut client_random = vec![];
|
||||||
|
{
|
||||||
|
let mut writer = BufWriter::<&mut Vec<u8>>::new(client_random.as_mut());
|
||||||
|
let _ = state.remote_random.marshal(&mut writer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find compatible signature scheme
|
||||||
|
let signature_hash_algo = match select_signature_scheme(
|
||||||
|
&cfg.local_signature_schemes,
|
||||||
|
&certificate.private_key,
|
||||||
|
) {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(err) => {
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::InsufficientSecurity,
|
||||||
|
}),
|
||||||
|
Some(err),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(local_keypair) = &state.local_keypair {
|
||||||
|
let signature = match generate_key_signature(
|
||||||
|
&client_random,
|
||||||
|
&server_random,
|
||||||
|
&local_keypair.public_key,
|
||||||
|
state.named_curve,
|
||||||
|
&certificate.private_key, /*, signature_hash_algo.hash*/
|
||||||
|
) {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(err) => {
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::InternalError,
|
||||||
|
}),
|
||||||
|
Some(err),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
state.local_key_signature = signature;
|
||||||
|
|
||||||
|
pkts.push(Packet {
|
||||||
|
record: RecordLayer::new(
|
||||||
|
PROTOCOL_VERSION1_2,
|
||||||
|
0,
|
||||||
|
Content::Handshake(Handshake::new(HandshakeMessage::ServerKeyExchange(
|
||||||
|
HandshakeMessageServerKeyExchange {
|
||||||
|
identity_hint: vec![],
|
||||||
|
elliptic_curve_type: EllipticCurveType::NamedCurve,
|
||||||
|
named_curve: state.named_curve,
|
||||||
|
public_key: local_keypair.public_key.clone(),
|
||||||
|
algorithm: SignatureHashAlgorithm {
|
||||||
|
hash: signature_hash_algo.hash,
|
||||||
|
signature: signature_hash_algo.signature,
|
||||||
|
},
|
||||||
|
signature: state.local_key_signature.clone(),
|
||||||
|
},
|
||||||
|
))),
|
||||||
|
),
|
||||||
|
should_encrypt: false,
|
||||||
|
reset_local_sequence_number: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.client_auth as u8 > ClientAuthType::NoClientCert as u8 {
|
||||||
|
pkts.push(Packet {
|
||||||
|
record: RecordLayer::new(
|
||||||
|
PROTOCOL_VERSION1_2,
|
||||||
|
0,
|
||||||
|
Content::Handshake(Handshake::new(HandshakeMessage::CertificateRequest(
|
||||||
|
HandshakeMessageCertificateRequest {
|
||||||
|
certificate_types: vec![
|
||||||
|
ClientCertificateType::RsaSign,
|
||||||
|
ClientCertificateType::EcdsaSign,
|
||||||
|
],
|
||||||
|
signature_hash_algorithms: cfg.local_signature_schemes.clone(),
|
||||||
|
},
|
||||||
|
))),
|
||||||
|
),
|
||||||
|
should_encrypt: false,
|
||||||
|
reset_local_sequence_number: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if let Some(local_psk_identity_hint) = &cfg.local_psk_identity_hint {
|
||||||
|
// To help the client in selecting which identity to use, the server
|
||||||
|
// can provide a "PSK identity hint" in the ServerKeyExchange message.
|
||||||
|
// If no hint is provided, the ServerKeyExchange message is omitted.
|
||||||
|
//
|
||||||
|
// https://tools.ietf.org/html/rfc4279#section-2
|
||||||
|
pkts.push(Packet {
|
||||||
|
record: RecordLayer::new(
|
||||||
|
PROTOCOL_VERSION1_2,
|
||||||
|
0,
|
||||||
|
Content::Handshake(Handshake::new(HandshakeMessage::ServerKeyExchange(
|
||||||
|
HandshakeMessageServerKeyExchange {
|
||||||
|
identity_hint: local_psk_identity_hint.clone(),
|
||||||
|
elliptic_curve_type: EllipticCurveType::Unsupported,
|
||||||
|
named_curve: NamedCurve::Unsupported,
|
||||||
|
public_key: vec![],
|
||||||
|
algorithm: SignatureHashAlgorithm {
|
||||||
|
hash: HashAlgorithm::Unsupported,
|
||||||
|
signature: SignatureAlgorithm::Unsupported,
|
||||||
|
},
|
||||||
|
signature: vec![],
|
||||||
|
},
|
||||||
|
))),
|
||||||
|
),
|
||||||
|
should_encrypt: false,
|
||||||
|
reset_local_sequence_number: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pkts.push(Packet {
|
||||||
|
record: RecordLayer::new(
|
||||||
|
PROTOCOL_VERSION1_2,
|
||||||
|
0,
|
||||||
|
Content::Handshake(Handshake::new(HandshakeMessage::ServerHelloDone(
|
||||||
|
HandshakeMessageServerHelloDone {},
|
||||||
|
))),
|
||||||
|
),
|
||||||
|
should_encrypt: false,
|
||||||
|
reset_local_sequence_number: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(pkts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::error::Result;
|
||||||
|
|
||||||
|
struct MockCipherSuite {}
|
||||||
|
|
||||||
|
impl CipherSuite for MockCipherSuite {
|
||||||
|
fn to_string(&self) -> String {
|
||||||
|
"MockCipherSuite".into()
|
||||||
|
}
|
||||||
|
fn id(&self) -> CipherSuiteId {
|
||||||
|
unimplemented!();
|
||||||
|
}
|
||||||
|
fn certificate_type(&self) -> ClientCertificateType {
|
||||||
|
unimplemented!();
|
||||||
|
}
|
||||||
|
fn hash_func(&self) -> CipherSuiteHash {
|
||||||
|
unimplemented!();
|
||||||
|
}
|
||||||
|
fn is_psk(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
fn is_initialized(&self) -> bool {
|
||||||
|
panic!("is_initialized called with Certificate but not CertificateVerify");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate the internal encryption state
|
||||||
|
fn init(
|
||||||
|
&mut self,
|
||||||
|
_master_secret: &[u8],
|
||||||
|
_client_random: &[u8],
|
||||||
|
_server_random: &[u8],
|
||||||
|
_is_client: bool,
|
||||||
|
) -> Result<()> {
|
||||||
|
unimplemented!();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encrypt(&self, _pkt_rlh: &RecordLayerHeader, _raw: &[u8]) -> Result<Vec<u8>> {
|
||||||
|
unimplemented!();
|
||||||
|
}
|
||||||
|
fn decrypt(&self, _input: &[u8]) -> Result<Vec<u8>> {
|
||||||
|
unimplemented!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert that if a client sends a certificate they must also send a `CertificateVerify`
|
||||||
|
// message. The `Flight4` must not interact with the `cipher_suite` if the `CertificateVerify`
|
||||||
|
// is missing.
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_flight4_process_certificateverify() {
|
||||||
|
let mut state = State {
|
||||||
|
cipher_suite: Arc::new(Mutex::new(Some(Box::new(MockCipherSuite {})))),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let raw_certificate = vec![
|
||||||
|
0x0b, 0x00, 0x01, 0x9b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x9b, 0x00, 0x01,
|
||||||
|
0x98, 0x00, 0x01, 0x95, 0x30, 0x82, 0x01, 0x91, 0x30, 0x82, 0x01, 0x38, 0xa0, 0x03,
|
||||||
|
0x02, 0x01, 0x02, 0x02, 0x11, 0x01, 0x65, 0x03, 0x3f, 0x4d, 0x0b, 0x9a, 0x62, 0x91,
|
||||||
|
0xdb, 0x4d, 0x28, 0x2c, 0x1f, 0xd6, 0x73, 0x32, 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86,
|
||||||
|
0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, 0x30, 0x00, 0x30, 0x1e, 0x17, 0x0d, 0x32, 0x32,
|
||||||
|
0x30, 0x35, 0x31, 0x35, 0x31, 0x38, 0x34, 0x33, 0x35, 0x35, 0x5a, 0x17, 0x0d, 0x32,
|
||||||
|
0x32, 0x30, 0x36, 0x31, 0x35, 0x31, 0x38, 0x34, 0x33, 0x35, 0x35, 0x5a, 0x30, 0x00,
|
||||||
|
0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06,
|
||||||
|
0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00, 0x04, 0xc3,
|
||||||
|
0xb7, 0x13, 0x1a, 0x0a, 0xfc, 0xd0, 0x82, 0xf8, 0x94, 0x5e, 0xc0, 0x77, 0x07, 0x81,
|
||||||
|
0x28, 0xc9, 0xcb, 0x08, 0x84, 0x50, 0x6b, 0xf0, 0x22, 0xe8, 0x79, 0xb9, 0x15, 0x33,
|
||||||
|
0xc4, 0x56, 0xa1, 0xd3, 0x1b, 0x24, 0xe3, 0x61, 0xbd, 0x4d, 0x65, 0x80, 0x6b, 0x5d,
|
||||||
|
0x96, 0x48, 0xa2, 0x44, 0x9e, 0xce, 0xe8, 0x65, 0xd6, 0x3c, 0xe0, 0x9b, 0x6b, 0xa1,
|
||||||
|
0x36, 0x34, 0xb2, 0x39, 0xe2, 0x03, 0x00, 0xa3, 0x81, 0x92, 0x30, 0x81, 0x8f, 0x30,
|
||||||
|
0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x02,
|
||||||
|
0xa4, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x25, 0x04, 0x16, 0x30, 0x14, 0x06, 0x08,
|
||||||
|
0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x02, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05,
|
||||||
|
0x05, 0x07, 0x03, 0x01, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff,
|
||||||
|
0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e,
|
||||||
|
0x04, 0x16, 0x04, 0x14, 0xb1, 0x1a, 0xe3, 0xeb, 0x6f, 0x7c, 0xc3, 0x8f, 0xba, 0x6f,
|
||||||
|
0x1c, 0xe8, 0xf0, 0x23, 0x08, 0x50, 0x8d, 0x3c, 0xea, 0x31, 0x30, 0x2e, 0x06, 0x03,
|
||||||
|
0x55, 0x1d, 0x11, 0x01, 0x01, 0xff, 0x04, 0x24, 0x30, 0x22, 0x82, 0x20, 0x30, 0x30,
|
||||||
|
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
|
||||||
|
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
|
||||||
|
0x30, 0x30, 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02,
|
||||||
|
0x03, 0x47, 0x00, 0x30, 0x44, 0x02, 0x20, 0x06, 0x31, 0x43, 0xac, 0x03, 0x45, 0x79,
|
||||||
|
0x3c, 0xd7, 0x5f, 0x6e, 0x6a, 0xf8, 0x0e, 0xfd, 0x35, 0x49, 0xee, 0x1b, 0xbc, 0x47,
|
||||||
|
0xce, 0xe3, 0x39, 0xec, 0xe4, 0x62, 0xe1, 0x30, 0x1a, 0xa1, 0x89, 0x02, 0x20, 0x35,
|
||||||
|
0xcd, 0x7a, 0x15, 0x68, 0x09, 0x50, 0x49, 0x9e, 0x3e, 0x05, 0xd7, 0xc2, 0x69, 0x3f,
|
||||||
|
0x9c, 0x0c, 0x98, 0x92, 0x65, 0xec, 0xae, 0x44, 0xfe, 0xe5, 0x68, 0xb8, 0x09, 0x78,
|
||||||
|
0x7f, 0x6b, 0x77,
|
||||||
|
];
|
||||||
|
|
||||||
|
let raw_client_key_exchange = vec![
|
||||||
|
0x10, 0x00, 0x00, 0x21, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x20, 0x96,
|
||||||
|
0xed, 0x0c, 0xee, 0xf3, 0x11, 0xb1, 0x9d, 0x8b, 0x1c, 0x02, 0x7f, 0x06, 0x7c, 0x57,
|
||||||
|
0x7a, 0x14, 0xa6, 0x41, 0xde, 0x63, 0x57, 0x9e, 0xcd, 0x34, 0x54, 0xba, 0x37, 0x4d,
|
||||||
|
0x34, 0x15, 0x18,
|
||||||
|
];
|
||||||
|
|
||||||
|
let mut cache = HandshakeCache::new();
|
||||||
|
cache
|
||||||
|
.push(raw_certificate, 0, 0, HandshakeType::Certificate, true)
|
||||||
|
.await;
|
||||||
|
cache
|
||||||
|
.push(
|
||||||
|
raw_client_key_exchange,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
HandshakeType::ClientKeyExchange,
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let cfg = HandshakeConfig::default();
|
||||||
|
|
||||||
|
let (mut tx, _rx) = mpsc::channel::<mpsc::Sender<()>>(1);
|
||||||
|
|
||||||
|
let f = Flight4 {};
|
||||||
|
let res = f.parse(&mut tx, &mut state, &cache, &cfg).await;
|
||||||
|
assert!(res.is_err());
|
||||||
|
}
|
||||||
|
}
|
||||||
779
libs/dtls/src/flight/flight5.rs
Normal file
779
libs/dtls/src/flight/flight5.rs
Normal file
@@ -0,0 +1,779 @@
|
|||||||
|
use std::fmt;
|
||||||
|
use std::io::{BufReader, BufWriter};
|
||||||
|
|
||||||
|
use async_trait::async_trait;
|
||||||
|
|
||||||
|
use super::flight3::*;
|
||||||
|
use super::*;
|
||||||
|
use crate::change_cipher_spec::ChangeCipherSpec;
|
||||||
|
use crate::content::*;
|
||||||
|
use crate::crypto::*;
|
||||||
|
use crate::curve::named_curve::*;
|
||||||
|
use crate::curve::*;
|
||||||
|
use crate::error::Error;
|
||||||
|
use crate::handshake::handshake_message_certificate::*;
|
||||||
|
use crate::handshake::handshake_message_certificate_verify::*;
|
||||||
|
use crate::handshake::handshake_message_client_key_exchange::*;
|
||||||
|
use crate::handshake::handshake_message_finished::*;
|
||||||
|
use crate::handshake::handshake_message_server_key_exchange::*;
|
||||||
|
use crate::handshake::*;
|
||||||
|
use crate::prf::*;
|
||||||
|
use crate::record_layer::record_layer_header::*;
|
||||||
|
use crate::record_layer::*;
|
||||||
|
use crate::signature_hash_algorithm::*;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub(crate) struct Flight5;
|
||||||
|
|
||||||
|
impl fmt::Display for Flight5 {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "Flight 5")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Flight for Flight5 {
|
||||||
|
fn is_last_recv_flight(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn parse(
|
||||||
|
&self,
|
||||||
|
_tx: &mut mpsc::Sender<mpsc::Sender<()>>,
|
||||||
|
state: &mut State,
|
||||||
|
cache: &HandshakeCache,
|
||||||
|
cfg: &HandshakeConfig,
|
||||||
|
) -> Result<Box<dyn Flight + Send + Sync>, (Option<Alert>, Option<Error>)> {
|
||||||
|
let (_seq, msgs) = match cache
|
||||||
|
.full_pull_map(
|
||||||
|
state.handshake_recv_sequence,
|
||||||
|
&[HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::Finished,
|
||||||
|
epoch: cfg.initial_epoch + 1,
|
||||||
|
is_client: false,
|
||||||
|
optional: false,
|
||||||
|
}],
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok((seq, msgs)) => (seq, msgs),
|
||||||
|
Err(_) => return Err((None, None)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let finished =
|
||||||
|
if let Some(HandshakeMessage::Finished(h)) = msgs.get(&HandshakeType::Finished) {
|
||||||
|
h
|
||||||
|
} else {
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::InternalError,
|
||||||
|
}),
|
||||||
|
None,
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
let plain_text = cache
|
||||||
|
.pull_and_merge(&[
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::ClientHello,
|
||||||
|
epoch: cfg.initial_epoch,
|
||||||
|
is_client: true,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::ServerHello,
|
||||||
|
epoch: cfg.initial_epoch,
|
||||||
|
is_client: false,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::Certificate,
|
||||||
|
epoch: cfg.initial_epoch,
|
||||||
|
is_client: false,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::ServerKeyExchange,
|
||||||
|
epoch: cfg.initial_epoch,
|
||||||
|
is_client: false,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::CertificateRequest,
|
||||||
|
epoch: cfg.initial_epoch,
|
||||||
|
is_client: false,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::ServerHelloDone,
|
||||||
|
epoch: cfg.initial_epoch,
|
||||||
|
is_client: false,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::Certificate,
|
||||||
|
epoch: cfg.initial_epoch,
|
||||||
|
is_client: true,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::ClientKeyExchange,
|
||||||
|
epoch: cfg.initial_epoch,
|
||||||
|
is_client: true,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::CertificateVerify,
|
||||||
|
epoch: cfg.initial_epoch,
|
||||||
|
is_client: true,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::Finished,
|
||||||
|
epoch: cfg.initial_epoch + 1,
|
||||||
|
is_client: true,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
.await;
|
||||||
|
|
||||||
|
{
|
||||||
|
let cipher_suite = state.cipher_suite.lock().await;
|
||||||
|
if let Some(cipher_suite) = &*cipher_suite {
|
||||||
|
let expected_verify_data = match prf_verify_data_server(
|
||||||
|
&state.master_secret,
|
||||||
|
&plain_text,
|
||||||
|
cipher_suite.hash_func(),
|
||||||
|
) {
|
||||||
|
Ok(d) => d,
|
||||||
|
Err(err) => {
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::InsufficientSecurity,
|
||||||
|
}),
|
||||||
|
Some(err),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if expected_verify_data != finished.verify_data {
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::HandshakeFailure,
|
||||||
|
}),
|
||||||
|
Some(Error::ErrVerifyDataMismatch),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Box::new(Flight5 {}))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn generate(
|
||||||
|
&self,
|
||||||
|
state: &mut State,
|
||||||
|
cache: &HandshakeCache,
|
||||||
|
cfg: &HandshakeConfig,
|
||||||
|
) -> Result<Vec<Packet>, (Option<Alert>, Option<Error>)> {
|
||||||
|
let certificate = if !cfg.local_certificates.is_empty() {
|
||||||
|
let cert = match cfg.get_certificate(&cfg.server_name) {
|
||||||
|
Ok(cert) => cert,
|
||||||
|
Err(err) => {
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::HandshakeFailure,
|
||||||
|
}),
|
||||||
|
Some(err),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Some(cert)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut pkts = vec![];
|
||||||
|
|
||||||
|
if state.remote_requested_certificate {
|
||||||
|
pkts.push(Packet {
|
||||||
|
record: RecordLayer::new(
|
||||||
|
PROTOCOL_VERSION1_2,
|
||||||
|
0,
|
||||||
|
Content::Handshake(Handshake::new(HandshakeMessage::Certificate(
|
||||||
|
HandshakeMessageCertificate {
|
||||||
|
certificate: if let Some(cert) = &certificate {
|
||||||
|
cert.certificate
|
||||||
|
.iter()
|
||||||
|
.map(|x| x.as_ref().to_owned())
|
||||||
|
.collect()
|
||||||
|
} else {
|
||||||
|
vec![]
|
||||||
|
},
|
||||||
|
},
|
||||||
|
))),
|
||||||
|
),
|
||||||
|
should_encrypt: false,
|
||||||
|
reset_local_sequence_number: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut client_key_exchange = HandshakeMessageClientKeyExchange {
|
||||||
|
identity_hint: vec![],
|
||||||
|
public_key: vec![],
|
||||||
|
};
|
||||||
|
if cfg.local_psk_callback.is_none() {
|
||||||
|
if let Some(local_keypair) = &state.local_keypair {
|
||||||
|
client_key_exchange
|
||||||
|
.public_key
|
||||||
|
.clone_from(&local_keypair.public_key);
|
||||||
|
}
|
||||||
|
} else if let Some(local_psk_identity_hint) = &cfg.local_psk_identity_hint {
|
||||||
|
client_key_exchange
|
||||||
|
.identity_hint
|
||||||
|
.clone_from(local_psk_identity_hint);
|
||||||
|
}
|
||||||
|
|
||||||
|
pkts.push(Packet {
|
||||||
|
record: RecordLayer::new(
|
||||||
|
PROTOCOL_VERSION1_2,
|
||||||
|
0,
|
||||||
|
Content::Handshake(Handshake::new(HandshakeMessage::ClientKeyExchange(
|
||||||
|
client_key_exchange,
|
||||||
|
))),
|
||||||
|
),
|
||||||
|
should_encrypt: false,
|
||||||
|
reset_local_sequence_number: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
let server_key_exchange_data = cache
|
||||||
|
.pull_and_merge(&[HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::ServerKeyExchange,
|
||||||
|
epoch: cfg.initial_epoch,
|
||||||
|
is_client: false,
|
||||||
|
optional: false,
|
||||||
|
}])
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let mut server_key_exchange = HandshakeMessageServerKeyExchange {
|
||||||
|
identity_hint: vec![],
|
||||||
|
elliptic_curve_type: EllipticCurveType::Unsupported,
|
||||||
|
named_curve: NamedCurve::Unsupported,
|
||||||
|
public_key: vec![],
|
||||||
|
algorithm: SignatureHashAlgorithm {
|
||||||
|
hash: HashAlgorithm::Unsupported,
|
||||||
|
signature: SignatureAlgorithm::Unsupported,
|
||||||
|
},
|
||||||
|
signature: vec![],
|
||||||
|
};
|
||||||
|
|
||||||
|
// handshakeMessageServerKeyExchange is optional for PSK
|
||||||
|
if server_key_exchange_data.is_empty() {
|
||||||
|
if let Err((alert, err)) =
|
||||||
|
handle_server_key_exchange(state, cfg, &server_key_exchange).await
|
||||||
|
{
|
||||||
|
return Err((alert, err));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let mut reader = BufReader::new(server_key_exchange_data.as_slice());
|
||||||
|
let raw_handshake = match Handshake::unmarshal(&mut reader) {
|
||||||
|
Ok(h) => h,
|
||||||
|
Err(err) => {
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::UnexpectedMessage,
|
||||||
|
}),
|
||||||
|
Some(err),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match raw_handshake.handshake_message {
|
||||||
|
HandshakeMessage::ServerKeyExchange(h) => server_key_exchange = h,
|
||||||
|
_ => {
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::UnexpectedMessage,
|
||||||
|
}),
|
||||||
|
Some(Error::ErrInvalidContentType),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append not-yet-sent packets
|
||||||
|
let mut merged = vec![];
|
||||||
|
let mut seq_pred = state.handshake_send_sequence as u16;
|
||||||
|
for p in &mut pkts {
|
||||||
|
let h = match &mut p.record.content {
|
||||||
|
Content::Handshake(h) => h,
|
||||||
|
_ => {
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::InternalError,
|
||||||
|
}),
|
||||||
|
Some(Error::ErrInvalidContentType),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
h.handshake_header.message_sequence = seq_pred;
|
||||||
|
seq_pred += 1;
|
||||||
|
|
||||||
|
let mut raw = vec![];
|
||||||
|
{
|
||||||
|
let mut writer = BufWriter::<&mut Vec<u8>>::new(raw.as_mut());
|
||||||
|
if let Err(err) = h.marshal(&mut writer) {
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::InternalError,
|
||||||
|
}),
|
||||||
|
Some(err),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
merged.extend_from_slice(&raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Err((alert, err)) =
|
||||||
|
initialize_cipher_suite(state, cache, cfg, &server_key_exchange, &merged).await
|
||||||
|
{
|
||||||
|
return Err((alert, err));
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the client has sent a certificate with signing ability, a digitally-signed
|
||||||
|
// CertificateVerify message is sent to explicitly verify possession of the
|
||||||
|
// private key in the certificate.
|
||||||
|
if state.remote_requested_certificate && !cfg.local_certificates.is_empty() {
|
||||||
|
let mut plain_text = cache
|
||||||
|
.pull_and_merge(&[
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::ClientHello,
|
||||||
|
epoch: cfg.initial_epoch,
|
||||||
|
is_client: true,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::ServerHello,
|
||||||
|
epoch: cfg.initial_epoch,
|
||||||
|
is_client: false,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::Certificate,
|
||||||
|
epoch: cfg.initial_epoch,
|
||||||
|
is_client: false,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::ServerKeyExchange,
|
||||||
|
epoch: cfg.initial_epoch,
|
||||||
|
is_client: false,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::CertificateRequest,
|
||||||
|
epoch: cfg.initial_epoch,
|
||||||
|
is_client: false,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::ServerHelloDone,
|
||||||
|
epoch: cfg.initial_epoch,
|
||||||
|
is_client: false,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::Certificate,
|
||||||
|
epoch: cfg.initial_epoch,
|
||||||
|
is_client: true,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::ClientKeyExchange,
|
||||||
|
epoch: cfg.initial_epoch,
|
||||||
|
is_client: true,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
.await;
|
||||||
|
|
||||||
|
plain_text.extend_from_slice(&merged);
|
||||||
|
|
||||||
|
// Find compatible signature scheme
|
||||||
|
let signature_hash_algo = match select_signature_scheme(
|
||||||
|
&cfg.local_signature_schemes,
|
||||||
|
&certificate.as_ref().unwrap().private_key,
|
||||||
|
) {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(err) => {
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::InsufficientSecurity,
|
||||||
|
}),
|
||||||
|
Some(err),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let cert_verify = match generate_certificate_verify(
|
||||||
|
&plain_text,
|
||||||
|
&certificate.as_ref().unwrap().private_key, /*, signature_hash_algo.hash*/
|
||||||
|
) {
|
||||||
|
Ok(cert) => cert,
|
||||||
|
Err(err) => {
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::InternalError,
|
||||||
|
}),
|
||||||
|
Some(err),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
state.local_certificates_verify = cert_verify;
|
||||||
|
|
||||||
|
let mut p = Packet {
|
||||||
|
record: RecordLayer::new(
|
||||||
|
PROTOCOL_VERSION1_2,
|
||||||
|
0,
|
||||||
|
Content::Handshake(Handshake::new(HandshakeMessage::CertificateVerify(
|
||||||
|
HandshakeMessageCertificateVerify {
|
||||||
|
algorithm: signature_hash_algo,
|
||||||
|
signature: state.local_certificates_verify.clone(),
|
||||||
|
},
|
||||||
|
))),
|
||||||
|
),
|
||||||
|
should_encrypt: false,
|
||||||
|
reset_local_sequence_number: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let h = match &mut p.record.content {
|
||||||
|
Content::Handshake(h) => h,
|
||||||
|
_ => {
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::InternalError,
|
||||||
|
}),
|
||||||
|
Some(Error::ErrInvalidContentType),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
h.handshake_header.message_sequence = seq_pred;
|
||||||
|
|
||||||
|
// seqPred++ // this is the last use of seqPred
|
||||||
|
|
||||||
|
let mut raw = vec![];
|
||||||
|
{
|
||||||
|
let mut writer = BufWriter::<&mut Vec<u8>>::new(raw.as_mut());
|
||||||
|
if let Err(err) = h.marshal(&mut writer) {
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::InternalError,
|
||||||
|
}),
|
||||||
|
Some(err),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
merged.extend_from_slice(&raw);
|
||||||
|
|
||||||
|
pkts.push(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
pkts.push(Packet {
|
||||||
|
record: RecordLayer::new(
|
||||||
|
PROTOCOL_VERSION1_2,
|
||||||
|
0,
|
||||||
|
Content::ChangeCipherSpec(ChangeCipherSpec {}),
|
||||||
|
),
|
||||||
|
should_encrypt: false,
|
||||||
|
reset_local_sequence_number: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
if state.local_verify_data.is_empty() {
|
||||||
|
let mut plain_text = cache
|
||||||
|
.pull_and_merge(&[
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::ClientHello,
|
||||||
|
epoch: cfg.initial_epoch,
|
||||||
|
is_client: true,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::ServerHello,
|
||||||
|
epoch: cfg.initial_epoch,
|
||||||
|
is_client: false,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::Certificate,
|
||||||
|
epoch: cfg.initial_epoch,
|
||||||
|
is_client: false,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::ServerKeyExchange,
|
||||||
|
epoch: cfg.initial_epoch,
|
||||||
|
is_client: false,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::CertificateRequest,
|
||||||
|
epoch: cfg.initial_epoch,
|
||||||
|
is_client: false,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::ServerHelloDone,
|
||||||
|
epoch: cfg.initial_epoch,
|
||||||
|
is_client: false,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::Certificate,
|
||||||
|
epoch: cfg.initial_epoch,
|
||||||
|
is_client: true,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::ClientKeyExchange,
|
||||||
|
epoch: cfg.initial_epoch,
|
||||||
|
is_client: true,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::CertificateVerify,
|
||||||
|
epoch: cfg.initial_epoch,
|
||||||
|
is_client: true,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::Finished,
|
||||||
|
epoch: cfg.initial_epoch + 1,
|
||||||
|
is_client: true,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
.await;
|
||||||
|
|
||||||
|
plain_text.extend_from_slice(&merged);
|
||||||
|
|
||||||
|
let cipher_suite = state.cipher_suite.lock().await;
|
||||||
|
if let Some(cipher_suite) = &*cipher_suite {
|
||||||
|
state.local_verify_data = match prf_verify_data_client(
|
||||||
|
&state.master_secret,
|
||||||
|
&plain_text,
|
||||||
|
cipher_suite.hash_func(),
|
||||||
|
) {
|
||||||
|
Ok(data) => data,
|
||||||
|
Err(err) => {
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::InternalError,
|
||||||
|
}),
|
||||||
|
Some(err),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pkts.push(Packet {
|
||||||
|
record: RecordLayer::new(
|
||||||
|
PROTOCOL_VERSION1_2,
|
||||||
|
1,
|
||||||
|
Content::Handshake(Handshake::new(HandshakeMessage::Finished(
|
||||||
|
HandshakeMessageFinished {
|
||||||
|
verify_data: state.local_verify_data.clone(),
|
||||||
|
},
|
||||||
|
))),
|
||||||
|
),
|
||||||
|
should_encrypt: true,
|
||||||
|
reset_local_sequence_number: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(pkts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async fn initialize_cipher_suite(
|
||||||
|
state: &mut State,
|
||||||
|
cache: &HandshakeCache,
|
||||||
|
cfg: &HandshakeConfig,
|
||||||
|
h: &HandshakeMessageServerKeyExchange,
|
||||||
|
sending_plain_text: &[u8],
|
||||||
|
) -> Result<(), (Option<Alert>, Option<Error>)> {
|
||||||
|
let mut cipher_suite = state.cipher_suite.lock().await;
|
||||||
|
|
||||||
|
if let Some(cipher_suite) = &*cipher_suite {
|
||||||
|
if cipher_suite.is_initialized() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut client_random = vec![];
|
||||||
|
{
|
||||||
|
let mut writer = BufWriter::<&mut Vec<u8>>::new(client_random.as_mut());
|
||||||
|
let _ = state.local_random.marshal(&mut writer);
|
||||||
|
}
|
||||||
|
let mut server_random = vec![];
|
||||||
|
{
|
||||||
|
let mut writer = BufWriter::<&mut Vec<u8>>::new(server_random.as_mut());
|
||||||
|
let _ = state.remote_random.marshal(&mut writer);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(cipher_suite) = &*cipher_suite {
|
||||||
|
if state.extended_master_secret {
|
||||||
|
let session_hash = match cache
|
||||||
|
.session_hash(
|
||||||
|
cipher_suite.hash_func(),
|
||||||
|
cfg.initial_epoch,
|
||||||
|
sending_plain_text,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(err) => {
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::InternalError,
|
||||||
|
}),
|
||||||
|
Some(err),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
state.master_secret = match prf_extended_master_secret(
|
||||||
|
&state.pre_master_secret,
|
||||||
|
&session_hash,
|
||||||
|
cipher_suite.hash_func(),
|
||||||
|
) {
|
||||||
|
Ok(m) => m,
|
||||||
|
Err(err) => {
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::IllegalParameter,
|
||||||
|
}),
|
||||||
|
Some(err),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
state.master_secret = match prf_master_secret(
|
||||||
|
&state.pre_master_secret,
|
||||||
|
&client_random,
|
||||||
|
&server_random,
|
||||||
|
cipher_suite.hash_func(),
|
||||||
|
) {
|
||||||
|
Ok(m) => m,
|
||||||
|
Err(err) => {
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::InternalError,
|
||||||
|
}),
|
||||||
|
Some(err),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.local_psk_callback.is_none() {
|
||||||
|
// Verify that the pair of hash algorithm and signiture is listed.
|
||||||
|
let mut valid_signature_scheme = false;
|
||||||
|
for ss in &cfg.local_signature_schemes {
|
||||||
|
if ss.hash == h.algorithm.hash && ss.signature == h.algorithm.signature {
|
||||||
|
valid_signature_scheme = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !valid_signature_scheme {
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::InsufficientSecurity,
|
||||||
|
}),
|
||||||
|
Some(Error::ErrNoAvailableSignatureSchemes),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let expected_msg =
|
||||||
|
value_key_message(&client_random, &server_random, &h.public_key, h.named_curve);
|
||||||
|
if let Err(err) = verify_key_signature(
|
||||||
|
&expected_msg,
|
||||||
|
&h.algorithm,
|
||||||
|
&h.signature,
|
||||||
|
&state.peer_certificates,
|
||||||
|
cfg.insecure_verification,
|
||||||
|
) {
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::BadCertificate,
|
||||||
|
}),
|
||||||
|
Some(err),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut chains = vec![];
|
||||||
|
if !cfg.insecure_skip_verify {
|
||||||
|
chains = match verify_server_cert(
|
||||||
|
&state.peer_certificates,
|
||||||
|
&cfg.server_cert_verifier,
|
||||||
|
&cfg.server_name,
|
||||||
|
) {
|
||||||
|
Ok(chains) => chains,
|
||||||
|
Err(err) => {
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::BadCertificate,
|
||||||
|
}),
|
||||||
|
Some(err),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(verify_peer_certificate) = &cfg.verify_peer_certificate {
|
||||||
|
if let Err(err) = verify_peer_certificate(&state.peer_certificates, &chains) {
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::BadCertificate,
|
||||||
|
}),
|
||||||
|
Some(err),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(cipher_suite) = &mut *cipher_suite {
|
||||||
|
if let Err(err) =
|
||||||
|
cipher_suite.init(&state.master_secret, &client_random, &server_random, true)
|
||||||
|
{
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::InternalError,
|
||||||
|
}),
|
||||||
|
Some(err),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
190
libs/dtls/src/flight/flight6.rs
Normal file
190
libs/dtls/src/flight/flight6.rs
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
use async_trait::async_trait;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::change_cipher_spec::*;
|
||||||
|
use crate::content::*;
|
||||||
|
use crate::handshake::handshake_message_finished::*;
|
||||||
|
use crate::handshake::*;
|
||||||
|
use crate::prf::*;
|
||||||
|
use crate::record_layer::record_layer_header::*;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub(crate) struct Flight6;
|
||||||
|
|
||||||
|
impl fmt::Display for Flight6 {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "Flight 6")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Flight for Flight6 {
|
||||||
|
fn is_last_send_flight(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn parse(
|
||||||
|
&self,
|
||||||
|
_tx: &mut mpsc::Sender<mpsc::Sender<()>>,
|
||||||
|
state: &mut State,
|
||||||
|
cache: &HandshakeCache,
|
||||||
|
cfg: &HandshakeConfig,
|
||||||
|
) -> Result<Box<dyn Flight + Send + Sync>, (Option<Alert>, Option<Error>)> {
|
||||||
|
let (_, msgs) = match cache
|
||||||
|
.full_pull_map(
|
||||||
|
state.handshake_recv_sequence - 1,
|
||||||
|
&[HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::Finished,
|
||||||
|
epoch: cfg.initial_epoch + 1,
|
||||||
|
is_client: true,
|
||||||
|
optional: false,
|
||||||
|
}],
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok((seq, msgs)) => (seq, msgs),
|
||||||
|
// No valid message received. Keep reading
|
||||||
|
Err(_) => return Err((None, None)),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(message) = msgs.get(&HandshakeType::Finished) {
|
||||||
|
match message {
|
||||||
|
HandshakeMessage::Finished(_) => {}
|
||||||
|
_ => {
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::InternalError,
|
||||||
|
}),
|
||||||
|
None,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Other party retransmitted the last flight.
|
||||||
|
Ok(Box::new(Flight6 {}))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn generate(
|
||||||
|
&self,
|
||||||
|
state: &mut State,
|
||||||
|
cache: &HandshakeCache,
|
||||||
|
cfg: &HandshakeConfig,
|
||||||
|
) -> Result<Vec<Packet>, (Option<Alert>, Option<Error>)> {
|
||||||
|
let mut pkts = vec![Packet {
|
||||||
|
record: RecordLayer::new(
|
||||||
|
PROTOCOL_VERSION1_2,
|
||||||
|
0,
|
||||||
|
Content::ChangeCipherSpec(ChangeCipherSpec {}),
|
||||||
|
),
|
||||||
|
should_encrypt: false,
|
||||||
|
reset_local_sequence_number: false,
|
||||||
|
}];
|
||||||
|
|
||||||
|
if state.local_verify_data.is_empty() {
|
||||||
|
let plain_text = cache
|
||||||
|
.pull_and_merge(&[
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::ClientHello,
|
||||||
|
epoch: cfg.initial_epoch,
|
||||||
|
is_client: true,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::ServerHello,
|
||||||
|
epoch: cfg.initial_epoch,
|
||||||
|
is_client: false,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::Certificate,
|
||||||
|
epoch: cfg.initial_epoch,
|
||||||
|
is_client: false,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::ServerKeyExchange,
|
||||||
|
epoch: cfg.initial_epoch,
|
||||||
|
is_client: false,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::CertificateRequest,
|
||||||
|
epoch: cfg.initial_epoch,
|
||||||
|
is_client: false,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::ServerHelloDone,
|
||||||
|
epoch: cfg.initial_epoch,
|
||||||
|
is_client: false,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::Certificate,
|
||||||
|
epoch: cfg.initial_epoch,
|
||||||
|
is_client: true,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::ClientKeyExchange,
|
||||||
|
epoch: cfg.initial_epoch,
|
||||||
|
is_client: true,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::CertificateVerify,
|
||||||
|
epoch: cfg.initial_epoch,
|
||||||
|
is_client: true,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::Finished,
|
||||||
|
epoch: cfg.initial_epoch + 1,
|
||||||
|
is_client: true,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let cipher_suite = state.cipher_suite.lock().await;
|
||||||
|
if let Some(cipher_suite) = &*cipher_suite {
|
||||||
|
state.local_verify_data = match prf_verify_data_server(
|
||||||
|
&state.master_secret,
|
||||||
|
&plain_text,
|
||||||
|
cipher_suite.hash_func(),
|
||||||
|
) {
|
||||||
|
Ok(data) => data,
|
||||||
|
Err(err) => {
|
||||||
|
return Err((
|
||||||
|
Some(Alert {
|
||||||
|
alert_level: AlertLevel::Fatal,
|
||||||
|
alert_description: AlertDescription::InternalError,
|
||||||
|
}),
|
||||||
|
Some(err),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pkts.push(Packet {
|
||||||
|
record: RecordLayer::new(
|
||||||
|
PROTOCOL_VERSION1_2,
|
||||||
|
1,
|
||||||
|
Content::Handshake(Handshake::new(HandshakeMessage::Finished(
|
||||||
|
HandshakeMessageFinished {
|
||||||
|
verify_data: state.local_verify_data.clone(),
|
||||||
|
},
|
||||||
|
))),
|
||||||
|
),
|
||||||
|
should_encrypt: true,
|
||||||
|
reset_local_sequence_number: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(pkts)
|
||||||
|
}
|
||||||
|
}
|
||||||
87
libs/dtls/src/flight/mod.rs
Normal file
87
libs/dtls/src/flight/mod.rs
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
pub(crate) mod flight0;
|
||||||
|
pub(crate) mod flight1;
|
||||||
|
pub(crate) mod flight2;
|
||||||
|
pub(crate) mod flight3;
|
||||||
|
pub(crate) mod flight4;
|
||||||
|
pub(crate) mod flight5;
|
||||||
|
pub(crate) mod flight6;
|
||||||
|
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
|
use crate::alert::*;
|
||||||
|
use crate::error::Error;
|
||||||
|
use crate::handshake::handshake_cache::*;
|
||||||
|
use crate::handshaker::*;
|
||||||
|
use crate::record_layer::*;
|
||||||
|
use crate::state::*;
|
||||||
|
|
||||||
|
/*
|
||||||
|
DTLS messages are grouped into a series of message flights, according
|
||||||
|
to the diagrams below. Although each Flight of messages may consist
|
||||||
|
of a number of messages, they should be viewed as monolithic for the
|
||||||
|
purpose of timeout and retransmission.
|
||||||
|
https://tools.ietf.org/html/rfc4347#section-4.2.4
|
||||||
|
Client Server
|
||||||
|
------ ------
|
||||||
|
Waiting Flight 0
|
||||||
|
|
||||||
|
ClientHello --------> Flight 1
|
||||||
|
|
||||||
|
<------- HelloVerifyRequest Flight 2
|
||||||
|
|
||||||
|
ClientHello --------> Flight 3
|
||||||
|
|
||||||
|
ServerHello \
|
||||||
|
Certificate* \
|
||||||
|
ServerKeyExchange* Flight 4
|
||||||
|
CertificateRequest* /
|
||||||
|
<-------- ServerHelloDone /
|
||||||
|
|
||||||
|
Certificate* \
|
||||||
|
ClientKeyExchange \
|
||||||
|
CertificateVerify* Flight 5
|
||||||
|
[ChangeCipherSpec] /
|
||||||
|
Finished --------> /
|
||||||
|
|
||||||
|
[ChangeCipherSpec] \ Flight 6
|
||||||
|
<-------- Finished /
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub(crate) struct Packet {
|
||||||
|
pub(crate) record: RecordLayer,
|
||||||
|
pub(crate) should_encrypt: bool,
|
||||||
|
pub(crate) reset_local_sequence_number: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
pub(crate) trait Flight: fmt::Display + fmt::Debug {
|
||||||
|
fn is_last_send_flight(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
fn is_last_recv_flight(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
fn has_retransmit(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn parse(
|
||||||
|
&self,
|
||||||
|
tx: &mut mpsc::Sender<mpsc::Sender<()>>,
|
||||||
|
state: &mut State,
|
||||||
|
cache: &HandshakeCache,
|
||||||
|
cfg: &HandshakeConfig,
|
||||||
|
) -> Result<Box<dyn Flight + Send + Sync>, (Option<Alert>, Option<Error>)>;
|
||||||
|
|
||||||
|
async fn generate(
|
||||||
|
&self,
|
||||||
|
state: &mut State,
|
||||||
|
cache: &HandshakeCache,
|
||||||
|
cfg: &HandshakeConfig,
|
||||||
|
) -> Result<Vec<Packet>, (Option<Alert>, Option<Error>)>;
|
||||||
|
}
|
||||||
174
libs/dtls/src/fragment_buffer/fragment_buffer_test.rs
Normal file
174
libs/dtls/src/fragment_buffer/fragment_buffer_test.rs
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_fragment_buffer() -> Result<()> {
|
||||||
|
let tests = vec![
|
||||||
|
(
|
||||||
|
"Single Fragment",
|
||||||
|
vec![vec![
|
||||||
|
0x16, 0xfe, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x03,
|
||||||
|
0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xfe, 0xff, 0x00,
|
||||||
|
]],
|
||||||
|
vec![vec![
|
||||||
|
0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xfe, 0xff,
|
||||||
|
0x00,
|
||||||
|
]],
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Single Fragment Epoch 3",
|
||||||
|
vec![vec![
|
||||||
|
0x16, 0xfe, 0xff, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x03,
|
||||||
|
0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xfe, 0xff, 0x00,
|
||||||
|
]],
|
||||||
|
vec![vec![
|
||||||
|
0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xfe, 0xff,
|
||||||
|
0x00,
|
||||||
|
]],
|
||||||
|
3,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Multiple Fragments",
|
||||||
|
vec![
|
||||||
|
vec![
|
||||||
|
0x16, 0xfe, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x81,
|
||||||
|
0x0b, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00,
|
||||||
|
0x01, 0x02, 0x03, 0x04,
|
||||||
|
],
|
||||||
|
vec![
|
||||||
|
0x16, 0xfe, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x81,
|
||||||
|
0x0b, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x05, 0x05,
|
||||||
|
0x06, 0x07, 0x08, 0x09,
|
||||||
|
],
|
||||||
|
vec![
|
||||||
|
0x16, 0xfe, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x81,
|
||||||
|
0x0b, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x05, 0x0A,
|
||||||
|
0x0B, 0x0C, 0x0D, 0x0E,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
vec![vec![
|
||||||
|
0x0b, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x01,
|
||||||
|
0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
|
||||||
|
]],
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Multiple Unordered Fragments",
|
||||||
|
vec![
|
||||||
|
vec![
|
||||||
|
0x16, 0xfe, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x81,
|
||||||
|
0x0b, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00,
|
||||||
|
0x01, 0x02, 0x03, 0x04,
|
||||||
|
],
|
||||||
|
vec![
|
||||||
|
0x16, 0xfe, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x81,
|
||||||
|
0x0b, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x05, 0x0A,
|
||||||
|
0x0B, 0x0C, 0x0D, 0x0E,
|
||||||
|
],
|
||||||
|
vec![
|
||||||
|
0x16, 0xfe, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x81,
|
||||||
|
0x0b, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x05, 0x05,
|
||||||
|
0x06, 0x07, 0x08, 0x09,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
vec![vec![
|
||||||
|
0x0b, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x01,
|
||||||
|
0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
|
||||||
|
]],
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Multiple Handshakes in Single Fragment",
|
||||||
|
vec![vec![
|
||||||
|
0x16, 0xfe, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
|
||||||
|
0x30, /* record header */
|
||||||
|
0x03, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xfe, 0xff,
|
||||||
|
0x01, 0x01, /*handshake msg 1*/
|
||||||
|
0x03, 0x00, 0x00, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xfe, 0xff,
|
||||||
|
0x01, 0x01, /*handshake msg 2*/
|
||||||
|
0x03, 0x00, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xfe, 0xff,
|
||||||
|
0x01, 0x01, /*handshake msg 3*/
|
||||||
|
]],
|
||||||
|
vec![
|
||||||
|
vec![
|
||||||
|
0x03, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xfe,
|
||||||
|
0xff, 0x01, 0x01,
|
||||||
|
],
|
||||||
|
vec![
|
||||||
|
0x03, 0x00, 0x00, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xfe,
|
||||||
|
0xff, 0x01, 0x01,
|
||||||
|
],
|
||||||
|
vec![
|
||||||
|
0x03, 0x00, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xfe,
|
||||||
|
0xff, 0x01, 0x01,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
// Ensure zero length fragments don't cause an infinite recursive loop which in turn causes
|
||||||
|
// a stack overflow.
|
||||||
|
(
|
||||||
|
"Zero length fragment",
|
||||||
|
vec![vec![
|
||||||
|
0x16, 0xfe, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00,
|
||||||
|
0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
]],
|
||||||
|
vec![vec![
|
||||||
|
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
|
||||||
|
]],
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (name, inputs, expects, expected_epoch) in tests {
|
||||||
|
let mut fragment_buffer = FragmentBuffer::new();
|
||||||
|
for frag in inputs {
|
||||||
|
let status = fragment_buffer.push(&frag)?;
|
||||||
|
assert!(
|
||||||
|
status,
|
||||||
|
"fragment_buffer didn't accept fragments for '{name}'"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
for expected in expects {
|
||||||
|
let (out, epoch) = fragment_buffer.pop()?;
|
||||||
|
assert_eq!(
|
||||||
|
out, expected,
|
||||||
|
"fragment_buffer '{name}' push/pop: got {out:?}, want {expected:?}"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
epoch, expected_epoch,
|
||||||
|
"fragment_buffer returned wrong epoch: got {epoch}, want {expected_epoch}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = fragment_buffer.pop();
|
||||||
|
assert!(
|
||||||
|
result.is_err(),
|
||||||
|
"fragment_buffer popped single buffer multiple times for '{name}'"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_fragment_buffer_overflow() -> Result<()> {
|
||||||
|
let mut fragment_buffer = FragmentBuffer::new();
|
||||||
|
|
||||||
|
fragment_buffer.push(&[
|
||||||
|
0x16, 0xfe, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x03, 0x00,
|
||||||
|
0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xfe, 0xff, 0x00,
|
||||||
|
])?;
|
||||||
|
|
||||||
|
let big_buffer = vec![0; 2_000_000];
|
||||||
|
let result = fragment_buffer.push(&big_buffer);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
result.is_err(),
|
||||||
|
"Pushing a buffer of size 2MB should have caused FragmentBuffer::push to return an error"
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
160
libs/dtls/src/fragment_buffer/mod.rs
Normal file
160
libs/dtls/src/fragment_buffer/mod.rs
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod fragment_buffer_test;
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::io::{BufWriter, Cursor};
|
||||||
|
|
||||||
|
use crate::content::*;
|
||||||
|
use crate::error::*;
|
||||||
|
use crate::handshake::handshake_header::*;
|
||||||
|
use crate::record_layer::record_layer_header::*;
|
||||||
|
|
||||||
|
// 2 mb max buffer size
|
||||||
|
const FRAGMENT_BUFFER_MAX_SIZE: usize = 2_000_000;
|
||||||
|
|
||||||
|
pub(crate) struct Fragment {
|
||||||
|
record_layer_header: RecordLayerHeader,
|
||||||
|
handshake_header: HandshakeHeader,
|
||||||
|
data: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct FragmentBuffer {
|
||||||
|
// map of MessageSequenceNumbers that hold slices of fragments
|
||||||
|
cache: HashMap<u16, Vec<Fragment>>,
|
||||||
|
|
||||||
|
current_message_sequence_number: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FragmentBuffer {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
FragmentBuffer {
|
||||||
|
cache: HashMap::new(),
|
||||||
|
current_message_sequence_number: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempts to push a DTLS packet to the FragmentBuffer
|
||||||
|
// when it returns true it means the FragmentBuffer has inserted and the buffer shouldn't be handled
|
||||||
|
// when an error returns it is fatal, and the DTLS connection should be stopped
|
||||||
|
pub fn push(&mut self, mut buf: &[u8]) -> Result<bool> {
|
||||||
|
let current_size = self.size();
|
||||||
|
if current_size + buf.len() >= FRAGMENT_BUFFER_MAX_SIZE {
|
||||||
|
return Err(Error::ErrFragmentBufferOverflow {
|
||||||
|
new_size: current_size + buf.len(),
|
||||||
|
max_size: FRAGMENT_BUFFER_MAX_SIZE,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut reader = Cursor::new(buf);
|
||||||
|
let record_layer_header = RecordLayerHeader::unmarshal(&mut reader)?;
|
||||||
|
|
||||||
|
// Fragment isn't a handshake, we don't need to handle it
|
||||||
|
if record_layer_header.content_type != ContentType::Handshake {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
buf = &buf[RECORD_LAYER_HEADER_SIZE..];
|
||||||
|
while !buf.is_empty() {
|
||||||
|
let mut reader = Cursor::new(buf);
|
||||||
|
let handshake_header = HandshakeHeader::unmarshal(&mut reader)?;
|
||||||
|
|
||||||
|
self.cache
|
||||||
|
.entry(handshake_header.message_sequence)
|
||||||
|
.or_default();
|
||||||
|
|
||||||
|
// end index should be the length of handshake header but if the handshake
|
||||||
|
// was fragmented, we should keep them all
|
||||||
|
let mut end = HANDSHAKE_HEADER_LENGTH + handshake_header.length as usize;
|
||||||
|
if end > buf.len() {
|
||||||
|
end = buf.len();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Discard all headers, when rebuilding the packet we will re-build
|
||||||
|
let data = buf[HANDSHAKE_HEADER_LENGTH..end].to_vec();
|
||||||
|
|
||||||
|
if let Some(x) = self.cache.get_mut(&handshake_header.message_sequence) {
|
||||||
|
x.push(Fragment {
|
||||||
|
record_layer_header,
|
||||||
|
handshake_header,
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
buf = &buf[end..];
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pop(&mut self) -> Result<(Vec<u8>, u16)> {
|
||||||
|
let seq_num = self.current_message_sequence_number;
|
||||||
|
if !self.cache.contains_key(&seq_num) {
|
||||||
|
return Err(Error::ErrEmptyFragment);
|
||||||
|
}
|
||||||
|
|
||||||
|
let (content, epoch) = if let Some(frags) = self.cache.get_mut(&seq_num) {
|
||||||
|
let mut raw_message = vec![];
|
||||||
|
// Recursively collect up
|
||||||
|
if !append_message(0, frags, &mut raw_message) {
|
||||||
|
return Err(Error::ErrEmptyFragment);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut first_header = frags[0].handshake_header;
|
||||||
|
first_header.fragment_offset = 0;
|
||||||
|
first_header.fragment_length = first_header.length;
|
||||||
|
|
||||||
|
let mut raw_header = vec![];
|
||||||
|
{
|
||||||
|
let mut writer = BufWriter::<&mut Vec<u8>>::new(raw_header.as_mut());
|
||||||
|
if first_header.marshal(&mut writer).is_err() {
|
||||||
|
return Err(Error::ErrEmptyFragment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let message_epoch = frags[0].record_layer_header.epoch;
|
||||||
|
|
||||||
|
raw_header.extend_from_slice(&raw_message);
|
||||||
|
|
||||||
|
(raw_header, message_epoch)
|
||||||
|
} else {
|
||||||
|
return Err(Error::ErrEmptyFragment);
|
||||||
|
};
|
||||||
|
|
||||||
|
self.cache.remove(&seq_num);
|
||||||
|
self.current_message_sequence_number += 1;
|
||||||
|
|
||||||
|
Ok((content, epoch))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size(&self) -> usize {
|
||||||
|
self.cache
|
||||||
|
.values()
|
||||||
|
.map(|fragment| fragment.iter().map(|f| f.data.len()).sum::<usize>())
|
||||||
|
.sum()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn append_message(target_offset: u32, frags: &[Fragment], raw_message: &mut Vec<u8>) -> bool {
|
||||||
|
for f in frags {
|
||||||
|
if f.handshake_header.fragment_offset == target_offset {
|
||||||
|
let fragment_end =
|
||||||
|
f.handshake_header.fragment_offset + f.handshake_header.fragment_length;
|
||||||
|
|
||||||
|
// NB: Order here is important, the `f.handshake_header.fragment_length != 0`
|
||||||
|
// MUST come before the recursive call.
|
||||||
|
if fragment_end != f.handshake_header.length
|
||||||
|
&& f.handshake_header.fragment_length != 0
|
||||||
|
&& !append_message(fragment_end, frags, raw_message)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut message = vec![];
|
||||||
|
message.extend_from_slice(&f.data);
|
||||||
|
message.extend_from_slice(raw_message);
|
||||||
|
*raw_message = message;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
241
libs/dtls/src/handshake/handshake_cache.rs
Normal file
241
libs/dtls/src/handshake/handshake_cache.rs
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod handshake_cache_test;
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::io::BufReader;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use sha2::{Digest, Sha256};
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
use crate::cipher_suite::*;
|
||||||
|
use crate::handshake::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub(crate) struct HandshakeCacheItem {
|
||||||
|
typ: HandshakeType,
|
||||||
|
is_client: bool,
|
||||||
|
epoch: u16,
|
||||||
|
message_sequence: u16,
|
||||||
|
data: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
pub(crate) struct HandshakeCachePullRule {
|
||||||
|
pub(crate) typ: HandshakeType,
|
||||||
|
pub(crate) epoch: u16,
|
||||||
|
pub(crate) is_client: bool,
|
||||||
|
pub(crate) optional: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub(crate) struct HandshakeCache {
|
||||||
|
cache: Arc<Mutex<Vec<HandshakeCacheItem>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HandshakeCache {
|
||||||
|
pub(crate) fn new() -> Self {
|
||||||
|
HandshakeCache {
|
||||||
|
cache: Arc::new(Mutex::new(vec![])),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn push(
|
||||||
|
&mut self,
|
||||||
|
data: Vec<u8>,
|
||||||
|
epoch: u16,
|
||||||
|
message_sequence: u16,
|
||||||
|
typ: HandshakeType,
|
||||||
|
is_client: bool,
|
||||||
|
) -> bool {
|
||||||
|
let mut cache = self.cache.lock().await;
|
||||||
|
|
||||||
|
for i in &*cache {
|
||||||
|
if i.message_sequence == message_sequence && i.is_client == is_client {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cache.push(HandshakeCacheItem {
|
||||||
|
typ,
|
||||||
|
is_client,
|
||||||
|
epoch,
|
||||||
|
message_sequence,
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns a list handshakes that match the requested rules
|
||||||
|
// the list will contain null entries for rules that can't be satisfied
|
||||||
|
// multiple entries may match a rule, but only the last match is returned (ie ClientHello with cookies)
|
||||||
|
pub(crate) async fn pull(&self, rules: &[HandshakeCachePullRule]) -> Vec<HandshakeCacheItem> {
|
||||||
|
let cache = self.cache.lock().await;
|
||||||
|
|
||||||
|
let mut out = vec![];
|
||||||
|
for r in rules {
|
||||||
|
let mut item: Option<HandshakeCacheItem> = None;
|
||||||
|
for c in &*cache {
|
||||||
|
if c.typ == r.typ && c.is_client == r.is_client && c.epoch == r.epoch {
|
||||||
|
if let Some(x) = &item {
|
||||||
|
if x.message_sequence < c.message_sequence {
|
||||||
|
item = Some(c.clone());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
item = Some(c.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(c) = item {
|
||||||
|
out.push(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
// full_pull_map pulls all handshakes between rules[0] to rules[len(rules)-1] as map.
|
||||||
|
pub(crate) async fn full_pull_map(
|
||||||
|
&self,
|
||||||
|
start_seq: isize,
|
||||||
|
rules: &[HandshakeCachePullRule],
|
||||||
|
) -> Result<(isize, HashMap<HandshakeType, HandshakeMessage>)> {
|
||||||
|
let cache = self.cache.lock().await;
|
||||||
|
|
||||||
|
let mut ci = HashMap::new();
|
||||||
|
for r in rules {
|
||||||
|
let mut item: Option<HandshakeCacheItem> = None;
|
||||||
|
for c in &*cache {
|
||||||
|
if c.typ == r.typ && c.is_client == r.is_client && c.epoch == r.epoch {
|
||||||
|
if let Some(x) = &item {
|
||||||
|
if x.message_sequence < c.message_sequence {
|
||||||
|
item = Some(c.clone());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
item = Some(c.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !r.optional && item.is_none() {
|
||||||
|
// Missing mandatory message.
|
||||||
|
return Err(Error::Other("Missing mandatory message".to_owned()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(c) = item {
|
||||||
|
ci.insert(r.typ, c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut out = HashMap::new();
|
||||||
|
let mut seq = start_seq;
|
||||||
|
for r in rules {
|
||||||
|
let t = r.typ;
|
||||||
|
if let Some(i) = ci.get(&t) {
|
||||||
|
let mut reader = BufReader::new(i.data.as_slice());
|
||||||
|
let raw_handshake = Handshake::unmarshal(&mut reader)?;
|
||||||
|
if seq as u16 != raw_handshake.handshake_header.message_sequence {
|
||||||
|
// There is a gap. Some messages are not arrived.
|
||||||
|
return Err(Error::Other(
|
||||||
|
"There is a gap. Some messages are not arrived.".to_owned(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
seq += 1;
|
||||||
|
out.insert(t, raw_handshake.handshake_message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((seq, out))
|
||||||
|
}
|
||||||
|
|
||||||
|
// pull_and_merge calls pull and then merges the results, ignoring any null entries
|
||||||
|
pub(crate) async fn pull_and_merge(&self, rules: &[HandshakeCachePullRule]) -> Vec<u8> {
|
||||||
|
let mut merged = vec![];
|
||||||
|
|
||||||
|
for p in &self.pull(rules).await {
|
||||||
|
merged.extend_from_slice(&p.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
merged
|
||||||
|
}
|
||||||
|
|
||||||
|
// session_hash returns the session hash for Extended Master Secret support
|
||||||
|
// https://tools.ietf.org/html/draft-ietf-tls-session-hash-06#section-4
|
||||||
|
pub(crate) async fn session_hash(
|
||||||
|
&self,
|
||||||
|
hf: CipherSuiteHash,
|
||||||
|
epoch: u16,
|
||||||
|
additional: &[u8],
|
||||||
|
) -> Result<Vec<u8>> {
|
||||||
|
let mut merged = vec![];
|
||||||
|
|
||||||
|
// Order defined by https://tools.ietf.org/html/rfc5246#section-7.3
|
||||||
|
let handshake_buffer = self
|
||||||
|
.pull(&[
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::ClientHello,
|
||||||
|
epoch,
|
||||||
|
is_client: true,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::ServerHello,
|
||||||
|
epoch,
|
||||||
|
is_client: false,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::Certificate,
|
||||||
|
epoch,
|
||||||
|
is_client: false,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::ServerKeyExchange,
|
||||||
|
epoch,
|
||||||
|
is_client: false,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::CertificateRequest,
|
||||||
|
epoch,
|
||||||
|
is_client: false,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::ServerHelloDone,
|
||||||
|
epoch,
|
||||||
|
is_client: false,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::Certificate,
|
||||||
|
epoch,
|
||||||
|
is_client: true,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: HandshakeType::ClientKeyExchange,
|
||||||
|
epoch,
|
||||||
|
is_client: true,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
.await;
|
||||||
|
|
||||||
|
for p in &handshake_buffer {
|
||||||
|
merged.extend_from_slice(&p.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
merged.extend_from_slice(additional);
|
||||||
|
|
||||||
|
let mut hasher = match hf {
|
||||||
|
CipherSuiteHash::Sha256 => Sha256::new(),
|
||||||
|
};
|
||||||
|
hasher.update(&merged);
|
||||||
|
let result = hasher.finalize();
|
||||||
|
|
||||||
|
Ok(result.as_slice().to_vec())
|
||||||
|
}
|
||||||
|
}
|
||||||
658
libs/dtls/src/handshake/handshake_cache/handshake_cache_test.rs
Normal file
658
libs/dtls/src/handshake/handshake_cache/handshake_cache_test.rs
Normal file
@@ -0,0 +1,658 @@
|
|||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handshake_cache_single_push() -> Result<()> {
|
||||||
|
let tests = vec![
|
||||||
|
(
|
||||||
|
"Single Push",
|
||||||
|
vec![HandshakeCacheItem {
|
||||||
|
typ: 0.into(),
|
||||||
|
is_client: true,
|
||||||
|
epoch: 0,
|
||||||
|
message_sequence: 0,
|
||||||
|
data: vec![0x00],
|
||||||
|
}],
|
||||||
|
vec![HandshakeCachePullRule {
|
||||||
|
typ: 0.into(),
|
||||||
|
epoch: 0,
|
||||||
|
is_client: true,
|
||||||
|
optional: false,
|
||||||
|
}],
|
||||||
|
vec![0x00],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Multi Push",
|
||||||
|
vec![
|
||||||
|
HandshakeCacheItem {
|
||||||
|
typ: 0.into(),
|
||||||
|
is_client: true,
|
||||||
|
epoch: 0,
|
||||||
|
message_sequence: 0,
|
||||||
|
data: vec![0x00],
|
||||||
|
},
|
||||||
|
HandshakeCacheItem {
|
||||||
|
typ: 1.into(),
|
||||||
|
is_client: true,
|
||||||
|
epoch: 0,
|
||||||
|
message_sequence: 1,
|
||||||
|
data: vec![0x01],
|
||||||
|
},
|
||||||
|
HandshakeCacheItem {
|
||||||
|
typ: 2.into(),
|
||||||
|
is_client: true,
|
||||||
|
epoch: 0,
|
||||||
|
message_sequence: 2,
|
||||||
|
data: vec![0x02],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
vec![
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: 0.into(),
|
||||||
|
epoch: 0,
|
||||||
|
is_client: true,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: 1.into(),
|
||||||
|
epoch: 0,
|
||||||
|
is_client: true,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: 2.into(),
|
||||||
|
epoch: 0,
|
||||||
|
is_client: true,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
vec![0x00, 0x01, 0x02],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Multi Push, Rules set order",
|
||||||
|
vec![
|
||||||
|
HandshakeCacheItem {
|
||||||
|
typ: 2.into(),
|
||||||
|
is_client: true,
|
||||||
|
epoch: 0,
|
||||||
|
message_sequence: 2,
|
||||||
|
data: vec![0x02],
|
||||||
|
},
|
||||||
|
HandshakeCacheItem {
|
||||||
|
typ: 0.into(),
|
||||||
|
is_client: true,
|
||||||
|
epoch: 0,
|
||||||
|
message_sequence: 0,
|
||||||
|
data: vec![0x00],
|
||||||
|
},
|
||||||
|
HandshakeCacheItem {
|
||||||
|
typ: 1.into(),
|
||||||
|
is_client: true,
|
||||||
|
epoch: 0,
|
||||||
|
message_sequence: 1,
|
||||||
|
data: vec![0x01],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
vec![
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: 0.into(),
|
||||||
|
epoch: 0,
|
||||||
|
is_client: true,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: 1.into(),
|
||||||
|
epoch: 0,
|
||||||
|
is_client: true,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: 2.into(),
|
||||||
|
epoch: 0,
|
||||||
|
is_client: true,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
vec![0x00, 0x01, 0x02],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Multi Push, Dupe Seqnum",
|
||||||
|
vec![
|
||||||
|
HandshakeCacheItem {
|
||||||
|
typ: 0.into(),
|
||||||
|
is_client: true,
|
||||||
|
epoch: 0,
|
||||||
|
message_sequence: 0,
|
||||||
|
data: vec![0x00],
|
||||||
|
},
|
||||||
|
HandshakeCacheItem {
|
||||||
|
typ: 1.into(),
|
||||||
|
is_client: true,
|
||||||
|
epoch: 0,
|
||||||
|
message_sequence: 1,
|
||||||
|
data: vec![0x01],
|
||||||
|
},
|
||||||
|
HandshakeCacheItem {
|
||||||
|
typ: 1.into(),
|
||||||
|
is_client: true,
|
||||||
|
epoch: 0,
|
||||||
|
message_sequence: 1,
|
||||||
|
data: vec![0x01],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
vec![
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: 0.into(),
|
||||||
|
epoch: 0,
|
||||||
|
is_client: true,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: 1.into(),
|
||||||
|
epoch: 0,
|
||||||
|
is_client: true,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
vec![0x00, 0x01],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Multi Push, Dupe Seqnum Client/Server",
|
||||||
|
vec![
|
||||||
|
HandshakeCacheItem {
|
||||||
|
typ: 0.into(),
|
||||||
|
is_client: true,
|
||||||
|
epoch: 0,
|
||||||
|
message_sequence: 0,
|
||||||
|
data: vec![0x00],
|
||||||
|
},
|
||||||
|
HandshakeCacheItem {
|
||||||
|
typ: 1.into(),
|
||||||
|
is_client: true,
|
||||||
|
epoch: 0,
|
||||||
|
message_sequence: 1,
|
||||||
|
data: vec![0x01],
|
||||||
|
},
|
||||||
|
HandshakeCacheItem {
|
||||||
|
typ: 1.into(),
|
||||||
|
is_client: false,
|
||||||
|
epoch: 0,
|
||||||
|
message_sequence: 1,
|
||||||
|
data: vec![0x02],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
vec![
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: 0.into(),
|
||||||
|
epoch: 0,
|
||||||
|
is_client: true,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: 1.into(),
|
||||||
|
epoch: 0,
|
||||||
|
is_client: true,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: 1.into(),
|
||||||
|
epoch: 0,
|
||||||
|
is_client: false,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
vec![0x00, 0x01, 0x02],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Multi Push, Dupe Seqnum with Unique HandshakeType",
|
||||||
|
vec![
|
||||||
|
HandshakeCacheItem {
|
||||||
|
typ: 1.into(),
|
||||||
|
is_client: true,
|
||||||
|
epoch: 0,
|
||||||
|
message_sequence: 0,
|
||||||
|
data: vec![0x00],
|
||||||
|
},
|
||||||
|
HandshakeCacheItem {
|
||||||
|
typ: 2.into(),
|
||||||
|
is_client: true,
|
||||||
|
epoch: 0,
|
||||||
|
message_sequence: 1,
|
||||||
|
data: vec![0x01],
|
||||||
|
},
|
||||||
|
HandshakeCacheItem {
|
||||||
|
typ: 3.into(),
|
||||||
|
is_client: false,
|
||||||
|
epoch: 0,
|
||||||
|
message_sequence: 0,
|
||||||
|
data: vec![0x02],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
vec![
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: 1.into(),
|
||||||
|
epoch: 0,
|
||||||
|
is_client: true,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: 2.into(),
|
||||||
|
epoch: 0,
|
||||||
|
is_client: true,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: 3.into(),
|
||||||
|
epoch: 0,
|
||||||
|
is_client: false,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
vec![0x00, 0x01, 0x02],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Multi Push, Wrong epoch",
|
||||||
|
vec![
|
||||||
|
HandshakeCacheItem {
|
||||||
|
typ: 1.into(),
|
||||||
|
is_client: true,
|
||||||
|
epoch: 0,
|
||||||
|
message_sequence: 0,
|
||||||
|
data: vec![0x00],
|
||||||
|
},
|
||||||
|
HandshakeCacheItem {
|
||||||
|
typ: 2.into(),
|
||||||
|
is_client: true,
|
||||||
|
epoch: 1,
|
||||||
|
message_sequence: 1,
|
||||||
|
data: vec![0x01],
|
||||||
|
},
|
||||||
|
HandshakeCacheItem {
|
||||||
|
typ: 2.into(),
|
||||||
|
is_client: true,
|
||||||
|
epoch: 0,
|
||||||
|
message_sequence: 2,
|
||||||
|
data: vec![0x11],
|
||||||
|
},
|
||||||
|
HandshakeCacheItem {
|
||||||
|
typ: 3.into(),
|
||||||
|
is_client: false,
|
||||||
|
epoch: 0,
|
||||||
|
message_sequence: 0,
|
||||||
|
data: vec![0x02],
|
||||||
|
},
|
||||||
|
HandshakeCacheItem {
|
||||||
|
typ: 3.into(),
|
||||||
|
is_client: false,
|
||||||
|
epoch: 1,
|
||||||
|
message_sequence: 0,
|
||||||
|
data: vec![0x12],
|
||||||
|
},
|
||||||
|
HandshakeCacheItem {
|
||||||
|
typ: 3.into(),
|
||||||
|
is_client: false,
|
||||||
|
epoch: 2,
|
||||||
|
message_sequence: 0,
|
||||||
|
data: vec![0x12],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
vec![
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: 1.into(),
|
||||||
|
epoch: 0,
|
||||||
|
is_client: true,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: 2.into(),
|
||||||
|
epoch: 1,
|
||||||
|
is_client: true,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
HandshakeCachePullRule {
|
||||||
|
typ: 3.into(),
|
||||||
|
epoch: 0,
|
||||||
|
is_client: false,
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
vec![0x00, 0x01, 0x02],
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (name, inputs, rules, expected) in tests {
|
||||||
|
let mut h = HandshakeCache::new();
|
||||||
|
for i in inputs {
|
||||||
|
h.push(i.data, i.epoch, i.message_sequence, i.typ, i.is_client)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
let verify_data = h.pull_and_merge(&rules).await;
|
||||||
|
assert_eq!(
|
||||||
|
verify_data, expected,
|
||||||
|
"handshakeCache '{name}' exp:{expected:?} actual {verify_data:?}",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handshake_cache_session_hash() -> Result<()> {
|
||||||
|
let tests = vec![
|
||||||
|
(
|
||||||
|
"Standard Handshake",
|
||||||
|
vec![
|
||||||
|
HandshakeCacheItem {
|
||||||
|
typ: HandshakeType::ClientHello,
|
||||||
|
is_client: true,
|
||||||
|
epoch: 0,
|
||||||
|
message_sequence: 0,
|
||||||
|
data: vec![0x00],
|
||||||
|
},
|
||||||
|
HandshakeCacheItem {
|
||||||
|
typ: HandshakeType::ServerHello,
|
||||||
|
is_client: false,
|
||||||
|
epoch: 0,
|
||||||
|
message_sequence: 1,
|
||||||
|
data: vec![0x01],
|
||||||
|
},
|
||||||
|
HandshakeCacheItem {
|
||||||
|
typ: HandshakeType::Certificate,
|
||||||
|
is_client: false,
|
||||||
|
epoch: 0,
|
||||||
|
message_sequence: 2,
|
||||||
|
data: vec![0x02],
|
||||||
|
},
|
||||||
|
HandshakeCacheItem {
|
||||||
|
typ: HandshakeType::ServerKeyExchange,
|
||||||
|
is_client: false,
|
||||||
|
epoch: 0,
|
||||||
|
message_sequence: 3,
|
||||||
|
data: vec![0x03],
|
||||||
|
},
|
||||||
|
HandshakeCacheItem {
|
||||||
|
typ: HandshakeType::ServerHelloDone,
|
||||||
|
is_client: false,
|
||||||
|
epoch: 0,
|
||||||
|
message_sequence: 4,
|
||||||
|
data: vec![0x04],
|
||||||
|
},
|
||||||
|
HandshakeCacheItem {
|
||||||
|
typ: HandshakeType::ClientKeyExchange,
|
||||||
|
is_client: true,
|
||||||
|
epoch: 0,
|
||||||
|
message_sequence: 5,
|
||||||
|
data: vec![0x05],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
vec![
|
||||||
|
0x17, 0xe8, 0x8d, 0xb1, 0x87, 0xaf, 0xd6, 0x2c, 0x16, 0xe5, 0xde, 0xbf, 0x3e, 0x65,
|
||||||
|
0x27, 0xcd, 0x00, 0x6b, 0xc0, 0x12, 0xbc, 0x90, 0xb5, 0x1a, 0x81, 0x0c, 0xd8, 0x0c,
|
||||||
|
0x2d, 0x51, 0x1f, 0x43,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Handshake With Client Cert Request",
|
||||||
|
vec![
|
||||||
|
HandshakeCacheItem {
|
||||||
|
typ: HandshakeType::ClientHello,
|
||||||
|
is_client: true,
|
||||||
|
epoch: 0,
|
||||||
|
message_sequence: 0,
|
||||||
|
data: vec![0x00],
|
||||||
|
},
|
||||||
|
HandshakeCacheItem {
|
||||||
|
typ: HandshakeType::ServerHello,
|
||||||
|
is_client: false,
|
||||||
|
epoch: 0,
|
||||||
|
message_sequence: 1,
|
||||||
|
data: vec![0x01],
|
||||||
|
},
|
||||||
|
HandshakeCacheItem {
|
||||||
|
typ: HandshakeType::Certificate,
|
||||||
|
is_client: false,
|
||||||
|
epoch: 0,
|
||||||
|
message_sequence: 2,
|
||||||
|
data: vec![0x02],
|
||||||
|
},
|
||||||
|
HandshakeCacheItem {
|
||||||
|
typ: HandshakeType::ServerKeyExchange,
|
||||||
|
is_client: false,
|
||||||
|
epoch: 0,
|
||||||
|
message_sequence: 3,
|
||||||
|
data: vec![0x03],
|
||||||
|
},
|
||||||
|
HandshakeCacheItem {
|
||||||
|
typ: HandshakeType::CertificateRequest,
|
||||||
|
is_client: false,
|
||||||
|
epoch: 0,
|
||||||
|
message_sequence: 4,
|
||||||
|
data: vec![0x04],
|
||||||
|
},
|
||||||
|
HandshakeCacheItem {
|
||||||
|
typ: HandshakeType::ServerHelloDone,
|
||||||
|
is_client: false,
|
||||||
|
epoch: 0,
|
||||||
|
message_sequence: 5,
|
||||||
|
data: vec![0x05],
|
||||||
|
},
|
||||||
|
HandshakeCacheItem {
|
||||||
|
typ: HandshakeType::ClientKeyExchange,
|
||||||
|
is_client: true,
|
||||||
|
epoch: 0,
|
||||||
|
message_sequence: 6,
|
||||||
|
data: vec![0x06],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
vec![
|
||||||
|
0x57, 0x35, 0x5a, 0xc3, 0x30, 0x3c, 0x14, 0x8f, 0x11, 0xae, 0xf7, 0xcb, 0x17, 0x94,
|
||||||
|
0x56, 0xb9, 0x23, 0x2c, 0xde, 0x33, 0xa8, 0x18, 0xdf, 0xda, 0x2c, 0x2f, 0xcb, 0x93,
|
||||||
|
0x25, 0x74, 0x9a, 0x6b,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Handshake Ignores after ClientKeyExchange",
|
||||||
|
vec![
|
||||||
|
HandshakeCacheItem {
|
||||||
|
typ: HandshakeType::ClientHello,
|
||||||
|
is_client: true,
|
||||||
|
epoch: 0,
|
||||||
|
message_sequence: 0,
|
||||||
|
data: vec![0x00],
|
||||||
|
},
|
||||||
|
HandshakeCacheItem {
|
||||||
|
typ: HandshakeType::ServerHello,
|
||||||
|
is_client: false,
|
||||||
|
epoch: 0,
|
||||||
|
message_sequence: 1,
|
||||||
|
data: vec![0x01],
|
||||||
|
},
|
||||||
|
HandshakeCacheItem {
|
||||||
|
typ: HandshakeType::Certificate,
|
||||||
|
is_client: false,
|
||||||
|
epoch: 0,
|
||||||
|
message_sequence: 2,
|
||||||
|
data: vec![0x02],
|
||||||
|
},
|
||||||
|
HandshakeCacheItem {
|
||||||
|
typ: HandshakeType::ServerKeyExchange,
|
||||||
|
is_client: false,
|
||||||
|
epoch: 0,
|
||||||
|
message_sequence: 3,
|
||||||
|
data: vec![0x03],
|
||||||
|
},
|
||||||
|
HandshakeCacheItem {
|
||||||
|
typ: HandshakeType::CertificateRequest,
|
||||||
|
is_client: false,
|
||||||
|
epoch: 0,
|
||||||
|
message_sequence: 4,
|
||||||
|
data: vec![0x04],
|
||||||
|
},
|
||||||
|
HandshakeCacheItem {
|
||||||
|
typ: HandshakeType::ServerHelloDone,
|
||||||
|
is_client: false,
|
||||||
|
epoch: 0,
|
||||||
|
message_sequence: 5,
|
||||||
|
data: vec![0x05],
|
||||||
|
},
|
||||||
|
HandshakeCacheItem {
|
||||||
|
typ: HandshakeType::ClientKeyExchange,
|
||||||
|
is_client: true,
|
||||||
|
epoch: 0,
|
||||||
|
message_sequence: 6,
|
||||||
|
data: vec![0x06],
|
||||||
|
},
|
||||||
|
HandshakeCacheItem {
|
||||||
|
typ: HandshakeType::CertificateVerify,
|
||||||
|
is_client: true,
|
||||||
|
epoch: 0,
|
||||||
|
message_sequence: 7,
|
||||||
|
data: vec![0x07],
|
||||||
|
},
|
||||||
|
HandshakeCacheItem {
|
||||||
|
typ: HandshakeType::Finished,
|
||||||
|
is_client: true,
|
||||||
|
epoch: 1,
|
||||||
|
message_sequence: 7,
|
||||||
|
data: vec![0x08],
|
||||||
|
},
|
||||||
|
HandshakeCacheItem {
|
||||||
|
typ: HandshakeType::Finished,
|
||||||
|
is_client: false,
|
||||||
|
epoch: 1,
|
||||||
|
message_sequence: 7,
|
||||||
|
data: vec![0x09],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
vec![
|
||||||
|
0x57, 0x35, 0x5a, 0xc3, 0x30, 0x3c, 0x14, 0x8f, 0x11, 0xae, 0xf7, 0xcb, 0x17, 0x94,
|
||||||
|
0x56, 0xb9, 0x23, 0x2c, 0xde, 0x33, 0xa8, 0x18, 0xdf, 0xda, 0x2c, 0x2f, 0xcb, 0x93,
|
||||||
|
0x25, 0x74, 0x9a, 0x6b,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Handshake Ignores wrong epoch",
|
||||||
|
vec![
|
||||||
|
HandshakeCacheItem {
|
||||||
|
typ: HandshakeType::ClientHello,
|
||||||
|
is_client: true,
|
||||||
|
epoch: 0,
|
||||||
|
message_sequence: 0,
|
||||||
|
data: vec![0x00],
|
||||||
|
},
|
||||||
|
HandshakeCacheItem {
|
||||||
|
typ: HandshakeType::ServerHello,
|
||||||
|
is_client: false,
|
||||||
|
epoch: 0,
|
||||||
|
message_sequence: 1,
|
||||||
|
data: vec![0x01],
|
||||||
|
},
|
||||||
|
HandshakeCacheItem {
|
||||||
|
typ: HandshakeType::Certificate,
|
||||||
|
is_client: false,
|
||||||
|
epoch: 0,
|
||||||
|
message_sequence: 2,
|
||||||
|
data: vec![0x02],
|
||||||
|
},
|
||||||
|
HandshakeCacheItem {
|
||||||
|
typ: HandshakeType::ServerKeyExchange,
|
||||||
|
is_client: false,
|
||||||
|
epoch: 0,
|
||||||
|
message_sequence: 3,
|
||||||
|
data: vec![0x03],
|
||||||
|
},
|
||||||
|
HandshakeCacheItem {
|
||||||
|
typ: HandshakeType::CertificateRequest,
|
||||||
|
is_client: false,
|
||||||
|
epoch: 0,
|
||||||
|
message_sequence: 4,
|
||||||
|
data: vec![0x04],
|
||||||
|
},
|
||||||
|
HandshakeCacheItem {
|
||||||
|
typ: HandshakeType::ServerHelloDone,
|
||||||
|
is_client: false,
|
||||||
|
epoch: 0,
|
||||||
|
message_sequence: 5,
|
||||||
|
data: vec![0x05],
|
||||||
|
},
|
||||||
|
HandshakeCacheItem {
|
||||||
|
typ: HandshakeType::ClientKeyExchange,
|
||||||
|
is_client: true,
|
||||||
|
epoch: 0,
|
||||||
|
message_sequence: 6,
|
||||||
|
data: vec![0x06],
|
||||||
|
},
|
||||||
|
HandshakeCacheItem {
|
||||||
|
typ: HandshakeType::CertificateVerify,
|
||||||
|
is_client: true,
|
||||||
|
epoch: 0,
|
||||||
|
message_sequence: 7,
|
||||||
|
data: vec![0x07],
|
||||||
|
},
|
||||||
|
HandshakeCacheItem {
|
||||||
|
typ: HandshakeType::Finished,
|
||||||
|
is_client: true,
|
||||||
|
epoch: 0,
|
||||||
|
message_sequence: 7,
|
||||||
|
data: vec![0xf0],
|
||||||
|
},
|
||||||
|
HandshakeCacheItem {
|
||||||
|
typ: HandshakeType::Finished,
|
||||||
|
is_client: false,
|
||||||
|
epoch: 0,
|
||||||
|
message_sequence: 7,
|
||||||
|
data: vec![0xf1],
|
||||||
|
},
|
||||||
|
HandshakeCacheItem {
|
||||||
|
typ: HandshakeType::Finished,
|
||||||
|
is_client: true,
|
||||||
|
epoch: 1,
|
||||||
|
message_sequence: 7,
|
||||||
|
data: vec![0x08],
|
||||||
|
},
|
||||||
|
HandshakeCacheItem {
|
||||||
|
typ: HandshakeType::Finished,
|
||||||
|
is_client: false,
|
||||||
|
epoch: 1,
|
||||||
|
message_sequence: 7,
|
||||||
|
data: vec![0x09],
|
||||||
|
},
|
||||||
|
HandshakeCacheItem {
|
||||||
|
typ: HandshakeType::Finished,
|
||||||
|
is_client: true,
|
||||||
|
epoch: 0,
|
||||||
|
message_sequence: 7,
|
||||||
|
data: vec![0xf0],
|
||||||
|
},
|
||||||
|
HandshakeCacheItem {
|
||||||
|
typ: HandshakeType::Finished,
|
||||||
|
is_client: false,
|
||||||
|
epoch: 0,
|
||||||
|
message_sequence: 7,
|
||||||
|
data: vec![0xf1],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
vec![
|
||||||
|
0x57, 0x35, 0x5a, 0xc3, 0x30, 0x3c, 0x14, 0x8f, 0x11, 0xae, 0xf7, 0xcb, 0x17, 0x94,
|
||||||
|
0x56, 0xb9, 0x23, 0x2c, 0xde, 0x33, 0xa8, 0x18, 0xdf, 0xda, 0x2c, 0x2f, 0xcb, 0x93,
|
||||||
|
0x25, 0x74, 0x9a, 0x6b,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (name, inputs, expected) in tests {
|
||||||
|
let mut h = HandshakeCache::new();
|
||||||
|
for i in inputs {
|
||||||
|
h.push(i.data, i.epoch, i.message_sequence, i.typ, i.is_client)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
let verify_data = h.session_hash(CipherSuiteHash::Sha256, 0, &[]).await?;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
verify_data, expected,
|
||||||
|
"handshakeCacheSessionHassh '{name}' exp: {expected:?} actual {verify_data:?}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
50
libs/dtls/src/handshake/handshake_header.rs
Normal file
50
libs/dtls/src/handshake/handshake_header.rs
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
use std::io::{Read, Write};
|
||||||
|
|
||||||
|
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
// msg_len for Handshake messages assumes an extra 12 bytes for
|
||||||
|
// sequence, Fragment and version information
|
||||||
|
pub(crate) const HANDSHAKE_HEADER_LENGTH: usize = 12;
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq, Debug, Default)]
|
||||||
|
pub struct HandshakeHeader {
|
||||||
|
pub(crate) handshake_type: HandshakeType,
|
||||||
|
pub(crate) length: u32, // uint24 in spec
|
||||||
|
pub(crate) message_sequence: u16,
|
||||||
|
pub(crate) fragment_offset: u32, // uint24 in spec
|
||||||
|
pub(crate) fragment_length: u32, // uint24 in spec
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HandshakeHeader {
|
||||||
|
pub fn size(&self) -> usize {
|
||||||
|
1 + 3 + 2 + 3 + 3
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn marshal<W: Write>(&self, writer: &mut W) -> Result<()> {
|
||||||
|
writer.write_u8(self.handshake_type as u8)?;
|
||||||
|
writer.write_u24::<BigEndian>(self.length)?;
|
||||||
|
writer.write_u16::<BigEndian>(self.message_sequence)?;
|
||||||
|
writer.write_u24::<BigEndian>(self.fragment_offset)?;
|
||||||
|
writer.write_u24::<BigEndian>(self.fragment_length)?;
|
||||||
|
|
||||||
|
Ok(writer.flush()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unmarshal<R: Read>(reader: &mut R) -> Result<Self> {
|
||||||
|
let handshake_type = reader.read_u8()?.into();
|
||||||
|
let length = reader.read_u24::<BigEndian>()?;
|
||||||
|
let message_sequence = reader.read_u16::<BigEndian>()?;
|
||||||
|
let fragment_offset = reader.read_u24::<BigEndian>()?;
|
||||||
|
let fragment_length = reader.read_u24::<BigEndian>()?;
|
||||||
|
|
||||||
|
Ok(HandshakeHeader {
|
||||||
|
handshake_type,
|
||||||
|
length,
|
||||||
|
message_sequence,
|
||||||
|
fragment_offset,
|
||||||
|
fragment_length,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
70
libs/dtls/src/handshake/handshake_message_certificate.rs
Normal file
70
libs/dtls/src/handshake/handshake_message_certificate.rs
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
use std::io::{Read, Write};
|
||||||
|
|
||||||
|
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod handshake_message_certificate_test;
|
||||||
|
|
||||||
|
const HANDSHAKE_MESSAGE_CERTIFICATE_LENGTH_FIELD_SIZE: usize = 3;
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Debug, Clone)]
|
||||||
|
pub struct HandshakeMessageCertificate {
|
||||||
|
pub(crate) certificate: Vec<Vec<u8>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HandshakeMessageCertificate {
|
||||||
|
pub fn handshake_type(&self) -> HandshakeType {
|
||||||
|
HandshakeType::Certificate
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn size(&self) -> usize {
|
||||||
|
let mut len = 3;
|
||||||
|
|
||||||
|
for r in &self.certificate {
|
||||||
|
len += HANDSHAKE_MESSAGE_CERTIFICATE_LENGTH_FIELD_SIZE + r.len();
|
||||||
|
}
|
||||||
|
|
||||||
|
len
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn marshal<W: Write>(&self, writer: &mut W) -> Result<()> {
|
||||||
|
let mut payload_size = 0;
|
||||||
|
for r in &self.certificate {
|
||||||
|
payload_size += HANDSHAKE_MESSAGE_CERTIFICATE_LENGTH_FIELD_SIZE + r.len();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Total Payload Size
|
||||||
|
writer.write_u24::<BigEndian>(payload_size as u32)?;
|
||||||
|
|
||||||
|
for r in &self.certificate {
|
||||||
|
// Certificate Length
|
||||||
|
writer.write_u24::<BigEndian>(r.len() as u32)?;
|
||||||
|
|
||||||
|
// Certificate body
|
||||||
|
writer.write_all(r)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(writer.flush()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unmarshal<R: Read>(reader: &mut R) -> Result<Self> {
|
||||||
|
let mut certificate: Vec<Vec<u8>> = vec![];
|
||||||
|
|
||||||
|
let payload_size = reader.read_u24::<BigEndian>()? as usize;
|
||||||
|
let mut offset = 0;
|
||||||
|
while offset < payload_size {
|
||||||
|
let certificate_len = reader.read_u24::<BigEndian>()? as usize;
|
||||||
|
offset += HANDSHAKE_MESSAGE_CERTIFICATE_LENGTH_FIELD_SIZE;
|
||||||
|
|
||||||
|
let mut buf = vec![0; certificate_len];
|
||||||
|
reader.read_exact(&mut buf)?;
|
||||||
|
offset += certificate_len;
|
||||||
|
|
||||||
|
certificate.push(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(HandshakeMessageCertificate { certificate })
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
use std::io::{BufReader, BufWriter};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_handshake_message_certificate() -> Result<()> {
|
||||||
|
let raw_certificate = vec![
|
||||||
|
0x00, 0x01, 0x8c, 0x00, 0x01, 0x89, 0x30, 0x82, 0x01, 0x85, 0x30, 0x82, 0x01, 0x2b, 0x02,
|
||||||
|
0x14, 0x7d, 0x00, 0xcf, 0x07, 0xfc, 0xe2, 0xb6, 0xb8, 0x3f, 0x72, 0xeb, 0x11, 0x36, 0x1b,
|
||||||
|
0xf6, 0x39, 0xf1, 0x3c, 0x33, 0x41, 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d,
|
||||||
|
0x04, 0x03, 0x02, 0x30, 0x45, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13,
|
||||||
|
0x02, 0x41, 0x55, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08, 0x0c, 0x0a, 0x53,
|
||||||
|
0x6f, 0x6d, 0x65, 0x2d, 0x53, 0x74, 0x61, 0x74, 0x65, 0x31, 0x21, 0x30, 0x1f, 0x06, 0x03,
|
||||||
|
0x55, 0x04, 0x0a, 0x0c, 0x18, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x20, 0x57,
|
||||||
|
0x69, 0x64, 0x67, 0x69, 0x74, 0x73, 0x20, 0x50, 0x74, 0x79, 0x20, 0x4c, 0x74, 0x64, 0x30,
|
||||||
|
0x1e, 0x17, 0x0d, 0x31, 0x38, 0x31, 0x30, 0x32, 0x35, 0x30, 0x38, 0x35, 0x31, 0x31, 0x32,
|
||||||
|
0x5a, 0x17, 0x0d, 0x31, 0x39, 0x31, 0x30, 0x32, 0x35, 0x30, 0x38, 0x35, 0x31, 0x31, 0x32,
|
||||||
|
0x5a, 0x30, 0x45, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x41,
|
||||||
|
0x55, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08, 0x0c, 0x0a, 0x53, 0x6f, 0x6d,
|
||||||
|
0x65, 0x2d, 0x53, 0x74, 0x61, 0x74, 0x65, 0x31, 0x21, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x04,
|
||||||
|
0x0a, 0x0c, 0x18, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x20, 0x57, 0x69, 0x64,
|
||||||
|
0x67, 0x69, 0x74, 0x73, 0x20, 0x50, 0x74, 0x79, 0x20, 0x4c, 0x74, 0x64, 0x30, 0x59, 0x30,
|
||||||
|
0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a, 0x86, 0x48,
|
||||||
|
0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00, 0x04, 0xf9, 0xb1, 0x62, 0xd6, 0x07, 0xae,
|
||||||
|
0xc3, 0x36, 0x34, 0xf5, 0xa3, 0x09, 0x39, 0x86, 0xe7, 0x3b, 0x59, 0xf7, 0x4a, 0x1d, 0xf4,
|
||||||
|
0x97, 0x4f, 0x91, 0x40, 0x56, 0x1b, 0x3d, 0x6c, 0x5a, 0x38, 0x10, 0x15, 0x58, 0xf5, 0xa4,
|
||||||
|
0xcc, 0xdf, 0xd5, 0xf5, 0x4a, 0x35, 0x40, 0x0f, 0x9f, 0x54, 0xb7, 0xe9, 0xe2, 0xae, 0x63,
|
||||||
|
0x83, 0x6a, 0x4c, 0xfc, 0xc2, 0x5f, 0x78, 0xa0, 0xbb, 0x46, 0x54, 0xa4, 0xda, 0x30, 0x0a,
|
||||||
|
0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, 0x03, 0x48, 0x00, 0x30, 0x45,
|
||||||
|
0x02, 0x20, 0x47, 0x1a, 0x5f, 0x58, 0x2a, 0x74, 0x33, 0x6d, 0xed, 0xac, 0x37, 0x21, 0xfa,
|
||||||
|
0x76, 0x5a, 0x4d, 0x78, 0x68, 0x1a, 0xdd, 0x80, 0xa4, 0xd4, 0xb7, 0x7f, 0x7d, 0x78, 0xb3,
|
||||||
|
0xfb, 0xf3, 0x95, 0xfb, 0x02, 0x21, 0x00, 0xc0, 0x73, 0x30, 0xda, 0x2b, 0xc0, 0x0c, 0x9e,
|
||||||
|
0xb2, 0x25, 0x0d, 0x46, 0xb0, 0xbc, 0x66, 0x7f, 0x71, 0x66, 0xbf, 0x16, 0xb3, 0x80, 0x78,
|
||||||
|
0xd0, 0x0c, 0xef, 0xcc, 0xf5, 0xc1, 0x15, 0x0f, 0x58,
|
||||||
|
];
|
||||||
|
|
||||||
|
let mut reader = BufReader::new(raw_certificate.as_slice());
|
||||||
|
let c = HandshakeMessageCertificate::unmarshal(&mut reader)?;
|
||||||
|
//TODO: add x509 parse
|
||||||
|
// certificate, err := x509.ParseCertificate(c.certificate[0])
|
||||||
|
// if err != nil {
|
||||||
|
// t.Error(err)
|
||||||
|
// }
|
||||||
|
// copyCertificatePrivateMembers(certificate, parsedCertificate)
|
||||||
|
// if !reflect.DeepEqual(certificate, parsedCertificate) {
|
||||||
|
// t.Errorf("handshakeMessageCertificate unmarshal: got %#v, want %#v", c, parsedCertificate)
|
||||||
|
// }
|
||||||
|
|
||||||
|
let mut raw = vec![];
|
||||||
|
{
|
||||||
|
let mut writer = BufWriter::<&mut Vec<u8>>::new(raw.as_mut());
|
||||||
|
c.marshal(&mut writer)?;
|
||||||
|
}
|
||||||
|
assert_eq!(
|
||||||
|
raw, raw_certificate,
|
||||||
|
"handshakeMessageCertificate marshal: got {raw:?}, want {raw_certificate:?}"
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_empty_handshake_message_certificate() -> Result<()> {
|
||||||
|
let raw_certificate = vec![0x00, 0x00, 0x00];
|
||||||
|
|
||||||
|
let expected_certificate = HandshakeMessageCertificate {
|
||||||
|
certificate: vec![],
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut reader = BufReader::new(raw_certificate.as_slice());
|
||||||
|
let c = HandshakeMessageCertificate::unmarshal(&mut reader)?;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
c, expected_certificate,
|
||||||
|
"handshakeMessageCertificate unmarshal: got {c:?}, want {expected_certificate:?}",
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod handshake_message_certificate_request_test;
|
||||||
|
|
||||||
|
use std::io::{Read, Write};
|
||||||
|
|
||||||
|
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::client_certificate_type::*;
|
||||||
|
use crate::signature_hash_algorithm::*;
|
||||||
|
|
||||||
|
/*
|
||||||
|
A non-anonymous server can optionally request a certificate from
|
||||||
|
the client, if appropriate for the selected cipher suite. This
|
||||||
|
message, if sent, will immediately follow the ServerKeyExchange
|
||||||
|
message (if it is sent; otherwise, this message follows the
|
||||||
|
server's Certificate message).
|
||||||
|
*/
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct HandshakeMessageCertificateRequest {
|
||||||
|
pub(crate) certificate_types: Vec<ClientCertificateType>,
|
||||||
|
pub(crate) signature_hash_algorithms: Vec<SignatureHashAlgorithm>,
|
||||||
|
}
|
||||||
|
|
||||||
|
const HANDSHAKE_MESSAGE_CERTIFICATE_REQUEST_MIN_LENGTH: usize = 5;
|
||||||
|
|
||||||
|
impl HandshakeMessageCertificateRequest {
|
||||||
|
pub fn handshake_type(&self) -> HandshakeType {
|
||||||
|
HandshakeType::CertificateRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn size(&self) -> usize {
|
||||||
|
1 + self.certificate_types.len() + 2 + self.signature_hash_algorithms.len() * 2 + 2
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn marshal<W: Write>(&self, writer: &mut W) -> Result<()> {
|
||||||
|
writer.write_u8(self.certificate_types.len() as u8)?;
|
||||||
|
for v in &self.certificate_types {
|
||||||
|
writer.write_u8(*v as u8)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.write_u16::<BigEndian>(2 * self.signature_hash_algorithms.len() as u16)?;
|
||||||
|
for v in &self.signature_hash_algorithms {
|
||||||
|
writer.write_u8(v.hash as u8)?;
|
||||||
|
writer.write_u8(v.signature as u8)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.write_all(&[0x00, 0x00])?; // Distinguished Names Length
|
||||||
|
|
||||||
|
Ok(writer.flush()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unmarshal<R: Read>(reader: &mut R) -> Result<Self> {
|
||||||
|
let certificate_types_length = reader.read_u8()?;
|
||||||
|
|
||||||
|
let mut certificate_types = vec![];
|
||||||
|
for _ in 0..certificate_types_length {
|
||||||
|
let cert_type = reader.read_u8()?.into();
|
||||||
|
certificate_types.push(cert_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
let signature_hash_algorithms_length = reader.read_u16::<BigEndian>()?;
|
||||||
|
|
||||||
|
let mut signature_hash_algorithms = vec![];
|
||||||
|
for _ in (0..signature_hash_algorithms_length).step_by(2) {
|
||||||
|
let hash = reader.read_u8()?.into();
|
||||||
|
let signature = reader.read_u8()?.into();
|
||||||
|
|
||||||
|
signature_hash_algorithms.push(SignatureHashAlgorithm { hash, signature });
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(HandshakeMessageCertificateRequest {
|
||||||
|
certificate_types,
|
||||||
|
signature_hash_algorithms,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
use std::io::{BufReader, BufWriter};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::signature_hash_algorithm::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_handshake_message_certificate_request() -> Result<()> {
|
||||||
|
let raw_certificate_request = vec![
|
||||||
|
0x02, 0x01, 0x40, 0x00, 0x0C, 0x04, 0x03, 0x04, 0x01, 0x05, 0x03, 0x05, 0x01, 0x06, 0x01,
|
||||||
|
0x02, 0x01, 0x00, 0x00,
|
||||||
|
];
|
||||||
|
|
||||||
|
let parsed_certificate_request = HandshakeMessageCertificateRequest {
|
||||||
|
certificate_types: vec![
|
||||||
|
ClientCertificateType::RsaSign,
|
||||||
|
ClientCertificateType::EcdsaSign,
|
||||||
|
],
|
||||||
|
signature_hash_algorithms: vec![
|
||||||
|
SignatureHashAlgorithm {
|
||||||
|
hash: HashAlgorithm::Sha256,
|
||||||
|
signature: SignatureAlgorithm::Ecdsa,
|
||||||
|
},
|
||||||
|
SignatureHashAlgorithm {
|
||||||
|
hash: HashAlgorithm::Sha256,
|
||||||
|
signature: SignatureAlgorithm::Rsa,
|
||||||
|
},
|
||||||
|
SignatureHashAlgorithm {
|
||||||
|
hash: HashAlgorithm::Sha384,
|
||||||
|
signature: SignatureAlgorithm::Ecdsa,
|
||||||
|
},
|
||||||
|
SignatureHashAlgorithm {
|
||||||
|
hash: HashAlgorithm::Sha384,
|
||||||
|
signature: SignatureAlgorithm::Rsa,
|
||||||
|
},
|
||||||
|
SignatureHashAlgorithm {
|
||||||
|
hash: HashAlgorithm::Sha512,
|
||||||
|
signature: SignatureAlgorithm::Rsa,
|
||||||
|
},
|
||||||
|
SignatureHashAlgorithm {
|
||||||
|
hash: HashAlgorithm::Sha1,
|
||||||
|
signature: SignatureAlgorithm::Rsa,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut reader = BufReader::new(raw_certificate_request.as_slice());
|
||||||
|
let c = HandshakeMessageCertificateRequest::unmarshal(&mut reader)?;
|
||||||
|
assert_eq!(
|
||||||
|
c, parsed_certificate_request,
|
||||||
|
"parsedCertificateRequest unmarshal: got {c:?}, want {parsed_certificate_request:?}"
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut raw = vec![];
|
||||||
|
{
|
||||||
|
let mut writer = BufWriter::<&mut Vec<u8>>::new(raw.as_mut());
|
||||||
|
c.marshal(&mut writer)?;
|
||||||
|
}
|
||||||
|
assert_eq!(
|
||||||
|
raw, raw_certificate_request,
|
||||||
|
"parsedCertificateRequest marshal: got {raw:?}, want {raw_certificate_request:?}"
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod handshake_message_certificate_verify_test;
|
||||||
|
|
||||||
|
use std::io::{Read, Write};
|
||||||
|
|
||||||
|
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::signature_hash_algorithm::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct HandshakeMessageCertificateVerify {
|
||||||
|
pub(crate) algorithm: SignatureHashAlgorithm,
|
||||||
|
pub(crate) signature: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
const HANDSHAKE_MESSAGE_CERTIFICATE_VERIFY_MIN_LENGTH: usize = 4;
|
||||||
|
|
||||||
|
impl HandshakeMessageCertificateVerify {
|
||||||
|
pub fn handshake_type(&self) -> HandshakeType {
|
||||||
|
HandshakeType::CertificateVerify
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn size(&self) -> usize {
|
||||||
|
1 + 1 + 2 + self.signature.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn marshal<W: Write>(&self, writer: &mut W) -> Result<()> {
|
||||||
|
writer.write_u8(self.algorithm.hash as u8)?;
|
||||||
|
writer.write_u8(self.algorithm.signature as u8)?;
|
||||||
|
writer.write_u16::<BigEndian>(self.signature.len() as u16)?;
|
||||||
|
writer.write_all(&self.signature)?;
|
||||||
|
|
||||||
|
Ok(writer.flush()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unmarshal<R: Read>(reader: &mut R) -> Result<Self> {
|
||||||
|
let hash_algorithm = reader.read_u8()?.into();
|
||||||
|
let signature_algorithm = reader.read_u8()?.into();
|
||||||
|
let signature_length = reader.read_u16::<BigEndian>()? as usize;
|
||||||
|
let mut signature = vec![0; signature_length];
|
||||||
|
reader.read_exact(&mut signature)?;
|
||||||
|
|
||||||
|
Ok(HandshakeMessageCertificateVerify {
|
||||||
|
algorithm: SignatureHashAlgorithm {
|
||||||
|
hash: hash_algorithm,
|
||||||
|
signature: signature_algorithm,
|
||||||
|
},
|
||||||
|
signature,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
use std::io::{BufReader, BufWriter};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_handshake_message_certificate_request() -> Result<()> {
|
||||||
|
let raw_certificate_verify = vec![
|
||||||
|
0x04, 0x03, 0x00, 0x47, 0x30, 0x45, 0x02, 0x20, 0x6b, 0x63, 0x17, 0xad, 0xbe, 0xb7, 0x7b,
|
||||||
|
0x0f, 0x86, 0x73, 0x39, 0x1e, 0xba, 0xb3, 0x50, 0x9c, 0xce, 0x9c, 0xe4, 0x8b, 0xe5, 0x13,
|
||||||
|
0x07, 0x59, 0x18, 0x1f, 0xe5, 0xa0, 0x2b, 0xca, 0xa6, 0xad, 0x02, 0x21, 0x00, 0xd3, 0xb5,
|
||||||
|
0x01, 0xbe, 0x87, 0x6c, 0x04, 0xa1, 0xdc, 0x28, 0xaa, 0x5f, 0xf7, 0x1e, 0x9c, 0xc0, 0x1e,
|
||||||
|
0x00, 0x2c, 0xe5, 0x94, 0xbb, 0x03, 0x0e, 0xf1, 0xcb, 0x28, 0x22, 0x33, 0x23, 0x88, 0xad,
|
||||||
|
];
|
||||||
|
let parsed_certificate_verify = HandshakeMessageCertificateVerify {
|
||||||
|
algorithm: SignatureHashAlgorithm {
|
||||||
|
hash: raw_certificate_verify[0].into(),
|
||||||
|
signature: raw_certificate_verify[1].into(),
|
||||||
|
},
|
||||||
|
signature: raw_certificate_verify[4..].to_vec(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut reader = BufReader::new(raw_certificate_verify.as_slice());
|
||||||
|
let c = HandshakeMessageCertificateVerify::unmarshal(&mut reader)?;
|
||||||
|
assert_eq!(
|
||||||
|
c, parsed_certificate_verify,
|
||||||
|
"handshakeMessageCertificate unmarshal: got {c:?}, want {parsed_certificate_verify:?}"
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut raw = vec![];
|
||||||
|
{
|
||||||
|
let mut writer = BufWriter::<&mut Vec<u8>>::new(raw.as_mut());
|
||||||
|
c.marshal(&mut writer)?;
|
||||||
|
}
|
||||||
|
assert_eq!(
|
||||||
|
raw, raw_certificate_verify,
|
||||||
|
"handshakeMessageCertificateVerify marshal: got {raw:?}, want {raw_certificate_verify:?}"
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
196
libs/dtls/src/handshake/handshake_message_client_hello.rs
Normal file
196
libs/dtls/src/handshake/handshake_message_client_hello.rs
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod handshake_message_client_hello_test;
|
||||||
|
|
||||||
|
use std::fmt;
|
||||||
|
use std::io::{BufReader, BufWriter};
|
||||||
|
|
||||||
|
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||||
|
|
||||||
|
use super::handshake_random::*;
|
||||||
|
use super::*;
|
||||||
|
use crate::cipher_suite::*;
|
||||||
|
use crate::compression_methods::*;
|
||||||
|
use crate::extension::*;
|
||||||
|
use crate::record_layer::record_layer_header::*;
|
||||||
|
|
||||||
|
/*
|
||||||
|
When a client first connects to a server it is required to send
|
||||||
|
the client hello as its first message. The client can also send a
|
||||||
|
client hello in response to a hello request or on its own
|
||||||
|
initiative in order to renegotiate the security parameters in an
|
||||||
|
existing connection.
|
||||||
|
*/
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct HandshakeMessageClientHello {
|
||||||
|
pub(crate) version: ProtocolVersion,
|
||||||
|
pub(crate) random: HandshakeRandom,
|
||||||
|
pub(crate) cookie: Vec<u8>,
|
||||||
|
|
||||||
|
pub(crate) cipher_suites: Vec<CipherSuiteId>,
|
||||||
|
pub(crate) compression_methods: CompressionMethods,
|
||||||
|
pub(crate) extensions: Vec<Extension>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for HandshakeMessageClientHello {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
if !(self.version == other.version
|
||||||
|
&& self.random == other.random
|
||||||
|
&& self.cookie == other.cookie
|
||||||
|
&& self.compression_methods == other.compression_methods
|
||||||
|
&& self.extensions == other.extensions
|
||||||
|
&& self.cipher_suites.len() == other.cipher_suites.len())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for i in 0..self.cipher_suites.len() {
|
||||||
|
if self.cipher_suites[i] != other.cipher_suites[i] {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for HandshakeMessageClientHello {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
let mut cipher_suites_str = String::new();
|
||||||
|
for cipher_suite in &self.cipher_suites {
|
||||||
|
cipher_suites_str += &cipher_suite.to_string();
|
||||||
|
cipher_suites_str += " ";
|
||||||
|
}
|
||||||
|
let s = [
|
||||||
|
format!("version: {:?} random: {:?}", self.version, self.random),
|
||||||
|
format!("cookie: {:?}", self.cookie),
|
||||||
|
format!("cipher_suites: {cipher_suites_str:?}"),
|
||||||
|
format!("compression_methods: {:?}", self.compression_methods),
|
||||||
|
format!("extensions: {:?}", self.extensions),
|
||||||
|
];
|
||||||
|
write!(f, "{}", s.join(" "))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const HANDSHAKE_MESSAGE_CLIENT_HELLO_VARIABLE_WIDTH_START: usize = 34;
|
||||||
|
|
||||||
|
impl HandshakeMessageClientHello {
|
||||||
|
pub fn handshake_type(&self) -> HandshakeType {
|
||||||
|
HandshakeType::ClientHello
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn size(&self) -> usize {
|
||||||
|
let mut len = 0;
|
||||||
|
|
||||||
|
len += 2; // version.major+minor
|
||||||
|
len += self.random.size();
|
||||||
|
|
||||||
|
// SessionID
|
||||||
|
len += 1;
|
||||||
|
|
||||||
|
len += 1 + self.cookie.len();
|
||||||
|
|
||||||
|
len += 2 + 2 * self.cipher_suites.len();
|
||||||
|
|
||||||
|
len += self.compression_methods.size();
|
||||||
|
|
||||||
|
len += 2;
|
||||||
|
for extension in &self.extensions {
|
||||||
|
len += extension.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
len
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn marshal<W: Write>(&self, writer: &mut W) -> Result<()> {
|
||||||
|
if self.cookie.len() > 255 {
|
||||||
|
return Err(Error::ErrCookieTooLong);
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.write_u8(self.version.major)?;
|
||||||
|
writer.write_u8(self.version.minor)?;
|
||||||
|
self.random.marshal(writer)?;
|
||||||
|
|
||||||
|
// SessionID
|
||||||
|
writer.write_u8(0x00)?;
|
||||||
|
|
||||||
|
writer.write_u8(self.cookie.len() as u8)?;
|
||||||
|
writer.write_all(&self.cookie)?;
|
||||||
|
|
||||||
|
writer.write_u16::<BigEndian>(2 * self.cipher_suites.len() as u16)?;
|
||||||
|
for cipher_suite in &self.cipher_suites {
|
||||||
|
writer.write_u16::<BigEndian>(*cipher_suite as u16)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.compression_methods.marshal(writer)?;
|
||||||
|
|
||||||
|
let mut extension_buffer = vec![];
|
||||||
|
{
|
||||||
|
let mut extension_writer = BufWriter::<&mut Vec<u8>>::new(extension_buffer.as_mut());
|
||||||
|
for extension in &self.extensions {
|
||||||
|
extension.marshal(&mut extension_writer)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.write_u16::<BigEndian>(extension_buffer.len() as u16)?;
|
||||||
|
writer.write_all(&extension_buffer)?;
|
||||||
|
|
||||||
|
Ok(writer.flush()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unmarshal<R: Read>(reader: &mut R) -> Result<Self> {
|
||||||
|
let major = reader.read_u8()?;
|
||||||
|
let minor = reader.read_u8()?;
|
||||||
|
let random = HandshakeRandom::unmarshal(reader)?;
|
||||||
|
|
||||||
|
// Session ID
|
||||||
|
reader.read_u8()?;
|
||||||
|
|
||||||
|
let cookie_len = reader.read_u8()? as usize;
|
||||||
|
let mut cookie = vec![0; cookie_len];
|
||||||
|
reader.read_exact(&mut cookie)?;
|
||||||
|
|
||||||
|
let cipher_suites_len = reader.read_u16::<BigEndian>()? as usize / 2;
|
||||||
|
let mut cipher_suites = vec![];
|
||||||
|
for _ in 0..cipher_suites_len {
|
||||||
|
let id: CipherSuiteId = reader.read_u16::<BigEndian>()?.into();
|
||||||
|
//let cipher_suite = cipher_suite_for_id(id)?;
|
||||||
|
cipher_suites.push(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
let compression_methods = CompressionMethods::unmarshal(reader)?;
|
||||||
|
let mut extensions = vec![];
|
||||||
|
|
||||||
|
let extension_buffer_len = reader.read_u16::<BigEndian>()? as usize;
|
||||||
|
let mut extension_buffer = vec![0u8; extension_buffer_len];
|
||||||
|
reader.read_exact(&mut extension_buffer)?;
|
||||||
|
|
||||||
|
let mut offset = 0;
|
||||||
|
while offset < extension_buffer_len {
|
||||||
|
let mut extension_reader = BufReader::new(&extension_buffer[offset..]);
|
||||||
|
if let Ok(extension) = Extension::unmarshal(&mut extension_reader) {
|
||||||
|
extensions.push(extension);
|
||||||
|
} else {
|
||||||
|
log::warn!(
|
||||||
|
"Unsupported Extension Type {} {}",
|
||||||
|
extension_buffer[offset],
|
||||||
|
extension_buffer[offset + 1]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let extension_len =
|
||||||
|
u16::from_be_bytes([extension_buffer[offset + 2], extension_buffer[offset + 3]])
|
||||||
|
as usize;
|
||||||
|
offset += 4 + extension_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(HandshakeMessageClientHello {
|
||||||
|
version: ProtocolVersion { major, minor },
|
||||||
|
random,
|
||||||
|
cookie,
|
||||||
|
|
||||||
|
cipher_suites,
|
||||||
|
compression_methods,
|
||||||
|
extensions,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
use std::io::{BufReader, BufWriter};
|
||||||
|
use std::time::{Duration, SystemTime};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::curve::named_curve::*;
|
||||||
|
use crate::extension::extension_supported_elliptic_curves::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_handshake_message_client_hello() -> Result<()> {
|
||||||
|
let raw_client_hello = vec![
|
||||||
|
0xfe, 0xfd, 0xb6, 0x2f, 0xce, 0x5c, 0x42, 0x54, 0xff, 0x86, 0xe1, 0x24, 0x41, 0x91, 0x42,
|
||||||
|
0x62, 0x15, 0xad, 0x16, 0xc9, 0x15, 0x8d, 0x95, 0x71, 0x8a, 0xbb, 0x22, 0xd7, 0x47, 0xec,
|
||||||
|
0xd8, 0x3d, 0xdc, 0x4b, 0x00, 0x14, 0xe6, 0x14, 0x3a, 0x1b, 0x04, 0xea, 0x9e, 0x7a, 0x14,
|
||||||
|
0xd6, 0x6c, 0x57, 0xd0, 0x0e, 0x32, 0x85, 0x76, 0x18, 0xde, 0xd8, 0x00, 0x04, 0xc0, 0x2b,
|
||||||
|
0xc0, 0x0a, 0x01, 0x00, 0x00, 0x08, 0x00, 0x0a, 0x00, 0x04, 0x00, 0x02, 0x00, 0x1d,
|
||||||
|
];
|
||||||
|
|
||||||
|
let gmt_unix_time = if let Some(unix_time) =
|
||||||
|
SystemTime::UNIX_EPOCH.checked_add(Duration::new(3056586332u64, 0))
|
||||||
|
{
|
||||||
|
unix_time
|
||||||
|
} else {
|
||||||
|
SystemTime::UNIX_EPOCH
|
||||||
|
};
|
||||||
|
let parsed_client_hello = HandshakeMessageClientHello {
|
||||||
|
version: ProtocolVersion {
|
||||||
|
major: 0xFE,
|
||||||
|
minor: 0xFD,
|
||||||
|
},
|
||||||
|
random: HandshakeRandom {
|
||||||
|
gmt_unix_time,
|
||||||
|
random_bytes: [
|
||||||
|
0x42, 0x54, 0xff, 0x86, 0xe1, 0x24, 0x41, 0x91, 0x42, 0x62, 0x15, 0xad, 0x16, 0xc9,
|
||||||
|
0x15, 0x8d, 0x95, 0x71, 0x8a, 0xbb, 0x22, 0xd7, 0x47, 0xec, 0xd8, 0x3d, 0xdc, 0x4b,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
cookie: vec![
|
||||||
|
0xe6, 0x14, 0x3a, 0x1b, 0x04, 0xea, 0x9e, 0x7a, 0x14, 0xd6, 0x6c, 0x57, 0xd0, 0x0e,
|
||||||
|
0x32, 0x85, 0x76, 0x18, 0xde, 0xd8,
|
||||||
|
],
|
||||||
|
cipher_suites: vec![
|
||||||
|
CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_128_Gcm_Sha256,
|
||||||
|
CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_256_Cbc_Sha,
|
||||||
|
//Box::<CipherSuiteTLSEcdheEcdsaWithAes128GcmSha256>::default(),
|
||||||
|
//Box::<CipherSuiteTLSEcdheEcdsaWithAes256CbcSha>::default(),
|
||||||
|
],
|
||||||
|
compression_methods: CompressionMethods {
|
||||||
|
ids: vec![CompressionMethodId::Null],
|
||||||
|
},
|
||||||
|
extensions: vec![Extension::SupportedEllipticCurves(
|
||||||
|
ExtensionSupportedEllipticCurves {
|
||||||
|
elliptic_curves: vec![NamedCurve::X25519],
|
||||||
|
},
|
||||||
|
)],
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut reader = BufReader::new(raw_client_hello.as_slice());
|
||||||
|
let c = HandshakeMessageClientHello::unmarshal(&mut reader)?;
|
||||||
|
assert_eq!(
|
||||||
|
c, parsed_client_hello,
|
||||||
|
"handshakeMessageClientHello unmarshal: got {c:?}, want {parsed_client_hello:?}"
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut raw = vec![];
|
||||||
|
{
|
||||||
|
let mut writer = BufWriter::<&mut Vec<u8>>::new(raw.as_mut());
|
||||||
|
c.marshal(&mut writer)?;
|
||||||
|
}
|
||||||
|
assert_eq!(
|
||||||
|
raw, raw_client_hello,
|
||||||
|
"handshakeMessageClientHello marshal: got {raw:?}, want {raw_client_hello:?}"
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod handshake_message_client_key_exchange_test;
|
||||||
|
|
||||||
|
use std::io::{Read, Write};
|
||||||
|
|
||||||
|
use byteorder::{BigEndian, WriteBytesExt};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct HandshakeMessageClientKeyExchange {
|
||||||
|
pub(crate) identity_hint: Vec<u8>,
|
||||||
|
pub(crate) public_key: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HandshakeMessageClientKeyExchange {
|
||||||
|
pub fn handshake_type(&self) -> HandshakeType {
|
||||||
|
HandshakeType::ClientKeyExchange
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn size(&self) -> usize {
|
||||||
|
if !self.public_key.is_empty() {
|
||||||
|
1 + self.public_key.len()
|
||||||
|
} else {
|
||||||
|
2 + self.identity_hint.len()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn marshal<W: Write>(&self, writer: &mut W) -> Result<()> {
|
||||||
|
if (!self.identity_hint.is_empty() && !self.public_key.is_empty())
|
||||||
|
|| (self.identity_hint.is_empty() && self.public_key.is_empty())
|
||||||
|
{
|
||||||
|
return Err(Error::ErrInvalidClientKeyExchange);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.public_key.is_empty() {
|
||||||
|
writer.write_u8(self.public_key.len() as u8)?;
|
||||||
|
writer.write_all(&self.public_key)?;
|
||||||
|
} else {
|
||||||
|
writer.write_u16::<BigEndian>(self.identity_hint.len() as u16)?;
|
||||||
|
writer.write_all(&self.identity_hint)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(writer.flush()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unmarshal<R: Read>(reader: &mut R) -> Result<Self> {
|
||||||
|
let mut data = vec![];
|
||||||
|
reader.read_to_end(&mut data)?;
|
||||||
|
|
||||||
|
// If parsed as PSK return early and only populate PSK Identity Hint
|
||||||
|
let psk_length = ((data[0] as u16) << 8) | data[1] as u16;
|
||||||
|
if data.len() == psk_length as usize + 2 {
|
||||||
|
return Ok(HandshakeMessageClientKeyExchange {
|
||||||
|
identity_hint: data[2..].to_vec(),
|
||||||
|
public_key: vec![],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let public_key_length = data[0] as usize;
|
||||||
|
if data.len() != public_key_length + 1 {
|
||||||
|
return Err(Error::ErrBufferTooSmall);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(HandshakeMessageClientKeyExchange {
|
||||||
|
identity_hint: vec![],
|
||||||
|
public_key: data[1..].to_vec(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
use std::io::{BufReader, BufWriter};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_handshake_message_client_key_exchange() -> Result<()> {
|
||||||
|
let raw_client_key_exchange = vec![
|
||||||
|
0x20, 0x26, 0x78, 0x4a, 0x78, 0x70, 0xc1, 0xf9, 0x71, 0xea, 0x50, 0x4a, 0xb5, 0xbb, 0x00,
|
||||||
|
0x76, 0x02, 0x05, 0xda, 0xf7, 0xd0, 0x3f, 0xe3, 0xf7, 0x4e, 0x8a, 0x14, 0x6f, 0xb7, 0xe0,
|
||||||
|
0xc0, 0xff, 0x54,
|
||||||
|
];
|
||||||
|
let parsed_client_key_exchange = HandshakeMessageClientKeyExchange {
|
||||||
|
identity_hint: vec![],
|
||||||
|
public_key: raw_client_key_exchange[1..].to_vec(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut reader = BufReader::new(raw_client_key_exchange.as_slice());
|
||||||
|
let c = HandshakeMessageClientKeyExchange::unmarshal(&mut reader)?;
|
||||||
|
assert_eq!(
|
||||||
|
c, parsed_client_key_exchange,
|
||||||
|
"parsedCertificateRequest unmarshal: got {c:?}, want {parsed_client_key_exchange:?}"
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut raw = vec![];
|
||||||
|
{
|
||||||
|
let mut writer = BufWriter::<&mut Vec<u8>>::new(raw.as_mut());
|
||||||
|
c.marshal(&mut writer)?;
|
||||||
|
}
|
||||||
|
assert_eq!(
|
||||||
|
raw, raw_client_key_exchange,
|
||||||
|
"handshakeMessageClientKeyExchange marshal: got {raw:?}, want {raw_client_key_exchange:?}"
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
34
libs/dtls/src/handshake/handshake_message_finished.rs
Normal file
34
libs/dtls/src/handshake/handshake_message_finished.rs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod handshake_message_finished_test;
|
||||||
|
|
||||||
|
use std::io::{Read, Write};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct HandshakeMessageFinished {
|
||||||
|
pub(crate) verify_data: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HandshakeMessageFinished {
|
||||||
|
pub fn handshake_type(&self) -> HandshakeType {
|
||||||
|
HandshakeType::Finished
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn size(&self) -> usize {
|
||||||
|
self.verify_data.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn marshal<W: Write>(&self, writer: &mut W) -> Result<()> {
|
||||||
|
writer.write_all(&self.verify_data)?;
|
||||||
|
|
||||||
|
Ok(writer.flush()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unmarshal<R: Read>(reader: &mut R) -> Result<Self> {
|
||||||
|
let mut verify_data: Vec<u8> = vec![];
|
||||||
|
reader.read_to_end(&mut verify_data)?;
|
||||||
|
|
||||||
|
Ok(HandshakeMessageFinished { verify_data })
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
use std::io::{BufReader, BufWriter};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_handshake_message_finished() -> Result<()> {
|
||||||
|
let raw_finished = vec![
|
||||||
|
0x01, 0x01, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
|
||||||
|
];
|
||||||
|
let parsed_finished = HandshakeMessageFinished {
|
||||||
|
verify_data: raw_finished.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut reader = BufReader::new(raw_finished.as_slice());
|
||||||
|
let c = HandshakeMessageFinished::unmarshal(&mut reader)?;
|
||||||
|
assert_eq!(
|
||||||
|
c, parsed_finished,
|
||||||
|
"handshakeMessageFinished unmarshal: got {c:?}, want {parsed_finished:?}"
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut raw = vec![];
|
||||||
|
{
|
||||||
|
let mut writer = BufWriter::<&mut Vec<u8>>::new(raw.as_mut());
|
||||||
|
c.marshal(&mut writer)?;
|
||||||
|
}
|
||||||
|
assert_eq!(
|
||||||
|
raw, raw_finished,
|
||||||
|
"handshakeMessageFinished marshal: got {raw:?}, want {raw_finished:?}"
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod handshake_message_hello_verify_request_test;
|
||||||
|
|
||||||
|
use std::io::{Read, Write};
|
||||||
|
|
||||||
|
use byteorder::{ReadBytesExt, WriteBytesExt};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::record_layer::record_layer_header::*;
|
||||||
|
|
||||||
|
/*
|
||||||
|
The definition of HelloVerifyRequest is as follows:
|
||||||
|
|
||||||
|
struct {
|
||||||
|
ProtocolVersion server_version;
|
||||||
|
opaque cookie<0..2^8-1>;
|
||||||
|
} HelloVerifyRequest;
|
||||||
|
|
||||||
|
The HelloVerifyRequest message type is hello_verify_request(3).
|
||||||
|
|
||||||
|
When the client sends its ClientHello message to the server, the server
|
||||||
|
MAY respond with a HelloVerifyRequest message. This message contains
|
||||||
|
a stateless cookie generated using the technique of [PHOTURIS]. The
|
||||||
|
client MUST retransmit the ClientHello with the cookie added.
|
||||||
|
*/
|
||||||
|
/// ## Specifications
|
||||||
|
///
|
||||||
|
/// * [RFC 6347 §4.2.1]
|
||||||
|
///
|
||||||
|
/// [RFC 6347 §4.2.1]: https://tools.ietf.org/html/rfc6347#section-4.2.1
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct HandshakeMessageHelloVerifyRequest {
|
||||||
|
pub(crate) version: ProtocolVersion,
|
||||||
|
pub(crate) cookie: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HandshakeMessageHelloVerifyRequest {
|
||||||
|
pub fn handshake_type(&self) -> HandshakeType {
|
||||||
|
HandshakeType::HelloVerifyRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn size(&self) -> usize {
|
||||||
|
1 + 1 + 1 + self.cookie.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn marshal<W: Write>(&self, writer: &mut W) -> Result<()> {
|
||||||
|
if self.cookie.len() > 255 {
|
||||||
|
return Err(Error::ErrCookieTooLong);
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.write_u8(self.version.major)?;
|
||||||
|
writer.write_u8(self.version.minor)?;
|
||||||
|
writer.write_u8(self.cookie.len() as u8)?;
|
||||||
|
writer.write_all(&self.cookie)?;
|
||||||
|
|
||||||
|
Ok(writer.flush()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unmarshal<R: Read>(reader: &mut R) -> Result<Self> {
|
||||||
|
let major = reader.read_u8()?;
|
||||||
|
let minor = reader.read_u8()?;
|
||||||
|
let cookie_length = reader.read_u8()?;
|
||||||
|
let mut cookie = vec![];
|
||||||
|
reader.read_to_end(&mut cookie)?;
|
||||||
|
|
||||||
|
if cookie.len() < cookie_length as usize {
|
||||||
|
return Err(Error::ErrBufferTooSmall);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(HandshakeMessageHelloVerifyRequest {
|
||||||
|
version: ProtocolVersion { major, minor },
|
||||||
|
cookie,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
use std::io::{BufReader, BufWriter};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_handshake_message_hello_verify_request() -> Result<()> {
|
||||||
|
let raw_hello_verify_request = vec![
|
||||||
|
0xfe, 0xff, 0x14, 0x25, 0xfb, 0xee, 0xb3, 0x7c, 0x95, 0xcf, 0x00, 0xeb, 0xad, 0xe2, 0xef,
|
||||||
|
0xc7, 0xfd, 0xbb, 0xed, 0xf7, 0x1f, 0x6c, 0xcd,
|
||||||
|
];
|
||||||
|
let parsed_hello_verify_request = HandshakeMessageHelloVerifyRequest {
|
||||||
|
version: ProtocolVersion {
|
||||||
|
major: 0xFE,
|
||||||
|
minor: 0xFF,
|
||||||
|
},
|
||||||
|
cookie: vec![
|
||||||
|
0x25, 0xfb, 0xee, 0xb3, 0x7c, 0x95, 0xcf, 0x00, 0xeb, 0xad, 0xe2, 0xef, 0xc7, 0xfd,
|
||||||
|
0xbb, 0xed, 0xf7, 0x1f, 0x6c, 0xcd,
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut reader = BufReader::new(raw_hello_verify_request.as_slice());
|
||||||
|
let c = HandshakeMessageHelloVerifyRequest::unmarshal(&mut reader)?;
|
||||||
|
assert_eq!(
|
||||||
|
c, parsed_hello_verify_request,
|
||||||
|
"parsed_hello_verify_request unmarshal: got {c:?}, want {parsed_hello_verify_request:?}"
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut raw = vec![];
|
||||||
|
{
|
||||||
|
let mut writer = BufWriter::<&mut Vec<u8>>::new(raw.as_mut());
|
||||||
|
c.marshal(&mut writer)?;
|
||||||
|
}
|
||||||
|
assert_eq!(
|
||||||
|
raw, raw_hello_verify_request,
|
||||||
|
"parsed_hello_verify_request marshal: got {raw:?}, want {raw_hello_verify_request:?}"
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
151
libs/dtls/src/handshake/handshake_message_server_hello.rs
Normal file
151
libs/dtls/src/handshake/handshake_message_server_hello.rs
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod handshake_message_server_hello_test;
|
||||||
|
|
||||||
|
use std::fmt;
|
||||||
|
use std::io::{BufReader, BufWriter};
|
||||||
|
|
||||||
|
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||||
|
|
||||||
|
use super::handshake_random::*;
|
||||||
|
use super::*;
|
||||||
|
use crate::cipher_suite::*;
|
||||||
|
use crate::compression_methods::*;
|
||||||
|
use crate::extension::*;
|
||||||
|
use crate::record_layer::record_layer_header::*;
|
||||||
|
|
||||||
|
/*
|
||||||
|
The server will send this message in response to a ClientHello
|
||||||
|
message when it was able to find an acceptable set of algorithms.
|
||||||
|
If it cannot find such a match, it will respond with a handshake
|
||||||
|
failure alert.
|
||||||
|
https://tools.ietf.org/html/rfc5246#section-7.4.1.3
|
||||||
|
*/
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct HandshakeMessageServerHello {
|
||||||
|
pub(crate) version: ProtocolVersion,
|
||||||
|
pub(crate) random: HandshakeRandom,
|
||||||
|
|
||||||
|
pub(crate) cipher_suite: CipherSuiteId,
|
||||||
|
pub(crate) compression_method: CompressionMethodId,
|
||||||
|
pub(crate) extensions: Vec<Extension>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for HandshakeMessageServerHello {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.version == other.version
|
||||||
|
&& self.random == other.random
|
||||||
|
&& self.compression_method == other.compression_method
|
||||||
|
&& self.extensions == other.extensions
|
||||||
|
&& self.cipher_suite == other.cipher_suite
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for HandshakeMessageServerHello {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
let s = [
|
||||||
|
format!("version: {:?} random: {:?}", self.version, self.random),
|
||||||
|
format!("cipher_suites: {:?}", self.cipher_suite),
|
||||||
|
format!("compression_method: {:?}", self.compression_method),
|
||||||
|
format!("extensions: {:?}", self.extensions),
|
||||||
|
];
|
||||||
|
write!(f, "{}", s.join(" "))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HandshakeMessageServerHello {
|
||||||
|
pub fn handshake_type(&self) -> HandshakeType {
|
||||||
|
HandshakeType::ServerHello
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn size(&self) -> usize {
|
||||||
|
let mut len = 2 + self.random.size();
|
||||||
|
|
||||||
|
// SessionID
|
||||||
|
len += 1;
|
||||||
|
|
||||||
|
len += 2;
|
||||||
|
|
||||||
|
len += 1;
|
||||||
|
|
||||||
|
len += 2;
|
||||||
|
for extension in &self.extensions {
|
||||||
|
len += extension.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
len
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn marshal<W: Write>(&self, writer: &mut W) -> Result<()> {
|
||||||
|
writer.write_u8(self.version.major)?;
|
||||||
|
writer.write_u8(self.version.minor)?;
|
||||||
|
self.random.marshal(writer)?;
|
||||||
|
|
||||||
|
// SessionID
|
||||||
|
writer.write_u8(0x00)?;
|
||||||
|
|
||||||
|
writer.write_u16::<BigEndian>(self.cipher_suite as u16)?;
|
||||||
|
|
||||||
|
writer.write_u8(self.compression_method as u8)?;
|
||||||
|
|
||||||
|
let mut extension_buffer = vec![];
|
||||||
|
{
|
||||||
|
let mut extension_writer = BufWriter::<&mut Vec<u8>>::new(extension_buffer.as_mut());
|
||||||
|
for extension in &self.extensions {
|
||||||
|
extension.marshal(&mut extension_writer)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.write_u16::<BigEndian>(extension_buffer.len() as u16)?;
|
||||||
|
writer.write_all(&extension_buffer)?;
|
||||||
|
|
||||||
|
Ok(writer.flush()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unmarshal<R: Read>(reader: &mut R) -> Result<Self> {
|
||||||
|
let major = reader.read_u8()?;
|
||||||
|
let minor = reader.read_u8()?;
|
||||||
|
let random = HandshakeRandom::unmarshal(reader)?;
|
||||||
|
|
||||||
|
// Session ID
|
||||||
|
let session_id_len = reader.read_u8()? as usize;
|
||||||
|
let mut session_id_buffer = vec![0u8; session_id_len];
|
||||||
|
reader.read_exact(&mut session_id_buffer)?;
|
||||||
|
|
||||||
|
let cipher_suite: CipherSuiteId = reader.read_u16::<BigEndian>()?.into();
|
||||||
|
|
||||||
|
let compression_method = reader.read_u8()?.into();
|
||||||
|
let mut extensions = vec![];
|
||||||
|
|
||||||
|
let extension_buffer_len = reader.read_u16::<BigEndian>()? as usize;
|
||||||
|
let mut extension_buffer = vec![0u8; extension_buffer_len];
|
||||||
|
reader.read_exact(&mut extension_buffer)?;
|
||||||
|
|
||||||
|
let mut offset = 0;
|
||||||
|
while offset < extension_buffer_len {
|
||||||
|
let mut extension_reader = BufReader::new(&extension_buffer[offset..]);
|
||||||
|
if let Ok(extension) = Extension::unmarshal(&mut extension_reader) {
|
||||||
|
extensions.push(extension);
|
||||||
|
} else {
|
||||||
|
log::warn!(
|
||||||
|
"Unsupported Extension Type {} {}",
|
||||||
|
extension_buffer[offset],
|
||||||
|
extension_buffer[offset + 1]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let extension_len =
|
||||||
|
u16::from_be_bytes([extension_buffer[offset + 2], extension_buffer[offset + 3]])
|
||||||
|
as usize;
|
||||||
|
offset += 4 + extension_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(HandshakeMessageServerHello {
|
||||||
|
version: ProtocolVersion { major, minor },
|
||||||
|
random,
|
||||||
|
|
||||||
|
cipher_suite,
|
||||||
|
compression_method,
|
||||||
|
extensions,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
use std::io::{BufReader, BufWriter};
|
||||||
|
use std::time::{Duration, SystemTime};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_handshake_message_server_hello() -> Result<()> {
|
||||||
|
let raw_server_hello = vec![
|
||||||
|
0xfe, 0xfd, 0x21, 0x63, 0x32, 0x21, 0x81, 0x0e, 0x98, 0x6c, 0x85, 0x3d, 0xa4, 0x39, 0xaf,
|
||||||
|
0x5f, 0xd6, 0x5c, 0xcc, 0x20, 0x7f, 0x7c, 0x78, 0xf1, 0x5f, 0x7e, 0x1c, 0xb7, 0xa1, 0x1e,
|
||||||
|
0xcf, 0x63, 0x84, 0x28, 0x00, 0xc0, 0x2b, 0x00, 0x00, 0x00,
|
||||||
|
];
|
||||||
|
|
||||||
|
let gmt_unix_time = if let Some(unix_time) =
|
||||||
|
SystemTime::UNIX_EPOCH.checked_add(Duration::new(560149025u64, 0))
|
||||||
|
{
|
||||||
|
unix_time
|
||||||
|
} else {
|
||||||
|
SystemTime::UNIX_EPOCH
|
||||||
|
};
|
||||||
|
let parsed_server_hello = HandshakeMessageServerHello {
|
||||||
|
version: ProtocolVersion {
|
||||||
|
major: 0xFE,
|
||||||
|
minor: 0xFD,
|
||||||
|
},
|
||||||
|
random: HandshakeRandom {
|
||||||
|
gmt_unix_time,
|
||||||
|
random_bytes: [
|
||||||
|
0x81, 0x0e, 0x98, 0x6c, 0x85, 0x3d, 0xa4, 0x39, 0xaf, 0x5f, 0xd6, 0x5c, 0xcc, 0x20,
|
||||||
|
0x7f, 0x7c, 0x78, 0xf1, 0x5f, 0x7e, 0x1c, 0xb7, 0xa1, 0x1e, 0xcf, 0x63, 0x84, 0x28,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
cipher_suite: CipherSuiteId::Tls_Ecdhe_Ecdsa_With_Aes_128_Gcm_Sha256,
|
||||||
|
compression_method: CompressionMethodId::Null,
|
||||||
|
extensions: vec![],
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut reader = BufReader::new(raw_server_hello.as_slice());
|
||||||
|
let c = HandshakeMessageServerHello::unmarshal(&mut reader)?;
|
||||||
|
assert_eq!(
|
||||||
|
c, parsed_server_hello,
|
||||||
|
"handshakeMessageServerHello unmarshal: got {c:?}, want {parsed_server_hello:?}"
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut raw = vec![];
|
||||||
|
{
|
||||||
|
let mut writer = BufWriter::<&mut Vec<u8>>::new(raw.as_mut());
|
||||||
|
c.marshal(&mut writer)?;
|
||||||
|
}
|
||||||
|
assert_eq!(
|
||||||
|
raw, raw_server_hello,
|
||||||
|
"handshakeMessageServerHello marshal: got {raw:?}, want {raw_server_hello:?}"
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod handshake_message_server_hello_done_test;
|
||||||
|
|
||||||
|
use std::io::{Read, Write};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct HandshakeMessageServerHelloDone;
|
||||||
|
|
||||||
|
impl HandshakeMessageServerHelloDone {
|
||||||
|
pub fn handshake_type(&self) -> HandshakeType {
|
||||||
|
HandshakeType::ServerHelloDone
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn size(&self) -> usize {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn marshal<W: Write>(&self, _writer: &mut W) -> Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unmarshal<R: Read>(_reader: &mut R) -> Result<Self> {
|
||||||
|
Ok(HandshakeMessageServerHelloDone {})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
use std::io::{BufReader, BufWriter};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_handshake_message_server_hello_done() -> Result<()> {
|
||||||
|
let raw_server_hello_done = vec![];
|
||||||
|
let parsed_server_hello_done = HandshakeMessageServerHelloDone {};
|
||||||
|
|
||||||
|
let mut reader = BufReader::new(raw_server_hello_done.as_slice());
|
||||||
|
let c = HandshakeMessageServerHelloDone::unmarshal(&mut reader)?;
|
||||||
|
assert_eq!(
|
||||||
|
c, parsed_server_hello_done,
|
||||||
|
"handshakeMessageServerHelloDone unmarshal: got {c:?}, want {parsed_server_hello_done:?}"
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut raw = vec![];
|
||||||
|
{
|
||||||
|
let mut writer = BufWriter::<&mut Vec<u8>>::new(raw.as_mut());
|
||||||
|
c.marshal(&mut writer)?;
|
||||||
|
}
|
||||||
|
assert_eq!(
|
||||||
|
raw, raw_server_hello_done,
|
||||||
|
"handshakeMessageServerHelloDone marshal: got {raw:?}, want {raw_server_hello_done:?}"
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
133
libs/dtls/src/handshake/handshake_message_server_key_exchange.rs
Normal file
133
libs/dtls/src/handshake/handshake_message_server_key_exchange.rs
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod handshake_message_server_key_exchange_test;
|
||||||
|
|
||||||
|
use std::io::{Read, Write};
|
||||||
|
|
||||||
|
use byteorder::{BigEndian, WriteBytesExt};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::curve::named_curve::*;
|
||||||
|
use crate::curve::*;
|
||||||
|
use crate::signature_hash_algorithm::*;
|
||||||
|
|
||||||
|
// Structure supports ECDH and PSK
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct HandshakeMessageServerKeyExchange {
|
||||||
|
pub(crate) identity_hint: Vec<u8>,
|
||||||
|
|
||||||
|
pub(crate) elliptic_curve_type: EllipticCurveType,
|
||||||
|
pub(crate) named_curve: NamedCurve,
|
||||||
|
pub(crate) public_key: Vec<u8>,
|
||||||
|
pub(crate) algorithm: SignatureHashAlgorithm,
|
||||||
|
pub(crate) signature: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HandshakeMessageServerKeyExchange {
|
||||||
|
pub fn handshake_type(&self) -> HandshakeType {
|
||||||
|
HandshakeType::ServerKeyExchange
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn size(&self) -> usize {
|
||||||
|
if !self.identity_hint.is_empty() {
|
||||||
|
2 + self.identity_hint.len()
|
||||||
|
} else {
|
||||||
|
1 + 2 + 1 + self.public_key.len() + 2 + 2 + self.signature.len()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn marshal<W: Write>(&self, writer: &mut W) -> Result<()> {
|
||||||
|
if !self.identity_hint.is_empty() {
|
||||||
|
writer.write_u16::<BigEndian>(self.identity_hint.len() as u16)?;
|
||||||
|
writer.write_all(&self.identity_hint)?;
|
||||||
|
return Ok(writer.flush()?);
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.write_u8(self.elliptic_curve_type as u8)?;
|
||||||
|
writer.write_u16::<BigEndian>(self.named_curve as u16)?;
|
||||||
|
|
||||||
|
writer.write_u8(self.public_key.len() as u8)?;
|
||||||
|
writer.write_all(&self.public_key)?;
|
||||||
|
|
||||||
|
writer.write_u8(self.algorithm.hash as u8)?;
|
||||||
|
writer.write_u8(self.algorithm.signature as u8)?;
|
||||||
|
|
||||||
|
writer.write_u16::<BigEndian>(self.signature.len() as u16)?;
|
||||||
|
writer.write_all(&self.signature)?;
|
||||||
|
|
||||||
|
Ok(writer.flush()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unmarshal<R: Read>(reader: &mut R) -> Result<Self> {
|
||||||
|
let mut data = vec![];
|
||||||
|
reader.read_to_end(&mut data)?;
|
||||||
|
|
||||||
|
// If parsed as PSK return early and only populate PSK Identity Hint
|
||||||
|
let psk_length = ((data[0] as u16) << 8) | data[1] as u16;
|
||||||
|
if data.len() == psk_length as usize + 2 {
|
||||||
|
return Ok(HandshakeMessageServerKeyExchange {
|
||||||
|
identity_hint: data[2..].to_vec(),
|
||||||
|
|
||||||
|
elliptic_curve_type: EllipticCurveType::Unsupported,
|
||||||
|
named_curve: NamedCurve::Unsupported,
|
||||||
|
public_key: vec![],
|
||||||
|
algorithm: SignatureHashAlgorithm {
|
||||||
|
hash: HashAlgorithm::Unsupported,
|
||||||
|
signature: SignatureAlgorithm::Unsupported,
|
||||||
|
},
|
||||||
|
signature: vec![],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let elliptic_curve_type = data[0].into();
|
||||||
|
if data[1..].len() < 2 {
|
||||||
|
return Err(Error::ErrBufferTooSmall);
|
||||||
|
}
|
||||||
|
|
||||||
|
let named_curve = (((data[1] as u16) << 8) | data[2] as u16).into();
|
||||||
|
if data.len() < 4 {
|
||||||
|
return Err(Error::ErrBufferTooSmall);
|
||||||
|
}
|
||||||
|
|
||||||
|
let public_key_length = data[3] as usize;
|
||||||
|
let mut offset = 4 + public_key_length;
|
||||||
|
if data.len() < offset {
|
||||||
|
return Err(Error::ErrBufferTooSmall);
|
||||||
|
}
|
||||||
|
let public_key = data[4..offset].to_vec();
|
||||||
|
if data.len() <= offset {
|
||||||
|
return Err(Error::ErrBufferTooSmall);
|
||||||
|
}
|
||||||
|
|
||||||
|
let hash_algorithm = data[offset].into();
|
||||||
|
offset += 1;
|
||||||
|
if data.len() <= offset {
|
||||||
|
return Err(Error::ErrBufferTooSmall);
|
||||||
|
}
|
||||||
|
|
||||||
|
let signature_algorithm = data[offset].into();
|
||||||
|
offset += 1;
|
||||||
|
if data.len() < offset + 2 {
|
||||||
|
return Err(Error::ErrBufferTooSmall);
|
||||||
|
}
|
||||||
|
|
||||||
|
let signature_length = (((data[offset] as u16) << 8) | data[offset + 1] as u16) as usize;
|
||||||
|
offset += 2;
|
||||||
|
if data.len() < offset + signature_length {
|
||||||
|
return Err(Error::ErrBufferTooSmall);
|
||||||
|
}
|
||||||
|
let signature = data[offset..offset + signature_length].to_vec();
|
||||||
|
|
||||||
|
Ok(HandshakeMessageServerKeyExchange {
|
||||||
|
identity_hint: vec![],
|
||||||
|
|
||||||
|
elliptic_curve_type,
|
||||||
|
named_curve,
|
||||||
|
public_key,
|
||||||
|
algorithm: SignatureHashAlgorithm {
|
||||||
|
hash: hash_algorithm,
|
||||||
|
signature: signature_algorithm,
|
||||||
|
},
|
||||||
|
signature,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
use std::io::{BufReader, BufWriter};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_handshake_message_server_key_exchange() -> Result<()> {
|
||||||
|
let raw_server_key_exchange = vec![
|
||||||
|
0x03, 0x00, 0x1d, 0x41, 0x04, 0x0c, 0xb9, 0xa3, 0xb9, 0x90, 0x71, 0x35, 0x4a, 0x08, 0x66,
|
||||||
|
0xaf, 0xd6, 0x88, 0x58, 0x29, 0x69, 0x98, 0xf1, 0x87, 0x0f, 0xb5, 0xa8, 0xcd, 0x92, 0xf6,
|
||||||
|
0x2b, 0x08, 0x0c, 0xd4, 0x16, 0x5b, 0xcc, 0x81, 0xf2, 0x58, 0x91, 0x8e, 0x62, 0xdf, 0xc1,
|
||||||
|
0xec, 0x72, 0xe8, 0x47, 0x24, 0x42, 0x96, 0xb8, 0x7b, 0xee, 0xe7, 0x0d, 0xdc, 0x44, 0xec,
|
||||||
|
0xf3, 0x97, 0x6b, 0x1b, 0x45, 0x28, 0xac, 0x3f, 0x35, 0x02, 0x03, 0x00, 0x47, 0x30, 0x45,
|
||||||
|
0x02, 0x21, 0x00, 0xb2, 0x0b, 0x22, 0x95, 0x3d, 0x56, 0x57, 0x6a, 0x3f, 0x85, 0x30, 0x6f,
|
||||||
|
0x55, 0xc3, 0xf4, 0x24, 0x1b, 0x21, 0x07, 0xe5, 0xdf, 0xba, 0x24, 0x02, 0x68, 0x95, 0x1f,
|
||||||
|
0x6e, 0x13, 0xbd, 0x9f, 0xaa, 0x02, 0x20, 0x49, 0x9c, 0x9d, 0xdf, 0x84, 0x60, 0x33, 0x27,
|
||||||
|
0x96, 0x9e, 0x58, 0x6d, 0x72, 0x13, 0xe7, 0x3a, 0xe8, 0xdf, 0x43, 0x75, 0xc7, 0xb9, 0x37,
|
||||||
|
0x6e, 0x90, 0xe5, 0x3b, 0x81, 0xd4, 0xda, 0x68, 0xcd,
|
||||||
|
];
|
||||||
|
let parsed_server_key_exchange = HandshakeMessageServerKeyExchange {
|
||||||
|
identity_hint: vec![],
|
||||||
|
elliptic_curve_type: EllipticCurveType::NamedCurve,
|
||||||
|
named_curve: NamedCurve::X25519,
|
||||||
|
public_key: raw_server_key_exchange[4..69].to_vec(),
|
||||||
|
algorithm: SignatureHashAlgorithm {
|
||||||
|
hash: HashAlgorithm::Sha1,
|
||||||
|
signature: SignatureAlgorithm::Ecdsa,
|
||||||
|
},
|
||||||
|
|
||||||
|
signature: raw_server_key_exchange[73..144].to_vec(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut reader = BufReader::new(raw_server_key_exchange.as_slice());
|
||||||
|
let c = HandshakeMessageServerKeyExchange::unmarshal(&mut reader)?;
|
||||||
|
assert_eq!(
|
||||||
|
c, parsed_server_key_exchange,
|
||||||
|
"handshakeMessageServerKeyExchange unmarshal: got {c:?}, want {parsed_server_key_exchange:?}"
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut raw = vec![];
|
||||||
|
{
|
||||||
|
let mut writer = BufWriter::<&mut Vec<u8>>::new(raw.as_mut());
|
||||||
|
c.marshal(&mut writer)?;
|
||||||
|
}
|
||||||
|
assert_eq!(
|
||||||
|
raw, raw_server_key_exchange,
|
||||||
|
"handshakeMessageServerKeyExchange marshal: got {raw:?}, want {raw_server_key_exchange:?}"
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
71
libs/dtls/src/handshake/handshake_random.rs
Normal file
71
libs/dtls/src/handshake/handshake_random.rs
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
use std::io::{self, Read, Write};
|
||||||
|
use std::time::{Duration, SystemTime};
|
||||||
|
|
||||||
|
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||||
|
use rand::Rng;
|
||||||
|
|
||||||
|
pub const RANDOM_BYTES_LENGTH: usize = 28;
|
||||||
|
pub const HANDSHAKE_RANDOM_LENGTH: usize = RANDOM_BYTES_LENGTH + 4;
|
||||||
|
|
||||||
|
/// ## Specifications
|
||||||
|
///
|
||||||
|
/// * [RFC 4346 §7.4.1.2]
|
||||||
|
///
|
||||||
|
/// [RFC 4346 §7.4.1.2]: https://tools.ietf.org/html/rfc4346#section-7.4.1.2
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct HandshakeRandom {
|
||||||
|
pub gmt_unix_time: SystemTime,
|
||||||
|
pub random_bytes: [u8; RANDOM_BYTES_LENGTH],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for HandshakeRandom {
|
||||||
|
fn default() -> Self {
|
||||||
|
HandshakeRandom {
|
||||||
|
gmt_unix_time: SystemTime::UNIX_EPOCH,
|
||||||
|
random_bytes: [0u8; RANDOM_BYTES_LENGTH],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HandshakeRandom {
|
||||||
|
pub fn size(&self) -> usize {
|
||||||
|
4 + RANDOM_BYTES_LENGTH
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn marshal<W: Write>(&self, writer: &mut W) -> io::Result<()> {
|
||||||
|
let secs = match self.gmt_unix_time.duration_since(SystemTime::UNIX_EPOCH) {
|
||||||
|
Ok(d) => d.as_secs() as u32,
|
||||||
|
Err(_) => 0,
|
||||||
|
};
|
||||||
|
writer.write_u32::<BigEndian>(secs)?;
|
||||||
|
writer.write_all(&self.random_bytes)?;
|
||||||
|
|
||||||
|
writer.flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unmarshal<R: Read>(reader: &mut R) -> io::Result<Self> {
|
||||||
|
let secs = reader.read_u32::<BigEndian>()?;
|
||||||
|
let gmt_unix_time = if let Some(unix_time) =
|
||||||
|
SystemTime::UNIX_EPOCH.checked_add(Duration::new(secs as u64, 0))
|
||||||
|
{
|
||||||
|
unix_time
|
||||||
|
} else {
|
||||||
|
SystemTime::UNIX_EPOCH
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut random_bytes = [0u8; RANDOM_BYTES_LENGTH];
|
||||||
|
reader.read_exact(&mut random_bytes)?;
|
||||||
|
|
||||||
|
Ok(HandshakeRandom {
|
||||||
|
gmt_unix_time,
|
||||||
|
random_bytes,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// populate fills the HandshakeRandom with random values
|
||||||
|
// may be called multiple times
|
||||||
|
pub fn populate(&mut self) {
|
||||||
|
self.gmt_unix_time = SystemTime::now();
|
||||||
|
rand::rng().fill(&mut self.random_bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
70
libs/dtls/src/handshake/handshake_test.rs
Normal file
70
libs/dtls/src/handshake/handshake_test.rs
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
use std::io::{BufReader, BufWriter};
|
||||||
|
use std::time::{Duration, SystemTime};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::compression_methods::*;
|
||||||
|
use crate::handshake::handshake_message_client_hello::*;
|
||||||
|
use crate::handshake::handshake_random::HandshakeRandom;
|
||||||
|
use crate::record_layer::record_layer_header::ProtocolVersion;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_handshake_message() -> Result<()> {
|
||||||
|
let raw_handshake_message = vec![
|
||||||
|
0x01, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x29, 0xfe, 0xfd, 0xb6,
|
||||||
|
0x2f, 0xce, 0x5c, 0x42, 0x54, 0xff, 0x86, 0xe1, 0x24, 0x41, 0x91, 0x42, 0x62, 0x15, 0xad,
|
||||||
|
0x16, 0xc9, 0x15, 0x8d, 0x95, 0x71, 0x8a, 0xbb, 0x22, 0xd7, 0x47, 0xec, 0xd8, 0x3d, 0xdc,
|
||||||
|
0x4b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
];
|
||||||
|
let parsed_handshake = Handshake {
|
||||||
|
handshake_header: HandshakeHeader {
|
||||||
|
handshake_type: HandshakeType::ClientHello,
|
||||||
|
length: 0x29,
|
||||||
|
message_sequence: 0,
|
||||||
|
fragment_offset: 0,
|
||||||
|
fragment_length: 0x29,
|
||||||
|
},
|
||||||
|
handshake_message: HandshakeMessage::ClientHello(HandshakeMessageClientHello {
|
||||||
|
version: ProtocolVersion {
|
||||||
|
major: 0xFE,
|
||||||
|
minor: 0xFD,
|
||||||
|
},
|
||||||
|
random: HandshakeRandom {
|
||||||
|
gmt_unix_time: if let Some(unix_time) =
|
||||||
|
SystemTime::UNIX_EPOCH.checked_add(Duration::new(3056586332u64, 0))
|
||||||
|
{
|
||||||
|
unix_time
|
||||||
|
} else {
|
||||||
|
SystemTime::UNIX_EPOCH
|
||||||
|
},
|
||||||
|
random_bytes: [
|
||||||
|
0x42, 0x54, 0xff, 0x86, 0xe1, 0x24, 0x41, 0x91, 0x42, 0x62, 0x15, 0xad, 0x16,
|
||||||
|
0xc9, 0x15, 0x8d, 0x95, 0x71, 0x8a, 0xbb, 0x22, 0xd7, 0x47, 0xec, 0xd8, 0x3d,
|
||||||
|
0xdc, 0x4b,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
cookie: vec![],
|
||||||
|
cipher_suites: vec![],
|
||||||
|
compression_methods: CompressionMethods { ids: vec![] },
|
||||||
|
extensions: vec![],
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut reader = BufReader::new(raw_handshake_message.as_slice());
|
||||||
|
let h = Handshake::unmarshal(&mut reader)?;
|
||||||
|
assert_eq!(
|
||||||
|
h, parsed_handshake,
|
||||||
|
"handshakeMessageClientHello unmarshal: got {h:?}, want {parsed_handshake:?}"
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut raw = vec![];
|
||||||
|
{
|
||||||
|
let mut writer = BufWriter::<&mut Vec<u8>>::new(raw.as_mut());
|
||||||
|
h.marshal(&mut writer)?;
|
||||||
|
}
|
||||||
|
assert_eq!(
|
||||||
|
raw, raw_handshake_message,
|
||||||
|
"handshakeMessageClientHello marshal: got {raw:?}, want {raw_handshake_message:?}"
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user