Expand Shadowsocks runtime compatibility

This commit is contained in:
JettChenT 2026-06-05 10:33:41 -07:00
parent d1638726ca
commit 4d1f589280
167 changed files with 57173 additions and 1640 deletions

View file

@ -183,15 +183,20 @@ private struct StoredProxySubscriptionNodeConfig: Decodable {
var network: StoredProxySubscriptionNetworkTransport?
var cipher: String?
var plugin: StoredJSONValue?
var smux: StoredJSONValue?
var udp: Bool?
var udpOverTCP: Bool?
var clientFingerprint: String?
var runtimeSupported: Bool {
switch type {
case "trojan":
return network?.isTrojanTCP ?? true
case "shadowsocks":
return plugin == nil
&& udpOverTCP != true
guard Self.supportedShadowsocksSmux(smux) else {
return false
}
return Self.supportedShadowsocksPlugin(plugin, clientFingerprint: clientFingerprint)
&& Self.supportedShadowsocksCiphers.contains(cipher?.lowercased() ?? "")
default:
return false
@ -203,18 +208,215 @@ private struct StoredProxySubscriptionNodeConfig: Decodable {
case network
case cipher
case plugin
case smux
case udp
case udpOverTCP = "udp_over_tcp"
case clientFingerprint = "client_fingerprint"
}
private static let supportedShadowsocksCiphers: Set<String> = [
"none",
"plain",
"table",
"rc4-md5",
"rc4",
"aes-128-ctr",
"aes-192-ctr",
"aes-256-ctr",
"aes-128-cfb",
"aes-128-cfb1",
"aes-128-cfb8",
"aes-128-cfb128",
"aes-192-cfb",
"aes-192-cfb1",
"aes-192-cfb8",
"aes-192-cfb128",
"aes-256-cfb",
"aes-256-cfb1",
"aes-256-cfb8",
"aes-256-cfb128",
"aes-128-ofb",
"aes-192-ofb",
"aes-256-ofb",
"camellia-128-ctr",
"camellia-192-ctr",
"camellia-256-ctr",
"camellia-128-cfb",
"camellia-128-cfb1",
"camellia-128-cfb8",
"camellia-128-cfb128",
"camellia-192-cfb",
"camellia-192-cfb1",
"camellia-192-cfb8",
"camellia-192-cfb128",
"camellia-256-cfb",
"camellia-256-cfb1",
"camellia-256-cfb8",
"camellia-256-cfb128",
"camellia-128-ofb",
"camellia-192-ofb",
"camellia-256-ofb",
"chacha20",
"chacha20-ietf",
"xchacha20",
"aes-128-gcm",
"aead_aes_128_gcm",
"aes-192-gcm",
"aes-256-gcm",
"aead_aes_256_gcm",
"aes-128-ccm",
"aes-192-ccm",
"aes-256-ccm",
"aes-128-gcm-siv",
"aes-256-gcm-siv",
"chacha8-ietf-poly1305",
"chacha20-ietf-poly1305",
"chacha20-poly1305",
"aead_chacha20_poly1305",
"rabbit128-poly1305",
"xchacha8-ietf-poly1305",
"xchacha20-ietf-poly1305",
"sm4-gcm",
"sm4-ccm",
"aegis-128l",
"aegis-256",
"aez-384",
"deoxys-ii-256-128",
"ascon128",
"ascon128a",
"lea-128-gcm",
"lea-192-gcm",
"lea-256-gcm",
"2022-blake3-aes-128-gcm",
"2022-blake3-aes-256-gcm",
"2022-blake3-aes-128-ccm",
"2022-blake3-aes-256-ccm",
"2022-blake3-chacha20-poly1305",
"2022-blake3-chacha8-poly1305",
]
private static func supportedShadowsocksPlugin(
_ plugin: StoredJSONValue?,
clientFingerprint: String?
) -> Bool {
guard let plugin else {
return true
}
guard case .object(let object) = plugin,
case .string(let name)? = object["name"]
else {
return false
}
let normalizedName = name.trimmingCharacters(in: .whitespacesAndNewlines)
let opts: [String: StoredJSONValue]
if case .object(let decodedOpts)? = object["opts"] {
opts = decodedOpts
} else {
opts = [:]
}
if normalizedName == "restls",
let clientFingerprint,
Self.shadowsocksClientFingerprintIsActive(clientFingerprint)
{
return false
}
if normalizedName == "v2ray-plugin" || normalizedName == "gost-plugin" {
guard let mode = opts["mode"]?.stringValue ?? opts["obfs"]?.stringValue else {
return false
}
guard mode.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() == "websocket" else {
return false
}
return true
}
if normalizedName == "shadow-tls" {
let version = opts["version"]?.stringValue ?? "2"
let normalizedVersion = version.trimmingCharacters(in: .whitespacesAndNewlines)
switch normalizedVersion {
case "1":
return true
case "2", "3":
if let clientFingerprint,
Self.shadowsocksClientFingerprintIsActive(clientFingerprint)
{
return false
}
return true
default:
return false
}
}
if normalizedName == "kcptun" {
guard let smuxVersion = opts["smuxver"]?.stringValue else {
return true
}
guard let version = Double(smuxVersion.trimmingCharacters(in: .whitespacesAndNewlines)) else {
return false
}
return version >= 0
}
if normalizedName == "restls" {
let versionHint = opts["version-hint"]?.stringValue ?? ""
switch versionHint.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() {
case "tls12", "tls13":
return true
default:
return false
}
}
guard normalizedName == "obfs" else {
return true
}
guard let mode = opts["mode"]?.stringValue ?? opts["obfs"]?.stringValue else {
return false
}
switch mode.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() {
case "http", "tls":
return true
default:
return false
}
}
private static func shadowsocksClientFingerprintIsActive(_ value: String) -> Bool {
let normalized = value.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
return [
"chrome",
"firefox",
"safari",
"ios",
"android",
"edge",
"360",
"qq",
"random",
"chrome120",
"firefox120",
"safari16",
"chrome_psk",
"chrome_psk_shuffle",
"chrome_padding_psk_shuffle",
"chrome_pq",
"chrome_pq_psk",
"randomized",
].contains(normalized)
}
private static func supportedShadowsocksSmux(_ smux: StoredJSONValue?) -> Bool {
guard case .object(let object)? = smux else {
return true
}
guard object["enabled"]?.boolValue ?? false else {
return true
}
let protocolName = object["protocol"]?.stringValue?
.trimmingCharacters(in: .whitespacesAndNewlines)
.lowercased() ?? "h2mux"
guard protocolName == "h2mux" || protocolName == "smux" || protocolName == "yamux" else {
return false
}
return true
}
}
private enum StoredProxySubscriptionNetworkTransport: Decodable, Hashable {
@ -247,6 +449,42 @@ private enum StoredJSONValue: Decodable, Hashable {
case array([StoredJSONValue])
case null
var stringValue: String? {
switch self {
case .string(let value):
return value
case .number(let value):
if value.rounded(.towardZero) == value {
return String(Int(value))
}
return String(value)
case .bool(let value):
return String(value)
default:
return nil
}
}
var boolValue: Bool? {
switch self {
case .bool(let value):
return value
case .string(let value):
switch value.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() {
case "1", "true", "yes", "y":
return true
case "0", "false", "no", "n":
return false
default:
return nil
}
case .number(let value):
return value != 0
default:
return nil
}
}
init(from decoder: Swift.Decoder) throws {
let container = try decoder.singleValueContainer()
if container.decodeNil() {

1601
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -3,6 +3,10 @@ members = ["burrow", "tun"]
resolver = "2"
exclude = ["burrow-gtk"]
[patch.crates-io]
rustls-fork-shadow-tls = { path = "third_party/rustls-fork-shadow-tls" }
tokio-rustls-fork-shadow-tls = { path = "third_party/tokio-rustls-fork-shadow-tls" }
[profile.release]
lto = true
panic = "abort"

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,504 @@
#!/usr/bin/env bash
set -euo pipefail
usage() {
cat <<'EOF'
Usage: Scripts/burrow-shadowsocks-singmux-interop
Run controlled Burrow Shadowsocks top-level sing-mux interop tests against the
same github.com/metacubex/sing-mux Go module used by Mihomo. By default this
checks smux, yamux, and h2mux with and without Mihomo's padded sing-mux
preface/framing. The test does not change system proxy settings or use the
user's Burrow database.
EOF
}
if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
usage
exit 0
fi
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
burrow_bin="${BURROW_BIN:-$repo_root/target/debug/burrow}"
sing_mux_dir="${SING_MUX_DIR:-/Users/jettchen/go/pkg/mod/github.com/metacubex/sing-mux@v0.3.9}"
sing_dir="${SING_DIR:-/Users/jettchen/go/pkg/mod/github.com/metacubex/sing@v0.5.7}"
if [[ -z "${BURROW_SINGMUX_INTEROP_SINGLE:-}" ]]; then
for protocol in ${BURROW_SINGMUX_PROTOCOLS:-smux yamux h2mux}; do
for padding in ${BURROW_SINGMUX_PADDING_MODES:-false true}; do
echo "=== Mihomo sing-mux interop: $protocol padding=$padding ==="
BURROW_SINGMUX_INTEROP_SINGLE=1 \
BURROW_SINGMUX_PROTOCOL="$protocol" \
BURROW_SINGMUX_PADDING="$padding" \
"$0"
done
done
exit 0
fi
protocol="${BURROW_SINGMUX_PROTOCOL:-smux}"
case "$protocol" in
smux | yamux | h2mux) ;;
*)
echo "unsupported sing-mux protocol: $protocol" >&2
exit 2
;;
esac
padding="${BURROW_SINGMUX_PADDING:-false}"
case "$padding" in
false | true) ;;
*)
echo "unsupported sing-mux padding mode: $padding" >&2
exit 2
;;
esac
node_udp="${BURROW_SINGMUX_NODE_UDP:-false}"
case "$node_udp" in
false | true) ;;
*)
echo "unsupported Shadowsocks node udp flag: $node_udp" >&2
exit 2
;;
esac
if [[ -z "${BURROW_BIN:-}" ]]; then
(cd "$repo_root" && cargo build -p burrow)
elif [[ ! -x "$burrow_bin" ]]; then
echo "BURROW_BIN is not executable: $burrow_bin" >&2
exit 2
fi
if ! command -v go >/dev/null 2>&1; then
echo "go is required for the Mihomo sing-mux interop peer" >&2
exit 2
fi
if ! command -v uv >/dev/null 2>&1; then
echo "uv is required for result assertions" >&2
exit 2
fi
if [[ ! -d "$sing_mux_dir" ]]; then
echo "sing-mux module directory not found: $sing_mux_dir" >&2
exit 2
fi
if [[ ! -d "$sing_dir" ]]; then
echo "sing module directory not found: $sing_dir" >&2
exit 2
fi
tmpdir="$(mktemp -d "${TMPDIR:-/tmp}/burrow-singmux-interop.XXXXXX")"
daemon_pid=""
server_pid=""
status=0
cleanup() {
if [[ -n "$server_pid" ]]; then
kill "$server_pid" >/dev/null 2>&1 || true
fi
if [[ -n "$daemon_pid" ]]; then
kill "$daemon_pid" >/dev/null 2>&1 || true
fi
if [[ "${BURROW_SELFTEST_KEEP:-}" == "1" || "$status" -ne 0 ]]; then
echo "preserving sing-mux interop artifacts: $tmpdir" >&2
else
rm -rf "$tmpdir"
fi
}
trap 'status=$?; cleanup' EXIT
socket="$tmpdir/burrow.sock"
port_file="$tmpdir/singmux.port"
result_file="$tmpdir/singmux-result.json"
payload="$tmpdir/proxy-payload.json"
server_log="$tmpdir/singmux-server.log"
daemon_log="$tmpdir/daemon.log"
go_dir="$tmpdir/go-peer"
mkdir -p "$go_dir"
cat >"$go_dir/go.mod" <<EOF
module burrow-singmux-interop
go 1.23
require (
github.com/metacubex/sing v0.5.7
github.com/metacubex/sing-mux v0.3.9
)
replace github.com/metacubex/sing => $sing_dir
replace github.com/metacubex/sing-mux => $sing_mux_dir
EOF
cat >"$go_dir/main.go" <<'EOF'
package main
import (
"context"
"encoding/json"
"fmt"
"io"
"net"
"os"
"strings"
"sync"
"time"
mux "github.com/metacubex/sing-mux"
"github.com/metacubex/sing/common/buf"
"github.com/metacubex/sing/common/bufio"
"github.com/metacubex/sing/common/logger"
M "github.com/metacubex/sing/common/metadata"
N "github.com/metacubex/sing/common/network"
)
type noopLogger struct{}
func (noopLogger) Trace(args ...any) {}
func (noopLogger) Debug(args ...any) {}
func (noopLogger) Info(args ...any) {}
func (noopLogger) Warn(args ...any) {}
func (noopLogger) Error(args ...any) {}
func (noopLogger) Fatal(args ...any) {}
func (noopLogger) Panic(args ...any) {}
func (noopLogger) TraceContext(context.Context, ...any) {}
func (noopLogger) DebugContext(context.Context, ...any) {}
func (noopLogger) InfoContext(context.Context, ...any) {}
func (noopLogger) WarnContext(context.Context, ...any) {}
func (noopLogger) ErrorContext(context.Context, ...any) {}
func (noopLogger) FatalContext(context.Context, ...any) {}
func (noopLogger) PanicContext(context.Context, ...any) {}
var _ logger.ContextLogger = noopLogger{}
type observed struct {
MuxTarget map[string]any `json:"mux_target,omitempty"`
TCPDestination map[string]any `json:"tcp_destination,omitempty"`
TCPFirstLine string `json:"tcp_first_line,omitempty"`
UDPDestination map[string]any `json:"udp_destination,omitempty"`
UDPPayload string `json:"udp_payload,omitempty"`
Protocol string `json:"protocol,omitempty"`
Padding bool `json:"padding,omitempty"`
PacketReplyCount int `json:"packet_reply_count,omitempty"`
}
type handler struct {
mu sync.Mutex
result observed
done chan struct{}
doneOnce sync.Once
}
func (h *handler) markDoneIfReady() {
if h.result.TCPFirstLine != "" && h.result.UDPPayload != "" {
h.doneOnce.Do(func() { close(h.done) })
}
}
func (h *handler) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
defer conn.Close()
request, err := readHTTPHeaders(conn)
if err != nil {
return err
}
firstLine := strings.SplitN(string(request), "\r\n", 2)[0]
body := []byte("burrow sing-mux tcp ok\n")
response := fmt.Sprintf(
"HTTP/1.1 200 OK\r\nContent-Length: %d\r\nConnection: close\r\nContent-Type: text/plain\r\n\r\n%s",
len(body),
body,
)
if _, err := conn.Write([]byte(response)); err != nil {
return err
}
h.mu.Lock()
h.result.TCPDestination = socksaddrJSON(metadata.Destination)
h.result.TCPFirstLine = firstLine
h.markDoneIfReady()
h.mu.Unlock()
return nil
}
func (h *handler) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
defer conn.Close()
packet := buf.NewPacket()
defer packet.Release()
destination, err := conn.ReadPacket(packet)
if err != nil {
return err
}
payload := append([]byte(nil), packet.Bytes()...)
if _, err := bufio.WritePacketBuffer(conn, buf.As(payload), destination); err != nil {
return err
}
h.mu.Lock()
h.result.UDPDestination = socksaddrJSON(metadata.Destination)
h.result.UDPPayload = string(payload)
h.result.PacketReplyCount++
h.markDoneIfReady()
h.mu.Unlock()
return nil
}
func main() {
portFile := os.Args[1]
resultFile := os.Args[2]
protocol := os.Args[3]
padding := os.Args[4] == "true"
listener, err := net.Listen("tcp4", "127.0.0.1:0")
if err != nil {
panic(err)
}
defer listener.Close()
port := listener.Addr().(*net.TCPAddr).Port
if err := os.WriteFile(portFile, []byte(fmt.Sprint(port)), 0o644); err != nil {
panic(err)
}
h := &handler{done: make(chan struct{})}
h.result.Protocol = protocol
h.result.Padding = padding
service, err := mux.NewService(mux.ServiceOptions{
NewStreamContext: func(ctx context.Context, conn net.Conn) context.Context { return ctx },
Logger: noopLogger{},
Handler: h,
})
if err != nil {
panic(err)
}
raw, err := listener.Accept()
if err != nil {
panic(err)
}
raw.SetDeadline(time.Now().Add(20 * time.Second))
target, err := readSocksAddr(raw)
if err != nil {
panic(err)
}
h.mu.Lock()
h.result.MuxTarget = target
h.mu.Unlock()
go func() {
if err := service.NewConnection(context.Background(), raw, M.Metadata{}); err != nil {
fmt.Fprintln(os.Stderr, err)
}
}()
select {
case <-h.done:
case <-time.After(20 * time.Second):
panic("timed out waiting for sing-mux TCP and UDP probes")
}
h.mu.Lock()
output, err := json.MarshalIndent(h.result, "", " ")
h.mu.Unlock()
if err != nil {
panic(err)
}
if err := os.WriteFile(resultFile, append(output, '\n'), 0o644); err != nil {
panic(err)
}
}
func readHTTPHeaders(reader io.Reader) ([]byte, error) {
data := make([]byte, 0, 512)
tmp := make([]byte, 1)
for !strings.Contains(string(data), "\r\n\r\n") {
if _, err := io.ReadFull(reader, tmp); err != nil {
return nil, err
}
data = append(data, tmp[0])
if len(data) > 65535 {
return nil, fmt.Errorf("HTTP headers exceeded 64 KiB")
}
}
return data, nil
}
func readSocksAddr(reader io.Reader) (map[string]any, error) {
destination, err := M.SocksaddrSerializer.ReadAddrPort(reader)
if err != nil {
return nil, err
}
return socksaddrJSON(destination), nil
}
func socksaddrJSON(destination M.Socksaddr) map[string]any {
if destination.IsFqdn() {
return map[string]any{
"host": destination.Fqdn,
"port": destination.Port,
}
}
return map[string]any{
"host": destination.Addr.String(),
"port": destination.Port,
}
}
func (h *handler) NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, metadata M.Metadata) error {
return fmt.Errorf("unexpected packet handler path")
}
EOF
(
cd "$go_dir"
go mod tidy
)
(
cd "$go_dir"
go run . "$port_file" "$result_file" "$protocol" "$padding"
) >"$server_log" 2>&1 &
server_pid=$!
for _ in {1..600}; do
if [[ -s "$port_file" ]]; then
break
fi
if ! kill -0 "$server_pid" >/dev/null 2>&1; then
echo "sing-mux peer exited before listening" >&2
cat "$server_log" >&2 || true
exit 1
fi
sleep 0.05
done
if [[ ! -s "$port_file" ]]; then
echo "timed out waiting for sing-mux peer" >&2
cat "$server_log" >&2 || true
exit 1
fi
server_port="$(cat "$port_file")"
cat >"$payload" <<EOF
{
"name": "burrow sing-mux interop",
"source": { "url": "selftest://sing-mux" },
"detected_format": "uri-list",
"selected_ordinal": 1,
"selected_name": "local shadowsocks sing-mux $protocol padding $padding",
"nodes": [
{
"ordinal": 1,
"name": "local shadowsocks sing-mux $protocol padding $padding",
"protocol": "shadowsocks",
"server": "127.0.0.1",
"port": $server_port,
"warnings": [],
"config": {
"type": "shadowsocks",
"cipher": "none",
"password": "unused",
"udp": $node_udp,
"udp_over_tcp": false,
"udp_over_tcp_version": 1,
"client_fingerprint": null,
"plugin": null,
"smux": {
"enabled": true,
"protocol": "$protocol",
"max_connections": 1,
"min_streams": 0,
"max_streams": 16,
"padding": $padding,
"statistic": false,
"only_tcp": false,
"brutal": null
}
}
}
],
"warnings": []
}
EOF
(
cd "$tmpdir"
BURROW_SOCKET_PATH="$socket" "$burrow_bin" daemon >"$daemon_log" 2>&1
) &
daemon_pid=$!
for _ in {1..100}; do
if [[ -S "$socket" ]]; then
break
fi
if ! kill -0 "$daemon_pid" >/dev/null 2>&1; then
echo "Burrow daemon exited before creating socket" >&2
cat "$daemon_log" >&2 || true
exit 1
fi
sleep 0.05
done
if [[ ! -S "$socket" ]]; then
echo "timed out waiting for Burrow daemon socket" >&2
cat "$daemon_log" >&2 || true
exit 1
fi
(
cd "$tmpdir"
BURROW_SOCKET_PATH="$socket" "$burrow_bin" network-add 1 3 "$payload"
BURROW_SOCKET_PATH="$socket" "$burrow_bin" tunnel-config
BURROW_SOCKET_PATH="$socket" "$burrow_bin" proxy-tcp-probe \
--dns-name selftest.invalid \
--remote 1.1.1.1:80 \
--timeout-ms 8000
BURROW_SOCKET_PATH="$socket" "$burrow_bin" proxy-udp-echo \
--message burrow-singmux-udp-selftest \
--timeout-ms 8000 \
9.9.9.9:5353
)
wait "$server_pid"
server_pid=""
echo
echo "Mihomo sing-mux peer observed:"
cat "$result_file"
echo
uv run python - "$result_file" "$protocol" "$padding" <<'PY'
from __future__ import annotations
import json
import sys
with open(sys.argv[1], "r", encoding="utf-8") as f:
result = json.load(f)
expected_protocol = sys.argv[2]
expected_padding = sys.argv[3] == "true"
def expect(value: object, expected: object, label: str) -> None:
if value != expected:
raise AssertionError(f"{label}: expected {expected!r}, got {value!r}")
def expect_target(key: str, host: str, port: int) -> None:
target = result.get(key)
if not isinstance(target, dict):
raise AssertionError(f"{key}: missing target object")
expect(target.get("host"), host, f"{key}.host")
expect(target.get("port"), port, f"{key}.port")
expect(result.get("protocol"), expected_protocol, "sing-mux protocol")
expect(result.get("padding", False), expected_padding, "sing-mux padding")
expect_target("mux_target", "sp.mux.sing-box.arpa", 444)
expect_target("tcp_destination", "selftest.invalid", 80)
expect(result.get("tcp_first_line"), "GET / HTTP/1.1", "tcp first line")
expect_target("udp_destination", "9.9.9.9", 5353)
expect(result.get("udp_payload"), "burrow-singmux-udp-selftest", "udp payload")
expect(result.get("packet_reply_count"), 1, "packet reply count")
padding_label = "padded" if expected_padding else "unpadded"
print(f"Mihomo sing-mux {expected_protocol} {padding_label} interop assertions passed")
PY

View file

@ -33,11 +33,22 @@ serde = { version = "1", features = ["derive"] }
serde_json = "1.0"
serde_yaml = "0.9"
blake2 = "0.10"
blake3 = "1"
chacha20 = "0.9"
chacha20poly1305 = "0.10"
rand = "0.8"
bytes = "1"
rand_core = "0.6"
aead = "0.5"
aegis = { version = "0.9.9", default-features = false, features = ["pure-rust"] }
aes = "0.8"
aes-gcm = "0.10"
ccm = "0.5"
deoxys = { version = "0.2.0-rc.3", default-features = false }
lea = "0.5.4"
poly1305 = "0.8"
rabbit = "0.5"
zears = "0.2.1"
x25519-dalek = { version = "2.0", features = [
"reusable_secrets",
"static_secrets",
@ -59,6 +70,7 @@ arti-client = "0.40.0"
hickory-proto = "0.25.2"
netstack-smoltcp = "0.2.1"
tokio-util = { version = "0.7.18", features = ["compat"] }
tokio_smux = "0.2"
tor-rtcompat = "0.40.0"
console-subscriber = { version = "0.2.0", optional = true }
console = "0.15.8"
@ -86,9 +98,46 @@ rustls = { version = "0.23", default-features = false, features = [
"std",
"tls12",
] }
rustls-fork-shadow-tls = { version = "0.20.8", features = ["dangerous_configuration"] }
rustls-pemfile = "2"
sha2 = "0.10"
tokio-rustls = "0.26"
tokio-rustls-fork-shadow-tls = { version = "0.0.8", features = [
"dangerous_configuration",
] }
rama-tls-boring = { version = "0.3.0-alpha.4", default-features = false, optional = true }
rama-ua = { version = "0.3.0-alpha.4", default-features = false, optional = true, features = [
"embed-profiles",
"tls",
] }
rama-net = { version = "0.3.0-alpha.4", default-features = false, optional = true, features = ["tls"] }
webpki-roots = "0.26"
webpki-roots-legacy = { package = "webpki-roots", version = "0.22" }
rust_tokio_kcp = { version = "0.2.5", default-features = false, features = [
"aes",
"tea",
"xtea",
"simple_xor",
"blowfish",
"cast5",
"triple_des",
"twofish",
"salsa20",
"sm4",
"aes-gcm",
] }
smux_rust = "0.2.1"
tokio-snappy = "0.2.0"
shadowsocks = { version = "1.24.0", default-features = false, features = [
"aead-cipher",
"aead-cipher-extra",
"aead-cipher-2022",
"aead-cipher-2022-extra",
"stream-cipher",
] }
yamux = "0.13.10"
h2 = "0.4.12"
http = "1.3.1"
[target.'cfg(target_os = "linux")'.dependencies]
caps = "0.5"
@ -123,6 +172,11 @@ pre_uninstall_script = "../package/rpm/pre_uninstall"
[features]
tokio-console = ["dep:console-subscriber"]
bundled = ["rusqlite/bundled"]
boring-browser-fingerprints = [
"dep:rama-tls-boring",
"dep:rama-ua",
"dep:rama-net",
]
[build-dependencies]

View file

@ -85,6 +85,8 @@ enum Commands {
TailnetUdpEcho(TailnetUdpEchoArgs),
/// Send an HTTP probe through the active proxy tunnel over daemon packet streaming
ProxyTcpProbe(ProxyTcpProbeArgs),
/// Send a UDP echo probe through the active proxy tunnel over daemon packet streaming
ProxyUdpEcho(ProxyUdpEchoArgs),
/// Send an HTTPS probe through the active proxy tunnel over daemon packet streaming
ProxyHttpsProbe(ProxyHttpsProbeArgs),
/// Select a proxy subscription node through the daemon
@ -174,6 +176,16 @@ struct ProxyTcpProbeArgs {
timeout_ms: u64,
}
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
#[derive(Args)]
struct ProxyUdpEchoArgs {
remote: String,
#[arg(long, default_value = "burrow-proxy-udp-smoke")]
message: String,
#[arg(long, default_value_t = 5000)]
timeout_ms: u64,
}
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
#[derive(Args)]
struct ProxyHttpsProbeArgs {
@ -443,6 +455,21 @@ async fn try_tailnet_ping(remote: &str, payload: &str, timeout_ms: u64) -> Resul
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
async fn try_tailnet_udp_echo(remote: &str, message: &str, timeout_ms: u64) -> Result<()> {
try_packet_udp_echo("Tailnet", remote, message, timeout_ms).await
}
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
async fn try_proxy_udp_echo(remote: &str, message: &str, timeout_ms: u64) -> Result<()> {
try_packet_udp_echo("Proxy", remote, message, timeout_ms).await
}
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
async fn try_packet_udp_echo(
label: &str,
remote: &str,
message: &str,
timeout_ms: u64,
) -> Result<()> {
use std::net::SocketAddr;
use anyhow::{bail, Context};
@ -459,6 +486,7 @@ async fn try_tailnet_udp_echo(remote: &str, message: &str, timeout_ms: u64) -> R
let remote_addr: SocketAddr = remote
.parse()
.with_context(|| format!("invalid remote socket address {remote}"))?;
let label = label.to_owned();
let mut client = BurrowClient::from_uds().await?;
client.tunnel_client.tunnel_start(Empty {}).await?;
@ -478,9 +506,11 @@ async fn try_tailnet_udp_echo(remote: &str, message: &str, timeout_ms: u64) -> R
.enable_udp(true)
.enable_tcp(true)
.build()
.context("failed to build userspace UDP stack")?;
let runner = runner.context("userspace UDP stack runner unavailable")?;
let udp_socket = udp_socket.context("userspace UDP stack socket unavailable")?;
.with_context(|| format!("failed to build userspace {label} UDP stack"))?;
let runner =
runner.with_context(|| format!("userspace {label} UDP stack runner unavailable"))?;
let udp_socket =
udp_socket.with_context(|| format!("userspace {label} UDP stack socket unavailable"))?;
let (mut stack_sink, mut stack_stream) = stack.split();
let (mut udp_reader, mut udp_writer) = udp_socket.split();
@ -491,18 +521,21 @@ async fn try_tailnet_udp_echo(remote: &str, message: &str, timeout_ms: u64) -> R
.await?
.into_inner();
let ingress_label = label.clone();
let ingress_task = tokio::spawn(async move {
loop {
match tunnel_packets.message().await? {
Some(packet) => {
log::debug!(
"tailnet udp echo received {} bytes from daemon packet stream",
"{} UDP echo received {} bytes from daemon packet stream",
ingress_label,
packet.payload.len()
);
stack_sink
.send(packet.payload)
.await
.context("failed to feed inbound tailnet packet into userspace stack")?;
stack_sink.send(packet.payload).await.with_context(|| {
format!(
"failed to feed inbound {ingress_label} packet into userspace stack"
)
})?;
}
None => break,
}
@ -510,17 +543,21 @@ async fn try_tailnet_udp_echo(remote: &str, message: &str, timeout_ms: u64) -> R
Result::<()>::Ok(())
});
let egress_label = label.clone();
let egress_task = tokio::spawn(async move {
while let Some(packet) = stack_stream.next().await {
let payload = packet.context("failed to read outbound packet from userspace stack")?;
log::debug!(
"tailnet udp echo sending {} bytes into daemon packet stream",
"{} UDP echo sending {} bytes into daemon packet stream",
egress_label,
payload.len()
);
outbound_tx
.send(TunnelPacket { payload })
.await
.context("failed to forward outbound tailnet packet to daemon")?;
.with_context(|| {
format!("failed to forward outbound {egress_label} packet to daemon")
})?;
}
Result::<()>::Ok(())
});
@ -530,13 +567,13 @@ async fn try_tailnet_udp_echo(remote: &str, message: &str, timeout_ms: u64) -> R
udp_writer
.send((message.as_bytes().to_vec(), local_addr, remote_addr))
.await
.context("failed to send UDP echo probe into userspace stack")?;
log::debug!("tailnet udp echo probe queued from {local_addr} to {remote_addr}");
.with_context(|| format!("failed to send {label} UDP echo probe into userspace stack"))?;
log::debug!("{label} UDP echo probe queued from {local_addr} to {remote_addr}");
let response = timeout(Duration::from_millis(timeout_ms), udp_reader.next())
.await
.with_context(|| format!("timed out waiting for UDP echo from {remote_addr}"))?
.context("userspace UDP stack ended before returning a reply")?;
.with_context(|| format!("timed out waiting for {label} UDP echo from {remote_addr}"))?
.with_context(|| format!("userspace {label} UDP stack ended before returning a reply"))?;
let (payload, reply_source, reply_destination) = response;
let response_text = String::from_utf8_lossy(&payload);
@ -554,9 +591,9 @@ async fn try_tailnet_udp_echo(remote: &str, message: &str, timeout_ms: u64) -> R
bail!("UDP echo payload mismatch");
}
println!("Tailnet UDP Echo Source: {reply_source}");
println!("Tailnet UDP Echo Destination: {reply_destination}");
println!("Tailnet UDP Echo Payload: {response_text}");
println!("{label} UDP Echo Source: {reply_source}");
println!("{label} UDP Echo Destination: {reply_destination}");
println!("{label} UDP Echo Payload: {response_text}");
Ok(())
}
@ -1646,6 +1683,9 @@ async fn main() -> Result<()> {
)
.await?
}
Commands::ProxyUdpEcho(args) => {
try_proxy_udp_echo(&args.remote, &args.message, args.timeout_ms).await?
}
Commands::ProxyHttpsProbe(args) => {
try_proxy_https_probe(
&args.remote,

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,961 @@
impl ProxyServerEndpoint {
fn resolve(host: &str, port: u16) -> Result<Self> {
let addresses = resolve_server_addresses(host, port)?;
let endpoint = Self {
host: host.to_owned(),
port,
addresses: Arc::from(addresses),
};
if endpoint
.addresses
.iter()
.any(|address| is_fake_or_benchmark_ip(address.ip()))
{
tracing::warn!(
server = %endpoint.host,
port = endpoint.port,
resolved_addresses = ?endpoint.addresses,
"proxy server resolved to a fake-IP or benchmarking range; outbound dial may recurse or fail"
);
} else {
tracing::debug!(
server = %endpoint.host,
port = endpoint.port,
resolved_addresses = ?endpoint.addresses,
"resolved proxy server addresses"
);
}
Ok(endpoint)
}
}
async fn connect_proxy_tcp(endpoint: &ProxyServerEndpoint) -> Result<(TcpStream, SocketAddr)> {
let mut last_error = None;
for address in endpoint.addresses.iter().copied() {
tracing::debug!(
server = %endpoint.host,
port = endpoint.port,
%address,
"dialing proxy tcp server"
);
let socket = match address {
SocketAddr::V4(_) => TcpSocket::new_v4(),
SocketAddr::V6(_) => TcpSocket::new_v6(),
}
.with_context(|| format!("failed to create tcp socket for {address}"))?;
if let Err(err) = bind_proxy_outbound_socket(socket.as_raw_fd(), address.ip()) {
tracing::warn!(
server = %endpoint.host,
port = endpoint.port,
%address,
error = ?err,
"failed to bind proxy tcp socket to physical interface"
);
last_error = Some(err);
continue;
}
match socket.connect(address).await {
Ok(stream) => {
tracing::debug!(
server = %endpoint.host,
port = endpoint.port,
%address,
"connected proxy tcp server"
);
return Ok((stream, address));
}
Err(err) => {
tracing::warn!(
server = %endpoint.host,
port = endpoint.port,
%address,
error = %err,
"failed to connect proxy tcp server"
);
last_error = Some(anyhow!(err).context(format!("failed to connect {address}")));
}
}
}
Err(last_error.unwrap_or_else(|| {
anyhow!(
"no cached addresses available for {}:{}",
endpoint.host,
endpoint.port
)
}))
}
async fn connect_proxy_udp(endpoint: &ProxyServerEndpoint) -> Result<(UdpSocket, SocketAddr)> {
let mut last_error = None;
for address in endpoint.addresses.iter().copied() {
tracing::debug!(
server = %endpoint.host,
port = endpoint.port,
%address,
"dialing proxy udp server"
);
let bind_addr = match address {
SocketAddr::V4(_) => SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0),
SocketAddr::V6(_) => SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), 0),
};
let socket = match std::net::UdpSocket::bind(bind_addr) {
Ok(socket) => socket,
Err(err) => {
tracing::warn!(
server = %endpoint.host,
port = endpoint.port,
%address,
error = %err,
"failed to bind proxy udp socket"
);
last_error =
Some(anyhow!(err).context(format!("failed to bind udp socket for {address}")));
continue;
}
};
if let Err(err) = bind_proxy_outbound_socket(socket.as_raw_fd(), address.ip()) {
tracing::warn!(
server = %endpoint.host,
port = endpoint.port,
%address,
error = ?err,
"failed to bind proxy udp socket to physical interface"
);
last_error = Some(err);
continue;
}
if let Err(err) = socket.set_nonblocking(true) {
tracing::warn!(
server = %endpoint.host,
port = endpoint.port,
%address,
error = %err,
"failed to make proxy udp socket nonblocking"
);
last_error = Some(anyhow!(err).context("failed to make proxy udp socket nonblocking"));
continue;
}
match UdpSocket::from_std(socket) {
Ok(socket) => {
tracing::debug!(
server = %endpoint.host,
port = endpoint.port,
%address,
"connected proxy udp server"
);
return Ok((socket, address));
}
Err(err) => {
tracing::warn!(
server = %endpoint.host,
port = endpoint.port,
%address,
error = %err,
"failed to wrap proxy udp socket"
);
last_error = Some(anyhow!(err).context("failed to wrap proxy udp socket"));
}
}
}
Err(last_error.unwrap_or_else(|| {
anyhow!(
"no cached addresses available for {}:{}",
endpoint.host,
endpoint.port
)
}))
}
fn resolve_server_addresses(host: &str, port: u16) -> Result<Vec<SocketAddr>> {
let addresses: Vec<_> = (host, port)
.to_socket_addrs()
.with_context(|| format!("failed to resolve {host}:{port}"))?
.collect();
if addresses.is_empty() {
bail!("no addresses resolved for {host}:{port}");
}
if addresses
.iter()
.all(|address| is_fake_or_benchmark_ip(address.ip()))
{
tracing::warn!(
server = %host,
port,
resolved_addresses = ?addresses,
"system DNS returned only fake-IP proxy server addresses; trying direct DNS"
);
let direct_addresses = resolve_server_addresses_direct(host, port)?;
tracing::info!(
server = %host,
port,
resolved_addresses = ?direct_addresses,
"direct DNS resolved proxy server addresses"
);
return Ok(direct_addresses);
}
Ok(addresses)
}
fn resolve_server_addresses_direct(host: &str, port: u16) -> Result<Vec<SocketAddr>> {
if let Ok(ip) = host.parse::<IpAddr>() {
return Ok(vec![SocketAddr::new(ip, port)]);
}
let mut dns_servers = Vec::new();
for server in platform_proxy_dns_servers()
.into_iter()
.chain(PUBLIC_DNS_SERVERS.iter().map(|server| (*server).to_owned()))
{
if dns_servers.iter().any(|existing| existing == &server) {
continue;
}
let Ok(ip) = server.parse::<IpAddr>() else {
continue;
};
if is_usable_proxy_dns_ip(ip) {
dns_servers.push(server);
}
}
let mut addresses = Vec::new();
let mut last_error = None;
for dns_server in dns_servers {
let Ok(ip) = dns_server.parse::<IpAddr>() else {
continue;
};
let dns_addr = SocketAddr::new(ip, 53);
match direct_dns_lookup(host, dns_addr) {
Ok(resolved) => {
for ip in resolved {
if is_fake_or_benchmark_ip(ip) {
continue;
}
let address = SocketAddr::new(ip, port);
if !addresses.contains(&address) {
addresses.push(address);
}
}
if !addresses.is_empty() {
break;
}
}
Err(err) => {
tracing::debug!(
server = %host,
%dns_addr,
error = ?err,
"direct DNS lookup failed"
);
last_error = Some(err);
}
}
}
if addresses.is_empty() {
return Err(last_error.unwrap_or_else(|| {
anyhow!("direct DNS did not resolve any usable addresses for {host}:{port}")
}));
}
Ok(addresses)
}
fn direct_dns_lookup(host: &str, dns_server: SocketAddr) -> Result<Vec<IpAddr>> {
let mut addresses = Vec::new();
let mut last_error = None;
for query_type in [1u16, 28u16] {
let mut query = build_dns_query(host, query_type)?;
let id = (OsRng.next_u32() & 0xffff) as u16;
query[0..2].copy_from_slice(&id.to_be_bytes());
let bind_addr = match dns_server {
SocketAddr::V4(_) => SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0),
SocketAddr::V6(_) => SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), 0),
};
let socket = std::net::UdpSocket::bind(bind_addr)
.with_context(|| format!("failed to bind DNS socket for {dns_server}"))?;
bind_proxy_outbound_socket(socket.as_raw_fd(), dns_server.ip()).with_context(|| {
format!("failed to bind DNS socket to physical interface for {dns_server}")
})?;
socket
.set_read_timeout(Some(DIRECT_DNS_TIMEOUT))
.context("failed to set DNS read timeout")?;
socket
.set_write_timeout(Some(DIRECT_DNS_TIMEOUT))
.context("failed to set DNS write timeout")?;
if let Err(err) = socket.send_to(&query, dns_server) {
last_error =
Some(anyhow!(err).context(format!("failed to send DNS query to {dns_server}")));
continue;
}
let mut response = [0u8; 1500];
match socket.recv_from(&mut response) {
Ok((len, _)) => match parse_dns_response(&response[..len], id)
.with_context(|| format!("failed to parse DNS response from {dns_server}"))
{
Ok(mut resolved) => addresses.append(&mut resolved),
Err(err) => last_error = Some(err),
},
Err(err) => {
last_error = Some(
anyhow!(err)
.context(format!("failed to receive DNS response from {dns_server}")),
);
}
}
}
if addresses.is_empty() {
return Err(last_error.unwrap_or_else(|| anyhow!("DNS lookup returned no answers")));
}
Ok(addresses)
}
fn direct_dns_https_ech_config_list(host: &str, dns_server: SocketAddr) -> Result<Option<Vec<u8>>> {
let mut query = build_dns_query(host, DNS_TYPE_HTTPS)?;
let id = (OsRng.next_u32() & 0xffff) as u16;
query[0..2].copy_from_slice(&id.to_be_bytes());
let bind_addr = match dns_server {
SocketAddr::V4(_) => SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0),
SocketAddr::V6(_) => SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), 0),
};
let socket = std::net::UdpSocket::bind(bind_addr)
.with_context(|| format!("failed to bind DNS socket for {dns_server}"))?;
bind_proxy_outbound_socket(socket.as_raw_fd(), dns_server.ip()).with_context(|| {
format!("failed to bind DNS socket to physical interface for {dns_server}")
})?;
socket
.set_read_timeout(Some(DIRECT_DNS_TIMEOUT))
.context("failed to set DNS read timeout")?;
socket
.set_write_timeout(Some(DIRECT_DNS_TIMEOUT))
.context("failed to set DNS write timeout")?;
socket
.send_to(&query, dns_server)
.with_context(|| format!("failed to send DNS HTTPS query to {dns_server}"))?;
let mut response = [0u8; 1500];
let (len, _) = socket
.recv_from(&mut response)
.with_context(|| format!("failed to receive DNS HTTPS response from {dns_server}"))?;
parse_dns_https_ech_config_list(&response[..len], id)
.with_context(|| format!("failed to parse DNS HTTPS response from {dns_server}"))
}
pub(crate) fn build_dns_query(host: &str, query_type: u16) -> Result<Vec<u8>> {
let mut query = Vec::with_capacity(512);
query.extend_from_slice(&0u16.to_be_bytes());
query.extend_from_slice(&0x0100u16.to_be_bytes());
query.extend_from_slice(&1u16.to_be_bytes());
query.extend_from_slice(&0u16.to_be_bytes());
query.extend_from_slice(&0u16.to_be_bytes());
query.extend_from_slice(&0u16.to_be_bytes());
for label in host.trim_end_matches('.').split('.') {
if label.is_empty() || label.len() > 63 {
bail!("invalid DNS label in {host}");
}
query.push(label.len() as u8);
query.extend_from_slice(label.as_bytes());
}
query.push(0);
query.extend_from_slice(&query_type.to_be_bytes());
query.extend_from_slice(&1u16.to_be_bytes());
Ok(query)
}
pub(crate) fn parse_dns_response(response: &[u8], expected_id: u16) -> Result<Vec<IpAddr>> {
if response.len() < 12 {
bail!("truncated DNS response");
}
let id = u16::from_be_bytes([response[0], response[1]]);
if id != expected_id {
bail!("DNS response id mismatch");
}
let flags = u16::from_be_bytes([response[2], response[3]]);
if flags & 0x8000 == 0 {
bail!("DNS response is not marked as a response");
}
let rcode = flags & 0x000f;
if rcode != 0 {
bail!("DNS response error code {rcode}");
}
let question_count = u16::from_be_bytes([response[4], response[5]]) as usize;
let answer_count = u16::from_be_bytes([response[6], response[7]]) as usize;
let mut offset = 12;
for _ in 0..question_count {
offset = skip_dns_name(response, offset)?;
if response.len() < offset + 4 {
bail!("truncated DNS question");
}
offset += 4;
}
let mut addresses = Vec::new();
for _ in 0..answer_count {
offset = skip_dns_name(response, offset)?;
if response.len() < offset + 10 {
bail!("truncated DNS answer");
}
let record_type = u16::from_be_bytes([response[offset], response[offset + 1]]);
let record_class = u16::from_be_bytes([response[offset + 2], response[offset + 3]]);
let data_len = u16::from_be_bytes([response[offset + 8], response[offset + 9]]) as usize;
offset += 10;
if response.len() < offset + data_len {
bail!("truncated DNS answer data");
}
if record_class == 1 && record_type == 1 && data_len == 4 {
addresses.push(IpAddr::V4(Ipv4Addr::new(
response[offset],
response[offset + 1],
response[offset + 2],
response[offset + 3],
)));
} else if record_class == 1 && record_type == 28 && data_len == 16 {
let mut octets = [0u8; 16];
octets.copy_from_slice(&response[offset..offset + 16]);
addresses.push(IpAddr::V6(Ipv6Addr::from(octets)));
}
offset += data_len;
}
Ok(addresses)
}
fn parse_dns_https_ech_config_list(response: &[u8], expected_id: u16) -> Result<Option<Vec<u8>>> {
if response.len() < 12 {
bail!("truncated DNS response");
}
let id = u16::from_be_bytes([response[0], response[1]]);
if id != expected_id {
bail!("DNS response id mismatch");
}
let flags = u16::from_be_bytes([response[2], response[3]]);
if flags & 0x8000 == 0 {
bail!("DNS response is not marked as a response");
}
let rcode = flags & 0x000f;
if rcode != 0 {
bail!("DNS response error code {rcode}");
}
let question_count = u16::from_be_bytes([response[4], response[5]]) as usize;
let answer_count = u16::from_be_bytes([response[6], response[7]]) as usize;
let mut offset = 12;
for _ in 0..question_count {
offset = skip_dns_name(response, offset)?;
if response.len() < offset + 4 {
bail!("truncated DNS question");
}
offset += 4;
}
for _ in 0..answer_count {
offset = skip_dns_name(response, offset)?;
if response.len() < offset + 10 {
bail!("truncated DNS answer");
}
let record_type = u16::from_be_bytes([response[offset], response[offset + 1]]);
let record_class = u16::from_be_bytes([response[offset + 2], response[offset + 3]]);
let data_len = u16::from_be_bytes([response[offset + 8], response[offset + 9]]) as usize;
offset += 10;
if response.len() < offset + data_len {
bail!("truncated DNS answer data");
}
if record_class == 1 && record_type == DNS_TYPE_HTTPS {
if let Some(config_list) = parse_https_rr_ech_config_list(response, offset, data_len)? {
return Ok(Some(config_list));
}
}
offset += data_len;
}
Ok(None)
}
fn parse_https_rr_ech_config_list(
response: &[u8],
offset: usize,
data_len: usize,
) -> Result<Option<Vec<u8>>> {
let end = offset
.checked_add(data_len)
.ok_or_else(|| anyhow!("DNS HTTPS record offset overflow"))?;
if response.len() < end || data_len < 3 {
bail!("truncated DNS HTTPS record");
}
let (_target, mut cursor) = read_dns_name(response, offset + 2)?;
if cursor > end {
bail!("DNS HTTPS record target exceeded RDATA");
}
while cursor < end {
if response.len() < cursor + 4 || cursor + 4 > end {
bail!("truncated DNS HTTPS service parameter");
}
let key = u16::from_be_bytes([response[cursor], response[cursor + 1]]);
let value_len = u16::from_be_bytes([response[cursor + 2], response[cursor + 3]]) as usize;
cursor += 4;
let value_end = cursor
.checked_add(value_len)
.ok_or_else(|| anyhow!("DNS HTTPS parameter offset overflow"))?;
if value_end > end {
bail!("truncated DNS HTTPS service parameter value");
}
if key == HTTPS_SVC_PARAM_ECH {
return Ok(Some(response[cursor..value_end].to_vec()));
}
cursor = value_end;
}
Ok(None)
}
async fn record_dns_mappings(cache: &DnsHostnameCache, response: &[u8]) {
let mappings = match dns_response_host_mappings(response) {
Ok(mappings) => mappings,
Err(_) => return,
};
if mappings.is_empty() {
return;
}
let mut guard = cache.write().await;
for (ip, hostname) in mappings {
tracing::debug!(%ip, %hostname, "cached proxy DNS hostname mapping");
guard.insert_mapping(ip, hostname);
}
}
fn dns_response_host_mappings(response: &[u8]) -> Result<Vec<(IpAddr, String)>> {
if response.len() < 12 {
bail!("truncated DNS response");
}
let flags = u16::from_be_bytes([response[2], response[3]]);
if flags & 0x8000 == 0 {
bail!("DNS packet is not a response");
}
if flags & 0x000f != 0 {
bail!("DNS response returned an error");
}
let question_count = u16::from_be_bytes([response[4], response[5]]) as usize;
let answer_count = u16::from_be_bytes([response[6], response[7]]) as usize;
let mut offset = 12;
let mut question_names = Vec::new();
for _ in 0..question_count {
let (name, next_offset) = read_dns_name(response, offset)?;
offset = next_offset;
if response.len() < offset + 4 {
bail!("truncated DNS question");
}
if !name.is_empty() {
question_names.push(name);
}
offset += 4;
}
let mut mappings = Vec::new();
for _ in 0..answer_count {
let (answer_name, next_offset) = read_dns_name(response, offset)?;
offset = next_offset;
if response.len() < offset + 10 {
bail!("truncated DNS answer");
}
let record_type = u16::from_be_bytes([response[offset], response[offset + 1]]);
let record_class = u16::from_be_bytes([response[offset + 2], response[offset + 3]]);
let data_len = u16::from_be_bytes([response[offset + 8], response[offset + 9]]) as usize;
offset += 10;
if response.len() < offset + data_len {
bail!("truncated DNS answer data");
}
let hostname = question_names
.first()
.cloned()
.filter(|name| !name.is_empty())
.unwrap_or(answer_name);
if record_class == 1 && record_type == 1 && data_len == 4 {
mappings.push((
IpAddr::V4(Ipv4Addr::new(
response[offset],
response[offset + 1],
response[offset + 2],
response[offset + 3],
)),
hostname,
));
} else if record_class == 1 && record_type == 28 && data_len == 16 {
let mut octets = [0u8; 16];
octets.copy_from_slice(&response[offset..offset + 16]);
mappings.push((IpAddr::V6(Ipv6Addr::from(octets)), hostname));
}
offset += data_len;
}
Ok(mappings)
}
fn read_dns_name(response: &[u8], offset: usize) -> Result<(String, usize)> {
let mut labels = Vec::new();
let mut cursor = offset;
let mut next_offset = None;
let mut jumps = 0usize;
loop {
if cursor >= response.len() {
bail!("truncated DNS name");
}
let len = response[cursor];
if len & 0xc0 == 0xc0 {
if response.len() < cursor + 2 {
bail!("truncated compressed DNS name");
}
if next_offset.is_none() {
next_offset = Some(cursor + 2);
}
let pointer = (((len & 0x3f) as usize) << 8) | response[cursor + 1] as usize;
cursor = pointer;
jumps += 1;
if jumps > 16 {
bail!("too many compressed DNS name jumps");
}
continue;
}
if len & 0xc0 != 0 {
bail!("unsupported DNS label encoding");
}
cursor += 1;
if len == 0 {
return Ok((labels.join("."), next_offset.unwrap_or(cursor)));
}
let end = cursor
.checked_add(len as usize)
.ok_or_else(|| anyhow!("DNS name offset overflow"))?;
if end > response.len() {
bail!("truncated DNS label");
}
let label =
std::str::from_utf8(&response[cursor..end]).context("DNS label is not valid UTF-8")?;
labels.push(label.to_owned());
cursor = end;
}
}
fn skip_dns_name(response: &[u8], mut offset: usize) -> Result<usize> {
loop {
if offset >= response.len() {
bail!("truncated DNS name");
}
let len = response[offset];
if len & 0xc0 == 0xc0 {
if response.len() < offset + 2 {
bail!("truncated compressed DNS name");
}
return Ok(offset + 2);
}
if len & 0xc0 != 0 {
bail!("unsupported DNS label encoding");
}
offset += 1;
if len == 0 {
return Ok(offset);
}
offset = offset
.checked_add(len as usize)
.ok_or_else(|| anyhow!("DNS name offset overflow"))?;
}
}
fn is_fake_or_benchmark_ip(ip: IpAddr) -> bool {
match ip {
IpAddr::V4(ip) => {
let octets = ip.octets();
octets[0] == 198 && matches!(octets[1], 18 | 19)
}
IpAddr::V6(_) => false,
}
}
#[cfg(target_vendor = "apple")]
fn platform_proxy_dns_servers() -> Vec<String> {
match std::fs::read_to_string("/etc/resolv.conf") {
Ok(contents) => {
let servers = parse_resolv_conf_dns_servers(&contents);
if !servers.is_empty() {
return servers;
}
}
Err(err) => {
tracing::warn!(error = %err, "failed to read /etc/resolv.conf");
}
}
match Command::new("scutil").arg("--dns").output() {
Ok(output) if output.status.success() => {
parse_apple_physical_dns_servers(&String::from_utf8_lossy(&output.stdout))
}
Ok(output) => {
tracing::warn!(
status = ?output.status.code(),
stderr = %String::from_utf8_lossy(&output.stderr),
"failed to read macOS DNS configuration"
);
Vec::new()
}
Err(err) => {
tracing::warn!(error = %err, "failed to run scutil --dns");
Vec::new()
}
}
}
#[cfg(not(target_vendor = "apple"))]
fn platform_proxy_dns_servers() -> Vec<String> {
Vec::new()
}
fn parse_apple_physical_dns_servers(output: &str) -> Vec<String> {
let mut servers = Vec::new();
let mut resolver_servers = Vec::<String>::new();
let mut resolver_interface = None::<String>;
let mut flush_resolver =
|resolver_servers: &mut Vec<String>, resolver_interface: &mut Option<String>| {
let is_tunnel_resolver = resolver_interface
.as_deref()
.is_some_and(should_skip_dns_resolver_interface);
if !is_tunnel_resolver {
for server in resolver_servers.drain(..) {
if !servers.contains(&server) {
servers.push(server);
}
}
} else {
resolver_servers.clear();
}
*resolver_interface = None;
};
for line in output.lines() {
let trimmed = line.trim();
if trimmed.starts_with("resolver #") {
flush_resolver(&mut resolver_servers, &mut resolver_interface);
continue;
}
if trimmed.starts_with("nameserver[") {
if let Some((_, value)) = trimmed.split_once(':') {
let server = value.trim();
if let Ok(ip) = server.parse::<IpAddr>() {
if is_usable_proxy_dns_ip(ip) {
resolver_servers.push(server.to_owned());
}
}
}
continue;
}
if trimmed.starts_with("if_index") {
if let (Some(start), Some(end)) = (trimmed.find('('), trimmed.find(')')) {
resolver_interface = Some(trimmed[start + 1..end].to_owned());
}
}
}
flush_resolver(&mut resolver_servers, &mut resolver_interface);
servers
}
fn parse_resolv_conf_dns_servers(contents: &str) -> Vec<String> {
let mut servers = Vec::new();
for line in contents.lines() {
let line = line.split('#').next().unwrap_or("").trim();
let mut fields = line.split_whitespace();
if fields.next() != Some("nameserver") {
continue;
}
let Some(server) = fields.next() else {
continue;
};
let Ok(ip) = server.parse::<IpAddr>() else {
continue;
};
if is_usable_proxy_dns_ip(ip) && !servers.iter().any(|existing| existing == server) {
servers.push(server.to_owned());
}
}
servers
}
fn should_skip_dns_resolver_interface(interface: &str) -> bool {
interface.starts_with("utun")
|| interface.starts_with("lo")
|| interface.starts_with("awdl")
|| interface.starts_with("llw")
}
fn is_usable_proxy_dns_ip(ip: IpAddr) -> bool {
match ip {
IpAddr::V4(ip) => {
!ip.is_unspecified()
&& !ip.is_loopback()
&& !ip.is_multicast()
&& !is_fake_or_benchmark_ip(IpAddr::V4(ip))
}
IpAddr::V6(ip) => !ip.is_unspecified() && !ip.is_loopback() && !ip.is_multicast(),
}
}
#[cfg(target_vendor = "apple")]
fn bind_proxy_outbound_socket(fd: std::os::fd::RawFd, ip: IpAddr) -> Result<()> {
if !should_bind_proxy_outbound_socket(ip) {
return Ok(());
}
let interface_index = default_physical_interface_index().ok_or_else(|| {
anyhow!("no non-tunnel default interface found for proxy outbound socket")
})?;
let interface_index = interface_index as libc::c_uint;
let (level, option) = match ip {
IpAddr::V4(_) => (libc::IPPROTO_IP, libc::IP_BOUND_IF),
IpAddr::V6(_) => (libc::IPPROTO_IPV6, libc::IPV6_BOUND_IF),
};
let result = unsafe {
libc::setsockopt(
fd,
level,
option,
(&interface_index as *const libc::c_uint).cast::<libc::c_void>(),
std::mem::size_of_val(&interface_index) as libc::socklen_t,
)
};
if result != 0 {
return Err(std::io::Error::last_os_error()).with_context(|| {
format!("failed to bind proxy outbound socket to interface index {interface_index}")
});
}
Ok(())
}
#[cfg(not(target_vendor = "apple"))]
fn bind_proxy_outbound_socket(_fd: std::os::fd::RawFd, _ip: IpAddr) -> Result<()> {
Ok(())
}
fn should_bind_proxy_outbound_socket(ip: IpAddr) -> bool {
!ip.is_loopback()
}
#[cfg(target_vendor = "apple")]
fn default_physical_interface_index() -> Option<u32> {
#[derive(Clone)]
struct Candidate {
name: String,
index: u32,
score: i32,
}
let mut addrs = std::ptr::null_mut();
if unsafe { libc::getifaddrs(&mut addrs) } != 0 || addrs.is_null() {
return None;
}
struct IfAddrs(*mut libc::ifaddrs);
impl Drop for IfAddrs {
fn drop(&mut self) {
unsafe { libc::freeifaddrs(self.0) };
}
}
let _guard = IfAddrs(addrs);
let mut candidates = Vec::<Candidate>::new();
let mut current = addrs;
while !current.is_null() {
let ifa = unsafe { &*current };
current = ifa.ifa_next;
if ifa.ifa_addr.is_null() || ifa.ifa_name.is_null() {
continue;
}
let flags = ifa.ifa_flags as libc::c_uint;
if flags & libc::IFF_UP as libc::c_uint == 0
|| flags & libc::IFF_RUNNING as libc::c_uint == 0
|| flags & libc::IFF_LOOPBACK as libc::c_uint != 0
|| flags & libc::IFF_POINTOPOINT as libc::c_uint != 0
{
continue;
}
let family = unsafe { (*ifa.ifa_addr).sa_family as libc::c_int };
if family != libc::AF_INET && family != libc::AF_INET6 {
continue;
}
let name = unsafe { CStr::from_ptr(ifa.ifa_name) }
.to_string_lossy()
.into_owned();
if should_skip_outbound_interface(&name) {
continue;
}
let index = unsafe { libc::if_nametoindex(ifa.ifa_name) };
if index == 0 {
continue;
}
let mut score = if name.starts_with("en") { 100 } else { 10 };
if family == libc::AF_INET {
score += 20;
}
if name == "en0" {
score += 10;
}
candidates.push(Candidate { name, index, score });
}
candidates.sort_by_key(|candidate| (Reverse(candidate.score), candidate.name.clone()));
candidates.first().map(|candidate| {
tracing::debug!(
interface = %candidate.name,
index = candidate.index,
score = candidate.score,
"selected proxy outbound physical interface"
);
candidate.index
})
}
#[cfg(target_vendor = "apple")]
fn should_skip_outbound_interface(name: &str) -> bool {
name.starts_with("lo")
|| name.starts_with("utun")
|| name.starts_with("awdl")
|| name.starts_with("llw")
|| name.starts_with("bridge")
|| name.starts_with("gif")
|| name.starts_with("stf")
|| name.starts_with("p2p")
}
fn excluded_routes_for_server(host: &str, port: u16) -> Result<Vec<String>> {
let mut routes = Vec::new();
for address in resolve_server_addresses(host, port)
.with_context(|| format!("failed to resolve proxy server {host}:{port}"))?
{
let route = host_route_for_ip(address.ip());
if !routes.contains(&route) {
routes.push(route);
}
}
if routes.is_empty() {
bail!("no addresses resolved for proxy server {host}:{port}");
}
Ok(routes)
}
fn host_route_for_ip(ip: IpAddr) -> String {
match ip {
IpAddr::V4(ip) => format!("{ip}/32"),
IpAddr::V6(ip) => format!("{ip}/128"),
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -113,7 +113,12 @@ pub struct ShadowsocksNode {
pub password: String,
pub udp: bool,
pub udp_over_tcp: bool,
#[serde(default = "default_uot_legacy_version")]
pub udp_over_tcp_version: u8,
pub client_fingerprint: Option<String>,
pub plugin: Option<ShadowsocksPlugin>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub smux: Option<ShadowsocksSmux>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
@ -122,6 +127,38 @@ pub struct ShadowsocksPlugin {
pub opts: HashMap<String, String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ShadowsocksSmux {
#[serde(default)]
pub enabled: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub protocol: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub max_connections: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub min_streams: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub max_streams: Option<u32>,
#[serde(default)]
pub padding: bool,
#[serde(default)]
pub statistic: bool,
#[serde(default)]
pub only_tcp: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub brutal: Option<ShadowsocksSmuxBrutal>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ShadowsocksSmuxBrutal {
#[serde(default)]
pub enabled: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub up: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub down: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ProxySubscriptionPreview {
pub suggested_name: String,
@ -300,6 +337,14 @@ fn parse_yaml_subscription(
body: &str,
source_url: Option<&str>,
) -> Result<ProxySubscriptionPreview> {
let value: Value = serde_yaml::from_str(body).context("subscription is not proxy YAML")?;
let mapping = value
.as_mapping()
.ok_or_else(|| anyhow!("YAML subscription must be a mapping"))?;
if !mapping.contains_key(Value::String("proxies".to_owned())) {
bail!("YAML subscription must include a `proxies` field");
}
let schema: ProxySchema =
serde_yaml::from_str(body).context("subscription is not proxy YAML")?;
let proxies = schema
@ -449,11 +494,11 @@ fn parse_yaml_trojan(ordinal: usize, mapping: &HashMap<String, Value>) -> Result
}
fn parse_yaml_shadowsocks(ordinal: usize, mapping: &HashMap<String, Value>) -> Result<ProxyNode> {
let name = required_yaml_string(mapping, "name")?.to_owned();
let server = required_yaml_string(mapping, "server")?.to_owned();
let name = required_yaml_scalar_string(mapping, "name")?;
let server = required_yaml_scalar_string(mapping, "server")?;
let port = required_yaml_u16(mapping, "port")?;
let cipher = required_yaml_string(mapping, "cipher")?.to_owned();
let password = required_yaml_string(mapping, "password")?.to_owned();
let cipher = required_yaml_scalar_string(mapping, "cipher")?;
let password = required_yaml_scalar_string(mapping, "password")?;
Ok(ProxyNode {
ordinal,
name,
@ -464,9 +509,13 @@ fn parse_yaml_shadowsocks(ordinal: usize, mapping: &HashMap<String, Value>) -> R
config: ProxyNodeConfig::Shadowsocks(ShadowsocksNode {
cipher,
password,
udp: yaml_bool(mapping, "udp").unwrap_or(true),
udp: yaml_bool(mapping, "udp").unwrap_or(false),
udp_over_tcp: yaml_bool(mapping, "udp-over-tcp").unwrap_or(false),
udp_over_tcp_version: yaml_u8(mapping, "udp-over-tcp-version")
.unwrap_or(UOT_LEGACY_VERSION),
client_fingerprint: yaml_scalar_string(mapping, "client-fingerprint"),
plugin: yaml_plugin(mapping),
smux: yaml_smux(mapping),
}),
})
}
@ -586,6 +635,15 @@ fn parse_shadowsocks_uri(uri: &str, ordinal: usize) -> Result<ProxyNode> {
.map(|value| value == "true")
.unwrap_or(false)
|| query.get("uot").map(|value| value == "1").unwrap_or(false);
let udp_over_tcp_version = query
.get("udp-over-tcp-version")
.and_then(|value| value.parse::<u8>().ok())
.or_else(|| {
query
.get("uot-version")
.and_then(|value| value.parse::<u8>().ok())
})
.unwrap_or(UOT_LEGACY_VERSION);
Ok(ProxyNode {
ordinal,
@ -599,7 +657,13 @@ fn parse_shadowsocks_uri(uri: &str, ordinal: usize) -> Result<ProxyNode> {
password,
udp: true,
udp_over_tcp,
udp_over_tcp_version,
client_fingerprint: query
.get("client-fingerprint")
.or_else(|| query.get("fp"))
.and_then(|value| non_empty(value).map(ToOwned::to_owned)),
plugin: query.get("plugin").and_then(|value| parse_ss_plugin(value)),
smux: None,
}),
})
}
@ -628,6 +692,20 @@ fn yaml_string<'a>(mapping: &'a HashMap<String, Value>, key: &str) -> Option<&'a
mapping.get(key).and_then(Value::as_str)
}
fn yaml_scalar_string(mapping: &HashMap<String, Value>, key: &str) -> Option<String> {
yaml_value_to_scalar_string(mapping.get(key)?)
}
fn yaml_value_to_scalar_string(value: &Value) -> Option<String> {
value
.as_str()
.map(ToOwned::to_owned)
.or_else(|| value.as_bool().map(|value| value.to_string()))
.or_else(|| value.as_i64().map(|value| value.to_string()))
.or_else(|| value.as_u64().map(|value| value.to_string()))
.or_else(|| value.as_f64().map(|value| value.to_string()))
}
fn yaml_string_vec(mapping: &HashMap<String, Value>, key: &str) -> Option<Vec<String>> {
let values = mapping.get(key)?.as_sequence()?;
Some(
@ -654,6 +732,10 @@ fn required_yaml_string<'a>(mapping: &'a HashMap<String, Value>, key: &str) -> R
yaml_string(mapping, key).ok_or_else(|| anyhow!("missing `{key}`"))
}
fn required_yaml_scalar_string(mapping: &HashMap<String, Value>, key: &str) -> Result<String> {
yaml_scalar_string(mapping, key).ok_or_else(|| anyhow!("missing `{key}`"))
}
fn required_yaml_u16(mapping: &HashMap<String, Value>, key: &str) -> Result<u16> {
if let Some(value) = mapping.get(key).and_then(Value::as_u64) {
return u16::try_from(value).with_context(|| format!("invalid `{key}`"));
@ -668,39 +750,166 @@ fn yaml_bool(mapping: &HashMap<String, Value>, key: &str) -> Option<bool> {
mapping
.get(key)
.and_then(Value::as_bool)
.or_else(|| {
mapping
.get(key)
.and_then(Value::as_i64)
.map(|value| value != 0)
})
.or_else(|| {
mapping
.get(key)
.and_then(Value::as_u64)
.map(|value| value != 0)
})
.or_else(|| yaml_string(mapping, key).map(is_truthy))
}
fn yaml_u8(mapping: &HashMap<String, Value>, key: &str) -> Option<u8> {
mapping
.get(key)
.and_then(Value::as_u64)
.and_then(|value| u8::try_from(value).ok())
.or_else(|| yaml_string(mapping, key).and_then(|value| value.parse::<u8>().ok()))
}
fn yaml_u32(mapping: &HashMap<String, Value>, key: &str) -> Option<u32> {
mapping
.get(key)
.and_then(Value::as_u64)
.and_then(|value| u32::try_from(value).ok())
.or_else(|| yaml_string(mapping, key).and_then(|value| value.parse::<u32>().ok()))
}
fn yaml_smux(mapping: &HashMap<String, Value>) -> Option<ShadowsocksSmux> {
let smux = yaml_mapping(mapping, "smux")?;
let brutal = yaml_mapping(&smux, "brutal-opts").map(|brutal| ShadowsocksSmuxBrutal {
enabled: yaml_bool(&brutal, "enabled").unwrap_or(false),
up: yaml_string(&brutal, "up").map(ToOwned::to_owned),
down: yaml_string(&brutal, "down").map(ToOwned::to_owned),
});
Some(ShadowsocksSmux {
enabled: yaml_bool(&smux, "enabled").unwrap_or(false),
protocol: yaml_string(&smux, "protocol").map(ToOwned::to_owned),
max_connections: yaml_u32(&smux, "max-connections"),
min_streams: yaml_u32(&smux, "min-streams"),
max_streams: yaml_u32(&smux, "max-streams"),
padding: yaml_bool(&smux, "padding").unwrap_or(false),
statistic: yaml_bool(&smux, "statistic").unwrap_or(false),
only_tcp: yaml_bool(&smux, "only-tcp").unwrap_or(false),
brutal,
})
}
fn yaml_plugin(mapping: &HashMap<String, Value>) -> Option<ShadowsocksPlugin> {
let name = yaml_string(mapping, "plugin")?.to_owned();
let opts = yaml_mapping(mapping, "plugin-opts")
let name = yaml_scalar_string(mapping, "plugin")?;
let mut opts: HashMap<String, String> = yaml_mapping(mapping, "plugin-opts")
.map(|opts| {
opts.iter()
.filter_map(|(key, value)| {
value
.as_str()
.map(|value| (key.clone(), value.to_owned()))
.or_else(|| {
value
.as_bool()
.map(|value| (key.clone(), value.to_string()))
})
yaml_plugin_value(value).map(|value| (key.clone(), value))
})
.collect()
})
.unwrap_or_default();
if let Some(headers) =
yaml_mapping(mapping, "plugin-opts").and_then(|opts| yaml_mapping(&opts, "headers"))
{
for (key, value) in headers {
if let Some(value) = yaml_plugin_value(&value) {
opts.insert(format!("headers.{key}"), value.to_owned());
}
}
}
if let Some(ech_opts) =
yaml_mapping(mapping, "plugin-opts").and_then(|opts| yaml_mapping(&opts, "ech-opts"))
{
for (key, value) in ech_opts {
if let Some(value) = yaml_plugin_value(&value) {
opts.insert(format!("ech-opts.{key}"), value.to_owned());
}
}
}
Some(ShadowsocksPlugin { name, opts })
}
fn yaml_plugin_value(value: &Value) -> Option<String> {
yaml_value_to_scalar_string(value).or_else(|| {
value.as_sequence().map(|values| {
values
.iter()
.filter_map(yaml_value_to_scalar_string)
.collect::<Vec<_>>()
.join(",")
})
})
}
fn parse_ss_plugin(plugin: &str) -> Option<ShadowsocksPlugin> {
if !plugin.contains(';') {
return None;
}
let (name, rest) = plugin.split_once(';').unwrap_or((plugin, ""));
let mut opts = HashMap::new();
for part in rest.split(';').filter(|part| !part.is_empty()) {
if let Some((key, value)) = part.split_once('=') {
opts.insert(key.to_owned(), value.to_owned());
let key = decode_ss_plugin_component(key);
if let Some(key) = non_empty(&key) {
opts.insert(key.to_owned(), decode_ss_plugin_component(value));
}
} else if let Some(key) = non_empty(part) {
let key = decode_ss_plugin_component(key);
if let Some(key) = non_empty(&key) {
opts.insert(key.to_owned(), "true".to_owned());
}
}
non_empty(name).map(|name| ShadowsocksPlugin { name: name.to_owned(), opts })
}
let name = non_empty(name)?;
let normalized_name = normalize_ss_uri_plugin_name(name)?;
normalize_ss_uri_plugin_opts(plugin, normalized_name, &mut opts);
Some(ShadowsocksPlugin {
name: normalized_name.to_owned(),
opts,
})
}
fn normalize_ss_uri_plugin_name(name: &str) -> Option<&str> {
let normalized = name.trim().to_ascii_lowercase();
if normalized.contains("obfs") {
Some("obfs")
} else if normalized.contains("v2ray-plugin") {
Some("v2ray-plugin")
} else {
None
}
}
fn normalize_ss_uri_plugin_opts(
raw_plugin: &str,
normalized_name: &str,
opts: &mut HashMap<String, String>,
) {
if normalized_name == "v2ray-plugin" {
if !opts.contains_key("mode") {
if let Some(mode) = opts.get("obfs").cloned() {
opts.insert("mode".to_owned(), mode);
}
}
if !opts.contains_key("host") {
if let Some(host) = opts.get("obfs-host").cloned() {
opts.insert("host".to_owned(), host);
}
}
if raw_plugin.contains("tls") {
opts.insert("tls".to_owned(), "true".to_owned());
}
}
}
fn decode_ss_plugin_component(value: &str) -> String {
let query_value = value.replace('+', " ");
percent_decode(&query_value).unwrap_or_else(|_| value.to_owned())
}
fn decode_subscription_body(body: &str) -> Option<String> {
@ -761,6 +970,12 @@ fn default_true() -> bool {
true
}
const UOT_LEGACY_VERSION: u8 = 1;
fn default_uot_legacy_version() -> u8 {
UOT_LEGACY_VERSION
}
fn non_empty(value: &str) -> Option<&str> {
let value = value.trim();
(!value.is_empty()).then_some(value)
@ -803,7 +1018,7 @@ mod tests {
#[test]
fn parses_base64_uri_list_with_trojan_and_shadowsocks() {
let body = STANDARD.encode(
"trojan://secret@example.com:443?security=tls&sni=front.example&type=grpc&serviceName=edge&allowInsecure=1&fp=random#US%20Trojan\nss://YWVzLTEyOC1nY206cGFzcw@example.net:8388?uot=1#SS%20Node\n",
"trojan://secret@example.com:443?security=tls&sni=front.example&type=grpc&serviceName=edge&allowInsecure=1&fp=random#US%20Trojan\nss://YWVzLTEyOC1nY206cGFzcw@example.net:8388?uot=1&uot-version=2#SS%20Node\n",
);
let preview = parse_subscription_body(&body, Some("https://sub.example/path?token=secret"));
assert_eq!(preview.detected_format, ProxySubscriptionFormat::UriList);
@ -818,6 +1033,96 @@ mod tests {
assert_eq!(trojan_alpn_for_test(trojan), Vec::<String>::new());
assert!(trojan.udp);
assert_eq!(preview.nodes[1].protocol, ProxyProtocol::Shadowsocks);
let ProxyNodeConfig::Shadowsocks(shadowsocks) = &preview.nodes[1].config else {
panic!("expected Shadowsocks node");
};
assert!(shadowsocks.udp);
assert!(shadowsocks.udp_over_tcp);
assert_eq!(shadowsocks.udp_over_tcp_version, 2);
}
#[test]
fn parses_shadowsocks_v2ray_plugin_uri_aliases() {
let body = "ss://YWVzLTEyOC1nY206cGFzcw@example.net:8388?plugin=v2ray-plugin%3Bobfs%3Dwebsocket%3Bobfs-host%3Dcdn.example%3Bpath%3D%252Fws%3Btls#SS%20V2Ray";
let preview = parse_subscription_body(body, Some("https://sub.example/path?token=secret"));
assert_eq!(preview.detected_format, ProxySubscriptionFormat::UriList);
assert_eq!(preview.nodes.len(), 1);
assert_eq!(preview.rejected.len(), 0);
let ProxyNodeConfig::Shadowsocks(shadowsocks) = &preview.nodes[0].config else {
panic!("expected Shadowsocks node");
};
let plugin = shadowsocks.plugin.as_ref().unwrap();
assert_eq!(plugin.name, "v2ray-plugin");
assert_eq!(plugin.opts.get("mode"), Some(&"websocket".to_owned()));
assert_eq!(plugin.opts.get("host"), Some(&"cdn.example".to_owned()));
assert_eq!(plugin.opts.get("obfs"), Some(&"websocket".to_owned()));
assert_eq!(
plugin.opts.get("obfs-host"),
Some(&"cdn.example".to_owned())
);
assert_eq!(plugin.opts.get("path"), Some(&"/ws".to_owned()));
assert_eq!(plugin.opts.get("tls"), Some(&"true".to_owned()));
}
#[test]
fn parses_base64_uri_list_even_when_yaml_accepts_scalar_text() {
let body = STANDARD
.encode("ss://Y2hhY2hhMjAtaWV0Zi1wb2x5MTMwNTpwYXNz@example.net:1080#SS%20Node\n");
let preview = parse_subscription_body(&body, Some("https://sub.example/path?token=secret"));
assert_eq!(preview.detected_format, ProxySubscriptionFormat::UriList);
assert_eq!(preview.rejected.len(), 0);
assert_eq!(preview.nodes.len(), 1);
let ProxyNodeConfig::Shadowsocks(shadowsocks) = &preview.nodes[0].config else {
panic!("expected Shadowsocks node");
};
assert_eq!(shadowsocks.cipher, "chacha20-ietf-poly1305");
assert_eq!(shadowsocks.password, "pass");
}
#[test]
fn parses_shadowsocks_simple_obfs_uri_like_mihomo_converter() {
let body = "ss://YWVzLTEyOC1nY206cGFzcw@example.net:8388?plugin=obfs-local%3Bobfs%3Dhttp%3Bobfs-host%3Dcdn.example#SS%20Obfs";
let preview = parse_subscription_body(body, Some("https://sub.example/path?token=secret"));
assert_eq!(preview.detected_format, ProxySubscriptionFormat::UriList);
assert_eq!(preview.nodes.len(), 1);
assert_eq!(preview.rejected.len(), 0);
let ProxyNodeConfig::Shadowsocks(shadowsocks) = &preview.nodes[0].config else {
panic!("expected Shadowsocks node");
};
let plugin = shadowsocks.plugin.as_ref().unwrap();
assert_eq!(plugin.name, "obfs");
assert_eq!(plugin.opts.get("obfs"), Some(&"http".to_owned()));
assert_eq!(
plugin.opts.get("obfs-host"),
Some(&"cdn.example".to_owned())
);
}
#[test]
fn ignores_bare_shadowsocks_uri_plugin_like_mihomo_converter() {
let body =
"ss://YWVzLTEyOC1nY206cGFzcw@example.net:8388?plugin=v2ray-plugin#SS%20Bare%20Plugin";
let preview = parse_subscription_body(body, Some("https://sub.example/path?token=secret"));
assert_eq!(preview.detected_format, ProxySubscriptionFormat::UriList);
assert_eq!(preview.nodes.len(), 1);
assert_eq!(preview.rejected.len(), 0);
let ProxyNodeConfig::Shadowsocks(shadowsocks) = &preview.nodes[0].config else {
panic!("expected Shadowsocks node");
};
assert!(shadowsocks.plugin.is_none());
}
#[test]
fn ignores_unknown_shadowsocks_uri_plugin_like_mihomo_converter() {
let body = "ss://YWVzLTEyOC1nY206cGFzcw@example.net:8388?plugin=gost-plugin%3Bmode%3Dwebsocket#SS%20Gost%20URI";
let preview = parse_subscription_body(body, Some("https://sub.example/path?token=secret"));
assert_eq!(preview.detected_format, ProxySubscriptionFormat::UriList);
assert_eq!(preview.nodes.len(), 1);
assert_eq!(preview.rejected.len(), 0);
let ProxyNodeConfig::Shadowsocks(shadowsocks) = &preview.nodes[0].config else {
panic!("expected Shadowsocks node");
};
assert!(shadowsocks.plugin.is_none());
}
#[test]
@ -842,17 +1147,240 @@ proxies:
port: 8388
cipher: aes-128-gcm
password: pass
plugin: v2ray-plugin
plugin-opts:
mode: websocket
path: /ws
v2ray-http-upgrade: true
v2ray-http-upgrade-fast-open: true
headers:
Host: edge.example.net
ech-opts:
enable: true
config: AQIDBA==
query-server-name: public.example.net
udp-over-tcp: true
udp-over-tcp-version: 2
smux:
enabled: true
protocol: smux
max-connections: 4
min-streams: 2
padding: true
statistic: true
only-tcp: false
brutal-opts:
enabled: true
up: 10 Mbps
down: 20 Mbps
- name: ss-shadowtls
type: ss
server: shadow.example.net
port: 443
cipher: aes-128-gcm
password: pass
client-fingerprint: chrome
plugin: shadow-tls
plugin-opts:
host: cover.example.com
password: shadow-secret
version: 1
fingerprint: "00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"
alpn:
- h2
- http/1.1
- name: ss-kcptun
type: ss
server: kcp.example.net
port: 29900
cipher: aes-128-gcm
password: pass
plugin: kcptun
plugin-opts:
key: secret
crypt: aes
mode: fast2
conn: 2
nocomp: true
smuxver: 2
"#,
None,
);
assert_eq!(preview.detected_format, ProxySubscriptionFormat::ClashYaml);
assert_eq!(preview.nodes.len(), 2);
assert_eq!(preview.nodes.len(), 4);
assert_eq!(preview.rejected.len(), 0);
let ProxyNodeConfig::Trojan(trojan) = &preview.nodes[0].config else {
panic!("expected Trojan node");
};
assert!(!trojan.alpn_present);
assert!(!trojan.udp);
let ProxyNodeConfig::Shadowsocks(shadowsocks) = &preview.nodes[1].config else {
panic!("expected Shadowsocks node");
};
assert!(!shadowsocks.udp);
assert!(shadowsocks.udp_over_tcp);
assert_eq!(shadowsocks.udp_over_tcp_version, 2);
assert_eq!(
shadowsocks
.plugin
.as_ref()
.unwrap()
.opts
.get("headers.Host"),
Some(&"edge.example.net".to_owned())
);
assert_eq!(
shadowsocks
.plugin
.as_ref()
.unwrap()
.opts
.get("v2ray-http-upgrade-fast-open"),
Some(&"true".to_owned())
);
assert_eq!(
shadowsocks
.plugin
.as_ref()
.unwrap()
.opts
.get("ech-opts.enable"),
Some(&"true".to_owned())
);
assert_eq!(
shadowsocks
.plugin
.as_ref()
.unwrap()
.opts
.get("ech-opts.config"),
Some(&"AQIDBA==".to_owned())
);
assert_eq!(
shadowsocks
.plugin
.as_ref()
.unwrap()
.opts
.get("ech-opts.query-server-name"),
Some(&"public.example.net".to_owned())
);
let smux = shadowsocks
.smux
.as_ref()
.expect("expected Shadowsocks smux");
assert!(smux.enabled);
assert_eq!(smux.protocol.as_deref(), Some("smux"));
assert_eq!(smux.max_connections, Some(4));
assert_eq!(smux.min_streams, Some(2));
assert!(smux.padding);
assert!(smux.statistic);
assert!(!smux.only_tcp);
let brutal = smux.brutal.as_ref().expect("expected brutal opts");
assert!(brutal.enabled);
assert_eq!(brutal.up.as_deref(), Some("10 Mbps"));
assert_eq!(brutal.down.as_deref(), Some("20 Mbps"));
let ProxyNodeConfig::Shadowsocks(shadowsocks) = &preview.nodes[2].config else {
panic!("expected Shadowsocks node");
};
let plugin = shadowsocks.plugin.as_ref().unwrap();
assert_eq!(plugin.name, "shadow-tls");
assert_eq!(shadowsocks.client_fingerprint.as_deref(), Some("chrome"));
assert_eq!(plugin.opts.get("version"), Some(&"1".to_owned()));
assert_eq!(
plugin.opts.get("fingerprint"),
Some(&"00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff".to_owned())
);
assert_eq!(plugin.opts.get("alpn"), Some(&"h2,http/1.1".to_owned()));
let ProxyNodeConfig::Shadowsocks(shadowsocks) = &preview.nodes[3].config else {
panic!("expected Shadowsocks node");
};
let plugin = shadowsocks.plugin.as_ref().unwrap();
assert_eq!(plugin.name, "kcptun");
assert_eq!(plugin.opts.get("conn"), Some(&"2".to_owned()));
assert_eq!(plugin.opts.get("nocomp"), Some(&"true".to_owned()));
assert_eq!(plugin.opts.get("smuxver"), Some(&"2".to_owned()));
}
#[test]
fn shadowsocks_yaml_udp_defaults_match_mihomo() {
let preview = parse_subscription_body(
r#"
proxies:
- name: ss-default
type: ss
server: example.net
port: 8388
cipher: aes-128-gcm
password: pass
- name: ss-udp
type: ss
server: example.org
port: 8388
cipher: aes-128-gcm
password: pass
udp: true
"#,
None,
);
assert_eq!(preview.detected_format, ProxySubscriptionFormat::ClashYaml);
assert_eq!(preview.rejected.len(), 0);
let ProxyNodeConfig::Shadowsocks(default_node) = &preview.nodes[0].config else {
panic!("expected Shadowsocks node");
};
assert!(!default_node.udp);
let ProxyNodeConfig::Shadowsocks(udp_node) = &preview.nodes[1].config else {
panic!("expected Shadowsocks node");
};
assert!(udp_node.udp);
}
#[test]
fn shadowsocks_yaml_accepts_weakly_typed_scalars_like_mihomo() {
let preview = parse_subscription_body(
r#"
proxies:
- name: 12345
type: ss
server: 127.0.0.1
port: "8388"
cipher: aes-128-gcm
password: 123456
udp: 1
udp-over-tcp: 0
client-fingerprint: false
plugin: v2ray-plugin
plugin-opts:
mode: websocket
path: /ws
mux: true
alpn:
- h2
- 123
smux:
enabled: 1
padding: 0
"#,
None,
);
assert_eq!(preview.detected_format, ProxySubscriptionFormat::ClashYaml);
assert_eq!(preview.rejected.len(), 0);
assert_eq!(preview.nodes.len(), 1);
assert_eq!(preview.nodes[0].name, "12345");
let ProxyNodeConfig::Shadowsocks(shadowsocks) = &preview.nodes[0].config else {
panic!("expected Shadowsocks node");
};
assert_eq!(shadowsocks.password, "123456");
assert!(shadowsocks.udp);
assert!(!shadowsocks.udp_over_tcp);
assert_eq!(shadowsocks.client_fingerprint.as_deref(), Some("false"));
let smux = shadowsocks.smux.as_ref().expect("expected smux");
assert!(smux.enabled);
assert!(!smux.padding);
let plugin = shadowsocks.plugin.as_ref().expect("expected plugin");
assert_eq!(plugin.name, "v2ray-plugin");
assert_eq!(plugin.opts.get("mux"), Some(&"true".to_owned()));
assert_eq!(plugin.opts.get("alpn"), Some(&"h2,123".to_owned()));
}
#[test]

View file

@ -0,0 +1,6 @@
FROM ghcr.io/sagernet/sing-box:v1.13.12
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]

View file

@ -0,0 +1,68 @@
# Railway sing-box Shadowsocks Test Node
This is a minimal sing-box Shadowsocks server for testing Burrow against a real
internet-hosted node on Railway.
Railway public networking exposes raw TCP through TCP Proxy. That is enough for
plain Shadowsocks TCP testing. It is not a full UDP validation environment, so
use Burrow's local self-tests or a VPS for native UDP testing.
## Deploy
1. Create a new Railway service from this repo.
2. Set the service root directory to:
```text
deploy/railway-sing-box
```
3. Add Railway variables:
```text
SS_PASSWORD=<strong random password>
SS_METHOD=chacha20-ietf-poly1305
SS_LISTEN_HOST=0.0.0.0
SS_LISTEN_PORT=8388
```
`SS_METHOD`, `SS_LISTEN_HOST`, and `SS_LISTEN_PORT` are optional; those are
the defaults.
4. Deploy the service.
5. In the service settings, create a TCP Proxy with internal port `8388`.
6. Railway will show an external proxy host and port, for example:
```text
shuttle.proxy.rlwy.net:15140
```
## Burrow Test URI
Build the Shadowsocks URI with:
```sh
uv run python - <<'PY'
import base64
import urllib.parse
method = "chacha20-ietf-poly1305"
password = "<SS_PASSWORD>"
host = "<RAILWAY_TCP_PROXY_DOMAIN>"
port = "<RAILWAY_TCP_PROXY_PORT>"
name = "railway-sing-box-ss"
userinfo = base64.urlsafe_b64encode(f"{method}:{password}".encode()).decode().rstrip("=")
print(f"ss://{userinfo}@{host}:{port}#{urllib.parse.quote(name)}")
PY
```
Use the generated `ss://` URI in a Burrow proxy subscription or local test
payload.
## Notes
- Railway's TCP Proxy external port is assigned by Railway. Use that external
port in the client URI, not `8388`.
- This is intentionally a plain Shadowsocks node. Once plain TCP works, add
extra protocol features in separate test deployments so failures stay easy to
isolate.

View file

@ -0,0 +1,47 @@
#!/bin/sh
set -eu
: "${SS_PASSWORD:?Set SS_PASSWORD to a strong test password in Railway variables}"
SS_METHOD="${SS_METHOD:-chacha20-ietf-poly1305}"
SS_LISTEN_HOST="${SS_LISTEN_HOST:-0.0.0.0}"
SS_LISTEN_PORT="${SS_LISTEN_PORT:-8388}"
LOG_LEVEL="${LOG_LEVEL:-info}"
json_escape() {
printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g'
}
SS_METHOD_JSON="$(json_escape "$SS_METHOD")"
SS_PASSWORD_JSON="$(json_escape "$SS_PASSWORD")"
LOG_LEVEL_JSON="$(json_escape "$LOG_LEVEL")"
SS_LISTEN_HOST_JSON="$(json_escape "$SS_LISTEN_HOST")"
mkdir -p /etc/sing-box
cat >/etc/sing-box/config.json <<EOF
{
"log": {
"level": "$LOG_LEVEL_JSON",
"timestamp": true
},
"inbounds": [
{
"type": "shadowsocks",
"tag": "ss-test",
"listen": "$SS_LISTEN_HOST_JSON",
"listen_port": $SS_LISTEN_PORT,
"method": "$SS_METHOD_JSON",
"password": "$SS_PASSWORD_JSON"
}
],
"outbounds": [
{
"type": "direct",
"tag": "direct"
}
]
}
EOF
echo "starting sing-box Shadowsocks test server on $SS_LISTEN_HOST:$SS_LISTEN_PORT"
exec sing-box run -c /etc/sing-box/config.json

View file

@ -0,0 +1,10 @@
{
"$schema": "https://railway.com/railway.schema.json",
"build": {
"builder": "DOCKERFILE"
},
"deploy": {
"restartPolicyType": "ON_FAILURE",
"restartPolicyMaxRetries": 10
}
}

View file

@ -134,7 +134,7 @@ The first packet-tunnel implementation follows the Mihomo adapter shape but keep
- On Apple, node selection and refresh are applied to both the app-facing control daemon and, while connected, the packet-tunnel daemon over `burrow-packet-tunnel.sock`. The packet daemon swaps the active proxy outbound inside the existing packet bridge, so new TCP and UDP flows use the selected node without restarting the Network Extension. Existing flows may continue on the outbound they opened with until they close.
- TCP and UDP packets are reconstructed with the existing smoltcp userspace stack.
- Trojan TCP nodes are dialed over TLS and use Trojan TCP and UDP request framing directly.
- Shadowsocks AEAD nodes are dialed directly for TCP and UDP using SIP004-style salt, HKDF subkeys, and AEAD chunks/datagrams.
- Shadowsocks nodes are dialed directly for TCP and UDP through Burrow's Shadowsocks runtime adapter. `BEP-0012` refines the Mihomo-compatible Shadowsocks runtime path, including expanded cipher families, UDP gating, and staged plugin/UDP-over-TCP support.
- Unsupported runtime transports such as Trojan `ws`/`grpc`, Shadowsocks plugins, and Shadowsocks UDP-over-TCP are still parsed and preserved from subscriptions, but they are rejected by runtime selection until those adapter transports are implemented. If no node is explicitly selected, Burrow chooses the first runtime-supported node instead of starting an unsupported transport.
- Apple SwiftUI and Linux GTK import flows preview nodes, restrict the picker to runtime-supported nodes, and pass the chosen node ordinal to the daemon when applying an import.
- Proxy-subscription tunnel settings point DNS at the daemon-owned tunnel address. The packet runtime answers ordinary A queries with fake IPs and records the fake-IP to hostname mapping so later TCP flows can send hostname-form proxy requests. This avoids making website DNS depend on outbound UDP support while preserving hostname metadata for Trojan and Shadowsocks dialing.

View file

@ -0,0 +1,356 @@
# `BEP-0012` - Mihomo-Compatible Shadowsocks Runtime
```text
Status: Draft
Proposal: BEP-0012
Authors: Codex
Coordinator: Jett Chen
Reviewers: Pending
Constitution Sections: 2, 4, 5
Implementation PRs: Pending
Decision Date: Pending
```
## Summary
Burrow's proxy subscription runtime should move from a narrow hand-written
Shadowsocks AEAD implementation toward Mihomo-compatible Shadowsocks behavior
for imported nodes. Mihomo remains the reference for accepted ciphers, URI
conversion, plugins, UDP-over-TCP, and packet tunnel outbound semantics, while
Burrow keeps the daemon-owned packet runtime and Apple gRPC boundary described
by `BEP-0005` and `BEP-0010`.
This proposal covers the compatibility path for Shadowsocks outbounds. It starts
by delegating protocol crypto and stream/datagram framing to a maintained Rust
Shadowsocks implementation, adds Burrow-owned adapters for Mihomo cipher gaps
where Rust crates are available, and implements Mihomo-compatible UDP-over-TCP.
Plugin transports remain staged behind explicit lifecycle and security controls.
## Motivation
- Subscriptions that work in Mihomo commonly use more than `aes-128-gcm`,
`aes-256-gcm`, or `chacha20-ietf-poly1305`.
- Mihomo supports legacy stream ciphers, AEAD extra ciphers, AEAD 2022 methods,
SIP003-style plugins, ShadowTLS/restls/kcptun transports, and UDP-over-TCP.
- Burrow's previous hand-written Shadowsocks runtime made unsupported ciphers
appear like permanent product choices rather than implementation gaps.
- Protocol crypto and framing are security-sensitive. Burrow should use a
maintained Rust crate where it can do so without shelling out to Mihomo or
weakening the daemon packet-runtime boundary.
## Detailed Design
- Use the Rust `shadowsocks` crate as Burrow's default Shadowsocks protocol
adapter for supported cipher families, TCP stream framing, and UDP datagram
framing.
- Add Burrow-owned AEAD adapters backed by Rust crypto crates for Mihomo methods
missing from the default adapter when their wire behavior is straightforward.
The first such methods are `aes-192-gcm`, `aes-192-ccm`,
`chacha8-ietf-poly1305`, `xchacha8-ietf-poly1305`, `rabbit128-poly1305`,
`aegis-128l`, `aegis-256`, `aez-384`, `deoxys-ii-256-128`, `ascon128`,
`ascon128a`, `lea-*-gcm`, `2022-blake3-aes-128-ccm`, and
`2022-blake3-aes-256-ccm`.
- Add Burrow-owned legacy stream adapters for Mihomo's `chacha20` and
`xchacha20` methods. They use the same Shadowsocks v1 IV-plus-stream shape as
Mihomo's `sing-shadowsocks2` implementation, backed by Rust `chacha20`
primitives.
- Keep Burrow-owned socket creation, route exclusion, and Apple physical
interface binding behavior. The Shadowsocks adapter wraps already-connected
Burrow sockets where needed instead of taking over tunnel ownership.
- Runtime-supported ciphers include the compiled Rust crate feature set:
legacy stream methods, AEAD methods, AEAD extra methods, and AEAD 2022
methods, plus an explicit Mihomo-compatible `none` transport, custom
ChaCha20/XChaCha20 stream, AES-192 GCM/CCM, ChaCha8-Poly1305,
Rabbit128-Poly1305, AEGIS, AEZ-384, Deoxys-II-256-128, Ascon128, Ascon128a,
LEA-GCM, and AEAD 2022 AES-CCM adapters. `none` stays Burrow-owned because
the upstream Rust crate accepts the method name during validation but cannot
build a client runtime for it.
AEAD 2022 password handling follows Mihomo's base64 key parsing rule:
every decoded password segment must be exactly the method key length, with
no hashing or truncation fallback for oversized material. Burrow pre-validates
the delegated GCM/ChaCha 2022 methods so the upstream Rust adapter cannot
accept unpadded key material that Mihomo's `base64.StdEncoding` would reject.
Burrow keeps legacy aliases such as `aead_aes_128_gcm` and
`chacha20-poly1305` for previously imported payloads.
Burrow also preserves Mihomo's top-level Shadowsocks `smux` option during
import. The packet runtime supports TCP and UDP `protocol: h2mux`, `protocol:
smux`, and `protocol: yamux` paths for delegated standard ciphers, `none`, Burrow's custom ciphers,
and AEAD 2022 CCM by dialing Mihomo's `sp.mux.sing-box.arpa:444` control
destination through Shadowsocks, writing the sing-mux protocol preface, and
opening mux streams with Mihomo's per-stream SOCKS destination request. Burrow
also supports Mihomo's top-level sing-mux padding shape: the version 1 session
preface carries a padding flag and 256-767 bytes of skipped padding, then the
first 16 mux reads and writes are length-prefixed padded frames before the
stream falls back to raw mux bytes. UDP-over-sing-mux uses Mihomo's packet
stream shape: UDP stream flags, one SOCKS destination, one status byte, and
length-prefixed datagrams. Brutal mode performs Mihomo's `_BrutalBwExchange`
stream handshake and negotiates the same send-rate cap; OS-level TCP Brutal
congestion control remains a runtime best-effort because it is
Linux-kernel-module dependent in Mihomo and unavailable through every Burrow
plugin wrapper.
The remaining Mihomo compatibility gaps for Shadowsocks are advanced plugin
transports and live interop coverage for additional external permutations,
not base Shadowsocks cipher, UDP-over-TCP framing, or top-level sing-mux
negotiation.
- Honor the Shadowsocks `udp` flag at runtime. Clash/Mihomo YAML imports default
omitted `udp` to false like Mihomo's `ShadowSocksOption` zero value, while
`ss://` URI conversion keeps Mihomo's converter behavior of setting `udp` to
true. UDP-disabled nodes must reject UDP sessions instead of silently
attempting native UDP relay.
- Honor `udp-over-tcp` for Shadowsocks nodes by opening a Shadowsocks TCP stream
to Mihomo's UOT magic destination. Burrow supports both Mihomo's legacy
version 1 destination, `sp.udp-over-tcp.arpa`, and version 2 destination,
`sp.v2.udp-over-tcp.arpa`; absent or zero versions normalize to legacy v1 to
match Mihomo config defaults. Runtime UDP sessions are allowed when
`udp-over-tcp` is active even if native UDP is disabled, because packets are
carried over the selected TCP plugin path instead of a native Shadowsocks UDP
relay.
- Support Mihomo's `obfs` plugin for HTTP and TLS simple-obfs by wrapping the
already-connected TCP socket before the Shadowsocks stream handshake. Native
UDP remains direct Shadowsocks UDP, while `udp-over-tcp` uses the same wrapped
TCP path. The plugin mode must be explicit, matching Mihomo's `obfs` option
decoder: simple-obfs accepts only `http` and `tls`. Burrow normalizes SIP002
URI plugin names that contain `obfs` into Mihomo's YAML/runtime plugin name
`obfs` during import only when the plugin string has semicolon-delimited
SIP002 options; bare `ss://` plugin names and non-Mihomo-converted URI plugin
names are ignored like Mihomo's converter. Daemon runtime and Apple usability
checks intentionally require Mihomo's exact, case-sensitive plugin names. YAML
`plugin: obfs-local`, `plugin: Obfs`, `plugin: shadowtls`, and other
unrecognized plugin names therefore fall through to plain Shadowsocks like
Mihomo's outbound constructor instead of being treated as supported plugin
transports.
- Support Mihomo's `v2ray-plugin` websocket mode, including the lightweight
v2ray-plugin mux preface enabled by Mihomo's default `mux: true`, optional
TLS, custom path, host, and `v2ray-http-upgrade` raw-upgrade mode. Burrow also supports Mihomo's
`v2ray-http-upgrade-fast-open` behavior by returning the upgraded raw stream
after sending the HTTP request and validating the 101 response on first read.
Imported `ss://` v2ray-plugin strings preserve Mihomo's SIP002 aliases such
as `obfs=websocket`, `obfs-host`, and bare `tls` flags, while also
materializing Mihomo's converted `mode`, `host`, and boolean `tls` option
shape.
For websocket paths with Mihomo's `ed=` query option, Burrow removes `ed`
from the request path and moves the first payload bytes into the
`Sec-WebSocket-Protocol` header with unpadded URL-safe base64 encoding.
When TLS is enabled, Burrow accepts Mihomo's `certificate` and `private-key`
plugin options as a PEM client
certificate/key pair for mTLS, and enforces Mihomo's `fingerprint`
certificate pin option on both Rustls and Apple native TLS paths. Burrow also
supports Mihomo's `ech-opts` for websocket TLS by passing inline ECH config
lists to Rustls, or by resolving HTTPS records through the physical DNS
resolver when `ech-opts.enable` is set without an inline config.
- Support Mihomo's `gost-plugin` websocket mode, including the Go-smux client
stream enabled by Mihomo's default `mux: true` and `mux=false` raw websocket
mode. `v2ray-plugin` and `gost-plugin` require explicit `mode: websocket` or
the imported `obfs=websocket` alias, because Mihomo rejects those plugin nodes
when the mode field is omitted. TLS-enabled gost websocket nodes use the same
Mihomo-compatible client certificate/key and certificate pin handling,
including websocket ECH support.
- Support Mihomo's `shadow-tls` plugin for versions 1, 2, and 3 by performing the
cover TLS handshake in-process with Rustls, then exposing the post-handshake
stream to the Shadowsocks adapter. Version 2 wraps application data in
ShadowTLS application-data records and prepends the first client payload with
the handshake HMAC, matching Mihomo's `sing-shadowtls` client behavior.
Version 3 uses a ShadowTLS-capable Rustls fork for the ClientHello session ID
HMAC, then verifies and emits the four-byte HMAC application-data frames used
by Mihomo's `sing-shadowtls` v3 client. Version 1 follows Mihomo's option
shape and does not require a plugin password.
Burrow also parses and enforces Mihomo's ShadowTLS `fingerprint` certificate
pin option and supports Mihomo's `certificate` and `private-key` mTLS client
certificate options. `client-fingerprint` is preserved from subscriptions.
ShadowTLS v1 ignores `client-fingerprint` like Mihomo because it always uses a
normal TLS 1.2 handshake. For ShadowTLS v2/v3, unknown fingerprint names fall
back to the normal TLS ClientHello, while names that Mihomo maps to uTLS
profiles are rejected by the default runtime and Apple usability filter until
Burrow has uTLS-style ClientHello impersonation. Burrow carries an optional
`boring-browser-fingerprints` feature that wires ShadowTLS v2 active browser
fingerprints through Rama's BoringSSL-backed embedded browser TLS profiles.
ShadowTLS ALPN defaults to `h2,http/1.1` only when the option is absent;
explicitly empty `alpn` remains empty like Mihomo's decoded option struct.
The same feature also provides the ShadowTLS v3 browser-profile path by
signing the first ClientHello record's 32-byte session ID before it reaches
the wire, matching Mihomo's uTLS `SessionIDGenerator` shape. The BoringSSL
browser-profile paths also load Mihomo-compatible `certificate` and
`private-key` client-auth material, so ShadowTLS v2/v3 browser fingerprints
and mTLS can be combined the same way Mihomo passes those options into
`sing-shadowtls`. For ShadowTLS `client-fingerprint: random`, Burrow follows
Mihomo's initial random selection shape by choosing one process-wide browser
profile with the same chrome/safari/ios/firefox weights instead of re-rolling
each connection. The ShadowTLS v2 browser-profile path also removes the
`X25519MLKEM768` supported group before building the BoringSSL connector,
matching Mihomo's v2-only workaround for servers that fail with that hybrid
key-share. These paths are intentionally not default yet because they add
a CMake/BoringSSL toolchain requirement, still need CI coverage, and still need
live interop coverage. The optional browser-profile path covers Mihomo's
common `chrome`, `firefox`, `safari`, `ios`, `android`, `edge`, `random`, and
`safari16` names where Rama has matching embedded profiles; older fixed
aliases such as `chrome120` and `firefox120` remain gated until Burrow has
exact matching ClientHello profiles instead of approximate fallbacks.
- Support Mihomo's `kcptun` plugin in-process with Rust KCP, snappy, and smux
crates. Burrow parses Mihomo's kcptun option names and defaults, forces
UDP-over-TCP for kcptun nodes like Mihomo, and opens Shadowsocks TCP streams
over KCP plus optional snappy plus smux. Burrow maintains a Mihomo-style
round-robin smux session pool for `conn` and rotates expired sessions using
`autoexpire` plus delayed `scavengettl` cleanup. Burrow mirrors Mihomo's smux
keepalive timeout shape: the default timeout remains 30 seconds unless the
keepalive interval is at least that value, in which case timeout becomes three
times the interval. Burrow also applies Mihomo-style best-effort `dscp` and
`sockbuf` settings to the underlying UDP socket. For `ratelimit`, Burrow
honors Mihomo's bytes-per-second value with
the same `64 * 1500` byte burst shape at the KCP stream boundary; this is the
closest in-process equivalent available through the selected Rust KCP crate,
which does not expose kcp-go's exact queued UDP packet transmit hook.
- Plugin support landed in stages and must stay covered:
- `obfs`, matching Mihomo URI conversion and option shapes for
`mode`/`obfs`, `host`/`obfs-host`, and the default `bing.com` host.
- `v2ray-plugin` websocket and its lightweight mux.
- `gost-plugin` websocket and smux.
- TLS client certificate/key support for websocket plugins and `shadow-tls`
v1/v2.
- `kcptun` with Rust KCP, snappy, and smux transport code.
- ShadowTLS v3 with a Rustls fork that exposes the session ID generator
required by the protocol.
- Websocket `ech-opts` on Rustls-backed TLS paths.
- `v2ray-http-upgrade-fast-open` for v2ray-plugin raw upgrade mode.
- Websocket early data via the `ed=` path query option.
- `restls` after its TLS ClientHello, authentication, and traffic-shaping
behavior are mapped to audited Rust code. Burrow now parses Mihomo's
`restls` option shape, including `host`, `password`, `version-hint`,
`restls-script`, top-level `client-fingerprint` preservation, Mihomo's
case-sensitive Restls client ID lookup with Chrome fallback, Mihomo's TLS
1.3 session ticket disablement rule, and the BLAKE3-derived traffic secret.
The default runtime still rejects non-`none` uTLS fingerprints, while the
optional `boring-browser-fingerprints` feature can run TLS 1.3 Restls over a
BoringSSL-backed browser profile and signs the first ClientHello record's
session ID using Mihomo's Restls TLS 1.3 auth material. It also
ports and tests Mihomo's BLAKE3-authenticated ClientHello session-ID
material for TLS 1.2 and TLS 1.3, extraction of TLS 1.3 key-share and PSK
identity labels from the serialized ClientHello shape exposed by Burrow's
Rustls session-ID hook, the server-auth record unmasking offsets, a
handshake stream shim that captures ServerHello random, unmasks the first
server-auth record, and captures the encrypted ClientFinished record for
later application-data authentication, the application-data record header,
masked length/command bytes, one-shot ClientFinished binding on the first
client application record,
script-driven padding target, TLS 1.2 GCM explicit counter shape, Mihomo's
signed one-byte response-interrupt count behavior, and the post-auth stream
state machine for buffering blocked writes, resuming them after server data,
and emitting response-interrupt records. The Restls
post-handshake state machine is also covered by an async stream wrapper that
turns user writes into authenticated TLS application-data records, decodes
server records back into plaintext reads, and flushes resumed uploads plus
response-interrupt records before surfacing server plaintext. Burrow now
wires TLS 1.3 Restls nodes into the Shadowsocks outbound path through the
Rustls session-ID hook, the server-auth handshake shim, and the async Restls
stream wrapper. Burrow vendors the ShadowTLS Rustls fork and its Tokio
adapter so TLS 1.2 Restls can pre-generate the ECDHE keys used in Mihomo's
session-ID HMAC material and then reuse the matching private key when
emitting ClientKeyExchange. TLS 1.2 Restls nodes are now accepted by runtime
support checks, surfaced as selectable Apple subscription nodes, and use the
TLS 1.2 GCM Restls application-record codec.
Restls still needs end-to-end interop coverage before it can be claimed as
full Mihomo parity.
- `Scripts/burrow-proxy-selftest` exercises daemon packet streaming against a
controlled local Trojan/Shadowsocks harness and asserts observed TCP, native
UDP, legacy UDP-over-TCP, version 2 UDP-over-TCP, simple-obfs HTTP/TLS,
v2ray HTTP-upgrade/websocket/mux/early-data, and gost raw websocket shapes.
- `Scripts/burrow-shadowsocks-singmux-interop` drives Burrow's daemon against a
temporary Go peer built from Mihomo's `github.com/metacubex/sing-mux` module
and asserts top-level Shadowsocks `smux`, `yamux`, and `h2mux` TCP and UDP
behavior through the daemon with both unpadded and padded sing-mux sessions,
including the `sp.mux.sing-box.arpa:444` control destination, per-stream TCP
destination metadata, UDP packet destination metadata, and UDP packet echo
delivery. The interop matrix intentionally leaves the underlying Shadowsocks
`udp` flag false so Burrow matches Mihomo's behavior: top-level sing-mux
carries UDP unless `only-tcp` is set.
Remaining compatibility evidence still needs live interop against full
Mihomo/sing-box deployments for plugin combinations that the local harness
does not cover, especially Restls post-handshake behavior and
kernel-dependent TCP Brutal socket behavior.
- Keep UDP-over-TCP packet framing compatible with Mihomo's
`udp-over-tcp-version` handling, including the version 2 request prefix and
per-packet address framing.
- UI runtime-support checks must not hard-code stale cipher lists that disagree
with the daemon. When local decoding is unavoidable, the local list must be
updated with the daemon-supported cipher families or replaced with daemon
preview/list-node data.
## Security and Operational Considerations
- Shadowsocks passwords and subscription URLs remain secrets. Error messages and
logs must not include decoded credentials or bearer tokens.
- Plugin support must stay within Burrow's daemon-owned socket lifecycle unless
a later BEP explicitly introduces subprocess management. Shelling out to
SIP003 plugins would add lifecycle and supply-chain risk and must include
teardown and selected-network scoping if introduced.
- `none` and legacy stream methods exist for compatibility, not as a security
recommendation. UI warnings may be added later for weak ciphers without
rejecting Mihomo-compatible imports.
- AEAD 2022 passwords may be encoded keys. Validation must report malformed keys
without logging the raw password.
- Rollback is a code rollback plus re-importing affected subscriptions if stored
payload shape changes.
## Contributor Playbook
1. Compare behavior against `/Users/jettchen/dev/vpn-ref/mihomo` on the `Alpha`
branch before changing Shadowsocks protocol semantics.
2. Keep Apple UI subscription operations behind daemon gRPC.
3. Prefer maintained Rust crates for Shadowsocks crypto/framing rather than
hand-rolled protocol code.
4. Preserve Burrow socket binding and proxy-server route exclusion behavior.
5. Add tests for each parity expansion:
- cipher acceptance and legacy aliases
- `udp` gating
- plugin parsing and runtime gating
- UDP-over-TCP version handling and packet framing
- AEAD 2022 TCP/UDP packet framing for custom adapters
- end-to-end local Shadowsocks TCP, native UDP, UDP-over-TCP, and plugin
selftests; the proxy selftest must switch the daemon from a Trojan node to
Shadowsocks nodes, prove both selected outbounds can carry a TCP probe,
prove the selected Shadowsocks outbounds can carry native UDP, legacy
UDP-over-TCP, and version 2 UDP-over-TCP echo probes, and prove the
simple-obfs HTTP/TLS plugins, v2ray-plugin websocket/default-mux/
HTTP-upgrade/early-data modes, and gost-plugin raw websocket mode wrap
selected Shadowsocks TCP probes
- top-level sing-mux Shadowsocks TCP and UDP interop against Mihomo's Go
`sing-mux` module
6. Run:
```bash
uv run python Scripts/check-bep-metadata.py
cargo fmt -p burrow
cargo test -p burrow proxy_subscription::tests::
cargo test -p burrow proxy_runtime::tests::
cargo check -p burrow
Scripts/burrow-proxy-selftest
Scripts/burrow-shadowsocks-singmux-interop
```
## Alternatives Considered
- Keep the hand-written AEAD-only implementation. Rejected because it locks
Burrow into a narrow subset and makes Mihomo parity much slower.
- Shell out to Mihomo. Rejected for the same reasons recorded in `BEP-0010`:
Burrow would inherit a separate runtime, control plane, and lifecycle surface.
- Implement plugins before cipher parity. Rejected because cipher parity is a
lower-risk foundation and reduces custom crypto code first.
## Impact on Other Work
- Refines the Shadowsocks runtime portion of `BEP-0010`.
- Complements `BEP-0011`, which covers Trojan-specific Mihomo compatibility.
- Future plugin work may require a dedicated subprocess lifecycle BEP if the
runtime cannot stay within the existing daemon teardown model.
## Decision
Pending review.
## References
- `evolution/proposals/BEP-0005-daemon-ipc-and-apple-boundary.md`
- `evolution/proposals/BEP-0010-proxy-subscriptions-as-packet-tunnel-networks.md`
- `evolution/proposals/BEP-0011-mihomo-compatible-trojan-runtime.md`
- `/Users/jettchen/dev/vpn-ref/mihomo/adapter/outbound/shadowsocks.go`
- `/Users/jettchen/dev/vpn-ref/mihomo/common/convert/converter.go`
- Rust `shadowsocks` crate: https://crates.io/crates/shadowsocks

729
third_party/rustls-fork-shadow-tls/Cargo.lock generated vendored Normal file
View file

@ -0,0 +1,729 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "0.7.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e"
dependencies = [
"memchr",
]
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi 0.1.19",
"libc",
"winapi",
]
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "base64"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bstr"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223"
dependencies = [
"lazy_static",
"memchr",
"regex-automata",
"serde",
]
[[package]]
name = "bumpalo"
version = "3.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d"
[[package]]
name = "cast"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
[[package]]
name = "cc"
version = "1.0.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "2.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
dependencies = [
"bitflags",
"textwrap",
"unicode-width",
]
[[package]]
name = "criterion"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b01d6de93b2b6c65e17c634a26653a29d107b3c98c607c765bf38d041531cd8f"
dependencies = [
"atty",
"cast",
"clap",
"criterion-plot",
"csv",
"itertools",
"lazy_static",
"num-traits",
"oorandom",
"plotters",
"rayon",
"regex",
"serde",
"serde_cbor",
"serde_derive",
"serde_json",
"tinytemplate",
"walkdir",
]
[[package]]
name = "criterion-plot"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2673cc8207403546f45f5fd319a974b1e6983ad1a3ee7e6041650013be041876"
dependencies = [
"cast",
"itertools",
]
[[package]]
name = "crossbeam-channel"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521"
dependencies = [
"cfg-if",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc"
dependencies = [
"cfg-if",
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01a9af1f4c2ef74bb8aa1f7e19706bc72d03598c8a570bb5de72243c7a9d9d5a"
dependencies = [
"autocfg",
"cfg-if",
"crossbeam-utils",
"memoffset",
"scopeguard",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f"
dependencies = [
"cfg-if",
]
[[package]]
name = "csv"
version = "1.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1"
dependencies = [
"bstr",
"csv-core",
"itoa 0.4.8",
"ryu",
"serde",
]
[[package]]
name = "csv-core"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90"
dependencies = [
"memchr",
]
[[package]]
name = "either"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
[[package]]
name = "env_logger"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c90bf5f19754d10198ccb95b70664fc925bd1fc090a0fd9a6ebc54acc8cd6272"
dependencies = [
"atty",
"humantime",
"log",
"regex",
"termcolor",
]
[[package]]
name = "half"
version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7"
[[package]]
name = "hermit-abi"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
]
[[package]]
name = "hermit-abi"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
dependencies = [
"libc",
]
[[package]]
name = "humantime"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "itertools"
version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
[[package]]
name = "itoa"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440"
[[package]]
name = "js-sys"
version = "0.3.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.135"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68783febc7782c6c5cb401fbda4de5a9898be1762314da0bb2c10ced61f18b0c"
[[package]]
name = "log"
version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
dependencies = [
"cfg-if",
]
[[package]]
name = "memchr"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "memoffset"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4"
dependencies = [
"autocfg",
]
[[package]]
name = "num-traits"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
dependencies = [
"autocfg",
]
[[package]]
name = "num_cpus"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
dependencies = [
"hermit-abi 0.2.6",
"libc",
]
[[package]]
name = "once_cell"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1"
[[package]]
name = "oorandom"
version = "11.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
[[package]]
name = "plotters"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2538b639e642295546c50fcd545198c9d64ee2a38620a628724a3b266d5fbf97"
dependencies = [
"num-traits",
"plotters-backend",
"plotters-svg",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "plotters-backend"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "193228616381fecdc1224c62e96946dfbc73ff4384fba576e052ff8c1bea8142"
[[package]]
name = "plotters-svg"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9a81d2759aae1dae668f783c308bc5c8ebd191ff4184aaa1b37f65a6ae5a56f"
dependencies = [
"plotters-backend",
]
[[package]]
name = "proc-macro2"
version = "1.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rayon"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db3a213adf02b3bcfd2d3846bb41cb22857d131789e01df434fb7e7bc0759b7"
dependencies = [
"either",
"rayon-core",
]
[[package]]
name = "rayon-core"
version = "1.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "356a0625f1954f730c0201cdab48611198dc6ce21f4acff55089b5a78e6e835b"
dependencies = [
"crossbeam-channel",
"crossbeam-deque",
"crossbeam-utils",
"num_cpus",
]
[[package]]
name = "regex"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
[[package]]
name = "regex-syntax"
version = "0.6.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244"
[[package]]
name = "ring"
version = "0.16.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
dependencies = [
"cc",
"libc",
"once_cell",
"spin",
"untrusted",
"web-sys",
"winapi",
]
[[package]]
name = "rustls-fork-shadow-tls"
version = "0.20.8"
dependencies = [
"base64",
"criterion",
"env_logger",
"log",
"ring",
"rustls-pemfile",
"rustversion",
"sct",
"webpki",
"webpki-roots",
]
[[package]]
name = "rustls-pemfile"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0864aeff53f8c05aa08d86e5ef839d3dfcf07aeba2db32f12db0ef716e87bd55"
dependencies = [
"base64",
]
[[package]]
name = "rustversion"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8"
[[package]]
name = "ryu"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde"
[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
"winapi-util",
]
[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "sct"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4"
dependencies = [
"ring",
"untrusted",
]
[[package]]
name = "serde"
version = "1.0.145"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "728eb6351430bccb993660dfffc5a72f91ccc1295abaa8ce19b27ebe4f75568b"
[[package]]
name = "serde_cbor"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5"
dependencies = [
"half",
"serde",
]
[[package]]
name = "serde_derive"
version = "1.0.145"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81fa1584d3d1bcacd84c277a0dfe21f5b0f6accf4a23d04d4c6d61f1af522b4c"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cad406b69c91885b5107daf2c29572f6c8cdb3c66826821e286c533490c0bc76"
dependencies = [
"itoa 1.0.5",
"ryu",
"serde",
]
[[package]]
name = "spin"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
[[package]]
name = "syn"
version = "1.0.102"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fcd952facd492f9be3ef0d0b7032a6e442ee9b361d4acc2b1d0c4aaa5f613a1"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "termcolor"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
dependencies = [
"winapi-util",
]
[[package]]
name = "textwrap"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
dependencies = [
"unicode-width",
]
[[package]]
name = "tinytemplate"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"
dependencies = [
"serde",
"serde_json",
]
[[package]]
name = "unicode-ident"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3"
[[package]]
name = "unicode-width"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
[[package]]
name = "untrusted"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
[[package]]
name = "walkdir"
version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
dependencies = [
"same-file",
"winapi",
"winapi-util",
]
[[package]]
name = "wasm-bindgen"
version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f"
[[package]]
name = "web-sys"
version = "0.3.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "webpki"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd"
dependencies = [
"ring",
"untrusted",
]
[[package]]
name = "webpki-roots"
version = "0.22.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "368bfe657969fb01238bb756d351dcade285e0f6fcbd36dcb23359a5169975be"
dependencies = [
"webpki",
]
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

View file

@ -0,0 +1,107 @@
# 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 = "2018"
rust-version = "1.57"
name = "rustls-fork-shadow-tls"
version = "0.20.8"
build = "build.rs"
autobenches = false
description = "Rustls is a modern TLS library written in Rust."
homepage = "https://github.com/rustls/rustls"
readme = "README.md"
categories = [
"network-programming",
"cryptography",
]
license = "Apache-2.0/ISC/MIT"
repository = "https://github.com/rustls/rustls"
resolver = "2"
[package.metadata.docs.rs]
all-features = true
rustdoc-args = [
"--cfg",
"docsrs",
]
[[example]]
name = "bogo_shim"
path = "examples/internal/bogo_shim.rs"
required-features = [
"dangerous_configuration",
"quic",
]
[[example]]
name = "trytls_shim"
path = "examples/internal/trytls_shim.rs"
[[example]]
name = "bench"
path = "examples/internal/bench.rs"
[[bench]]
name = "benchmarks"
path = "benches/benchmarks.rs"
harness = false
[dependencies.log]
version = "0.4.4"
optional = true
[dependencies.ring]
version = "0.16.20"
[dependencies.sct]
version = "0.7.0"
[dependencies.webpki]
version = "0.22.0"
features = [
"alloc",
"std",
]
[dev-dependencies.base64]
version = "0.13.0"
[dev-dependencies.criterion]
version = "0.3.0"
[dev-dependencies.env_logger]
version = "0.9.0"
[dev-dependencies.log]
version = "0.4.4"
[dev-dependencies.rustls-pemfile]
version = "1.0.0"
[dev-dependencies.webpki-roots]
version = "0.22.0"
[build-dependencies.rustversion]
version = "1.0.6"
optional = true
[features]
dangerous_configuration = []
default = [
"logging",
"tls12",
]
logging = ["log"]
quic = []
read_buf = ["rustversion"]
secret_extraction = []
tls12 = []

62
third_party/rustls-fork-shadow-tls/Cargo.toml.orig generated vendored Normal file
View file

@ -0,0 +1,62 @@
[package]
name = "rustls-fork-shadow-tls"
version = "0.20.8"
edition = "2018"
rust-version = "1.57"
license = "Apache-2.0/ISC/MIT"
readme = "../README.md"
description = "Rustls is a modern TLS library written in Rust."
homepage = "https://github.com/rustls/rustls"
repository = "https://github.com/rustls/rustls"
categories = ["network-programming", "cryptography"]
autobenches = false
build = "build.rs"
resolver = "2"
[build-dependencies]
rustversion = { version = "1.0.6", optional = true }
[dependencies]
log = { version = "0.4.4", optional = true }
ring = "0.16.20"
sct = "0.7.0"
webpki = { version = "0.22.0", features = ["alloc", "std"] }
[features]
default = ["logging", "tls12"]
logging = ["log"]
dangerous_configuration = []
secret_extraction = []
quic = []
tls12 = []
read_buf = ["rustversion"]
[dev-dependencies]
env_logger = "0.9.0"
log = "0.4.4"
webpki-roots = "0.22.0"
criterion = "0.3.0"
rustls-pemfile = "1.0.0"
base64 = "0.13.0"
[[example]]
name = "bogo_shim"
path = "examples/internal/bogo_shim.rs"
required-features = ["dangerous_configuration", "quic"]
[[example]]
name = "trytls_shim"
path = "examples/internal/trytls_shim.rs"
[[example]]
name = "bench"
path = "examples/internal/bench.rs"
[[bench]]
name = "benchmarks"
path = "benches/benchmarks.rs"
harness = false
[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]

View 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.

View file

@ -0,0 +1,15 @@
ISC License (ISC)
Copyright (c) 2016, Joseph Birr-Pixton <jpixton@gmail.com>
Permission to use, copy, modify, and/or distribute this software for
any purpose with or without fee is hereby granted, provided that the
above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.

View file

@ -0,0 +1,25 @@
Copyright (c) 2016 Joseph Birr-Pixton <jpixton@gmail.com>
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.

View file

@ -0,0 +1,298 @@
<p align="center">
<img width="460" height="300" src="https://raw.githubusercontent.com/rustls/rustls/main/admin/rustls-logo-web.png">
</p>
<p align="center">
Rustls is a modern TLS library written in Rust. It uses <a href = "https://github.com/briansmith/ring"><em>ring</em></a> for cryptography and <a href = "https://github.com/briansmith/webpki">webpki</a> for certificate
verification.
</p>
# Status
Rustls is ready for use. There are no major breaking interface changes
envisioned after the set included in the 0.20 release.
If you'd like to help out, please see [CONTRIBUTING.md](CONTRIBUTING.md).
[![Build Status](https://github.com/rustls/rustls/actions/workflows/build.yml/badge.svg?branch=main)](https://github.com/rustls/rustls/actions/workflows/build.yml?query=branch%3Amain)
[![Coverage Status (codecov.io)](https://codecov.io/gh/rustls/rustls/branch/main/graph/badge.svg)](https://codecov.io/gh/rustls/rustls/)
[![Documentation](https://docs.rs/rustls/badge.svg)](https://docs.rs/rustls/)
[![Chat](https://img.shields.io/discord/976380008299917365?logo=discord)](https://discord.gg/MCSB76RU96)
## Release history
* Next release
- Planned: removal of unused signature verification schemes at link-time.
* 0.20.8 (2023-01-12)
- Yield an error from `ConnectionCommon::read_tls()` if buffers are full.
Both a full deframer buffer and a full incoming plaintext buffer will
now cause an error to be returned. Callers should call `process_new_packets()`
and read out the plaintext data from `reader()` after each successful call to `read_tls()`.
- The minimum supported Rust version is now 1.57.0 due to some dependencies
requiring it.
* 0.20.7 (2022-10-18)
- Expose secret extraction API under the `secret_extraction` cargo feature.
This is designed to enable switching from rustls to kTLS (kernel TLS
offload) after a successful TLS 1.2/1.3 handshake, for example.
- Move filtering of signature schemes after config selection, avoiding the need
for linking in encryption/decryption code for all cipher suites at the cost of
exposing more signature schemes in the `ClientHello` emitted by the `Acceptor`.
- Expose AlertDescription, ContentType, and HandshakeType,
SignatureAlgorithm, and NamedGroup as part of the stable API. Previously they
were part of the unstable internals API, but were referenced by parts of the
stable API.
- We now have a [Discord channel](https://discord.gg/MCSB76RU96) for community
discussions.
- The minimum supported Rust version is now 1.56.0 due to several dependencies
requiring it.
* 0.20.6 (2022-05-18)
- 0.20.5 included a change to track more context for the `Error::CorruptMessage`
which made API-incompatible changes to the `Error` type. We yanked 0.20.5
and have reverted that change as part of 0.20.6.
* 0.20.5 (2022-05-14)
- Correct compatbility with servers which return no TLS extensions and take
advantage of a special case encoding.
- Remove spurious warn-level logging introduced in 0.20.3.
- Expose cipher suites in `ClientHello` type.
- Allow verification of IP addresses with `dangerous_config` enabled.
- Retry I/O operations in `ConnectionCommon::complete_io()` when interrupted.
- Fix server::ResolvesServerCertUsingSni case sensitivity.
* 0.20.4 (2022-02-19)
- Correct regression in QUIC 0-RTT support.
* 0.20.3 (2022-02-13)
- Support loading ECDSA keys in SEC1 format.
- Support receipt of 0-RTT "early data" in TLS1.3 servers. It is not enabled
by default; opt in by setting `ServerConfig::max_early_data_size` to a non-zero
value.
- Support sending of data with the first server flight. This is also not
enabled by default either: opt in by setting `ServerConfig::send_half_rtt_data`.
- Support `read_buf` interface when compiled with nightly. This means
data can be safely read out of a rustls connection into a buffer without
the buffer requiring initialisation first. Set the `read_buf` feature to
use this.
- Improve efficiency when writing vectors of TLS types.
- Reduce copying and improve efficiency in TLS1.2 handshake.
* 0.20.2 (2021-11-21)
- Fix `CipherSuite::as_str()` value (as introduced in 0.20.1).
* 0.20.1 (2021-11-14)
- Allow cipher suite enum items to be stringified.
- Improve documentation of configuration builder types.
- Ensure unused cipher suites can be removed at link-time.
- Ensure single-use error types implement `std::error::Error`, and are public.
See [RELEASE_NOTES.md](RELEASE_NOTES.md) for further change history.
# Documentation
Lives here: https://docs.rs/rustls/
# Approach
Rustls is a TLS library that aims to provide a good level of cryptographic security,
requires no configuration to achieve that security, and provides no unsafe features or
obsolete cryptography.
## Current features
* TLS1.2 and TLS1.3.
* ECDSA, Ed25519 or RSA server authentication by clients.
* ECDSA, Ed25519 or RSA server authentication by servers.
* Forward secrecy using ECDHE; with curve25519, nistp256 or nistp384 curves.
* AES128-GCM and AES256-GCM bulk encryption, with safe nonces.
* ChaCha20-Poly1305 bulk encryption ([RFC7905](https://tools.ietf.org/html/rfc7905)).
* ALPN support.
* SNI support.
* Tunable fragment size to make TLS messages match size of underlying transport.
* Optional use of vectored IO to minimise system calls.
* TLS1.2 session resumption.
* TLS1.2 resumption via tickets ([RFC5077](https://tools.ietf.org/html/rfc5077)).
* TLS1.3 resumption via tickets or session storage.
* TLS1.3 0-RTT data for clients.
* TLS1.3 0-RTT data for servers.
* Client authentication by clients.
* Client authentication by servers.
* Extended master secret support ([RFC7627](https://tools.ietf.org/html/rfc7627)).
* Exporters ([RFC5705](https://tools.ietf.org/html/rfc5705)).
* OCSP stapling by servers.
* SCT stapling by servers.
* SCT verification by clients.
## Possible future features
* PSK support.
* OCSP verification by clients.
* Certificate pinning.
## Non-features
For reasons [explained in the manual](https://docs.rs/rustls/latest/rustls/manual/_02_tls_vulnerabilities/index.html),
rustls does not and will not support:
* SSL1, SSL2, SSL3, TLS1 or TLS1.1.
* RC4.
* DES or triple DES.
* EXPORT ciphersuites.
* MAC-then-encrypt ciphersuites.
* Ciphersuites without forward secrecy.
* Renegotiation.
* Kerberos.
* Compression.
* Discrete-log Diffie-Hellman.
* Automatic protocol version downgrade.
There are plenty of other libraries that provide these features should you
need them.
### Platform support
Rustls uses [`ring`](https://crates.io/crates/ring) for implementing the
cryptography in TLS. As a result, rustls only runs on platforms
[supported by `ring`](https://github.com/briansmith/ring#online-automated-testing).
At the time of writing this means x86, x86-64, armv7, and aarch64.
Rustls requires Rust 1.56 or later.
# Example code
There are two example programs which use
[mio](https://github.com/carllerche/mio) to do asynchronous IO.
## Client example program
The client example program is named `tlsclient-mio`. The interface looks like:
```tlsclient-mio
Connects to the TLS server at hostname:PORT. The default PORT
is 443. By default, this reads a request from stdin (to EOF)
before making the connection. --http replaces this with a
basic HTTP GET request for /.
If --cafile is not supplied, a built-in set of CA certificates
are used from the webpki-roots crate.
Usage:
tlsclient-mio [options] [--suite SUITE ...] [--proto PROTO ...] [--protover PROTOVER ...] <hostname>
tlsclient-mio (--version | -v)
tlsclient-mio (--help | -h)
Options:
-p, --port PORT Connect to PORT [default: 443].
--http Send a basic HTTP GET request for /.
--cafile CAFILE Read root certificates from CAFILE.
--auth-key KEY Read client authentication key from KEY.
--auth-certs CERTS Read client authentication certificates from CERTS.
CERTS must match up with KEY.
--protover VERSION Disable default TLS version list, and use
VERSION instead. May be used multiple times.
--suite SUITE Disable default cipher suite list, and use
SUITE instead. May be used multiple times.
--proto PROTOCOL Send ALPN extension containing PROTOCOL.
May be used multiple times to offer several protocols.
--cache CACHE Save session cache to file CACHE.
--no-tickets Disable session ticket support.
--no-sni Disable server name indication support.
--insecure Disable certificate verification.
--verbose Emit log output.
--max-frag-size M Limit outgoing messages to M bytes.
--version, -v Show tool version.
--help, -h Show this screen.
```
Some sample runs:
```
$ cargo run --bin tlsclient-mio -- --http mozilla-modern.badssl.com
HTTP/1.1 200 OK
Server: nginx/1.6.2 (Ubuntu)
Date: Wed, 01 Jun 2016 18:44:00 GMT
Content-Type: text/html
Content-Length: 644
(...)
```
or
```
$ cargo run --bin tlsclient-mio -- --http expired.badssl.com
TLS error: WebPkiError(CertExpired, ValidateServerCert)
Connection closed
```
## Server example program
The server example program is named `tlsserver-mio`. The interface looks like:
```tlsserver-mio
Runs a TLS server on :PORT. The default PORT is 443.
`echo' mode means the server echoes received data on each connection.
`http' mode means the server blindly sends a HTTP response on each
connection.
`forward' means the server forwards plaintext to a connection made to
localhost:fport.
`--certs' names the full certificate chain, `--key' provides the
RSA private key.
Usage:
tlsserver-mio --certs CERTFILE --key KEYFILE [--suite SUITE ...] [--proto PROTO ...] [--protover PROTOVER ...] [options] echo
tlsserver-mio --certs CERTFILE --key KEYFILE [--suite SUITE ...] [--proto PROTO ...] [--protover PROTOVER ...] [options] http
tlsserver-mio --certs CERTFILE --key KEYFILE [--suite SUITE ...] [--proto PROTO ...] [--protover PROTOVER ...] [options] forward <fport>
tlsserver-mio (--version | -v)
tlsserver-mio (--help | -h)
Options:
-p, --port PORT Listen on PORT [default: 443].
--certs CERTFILE Read server certificates from CERTFILE.
This should contain PEM-format certificates
in the right order (the first certificate should
certify KEYFILE, the last should be a root CA).
--key KEYFILE Read private key from KEYFILE. This should be a RSA
private key or PKCS8-encoded private key, in PEM format.
--ocsp OCSPFILE Read DER-encoded OCSP response from OCSPFILE and staple
to certificate. Optional.
--auth CERTFILE Enable client authentication, and accept certificates
signed by those roots provided in CERTFILE.
--require-auth Send a fatal alert if the client does not complete client
authentication.
--resumption Support session resumption.
--tickets Support tickets.
--protover VERSION Disable default TLS version list, and use
VERSION instead. May be used multiple times.
--suite SUITE Disable default cipher suite list, and use
SUITE instead. May be used multiple times.
--proto PROTOCOL Negotiate PROTOCOL using ALPN.
May be used multiple times.
--verbose Emit log output.
--version, -v Show tool version.
--help, -h Show this screen.
```
Here's a sample run; we start a TLS echo server, then connect to it with
`openssl` and `tlsclient-mio`:
```
$ cargo run --bin tlsserver-mio -- --certs test-ca/rsa/end.fullchain --key test-ca/rsa/end.rsa -p 8443 echo &
$ echo hello world | openssl s_client -ign_eof -quiet -connect localhost:8443
depth=2 CN = ponytown RSA CA
verify error:num=19:self signed certificate in certificate chain
hello world
^C
$ echo hello world | cargo run --bin tlsclient-mio -- --cafile test-ca/rsa/ca.cert -p 8443 localhost
hello world
^C
```
# License
Rustls is distributed under the following three licenses:
- Apache License version 2.0.
- MIT license.
- ISC license.
These are included as LICENSE-APACHE, LICENSE-MIT and LICENSE-ISC
respectively. You may use this software under the terms of any
of these licenses, at your option.
# Code of conduct
This project adopts the [Rust Code of Conduct](https://www.rust-lang.org/policies/code-of-conduct).
Please email rustls-mod@googlegroups.com to report any instance of misconduct, or if you
have any comments or questions on the Code of Conduct.

View file

@ -0,0 +1,26 @@
use criterion::criterion_group;
use criterion::criterion_main;
/// Microbenchmarks go here. Larger benchmarks of (e.g..) protocol
/// performance go in examples/internal/bench.rs.
use criterion::Criterion;
#[path = "../tests/common/mod.rs"]
mod test_utils;
use test_utils::*;
use rustls::ServerConnection;
use std::io;
use std::sync::Arc;
fn bench_ewouldblock(c: &mut Criterion) {
let server_config = make_server_config(KeyType::Rsa);
let mut server = ServerConnection::new(Arc::new(server_config)).unwrap();
let mut read_ewouldblock = FailsReads::new(io::ErrorKind::WouldBlock);
c.bench_function("read_tls with EWOULDBLOCK", move |b| {
b.iter(|| server.read_tls(&mut read_ewouldblock))
});
}
criterion_group!(benches, bench_ewouldblock);
criterion_main!(benches);

View file

@ -0,0 +1,13 @@
/// This build script allows us to enable the `read_buf` language feature only
/// for Rust Nightly.
///
/// See the comment in lib.rs to understand why we need this.
#[cfg_attr(feature = "read_buf", rustversion::not(nightly))]
fn main() {}
#[cfg(feature = "read_buf")]
#[rustversion::nightly]
fn main() {
println!("cargo:rustc-cfg=read_buf");
}

View file

@ -0,0 +1,664 @@
// This program does assorted benchmarking of rustls.
//
// Note: we don't use any of the standard 'cargo bench', 'test::Bencher',
// etc. because it's unstable at the time of writing.
use std::convert::TryInto;
use std::env;
use std::fs;
use std::io::{self, Read, Write};
use std::ops::Deref;
use std::ops::DerefMut;
use std::sync::Arc;
use std::time::{Duration, Instant};
use rustls::client::{ClientSessionMemoryCache, NoClientSessionStorage};
use rustls::server::{
AllowAnyAuthenticatedClient, NoClientAuth, NoServerSessionStorage, ServerSessionMemoryCache,
};
use rustls::RootCertStore;
use rustls::Ticketer;
use rustls::{ClientConfig, ClientConnection};
use rustls::{ConnectionCommon, SideData};
use rustls::{ServerConfig, ServerConnection};
fn duration_nanos(d: Duration) -> f64 {
(d.as_secs() as f64) + f64::from(d.subsec_nanos()) / 1e9
}
fn _bench<Fsetup, Ftest, S>(count: usize, name: &'static str, f_setup: Fsetup, f_test: Ftest)
where
Fsetup: Fn() -> S,
Ftest: Fn(S),
{
let mut times = Vec::new();
for _ in 0..count {
let state = f_setup();
let start = Instant::now();
f_test(state);
times.push(duration_nanos(Instant::now().duration_since(start)));
}
println!("{}", name);
println!("{:?}", times);
}
fn time<F>(mut f: F) -> f64
where
F: FnMut(),
{
let start = Instant::now();
f();
let end = Instant::now();
duration_nanos(end.duration_since(start))
}
fn transfer<L, R, LS, RS>(left: &mut L, right: &mut R, expect_data: Option<usize>) -> f64
where
L: DerefMut + Deref<Target = ConnectionCommon<LS>>,
R: DerefMut + Deref<Target = ConnectionCommon<RS>>,
LS: SideData,
RS: SideData,
{
let mut tls_buf = [0u8; 262144];
let mut read_time = 0f64;
let mut data_left = expect_data;
let mut data_buf = [0u8; 8192];
loop {
let mut sz = 0;
while left.wants_write() {
let written = left
.write_tls(&mut tls_buf[sz..].as_mut())
.unwrap();
if written == 0 {
break;
}
sz += written;
}
if sz == 0 {
return read_time;
}
let mut offs = 0;
loop {
let start = Instant::now();
match right.read_tls(&mut tls_buf[offs..sz].as_ref()) {
Ok(read) => {
right.process_new_packets().unwrap();
offs += read;
}
Err(err) => {
panic!("error on transfer {}..{}: {}", offs, sz, err);
}
}
if let Some(left) = &mut data_left {
loop {
let sz = match right.reader().read(&mut data_buf) {
Ok(sz) => sz,
Err(err) if err.kind() == io::ErrorKind::WouldBlock => break,
Err(err) => panic!("failed to read data: {}", err),
};
*left -= sz;
if *left == 0 {
break;
}
}
}
let end = Instant::now();
read_time += duration_nanos(end.duration_since(start));
if sz == offs {
break;
}
}
}
}
#[derive(PartialEq, Clone, Copy)]
enum ClientAuth {
No,
Yes,
}
#[derive(PartialEq, Clone, Copy)]
enum Resumption {
No,
SessionID,
Tickets,
}
impl Resumption {
fn label(&self) -> &'static str {
match *self {
Resumption::No => "no-resume",
Resumption::SessionID => "sessionid",
Resumption::Tickets => "tickets",
}
}
}
// copied from tests/api.rs
#[derive(PartialEq, Clone, Copy, Debug)]
enum KeyType {
Rsa,
Ecdsa,
Ed25519,
}
struct BenchmarkParam {
key_type: KeyType,
ciphersuite: rustls::SupportedCipherSuite,
version: &'static rustls::SupportedProtocolVersion,
}
impl BenchmarkParam {
const fn new(
key_type: KeyType,
ciphersuite: rustls::SupportedCipherSuite,
version: &'static rustls::SupportedProtocolVersion,
) -> BenchmarkParam {
BenchmarkParam {
key_type,
ciphersuite,
version,
}
}
}
static ALL_BENCHMARKS: &[BenchmarkParam] = &[
#[cfg(feature = "tls12")]
BenchmarkParam::new(
KeyType::Rsa,
rustls::cipher_suite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
&rustls::version::TLS12,
),
#[cfg(feature = "tls12")]
BenchmarkParam::new(
KeyType::Ecdsa,
rustls::cipher_suite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
&rustls::version::TLS12,
),
#[cfg(feature = "tls12")]
BenchmarkParam::new(
KeyType::Rsa,
rustls::cipher_suite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
&rustls::version::TLS12,
),
#[cfg(feature = "tls12")]
BenchmarkParam::new(
KeyType::Rsa,
rustls::cipher_suite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
&rustls::version::TLS12,
),
#[cfg(feature = "tls12")]
BenchmarkParam::new(
KeyType::Rsa,
rustls::cipher_suite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
&rustls::version::TLS12,
),
#[cfg(feature = "tls12")]
BenchmarkParam::new(
KeyType::Ecdsa,
rustls::cipher_suite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
&rustls::version::TLS12,
),
#[cfg(feature = "tls12")]
BenchmarkParam::new(
KeyType::Ecdsa,
rustls::cipher_suite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
&rustls::version::TLS12,
),
BenchmarkParam::new(
KeyType::Rsa,
rustls::cipher_suite::TLS13_CHACHA20_POLY1305_SHA256,
&rustls::version::TLS13,
),
BenchmarkParam::new(
KeyType::Rsa,
rustls::cipher_suite::TLS13_AES_256_GCM_SHA384,
&rustls::version::TLS13,
),
BenchmarkParam::new(
KeyType::Rsa,
rustls::cipher_suite::TLS13_AES_128_GCM_SHA256,
&rustls::version::TLS13,
),
BenchmarkParam::new(
KeyType::Ecdsa,
rustls::cipher_suite::TLS13_AES_128_GCM_SHA256,
&rustls::version::TLS13,
),
BenchmarkParam::new(
KeyType::Ed25519,
rustls::cipher_suite::TLS13_AES_128_GCM_SHA256,
&rustls::version::TLS13,
),
];
impl KeyType {
fn path_for(&self, part: &str) -> String {
match self {
KeyType::Rsa => format!("test-ca/rsa/{}", part),
KeyType::Ecdsa => format!("test-ca/ecdsa/{}", part),
KeyType::Ed25519 => format!("test-ca/eddsa/{}", part),
}
}
fn get_chain(&self) -> Vec<rustls::Certificate> {
rustls_pemfile::certs(&mut io::BufReader::new(
fs::File::open(self.path_for("end.fullchain")).unwrap(),
))
.unwrap()
.iter()
.map(|v| rustls::Certificate(v.clone()))
.collect()
}
fn get_key(&self) -> rustls::PrivateKey {
rustls::PrivateKey(
rustls_pemfile::pkcs8_private_keys(&mut io::BufReader::new(
fs::File::open(self.path_for("end.key")).unwrap(),
))
.unwrap()[0]
.clone(),
)
}
fn get_client_chain(&self) -> Vec<rustls::Certificate> {
rustls_pemfile::certs(&mut io::BufReader::new(
fs::File::open(self.path_for("client.fullchain")).unwrap(),
))
.unwrap()
.iter()
.map(|v| rustls::Certificate(v.clone()))
.collect()
}
fn get_client_key(&self) -> rustls::PrivateKey {
rustls::PrivateKey(
rustls_pemfile::pkcs8_private_keys(&mut io::BufReader::new(
fs::File::open(self.path_for("client.key")).unwrap(),
))
.unwrap()[0]
.clone(),
)
}
}
fn make_server_config(
params: &BenchmarkParam,
client_auth: ClientAuth,
resume: Resumption,
max_fragment_size: Option<usize>,
) -> ServerConfig {
let client_auth = match client_auth {
ClientAuth::Yes => {
let roots = params.key_type.get_chain();
let mut client_auth_roots = RootCertStore::empty();
for root in roots {
client_auth_roots.add(&root).unwrap();
}
AllowAnyAuthenticatedClient::new(client_auth_roots)
}
ClientAuth::No => NoClientAuth::new(),
};
let mut cfg = ServerConfig::builder()
.with_safe_default_cipher_suites()
.with_safe_default_kx_groups()
.with_protocol_versions(&[params.version])
.unwrap()
.with_client_cert_verifier(client_auth)
.with_single_cert(params.key_type.get_chain(), params.key_type.get_key())
.expect("bad certs/private key?");
if resume == Resumption::SessionID {
cfg.session_storage = ServerSessionMemoryCache::new(128);
} else if resume == Resumption::Tickets {
cfg.ticketer = Ticketer::new().unwrap();
} else {
cfg.session_storage = Arc::new(NoServerSessionStorage {});
}
cfg.max_fragment_size = max_fragment_size;
cfg
}
fn make_client_config(
params: &BenchmarkParam,
clientauth: ClientAuth,
resume: Resumption,
) -> ClientConfig {
let mut root_store = RootCertStore::empty();
let mut rootbuf =
io::BufReader::new(fs::File::open(params.key_type.path_for("ca.cert")).unwrap());
root_store.add_parsable_certificates(&rustls_pemfile::certs(&mut rootbuf).unwrap());
let cfg = ClientConfig::builder()
.with_cipher_suites(&[params.ciphersuite])
.with_safe_default_kx_groups()
.with_protocol_versions(&[params.version])
.unwrap()
.with_root_certificates(root_store);
let mut cfg = if clientauth == ClientAuth::Yes {
cfg.with_single_cert(
params.key_type.get_client_chain(),
params.key_type.get_client_key(),
)
.unwrap()
} else {
cfg.with_no_client_auth()
};
if resume != Resumption::No {
cfg.session_storage = ClientSessionMemoryCache::new(128);
} else {
cfg.session_storage = Arc::new(NoClientSessionStorage {});
}
cfg
}
fn apply_work_multiplier(work: u64) -> u64 {
let mul = match env::var("BENCH_MULTIPLIER") {
Ok(val) => val
.parse::<f64>()
.expect("invalid BENCH_MULTIPLIER value"),
Err(_) => 1.,
};
((work as f64) * mul).round() as u64
}
fn bench_handshake(params: &BenchmarkParam, clientauth: ClientAuth, resume: Resumption) {
let client_config = Arc::new(make_client_config(params, clientauth, resume));
let server_config = Arc::new(make_server_config(params, clientauth, resume, None));
assert!(params.ciphersuite.version() == params.version);
let rounds = apply_work_multiplier(if resume == Resumption::No { 512 } else { 4096 });
let mut client_time = 0f64;
let mut server_time = 0f64;
for _ in 0..rounds {
let server_name = "localhost".try_into().unwrap();
let mut client = ClientConnection::new(Arc::clone(&client_config), server_name).unwrap();
let mut server = ServerConnection::new(Arc::clone(&server_config)).unwrap();
server_time += time(|| {
transfer(&mut client, &mut server, None);
});
client_time += time(|| {
transfer(&mut server, &mut client, None);
});
server_time += time(|| {
transfer(&mut client, &mut server, None);
});
client_time += time(|| {
transfer(&mut server, &mut client, None);
});
}
println!(
"handshakes\t{:?}\t{:?}\t{:?}\tclient\t{}\t{}\t{:.2}\thandshake/s",
params.version,
params.key_type,
params.ciphersuite.suite(),
if clientauth == ClientAuth::Yes {
"mutual"
} else {
"server-auth"
},
resume.label(),
(rounds as f64) / client_time
);
println!(
"handshakes\t{:?}\t{:?}\t{:?}\tserver\t{}\t{}\t{:.2}\thandshake/s",
params.version,
params.key_type,
params.ciphersuite.suite(),
if clientauth == ClientAuth::Yes {
"mutual"
} else {
"server-auth"
},
resume.label(),
(rounds as f64) / server_time
);
}
fn do_handshake_step(client: &mut ClientConnection, server: &mut ServerConnection) -> bool {
if server.is_handshaking() || client.is_handshaking() {
transfer(client, server, None);
transfer(server, client, None);
true
} else {
false
}
}
fn do_handshake(client: &mut ClientConnection, server: &mut ServerConnection) {
while do_handshake_step(client, server) {}
}
fn bench_bulk(params: &BenchmarkParam, plaintext_size: u64, max_fragment_size: Option<usize>) {
let client_config = Arc::new(make_client_config(params, ClientAuth::No, Resumption::No));
let server_config = Arc::new(make_server_config(
params,
ClientAuth::No,
Resumption::No,
max_fragment_size,
));
let server_name = "localhost".try_into().unwrap();
let mut client = ClientConnection::new(client_config, server_name).unwrap();
client.set_buffer_limit(None);
let mut server = ServerConnection::new(Arc::clone(&server_config)).unwrap();
server.set_buffer_limit(None);
do_handshake(&mut client, &mut server);
let mut buf = Vec::new();
buf.resize(plaintext_size as usize, 0u8);
let total_data = apply_work_multiplier(if plaintext_size < 8192 {
64 * 1024 * 1024
} else {
1024 * 1024 * 1024
});
let rounds = total_data / plaintext_size;
let mut time_send = 0f64;
let mut time_recv = 0f64;
for _ in 0..rounds {
time_send += time(|| {
server.writer().write_all(&buf).unwrap();
});
time_recv += transfer(&mut server, &mut client, Some(buf.len()));
}
let mfs_str = format!(
"max_fragment_size:{}",
max_fragment_size
.map(|v| v.to_string())
.unwrap_or_else(|| "default".to_string())
);
let total_mbs = ((plaintext_size * rounds) as f64) / (1024. * 1024.);
println!(
"bulk\t{:?}\t{:?}\t{}\tsend\t{:.2}\tMB/s",
params.version,
params.ciphersuite.suite(),
mfs_str,
total_mbs / time_send
);
println!(
"bulk\t{:?}\t{:?}\t{}\trecv\t{:.2}\tMB/s",
params.version,
params.ciphersuite.suite(),
mfs_str,
total_mbs / time_recv
);
}
fn bench_memory(params: &BenchmarkParam, conn_count: u64) {
let client_config = Arc::new(make_client_config(params, ClientAuth::No, Resumption::No));
let server_config = Arc::new(make_server_config(
params,
ClientAuth::No,
Resumption::No,
None,
));
// The target here is to end up with conn_count post-handshake
// server and client sessions.
let conn_count = (conn_count / 2) as usize;
let mut servers = Vec::with_capacity(conn_count);
let mut clients = Vec::with_capacity(conn_count);
for _i in 0..conn_count {
servers.push(ServerConnection::new(Arc::clone(&server_config)).unwrap());
let server_name = "localhost".try_into().unwrap();
clients.push(ClientConnection::new(Arc::clone(&client_config), server_name).unwrap());
}
for _step in 0..5 {
for (client, server) in clients
.iter_mut()
.zip(servers.iter_mut())
{
do_handshake_step(client, server);
}
}
for client in clients.iter_mut() {
client
.writer()
.write_all(&[0u8; 1024])
.unwrap();
}
for (client, server) in clients
.iter_mut()
.zip(servers.iter_mut())
{
transfer(client, server, Some(1024));
}
}
fn lookup_matching_benches(name: &str) -> Vec<&BenchmarkParam> {
let r: Vec<&BenchmarkParam> = ALL_BENCHMARKS
.iter()
.filter(|params| {
format!("{:?}", params.ciphersuite.suite()).to_lowercase() == name.to_lowercase()
})
.collect();
if r.is_empty() {
panic!("unknown suite {:?}", name);
}
r
}
fn selected_tests(mut args: env::Args) {
let mode = args
.next()
.expect("first argument must be mode");
match mode.as_ref() {
"bulk" => match args.next() {
Some(suite) => {
let len = args
.next()
.map(|arg| {
arg.parse::<u64>()
.expect("3rd arg must be plaintext size integer")
})
.unwrap_or(1048576);
let mfs = args.next().map(|arg| {
arg.parse::<usize>()
.expect("4th arg must be max_fragment_size integer")
});
for param in lookup_matching_benches(&suite).iter() {
bench_bulk(param, len, mfs);
}
}
None => {
panic!("bulk needs ciphersuite argument");
}
},
"handshake" | "handshake-resume" | "handshake-ticket" => match args.next() {
Some(suite) => {
let resume = if mode == "handshake" {
Resumption::No
} else if mode == "handshake-resume" {
Resumption::SessionID
} else {
Resumption::Tickets
};
for param in lookup_matching_benches(&suite).iter() {
bench_handshake(param, ClientAuth::No, resume);
}
}
None => {
panic!("handshake* needs ciphersuite argument");
}
},
"memory" => match args.next() {
Some(suite) => {
let count = args
.next()
.map(|arg| {
arg.parse::<u64>()
.expect("3rd arg must be connection count integer")
})
.unwrap_or(1000000);
for param in lookup_matching_benches(&suite).iter() {
bench_memory(param, count);
}
}
None => {
panic!("memory needs ciphersuite argument");
}
},
_ => {
panic!("unsupported mode {:?}", mode);
}
}
}
fn all_tests() {
for test in ALL_BENCHMARKS.iter() {
bench_bulk(test, 1024 * 1024, None);
bench_bulk(test, 1024 * 1024, Some(10000));
bench_handshake(test, ClientAuth::No, Resumption::No);
bench_handshake(test, ClientAuth::Yes, Resumption::No);
bench_handshake(test, ClientAuth::No, Resumption::SessionID);
bench_handshake(test, ClientAuth::Yes, Resumption::SessionID);
bench_handshake(test, ClientAuth::No, Resumption::Tickets);
bench_handshake(test, ClientAuth::Yes, Resumption::Tickets);
}
}
fn main() {
let mut args = env::args();
if args.len() > 1 {
args.next();
selected_tests(args);
} else {
all_tests();
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,120 @@
// A Rustls stub for TryTLS
//
// Author: Joachim Viide
// See: https://github.com/HowNetWorks/trytls-rustls-stub
//
use rustls::{ClientConfig, ClientConnection, Error, OwnedTrustAnchor, RootCertStore};
use std::convert::TryInto;
use std::env;
use std::error::Error as StdError;
use std::fs::File;
use std::io::{BufReader, Read, Write};
use std::net::TcpStream;
use std::process;
use std::sync::Arc;
enum Verdict {
Accept,
Reject(Error),
}
fn parse_args(args: &[String]) -> Result<(String, u16, ClientConfig), Box<dyn StdError>> {
let mut root_store = RootCertStore::empty();
match args.len() {
3 => {
root_store.add_server_trust_anchors(
webpki_roots::TLS_SERVER_ROOTS
.0
.iter()
.map(|ta| {
OwnedTrustAnchor::from_subject_spki_name_constraints(
ta.subject,
ta.spki,
ta.name_constraints,
)
}),
);
}
4 => {
let f = File::open(&args[3])?;
root_store
.add_parsable_certificates(&rustls_pemfile::certs(&mut BufReader::new(f)).unwrap());
}
_ => {
return Err(From::from("Incorrect number of arguments"));
}
};
let config = rustls::ClientConfig::builder()
.with_safe_defaults()
.with_root_certificates(root_store)
.with_no_client_auth();
let port = args[2].parse()?;
Ok((args[1].clone(), port, config))
}
fn communicate(
host: String,
port: u16,
config: ClientConfig,
) -> Result<Verdict, Box<dyn StdError>> {
let server_name = host.as_str().try_into().unwrap();
let rc_config = Arc::new(config);
let mut client = ClientConnection::new(rc_config, server_name).unwrap();
let mut stream = TcpStream::connect((&*host, port))?;
client
.writer()
.write_all(b"GET / HTTP/1.0\r\nConnection: close\r\nContent-Length: 0\r\n\r\n")?;
loop {
while client.wants_write() {
client.write_tls(&mut stream)?;
}
if client.wants_read() {
if client.read_tls(&mut stream)? == 0 {
return Err(From::from("Connection closed"));
}
if let Err(err) = client.process_new_packets() {
return match err {
Error::InvalidCertificateData(_)
| Error::InvalidCertificateSignature
| Error::InvalidCertificateSignatureType
| Error::InvalidCertificateEncoding
| Error::AlertReceived(_) => Ok(Verdict::Reject(err)),
_ => Err(From::from(format!("{:?}", err))),
};
}
if client.reader().read(&mut [0])? > 0 {
return Ok(Verdict::Accept);
}
}
}
}
fn main() {
let args: Vec<String> = env::args().collect();
let (host, port, config) = parse_args(&args).unwrap_or_else(|err| {
println!("Argument error: {}", err);
process::exit(2);
});
match communicate(host, port, config) {
Ok(Verdict::Accept) => {
println!("ACCEPT");
process::exit(0);
}
Ok(Verdict::Reject(reason)) => {
println!("{:?}", reason);
println!("REJECT");
process::exit(0);
}
Err(err) => {
println!("{}", err);
process::exit(1);
}
}
}

View file

@ -0,0 +1,154 @@
use crate::key;
#[cfg(feature = "logging")]
use crate::log::{debug, trace};
use crate::msgs::handshake::{DistinguishedName, DistinguishedNames};
use crate::x509;
/// A trust anchor, commonly known as a "Root Certificate."
#[derive(Debug, Clone)]
pub struct OwnedTrustAnchor {
subject: Vec<u8>,
spki: Vec<u8>,
name_constraints: Option<Vec<u8>>,
}
impl OwnedTrustAnchor {
/// Get a `webpki::TrustAnchor` by borrowing the owned elements.
pub(crate) fn to_trust_anchor(&self) -> webpki::TrustAnchor {
webpki::TrustAnchor {
subject: &self.subject,
spki: &self.spki,
name_constraints: self.name_constraints.as_deref(),
}
}
/// Constructs an `OwnedTrustAnchor` from its components.
///
/// All inputs are DER-encoded.
///
/// `subject` is the [Subject] field of the trust anchor.
///
/// `spki` is the [SubjectPublicKeyInfo] field of the trust anchor.
///
/// `name_constraints` is the [Name Constraints] to
/// apply for this trust anchor, if any.
///
/// [Subject]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.6
/// [SubjectPublicKeyInfo]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.7
/// [Name Constraints]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.10
pub fn from_subject_spki_name_constraints(
subject: impl Into<Vec<u8>>,
spki: impl Into<Vec<u8>>,
name_constraints: Option<impl Into<Vec<u8>>>,
) -> Self {
Self {
subject: subject.into(),
spki: spki.into(),
name_constraints: name_constraints.map(|x| x.into()),
}
}
/// Return the subject field.
///
/// This can be decoded using [x509-parser's FromDer trait](https://docs.rs/x509-parser/latest/x509_parser/traits/trait.FromDer.html).
///
/// ```ignore
/// use x509_parser::traits::FromDer;
/// println!("{}", x509_parser::x509::X509Name::from_der(anchor.subject())?.1);
/// ```
pub fn subject(&self) -> &[u8] {
&self.subject
}
}
/// A container for root certificates able to provide a root-of-trust
/// for connection authentication.
#[derive(Debug, Clone)]
pub struct RootCertStore {
/// The list of roots.
pub roots: Vec<OwnedTrustAnchor>,
}
impl RootCertStore {
/// Make a new, empty `RootCertStore`.
pub fn empty() -> Self {
Self { roots: Vec::new() }
}
/// Return true if there are no certificates.
pub fn is_empty(&self) -> bool {
self.len() == 0
}
/// Say how many certificates are in the container.
pub fn len(&self) -> usize {
self.roots.len()
}
/// Return the Subject Names for certificates in the container.
#[deprecated(since = "0.20.7", note = "Use OwnedTrustAnchor::subject() instead")]
pub fn subjects(&self) -> DistinguishedNames {
let mut r = DistinguishedNames::new();
for ota in &self.roots {
let mut name = Vec::new();
name.extend_from_slice(&ota.subject);
x509::wrap_in_sequence(&mut name);
r.push(DistinguishedName::new(name));
}
r
}
/// Add a single DER-encoded certificate to the store.
pub fn add(&mut self, der: &key::Certificate) -> Result<(), webpki::Error> {
let ta = webpki::TrustAnchor::try_from_cert_der(&der.0)?;
let ota = OwnedTrustAnchor::from_subject_spki_name_constraints(
ta.subject,
ta.spki,
ta.name_constraints,
);
self.roots.push(ota);
Ok(())
}
/// Adds all the given TrustAnchors `anchors`. This does not
/// fail.
pub fn add_server_trust_anchors(
&mut self,
trust_anchors: impl Iterator<Item = OwnedTrustAnchor>,
) {
self.roots.extend(trust_anchors)
}
/// Parse the given DER-encoded certificates and add all that can be parsed
/// in a best-effort fashion.
///
/// This is because large collections of root certificates often
/// include ancient or syntactically invalid certificates.
///
/// Returns the number of certificates added, and the number that were ignored.
pub fn add_parsable_certificates(&mut self, der_certs: &[Vec<u8>]) -> (usize, usize) {
let mut valid_count = 0;
let mut invalid_count = 0;
for der_cert in der_certs {
#[cfg_attr(not(feature = "logging"), allow(unused_variables))]
match self.add(&key::Certificate(der_cert.clone())) {
Ok(_) => valid_count += 1,
Err(err) => {
trace!("invalid cert der {:?}", der_cert);
debug!("certificate parsing failed: {:?}", err);
invalid_count += 1
}
}
}
debug!(
"add_parsable_certificates processed {} valid and {} invalid certs",
valid_count, invalid_count
);
(valid_count, invalid_count)
}
}

View file

@ -0,0 +1,77 @@
use std::fmt;
/// Alternative implementation of `fmt::Debug` for byte slice.
///
/// Standard `Debug` implementation for `[u8]` is comma separated
/// list of numbers. Since large amount of byte strings are in fact
/// ASCII strings or contain a lot of ASCII strings (e. g. HTTP),
/// it is convenient to print strings as ASCII when possible.
///
/// This struct wraps `&[u8]` just to override `fmt::Debug`.
///
/// `BsDebug` is not a part of public API of bytes crate.
pub(crate) struct BsDebug<'a>(pub(crate) &'a [u8]);
impl<'a> fmt::Debug for BsDebug<'a> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(fmt, "b\"")?;
for &c in self.0 {
// https://doc.rust-lang.org/reference.html#byte-escapes
if c == b'\n' {
write!(fmt, "\\n")?;
} else if c == b'\r' {
write!(fmt, "\\r")?;
} else if c == b'\t' {
write!(fmt, "\\t")?;
} else if c == b'\\' || c == b'"' {
write!(fmt, "\\{}", c as char)?;
} else if c == b'\0' {
write!(fmt, "\\0")?;
// ASCII printable
} else if (0x20..0x7f).contains(&c) {
write!(fmt, "{}", c as char)?;
} else {
write!(fmt, "\\x{:02x}", c)?;
}
}
write!(fmt, "\"")?;
Ok(())
}
}
#[cfg(test)]
mod test {
use super::BsDebug;
#[test]
fn debug() {
let vec: Vec<_> = (0..0x100).map(|b| b as u8).collect();
let expected = "b\"\
\\0\\x01\\x02\\x03\\x04\\x05\\x06\\x07\
\\x08\\t\\n\\x0b\\x0c\\r\\x0e\\x0f\
\\x10\\x11\\x12\\x13\\x14\\x15\\x16\\x17\
\\x18\\x19\\x1a\\x1b\\x1c\\x1d\\x1e\\x1f\
\x20!\\\"#$%&'()*+,-./0123456789:;<=>?\
@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\\\]^_\
`abcdefghijklmnopqrstuvwxyz{|}~\\x7f\
\\x80\\x81\\x82\\x83\\x84\\x85\\x86\\x87\
\\x88\\x89\\x8a\\x8b\\x8c\\x8d\\x8e\\x8f\
\\x90\\x91\\x92\\x93\\x94\\x95\\x96\\x97\
\\x98\\x99\\x9a\\x9b\\x9c\\x9d\\x9e\\x9f\
\\xa0\\xa1\\xa2\\xa3\\xa4\\xa5\\xa6\\xa7\
\\xa8\\xa9\\xaa\\xab\\xac\\xad\\xae\\xaf\
\\xb0\\xb1\\xb2\\xb3\\xb4\\xb5\\xb6\\xb7\
\\xb8\\xb9\\xba\\xbb\\xbc\\xbd\\xbe\\xbf\
\\xc0\\xc1\\xc2\\xc3\\xc4\\xc5\\xc6\\xc7\
\\xc8\\xc9\\xca\\xcb\\xcc\\xcd\\xce\\xcf\
\\xd0\\xd1\\xd2\\xd3\\xd4\\xd5\\xd6\\xd7\
\\xd8\\xd9\\xda\\xdb\\xdc\\xdd\\xde\\xdf\
\\xe0\\xe1\\xe2\\xe3\\xe4\\xe5\\xe6\\xe7\
\\xe8\\xe9\\xea\\xeb\\xec\\xed\\xee\\xef\
\\xf0\\xf1\\xf2\\xf3\\xf4\\xf5\\xf6\\xf7\
\\xf8\\xf9\\xfa\\xfb\\xfc\\xfd\\xfe\\xff\"";
assert_eq!(expected, format!("{:?}", BsDebug(&vec)));
}
}

View file

@ -0,0 +1,268 @@
use crate::error::Error;
use crate::kx::{SupportedKxGroup, ALL_KX_GROUPS};
use crate::suites::{SupportedCipherSuite, DEFAULT_CIPHER_SUITES};
use crate::versions;
use std::fmt;
use std::marker::PhantomData;
/// Building a [`ServerConfig`] or [`ClientConfig`] in a linker-friendly and
/// complete way.
///
/// Linker-friendly: meaning unused cipher suites, protocol
/// versions, key exchange mechanisms, etc. can be discarded
/// by the linker as they'll be unreferenced.
///
/// Complete: the type system ensures all decisions required to run a
/// server or client have been made by the time the process finishes.
///
/// Example, to make a [`ServerConfig`]:
///
/// ```no_run
/// # use rustls::ServerConfig;
/// # let certs = vec![];
/// # let private_key = rustls::PrivateKey(vec![]);
/// ServerConfig::builder()
/// .with_safe_default_cipher_suites()
/// .with_safe_default_kx_groups()
/// .with_safe_default_protocol_versions()
/// .unwrap()
/// .with_no_client_auth()
/// .with_single_cert(certs, private_key)
/// .expect("bad certificate/key");
/// ```
///
/// This may be shortened to:
///
/// ```no_run
/// # use rustls::ServerConfig;
/// # let certs = vec![];
/// # let private_key = rustls::PrivateKey(vec![]);
/// ServerConfig::builder()
/// .with_safe_defaults()
/// .with_no_client_auth()
/// .with_single_cert(certs, private_key)
/// .expect("bad certificate/key");
/// ```
///
/// To make a [`ClientConfig`]:
///
/// ```no_run
/// # use rustls::ClientConfig;
/// # let root_certs = rustls::RootCertStore::empty();
/// # let certs = vec![];
/// # let private_key = rustls::PrivateKey(vec![]);
/// ClientConfig::builder()
/// .with_safe_default_cipher_suites()
/// .with_safe_default_kx_groups()
/// .with_safe_default_protocol_versions()
/// .unwrap()
/// .with_root_certificates(root_certs)
/// .with_single_cert(certs, private_key)
/// .expect("bad certificate/key");
/// ```
///
/// This may be shortened to:
///
/// ```
/// # use rustls::ClientConfig;
/// # let root_certs = rustls::RootCertStore::empty();
/// ClientConfig::builder()
/// .with_safe_defaults()
/// .with_root_certificates(root_certs)
/// .with_no_client_auth();
/// ```
///
/// The types used here fit together like this:
///
/// 1. Call [`ClientConfig::builder()`] or [`ServerConfig::builder()`] to initialize a builder.
/// 1. You must make a decision on which cipher suites to use, typically
/// by calling [`ConfigBuilder<S, WantsCipherSuites>::with_safe_default_cipher_suites()`].
/// 2. Now you must make a decision
/// on key exchange groups: typically by calling
/// [`ConfigBuilder<S, WantsKxGroups>::with_safe_default_kx_groups()`].
/// 3. Now you must make
/// a decision on which protocol versions to support, typically by calling
/// [`ConfigBuilder<S, WantsVersions>::with_safe_default_protocol_versions()`].
/// 5. Now see [`ConfigBuilder<ClientConfig, WantsVerifier>`] or
/// [`ConfigBuilder<ServerConfig, WantsVerifier>`] for further steps.
///
/// [`ServerConfig`]: crate::ServerConfig
/// [`ClientConfig`]: crate::ClientConfig
/// [`ClientConfig::builder()`]: crate::ClientConfig::builder()
/// [`ServerConfig::builder()`]: crate::ServerConfig::builder()
/// [`ConfigBuilder<ClientConfig, WantsVerifier>`]: struct.ConfigBuilder.html#impl-3
/// [`ConfigBuilder<ServerConfig, WantsVerifier>`]: struct.ConfigBuilder.html#impl-6
#[derive(Clone)]
pub struct ConfigBuilder<Side: ConfigSide, State> {
pub(crate) state: State,
pub(crate) side: PhantomData<Side>,
}
impl<Side: ConfigSide, State: fmt::Debug> fmt::Debug for ConfigBuilder<Side, State> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let side_name = std::any::type_name::<Side>();
let side_name = side_name
.split("::")
.last()
.unwrap_or(side_name);
f.debug_struct(&format!("ConfigBuilder<{}, _>", side_name))
.field("state", &self.state)
.finish()
}
}
/// Config builder state where the caller must supply cipher suites.
///
/// For more information, see the [`ConfigBuilder`] documentation.
#[derive(Clone, Debug)]
pub struct WantsCipherSuites(pub(crate) ());
impl<S: ConfigSide> ConfigBuilder<S, WantsCipherSuites> {
/// Start side-specific config with defaults for underlying cryptography.
///
/// If used, this will enable all safe supported cipher suites ([`DEFAULT_CIPHER_SUITES`]), all
/// safe supported key exchange groups ([`ALL_KX_GROUPS`]) and all safe supported protocol
/// versions ([`DEFAULT_VERSIONS`]).
///
/// These are safe defaults, useful for 99% of applications.
///
/// [`DEFAULT_VERSIONS`]: versions::DEFAULT_VERSIONS
pub fn with_safe_defaults(self) -> ConfigBuilder<S, WantsVerifier> {
ConfigBuilder {
state: WantsVerifier {
cipher_suites: DEFAULT_CIPHER_SUITES.to_vec(),
kx_groups: ALL_KX_GROUPS.to_vec(),
versions: versions::EnabledVersions::new(versions::DEFAULT_VERSIONS),
},
side: self.side,
}
}
/// Choose a specific set of cipher suites.
pub fn with_cipher_suites(
self,
cipher_suites: &[SupportedCipherSuite],
) -> ConfigBuilder<S, WantsKxGroups> {
ConfigBuilder {
state: WantsKxGroups {
cipher_suites: cipher_suites.to_vec(),
},
side: self.side,
}
}
/// Choose the default set of cipher suites ([`DEFAULT_CIPHER_SUITES`]).
///
/// Note that this default provides only high-quality suites: there is no need
/// to filter out low-, export- or NULL-strength cipher suites: rustls does not
/// implement these.
pub fn with_safe_default_cipher_suites(self) -> ConfigBuilder<S, WantsKxGroups> {
self.with_cipher_suites(DEFAULT_CIPHER_SUITES)
}
}
/// Config builder state where the caller must supply key exchange groups.
///
/// For more information, see the [`ConfigBuilder`] documentation.
#[derive(Clone, Debug)]
pub struct WantsKxGroups {
cipher_suites: Vec<SupportedCipherSuite>,
}
impl<S: ConfigSide> ConfigBuilder<S, WantsKxGroups> {
/// Choose a specific set of key exchange groups.
pub fn with_kx_groups(
self,
kx_groups: &[&'static SupportedKxGroup],
) -> ConfigBuilder<S, WantsVersions> {
ConfigBuilder {
state: WantsVersions {
cipher_suites: self.state.cipher_suites,
kx_groups: kx_groups.to_vec(),
},
side: self.side,
}
}
/// Choose the default set of key exchange groups ([`ALL_KX_GROUPS`]).
///
/// This is a safe default: rustls doesn't implement any poor-quality groups.
pub fn with_safe_default_kx_groups(self) -> ConfigBuilder<S, WantsVersions> {
self.with_kx_groups(&ALL_KX_GROUPS)
}
}
/// Config builder state where the caller must supply TLS protocol versions.
///
/// For more information, see the [`ConfigBuilder`] documentation.
#[derive(Clone, Debug)]
pub struct WantsVersions {
cipher_suites: Vec<SupportedCipherSuite>,
kx_groups: Vec<&'static SupportedKxGroup>,
}
impl<S: ConfigSide> ConfigBuilder<S, WantsVersions> {
/// Accept the default protocol versions: both TLS1.2 and TLS1.3 are enabled.
pub fn with_safe_default_protocol_versions(
self,
) -> Result<ConfigBuilder<S, WantsVerifier>, Error> {
self.with_protocol_versions(versions::DEFAULT_VERSIONS)
}
/// Use a specific set of protocol versions.
pub fn with_protocol_versions(
self,
versions: &[&'static versions::SupportedProtocolVersion],
) -> Result<ConfigBuilder<S, WantsVerifier>, Error> {
let mut any_usable_suite = false;
for suite in &self.state.cipher_suites {
if versions.contains(&suite.version()) {
any_usable_suite = true;
break;
}
}
if !any_usable_suite {
return Err(Error::General("no usable cipher suites configured".into()));
}
if self.state.kx_groups.is_empty() {
return Err(Error::General("no kx groups configured".into()));
}
Ok(ConfigBuilder {
state: WantsVerifier {
cipher_suites: self.state.cipher_suites,
kx_groups: self.state.kx_groups,
versions: versions::EnabledVersions::new(versions),
},
side: self.side,
})
}
}
/// Config builder state where the caller must supply a verifier.
///
/// For more information, see the [`ConfigBuilder`] documentation.
#[derive(Clone, Debug)]
pub struct WantsVerifier {
pub(crate) cipher_suites: Vec<SupportedCipherSuite>,
pub(crate) kx_groups: Vec<&'static SupportedKxGroup>,
pub(crate) versions: versions::EnabledVersions,
}
/// Helper trait to abstract [`ConfigBuilder`] over building a [`ClientConfig`] or [`ServerConfig`].
///
/// [`ClientConfig`]: crate::ClientConfig
/// [`ServerConfig`]: crate::ServerConfig
pub trait ConfigSide: sealed::Sealed {}
impl ConfigSide for crate::ClientConfig {}
impl ConfigSide for crate::ServerConfig {}
mod sealed {
pub trait Sealed {}
impl Sealed for crate::ClientConfig {}
impl Sealed for crate::ServerConfig {}
}

View file

@ -0,0 +1,77 @@
use crate::error::Error;
#[cfg(feature = "logging")]
use crate::log::warn;
use crate::msgs::enums::{ContentType, HandshakeType};
use crate::msgs::message::MessagePayload;
/// For a Message $m, and a HandshakePayload enum member $payload_type,
/// return Ok(payload) if $m is both a handshake message and one that
/// has the given $payload_type. If not, return Err(rustls::Error) quoting
/// $handshake_type as the expected handshake type.
macro_rules! require_handshake_msg(
( $m:expr, $handshake_type:path, $payload_type:path ) => (
match &$m.payload {
MessagePayload::Handshake { parsed: $crate::msgs::handshake::HandshakeMessagePayload {
payload: $payload_type(hm),
..
}, .. } => Ok(hm),
payload => Err($crate::check::inappropriate_handshake_message(
payload,
&[$crate::msgs::enums::ContentType::Handshake],
&[$handshake_type]))
}
)
);
/// Like require_handshake_msg, but moves the payload out of $m.
#[cfg(feature = "tls12")]
macro_rules! require_handshake_msg_move(
( $m:expr, $handshake_type:path, $payload_type:path ) => (
match $m.payload {
MessagePayload::Handshake { parsed: $crate::msgs::handshake::HandshakeMessagePayload {
payload: $payload_type(hm),
..
}, .. } => Ok(hm),
payload =>
Err($crate::check::inappropriate_handshake_message(
&payload,
&[$crate::msgs::enums::ContentType::Handshake],
&[$handshake_type]))
}
)
);
pub(crate) fn inappropriate_message(
payload: &MessagePayload,
content_types: &[ContentType],
) -> Error {
warn!(
"Received a {:?} message while expecting {:?}",
payload.content_type(),
content_types
);
Error::InappropriateMessage {
expect_types: content_types.to_vec(),
got_type: payload.content_type(),
}
}
pub(crate) fn inappropriate_handshake_message(
payload: &MessagePayload,
content_types: &[ContentType],
handshake_types: &[HandshakeType],
) -> Error {
match payload {
MessagePayload::Handshake { parsed, .. } => {
warn!(
"Received a {:?} handshake message while expecting {:?}",
parsed.typ, handshake_types
);
Error::InappropriateHandshakeMessage {
expect_types: handshake_types.to_vec(),
got_type: parsed.typ,
}
}
payload => inappropriate_message(payload, content_types),
}
}

View file

@ -0,0 +1,101 @@
use crate::error::Error;
use crate::msgs::codec;
use crate::msgs::message::{BorrowedPlainMessage, OpaqueMessage, PlainMessage};
use ring::{aead, hkdf};
/// Objects with this trait can decrypt TLS messages.
pub trait MessageDecrypter: Send + Sync {
/// Perform the decryption over the concerned TLS message.
fn decrypt(&self, m: OpaqueMessage, seq: u64) -> Result<PlainMessage, Error>;
}
/// Objects with this trait can encrypt TLS messages.
pub(crate) trait MessageEncrypter: Send + Sync {
fn encrypt(&self, m: BorrowedPlainMessage, seq: u64) -> Result<OpaqueMessage, Error>;
}
impl dyn MessageEncrypter {
pub(crate) fn invalid() -> Box<dyn MessageEncrypter> {
Box::new(InvalidMessageEncrypter {})
}
}
impl dyn MessageDecrypter {
pub(crate) fn invalid() -> Box<dyn MessageDecrypter> {
Box::new(InvalidMessageDecrypter {})
}
}
/// A write or read IV.
#[derive(Default)]
pub(crate) struct Iv(pub(crate) [u8; ring::aead::NONCE_LEN]);
impl Iv {
#[cfg(feature = "tls12")]
fn new(value: [u8; ring::aead::NONCE_LEN]) -> Self {
Self(value)
}
#[cfg(feature = "tls12")]
pub(crate) fn copy(value: &[u8]) -> Self {
debug_assert_eq!(value.len(), ring::aead::NONCE_LEN);
let mut iv = Self::new(Default::default());
iv.0.copy_from_slice(value);
iv
}
#[cfg(test)]
pub(crate) fn value(&self) -> &[u8; 12] {
&self.0
}
}
pub(crate) struct IvLen;
impl hkdf::KeyType for IvLen {
fn len(&self) -> usize {
aead::NONCE_LEN
}
}
impl From<hkdf::Okm<'_, IvLen>> for Iv {
fn from(okm: hkdf::Okm<IvLen>) -> Self {
let mut r = Self(Default::default());
okm.fill(&mut r.0[..]).unwrap();
r
}
}
pub(crate) fn make_nonce(iv: &Iv, seq: u64) -> ring::aead::Nonce {
let mut nonce = [0u8; ring::aead::NONCE_LEN];
codec::put_u64(seq, &mut nonce[4..]);
nonce
.iter_mut()
.zip(iv.0.iter())
.for_each(|(nonce, iv)| {
*nonce ^= *iv;
});
aead::Nonce::assume_unique_for_key(nonce)
}
/// A `MessageEncrypter` which doesn't work.
struct InvalidMessageEncrypter {}
impl MessageEncrypter for InvalidMessageEncrypter {
fn encrypt(&self, _m: BorrowedPlainMessage, _seq: u64) -> Result<OpaqueMessage, Error> {
Err(Error::General("encrypt not yet available".to_string()))
}
}
/// A `MessageDecrypter` which doesn't work.
struct InvalidMessageDecrypter {}
impl MessageDecrypter for InvalidMessageDecrypter {
fn decrypt(&self, _m: OpaqueMessage, _seq: u64) -> Result<PlainMessage, Error> {
Err(Error::DecryptError)
}
}

View file

@ -0,0 +1,192 @@
use crate::anchors;
use crate::builder::{ConfigBuilder, WantsVerifier};
use crate::client::handy;
use crate::client::{ClientConfig, ResolvesClientCert};
use crate::error::Error;
use crate::key;
use crate::kx::SupportedKxGroup;
use crate::suites::SupportedCipherSuite;
use crate::verify::{self, CertificateTransparencyPolicy};
use crate::versions;
use crate::NoKeyLog;
use std::marker::PhantomData;
use std::sync::Arc;
use std::time::SystemTime;
impl ConfigBuilder<ClientConfig, WantsVerifier> {
/// Choose how to verify client certificates.
pub fn with_root_certificates(
self,
root_store: anchors::RootCertStore,
) -> ConfigBuilder<ClientConfig, WantsTransparencyPolicyOrClientCert> {
ConfigBuilder {
state: WantsTransparencyPolicyOrClientCert {
cipher_suites: self.state.cipher_suites,
kx_groups: self.state.kx_groups,
versions: self.state.versions,
root_store,
},
side: PhantomData::default(),
}
}
#[cfg(feature = "dangerous_configuration")]
/// Set a custom certificate verifier.
pub fn with_custom_certificate_verifier(
self,
verifier: Arc<dyn verify::ServerCertVerifier>,
) -> ConfigBuilder<ClientConfig, WantsClientCert> {
ConfigBuilder {
state: WantsClientCert {
cipher_suites: self.state.cipher_suites,
kx_groups: self.state.kx_groups,
versions: self.state.versions,
verifier,
},
side: PhantomData::default(),
}
}
}
/// A config builder state where the caller needs to supply a certificate transparency policy or
/// client certificate resolver.
///
/// In this state, the caller can optionally enable certificate transparency, or ignore CT and
/// invoke one of the methods related to client certificates (as in the [`WantsClientCert`] state).
///
/// For more information, see the [`ConfigBuilder`] documentation.
#[derive(Clone, Debug)]
pub struct WantsTransparencyPolicyOrClientCert {
cipher_suites: Vec<SupportedCipherSuite>,
kx_groups: Vec<&'static SupportedKxGroup>,
versions: versions::EnabledVersions,
root_store: anchors::RootCertStore,
}
impl ConfigBuilder<ClientConfig, WantsTransparencyPolicyOrClientCert> {
/// Set Certificate Transparency logs to use for server certificate validation.
///
/// Because Certificate Transparency logs are sharded on a per-year basis and can be trusted or
/// distrusted relatively quickly, rustls stores a validation deadline. Server certificates will
/// be validated against the configured CT logs until the deadline expires. After the deadline,
/// certificates will no longer be validated, and a warning message will be logged. The deadline
/// may vary depending on how often you deploy builds with updated dependencies.
pub fn with_certificate_transparency_logs(
self,
logs: &'static [&'static sct::Log],
validation_deadline: SystemTime,
) -> ConfigBuilder<ClientConfig, WantsClientCert> {
self.with_logs(Some(CertificateTransparencyPolicy::new(
logs,
validation_deadline,
)))
}
/// Sets a single certificate chain and matching private key for use
/// in client authentication.
///
/// `cert_chain` is a vector of DER-encoded certificates.
/// `key_der` is a DER-encoded RSA, ECDSA, or Ed25519 private key.
///
/// This function fails if `key_der` is invalid.
pub fn with_single_cert(
self,
cert_chain: Vec<key::Certificate>,
key_der: key::PrivateKey,
) -> Result<ClientConfig, Error> {
self.with_logs(None)
.with_single_cert(cert_chain, key_der)
}
/// Do not support client auth.
pub fn with_no_client_auth(self) -> ClientConfig {
self.with_logs(None)
.with_client_cert_resolver(Arc::new(handy::FailResolveClientCert {}))
}
/// Sets a custom [`ResolvesClientCert`].
pub fn with_client_cert_resolver(
self,
client_auth_cert_resolver: Arc<dyn ResolvesClientCert>,
) -> ClientConfig {
self.with_logs(None)
.with_client_cert_resolver(client_auth_cert_resolver)
}
fn with_logs(
self,
ct_policy: Option<CertificateTransparencyPolicy>,
) -> ConfigBuilder<ClientConfig, WantsClientCert> {
ConfigBuilder {
state: WantsClientCert {
cipher_suites: self.state.cipher_suites,
kx_groups: self.state.kx_groups,
versions: self.state.versions,
verifier: Arc::new(verify::WebPkiVerifier::new(
self.state.root_store,
ct_policy,
)),
},
side: PhantomData,
}
}
}
/// A config builder state where the caller needs to supply whether and how to provide a client
/// certificate.
///
/// For more information, see the [`ConfigBuilder`] documentation.
#[derive(Clone, Debug)]
pub struct WantsClientCert {
cipher_suites: Vec<SupportedCipherSuite>,
kx_groups: Vec<&'static SupportedKxGroup>,
versions: versions::EnabledVersions,
verifier: Arc<dyn verify::ServerCertVerifier>,
}
impl ConfigBuilder<ClientConfig, WantsClientCert> {
/// Sets a single certificate chain and matching private key for use
/// in client authentication.
///
/// `cert_chain` is a vector of DER-encoded certificates.
/// `key_der` is a DER-encoded RSA, ECDSA, or Ed25519 private key.
///
/// This function fails if `key_der` is invalid.
pub fn with_single_cert(
self,
cert_chain: Vec<key::Certificate>,
key_der: key::PrivateKey,
) -> Result<ClientConfig, Error> {
let resolver = handy::AlwaysResolvesClientCert::new(cert_chain, &key_der)?;
Ok(self.with_client_cert_resolver(Arc::new(resolver)))
}
/// Do not support client auth.
pub fn with_no_client_auth(self) -> ClientConfig {
self.with_client_cert_resolver(Arc::new(handy::FailResolveClientCert {}))
}
/// Sets a custom [`ResolvesClientCert`].
pub fn with_client_cert_resolver(
self,
client_auth_cert_resolver: Arc<dyn ResolvesClientCert>,
) -> ClientConfig {
ClientConfig {
cipher_suites: self.state.cipher_suites,
kx_groups: self.state.kx_groups,
alpn_protocols: Vec::new(),
session_storage: handy::ClientSessionMemoryCache::new(256),
max_fragment_size: None,
client_auth_cert_resolver,
enable_tickets: true,
versions: self.state.versions,
enable_sni: true,
verifier: self.state.verifier,
key_log: Arc::new(NoKeyLog {}),
#[cfg(feature = "secret_extraction")]
enable_secret_extraction: false,
enable_early_data: false,
}
}
}

View file

@ -0,0 +1,742 @@
use crate::builder::{ConfigBuilder, WantsCipherSuites};
use crate::conn::{CommonState, ConnectionCommon, Protocol, Side};
use crate::enums::{CipherSuite, ProtocolVersion, SignatureScheme};
use crate::error::Error;
use crate::kx;
use crate::kx::SupportedKxGroup;
#[cfg(feature = "logging")]
use crate::log::trace;
#[cfg(feature = "quic")]
use crate::msgs::enums::AlertDescription;
use crate::msgs::handshake::ClientExtension;
use crate::sign;
use crate::suites::SupportedCipherSuite;
use crate::verify;
use crate::versions;
#[cfg(feature = "secret_extraction")]
use crate::ExtractedSecrets;
use crate::KeyLog;
use super::hs;
#[cfg(feature = "quic")]
use crate::quic;
use std::convert::TryFrom;
use std::error::Error as StdError;
use std::marker::PhantomData;
use std::net::IpAddr;
use std::ops::{Deref, DerefMut};
use std::sync::Arc;
/// Callback used to replace the ClientHello session ID from the encoded ClientHello.
pub type ClientSessionIdGenerator = Arc<dyn Fn(&[u8]) -> [u8; 32] + Send + Sync + 'static>;
/// Callback used to replace a TLS 1.2 ClientHello session ID from eager key material.
pub type ClientTls12SessionIdGenerator =
Arc<dyn Fn(&[u8], &[Vec<u8>]) -> [u8; 32] + Send + Sync + 'static>;
/// Session ID generators for custom ClientHello authentication schemes.
#[derive(Clone, Default)]
pub struct ClientSessionIdGenerators {
/// Generator for TLS 1.3 ClientHello authentication.
pub tls13: Option<ClientSessionIdGenerator>,
/// Generator for TLS 1.2 ClientHello authentication.
pub tls12: Option<ClientTls12SessionIdGenerator>,
}
use std::{fmt, io, mem};
/// A trait for the ability to store client session data.
/// The keys and values are opaque.
///
/// Both the keys and values should be treated as
/// **highly sensitive data**, containing enough key material
/// to break all security of the corresponding session.
///
/// `put` is a mutating operation; this isn't expressed
/// in the type system to allow implementations freedom in
/// how to achieve interior mutability. `Mutex` is a common
/// choice.
pub trait StoresClientSessions: Send + Sync {
/// Stores a new `value` for `key`. Returns `true`
/// if the value was stored.
fn put(&self, key: Vec<u8>, value: Vec<u8>) -> bool;
/// Returns the latest value for `key`. Returns `None`
/// if there's no such value.
fn get(&self, key: &[u8]) -> Option<Vec<u8>>;
}
/// A trait for the ability to choose a certificate chain and
/// private key for the purposes of client authentication.
pub trait ResolvesClientCert: Send + Sync {
/// With the server-supplied acceptable issuers in `acceptable_issuers`,
/// the server's supported signature schemes in `sigschemes`,
/// return a certificate chain and signing key to authenticate.
///
/// `acceptable_issuers` is undecoded and unverified by the rustls
/// library, but it should be expected to contain a DER encodings
/// of X501 NAMEs.
///
/// Return None to continue the handshake without any client
/// authentication. The server may reject the handshake later
/// if it requires authentication.
fn resolve(
&self,
acceptable_issuers: &[&[u8]],
sigschemes: &[SignatureScheme],
) -> Option<Arc<sign::CertifiedKey>>;
/// Return true if any certificates at all are available.
fn has_certs(&self) -> bool;
}
/// Common configuration for (typically) all connections made by
/// a program.
///
/// Making one of these can be expensive, and should be
/// once per process rather than once per connection.
///
/// These must be created via the [`ClientConfig::builder()`] function.
///
/// # Defaults
///
/// * [`ClientConfig::max_fragment_size`]: the default is `None`: TLS packets are not fragmented to a specific size.
/// * [`ClientConfig::session_storage`]: the default stores 256 sessions in memory.
/// * [`ClientConfig::alpn_protocols`]: the default is empty -- no ALPN protocol is negotiated.
/// * [`ClientConfig::key_log`]: key material is not logged.
#[derive(Clone)]
pub struct ClientConfig {
/// List of ciphersuites, in preference order.
pub(super) cipher_suites: Vec<SupportedCipherSuite>,
/// List of supported key exchange algorithms, in preference order -- the
/// first element is the highest priority.
///
/// The first element in this list is the _default key share algorithm_,
/// and in TLS1.3 a key share for it is sent in the client hello.
pub(super) kx_groups: Vec<&'static SupportedKxGroup>,
/// Which ALPN protocols we include in our client hello.
/// If empty, no ALPN extension is sent.
pub alpn_protocols: Vec<Vec<u8>>,
/// How we store session data or tickets.
pub session_storage: Arc<dyn StoresClientSessions>,
/// The maximum size of TLS message we'll emit. If None, we don't limit TLS
/// message lengths except to the 2**16 limit specified in the standard.
///
/// rustls enforces an arbitrary minimum of 32 bytes for this field.
/// Out of range values are reported as errors from ClientConnection::new.
///
/// Setting this value to the TCP MSS may improve latency for stream-y workloads.
pub max_fragment_size: Option<usize>,
/// How to decide what client auth certificate/keys to use.
pub client_auth_cert_resolver: Arc<dyn ResolvesClientCert>,
/// Whether to support RFC5077 tickets. You must provide a working
/// `session_storage` member for this to have any meaningful
/// effect.
///
/// The default is true.
pub enable_tickets: bool,
/// Supported versions, in no particular order. The default
/// is all supported versions.
pub(super) versions: versions::EnabledVersions,
/// Whether to send the Server Name Indication (SNI) extension
/// during the client handshake.
///
/// The default is true.
pub enable_sni: bool,
/// How to verify the server certificate chain.
pub(super) verifier: Arc<dyn verify::ServerCertVerifier>,
/// How to output key material for debugging. The default
/// does nothing.
pub key_log: Arc<dyn KeyLog>,
/// Allows traffic secrets to be extracted after the handshake,
/// e.g. for kTLS setup.
#[cfg(feature = "secret_extraction")]
pub enable_secret_extraction: bool,
/// Whether to send data on the first flight ("early data") in
/// TLS 1.3 handshakes.
///
/// The default is false.
pub enable_early_data: bool,
}
impl fmt::Debug for ClientConfig {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ClientConfig")
.field("alpn_protocols", &self.alpn_protocols)
.field("max_fragment_size", &self.max_fragment_size)
.field("enable_tickets", &self.enable_tickets)
.field("enable_sni", &self.enable_sni)
.field("enable_early_data", &self.enable_early_data)
.finish_non_exhaustive()
}
}
impl ClientConfig {
/// Create a builder to build up the client configuration.
///
/// For more information, see the [`ConfigBuilder`] documentation.
pub fn builder() -> ConfigBuilder<Self, WantsCipherSuites> {
ConfigBuilder {
state: WantsCipherSuites(()),
side: PhantomData::default(),
}
}
#[doc(hidden)]
/// We support a given TLS version if it's quoted in the configured
/// versions *and* at least one ciphersuite for this version is
/// also configured.
pub fn supports_version(&self, v: ProtocolVersion) -> bool {
self.versions.contains(v)
&& self
.cipher_suites
.iter()
.any(|cs| cs.version().version == v)
}
/// Access configuration options whose use is dangerous and requires
/// extra care.
#[cfg(feature = "dangerous_configuration")]
pub fn dangerous(&mut self) -> danger::DangerousClientConfig {
danger::DangerousClientConfig { cfg: self }
}
pub(super) fn find_cipher_suite(&self, suite: CipherSuite) -> Option<SupportedCipherSuite> {
self.cipher_suites
.iter()
.copied()
.find(|&scs| scs.suite() == suite)
}
}
/// Encodes ways a client can know the expected name of the server.
///
/// This currently covers knowing the DNS name of the server, but
/// will be extended in the future to supporting privacy-preserving names
/// for the server ("ECH"). For this reason this enum is `non_exhaustive`.
///
/// # Making one
///
/// If you have a DNS name as a `&str`, this type implements `TryFrom<&str>`,
/// so you can do:
///
/// ```
/// # use std::convert::{TryInto, TryFrom};
/// # use rustls::ServerName;
/// ServerName::try_from("example.com").expect("invalid DNS name");
///
/// // or, alternatively...
///
/// let x = "example.com".try_into().expect("invalid DNS name");
/// # let _: ServerName = x;
/// ```
#[non_exhaustive]
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub enum ServerName {
/// The server is identified by a DNS name. The name
/// is sent in the TLS Server Name Indication (SNI)
/// extension.
DnsName(verify::DnsName),
/// The server is identified by an IP address. SNI is not
/// done.
IpAddress(IpAddr),
}
impl ServerName {
/// Return the name that should go in the SNI extension.
/// If [`None`] is returned, the SNI extension is not included
/// in the handshake.
pub(crate) fn for_sni(&self) -> Option<webpki::DnsNameRef> {
match self {
Self::DnsName(dns_name) => Some(dns_name.0.as_ref()),
Self::IpAddress(_) => None,
}
}
/// Return a prefix-free, unique encoding for the name.
pub(crate) fn encode(&self) -> Vec<u8> {
enum UniqueTypeCode {
DnsName = 0x01,
IpAddr = 0x02,
}
match self {
Self::DnsName(dns_name) => {
let bytes = dns_name.0.as_ref();
let mut r = Vec::with_capacity(2 + bytes.as_ref().len());
r.push(UniqueTypeCode::DnsName as u8);
r.push(bytes.as_ref().len() as u8);
r.extend_from_slice(bytes.as_ref());
r
}
Self::IpAddress(address) => {
let string = address.to_string();
let bytes = string.as_bytes();
let mut r = Vec::with_capacity(2 + bytes.len());
r.push(UniqueTypeCode::IpAddr as u8);
r.push(bytes.len() as u8);
r.extend_from_slice(bytes);
r
}
}
}
}
/// Attempt to make a ServerName from a string by parsing
/// it as a DNS name.
impl TryFrom<&str> for ServerName {
type Error = InvalidDnsNameError;
fn try_from(s: &str) -> Result<Self, Self::Error> {
match webpki::DnsNameRef::try_from_ascii_str(s) {
Ok(dns) => Ok(Self::DnsName(verify::DnsName(dns.into()))),
Err(webpki::InvalidDnsNameError) => match s.parse() {
Ok(ip) => Ok(Self::IpAddress(ip)),
Err(_) => Err(InvalidDnsNameError),
},
}
}
}
/// The provided input could not be parsed because
/// it is not a syntactically-valid DNS Name.
#[derive(Debug)]
pub struct InvalidDnsNameError;
impl fmt::Display for InvalidDnsNameError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("invalid dns name")
}
}
impl StdError for InvalidDnsNameError {}
/// Container for unsafe APIs
#[cfg(feature = "dangerous_configuration")]
pub(super) mod danger {
use std::sync::Arc;
use super::verify::ServerCertVerifier;
use super::ClientConfig;
/// Accessor for dangerous configuration options.
#[derive(Debug)]
#[cfg_attr(docsrs, doc(cfg(feature = "dangerous_configuration")))]
pub struct DangerousClientConfig<'a> {
/// The underlying ClientConfig
pub cfg: &'a mut ClientConfig,
}
impl<'a> DangerousClientConfig<'a> {
/// Overrides the default `ServerCertVerifier` with something else.
pub fn set_certificate_verifier(&mut self, verifier: Arc<dyn ServerCertVerifier>) {
self.cfg.verifier = verifier;
}
}
}
#[derive(Debug, PartialEq)]
enum EarlyDataState {
Disabled,
Ready,
Accepted,
AcceptedFinished,
Rejected,
}
pub(super) struct EarlyData {
state: EarlyDataState,
left: usize,
}
impl EarlyData {
fn new() -> Self {
Self {
left: 0,
state: EarlyDataState::Disabled,
}
}
pub(super) fn is_enabled(&self) -> bool {
matches!(self.state, EarlyDataState::Ready | EarlyDataState::Accepted)
}
fn is_accepted(&self) -> bool {
matches!(
self.state,
EarlyDataState::Accepted | EarlyDataState::AcceptedFinished
)
}
pub(super) fn enable(&mut self, max_data: usize) {
assert_eq!(self.state, EarlyDataState::Disabled);
self.state = EarlyDataState::Ready;
self.left = max_data;
}
pub(super) fn rejected(&mut self) {
trace!("EarlyData rejected");
self.state = EarlyDataState::Rejected;
}
pub(super) fn accepted(&mut self) {
trace!("EarlyData accepted");
assert_eq!(self.state, EarlyDataState::Ready);
self.state = EarlyDataState::Accepted;
}
pub(super) fn finished(&mut self) {
trace!("EarlyData finished");
self.state = match self.state {
EarlyDataState::Accepted => EarlyDataState::AcceptedFinished,
_ => panic!("bad EarlyData state"),
}
}
fn check_write(&mut self, sz: usize) -> io::Result<usize> {
match self.state {
EarlyDataState::Disabled => unreachable!(),
EarlyDataState::Ready | EarlyDataState::Accepted => {
let take = if self.left < sz {
mem::replace(&mut self.left, 0)
} else {
self.left -= sz;
sz
};
Ok(take)
}
EarlyDataState::Rejected | EarlyDataState::AcceptedFinished => {
Err(io::Error::from(io::ErrorKind::InvalidInput))
}
}
}
fn bytes_left(&self) -> usize {
self.left
}
}
/// Stub that implements io::Write and dispatches to `write_early_data`.
pub struct WriteEarlyData<'a> {
sess: &'a mut ClientConnection,
}
impl<'a> WriteEarlyData<'a> {
fn new(sess: &'a mut ClientConnection) -> WriteEarlyData<'a> {
WriteEarlyData { sess }
}
/// How many bytes you may send. Writes will become short
/// once this reaches zero.
pub fn bytes_left(&self) -> usize {
self.sess
.inner
.data
.early_data
.bytes_left()
}
}
impl<'a> io::Write for WriteEarlyData<'a> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.sess.write_early_data(buf)
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
/// This represents a single TLS client connection.
pub struct ClientConnection {
inner: ConnectionCommon<ClientConnectionData>,
}
impl fmt::Debug for ClientConnection {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("ClientConnection")
.finish()
}
}
impl ClientConnection {
/// Make a new ClientConnection. `config` controls how
/// we behave in the TLS protocol, `name` is the
/// name of the server we want to talk to.
pub fn new(
config: Arc<ClientConfig>,
name: ServerName,
) -> Result<Self, Error> {
Self::new_inner(config, name, Vec::new(), Protocol::Tcp)
}
/// Create a new ClientConnection with a generator.
pub fn new_with_session_id_generator<F>(
config: Arc<ClientConfig>,
name: ServerName,
session_id_generator: F,
) -> Result<Self, Error>
where
F: Fn(&[u8]) -> [u8; 32] + Send + Sync + 'static,
{
Self::new_inner_with_session_id_generators(
config,
name,
Vec::new(),
Protocol::Tcp,
ClientSessionIdGenerators {
tls13: Some(Arc::new(session_id_generator)),
tls12: None,
},
)
}
/// Create a new ClientConnection with TLS session-id generators.
pub fn new_with_session_id_generators(
config: Arc<ClientConfig>,
name: ServerName,
session_id_generators: ClientSessionIdGenerators,
) -> Result<Self, Error> {
Self::new_inner_with_session_id_generators(
config,
name,
Vec::new(),
Protocol::Tcp,
session_id_generators,
)
}
fn new_inner(
config: Arc<ClientConfig>,
name: ServerName,
extra_exts: Vec<ClientExtension>,
proto: Protocol,
) -> Result<Self, Error> {
Self::new_inner_with_session_id_generators(
config,
name,
extra_exts,
proto,
ClientSessionIdGenerators::default(),
)
}
fn new_inner_with_session_id_generators(
config: Arc<ClientConfig>,
name: ServerName,
extra_exts: Vec<ClientExtension>,
proto: Protocol,
session_id_generators: ClientSessionIdGenerators,
) -> Result<Self, Error> {
let mut common_state = CommonState::new(Side::Client);
common_state.set_max_fragment_size(config.max_fragment_size)?;
common_state.protocol = proto;
#[cfg(feature = "secret_extraction")]
{
common_state.enable_secret_extraction = config.enable_secret_extraction;
}
let mut data = ClientConnectionData::new();
let mut cx = hs::ClientContext {
common: &mut common_state,
data: &mut data,
};
let state =
hs::start_handshake(name, extra_exts, config, &mut cx, session_id_generators)?;
let inner = ConnectionCommon::new(state, data, common_state);
Ok(Self { inner })
}
/// Returns an `io::Write` implementer you can write bytes to
/// to send TLS1.3 early data (a.k.a. "0-RTT data") to the server.
///
/// This returns None in many circumstances when the capability to
/// send early data is not available, including but not limited to:
///
/// - The server hasn't been talked to previously.
/// - The server does not support resumption.
/// - The server does not support early data.
/// - The resumption data for the server has expired.
///
/// The server specifies a maximum amount of early data. You can
/// learn this limit through the returned object, and writes through
/// it will process only this many bytes.
///
/// The server can choose not to accept any sent early data --
/// in this case the data is lost but the connection continues. You
/// can tell this happened using `is_early_data_accepted`.
pub fn early_data(&mut self) -> Option<WriteEarlyData> {
if self.inner.data.early_data.is_enabled() {
Some(WriteEarlyData::new(self))
} else {
None
}
}
/// Returns True if the server signalled it will process early data.
///
/// If you sent early data and this returns false at the end of the
/// handshake then the server will not process the data. This
/// is not an error, but you may wish to resend the data.
pub fn is_early_data_accepted(&self) -> bool {
self.inner.data.early_data.is_accepted()
}
fn write_early_data(&mut self, data: &[u8]) -> io::Result<usize> {
self.inner
.data
.early_data
.check_write(data.len())
.map(|sz| {
self.inner
.common_state
.send_early_plaintext(&data[..sz])
})
}
/// Extract secrets, so they can be used when configuring kTLS, for example.
#[cfg(feature = "secret_extraction")]
pub fn extract_secrets(self) -> Result<ExtractedSecrets, Error> {
self.inner.extract_secrets()
}
}
impl Deref for ClientConnection {
type Target = ConnectionCommon<ClientConnectionData>;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl DerefMut for ClientConnection {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}
#[doc(hidden)]
impl<'a> TryFrom<&'a mut crate::Connection> for &'a mut ClientConnection {
type Error = ();
fn try_from(value: &'a mut crate::Connection) -> Result<Self, Self::Error> {
use crate::Connection::*;
match value {
Client(conn) => Ok(conn),
Server(_) => Err(()),
}
}
}
impl From<ClientConnection> for crate::Connection {
fn from(conn: ClientConnection) -> Self {
Self::Client(conn)
}
}
/// State associated with a client connection.
pub struct ClientConnectionData {
pub(super) early_data: EarlyData,
pub(super) resumption_ciphersuite: Option<SupportedCipherSuite>,
pub(in crate::client) tls12_eager_key_exchanges: Vec<kx::KeyExchange>,
}
impl ClientConnectionData {
fn new() -> Self {
Self {
early_data: EarlyData::new(),
resumption_ciphersuite: None,
tls12_eager_key_exchanges: Vec::new(),
}
}
}
impl crate::conn::SideData for ClientConnectionData {}
#[cfg(feature = "quic")]
impl quic::QuicExt for ClientConnection {
fn quic_transport_parameters(&self) -> Option<&[u8]> {
self.inner
.common_state
.quic
.params
.as_ref()
.map(|v| v.as_ref())
}
fn zero_rtt_keys(&self) -> Option<quic::DirectionalKeys> {
Some(quic::DirectionalKeys::new(
self.inner
.data
.resumption_ciphersuite
.and_then(|suite| suite.tls13())?,
self.inner
.common_state
.quic
.early_secret
.as_ref()?,
))
}
fn read_hs(&mut self, plaintext: &[u8]) -> Result<(), Error> {
self.inner.read_quic_hs(plaintext)
}
fn write_hs(&mut self, buf: &mut Vec<u8>) -> Option<quic::KeyChange> {
quic::write_hs(&mut self.inner.common_state, buf)
}
fn alert(&self) -> Option<AlertDescription> {
self.inner.common_state.quic.alert
}
}
/// Methods specific to QUIC client sessions
#[cfg(feature = "quic")]
#[cfg_attr(docsrs, doc(cfg(feature = "quic")))]
pub trait ClientQuicExt {
/// Make a new QUIC ClientConnection. This differs from `ClientConnection::new()`
/// in that it takes an extra argument, `params`, which contains the
/// TLS-encoded transport parameters to send.
fn new_quic(
config: Arc<ClientConfig>,
quic_version: quic::Version,
name: ServerName,
params: Vec<u8>,
) -> Result<ClientConnection, Error> {
if !config.supports_version(ProtocolVersion::TLSv1_3) {
return Err(Error::General(
"TLS 1.3 support is required for QUIC".into(),
));
}
let ext = match quic_version {
quic::Version::V1Draft => ClientExtension::TransportParametersDraft(params),
quic::Version::V1 => ClientExtension::TransportParameters(params),
};
// hack: only a work around, do not use it.
ClientConnection::new_inner(config, name, vec![ext], Protocol::Quic)
}
}
#[cfg(feature = "quic")]
impl ClientQuicExt for ClientConnection {}

View file

@ -0,0 +1,114 @@
use super::ResolvesClientCert;
#[cfg(feature = "logging")]
use crate::log::{debug, trace};
use crate::msgs::enums::ExtensionType;
use crate::msgs::handshake::CertificatePayload;
use crate::msgs::handshake::SCTList;
use crate::msgs::handshake::ServerExtension;
use crate::{sign, DistinguishedNames, SignatureScheme};
use std::sync::Arc;
#[derive(Debug)]
pub(super) struct ServerCertDetails {
pub(super) cert_chain: CertificatePayload,
pub(super) ocsp_response: Vec<u8>,
pub(super) scts: Option<SCTList>,
}
impl ServerCertDetails {
pub(super) fn new(
cert_chain: CertificatePayload,
ocsp_response: Vec<u8>,
scts: Option<SCTList>,
) -> Self {
Self {
cert_chain,
ocsp_response,
scts,
}
}
pub(super) fn scts(&self) -> impl Iterator<Item = &[u8]> {
self.scts
.as_deref()
.unwrap_or(&[])
.iter()
.map(|payload| payload.0.as_slice())
}
}
pub(super) struct ClientHelloDetails {
pub(super) sent_extensions: Vec<ExtensionType>,
}
impl ClientHelloDetails {
pub(super) fn new() -> Self {
Self {
sent_extensions: Vec::new(),
}
}
pub(super) fn server_may_send_sct_list(&self) -> bool {
self.sent_extensions
.contains(&ExtensionType::SCT)
}
pub(super) fn server_sent_unsolicited_extensions(
&self,
received_exts: &[ServerExtension],
allowed_unsolicited: &[ExtensionType],
) -> bool {
for ext in received_exts {
let ext_type = ext.get_type();
if !self.sent_extensions.contains(&ext_type) && !allowed_unsolicited.contains(&ext_type)
{
trace!("Unsolicited extension {:?}", ext_type);
return true;
}
}
false
}
}
pub(super) enum ClientAuthDetails {
/// Send an empty `Certificate` and no `CertificateVerify`.
Empty { auth_context_tls13: Option<Vec<u8>> },
/// Send a non-empty `Certificate` and a `CertificateVerify`.
Verify {
certkey: Arc<sign::CertifiedKey>,
signer: Box<dyn sign::Signer>,
auth_context_tls13: Option<Vec<u8>>,
},
}
impl ClientAuthDetails {
pub(super) fn resolve(
resolver: &dyn ResolvesClientCert,
canames: Option<&DistinguishedNames>,
sigschemes: &[SignatureScheme],
auth_context_tls13: Option<Vec<u8>>,
) -> Self {
let acceptable_issuers = canames
.map(Vec::as_slice)
.unwrap_or_default()
.iter()
.map(|p| p.0.as_slice())
.collect::<Vec<&[u8]>>();
if let Some(certkey) = resolver.resolve(&acceptable_issuers, sigschemes) {
if let Some(signer) = certkey.key.choose_scheme(sigschemes) {
debug!("Attempting client auth");
return Self::Verify {
certkey,
signer,
auth_context_tls13,
};
}
}
debug!("Client auth requested but no cert/sigscheme available");
Self::Empty { auth_context_tls13 }
}
}

View file

@ -0,0 +1,161 @@
use crate::client;
use crate::enums::SignatureScheme;
use crate::error::Error;
use crate::key;
use crate::limited_cache;
use crate::sign;
use std::sync::{Arc, Mutex};
/// An implementer of `StoresClientSessions` which does nothing.
pub struct NoClientSessionStorage {}
impl client::StoresClientSessions for NoClientSessionStorage {
fn put(&self, _key: Vec<u8>, _value: Vec<u8>) -> bool {
false
}
fn get(&self, _key: &[u8]) -> Option<Vec<u8>> {
None
}
}
/// An implementer of `StoresClientSessions` that stores everything
/// in memory. It enforces a limit on the number of entries
/// to bound memory usage.
pub struct ClientSessionMemoryCache {
cache: Mutex<limited_cache::LimitedCache<Vec<u8>, Vec<u8>>>,
}
impl ClientSessionMemoryCache {
/// Make a new ClientSessionMemoryCache. `size` is the
/// maximum number of stored sessions.
pub fn new(size: usize) -> Arc<Self> {
debug_assert!(size > 0);
Arc::new(Self {
cache: Mutex::new(limited_cache::LimitedCache::new(size)),
})
}
}
impl client::StoresClientSessions for ClientSessionMemoryCache {
fn put(&self, key: Vec<u8>, value: Vec<u8>) -> bool {
self.cache
.lock()
.unwrap()
.insert(key, value);
true
}
fn get(&self, key: &[u8]) -> Option<Vec<u8>> {
self.cache
.lock()
.unwrap()
.get(key)
.cloned()
}
}
pub(super) struct FailResolveClientCert {}
impl client::ResolvesClientCert for FailResolveClientCert {
fn resolve(
&self,
_acceptable_issuers: &[&[u8]],
_sigschemes: &[SignatureScheme],
) -> Option<Arc<sign::CertifiedKey>> {
None
}
fn has_certs(&self) -> bool {
false
}
}
pub(super) struct AlwaysResolvesClientCert(Arc<sign::CertifiedKey>);
impl AlwaysResolvesClientCert {
pub(super) fn new(
chain: Vec<key::Certificate>,
priv_key: &key::PrivateKey,
) -> Result<Self, Error> {
let key = sign::any_supported_type(priv_key)
.map_err(|_| Error::General("invalid private key".into()))?;
Ok(Self(Arc::new(sign::CertifiedKey::new(chain, key))))
}
}
impl client::ResolvesClientCert for AlwaysResolvesClientCert {
fn resolve(
&self,
_acceptable_issuers: &[&[u8]],
_sigschemes: &[SignatureScheme],
) -> Option<Arc<sign::CertifiedKey>> {
Some(Arc::clone(&self.0))
}
fn has_certs(&self) -> bool {
true
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::client::StoresClientSessions;
#[test]
fn test_noclientsessionstorage_drops_put() {
let c = NoClientSessionStorage {};
assert!(!c.put(vec![0x01], vec![0x02]));
}
#[test]
fn test_noclientsessionstorage_denies_gets() {
let c = NoClientSessionStorage {};
c.put(vec![0x01], vec![0x02]);
assert_eq!(c.get(&[]), None);
assert_eq!(c.get(&[0x01]), None);
assert_eq!(c.get(&[0x02]), None);
}
#[test]
fn test_clientsessionmemorycache_accepts_put() {
let c = ClientSessionMemoryCache::new(4);
assert!(c.put(vec![0x01], vec![0x02]));
}
#[test]
fn test_clientsessionmemorycache_persists_put() {
let c = ClientSessionMemoryCache::new(4);
assert!(c.put(vec![0x01], vec![0x02]));
assert_eq!(c.get(&[0x01]), Some(vec![0x02]));
assert_eq!(c.get(&[0x01]), Some(vec![0x02]));
}
#[test]
fn test_clientsessionmemorycache_overwrites_put() {
let c = ClientSessionMemoryCache::new(4);
assert!(c.put(vec![0x01], vec![0x02]));
assert!(c.put(vec![0x01], vec![0x04]));
assert_eq!(c.get(&[0x01]), Some(vec![0x04]));
}
#[test]
fn test_clientsessionmemorycache_drops_to_maintain_size_invariant() {
let c = ClientSessionMemoryCache::new(2);
assert!(c.put(vec![0x01], vec![0x02]));
assert!(c.put(vec![0x03], vec![0x04]));
assert!(c.put(vec![0x05], vec![0x06]));
assert!(c.put(vec![0x07], vec![0x08]));
assert!(c.put(vec![0x09], vec![0x0a]));
let count = c.get(&[0x01]).iter().count()
+ c.get(&[0x03]).iter().count()
+ c.get(&[0x05]).iter().count()
+ c.get(&[0x07]).iter().count()
+ c.get(&[0x09]).iter().count();
assert!(count < 5);
}
}

View file

@ -0,0 +1,891 @@
#[cfg(feature = "logging")]
use crate::bs_debug;
use crate::check::inappropriate_handshake_message;
use crate::conn::{CommonState, ConnectionRandoms, State};
use crate::enums::{CipherSuite, ProtocolVersion};
use crate::error::Error;
use crate::hash_hs::HandshakeHashBuffer;
use crate::kx;
#[cfg(feature = "logging")]
use crate::log::{debug, trace};
use crate::msgs::base::Payload;
#[cfg(feature = "quic")]
use crate::msgs::base::PayloadU16;
use crate::msgs::codec::{Codec, Reader};
use crate::msgs::enums::{AlertDescription, Compression, ContentType};
use crate::msgs::enums::{ECPointFormat, PSKKeyExchangeMode};
use crate::msgs::enums::{ExtensionType, HandshakeType};
use crate::msgs::handshake::{CertificateStatusRequest, ClientSessionTicket, SCTList};
use crate::msgs::handshake::{ClientExtension, HasServerExtensions};
use crate::msgs::handshake::{ClientHelloPayload, HandshakeMessagePayload, HandshakePayload};
use crate::msgs::handshake::{ConvertProtocolNameList, ProtocolNameList};
use crate::msgs::handshake::{ECPointFormatList, SupportedPointFormats};
use crate::msgs::handshake::{HelloRetryRequest, KeyShareEntry};
use crate::msgs::handshake::{Random, SessionID};
use crate::msgs::message::{Message, MessagePayload};
use crate::msgs::persist;
use crate::ticketer::TimeBase;
use crate::tls13::key_schedule::KeyScheduleEarly;
use crate::SupportedCipherSuite;
#[cfg(feature = "tls12")]
use super::tls12;
use crate::client::client_conn::{ClientConnectionData, ClientSessionIdGenerators};
use crate::client::common::ClientHelloDetails;
use crate::client::{tls13, ClientConfig, ServerName};
use std::sync::Arc;
pub(super) type NextState = Box<dyn State<ClientConnectionData>>;
pub(super) type NextStateOrError = Result<NextState, Error>;
pub(super) type ClientContext<'a> = crate::conn::Context<'a, ClientConnectionData>;
fn find_session(
server_name: &ServerName,
config: &ClientConfig,
#[cfg(feature = "quic")] cx: &mut ClientContext<'_>,
) -> Option<persist::Retrieved<persist::ClientSessionValue>> {
let key = persist::ClientSessionKey::session_for_server_name(server_name);
let key_buf = key.get_encoding();
let value = config
.session_storage
.get(&key_buf)
.or_else(|| {
debug!("No cached session for {:?}", server_name);
None
})?;
#[allow(unused_mut)]
let mut reader = Reader::init(&value[2..]);
#[allow(clippy::bind_instead_of_map)] // https://github.com/rust-lang/rust-clippy/issues/8082
CipherSuite::read_bytes(&value[..2])
.and_then(|suite| {
persist::ClientSessionValue::read(&mut reader, suite, &config.cipher_suites)
})
.and_then(|resuming| {
let retrieved = persist::Retrieved::new(resuming, TimeBase::now().ok()?);
match retrieved.has_expired() {
false => Some(retrieved),
true => None,
}
})
.and_then(|resuming| {
#[cfg(feature = "quic")]
if cx.common.is_quic() {
let params = PayloadU16::read(&mut reader)?;
cx.common.quic.params = Some(params.0);
}
Some(resuming)
})
}
pub(super) fn start_handshake(
server_name: ServerName,
extra_exts: Vec<ClientExtension>,
config: Arc<ClientConfig>,
cx: &mut ClientContext<'_>,
session_id_generators: ClientSessionIdGenerators,
) -> NextStateOrError {
let mut transcript_buffer = HandshakeHashBuffer::new();
if config
.client_auth_cert_resolver
.has_certs()
{
transcript_buffer.set_client_auth_enabled();
}
let support_tls13 = config.supports_version(ProtocolVersion::TLSv1_3);
let mut session_id: Option<SessionID> = None;
let mut resuming_session = find_session(
&server_name,
&config,
#[cfg(feature = "quic")]
cx,
);
let key_share = if support_tls13 {
Some(tls13::initial_key_share(&config, &server_name)?)
} else {
None
};
if let Some(_resuming) = &mut resuming_session {
#[cfg(feature = "tls12")]
if let persist::ClientSessionValue::Tls12(inner) = &mut _resuming.value {
// If we have a ticket, we use the sessionid as a signal that
// we're doing an abbreviated handshake. See section 3.4 in
// RFC5077.
if !inner.ticket().is_empty() {
inner.session_id = SessionID::random()?;
}
session_id = Some(inner.session_id);
}
debug!("Resuming session");
} else {
debug!("Not resuming any session");
}
// https://tools.ietf.org/html/rfc8446#appendix-D.4
// https://tools.ietf.org/html/draft-ietf-quic-tls-34#section-8.4
if session_id.is_none() && !cx.common.is_quic() {
session_id = Some(SessionID::random()?);
}
let random = Random::new()?;
let hello_details = ClientHelloDetails::new();
let sent_tls13_fake_ccs = false;
let may_send_sct_list = config.verifier.request_scts();
Ok(emit_client_hello_for_retry(
config,
cx,
resuming_session,
random,
false,
transcript_buffer,
sent_tls13_fake_ccs,
hello_details,
session_id,
session_id_generators,
None,
server_name,
key_share,
extra_exts,
may_send_sct_list,
None,
))
}
struct ExpectServerHello {
config: Arc<ClientConfig>,
resuming_session: Option<persist::Retrieved<persist::ClientSessionValue>>,
server_name: ServerName,
random: Random,
using_ems: bool,
transcript_buffer: HandshakeHashBuffer,
early_key_schedule: Option<KeyScheduleEarly>,
hello: ClientHelloDetails,
offered_key_share: Option<kx::KeyExchange>,
session_id: SessionID,
sent_tls13_fake_ccs: bool,
suite: Option<SupportedCipherSuite>,
}
struct ExpectServerHelloOrHelloRetryRequest {
next: ExpectServerHello,
extra_exts: Vec<ClientExtension>,
}
fn emit_client_hello_for_retry(
config: Arc<ClientConfig>,
cx: &mut ClientContext<'_>,
resuming_session: Option<persist::Retrieved<persist::ClientSessionValue>>,
random: Random,
using_ems: bool,
mut transcript_buffer: HandshakeHashBuffer,
mut sent_tls13_fake_ccs: bool,
mut hello: ClientHelloDetails,
session_id: Option<SessionID>,
session_id_generators: ClientSessionIdGenerators,
retryreq: Option<&HelloRetryRequest>,
server_name: ServerName,
key_share: Option<kx::KeyExchange>,
extra_exts: Vec<ClientExtension>,
may_send_sct_list: bool,
suite: Option<SupportedCipherSuite>,
) -> NextState {
// Do we have a SessionID or ticket cached for this host?
let (ticket, resume_version) = if let Some(resuming) = &resuming_session {
match &resuming.value {
persist::ClientSessionValue::Tls13(inner) => {
(inner.ticket().to_vec(), ProtocolVersion::TLSv1_3)
}
#[cfg(feature = "tls12")]
persist::ClientSessionValue::Tls12(inner) => {
(inner.ticket().to_vec(), ProtocolVersion::TLSv1_2)
}
}
} else {
(Vec::new(), ProtocolVersion::Unknown(0))
};
let support_tls12 = config.supports_version(ProtocolVersion::TLSv1_2) && !cx.common.is_quic();
let support_tls13 = config.supports_version(ProtocolVersion::TLSv1_3);
let mut supported_versions = Vec::new();
if support_tls13 {
supported_versions.push(ProtocolVersion::TLSv1_3);
}
if support_tls12 {
supported_versions.push(ProtocolVersion::TLSv1_2);
}
cx.data.tls12_eager_key_exchanges.clear();
let tls12_session_id_materials = if retryreq.is_none()
&& support_tls12
&& session_id_generators.tls12.is_some()
{
let mut materials = Vec::new();
for &group in &config.kx_groups {
if let Some(kx) = kx::KeyExchange::start(group) {
materials.push(kx.pubkey.as_ref().to_vec());
cx.data.tls12_eager_key_exchanges.push(kx);
}
}
if resume_version == ProtocolVersion::TLSv1_2 && !ticket.is_empty() {
materials.push(ticket.clone());
}
Some(materials)
} else {
None
};
// should be unreachable thanks to config builder
assert!(!supported_versions.is_empty());
let mut exts = vec![
ClientExtension::SupportedVersions(supported_versions),
ClientExtension::ECPointFormats(ECPointFormatList::supported()),
ClientExtension::NamedGroups(
config
.kx_groups
.iter()
.map(|skxg| skxg.name)
.collect(),
),
ClientExtension::SignatureAlgorithms(
config
.verifier
.supported_verify_schemes(),
),
ClientExtension::ExtendedMasterSecretRequest,
ClientExtension::CertificateStatusRequest(CertificateStatusRequest::build_ocsp()),
];
if let (Some(sni_name), true) = (server_name.for_sni(), config.enable_sni) {
exts.push(ClientExtension::make_sni(sni_name));
}
if may_send_sct_list {
exts.push(ClientExtension::SignedCertificateTimestampRequest);
}
if let Some(key_share) = &key_share {
debug_assert!(support_tls13);
let key_share = KeyShareEntry::new(key_share.group(), key_share.pubkey.as_ref());
exts.push(ClientExtension::KeyShare(vec![key_share]));
}
if let Some(cookie) = retryreq.and_then(HelloRetryRequest::get_cookie) {
exts.push(ClientExtension::Cookie(cookie.clone()));
}
if support_tls13 && config.enable_tickets {
// We could support PSK_KE here too. Such connections don't
// have forward secrecy, and are similar to TLS1.2 resumption.
let psk_modes = vec![PSKKeyExchangeMode::PSK_DHE_KE];
exts.push(ClientExtension::PresharedKeyModes(psk_modes));
}
if !config.alpn_protocols.is_empty() {
exts.push(ClientExtension::Protocols(ProtocolNameList::from_slices(
&config
.alpn_protocols
.iter()
.map(|proto| &proto[..])
.collect::<Vec<_>>(),
)));
}
// Extra extensions must be placed before the PSK extension
exts.extend(extra_exts.iter().cloned());
let fill_in_binder = if support_tls13
&& config.enable_tickets
&& resume_version == ProtocolVersion::TLSv1_3
&& !ticket.is_empty()
{
resuming_session
.as_ref()
.and_then(|resuming| match (suite, resuming.tls13()) {
(Some(suite), Some(resuming)) => {
suite
.tls13()?
.can_resume_from(resuming.suite())?;
Some(resuming)
}
(None, Some(resuming)) => Some(resuming),
_ => None,
})
.map(|resuming| {
tls13::prepare_resumption(
&config,
cx,
ticket,
&resuming,
&mut exts,
retryreq.is_some(),
);
resuming
})
} else if config.enable_tickets {
// If we have a ticket, include it. Otherwise, request one.
if ticket.is_empty() {
exts.push(ClientExtension::SessionTicket(ClientSessionTicket::Request));
} else {
exts.push(ClientExtension::SessionTicket(ClientSessionTicket::Offer(
Payload::new(ticket),
)));
}
None
} else {
None
};
// Note what extensions we sent.
hello.sent_extensions = exts
.iter()
.map(ClientExtension::get_type)
.collect();
let mut session_id = session_id.unwrap_or_else(SessionID::empty);
let mut cipher_suites: Vec<_> = config
.cipher_suites
.iter()
.map(|cs| cs.suite())
.collect();
// We don't do renegotiation at all, in fact.
cipher_suites.push(CipherSuite::TLS_EMPTY_RENEGOTIATION_INFO_SCSV);
let mut chp = HandshakeMessagePayload {
typ: HandshakeType::ClientHello,
payload: HandshakePayload::ClientHello(ClientHelloPayload {
client_version: ProtocolVersion::TLSv1_2,
random,
session_id,
cipher_suites,
compression_methods: vec![Compression::Null],
extensions: exts,
}),
};
// hack: sign chp and overwrite session id
if session_id_generators.tls13.is_some() || tls12_session_id_materials.is_some() {
let mut buffer = Vec::new();
match &mut chp.payload {
HandshakePayload::ClientHello(c) => {
c.session_id = SessionID::zero();
}
_ => unreachable!(),
}
chp.encode(&mut buffer);
let generated_session_id = if let (Some(generator), Some(materials)) = (
session_id_generators.tls12.as_ref(),
tls12_session_id_materials.as_ref(),
) {
generator(&buffer, materials)
} else if let Some(generator) = session_id_generators.tls13.as_ref() {
generator(&buffer)
} else {
unreachable!()
};
session_id = SessionID {
len: 32,
data: generated_session_id,
};
match &mut chp.payload {
HandshakePayload::ClientHello(c) => {
c.session_id = session_id;
}
_ => unreachable!(),
}
}
let early_key_schedule = if let Some(resuming) = fill_in_binder {
let schedule = tls13::fill_in_psk_binder(&resuming, &transcript_buffer, &mut chp);
Some((resuming.suite(), schedule))
} else {
None
};
let ch = Message {
// "This value MUST be set to 0x0303 for all records generated
// by a TLS 1.3 implementation other than an initial ClientHello
// (i.e., one not generated after a HelloRetryRequest)"
version: if retryreq.is_some() {
ProtocolVersion::TLSv1_2
} else {
ProtocolVersion::TLSv1_0
},
payload: MessagePayload::handshake(chp),
};
if retryreq.is_some() {
// send dummy CCS to fool middleboxes prior
// to second client hello
tls13::emit_fake_ccs(&mut sent_tls13_fake_ccs, cx.common);
}
trace!("Sending ClientHello {:#?}", ch);
transcript_buffer.add_message(&ch);
cx.common.send_msg(ch, false);
// Calculate the hash of ClientHello and use it to derive EarlyTrafficSecret
let early_key_schedule = early_key_schedule.map(|(resuming_suite, schedule)| {
if !cx.data.early_data.is_enabled() {
return schedule;
}
tls13::derive_early_traffic_secret(
&*config.key_log,
cx,
resuming_suite,
&schedule,
&mut sent_tls13_fake_ccs,
&transcript_buffer,
&random.0,
);
schedule
});
let next = ExpectServerHello {
config,
resuming_session,
server_name,
random,
using_ems,
transcript_buffer,
early_key_schedule,
hello,
offered_key_share: key_share,
session_id,
sent_tls13_fake_ccs,
suite,
};
if support_tls13 && retryreq.is_none() {
Box::new(ExpectServerHelloOrHelloRetryRequest { next, extra_exts })
} else {
Box::new(next)
}
}
pub(super) fn process_alpn_protocol(
common: &mut CommonState,
config: &ClientConfig,
proto: Option<&[u8]>,
) -> Result<(), Error> {
common.alpn_protocol = proto.map(ToOwned::to_owned);
if let Some(alpn_protocol) = &common.alpn_protocol {
if !config
.alpn_protocols
.contains(alpn_protocol)
{
return Err(common.illegal_param("server sent non-offered ALPN protocol"));
}
}
#[cfg(feature = "quic")]
{
// RFC 9001 says: "While ALPN only specifies that servers use this alert, QUIC clients MUST
// use error 0x0178 to terminate a connection when ALPN negotiation fails." We judge that
// the user intended to use ALPN (rather than some out-of-band protocol negotiation
// mechanism) iff any ALPN protocols were configured. This defends against badly-behaved
// servers which accept a connection that requires an application-layer protocol they do not
// understand.
if common.is_quic() && common.alpn_protocol.is_none() && !config.alpn_protocols.is_empty() {
common.send_fatal_alert(AlertDescription::NoApplicationProtocol);
return Err(Error::NoApplicationProtocol);
}
}
debug!(
"ALPN protocol is {:?}",
common
.alpn_protocol
.as_ref()
.map(|v| bs_debug::BsDebug(v))
);
Ok(())
}
pub(super) fn sct_list_is_invalid(scts: &SCTList) -> bool {
scts.is_empty() || scts.iter().any(|sct| sct.0.is_empty())
}
impl State<ClientConnectionData> for ExpectServerHello {
fn handle(mut self: Box<Self>, cx: &mut ClientContext<'_>, m: Message) -> NextStateOrError {
let server_hello =
require_handshake_msg!(m, HandshakeType::ServerHello, HandshakePayload::ServerHello)?;
trace!("We got ServerHello {:#?}", server_hello);
use crate::ProtocolVersion::{TLSv1_2, TLSv1_3};
let tls13_supported = self.config.supports_version(TLSv1_3);
let server_version = if server_hello.legacy_version == TLSv1_2 {
server_hello
.get_supported_versions()
.unwrap_or(server_hello.legacy_version)
} else {
server_hello.legacy_version
};
let version = match server_version {
TLSv1_3 if tls13_supported => TLSv1_3,
TLSv1_2 if self.config.supports_version(TLSv1_2) => {
if cx.data.early_data.is_enabled() && cx.common.early_traffic {
// The client must fail with a dedicated error code if the server
// responds with TLS 1.2 when offering 0-RTT.
return Err(Error::PeerMisbehavedError(
"server chose v1.2 when offering 0-rtt".to_string(),
));
}
if server_hello
.get_supported_versions()
.is_some()
{
return Err(cx
.common
.illegal_param("server chose v1.2 using v1.3 extension"));
}
TLSv1_2
}
_ => {
cx.common
.send_fatal_alert(AlertDescription::ProtocolVersion);
let msg = match server_version {
TLSv1_2 | TLSv1_3 => "server's TLS version is disabled in client",
_ => "server does not support TLS v1.2/v1.3",
};
return Err(Error::PeerIncompatibleError(msg.to_string()));
}
};
if server_hello.compression_method != Compression::Null {
return Err(cx
.common
.illegal_param("server chose non-Null compression"));
}
if server_hello.has_duplicate_extension() {
cx.common
.send_fatal_alert(AlertDescription::DecodeError);
return Err(Error::PeerMisbehavedError(
"server sent duplicate extensions".to_string(),
));
}
let allowed_unsolicited = [ExtensionType::RenegotiationInfo];
if self
.hello
.server_sent_unsolicited_extensions(&server_hello.extensions, &allowed_unsolicited)
{
cx.common
.send_fatal_alert(AlertDescription::UnsupportedExtension);
return Err(Error::PeerMisbehavedError(
"server sent unsolicited extension".to_string(),
));
}
cx.common.negotiated_version = Some(version);
// Extract ALPN protocol
if !cx.common.is_tls13() {
process_alpn_protocol(cx.common, &self.config, server_hello.get_alpn_protocol())?;
}
// If ECPointFormats extension is supplied by the server, it must contain
// Uncompressed. But it's allowed to be omitted.
if let Some(point_fmts) = server_hello.get_ecpoints_extension() {
if !point_fmts.contains(&ECPointFormat::Uncompressed) {
cx.common
.send_fatal_alert(AlertDescription::HandshakeFailure);
return Err(Error::PeerMisbehavedError(
"server does not support uncompressed points".to_string(),
));
}
}
let suite = self
.config
.find_cipher_suite(server_hello.cipher_suite)
.ok_or_else(|| {
cx.common
.send_fatal_alert(AlertDescription::HandshakeFailure);
Error::PeerMisbehavedError("server chose non-offered ciphersuite".to_string())
})?;
if version != suite.version().version {
return Err(cx
.common
.illegal_param("server chose unusable ciphersuite for version"));
}
match self.suite {
Some(prev_suite) if prev_suite != suite => {
return Err(cx
.common
.illegal_param("server varied selected ciphersuite"));
}
_ => {
debug!("Using ciphersuite {:?}", suite);
self.suite = Some(suite);
cx.common.suite = Some(suite);
}
}
// Start our handshake hash, and input the server-hello.
let mut transcript = self
.transcript_buffer
.start_hash(suite.hash_algorithm());
transcript.add_message(&m);
let randoms = ConnectionRandoms::new(self.random, server_hello.random);
// For TLS1.3, start message encryption using
// handshake_traffic_secret.
match suite {
SupportedCipherSuite::Tls13(suite) => {
let resuming_session = self
.resuming_session
.and_then(|resuming| match resuming.value {
persist::ClientSessionValue::Tls13(inner) => Some(inner),
#[cfg(feature = "tls12")]
persist::ClientSessionValue::Tls12(_) => None,
});
tls13::handle_server_hello(
self.config,
cx,
server_hello,
resuming_session,
self.server_name,
randoms,
suite,
transcript,
self.early_key_schedule,
self.hello,
// We always send a key share when TLS 1.3 is enabled.
self.offered_key_share.unwrap(),
self.sent_tls13_fake_ccs,
)
}
#[cfg(feature = "tls12")]
SupportedCipherSuite::Tls12(suite) => {
let resuming_session = self
.resuming_session
.and_then(|resuming| match resuming.value {
persist::ClientSessionValue::Tls12(inner) => Some(inner),
persist::ClientSessionValue::Tls13(_) => None,
});
tls12::CompleteServerHelloHandling {
config: self.config,
resuming_session,
server_name: self.server_name,
randoms,
using_ems: self.using_ems,
transcript,
}
.handle_server_hello(cx, suite, server_hello, tls13_supported)
}
}
}
}
impl ExpectServerHelloOrHelloRetryRequest {
fn into_expect_server_hello(self) -> NextState {
Box::new(self.next)
}
fn handle_hello_retry_request(
self,
cx: &mut ClientContext<'_>,
m: Message,
) -> NextStateOrError {
let hrr = require_handshake_msg!(
m,
HandshakeType::HelloRetryRequest,
HandshakePayload::HelloRetryRequest
)?;
trace!("Got HRR {:?}", hrr);
cx.common.check_aligned_handshake()?;
let cookie = hrr.get_cookie();
let req_group = hrr.get_requested_key_share_group();
// We always send a key share when TLS 1.3 is enabled.
let offered_key_share = self.next.offered_key_share.unwrap();
// A retry request is illegal if it contains no cookie and asks for
// retry of a group we already sent.
if cookie.is_none() && req_group == Some(offered_key_share.group()) {
return Err(cx
.common
.illegal_param("server requested hrr with our group"));
}
// Or has an empty cookie.
if let Some(cookie) = cookie {
if cookie.0.is_empty() {
return Err(cx
.common
.illegal_param("server requested hrr with empty cookie"));
}
}
// Or has something unrecognised
if hrr.has_unknown_extension() {
cx.common
.send_fatal_alert(AlertDescription::UnsupportedExtension);
return Err(Error::PeerIncompatibleError(
"server sent hrr with unhandled extension".to_string(),
));
}
// Or has the same extensions more than once
if hrr.has_duplicate_extension() {
return Err(cx
.common
.illegal_param("server send duplicate hrr extensions"));
}
// Or asks us to change nothing.
if cookie.is_none() && req_group.is_none() {
return Err(cx
.common
.illegal_param("server requested hrr with no changes"));
}
// Or asks us to talk a protocol we didn't offer, or doesn't support HRR at all.
match hrr.get_supported_versions() {
Some(ProtocolVersion::TLSv1_3) => {
cx.common.negotiated_version = Some(ProtocolVersion::TLSv1_3);
}
_ => {
return Err(cx
.common
.illegal_param("server requested unsupported version in hrr"));
}
}
// Or asks us to use a ciphersuite we didn't offer.
let maybe_cs = self
.next
.config
.find_cipher_suite(hrr.cipher_suite);
let cs = match maybe_cs {
Some(cs) => cs,
None => {
return Err(cx
.common
.illegal_param("server requested unsupported cs in hrr"));
}
};
// HRR selects the ciphersuite.
cx.common.suite = Some(cs);
// This is the draft19 change where the transcript became a tree
let transcript = self
.next
.transcript_buffer
.start_hash(cs.hash_algorithm());
let mut transcript_buffer = transcript.into_hrr_buffer();
transcript_buffer.add_message(&m);
// Early data is not allowed after HelloRetryrequest
if cx.data.early_data.is_enabled() {
cx.data.early_data.rejected();
}
let may_send_sct_list = self
.next
.hello
.server_may_send_sct_list();
let key_share = match req_group {
Some(group) if group != offered_key_share.group() => {
let group = kx::KeyExchange::choose(group, &self.next.config.kx_groups)
.ok_or_else(|| {
cx.common
.illegal_param("server requested hrr with bad group")
})?;
kx::KeyExchange::start(group).ok_or(Error::FailedToGetRandomBytes)?
}
_ => offered_key_share,
};
Ok(emit_client_hello_for_retry(
self.next.config,
cx,
self.next.resuming_session,
self.next.random,
self.next.using_ems,
transcript_buffer,
self.next.sent_tls13_fake_ccs,
self.next.hello,
Some(self.next.session_id),
ClientSessionIdGenerators::default(),
Some(hrr),
self.next.server_name,
Some(key_share),
self.extra_exts,
may_send_sct_list,
Some(cs),
))
}
}
impl State<ClientConnectionData> for ExpectServerHelloOrHelloRetryRequest {
fn handle(self: Box<Self>, cx: &mut ClientContext<'_>, m: Message) -> NextStateOrError {
match m.payload {
MessagePayload::Handshake {
parsed:
HandshakeMessagePayload {
payload: HandshakePayload::ServerHello(..),
..
},
..
} => self
.into_expect_server_hello()
.handle(cx, m),
MessagePayload::Handshake {
parsed:
HandshakeMessagePayload {
payload: HandshakePayload::HelloRetryRequest(..),
..
},
..
} => self.handle_hello_retry_request(cx, m),
payload => Err(inappropriate_handshake_message(
&payload,
&[ContentType::Handshake],
&[HandshakeType::ServerHello, HandshakeType::HelloRetryRequest],
)),
}
}
}
pub(super) fn send_cert_error_alert(common: &mut CommonState, err: Error) -> Error {
match err {
Error::InvalidCertificateEncoding => {
common.send_fatal_alert(AlertDescription::DecodeError);
}
Error::PeerMisbehavedError(_) => {
common.send_fatal_alert(AlertDescription::IllegalParameter);
}
_ => {
common.send_fatal_alert(AlertDescription::BadCertificate);
}
};
err
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,431 @@
#![allow(non_camel_case_types)]
#![allow(missing_docs)]
use crate::msgs::codec::{Codec, Reader};
enum_builder! {
/// The `ProtocolVersion` TLS protocol enum. Values in this enum are taken
/// from the various RFCs covering TLS, and are listed by IANA.
/// The `Unknown` item is used when processing unrecognised ordinals.
@U16
EnumName: ProtocolVersion;
EnumVal{
SSLv2 => 0x0200,
SSLv3 => 0x0300,
TLSv1_0 => 0x0301,
TLSv1_1 => 0x0302,
TLSv1_2 => 0x0303,
TLSv1_3 => 0x0304,
DTLSv1_0 => 0xFEFF,
DTLSv1_2 => 0xFEFD,
DTLSv1_3 => 0xFEFC
}
}
enum_builder! {
/// The `CipherSuite` TLS protocol enum. Values in this enum are taken
/// from the various RFCs covering TLS, and are listed by IANA.
/// The `Unknown` item is used when processing unrecognised ordinals.
@U16
EnumName: CipherSuite;
EnumVal{
TLS_NULL_WITH_NULL_NULL => 0x0000,
TLS_RSA_WITH_NULL_MD5 => 0x0001,
TLS_RSA_WITH_NULL_SHA => 0x0002,
TLS_RSA_EXPORT_WITH_RC4_40_MD5 => 0x0003,
TLS_RSA_WITH_RC4_128_MD5 => 0x0004,
TLS_RSA_WITH_RC4_128_SHA => 0x0005,
TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5 => 0x0006,
TLS_RSA_WITH_IDEA_CBC_SHA => 0x0007,
TLS_RSA_EXPORT_WITH_DES40_CBC_SHA => 0x0008,
TLS_RSA_WITH_DES_CBC_SHA => 0x0009,
TLS_RSA_WITH_3DES_EDE_CBC_SHA => 0x000a,
TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA => 0x000b,
TLS_DH_DSS_WITH_DES_CBC_SHA => 0x000c,
TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA => 0x000d,
TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA => 0x000e,
TLS_DH_RSA_WITH_DES_CBC_SHA => 0x000f,
TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA => 0x0010,
TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA => 0x0011,
TLS_DHE_DSS_WITH_DES_CBC_SHA => 0x0012,
TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA => 0x0013,
TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA => 0x0014,
TLS_DHE_RSA_WITH_DES_CBC_SHA => 0x0015,
TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA => 0x0016,
TLS_DH_anon_EXPORT_WITH_RC4_40_MD5 => 0x0017,
TLS_DH_anon_WITH_RC4_128_MD5 => 0x0018,
TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA => 0x0019,
TLS_DH_anon_WITH_DES_CBC_SHA => 0x001a,
TLS_DH_anon_WITH_3DES_EDE_CBC_SHA => 0x001b,
SSL_FORTEZZA_KEA_WITH_NULL_SHA => 0x001c,
SSL_FORTEZZA_KEA_WITH_FORTEZZA_CBC_SHA => 0x001d,
TLS_KRB5_WITH_DES_CBC_SHA_or_SSL_FORTEZZA_KEA_WITH_RC4_128_SHA => 0x001e,
TLS_KRB5_WITH_3DES_EDE_CBC_SHA => 0x001f,
TLS_KRB5_WITH_RC4_128_SHA => 0x0020,
TLS_KRB5_WITH_IDEA_CBC_SHA => 0x0021,
TLS_KRB5_WITH_DES_CBC_MD5 => 0x0022,
TLS_KRB5_WITH_3DES_EDE_CBC_MD5 => 0x0023,
TLS_KRB5_WITH_RC4_128_MD5 => 0x0024,
TLS_KRB5_WITH_IDEA_CBC_MD5 => 0x0025,
TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA => 0x0026,
TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA => 0x0027,
TLS_KRB5_EXPORT_WITH_RC4_40_SHA => 0x0028,
TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5 => 0x0029,
TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5 => 0x002a,
TLS_KRB5_EXPORT_WITH_RC4_40_MD5 => 0x002b,
TLS_PSK_WITH_NULL_SHA => 0x002c,
TLS_DHE_PSK_WITH_NULL_SHA => 0x002d,
TLS_RSA_PSK_WITH_NULL_SHA => 0x002e,
TLS_RSA_WITH_AES_128_CBC_SHA => 0x002f,
TLS_DH_DSS_WITH_AES_128_CBC_SHA => 0x0030,
TLS_DH_RSA_WITH_AES_128_CBC_SHA => 0x0031,
TLS_DHE_DSS_WITH_AES_128_CBC_SHA => 0x0032,
TLS_DHE_RSA_WITH_AES_128_CBC_SHA => 0x0033,
TLS_DH_anon_WITH_AES_128_CBC_SHA => 0x0034,
TLS_RSA_WITH_AES_256_CBC_SHA => 0x0035,
TLS_DH_DSS_WITH_AES_256_CBC_SHA => 0x0036,
TLS_DH_RSA_WITH_AES_256_CBC_SHA => 0x0037,
TLS_DHE_DSS_WITH_AES_256_CBC_SHA => 0x0038,
TLS_DHE_RSA_WITH_AES_256_CBC_SHA => 0x0039,
TLS_DH_anon_WITH_AES_256_CBC_SHA => 0x003a,
TLS_RSA_WITH_NULL_SHA256 => 0x003b,
TLS_RSA_WITH_AES_128_CBC_SHA256 => 0x003c,
TLS_RSA_WITH_AES_256_CBC_SHA256 => 0x003d,
TLS_DH_DSS_WITH_AES_128_CBC_SHA256 => 0x003e,
TLS_DH_RSA_WITH_AES_128_CBC_SHA256 => 0x003f,
TLS_DHE_DSS_WITH_AES_128_CBC_SHA256 => 0x0040,
TLS_RSA_WITH_CAMELLIA_128_CBC_SHA => 0x0041,
TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA => 0x0042,
TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA => 0x0043,
TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA => 0x0044,
TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA => 0x0045,
TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA => 0x0046,
TLS_ECDH_ECDSA_WITH_NULL_SHA_draft => 0x0047,
TLS_ECDH_ECDSA_WITH_RC4_128_SHA_draft => 0x0048,
TLS_ECDH_ECDSA_WITH_DES_CBC_SHA_draft => 0x0049,
TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA_draft => 0x004a,
TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA_draft => 0x004b,
TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA_draft => 0x004c,
TLS_ECDH_ECNRA_WITH_DES_CBC_SHA_draft => 0x004d,
TLS_ECDH_ECNRA_WITH_3DES_EDE_CBC_SHA_draft => 0x004e,
TLS_ECMQV_ECDSA_NULL_SHA_draft => 0x004f,
TLS_ECMQV_ECDSA_WITH_RC4_128_SHA_draft => 0x0050,
TLS_ECMQV_ECDSA_WITH_DES_CBC_SHA_draft => 0x0051,
TLS_ECMQV_ECDSA_WITH_3DES_EDE_CBC_SHA_draft => 0x0052,
TLS_ECMQV_ECNRA_NULL_SHA_draft => 0x0053,
TLS_ECMQV_ECNRA_WITH_RC4_128_SHA_draft => 0x0054,
TLS_ECMQV_ECNRA_WITH_DES_CBC_SHA_draft => 0x0055,
TLS_ECMQV_ECNRA_WITH_3DES_EDE_CBC_SHA_draft => 0x0056,
TLS_ECDH_anon_NULL_WITH_SHA_draft => 0x0057,
TLS_ECDH_anon_WITH_RC4_128_SHA_draft => 0x0058,
TLS_ECDH_anon_WITH_DES_CBC_SHA_draft => 0x0059,
TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA_draft => 0x005a,
TLS_ECDH_anon_EXPORT_WITH_DES40_CBC_SHA_draft => 0x005b,
TLS_ECDH_anon_EXPORT_WITH_RC4_40_SHA_draft => 0x005c,
TLS_RSA_EXPORT1024_WITH_RC4_56_MD5 => 0x0060,
TLS_RSA_EXPORT1024_WITH_RC2_CBC_56_MD5 => 0x0061,
TLS_RSA_EXPORT1024_WITH_DES_CBC_SHA => 0x0062,
TLS_DHE_DSS_EXPORT1024_WITH_DES_CBC_SHA => 0x0063,
TLS_RSA_EXPORT1024_WITH_RC4_56_SHA => 0x0064,
TLS_DHE_DSS_EXPORT1024_WITH_RC4_56_SHA => 0x0065,
TLS_DHE_DSS_WITH_RC4_128_SHA => 0x0066,
TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 => 0x0067,
TLS_DH_DSS_WITH_AES_256_CBC_SHA256 => 0x0068,
TLS_DH_RSA_WITH_AES_256_CBC_SHA256 => 0x0069,
TLS_DHE_DSS_WITH_AES_256_CBC_SHA256 => 0x006a,
TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 => 0x006b,
TLS_DH_anon_WITH_AES_128_CBC_SHA256 => 0x006c,
TLS_DH_anon_WITH_AES_256_CBC_SHA256 => 0x006d,
TLS_DHE_DSS_WITH_3DES_EDE_CBC_RMD => 0x0072,
TLS_DHE_DSS_WITH_AES_128_CBC_RMD => 0x0073,
TLS_DHE_DSS_WITH_AES_256_CBC_RMD => 0x0074,
TLS_DHE_RSA_WITH_3DES_EDE_CBC_RMD => 0x0077,
TLS_DHE_RSA_WITH_AES_128_CBC_RMD => 0x0078,
TLS_DHE_RSA_WITH_AES_256_CBC_RMD => 0x0079,
TLS_RSA_WITH_3DES_EDE_CBC_RMD => 0x007c,
TLS_RSA_WITH_AES_128_CBC_RMD => 0x007d,
TLS_RSA_WITH_AES_256_CBC_RMD => 0x007e,
TLS_GOSTR341094_WITH_28147_CNT_IMIT => 0x0080,
TLS_GOSTR341001_WITH_28147_CNT_IMIT => 0x0081,
TLS_GOSTR341094_WITH_NULL_GOSTR3411 => 0x0082,
TLS_GOSTR341001_WITH_NULL_GOSTR3411 => 0x0083,
TLS_RSA_WITH_CAMELLIA_256_CBC_SHA => 0x0084,
TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA => 0x0085,
TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA => 0x0086,
TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA => 0x0087,
TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA => 0x0088,
TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA => 0x0089,
TLS_PSK_WITH_RC4_128_SHA => 0x008a,
TLS_PSK_WITH_3DES_EDE_CBC_SHA => 0x008b,
TLS_PSK_WITH_AES_128_CBC_SHA => 0x008c,
TLS_PSK_WITH_AES_256_CBC_SHA => 0x008d,
TLS_DHE_PSK_WITH_RC4_128_SHA => 0x008e,
TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA => 0x008f,
TLS_DHE_PSK_WITH_AES_128_CBC_SHA => 0x0090,
TLS_DHE_PSK_WITH_AES_256_CBC_SHA => 0x0091,
TLS_RSA_PSK_WITH_RC4_128_SHA => 0x0092,
TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA => 0x0093,
TLS_RSA_PSK_WITH_AES_128_CBC_SHA => 0x0094,
TLS_RSA_PSK_WITH_AES_256_CBC_SHA => 0x0095,
TLS_RSA_WITH_SEED_CBC_SHA => 0x0096,
TLS_DH_DSS_WITH_SEED_CBC_SHA => 0x0097,
TLS_DH_RSA_WITH_SEED_CBC_SHA => 0x0098,
TLS_DHE_DSS_WITH_SEED_CBC_SHA => 0x0099,
TLS_DHE_RSA_WITH_SEED_CBC_SHA => 0x009a,
TLS_DH_anon_WITH_SEED_CBC_SHA => 0x009b,
TLS_RSA_WITH_AES_128_GCM_SHA256 => 0x009c,
TLS_RSA_WITH_AES_256_GCM_SHA384 => 0x009d,
TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 => 0x009e,
TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 => 0x009f,
TLS_DH_RSA_WITH_AES_128_GCM_SHA256 => 0x00a0,
TLS_DH_RSA_WITH_AES_256_GCM_SHA384 => 0x00a1,
TLS_DHE_DSS_WITH_AES_128_GCM_SHA256 => 0x00a2,
TLS_DHE_DSS_WITH_AES_256_GCM_SHA384 => 0x00a3,
TLS_DH_DSS_WITH_AES_128_GCM_SHA256 => 0x00a4,
TLS_DH_DSS_WITH_AES_256_GCM_SHA384 => 0x00a5,
TLS_DH_anon_WITH_AES_128_GCM_SHA256 => 0x00a6,
TLS_DH_anon_WITH_AES_256_GCM_SHA384 => 0x00a7,
TLS_PSK_WITH_AES_128_GCM_SHA256 => 0x00a8,
TLS_PSK_WITH_AES_256_GCM_SHA384 => 0x00a9,
TLS_DHE_PSK_WITH_AES_128_GCM_SHA256 => 0x00aa,
TLS_DHE_PSK_WITH_AES_256_GCM_SHA384 => 0x00ab,
TLS_RSA_PSK_WITH_AES_128_GCM_SHA256 => 0x00ac,
TLS_RSA_PSK_WITH_AES_256_GCM_SHA384 => 0x00ad,
TLS_PSK_WITH_AES_128_CBC_SHA256 => 0x00ae,
TLS_PSK_WITH_AES_256_CBC_SHA384 => 0x00af,
TLS_PSK_WITH_NULL_SHA256 => 0x00b0,
TLS_PSK_WITH_NULL_SHA384 => 0x00b1,
TLS_DHE_PSK_WITH_AES_128_CBC_SHA256 => 0x00b2,
TLS_DHE_PSK_WITH_AES_256_CBC_SHA384 => 0x00b3,
TLS_DHE_PSK_WITH_NULL_SHA256 => 0x00b4,
TLS_DHE_PSK_WITH_NULL_SHA384 => 0x00b5,
TLS_RSA_PSK_WITH_AES_128_CBC_SHA256 => 0x00b6,
TLS_RSA_PSK_WITH_AES_256_CBC_SHA384 => 0x00b7,
TLS_RSA_PSK_WITH_NULL_SHA256 => 0x00b8,
TLS_RSA_PSK_WITH_NULL_SHA384 => 0x00b9,
TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256 => 0x00ba,
TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256 => 0x00bb,
TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256 => 0x00bc,
TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256 => 0x00bd,
TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256 => 0x00be,
TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256 => 0x00bf,
TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256 => 0x00c0,
TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256 => 0x00c1,
TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256 => 0x00c2,
TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256 => 0x00c3,
TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256 => 0x00c4,
TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256 => 0x00c5,
TLS_EMPTY_RENEGOTIATION_INFO_SCSV => 0x00ff,
TLS13_AES_128_GCM_SHA256 => 0x1301,
TLS13_AES_256_GCM_SHA384 => 0x1302,
TLS13_CHACHA20_POLY1305_SHA256 => 0x1303,
TLS13_AES_128_CCM_SHA256 => 0x1304,
TLS13_AES_128_CCM_8_SHA256 => 0x1305,
TLS_ECDH_ECDSA_WITH_NULL_SHA => 0xc001,
TLS_ECDH_ECDSA_WITH_RC4_128_SHA => 0xc002,
TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA => 0xc003,
TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA => 0xc004,
TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA => 0xc005,
TLS_ECDHE_ECDSA_WITH_NULL_SHA => 0xc006,
TLS_ECDHE_ECDSA_WITH_RC4_128_SHA => 0xc007,
TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA => 0xc008,
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA => 0xc009,
TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA => 0xc00a,
TLS_ECDH_RSA_WITH_NULL_SHA => 0xc00b,
TLS_ECDH_RSA_WITH_RC4_128_SHA => 0xc00c,
TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA => 0xc00d,
TLS_ECDH_RSA_WITH_AES_128_CBC_SHA => 0xc00e,
TLS_ECDH_RSA_WITH_AES_256_CBC_SHA => 0xc00f,
TLS_ECDHE_RSA_WITH_NULL_SHA => 0xc010,
TLS_ECDHE_RSA_WITH_RC4_128_SHA => 0xc011,
TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA => 0xc012,
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA => 0xc013,
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA => 0xc014,
TLS_ECDH_anon_WITH_NULL_SHA => 0xc015,
TLS_ECDH_anon_WITH_RC4_128_SHA => 0xc016,
TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA => 0xc017,
TLS_ECDH_anon_WITH_AES_128_CBC_SHA => 0xc018,
TLS_ECDH_anon_WITH_AES_256_CBC_SHA => 0xc019,
TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA => 0xc01a,
TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA => 0xc01b,
TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA => 0xc01c,
TLS_SRP_SHA_WITH_AES_128_CBC_SHA => 0xc01d,
TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA => 0xc01e,
TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA => 0xc01f,
TLS_SRP_SHA_WITH_AES_256_CBC_SHA => 0xc020,
TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA => 0xc021,
TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA => 0xc022,
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 => 0xc023,
TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 => 0xc024,
TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256 => 0xc025,
TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384 => 0xc026,
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 => 0xc027,
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 => 0xc028,
TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256 => 0xc029,
TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384 => 0xc02a,
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 => 0xc02b,
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 => 0xc02c,
TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256 => 0xc02d,
TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384 => 0xc02e,
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 => 0xc02f,
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 => 0xc030,
TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256 => 0xc031,
TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384 => 0xc032,
TLS_ECDHE_PSK_WITH_RC4_128_SHA => 0xc033,
TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA => 0xc034,
TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA => 0xc035,
TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA => 0xc036,
TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256 => 0xc037,
TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384 => 0xc038,
TLS_ECDHE_PSK_WITH_NULL_SHA => 0xc039,
TLS_ECDHE_PSK_WITH_NULL_SHA256 => 0xc03a,
TLS_ECDHE_PSK_WITH_NULL_SHA384 => 0xc03b,
TLS_RSA_WITH_ARIA_128_CBC_SHA256 => 0xc03c,
TLS_RSA_WITH_ARIA_256_CBC_SHA384 => 0xc03d,
TLS_DH_DSS_WITH_ARIA_128_CBC_SHA256 => 0xc03e,
TLS_DH_DSS_WITH_ARIA_256_CBC_SHA384 => 0xc03f,
TLS_DH_RSA_WITH_ARIA_128_CBC_SHA256 => 0xc040,
TLS_DH_RSA_WITH_ARIA_256_CBC_SHA384 => 0xc041,
TLS_DHE_DSS_WITH_ARIA_128_CBC_SHA256 => 0xc042,
TLS_DHE_DSS_WITH_ARIA_256_CBC_SHA384 => 0xc043,
TLS_DHE_RSA_WITH_ARIA_128_CBC_SHA256 => 0xc044,
TLS_DHE_RSA_WITH_ARIA_256_CBC_SHA384 => 0xc045,
TLS_DH_anon_WITH_ARIA_128_CBC_SHA256 => 0xc046,
TLS_DH_anon_WITH_ARIA_256_CBC_SHA384 => 0xc047,
TLS_ECDHE_ECDSA_WITH_ARIA_128_CBC_SHA256 => 0xc048,
TLS_ECDHE_ECDSA_WITH_ARIA_256_CBC_SHA384 => 0xc049,
TLS_ECDH_ECDSA_WITH_ARIA_128_CBC_SHA256 => 0xc04a,
TLS_ECDH_ECDSA_WITH_ARIA_256_CBC_SHA384 => 0xc04b,
TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256 => 0xc04c,
TLS_ECDHE_RSA_WITH_ARIA_256_CBC_SHA384 => 0xc04d,
TLS_ECDH_RSA_WITH_ARIA_128_CBC_SHA256 => 0xc04e,
TLS_ECDH_RSA_WITH_ARIA_256_CBC_SHA384 => 0xc04f,
TLS_RSA_WITH_ARIA_128_GCM_SHA256 => 0xc050,
TLS_RSA_WITH_ARIA_256_GCM_SHA384 => 0xc051,
TLS_DHE_RSA_WITH_ARIA_128_GCM_SHA256 => 0xc052,
TLS_DHE_RSA_WITH_ARIA_256_GCM_SHA384 => 0xc053,
TLS_DH_RSA_WITH_ARIA_128_GCM_SHA256 => 0xc054,
TLS_DH_RSA_WITH_ARIA_256_GCM_SHA384 => 0xc055,
TLS_DHE_DSS_WITH_ARIA_128_GCM_SHA256 => 0xc056,
TLS_DHE_DSS_WITH_ARIA_256_GCM_SHA384 => 0xc057,
TLS_DH_DSS_WITH_ARIA_128_GCM_SHA256 => 0xc058,
TLS_DH_DSS_WITH_ARIA_256_GCM_SHA384 => 0xc059,
TLS_DH_anon_WITH_ARIA_128_GCM_SHA256 => 0xc05a,
TLS_DH_anon_WITH_ARIA_256_GCM_SHA384 => 0xc05b,
TLS_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256 => 0xc05c,
TLS_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384 => 0xc05d,
TLS_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256 => 0xc05e,
TLS_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384 => 0xc05f,
TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256 => 0xc060,
TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384 => 0xc061,
TLS_ECDH_RSA_WITH_ARIA_128_GCM_SHA256 => 0xc062,
TLS_ECDH_RSA_WITH_ARIA_256_GCM_SHA384 => 0xc063,
TLS_PSK_WITH_ARIA_128_CBC_SHA256 => 0xc064,
TLS_PSK_WITH_ARIA_256_CBC_SHA384 => 0xc065,
TLS_DHE_PSK_WITH_ARIA_128_CBC_SHA256 => 0xc066,
TLS_DHE_PSK_WITH_ARIA_256_CBC_SHA384 => 0xc067,
TLS_RSA_PSK_WITH_ARIA_128_CBC_SHA256 => 0xc068,
TLS_RSA_PSK_WITH_ARIA_256_CBC_SHA384 => 0xc069,
TLS_PSK_WITH_ARIA_128_GCM_SHA256 => 0xc06a,
TLS_PSK_WITH_ARIA_256_GCM_SHA384 => 0xc06b,
TLS_DHE_PSK_WITH_ARIA_128_GCM_SHA256 => 0xc06c,
TLS_DHE_PSK_WITH_ARIA_256_GCM_SHA384 => 0xc06d,
TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256 => 0xc06e,
TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384 => 0xc06f,
TLS_ECDHE_PSK_WITH_ARIA_128_CBC_SHA256 => 0xc070,
TLS_ECDHE_PSK_WITH_ARIA_256_CBC_SHA384 => 0xc071,
TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256 => 0xc072,
TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384 => 0xc073,
TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256 => 0xc074,
TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384 => 0xc075,
TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256 => 0xc076,
TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384 => 0xc077,
TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256 => 0xc078,
TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384 => 0xc079,
TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256 => 0xc07a,
TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384 => 0xc07b,
TLS_DHE_RSA_WITH_CAMELLIA_128_GCM_SHA256 => 0xc07c,
TLS_DHE_RSA_WITH_CAMELLIA_256_GCM_SHA384 => 0xc07d,
TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256 => 0xc07e,
TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384 => 0xc07f,
TLS_DHE_DSS_WITH_CAMELLIA_128_GCM_SHA256 => 0xc080,
TLS_DHE_DSS_WITH_CAMELLIA_256_GCM_SHA384 => 0xc081,
TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256 => 0xc082,
TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384 => 0xc083,
TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256 => 0xc084,
TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384 => 0xc085,
TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256 => 0xc086,
TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384 => 0xc087,
TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256 => 0xc088,
TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384 => 0xc089,
TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256 => 0xc08a,
TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384 => 0xc08b,
TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256 => 0xc08c,
TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384 => 0xc08d,
TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256 => 0xc08e,
TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384 => 0xc08f,
TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256 => 0xc090,
TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384 => 0xc091,
TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256 => 0xc092,
TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384 => 0xc093,
TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256 => 0xc094,
TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384 => 0xc095,
TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256 => 0xc096,
TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384 => 0xc097,
TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256 => 0xc098,
TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384 => 0xc099,
TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256 => 0xc09a,
TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384 => 0xc09b,
TLS_RSA_WITH_AES_128_CCM => 0xc09c,
TLS_RSA_WITH_AES_256_CCM => 0xc09d,
TLS_DHE_RSA_WITH_AES_128_CCM => 0xc09e,
TLS_DHE_RSA_WITH_AES_256_CCM => 0xc09f,
TLS_RSA_WITH_AES_128_CCM_8 => 0xc0a0,
TLS_RSA_WITH_AES_256_CCM_8 => 0xc0a1,
TLS_DHE_RSA_WITH_AES_128_CCM_8 => 0xc0a2,
TLS_DHE_RSA_WITH_AES_256_CCM_8 => 0xc0a3,
TLS_PSK_WITH_AES_128_CCM => 0xc0a4,
TLS_PSK_WITH_AES_256_CCM => 0xc0a5,
TLS_DHE_PSK_WITH_AES_128_CCM => 0xc0a6,
TLS_DHE_PSK_WITH_AES_256_CCM => 0xc0a7,
TLS_PSK_WITH_AES_128_CCM_8 => 0xc0a8,
TLS_PSK_WITH_AES_256_CCM_8 => 0xc0a9,
TLS_PSK_DHE_WITH_AES_128_CCM_8 => 0xc0aa,
TLS_PSK_DHE_WITH_AES_256_CCM_8 => 0xc0ab,
TLS_ECDHE_ECDSA_WITH_AES_128_CCM => 0xc0ac,
TLS_ECDHE_ECDSA_WITH_AES_256_CCM => 0xc0ad,
TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8 => 0xc0ae,
TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8 => 0xc0af,
TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 => 0xcca8,
TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 => 0xcca9,
TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256 => 0xccaa,
TLS_PSK_WITH_CHACHA20_POLY1305_SHA256 => 0xccab,
TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256 => 0xccac,
TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256 => 0xccad,
TLS_RSA_PSK_WITH_CHACHA20_POLY1305_SHA256 => 0xccae,
SSL_RSA_FIPS_WITH_DES_CBC_SHA => 0xfefe,
SSL_RSA_FIPS_WITH_3DES_EDE_CBC_SHA => 0xfeff
}
}
enum_builder! {
/// The `SignatureScheme` TLS protocol enum. Values in this enum are taken
/// from the various RFCs covering TLS, and are listed by IANA.
/// The `Unknown` item is used when processing unrecognised ordinals.
@U16
EnumName: SignatureScheme;
EnumVal{
RSA_PKCS1_SHA1 => 0x0201,
ECDSA_SHA1_Legacy => 0x0203,
RSA_PKCS1_SHA256 => 0x0401,
ECDSA_NISTP256_SHA256 => 0x0403,
RSA_PKCS1_SHA384 => 0x0501,
ECDSA_NISTP384_SHA384 => 0x0503,
RSA_PKCS1_SHA512 => 0x0601,
ECDSA_NISTP521_SHA512 => 0x0603,
RSA_PSS_SHA256 => 0x0804,
RSA_PSS_SHA384 => 0x0805,
RSA_PSS_SHA512 => 0x0806,
ED25519 => 0x0807,
ED448 => 0x0808
}
}

View file

@ -0,0 +1,245 @@
use crate::msgs::enums::{AlertDescription, ContentType, HandshakeType};
use crate::rand;
use std::error::Error as StdError;
use std::fmt;
use std::time::SystemTimeError;
/// rustls reports protocol errors using this type.
#[derive(Debug, PartialEq, Clone)]
pub enum Error {
/// We received a TLS message that isn't valid right now.
/// `expect_types` lists the message types we can expect right now.
/// `got_type` is the type we found. This error is typically
/// caused by a buggy TLS stack (the peer or this one), a broken
/// network, or an attack.
InappropriateMessage {
/// Which types we expected
expect_types: Vec<ContentType>,
/// What type we received
got_type: ContentType,
},
/// We received a TLS handshake message that isn't valid right now.
/// `expect_types` lists the handshake message types we can expect
/// right now. `got_type` is the type we found.
InappropriateHandshakeMessage {
/// Which handshake type we expected
expect_types: Vec<HandshakeType>,
/// What handshake type we received
got_type: HandshakeType,
},
/// The peer sent us a syntactically incorrect TLS message.
CorruptMessage,
/// The peer sent us a TLS message with invalid contents.
CorruptMessagePayload(ContentType),
/// The peer didn't give us any certificates.
NoCertificatesPresented,
/// The certificate verifier doesn't support the given type of name.
UnsupportedNameType,
/// We couldn't decrypt a message. This is invariably fatal.
DecryptError,
/// We couldn't encrypt a message because it was larger than the allowed message size.
/// This should never happen if the application is using valid record sizes.
EncryptError,
/// The peer doesn't support a protocol version/feature we require.
/// The parameter gives a hint as to what version/feature it is.
PeerIncompatibleError(String),
/// The peer deviated from the standard TLS protocol.
/// The parameter gives a hint where.
PeerMisbehavedError(String),
/// We received a fatal alert. This means the peer is unhappy.
AlertReceived(AlertDescription),
/// We received an invalidly encoded certificate from the peer.
InvalidCertificateEncoding,
/// We received a certificate with invalid signature type.
InvalidCertificateSignatureType,
/// We received a certificate with invalid signature.
InvalidCertificateSignature,
/// We received a certificate which includes invalid data.
InvalidCertificateData(String),
/// The presented SCT(s) were invalid.
InvalidSct(sct::Error),
/// A catch-all error for unlikely errors.
General(String),
/// We failed to figure out what time it currently is.
FailedToGetCurrentTime,
/// We failed to acquire random bytes from the system.
FailedToGetRandomBytes,
/// This function doesn't work until the TLS handshake
/// is complete.
HandshakeNotComplete,
/// The peer sent an oversized record/fragment.
PeerSentOversizedRecord,
/// An incoming connection did not support any known application protocol.
NoApplicationProtocol,
/// The `max_fragment_size` value supplied in configuration was too small,
/// or too large.
BadMaxFragmentSize,
}
fn join<T: fmt::Debug>(items: &[T]) -> String {
items
.iter()
.map(|x| format!("{:?}", x))
.collect::<Vec<String>>()
.join(" or ")
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Self::InappropriateMessage {
ref expect_types,
ref got_type,
} => write!(
f,
"received unexpected message: got {:?} when expecting {}",
got_type,
join::<ContentType>(expect_types)
),
Self::InappropriateHandshakeMessage {
ref expect_types,
ref got_type,
} => write!(
f,
"received unexpected handshake message: got {:?} when expecting {}",
got_type,
join::<HandshakeType>(expect_types)
),
Self::CorruptMessagePayload(ref typ) => {
write!(f, "received corrupt message of type {:?}", typ)
}
Self::PeerIncompatibleError(ref why) => write!(f, "peer is incompatible: {}", why),
Self::PeerMisbehavedError(ref why) => write!(f, "peer misbehaved: {}", why),
Self::AlertReceived(ref alert) => write!(f, "received fatal alert: {:?}", alert),
Self::InvalidCertificateEncoding => {
write!(f, "invalid peer certificate encoding")
}
Self::InvalidCertificateSignatureType => {
write!(f, "invalid peer certificate signature type")
}
Self::InvalidCertificateSignature => {
write!(f, "invalid peer certificate signature")
}
Self::InvalidCertificateData(ref reason) => {
write!(f, "invalid peer certificate contents: {}", reason)
}
Self::CorruptMessage => write!(f, "received corrupt message"),
Self::NoCertificatesPresented => write!(f, "peer sent no certificates"),
Self::UnsupportedNameType => write!(f, "presented server name type wasn't supported"),
Self::DecryptError => write!(f, "cannot decrypt peer's message"),
Self::EncryptError => write!(f, "cannot encrypt message"),
Self::PeerSentOversizedRecord => write!(f, "peer sent excess record size"),
Self::HandshakeNotComplete => write!(f, "handshake not complete"),
Self::NoApplicationProtocol => write!(f, "peer doesn't support any known protocol"),
Self::InvalidSct(ref err) => write!(f, "invalid certificate timestamp: {:?}", err),
Self::FailedToGetCurrentTime => write!(f, "failed to get current time"),
Self::FailedToGetRandomBytes => write!(f, "failed to get random bytes"),
Self::BadMaxFragmentSize => {
write!(f, "the supplied max_fragment_size was too small or large")
}
Self::General(ref err) => write!(f, "unexpected error: {}", err),
}
}
}
impl From<SystemTimeError> for Error {
#[inline]
fn from(_: SystemTimeError) -> Self {
Self::FailedToGetCurrentTime
}
}
impl StdError for Error {}
impl From<rand::GetRandomFailed> for Error {
fn from(_: rand::GetRandomFailed) -> Self {
Self::FailedToGetRandomBytes
}
}
#[cfg(test)]
mod tests {
use super::Error;
#[test]
fn smoke() {
use crate::msgs::enums::{AlertDescription, ContentType, HandshakeType};
use sct;
let all = vec![
Error::InappropriateMessage {
expect_types: vec![ContentType::Alert],
got_type: ContentType::Handshake,
},
Error::InappropriateHandshakeMessage {
expect_types: vec![HandshakeType::ClientHello, HandshakeType::Finished],
got_type: HandshakeType::ServerHello,
},
Error::CorruptMessage,
Error::CorruptMessagePayload(ContentType::Alert),
Error::NoCertificatesPresented,
Error::DecryptError,
Error::PeerIncompatibleError("no tls1.2".to_string()),
Error::PeerMisbehavedError("inconsistent something".to_string()),
Error::AlertReceived(AlertDescription::ExportRestriction),
Error::InvalidCertificateEncoding,
Error::InvalidCertificateSignatureType,
Error::InvalidCertificateSignature,
Error::InvalidCertificateData("Data".into()),
Error::InvalidSct(sct::Error::MalformedSct),
Error::General("undocumented error".to_string()),
Error::FailedToGetCurrentTime,
Error::FailedToGetRandomBytes,
Error::HandshakeNotComplete,
Error::PeerSentOversizedRecord,
Error::NoApplicationProtocol,
Error::BadMaxFragmentSize,
];
for err in all {
println!("{:?}:", err);
println!(" fmt '{}'", err);
}
}
#[test]
fn rand_error_mapping() {
use super::rand;
let err: Error = rand::GetRandomFailed.into();
assert_eq!(err, Error::FailedToGetRandomBytes);
}
#[test]
fn time_error_mapping() {
use std::time::SystemTime;
let time_error = SystemTime::UNIX_EPOCH
.duration_since(SystemTime::now())
.unwrap_err();
let err: Error = time_error.into();
assert_eq!(err, Error::FailedToGetCurrentTime);
}
}

View file

@ -0,0 +1,240 @@
use crate::msgs::codec::Codec;
use crate::msgs::handshake::HandshakeMessagePayload;
use crate::msgs::message::{Message, MessagePayload};
use ring::digest;
use std::mem;
/// Early stage buffering of handshake payloads.
///
/// Before we know the hash algorithm to use to verify the handshake, we just buffer the messages.
/// During the handshake, we may restart the transcript due to a HelloRetryRequest, reverting
/// from the `HandshakeHash` to a `HandshakeHashBuffer` again.
pub(crate) struct HandshakeHashBuffer {
buffer: Vec<u8>,
client_auth_enabled: bool,
}
impl HandshakeHashBuffer {
pub(crate) fn new() -> Self {
Self {
buffer: Vec::new(),
client_auth_enabled: false,
}
}
/// We might be doing client auth, so need to keep a full
/// log of the handshake.
pub(crate) fn set_client_auth_enabled(&mut self) {
self.client_auth_enabled = true;
}
/// Hash/buffer a handshake message.
pub(crate) fn add_message(&mut self, m: &Message) {
if let MessagePayload::Handshake { encoded, .. } = &m.payload {
self.buffer
.extend_from_slice(&encoded.0);
}
}
/// Hash or buffer a byte slice.
#[cfg(test)]
fn update_raw(&mut self, buf: &[u8]) {
self.buffer.extend_from_slice(buf);
}
/// Get the hash value if we were to hash `extra` too.
pub(crate) fn get_hash_given(
&self,
hash: &'static digest::Algorithm,
extra: &[u8],
) -> digest::Digest {
let mut ctx = digest::Context::new(hash);
ctx.update(&self.buffer);
ctx.update(extra);
ctx.finish()
}
/// We now know what hash function the verify_data will use.
pub(crate) fn start_hash(self, alg: &'static digest::Algorithm) -> HandshakeHash {
let mut ctx = digest::Context::new(alg);
ctx.update(&self.buffer);
HandshakeHash {
ctx,
client_auth: match self.client_auth_enabled {
true => Some(self.buffer),
false => None,
},
}
}
}
/// This deals with keeping a running hash of the handshake
/// payloads. This is computed by buffering initially. Once
/// we know what hash function we need to use we switch to
/// incremental hashing.
///
/// For client auth, we also need to buffer all the messages.
/// This is disabled in cases where client auth is not possible.
pub(crate) struct HandshakeHash {
/// None before we know what hash function we're using
ctx: digest::Context,
/// buffer for client-auth.
client_auth: Option<Vec<u8>>,
}
impl HandshakeHash {
/// We decided not to do client auth after all, so discard
/// the transcript.
pub(crate) fn abandon_client_auth(&mut self) {
self.client_auth = None;
}
/// Hash/buffer a handshake message.
pub(crate) fn add_message(&mut self, m: &Message) -> &mut Self {
if let MessagePayload::Handshake { encoded, .. } = &m.payload {
self.update_raw(&encoded.0);
}
self
}
/// Hash or buffer a byte slice.
fn update_raw(&mut self, buf: &[u8]) -> &mut Self {
self.ctx.update(buf);
if let Some(buffer) = &mut self.client_auth {
buffer.extend_from_slice(buf);
}
self
}
/// Get the hash value if we were to hash `extra` too,
/// using hash function `hash`.
pub(crate) fn get_hash_given(&self, extra: &[u8]) -> digest::Digest {
let mut ctx = self.ctx.clone();
ctx.update(extra);
ctx.finish()
}
pub(crate) fn into_hrr_buffer(self) -> HandshakeHashBuffer {
let old_hash = self.ctx.finish();
let old_handshake_hash_msg =
HandshakeMessagePayload::build_handshake_hash(old_hash.as_ref());
HandshakeHashBuffer {
client_auth_enabled: self.client_auth.is_some(),
buffer: old_handshake_hash_msg.get_encoding(),
}
}
/// Take the current hash value, and encapsulate it in a
/// 'handshake_hash' handshake message. Start this hash
/// again, with that message at the front.
pub(crate) fn rollup_for_hrr(&mut self) {
let ctx = &mut self.ctx;
let old_ctx = mem::replace(ctx, digest::Context::new(ctx.algorithm()));
let old_hash = old_ctx.finish();
let old_handshake_hash_msg =
HandshakeMessagePayload::build_handshake_hash(old_hash.as_ref());
self.update_raw(&old_handshake_hash_msg.get_encoding());
}
/// Get the current hash value.
pub(crate) fn get_current_hash(&self) -> digest::Digest {
self.ctx.clone().finish()
}
/// Takes this object's buffer containing all handshake messages
/// so far. This method only works once; it resets the buffer
/// to empty.
#[cfg(feature = "tls12")]
pub(crate) fn take_handshake_buf(&mut self) -> Option<Vec<u8>> {
self.client_auth.take()
}
/// The digest algorithm
pub(crate) fn algorithm(&self) -> &'static digest::Algorithm {
self.ctx.algorithm()
}
}
#[cfg(test)]
mod test {
use super::HandshakeHashBuffer;
use ring::digest;
#[test]
fn hashes_correctly() {
let mut hhb = HandshakeHashBuffer::new();
hhb.update_raw(b"hello");
assert_eq!(hhb.buffer.len(), 5);
let mut hh = hhb.start_hash(&digest::SHA256);
assert!(hh.client_auth.is_none());
hh.update_raw(b"world");
let h = hh.get_current_hash();
let h = h.as_ref();
assert_eq!(h[0], 0x93);
assert_eq!(h[1], 0x6a);
assert_eq!(h[2], 0x18);
assert_eq!(h[3], 0x5c);
}
#[cfg(feature = "tls12")]
#[test]
fn buffers_correctly() {
let mut hhb = HandshakeHashBuffer::new();
hhb.set_client_auth_enabled();
hhb.update_raw(b"hello");
assert_eq!(hhb.buffer.len(), 5);
let mut hh = hhb.start_hash(&digest::SHA256);
assert_eq!(
hh.client_auth
.as_ref()
.map(|buf| buf.len()),
Some(5)
);
hh.update_raw(b"world");
assert_eq!(
hh.client_auth
.as_ref()
.map(|buf| buf.len()),
Some(10)
);
let h = hh.get_current_hash();
let h = h.as_ref();
assert_eq!(h[0], 0x93);
assert_eq!(h[1], 0x6a);
assert_eq!(h[2], 0x18);
assert_eq!(h[3], 0x5c);
let buf = hh.take_handshake_buf();
assert_eq!(Some(b"helloworld".to_vec()), buf);
}
#[test]
fn abandon() {
let mut hhb = HandshakeHashBuffer::new();
hhb.set_client_auth_enabled();
hhb.update_raw(b"hello");
assert_eq!(hhb.buffer.len(), 5);
let mut hh = hhb.start_hash(&digest::SHA256);
assert_eq!(
hh.client_auth
.as_ref()
.map(|buf| buf.len()),
Some(5)
);
hh.abandon_client_auth();
assert_eq!(hh.client_auth, None);
hh.update_raw(b"world");
assert_eq!(hh.client_auth, None);
let h = hh.get_current_hash();
let h = h.as_ref();
assert_eq!(h[0], 0x93);
assert_eq!(h[1], 0x6a);
assert_eq!(h[2], 0x18);
assert_eq!(h[3], 0x5c);
}
}

View file

@ -0,0 +1,52 @@
use std::fmt;
/// This type contains a private key by value.
///
/// The private key must be DER-encoded ASN.1 in either
/// PKCS#8 or PKCS#1 format.
///
/// The `rustls-pemfile` crate can be used to extract
/// private keys from a PEM file in these formats.
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct PrivateKey(pub Vec<u8>);
/// This type contains a single certificate by value.
///
/// The certificate must be DER-encoded X.509.
///
/// The `rustls-pemfile` crate can be used to parse a PEM file.
///
/// ## Note
///
/// If you are receiving certificates from an untrusted client or server, the contents
/// must be validated manually.
#[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct Certificate(pub Vec<u8>);
impl AsRef<[u8]> for Certificate {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl fmt::Debug for Certificate {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use super::bs_debug::BsDebug;
f.debug_tuple("Certificate")
.field(&BsDebug(&self.0))
.finish()
}
}
#[cfg(test)]
mod test {
use super::Certificate;
#[test]
fn certificate_debug() {
assert_eq!(
"Certificate(b\"ab\")",
format!("{:?}", Certificate(b"ab".to_vec()))
);
}
}

View file

@ -0,0 +1,55 @@
/// This trait represents the ability to do something useful
/// with key material, such as logging it to a file for debugging.
///
/// Naturally, secrets passed over the interface are *extremely*
/// sensitive and can break the security of past, present and
/// future sessions.
///
/// You'll likely want some interior mutability in your
/// implementation to make this useful.
///
/// See [`KeyLogFile`](crate::KeyLogFile) that implements the standard
/// `SSLKEYLOGFILE` environment variable behaviour.
pub trait KeyLog: Send + Sync {
/// Log the given `secret`. `client_random` is provided for
/// session identification. `label` describes precisely what
/// `secret` means:
///
/// - `CLIENT_RANDOM`: `secret` is the master secret for a TLSv1.2 session.
/// - `CLIENT_EARLY_TRAFFIC_SECRET`: `secret` encrypts early data
/// transmitted by a client
/// - `SERVER_HANDSHAKE_TRAFFIC_SECRET`: `secret` encrypts
/// handshake messages from the server during a TLSv1.3 handshake.
/// - `CLIENT_HANDSHAKE_TRAFFIC_SECRET`: `secret` encrypts
/// handshake messages from the client during a TLSv1.3 handshake.
/// - `SERVER_TRAFFIC_SECRET_0`: `secret` encrypts post-handshake data
/// from the server in a TLSv1.3 session.
/// - `CLIENT_TRAFFIC_SECRET_0`: `secret` encrypts post-handshake data
/// from the client in a TLSv1.3 session.
/// - `EXPORTER_SECRET`: `secret` is the post-handshake exporter secret
/// in a TLSv1.3 session.
///
/// These strings are selected to match the NSS key log format:
/// <https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format>
fn log(&self, label: &str, client_random: &[u8], secret: &[u8]);
/// Indicates whether the secret with label `label` will be logged.
///
/// If `will_log` returns true then `log` will be called with the secret.
/// Otherwise, `log` will not be called for the secret. This is a
/// performance optimization.
fn will_log(&self, _label: &str) -> bool {
true
}
}
/// KeyLog that does exactly nothing.
pub struct NoKeyLog;
impl KeyLog for NoKeyLog {
fn log(&self, _: &str, _: &[u8], _: &[u8]) {}
#[inline]
fn will_log(&self, _label: &str) -> bool {
false
}
}

View file

@ -0,0 +1,154 @@
#[cfg(feature = "logging")]
use crate::log::warn;
use crate::KeyLog;
use std::env;
use std::fs::{File, OpenOptions};
use std::io;
use std::io::Write;
use std::path::Path;
use std::sync::Mutex;
// Internal mutable state for KeyLogFile
struct KeyLogFileInner {
file: Option<File>,
buf: Vec<u8>,
}
impl KeyLogFileInner {
fn new(var: Result<String, env::VarError>) -> Self {
let path = match var {
Ok(ref s) => Path::new(s),
Err(env::VarError::NotUnicode(ref s)) => Path::new(s),
Err(env::VarError::NotPresent) => {
return Self {
file: None,
buf: Vec::new(),
};
}
};
#[cfg_attr(not(feature = "logging"), allow(unused_variables))]
let file = match OpenOptions::new()
.append(true)
.create(true)
.open(path)
{
Ok(f) => Some(f),
Err(e) => {
warn!("unable to create key log file {:?}: {}", path, e);
None
}
};
Self {
file,
buf: Vec::new(),
}
}
fn try_write(&mut self, label: &str, client_random: &[u8], secret: &[u8]) -> io::Result<()> {
let mut file = match self.file {
None => {
return Ok(());
}
Some(ref f) => f,
};
self.buf.truncate(0);
write!(self.buf, "{} ", label)?;
for b in client_random.iter() {
write!(self.buf, "{:02x}", b)?;
}
write!(self.buf, " ")?;
for b in secret.iter() {
write!(self.buf, "{:02x}", b)?;
}
writeln!(self.buf)?;
file.write_all(&self.buf)
}
}
/// [`KeyLog`] implementation that opens a file whose name is
/// given by the `SSLKEYLOGFILE` environment variable, and writes
/// keys into it.
///
/// If `SSLKEYLOGFILE` is not set, this does nothing.
///
/// If such a file cannot be opened, or cannot be written then
/// this does nothing but logs errors at warning-level.
pub struct KeyLogFile(Mutex<KeyLogFileInner>);
impl KeyLogFile {
/// Makes a new `KeyLogFile`. The environment variable is
/// inspected and the named file is opened during this call.
pub fn new() -> Self {
let var = env::var("SSLKEYLOGFILE");
Self(Mutex::new(KeyLogFileInner::new(var)))
}
}
impl KeyLog for KeyLogFile {
fn log(&self, label: &str, client_random: &[u8], secret: &[u8]) {
#[cfg_attr(not(feature = "logging"), allow(unused_variables))]
match self
.0
.lock()
.unwrap()
.try_write(label, client_random, secret)
{
Ok(()) => {}
Err(e) => {
warn!("error writing to key log file: {}", e);
}
}
}
}
#[cfg(all(test, target_os = "linux"))]
mod test {
use super::*;
fn init() {
let _ = env_logger::builder()
.is_test(true)
.try_init();
}
#[test]
fn test_env_var_is_not_unicode() {
init();
let mut inner = KeyLogFileInner::new(Err(env::VarError::NotUnicode(
"/tmp/keylogfileinnertest".into(),
)));
assert!(inner
.try_write("label", b"random", b"secret")
.is_ok());
}
#[test]
fn test_env_var_is_not_set() {
init();
let mut inner = KeyLogFileInner::new(Err(env::VarError::NotPresent));
assert!(inner
.try_write("label", b"random", b"secret")
.is_ok());
}
#[test]
fn test_env_var_cannot_be_opened() {
init();
let mut inner = KeyLogFileInner::new(Ok("/dev/does-not-exist".into()));
assert!(inner
.try_write("label", b"random", b"secret")
.is_ok());
}
#[test]
fn test_env_var_cannot_be_written() {
init();
let mut inner = KeyLogFileInner::new(Ok("/dev/full".into()));
assert!(inner
.try_write("label", b"random", b"secret")
.is_err());
}
}

View file

@ -0,0 +1,100 @@
use std::fmt;
use crate::error::Error;
use crate::msgs::enums::NamedGroup;
/// An in-progress key exchange. This has the algorithm,
/// our private key, and our public key.
pub(crate) struct KeyExchange {
skxg: &'static SupportedKxGroup,
privkey: ring::agreement::EphemeralPrivateKey,
pub(crate) pubkey: ring::agreement::PublicKey,
}
impl KeyExchange {
/// Choose a SupportedKxGroup by name, from a list of supported groups.
pub(crate) fn choose(
name: NamedGroup,
supported: &[&'static SupportedKxGroup],
) -> Option<&'static SupportedKxGroup> {
supported
.iter()
.find(|skxg| skxg.name == name)
.cloned()
}
/// Start a key exchange, using the given SupportedKxGroup.
///
/// This generates an ephemeral key pair and stores it in the returned KeyExchange object.
pub(crate) fn start(skxg: &'static SupportedKxGroup) -> Option<Self> {
let rng = ring::rand::SystemRandom::new();
let ours =
ring::agreement::EphemeralPrivateKey::generate(skxg.agreement_algorithm, &rng).ok()?;
let pubkey = ours.compute_public_key().ok()?;
Some(Self {
skxg,
privkey: ours,
pubkey,
})
}
/// Return the group being used.
pub(crate) fn group(&self) -> NamedGroup {
self.skxg.name
}
/// Completes the key exchange, given the peer's public key.
///
/// The shared secret is passed into the closure passed down in `f`, and the result of calling
/// `f` is returned to the caller.
pub(crate) fn complete<T>(
self,
peer: &[u8],
f: impl FnOnce(&[u8]) -> Result<T, ()>,
) -> Result<T, Error> {
let peer_key = ring::agreement::UnparsedPublicKey::new(self.skxg.agreement_algorithm, peer);
ring::agreement::agree_ephemeral(self.privkey, &peer_key, (), f)
.map_err(|()| Error::PeerMisbehavedError("key agreement failed".to_string()))
}
}
/// A key-exchange group supported by rustls.
///
/// All possible instances of this class are provided by the library in
/// the `ALL_KX_GROUPS` array.
pub struct SupportedKxGroup {
/// The IANA "TLS Supported Groups" name of the group
pub name: NamedGroup,
/// The corresponding ring agreement::Algorithm
agreement_algorithm: &'static ring::agreement::Algorithm,
}
impl fmt::Debug for SupportedKxGroup {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.name.fmt(f)
}
}
/// Ephemeral ECDH on curve25519 (see RFC7748)
pub static X25519: SupportedKxGroup = SupportedKxGroup {
name: NamedGroup::X25519,
agreement_algorithm: &ring::agreement::X25519,
};
/// Ephemeral ECDH on secp256r1 (aka NIST-P256)
pub static SECP256R1: SupportedKxGroup = SupportedKxGroup {
name: NamedGroup::secp256r1,
agreement_algorithm: &ring::agreement::ECDH_P256,
};
/// Ephemeral ECDH on secp384r1 (aka NIST-P384)
pub static SECP384R1: SupportedKxGroup = SupportedKxGroup {
name: NamedGroup::secp384r1,
agreement_algorithm: &ring::agreement::ECDH_P384,
};
/// A list of all the key exchange groups supported by rustls.
pub static ALL_KX_GROUPS: [&SupportedKxGroup; 3] = [&X25519, &SECP256R1, &SECP384R1];

View file

@ -0,0 +1,534 @@
//! # Rustls - a modern TLS library
//! Rustls is a TLS library that aims to provide a good level of cryptographic security,
//! requires no configuration to achieve that security, and provides no unsafe features or
//! obsolete cryptography.
//!
//! ## Current features
//!
//! * TLS1.2 and TLS1.3.
//! * ECDSA, Ed25519 or RSA server authentication by clients.
//! * ECDSA, Ed25519 or RSA server authentication by servers.
//! * Forward secrecy using ECDHE; with curve25519, nistp256 or nistp384 curves.
//! * AES128-GCM and AES256-GCM bulk encryption, with safe nonces.
//! * ChaCha20-Poly1305 bulk encryption ([RFC7905](https://tools.ietf.org/html/rfc7905)).
//! * ALPN support.
//! * SNI support.
//! * Tunable fragment size to make TLS messages match size of underlying transport.
//! * Optional use of vectored IO to minimise system calls.
//! * TLS1.2 session resumption.
//! * TLS1.2 resumption via tickets ([RFC5077](https://tools.ietf.org/html/rfc5077)).
//! * TLS1.3 resumption via tickets or session storage.
//! * TLS1.3 0-RTT data for clients.
//! * TLS1.3 0-RTT data for servers.
//! * Client authentication by clients.
//! * Client authentication by servers.
//! * Extended master secret support ([RFC7627](https://tools.ietf.org/html/rfc7627)).
//! * Exporters ([RFC5705](https://tools.ietf.org/html/rfc5705)).
//! * OCSP stapling by servers.
//! * SCT stapling by servers.
//! * SCT verification by clients.
//!
//! ## Possible future features
//!
//! * PSK support.
//! * OCSP verification by clients.
//! * Certificate pinning.
//!
//! ## Non-features
//!
//! For reasons [explained in the manual](manual),
//! rustls does not and will not support:
//!
//! * SSL1, SSL2, SSL3, TLS1 or TLS1.1.
//! * RC4.
//! * DES or triple DES.
//! * EXPORT ciphersuites.
//! * MAC-then-encrypt ciphersuites.
//! * Ciphersuites without forward secrecy.
//! * Renegotiation.
//! * Kerberos.
//! * Compression.
//! * Discrete-log Diffie-Hellman.
//! * Automatic protocol version downgrade.
//!
//! There are plenty of other libraries that provide these features should you
//! need them.
//!
//! ### Platform support
//!
//! Rustls uses [`ring`](https://crates.io/crates/ring) for implementing the
//! cryptography in TLS. As a result, rustls only runs on platforms
//! [supported by `ring`](https://github.com/briansmith/ring#online-automated-testing).
//! At the time of writing this means x86, x86-64, armv7, and aarch64.
//!
//! ## Design Overview
//! ### Rustls does not take care of network IO
//! It doesn't make or accept TCP connections, or do DNS, or read or write files.
//!
//! There's example client and server code which uses mio to do all needed network
//! IO.
//!
//! ### Rustls provides encrypted pipes
//! These are the [`ServerConnection`] and [`ClientConnection`] types. You supply raw TLS traffic
//! on the left (via the [`read_tls()`] and [`write_tls()`] methods) and then read/write the
//! plaintext on the right:
//!
//! [`read_tls()`]: Connection::read_tls
//! [`write_tls()`]: Connection::read_tls
//!
//! ```text
//! TLS Plaintext
//! === =========
//! read_tls() +-----------------------+ reader() as io::Read
//! | |
//! +---------> ClientConnection +--------->
//! | or |
//! <---------+ ServerConnection <---------+
//! | |
//! write_tls() +-----------------------+ writer() as io::Write
//! ```
//!
//! ### Rustls takes care of server certificate verification
//! You do not need to provide anything other than a set of root certificates to trust.
//! Certificate verification cannot be turned off or disabled in the main API.
//!
//! ## Getting started
//! This is the minimum you need to do to make a TLS client connection.
//!
//! First we load some root certificates. These are used to authenticate the server.
//! The recommended way is to depend on the `webpki_roots` crate which contains
//! the Mozilla set of root certificates.
//!
//! ```rust,no_run
//! let mut root_store = rustls::RootCertStore::empty();
//! root_store.add_server_trust_anchors(
//! webpki_roots::TLS_SERVER_ROOTS
//! .0
//! .iter()
//! .map(|ta| {
//! rustls::OwnedTrustAnchor::from_subject_spki_name_constraints(
//! ta.subject,
//! ta.spki,
//! ta.name_constraints,
//! )
//! })
//! );
//! ```
//!
//! Next, we make a `ClientConfig`. You're likely to make one of these per process,
//! and use it for all connections made by that process.
//!
//! ```rust,no_run
//! # let root_store: rustls::RootCertStore = panic!();
//! let config = rustls::ClientConfig::builder()
//! .with_safe_defaults()
//! .with_root_certificates(root_store)
//! .with_no_client_auth();
//! ```
//!
//! Now we can make a connection. You need to provide the server's hostname so we
//! know what to expect to find in the server's certificate.
//!
//! ```rust
//! # use rustls;
//! # use webpki;
//! # use std::sync::Arc;
//! # use std::convert::TryInto;
//! # let mut root_store = rustls::RootCertStore::empty();
//! # root_store.add_server_trust_anchors(
//! # webpki_roots::TLS_SERVER_ROOTS
//! # .0
//! # .iter()
//! # .map(|ta| {
//! # rustls::OwnedTrustAnchor::from_subject_spki_name_constraints(
//! # ta.subject,
//! # ta.spki,
//! # ta.name_constraints,
//! # )
//! # })
//! # );
//! # let config = rustls::ClientConfig::builder()
//! # .with_safe_defaults()
//! # .with_root_certificates(root_store)
//! # .with_no_client_auth();
//! let rc_config = Arc::new(config);
//! let example_com = "example.com".try_into().unwrap();
//! let mut client = rustls::ClientConnection::new(rc_config, example_com);
//! ```
//!
//! Now you should do appropriate IO for the `client` object. If `client.wants_read()` yields
//! true, you should call `client.read_tls()` when the underlying connection has data.
//! Likewise, if `client.wants_write()` yields true, you should call `client.write_tls()`
//! when the underlying connection is able to send data. You should continue doing this
//! as long as the connection is valid.
//!
//! The return types of `read_tls()` and `write_tls()` only tell you if the IO worked. No
//! parsing or processing of the TLS messages is done. After each `read_tls()` you should
//! therefore call `client.process_new_packets()` which parses and processes the messages.
//! Any error returned from `process_new_packets` is fatal to the connection, and will tell you
//! why. For example, if the server's certificate is expired `process_new_packets` will
//! return `Err(WebPkiError(CertExpired, ValidateServerCert))`. From this point on,
//! `process_new_packets` will not do any new work and will return that error continually.
//!
//! You can extract newly received data by calling `client.reader()` (which implements the
//! `io::Read` trait). You can send data to the peer by calling `client.writer()` (which
//! implements `io::Write` trait). Note that `client.writer().write()` buffers data you
//! send if the TLS connection is not yet established: this is useful for writing (say) a
//! HTTP request, but this is buffered so avoid large amounts of data.
//!
//! The following code uses a fictional socket IO API for illustration, and does not handle
//! errors.
//!
//! ```rust,no_run
//! # let mut client = rustls::ClientConnection::new(panic!(), panic!()).unwrap();
//! # struct Socket { }
//! # impl Socket {
//! # fn ready_for_write(&self) -> bool { false }
//! # fn ready_for_read(&self) -> bool { false }
//! # fn wait_for_something_to_happen(&self) { }
//! # }
//! #
//! # use std::io::{Read, Write, Result};
//! # impl Read for Socket {
//! # fn read(&mut self, buf: &mut [u8]) -> Result<usize> { panic!() }
//! # }
//! # impl Write for Socket {
//! # fn write(&mut self, buf: &[u8]) -> Result<usize> { panic!() }
//! # fn flush(&mut self) -> Result<()> { panic!() }
//! # }
//! #
//! # fn connect(_address: &str, _port: u16) -> Socket {
//! # panic!();
//! # }
//! use std::io;
//! use rustls::Connection;
//!
//! client.writer().write(b"GET / HTTP/1.0\r\n\r\n").unwrap();
//! let mut socket = connect("example.com", 443);
//! loop {
//! if client.wants_read() && socket.ready_for_read() {
//! client.read_tls(&mut socket).unwrap();
//! client.process_new_packets().unwrap();
//!
//! let mut plaintext = Vec::new();
//! client.reader().read_to_end(&mut plaintext).unwrap();
//! io::stdout().write(&plaintext).unwrap();
//! }
//!
//! if client.wants_write() && socket.ready_for_write() {
//! client.write_tls(&mut socket).unwrap();
//! }
//!
//! socket.wait_for_something_to_happen();
//! }
//! ```
//!
//! # Examples
//! [`tlsserver`](https://github.com/rustls/rustls/blob/main/examples/src/bin/tlsserver-mio.rs)
//! and [`tlsclient`](https://github.com/rustls/rustls/blob/main/examples/src/bin/tlsclient-mio.rs)
//! are full worked examples. These both use mio.
//!
//! # Crate features
//! Here's a list of what features are exposed by the rustls crate and what
//! they mean.
//!
//! - `logging`: this makes the rustls crate depend on the `log` crate.
//! rustls outputs interesting protocol-level messages at `trace!` and `debug!`
//! level, and protocol-level errors at `warn!` and `error!` level. The log
//! messages do not contain secret key data, and so are safe to archive without
//! affecting session security. This feature is in the default set.
//!
//! - `dangerous_configuration`: this feature enables a `dangerous()` method on
//! `ClientConfig` and `ServerConfig` that allows setting inadvisable options,
//! such as replacing the certificate verification process. Applications
//! requesting this feature should be reviewed carefully.
//!
//! - `quic`: this feature exposes additional constructors and functions
//! for using rustls as a TLS library for QUIC. See the `quic` module for
//! details of these. You will only need this if you're writing a QUIC
//! implementation.
//!
//! - `tls12`: enables support for TLS version 1.2. This feature is in the default
//! set. Note that, due to the additive nature of Cargo features and because it
//! is enabled by default, other crates in your dependency graph could re-enable
//! it for your application. If you want to disable TLS 1.2 for security reasons,
//! consider explicitly enabling TLS 1.3 only in the config builder API.
//!
//! - `read_buf`: When building with Rust Nightly, adds support for the unstable
//! `std::io::ReadBuf` and related APIs. This reduces costs from initializing
//! buffers. Will do nothing on non-Nightly releases.
// Require docs for public APIs, deny unsafe code, etc.
#![forbid(unsafe_code, unused_must_use)]
#![cfg_attr(not(read_buf), forbid(unstable_features))]
#![deny(
clippy::clone_on_ref_ptr,
clippy::use_self,
trivial_casts,
trivial_numeric_casts,
missing_docs,
unreachable_pub,
unused_import_braces,
unused_extern_crates
)]
// Relax these clippy lints:
// - ptr_arg: this triggers on references to type aliases that are Vec
// underneath.
// - too_many_arguments: some things just need a lot of state, wrapping it
// doesn't necessarily make it easier to follow what's going on
// - new_ret_no_self: we sometimes return `Arc<Self>`, which seems fine
// - single_component_path_imports: our top-level `use log` import causes
// a false positive, https://github.com/rust-lang/rust-clippy/issues/5210
// - new_without_default: for internal constructors, the indirection is not
// helpful
#![allow(
clippy::too_many_arguments,
clippy::new_ret_no_self,
clippy::ptr_arg,
clippy::single_component_path_imports,
clippy::new_without_default
)]
// Enable documentation for all features on docs.rs
#![cfg_attr(docsrs, feature(doc_cfg))]
// XXX: Because of https://github.com/rust-lang/rust/issues/54726, we cannot
// write `#![rustversion::attr(nightly, feature(read_buf))]` here. Instead,
// build.rs set `read_buf` for (only) Rust Nightly to get the same effect.
//
// All the other conditional logic in the crate could use
// `#[rustversion::nightly]` instead of `#[cfg(read_buf)]`; `#[cfg(read_buf)]`
// is used to avoid needing `rustversion` to be compiled twice during
// cross-compiling.
#![cfg_attr(read_buf, feature(read_buf))]
// log for logging (optional).
#[cfg(feature = "logging")]
use log;
#[cfg(not(feature = "logging"))]
#[macro_use]
mod log {
macro_rules! trace ( ($($tt:tt)*) => {{}} );
macro_rules! debug ( ($($tt:tt)*) => {{}} );
macro_rules! warn ( ($($tt:tt)*) => {{}} );
macro_rules! error ( ($($tt:tt)*) => {{}} );
}
#[macro_use]
mod msgs;
mod anchors;
mod cipher;
mod conn;
mod error;
mod hash_hs;
mod limited_cache;
mod rand;
mod record_layer;
mod stream;
#[cfg(feature = "tls12")]
mod tls12;
mod tls13;
mod vecbuf;
mod verify;
#[cfg(test)]
mod verifybench;
mod x509;
#[macro_use]
mod check;
mod bs_debug;
mod builder;
mod enums;
mod key;
mod key_log;
mod key_log_file;
mod kx;
mod suites;
mod ticketer;
mod versions;
/// Internal classes which may be useful outside the library.
/// The contents of this section DO NOT form part of the stable interface.
pub mod internal {
/// Low-level TLS message parsing and encoding functions.
pub mod msgs {
pub use crate::msgs::*;
}
/// Low-level TLS message decryption functions.
pub mod cipher {
pub use crate::cipher::MessageDecrypter;
}
}
// The public interface is:
pub use crate::anchors::{OwnedTrustAnchor, RootCertStore};
pub use crate::builder::{
ConfigBuilder, ConfigSide, WantsCipherSuites, WantsKxGroups, WantsVerifier, WantsVersions,
};
pub use crate::conn::{
CommonState, Connection, ConnectionCommon, IoState, Reader, SideData, Writer,
};
pub use crate::enums::{CipherSuite, ProtocolVersion, SignatureScheme};
pub use crate::error::Error;
pub use crate::key::{Certificate, PrivateKey};
pub use crate::key_log::{KeyLog, NoKeyLog};
pub use crate::key_log_file::KeyLogFile;
pub use crate::kx::{SupportedKxGroup, ALL_KX_GROUPS};
pub use crate::msgs::enums::{
AlertDescription, ContentType, HandshakeType, NamedGroup, SignatureAlgorithm,
};
pub use crate::msgs::handshake::{DigitallySignedStruct, DistinguishedNames};
pub use crate::stream::{Stream, StreamOwned};
pub use crate::suites::{
BulkAlgorithm, SupportedCipherSuite, ALL_CIPHER_SUITES, DEFAULT_CIPHER_SUITES,
};
#[cfg(feature = "secret_extraction")]
pub use crate::suites::{ConnectionTrafficSecrets, ExtractedSecrets};
pub use crate::ticketer::Ticketer;
#[cfg(feature = "tls12")]
pub use crate::tls12::Tls12CipherSuite;
pub use crate::tls13::Tls13CipherSuite;
pub use crate::versions::{SupportedProtocolVersion, ALL_VERSIONS, DEFAULT_VERSIONS};
/// Items for use in a client.
pub mod client {
pub(super) mod builder;
mod client_conn;
mod common;
pub(super) mod handy;
mod hs;
#[cfg(feature = "tls12")]
mod tls12;
mod tls13;
pub use builder::{WantsClientCert, WantsTransparencyPolicyOrClientCert};
#[cfg(feature = "quic")]
pub use client_conn::ClientQuicExt;
pub use client_conn::InvalidDnsNameError;
pub use client_conn::ResolvesClientCert;
pub use client_conn::ServerName;
pub use client_conn::StoresClientSessions;
pub use client_conn::{
ClientConfig, ClientConnection, ClientConnectionData, ClientSessionIdGenerator,
ClientSessionIdGenerators, ClientTls12SessionIdGenerator, WriteEarlyData,
};
pub use handy::{ClientSessionMemoryCache, NoClientSessionStorage};
#[cfg(feature = "dangerous_configuration")]
pub use crate::verify::{
CertificateTransparencyPolicy, HandshakeSignatureValid, ServerCertVerified,
ServerCertVerifier, WebPkiVerifier,
};
#[cfg(feature = "dangerous_configuration")]
pub use client_conn::danger::DangerousClientConfig;
}
pub use client::{
ClientConfig, ClientConnection, ClientSessionIdGenerator, ClientSessionIdGenerators,
ClientTls12SessionIdGenerator, ServerName,
};
/// Items for use in a server.
pub mod server {
pub(crate) mod builder;
mod common;
pub(crate) mod handy;
mod hs;
mod server_conn;
#[cfg(feature = "tls12")]
mod tls12;
mod tls13;
pub use crate::verify::{
AllowAnyAnonymousOrAuthenticatedClient, AllowAnyAuthenticatedClient, NoClientAuth,
};
pub use builder::WantsServerCert;
pub use handy::ResolvesServerCertUsingSni;
pub use handy::{NoServerSessionStorage, ServerSessionMemoryCache};
#[cfg(feature = "quic")]
pub use server_conn::ServerQuicExt;
pub use server_conn::StoresServerSessions;
pub use server_conn::{
Accepted, Acceptor, ReadEarlyData, ServerConfig, ServerConnection, ServerConnectionData,
};
pub use server_conn::{ClientHello, ProducesTickets, ResolvesServerCert};
#[cfg(feature = "dangerous_configuration")]
pub use crate::verify::{ClientCertVerified, ClientCertVerifier, DnsName};
}
pub use server::{ServerConfig, ServerConnection};
/// All defined ciphersuites appear in this module.
///
/// [`ALL_CIPHER_SUITES`] is provided as an array of all of these values.
pub mod cipher_suite {
pub use crate::suites::CipherSuiteCommon;
#[cfg(feature = "tls12")]
pub use crate::tls12::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256;
#[cfg(feature = "tls12")]
pub use crate::tls12::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384;
#[cfg(feature = "tls12")]
pub use crate::tls12::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256;
#[cfg(feature = "tls12")]
pub use crate::tls12::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256;
#[cfg(feature = "tls12")]
pub use crate::tls12::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384;
#[cfg(feature = "tls12")]
pub use crate::tls12::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256;
pub use crate::tls13::TLS13_AES_128_GCM_SHA256;
pub use crate::tls13::TLS13_AES_256_GCM_SHA384;
pub use crate::tls13::TLS13_CHACHA20_POLY1305_SHA256;
}
/// All defined protocol versions appear in this module.
///
/// ALL_VERSIONS is a provided as an array of all of these values.
pub mod version {
#[cfg(feature = "tls12")]
pub use crate::versions::TLS12;
pub use crate::versions::TLS13;
}
/// All defined key exchange groups appear in this module.
///
/// ALL_KX_GROUPS is provided as an array of all of these values.
pub mod kx_group {
pub use crate::kx::SECP256R1;
pub use crate::kx::SECP384R1;
pub use crate::kx::X25519;
}
/// Message signing interfaces and implementations.
pub mod sign;
#[cfg(feature = "quic")]
#[cfg_attr(docsrs, doc(cfg(feature = "quic")))]
/// APIs for implementing QUIC TLS
pub mod quic;
/// This is the rustls manual.
pub mod manual;
/** Type renames. */
#[allow(clippy::upper_case_acronyms)]
#[doc(hidden)]
#[deprecated(since = "0.20.0", note = "Use ResolvesServerCertUsingSni")]
pub type ResolvesServerCertUsingSNI = server::ResolvesServerCertUsingSni;
#[allow(clippy::upper_case_acronyms)]
#[cfg(feature = "dangerous_configuration")]
#[doc(hidden)]
#[deprecated(since = "0.20.0", note = "Use client::WebPkiVerifier")]
pub type WebPKIVerifier = client::WebPkiVerifier;
#[allow(clippy::upper_case_acronyms)]
#[doc(hidden)]
#[deprecated(since = "0.20.0", note = "Use Error")]
pub type TLSError = Error;
#[doc(hidden)]
#[deprecated(since = "0.20.0", note = "Use ClientConnection")]
pub type ClientSession = ClientConnection;
#[doc(hidden)]
#[deprecated(since = "0.20.0", note = "Use ServerConnection")]
pub type ServerSession = ServerConnection;
/* Apologies: would make a trait alias here, but those remain unstable.
pub trait Session = Connection;
*/

View file

@ -0,0 +1,175 @@
use std::borrow::Borrow;
use std::collections::hash_map::Entry;
use std::collections::{HashMap, VecDeque};
use std::hash::Hash;
/// A HashMap-alike, which never gets larger than a specified
/// capacity, and evicts the oldest insertion to maintain this.
///
/// The requested capacity may be rounded up by the underlying
/// collections. This implementation uses all the allocated
/// storage.
///
/// This is inefficient: it stores keys twice.
pub(crate) struct LimitedCache<K: Clone + Hash + Eq, V> {
map: HashMap<K, V>,
// first item is the oldest key
oldest: VecDeque<K>,
}
impl<K, V> LimitedCache<K, V>
where
K: Eq + Hash + Clone + std::fmt::Debug,
{
/// Create a new LimitedCache with the given rough capacity.
pub(crate) fn new(capacity_order_of_magnitude: usize) -> Self {
Self {
map: HashMap::with_capacity(capacity_order_of_magnitude),
oldest: VecDeque::with_capacity(capacity_order_of_magnitude),
}
}
pub(crate) fn insert(&mut self, k: K, v: V) {
let inserted_new_item = match self.map.entry(k) {
Entry::Occupied(mut old) => {
// nb. does not freshen entry in `oldest`
old.insert(v);
false
}
entry @ Entry::Vacant(_) => {
self.oldest
.push_back(entry.key().clone());
entry.or_insert(v);
true
}
};
// ensure next insert() does not require a realloc
if inserted_new_item && self.oldest.capacity() == self.oldest.len() {
if let Some(oldest_key) = self.oldest.pop_front() {
self.map.remove(&oldest_key);
}
}
}
pub(crate) fn get<Q: ?Sized>(&self, k: &Q) -> Option<&V>
where
K: Borrow<Q>,
Q: Hash + Eq,
{
self.map.get(k)
}
pub(crate) fn remove<Q: ?Sized>(&mut self, k: &Q) -> Option<V>
where
K: Borrow<Q>,
Q: Hash + Eq,
{
if let Some(value) = self.map.remove(k) {
// O(N) search, followed by O(N) removal
if let Some(index) = self
.oldest
.iter()
.position(|item| item.borrow() == k)
{
self.oldest.remove(index);
}
Some(value)
} else {
None
}
}
}
#[cfg(test)]
mod test {
type Test = super::LimitedCache<String, usize>;
#[test]
fn test_updates_existing_item() {
let mut t = Test::new(3);
t.insert("abc".into(), 1);
t.insert("abc".into(), 2);
assert_eq!(t.get("abc"), Some(&2));
}
#[test]
fn test_evicts_oldest_item() {
let mut t = Test::new(3);
t.insert("abc".into(), 1);
t.insert("def".into(), 2);
t.insert("ghi".into(), 3);
assert_eq!(t.get("abc"), None);
assert_eq!(t.get("def"), Some(&2));
assert_eq!(t.get("ghi"), Some(&3));
}
#[test]
fn test_evicts_second_oldest_item_if_first_removed() {
let mut t = Test::new(3);
t.insert("abc".into(), 1);
t.insert("def".into(), 2);
assert_eq!(t.remove("abc"), Some(1));
t.insert("ghi".into(), 3);
t.insert("jkl".into(), 4);
assert_eq!(t.get("abc"), None);
assert_eq!(t.get("def"), None);
assert_eq!(t.get("ghi"), Some(&3));
assert_eq!(t.get("jkl"), Some(&4));
}
#[test]
fn test_evicts_after_second_oldest_item_removed() {
let mut t = Test::new(3);
t.insert("abc".into(), 1);
t.insert("def".into(), 2);
assert_eq!(t.remove("def"), Some(2));
assert_eq!(t.get("abc"), Some(&1));
t.insert("ghi".into(), 3);
t.insert("jkl".into(), 4);
assert_eq!(t.get("abc"), None);
assert_eq!(t.get("def"), None);
assert_eq!(t.get("ghi"), Some(&3));
assert_eq!(t.get("jkl"), Some(&4));
}
#[test]
fn test_removes_all_items() {
let mut t = Test::new(3);
t.insert("abc".into(), 1);
t.insert("def".into(), 2);
assert_eq!(t.remove("def"), Some(2));
assert_eq!(t.remove("abc"), Some(1));
t.insert("ghi".into(), 3);
t.insert("jkl".into(), 4);
t.insert("mno".into(), 5);
assert_eq!(t.get("abc"), None);
assert_eq!(t.get("def"), None);
assert_eq!(t.get("ghi"), None);
assert_eq!(t.get("jkl"), Some(&4));
assert_eq!(t.get("mno"), Some(&5));
}
#[test]
fn test_inserts_many_items() {
let mut t = Test::new(3);
for _ in 0..10000 {
t.insert("abc".into(), 1);
t.insert("def".into(), 2);
t.insert("ghi".into(), 3);
}
}
}

View file

@ -0,0 +1,29 @@
/*!
## Rationale for defaults
### Why is AES-256 preferred over AES-128?
This is a trade-off between:
1. classical security level: searching a 2^128 key space is as implausible as 2^256.
2. post-quantum security level: the difference is more meaningful, and AES-256 seems like the conservative choice.
3. performance: AES-256 is around 40% slower than AES-128, though hardware acceleration typically narrows this gap.
The choice is frankly quite marginal.
### Why is AES-GCM preferred over chacha20-poly1305?
Hardware support for accelerating AES-GCM is widespread, and hardware-accelerated AES-GCM
is quicker than un-accelerated chacha20-poly1305.
However, if you know your application will run on a platform without that, you should
_definitely_ change the default order to prefer chacha20-poly1305: both the performance and
the implementation security will be improved. We think this is an uncommon case.
### Why is x25519 preferred for key exchange over nistp256?
Both provide roughly the same classical security level, but x25519 has better performance and
it's _much_ more likely that both peers will have good quality implementations.
*/

View file

@ -0,0 +1,50 @@
/*!
## Current features
* TLS1.2 and TLS1.3.
* ECDSA, Ed25519 or RSA server authentication by clients.
* ECDSA, Ed25519 or RSA server authentication by servers.
* Forward secrecy using ECDHE; with curve25519, nistp256 or nistp384 curves.
* AES128-GCM and AES256-GCM bulk encryption, with safe nonces.
* ChaCha20-Poly1305 bulk encryption ([RFC7905](https://tools.ietf.org/html/rfc7905)).
* ALPN support.
* SNI support.
* Tunable MTU to make TLS messages match size of underlying transport.
* Optional use of vectored IO to minimise system calls.
* TLS1.2 session resumption.
* TLS1.2 resumption via tickets (RFC5077).
* TLS1.3 resumption via tickets or session storage.
* TLS1.3 0-RTT data for clients.
* Client authentication by clients.
* Client authentication by servers.
* Extended master secret support (RFC7627).
* Exporters (RFC5705).
* OCSP stapling by servers.
* SCT stapling by servers.
* SCT verification by clients.
## Possible future features
* PSK support.
* OCSP verification by clients.
* Certificate pinning.
## Non-features
For reasons explained in the other sections of this manual, rustls does not
and will not support:
* SSL1, SSL2, SSL3, TLS1 or TLS1.1.
* RC4.
* DES or triple DES.
* EXPORT ciphersuites.
* MAC-then-encrypt ciphersuites.
* Ciphersuites without forward secrecy.
* Renegotiation.
* Kerberos.
* Compression.
* Discrete-log Diffie-Hellman.
* Automatic protocol version downgrade.
*/

View file

@ -0,0 +1,36 @@
/*! # Customising private key usage
By default rustls supports PKCS#8-format[^1] RSA or ECDSA keys, plus PKCS#1-format RSA keys.
However, if your private key resides in a HSM, or in another process, or perhaps
another machine, rustls has some extension points to support this:
The main trait you must implement is [`sign::SigningKey`][signing_key]. The primary method here
is [`choose_scheme`][choose_scheme] where you are given a set of [`SignatureScheme`s][sig_scheme] the client says
it supports: you must choose one (or return `None` -- this aborts the handshake). Having
done that, you return an implementation of the [`sign::Signer`][signer] trait.
The [`sign()`][sign_method] performs the signature and returns it.
(Unfortunately this is currently designed for keys with low latency access, like in a
PKCS#11 provider, Microsoft CryptoAPI, etc. so is blocking rather than asynchronous.
It's a TODO to make these and other extension points async.)
Once you have these two pieces, configuring a server to use them involves, briefly:
- packaging your `sign::SigningKey` with the matching certificate chain into a [`sign::CertifiedKey`][certified_key]
- making a [`ResolvesServerCertUsingSni`][cert_using_sni] and feeding in your `sign::CertifiedKey` for all SNI hostnames you want to use it for,
- setting that as your `ServerConfig`'s [`cert_resolver`][cert_resolver]
[signing_key]: ../../sign/trait.SigningKey.html
[choose_scheme]: ../../sign/trait.SigningKey.html#tymethod.choose_scheme
[sig_scheme]: ../../enum.SignatureScheme.html
[signer]: ../../sign/trait.Signer.html
[sign_method]: ../../sign/trait.Signer.html#tymethod.sign
[certified_key]: ../../sign/struct.CertifiedKey.html
[cert_using_sni]: ../../struct.ResolvesServerCertUsingSni.html
[cert_resolver]: ../../struct.ServerConfig.html#structfield.cert_resolver
[^1]: For PKCS#8 it does not support password encryption -- there's not a meaningful threat
model addressed by this, and the encryption supported is typically extremely poor.
*/

View file

@ -0,0 +1,104 @@
/*! # A review of TLS Implementation Vulnerabilities
An important part of engineering involves studying and learning from the mistakes of the past.
It would be tremendously unfortunate to spend effort re-discovering and re-fixing the same
vulnerabilities that were discovered in the past.
## Memory safety
Being written entirely in the safe-subset of Rust immediately offers us freedom from the entire
class of memory safety vulnerabilities. There are too many to exhaustively list, and there will
certainly be more in the future.
Examples:
- Heartbleed [CVE-2014-0160](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2014-0160) (OpenSSL)
- Memory corruption in ASN.1 decoder [CVE-2016-2108](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-2108) (OpenSSL)
- Buffer overflow in read_server_hello [CVE-2014-3466](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2014-3466) (GnuTLS)
## `goto fail`
This is the name of a vulnerability in Apple Secure Transport [CVE-2014-1266](https://nvd.nist.gov/vuln/detail/CVE-2014-1266).
This boiled down to the following code, which validates the server's signature on the key exchange:
```c
if ((err = SSLHashSHA1.update(&hashCtx, &serverRandom)) != 0)
goto fail;
if ((err = SSLHashSHA1.update(&hashCtx, &signedParams)) != 0)
goto fail;
> goto fail;
if ((err = SSLHashSHA1.final(&hashCtx, &hashOut)) != 0)
goto fail;
```
The marked line was duplicated, likely accidentally during a merge. This meant
the remaining part of the function (including the actual signature validation)
was unconditionally skipped.
Ultimately the one countermeasure to this type of bug is basic testing: that a
valid signature returns success, and that an invalid one does not. rustls
has such testing, but this is really table stakes for security code.
Further than this, though, we could consider that the *lack* of an error from
this function is a poor indicator that the signature was valid. rustls, instead,
has zero-size and non-copyable types that indicate a particular signature validation
has been performed. These types can be thought of as *capabilities* originated only
by designated signature verification functions -- such functions can then be a focus
of manual code review. Like capabilities, values of these types are otherwise unforgeable,
and are communicable only by Rust's move semantics.
Values of these types are threaded through the protocol state machine, leading to terminal
states that look like:
```ignore
struct ExpectTraffic {
(...)
_cert_verified: verify::ServerCertVerified,
_sig_verified: verify::HandshakeSignatureValid,
_fin_verified: verify::FinishedMessageVerified,
}
```
Since this state requires a value of these types, it will be a compile-time error to
reach that state without performing the requisite security-critical operations.
This approach is not infallible, but it has zero runtime cost.
## State machine attacks: EarlyCCS and SMACK/SKIP/FREAK
EarlyCCS [CVE-2014-0224](https://nvd.nist.gov/vuln/detail/CVE-2014-0224) was a vulnerability in OpenSSL
found in 2014. The TLS `ChangeCipherSpec` message would be processed at inappropriate times, leading
to data being encrypted with the wrong keys (specifically, keys which were not secret). This resulted
from OpenSSL taking a *reactive* strategy to incoming messages ("when I get a message X, I should do Y")
which allows it to diverge from the proper state machine under attacker control.
[SMACK](https://mitls.org/pages/attacks/SMACK) is a similar suite of vulnerabilities found in JSSE,
CyaSSL, OpenSSL, Mono and axTLS. "SKIP-TLS" demonstrated that some implementations allowed handshake
messages (and in one case, the entire handshake!) to be skipped leading to breaks in security. "FREAK"
found that some implementations incorrectly allowed export-only state transitions (i.e., transitions that
were only valid when an export ciphersuite was in use).
rustls represents its protocol state machine carefully to avoid these defects. We model the handshake,
CCS and application data subprotocols in the same single state machine. Each state in this machine is
represented with a single struct, and transitions are modelled as functions that consume the current state
plus one TLS message[^1] and return a struct representing the next state. These functions fully validate
the message type before further operations.
A sample sequence for a full TLSv1.2 handshake by a client looks like:
- `hs::ExpectServerHello` (nb. ClientHello is logically sent before this state); transition to `tls12::ExpectCertificate`
- `tls12::ExpectCertificate`; transition to `tls12::ExpectServerKX`
- `tls12::ExpectServerKX`; transition to `tls12::ExpectServerDoneOrCertReq`
- `tls12::ExpectServerDoneOrCertReq`; delegates to `tls12::ExpectCertificateRequest` or `tls12::ExpectServerDone` depending on incoming message.
- `tls12::ExpectServerDone`; transition to `tls12::ExpectCCS`
- `tls12::ExpectCCS`; transition to `tls12::ExpectFinished`
- `tls12::ExpectFinished`; transition to `tls12::ExpectTraffic`
- `tls12::ExpectTraffic`; terminal state; transitions to `tls12::ExpectTraffic`
In the future we plan to formally prove that all possible transitions modelled in this system of types
are correct with respect to the standard(s). At the moment we rely merely on exhaustive testing.
[^1]: a logical TLS message: post-decryption, post-fragmentation.
*/

View file

@ -0,0 +1,30 @@
/*!
This documentation primarily aims to explain design decisions taken in rustls.
It does this from a few aspects: how rustls attempts to avoid construction errors
that occurred in other TLS libraries, how rustls attempts to avoid past TLS
protocol vulnerabilities, and assorted advice for achieving common tasks with rustls.
*/
#![allow(non_snake_case)]
/// This section discusses vulnerabilities in other TLS implementations, theorising their
/// root cause and how we aim to avoid them in rustls.
#[path = "implvulns.rs"]
pub mod _01_impl_vulnerabilities;
/// This section discusses vulnerabilities and design errors in the TLS protocol.
#[path = "tlsvulns.rs"]
pub mod _02_tls_vulnerabilities;
/// This section collects together goal-oriented documentation.
#[path = "howto.rs"]
pub mod _03_howto;
/// This section documents rustls itself: what protocol features are and are not implemented.
#[path = "features.rs"]
pub mod _04_features;
/// This section provides rationale for the defaults in rustls.
#[path = "defaults.rs"]
pub mod _05_defaults;

View file

@ -0,0 +1,173 @@
/*! # A review of protocol vulnerabilities
## CBC MAC-then-encrypt ciphersuites
Back in 2000 [Bellare and Namprempre](https://eprint.iacr.org/2000/025) discussed how to make authenticated
encryption by composing separate encryption and authentication primitives. That paper included this table:
| Composition Method | Privacy || Integrity ||
|--------------------|---------||-----------||
|| IND-CPA | IND-CCA | NM-CPA | INT-PTXT | INT-CTXT |
| Encrypt-and-MAC | insecure | insecure | insecure | secure | insecure |
| MAC-then-encrypt | secure | insecure | insecure | secure | insecure |
| Encrypt-then-MAC | secure | secure | secure | secure | secure |
One may assume from this fairly clear result that encrypt-and-MAC and MAC-then-encrypt compositions would be quickly abandoned
in favour of the remaining proven-secure option. But that didn't happen, not in TLSv1.1 (2006) nor in TLSv1.2 (2008). Worse,
both RFCs included incorrect advice on countermeasures for implementers, suggesting that the flaw was "not believed to be large
enough to be exploitable".
[Lucky 13](http://www.isg.rhul.ac.uk/tls/Lucky13.html) (2013) exploited this flaw and affected all implementations, including
those written [after discovery](https://aws.amazon.com/blogs/security/s2n-and-lucky-13/). OpenSSL even had a
[memory safety vulnerability in the fix for Lucky 13](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-2107), which
gives a flavour of the kind of complexity required to remove the side channel.
rustls does not implement CBC MAC-then-encrypt ciphersuites for these reasons. TLSv1.3 removed support for these
ciphersuites in 2018.
There are some further rejected options worth mentioning: [RFC7366](https://tools.ietf.org/html/rfc7366) defines
Encrypt-then-MAC for TLS, but unfortunately cannot be negotiated without also supporting MAC-then-encrypt
(clients cannot express "I offer CBC, but only EtM and not MtE").
## RSA PKCS#1 encryption
"RSA key exchange" in TLS involves the client choosing a large random value and encrypting it using the server's
public key. This has two overall problems:
1. It provides no _forward secrecy_: later compromise of the server's private key breaks confidentiality of
*all* past sessions using that key. This is a crucial property in the presence of software that is often
[poor at keeping a secret](http://heartbleed.com/).
2. The padding used in practice in TLS ("PKCS#1", or fully "RSAES-PKCS1-v1_5") has been known to be broken since
[1998](http://archiv.infsec.ethz.ch/education/fs08/secsem/bleichenbacher98.pdf).
In a similar pattern to the MAC-then-encrypt problem discussed above, TLSv1.0 (1999), TLSv1.1 (2006) and TLSv1.2 (2008)
continued to specify use of PKCS#1 encryption, again with incrementally more complex and incorrect advice on countermeasures.
[ROBOT](https://robotattack.org/) (2018) showed that implementations were still vulnerable to these attacks twenty years later.
rustls does not support RSA key exchange. TLSv1.3 also removed support.
## BEAST
[BEAST](https://vnhacker.blogspot.com/2011/09/beast.html) ([CVE-2011-3389](https://nvd.nist.gov/vuln/detail/CVE-2011-3389))
was demonstrated in 2011 by Thai Duong and Juliano Rizzo,
and was another vulnerability in CBC-based ciphersuites in SSLv3.0 and TLSv1.0. CBC mode is vulnerable to adaptive
chosen-plaintext attacks if the IV is predictable. In the case of these protocol versions, the IV was the previous
block of ciphertext (as if the entire TLS session was one CBC ciphertext, albeit revealed incrementally). This was
obviously predictable, since it was published on the wire.
OpenSSL contained a countermeasure for this problem from 2002 onwards: it encrypts an empty message before each real
one, so that the IV used in the real message is unpredictable. This was turned off by default due to bugs in IE6.
TLSv1.1 fix this vulnerability, but not any of the other deficiencies of CBC mode (see above).
rustls does not support these ciphersuites.
## CRIME
In 2002 [John Kelsey](https://www.iacr.org/cryptodb/archive/2002/FSE/3091/3091.pdf) discussed the length side channel
as applied to compression of combined secret and attacker-chosen strings.
Compression continued to be an option in TLSv1.1 (2006) and in TLSv1.2 (2008). Support in libraries was widespread.
[CRIME](http://netifera.com/research/crime/CRIME_ekoparty2012.pdf) ([CVE-2012-4929](https://nvd.nist.gov/vuln/detail/CVE-2012-4929))
was demonstrated in 2012, again by Thai Duong and Juliano Rizzo. It attacked several protocols offering transparent
compression of application data, allowing quick adaptive chosen-plaintext attacks against secret values like cookies.
rustls does not implement compression. TLSv1.3 also removed support.
## Logjam / FREAK
Way back when SSL was first being born, circa 1995, the US government considered cryptography a munition requiring
export control. SSL contained specific ciphersuites with dramatically small key sizes that were not subject
to export control. These controls were dropped in 2000.
Since the "export-grade" ciphersuites no longer fulfilled any purpose, and because they were actively harmful to users,
one may have expected software support to disappear quickly. This did not happen.
In 2015 [the FREAK attack](https://mitls.org/pages/attacks/SMACK#freak) ([CVE-2015-0204](https://nvd.nist.gov/vuln/detail/CVE-2015-0204))
and [the Logjam attack](https://weakdh.org/) ([CVE-2015-4000](https://nvd.nist.gov/vuln/detail/CVE-2015-4000)) both
demonstrated total breaks of security in the presence of servers that accepted export ciphersuites. FREAK factored
512-bit RSA keys, while Logjam optimised solving discrete logs in the 512-bit group used by many different servers.
Naturally, rustls does not implement any of these ciphersuites.
## SWEET32
Block ciphers are vulnerable to birthday attacks, where the probability of repeating a block increases dramatically
once a particular key has been used for many blocks. For block ciphers with 64-bit blocks, this becomes probable
once a given key encrypts the order of 32GB of data.
[Sweet32](https://sweet32.info/) ([CVE-2016-2183](https://nvd.nist.gov/vuln/detail/CVE-2016-2183)) attacked this fact
in the context of TLS support for 3DES, breaking confidentiality by analysing a large amount of attacker-induced traffic
in one session.
rustls does not support any 64-bit block ciphers.
## DROWN
[DROWN](https://drownattack.com/) ([CVE-2016-0800](https://nvd.nist.gov/vuln/detail/CVE-2016-0800)) is a cross-protocol
attack that breaks the security of TLSv1.2 and earlier (when used with RSA key exchange) by using SSLv2. It is required
that the server uses the same key for both protocol versions.
rustls naturally does not support SSLv2, but most importantly does not support RSA key exchange for TLSv1.2.
## Poodle
[POODLE](https://www.openssl.org/~bodo/ssl-poodle.pdf) ([CVE-2014-3566](https://nvd.nist.gov/vuln/detail/CVE-2014-3566))
is an attack against CBC mode ciphersuites in SSLv3. This was possible in most cases because some clients willingly
downgraded to SSLv3 after failed handshakes for later versions.
rustls does not support CBC mode ciphersuites, or SSLv3. Note that rustls does not need to implement `TLS_FALLBACK_SCSV`
introduced as a countermeasure because it contains no ability to downgrade to earlier protocol versions.
## GCM nonces
[RFC5288](https://tools.ietf.org/html/rfc5288) introduced GCM-based ciphersuites for use in TLS. Unfortunately
the design was poor; it reused design for an unrelated security setting proposed in RFC5116.
GCM is a typical nonce-based AEAD: it requires a unique (but not necessarily unpredictable) 96-bit nonce for each encryption
with a given key. The design specified by RFC5288 left two-thirds of the nonce construction up to implementations:
- wasting 8 bytes per TLS ciphertext,
- meaning correct operation cannot be tested for (e.g., in protocol-level test vectors).
There were no trade-offs here: TLS has a 64-bit sequence number that is not allowed to wrap and would make an ideal nonce.
As a result, a [2016 study](https://eprint.iacr.org/2016/475.pdf) found:
- implementations from IBM, A10 and Citrix used randomly-chosen nonces, which are unlikely to be unique over long connections,
- an implementation from Radware used the same nonce for the first two messages.
rustls uses a counter from a random starting point for GCM nonces. TLSv1.3 and the Chacha20-Poly1305 TLSv1.2 ciphersuite
standardise this method.
## Renegotiation
In 2009 Marsh Ray and Steve Dispensa [discovered](https://kryptera.se/Renegotiating%20TLS.pdf) that the renegotiation
feature of all versions of TLS allows a MitM to splice a request of their choice onto the front of the client's real HTTP
request. A countermeasure was proposed and widely implemented to bind renegotiations to their previous negotiations;
unfortunately this was insufficient.
rustls does not support renegotiation in TLSv1.2. TLSv1.3 also no longer supports renegotiation.
## 3SHAKE
[3SHAKE](https://www.mitls.org/pages/attacks/3SHAKE) (2014) described a complex attack that broke the "Secure Renegotiation" extension
introduced as a countermeasure to the previous protocol flaw.
rustls does not support renegotiation for TLSv1.2 connections, or RSA key exchange, and both are required for this attack
to work. rustls implements the "Extended Master Secret" (RFC7627) extension for TLSv1.2 which was standardised as a countermeasure.
TLSv1.3 no longer supports renegotiation and RSA key exchange. It also effectively incorporates the improvements made in RFC7627.
## KCI
[This vulnerability](https://kcitls.org/) makes use of TLS ciphersuites (those offering static DH) which were standardised
yet not widely used. However, they were implemented by libraries, and as a result enabled for various clients. It coupled
this with misconfigured certificates (on services including facebook.com) which allowed their misuse to MitM connections.
rustls does not support static DH/EC-DH ciphersuites. We assert that it is misissuance to sign an EC certificate
with the keyUsage extension allowing both signatures and key exchange. That it isn't is probably a failure
of CAB Forum baseline requirements.
*/

View file

@ -0,0 +1,22 @@
use crate::msgs::codec::{Codec, Reader};
use crate::msgs::enums::{AlertDescription, AlertLevel};
#[derive(Debug)]
pub struct AlertMessagePayload {
pub level: AlertLevel,
pub description: AlertDescription,
}
impl Codec for AlertMessagePayload {
fn encode(&self, bytes: &mut Vec<u8>) {
self.level.encode(bytes);
self.description.encode(bytes);
}
fn read(r: &mut Reader) -> Option<Self> {
let level = AlertLevel::read(r)?;
let description = AlertDescription::read(r)?;
Some(Self { level, description })
}
}

View file

@ -0,0 +1,170 @@
use std::fmt;
use crate::key;
use crate::msgs::codec;
use crate::msgs::codec::{Codec, Reader};
/// An externally length'd payload
#[derive(Clone, Eq, PartialEq)]
pub struct Payload(pub Vec<u8>);
impl Codec for Payload {
fn encode(&self, bytes: &mut Vec<u8>) {
bytes.extend_from_slice(&self.0);
}
fn read(r: &mut Reader) -> Option<Self> {
Some(Self::read(r))
}
}
impl Payload {
pub fn new(bytes: impl Into<Vec<u8>>) -> Self {
Self(bytes.into())
}
pub fn empty() -> Self {
Self::new(Vec::new())
}
pub fn read(r: &mut Reader) -> Self {
Self(r.rest().to_vec())
}
}
impl Codec for key::Certificate {
fn encode(&self, bytes: &mut Vec<u8>) {
codec::u24(self.0.len() as u32).encode(bytes);
bytes.extend_from_slice(&self.0);
}
fn read(r: &mut Reader) -> Option<Self> {
let len = codec::u24::read(r)?.0 as usize;
let mut sub = r.sub(len)?;
let body = sub.rest().to_vec();
Some(Self(body))
}
}
impl fmt::Debug for Payload {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
hex(f, &self.0)
}
}
/// An arbitrary, unknown-content, u24-length-prefixed payload
#[derive(Clone, Eq, PartialEq)]
pub struct PayloadU24(pub Vec<u8>);
impl PayloadU24 {
pub fn new(bytes: Vec<u8>) -> Self {
Self(bytes)
}
}
impl Codec for PayloadU24 {
fn encode(&self, bytes: &mut Vec<u8>) {
codec::u24(self.0.len() as u32).encode(bytes);
bytes.extend_from_slice(&self.0);
}
fn read(r: &mut Reader) -> Option<Self> {
let len = codec::u24::read(r)?.0 as usize;
let mut sub = r.sub(len)?;
let body = sub.rest().to_vec();
Some(Self(body))
}
}
impl fmt::Debug for PayloadU24 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
hex(f, &self.0)
}
}
/// An arbitrary, unknown-content, u16-length-prefixed payload
#[derive(Clone, Eq, PartialEq)]
pub struct PayloadU16(pub Vec<u8>);
impl PayloadU16 {
pub fn new(bytes: Vec<u8>) -> Self {
Self(bytes)
}
pub fn empty() -> Self {
Self::new(Vec::new())
}
pub fn encode_slice(slice: &[u8], bytes: &mut Vec<u8>) {
(slice.len() as u16).encode(bytes);
bytes.extend_from_slice(slice);
}
}
impl Codec for PayloadU16 {
fn encode(&self, bytes: &mut Vec<u8>) {
Self::encode_slice(&self.0, bytes);
}
fn read(r: &mut Reader) -> Option<Self> {
let len = u16::read(r)? as usize;
let mut sub = r.sub(len)?;
let body = sub.rest().to_vec();
Some(Self(body))
}
}
impl fmt::Debug for PayloadU16 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
hex(f, &self.0)
}
}
/// An arbitrary, unknown-content, u8-length-prefixed payload
#[derive(Clone, Eq, PartialEq)]
pub struct PayloadU8(pub Vec<u8>);
impl PayloadU8 {
pub fn new(bytes: Vec<u8>) -> Self {
Self(bytes)
}
pub fn empty() -> Self {
Self(Vec::new())
}
pub fn into_inner(self) -> Vec<u8> {
self.0
}
}
impl Codec for PayloadU8 {
fn encode(&self, bytes: &mut Vec<u8>) {
(self.0.len() as u8).encode(bytes);
bytes.extend_from_slice(&self.0);
}
fn read(r: &mut Reader) -> Option<Self> {
let len = u8::read(r)? as usize;
let mut sub = r.sub(len)?;
let body = sub.rest().to_vec();
Some(Self(body))
}
}
impl fmt::Debug for PayloadU8 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
hex(f, &self.0)
}
}
// Format an iterator of u8 into a hex string
pub(super) fn hex<'a>(
f: &mut fmt::Formatter<'_>,
payload: impl IntoIterator<Item = &'a u8>,
) -> fmt::Result {
for b in payload {
write!(f, "{:02x}", b)?
}
Ok(())
}

View file

@ -0,0 +1,20 @@
use crate::msgs::codec::{Codec, Reader};
#[derive(Debug)]
pub struct ChangeCipherSpecPayload;
impl Codec for ChangeCipherSpecPayload {
fn encode(&self, bytes: &mut Vec<u8>) {
1u8.encode(bytes);
}
fn read(r: &mut Reader) -> Option<Self> {
let typ = u8::read(r)?;
if typ == 1 && !r.any_left() {
Some(Self {})
} else {
None
}
}
}

View file

@ -0,0 +1,290 @@
use std::convert::TryInto;
use std::fmt::Debug;
/// Wrapper over a slice of bytes that allows reading chunks from
/// with the current position state held using a cursor.
///
/// A new reader for a sub section of the the buffer can be created
/// using the `sub` function or a section of a certain length can
/// be obtained using the `take` function
pub struct Reader<'a> {
/// The underlying buffer storing the readers content
buffer: &'a [u8],
/// Stores the current reading position for the buffer
cursor: usize,
}
impl<'a> Reader<'a> {
/// Creates a new Reader of the provided `bytes` slice with
/// the initial cursor position of zero.
pub fn init(bytes: &[u8]) -> Reader {
Reader {
buffer: bytes,
cursor: 0,
}
}
/// Attempts to create a new Reader on a sub section of this
/// readers bytes by taking a slice of the provided `length`
/// will return None if there is not enough bytes
pub fn sub(&mut self, length: usize) -> Option<Reader> {
self.take(length).map(Reader::init)
}
/// Borrows a slice of all the remaining bytes
/// that appear after the cursor position.
///
/// Moves the cursor to the end of the buffer length.
pub fn rest(&mut self) -> &[u8] {
let rest = &self.buffer[self.cursor..];
self.cursor = self.buffer.len();
rest
}
/// Attempts to borrow a slice of bytes from the current
/// cursor position of `length` if there is not enough
/// bytes remaining after the cursor to take the length
/// then None is returned instead.
pub fn take(&mut self, length: usize) -> Option<&[u8]> {
if self.left() < length {
return None;
}
let current = self.cursor;
self.cursor += length;
Some(&self.buffer[current..current + length])
}
/// Used to check whether the reader has any content left
/// after the cursor (cursor has not reached end of buffer)
pub fn any_left(&self) -> bool {
self.cursor < self.buffer.len()
}
/// Returns the cursor position which is also the number
/// of bytes that have been read from the buffer.
pub fn used(&self) -> usize {
self.cursor
}
/// Returns the number of bytes that are still able to be
/// read (The number of remaining takes)
pub fn left(&self) -> usize {
self.buffer.len() - self.cursor
}
}
/// Trait for implementing encoding and decoding functionality
/// on something.
pub trait Codec: Debug + Sized {
/// Function for encoding itself by appending itself to
/// the provided vec of bytes.
fn encode(&self, bytes: &mut Vec<u8>);
/// Function for decoding itself from the provided reader
/// will return Some if the decoding was successful or
/// None if it was not.
fn read(_: &mut Reader) -> Option<Self>;
/// Convenience function for encoding the implementation
/// into a vec and returning it
fn get_encoding(&self) -> Vec<u8> {
let mut bytes = Vec::new();
self.encode(&mut bytes);
bytes
}
/// Function for wrapping a call to the read function in
/// a Reader for the slice of bytes provided
fn read_bytes(bytes: &[u8]) -> Option<Self> {
let mut reader = Reader::init(bytes);
Self::read(&mut reader)
}
}
fn decode_u8(bytes: &[u8]) -> Option<u8> {
let [value]: [u8; 1] = bytes.try_into().ok()?;
Some(value)
}
impl Codec for u8 {
fn encode(&self, bytes: &mut Vec<u8>) {
bytes.push(*self);
}
fn read(r: &mut Reader) -> Option<Self> {
r.take(1).and_then(decode_u8)
}
}
pub fn put_u16(v: u16, out: &mut [u8]) {
let out: &mut [u8; 2] = (&mut out[..2]).try_into().unwrap();
*out = u16::to_be_bytes(v);
}
pub fn decode_u16(bytes: &[u8]) -> Option<u16> {
Some(u16::from_be_bytes(bytes.try_into().ok()?))
}
impl Codec for u16 {
fn encode(&self, bytes: &mut Vec<u8>) {
let mut b16 = [0u8; 2];
put_u16(*self, &mut b16);
bytes.extend_from_slice(&b16);
}
fn read(r: &mut Reader) -> Option<Self> {
r.take(2).and_then(decode_u16)
}
}
// Make a distinct type for u24, even though it's a u32 underneath
#[allow(non_camel_case_types)]
#[derive(Debug, Copy, Clone)]
pub struct u24(pub u32);
impl u24 {
pub fn decode(bytes: &[u8]) -> Option<Self> {
let [a, b, c]: [u8; 3] = bytes.try_into().ok()?;
Some(Self(u32::from_be_bytes([0, a, b, c])))
}
}
#[cfg(any(target_pointer_width = "32", target_pointer_width = "64"))]
impl From<u24> for usize {
#[inline]
fn from(v: u24) -> Self {
v.0 as Self
}
}
impl Codec for u24 {
fn encode(&self, bytes: &mut Vec<u8>) {
let be_bytes = u32::to_be_bytes(self.0);
bytes.extend_from_slice(&be_bytes[1..])
}
fn read(r: &mut Reader) -> Option<Self> {
r.take(3).and_then(Self::decode)
}
}
pub fn decode_u32(bytes: &[u8]) -> Option<u32> {
Some(u32::from_be_bytes(bytes.try_into().ok()?))
}
impl Codec for u32 {
fn encode(&self, bytes: &mut Vec<u8>) {
bytes.extend(Self::to_be_bytes(*self))
}
fn read(r: &mut Reader) -> Option<Self> {
r.take(4).and_then(decode_u32)
}
}
pub fn put_u64(v: u64, bytes: &mut [u8]) {
let bytes: &mut [u8; 8] = (&mut bytes[..8]).try_into().unwrap();
*bytes = u64::to_be_bytes(v)
}
pub fn decode_u64(bytes: &[u8]) -> Option<u64> {
Some(u64::from_be_bytes(bytes.try_into().ok()?))
}
impl Codec for u64 {
fn encode(&self, bytes: &mut Vec<u8>) {
let mut b64 = [0u8; 8];
put_u64(*self, &mut b64);
bytes.extend_from_slice(&b64);
}
fn read(r: &mut Reader) -> Option<Self> {
r.take(8).and_then(decode_u64)
}
}
pub fn encode_vec_u8<T: Codec>(bytes: &mut Vec<u8>, items: &[T]) {
let len_offset = bytes.len();
bytes.push(0);
for i in items {
i.encode(bytes);
}
let len = bytes.len() - len_offset - 1;
debug_assert!(len <= 0xff);
bytes[len_offset] = len as u8;
}
pub fn encode_vec_u16<T: Codec>(bytes: &mut Vec<u8>, items: &[T]) {
let len_offset = bytes.len();
bytes.extend([0, 0]);
for i in items {
i.encode(bytes);
}
let len = bytes.len() - len_offset - 2;
debug_assert!(len <= 0xffff);
let out: &mut [u8; 2] = (&mut bytes[len_offset..len_offset + 2])
.try_into()
.unwrap();
*out = u16::to_be_bytes(len as u16);
}
pub fn encode_vec_u24<T: Codec>(bytes: &mut Vec<u8>, items: &[T]) {
let len_offset = bytes.len();
bytes.extend([0, 0, 0]);
for i in items {
i.encode(bytes);
}
let len = bytes.len() - len_offset - 3;
debug_assert!(len <= 0xff_ffff);
let len_bytes = u32::to_be_bytes(len as u32);
let out: &mut [u8; 3] = (&mut bytes[len_offset..len_offset + 3])
.try_into()
.unwrap();
out.copy_from_slice(&len_bytes[1..]);
}
pub fn read_vec_u8<T: Codec>(r: &mut Reader) -> Option<Vec<T>> {
let mut ret: Vec<T> = Vec::new();
let len = usize::from(u8::read(r)?);
let mut sub = r.sub(len)?;
while sub.any_left() {
ret.push(T::read(&mut sub)?);
}
Some(ret)
}
pub fn read_vec_u16<T: Codec>(r: &mut Reader) -> Option<Vec<T>> {
let mut ret: Vec<T> = Vec::new();
let len = usize::from(u16::read(r)?);
let mut sub = r.sub(len)?;
while sub.any_left() {
ret.push(T::read(&mut sub)?);
}
Some(ret)
}
pub fn read_vec_u24_limited<T: Codec>(r: &mut Reader, max_bytes: usize) -> Option<Vec<T>> {
let mut ret: Vec<T> = Vec::new();
let len = u24::read(r)?.0 as usize;
if len > max_bytes {
return None;
}
let mut sub = r.sub(len)?;
while sub.any_left() {
ret.push(T::read(&mut sub)?);
}
Some(ret)
}

View file

@ -0,0 +1,425 @@
use std::collections::VecDeque;
use std::io;
use crate::error::Error;
use crate::msgs::codec;
use crate::msgs::message::{MessageError, OpaqueMessage};
/// This deframer works to reconstruct TLS messages
/// from arbitrary-sized reads, buffering as necessary.
/// The input is `read()`, get the output from `pop()`.
pub struct MessageDeframer {
/// Completed frames for output.
frames: VecDeque<OpaqueMessage>,
/// Set to true if the peer is not talking TLS, but some other
/// protocol. The caller should abort the connection, because
/// the deframer cannot recover.
desynced: bool,
/// A fixed-size buffer containing the currently-accumulating
/// TLS message.
buf: Box<[u8; OpaqueMessage::MAX_WIRE_SIZE]>,
/// What size prefix of `buf` is used.
used: usize,
}
impl Default for MessageDeframer {
fn default() -> Self {
Self::new()
}
}
impl MessageDeframer {
pub fn new() -> Self {
Self {
frames: VecDeque::new(),
desynced: false,
buf: Box::new([0u8; OpaqueMessage::MAX_WIRE_SIZE]),
used: 0,
}
}
/// Return any complete messages that the deframer has been able to parse.
///
/// Returns an `Error` if the deframer failed to parse some message contents,
/// `Ok(None)` if no full message is buffered, and `Ok(Some(_))` if a valid message was found.
pub fn pop(&mut self) -> Result<Option<OpaqueMessage>, Error> {
if self.desynced {
return Err(Error::CorruptMessage);
} else if let Some(msg) = self.frames.pop_front() {
return Ok(Some(msg));
}
let mut taken = 0;
loop {
// Does our `buf` contain a full message? It does if it is big enough to
// contain a header, and that header has a length which falls within `buf`.
// If so, deframe it and place the message onto the frames output queue.
let mut rd = codec::Reader::init(&self.buf[taken..self.used]);
let m = match OpaqueMessage::read(&mut rd) {
Ok(m) => m,
Err(MessageError::TooShortForHeader | MessageError::TooShortForLength) => break,
Err(_) => {
self.desynced = true;
return Err(Error::CorruptMessage);
}
};
taken += rd.used();
self.frames.push_back(m);
}
#[allow(clippy::comparison_chain)]
if taken < self.used {
/* Before:
* +----------+----------+----------+
* | taken | pending |xxxxxxxxxx|
* +----------+----------+----------+
* 0 ^ taken ^ self.used
*
* After:
* +----------+----------+----------+
* | pending |xxxxxxxxxxxxxxxxxxxxx|
* +----------+----------+----------+
* 0 ^ self.used
*/
self.buf
.copy_within(taken..self.used, 0);
self.used -= taken;
} else if taken == self.used {
self.used = 0;
}
Ok(self.frames.pop_front())
}
/// Read some bytes from `rd`, and add them to our internal buffer.
#[allow(clippy::comparison_chain)]
pub fn read(&mut self, rd: &mut dyn io::Read) -> io::Result<usize> {
if self.used == OpaqueMessage::MAX_WIRE_SIZE {
return Err(io::Error::new(io::ErrorKind::Other, "message buffer full"));
}
// Try to do the largest reads possible. Note that if
// we get a message with a length field out of range here,
// we do a zero length read. That looks like an EOF to
// the next layer up, which is fine.
debug_assert!(self.used <= OpaqueMessage::MAX_WIRE_SIZE);
let new_bytes = rd.read(&mut self.buf[self.used..])?;
self.used += new_bytes;
Ok(new_bytes)
}
/// Returns true if we have messages for the caller
/// to process, either whole messages in our output
/// queue or partial messages in our buffer.
pub fn has_pending(&self) -> bool {
!self.frames.is_empty() || self.used > 0
}
}
#[cfg(test)]
mod tests {
use super::MessageDeframer;
use crate::msgs::message::{Message, OpaqueMessage};
use crate::{msgs, Error};
use std::convert::TryFrom;
use std::io;
const FIRST_MESSAGE: &[u8] = include_bytes!("../testdata/deframer-test.1.bin");
const SECOND_MESSAGE: &[u8] = include_bytes!("../testdata/deframer-test.2.bin");
const EMPTY_APPLICATIONDATA_MESSAGE: &[u8] =
include_bytes!("../testdata/deframer-empty-applicationdata.bin");
const INVALID_EMPTY_MESSAGE: &[u8] = include_bytes!("../testdata/deframer-invalid-empty.bin");
const INVALID_CONTENTTYPE_MESSAGE: &[u8] =
include_bytes!("../testdata/deframer-invalid-contenttype.bin");
const INVALID_VERSION_MESSAGE: &[u8] =
include_bytes!("../testdata/deframer-invalid-version.bin");
const INVALID_LENGTH_MESSAGE: &[u8] = include_bytes!("../testdata/deframer-invalid-length.bin");
struct ByteRead<'a> {
buf: &'a [u8],
offs: usize,
}
impl<'a> ByteRead<'a> {
fn new(bytes: &'a [u8]) -> Self {
ByteRead {
buf: bytes,
offs: 0,
}
}
}
impl<'a> io::Read for ByteRead<'a> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let mut len = 0;
while len < buf.len() && len < self.buf.len() - self.offs {
buf[len] = self.buf[self.offs + len];
len += 1;
}
self.offs += len;
Ok(len)
}
}
fn input_bytes(d: &mut MessageDeframer, bytes: &[u8]) -> io::Result<usize> {
let mut rd = ByteRead::new(bytes);
d.read(&mut rd)
}
fn input_bytes_concat(
d: &mut MessageDeframer,
bytes1: &[u8],
bytes2: &[u8],
) -> io::Result<usize> {
let mut bytes = vec![0u8; bytes1.len() + bytes2.len()];
bytes[..bytes1.len()].clone_from_slice(bytes1);
bytes[bytes1.len()..].clone_from_slice(bytes2);
let mut rd = ByteRead::new(&bytes);
d.read(&mut rd)
}
struct ErrorRead {
error: Option<io::Error>,
}
impl ErrorRead {
fn new(error: io::Error) -> Self {
Self { error: Some(error) }
}
}
impl io::Read for ErrorRead {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
for (i, b) in buf.iter_mut().enumerate() {
*b = i as u8;
}
let error = self.error.take().unwrap();
Err(error)
}
}
fn input_error(d: &mut MessageDeframer) {
let error = io::Error::from(io::ErrorKind::TimedOut);
let mut rd = ErrorRead::new(error);
d.read(&mut rd)
.expect_err("error not propagated");
}
fn input_whole_incremental(d: &mut MessageDeframer, bytes: &[u8]) {
let before = d.used;
for i in 0..bytes.len() {
assert_len(1, input_bytes(d, &bytes[i..i + 1]));
assert!(d.has_pending());
}
assert_eq!(before + bytes.len(), d.used);
}
fn assert_len(want: usize, got: io::Result<usize>) {
if let Ok(gotval) = got {
assert_eq!(gotval, want);
} else {
panic!("read failed, expected {:?} bytes", want);
}
}
fn pop_first(d: &mut MessageDeframer) {
let m = d.pop().unwrap().unwrap();
assert_eq!(m.typ, msgs::enums::ContentType::Handshake);
Message::try_from(m.into_plain_message()).unwrap();
}
fn pop_second(d: &mut MessageDeframer) {
let m = d.pop().unwrap().unwrap();
assert_eq!(m.typ, msgs::enums::ContentType::Alert);
Message::try_from(m.into_plain_message()).unwrap();
}
#[test]
fn check_incremental() {
let mut d = MessageDeframer::new();
assert!(!d.has_pending());
input_whole_incremental(&mut d, FIRST_MESSAGE);
assert!(d.has_pending());
assert_eq!(0, d.frames.len());
pop_first(&mut d);
assert!(!d.has_pending());
assert!(!d.desynced);
}
#[test]
fn check_incremental_2() {
let mut d = MessageDeframer::new();
assert!(!d.has_pending());
input_whole_incremental(&mut d, FIRST_MESSAGE);
assert!(d.has_pending());
input_whole_incremental(&mut d, SECOND_MESSAGE);
assert!(d.has_pending());
assert_eq!(0, d.frames.len());
pop_first(&mut d);
assert!(d.has_pending());
assert_eq!(1, d.frames.len());
pop_second(&mut d);
assert!(!d.has_pending());
assert!(!d.desynced);
}
#[test]
fn check_whole() {
let mut d = MessageDeframer::new();
assert!(!d.has_pending());
assert_len(FIRST_MESSAGE.len(), input_bytes(&mut d, FIRST_MESSAGE));
assert!(d.has_pending());
assert_eq!(d.frames.len(), 0);
pop_first(&mut d);
assert!(!d.has_pending());
assert!(!d.desynced);
}
#[test]
fn check_whole_2() {
let mut d = MessageDeframer::new();
assert!(!d.has_pending());
assert_len(FIRST_MESSAGE.len(), input_bytes(&mut d, FIRST_MESSAGE));
assert_len(SECOND_MESSAGE.len(), input_bytes(&mut d, SECOND_MESSAGE));
assert_eq!(d.frames.len(), 0);
pop_first(&mut d);
assert_eq!(d.frames.len(), 1);
pop_second(&mut d);
assert!(!d.has_pending());
assert!(!d.desynced);
}
#[test]
fn test_two_in_one_read() {
let mut d = MessageDeframer::new();
assert!(!d.has_pending());
assert_len(
FIRST_MESSAGE.len() + SECOND_MESSAGE.len(),
input_bytes_concat(&mut d, FIRST_MESSAGE, SECOND_MESSAGE),
);
assert_eq!(d.frames.len(), 0);
pop_first(&mut d);
assert_eq!(d.frames.len(), 1);
pop_second(&mut d);
assert!(!d.has_pending());
assert!(!d.desynced);
}
#[test]
fn test_two_in_one_read_shortest_first() {
let mut d = MessageDeframer::new();
assert!(!d.has_pending());
assert_len(
FIRST_MESSAGE.len() + SECOND_MESSAGE.len(),
input_bytes_concat(&mut d, SECOND_MESSAGE, FIRST_MESSAGE),
);
assert_eq!(d.frames.len(), 0);
pop_second(&mut d);
assert_eq!(d.frames.len(), 1);
pop_first(&mut d);
assert!(!d.has_pending());
assert!(!d.desynced);
}
#[test]
fn test_incremental_with_nonfatal_read_error() {
let mut d = MessageDeframer::new();
assert_len(3, input_bytes(&mut d, &FIRST_MESSAGE[..3]));
input_error(&mut d);
assert_len(
FIRST_MESSAGE.len() - 3,
input_bytes(&mut d, &FIRST_MESSAGE[3..]),
);
assert_eq!(d.frames.len(), 0);
pop_first(&mut d);
assert!(!d.has_pending());
assert!(!d.desynced);
}
#[test]
fn test_invalid_contenttype_errors() {
let mut d = MessageDeframer::new();
assert_len(
INVALID_CONTENTTYPE_MESSAGE.len(),
input_bytes(&mut d, INVALID_CONTENTTYPE_MESSAGE),
);
assert_eq!(d.pop().unwrap_err(), Error::CorruptMessage);
}
#[test]
fn test_invalid_version_errors() {
let mut d = MessageDeframer::new();
assert_len(
INVALID_VERSION_MESSAGE.len(),
input_bytes(&mut d, INVALID_VERSION_MESSAGE),
);
assert_eq!(d.pop().unwrap_err(), Error::CorruptMessage);
}
#[test]
fn test_invalid_length_errors() {
let mut d = MessageDeframer::new();
assert_len(
INVALID_LENGTH_MESSAGE.len(),
input_bytes(&mut d, INVALID_LENGTH_MESSAGE),
);
assert_eq!(d.pop().unwrap_err(), Error::CorruptMessage);
}
#[test]
fn test_empty_applicationdata() {
let mut d = MessageDeframer::new();
assert_len(
EMPTY_APPLICATIONDATA_MESSAGE.len(),
input_bytes(&mut d, EMPTY_APPLICATIONDATA_MESSAGE),
);
let m = d.pop().unwrap().unwrap();
assert_eq!(m.typ, msgs::enums::ContentType::ApplicationData);
assert_eq!(m.payload.0.len(), 0);
assert!(!d.has_pending());
assert!(!d.desynced);
}
#[test]
fn test_invalid_empty_errors() {
let mut d = MessageDeframer::new();
assert_len(
INVALID_EMPTY_MESSAGE.len(),
input_bytes(&mut d, INVALID_EMPTY_MESSAGE),
);
assert_eq!(d.pop().unwrap_err(), Error::CorruptMessage);
// CorruptMessage has been fused
assert_eq!(d.pop().unwrap_err(), Error::CorruptMessage);
}
#[test]
fn test_limited_buffer() {
const PAYLOAD_LEN: usize = 16_384;
let mut message = Vec::with_capacity(8192);
message.push(0x17); // ApplicationData
message.extend(&[0x03, 0x04]); // ProtocolVersion
message.extend((PAYLOAD_LEN as u16).to_be_bytes()); // payload length
message.extend(&[0; PAYLOAD_LEN]);
let mut d = MessageDeframer::new();
assert_len(message.len(), input_bytes(&mut d, &message));
assert_len(
OpaqueMessage::MAX_WIRE_SIZE - 16_389,
input_bytes(&mut d, &message),
);
assert!(input_bytes(&mut d, &message).is_err());
}
}

View file

@ -0,0 +1,375 @@
#![allow(clippy::upper_case_acronyms)]
#![allow(non_camel_case_types)]
/// This file is autogenerated. See https://github.com/ctz/tls-hacking/
use crate::msgs::codec::{Codec, Reader};
enum_builder! {
/// The `HashAlgorithm` TLS protocol enum. Values in this enum are taken
/// from the various RFCs covering TLS, and are listed by IANA.
/// The `Unknown` item is used when processing unrecognised ordinals.
@U8
EnumName: HashAlgorithm;
EnumVal{
NONE => 0x00,
MD5 => 0x01,
SHA1 => 0x02,
SHA224 => 0x03,
SHA256 => 0x04,
SHA384 => 0x05,
SHA512 => 0x06
}
}
enum_builder! {
/// The `SignatureAlgorithm` TLS protocol enum. Values in this enum are taken
/// from the various RFCs covering TLS, and are listed by IANA.
/// The `Unknown` item is used when processing unrecognised ordinals.
@U8
EnumName: SignatureAlgorithm;
EnumVal{
Anonymous => 0x00,
RSA => 0x01,
DSA => 0x02,
ECDSA => 0x03,
ED25519 => 0x07,
ED448 => 0x08
}
}
enum_builder! {
/// The `ClientCertificateType` TLS protocol enum. Values in this enum are taken
/// from the various RFCs covering TLS, and are listed by IANA.
/// The `Unknown` item is used when processing unrecognised ordinals.
@U8
EnumName: ClientCertificateType;
EnumVal{
RSASign => 0x01,
DSSSign => 0x02,
RSAFixedDH => 0x03,
DSSFixedDH => 0x04,
RSAEphemeralDH => 0x05,
DSSEphemeralDH => 0x06,
FortezzaDMS => 0x14,
ECDSASign => 0x40,
RSAFixedECDH => 0x41,
ECDSAFixedECDH => 0x42
}
}
enum_builder! {
/// The `Compression` TLS protocol enum. Values in this enum are taken
/// from the various RFCs covering TLS, and are listed by IANA.
/// The `Unknown` item is used when processing unrecognised ordinals.
@U8
EnumName: Compression;
EnumVal{
Null => 0x00,
Deflate => 0x01,
LSZ => 0x40
}
}
enum_builder! {
/// The `ContentType` TLS protocol enum. Values in this enum are taken
/// from the various RFCs covering TLS, and are listed by IANA.
/// The `Unknown` item is used when processing unrecognised ordinals.
@U8
EnumName: ContentType;
EnumVal{
ChangeCipherSpec => 0x14,
Alert => 0x15,
Handshake => 0x16,
ApplicationData => 0x17,
Heartbeat => 0x18
}
}
enum_builder! {
/// The `HandshakeType` TLS protocol enum. Values in this enum are taken
/// from the various RFCs covering TLS, and are listed by IANA.
/// The `Unknown` item is used when processing unrecognised ordinals.
@U8
EnumName: HandshakeType;
EnumVal{
HelloRequest => 0x00,
ClientHello => 0x01,
ServerHello => 0x02,
HelloVerifyRequest => 0x03,
NewSessionTicket => 0x04,
EndOfEarlyData => 0x05,
HelloRetryRequest => 0x06,
EncryptedExtensions => 0x08,
Certificate => 0x0b,
ServerKeyExchange => 0x0c,
CertificateRequest => 0x0d,
ServerHelloDone => 0x0e,
CertificateVerify => 0x0f,
ClientKeyExchange => 0x10,
Finished => 0x14,
CertificateURL => 0x15,
CertificateStatus => 0x16,
KeyUpdate => 0x18,
MessageHash => 0xfe
}
}
enum_builder! {
/// The `AlertLevel` TLS protocol enum. Values in this enum are taken
/// from the various RFCs covering TLS, and are listed by IANA.
/// The `Unknown` item is used when processing unrecognised ordinals.
@U8
EnumName: AlertLevel;
EnumVal{
Warning => 0x01,
Fatal => 0x02
}
}
enum_builder! {
/// The `AlertDescription` TLS protocol enum. Values in this enum are taken
/// from the various RFCs covering TLS, and are listed by IANA.
/// The `Unknown` item is used when processing unrecognised ordinals.
@U8
EnumName: AlertDescription;
EnumVal{
CloseNotify => 0x00,
UnexpectedMessage => 0x0a,
BadRecordMac => 0x14,
DecryptionFailed => 0x15,
RecordOverflow => 0x16,
DecompressionFailure => 0x1e,
HandshakeFailure => 0x28,
NoCertificate => 0x29,
BadCertificate => 0x2a,
UnsupportedCertificate => 0x2b,
CertificateRevoked => 0x2c,
CertificateExpired => 0x2d,
CertificateUnknown => 0x2e,
IllegalParameter => 0x2f,
UnknownCA => 0x30,
AccessDenied => 0x31,
DecodeError => 0x32,
DecryptError => 0x33,
ExportRestriction => 0x3c,
ProtocolVersion => 0x46,
InsufficientSecurity => 0x47,
InternalError => 0x50,
InappropriateFallback => 0x56,
UserCanceled => 0x5a,
NoRenegotiation => 0x64,
MissingExtension => 0x6d,
UnsupportedExtension => 0x6e,
CertificateUnobtainable => 0x6f,
UnrecognisedName => 0x70,
BadCertificateStatusResponse => 0x71,
BadCertificateHashValue => 0x72,
UnknownPSKIdentity => 0x73,
CertificateRequired => 0x74,
NoApplicationProtocol => 0x78
}
}
enum_builder! {
/// The `HeartbeatMessageType` TLS protocol enum. Values in this enum are taken
/// from the various RFCs covering TLS, and are listed by IANA.
/// The `Unknown` item is used when processing unrecognised ordinals.
@U8
EnumName: HeartbeatMessageType;
EnumVal{
Request => 0x01,
Response => 0x02
}
}
enum_builder! {
/// The `ExtensionType` TLS protocol enum. Values in this enum are taken
/// from the various RFCs covering TLS, and are listed by IANA.
/// The `Unknown` item is used when processing unrecognised ordinals.
@U16
EnumName: ExtensionType;
EnumVal{
ServerName => 0x0000,
MaxFragmentLength => 0x0001,
ClientCertificateUrl => 0x0002,
TrustedCAKeys => 0x0003,
TruncatedHMAC => 0x0004,
StatusRequest => 0x0005,
UserMapping => 0x0006,
ClientAuthz => 0x0007,
ServerAuthz => 0x0008,
CertificateType => 0x0009,
EllipticCurves => 0x000a,
ECPointFormats => 0x000b,
SRP => 0x000c,
SignatureAlgorithms => 0x000d,
UseSRTP => 0x000e,
Heartbeat => 0x000f,
ALProtocolNegotiation => 0x0010,
SCT => 0x0012,
Padding => 0x0015,
ExtendedMasterSecret => 0x0017,
SessionTicket => 0x0023,
PreSharedKey => 0x0029,
EarlyData => 0x002a,
SupportedVersions => 0x002b,
Cookie => 0x002c,
PSKKeyExchangeModes => 0x002d,
TicketEarlyDataInfo => 0x002e,
CertificateAuthorities => 0x002f,
OIDFilters => 0x0030,
PostHandshakeAuth => 0x0031,
SignatureAlgorithmsCert => 0x0032,
KeyShare => 0x0033,
TransportParameters => 0x0039,
NextProtocolNegotiation => 0x3374,
ChannelId => 0x754f,
RenegotiationInfo => 0xff01,
TransportParametersDraft => 0xffa5
}
}
enum_builder! {
/// The `ServerNameType` TLS protocol enum. Values in this enum are taken
/// from the various RFCs covering TLS, and are listed by IANA.
/// The `Unknown` item is used when processing unrecognised ordinals.
@U8
EnumName: ServerNameType;
EnumVal{
HostName => 0x00
}
}
enum_builder! {
/// The `NamedCurve` TLS protocol enum. Values in this enum are taken
/// from the various RFCs covering TLS, and are listed by IANA.
/// The `Unknown` item is used when processing unrecognised ordinals.
@U16
EnumName: NamedCurve;
EnumVal{
sect163k1 => 0x0001,
sect163r1 => 0x0002,
sect163r2 => 0x0003,
sect193r1 => 0x0004,
sect193r2 => 0x0005,
sect233k1 => 0x0006,
sect233r1 => 0x0007,
sect239k1 => 0x0008,
sect283k1 => 0x0009,
sect283r1 => 0x000a,
sect409k1 => 0x000b,
sect409r1 => 0x000c,
sect571k1 => 0x000d,
sect571r1 => 0x000e,
secp160k1 => 0x000f,
secp160r1 => 0x0010,
secp160r2 => 0x0011,
secp192k1 => 0x0012,
secp192r1 => 0x0013,
secp224k1 => 0x0014,
secp224r1 => 0x0015,
secp256k1 => 0x0016,
secp256r1 => 0x0017,
secp384r1 => 0x0018,
secp521r1 => 0x0019,
brainpoolp256r1 => 0x001a,
brainpoolp384r1 => 0x001b,
brainpoolp512r1 => 0x001c,
X25519 => 0x001d,
X448 => 0x001e,
arbitrary_explicit_prime_curves => 0xff01,
arbitrary_explicit_char2_curves => 0xff02
}
}
enum_builder! {
/// The `NamedGroup` TLS protocol enum. Values in this enum are taken
/// from the various RFCs covering TLS, and are listed by IANA.
/// The `Unknown` item is used when processing unrecognised ordinals.
@U16
EnumName: NamedGroup;
EnumVal{
secp256r1 => 0x0017,
secp384r1 => 0x0018,
secp521r1 => 0x0019,
X25519 => 0x001d,
X448 => 0x001e,
FFDHE2048 => 0x0100,
FFDHE3072 => 0x0101,
FFDHE4096 => 0x0102,
FFDHE6144 => 0x0103,
FFDHE8192 => 0x0104
}
}
enum_builder! {
/// The `ECPointFormat` TLS protocol enum. Values in this enum are taken
/// from the various RFCs covering TLS, and are listed by IANA.
/// The `Unknown` item is used when processing unrecognised ordinals.
@U8
EnumName: ECPointFormat;
EnumVal{
Uncompressed => 0x00,
ANSIX962CompressedPrime => 0x01,
ANSIX962CompressedChar2 => 0x02
}
}
enum_builder! {
/// The `HeartbeatMode` TLS protocol enum. Values in this enum are taken
/// from the various RFCs covering TLS, and are listed by IANA.
/// The `Unknown` item is used when processing unrecognised ordinals.
@U8
EnumName: HeartbeatMode;
EnumVal{
PeerAllowedToSend => 0x01,
PeerNotAllowedToSend => 0x02
}
}
enum_builder! {
/// The `ECCurveType` TLS protocol enum. Values in this enum are taken
/// from the various RFCs covering TLS, and are listed by IANA.
/// The `Unknown` item is used when processing unrecognised ordinals.
@U8
EnumName: ECCurveType;
EnumVal{
ExplicitPrime => 0x01,
ExplicitChar2 => 0x02,
NamedCurve => 0x03
}
}
enum_builder! {
/// The `PSKKeyExchangeMode` TLS protocol enum. Values in this enum are taken
/// from the various RFCs covering TLS, and are listed by IANA.
/// The `Unknown` item is used when processing unrecognised ordinals.
@U8
EnumName: PSKKeyExchangeMode;
EnumVal{
PSK_KE => 0x00,
PSK_DHE_KE => 0x01
}
}
enum_builder! {
/// The `KeyUpdateRequest` TLS protocol enum. Values in this enum are taken
/// from the various RFCs covering TLS, and are listed by IANA.
/// The `Unknown` item is used when processing unrecognised ordinals.
@U8
EnumName: KeyUpdateRequest;
EnumVal{
UpdateNotRequested => 0x00,
UpdateRequested => 0x01
}
}
enum_builder! {
/// The `CertificateStatusType` TLS protocol enum. Values in this enum are taken
/// from the various RFCs covering TLS, and are listed by IANA.
/// The `Unknown` item is used when processing unrecognised ordinals.
@U8
EnumName: CertificateStatusType;
EnumVal{
OCSP => 0x01
}
}

View file

@ -0,0 +1,88 @@
/// These tests are intended to provide coverage and
/// check panic-safety of relatively unused values.
use super::codec::Codec;
use super::enums::*;
fn get8<T: Codec>(enum_value: &T) -> u8 {
let enc = enum_value.get_encoding();
assert_eq!(enc.len(), 1);
enc[0]
}
fn get16<T: Codec>(enum_value: &T) -> u16 {
let enc = enum_value.get_encoding();
assert_eq!(enc.len(), 2);
(enc[0] as u16 >> 8) | (enc[1] as u16)
}
fn test_enum16<T: Codec>(first: T, last: T) {
let first_v = get16(&first);
let last_v = get16(&last);
for val in first_v..last_v + 1 {
let mut buf = Vec::new();
val.encode(&mut buf);
assert_eq!(buf.len(), 2);
let t = T::read_bytes(&buf).unwrap();
assert_eq!(val, get16(&t));
}
}
fn test_enum8<T: Codec>(first: T, last: T) {
let first_v = get8(&first);
let last_v = get8(&last);
for val in first_v..last_v + 1 {
let mut buf = Vec::new();
val.encode(&mut buf);
assert_eq!(buf.len(), 1);
let t = T::read_bytes(&buf).unwrap();
assert_eq!(val, get8(&t));
}
}
#[test]
fn test_enums() {
test_enum8::<HashAlgorithm>(HashAlgorithm::NONE, HashAlgorithm::SHA512);
test_enum8::<SignatureAlgorithm>(SignatureAlgorithm::Anonymous, SignatureAlgorithm::ECDSA);
test_enum8::<ClientCertificateType>(
ClientCertificateType::RSASign,
ClientCertificateType::ECDSAFixedECDH,
);
test_enum8::<Compression>(Compression::Null, Compression::LSZ);
test_enum8::<ContentType>(ContentType::ChangeCipherSpec, ContentType::Heartbeat);
test_enum8::<HandshakeType>(HandshakeType::HelloRequest, HandshakeType::MessageHash);
test_enum8::<AlertLevel>(AlertLevel::Warning, AlertLevel::Fatal);
test_enum8::<AlertDescription>(
AlertDescription::CloseNotify,
AlertDescription::NoApplicationProtocol,
);
test_enum8::<HeartbeatMessageType>(
HeartbeatMessageType::Request,
HeartbeatMessageType::Response,
);
test_enum16::<ExtensionType>(ExtensionType::ServerName, ExtensionType::RenegotiationInfo);
test_enum8::<ServerNameType>(ServerNameType::HostName, ServerNameType::HostName);
test_enum16::<NamedCurve>(
NamedCurve::sect163k1,
NamedCurve::arbitrary_explicit_char2_curves,
);
test_enum16::<NamedGroup>(NamedGroup::secp256r1, NamedGroup::FFDHE8192);
test_enum8::<ECPointFormat>(
ECPointFormat::Uncompressed,
ECPointFormat::ANSIX962CompressedChar2,
);
test_enum8::<HeartbeatMode>(
HeartbeatMode::PeerAllowedToSend,
HeartbeatMode::PeerNotAllowedToSend,
);
test_enum8::<ECCurveType>(ECCurveType::ExplicitPrime, ECCurveType::NamedCurve);
test_enum8::<PSKKeyExchangeMode>(PSKKeyExchangeMode::PSK_KE, PSKKeyExchangeMode::PSK_DHE_KE);
test_enum8::<KeyUpdateRequest>(
KeyUpdateRequest::UpdateNotRequested,
KeyUpdateRequest::UpdateRequested,
);
test_enum8::<CertificateStatusType>(CertificateStatusType::OCSP, CertificateStatusType::OCSP);
}

View file

@ -0,0 +1,162 @@
use crate::enums::ProtocolVersion;
use crate::msgs::enums::ContentType;
use crate::msgs::message::{BorrowedPlainMessage, PlainMessage};
use crate::Error;
pub const MAX_FRAGMENT_LEN: usize = 16384;
pub const PACKET_OVERHEAD: usize = 1 + 2 + 2;
pub const MAX_FRAGMENT_SIZE: usize = MAX_FRAGMENT_LEN + PACKET_OVERHEAD;
pub struct MessageFragmenter {
max_frag: usize,
}
impl Default for MessageFragmenter {
fn default() -> Self {
Self {
max_frag: MAX_FRAGMENT_LEN,
}
}
}
impl MessageFragmenter {
/// Take the Message `msg` and re-fragment it into new
/// messages whose fragment is no more than max_frag.
/// Return an iterator across those messages.
/// Payloads are borrowed.
pub fn fragment_message<'a>(
&self,
msg: &'a PlainMessage,
) -> impl Iterator<Item = BorrowedPlainMessage<'a>> + 'a {
self.fragment_slice(msg.typ, msg.version, &msg.payload.0)
}
/// Enqueue borrowed fragments of (version, typ, payload) which
/// are no longer than max_frag onto the `out` deque.
pub fn fragment_slice<'a>(
&self,
typ: ContentType,
version: ProtocolVersion,
payload: &'a [u8],
) -> impl Iterator<Item = BorrowedPlainMessage<'a>> + 'a {
payload
.chunks(self.max_frag)
.map(move |c| BorrowedPlainMessage {
typ,
version,
payload: c,
})
}
/// Set the maximum fragment size that will be produced.
///
/// This includes overhead. A `max_fragment_size` of 10 will produce TLS fragments
/// up to 10 bytes long.
///
/// A `max_fragment_size` of `None` sets the highest allowable fragment size.
///
/// Returns BadMaxFragmentSize if the size is smaller than 32 or larger than 16389.
pub fn set_max_fragment_size(&mut self, max_fragment_size: Option<usize>) -> Result<(), Error> {
self.max_frag = match max_fragment_size {
Some(sz @ 32..=MAX_FRAGMENT_SIZE) => sz - PACKET_OVERHEAD,
None => MAX_FRAGMENT_LEN,
_ => return Err(Error::BadMaxFragmentSize),
};
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::{MessageFragmenter, PACKET_OVERHEAD};
use crate::enums::ProtocolVersion;
use crate::msgs::base::Payload;
use crate::msgs::enums::ContentType;
use crate::msgs::message::{BorrowedPlainMessage, PlainMessage};
fn msg_eq(
m: &BorrowedPlainMessage,
total_len: usize,
typ: &ContentType,
version: &ProtocolVersion,
bytes: &[u8],
) {
assert_eq!(&m.typ, typ);
assert_eq!(&m.version, version);
assert_eq!(m.payload, bytes);
let buf = m.to_unencrypted_opaque().encode();
assert_eq!(total_len, buf.len());
}
#[test]
fn smoke() {
let typ = ContentType::Handshake;
let version = ProtocolVersion::TLSv1_2;
let data: Vec<u8> = (1..70u8).collect();
let m = PlainMessage {
typ,
version,
payload: Payload::new(data),
};
let mut frag = MessageFragmenter::default();
frag.set_max_fragment_size(Some(32))
.unwrap();
let q = frag
.fragment_message(&m)
.collect::<Vec<_>>();
assert_eq!(q.len(), 3);
msg_eq(
&q[0],
32,
&typ,
&version,
&[
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
24, 25, 26, 27,
],
);
msg_eq(
&q[1],
32,
&typ,
&version,
&[
28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48,
49, 50, 51, 52, 53, 54,
],
);
msg_eq(
&q[2],
20,
&typ,
&version,
&[55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69],
);
}
#[test]
fn non_fragment() {
let m = PlainMessage {
typ: ContentType::Handshake,
version: ProtocolVersion::TLSv1_2,
payload: Payload::new(b"\x01\x02\x03\x04\x05\x06\x07\x08".to_vec()),
};
let mut frag = MessageFragmenter::default();
frag.set_max_fragment_size(Some(32))
.unwrap();
let q = frag
.fragment_message(&m)
.collect::<Vec<_>>();
assert_eq!(q.len(), 1);
msg_eq(
&q[0],
PACKET_OVERHEAD + 8,
&ContentType::Handshake,
&ProtocolVersion::TLSv1_2,
b"\x01\x02\x03\x04\x05\x06\x07\x08",
);
}
}

Binary file not shown.

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,281 @@
use std::collections::VecDeque;
use crate::enums::ProtocolVersion;
use crate::msgs::base::Payload;
use crate::msgs::codec;
use crate::msgs::enums::ContentType;
use crate::msgs::handshake::HandshakeMessagePayload;
use crate::msgs::message::{Message, MessagePayload, PlainMessage};
const HEADER_SIZE: usize = 1 + 3;
/// TLS allows for handshake messages of up to 16MB. We
/// restrict that to 64KB to limit potential for denial-of-
/// service.
const MAX_HANDSHAKE_SIZE: u32 = 0xffff;
/// This works to reconstruct TLS handshake messages
/// from individual TLS messages. It's guaranteed that
/// TLS messages output from this layer contain precisely
/// one handshake payload.
pub struct HandshakeJoiner {
/// The message payload(s) we're currently accumulating.
buf: Vec<u8>,
/// Sizes of messages currently in the buffer.
///
/// The buffer can be larger than the sum of the sizes in this queue, because it might contain
/// the start of a message that hasn't fully been received yet as its suffix.
sizes: VecDeque<usize>,
/// Version of the protocol we're currently parsing.
version: ProtocolVersion,
}
impl HandshakeJoiner {
/// Make a new HandshakeJoiner.
pub fn new() -> Self {
Self {
buf: Vec::new(),
sizes: VecDeque::new(),
version: ProtocolVersion::TLSv1_2,
}
}
/// Take the message, and join/split it as needed.
///
/// Returns `Err(JoinerError::Unwanted(msg))` if `msg`'s type is not `ContentType::Handshake` or
/// `JoinerError::Decode` if a received payload has an advertised size larger than we accept.
///
/// Otherwise, yields a `bool` to indicate whether the handshake is "aligned": if the buffer currently
/// only contains complete payloads (that is, no incomplete message in the suffix).
pub fn push(&mut self, msg: PlainMessage) -> Result<bool, JoinerError> {
if msg.typ != ContentType::Handshake {
return Err(JoinerError::Unwanted(msg));
}
// The vast majority of the time `self.buf` will be empty since most
// handshake messages arrive in a single fragment. Avoid allocating and
// copying in that common case.
if self.buf.is_empty() {
self.buf = msg.payload.0;
} else {
self.buf
.extend_from_slice(&msg.payload.0[..]);
}
if msg.version == ProtocolVersion::TLSv1_3 {
self.version = msg.version;
}
// Check the suffix of the buffer that hasn't been covered by `sizes` so far
// for complete messages. If we find any, update `self.sizes` and `complete`.
let mut complete = self.sizes.iter().copied().sum();
while let Some(size) = payload_size(&self.buf[complete..])? {
self.sizes.push_back(size);
complete += size;
}
// Use the value of `complete` to determine if the buffer currently contains any
// incomplete messages. If not, an incoming message is said to be "aligned".
Ok(complete == self.buf.len())
}
/// Parse the first received message out of the buffer.
///
/// Returns `Ok(None)` if we don't have a complete message in the buffer, or `Err` if we
/// fail to parse the first message in the buffer.
pub fn pop(&mut self) -> Result<Option<Message>, JoinerError> {
let len = match self.sizes.pop_front() {
Some(len) => len,
None => return Ok(None),
};
// Parse the first part of the buffer as a handshake buffer.
// If we get `None` back, we've failed to parse the message.
// If we succeed, drain the relevant bytes from the buffer.
let buf = &self.buf[..len];
let mut rd = codec::Reader::init(buf);
let parsed = match HandshakeMessagePayload::read_version(&mut rd, self.version) {
Some(p) => p,
None => return Err(JoinerError::Decode),
};
let message = Message {
version: self.version,
payload: MessagePayload::Handshake {
parsed,
encoded: Payload::new(buf),
},
};
self.buf.drain(..len);
Ok(Some(message))
}
}
/// Does `buf` contain a full handshake payload?
///
/// Returns `Ok(Some(_))` with the length of the payload (including header) if it does,
/// `Ok(None)` if the buffer is too small to contain a message with the length advertised in the
/// header, or `Err` if the advertised length is larger than what we want to accept
/// (`MAX_HANDSHAKE_SIZE`).
fn payload_size(buf: &[u8]) -> Result<Option<usize>, JoinerError> {
if buf.len() < HEADER_SIZE {
return Ok(None);
}
let (header, rest) = buf.split_at(HEADER_SIZE);
match codec::u24::decode(&header[1..]) {
Some(len) if len.0 > MAX_HANDSHAKE_SIZE => Err(JoinerError::Decode),
Some(len) if rest.get(..len.into()).is_some() => Ok(Some(HEADER_SIZE + usize::from(len))),
_ => Ok(None),
}
}
#[derive(Debug)]
pub enum JoinerError {
Unwanted(PlainMessage),
Decode,
}
#[cfg(test)]
mod tests {
use super::HandshakeJoiner;
use crate::enums::ProtocolVersion;
use crate::msgs::base::Payload;
use crate::msgs::codec::Codec;
use crate::msgs::enums::{ContentType, HandshakeType};
use crate::msgs::handshake::{HandshakeMessagePayload, HandshakePayload};
use crate::msgs::message::{Message, MessagePayload, PlainMessage};
#[test]
fn want() {
let mut hj = HandshakeJoiner::new();
let wanted = PlainMessage {
typ: ContentType::Handshake,
version: ProtocolVersion::TLSv1_2,
payload: Payload::new(b"\x00\x00\x00\x00".to_vec()),
};
let unwanted = PlainMessage {
typ: ContentType::Alert,
version: ProtocolVersion::TLSv1_2,
payload: Payload::new(b"ponytown".to_vec()),
};
hj.push(wanted).unwrap();
hj.push(unwanted).unwrap_err();
}
fn pop_eq(expect: &PlainMessage, hj: &mut HandshakeJoiner) {
let got = hj.pop().unwrap().unwrap();
assert_eq!(got.payload.content_type(), expect.typ);
assert_eq!(got.version, expect.version);
let (mut left, mut right) = (Vec::new(), Vec::new());
got.payload.encode(&mut left);
expect.payload.encode(&mut right);
assert_eq!(left, right);
}
#[test]
fn split() {
// Check we split two handshake messages within one PDU.
let mut hj = HandshakeJoiner::new();
// two HelloRequests
assert!(hj
.push(PlainMessage {
typ: ContentType::Handshake,
version: ProtocolVersion::TLSv1_2,
payload: Payload::new(b"\x00\x00\x00\x00\x00\x00\x00\x00".to_vec()),
})
.unwrap());
let expect = Message {
version: ProtocolVersion::TLSv1_2,
payload: MessagePayload::handshake(HandshakeMessagePayload {
typ: HandshakeType::HelloRequest,
payload: HandshakePayload::HelloRequest,
}),
}
.into();
pop_eq(&expect, &mut hj);
pop_eq(&expect, &mut hj);
}
#[test]
fn broken() {
// Check obvious crap payloads are reported as errors, not panics.
let mut hj = HandshakeJoiner::new();
// short ClientHello
hj.push(PlainMessage {
typ: ContentType::Handshake,
version: ProtocolVersion::TLSv1_2,
payload: Payload::new(b"\x01\x00\x00\x02\xff\xff".to_vec()),
})
.unwrap();
hj.pop().unwrap_err();
}
#[test]
fn join() {
// Check we join one handshake message split over two PDUs.
let mut hj = HandshakeJoiner::new();
// Introduce Finished of 16 bytes, providing 4.
hj.push(PlainMessage {
typ: ContentType::Handshake,
version: ProtocolVersion::TLSv1_2,
payload: Payload::new(b"\x14\x00\x00\x10\x00\x01\x02\x03\x04".to_vec()),
})
.unwrap();
// 11 more bytes.
assert!(!hj
.push(PlainMessage {
typ: ContentType::Handshake,
version: ProtocolVersion::TLSv1_2,
payload: Payload::new(b"\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e".to_vec()),
})
.unwrap());
// Final 1 byte.
assert!(hj
.push(PlainMessage {
typ: ContentType::Handshake,
version: ProtocolVersion::TLSv1_2,
payload: Payload::new(b"\x0f".to_vec()),
})
.unwrap());
let payload = b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f".to_vec();
let expect = Message {
version: ProtocolVersion::TLSv1_2,
payload: MessagePayload::handshake(HandshakeMessagePayload {
typ: HandshakeType::Finished,
payload: HandshakePayload::Finished(Payload::new(payload)),
}),
}
.into();
pop_eq(&expect, &mut hj);
}
#[test]
fn test_rejects_giant_certs() {
let mut hj = HandshakeJoiner::new();
hj.push(PlainMessage {
typ: ContentType::Handshake,
version: ProtocolVersion::TLSv1_2,
payload: Payload::new(b"\x0b\x01\x00\x04\x01\x00\x01\x00\xff\xfe".to_vec()),
})
.unwrap_err();
}
}

View file

@ -0,0 +1,88 @@
/// A macro which defines an enum type.
macro_rules! enum_builder {
(
$(#[$comment:meta])*
@U8
EnumName: $enum_name: ident;
EnumVal { $( $enum_var: ident => $enum_val: expr ),* }
) => {
$(#[$comment])*
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum $enum_name {
$( $enum_var),*
,Unknown(u8)
}
impl $enum_name {
pub fn get_u8(&self) -> u8 {
let x = self.clone();
match x {
$( $enum_name::$enum_var => $enum_val),*
,$enum_name::Unknown(x) => x
}
}
}
impl Codec for $enum_name {
fn encode(&self, bytes: &mut Vec<u8>) {
self.get_u8().encode(bytes);
}
fn read(r: &mut Reader) -> Option<Self> {
u8::read(r).map($enum_name::from)
}
}
impl From<u8> for $enum_name {
fn from(x: u8) -> Self {
match x {
$($enum_val => $enum_name::$enum_var),*
, x => $enum_name::Unknown(x),
}
}
}
};
(
$(#[$comment:meta])*
@U16
EnumName: $enum_name: ident;
EnumVal { $( $enum_var: ident => $enum_val: expr ),* }
) => {
$(#[$comment])*
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum $enum_name {
$( $enum_var),*
,Unknown(u16)
}
impl $enum_name {
pub fn get_u16(&self) -> u16 {
let x = self.clone();
match x {
$( $enum_name::$enum_var => $enum_val),*
,$enum_name::Unknown(x) => x
}
}
pub fn as_str(&self) -> Option<&'static str> {
match self {
$( $enum_name::$enum_var => Some(stringify!($enum_var))),*
,$enum_name::Unknown(_) => None,
}
}
}
impl Codec for $enum_name {
fn encode(&self, bytes: &mut Vec<u8>) {
self.get_u16().encode(bytes);
}
fn read(r: &mut Reader) -> Option<Self> {
u16::read(r).map($enum_name::from)
}
}
impl From<u16> for $enum_name {
fn from(x: u16) -> Self {
match x {
$($enum_val => $enum_name::$enum_var),*
, x => $enum_name::Unknown(x),
}
}
}
};
}

View file

@ -0,0 +1,292 @@
use crate::enums::ProtocolVersion;
use crate::error::Error;
use crate::msgs::alert::AlertMessagePayload;
use crate::msgs::base::Payload;
use crate::msgs::ccs::ChangeCipherSpecPayload;
use crate::msgs::codec::{Codec, Reader};
use crate::msgs::enums::{AlertDescription, AlertLevel, ContentType, HandshakeType};
use crate::msgs::handshake::HandshakeMessagePayload;
use std::convert::TryFrom;
#[derive(Debug)]
pub enum MessagePayload {
Alert(AlertMessagePayload),
Handshake {
parsed: HandshakeMessagePayload,
encoded: Payload,
},
ChangeCipherSpec(ChangeCipherSpecPayload),
ApplicationData(Payload),
}
impl MessagePayload {
pub fn encode(&self, bytes: &mut Vec<u8>) {
match self {
Self::Alert(x) => x.encode(bytes),
Self::Handshake { encoded, .. } => bytes.extend(&encoded.0),
Self::ChangeCipherSpec(x) => x.encode(bytes),
Self::ApplicationData(x) => x.encode(bytes),
}
}
pub fn handshake(parsed: HandshakeMessagePayload) -> Self {
Self::Handshake {
encoded: Payload::new(parsed.get_encoding()),
parsed,
}
}
pub fn new(typ: ContentType, vers: ProtocolVersion, payload: Payload) -> Result<Self, Error> {
let mut r = Reader::init(&payload.0);
let parsed = match typ {
ContentType::ApplicationData => return Ok(Self::ApplicationData(payload)),
ContentType::Alert => AlertMessagePayload::read(&mut r)
.filter(|_| !r.any_left())
.map(MessagePayload::Alert),
ContentType::Handshake => HandshakeMessagePayload::read_version(&mut r, vers)
.filter(|_| !r.any_left())
.map(|parsed| Self::Handshake {
parsed,
encoded: payload,
}),
ContentType::ChangeCipherSpec => ChangeCipherSpecPayload::read(&mut r)
.filter(|_| !r.any_left())
.map(MessagePayload::ChangeCipherSpec),
_ => None,
};
parsed.ok_or(Error::CorruptMessagePayload(typ))
}
pub fn content_type(&self) -> ContentType {
match self {
Self::Alert(_) => ContentType::Alert,
Self::Handshake { .. } => ContentType::Handshake,
Self::ChangeCipherSpec(_) => ContentType::ChangeCipherSpec,
Self::ApplicationData(_) => ContentType::ApplicationData,
}
}
}
/// A TLS frame, named TLSPlaintext in the standard.
///
/// This type owns all memory for its interior parts. It is used to read/write from/to I/O
/// buffers as well as for fragmenting, joining and encryption/decryption. It can be converted
/// into a `Message` by decoding the payload.
#[derive(Clone, Debug)]
pub struct OpaqueMessage {
pub typ: ContentType,
pub version: ProtocolVersion,
pub payload: Payload,
}
impl OpaqueMessage {
/// `MessageError` allows callers to distinguish between valid prefixes (might
/// become valid if we read more data) and invalid data.
pub fn read(r: &mut Reader) -> Result<Self, MessageError> {
let typ = ContentType::read(r).ok_or(MessageError::TooShortForHeader)?;
let version = ProtocolVersion::read(r).ok_or(MessageError::TooShortForHeader)?;
let len = u16::read(r).ok_or(MessageError::TooShortForHeader)?;
// Reject undersize messages
// implemented per section 5.1 of RFC8446 (TLSv1.3)
// per section 6.2.1 of RFC5246 (TLSv1.2)
if typ != ContentType::ApplicationData && len == 0 {
return Err(MessageError::IllegalLength);
}
// Reject oversize messages
if len >= Self::MAX_PAYLOAD {
return Err(MessageError::IllegalLength);
}
// Don't accept any new content-types.
if let ContentType::Unknown(_) = typ {
return Err(MessageError::IllegalContentType);
}
// Accept only versions 0x03XX for any XX.
match version {
ProtocolVersion::Unknown(ref v) if (v & 0xff00) != 0x0300 => {
return Err(MessageError::IllegalProtocolVersion);
}
_ => {}
};
let mut sub = r
.sub(len as usize)
.ok_or(MessageError::TooShortForLength)?;
let payload = Payload::read(&mut sub);
Ok(Self {
typ,
version,
payload,
})
}
pub fn encode(self) -> Vec<u8> {
let mut buf = Vec::new();
self.typ.encode(&mut buf);
self.version.encode(&mut buf);
(self.payload.0.len() as u16).encode(&mut buf);
self.payload.encode(&mut buf);
buf
}
/// Force conversion into a plaintext message.
///
/// This should only be used for messages that are known to be in plaintext. Otherwise, the
/// `OpaqueMessage` should be decrypted into a `PlainMessage` using a `MessageDecrypter`.
pub fn into_plain_message(self) -> PlainMessage {
PlainMessage {
version: self.version,
typ: self.typ,
payload: self.payload,
}
}
/// This is the maximum on-the-wire size of a TLSCiphertext.
/// That's 2^14 payload bytes, a header, and a 2KB allowance
/// for ciphertext overheads.
const MAX_PAYLOAD: u16 = 16384 + 2048;
/// Content type, version and size.
const HEADER_SIZE: u16 = 1 + 2 + 2;
/// Maximum on-wire message size.
pub const MAX_WIRE_SIZE: usize = (Self::MAX_PAYLOAD + Self::HEADER_SIZE) as usize;
}
impl From<Message> for PlainMessage {
fn from(msg: Message) -> Self {
let typ = msg.payload.content_type();
let payload = match msg.payload {
MessagePayload::ApplicationData(payload) => payload,
_ => {
let mut buf = Vec::new();
msg.payload.encode(&mut buf);
Payload(buf)
}
};
Self {
typ,
version: msg.version,
payload,
}
}
}
/// A decrypted TLS frame
///
/// This type owns all memory for its interior parts. It can be decrypted from an OpaqueMessage
/// or encrypted into an OpaqueMessage, and it is also used for joining and fragmenting.
#[derive(Clone, Debug)]
pub struct PlainMessage {
pub typ: ContentType,
pub version: ProtocolVersion,
pub payload: Payload,
}
impl PlainMessage {
pub fn into_unencrypted_opaque(self) -> OpaqueMessage {
OpaqueMessage {
version: self.version,
typ: self.typ,
payload: self.payload,
}
}
pub fn borrow(&self) -> BorrowedPlainMessage<'_> {
BorrowedPlainMessage {
version: self.version,
typ: self.typ,
payload: &self.payload.0,
}
}
}
/// A message with decoded payload
#[derive(Debug)]
pub struct Message {
pub version: ProtocolVersion,
pub payload: MessagePayload,
}
impl Message {
pub fn is_handshake_type(&self, hstyp: HandshakeType) -> bool {
// Bit of a layering violation, but OK.
if let MessagePayload::Handshake { parsed, .. } = &self.payload {
parsed.typ == hstyp
} else {
false
}
}
pub fn build_alert(level: AlertLevel, desc: AlertDescription) -> Self {
Self {
version: ProtocolVersion::TLSv1_2,
payload: MessagePayload::Alert(AlertMessagePayload {
level,
description: desc,
}),
}
}
pub fn build_key_update_notify() -> Self {
Self {
version: ProtocolVersion::TLSv1_3,
payload: MessagePayload::handshake(HandshakeMessagePayload::build_key_update_notify()),
}
}
}
/// Parses a plaintext message into a well-typed [`Message`].
///
/// A [`PlainMessage`] must contain plaintext content. Encrypted content should be stored in an
/// [`OpaqueMessage`] and decrypted before being stored into a [`PlainMessage`].
impl TryFrom<PlainMessage> for Message {
type Error = Error;
fn try_from(plain: PlainMessage) -> Result<Self, Self::Error> {
Ok(Self {
version: plain.version,
payload: MessagePayload::new(plain.typ, plain.version, plain.payload)?,
})
}
}
/// A TLS frame, named TLSPlaintext in the standard.
///
/// This type differs from `OpaqueMessage` because it borrows
/// its payload. You can make a `OpaqueMessage` from an
/// `BorrowMessage`, but this involves a copy.
///
/// This type also cannot decode its internals and
/// cannot be read/encoded; only `OpaqueMessage` can do that.
pub struct BorrowedPlainMessage<'a> {
pub typ: ContentType,
pub version: ProtocolVersion,
pub payload: &'a [u8],
}
impl<'a> BorrowedPlainMessage<'a> {
pub fn to_unencrypted_opaque(&self) -> OpaqueMessage {
OpaqueMessage {
version: self.version,
typ: self.typ,
payload: Payload(self.payload.to_vec()),
}
}
}
#[derive(Debug)]
pub enum MessageError {
TooShortForHeader,
TooShortForLength,
IllegalLength,
IllegalContentType,
IllegalProtocolVersion,
}

View file

@ -0,0 +1,113 @@
use crate::msgs::base::{PayloadU16, PayloadU24, PayloadU8};
use super::base::Payload;
use super::codec::Reader;
use super::enums::{AlertDescription, AlertLevel, HandshakeType};
use super::message::{Message, OpaqueMessage, PlainMessage};
use std::convert::TryFrom;
use std::fs;
use std::io::Read;
use std::path::{Path, PathBuf};
#[test]
fn test_read_fuzz_corpus() {
fn corpus_dir() -> PathBuf {
let from_subcrate = Path::new("../fuzz/corpus/message");
let from_root = Path::new("fuzz/corpus/message");
if from_root.is_dir() {
from_root.to_path_buf()
} else {
from_subcrate.to_path_buf()
}
}
for file in fs::read_dir(corpus_dir()).unwrap() {
let mut f = fs::File::open(file.unwrap().path()).unwrap();
let mut bytes = Vec::new();
f.read_to_end(&mut bytes).unwrap();
let mut rd = Reader::init(&bytes);
let msg = OpaqueMessage::read(&mut rd)
.unwrap()
.into_plain_message();
println!("{:?}", msg);
let msg = match Message::try_from(msg) {
Ok(msg) => msg,
Err(_) => continue,
};
let enc = PlainMessage::from(msg)
.into_unencrypted_opaque()
.encode();
assert_eq!(bytes.to_vec(), enc);
assert_eq!(bytes[..rd.used()].to_vec(), enc);
}
}
#[test]
fn can_read_safari_client_hello() {
let _ = env_logger::Builder::new()
.filter(None, log::LevelFilter::Trace)
.try_init();
let bytes = b"\
\x16\x03\x01\x00\xeb\x01\x00\x00\xe7\x03\x03\xb6\x1f\xe4\x3a\x55\
\x90\x3e\xc0\x28\x9c\x12\xe0\x5c\x84\xea\x90\x1b\xfb\x11\xfc\xbd\
\x25\x55\xda\x9f\x51\x93\x1b\x8d\x92\x66\xfd\x00\x00\x2e\xc0\x2c\
\xc0\x2b\xc0\x24\xc0\x23\xc0\x0a\xc0\x09\xcc\xa9\xc0\x30\xc0\x2f\
\xc0\x28\xc0\x27\xc0\x14\xc0\x13\xcc\xa8\x00\x9d\x00\x9c\x00\x3d\
\x00\x3c\x00\x35\x00\x2f\xc0\x08\xc0\x12\x00\x0a\x01\x00\x00\x90\
\xff\x01\x00\x01\x00\x00\x00\x00\x0e\x00\x0c\x00\x00\x09\x31\x32\
\x37\x2e\x30\x2e\x30\x2e\x31\x00\x17\x00\x00\x00\x0d\x00\x18\x00\
\x16\x04\x03\x08\x04\x04\x01\x05\x03\x02\x03\x08\x05\x08\x05\x05\
\x01\x08\x06\x06\x01\x02\x01\x00\x05\x00\x05\x01\x00\x00\x00\x00\
\x33\x74\x00\x00\x00\x12\x00\x00\x00\x10\x00\x30\x00\x2e\x02\x68\
\x32\x05\x68\x32\x2d\x31\x36\x05\x68\x32\x2d\x31\x35\x05\x68\x32\
\x2d\x31\x34\x08\x73\x70\x64\x79\x2f\x33\x2e\x31\x06\x73\x70\x64\
\x79\x2f\x33\x08\x68\x74\x74\x70\x2f\x31\x2e\x31\x00\x0b\x00\x02\
\x01\x00\x00\x0a\x00\x0a\x00\x08\x00\x1d\x00\x17\x00\x18\x00\x19";
let mut rd = Reader::init(bytes);
let m = OpaqueMessage::read(&mut rd).unwrap();
println!("m = {:?}", m);
assert!(Message::try_from(m.into_plain_message()).is_err());
}
#[test]
fn alert_is_not_handshake() {
let m = Message::build_alert(AlertLevel::Fatal, AlertDescription::DecodeError);
assert!(!m.is_handshake_type(HandshakeType::ClientHello));
}
#[test]
fn alert_is_not_opaque() {
let m = Message::build_alert(AlertLevel::Fatal, AlertDescription::DecodeError);
assert!(Message::try_from(m).is_ok());
}
#[test]
fn construct_all_types() {
let samples = [
&b"\x14\x03\x04\x00\x01\x01"[..],
&b"\x15\x03\x04\x00\x02\x01\x16"[..],
&b"\x16\x03\x04\x00\x05\x18\x00\x00\x01\x00"[..],
&b"\x17\x03\x04\x00\x04\x11\x22\x33\x44"[..],
&b"\x18\x03\x04\x00\x04\x11\x22\x33\x44"[..],
];
for &bytes in samples.iter() {
let m = OpaqueMessage::read(&mut Reader::init(bytes)).unwrap();
println!("m = {:?}", m);
let m = Message::try_from(m.into_plain_message());
println!("m' = {:?}", m);
}
}
#[test]
fn debug_payload() {
assert_eq!("01020304", format!("{:?}", Payload(vec![1, 2, 3, 4])));
assert_eq!("01020304", format!("{:?}", PayloadU8(vec![1, 2, 3, 4])));
assert_eq!("01020304", format!("{:?}", PayloadU16(vec![1, 2, 3, 4])));
assert_eq!("01020304", format!("{:?}", PayloadU24(vec![1, 2, 3, 4])));
}

View file

@ -0,0 +1,51 @@
#![allow(clippy::upper_case_acronyms)]
#![allow(missing_docs)]
#[macro_use]
mod macros;
pub mod alert;
pub mod base;
pub mod ccs;
pub mod codec;
pub mod deframer;
pub mod enums;
pub mod fragmenter;
pub mod handshake;
pub mod hsjoiner;
pub mod message;
pub mod persist;
#[cfg(test)]
mod handshake_test;
#[cfg(test)]
mod persist_test;
#[cfg(test)]
pub(crate) mod enums_test;
#[cfg(test)]
mod message_test;
#[cfg(test)]
mod test {
use std::convert::TryFrom;
#[test]
fn smoketest() {
use super::codec::Reader;
use super::message::{Message, OpaqueMessage};
let bytes = include_bytes!("handshake-test.1.bin");
let mut r = Reader::init(bytes);
while r.any_left() {
let m = OpaqueMessage::read(&mut r).unwrap();
let out = m.clone().encode();
assert!(!out.is_empty());
Message::try_from(m.into_plain_message()).unwrap();
}
}
}

View file

@ -0,0 +1,543 @@
use crate::client::ServerName;
use crate::enums::{CipherSuite, ProtocolVersion};
use crate::key;
use crate::msgs::base::{PayloadU16, PayloadU8};
use crate::msgs::codec::{Codec, Reader};
use crate::msgs::handshake::CertificatePayload;
use crate::msgs::handshake::SessionID;
use crate::suites::SupportedCipherSuite;
use crate::ticketer::TimeBase;
#[cfg(feature = "tls12")]
use crate::tls12::Tls12CipherSuite;
use crate::tls13::Tls13CipherSuite;
use std::cmp;
#[cfg(feature = "tls12")]
use std::mem;
// These are the keys and values we store in session storage.
// --- Client types ---
/// Keys for session resumption and tickets.
/// Matching value is a `ClientSessionValue`.
#[derive(Debug)]
pub struct ClientSessionKey {
kind: &'static [u8],
name: Vec<u8>,
}
impl Codec for ClientSessionKey {
fn encode(&self, bytes: &mut Vec<u8>) {
bytes.extend_from_slice(self.kind);
bytes.extend_from_slice(&self.name);
}
// Don't need to read these.
fn read(_r: &mut Reader) -> Option<Self> {
None
}
}
impl ClientSessionKey {
pub fn session_for_server_name(server_name: &ServerName) -> Self {
Self {
kind: b"session",
name: server_name.encode(),
}
}
pub fn hint_for_server_name(server_name: &ServerName) -> Self {
Self {
kind: b"kx-hint",
name: server_name.encode(),
}
}
}
#[derive(Debug)]
pub enum ClientSessionValue {
Tls13(Tls13ClientSessionValue),
#[cfg(feature = "tls12")]
Tls12(Tls12ClientSessionValue),
}
impl ClientSessionValue {
pub fn read(
reader: &mut Reader<'_>,
suite: CipherSuite,
supported: &[SupportedCipherSuite],
) -> Option<Self> {
match supported
.iter()
.find(|s| s.suite() == suite)?
{
SupportedCipherSuite::Tls13(inner) => {
Tls13ClientSessionValue::read(inner, reader).map(ClientSessionValue::Tls13)
}
#[cfg(feature = "tls12")]
SupportedCipherSuite::Tls12(inner) => {
Tls12ClientSessionValue::read(inner, reader).map(ClientSessionValue::Tls12)
}
}
}
fn common(&self) -> &ClientSessionCommon {
match self {
Self::Tls13(inner) => &inner.common,
#[cfg(feature = "tls12")]
Self::Tls12(inner) => &inner.common,
}
}
}
impl From<Tls13ClientSessionValue> for ClientSessionValue {
fn from(v: Tls13ClientSessionValue) -> Self {
Self::Tls13(v)
}
}
#[cfg(feature = "tls12")]
impl From<Tls12ClientSessionValue> for ClientSessionValue {
fn from(v: Tls12ClientSessionValue) -> Self {
Self::Tls12(v)
}
}
pub struct Retrieved<T> {
pub value: T,
retrieved_at: TimeBase,
}
impl<T> Retrieved<T> {
pub fn new(value: T, retrieved_at: TimeBase) -> Self {
Self {
value,
retrieved_at,
}
}
}
impl Retrieved<&Tls13ClientSessionValue> {
pub fn obfuscated_ticket_age(&self) -> u32 {
let age_secs = self
.retrieved_at
.as_secs()
.saturating_sub(self.value.common.epoch);
let age_millis = age_secs as u32 * 1000;
age_millis.wrapping_add(self.value.age_add)
}
}
impl Retrieved<ClientSessionValue> {
pub fn tls13(&self) -> Option<Retrieved<&Tls13ClientSessionValue>> {
match &self.value {
ClientSessionValue::Tls13(value) => Some(Retrieved::new(value, self.retrieved_at)),
#[cfg(feature = "tls12")]
ClientSessionValue::Tls12(_) => None,
}
}
pub fn has_expired(&self) -> bool {
let common = self.value.common();
common.lifetime_secs != 0
&& common
.epoch
.saturating_add(u64::from(common.lifetime_secs))
< self.retrieved_at.as_secs()
}
}
impl<T> std::ops::Deref for Retrieved<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.value
}
}
#[derive(Debug)]
pub struct Tls13ClientSessionValue {
suite: &'static Tls13CipherSuite,
age_add: u32,
max_early_data_size: u32,
pub common: ClientSessionCommon,
}
impl Tls13ClientSessionValue {
pub fn new(
suite: &'static Tls13CipherSuite,
ticket: Vec<u8>,
secret: Vec<u8>,
server_cert_chain: Vec<key::Certificate>,
time_now: TimeBase,
lifetime_secs: u32,
age_add: u32,
max_early_data_size: u32,
) -> Self {
Self {
suite,
age_add,
max_early_data_size,
common: ClientSessionCommon::new(
ticket,
secret,
time_now,
lifetime_secs,
server_cert_chain,
),
}
}
/// [`Codec::read()`] with an extra `suite` argument.
///
/// We decode the `suite` argument separately because it allows us to
/// decide whether we're decoding an 1.2 or 1.3 session value.
pub fn read(suite: &'static Tls13CipherSuite, r: &mut Reader) -> Option<Self> {
Some(Self {
suite,
age_add: u32::read(r)?,
max_early_data_size: u32::read(r)?,
common: ClientSessionCommon::read(r)?,
})
}
/// Inherent implementation of the [`Codec::get_encoding()`] method.
///
/// (See `read()` for why this is inherent here.)
pub fn get_encoding(&self) -> Vec<u8> {
let mut bytes = Vec::with_capacity(16);
self.suite
.common
.suite
.encode(&mut bytes);
self.age_add.encode(&mut bytes);
self.max_early_data_size
.encode(&mut bytes);
self.common.encode(&mut bytes);
bytes
}
pub fn max_early_data_size(&self) -> u32 {
self.max_early_data_size
}
pub fn suite(&self) -> &'static Tls13CipherSuite {
self.suite
}
}
impl std::ops::Deref for Tls13ClientSessionValue {
type Target = ClientSessionCommon;
fn deref(&self) -> &Self::Target {
&self.common
}
}
#[cfg(feature = "tls12")]
#[derive(Debug)]
pub struct Tls12ClientSessionValue {
suite: &'static Tls12CipherSuite,
pub session_id: SessionID,
extended_ms: bool,
pub common: ClientSessionCommon,
}
#[cfg(feature = "tls12")]
impl Tls12ClientSessionValue {
pub fn new(
suite: &'static Tls12CipherSuite,
session_id: SessionID,
ticket: Vec<u8>,
master_secret: Vec<u8>,
server_cert_chain: Vec<key::Certificate>,
time_now: TimeBase,
lifetime_secs: u32,
extended_ms: bool,
) -> Self {
Self {
suite,
session_id,
extended_ms,
common: ClientSessionCommon::new(
ticket,
master_secret,
time_now,
lifetime_secs,
server_cert_chain,
),
}
}
/// [`Codec::read()`] with an extra `suite` argument.
///
/// We decode the `suite` argument separately because it allows us to
/// decide whether we're decoding an 1.2 or 1.3 session value.
fn read(suite: &'static Tls12CipherSuite, r: &mut Reader) -> Option<Self> {
Some(Self {
suite,
session_id: SessionID::read(r)?,
extended_ms: u8::read(r)? == 1,
common: ClientSessionCommon::read(r)?,
})
}
/// Inherent implementation of the [`Codec::get_encoding()`] method.
///
/// (See `read()` for why this is inherent here.)
pub fn get_encoding(&self) -> Vec<u8> {
let mut bytes = Vec::with_capacity(16);
self.suite
.common
.suite
.encode(&mut bytes);
self.session_id.encode(&mut bytes);
(u8::from(self.extended_ms)).encode(&mut bytes);
self.common.encode(&mut bytes);
bytes
}
pub fn take_ticket(&mut self) -> Vec<u8> {
mem::take(&mut self.common.ticket.0)
}
pub fn extended_ms(&self) -> bool {
self.extended_ms
}
pub fn suite(&self) -> &'static Tls12CipherSuite {
self.suite
}
}
#[cfg(feature = "tls12")]
impl std::ops::Deref for Tls12ClientSessionValue {
type Target = ClientSessionCommon;
fn deref(&self) -> &Self::Target {
&self.common
}
}
#[derive(Debug)]
pub struct ClientSessionCommon {
ticket: PayloadU16,
secret: PayloadU8,
epoch: u64,
lifetime_secs: u32,
server_cert_chain: CertificatePayload,
}
impl ClientSessionCommon {
fn new(
ticket: Vec<u8>,
secret: Vec<u8>,
time_now: TimeBase,
lifetime_secs: u32,
server_cert_chain: Vec<key::Certificate>,
) -> Self {
Self {
ticket: PayloadU16(ticket),
secret: PayloadU8(secret),
epoch: time_now.as_secs(),
lifetime_secs: cmp::min(lifetime_secs, MAX_TICKET_LIFETIME),
server_cert_chain,
}
}
/// [`Codec::read()`] is inherent here to avoid leaking the [`Codec`]
/// implementation through [`Deref`] implementations on
/// [`Tls12ClientSessionValue`] and [`Tls13ClientSessionValue`].
fn read(r: &mut Reader) -> Option<Self> {
Some(Self {
ticket: PayloadU16::read(r)?,
secret: PayloadU8::read(r)?,
epoch: u64::read(r)?,
lifetime_secs: u32::read(r)?,
server_cert_chain: CertificatePayload::read(r)?,
})
}
/// [`Codec::encode()`] is inherent here to avoid leaking the [`Codec`]
/// implementation through [`Deref`] implementations on
/// [`Tls12ClientSessionValue`] and [`Tls13ClientSessionValue`].
fn encode(&self, bytes: &mut Vec<u8>) {
self.ticket.encode(bytes);
self.secret.encode(bytes);
self.epoch.encode(bytes);
self.lifetime_secs.encode(bytes);
self.server_cert_chain.encode(bytes);
}
pub fn server_cert_chain(&self) -> &[key::Certificate] {
self.server_cert_chain.as_ref()
}
pub fn secret(&self) -> &[u8] {
self.secret.0.as_ref()
}
pub fn ticket(&self) -> &[u8] {
self.ticket.0.as_ref()
}
/// Test only: wind back epoch by delta seconds.
pub fn rewind_epoch(&mut self, delta: u32) {
self.epoch -= delta as u64;
}
}
static MAX_TICKET_LIFETIME: u32 = 7 * 24 * 60 * 60;
/// This is the maximum allowed skew between server and client clocks, over
/// the maximum ticket lifetime period. This encompasses TCP retransmission
/// times in case packet loss occurs when the client sends the ClientHello
/// or receives the NewSessionTicket, _and_ actual clock skew over this period.
static MAX_FRESHNESS_SKEW_MS: u32 = 60 * 1000;
// --- Server types ---
pub type ServerSessionKey = SessionID;
#[derive(Debug)]
pub struct ServerSessionValue {
pub sni: Option<webpki::DnsName>,
pub version: ProtocolVersion,
pub cipher_suite: CipherSuite,
pub master_secret: PayloadU8,
pub extended_ms: bool,
pub client_cert_chain: Option<CertificatePayload>,
pub alpn: Option<PayloadU8>,
pub application_data: PayloadU16,
pub creation_time_sec: u64,
pub age_obfuscation_offset: u32,
freshness: Option<bool>,
}
impl Codec for ServerSessionValue {
fn encode(&self, bytes: &mut Vec<u8>) {
if let Some(ref sni) = self.sni {
1u8.encode(bytes);
let sni_bytes: &str = sni.as_ref().into();
PayloadU8::new(Vec::from(sni_bytes)).encode(bytes);
} else {
0u8.encode(bytes);
}
self.version.encode(bytes);
self.cipher_suite.encode(bytes);
self.master_secret.encode(bytes);
(u8::from(self.extended_ms)).encode(bytes);
if let Some(ref chain) = self.client_cert_chain {
1u8.encode(bytes);
chain.encode(bytes);
} else {
0u8.encode(bytes);
}
if let Some(ref alpn) = self.alpn {
1u8.encode(bytes);
alpn.encode(bytes);
} else {
0u8.encode(bytes);
}
self.application_data.encode(bytes);
self.creation_time_sec.encode(bytes);
self.age_obfuscation_offset
.encode(bytes);
}
fn read(r: &mut Reader) -> Option<Self> {
let has_sni = u8::read(r)?;
let sni = if has_sni == 1 {
let dns_name = PayloadU8::read(r)?;
let dns_name = webpki::DnsNameRef::try_from_ascii(&dns_name.0).ok()?;
Some(dns_name.into())
} else {
None
};
let v = ProtocolVersion::read(r)?;
let cs = CipherSuite::read(r)?;
let ms = PayloadU8::read(r)?;
let ems = u8::read(r)?;
let has_ccert = u8::read(r)? == 1;
let ccert = if has_ccert {
Some(CertificatePayload::read(r)?)
} else {
None
};
let has_alpn = u8::read(r)? == 1;
let alpn = if has_alpn {
Some(PayloadU8::read(r)?)
} else {
None
};
let application_data = PayloadU16::read(r)?;
let creation_time_sec = u64::read(r)?;
let age_obfuscation_offset = u32::read(r)?;
Some(Self {
sni,
version: v,
cipher_suite: cs,
master_secret: ms,
extended_ms: ems == 1u8,
client_cert_chain: ccert,
alpn,
application_data,
creation_time_sec,
age_obfuscation_offset,
freshness: None,
})
}
}
impl ServerSessionValue {
pub fn new(
sni: Option<&webpki::DnsName>,
v: ProtocolVersion,
cs: CipherSuite,
ms: Vec<u8>,
client_cert_chain: Option<CertificatePayload>,
alpn: Option<Vec<u8>>,
application_data: Vec<u8>,
creation_time: TimeBase,
age_obfuscation_offset: u32,
) -> Self {
Self {
sni: sni.cloned(),
version: v,
cipher_suite: cs,
master_secret: PayloadU8::new(ms),
extended_ms: false,
client_cert_chain,
alpn: alpn.map(PayloadU8::new),
application_data: PayloadU16::new(application_data),
creation_time_sec: creation_time.as_secs(),
age_obfuscation_offset,
freshness: None,
}
}
pub fn set_extended_ms_used(&mut self) {
self.extended_ms = true;
}
pub fn set_freshness(mut self, obfuscated_client_age_ms: u32, time_now: TimeBase) -> Self {
let client_age_ms = obfuscated_client_age_ms.wrapping_sub(self.age_obfuscation_offset);
let server_age_ms = (time_now
.as_secs()
.saturating_sub(self.creation_time_sec) as u32)
.saturating_mul(1000);
let age_difference = if client_age_ms < server_age_ms {
server_age_ms - client_age_ms
} else {
client_age_ms - server_age_ms
};
self.freshness = Some(age_difference <= MAX_FRESHNESS_SKEW_MS);
self
}
pub fn is_fresh(&self) -> bool {
self.freshness.unwrap_or_default()
}
}

View file

@ -0,0 +1,78 @@
use super::codec::{Codec, Reader};
use super::persist::*;
use crate::enums::*;
use crate::key::Certificate;
use crate::ticketer::TimeBase;
use crate::tls13::TLS13_AES_128_GCM_SHA256;
use std::convert::TryInto;
#[test]
fn clientsessionkey_is_debug() {
let name = "hello".try_into().unwrap();
let csk = ClientSessionKey::session_for_server_name(&name);
println!("{:?}", csk);
}
#[test]
fn clientsessionkey_cannot_be_read() {
let bytes = [0; 1];
let mut rd = Reader::init(&bytes);
assert!(ClientSessionKey::read(&mut rd).is_none());
}
#[test]
fn clientsessionvalue_is_debug() {
let csv = ClientSessionValue::from(Tls13ClientSessionValue::new(
TLS13_AES_128_GCM_SHA256
.tls13()
.unwrap(),
vec![],
vec![1, 2, 3],
vec![Certificate(b"abc".to_vec()), Certificate(b"def".to_vec())],
TimeBase::now().unwrap(),
15,
10,
128,
));
println!("{:?}", csv);
}
#[test]
fn serversessionvalue_is_debug() {
let ssv = ServerSessionValue::new(
None,
ProtocolVersion::TLSv1_3,
CipherSuite::TLS13_AES_128_GCM_SHA256,
vec![1, 2, 3],
None,
None,
vec![4, 5, 6],
TimeBase::now().unwrap(),
0x12345678,
);
println!("{:?}", ssv);
}
#[test]
fn serversessionvalue_no_sni() {
let bytes = [
0x00, 0x03, 0x03, 0xc0, 0x23, 0x03, 0x01, 0x02, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12,
0x23, 0x34, 0x45, 0x56, 0x67, 0x78, 0x89, 0xfe, 0xed, 0xf0, 0x0d,
];
let mut rd = Reader::init(&bytes);
let ssv = ServerSessionValue::read(&mut rd).unwrap();
assert_eq!(ssv.get_encoding(), bytes);
}
#[test]
fn serversessionvalue_with_cert() {
let bytes = [
0x00, 0x03, 0x03, 0xc0, 0x23, 0x03, 0x01, 0x02, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12,
0x23, 0x34, 0x45, 0x56, 0x67, 0x78, 0x89, 0xfe, 0xed, 0xf0, 0x0d,
];
let mut rd = Reader::init(&bytes);
let ssv = ServerSessionValue::read(&mut rd).unwrap();
assert_eq!(ssv.get_encoding(), bytes);
}

View file

@ -0,0 +1,620 @@
/// This module contains optional APIs for implementing QUIC TLS.
use crate::cipher::{Iv, IvLen};
pub use crate::client::ClientQuicExt;
use crate::conn::CommonState;
use crate::error::Error;
use crate::msgs::enums::AlertDescription;
pub use crate::server::ServerQuicExt;
use crate::suites::BulkAlgorithm;
use crate::tls13::key_schedule::hkdf_expand;
use crate::tls13::{Tls13CipherSuite, TLS13_AES_128_GCM_SHA256_INTERNAL};
use std::fmt::Debug;
use ring::{aead, hkdf};
/// Secrets used to encrypt/decrypt traffic
#[derive(Clone, Debug)]
pub struct Secrets {
/// Secret used to encrypt packets transmitted by the client
client: hkdf::Prk,
/// Secret used to encrypt packets transmitted by the server
server: hkdf::Prk,
/// Cipher suite used with these secrets
suite: &'static Tls13CipherSuite,
is_client: bool,
}
impl Secrets {
pub(crate) fn new(
client: hkdf::Prk,
server: hkdf::Prk,
suite: &'static Tls13CipherSuite,
is_client: bool,
) -> Self {
Self {
client,
server,
suite,
is_client,
}
}
/// Derive the next set of packet keys
pub fn next_packet_keys(&mut self) -> PacketKeySet {
let keys = PacketKeySet::new(self);
self.update();
keys
}
fn update(&mut self) {
let hkdf_alg = self.suite.hkdf_algorithm;
self.client = hkdf_expand(&self.client, hkdf_alg, b"quic ku", &[]);
self.server = hkdf_expand(&self.server, hkdf_alg, b"quic ku", &[]);
}
fn local_remote(&self) -> (&hkdf::Prk, &hkdf::Prk) {
if self.is_client {
(&self.client, &self.server)
} else {
(&self.server, &self.client)
}
}
}
/// Generic methods for QUIC sessions
pub trait QuicExt {
/// Return the TLS-encoded transport parameters for the session's peer.
///
/// While the transport parameters are technically available prior to the
/// completion of the handshake, they cannot be fully trusted until the
/// handshake completes, and reliance on them should be minimized.
/// However, any tampering with the parameters will cause the handshake
/// to fail.
fn quic_transport_parameters(&self) -> Option<&[u8]>;
/// Compute the keys for encrypting/decrypting 0-RTT packets, if available
fn zero_rtt_keys(&self) -> Option<DirectionalKeys>;
/// Consume unencrypted TLS handshake data.
///
/// Handshake data obtained from separate encryption levels should be supplied in separate calls.
fn read_hs(&mut self, plaintext: &[u8]) -> Result<(), Error>;
/// Emit unencrypted TLS handshake data.
///
/// When this returns `Some(_)`, the new keys must be used for future handshake data.
fn write_hs(&mut self, buf: &mut Vec<u8>) -> Option<KeyChange>;
/// Emit the TLS description code of a fatal alert, if one has arisen.
///
/// Check after `read_hs` returns `Err(_)`.
fn alert(&self) -> Option<AlertDescription>;
}
/// Keys used to communicate in a single direction
pub struct DirectionalKeys {
/// Encrypts or decrypts a packet's headers
pub header: HeaderProtectionKey,
/// Encrypts or decrypts the payload of a packet
pub packet: PacketKey,
}
impl DirectionalKeys {
pub(crate) fn new(suite: &'static Tls13CipherSuite, secret: &hkdf::Prk) -> Self {
Self {
header: HeaderProtectionKey::new(suite, secret),
packet: PacketKey::new(suite, secret),
}
}
}
/// A QUIC header protection key
pub struct HeaderProtectionKey(aead::quic::HeaderProtectionKey);
impl HeaderProtectionKey {
fn new(suite: &'static Tls13CipherSuite, secret: &hkdf::Prk) -> Self {
let alg = match suite.common.bulk {
BulkAlgorithm::Aes128Gcm => &aead::quic::AES_128,
BulkAlgorithm::Aes256Gcm => &aead::quic::AES_256,
BulkAlgorithm::Chacha20Poly1305 => &aead::quic::CHACHA20,
};
Self(hkdf_expand(secret, alg, b"quic hp", &[]))
}
/// Adds QUIC Header Protection.
///
/// `sample` must contain the sample of encrypted payload; see
/// [Header Protection Sample].
///
/// `first` must reference the first byte of the header, referred to as
/// `packet[0]` in [Header Protection Application].
///
/// `packet_number` must reference the Packet Number field; this is
/// `packet[pn_offset:pn_offset+pn_length]` in [Header Protection Application].
///
/// Returns an error without modifying anything if `sample` is not
/// the correct length (see [Header Protection Sample] and [`Self::sample_len()`]),
/// or `packet_number` is longer than allowed (see [Packet Number Encoding and Decoding]).
///
/// Otherwise, `first` and `packet_number` will have the header protection added.
///
/// [Header Protection Application]: https://datatracker.ietf.org/doc/html/rfc9001#section-5.4.1
/// [Header Protection Sample]: https://datatracker.ietf.org/doc/html/rfc9001#section-5.4.2
/// [Packet Number Encoding and Decoding]: https://datatracker.ietf.org/doc/html/rfc9000#section-17.1
#[inline]
pub fn encrypt_in_place(
&self,
sample: &[u8],
first: &mut u8,
packet_number: &mut [u8],
) -> Result<(), Error> {
self.xor_in_place(sample, first, packet_number, false)
}
/// Removes QUIC Header Protection.
///
/// `sample` must contain the sample of encrypted payload; see
/// [Header Protection Sample].
///
/// `first` must reference the first byte of the header, referred to as
/// `packet[0]` in [Header Protection Application].
///
/// `packet_number` must reference the Packet Number field; this is
/// `packet[pn_offset:pn_offset+pn_length]` in [Header Protection Application].
///
/// Returns an error without modifying anything if `sample` is not
/// the correct length (see [Header Protection Sample] and [`Self::sample_len()`]),
/// or `packet_number` is longer than allowed (see
/// [Packet Number Encoding and Decoding]).
///
/// Otherwise, `first` and `packet_number` will have the header protection removed.
///
/// [Header Protection Application]: https://datatracker.ietf.org/doc/html/rfc9001#section-5.4.1
/// [Header Protection Sample]: https://datatracker.ietf.org/doc/html/rfc9001#section-5.4.2
/// [Packet Number Encoding and Decoding]: https://datatracker.ietf.org/doc/html/rfc9000#section-17.1
#[inline]
pub fn decrypt_in_place(
&self,
sample: &[u8],
first: &mut u8,
packet_number: &mut [u8],
) -> Result<(), Error> {
self.xor_in_place(sample, first, packet_number, true)
}
fn xor_in_place(
&self,
sample: &[u8],
first: &mut u8,
packet_number: &mut [u8],
masked: bool,
) -> Result<(), Error> {
// This implements [Header Protection Application] almost verbatim.
let mask = self
.0
.new_mask(sample)
.map_err(|_| Error::General("sample of invalid length".into()))?;
// The `unwrap()` will not panic because `new_mask` returns a
// non-empty result.
let (first_mask, pn_mask) = mask.split_first().unwrap();
// It is OK for the `mask` to be longer than `packet_number`,
// but a valid `packet_number` will never be longer than `mask`.
if packet_number.len() > pn_mask.len() {
return Err(Error::General("packet number too long".into()));
}
// Infallible from this point on. Before this point, `first` and
// `packet_number` are unchanged.
const LONG_HEADER_FORM: u8 = 0x80;
let bits = match *first & LONG_HEADER_FORM == LONG_HEADER_FORM {
true => 0x0f, // Long header: 4 bits masked
false => 0x1f, // Short header: 5 bits masked
};
let first_plain = match masked {
// When unmasking, use the packet length bits after unmasking
true => *first ^ (first_mask & bits),
// When masking, use the packet length bits before masking
false => *first,
};
let pn_len = (first_plain & 0x03) as usize + 1;
*first ^= first_mask & bits;
for (dst, m) in packet_number
.iter_mut()
.zip(pn_mask)
.take(pn_len)
{
*dst ^= m;
}
Ok(())
}
/// Expected sample length for the key's algorithm
#[inline]
pub fn sample_len(&self) -> usize {
self.0.algorithm().sample_len()
}
}
/// Keys to encrypt or decrypt the payload of a packet
pub struct PacketKey {
/// Encrypts or decrypts a packet's payload
key: aead::LessSafeKey,
/// Computes unique nonces for each packet
iv: Iv,
/// The cipher suite used for this packet key
suite: &'static Tls13CipherSuite,
}
impl PacketKey {
fn new(suite: &'static Tls13CipherSuite, secret: &hkdf::Prk) -> Self {
Self {
key: aead::LessSafeKey::new(hkdf_expand(
secret,
suite.common.aead_algorithm,
b"quic key",
&[],
)),
iv: hkdf_expand(secret, IvLen, b"quic iv", &[]),
suite,
}
}
/// Encrypt a QUIC packet
///
/// Takes a `packet_number`, used to derive the nonce; the packet `header`, which is used as
/// the additional authenticated data; and the `payload`. The authentication tag is returned if
/// encryption succeeds.
///
/// Fails iff the payload is longer than allowed by the cipher suite's AEAD algorithm.
pub fn encrypt_in_place(
&self,
packet_number: u64,
header: &[u8],
payload: &mut [u8],
) -> Result<Tag, Error> {
let aad = aead::Aad::from(header);
let nonce = nonce_for(packet_number, &self.iv);
let tag = self
.key
.seal_in_place_separate_tag(nonce, aad, payload)
.map_err(|_| Error::EncryptError)?;
Ok(Tag(tag))
}
/// Decrypt a QUIC packet
///
/// Takes the packet `header`, which is used as the additional authenticated data, and the
/// `payload`, which includes the authentication tag.
///
/// If the return value is `Ok`, the decrypted payload can be found in `payload`, up to the
/// length found in the return value.
pub fn decrypt_in_place<'a>(
&self,
packet_number: u64,
header: &[u8],
payload: &'a mut [u8],
) -> Result<&'a [u8], Error> {
let payload_len = payload.len();
let aad = aead::Aad::from(header);
let nonce = nonce_for(packet_number, &self.iv);
self.key
.open_in_place(nonce, aad, payload)
.map_err(|_| Error::DecryptError)?;
let plain_len = payload_len - self.key.algorithm().tag_len();
Ok(&payload[..plain_len])
}
/// Number of times the packet key can be used without sacrificing confidentiality
///
/// See <https://www.rfc-editor.org/rfc/rfc9001.html#name-confidentiality-limit>.
#[inline]
pub fn confidentiality_limit(&self) -> u64 {
self.suite.confidentiality_limit
}
/// Number of times the packet key can be used without sacrificing integrity
///
/// See <https://www.rfc-editor.org/rfc/rfc9001.html#name-integrity-limit>.
#[inline]
pub fn integrity_limit(&self) -> u64 {
self.suite.integrity_limit
}
/// Tag length for the underlying AEAD algorithm
#[inline]
pub fn tag_len(&self) -> usize {
self.key.algorithm().tag_len()
}
}
/// AEAD tag, must be appended to encrypted cipher text
pub struct Tag(aead::Tag);
impl AsRef<[u8]> for Tag {
#[inline]
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}
/// Packet protection keys for bidirectional 1-RTT communication
pub struct PacketKeySet {
/// Encrypts outgoing packets
pub local: PacketKey,
/// Decrypts incoming packets
pub remote: PacketKey,
}
impl PacketKeySet {
fn new(secrets: &Secrets) -> Self {
let (local, remote) = secrets.local_remote();
Self {
local: PacketKey::new(secrets.suite, local),
remote: PacketKey::new(secrets.suite, remote),
}
}
}
/// Complete set of keys used to communicate with the peer
pub struct Keys {
/// Encrypts outgoing packets
pub local: DirectionalKeys,
/// Decrypts incoming packets
pub remote: DirectionalKeys,
}
impl Keys {
/// Construct keys for use with initial packets
pub fn initial(version: Version, client_dst_connection_id: &[u8], is_client: bool) -> Self {
const CLIENT_LABEL: &[u8] = b"client in";
const SERVER_LABEL: &[u8] = b"server in";
let salt = version.initial_salt();
let hs_secret = hkdf::Salt::new(hkdf::HKDF_SHA256, salt).extract(client_dst_connection_id);
let secrets = Secrets {
client: hkdf_expand(&hs_secret, hkdf::HKDF_SHA256, CLIENT_LABEL, &[]),
server: hkdf_expand(&hs_secret, hkdf::HKDF_SHA256, SERVER_LABEL, &[]),
suite: TLS13_AES_128_GCM_SHA256_INTERNAL,
is_client,
};
Self::new(&secrets)
}
fn new(secrets: &Secrets) -> Self {
let (local, remote) = secrets.local_remote();
Self {
local: DirectionalKeys::new(secrets.suite, local),
remote: DirectionalKeys::new(secrets.suite, remote),
}
}
}
pub(crate) fn write_hs(this: &mut CommonState, buf: &mut Vec<u8>) -> Option<KeyChange> {
while let Some((_, msg)) = this.quic.hs_queue.pop_front() {
buf.extend_from_slice(&msg);
if let Some(&(true, _)) = this.quic.hs_queue.front() {
if this.quic.hs_secrets.is_some() {
// Allow the caller to switch keys before proceeding.
break;
}
}
}
if let Some(secrets) = this.quic.hs_secrets.take() {
return Some(KeyChange::Handshake {
keys: Keys::new(&secrets),
});
}
if let Some(mut secrets) = this.quic.traffic_secrets.take() {
if !this.quic.returned_traffic_keys {
this.quic.returned_traffic_keys = true;
let keys = Keys::new(&secrets);
secrets.update();
return Some(KeyChange::OneRtt {
keys,
next: secrets,
});
}
}
None
}
/// Key material for use in QUIC packet spaces
///
/// QUIC uses 4 different sets of keys (and progressive key updates for long-running connections):
///
/// * Initial: these can be created from [`Keys::initial()`]
/// * 0-RTT keys: can be retrieved from [`QuicExt::zero_rtt_keys()`]
/// * Handshake: these are returned from [`QuicExt::write_hs()`] after `ClientHello` and
/// `ServerHello` messages have been exchanged
/// * 1-RTT keys: these are returned from [`QuicExt::write_hs()`] after the handshake is done
///
/// Once the 1-RTT keys have been exchanged, either side may initiate a key update. Progressive
/// update keys can be obtained from the [`Secrets`] returned in [`KeyChange::OneRtt`]. Note that
/// only packet keys are updated by key updates; header protection keys remain the same.
#[allow(clippy::large_enum_variant)]
pub enum KeyChange {
/// Keys for the handshake space
Handshake {
/// Header and packet keys for the handshake space
keys: Keys,
},
/// Keys for 1-RTT data
OneRtt {
/// Header and packet keys for 1-RTT data
keys: Keys,
/// Secrets to derive updated keys from
next: Secrets,
},
}
/// Compute the nonce to use for encrypting or decrypting `packet_number`
fn nonce_for(packet_number: u64, iv: &Iv) -> ring::aead::Nonce {
let mut out = [0; aead::NONCE_LEN];
out[4..].copy_from_slice(&packet_number.to_be_bytes());
for (out, inp) in out.iter_mut().zip(iv.0.iter()) {
*out ^= inp;
}
aead::Nonce::assume_unique_for_key(out)
}
/// QUIC protocol version
///
/// Governs version-specific behavior in the TLS layer
#[non_exhaustive]
#[derive(Clone, Copy, Debug)]
pub enum Version {
/// Draft versions 29, 30, 31 and 32
V1Draft,
/// First stable RFC
V1,
}
impl Version {
fn initial_salt(self) -> &'static [u8; 20] {
match self {
Self::V1Draft => &[
// https://datatracker.ietf.org/doc/html/draft-ietf-quic-tls-32#section-5.2
0xaf, 0xbf, 0xec, 0x28, 0x99, 0x93, 0xd2, 0x4c, 0x9e, 0x97, 0x86, 0xf1, 0x9c, 0x61,
0x11, 0xe0, 0x43, 0x90, 0xa8, 0x99,
],
Self::V1 => &[
// https://www.rfc-editor.org/rfc/rfc9001.html#name-initial-secrets
0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8,
0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a,
],
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn short_packet_header_protection() {
// https://www.rfc-editor.org/rfc/rfc9001.html#name-chacha20-poly1305-short-hea
const PN: u64 = 654360564;
const SECRET: &[u8] = &[
0x9a, 0xc3, 0x12, 0xa7, 0xf8, 0x77, 0x46, 0x8e, 0xbe, 0x69, 0x42, 0x27, 0x48, 0xad,
0x00, 0xa1, 0x54, 0x43, 0xf1, 0x82, 0x03, 0xa0, 0x7d, 0x60, 0x60, 0xf6, 0x88, 0xf3,
0x0f, 0x21, 0x63, 0x2b,
];
let secret = hkdf::Prk::new_less_safe(hkdf::HKDF_SHA256, SECRET);
use crate::tls13::TLS13_CHACHA20_POLY1305_SHA256_INTERNAL;
let hpk = HeaderProtectionKey::new(TLS13_CHACHA20_POLY1305_SHA256_INTERNAL, &secret);
let packet = PacketKey::new(TLS13_CHACHA20_POLY1305_SHA256_INTERNAL, &secret);
const PLAIN: &[u8] = &[0x42, 0x00, 0xbf, 0xf4, 0x01];
let mut buf = PLAIN.to_vec();
let (header, payload) = buf.split_at_mut(4);
let tag = packet
.encrypt_in_place(PN, &*header, payload)
.unwrap();
buf.extend(tag.as_ref());
let pn_offset = 1;
let (header, sample) = buf.split_at_mut(pn_offset + 4);
let (first, rest) = header.split_at_mut(1);
let sample = &sample[..hpk.sample_len()];
hpk.encrypt_in_place(sample, &mut first[0], dbg!(rest))
.unwrap();
const PROTECTED: &[u8] = &[
0x4c, 0xfe, 0x41, 0x89, 0x65, 0x5e, 0x5c, 0xd5, 0x5c, 0x41, 0xf6, 0x90, 0x80, 0x57,
0x5d, 0x79, 0x99, 0xc2, 0x5a, 0x5b, 0xfb,
];
assert_eq!(&buf, PROTECTED);
let (header, sample) = buf.split_at_mut(pn_offset + 4);
let (first, rest) = header.split_at_mut(1);
let sample = &sample[..hpk.sample_len()];
hpk.decrypt_in_place(sample, &mut first[0], rest)
.unwrap();
let (header, payload_tag) = buf.split_at_mut(4);
let plain = packet
.decrypt_in_place(PN, &*header, payload_tag)
.unwrap();
assert_eq!(plain, &PLAIN[4..]);
}
#[test]
fn key_update_test_vector() {
fn equal_prk(x: &hkdf::Prk, y: &hkdf::Prk) -> bool {
let mut x_data = [0; 16];
let mut y_data = [0; 16];
let x_okm = x
.expand(&[b"info"], &aead::quic::AES_128)
.unwrap();
x_okm.fill(&mut x_data[..]).unwrap();
let y_okm = y
.expand(&[b"info"], &aead::quic::AES_128)
.unwrap();
y_okm.fill(&mut y_data[..]).unwrap();
x_data == y_data
}
let mut secrets = Secrets {
// Constant dummy values for reproducibility
client: hkdf::Prk::new_less_safe(
hkdf::HKDF_SHA256,
&[
0xb8, 0x76, 0x77, 0x08, 0xf8, 0x77, 0x23, 0x58, 0xa6, 0xea, 0x9f, 0xc4, 0x3e,
0x4a, 0xdd, 0x2c, 0x96, 0x1b, 0x3f, 0x52, 0x87, 0xa6, 0xd1, 0x46, 0x7e, 0xe0,
0xae, 0xab, 0x33, 0x72, 0x4d, 0xbf,
],
),
server: hkdf::Prk::new_less_safe(
hkdf::HKDF_SHA256,
&[
0x42, 0xdc, 0x97, 0x21, 0x40, 0xe0, 0xf2, 0xe3, 0x98, 0x45, 0xb7, 0x67, 0x61,
0x34, 0x39, 0xdc, 0x67, 0x58, 0xca, 0x43, 0x25, 0x9b, 0x87, 0x85, 0x06, 0x82,
0x4e, 0xb1, 0xe4, 0x38, 0xd8, 0x55,
],
),
suite: TLS13_AES_128_GCM_SHA256_INTERNAL,
is_client: true,
};
secrets.update();
assert!(equal_prk(
&secrets.client,
&hkdf::Prk::new_less_safe(
hkdf::HKDF_SHA256,
&[
0x42, 0xca, 0xc8, 0xc9, 0x1c, 0xd5, 0xeb, 0x40, 0x68, 0x2e, 0x43, 0x2e, 0xdf,
0x2d, 0x2b, 0xe9, 0xf4, 0x1a, 0x52, 0xca, 0x6b, 0x22, 0xd8, 0xe6, 0xcd, 0xb1,
0xe8, 0xac, 0xa9, 0x6, 0x1f, 0xce
]
)
));
assert!(equal_prk(
&secrets.server,
&hkdf::Prk::new_less_safe(
hkdf::HKDF_SHA256,
&[
0xeb, 0x7f, 0x5e, 0x2a, 0x12, 0x3f, 0x40, 0x7d, 0xb4, 0x99, 0xe3, 0x61, 0xca,
0xe5, 0x90, 0xd4, 0xd9, 0x92, 0xe1, 0x4b, 0x7a, 0xce, 0x3, 0xc2, 0x44, 0xe0,
0x42, 0x21, 0x15, 0xb6, 0xd3, 0x8a
]
)
));
}
}

View file

@ -0,0 +1,30 @@
use crate::msgs::codec;
/// The single place where we generate random material
/// for our own use. These functions never fail,
/// they panic on error.
use ring::rand::{SecureRandom, SystemRandom};
/// Fill the whole slice with random material.
pub(crate) fn fill_random(bytes: &mut [u8]) -> Result<(), GetRandomFailed> {
SystemRandom::new()
.fill(bytes)
.map_err(|_| GetRandomFailed)
}
/// Make a Vec<u8> of the given size
/// containing random material.
pub(crate) fn random_vec(len: usize) -> Result<Vec<u8>, GetRandomFailed> {
let mut v = vec![0; len];
fill_random(&mut v)?;
Ok(v)
}
/// Return a uniformly random u32.
pub(crate) fn random_u32() -> Result<u32, GetRandomFailed> {
let mut buf = [0u8; 4];
fill_random(&mut buf)?;
codec::decode_u32(&buf).ok_or(GetRandomFailed)
}
#[derive(Debug)]
pub struct GetRandomFailed;

View file

@ -0,0 +1,192 @@
use crate::cipher::{MessageDecrypter, MessageEncrypter};
use crate::error::Error;
use crate::msgs::message::{BorrowedPlainMessage, OpaqueMessage, PlainMessage};
static SEQ_SOFT_LIMIT: u64 = 0xffff_ffff_ffff_0000u64;
static SEQ_HARD_LIMIT: u64 = 0xffff_ffff_ffff_fffeu64;
#[derive(PartialEq)]
enum DirectionState {
/// No keying material.
Invalid,
/// Keying material present, but not yet in use.
Prepared,
/// Keying material in use.
Active,
}
pub(crate) struct RecordLayer {
message_encrypter: Box<dyn MessageEncrypter>,
message_decrypter: Box<dyn MessageDecrypter>,
write_seq: u64,
read_seq: u64,
encrypt_state: DirectionState,
decrypt_state: DirectionState,
// Message encrypted with other keys may be encountered, so failures
// should be swallowed by the caller. This struct tracks the amount
// of message size this is allowed for.
trial_decryption_len: Option<usize>,
}
impl RecordLayer {
pub(crate) fn new() -> Self {
Self {
message_encrypter: <dyn MessageEncrypter>::invalid(),
message_decrypter: <dyn MessageDecrypter>::invalid(),
write_seq: 0,
read_seq: 0,
encrypt_state: DirectionState::Invalid,
decrypt_state: DirectionState::Invalid,
trial_decryption_len: None,
}
}
pub(crate) fn is_encrypting(&self) -> bool {
self.encrypt_state == DirectionState::Active
}
pub(crate) fn is_decrypting(&self) -> bool {
self.decrypt_state == DirectionState::Active
}
#[cfg(feature = "secret_extraction")]
pub(crate) fn write_seq(&self) -> u64 {
self.write_seq
}
#[cfg(feature = "secret_extraction")]
pub(crate) fn read_seq(&self) -> u64 {
self.read_seq
}
pub(crate) fn doing_trial_decryption(&mut self, requested: usize) -> bool {
match self
.trial_decryption_len
.and_then(|value| value.checked_sub(requested))
{
Some(remaining) => {
self.trial_decryption_len = Some(remaining);
true
}
_ => false,
}
}
/// Prepare to use the given `MessageEncrypter` for future message encryption.
/// It is not used until you call `start_encrypting`.
pub(crate) fn prepare_message_encrypter(&mut self, cipher: Box<dyn MessageEncrypter>) {
self.message_encrypter = cipher;
self.write_seq = 0;
self.encrypt_state = DirectionState::Prepared;
}
/// Prepare to use the given `MessageDecrypter` for future message decryption.
/// It is not used until you call `start_decrypting`.
pub(crate) fn prepare_message_decrypter(&mut self, cipher: Box<dyn MessageDecrypter>) {
self.message_decrypter = cipher;
self.read_seq = 0;
self.decrypt_state = DirectionState::Prepared;
}
/// Start using the `MessageEncrypter` previously provided to the previous
/// call to `prepare_message_encrypter`.
pub(crate) fn start_encrypting(&mut self) {
debug_assert!(self.encrypt_state == DirectionState::Prepared);
self.encrypt_state = DirectionState::Active;
}
/// Start using the `MessageDecrypter` previously provided to the previous
/// call to `prepare_message_decrypter`.
pub(crate) fn start_decrypting(&mut self) {
debug_assert!(self.decrypt_state == DirectionState::Prepared);
self.decrypt_state = DirectionState::Active;
}
/// Set and start using the given `MessageEncrypter` for future outgoing
/// message encryption.
pub(crate) fn set_message_encrypter(&mut self, cipher: Box<dyn MessageEncrypter>) {
self.prepare_message_encrypter(cipher);
self.start_encrypting();
}
/// Set and start using the given `MessageDecrypter` for future incoming
/// message decryption.
pub(crate) fn set_message_decrypter(&mut self, cipher: Box<dyn MessageDecrypter>) {
self.prepare_message_decrypter(cipher);
self.start_decrypting();
self.trial_decryption_len = None;
}
/// Set and start using the given `MessageDecrypter` for future incoming
/// message decryption, and enable "trial decryption" mode for when TLS1.3
/// 0-RTT is attempted but rejected by the server.
pub(crate) fn set_message_decrypter_with_trial_decryption(
&mut self,
cipher: Box<dyn MessageDecrypter>,
max_length: usize,
) {
self.prepare_message_decrypter(cipher);
self.start_decrypting();
self.trial_decryption_len = Some(max_length);
}
pub(crate) fn finish_trial_decryption(&mut self) {
self.trial_decryption_len = None;
}
/// Return true if the peer appears to getting close to encrypting
/// too many messages with this key.
///
/// Perhaps if we send an alert well before their counter wraps, a
/// buggy peer won't make a terrible mistake here?
///
/// Note that there's no reason to refuse to decrypt: the security
/// failure has already happened.
pub(crate) fn wants_close_before_decrypt(&self) -> bool {
self.read_seq == SEQ_SOFT_LIMIT
}
/// Return true if we are getting close to encrypting too many
/// messages with our encryption key.
pub(crate) fn wants_close_before_encrypt(&self) -> bool {
self.write_seq == SEQ_SOFT_LIMIT
}
/// Return true if we outright refuse to do anything with the
/// encryption key.
pub(crate) fn encrypt_exhausted(&self) -> bool {
self.write_seq >= SEQ_HARD_LIMIT
}
/// Decrypt a TLS message.
///
/// `encr` is a decoded message allegedly received from the peer.
/// If it can be decrypted, its decryption is returned. Otherwise,
/// an error is returned.
pub(crate) fn decrypt_incoming(&mut self, encr: OpaqueMessage) -> Result<PlainMessage, Error> {
debug_assert!(self.is_decrypting());
let seq = self.read_seq;
let msg = self
.message_decrypter
.decrypt(encr, seq)?;
self.read_seq += 1;
Ok(msg)
}
/// Encrypt a TLS message.
///
/// `plain` is a TLS message we'd like to send. This function
/// panics if the requisite keying material hasn't been established yet.
pub(crate) fn encrypt_outgoing(&mut self, plain: BorrowedPlainMessage) -> OpaqueMessage {
debug_assert!(self.encrypt_state == DirectionState::Active);
assert!(!self.encrypt_exhausted());
let seq = self.write_seq;
self.write_seq += 1;
self.message_encrypter
.encrypt(plain, seq)
.unwrap()
}
}

View file

@ -0,0 +1,116 @@
use crate::builder::{ConfigBuilder, WantsVerifier};
use crate::error::Error;
use crate::key;
use crate::kx::SupportedKxGroup;
use crate::server::handy;
use crate::server::{ResolvesServerCert, ServerConfig};
use crate::suites::SupportedCipherSuite;
use crate::verify;
use crate::versions;
use crate::NoKeyLog;
use std::marker::PhantomData;
use std::sync::Arc;
impl ConfigBuilder<ServerConfig, WantsVerifier> {
/// Choose how to verify client certificates.
pub fn with_client_cert_verifier(
self,
client_cert_verifier: Arc<dyn verify::ClientCertVerifier>,
) -> ConfigBuilder<ServerConfig, WantsServerCert> {
ConfigBuilder {
state: WantsServerCert {
cipher_suites: self.state.cipher_suites,
kx_groups: self.state.kx_groups,
versions: self.state.versions,
verifier: client_cert_verifier,
},
side: PhantomData::default(),
}
}
/// Disable client authentication.
pub fn with_no_client_auth(self) -> ConfigBuilder<ServerConfig, WantsServerCert> {
self.with_client_cert_verifier(verify::NoClientAuth::new())
}
}
/// A config builder state where the caller must supply how to provide a server certificate to
/// the connecting peer.
///
/// For more information, see the [`ConfigBuilder`] documentation.
#[derive(Clone, Debug)]
pub struct WantsServerCert {
cipher_suites: Vec<SupportedCipherSuite>,
kx_groups: Vec<&'static SupportedKxGroup>,
versions: versions::EnabledVersions,
verifier: Arc<dyn verify::ClientCertVerifier>,
}
impl ConfigBuilder<ServerConfig, WantsServerCert> {
/// Sets a single certificate chain and matching private key. This
/// certificate and key is used for all subsequent connections,
/// irrespective of things like SNI hostname.
///
/// Note that the end-entity certificate must have the
/// [Subject Alternative Name](https://tools.ietf.org/html/rfc6125#section-4.1)
/// extension to describe, e.g., the valid DNS name. The `commonName` field is
/// disregarded.
///
/// `cert_chain` is a vector of DER-encoded certificates.
/// `key_der` is a DER-encoded RSA, ECDSA, or Ed25519 private key.
///
/// This function fails if `key_der` is invalid.
pub fn with_single_cert(
self,
cert_chain: Vec<key::Certificate>,
key_der: key::PrivateKey,
) -> Result<ServerConfig, Error> {
let resolver = handy::AlwaysResolvesChain::new(cert_chain, &key_der)?;
Ok(self.with_cert_resolver(Arc::new(resolver)))
}
/// Sets a single certificate chain, matching private key, OCSP
/// response and SCTs. This certificate and key is used for all
/// subsequent connections, irrespective of things like SNI hostname.
///
/// `cert_chain` is a vector of DER-encoded certificates.
/// `key_der` is a DER-encoded RSA, ECDSA, or Ed25519 private key.
/// `ocsp` is a DER-encoded OCSP response. Ignored if zero length.
/// `scts` is an `SignedCertificateTimestampList` encoding (see RFC6962)
/// and is ignored if empty.
///
/// This function fails if `key_der` is invalid.
pub fn with_single_cert_with_ocsp_and_sct(
self,
cert_chain: Vec<key::Certificate>,
key_der: key::PrivateKey,
ocsp: Vec<u8>,
scts: Vec<u8>,
) -> Result<ServerConfig, Error> {
let resolver =
handy::AlwaysResolvesChain::new_with_extras(cert_chain, &key_der, ocsp, scts)?;
Ok(self.with_cert_resolver(Arc::new(resolver)))
}
/// Sets a custom [`ResolvesServerCert`].
pub fn with_cert_resolver(self, cert_resolver: Arc<dyn ResolvesServerCert>) -> ServerConfig {
ServerConfig {
cipher_suites: self.state.cipher_suites,
kx_groups: self.state.kx_groups,
verifier: self.state.verifier,
cert_resolver,
ignore_client_order: false,
max_fragment_size: None,
session_storage: handy::ServerSessionMemoryCache::new(256),
ticketer: Arc::new(handy::NeverProducesTickets {}),
alpn_protocols: Vec::new(),
versions: self.state.versions,
key_log: Arc::new(NoKeyLog {}),
#[cfg(feature = "secret_extraction")]
enable_secret_extraction: false,
max_early_data_size: 0,
send_half_rtt_data: false,
}
}
}

View file

@ -0,0 +1,41 @@
use crate::{key, sign};
/// ActiveCertifiedKey wraps CertifiedKey and tracks OSCP and SCT state
/// in a single handshake.
pub(super) struct ActiveCertifiedKey<'a> {
key: &'a sign::CertifiedKey,
ocsp: Option<&'a [u8]>,
sct_list: Option<&'a [u8]>,
}
impl<'a> ActiveCertifiedKey<'a> {
pub(super) fn from_certified_key(key: &sign::CertifiedKey) -> ActiveCertifiedKey {
ActiveCertifiedKey {
key,
ocsp: key.ocsp.as_deref(),
sct_list: key.sct_list.as_deref(),
}
}
/// Get the certificate chain
#[inline]
pub(super) fn get_cert(&self) -> &[key::Certificate] {
&self.key.cert
}
/// Get the signing key
#[inline]
pub(super) fn get_key(&self) -> &dyn sign::SigningKey {
&*self.key.key
}
#[inline]
pub(super) fn get_ocsp(&self) -> Option<&[u8]> {
self.ocsp
}
#[inline]
pub(super) fn get_sct_list(&self) -> Option<&[u8]> {
self.sct_list
}
}

View file

@ -0,0 +1,278 @@
use crate::error::Error;
use crate::key;
use crate::limited_cache;
use crate::server;
use crate::server::ClientHello;
use crate::sign;
use std::collections;
use std::sync::{Arc, Mutex};
/// Something which never stores sessions.
pub struct NoServerSessionStorage {}
impl server::StoresServerSessions for NoServerSessionStorage {
fn put(&self, _id: Vec<u8>, _sec: Vec<u8>) -> bool {
false
}
fn get(&self, _id: &[u8]) -> Option<Vec<u8>> {
None
}
fn take(&self, _id: &[u8]) -> Option<Vec<u8>> {
None
}
fn can_cache(&self) -> bool {
false
}
}
/// An implementer of `StoresServerSessions` that stores everything
/// in memory. If enforces a limit on the number of stored sessions
/// to bound memory usage.
pub struct ServerSessionMemoryCache {
cache: Mutex<limited_cache::LimitedCache<Vec<u8>, Vec<u8>>>,
}
impl ServerSessionMemoryCache {
/// Make a new ServerSessionMemoryCache. `size` is the maximum
/// number of stored sessions, and may be rounded-up for
/// efficiency.
pub fn new(size: usize) -> Arc<Self> {
Arc::new(Self {
cache: Mutex::new(limited_cache::LimitedCache::new(size)),
})
}
}
impl server::StoresServerSessions for ServerSessionMemoryCache {
fn put(&self, key: Vec<u8>, value: Vec<u8>) -> bool {
self.cache
.lock()
.unwrap()
.insert(key, value);
true
}
fn get(&self, key: &[u8]) -> Option<Vec<u8>> {
self.cache
.lock()
.unwrap()
.get(key)
.cloned()
}
fn take(&self, key: &[u8]) -> Option<Vec<u8>> {
self.cache.lock().unwrap().remove(key)
}
fn can_cache(&self) -> bool {
true
}
}
/// Something which never produces tickets.
pub(super) struct NeverProducesTickets {}
impl server::ProducesTickets for NeverProducesTickets {
fn enabled(&self) -> bool {
false
}
fn lifetime(&self) -> u32 {
0
}
fn encrypt(&self, _bytes: &[u8]) -> Option<Vec<u8>> {
None
}
fn decrypt(&self, _bytes: &[u8]) -> Option<Vec<u8>> {
None
}
}
/// Something which always resolves to the same cert chain.
pub(super) struct AlwaysResolvesChain(Arc<sign::CertifiedKey>);
impl AlwaysResolvesChain {
/// Creates an `AlwaysResolvesChain`, auto-detecting the underlying private
/// key type and encoding.
pub(super) fn new(
chain: Vec<key::Certificate>,
priv_key: &key::PrivateKey,
) -> Result<Self, Error> {
let key = sign::any_supported_type(priv_key)
.map_err(|_| Error::General("invalid private key".into()))?;
Ok(Self(Arc::new(sign::CertifiedKey::new(chain, key))))
}
/// Creates an `AlwaysResolvesChain`, auto-detecting the underlying private
/// key type and encoding.
///
/// If non-empty, the given OCSP response and SCTs are attached.
pub(super) fn new_with_extras(
chain: Vec<key::Certificate>,
priv_key: &key::PrivateKey,
ocsp: Vec<u8>,
scts: Vec<u8>,
) -> Result<Self, Error> {
let mut r = Self::new(chain, priv_key)?;
{
let cert = Arc::make_mut(&mut r.0);
if !ocsp.is_empty() {
cert.ocsp = Some(ocsp);
}
if !scts.is_empty() {
cert.sct_list = Some(scts);
}
}
Ok(r)
}
}
impl server::ResolvesServerCert for AlwaysResolvesChain {
fn resolve(&self, _client_hello: ClientHello) -> Option<Arc<sign::CertifiedKey>> {
Some(Arc::clone(&self.0))
}
}
/// Something that resolves do different cert chains/keys based
/// on client-supplied server name (via SNI).
pub struct ResolvesServerCertUsingSni {
by_name: collections::HashMap<String, Arc<sign::CertifiedKey>>,
}
impl ResolvesServerCertUsingSni {
/// Create a new and empty (i.e., knows no certificates) resolver.
pub fn new() -> Self {
Self {
by_name: collections::HashMap::new(),
}
}
/// Add a new `sign::CertifiedKey` to be used for the given SNI `name`.
///
/// This function fails if `name` is not a valid DNS name, or if
/// it's not valid for the supplied certificate, or if the certificate
/// chain is syntactically faulty.
pub fn add(&mut self, name: &str, ck: sign::CertifiedKey) -> Result<(), Error> {
let checked_name = webpki::DnsNameRef::try_from_ascii_str(name)
.map_err(|_| Error::General("Bad DNS name".into()))?
.to_owned();
ck.cross_check_end_entity_cert(Some(checked_name.as_ref()))?;
let as_str: &str = checked_name.as_ref().into();
self.by_name
.insert(as_str.to_string(), Arc::new(ck));
Ok(())
}
}
impl server::ResolvesServerCert for ResolvesServerCertUsingSni {
fn resolve(&self, client_hello: ClientHello) -> Option<Arc<sign::CertifiedKey>> {
if let Some(name) = client_hello.server_name() {
self.by_name.get(name).map(Arc::clone)
} else {
// This kind of resolver requires SNI
None
}
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::server::ProducesTickets;
use crate::server::ResolvesServerCert;
use crate::server::StoresServerSessions;
#[test]
fn test_noserversessionstorage_drops_put() {
let c = NoServerSessionStorage {};
assert!(!c.put(vec![0x01], vec![0x02]));
}
#[test]
fn test_noserversessionstorage_denies_gets() {
let c = NoServerSessionStorage {};
c.put(vec![0x01], vec![0x02]);
assert_eq!(c.get(&[]), None);
assert_eq!(c.get(&[0x01]), None);
assert_eq!(c.get(&[0x02]), None);
}
#[test]
fn test_noserversessionstorage_denies_takes() {
let c = NoServerSessionStorage {};
assert_eq!(c.take(&[]), None);
assert_eq!(c.take(&[0x01]), None);
assert_eq!(c.take(&[0x02]), None);
}
#[test]
fn test_serversessionmemorycache_accepts_put() {
let c = ServerSessionMemoryCache::new(4);
assert!(c.put(vec![0x01], vec![0x02]));
}
#[test]
fn test_serversessionmemorycache_persists_put() {
let c = ServerSessionMemoryCache::new(4);
assert!(c.put(vec![0x01], vec![0x02]));
assert_eq!(c.get(&[0x01]), Some(vec![0x02]));
assert_eq!(c.get(&[0x01]), Some(vec![0x02]));
}
#[test]
fn test_serversessionmemorycache_overwrites_put() {
let c = ServerSessionMemoryCache::new(4);
assert!(c.put(vec![0x01], vec![0x02]));
assert!(c.put(vec![0x01], vec![0x04]));
assert_eq!(c.get(&[0x01]), Some(vec![0x04]));
}
#[test]
fn test_serversessionmemorycache_drops_to_maintain_size_invariant() {
let c = ServerSessionMemoryCache::new(2);
assert!(c.put(vec![0x01], vec![0x02]));
assert!(c.put(vec![0x03], vec![0x04]));
assert!(c.put(vec![0x05], vec![0x06]));
assert!(c.put(vec![0x07], vec![0x08]));
assert!(c.put(vec![0x09], vec![0x0a]));
let count = c.get(&[0x01]).iter().count()
+ c.get(&[0x03]).iter().count()
+ c.get(&[0x05]).iter().count()
+ c.get(&[0x07]).iter().count()
+ c.get(&[0x09]).iter().count();
assert!(count < 5);
}
#[test]
fn test_neverproducestickets_does_nothing() {
let npt = NeverProducesTickets {};
assert!(!npt.enabled());
assert_eq!(0, npt.lifetime());
assert_eq!(None, npt.encrypt(&[]));
assert_eq!(None, npt.decrypt(&[]));
}
#[test]
fn test_resolvesservercertusingsni_requires_sni() {
let rscsni = ResolvesServerCertUsingSni::new();
assert!(rscsni
.resolve(ClientHello::new(&None, &[], None, &[]))
.is_none());
}
#[test]
fn test_resolvesservercertusingsni_handles_unknown_name() {
let rscsni = ResolvesServerCertUsingSni::new();
let name = webpki::DnsNameRef::try_from_ascii_str("hello.com")
.unwrap()
.to_owned();
assert!(rscsni
.resolve(ClientHello::new(&Some(name), &[], None, &[]))
.is_none());
}
}

View file

@ -0,0 +1,522 @@
use crate::conn::{CommonState, ConnectionRandoms, State};
#[cfg(feature = "tls12")]
use crate::enums::CipherSuite;
use crate::enums::{ProtocolVersion, SignatureScheme};
use crate::error::Error;
use crate::hash_hs::{HandshakeHash, HandshakeHashBuffer};
#[cfg(feature = "logging")]
use crate::log::{debug, trace};
use crate::msgs::enums::HandshakeType;
use crate::msgs::enums::{AlertDescription, Compression, ExtensionType};
#[cfg(feature = "tls12")]
use crate::msgs::handshake::SessionID;
use crate::msgs::handshake::{ClientHelloPayload, Random, ServerExtension};
use crate::msgs::handshake::{ConvertProtocolNameList, ConvertServerNameList, HandshakePayload};
use crate::msgs::message::{Message, MessagePayload};
use crate::msgs::persist;
use crate::server::{ClientHello, ServerConfig};
use crate::suites;
use crate::SupportedCipherSuite;
use super::server_conn::ServerConnectionData;
#[cfg(feature = "tls12")]
use super::tls12;
use crate::server::common::ActiveCertifiedKey;
use crate::server::tls13;
use std::sync::Arc;
pub(super) type NextState = Box<dyn State<ServerConnectionData>>;
pub(super) type NextStateOrError = Result<NextState, Error>;
pub(super) type ServerContext<'a> = crate::conn::Context<'a, ServerConnectionData>;
pub(super) fn incompatible(common: &mut CommonState, why: &str) -> Error {
common.send_fatal_alert(AlertDescription::HandshakeFailure);
Error::PeerIncompatibleError(why.to_string())
}
fn bad_version(common: &mut CommonState, why: &str) -> Error {
common.send_fatal_alert(AlertDescription::ProtocolVersion);
Error::PeerIncompatibleError(why.to_string())
}
pub(super) fn decode_error(common: &mut CommonState, why: &str) -> Error {
common.send_fatal_alert(AlertDescription::DecodeError);
Error::PeerMisbehavedError(why.to_string())
}
pub(super) fn can_resume(
suite: SupportedCipherSuite,
sni: &Option<webpki::DnsName>,
using_ems: bool,
resumedata: &persist::ServerSessionValue,
) -> bool {
// The RFCs underspecify what happens if we try to resume to
// an unoffered/varying suite. We merely don't resume in weird cases.
//
// RFC 6066 says "A server that implements this extension MUST NOT accept
// the request to resume the session if the server_name extension contains
// a different name. Instead, it proceeds with a full handshake to
// establish a new session."
resumedata.cipher_suite == suite.suite()
&& (resumedata.extended_ms == using_ems || (resumedata.extended_ms && !using_ems))
&& &resumedata.sni == sni
}
#[derive(Default)]
pub(super) struct ExtensionProcessing {
// extensions to reply with
pub(super) exts: Vec<ServerExtension>,
#[cfg(feature = "tls12")]
pub(super) send_ticket: bool,
}
impl ExtensionProcessing {
pub(super) fn new() -> Self {
Default::default()
}
pub(super) fn process_common(
&mut self,
config: &ServerConfig,
cx: &mut ServerContext<'_>,
ocsp_response: &mut Option<&[u8]>,
sct_list: &mut Option<&[u8]>,
hello: &ClientHelloPayload,
resumedata: Option<&persist::ServerSessionValue>,
extra_exts: Vec<ServerExtension>,
) -> Result<(), Error> {
// ALPN
let our_protocols = &config.alpn_protocols;
let maybe_their_protocols = hello.get_alpn_extension();
if let Some(their_protocols) = maybe_their_protocols {
let their_protocols = their_protocols.to_slices();
if their_protocols
.iter()
.any(|protocol| protocol.is_empty())
{
return Err(Error::PeerMisbehavedError(
"client offered empty ALPN protocol".to_string(),
));
}
cx.common.alpn_protocol = our_protocols
.iter()
.find(|protocol| their_protocols.contains(&protocol.as_slice()))
.cloned();
if let Some(ref selected_protocol) = cx.common.alpn_protocol {
debug!("Chosen ALPN protocol {:?}", selected_protocol);
self.exts
.push(ServerExtension::make_alpn(&[selected_protocol]));
} else if !our_protocols.is_empty() {
cx.common
.send_fatal_alert(AlertDescription::NoApplicationProtocol);
return Err(Error::NoApplicationProtocol);
}
}
#[cfg(feature = "quic")]
{
if cx.common.is_quic() {
// QUIC has strict ALPN, unlike TLS's more backwards-compatible behavior. RFC 9001
// says: "The server MUST treat the inability to select a compatible application
// protocol as a connection error of type 0x0178". We judge that ALPN was desired
// (rather than some out-of-band protocol negotiation mechanism) iff any ALPN
// protocols were configured locally or offered by the client. This helps prevent
// successful establishment of connections between peers that can't understand
// each other.
if cx.common.alpn_protocol.is_none()
&& (!our_protocols.is_empty() || maybe_their_protocols.is_some())
{
cx.common
.send_fatal_alert(AlertDescription::NoApplicationProtocol);
return Err(Error::NoApplicationProtocol);
}
match hello.get_quic_params_extension() {
Some(params) => cx.common.quic.params = Some(params),
None => {
return Err(cx
.common
.missing_extension("QUIC transport parameters not found"));
}
}
}
}
let for_resume = resumedata.is_some();
// SNI
if !for_resume && hello.get_sni_extension().is_some() {
self.exts
.push(ServerExtension::ServerNameAck);
}
// Send status_request response if we have one. This is not allowed
// if we're resuming, and is only triggered if we have an OCSP response
// to send.
if !for_resume
&& hello
.find_extension(ExtensionType::StatusRequest)
.is_some()
{
if ocsp_response.is_some() && !cx.common.is_tls13() {
// Only TLS1.2 sends confirmation in ServerHello
self.exts
.push(ServerExtension::CertificateStatusAck);
}
} else {
// Throw away any OCSP response so we don't try to send it later.
ocsp_response.take();
}
if !for_resume
&& hello
.find_extension(ExtensionType::SCT)
.is_some()
{
if !cx.common.is_tls13() {
// Take the SCT list, if any, so we don't send it later,
// and put it in the legacy extension.
if let Some(sct_list) = sct_list.take() {
self.exts
.push(ServerExtension::make_sct(sct_list.to_vec()));
}
}
} else {
// Throw away any SCT list so we don't send it later.
sct_list.take();
}
self.exts.extend(extra_exts);
Ok(())
}
#[cfg(feature = "tls12")]
pub(super) fn process_tls12(
&mut self,
config: &ServerConfig,
hello: &ClientHelloPayload,
using_ems: bool,
) {
// Renegotiation.
// (We don't do reneg at all, but would support the secure version if we did.)
let secure_reneg_offered = hello
.find_extension(ExtensionType::RenegotiationInfo)
.is_some()
|| hello
.cipher_suites
.contains(&CipherSuite::TLS_EMPTY_RENEGOTIATION_INFO_SCSV);
if secure_reneg_offered {
self.exts
.push(ServerExtension::make_empty_renegotiation_info());
}
// Tickets:
// If we get any SessionTicket extension and have tickets enabled,
// we send an ack.
if hello
.find_extension(ExtensionType::SessionTicket)
.is_some()
&& config.ticketer.enabled()
{
self.send_ticket = true;
self.exts
.push(ServerExtension::SessionTicketAck);
}
// Confirm use of EMS if offered.
if using_ems {
self.exts
.push(ServerExtension::ExtendedMasterSecretAck);
}
}
}
pub(super) struct ExpectClientHello {
pub(super) config: Arc<ServerConfig>,
pub(super) extra_exts: Vec<ServerExtension>,
pub(super) transcript: HandshakeHashOrBuffer,
#[cfg(feature = "tls12")]
pub(super) session_id: SessionID,
#[cfg(feature = "tls12")]
pub(super) using_ems: bool,
pub(super) done_retry: bool,
pub(super) send_ticket: bool,
}
impl ExpectClientHello {
pub(super) fn new(config: Arc<ServerConfig>, extra_exts: Vec<ServerExtension>) -> Self {
let mut transcript_buffer = HandshakeHashBuffer::new();
if config.verifier.offer_client_auth() {
transcript_buffer.set_client_auth_enabled();
}
Self {
config,
extra_exts,
transcript: HandshakeHashOrBuffer::Buffer(transcript_buffer),
#[cfg(feature = "tls12")]
session_id: SessionID::empty(),
#[cfg(feature = "tls12")]
using_ems: false,
done_retry: false,
send_ticket: false,
}
}
/// Continues handling of a `ClientHello` message once config and certificate are available.
pub(super) fn with_certified_key(
self,
mut sig_schemes: Vec<SignatureScheme>,
client_hello: &ClientHelloPayload,
m: &Message,
cx: &mut ServerContext<'_>,
) -> NextStateOrError {
let tls13_enabled = self
.config
.supports_version(ProtocolVersion::TLSv1_3);
let tls12_enabled = self
.config
.supports_version(ProtocolVersion::TLSv1_2);
// Are we doing TLS1.3?
let maybe_versions_ext = client_hello.get_versions_extension();
let version = if let Some(versions) = maybe_versions_ext {
if versions.contains(&ProtocolVersion::TLSv1_3) && tls13_enabled {
ProtocolVersion::TLSv1_3
} else if !versions.contains(&ProtocolVersion::TLSv1_2) || !tls12_enabled {
return Err(bad_version(cx.common, "TLS1.2 not offered/enabled"));
} else if cx.common.is_quic() {
return Err(bad_version(
cx.common,
"Expecting QUIC connection, but client does not support TLSv1_3",
));
} else {
ProtocolVersion::TLSv1_2
}
} else if client_hello.client_version.get_u16() < ProtocolVersion::TLSv1_2.get_u16() {
return Err(bad_version(cx.common, "Client does not support TLSv1_2"));
} else if !tls12_enabled && tls13_enabled {
return Err(bad_version(
cx.common,
"Server requires TLS1.3, but client omitted versions ext",
));
} else if cx.common.is_quic() {
return Err(bad_version(
cx.common,
"Expecting QUIC connection, but client does not support TLSv1_3",
));
} else {
ProtocolVersion::TLSv1_2
};
cx.common.negotiated_version = Some(version);
// We communicate to the upper layer what kind of key they should choose
// via the sigschemes value. Clients tend to treat this extension
// orthogonally to offered ciphersuites (even though, in TLS1.2 it is not).
// So: reduce the offered sigschemes to those compatible with the
// intersection of ciphersuites.
let client_suites = self
.config
.cipher_suites
.iter()
.copied()
.filter(|scs| {
client_hello
.cipher_suites
.contains(&scs.suite())
})
.collect::<Vec<_>>();
sig_schemes
.retain(|scheme| suites::compatible_sigscheme_for_suites(*scheme, &client_suites));
// Choose a certificate.
let certkey = {
let client_hello = ClientHello::new(
&cx.data.sni,
&sig_schemes,
client_hello.get_alpn_extension(),
&client_hello.cipher_suites,
);
let certkey = self
.config
.cert_resolver
.resolve(client_hello);
certkey.ok_or_else(|| {
cx.common
.send_fatal_alert(AlertDescription::AccessDenied);
Error::General("no server certificate chain resolved".to_string())
})?
};
let certkey = ActiveCertifiedKey::from_certified_key(&certkey);
// Reduce our supported ciphersuites by the certificate.
// (no-op for TLS1.3)
let suitable_suites =
suites::reduce_given_sigalg(&self.config.cipher_suites, certkey.get_key().algorithm());
// And version
let suitable_suites = suites::reduce_given_version(&suitable_suites, version);
let suite = if self.config.ignore_client_order {
suites::choose_ciphersuite_preferring_server(
&client_hello.cipher_suites,
&suitable_suites,
)
} else {
suites::choose_ciphersuite_preferring_client(
&client_hello.cipher_suites,
&suitable_suites,
)
}
.ok_or_else(|| incompatible(cx.common, "no ciphersuites in common"))?;
debug!("decided upon suite {:?}", suite);
cx.common.suite = Some(suite);
// Start handshake hash.
let starting_hash = suite.hash_algorithm();
let transcript = match self.transcript {
HandshakeHashOrBuffer::Buffer(inner) => inner.start_hash(starting_hash),
HandshakeHashOrBuffer::Hash(inner) if inner.algorithm() == starting_hash => inner,
_ => {
return Err(cx
.common
.illegal_param("hash differed on retry"));
}
};
// Save their Random.
let randoms = ConnectionRandoms::new(client_hello.random, Random::new()?);
match suite {
SupportedCipherSuite::Tls13(suite) => tls13::CompleteClientHelloHandling {
config: self.config,
transcript,
suite,
randoms,
done_retry: self.done_retry,
send_ticket: self.send_ticket,
extra_exts: self.extra_exts,
}
.handle_client_hello(cx, certkey, m, client_hello, sig_schemes),
#[cfg(feature = "tls12")]
SupportedCipherSuite::Tls12(suite) => tls12::CompleteClientHelloHandling {
config: self.config,
transcript,
session_id: self.session_id,
suite,
using_ems: self.using_ems,
randoms,
send_ticket: self.send_ticket,
extra_exts: self.extra_exts,
}
.handle_client_hello(
cx,
certkey,
m,
client_hello,
sig_schemes,
tls13_enabled,
),
}
}
}
impl State<ServerConnectionData> for ExpectClientHello {
fn handle(self: Box<Self>, cx: &mut ServerContext<'_>, m: Message) -> NextStateOrError {
let (client_hello, sig_schemes) =
process_client_hello(&m, self.done_retry, cx.common, cx.data)?;
self.with_certified_key(sig_schemes, client_hello, &m, cx)
}
}
/// Configuration-independent validation of a `ClientHello` message.
///
/// This represents the first part of the `ClientHello` handling, where we do all validation that
/// doesn't depend on a `ServerConfig` being available and extract everything needed to build a
/// [`ClientHello`] value for a [`ResolvesServerConfig`]/`ResolvesServerCert`].
///
/// Note that this will modify `data.sni` even if config or certificate resolution fail.
pub(super) fn process_client_hello<'a>(
m: &'a Message,
done_retry: bool,
common: &mut CommonState,
data: &mut ServerConnectionData,
) -> Result<(&'a ClientHelloPayload, Vec<SignatureScheme>), Error> {
let client_hello =
require_handshake_msg!(m, HandshakeType::ClientHello, HandshakePayload::ClientHello)?;
trace!("we got a clienthello {:?}", client_hello);
if !client_hello
.compression_methods
.contains(&Compression::Null)
{
common.send_fatal_alert(AlertDescription::IllegalParameter);
return Err(Error::PeerIncompatibleError(
"client did not offer Null compression".to_string(),
));
}
if client_hello.has_duplicate_extension() {
return Err(decode_error(common, "client sent duplicate extensions"));
}
// No handshake messages should follow this one in this flight.
common.check_aligned_handshake()?;
// Extract and validate the SNI DNS name, if any, before giving it to
// the cert resolver. In particular, if it is invalid then we should
// send an Illegal Parameter alert instead of the Internal Error alert
// (or whatever) that we'd send if this were checked later or in a
// different way.
let sni: Option<webpki::DnsName> = match client_hello.get_sni_extension() {
Some(sni) => {
if sni.has_duplicate_names_for_type() {
return Err(decode_error(
common,
"ClientHello SNI contains duplicate name types",
));
}
if let Some(hostname) = sni.get_single_hostname() {
Some(hostname.into())
} else {
return Err(common.illegal_param("ClientHello SNI did not contain a hostname"));
}
}
None => None,
};
// save only the first SNI
if let (Some(sni), false) = (&sni, done_retry) {
// Save the SNI into the session.
// The SNI hostname is immutable once set.
assert!(data.sni.is_none());
data.sni = Some(sni.clone())
} else if data.sni != sni {
return Err(Error::PeerIncompatibleError(
"SNI differed on retry".to_string(),
));
}
let sig_schemes = client_hello
.get_sigalgs_extension()
.ok_or_else(|| incompatible(common, "client didn't describe signature schemes"))?
.clone();
Ok((client_hello, sig_schemes))
}
#[allow(clippy::large_enum_variant)]
pub(crate) enum HandshakeHashOrBuffer {
Buffer(HandshakeHashBuffer),
Hash(HandshakeHash),
}

View file

@ -0,0 +1,849 @@
use crate::builder::{ConfigBuilder, WantsCipherSuites};
use crate::conn::{CommonState, ConnectionCommon, Side, State};
use crate::enums::{CipherSuite, ProtocolVersion, SignatureScheme};
use crate::error::Error;
use crate::kx::SupportedKxGroup;
#[cfg(feature = "logging")]
use crate::log::trace;
use crate::msgs::base::{Payload, PayloadU8};
#[cfg(feature = "quic")]
use crate::msgs::enums::AlertDescription;
use crate::msgs::handshake::{ClientHelloPayload, ServerExtension};
use crate::msgs::message::Message;
use crate::sign;
use crate::suites::SupportedCipherSuite;
use crate::vecbuf::ChunkVecBuffer;
use crate::verify;
#[cfg(feature = "secret_extraction")]
use crate::ExtractedSecrets;
use crate::KeyLog;
#[cfg(feature = "quic")]
use crate::{conn::Protocol, quic};
use super::hs;
use std::marker::PhantomData;
use std::ops::{Deref, DerefMut};
use std::sync::Arc;
use std::{fmt, io};
/// A trait for the ability to store server session data.
///
/// The keys and values are opaque.
///
/// Both the keys and values should be treated as
/// **highly sensitive data**, containing enough key material
/// to break all security of the corresponding sessions.
///
/// Implementations can be lossy (in other words, forgetting
/// key/value pairs) without any negative security consequences.
///
/// However, note that `take` **must** reliably delete a returned
/// value. If it does not, there may be security consequences.
///
/// `put` and `take` are mutating operations; this isn't expressed
/// in the type system to allow implementations freedom in
/// how to achieve interior mutability. `Mutex` is a common
/// choice.
pub trait StoresServerSessions: Send + Sync {
/// Store session secrets encoded in `value` against `key`,
/// overwrites any existing value against `key`. Returns `true`
/// if the value was stored.
fn put(&self, key: Vec<u8>, value: Vec<u8>) -> bool;
/// Find a value with the given `key`. Return it, or None
/// if it doesn't exist.
fn get(&self, key: &[u8]) -> Option<Vec<u8>>;
/// Find a value with the given `key`. Return it and delete it;
/// or None if it doesn't exist.
fn take(&self, key: &[u8]) -> Option<Vec<u8>>;
/// Whether the store can cache another session. This is used to indicate to clients
/// whether their session can be resumed; the implementation is not required to remember
/// a session even if it returns `true` here.
fn can_cache(&self) -> bool;
}
/// A trait for the ability to encrypt and decrypt tickets.
pub trait ProducesTickets: Send + Sync {
/// Returns true if this implementation will encrypt/decrypt
/// tickets. Should return false if this is a dummy
/// implementation: the server will not send the SessionTicket
/// extension and will not call the other functions.
fn enabled(&self) -> bool;
/// Returns the lifetime in seconds of tickets produced now.
/// The lifetime is provided as a hint to clients that the
/// ticket will not be useful after the given time.
///
/// This lifetime must be implemented by key rolling and
/// erasure, *not* by storing a lifetime in the ticket.
///
/// The objective is to limit damage to forward secrecy caused
/// by tickets, not just limiting their lifetime.
fn lifetime(&self) -> u32;
/// Encrypt and authenticate `plain`, returning the resulting
/// ticket. Return None if `plain` cannot be encrypted for
/// some reason: an empty ticket will be sent and the connection
/// will continue.
fn encrypt(&self, plain: &[u8]) -> Option<Vec<u8>>;
/// Decrypt `cipher`, validating its authenticity protection
/// and recovering the plaintext. `cipher` is fully attacker
/// controlled, so this decryption must be side-channel free,
/// panic-proof, and otherwise bullet-proof. If the decryption
/// fails, return None.
fn decrypt(&self, cipher: &[u8]) -> Option<Vec<u8>>;
}
/// How to choose a certificate chain and signing key for use
/// in server authentication.
pub trait ResolvesServerCert: Send + Sync {
/// Choose a certificate chain and matching key given simplified
/// ClientHello information.
///
/// Return `None` to abort the handshake.
fn resolve(&self, client_hello: ClientHello) -> Option<Arc<sign::CertifiedKey>>;
}
/// A struct representing the received Client Hello
pub struct ClientHello<'a> {
server_name: &'a Option<webpki::DnsName>,
signature_schemes: &'a [SignatureScheme],
alpn: Option<&'a Vec<PayloadU8>>,
cipher_suites: &'a [CipherSuite],
}
impl<'a> ClientHello<'a> {
/// Creates a new ClientHello
pub(super) fn new(
server_name: &'a Option<webpki::DnsName>,
signature_schemes: &'a [SignatureScheme],
alpn: Option<&'a Vec<PayloadU8>>,
cipher_suites: &'a [CipherSuite],
) -> Self {
trace!("sni {:?}", server_name);
trace!("sig schemes {:?}", signature_schemes);
trace!("alpn protocols {:?}", alpn);
trace!("cipher suites {:?}", cipher_suites);
ClientHello {
server_name,
signature_schemes,
alpn,
cipher_suites,
}
}
/// Get the server name indicator.
///
/// Returns `None` if the client did not supply a SNI.
pub fn server_name(&self) -> Option<&str> {
self.server_name
.as_ref()
.map(<webpki::DnsName as AsRef<str>>::as_ref)
}
/// Get the compatible signature schemes.
///
/// Returns standard-specified default if the client omitted this extension.
pub fn signature_schemes(&self) -> &[SignatureScheme] {
self.signature_schemes
}
/// Get the ALPN protocol identifiers submitted by the client.
///
/// Returns `None` if the client did not include an ALPN extension.
///
/// Application Layer Protocol Negotiation (ALPN) is a TLS extension that lets a client
/// submit a set of identifiers that each a represent an application-layer protocol.
/// The server will then pick its preferred protocol from the set submitted by the client.
/// Each identifier is represented as a byte array, although common values are often ASCII-encoded.
/// See the official RFC-7301 specifications at <https://datatracker.ietf.org/doc/html/rfc7301>
/// for more information on ALPN.
///
/// For example, a HTTP client might specify "http/1.1" and/or "h2". Other well-known values
/// are listed in the at IANA registry at
/// <https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids>.
///
/// The server can specify supported ALPN protocols by setting [`ServerConfig::alpn_protocols`].
/// During the handshake, the server will select the first protocol configured that the client supports.
pub fn alpn(&self) -> Option<impl Iterator<Item = &'a [u8]>> {
self.alpn.map(|protocols| {
protocols
.iter()
.map(|proto| proto.0.as_slice())
})
}
/// Get cipher suites.
pub fn cipher_suites(&self) -> &[CipherSuite] {
self.cipher_suites
}
}
/// Common configuration for a set of server sessions.
///
/// Making one of these can be expensive, and should be
/// once per process rather than once per connection.
///
/// These must be created via the [`ServerConfig::builder()`] function.
///
/// # Defaults
///
/// * [`ServerConfig::max_fragment_size`]: the default is `None`: TLS packets are not fragmented to a specific size.
/// * [`ServerConfig::session_storage`]: the default stores 256 sessions in memory.
/// * [`ServerConfig::alpn_protocols`]: the default is empty -- no ALPN protocol is negotiated.
/// * [`ServerConfig::key_log`]: key material is not logged.
#[derive(Clone)]
pub struct ServerConfig {
/// List of ciphersuites, in preference order.
pub(super) cipher_suites: Vec<SupportedCipherSuite>,
/// List of supported key exchange groups.
///
/// The first is the highest priority: they will be
/// offered to the client in this order.
pub(super) kx_groups: Vec<&'static SupportedKxGroup>,
/// Ignore the client's ciphersuite order. Instead,
/// choose the top ciphersuite in the server list
/// which is supported by the client.
pub ignore_client_order: bool,
/// The maximum size of TLS message we'll emit. If None, we don't limit TLS
/// message lengths except to the 2**16 limit specified in the standard.
///
/// rustls enforces an arbitrary minimum of 32 bytes for this field.
/// Out of range values are reported as errors from ServerConnection::new.
///
/// Setting this value to the TCP MSS may improve latency for stream-y workloads.
pub max_fragment_size: Option<usize>,
/// How to store client sessions.
pub session_storage: Arc<dyn StoresServerSessions + Send + Sync>,
/// How to produce tickets.
pub ticketer: Arc<dyn ProducesTickets>,
/// How to choose a server cert and key.
pub cert_resolver: Arc<dyn ResolvesServerCert>,
/// Protocol names we support, most preferred first.
/// If empty we don't do ALPN at all.
pub alpn_protocols: Vec<Vec<u8>>,
/// Supported protocol versions, in no particular order.
/// The default is all supported versions.
pub(super) versions: crate::versions::EnabledVersions,
/// How to verify client certificates.
pub(super) verifier: Arc<dyn verify::ClientCertVerifier>,
/// How to output key material for debugging. The default
/// does nothing.
pub key_log: Arc<dyn KeyLog>,
/// Allows traffic secrets to be extracted after the handshake,
/// e.g. for kTLS setup.
#[cfg(feature = "secret_extraction")]
pub enable_secret_extraction: bool,
/// Amount of early data to accept for sessions created by
/// this config. Specify 0 to disable early data. The
/// default is 0.
///
/// Read the early data via [`ServerConnection::early_data`].
///
/// The units for this are _both_ plaintext bytes, _and_ ciphertext
/// bytes, depending on whether the server accepts a client's early_data
/// or not. It is therefore recommended to include some slop in
/// this value to account for the unknown amount of ciphertext
/// expansion in the latter case.
pub max_early_data_size: u32,
/// Whether the server should send "0.5RTT" data. This means the server
/// sends data after its first flight of handshake messages, without
/// waiting for the client to complete the handshake.
///
/// This can improve TTFB latency for either server-speaks-first protocols,
/// or client-speaks-first protocols when paired with "0RTT" data. This
/// comes at the cost of a subtle weakening of the normal handshake
/// integrity guarantees that TLS provides. Note that the initial
/// `ClientHello` is indirectly authenticated because it is included
/// in the transcript used to derive the keys used to encrypt the data.
///
/// This only applies to TLS1.3 connections. TLS1.2 connections cannot
/// do this optimisation and this setting is ignored for them. It is
/// also ignored for TLS1.3 connections that even attempt client
/// authentication.
///
/// This defaults to false. This means the first application data
/// sent by the server comes after receiving and validating the client's
/// handshake up to the `Finished` message. This is the safest option.
pub send_half_rtt_data: bool,
}
impl fmt::Debug for ServerConfig {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ServerConfig")
.field("ignore_client_order", &self.ignore_client_order)
.field("max_fragment_size", &self.max_fragment_size)
.field("alpn_protocols", &self.alpn_protocols)
.field("max_early_data_size", &self.max_early_data_size)
.field("send_half_rtt_data", &self.send_half_rtt_data)
.finish_non_exhaustive()
}
}
impl ServerConfig {
/// Create builder to build up the server configuration.
///
/// For more information, see the [`ConfigBuilder`] documentation.
pub fn builder() -> ConfigBuilder<Self, WantsCipherSuites> {
ConfigBuilder {
state: WantsCipherSuites(()),
side: PhantomData::default(),
}
}
#[doc(hidden)]
/// We support a given TLS version if it's quoted in the configured
/// versions *and* at least one ciphersuite for this version is
/// also configured.
pub fn supports_version(&self, v: ProtocolVersion) -> bool {
self.versions.contains(v)
&& self
.cipher_suites
.iter()
.any(|cs| cs.version().version == v)
}
}
/// Allows reading of early data in resumed TLS1.3 connections.
///
/// "Early data" is also known as "0-RTT data".
///
/// This structure implements [`std::io::Read`].
pub struct ReadEarlyData<'a> {
early_data: &'a mut EarlyDataState,
}
impl<'a> ReadEarlyData<'a> {
fn new(early_data: &'a mut EarlyDataState) -> Self {
ReadEarlyData { early_data }
}
}
impl<'a> std::io::Read for ReadEarlyData<'a> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.early_data.read(buf)
}
#[cfg(read_buf)]
fn read_buf(&mut self, cursor: io::BorrowedCursor<'_>) -> io::Result<()> {
self.early_data.read_buf(cursor)
}
}
/// This represents a single TLS server connection.
///
/// Send TLS-protected data to the peer using the `io::Write` trait implementation.
/// Read data from the peer using the `io::Read` trait implementation.
pub struct ServerConnection {
inner: ConnectionCommon<ServerConnectionData>,
}
impl ServerConnection {
/// Make a new ServerConnection. `config` controls how
/// we behave in the TLS protocol.
pub fn new(config: Arc<ServerConfig>) -> Result<Self, Error> {
Self::from_config(config, vec![])
}
fn from_config(
config: Arc<ServerConfig>,
extra_exts: Vec<ServerExtension>,
) -> Result<Self, Error> {
let mut common = CommonState::new(Side::Server);
common.set_max_fragment_size(config.max_fragment_size)?;
#[cfg(feature = "secret_extraction")]
{
common.enable_secret_extraction = config.enable_secret_extraction;
}
Ok(Self {
inner: ConnectionCommon::new(
Box::new(hs::ExpectClientHello::new(config, extra_exts)),
ServerConnectionData::default(),
common,
),
})
}
/// Retrieves the SNI hostname, if any, used to select the certificate and
/// private key.
///
/// This returns `None` until some time after the client's SNI extension
/// value is processed during the handshake. It will never be `None` when
/// the connection is ready to send or process application data, unless the
/// client does not support SNI.
///
/// This is useful for application protocols that need to enforce that the
/// SNI hostname matches an application layer protocol hostname. For
/// example, HTTP/1.1 servers commonly expect the `Host:` header field of
/// every request on a connection to match the hostname in the SNI extension
/// when the client provides the SNI extension.
///
/// The SNI hostname is also used to match sessions during session
/// resumption.
pub fn sni_hostname(&self) -> Option<&str> {
self.inner.data.get_sni_str()
}
/// Application-controlled portion of the resumption ticket supplied by the client, if any.
///
/// Recovered from the prior session's `set_resumption_data`. Integrity is guaranteed by rustls.
///
/// Returns `Some` iff a valid resumption ticket has been received from the client.
pub fn received_resumption_data(&self) -> Option<&[u8]> {
self.inner
.data
.received_resumption_data
.as_ref()
.map(|x| &x[..])
}
/// Set the resumption data to embed in future resumption tickets supplied to the client.
///
/// Defaults to the empty byte string. Must be less than 2^15 bytes to allow room for other
/// data. Should be called while `is_handshaking` returns true to ensure all transmitted
/// resumption tickets are affected.
///
/// Integrity will be assured by rustls, but the data will be visible to the client. If secrecy
/// from the client is desired, encrypt the data separately.
pub fn set_resumption_data(&mut self, data: &[u8]) {
assert!(data.len() < 2usize.pow(15));
self.inner.data.resumption_data = data.into();
}
/// Explicitly discard early data, notifying the client
///
/// Useful if invariants encoded in `received_resumption_data()` cannot be respected.
///
/// Must be called while `is_handshaking` is true.
pub fn reject_early_data(&mut self) {
assert!(
self.is_handshaking(),
"cannot retroactively reject early data"
);
self.inner.data.early_data.reject();
}
/// Returns an `io::Read` implementer you can read bytes from that are
/// received from a client as TLS1.3 0RTT/"early" data, during the handshake.
///
/// This returns `None` in many circumstances, such as :
///
/// - Early data is disabled if [`ServerConfig::max_early_data_size`] is zero (the default).
/// - The session negotiated with the client is not TLS1.3.
/// - The client just doesn't support early data.
/// - The connection doesn't resume an existing session.
/// - The client hasn't sent a full ClientHello yet.
pub fn early_data(&mut self) -> Option<ReadEarlyData> {
if self
.inner
.data
.early_data
.was_accepted()
{
Some(ReadEarlyData::new(&mut self.inner.data.early_data))
} else {
None
}
}
/// Extract secrets, so they can be used when configuring kTLS, for example.
#[cfg(feature = "secret_extraction")]
pub fn extract_secrets(self) -> Result<ExtractedSecrets, Error> {
self.inner.extract_secrets()
}
}
impl fmt::Debug for ServerConnection {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("ServerConnection")
.finish()
}
}
impl Deref for ServerConnection {
type Target = ConnectionCommon<ServerConnectionData>;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl DerefMut for ServerConnection {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}
impl From<ServerConnection> for crate::Connection {
fn from(conn: ServerConnection) -> Self {
Self::Server(conn)
}
}
/// Handle on a server-side connection before configuration is available.
///
/// The `Acceptor` allows the caller to provide a [`ServerConfig`] based on the [`ClientHello`] of
/// the incoming connection.
pub struct Acceptor {
inner: Option<ConnectionCommon<ServerConnectionData>>,
}
impl Default for Acceptor {
fn default() -> Self {
Self {
inner: Some(ConnectionCommon::new(
Box::new(Accepting),
ServerConnectionData::default(),
CommonState::new(Side::Server),
)),
}
}
}
impl Acceptor {
/// Create a new `Acceptor`.
#[deprecated(
since = "0.20.7",
note = "Use Acceptor::default instead for an infallible constructor"
)]
pub fn new() -> Result<Self, Error> {
Ok(Self::default())
}
/// Returns true if the caller should call [`Connection::read_tls()`] as soon as possible.
///
/// Since the purpose of an Acceptor is to read and then parse TLS bytes, this always returns true.
///
/// [`Connection::read_tls()`]: crate::Connection::read_tls
#[deprecated(since = "0.20.7", note = "Always returns true")]
pub fn wants_read(&self) -> bool {
self.inner
.as_ref()
.map(|conn| conn.common_state.wants_read())
.unwrap_or(false)
}
/// Read TLS content from `rd`.
///
/// Returns an error if this `Acceptor` has already yielded an [`Accepted`]. For more details,
/// refer to [`Connection::read_tls()`].
///
/// [`Connection::read_tls()`]: crate::Connection::read_tls
pub fn read_tls(&mut self, rd: &mut dyn io::Read) -> Result<usize, io::Error> {
match &mut self.inner {
Some(conn) => conn.read_tls(rd),
None => Err(io::Error::new(
io::ErrorKind::Other,
"acceptor cannot read after successful acceptance",
)),
}
}
/// Check if a `ClientHello` message has been received.
///
/// Returns `Ok(None)` if the complete `ClientHello` has not yet been received.
/// Do more I/O and then call this function again.
///
/// Returns `Ok(Some(accepted))` if the connection has been accepted. Call
/// `accepted.into_connection()` to continue. Do not call this function again.
///
/// Returns `Err(err)` if an error occurred. Do not call this function again.
pub fn accept(&mut self) -> Result<Option<Accepted>, Error> {
let mut connection = match self.inner.take() {
Some(conn) => conn,
None => {
return Err(Error::General("Acceptor polled after completion".into()));
}
};
let message = match connection.first_handshake_message()? {
Some(msg) => msg,
None => {
self.inner = Some(connection);
return Ok(None);
}
};
let (_, sig_schemes) = hs::process_client_hello(
&message,
false,
&mut connection.common_state,
&mut connection.data,
)?;
Ok(Some(Accepted {
connection,
message,
sig_schemes,
}))
}
}
/// Represents a `ClientHello` message received through the [`Acceptor`].
///
/// Contains the state required to resume the connection through [`Accepted::into_connection()`].
pub struct Accepted {
connection: ConnectionCommon<ServerConnectionData>,
message: Message,
sig_schemes: Vec<SignatureScheme>,
}
impl Accepted {
/// Get the [`ClientHello`] for this connection.
pub fn client_hello(&self) -> ClientHello<'_> {
let payload = Self::client_hello_payload(&self.message);
ClientHello::new(
&self.connection.data.sni,
&self.sig_schemes,
payload.get_alpn_extension(),
&payload.cipher_suites,
)
}
/// Convert the [`Accepted`] into a [`ServerConnection`].
///
/// Takes the state returned from [`Acceptor::accept()`] as well as the [`ServerConfig`] and
/// [`sign::CertifiedKey`] that should be used for the session. Returns an error if
/// configuration-dependent validation of the received `ClientHello` message fails.
pub fn into_connection(mut self, config: Arc<ServerConfig>) -> Result<ServerConnection, Error> {
self.connection
.common_state
.set_max_fragment_size(config.max_fragment_size)?;
#[cfg(feature = "secret_extraction")]
{
self.connection
.common_state
.enable_secret_extraction = config.enable_secret_extraction;
}
let state = hs::ExpectClientHello::new(config, Vec::new());
let mut cx = hs::ServerContext {
common: &mut self.connection.common_state,
data: &mut self.connection.data,
};
let new = state.with_certified_key(
self.sig_schemes,
Self::client_hello_payload(&self.message),
&self.message,
&mut cx,
)?;
self.connection.replace_state(new);
Ok(ServerConnection {
inner: self.connection,
})
}
fn client_hello_payload(message: &Message) -> &ClientHelloPayload {
match &message.payload {
crate::msgs::message::MessagePayload::Handshake { parsed, .. } => match &parsed.payload
{
crate::msgs::handshake::HandshakePayload::ClientHello(ch) => ch,
_ => unreachable!(),
},
_ => unreachable!(),
}
}
}
struct Accepting;
impl State<ServerConnectionData> for Accepting {
fn handle(
self: Box<Self>,
_cx: &mut hs::ServerContext<'_>,
_m: Message,
) -> Result<Box<dyn State<ServerConnectionData>>, Error> {
Err(Error::General("unreachable state".into()))
}
}
pub(super) enum EarlyDataState {
New,
Accepted(ChunkVecBuffer),
Rejected,
}
impl Default for EarlyDataState {
fn default() -> Self {
Self::New
}
}
impl EarlyDataState {
pub(super) fn reject(&mut self) {
*self = Self::Rejected;
}
pub(super) fn accept(&mut self, max_size: usize) {
*self = Self::Accepted(ChunkVecBuffer::new(Some(max_size)));
}
fn was_accepted(&self) -> bool {
matches!(self, Self::Accepted(_))
}
pub(super) fn was_rejected(&self) -> bool {
matches!(self, Self::Rejected)
}
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
match self {
Self::Accepted(ref mut received) => received.read(buf),
_ => Err(io::Error::from(io::ErrorKind::BrokenPipe)),
}
}
#[cfg(read_buf)]
fn read_buf(&mut self, cursor: io::BorrowedCursor<'_>) -> io::Result<()> {
match self {
Self::Accepted(ref mut received) => received.read_buf(cursor),
_ => Err(io::Error::from(io::ErrorKind::BrokenPipe)),
}
}
pub(super) fn take_received_plaintext(&mut self, bytes: Payload) -> bool {
let available = bytes.0.len();
match self {
Self::Accepted(ref mut received) if received.apply_limit(available) == available => {
received.append(bytes.0);
true
}
_ => false,
}
}
}
// these branches not reachable externally, unless something else goes wrong.
#[test]
fn test_read_in_new_state() {
assert_eq!(
format!("{:?}", EarlyDataState::default().read(&mut [0u8; 5])),
"Err(Kind(BrokenPipe))"
);
}
#[cfg(read_buf)]
#[test]
fn test_read_buf_in_new_state() {
use std::io::BorrowedBuf;
let mut buf = [0u8; 5];
let mut buf: BorrowedBuf<'_> = buf.as_mut_slice().into();
assert_eq!(
format!("{:?}", EarlyDataState::default().read_buf(buf.unfilled())),
"Err(Kind(BrokenPipe))"
);
}
/// State associated with a server connection.
#[derive(Default)]
pub struct ServerConnectionData {
pub(super) sni: Option<webpki::DnsName>,
pub(super) received_resumption_data: Option<Vec<u8>>,
pub(super) resumption_data: Vec<u8>,
pub(super) early_data: EarlyDataState,
}
impl ServerConnectionData {
pub(super) fn get_sni_str(&self) -> Option<&str> {
self.sni.as_ref().map(AsRef::as_ref)
}
}
impl crate::conn::SideData for ServerConnectionData {}
#[cfg(feature = "quic")]
impl quic::QuicExt for ServerConnection {
fn quic_transport_parameters(&self) -> Option<&[u8]> {
self.inner
.common_state
.quic
.params
.as_ref()
.map(|v| v.as_ref())
}
fn zero_rtt_keys(&self) -> Option<quic::DirectionalKeys> {
Some(quic::DirectionalKeys::new(
self.inner
.common_state
.suite
.and_then(|suite| suite.tls13())?,
self.inner
.common_state
.quic
.early_secret
.as_ref()?,
))
}
fn read_hs(&mut self, plaintext: &[u8]) -> Result<(), Error> {
self.inner.read_quic_hs(plaintext)
}
fn write_hs(&mut self, buf: &mut Vec<u8>) -> Option<quic::KeyChange> {
quic::write_hs(&mut self.inner.common_state, buf)
}
fn alert(&self) -> Option<AlertDescription> {
self.inner.common_state.quic.alert
}
}
/// Methods specific to QUIC server sessions
#[cfg(feature = "quic")]
#[cfg_attr(docsrs, doc(cfg(feature = "quic")))]
pub trait ServerQuicExt {
/// Make a new QUIC ServerConnection. This differs from `ServerConnection::new()`
/// in that it takes an extra argument, `params`, which contains the
/// TLS-encoded transport parameters to send.
fn new_quic(
config: Arc<ServerConfig>,
quic_version: quic::Version,
params: Vec<u8>,
) -> Result<ServerConnection, Error> {
if !config.supports_version(ProtocolVersion::TLSv1_3) {
return Err(Error::General(
"TLS 1.3 support is required for QUIC".into(),
));
}
if config.max_early_data_size != 0 && config.max_early_data_size != 0xffff_ffff {
return Err(Error::General(
"QUIC sessions must set a max early data of 0 or 2^32-1".into(),
));
}
let ext = match quic_version {
quic::Version::V1Draft => ServerExtension::TransportParametersDraft(params),
quic::Version::V1 => ServerExtension::TransportParameters(params),
};
let mut new = ServerConnection::from_config(config, vec![ext])?;
new.inner.common_state.protocol = Protocol::Quic;
Ok(new)
}
}
#[cfg(feature = "quic")]
impl ServerQuicExt for ServerConnection {}

View file

@ -0,0 +1,941 @@
use crate::check::inappropriate_message;
use crate::conn::{CommonState, ConnectionRandoms, Side, State};
use crate::enums::ProtocolVersion;
use crate::error::Error;
use crate::hash_hs::HandshakeHash;
use crate::key::Certificate;
#[cfg(feature = "logging")]
use crate::log::{debug, trace};
use crate::msgs::base::Payload;
use crate::msgs::ccs::ChangeCipherSpecPayload;
use crate::msgs::codec::Codec;
use crate::msgs::enums::{AlertDescription, ContentType, HandshakeType};
use crate::msgs::handshake::{ClientECDHParams, HandshakeMessagePayload, HandshakePayload};
use crate::msgs::handshake::{NewSessionTicketPayload, SessionID};
use crate::msgs::message::{Message, MessagePayload};
use crate::msgs::persist;
#[cfg(feature = "secret_extraction")]
use crate::suites::PartiallyExtractedSecrets;
use crate::tls12::{self, ConnectionSecrets, Tls12CipherSuite};
use crate::{kx, ticketer, verify};
use super::common::ActiveCertifiedKey;
use super::hs::{self, ServerContext};
use super::server_conn::{ProducesTickets, ServerConfig, ServerConnectionData};
use ring::constant_time;
use std::sync::Arc;
pub(super) use client_hello::CompleteClientHelloHandling;
mod client_hello {
use crate::enums::SignatureScheme;
use crate::msgs::enums::ECPointFormat;
use crate::msgs::enums::{ClientCertificateType, Compression};
use crate::msgs::handshake::{CertificateRequestPayload, ClientSessionTicket, Random};
use crate::msgs::handshake::{
CertificateStatus, DigitallySignedStruct, ECDHEServerKeyExchange,
};
use crate::msgs::handshake::{ClientExtension, SessionID};
use crate::msgs::handshake::{ClientHelloPayload, ServerHelloPayload};
use crate::msgs::handshake::{ECPointFormatList, ServerECDHParams, SupportedPointFormats};
use crate::msgs::handshake::{ServerExtension, ServerKeyExchangePayload};
use crate::sign;
use super::*;
pub(in crate::server) struct CompleteClientHelloHandling {
pub(in crate::server) config: Arc<ServerConfig>,
pub(in crate::server) transcript: HandshakeHash,
pub(in crate::server) session_id: SessionID,
pub(in crate::server) suite: &'static Tls12CipherSuite,
pub(in crate::server) using_ems: bool,
pub(in crate::server) randoms: ConnectionRandoms,
pub(in crate::server) send_ticket: bool,
pub(in crate::server) extra_exts: Vec<ServerExtension>,
}
impl CompleteClientHelloHandling {
pub(in crate::server) fn handle_client_hello(
mut self,
cx: &mut ServerContext<'_>,
server_key: ActiveCertifiedKey,
chm: &Message,
client_hello: &ClientHelloPayload,
sigschemes_ext: Vec<SignatureScheme>,
tls13_enabled: bool,
) -> hs::NextStateOrError {
// -- TLS1.2 only from hereon in --
self.transcript.add_message(chm);
if client_hello.ems_support_offered() {
self.using_ems = true;
}
let groups_ext = client_hello
.get_namedgroups_extension()
.ok_or_else(|| hs::incompatible(cx.common, "client didn't describe groups"))?;
let ecpoints_ext = client_hello
.get_ecpoints_extension()
.ok_or_else(|| hs::incompatible(cx.common, "client didn't describe ec points"))?;
trace!("namedgroups {:?}", groups_ext);
trace!("ecpoints {:?}", ecpoints_ext);
if !ecpoints_ext.contains(&ECPointFormat::Uncompressed) {
cx.common
.send_fatal_alert(AlertDescription::IllegalParameter);
return Err(Error::PeerIncompatibleError(
"client didn't support uncompressed ec points".to_string(),
));
}
// -- If TLS1.3 is enabled, signal the downgrade in the server random
if tls13_enabled {
self.randoms.server[24..].copy_from_slice(&tls12::DOWNGRADE_SENTINEL);
}
// -- Check for resumption --
// We can do this either by (in order of preference):
// 1. receiving a ticket that decrypts
// 2. receiving a sessionid that is in our cache
//
// If we receive a ticket, the sessionid won't be in our
// cache, so don't check.
//
// If either works, we end up with a ServerConnectionValue
// which is passed to start_resumption and concludes
// our handling of the ClientHello.
//
let mut ticket_received = false;
let resume_data = client_hello
.get_ticket_extension()
.and_then(|ticket_ext| match ticket_ext {
ClientExtension::SessionTicket(ClientSessionTicket::Offer(ticket)) => {
Some(ticket)
}
_ => None,
})
.and_then(|ticket| {
ticket_received = true;
debug!("Ticket received");
let data = self.config.ticketer.decrypt(&ticket.0);
if data.is_none() {
debug!("Ticket didn't decrypt");
}
data
})
.or_else(|| {
// Perhaps resume? If we received a ticket, the sessionid
// does not correspond to a real session.
if client_hello.session_id.is_empty() || ticket_received {
return None;
}
self.config
.session_storage
.get(&client_hello.session_id.get_encoding())
})
.and_then(|x| persist::ServerSessionValue::read_bytes(&x))
.filter(|resumedata| {
hs::can_resume(self.suite.into(), &cx.data.sni, self.using_ems, resumedata)
});
if let Some(data) = resume_data {
return self.start_resumption(cx, client_hello, &client_hello.session_id, data);
}
// Now we have chosen a ciphersuite, we can make kx decisions.
let sigschemes = self
.suite
.resolve_sig_schemes(&sigschemes_ext);
if sigschemes.is_empty() {
return Err(hs::incompatible(cx.common, "no overlapping sigschemes"));
}
let group = self
.config
.kx_groups
.iter()
.find(|skxg| groups_ext.contains(&skxg.name))
.cloned()
.ok_or_else(|| hs::incompatible(cx.common, "no supported group"))?;
let ecpoint = ECPointFormatList::supported()
.iter()
.find(|format| ecpoints_ext.contains(format))
.cloned()
.ok_or_else(|| hs::incompatible(cx.common, "no supported point format"))?;
debug_assert_eq!(ecpoint, ECPointFormat::Uncompressed);
let (mut ocsp_response, mut sct_list) =
(server_key.get_ocsp(), server_key.get_sct_list());
// If we're not offered a ticket or a potential session ID, allocate a session ID.
if !self.config.session_storage.can_cache() {
self.session_id = SessionID::empty();
} else if self.session_id.is_empty() && !ticket_received {
self.session_id = SessionID::random()?;
}
self.send_ticket = emit_server_hello(
&self.config,
&mut self.transcript,
cx,
self.session_id,
self.suite,
self.using_ems,
&mut ocsp_response,
&mut sct_list,
client_hello,
None,
&self.randoms,
self.extra_exts,
)?;
emit_certificate(&mut self.transcript, cx.common, server_key.get_cert());
if let Some(ocsp_response) = ocsp_response {
emit_cert_status(&mut self.transcript, cx.common, ocsp_response);
}
let server_kx = emit_server_kx(
&mut self.transcript,
cx.common,
sigschemes,
group,
server_key.get_key(),
&self.randoms,
)?;
let doing_client_auth = emit_certificate_req(&self.config, &mut self.transcript, cx)?;
emit_server_hello_done(&mut self.transcript, cx.common);
if doing_client_auth {
Ok(Box::new(ExpectCertificate {
config: self.config,
transcript: self.transcript,
randoms: self.randoms,
session_id: self.session_id,
suite: self.suite,
using_ems: self.using_ems,
server_kx,
send_ticket: self.send_ticket,
}))
} else {
Ok(Box::new(ExpectClientKx {
config: self.config,
transcript: self.transcript,
randoms: self.randoms,
session_id: self.session_id,
suite: self.suite,
using_ems: self.using_ems,
server_kx,
client_cert: None,
send_ticket: self.send_ticket,
}))
}
}
fn start_resumption(
mut self,
cx: &mut ServerContext<'_>,
client_hello: &ClientHelloPayload,
id: &SessionID,
resumedata: persist::ServerSessionValue,
) -> hs::NextStateOrError {
debug!("Resuming connection");
if resumedata.extended_ms && !self.using_ems {
return Err(cx
.common
.illegal_param("refusing to resume without ems"));
}
self.session_id = *id;
self.send_ticket = emit_server_hello(
&self.config,
&mut self.transcript,
cx,
self.session_id,
self.suite,
self.using_ems,
&mut None,
&mut None,
client_hello,
Some(&resumedata),
&self.randoms,
self.extra_exts,
)?;
let secrets = ConnectionSecrets::new_resume(
self.randoms,
self.suite,
&resumedata.master_secret.0,
);
self.config.key_log.log(
"CLIENT_RANDOM",
&secrets.randoms.client,
&secrets.master_secret,
);
cx.common
.start_encryption_tls12(&secrets, Side::Server);
cx.common.peer_certificates = resumedata.client_cert_chain;
if self.send_ticket {
emit_ticket(
&secrets,
&mut self.transcript,
self.using_ems,
cx,
&*self.config.ticketer,
)?;
}
emit_ccs(cx.common);
cx.common
.record_layer
.start_encrypting();
emit_finished(&secrets, &mut self.transcript, cx.common);
Ok(Box::new(ExpectCcs {
config: self.config,
secrets,
transcript: self.transcript,
session_id: self.session_id,
using_ems: self.using_ems,
resuming: true,
send_ticket: self.send_ticket,
}))
}
}
fn emit_server_hello(
config: &ServerConfig,
transcript: &mut HandshakeHash,
cx: &mut ServerContext<'_>,
session_id: SessionID,
suite: &'static Tls12CipherSuite,
using_ems: bool,
ocsp_response: &mut Option<&[u8]>,
sct_list: &mut Option<&[u8]>,
hello: &ClientHelloPayload,
resumedata: Option<&persist::ServerSessionValue>,
randoms: &ConnectionRandoms,
extra_exts: Vec<ServerExtension>,
) -> Result<bool, Error> {
let mut ep = hs::ExtensionProcessing::new();
ep.process_common(
config,
cx,
ocsp_response,
sct_list,
hello,
resumedata,
extra_exts,
)?;
ep.process_tls12(config, hello, using_ems);
let sh = Message {
version: ProtocolVersion::TLSv1_2,
payload: MessagePayload::handshake(HandshakeMessagePayload {
typ: HandshakeType::ServerHello,
payload: HandshakePayload::ServerHello(ServerHelloPayload {
legacy_version: ProtocolVersion::TLSv1_2,
random: Random::from(randoms.server),
session_id,
cipher_suite: suite.common.suite,
compression_method: Compression::Null,
extensions: ep.exts,
}),
}),
};
trace!("sending server hello {:?}", sh);
transcript.add_message(&sh);
cx.common.send_msg(sh, false);
Ok(ep.send_ticket)
}
fn emit_certificate(
transcript: &mut HandshakeHash,
common: &mut CommonState,
cert_chain: &[Certificate],
) {
let c = Message {
version: ProtocolVersion::TLSv1_2,
payload: MessagePayload::handshake(HandshakeMessagePayload {
typ: HandshakeType::Certificate,
payload: HandshakePayload::Certificate(cert_chain.to_owned()),
}),
};
transcript.add_message(&c);
common.send_msg(c, false);
}
fn emit_cert_status(transcript: &mut HandshakeHash, common: &mut CommonState, ocsp: &[u8]) {
let st = CertificateStatus::new(ocsp.to_owned());
let c = Message {
version: ProtocolVersion::TLSv1_2,
payload: MessagePayload::handshake(HandshakeMessagePayload {
typ: HandshakeType::CertificateStatus,
payload: HandshakePayload::CertificateStatus(st),
}),
};
transcript.add_message(&c);
common.send_msg(c, false);
}
fn emit_server_kx(
transcript: &mut HandshakeHash,
common: &mut CommonState,
sigschemes: Vec<SignatureScheme>,
skxg: &'static kx::SupportedKxGroup,
signing_key: &dyn sign::SigningKey,
randoms: &ConnectionRandoms,
) -> Result<kx::KeyExchange, Error> {
let kx = kx::KeyExchange::start(skxg).ok_or(Error::FailedToGetRandomBytes)?;
let secdh = ServerECDHParams::new(skxg.name, kx.pubkey.as_ref());
let mut msg = Vec::new();
msg.extend(randoms.client);
msg.extend(randoms.server);
secdh.encode(&mut msg);
let signer = signing_key
.choose_scheme(&sigschemes)
.ok_or_else(|| Error::General("incompatible signing key".to_string()))?;
let sigscheme = signer.scheme();
let sig = signer.sign(&msg)?;
let skx = ServerKeyExchangePayload::ECDHE(ECDHEServerKeyExchange {
params: secdh,
dss: DigitallySignedStruct::new(sigscheme, sig),
});
let m = Message {
version: ProtocolVersion::TLSv1_2,
payload: MessagePayload::handshake(HandshakeMessagePayload {
typ: HandshakeType::ServerKeyExchange,
payload: HandshakePayload::ServerKeyExchange(skx),
}),
};
transcript.add_message(&m);
common.send_msg(m, false);
Ok(kx)
}
fn emit_certificate_req(
config: &ServerConfig,
transcript: &mut HandshakeHash,
cx: &mut ServerContext<'_>,
) -> Result<bool, Error> {
let client_auth = &config.verifier;
if !client_auth.offer_client_auth() {
return Ok(false);
}
let verify_schemes = client_auth.supported_verify_schemes();
let names = client_auth
.client_auth_root_subjects()
.ok_or_else(|| {
debug!("could not determine root subjects based on SNI");
cx.common
.send_fatal_alert(AlertDescription::AccessDenied);
Error::General("client rejected by client_auth_root_subjects".into())
})?;
let cr = CertificateRequestPayload {
certtypes: vec![
ClientCertificateType::RSASign,
ClientCertificateType::ECDSASign,
],
sigschemes: verify_schemes,
canames: names,
};
let m = Message {
version: ProtocolVersion::TLSv1_2,
payload: MessagePayload::handshake(HandshakeMessagePayload {
typ: HandshakeType::CertificateRequest,
payload: HandshakePayload::CertificateRequest(cr),
}),
};
trace!("Sending CertificateRequest {:?}", m);
transcript.add_message(&m);
cx.common.send_msg(m, false);
Ok(true)
}
fn emit_server_hello_done(transcript: &mut HandshakeHash, common: &mut CommonState) {
let m = Message {
version: ProtocolVersion::TLSv1_2,
payload: MessagePayload::handshake(HandshakeMessagePayload {
typ: HandshakeType::ServerHelloDone,
payload: HandshakePayload::ServerHelloDone,
}),
};
transcript.add_message(&m);
common.send_msg(m, false);
}
}
// --- Process client's Certificate for client auth ---
struct ExpectCertificate {
config: Arc<ServerConfig>,
transcript: HandshakeHash,
randoms: ConnectionRandoms,
session_id: SessionID,
suite: &'static Tls12CipherSuite,
using_ems: bool,
server_kx: kx::KeyExchange,
send_ticket: bool,
}
impl State<ServerConnectionData> for ExpectCertificate {
fn handle(mut self: Box<Self>, cx: &mut ServerContext<'_>, m: Message) -> hs::NextStateOrError {
self.transcript.add_message(&m);
let cert_chain = require_handshake_msg_move!(
m,
HandshakeType::Certificate,
HandshakePayload::Certificate
)?;
// If we can't determine if the auth is mandatory, abort
let mandatory = self
.config
.verifier
.client_auth_mandatory()
.ok_or_else(|| {
debug!("could not determine if client auth is mandatory based on SNI");
cx.common
.send_fatal_alert(AlertDescription::AccessDenied);
Error::General("client rejected by client_auth_mandatory".into())
})?;
trace!("certs {:?}", cert_chain);
let client_cert = match cert_chain.split_first() {
None if mandatory => {
cx.common
.send_fatal_alert(AlertDescription::CertificateRequired);
return Err(Error::NoCertificatesPresented);
}
None => {
debug!("client auth requested but no certificate supplied");
self.transcript.abandon_client_auth();
None
}
Some((end_entity, intermediates)) => {
let now = std::time::SystemTime::now();
self.config
.verifier
.verify_client_cert(end_entity, intermediates, now)
.map_err(|err| {
hs::incompatible(cx.common, "certificate invalid");
err
})?;
Some(cert_chain)
}
};
Ok(Box::new(ExpectClientKx {
config: self.config,
transcript: self.transcript,
randoms: self.randoms,
session_id: self.session_id,
suite: self.suite,
using_ems: self.using_ems,
server_kx: self.server_kx,
client_cert,
send_ticket: self.send_ticket,
}))
}
}
// --- Process client's KeyExchange ---
struct ExpectClientKx {
config: Arc<ServerConfig>,
transcript: HandshakeHash,
randoms: ConnectionRandoms,
session_id: SessionID,
suite: &'static Tls12CipherSuite,
using_ems: bool,
server_kx: kx::KeyExchange,
client_cert: Option<Vec<Certificate>>,
send_ticket: bool,
}
impl State<ServerConnectionData> for ExpectClientKx {
fn handle(mut self: Box<Self>, cx: &mut ServerContext<'_>, m: Message) -> hs::NextStateOrError {
let client_kx = require_handshake_msg!(
m,
HandshakeType::ClientKeyExchange,
HandshakePayload::ClientKeyExchange
)?;
self.transcript.add_message(&m);
let ems_seed = self
.using_ems
.then(|| self.transcript.get_current_hash());
// Complete key agreement, and set up encryption with the
// resulting premaster secret.
let peer_kx_params =
tls12::decode_ecdh_params::<ClientECDHParams>(cx.common, &client_kx.0)?;
let secrets = ConnectionSecrets::from_key_exchange(
self.server_kx,
&peer_kx_params.public.0,
ems_seed,
self.randoms,
self.suite,
)?;
self.config.key_log.log(
"CLIENT_RANDOM",
&secrets.randoms.client,
&secrets.master_secret,
);
cx.common
.start_encryption_tls12(&secrets, Side::Server);
if let Some(client_cert) = self.client_cert {
Ok(Box::new(ExpectCertificateVerify {
config: self.config,
secrets,
transcript: self.transcript,
session_id: self.session_id,
using_ems: self.using_ems,
client_cert,
send_ticket: self.send_ticket,
}))
} else {
Ok(Box::new(ExpectCcs {
config: self.config,
secrets,
transcript: self.transcript,
session_id: self.session_id,
using_ems: self.using_ems,
resuming: false,
send_ticket: self.send_ticket,
}))
}
}
}
// --- Process client's certificate proof ---
struct ExpectCertificateVerify {
config: Arc<ServerConfig>,
secrets: ConnectionSecrets,
transcript: HandshakeHash,
session_id: SessionID,
using_ems: bool,
client_cert: Vec<Certificate>,
send_ticket: bool,
}
impl State<ServerConnectionData> for ExpectCertificateVerify {
fn handle(mut self: Box<Self>, cx: &mut ServerContext<'_>, m: Message) -> hs::NextStateOrError {
let rc = {
let sig = require_handshake_msg!(
m,
HandshakeType::CertificateVerify,
HandshakePayload::CertificateVerify
)?;
match self.transcript.take_handshake_buf() {
Some(msgs) => {
let certs = &self.client_cert;
self.config
.verifier
.verify_tls12_signature(&msgs, &certs[0], sig)
}
None => {
// This should be unreachable; the handshake buffer was initialized with
// client authentication if the verifier wants to offer it.
// `transcript.abandon_client_auth()` can extract it, but its only caller in
// this flow will also set `ExpectClientKx::client_cert` to `None`, making it
// impossible to reach this state.
cx.common
.send_fatal_alert(AlertDescription::AccessDenied);
Err(Error::General("client authentication not set up".into()))
}
}
};
if let Err(e) = rc {
cx.common
.send_fatal_alert(AlertDescription::AccessDenied);
return Err(e);
}
trace!("client CertificateVerify OK");
cx.common.peer_certificates = Some(self.client_cert);
self.transcript.add_message(&m);
Ok(Box::new(ExpectCcs {
config: self.config,
secrets: self.secrets,
transcript: self.transcript,
session_id: self.session_id,
using_ems: self.using_ems,
resuming: false,
send_ticket: self.send_ticket,
}))
}
}
// --- Process client's ChangeCipherSpec ---
struct ExpectCcs {
config: Arc<ServerConfig>,
secrets: ConnectionSecrets,
transcript: HandshakeHash,
session_id: SessionID,
using_ems: bool,
resuming: bool,
send_ticket: bool,
}
impl State<ServerConnectionData> for ExpectCcs {
fn handle(self: Box<Self>, cx: &mut ServerContext<'_>, m: Message) -> hs::NextStateOrError {
match m.payload {
MessagePayload::ChangeCipherSpec(..) => {}
payload => {
return Err(inappropriate_message(
&payload,
&[ContentType::ChangeCipherSpec],
))
}
}
// CCS should not be received interleaved with fragmented handshake-level
// message.
cx.common.check_aligned_handshake()?;
cx.common
.record_layer
.start_decrypting();
Ok(Box::new(ExpectFinished {
config: self.config,
secrets: self.secrets,
transcript: self.transcript,
session_id: self.session_id,
using_ems: self.using_ems,
resuming: self.resuming,
send_ticket: self.send_ticket,
}))
}
}
// --- Process client's Finished ---
fn get_server_connection_value_tls12(
secrets: &ConnectionSecrets,
using_ems: bool,
cx: &ServerContext<'_>,
time_now: ticketer::TimeBase,
) -> persist::ServerSessionValue {
let version = ProtocolVersion::TLSv1_2;
let secret = secrets.get_master_secret();
let mut v = persist::ServerSessionValue::new(
cx.data.sni.as_ref(),
version,
secrets.suite().common.suite,
secret,
cx.common.peer_certificates.clone(),
cx.common.alpn_protocol.clone(),
cx.data.resumption_data.clone(),
time_now,
0,
);
if using_ems {
v.set_extended_ms_used();
}
v
}
fn emit_ticket(
secrets: &ConnectionSecrets,
transcript: &mut HandshakeHash,
using_ems: bool,
cx: &mut ServerContext<'_>,
ticketer: &dyn ProducesTickets,
) -> Result<(), Error> {
let time_now = ticketer::TimeBase::now()?;
let plain = get_server_connection_value_tls12(secrets, using_ems, cx, time_now).get_encoding();
// If we can't produce a ticket for some reason, we can't
// report an error. Send an empty one.
let ticket = ticketer
.encrypt(&plain)
.unwrap_or_default();
let ticket_lifetime = ticketer.lifetime();
let m = Message {
version: ProtocolVersion::TLSv1_2,
payload: MessagePayload::handshake(HandshakeMessagePayload {
typ: HandshakeType::NewSessionTicket,
payload: HandshakePayload::NewSessionTicket(NewSessionTicketPayload::new(
ticket_lifetime,
ticket,
)),
}),
};
transcript.add_message(&m);
cx.common.send_msg(m, false);
Ok(())
}
fn emit_ccs(common: &mut CommonState) {
let m = Message {
version: ProtocolVersion::TLSv1_2,
payload: MessagePayload::ChangeCipherSpec(ChangeCipherSpecPayload {}),
};
common.send_msg(m, false);
}
fn emit_finished(
secrets: &ConnectionSecrets,
transcript: &mut HandshakeHash,
common: &mut CommonState,
) {
let vh = transcript.get_current_hash();
let verify_data = secrets.server_verify_data(&vh);
let verify_data_payload = Payload::new(verify_data);
let f = Message {
version: ProtocolVersion::TLSv1_2,
payload: MessagePayload::handshake(HandshakeMessagePayload {
typ: HandshakeType::Finished,
payload: HandshakePayload::Finished(verify_data_payload),
}),
};
transcript.add_message(&f);
common.send_msg(f, true);
}
struct ExpectFinished {
config: Arc<ServerConfig>,
secrets: ConnectionSecrets,
transcript: HandshakeHash,
session_id: SessionID,
using_ems: bool,
resuming: bool,
send_ticket: bool,
}
impl State<ServerConnectionData> for ExpectFinished {
fn handle(mut self: Box<Self>, cx: &mut ServerContext<'_>, m: Message) -> hs::NextStateOrError {
let finished =
require_handshake_msg!(m, HandshakeType::Finished, HandshakePayload::Finished)?;
cx.common.check_aligned_handshake()?;
let vh = self.transcript.get_current_hash();
let expect_verify_data = self.secrets.client_verify_data(&vh);
let _fin_verified =
constant_time::verify_slices_are_equal(&expect_verify_data, &finished.0)
.map_err(|_| {
cx.common
.send_fatal_alert(AlertDescription::DecryptError);
Error::DecryptError
})
.map(|_| verify::FinishedMessageVerified::assertion())?;
// Save connection, perhaps
if !self.resuming && !self.session_id.is_empty() {
let time_now = ticketer::TimeBase::now()?;
let value =
get_server_connection_value_tls12(&self.secrets, self.using_ems, cx, time_now);
let worked = self
.config
.session_storage
.put(self.session_id.get_encoding(), value.get_encoding());
if worked {
debug!("Session saved");
} else {
debug!("Session not saved");
}
}
// Send our CCS and Finished.
self.transcript.add_message(&m);
if !self.resuming {
if self.send_ticket {
emit_ticket(
&self.secrets,
&mut self.transcript,
self.using_ems,
cx,
&*self.config.ticketer,
)?;
}
emit_ccs(cx.common);
cx.common
.record_layer
.start_encrypting();
emit_finished(&self.secrets, &mut self.transcript, cx.common);
}
cx.common.start_traffic();
Ok(Box::new(ExpectTraffic {
secrets: self.secrets,
_fin_verified,
}))
}
}
// --- Process traffic ---
struct ExpectTraffic {
secrets: ConnectionSecrets,
_fin_verified: verify::FinishedMessageVerified,
}
impl ExpectTraffic {}
impl State<ServerConnectionData> for ExpectTraffic {
fn handle(self: Box<Self>, cx: &mut ServerContext<'_>, m: Message) -> hs::NextStateOrError {
match m.payload {
MessagePayload::ApplicationData(payload) => cx
.common
.take_received_plaintext(payload),
payload => {
return Err(inappropriate_message(
&payload,
&[ContentType::ApplicationData],
));
}
}
Ok(self)
}
fn export_keying_material(
&self,
output: &mut [u8],
label: &[u8],
context: Option<&[u8]>,
) -> Result<(), Error> {
self.secrets
.export_keying_material(output, label, context);
Ok(())
}
#[cfg(feature = "secret_extraction")]
fn extract_secrets(&self) -> Result<PartiallyExtractedSecrets, Error> {
self.secrets
.extract_secrets(Side::Server)
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,523 @@
use crate::enums::SignatureScheme;
use crate::error::Error;
use crate::key;
use crate::msgs::enums::SignatureAlgorithm;
use crate::x509::{wrap_in_asn1_len, wrap_in_sequence};
use ring::io::der;
use ring::signature::{self, EcdsaKeyPair, Ed25519KeyPair, RsaKeyPair};
use std::convert::TryFrom;
use std::error::Error as StdError;
use std::fmt;
use std::sync::Arc;
/// An abstract signing key.
pub trait SigningKey: Send + Sync {
/// Choose a `SignatureScheme` from those offered.
///
/// Expresses the choice by returning something that implements `Signer`,
/// using the chosen scheme.
fn choose_scheme(&self, offered: &[SignatureScheme]) -> Option<Box<dyn Signer>>;
/// What kind of key we have.
fn algorithm(&self) -> SignatureAlgorithm;
}
/// A thing that can sign a message.
pub trait Signer: Send + Sync {
/// Signs `message` using the selected scheme.
fn sign(&self, message: &[u8]) -> Result<Vec<u8>, Error>;
/// Reveals which scheme will be used when you call `sign()`.
fn scheme(&self) -> SignatureScheme;
}
/// A packaged-together certificate chain, matching `SigningKey` and
/// optional stapled OCSP response and/or SCT list.
#[derive(Clone)]
pub struct CertifiedKey {
/// The certificate chain.
pub cert: Vec<key::Certificate>,
/// The certified key.
pub key: Arc<dyn SigningKey>,
/// An optional OCSP response from the certificate issuer,
/// attesting to its continued validity.
pub ocsp: Option<Vec<u8>>,
/// An optional collection of SCTs from CT logs, proving the
/// certificate is included on those logs. This must be
/// a `SignedCertificateTimestampList` encoding; see RFC6962.
pub sct_list: Option<Vec<u8>>,
}
impl CertifiedKey {
/// Make a new CertifiedKey, with the given chain and key.
///
/// The cert chain must not be empty. The first certificate in the chain
/// must be the end-entity certificate.
pub fn new(cert: Vec<key::Certificate>, key: Arc<dyn SigningKey>) -> Self {
Self {
cert,
key,
ocsp: None,
sct_list: None,
}
}
/// The end-entity certificate.
pub fn end_entity_cert(&self) -> Result<&key::Certificate, SignError> {
self.cert.get(0).ok_or(SignError(()))
}
/// Check the certificate chain for validity:
/// - it should be non-empty list
/// - the first certificate should be parsable as a x509v3,
/// - the first certificate should quote the given server name
/// (if provided)
///
/// These checks are not security-sensitive. They are the
/// *server* attempting to detect accidental misconfiguration.
pub(crate) fn cross_check_end_entity_cert(
&self,
name: Option<webpki::DnsNameRef>,
) -> Result<(), Error> {
// Always reject an empty certificate chain.
let end_entity_cert = self
.end_entity_cert()
.map_err(|SignError(())| {
Error::General("No end-entity certificate in certificate chain".to_string())
})?;
// Reject syntactically-invalid end-entity certificates.
let end_entity_cert =
webpki::EndEntityCert::try_from(end_entity_cert.as_ref()).map_err(|_| {
Error::General(
"End-entity certificate in certificate \
chain is syntactically invalid"
.to_string(),
)
})?;
if let Some(name) = name {
// If SNI was offered then the certificate must be valid for
// that hostname. Note that this doesn't fully validate that the
// certificate is valid; it only validates that the name is one
// that the certificate is valid for, if the certificate is
// valid.
if end_entity_cert
.verify_is_valid_for_dns_name(name)
.is_err()
{
return Err(Error::General(
"The server certificate is not \
valid for the given name"
.to_string(),
));
}
}
Ok(())
}
}
/// Parse `der` as any supported key encoding/type, returning
/// the first which works.
pub fn any_supported_type(der: &key::PrivateKey) -> Result<Arc<dyn SigningKey>, SignError> {
if let Ok(rsa) = RsaSigningKey::new(der) {
Ok(Arc::new(rsa))
} else if let Ok(ecdsa) = any_ecdsa_type(der) {
Ok(ecdsa)
} else {
any_eddsa_type(der)
}
}
/// Parse `der` as any ECDSA key type, returning the first which works.
///
/// Both SEC1 (PEM section starting with 'BEGIN EC PRIVATE KEY') and PKCS8
/// (PEM section starting with 'BEGIN PRIVATE KEY') encodings are supported.
pub fn any_ecdsa_type(der: &key::PrivateKey) -> Result<Arc<dyn SigningKey>, SignError> {
if let Ok(ecdsa_p256) = EcdsaSigningKey::new(
der,
SignatureScheme::ECDSA_NISTP256_SHA256,
&signature::ECDSA_P256_SHA256_ASN1_SIGNING,
) {
return Ok(Arc::new(ecdsa_p256));
}
if let Ok(ecdsa_p384) = EcdsaSigningKey::new(
der,
SignatureScheme::ECDSA_NISTP384_SHA384,
&signature::ECDSA_P384_SHA384_ASN1_SIGNING,
) {
return Ok(Arc::new(ecdsa_p384));
}
Err(SignError(()))
}
/// Parse `der` as any EdDSA key type, returning the first which works.
pub fn any_eddsa_type(der: &key::PrivateKey) -> Result<Arc<dyn SigningKey>, SignError> {
if let Ok(ed25519) = Ed25519SigningKey::new(der, SignatureScheme::ED25519) {
return Ok(Arc::new(ed25519));
}
// TODO: Add support for Ed448
Err(SignError(()))
}
/// A `SigningKey` for RSA-PKCS1 or RSA-PSS.
///
/// This is used by the test suite, so it must be `pub`, but it isn't part of
/// the public, stable, API.
#[doc(hidden)]
pub struct RsaSigningKey {
key: Arc<RsaKeyPair>,
}
static ALL_RSA_SCHEMES: &[SignatureScheme] = &[
SignatureScheme::RSA_PSS_SHA512,
SignatureScheme::RSA_PSS_SHA384,
SignatureScheme::RSA_PSS_SHA256,
SignatureScheme::RSA_PKCS1_SHA512,
SignatureScheme::RSA_PKCS1_SHA384,
SignatureScheme::RSA_PKCS1_SHA256,
];
impl RsaSigningKey {
/// Make a new `RsaSigningKey` from a DER encoding, in either
/// PKCS#1 or PKCS#8 format.
pub fn new(der: &key::PrivateKey) -> Result<Self, SignError> {
RsaKeyPair::from_der(&der.0)
.or_else(|_| RsaKeyPair::from_pkcs8(&der.0))
.map(|s| Self { key: Arc::new(s) })
.map_err(|_| SignError(()))
}
}
impl SigningKey for RsaSigningKey {
fn choose_scheme(&self, offered: &[SignatureScheme]) -> Option<Box<dyn Signer>> {
ALL_RSA_SCHEMES
.iter()
.find(|scheme| offered.contains(scheme))
.map(|scheme| RsaSigner::new(Arc::clone(&self.key), *scheme))
}
fn algorithm(&self) -> SignatureAlgorithm {
SignatureAlgorithm::RSA
}
}
#[allow(clippy::upper_case_acronyms)]
#[doc(hidden)]
#[deprecated(since = "0.20.0", note = "Use RsaSigningKey")]
pub type RSASigningKey = RsaSigningKey;
struct RsaSigner {
key: Arc<RsaKeyPair>,
scheme: SignatureScheme,
encoding: &'static dyn signature::RsaEncoding,
}
impl RsaSigner {
fn new(key: Arc<RsaKeyPair>, scheme: SignatureScheme) -> Box<dyn Signer> {
let encoding: &dyn signature::RsaEncoding = match scheme {
SignatureScheme::RSA_PKCS1_SHA256 => &signature::RSA_PKCS1_SHA256,
SignatureScheme::RSA_PKCS1_SHA384 => &signature::RSA_PKCS1_SHA384,
SignatureScheme::RSA_PKCS1_SHA512 => &signature::RSA_PKCS1_SHA512,
SignatureScheme::RSA_PSS_SHA256 => &signature::RSA_PSS_SHA256,
SignatureScheme::RSA_PSS_SHA384 => &signature::RSA_PSS_SHA384,
SignatureScheme::RSA_PSS_SHA512 => &signature::RSA_PSS_SHA512,
_ => unreachable!(),
};
Box::new(Self {
key,
scheme,
encoding,
})
}
}
impl Signer for RsaSigner {
fn sign(&self, message: &[u8]) -> Result<Vec<u8>, Error> {
let mut sig = vec![0; self.key.public_modulus_len()];
let rng = ring::rand::SystemRandom::new();
self.key
.sign(self.encoding, &rng, message, &mut sig)
.map(|_| sig)
.map_err(|_| Error::General("signing failed".to_string()))
}
fn scheme(&self) -> SignatureScheme {
self.scheme
}
}
/// A SigningKey that uses exactly one TLS-level SignatureScheme
/// and one ring-level signature::SigningAlgorithm.
///
/// Compare this to RsaSigningKey, which for a particular key is
/// willing to sign with several algorithms. This is quite poor
/// cryptography practice, but is necessary because a given RSA key
/// is expected to work in TLS1.2 (PKCS#1 signatures) and TLS1.3
/// (PSS signatures) -- nobody is willing to obtain certificates for
/// different protocol versions.
///
/// Currently this is only implemented for ECDSA keys.
struct EcdsaSigningKey {
key: Arc<EcdsaKeyPair>,
scheme: SignatureScheme,
}
impl EcdsaSigningKey {
/// Make a new `ECDSASigningKey` from a DER encoding in PKCS#8 or SEC1
/// format, expecting a key usable with precisely the given signature
/// scheme.
fn new(
der: &key::PrivateKey,
scheme: SignatureScheme,
sigalg: &'static signature::EcdsaSigningAlgorithm,
) -> Result<Self, ()> {
EcdsaKeyPair::from_pkcs8(sigalg, &der.0)
.map_err(|_| ())
.or_else(|_| Self::convert_sec1_to_pkcs8(scheme, sigalg, &der.0))
.map(|kp| Self {
key: Arc::new(kp),
scheme,
})
}
/// Convert a SEC1 encoding to PKCS8, and ask ring to parse it. This
/// can be removed once https://github.com/briansmith/ring/pull/1456
/// (or equivalent) is landed.
fn convert_sec1_to_pkcs8(
scheme: SignatureScheme,
sigalg: &'static signature::EcdsaSigningAlgorithm,
maybe_sec1_der: &[u8],
) -> Result<EcdsaKeyPair, ()> {
let pkcs8_prefix = match scheme {
SignatureScheme::ECDSA_NISTP256_SHA256 => &PKCS8_PREFIX_ECDSA_NISTP256,
SignatureScheme::ECDSA_NISTP384_SHA384 => &PKCS8_PREFIX_ECDSA_NISTP384,
_ => unreachable!(), // all callers are in this file
};
// wrap sec1 encoding in an OCTET STRING
let mut sec1_wrap = Vec::with_capacity(maybe_sec1_der.len() + 8);
sec1_wrap.extend_from_slice(maybe_sec1_der);
wrap_in_asn1_len(&mut sec1_wrap);
sec1_wrap.insert(0, der::Tag::OctetString as u8);
let mut pkcs8 = Vec::with_capacity(pkcs8_prefix.len() + sec1_wrap.len() + 4);
pkcs8.extend_from_slice(pkcs8_prefix);
pkcs8.extend_from_slice(&sec1_wrap);
wrap_in_sequence(&mut pkcs8);
EcdsaKeyPair::from_pkcs8(sigalg, &pkcs8).map_err(|_| ())
}
}
// This is (line-by-line):
// - INTEGER Version = 0
// - SEQUENCE (privateKeyAlgorithm)
// - id-ecPublicKey OID
// - prime256v1 OID
const PKCS8_PREFIX_ECDSA_NISTP256: &[u8] = b"\x02\x01\x00\
\x30\x13\
\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\
\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07";
// This is (line-by-line):
// - INTEGER Version = 0
// - SEQUENCE (privateKeyAlgorithm)
// - id-ecPublicKey OID
// - secp384r1 OID
const PKCS8_PREFIX_ECDSA_NISTP384: &[u8] = b"\x02\x01\x00\
\x30\x10\
\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\
\x06\x05\x2b\x81\x04\x00\x22";
impl SigningKey for EcdsaSigningKey {
fn choose_scheme(&self, offered: &[SignatureScheme]) -> Option<Box<dyn Signer>> {
if offered.contains(&self.scheme) {
Some(Box::new(EcdsaSigner {
key: Arc::clone(&self.key),
scheme: self.scheme,
}))
} else {
None
}
}
fn algorithm(&self) -> SignatureAlgorithm {
use crate::msgs::handshake::DecomposedSignatureScheme;
self.scheme.sign()
}
}
struct EcdsaSigner {
key: Arc<EcdsaKeyPair>,
scheme: SignatureScheme,
}
impl Signer for EcdsaSigner {
fn sign(&self, message: &[u8]) -> Result<Vec<u8>, Error> {
let rng = ring::rand::SystemRandom::new();
self.key
.sign(&rng, message)
.map_err(|_| Error::General("signing failed".into()))
.map(|sig| sig.as_ref().into())
}
fn scheme(&self) -> SignatureScheme {
self.scheme
}
}
/// A SigningKey that uses exactly one TLS-level SignatureScheme
/// and one ring-level signature::SigningAlgorithm.
///
/// Compare this to RsaSigningKey, which for a particular key is
/// willing to sign with several algorithms. This is quite poor
/// cryptography practice, but is necessary because a given RSA key
/// is expected to work in TLS1.2 (PKCS#1 signatures) and TLS1.3
/// (PSS signatures) -- nobody is willing to obtain certificates for
/// different protocol versions.
///
/// Currently this is only implemented for Ed25519 keys.
struct Ed25519SigningKey {
key: Arc<Ed25519KeyPair>,
scheme: SignatureScheme,
}
impl Ed25519SigningKey {
/// Make a new `Ed25519SigningKey` from a DER encoding in PKCS#8 format,
/// expecting a key usable with precisely the given signature scheme.
fn new(der: &key::PrivateKey, scheme: SignatureScheme) -> Result<Self, SignError> {
Ed25519KeyPair::from_pkcs8_maybe_unchecked(&der.0)
.map(|kp| Self {
key: Arc::new(kp),
scheme,
})
.map_err(|_| SignError(()))
}
}
impl SigningKey for Ed25519SigningKey {
fn choose_scheme(&self, offered: &[SignatureScheme]) -> Option<Box<dyn Signer>> {
if offered.contains(&self.scheme) {
Some(Box::new(Ed25519Signer {
key: Arc::clone(&self.key),
scheme: self.scheme,
}))
} else {
None
}
}
fn algorithm(&self) -> SignatureAlgorithm {
use crate::msgs::handshake::DecomposedSignatureScheme;
self.scheme.sign()
}
}
struct Ed25519Signer {
key: Arc<Ed25519KeyPair>,
scheme: SignatureScheme,
}
impl Signer for Ed25519Signer {
fn sign(&self, message: &[u8]) -> Result<Vec<u8>, Error> {
Ok(self.key.sign(message).as_ref().into())
}
fn scheme(&self) -> SignatureScheme {
self.scheme
}
}
/// The set of schemes we support for signatures and
/// that are allowed for TLS1.3.
pub fn supported_sign_tls13() -> &'static [SignatureScheme] {
&[
SignatureScheme::ECDSA_NISTP384_SHA384,
SignatureScheme::ECDSA_NISTP256_SHA256,
SignatureScheme::RSA_PSS_SHA512,
SignatureScheme::RSA_PSS_SHA384,
SignatureScheme::RSA_PSS_SHA256,
SignatureScheme::ED25519,
]
}
/// Errors while signing
#[derive(Debug)]
pub struct SignError(());
impl fmt::Display for SignError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("sign error")
}
}
impl StdError for SignError {}
#[test]
fn can_load_ecdsa_nistp256_pkcs8() {
let key = key::PrivateKey(include_bytes!("testdata/nistp256key.pkcs8.der").to_vec());
assert!(any_supported_type(&key).is_ok());
assert!(any_ecdsa_type(&key).is_ok());
assert!(any_eddsa_type(&key).is_err());
}
#[test]
fn can_load_ecdsa_nistp256_sec1() {
let key = key::PrivateKey(include_bytes!("testdata/nistp256key.der").to_vec());
assert!(any_supported_type(&key).is_ok());
assert!(any_ecdsa_type(&key).is_ok());
assert!(any_eddsa_type(&key).is_err());
}
#[test]
fn can_load_ecdsa_nistp384_pkcs8() {
let key = key::PrivateKey(include_bytes!("testdata/nistp384key.pkcs8.der").to_vec());
assert!(any_supported_type(&key).is_ok());
assert!(any_ecdsa_type(&key).is_ok());
assert!(any_eddsa_type(&key).is_err());
}
#[test]
fn can_load_ecdsa_nistp384_sec1() {
let key = key::PrivateKey(include_bytes!("testdata/nistp384key.der").to_vec());
assert!(any_supported_type(&key).is_ok());
assert!(any_ecdsa_type(&key).is_ok());
assert!(any_eddsa_type(&key).is_err());
}
#[test]
fn can_load_eddsa_pkcs8() {
let key = key::PrivateKey(include_bytes!("testdata/eddsakey.der").to_vec());
assert!(any_supported_type(&key).is_ok());
assert!(any_eddsa_type(&key).is_ok());
assert!(any_ecdsa_type(&key).is_err());
}
#[test]
fn can_load_rsa2048_pkcs8() {
let key = key::PrivateKey(include_bytes!("testdata/rsa2048key.pkcs8.der").to_vec());
assert!(any_supported_type(&key).is_ok());
assert!(any_eddsa_type(&key).is_err());
assert!(any_ecdsa_type(&key).is_err());
}
#[test]
fn can_load_rsa2048_pkcs1() {
let key = key::PrivateKey(include_bytes!("testdata/rsa2048key.pkcs1.der").to_vec());
assert!(any_supported_type(&key).is_ok());
assert!(any_eddsa_type(&key).is_err());
assert!(any_ecdsa_type(&key).is_err());
}

View file

@ -0,0 +1,254 @@
use crate::conn::{ConnectionCommon, SideData};
use std::io::{IoSlice, Read, Result, Write};
use std::ops::{Deref, DerefMut};
/// This type implements `io::Read` and `io::Write`, encapsulating
/// a Connection `C` and an underlying transport `T`, such as a socket.
///
/// This allows you to use a rustls Connection like a normal stream.
#[derive(Debug)]
pub struct Stream<'a, C: 'a + ?Sized, T: 'a + Read + Write + ?Sized> {
/// Our TLS connection
pub conn: &'a mut C,
/// The underlying transport, like a socket
pub sock: &'a mut T,
}
impl<'a, C, T, S> Stream<'a, C, T>
where
C: 'a + DerefMut + Deref<Target = ConnectionCommon<S>>,
T: 'a + Read + Write,
S: SideData,
{
/// Make a new Stream using the Connection `conn` and socket-like object
/// `sock`. This does not fail and does no IO.
pub fn new(conn: &'a mut C, sock: &'a mut T) -> Self {
Self { conn, sock }
}
/// If we're handshaking, complete all the IO for that.
/// If we have data to write, write it all.
fn complete_prior_io(&mut self) -> Result<()> {
if self.conn.is_handshaking() {
self.conn.complete_io(self.sock)?;
}
if self.conn.wants_write() {
self.conn.complete_io(self.sock)?;
}
Ok(())
}
}
impl<'a, C, T, S> Read for Stream<'a, C, T>
where
C: 'a + DerefMut + Deref<Target = ConnectionCommon<S>>,
T: 'a + Read + Write,
S: SideData,
{
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
self.complete_prior_io()?;
// We call complete_io() in a loop since a single call may read only
// a partial packet from the underlying transport. A full packet is
// needed to get more plaintext, which we must do if EOF has not been
// hit. Otherwise, we will prematurely signal EOF by returning 0. We
// determine if EOF has actually been hit by checking if 0 bytes were
// read from the underlying transport.
while self.conn.wants_read() {
let at_eof = self.conn.complete_io(self.sock)?.0 == 0;
if at_eof {
if let Ok(io_state) = self.conn.process_new_packets() {
if at_eof && io_state.plaintext_bytes_to_read() == 0 {
return Ok(0);
}
}
break;
}
}
self.conn.reader().read(buf)
}
#[cfg(read_buf)]
fn read_buf(&mut self, cursor: std::io::BorrowedCursor<'_>) -> Result<()> {
self.complete_prior_io()?;
// We call complete_io() in a loop since a single call may read only
// a partial packet from the underlying transport. A full packet is
// needed to get more plaintext, which we must do if EOF has not been
// hit. Otherwise, we will prematurely signal EOF by returning without
// writing anything. We determine if EOF has actually been hit by
// checking if 0 bytes were read from the underlying transport.
while self.conn.wants_read() {
let at_eof = self.conn.complete_io(self.sock)?.0 == 0;
if at_eof {
if let Ok(io_state) = self.conn.process_new_packets() {
if at_eof && io_state.plaintext_bytes_to_read() == 0 {
return Ok(());
}
}
break;
}
}
self.conn.reader().read_buf(cursor)
}
}
impl<'a, C, T, S> Write for Stream<'a, C, T>
where
C: 'a + DerefMut + Deref<Target = ConnectionCommon<S>>,
T: 'a + Read + Write,
S: SideData,
{
fn write(&mut self, buf: &[u8]) -> Result<usize> {
self.complete_prior_io()?;
let len = self.conn.writer().write(buf)?;
// Try to write the underlying transport here, but don't let
// any errors mask the fact we've consumed `len` bytes.
// Callers will learn of permanent errors on the next call.
let _ = self.conn.complete_io(self.sock);
Ok(len)
}
fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> Result<usize> {
self.complete_prior_io()?;
let len = self
.conn
.writer()
.write_vectored(bufs)?;
// Try to write the underlying transport here, but don't let
// any errors mask the fact we've consumed `len` bytes.
// Callers will learn of permanent errors on the next call.
let _ = self.conn.complete_io(self.sock);
Ok(len)
}
fn flush(&mut self) -> Result<()> {
self.complete_prior_io()?;
self.conn.writer().flush()?;
if self.conn.wants_write() {
self.conn.complete_io(self.sock)?;
}
Ok(())
}
}
/// This type implements `io::Read` and `io::Write`, encapsulating
/// and owning a Connection `C` and an underlying blocking transport
/// `T`, such as a socket.
///
/// This allows you to use a rustls Connection like a normal stream.
#[derive(Debug)]
pub struct StreamOwned<C: Sized, T: Read + Write + Sized> {
/// Our connection
pub conn: C,
/// The underlying transport, like a socket
pub sock: T,
}
impl<C, T, S> StreamOwned<C, T>
where
C: DerefMut + Deref<Target = ConnectionCommon<S>>,
T: Read + Write,
S: SideData,
{
/// Make a new StreamOwned taking the Connection `conn` and socket-like
/// object `sock`. This does not fail and does no IO.
///
/// This is the same as `Stream::new` except `conn` and `sock` are
/// moved into the StreamOwned.
pub fn new(conn: C, sock: T) -> Self {
Self { conn, sock }
}
/// Get a reference to the underlying socket
pub fn get_ref(&self) -> &T {
&self.sock
}
/// Get a mutable reference to the underlying socket
pub fn get_mut(&mut self) -> &mut T {
&mut self.sock
}
}
impl<'a, C, T, S> StreamOwned<C, T>
where
C: DerefMut + Deref<Target = ConnectionCommon<S>>,
T: Read + Write,
S: SideData,
{
fn as_stream(&'a mut self) -> Stream<'a, C, T> {
Stream {
conn: &mut self.conn,
sock: &mut self.sock,
}
}
}
impl<C, T, S> Read for StreamOwned<C, T>
where
C: DerefMut + Deref<Target = ConnectionCommon<S>>,
T: Read + Write,
S: SideData,
{
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
self.as_stream().read(buf)
}
#[cfg(read_buf)]
fn read_buf(&mut self, cursor: std::io::BorrowedCursor<'_>) -> Result<()> {
self.as_stream().read_buf(cursor)
}
}
impl<C, T, S> Write for StreamOwned<C, T>
where
C: DerefMut + Deref<Target = ConnectionCommon<S>>,
T: Read + Write,
S: SideData,
{
fn write(&mut self, buf: &[u8]) -> Result<usize> {
self.as_stream().write(buf)
}
fn flush(&mut self) -> Result<()> {
self.as_stream().flush()
}
}
#[cfg(test)]
mod tests {
use super::{Stream, StreamOwned};
use crate::client::ClientConnection;
use crate::server::ServerConnection;
use std::net::TcpStream;
#[test]
fn stream_can_be_created_for_connection_and_tcpstream() {
type _Test<'a> = Stream<'a, ClientConnection, TcpStream>;
}
#[test]
fn streamowned_can_be_created_for_client_and_tcpstream() {
type _Test = StreamOwned<ClientConnection, TcpStream>;
}
#[test]
fn streamowned_can_be_created_for_server_and_tcpstream() {
type _Test = StreamOwned<ServerConnection, TcpStream>;
}
}

View file

@ -0,0 +1,340 @@
use std::fmt;
use crate::enums::{CipherSuite, ProtocolVersion, SignatureScheme};
use crate::msgs::enums::SignatureAlgorithm;
use crate::msgs::handshake::DecomposedSignatureScheme;
#[cfg(feature = "tls12")]
use crate::tls12::Tls12CipherSuite;
#[cfg(feature = "tls12")]
use crate::tls12::{
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
// TLS1.2 suites
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
};
use crate::tls13::Tls13CipherSuite;
use crate::tls13::{
TLS13_AES_128_GCM_SHA256, TLS13_AES_256_GCM_SHA384, TLS13_CHACHA20_POLY1305_SHA256,
};
#[cfg(feature = "tls12")]
use crate::versions::TLS12;
use crate::versions::{SupportedProtocolVersion, TLS13};
/// Bulk symmetric encryption scheme used by a cipher suite.
#[allow(non_camel_case_types)]
#[derive(Debug, Eq, PartialEq)]
pub enum BulkAlgorithm {
/// AES with 128-bit keys in Galois counter mode.
Aes128Gcm,
/// AES with 256-bit keys in Galois counter mode.
Aes256Gcm,
/// Chacha20 for confidentiality with poly1305 for authenticity.
Chacha20Poly1305,
}
/// Common state for cipher suites (both for TLS 1.2 and TLS 1.3)
#[derive(Debug)]
pub struct CipherSuiteCommon {
/// The TLS enumeration naming this cipher suite.
pub suite: CipherSuite,
/// How to do bulk encryption.
pub bulk: BulkAlgorithm,
pub(crate) aead_algorithm: &'static ring::aead::Algorithm,
}
/// A cipher suite supported by rustls.
///
/// All possible instances of this type are provided by the library in
/// the [`ALL_CIPHER_SUITES`] array.
#[derive(Clone, Copy, PartialEq)]
pub enum SupportedCipherSuite {
/// A TLS 1.2 cipher suite
#[cfg(feature = "tls12")]
Tls12(&'static Tls12CipherSuite),
/// A TLS 1.3 cipher suite
Tls13(&'static Tls13CipherSuite),
}
impl fmt::Debug for SupportedCipherSuite {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.suite().fmt(f)
}
}
impl SupportedCipherSuite {
/// Which hash function to use with this suite.
pub fn hash_algorithm(&self) -> &'static ring::digest::Algorithm {
match self {
#[cfg(feature = "tls12")]
Self::Tls12(inner) => inner.hash_algorithm(),
Self::Tls13(inner) => inner.hash_algorithm(),
}
}
/// The cipher suite's identifier
pub fn suite(&self) -> CipherSuite {
self.common().suite
}
pub(crate) fn common(&self) -> &CipherSuiteCommon {
match self {
#[cfg(feature = "tls12")]
Self::Tls12(inner) => &inner.common,
Self::Tls13(inner) => &inner.common,
}
}
pub(crate) fn tls13(&self) -> Option<&'static Tls13CipherSuite> {
match self {
#[cfg(feature = "tls12")]
Self::Tls12(_) => None,
Self::Tls13(inner) => Some(inner),
}
}
/// Return supported protocol version for the cipher suite.
pub fn version(&self) -> &'static SupportedProtocolVersion {
match self {
#[cfg(feature = "tls12")]
Self::Tls12(_) => &TLS12,
Self::Tls13(_) => &TLS13,
}
}
/// Return true if this suite is usable for a key only offering `sig_alg`
/// signatures. This resolves to true for all TLS1.3 suites.
pub fn usable_for_signature_algorithm(&self, _sig_alg: SignatureAlgorithm) -> bool {
match self {
Self::Tls13(_) => true, // no constraint expressed by ciphersuite (e.g., TLS1.3)
#[cfg(feature = "tls12")]
Self::Tls12(inner) => inner
.sign
.iter()
.any(|scheme| scheme.sign() == _sig_alg),
}
}
}
/// A list of all the cipher suites supported by rustls.
pub static ALL_CIPHER_SUITES: &[SupportedCipherSuite] = &[
// TLS1.3 suites
TLS13_AES_256_GCM_SHA384,
TLS13_AES_128_GCM_SHA256,
TLS13_CHACHA20_POLY1305_SHA256,
// TLS1.2 suites
#[cfg(feature = "tls12")]
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
#[cfg(feature = "tls12")]
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
#[cfg(feature = "tls12")]
TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
#[cfg(feature = "tls12")]
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
#[cfg(feature = "tls12")]
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
#[cfg(feature = "tls12")]
TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
];
/// The cipher suite configuration that an application should use by default.
///
/// This will be [`ALL_CIPHER_SUITES`] sans any supported cipher suites that
/// shouldn't be enabled by most applications.
pub static DEFAULT_CIPHER_SUITES: &[SupportedCipherSuite] = ALL_CIPHER_SUITES;
// These both O(N^2)!
pub(crate) fn choose_ciphersuite_preferring_client(
client_suites: &[CipherSuite],
server_suites: &[SupportedCipherSuite],
) -> Option<SupportedCipherSuite> {
for client_suite in client_suites {
if let Some(selected) = server_suites
.iter()
.find(|x| *client_suite == x.suite())
{
return Some(*selected);
}
}
None
}
pub(crate) fn choose_ciphersuite_preferring_server(
client_suites: &[CipherSuite],
server_suites: &[SupportedCipherSuite],
) -> Option<SupportedCipherSuite> {
if let Some(selected) = server_suites
.iter()
.find(|x| client_suites.contains(&x.suite()))
{
return Some(*selected);
}
None
}
/// Return a list of the ciphersuites in `all` with the suites
/// incompatible with `SignatureAlgorithm` `sigalg` removed.
pub(crate) fn reduce_given_sigalg(
all: &[SupportedCipherSuite],
sigalg: SignatureAlgorithm,
) -> Vec<SupportedCipherSuite> {
all.iter()
.filter(|&&suite| suite.usable_for_signature_algorithm(sigalg))
.copied()
.collect()
}
/// Return a list of the ciphersuites in `all` with the suites
/// incompatible with the chosen `version` removed.
pub(crate) fn reduce_given_version(
all: &[SupportedCipherSuite],
version: ProtocolVersion,
) -> Vec<SupportedCipherSuite> {
all.iter()
.filter(|&&suite| suite.version().version == version)
.copied()
.collect()
}
/// Return true if `sigscheme` is usable by any of the given suites.
pub(crate) fn compatible_sigscheme_for_suites(
sigscheme: SignatureScheme,
common_suites: &[SupportedCipherSuite],
) -> bool {
let sigalg = sigscheme.sign();
common_suites
.iter()
.any(|&suite| suite.usable_for_signature_algorithm(sigalg))
}
/// Secrets for transmitting/receiving data over a TLS session.
///
/// After performing a handshake with rustls, these secrets can be extracted
/// to configure kTLS for a socket, and have the kernel take over encryption
/// and/or decryption.
#[cfg(feature = "secret_extraction")]
pub struct ExtractedSecrets {
/// sequence number and secrets for the "tx" (transmit) direction
pub tx: (u64, ConnectionTrafficSecrets),
/// sequence number and secrets for the "rx" (receive) direction
pub rx: (u64, ConnectionTrafficSecrets),
}
/// [ExtractedSecrets] minus the sequence numbers
#[cfg(feature = "secret_extraction")]
pub(crate) struct PartiallyExtractedSecrets {
/// secrets for the "tx" (transmit) direction
pub(crate) tx: ConnectionTrafficSecrets,
/// secrets for the "rx" (receive) direction
pub(crate) rx: ConnectionTrafficSecrets,
}
/// Secrets used to encrypt/decrypt data in a TLS session.
///
/// These can be used to configure kTLS for a socket in one direction.
/// The only other piece of information needed is the sequence number,
/// which is in [ExtractedSecrets].
#[cfg(feature = "secret_extraction")]
#[non_exhaustive]
pub enum ConnectionTrafficSecrets {
/// Secrets for the AES_128_GCM AEAD algorithm
Aes128Gcm {
/// key (16 bytes)
key: [u8; 16],
/// salt (4 bytes)
salt: [u8; 4],
/// initialization vector (8 bytes, chopped from key block)
iv: [u8; 8],
},
/// Secrets for the AES_256_GCM AEAD algorithm
Aes256Gcm {
/// key (32 bytes)
key: [u8; 32],
/// salt (4 bytes)
salt: [u8; 4],
/// initialization vector (8 bytes, chopped from key block)
iv: [u8; 8],
},
/// Secrets for the CHACHA20_POLY1305 AEAD algorithm
Chacha20Poly1305 {
/// key (32 bytes)
key: [u8; 32],
/// initialization vector (12 bytes)
iv: [u8; 12],
},
}
#[cfg(test)]
mod test {
use super::*;
use crate::enums::CipherSuite;
#[test]
fn test_client_pref() {
let client = vec![
CipherSuite::TLS13_AES_128_GCM_SHA256,
CipherSuite::TLS13_AES_256_GCM_SHA384,
];
let server = vec![TLS13_AES_256_GCM_SHA384, TLS13_AES_128_GCM_SHA256];
let chosen = choose_ciphersuite_preferring_client(&client, &server);
assert!(chosen.is_some());
assert_eq!(chosen.unwrap(), TLS13_AES_128_GCM_SHA256);
}
#[test]
fn test_server_pref() {
let client = vec![
CipherSuite::TLS13_AES_128_GCM_SHA256,
CipherSuite::TLS13_AES_256_GCM_SHA384,
];
let server = vec![TLS13_AES_256_GCM_SHA384, TLS13_AES_128_GCM_SHA256];
let chosen = choose_ciphersuite_preferring_server(&client, &server);
assert!(chosen.is_some());
assert_eq!(chosen.unwrap(), TLS13_AES_256_GCM_SHA384);
}
#[test]
fn test_pref_fails() {
assert!(choose_ciphersuite_preferring_client(
&[CipherSuite::TLS_NULL_WITH_NULL_NULL],
ALL_CIPHER_SUITES
)
.is_none());
assert!(choose_ciphersuite_preferring_server(
&[CipherSuite::TLS_NULL_WITH_NULL_NULL],
ALL_CIPHER_SUITES
)
.is_none());
}
#[test]
fn test_scs_is_debug() {
println!("{:?}", ALL_CIPHER_SUITES);
}
#[test]
fn test_can_resume_to() {
assert!(TLS13_AES_128_GCM_SHA256
.tls13()
.unwrap()
.can_resume_from(crate::tls13::TLS13_CHACHA20_POLY1305_SHA256_INTERNAL)
.is_some());
assert!(TLS13_AES_256_GCM_SHA384
.tls13()
.unwrap()
.can_resume_from(crate::tls13::TLS13_CHACHA20_POLY1305_SHA256_INTERNAL)
.is_none());
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show more