diff --git a/Apple/UI/Networks/Network.swift b/Apple/UI/Networks/Network.swift index 0f46406..57ba7a5 100644 --- a/Apple/UI/Networks/Network.swift +++ b/Apple/UI/Networks/Network.swift @@ -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 = [ + "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() { diff --git a/Cargo.lock b/Cargo.lock index 53e4faf..fc9a5ca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,15 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "addr2line" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" -dependencies = [ - "gimli", -] - [[package]] name = "adler2" version = "2.0.1" @@ -23,10 +14,30 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" dependencies = [ - "crypto-common", + "crypto-common 0.1.6", "generic-array", ] +[[package]] +name = "aead" +version = "0.6.0-rc.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b657e772794c6b04730ea897b66a058ccd866c16d1967da05eeeecec39043fe" +dependencies = [ + "crypto-common 0.2.2", + "inout 0.2.2", +] + +[[package]] +name = "aegis" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85687c501b265f8538af0b3245221b56ff5c40acd341ba06f050466516fcf2cd" +dependencies = [ + "cc", + "softaes", +] + [[package]] name = "aes" version = "0.8.4" @@ -34,11 +45,75 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", - "cipher", - "cpufeatures", + "cipher 0.4.4", + "cpufeatures 0.2.17", "zeroize", ] +[[package]] +name = "aes" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1fc76eaeac4c9164506c466d4ffdd8ec9d0c5bf57ee97177c4d8eceb3a0e138" +dependencies = [ + "cipher 0.5.2", + "cpubits", + "cpufeatures 0.3.0", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead 0.5.2", + "aes 0.8.4", + "cipher 0.4.4", + "ctr", + "ghash", + "subtle", +] + +[[package]] +name = "aes-gcm-siv" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae0784134ba9375416d469ec31e7c5f9fa94405049cf08c5ce5b4698be673e0d" +dependencies = [ + "aead 0.5.2", + "aes 0.8.4", + "cipher 0.4.4", + "ctr", + "polyval", + "subtle", + "zeroize", +] + +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom 0.2.16", + "once_cell", + "version_check", +] + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "getrandom 0.3.3", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.3" @@ -182,10 +257,16 @@ checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" dependencies = [ "base64ct", "blake2", - "cpufeatures", + "cpufeatures 0.2.17", "password-hash 0.5.0", ] +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + [[package]] name = "arrayvec" version = "0.7.6" @@ -410,6 +491,16 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "asynk-strim" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52697735bdaac441a29391a9e97102c74c6ef0f9b60a40cf109b1b404e29d2f6" +dependencies = [ + "futures-core", + "pin-project-lite", +] + [[package]] name = "atomic" version = "0.5.3" @@ -474,7 +565,7 @@ dependencies = [ "http-body 0.4.6", "hyper 0.14.32", "itoa", - "matchit", + "matchit 0.7.3", "memchr", "mime", "percent-encoding", @@ -503,7 +594,7 @@ dependencies = [ "hyper 1.7.0", "hyper-util", "itoa", - "matchit", + "matchit 0.7.3", "memchr", "mime", "percent-encoding", @@ -559,21 +650,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "backtrace" -version = "0.3.75" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-targets 0.52.6", -] - [[package]] name = "base16ct" version = "0.2.0" @@ -625,7 +701,7 @@ dependencies = [ "quote", "regex", "rustc-hash 1.1.0", - "shlex", + "shlex 1.3.0", "syn 1.0.109", "which", ] @@ -648,11 +724,29 @@ dependencies = [ "quote", "regex", "rustc-hash 1.1.0", - "shlex", + "shlex 1.3.0", "syn 2.0.106", "which", ] +[[package]] +name = "bindgen" +version = "0.72.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" +dependencies = [ + "bitflags 2.11.1", + "cexpr", + "clang-sys", + "itertools 0.13.0", + "proc-macro2", + "quote", + "regex", + "rustc-hash 2.1.1", + "shlex 1.3.0", + "syn 2.0.106", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -661,9 +755,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.4" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" [[package]] name = "bitvec" @@ -686,6 +780,20 @@ dependencies = [ "digest", ] +[[package]] +name = "blake3" +version = "1.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aa83c34e62843d924f905e0f5c866eb1dd6545fc4d719e803d9ba6030371fce" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq 0.4.2", + "cpufeatures 0.3.0", +] + [[package]] name = "blanket" version = "0.3.0" @@ -706,6 +814,35 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-buffer" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "blowfish" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62ce3946557b35e71d1bbe07ec385073ce9eda05043f95de134eb578fcf1a298" +dependencies = [ + "byteorder", + "cipher 0.5.2", +] + +[[package]] +name = "borsh" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfd1e3f8955a5d7de9fab72fc8373fade9fb8a703968cb200ae3dc6cf08e185a" +dependencies = [ + "bytes", + "cfg_aliases", +] + [[package]] name = "bstr" version = "1.12.1" @@ -727,7 +864,10 @@ checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" name = "burrow" version = "0.1.0" dependencies = [ - "aead", + "aead 0.5.2", + "aegis", + "aes 0.8.4", + "aes-gcm", "anyhow", "argon2", "arti-client", @@ -736,22 +876,29 @@ dependencies = [ "axum 0.7.9", "base64 0.21.7", "blake2", + "blake3", "bytes", "caps", + "ccm", + "chacha20", "chacha20poly1305", "clap", "console", "console-subscriber", + "deoxys", "dotenv", "fehler", "futures", + "h2 0.4.12", "hickory-proto", "hmac", + "http 1.3.1", "hyper-util", "insta", "ip_network", "ip_network_table", "ipnetwork", + "lea", "libc", "libsystemd", "log", @@ -760,28 +907,41 @@ dependencies = [ "netstack-smoltcp", "nix 0.27.1", "once_cell", - "parking_lot", + "parking_lot 0.12.4", + "poly1305", "prost 0.13.5", "prost-types 0.13.5", + "rabbit", + "rama-net", + "rama-tls-boring", + "rama-ua", "rand 0.8.5", "rand_core 0.6.4", "reqwest 0.12.23", - "ring", + "ring 0.17.14", "rusqlite", "rust-ini", + "rust_tokio_kcp", "rustls", + "rustls-fork-shadow-tls", + "rustls-pemfile 2.2.0", "schemars 0.8.22", "serde", "serde_json", "serde_yaml", "sha2", + "shadowsocks", + "smux_rust", "subtle", "tempfile", "tokio", "tokio-native-tls", "tokio-rustls", + "tokio-rustls-fork-shadow-tls", + "tokio-snappy", "tokio-stream", "tokio-util", + "tokio_smux", "toml 0.8.23", "tonic 0.12.3", "tonic-build", @@ -793,8 +953,11 @@ dependencies = [ "tracing-oslog", "tracing-subscriber", "tun", + "webpki-roots 0.22.6", "webpki-roots 0.26.11", "x25519-dalek", + "yamux", + "zears", ] [[package]] @@ -803,6 +966,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06" +[[package]] +name = "byte_string" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11aade7a05aa8c3a351cedc44c3fc45806430543382fcc4743a9b757a2a0b4ed" + [[package]] name = "bytemuck" version = "1.25.0" @@ -841,6 +1010,16 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "camellia" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3264e2574e9ef2b53ce6f536dea83a69ac0bc600b762d1523ff83fe07230ce30" +dependencies = [ + "byteorder", + "cipher 0.4.4", +] + [[package]] name = "caps" version = "0.5.5" @@ -864,15 +1043,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] -name = "cc" -version = "1.2.61" +name = "cast5" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d" +checksum = "1ed80521f830bbce4d3a4857d6f2b91a8339bf0b65711fe189ab6f8d7a2f629e" +dependencies = [ + "cipher 0.5.2", +] + +[[package]] +name = "cc" +version = "1.2.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f" dependencies = [ "find-msvc-tools", "jobserver", "libc", - "shlex", + "shlex 2.0.1", +] + +[[package]] +name = "ccm" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae3c82e4355234767756212c570e29833699ab63e6ffd161887314cc5b43847" +dependencies = [ + "aead 0.5.2", + "cipher 0.4.4", + "ctr", + "subtle", ] [[package]] @@ -903,8 +1103,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" dependencies = [ "cfg-if", - "cipher", - "cpufeatures", + "cipher 0.4.4", + "cpufeatures 0.2.17", ] [[package]] @@ -913,9 +1113,9 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" dependencies = [ - "aead", + "aead 0.5.2", "chacha20", - "cipher", + "cipher 0.4.4", "poly1305", "zeroize", ] @@ -959,17 +1159,37 @@ dependencies = [ "half", ] +[[package]] +name = "cipher" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" +dependencies = [ + "generic-array", +] + [[package]] name = "cipher" version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ - "crypto-common", - "inout", + "crypto-common 0.1.6", + "inout 0.1.4", "zeroize", ] +[[package]] +name = "cipher" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c" +dependencies = [ + "block-buffer 0.12.0", + "crypto-common 0.2.2", + "inout 0.2.2", +] + [[package]] name = "clang-sys" version = "1.8.1" @@ -1151,12 +1371,39 @@ dependencies = [ "tiny-keccak", ] +[[package]] +name = "const_format" +version = "0.2.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4481a617ad9a412be3b97c5d403fef8ed023103368908b9c50af598ff467cc1e" +dependencies = [ + "const_format_proc_macros", + "konst", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + [[package]] name = "constant_time_eq" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +[[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + [[package]] name = "convert_case" version = "0.7.1" @@ -1191,6 +1438,12 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "cpubits" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -1200,6 +1453,15 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + [[package]] name = "crc32fast" version = "1.5.0" @@ -1332,13 +1594,43 @@ dependencies = [ "typenum", ] +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "csv" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52cd9d68cf7efc6ddfaaee42e7288d3a99d613d4b50f76ce9827ae0c6e14f938" +dependencies = [ + "csv-core", + "itoa", + "ryu", + "serde_core", +] + +[[package]] +name = "csv-core" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704a3c26996a80471189265814dbc2c257598b96b8a7feae2d31ace646bb9782" +dependencies = [ + "memchr", +] + [[package]] name = "ctr" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" dependencies = [ - "cipher", + "cipher 0.4.4", ] [[package]] @@ -1348,7 +1640,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "curve25519-dalek-derive", "digest", "fiat-crypto", @@ -1471,6 +1763,19 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "dashmap" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +dependencies = [ + "cfg-if", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core 0.9.11", +] + [[package]] name = "data-encoding" version = "2.9.0" @@ -1518,6 +1823,17 @@ dependencies = [ "thiserror 2.0.16", ] +[[package]] +name = "deoxys" +version = "0.2.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cb5efc49a3af2efb079e9c9d1cfad0201ea65c2a6f1a4eb1ede501593d2ddb8" +dependencies = [ + "aead 0.6.0-rc.10", + "aes 0.9.1", + "subtle", +] + [[package]] name = "der" version = "0.7.10" @@ -1634,15 +1950,24 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "des" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "916a94e407b54f9034d71dd748234cd1e516ced6284009906ae246f177eafe5a" +dependencies = [ + "cipher 0.5.2", +] + [[package]] name = "digest" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", + "block-buffer 0.10.4", "const-oid", - "crypto-common", + "crypto-common 0.1.6", "subtle", ] @@ -1720,6 +2045,26 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" +[[package]] +name = "dynosaur" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a12303417f378f29ba12cb12fc78a9df0d8e16ccb1ad94abf04d48d96bdda532" +dependencies = [ + "dynosaur_derive", +] + +[[package]] +name = "dynosaur_derive" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b0713d5c1d52e774c5cd7bb8b043d7c0fc4f921abfb678556140bfbe6ab2364" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "ecdsa" version = "0.16.9" @@ -1812,6 +2157,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "endian-type" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "869b0adbda23651a9c5c0c3d270aac9fcb52e8622a8f2b17e57802d7791962f2" + [[package]] name = "enum-as-inner" version = "0.6.1" @@ -1933,6 +2284,9 @@ name = "fastrand" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +dependencies = [ + "getrandom 0.2.16", +] [[package]] name = "fehler" @@ -2022,6 +2376,18 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "749cff877dc1af878a0b31a41dd221a753634401ea0ef2f87b62d3171522485a" +[[package]] +name = "flume" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e139bc46ca777eb5efaf62df0ab8cc5fd400866427e56c68b22e414e53bd3be" +dependencies = [ + "fastrand", + "futures-core", + "futures-sink", + "spin 0.9.8", +] + [[package]] name = "fnv" version = "1.0.7" @@ -2046,7 +2412,28 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" dependencies = [ - "foreign-types-shared", + "foreign-types-shared 0.1.1", +] + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared 0.3.1", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", ] [[package]] @@ -2055,6 +2442,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + [[package]] name = "form_urlencoded" version = "1.2.2" @@ -2190,6 +2583,21 @@ dependencies = [ "slab", ] +[[package]] +name = "generator" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3b854b0e584ead1a33f18b2fcad7cf7be18b3875c78816b753639aa501513ae" +dependencies = [ + "cc", + "cfg-if", + "libc", + "log", + "rustversion", + "windows-link 0.2.1", + "windows-result 0.4.1", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -2254,10 +2662,14 @@ dependencies = [ ] [[package]] -name = "gimli" -version = "0.31.1" +name = "ghash" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", +] [[package]] name = "glob" @@ -2345,6 +2757,9 @@ name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash 0.7.8", +] [[package]] name = "hashbrown" @@ -2431,7 +2846,7 @@ dependencies = [ "ipnet", "once_cell", "rand 0.9.2", - "ring", + "ring 0.17.14", "thiserror 2.0.16", "tinyvec", "tokio", @@ -2528,6 +2943,12 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "http-range-header" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c" + [[package]] name = "httparse" version = "1.10.1" @@ -2556,6 +2977,15 @@ dependencies = [ "serde", ] +[[package]] +name = "hybrid-array" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9155a582abd142abc056962c29e3ce5ff2ad5469f4246b537ed42c5deba857da" +dependencies = [ + "typenum", +] + [[package]] name = "hyper" version = "0.14.32" @@ -2854,7 +3284,7 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd5b3eaf1a28b758ac0faa5a4254e8ab2705605496f1b1f3fbbc3988ad73d199" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.1", "inotify-sys", "libc", ] @@ -2877,6 +3307,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "inout" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7" +dependencies = [ + "hybrid-array", +] + [[package]] name = "insta" version = "1.43.2" @@ -2889,6 +3328,15 @@ dependencies = [ "similar", ] +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] + [[package]] name = "inventory" version = "0.3.24" @@ -2898,17 +3346,6 @@ dependencies = [ "rustversion", ] -[[package]] -name = "io-uring" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" -dependencies = [ - "bitflags 2.9.4", - "cfg-if", - "libc", -] - [[package]] name = "ip_network" version = "0.4.1" @@ -3017,15 +3454,41 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "kcp" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e387a924f42063d380be14857714a2f0c177e44dbeab8895244e5370d8a8e68" +dependencies = [ + "bytes", + "log", + "thiserror 1.0.69", +] + [[package]] name = "keccak" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" dependencies = [ - "cpufeatures", + "cpufeatures 0.2.17", ] +[[package]] +name = "konst" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "128133ed7824fcd73d6e7b17957c5eb7bacb885649bd8c69708b2331a10bcefb" +dependencies = [ + "konst_macro_rules", +] + +[[package]] +name = "konst_macro_rules" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4933f3f57a8e9d9da04db23fb153356ecaf00cbd14aee46279c33dc80925c37" + [[package]] name = "kqueue" version = "1.1.1" @@ -3052,7 +3515,7 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" dependencies = [ - "spin", + "spin 0.9.8", ] [[package]] @@ -3061,6 +3524,16 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" +[[package]] +name = "lea" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00f03a660d3662be37724af25ce5152a4df89c4ffc3649823e6d1b415c19608a" +dependencies = [ + "cfg-if", + "cipher 0.3.0", +] + [[package]] name = "leb128fmt" version = "0.1.0" @@ -3125,7 +3598,7 @@ version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ddbf48fd451246b1f8c2610bd3b4ac0cc6e149d89832867093ab69a17194f08" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.1", "libc", "plain", "redox_syscall 0.7.3", @@ -3194,12 +3667,43 @@ version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" +[[package]] +name = "loom" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" +dependencies = [ + "cfg-if", + "generator", + "pin-utils", + "scoped-tls", + "serde", + "serde_json", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "lru" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999beba7b6e8345721bd280141ed958096a2e4abdf74f67ff4ce49b4b54e47a" +dependencies = [ + "hashbrown 0.12.3", +] + [[package]] name = "lru-slab" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" +[[package]] +name = "lru_time_cache" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9106e1d747ffd48e6be5bb2d97fa706ed25b144fbee4d5c02eae110cd8d6badd" + [[package]] name = "managed" version = "0.8.0" @@ -3221,6 +3725,12 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" +[[package]] +name = "matchit" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8863b587001c1b9a8a4e36008cebc6b3612cb1226fe2de94858e06092687b608" + [[package]] name = "md-5" version = "0.10.6" @@ -3231,6 +3741,12 @@ dependencies = [ "digest", ] +[[package]] +name = "md5" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae960838283323069879657ca3de837e9f7bbb4c7bf6ea7f1b290d5e9476d2e0" + [[package]] name = "memchr" version = "2.7.5" @@ -3305,6 +3821,16 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -3332,6 +3858,23 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "moka" +version = "0.12.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "957228ad12042ee839f93c8f257b62b4c0ab5eaae1d4fa60de53b27c9d7c5046" +dependencies = [ + "crossbeam-channel", + "crossbeam-epoch", + "crossbeam-utils", + "equivalent", + "parking_lot 0.12.4", + "portable-atomic", + "smallvec", + "tagptr", + "uuid", +] + [[package]] name = "multimap" version = "0.10.1" @@ -3365,12 +3908,21 @@ dependencies = [ "futures", "rand 0.8.5", "smoltcp", - "spin", + "spin 0.9.8", "tokio", "tokio-util", "tracing", ] +[[package]] +name = "nibble_vec" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" +dependencies = [ + "smallvec", +] + [[package]] name = "nix" version = "0.26.4" @@ -3390,7 +3942,7 @@ version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.1", "cfg-if", "libc", "memoffset 0.9.1", @@ -3402,13 +3954,19 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.1", "cfg-if", "cfg_aliases", "libc", "memoffset 0.9.1", ] +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + [[package]] name = "nom" version = "7.1.3" @@ -3440,7 +3998,7 @@ version = "8.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.1", "inotify", "kqueue", "libc", @@ -3457,7 +4015,7 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42b8cfee0e339a0337359f3c88165702ac6e600dc01c0cc9579a92d62b08477a" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.1", ] [[package]] @@ -3568,7 +4126,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.1", ] [[package]] @@ -3581,15 +4139,6 @@ dependencies = [ "objc2-core-foundation", ] -[[package]] -name = "object" -version = "0.36.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" -dependencies = [ - "memchr", -] - [[package]] name = "once_cell" version = "1.21.3" @@ -3633,9 +4182,9 @@ version = "0.10.73" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.1", "cfg-if", - "foreign-types", + "foreign-types 0.3.2", "libc", "once_cell", "openssl-macros", @@ -3759,6 +4308,17 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core 0.8.6", +] + [[package]] name = "parking_lot" version = "0.12.4" @@ -3766,7 +4326,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" dependencies = [ "lock_api", - "parking_lot_core", + "parking_lot_core 0.9.11", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" +dependencies = [ + "cfg-if", + "instant", + "libc", + "redox_syscall 0.2.16", + "smallvec", + "winapi", ] [[package]] @@ -3822,6 +4396,16 @@ dependencies = [ "sha2", ] +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", + "hmac", +] + [[package]] name = "peeking_take_while" version = "0.1.2" @@ -3995,7 +4579,19 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" dependencies = [ - "cpufeatures", + "cpufeatures 0.2.17", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", "opaque-debug", "universal-hash", ] @@ -4015,7 +4611,7 @@ dependencies = [ "atomic 0.5.3", "crossbeam-queue", "futures", - "parking_lot", + "parking_lot 0.12.4", "pin-project", "static_assertions", "thiserror 1.0.69", @@ -4199,6 +4795,21 @@ dependencies = [ "prost 0.13.5", ] +[[package]] +name = "psl" +version = "2.1.212" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9f6415ab1b08394487005d41ae7ee69e69903efa1ace538f3b274db605bf8ec" +dependencies = [ + "psl-types", +] + +[[package]] +name = "psl-types" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac" + [[package]] name = "pwd-grp" version = "1.0.2" @@ -4241,7 +4852,7 @@ dependencies = [ "getrandom 0.3.3", "lru-slab", "rand 0.9.2", - "ring", + "ring 0.17.14", "rustc-hash 2.1.1", "rustls", "rustls-pki-types", @@ -4287,12 +4898,280 @@ version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" +[[package]] +name = "rabbit" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7204c97584ac097d9749f4a52f2f96910d988f12303fe3e3b021628f88923f1" +dependencies = [ + "cipher 0.5.2", +] + [[package]] name = "radium" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" +[[package]] +name = "radix_trie" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b4431027dcd37fc2a73ef740b5f233aa805897935b8bce0195e41bbf9a3289a" +dependencies = [ + "endian-type", + "nibble_vec", +] + +[[package]] +name = "rama-boring" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a8c66935959385543d370f6f1485461d520028d4ac9ae852131ec1c9226869b" +dependencies = [ + "bitflags 2.11.1", + "foreign-types 0.5.0", + "libc", + "openssl-macros", + "rama-boring-sys", +] + +[[package]] +name = "rama-boring-sys" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a50c9107d147c768182b5d6494670c0a54eadc99ee3eb24784a5b5504480eed8" +dependencies = [ + "bindgen 0.72.1", + "cmake", + "fs_extra", + "fslock", +] + +[[package]] +name = "rama-boring-tokio" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec7bccdd6efce0302b0fdd0e8b8ff8fba6202841bdfeeac357be115b9f42dd6b" +dependencies = [ + "rama-boring", + "rama-boring-sys", + "tokio", +] + +[[package]] +name = "rama-core" +version = "0.3.0-alpha.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b93751ab27c9d151e84c1100057eab3f2a6a1378bc31b62abd416ecb1847658" +dependencies = [ + "ahash 0.8.12", + "asynk-strim", + "bytes", + "futures", + "parking_lot 0.12.4", + "pin-project-lite", + "rama-error", + "rama-macros", + "rama-utils", + "serde", + "serde_json", + "tokio", + "tokio-graceful", + "tokio-util", + "tracing", +] + +[[package]] +name = "rama-error" +version = "0.3.0-alpha.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c452aba1beb7e29b873ff32f304536164cffcc596e786921aea64e858ff8f40" + +[[package]] +name = "rama-http" +version = "0.3.0-alpha.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "453d60af031e23af2d48995e41b17023f6150044738680508b63671f8d7417dd" +dependencies = [ + "ahash 0.8.12", + "base64 0.22.1", + "bitflags 2.11.1", + "chrono", + "const_format", + "csv", + "http 1.3.1", + "http-range-header", + "httpdate", + "iri-string", + "matchit 0.9.2", + "parking_lot 0.12.4", + "percent-encoding", + "pin-project-lite", + "radix_trie", + "rama-core", + "rama-error", + "rama-http-headers", + "rama-http-types", + "rama-net", + "rama-utils", + "rand 0.9.2", + "serde", + "serde_html_form", + "serde_json", + "tokio", + "uuid", +] + +[[package]] +name = "rama-http-headers" +version = "0.3.0-alpha.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d74fe0cd9bd4440827dc6dc0f504cf66065396532e798891dee2c1b740b2285" +dependencies = [ + "ahash 0.8.12", + "base64 0.22.1", + "chrono", + "const_format", + "httpdate", + "rama-core", + "rama-error", + "rama-http-types", + "rama-macros", + "rama-net", + "rama-utils", + "rand 0.9.2", + "serde", + "sha1", +] + +[[package]] +name = "rama-http-types" +version = "0.3.0-alpha.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6dae655a72da5f2b97cfacb67960d8b28c5025e62707b4c8c5f0c5c9843a444" +dependencies = [ + "ahash 0.8.12", + "bytes", + "const_format", + "fnv", + "http 1.3.1", + "http-body 1.0.1", + "http-body-util", + "itoa", + "memchr", + "mime", + "mime_guess", + "nom 8.0.0", + "pin-project-lite", + "rama-core", + "rama-error", + "rama-macros", + "rama-utils", + "rand 0.9.2", + "serde", + "serde_json", + "sync_wrapper 1.0.2", + "tokio", +] + +[[package]] +name = "rama-macros" +version = "0.3.0-alpha.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea18a110bcf21e35c5f194168e6914ccea45ffdd0fea51bc4b169fbeafef6428" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "rama-net" +version = "0.3.0-alpha.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b28ee9e1e5d39264414b71f5c33e7fbb66b382c3fac456fe0daad39cf5509933" +dependencies = [ + "ahash 0.8.12", + "const_format", + "flume", + "hex", + "ipnet", + "itertools 0.14.0", + "md5", + "nom 8.0.0", + "parking_lot 0.12.4", + "pin-project-lite", + "psl", + "radix_trie", + "rama-core", + "rama-http-types", + "rama-macros", + "rama-utils", + "serde", + "sha2", + "socket2 0.6.3", + "tokio", +] + +[[package]] +name = "rama-tls-boring" +version = "0.3.0-alpha.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "def3d5d06d3ca3a2d2e4376cf93de0555cd9c7960f085bf77be9562f5c9ace8f" +dependencies = [ + "ahash 0.8.12", + "flume", + "itertools 0.14.0", + "moka", + "parking_lot 0.12.4", + "pin-project-lite", + "rama-boring", + "rama-boring-tokio", + "rama-core", + "rama-net", + "rama-utils", + "schannel", + "tokio", +] + +[[package]] +name = "rama-ua" +version = "0.3.0-alpha.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7abde8e7b428c80c5948885c1ee0492852a21d91844c8414a0de4a8a1d62262" +dependencies = [ + "ahash 0.8.12", + "itertools 0.14.0", + "rama-core", + "rama-http", + "rama-net", + "rama-utils", + "rand 0.9.2", + "serde", + "serde_html_form", + "serde_json", +] + +[[package]] +name = "rama-utils" +version = "0.3.0-alpha.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf28b18ba4a57f8334d7992d3f8020194ea359b246ae6f8f98b8df524c7a14ef" +dependencies = [ + "const_format", + "parking_lot 0.12.4", + "pin-project-lite", + "rama-macros", + "regex", + "serde", + "smallvec", + "smol_str", + "tokio", + "wildcard", +] + [[package]] name = "rand" version = "0.8.5" @@ -4392,13 +5271,22 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "redox_syscall" version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.1", ] [[package]] @@ -4407,7 +5295,7 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce70a74e890531977d37e532c34d45e9055d2409ed08ddba14529471ed0be16" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.1", ] [[package]] @@ -4421,6 +5309,19 @@ dependencies = [ "thiserror 2.0.16", ] +[[package]] +name = "reed-solomon-erasure" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7263373d500d4d4f505d43a2a662d475a894aa94503a1ee28e9188b5f3960d4f" +dependencies = [ + "libm", + "lru", + "parking_lot 0.11.2", + "smallvec", + "spin 0.9.8", +] + [[package]] name = "ref-cast" version = "1.0.25" @@ -4443,9 +5344,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.2" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", @@ -4455,9 +5356,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.10" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", @@ -4494,7 +5395,7 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls-pemfile", + "rustls-pemfile 1.0.4", "serde", "serde_json", "serde_urlencoded", @@ -4567,6 +5468,21 @@ dependencies = [ "subtle", ] +[[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 0.5.2", + "untrusted 0.7.1", + "web-sys", + "winapi", +] + [[package]] name = "ring" version = "0.17.14" @@ -4577,10 +5493,23 @@ dependencies = [ "cfg-if", "getrandom 0.2.16", "libc", - "untrusted", + "untrusted 0.9.0", "windows-sys 0.52.0", ] +[[package]] +name = "ring-compat" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccce7bae150b815f0811db41b8312fcb74bffa4cab9cee5429ee00f356dd5bd4" +dependencies = [ + "aead 0.5.2", + "ed25519", + "generic-array", + "pkcs8", + "ring 0.17.14", +] + [[package]] name = "rsa" version = "0.9.10" @@ -4618,7 +5547,7 @@ version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1c93dd1c9683b438c392c492109cb702b8090b2bfc8fed6f6e4eb4523f17af3" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.1", "fallible-iterator", "fallible-streaming-iterator", "hashlink", @@ -4639,10 +5568,35 @@ dependencies = [ ] [[package]] -name = "rustc-demangle" -version = "0.1.26" +name = "rust_tokio_kcp" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" +checksum = "74fa76f74fbbd4a3c15e3d688e8125e64583c4cd73f75f96ab2ba5b683708b1e" +dependencies = [ + "aes 0.9.1", + "aes-gcm", + "blowfish", + "byte_string", + "byteorder", + "bytes", + "cast5", + "cipher 0.5.2", + "crc32fast", + "des", + "futures-util", + "hybrid-array", + "kcp", + "log", + "pbkdf2 0.12.2", + "rand 0.8.5", + "reed-solomon-erasure", + "salsa20", + "sha1", + "sm4 0.6.0", + "spin 0.9.8", + "tokio", + "twofish", +] [[package]] name = "rustc-hash" @@ -4680,7 +5634,7 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.1", "errno", "libc", "linux-raw-sys 0.4.15", @@ -4693,7 +5647,7 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.1", "errno", "libc", "linux-raw-sys 0.11.0", @@ -4709,13 +5663,23 @@ dependencies = [ "aws-lc-rs", "log", "once_cell", - "ring", + "ring 0.17.14", "rustls-pki-types", "rustls-webpki", "subtle", "zeroize", ] +[[package]] +name = "rustls-fork-shadow-tls" +version = "0.20.8" +dependencies = [ + "log", + "ring 0.16.20", + "sct", + "webpki", +] + [[package]] name = "rustls-pemfile" version = "1.0.4" @@ -4725,6 +5689,15 @@ dependencies = [ "base64 0.21.7", ] +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "rustls-pki-types" version = "1.12.0" @@ -4742,9 +5715,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" dependencies = [ "aws-lc-rs", - "ring", + "ring 0.17.14", "rustls-pki-types", - "untrusted", + "untrusted 0.9.0", ] [[package]] @@ -4772,6 +5745,16 @@ dependencies = [ "thiserror 2.0.16", ] +[[package]] +name = "salsa20" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f874456e72520ff1375a06c588eaf074b0f01f9e9e1aada45bd9b7954a6e42c" +dependencies = [ + "cfg-if", + "cipher 0.5.2", +] + [[package]] name = "same-file" version = "1.0.6" @@ -4853,12 +5836,39 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring 0.17.14", + "untrusted 0.9.0", +] + +[[package]] +name = "sealed" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22f968c5ea23d555e670b449c1c5e7b2fc399fdaec1d304a17cd48e288abc107" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "sec1" version = "0.7.3" @@ -4879,7 +5889,7 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.1", "core-foundation", "core-foundation-sys", "libc", @@ -4902,6 +5912,16 @@ version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +[[package]] +name = "sendfd" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b183bfd5b1bc64ab0c1ef3ee06b008a9ef1b68a7d3a99ba566fbfe7a7c6d745b" +dependencies = [ + "libc", + "tokio", +] + [[package]] name = "serde" version = "1.0.228" @@ -4953,6 +5973,19 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "serde_html_form" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acf96b1d9364968fce46ebb548f1c0e1d7eceae27bdff73865d42e6c7369d94" +dependencies = [ + "form_urlencoded", + "indexmap 2.11.4", + "itoa", + "ryu", + "serde_core", +] + [[package]] name = "serde_ignored" version = "0.1.14" @@ -5068,7 +6101,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest", ] @@ -5079,7 +6112,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest", ] @@ -5090,7 +6123,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest", ] @@ -5104,6 +6137,70 @@ dependencies = [ "keccak", ] +[[package]] +name = "shadowsocks" +version = "1.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "482831bf9d55acf3c98e211b6c852c3dfdf1d1b0d23fdf1d887c5a4b2acad4e4" +dependencies = [ + "aes 0.8.4", + "base64 0.22.1", + "blake3", + "byte_string", + "bytes", + "cfg-if", + "dynosaur", + "futures", + "libc", + "log", + "lru_time_cache", + "percent-encoding", + "pin-project", + "rand 0.9.2", + "sealed", + "sendfd", + "serde", + "serde_json", + "serde_urlencoded", + "shadowsocks-crypto", + "socket2 0.6.3", + "spin 0.10.0", + "thiserror 2.0.16", + "tokio", + "tokio-tfo", + "trait-variant", + "url", + "windows-sys 0.61.0", +] + +[[package]] +name = "shadowsocks-crypto" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d038a3d17586f1c1ab3c1c3b9e4d5ef8fba98fb3890ad740c8487038b2e2ca5" +dependencies = [ + "aead 0.5.2", + "aes 0.8.4", + "aes-gcm", + "aes-gcm-siv", + "blake3", + "bytes", + "camellia", + "ccm", + "cfg-if", + "chacha20", + "chacha20poly1305", + "ctr", + "ghash", + "hkdf", + "md-5", + "rand 0.9.2", + "ring-compat", + "sha1", + "sm4 0.5.1", + "subtle", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -5130,6 +6227,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + [[package]] name = "signal-hook-registry" version = "1.4.6" @@ -5190,11 +6293,42 @@ dependencies = [ "void", ] +[[package]] +name = "sm4" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d7abf5135ffd68fb4b438e1fb246923b80d25eda386d8b798bb4ad3ed00f75f" +dependencies = [ + "cipher 0.4.4", +] + +[[package]] +name = "sm4" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2d621883cdeeaee6759d39f4bc8cd314b29db72b6ac3d6714b31422512ee11" +dependencies = [ + "cipher 0.5.2", +] + [[package]] name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +dependencies = [ + "serde", +] + +[[package]] +name = "smol_str" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4aaa7368fcf4852a4c2dd92df0cace6a71f2091ca0a23391ce7f3a31833f1523" +dependencies = [ + "borsh", + "serde_core", +] [[package]] name = "smoltcp" @@ -5211,6 +6345,23 @@ dependencies = [ "managed", ] +[[package]] +name = "smux_rust" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6da4231ce17872ec83fffdc189d64a121e1df79e2c0e08ee6c2d9f12f13f9994" +dependencies = [ + "bytes", + "futures", + "tokio", +] + +[[package]] +name = "snap" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b6b67fb9a61334225b5b790716f609cd58395f895b3fe8b328786812a40bc3b" + [[package]] name = "socket2" version = "0.5.10" @@ -5231,6 +6382,18 @@ dependencies = [ "windows-sys 0.61.0", ] +[[package]] +name = "softaes" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45e14297decde697ddf377c25752aead0927d5cfc89c2684d2af96901a4ceeea" + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + [[package]] name = "spin" version = "0.9.8" @@ -5240,6 +6403,15 @@ dependencies = [ "lock_api", ] +[[package]] +name = "spin" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591" +dependencies = [ + "lock_api", +] + [[package]] name = "spki" version = "0.7.3" @@ -5268,7 +6440,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "caac132742f0d33c3af65bfcde7f6aa8f62f0e991d80db99149eb9d44708784f" dependencies = [ - "cipher", + "cipher 0.4.4", "ssh-encoding", ] @@ -5454,6 +6626,12 @@ dependencies = [ "libc", ] +[[package]] +name = "tagptr" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" + [[package]] name = "tap" version = "1.0.1" @@ -5600,22 +6778,33 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.47.1" +version = "1.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" +checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" dependencies = [ - "backtrace", "bytes", - "io-uring", "libc", "mio", + "parking_lot 0.12.4", "pin-project-lite", "signal-hook-registry", - "slab", "socket2 0.6.3", "tokio-macros", "tracing", - "windows-sys 0.59.0", + "windows-sys 0.61.0", +] + +[[package]] +name = "tokio-graceful" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45740b38b48641855471cd402922e89156bdfbd97b69b45eeff170369cc18c7d" +dependencies = [ + "loom", + "pin-project-lite", + "slab", + "tokio", + "tracing", ] [[package]] @@ -5630,9 +6819,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.5.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" dependencies = [ "proc-macro2", "quote", @@ -5659,6 +6848,29 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls-fork-shadow-tls" +version = "0.0.8" +dependencies = [ + "bytes", + "rustls-fork-shadow-tls", + "thiserror 1.0.69", + "tokio", +] + +[[package]] +name = "tokio-snappy" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb0ab9ee987e7134c0cdfe038b737102433aa8d7e04ad99cf1ec258907af976" +dependencies = [ + "bytes", + "futures", + "pin-project", + "snap", + "tokio", +] + [[package]] name = "tokio-stream" version = "0.1.17" @@ -5670,6 +6882,23 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-tfo" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6ad2c3b3bb958ad992354a7ebc468fc0f7cdc9af4997bf4d3fd3cb28bad36dc" +dependencies = [ + "cfg-if", + "futures", + "libc", + "log", + "once_cell", + "pin-project", + "socket2 0.6.3", + "tokio", + "windows-sys 0.60.2", +] + [[package]] name = "tokio-util" version = "0.7.18" @@ -5684,6 +6913,19 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio_smux" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a5826746b94900340cfda9e18a29a75986992df653d5941b5e359ca3b667117" +dependencies = [ + "byteorder", + "dashmap", + "log", + "thiserror 1.0.69", + "tokio", +] + [[package]] name = "toml" version = "0.8.23" @@ -5908,7 +7150,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ab0c79bc92a957e85959cf397a2d8f9c8294c35fa4f65247a9393b20ac95551" dependencies = [ "amplify", - "bitflags 2.9.4", + "bitflags 2.11.1", "bytes", "caret", "derive-deftly", @@ -6407,7 +7649,7 @@ version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c6989a1c6d06ffd6835e2917edaae4aeef544f8e5fdd68b54cc365f2af523de" dependencies = [ - "aes", + "aes 0.8.4", "base64ct", "ctr", "curve25519-dalek", @@ -6506,7 +7748,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41be8f47f521fc95206d2ba5facac8fb1a5b5b82169bd41ebeecdf46d1e77246" dependencies = [ "async-trait", - "bitflags 2.9.4", + "bitflags 2.11.1", "derive_more", "futures", "humantime", @@ -6535,7 +7777,7 @@ checksum = "ea8bce73d2c78bd78a2a927336ca639cf6bd5d8ad092ebcd0b3fdeaa47dcc77e" dependencies = [ "amplify", "base64ct", - "cipher", + "cipher 0.4.4", "derive-deftly", "derive_builder_fork_arti", "derive_more", @@ -6610,7 +7852,7 @@ dependencies = [ "bytes", "caret", "cfg-if", - "cipher", + "cipher 0.4.4", "coarsetime", "criterion-cycles-per-byte", "derive-deftly", @@ -6842,7 +8084,7 @@ version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.1", "bytes", "futures-util", "http 1.3.1", @@ -6942,7 +8184,7 @@ dependencies = [ "cfg-if", "fnv", "once_cell", - "parking_lot", + "parking_lot 0.12.4", "tracing-core", "tracing-subscriber", ] @@ -6986,6 +8228,17 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "trait-variant" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70977707304198400eb4835a78f6a9f928bf41bba420deb8fdb175cd965d77a7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "try-lock" version = "0.2.5" @@ -7019,6 +8272,15 @@ dependencies = [ "zip", ] +[[package]] +name = "twofish" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0218ef702a91e18ba84dd516edf8df33a9f9fb4431d6ada13e6f9502c3bcb6fc" +dependencies = [ + "cipher 0.5.2", +] + [[package]] name = "typed-index-collections" version = "3.5.0" @@ -7044,6 +8306,12 @@ dependencies = [ "version_check", ] +[[package]] +name = "unicase" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" + [[package]] name = "unicode-ident" version = "1.0.19" @@ -7080,7 +8348,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" dependencies = [ - "crypto-common", + "crypto-common 0.1.6", "subtle", ] @@ -7090,6 +8358,12 @@ version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + [[package]] name = "untrusted" version = "0.9.0" @@ -7132,6 +8406,7 @@ version = "1.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" dependencies = [ + "getrandom 0.3.3", "js-sys", "serde", "wasm-bindgen", @@ -7316,7 +8591,7 @@ version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.1", "hashbrown 0.15.5", "indexmap 2.11.4", "semver", @@ -7348,6 +8623,25 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53" +dependencies = [ + "ring 0.17.14", + "untrusted 0.9.0", +] + +[[package]] +name = "webpki-roots" +version = "0.22.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" +dependencies = [ + "webpki", +] + [[package]] name = "webpki-roots" version = "0.26.11" @@ -7384,6 +8678,15 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd7cf3379ca1aac9eea11fba24fd7e315d621f8dfe35c8d7d2be8b793726e07d" +[[package]] +name = "wildcard" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9b0540e91e49de3817c314da0dd3bc518093ceacc6ea5327cb0e1eb073e5189" +dependencies = [ + "thiserror 2.0.16", +] + [[package]] name = "winapi" version = "0.3.9" @@ -7892,7 +9195,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", - "bitflags 2.9.4", + "bitflags 2.11.1", "indexmap 2.11.4", "log", "serde", @@ -7955,6 +9258,22 @@ version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3" +[[package]] +name = "yamux" +version = "0.13.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1991f6690292030e31b0144d73f5e8368936c58e45e7068254f7138b23b00672" +dependencies = [ + "futures", + "log", + "nohash-hasher", + "parking_lot 0.12.4", + "pin-project", + "rand 0.9.2", + "static_assertions", + "web-time", +] + [[package]] name = "yoke" version = "0.8.0" @@ -7979,6 +9298,18 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zears" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e54700681e12629b566fc71a9ee538961b451ebf65b7509c2e12f114dd8e5aa" +dependencies = [ + "aes 0.8.4", + "blake2", + "constant_time_eq 0.4.2", + "cpufeatures 0.2.17", +] + [[package]] name = "zerocopy" version = "0.8.27" @@ -8079,15 +9410,15 @@ version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" dependencies = [ - "aes", + "aes 0.8.4", "byteorder", "bzip2", - "constant_time_eq", + "constant_time_eq 0.1.5", "crc32fast", "crossbeam-utils", "flate2", "hmac", - "pbkdf2", + "pbkdf2 0.11.0", "sha1", "time", "zstd 0.11.2+zstd.1.5.2", diff --git a/Cargo.toml b/Cargo.toml index 362ba2b..9c99d53 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/Scripts/burrow-proxy-selftest b/Scripts/burrow-proxy-selftest index bc980c9..fd3daaa 100755 --- a/Scripts/burrow-proxy-selftest +++ b/Scripts/burrow-proxy-selftest @@ -6,10 +6,11 @@ usage() { Usage: Scripts/burrow-proxy-selftest Run a controlled Burrow proxy packet-runtime test without changing system proxy -settings or using the user's Burrow database. The script starts a local Trojan -TLS server, starts a Burrow daemon against a temporary database/socket, imports a -temporary ProxySubscription pointed at that server, then runs the packet-level -proxy-tcp-probe through daemon DNS and TCP packet streaming. +settings or using the user's Burrow database. The script starts local Trojan and +Shadowsocks servers, starts a Burrow daemon against a temporary database/socket, +imports a temporary ProxySubscription pointed at those servers, then runs the +packet-level proxy TCP, UDP, UDP-over-TCP, and plugin probes through daemon +packet streaming. EOF } @@ -21,8 +22,11 @@ fi repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" burrow_bin="${BURROW_BIN:-$repo_root/target/debug/burrow}" -if [[ ! -x "$burrow_bin" ]]; then +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 openssl >/dev/null 2>&1; then @@ -30,12 +34,21 @@ if ! command -v openssl >/dev/null 2>&1; then exit 2 fi +if ! command -v uv >/dev/null 2>&1; then + echo "uv is required for the local Python self-test harness" >&2 + exit 2 +fi + tmpdir="$(mktemp -d "${TMPDIR:-/tmp}/burrow-proxy-selftest.XXXXXX")" daemon_pid="" server_pid="" +ss_server_pid="" status=0 cleanup() { + if [[ -n "$ss_server_pid" ]]; then + kill "$ss_server_pid" >/dev/null 2>&1 || true + fi if [[ -n "$server_pid" ]]; then kill "$server_pid" >/dev/null 2>&1 || true fi @@ -54,10 +67,13 @@ cert="$tmpdir/cert.pem" key="$tmpdir/key.pem" socket="$tmpdir/burrow.sock" port_file="$tmpdir/server.port" +ss_port_file="$tmpdir/shadowsocks-server.port" server_result="$tmpdir/server-result.json" +ss_server_result="$tmpdir/shadowsocks-server-result.json" payload="$tmpdir/proxy-payload.json" daemon_log="$tmpdir/daemon.log" server_log="$tmpdir/server.log" +ss_server_log="$tmpdir/shadowsocks-server.log" openssl req \ -x509 \ @@ -68,7 +84,7 @@ openssl req \ -out "$cert" \ -days 1 >/dev/null 2>&1 -python3 - "$port_file" "$server_result" "$cert" "$key" >"$server_log" 2>&1 <<'PY' & +uv run python - "$port_file" "$server_result" "$cert" "$key" >"$server_log" 2>&1 <<'PY' & from __future__ import annotations import hashlib @@ -196,10 +212,760 @@ if [[ ! -s "$port_file" ]]; then fi server_port="$(cat "$port_file")" +uv run python - "$ss_port_file" "$ss_server_result" >"$ss_server_log" 2>&1 <<'PY' & +from __future__ import annotations + +import json +import base64 +import socket +import struct +import sys + +port_file, result_file = sys.argv[1:] + + +def read_exact(sock: socket.socket, length: int) -> bytes: + chunks: list[bytes] = [] + remaining = length + while remaining: + chunk = sock.recv(remaining) + if not chunk: + raise EOFError(f"expected {remaining} more bytes") + chunks.append(chunk) + remaining -= len(chunk) + return b"".join(chunks) + + +def read_socks_addr(sock: socket.socket) -> dict[str, object]: + atyp = read_exact(sock, 1)[0] + if atyp == 1: + host = socket.inet_ntop(socket.AF_INET, read_exact(sock, 4)) + elif atyp == 4: + host = socket.inet_ntop(socket.AF_INET6, read_exact(sock, 16)) + elif atyp == 3: + length = read_exact(sock, 1)[0] + host = read_exact(sock, length).decode("idna") + else: + raise ValueError(f"unsupported SOCKS address type {atyp}") + port = struct.unpack("!H", read_exact(sock, 2))[0] + return {"atyp": atyp, "host": host, "port": port} + + +def parse_socks_addr_packet(data: bytes) -> tuple[dict[str, object], int]: + offset = 0 + atyp = data[offset] + offset += 1 + if atyp == 1: + host = socket.inet_ntop(socket.AF_INET, data[offset : offset + 4]) + offset += 4 + elif atyp == 4: + host = socket.inet_ntop(socket.AF_INET6, data[offset : offset + 16]) + offset += 16 + elif atyp == 3: + length = data[offset] + offset += 1 + host = data[offset : offset + length].decode("idna") + offset += length + else: + raise ValueError(f"unsupported SOCKS address type {atyp}") + port = struct.unpack("!H", data[offset : offset + 2])[0] + offset += 2 + return {"atyp": atyp, "host": host, "port": port}, offset + + +def read_uot_addr(sock: socket.socket) -> tuple[dict[str, object], bytes]: + raw = bytearray() + atyp = read_exact(sock, 1)[0] + raw.append(atyp) + if atyp == 0: + addr = read_exact(sock, 4) + raw.extend(addr) + host = socket.inet_ntop(socket.AF_INET, addr) + elif atyp == 1: + addr = read_exact(sock, 16) + raw.extend(addr) + host = socket.inet_ntop(socket.AF_INET6, addr) + elif atyp == 2: + length = read_exact(sock, 1)[0] + raw.append(length) + addr = read_exact(sock, length) + raw.extend(addr) + host = addr.decode("idna") + else: + raise ValueError(f"unsupported UDP-over-TCP address type {atyp}") + port_bytes = read_exact(sock, 2) + raw.extend(port_bytes) + port = struct.unpack("!H", port_bytes)[0] + return {"atyp": atyp, "host": host, "port": port}, bytes(raw) + + +def read_uot_frame(sock: socket.socket) -> tuple[dict[str, object], bytes, bytes]: + target, target_bytes = read_uot_addr(sock) + length_bytes = read_exact(sock, 2) + payload_len = struct.unpack("!H", length_bytes)[0] + payload = read_exact(sock, payload_len) + return target, target_bytes + length_bytes + payload, payload + + +def read_uot_request(sock: socket.socket) -> dict[str, object]: + version = read_exact(sock, 1)[0] + target = read_socks_addr(sock) + return {"version": version, "target": target} + + +def handle_uot_connection( + listener: socket.socket, + result: dict[str, object], + prefix: str, + expect_request: bool, +) -> None: + uot_raw, uot_peer = listener.accept() + uot_raw.settimeout(10) + try: + uot_magic = read_socks_addr(uot_raw) + if expect_request: + result[f"{prefix}_request"] = read_uot_request(uot_raw) + uot_target, uot_frame, uot_payload = read_uot_frame(uot_raw) + result[f"{prefix}_peer"] = str(uot_peer) + result[f"{prefix}_magic_target"] = uot_magic + result[f"{prefix}_target"] = uot_target + result[f"{prefix}_payload"] = uot_payload.decode("utf-8", "replace") + uot_raw.sendall(uot_frame) + finally: + uot_raw.close() + + +def read_http_headers(sock: socket.socket) -> bytes: + return read_http_headers_with_prefix(sock, b"") + + +def read_http_headers_with_prefix(sock: socket.socket, prefix: bytes) -> bytes: + data = bytearray(prefix) + while b"\r\n\r\n" not in data: + chunk = sock.recv(4096) + if not chunk: + break + data.extend(chunk) + if len(data) > 65535: + raise ValueError("HTTP request headers exceeded 64 KiB") + return bytes(data) + + +def read_obfs_http_headers(sock: socket.socket) -> tuple[bytes, bytes]: + data = bytearray() + while b"\r\n\r\n" not in data: + chunk = sock.recv(4096) + if not chunk: + break + data.extend(chunk) + if len(data) > 65535: + raise ValueError("simple-obfs HTTP headers exceeded 64 KiB") + header_end = bytes(data).find(b"\r\n\r\n") + if header_end < 0: + raise ValueError("simple-obfs HTTP headers were incomplete") + header_end += 4 + return bytes(data[:header_end]), bytes(data[header_end:]) + + +def read_simple_obfs_tls_payload(sock: socket.socket) -> tuple[bytes, str | None]: + header = read_exact(sock, 5) + if header[0] != 22: + raise ValueError(f"expected TLS handshake record, got type {header[0]}") + record_len = struct.unpack("!H", header[3:5])[0] + record = read_exact(sock, record_len) + if len(record) < 4 or record[0] != 1: + raise ValueError("expected TLS ClientHello handshake") + body_len = int.from_bytes(record[1:4], "big") + body = record[4 : 4 + body_len] + offset = 0 + offset += 2 # version + offset += 32 # random + session_id_len = body[offset] + offset += 1 + session_id_len + cipher_len = struct.unpack("!H", body[offset : offset + 2])[0] + offset += 2 + cipher_len + compression_len = body[offset] + offset += 1 + compression_len + extension_len = struct.unpack("!H", body[offset : offset + 2])[0] + offset += 2 + end = offset + extension_len + ticket_payload: bytes | None = None + server_name: str | None = None + while offset + 4 <= end: + extension_type = struct.unpack("!H", body[offset : offset + 2])[0] + length = struct.unpack("!H", body[offset + 2 : offset + 4])[0] + offset += 4 + extension = body[offset : offset + length] + offset += length + if extension_type == 0x0023: + ticket_payload = extension + elif extension_type == 0x0000 and len(extension) >= 5: + name_len = struct.unpack("!H", extension[3:5])[0] + if len(extension) >= 5 + name_len: + server_name = extension[5 : 5 + name_len].decode("idna") + if ticket_payload is None: + raise ValueError("simple-obfs TLS ClientHello did not include payload extension") + return ticket_payload, server_name + + +def read_simple_obfs_tls_application_payload(sock: socket.socket) -> bytes: + header = read_exact(sock, 5) + if header[:3] != b"\x17\x03\x03": + raise ValueError(f"expected TLS application-data record, got {header.hex()}") + record_len = struct.unpack("!H", header[3:5])[0] + return read_exact(sock, record_len) + + +def read_simple_obfs_tls_plaintext_headers(sock: socket.socket, prefix: bytes) -> bytes: + data = bytearray(prefix) + while b"\r\n\r\n" not in data: + data.extend(read_simple_obfs_tls_application_payload(sock)) + if len(data) > 65535: + raise ValueError("simple-obfs TLS plaintext headers exceeded 64 KiB") + return bytes(data) + + +def read_client_websocket_frame(sock: socket.socket) -> bytes: + header = read_exact(sock, 2) + if header[0] & 0x0F != 2: + raise ValueError(f"expected binary websocket frame, got opcode {header[0] & 0x0F}") + if header[1] & 0x80 == 0: + raise ValueError("client websocket frame was not masked") + length = header[1] & 0x7F + if length == 126: + length = struct.unpack("!H", read_exact(sock, 2))[0] + elif length == 127: + length = struct.unpack("!Q", read_exact(sock, 8))[0] + mask = read_exact(sock, 4) + payload = bytearray(read_exact(sock, length)) + for index, byte in enumerate(payload): + payload[index] = byte ^ mask[index % 4] + return bytes(payload) + + +def server_websocket_frame(payload: bytes) -> bytes: + if len(payload) < 126: + return b"\x82" + bytes([len(payload)]) + payload + if len(payload) <= 65535: + return b"\x82\x7e" + struct.pack("!H", len(payload)) + payload + return b"\x82\x7f" + struct.pack("!Q", len(payload)) + payload + + +def read_websocket_plaintext_headers(sock: socket.socket, prefix: bytes) -> bytes: + data = bytearray(prefix) + while b"\r\n\r\n" not in data: + data.extend(read_client_websocket_frame(sock)) + if len(data) > 65535: + raise ValueError("websocket plaintext headers exceeded 64 KiB") + return bytes(data) + + +def http_header_value(headers: bytes, name: bytes) -> str | None: + name = name.lower() + b":" + for line in headers.splitlines(): + if line.lower().startswith(name): + return line.decode("utf-8", "replace").split(":", 1)[1].strip() + return None + + +def decode_websocket_early_data(value: str | None) -> bytes: + if not value: + raise ValueError("missing websocket early-data protocol header") + padding = "=" * (-len(value) % 4) + return base64.urlsafe_b64decode((value + padding).encode()) + + +def parse_v2ray_mux_client_payload(payload: bytes) -> bytes: + open_len = struct.unpack("!H", payload[:2])[0] + open_frame = payload[2 : 2 + open_len] + if len(open_frame) != open_len or open_frame[:4] != b"\x00\x00\x01\x00": + raise ValueError("v2ray mux open frame was invalid") + offset = 2 + open_len + data_header = payload[offset : offset + 8] + if len(data_header) != 8 or data_header[:6] != b"\x00\x04\x00\x00\x02\x01": + raise ValueError("v2ray mux data frame header was invalid") + data_len = struct.unpack("!H", data_header[6:8])[0] + data_start = offset + 8 + data = payload[data_start : data_start + data_len] + if len(data) != data_len: + raise ValueError("v2ray mux data frame payload was incomplete") + return data + + +def v2ray_mux_server_data_frame(payload: bytes) -> bytes: + if len(payload) > 65535: + raise ValueError("v2ray mux payload is too large") + return b"\x00\x04\x00\x00\x02\x01" + struct.pack("!H", len(payload)) + payload + + +def parse_v2ray_mux_data_payload(payload: bytes) -> bytes: + if len(payload) < 8 or payload[:6] != b"\x00\x04\x00\x00\x02\x01": + raise ValueError("v2ray mux data frame header was invalid") + data_len = struct.unpack("!H", payload[6:8])[0] + data = payload[8 : 8 + data_len] + if len(data) != data_len: + raise ValueError("v2ray mux data frame payload was incomplete") + return data + + +def read_v2ray_mux_plaintext_headers(sock: socket.socket, prefix: bytes) -> bytes: + data = bytearray(prefix) + while b"\r\n\r\n" not in data: + data.extend(parse_v2ray_mux_data_payload(read_client_websocket_frame(sock))) + if len(data) > 65535: + raise ValueError("v2ray mux plaintext headers exceeded 64 KiB") + return bytes(data) + + +listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) +listener.bind(("127.0.0.1", 0)) +listener.listen(16) +port = listener.getsockname()[1] +udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) +udp.bind(("127.0.0.1", port)) +udp.settimeout(10) +with open(port_file, "w", encoding="utf-8") as f: + f.write(str(port)) + +raw, peer = listener.accept() +raw.settimeout(10) +result: dict[str, object] = {"tcp_peer": str(peer)} +try: + target = read_socks_addr(raw) + result["tcp_target"] = target + request = read_http_headers(raw) + result["tcp_request_first_line"] = request.splitlines()[0].decode("utf-8", "replace") + body = json.dumps( + { + "ok": True, + "target": target, + "request_first_line": result["tcp_request_first_line"], + }, + sort_keys=True, + ).encode() + raw.sendall( + b"HTTP/1.1 200 OK\r\n" + + f"Content-Length: {len(body)}\r\n".encode() + + b"Connection: close\r\n" + + b"Content-Type: application/json\r\n\r\n" + + body + ) + datagram, udp_peer = udp.recvfrom(65535) + udp_target, used = parse_socks_addr_packet(datagram) + udp_payload = datagram[used:] + result["udp_peer"] = str(udp_peer) + result["udp_target"] = udp_target + result["udp_payload"] = udp_payload.decode("utf-8", "replace") + udp.sendto(datagram[:used] + udp_payload, udp_peer) + handle_uot_connection(listener, result, "uot", False) + handle_uot_connection(listener, result, "uot_v2", True) + obfs_raw, obfs_peer = listener.accept() + obfs_raw.settimeout(10) + try: + obfs_headers, obfs_payload = read_obfs_http_headers(obfs_raw) + obfs_target, used = parse_socks_addr_packet(obfs_payload) + result["obfs_http_peer"] = str(obfs_peer) + result["obfs_http_first_line"] = obfs_headers.splitlines()[0].decode( + "utf-8", + "replace", + ) + result["obfs_http_host"] = next( + ( + line.decode("utf-8", "replace").split(":", 1)[1].strip() + for line in obfs_headers.splitlines() + if line.lower().startswith(b"host:") + ), + None, + ) + result["obfs_http_content_length"] = next( + ( + line.decode("utf-8", "replace").split(":", 1)[1].strip() + for line in obfs_headers.splitlines() + if line.lower().startswith(b"content-length:") + ), + None, + ) + result["obfs_http_target"] = obfs_target + obfs_request = read_http_headers_with_prefix(obfs_raw, obfs_payload[used:]) + result["obfs_http_request_first_line"] = obfs_request.splitlines()[0].decode( + "utf-8", + "replace", + ) + body = json.dumps( + { + "ok": True, + "target": obfs_target, + "request_first_line": result["obfs_http_request_first_line"], + }, + sort_keys=True, + ).encode() + obfs_raw.sendall( + b"HTTP/1.1 101 Switching Protocols\r\n" + b"Upgrade: websocket\r\n" + b"Connection: Upgrade\r\n\r\n" + b"HTTP/1.1 200 OK\r\n" + + f"Content-Length: {len(body)}\r\n".encode() + + b"Connection: close\r\n" + + b"Content-Type: application/json\r\n\r\n" + + body + ) + finally: + obfs_raw.close() + obfs_tls_raw, obfs_tls_peer = listener.accept() + obfs_tls_raw.settimeout(10) + try: + obfs_tls_payload, obfs_tls_server_name = read_simple_obfs_tls_payload(obfs_tls_raw) + obfs_tls_target, used = parse_socks_addr_packet(obfs_tls_payload) + obfs_tls_request = read_simple_obfs_tls_plaintext_headers( + obfs_tls_raw, + obfs_tls_payload[used:], + ) + result["obfs_tls_peer"] = str(obfs_tls_peer) + result["obfs_tls_server_name"] = obfs_tls_server_name + result["obfs_tls_target"] = obfs_tls_target + result["obfs_tls_request_first_line"] = obfs_tls_request.splitlines()[0].decode( + "utf-8", + "replace", + ) + body = json.dumps( + { + "ok": True, + "target": obfs_tls_target, + "request_first_line": result["obfs_tls_request_first_line"], + }, + sort_keys=True, + ).encode() + response = ( + b"HTTP/1.1 200 OK\r\n" + + f"Content-Length: {len(body)}\r\n".encode() + + b"Connection: close\r\n" + + b"Content-Type: application/json\r\n\r\n" + + body + ) + obfs_tls_raw.sendall(bytes(105) + len(response).to_bytes(2, "big") + response) + finally: + obfs_tls_raw.close() + v2ray_raw, v2ray_peer = listener.accept() + v2ray_raw.settimeout(10) + try: + v2ray_headers = read_http_headers(v2ray_raw) + result["v2ray_http_upgrade_peer"] = str(v2ray_peer) + result["v2ray_http_upgrade_first_line"] = v2ray_headers.splitlines()[0].decode( + "utf-8", + "replace", + ) + result["v2ray_http_upgrade_host"] = next( + ( + line.decode("utf-8", "replace").split(":", 1)[1].strip() + for line in v2ray_headers.splitlines() + if line.lower().startswith(b"host:") + ), + None, + ) + result["v2ray_http_upgrade_has_websocket_key"] = any( + line.lower().startswith(b"sec-websocket-key:") + for line in v2ray_headers.splitlines() + ) + v2ray_raw.sendall( + b"HTTP/1.1 101 Switching Protocols\r\n" + b"Upgrade: websocket\r\n" + b"Connection: Upgrade\r\n\r\n" + ) + v2ray_target = read_socks_addr(v2ray_raw) + v2ray_request = read_http_headers(v2ray_raw) + result["v2ray_http_upgrade_target"] = v2ray_target + result["v2ray_http_upgrade_request_first_line"] = ( + v2ray_request.splitlines()[0].decode( + "utf-8", + "replace", + ) + ) + body = json.dumps( + { + "ok": True, + "target": v2ray_target, + "request_first_line": result["v2ray_http_upgrade_request_first_line"], + }, + sort_keys=True, + ).encode() + v2ray_raw.sendall( + b"HTTP/1.1 200 OK\r\n" + + f"Content-Length: {len(body)}\r\n".encode() + + b"Connection: close\r\n" + + b"Content-Type: application/json\r\n\r\n" + + body + ) + finally: + v2ray_raw.close() + v2ray_ws_raw, v2ray_ws_peer = listener.accept() + v2ray_ws_raw.settimeout(10) + try: + v2ray_ws_headers = read_http_headers(v2ray_ws_raw) + result["v2ray_websocket_peer"] = str(v2ray_ws_peer) + result["v2ray_websocket_first_line"] = ( + v2ray_ws_headers.splitlines()[0].decode( + "utf-8", + "replace", + ) + ) + result["v2ray_websocket_host"] = next( + ( + line.decode("utf-8", "replace").split(":", 1)[1].strip() + for line in v2ray_ws_headers.splitlines() + if line.lower().startswith(b"host:") + ), + None, + ) + result["v2ray_websocket_has_websocket_key"] = any( + line.lower().startswith(b"sec-websocket-key:") + for line in v2ray_ws_headers.splitlines() + ) + v2ray_ws_raw.sendall( + b"HTTP/1.1 101 Switching Protocols\r\n" + b"Upgrade: websocket\r\n" + b"Connection: Upgrade\r\n\r\n" + ) + v2ray_ws_payload = read_client_websocket_frame(v2ray_ws_raw) + v2ray_ws_target, used = parse_socks_addr_packet(v2ray_ws_payload) + v2ray_ws_request = read_websocket_plaintext_headers( + v2ray_ws_raw, + v2ray_ws_payload[used:], + ) + result["v2ray_websocket_target"] = v2ray_ws_target + result["v2ray_websocket_request_first_line"] = ( + v2ray_ws_request.splitlines()[0].decode( + "utf-8", + "replace", + ) + ) + body = json.dumps( + { + "ok": True, + "target": v2ray_ws_target, + "request_first_line": result["v2ray_websocket_request_first_line"], + }, + sort_keys=True, + ).encode() + response = ( + b"HTTP/1.1 200 OK\r\n" + + f"Content-Length: {len(body)}\r\n".encode() + + b"Connection: close\r\n" + + b"Content-Type: application/json\r\n\r\n" + + body + ) + v2ray_ws_raw.sendall(server_websocket_frame(response)) + finally: + v2ray_ws_raw.close() + v2ray_mux_raw, v2ray_mux_peer = listener.accept() + v2ray_mux_raw.settimeout(10) + try: + v2ray_mux_headers = read_http_headers(v2ray_mux_raw) + result["v2ray_mux_peer"] = str(v2ray_mux_peer) + result["v2ray_mux_first_line"] = ( + v2ray_mux_headers.splitlines()[0].decode( + "utf-8", + "replace", + ) + ) + result["v2ray_mux_host"] = next( + ( + line.decode("utf-8", "replace").split(":", 1)[1].strip() + for line in v2ray_mux_headers.splitlines() + if line.lower().startswith(b"host:") + ), + None, + ) + result["v2ray_mux_has_websocket_key"] = any( + line.lower().startswith(b"sec-websocket-key:") + for line in v2ray_mux_headers.splitlines() + ) + v2ray_mux_raw.sendall( + b"HTTP/1.1 101 Switching Protocols\r\n" + b"Upgrade: websocket\r\n" + b"Connection: Upgrade\r\n\r\n" + ) + v2ray_mux_payload = parse_v2ray_mux_client_payload( + read_client_websocket_frame(v2ray_mux_raw), + ) + v2ray_mux_target, used = parse_socks_addr_packet(v2ray_mux_payload) + v2ray_mux_request = read_v2ray_mux_plaintext_headers( + v2ray_mux_raw, + v2ray_mux_payload[used:], + ) + result["v2ray_mux_target"] = v2ray_mux_target + result["v2ray_mux_request_first_line"] = ( + v2ray_mux_request.splitlines()[0].decode( + "utf-8", + "replace", + ) + ) + body = json.dumps( + { + "ok": True, + "target": v2ray_mux_target, + "request_first_line": result["v2ray_mux_request_first_line"], + }, + sort_keys=True, + ).encode() + response = ( + b"HTTP/1.1 200 OK\r\n" + + f"Content-Length: {len(body)}\r\n".encode() + + b"Connection: close\r\n" + + b"Content-Type: application/json\r\n\r\n" + + body + ) + v2ray_mux_raw.sendall( + server_websocket_frame(v2ray_mux_server_data_frame(response)), + ) + finally: + v2ray_mux_raw.close() + gost_ws_raw, gost_ws_peer = listener.accept() + gost_ws_raw.settimeout(10) + try: + gost_ws_headers = read_http_headers(gost_ws_raw) + result["gost_websocket_peer"] = str(gost_ws_peer) + result["gost_websocket_first_line"] = ( + gost_ws_headers.splitlines()[0].decode( + "utf-8", + "replace", + ) + ) + result["gost_websocket_host"] = next( + ( + line.decode("utf-8", "replace").split(":", 1)[1].strip() + for line in gost_ws_headers.splitlines() + if line.lower().startswith(b"host:") + ), + None, + ) + result["gost_websocket_has_websocket_key"] = any( + line.lower().startswith(b"sec-websocket-key:") + for line in gost_ws_headers.splitlines() + ) + gost_ws_raw.sendall( + b"HTTP/1.1 101 Switching Protocols\r\n" + b"Upgrade: websocket\r\n" + b"Connection: Upgrade\r\n\r\n" + ) + gost_ws_payload = read_client_websocket_frame(gost_ws_raw) + gost_ws_target, used = parse_socks_addr_packet(gost_ws_payload) + gost_ws_request = read_websocket_plaintext_headers( + gost_ws_raw, + gost_ws_payload[used:], + ) + result["gost_websocket_target"] = gost_ws_target + result["gost_websocket_request_first_line"] = ( + gost_ws_request.splitlines()[0].decode( + "utf-8", + "replace", + ) + ) + body = json.dumps( + { + "ok": True, + "target": gost_ws_target, + "request_first_line": result["gost_websocket_request_first_line"], + }, + sort_keys=True, + ).encode() + response = ( + b"HTTP/1.1 200 OK\r\n" + + f"Content-Length: {len(body)}\r\n".encode() + + b"Connection: close\r\n" + + b"Content-Type: application/json\r\n\r\n" + + body + ) + gost_ws_raw.sendall(server_websocket_frame(response)) + finally: + gost_ws_raw.close() + v2ray_ed_raw, v2ray_ed_peer = listener.accept() + v2ray_ed_raw.settimeout(10) + try: + v2ray_ed_headers = read_http_headers(v2ray_ed_raw) + result["v2ray_early_data_peer"] = str(v2ray_ed_peer) + result["v2ray_early_data_first_line"] = ( + v2ray_ed_headers.splitlines()[0].decode( + "utf-8", + "replace", + ) + ) + result["v2ray_early_data_host"] = http_header_value(v2ray_ed_headers, b"host") + result["v2ray_early_data_has_websocket_key"] = ( + http_header_value(v2ray_ed_headers, b"sec-websocket-key") is not None + ) + early_data = decode_websocket_early_data( + http_header_value(v2ray_ed_headers, b"sec-websocket-protocol"), + ) + result["v2ray_early_data_len"] = len(early_data) + v2ray_ed_raw.sendall( + b"HTTP/1.1 101 Switching Protocols\r\n" + b"Upgrade: websocket\r\n" + b"Connection: Upgrade\r\n\r\n" + ) + v2ray_ed_payload = early_data + read_client_websocket_frame(v2ray_ed_raw) + v2ray_ed_target, used = parse_socks_addr_packet(v2ray_ed_payload) + v2ray_ed_request = read_websocket_plaintext_headers( + v2ray_ed_raw, + v2ray_ed_payload[used:], + ) + result["v2ray_early_data_target"] = v2ray_ed_target + result["v2ray_early_data_request_first_line"] = ( + v2ray_ed_request.splitlines()[0].decode( + "utf-8", + "replace", + ) + ) + body = json.dumps( + { + "ok": True, + "target": v2ray_ed_target, + "request_first_line": result["v2ray_early_data_request_first_line"], + }, + sort_keys=True, + ).encode() + response = ( + b"HTTP/1.1 200 OK\r\n" + + f"Content-Length: {len(body)}\r\n".encode() + + b"Connection: close\r\n" + + b"Content-Type: application/json\r\n\r\n" + + body + ) + v2ray_ed_raw.sendall(server_websocket_frame(response)) + finally: + v2ray_ed_raw.close() +finally: + raw.close() + udp.close() + with open(result_file, "w", encoding="utf-8") as f: + json.dump(result, f, ensure_ascii=False, indent=2, sort_keys=True) + listener.close() +PY +ss_server_pid=$! + +for _ in {1..100}; do + if [[ -s "$ss_port_file" ]]; then + break + fi + if ! kill -0 "$ss_server_pid" >/dev/null 2>&1; then + echo "local Shadowsocks server exited before listening" >&2 + cat "$ss_server_log" >&2 || true + exit 1 + fi + sleep 0.05 +done + +if [[ ! -s "$ss_port_file" ]]; then + echo "timed out waiting for local Shadowsocks server" >&2 + cat "$ss_server_log" >&2 || true + exit 1 +fi +ss_server_port="$(cat "$ss_port_file")" + cat >"$payload" <"$payload" < dict[str, object]: + with open(path, "r", encoding="utf-8") as f: + return json.load(f) + + +def expect(value: object, expected: object, label: str) -> None: + if value != expected: + raise AssertionError(f"{label}: expected {expected!r}, got {value!r}") + + +def expect_startswith(value: object, prefix: str, label: str) -> None: + if not isinstance(value, str) or not value.startswith(prefix): + raise AssertionError(f"{label}: expected prefix {prefix!r}, got {value!r}") + + +def expect_target(result: dict[str, object], 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") + + +trojan = load(trojan_path) +expect(trojan.get("password_hash_ok"), True, "trojan password hash") +expect(trojan.get("command"), 1, "trojan tcp command") +expect(trojan.get("request_first_line"), "GET / HTTP/1.1", "trojan request") +expect_target(trojan, "target", "selftest.invalid", 80) + +ss = load(shadowsocks_path) +for key in [ + "tcp_target", + "obfs_http_target", + "obfs_tls_target", + "v2ray_http_upgrade_target", + "v2ray_websocket_target", + "v2ray_mux_target", + "gost_websocket_target", + "v2ray_early_data_target", +]: + expect_target(ss, key, "selftest.invalid", 80) + +expect(ss.get("udp_payload"), "burrow-shadowsocks-udp-selftest", "native UDP payload") +expect_target(ss, "udp_target", "9.9.9.9", 5353) +expect(ss.get("uot_payload"), "burrow-shadowsocks-uot-selftest", "legacy UOT payload") +expect_target(ss, "uot_magic_target", "sp.udp-over-tcp.arpa", 0) +expect_target(ss, "uot_target", "9.9.9.9", 5354) +expect(ss.get("uot_v2_payload"), "burrow-shadowsocks-uot-v2-selftest", "v2 UOT payload") +expect_target(ss, "uot_v2_magic_target", "sp.v2.udp-over-tcp.arpa", 0) +expect_target(ss, "uot_v2_target", "9.9.9.9", 5355) +expect_startswith(ss.get("obfs_http_host"), "front.example:", "simple-obfs HTTP host") +expect(ss.get("obfs_tls_server_name"), "front.example", "simple-obfs TLS SNI") +expect(ss.get("v2ray_http_upgrade_has_websocket_key"), False, "v2ray raw upgrade key") +expect(ss.get("v2ray_websocket_has_websocket_key"), True, "v2ray websocket key") +expect(ss.get("v2ray_mux_has_websocket_key"), True, "v2ray mux websocket key") +expect(ss.get("gost_websocket_has_websocket_key"), True, "gost websocket key") +expect(ss.get("v2ray_early_data_first_line"), "GET /ed?trace=1 HTTP/1.1", "early-data path") +expect(ss.get("v2ray_early_data_len"), 8, "early-data length") + +print("Proxy self-test assertions passed") +PY diff --git a/Scripts/burrow-shadowsocks-singmux-interop b/Scripts/burrow-shadowsocks-singmux-interop new file mode 100755 index 0000000..e02374b --- /dev/null +++ b/Scripts/burrow-shadowsocks-singmux-interop @@ -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" < $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" <"$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 diff --git a/burrow/Cargo.toml b/burrow/Cargo.toml index 27d072d..f3cccb9 100644 --- a/burrow/Cargo.toml +++ b/burrow/Cargo.toml @@ -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] diff --git a/burrow/src/main.rs b/burrow/src/main.rs index 4183a75..39a4af1 100644 --- a/burrow/src/main.rs +++ b/burrow/src/main.rs @@ -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, diff --git a/burrow/src/proxy_runtime.rs b/burrow/src/proxy_runtime.rs index 46345d5..09e49f9 100644 --- a/burrow/src/proxy_runtime.rs +++ b/burrow/src/proxy_runtime.rs @@ -1,49 +1,161 @@ -#[cfg(not(target_vendor = "apple"))] -use std::sync::Once; #[cfg(target_vendor = "apple")] use std::{cmp::Reverse, ffi::CStr, process::Command}; use std::{ - collections::HashMap, + collections::{HashMap, VecDeque}, + fs, + future::Future, + io, net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, ToSocketAddrs}, + num::NonZeroU32, os::fd::AsRawFd, - sync::Arc, - time::Duration, + pin::Pin, + str::FromStr, + sync::{ + atomic::{AtomicBool, AtomicUsize, Ordering}, + Arc, Mutex as StdMutex, Once, + }, + task::{Context as TaskContext, Poll}, + time::{Duration, Instant, SystemTime, UNIX_EPOCH}, }; +use aead::{ + generic_array::{ + typenum::{U12, U16, U24}, + GenericArray, + }, + AeadInPlace, KeyInit, +}; +use aegis::{aegis128l::Aegis128L, aegis256::Aegis256}; +use aes::{ + cipher::{generic_array::GenericArray as AesGenericArray, BlockDecrypt, BlockEncrypt}, + Aes128, Aes192, Aes256, +}; +use aes_gcm::AesGcm; use anyhow::{anyhow, bail, Context, Result}; +use base64::{ + engine::general_purpose::{ + STANDARD as BASE64_STANDARD, URL_SAFE as BASE64_URL_SAFE, + URL_SAFE_NO_PAD as BASE64_URL_SAFE_NO_PAD, + }, + Engine as _, +}; +use bytes::Bytes; +use ccm::Ccm; +use chacha20::{ + cipher::{KeyIvInit as ChachaKeyIvInit, StreamCipher as ChachaStreamCipher}, + ChaCha20Legacy, XChaCha20, +}; +use chacha20poly1305::{ChaCha8Poly1305, XChaCha8Poly1305}; +use deoxys::{ + aead::{array::Array as DeoxysArray, AeadInOut as DeoxysAeadInOut, KeyInit as DeoxysKeyInit}, + DeoxysII256, +}; use futures::{SinkExt, StreamExt}; -use md5::{Digest as Md5Digest, Md5}; +use http::{Method, Request, StatusCode}; +use lea::{ + cipher::{ + generic_array::GenericArray as LeaGenericArray, BlockEncrypt as LeaBlockEncrypt, + NewBlockCipher as LeaNewBlockCipher, + }, + Lea128, Lea192, Lea256, +}; +use md5::Md5; #[cfg(target_vendor = "apple")] -use native_tls::TlsConnector as NativeTlsConnector; +use native_tls::{Identity as NativeTlsIdentity, TlsConnector as NativeTlsConnector}; use netstack_smoltcp::{ StackBuilder, TcpListener as StackTcpListener, TcpStream as StackTcpStream, UdpSocket as StackUdpSocket, }; -use rand::{rngs::OsRng, RngCore}; -use ring::{aead, hmac}; -#[cfg(not(target_vendor = "apple"))] -use rustls::{ - client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}, - pki_types::{CertificateDer, ServerName, UnixTime}, - ClientConfig, DigitallySignedStruct, Error as RustlsError, RootCertStore, SignatureScheme, +use poly1305::{universal_hash::UniversalHash, Poly1305}; +use rabbit::{ + cipher::{KeyIvInit as RabbitKeyIvInit, StreamCipher as RabbitStreamCipher}, + Rabbit, }; -use sha2::{Sha224, Sha256}; +#[cfg(feature = "boring-browser-fingerprints")] +use rama_net::{ + address::Domain as RamaDomain, + tls::{ + client::{ + ClientHelloExtension as RamaClientHelloExtension, + ServerVerifyMode as RamaServerVerifyMode, + }, + SupportedGroup as RamaSupportedGroup, + }, +}; +#[cfg(feature = "boring-browser-fingerprints")] +use rama_tls_boring::{ + client::{ + ConnectorConfigClientAuth as BoringConnectorConfigClientAuth, + TlsConnectorDataBuilder as BoringTlsConnectorDataBuilder, + }, + core::{ + hash::MessageDigest as BoringMessageDigest, + pkey::PKey as BoringPKey, + x509::{X509Ref as BoringX509Ref, X509 as BoringX509}, + }, +}; +#[cfg(feature = "boring-browser-fingerprints")] +use rama_ua::{ + profile::{try_load_embedded_profiles, TlsProfile}, + PlatformKind, UserAgentKind, +}; +use rand::{rngs::OsRng, Rng, RngCore}; +use ring::{hmac, pbkdf2}; +use rust_tokio_kcp::{ + Aes128BlockCrypt as KcpAes128BlockCrypt, Aes192BlockCrypt as KcpAes192BlockCrypt, + Aes256BlockCrypt as KcpAes256BlockCrypt, AesGcmBlockCrypt as KcpAesGcmBlockCrypt, + BlockCrypt as KcpBlockCrypt, BlowfishBlockCrypt as KcpBlowfishBlockCrypt, + Cast5BlockCrypt as KcpCast5BlockCrypt, KcpConfig, KcpNoDelayConfig, KcpStream, + NoneBlockCrypt as KcpNoneBlockCrypt, Salsa20BlockCrypt as KcpSalsa20BlockCrypt, + SimpleXorBlockCrypt as KcpSimpleXorBlockCrypt, Sm4BlockCrypt as KcpSm4BlockCrypt, + TeaBlockCrypt as KcpTeaBlockCrypt, TripleDesBlockCrypt as KcpTripleDesBlockCrypt, + TwofishBlockCrypt as KcpTwofishBlockCrypt, XteaBlockCrypt as KcpXteaBlockCrypt, +}; +use rustls::{ + client::{ + danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}, + EchConfig, EchMode, + }, + pki_types::{CertificateDer, EchConfigListBytes, PrivateKeyDer, ServerName, UnixTime}, + version, ClientConfig, DigitallySignedStruct, Error as RustlsError, RootCertStore, + SignatureScheme, +}; +use rustls_fork_shadow_tls as shadow_rustls; +use sha2::{Digest, Sha224, Sha256}; +use shadowsocks::{ + config::{ + ServerAddr as SsServerAddr, ServerConfig as SsServerConfig, ServerType as SsServerType, + }, + context::{Context as SsContext, SharedContext as SsSharedContext}, + crypto::CipherKind as SsCipherKind, + relay::{ + socks5::Address as SsAddress, + udprelay::{ + proxy_socket::UdpSocketType as SsUdpSocketType, DatagramReceive, DatagramSend, + DatagramSocket, ProxySocket as SsProxySocket, + }, + }, + ProxyClientStream as SsProxyClientStream, +}; +use subtle::ConstantTimeEq; use tokio::{ - io::{split, AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}, + io::{split, AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, ReadBuf}, net::{TcpSocket, TcpStream, UdpSocket}, - sync::{broadcast, mpsc, Mutex, RwLock}, + sync::{broadcast, mpsc, oneshot, Mutex, RwLock}, task::{JoinHandle, JoinSet}, time::timeout, }; #[cfg(target_vendor = "apple")] use tokio_native_tls::TlsConnector as TokioNativeTlsConnector; -#[cfg(not(target_vendor = "apple"))] use tokio_rustls::TlsConnector; +use tokio_rustls_fork_shadow_tls::TlsConnector as ShadowTlsV3TlsConnector; +use tokio_snappy::SnappyIO; +use tokio_util::compat::{FuturesAsyncReadCompatExt, TokioAsyncReadCompatExt}; use tun::tokio::TunInterface; use crate::proxy_subscription::{ - ProxyNode, ProxyNodeConfig, ProxySubscriptionPayload, ShadowsocksNode, TrojanNetwork, - TrojanNode, + ProxyNode, ProxyNodeConfig, ProxySubscriptionPayload, ShadowsocksNode, ShadowsocksPlugin, + ShadowsocksSmux, TrojanNetwork, TrojanNode, }; const PROXY_TUN_V4: &str = "100.64.0.2/24"; @@ -53,10 +165,82 @@ const UDP_IDLE_TIMEOUT: Duration = Duration::from_secs(30); const TROJAN_MAX_UDP_PAYLOAD: usize = 8192; const MIHOMO_TROJAN_TCP_DEFAULT_ALPN: &[&str] = &["h2", "http/1.1"]; const SS_INFO: &[u8] = b"ss-subkey"; +const UOT_VERSION: u8 = 2; +#[allow(dead_code)] +const TLS_RECORD_HEADER_LEN: usize = 5; +#[allow(dead_code)] +const TLS_APPLICATION_DATA_RECORD_TYPE: u8 = 0x17; +#[allow(dead_code)] +const TLS12_RECORD_VERSION: [u8; 2] = [0x03, 0x03]; +const UOT_LEGACY_VERSION: u8 = 1; +const UOT_MAGIC_ADDRESS: &str = "sp.v2.udp-over-tcp.arpa"; +const UOT_LEGACY_MAGIC_ADDRESS: &str = "sp.udp-over-tcp.arpa"; +const SING_MUX_ADDRESS: &str = "sp.mux.sing-box.arpa"; +const SING_MUX_PORT: u16 = 444; +const SING_MUX_PROTOCOL_SMUX: u8 = 0; +const SING_MUX_PROTOCOL_YAMUX: u8 = 1; +const SING_MUX_PROTOCOL_H2MUX: u8 = 2; +const SING_MUX_VERSION_0: u8 = 0; +const SING_MUX_VERSION_1: u8 = 1; +const SING_MUX_PADDING_FRAMES: usize = 16; +const SING_MUX_MIN_PADDING_LEN: usize = 256; +const SING_MUX_PADDING_LEN_RANGE: usize = 512; +const SING_MUX_STREAM_FLAG_UDP: u16 = 1; +const SING_MUX_STREAM_STATUS_SUCCESS: u8 = 0; +const SING_MUX_STREAM_STATUS_ERROR: u8 = 1; +const SING_MUX_BRUTAL_EXCHANGE_DOMAIN: &str = "_BrutalBwExchange"; +const AEAD2022_SESSION_SUBKEY_CONTEXT: &str = "shadowsocks 2022 session subkey"; +const AEAD2022_IDENTITY_SUBKEY_CONTEXT: &str = "shadowsocks 2022 identity subkey"; +const AEAD2022_HEADER_TYPE_CLIENT: u8 = 0; +const AEAD2022_HEADER_TYPE_SERVER: u8 = 1; +const AEAD2022_TIMESTAMP_MAX_DIFF: u64 = 30; const PUBLIC_DNS_SERVERS: &[&str] = &["1.1.1.1", "8.8.8.8"]; +const SHADOW_TLS_MAX_RECORD_PAYLOAD: usize = 16 * 1024; +const SHADOW_TLS_V3_HMAC_SIZE: usize = 4; +const SHADOW_TLS_V3_TLS_HEADER_SIZE: usize = 5; +const SHADOW_TLS_V3_TLS_RANDOM_SIZE: usize = 32; +const SHADOW_TLS_V3_SESSION_ID_SIZE: usize = 32; +const SHADOW_TLS_V3_SESSION_ID_START: usize = 1 + 3 + 2 + SHADOW_TLS_V3_TLS_RANDOM_SIZE + 1; +const SHADOW_TLS_V3_SERVER_RANDOM_INDEX: usize = SHADOW_TLS_V3_TLS_HEADER_SIZE + 1 + 3 + 2; +const SHADOW_TLS_V3_SESSION_ID_LENGTH_INDEX: usize = + SHADOW_TLS_V3_SERVER_RANDOM_INDEX + SHADOW_TLS_V3_TLS_RANDOM_SIZE; +const SHADOW_TLS_V3_HMAC_HEADER_SIZE: usize = + SHADOW_TLS_V3_TLS_HEADER_SIZE + SHADOW_TLS_V3_HMAC_SIZE; +const KCPTUN_RATE_LIMIT_BURST_BYTES: u64 = 64 * 1500; +const KCPTUN_SMUX_DEFAULT_KEEP_ALIVE_TIMEOUT: Duration = Duration::from_secs(30); +const RESTLS_DEFAULT_SCRIPT: &str = "250?100<1,350~100<1,600~100,300~200,300~100"; +#[allow(dead_code)] +const RESTLS_APP_DATA_MAC_LEN: usize = 8; +#[allow(dead_code)] +const RESTLS_CMD_LEN: usize = 2; +#[allow(dead_code)] +const RESTLS_MASK_LEN: usize = RESTLS_CMD_LEN + 2; +#[allow(dead_code)] +const RESTLS_APP_DATA_AUTH_HEADER_LEN: usize = RESTLS_APP_DATA_MAC_LEN + RESTLS_MASK_LEN; +#[allow(dead_code)] +const RESTLS_APP_DATA_OFFSET: usize = RESTLS_APP_DATA_AUTH_HEADER_LEN; +#[allow(dead_code)] +const RESTLS_APP_DATA_LEN_OFFSET: usize = RESTLS_APP_DATA_MAC_LEN; +#[allow(dead_code)] +const RESTLS_MAX_PLAINTEXT: usize = 16_384; +#[allow(dead_code)] +const RESTLS_RANDOM_RESPONSE_MAGIC: &[u8] = b"restls-random-response"; +#[allow(dead_code)] +const RESTLS_HANDSHAKE_MAC_LEN: usize = 16; +#[allow(dead_code)] +const RESTLS_TLS12_CLIENT_AUTH_LAYOUT_3: [usize; 4] = [0, 11, 22, 32]; +#[allow(dead_code)] +const RESTLS_TLS12_CLIENT_AUTH_LAYOUT_4: [usize; 5] = [0, 8, 16, 24, 32]; const DIRECT_DNS_TIMEOUT: Duration = Duration::from_secs(2); +const DNS_TYPE_HTTPS: u16 = 65; +const HTTPS_SVC_PARAM_ECH: u16 = 5; const FAKE_DNS_BASE: u32 = u32::from_be_bytes([198, 18, 64, 1]); +type Aes192Gcm = AesGcm; +type Aes128Ccm = Ccm; +type Aes192Ccm = Ccm; +type Aes256Ccm = Ccm; + pub struct ProxyRuntimeConfig { pub selected_name: String, pub outbound: ProxyOutbound, @@ -185,8 +369,610 @@ pub struct TrojanOutbound { #[derive(Clone)] pub struct ShadowsocksOutbound { endpoint: ProxyServerEndpoint, - cipher: SsCipherKind, - master_key: Vec, + protocol: ShadowsocksProtocol, + plugin: Option, + kcptun_pool: Option>, + sing_mux: Option, + sing_mux_sessions: Arc>>, + udp_enabled: bool, + udp_over_tcp: bool, + udp_over_tcp_version: u8, +} + +#[derive(Clone)] +enum ShadowsocksProtocol { + None, + Standard { + server_config: Arc, + context: SsSharedContext, + }, + CustomAead { + kind: CustomSsAeadKind, + master_key: Arc<[u8]>, + }, + CustomStream { + kind: CustomSsStreamKind, + key: Arc<[u8]>, + }, + Aead2022AesCcm { + kind: Aead2022AesCcmKind, + user_key: Arc<[u8]>, + identity_keys: Arc<[Arc<[u8]>]>, + }, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +enum ShadowsocksPluginTransport { + SimpleObfs { mode: SimpleObfsMode, host: String }, + WebSocket(ShadowsocksWebSocketTransport), + ShadowTls(ShadowsocksShadowTlsTransport), + Kcptun(ShadowsocksKcptunTransport), + Restls(ShadowsocksRestlsTransport), +} + +#[derive(Debug, Clone, PartialEq, Eq)] +struct ShadowsocksSingMuxTransport { + protocol: ShadowsocksSingMuxProtocol, + max_connections: usize, + min_streams: usize, + max_streams: usize, + padding: bool, + udp: bool, + brutal: Option, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +struct ShadowsocksSingMuxBrutalTransport { + send_bps: u64, + receive_bps: u64, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum ShadowsocksSingMuxProtocol { + H2Mux, + Smux, + Yamux, +} + +impl ShadowsocksSingMuxProtocol { + fn name(self) -> &'static str { + match self { + Self::H2Mux => "h2mux", + Self::Smux => "smux", + Self::Yamux => "yamux", + } + } + + fn wire_id(self) -> u8 { + match self { + Self::H2Mux => SING_MUX_PROTOCOL_H2MUX, + Self::Smux => SING_MUX_PROTOCOL_SMUX, + Self::Yamux => SING_MUX_PROTOCOL_YAMUX, + } + } +} + +#[derive(Clone)] +enum ShadowsocksSingMuxSession { + H2Mux(ShadowsocksH2MuxSession), + Smux(Arc), + Yamux(ShadowsocksYamuxSession), +} + +#[derive(Clone)] +struct ShadowsocksH2MuxSession { + send_request: h2::client::SendRequest, + active_streams: Arc, + closed: Arc, +} + +#[derive(Clone)] +struct ShadowsocksYamuxSession { + open_tx: mpsc::Sender, + active_streams: Arc, + closed: Arc, +} + +struct YamuxCommand { + response: oneshot::Sender>, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum SimpleObfsMode { + Http, + Tls, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +struct ShadowsocksWebSocketTransport { + kind: ShadowsocksWebSocketKind, + host: String, + path: String, + headers: Vec<(String, String)>, + tls: bool, + ech: Option, + skip_cert_verify: bool, + certificate_fingerprint: Option, + client_identity: Option, + mux: ShadowsocksWebSocketMux, + v2ray_http_upgrade: bool, + v2ray_http_upgrade_fast_open: bool, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum ShadowsocksWebSocketKind { + V2ray, + Gost, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum ShadowsocksWebSocketMux { + None, + V2ray, + Gost, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +struct ShadowsocksEchConfig { + config_list: Option>, + query_server_name: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +struct ShadowsocksShadowTlsTransport { + host: String, + password: String, + version: u8, + alpn: Vec, + skip_cert_verify: bool, + certificate_fingerprint: Option, + client_identity: Option, + client_fingerprint: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +struct TlsClientIdentity { + certificate: String, + private_key: String, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +struct ShadowsocksKcptunTransport { + key: String, + crypt: String, + mode: String, + conn: usize, + auto_expire: u64, + scavenge_ttl: u64, + mtu: usize, + rate_limit: u32, + sndwnd: u16, + rcvwnd: u16, + data_shard: usize, + parity_shard: usize, + dscp: u32, + no_comp: bool, + ack_nodelay: bool, + no_delay: i32, + interval: i32, + resend: i32, + no_congestion: bool, + sockbuf: usize, + smux_ver: u8, + smux_buf: usize, + frame_size: usize, + stream_buf: usize, + keep_alive: u64, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +struct ShadowsocksRestlsTransport { + host: String, + password: String, + version_hint: RestlsVersionHint, + script: Vec, + client_fingerprint: RestlsClientFingerprint, + session_tickets_disabled: bool, + secret: [u8; 32], +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum RestlsVersionHint { + Tls12, + Tls13, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum RestlsClientFingerprint { + Chrome, + Firefox, + Safari, + Ios, +} + +impl RestlsClientFingerprint { + #[cfg(feature = "boring-browser-fingerprints")] + fn browser_profile(self) -> MihomoClientFingerprint { + match self { + Self::Chrome => MihomoClientFingerprint::Chrome, + Self::Firefox => MihomoClientFingerprint::Firefox, + Self::Safari => MihomoClientFingerprint::Safari, + Self::Ios => MihomoClientFingerprint::Ios, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum MihomoClientFingerprint { + Chrome, + Firefox, + Safari, + Ios, + Android, + Edge, + Browser360, + Qq, + Random, + Chrome120, + Firefox120, + Safari16, + ChromePsk, + ChromePskShuffle, + ChromePaddingPskShuffle, + ChromePq, + ChromePqPsk, + Randomized, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +struct RestlsScriptLine { + target_len: RestlsTargetLength, + command: RestlsCommand, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +struct RestlsTargetLength { + base: u16, + random_range: u16, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum RestlsCommand { + Noop, + Response(u16), +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[allow(dead_code)] +enum RestlsRecordDirection { + ToServer, + ToClient, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +#[allow(dead_code)] +struct RestlsApplicationFrame { + data: Vec, + command: RestlsCommand, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +#[allow(dead_code)] +struct RestlsWriteResult { + record: Vec, + consumed: usize, + command: RestlsCommand, +} + +#[derive(Debug, Clone)] +#[allow(dead_code)] +struct RestlsApplicationCodec { + secret: [u8; 32], + server_random: [u8; 32], + to_server_counter: u64, + to_client_counter: u64, + tls12_gcm: bool, + tls12_gcm_to_client_disable_counter: bool, + client_finished_auth: Option>, +} + +#[derive(Debug, Clone)] +#[allow(dead_code)] +struct RestlsStreamState { + codec: RestlsApplicationCodec, + script: Vec, + send_buffer: Vec, + write_pending: bool, +} + +#[allow(dead_code)] +struct RestlsStream { + inner: S, + state: RestlsStreamState, + read_stage: RestlsRecordReadStage, + read_buf: Vec, + read_offset: usize, + write_buf: Vec, + write_offset: usize, +} + +#[allow(dead_code)] +enum RestlsRecordReadStage { + Header { + bytes: [u8; TLS_RECORD_HEADER_LEN], + filled: usize, + }, + Payload { + header: [u8; TLS_RECORD_HEADER_LEN], + bytes: Vec, + filled: usize, + }, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +#[allow(dead_code)] +struct RestlsStreamWriteResult { + accepted: usize, + records: Vec>, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +#[allow(dead_code)] +struct RestlsStreamReadResult { + data: Vec, + records: Vec>, + resumed_pending_write: bool, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +#[allow(dead_code)] +struct RestlsServerAuthRecord { + record: Vec, + tls12_gcm_server_disable_counter: bool, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +#[allow(dead_code)] +struct RestlsTls13ClientHelloAuthMaterial { + key_shares: Vec<(u16, Vec)>, + psk_labels: Vec>, +} + +struct KcptunSessionPool { + endpoint: ProxyServerEndpoint, + config: ShadowsocksKcptunTransport, + state: Mutex, +} + +struct KcptunSessionPoolState { + sessions: Vec>, + rr: usize, +} + +struct KcptunTimedSession { + session: Arc, + expiry_at: Option, +} + +impl Default for ShadowsocksKcptunTransport { + fn default() -> Self { + let mut config = Self::empty(); + config.fill_defaults(); + config + } +} + +impl ShadowsocksKcptunTransport { + fn empty() -> Self { + Self { + key: String::new(), + crypt: String::new(), + mode: String::new(), + conn: 0, + auto_expire: 0, + scavenge_ttl: 0, + mtu: 0, + rate_limit: 0, + sndwnd: 0, + rcvwnd: 0, + data_shard: 0, + parity_shard: 0, + dscp: 0, + no_comp: false, + ack_nodelay: false, + no_delay: 0, + interval: 0, + resend: 0, + no_congestion: false, + sockbuf: 0, + smux_ver: 0, + smux_buf: 0, + frame_size: 0, + stream_buf: 0, + keep_alive: 0, + } + } + + fn fill_defaults(&mut self) { + if self.key.is_empty() { + self.key = "it's a secrect".to_owned(); + } + if self.crypt.is_empty() { + self.crypt = "aes".to_owned(); + } + if self.mode.is_empty() { + self.mode = "fast".to_owned(); + } + if self.conn == 0 { + self.conn = 1; + } + if self.scavenge_ttl == 0 { + self.scavenge_ttl = 600; + } + if self.mtu == 0 { + self.mtu = 1350; + } + if self.sndwnd == 0 { + self.sndwnd = 128; + } + if self.rcvwnd == 0 { + self.rcvwnd = 512; + } + if self.data_shard == 0 { + self.data_shard = 10; + } + if self.parity_shard == 0 { + self.parity_shard = 3; + } + if self.interval == 0 { + self.interval = 50; + } + if self.sockbuf == 0 { + self.sockbuf = 4_194_304; + } + if self.smux_ver == 0 { + self.smux_ver = 1; + } + if self.smux_buf == 0 { + self.smux_buf = 4_194_304; + } + if self.frame_size == 0 { + self.frame_size = 8192; + } + if self.stream_buf == 0 { + self.stream_buf = 2_097_152; + } + if self.keep_alive == 0 { + self.keep_alive = 10; + } + match self.mode.trim().to_ascii_lowercase().as_str() { + "normal" => { + self.no_delay = 0; + self.interval = 40; + self.resend = 2; + self.no_congestion = true; + } + "fast" => { + self.no_delay = 0; + self.interval = 30; + self.resend = 2; + self.no_congestion = true; + } + "fast2" => { + self.no_delay = 1; + self.interval = 20; + self.resend = 2; + self.no_congestion = true; + } + "fast3" => { + self.no_delay = 1; + self.interval = 10; + self.resend = 2; + self.no_congestion = true; + } + _ => {} + } + if self.smux_ver > 2 { + self.smux_ver = 2; + } + } +} + +impl KcptunSessionPool { + fn new(endpoint: ProxyServerEndpoint, config: ShadowsocksKcptunTransport) -> Self { + let conn = config.conn.max(1); + Self { + endpoint, + config, + state: Mutex::new(KcptunSessionPoolState { + sessions: std::iter::repeat_with(|| None).take(conn).collect(), + rr: 0, + }), + } + } + + async fn open_stream(&self) -> Result<(Box, SocketAddr)> { + let address = *self + .endpoint + .addresses + .first() + .ok_or_else(|| anyhow!("Shadowsocks kcptun endpoint has no resolved address"))?; + for attempt in 0..2 { + let session = self.session_for_next_slot(address).await?; + match kcptun_open_smux_stream(session.clone()).await { + Ok(stream) => return Ok((stream, address)), + Err(err) if attempt == 0 => { + tracing::debug!( + error = %err, + server = %self.endpoint.host, + port = self.endpoint.port, + "discarding failed Shadowsocks kcptun smux session and retrying" + ); + self.invalidate_session(&session).await; + } + Err(err) => return Err(err), + } + } + unreachable!("kcptun retry loop has fixed attempts") + } + + async fn session_for_next_slot(&self, address: SocketAddr) -> Result> { + let mut state = self.state.lock().await; + if state.sessions.is_empty() { + state.sessions.push(None); + } + let index = state.rr % state.sessions.len(); + state.rr = state.rr.wrapping_add(1); + let now = Instant::now(); + let expired = state.sessions[index] + .as_ref() + .and_then(|session| session.expiry_at) + .is_some_and(|expiry| now >= expiry); + let needs_reconnect = state.sessions[index] + .as_ref() + .map(|session| session.session.is_closed() || expired) + .unwrap_or(true); + if needs_reconnect { + if let Some(previous) = state.sessions[index].take() { + if expired && !previous.session.is_closed() { + schedule_kcptun_session_close(previous.session, self.config.scavenge_ttl); + } + } + let session = kcptun_open_session(&self.endpoint, address, &self.config).await?; + let expiry_at = if self.config.auto_expire == 0 { + None + } else { + Some(Instant::now() + Duration::from_secs(self.config.auto_expire)) + }; + state.sessions[index] = Some(KcptunTimedSession { + session: session.clone(), + expiry_at, + }); + return Ok(session); + } + Ok(state.sessions[index] + .as_ref() + .expect("kcptun session exists when reconnect is not needed") + .session + .clone()) + } + + async fn invalidate_session(&self, session: &Arc) { + let mut state = self.state.lock().await; + let stale = state + .sessions + .iter_mut() + .find(|slot| { + slot.as_ref() + .map(|candidate| Arc::ptr_eq(&candidate.session, session)) + .unwrap_or(false) + }) + .and_then(Option::take); + drop(state); + if let Some(stale) = stale { + let _ = stale.session.close().await; + } + } } #[derive(Clone)] @@ -196,29 +982,87 @@ struct ProxyServerEndpoint { addresses: Arc<[SocketAddr]>, } -#[derive(Debug, Clone, Copy)] -enum SsCipherKind { - Aes128Gcm, - Aes256Gcm, - Chacha20IetfPoly1305, +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum CustomSsAeadKind { + Aes192Gcm, + Aes192Ccm, + Chacha8IetfPoly1305, + XChacha8IetfPoly1305, + Rabbit128Poly1305, + Aegis128L, + Aegis256, + Aez384, + DeoxysII256128, + Ascon128, + Ascon128A, + Lea128Gcm, + Lea192Gcm, + Lea256Gcm, } -impl SsCipherKind { - fn from_name(name: &str) -> Result { - match name.to_ascii_lowercase().as_str() { - "aes-128-gcm" | "aead_aes_128_gcm" => Ok(Self::Aes128Gcm), - "aes-256-gcm" | "aead_aes_256_gcm" => Ok(Self::Aes256Gcm), - "chacha20-ietf-poly1305" | "chacha20-poly1305" | "aead_chacha20_poly1305" => { - Ok(Self::Chacha20IetfPoly1305) - } - other => bail!("unsupported Shadowsocks cipher {other}"), +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum CustomSsStreamKind { + Chacha20, + XChacha20, +} + +impl CustomSsStreamKind { + fn from_name(name: &str) -> Option { + match name.trim().to_ascii_lowercase().as_str() { + "chacha20" => Some(Self::Chacha20), + "xchacha20" => Some(Self::XChacha20), + _ => None, + } + } + + fn name(self) -> &'static str { + match self { + Self::Chacha20 => "chacha20", + Self::XChacha20 => "xchacha20", + } + } + + fn key_len(self) -> usize { + 32 + } + + fn salt_len(self) -> usize { + match self { + Self::Chacha20 => 8, + Self::XChacha20 => 24, + } + } +} + +impl CustomSsAeadKind { + fn from_name(name: &str) -> Option { + match name.trim().to_ascii_lowercase().as_str() { + "aes-192-gcm" => Some(Self::Aes192Gcm), + "aes-192-ccm" => Some(Self::Aes192Ccm), + "chacha8-ietf-poly1305" => Some(Self::Chacha8IetfPoly1305), + "xchacha8-ietf-poly1305" => Some(Self::XChacha8IetfPoly1305), + "rabbit128-poly1305" => Some(Self::Rabbit128Poly1305), + "aegis-128l" => Some(Self::Aegis128L), + "aegis-256" => Some(Self::Aegis256), + "aez-384" => Some(Self::Aez384), + "deoxys-ii-256-128" => Some(Self::DeoxysII256128), + "ascon128" => Some(Self::Ascon128), + "ascon128a" => Some(Self::Ascon128A), + "lea-128-gcm" => Some(Self::Lea128Gcm), + "lea-192-gcm" => Some(Self::Lea192Gcm), + "lea-256-gcm" => Some(Self::Lea256Gcm), + _ => None, } } fn key_len(self) -> usize { match self { - Self::Aes128Gcm => 16, - Self::Aes256Gcm | Self::Chacha20IetfPoly1305 => 32, + Self::Aes192Gcm | Self::Aes192Ccm | Self::Lea192Gcm => 24, + Self::Chacha8IetfPoly1305 | Self::XChacha8IetfPoly1305 => 32, + Self::Rabbit128Poly1305 | Self::Aegis128L | Self::Lea128Gcm => 16, + Self::Aegis256 | Self::DeoxysII256128 | Self::Lea256Gcm => 32, + Self::Aez384 => 48, + Self::Ascon128 | Self::Ascon128A => 16, } } @@ -226,13 +1070,84 @@ impl SsCipherKind { self.key_len() } - fn algorithm(self) -> &'static aead::Algorithm { + fn tag_len(self) -> usize { + 16 + } + + fn nonce_len(self) -> usize { match self { - Self::Aes128Gcm => &aead::AES_128_GCM, - Self::Aes256Gcm => &aead::AES_256_GCM, - Self::Chacha20IetfPoly1305 => &aead::CHACHA20_POLY1305, + Self::Aes192Gcm + | Self::Aes192Ccm + | Self::Lea128Gcm + | Self::Lea192Gcm + | Self::Lea256Gcm => 12, + Self::Chacha8IetfPoly1305 => 12, + Self::XChacha8IetfPoly1305 => 24, + Self::Rabbit128Poly1305 => 8, + Self::Aegis128L => 16, + Self::Aegis256 => 32, + Self::Aez384 => 16, + Self::DeoxysII256128 => 15, + Self::Ascon128 | Self::Ascon128A => 16, } } + + fn name(self) -> &'static str { + match self { + Self::Aes192Gcm => "aes-192-gcm", + Self::Aes192Ccm => "aes-192-ccm", + Self::Chacha8IetfPoly1305 => "chacha8-ietf-poly1305", + Self::XChacha8IetfPoly1305 => "xchacha8-ietf-poly1305", + Self::Rabbit128Poly1305 => "rabbit128-poly1305", + Self::Aegis128L => "aegis-128l", + Self::Aegis256 => "aegis-256", + Self::Aez384 => "aez-384", + Self::DeoxysII256128 => "deoxys-ii-256-128", + Self::Ascon128 => "ascon128", + Self::Ascon128A => "ascon128a", + Self::Lea128Gcm => "lea-128-gcm", + Self::Lea192Gcm => "lea-192-gcm", + Self::Lea256Gcm => "lea-256-gcm", + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum Aead2022AesCcmKind { + Aes128, + Aes256, +} + +impl Aead2022AesCcmKind { + fn from_name(name: &str) -> Option { + match name.trim().to_ascii_lowercase().as_str() { + "2022-blake3-aes-128-ccm" => Some(Self::Aes128), + "2022-blake3-aes-256-ccm" => Some(Self::Aes256), + _ => None, + } + } + + fn name(self) -> &'static str { + match self { + Self::Aes128 => "2022-blake3-aes-128-ccm", + Self::Aes256 => "2022-blake3-aes-256-ccm", + } + } + + fn key_len(self) -> usize { + match self { + Self::Aes128 => 16, + Self::Aes256 => 32, + } + } + + fn tag_len(self) -> usize { + 16 + } + + fn nonce_len(self) -> usize { + 12 + } } #[derive(Debug, Clone, PartialEq, Eq)] @@ -370,19 +1285,16 @@ fn validate_runtime_supported_node(node: &ProxyNode) -> Result<()> { } } ProxyNodeConfig::Shadowsocks(config) => { - if config.plugin.is_some() { - bail!( - "Shadowsocks node {} uses plugin transport; plugin transports are not supported by the packet runtime", - node.name - ); - } + shadowsocks_plugin_transport( + config.plugin.as_ref(), + config.client_fingerprint.as_deref(), + )?; if config.udp_over_tcp { - bail!( - "Shadowsocks node {} uses udp-over-tcp; native UDP relay is used by the packet runtime", - node.name - ); + normalize_uot_version(config.udp_over_tcp_version)?; } - SsCipherKind::from_name(&config.cipher)?; + validate_shadowsocks_cipher(&config.cipher)?; + let protocol = shadowsocks_protocol_for_node(node, config)?; + shadowsocks_sing_mux_transport(config.smux.as_ref(), config, &protocol)?; } } Ok(()) @@ -1012,7 +1924,7 @@ impl TrojanOutbound { let tls = match connector.connect(&self.sni, tcp).await { Ok(tls) => { if let Some(fingerprint) = self.certificate_fingerprint.as_ref() { - verify_native_tls_peer_certificate(&tls, fingerprint)?; + verify_native_tls_peer_certificate(&tls, fingerprint, "Trojan")?; } let negotiated_alpn = tls .get_ref() @@ -1158,22 +2070,85 @@ fn trojan_alpn(config: &TrojanNode) -> Vec { } } +async fn bridge_streams(inbound: StackTcpStream, stream: S) -> Result<()> +where + S: AsyncRead + AsyncWrite + Unpin + Send + 'static, +{ + let (mut inbound_reader, mut inbound_writer) = split(inbound); + let (mut stream_reader, mut stream_writer) = split(stream); + let mut upload = tokio::spawn(async move { + let mut buf = vec![0u8; 16 * 1024]; + loop { + let n = inbound_reader + .read(&mut buf) + .await + .context("failed to read tcp payload from proxy netstack")?; + if n == 0 { + break; + } + stream_writer.write_all(&buf[..n]).await?; + } + stream_writer + .shutdown() + .await + .context("failed to shutdown proxy tcp upload")?; + Result::<()>::Ok(()) + }); + + let mut download = tokio::spawn(async move { + let mut buf = vec![0u8; 16 * 1024]; + loop { + let n = stream_reader + .read(&mut buf) + .await + .context("failed to read proxy tcp payload")?; + if n == 0 { + break; + } + inbound_writer + .write_all(&buf[..n]) + .await + .context("failed to write tcp payload to proxy netstack")?; + } + inbound_writer + .shutdown() + .await + .context("failed to shutdown proxy netstack tcp download")?; + Result::<()>::Ok(()) + }); + + tokio::select! { + result = &mut upload => { + download.abort(); + result??; + } + result = &mut download => { + upload.abort(); + result??; + } + } + Ok(()) +} + impl ShadowsocksOutbound { fn from_node(node: &ProxyNode, config: &ShadowsocksNode) -> Result { - if config.plugin.is_some() { - bail!( - "Shadowsocks node {} uses plugin transport; plugin transports are not supported by the packet runtime", + let plugin = shadowsocks_plugin_transport( + config.plugin.as_ref(), + config.client_fingerprint.as_deref(), + ) + .with_context(|| { + format!( + "Shadowsocks node {} uses an unsupported plugin transport", node.name - ); - } - if config.udp_over_tcp { - bail!( - "Shadowsocks node {} uses udp-over-tcp; native UDP relay is used by the packet runtime", - node.name - ); - } - let cipher = SsCipherKind::from_name(&config.cipher)?; - let master_key = evp_bytes_to_key(config.password.as_bytes(), cipher.key_len()); + ) + })?; + let force_udp_over_tcp = matches!(plugin, Some(ShadowsocksPluginTransport::Kcptun(_))); + let udp_over_tcp = config.udp_over_tcp || force_udp_over_tcp; + let udp_over_tcp_version = if udp_over_tcp { + normalize_uot_version(config.udp_over_tcp_version)? + } else { + UOT_LEGACY_VERSION + }; let endpoint = ProxyServerEndpoint::resolve(&node.server, node.port).with_context(|| { format!( @@ -1181,36 +2156,323 @@ impl ShadowsocksOutbound { node.name ) })?; + let protocol = shadowsocks_protocol_for_node(node, config)?; + let sing_mux = shadowsocks_sing_mux_transport(config.smux.as_ref(), config, &protocol) + .with_context(|| { + format!( + "Shadowsocks node {} uses an unsupported top-level sing-mux mode", + node.name + ) + })?; + let kcptun_pool = match plugin.as_ref() { + Some(ShadowsocksPluginTransport::Kcptun(config)) => Some(Arc::new( + KcptunSessionPool::new(endpoint.clone(), config.clone()), + )), + _ => None, + }; tracing::info!( node = %node.name, server = %endpoint.host, port = endpoint.port, cipher = %config.cipher, + udp = config.udp, + udp_over_tcp, + udp_over_tcp_version, + plugin = ?plugin, + sing_mux = ?sing_mux, resolved_addresses = ?endpoint.addresses, "configured Shadowsocks proxy outbound" ); - Ok(Self { endpoint, cipher, master_key }) + Ok(Self { + endpoint, + protocol, + plugin, + kcptun_pool, + udp_enabled: config.udp + || udp_over_tcp + || sing_mux + .as_ref() + .map(|sing_mux| sing_mux.udp) + .unwrap_or(false), + sing_mux, + sing_mux_sessions: Arc::new(Mutex::new(Vec::new())), + udp_over_tcp, + udp_over_tcp_version, + }) } - async fn bridge_tcp(&self, inbound: StackTcpStream, target: ProxyTcpTarget) -> Result<()> { - let remote_addr = target.address; - let (tcp, _) = connect_proxy_tcp(&self.endpoint).await.with_context(|| { + async fn connect_tcp(&self) -> Result<(Box, SocketAddr)> { + if let Some(pool) = self.kcptun_pool.as_ref() { + return pool.open_stream().await; + } + let (tcp, address) = connect_proxy_tcp(&self.endpoint).await.with_context(|| { format!( "failed to connect Shadowsocks server {}:{}", self.endpoint.host, self.endpoint.port ) })?; - let (reader, writer) = tcp.into_split(); - let mut ss_reader = SsReader::new(reader, self.cipher, self.master_key.clone()); - let mut ss_writer = SsWriter::new(writer, self.cipher, self.master_key.clone()).await?; + let stream: Box = match self.plugin.as_ref() { + Some(ShadowsocksPluginTransport::SimpleObfs { mode, host }) => Box::new( + SimpleObfsStream::new(tcp, *mode, host.clone(), self.endpoint.port), + ), + Some(ShadowsocksPluginTransport::WebSocket(config)) => { + let stream = + websocket_plugin_connect(Box::new(tcp), config, self.endpoint.port).await?; + match config.mux { + ShadowsocksWebSocketMux::None => stream, + ShadowsocksWebSocketMux::V2ray => Box::new(V2rayMuxStream::new(stream)), + ShadowsocksWebSocketMux::Gost => gost_smux_stream(stream).await?, + } + } + Some(ShadowsocksPluginTransport::ShadowTls(config)) => { + shadow_tls_connect(Box::new(tcp), config).await? + } + Some(ShadowsocksPluginTransport::Restls(config)) => { + restls_connect(Box::new(tcp), config).await? + } + Some(ShadowsocksPluginTransport::Kcptun(_)) => { + bail!("internal Shadowsocks kcptun transport dispatch error") + } + None => Box::new(tcp), + }; + Ok((stream, address)) + } + + async fn connect_tcp_to_target( + &self, + target: &ProxyTcpTarget, + ) -> Result<(Box, SocketAddr)> { + let (mut tcp, address) = self.connect_tcp().await?; + match &self.protocol { + ShadowsocksProtocol::None => { + let socks_target = socks_addr_for_target(target)?; + tcp.write_all(&socks_target).await.with_context(|| { + format!( + "failed to write Shadowsocks none target address for {}", + target.address + ) + })?; + tcp.flush() + .await + .context("failed to flush Shadowsocks none target address")?; + Ok((tcp, address)) + } + ShadowsocksProtocol::Standard { server_config, context } => { + let stream = SsProxyClientStream::from_stream( + context.clone(), + tcp, + server_config, + shadowsocks_addr_for_target(target)?, + ); + Ok((Box::new(stream), address)) + } + ShadowsocksProtocol::CustomAead { kind, master_key } => { + let stream = + custom_aead_plain_tcp_stream(tcp, target, *kind, master_key.clone()).await?; + Ok((stream, address)) + } + ShadowsocksProtocol::CustomStream { kind, key } => { + let stream = + custom_stream_plain_tcp_stream(tcp, target, *kind, key.clone()).await?; + Ok((stream, address)) + } + ShadowsocksProtocol::Aead2022AesCcm { kind, user_key, identity_keys } => { + let stream = aead2022_aes_ccm_plain_tcp_stream( + tcp, + target, + *kind, + user_key.clone(), + identity_keys.clone(), + ) + .await?; + Ok((stream, address)) + } + } + } + + async fn open_sing_mux_tcp_stream(&self, target: &ProxyTcpTarget) -> Result> { + let session = self.sing_mux_session().await?; + let mut stream = session.open_stream().await?; + write_sing_mux_tcp_request(&mut stream, target).await?; + Ok(Box::new(SingMuxTcpStream::new(stream))) + } + + async fn sing_mux_session(&self) -> Result { + let config = self + .sing_mux + .as_ref() + .context("Shadowsocks sing-mux is not configured")?; + let mut sessions = self.sing_mux_sessions.lock().await; + sessions.retain(|session| !session.is_closed()); + + if config.brutal.is_some() { + if let Some(session) = sessions.first() { + return Ok(session.clone()); + } + let session = self.connect_sing_mux_session().await?; + sessions.push(session.clone()); + return Ok(session); + } + + let mut best = None; + for (index, session) in sessions.iter().enumerate() { + let streams = session.num_streams().await; + match best { + None => best = Some((index, streams)), + Some((_, current)) if streams < current => best = Some((index, streams)), + _ => {} + } + } + + if let Some((index, streams)) = best { + if streams == 0 + || (config.max_connections > 0 + && (sessions.len() >= config.max_connections || streams < config.min_streams)) + || (config.max_connections == 0 + && config.max_streams > 0 + && streams < config.max_streams) + { + return Ok(sessions[index].clone()); + } + } + + let session = self.connect_sing_mux_session().await?; + sessions.push(session.clone()); + Ok(session) + } + + async fn connect_sing_mux_session(&self) -> Result { + let target = ProxyTcpTarget { + address: SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), SING_MUX_PORT), + hostname: Some(SING_MUX_ADDRESS.to_owned()), + }; + let sing_mux = self + .sing_mux + .as_ref() + .context("Shadowsocks sing-mux session requested without sing-mux config")?; + let (mut stream, address) = self.connect_tcp_to_target(&target).await?; + write_sing_mux_session_preface(&mut stream, sing_mux.protocol, sing_mux.padding).await?; + let stream = if sing_mux.padding { + sing_mux_padding_stream(stream) + } else { + stream + }; + + let session = match sing_mux.protocol { + ShadowsocksSingMuxProtocol::H2Mux => start_sing_mux_h2mux_session(stream).await?, + ShadowsocksSingMuxProtocol::Smux => { + let mut config = smux_rust::Config::default(); + config.keep_alive_disabled = true; + let session = + smux_rust::client(Box::new(ProxyIoAdapter::new(stream)), Some(config)) + .await + .map_err(|err| { + anyhow!("failed to start Shadowsocks sing-mux smux session: {err}") + })?; + ShadowsocksSingMuxSession::Smux(session) + } + ShadowsocksSingMuxProtocol::Yamux => start_sing_mux_yamux_session(stream).await?, + }; + if let Some(brutal) = sing_mux.brutal { + sing_mux_brutal_exchange(&session, brutal).await?; + } + tracing::debug!( + %address, + mux_host = SING_MUX_ADDRESS, + mux_port = SING_MUX_PORT, + protocol = sing_mux.protocol.name(), + brutal = sing_mux.brutal.is_some(), + "opened Shadowsocks sing-mux session" + ); + Ok(session) + } + + async fn bridge_tcp(&self, inbound: StackTcpStream, target: ProxyTcpTarget) -> Result<()> { + if self.sing_mux.is_some() { + let stream = self.open_sing_mux_tcp_stream(&target).await?; + return bridge_streams(inbound, stream).await; + } + let (tcp, _) = self.connect_tcp().await?; + match &self.protocol { + ShadowsocksProtocol::None => self.bridge_none_tcp(inbound, tcp, target).await, + ShadowsocksProtocol::Standard { server_config, context } => { + let stream = SsProxyClientStream::from_stream( + context.clone(), + tcp, + server_config, + shadowsocks_addr_for_target(&target)?, + ); + bridge_streams(inbound, stream).await + } + ShadowsocksProtocol::CustomAead { kind, master_key } => { + self.bridge_custom_aead_tcp(inbound, tcp, target, *kind, master_key.clone()) + .await + } + ShadowsocksProtocol::CustomStream { kind, key } => { + self.bridge_custom_stream_tcp(inbound, tcp, target, *kind, key.clone()) + .await + } + ShadowsocksProtocol::Aead2022AesCcm { kind, user_key, identity_keys } => { + self.bridge_aead2022_aes_ccm_tcp( + inbound, + tcp, + target, + *kind, + user_key.clone(), + identity_keys.clone(), + ) + .await + } + } + } + + async fn bridge_none_tcp( + &self, + inbound: StackTcpStream, + mut tcp: S, + target: ProxyTcpTarget, + ) -> Result<()> + where + S: AsyncRead + AsyncWrite + Unpin + Send + 'static, + { + let remote_addr = target.address; + let socks_target = socks_addr_for_target(&target)?; + tcp.write_all(&socks_target).await.with_context(|| { + format!("failed to write Shadowsocks none target address for {remote_addr}") + })?; + tcp.flush() + .await + .context("failed to flush Shadowsocks none target address")?; + bridge_streams(inbound, tcp).await + } + + async fn bridge_custom_aead_tcp( + &self, + inbound: StackTcpStream, + tcp: S, + target: ProxyTcpTarget, + kind: CustomSsAeadKind, + master_key: Arc<[u8]>, + ) -> Result<()> + where + S: AsyncRead + AsyncWrite + Unpin + Send + 'static, + { + let remote_addr = target.address; + let (reader, writer) = split(tcp); + let mut ss_reader = CustomSsAeadReader::new(reader, kind, master_key.clone()); + let mut ss_writer = CustomSsAeadWriter::new(writer, kind, master_key) + .await + .with_context(|| format!("failed to initialize {} tcp stream", kind.name()))?; let socks_target = socks_addr_for_target(&target)?; ss_writer .write_chunk(&socks_target) .await .with_context(|| { - format!("failed to write Shadowsocks target address for {remote_addr}") + format!( + "failed to write Shadowsocks target address for {remote_addr} with {}", + kind.name() + ) })?; - let (mut inbound_reader, mut inbound_writer) = split(inbound); let mut upload = tokio::spawn(async move { let mut buf = vec![0u8; 16 * 1024]; @@ -1224,7 +2486,10 @@ impl ShadowsocksOutbound { } ss_writer.write_chunk(&buf[..n]).await?; } - ss_writer.shutdown().await?; + ss_writer + .shutdown() + .await + .context("failed to shutdown Shadowsocks tcp upload")?; Result::<()>::Ok(()) }); @@ -1243,7 +2508,172 @@ impl ShadowsocksOutbound { Err(err) => return Err(err), } } - inbound_writer.shutdown().await?; + inbound_writer + .shutdown() + .await + .context("failed to shutdown proxy netstack tcp download")?; + Result::<()>::Ok(()) + }); + + tokio::select! { + result = &mut upload => { + download.abort(); + result??; + } + result = &mut download => { + upload.abort(); + result??; + } + } + Ok(()) + } + + async fn bridge_custom_stream_tcp( + &self, + inbound: StackTcpStream, + tcp: S, + target: ProxyTcpTarget, + kind: CustomSsStreamKind, + key: Arc<[u8]>, + ) -> Result<()> + where + S: AsyncRead + AsyncWrite + Unpin + Send + 'static, + { + let remote_addr = target.address; + let (reader, writer) = split(tcp); + let mut ss_reader = CustomSsStreamReader::new(reader, kind, key.clone()); + let mut ss_writer = CustomSsStreamWriter::new(writer, kind, key) + .await + .with_context(|| format!("failed to initialize {} tcp stream", kind.name()))?; + let socks_target = socks_addr_for_target(&target)?; + ss_writer + .write_all_encrypted(&socks_target) + .await + .with_context(|| { + format!( + "failed to write Shadowsocks target address for {remote_addr} with {}", + kind.name() + ) + })?; + let (mut inbound_reader, mut inbound_writer) = split(inbound); + let mut upload = tokio::spawn(async move { + let mut buf = vec![0u8; 16 * 1024]; + loop { + let n = inbound_reader + .read(&mut buf) + .await + .context("failed to read tcp payload from proxy netstack")?; + if n == 0 { + break; + } + ss_writer.write_all_encrypted(&buf[..n]).await?; + } + ss_writer + .shutdown() + .await + .context("failed to shutdown Shadowsocks tcp upload")?; + Result::<()>::Ok(()) + }); + + let mut download = tokio::spawn(async move { + let mut buf = vec![0u8; 16 * 1024]; + loop { + let n = ss_reader.read_decrypted(&mut buf).await?; + if n == 0 { + break; + } + inbound_writer + .write_all(&buf[..n]) + .await + .context("failed to write tcp payload to proxy netstack")?; + } + inbound_writer + .shutdown() + .await + .context("failed to shutdown proxy netstack tcp download")?; + Result::<()>::Ok(()) + }); + + tokio::select! { + result = &mut upload => { + download.abort(); + result??; + } + result = &mut download => { + upload.abort(); + result??; + } + } + Ok(()) + } + + async fn bridge_aead2022_aes_ccm_tcp( + &self, + inbound: StackTcpStream, + tcp: S, + target: ProxyTcpTarget, + kind: Aead2022AesCcmKind, + user_key: Arc<[u8]>, + identity_keys: Arc<[Arc<[u8]>]>, + ) -> Result<()> + where + S: AsyncRead + AsyncWrite + Unpin + Send + 'static, + { + let remote_addr = target.address; + let (reader, writer) = split(tcp); + let mut ss_writer = + Aead2022AesCcmWriter::new(writer, kind, user_key.clone(), identity_keys) + .await + .with_context(|| format!("failed to initialize {} tcp stream", kind.name()))?; + let request_salt = ss_writer + .write_request(&socks_addr_for_target(&target)?, 1) + .await + .with_context(|| { + format!( + "failed to write Shadowsocks 2022 target address for {remote_addr} with {}", + kind.name() + ) + })?; + let mut ss_reader = Aead2022AesCcmReader::new(reader, kind, user_key, request_salt); + let (mut inbound_reader, mut inbound_writer) = split(inbound); + let mut upload = tokio::spawn(async move { + let mut buf = vec![0u8; 16 * 1024]; + loop { + let n = inbound_reader + .read(&mut buf) + .await + .context("failed to read tcp payload from proxy netstack")?; + if n == 0 { + break; + } + ss_writer.write_chunk(&buf[..n]).await?; + } + ss_writer + .shutdown() + .await + .context("failed to shutdown Shadowsocks 2022 tcp upload")?; + Result::<()>::Ok(()) + }); + + let mut download = tokio::spawn(async move { + let mut buf = Vec::new(); + loop { + buf.clear(); + match ss_reader.read_chunk(&mut buf).await { + Ok(0) => break, + Ok(_) => { + inbound_writer + .write_all(&buf) + .await + .context("failed to write tcp payload to proxy netstack")?; + } + Err(err) => return Err(err), + } + } + inbound_writer + .shutdown() + .await + .context("failed to shutdown proxy netstack tcp download")?; Result::<()>::Ok(()) }); @@ -1263,9 +2693,27 @@ impl ShadowsocksOutbound { async fn run_udp_session( &self, key: UdpFlowKey, - mut outbound_rx: mpsc::Receiver>, + outbound_rx: mpsc::Receiver>, reply_tx: mpsc::Sender, ) -> Result<()> { + if self + .sing_mux + .as_ref() + .map(|sing_mux| sing_mux.udp) + .unwrap_or(false) + { + return self + .run_sing_mux_udp_session(key, outbound_rx, reply_tx) + .await; + } + if self.udp_over_tcp { + return self + .run_udp_over_tcp_session(key, outbound_rx, reply_tx) + .await; + } + if !self.udp_enabled { + bail!("Shadowsocks UDP is disabled for this node"); + } let (socket, server) = connect_proxy_udp(&self.endpoint).await.with_context(|| { format!( "failed to connect Shadowsocks udp socket to {}:{}", @@ -1275,23 +2723,404 @@ impl ShadowsocksOutbound { socket.connect(server).await.with_context(|| { format!("failed to connect udp socket to Shadowsocks server {server}") })?; + match &self.protocol { + ShadowsocksProtocol::None => { + self.run_none_udp_loop(socket, server, key, outbound_rx, reply_tx) + .await + } + ShadowsocksProtocol::Standard { server_config, context } => { + let socket = SsProxySocket::from_socket( + SsUdpSocketType::Client, + context.clone(), + server_config, + BurrowDatagramSocket(socket), + ); + self.run_standard_udp_loop(socket, server, key, outbound_rx, reply_tx) + .await + } + ShadowsocksProtocol::CustomAead { kind, master_key } => { + self.run_custom_aead_udp_loop( + socket, + server, + key, + outbound_rx, + reply_tx, + *kind, + master_key.clone(), + ) + .await + } + ShadowsocksProtocol::CustomStream { kind, key: stream_key } => { + self.run_custom_stream_udp_loop( + socket, + server, + key, + outbound_rx, + reply_tx, + *kind, + stream_key.clone(), + ) + .await + } + ShadowsocksProtocol::Aead2022AesCcm { kind, user_key, identity_keys } => { + self.run_aead2022_aes_ccm_udp_loop( + socket, + server, + key, + outbound_rx, + reply_tx, + *kind, + user_key.clone(), + identity_keys.clone(), + ) + .await + } + } + } + + async fn run_sing_mux_udp_session( + &self, + key: UdpFlowKey, + mut outbound_rx: mpsc::Receiver>, + reply_tx: mpsc::Sender, + ) -> Result<()> { + let session = self.sing_mux_session().await?; + let mut stream = session.open_stream().await?; + let mut request_written = false; + let mut response_read = false; + loop { + tokio::select! { + maybe_payload = outbound_rx.recv() => match maybe_payload { + Some(payload) => { + if !request_written { + write_sing_mux_udp_request(&mut stream, key.remote, &payload).await?; + request_written = true; + } else { + write_sing_mux_udp_payload(&mut stream, &payload).await?; + } + } + None => break, + }, + recv = timeout(UDP_IDLE_TIMEOUT, read_sing_mux_udp_payload(&mut stream, &mut response_read)), if request_written => match recv { + Ok(Ok(payload)) => { + reply_tx + .send(UdpReply { + payload, + source: key.remote, + destination: key.local, + }) + .await + .context("failed to enqueue Shadowsocks sing-mux udp reply")?; + } + Ok(Err(err)) => return Err(err).context("failed to receive Shadowsocks sing-mux udp response"), + Err(_) => break, + } + } + } + Ok(()) + } + + async fn run_udp_over_tcp_session( + &self, + key: UdpFlowKey, + outbound_rx: mpsc::Receiver>, + reply_tx: mpsc::Sender, + ) -> Result<()> { + let (tcp, server) = self.connect_tcp().await.with_context(|| { + format!( + "failed to connect Shadowsocks tcp socket to {}:{} for udp-over-tcp", + self.endpoint.host, self.endpoint.port + ) + })?; + match &self.protocol { + ShadowsocksProtocol::None => { + let mut stream = tcp; + let uot_target = socks_domain_addr(uot_magic_domain(self.udp_over_tcp_version), 0)?; + stream.write_all(&uot_target).await.with_context(|| { + "failed to write Shadowsocks none udp-over-tcp target".to_owned() + })?; + stream + .flush() + .await + .context("failed to flush Shadowsocks none udp-over-tcp target")?; + self.run_standard_uot_loop( + stream, + server, + key, + outbound_rx, + reply_tx, + self.udp_over_tcp_version, + ) + .await + } + ShadowsocksProtocol::Standard { server_config, context } => { + let stream = SsProxyClientStream::from_stream( + context.clone(), + tcp, + server_config, + uot_magic_shadowsocks_addr(self.udp_over_tcp_version), + ); + self.run_standard_uot_loop( + stream, + server, + key, + outbound_rx, + reply_tx, + self.udp_over_tcp_version, + ) + .await + } + ShadowsocksProtocol::CustomAead { kind, master_key } => { + let (reader, writer) = split(tcp); + let mut ss_writer = CustomSsAeadWriter::new(writer, *kind, master_key.clone()) + .await + .with_context(|| { + format!("failed to initialize {} udp-over-tcp stream", kind.name()) + })?; + let uot_target = socks_domain_addr(uot_magic_domain(self.udp_over_tcp_version), 0)?; + ss_writer.write_chunk(&uot_target).await.with_context(|| { + format!( + "failed to write Shadowsocks udp-over-tcp target for {}", + kind.name() + ) + })?; + let ss_reader = CustomSsAeadReader::new(reader, *kind, master_key.clone()); + self.run_custom_aead_uot_loop( + CustomSsAeadByteReader::new(ss_reader), + ss_writer, + server, + key, + outbound_rx, + reply_tx, + self.udp_over_tcp_version, + ) + .await + } + ShadowsocksProtocol::CustomStream { kind, key: stream_key } => { + let (reader, writer) = split(tcp); + let mut ss_writer = CustomSsStreamWriter::new(writer, *kind, stream_key.clone()) + .await + .with_context(|| { + format!("failed to initialize {} udp-over-tcp stream", kind.name()) + })?; + let uot_target = socks_domain_addr(uot_magic_domain(self.udp_over_tcp_version), 0)?; + ss_writer + .write_all_encrypted(&uot_target) + .await + .with_context(|| { + format!( + "failed to write Shadowsocks udp-over-tcp target for {}", + kind.name() + ) + })?; + let ss_reader = CustomSsStreamReader::new(reader, *kind, stream_key.clone()); + self.run_custom_stream_uot_loop( + CustomSsStreamByteReader::new(ss_reader), + ss_writer, + server, + key, + outbound_rx, + reply_tx, + self.udp_over_tcp_version, + ) + .await + } + ShadowsocksProtocol::Aead2022AesCcm { kind, user_key, identity_keys } => { + let (reader, writer) = split(tcp); + let mut ss_writer = Aead2022AesCcmWriter::new( + writer, + *kind, + user_key.clone(), + identity_keys.clone(), + ) + .await + .with_context(|| { + format!("failed to initialize {} udp-over-tcp stream", kind.name()) + })?; + let uot_target = socks_domain_addr(uot_magic_domain(self.udp_over_tcp_version), 0)?; + let request_salt = + ss_writer + .write_request(&uot_target, 1) + .await + .with_context(|| { + format!( + "failed to write Shadowsocks 2022 udp-over-tcp target for {}", + kind.name() + ) + })?; + let ss_reader = + Aead2022AesCcmReader::new(reader, *kind, user_key.clone(), request_salt); + self.run_aead2022_aes_ccm_uot_loop( + Aead2022AesCcmByteReader::new(ss_reader), + ss_writer, + server, + key, + outbound_rx, + reply_tx, + self.udp_over_tcp_version, + ) + .await + } + } + } + + async fn run_none_udp_loop( + &self, + socket: UdpSocket, + server: SocketAddr, + key: UdpFlowKey, + mut outbound_rx: mpsc::Receiver>, + reply_tx: mpsc::Sender, + ) -> Result<()> { + let remote = socks_addr(key.remote)?; let mut buf = vec![0u8; 65_535]; loop { tokio::select! { maybe_payload = outbound_rx.recv() => match maybe_payload { Some(payload) => { - let packet = encrypt_ss_udp_packet(self.cipher, &self.master_key, key.remote, &payload)?; + let mut packet = Vec::with_capacity(remote.len() + payload.len()); + packet.extend_from_slice(&remote); + packet.extend_from_slice(&payload); socket .send(&packet) .await + .with_context(|| format!("failed to send Shadowsocks none udp payload to {server}"))?; + } + None => break, + }, + recv = timeout(UDP_IDLE_TIMEOUT, socket.recv(&mut buf)) => match recv { + Ok(Ok(packet_len)) => { + let (source, used) = parse_socks_addr(&buf[..packet_len]) + .context("failed to parse Shadowsocks none udp response source")?; + reply_tx + .send(UdpReply { + payload: buf[used..packet_len].to_vec(), + source, + destination: key.local, + }) + .await + .context("failed to enqueue Shadowsocks none udp reply")?; + } + Ok(Err(err)) => return Err(err).context("failed to receive Shadowsocks none udp response"), + Err(_) => break, + }, + } + } + Ok(()) + } + + async fn run_standard_udp_loop( + &self, + socket: SsProxySocket, + server: SocketAddr, + key: UdpFlowKey, + mut outbound_rx: mpsc::Receiver>, + reply_tx: mpsc::Sender, + ) -> Result<()> { + let remote = SsAddress::SocketAddress(key.remote); + let mut buf = vec![0u8; 65_535]; + loop { + tokio::select! { + maybe_payload = outbound_rx.recv() => match maybe_payload { + Some(payload) => { + socket + .send(&remote, &payload) + .await .with_context(|| format!("failed to send Shadowsocks udp payload to {server}"))?; } None => break, }, + recv = timeout(UDP_IDLE_TIMEOUT, socket.recv(&mut buf)) => match recv { + Ok(Ok((payload_len, source, _packet_len))) => { + let source = shadowsocks_addr_to_socket_addr(source)?; + reply_tx + .send(UdpReply { + payload: buf[..payload_len].to_vec(), + source, + destination: key.local, + }) + .await + .context("failed to enqueue Shadowsocks udp reply")?; + } + Ok(Err(err)) => return Err(err).context("failed to receive Shadowsocks udp response"), + Err(_) => break, + } + } + } + Ok(()) + } + + async fn run_standard_uot_loop( + &self, + mut stream: S, + server: SocketAddr, + key: UdpFlowKey, + mut outbound_rx: mpsc::Receiver>, + reply_tx: mpsc::Sender, + version: u8, + ) -> Result<()> + where + S: AsyncRead + AsyncWrite + Unpin, + { + if version == UOT_VERSION { + write_uot_request(&mut stream, key.remote).await?; + } + loop { + tokio::select! { + maybe_payload = outbound_rx.recv() => match maybe_payload { + Some(payload) => { + write_uot_frame(&mut stream, key.remote, &payload) + .await + .with_context(|| format!("failed to send Shadowsocks udp-over-tcp payload to {server}"))?; + } + None => break, + }, + recv = timeout(UDP_IDLE_TIMEOUT, read_uot_frame(&mut stream)) => match recv { + Ok(Ok((source, payload))) => { + reply_tx + .send(UdpReply { + payload, + source, + destination: key.local, + }) + .await + .context("failed to enqueue Shadowsocks udp-over-tcp reply")?; + } + Ok(Err(err)) => return Err(err).context("failed to receive Shadowsocks udp-over-tcp response"), + Err(_) => break, + } + } + } + Ok(()) + } + + async fn run_custom_aead_udp_loop( + &self, + socket: UdpSocket, + server: SocketAddr, + key: UdpFlowKey, + mut outbound_rx: mpsc::Receiver>, + reply_tx: mpsc::Sender, + kind: CustomSsAeadKind, + master_key: Arc<[u8]>, + ) -> Result<()> { + let mut buf = vec![0u8; 65_535]; + loop { + tokio::select! { + maybe_payload = outbound_rx.recv() => match maybe_payload { + Some(payload) => { + let packet = encrypt_custom_ss_udp_packet(kind, &master_key, key.remote, &payload)?; + socket + .send(&packet) + .await + .with_context(|| format!("failed to send {} udp payload to {server}", kind.name()))?; + } + None => break, + }, recv = timeout(UDP_IDLE_TIMEOUT, socket.recv(&mut buf)) => match recv { Ok(Ok(len)) => { let (source, payload) = - decrypt_ss_udp_packet(self.cipher, &self.master_key, &buf[..len])?; + decrypt_custom_ss_udp_packet(kind, &master_key, &buf[..len])?; reply_tx .send(UdpReply { payload, @@ -1308,30 +3137,3919 @@ impl ShadowsocksOutbound { } Ok(()) } + + async fn run_custom_aead_uot_loop( + &self, + mut reader: CustomSsAeadByteReader, + mut writer: CustomSsAeadWriter, + server: SocketAddr, + key: UdpFlowKey, + mut outbound_rx: mpsc::Receiver>, + reply_tx: mpsc::Sender, + version: u8, + ) -> Result<()> + where + W: AsyncWrite + Unpin, + R: AsyncRead + Unpin, + { + if version == UOT_VERSION { + writer.write_chunk(&uot_request(key.remote)?).await?; + } + loop { + tokio::select! { + maybe_payload = outbound_rx.recv() => match maybe_payload { + Some(payload) => { + let frame = uot_frame(key.remote, &payload)?; + writer.write_chunk(&frame) + .await + .with_context(|| format!("failed to send custom Shadowsocks udp-over-tcp payload to {server}"))?; + } + None => break, + }, + recv = timeout(UDP_IDLE_TIMEOUT, reader.read_frame()) => match recv { + Ok(Ok(Some((source, payload)))) => { + reply_tx + .send(UdpReply { + payload, + source, + destination: key.local, + }) + .await + .context("failed to enqueue custom Shadowsocks udp-over-tcp reply")?; + } + Ok(Ok(None)) => break, + Ok(Err(err)) => return Err(err).context("failed to receive custom Shadowsocks udp-over-tcp response"), + Err(_) => break, + } + } + } + Ok(()) + } + + async fn run_custom_stream_udp_loop( + &self, + socket: UdpSocket, + server: SocketAddr, + key: UdpFlowKey, + mut outbound_rx: mpsc::Receiver>, + reply_tx: mpsc::Sender, + kind: CustomSsStreamKind, + stream_key: Arc<[u8]>, + ) -> Result<()> { + let mut buf = vec![0u8; 65_535]; + loop { + tokio::select! { + maybe_payload = outbound_rx.recv() => match maybe_payload { + Some(payload) => { + let packet = encrypt_custom_ss_stream_udp_packet(kind, &stream_key, key.remote, &payload)?; + socket + .send(&packet) + .await + .with_context(|| format!("failed to send {} udp payload to {server}", kind.name()))?; + } + None => break, + }, + recv = timeout(UDP_IDLE_TIMEOUT, socket.recv(&mut buf)) => match recv { + Ok(Ok(len)) => { + let (source, payload) = + decrypt_custom_ss_stream_udp_packet(kind, &stream_key, &buf[..len])?; + reply_tx + .send(UdpReply { + payload, + source, + destination: key.local, + }) + .await + .context("failed to enqueue Shadowsocks udp reply")?; + } + Ok(Err(err)) => return Err(err).context("failed to receive Shadowsocks udp response"), + Err(_) => break, + } + } + } + Ok(()) + } + + async fn run_custom_stream_uot_loop( + &self, + mut reader: CustomSsStreamByteReader, + mut writer: CustomSsStreamWriter, + server: SocketAddr, + key: UdpFlowKey, + mut outbound_rx: mpsc::Receiver>, + reply_tx: mpsc::Sender, + version: u8, + ) -> Result<()> + where + W: AsyncWrite + Unpin, + R: AsyncRead + Unpin, + { + if version == UOT_VERSION { + writer + .write_all_encrypted(&uot_request(key.remote)?) + .await?; + } + loop { + tokio::select! { + maybe_payload = outbound_rx.recv() => match maybe_payload { + Some(payload) => { + let frame = uot_frame(key.remote, &payload)?; + writer.write_all_encrypted(&frame) + .await + .with_context(|| format!("failed to send custom Shadowsocks udp-over-tcp payload to {server}"))?; + } + None => break, + }, + recv = timeout(UDP_IDLE_TIMEOUT, reader.read_frame()) => match recv { + Ok(Ok(Some((source, payload)))) => { + reply_tx + .send(UdpReply { + payload, + source, + destination: key.local, + }) + .await + .context("failed to enqueue custom Shadowsocks udp-over-tcp reply")?; + } + Ok(Ok(None)) => break, + Ok(Err(err)) => return Err(err).context("failed to receive custom Shadowsocks udp-over-tcp response"), + Err(_) => break, + } + } + } + Ok(()) + } + + async fn run_aead2022_aes_ccm_udp_loop( + &self, + socket: UdpSocket, + server: SocketAddr, + key: UdpFlowKey, + mut outbound_rx: mpsc::Receiver>, + reply_tx: mpsc::Sender, + kind: Aead2022AesCcmKind, + user_key: Arc<[u8]>, + identity_keys: Arc<[Arc<[u8]>]>, + ) -> Result<()> { + let mut session = Aead2022AesCcmUdpSession::new(kind, user_key, identity_keys)?; + let mut buf = vec![0u8; 65_535]; + loop { + tokio::select! { + maybe_payload = outbound_rx.recv() => match maybe_payload { + Some(payload) => { + let packet = session.encrypt_client_packet(key.remote, &payload)?; + socket + .send(&packet) + .await + .with_context(|| format!("failed to send {} udp payload to {server}", kind.name()))?; + } + None => break, + }, + recv = timeout(UDP_IDLE_TIMEOUT, socket.recv(&mut buf)) => match recv { + Ok(Ok(len)) => { + let (source, payload) = session.decrypt_server_packet(&buf[..len])?; + reply_tx + .send(UdpReply { + payload, + source, + destination: key.local, + }) + .await + .context("failed to enqueue Shadowsocks 2022 udp reply")?; + } + Ok(Err(err)) => return Err(err).context("failed to receive Shadowsocks 2022 udp response"), + Err(_) => break, + } + } + } + Ok(()) + } + + async fn run_aead2022_aes_ccm_uot_loop( + &self, + mut reader: Aead2022AesCcmByteReader, + mut writer: Aead2022AesCcmWriter, + server: SocketAddr, + key: UdpFlowKey, + mut outbound_rx: mpsc::Receiver>, + reply_tx: mpsc::Sender, + version: u8, + ) -> Result<()> + where + W: AsyncWrite + Unpin, + R: AsyncRead + Unpin, + { + if version == UOT_VERSION { + writer.write_chunk(&uot_request(key.remote)?).await?; + } + loop { + tokio::select! { + maybe_payload = outbound_rx.recv() => match maybe_payload { + Some(payload) => { + let frame = uot_frame(key.remote, &payload)?; + writer.write_chunk(&frame) + .await + .with_context(|| format!("failed to send Shadowsocks 2022 udp-over-tcp payload to {server}"))?; + } + None => break, + }, + recv = timeout(UDP_IDLE_TIMEOUT, reader.read_frame()) => match recv { + Ok(Ok(Some((source, payload)))) => { + reply_tx + .send(UdpReply { + payload, + source, + destination: key.local, + }) + .await + .context("failed to enqueue Shadowsocks 2022 udp-over-tcp reply")?; + } + Ok(Ok(None)) => break, + Ok(Err(err)) => return Err(err).context("failed to receive Shadowsocks 2022 udp-over-tcp response"), + Err(_) => break, + } + } + } + Ok(()) + } } trait ProxyIo: AsyncRead + AsyncWrite + Unpin + Send {} impl ProxyIo for T where T: AsyncRead + AsyncWrite + Unpin + Send {} +impl ShadowsocksSingMuxSession { + fn is_closed(&self) -> bool { + match self { + Self::H2Mux(session) => session.closed.load(Ordering::Relaxed), + Self::Smux(session) => session.is_closed(), + Self::Yamux(session) => session.closed.load(Ordering::Relaxed), + } + } + + async fn num_streams(&self) -> usize { + match self { + Self::H2Mux(session) => session.active_streams.load(Ordering::Relaxed), + Self::Smux(session) => session.num_streams().await, + Self::Yamux(session) => session.active_streams.load(Ordering::Relaxed), + } + } + + async fn open_stream(&self) -> Result> { + match self { + Self::H2Mux(session) => session.open_stream().await, + Self::Smux(session) => { + let stream = session.open_stream().await.map_err(|err| { + anyhow!("failed to open Shadowsocks sing-mux smux stream: {err}") + })?; + Ok(Box::new(stream)) + } + Self::Yamux(session) => { + let (response, stream_rx) = oneshot::channel(); + session + .open_tx + .send(YamuxCommand { response }) + .await + .context("Shadowsocks sing-mux yamux session is closed")?; + let stream = stream_rx + .await + .context("Shadowsocks sing-mux yamux session stopped before opening stream")? + .map_err(|err| { + anyhow!("failed to open Shadowsocks sing-mux yamux stream: {err}") + })?; + session.active_streams.fetch_add(1, Ordering::Relaxed); + Ok(Box::new(CountingProxyIo::new( + stream.compat(), + session.active_streams.clone(), + ))) + } + } + } +} + +impl ShadowsocksH2MuxSession { + async fn open_stream(&self) -> Result> { + let mut send_request = self + .send_request + .clone() + .ready() + .await + .context("Shadowsocks sing-mux h2mux session is not ready")?; + let request = Request::builder() + .method(Method::CONNECT) + .uri("https://localhost") + .body(()) + .context("failed to build Shadowsocks sing-mux h2mux CONNECT request")?; + let (response, send_stream) = send_request + .send_request(request, false) + .context("failed to open Shadowsocks sing-mux h2mux stream")?; + self.active_streams.fetch_add(1, Ordering::Relaxed); + Ok(Box::new(H2MuxStream::new( + Box::pin(response), + send_stream, + self.active_streams.clone(), + ))) + } +} + +async fn start_sing_mux_h2mux_session( + stream: Box, +) -> Result { + let (send_request, connection) = h2::client::handshake(ProxyIoAdapter::new(stream)) + .await + .context("failed to start Shadowsocks sing-mux h2mux session")?; + let active_streams = Arc::new(AtomicUsize::new(0)); + let closed = Arc::new(AtomicBool::new(false)); + let driver_closed = closed.clone(); + + tokio::spawn(async move { + if let Err(err) = connection.await { + tracing::debug!(?err, "Shadowsocks sing-mux h2mux session stopped"); + } + driver_closed.store(true, Ordering::Relaxed); + }); + + Ok(ShadowsocksSingMuxSession::H2Mux(ShadowsocksH2MuxSession { + send_request, + active_streams, + closed, + })) +} + +async fn start_sing_mux_yamux_session( + stream: Box, +) -> Result { + let connection = yamux::Connection::new( + ProxyIoAdapter::new(stream).compat(), + yamux::Config::default(), + yamux::Mode::Client, + ); + let (open_tx, open_rx) = mpsc::channel(32); + let active_streams = Arc::new(AtomicUsize::new(0)); + let closed = Arc::new(AtomicBool::new(false)); + let driver_closed = closed.clone(); + + tokio::spawn(async move { + YamuxDriver::new(connection, open_rx).await; + driver_closed.store(true, Ordering::Relaxed); + }); + + Ok(ShadowsocksSingMuxSession::Yamux(ShadowsocksYamuxSession { + open_tx, + active_streams, + closed, + })) +} + +struct YamuxDriver { + connection: yamux::Connection, + open_rx: mpsc::Receiver, + pending_open: VecDeque>>, +} + +impl YamuxDriver { + fn new(connection: yamux::Connection, open_rx: mpsc::Receiver) -> Self { + Self { + connection, + open_rx, + pending_open: VecDeque::new(), + } + } +} + +impl Future for YamuxDriver +where + T: futures::io::AsyncRead + futures::io::AsyncWrite + Unpin, +{ + type Output = (); + + fn poll(mut self: Pin<&mut Self>, cx: &mut TaskContext<'_>) -> Poll { + loop { + let mut progressed = false; + + while let Poll::Ready(command) = self.open_rx.poll_recv(cx) { + let Some(command) = command else { + break; + }; + self.pending_open.push_back(command.response); + progressed = true; + } + + while let Some(response) = self.pending_open.pop_front() { + match self.connection.poll_new_outbound(cx) { + Poll::Ready(Ok(stream)) => { + let _ = response.send(Ok(stream)); + progressed = true; + } + Poll::Ready(Err(err)) => { + let _ = response.send(Err(err.to_string())); + progressed = true; + } + Poll::Pending => { + self.pending_open.push_front(response); + break; + } + } + } + + match self.connection.poll_next_inbound(cx) { + Poll::Ready(Some(Ok(_stream))) => { + tracing::debug!("discarding unexpected server-opened Shadowsocks yamux stream"); + progressed = true; + } + Poll::Ready(Some(Err(err))) => { + tracing::debug!(?err, "Shadowsocks sing-mux yamux session stopped"); + return Poll::Ready(()); + } + Poll::Ready(None) => return Poll::Ready(()), + Poll::Pending => {} + } + + if !progressed { + return Poll::Pending; + } + } + } +} + +type H2MuxResponseFuture = Pin< + Box, h2::Error>> + Send>, +>; + +struct H2MuxStream { + response: Option, + recv: Option, + send: h2::SendStream, + read_buf: Bytes, + active_streams: Arc, + send_closed: bool, +} + +impl H2MuxStream { + fn new( + response: H2MuxResponseFuture, + send: h2::SendStream, + active_streams: Arc, + ) -> Self { + Self { + response: Some(response), + recv: None, + send, + read_buf: Bytes::new(), + active_streams, + send_closed: false, + } + } +} + +impl Drop for H2MuxStream { + fn drop(&mut self) { + if !self.send_closed { + self.send.send_reset(h2::Reason::CANCEL); + } + self.active_streams.fetch_sub(1, Ordering::Relaxed); + } +} + +impl AsyncRead for H2MuxStream { + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut TaskContext<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + loop { + if !self.read_buf.is_empty() { + let n = buf.remaining().min(self.read_buf.len()); + buf.put_slice(&self.read_buf[..n]); + let _ = self.read_buf.split_to(n); + return Poll::Ready(Ok(())); + } + + if self.recv.is_none() { + let Some(response) = self.response.as_mut() else { + return Poll::Ready(Ok(())); + }; + let response = match response.as_mut().poll(cx) { + Poll::Pending => return Poll::Pending, + Poll::Ready(Ok(response)) => response, + Poll::Ready(Err(err)) => return Poll::Ready(Err(io::Error::other(err))), + }; + if response.status() != StatusCode::OK { + return Poll::Ready(Err(io::Error::other(format!( + "unexpected Shadowsocks sing-mux h2mux status {}", + response.status() + )))); + } + self.recv = Some(response.into_body()); + self.response = None; + } + + let recv = self.recv.as_mut().expect("h2mux response body initialized"); + match recv.poll_data(cx) { + Poll::Pending => return Poll::Pending, + Poll::Ready(Some(Ok(bytes))) => { + if bytes.is_empty() { + continue; + } + let len = bytes.len(); + if let Err(err) = recv.flow_control().release_capacity(len) { + return Poll::Ready(Err(io::Error::other(err))); + } + self.read_buf = bytes; + } + Poll::Ready(Some(Err(err))) => return Poll::Ready(Err(io::Error::other(err))), + Poll::Ready(None) => return Poll::Ready(Ok(())), + } + } + } +} + +impl AsyncWrite for H2MuxStream { + fn poll_write( + mut self: Pin<&mut Self>, + _cx: &mut TaskContext<'_>, + buf: &[u8], + ) -> Poll> { + if self.send_closed { + return Poll::Ready(Err(io::Error::new( + io::ErrorKind::BrokenPipe, + "Shadowsocks sing-mux h2mux stream is closed", + ))); + } + if buf.is_empty() { + return Poll::Ready(Ok(0)); + } + self.send + .send_data(Bytes::copy_from_slice(buf), false) + .map_err(io::Error::other)?; + Poll::Ready(Ok(buf.len())) + } + + fn poll_flush(self: Pin<&mut Self>, _cx: &mut TaskContext<'_>) -> Poll> { + Poll::Ready(Ok(())) + } + + fn poll_shutdown(mut self: Pin<&mut Self>, _cx: &mut TaskContext<'_>) -> Poll> { + if !self.send_closed { + self.send + .send_data(Bytes::new(), true) + .map_err(io::Error::other)?; + self.send_closed = true; + } + Poll::Ready(Ok(())) + } +} + +struct CountingProxyIo { + inner: S, + active_streams: Arc, +} + +impl CountingProxyIo { + fn new(inner: S, active_streams: Arc) -> Self { + Self { inner, active_streams } + } +} + +impl Drop for CountingProxyIo { + fn drop(&mut self) { + self.active_streams.fetch_sub(1, Ordering::Relaxed); + } +} + +impl AsyncRead for CountingProxyIo +where + S: AsyncRead + Unpin, +{ + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut TaskContext<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + Pin::new(&mut self.inner).poll_read(cx, buf) + } +} + +impl AsyncWrite for CountingProxyIo +where + S: AsyncWrite + Unpin, +{ + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut TaskContext<'_>, + buf: &[u8], + ) -> Poll> { + Pin::new(&mut self.inner).poll_write(cx, buf) + } + + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut TaskContext<'_>) -> Poll> { + Pin::new(&mut self.inner).poll_flush(cx) + } + + fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut TaskContext<'_>) -> Poll> { + Pin::new(&mut self.inner).poll_shutdown(cx) + } +} + +struct SingMuxTcpStream { + inner: S, + response_read: bool, +} + +impl SingMuxTcpStream { + fn new(inner: S) -> Self { + Self { inner, response_read: false } + } +} + +impl AsyncRead for SingMuxTcpStream +where + S: AsyncRead + Unpin, +{ + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut TaskContext<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + if !self.response_read { + let mut status = [0u8; 1]; + let mut status_buf = ReadBuf::new(&mut status); + match Pin::new(&mut self.inner).poll_read(cx, &mut status_buf) { + Poll::Pending => return Poll::Pending, + Poll::Ready(Err(err)) => return Poll::Ready(Err(err)), + Poll::Ready(Ok(())) if status_buf.filled().is_empty() => { + return Poll::Ready(Err(io::Error::new( + io::ErrorKind::UnexpectedEof, + "Shadowsocks sing-mux stream closed before response status", + ))); + } + Poll::Ready(Ok(())) => match status[0] { + SING_MUX_STREAM_STATUS_SUCCESS => self.response_read = true, + SING_MUX_STREAM_STATUS_ERROR => { + return Poll::Ready(Err(io::Error::other( + "Shadowsocks sing-mux remote error", + ))); + } + other => { + return Poll::Ready(Err(io::Error::new( + io::ErrorKind::InvalidData, + format!("unknown Shadowsocks sing-mux stream response status {other}"), + ))); + } + }, + } + } + Pin::new(&mut self.inner).poll_read(cx, buf) + } +} + +impl AsyncWrite for SingMuxTcpStream +where + S: AsyncWrite + Unpin, +{ + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut TaskContext<'_>, + buf: &[u8], + ) -> Poll> { + Pin::new(&mut self.inner).poll_write(cx, buf) + } + + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut TaskContext<'_>) -> Poll> { + Pin::new(&mut self.inner).poll_flush(cx) + } + + fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut TaskContext<'_>) -> Poll> { + Pin::new(&mut self.inner).poll_shutdown(cx) + } +} + +struct ProxyIoAdapter { + inner: Box, +} + +impl ProxyIoAdapter { + fn new(inner: Box) -> Self { + Self { inner } + } +} + +impl AsyncRead for ProxyIoAdapter { + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut TaskContext<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + Pin::new(&mut self.inner).poll_read(cx, buf) + } +} + +impl AsyncWrite for ProxyIoAdapter { + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut TaskContext<'_>, + buf: &[u8], + ) -> Poll> { + Pin::new(&mut self.inner).poll_write(cx, buf) + } + + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut TaskContext<'_>) -> Poll> { + Pin::new(&mut self.inner).poll_flush(cx) + } + + fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut TaskContext<'_>) -> Poll> { + Pin::new(&mut self.inner).poll_shutdown(cx) + } +} + +async fn custom_aead_plain_tcp_stream( + tcp: S, + target: &ProxyTcpTarget, + kind: CustomSsAeadKind, + master_key: Arc<[u8]>, +) -> Result> +where + S: AsyncRead + AsyncWrite + Unpin + Send + 'static, +{ + let remote_addr = target.address; + let (reader, writer) = split(tcp); + let mut ss_reader = CustomSsAeadReader::new(reader, kind, master_key.clone()); + let mut ss_writer = CustomSsAeadWriter::new(writer, kind, master_key) + .await + .with_context(|| format!("failed to initialize {} tcp stream", kind.name()))?; + let socks_target = socks_addr_for_target(target)?; + ss_writer + .write_chunk(&socks_target) + .await + .with_context(|| { + format!( + "failed to write Shadowsocks target address for {remote_addr} with {}", + kind.name() + ) + })?; + + let (plain, bridge) = tokio::io::duplex(64 * 1024); + let (mut plain_reader, mut plain_writer) = split(bridge); + let _upload = tokio::spawn(async move { + let result = async { + let mut buf = vec![0u8; 16 * 1024]; + loop { + let n = plain_reader + .read(&mut buf) + .await + .context("failed to read plaintext payload for Shadowsocks tcp upload")?; + if n == 0 { + break; + } + ss_writer.write_chunk(&buf[..n]).await?; + } + ss_writer + .shutdown() + .await + .context("failed to shutdown Shadowsocks tcp upload")?; + Result::<()>::Ok(()) + } + .await; + if let Err(err) = result { + tracing::debug!( + ?err, + "Shadowsocks custom AEAD plaintext upload task stopped" + ); + } + }); + + let _download = tokio::spawn(async move { + let result = async { + let mut buf = Vec::new(); + loop { + buf.clear(); + match ss_reader.read_chunk(&mut buf).await { + Ok(0) => break, + Ok(_) => { + plain_writer + .write_all(&buf) + .await + .context("failed to write plaintext payload from Shadowsocks tcp")?; + } + Err(err) => return Err(err), + } + } + plain_writer + .shutdown() + .await + .context("failed to shutdown Shadowsocks tcp download")?; + Result::<()>::Ok(()) + } + .await; + if let Err(err) = result { + tracing::debug!( + ?err, + "Shadowsocks custom AEAD plaintext download task stopped" + ); + } + }); + + Ok(Box::new(plain)) +} + +async fn custom_stream_plain_tcp_stream( + tcp: S, + target: &ProxyTcpTarget, + kind: CustomSsStreamKind, + key: Arc<[u8]>, +) -> Result> +where + S: AsyncRead + AsyncWrite + Unpin + Send + 'static, +{ + let remote_addr = target.address; + let (reader, writer) = split(tcp); + let mut ss_reader = CustomSsStreamReader::new(reader, kind, key.clone()); + let mut ss_writer = CustomSsStreamWriter::new(writer, kind, key) + .await + .with_context(|| format!("failed to initialize {} tcp stream", kind.name()))?; + let socks_target = socks_addr_for_target(target)?; + ss_writer + .write_all_encrypted(&socks_target) + .await + .with_context(|| { + format!( + "failed to write Shadowsocks target address for {remote_addr} with {}", + kind.name() + ) + })?; + + let (plain, bridge) = tokio::io::duplex(64 * 1024); + let (mut plain_reader, mut plain_writer) = split(bridge); + let _upload = tokio::spawn(async move { + let result = async { + let mut buf = vec![0u8; 16 * 1024]; + loop { + let n = plain_reader + .read(&mut buf) + .await + .context("failed to read plaintext payload for Shadowsocks tcp upload")?; + if n == 0 { + break; + } + ss_writer.write_all_encrypted(&buf[..n]).await?; + } + ss_writer + .shutdown() + .await + .context("failed to shutdown Shadowsocks tcp upload")?; + Result::<()>::Ok(()) + } + .await; + if let Err(err) = result { + tracing::debug!( + ?err, + "Shadowsocks custom stream plaintext upload task stopped" + ); + } + }); + + let _download = tokio::spawn(async move { + let result = async { + let mut buf = vec![0u8; 16 * 1024]; + loop { + let n = ss_reader.read_decrypted(&mut buf).await?; + if n == 0 { + break; + } + plain_writer + .write_all(&buf[..n]) + .await + .context("failed to write plaintext payload from Shadowsocks tcp")?; + } + plain_writer + .shutdown() + .await + .context("failed to shutdown Shadowsocks tcp download")?; + Result::<()>::Ok(()) + } + .await; + if let Err(err) = result { + tracing::debug!( + ?err, + "Shadowsocks custom stream plaintext download task stopped" + ); + } + }); + + Ok(Box::new(plain)) +} + +async fn aead2022_aes_ccm_plain_tcp_stream( + tcp: S, + target: &ProxyTcpTarget, + kind: Aead2022AesCcmKind, + user_key: Arc<[u8]>, + identity_keys: Arc<[Arc<[u8]>]>, +) -> Result> +where + S: AsyncRead + AsyncWrite + Unpin + Send + 'static, +{ + let remote_addr = target.address; + let (reader, writer) = split(tcp); + let mut ss_writer = Aead2022AesCcmWriter::new(writer, kind, user_key.clone(), identity_keys) + .await + .with_context(|| format!("failed to initialize {} tcp stream", kind.name()))?; + let request_salt = ss_writer + .write_request(&socks_addr_for_target(target)?, 1) + .await + .with_context(|| { + format!( + "failed to write Shadowsocks 2022 target address for {remote_addr} with {}", + kind.name() + ) + })?; + let mut ss_reader = Aead2022AesCcmReader::new(reader, kind, user_key, request_salt); + + let (plain, bridge) = tokio::io::duplex(64 * 1024); + let (mut plain_reader, mut plain_writer) = split(bridge); + let _upload = tokio::spawn(async move { + let result = async { + let mut buf = vec![0u8; 16 * 1024]; + loop { + let n = plain_reader + .read(&mut buf) + .await + .context("failed to read plaintext payload for Shadowsocks 2022 tcp upload")?; + if n == 0 { + break; + } + ss_writer.write_chunk(&buf[..n]).await?; + } + ss_writer + .shutdown() + .await + .context("failed to shutdown Shadowsocks 2022 tcp upload")?; + Result::<()>::Ok(()) + } + .await; + if let Err(err) = result { + tracing::debug!(?err, "Shadowsocks 2022 plaintext upload task stopped"); + } + }); + + let _download = tokio::spawn(async move { + let result = async { + let mut buf = Vec::new(); + loop { + buf.clear(); + match ss_reader.read_chunk(&mut buf).await { + Ok(0) => break, + Ok(_) => { + plain_writer.write_all(&buf).await.context( + "failed to write plaintext payload from Shadowsocks 2022 tcp", + )?; + } + Err(err) => return Err(err), + } + } + plain_writer + .shutdown() + .await + .context("failed to shutdown Shadowsocks 2022 tcp download")?; + Result::<()>::Ok(()) + } + .await; + if let Err(err) = result { + tracing::debug!(?err, "Shadowsocks 2022 plaintext download task stopped"); + } + }); + + Ok(Box::new(plain)) +} + type TrojanStream = Box; +struct SimpleObfsStream { + inner: S, + mode: SimpleObfsMode, + host: String, + port: u16, + first_write: bool, + first_read: bool, + write_buf: Vec, + write_offset: usize, + read_buf: Vec, + read_offset: usize, + tls_read_stage: Option, +} + +enum SimpleObfsTlsReadStage { + Discard { remaining: usize }, + Length { bytes: [u8; 2], filled: usize }, + Payload { bytes: Vec, filled: usize }, +} + +impl SimpleObfsStream { + fn new(inner: S, mode: SimpleObfsMode, host: String, port: u16) -> Self { + Self { + inner, + mode, + host, + port, + first_write: true, + first_read: true, + write_buf: Vec::new(), + write_offset: 0, + read_buf: Vec::new(), + read_offset: 0, + tls_read_stage: None, + } + } + + fn compact_write_buffer(&mut self) { + if self.write_offset == self.write_buf.len() { + self.write_buf.clear(); + self.write_offset = 0; + } + } + + fn copy_read_buffer(&mut self, buf: &mut ReadBuf<'_>) -> bool { + if self.read_offset == self.read_buf.len() { + self.read_buf.clear(); + self.read_offset = 0; + return false; + } + let len = (self.read_buf.len() - self.read_offset).min(buf.remaining()); + buf.put_slice(&self.read_buf[self.read_offset..self.read_offset + len]); + self.read_offset += len; + if self.read_offset == self.read_buf.len() { + self.read_buf.clear(); + self.read_offset = 0; + } + true + } +} + +impl AsyncRead for SimpleObfsStream +where + S: AsyncRead + AsyncWrite + Unpin, +{ + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut TaskContext<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + if self.copy_read_buffer(buf) { + return Poll::Ready(Ok(())); + } + match self.mode { + SimpleObfsMode::Http => self.poll_read_http(cx, buf), + SimpleObfsMode::Tls => self.poll_read_tls(cx, buf), + } + } +} + +impl SimpleObfsStream +where + S: AsyncRead + AsyncWrite + Unpin, +{ + fn poll_read_http( + mut self: Pin<&mut Self>, + cx: &mut TaskContext<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + if !self.first_read { + return Pin::new(&mut self.inner).poll_read(cx, buf); + } + let mut response = vec![0u8; 16 * 1024]; + let mut response_buf = ReadBuf::new(&mut response); + match Pin::new(&mut self.inner).poll_read(cx, &mut response_buf) { + Poll::Pending => Poll::Pending, + Poll::Ready(Err(err)) => Poll::Ready(Err(err)), + Poll::Ready(Ok(())) => { + let filled = response_buf.filled().len(); + let Some(header_end) = response[..filled] + .windows(4) + .position(|window| window == b"\r\n\r\n") + .map(|index| index + 4) + else { + return Poll::Ready(Err(io::Error::new( + io::ErrorKind::UnexpectedEof, + "simple-obfs HTTP response header was incomplete", + ))); + }; + self.first_read = false; + self.read_buf + .extend_from_slice(&response[header_end..filled]); + self.copy_read_buffer(buf); + Poll::Ready(Ok(())) + } + } + } + + fn poll_read_tls( + mut self: Pin<&mut Self>, + cx: &mut TaskContext<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + loop { + if self.copy_read_buffer(buf) { + return Poll::Ready(Ok(())); + } + if self.tls_read_stage.is_none() { + let discard = if self.first_read { 105 } else { 3 }; + self.first_read = false; + self.tls_read_stage = Some(SimpleObfsTlsReadStage::Discard { remaining: discard }); + } + + match self.tls_read_stage.take().expect("stage initialized") { + SimpleObfsTlsReadStage::Discard { mut remaining } => { + while remaining > 0 { + let mut scratch = [0u8; 128]; + let read_len = remaining.min(scratch.len()); + let mut discard_buf = ReadBuf::new(&mut scratch[..read_len]); + match Pin::new(&mut self.inner).poll_read(cx, &mut discard_buf) { + Poll::Pending => { + self.tls_read_stage = + Some(SimpleObfsTlsReadStage::Discard { remaining }); + return Poll::Pending; + } + Poll::Ready(Err(err)) => return Poll::Ready(Err(err)), + Poll::Ready(Ok(())) if discard_buf.filled().is_empty() => { + return Poll::Ready(Err(io::ErrorKind::UnexpectedEof.into())); + } + Poll::Ready(Ok(())) => { + remaining -= discard_buf.filled().len(); + } + } + } + self.tls_read_stage = + Some(SimpleObfsTlsReadStage::Length { bytes: [0u8; 2], filled: 0 }); + } + SimpleObfsTlsReadStage::Length { mut bytes, mut filled } => { + while filled < 2 { + let mut len_buf = ReadBuf::new(&mut bytes[filled..]); + match Pin::new(&mut self.inner).poll_read(cx, &mut len_buf) { + Poll::Pending => { + self.tls_read_stage = + Some(SimpleObfsTlsReadStage::Length { bytes, filled }); + return Poll::Pending; + } + Poll::Ready(Err(err)) => return Poll::Ready(Err(err)), + Poll::Ready(Ok(())) if len_buf.filled().is_empty() => { + return Poll::Ready(Err(io::ErrorKind::UnexpectedEof.into())); + } + Poll::Ready(Ok(())) => { + filled += len_buf.filled().len(); + } + } + } + let len = u16::from_be_bytes(bytes) as usize; + self.tls_read_stage = Some(SimpleObfsTlsReadStage::Payload { + bytes: vec![0u8; len], + filled: 0, + }); + } + SimpleObfsTlsReadStage::Payload { mut bytes, mut filled } => { + while filled < bytes.len() { + let mut payload_buf = ReadBuf::new(&mut bytes[filled..]); + match Pin::new(&mut self.inner).poll_read(cx, &mut payload_buf) { + Poll::Pending => { + self.tls_read_stage = + Some(SimpleObfsTlsReadStage::Payload { bytes, filled }); + return Poll::Pending; + } + Poll::Ready(Err(err)) => return Poll::Ready(Err(err)), + Poll::Ready(Ok(())) if payload_buf.filled().is_empty() => { + return Poll::Ready(Err(io::ErrorKind::UnexpectedEof.into())); + } + Poll::Ready(Ok(())) => { + filled += payload_buf.filled().len(); + } + } + } + self.tls_read_stage = None; + self.read_buf = bytes; + } + } + } + } +} + +impl AsyncWrite for SimpleObfsStream +where + S: AsyncRead + AsyncWrite + Unpin, +{ + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut TaskContext<'_>, + buf: &[u8], + ) -> Poll> { + while self.write_offset < self.write_buf.len() { + let offset = self.write_offset; + let pending = self.write_buf[offset..].to_vec(); + match Pin::new(&mut self.inner).poll_write(cx, &pending) { + Poll::Pending => return Poll::Pending, + Poll::Ready(Err(err)) => return Poll::Ready(Err(err)), + Poll::Ready(Ok(0)) => { + return Poll::Ready(Err(io::ErrorKind::WriteZero.into())); + } + Poll::Ready(Ok(written)) => { + self.write_offset += written; + self.compact_write_buffer(); + } + } + } + + if self.first_write { + self.first_write = false; + self.write_buf = match self.mode { + SimpleObfsMode::Http => simple_obfs_http_request(&self.host, self.port, buf), + SimpleObfsMode::Tls => simple_obfs_tls_client_hello(&self.host, buf)?, + }; + self.write_offset = 0; + while self.write_offset < self.write_buf.len() { + let offset = self.write_offset; + let pending = self.write_buf[offset..].to_vec(); + match Pin::new(&mut self.inner).poll_write(cx, &pending) { + Poll::Pending => return Poll::Ready(Ok(buf.len())), + Poll::Ready(Err(err)) => return Poll::Ready(Err(err)), + Poll::Ready(Ok(0)) => { + return Poll::Ready(Err(io::ErrorKind::WriteZero.into())); + } + Poll::Ready(Ok(written)) => { + self.write_offset += written; + self.compact_write_buffer(); + } + } + } + return Poll::Ready(Ok(buf.len())); + } + + match self.mode { + SimpleObfsMode::Http => Pin::new(&mut self.inner).poll_write(cx, buf), + SimpleObfsMode::Tls => { + self.write_buf = simple_obfs_tls_record(buf)?; + self.write_offset = 0; + while self.write_offset < self.write_buf.len() { + let offset = self.write_offset; + let pending = self.write_buf[offset..].to_vec(); + match Pin::new(&mut self.inner).poll_write(cx, &pending) { + Poll::Pending => return Poll::Ready(Ok(buf.len())), + Poll::Ready(Err(err)) => return Poll::Ready(Err(err)), + Poll::Ready(Ok(0)) => { + return Poll::Ready(Err(io::ErrorKind::WriteZero.into())); + } + Poll::Ready(Ok(written)) => { + self.write_offset += written; + self.compact_write_buffer(); + } + } + } + Poll::Ready(Ok(buf.len())) + } + } + } + + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut TaskContext<'_>) -> Poll> { + while self.write_offset < self.write_buf.len() { + let offset = self.write_offset; + let pending = self.write_buf[offset..].to_vec(); + match Pin::new(&mut self.inner).poll_write(cx, &pending) { + Poll::Pending => return Poll::Pending, + Poll::Ready(Err(err)) => return Poll::Ready(Err(err)), + Poll::Ready(Ok(0)) => return Poll::Ready(Err(io::ErrorKind::WriteZero.into())), + Poll::Ready(Ok(written)) => { + self.write_offset += written; + self.compact_write_buffer(); + } + } + } + Pin::new(&mut self.inner).poll_flush(cx) + } + + fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut TaskContext<'_>) -> Poll> { + match self.as_mut().poll_flush(cx) { + Poll::Pending => Poll::Pending, + Poll::Ready(Err(err)) => Poll::Ready(Err(err)), + Poll::Ready(Ok(())) => Pin::new(&mut self.inner).poll_shutdown(cx), + } + } +} + +struct HmacReadStream { + inner: S, + hmac: hmac::Context, +} + +#[cfg(feature = "boring-browser-fingerprints")] +struct DetachableStream { + inner: Option, +} + +#[cfg(feature = "boring-browser-fingerprints")] +impl DetachableStream { + fn new(inner: S) -> Self { + Self { inner: Some(inner) } + } + + fn take(&mut self) -> io::Result { + self.inner.take().ok_or_else(|| { + io::Error::new( + io::ErrorKind::BrokenPipe, + "detachable TLS stream already taken", + ) + }) + } + + fn inner_mut(&mut self) -> io::Result<&mut S> { + self.inner.as_mut().ok_or_else(|| { + io::Error::new( + io::ErrorKind::BrokenPipe, + "detachable TLS stream already taken", + ) + }) + } +} + +#[cfg(feature = "boring-browser-fingerprints")] +impl AsyncRead for DetachableStream +where + S: AsyncRead + Unpin, +{ + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut TaskContext<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + let inner = match self.inner_mut() { + Ok(inner) => inner, + Err(err) => return Poll::Ready(Err(err)), + }; + Pin::new(inner).poll_read(cx, buf) + } +} + +#[cfg(feature = "boring-browser-fingerprints")] +impl AsyncWrite for DetachableStream +where + S: AsyncWrite + Unpin, +{ + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut TaskContext<'_>, + buf: &[u8], + ) -> Poll> { + let inner = match self.inner_mut() { + Ok(inner) => inner, + Err(err) => return Poll::Ready(Err(err)), + }; + Pin::new(inner).poll_write(cx, buf) + } + + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut TaskContext<'_>) -> Poll> { + let inner = match self.inner_mut() { + Ok(inner) => inner, + Err(err) => return Poll::Ready(Err(err)), + }; + Pin::new(inner).poll_flush(cx) + } + + fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut TaskContext<'_>) -> Poll> { + let inner = match self.inner_mut() { + Ok(inner) => inner, + Err(err) => return Poll::Ready(Err(err)), + }; + Pin::new(inner).poll_shutdown(cx) + } +} + +impl HmacReadStream { + fn new(inner: S, password: &str) -> Self { + Self { + inner, + hmac: hmac::Context::with_key(&hmac::Key::new( + hmac::HMAC_SHA1_FOR_LEGACY_USE_ONLY, + password.as_bytes(), + )), + } + } + + fn finish(self) -> (S, [u8; 8]) { + let digest = self.hmac.sign(); + let mut prefix = [0u8; 8]; + prefix.copy_from_slice(&digest.as_ref()[..8]); + (self.inner, prefix) + } +} + +impl AsyncRead for HmacReadStream +where + S: AsyncRead + Unpin, +{ + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut TaskContext<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + let before = buf.filled().len(); + match Pin::new(&mut self.inner).poll_read(cx, buf) { + Poll::Ready(Ok(())) => { + let filled = buf.filled(); + if filled.len() > before { + self.hmac.update(&filled[before..]); + } + Poll::Ready(Ok(())) + } + other => other, + } + } +} + +impl AsyncWrite for HmacReadStream +where + S: AsyncWrite + Unpin, +{ + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut TaskContext<'_>, + buf: &[u8], + ) -> Poll> { + Pin::new(&mut self.inner).poll_write(cx, buf) + } + + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut TaskContext<'_>) -> Poll> { + Pin::new(&mut self.inner).poll_flush(cx) + } + + fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut TaskContext<'_>) -> Poll> { + Pin::new(&mut self.inner).poll_shutdown(cx) + } +} + +struct ShadowTlsV2Stream { + inner: S, + first_write_hmac: Option<[u8; 8]>, + read_stage: ShadowTlsReadStage, + write_buf: Vec, + write_offset: usize, +} + +enum ShadowTlsReadStage { + Header { bytes: [u8; 5], filled: usize }, + Payload { remaining: usize }, +} + +impl ShadowTlsV2Stream { + fn new(inner: S, first_write_hmac: [u8; 8]) -> Self { + Self { + inner, + first_write_hmac: Some(first_write_hmac), + read_stage: ShadowTlsReadStage::Header { bytes: [0u8; 5], filled: 0 }, + write_buf: Vec::new(), + write_offset: 0, + } + } + + fn compact_write_buffer(&mut self) { + if self.write_offset == self.write_buf.len() { + self.write_buf.clear(); + self.write_offset = 0; + } + } +} + +impl AsyncRead for ShadowTlsV2Stream +where + S: AsyncRead + AsyncWrite + Unpin, +{ + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut TaskContext<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + loop { + match self.read_stage { + ShadowTlsReadStage::Header { mut bytes, mut filled } => { + while filled < bytes.len() { + let mut header_buf = ReadBuf::new(&mut bytes[filled..]); + match Pin::new(&mut self.inner).poll_read(cx, &mut header_buf) { + Poll::Pending => { + self.read_stage = ShadowTlsReadStage::Header { bytes, filled }; + return Poll::Pending; + } + Poll::Ready(Err(err)) => return Poll::Ready(Err(err)), + Poll::Ready(Ok(())) if header_buf.filled().is_empty() => { + return Poll::Ready(Ok(())); + } + Poll::Ready(Ok(())) => { + filled += header_buf.filled().len(); + } + } + } + if bytes[0] != 23 { + return Poll::Ready(Err(io::Error::new( + io::ErrorKind::InvalidData, + format!("unexpected ShadowTLS record type {}", bytes[0]), + ))); + } + let remaining = u16::from_be_bytes([bytes[3], bytes[4]]) as usize; + self.read_stage = ShadowTlsReadStage::Payload { remaining }; + } + ShadowTlsReadStage::Payload { remaining } => { + if remaining == 0 { + self.read_stage = ShadowTlsReadStage::Header { bytes: [0u8; 5], filled: 0 }; + continue; + } + let before = buf.filled().len(); + let read_len = remaining.min(buf.remaining()); + if read_len == 0 { + return Poll::Ready(Ok(())); + } + let initialized = buf.initialize_unfilled_to(read_len); + let mut payload_buf = ReadBuf::new(initialized); + match Pin::new(&mut self.inner).poll_read(cx, &mut payload_buf) { + Poll::Pending => { + self.read_stage = ShadowTlsReadStage::Payload { remaining }; + return Poll::Pending; + } + Poll::Ready(Err(err)) => return Poll::Ready(Err(err)), + Poll::Ready(Ok(())) if payload_buf.filled().is_empty() => { + return Poll::Ready(Err(io::ErrorKind::UnexpectedEof.into())); + } + Poll::Ready(Ok(())) => { + let read = payload_buf.filled().len(); + buf.advance(read); + let remaining = remaining - read; + self.read_stage = if remaining == 0 { + ShadowTlsReadStage::Header { bytes: [0u8; 5], filled: 0 } + } else { + ShadowTlsReadStage::Payload { remaining } + }; + if buf.filled().len() > before { + return Poll::Ready(Ok(())); + } + } + } + } + } + } + } +} + +impl AsyncWrite for ShadowTlsV2Stream +where + S: AsyncRead + AsyncWrite + Unpin, +{ + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut TaskContext<'_>, + buf: &[u8], + ) -> Poll> { + while self.write_offset < self.write_buf.len() { + let offset = self.write_offset; + let pending = self.write_buf[offset..].to_vec(); + match Pin::new(&mut self.inner).poll_write(cx, &pending) { + Poll::Pending => return Poll::Pending, + Poll::Ready(Err(err)) => return Poll::Ready(Err(err)), + Poll::Ready(Ok(0)) => return Poll::Ready(Err(io::ErrorKind::WriteZero.into())), + Poll::Ready(Ok(written)) => { + self.write_offset += written; + self.compact_write_buffer(); + } + } + } + + let write_len = buf.len().min(SHADOW_TLS_MAX_RECORD_PAYLOAD); + self.write_buf = shadow_tls_v2_record(buf, write_len, self.first_write_hmac.take())?; + self.write_offset = 0; + while self.write_offset < self.write_buf.len() { + let offset = self.write_offset; + let pending = self.write_buf[offset..].to_vec(); + match Pin::new(&mut self.inner).poll_write(cx, &pending) { + Poll::Pending => return Poll::Ready(Ok(write_len)), + Poll::Ready(Err(err)) => return Poll::Ready(Err(err)), + Poll::Ready(Ok(0)) => return Poll::Ready(Err(io::ErrorKind::WriteZero.into())), + Poll::Ready(Ok(written)) => { + self.write_offset += written; + self.compact_write_buffer(); + } + } + } + Poll::Ready(Ok(write_len)) + } + + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut TaskContext<'_>) -> Poll> { + while self.write_offset < self.write_buf.len() { + let offset = self.write_offset; + let pending = self.write_buf[offset..].to_vec(); + match Pin::new(&mut self.inner).poll_write(cx, &pending) { + Poll::Pending => return Poll::Pending, + Poll::Ready(Err(err)) => return Poll::Ready(Err(err)), + Poll::Ready(Ok(0)) => return Poll::Ready(Err(io::ErrorKind::WriteZero.into())), + Poll::Ready(Ok(written)) => { + self.write_offset += written; + self.compact_write_buffer(); + } + } + } + Pin::new(&mut self.inner).poll_flush(cx) + } + + fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut TaskContext<'_>) -> Poll> { + match self.as_mut().poll_flush(cx) { + Poll::Pending => Poll::Pending, + Poll::Ready(Err(err)) => Poll::Ready(Err(err)), + Poll::Ready(Ok(())) => Pin::new(&mut self.inner).poll_shutdown(cx), + } + } +} + +struct ShadowTlsV3HandshakeStream { + inner: S, + password: String, + read_stage: ShadowTlsV3ReadStage, + read_buf: Vec, + read_offset: usize, + server_random: Option<[u8; SHADOW_TLS_V3_TLS_RANDOM_SIZE]>, + read_hmac: Option, + read_hmac_key: Option<[u8; 32]>, + is_tls13: bool, + authorized: bool, +} + +#[cfg(feature = "boring-browser-fingerprints")] +struct ShadowTlsV3SignedClientHelloStream { + inner: ShadowTlsV3HandshakeStream, + password: String, + client_hello_signed: bool, + client_hello_buf: Vec, + write_buf: Vec, + write_offset: usize, +} + +enum ShadowTlsV3ReadStage { + Header { + bytes: [u8; 5], + filled: usize, + }, + Payload { + header: [u8; 5], + bytes: Vec, + filled: usize, + }, +} + +impl ShadowTlsV3HandshakeStream { + fn new(inner: S, password: &str) -> Self { + Self { + inner, + password: password.to_owned(), + read_stage: ShadowTlsV3ReadStage::Header { bytes: [0u8; 5], filled: 0 }, + read_buf: Vec::new(), + read_offset: 0, + server_random: None, + read_hmac: None, + read_hmac_key: None, + is_tls13: false, + authorized: false, + } + } + + fn copy_read_buffer(&mut self, buf: &mut ReadBuf<'_>) -> bool { + if self.read_offset == self.read_buf.len() { + self.read_buf.clear(); + self.read_offset = 0; + return false; + } + let len = (self.read_buf.len() - self.read_offset).min(buf.remaining()); + buf.put_slice(&self.read_buf[self.read_offset..self.read_offset + len]); + self.read_offset += len; + if self.read_offset == self.read_buf.len() { + self.read_buf.clear(); + self.read_offset = 0; + } + true + } + + fn process_frame(&mut self, mut frame: Vec) -> io::Result<()> { + match frame.first().copied() { + Some(0x16) => { + if self.read_hmac.is_none() + && frame.len() + >= SHADOW_TLS_V3_SERVER_RANDOM_INDEX + SHADOW_TLS_V3_TLS_RANDOM_SIZE + && frame[SHADOW_TLS_V3_TLS_HEADER_SIZE] == 0x02 + { + let mut server_random = [0u8; SHADOW_TLS_V3_TLS_RANDOM_SIZE]; + server_random.copy_from_slice( + &frame[SHADOW_TLS_V3_SERVER_RANDOM_INDEX + ..SHADOW_TLS_V3_SERVER_RANDOM_INDEX + SHADOW_TLS_V3_TLS_RANDOM_SIZE], + ); + let read_hmac = shadow_tls_v3_hmac(&self.password, &server_random, b""); + let read_hmac_key = shadow_tls_v3_kdf(&self.password, &server_random); + self.is_tls13 = shadow_tls_v3_server_hello_supports_tls13(&frame); + if !self.is_tls13 { + self.authorized = true; + } + self.server_random = Some(server_random); + self.read_hmac_key = Some(read_hmac_key); + self.read_hmac = Some(read_hmac); + } + self.read_buf = frame; + } + Some(0x17) => { + self.authorized = false; + if frame.len() > SHADOW_TLS_V3_HMAC_HEADER_SIZE { + if let (Some(read_hmac), Some(read_hmac_key)) = + (self.read_hmac.as_mut(), self.read_hmac_key.as_ref()) + { + read_hmac.update(&frame[SHADOW_TLS_V3_HMAC_HEADER_SIZE..]); + let expected = + shadow_tls_v3_hmac_prefix(read_hmac, SHADOW_TLS_V3_HMAC_SIZE); + if expected[..] + == frame[SHADOW_TLS_V3_TLS_HEADER_SIZE..SHADOW_TLS_V3_HMAC_HEADER_SIZE] + { + shadow_tls_v3_xor( + &mut frame[SHADOW_TLS_V3_HMAC_HEADER_SIZE..], + read_hmac_key, + ); + let payload_len = frame.len() - SHADOW_TLS_V3_HMAC_HEADER_SIZE; + let mut rewritten = + Vec::with_capacity(SHADOW_TLS_V3_TLS_HEADER_SIZE + payload_len); + rewritten.extend_from_slice(&frame[..SHADOW_TLS_V3_TLS_HEADER_SIZE]); + rewritten[3..5].copy_from_slice(&(payload_len as u16).to_be_bytes()); + rewritten.extend_from_slice(&frame[SHADOW_TLS_V3_HMAC_HEADER_SIZE..]); + self.read_buf = rewritten; + self.authorized = true; + } else { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "ShadowTLS v3 handshake HMAC mismatch", + )); + } + } else { + self.read_buf = frame; + } + } else { + self.read_buf = frame; + } + } + _ => { + self.read_buf = frame; + } + } + self.read_offset = 0; + Ok(()) + } + + fn into_inner(self) -> (S, ShadowTlsV3HandshakeState) { + ( + self.inner, + ShadowTlsV3HandshakeState { + server_random: self.server_random, + read_hmac: self.read_hmac, + is_tls13: self.is_tls13, + authorized: self.authorized, + }, + ) + } +} + +#[cfg(feature = "boring-browser-fingerprints")] +impl ShadowTlsV3SignedClientHelloStream { + fn new(inner: ShadowTlsV3HandshakeStream, password: &str) -> Self { + Self { + inner, + password: password.to_owned(), + client_hello_signed: false, + client_hello_buf: Vec::new(), + write_buf: Vec::new(), + write_offset: 0, + } + } + + fn into_inner(self) -> (ShadowTlsV3HandshakeStream, bool) { + (self.inner, self.client_hello_signed) + } + + fn queue_write(&mut self, buf: &[u8]) -> io::Result<()> { + if self.client_hello_signed { + self.write_buf.extend_from_slice(buf); + return Ok(()); + } + self.client_hello_buf.extend_from_slice(buf); + if let Some(signed) = + shadow_tls_v3_sign_client_hello_record(&self.password, &self.client_hello_buf)? + { + self.write_buf.extend_from_slice(&signed); + self.client_hello_buf.clear(); + self.client_hello_signed = true; + } + Ok(()) + } + + fn poll_write_pending(&mut self, cx: &mut TaskContext<'_>) -> Poll> + where + S: AsyncRead + AsyncWrite + Unpin, + { + while self.write_offset < self.write_buf.len() { + let pending = self.write_buf[self.write_offset..].to_vec(); + match Pin::new(&mut self.inner).poll_write(cx, &pending) { + Poll::Pending => return Poll::Pending, + Poll::Ready(Err(err)) => return Poll::Ready(Err(err)), + Poll::Ready(Ok(0)) => return Poll::Ready(Err(io::ErrorKind::WriteZero.into())), + Poll::Ready(Ok(written)) => { + self.write_offset += written; + } + } + } + self.write_buf.clear(); + self.write_offset = 0; + Poll::Ready(Ok(())) + } +} + +struct ShadowTlsV3HandshakeState { + server_random: Option<[u8; SHADOW_TLS_V3_TLS_RANDOM_SIZE]>, + read_hmac: Option, + is_tls13: bool, + authorized: bool, +} + +#[allow(dead_code)] +struct RestlsHandshakeStream { + inner: S, + secret: [u8; 32], + tls12_gcm: bool, + read_stage: RestlsHandshakeReadStage, + read_buf: Vec, + read_offset: usize, + write_buf: Vec, + write_offset: usize, + write_capture_buf: Vec, + server_random: Option<[u8; 32]>, + client_finished_auth: Option>, + server_auth_unmasked: bool, + tls12_gcm_server_disable_counter: bool, +} + +#[cfg(feature = "boring-browser-fingerprints")] +struct RestlsSignedClientHelloStream { + inner: RestlsHandshakeStream, + secret: [u8; 32], + client_hello_signed: bool, + client_hello_buf: Vec, + write_buf: Vec, + write_offset: usize, +} + +#[allow(dead_code)] +enum RestlsHandshakeReadStage { + Header { + bytes: [u8; TLS_RECORD_HEADER_LEN], + filled: usize, + }, + Payload { + header: [u8; TLS_RECORD_HEADER_LEN], + bytes: Vec, + filled: usize, + }, +} + +#[allow(dead_code)] +struct RestlsHandshakeState { + server_random: Option<[u8; 32]>, + client_finished_auth: Option>, + server_auth_unmasked: bool, + tls12_gcm_server_disable_counter: bool, +} + +#[allow(dead_code)] +impl RestlsHandshakeStream { + fn new(inner: S, secret: [u8; 32], tls12_gcm: bool) -> Self { + Self { + inner, + secret, + tls12_gcm, + read_stage: RestlsHandshakeReadStage::Header { + bytes: [0u8; TLS_RECORD_HEADER_LEN], + filled: 0, + }, + read_buf: Vec::new(), + read_offset: 0, + write_buf: Vec::new(), + write_offset: 0, + write_capture_buf: Vec::new(), + server_random: None, + client_finished_auth: None, + server_auth_unmasked: false, + tls12_gcm_server_disable_counter: false, + } + } + + fn copy_read_buffer(&mut self, buf: &mut ReadBuf<'_>) -> bool { + if self.read_offset == self.read_buf.len() { + self.read_buf.clear(); + self.read_offset = 0; + return false; + } + let len = (self.read_buf.len() - self.read_offset).min(buf.remaining()); + buf.put_slice(&self.read_buf[self.read_offset..self.read_offset + len]); + self.read_offset += len; + if self.read_offset == self.read_buf.len() { + self.read_buf.clear(); + self.read_offset = 0; + } + true + } + + fn compact_write_buffer(&mut self) { + if self.write_offset == self.write_buf.len() { + self.write_buf.clear(); + self.write_offset = 0; + } + } + + fn process_read_frame(&mut self, frame: Vec) -> io::Result<()> { + if let Some(server_random) = restls_server_hello_random(&frame) { + self.server_random = Some(server_random); + self.read_buf = frame; + } else if !self.server_auth_unmasked && frame.first().copied() == Some(0x17) { + let server_random = self.server_random.ok_or_else(|| { + io::Error::new( + io::ErrorKind::InvalidData, + "Restls server-auth record arrived before ServerHello random", + ) + })?; + let unmasked = restls_unmask_server_auth_record( + &self.secret, + &server_random, + &frame, + self.tls12_gcm, + ) + .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?; + self.tls12_gcm_server_disable_counter = unmasked.tls12_gcm_server_disable_counter; + self.server_auth_unmasked = true; + self.read_buf = unmasked.record; + } else { + self.read_buf = frame; + } + self.read_offset = 0; + Ok(()) + } + + fn process_write_bytes(&mut self, buf: &[u8]) { + if self.client_finished_auth.is_some() { + return; + } + self.write_capture_buf.extend_from_slice(buf); + loop { + if self.write_capture_buf.len() < TLS_RECORD_HEADER_LEN { + return; + } + let payload_len = usize::from(u16::from_be_bytes([ + self.write_capture_buf[3], + self.write_capture_buf[4], + ])); + let record_len = TLS_RECORD_HEADER_LEN + payload_len; + if self.write_capture_buf.len() < record_len { + return; + } + let record = self.write_capture_buf[..record_len].to_vec(); + self.write_capture_buf.drain(..record_len); + if record.first().copied() == Some(TLS_APPLICATION_DATA_RECORD_TYPE) { + self.client_finished_auth = Some(record); + self.write_capture_buf.clear(); + return; + } + } + } + + fn into_inner(self) -> (S, RestlsHandshakeState) { + ( + self.inner, + RestlsHandshakeState { + server_random: self.server_random, + client_finished_auth: self.client_finished_auth, + server_auth_unmasked: self.server_auth_unmasked, + tls12_gcm_server_disable_counter: self.tls12_gcm_server_disable_counter, + }, + ) + } +} + +#[cfg(feature = "boring-browser-fingerprints")] +impl RestlsSignedClientHelloStream { + fn new(inner: RestlsHandshakeStream, secret: [u8; 32]) -> Self { + Self { + inner, + secret, + client_hello_signed: false, + client_hello_buf: Vec::new(), + write_buf: Vec::new(), + write_offset: 0, + } + } + + fn into_inner(self) -> (RestlsHandshakeStream, bool) { + (self.inner, self.client_hello_signed) + } + + fn queue_write(&mut self, buf: &[u8]) -> io::Result<()> { + if self.client_hello_signed { + self.write_buf.extend_from_slice(buf); + return Ok(()); + } + self.client_hello_buf.extend_from_slice(buf); + if let Some(signed) = + restls_tls13_sign_client_hello_record(&self.secret, &self.client_hello_buf)? + { + self.write_buf.extend_from_slice(&signed); + self.client_hello_buf.clear(); + self.client_hello_signed = true; + } + Ok(()) + } + + fn poll_write_pending(&mut self, cx: &mut TaskContext<'_>) -> Poll> + where + S: AsyncRead + AsyncWrite + Unpin, + { + while self.write_offset < self.write_buf.len() { + let pending = self.write_buf[self.write_offset..].to_vec(); + match Pin::new(&mut self.inner).poll_write(cx, &pending) { + Poll::Pending => return Poll::Pending, + Poll::Ready(Err(err)) => return Poll::Ready(Err(err)), + Poll::Ready(Ok(0)) => return Poll::Ready(Err(io::ErrorKind::WriteZero.into())), + Poll::Ready(Ok(written)) => { + self.write_offset += written; + } + } + } + self.write_buf.clear(); + self.write_offset = 0; + Poll::Ready(Ok(())) + } +} + +impl AsyncRead for RestlsHandshakeStream +where + S: AsyncRead + AsyncWrite + Unpin, +{ + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut TaskContext<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + if self.copy_read_buffer(buf) { + return Poll::Ready(Ok(())); + } + loop { + match std::mem::replace( + &mut self.read_stage, + RestlsHandshakeReadStage::Header { + bytes: [0u8; TLS_RECORD_HEADER_LEN], + filled: 0, + }, + ) { + RestlsHandshakeReadStage::Header { mut bytes, mut filled } => { + while filled < bytes.len() { + let mut header_buf = ReadBuf::new(&mut bytes[filled..]); + match Pin::new(&mut self.inner).poll_read(cx, &mut header_buf) { + Poll::Pending => { + self.read_stage = + RestlsHandshakeReadStage::Header { bytes, filled }; + return Poll::Pending; + } + Poll::Ready(Err(err)) => return Poll::Ready(Err(err)), + Poll::Ready(Ok(())) if header_buf.filled().is_empty() => { + self.read_stage = + RestlsHandshakeReadStage::Header { bytes, filled }; + return Poll::Ready(Ok(())); + } + Poll::Ready(Ok(())) => { + filled += header_buf.filled().len(); + } + } + } + let remaining = usize::from(u16::from_be_bytes([bytes[3], bytes[4]])); + self.read_stage = RestlsHandshakeReadStage::Payload { + header: bytes, + bytes: vec![0u8; remaining], + filled: 0, + }; + } + RestlsHandshakeReadStage::Payload { header, mut bytes, mut filled } => { + while filled < bytes.len() { + let mut payload_buf = ReadBuf::new(&mut bytes[filled..]); + match Pin::new(&mut self.inner).poll_read(cx, &mut payload_buf) { + Poll::Pending => { + self.read_stage = + RestlsHandshakeReadStage::Payload { header, bytes, filled }; + return Poll::Pending; + } + Poll::Ready(Err(err)) => return Poll::Ready(Err(err)), + Poll::Ready(Ok(())) if payload_buf.filled().is_empty() => { + return Poll::Ready(Err(io::ErrorKind::UnexpectedEof.into())); + } + Poll::Ready(Ok(())) => { + filled += payload_buf.filled().len(); + } + } + } + let mut frame = Vec::with_capacity(TLS_RECORD_HEADER_LEN + bytes.len()); + frame.extend_from_slice(&header); + frame.extend_from_slice(&bytes); + self.process_read_frame(frame)?; + self.read_stage = RestlsHandshakeReadStage::Header { + bytes: [0u8; TLS_RECORD_HEADER_LEN], + filled: 0, + }; + if self.copy_read_buffer(buf) { + return Poll::Ready(Ok(())); + } + } + } + } + } +} + +impl AsyncWrite for RestlsHandshakeStream +where + S: AsyncRead + AsyncWrite + Unpin, +{ + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut TaskContext<'_>, + buf: &[u8], + ) -> Poll> { + while self.write_offset < self.write_buf.len() { + let offset = self.write_offset; + let pending = self.write_buf[offset..].to_vec(); + match Pin::new(&mut self.inner).poll_write(cx, &pending) { + Poll::Pending => return Poll::Pending, + Poll::Ready(Err(err)) => return Poll::Ready(Err(err)), + Poll::Ready(Ok(0)) => return Poll::Ready(Err(io::ErrorKind::WriteZero.into())), + Poll::Ready(Ok(written)) => { + self.write_offset += written; + self.compact_write_buffer(); + } + } + } + self.process_write_bytes(buf); + match Pin::new(&mut self.inner).poll_write(cx, buf) { + Poll::Pending => Poll::Pending, + Poll::Ready(result) => Poll::Ready(result), + } + } + + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut TaskContext<'_>) -> Poll> { + while self.write_offset < self.write_buf.len() { + let offset = self.write_offset; + let pending = self.write_buf[offset..].to_vec(); + match Pin::new(&mut self.inner).poll_write(cx, &pending) { + Poll::Pending => return Poll::Pending, + Poll::Ready(Err(err)) => return Poll::Ready(Err(err)), + Poll::Ready(Ok(0)) => return Poll::Ready(Err(io::ErrorKind::WriteZero.into())), + Poll::Ready(Ok(written)) => { + self.write_offset += written; + self.compact_write_buffer(); + } + } + } + Pin::new(&mut self.inner).poll_flush(cx) + } + + fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut TaskContext<'_>) -> Poll> { + match self.as_mut().poll_flush(cx) { + Poll::Pending => Poll::Pending, + Poll::Ready(Err(err)) => Poll::Ready(Err(err)), + Poll::Ready(Ok(())) => Pin::new(&mut self.inner).poll_shutdown(cx), + } + } +} + +#[cfg(feature = "boring-browser-fingerprints")] +impl AsyncRead for RestlsSignedClientHelloStream +where + S: AsyncRead + AsyncWrite + Unpin, +{ + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut TaskContext<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + match self.poll_write_pending(cx) { + Poll::Pending => return Poll::Pending, + Poll::Ready(Err(err)) => return Poll::Ready(Err(err)), + Poll::Ready(Ok(())) => {} + } + Pin::new(&mut self.inner).poll_read(cx, buf) + } +} + +#[cfg(feature = "boring-browser-fingerprints")] +impl AsyncWrite for RestlsSignedClientHelloStream +where + S: AsyncRead + AsyncWrite + Unpin, +{ + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut TaskContext<'_>, + buf: &[u8], + ) -> Poll> { + if let Err(err) = self.queue_write(buf) { + return Poll::Ready(Err(err)); + } + match self.poll_write_pending(cx) { + Poll::Ready(Err(err)) => Poll::Ready(Err(err)), + Poll::Pending | Poll::Ready(Ok(())) => Poll::Ready(Ok(buf.len())), + } + } + + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut TaskContext<'_>) -> Poll> { + if !self.client_hello_signed && !self.client_hello_buf.is_empty() { + return Poll::Ready(Err(io::Error::new( + io::ErrorKind::InvalidData, + "incomplete Restls ClientHello before flush", + ))); + } + match self.poll_write_pending(cx) { + Poll::Pending => Poll::Pending, + Poll::Ready(Err(err)) => Poll::Ready(Err(err)), + Poll::Ready(Ok(())) => Pin::new(&mut self.inner).poll_flush(cx), + } + } + + fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut TaskContext<'_>) -> Poll> { + match self.as_mut().poll_flush(cx) { + Poll::Pending => Poll::Pending, + Poll::Ready(Err(err)) => Poll::Ready(Err(err)), + Poll::Ready(Ok(())) => Pin::new(&mut self.inner).poll_shutdown(cx), + } + } +} + +#[allow(dead_code)] +impl RestlsStream { + fn new(inner: S, state: RestlsStreamState) -> Self { + Self { + inner, + state, + read_stage: RestlsRecordReadStage::Header { + bytes: [0u8; TLS_RECORD_HEADER_LEN], + filled: 0, + }, + read_buf: Vec::new(), + read_offset: 0, + write_buf: Vec::new(), + write_offset: 0, + } + } + + fn copy_read_buffer(&mut self, buf: &mut ReadBuf<'_>) -> bool { + if self.read_offset == self.read_buf.len() { + self.read_buf.clear(); + self.read_offset = 0; + return false; + } + let len = (self.read_buf.len() - self.read_offset).min(buf.remaining()); + buf.put_slice(&self.read_buf[self.read_offset..self.read_offset + len]); + self.read_offset += len; + if self.read_offset == self.read_buf.len() { + self.read_buf.clear(); + self.read_offset = 0; + } + true + } + + fn queue_records(&mut self, records: Vec>) { + for record in records { + self.write_buf.extend_from_slice(&record); + } + } + + fn compact_write_buffer(&mut self) { + if self.write_offset == self.write_buf.len() { + self.write_buf.clear(); + self.write_offset = 0; + } + } + + fn process_read_frame(&mut self, frame: Vec) -> io::Result<()> { + let read = self + .state + .read_to_client_record(&frame) + .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?; + self.queue_records(read.records); + self.read_buf = read.data; + self.read_offset = 0; + Ok(()) + } +} + +impl AsyncRead for RestlsStream +where + S: AsyncRead + AsyncWrite + Unpin, +{ + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut TaskContext<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + loop { + while self.write_offset < self.write_buf.len() { + let offset = self.write_offset; + let pending = self.write_buf[offset..].to_vec(); + match Pin::new(&mut self.inner).poll_write(cx, &pending) { + Poll::Pending => return Poll::Pending, + Poll::Ready(Err(err)) => return Poll::Ready(Err(err)), + Poll::Ready(Ok(0)) => return Poll::Ready(Err(io::ErrorKind::WriteZero.into())), + Poll::Ready(Ok(written)) => { + self.write_offset += written; + self.compact_write_buffer(); + } + } + } + if self.copy_read_buffer(buf) { + return Poll::Ready(Ok(())); + } + match std::mem::replace( + &mut self.read_stage, + RestlsRecordReadStage::Header { + bytes: [0u8; TLS_RECORD_HEADER_LEN], + filled: 0, + }, + ) { + RestlsRecordReadStage::Header { mut bytes, mut filled } => { + while filled < bytes.len() { + let mut header_buf = ReadBuf::new(&mut bytes[filled..]); + match Pin::new(&mut self.inner).poll_read(cx, &mut header_buf) { + Poll::Pending => { + self.read_stage = RestlsRecordReadStage::Header { bytes, filled }; + return Poll::Pending; + } + Poll::Ready(Err(err)) => return Poll::Ready(Err(err)), + Poll::Ready(Ok(())) if header_buf.filled().is_empty() => { + self.read_stage = RestlsRecordReadStage::Header { bytes, filled }; + return Poll::Ready(Ok(())); + } + Poll::Ready(Ok(())) => { + filled += header_buf.filled().len(); + } + } + } + let remaining = usize::from(u16::from_be_bytes([bytes[3], bytes[4]])); + self.read_stage = RestlsRecordReadStage::Payload { + header: bytes, + bytes: vec![0u8; remaining], + filled: 0, + }; + } + RestlsRecordReadStage::Payload { header, mut bytes, mut filled } => { + while filled < bytes.len() { + let mut payload_buf = ReadBuf::new(&mut bytes[filled..]); + match Pin::new(&mut self.inner).poll_read(cx, &mut payload_buf) { + Poll::Pending => { + self.read_stage = + RestlsRecordReadStage::Payload { header, bytes, filled }; + return Poll::Pending; + } + Poll::Ready(Err(err)) => return Poll::Ready(Err(err)), + Poll::Ready(Ok(())) if payload_buf.filled().is_empty() => { + return Poll::Ready(Err(io::ErrorKind::UnexpectedEof.into())); + } + Poll::Ready(Ok(())) => { + filled += payload_buf.filled().len(); + } + } + } + let mut frame = Vec::with_capacity(TLS_RECORD_HEADER_LEN + bytes.len()); + frame.extend_from_slice(&header); + frame.extend_from_slice(&bytes); + self.process_read_frame(frame)?; + self.read_stage = RestlsRecordReadStage::Header { + bytes: [0u8; TLS_RECORD_HEADER_LEN], + filled: 0, + }; + } + } + } + } +} + +impl AsyncWrite for RestlsStream +where + S: AsyncRead + AsyncWrite + Unpin, +{ + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut TaskContext<'_>, + buf: &[u8], + ) -> Poll> { + while self.write_offset < self.write_buf.len() { + let offset = self.write_offset; + let pending = self.write_buf[offset..].to_vec(); + match Pin::new(&mut self.inner).poll_write(cx, &pending) { + Poll::Pending => return Poll::Pending, + Poll::Ready(Err(err)) => return Poll::Ready(Err(err)), + Poll::Ready(Ok(0)) => return Poll::Ready(Err(io::ErrorKind::WriteZero.into())), + Poll::Ready(Ok(written)) => { + self.write_offset += written; + self.compact_write_buffer(); + } + } + } + let write = self + .state + .write(buf) + .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?; + let accepted = write.accepted; + self.queue_records(write.records); + while self.write_offset < self.write_buf.len() { + let offset = self.write_offset; + let pending = self.write_buf[offset..].to_vec(); + match Pin::new(&mut self.inner).poll_write(cx, &pending) { + Poll::Pending => return Poll::Ready(Ok(accepted)), + Poll::Ready(Err(err)) => return Poll::Ready(Err(err)), + Poll::Ready(Ok(0)) => return Poll::Ready(Err(io::ErrorKind::WriteZero.into())), + Poll::Ready(Ok(written)) => { + self.write_offset += written; + self.compact_write_buffer(); + } + } + } + Poll::Ready(Ok(accepted)) + } + + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut TaskContext<'_>) -> Poll> { + while self.write_offset < self.write_buf.len() { + let offset = self.write_offset; + let pending = self.write_buf[offset..].to_vec(); + match Pin::new(&mut self.inner).poll_write(cx, &pending) { + Poll::Pending => return Poll::Pending, + Poll::Ready(Err(err)) => return Poll::Ready(Err(err)), + Poll::Ready(Ok(0)) => return Poll::Ready(Err(io::ErrorKind::WriteZero.into())), + Poll::Ready(Ok(written)) => { + self.write_offset += written; + self.compact_write_buffer(); + } + } + } + Pin::new(&mut self.inner).poll_flush(cx) + } + + fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut TaskContext<'_>) -> Poll> { + match self.as_mut().poll_flush(cx) { + Poll::Pending => Poll::Pending, + Poll::Ready(Err(err)) => Poll::Ready(Err(err)), + Poll::Ready(Ok(())) => Pin::new(&mut self.inner).poll_shutdown(cx), + } + } +} + +impl AsyncRead for ShadowTlsV3HandshakeStream +where + S: AsyncRead + AsyncWrite + Unpin, +{ + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut TaskContext<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + loop { + if self.copy_read_buffer(buf) { + return Poll::Ready(Ok(())); + } + match std::mem::replace( + &mut self.read_stage, + ShadowTlsV3ReadStage::Header { bytes: [0u8; 5], filled: 0 }, + ) { + ShadowTlsV3ReadStage::Header { mut bytes, mut filled } => { + while filled < bytes.len() { + let mut header_buf = ReadBuf::new(&mut bytes[filled..]); + match Pin::new(&mut self.inner).poll_read(cx, &mut header_buf) { + Poll::Pending => { + self.read_stage = ShadowTlsV3ReadStage::Header { bytes, filled }; + return Poll::Pending; + } + Poll::Ready(Err(err)) => return Poll::Ready(Err(err)), + Poll::Ready(Ok(())) if header_buf.filled().is_empty() => { + return Poll::Ready(Ok(())); + } + Poll::Ready(Ok(())) => { + filled += header_buf.filled().len(); + } + } + } + let len = u16::from_be_bytes([bytes[3], bytes[4]]) as usize; + self.read_stage = ShadowTlsV3ReadStage::Payload { + header: bytes, + bytes: vec![0u8; len], + filled: 0, + }; + } + ShadowTlsV3ReadStage::Payload { header, mut bytes, mut filled } => { + while filled < bytes.len() { + let mut payload_buf = ReadBuf::new(&mut bytes[filled..]); + match Pin::new(&mut self.inner).poll_read(cx, &mut payload_buf) { + Poll::Pending => { + self.read_stage = + ShadowTlsV3ReadStage::Payload { header, bytes, filled }; + return Poll::Pending; + } + Poll::Ready(Err(err)) => return Poll::Ready(Err(err)), + Poll::Ready(Ok(())) if payload_buf.filled().is_empty() => { + return Poll::Ready(Err(io::ErrorKind::UnexpectedEof.into())); + } + Poll::Ready(Ok(())) => { + filled += payload_buf.filled().len(); + } + } + } + let mut frame = Vec::with_capacity(header.len() + bytes.len()); + frame.extend_from_slice(&header); + frame.extend_from_slice(&bytes); + if let Err(err) = self.process_frame(frame) { + return Poll::Ready(Err(err)); + } + self.read_stage = ShadowTlsV3ReadStage::Header { bytes: [0u8; 5], filled: 0 }; + } + } + } + } +} + +impl AsyncWrite for ShadowTlsV3HandshakeStream +where + S: AsyncRead + AsyncWrite + Unpin, +{ + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut TaskContext<'_>, + buf: &[u8], + ) -> Poll> { + Pin::new(&mut self.inner).poll_write(cx, buf) + } + + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut TaskContext<'_>) -> Poll> { + Pin::new(&mut self.inner).poll_flush(cx) + } + + fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut TaskContext<'_>) -> Poll> { + Pin::new(&mut self.inner).poll_shutdown(cx) + } +} + +#[cfg(feature = "boring-browser-fingerprints")] +impl AsyncRead for ShadowTlsV3SignedClientHelloStream +where + S: AsyncRead + AsyncWrite + Unpin, +{ + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut TaskContext<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + match self.poll_write_pending(cx) { + Poll::Pending => return Poll::Pending, + Poll::Ready(Err(err)) => return Poll::Ready(Err(err)), + Poll::Ready(Ok(())) => {} + } + Pin::new(&mut self.inner).poll_read(cx, buf) + } +} + +#[cfg(feature = "boring-browser-fingerprints")] +impl AsyncWrite for ShadowTlsV3SignedClientHelloStream +where + S: AsyncRead + AsyncWrite + Unpin, +{ + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut TaskContext<'_>, + buf: &[u8], + ) -> Poll> { + if let Err(err) = self.queue_write(buf) { + return Poll::Ready(Err(err)); + } + match self.poll_write_pending(cx) { + Poll::Ready(Err(err)) => Poll::Ready(Err(err)), + Poll::Pending | Poll::Ready(Ok(())) => Poll::Ready(Ok(buf.len())), + } + } + + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut TaskContext<'_>) -> Poll> { + if !self.client_hello_signed && !self.client_hello_buf.is_empty() { + return Poll::Ready(Err(io::Error::new( + io::ErrorKind::InvalidData, + "incomplete ShadowTLS v3 ClientHello before flush", + ))); + } + match self.poll_write_pending(cx) { + Poll::Pending => Poll::Pending, + Poll::Ready(Err(err)) => Poll::Ready(Err(err)), + Poll::Ready(Ok(())) => Pin::new(&mut self.inner).poll_flush(cx), + } + } + + fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut TaskContext<'_>) -> Poll> { + match self.as_mut().poll_flush(cx) { + Poll::Pending => Poll::Pending, + Poll::Ready(Err(err)) => Poll::Ready(Err(err)), + Poll::Ready(Ok(())) => Pin::new(&mut self.inner).poll_shutdown(cx), + } + } +} + +struct ShadowTlsV3Stream { + inner: S, + hmac_add: hmac::Context, + hmac_verify: hmac::Context, + hmac_ignore: Option, + read_buf: Vec, + read_offset: usize, + read_stage: ShadowTlsV3ReadStage, + write_buf: Vec, + write_offset: usize, +} + +impl ShadowTlsV3Stream { + fn new( + inner: S, + password: &str, + server_random: [u8; SHADOW_TLS_V3_TLS_RANDOM_SIZE], + hmac_ignore: Option, + ) -> Self { + Self { + inner, + hmac_add: shadow_tls_v3_hmac(password, &server_random, b"C"), + hmac_verify: shadow_tls_v3_hmac(password, &server_random, b"S"), + hmac_ignore, + read_buf: Vec::new(), + read_offset: 0, + read_stage: ShadowTlsV3ReadStage::Header { bytes: [0u8; 5], filled: 0 }, + write_buf: Vec::new(), + write_offset: 0, + } + } + + fn copy_read_buffer(&mut self, buf: &mut ReadBuf<'_>) -> bool { + if self.read_offset == self.read_buf.len() { + self.read_buf.clear(); + self.read_offset = 0; + return false; + } + let len = (self.read_buf.len() - self.read_offset).min(buf.remaining()); + buf.put_slice(&self.read_buf[self.read_offset..self.read_offset + len]); + self.read_offset += len; + if self.read_offset == self.read_buf.len() { + self.read_buf.clear(); + self.read_offset = 0; + } + true + } + + fn process_frame(&mut self, frame: Vec) -> io::Result { + match frame.first().copied() { + Some(0x15) => Err(io::Error::new( + io::ErrorKind::ConnectionAborted, + "ShadowTLS v3 remote alert", + )), + Some(0x17) => { + if let Some(hmac_ignore) = self.hmac_ignore.as_ref() { + if shadow_tls_v3_application_data_hmac(&frame, hmac_ignore).is_some_and( + |expected| { + expected[..] + == frame + [SHADOW_TLS_V3_TLS_HEADER_SIZE..SHADOW_TLS_V3_HMAC_HEADER_SIZE] + }, + ) { + return Ok(false); + } + } + self.hmac_ignore = None; + let Some(expected) = shadow_tls_v3_application_data_hmac(&frame, &self.hmac_verify) + else { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "ShadowTLS v3 application data frame was invalid", + )); + }; + if expected[..] + != frame[SHADOW_TLS_V3_TLS_HEADER_SIZE..SHADOW_TLS_V3_HMAC_HEADER_SIZE] + { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "ShadowTLS v3 application data verification failed", + )); + } + self.hmac_verify + .update(&frame[SHADOW_TLS_V3_HMAC_HEADER_SIZE..]); + self.hmac_verify.update(&expected); + self.read_buf = frame[SHADOW_TLS_V3_HMAC_HEADER_SIZE..].to_vec(); + self.read_offset = 0; + Ok(true) + } + Some(other) => Err(io::Error::new( + io::ErrorKind::InvalidData, + format!("unexpected ShadowTLS v3 record type {other}"), + )), + None => Ok(false), + } + } + + fn compact_write_buffer(&mut self) { + if self.write_offset == self.write_buf.len() { + self.write_buf.clear(); + self.write_offset = 0; + } + } +} + +impl AsyncRead for ShadowTlsV3Stream +where + S: AsyncRead + AsyncWrite + Unpin, +{ + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut TaskContext<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + loop { + if self.copy_read_buffer(buf) { + return Poll::Ready(Ok(())); + } + match std::mem::replace( + &mut self.read_stage, + ShadowTlsV3ReadStage::Header { bytes: [0u8; 5], filled: 0 }, + ) { + ShadowTlsV3ReadStage::Header { mut bytes, mut filled } => { + while filled < bytes.len() { + let mut header_buf = ReadBuf::new(&mut bytes[filled..]); + match Pin::new(&mut self.inner).poll_read(cx, &mut header_buf) { + Poll::Pending => { + self.read_stage = ShadowTlsV3ReadStage::Header { bytes, filled }; + return Poll::Pending; + } + Poll::Ready(Err(err)) => return Poll::Ready(Err(err)), + Poll::Ready(Ok(())) if header_buf.filled().is_empty() => { + return Poll::Ready(Ok(())); + } + Poll::Ready(Ok(())) => filled += header_buf.filled().len(), + } + } + let len = u16::from_be_bytes([bytes[3], bytes[4]]) as usize; + self.read_stage = ShadowTlsV3ReadStage::Payload { + header: bytes, + bytes: vec![0u8; len], + filled: 0, + }; + } + ShadowTlsV3ReadStage::Payload { header, mut bytes, mut filled } => { + while filled < bytes.len() { + let mut payload_buf = ReadBuf::new(&mut bytes[filled..]); + match Pin::new(&mut self.inner).poll_read(cx, &mut payload_buf) { + Poll::Pending => { + self.read_stage = + ShadowTlsV3ReadStage::Payload { header, bytes, filled }; + return Poll::Pending; + } + Poll::Ready(Err(err)) => return Poll::Ready(Err(err)), + Poll::Ready(Ok(())) if payload_buf.filled().is_empty() => { + return Poll::Ready(Err(io::ErrorKind::UnexpectedEof.into())); + } + Poll::Ready(Ok(())) => filled += payload_buf.filled().len(), + } + } + let mut frame = Vec::with_capacity(header.len() + bytes.len()); + frame.extend_from_slice(&header); + frame.extend_from_slice(&bytes); + match self.process_frame(frame) { + Ok(true) => { + self.read_stage = + ShadowTlsV3ReadStage::Header { bytes: [0u8; 5], filled: 0 }; + continue; + } + Ok(false) => { + self.read_stage = + ShadowTlsV3ReadStage::Header { bytes: [0u8; 5], filled: 0 }; + continue; + } + Err(err) => return Poll::Ready(Err(err)), + } + } + } + } + } +} + +impl AsyncWrite for ShadowTlsV3Stream +where + S: AsyncRead + AsyncWrite + Unpin, +{ + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut TaskContext<'_>, + buf: &[u8], + ) -> Poll> { + while self.write_offset < self.write_buf.len() { + let offset = self.write_offset; + let pending = self.write_buf[offset..].to_vec(); + match Pin::new(&mut self.inner).poll_write(cx, &pending) { + Poll::Pending => return Poll::Pending, + Poll::Ready(Err(err)) => return Poll::Ready(Err(err)), + Poll::Ready(Ok(0)) => return Poll::Ready(Err(io::ErrorKind::WriteZero.into())), + Poll::Ready(Ok(written)) => { + self.write_offset += written; + self.compact_write_buffer(); + } + } + } + let write_len = buf.len().min(SHADOW_TLS_MAX_RECORD_PAYLOAD); + self.write_buf = shadow_tls_v3_record(buf, write_len, &mut self.hmac_add)?; + self.write_offset = 0; + while self.write_offset < self.write_buf.len() { + let offset = self.write_offset; + let pending = self.write_buf[offset..].to_vec(); + match Pin::new(&mut self.inner).poll_write(cx, &pending) { + Poll::Pending => return Poll::Ready(Ok(write_len)), + Poll::Ready(Err(err)) => return Poll::Ready(Err(err)), + Poll::Ready(Ok(0)) => return Poll::Ready(Err(io::ErrorKind::WriteZero.into())), + Poll::Ready(Ok(written)) => { + self.write_offset += written; + self.compact_write_buffer(); + } + } + } + Poll::Ready(Ok(write_len)) + } + + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut TaskContext<'_>) -> Poll> { + while self.write_offset < self.write_buf.len() { + let offset = self.write_offset; + let pending = self.write_buf[offset..].to_vec(); + match Pin::new(&mut self.inner).poll_write(cx, &pending) { + Poll::Pending => return Poll::Pending, + Poll::Ready(Err(err)) => return Poll::Ready(Err(err)), + Poll::Ready(Ok(0)) => return Poll::Ready(Err(io::ErrorKind::WriteZero.into())), + Poll::Ready(Ok(written)) => { + self.write_offset += written; + self.compact_write_buffer(); + } + } + } + Pin::new(&mut self.inner).poll_flush(cx) + } + + fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut TaskContext<'_>) -> Poll> { + match self.as_mut().poll_flush(cx) { + Poll::Pending => Poll::Pending, + Poll::Ready(Err(err)) => Poll::Ready(Err(err)), + Poll::Ready(Ok(())) => Pin::new(&mut self.inner).poll_shutdown(cx), + } + } +} + +struct RateLimitedStream { + inner: S, + bytes_per_second: u64, + burst_capacity: u64, + available: u64, + last_refill: Instant, + sleep: Option>>, +} + +impl RateLimitedStream { + fn new(inner: S, bytes_per_second: u32) -> Self { + let burst_capacity = KCPTUN_RATE_LIMIT_BURST_BYTES; + Self { + inner, + bytes_per_second: u64::from(bytes_per_second), + burst_capacity, + available: burst_capacity, + last_refill: Instant::now(), + sleep: None, + } + } + + fn refill(&mut self) { + if self.bytes_per_second == 0 { + self.available = self.burst_capacity; + self.last_refill = Instant::now(); + return; + } + let now = Instant::now(); + let elapsed = now.saturating_duration_since(self.last_refill); + let tokens = (elapsed + .as_nanos() + .saturating_mul(u128::from(self.bytes_per_second)) + / 1_000_000_000) as u64; + if tokens > 0 { + self.available = self + .available + .saturating_add(tokens) + .min(self.burst_capacity); + self.last_refill = now; + } + } + + fn wait_duration(&self) -> Duration { + if self.bytes_per_second == 0 { + return Duration::ZERO; + } + let nanos = + 1_000_000_000_u64.saturating_add(self.bytes_per_second - 1) / self.bytes_per_second; + Duration::from_nanos(nanos.max(1)) + } +} + +impl AsyncRead for RateLimitedStream +where + S: AsyncRead + Unpin, +{ + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut TaskContext<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + Pin::new(&mut self.inner).poll_read(cx, buf) + } +} + +impl AsyncWrite for RateLimitedStream +where + S: AsyncWrite + Unpin, +{ + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut TaskContext<'_>, + buf: &[u8], + ) -> Poll> { + if buf.is_empty() || self.bytes_per_second == 0 { + return Pin::new(&mut self.inner).poll_write(cx, buf); + } + + loop { + self.refill(); + if self.available > 0 { + break; + } + if self.sleep.is_none() { + self.sleep = Some(Box::pin(tokio::time::sleep(self.wait_duration()))); + } + let sleep = self.sleep.as_mut().expect("rate limit sleep exists"); + match Future::poll(sleep.as_mut(), cx) { + Poll::Pending => return Poll::Pending, + Poll::Ready(()) => { + self.sleep = None; + self.refill(); + if self.available == 0 { + self.available = 1; + } + } + } + } + + let write_len = buf.len().min(self.available as usize); + match Pin::new(&mut self.inner).poll_write(cx, &buf[..write_len]) { + Poll::Ready(Ok(written)) => { + self.available = self.available.saturating_sub(written as u64); + Poll::Ready(Ok(written)) + } + other => other, + } + } + + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut TaskContext<'_>) -> Poll> { + Pin::new(&mut self.inner).poll_flush(cx) + } + + fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut TaskContext<'_>) -> Poll> { + Pin::new(&mut self.inner).poll_shutdown(cx) + } +} + +struct WebSocketStream { + inner: S, + read_stage: WebSocketReadStage, + read_buf: Vec, + read_offset: usize, + write_buf: Vec, + write_offset: usize, +} + +enum WebSocketReadStage { + Header { + bytes: [u8; 2], + filled: usize, + }, + ExtendedLength { + header: [u8; 2], + bytes: [u8; 8], + needed: usize, + filled: usize, + }, + Mask { + opcode: u8, + len: usize, + bytes: [u8; 4], + filled: usize, + }, + Payload { + opcode: u8, + len: usize, + mask: Option<[u8; 4]>, + bytes: Vec, + filled: usize, + }, +} + +impl WebSocketStream { + fn new(inner: S) -> Self { + Self { + inner, + read_stage: WebSocketReadStage::Header { bytes: [0u8; 2], filled: 0 }, + read_buf: Vec::new(), + read_offset: 0, + write_buf: Vec::new(), + write_offset: 0, + } + } + + fn copy_read_buffer(&mut self, buf: &mut ReadBuf<'_>) -> bool { + if self.read_offset == self.read_buf.len() { + self.read_buf.clear(); + self.read_offset = 0; + return false; + } + let len = (self.read_buf.len() - self.read_offset).min(buf.remaining()); + buf.put_slice(&self.read_buf[self.read_offset..self.read_offset + len]); + self.read_offset += len; + if self.read_offset == self.read_buf.len() { + self.read_buf.clear(); + self.read_offset = 0; + } + true + } + + fn compact_write_buffer(&mut self) { + if self.write_offset == self.write_buf.len() { + self.write_buf.clear(); + self.write_offset = 0; + } + } +} + +impl AsyncRead for WebSocketStream +where + S: AsyncRead + AsyncWrite + Unpin, +{ + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut TaskContext<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + if self.copy_read_buffer(buf) { + return Poll::Ready(Ok(())); + } + + loop { + let stage = std::mem::replace( + &mut self.read_stage, + WebSocketReadStage::Header { bytes: [0u8; 2], filled: 0 }, + ); + match stage { + WebSocketReadStage::Header { mut bytes, mut filled } => { + while filled < bytes.len() { + let mut read_buf = ReadBuf::new(&mut bytes[filled..]); + match Pin::new(&mut self.inner).poll_read(cx, &mut read_buf) { + Poll::Pending => { + self.read_stage = WebSocketReadStage::Header { bytes, filled }; + return Poll::Pending; + } + Poll::Ready(Err(err)) => return Poll::Ready(Err(err)), + Poll::Ready(Ok(())) if read_buf.filled().is_empty() => { + if filled == 0 { + return Poll::Ready(Ok(())); + } + return Poll::Ready(Err(io::ErrorKind::UnexpectedEof.into())); + } + Poll::Ready(Ok(())) => filled += read_buf.filled().len(), + } + } + let len_marker = bytes[1] & 0x7f; + if len_marker < 126 { + let len = usize::from(len_marker); + self.read_stage = if bytes[1] & 0x80 == 0 { + WebSocketReadStage::Payload { + opcode: bytes[0] & 0x0f, + len, + mask: None, + bytes: vec![0u8; len], + filled: 0, + } + } else { + WebSocketReadStage::Mask { + opcode: bytes[0] & 0x0f, + len, + bytes: [0u8; 4], + filled: 0, + } + }; + } else { + let needed = if len_marker == 126 { 2 } else { 8 }; + self.read_stage = WebSocketReadStage::ExtendedLength { + header: bytes, + bytes: [0u8; 8], + needed, + filled: 0, + }; + } + } + WebSocketReadStage::ExtendedLength { + header, + mut bytes, + needed, + mut filled, + } => { + while filled < needed { + let mut read_buf = ReadBuf::new(&mut bytes[filled..needed]); + match Pin::new(&mut self.inner).poll_read(cx, &mut read_buf) { + Poll::Pending => { + self.read_stage = WebSocketReadStage::ExtendedLength { + header, + bytes, + needed, + filled, + }; + return Poll::Pending; + } + Poll::Ready(Err(err)) => return Poll::Ready(Err(err)), + Poll::Ready(Ok(())) if read_buf.filled().is_empty() => { + return Poll::Ready(Err(io::ErrorKind::UnexpectedEof.into())); + } + Poll::Ready(Ok(())) => filled += read_buf.filled().len(), + } + } + let len = if needed == 2 { + u16::from_be_bytes([bytes[0], bytes[1]]) as usize + } else { + let len = u64::from_be_bytes(bytes); + match usize::try_from(len) { + Ok(len) => len, + Err(_) => { + return Poll::Ready(Err(io::Error::new( + io::ErrorKind::InvalidData, + "websocket frame is too large", + ))); + } + } + }; + if len > 16 * 1024 * 1024 { + return Poll::Ready(Err(io::Error::new( + io::ErrorKind::InvalidData, + "websocket frame exceeds Burrow frame limit", + ))); + } + self.read_stage = if header[1] & 0x80 == 0 { + WebSocketReadStage::Payload { + opcode: header[0] & 0x0f, + len, + mask: None, + bytes: vec![0u8; len], + filled: 0, + } + } else { + WebSocketReadStage::Mask { + opcode: header[0] & 0x0f, + len, + bytes: [0u8; 4], + filled: 0, + } + }; + } + WebSocketReadStage::Mask { + opcode, + len, + mut bytes, + mut filled, + } => { + while filled < bytes.len() { + let mut read_buf = ReadBuf::new(&mut bytes[filled..]); + match Pin::new(&mut self.inner).poll_read(cx, &mut read_buf) { + Poll::Pending => { + self.read_stage = + WebSocketReadStage::Mask { opcode, len, bytes, filled }; + return Poll::Pending; + } + Poll::Ready(Err(err)) => return Poll::Ready(Err(err)), + Poll::Ready(Ok(())) if read_buf.filled().is_empty() => { + return Poll::Ready(Err(io::ErrorKind::UnexpectedEof.into())); + } + Poll::Ready(Ok(())) => filled += read_buf.filled().len(), + } + } + self.read_stage = WebSocketReadStage::Payload { + opcode, + len, + mask: Some(bytes), + bytes: vec![0u8; len], + filled: 0, + }; + } + WebSocketReadStage::Payload { + opcode, + len, + mask, + mut bytes, + mut filled, + } => { + while filled < len { + let mut read_buf = ReadBuf::new(&mut bytes[filled..]); + match Pin::new(&mut self.inner).poll_read(cx, &mut read_buf) { + Poll::Pending => { + self.read_stage = WebSocketReadStage::Payload { + opcode, + len, + mask, + bytes, + filled, + }; + return Poll::Pending; + } + Poll::Ready(Err(err)) => return Poll::Ready(Err(err)), + Poll::Ready(Ok(())) if read_buf.filled().is_empty() => { + return Poll::Ready(Err(io::ErrorKind::UnexpectedEof.into())); + } + Poll::Ready(Ok(())) => filled += read_buf.filled().len(), + } + } + if let Some(mask) = mask { + apply_websocket_mask(&mut bytes, mask); + } + self.read_stage = WebSocketReadStage::Header { bytes: [0u8; 2], filled: 0 }; + match opcode { + 0x0 | 0x1 | 0x2 => { + self.read_buf = bytes; + if self.copy_read_buffer(buf) { + return Poll::Ready(Ok(())); + } + } + 0x8 => return Poll::Ready(Ok(())), + _ => {} + } + } + } + } + } +} + +impl AsyncWrite for WebSocketStream +where + S: AsyncRead + AsyncWrite + Unpin, +{ + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut TaskContext<'_>, + buf: &[u8], + ) -> Poll> { + while self.write_offset < self.write_buf.len() { + let pending = self.write_buf[self.write_offset..].to_vec(); + match Pin::new(&mut self.inner).poll_write(cx, &pending) { + Poll::Pending => return Poll::Pending, + Poll::Ready(Err(err)) => return Poll::Ready(Err(err)), + Poll::Ready(Ok(0)) => return Poll::Ready(Err(io::ErrorKind::WriteZero.into())), + Poll::Ready(Ok(written)) => { + self.write_offset += written; + self.compact_write_buffer(); + } + } + } + + self.write_buf = websocket_binary_frame(buf)?; + self.write_offset = 0; + while self.write_offset < self.write_buf.len() { + let pending = self.write_buf[self.write_offset..].to_vec(); + match Pin::new(&mut self.inner).poll_write(cx, &pending) { + Poll::Pending => return Poll::Ready(Ok(buf.len())), + Poll::Ready(Err(err)) => return Poll::Ready(Err(err)), + Poll::Ready(Ok(0)) => return Poll::Ready(Err(io::ErrorKind::WriteZero.into())), + Poll::Ready(Ok(written)) => { + self.write_offset += written; + self.compact_write_buffer(); + } + } + } + Poll::Ready(Ok(buf.len())) + } + + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut TaskContext<'_>) -> Poll> { + while self.write_offset < self.write_buf.len() { + let pending = self.write_buf[self.write_offset..].to_vec(); + match Pin::new(&mut self.inner).poll_write(cx, &pending) { + Poll::Pending => return Poll::Pending, + Poll::Ready(Err(err)) => return Poll::Ready(Err(err)), + Poll::Ready(Ok(0)) => return Poll::Ready(Err(io::ErrorKind::WriteZero.into())), + Poll::Ready(Ok(written)) => { + self.write_offset += written; + self.compact_write_buffer(); + } + } + } + Pin::new(&mut self.inner).poll_flush(cx) + } + + fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut TaskContext<'_>) -> Poll> { + match self.as_mut().poll_flush(cx) { + Poll::Pending => Poll::Pending, + Poll::Ready(Err(err)) => Poll::Ready(Err(err)), + Poll::Ready(Ok(())) => Pin::new(&mut self.inner).poll_shutdown(cx), + } + } +} + +struct HttpUpgradeFastOpenStream { + inner: Box, + response: Vec, + validated: bool, +} + +impl HttpUpgradeFastOpenStream { + fn new(inner: Box) -> Self { + Self { + inner, + response: Vec::with_capacity(1024), + validated: false, + } + } +} + +impl AsyncRead for HttpUpgradeFastOpenStream { + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut TaskContext<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + while !self.validated { + if self.response.len() >= 16 * 1024 { + return Poll::Ready(Err(io::Error::new( + io::ErrorKind::InvalidData, + "websocket plugin response headers exceeded Burrow limit", + ))); + } + + let mut byte = [0u8; 1]; + let mut read_buf = ReadBuf::new(&mut byte); + match Pin::new(&mut self.inner).poll_read(cx, &mut read_buf) { + Poll::Pending => return Poll::Pending, + Poll::Ready(Err(err)) => return Poll::Ready(Err(err)), + Poll::Ready(Ok(())) if read_buf.filled().is_empty() => { + return Poll::Ready(Err(io::ErrorKind::UnexpectedEof.into())); + } + Poll::Ready(Ok(())) => { + self.response.push(read_buf.filled()[0]); + if self.response.ends_with(b"\r\n\r\n") { + let response = match std::str::from_utf8(&self.response) { + Ok(response) => response, + Err(_) => { + return Poll::Ready(Err(io::Error::new( + io::ErrorKind::InvalidData, + "websocket plugin response headers were not valid UTF-8", + ))); + } + }; + if let Err(err) = validate_websocket_upgrade_response(response, true, None) + { + return Poll::Ready(Err(io::Error::new( + io::ErrorKind::InvalidData, + err.to_string(), + ))); + } + self.validated = true; + self.response.clear(); + } + } + } + } + + Pin::new(&mut self.inner).poll_read(cx, buf) + } +} + +impl AsyncWrite for HttpUpgradeFastOpenStream { + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut TaskContext<'_>, + buf: &[u8], + ) -> Poll> { + Pin::new(&mut self.inner).poll_write(cx, buf) + } + + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut TaskContext<'_>) -> Poll> { + Pin::new(&mut self.inner).poll_flush(cx) + } + + fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut TaskContext<'_>) -> Poll> { + Pin::new(&mut self.inner).poll_shutdown(cx) + } +} + +struct WebSocketEarlyDataStream { + inner: WebSocketEarlyDataInner, + config: ShadowsocksWebSocketTransport, + path: String, + port: u16, + max_early_data: usize, + request: Vec, + request_offset: usize, + request_flushed: bool, + response: Vec, + first_write_consumed: Option, + websocket_key: Option, +} + +enum WebSocketEarlyDataInner { + Pending(Box), + Raw(Box), + Framed(WebSocketStream>), + Empty, +} + +impl WebSocketEarlyDataStream { + fn new( + inner: Box, + config: ShadowsocksWebSocketTransport, + port: u16, + path: String, + max_early_data: usize, + ) -> Self { + Self { + inner: WebSocketEarlyDataInner::Pending(inner), + config, + path, + port, + max_early_data, + request: Vec::new(), + request_offset: 0, + request_flushed: false, + response: Vec::with_capacity(1024), + first_write_consumed: None, + websocket_key: None, + } + } + + fn start_handshake(&mut self, payload: &[u8]) -> io::Result<()> { + if self.first_write_consumed.is_some() { + return Ok(()); + } + let consumed = payload.len().min(self.max_early_data); + let (request, websocket_key) = websocket_plugin_request( + &self.config, + self.port, + &self.path, + (consumed > 0).then_some(&payload[..consumed]), + ) + .map_err(|err| io::Error::new(io::ErrorKind::InvalidInput, err.to_string()))?; + self.request = request; + self.websocket_key = websocket_key; + self.first_write_consumed = Some(consumed); + Ok(()) + } + + fn poll_handshake(&mut self, cx: &mut TaskContext<'_>) -> Poll> { + loop { + let WebSocketEarlyDataInner::Pending(stream) = &mut self.inner else { + return Poll::Ready(Ok(())); + }; + while self.request_offset < self.request.len() { + let pending = self.request[self.request_offset..].to_vec(); + match Pin::new(&mut **stream).poll_write(cx, &pending) { + Poll::Pending => return Poll::Pending, + Poll::Ready(Err(err)) => return Poll::Ready(Err(err)), + Poll::Ready(Ok(0)) => { + return Poll::Ready(Err(io::ErrorKind::WriteZero.into())); + } + Poll::Ready(Ok(written)) => self.request_offset += written, + } + } + + if !self.request_flushed { + match Pin::new(&mut **stream).poll_flush(cx) { + Poll::Pending => return Poll::Pending, + Poll::Ready(Err(err)) => return Poll::Ready(Err(err)), + Poll::Ready(Ok(())) => self.request_flushed = true, + } + } + + if self.config.v2ray_http_upgrade && self.config.v2ray_http_upgrade_fast_open { + let stream = + match std::mem::replace(&mut self.inner, WebSocketEarlyDataInner::Empty) { + WebSocketEarlyDataInner::Pending(stream) => stream, + _ => unreachable!("early-data fast-open stream must be pending"), + }; + self.inner = + WebSocketEarlyDataInner::Raw(Box::new(HttpUpgradeFastOpenStream::new(stream))); + self.request.clear(); + self.response.clear(); + return Poll::Ready(Ok(())); + } + + while !self.response.ends_with(b"\r\n\r\n") { + if self.response.len() >= 16 * 1024 { + return Poll::Ready(Err(io::Error::new( + io::ErrorKind::InvalidData, + "websocket plugin response headers exceeded Burrow limit", + ))); + } + let mut byte = [0u8; 1]; + let mut read_buf = ReadBuf::new(&mut byte); + match Pin::new(&mut **stream).poll_read(cx, &mut read_buf) { + Poll::Pending => return Poll::Pending, + Poll::Ready(Err(err)) => return Poll::Ready(Err(err)), + Poll::Ready(Ok(())) if read_buf.filled().is_empty() => { + return Poll::Ready(Err(io::ErrorKind::UnexpectedEof.into())); + } + Poll::Ready(Ok(())) => self.response.push(read_buf.filled()[0]), + } + } + + let response = match std::str::from_utf8(&self.response) { + Ok(response) => response, + Err(_) => { + return Poll::Ready(Err(io::Error::new( + io::ErrorKind::InvalidData, + "websocket plugin response headers were not valid UTF-8", + ))); + } + }; + validate_websocket_upgrade_response( + response, + self.config.v2ray_http_upgrade, + self.websocket_key.as_deref(), + ) + .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err.to_string()))?; + let stream = match std::mem::replace(&mut self.inner, WebSocketEarlyDataInner::Empty) { + WebSocketEarlyDataInner::Pending(stream) => stream, + _ => unreachable!("early-data stream must be pending before validation"), + }; + self.inner = if self.config.v2ray_http_upgrade { + WebSocketEarlyDataInner::Raw(stream) + } else { + WebSocketEarlyDataInner::Framed(WebSocketStream::new(stream)) + }; + self.request.clear(); + self.response.clear(); + return Poll::Ready(Ok(())); + } + } +} + +impl AsyncRead for WebSocketEarlyDataStream { + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut TaskContext<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + if matches!(self.inner, WebSocketEarlyDataInner::Pending(_)) { + if let Err(err) = self.start_handshake(&[]) { + return Poll::Ready(Err(err)); + } + match self.poll_handshake(cx) { + Poll::Pending => return Poll::Pending, + Poll::Ready(Err(err)) => return Poll::Ready(Err(err)), + Poll::Ready(Ok(())) => {} + } + } + + match &mut self.inner { + WebSocketEarlyDataInner::Raw(stream) => Pin::new(&mut **stream).poll_read(cx, buf), + WebSocketEarlyDataInner::Framed(stream) => Pin::new(stream).poll_read(cx, buf), + WebSocketEarlyDataInner::Pending(_) => { + unreachable!("pending early-data stream handled") + } + WebSocketEarlyDataInner::Empty => Poll::Ready(Err(io::ErrorKind::NotConnected.into())), + } + } +} + +impl AsyncWrite for WebSocketEarlyDataStream { + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut TaskContext<'_>, + buf: &[u8], + ) -> Poll> { + if matches!(self.inner, WebSocketEarlyDataInner::Pending(_)) { + if let Err(err) = self.start_handshake(buf) { + return Poll::Ready(Err(err)); + } + match self.poll_handshake(cx) { + Poll::Pending => return Poll::Pending, + Poll::Ready(Err(err)) => return Poll::Ready(Err(err)), + Poll::Ready(Ok(())) => { + return Poll::Ready(Ok(self.first_write_consumed.take().unwrap_or(0))); + } + } + } + + match &mut self.inner { + WebSocketEarlyDataInner::Raw(stream) => Pin::new(&mut **stream).poll_write(cx, buf), + WebSocketEarlyDataInner::Framed(stream) => Pin::new(stream).poll_write(cx, buf), + WebSocketEarlyDataInner::Pending(_) => { + unreachable!("pending early-data stream handled") + } + WebSocketEarlyDataInner::Empty => Poll::Ready(Err(io::ErrorKind::NotConnected.into())), + } + } + + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut TaskContext<'_>) -> Poll> { + match &mut self.inner { + WebSocketEarlyDataInner::Pending(stream) => Pin::new(&mut **stream).poll_flush(cx), + WebSocketEarlyDataInner::Raw(stream) => Pin::new(&mut **stream).poll_flush(cx), + WebSocketEarlyDataInner::Framed(stream) => Pin::new(stream).poll_flush(cx), + WebSocketEarlyDataInner::Empty => Poll::Ready(Err(io::ErrorKind::NotConnected.into())), + } + } + + fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut TaskContext<'_>) -> Poll> { + match &mut self.inner { + WebSocketEarlyDataInner::Pending(stream) => Pin::new(&mut **stream).poll_shutdown(cx), + WebSocketEarlyDataInner::Raw(stream) => Pin::new(&mut **stream).poll_shutdown(cx), + WebSocketEarlyDataInner::Framed(stream) => Pin::new(stream).poll_shutdown(cx), + WebSocketEarlyDataInner::Empty => Poll::Ready(Err(io::ErrorKind::NotConnected.into())), + } + } +} + +struct V2rayMuxStream { + inner: S, + read_stage: V2rayMuxReadStage, + read_buf: Vec, + read_offset: usize, + write_buf: Vec, + write_offset: usize, + wrote_open: bool, +} + +enum V2rayMuxReadStage { + MetadataLength { bytes: [u8; 2], filled: usize }, + Metadata { bytes: Vec, filled: usize }, + DataLength { bytes: [u8; 2], filled: usize }, + Data { bytes: Vec, filled: usize }, +} + +impl V2rayMuxStream { + fn new(inner: S) -> Self { + Self { + inner, + read_stage: V2rayMuxReadStage::MetadataLength { bytes: [0u8; 2], filled: 0 }, + read_buf: Vec::new(), + read_offset: 0, + write_buf: Vec::new(), + write_offset: 0, + wrote_open: false, + } + } + + fn copy_read_buffer(&mut self, buf: &mut ReadBuf<'_>) -> bool { + if self.read_offset == self.read_buf.len() { + self.read_buf.clear(); + self.read_offset = 0; + return false; + } + let len = (self.read_buf.len() - self.read_offset).min(buf.remaining()); + buf.put_slice(&self.read_buf[self.read_offset..self.read_offset + len]); + self.read_offset += len; + if self.read_offset == self.read_buf.len() { + self.read_buf.clear(); + self.read_offset = 0; + } + true + } + + fn compact_write_buffer(&mut self) { + if self.write_offset == self.write_buf.len() { + self.write_buf.clear(); + self.write_offset = 0; + } + } +} + +impl AsyncRead for V2rayMuxStream +where + S: AsyncRead + AsyncWrite + Unpin, +{ + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut TaskContext<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + if self.copy_read_buffer(buf) { + return Poll::Ready(Ok(())); + } + + loop { + let stage = std::mem::replace( + &mut self.read_stage, + V2rayMuxReadStage::MetadataLength { bytes: [0u8; 2], filled: 0 }, + ); + match stage { + V2rayMuxReadStage::MetadataLength { mut bytes, mut filled } => { + while filled < bytes.len() { + let mut read_buf = ReadBuf::new(&mut bytes[filled..]); + match Pin::new(&mut self.inner).poll_read(cx, &mut read_buf) { + Poll::Pending => { + self.read_stage = + V2rayMuxReadStage::MetadataLength { bytes, filled }; + return Poll::Pending; + } + Poll::Ready(Err(err)) => return Poll::Ready(Err(err)), + Poll::Ready(Ok(())) if read_buf.filled().is_empty() => { + if filled == 0 { + return Poll::Ready(Ok(())); + } + return Poll::Ready(Err(io::ErrorKind::UnexpectedEof.into())); + } + Poll::Ready(Ok(())) => filled += read_buf.filled().len(), + } + } + let len = u16::from_be_bytes(bytes) as usize; + if len > 512 { + return Poll::Ready(Err(io::Error::new( + io::ErrorKind::InvalidData, + "v2ray-plugin mux metadata is too large", + ))); + } + self.read_stage = V2rayMuxReadStage::Metadata { + bytes: vec![0u8; len], + filled: 0, + }; + } + V2rayMuxReadStage::Metadata { mut bytes, mut filled } => { + while filled < bytes.len() { + let mut read_buf = ReadBuf::new(&mut bytes[filled..]); + match Pin::new(&mut self.inner).poll_read(cx, &mut read_buf) { + Poll::Pending => { + self.read_stage = V2rayMuxReadStage::Metadata { bytes, filled }; + return Poll::Pending; + } + Poll::Ready(Err(err)) => return Poll::Ready(Err(err)), + Poll::Ready(Ok(())) if read_buf.filled().is_empty() => { + return Poll::Ready(Err(io::ErrorKind::UnexpectedEof.into())); + } + Poll::Ready(Ok(())) => filled += read_buf.filled().len(), + } + } + if bytes.len() < 4 { + return Poll::Ready(Err(io::Error::new( + io::ErrorKind::InvalidData, + "v2ray-plugin mux metadata is incomplete", + ))); + } + let status = bytes[2]; + let option = bytes[3]; + if status == 0x04 || option != 0x01 { + self.read_stage = + V2rayMuxReadStage::MetadataLength { bytes: [0u8; 2], filled: 0 }; + } else { + self.read_stage = + V2rayMuxReadStage::DataLength { bytes: [0u8; 2], filled: 0 }; + } + } + V2rayMuxReadStage::DataLength { mut bytes, mut filled } => { + while filled < bytes.len() { + let mut read_buf = ReadBuf::new(&mut bytes[filled..]); + match Pin::new(&mut self.inner).poll_read(cx, &mut read_buf) { + Poll::Pending => { + self.read_stage = V2rayMuxReadStage::DataLength { bytes, filled }; + return Poll::Pending; + } + Poll::Ready(Err(err)) => return Poll::Ready(Err(err)), + Poll::Ready(Ok(())) if read_buf.filled().is_empty() => { + return Poll::Ready(Err(io::ErrorKind::UnexpectedEof.into())); + } + Poll::Ready(Ok(())) => filled += read_buf.filled().len(), + } + } + let len = u16::from_be_bytes(bytes) as usize; + self.read_stage = V2rayMuxReadStage::Data { + bytes: vec![0u8; len], + filled: 0, + }; + } + V2rayMuxReadStage::Data { mut bytes, mut filled } => { + while filled < bytes.len() { + let mut read_buf = ReadBuf::new(&mut bytes[filled..]); + match Pin::new(&mut self.inner).poll_read(cx, &mut read_buf) { + Poll::Pending => { + self.read_stage = V2rayMuxReadStage::Data { bytes, filled }; + return Poll::Pending; + } + Poll::Ready(Err(err)) => return Poll::Ready(Err(err)), + Poll::Ready(Ok(())) if read_buf.filled().is_empty() => { + return Poll::Ready(Err(io::ErrorKind::UnexpectedEof.into())); + } + Poll::Ready(Ok(())) => filled += read_buf.filled().len(), + } + } + self.read_stage = + V2rayMuxReadStage::MetadataLength { bytes: [0u8; 2], filled: 0 }; + self.read_buf = bytes; + if self.copy_read_buffer(buf) { + return Poll::Ready(Ok(())); + } + } + } + } + } +} + +impl AsyncWrite for V2rayMuxStream +where + S: AsyncRead + AsyncWrite + Unpin, +{ + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut TaskContext<'_>, + buf: &[u8], + ) -> Poll> { + while self.write_offset < self.write_buf.len() { + let pending = self.write_buf[self.write_offset..].to_vec(); + match Pin::new(&mut self.inner).poll_write(cx, &pending) { + Poll::Pending => return Poll::Pending, + Poll::Ready(Err(err)) => return Poll::Ready(Err(err)), + Poll::Ready(Ok(0)) => return Poll::Ready(Err(io::ErrorKind::WriteZero.into())), + Poll::Ready(Ok(written)) => { + self.write_offset += written; + self.compact_write_buffer(); + } + } + } + + let payload_len = buf.len().min(u16::MAX as usize); + let payload = &buf[..payload_len]; + self.write_buf = if self.wrote_open { + v2ray_mux_data_frame(payload)? + } else { + self.wrote_open = true; + let mut frame = v2ray_mux_open_frame(); + frame.extend_from_slice(&v2ray_mux_data_frame(payload)?); + frame + }; + self.write_offset = 0; + + while self.write_offset < self.write_buf.len() { + let pending = self.write_buf[self.write_offset..].to_vec(); + match Pin::new(&mut self.inner).poll_write(cx, &pending) { + Poll::Pending => return Poll::Ready(Ok(payload_len)), + Poll::Ready(Err(err)) => return Poll::Ready(Err(err)), + Poll::Ready(Ok(0)) => return Poll::Ready(Err(io::ErrorKind::WriteZero.into())), + Poll::Ready(Ok(written)) => { + self.write_offset += written; + self.compact_write_buffer(); + } + } + } + Poll::Ready(Ok(payload_len)) + } + + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut TaskContext<'_>) -> Poll> { + while self.write_offset < self.write_buf.len() { + let pending = self.write_buf[self.write_offset..].to_vec(); + match Pin::new(&mut self.inner).poll_write(cx, &pending) { + Poll::Pending => return Poll::Pending, + Poll::Ready(Err(err)) => return Poll::Ready(Err(err)), + Poll::Ready(Ok(0)) => return Poll::Ready(Err(io::ErrorKind::WriteZero.into())), + Poll::Ready(Ok(written)) => { + self.write_offset += written; + self.compact_write_buffer(); + } + } + } + Pin::new(&mut self.inner).poll_flush(cx) + } + + fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut TaskContext<'_>) -> Poll> { + match self.as_mut().poll_flush(cx) { + Poll::Pending => Poll::Pending, + Poll::Ready(Err(err)) => Poll::Ready(Err(err)), + Poll::Ready(Ok(())) => Pin::new(&mut self.inner).poll_shutdown(cx), + } + } +} + #[cfg(target_vendor = "apple")] fn verify_native_tls_peer_certificate( - tls: &tokio_native_tls::TlsStream, + tls: &tokio_native_tls::TlsStream, fingerprint: &CertificateFingerprint, + protocol: &str, ) -> Result<()> { let cert = tls .get_ref() .peer_certificate() - .context("failed to read Trojan native TLS peer certificate")? - .ok_or_else(|| anyhow!("Trojan native TLS peer did not provide a certificate"))?; + .with_context(|| format!("failed to read {protocol} native TLS peer certificate"))? + .ok_or_else(|| anyhow!("{protocol} native TLS peer did not provide a certificate"))?; let digest = Sha256::digest( cert.to_der() - .context("failed to encode Trojan native TLS peer certificate")?, + .with_context(|| format!("failed to encode {protocol} native TLS peer certificate"))?, ); if digest.as_slice() != fingerprint.0 { - bail!("Trojan certificate fingerprint mismatch"); + bail!("{protocol} certificate fingerprint mismatch"); } Ok(()) } @@ -1341,13 +7059,37 @@ fn trojan_tls_config( alpn: &[String], skip_cert_verify: bool, certificate_fingerprint: Option<&CertificateFingerprint>, +) -> Result { + rustls_tls_config(alpn, skip_cert_verify, certificate_fingerprint, None, None) +} + +fn rustls_tls_config( + alpn: &[String], + skip_cert_verify: bool, + certificate_fingerprint: Option<&CertificateFingerprint>, + client_identity: Option<&TlsClientIdentity>, + ech_config_list: Option<&[u8]>, ) -> Result { install_rustls_crypto_provider(); let mut root_store = RootCertStore::empty(); root_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned()); - let mut config = ClientConfig::builder() - .with_root_certificates(root_store) - .with_no_client_auth(); + let builder = if let Some(ech_config_list) = ech_config_list { + let ech_config = rustls_ech_config(ech_config_list)?; + ClientConfig::builder_with_provider(Arc::new(rustls::crypto::aws_lc_rs::default_provider())) + .with_ech(EchMode::Enable(ech_config)) + .context("failed to enable ECH for Shadowsocks websocket plugin")? + } else { + ClientConfig::builder() + }; + let builder = builder.with_root_certificates(root_store); + let mut config = if let Some(identity) = client_identity { + let (cert_chain, private_key) = load_rustls_client_identity(identity)?; + builder + .with_client_auth_cert(cert_chain, private_key) + .context("failed to configure TLS client certificate")? + } else { + builder.with_no_client_auth() + }; config.alpn_protocols = alpn.iter().map(|value| value.as_bytes().to_vec()).collect(); if let Some(fingerprint) = certificate_fingerprint { config @@ -1363,7 +7105,14 @@ fn trojan_tls_config( Ok(config) } -#[cfg(not(target_vendor = "apple"))] +fn rustls_ech_config(config_list: &[u8]) -> Result { + EchConfig::new( + EchConfigListBytes::from(config_list), + rustls::crypto::aws_lc_rs::hpke::ALL_SUPPORTED_SUITES, + ) + .context("failed to parse Shadowsocks websocket ECH config list") +} + fn install_rustls_crypto_provider() { static INSTALL: Once = Once::new(); INSTALL.call_once( @@ -1388,7 +7137,7 @@ fn parse_certificate_fingerprint(value: &str) -> Result ); } if normalized.len() != 64 { - bail!("Trojan certificate fingerprint must be a SHA-256 hex digest"); + bail!("TLS certificate fingerprint must be a SHA-256 hex digest"); } let mut bytes = [0u8; 32]; for (idx, chunk) in normalized.as_bytes().chunks_exact(2).enumerate() { @@ -1399,12 +7148,191 @@ fn parse_certificate_fingerprint(value: &str) -> Result Ok(CertificateFingerprint(bytes)) } +fn tls_material_bytes(value: &str, label: &str) -> Result> { + let trimmed = value.trim(); + if trimmed.contains("-----BEGIN ") { + return Ok(trimmed.as_bytes().to_vec()); + } + fs::read(trimmed).with_context(|| format!("failed to read TLS {label} from {trimmed}")) +} + +fn load_rustls_client_identity( + identity: &TlsClientIdentity, +) -> Result<(Vec>, PrivateKeyDer<'static>)> { + let certificate = tls_material_bytes(&identity.certificate, "client certificate")?; + let private_key = tls_material_bytes(&identity.private_key, "client private key")?; + + let mut certificate_reader = io::Cursor::new(certificate); + let certificate_chain = rustls_pemfile::certs(&mut certificate_reader) + .collect::, _>>() + .context("failed to parse TLS client certificate PEM")?; + if certificate_chain.is_empty() { + bail!("TLS client certificate PEM did not contain any certificates"); + } + + let mut private_key_reader = io::Cursor::new(private_key); + let private_key = rustls_pemfile::private_key(&mut private_key_reader) + .context("failed to parse TLS client private key PEM")? + .ok_or_else(|| anyhow!("TLS client private key PEM did not contain a private key"))?; + + Ok((certificate_chain, private_key)) +} + +fn load_shadow_rustls_client_identity( + identity: &TlsClientIdentity, +) -> Result<(Vec, shadow_rustls::PrivateKey)> { + let certificate = tls_material_bytes(&identity.certificate, "client certificate")?; + let private_key = tls_material_bytes(&identity.private_key, "client private key")?; + + let mut certificate_reader = io::Cursor::new(certificate); + let certificate_chain = rustls_pemfile::certs(&mut certificate_reader) + .collect::, _>>() + .context("failed to parse TLS client certificate PEM")? + .into_iter() + .map(|certificate| shadow_rustls::Certificate(certificate.as_ref().to_vec())) + .collect::>(); + if certificate_chain.is_empty() { + bail!("TLS client certificate PEM did not contain any certificates"); + } + + let mut private_key_reader = io::Cursor::new(private_key); + let private_key = rustls_pemfile::private_key(&mut private_key_reader) + .context("failed to parse TLS client private key PEM")? + .ok_or_else(|| anyhow!("TLS client private key PEM did not contain a private key"))?; + + Ok(( + certificate_chain, + shadow_rustls::PrivateKey(private_key.secret_der().to_vec()), + )) +} + +#[cfg(feature = "boring-browser-fingerprints")] +fn load_boring_client_identity( + identity: &TlsClientIdentity, +) -> Result { + let certificate = tls_material_bytes(&identity.certificate, "client certificate")?; + let private_key = tls_material_bytes(&identity.private_key, "client private key")?; + + let mut certificate_reader = io::Cursor::new(certificate); + let certificate_chain = rustls_pemfile::certs(&mut certificate_reader) + .collect::, _>>() + .context("failed to parse TLS client certificate PEM")? + .into_iter() + .map(|certificate| { + BoringX509::from_der(certificate.as_ref()) + .context("failed to parse TLS client certificate as X.509") + }) + .collect::>>()?; + if certificate_chain.is_empty() { + bail!("TLS client certificate PEM did not contain any certificates"); + } + + let mut private_key_reader = io::Cursor::new(private_key); + let private_key = rustls_pemfile::private_key(&mut private_key_reader) + .context("failed to parse TLS client private key PEM")? + .ok_or_else(|| anyhow!("TLS client private key PEM did not contain a private key"))?; + let private_key = BoringPKey::private_key_from_der(private_key.secret_der()) + .context("failed to parse TLS client private key for BoringSSL")?; + + Ok(BoringConnectorConfigClientAuth { + cert_chain: certificate_chain, + private_key, + }) +} + +#[cfg(target_vendor = "apple")] +fn load_native_tls_identity(identity: &TlsClientIdentity) -> Result { + let certificate = tls_material_bytes(&identity.certificate, "client certificate")?; + let private_key = tls_material_bytes(&identity.private_key, "client private key")?; + NativeTlsIdentity::from_pkcs8(&certificate, &private_key) + .context("failed to configure TLS client certificate") +} + fn unsupported_client_fingerprint(config: &TrojanNode) -> Option<&str> { - let value = config.client_fingerprint.as_deref()?.trim(); + unsupported_client_fingerprint_value(config.client_fingerprint.as_deref()) +} + +fn unsupported_client_fingerprint_value(value: Option<&str>) -> Option<&str> { + let value = value?.trim(); if value.is_empty() || value.eq_ignore_ascii_case("none") { - None - } else { - Some(value) + return None; + } + MihomoClientFingerprint::from_mihomo_name(value) + .is_some() + .then_some(value) +} + +impl MihomoClientFingerprint { + fn from_mihomo_name(value: &str) -> Option { + Some(match value.trim().to_ascii_lowercase().as_str() { + "chrome" => Self::Chrome, + "firefox" => Self::Firefox, + "safari" => Self::Safari, + "ios" => Self::Ios, + "android" => Self::Android, + "edge" => Self::Edge, + "360" => Self::Browser360, + "qq" => Self::Qq, + "random" => Self::Random, + "chrome120" => Self::Chrome120, + "firefox120" => Self::Firefox120, + "safari16" => Self::Safari16, + "chrome_psk" => Self::ChromePsk, + "chrome_psk_shuffle" => Self::ChromePskShuffle, + "chrome_padding_psk_shuffle" => Self::ChromePaddingPskShuffle, + "chrome_pq" => Self::ChromePq, + "chrome_pq_psk" => Self::ChromePqPsk, + "randomized" => Self::Randomized, + _ => return None, + }) + } + + fn mihomo_name(self) -> &'static str { + match self { + Self::Chrome => "chrome", + Self::Firefox => "firefox", + Self::Safari => "safari", + Self::Ios => "ios", + Self::Android => "android", + Self::Edge => "edge", + Self::Browser360 => "360", + Self::Qq => "qq", + Self::Random => "random", + Self::Chrome120 => "chrome120", + Self::Firefox120 => "firefox120", + Self::Safari16 => "safari16", + Self::ChromePsk => "chrome_psk", + Self::ChromePskShuffle => "chrome_psk_shuffle", + Self::ChromePaddingPskShuffle => "chrome_padding_psk_shuffle", + Self::ChromePq => "chrome_pq", + Self::ChromePqPsk => "chrome_pq_psk", + Self::Randomized => "randomized", + } + } + + fn shadow_tls_v2_boring_supported(self) -> bool { + #[cfg(not(feature = "boring-browser-fingerprints"))] + { + let _ = self; + false + } + #[cfg(feature = "boring-browser-fingerprints")] + matches!( + self, + Self::Chrome + | Self::Firefox + | Self::Safari + | Self::Ios + | Self::Android + | Self::Edge + | Self::Random + | Self::Safari16 + ) + } + + #[cfg_attr(not(feature = "boring-browser-fingerprints"), allow(dead_code))] + fn shadow_tls_v3_boring_supported(self) -> bool { + self.shadow_tls_v2_boring_supported() } } @@ -1418,12 +7346,10 @@ fn hex_nibble(byte: u8) -> Option { } #[derive(Debug)] -#[cfg(not(target_vendor = "apple"))] struct PinnedCertificateVerifier { fingerprint: CertificateFingerprint, } -#[cfg(not(target_vendor = "apple"))] impl ServerCertVerifier for PinnedCertificateVerifier { fn verify_server_cert( &self, @@ -1468,7 +7394,6 @@ impl ServerCertVerifier for PinnedCertificateVerifier { } } -#[cfg(not(target_vendor = "apple"))] fn certificate_fingerprint_matches( certificate: &CertificateDer<'_>, fingerprint: &CertificateFingerprint, @@ -1478,10 +7403,8 @@ fn certificate_fingerprint_matches( } #[derive(Debug)] -#[cfg(not(target_vendor = "apple"))] struct NoCertificateVerifier; -#[cfg(not(target_vendor = "apple"))] impl ServerCertVerifier for NoCertificateVerifier { fn verify_server_cert( &self, @@ -1517,7 +7440,6 @@ impl ServerCertVerifier for NoCertificateVerifier { } } -#[cfg(not(target_vendor = "apple"))] fn supported_verify_schemes() -> Vec { vec![ SignatureScheme::ECDSA_NISTP256_SHA256, @@ -1532,6 +7454,72 @@ fn supported_verify_schemes() -> Vec { ] } +#[derive(Debug)] +struct ShadowPinnedCertificateVerifier { + fingerprint: CertificateFingerprint, +} + +impl shadow_rustls::client::ServerCertVerifier for ShadowPinnedCertificateVerifier { + fn verify_server_cert( + &self, + end_entity: &shadow_rustls::Certificate, + intermediates: &[shadow_rustls::Certificate], + _server_name: &shadow_rustls::ServerName, + _scts: &mut dyn Iterator, + _ocsp_response: &[u8], + _now: SystemTime, + ) -> std::result::Result { + let mut certificates = std::iter::once(end_entity).chain(intermediates.iter()); + if certificates.any(|certificate| { + let digest = Sha256::digest(&certificate.0); + digest[..] == self.fingerprint.0[..] + }) { + Ok(shadow_rustls::client::ServerCertVerified::assertion()) + } else { + Err(shadow_rustls::Error::General( + "certificate chain does not match pinned fingerprint".to_owned(), + )) + } + } +} + +#[derive(Debug)] +struct ShadowNoCertificateVerifier; + +impl shadow_rustls::client::ServerCertVerifier for ShadowNoCertificateVerifier { + fn verify_server_cert( + &self, + _end_entity: &shadow_rustls::Certificate, + _intermediates: &[shadow_rustls::Certificate], + _server_name: &shadow_rustls::ServerName, + _scts: &mut dyn Iterator, + _ocsp_response: &[u8], + _now: SystemTime, + ) -> std::result::Result { + Ok(shadow_rustls::client::ServerCertVerified::assertion()) + } + + fn verify_tls12_signature( + &self, + _message: &[u8], + _cert: &shadow_rustls::Certificate, + _dss: &shadow_rustls::DigitallySignedStruct, + ) -> std::result::Result + { + Ok(shadow_rustls::client::HandshakeSignatureValid::assertion()) + } + + fn verify_tls13_signature( + &self, + _message: &[u8], + _cert: &shadow_rustls::Certificate, + _dss: &shadow_rustls::DigitallySignedStruct, + ) -> std::result::Result + { + Ok(shadow_rustls::client::HandshakeSignatureValid::assertion()) + } +} + async fn write_trojan_header( writer: &mut W, password: &str, @@ -1614,226 +7602,232 @@ where Ok((source, buf[..payload_len].to_vec())) } -struct SsWriter { - writer: W, - key: aead::LessSafeKey, - nonce: [u8; 12], +include!("proxy_runtime/shadowsocks_crypto.rs"); + +include!("proxy_runtime/shadowsocks_plugins.rs"); + +fn validate_shadowsocks_cipher(name: &str) -> Result<()> { + if shadowsocks_cipher_kind(name).is_ok() + || CustomSsAeadKind::from_name(name).is_some() + || CustomSsStreamKind::from_name(name).is_some() + || Aead2022AesCcmKind::from_name(name).is_some() + { + return Ok(()); + } + bail!("unsupported Shadowsocks cipher {name}") } -impl SsWriter -where - W: AsyncWrite + Unpin, -{ - async fn new(mut writer: W, cipher: SsCipherKind, master_key: Vec) -> Result { - let mut salt = vec![0u8; cipher.salt_len()]; - OsRng.fill_bytes(&mut salt); - let key = ss_subkey(cipher, &master_key, &salt)?; - let nonce = [0u8; 12]; - writer - .write_all(&salt) - .await - .context("failed to write Shadowsocks request salt")?; - Ok(Self { writer, key, nonce }) - } - - async fn write_chunk(&mut self, payload: &[u8]) -> Result<()> { - if payload.is_empty() { - return Ok(()); - } - let mut offset = 0; - while offset < payload.len() { - let end = (offset + 0x3fff).min(payload.len()); - let chunk = &payload[offset..end]; - - let len = (chunk.len() as u16).to_be_bytes(); - let mut encrypted_len = len.to_vec(); - seal_in_place(&self.key, &mut self.nonce, &mut encrypted_len)?; - self.writer.write_all(&encrypted_len).await?; - - let mut encrypted_payload = chunk.to_vec(); - seal_in_place(&self.key, &mut self.nonce, &mut encrypted_payload)?; - self.writer.write_all(&encrypted_payload).await?; - offset = end; - } - self.writer.flush().await?; - Ok(()) - } - - async fn shutdown(&mut self) -> Result<()> { - self.writer.shutdown().await?; - Ok(()) +fn normalize_uot_version(version: u8) -> Result { + match version { + 0 | UOT_LEGACY_VERSION => Ok(UOT_LEGACY_VERSION), + UOT_VERSION => Ok(UOT_VERSION), + other => bail!("unsupported Shadowsocks udp-over-tcp version {other}"), } } -struct SsReader { - reader: R, - cipher: SsCipherKind, - master_key: Vec, - key: Option, - nonce: [u8; 12], +fn shadowsocks_sing_mux_transport( + smux: Option<&ShadowsocksSmux>, + _config: &ShadowsocksNode, + protocol: &ShadowsocksProtocol, +) -> Result> { + let Some(smux) = smux.filter(|smux| smux.enabled) else { + return Ok(None); + }; + let protocol_name = smux.protocol.as_deref().unwrap_or("h2mux"); + let sing_mux_protocol = if protocol_name.eq_ignore_ascii_case("h2mux") { + ShadowsocksSingMuxProtocol::H2Mux + } else if protocol_name.eq_ignore_ascii_case("smux") { + ShadowsocksSingMuxProtocol::Smux + } else if protocol_name.eq_ignore_ascii_case("yamux") { + ShadowsocksSingMuxProtocol::Yamux + } else { + bail!("top-level Shadowsocks sing-mux protocol {protocol_name} is not supported yet"); + }; + let brutal = smux + .brutal + .as_ref() + .filter(|brutal| brutal.enabled) + .map(|brutal| ShadowsocksSingMuxBrutalTransport { + send_bps: mihomo_string_to_bps(brutal.up.as_deref().unwrap_or("")), + receive_bps: mihomo_string_to_bps(brutal.down.as_deref().unwrap_or("")), + }); + match protocol { + ShadowsocksProtocol::None + | ShadowsocksProtocol::Standard { .. } + | ShadowsocksProtocol::CustomAead { .. } + | ShadowsocksProtocol::CustomStream { .. } + | ShadowsocksProtocol::Aead2022AesCcm { .. } => {} + } + Ok(Some(ShadowsocksSingMuxTransport { + protocol: sing_mux_protocol, + max_connections: smux.max_connections.unwrap_or(0) as usize, + min_streams: smux.min_streams.unwrap_or(0) as usize, + max_streams: smux.max_streams.unwrap_or(0) as usize, + padding: smux.padding, + udp: !smux.only_tcp, + brutal, + })) } -impl SsReader -where - R: AsyncRead + Unpin, -{ - fn new(reader: R, cipher: SsCipherKind, master_key: Vec) -> Self { - Self { - reader, +fn mihomo_string_to_bps(value: &str) -> u64 { + let value = value.trim(); + if value.is_empty() { + return 0; + } + if let Ok(mbps) = value.parse::() { + return mbps.saturating_mul(1_000_000 / 8); + } + + let digit_len = value + .as_bytes() + .iter() + .take_while(|byte| byte.is_ascii_digit()) + .count(); + if digit_len == 0 { + return 0; + } + let Ok(amount) = value[..digit_len].parse::() else { + return 0; + }; + let unit = value[digit_len..].trim(); + let Some(suffix) = unit.strip_suffix("ps") else { + return 0; + }; + let (scale, bits) = match suffix { + "b" => (1, true), + "B" => (1, false), + "Kb" => (1_000, true), + "KB" => (1_000, false), + "Mb" => (1_000_000, true), + "MB" => (1_000_000, false), + "Gb" => (1_000_000_000, true), + "GB" => (1_000_000_000, false), + "Tb" => (1_000_000_000_000, true), + "TB" => (1_000_000_000_000, false), + _ => return 0, + }; + let bytes = amount.saturating_mul(scale); + if bits { + bytes / 8 + } else { + bytes + } +} + +fn shadowsocks_protocol_for_node( + node: &ProxyNode, + config: &ShadowsocksNode, +) -> Result { + if config.cipher.trim().eq_ignore_ascii_case("none") { + return Ok(ShadowsocksProtocol::None); + } + + if let Some(kind) = Aead2022AesCcmKind::from_name(&config.cipher) { + let (user_key, identity_keys) = parse_aead2022_keys(kind, &config.password)?; + return Ok(ShadowsocksProtocol::Aead2022AesCcm { kind, user_key, identity_keys }); + } + + if let Ok(cipher) = shadowsocks_cipher_kind(&config.cipher) { + validate_standard_aead2022_mihomo_password(cipher, &config.password)?; + let server_config = SsServerConfig::new( + SsServerAddr::DomainName(node.server.clone(), node.port), + config.password.clone(), cipher, - master_key, - key: None, - nonce: [0u8; 12], + ) + .with_context(|| { + format!( + "failed to configure Shadowsocks cipher {} for node {}", + config.cipher, node.name + ) + })?; + return Ok(ShadowsocksProtocol::Standard { + server_config: Arc::new(server_config), + context: SsContext::new_shared(SsServerType::Local), + }); + } + + if let Some(kind) = CustomSsAeadKind::from_name(&config.cipher) { + let master_key = evp_bytes_to_key(config.password.as_bytes(), kind.key_len()); + return Ok(ShadowsocksProtocol::CustomAead { + kind, + master_key: Arc::from(master_key), + }); + } + + if let Some(kind) = CustomSsStreamKind::from_name(&config.cipher) { + let key = evp_bytes_to_key(config.password.as_bytes(), kind.key_len()); + return Ok(ShadowsocksProtocol::CustomStream { kind, key: Arc::from(key) }); + } + + bail!("unsupported Shadowsocks cipher {}", config.cipher) +} + +fn shadowsocks_cipher_kind(name: &str) -> Result { + let canonical = canonical_shadowsocks_cipher_name(name); + SsCipherKind::from_str(&canonical).map_err(|_| anyhow!("unsupported Shadowsocks cipher {name}")) +} + +fn validate_standard_aead2022_mihomo_password(cipher: SsCipherKind, password: &str) -> Result<()> { + if !cipher.is_aead_2022() { + return Ok(()); + } + + if password.is_empty() { + bail!("Shadowsocks 2022 password is missing"); + } + + let supports_eih = matches!( + cipher, + SsCipherKind::AEAD2022_BLAKE3_AES_128_GCM | SsCipherKind::AEAD2022_BLAKE3_AES_256_GCM + ); + let segments = password.split(':').collect::>(); + if segments.len() > 1 && !supports_eih { + bail!("Shadowsocks 2022 EIH support only available in AES ciphers"); + } + + for segment in segments { + let decoded = BASE64_STANDARD + .decode(segment) + .context("failed to decode Shadowsocks 2022 base64 key")?; + if decoded.len() != cipher.key_len() { + bail!( + "Shadowsocks 2022 key length mismatch: required {}, got {}", + cipher.key_len(), + decoded.len() + ); } } - async fn read_chunk(&mut self, output: &mut Vec) -> Result { - if self.key.is_none() { - let mut salt = vec![0u8; self.cipher.salt_len()]; - self.reader - .read_exact(&mut salt) - .await - .context("failed to read Shadowsocks response salt")?; - self.key = Some(ss_subkey(self.cipher, &self.master_key, &salt)?); - } - let key = self.key.as_ref().expect("key initialized"); - let tag_len = aead::MAX_TAG_LEN; - let mut encrypted_len = vec![0u8; 2 + tag_len]; - if let Err(err) = self.reader.read_exact(&mut encrypted_len).await { - if err.kind() == std::io::ErrorKind::UnexpectedEof { - return Ok(0); - } - return Err(err).context("failed to read Shadowsocks encrypted chunk length"); - } - open_in_place(key, &mut self.nonce, &mut encrypted_len)?; - let payload_len = u16::from_be_bytes([encrypted_len[0], encrypted_len[1]]) as usize; - if payload_len == 0 { - return Ok(0); - } - let mut encrypted_payload = vec![0u8; payload_len + tag_len]; - self.reader - .read_exact(&mut encrypted_payload) - .await - .context("failed to read Shadowsocks encrypted chunk")?; - let plaintext = open_in_place(key, &mut self.nonce, &mut encrypted_payload)?; - output.extend_from_slice(plaintext); - Ok(plaintext.len()) - } -} - -fn encrypt_ss_udp_packet( - cipher: SsCipherKind, - master_key: &[u8], - target: SocketAddr, - payload: &[u8], -) -> Result> { - let mut salt = vec![0u8; cipher.salt_len()]; - OsRng.fill_bytes(&mut salt); - let key = ss_subkey(cipher, master_key, &salt)?; - let mut nonce = [0u8; 12]; - let mut plaintext = socks_addr(target)?; - plaintext.extend_from_slice(payload); - seal_in_place(&key, &mut nonce, &mut plaintext)?; - let mut packet = salt; - packet.extend_from_slice(&plaintext); - Ok(packet) -} - -fn decrypt_ss_udp_packet( - cipher: SsCipherKind, - master_key: &[u8], - packet: &[u8], -) -> Result<(SocketAddr, Vec)> { - let salt_len = cipher.salt_len(); - if packet.len() <= salt_len { - bail!("Shadowsocks UDP packet missing salt"); - } - let (salt, encrypted) = packet.split_at(salt_len); - let key = ss_subkey(cipher, master_key, salt)?; - let mut nonce = [0u8; 12]; - let mut buf = encrypted.to_vec(); - let plaintext = open_in_place(&key, &mut nonce, &mut buf)?; - let (source, used) = parse_socks_addr(plaintext)?; - Ok((source, plaintext[used..].to_vec())) -} - -fn seal_in_place(key: &aead::LessSafeKey, nonce: &mut [u8; 12], buf: &mut Vec) -> Result<()> { - key.seal_in_place_append_tag( - aead::Nonce::assume_unique_for_key(*nonce), - aead::Aad::empty(), - buf, - ) - .map_err(|_| anyhow!("Shadowsocks AEAD seal failed"))?; - increment_nonce(nonce); Ok(()) } -fn open_in_place<'a>( - key: &aead::LessSafeKey, - nonce: &mut [u8; 12], - buf: &'a mut [u8], -) -> Result<&'a [u8]> { - let plaintext = key - .open_in_place( - aead::Nonce::assume_unique_for_key(*nonce), - aead::Aad::empty(), - buf, - ) - .map_err(|_| anyhow!("Shadowsocks AEAD open failed"))?; - increment_nonce(nonce); - Ok(plaintext) +fn canonical_shadowsocks_cipher_name(name: &str) -> String { + match name.trim().to_ascii_lowercase().as_str() { + "aead_aes_128_gcm" => "aes-128-gcm".to_owned(), + "aead_aes_256_gcm" => "aes-256-gcm".to_owned(), + "chacha20-poly1305" | "aead_chacha20_poly1305" => "chacha20-ietf-poly1305".to_owned(), + other => other.to_owned(), + } } -fn increment_nonce(nonce: &mut [u8; 12]) { - for byte in nonce { - let (next, overflow) = byte.overflowing_add(1); - *byte = next; - if !overflow { - break; +fn shadowsocks_addr_for_target(target: &ProxyTcpTarget) -> Result { + if let Some(hostname) = target.hostname.as_deref() { + let hostname = hostname.trim_end_matches('.'); + if !hostname.is_empty() && hostname.len() <= u8::MAX as usize { + return Ok(SsAddress::DomainNameAddress( + hostname.to_owned(), + target.address.port(), + )); } } + Ok(SsAddress::SocketAddress(target.address)) } -fn ss_subkey(cipher: SsCipherKind, master_key: &[u8], salt: &[u8]) -> Result { - let prk = hmac::sign( - &hmac::Key::new(hmac::HMAC_SHA1_FOR_LEGACY_USE_ONLY, salt), - master_key, - ); - let prk = hmac::Key::new(hmac::HMAC_SHA1_FOR_LEGACY_USE_ONLY, prk.as_ref()); - let mut okm = Vec::with_capacity(cipher.key_len()); - let mut previous = Vec::new(); - let mut counter = 1u8; - while okm.len() < cipher.key_len() { - let mut input = Vec::with_capacity(previous.len() + SS_INFO.len() + 1); - input.extend_from_slice(&previous); - input.extend_from_slice(SS_INFO); - input.push(counter); - previous = hmac::sign(&prk, &input).as_ref().to_vec(); - okm.extend_from_slice(&previous); - counter = counter - .checked_add(1) - .ok_or_else(|| anyhow!("Shadowsocks HKDF output too long"))?; +fn shadowsocks_addr_to_socket_addr(addr: SsAddress) -> Result { + match addr { + SsAddress::SocketAddress(addr) => Ok(addr), + SsAddress::DomainNameAddress(domain, port) => resolve_server(&domain, port), } - okm.truncate(cipher.key_len()); - let unbound = aead::UnboundKey::new(cipher.algorithm(), &okm) - .map_err(|_| anyhow!("invalid Shadowsocks AEAD key"))?; - Ok(aead::LessSafeKey::new(unbound)) -} - -fn evp_bytes_to_key(password: &[u8], key_len: usize) -> Vec { - let mut out = Vec::with_capacity(key_len); - let mut previous = Vec::new(); - while out.len() < key_len { - let mut hasher = Md5::new(); - if !previous.is_empty() { - hasher.update(&previous); - } - hasher.update(password); - previous = hasher.finalize().to_vec(); - out.extend_from_slice(&previous); - } - out.truncate(key_len); - out } fn socks_addr(addr: SocketAddr) -> Result> { @@ -1852,21 +7846,518 @@ fn socks_addr(addr: SocketAddr) -> Result> { Ok(encoded) } +fn socks_domain_addr(domain: &str, port: u16) -> Result> { + let domain = domain.trim_end_matches('.'); + if domain.is_empty() || domain.len() > u8::MAX as usize { + bail!("invalid SOCKS domain length"); + } + let mut encoded = Vec::with_capacity(1 + 1 + domain.len() + 2); + encoded.push(3); + encoded.push(domain.len() as u8); + encoded.extend_from_slice(domain.as_bytes()); + encoded.extend_from_slice(&port.to_be_bytes()); + Ok(encoded) +} + fn socks_addr_for_target(target: &ProxyTcpTarget) -> Result> { if let Some(hostname) = target.hostname.as_deref() { let hostname = hostname.trim_end_matches('.'); if !hostname.is_empty() && hostname.len() <= u8::MAX as usize { - let mut encoded = Vec::with_capacity(1 + 1 + hostname.len() + 2); - encoded.push(3); - encoded.push(hostname.len() as u8); - encoded.extend_from_slice(hostname.as_bytes()); - encoded.extend_from_slice(&target.address.port().to_be_bytes()); - return Ok(encoded); + return socks_domain_addr(hostname, target.address.port()); } } socks_addr(target.address) } +async fn sing_mux_brutal_exchange( + session: &ShadowsocksSingMuxSession, + brutal: ShadowsocksSingMuxBrutalTransport, +) -> Result<()> { + let mut stream = session.open_stream().await?; + write_sing_mux_brutal_request(&mut stream, brutal.receive_bps).await?; + let server_receive_bps = read_sing_mux_brutal_response(&mut stream).await?; + let send_bps = if server_receive_bps < brutal.send_bps { + server_receive_bps + } else { + brutal.send_bps + }; + tracing::debug!( + send_bps, + receive_bps = brutal.receive_bps, + server_receive_bps, + "completed Shadowsocks sing-mux brutal bandwidth exchange" + ); + Ok(()) +} + +async fn write_sing_mux_brutal_request(writer: &mut W, receive_bps: u64) -> Result<()> +where + W: AsyncWrite + Unpin, +{ + writer + .write_all(&0u16.to_be_bytes()) + .await + .context("failed to write Shadowsocks sing-mux brutal tcp flags")?; + writer + .write_all(&socks_domain_addr(SING_MUX_BRUTAL_EXCHANGE_DOMAIN, 0)?) + .await + .context("failed to write Shadowsocks sing-mux brutal exchange destination")?; + writer + .write_all(&receive_bps.to_be_bytes()) + .await + .context("failed to write Shadowsocks sing-mux brutal receive bps")?; + writer + .flush() + .await + .context("failed to flush Shadowsocks sing-mux brutal request")?; + Ok(()) +} + +async fn read_sing_mux_brutal_response(reader: &mut R) -> Result +where + R: AsyncRead + Unpin, +{ + read_sing_mux_stream_response(reader).await?; + let ok = reader + .read_u8() + .await + .context("failed to read Shadowsocks sing-mux brutal response status")?; + match ok { + 0 => { + let len = read_sing_mux_uvarint(reader).await?; + if len > 4096 { + bail!("Shadowsocks sing-mux brutal remote error message is too long"); + } + let mut message = vec![0u8; len as usize]; + reader + .read_exact(&mut message) + .await + .context("failed to read Shadowsocks sing-mux brutal remote error")?; + let message = String::from_utf8_lossy(&message); + bail!("Shadowsocks sing-mux brutal remote error: {message}"); + } + 1 => reader + .read_u64() + .await + .context("failed to read Shadowsocks sing-mux brutal server receive bps"), + other => bail!("unknown Shadowsocks sing-mux brutal response status {other}"), + } +} + +async fn write_sing_mux_tcp_request(writer: &mut W, target: &ProxyTcpTarget) -> Result<()> +where + W: AsyncWrite + Unpin, +{ + writer + .write_all(&0u16.to_be_bytes()) + .await + .context("failed to write Shadowsocks sing-mux tcp flags")?; + writer + .write_all(&socks_addr_for_target(target)?) + .await + .context("failed to write Shadowsocks sing-mux tcp destination")?; + writer + .flush() + .await + .context("failed to flush Shadowsocks sing-mux tcp request")?; + Ok(()) +} + +async fn write_sing_mux_udp_request( + writer: &mut W, + destination: SocketAddr, + payload: &[u8], +) -> Result<()> +where + W: AsyncWrite + Unpin, +{ + writer + .write_all(&SING_MUX_STREAM_FLAG_UDP.to_be_bytes()) + .await + .context("failed to write Shadowsocks sing-mux udp flags")?; + writer + .write_all(&socks_addr(destination)?) + .await + .context("failed to write Shadowsocks sing-mux udp destination")?; + if !payload.is_empty() { + write_sing_mux_udp_payload(writer, payload).await?; + } else { + writer + .flush() + .await + .context("failed to flush Shadowsocks sing-mux udp request")?; + } + Ok(()) +} + +async fn write_sing_mux_udp_payload(writer: &mut W, payload: &[u8]) -> Result<()> +where + W: AsyncWrite + Unpin, +{ + if payload.len() > u16::MAX as usize { + bail!("Shadowsocks sing-mux udp payload too large"); + } + writer + .write_all(&(payload.len() as u16).to_be_bytes()) + .await + .context("failed to write Shadowsocks sing-mux udp payload length")?; + writer + .write_all(payload) + .await + .context("failed to write Shadowsocks sing-mux udp payload")?; + writer + .flush() + .await + .context("failed to flush Shadowsocks sing-mux udp payload")?; + Ok(()) +} + +async fn read_sing_mux_udp_payload(reader: &mut R, response_read: &mut bool) -> Result> +where + R: AsyncRead + Unpin, +{ + if !*response_read { + read_sing_mux_stream_response(reader).await?; + *response_read = true; + } + let payload_len = reader + .read_u16() + .await + .context("failed to read Shadowsocks sing-mux udp payload length")? + as usize; + let mut payload = vec![0u8; payload_len]; + reader + .read_exact(&mut payload) + .await + .context("failed to read Shadowsocks sing-mux udp payload")?; + Ok(payload) +} + +fn sing_mux_padding_len() -> usize { + SING_MUX_MIN_PADDING_LEN + (OsRng.next_u32() as usize % SING_MUX_PADDING_LEN_RANGE) +} + +async fn write_sing_mux_session_preface( + writer: &mut W, + protocol: ShadowsocksSingMuxProtocol, + padding: bool, +) -> Result<()> +where + W: AsyncWrite + Unpin, +{ + if !padding { + writer + .write_all(&[SING_MUX_VERSION_0, protocol.wire_id()]) + .await + .with_context(|| { + format!( + "failed to write Shadowsocks sing-mux {} preface", + protocol.name() + ) + })?; + writer.flush().await.with_context(|| { + format!( + "failed to flush Shadowsocks sing-mux {} preface", + protocol.name() + ) + })?; + return Ok(()); + } + + let padding_len = sing_mux_padding_len(); + writer + .write_all(&[SING_MUX_VERSION_1, protocol.wire_id(), 1]) + .await + .with_context(|| { + format!( + "failed to write Shadowsocks sing-mux padded {} preface", + protocol.name() + ) + })?; + writer + .write_all(&(padding_len as u16).to_be_bytes()) + .await + .context("failed to write Shadowsocks sing-mux preface padding length")?; + writer + .write_all(&vec![0u8; padding_len]) + .await + .context("failed to write Shadowsocks sing-mux preface padding")?; + writer + .flush() + .await + .context("failed to flush Shadowsocks sing-mux padded preface")?; + Ok(()) +} + +fn sing_mux_padding_stream(stream: Box) -> Box { + let (plain, bridge) = tokio::io::duplex(64 * 1024); + let (mut plain_reader, mut plain_writer) = split(bridge); + let (mut stream_reader, mut stream_writer) = split(stream); + + let _upload = tokio::spawn(async move { + let result = async { + let mut buf = vec![0u8; 16 * 1024]; + let mut frames = 0usize; + loop { + let n = plain_reader + .read(&mut buf) + .await + .context("failed to read Shadowsocks sing-mux padded upload payload")?; + if n == 0 { + break; + } + if frames < SING_MUX_PADDING_FRAMES { + let padding_len = sing_mux_padding_len(); + stream_writer + .write_all(&(n as u16).to_be_bytes()) + .await + .context("failed to write Shadowsocks sing-mux padded upload length")?; + stream_writer + .write_all(&(padding_len as u16).to_be_bytes()) + .await + .context("failed to write Shadowsocks sing-mux upload padding length")?; + stream_writer + .write_all(&buf[..n]) + .await + .context("failed to write Shadowsocks sing-mux padded upload payload")?; + stream_writer + .write_all(&vec![0u8; padding_len]) + .await + .context("failed to write Shadowsocks sing-mux upload padding")?; + frames += 1; + } else { + stream_writer + .write_all(&buf[..n]) + .await + .context("failed to write Shadowsocks sing-mux upload payload")?; + } + } + stream_writer + .shutdown() + .await + .context("failed to shutdown Shadowsocks sing-mux padded upload")?; + Result::<()>::Ok(()) + } + .await; + if let Err(err) = result { + tracing::debug!(?err, "Shadowsocks sing-mux padded upload task stopped"); + } + }); + + let _download = tokio::spawn(async move { + let result = async { + let mut header = [0u8; 4]; + let mut padding = vec![0u8; SING_MUX_MIN_PADDING_LEN + SING_MUX_PADDING_LEN_RANGE - 1]; + let mut frames = 0usize; + while frames < SING_MUX_PADDING_FRAMES { + stream_reader + .read_exact(&mut header) + .await + .context("failed to read Shadowsocks sing-mux padded download header")?; + let payload_len = u16::from_be_bytes([header[0], header[1]]) as usize; + let padding_len = u16::from_be_bytes([header[2], header[3]]) as usize; + let mut payload = vec![0u8; payload_len]; + stream_reader + .read_exact(&mut payload) + .await + .context("failed to read Shadowsocks sing-mux padded download payload")?; + let mut remaining_padding = padding_len; + while remaining_padding > 0 { + let read_len = remaining_padding.min(padding.len()); + stream_reader + .read_exact(&mut padding[..read_len]) + .await + .context("failed to read Shadowsocks sing-mux download padding")?; + remaining_padding -= read_len; + } + plain_writer + .write_all(&payload) + .await + .context("failed to write Shadowsocks sing-mux padded download payload")?; + frames += 1; + } + + let mut buf = vec![0u8; 16 * 1024]; + loop { + let n = stream_reader + .read(&mut buf) + .await + .context("failed to read Shadowsocks sing-mux download payload")?; + if n == 0 { + break; + } + plain_writer + .write_all(&buf[..n]) + .await + .context("failed to write Shadowsocks sing-mux download payload")?; + } + plain_writer + .shutdown() + .await + .context("failed to shutdown Shadowsocks sing-mux padded download")?; + Result::<()>::Ok(()) + } + .await; + if let Err(err) = result { + tracing::debug!(?err, "Shadowsocks sing-mux padded download task stopped"); + } + }); + + Box::new(plain) +} + +async fn read_sing_mux_stream_response(reader: &mut R) -> Result<()> +where + R: AsyncRead + Unpin, +{ + let mut status = [0u8; 1]; + reader + .read_exact(&mut status) + .await + .context("failed to read Shadowsocks sing-mux stream response")?; + match status[0] { + SING_MUX_STREAM_STATUS_SUCCESS => Ok(()), + SING_MUX_STREAM_STATUS_ERROR => { + let len = read_sing_mux_uvarint(reader).await?; + if len > 4096 { + bail!("Shadowsocks sing-mux remote error message is too long"); + } + let mut message = vec![0u8; len as usize]; + reader + .read_exact(&mut message) + .await + .context("failed to read Shadowsocks sing-mux remote error message")?; + bail!( + "Shadowsocks sing-mux remote error: {}", + String::from_utf8_lossy(&message) + ) + } + other => bail!("unknown Shadowsocks sing-mux stream response status {other}"), + } +} + +async fn read_sing_mux_uvarint(reader: &mut R) -> Result +where + R: AsyncRead + Unpin, +{ + let mut value = 0u64; + for shift in (0..64).step_by(7) { + let mut byte = [0u8; 1]; + reader + .read_exact(&mut byte) + .await + .context("failed to read Shadowsocks sing-mux uvarint")?; + value |= u64::from(byte[0] & 0x7f) << shift; + if byte[0] < 0x80 { + return Ok(value); + } + } + bail!("Shadowsocks sing-mux uvarint is too large") +} + +fn uot_magic_domain(version: u8) -> &'static str { + match version { + UOT_VERSION => UOT_MAGIC_ADDRESS, + _ => UOT_LEGACY_MAGIC_ADDRESS, + } +} + +fn uot_magic_shadowsocks_addr(version: u8) -> SsAddress { + SsAddress::DomainNameAddress(uot_magic_domain(version).to_owned(), 0) +} + +fn uot_request(destination: SocketAddr) -> Result> { + let mut request = Vec::with_capacity(1 + 1 + 16 + 2); + request.push(0); + request.extend_from_slice(&socks_addr(destination)?); + Ok(request) +} + +async fn write_uot_request(writer: &mut W, destination: SocketAddr) -> Result<()> +where + W: AsyncWrite + Unpin, +{ + writer.write_all(&uot_request(destination)?).await?; + writer.flush().await?; + Ok(()) +} + +fn uot_frame(destination: SocketAddr, payload: &[u8]) -> Result> { + if payload.len() > u16::MAX as usize { + bail!("udp-over-tcp payload too large"); + } + let mut frame = uot_addr(destination); + frame.extend_from_slice(&(payload.len() as u16).to_be_bytes()); + frame.extend_from_slice(payload); + Ok(frame) +} + +async fn write_uot_frame(writer: &mut W, destination: SocketAddr, payload: &[u8]) -> Result<()> +where + W: AsyncWrite + Unpin, +{ + writer.write_all(&uot_frame(destination, payload)?).await?; + writer.flush().await?; + Ok(()) +} + +fn uot_addr(addr: SocketAddr) -> Vec { + let mut encoded = Vec::with_capacity(1 + 16 + 2); + match addr.ip() { + IpAddr::V4(ip) => { + encoded.push(0x00); + encoded.extend_from_slice(&ip.octets()); + } + IpAddr::V6(ip) => { + encoded.push(0x01); + encoded.extend_from_slice(&ip.octets()); + } + } + encoded.extend_from_slice(&addr.port().to_be_bytes()); + encoded +} + +async fn read_uot_frame(reader: &mut R) -> Result<(SocketAddr, Vec)> +where + R: AsyncRead + Unpin, +{ + let source = read_uot_addr_async(reader).await?; + let payload_len = reader.read_u16().await? as usize; + let mut payload = vec![0u8; payload_len]; + reader.read_exact(&mut payload).await?; + Ok((source, payload)) +} + +async fn read_uot_addr_async(reader: &mut R) -> Result +where + R: AsyncRead + Unpin, +{ + let atyp = reader.read_u8().await?; + match atyp { + 0x00 => { + let mut ip = [0u8; 4]; + reader.read_exact(&mut ip).await?; + let port = reader.read_u16().await?; + Ok(SocketAddr::new(IpAddr::V4(Ipv4Addr::from(ip)), port)) + } + 0x01 => { + let mut ip = [0u8; 16]; + reader.read_exact(&mut ip).await?; + let port = reader.read_u16().await?; + Ok(SocketAddr::new(IpAddr::V6(Ipv6Addr::from(ip)), port)) + } + 0x02 => { + let len = reader.read_u8().await? as usize; + let mut domain = vec![0u8; len]; + reader.read_exact(&mut domain).await?; + let port = reader.read_u16().await?; + let domain = String::from_utf8(domain).context("invalid udp-over-tcp domain")?; + resolve_server(&domain, port) + } + other => bail!("unsupported udp-over-tcp address type {other}"), + } +} + async fn read_socks_addr_async(reader: &mut R) -> Result where R: AsyncRead + Unpin, @@ -1943,848 +8434,7 @@ fn resolve_server(host: &str, port: u16) -> Result { .ok_or_else(|| anyhow!("no addresses resolved for {host}:{port}")) } -impl ProxyServerEndpoint { - fn resolve(host: &str, port: u16) -> Result { - 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> { - 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> { - if let Ok(ip) = host.parse::() { - 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::() 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::() 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> { - 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) -} - -pub(crate) fn build_dns_query(host: &str, query_type: u16) -> Result> { - 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> { - 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) -} - -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> { - 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 { - 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 { - 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 { - Vec::new() -} - -fn parse_apple_physical_dns_servers(output: &str) -> Vec { - let mut servers = Vec::new(); - let mut resolver_servers = Vec::::new(); - let mut resolver_interface = None::; - - let mut flush_resolver = - |resolver_servers: &mut Vec, resolver_interface: &mut Option| { - 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::() { - 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 { - 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::() 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::(), - 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 { - #[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::::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> { - 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"), - } -} +include!("proxy_runtime/networking.rs"); fn hex_lower(bytes: &[u8]) -> String { const HEX: &[u8; 16] = b"0123456789abcdef"; @@ -2797,314 +8447,4 @@ fn hex_lower(bytes: &[u8]) -> String { } #[cfg(test)] -mod tests { - use super::*; - - #[test] - fn trojan_hash_is_sha224_hex() { - assert_eq!( - trojan_password_hash("password"), - "d63dc919e201d7bc4c825630d2cf25fdc93d4b2f0d46706d29038d01" - ); - } - - #[test] - fn socks_addr_round_trips_ipv4_and_ipv6() { - for addr in [ - "1.2.3.4:53".parse::().unwrap(), - "[2001:db8::1]:443".parse::().unwrap(), - ] { - let encoded = socks_addr(addr).unwrap(); - let (decoded, used) = parse_socks_addr(&encoded).unwrap(); - assert_eq!(decoded, addr); - assert_eq!(used, encoded.len()); - } - } - - #[test] - fn shadowsocks_udp_packet_round_trips() { - for cipher in [ - SsCipherKind::Aes128Gcm, - SsCipherKind::Aes256Gcm, - SsCipherKind::Chacha20IetfPoly1305, - ] { - let key = evp_bytes_to_key(b"secret", cipher.key_len()); - let target = "8.8.8.8:53".parse().unwrap(); - let packet = encrypt_ss_udp_packet(cipher, &key, target, b"hello").unwrap(); - let (decoded_target, payload) = decrypt_ss_udp_packet(cipher, &key, &packet).unwrap(); - assert_eq!(decoded_target, target); - assert_eq!(payload, b"hello"); - } - } - - #[test] - fn trojan_runtime_uses_mihomo_default_alpn_when_absent() { - let mut node = TrojanNode { - password: "secret".to_owned(), - sni: None, - alpn: Vec::new(), - alpn_present: false, - skip_cert_verify: false, - network: TrojanNetwork::Tcp, - udp: true, - client_fingerprint: None, - fingerprint: None, - }; - assert_eq!(trojan_alpn(&node), vec!["h2", "http/1.1"]); - - node.alpn_present = true; - assert!(trojan_alpn(&node).is_empty()); - - node.alpn = vec!["h3".to_owned()]; - assert_eq!(trojan_alpn(&node), vec!["h3"]); - } - - #[test] - fn parses_certificate_fingerprint_hex() { - let fp = parse_certificate_fingerprint( - "00:112233445566778899aabbccddeeff00112233445566778899aabbccddeeff", - ) - .unwrap(); - assert_eq!(fp.0[0], 0x00); - assert_eq!(fp.0[1], 0x11); - assert_eq!(fp.0[31], 0xff); - assert!(parse_certificate_fingerprint("chrome").is_err()); - assert!(parse_certificate_fingerprint("abcd").is_err()); - } - - #[tokio::test] - async fn trojan_udp_writer_fragments_oversized_payloads() { - let target = socks_addr("1.2.3.4:53".parse().unwrap()).unwrap(); - let payload = vec![7u8; TROJAN_MAX_UDP_PAYLOAD + 3]; - let (mut writer, mut reader) = tokio::io::duplex(20_000); - - let write = - tokio::spawn( - async move { write_trojan_udp_packet(&mut writer, &target, &payload).await }, - ); - - let mut encoded = Vec::new(); - reader.read_to_end(&mut encoded).await.unwrap(); - write.await.unwrap().unwrap(); - - let first_payload_offset = 7 + 2 + 2; - assert_eq!( - u16::from_be_bytes([encoded[7], encoded[8]]) as usize, - TROJAN_MAX_UDP_PAYLOAD - ); - assert_eq!(&encoded[9..11], b"\r\n"); - assert_eq!(encoded[first_payload_offset], 7); - let second = 7 + 2 + 2 + TROJAN_MAX_UDP_PAYLOAD; - assert_eq!( - u16::from_be_bytes([encoded[second + 7], encoded[second + 8]]) as usize, - 3 - ); - assert_eq!(&encoded[second + 9..second + 11], b"\r\n"); - assert_eq!(encoded.len(), second + 11 + 3); - } - - #[test] - fn fake_ip_detection_flags_benchmark_range() { - assert!(is_fake_or_benchmark_ip("198.18.0.1".parse().unwrap())); - assert!(is_fake_or_benchmark_ip("198.19.255.254".parse().unwrap())); - assert!(!is_fake_or_benchmark_ip("198.20.0.1".parse().unwrap())); - assert!(!is_fake_or_benchmark_ip("2001:db8::1".parse().unwrap())); - } - - #[test] - fn proxy_outbound_socket_binding_skips_loopback() { - assert!(!should_bind_proxy_outbound_socket( - "127.0.0.1".parse().unwrap() - )); - assert!(!should_bind_proxy_outbound_socket("::1".parse().unwrap())); - assert!(should_bind_proxy_outbound_socket( - "192.0.2.10".parse().unwrap() - )); - } - - #[test] - fn dns_query_encodes_host_labels() { - let query = build_dns_query("example.com", 1).unwrap(); - assert_eq!(&query[12..25], b"\x07example\x03com\0"); - assert_eq!(u16::from_be_bytes([query[25], query[26]]), 1); - assert_eq!(u16::from_be_bytes([query[27], query[28]]), 1); - } - - #[test] - fn dns_response_parser_reads_compressed_a_and_aaaa_answers() { - let mut response = Vec::new(); - response.extend_from_slice(&0x1234u16.to_be_bytes()); - response.extend_from_slice(&0x8180u16.to_be_bytes()); - response.extend_from_slice(&1u16.to_be_bytes()); - response.extend_from_slice(&2u16.to_be_bytes()); - response.extend_from_slice(&0u16.to_be_bytes()); - response.extend_from_slice(&0u16.to_be_bytes()); - response.extend_from_slice(b"\x07example\x03com\0"); - response.extend_from_slice(&1u16.to_be_bytes()); - response.extend_from_slice(&1u16.to_be_bytes()); - response.extend_from_slice(&[0xc0, 0x0c]); - response.extend_from_slice(&1u16.to_be_bytes()); - response.extend_from_slice(&1u16.to_be_bytes()); - response.extend_from_slice(&60u32.to_be_bytes()); - response.extend_from_slice(&4u16.to_be_bytes()); - response.extend_from_slice(&[93, 184, 216, 34]); - response.extend_from_slice(&[0xc0, 0x0c]); - response.extend_from_slice(&28u16.to_be_bytes()); - response.extend_from_slice(&1u16.to_be_bytes()); - response.extend_from_slice(&60u32.to_be_bytes()); - response.extend_from_slice(&16u16.to_be_bytes()); - response.extend_from_slice(&[ - 0x26, 0x06, 0x28, 0x00, 0x02, 0x20, 0x00, 0x01, 0x02, 0x48, 0x18, 0x93, 0x25, 0xc8, - 0x19, 0x46, - ]); - - let addresses = parse_dns_response(&response, 0x1234).unwrap(); - assert_eq!( - addresses, - vec![ - "93.184.216.34".parse::().unwrap(), - "2606:2800:220:1:248:1893:25c8:1946" - .parse::() - .unwrap(), - ] - ); - } - - #[tokio::test] - async fn fake_dns_response_returns_stable_hostname_mapping() { - let query = build_dns_query("example.com", 1).unwrap(); - let cache = Arc::new(RwLock::new(DnsHostnameCacheState::new())); - let response = build_fake_dns_response(&cache, &query).await.unwrap(); - let addresses = parse_dns_response(&response, 0).unwrap(); - assert_eq!(addresses, vec!["198.18.64.1".parse::().unwrap()]); - assert_eq!( - cache - .read() - .await - .lookup_hostname("198.18.64.1".parse().unwrap()), - Some("example.com".to_owned()) - ); - } - - #[test] - fn runtime_selection_skips_unsupported_transports_when_unselected() { - let preview = crate::proxy_subscription::parse_subscription_body( - r#" -proxies: - - name: trojan-grpc - type: trojan - server: example.com - port: 443 - password: secret - network: grpc - - name: ss-aead - type: ss - server: example.net - port: 8388 - cipher: aes-128-gcm - password: secret -"#, - Some("https://example.com/sub"), - ); - let payload = crate::proxy_subscription::build_payload( - preview, - "https://example.com/sub", - Some("Proxy Subscription".to_owned()), - None, - ); - assert_eq!(selected_node(&payload).unwrap().name, "ss-aead"); - } - - #[test] - fn runtime_selection_prefers_selected_name_over_stale_ordinal() { - let preview = crate::proxy_subscription::parse_subscription_body( - r#" -proxies: - - name: first - type: ss - server: first.example.net - port: 8388 - cipher: aes-128-gcm - password: secret - - name: selected - type: ss - server: selected.example.net - port: 8388 - cipher: aes-128-gcm - password: secret -"#, - Some("https://example.com/sub"), - ); - let mut payload = crate::proxy_subscription::build_payload( - preview, - "https://example.com/sub", - Some("Proxy Subscription".to_owned()), - Some(0), - ); - payload.selected_name = Some("selected".to_owned()); - - assert_eq!(selected_node(&payload).unwrap().name, "selected"); - } - - #[test] - fn proxy_server_bypass_routes_use_host_prefixes() { - let routes = excluded_routes_for_server("127.0.0.1", 443).unwrap(); - assert_eq!(routes, vec!["127.0.0.1/32"]); - } - - #[test] - fn parses_physical_dns_from_scutil_output() { - let servers = parse_apple_physical_dns_servers( - r#" -DNS configuration - -resolver #1 - nameserver[0] : 2606:4700:4700::1111 - nameserver[1] : 1.1.1.1 - if_index : 35 (utun8) - -DNS configuration (for scoped queries) - -resolver #1 - search domain[0] : lan - nameserver[0] : 192.168.2.1 - if_index : 17 (en0) - -resolver #2 - nameserver[0] : 198.18.0.1 - if_index : 35 (utun8) -"#, - ); - assert_eq!(servers, vec!["192.168.2.1"]); - } - - #[test] - fn parses_resolv_conf_dns_servers() { - let servers = parse_resolv_conf_dns_servers( - r#" -# macOS generated resolver file -nameserver 192.168.2.1 -nameserver 198.18.0.1 -nameserver 192.168.2.1 -nameserver ::1 -"#, - ); - assert_eq!(servers, vec!["192.168.2.1"]); - } - - #[test] - fn proxy_dns_excluded_routes_use_host_prefixes() { - let routes = proxy_dns_excluded_routes(&[ - "100.64.0.2".to_owned(), - "192.168.2.1".to_owned(), - "2001:db8::53".to_owned(), - "not-an-ip".to_owned(), - ]); - assert_eq!(routes, vec!["192.168.2.1/32", "2001:db8::53/128"]); - } - - #[test] - fn proxy_dns_servers_use_daemon_resolver() { - assert_eq!(proxy_dns_servers(), vec!["100.64.0.2"]); - } -} +include!("proxy_runtime/tests.rs"); diff --git a/burrow/src/proxy_runtime/networking.rs b/burrow/src/proxy_runtime/networking.rs new file mode 100644 index 0000000..f186543 --- /dev/null +++ b/burrow/src/proxy_runtime/networking.rs @@ -0,0 +1,961 @@ +impl ProxyServerEndpoint { + fn resolve(host: &str, port: u16) -> Result { + 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> { + 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> { + if let Ok(ip) = host.parse::() { + 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::() 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::() 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> { + 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>> { + 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> { + 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> { + 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>> { + 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>> { + 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> { + 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 { + 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 { + 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 { + Vec::new() +} + +fn parse_apple_physical_dns_servers(output: &str) -> Vec { + let mut servers = Vec::new(); + let mut resolver_servers = Vec::::new(); + let mut resolver_interface = None::; + + let mut flush_resolver = + |resolver_servers: &mut Vec, resolver_interface: &mut Option| { + 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::() { + 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 { + 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::() 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::(), + 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 { + #[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::::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> { + 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"), + } +} diff --git a/burrow/src/proxy_runtime/shadowsocks_crypto.rs b/burrow/src/proxy_runtime/shadowsocks_crypto.rs new file mode 100644 index 0000000..773a37d --- /dev/null +++ b/burrow/src/proxy_runtime/shadowsocks_crypto.rs @@ -0,0 +1,2004 @@ +struct BurrowDatagramSocket(UdpSocket); + +impl DatagramSocket for BurrowDatagramSocket { + fn local_addr(&self) -> std::io::Result { + self.0.local_addr() + } +} + +impl DatagramReceive for BurrowDatagramSocket { + fn poll_recv( + &self, + cx: &mut TaskContext<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + self.0.poll_recv(cx, buf) + } + + fn poll_recv_from( + &self, + cx: &mut TaskContext<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + self.0.poll_recv_from(cx, buf) + } + + fn poll_recv_ready(&self, cx: &mut TaskContext<'_>) -> Poll> { + self.0.poll_recv_ready(cx) + } +} + +impl DatagramSend for BurrowDatagramSocket { + fn poll_send(&self, cx: &mut TaskContext<'_>, buf: &[u8]) -> Poll> { + self.0.poll_send(cx, buf) + } + + fn poll_send_to( + &self, + cx: &mut TaskContext<'_>, + buf: &[u8], + target: SocketAddr, + ) -> Poll> { + self.0.poll_send_to(cx, buf, target) + } + + fn poll_send_ready(&self, cx: &mut TaskContext<'_>) -> Poll> { + self.0.poll_send_ready(cx) + } +} + +struct CustomSsStreamWriter { + writer: W, + cipher: CustomSsStreamCipher, +} + +impl CustomSsStreamWriter +where + W: AsyncWrite + Unpin, +{ + async fn new(mut writer: W, kind: CustomSsStreamKind, key: Arc<[u8]>) -> Result { + let mut salt = vec![0u8; kind.salt_len()]; + OsRng.fill_bytes(&mut salt); + let cipher = CustomSsStreamCipher::new(kind, &key, &salt)?; + writer + .write_all(&salt) + .await + .context("failed to write Shadowsocks request salt")?; + Ok(Self { writer, cipher }) + } + + async fn write_all_encrypted(&mut self, payload: &[u8]) -> Result<()> { + if payload.is_empty() { + return Ok(()); + } + let mut encrypted = payload.to_vec(); + self.cipher.apply_keystream(&mut encrypted); + self.writer + .write_all(&encrypted) + .await + .context("failed to write Shadowsocks encrypted stream payload")?; + self.writer.flush().await?; + Ok(()) + } + + async fn shutdown(&mut self) -> Result<()> { + self.writer.shutdown().await?; + Ok(()) + } +} + +struct CustomSsStreamReader { + reader: R, + kind: CustomSsStreamKind, + key: Arc<[u8]>, + cipher: Option, +} + +impl CustomSsStreamReader +where + R: AsyncRead + Unpin, +{ + fn new(reader: R, kind: CustomSsStreamKind, key: Arc<[u8]>) -> Self { + Self { + reader, + kind, + key, + cipher: None, + } + } + + async fn read_decrypted(&mut self, output: &mut [u8]) -> Result { + if self.cipher.is_none() { + let mut salt = vec![0u8; self.kind.salt_len()]; + if let Err(err) = self.reader.read_exact(&mut salt).await { + if err.kind() == std::io::ErrorKind::UnexpectedEof { + return Ok(0); + } + return Err(err).context("failed to read Shadowsocks response salt"); + } + self.cipher = Some(CustomSsStreamCipher::new(self.kind, &self.key, &salt)?); + } + let n = self + .reader + .read(output) + .await + .context("failed to read Shadowsocks encrypted stream payload")?; + if n == 0 { + return Ok(0); + } + self.cipher + .as_mut() + .expect("cipher initialized") + .apply_keystream(&mut output[..n]); + Ok(n) + } +} + +struct CustomSsStreamByteReader { + reader: CustomSsStreamReader, + buffer: Vec, + offset: usize, +} + +impl CustomSsStreamByteReader +where + R: AsyncRead + Unpin, +{ + fn new(reader: CustomSsStreamReader) -> Self { + Self { + reader, + buffer: Vec::new(), + offset: 0, + } + } + + async fn read_frame(&mut self) -> Result)>> { + let source = match self.read_uot_addr().await? { + Some(source) => source, + None => return Ok(None), + }; + let Some(payload_len) = self.read_u16().await? else { + bail!("truncated udp-over-tcp payload length"); + }; + let payload = self.read_exact_vec(payload_len as usize).await?; + Ok(Some((source, payload))) + } + + async fn read_uot_addr(&mut self) -> Result> { + let Some(atyp) = self.read_u8().await? else { + return Ok(None); + }; + match atyp { + 0x00 => { + let ip = self.read_exact_array::<4>().await?; + let port = self.read_u16_required().await?; + Ok(Some(SocketAddr::new(IpAddr::V4(Ipv4Addr::from(ip)), port))) + } + 0x01 => { + let ip = self.read_exact_array::<16>().await?; + let port = self.read_u16_required().await?; + Ok(Some(SocketAddr::new(IpAddr::V6(Ipv6Addr::from(ip)), port))) + } + 0x02 => { + let len = self.read_u8_required().await? as usize; + let domain = self.read_exact_vec(len).await?; + let port = self.read_u16_required().await?; + let domain = String::from_utf8(domain).context("invalid udp-over-tcp domain")?; + Ok(Some(resolve_server(&domain, port)?)) + } + other => bail!("unsupported udp-over-tcp address type {other}"), + } + } + + async fn read_u8_required(&mut self) -> Result { + self.read_u8() + .await? + .ok_or_else(|| anyhow!("truncated udp-over-tcp frame")) + } + + async fn read_u16_required(&mut self) -> Result { + self.read_u16() + .await? + .ok_or_else(|| anyhow!("truncated udp-over-tcp frame")) + } + + async fn read_u8(&mut self) -> Result> { + if !self.ensure_available(1).await? { + return Ok(None); + } + let value = self.buffer[self.offset]; + self.offset += 1; + self.compact_buffer(); + Ok(Some(value)) + } + + async fn read_u16(&mut self) -> Result> { + if !self.ensure_available(2).await? { + return Ok(None); + } + let value = u16::from_be_bytes([self.buffer[self.offset], self.buffer[self.offset + 1]]); + self.offset += 2; + self.compact_buffer(); + Ok(Some(value)) + } + + async fn read_exact_array(&mut self) -> Result<[u8; N]> { + let bytes = self.read_exact_vec(N).await?; + Ok(bytes + .try_into() + .expect("read_exact_vec returned requested length")) + } + + async fn read_exact_vec(&mut self, len: usize) -> Result> { + if !self.ensure_available(len).await? { + bail!("truncated udp-over-tcp frame"); + } + let value = self.buffer[self.offset..self.offset + len].to_vec(); + self.offset += len; + self.compact_buffer(); + Ok(value) + } + + async fn ensure_available(&mut self, len: usize) -> Result { + while self.buffer.len().saturating_sub(self.offset) < len { + if self.offset > 0 { + self.buffer.drain(..self.offset); + self.offset = 0; + } + let mut chunk = vec![0u8; 16 * 1024]; + let read = self.reader.read_decrypted(&mut chunk).await?; + if read == 0 { + return Ok(false); + } + self.buffer.extend_from_slice(&chunk[..read]); + } + Ok(true) + } + + fn compact_buffer(&mut self) { + if self.offset == self.buffer.len() { + self.buffer.clear(); + self.offset = 0; + } else if self.offset > 4096 && self.offset * 2 > self.buffer.len() { + self.buffer.drain(..self.offset); + self.offset = 0; + } + } +} + +enum CustomSsStreamCipher { + Chacha20(ChaCha20Legacy), + XChacha20(XChaCha20), +} + +impl CustomSsStreamCipher { + fn new(kind: CustomSsStreamKind, key: &[u8], salt: &[u8]) -> Result { + match kind { + CustomSsStreamKind::Chacha20 => Ok(Self::Chacha20( + ChaCha20Legacy::new_from_slices(key, salt) + .map_err(|err| anyhow!("invalid chacha20 stream cipher material: {err}"))?, + )), + CustomSsStreamKind::XChacha20 => Ok(Self::XChacha20( + XChaCha20::new_from_slices(key, salt) + .map_err(|err| anyhow!("invalid xchacha20 stream cipher material: {err}"))?, + )), + } + } + + fn apply_keystream(&mut self, buf: &mut [u8]) { + match self { + Self::Chacha20(cipher) => cipher.apply_keystream(buf), + Self::XChacha20(cipher) => cipher.apply_keystream(buf), + } + } +} + +struct CustomSsAeadWriter { + writer: W, + cipher: CustomSsAeadCipher, + nonce: Vec, +} + +impl CustomSsAeadWriter +where + W: AsyncWrite + Unpin, +{ + async fn new(mut writer: W, kind: CustomSsAeadKind, master_key: Arc<[u8]>) -> Result { + let mut salt = vec![0u8; kind.salt_len()]; + OsRng.fill_bytes(&mut salt); + let cipher = custom_ss_subkey(kind, &master_key, &salt)?; + writer + .write_all(&salt) + .await + .context("failed to write Shadowsocks request salt")?; + Ok(Self { + writer, + cipher, + nonce: vec![0u8; kind.nonce_len()], + }) + } + + async fn write_chunk(&mut self, payload: &[u8]) -> Result<()> { + if payload.is_empty() { + return Ok(()); + } + let mut offset = 0; + while offset < payload.len() { + let end = (offset + 0x3fff).min(payload.len()); + let chunk = &payload[offset..end]; + + let mut encrypted_len = (chunk.len() as u16).to_be_bytes().to_vec(); + self.cipher + .seal_in_place(&mut self.nonce, &mut encrypted_len)?; + self.writer.write_all(&encrypted_len).await?; + + let mut encrypted_payload = chunk.to_vec(); + self.cipher + .seal_in_place(&mut self.nonce, &mut encrypted_payload)?; + self.writer.write_all(&encrypted_payload).await?; + offset = end; + } + self.writer.flush().await?; + Ok(()) + } + + async fn shutdown(&mut self) -> Result<()> { + self.writer.shutdown().await?; + Ok(()) + } +} + +struct CustomSsAeadReader { + reader: R, + kind: CustomSsAeadKind, + master_key: Arc<[u8]>, + cipher: Option, + nonce: Vec, +} + +impl CustomSsAeadReader +where + R: AsyncRead + Unpin, +{ + fn new(reader: R, kind: CustomSsAeadKind, master_key: Arc<[u8]>) -> Self { + Self { + reader, + kind, + master_key, + cipher: None, + nonce: vec![0u8; kind.nonce_len()], + } + } + + async fn read_chunk(&mut self, output: &mut Vec) -> Result { + if self.cipher.is_none() { + let mut salt = vec![0u8; self.kind.salt_len()]; + self.reader + .read_exact(&mut salt) + .await + .context("failed to read Shadowsocks response salt")?; + self.cipher = Some(custom_ss_subkey(self.kind, &self.master_key, &salt)?); + } + let cipher = self.cipher.as_ref().expect("cipher initialized"); + let tag_len = self.kind.tag_len(); + let mut encrypted_len = vec![0u8; 2 + tag_len]; + if let Err(err) = self.reader.read_exact(&mut encrypted_len).await { + if err.kind() == std::io::ErrorKind::UnexpectedEof { + return Ok(0); + } + return Err(err).context("failed to read Shadowsocks encrypted chunk length"); + } + let plaintext_len = cipher.open_in_place(&mut self.nonce, &mut encrypted_len)?; + let payload_len = u16::from_be_bytes([plaintext_len[0], plaintext_len[1]]) as usize; + if payload_len == 0 { + return Ok(0); + } + let mut encrypted_payload = vec![0u8; payload_len + tag_len]; + self.reader + .read_exact(&mut encrypted_payload) + .await + .context("failed to read Shadowsocks encrypted chunk")?; + let plaintext = cipher.open_in_place(&mut self.nonce, &mut encrypted_payload)?; + output.extend_from_slice(plaintext); + Ok(plaintext.len()) + } +} + +struct CustomSsAeadByteReader { + reader: CustomSsAeadReader, + buffer: Vec, + offset: usize, +} + +impl CustomSsAeadByteReader +where + R: AsyncRead + Unpin, +{ + fn new(reader: CustomSsAeadReader) -> Self { + Self { + reader, + buffer: Vec::new(), + offset: 0, + } + } + + async fn read_frame(&mut self) -> Result)>> { + let source = match self.read_uot_addr().await? { + Some(source) => source, + None => return Ok(None), + }; + let Some(payload_len) = self.read_u16().await? else { + bail!("truncated udp-over-tcp payload length"); + }; + let payload = self.read_exact_vec(payload_len as usize).await?; + Ok(Some((source, payload))) + } + + async fn read_uot_addr(&mut self) -> Result> { + let Some(atyp) = self.read_u8().await? else { + return Ok(None); + }; + match atyp { + 0x00 => { + let ip = self.read_exact_array::<4>().await?; + let port = self.read_u16_required().await?; + Ok(Some(SocketAddr::new(IpAddr::V4(Ipv4Addr::from(ip)), port))) + } + 0x01 => { + let ip = self.read_exact_array::<16>().await?; + let port = self.read_u16_required().await?; + Ok(Some(SocketAddr::new(IpAddr::V6(Ipv6Addr::from(ip)), port))) + } + 0x02 => { + let len = self.read_u8_required().await? as usize; + let domain = self.read_exact_vec(len).await?; + let port = self.read_u16_required().await?; + let domain = String::from_utf8(domain).context("invalid udp-over-tcp domain")?; + Ok(Some(resolve_server(&domain, port)?)) + } + other => bail!("unsupported udp-over-tcp address type {other}"), + } + } + + async fn read_u8_required(&mut self) -> Result { + self.read_u8() + .await? + .ok_or_else(|| anyhow!("truncated udp-over-tcp frame")) + } + + async fn read_u16_required(&mut self) -> Result { + self.read_u16() + .await? + .ok_or_else(|| anyhow!("truncated udp-over-tcp frame")) + } + + async fn read_u8(&mut self) -> Result> { + if !self.ensure_available(1).await? { + return Ok(None); + } + let value = self.buffer[self.offset]; + self.offset += 1; + self.compact_buffer(); + Ok(Some(value)) + } + + async fn read_u16(&mut self) -> Result> { + if !self.ensure_available(2).await? { + return Ok(None); + } + let value = u16::from_be_bytes([self.buffer[self.offset], self.buffer[self.offset + 1]]); + self.offset += 2; + self.compact_buffer(); + Ok(Some(value)) + } + + async fn read_exact_array(&mut self) -> Result<[u8; N]> { + let bytes = self.read_exact_vec(N).await?; + Ok(bytes + .try_into() + .expect("read_exact_vec returned requested length")) + } + + async fn read_exact_vec(&mut self, len: usize) -> Result> { + if !self.ensure_available(len).await? { + bail!("truncated udp-over-tcp frame"); + } + let value = self.buffer[self.offset..self.offset + len].to_vec(); + self.offset += len; + self.compact_buffer(); + Ok(value) + } + + async fn ensure_available(&mut self, len: usize) -> Result { + while self.buffer.len().saturating_sub(self.offset) < len { + if self.offset > 0 { + self.buffer.drain(..self.offset); + self.offset = 0; + } + let mut chunk = Vec::new(); + let read = self.reader.read_chunk(&mut chunk).await?; + if read == 0 { + return Ok(false); + } + self.buffer.extend_from_slice(&chunk); + } + Ok(true) + } + + fn compact_buffer(&mut self) { + if self.offset == self.buffer.len() { + self.buffer.clear(); + self.offset = 0; + } else if self.offset > 4096 && self.offset * 2 > self.buffer.len() { + self.buffer.drain(..self.offset); + self.offset = 0; + } + } +} + +struct Aead2022AesCcmWriter { + writer: W, + kind: Aead2022AesCcmKind, + user_key: Arc<[u8]>, + identity_keys: Arc<[Arc<[u8]>]>, + cipher: Option, +} + +impl Aead2022AesCcmWriter +where + W: AsyncWrite + Unpin, +{ + async fn new( + writer: W, + kind: Aead2022AesCcmKind, + user_key: Arc<[u8]>, + identity_keys: Arc<[Arc<[u8]>]>, + ) -> Result { + Ok(Self { + writer, + kind, + user_key, + identity_keys, + cipher: None, + }) + } + + async fn write_request( + &mut self, + socks_target: &[u8], + padding_len: usize, + ) -> Result> { + if padding_len > u16::MAX as usize { + bail!("Shadowsocks 2022 TCP request padding is too large"); + } + let mut salt = vec![0u8; self.kind.key_len()]; + OsRng.fill_bytes(&mut salt); + + let mut header = salt.clone(); + header.extend_from_slice(&aead2022_eih_blocks( + self.kind, + &self.identity_keys, + &self.user_key, + &salt, + )?); + + let mut cipher = Aead2022TcpCipher::new(self.kind, &self.user_key, &salt)?; + let variable_len = socks_target + .len() + .checked_add(2) + .and_then(|len| len.checked_add(padding_len)) + .ok_or_else(|| anyhow!("Shadowsocks 2022 TCP request header is too large"))?; + if variable_len > u16::MAX as usize { + bail!("Shadowsocks 2022 TCP request header is too large"); + } + + let mut fixed = Vec::with_capacity(1 + 8 + 2); + fixed.push(AEAD2022_HEADER_TYPE_CLIENT); + fixed.extend_from_slice(&aead2022_now_timestamp()?.to_be_bytes()); + fixed.extend_from_slice(&(variable_len as u16).to_be_bytes()); + header.extend_from_slice(&cipher.seal_packet(&fixed)?); + + let mut variable = Vec::with_capacity(variable_len); + variable.extend_from_slice(socks_target); + variable.extend_from_slice(&(padding_len as u16).to_be_bytes()); + variable.resize(variable.len() + padding_len, 0); + header.extend_from_slice(&cipher.seal_packet(&variable)?); + + self.writer + .write_all(&header) + .await + .context("failed to write Shadowsocks 2022 request header")?; + self.writer + .flush() + .await + .context("failed to flush Shadowsocks 2022 request header")?; + self.cipher = Some(cipher); + Ok(Arc::from(salt)) + } + + async fn write_chunk(&mut self, payload: &[u8]) -> Result<()> { + if payload.is_empty() { + return Ok(()); + } + let cipher = self + .cipher + .as_mut() + .ok_or_else(|| anyhow!("Shadowsocks 2022 TCP request was not initialized"))?; + let mut offset = 0; + while offset < payload.len() { + let end = (offset + u16::MAX as usize).min(payload.len()); + let chunk = &payload[offset..end]; + + let encrypted_len = cipher.seal_packet(&(chunk.len() as u16).to_be_bytes())?; + self.writer.write_all(&encrypted_len).await?; + + let encrypted_payload = cipher.seal_packet(chunk)?; + self.writer.write_all(&encrypted_payload).await?; + offset = end; + } + self.writer.flush().await?; + Ok(()) + } + + async fn shutdown(&mut self) -> Result<()> { + self.writer.shutdown().await?; + Ok(()) + } +} + +struct Aead2022AesCcmReader { + reader: R, + kind: Aead2022AesCcmKind, + user_key: Arc<[u8]>, + request_salt: Arc<[u8]>, + cipher: Option, + response_read: bool, +} + +impl Aead2022AesCcmReader +where + R: AsyncRead + Unpin, +{ + fn new( + reader: R, + kind: Aead2022AesCcmKind, + user_key: Arc<[u8]>, + request_salt: Arc<[u8]>, + ) -> Self { + Self { + reader, + kind, + user_key, + request_salt, + cipher: None, + response_read: false, + } + } + + async fn read_response(&mut self) -> Result<()> { + if self.response_read { + return Ok(()); + } + let mut salt = vec![0u8; self.kind.key_len()]; + self.reader + .read_exact(&mut salt) + .await + .context("failed to read Shadowsocks 2022 response salt")?; + let mut cipher = Aead2022TcpCipher::new(self.kind, &self.user_key, &salt)?; + + let fixed_len = 1 + 8 + self.kind.key_len() + 2; + let mut encrypted_fixed = vec![0u8; fixed_len + self.kind.tag_len()]; + self.reader + .read_exact(&mut encrypted_fixed) + .await + .context("failed to read Shadowsocks 2022 response fixed header")?; + let fixed = cipher.open_packet(&mut encrypted_fixed)?; + if fixed[0] != AEAD2022_HEADER_TYPE_SERVER { + bail!( + "unexpected Shadowsocks 2022 response header type {}", + fixed[0] + ); + } + aead2022_validate_timestamp(u64::from_be_bytes( + fixed[1..9].try_into().expect("fixed timestamp"), + ))?; + let response_request_salt = &fixed[9..9 + self.kind.key_len()]; + if !bool::from(response_request_salt.ct_eq(self.request_salt.as_ref())) { + bail!("Shadowsocks 2022 response request salt mismatch"); + } + let padding_len_offset = 9 + self.kind.key_len(); + let padding_len = + u16::from_be_bytes([fixed[padding_len_offset], fixed[padding_len_offset + 1]]) as usize; + if padding_len > 0 { + let mut encrypted_padding = vec![0u8; padding_len + self.kind.tag_len()]; + self.reader + .read_exact(&mut encrypted_padding) + .await + .context("failed to read Shadowsocks 2022 response padding")?; + let padding = cipher.open_packet(&mut encrypted_padding)?; + if padding.len() != padding_len { + bail!("Shadowsocks 2022 response padding length mismatch"); + } + } + + self.cipher = Some(cipher); + self.response_read = true; + Ok(()) + } + + async fn read_chunk(&mut self, output: &mut Vec) -> Result { + self.read_response().await?; + let cipher = self + .cipher + .as_mut() + .ok_or_else(|| anyhow!("Shadowsocks 2022 TCP response was not initialized"))?; + let mut encrypted_len = vec![0u8; 2 + self.kind.tag_len()]; + if let Err(err) = self.reader.read_exact(&mut encrypted_len).await { + if err.kind() == std::io::ErrorKind::UnexpectedEof { + return Ok(0); + } + return Err(err).context("failed to read Shadowsocks 2022 encrypted chunk length"); + } + let plaintext_len = cipher.open_packet(&mut encrypted_len)?; + let payload_len = u16::from_be_bytes([plaintext_len[0], plaintext_len[1]]) as usize; + if payload_len == 0 { + return Ok(0); + } + let mut encrypted_payload = vec![0u8; payload_len + self.kind.tag_len()]; + self.reader + .read_exact(&mut encrypted_payload) + .await + .context("failed to read Shadowsocks 2022 encrypted chunk")?; + let plaintext = cipher.open_packet(&mut encrypted_payload)?; + output.extend_from_slice(plaintext); + Ok(plaintext.len()) + } +} + +struct Aead2022AesCcmByteReader { + reader: Aead2022AesCcmReader, + buffer: Vec, + offset: usize, +} + +impl Aead2022AesCcmByteReader +where + R: AsyncRead + Unpin, +{ + fn new(reader: Aead2022AesCcmReader) -> Self { + Self { + reader, + buffer: Vec::new(), + offset: 0, + } + } + + async fn read_frame(&mut self) -> Result)>> { + let source = match self.read_uot_addr().await? { + Some(source) => source, + None => return Ok(None), + }; + let Some(payload_len) = self.read_u16().await? else { + bail!("truncated udp-over-tcp payload length"); + }; + let payload = self.read_exact_vec(payload_len as usize).await?; + Ok(Some((source, payload))) + } + + async fn read_uot_addr(&mut self) -> Result> { + let Some(atyp) = self.read_u8().await? else { + return Ok(None); + }; + match atyp { + 0x00 => { + let ip = self.read_exact_array::<4>().await?; + let port = self.read_u16_required().await?; + Ok(Some(SocketAddr::new(IpAddr::V4(Ipv4Addr::from(ip)), port))) + } + 0x01 => { + let ip = self.read_exact_array::<16>().await?; + let port = self.read_u16_required().await?; + Ok(Some(SocketAddr::new(IpAddr::V6(Ipv6Addr::from(ip)), port))) + } + 0x02 => { + let len = self.read_u8_required().await? as usize; + let domain = self.read_exact_vec(len).await?; + let port = self.read_u16_required().await?; + let domain = String::from_utf8(domain).context("invalid udp-over-tcp domain")?; + Ok(Some(resolve_server(&domain, port)?)) + } + other => bail!("unsupported udp-over-tcp address type {other}"), + } + } + + async fn read_u8_required(&mut self) -> Result { + self.read_u8() + .await? + .ok_or_else(|| anyhow!("truncated udp-over-tcp frame")) + } + + async fn read_u16_required(&mut self) -> Result { + self.read_u16() + .await? + .ok_or_else(|| anyhow!("truncated udp-over-tcp frame")) + } + + async fn read_u8(&mut self) -> Result> { + if !self.ensure_available(1).await? { + return Ok(None); + } + let value = self.buffer[self.offset]; + self.offset += 1; + self.compact_buffer(); + Ok(Some(value)) + } + + async fn read_u16(&mut self) -> Result> { + if !self.ensure_available(2).await? { + return Ok(None); + } + let value = u16::from_be_bytes([self.buffer[self.offset], self.buffer[self.offset + 1]]); + self.offset += 2; + self.compact_buffer(); + Ok(Some(value)) + } + + async fn read_exact_array(&mut self) -> Result<[u8; N]> { + let bytes = self.read_exact_vec(N).await?; + Ok(bytes + .try_into() + .expect("read_exact_vec returned requested length")) + } + + async fn read_exact_vec(&mut self, len: usize) -> Result> { + if !self.ensure_available(len).await? { + bail!("truncated udp-over-tcp frame"); + } + let value = self.buffer[self.offset..self.offset + len].to_vec(); + self.offset += len; + self.compact_buffer(); + Ok(value) + } + + async fn ensure_available(&mut self, len: usize) -> Result { + while self.buffer.len().saturating_sub(self.offset) < len { + if self.offset > 0 { + self.buffer.drain(..self.offset); + self.offset = 0; + } + let mut chunk = Vec::new(); + let read = self.reader.read_chunk(&mut chunk).await?; + if read == 0 { + return Ok(false); + } + self.buffer.extend_from_slice(&chunk); + } + Ok(true) + } + + fn compact_buffer(&mut self) { + if self.offset == self.buffer.len() { + self.buffer.clear(); + self.offset = 0; + } else if self.offset > 4096 && self.offset * 2 > self.buffer.len() { + self.buffer.drain(..self.offset); + self.offset = 0; + } + } +} + +enum Aead2022AesCcmCipher { + Aes128(Aes128Ccm), + Aes256(Aes256Ccm), +} + +impl Aead2022AesCcmCipher { + fn new(kind: Aead2022AesCcmKind, key: &[u8]) -> Result { + match kind { + Aead2022AesCcmKind::Aes128 => Ok(Self::Aes128( + Aes128Ccm::new_from_slice(key) + .map_err(|_| anyhow!("invalid Shadowsocks 2022 AES-128-CCM key"))?, + )), + Aead2022AesCcmKind::Aes256 => Ok(Self::Aes256( + Aes256Ccm::new_from_slice(key) + .map_err(|_| anyhow!("invalid Shadowsocks 2022 AES-256-CCM key"))?, + )), + } + } + + fn seal_in_place(&self, nonce: &[u8], buf: &mut Vec) -> Result<()> { + let tag = match self { + Self::Aes128(cipher) => cipher + .encrypt_in_place_detached(GenericArray::from_slice(nonce), &[], buf) + .map_err(|_| anyhow!("Shadowsocks 2022 AES-128-CCM seal failed"))? + .as_slice() + .to_vec(), + Self::Aes256(cipher) => cipher + .encrypt_in_place_detached(GenericArray::from_slice(nonce), &[], buf) + .map_err(|_| anyhow!("Shadowsocks 2022 AES-256-CCM seal failed"))? + .as_slice() + .to_vec(), + }; + buf.extend_from_slice(&tag); + Ok(()) + } + + fn open_in_place<'a>(&self, nonce: &[u8], buf: &'a mut [u8]) -> Result<&'a [u8]> { + if buf.len() < 16 { + bail!("Shadowsocks 2022 AEAD packet missing tag"); + } + let (ciphertext, tag) = buf.split_at_mut(buf.len() - 16); + match self { + Self::Aes128(cipher) => cipher + .decrypt_in_place_detached( + GenericArray::from_slice(nonce), + &[], + ciphertext, + GenericArray::from_slice(tag), + ) + .map_err(|_| anyhow!("Shadowsocks 2022 AES-128-CCM open failed"))?, + Self::Aes256(cipher) => cipher + .decrypt_in_place_detached( + GenericArray::from_slice(nonce), + &[], + ciphertext, + GenericArray::from_slice(tag), + ) + .map_err(|_| anyhow!("Shadowsocks 2022 AES-256-CCM open failed"))?, + }; + Ok(ciphertext) + } +} + +struct Aead2022TcpCipher { + cipher: Aead2022AesCcmCipher, + nonce: Vec, +} + +impl Aead2022TcpCipher { + fn new(kind: Aead2022AesCcmKind, user_key: &[u8], salt: &[u8]) -> Result { + Ok(Self { + cipher: Aead2022AesCcmCipher::new(kind, &aead2022_session_key(kind, user_key, salt)?)?, + nonce: vec![0u8; kind.nonce_len()], + }) + } + + fn seal_packet(&mut self, payload: &[u8]) -> Result> { + let mut packet = payload.to_vec(); + self.cipher.seal_in_place(&self.nonce, &mut packet)?; + increment_nonce(&mut self.nonce); + Ok(packet) + } + + fn open_packet<'a>(&mut self, packet: &'a mut [u8]) -> Result<&'a [u8]> { + let plaintext = self.cipher.open_in_place(&self.nonce, packet)?; + increment_nonce(&mut self.nonce); + Ok(plaintext) + } +} + +struct Aead2022AesCcmUdpSession { + kind: Aead2022AesCcmKind, + user_key: Arc<[u8]>, + identity_keys: Arc<[Arc<[u8]>]>, + client_session_id: u64, + packet_id: u64, + client_cipher: Aead2022AesCcmCipher, +} + +impl Aead2022AesCcmUdpSession { + fn new( + kind: Aead2022AesCcmKind, + user_key: Arc<[u8]>, + identity_keys: Arc<[Arc<[u8]>]>, + ) -> Result { + let client_session_id = OsRng.next_u64(); + let client_session_id_bytes = client_session_id.to_be_bytes(); + let client_cipher = Aead2022AesCcmCipher::new( + kind, + &aead2022_session_key(kind, &user_key, &client_session_id_bytes)?, + )?; + Ok(Self { + kind, + user_key, + identity_keys, + client_session_id, + packet_id: 0, + client_cipher, + }) + } + + fn encrypt_client_packet( + &mut self, + destination: SocketAddr, + payload: &[u8], + ) -> Result> { + let packet_id = self.packet_id; + self.packet_id = self.packet_id.wrapping_add(1); + + let padding_len = aead2022_udp_padding_len(destination, payload); + let mut packet = Vec::with_capacity( + 16 + self.identity_keys.len() * 16 + 1 + 8 + 2 + padding_len + 19 + payload.len() + 16, + ); + packet.extend_from_slice(&self.client_session_id.to_be_bytes()); + packet.extend_from_slice(&packet_id.to_be_bytes()); + packet.extend_from_slice(&aead2022_udp_eih_blocks( + self.kind, + &self.identity_keys, + &self.user_key, + &packet[..16], + )?); + let data_index = packet.len(); + packet.push(AEAD2022_HEADER_TYPE_CLIENT); + packet.extend_from_slice(&aead2022_now_timestamp()?.to_be_bytes()); + packet.extend_from_slice(&(padding_len as u16).to_be_bytes()); + packet.resize(packet.len() + padding_len, 0); + packet.extend_from_slice(&socks_addr(destination)?); + packet.extend_from_slice(payload); + + let nonce: [u8; 12] = packet[4..16].try_into().expect("fixed packet nonce"); + let mut encrypted_body = packet[data_index..].to_vec(); + self.client_cipher + .seal_in_place(&nonce, &mut encrypted_body)?; + packet.truncate(data_index); + packet.extend_from_slice(&encrypted_body); + aead2022_aes_encrypt_block( + self.kind, + aead2022_udp_header_key(&self.identity_keys, &self.user_key), + &mut packet[..16], + )?; + Ok(packet) + } + + fn decrypt_server_packet(&mut self, packet: &[u8]) -> Result<(SocketAddr, Vec)> { + if packet.len() < 16 + 1 + 8 + 8 + 2 + self.kind.tag_len() { + bail!("Shadowsocks 2022 UDP packet is too short"); + } + let mut buf = packet.to_vec(); + aead2022_aes_decrypt_block(self.kind, &self.user_key, &mut buf[..16])?; + let session_id = u64::from_be_bytes(buf[..8].try_into().expect("fixed session id")); + let session_id_bytes = session_id.to_be_bytes(); + let cipher = Aead2022AesCcmCipher::new( + self.kind, + &aead2022_session_key(self.kind, &self.user_key, &session_id_bytes)?, + )?; + let nonce: [u8; 12] = buf[4..16].try_into().expect("fixed packet nonce"); + let plaintext = cipher.open_in_place(&nonce, &mut buf[16..])?; + if plaintext.len() < 1 + 8 + 8 + 2 { + bail!("Shadowsocks 2022 UDP response is truncated"); + } + if plaintext[0] != AEAD2022_HEADER_TYPE_SERVER { + bail!( + "unexpected Shadowsocks 2022 UDP response header type {}", + plaintext[0] + ); + } + aead2022_validate_timestamp(u64::from_be_bytes( + plaintext[1..9].try_into().expect("fixed timestamp"), + ))?; + let client_session_id = u64::from_be_bytes( + plaintext[9..17] + .try_into() + .expect("fixed client session id"), + ); + if client_session_id != self.client_session_id { + bail!("Shadowsocks 2022 UDP response client session id mismatch"); + } + let padding_len = u16::from_be_bytes([plaintext[17], plaintext[18]]) as usize; + let address_offset = 19usize + .checked_add(padding_len) + .ok_or_else(|| anyhow!("Shadowsocks 2022 UDP response padding is too large"))?; + if plaintext.len() < address_offset { + bail!("Shadowsocks 2022 UDP response padding is truncated"); + } + let (source, used) = parse_socks_addr(&plaintext[address_offset..])?; + Ok((source, plaintext[address_offset + used..].to_vec())) + } +} + +enum CustomSsAeadCipher { + Aes192Gcm(Aes192Gcm), + Aes192Ccm(Aes192Ccm), + Chacha8IetfPoly1305(ChaCha8Poly1305), + XChacha8IetfPoly1305(XChaCha8Poly1305), + Rabbit128Poly1305([u8; 16]), + Aegis128L([u8; 16]), + Aegis256([u8; 32]), + Aez384([u8; 48]), + DeoxysII256128(DeoxysII256), + Ascon128([u8; 16]), + Ascon128A([u8; 16]), + Lea128Gcm(Lea128), + Lea192Gcm(Lea192), + Lea256Gcm(Lea256), +} + +impl CustomSsAeadCipher { + fn new(kind: CustomSsAeadKind, key: &[u8]) -> Result { + match kind { + CustomSsAeadKind::Aes192Gcm => Ok(Self::Aes192Gcm( + Aes192Gcm::new_from_slice(key).map_err(|_| anyhow!("invalid AES-192-GCM key"))?, + )), + CustomSsAeadKind::Aes192Ccm => Ok(Self::Aes192Ccm( + Aes192Ccm::new_from_slice(key).map_err(|_| anyhow!("invalid AES-192-CCM key"))?, + )), + CustomSsAeadKind::Chacha8IetfPoly1305 => Ok(Self::Chacha8IetfPoly1305( + ChaCha8Poly1305::new_from_slice(key) + .map_err(|_| anyhow!("invalid ChaCha8-Poly1305 key"))?, + )), + CustomSsAeadKind::XChacha8IetfPoly1305 => Ok(Self::XChacha8IetfPoly1305( + XChaCha8Poly1305::new_from_slice(key) + .map_err(|_| anyhow!("invalid XChaCha8-Poly1305 key"))?, + )), + CustomSsAeadKind::Rabbit128Poly1305 => Ok(Self::Rabbit128Poly1305( + key.try_into() + .map_err(|_| anyhow!("invalid Rabbit128-Poly1305 key"))?, + )), + CustomSsAeadKind::Aegis128L => Ok(Self::Aegis128L( + key.try_into() + .map_err(|_| anyhow!("invalid AEGIS-128L key"))?, + )), + CustomSsAeadKind::Aegis256 => Ok(Self::Aegis256( + key.try_into() + .map_err(|_| anyhow!("invalid AEGIS-256 key"))?, + )), + CustomSsAeadKind::Aez384 => Ok(Self::Aez384( + key.try_into().map_err(|_| anyhow!("invalid AEZ-384 key"))?, + )), + CustomSsAeadKind::DeoxysII256128 => Ok(Self::DeoxysII256128( + DeoxysII256::new_from_slice(key) + .map_err(|_| anyhow!("invalid Deoxys-II-256-128 key"))?, + )), + CustomSsAeadKind::Ascon128 => Ok(Self::Ascon128( + key.try_into() + .map_err(|_| anyhow!("invalid Ascon128 key"))?, + )), + CustomSsAeadKind::Ascon128A => Ok(Self::Ascon128A( + key.try_into() + .map_err(|_| anyhow!("invalid Ascon128a key"))?, + )), + CustomSsAeadKind::Lea128Gcm => Ok(Self::Lea128Gcm(Lea128::new( + LeaGenericArray::from_slice(key), + ))), + CustomSsAeadKind::Lea192Gcm => Ok(Self::Lea192Gcm(Lea192::new( + LeaGenericArray::from_slice(key), + ))), + CustomSsAeadKind::Lea256Gcm => Ok(Self::Lea256Gcm(Lea256::new( + LeaGenericArray::from_slice(key), + ))), + } + } + + fn seal_in_place(&self, nonce: &mut [u8], buf: &mut Vec) -> Result<()> { + let tag = match self { + Self::Aes192Gcm(cipher) => cipher + .encrypt_in_place_detached(GenericArray::from_slice(nonce), &[], buf) + .map_err(|_| anyhow!("AES-192-GCM Shadowsocks seal failed"))? + .as_slice() + .to_vec(), + Self::Aes192Ccm(cipher) => cipher + .encrypt_in_place_detached(GenericArray::from_slice(nonce), &[], buf) + .map_err(|_| anyhow!("AES-192-CCM Shadowsocks seal failed"))? + .as_slice() + .to_vec(), + Self::Chacha8IetfPoly1305(cipher) => cipher + .encrypt_in_place_detached(GenericArray::::from_slice(nonce), &[], buf) + .map_err(|_| anyhow!("ChaCha8-Poly1305 Shadowsocks seal failed"))? + .as_slice() + .to_vec(), + Self::XChacha8IetfPoly1305(cipher) => cipher + .encrypt_in_place_detached(GenericArray::::from_slice(nonce), &[], buf) + .map_err(|_| anyhow!("XChaCha8-Poly1305 Shadowsocks seal failed"))? + .as_slice() + .to_vec(), + Self::Rabbit128Poly1305(key) => { + let nonce: [u8; 8] = nonce + .try_into() + .map_err(|_| anyhow!("invalid Rabbit128-Poly1305 nonce"))?; + rabbit_poly1305_seal_in_place(key, &nonce, buf)? + } + Self::Aegis128L(key) => { + let nonce: [u8; 16] = nonce + .try_into() + .map_err(|_| anyhow!("invalid AEGIS-128L nonce"))?; + Aegis128L::<16>::new(key, &nonce) + .encrypt_in_place(buf, &[]) + .to_vec() + } + Self::Aegis256(key) => { + let nonce: [u8; 32] = nonce + .try_into() + .map_err(|_| anyhow!("invalid AEGIS-256 nonce"))?; + Aegis256::<16>::new(key, &nonce) + .encrypt_in_place(buf, &[]) + .to_vec() + } + Self::Aez384(key) => { + let aead_nonce: [u8; 16] = nonce + .try_into() + .map_err(|_| anyhow!("invalid AEZ-384 nonce"))?; + let cipher = zears::Aez::new(key); + cipher.encrypt_vec(&aead_nonce, &[], 16, buf); + increment_nonce(nonce); + return Ok(()); + } + Self::DeoxysII256128(cipher) => cipher + .encrypt_inout_detached( + &DeoxysArray::try_from(&nonce[..]) + .map_err(|_| anyhow!("invalid Deoxys-II-256-128 nonce"))?, + &[], + buf.as_mut_slice().into(), + ) + .map_err(|_| anyhow!("Deoxys-II-256-128 Shadowsocks seal failed"))? + .as_slice() + .to_vec(), + Self::Ascon128(key) => { + let nonce: [u8; 16] = nonce + .try_into() + .map_err(|_| anyhow!("invalid Ascon128 nonce"))?; + ascon_seal_in_place(key, &nonce, AsconMode::Ascon128, buf) + } + Self::Ascon128A(key) => { + let nonce: [u8; 16] = nonce + .try_into() + .map_err(|_| anyhow!("invalid Ascon128a nonce"))?; + ascon_seal_in_place(key, &nonce, AsconMode::Ascon128A, buf) + } + Self::Lea128Gcm(_) | Self::Lea192Gcm(_) | Self::Lea256Gcm(_) => { + let nonce: [u8; 12] = nonce + .try_into() + .map_err(|_| anyhow!("invalid LEA-GCM nonce"))?; + lea_gcm_seal_in_place(self, &nonce, buf) + } + }; + buf.extend_from_slice(&tag); + increment_nonce(nonce); + Ok(()) + } + + fn open_in_place<'a>(&self, nonce: &mut [u8], buf: &'a mut [u8]) -> Result<&'a [u8]> { + if buf.len() < 16 { + bail!("Shadowsocks AEAD packet missing tag"); + } + let (ciphertext, tag) = buf.split_at_mut(buf.len() - 16); + match self { + Self::Aes192Gcm(cipher) => cipher + .decrypt_in_place_detached( + GenericArray::from_slice(nonce), + &[], + ciphertext, + GenericArray::from_slice(tag), + ) + .map_err(|_| anyhow!("Shadowsocks AEAD open failed"))?, + Self::Aes192Ccm(cipher) => cipher + .decrypt_in_place_detached( + GenericArray::from_slice(nonce), + &[], + ciphertext, + GenericArray::from_slice(tag), + ) + .map_err(|_| anyhow!("Shadowsocks AEAD open failed"))?, + Self::Chacha8IetfPoly1305(cipher) => cipher + .decrypt_in_place_detached( + GenericArray::::from_slice(nonce), + &[], + ciphertext, + GenericArray::from_slice(tag), + ) + .map_err(|_| anyhow!("Shadowsocks AEAD open failed"))?, + Self::XChacha8IetfPoly1305(cipher) => cipher + .decrypt_in_place_detached( + GenericArray::::from_slice(nonce), + &[], + ciphertext, + GenericArray::from_slice(tag), + ) + .map_err(|_| anyhow!("Shadowsocks AEAD open failed"))?, + Self::Rabbit128Poly1305(key) => { + let nonce: [u8; 8] = nonce + .try_into() + .map_err(|_| anyhow!("invalid Rabbit128-Poly1305 nonce"))?; + rabbit_poly1305_open_in_place(key, &nonce, ciphertext, tag)? + } + Self::Aegis128L(key) => { + let nonce: [u8; 16] = nonce + .try_into() + .map_err(|_| anyhow!("invalid AEGIS-128L nonce"))?; + let tag: [u8; 16] = tag + .try_into() + .map_err(|_| anyhow!("invalid AEGIS-128L tag"))?; + Aegis128L::<16>::new(key, &nonce) + .decrypt_in_place(ciphertext, &tag, &[]) + .map_err(|_| anyhow!("Shadowsocks AEAD open failed"))? + } + Self::Aegis256(key) => { + let nonce: [u8; 32] = nonce + .try_into() + .map_err(|_| anyhow!("invalid AEGIS-256 nonce"))?; + let tag: [u8; 16] = tag + .try_into() + .map_err(|_| anyhow!("invalid AEGIS-256 tag"))?; + Aegis256::<16>::new(key, &nonce) + .decrypt_in_place(ciphertext, &tag, &[]) + .map_err(|_| anyhow!("Shadowsocks AEAD open failed"))? + } + Self::Aez384(key) => { + let aead_nonce: [u8; 16] = nonce + .try_into() + .map_err(|_| anyhow!("invalid AEZ-384 nonce"))?; + let cipher = zears::Aez::new(key); + let mut encrypted = Vec::with_capacity(ciphertext.len() + tag.len()); + encrypted.extend_from_slice(ciphertext); + encrypted.extend_from_slice(tag); + let plaintext = cipher + .decrypt(&aead_nonce, &[], 16, &encrypted) + .ok_or_else(|| anyhow!("Shadowsocks AEZ-384 open failed"))?; + ciphertext[..plaintext.len()].copy_from_slice(&plaintext); + increment_nonce(nonce); + return Ok(&ciphertext[..plaintext.len()]); + } + Self::DeoxysII256128(cipher) => cipher + .decrypt_inout_detached( + &DeoxysArray::try_from(&nonce[..]) + .map_err(|_| anyhow!("invalid Deoxys-II-256-128 nonce"))?, + &[], + ciphertext.into(), + &DeoxysArray::try_from(&tag[..]) + .map_err(|_| anyhow!("invalid Deoxys-II-256-128 tag"))?, + ) + .map_err(|_| anyhow!("Shadowsocks Deoxys-II-256-128 open failed"))?, + Self::Ascon128(key) => { + let nonce: [u8; 16] = nonce + .try_into() + .map_err(|_| anyhow!("invalid Ascon128 nonce"))?; + ascon_open_in_place(key, &nonce, AsconMode::Ascon128, ciphertext, tag)? + } + Self::Ascon128A(key) => { + let nonce: [u8; 16] = nonce + .try_into() + .map_err(|_| anyhow!("invalid Ascon128a nonce"))?; + ascon_open_in_place(key, &nonce, AsconMode::Ascon128A, ciphertext, tag)? + } + Self::Lea128Gcm(_) | Self::Lea192Gcm(_) | Self::Lea256Gcm(_) => { + let nonce: [u8; 12] = nonce + .try_into() + .map_err(|_| anyhow!("invalid LEA-GCM nonce"))?; + lea_gcm_open_in_place(self, &nonce, ciphertext, tag)? + } + }; + increment_nonce(nonce); + Ok(ciphertext) + } +} + +fn lea_gcm_seal_in_place( + cipher: &CustomSsAeadCipher, + nonce: &[u8; 12], + plaintext_in_ciphertext_out: &mut [u8], +) -> Vec { + lea_gcm_apply_ctr(cipher, nonce, plaintext_in_ciphertext_out); + lea_gcm_tag(cipher, nonce, plaintext_in_ciphertext_out) +} + +fn lea_gcm_open_in_place( + cipher: &CustomSsAeadCipher, + nonce: &[u8; 12], + ciphertext: &mut [u8], + tag: &[u8], +) -> Result<()> { + let expected = lea_gcm_tag(cipher, nonce, ciphertext); + if !bool::from(expected.as_slice().ct_eq(tag)) { + bail!("Shadowsocks LEA-GCM tag verification failed"); + } + lea_gcm_apply_ctr(cipher, nonce, ciphertext); + Ok(()) +} + +fn lea_gcm_apply_ctr(cipher: &CustomSsAeadCipher, nonce: &[u8; 12], buf: &mut [u8]) { + let mut counter = [0u8; 16]; + counter[..12].copy_from_slice(nonce); + counter[15] = 1; + lea_gcm_increment_counter(&mut counter); + + for chunk in buf.chunks_mut(16) { + let mut key_stream = counter; + lea_encrypt_block(cipher, &mut key_stream); + for (byte, key_stream_byte) in chunk.iter_mut().zip(key_stream) { + *byte ^= key_stream_byte; + } + lea_gcm_increment_counter(&mut counter); + } +} + +fn lea_gcm_tag(cipher: &CustomSsAeadCipher, nonce: &[u8; 12], ciphertext: &[u8]) -> Vec { + let mut h = [0u8; 16]; + lea_encrypt_block(cipher, &mut h); + + let mut tag_mask = [0u8; 16]; + tag_mask[..12].copy_from_slice(nonce); + tag_mask[15] = 1; + lea_encrypt_block(cipher, &mut tag_mask); + + let auth = ghash(&h, &[], ciphertext); + tag_mask + .iter() + .zip(auth) + .map(|(left, right)| left ^ right) + .collect() +} + +fn lea_encrypt_block(cipher: &CustomSsAeadCipher, block: &mut [u8; 16]) { + let block = LeaGenericArray::from_mut_slice(block); + match cipher { + CustomSsAeadCipher::Lea128Gcm(cipher) => cipher.encrypt_block(block), + CustomSsAeadCipher::Lea192Gcm(cipher) => cipher.encrypt_block(block), + CustomSsAeadCipher::Lea256Gcm(cipher) => cipher.encrypt_block(block), + _ => unreachable!("LEA-GCM block encryption called with non-LEA cipher"), + } +} + +fn lea_gcm_increment_counter(counter: &mut [u8; 16]) { + let value = + u32::from_be_bytes(counter[12..16].try_into().expect("fixed GCM counter")).wrapping_add(1); + counter[12..16].copy_from_slice(&value.to_be_bytes()); +} + +fn ghash(h: &[u8; 16], associated_data: &[u8], ciphertext: &[u8]) -> [u8; 16] { + let h = u128::from_be_bytes(*h); + let mut y = 0u128; + ghash_update(&mut y, h, associated_data); + ghash_update(&mut y, h, ciphertext); + let length_block = + ((associated_data.len() as u128) * 8) << 64 | ((ciphertext.len() as u128) * 8); + y = ghash_mul(y ^ length_block, h); + y.to_be_bytes() +} + +fn ghash_update(y: &mut u128, h: u128, mut input: &[u8]) { + while input.len() >= 16 { + *y = ghash_mul( + *y ^ u128::from_be_bytes(input[..16].try_into().expect("full GHASH block")), + h, + ); + input = &input[16..]; + } + if !input.is_empty() { + let mut block = [0u8; 16]; + block[..input.len()].copy_from_slice(input); + *y = ghash_mul(*y ^ u128::from_be_bytes(block), h); + } +} + +fn ghash_mul(mut x: u128, mut y: u128) -> u128 { + let reduction = 0xe1000000000000000000000000000000u128; + let mut z = 0u128; + for _ in 0..128 { + if x & (1u128 << 127) != 0 { + z ^= y; + } + let carry = y & 1 != 0; + y >>= 1; + if carry { + y ^= reduction; + } + x <<= 1; + } + z +} + +#[derive(Debug, Clone, Copy)] +enum AsconMode { + Ascon128, + Ascon128A, +} + +impl AsconMode { + fn block_size(self) -> usize { + match self { + Self::Ascon128 => 8, + Self::Ascon128A => 16, + } + } + + fn perm_b(self) -> usize { + match self { + Self::Ascon128 => 6, + Self::Ascon128A => 8, + } + } +} + +fn ascon_seal_in_place( + key: &[u8; 16], + nonce: &[u8; 16], + mode: AsconMode, + plaintext_in_ciphertext_out: &mut [u8], +) -> Vec { + let mut state = ascon_initialize(key, nonce, mode); + ascon_assoc_data(&mut state, mode, &[]); + ascon_proc_text(&mut state, mode, plaintext_in_ciphertext_out, true); + ascon_finalize(&mut state, mode, key) +} + +fn ascon_open_in_place( + key: &[u8; 16], + nonce: &[u8; 16], + mode: AsconMode, + ciphertext: &mut [u8], + tag: &[u8], +) -> Result<()> { + let mut state = ascon_initialize(key, nonce, mode); + ascon_assoc_data(&mut state, mode, &[]); + ascon_proc_text(&mut state, mode, ciphertext, false); + let expected = ascon_finalize(&mut state, mode, key); + if !bool::from(expected.as_slice().ct_eq(tag)) { + bail!("Shadowsocks Ascon tag verification failed"); + } + Ok(()) +} + +fn ascon_initialize(key: &[u8; 16], nonce: &[u8; 16], mode: AsconMode) -> [u64; 5] { + let k1 = u64::from_be_bytes(key[..8].try_into().expect("fixed key half")); + let k2 = u64::from_be_bytes(key[8..].try_into().expect("fixed key half")); + let block_bits = (mode.block_size() as u64) * 8; + let perm_b = mode.perm_b() as u64; + let n1 = u64::from_be_bytes(nonce[..8].try_into().expect("fixed nonce half")); + let n2 = u64::from_be_bytes(nonce[8..].try_into().expect("fixed nonce half")); + let mut state = [ + (128u64 << 56) | (block_bits << 48) | (12u64 << 40) | (perm_b << 32), + k1, + k2, + n1, + n2, + ]; + ascon_perm(12, &mut state); + state[3] ^= k1; + state[4] ^= k2; + state +} + +fn ascon_assoc_data(state: &mut [u64; 5], mode: AsconMode, mut associated_data: &[u8]) { + let block_size = mode.block_size(); + let perm_b = mode.perm_b(); + if !associated_data.is_empty() { + while associated_data.len() >= block_size { + for index in 0..(block_size / 8) { + let start = index * 8; + state[index] ^= u64::from_be_bytes( + associated_data[start..start + 8] + .try_into() + .expect("full Ascon AD block"), + ); + } + ascon_perm(perm_b, state); + associated_data = &associated_data[block_size..]; + } + for (index, byte) in associated_data.iter().enumerate() { + state[index / 8] ^= (*byte as u64) << (56 - 8 * (index % 8)); + } + state[associated_data.len() / 8] ^= 0x80u64 << (56 - 8 * (associated_data.len() % 8)); + ascon_perm(perm_b, state); + } + state[4] ^= 0x01; +} + +fn ascon_proc_text(state: &mut [u64; 5], mode: AsconMode, buf: &mut [u8], encrypt: bool) { + let block_size = mode.block_size(); + let perm_b = mode.perm_b(); + let mut offset = 0; + while buf.len() - offset >= block_size { + for index in 0..(block_size / 8) { + let start = offset + index * 8; + let input = u64::from_be_bytes(buf[start..start + 8].try_into().expect("full block")); + let output = state[index] ^ input; + buf[start..start + 8].copy_from_slice(&output.to_be_bytes()); + state[index] = if encrypt { output } else { input }; + } + ascon_perm(perm_b, state); + offset += block_size; + } + + let remaining = buf.len() - offset; + for index in 0..remaining { + let state_index = index / 8; + let shift = 56 - 8 * (index % 8); + let state_byte = ((state[state_index] >> shift) & 0xff) as u8; + let input = buf[offset + index]; + let output = state_byte ^ input; + buf[offset + index] = output; + let state_value = if encrypt { output } else { input }; + state[state_index] = + (state[state_index] & !(0xffu64 << shift)) | ((state_value as u64) << shift); + } + state[remaining / 8] ^= 0x80u64 << (56 - 8 * (remaining % 8)); +} + +fn ascon_finalize(state: &mut [u64; 5], mode: AsconMode, key: &[u8; 16]) -> Vec { + let k1 = u64::from_be_bytes(key[..8].try_into().expect("fixed key half")); + let k2 = u64::from_be_bytes(key[8..].try_into().expect("fixed key half")); + let block_words = mode.block_size() / 8; + state[block_words] ^= k1; + state[block_words + 1] ^= k2; + ascon_perm(12, state); + + let mut tag = Vec::with_capacity(16); + tag.extend_from_slice(&(state[3] ^ k1).to_be_bytes()); + tag.extend_from_slice(&(state[4] ^ k2).to_be_bytes()); + tag +} + +fn ascon_perm(rounds: usize, state: &mut [u64; 5]) { + let [mut x0, mut x1, mut x2, mut x3, mut x4] = *state; + for round in (12 - rounds)..12 { + x2 ^= (((0xf - round) << 4) | round) as u64; + + x0 ^= x4; + x4 ^= x3; + x2 ^= x1; + let t0 = x0 & !x4; + let mut t1 = x2 & !x1; + x0 ^= t1; + t1 = x4 & !x3; + x2 ^= t1; + t1 = x1 & !x0; + x4 ^= t1; + t1 = x3 & !x2; + x1 ^= t1; + x3 ^= t0; + x1 ^= x0; + x3 ^= x2; + x0 ^= x4; + x2 = !x2; + + x0 ^= x0.rotate_right(19) ^ x0.rotate_right(28); + x1 ^= x1.rotate_right(61) ^ x1.rotate_right(39); + x2 ^= x2.rotate_right(1) ^ x2.rotate_right(6); + x3 ^= x3.rotate_right(10) ^ x3.rotate_right(17); + x4 ^= x4.rotate_right(7) ^ x4.rotate_right(41); + } + *state = [x0, x1, x2, x3, x4]; +} + +fn rabbit_poly1305_seal_in_place( + key: &[u8; 16], + nonce: &[u8; 8], + plaintext_in_ciphertext_out: &mut [u8], +) -> Result> { + let mut poly_key = [0u8; 32]; + rabbit_apply_keystream(key, nonce, &mut poly_key)?; + rabbit_apply_keystream(key, nonce, plaintext_in_ciphertext_out)?; + Ok(rabbit_poly1305_tag(&poly_key, &[], plaintext_in_ciphertext_out).to_vec()) +} + +fn rabbit_poly1305_open_in_place( + key: &[u8; 16], + nonce: &[u8; 8], + ciphertext: &mut [u8], + tag: &[u8], +) -> Result<()> { + let mut poly_key = [0u8; 32]; + rabbit_apply_keystream(key, nonce, &mut poly_key)?; + let expected = rabbit_poly1305_tag(&poly_key, &[], ciphertext); + if !bool::from(expected.as_slice().ct_eq(tag)) { + bail!("Shadowsocks Rabbit128-Poly1305 tag verification failed"); + } + rabbit_apply_keystream(key, nonce, ciphertext)?; + Ok(()) +} + +fn rabbit_apply_keystream(key: &[u8; 16], nonce: &[u8; 8], buf: &mut [u8]) -> Result<()> { + let mut cipher = Rabbit::new_from_slices(key, nonce) + .map_err(|_| anyhow!("invalid Rabbit128-Poly1305 key or nonce"))?; + cipher.apply_keystream(buf); + Ok(()) +} + +fn rabbit_poly1305_tag(poly_key: &[u8; 32], associated_data: &[u8], ciphertext: &[u8]) -> [u8; 16] { + let mut mac = Poly1305::new(GenericArray::from_slice(poly_key)); + mac.update_padded(associated_data); + mac.update_padded(ciphertext); + let mut lengths = [0u8; 16]; + lengths[..8].copy_from_slice(&(associated_data.len() as u64).to_le_bytes()); + lengths[8..].copy_from_slice(&(ciphertext.len() as u64).to_le_bytes()); + mac.update_padded(&lengths); + mac.finalize().into() +} + +fn encrypt_custom_ss_udp_packet( + kind: CustomSsAeadKind, + master_key: &[u8], + target: SocketAddr, + payload: &[u8], +) -> Result> { + let mut salt = vec![0u8; kind.salt_len()]; + OsRng.fill_bytes(&mut salt); + let cipher = custom_ss_subkey(kind, master_key, &salt)?; + let mut nonce = vec![0u8; kind.nonce_len()]; + let mut plaintext = socks_addr(target)?; + plaintext.extend_from_slice(payload); + cipher.seal_in_place(&mut nonce, &mut plaintext)?; + let mut packet = salt; + packet.extend_from_slice(&plaintext); + Ok(packet) +} + +fn decrypt_custom_ss_udp_packet( + kind: CustomSsAeadKind, + master_key: &[u8], + packet: &[u8], +) -> Result<(SocketAddr, Vec)> { + let salt_len = kind.salt_len(); + if packet.len() <= salt_len { + bail!("Shadowsocks UDP packet missing salt"); + } + let (salt, encrypted) = packet.split_at(salt_len); + let cipher = custom_ss_subkey(kind, master_key, salt)?; + let mut nonce = vec![0u8; kind.nonce_len()]; + let mut buf = encrypted.to_vec(); + let plaintext = cipher.open_in_place(&mut nonce, &mut buf)?; + let (source, used) = parse_socks_addr(plaintext)?; + Ok((source, plaintext[used..].to_vec())) +} + +fn encrypt_custom_ss_stream_udp_packet( + kind: CustomSsStreamKind, + key: &[u8], + target: SocketAddr, + payload: &[u8], +) -> Result> { + let mut salt = vec![0u8; kind.salt_len()]; + OsRng.fill_bytes(&mut salt); + let mut cipher = CustomSsStreamCipher::new(kind, key, &salt)?; + let mut plaintext = socks_addr(target)?; + plaintext.extend_from_slice(payload); + cipher.apply_keystream(&mut plaintext); + let mut packet = salt; + packet.extend_from_slice(&plaintext); + Ok(packet) +} + +fn decrypt_custom_ss_stream_udp_packet( + kind: CustomSsStreamKind, + key: &[u8], + packet: &[u8], +) -> Result<(SocketAddr, Vec)> { + let salt_len = kind.salt_len(); + if packet.len() <= salt_len { + bail!("Shadowsocks UDP packet missing salt"); + } + let (salt, encrypted) = packet.split_at(salt_len); + let mut cipher = CustomSsStreamCipher::new(kind, key, salt)?; + let mut plaintext = encrypted.to_vec(); + cipher.apply_keystream(&mut plaintext); + let (source, used) = parse_socks_addr(&plaintext)?; + Ok((source, plaintext[used..].to_vec())) +} + +fn parse_aead2022_keys( + kind: Aead2022AesCcmKind, + password: &str, +) -> Result<(Arc<[u8]>, Arc<[Arc<[u8]>]>)> { + if password.is_empty() { + bail!("Shadowsocks 2022 password is missing"); + } + let mut keys = Vec::new(); + for segment in password.split(':') { + let decoded = BASE64_STANDARD + .decode(segment) + .context("failed to decode Shadowsocks 2022 base64 key")?; + if decoded.len() != kind.key_len() { + bail!( + "Shadowsocks 2022 key length mismatch: required {}, got {}", + kind.key_len(), + decoded.len() + ); + } + keys.push(Arc::<[u8]>::from(decoded)); + } + let user_key = keys + .pop() + .ok_or_else(|| anyhow!("Shadowsocks 2022 password is missing"))?; + Ok((user_key, Arc::from(keys))) +} + +fn aead2022_session_key(kind: Aead2022AesCcmKind, user_key: &[u8], salt: &[u8]) -> Result> { + if user_key.len() != kind.key_len() || salt.is_empty() { + bail!("invalid Shadowsocks 2022 session key material"); + } + let mut material = Vec::with_capacity(user_key.len() + salt.len()); + material.extend_from_slice(user_key); + material.extend_from_slice(salt); + let derived = blake3::derive_key(AEAD2022_SESSION_SUBKEY_CONTEXT, &material); + Ok(derived[..kind.key_len()].to_vec()) +} + +fn aead2022_identity_subkey( + kind: Aead2022AesCcmKind, + identity_key: &[u8], + salt: &[u8], +) -> Result> { + if identity_key.len() != kind.key_len() || salt.len() != kind.key_len() { + bail!("invalid Shadowsocks 2022 identity key material"); + } + let mut material = Vec::with_capacity(identity_key.len() + salt.len()); + material.extend_from_slice(identity_key); + material.extend_from_slice(salt); + let derived = blake3::derive_key(AEAD2022_IDENTITY_SUBKEY_CONTEXT, &material); + Ok(derived[..kind.key_len()].to_vec()) +} + +fn aead2022_eih_blocks( + kind: Aead2022AesCcmKind, + identity_keys: &[Arc<[u8]>], + user_key: &[u8], + salt: &[u8], +) -> Result> { + let mut blocks = Vec::with_capacity(identity_keys.len() * 16); + for (index, identity_key) in identity_keys.iter().enumerate() { + let next_key = identity_keys + .get(index + 1) + .map(AsRef::as_ref) + .unwrap_or(user_key); + let mut block = aead2022_psk_hash_block(next_key); + let subkey = aead2022_identity_subkey(kind, identity_key, salt)?; + aead2022_aes_encrypt_block(kind, &subkey, &mut block)?; + blocks.extend_from_slice(&block); + } + Ok(blocks) +} + +fn aead2022_udp_eih_blocks( + kind: Aead2022AesCcmKind, + identity_keys: &[Arc<[u8]>], + user_key: &[u8], + packet_header: &[u8], +) -> Result> { + let mut blocks = Vec::with_capacity(identity_keys.len() * 16); + for (index, identity_key) in identity_keys.iter().enumerate() { + let next_key = identity_keys + .get(index + 1) + .map(AsRef::as_ref) + .unwrap_or(user_key); + let mut block = aead2022_psk_hash_block(next_key); + for (left, right) in block.iter_mut().zip(packet_header.iter().copied()) { + *left ^= right; + } + aead2022_aes_encrypt_block(kind, identity_key, &mut block)?; + blocks.extend_from_slice(&block); + } + Ok(blocks) +} + +fn aead2022_psk_hash_block(key: &[u8]) -> [u8; 16] { + let hash = blake3::hash(key); + hash.as_bytes()[..16] + .try_into() + .expect("fixed BLAKE3 block") +} + +fn aead2022_udp_header_key<'a>(identity_keys: &'a [Arc<[u8]>], user_key: &'a [u8]) -> &'a [u8] { + identity_keys.first().map(AsRef::as_ref).unwrap_or(user_key) +} + +fn aead2022_aes_encrypt_block( + kind: Aead2022AesCcmKind, + key: &[u8], + block: &mut [u8], +) -> Result<()> { + if block.len() != 16 { + bail!("Shadowsocks 2022 AES block must be 16 bytes"); + } + match kind { + Aead2022AesCcmKind::Aes128 => { + let cipher = Aes128::new_from_slice(key) + .map_err(|_| anyhow!("invalid Shadowsocks 2022 AES-128 block key"))?; + cipher.encrypt_block(AesGenericArray::from_mut_slice(block)); + } + Aead2022AesCcmKind::Aes256 => { + let cipher = Aes256::new_from_slice(key) + .map_err(|_| anyhow!("invalid Shadowsocks 2022 AES-256 block key"))?; + cipher.encrypt_block(AesGenericArray::from_mut_slice(block)); + } + } + Ok(()) +} + +fn aead2022_aes_decrypt_block( + kind: Aead2022AesCcmKind, + key: &[u8], + block: &mut [u8], +) -> Result<()> { + if block.len() != 16 { + bail!("Shadowsocks 2022 AES block must be 16 bytes"); + } + match kind { + Aead2022AesCcmKind::Aes128 => { + let cipher = Aes128::new_from_slice(key) + .map_err(|_| anyhow!("invalid Shadowsocks 2022 AES-128 block key"))?; + cipher.decrypt_block(AesGenericArray::from_mut_slice(block)); + } + Aead2022AesCcmKind::Aes256 => { + let cipher = Aes256::new_from_slice(key) + .map_err(|_| anyhow!("invalid Shadowsocks 2022 AES-256 block key"))?; + cipher.decrypt_block(AesGenericArray::from_mut_slice(block)); + } + } + Ok(()) +} + +fn aead2022_now_timestamp() -> Result { + Ok(SystemTime::now() + .duration_since(UNIX_EPOCH) + .context("system clock is before unix epoch")? + .as_secs()) +} + +fn aead2022_validate_timestamp(timestamp: u64) -> Result<()> { + let now = aead2022_now_timestamp()?; + if now.abs_diff(timestamp) > AEAD2022_TIMESTAMP_MAX_DIFF { + bail!("Shadowsocks 2022 timestamp is outside the allowed skew"); + } + Ok(()) +} + +fn aead2022_udp_padding_len(destination: SocketAddr, payload: &[u8]) -> usize { + if destination.port() == 53 && payload.len() < 900 { + (OsRng.next_u32() as usize % (900 - payload.len())) + 1 + } else { + 0 + } +} + +fn custom_ss_subkey( + kind: CustomSsAeadKind, + master_key: &[u8], + salt: &[u8], +) -> Result { + let prk = hmac::sign( + &hmac::Key::new(hmac::HMAC_SHA1_FOR_LEGACY_USE_ONLY, salt), + master_key, + ); + let prk = hmac::Key::new(hmac::HMAC_SHA1_FOR_LEGACY_USE_ONLY, prk.as_ref()); + let mut okm = Vec::with_capacity(kind.key_len()); + let mut previous = Vec::new(); + let mut counter = 1u8; + while okm.len() < kind.key_len() { + let mut input = Vec::with_capacity(previous.len() + SS_INFO.len() + 1); + input.extend_from_slice(&previous); + input.extend_from_slice(SS_INFO); + input.push(counter); + previous = hmac::sign(&prk, &input).as_ref().to_vec(); + okm.extend_from_slice(&previous); + counter = counter + .checked_add(1) + .ok_or_else(|| anyhow!("Shadowsocks HKDF output too long"))?; + } + okm.truncate(kind.key_len()); + CustomSsAeadCipher::new(kind, &okm) +} + +fn evp_bytes_to_key(password: &[u8], key_len: usize) -> Vec { + let mut out = Vec::with_capacity(key_len); + let mut previous = Vec::new(); + while out.len() < key_len { + let mut hasher = Md5::new(); + if !previous.is_empty() { + hasher.update(&previous); + } + hasher.update(password); + previous = hasher.finalize().to_vec(); + out.extend_from_slice(&previous); + } + out.truncate(key_len); + out +} + +fn increment_nonce(nonce: &mut [u8]) { + for byte in nonce { + let (next, overflow) = byte.overflowing_add(1); + *byte = next; + if !overflow { + break; + } + } +} diff --git a/burrow/src/proxy_runtime/shadowsocks_plugins.rs b/burrow/src/proxy_runtime/shadowsocks_plugins.rs new file mode 100644 index 0000000..cd253e1 --- /dev/null +++ b/burrow/src/proxy_runtime/shadowsocks_plugins.rs @@ -0,0 +1,3061 @@ +async fn websocket_plugin_connect( + mut stream: Box, + config: &ShadowsocksWebSocketTransport, + port: u16, +) -> Result> { + if config.tls { + stream = websocket_plugin_tls_connect( + stream, + config.websocket_host_header(), + config.skip_cert_verify, + config.certificate_fingerprint.as_ref(), + config.client_identity.as_ref(), + config.ech.as_ref(), + ) + .await?; + } + let (path, max_early_data) = websocket_path_and_early_data(&config.path); + tracing::debug!( + kind = ?config.kind, + host = %config.host, + port, + path = %path, + tls = config.tls, + mux = ?config.mux, + v2ray_http_upgrade = config.v2ray_http_upgrade, + v2ray_http_upgrade_fast_open = config.v2ray_http_upgrade_fast_open, + "starting Shadowsocks websocket plugin handshake" + ); + if let Some(max_early_data) = max_early_data { + return Ok(Box::new(WebSocketEarlyDataStream::new( + stream, + config.clone(), + port, + path, + max_early_data, + ))); + } + let (request, websocket_key) = websocket_plugin_request(config, port, &path, None)?; + stream.write_all(&request).await?; + stream.flush().await?; + if config.v2ray_http_upgrade && config.v2ray_http_upgrade_fast_open { + return Ok(Box::new(HttpUpgradeFastOpenStream::new(stream))); + } + let response = read_http_upgrade_response(&mut stream).await?; + validate_websocket_upgrade_response( + &response, + config.v2ray_http_upgrade, + websocket_key.as_deref(), + )?; + tracing::debug!( + kind = ?config.kind, + host = %config.host, + port, + path = %path, + "completed Shadowsocks websocket plugin handshake" + ); + + if config.v2ray_http_upgrade { + Ok(stream) + } else { + Ok(Box::new(WebSocketStream::new(stream))) + } +} + +async fn gost_smux_stream(stream: Box) -> Result> { + let mut smux_config = tokio_smux::SmuxConfig::default(); + smux_config.keep_alive_disable = true; + let mut session = tokio_smux::Session::client(stream, smux_config) + .map_err(|err| anyhow!("failed to start gost-plugin smux session: {err}"))?; + let mut smux_stream = session + .open_stream() + .await + .map_err(|err| anyhow!("failed to open gost-plugin smux stream: {err}"))?; + let (client, bridge) = tokio::io::duplex(64 * 1024); + let (mut bridge_read, mut bridge_write) = split(bridge); + let (upload_tx, mut upload_rx) = mpsc::channel::>(32); + + let upload = tokio::spawn(async move { + let mut buf = vec![0u8; 16 * 1024]; + loop { + let read = bridge_read.read(&mut buf).await?; + if read == 0 { + break; + } + if upload_tx.send(buf[..read].to_vec()).await.is_err() { + break; + } + } + Result::<()>::Ok(()) + }); + + tokio::spawn(async move { + let _session = session; + let result: Result<()> = async { + loop { + tokio::select! { + outbound = upload_rx.recv() => { + let Some(outbound) = outbound else { + break; + }; + smux_stream + .send_message(outbound) + .await + .map_err(|err| anyhow!("failed to write gost-plugin smux stream: {err}"))?; + } + inbound = smux_stream.recv_message() => { + let Some(inbound) = inbound + .map_err(|err| anyhow!("failed to read gost-plugin smux stream: {err}"))? + else { + break; + }; + bridge_write.write_all(&inbound).await?; + bridge_write.flush().await?; + } + } + } + Ok(()) + } + .await; + if let Err(err) = result { + tracing::debug!(error = %err, "gost-plugin smux bridge stopped"); + } + upload.abort(); + }); + + Ok(Box::new(client)) +} + +async fn kcptun_open_session( + endpoint: &ProxyServerEndpoint, + address: SocketAddr, + config: &ShadowsocksKcptunTransport, +) -> Result> { + let kcp_config = KcpConfig { + mtu: config.mtu, + nodelay: KcpNoDelayConfig { + nodelay: config.no_delay != 0, + interval: config.interval, + resend: config.resend, + nc: config.no_congestion, + }, + wnd_size: (config.sndwnd, config.rcvwnd), + flush_write: false, + flush_acks_input: config.ack_nodelay, + stream: true, + fec_data_shards: config.data_shard, + fec_parity_shards: config.parity_shard, + crypt: kcptun_block_crypt(&config.key, &config.crypt)?, + ..Default::default() + }; + let udp = match address.ip() { + IpAddr::V4(_) => UdpSocket::bind((Ipv4Addr::UNSPECIFIED, 0)).await?, + IpAddr::V6(_) => UdpSocket::bind((Ipv6Addr::UNSPECIFIED, 0)).await?, + }; + bind_proxy_outbound_socket(udp.as_raw_fd(), address.ip())?; + configure_kcptun_udp_socket(udp.as_raw_fd(), address.ip(), config); + let kcp = KcpStream::connect_with_socket(&kcp_config, udp, address) + .await + .with_context(|| { + format!( + "failed to connect Shadowsocks kcptun transport {}:{}", + endpoint.host, endpoint.port + ) + })?; + let keep_alive = if config.keep_alive == 0 { + 10 + } else { + config.keep_alive + }; + let keep_alive_interval = Duration::from_secs(keep_alive); + let smux_config = smux_rust::Config { + version: config.smux_ver, + keep_alive_disabled: false, + keep_alive_interval, + keep_alive_timeout: kcptun_smux_keep_alive_timeout(keep_alive_interval), + max_frame_size: config.frame_size, + max_receive_buffer: config.smux_buf, + max_stream_buffer: config.stream_buf, + }; + let session = match (config.no_comp, config.rate_limit) { + (true, 0) => kcptun_open_smux_session(kcp, smux_config).await?, + (true, rate_limit) => { + kcptun_open_smux_session(RateLimitedStream::new(kcp, rate_limit), smux_config).await? + } + (false, 0) => kcptun_open_smux_session(SnappyIO::new(kcp), smux_config).await?, + (false, rate_limit) => { + kcptun_open_smux_session( + SnappyIO::new(RateLimitedStream::new(kcp, rate_limit)), + smux_config, + ) + .await? + } + }; + tracing::debug!( + server = %endpoint.host, + port = endpoint.port, + crypt = %config.crypt, + mode = %config.mode, + rate_limit = config.rate_limit, + no_comp = config.no_comp, + smux_ver = config.smux_ver, + "opened Shadowsocks kcptun plugin session" + ); + Ok(session) +} + +async fn kcptun_open_smux_session( + transport: T, + smux_config: smux_rust::Config, +) -> Result> +where + T: AsyncRead + AsyncWrite + Unpin + Send + 'static, +{ + smux_config + .verify() + .map_err(|err| anyhow!("invalid Shadowsocks kcptun smux config: {err}"))?; + let session = smux_rust::client(Box::new(transport), Some(smux_config)) + .await + .map_err(|err| anyhow!("failed to start Shadowsocks kcptun smux session: {err}"))?; + Ok(session) +} + +async fn kcptun_open_smux_stream(session: Arc) -> Result> { + let stream = session + .open_stream() + .await + .map_err(|err| anyhow!("failed to open Shadowsocks kcptun smux stream: {err}"))?; + Ok(Box::new(stream)) +} + +fn kcptun_smux_keep_alive_timeout(keep_alive_interval: Duration) -> Duration { + if keep_alive_interval >= KCPTUN_SMUX_DEFAULT_KEEP_ALIVE_TIMEOUT { + keep_alive_interval.saturating_mul(3) + } else { + KCPTUN_SMUX_DEFAULT_KEEP_ALIVE_TIMEOUT + } +} + +fn schedule_kcptun_session_close(session: Arc, scavenge_ttl: u64) { + tokio::spawn(async move { + tokio::time::sleep(Duration::from_secs(scavenge_ttl)).await; + if !session.is_closed() { + let _ = session.close().await; + } + }); +} + +fn configure_kcptun_udp_socket( + fd: std::os::fd::RawFd, + ip: IpAddr, + config: &ShadowsocksKcptunTransport, +) { + if config.dscp > 0 { + if let Err(err) = set_kcptun_dscp(fd, ip, config.dscp) { + tracing::debug!(dscp = config.dscp, error = %err, "failed to set Shadowsocks kcptun DSCP"); + } + } + if config.sockbuf > 0 { + let sockbuf = kcptun_socket_int_value(config.sockbuf as u64); + if let Err(err) = set_socket_int_option(fd, libc::SOL_SOCKET, libc::SO_RCVBUF, sockbuf) { + tracing::debug!(sockbuf = config.sockbuf, error = %err, "failed to set Shadowsocks kcptun receive buffer"); + } + if let Err(err) = set_socket_int_option(fd, libc::SOL_SOCKET, libc::SO_SNDBUF, sockbuf) { + tracing::debug!(sockbuf = config.sockbuf, error = %err, "failed to set Shadowsocks kcptun send buffer"); + } + } +} + +fn set_kcptun_dscp(fd: std::os::fd::RawFd, ip: IpAddr, dscp: u32) -> io::Result<()> { + match ip { + IpAddr::V4(_) => set_socket_int_option( + fd, + libc::IPPROTO_IP, + libc::IP_TOS, + kcptun_ipv4_dscp_tos(dscp), + ), + IpAddr::V6(_) => set_socket_int_option( + fd, + libc::IPPROTO_IPV6, + libc::IPV6_TCLASS, + kcptun_socket_int_value(dscp as u64), + ), + } +} + +fn kcptun_ipv4_dscp_tos(dscp: u32) -> libc::c_int { + dscp.saturating_mul(4).min(u8::MAX as u32) as libc::c_int +} + +fn kcptun_socket_int_value(value: u64) -> libc::c_int { + value.min(libc::c_int::MAX as u64) as libc::c_int +} + +fn set_socket_int_option( + fd: std::os::fd::RawFd, + level: libc::c_int, + option: libc::c_int, + value: libc::c_int, +) -> io::Result<()> { + let result = unsafe { + libc::setsockopt( + fd, + level, + option, + (&value as *const libc::c_int).cast::(), + std::mem::size_of_val(&value) as libc::socklen_t, + ) + }; + if result != 0 { + return Err(io::Error::last_os_error()); + } + Ok(()) +} + +fn kcptun_block_crypt(key: &str, crypt: &str) -> Result>> { + let mut derived = [0u8; 32]; + pbkdf2::derive( + pbkdf2::PBKDF2_HMAC_SHA1, + NonZeroU32::new(4096).expect("non-zero PBKDF2 iterations"), + b"kcp-go", + key.as_bytes(), + &mut derived, + ); + let crypt = crypt.trim().to_ascii_lowercase(); + let block: Arc = match crypt.as_str() { + "null" => return Ok(None), + "none" => KcpNoneBlockCrypt::new(&derived) + .map_err(|err| anyhow!("invalid kcptun none crypt key: {err}"))?, + "tea" => Arc::new( + KcpTeaBlockCrypt::new(&derived[..16]) + .map_err(|err| anyhow!("invalid kcptun tea crypt key: {err}"))?, + ), + "xor" | "simple_xor" => Arc::new( + KcpSimpleXorBlockCrypt::new(&derived) + .map_err(|err| anyhow!("invalid kcptun xor crypt key: {err}"))?, + ), + "aes" | "aes-256" => Arc::new( + KcpAes256BlockCrypt::new(&derived) + .map_err(|err| anyhow!("invalid kcptun aes crypt key: {err}"))?, + ), + "aes-128" => Arc::new( + KcpAes128BlockCrypt::new(&derived[..16]) + .map_err(|err| anyhow!("invalid kcptun aes-128 crypt key: {err}"))?, + ), + "aes-192" => Arc::new( + KcpAes192BlockCrypt::new(&derived[..24]) + .map_err(|err| anyhow!("invalid kcptun aes-192 crypt key: {err}"))?, + ), + "blowfish" => Arc::new( + KcpBlowfishBlockCrypt::new(&derived) + .map_err(|err| anyhow!("invalid kcptun blowfish crypt key: {err}"))?, + ), + "cast5" => Arc::new( + KcpCast5BlockCrypt::new(&derived[..16]) + .map_err(|err| anyhow!("invalid kcptun cast5 crypt key: {err}"))?, + ), + "3des" | "triple_des" => Arc::new( + KcpTripleDesBlockCrypt::new(&derived[..24]) + .map_err(|err| anyhow!("invalid kcptun 3des crypt key: {err}"))?, + ), + "twofish" => Arc::new( + KcpTwofishBlockCrypt::new(&derived) + .map_err(|err| anyhow!("invalid kcptun twofish crypt key: {err}"))?, + ), + "xtea" => Arc::new( + KcpXteaBlockCrypt::new(&derived[..16]) + .map_err(|err| anyhow!("invalid kcptun xtea crypt key: {err}"))?, + ), + "salsa20" => Arc::new( + KcpSalsa20BlockCrypt::new(&derived) + .map_err(|err| anyhow!("invalid kcptun salsa20 crypt key: {err}"))?, + ), + "sm4" => Arc::new( + KcpSm4BlockCrypt::new(&derived[..16]) + .map_err(|err| anyhow!("invalid kcptun sm4 crypt key: {err}"))?, + ), + "aes-128-gcm" => Arc::new( + KcpAesGcmBlockCrypt::new(&derived[..16]) + .map_err(|err| anyhow!("invalid kcptun aes-128-gcm crypt key: {err}"))?, + ), + _ => Arc::new( + KcpAes256BlockCrypt::new(&derived) + .map_err(|err| anyhow!("invalid kcptun fallback aes crypt key: {err}"))?, + ), + }; + Ok(Some(block)) +} + +async fn websocket_plugin_tls_connect( + stream: Box, + server_name: &str, + skip_cert_verify: bool, + certificate_fingerprint: Option<&CertificateFingerprint>, + client_identity: Option<&TlsClientIdentity>, + ech: Option<&ShadowsocksEchConfig>, +) -> Result> { + #[cfg(target_vendor = "apple")] + { + if ech.is_none() { + return websocket_plugin_native_tls_connect( + stream, + server_name, + skip_cert_verify, + certificate_fingerprint, + client_identity, + ) + .await; + } + } + websocket_plugin_rustls_tls_connect( + stream, + server_name, + skip_cert_verify, + certificate_fingerprint, + client_identity, + ech, + ) + .await +} + +async fn websocket_plugin_rustls_tls_connect( + stream: Box, + server_name: &str, + skip_cert_verify: bool, + certificate_fingerprint: Option<&CertificateFingerprint>, + client_identity: Option<&TlsClientIdentity>, + ech: Option<&ShadowsocksEchConfig>, +) -> Result> { + let ech_config_list = resolve_shadowsocks_ech_config_list(server_name, ech)?; + let config = rustls_tls_config( + &["http/1.1".to_owned()], + skip_cert_verify, + certificate_fingerprint, + client_identity, + ech_config_list.as_deref(), + )?; + let connector = TlsConnector::from(Arc::new(config)); + let name = ServerName::try_from(server_name.to_owned()) + .with_context(|| format!("invalid websocket plugin SNI {server_name}"))?; + Ok(Box::new(connector.connect(name, stream).await?)) +} + +fn resolve_shadowsocks_ech_config_list( + server_name: &str, + ech: Option<&ShadowsocksEchConfig>, +) -> Result>> { + let Some(ech) = ech else { + return Ok(None); + }; + if let Some(config_list) = ech.config_list.as_ref() { + return Ok(Some(config_list.clone())); + } + + let query_name = ech.query_server_name.as_deref().unwrap_or(server_name); + let mut last_error = None; + for dns_server in platform_proxy_dns_servers() { + let Ok(ip) = dns_server.parse::() else { + continue; + }; + let dns_addr = SocketAddr::new(ip, 53); + match direct_dns_https_ech_config_list(query_name, dns_addr) { + Ok(Some(config_list)) => return Ok(Some(config_list)), + Ok(None) => { + last_error = Some(anyhow!( + "DNS HTTPS response for {query_name} did not include an ECH config" + )); + } + Err(err) => last_error = Some(err), + } + } + Err(last_error.unwrap_or_else(|| { + anyhow!("failed to resolve ECH config for Shadowsocks websocket host {query_name}") + })) +} + +#[cfg(target_vendor = "apple")] +async fn websocket_plugin_native_tls_connect( + stream: Box, + server_name: &str, + skip_cert_verify: bool, + certificate_fingerprint: Option<&CertificateFingerprint>, + client_identity: Option<&TlsClientIdentity>, +) -> Result> { + let mut builder = NativeTlsConnector::builder(); + if skip_cert_verify || certificate_fingerprint.is_some() { + builder.danger_accept_invalid_certs(true); + } + if let Some(identity) = client_identity { + builder.identity(load_native_tls_identity(identity)?); + } + builder.request_alpns(&["http/1.1"]); + let connector = TokioNativeTlsConnector::from( + builder + .build() + .context("failed to build native TLS connector for Shadowsocks websocket plugin")?, + ); + let tls = connector.connect(server_name, stream).await?; + if let Some(fingerprint) = certificate_fingerprint { + verify_native_tls_peer_certificate(&tls, fingerprint, "Shadowsocks websocket plugin")?; + } + Ok(Box::new(tls)) +} + +async fn shadow_tls_connect( + stream: Box, + config: &ShadowsocksShadowTlsTransport, +) -> Result> { + match config.version { + 1 => { + let tls_config = shadow_tls_client_config(config)?; + let connector = TlsConnector::from(Arc::new(tls_config)); + let name = ServerName::try_from(config.host.clone()) + .with_context(|| format!("invalid ShadowTLS host {}", config.host))?; + let tls = connector + .connect(name, stream) + .await + .with_context(|| format!("ShadowTLS v1 handshake failed for {}", config.host))?; + let (stream, _) = tls.into_inner(); + Ok(stream) + } + 2 => { + if config.client_fingerprint.is_some() { + #[cfg(feature = "boring-browser-fingerprints")] + return shadow_tls_v2_boring_connect(stream, config).await; + #[cfg(not(feature = "boring-browser-fingerprints"))] + bail!( + "unsupported ShadowTLS v2 client-fingerprint: browser TLS profiles require the boring-browser-fingerprints feature" + ); + } + let tls_config = shadow_tls_client_config(config)?; + let connector = TlsConnector::from(Arc::new(tls_config)); + let name = ServerName::try_from(config.host.clone()) + .with_context(|| format!("invalid ShadowTLS host {}", config.host))?; + let hashed = HmacReadStream::new(stream, &config.password); + let tls = connector + .connect(name, hashed) + .await + .with_context(|| format!("ShadowTLS v2 handshake failed for {}", config.host))?; + let (hashed, _) = tls.into_inner(); + let (stream, prefix) = hashed.finish(); + Ok(Box::new(ShadowTlsV2Stream::new(stream, prefix))) + } + 3 => { + if config.client_fingerprint.is_some() { + #[cfg(feature = "boring-browser-fingerprints")] + return shadow_tls_v3_boring_connect(stream, config).await; + #[cfg(not(feature = "boring-browser-fingerprints"))] + bail!( + "unsupported ShadowTLS v3 client-fingerprint: browser TLS profiles require the boring-browser-fingerprints feature" + ); + } + shadow_tls_v3_connect(stream, config).await + } + version => bail!("unsupported ShadowTLS version {version}"), + } +} + +#[cfg(feature = "boring-browser-fingerprints")] +async fn shadow_tls_v2_boring_connect( + stream: Box, + config: &ShadowsocksShadowTlsTransport, +) -> Result> { + let fingerprint = config + .client_fingerprint + .ok_or_else(|| anyhow!("missing ShadowTLS v2 client fingerprint"))?; + let profile = shadow_tls_v2_boring_profile(fingerprint)?; + let domain = RamaDomain::try_from(config.host.clone()) + .with_context(|| format!("invalid ShadowTLS host {}", config.host))?; + let mut builder = BoringTlsConnectorDataBuilder::try_from(profile.client_config.as_ref()) + .with_context(|| { + format!( + "failed to build browser TLS profile for client-fingerprint {}", + fingerprint.mihomo_name() + ) + })?; + builder = builder + .with_server_name(domain) + .with_alpn_protos(Bytes::from(alpn_wire_protocols(&config.alpn)?)); + if config.skip_cert_verify || config.certificate_fingerprint.is_some() { + builder = builder.with_server_verify_mode(RamaServerVerifyMode::Disable); + } + if let Some(identity) = &config.client_identity { + builder = builder.with_client_auth(load_boring_client_identity(identity)?); + } + let data = builder.build().with_context(|| { + format!( + "failed to configure ShadowTLS v2 browser TLS profile for {}", + config.host + ) + })?; + let hashed = DetachableStream::new(HmacReadStream::new(stream, &config.password)); + let mut tls = rama_tls_boring::core::tokio::connect(data.config, config.host.as_str(), hashed) + .await + .map_err(|err| { + anyhow!( + "ShadowTLS v2 browser-profile handshake failed for {} using client-fingerprint {}: {err}", + config.host, + fingerprint.mihomo_name() + ) + })?; + if let Some(certificate_fingerprint) = config.certificate_fingerprint.as_ref() { + verify_boring_tls_peer_certificate( + &tls, + certificate_fingerprint, + "ShadowTLS v2 browser-profile", + )?; + } + let hashed = tls + .get_mut() + .take() + .context("failed to recover ShadowTLS v2 browser-profile transport")?; + let (stream, prefix) = hashed.finish(); + Ok(Box::new(ShadowTlsV2Stream::new(stream, prefix))) +} + +#[cfg(feature = "boring-browser-fingerprints")] +fn shadow_tls_v2_boring_profile(fingerprint: MihomoClientFingerprint) -> Result { + let profile = mihomo_browser_tls_profile(fingerprint)?; + Ok(strip_shadow_tls_v2_unsupported_groups(profile)) +} + +#[cfg(feature = "boring-browser-fingerprints")] +fn mihomo_browser_tls_profile(fingerprint: MihomoClientFingerprint) -> Result { + Ok(mihomo_browser_user_agent_profile(fingerprint)?.tls.clone()) +} + +#[cfg(feature = "boring-browser-fingerprints")] +fn mihomo_browser_user_agent_profile( + fingerprint: MihomoClientFingerprint, +) -> Result<&'static rama_ua::profile::UserAgentProfile> { + let selected = match fingerprint { + MihomoClientFingerprint::Random => mihomo_initial_random_fingerprint(), + other => other, + }; + let (kind, platform, edge, required_ua_marker) = match selected { + MihomoClientFingerprint::Chrome => ( + UserAgentKind::Chromium, + Some(PlatformKind::MacOS), + false, + None, + ), + MihomoClientFingerprint::Firefox => ( + UserAgentKind::Firefox, + Some(PlatformKind::MacOS), + false, + None, + ), + MihomoClientFingerprint::Safari => ( + UserAgentKind::Safari, + Some(PlatformKind::MacOS), + false, + None, + ), + MihomoClientFingerprint::Safari16 => ( + UserAgentKind::Safari, + Some(PlatformKind::MacOS), + false, + Some("Version/16."), + ), + MihomoClientFingerprint::Ios => { + (UserAgentKind::Safari, Some(PlatformKind::IOS), false, None) + } + MihomoClientFingerprint::Android => ( + UserAgentKind::Chromium, + Some(PlatformKind::Android), + false, + None, + ), + MihomoClientFingerprint::Edge => ( + UserAgentKind::Chromium, + Some(PlatformKind::Windows), + true, + None, + ), + other => bail!( + "unsupported client-fingerprint {}: no matching browser TLS profile is available yet", + other.mihomo_name() + ), + }; + embedded_user_agent_profiles()? + .iter() + .filter(|profile| { + profile.ua_kind == kind + && profile.platform == platform + && required_ua_marker + .map(|marker| { + profile + .ua_str() + .map(|ua| ua.contains(marker)) + .unwrap_or(false) + }) + .unwrap_or(true) + && profile + .ua_str() + .map(|ua| ua.contains(" Edg/") == edge) + .unwrap_or(!edge) + }) + .max_by_key(|profile| profile.ua_version.unwrap_or(0)) + .ok_or_else(|| { + anyhow!( + "no embedded browser TLS profile matched client-fingerprint {}", + fingerprint.mihomo_name() + ) + }) +} + +#[cfg(feature = "boring-browser-fingerprints")] +fn strip_shadow_tls_v2_unsupported_groups(mut profile: TlsProfile) -> TlsProfile { + let mut config = profile.client_config.as_ref().clone(); + strip_x25519_mlkem768_from_client_config(&mut config); + profile.client_config = Arc::new(config); + profile +} + +#[cfg(feature = "boring-browser-fingerprints")] +fn strip_x25519_mlkem768_from_client_config(config: &mut rama_net::tls::client::ClientConfig) { + if let Some(extensions) = config.extensions.as_mut() { + for extension in extensions { + if let RamaClientHelloExtension::SupportedGroups(groups) = extension { + groups.retain(|group| *group != RamaSupportedGroup::X25519MLKEM768); + } + } + } +} + +#[cfg(feature = "boring-browser-fingerprints")] +fn mihomo_initial_random_fingerprint() -> MihomoClientFingerprint { + use std::sync::OnceLock; + + static FINGERPRINT: OnceLock = OnceLock::new(); + *FINGERPRINT.get_or_init(|| match rand::thread_rng().gen_range(0..12) { + 0..=5 => MihomoClientFingerprint::Chrome, + 6..=8 => MihomoClientFingerprint::Safari, + 9..=10 => MihomoClientFingerprint::Ios, + _ => MihomoClientFingerprint::Firefox, + }) +} + +#[cfg(feature = "boring-browser-fingerprints")] +fn embedded_user_agent_profiles() -> Result<&'static [rama_ua::profile::UserAgentProfile]> { + use std::sync::OnceLock; + + static PROFILES: OnceLock, String>> = + OnceLock::new(); + PROFILES + .get_or_init(|| { + try_load_embedded_profiles() + .map(|profiles| profiles.collect()) + .map_err(|err| err.to_string()) + }) + .as_deref() + .map_err(|err| anyhow!("failed to load embedded browser TLS profiles: {err}")) +} + +#[cfg(feature = "boring-browser-fingerprints")] +fn alpn_wire_protocols(protocols: &[String]) -> Result> { + let mut out = Vec::new(); + for protocol in protocols { + let protocol = protocol.as_bytes(); + let len = u8::try_from(protocol.len()).context("ALPN protocol identifier is too long")?; + out.push(len); + out.extend_from_slice(protocol); + } + Ok(out) +} + +#[cfg(feature = "boring-browser-fingerprints")] +fn verify_boring_tls_peer_certificate( + tls: &rama_tls_boring::client::BoringTlsStream, + fingerprint: &CertificateFingerprint, + protocol: &str, +) -> Result<()> { + let leaf = tls + .ssl() + .peer_certificate() + .ok_or_else(|| anyhow!("{protocol} peer did not present a certificate"))?; + if boring_certificate_fingerprint_matches(&leaf, fingerprint)? { + return Ok(()); + } + if let Some(chain) = tls.ssl().peer_cert_chain() { + for certificate in chain { + if boring_certificate_fingerprint_matches(certificate, fingerprint)? { + return Ok(()); + } + } + } + bail!("{protocol} certificate fingerprint mismatch"); +} + +#[cfg(feature = "boring-browser-fingerprints")] +fn boring_certificate_fingerprint_matches( + certificate: &BoringX509Ref, + fingerprint: &CertificateFingerprint, +) -> Result { + let digest = certificate + .digest(BoringMessageDigest::sha256()) + .context("failed to compute TLS certificate fingerprint")?; + Ok(&*digest == fingerprint.0) +} + +#[cfg(feature = "boring-browser-fingerprints")] +async fn shadow_tls_v3_boring_connect( + stream: Box, + config: &ShadowsocksShadowTlsTransport, +) -> Result> { + let fingerprint = config + .client_fingerprint + .ok_or_else(|| anyhow!("missing ShadowTLS v3 client fingerprint"))?; + let profile = mihomo_browser_tls_profile(fingerprint)?; + let domain = RamaDomain::try_from(config.host.clone()) + .with_context(|| format!("invalid ShadowTLS host {}", config.host))?; + let mut builder = BoringTlsConnectorDataBuilder::try_from(profile.client_config.as_ref()) + .with_context(|| { + format!( + "failed to build browser TLS profile for client-fingerprint {}", + fingerprint.mihomo_name() + ) + })?; + builder = builder + .with_server_name(domain) + .with_alpn_protos(Bytes::from(alpn_wire_protocols(&config.alpn)?)); + if config.skip_cert_verify || config.certificate_fingerprint.is_some() { + builder = builder.with_server_verify_mode(RamaServerVerifyMode::Disable); + } + if let Some(identity) = &config.client_identity { + builder = builder.with_client_auth(load_boring_client_identity(identity)?); + } + let data = builder.build().with_context(|| { + format!( + "failed to configure ShadowTLS v3 browser TLS profile for {}", + config.host + ) + })?; + let handshake_stream = ShadowTlsV3HandshakeStream::new(stream, &config.password); + let signed_stream = DetachableStream::new(ShadowTlsV3SignedClientHelloStream::new( + handshake_stream, + &config.password, + )); + let mut tls = + rama_tls_boring::core::tokio::connect(data.config, config.host.as_str(), signed_stream) + .await + .map_err(|err| { + anyhow!( + "ShadowTLS v3 browser-profile handshake failed for {} using client-fingerprint {}: {err}", + config.host, + fingerprint.mihomo_name() + ) + })?; + if let Some(certificate_fingerprint) = config.certificate_fingerprint.as_ref() { + verify_boring_tls_peer_certificate( + &tls, + certificate_fingerprint, + "ShadowTLS v3 browser-profile", + )?; + } + let signed_stream = tls + .get_mut() + .take() + .context("failed to recover ShadowTLS v3 browser-profile transport")?; + let (handshake_stream, client_hello_signed) = signed_stream.into_inner(); + if !client_hello_signed { + bail!("ShadowTLS v3 browser-profile handshake did not sign the ClientHello session ID"); + } + let (stream, state) = handshake_stream.into_inner(); + if !state.authorized { + bail!("ShadowTLS v3 server authorization failed"); + } + let server_random = state + .server_random + .ok_or_else(|| anyhow!("ShadowTLS v3 handshake did not expose server random"))?; + if !state.is_tls13 { + tracing::debug!( + "ShadowTLS v3 completed without TLS 1.3; continuing like Mihomo non-strict mode" + ); + } + Ok(Box::new(ShadowTlsV3Stream::new( + stream, + &config.password, + server_random, + state.read_hmac, + ))) +} + +async fn shadow_tls_v3_connect( + stream: Box, + config: &ShadowsocksShadowTlsTransport, +) -> Result> { + let tls_config = shadow_tls_v3_client_config(config)?; + let connector = ShadowTlsV3TlsConnector::from(Arc::new(tls_config)); + let name = shadow_rustls::ServerName::try_from(config.host.as_str()) + .map_err(|_| anyhow!("invalid ShadowTLS host {}", config.host))?; + let password = config.password.clone(); + let handshake_stream = ShadowTlsV3HandshakeStream::new(stream, &config.password); + let tls = connector + .connect_with_session_id_generator(name, handshake_stream, move |client_hello| { + shadow_tls_v3_generate_session_id(&password, client_hello) + }) + .await + .with_context(|| format!("ShadowTLS v3 handshake failed for {}", config.host))?; + let (handshake_stream, _) = tls.into_parts(); + let (stream, state) = handshake_stream.into_inner(); + if !state.authorized { + bail!("ShadowTLS v3 server authorization failed"); + } + let server_random = state + .server_random + .ok_or_else(|| anyhow!("ShadowTLS v3 handshake did not expose server random"))?; + if !state.is_tls13 { + tracing::debug!( + "ShadowTLS v3 completed without TLS 1.3; continuing like Mihomo non-strict mode" + ); + } + Ok(Box::new(ShadowTlsV3Stream::new( + stream, + &config.password, + server_random, + state.read_hmac, + ))) +} + +async fn restls_connect( + stream: Box, + config: &ShadowsocksRestlsTransport, +) -> Result> { + #[cfg(feature = "boring-browser-fingerprints")] + if config.version_hint == RestlsVersionHint::Tls13 { + return restls_boring_connect(stream, config).await; + } + + let tls_config = restls_client_config(config)?; + let connector = ShadowTlsV3TlsConnector::from(Arc::new(tls_config)); + let name = shadow_rustls::ServerName::try_from(config.host.as_str()) + .map_err(|_| anyhow!("invalid Restls host {}", config.host))?; + let secret = config.secret; + let tls12 = config.version_hint == RestlsVersionHint::Tls12; + let handshake_stream = RestlsHandshakeStream::new(stream, config.secret, tls12); + let session_id_error = Arc::new(StdMutex::new(None::)); + let session_id_error_for_generator = Arc::clone(&session_id_error); + let tls_result = if tls12 { + connector + .connect_with_session_id_generators( + name, + handshake_stream, + shadow_rustls::ClientSessionIdGenerators { + tls13: None, + tls12: Some(Arc::new(move |client_hello, materials| { + restls_tls12_session_id_for_client_hello( + &secret, + client_hello, + materials, + &session_id_error_for_generator, + ) + })), + }, + ) + .await + } else { + connector + .connect_with_session_id_generator(name, handshake_stream, move |client_hello| { + restls_tls13_session_id_for_client_hello( + &secret, + client_hello, + &session_id_error_for_generator, + ) + }) + .await + }; + let tls = match tls_result { + Ok(tls) => tls, + Err(err) => { + if let Some(session_id_err) = take_restls_session_id_error(&session_id_error) { + return Err(err).with_context(|| { + format!( + "{session_id_err}; Restls handshake failed for {}", + config.host + ) + }); + } + return Err(err) + .with_context(|| format!("Restls handshake failed for {}", config.host)); + } + }; + if let Some(session_id_err) = take_restls_session_id_error(&session_id_error) { + bail!("{session_id_err}"); + } + let (handshake_stream, session) = tls.into_parts(); + let tls12_gcm = restls_tls12_gcm_suite(session.negotiated_cipher_suite()); + let (stream, handshake) = handshake_stream.into_inner(); + if !handshake.server_auth_unmasked { + bail!("Restls server authentication record was not observed"); + } + let server_random = handshake + .server_random + .ok_or_else(|| anyhow!("Restls handshake did not expose server random"))?; + let client_finished = handshake + .client_finished_auth + .ok_or_else(|| anyhow!("Restls handshake did not capture ClientFinished record"))?; + let mut codec = RestlsApplicationCodec::new(config.secret, server_random, tls12_gcm); + codec.set_client_finished_auth(client_finished); + codec.set_tls12_gcm_to_client_disable_counter(handshake.tls12_gcm_server_disable_counter); + let state = RestlsStreamState::new(codec, config.script.clone()); + Ok(Box::new(RestlsStream::new(stream, state))) +} + +#[cfg(feature = "boring-browser-fingerprints")] +async fn restls_boring_connect( + stream: Box, + config: &ShadowsocksRestlsTransport, +) -> Result> { + let fingerprint = config.client_fingerprint.browser_profile(); + let profile = mihomo_browser_tls_profile(fingerprint)?; + let domain = RamaDomain::try_from(config.host.clone()) + .with_context(|| format!("invalid Restls host {}", config.host))?; + let builder = BoringTlsConnectorDataBuilder::try_from(profile.client_config.as_ref()) + .with_context(|| { + format!( + "failed to build Restls browser TLS profile for client-fingerprint {}", + fingerprint.mihomo_name() + ) + })? + .with_server_name(domain); + let data = builder.build().with_context(|| { + format!( + "failed to configure Restls browser TLS profile for {}", + config.host + ) + })?; + let handshake_stream = RestlsHandshakeStream::new(stream, config.secret, false); + let signed_stream = DetachableStream::new(RestlsSignedClientHelloStream::new( + handshake_stream, + config.secret, + )); + let mut tls = + rama_tls_boring::core::tokio::connect(data.config, config.host.as_str(), signed_stream) + .await + .map_err(|err| { + anyhow!( + "Restls browser-profile handshake failed for {} using client-fingerprint {}: {err}", + config.host, + fingerprint.mihomo_name() + ) + })?; + let signed_stream = tls + .get_mut() + .take() + .context("failed to recover Restls browser-profile transport")?; + let (handshake_stream, client_hello_signed) = signed_stream.into_inner(); + if !client_hello_signed { + bail!("Restls browser-profile handshake did not sign the ClientHello session ID"); + } + let (stream, handshake) = handshake_stream.into_inner(); + if !handshake.server_auth_unmasked { + bail!("Restls server authentication record was not observed"); + } + let server_random = handshake + .server_random + .ok_or_else(|| anyhow!("Restls handshake did not expose server random"))?; + let client_finished = handshake + .client_finished_auth + .ok_or_else(|| anyhow!("Restls handshake did not capture ClientFinished record"))?; + let mut codec = RestlsApplicationCodec::new(config.secret, server_random, false); + codec.set_client_finished_auth(client_finished); + codec.set_tls12_gcm_to_client_disable_counter(handshake.tls12_gcm_server_disable_counter); + let state = RestlsStreamState::new(codec, config.script.clone()); + Ok(Box::new(RestlsStream::new(stream, state))) +} + +fn restls_client_config( + config: &ShadowsocksRestlsTransport, +) -> Result { + let mut root_store = shadow_rustls::RootCertStore::empty(); + root_store.add_server_trust_anchors(webpki_roots_legacy::TLS_SERVER_ROOTS.0.iter().map( + |anchor| { + shadow_rustls::OwnedTrustAnchor::from_subject_spki_name_constraints( + anchor.subject, + anchor.spki, + anchor.name_constraints, + ) + }, + )); + let versions = match config.version_hint { + RestlsVersionHint::Tls12 => &[&shadow_rustls::version::TLS12][..], + RestlsVersionHint::Tls13 => &[&shadow_rustls::version::TLS13][..], + }; + let mut tls_config = shadow_rustls::ClientConfig::builder() + .with_safe_default_cipher_suites() + .with_safe_default_kx_groups() + .with_protocol_versions(versions) + .context("failed to configure Restls TLS protocol versions")? + .with_root_certificates(root_store) + .with_no_client_auth(); + tls_config.enable_tickets = !config.session_tickets_disabled; + Ok(tls_config) +} + +fn restls_tls12_gcm_suite(suite: Option) -> bool { + matches!( + suite.map(|suite| suite.suite()), + Some( + shadow_rustls::CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 + | shadow_rustls::CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 + | shadow_rustls::CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 + | shadow_rustls::CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 + ) + ) +} + +fn shadow_tls_client_config(config: &ShadowsocksShadowTlsTransport) -> Result { + install_rustls_crypto_provider(); + let mut root_store = RootCertStore::empty(); + root_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned()); + let versions = if config.version == 1 { + &[&version::TLS12][..] + } else { + rustls::DEFAULT_VERSIONS + }; + let builder = + ClientConfig::builder_with_protocol_versions(versions).with_root_certificates(root_store); + let mut tls_config = if let Some(identity) = config.client_identity.as_ref() { + let (cert_chain, private_key) = load_rustls_client_identity(identity)?; + builder + .with_client_auth_cert(cert_chain, private_key) + .context("failed to configure ShadowTLS client certificate")? + } else { + builder.with_no_client_auth() + }; + tls_config.alpn_protocols = config + .alpn + .iter() + .map(|value| value.as_bytes().to_vec()) + .collect(); + if let Some(fingerprint) = config.certificate_fingerprint.as_ref() { + tls_config + .dangerous() + .set_certificate_verifier(Arc::new(PinnedCertificateVerifier { + fingerprint: fingerprint.clone(), + })); + } else if config.skip_cert_verify { + tls_config + .dangerous() + .set_certificate_verifier(Arc::new(NoCertificateVerifier)); + } + Ok(tls_config) +} + +fn shadow_tls_v3_client_config( + config: &ShadowsocksShadowTlsTransport, +) -> Result { + let mut root_store = shadow_rustls::RootCertStore::empty(); + root_store.add_server_trust_anchors(webpki_roots_legacy::TLS_SERVER_ROOTS.0.iter().map( + |anchor| { + shadow_rustls::OwnedTrustAnchor::from_subject_spki_name_constraints( + anchor.subject, + anchor.spki, + anchor.name_constraints, + ) + }, + )); + let builder = shadow_rustls::ClientConfig::builder() + .with_safe_defaults() + .with_root_certificates(root_store); + let mut tls_config = if let Some(identity) = config.client_identity.as_ref() { + let (cert_chain, private_key) = load_shadow_rustls_client_identity(identity)?; + builder + .with_single_cert(cert_chain, private_key) + .context("failed to configure ShadowTLS v3 client certificate")? + } else { + builder.with_no_client_auth() + }; + tls_config.alpn_protocols = config + .alpn + .iter() + .map(|value| value.as_bytes().to_vec()) + .collect(); + if let Some(fingerprint) = config.certificate_fingerprint.as_ref() { + tls_config.dangerous().set_certificate_verifier(Arc::new( + ShadowPinnedCertificateVerifier { + fingerprint: fingerprint.clone(), + }, + )); + } else if config.skip_cert_verify { + tls_config + .dangerous() + .set_certificate_verifier(Arc::new(ShadowNoCertificateVerifier)); + } + Ok(tls_config) +} + +async fn read_http_upgrade_response(stream: &mut Box) -> Result { + let mut response = Vec::with_capacity(1024); + let mut byte = [0u8; 1]; + while response.len() < 16 * 1024 { + stream.read_exact(&mut byte).await?; + response.push(byte[0]); + if response.ends_with(b"\r\n\r\n") { + return String::from_utf8(response) + .context("websocket plugin response headers were not valid UTF-8"); + } + } + bail!("websocket plugin response headers exceeded Burrow limit") +} + +fn validate_websocket_upgrade_response( + response: &str, + v2ray_http_upgrade: bool, + _websocket_key: Option<&str>, +) -> Result<()> { + let mut lines = response.split("\r\n"); + let status = lines.next().unwrap_or_default(); + let switching = status.contains(" 101 "); + let mut connection_upgrade = false; + let mut upgrade_websocket = false; + for line in lines { + let Some((key, value)) = line.split_once(':') else { + continue; + }; + let value = value.trim(); + if key.eq_ignore_ascii_case("connection") + && value + .split(',') + .any(|token| token.trim().eq_ignore_ascii_case("upgrade")) + { + connection_upgrade = true; + } + if key.eq_ignore_ascii_case("upgrade") && value.eq_ignore_ascii_case("websocket") { + upgrade_websocket = true; + } + } + if !switching || !connection_upgrade || !upgrade_websocket { + bail!("unexpected websocket plugin response status {status}"); + } + if !v2ray_http_upgrade { + // Mihomo validates Sec-WebSocket-Accept only in debug builds. Burrow keeps + // the same operational behavior and requires the protocol switch headers. + } + Ok(()) +} + +fn websocket_binary_frame(payload: &[u8]) -> io::Result> { + let mut frame = Vec::with_capacity(payload.len() + 14); + frame.push(0x82); + let len = payload.len(); + if len < 126 { + frame.push(0x80 | len as u8); + } else if u16::try_from(len).is_ok() { + frame.push(0x80 | 126); + frame.extend_from_slice(&(len as u16).to_be_bytes()); + } else { + frame.push(0x80 | 127); + frame.extend_from_slice(&(len as u64).to_be_bytes()); + } + let mut mask = [0u8; 4]; + OsRng.fill_bytes(&mut mask); + frame.extend_from_slice(&mask); + let mut masked = payload.to_vec(); + apply_websocket_mask(&mut masked, mask); + frame.extend_from_slice(&masked); + Ok(frame) +} + +fn apply_websocket_mask(payload: &mut [u8], mask: [u8; 4]) { + for (index, byte) in payload.iter_mut().enumerate() { + *byte ^= mask[index % 4]; + } +} + +fn websocket_path(path: &str) -> String { + let path = path.trim(); + if path.is_empty() { + return "/".to_owned(); + } + if path.starts_with('/') { + path.to_owned() + } else { + format!("/{path}") + } +} + +fn websocket_path_and_early_data(path: &str) -> (String, Option) { + let path = websocket_path(path); + let Some((base, query)) = path.split_once('?') else { + return (path, None); + }; + let mut max_early_data = None; + let mut kept = Vec::new(); + for pair in query.split('&') { + if pair.is_empty() { + continue; + } + let (key, value) = pair.split_once('=').unwrap_or((pair, "")); + if key == "ed" { + if max_early_data.is_none() { + max_early_data = value.parse::().ok().filter(|value| *value > 0); + } + continue; + } + kept.push(pair); + } + let path = if kept.is_empty() { + base.to_owned() + } else { + format!("{base}?{}", kept.join("&")) + }; + (path, max_early_data) +} + +fn websocket_plugin_request( + config: &ShadowsocksWebSocketTransport, + _port: u16, + path: &str, + early_data: Option<&[u8]>, +) -> Result<(Vec, Option)> { + let host = config.websocket_host_header(); + let mut request = format!( + "GET {path} HTTP/1.1\r\nHost: {host}\r\nConnection: Upgrade\r\nUpgrade: websocket\r\n" + ); + for (key, value) in config.headers.iter() { + if key.eq_ignore_ascii_case("host") + || key.eq_ignore_ascii_case("connection") + || key.eq_ignore_ascii_case("upgrade") + || (early_data.is_some() && key.eq_ignore_ascii_case("sec-websocket-protocol")) + || (!config.v2ray_http_upgrade + && (key.eq_ignore_ascii_case("sec-websocket-key") + || key.eq_ignore_ascii_case("sec-websocket-version"))) + { + continue; + } + request.push_str(key); + request.push_str(": "); + request.push_str(value); + request.push_str("\r\n"); + } + + let websocket_key = if config.v2ray_http_upgrade { + None + } else { + let mut key = [0u8; 16]; + OsRng.fill_bytes(&mut key); + let key = BASE64_STANDARD.encode(key); + request.push_str("Sec-WebSocket-Version: 13\r\n"); + request.push_str("Sec-WebSocket-Key: "); + request.push_str(&key); + request.push_str("\r\n"); + Some(key) + }; + if let Some(early_data) = early_data { + request.push_str("Sec-WebSocket-Protocol: "); + request.push_str(&BASE64_URL_SAFE_NO_PAD.encode(early_data)); + request.push_str("\r\n"); + } + request.push_str("\r\n"); + Ok((request.into_bytes(), websocket_key)) +} + +impl ShadowsocksWebSocketTransport { + fn websocket_host_header(&self) -> &str { + self.headers + .iter() + .find(|(key, _)| key.eq_ignore_ascii_case("host")) + .map(|(_, value)| value.as_str()) + .unwrap_or(&self.host) + } +} + +fn v2ray_mux_open_frame() -> Vec { + let mut frame = Vec::new(); + frame.extend_from_slice(&[0u8, 0u8]); + frame.extend_from_slice(&[0u8, 0u8]); + frame.push(0x01); + frame.push(0x00); + frame.push(0x01); + frame.extend_from_slice(&0u16.to_be_bytes()); + frame.push(0x02); + frame.extend_from_slice(b"127.0.0.1"); + let len = u16::try_from(frame.len() - 2).expect("v2ray mux open frame fits u16"); + frame[..2].copy_from_slice(&len.to_be_bytes()); + frame +} + +fn v2ray_mux_data_frame(payload: &[u8]) -> io::Result> { + if payload.len() > u16::MAX as usize { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "v2ray-plugin mux payload is too large", + )); + } + let mut frame = Vec::with_capacity(payload.len() + 8); + frame.extend_from_slice(&4u16.to_be_bytes()); + frame.extend_from_slice(&[0u8, 0u8]); + frame.push(0x02); + frame.push(0x01); + frame.extend_from_slice(&(payload.len() as u16).to_be_bytes()); + frame.extend_from_slice(payload); + Ok(frame) +} + +fn shadowsocks_plugin_transport( + plugin: Option<&ShadowsocksPlugin>, + client_fingerprint: Option<&str>, +) -> Result> { + let Some(plugin) = plugin else { + return Ok(None); + }; + let name = plugin.name.trim(); + if name == "obfs" { + let mode = plugin + .opts + .get("mode") + .or_else(|| plugin.opts.get("obfs")) + .map(|value| value.trim().to_ascii_lowercase()) + .ok_or_else(|| anyhow!("Shadowsocks obfs plugin requires mode"))?; + let mode = match mode.as_str() { + "http" => SimpleObfsMode::Http, + "tls" => SimpleObfsMode::Tls, + other => bail!("unsupported Shadowsocks obfs mode {other}"), + }; + let host = plugin + .opts + .get("host") + .or_else(|| plugin.opts.get("obfs-host")) + .map(|value| value.trim()) + .filter(|value| !value.is_empty()) + .unwrap_or("bing.com") + .to_owned(); + return Ok(Some(ShadowsocksPluginTransport::SimpleObfs { mode, host })); + } + if name == "v2ray-plugin" || name == "gost-plugin" { + let mode = plugin + .opts + .get("mode") + .or_else(|| plugin.opts.get("obfs")) + .map(|value| value.trim().to_ascii_lowercase()) + .ok_or_else(|| anyhow!("Shadowsocks {name} plugin requires websocket mode"))?; + if mode != "websocket" { + bail!("unsupported Shadowsocks {name} mode {mode}"); + } + let mux = plugin_bool_opt(&plugin.opts, "mux").unwrap_or(true); + let mux = match (name, mux) { + ("v2ray-plugin", true) => ShadowsocksWebSocketMux::V2ray, + ("gost-plugin", true) => ShadowsocksWebSocketMux::Gost, + (_, false) => ShadowsocksWebSocketMux::None, + _ => ShadowsocksWebSocketMux::None, + }; + let host = plugin + .opts + .get("host") + .or_else(|| plugin.opts.get("obfs-host")) + .map(|value| value.trim()) + .filter(|value| !value.is_empty()) + .unwrap_or("bing.com") + .to_owned(); + let path = plugin + .opts + .get("path") + .map(|value| value.trim()) + .filter(|value| !value.is_empty()) + .unwrap_or("/") + .to_owned(); + let transport = ShadowsocksWebSocketTransport { + kind: if name == "v2ray-plugin" { + ShadowsocksWebSocketKind::V2ray + } else { + ShadowsocksWebSocketKind::Gost + }, + host, + path, + headers: plugin_headers(&plugin.opts), + tls: plugin_bool_opt(&plugin.opts, "tls").unwrap_or(false), + ech: plugin_ech_config(&plugin.opts)?, + skip_cert_verify: plugin_bool_opt(&plugin.opts, "skip-cert-verify").unwrap_or(false), + certificate_fingerprint: plugin + .opts + .get("fingerprint") + .map(|value| parse_certificate_fingerprint(value)) + .transpose()?, + client_identity: plugin_tls_client_identity(&plugin.opts)?, + mux, + v2ray_http_upgrade: plugin_bool_opt(&plugin.opts, "v2ray-http-upgrade") + .unwrap_or(false), + v2ray_http_upgrade_fast_open: name == "v2ray-plugin" + && plugin_bool_opt(&plugin.opts, "v2ray-http-upgrade-fast-open").unwrap_or(false), + }; + return Ok(Some(ShadowsocksPluginTransport::WebSocket(transport))); + } + if name == "shadow-tls" { + let host = plugin + .opts + .get("host") + .map(|value| value.trim()) + .filter(|value| !value.is_empty()) + .ok_or_else(|| anyhow!("Shadowsocks shadow-tls plugin requires host"))? + .to_owned(); + let version = plugin_u8_opt(&plugin.opts, "version").unwrap_or(2); + match version { + 1 | 2 | 3 => {} + other => bail!("unsupported Shadowsocks shadow-tls version {other}"), + } + let parsed_client_fingerprint = + client_fingerprint.and_then(MihomoClientFingerprint::from_mihomo_name); + if let Some(client_fingerprint) = parsed_client_fingerprint { + if version == 3 { + #[cfg(not(feature = "boring-browser-fingerprints"))] + bail!( + "unsupported Shadowsocks {name} client-fingerprint {}: uTLS-style ClientHello impersonation for ShadowTLS v3 requires the boring-browser-fingerprints feature", + client_fingerprint.mihomo_name() + ); + #[cfg(feature = "boring-browser-fingerprints")] + if !client_fingerprint.shadow_tls_v3_boring_supported() { + bail!( + "unsupported Shadowsocks {name} client-fingerprint {}: no matching browser TLS profile with ShadowTLS session ID signing is available yet", + client_fingerprint.mihomo_name() + ); + } + } + if version == 2 && !client_fingerprint.shadow_tls_v2_boring_supported() { + bail!( + "unsupported Shadowsocks {name} client-fingerprint {}: no matching browser TLS profile is available yet", + client_fingerprint.mihomo_name() + ); + } + } + let password = plugin + .opts + .get("password") + .map(|value| value.trim()) + .filter(|value| !value.is_empty()) + .unwrap_or(""); + if version != 1 && password.is_empty() { + bail!("Shadowsocks shadow-tls plugin requires password for version {version}"); + } + let password = password.to_owned(); + let alpn = plugin_list_opt(&plugin.opts, "alpn") + .unwrap_or_else(|| vec!["h2".to_owned(), "http/1.1".to_owned()]); + return Ok(Some(ShadowsocksPluginTransport::ShadowTls( + ShadowsocksShadowTlsTransport { + host, + password, + version, + alpn, + skip_cert_verify: plugin_bool_opt(&plugin.opts, "skip-cert-verify") + .unwrap_or(false), + certificate_fingerprint: plugin + .opts + .get("fingerprint") + .map(|value| parse_certificate_fingerprint(value)) + .transpose()?, + client_identity: plugin_tls_client_identity(&plugin.opts)?, + client_fingerprint: if version == 2 || version == 3 { + parsed_client_fingerprint + } else { + None + }, + }, + ))); + } + if name == "restls" { + if let Some(client_fingerprint) = unsupported_client_fingerprint_value(client_fingerprint) { + #[cfg(not(feature = "boring-browser-fingerprints"))] + bail!( + "unsupported Shadowsocks {name} client-fingerprint {client_fingerprint}: uTLS ClientHello impersonation is not implemented" + ); + #[cfg(feature = "boring-browser-fingerprints")] + { + let _ = client_fingerprint; + } + } + let transport = restls_transport(plugin, client_fingerprint)?; + return Ok(Some(ShadowsocksPluginTransport::Restls(transport))); + } + if name == "kcptun" { + let mut transport = ShadowsocksKcptunTransport::empty(); + if let Some(value) = plugin.opts.get("key") { + transport.key = value.trim().to_owned(); + } + if let Some(value) = plugin.opts.get("crypt") { + transport.crypt = value.trim().to_owned(); + } + if let Some(value) = plugin.opts.get("mode") { + transport.mode = value.trim().to_owned(); + } + if let Some(value) = plugin_usize_opt(&plugin.opts, "conn") { + transport.conn = value; + } + if let Some(value) = plugin_u64_opt(&plugin.opts, "autoexpire") { + transport.auto_expire = value; + } + if let Some(value) = plugin_u64_opt(&plugin.opts, "scavengettl") { + transport.scavenge_ttl = value; + } + if let Some(value) = plugin_usize_opt(&plugin.opts, "mtu") { + transport.mtu = value; + } + if let Some(value) = plugin_u32_opt(&plugin.opts, "ratelimit") { + transport.rate_limit = value; + } + if let Some(value) = plugin_u16_opt(&plugin.opts, "sndwnd") { + transport.sndwnd = value; + } + if let Some(value) = plugin_u16_opt(&plugin.opts, "rcvwnd") { + transport.rcvwnd = value; + } + if let Some(value) = plugin_usize_opt(&plugin.opts, "datashard") { + transport.data_shard = value; + } + if let Some(value) = plugin_usize_opt(&plugin.opts, "parityshard") { + transport.parity_shard = value; + } + if let Some(value) = plugin_u32_opt(&plugin.opts, "dscp") { + transport.dscp = value; + } + if let Some(value) = plugin_bool_opt(&plugin.opts, "nocomp") { + transport.no_comp = value; + } + if let Some(value) = plugin_bool_opt(&plugin.opts, "acknodelay") { + transport.ack_nodelay = value; + } + if let Some(value) = plugin_i32_opt(&plugin.opts, "nodelay") { + transport.no_delay = value; + } + if let Some(value) = plugin_i32_opt(&plugin.opts, "interval") { + transport.interval = value; + } + if let Some(value) = plugin_i32_opt(&plugin.opts, "resend") { + transport.resend = value; + } + if let Some(value) = plugin_bool_opt(&plugin.opts, "nc") { + transport.no_congestion = value; + } + if let Some(value) = plugin_usize_opt(&plugin.opts, "sockbuf") { + transport.sockbuf = value; + } + if let Some(value) = plugin_u8_opt(&plugin.opts, "smuxver") { + transport.smux_ver = value; + } + if let Some(value) = plugin_usize_opt(&plugin.opts, "smuxbuf") { + transport.smux_buf = value; + } + if let Some(value) = plugin_usize_opt(&plugin.opts, "framesize") { + transport.frame_size = value; + } + if let Some(value) = plugin_usize_opt(&plugin.opts, "streambuf") { + transport.stream_buf = value; + } + if let Some(value) = plugin_u64_opt(&plugin.opts, "keepalive") { + transport.keep_alive = value; + } + transport.fill_defaults(); + return Ok(Some(ShadowsocksPluginTransport::Kcptun(transport))); + } + Ok(None) +} + +fn restls_transport( + plugin: &ShadowsocksPlugin, + client_fingerprint: Option<&str>, +) -> Result { + let host = plugin + .opts + .get("host") + .map(|value| value.trim()) + .filter(|value| !value.is_empty()) + .ok_or_else(|| anyhow!("Shadowsocks restls plugin requires host"))? + .to_owned(); + let password = plugin + .opts + .get("password") + .map(|value| value.trim()) + .unwrap_or("") + .to_owned(); + let version_hint = plugin + .opts + .get("version-hint") + .map(|value| restls_version_hint(value)) + .transpose()? + .ok_or_else(|| anyhow!("Shadowsocks restls plugin requires version-hint tls12 or tls13"))?; + let script = plugin + .opts + .get("restls-script") + .map(|value| value.as_str()) + .unwrap_or(RESTLS_DEFAULT_SCRIPT); + let script = parse_restls_script(script)?; + let client_fingerprint = restls_client_fingerprint(client_fingerprint); + let secret = blake3::derive_key("restls-traffic-key", password.as_bytes()); + let session_tickets_disabled = version_hint == RestlsVersionHint::Tls13; + Ok(ShadowsocksRestlsTransport { + host, + password, + version_hint, + script, + client_fingerprint, + session_tickets_disabled, + secret, + }) +} + +fn restls_version_hint(value: &str) -> Result { + match value.trim().to_ascii_lowercase().as_str() { + "tls12" => Ok(RestlsVersionHint::Tls12), + "tls13" => Ok(RestlsVersionHint::Tls13), + _ => bail!("invalid Shadowsocks restls version-hint: expected tls12 or tls13"), + } +} + +fn restls_client_fingerprint(value: Option<&str>) -> RestlsClientFingerprint { + match value.map(str::trim).unwrap_or("") { + "firefox" => RestlsClientFingerprint::Firefox, + "safari" => RestlsClientFingerprint::Safari, + "ios" => RestlsClientFingerprint::Ios, + _ => RestlsClientFingerprint::Chrome, + } +} + +fn parse_restls_script(script: &str) -> Result> { + let mut lines = Vec::new(); + for raw in script.replace(' ', "").split(',') { + if raw.is_empty() { + continue; + } + let mut input = raw; + let base = restls_take_integer(&mut input) + .with_context(|| format!("invalid Shadowsocks restls script line {raw}"))?; + let mut target_len = RestlsTargetLength { base, random_range: 0 }; + if input.starts_with('~') || input.starts_with('?') { + let randomize_once = input.starts_with('?'); + input = &input[1..]; + let random_range = restls_take_integer(&mut input) + .with_context(|| format!("invalid Shadowsocks restls script line {raw}"))?; + if u32::from(base) + u32::from(random_range) > 32768 { + bail!("Shadowsocks restls script target length exceeds 32768"); + } + if randomize_once { + let offset = if random_range == 0 { + 0 + } else { + rand::thread_rng().gen_range(0..usize::from(random_range)) as u16 + }; + target_len.base = base + offset; + } else { + target_len.random_range = random_range; + } + } + let command = if input.is_empty() { + RestlsCommand::Noop + } else if let Some(rest) = input.strip_prefix('<') { + input = rest; + RestlsCommand::Response( + restls_take_integer(&mut input) + .with_context(|| format!("invalid Shadowsocks restls script line {raw}"))?, + ) + } else { + bail!("invalid Shadowsocks restls script line {raw}"); + }; + if !input.is_empty() { + bail!("invalid Shadowsocks restls script line {raw}"); + } + lines.push(RestlsScriptLine { target_len, command }); + } + Ok(lines) +} + +fn restls_take_integer(input: &mut &str) -> Result { + let digit_len = input + .bytes() + .take_while(|byte| byte.is_ascii_digit()) + .count(); + if digit_len == 0 { + bail!("missing integer"); + } + let (digits, rest) = input.split_at(digit_len); + let value = digits.parse::().context("invalid integer")?; + if value > 32768 { + bail!("integer exceeds 32768"); + } + *input = rest; + Ok(value) +} + +#[allow(dead_code)] +impl RestlsCommand { + fn to_bytes(self) -> [u8; RESTLS_CMD_LEN] { + match self { + RestlsCommand::Noop => [0, 0], + RestlsCommand::Response(value) => [1, value as u8], + } + } + + fn from_bytes(bytes: [u8; RESTLS_CMD_LEN]) -> Result { + match bytes[0] { + 0 => Ok(RestlsCommand::Noop), + 1 => Ok(RestlsCommand::Response(u16::from(bytes[1]))), + _ => bail!("unsupported Shadowsocks restls command"), + } + } +} + +#[allow(dead_code)] +impl RestlsTargetLength { + fn len(&self) -> usize { + let offset = if self.random_range == 0 { + 0 + } else { + rand::thread_rng().gen_range(0..usize::from(self.random_range)) + }; + usize::from(self.base) + offset + } +} + +#[allow(dead_code)] +fn restls_tls13_session_id( + secret: &[u8; 32], + key_shares: &[(u16, Vec)], + psk_labels: &[Vec], + mut session_id: [u8; 32], +) -> [u8; 32] { + let mut hmac = blake3::Hasher::new_keyed(secret); + for (group, data) in key_shares { + hmac.update(&group.to_be_bytes()); + hmac.update(data); + } + for label in psk_labels { + hmac.update(label); + } + let digest = hmac.finalize(); + session_id[..RESTLS_HANDSHAKE_MAC_LEN] + .copy_from_slice(&digest.as_bytes()[..RESTLS_HANDSHAKE_MAC_LEN]); + session_id +} + +#[allow(dead_code)] +fn restls_tls13_session_id_from_client_hello( + secret: &[u8; 32], + client_hello: &[u8], + seed_session_id: [u8; 32], +) -> Result<[u8; 32]> { + let material = restls_tls13_client_hello_auth_material(client_hello)?; + Ok(restls_tls13_session_id( + secret, + &material.key_shares, + &material.psk_labels, + seed_session_id, + )) +} + +fn restls_tls13_session_id_for_client_hello( + secret: &[u8; 32], + client_hello: &[u8], + error: &StdMutex>, +) -> [u8; 32] { + let mut session_id_seed = [0u8; 32]; + OsRng.fill_bytes(&mut session_id_seed[RESTLS_HANDSHAKE_MAC_LEN..]); + match restls_tls13_session_id_from_client_hello(secret, client_hello, session_id_seed) { + Ok(session_id) => session_id, + Err(err) => { + let message = + format!("failed to derive Restls TLS 1.3 session id from ClientHello: {err:#}"); + tracing::warn!(error = %err, "failed to derive Restls TLS 1.3 session id from ClientHello"); + if let Ok(mut slot) = error.lock() { + *slot = Some(message); + } + session_id_seed + } + } +} + +fn restls_tls12_session_id_for_client_hello( + secret: &[u8; 32], + _client_hello: &[u8], + materials: &[Vec], + error: &StdMutex>, +) -> [u8; 32] { + match restls_tls12_session_id(secret, materials) { + Ok(session_id) => session_id, + Err(err) => { + let message = + format!("failed to derive Restls TLS 1.2 session id from key materials: {err:#}"); + tracing::warn!(error = %err, "failed to derive Restls TLS 1.2 session id"); + if let Ok(mut slot) = error.lock() { + *slot = Some(message); + } + let mut fallback = [0u8; 32]; + OsRng.fill_bytes(&mut fallback); + fallback + } + } +} + +fn take_restls_session_id_error(error: &StdMutex>) -> Option { + error.lock().ok().and_then(|mut slot| slot.take()) +} + +#[allow(dead_code)] +fn restls_tls13_sign_client_hello_record( + secret: &[u8; 32], + bytes: &[u8], +) -> io::Result>> { + if bytes.len() < TLS_RECORD_HEADER_LEN { + return Ok(None); + } + if bytes[0] != 0x16 { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "Restls first TLS record is not a handshake record", + )); + } + let payload_len = usize::from(u16::from_be_bytes([bytes[3], bytes[4]])); + let record_len = TLS_RECORD_HEADER_LEN + payload_len; + if bytes.len() < record_len { + return Ok(None); + } + let payload = &bytes[TLS_RECORD_HEADER_LEN..record_len]; + if payload.first().copied() != Some(0x01) { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "Restls first handshake message is not ClientHello", + )); + } + let session_id_length_index = SHADOW_TLS_V3_SESSION_ID_START - 1; + if payload.get(session_id_length_index).copied() != Some(32) { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "Restls browser ClientHello did not reserve a 32-byte session ID", + )); + } + if payload.len() < SHADOW_TLS_V3_SESSION_ID_START + 32 { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "Restls browser ClientHello session ID is truncated", + )); + } + let mut seed_session_id = [0u8; 32]; + seed_session_id.copy_from_slice( + &payload[SHADOW_TLS_V3_SESSION_ID_START..SHADOW_TLS_V3_SESSION_ID_START + 32], + ); + let session_id = restls_tls13_session_id_from_client_hello(secret, payload, seed_session_id) + .map_err(|err| { + io::Error::new( + io::ErrorKind::InvalidData, + format!("failed to derive Restls TLS 1.3 session ID from ClientHello: {err:#}"), + ) + })?; + let mut signed = bytes.to_vec(); + let session_id_start = TLS_RECORD_HEADER_LEN + SHADOW_TLS_V3_SESSION_ID_START; + signed[session_id_start..session_id_start + 32].copy_from_slice(&session_id); + Ok(Some(signed)) +} + +#[allow(dead_code)] +fn restls_tls13_client_hello_auth_material( + client_hello: &[u8], +) -> Result { + let mut input = client_hello; + let handshake_type = restls_read_u8(&mut input) + .context("Shadowsocks restls ClientHello is missing handshake type")?; + if handshake_type != 0x01 { + bail!("Shadowsocks restls auth material requires a ClientHello handshake"); + } + let handshake_len = usize::try_from(restls_read_u24(&mut input)?).expect("u24 fits into usize"); + let handshake = restls_read_exact(&mut input, handshake_len) + .context("Shadowsocks restls ClientHello body is truncated")?; + if !input.is_empty() { + bail!("Shadowsocks restls ClientHello has trailing bytes"); + } + + let mut body = handshake; + restls_read_exact(&mut body, 2).context("Shadowsocks restls ClientHello misses version")?; + restls_read_exact(&mut body, 32).context("Shadowsocks restls ClientHello misses random")?; + let session_id_len = usize::from( + restls_read_u8(&mut body).context("Shadowsocks restls ClientHello misses session ID")?, + ); + restls_read_exact(&mut body, session_id_len) + .context("Shadowsocks restls ClientHello session ID is truncated")?; + let cipher_suites_len = usize::from( + restls_read_u16(&mut body) + .context("Shadowsocks restls ClientHello misses cipher suites")?, + ); + if cipher_suites_len % 2 != 0 { + bail!("Shadowsocks restls ClientHello cipher suites length is invalid"); + } + restls_read_exact(&mut body, cipher_suites_len) + .context("Shadowsocks restls ClientHello cipher suites are truncated")?; + let compression_len = usize::from( + restls_read_u8(&mut body).context("Shadowsocks restls ClientHello misses compression")?, + ); + restls_read_exact(&mut body, compression_len) + .context("Shadowsocks restls ClientHello compression list is truncated")?; + let extensions_len = usize::from( + restls_read_u16(&mut body).context("Shadowsocks restls ClientHello misses extensions")?, + ); + let mut extensions = restls_read_exact(&mut body, extensions_len) + .context("Shadowsocks restls ClientHello extensions are truncated")?; + if !body.is_empty() { + bail!("Shadowsocks restls ClientHello body has trailing bytes"); + } + + let mut key_shares = Vec::new(); + let mut psk_labels = Vec::new(); + while !extensions.is_empty() { + let extension_type = restls_read_u16(&mut extensions) + .context("Shadowsocks restls ClientHello extension type is truncated")?; + let extension_len = usize::from(restls_read_u16(&mut extensions).with_context(|| { + format!("Shadowsocks restls ClientHello extension {extension_type} misses length") + })?); + let mut extension = + restls_read_exact(&mut extensions, extension_len).with_context(|| { + format!("Shadowsocks restls ClientHello extension {extension_type} is truncated") + })?; + match extension_type { + 0x0033 => key_shares = restls_tls13_key_share_material(&mut extension)?, + 0x0029 => psk_labels = restls_tls13_psk_labels(&mut extension)?, + _ => {} + } + if !extension.is_empty() { + bail!("Shadowsocks restls ClientHello extension {extension_type} has trailing bytes"); + } + } + + if key_shares.is_empty() { + bail!("Shadowsocks restls TLS 1.3 ClientHello has no key share material"); + } + Ok(RestlsTls13ClientHelloAuthMaterial { key_shares, psk_labels }) +} + +#[allow(dead_code)] +fn restls_tls13_key_share_material(extension: &mut &[u8]) -> Result)>> { + let list_len = usize::from( + restls_read_u16(extension) + .context("Shadowsocks restls key_share extension misses length")?, + ); + let mut shares = restls_read_exact(extension, list_len) + .context("Shadowsocks restls key_share extension is truncated")?; + let mut material = Vec::new(); + while !shares.is_empty() { + let group = restls_read_u16(&mut shares) + .context("Shadowsocks restls key_share entry misses group")?; + let key_len = usize::from( + restls_read_u16(&mut shares) + .context("Shadowsocks restls key_share entry misses key length")?, + ); + let key = restls_read_exact(&mut shares, key_len) + .context("Shadowsocks restls key_share entry is truncated")?; + material.push((group, key.to_vec())); + } + Ok(material) +} + +#[allow(dead_code)] +fn restls_tls13_psk_labels(extension: &mut &[u8]) -> Result>> { + let identities_len = usize::from( + restls_read_u16(extension) + .context("Shadowsocks restls pre_shared_key extension misses identities length")?, + ); + let mut identities = restls_read_exact(extension, identities_len) + .context("Shadowsocks restls pre_shared_key identities are truncated")?; + let mut labels = Vec::new(); + while !identities.is_empty() { + let label_len = usize::from( + restls_read_u16(&mut identities) + .context("Shadowsocks restls pre_shared_key identity misses label length")?, + ); + let label = restls_read_exact(&mut identities, label_len) + .context("Shadowsocks restls pre_shared_key identity label is truncated")?; + labels.push(label.to_vec()); + restls_read_exact(&mut identities, 4) + .context("Shadowsocks restls pre_shared_key identity misses ticket age")?; + } + let binders_len = usize::from( + restls_read_u16(extension) + .context("Shadowsocks restls pre_shared_key extension misses binders length")?, + ); + restls_read_exact(extension, binders_len) + .context("Shadowsocks restls pre_shared_key binders are truncated")?; + Ok(labels) +} + +#[allow(dead_code)] +fn restls_read_u8(input: &mut &[u8]) -> Result { + let (byte, rest) = input + .split_first() + .ok_or_else(|| anyhow!("unexpected end of Restls TLS data"))?; + *input = rest; + Ok(*byte) +} + +#[allow(dead_code)] +fn restls_read_u16(input: &mut &[u8]) -> Result { + let bytes = restls_read_exact(input, 2)?; + Ok(u16::from_be_bytes( + bytes.try_into().expect("fixed u16 byte length"), + )) +} + +#[allow(dead_code)] +fn restls_read_u24(input: &mut &[u8]) -> Result { + let bytes = restls_read_exact(input, 3)?; + Ok((u32::from(bytes[0]) << 16) | (u32::from(bytes[1]) << 8) | u32::from(bytes[2])) +} + +#[allow(dead_code)] +fn restls_read_exact<'a>(input: &mut &'a [u8], len: usize) -> Result<&'a [u8]> { + if input.len() < len { + bail!("unexpected end of Restls TLS data"); + } + let (head, tail) = input.split_at(len); + *input = tail; + Ok(head) +} + +#[allow(dead_code)] +fn restls_tls12_session_id(secret: &[u8; 32], materials: &[Vec]) -> Result<[u8; 32]> { + let layout: &[usize] = match materials.len() { + 3 => &RESTLS_TLS12_CLIENT_AUTH_LAYOUT_3, + 4 => &RESTLS_TLS12_CLIENT_AUTH_LAYOUT_4, + len => bail!("Shadowsocks restls TLS 1.2 auth requires 3 or 4 key materials, got {len}"), + }; + let mut session_id = [0u8; 32]; + for (index, material) in materials.iter().enumerate() { + let mut hmac = blake3::Hasher::new_keyed(secret); + hmac.update(material); + let digest = hmac.finalize(); + let start = layout[index]; + let end = layout[index + 1]; + session_id[start..end].copy_from_slice(&digest.as_bytes()[..end - start]); + } + Ok(session_id) +} + +#[allow(dead_code)] +fn restls_unmask_server_auth_record( + secret: &[u8; 32], + server_random: &[u8; 32], + record: &[u8], + tls12_gcm: bool, +) -> Result { + if record.len() < TLS_RECORD_HEADER_LEN { + bail!("Shadowsocks restls server auth record is too short"); + } + let mut hmac = blake3::Hasher::new_keyed(secret); + hmac.update(server_random); + let digest = hmac.finalize(); + let mask = &digest.as_bytes()[..RESTLS_HANDSHAKE_MAC_LEN]; + let mut unmasked = record.to_vec(); + let mut tls12_gcm_server_disable_counter = false; + let offset = if tls12_gcm + && record.len() >= TLS_RECORD_HEADER_LEN + 8 + && record[TLS_RECORD_HEADER_LEN..TLS_RECORD_HEADER_LEN + 8] == [0u8; 8] + { + TLS_RECORD_HEADER_LEN + 8 + } else { + tls12_gcm_server_disable_counter = tls12_gcm; + TLS_RECORD_HEADER_LEN + }; + xor_with_restls_mask(&mut unmasked[offset..], mask); + Ok(RestlsServerAuthRecord { + record: unmasked, + tls12_gcm_server_disable_counter, + }) +} + +#[allow(dead_code)] +fn restls_server_hello_random(record: &[u8]) -> Option<[u8; 32]> { + if record.len() < TLS_RECORD_HEADER_LEN + 1 + 3 + 2 + 32 { + return None; + } + if record[0] != 0x16 || record[TLS_RECORD_HEADER_LEN] != 0x02 { + return None; + } + let payload_len = usize::from(u16::from_be_bytes([record[3], record[4]])); + if payload_len + TLS_RECORD_HEADER_LEN != record.len() { + return None; + } + let handshake_len = (usize::from(record[TLS_RECORD_HEADER_LEN + 1]) << 16) + | (usize::from(record[TLS_RECORD_HEADER_LEN + 2]) << 8) + | usize::from(record[TLS_RECORD_HEADER_LEN + 3]); + if handshake_len + 4 > payload_len || handshake_len < 34 { + return None; + } + let random_start = TLS_RECORD_HEADER_LEN + 1 + 3 + 2; + let mut server_random = [0u8; 32]; + server_random.copy_from_slice(&record[random_start..random_start + 32]); + Some(server_random) +} + +#[allow(dead_code)] +fn restls_tls_records(mut bytes: &[u8]) -> Vec<&[u8]> { + let mut records = Vec::new(); + while bytes.len() >= TLS_RECORD_HEADER_LEN { + let record_len = + TLS_RECORD_HEADER_LEN + usize::from(u16::from_be_bytes([bytes[3], bytes[4]])); + if bytes.len() < record_len { + break; + } + let (record, rest) = bytes.split_at(record_len); + records.push(record); + bytes = rest; + } + records +} + +#[allow(dead_code)] +impl RestlsApplicationCodec { + fn new(secret: [u8; 32], server_random: [u8; 32], tls12_gcm: bool) -> Self { + Self { + secret, + server_random, + to_server_counter: 0, + to_client_counter: 0, + tls12_gcm, + tls12_gcm_to_client_disable_counter: false, + client_finished_auth: None, + } + } + + fn set_client_finished_auth(&mut self, record: Vec) { + self.client_finished_auth = Some(record); + } + + fn set_tls12_gcm_to_client_disable_counter(&mut self, disabled: bool) { + self.tls12_gcm_to_client_disable_counter = disabled; + } + + fn auth_header_hash(&self, direction: RestlsRecordDirection) -> blake3::Hasher { + let mut hasher = blake3::Hasher::new_keyed(&self.secret); + hasher.update(&self.server_random); + let mut counter = [0u8; 8]; + match direction { + RestlsRecordDirection::ToServer => { + hasher.update(b"client-to-server"); + counter.copy_from_slice(&self.to_server_counter.to_be_bytes()); + } + RestlsRecordDirection::ToClient => { + hasher.update(b"server-to-client"); + counter.copy_from_slice(&self.to_client_counter.to_be_bytes()); + } + } + hasher.update(&counter); + hasher + } + + fn build_to_server_record( + &mut self, + data: &[u8], + script: &[RestlsScriptLine], + ) -> Result { + self.build_to_server_record_inner(data, script, false) + } + + fn build_to_server_fake_response_record( + &mut self, + script: &[RestlsScriptLine], + ) -> Result { + self.build_to_server_record_inner(&[], script, true) + } + + fn build_to_server_record_inner( + &mut self, + data: &[u8], + script: &[RestlsScriptLine], + allow_empty: bool, + ) -> Result { + if data.is_empty() && !allow_empty { + bail!("Shadowsocks restls application record requires non-empty data"); + } + let mut data_len = data.len(); + let mut padding_len = 0usize; + let mut command = RestlsCommand::Noop; + if let Some(line) = script.get(self.to_server_counter as usize) { + data_len = line.target_len.len(); + command = line.command; + } + if data_len == 0 { + padding_len = 19 + rand::thread_rng().gen_range(0..100); + } + if data.len() < data_len { + padding_len = data_len - data.len(); + data_len = data.len(); + } + + let auth_header_len = RESTLS_APP_DATA_AUTH_HEADER_LEN + if self.tls12_gcm { 8 } else { 0 }; + let mut payload_len = data_len + padding_len + auth_header_len; + if payload_len > RESTLS_MAX_PLAINTEXT { + payload_len = RESTLS_MAX_PLAINTEXT; + } + data_len = payload_len + .checked_sub(auth_header_len + padding_len) + .ok_or_else(|| anyhow!("Shadowsocks restls target length is too large"))?; + let payload_len_u16 = u16::try_from(payload_len) + .context("Shadowsocks restls application record is too large")?; + + let header_len = TLS_RECORD_HEADER_LEN + if self.tls12_gcm { 8 } else { 0 }; + let mut record = Vec::with_capacity(TLS_RECORD_HEADER_LEN + payload_len); + record.push(TLS_APPLICATION_DATA_RECORD_TYPE); + record.extend_from_slice(&TLS12_RECORD_VERSION); + record.extend_from_slice(&payload_len_u16.to_be_bytes()); + if self.tls12_gcm { + record.extend_from_slice(&(self.to_server_counter + 1).to_be_bytes()); + } + record.resize(header_len + RESTLS_APP_DATA_AUTH_HEADER_LEN, 0); + record.extend_from_slice(&data[..data_len]); + let padding_start = record.len(); + record.resize(padding_start + padding_len, 0); + OsRng.fill_bytes(&mut record[padding_start..]); + self.write_auth_header( + &mut record, + data_len, + command, + header_len, + RestlsRecordDirection::ToServer, + )?; + self.to_server_counter += 1; + Ok(RestlsWriteResult { + record, + consumed: data_len, + command, + }) + } + + fn decode_to_client_record(&mut self, record: &[u8]) -> Result { + let frame = self.decode_record(record, RestlsRecordDirection::ToClient)?; + self.to_client_counter += 1; + Ok(frame) + } + + fn decode_to_server_record(&mut self, record: &[u8]) -> Result { + let frame = self.decode_record(record, RestlsRecordDirection::ToServer)?; + self.to_server_counter += 1; + Ok(frame) + } + + #[cfg(test)] + fn build_to_client_test_record( + &mut self, + data: &[u8], + command: RestlsCommand, + ) -> Result> { + let payload_len = + RESTLS_APP_DATA_AUTH_HEADER_LEN + if self.tls12_gcm { 8 } else { 0 } + data.len(); + let payload_len_u16 = u16::try_from(payload_len) + .context("Shadowsocks restls application record is too large")?; + let header_len = TLS_RECORD_HEADER_LEN + if self.tls12_gcm { 8 } else { 0 }; + let mut record = Vec::with_capacity(TLS_RECORD_HEADER_LEN + payload_len); + record.push(TLS_APPLICATION_DATA_RECORD_TYPE); + record.extend_from_slice(&TLS12_RECORD_VERSION); + record.extend_from_slice(&payload_len_u16.to_be_bytes()); + if self.tls12_gcm { + record.extend_from_slice(&(self.to_client_counter + 1).to_be_bytes()); + } + record.resize(header_len + RESTLS_APP_DATA_AUTH_HEADER_LEN, 0); + record.extend_from_slice(data); + self.write_auth_header( + &mut record, + data.len(), + command, + header_len, + RestlsRecordDirection::ToClient, + )?; + self.to_client_counter += 1; + Ok(record) + } + + fn write_auth_header( + &mut self, + record: &mut [u8], + data_len: usize, + command: RestlsCommand, + header_len: usize, + direction: RestlsRecordDirection, + ) -> Result<()> { + let (header, body) = record.split_at_mut(header_len); + let sample_len = 32.min(body[RESTLS_APP_DATA_OFFSET..].len()); + let mut hmac_mask = self.auth_header_hash(direction); + hmac_mask.update(&body[RESTLS_APP_DATA_OFFSET..RESTLS_APP_DATA_OFFSET + sample_len]); + let mask = hmac_mask.finalize(); + let mask = &mask.as_bytes()[..RESTLS_MASK_LEN]; + + body[RESTLS_APP_DATA_LEN_OFFSET..RESTLS_APP_DATA_LEN_OFFSET + 2] + .copy_from_slice(&(data_len as u16).to_be_bytes()); + body[RESTLS_APP_DATA_LEN_OFFSET + 2..RESTLS_APP_DATA_OFFSET] + .copy_from_slice(&command.to_bytes()); + xor_with_restls_mask( + &mut body[RESTLS_APP_DATA_LEN_OFFSET..RESTLS_APP_DATA_OFFSET], + mask, + ); + + let mut hmac_auth = self.auth_header_hash(direction); + if direction == RestlsRecordDirection::ToServer { + if let Some(client_finished) = self.client_finished_auth.take() { + hmac_auth.update(&client_finished); + } + } + hmac_auth.update(header); + hmac_auth.update(&body[RESTLS_APP_DATA_LEN_OFFSET..]); + let auth_mac = hmac_auth.finalize(); + body[..RESTLS_APP_DATA_MAC_LEN] + .copy_from_slice(&auth_mac.as_bytes()[..RESTLS_APP_DATA_MAC_LEN]); + Ok(()) + } + + fn decode_record( + &mut self, + record: &[u8], + direction: RestlsRecordDirection, + ) -> Result { + if record.len() < TLS_RECORD_HEADER_LEN + RESTLS_APP_DATA_AUTH_HEADER_LEN { + bail!("Shadowsocks restls application record is too short"); + } + if record[0] != TLS_APPLICATION_DATA_RECORD_TYPE { + bail!("Shadowsocks restls record is not application data"); + } + if record[1..3] != TLS12_RECORD_VERSION { + bail!("Shadowsocks restls record has invalid TLS record version"); + } + let payload_len = usize::from(u16::from_be_bytes([record[3], record[4]])); + if payload_len + TLS_RECORD_HEADER_LEN != record.len() { + bail!("Shadowsocks restls application record length mismatch"); + } + + let mut header_len = TLS_RECORD_HEADER_LEN; + let tls12_gcm_counter_present = self.tls12_gcm + && !(direction == RestlsRecordDirection::ToClient + && self.tls12_gcm_to_client_disable_counter); + if tls12_gcm_counter_present { + if record.len() < TLS_RECORD_HEADER_LEN + 8 + RESTLS_APP_DATA_AUTH_HEADER_LEN { + bail!("Shadowsocks restls GCM application record is too short"); + } + let expected_counter = match direction { + RestlsRecordDirection::ToServer => self.to_server_counter + 1, + RestlsRecordDirection::ToClient => self.to_client_counter + 1, + }; + let nonce = u64::from_be_bytes( + record[TLS_RECORD_HEADER_LEN..TLS_RECORD_HEADER_LEN + 8] + .try_into() + .expect("nonce slice has fixed length"), + ); + if nonce != expected_counter { + bail!("Shadowsocks restls GCM application record counter mismatch"); + } + header_len += 8; + } + + let header = &record[..header_len]; + let mut body = record[header_len..].to_vec(); + let mut hmac_auth = self.auth_header_hash(direction); + if direction == RestlsRecordDirection::ToServer { + if let Some(client_finished) = self.client_finished_auth.as_ref() { + hmac_auth.update(client_finished); + } + } + hmac_auth.update(header); + hmac_auth.update(&body[RESTLS_APP_DATA_LEN_OFFSET..]); + let auth_mac = hmac_auth.finalize(); + if auth_mac.as_bytes()[..RESTLS_APP_DATA_MAC_LEN] + .ct_eq(&body[..RESTLS_APP_DATA_MAC_LEN]) + .unwrap_u8() + != 1 + { + bail!("Shadowsocks restls application record authentication failed"); + } + if direction == RestlsRecordDirection::ToServer { + self.client_finished_auth = None; + } + + let sample_len = 32.min(body[RESTLS_APP_DATA_OFFSET..].len()); + let mut hmac_mask = self.auth_header_hash(direction); + hmac_mask.update(&body[RESTLS_APP_DATA_OFFSET..RESTLS_APP_DATA_OFFSET + sample_len]); + let mask = hmac_mask.finalize(); + xor_with_restls_mask( + &mut body[RESTLS_APP_DATA_LEN_OFFSET..RESTLS_APP_DATA_OFFSET], + &mask.as_bytes()[..RESTLS_MASK_LEN], + ); + let data_len = usize::from(u16::from_be_bytes([ + body[RESTLS_APP_DATA_LEN_OFFSET], + body[RESTLS_APP_DATA_LEN_OFFSET + 1], + ])); + if body.len() < RESTLS_APP_DATA_OFFSET + data_len { + bail!("Shadowsocks restls application record data length exceeds payload"); + } + let command = RestlsCommand::from_bytes([ + body[RESTLS_APP_DATA_LEN_OFFSET + 2], + body[RESTLS_APP_DATA_LEN_OFFSET + 3], + ])?; + Ok(RestlsApplicationFrame { + data: body[RESTLS_APP_DATA_OFFSET..RESTLS_APP_DATA_OFFSET + data_len].to_vec(), + command, + }) + } +} + +#[allow(dead_code)] +impl RestlsCommand { + fn need_interrupt(self) -> bool { + matches!(self, RestlsCommand::Response(_)) + } +} + +#[allow(dead_code)] +impl RestlsStreamState { + fn new(codec: RestlsApplicationCodec, script: Vec) -> Self { + Self { + codec, + script, + send_buffer: Vec::new(), + write_pending: false, + } + } + + fn write(&mut self, data_new: &[u8]) -> Result { + let fake_response = data_new == RESTLS_RANDOM_RESPONSE_MAGIC; + let accepted = if fake_response { 0 } else { data_new.len() }; + let data_new = if fake_response { &[][..] } else { data_new }; + if fake_response { + self.write_pending = false; + } + + if self.write_pending { + self.send_buffer.extend_from_slice(data_new); + return Ok(RestlsStreamWriteResult { accepted, records: Vec::new() }); + } + + let mut data = if self.send_buffer.is_empty() { + data_new.to_vec() + } else { + self.send_buffer.extend_from_slice(data_new); + std::mem::take(&mut self.send_buffer) + }; + let mut records = Vec::new(); + let mut fake_response_pending = fake_response; + + while !data.is_empty() || fake_response_pending { + let record = if fake_response_pending { + self.codec + .build_to_server_fake_response_record(&self.script)? + } else { + self.codec.build_to_server_record(&data, &self.script)? + }; + let consumed = record.consumed; + let command = record.command; + records.push(record.record); + if command.need_interrupt() && !fake_response_pending { + self.write_pending = true; + } + if consumed > data.len() { + bail!("Shadowsocks restls consumed more data than available"); + } + data.drain(..consumed); + if command.need_interrupt() && !fake_response_pending { + break; + } + fake_response_pending = false; + } + + self.send_buffer = data; + Ok(RestlsStreamWriteResult { accepted, records }) + } + + fn receive_command( + &mut self, + command: RestlsCommand, + resumed_pending_write: bool, + ) -> Result>> { + let mut records = Vec::new(); + let RestlsCommand::Response(count) = command else { + return Ok(records); + }; + let count = restls_response_repeat_count(count, resumed_pending_write); + for _ in 0..count { + records.extend(self.write(RESTLS_RANDOM_RESPONSE_MAGIC)?.records); + } + Ok(records) + } + + fn resume_pending_write(&mut self) -> Result { + self.write_pending = false; + self.write(&[]) + } + + fn read_to_client_record(&mut self, record: &[u8]) -> Result { + let frame = self.codec.decode_to_client_record(record)?; + let mut records = Vec::new(); + let mut resumed_pending_write = false; + if self.write_pending { + let resumed = self.resume_pending_write()?; + resumed_pending_write = !resumed.records.is_empty(); + records.extend(resumed.records); + } + records.extend(self.receive_command(frame.command, resumed_pending_write)?); + Ok(RestlsStreamReadResult { + data: frame.data, + records, + resumed_pending_write, + }) + } +} + +fn restls_response_repeat_count(count: u16, resumed_pending_write: bool) -> usize { + let mut count = (count as u8) as i8; + if resumed_pending_write { + count = count.wrapping_sub(1); + } + if count <= 0 { + 0 + } else { + count as usize + } +} + +#[allow(dead_code)] +fn xor_with_restls_mask(data: &mut [u8], mask: &[u8]) { + for (byte, mask) in data.iter_mut().zip(mask.iter()) { + *byte ^= *mask; + } +} + +fn plugin_bool_opt(opts: &HashMap, key: &str) -> Option { + opts.get(key).map(|value| { + matches!( + value.trim().to_ascii_lowercase().as_str(), + "1" | "true" | "yes" | "y" | "on" + ) + }) +} + +fn plugin_u8_opt(opts: &HashMap, key: &str) -> Option { + opts.get(key) + .and_then(|value| value.trim().parse::().ok()) +} + +fn plugin_u16_opt(opts: &HashMap, key: &str) -> Option { + opts.get(key) + .and_then(|value| value.trim().parse::().ok()) +} + +fn plugin_u32_opt(opts: &HashMap, key: &str) -> Option { + opts.get(key) + .and_then(|value| value.trim().parse::().ok()) +} + +fn plugin_u64_opt(opts: &HashMap, key: &str) -> Option { + opts.get(key) + .and_then(|value| value.trim().parse::().ok()) +} + +fn plugin_usize_opt(opts: &HashMap, key: &str) -> Option { + opts.get(key) + .and_then(|value| value.trim().parse::().ok()) +} + +fn plugin_i32_opt(opts: &HashMap, key: &str) -> Option { + opts.get(key) + .and_then(|value| value.trim().parse::().ok()) +} + +fn plugin_list_opt(opts: &HashMap, key: &str) -> Option> { + opts.get(key).map(|value| { + value + .split(',') + .map(str::trim) + .filter(|value| !value.is_empty()) + .map(ToOwned::to_owned) + .collect() + }) +} + +fn plugin_tls_client_identity(opts: &HashMap) -> Result> { + let certificate = opts + .get("certificate") + .map(|value| value.trim()) + .filter(|value| !value.is_empty()); + let private_key = opts + .get("private-key") + .map(|value| value.trim()) + .filter(|value| !value.is_empty()); + match (certificate, private_key) { + (None, None) => Ok(None), + (Some(certificate), Some(private_key)) => Ok(Some(TlsClientIdentity { + certificate: certificate.to_owned(), + private_key: private_key.to_owned(), + })), + _ => bail!("Shadowsocks TLS plugin certificate and private-key must be provided together"), + } +} + +fn plugin_ech_config(opts: &HashMap) -> Result> { + let enabled = plugin_bool_opt(opts, "ech-opts.enable") + .or_else(|| plugin_bool_opt(opts, "ech.enable")) + .unwrap_or(false); + if !enabled { + return Ok(None); + } + + let config_list = opts + .get("ech-opts.config") + .or_else(|| opts.get("ech.config")) + .map(|value| { + BASE64_STANDARD + .decode(value.trim()) + .context("failed to decode Shadowsocks websocket ECH config") + }) + .transpose()?; + let query_server_name = opts + .get("ech-opts.query-server-name") + .or_else(|| opts.get("ech.query-server-name")) + .map(|value| value.trim()) + .filter(|value| !value.is_empty()) + .map(ToOwned::to_owned); + + Ok(Some(ShadowsocksEchConfig { + config_list, + query_server_name, + })) +} + +fn plugin_headers(opts: &HashMap) -> Vec<(String, String)> { + opts.iter() + .filter_map(|(key, value)| { + key.strip_prefix("headers.") + .or_else(|| key.strip_prefix("header.")) + .map(|header| (header.to_owned(), value.to_owned())) + }) + .collect() +} + +fn simple_obfs_http_request(host: &str, port: u16, payload: &[u8]) -> Vec { + let mut websocket_key = [0u8; 16]; + OsRng.fill_bytes(&mut websocket_key); + let user_agent_minor = OsRng.next_u32() % 54; + let user_agent_patch = OsRng.next_u32() % 2; + let request_host = if port == 80 { + host.to_owned() + } else { + format!("{host}:{port}") + }; + let mut request = Vec::new(); + request.extend_from_slice(format!("GET http://{host}/ HTTP/1.1\r\n").as_bytes()); + request.extend_from_slice(format!("Host: {request_host}\r\n").as_bytes()); + request.extend_from_slice( + format!("User-Agent: curl/7.{user_agent_minor}.{user_agent_patch}\r\n").as_bytes(), + ); + request.extend_from_slice(b"Upgrade: websocket\r\n"); + request.extend_from_slice(b"Connection: Upgrade\r\n"); + request.extend_from_slice( + format!( + "Sec-WebSocket-Key: {}\r\n", + BASE64_URL_SAFE.encode(websocket_key) + ) + .as_bytes(), + ); + request.extend_from_slice(format!("Content-Length: {}\r\n\r\n", payload.len()).as_bytes()); + request.extend_from_slice(payload); + request +} + +fn simple_obfs_tls_record(payload: &[u8]) -> io::Result> { + if payload.len() > u16::MAX as usize { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "simple-obfs TLS payload is too large", + )); + } + let mut record = Vec::with_capacity(5 + payload.len()); + record.extend_from_slice(&[0x17, 0x03, 0x03]); + record.extend_from_slice(&(payload.len() as u16).to_be_bytes()); + record.extend_from_slice(payload); + Ok(record) +} + +fn shadow_tls_v2_record( + payload: &[u8], + len: usize, + first_write_hmac: Option<[u8; 8]>, +) -> io::Result> { + if len > SHADOW_TLS_MAX_RECORD_PAYLOAD || len > payload.len() { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "ShadowTLS v2 payload is too large", + )); + } + let prefix_len = first_write_hmac.as_ref().map(|_| 8).unwrap_or(0); + let record_len = prefix_len + len; + let mut record = Vec::with_capacity(5 + record_len); + record.extend_from_slice(&[0x17, 0x03, 0x03]); + record.extend_from_slice(&(record_len as u16).to_be_bytes()); + if let Some(prefix) = first_write_hmac { + record.extend_from_slice(&prefix); + } + record.extend_from_slice(&payload[..len]); + Ok(record) +} + +fn shadow_tls_v3_record( + payload: &[u8], + len: usize, + hmac_add: &mut hmac::Context, +) -> io::Result> { + if len > SHADOW_TLS_MAX_RECORD_PAYLOAD || len > payload.len() { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "ShadowTLS v3 payload is too large", + )); + } + let payload = &payload[..len]; + let mut record = Vec::with_capacity(SHADOW_TLS_V3_HMAC_HEADER_SIZE + payload.len()); + record.extend_from_slice(&[0x17, 0x03, 0x03]); + record.extend_from_slice(&((SHADOW_TLS_V3_HMAC_SIZE + payload.len()) as u16).to_be_bytes()); + hmac_add.update(payload); + let hmac_prefix = shadow_tls_v3_hmac_prefix(hmac_add, SHADOW_TLS_V3_HMAC_SIZE); + hmac_add.update(&hmac_prefix); + record.extend_from_slice(&hmac_prefix); + record.extend_from_slice(payload); + Ok(record) +} + +fn shadow_tls_v3_generate_session_id(password: &str, client_hello: &[u8]) -> [u8; 32] { + let mut session_id = [0u8; SHADOW_TLS_V3_SESSION_ID_SIZE]; + OsRng.fill_bytes(&mut session_id[..SHADOW_TLS_V3_SESSION_ID_SIZE - SHADOW_TLS_V3_HMAC_SIZE]); + if client_hello.len() >= SHADOW_TLS_V3_SESSION_ID_START + SHADOW_TLS_V3_SESSION_ID_SIZE { + let mut context = shadow_tls_v3_hmac(password, b"", b""); + context.update(&client_hello[..SHADOW_TLS_V3_SESSION_ID_START]); + context.update(&session_id); + context.update( + &client_hello[SHADOW_TLS_V3_SESSION_ID_START + SHADOW_TLS_V3_SESSION_ID_SIZE..], + ); + let prefix = shadow_tls_v3_hmac_prefix(&context, SHADOW_TLS_V3_HMAC_SIZE); + session_id[SHADOW_TLS_V3_SESSION_ID_SIZE - SHADOW_TLS_V3_HMAC_SIZE..] + .copy_from_slice(&prefix); + } + session_id +} + +#[allow(dead_code)] +fn shadow_tls_v3_sign_client_hello_record( + password: &str, + bytes: &[u8], +) -> io::Result>> { + if bytes.len() < SHADOW_TLS_V3_TLS_HEADER_SIZE { + return Ok(None); + } + if bytes[0] != 0x16 { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "ShadowTLS v3 first TLS record is not a handshake record", + )); + } + let payload_len = u16::from_be_bytes([bytes[3], bytes[4]]) as usize; + let record_len = SHADOW_TLS_V3_TLS_HEADER_SIZE + payload_len; + if bytes.len() < record_len { + return Ok(None); + } + let payload = &bytes[SHADOW_TLS_V3_TLS_HEADER_SIZE..record_len]; + if payload.first().copied() != Some(0x01) { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "ShadowTLS v3 first handshake message is not ClientHello", + )); + } + let session_id_length_index = SHADOW_TLS_V3_SESSION_ID_START - 1; + if payload.get(session_id_length_index).copied() != Some(SHADOW_TLS_V3_SESSION_ID_SIZE as u8) { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "ShadowTLS v3 browser ClientHello did not reserve a 32-byte session ID", + )); + } + if payload.len() < SHADOW_TLS_V3_SESSION_ID_START + SHADOW_TLS_V3_SESSION_ID_SIZE { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "ShadowTLS v3 browser ClientHello session ID is truncated", + )); + } + let session_id = shadow_tls_v3_generate_session_id(password, payload); + let mut signed = bytes.to_vec(); + let session_id_start = SHADOW_TLS_V3_TLS_HEADER_SIZE + SHADOW_TLS_V3_SESSION_ID_START; + signed[session_id_start..session_id_start + SHADOW_TLS_V3_SESSION_ID_SIZE] + .copy_from_slice(&session_id); + Ok(Some(signed)) +} + +fn shadow_tls_v3_hmac(password: &str, first: &[u8], second: &[u8]) -> hmac::Context { + let key = hmac::Key::new(hmac::HMAC_SHA1_FOR_LEGACY_USE_ONLY, password.as_bytes()); + let mut context = hmac::Context::with_key(&key); + context.update(first); + context.update(second); + context +} + +fn shadow_tls_v3_hmac_prefix(context: &hmac::Context, len: usize) -> Vec { + let digest = context.clone().sign(); + digest.as_ref()[..len].to_vec() +} + +fn shadow_tls_v3_application_data_hmac(frame: &[u8], context: &hmac::Context) -> Option> { + if frame.len() < SHADOW_TLS_V3_HMAC_HEADER_SIZE + || frame[0] != 0x17 + || frame[1] != 0x03 + || frame[2] != 0x03 + { + return None; + } + let mut candidate = context.clone(); + candidate.update(&frame[SHADOW_TLS_V3_HMAC_HEADER_SIZE..]); + Some(shadow_tls_v3_hmac_prefix( + &candidate, + SHADOW_TLS_V3_HMAC_SIZE, + )) +} + +fn shadow_tls_v3_kdf( + password: &str, + server_random: &[u8; SHADOW_TLS_V3_TLS_RANDOM_SIZE], +) -> [u8; 32] { + let mut hasher = Sha256::new(); + hasher.update(password.as_bytes()); + hasher.update(server_random); + hasher.finalize().into() +} + +fn shadow_tls_v3_xor(data: &mut [u8], key: &[u8; 32]) { + for (index, value) in data.iter_mut().enumerate() { + *value ^= key[index % key.len()]; + } +} + +fn shadow_tls_v3_server_hello_supports_tls13(frame: &[u8]) -> bool { + if frame.len() < SHADOW_TLS_V3_SESSION_ID_LENGTH_INDEX { + return false; + } + let mut index = SHADOW_TLS_V3_SESSION_ID_LENGTH_INDEX; + let Some(session_id_len) = frame.get(index).copied().map(usize::from) else { + return false; + }; + index += 1 + session_id_len; + if frame.len() < index + 3 { + return false; + } + index += 3; + if frame.len() < index + 2 { + return false; + } + let extensions_len = u16::from_be_bytes([frame[index], frame[index + 1]]) as usize; + index += 2; + let end = index.saturating_add(extensions_len).min(frame.len()); + while index + 4 <= end { + let extension_type = u16::from_be_bytes([frame[index], frame[index + 1]]); + let extension_len = u16::from_be_bytes([frame[index + 2], frame[index + 3]]) as usize; + index += 4; + if index + extension_len > end { + return false; + } + if extension_type == 43 { + return extension_len == 2 + && u16::from_be_bytes([frame[index], frame[index + 1]]) == 0x0304; + } + index += extension_len; + } + false +} + +fn simple_obfs_tls_client_hello(server: &str, payload: &[u8]) -> io::Result> { + let payload_len = u16::try_from(payload.len()).map_err(|_| { + io::Error::new( + io::ErrorKind::InvalidInput, + "simple-obfs TLS client hello payload is too large", + ) + })?; + let server_len = u16::try_from(server.len()).map_err(|_| { + io::Error::new( + io::ErrorKind::InvalidInput, + "simple-obfs TLS client hello server name is too large", + ) + })?; + let record_len = 212u16 + .checked_add(payload_len) + .and_then(|len| len.checked_add(server_len)) + .ok_or_else(|| { + io::Error::new( + io::ErrorKind::InvalidInput, + "simple-obfs TLS client hello is too large", + ) + })?; + let handshake_len = 208u16 + .checked_add(payload_len) + .and_then(|len| len.checked_add(server_len)) + .ok_or_else(|| { + io::Error::new( + io::ErrorKind::InvalidInput, + "simple-obfs TLS client hello is too large", + ) + })?; + let extension_len = 79u16 + .checked_add(payload_len) + .and_then(|len| len.checked_add(server_len)) + .ok_or_else(|| { + io::Error::new( + io::ErrorKind::InvalidInput, + "simple-obfs TLS client hello is too large", + ) + })?; + let sni_ext_len = server_len.checked_add(5).ok_or_else(|| { + io::Error::new( + io::ErrorKind::InvalidInput, + "simple-obfs TLS client hello server name is too large", + ) + })?; + let sni_list_len = server_len.checked_add(3).ok_or_else(|| { + io::Error::new( + io::ErrorKind::InvalidInput, + "simple-obfs TLS client hello server name is too large", + ) + })?; + if server_len == 0 { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "simple-obfs TLS client hello server name is empty", + )); + } + let mut random = [0u8; 28]; + let mut session_id = [0u8; 32]; + OsRng.fill_bytes(&mut random); + OsRng.fill_bytes(&mut session_id); + + let mut hello = Vec::with_capacity(217 + server.len() + payload.len()); + hello.push(22); + hello.extend_from_slice(&[0x03, 0x01]); + hello.extend_from_slice(&record_len.to_be_bytes()); + hello.push(1); + hello.push(0); + hello.extend_from_slice(&handshake_len.to_be_bytes()); + hello.extend_from_slice(&[0x03, 0x03]); + hello.extend_from_slice(&(aead2022_now_timestamp().unwrap_or(0) as u32).to_be_bytes()); + hello.extend_from_slice(&random); + hello.push(32); + hello.extend_from_slice(&session_id); + hello.extend_from_slice(&[0x00, 0x38]); + hello.extend_from_slice(&[ + 0xc0, 0x2c, 0xc0, 0x30, 0x00, 0x9f, 0xcc, 0xa9, 0xcc, 0xa8, 0xcc, 0xaa, 0xc0, 0x2b, 0xc0, + 0x2f, 0x00, 0x9e, 0xc0, 0x24, 0xc0, 0x28, 0x00, 0x6b, 0xc0, 0x23, 0xc0, 0x27, 0x00, 0x67, + 0xc0, 0x0a, 0xc0, 0x14, 0x00, 0x39, 0xc0, 0x09, 0xc0, 0x13, 0x00, 0x33, 0x00, 0x9d, 0x00, + 0x9c, 0x00, 0x3d, 0x00, 0x3c, 0x00, 0x35, 0x00, 0x2f, 0x00, 0xff, + ]); + hello.extend_from_slice(&[0x01, 0x00]); + hello.extend_from_slice(&extension_len.to_be_bytes()); + hello.extend_from_slice(&[0x00, 0x23]); + hello.extend_from_slice(&payload_len.to_be_bytes()); + hello.extend_from_slice(payload); + hello.extend_from_slice(&[0x00, 0x00]); + hello.extend_from_slice(&sni_ext_len.to_be_bytes()); + hello.extend_from_slice(&sni_list_len.to_be_bytes()); + hello.push(0); + hello.extend_from_slice(&server_len.to_be_bytes()); + hello.extend_from_slice(server.as_bytes()); + hello.extend_from_slice(&[0x00, 0x0b, 0x00, 0x04, 0x03, 0x01, 0x00, 0x02]); + hello.extend_from_slice(&[ + 0x00, 0x0a, 0x00, 0x0a, 0x00, 0x08, 0x00, 0x1d, 0x00, 0x17, 0x00, 0x19, 0x00, 0x18, + ]); + hello.extend_from_slice(&[ + 0x00, 0x0d, 0x00, 0x20, 0x00, 0x1e, 0x06, 0x01, 0x06, 0x02, 0x06, 0x03, 0x05, 0x01, 0x05, + 0x02, 0x05, 0x03, 0x04, 0x01, 0x04, 0x02, 0x04, 0x03, 0x03, 0x01, 0x03, 0x02, 0x03, 0x03, + 0x02, 0x01, 0x02, 0x02, 0x02, 0x03, + ]); + hello.extend_from_slice(&[0x00, 0x16, 0x00, 0x00]); + hello.extend_from_slice(&[0x00, 0x17, 0x00, 0x00]); + Ok(hello) +} diff --git a/burrow/src/proxy_runtime/tests.rs b/burrow/src/proxy_runtime/tests.rs new file mode 100644 index 0000000..dbe5bca --- /dev/null +++ b/burrow/src/proxy_runtime/tests.rs @@ -0,0 +1,3855 @@ +#[cfg(test)] +mod tests { + use super::*; + + async fn read_test_tls_record(stream: &mut S) -> Vec + where + S: AsyncRead + Unpin, + { + let mut header = [0u8; TLS_RECORD_HEADER_LEN]; + stream.read_exact(&mut header).await.unwrap(); + let payload_len = usize::from(u16::from_be_bytes([header[3], header[4]])); + let mut record = header.to_vec(); + record.resize(TLS_RECORD_HEADER_LEN + payload_len, 0); + stream + .read_exact(&mut record[TLS_RECORD_HEADER_LEN..]) + .await + .unwrap(); + record + } + + #[test] + fn trojan_hash_is_sha224_hex() { + assert_eq!( + trojan_password_hash("password"), + "d63dc919e201d7bc4c825630d2cf25fdc93d4b2f0d46706d29038d01" + ); + } + + #[test] + fn socks_addr_round_trips_ipv4_and_ipv6() { + for addr in [ + "1.2.3.4:53".parse::().unwrap(), + "[2001:db8::1]:443".parse::().unwrap(), + ] { + let encoded = socks_addr(addr).unwrap(); + let (decoded, used) = parse_socks_addr(&encoded).unwrap(); + assert_eq!(decoded, addr); + assert_eq!(used, encoded.len()); + } + } + + #[test] + fn shadowsocks_runtime_accepts_expanded_cipher_families() { + for cipher in [ + "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", + "aes-192-gcm", + "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", + "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", + ] { + validate_shadowsocks_cipher(cipher) + .unwrap_or_else(|err| panic!("expected {cipher} to be supported: {err:#}")); + } + assert!(validate_shadowsocks_cipher("not-a-real-cipher").is_err()); + } + + #[test] + fn shadowsocks_runtime_has_no_known_mihomo_cipher_gaps() { + let mihomo_only_ciphers: [&str; 0] = []; + assert!(mihomo_only_ciphers + .iter() + .all(|cipher| validate_shadowsocks_cipher(cipher).is_ok())); + } + + #[test] + fn shadowsocks_none_cipher_builds_runtime_outbound() { + let node = ProxyNode { + ordinal: 0, + name: "ss-none".to_owned(), + protocol: crate::proxy_subscription::ProxyProtocol::Shadowsocks, + server: "127.0.0.1".to_owned(), + port: 8388, + warnings: Vec::new(), + config: ProxyNodeConfig::Shadowsocks(ShadowsocksNode { + cipher: "none".to_owned(), + password: "unused".to_owned(), + udp: true, + udp_over_tcp: true, + udp_over_tcp_version: UOT_LEGACY_VERSION, + client_fingerprint: None, + plugin: None, + smux: None, + }), + }; + + assert!(runtime_support_error(&node).is_none()); + let ProxyNodeConfig::Shadowsocks(config) = &node.config else { + panic!("expected Shadowsocks config"); + }; + let outbound = ShadowsocksOutbound::from_node(&node, config).unwrap(); + assert!(matches!(&outbound.protocol, ShadowsocksProtocol::None)); + assert!(outbound.udp_enabled); + assert!(outbound.udp_over_tcp); + } + + #[test] + fn shadowsocks_runtime_keeps_legacy_cipher_aliases() { + for cipher in [ + "aead_aes_128_gcm", + "aead_aes_256_gcm", + "chacha20-poly1305", + "aead_chacha20_poly1305", + ] { + shadowsocks_cipher_kind(cipher) + .unwrap_or_else(|err| panic!("expected {cipher} alias to be supported: {err:#}")); + } + } + + #[test] + fn shadowsocks_runtime_accepts_udp_over_tcp_nodes() { + let payload = ProxySubscriptionPayload { + name: "sub".to_owned(), + source: crate::proxy_subscription::ProxySubscriptionSource { + url: "https://sub.example/path".to_owned(), + }, + detected_format: crate::proxy_subscription::ProxySubscriptionFormat::ClashYaml, + selected_ordinal: Some(0), + selected_name: None, + nodes: vec![ProxyNode { + ordinal: 0, + name: "ss-uot".to_owned(), + protocol: crate::proxy_subscription::ProxyProtocol::Shadowsocks, + server: "127.0.0.1".to_owned(), + port: 8388, + warnings: Vec::new(), + config: ProxyNodeConfig::Shadowsocks(ShadowsocksNode { + cipher: "aes-128-gcm".to_owned(), + password: "pass".to_owned(), + udp: true, + udp_over_tcp: true, + udp_over_tcp_version: UOT_VERSION, + client_fingerprint: None, + plugin: None, + smux: None, + }), + }], + warnings: Vec::new(), + }; + + assert_eq!(selected_node(&payload).unwrap().name, "ss-uot"); + assert!(normalize_uot_version(0).is_ok()); + assert!(normalize_uot_version(UOT_LEGACY_VERSION).is_ok()); + assert!(normalize_uot_version(UOT_VERSION).is_ok()); + assert!(normalize_uot_version(3).is_err()); + } + + #[test] + fn runtime_support_accepts_tcp_only_shadowsocks_smux() { + let mut node = ProxyNode { + ordinal: 0, + name: "ss-smux".to_owned(), + protocol: crate::proxy_subscription::ProxyProtocol::Shadowsocks, + server: "127.0.0.1".to_owned(), + port: 8388, + warnings: Vec::new(), + config: ProxyNodeConfig::Shadowsocks(ShadowsocksNode { + cipher: "aes-128-gcm".to_owned(), + password: "pass".to_owned(), + udp: true, + udp_over_tcp: false, + udp_over_tcp_version: UOT_VERSION, + client_fingerprint: None, + plugin: None, + smux: Some(crate::proxy_subscription::ShadowsocksSmux { + enabled: true, + protocol: Some("smux".to_owned()), + max_connections: Some(4), + min_streams: Some(2), + max_streams: None, + padding: false, + statistic: false, + only_tcp: true, + brutal: None, + }), + }), + }; + + assert!(runtime_support_error(&node).is_none()); + let ProxyNodeConfig::Shadowsocks(config) = &node.config else { + panic!("expected Shadowsocks node"); + }; + let protocol = shadowsocks_protocol_for_node(&node, config).unwrap(); + let transport = + shadowsocks_sing_mux_transport(config.smux.as_ref(), config, &protocol).unwrap(); + assert_eq!( + transport, + Some(ShadowsocksSingMuxTransport { + protocol: ShadowsocksSingMuxProtocol::Smux, + max_connections: 4, + min_streams: 2, + max_streams: 0, + padding: false, + udp: false, + brutal: None, + }) + ); + + if let ProxyNodeConfig::Shadowsocks(config) = &mut node.config { + config.udp = true; + config.smux.as_mut().unwrap().only_tcp = false; + } + assert!(runtime_support_error(&node).is_none()); + let ProxyNodeConfig::Shadowsocks(config) = &node.config else { + panic!("expected Shadowsocks node"); + }; + let protocol = shadowsocks_protocol_for_node(&node, config).unwrap(); + let transport = + shadowsocks_sing_mux_transport(config.smux.as_ref(), config, &protocol).unwrap(); + assert_eq!( + transport, + Some(ShadowsocksSingMuxTransport { + protocol: ShadowsocksSingMuxProtocol::Smux, + max_connections: 4, + min_streams: 2, + max_streams: 0, + padding: false, + udp: true, + brutal: None, + }) + ); + } + + #[test] + fn runtime_support_accepts_default_shadowsocks_h2mux() { + let node = ProxyNode { + ordinal: 0, + name: "ss-h2mux".to_owned(), + protocol: crate::proxy_subscription::ProxyProtocol::Shadowsocks, + server: "127.0.0.1".to_owned(), + port: 8388, + warnings: Vec::new(), + config: ProxyNodeConfig::Shadowsocks(ShadowsocksNode { + cipher: "aes-128-gcm".to_owned(), + password: "pass".to_owned(), + udp: true, + udp_over_tcp: false, + udp_over_tcp_version: UOT_VERSION, + client_fingerprint: None, + plugin: None, + smux: Some(crate::proxy_subscription::ShadowsocksSmux { + enabled: true, + protocol: None, + max_connections: Some(4), + min_streams: Some(2), + max_streams: None, + padding: false, + statistic: false, + only_tcp: true, + brutal: None, + }), + }), + }; + + assert!(runtime_support_error(&node).is_none()); + let ProxyNodeConfig::Shadowsocks(config) = &node.config else { + panic!("expected Shadowsocks node"); + }; + let protocol = shadowsocks_protocol_for_node(&node, config).unwrap(); + let transport = + shadowsocks_sing_mux_transport(config.smux.as_ref(), config, &protocol).unwrap(); + assert_eq!( + transport, + Some(ShadowsocksSingMuxTransport { + protocol: ShadowsocksSingMuxProtocol::H2Mux, + max_connections: 4, + min_streams: 2, + max_streams: 0, + padding: false, + udp: false, + brutal: None, + }) + ); + } + + #[test] + fn runtime_support_accepts_padded_shadowsocks_smux_udp_when_base_udp_is_false() { + let node = ProxyNode { + ordinal: 0, + name: "ss-smux-padding".to_owned(), + protocol: crate::proxy_subscription::ProxyProtocol::Shadowsocks, + server: "127.0.0.1".to_owned(), + port: 8388, + warnings: Vec::new(), + config: ProxyNodeConfig::Shadowsocks(ShadowsocksNode { + cipher: "aes-128-gcm".to_owned(), + password: "pass".to_owned(), + udp: false, + udp_over_tcp: false, + udp_over_tcp_version: UOT_VERSION, + client_fingerprint: None, + plugin: None, + smux: Some(crate::proxy_subscription::ShadowsocksSmux { + enabled: true, + protocol: Some("smux".to_owned()), + max_connections: Some(4), + min_streams: Some(2), + max_streams: None, + padding: true, + statistic: false, + only_tcp: false, + brutal: None, + }), + }), + }; + + assert!(runtime_support_error(&node).is_none()); + let ProxyNodeConfig::Shadowsocks(config) = &node.config else { + panic!("expected Shadowsocks node"); + }; + let protocol = shadowsocks_protocol_for_node(&node, config).unwrap(); + let transport = + shadowsocks_sing_mux_transport(config.smux.as_ref(), config, &protocol).unwrap(); + assert_eq!( + transport, + Some(ShadowsocksSingMuxTransport { + protocol: ShadowsocksSingMuxProtocol::Smux, + max_connections: 4, + min_streams: 2, + max_streams: 0, + padding: true, + udp: true, + brutal: None, + }) + ); + + let outbound = ShadowsocksOutbound::from_node(&node, config).unwrap(); + assert!( + outbound.udp_enabled, + "Mihomo sing-mux supports UDP unless only-tcp is set, even when the underlying Shadowsocks udp flag is false" + ); + } + + #[test] + fn runtime_support_accepts_brutal_shadowsocks_smux() { + let node = ProxyNode { + ordinal: 0, + name: "ss-smux-brutal".to_owned(), + protocol: crate::proxy_subscription::ProxyProtocol::Shadowsocks, + server: "127.0.0.1".to_owned(), + port: 8388, + warnings: Vec::new(), + config: ProxyNodeConfig::Shadowsocks(ShadowsocksNode { + cipher: "aes-128-gcm".to_owned(), + password: "pass".to_owned(), + udp: true, + udp_over_tcp: false, + udp_over_tcp_version: UOT_VERSION, + client_fingerprint: None, + plugin: None, + smux: Some(crate::proxy_subscription::ShadowsocksSmux { + enabled: true, + protocol: Some("smux".to_owned()), + max_connections: Some(4), + min_streams: Some(2), + max_streams: None, + padding: false, + statistic: false, + only_tcp: false, + brutal: Some(crate::proxy_subscription::ShadowsocksSmuxBrutal { + enabled: true, + up: Some("10 Mbps".to_owned()), + down: Some("20 Mbps".to_owned()), + }), + }), + }), + }; + + assert!(runtime_support_error(&node).is_none()); + let ProxyNodeConfig::Shadowsocks(config) = &node.config else { + panic!("expected Shadowsocks node"); + }; + let protocol = shadowsocks_protocol_for_node(&node, config).unwrap(); + let transport = + shadowsocks_sing_mux_transport(config.smux.as_ref(), config, &protocol).unwrap(); + assert_eq!( + transport, + Some(ShadowsocksSingMuxTransport { + protocol: ShadowsocksSingMuxProtocol::Smux, + max_connections: 4, + min_streams: 2, + max_streams: 0, + padding: false, + udp: true, + brutal: Some(ShadowsocksSingMuxBrutalTransport { + send_bps: 1_250_000, + receive_bps: 2_500_000, + }), + }) + ); + } + + #[test] + fn runtime_support_accepts_tcp_only_shadowsocks_yamux() { + let node = ProxyNode { + ordinal: 0, + name: "ss-yamux".to_owned(), + protocol: crate::proxy_subscription::ProxyProtocol::Shadowsocks, + server: "127.0.0.1".to_owned(), + port: 8388, + warnings: Vec::new(), + config: ProxyNodeConfig::Shadowsocks(ShadowsocksNode { + cipher: "aes-128-gcm".to_owned(), + password: "pass".to_owned(), + udp: true, + udp_over_tcp: false, + udp_over_tcp_version: UOT_VERSION, + client_fingerprint: None, + plugin: None, + smux: Some(crate::proxy_subscription::ShadowsocksSmux { + enabled: true, + protocol: Some("yamux".to_owned()), + max_connections: Some(4), + min_streams: Some(2), + max_streams: None, + padding: false, + statistic: false, + only_tcp: true, + brutal: None, + }), + }), + }; + + assert!(runtime_support_error(&node).is_none()); + let ProxyNodeConfig::Shadowsocks(config) = &node.config else { + panic!("expected Shadowsocks node"); + }; + let protocol = shadowsocks_protocol_for_node(&node, config).unwrap(); + let transport = + shadowsocks_sing_mux_transport(config.smux.as_ref(), config, &protocol).unwrap(); + assert_eq!( + transport, + Some(ShadowsocksSingMuxTransport { + protocol: ShadowsocksSingMuxProtocol::Yamux, + max_connections: 4, + min_streams: 2, + max_streams: 0, + padding: false, + udp: false, + brutal: None, + }) + ); + } + + #[test] + fn runtime_support_accepts_custom_cipher_shadowsocks_smux() { + let node = ProxyNode { + ordinal: 0, + name: "ss-custom-smux".to_owned(), + protocol: crate::proxy_subscription::ProxyProtocol::Shadowsocks, + server: "127.0.0.1".to_owned(), + port: 8388, + warnings: Vec::new(), + config: ProxyNodeConfig::Shadowsocks(ShadowsocksNode { + cipher: "aes-192-gcm".to_owned(), + password: "pass".to_owned(), + udp: false, + udp_over_tcp: false, + udp_over_tcp_version: UOT_VERSION, + client_fingerprint: None, + plugin: None, + smux: Some(crate::proxy_subscription::ShadowsocksSmux { + enabled: true, + protocol: Some("smux".to_owned()), + max_connections: Some(2), + min_streams: None, + max_streams: Some(16), + padding: false, + statistic: false, + only_tcp: false, + brutal: None, + }), + }), + }; + + assert!(runtime_support_error(&node).is_none()); + } + + #[test] + fn runtime_support_accepts_aead2022_ccm_shadowsocks_smux() { + let kind = Aead2022AesCcmKind::Aes128; + let password = BASE64_STANDARD.encode(vec![3u8; kind.key_len()]); + let node = ProxyNode { + ordinal: 0, + name: "ss-2022-ccm-smux".to_owned(), + protocol: crate::proxy_subscription::ProxyProtocol::Shadowsocks, + server: "127.0.0.1".to_owned(), + port: 8388, + warnings: Vec::new(), + config: ProxyNodeConfig::Shadowsocks(ShadowsocksNode { + cipher: kind.name().to_owned(), + password, + udp: false, + udp_over_tcp: false, + udp_over_tcp_version: UOT_VERSION, + client_fingerprint: None, + plugin: None, + smux: Some(crate::proxy_subscription::ShadowsocksSmux { + enabled: true, + protocol: Some("smux".to_owned()), + max_connections: Some(2), + min_streams: None, + max_streams: Some(16), + padding: false, + statistic: false, + only_tcp: false, + brutal: None, + }), + }), + }; + + assert!(runtime_support_error(&node).is_none()); + } + + #[tokio::test] + async fn sing_mux_tcp_request_uses_mihomo_stream_header_shape() { + let (mut client, mut server) = tokio::io::duplex(64); + let target = ProxyTcpTarget { + address: "198.18.64.1:443".parse().unwrap(), + hostname: Some("example.com.".to_owned()), + }; + + let writer = + tokio::spawn(async move { write_sing_mux_tcp_request(&mut client, &target).await }); + let mut request = vec![0u8; 2 + 1 + 1 + "example.com".len() + 2]; + server.read_exact(&mut request).await.unwrap(); + writer.await.unwrap().unwrap(); + + let mut expected = vec![0, 0, 3, "example.com".len() as u8]; + expected.extend_from_slice(b"example.com"); + expected.extend_from_slice(&443u16.to_be_bytes()); + assert_eq!(request, expected); + } + + #[tokio::test] + async fn sing_mux_udp_request_uses_mihomo_packet_header_shape() { + let (mut client, mut server) = tokio::io::duplex(64); + let target: SocketAddr = "198.18.64.1:53".parse().unwrap(); + let writer = + tokio::spawn( + async move { write_sing_mux_udp_request(&mut client, target, b"query").await }, + ); + let mut request = vec![0u8; 2 + 1 + 4 + 2 + 2 + "query".len()]; + server.read_exact(&mut request).await.unwrap(); + writer.await.unwrap().unwrap(); + + let mut expected = vec![0, SING_MUX_STREAM_FLAG_UDP as u8, 1, 198, 18, 64, 1]; + expected.extend_from_slice(&53u16.to_be_bytes()); + expected.extend_from_slice(&5u16.to_be_bytes()); + expected.extend_from_slice(b"query"); + assert_eq!(request, expected); + } + + #[tokio::test] + async fn sing_mux_udp_payload_reads_status_then_length_prefixed_packet() { + let (mut client, mut server) = tokio::io::duplex(64); + let writer = tokio::spawn(async move { + server + .write_all(&[SING_MUX_STREAM_STATUS_SUCCESS]) + .await + .unwrap(); + server.write_all(&4u16.to_be_bytes()).await.unwrap(); + server.write_all(b"pong").await.unwrap(); + }); + let mut response_read = false; + let payload = read_sing_mux_udp_payload(&mut client, &mut response_read) + .await + .unwrap(); + writer.await.unwrap(); + assert!(response_read); + assert_eq!(&payload, b"pong"); + } + + #[test] + fn mihomo_bps_parser_matches_decimal_rate_strings() { + assert_eq!(mihomo_string_to_bps(""), 0); + assert_eq!(mihomo_string_to_bps("10"), 1_250_000); + assert_eq!(mihomo_string_to_bps("10 Mbps"), 1_250_000); + assert_eq!(mihomo_string_to_bps("10Mbps"), 1_250_000); + assert_eq!(mihomo_string_to_bps("10 MBps"), 10_000_000); + assert_eq!(mihomo_string_to_bps("1 Kbps"), 125); + assert_eq!(mihomo_string_to_bps("not-a-rate"), 0); + } + + #[tokio::test] + async fn sing_mux_brutal_request_uses_mihomo_exchange_shape() { + let (mut client, mut server) = tokio::io::duplex(128); + let writer = + tokio::spawn( + async move { write_sing_mux_brutal_request(&mut client, 2_500_000).await }, + ); + let mut request = vec![0u8; 2 + 1 + 1 + SING_MUX_BRUTAL_EXCHANGE_DOMAIN.len() + 2 + 8]; + server.read_exact(&mut request).await.unwrap(); + writer.await.unwrap().unwrap(); + + let mut expected = vec![0, 0, 3, SING_MUX_BRUTAL_EXCHANGE_DOMAIN.len() as u8]; + expected.extend_from_slice(SING_MUX_BRUTAL_EXCHANGE_DOMAIN.as_bytes()); + expected.extend_from_slice(&0u16.to_be_bytes()); + expected.extend_from_slice(&2_500_000u64.to_be_bytes()); + assert_eq!(request, expected); + } + + #[tokio::test] + async fn sing_mux_brutal_response_reads_stream_status_then_server_rate() { + let (mut client, mut server) = tokio::io::duplex(64); + let writer = tokio::spawn(async move { + server + .write_all(&[SING_MUX_STREAM_STATUS_SUCCESS, 1]) + .await + .unwrap(); + server.write_all(&3_000_000u64.to_be_bytes()).await.unwrap(); + }); + let receive_bps = read_sing_mux_brutal_response(&mut client).await.unwrap(); + writer.await.unwrap(); + assert_eq!(receive_bps, 3_000_000); + + let (mut client, mut server) = tokio::io::duplex(64); + let writer = tokio::spawn(async move { + server + .write_all(&[SING_MUX_STREAM_STATUS_SUCCESS, 0, 4]) + .await + .unwrap(); + server.write_all(b"oops").await.unwrap(); + }); + let err = read_sing_mux_brutal_response(&mut client) + .await + .unwrap_err(); + writer.await.unwrap(); + assert!(err.to_string().contains("oops"), "{err}"); + } + + #[tokio::test] + async fn sing_mux_session_preface_uses_mihomo_padding_shape() { + let (mut client, mut server) = tokio::io::duplex(1024); + let writer = tokio::spawn(async move { + write_sing_mux_session_preface(&mut client, ShadowsocksSingMuxProtocol::Smux, false) + .await + }); + let mut plain = [0u8; 2]; + server.read_exact(&mut plain).await.unwrap(); + writer.await.unwrap().unwrap(); + assert_eq!(plain, [SING_MUX_VERSION_0, SING_MUX_PROTOCOL_SMUX]); + + let (mut client, mut server) = tokio::io::duplex(1024); + let writer = tokio::spawn(async move { + write_sing_mux_session_preface(&mut client, ShadowsocksSingMuxProtocol::Yamux, false) + .await + }); + let mut plain = [0u8; 2]; + server.read_exact(&mut plain).await.unwrap(); + writer.await.unwrap().unwrap(); + assert_eq!(plain, [SING_MUX_VERSION_0, SING_MUX_PROTOCOL_YAMUX]); + + let (mut client, mut server) = tokio::io::duplex(1024); + let writer = tokio::spawn(async move { + write_sing_mux_session_preface(&mut client, ShadowsocksSingMuxProtocol::H2Mux, false) + .await + }); + let mut plain = [0u8; 2]; + server.read_exact(&mut plain).await.unwrap(); + writer.await.unwrap().unwrap(); + assert_eq!(plain, [SING_MUX_VERSION_0, SING_MUX_PROTOCOL_H2MUX]); + + let (mut client, mut server) = tokio::io::duplex(2048); + let writer = tokio::spawn(async move { + write_sing_mux_session_preface(&mut client, ShadowsocksSingMuxProtocol::Smux, true) + .await + }); + let mut header = [0u8; 5]; + server.read_exact(&mut header).await.unwrap(); + assert_eq!( + &header[..3], + &[SING_MUX_VERSION_1, SING_MUX_PROTOCOL_SMUX, 1] + ); + let padding_len = u16::from_be_bytes([header[3], header[4]]) as usize; + assert!(padding_len >= SING_MUX_MIN_PADDING_LEN); + assert!(padding_len < SING_MUX_MIN_PADDING_LEN + SING_MUX_PADDING_LEN_RANGE); + let mut padding = vec![0u8; padding_len]; + server.read_exact(&mut padding).await.unwrap(); + writer.await.unwrap().unwrap(); + assert!(padding.iter().all(|byte| *byte == 0)); + } + + #[tokio::test] + async fn sing_mux_yamux_session_opens_mihomo_style_streams() { + let (client, server) = tokio::io::duplex(4096); + let session = start_sing_mux_yamux_session(Box::new(client)) + .await + .unwrap(); + let mut server_connection = yamux::Connection::new( + server.compat(), + yamux::Config::default(), + yamux::Mode::Server, + ); + let (stream_tx, mut stream_rx) = mpsc::channel(1); + let server_driver = tokio::spawn(async move { + let mut stream_tx = Some(stream_tx); + std::future::poll_fn(move |cx| loop { + match server_connection.poll_next_inbound(cx) { + Poll::Ready(Some(Ok(stream))) => { + if let Some(sender) = stream_tx.take() { + let _ = sender.try_send(stream.compat()); + } + } + Poll::Ready(Some(Err(err))) => return Poll::Ready(Err(anyhow!(err))), + Poll::Ready(None) => return Poll::Ready(Ok(())), + Poll::Pending => return Poll::Pending, + } + }) + .await + }); + + let mut client_stream = session.open_stream().await.unwrap(); + client_stream.write_all(b"ping").await.unwrap(); + let mut server_stream = stream_rx.recv().await.unwrap(); + let mut request = [0u8; 4]; + server_stream.read_exact(&mut request).await.unwrap(); + assert_eq!(&request, b"ping"); + + drop(client_stream); + assert_eq!(session.num_streams().await, 0); + server_driver.abort(); + } + + #[tokio::test] + async fn sing_mux_h2mux_session_opens_mihomo_style_connect_streams() { + let (client, server) = tokio::io::duplex(4096); + let session = start_sing_mux_h2mux_session(Box::new(client)) + .await + .unwrap(); + let (done_tx, mut done_rx) = oneshot::channel(); + let server_driver = tokio::spawn(async move { + let mut server = h2::server::handshake(server).await?; + let Some(request) = server.accept().await else { + bail!("h2mux server closed before receiving CONNECT stream"); + }; + let (request, mut respond) = request?; + assert_eq!(request.method(), Method::CONNECT); + assert_eq!(request.uri().to_string(), "localhost"); + + let mut body = request.into_body(); + let response = http::Response::builder() + .status(StatusCode::OK) + .body(()) + .unwrap(); + let mut send = respond.send_response(response, false)?; + let data = body.data().await.unwrap()?; + body.flow_control().release_capacity(data.len())?; + assert_eq!(&data[..], b"ping"); + send.reserve_capacity(4); + std::future::poll_fn(|cx| send.poll_capacity(cx)) + .await + .context("h2mux test response stream closed before capacity")??; + send.send_data(Bytes::from_static(b"pong"), true)?; + loop { + tokio::select! { + _ = &mut done_rx => break, + accepted = server.accept() => { + if accepted.is_none() { + break; + } + } + } + } + Ok::<_, anyhow::Error>(()) + }); + + let mut client_stream = session.open_stream().await.unwrap(); + client_stream.write_all(b"ping").await.unwrap(); + let mut response = [0u8; 4]; + client_stream.read_exact(&mut response).await.unwrap(); + assert_eq!(&response, b"pong"); + let _ = done_tx.send(()); + + drop(client_stream); + assert_eq!(session.num_streams().await, 0); + server_driver.await.unwrap().unwrap(); + } + + #[tokio::test] + async fn sing_mux_padding_stream_frames_first_reads_and_writes() { + let (client, mut server) = tokio::io::duplex(4096); + let mut stream = sing_mux_padding_stream(Box::new(client)); + + stream.write_all(b"ping").await.unwrap(); + let mut header = [0u8; 4]; + server.read_exact(&mut header).await.unwrap(); + let payload_len = u16::from_be_bytes([header[0], header[1]]) as usize; + let padding_len = u16::from_be_bytes([header[2], header[3]]) as usize; + assert_eq!(payload_len, 4); + assert!(padding_len >= SING_MUX_MIN_PADDING_LEN); + assert!(padding_len < SING_MUX_MIN_PADDING_LEN + SING_MUX_PADDING_LEN_RANGE); + let mut payload = vec![0u8; payload_len]; + server.read_exact(&mut payload).await.unwrap(); + assert_eq!(&payload, b"ping"); + let mut padding = vec![0u8; padding_len]; + server.read_exact(&mut padding).await.unwrap(); + + let response_padding = SING_MUX_MIN_PADDING_LEN; + server.write_all(&4u16.to_be_bytes()).await.unwrap(); + server + .write_all(&(response_padding as u16).to_be_bytes()) + .await + .unwrap(); + server.write_all(b"pong").await.unwrap(); + server + .write_all(&vec![0u8; response_padding]) + .await + .unwrap(); + + let mut response = [0u8; 4]; + stream.read_exact(&mut response).await.unwrap(); + assert_eq!(&response, b"pong"); + } + + #[tokio::test] + async fn sing_mux_stream_response_accepts_success_and_reports_remote_errors() { + let (mut client, mut server) = tokio::io::duplex(64); + let success = tokio::spawn(async move { + server + .write_all(&[SING_MUX_STREAM_STATUS_SUCCESS]) + .await + .unwrap(); + }); + read_sing_mux_stream_response(&mut client).await.unwrap(); + success.await.unwrap(); + + let (mut client, mut server) = tokio::io::duplex(64); + let failure = tokio::spawn(async move { + server + .write_all(&[SING_MUX_STREAM_STATUS_ERROR, 4, b'o', b'o', b'p', b's']) + .await + .unwrap(); + }); + let err = read_sing_mux_stream_response(&mut client) + .await + .unwrap_err(); + failure.await.unwrap(); + assert!(err.to_string().contains("oops"), "{err}"); + } + + #[tokio::test] + async fn sing_mux_tcp_stream_reads_lazy_success_status_before_payload() { + let (client, mut server) = tokio::io::duplex(64); + let mut stream = SingMuxTcpStream::new(client); + let writer = tokio::spawn(async move { + server + .write_all(&[SING_MUX_STREAM_STATUS_SUCCESS, b'o', b'k']) + .await + .unwrap(); + }); + + let mut payload = [0u8; 2]; + stream.read_exact(&mut payload).await.unwrap(); + writer.await.unwrap(); + assert_eq!(&payload, b"ok"); + + let (client, mut server) = tokio::io::duplex(64); + let mut stream = SingMuxTcpStream::new(client); + let writer = tokio::spawn(async move { + server + .write_all(&[SING_MUX_STREAM_STATUS_ERROR]) + .await + .unwrap(); + }); + let err = stream.read_exact(&mut payload).await.unwrap_err(); + writer.await.unwrap(); + assert!(err.to_string().contains("remote error"), "{err}"); + } + + #[tokio::test] + async fn custom_aead_plain_tcp_stream_exposes_plaintext_for_sing_mux() { + let kind = CustomSsAeadKind::Aes192Gcm; + let key: Arc<[u8]> = Arc::from(vec![7u8; kind.key_len()]); + let (client_tcp, server_tcp) = tokio::io::duplex(4096); + let target = ProxyTcpTarget { + address: "198.18.64.1:443".parse().unwrap(), + hostname: Some("example.com.".to_owned()), + }; + let mut plain = custom_aead_plain_tcp_stream(client_tcp, &target, kind, key.clone()) + .await + .unwrap(); + let (server_reader, server_writer) = split(server_tcp); + let mut ss_reader = CustomSsAeadReader::new(server_reader, kind, key.clone()); + let mut ss_writer = CustomSsAeadWriter::new(server_writer, kind, key) + .await + .unwrap(); + + let mut received = Vec::new(); + ss_reader.read_chunk(&mut received).await.unwrap(); + assert_eq!(received, socks_domain_addr("example.com", 443).unwrap()); + + plain.write_all(b"ping").await.unwrap(); + received.clear(); + ss_reader.read_chunk(&mut received).await.unwrap(); + assert_eq!(received, b"ping"); + + ss_writer.write_chunk(b"pong").await.unwrap(); + let mut response = [0u8; 4]; + plain.read_exact(&mut response).await.unwrap(); + assert_eq!(&response, b"pong"); + } + + #[tokio::test] + async fn custom_stream_plain_tcp_stream_exposes_plaintext_for_sing_mux() { + let kind = CustomSsStreamKind::XChacha20; + let key: Arc<[u8]> = Arc::from(vec![9u8; kind.key_len()]); + let (client_tcp, server_tcp) = tokio::io::duplex(4096); + let target = ProxyTcpTarget { + address: "198.18.64.1:443".parse().unwrap(), + hostname: Some("example.com.".to_owned()), + }; + let mut plain = custom_stream_plain_tcp_stream(client_tcp, &target, kind, key.clone()) + .await + .unwrap(); + let (server_reader, server_writer) = split(server_tcp); + let mut ss_reader = CustomSsStreamReader::new(server_reader, kind, key.clone()); + let mut ss_writer = CustomSsStreamWriter::new(server_writer, kind, key) + .await + .unwrap(); + + let mut received = vec![0u8; 64]; + let n = ss_reader.read_decrypted(&mut received).await.unwrap(); + assert_eq!( + &received[..n], + socks_domain_addr("example.com", 443).unwrap().as_slice() + ); + + plain.write_all(b"ping").await.unwrap(); + let n = ss_reader.read_decrypted(&mut received).await.unwrap(); + assert_eq!(&received[..n], b"ping"); + + ss_writer.write_all_encrypted(b"pong").await.unwrap(); + let mut response = [0u8; 4]; + plain.read_exact(&mut response).await.unwrap(); + assert_eq!(&response, b"pong"); + } + + #[tokio::test] + async fn aead2022_aes_ccm_plain_tcp_stream_exposes_plaintext_for_sing_mux() { + let kind = Aead2022AesCcmKind::Aes128; + let user_key: Arc<[u8]> = Arc::from(vec![11u8; kind.key_len()]); + let identity_keys: Arc<[Arc<[u8]>]> = Arc::from(Vec::>::new()); + let (client_tcp, mut server_tcp) = tokio::io::duplex(4096); + let target = ProxyTcpTarget { + address: "198.18.64.1:443".parse().unwrap(), + hostname: Some("example.com.".to_owned()), + }; + let mut plain = aead2022_aes_ccm_plain_tcp_stream( + client_tcp, + &target, + kind, + user_key.clone(), + identity_keys, + ) + .await + .unwrap(); + + let (request_salt, mut request_cipher, variable) = + read_aead2022_tcp_request_for_test(&mut server_tcp, kind, &user_key) + .await + .unwrap(); + let expected_target = socks_domain_addr("example.com", 443).unwrap(); + assert!(variable.starts_with(&expected_target)); + let padding_len_offset = expected_target.len(); + let padding_len = u16::from_be_bytes([ + variable[padding_len_offset], + variable[padding_len_offset + 1], + ]) as usize; + assert_eq!(padding_len, 1); + + plain.write_all(b"ping").await.unwrap(); + let payload = read_aead2022_tcp_chunk_for_test(&mut server_tcp, kind, &mut request_cipher) + .await + .unwrap(); + assert_eq!(payload, b"ping"); + + let mut response_cipher = write_aead2022_tcp_response_for_test( + &mut server_tcp, + kind, + &user_key, + request_salt.as_ref(), + ) + .await + .unwrap(); + write_aead2022_tcp_chunk_for_test(&mut server_tcp, &mut response_cipher, b"pong") + .await + .unwrap(); + + let mut response = [0u8; 4]; + plain.read_exact(&mut response).await.unwrap(); + assert_eq!(&response, b"pong"); + } + + async fn read_aead2022_tcp_request_for_test( + reader: &mut R, + kind: Aead2022AesCcmKind, + user_key: &[u8], + ) -> Result<(Arc<[u8]>, Aead2022TcpCipher, Vec)> + where + R: AsyncRead + Unpin, + { + let mut salt = vec![0u8; kind.key_len()]; + reader.read_exact(&mut salt).await?; + let mut cipher = Aead2022TcpCipher::new(kind, user_key, &salt)?; + let mut encrypted_fixed = vec![0u8; 1 + 8 + 2 + kind.tag_len()]; + reader.read_exact(&mut encrypted_fixed).await?; + let fixed = cipher.open_packet(&mut encrypted_fixed)?; + assert_eq!(fixed[0], AEAD2022_HEADER_TYPE_CLIENT); + aead2022_validate_timestamp(u64::from_be_bytes( + fixed[1..9].try_into().expect("fixed timestamp"), + ))?; + let variable_len = u16::from_be_bytes([fixed[9], fixed[10]]) as usize; + let mut encrypted_variable = vec![0u8; variable_len + kind.tag_len()]; + reader.read_exact(&mut encrypted_variable).await?; + let variable = cipher.open_packet(&mut encrypted_variable)?.to_vec(); + Ok((Arc::from(salt), cipher, variable)) + } + + async fn read_aead2022_tcp_chunk_for_test( + reader: &mut R, + kind: Aead2022AesCcmKind, + cipher: &mut Aead2022TcpCipher, + ) -> Result> + where + R: AsyncRead + Unpin, + { + let mut encrypted_len = vec![0u8; 2 + kind.tag_len()]; + reader.read_exact(&mut encrypted_len).await?; + let len = cipher.open_packet(&mut encrypted_len)?; + let payload_len = u16::from_be_bytes([len[0], len[1]]) as usize; + let mut encrypted_payload = vec![0u8; payload_len + kind.tag_len()]; + reader.read_exact(&mut encrypted_payload).await?; + Ok(cipher.open_packet(&mut encrypted_payload)?.to_vec()) + } + + async fn write_aead2022_tcp_response_for_test( + writer: &mut W, + kind: Aead2022AesCcmKind, + user_key: &[u8], + request_salt: &[u8], + ) -> Result + where + W: AsyncWrite + Unpin, + { + let response_salt = vec![13u8; kind.key_len()]; + let mut cipher = Aead2022TcpCipher::new(kind, user_key, &response_salt)?; + let mut fixed = Vec::with_capacity(1 + 8 + kind.key_len() + 2); + fixed.push(AEAD2022_HEADER_TYPE_SERVER); + fixed.extend_from_slice(&aead2022_now_timestamp()?.to_be_bytes()); + fixed.extend_from_slice(request_salt); + fixed.extend_from_slice(&0u16.to_be_bytes()); + writer.write_all(&response_salt).await?; + writer.write_all(&cipher.seal_packet(&fixed)?).await?; + writer.flush().await?; + Ok(cipher) + } + + async fn write_aead2022_tcp_chunk_for_test( + writer: &mut W, + cipher: &mut Aead2022TcpCipher, + payload: &[u8], + ) -> Result<()> + where + W: AsyncWrite + Unpin, + { + writer + .write_all(&cipher.seal_packet(&(payload.len() as u16).to_be_bytes())?) + .await?; + writer.write_all(&cipher.seal_packet(payload)?).await?; + writer.flush().await?; + Ok(()) + } + + #[test] + fn shadowsocks_udp_over_tcp_enables_udp_session_path() { + fn shadowsocks_node( + name: &str, + udp: bool, + udp_over_tcp: bool, + plugin: Option, + ) -> ProxyNode { + ProxyNode { + ordinal: 0, + name: name.to_owned(), + protocol: crate::proxy_subscription::ProxyProtocol::Shadowsocks, + server: "127.0.0.1".to_owned(), + port: 8388, + warnings: Vec::new(), + config: ProxyNodeConfig::Shadowsocks(ShadowsocksNode { + cipher: "aes-128-gcm".to_owned(), + password: "pass".to_owned(), + udp, + udp_over_tcp, + udp_over_tcp_version: UOT_LEGACY_VERSION, + client_fingerprint: None, + plugin, + smux: None, + }), + } + } + + let uot_node = shadowsocks_node("ss-uot-without-native-udp", false, true, None); + let ProxyNodeConfig::Shadowsocks(config) = &uot_node.config else { + panic!("expected Shadowsocks config"); + }; + let outbound = ShadowsocksOutbound::from_node(&uot_node, config).unwrap(); + assert!(outbound.udp_enabled); + assert!(outbound.udp_over_tcp); + + let native_udp_node = shadowsocks_node("ss-native-udp-disabled", false, false, None); + let ProxyNodeConfig::Shadowsocks(config) = &native_udp_node.config else { + panic!("expected Shadowsocks config"); + }; + let outbound = ShadowsocksOutbound::from_node(&native_udp_node, config).unwrap(); + assert!(!outbound.udp_enabled); + assert!(!outbound.udp_over_tcp); + + let kcptun_node = shadowsocks_node( + "ss-kcptun-forced-uot", + false, + false, + Some(ShadowsocksPlugin { + name: "kcptun".to_owned(), + opts: HashMap::new(), + }), + ); + let ProxyNodeConfig::Shadowsocks(config) = &kcptun_node.config else { + panic!("expected Shadowsocks config"); + }; + let outbound = ShadowsocksOutbound::from_node(&kcptun_node, config).unwrap(); + assert!(outbound.udp_enabled); + assert!(outbound.udp_over_tcp); + } + + #[test] + fn runtime_support_accepts_tls13_and_tls12_restls() { + fn restls_node(version_hint: &str) -> ProxyNode { + ProxyNode { + ordinal: 0, + name: format!("ss-restls-{version_hint}"), + protocol: crate::proxy_subscription::ProxyProtocol::Shadowsocks, + server: "proxy.example".to_owned(), + port: 443, + warnings: Vec::new(), + config: ProxyNodeConfig::Shadowsocks(ShadowsocksNode { + cipher: "aes-128-gcm".to_owned(), + password: "ss-secret".to_owned(), + udp: true, + udp_over_tcp: false, + udp_over_tcp_version: UOT_VERSION, + client_fingerprint: None, + plugin: Some(ShadowsocksPlugin { + name: "restls".to_owned(), + opts: HashMap::from([ + ("host".to_owned(), "cover.example".to_owned()), + ("password".to_owned(), "restls-secret".to_owned()), + ("version-hint".to_owned(), version_hint.to_owned()), + ]), + }), + smux: None, + }), + } + } + + assert!(runtime_support_error(&restls_node("tls13")).is_none()); + assert!(runtime_support_error(&restls_node("tls12")).is_none()); + } + + #[test] + fn runtime_support_rejects_shadowsocks_tls_client_fingerprint() { + fn tls_plugin_node(plugin: ShadowsocksPlugin) -> ProxyNode { + ProxyNode { + ordinal: 0, + name: "ss-tls-fingerprint".to_owned(), + protocol: crate::proxy_subscription::ProxyProtocol::Shadowsocks, + server: "proxy.example".to_owned(), + port: 443, + warnings: Vec::new(), + config: ProxyNodeConfig::Shadowsocks(ShadowsocksNode { + cipher: "aes-128-gcm".to_owned(), + password: "ss-secret".to_owned(), + udp: true, + udp_over_tcp: false, + udp_over_tcp_version: UOT_VERSION, + client_fingerprint: Some("firefox".to_owned()), + plugin: Some(plugin), + smux: None, + }), + } + } + + let shadow_tls = ShadowsocksPlugin { + name: "shadow-tls".to_owned(), + opts: HashMap::from([ + ("host".to_owned(), "cover.example".to_owned()), + ("password".to_owned(), "secret".to_owned()), + ("version".to_owned(), "3".to_owned()), + ]), + }; + let restls = ShadowsocksPlugin { + name: "restls".to_owned(), + opts: HashMap::from([ + ("host".to_owned(), "cover.example".to_owned()), + ("password".to_owned(), "restls-secret".to_owned()), + ("version-hint".to_owned(), "tls13".to_owned()), + ]), + }; + + #[cfg(not(feature = "boring-browser-fingerprints"))] + let rejected_nodes = [ + tls_plugin_node(shadow_tls.clone()), + tls_plugin_node(restls.clone()), + ]; + #[cfg(feature = "boring-browser-fingerprints")] + let rejected_nodes: [ProxyNode; 0] = []; + + for node in rejected_nodes { + let err = runtime_support_error(&node) + .expect("uTLS client-fingerprint is not runtime-supported yet"); + assert!(err.contains("client-fingerprint firefox"), "{err}"); + assert!(err.contains("uTLS"), "{err}"); + } + + #[cfg(feature = "boring-browser-fingerprints")] + assert!(runtime_support_error(&tls_plugin_node(restls)).is_none()); + #[cfg(feature = "boring-browser-fingerprints")] + assert!(runtime_support_error(&tls_plugin_node(shadow_tls)).is_none()); + + let shadow_tls_v1 = tls_plugin_node(ShadowsocksPlugin { + name: "shadow-tls".to_owned(), + opts: HashMap::from([ + ("host".to_owned(), "cover.example".to_owned()), + ("version".to_owned(), "1".to_owned()), + ]), + }); + assert!(runtime_support_error(&shadow_tls_v1).is_none()); + + let mut none_node = tls_plugin_node(ShadowsocksPlugin { + name: "shadow-tls".to_owned(), + opts: HashMap::from([ + ("host".to_owned(), "cover.example".to_owned()), + ("password".to_owned(), "secret".to_owned()), + ("version".to_owned(), "3".to_owned()), + ]), + }); + if let ProxyNodeConfig::Shadowsocks(shadowsocks) = &mut none_node.config { + shadowsocks.client_fingerprint = Some("none".to_owned()); + } + assert!(runtime_support_error(&none_node).is_none()); + + let mut unknown_node = none_node; + if let ProxyNodeConfig::Shadowsocks(shadowsocks) = &mut unknown_node.config { + shadowsocks.client_fingerprint = Some("unknown-browser".to_owned()); + } + assert!(runtime_support_error(&unknown_node).is_none()); + } + + #[cfg(feature = "boring-browser-fingerprints")] + const TEST_CLIENT_CERTIFICATE_PEM: &str = r#"-----BEGIN CERTIFICATE----- +MIIDDTCCAfWgAwIBAgIUcQjiH+9SQYcKZJBHGs3/ymabzqYwDQYJKoZIhvcNAQEL +BQAwFjEUMBIGA1UEAwwLYnVycm93LXRlc3QwHhcNMjYwNjAxMTUwMjA5WhcNMjYw +NjAyMTUwMjA5WjAWMRQwEgYDVQQDDAtidXJyb3ctdGVzdDCCASIwDQYJKoZIhvcN +AQEBBQADggEPADCCAQoCggEBAOahzwpBhDKatLPvsnSjIQGdb9jq34dGt5Yu2znj +PAL4aTI9mFsid5L7RlHJTV1MSRsYddrLTCchDZl46hEArRQCQRf/kGiHIqJWcYDt +7gJaQHZXNTasQ9CWEoC0X+Jqv89lf9q9caMYye9ZXICeRbDxhCrQSEfY1rxXtN3i +iqJFcxmaUdzMXjgfIyMlFCHdcYtGiITZQEVizI1EelFr0RyxDuE+9IEvokpgbI2y +PnMirWVoeOKyaWFbJyPR3qSKuDF1jYrtsluW7IkTHF0B8jvbg6IJNSV7r2JaiQhD +RrgPxvXXHvfPjoZbCqAMN6JarRnWkw58uuNCr+3OxZygUkECAwEAAaNTMFEwHQYD +VR0OBBYEFI2Pv9COWSRHYwScAy17EP8MUr6uMB8GA1UdIwQYMBaAFI2Pv9COWSRH +YwScAy17EP8MUr6uMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEB +AHgOvphtyX9CO/ibtA8KKcH9puWeV7CJLoqHW9XU5ZvdawI01IEdn8oIn5IAzurj +X7DAqaBUvzc58EB9cmnGnGhZZJMHBE2PVlxea1EcB5geWV+y6lIM7peqP+XGsAJo +32pUr2W+Sl3IMf6FkXy4VneZCkGlOgnxrEB9ULm1foLGQtTkXTT3dqT098Gqs3ip +HC37IQ6Bqg3/TseRUpzAJ8RVV8qvyJlYvrPvYqYRMl8xHRS2Gj5q2FvxuKlCWP/t +3oXNpvfjecQFMpghL3BcMoQUq2kMLwOz5sAUFxjP6pQf6K1kB98u1NgkzF8VXgUU +0dEYKIH7JFYVNeMzscI+9cM= +-----END CERTIFICATE-----"#; + + #[cfg(feature = "boring-browser-fingerprints")] + const TEST_CLIENT_PRIVATE_KEY_PEM: &str = r#"-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDmoc8KQYQymrSz +77J0oyEBnW/Y6t+HRreWLts54zwC+GkyPZhbIneS+0ZRyU1dTEkbGHXay0wnIQ2Z +eOoRAK0UAkEX/5BohyKiVnGA7e4CWkB2VzU2rEPQlhKAtF/iar/PZX/avXGjGMnv +WVyAnkWw8YQq0EhH2Na8V7Td4oqiRXMZmlHczF44HyMjJRQh3XGLRoiE2UBFYsyN +RHpRa9EcsQ7hPvSBL6JKYGyNsj5zIq1laHjismlhWycj0d6kirgxdY2K7bJbluyJ +ExxdAfI724OiCTUle69iWokIQ0a4D8b11x73z46GWwqgDDeiWq0Z1pMOfLrjQq/t +zsWcoFJBAgMBAAECggEADce+8wTCzTKWN9F2JGb3hWeunlORAz2z9tAaPuViGWar +WRpPZ1PyDhZfzL+OP6lbuDfoGX3Kho9V3ITaLiWQBzaH732l12u0+OXZBqmbbjON ++iQwJJylY/9biqonDS8bV9003FEzytl5KLPPsEsErKkO9cSX1Qcn9Cf6FsSUS6ll +SIq5iKPYtFIKOV7U8Bu1MqiMx9eKIpCcHVKNlP4uConpsq/TPc0pouSIkT8okdt0 +JxAAvNo+YvqkqbK7+t5rCvaG8csT+Uw6osa0GcupKmXWbVbIeMWjdHJNqBfinmRF +U62sGDzkVDdj1HroBNf9Ns9sovET29UgX5z6oj9WuwKBgQD1OTsDgrfkoWaXQuJR +8Vm5SILE+vKWlAXAyJrN6Dp+jbUNaV98kYWziTg4ngftjBPjUf0PYyZUmYo55+Os +LALCEjc7axgFSAjIvlH+WlfLChAGOAcOyLA1QzSn1G6f31OlMUFu9GRmGsZf5Ufc +wMn/MKQbAxTrOtmYyUmxztYnRwKBgQDwxGx2odDVD2NnE03427wQfnKETkS31DQb +7mp7uZDVg21Dr+5V0tmuxRywQFtSVDNo8sxB4REozbhCibDJcvWD0hAd/8FsxAuU +AkeXjfwaneSrw8b0JoEK/9aEkm0rin+XN/CmmCCPWNjiIguG/klL2YfXkSv71fpw +if4PgcgONwKBgQCJQVFAs8fOFnDftTYL+3Tm+ikHrBZgJdXag+3x1kv3TcXLDfG+ +PY2CYgmv1vRFB6SSFe/4ztxDefUeWCbc1X1ttthnT5gQTLNt+OjX3yVIpgc2E+IP +alEGXul4DrUkktG0oo8nVW9knxPt1N2WN+pYBZe07tKknznwBKpU9Zp0PQKBgC1w +1Qu61KAxrFAa659pUWBHjTN9VijfywnugHhjeHtjt66LuM7H4b/Dgfud2d5698z5 +7iUM5mEuGnWsaQpMQRwk/Fe9GnN9uLWxjHOFH6yiWjM02wrfbYF28bTJsgMCu7v9 +mdTHZ3XGjgB37ncG7Sx8nM/JnWSFaSPuV13z358XAoGAIuigV+9koumwqNd2ueIQ +uCWixSkOVwRHPiWG4iyWku9RpspR+Sw23gUbQ45J7u326Aw0P3oHDme2bxgsvME6 +ld+z0BdOLxyLEw55UtQrJkFNCbGsNFHRs774PuZdfExfzcd9d+56vYcVb49PSBb6 +OW4mfN8jmXq6wTtOv85mBGE= +-----END PRIVATE KEY-----"#; + + #[cfg(feature = "boring-browser-fingerprints")] + #[test] + fn boring_client_identity_loads_inline_pem() { + let auth = load_boring_client_identity(&TlsClientIdentity { + certificate: TEST_CLIENT_CERTIFICATE_PEM.to_owned(), + private_key: TEST_CLIENT_PRIVATE_KEY_PEM.to_owned(), + }) + .expect("inline PEM client identity should load for BoringSSL"); + + assert_eq!(auth.cert_chain.len(), 1); + } + + #[cfg(feature = "boring-browser-fingerprints")] + #[test] + fn shadow_tls_random_fingerprint_is_mihomo_style_initial_choice() { + let first = mihomo_initial_random_fingerprint(); + assert!(matches!( + first, + MihomoClientFingerprint::Chrome + | MihomoClientFingerprint::Safari + | MihomoClientFingerprint::Ios + | MihomoClientFingerprint::Firefox + )); + for _ in 0..16 { + assert_eq!(mihomo_initial_random_fingerprint(), first); + } + } + + #[cfg(feature = "boring-browser-fingerprints")] + #[test] + fn shadow_tls_v2_browser_profile_removes_x25519_mlkem768() { + let mut config = rama_net::tls::client::ClientConfig { + extensions: Some(vec![RamaClientHelloExtension::SupportedGroups(vec![ + RamaSupportedGroup::X25519MLKEM768, + RamaSupportedGroup::X25519, + ])]), + ..Default::default() + }; + + strip_x25519_mlkem768_from_client_config(&mut config); + + let extensions = config.extensions.expect("extensions should remain present"); + let RamaClientHelloExtension::SupportedGroups(groups) = &extensions[0] else { + panic!("expected supported groups extension"); + }; + assert_eq!(groups, &[RamaSupportedGroup::X25519]); + } + + #[cfg(feature = "boring-browser-fingerprints")] + #[test] + fn shadow_tls_browser_profiles_cover_mihomo_safari16_alias() { + let profile = mihomo_browser_user_agent_profile(MihomoClientFingerprint::Safari16) + .expect("safari16 should map to an embedded browser TLS profile"); + + assert_eq!(profile.ua_kind, UserAgentKind::Safari); + assert_eq!(profile.platform, Some(PlatformKind::MacOS)); + assert!(profile + .ua_str() + .is_some_and(|ua| ua.contains("Version/16."))); + assert!(MihomoClientFingerprint::Safari16.shadow_tls_v2_boring_supported()); + } + + #[test] + fn shadowsocks_runtime_accepts_simple_obfs_plugins() { + let plugin = ShadowsocksPlugin { + name: "obfs".to_owned(), + opts: HashMap::from([ + ("mode".to_owned(), "http".to_owned()), + ("host".to_owned(), "front.example".to_owned()), + ]), + }; + assert_eq!( + shadowsocks_plugin_transport(Some(&plugin), None).unwrap(), + Some(ShadowsocksPluginTransport::SimpleObfs { + mode: SimpleObfsMode::Http, + host: "front.example".to_owned() + }) + ); + + let uri_normalized_plugin = ShadowsocksPlugin { + name: "obfs".to_owned(), + opts: HashMap::from([ + ("obfs".to_owned(), "tls".to_owned()), + ("obfs-host".to_owned(), "cdn.example".to_owned()), + ]), + }; + assert_eq!( + shadowsocks_plugin_transport(Some(&uri_normalized_plugin), None).unwrap(), + Some(ShadowsocksPluginTransport::SimpleObfs { + mode: SimpleObfsMode::Tls, + host: "cdn.example".to_owned() + }) + ); + + let yaml_simple_obfs_alias = ShadowsocksPlugin { + name: "obfs-local".to_owned(), + opts: HashMap::from([ + ("obfs".to_owned(), "tls".to_owned()), + ("obfs-host".to_owned(), "cdn.example".to_owned()), + ]), + }; + assert_eq!( + shadowsocks_plugin_transport(Some(&yaml_simple_obfs_alias), None).unwrap(), + None + ); + + let v2ray = ShadowsocksPlugin { + name: "v2ray-plugin".to_owned(), + opts: HashMap::from([ + ("mode".to_owned(), "websocket".to_owned()), + ("path".to_owned(), "/ws".to_owned()), + ]), + }; + assert_eq!( + shadowsocks_plugin_transport(Some(&v2ray), None).unwrap(), + Some(ShadowsocksPluginTransport::WebSocket( + ShadowsocksWebSocketTransport { + kind: ShadowsocksWebSocketKind::V2ray, + host: "bing.com".to_owned(), + path: "/ws".to_owned(), + headers: Vec::new(), + tls: false, + ech: None, + skip_cert_verify: false, + certificate_fingerprint: None, + client_identity: None, + mux: ShadowsocksWebSocketMux::V2ray, + v2ray_http_upgrade: false, + v2ray_http_upgrade_fast_open: false, + } + )) + ); + + let v2ray_uri_aliases = ShadowsocksPlugin { + name: "v2ray-plugin".to_owned(), + opts: HashMap::from([ + ("obfs".to_owned(), "websocket".to_owned()), + ("obfs-host".to_owned(), "cdn.example".to_owned()), + ("path".to_owned(), "/ws".to_owned()), + ("tls".to_owned(), "true".to_owned()), + ]), + }; + assert_eq!( + shadowsocks_plugin_transport(Some(&v2ray_uri_aliases), None).unwrap(), + Some(ShadowsocksPluginTransport::WebSocket( + ShadowsocksWebSocketTransport { + kind: ShadowsocksWebSocketKind::V2ray, + host: "cdn.example".to_owned(), + path: "/ws".to_owned(), + headers: Vec::new(), + tls: true, + ech: None, + skip_cert_verify: false, + certificate_fingerprint: None, + client_identity: None, + mux: ShadowsocksWebSocketMux::V2ray, + v2ray_http_upgrade: false, + v2ray_http_upgrade_fast_open: false, + } + )) + ); + + let v2ray_fast_open = ShadowsocksPlugin { + name: "v2ray-plugin".to_owned(), + opts: HashMap::from([ + ("mode".to_owned(), "websocket".to_owned()), + ("v2ray-http-upgrade".to_owned(), "true".to_owned()), + ("v2ray-http-upgrade-fast-open".to_owned(), "true".to_owned()), + ]), + }; + let Some(ShadowsocksPluginTransport::WebSocket(v2ray_fast_open)) = + shadowsocks_plugin_transport(Some(&v2ray_fast_open), None).unwrap() + else { + panic!("expected websocket plugin transport"); + }; + assert!(v2ray_fast_open.v2ray_http_upgrade); + assert!(v2ray_fast_open.v2ray_http_upgrade_fast_open); + + let v2ray_mtls = ShadowsocksPlugin { + name: "v2ray-plugin".to_owned(), + opts: HashMap::from([ + ("mode".to_owned(), "websocket".to_owned()), + ("tls".to_owned(), "true".to_owned()), + ("certificate".to_owned(), "/tmp/client.crt".to_owned()), + ("private-key".to_owned(), "/tmp/client.key".to_owned()), + ]), + }; + let Some(ShadowsocksPluginTransport::WebSocket(v2ray_mtls)) = + shadowsocks_plugin_transport(Some(&v2ray_mtls), None).unwrap() + else { + panic!("expected websocket plugin transport"); + }; + assert_eq!( + v2ray_mtls.client_identity, + Some(TlsClientIdentity { + certificate: "/tmp/client.crt".to_owned(), + private_key: "/tmp/client.key".to_owned(), + }) + ); + + let v2ray_ech = ShadowsocksPlugin { + name: "v2ray-plugin".to_owned(), + opts: HashMap::from([ + ("mode".to_owned(), "websocket".to_owned()), + ("tls".to_owned(), "true".to_owned()), + ("ech-opts.enable".to_owned(), "true".to_owned()), + ("ech-opts.config".to_owned(), "AQIDBA==".to_owned()), + ( + "ech-opts.query-server-name".to_owned(), + "public.example".to_owned(), + ), + ]), + }; + let Some(ShadowsocksPluginTransport::WebSocket(v2ray_ech)) = + shadowsocks_plugin_transport(Some(&v2ray_ech), None).unwrap() + else { + panic!("expected websocket plugin transport"); + }; + assert_eq!( + v2ray_ech.ech, + Some(ShadowsocksEchConfig { + config_list: Some(vec![1, 2, 3, 4]), + query_server_name: Some("public.example".to_owned()), + }) + ); + + let gost_without_mux = ShadowsocksPlugin { + name: "gost-plugin".to_owned(), + opts: HashMap::from([ + ("mode".to_owned(), "websocket".to_owned()), + ("mux".to_owned(), "false".to_owned()), + ]), + }; + assert_eq!( + shadowsocks_plugin_transport(Some(&gost_without_mux), None).unwrap(), + Some(ShadowsocksPluginTransport::WebSocket( + ShadowsocksWebSocketTransport { + kind: ShadowsocksWebSocketKind::Gost, + host: "bing.com".to_owned(), + path: "/".to_owned(), + headers: Vec::new(), + tls: false, + ech: None, + skip_cert_verify: false, + certificate_fingerprint: None, + client_identity: None, + mux: ShadowsocksWebSocketMux::None, + v2ray_http_upgrade: false, + v2ray_http_upgrade_fast_open: false, + } + )) + ); + + let gost_default_mux = ShadowsocksPlugin { + name: "gost-plugin".to_owned(), + opts: HashMap::from([("mode".to_owned(), "websocket".to_owned())]), + }; + assert_eq!( + shadowsocks_plugin_transport(Some(&gost_default_mux), None).unwrap(), + Some(ShadowsocksPluginTransport::WebSocket( + ShadowsocksWebSocketTransport { + kind: ShadowsocksWebSocketKind::Gost, + host: "bing.com".to_owned(), + path: "/".to_owned(), + headers: Vec::new(), + tls: false, + ech: None, + skip_cert_verify: false, + certificate_fingerprint: None, + client_identity: None, + mux: ShadowsocksWebSocketMux::Gost, + v2ray_http_upgrade: false, + v2ray_http_upgrade_fast_open: false, + } + )) + ); + + let missing_obfs_mode = ShadowsocksPlugin { + name: "obfs".to_owned(), + opts: HashMap::new(), + }; + assert!(shadowsocks_plugin_transport(Some(&missing_obfs_mode), None).is_err()); + + let uppercase_obfs_plugin = ShadowsocksPlugin { + name: "Obfs".to_owned(), + opts: HashMap::from([("mode".to_owned(), "http".to_owned())]), + }; + assert_eq!( + shadowsocks_plugin_transport(Some(&uppercase_obfs_plugin), None).unwrap(), + None + ); + + let missing_websocket_mode = ShadowsocksPlugin { + name: "v2ray-plugin".to_owned(), + opts: HashMap::new(), + }; + assert!(shadowsocks_plugin_transport(Some(&missing_websocket_mode), None).is_err()); + + let shadow_tls_alias = ShadowsocksPlugin { + name: "shadowtls".to_owned(), + opts: HashMap::from([ + ("host".to_owned(), "cover.example".to_owned()), + ("password".to_owned(), "secret".to_owned()), + ]), + }; + assert_eq!( + shadowsocks_plugin_transport(Some(&shadow_tls_alias), None).unwrap(), + None + ); + + let unknown_plugin = ShadowsocksPlugin { + name: "unknown-plugin".to_owned(), + opts: HashMap::new(), + }; + assert_eq!( + shadowsocks_plugin_transport(Some(&unknown_plugin), None).unwrap(), + None + ); + + let shadow_tls = ShadowsocksPlugin { + name: "shadow-tls".to_owned(), + opts: HashMap::from([ + ("host".to_owned(), "cover.example".to_owned()), + ("password".to_owned(), "secret".to_owned()), + ("version".to_owned(), "1".to_owned()), + ("alpn".to_owned(), "http/1.1".to_owned()), + ("skip-cert-verify".to_owned(), "true".to_owned()), + ( + "fingerprint".to_owned(), + "00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff" + .to_owned(), + ), + ]), + }; + let shadow_tls_fingerprint = parse_certificate_fingerprint( + "00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff", + ) + .unwrap(); + assert_eq!( + shadowsocks_plugin_transport(Some(&shadow_tls), None).unwrap(), + Some(ShadowsocksPluginTransport::ShadowTls( + ShadowsocksShadowTlsTransport { + host: "cover.example".to_owned(), + password: "secret".to_owned(), + version: 1, + alpn: vec!["http/1.1".to_owned()], + skip_cert_verify: true, + certificate_fingerprint: Some(shadow_tls_fingerprint), + client_identity: None, + client_fingerprint: None, + } + )) + ); + + let shadow_tls_v1_without_password_plugin = ShadowsocksPlugin { + name: "shadow-tls".to_owned(), + opts: HashMap::from([ + ("host".to_owned(), "cover.example".to_owned()), + ("version".to_owned(), "1".to_owned()), + ]), + }; + let Some(ShadowsocksPluginTransport::ShadowTls(shadow_tls_v1_without_password)) = + shadowsocks_plugin_transport(Some(&shadow_tls_v1_without_password_plugin), None) + .unwrap() + else { + panic!("expected shadow-tls plugin transport"); + }; + assert_eq!(shadow_tls_v1_without_password.version, 1); + assert_eq!(shadow_tls_v1_without_password.password, ""); + assert_eq!( + shadow_tls_v1_without_password.alpn, + vec!["h2".to_owned(), "http/1.1".to_owned()] + ); + + let shadow_tls_explicit_empty_alpn = ShadowsocksPlugin { + name: "shadow-tls".to_owned(), + opts: HashMap::from([ + ("host".to_owned(), "cover.example".to_owned()), + ("version".to_owned(), "1".to_owned()), + ("alpn".to_owned(), "".to_owned()), + ]), + }; + let Some(ShadowsocksPluginTransport::ShadowTls(shadow_tls_empty_alpn)) = + shadowsocks_plugin_transport(Some(&shadow_tls_explicit_empty_alpn), None).unwrap() + else { + panic!("expected shadow-tls plugin transport"); + }; + assert!(shadow_tls_empty_alpn.alpn.is_empty()); + + let Some(ShadowsocksPluginTransport::ShadowTls(shadow_tls_v1_with_fingerprint)) = + shadowsocks_plugin_transport( + Some(&shadow_tls_v1_without_password_plugin), + Some("firefox"), + ) + .unwrap() + else { + panic!("expected shadow-tls plugin transport"); + }; + assert_eq!(shadow_tls_v1_with_fingerprint.version, 1); + assert_eq!(shadow_tls_v1_with_fingerprint.client_fingerprint, None); + + let shadow_tls_v2_with_fingerprint_plugin = ShadowsocksPlugin { + name: "shadow-tls".to_owned(), + opts: HashMap::from([ + ("host".to_owned(), "cover.example".to_owned()), + ("password".to_owned(), "secret".to_owned()), + ("version".to_owned(), "2".to_owned()), + ]), + }; + #[cfg(feature = "boring-browser-fingerprints")] + { + let Some(ShadowsocksPluginTransport::ShadowTls(shadow_tls_v2_with_fingerprint)) = + shadowsocks_plugin_transport( + Some(&shadow_tls_v2_with_fingerprint_plugin), + Some("firefox"), + ) + .unwrap() + else { + panic!("expected shadow-tls plugin transport"); + }; + assert_eq!(shadow_tls_v2_with_fingerprint.version, 2); + assert_eq!( + shadow_tls_v2_with_fingerprint.client_fingerprint, + Some(MihomoClientFingerprint::Firefox) + ); + } + #[cfg(not(feature = "boring-browser-fingerprints"))] + assert!(shadowsocks_plugin_transport( + Some(&shadow_tls_v2_with_fingerprint_plugin), + Some("firefox"), + ) + .is_err()); + + let shadow_tls_v2_without_password = ShadowsocksPlugin { + name: "shadow-tls".to_owned(), + opts: HashMap::from([ + ("host".to_owned(), "cover.example".to_owned()), + ("version".to_owned(), "2".to_owned()), + ]), + }; + assert!(shadowsocks_plugin_transport(Some(&shadow_tls_v2_without_password), None).is_err()); + + let incomplete_mtls = ShadowsocksPlugin { + name: "gost-plugin".to_owned(), + opts: HashMap::from([ + ("mode".to_owned(), "websocket".to_owned()), + ("certificate".to_owned(), "/tmp/client.crt".to_owned()), + ]), + }; + assert!(shadowsocks_plugin_transport(Some(&incomplete_mtls), None).is_err()); + + let shadow_tls_v3_plugin = ShadowsocksPlugin { + name: "shadow-tls".to_owned(), + opts: HashMap::from([ + ("host".to_owned(), "cover.example".to_owned()), + ("password".to_owned(), "secret".to_owned()), + ("version".to_owned(), "3".to_owned()), + ]), + }; + let Some(ShadowsocksPluginTransport::ShadowTls(shadow_tls_v3)) = + shadowsocks_plugin_transport(Some(&shadow_tls_v3_plugin), None).unwrap() + else { + panic!("expected shadow-tls plugin transport"); + }; + assert_eq!(shadow_tls_v3.version, 3); + #[cfg(feature = "boring-browser-fingerprints")] + { + let Some(ShadowsocksPluginTransport::ShadowTls(shadow_tls_v3_with_fingerprint)) = + shadowsocks_plugin_transport(Some(&shadow_tls_v3_plugin), Some("firefox")).unwrap() + else { + panic!("expected shadow-tls plugin transport"); + }; + assert_eq!(shadow_tls_v3_with_fingerprint.version, 3); + assert_eq!( + shadow_tls_v3_with_fingerprint.client_fingerprint, + Some(MihomoClientFingerprint::Firefox) + ); + } + #[cfg(not(feature = "boring-browser-fingerprints"))] + assert!( + shadowsocks_plugin_transport(Some(&shadow_tls_v3_plugin), Some("firefox")).is_err() + ); + + let kcptun = ShadowsocksPlugin { + name: "kcptun".to_owned(), + opts: HashMap::from([ + ("key".to_owned(), "secret".to_owned()), + ("crypt".to_owned(), "tea".to_owned()), + ("mode".to_owned(), "fast3".to_owned()), + ("conn".to_owned(), "2".to_owned()), + ("nocomp".to_owned(), "true".to_owned()), + ("ratelimit".to_owned(), "2048".to_owned()), + ("dscp".to_owned(), "46".to_owned()), + ("sockbuf".to_owned(), "8388608".to_owned()), + ("smuxver".to_owned(), "3".to_owned()), + ("framesize".to_owned(), "4096".to_owned()), + ]), + }; + let Some(ShadowsocksPluginTransport::Kcptun(kcptun)) = + shadowsocks_plugin_transport(Some(&kcptun), None).unwrap() + else { + panic!("expected kcptun plugin transport"); + }; + assert_eq!(kcptun.key, "secret"); + assert_eq!(kcptun.crypt, "tea"); + assert_eq!(kcptun.mode, "fast3"); + assert_eq!(kcptun.conn, 2); + assert_eq!(kcptun.no_delay, 1); + assert_eq!(kcptun.interval, 10); + assert_eq!(kcptun.resend, 2); + assert!(kcptun.no_congestion); + assert!(kcptun.no_comp); + assert_eq!(kcptun.rate_limit, 2048); + assert_eq!(kcptun.dscp, 46); + assert_eq!(kcptun.sockbuf, 8_388_608); + assert_eq!(kcptun.smux_ver, 2); + assert_eq!(kcptun.frame_size, 4096); + assert_eq!(kcptun_ipv4_dscp_tos(kcptun.dscp), 184); + + let restls = ShadowsocksPlugin { + name: "restls".to_owned(), + opts: HashMap::from([ + ("host".to_owned(), "cover.example".to_owned()), + ("password".to_owned(), "secret".to_owned()), + ("version-hint".to_owned(), "tls13".to_owned()), + ( + "restls-script".to_owned(), + "600~100,300~200<2,300".to_owned(), + ), + ]), + }; + let restls = restls_transport(&restls, Some("firefox")).unwrap(); + assert_eq!(restls.host, "cover.example"); + assert_eq!(restls.version_hint, RestlsVersionHint::Tls13); + assert_eq!(restls.client_fingerprint, RestlsClientFingerprint::Firefox); + assert!(restls.session_tickets_disabled); + assert_eq!( + restls.secret, + blake3::derive_key("restls-traffic-key", b"secret") + ); + assert_eq!( + restls.script, + vec![ + RestlsScriptLine { + target_len: RestlsTargetLength { base: 600, random_range: 100 }, + command: RestlsCommand::Noop, + }, + RestlsScriptLine { + target_len: RestlsTargetLength { base: 300, random_range: 200 }, + command: RestlsCommand::Response(2), + }, + RestlsScriptLine { + target_len: RestlsTargetLength { base: 300, random_range: 0 }, + command: RestlsCommand::Noop, + }, + ] + ); + } + + #[test] + fn restls_plugin_supports_tls13_and_tls12_runtime() { + let restls_tls12 = ShadowsocksPlugin { + name: "restls".to_owned(), + opts: HashMap::from([ + ("host".to_owned(), "cover.example".to_owned()), + ("password".to_owned(), "secret".to_owned()), + ("version-hint".to_owned(), "tls12".to_owned()), + ]), + }; + let Some(ShadowsocksPluginTransport::Restls(restls_tls12_transport)) = + shadowsocks_plugin_transport(Some(&restls_tls12), None).unwrap() + else { + panic!("expected TLS 1.2 restls plugin transport"); + }; + assert_eq!( + restls_tls12_transport.version_hint, + RestlsVersionHint::Tls12 + ); + assert_eq!( + restls_tls12_transport.client_fingerprint, + RestlsClientFingerprint::Chrome + ); + assert!(!restls_tls12_transport.session_tickets_disabled); + let tls12_config = restls_client_config(&restls_tls12_transport).unwrap(); + assert!(tls12_config.enable_tickets); + + let parsed = restls_transport(&restls_tls12, Some("safari")).unwrap(); + assert_eq!(parsed.version_hint, RestlsVersionHint::Tls12); + assert_eq!(parsed.client_fingerprint, RestlsClientFingerprint::Safari); + assert!(!parsed.session_tickets_disabled); + + let case_mismatch = restls_transport(&restls_tls12, Some("Safari")).unwrap(); + assert_eq!( + case_mismatch.client_fingerprint, + RestlsClientFingerprint::Chrome, + "Mihomo's restls clientIDMap is case-sensitive and falls back to Chrome" + ); + + let restls_tls13 = ShadowsocksPlugin { + name: "restls".to_owned(), + opts: HashMap::from([ + ("host".to_owned(), "cover.example".to_owned()), + ("password".to_owned(), "secret".to_owned()), + ("version-hint".to_owned(), "tls13".to_owned()), + ]), + }; + let Some(ShadowsocksPluginTransport::Restls(restls_tls13)) = + shadowsocks_plugin_transport(Some(&restls_tls13), None).unwrap() + else { + panic!("expected TLS 1.3 restls plugin transport"); + }; + assert_eq!(restls_tls13.version_hint, RestlsVersionHint::Tls13); + assert_eq!( + restls_tls13.client_fingerprint, + RestlsClientFingerprint::Chrome + ); + assert!(restls_tls13.session_tickets_disabled); + let tls_config = restls_client_config(&restls_tls13).unwrap(); + assert!(!tls_config.enable_tickets); + + let invalid = ShadowsocksPlugin { + name: "restls".to_owned(), + opts: HashMap::from([ + ("host".to_owned(), "cover.example".to_owned()), + ("version-hint".to_owned(), "tls14".to_owned()), + ]), + }; + assert!(restls_transport(&invalid, None).is_err()); + } + + #[test] + fn restls_application_records_authenticate_and_mask_headers() { + let secret = blake3::derive_key("restls-traffic-key", b"secret"); + let server_random = [0x42; 32]; + let script = vec![RestlsScriptLine { + target_len: RestlsTargetLength { base: 8, random_range: 0 }, + command: RestlsCommand::Response(1), + }]; + let mut writer = RestlsApplicationCodec::new(secret, server_random, false); + let result = writer.build_to_server_record(b"hello", &script).unwrap(); + + assert_eq!(result.consumed, 5); + assert_eq!(result.command, RestlsCommand::Response(1)); + assert_eq!(result.record[0], TLS_APPLICATION_DATA_RECORD_TYPE); + assert_eq!(&result.record[1..3], &TLS12_RECORD_VERSION); + assert_eq!( + usize::from(u16::from_be_bytes([result.record[3], result.record[4]])), + RESTLS_APP_DATA_AUTH_HEADER_LEN + 8 + ); + assert_ne!( + &result.record[TLS_RECORD_HEADER_LEN + RESTLS_APP_DATA_LEN_OFFSET + ..TLS_RECORD_HEADER_LEN + RESTLS_APP_DATA_OFFSET], + &[0, 5, 1, 1], + "Restls masks the length/command bytes with the BLAKE3 auth header hash" + ); + + let mut reader = RestlsApplicationCodec::new(secret, server_random, false); + let frame = reader.decode_to_server_record(&result.record).unwrap(); + assert_eq!(frame.data, b"hello"); + assert_eq!(frame.command, RestlsCommand::Response(1)); + + let mut tampered = result.record.clone(); + let last = tampered.last_mut().unwrap(); + *last ^= 0x01; + let mut reader = RestlsApplicationCodec::new(secret, server_random, false); + assert!(reader.decode_to_server_record(&tampered).is_err()); + } + + #[test] + fn restls_first_application_record_binds_client_finished() { + let secret = blake3::derive_key("restls-traffic-key", b"secret"); + let server_random = [0x11; 32]; + let client_finished = b"encrypted-client-finished".to_vec(); + let mut writer = RestlsApplicationCodec::new(secret, server_random, false); + writer.set_client_finished_auth(client_finished.clone()); + let first = writer.build_to_server_record(b"first", &[]).unwrap(); + let second = writer.build_to_server_record(b"second", &[]).unwrap(); + + let mut missing_finished = RestlsApplicationCodec::new(secret, server_random, false); + assert!(missing_finished + .decode_to_server_record(&first.record) + .is_err()); + + let mut reader = RestlsApplicationCodec::new(secret, server_random, false); + reader.set_client_finished_auth(client_finished); + let first_frame = reader.decode_to_server_record(&first.record).unwrap(); + assert_eq!(first_frame.data, b"first"); + assert_eq!(first_frame.command, RestlsCommand::Noop); + + let second_frame = reader.decode_to_server_record(&second.record).unwrap(); + assert_eq!(second_frame.data, b"second"); + assert_eq!(second_frame.command, RestlsCommand::Noop); + } + + #[test] + fn restls_tls12_gcm_application_records_carry_explicit_counters() { + let secret = blake3::derive_key("restls-traffic-key", b"secret"); + let server_random = [0x24; 32]; + let mut writer = RestlsApplicationCodec::new(secret, server_random, true); + let result = writer.build_to_server_record(b"abc", &[]).unwrap(); + assert_eq!( + u64::from_be_bytes( + result.record[TLS_RECORD_HEADER_LEN..TLS_RECORD_HEADER_LEN + 8] + .try_into() + .unwrap() + ), + 1 + ); + + let mut reader = RestlsApplicationCodec::new(secret, server_random, true); + let frame = reader.decode_to_server_record(&result.record).unwrap(); + assert_eq!(frame.data, b"abc"); + assert_eq!(frame.command, RestlsCommand::Noop); + + let mut replay_reader = RestlsApplicationCodec::new(secret, server_random, true); + replay_reader.to_server_counter = 1; + assert!(replay_reader + .decode_to_server_record(&result.record) + .is_err()); + } + + #[test] + fn restls_tls12_gcm_to_client_can_disable_explicit_counter() { + let secret = blake3::derive_key("restls-traffic-key", b"secret"); + let server_random = [0x25; 32]; + let mut server_writer = RestlsApplicationCodec::new(secret, server_random, false); + let record = server_writer + .build_to_client_test_record(b"abc", RestlsCommand::Noop) + .unwrap(); + + let mut strict_reader = RestlsApplicationCodec::new(secret, server_random, true); + assert!(strict_reader.decode_to_client_record(&record).is_err()); + + let mut reader = RestlsApplicationCodec::new(secret, server_random, true); + reader.set_tls12_gcm_to_client_disable_counter(true); + let frame = reader.decode_to_client_record(&record).unwrap(); + assert_eq!(frame.data, b"abc"); + assert_eq!(frame.command, RestlsCommand::Noop); + } + + #[test] + fn restls_response_count_matches_mihomo_signed_byte_shape() { + assert_eq!(restls_response_repeat_count(2, false), 2); + assert_eq!(restls_response_repeat_count(2, true), 1); + assert_eq!(restls_response_repeat_count(0, true), 0); + assert_eq!(restls_response_repeat_count(200, false), 0); + assert_eq!(restls_response_repeat_count(128, true), 127); + assert_eq!(restls_response_repeat_count(300, false), 44); + } + + #[test] + fn restls_stream_state_buffers_and_resumes_scripted_interrupts() { + let secret = blake3::derive_key("restls-traffic-key", b"secret"); + let server_random = [0x51; 32]; + let script = vec![ + RestlsScriptLine { + target_len: RestlsTargetLength { base: 3, random_range: 0 }, + command: RestlsCommand::Response(2), + }, + RestlsScriptLine { + target_len: RestlsTargetLength { base: 6, random_range: 0 }, + command: RestlsCommand::Noop, + }, + ]; + let mut writer = RestlsStreamState::new( + RestlsApplicationCodec::new(secret, server_random, false), + script, + ); + let mut reader = RestlsApplicationCodec::new(secret, server_random, false); + + let first = writer.write(b"abcdef").unwrap(); + assert_eq!(first.accepted, 6); + assert_eq!(first.records.len(), 1); + assert!(writer.write_pending); + assert_eq!(writer.send_buffer, b"def"); + let frame = reader.decode_to_server_record(&first.records[0]).unwrap(); + assert_eq!(frame.data, b"abc"); + assert_eq!(frame.command, RestlsCommand::Response(2)); + + let buffered = writer.write(b"ghi").unwrap(); + assert_eq!(buffered.accepted, 3); + assert!(buffered.records.is_empty()); + assert_eq!(writer.send_buffer, b"defghi"); + + let resumed = writer.resume_pending_write().unwrap(); + assert_eq!(resumed.records.len(), 1); + assert!(!writer.write_pending); + assert!(writer.send_buffer.is_empty()); + let frame = reader.decode_to_server_record(&resumed.records[0]).unwrap(); + assert_eq!(frame.data, b"defghi"); + assert_eq!(frame.command, RestlsCommand::Noop); + + let response_records = writer + .receive_command(RestlsCommand::Response(2), true) + .unwrap(); + assert_eq!(response_records.len(), 1); + let frame = reader + .decode_to_server_record(&response_records[0]) + .unwrap(); + assert!(frame.data.is_empty()); + assert_eq!(frame.command, RestlsCommand::Noop); + } + + #[test] + fn restls_stream_state_decodes_server_records_and_unblocks_upload() { + let secret = blake3::derive_key("restls-traffic-key", b"secret"); + let server_random = [0x52; 32]; + let script = vec![ + RestlsScriptLine { + target_len: RestlsTargetLength { base: 2, random_range: 0 }, + command: RestlsCommand::Response(2), + }, + RestlsScriptLine { + target_len: RestlsTargetLength { base: 2, random_range: 0 }, + command: RestlsCommand::Noop, + }, + RestlsScriptLine { + target_len: RestlsTargetLength { base: 0, random_range: 0 }, + command: RestlsCommand::Noop, + }, + ]; + let mut state = RestlsStreamState::new( + RestlsApplicationCodec::new(secret, server_random, false), + script, + ); + let mut peer_reader = RestlsApplicationCodec::new(secret, server_random, false); + let mut server_writer = RestlsApplicationCodec::new(secret, server_random, false); + + let upload = state.write(b"abcd").unwrap(); + assert_eq!(upload.records.len(), 1); + assert!(state.write_pending); + assert_eq!(state.send_buffer, b"cd"); + assert_eq!( + peer_reader + .decode_to_server_record(&upload.records[0]) + .unwrap() + .data, + b"ab" + ); + + let server_record = server_writer + .build_to_client_test_record(b"server-data", RestlsCommand::Response(2)) + .unwrap(); + let read = state.read_to_client_record(&server_record).unwrap(); + assert_eq!(read.data, b"server-data"); + assert!(read.resumed_pending_write); + assert_eq!(read.records.len(), 2); + assert!(!state.write_pending); + assert!(state.send_buffer.is_empty()); + + let resumed = peer_reader + .decode_to_server_record(&read.records[0]) + .unwrap(); + assert_eq!(resumed.data, b"cd"); + assert_eq!(resumed.command, RestlsCommand::Noop); + let response = peer_reader + .decode_to_server_record(&read.records[1]) + .unwrap(); + assert!(response.data.is_empty()); + assert_eq!(response.command, RestlsCommand::Noop); + } + + #[tokio::test] + async fn restls_stream_wraps_application_read_and_write() { + let secret = blake3::derive_key("restls-traffic-key", b"secret"); + let server_random = [0x61; 32]; + let state = RestlsStreamState::new( + RestlsApplicationCodec::new(secret, server_random, false), + Vec::new(), + ); + let (client, mut server) = tokio::io::duplex(4096); + let mut stream = RestlsStream::new(client, state); + + stream.write_all(b"upload").await.unwrap(); + stream.flush().await.unwrap(); + let record = read_test_tls_record(&mut server).await; + let mut peer_reader = RestlsApplicationCodec::new(secret, server_random, false); + let frame = peer_reader.decode_to_server_record(&record).unwrap(); + assert_eq!(frame.data, b"upload"); + assert_eq!(frame.command, RestlsCommand::Noop); + + let mut server_writer = RestlsApplicationCodec::new(secret, server_random, false); + let record = server_writer + .build_to_client_test_record(b"download", RestlsCommand::Noop) + .unwrap(); + server.write_all(&record).await.unwrap(); + + let mut body = [0u8; 8]; + stream.read_exact(&mut body).await.unwrap(); + assert_eq!(&body, b"download"); + } + + #[tokio::test] + async fn restls_stream_resumes_pending_write_from_server_response() { + let secret = blake3::derive_key("restls-traffic-key", b"secret"); + let server_random = [0x62; 32]; + let script = vec![ + RestlsScriptLine { + target_len: RestlsTargetLength { base: 2, random_range: 0 }, + command: RestlsCommand::Response(2), + }, + RestlsScriptLine { + target_len: RestlsTargetLength { base: 2, random_range: 0 }, + command: RestlsCommand::Noop, + }, + RestlsScriptLine { + target_len: RestlsTargetLength { base: 0, random_range: 0 }, + command: RestlsCommand::Noop, + }, + ]; + let state = RestlsStreamState::new( + RestlsApplicationCodec::new(secret, server_random, false), + script, + ); + let (client, mut server) = tokio::io::duplex(4096); + let mut stream = RestlsStream::new(client, state); + let mut peer_reader = RestlsApplicationCodec::new(secret, server_random, false); + + stream.write_all(b"abcd").await.unwrap(); + stream.flush().await.unwrap(); + let first = read_test_tls_record(&mut server).await; + let frame = peer_reader.decode_to_server_record(&first).unwrap(); + assert_eq!(frame.data, b"ab"); + assert_eq!(frame.command, RestlsCommand::Response(2)); + + let mut server_writer = RestlsApplicationCodec::new(secret, server_random, false); + let response = server_writer + .build_to_client_test_record(b"ok", RestlsCommand::Response(2)) + .unwrap(); + server.write_all(&response).await.unwrap(); + + let mut body = [0u8; 2]; + stream.read_exact(&mut body).await.unwrap(); + assert_eq!(&body, b"ok"); + + let resumed = read_test_tls_record(&mut server).await; + let frame = peer_reader.decode_to_server_record(&resumed).unwrap(); + assert_eq!(frame.data, b"cd"); + assert_eq!(frame.command, RestlsCommand::Noop); + + let fake_response = read_test_tls_record(&mut server).await; + let frame = peer_reader.decode_to_server_record(&fake_response).unwrap(); + assert!(frame.data.is_empty()); + assert_eq!(frame.command, RestlsCommand::Noop); + } + + #[test] + fn restls_client_hello_auth_material_matches_mihomo_shape() { + fn extension(extension_type: u16, payload: Vec) -> Vec { + let mut encoded = Vec::with_capacity(4 + payload.len()); + encoded.extend_from_slice(&extension_type.to_be_bytes()); + encoded.extend_from_slice(&(payload.len() as u16).to_be_bytes()); + encoded.extend_from_slice(&payload); + encoded + } + + fn test_client_hello(key_shares: &[(u16, Vec)], psk_labels: &[Vec]) -> Vec { + let mut key_share_entries = Vec::new(); + for (group, key) in key_shares { + key_share_entries.extend_from_slice(&group.to_be_bytes()); + key_share_entries.extend_from_slice(&(key.len() as u16).to_be_bytes()); + key_share_entries.extend_from_slice(key); + } + let mut key_share_payload = Vec::new(); + key_share_payload.extend_from_slice(&(key_share_entries.len() as u16).to_be_bytes()); + key_share_payload.extend_from_slice(&key_share_entries); + + let mut identities = Vec::new(); + for label in psk_labels { + identities.extend_from_slice(&(label.len() as u16).to_be_bytes()); + identities.extend_from_slice(label); + identities.extend_from_slice(&0u32.to_be_bytes()); + } + let mut psk_payload = Vec::new(); + psk_payload.extend_from_slice(&(identities.len() as u16).to_be_bytes()); + psk_payload.extend_from_slice(&identities); + psk_payload.extend_from_slice(&2u16.to_be_bytes()); + psk_payload.extend_from_slice(&[0xaa, 0xbb]); + + let mut extensions = Vec::new(); + extensions.extend_from_slice(&extension(0x0033, key_share_payload)); + extensions.extend_from_slice(&extension(0x0029, psk_payload)); + + let mut body = Vec::new(); + body.extend_from_slice(&[0x03, 0x03]); + body.extend_from_slice(&[0x44; 32]); + body.push(32); + body.extend_from_slice(&[0; 32]); + body.extend_from_slice(&2u16.to_be_bytes()); + body.extend_from_slice(&0x1301u16.to_be_bytes()); + body.push(1); + body.push(0); + body.extend_from_slice(&(extensions.len() as u16).to_be_bytes()); + body.extend_from_slice(&extensions); + + let mut hello = Vec::new(); + hello.push(0x01); + hello.extend_from_slice(&[ + ((body.len() >> 16) & 0xff) as u8, + ((body.len() >> 8) & 0xff) as u8, + (body.len() & 0xff) as u8, + ]); + hello.extend_from_slice(&body); + hello + } + + let secret = blake3::derive_key("restls-traffic-key", b"secret"); + let key_shares = vec![(0x001d, vec![1, 2, 3, 4]), (0x0017, vec![5, 6, 7, 8])]; + let psk_labels = vec![b"ticket-a".to_vec(), b"ticket-b".to_vec()]; + let seed_session_id = [0x7a; 32]; + let session_id = + restls_tls13_session_id(&secret, &key_shares, &psk_labels, seed_session_id); + + let mut expected_hmac = blake3::Hasher::new_keyed(&secret); + expected_hmac.update(&0x001du16.to_be_bytes()); + expected_hmac.update(&[1, 2, 3, 4]); + expected_hmac.update(&0x0017u16.to_be_bytes()); + expected_hmac.update(&[5, 6, 7, 8]); + expected_hmac.update(b"ticket-a"); + expected_hmac.update(b"ticket-b"); + let expected = expected_hmac.finalize(); + assert_eq!( + &session_id[..RESTLS_HANDSHAKE_MAC_LEN], + &expected.as_bytes()[..RESTLS_HANDSHAKE_MAC_LEN] + ); + assert_eq!(&session_id[RESTLS_HANDSHAKE_MAC_LEN..], &[0x7a; 16]); + + let client_hello = test_client_hello(&key_shares, &psk_labels); + let material = restls_tls13_client_hello_auth_material(&client_hello).unwrap(); + assert_eq!(material.key_shares, key_shares); + assert_eq!(material.psk_labels, psk_labels); + assert_eq!( + restls_tls13_session_id_from_client_hello(&secret, &client_hello, seed_session_id) + .unwrap(), + session_id + ); + let mut truncated = client_hello; + truncated.pop(); + assert!(restls_tls13_client_hello_auth_material(&truncated).is_err()); + + let error = StdMutex::new(None); + let fallback_session_id = restls_tls13_session_id_for_client_hello(&secret, &[], &error); + assert_eq!( + &fallback_session_id[..RESTLS_HANDSHAKE_MAC_LEN], + &[0; RESTLS_HANDSHAKE_MAC_LEN] + ); + let err = take_restls_session_id_error(&error).expect("session id error was recorded"); + assert!(err.contains("failed to derive Restls TLS 1.3 session id from ClientHello")); + assert!(err.contains("missing handshake type")); + + let materials = vec![ + vec![0x11; 32], + vec![0x22; 65], + vec![0x33; 97], + b"session-ticket".to_vec(), + ]; + let session_id = restls_tls12_session_id(&secret, &materials).unwrap(); + for (index, window) in RESTLS_TLS12_CLIENT_AUTH_LAYOUT_4.windows(2).enumerate() { + let mut hmac = blake3::Hasher::new_keyed(&secret); + hmac.update(&materials[index]); + let digest = hmac.finalize(); + assert_eq!( + &session_id[window[0]..window[1]], + &digest.as_bytes()[..window[1] - window[0]] + ); + } + assert!(restls_tls12_session_id(&secret, &materials[..2]).is_err()); + } + + #[test] + fn restls_tls13_client_hello_record_signer_preserves_random_tail() { + fn extension(extension_type: u16, payload: Vec) -> Vec { + let mut encoded = Vec::with_capacity(4 + payload.len()); + encoded.extend_from_slice(&extension_type.to_be_bytes()); + encoded.extend_from_slice(&(payload.len() as u16).to_be_bytes()); + encoded.extend_from_slice(&payload); + encoded + } + + let mut key_share_entries = Vec::new(); + key_share_entries.extend_from_slice(&0x001du16.to_be_bytes()); + key_share_entries.extend_from_slice(&4u16.to_be_bytes()); + key_share_entries.extend_from_slice(&[1, 2, 3, 4]); + let mut key_share_payload = Vec::new(); + key_share_payload.extend_from_slice(&(key_share_entries.len() as u16).to_be_bytes()); + key_share_payload.extend_from_slice(&key_share_entries); + + let mut extensions = Vec::new(); + extensions.extend_from_slice(&extension(0x0033, key_share_payload)); + + let mut body = Vec::new(); + body.extend_from_slice(&[0x03, 0x03]); + body.extend_from_slice(&[0x44; 32]); + body.push(32); + body.extend_from_slice(&[0x7a; 32]); + body.extend_from_slice(&2u16.to_be_bytes()); + body.extend_from_slice(&0x1301u16.to_be_bytes()); + body.push(1); + body.push(0); + body.extend_from_slice(&(extensions.len() as u16).to_be_bytes()); + body.extend_from_slice(&extensions); + + let mut payload = vec![0x01]; + payload.extend_from_slice(&[ + ((body.len() >> 16) & 0xff) as u8, + ((body.len() >> 8) & 0xff) as u8, + (body.len() & 0xff) as u8, + ]); + payload.extend_from_slice(&body); + + let mut record = vec![0x16, 0x03, 0x03]; + record.extend_from_slice(&(payload.len() as u16).to_be_bytes()); + record.extend_from_slice(&payload); + record.extend_from_slice(b"next-record"); + + let secret = blake3::derive_key("restls-traffic-key", b"secret"); + assert!(restls_tls13_sign_client_hello_record(&secret, &record[..4]) + .unwrap() + .is_none()); + let signed = restls_tls13_sign_client_hello_record(&secret, &record) + .unwrap() + .expect("complete ClientHello record is signed"); + assert_eq!( + &signed[signed.len() - b"next-record".len()..], + b"next-record" + ); + + let session_id_start = TLS_RECORD_HEADER_LEN + SHADOW_TLS_V3_SESSION_ID_START; + let session_id = &signed[session_id_start..session_id_start + 32]; + let mut expected_hmac = blake3::Hasher::new_keyed(&secret); + expected_hmac.update(&0x001du16.to_be_bytes()); + expected_hmac.update(&[1, 2, 3, 4]); + let expected = expected_hmac.finalize(); + assert_eq!( + &session_id[..RESTLS_HANDSHAKE_MAC_LEN], + &expected.as_bytes()[..RESTLS_HANDSHAKE_MAC_LEN] + ); + assert_eq!(&session_id[RESTLS_HANDSHAKE_MAC_LEN..], &[0x7a; 16]); + } + + #[test] + fn restls_server_auth_record_unmasking_matches_mihomo_offsets() { + let secret = blake3::derive_key("restls-traffic-key", b"secret"); + let server_random = [0x99; 32]; + let mut hmac = blake3::Hasher::new_keyed(&secret); + hmac.update(&server_random); + let digest = hmac.finalize(); + let mask = &digest.as_bytes()[..RESTLS_HANDSHAKE_MAC_LEN]; + + let mut plain = vec![TLS_APPLICATION_DATA_RECORD_TYPE, 3, 3, 0, 18]; + plain.extend_from_slice(b"server-auth-record"); + let mut masked = plain.clone(); + xor_with_restls_mask(&mut masked[TLS_RECORD_HEADER_LEN..], mask); + let unmasked = + restls_unmask_server_auth_record(&secret, &server_random, &masked, false).unwrap(); + assert_eq!(unmasked.record, plain); + assert!(!unmasked.tls12_gcm_server_disable_counter); + + let mut plain_gcm = vec![TLS_APPLICATION_DATA_RECORD_TYPE, 3, 3, 0, 23]; + plain_gcm.extend_from_slice(&[0; 8]); + plain_gcm.extend_from_slice(b"gcm-server-auth"); + let mut masked_gcm = plain_gcm.clone(); + xor_with_restls_mask(&mut masked_gcm[TLS_RECORD_HEADER_LEN + 8..], mask); + let unmasked = + restls_unmask_server_auth_record(&secret, &server_random, &masked_gcm, true).unwrap(); + assert_eq!(unmasked.record, plain_gcm); + assert!(!unmasked.tls12_gcm_server_disable_counter); + + let mut plain_gcm_no_counter = vec![TLS_APPLICATION_DATA_RECORD_TYPE, 3, 3, 0, 23]; + plain_gcm_no_counter.extend_from_slice(&[0, 0, 0, 0, 0, 0, 0, 1]); + plain_gcm_no_counter.extend_from_slice(b"gcm-server-auth"); + let mut masked_gcm_no_counter = plain_gcm_no_counter.clone(); + xor_with_restls_mask(&mut masked_gcm_no_counter[TLS_RECORD_HEADER_LEN..], mask); + let unmasked = + restls_unmask_server_auth_record(&secret, &server_random, &masked_gcm_no_counter, true) + .unwrap(); + assert_eq!(unmasked.record, plain_gcm_no_counter); + assert!(unmasked.tls12_gcm_server_disable_counter); + } + + #[test] + fn restls_handshake_stream_captures_auth_state() { + fn server_hello_record(server_random: [u8; 32]) -> Vec { + let mut body = Vec::new(); + body.extend_from_slice(&[0x03, 0x03]); + body.extend_from_slice(&server_random); + body.push(0); + + let mut payload = Vec::new(); + payload.push(0x02); + payload.extend_from_slice(&[ + ((body.len() >> 16) & 0xff) as u8, + ((body.len() >> 8) & 0xff) as u8, + (body.len() & 0xff) as u8, + ]); + payload.extend_from_slice(&body); + + let mut record = vec![0x16, 0x03, 0x03]; + record.extend_from_slice(&(payload.len() as u16).to_be_bytes()); + record.extend_from_slice(&payload); + record + } + + let secret = blake3::derive_key("restls-traffic-key", b"secret"); + let server_random = [0x71; 32]; + let mut stream = RestlsHandshakeStream::new((), secret, false); + let server_hello = server_hello_record(server_random); + stream.process_read_frame(server_hello.clone()).unwrap(); + assert_eq!(stream.server_random, Some(server_random)); + assert_eq!(stream.read_buf, server_hello); + + let mut hmac = blake3::Hasher::new_keyed(&secret); + hmac.update(&server_random); + let digest = hmac.finalize(); + let mask = &digest.as_bytes()[..RESTLS_HANDSHAKE_MAC_LEN]; + let mut plain_auth = vec![TLS_APPLICATION_DATA_RECORD_TYPE, 0x03, 0x03, 0, 11]; + plain_auth.extend_from_slice(b"server-auth"); + let mut masked_auth = plain_auth.clone(); + xor_with_restls_mask(&mut masked_auth[TLS_RECORD_HEADER_LEN..], mask); + stream.process_read_frame(masked_auth).unwrap(); + assert_eq!(stream.read_buf, plain_auth); + assert!(stream.server_auth_unmasked); + + let ccs = [0x14, 0x03, 0x03, 0, 1, 1]; + let client_finished = [TLS_APPLICATION_DATA_RECORD_TYPE, 0x03, 0x03, 0, 3, 1, 2, 3]; + let mut write = Vec::new(); + write.extend_from_slice(&ccs); + stream.process_write_bytes(&write); + stream.process_write_bytes(&client_finished[..2]); + assert_eq!(stream.client_finished_auth, None); + stream.process_write_bytes(&client_finished[2..]); + + let (_, state) = stream.into_inner(); + assert_eq!(state.server_random, Some(server_random)); + assert_eq!(state.client_finished_auth, Some(client_finished.to_vec())); + assert!(state.server_auth_unmasked); + assert!(!state.tls12_gcm_server_disable_counter); + } + + #[test] + fn kcptun_rate_limiter_uses_mihomo_burst_shape() { + let (client, _server) = tokio::io::duplex(1); + let stream = RateLimitedStream::new(client, 2048); + assert_eq!(stream.bytes_per_second, 2048); + assert_eq!(stream.burst_capacity, KCPTUN_RATE_LIMIT_BURST_BYTES); + assert_eq!(stream.available, KCPTUN_RATE_LIMIT_BURST_BYTES); + } + + #[test] + fn kcptun_smux_keep_alive_timeout_matches_mihomo_default_shape() { + assert_eq!( + kcptun_smux_keep_alive_timeout(Duration::from_secs(5)), + Duration::from_secs(30) + ); + assert_eq!( + kcptun_smux_keep_alive_timeout(Duration::from_secs(10)), + Duration::from_secs(30) + ); + assert_eq!( + kcptun_smux_keep_alive_timeout(Duration::from_secs(30)), + Duration::from_secs(90) + ); + } + + #[test] + fn kcptun_block_crypt_accepts_mihomo_names() { + assert!(kcptun_block_crypt("secret", "null").unwrap().is_none()); + for crypt in [ + "aes", + "aes-128", + "aes-192", + "aes-256", + "tea", + "xor", + "none", + "blowfish", + "cast5", + "3des", + "twofish", + "xtea", + "salsa20", + "sm4", + "aes-128-gcm", + "unknown-falls-back-to-aes", + ] { + assert!( + kcptun_block_crypt("secret", crypt).unwrap().is_some(), + "crypt {crypt}" + ); + } + } + + #[tokio::test] + async fn uot_frame_round_trips_ipv4_and_ipv6() { + for addr in [ + "1.2.3.4:53".parse::().unwrap(), + "[2001:db8::1]:443".parse::().unwrap(), + ] { + let (mut writer, mut reader) = tokio::io::duplex(128); + let write = + tokio::spawn(async move { write_uot_frame(&mut writer, addr, b"hello").await }); + + let (decoded_addr, payload) = read_uot_frame(&mut reader).await.unwrap(); + write.await.unwrap().unwrap(); + assert_eq!(decoded_addr, addr); + assert_eq!(payload, b"hello"); + } + } + + #[tokio::test] + async fn simple_obfs_http_wraps_first_write_and_strips_response() { + let (client, mut server) = tokio::io::duplex(4096); + let mut stream = SimpleObfsStream::new( + client, + SimpleObfsMode::Http, + "front.example".to_owned(), + 8080, + ); + + let write = tokio::spawn(async move { + stream.write_all(b"hello").await?; + stream.flush().await?; + let mut body = [0u8; 5]; + stream.read_exact(&mut body).await?; + Result::<[u8; 5]>::Ok(body) + }); + + let mut request = vec![0u8; 2048]; + let len = server.read(&mut request).await.unwrap(); + let request = &request[..len]; + assert!(request.starts_with(b"GET http://front.example/ HTTP/1.1\r\n")); + assert!(request + .windows(b"Host: front.example:8080\r\n".len()) + .any(|window| window == b"Host: front.example:8080\r\n")); + assert!(request.ends_with(b"\r\n\r\nhello")); + + server + .write_all(b"HTTP/1.1 101 Switching Protocols\r\nConnection: Upgrade\r\n\r\nworld") + .await + .unwrap(); + assert_eq!(write.await.unwrap().unwrap(), *b"world"); + } + + #[tokio::test] + async fn simple_obfs_tls_wraps_writes_and_strips_records() { + let (client, mut server) = tokio::io::duplex(4096); + let mut stream = + SimpleObfsStream::new(client, SimpleObfsMode::Tls, "front.example".to_owned(), 443); + + let write = tokio::spawn(async move { + stream.write_all(b"hello").await?; + stream.flush().await?; + let mut body = [0u8; 5]; + stream.read_exact(&mut body).await?; + stream.write_all(b"again").await?; + stream.flush().await?; + Result::<[u8; 5]>::Ok(body) + }); + + let mut client_hello = vec![0u8; 4096]; + let len = server.read(&mut client_hello).await.unwrap(); + let client_hello = &client_hello[..len]; + assert_eq!(client_hello[0], 22); + assert!(client_hello + .windows("front.example".len()) + .any(|window| window == b"front.example")); + assert!(client_hello.windows(5).any(|window| window == b"hello")); + + let mut response = vec![0u8; 105]; + response.extend_from_slice(&5u16.to_be_bytes()); + response.extend_from_slice(b"world"); + server.write_all(&response).await.unwrap(); + + let mut record = [0u8; 10]; + server.read_exact(&mut record).await.unwrap(); + assert_eq!(&record[..3], &[0x17, 0x03, 0x03]); + assert_eq!(u16::from_be_bytes([record[3], record[4]]), 5); + assert_eq!(&record[5..], b"again"); + assert_eq!(write.await.unwrap().unwrap(), *b"world"); + } + + #[tokio::test] + async fn shadow_tls_v2_wraps_records_and_strips_headers() { + let (client, mut server) = tokio::io::duplex(4096); + let mut stream = ShadowTlsV2Stream::new(client, [1, 2, 3, 4, 5, 6, 7, 8]); + + let write = tokio::spawn(async move { + stream.write_all(b"hello").await?; + stream.flush().await?; + let mut body = [0u8; 5]; + stream.read_exact(&mut body).await?; + stream.write_all(b"again").await?; + stream.flush().await?; + Result::<[u8; 5]>::Ok(body) + }); + + let mut first_record = [0u8; 18]; + server.read_exact(&mut first_record).await.unwrap(); + assert_eq!(&first_record[..3], &[0x17, 0x03, 0x03]); + assert_eq!(u16::from_be_bytes([first_record[3], first_record[4]]), 13); + assert_eq!(&first_record[5..13], &[1, 2, 3, 4, 5, 6, 7, 8]); + assert_eq!(&first_record[13..], b"hello"); + + server + .write_all(&[0x17, 0x03, 0x03, 0x00, 0x05]) + .await + .unwrap(); + server.write_all(b"world").await.unwrap(); + + let mut second_record = [0u8; 10]; + server.read_exact(&mut second_record).await.unwrap(); + assert_eq!(&second_record[..3], &[0x17, 0x03, 0x03]); + assert_eq!(u16::from_be_bytes([second_record[3], second_record[4]]), 5); + assert_eq!(&second_record[5..], b"again"); + assert_eq!(write.await.unwrap().unwrap(), *b"world"); + } + + #[test] + fn shadow_tls_v3_session_id_carries_password_hmac() { + let mut client_hello = + vec![0u8; SHADOW_TLS_V3_SESSION_ID_START + SHADOW_TLS_V3_SESSION_ID_SIZE + 8]; + client_hello[0] = 0x01; + client_hello[SHADOW_TLS_V3_SESSION_ID_START - 1] = SHADOW_TLS_V3_SESSION_ID_SIZE as u8; + let session_id = shadow_tls_v3_generate_session_id("secret", &client_hello); + + let mut context = shadow_tls_v3_hmac("secret", b"", b""); + let mut signed_session_id = session_id; + signed_session_id[SHADOW_TLS_V3_SESSION_ID_SIZE - SHADOW_TLS_V3_HMAC_SIZE..].fill(0); + context.update(&client_hello[..SHADOW_TLS_V3_SESSION_ID_START]); + context.update(&signed_session_id); + context.update( + &client_hello[SHADOW_TLS_V3_SESSION_ID_START + SHADOW_TLS_V3_SESSION_ID_SIZE..], + ); + let expected = shadow_tls_v3_hmac_prefix(&context, SHADOW_TLS_V3_HMAC_SIZE); + assert_eq!( + &session_id[SHADOW_TLS_V3_SESSION_ID_SIZE - SHADOW_TLS_V3_HMAC_SIZE..], + expected.as_slice() + ); + } + + #[test] + fn shadow_tls_v3_client_hello_record_signer_rewrites_session_id() { + let mut payload = + vec![0u8; SHADOW_TLS_V3_SESSION_ID_START + SHADOW_TLS_V3_SESSION_ID_SIZE + 8]; + payload[0] = 0x01; + payload[SHADOW_TLS_V3_SESSION_ID_START - 1] = SHADOW_TLS_V3_SESSION_ID_SIZE as u8; + payload[SHADOW_TLS_V3_SESSION_ID_START + SHADOW_TLS_V3_SESSION_ID_SIZE..].fill(0x7a); + + let mut record = vec![0x16, 0x03, 0x03]; + record.extend_from_slice(&(payload.len() as u16).to_be_bytes()); + record.extend_from_slice(&payload); + + assert!( + shadow_tls_v3_sign_client_hello_record("secret", &record[..4]) + .unwrap() + .is_none() + ); + let signed = shadow_tls_v3_sign_client_hello_record("secret", &record) + .unwrap() + .expect("complete ClientHello record is signed"); + assert_eq!(signed.len(), record.len()); + let session_id_start = SHADOW_TLS_V3_TLS_HEADER_SIZE + SHADOW_TLS_V3_SESSION_ID_START; + let session_id = + &signed[session_id_start..session_id_start + SHADOW_TLS_V3_SESSION_ID_SIZE]; + assert_ne!(session_id, &[0u8; SHADOW_TLS_V3_SESSION_ID_SIZE]); + + let mut expected_context = shadow_tls_v3_hmac("secret", b"", b""); + let mut signed_session_id = [0u8; SHADOW_TLS_V3_SESSION_ID_SIZE]; + signed_session_id.copy_from_slice(session_id); + signed_session_id[SHADOW_TLS_V3_SESSION_ID_SIZE - SHADOW_TLS_V3_HMAC_SIZE..].fill(0); + expected_context.update(&payload[..SHADOW_TLS_V3_SESSION_ID_START]); + expected_context.update(&signed_session_id); + expected_context + .update(&payload[SHADOW_TLS_V3_SESSION_ID_START + SHADOW_TLS_V3_SESSION_ID_SIZE..]); + let expected = shadow_tls_v3_hmac_prefix(&expected_context, SHADOW_TLS_V3_HMAC_SIZE); + assert_eq!( + &session_id[SHADOW_TLS_V3_SESSION_ID_SIZE - SHADOW_TLS_V3_HMAC_SIZE..], + expected.as_slice() + ); + } + + #[tokio::test] + async fn shadow_tls_v3_wraps_and_verifies_application_data() { + let (client, mut server) = tokio::io::duplex(4096); + let server_random = [9u8; SHADOW_TLS_V3_TLS_RANDOM_SIZE]; + let mut stream = ShadowTlsV3Stream::new(client, "secret", server_random, None); + + let task = tokio::spawn(async move { + stream.write_all(b"hello").await?; + stream.flush().await?; + let mut body = [0u8; 5]; + stream.read_exact(&mut body).await?; + Result::<[u8; 5]>::Ok(body) + }); + + let mut first_record = [0u8; SHADOW_TLS_V3_HMAC_HEADER_SIZE + 5]; + server.read_exact(&mut first_record).await.unwrap(); + assert_eq!(&first_record[..3], &[0x17, 0x03, 0x03]); + assert_eq!( + u16::from_be_bytes([first_record[3], first_record[4]]), + (SHADOW_TLS_V3_HMAC_SIZE + 5) as u16 + ); + let mut client_hmac = shadow_tls_v3_hmac("secret", &server_random, b"C"); + client_hmac.update(b"hello"); + assert_eq!( + &first_record[SHADOW_TLS_V3_TLS_HEADER_SIZE..SHADOW_TLS_V3_HMAC_HEADER_SIZE], + shadow_tls_v3_hmac_prefix(&client_hmac, SHADOW_TLS_V3_HMAC_SIZE).as_slice() + ); + assert_eq!(&first_record[SHADOW_TLS_V3_HMAC_HEADER_SIZE..], b"hello"); + + let mut server_hmac = shadow_tls_v3_hmac("secret", &server_random, b"S"); + let response = shadow_tls_v3_record(b"world", 5, &mut server_hmac).unwrap(); + server.write_all(&response).await.unwrap(); + + assert_eq!(task.await.unwrap().unwrap(), *b"world"); + } + + #[tokio::test] + async fn websocket_plugin_wraps_frames_after_upgrade() { + let (client, mut server) = tokio::io::duplex(4096); + let config = ShadowsocksWebSocketTransport { + kind: ShadowsocksWebSocketKind::V2ray, + host: "front.example".to_owned(), + path: "ws?ed=0".to_owned(), + headers: vec![("User-Agent".to_owned(), "BurrowTest".to_owned())], + tls: false, + ech: None, + skip_cert_verify: false, + certificate_fingerprint: None, + client_identity: None, + mux: ShadowsocksWebSocketMux::None, + v2ray_http_upgrade: false, + v2ray_http_upgrade_fast_open: false, + }; + + let client_task = tokio::spawn(async move { + let mut stream = websocket_plugin_connect(Box::new(client), &config, 443).await?; + stream.write_all(b"hello").await?; + stream.flush().await?; + let mut body = [0u8; 5]; + stream.read_exact(&mut body).await?; + Result::<[u8; 5]>::Ok(body) + }); + + let request = read_http_headers_for_test(&mut server).await; + assert!(request.starts_with("GET /ws HTTP/1.1\r\n")); + assert!(request.contains("Host: front.example\r\n")); + assert!(request.contains("Upgrade: websocket\r\n")); + assert!(request.contains("Sec-WebSocket-Key: ")); + assert!(request.contains("User-Agent: BurrowTest\r\n")); + server + .write_all(b"HTTP/1.1 101 Switching Protocols\r\nConnection: Upgrade\r\nUpgrade: websocket\r\n\r\n") + .await + .unwrap(); + + assert_eq!( + read_client_websocket_frame_for_test(&mut server).await, + b"hello" + ); + server + .write_all(&server_websocket_frame_for_test(b"world")) + .await + .unwrap(); + assert_eq!(client_task.await.unwrap().unwrap(), *b"world"); + } + + #[tokio::test] + async fn websocket_early_data_moves_first_payload_to_protocol_header() { + let (client, mut server) = tokio::io::duplex(4096); + let config = ShadowsocksWebSocketTransport { + kind: ShadowsocksWebSocketKind::V2ray, + host: "front.example".to_owned(), + path: "/ws?ed=8&x=1".to_owned(), + headers: vec![("Sec-WebSocket-Protocol".to_owned(), "stale".to_owned())], + tls: false, + ech: None, + skip_cert_verify: false, + certificate_fingerprint: None, + client_identity: None, + mux: ShadowsocksWebSocketMux::None, + v2ray_http_upgrade: false, + v2ray_http_upgrade_fast_open: false, + }; + + let client_task = tokio::spawn(async move { + let mut stream = websocket_plugin_connect(Box::new(client), &config, 443).await?; + stream.write_all(b"hello-world").await?; + stream.flush().await?; + let mut body = [0u8; 5]; + stream.read_exact(&mut body).await?; + Result::<[u8; 5]>::Ok(body) + }); + + let request = read_http_headers_for_test(&mut server).await; + assert!(request.starts_with("GET /ws?x=1 HTTP/1.1\r\n")); + assert!(request.contains("Sec-WebSocket-Protocol: aGVsbG8td28\r\n")); + assert!(!request.contains("Sec-WebSocket-Protocol: stale\r\n")); + server + .write_all(b"HTTP/1.1 101 Switching Protocols\r\nConnection: Upgrade\r\nUpgrade: websocket\r\n\r\n") + .await + .unwrap(); + assert_eq!( + read_client_websocket_frame_for_test(&mut server).await, + b"rld" + ); + server + .write_all(&server_websocket_frame_for_test(b"world")) + .await + .unwrap(); + assert_eq!(client_task.await.unwrap().unwrap(), *b"world"); + } + + #[tokio::test] + async fn websocket_early_data_without_payload_omits_protocol_header() { + let (client, mut server) = tokio::io::duplex(4096); + let config = ShadowsocksWebSocketTransport { + kind: ShadowsocksWebSocketKind::V2ray, + host: "front.example".to_owned(), + path: "/ws?ed=8".to_owned(), + headers: Vec::new(), + tls: false, + ech: None, + skip_cert_verify: false, + certificate_fingerprint: None, + client_identity: None, + mux: ShadowsocksWebSocketMux::None, + v2ray_http_upgrade: false, + v2ray_http_upgrade_fast_open: false, + }; + + let client_task = tokio::spawn(async move { + let mut stream = websocket_plugin_connect(Box::new(client), &config, 443).await?; + let mut body = [0u8; 5]; + stream.read_exact(&mut body).await?; + Result::<[u8; 5]>::Ok(body) + }); + + let request = read_http_headers_for_test(&mut server).await; + assert!(request.starts_with("GET /ws HTTP/1.1\r\n")); + assert!(!request.contains("Sec-WebSocket-Protocol:")); + server + .write_all(b"HTTP/1.1 101 Switching Protocols\r\nConnection: Upgrade\r\nUpgrade: websocket\r\n\r\n") + .await + .unwrap(); + server + .write_all(&server_websocket_frame_for_test(b"world")) + .await + .unwrap(); + assert_eq!(client_task.await.unwrap().unwrap(), *b"world"); + } + + #[tokio::test] + async fn websocket_http_upgrade_leaves_stream_raw() { + let (client, mut server) = tokio::io::duplex(4096); + let config = ShadowsocksWebSocketTransport { + kind: ShadowsocksWebSocketKind::V2ray, + host: "front.example".to_owned(), + path: "/upgrade".to_owned(), + headers: Vec::new(), + tls: false, + ech: None, + skip_cert_verify: false, + certificate_fingerprint: None, + client_identity: None, + mux: ShadowsocksWebSocketMux::None, + v2ray_http_upgrade: true, + v2ray_http_upgrade_fast_open: false, + }; + + let client_task = tokio::spawn(async move { + let mut stream = websocket_plugin_connect(Box::new(client), &config, 443).await?; + stream.write_all(b"raw").await?; + stream.flush().await?; + let mut body = [0u8; 4]; + stream.read_exact(&mut body).await?; + Result::<[u8; 4]>::Ok(body) + }); + + let request = read_http_headers_for_test(&mut server).await; + assert!(request.starts_with("GET /upgrade HTTP/1.1\r\n")); + assert!(!request.contains("Sec-WebSocket-Key")); + server + .write_all(b"HTTP/1.1 101 Switching Protocols\r\nConnection: Upgrade\r\nUpgrade: websocket\r\n\r\n") + .await + .unwrap(); + let mut raw = [0u8; 3]; + server.read_exact(&mut raw).await.unwrap(); + assert_eq!(&raw, b"raw"); + server.write_all(b"pong").await.unwrap(); + assert_eq!(client_task.await.unwrap().unwrap(), *b"pong"); + } + + #[tokio::test] + async fn websocket_http_upgrade_fast_open_writes_before_response() { + let (client, mut server) = tokio::io::duplex(4096); + let config = ShadowsocksWebSocketTransport { + kind: ShadowsocksWebSocketKind::V2ray, + host: "front.example".to_owned(), + path: "/upgrade".to_owned(), + headers: Vec::new(), + tls: false, + ech: None, + skip_cert_verify: false, + certificate_fingerprint: None, + client_identity: None, + mux: ShadowsocksWebSocketMux::None, + v2ray_http_upgrade: true, + v2ray_http_upgrade_fast_open: true, + }; + + let client_task = tokio::spawn(async move { + let mut stream = websocket_plugin_connect(Box::new(client), &config, 443).await?; + stream.write_all(b"raw").await?; + stream.flush().await?; + let mut body = [0u8; 4]; + stream.read_exact(&mut body).await?; + Result::<[u8; 4]>::Ok(body) + }); + + let request = read_http_headers_for_test(&mut server).await; + assert!(request.starts_with("GET /upgrade HTTP/1.1\r\n")); + let mut raw = [0u8; 3]; + server.read_exact(&mut raw).await.unwrap(); + assert_eq!(&raw, b"raw"); + server + .write_all(b"HTTP/1.1 101 Switching Protocols\r\nConnection: Upgrade\r\nUpgrade: websocket\r\n\r\npong") + .await + .unwrap(); + assert_eq!(client_task.await.unwrap().unwrap(), *b"pong"); + } + + #[tokio::test] + async fn v2ray_mux_wraps_open_and_data_frames() { + let (client, mut server) = tokio::io::duplex(4096); + let mut stream = V2rayMuxStream::new(client); + + let client_task = tokio::spawn(async move { + stream.write_all(b"hello").await?; + stream.flush().await?; + let mut body = [0u8; 5]; + stream.read_exact(&mut body).await?; + Result::<[u8; 5]>::Ok(body) + }); + + let mut open_len = [0u8; 2]; + server.read_exact(&mut open_len).await.unwrap(); + let open_len = u16::from_be_bytes(open_len) as usize; + let mut open = vec![0u8; open_len]; + server.read_exact(&mut open).await.unwrap(); + assert_eq!(&open[..4], &[0, 0, 0x01, 0x00]); + assert!(open + .windows("127.0.0.1".len()) + .any(|window| window == b"127.0.0.1")); + + let mut data_prefix = [0u8; 8]; + server.read_exact(&mut data_prefix).await.unwrap(); + assert_eq!(&data_prefix[..6], &[0, 4, 0, 0, 0x02, 0x01]); + assert_eq!(u16::from_be_bytes([data_prefix[6], data_prefix[7]]), 5); + let mut payload = [0u8; 5]; + server.read_exact(&mut payload).await.unwrap(); + assert_eq!(&payload, b"hello"); + + server + .write_all(&[0, 4, 0, 0, 0x02, 0x01, 0, 5]) + .await + .unwrap(); + server.write_all(b"world").await.unwrap(); + assert_eq!(client_task.await.unwrap().unwrap(), *b"world"); + } + + #[tokio::test] + async fn gost_smux_stream_opens_client_stream() { + let (client, server) = tokio::io::duplex(4096); + let client_task = tokio::spawn(async move { + let mut stream = gost_smux_stream(Box::new(client)).await?; + stream.write_all(b"hello").await?; + stream.flush().await?; + let mut body = [0u8; 5]; + stream.read_exact(&mut body).await?; + Result::<[u8; 5]>::Ok(body) + }); + + let mut config = tokio_smux::SmuxConfig::default(); + config.keep_alive_disable = true; + let mut session = tokio_smux::Session::server(server, config).unwrap(); + let mut stream = session.accept_stream().await.unwrap(); + assert_eq!(stream.recv_message().await.unwrap().unwrap(), b"hello"); + stream.send_message(b"world".to_vec()).await.unwrap(); + assert_eq!(client_task.await.unwrap().unwrap(), *b"world"); + } + + async fn read_http_headers_for_test(stream: &mut S) -> String + where + S: AsyncRead + Unpin, + { + let mut bytes = Vec::new(); + let mut byte = [0u8; 1]; + loop { + stream.read_exact(&mut byte).await.unwrap(); + bytes.push(byte[0]); + if bytes.ends_with(b"\r\n\r\n") { + return String::from_utf8(bytes).unwrap(); + } + } + } + + async fn read_client_websocket_frame_for_test(stream: &mut S) -> Vec + where + S: AsyncRead + Unpin, + { + let mut header = [0u8; 2]; + stream.read_exact(&mut header).await.unwrap(); + assert_eq!(header[0] & 0x0f, 0x02); + assert_ne!(header[1] & 0x80, 0); + let len = usize::from(header[1] & 0x7f); + let mut mask = [0u8; 4]; + stream.read_exact(&mut mask).await.unwrap(); + let mut payload = vec![0u8; len]; + stream.read_exact(&mut payload).await.unwrap(); + apply_websocket_mask(&mut payload, mask); + payload + } + + fn server_websocket_frame_for_test(payload: &[u8]) -> Vec { + let mut frame = Vec::new(); + frame.push(0x82); + frame.push(payload.len() as u8); + frame.extend_from_slice(payload); + frame + } + + #[tokio::test] + async fn custom_aead_byte_reader_reassembles_uot_frame_chunks() { + let kind = CustomSsAeadKind::Aes192Gcm; + let key = Arc::<[u8]>::from(evp_bytes_to_key(b"secret", kind.key_len())); + let target = "8.8.8.8:53".parse::().unwrap(); + let frame = uot_frame(target, b"hello").unwrap(); + let split_at = 3; + let (client, server) = tokio::io::duplex(256); + let mut writer = CustomSsAeadWriter::new(client, kind, key.clone()) + .await + .unwrap(); + let mut reader = + CustomSsAeadByteReader::new(CustomSsAeadReader::new(server, kind, key.clone())); + + let write = tokio::spawn(async move { + writer.write_chunk(&frame[..split_at]).await?; + writer.write_chunk(&frame[split_at..]).await?; + Result::<()>::Ok(()) + }); + + let (decoded_addr, payload) = reader.read_frame().await.unwrap().unwrap(); + write.await.unwrap().unwrap(); + assert_eq!(decoded_addr, target); + assert_eq!(payload, b"hello"); + } + + #[test] + fn aead2022_aes_ccm_keys_follow_mihomo_base64_rules() { + let kind = Aead2022AesCcmKind::Aes128; + let identity = vec![1u8; kind.key_len()]; + let user = vec![2u8; kind.key_len()]; + let password = format!( + "{}:{}", + BASE64_STANDARD.encode(&identity), + BASE64_STANDARD.encode(&user) + ); + let (parsed_user, parsed_identities) = parse_aead2022_keys(kind, &password).unwrap(); + assert_eq!(parsed_user.as_ref(), user.as_slice()); + assert_eq!(parsed_identities.len(), 1); + assert_eq!(parsed_identities[0].as_ref(), identity.as_slice()); + + assert!(parse_aead2022_keys(kind, &BASE64_STANDARD.encode([3u8; 64])).is_err()); + assert!(parse_aead2022_keys(kind, &BASE64_STANDARD.encode([1u8; 8])).is_err()); + } + + #[test] + fn standard_aead2022_keys_follow_mihomo_base64_rules() { + let identity = vec![1u8; 16]; + let user = vec![2u8; 16]; + let password = format!( + "{}:{}", + BASE64_STANDARD.encode(&identity), + BASE64_STANDARD.encode(&user) + ); + let config = ShadowsocksNode { + cipher: "2022-blake3-aes-128-gcm".to_owned(), + password, + udp: true, + udp_over_tcp: false, + udp_over_tcp_version: UOT_VERSION, + client_fingerprint: None, + plugin: None, + smux: None, + }; + let node = ProxyNode { + ordinal: 0, + name: "ss-2022-gcm".to_owned(), + protocol: crate::proxy_subscription::ProxyProtocol::Shadowsocks, + server: "127.0.0.1".to_owned(), + port: 8388, + warnings: Vec::new(), + config: ProxyNodeConfig::Shadowsocks(config.clone()), + }; + + let protocol = shadowsocks_protocol_for_node(&node, &config).unwrap(); + let ShadowsocksProtocol::Standard { server_config, .. } = protocol else { + panic!("expected delegated standard Shadowsocks protocol"); + }; + assert_eq!(server_config.key(), user.as_slice()); + assert_eq!(server_config.identity_keys().len(), 1); + assert_eq!( + server_config.identity_keys()[0].as_ref(), + identity.as_slice() + ); + + let unpadded_user = BASE64_STANDARD + .encode(&user) + .trim_end_matches('=') + .to_owned(); + assert!( + validate_standard_aead2022_mihomo_password( + SsCipherKind::AEAD2022_BLAKE3_AES_128_GCM, + &unpadded_user + ) + .is_err(), + "Mihomo's base64.StdEncoding requires key padding for 16-byte keys" + ); + assert!( + validate_standard_aead2022_mihomo_password( + SsCipherKind::AEAD2022_BLAKE3_CHACHA20_POLY1305, + &format!( + "{}:{}", + BASE64_STANDARD.encode([3u8; 32]), + BASE64_STANDARD.encode([4u8; 32]) + ) + ) + .is_err(), + "Mihomo rejects EIH identity chains on Shadowsocks 2022 chacha methods" + ); + validate_standard_aead2022_mihomo_password( + SsCipherKind::AEAD2022_BLAKE3_CHACHA20_POLY1305, + &BASE64_STANDARD.encode([5u8; 32]), + ) + .unwrap(); + } + + #[test] + fn aead2022_aes_ccm_tcp_cipher_round_trips_chunks() { + for kind in [Aead2022AesCcmKind::Aes128, Aead2022AesCcmKind::Aes256] { + let key = vec![7u8; kind.key_len()]; + let salt = vec![9u8; kind.key_len()]; + let mut writer = Aead2022TcpCipher::new(kind, &key, &salt).unwrap(); + let mut reader = Aead2022TcpCipher::new(kind, &key, &salt).unwrap(); + + let mut encrypted_len = writer.seal_packet(&5u16.to_be_bytes()).unwrap(); + let len = reader.open_packet(&mut encrypted_len).unwrap(); + assert_eq!(u16::from_be_bytes([len[0], len[1]]), 5); + + let mut encrypted_payload = writer.seal_packet(b"hello").unwrap(); + let payload = reader.open_packet(&mut encrypted_payload).unwrap(); + assert_eq!(payload, b"hello"); + } + } + + #[test] + fn aead2022_aes_ccm_udp_packets_round_trip_client_and_server_shapes() { + for kind in [Aead2022AesCcmKind::Aes128, Aead2022AesCcmKind::Aes256] { + let user_key = Arc::<[u8]>::from(vec![5u8; kind.key_len()]); + let mut session = + Aead2022AesCcmUdpSession::new(kind, user_key.clone(), Arc::from(Vec::new())) + .unwrap(); + let target = "8.8.8.8:443".parse::().unwrap(); + let packet = session.encrypt_client_packet(target, b"hello").unwrap(); + let (decoded_target, decoded_payload) = + decrypt_aead2022_client_packet_for_test(kind, &user_key, &packet).unwrap(); + assert_eq!(decoded_target, target); + assert_eq!(decoded_payload, b"hello"); + + let source = "1.1.1.1:53".parse::().unwrap(); + let response = encrypt_aead2022_server_packet_for_test( + kind, + &user_key, + 99, + 1, + session.client_session_id, + source, + b"world", + ) + .unwrap(); + let (decoded_source, decoded_payload) = + session.decrypt_server_packet(&response).unwrap(); + assert_eq!(decoded_source, source); + assert_eq!(decoded_payload, b"world"); + } + } + + #[test] + fn custom_shadowsocks_aead_udp_packet_round_trips_mihomo_extra_methods() { + for cipher in [ + "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-128-gcm", + "lea-192-gcm", + "lea-256-gcm", + ] { + let kind = CustomSsAeadKind::from_name(cipher).unwrap(); + let key = evp_bytes_to_key(b"secret", kind.key_len()); + let target = "8.8.8.8:53".parse().unwrap(); + let packet = encrypt_custom_ss_udp_packet(kind, &key, target, b"hello").unwrap(); + let (decoded_target, payload) = + decrypt_custom_ss_udp_packet(kind, &key, &packet).unwrap(); + assert_eq!(decoded_target, target); + assert_eq!(payload, b"hello"); + } + } + + #[test] + fn custom_shadowsocks_stream_udp_packet_round_trips_mihomo_methods() { + for cipher in ["chacha20", "xchacha20"] { + let kind = CustomSsStreamKind::from_name(cipher).unwrap(); + let key = evp_bytes_to_key(b"secret", kind.key_len()); + let target = "8.8.8.8:53".parse().unwrap(); + let packet = encrypt_custom_ss_stream_udp_packet(kind, &key, target, b"hello").unwrap(); + let (decoded_target, payload) = + decrypt_custom_ss_stream_udp_packet(kind, &key, &packet).unwrap(); + assert_eq!(decoded_target, target); + assert_eq!(payload, b"hello"); + } + } + + #[tokio::test] + async fn custom_shadowsocks_stream_tcp_cipher_keeps_state_across_writes() { + for cipher in ["chacha20", "xchacha20"] { + let kind = CustomSsStreamKind::from_name(cipher).unwrap(); + let key = Arc::<[u8]>::from(evp_bytes_to_key(b"secret", kind.key_len())); + let (client, mut server) = tokio::io::duplex(1024); + let mut writer = CustomSsStreamWriter::new(client, kind, key.clone()) + .await + .unwrap(); + writer.write_all_encrypted(b"hello").await.unwrap(); + writer.write_all_encrypted(b"world").await.unwrap(); + writer.shutdown().await.unwrap(); + + let mut raw = Vec::new(); + server.read_to_end(&mut raw).await.unwrap(); + let (salt, encrypted) = raw.split_at(kind.salt_len()); + let mut decrypted = encrypted.to_vec(); + let mut cipher = CustomSsStreamCipher::new(kind, &key, salt).unwrap(); + cipher.apply_keystream(&mut decrypted); + assert_eq!(decrypted, b"helloworld"); + + let (mut server, client) = tokio::io::duplex(1024); + let salt = vec![7u8; kind.salt_len()]; + let mut encrypted = b"response".to_vec(); + let mut cipher = CustomSsStreamCipher::new(kind, &key, &salt).unwrap(); + cipher.apply_keystream(&mut encrypted[..3]); + cipher.apply_keystream(&mut encrypted[3..]); + server.write_all(&salt).await.unwrap(); + server.write_all(&encrypted).await.unwrap(); + drop(server); + + let mut reader = CustomSsStreamReader::new(client, kind, key); + let mut response = Vec::new(); + let mut output = [0u8; 4]; + loop { + let n = reader.read_decrypted(&mut output).await.unwrap(); + if n == 0 { + break; + } + response.extend_from_slice(&output[..n]); + } + assert_eq!(response, b"response"); + } + } + + #[test] + fn custom_ascon128_matches_v12_reference_vector() { + let key: [u8; 16] = core::array::from_fn(|index| index as u8); + let nonce: [u8; 16] = core::array::from_fn(|index| index as u8); + let mut plaintext = Vec::new(); + let tag = ascon_seal_in_place(&key, &nonce, AsconMode::Ascon128, &mut plaintext); + assert_eq!(hex_lower(&tag), "e355159f292911f794cb1432a0103a8a"); + + let mut plaintext = Vec::new(); + let tag = ascon_seal_in_place(&key, &nonce, AsconMode::Ascon128A, &mut plaintext); + assert_eq!(hex_lower(&tag), "7a834e6f09210957067b10fd831f0078"); + } + + #[test] + fn custom_lea_gcm_matches_mihomo_vectors() { + for (cipher, expected) in [ + ("lea-128-gcm", "b644a1f5ad436a94f4eac45f12010eed1396e49e9b"), + ("lea-192-gcm", "9ed6792982070248de7ddcb355da658774ab781ea0"), + ("lea-256-gcm", "223060f9a91775199a3f9331cd8d410e5935b0be50"), + ] { + let kind = CustomSsAeadKind::from_name(cipher).unwrap(); + let key: Vec = (0..kind.key_len()).map(|index| index as u8).collect(); + let cipher = CustomSsAeadCipher::new(kind, &key).unwrap(); + let mut nonce: Vec = (0..kind.nonce_len()).map(|index| index as u8).collect(); + let mut message = b"hello".to_vec(); + cipher.seal_in_place(&mut nonce, &mut message).unwrap(); + assert_eq!(hex_lower(&message), expected); + } + } + + fn decrypt_aead2022_client_packet_for_test( + kind: Aead2022AesCcmKind, + user_key: &[u8], + packet: &[u8], + ) -> Result<(SocketAddr, Vec)> { + let mut buf = packet.to_vec(); + aead2022_aes_decrypt_block(kind, user_key, &mut buf[..16])?; + let session_id_bytes: [u8; 8] = buf[..8].try_into().expect("fixed session id"); + let cipher = Aead2022AesCcmCipher::new( + kind, + &aead2022_session_key(kind, user_key, &session_id_bytes)?, + )?; + let nonce: [u8; 12] = buf[4..16].try_into().expect("fixed nonce"); + let plaintext = cipher.open_in_place(&nonce, &mut buf[16..])?; + if plaintext[0] != AEAD2022_HEADER_TYPE_CLIENT { + bail!("unexpected test client packet type"); + } + let padding_len = u16::from_be_bytes([plaintext[9], plaintext[10]]) as usize; + let address_offset = 11 + padding_len; + let (target, used) = parse_socks_addr(&plaintext[address_offset..])?; + Ok((target, plaintext[address_offset + used..].to_vec())) + } + + fn encrypt_aead2022_server_packet_for_test( + kind: Aead2022AesCcmKind, + user_key: &[u8], + session_id: u64, + packet_id: u64, + client_session_id: u64, + source: SocketAddr, + payload: &[u8], + ) -> Result> { + let session_id_bytes = session_id.to_be_bytes(); + let cipher = Aead2022AesCcmCipher::new( + kind, + &aead2022_session_key(kind, user_key, &session_id_bytes)?, + )?; + let mut packet = Vec::new(); + packet.extend_from_slice(&session_id.to_be_bytes()); + packet.extend_from_slice(&packet_id.to_be_bytes()); + let data_index = packet.len(); + packet.push(AEAD2022_HEADER_TYPE_SERVER); + packet.extend_from_slice(&aead2022_now_timestamp()?.to_be_bytes()); + packet.extend_from_slice(&client_session_id.to_be_bytes()); + packet.extend_from_slice(&0u16.to_be_bytes()); + packet.extend_from_slice(&socks_addr(source)?); + packet.extend_from_slice(payload); + let nonce: [u8; 12] = packet[4..16].try_into().expect("fixed nonce"); + let mut encrypted_body = packet[data_index..].to_vec(); + cipher.seal_in_place(&nonce, &mut encrypted_body)?; + packet.truncate(data_index); + packet.extend_from_slice(&encrypted_body); + aead2022_aes_encrypt_block(kind, user_key, &mut packet[..16])?; + Ok(packet) + } + + #[test] + fn shadowsocks_address_preserves_fake_dns_hostname() { + let target = ProxyTcpTarget { + address: "198.18.64.1:443".parse().unwrap(), + hostname: Some("example.com.".to_owned()), + }; + + assert_eq!( + shadowsocks_addr_for_target(&target).unwrap(), + SsAddress::DomainNameAddress("example.com".to_owned(), 443) + ); + } + + #[test] + fn trojan_runtime_uses_mihomo_default_alpn_when_absent() { + let mut node = TrojanNode { + password: "secret".to_owned(), + sni: None, + alpn: Vec::new(), + alpn_present: false, + skip_cert_verify: false, + network: TrojanNetwork::Tcp, + udp: true, + client_fingerprint: None, + fingerprint: None, + }; + assert_eq!(trojan_alpn(&node), vec!["h2", "http/1.1"]); + + node.alpn_present = true; + assert!(trojan_alpn(&node).is_empty()); + + node.alpn = vec!["h3".to_owned()]; + assert_eq!(trojan_alpn(&node), vec!["h3"]); + } + + #[test] + fn parses_certificate_fingerprint_hex() { + let fp = parse_certificate_fingerprint( + "00:112233445566778899aabbccddeeff00112233445566778899aabbccddeeff", + ) + .unwrap(); + assert_eq!(fp.0[0], 0x00); + assert_eq!(fp.0[1], 0x11); + assert_eq!(fp.0[31], 0xff); + assert!(parse_certificate_fingerprint("chrome").is_err()); + assert!(parse_certificate_fingerprint("abcd").is_err()); + } + + #[tokio::test] + async fn trojan_udp_writer_fragments_oversized_payloads() { + let target = socks_addr("1.2.3.4:53".parse().unwrap()).unwrap(); + let payload = vec![7u8; TROJAN_MAX_UDP_PAYLOAD + 3]; + let (mut writer, mut reader) = tokio::io::duplex(20_000); + + let write = + tokio::spawn( + async move { write_trojan_udp_packet(&mut writer, &target, &payload).await }, + ); + + let mut encoded = Vec::new(); + reader.read_to_end(&mut encoded).await.unwrap(); + write.await.unwrap().unwrap(); + + let first_payload_offset = 7 + 2 + 2; + assert_eq!( + u16::from_be_bytes([encoded[7], encoded[8]]) as usize, + TROJAN_MAX_UDP_PAYLOAD + ); + assert_eq!(&encoded[9..11], b"\r\n"); + assert_eq!(encoded[first_payload_offset], 7); + let second = 7 + 2 + 2 + TROJAN_MAX_UDP_PAYLOAD; + assert_eq!( + u16::from_be_bytes([encoded[second + 7], encoded[second + 8]]) as usize, + 3 + ); + assert_eq!(&encoded[second + 9..second + 11], b"\r\n"); + assert_eq!(encoded.len(), second + 11 + 3); + } + + #[test] + fn fake_ip_detection_flags_benchmark_range() { + assert!(is_fake_or_benchmark_ip("198.18.0.1".parse().unwrap())); + assert!(is_fake_or_benchmark_ip("198.19.255.254".parse().unwrap())); + assert!(!is_fake_or_benchmark_ip("198.20.0.1".parse().unwrap())); + assert!(!is_fake_or_benchmark_ip("2001:db8::1".parse().unwrap())); + } + + #[test] + fn proxy_outbound_socket_binding_skips_loopback() { + assert!(!should_bind_proxy_outbound_socket( + "127.0.0.1".parse().unwrap() + )); + assert!(!should_bind_proxy_outbound_socket("::1".parse().unwrap())); + assert!(should_bind_proxy_outbound_socket( + "192.0.2.10".parse().unwrap() + )); + } + + #[test] + fn dns_query_encodes_host_labels() { + let query = build_dns_query("example.com", 1).unwrap(); + assert_eq!(&query[12..25], b"\x07example\x03com\0"); + assert_eq!(u16::from_be_bytes([query[25], query[26]]), 1); + assert_eq!(u16::from_be_bytes([query[27], query[28]]), 1); + } + + #[test] + fn dns_response_parser_reads_compressed_a_and_aaaa_answers() { + let mut response = Vec::new(); + response.extend_from_slice(&0x1234u16.to_be_bytes()); + response.extend_from_slice(&0x8180u16.to_be_bytes()); + response.extend_from_slice(&1u16.to_be_bytes()); + response.extend_from_slice(&2u16.to_be_bytes()); + response.extend_from_slice(&0u16.to_be_bytes()); + response.extend_from_slice(&0u16.to_be_bytes()); + response.extend_from_slice(b"\x07example\x03com\0"); + response.extend_from_slice(&1u16.to_be_bytes()); + response.extend_from_slice(&1u16.to_be_bytes()); + response.extend_from_slice(&[0xc0, 0x0c]); + response.extend_from_slice(&1u16.to_be_bytes()); + response.extend_from_slice(&1u16.to_be_bytes()); + response.extend_from_slice(&60u32.to_be_bytes()); + response.extend_from_slice(&4u16.to_be_bytes()); + response.extend_from_slice(&[93, 184, 216, 34]); + response.extend_from_slice(&[0xc0, 0x0c]); + response.extend_from_slice(&28u16.to_be_bytes()); + response.extend_from_slice(&1u16.to_be_bytes()); + response.extend_from_slice(&60u32.to_be_bytes()); + response.extend_from_slice(&16u16.to_be_bytes()); + response.extend_from_slice(&[ + 0x26, 0x06, 0x28, 0x00, 0x02, 0x20, 0x00, 0x01, 0x02, 0x48, 0x18, 0x93, 0x25, 0xc8, + 0x19, 0x46, + ]); + + let addresses = parse_dns_response(&response, 0x1234).unwrap(); + assert_eq!( + addresses, + vec![ + "93.184.216.34".parse::().unwrap(), + "2606:2800:220:1:248:1893:25c8:1946" + .parse::() + .unwrap(), + ] + ); + } + + #[test] + fn dns_https_parser_extracts_ech_service_parameter() { + let mut response = build_dns_query("example.com", DNS_TYPE_HTTPS).unwrap(); + response[0..2].copy_from_slice(&0x1234u16.to_be_bytes()); + response[2..4].copy_from_slice(&0x8180u16.to_be_bytes()); + response[6..8].copy_from_slice(&1u16.to_be_bytes()); + response.extend_from_slice(&[0xc0, 0x0c]); + response.extend_from_slice(&DNS_TYPE_HTTPS.to_be_bytes()); + response.extend_from_slice(&1u16.to_be_bytes()); + response.extend_from_slice(&60u32.to_be_bytes()); + response.extend_from_slice(&10u16.to_be_bytes()); + response.extend_from_slice(&1u16.to_be_bytes()); + response.push(0); + response.extend_from_slice(&HTTPS_SVC_PARAM_ECH.to_be_bytes()); + response.extend_from_slice(&3u16.to_be_bytes()); + response.extend_from_slice(&[0xaa, 0xbb, 0xcc]); + + assert_eq!( + parse_dns_https_ech_config_list(&response, 0x1234).unwrap(), + Some(vec![0xaa, 0xbb, 0xcc]) + ); + } + + #[tokio::test] + async fn fake_dns_response_returns_stable_hostname_mapping() { + let query = build_dns_query("example.com", 1).unwrap(); + let cache = Arc::new(RwLock::new(DnsHostnameCacheState::new())); + let response = build_fake_dns_response(&cache, &query).await.unwrap(); + let addresses = parse_dns_response(&response, 0).unwrap(); + assert_eq!(addresses, vec!["198.18.64.1".parse::().unwrap()]); + assert_eq!( + cache + .read() + .await + .lookup_hostname("198.18.64.1".parse().unwrap()), + Some("example.com".to_owned()) + ); + } + + #[test] + fn runtime_selection_skips_unsupported_transports_when_unselected() { + let preview = crate::proxy_subscription::parse_subscription_body( + r#" +proxies: + - name: trojan-grpc + type: trojan + server: example.com + port: 443 + password: secret + network: grpc + - name: ss-aead + type: ss + server: example.net + port: 8388 + cipher: aes-128-gcm + password: secret +"#, + Some("https://example.com/sub"), + ); + let payload = crate::proxy_subscription::build_payload( + preview, + "https://example.com/sub", + Some("Proxy Subscription".to_owned()), + None, + ); + assert_eq!(selected_node(&payload).unwrap().name, "ss-aead"); + } + + #[test] + fn runtime_selection_prefers_selected_name_over_stale_ordinal() { + let preview = crate::proxy_subscription::parse_subscription_body( + r#" +proxies: + - name: first + type: ss + server: first.example.net + port: 8388 + cipher: aes-128-gcm + password: secret + - name: selected + type: ss + server: selected.example.net + port: 8388 + cipher: aes-128-gcm + password: secret +"#, + Some("https://example.com/sub"), + ); + let mut payload = crate::proxy_subscription::build_payload( + preview, + "https://example.com/sub", + Some("Proxy Subscription".to_owned()), + Some(0), + ); + payload.selected_name = Some("selected".to_owned()); + + assert_eq!(selected_node(&payload).unwrap().name, "selected"); + } + + #[test] + fn proxy_server_bypass_routes_use_host_prefixes() { + let routes = excluded_routes_for_server("127.0.0.1", 443).unwrap(); + assert_eq!(routes, vec!["127.0.0.1/32"]); + } + + #[test] + fn parses_physical_dns_from_scutil_output() { + let servers = parse_apple_physical_dns_servers( + r#" +DNS configuration + +resolver #1 + nameserver[0] : 2606:4700:4700::1111 + nameserver[1] : 1.1.1.1 + if_index : 35 (utun8) + +DNS configuration (for scoped queries) + +resolver #1 + search domain[0] : lan + nameserver[0] : 192.168.2.1 + if_index : 17 (en0) + +resolver #2 + nameserver[0] : 198.18.0.1 + if_index : 35 (utun8) +"#, + ); + assert_eq!(servers, vec!["192.168.2.1"]); + } + + #[test] + fn parses_resolv_conf_dns_servers() { + let servers = parse_resolv_conf_dns_servers( + r#" +# macOS generated resolver file +nameserver 192.168.2.1 +nameserver 198.18.0.1 +nameserver 192.168.2.1 +nameserver ::1 +"#, + ); + assert_eq!(servers, vec!["192.168.2.1"]); + } + + #[test] + fn proxy_dns_excluded_routes_use_host_prefixes() { + let routes = proxy_dns_excluded_routes(&[ + "100.64.0.2".to_owned(), + "192.168.2.1".to_owned(), + "2001:db8::53".to_owned(), + "not-an-ip".to_owned(), + ]); + assert_eq!(routes, vec!["192.168.2.1/32", "2001:db8::53/128"]); + } + + #[test] + fn proxy_dns_servers_use_daemon_resolver() { + assert_eq!(proxy_dns_servers(), vec!["100.64.0.2"]); + } +} diff --git a/burrow/src/proxy_subscription.rs b/burrow/src/proxy_subscription.rs index 790f57a..428f74d 100644 --- a/burrow/src/proxy_subscription.rs +++ b/burrow/src/proxy_subscription.rs @@ -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, pub plugin: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub smux: Option, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] @@ -122,6 +127,38 @@ pub struct ShadowsocksPlugin { pub opts: HashMap, } +#[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, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub max_connections: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub min_streams: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub max_streams: Option, + #[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, +} + +#[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, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub down: Option, +} + #[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 { + 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) -> Result } fn parse_yaml_shadowsocks(ordinal: usize, mapping: &HashMap) -> Result { - 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) -> 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 { .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::().ok()) + .or_else(|| { + query + .get("uot-version") + .and_then(|value| value.parse::().ok()) + }) + .unwrap_or(UOT_LEGACY_VERSION); Ok(ProxyNode { ordinal, @@ -599,7 +657,13 @@ fn parse_shadowsocks_uri(uri: &str, ordinal: usize) -> Result { 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, key: &str) -> Option<&'a mapping.get(key).and_then(Value::as_str) } +fn yaml_scalar_string(mapping: &HashMap, key: &str) -> Option { + yaml_value_to_scalar_string(mapping.get(key)?) +} + +fn yaml_value_to_scalar_string(value: &Value) -> Option { + 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, key: &str) -> Option> { let values = mapping.get(key)?.as_sequence()?; Some( @@ -654,6 +732,10 @@ fn required_yaml_string<'a>(mapping: &'a HashMap, key: &str) -> R yaml_string(mapping, key).ok_or_else(|| anyhow!("missing `{key}`")) } +fn required_yaml_scalar_string(mapping: &HashMap, key: &str) -> Result { + yaml_scalar_string(mapping, key).ok_or_else(|| anyhow!("missing `{key}`")) +} + fn required_yaml_u16(mapping: &HashMap, key: &str) -> Result { 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, key: &str) -> Option { 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, key: &str) -> Option { + 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::().ok())) +} + +fn yaml_u32(mapping: &HashMap, key: &str) -> Option { + 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::().ok())) +} + +fn yaml_smux(mapping: &HashMap) -> Option { + 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) -> Option { - 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 = 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 { + yaml_value_to_scalar_string(value).or_else(|| { + value.as_sequence().map(|values| { + values + .iter() + .filter_map(yaml_value_to_scalar_string) + .collect::>() + .join(",") + }) + }) +} + fn parse_ss_plugin(plugin: &str) -> Option { + 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, +) { + 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 { @@ -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::::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] diff --git a/deploy/railway-sing-box/Dockerfile b/deploy/railway-sing-box/Dockerfile new file mode 100644 index 0000000..2b2c4cf --- /dev/null +++ b/deploy/railway-sing-box/Dockerfile @@ -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"] diff --git a/deploy/railway-sing-box/README.md b/deploy/railway-sing-box/README.md new file mode 100644 index 0000000..64d4c87 --- /dev/null +++ b/deploy/railway-sing-box/README.md @@ -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= + 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 = "" +host = "" +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. diff --git a/deploy/railway-sing-box/entrypoint.sh b/deploy/railway-sing-box/entrypoint.sh new file mode 100644 index 0000000..1ba2800 --- /dev/null +++ b/deploy/railway-sing-box/entrypoint.sh @@ -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 < + +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. diff --git a/third_party/rustls-fork-shadow-tls/LICENSE-MIT b/third_party/rustls-fork-shadow-tls/LICENSE-MIT new file mode 100644 index 0000000..ef480e6 --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2016 Joseph Birr-Pixton + +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. diff --git a/third_party/rustls-fork-shadow-tls/README.md b/third_party/rustls-fork-shadow-tls/README.md new file mode 100644 index 0000000..5ddb46d --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/README.md @@ -0,0 +1,298 @@ +

+ +

+ +

+Rustls is a modern TLS library written in Rust. It uses ring for cryptography and webpki for certificate +verification. +

+ +# 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 ...] + 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 + 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. diff --git a/third_party/rustls-fork-shadow-tls/benches/benchmarks.rs b/third_party/rustls-fork-shadow-tls/benches/benchmarks.rs new file mode 100644 index 0000000..08191be --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/benches/benchmarks.rs @@ -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); diff --git a/third_party/rustls-fork-shadow-tls/build.rs b/third_party/rustls-fork-shadow-tls/build.rs new file mode 100644 index 0000000..9c73252 --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/build.rs @@ -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"); +} diff --git a/third_party/rustls-fork-shadow-tls/examples/internal/bench.rs b/third_party/rustls-fork-shadow-tls/examples/internal/bench.rs new file mode 100644 index 0000000..5ef69d0 --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/examples/internal/bench.rs @@ -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(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(mut f: F) -> f64 +where + F: FnMut(), +{ + let start = Instant::now(); + f(); + let end = Instant::now(); + duration_nanos(end.duration_since(start)) +} + +fn transfer(left: &mut L, right: &mut R, expect_data: Option) -> f64 +where + L: DerefMut + Deref>, + R: DerefMut + Deref>, + 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_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_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, +) -> 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::() + .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) { + 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::() + .expect("3rd arg must be plaintext size integer") + }) + .unwrap_or(1048576); + let mfs = args.next().map(|arg| { + arg.parse::() + .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::() + .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(); + } +} diff --git a/third_party/rustls-fork-shadow-tls/examples/internal/bogo_shim.rs b/third_party/rustls-fork-shadow-tls/examples/internal/bogo_shim.rs new file mode 100644 index 0000000..6e4de62 --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/examples/internal/bogo_shim.rs @@ -0,0 +1,1195 @@ +// This is a test shim for the BoringSSL-Go ('bogo') TLS +// test suite. See bogo/ for this in action. +// +// https://boringssl.googlesource.com/boringssl/+/master/ssl/test +// + +use base64; +use env_logger; +use rustls; + +use rustls::internal::msgs::codec::{Codec, Reader}; +use rustls::internal::msgs::persist; +use rustls::quic::{self, ClientQuicExt, QuicExt, ServerQuicExt}; +use rustls::server::ClientHello; +use rustls::{CipherSuite, ProtocolVersion}; +use rustls::{ClientConnection, Connection, ServerConnection}; + +use std::convert::TryInto; +use std::env; +use std::fs; +use std::io; +use std::io::BufReader; +use std::io::{Read, Write}; +use std::net; +use std::process; +use std::sync::Arc; +use std::time::SystemTime; + +static BOGO_NACK: i32 = 89; + +macro_rules! println_err( + ($($arg:tt)*) => { { + writeln!(&mut ::std::io::stderr(), $($arg)*).unwrap(); + } } +); + +#[derive(Debug)] +struct Options { + port: u16, + server: bool, + max_fragment: Option, + resumes: usize, + verify_peer: bool, + require_any_client_cert: bool, + offer_no_client_cas: bool, + tickets: bool, + resume_with_tickets_disabled: bool, + queue_data: bool, + queue_data_on_resume: bool, + only_write_one_byte_after_handshake: bool, + only_write_one_byte_after_handshake_on_resume: bool, + shut_down_after_handshake: bool, + check_close_notify: bool, + host_name: String, + use_sni: bool, + send_sct: bool, + key_file: String, + cert_file: String, + protocols: Vec, + support_tls13: bool, + support_tls12: bool, + min_version: Option, + max_version: Option, + server_ocsp_response: Vec, + server_sct_list: Vec, + use_signing_scheme: u16, + curves: Option>, + export_keying_material: usize, + export_keying_material_label: String, + export_keying_material_context: String, + export_keying_material_context_used: bool, + read_size: usize, + quic_transport_params: Vec, + expect_quic_transport_params: Vec, + enable_early_data: bool, + expect_ticket_supports_early_data: bool, + expect_accept_early_data: bool, + expect_reject_early_data: bool, + expect_version: u16, + resumption_delay: u32, +} + +impl Options { + fn new() -> Self { + Options { + port: 0, + server: false, + max_fragment: None, + resumes: 0, + verify_peer: false, + tickets: true, + resume_with_tickets_disabled: false, + host_name: "example.com".to_string(), + use_sni: false, + send_sct: false, + queue_data: false, + queue_data_on_resume: false, + only_write_one_byte_after_handshake: false, + only_write_one_byte_after_handshake_on_resume: false, + shut_down_after_handshake: false, + check_close_notify: false, + require_any_client_cert: false, + offer_no_client_cas: false, + key_file: "".to_string(), + cert_file: "".to_string(), + protocols: vec![], + support_tls13: true, + support_tls12: true, + min_version: None, + max_version: None, + server_ocsp_response: vec![], + server_sct_list: vec![], + use_signing_scheme: 0, + curves: None, + export_keying_material: 0, + export_keying_material_label: "".to_string(), + export_keying_material_context: "".to_string(), + export_keying_material_context_used: false, + read_size: 512, + quic_transport_params: vec![], + expect_quic_transport_params: vec![], + enable_early_data: false, + expect_ticket_supports_early_data: false, + expect_accept_early_data: false, + expect_reject_early_data: false, + expect_version: 0, + resumption_delay: 0, + } + } + + fn version_allowed(&self, vers: ProtocolVersion) -> bool { + (self.min_version.is_none() || vers.get_u16() >= self.min_version.unwrap().get_u16()) + && (self.max_version.is_none() || vers.get_u16() <= self.max_version.unwrap().get_u16()) + } + + fn tls13_supported(&self) -> bool { + self.support_tls13 && self.version_allowed(ProtocolVersion::TLSv1_3) + } + + fn tls12_supported(&self) -> bool { + self.support_tls12 && self.version_allowed(ProtocolVersion::TLSv1_2) + } + + fn supported_versions(&self) -> Vec<&'static rustls::SupportedProtocolVersion> { + let mut versions = vec![]; + + if self.tls12_supported() { + versions.push(&rustls::version::TLS12); + } + + if self.tls13_supported() { + versions.push(&rustls::version::TLS13); + } + versions + } +} + +fn load_cert(filename: &str) -> Vec { + let certfile = fs::File::open(filename).expect("cannot open certificate file"); + let mut reader = BufReader::new(certfile); + rustls_pemfile::certs(&mut reader) + .unwrap() + .iter() + .map(|v| rustls::Certificate(v.clone())) + .collect() +} + +fn load_key(filename: &str) -> rustls::PrivateKey { + let keyfile = fs::File::open(filename).expect("cannot open private key file"); + let mut reader = BufReader::new(keyfile); + let keys = rustls_pemfile::pkcs8_private_keys(&mut reader).unwrap(); + assert!(keys.len() == 1); + rustls::PrivateKey(keys[0].clone()) +} + +fn split_protocols(protos: &str) -> Vec { + let mut ret = Vec::new(); + + let mut offs = 0; + while offs < protos.len() { + let len = protos.as_bytes()[offs] as usize; + let item = protos[offs + 1..offs + 1 + len].to_string(); + ret.push(item); + offs += 1 + len; + } + + ret +} + +struct DummyClientAuth { + mandatory: bool, +} + +impl rustls::server::ClientCertVerifier for DummyClientAuth { + fn offer_client_auth(&self) -> bool { + true + } + + fn client_auth_mandatory(&self) -> Option { + Some(self.mandatory) + } + + fn client_auth_root_subjects(&self) -> Option { + Some(rustls::DistinguishedNames::new()) + } + + fn verify_client_cert( + &self, + _end_entity: &rustls::Certificate, + _intermediates: &[rustls::Certificate], + _now: SystemTime, + ) -> Result { + Ok(rustls::server::ClientCertVerified::assertion()) + } +} + +struct DummyServerAuth { + send_sct: bool, +} + +impl rustls::client::ServerCertVerifier for DummyServerAuth { + fn verify_server_cert( + &self, + _end_entity: &rustls::Certificate, + _certs: &[rustls::Certificate], + _hostname: &rustls::ServerName, + _scts: &mut dyn Iterator, + _ocsp: &[u8], + _now: SystemTime, + ) -> Result { + Ok(rustls::client::ServerCertVerified::assertion()) + } + + fn request_scts(&self) -> bool { + self.send_sct + } +} + +struct FixedSignatureSchemeSigningKey { + key: Arc, + scheme: rustls::SignatureScheme, +} + +impl rustls::sign::SigningKey for FixedSignatureSchemeSigningKey { + fn choose_scheme( + &self, + offered: &[rustls::SignatureScheme], + ) -> Option> { + if offered.contains(&self.scheme) { + self.key.choose_scheme(&[self.scheme]) + } else { + self.key.choose_scheme(&[]) + } + } + fn algorithm(&self) -> rustls::SignatureAlgorithm { + self.key.algorithm() + } +} + +struct FixedSignatureSchemeServerCertResolver { + resolver: Arc, + scheme: rustls::SignatureScheme, +} + +impl rustls::server::ResolvesServerCert for FixedSignatureSchemeServerCertResolver { + fn resolve(&self, client_hello: ClientHello) -> Option> { + let mut certkey = self.resolver.resolve(client_hello)?; + Arc::make_mut(&mut certkey).key = Arc::new(FixedSignatureSchemeSigningKey { + key: certkey.key.clone(), + scheme: self.scheme, + }); + Some(certkey) + } +} + +struct FixedSignatureSchemeClientCertResolver { + resolver: Arc, + scheme: rustls::SignatureScheme, +} + +impl rustls::client::ResolvesClientCert for FixedSignatureSchemeClientCertResolver { + fn resolve( + &self, + acceptable_issuers: &[&[u8]], + sigschemes: &[rustls::SignatureScheme], + ) -> Option> { + if !sigschemes.contains(&self.scheme) { + quit(":NO_COMMON_SIGNATURE_ALGORITHMS:"); + } + let mut certkey = self + .resolver + .resolve(acceptable_issuers, sigschemes)?; + Arc::make_mut(&mut certkey).key = Arc::new(FixedSignatureSchemeSigningKey { + key: certkey.key.clone(), + scheme: self.scheme, + }); + Some(certkey) + } + + fn has_certs(&self) -> bool { + self.resolver.has_certs() + } +} + +fn lookup_scheme(scheme: u16) -> rustls::SignatureScheme { + match scheme { + 0x0401 => rustls::SignatureScheme::RSA_PKCS1_SHA256, + 0x0501 => rustls::SignatureScheme::RSA_PKCS1_SHA384, + 0x0601 => rustls::SignatureScheme::RSA_PKCS1_SHA512, + 0x0403 => rustls::SignatureScheme::ECDSA_NISTP256_SHA256, + 0x0503 => rustls::SignatureScheme::ECDSA_NISTP384_SHA384, + 0x0804 => rustls::SignatureScheme::RSA_PSS_SHA256, + 0x0805 => rustls::SignatureScheme::RSA_PSS_SHA384, + 0x0806 => rustls::SignatureScheme::RSA_PSS_SHA512, + 0x0807 => rustls::SignatureScheme::ED25519, + // TODO: add support for Ed448 + // 0x0808 => rustls::SignatureScheme::ED448, + _ => { + println_err!("Unsupported signature scheme {:04x}", scheme); + process::exit(BOGO_NACK); + } + } +} + +fn lookup_kx_group(group: u16) -> &'static rustls::SupportedKxGroup { + match group { + 0x001d => &rustls::kx_group::X25519, + 0x0017 => &rustls::kx_group::SECP256R1, + 0x0018 => &rustls::kx_group::SECP384R1, + _ => { + println_err!("Unsupported kx group {:04x}", group); + process::exit(BOGO_NACK); + } + } +} + +struct ServerCacheWithResumptionDelay { + delay: u32, + storage: Arc, +} + +impl ServerCacheWithResumptionDelay { + fn new(delay: u32) -> Arc { + Arc::new(Self { + delay, + storage: rustls::server::ServerSessionMemoryCache::new(32), + }) + } +} + +fn align_time() { + /* we don't have an injectable clock source in rustls' public api, and + * resumption timing is in seconds resolution, so tests that use + * resumption_delay tend to be flickery if the seconds time ticks + * during this. + * + * this function delays until a fresh second ticks, which alleviates + * this. gross! + */ + use std::{thread, time}; + + fn sample() -> u64 { + time::SystemTime::now() + .duration_since(time::SystemTime::UNIX_EPOCH) + .unwrap() + .as_secs() + } + + let start_secs = sample(); + while start_secs == sample() { + thread::sleep(time::Duration::from_millis(20)); + } +} + +impl rustls::server::StoresServerSessions for ServerCacheWithResumptionDelay { + fn put(&self, key: Vec, value: Vec) -> bool { + let mut ssv = persist::ServerSessionValue::read_bytes(&value).unwrap(); + ssv.creation_time_sec -= self.delay as u64; + + self.storage + .put(key, ssv.get_encoding()) + } + + fn get(&self, key: &[u8]) -> Option> { + self.storage.get(key) + } + + fn take(&self, key: &[u8]) -> Option> { + self.storage.take(key) + } + + fn can_cache(&self) -> bool { + self.storage.can_cache() + } +} + +fn make_server_cfg(opts: &Options) -> Arc { + let client_auth = + if opts.verify_peer || opts.offer_no_client_cas || opts.require_any_client_cert { + Arc::new(DummyClientAuth { + mandatory: opts.require_any_client_cert, + }) + } else { + rustls::server::NoClientAuth::new() + }; + + let cert = load_cert(&opts.cert_file); + let key = load_key(&opts.key_file); + + let kx_groups = if let Some(curves) = &opts.curves { + curves + .iter() + .map(|curveid| lookup_kx_group(*curveid)) + .collect() + } else { + rustls::ALL_KX_GROUPS.to_vec() + }; + + let mut cfg = rustls::ServerConfig::builder() + .with_safe_default_cipher_suites() + .with_kx_groups(&kx_groups) + .with_protocol_versions(&opts.supported_versions()) + .unwrap() + .with_client_cert_verifier(client_auth) + .with_single_cert_with_ocsp_and_sct( + cert.clone(), + key, + opts.server_ocsp_response.clone(), + opts.server_sct_list.clone(), + ) + .unwrap(); + + cfg.session_storage = ServerCacheWithResumptionDelay::new(opts.resumption_delay); + cfg.max_fragment_size = opts.max_fragment; + + if opts.use_signing_scheme > 0 { + let scheme = lookup_scheme(opts.use_signing_scheme); + cfg.cert_resolver = Arc::new(FixedSignatureSchemeServerCertResolver { + resolver: cfg.cert_resolver.clone(), + scheme, + }); + } + + if opts.tickets { + cfg.ticketer = rustls::Ticketer::new().unwrap(); + } else if opts.resumes == 0 { + cfg.session_storage = Arc::new(rustls::server::NoServerSessionStorage {}); + } + + if !opts.protocols.is_empty() { + cfg.alpn_protocols = opts + .protocols + .iter() + .map(|proto| proto.as_bytes().to_vec()) + .collect::>(); + } + + if opts.enable_early_data { + // see kMaxEarlyDataAccepted in boringssl, which bogo validates + cfg.max_early_data_size = 14336; + cfg.send_half_rtt_data = true; + } + + Arc::new(cfg) +} + +struct ClientCacheWithoutKxHints { + delay: u32, + storage: Arc, +} + +impl ClientCacheWithoutKxHints { + fn new(delay: u32) -> Arc { + Arc::new(ClientCacheWithoutKxHints { + delay, + storage: rustls::client::ClientSessionMemoryCache::new(32), + }) + } +} + +impl rustls::client::StoresClientSessions for ClientCacheWithoutKxHints { + fn put(&self, key: Vec, value: Vec) -> bool { + if key.len() > 2 && key[0] == b'k' && key[1] == b'x' { + return true; + } + + let mut reader = Reader::init(&value[2..]); + let csv = CipherSuite::read_bytes(&value[..2]) + .and_then(|suite| { + persist::ClientSessionValue::read(&mut reader, suite, &rustls::ALL_CIPHER_SUITES) + }) + .unwrap(); + + let value = match csv { + persist::ClientSessionValue::Tls13(mut tls13) => { + tls13.common.rewind_epoch(self.delay); + tls13.get_encoding() + } + persist::ClientSessionValue::Tls12(mut tls12) => { + tls12.common.rewind_epoch(self.delay); + tls12.get_encoding() + } + }; + + self.storage.put(key, value) + } + + fn get(&self, key: &[u8]) -> Option> { + self.storage.get(key) + } +} + +fn make_client_cfg(opts: &Options) -> Arc { + let kx_groups = if let Some(curves) = &opts.curves { + curves + .iter() + .map(|curveid| lookup_kx_group(*curveid)) + .collect() + } else { + rustls::ALL_KX_GROUPS.to_vec() + }; + + let cfg = rustls::ClientConfig::builder() + .with_safe_default_cipher_suites() + .with_kx_groups(&kx_groups) + .with_protocol_versions(&opts.supported_versions()) + .expect("inconsistent settings") + .with_custom_certificate_verifier(Arc::new(DummyServerAuth { + send_sct: opts.send_sct, + })); + + let mut cfg = if !opts.cert_file.is_empty() && !opts.key_file.is_empty() { + let cert = load_cert(&opts.cert_file); + let key = load_key(&opts.key_file); + cfg.with_single_cert(cert, key).unwrap() + } else { + cfg.with_no_client_auth() + }; + + if !opts.cert_file.is_empty() && opts.use_signing_scheme > 0 { + let scheme = lookup_scheme(opts.use_signing_scheme); + cfg.client_auth_cert_resolver = Arc::new(FixedSignatureSchemeClientCertResolver { + resolver: cfg.client_auth_cert_resolver.clone(), + scheme, + }); + } + + let persist = ClientCacheWithoutKxHints::new(opts.resumption_delay); + cfg.session_storage = persist; + cfg.enable_sni = opts.use_sni; + cfg.max_fragment_size = opts.max_fragment; + + if !opts.protocols.is_empty() { + cfg.alpn_protocols = opts + .protocols + .iter() + .map(|proto| proto.as_bytes().to_vec()) + .collect(); + } + + if opts.enable_early_data { + cfg.enable_early_data = true; + } + + Arc::new(cfg) +} + +fn quit(why: &str) -> ! { + println_err!("{}", why); + process::exit(0) +} + +fn quit_err(why: &str) -> ! { + println_err!("{}", why); + process::exit(1) +} + +fn handle_err(err: rustls::Error) -> ! { + use rustls::Error; + use rustls::{AlertDescription, ContentType}; + use std::{thread, time}; + + println!("TLS error: {:?}", err); + thread::sleep(time::Duration::from_millis(100)); + + match err { + Error::InappropriateHandshakeMessage { .. } | Error::InappropriateMessage { .. } => { + quit(":UNEXPECTED_MESSAGE:") + } + Error::AlertReceived(AlertDescription::RecordOverflow) => { + quit(":TLSV1_ALERT_RECORD_OVERFLOW:") + } + Error::AlertReceived(AlertDescription::HandshakeFailure) => quit(":HANDSHAKE_FAILURE:"), + Error::AlertReceived(AlertDescription::ProtocolVersion) => quit(":WRONG_VERSION:"), + Error::AlertReceived(AlertDescription::InternalError) => { + quit(":PEER_ALERT_INTERNAL_ERROR:") + } + Error::CorruptMessagePayload(ContentType::Alert) => quit(":BAD_ALERT:"), + Error::CorruptMessagePayload(ContentType::ChangeCipherSpec) => { + quit(":BAD_CHANGE_CIPHER_SPEC:") + } + Error::CorruptMessagePayload(ContentType::Handshake) => quit(":BAD_HANDSHAKE_MSG:"), + Error::CorruptMessagePayload(ContentType::Unknown(42)) => quit(":GARBAGE:"), + Error::CorruptMessage => quit(":GARBAGE:"), + Error::DecryptError => quit(":DECRYPTION_FAILED_OR_BAD_RECORD_MAC:"), + Error::PeerIncompatibleError(_) => quit(":INCOMPATIBLE:"), + Error::PeerMisbehavedError(s) if s == "too much early_data received" => { + quit(":TOO_MUCH_READ_EARLY_DATA:") + } + Error::PeerMisbehavedError(_) => quit(":PEER_MISBEHAVIOUR:"), + Error::NoCertificatesPresented => quit(":NO_CERTS:"), + Error::AlertReceived(AlertDescription::UnexpectedMessage) => quit(":BAD_ALERT:"), + Error::AlertReceived(AlertDescription::DecompressionFailure) => { + quit_err(":SSLV3_ALERT_DECOMPRESSION_FAILURE:") + } + Error::InvalidCertificateEncoding => quit(":CANNOT_PARSE_LEAF_CERT:"), + Error::InvalidCertificateSignature => quit(":BAD_SIGNATURE:"), + Error::InvalidCertificateSignatureType => quit(":WRONG_SIGNATURE_TYPE:"), + Error::PeerSentOversizedRecord => quit(":DATA_LENGTH_TOO_LONG:"), + _ => { + println_err!("unhandled error: {:?}", err); + quit(":FIXME:") + } + } +} + +fn flush(sess: &mut Connection, conn: &mut net::TcpStream) { + while sess.wants_write() { + match sess.write_tls(conn) { + Err(err) => { + println!("IO error: {:?}", err); + process::exit(0); + } + Ok(_) => {} + } + } + conn.flush().unwrap(); +} + +fn client(conn: &mut Connection) -> &mut ClientConnection { + conn.try_into().unwrap() +} + +fn server(conn: &mut Connection) -> &mut ServerConnection { + match conn { + Connection::Server(s) => s, + _ => panic!("Connection is not a ServerConnection"), + } +} + +fn exec(opts: &Options, mut sess: Connection, count: usize) { + let mut sent_message = false; + + if opts.queue_data || (opts.queue_data_on_resume && count > 0) { + if count > 0 && opts.enable_early_data { + let len = client(&mut sess) + .early_data() + .expect("0rtt not available") + .write(b"hello") + .expect("0rtt write failed"); + sess.writer() + .write_all(&b"hello"[len..]) + .unwrap(); + sent_message = true; + } else if !opts.only_write_one_byte_after_handshake { + let _ = sess.writer().write_all(b"hello"); + sent_message = true; + } + } + + let addrs = [ + net::SocketAddr::from((net::Ipv6Addr::LOCALHOST, opts.port)), + net::SocketAddr::from((net::Ipv4Addr::LOCALHOST, opts.port)), + ]; + let mut conn = net::TcpStream::connect(&addrs[..]).expect("cannot connect"); + let mut sent_shutdown = false; + let mut sent_exporter = false; + let mut quench_writes = false; + + loop { + if !quench_writes { + flush(&mut sess, &mut conn); + } + + if sess.wants_read() { + match sess.read_tls(&mut conn) { + Ok(_) => {} + Err(ref err) if err.kind() == io::ErrorKind::ConnectionReset => {} + Err(err) => panic!("invalid read: {}", err), + }; + + if let Err(err) = sess.process_new_packets() { + flush(&mut sess, &mut conn); /* send any alerts before exiting */ + handle_err(err); + } + } + + if opts.server && opts.enable_early_data { + if let Some(ref mut ed) = server(&mut sess).early_data() { + let mut data = Vec::new(); + let data_len = ed + .read_to_end(&mut data) + .expect("cannot read early_data"); + + for b in data.iter_mut() { + *b ^= 0xff; + } + + sess.writer() + .write_all(&data[..data_len]) + .expect("cannot echo early_data in 1rtt data"); + } + } + + if !sess.is_handshaking() && opts.export_keying_material > 0 && !sent_exporter { + let mut export = Vec::new(); + export.resize(opts.export_keying_material, 0u8); + sess.export_keying_material( + &mut export, + opts.export_keying_material_label + .as_bytes(), + if opts.export_keying_material_context_used { + Some( + opts.export_keying_material_context + .as_bytes(), + ) + } else { + None + }, + ) + .unwrap(); + sess.writer() + .write_all(&export) + .unwrap(); + sent_exporter = true; + } + + if !sess.is_handshaking() && opts.only_write_one_byte_after_handshake && !sent_message { + println!("writing message and then only one byte of its tls frame"); + flush(&mut sess, &mut conn); + + sess.writer() + .write_all(b"hello") + .unwrap(); + sent_message = true; + + let mut one_byte = [0u8]; + let mut cursor = io::Cursor::new(&mut one_byte[..]); + sess.write_tls(&mut cursor).unwrap(); + conn.write(&one_byte).expect("IO error"); + + quench_writes = true; + } + + if opts.enable_early_data && !opts.server && !sess.is_handshaking() && count > 0 { + if opts.expect_accept_early_data && !client(&mut sess).is_early_data_accepted() { + quit_err("Early data was not accepted, but we expect the opposite"); + } else if opts.expect_reject_early_data && client(&mut sess).is_early_data_accepted() { + quit_err("Early data was accepted, but we expect the opposite"); + } + if opts.expect_version == 0x0304 { + match sess.protocol_version() { + Some(ProtocolVersion::TLSv1_3) | Some(ProtocolVersion::Unknown(0x7f17)) => {} + _ => quit_err("wrong protocol version"), + } + } + } + + if !sess.is_handshaking() + && !opts + .expect_quic_transport_params + .is_empty() + { + let their_transport_params = sess + .quic_transport_parameters() + .expect("missing peer quic transport params"); + assert_eq!(opts.expect_quic_transport_params, their_transport_params); + } + + let mut buf = [0u8; 1024]; + let len = match sess + .reader() + .read(&mut buf[..opts.read_size]) + { + Ok(0) => { + if opts.check_close_notify { + println!("close notify ok"); + } + println!("EOF (tls)"); + return; + } + Ok(len) => len, + Err(err) if err.kind() == io::ErrorKind::WouldBlock => 0, + Err(err) if err.kind() == io::ErrorKind::UnexpectedEof => { + if opts.check_close_notify { + quit_err(":CLOSE_WITHOUT_CLOSE_NOTIFY:"); + } + println!("EOF (tcp)"); + return; + } + Err(err) => panic!("unhandled read error {:?}", err), + }; + + if opts.shut_down_after_handshake && !sent_shutdown && !sess.is_handshaking() { + sess.send_close_notify(); + sent_shutdown = true; + } + + if quench_writes && len > 0 { + println!("unquenching writes after {:?}", len); + quench_writes = false; + } + + for b in buf.iter_mut() { + *b ^= 0xff; + } + + sess.writer() + .write_all(&buf[..len]) + .unwrap(); + } +} + +fn main() { + let mut args: Vec<_> = env::args().collect(); + env_logger::init(); + + args.remove(0); + + if !args.is_empty() && args[0] == "-is-handshaker-supported" { + println!("No"); + process::exit(0); + } + println!("options: {:?}", args); + + let mut opts = Options::new(); + + while !args.is_empty() { + let arg = args.remove(0); + match arg.as_ref() { + "-port" => { + opts.port = args.remove(0).parse::().unwrap(); + } + "-server" => { + opts.server = true; + } + "-key-file" => { + opts.key_file = args.remove(0); + } + "-cert-file" => { + opts.cert_file = args.remove(0); + } + "-resume-count" => { + opts.resumes = args.remove(0).parse::().unwrap(); + } + "-no-tls13" => { + opts.support_tls13 = false; + } + "-no-tls12" => { + opts.support_tls12 = false; + } + "-min-version" => { + let min = args.remove(0).parse::().unwrap(); + opts.min_version = Some(ProtocolVersion::Unknown(min)); + } + "-max-version" => { + let max = args.remove(0).parse::().unwrap(); + opts.max_version = Some(ProtocolVersion::Unknown(max)); + } + "-max-send-fragment" => { + let max_fragment = args.remove(0).parse::().unwrap(); + opts.max_fragment = Some(max_fragment + 5); // ours includes header + } + "-read-size" => { + let rdsz = args.remove(0).parse::().unwrap(); + opts.read_size = rdsz; + } + "-tls13-variant" => { + let variant = args.remove(0).parse::().unwrap(); + if variant != 1 { + println!("NYI TLS1.3 variant selection: {:?} {:?}", arg, variant); + process::exit(BOGO_NACK); + } + } + "-no-ticket" => { + opts.tickets = false; + } + "-on-resume-no-ticket" => { + opts.resume_with_tickets_disabled = true; + } + "-signing-prefs" => { + let alg = args.remove(0).parse::().unwrap(); + opts.use_signing_scheme = alg; + } + "-max-cert-list" | + "-expect-curve-id" | + "-expect-resume-curve-id" | + "-expect-peer-signature-algorithm" | + "-expect-peer-verify-pref" | + "-expect-advertised-alpn" | + "-expect-alpn" | + "-on-initial-expect-alpn" | + "-on-resume-expect-alpn" | + "-on-retry-expect-alpn" | + "-expect-server-name" | + "-expect-ocsp-response" | + "-expect-signed-cert-timestamps" | + "-expect-certificate-types" | + "-expect-client-ca-list" | + "-on-retry-expect-early-data-reason" | + "-on-resume-expect-early-data-reason" | + "-on-initial-expect-early-data-reason" | + "-on-initial-expect-cipher" | + "-on-resume-expect-cipher" | + "-on-retry-expect-cipher" | + "-expect-ticket-age-skew" | + "-handshaker-path" | + "-application-settings" | + "-expect-msg-callback" => { + println!("not checking {} {}; NYI", arg, args.remove(0)); + } + + "-expect-secure-renegotiation" | + "-expect-no-session-id" | + "-enable-ed25519" | + "-expect-hrr" | + "-expect-no-hrr" | + "-on-resume-expect-no-offer-early-data" | + "-key-update" | //< we could implement an API for this + "-expect-tls13-downgrade" | + "-expect-session-id" => { + println!("not checking {}; NYI", arg); + } + + "-export-keying-material" => { + opts.export_keying_material = args.remove(0).parse::().unwrap(); + } + "-export-label" => { + opts.export_keying_material_label = args.remove(0); + } + "-export-context" => { + opts.export_keying_material_context = args.remove(0); + } + "-use-export-context" => { + opts.export_keying_material_context_used = true; + } + "-quic-transport-params" => { + opts.quic_transport_params = base64::decode(args.remove(0).as_bytes()) + .expect("invalid base64"); + } + "-expect-quic-transport-params" => { + opts.expect_quic_transport_params = base64::decode(args.remove(0).as_bytes()) + .expect("invalid base64"); + } + + "-ocsp-response" => { + opts.server_ocsp_response = base64::decode(args.remove(0).as_bytes()) + .expect("invalid base64"); + } + "-signed-cert-timestamps" => { + opts.server_sct_list = base64::decode(args.remove(0).as_bytes()) + .expect("invalid base64"); + + if opts.server_sct_list.len() == 2 && + opts.server_sct_list[0] == 0x00 && + opts.server_sct_list[1] == 0x00 { + quit(":INVALID_SCT_LIST:"); + } + } + "-select-alpn" => { + opts.protocols.push(args.remove(0)); + } + "-require-any-client-certificate" => { + opts.require_any_client_cert = true; + } + "-verify-peer" => { + opts.verify_peer = true; + } + "-shim-writes-first" => { + opts.queue_data = true; + } + "-read-with-unfinished-write" => { + opts.queue_data = true; + opts.only_write_one_byte_after_handshake = true; + } + "-shim-shuts-down" => { + opts.shut_down_after_handshake = true; + } + "-check-close-notify" => { + opts.check_close_notify = true; + } + "-host-name" => { + opts.host_name = args.remove(0); + opts.use_sni = true; + } + "-advertise-alpn" => { + opts.protocols = split_protocols(&args.remove(0)); + } + "-use-null-client-ca-list" => { + opts.offer_no_client_cas = true; + } + "-enable-signed-cert-timestamps" => { + opts.send_sct = true; + } + "-enable-early-data" => { + opts.tickets = false; + opts.enable_early_data = true; + } + "-on-resume-shim-writes-first" => { + opts.queue_data_on_resume = true; + } + "-on-resume-read-with-unfinished-write" => { + opts.queue_data_on_resume = true; + opts.only_write_one_byte_after_handshake_on_resume = true; + } + "-expect-ticket-supports-early-data" => { + opts.expect_ticket_supports_early_data = true; + } + "-expect-accept-early-data" | + "-on-resume-expect-accept-early-data" => { + opts.expect_accept_early_data = true; + } + "-expect-early-data-reason" | + "-on-resume-expect-reject-early-data-reason" => { + let reason = args.remove(0); + match reason.as_str() { + "disabled" | "protocol_version" => { + opts.expect_reject_early_data = true; + } + _ => { + println!("NYI early data reason: {}", reason); + process::exit(1); + } + } + } + "-expect-reject-early-data" | + "-on-resume-expect-reject-early-data" => { + opts.expect_reject_early_data = true; + } + "-expect-version" => { + opts.expect_version = args.remove(0).parse::().unwrap(); + } + "-curves" => { + let curve = args.remove(0).parse::().unwrap(); + if let Some(mut curves) = opts.curves.take() { + curves.push(curve); + } else { + opts.curves = Some(vec![ curve ]); + } + } + "-resumption-delay" => { + opts.resumption_delay = args.remove(0).parse::().unwrap(); + align_time(); + } + + // defaults: + "-enable-all-curves" | + "-renegotiate-ignore" | + "-no-tls11" | + "-no-tls1" | + "-no-ssl3" | + "-handoff" | + "-decline-alpn" | + "-expect-no-session" | + "-expect-session-miss" | + "-expect-extended-master-secret" | + "-expect-ticket-renewal" | + "-enable-ocsp-stapling" | + // internal openssl details: + "-async" | + "-implicit-handshake" | + "-use-old-client-cert-callback" | + "-use-early-callback" => {} + + // Not implemented things + "-dtls" | + "-cipher" | + "-psk" | + "-renegotiate-freely" | + "-false-start" | + "-fallback-scsv" | + "-fail-early-callback" | + "-fail-cert-callback" | + "-install-ddos-callback" | + "-advertise-npn" | + "-verify-fail" | + "-expect-channel-id" | + "-send-channel-id" | + "-select-next-proto" | + "-expect-verify-result" | + "-send-alert" | + "-digest-prefs" | + "-use-exporter-between-reads" | + "-ticket-key" | + "-tls-unique" | + "-enable-server-custom-extension" | + "-enable-client-custom-extension" | + "-expect-dhe-group-size" | + "-use-ticket-callback" | + "-enable-grease" | + "-enable-channel-id" | + "-expect-early-data-info" | + "-expect-cipher-aes" | + "-retain-only-sha256-client-cert-initial" | + "-use-client-ca-list" | + "-expect-draft-downgrade" | + "-allow-unknown-alpn-protos" | + "-on-initial-tls13-variant" | + "-on-initial-expect-curve-id" | + "-on-resume-export-early-keying-material" | + "-on-resume-enable-early-data" | + "-export-early-keying-material" | + "-handshake-twice" | + "-on-resume-verify-fail" | + "-reverify-on-resume" | + "-verify-prefs" | + "-no-op-extra-handshake" | + "-expect-peer-cert-file" | + "-no-rsa-pss-rsae-certs" | + "-ignore-tls13-downgrade" | + "-on-initial-expect-peer-cert-file" => { + println!("NYI option {:?}", arg); + process::exit(BOGO_NACK); + } + + _ => { + println!("unhandled option {:?}", arg); + process::exit(1); + } + } + } + + println!("opts {:?}", opts); + + let mut server_cfg = if opts.server { + Some(make_server_cfg(&opts)) + } else { + None + }; + let client_cfg = if !opts.server { + Some(make_client_cfg(&opts)) + } else { + None + }; + + fn make_session( + opts: &Options, + scfg: &Option>, + ccfg: &Option>, + ) -> Connection { + if opts.server { + let scfg = Arc::clone(scfg.as_ref().unwrap()); + let s = if opts.quic_transport_params.is_empty() { + rustls::ServerConnection::new(scfg).unwrap() + } else { + rustls::ServerConnection::new_quic( + scfg, + quic::Version::V1, + opts.quic_transport_params.clone(), + ) + .unwrap() + }; + s.into() + } else { + let server_name = opts + .host_name + .as_str() + .try_into() + .unwrap(); + let ccfg = Arc::clone(ccfg.as_ref().unwrap()); + let c = if opts.quic_transport_params.is_empty() { + rustls::ClientConnection::new(ccfg, server_name) + } else { + rustls::ClientConnection::new_quic( + ccfg, + quic::Version::V1, + server_name, + opts.quic_transport_params.clone(), + ) + } + .unwrap(); + c.into() + } + } + + for i in 0..opts.resumes + 1 { + let sess = make_session(&opts, &server_cfg, &client_cfg); + exec(&opts, sess, i); + + if opts.resume_with_tickets_disabled { + opts.tickets = false; + server_cfg = Some(make_server_cfg(&opts)); + } + } +} diff --git a/third_party/rustls-fork-shadow-tls/examples/internal/trytls_shim.rs b/third_party/rustls-fork-shadow-tls/examples/internal/trytls_shim.rs new file mode 100644 index 0000000..f47bbd0 --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/examples/internal/trytls_shim.rs @@ -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> { + 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> { + 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 = 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); + } + } +} diff --git a/third_party/rustls-fork-shadow-tls/src/anchors.rs b/third_party/rustls-fork-shadow-tls/src/anchors.rs new file mode 100644 index 0000000..4caf3e7 --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/src/anchors.rs @@ -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, + spki: Vec, + name_constraints: Option>, +} + +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>, + spki: impl Into>, + name_constraints: Option>>, + ) -> 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, +} + +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, + ) { + 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]) -> (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) + } +} diff --git a/third_party/rustls-fork-shadow-tls/src/bs_debug.rs b/third_party/rustls-fork-shadow-tls/src/bs_debug.rs new file mode 100644 index 0000000..ad73ee6 --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/src/bs_debug.rs @@ -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))); + } +} diff --git a/third_party/rustls-fork-shadow-tls/src/builder.rs b/third_party/rustls-fork-shadow-tls/src/builder.rs new file mode 100644 index 0000000..8b63f8c --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/src/builder.rs @@ -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::with_safe_default_cipher_suites()`]. +/// 2. Now you must make a decision +/// on key exchange groups: typically by calling +/// [`ConfigBuilder::with_safe_default_kx_groups()`]. +/// 3. Now you must make +/// a decision on which protocol versions to support, typically by calling +/// [`ConfigBuilder::with_safe_default_protocol_versions()`]. +/// 5. Now see [`ConfigBuilder`] or +/// [`ConfigBuilder`] for further steps. +/// +/// [`ServerConfig`]: crate::ServerConfig +/// [`ClientConfig`]: crate::ClientConfig +/// [`ClientConfig::builder()`]: crate::ClientConfig::builder() +/// [`ServerConfig::builder()`]: crate::ServerConfig::builder() +/// [`ConfigBuilder`]: struct.ConfigBuilder.html#impl-3 +/// [`ConfigBuilder`]: struct.ConfigBuilder.html#impl-6 +#[derive(Clone)] +pub struct ConfigBuilder { + pub(crate) state: State, + pub(crate) side: PhantomData, +} + +impl fmt::Debug for ConfigBuilder { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let side_name = std::any::type_name::(); + 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 ConfigBuilder { + /// 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 { + 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 { + 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 { + 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, +} + +impl ConfigBuilder { + /// Choose a specific set of key exchange groups. + pub fn with_kx_groups( + self, + kx_groups: &[&'static SupportedKxGroup], + ) -> ConfigBuilder { + 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 { + 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, + kx_groups: Vec<&'static SupportedKxGroup>, +} + +impl ConfigBuilder { + /// Accept the default protocol versions: both TLS1.2 and TLS1.3 are enabled. + pub fn with_safe_default_protocol_versions( + self, + ) -> Result, 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, 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, + 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 {} +} diff --git a/third_party/rustls-fork-shadow-tls/src/check.rs b/third_party/rustls-fork-shadow-tls/src/check.rs new file mode 100644 index 0000000..d318343 --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/src/check.rs @@ -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), + } +} diff --git a/third_party/rustls-fork-shadow-tls/src/cipher.rs b/third_party/rustls-fork-shadow-tls/src/cipher.rs new file mode 100644 index 0000000..b595ca6 --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/src/cipher.rs @@ -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; +} + +/// Objects with this trait can encrypt TLS messages. +pub(crate) trait MessageEncrypter: Send + Sync { + fn encrypt(&self, m: BorrowedPlainMessage, seq: u64) -> Result; +} + +impl dyn MessageEncrypter { + pub(crate) fn invalid() -> Box { + Box::new(InvalidMessageEncrypter {}) + } +} + +impl dyn MessageDecrypter { + pub(crate) fn invalid() -> Box { + 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> for Iv { + fn from(okm: hkdf::Okm) -> 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 { + 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 { + Err(Error::DecryptError) + } +} diff --git a/third_party/rustls-fork-shadow-tls/src/client/builder.rs b/third_party/rustls-fork-shadow-tls/src/client/builder.rs new file mode 100644 index 0000000..464bfb9 --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/src/client/builder.rs @@ -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 { + /// Choose how to verify client certificates. + pub fn with_root_certificates( + self, + root_store: anchors::RootCertStore, + ) -> ConfigBuilder { + 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, + ) -> ConfigBuilder { + 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, + kx_groups: Vec<&'static SupportedKxGroup>, + versions: versions::EnabledVersions, + root_store: anchors::RootCertStore, +} + +impl ConfigBuilder { + /// 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 { + 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_der: key::PrivateKey, + ) -> Result { + 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, + ) -> ClientConfig { + self.with_logs(None) + .with_client_cert_resolver(client_auth_cert_resolver) + } + + fn with_logs( + self, + ct_policy: Option, + ) -> ConfigBuilder { + 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, + kx_groups: Vec<&'static SupportedKxGroup>, + versions: versions::EnabledVersions, + verifier: Arc, +} + +impl ConfigBuilder { + /// 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_der: key::PrivateKey, + ) -> Result { + 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, + ) -> 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, + } + } +} diff --git a/third_party/rustls-fork-shadow-tls/src/client/client_conn.rs b/third_party/rustls-fork-shadow-tls/src/client/client_conn.rs new file mode 100644 index 0000000..59244a7 --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/src/client/client_conn.rs @@ -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 [u8; 32] + Send + Sync + 'static>; +/// Callback used to replace a TLS 1.2 ClientHello session ID from eager key material. +pub type ClientTls12SessionIdGenerator = + Arc]) -> [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, + /// Generator for TLS 1.2 ClientHello authentication. + pub tls12: Option, +} +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, value: Vec) -> bool; + + /// Returns the latest value for `key`. Returns `None` + /// if there's no such value. + fn get(&self, key: &[u8]) -> Option>; +} + +/// 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>; + + /// 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, + + /// 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>, + + /// How we store session data or tickets. + pub session_storage: Arc, + + /// 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, + + /// How to decide what client auth certificate/keys to use. + pub client_auth_cert_resolver: Arc, + + /// 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, + + /// How to output key material for debugging. The default + /// does nothing. + pub key_log: Arc, + + /// 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 { + 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 { + 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 { + 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 { + 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 { + 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) { + 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 { + 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 { + 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, +} + +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, + name: ServerName, + ) -> Result { + Self::new_inner(config, name, Vec::new(), Protocol::Tcp) + } + + /// Create a new ClientConnection with a generator. + pub fn new_with_session_id_generator( + config: Arc, + name: ServerName, + session_id_generator: F, + ) -> Result + 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, + name: ServerName, + session_id_generators: ClientSessionIdGenerators, + ) -> Result { + Self::new_inner_with_session_id_generators( + config, + name, + Vec::new(), + Protocol::Tcp, + session_id_generators, + ) + } + + fn new_inner( + config: Arc, + name: ServerName, + extra_exts: Vec, + proto: Protocol, + ) -> Result { + Self::new_inner_with_session_id_generators( + config, + name, + extra_exts, + proto, + ClientSessionIdGenerators::default(), + ) + } + + fn new_inner_with_session_id_generators( + config: Arc, + name: ServerName, + extra_exts: Vec, + proto: Protocol, + session_id_generators: ClientSessionIdGenerators, + ) -> Result { + 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 { + 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 { + 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 { + self.inner.extract_secrets() + } +} + +impl Deref for ClientConnection { + type Target = ConnectionCommon; + + 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 { + use crate::Connection::*; + match value { + Client(conn) => Ok(conn), + Server(_) => Err(()), + } + } +} + +impl From 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, + pub(in crate::client) tls12_eager_key_exchanges: Vec, +} + +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 { + 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) -> Option { + quic::write_hs(&mut self.inner.common_state, buf) + } + + fn alert(&self) -> Option { + 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, + quic_version: quic::Version, + name: ServerName, + params: Vec, + ) -> Result { + 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 {} diff --git a/third_party/rustls-fork-shadow-tls/src/client/common.rs b/third_party/rustls-fork-shadow-tls/src/client/common.rs new file mode 100644 index 0000000..ac9094d --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/src/client/common.rs @@ -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, + pub(super) scts: Option, +} + +impl ServerCertDetails { + pub(super) fn new( + cert_chain: CertificatePayload, + ocsp_response: Vec, + scts: Option, + ) -> Self { + Self { + cert_chain, + ocsp_response, + scts, + } + } + + pub(super) fn scts(&self) -> impl Iterator { + self.scts + .as_deref() + .unwrap_or(&[]) + .iter() + .map(|payload| payload.0.as_slice()) + } +} + +pub(super) struct ClientHelloDetails { + pub(super) sent_extensions: Vec, +} + +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> }, + /// Send a non-empty `Certificate` and a `CertificateVerify`. + Verify { + certkey: Arc, + signer: Box, + auth_context_tls13: Option>, + }, +} + +impl ClientAuthDetails { + pub(super) fn resolve( + resolver: &dyn ResolvesClientCert, + canames: Option<&DistinguishedNames>, + sigschemes: &[SignatureScheme], + auth_context_tls13: Option>, + ) -> Self { + let acceptable_issuers = canames + .map(Vec::as_slice) + .unwrap_or_default() + .iter() + .map(|p| p.0.as_slice()) + .collect::>(); + + 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 } + } +} diff --git a/third_party/rustls-fork-shadow-tls/src/client/handy.rs b/third_party/rustls-fork-shadow-tls/src/client/handy.rs new file mode 100644 index 0000000..804887a --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/src/client/handy.rs @@ -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, _value: Vec) -> bool { + false + } + + fn get(&self, _key: &[u8]) -> Option> { + 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, Vec>>, +} + +impl ClientSessionMemoryCache { + /// Make a new ClientSessionMemoryCache. `size` is the + /// maximum number of stored sessions. + pub fn new(size: usize) -> Arc { + 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, value: Vec) -> bool { + self.cache + .lock() + .unwrap() + .insert(key, value); + true + } + + fn get(&self, key: &[u8]) -> Option> { + 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> { + None + } + + fn has_certs(&self) -> bool { + false + } +} + +pub(super) struct AlwaysResolvesClientCert(Arc); + +impl AlwaysResolvesClientCert { + pub(super) fn new( + chain: Vec, + priv_key: &key::PrivateKey, + ) -> Result { + 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> { + 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); + } +} diff --git a/third_party/rustls-fork-shadow-tls/src/client/hs.rs b/third_party/rustls-fork-shadow-tls/src/client/hs.rs new file mode 100644 index 0000000..a4bec64 --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/src/client/hs.rs @@ -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>; +pub(super) type NextStateOrError = Result; +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> { + 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, + config: Arc, + 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 = 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, + resuming_session: Option>, + server_name: ServerName, + random: Random, + using_ems: bool, + transcript_buffer: HandshakeHashBuffer, + early_key_schedule: Option, + hello: ClientHelloDetails, + offered_key_share: Option, + session_id: SessionID, + sent_tls13_fake_ccs: bool, + suite: Option, +} + +struct ExpectServerHelloOrHelloRetryRequest { + next: ExpectServerHello, + extra_exts: Vec, +} + +fn emit_client_hello_for_retry( + config: Arc, + cx: &mut ClientContext<'_>, + resuming_session: Option>, + random: Random, + using_ems: bool, + mut transcript_buffer: HandshakeHashBuffer, + mut sent_tls13_fake_ccs: bool, + mut hello: ClientHelloDetails, + session_id: Option, + session_id_generators: ClientSessionIdGenerators, + retryreq: Option<&HelloRetryRequest>, + server_name: ServerName, + key_share: Option, + extra_exts: Vec, + may_send_sct_list: bool, + suite: Option, +) -> 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::>(), + ))); + } + + // 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 for ExpectServerHello { + fn handle(mut self: Box, 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 for ExpectServerHelloOrHelloRetryRequest { + fn handle(self: Box, 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 +} diff --git a/third_party/rustls-fork-shadow-tls/src/client/tls12.rs b/third_party/rustls-fork-shadow-tls/src/client/tls12.rs new file mode 100644 index 0000000..2956e28 --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/src/client/tls12.rs @@ -0,0 +1,1127 @@ +use crate::check::{inappropriate_handshake_message, inappropriate_message}; +use crate::conn::{CommonState, ConnectionRandoms, Side, State}; +use crate::enums::ProtocolVersion; +use crate::error::Error; +use crate::hash_hs::HandshakeHash; +#[cfg(feature = "logging")] +use crate::log::{debug, trace}; +use crate::msgs::base::{Payload, PayloadU8}; +use crate::msgs::ccs::ChangeCipherSpecPayload; +use crate::msgs::codec::Codec; +use crate::msgs::enums::AlertDescription; +use crate::msgs::enums::{ContentType, HandshakeType}; +use crate::msgs::handshake::{ + CertificatePayload, DecomposedSignatureScheme, DigitallySignedStruct, HandshakeMessagePayload, + HandshakePayload, NewSessionTicketPayload, SCTList, ServerECDHParams, SessionID, +}; +use crate::msgs::message::{Message, MessagePayload}; +use crate::msgs::persist; +use crate::sign::Signer; +#[cfg(feature = "secret_extraction")] +use crate::suites::PartiallyExtractedSecrets; +use crate::suites::SupportedCipherSuite; +use crate::ticketer::TimeBase; +use crate::tls12::{self, ConnectionSecrets, Tls12CipherSuite}; +use crate::{kx, verify}; + +use super::client_conn::ClientConnectionData; +use super::hs::ClientContext; +use crate::client::common::ClientAuthDetails; +use crate::client::common::ServerCertDetails; +use crate::client::{hs, ClientConfig, ServerName}; + +use ring::agreement::PublicKey; +use ring::constant_time; + +use std::sync::Arc; + +pub(super) use server_hello::CompleteServerHelloHandling; + +mod server_hello { + use crate::msgs::enums::ExtensionType; + use crate::msgs::handshake::HasServerExtensions; + use crate::msgs::handshake::ServerHelloPayload; + + use super::*; + + pub(in crate::client) struct CompleteServerHelloHandling { + pub(in crate::client) config: Arc, + pub(in crate::client) resuming_session: Option, + pub(in crate::client) server_name: ServerName, + pub(in crate::client) randoms: ConnectionRandoms, + pub(in crate::client) using_ems: bool, + pub(in crate::client) transcript: HandshakeHash, + } + + impl CompleteServerHelloHandling { + pub(in crate::client) fn handle_server_hello( + mut self, + cx: &mut ClientContext, + suite: &'static Tls12CipherSuite, + server_hello: &ServerHelloPayload, + tls13_supported: bool, + ) -> hs::NextStateOrError { + server_hello + .random + .write_slice(&mut self.randoms.server); + + // Look for TLS1.3 downgrade signal in server random + // both the server random and TLS12_DOWNGRADE_SENTINEL are + // public values and don't require constant time comparison + let has_downgrade_marker = self.randoms.server[24..] == tls12::DOWNGRADE_SENTINEL; + if tls13_supported && has_downgrade_marker { + return Err(cx + .common + .illegal_param("downgrade to TLS1.2 when TLS1.3 is supported")); + } + + // Doing EMS? + self.using_ems = server_hello.ems_support_acked(); + + // Might the server send a ticket? + let must_issue_new_ticket = if server_hello + .find_extension(ExtensionType::SessionTicket) + .is_some() + { + debug!("Server supports tickets"); + true + } else { + false + }; + + // Might the server send a CertificateStatus between Certificate and + // ServerKeyExchange? + let may_send_cert_status = server_hello + .find_extension(ExtensionType::StatusRequest) + .is_some(); + if may_send_cert_status { + debug!("Server may staple OCSP response"); + } + + // Save any sent SCTs for verification against the certificate. + let server_cert_sct_list = if let Some(sct_list) = server_hello.get_sct_list() { + debug!("Server sent {:?} SCTs", sct_list.len()); + + if hs::sct_list_is_invalid(sct_list) { + let error_msg = "server sent invalid SCT list".to_string(); + return Err(Error::PeerMisbehavedError(error_msg)); + } + Some(sct_list.clone()) + } else { + None + }; + + // See if we're successfully resuming. + if let Some(ref resuming) = self.resuming_session { + if resuming.session_id == server_hello.session_id { + debug!("Server agreed to resume"); + + // Is the server telling lies about the ciphersuite? + if resuming.suite() != suite { + let error_msg = + "abbreviated handshake offered, but with varied cs".to_string(); + return Err(Error::PeerMisbehavedError(error_msg)); + } + + // And about EMS support? + if resuming.extended_ms() != self.using_ems { + let error_msg = "server varied ems support over resume".to_string(); + return Err(Error::PeerMisbehavedError(error_msg)); + } + + let secrets = + ConnectionSecrets::new_resume(self.randoms, suite, resuming.secret()); + self.config.key_log.log( + "CLIENT_RANDOM", + &secrets.randoms.client, + &secrets.master_secret, + ); + cx.common + .start_encryption_tls12(&secrets, Side::Client); + + // Since we're resuming, we verified the certificate and + // proof of possession in the prior session. + cx.common.peer_certificates = Some(resuming.server_cert_chain().to_vec()); + let cert_verified = verify::ServerCertVerified::assertion(); + let sig_verified = verify::HandshakeSignatureValid::assertion(); + + return if must_issue_new_ticket { + Ok(Box::new(ExpectNewTicket { + config: self.config, + secrets, + resuming_session: self.resuming_session, + session_id: server_hello.session_id, + server_name: self.server_name, + using_ems: self.using_ems, + transcript: self.transcript, + resuming: true, + cert_verified, + sig_verified, + })) + } else { + Ok(Box::new(ExpectCcs { + config: self.config, + secrets, + resuming_session: self.resuming_session, + session_id: server_hello.session_id, + server_name: self.server_name, + using_ems: self.using_ems, + transcript: self.transcript, + ticket: None, + resuming: true, + cert_verified, + sig_verified, + })) + }; + } + } + + Ok(Box::new(ExpectCertificate { + config: self.config, + resuming_session: self.resuming_session, + session_id: server_hello.session_id, + server_name: self.server_name, + randoms: self.randoms, + using_ems: self.using_ems, + transcript: self.transcript, + suite, + may_send_cert_status, + must_issue_new_ticket, + server_cert_sct_list, + })) + } + } +} + +struct ExpectCertificate { + config: Arc, + resuming_session: Option, + session_id: SessionID, + server_name: ServerName, + randoms: ConnectionRandoms, + using_ems: bool, + transcript: HandshakeHash, + pub(super) suite: &'static Tls12CipherSuite, + may_send_cert_status: bool, + must_issue_new_ticket: bool, + server_cert_sct_list: Option, +} + +impl State for ExpectCertificate { + fn handle( + mut self: Box, + _cx: &mut ClientContext<'_>, + m: Message, + ) -> hs::NextStateOrError { + self.transcript.add_message(&m); + let server_cert_chain = require_handshake_msg_move!( + m, + HandshakeType::Certificate, + HandshakePayload::Certificate + )?; + + if self.may_send_cert_status { + Ok(Box::new(ExpectCertificateStatusOrServerKx { + config: self.config, + resuming_session: self.resuming_session, + session_id: self.session_id, + server_name: self.server_name, + randoms: self.randoms, + using_ems: self.using_ems, + transcript: self.transcript, + suite: self.suite, + server_cert_sct_list: self.server_cert_sct_list, + server_cert_chain, + must_issue_new_ticket: self.must_issue_new_ticket, + })) + } else { + let server_cert = + ServerCertDetails::new(server_cert_chain, vec![], self.server_cert_sct_list); + + Ok(Box::new(ExpectServerKx { + config: self.config, + resuming_session: self.resuming_session, + session_id: self.session_id, + server_name: self.server_name, + randoms: self.randoms, + using_ems: self.using_ems, + transcript: self.transcript, + suite: self.suite, + server_cert, + must_issue_new_ticket: self.must_issue_new_ticket, + })) + } + } +} + +struct ExpectCertificateStatusOrServerKx { + config: Arc, + resuming_session: Option, + session_id: SessionID, + server_name: ServerName, + randoms: ConnectionRandoms, + using_ems: bool, + transcript: HandshakeHash, + suite: &'static Tls12CipherSuite, + server_cert_sct_list: Option, + server_cert_chain: CertificatePayload, + must_issue_new_ticket: bool, +} + +impl State for ExpectCertificateStatusOrServerKx { + fn handle(self: Box, cx: &mut ClientContext<'_>, m: Message) -> hs::NextStateOrError { + match m.payload { + MessagePayload::Handshake { + parsed: + HandshakeMessagePayload { + payload: HandshakePayload::ServerKeyExchange(..), + .. + }, + .. + } => Box::new(ExpectServerKx { + config: self.config, + resuming_session: self.resuming_session, + session_id: self.session_id, + server_name: self.server_name, + randoms: self.randoms, + using_ems: self.using_ems, + transcript: self.transcript, + suite: self.suite, + server_cert: ServerCertDetails::new( + self.server_cert_chain, + vec![], + self.server_cert_sct_list, + ), + must_issue_new_ticket: self.must_issue_new_ticket, + }) + .handle(cx, m), + MessagePayload::Handshake { + parsed: + HandshakeMessagePayload { + payload: HandshakePayload::CertificateStatus(..), + .. + }, + .. + } => Box::new(ExpectCertificateStatus { + config: self.config, + resuming_session: self.resuming_session, + session_id: self.session_id, + server_name: self.server_name, + randoms: self.randoms, + using_ems: self.using_ems, + transcript: self.transcript, + suite: self.suite, + server_cert_sct_list: self.server_cert_sct_list, + server_cert_chain: self.server_cert_chain, + must_issue_new_ticket: self.must_issue_new_ticket, + }) + .handle(cx, m), + payload => Err(inappropriate_handshake_message( + &payload, + &[ContentType::Handshake], + &[ + HandshakeType::ServerKeyExchange, + HandshakeType::CertificateStatus, + ], + )), + } + } +} + +struct ExpectCertificateStatus { + config: Arc, + resuming_session: Option, + session_id: SessionID, + server_name: ServerName, + randoms: ConnectionRandoms, + using_ems: bool, + transcript: HandshakeHash, + suite: &'static Tls12CipherSuite, + server_cert_sct_list: Option, + server_cert_chain: CertificatePayload, + must_issue_new_ticket: bool, +} + +impl State for ExpectCertificateStatus { + fn handle( + mut self: Box, + _cx: &mut ClientContext<'_>, + m: Message, + ) -> hs::NextStateOrError { + self.transcript.add_message(&m); + let server_cert_ocsp_response = require_handshake_msg_move!( + m, + HandshakeType::CertificateStatus, + HandshakePayload::CertificateStatus + )? + .into_inner(); + + trace!( + "Server stapled OCSP response is {:?}", + &server_cert_ocsp_response + ); + + let server_cert = ServerCertDetails::new( + self.server_cert_chain, + server_cert_ocsp_response, + self.server_cert_sct_list, + ); + + Ok(Box::new(ExpectServerKx { + config: self.config, + resuming_session: self.resuming_session, + session_id: self.session_id, + server_name: self.server_name, + randoms: self.randoms, + using_ems: self.using_ems, + transcript: self.transcript, + suite: self.suite, + server_cert, + must_issue_new_ticket: self.must_issue_new_ticket, + })) + } +} + +struct ExpectServerKx { + config: Arc, + resuming_session: Option, + session_id: SessionID, + server_name: ServerName, + randoms: ConnectionRandoms, + using_ems: bool, + transcript: HandshakeHash, + suite: &'static Tls12CipherSuite, + server_cert: ServerCertDetails, + must_issue_new_ticket: bool, +} + +impl State for ExpectServerKx { + fn handle(mut self: Box, cx: &mut ClientContext<'_>, m: Message) -> hs::NextStateOrError { + let opaque_kx = require_handshake_msg!( + m, + HandshakeType::ServerKeyExchange, + HandshakePayload::ServerKeyExchange + )?; + self.transcript.add_message(&m); + + let ecdhe = opaque_kx + .unwrap_given_kxa(&self.suite.kx) + .ok_or_else(|| { + cx.common + .send_fatal_alert(AlertDescription::DecodeError); + Error::CorruptMessagePayload(ContentType::Handshake) + })?; + + // Save the signature and signed parameters for later verification. + let mut kx_params = Vec::new(); + ecdhe.params.encode(&mut kx_params); + let server_kx = ServerKxDetails::new(kx_params, ecdhe.dss); + + #[cfg_attr(not(feature = "logging"), allow(unused_variables))] + { + debug!("ECDHE curve is {:?}", ecdhe.params.curve_params); + } + + Ok(Box::new(ExpectServerDoneOrCertReq { + config: self.config, + resuming_session: self.resuming_session, + session_id: self.session_id, + server_name: self.server_name, + randoms: self.randoms, + using_ems: self.using_ems, + transcript: self.transcript, + suite: self.suite, + server_cert: self.server_cert, + server_kx, + must_issue_new_ticket: self.must_issue_new_ticket, + })) + } +} + +fn emit_certificate( + transcript: &mut HandshakeHash, + cert_chain: CertificatePayload, + common: &mut CommonState, +) { + let cert = Message { + version: ProtocolVersion::TLSv1_2, + payload: MessagePayload::handshake(HandshakeMessagePayload { + typ: HandshakeType::Certificate, + payload: HandshakePayload::Certificate(cert_chain), + }), + }; + + transcript.add_message(&cert); + common.send_msg(cert, false); +} + +fn emit_clientkx(transcript: &mut HandshakeHash, common: &mut CommonState, pubkey: &PublicKey) { + let mut buf = Vec::new(); + let ecpoint = PayloadU8::new(Vec::from(pubkey.as_ref())); + ecpoint.encode(&mut buf); + let pubkey = Payload::new(buf); + + let ckx = Message { + version: ProtocolVersion::TLSv1_2, + payload: MessagePayload::handshake(HandshakeMessagePayload { + typ: HandshakeType::ClientKeyExchange, + payload: HandshakePayload::ClientKeyExchange(pubkey), + }), + }; + + transcript.add_message(&ckx); + common.send_msg(ckx, false); +} + +fn emit_certverify( + transcript: &mut HandshakeHash, + signer: &dyn Signer, + common: &mut CommonState, +) -> Result<(), Error> { + let message = transcript + .take_handshake_buf() + .ok_or_else(|| Error::General("Expected transcript".to_owned()))?; + + let scheme = signer.scheme(); + let sig = signer.sign(&message)?; + let body = DigitallySignedStruct::new(scheme, sig); + + let m = Message { + version: ProtocolVersion::TLSv1_2, + payload: MessagePayload::handshake(HandshakeMessagePayload { + typ: HandshakeType::CertificateVerify, + payload: HandshakePayload::CertificateVerify(body), + }), + }; + + transcript.add_message(&m); + common.send_msg(m, false); + Ok(()) +} + +fn emit_ccs(common: &mut CommonState) { + let ccs = Message { + version: ProtocolVersion::TLSv1_2, + payload: MessagePayload::ChangeCipherSpec(ChangeCipherSpecPayload {}), + }; + + common.send_msg(ccs, false); +} + +fn emit_finished( + secrets: &ConnectionSecrets, + transcript: &mut HandshakeHash, + common: &mut CommonState, +) { + let vh = transcript.get_current_hash(); + let verify_data = secrets.client_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 ServerKxDetails { + kx_params: Vec, + kx_sig: DigitallySignedStruct, +} + +impl ServerKxDetails { + fn new(params: Vec, sig: DigitallySignedStruct) -> Self { + Self { + kx_params: params, + kx_sig: sig, + } + } +} + +// --- Either a CertificateRequest, or a ServerHelloDone. --- +// Existence of the CertificateRequest tells us the server is asking for +// client auth. Otherwise we go straight to ServerHelloDone. +struct ExpectServerDoneOrCertReq { + config: Arc, + resuming_session: Option, + session_id: SessionID, + server_name: ServerName, + randoms: ConnectionRandoms, + using_ems: bool, + transcript: HandshakeHash, + suite: &'static Tls12CipherSuite, + server_cert: ServerCertDetails, + server_kx: ServerKxDetails, + must_issue_new_ticket: bool, +} + +impl State for ExpectServerDoneOrCertReq { + fn handle(mut self: Box, cx: &mut ClientContext<'_>, m: Message) -> hs::NextStateOrError { + if matches!( + m.payload, + MessagePayload::Handshake { + parsed: HandshakeMessagePayload { + payload: HandshakePayload::CertificateRequest(_), + .. + }, + .. + } + ) { + Box::new(ExpectCertificateRequest { + config: self.config, + resuming_session: self.resuming_session, + session_id: self.session_id, + server_name: self.server_name, + randoms: self.randoms, + using_ems: self.using_ems, + transcript: self.transcript, + suite: self.suite, + server_cert: self.server_cert, + server_kx: self.server_kx, + must_issue_new_ticket: self.must_issue_new_ticket, + }) + .handle(cx, m) + } else { + self.transcript.abandon_client_auth(); + + Box::new(ExpectServerDone { + config: self.config, + resuming_session: self.resuming_session, + session_id: self.session_id, + server_name: self.server_name, + randoms: self.randoms, + using_ems: self.using_ems, + transcript: self.transcript, + suite: self.suite, + server_cert: self.server_cert, + server_kx: self.server_kx, + client_auth: None, + must_issue_new_ticket: self.must_issue_new_ticket, + }) + .handle(cx, m) + } + } +} + +struct ExpectCertificateRequest { + config: Arc, + resuming_session: Option, + session_id: SessionID, + server_name: ServerName, + randoms: ConnectionRandoms, + using_ems: bool, + transcript: HandshakeHash, + suite: &'static Tls12CipherSuite, + server_cert: ServerCertDetails, + server_kx: ServerKxDetails, + must_issue_new_ticket: bool, +} + +impl State for ExpectCertificateRequest { + fn handle( + mut self: Box, + _cx: &mut ClientContext<'_>, + m: Message, + ) -> hs::NextStateOrError { + let certreq = require_handshake_msg!( + m, + HandshakeType::CertificateRequest, + HandshakePayload::CertificateRequest + )?; + self.transcript.add_message(&m); + debug!("Got CertificateRequest {:?}", certreq); + + // The RFC jovially describes the design here as 'somewhat complicated' + // and 'somewhat underspecified'. So thanks for that. + // + // We ignore certreq.certtypes as a result, since the information it contains + // is entirely duplicated in certreq.sigschemes. + + const NO_CONTEXT: Option> = None; // TLS 1.2 doesn't use a context. + let client_auth = ClientAuthDetails::resolve( + self.config + .client_auth_cert_resolver + .as_ref(), + Some(&certreq.canames), + &certreq.sigschemes, + NO_CONTEXT, + ); + + Ok(Box::new(ExpectServerDone { + config: self.config, + resuming_session: self.resuming_session, + session_id: self.session_id, + server_name: self.server_name, + randoms: self.randoms, + using_ems: self.using_ems, + transcript: self.transcript, + suite: self.suite, + server_cert: self.server_cert, + server_kx: self.server_kx, + client_auth: Some(client_auth), + must_issue_new_ticket: self.must_issue_new_ticket, + })) + } +} + +struct ExpectServerDone { + config: Arc, + resuming_session: Option, + session_id: SessionID, + server_name: ServerName, + randoms: ConnectionRandoms, + using_ems: bool, + transcript: HandshakeHash, + suite: &'static Tls12CipherSuite, + server_cert: ServerCertDetails, + server_kx: ServerKxDetails, + client_auth: Option, + must_issue_new_ticket: bool, +} + +impl State for ExpectServerDone { + fn handle(self: Box, cx: &mut ClientContext<'_>, m: Message) -> hs::NextStateOrError { + match m.payload { + MessagePayload::Handshake { + parsed: + HandshakeMessagePayload { + payload: HandshakePayload::ServerHelloDone, + .. + }, + .. + } => {} + payload => { + return Err(inappropriate_handshake_message( + &payload, + &[ContentType::Handshake], + &[HandshakeType::ServerHelloDone], + )); + } + } + + let mut st = *self; + st.transcript.add_message(&m); + + cx.common.check_aligned_handshake()?; + + trace!("Server cert is {:?}", st.server_cert.cert_chain); + debug!("Server DNS name is {:?}", st.server_name); + + let suite = st.suite; + + // 1. Verify the cert chain. + // 2. Verify any SCTs provided with the certificate. + // 3. Verify that the top certificate signed their kx. + // 4. If doing client auth, send our Certificate. + // 5. Complete the key exchange: + // a) generate our kx pair + // b) emit a ClientKeyExchange containing it + // c) if doing client auth, emit a CertificateVerify + // d) emit a CCS + // e) derive the shared keys, and start encryption + // 6. emit a Finished, our first encrypted message under the new keys. + + // 1. + let (end_entity, intermediates) = st + .server_cert + .cert_chain + .split_first() + .ok_or(Error::NoCertificatesPresented)?; + let now = std::time::SystemTime::now(); + let cert_verified = st + .config + .verifier + .verify_server_cert( + end_entity, + intermediates, + &st.server_name, + &mut st.server_cert.scts(), + &st.server_cert.ocsp_response, + now, + ) + .map_err(|err| hs::send_cert_error_alert(cx.common, err))?; + + // 3. + // Build up the contents of the signed message. + // It's ClientHello.random || ServerHello.random || ServerKeyExchange.params + let sig_verified = { + let mut message = Vec::new(); + message.extend_from_slice(&st.randoms.client); + message.extend_from_slice(&st.randoms.server); + message.extend_from_slice(&st.server_kx.kx_params); + + // Check the signature is compatible with the ciphersuite. + let sig = &st.server_kx.kx_sig; + if !SupportedCipherSuite::from(suite).usable_for_signature_algorithm(sig.scheme.sign()) + { + let error_message = format!( + "peer signed kx with wrong algorithm (got {:?} expect {:?})", + sig.scheme.sign(), + suite.sign + ); + return Err(Error::PeerMisbehavedError(error_message)); + } + + st.config + .verifier + .verify_tls12_signature(&message, &st.server_cert.cert_chain[0], sig) + .map_err(|err| hs::send_cert_error_alert(cx.common, err))? + }; + cx.common.peer_certificates = Some(st.server_cert.cert_chain); + + // 4. + if let Some(client_auth) = &st.client_auth { + let certs = match client_auth { + ClientAuthDetails::Empty { .. } => Vec::new(), + ClientAuthDetails::Verify { certkey, .. } => certkey.cert.clone(), + }; + emit_certificate(&mut st.transcript, certs, cx.common); + } + + // 5a. + let ecdh_params = + tls12::decode_ecdh_params::(cx.common, &st.server_kx.kx_params)?; + let group = + kx::KeyExchange::choose(ecdh_params.curve_params.named_group, &st.config.kx_groups) + .ok_or_else(|| { + Error::PeerMisbehavedError("peer chose an unsupported group".to_string()) + })?; + let eager_key_index = cx + .data + .tls12_eager_key_exchanges + .iter() + .position(|kx| kx.group() == group.name); + let kx = if let Some(index) = eager_key_index { + cx.data.tls12_eager_key_exchanges.swap_remove(index) + } else { + kx::KeyExchange::start(group).ok_or(Error::FailedToGetRandomBytes)? + }; + + // 5b. + let mut transcript = st.transcript; + emit_clientkx(&mut transcript, cx.common, &kx.pubkey); + // nb. EMS handshake hash only runs up to ClientKeyExchange. + let ems_seed = st + .using_ems + .then(|| transcript.get_current_hash()); + + // 5c. + if let Some(ClientAuthDetails::Verify { signer, .. }) = &st.client_auth { + emit_certverify(&mut transcript, signer.as_ref(), cx.common)?; + } + + // 5d. + emit_ccs(cx.common); + + // 5e. Now commit secrets. + let secrets = ConnectionSecrets::from_key_exchange( + kx, + &ecdh_params.public.0, + ems_seed, + st.randoms, + suite, + )?; + + st.config.key_log.log( + "CLIENT_RANDOM", + &secrets.randoms.client, + &secrets.master_secret, + ); + cx.common + .start_encryption_tls12(&secrets, Side::Client); + cx.common + .record_layer + .start_encrypting(); + + // 6. + emit_finished(&secrets, &mut transcript, cx.common); + + if st.must_issue_new_ticket { + Ok(Box::new(ExpectNewTicket { + config: st.config, + secrets, + resuming_session: st.resuming_session, + session_id: st.session_id, + server_name: st.server_name, + using_ems: st.using_ems, + transcript, + resuming: false, + cert_verified, + sig_verified, + })) + } else { + Ok(Box::new(ExpectCcs { + config: st.config, + secrets, + resuming_session: st.resuming_session, + session_id: st.session_id, + server_name: st.server_name, + using_ems: st.using_ems, + transcript, + ticket: None, + resuming: false, + cert_verified, + sig_verified, + })) + } + } +} + +struct ExpectNewTicket { + config: Arc, + secrets: ConnectionSecrets, + resuming_session: Option, + session_id: SessionID, + server_name: ServerName, + using_ems: bool, + transcript: HandshakeHash, + resuming: bool, + cert_verified: verify::ServerCertVerified, + sig_verified: verify::HandshakeSignatureValid, +} + +impl State for ExpectNewTicket { + fn handle( + mut self: Box, + _cx: &mut ClientContext<'_>, + m: Message, + ) -> hs::NextStateOrError { + self.transcript.add_message(&m); + + let nst = require_handshake_msg_move!( + m, + HandshakeType::NewSessionTicket, + HandshakePayload::NewSessionTicket + )?; + + Ok(Box::new(ExpectCcs { + config: self.config, + secrets: self.secrets, + resuming_session: self.resuming_session, + session_id: self.session_id, + server_name: self.server_name, + using_ems: self.using_ems, + transcript: self.transcript, + ticket: Some(nst), + resuming: self.resuming, + cert_verified: self.cert_verified, + sig_verified: self.sig_verified, + })) + } +} + +// -- Waiting for their CCS -- +struct ExpectCcs { + config: Arc, + secrets: ConnectionSecrets, + resuming_session: Option, + session_id: SessionID, + server_name: ServerName, + using_ems: bool, + transcript: HandshakeHash, + ticket: Option, + resuming: bool, + cert_verified: verify::ServerCertVerified, + sig_verified: verify::HandshakeSignatureValid, +} + +impl State for ExpectCcs { + fn handle(self: Box, cx: &mut ClientContext<'_>, 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()?; + + // nb. msgs layer validates trivial contents of CCS + cx.common + .record_layer + .start_decrypting(); + + Ok(Box::new(ExpectFinished { + config: self.config, + secrets: self.secrets, + resuming_session: self.resuming_session, + session_id: self.session_id, + server_name: self.server_name, + using_ems: self.using_ems, + transcript: self.transcript, + ticket: self.ticket, + resuming: self.resuming, + cert_verified: self.cert_verified, + sig_verified: self.sig_verified, + })) + } +} + +struct ExpectFinished { + config: Arc, + resuming_session: Option, + session_id: SessionID, + server_name: ServerName, + using_ems: bool, + transcript: HandshakeHash, + ticket: Option, + secrets: ConnectionSecrets, + resuming: bool, + cert_verified: verify::ServerCertVerified, + sig_verified: verify::HandshakeSignatureValid, +} + +impl ExpectFinished { + // -- Waiting for their finished -- + fn save_session(&mut self, cx: &mut ClientContext<'_>) { + // Save a ticket. If we got a new ticket, save that. Otherwise, save the + // original ticket again. + let (mut ticket, lifetime) = match self.ticket.take() { + Some(nst) => (nst.ticket.0, nst.lifetime_hint), + None => (Vec::new(), 0), + }; + + if ticket.is_empty() { + if let Some(resuming_session) = &mut self.resuming_session { + ticket = resuming_session.take_ticket(); + } + } + + if self.session_id.is_empty() && ticket.is_empty() { + debug!("Session not saved: server didn't allocate id or ticket"); + return; + } + + let time_now = match TimeBase::now() { + Ok(time_now) => time_now, + #[allow(unused_variables)] + Err(e) => { + debug!("Session not saved: {}", e); + return; + } + }; + + let key = persist::ClientSessionKey::session_for_server_name(&self.server_name); + let value = persist::Tls12ClientSessionValue::new( + self.secrets.suite(), + self.session_id, + ticket, + self.secrets.get_master_secret(), + cx.common + .peer_certificates + .clone() + .unwrap_or_default(), + time_now, + lifetime, + self.using_ems, + ); + + let worked = self + .config + .session_storage + .put(key.get_encoding(), value.get_encoding()); + + if worked { + debug!("Session saved"); + } else { + debug!("Session not saved"); + } + } +} + +impl State for ExpectFinished { + fn handle(self: Box, cx: &mut ClientContext<'_>, m: Message) -> hs::NextStateOrError { + let mut st = *self; + let finished = + require_handshake_msg!(m, HandshakeType::Finished, HandshakePayload::Finished)?; + + cx.common.check_aligned_handshake()?; + + // Work out what verify_data we expect. + let vh = st.transcript.get_current_hash(); + let expect_verify_data = st.secrets.server_verify_data(&vh); + + // Constant-time verification of this is relatively unimportant: they only + // get one chance. But it can't hurt. + 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())?; + + // Hash this message too. + st.transcript.add_message(&m); + + st.save_session(cx); + + if st.resuming { + emit_ccs(cx.common); + cx.common + .record_layer + .start_encrypting(); + emit_finished(&st.secrets, &mut st.transcript, cx.common); + } + + cx.common.start_traffic(); + Ok(Box::new(ExpectTraffic { + secrets: st.secrets, + _cert_verified: st.cert_verified, + _sig_verified: st.sig_verified, + _fin_verified, + })) + } +} + +// -- Traffic transit state -- +struct ExpectTraffic { + secrets: ConnectionSecrets, + _cert_verified: verify::ServerCertVerified, + _sig_verified: verify::HandshakeSignatureValid, + _fin_verified: verify::FinishedMessageVerified, +} + +impl State for ExpectTraffic { + fn handle(self: Box, cx: &mut ClientContext<'_>, 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 { + self.secrets + .extract_secrets(Side::Client) + } +} diff --git a/third_party/rustls-fork-shadow-tls/src/client/tls13.rs b/third_party/rustls-fork-shadow-tls/src/client/tls13.rs new file mode 100644 index 0000000..4c118bb --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/src/client/tls13.rs @@ -0,0 +1,1191 @@ +use crate::check::inappropriate_handshake_message; +use crate::conn::{CommonState, ConnectionRandoms, State}; +use crate::enums::{ProtocolVersion, SignatureScheme}; +use crate::error::Error; +use crate::hash_hs::{HandshakeHash, HandshakeHashBuffer}; +use crate::kx; +#[cfg(feature = "logging")] +use crate::log::{debug, trace, warn}; +use crate::msgs::base::{Payload, PayloadU8}; +use crate::msgs::ccs::ChangeCipherSpecPayload; +use crate::msgs::codec::Codec; +use crate::msgs::enums::KeyUpdateRequest; +use crate::msgs::enums::{AlertDescription, NamedGroup}; +use crate::msgs::enums::{ContentType, ExtensionType, HandshakeType}; +use crate::msgs::handshake::ClientExtension; +use crate::msgs::handshake::DigitallySignedStruct; +use crate::msgs::handshake::EncryptedExtensions; +use crate::msgs::handshake::NewSessionTicketPayloadTLS13; +use crate::msgs::handshake::{CertificateEntry, CertificatePayloadTLS13}; +use crate::msgs::handshake::{HandshakeMessagePayload, HandshakePayload}; +use crate::msgs::handshake::{HasServerExtensions, ServerHelloPayload}; +use crate::msgs::handshake::{PresharedKeyIdentity, PresharedKeyOffer}; +use crate::msgs::message::{Message, MessagePayload}; +use crate::msgs::persist; +use crate::tls13::key_schedule::{ + KeyScheduleEarly, KeyScheduleHandshake, KeySchedulePreHandshake, KeyScheduleTraffic, +}; +use crate::tls13::Tls13CipherSuite; +use crate::verify; +#[cfg(feature = "quic")] +use crate::{conn::Protocol, msgs::base::PayloadU16, quic}; +#[cfg(feature = "secret_extraction")] +use crate::{conn::Side, suites::PartiallyExtractedSecrets}; +use crate::{sign, KeyLog}; + +use super::client_conn::ClientConnectionData; +use super::hs::ClientContext; +use crate::client::common::ServerCertDetails; +use crate::client::common::{ClientAuthDetails, ClientHelloDetails}; +use crate::client::{hs, ClientConfig, ServerName, StoresClientSessions}; + +use crate::ticketer::TimeBase; +use ring::constant_time; + +use crate::sign::{CertifiedKey, Signer}; +use std::sync::Arc; + +// Extensions we expect in plaintext in the ServerHello. +static ALLOWED_PLAINTEXT_EXTS: &[ExtensionType] = &[ + ExtensionType::KeyShare, + ExtensionType::PreSharedKey, + ExtensionType::SupportedVersions, +]; + +// Only the intersection of things we offer, and those disallowed +// in TLS1.3 +static DISALLOWED_TLS13_EXTS: &[ExtensionType] = &[ + ExtensionType::ECPointFormats, + ExtensionType::SessionTicket, + ExtensionType::RenegotiationInfo, + ExtensionType::ExtendedMasterSecret, +]; + +pub(super) fn handle_server_hello( + config: Arc, + cx: &mut ClientContext, + server_hello: &ServerHelloPayload, + mut resuming_session: Option, + server_name: ServerName, + randoms: ConnectionRandoms, + suite: &'static Tls13CipherSuite, + transcript: HandshakeHash, + early_key_schedule: Option, + hello: ClientHelloDetails, + our_key_share: kx::KeyExchange, + mut sent_tls13_fake_ccs: bool, +) -> hs::NextStateOrError { + validate_server_hello(cx.common, server_hello)?; + + let their_key_share = server_hello + .get_key_share() + .ok_or_else(|| { + cx.common + .send_fatal_alert(AlertDescription::MissingExtension); + Error::PeerMisbehavedError("missing key share".to_string()) + })?; + + if our_key_share.group() != their_key_share.group { + return Err(cx + .common + .illegal_param("wrong group for key share")); + } + + let key_schedule_pre_handshake = if let (Some(selected_psk), Some(early_key_schedule)) = + (server_hello.get_psk_index(), early_key_schedule) + { + if let Some(ref resuming) = resuming_session { + let resuming_suite = match suite.can_resume_from(resuming.suite()) { + Some(resuming) => resuming, + None => { + return Err(cx + .common + .illegal_param("server resuming incompatible suite")); + } + }; + + // If the server varies the suite here, we will have encrypted early data with + // the wrong suite. + if cx.data.early_data.is_enabled() && resuming_suite != suite { + return Err(cx + .common + .illegal_param("server varied suite with early data")); + } + + if selected_psk != 0 { + return Err(cx + .common + .illegal_param("server selected invalid psk")); + } + + debug!("Resuming using PSK"); + // The key schedule has been initialized and set in fill_in_psk_binder() + } else { + return Err(Error::PeerMisbehavedError( + "server selected unoffered psk".to_string(), + )); + } + KeySchedulePreHandshake::from(early_key_schedule) + } else { + debug!("Not resuming"); + // Discard the early data key schedule. + cx.data.early_data.rejected(); + cx.common.early_traffic = false; + resuming_session.take(); + KeySchedulePreHandshake::new(suite.hkdf_algorithm) + }; + + let key_schedule = our_key_share.complete(&their_key_share.payload.0, |secret| { + Ok(key_schedule_pre_handshake.into_handshake(secret)) + })?; + + // Remember what KX group the server liked for next time. + save_kx_hint(&config, &server_name, their_key_share.group); + + // If we change keying when a subsequent handshake message is being joined, + // the two halves will have different record layer protections. Disallow this. + cx.common.check_aligned_handshake()?; + + let hash_at_client_recvd_server_hello = transcript.get_current_hash(); + + let (key_schedule, client_key, server_key) = key_schedule.derive_handshake_secrets( + hash_at_client_recvd_server_hello, + &*config.key_log, + &randoms.client, + ); + + // Decrypt with the peer's key, encrypt with our own key + cx.common + .record_layer + .set_message_decrypter(suite.derive_decrypter(&server_key)); + + if !cx.data.early_data.is_enabled() { + // Set the client encryption key for handshakes if early data is not used + cx.common + .record_layer + .set_message_encrypter(suite.derive_encrypter(&client_key)); + } + + #[cfg(feature = "quic")] + if cx.common.is_quic() { + cx.common.quic.hs_secrets = Some(quic::Secrets::new(client_key, server_key, suite, true)); + } + + emit_fake_ccs(&mut sent_tls13_fake_ccs, cx.common); + + Ok(Box::new(ExpectEncryptedExtensions { + config, + resuming_session, + server_name, + randoms, + suite, + transcript, + key_schedule, + hello, + })) +} + +fn validate_server_hello( + common: &mut CommonState, + server_hello: &ServerHelloPayload, +) -> Result<(), Error> { + for ext in &server_hello.extensions { + if !ALLOWED_PLAINTEXT_EXTS.contains(&ext.get_type()) { + common.send_fatal_alert(AlertDescription::UnsupportedExtension); + return Err(Error::PeerMisbehavedError( + "server sent unexpected cleartext ext".to_string(), + )); + } + } + + Ok(()) +} + +pub(super) fn initial_key_share( + config: &ClientConfig, + server_name: &ServerName, +) -> Result { + let key = persist::ClientSessionKey::hint_for_server_name(server_name); + let key_buf = key.get_encoding(); + + let maybe_value = config.session_storage.get(&key_buf); + + let group = maybe_value + .and_then(|enc| NamedGroup::read_bytes(&enc)) + .and_then(|group| kx::KeyExchange::choose(group, &config.kx_groups)) + .unwrap_or_else(|| { + config + .kx_groups + .first() + .expect("No kx groups configured") + }); + + kx::KeyExchange::start(group).ok_or(Error::FailedToGetRandomBytes) +} + +fn save_kx_hint(config: &ClientConfig, server_name: &ServerName, group: NamedGroup) { + let key = persist::ClientSessionKey::hint_for_server_name(server_name); + + config + .session_storage + .put(key.get_encoding(), group.get_encoding()); +} + +/// This implements the horrifying TLS1.3 hack where PSK binders have a +/// data dependency on the message they are contained within. +pub(super) fn fill_in_psk_binder( + resuming: &persist::Tls13ClientSessionValue, + transcript: &HandshakeHashBuffer, + hmp: &mut HandshakeMessagePayload, +) -> KeyScheduleEarly { + // We need to know the hash function of the suite we're trying to resume into. + let hkdf_alg = resuming.suite().hkdf_algorithm; + let suite_hash = resuming.suite().hash_algorithm(); + + // The binder is calculated over the clienthello, but doesn't include itself or its + // length, or the length of its container. + let binder_plaintext = hmp.get_encoding_for_binder_signing(); + let handshake_hash = transcript.get_hash_given(suite_hash, &binder_plaintext); + + // Run a fake key_schedule to simulate what the server will do if it chooses + // to resume. + let key_schedule = KeyScheduleEarly::new(hkdf_alg, resuming.secret()); + let real_binder = key_schedule.resumption_psk_binder_key_and_sign_verify_data(&handshake_hash); + + if let HandshakePayload::ClientHello(ref mut ch) = hmp.payload { + ch.set_psk_binder(real_binder.as_ref()); + }; + + key_schedule +} + +pub(super) fn prepare_resumption( + config: &ClientConfig, + cx: &mut ClientContext<'_>, + ticket: Vec, + resuming_session: &persist::Retrieved<&persist::Tls13ClientSessionValue>, + exts: &mut Vec, + doing_retry: bool, +) { + let resuming_suite = resuming_session.suite(); + cx.common.suite = Some(resuming_suite.into()); + cx.data.resumption_ciphersuite = Some(resuming_suite.into()); + // The EarlyData extension MUST be supplied together with the + // PreSharedKey extension. + let max_early_data_size = resuming_session.max_early_data_size(); + if config.enable_early_data && max_early_data_size > 0 && !doing_retry { + cx.data + .early_data + .enable(max_early_data_size as usize); + exts.push(ClientExtension::EarlyData); + } + + // Finally, and only for TLS1.3 with a ticket resumption, include a binder + // for our ticket. This must go last. + // + // Include an empty binder. It gets filled in below because it depends on + // the message it's contained in (!!!). + let obfuscated_ticket_age = resuming_session.obfuscated_ticket_age(); + + let binder_len = resuming_suite + .hash_algorithm() + .output_len; + let binder = vec![0u8; binder_len]; + + let psk_identity = PresharedKeyIdentity::new(ticket, obfuscated_ticket_age); + let psk_ext = PresharedKeyOffer::new(psk_identity, binder); + exts.push(ClientExtension::PresharedKey(psk_ext)); +} + +pub(super) fn derive_early_traffic_secret( + key_log: &dyn KeyLog, + cx: &mut ClientContext<'_>, + resuming_suite: &'static Tls13CipherSuite, + early_key_schedule: &KeyScheduleEarly, + sent_tls13_fake_ccs: &mut bool, + transcript_buffer: &HandshakeHashBuffer, + client_random: &[u8; 32], +) { + // For middlebox compatibility + emit_fake_ccs(sent_tls13_fake_ccs, cx.common); + + let client_hello_hash = transcript_buffer.get_hash_given(resuming_suite.hash_algorithm(), &[]); + let client_early_traffic_secret = + early_key_schedule.client_early_traffic_secret(&client_hello_hash, key_log, client_random); + // Set early data encryption key + cx.common + .record_layer + .set_message_encrypter(resuming_suite.derive_encrypter(&client_early_traffic_secret)); + + #[cfg(feature = "quic")] + if cx.common.is_quic() { + cx.common.quic.early_secret = Some(client_early_traffic_secret); + } + + // Now the client can send encrypted early data + cx.common.early_traffic = true; + trace!("Starting early data traffic"); +} + +pub(super) fn emit_fake_ccs(sent_tls13_fake_ccs: &mut bool, common: &mut CommonState) { + if common.is_quic() { + return; + } + + if std::mem::replace(sent_tls13_fake_ccs, true) { + return; + } + + let m = Message { + version: ProtocolVersion::TLSv1_2, + payload: MessagePayload::ChangeCipherSpec(ChangeCipherSpecPayload {}), + }; + common.send_msg(m, false); +} + +fn validate_encrypted_extensions( + common: &mut CommonState, + hello: &ClientHelloDetails, + exts: &EncryptedExtensions, +) -> Result<(), Error> { + if exts.has_duplicate_extension() { + common.send_fatal_alert(AlertDescription::DecodeError); + return Err(Error::PeerMisbehavedError( + "server sent duplicate encrypted extensions".to_string(), + )); + } + + if hello.server_sent_unsolicited_extensions(exts, &[]) { + common.send_fatal_alert(AlertDescription::UnsupportedExtension); + let msg = "server sent unsolicited encrypted extension".to_string(); + return Err(Error::PeerMisbehavedError(msg)); + } + + for ext in exts { + if ALLOWED_PLAINTEXT_EXTS.contains(&ext.get_type()) + || DISALLOWED_TLS13_EXTS.contains(&ext.get_type()) + { + common.send_fatal_alert(AlertDescription::UnsupportedExtension); + let msg = "server sent inappropriate encrypted extension".to_string(); + return Err(Error::PeerMisbehavedError(msg)); + } + } + + Ok(()) +} + +struct ExpectEncryptedExtensions { + config: Arc, + resuming_session: Option, + server_name: ServerName, + randoms: ConnectionRandoms, + suite: &'static Tls13CipherSuite, + transcript: HandshakeHash, + key_schedule: KeyScheduleHandshake, + hello: ClientHelloDetails, +} + +impl State for ExpectEncryptedExtensions { + fn handle(mut self: Box, cx: &mut ClientContext<'_>, m: Message) -> hs::NextStateOrError { + let exts = require_handshake_msg!( + m, + HandshakeType::EncryptedExtensions, + HandshakePayload::EncryptedExtensions + )?; + debug!("TLS1.3 encrypted extensions: {:?}", exts); + self.transcript.add_message(&m); + + validate_encrypted_extensions(cx.common, &self.hello, exts)?; + hs::process_alpn_protocol(cx.common, &self.config, exts.get_alpn_protocol())?; + + #[cfg(feature = "quic")] + { + // QUIC transport parameters + if cx.common.is_quic() { + match exts.get_quic_params_extension() { + Some(params) => cx.common.quic.params = Some(params), + None => { + return Err(cx + .common + .missing_extension("QUIC transport parameters not found")); + } + } + } + } + + if let Some(resuming_session) = self.resuming_session { + let was_early_traffic = cx.common.early_traffic; + if was_early_traffic { + if exts.early_data_extension_offered() { + cx.data.early_data.accepted(); + } else { + cx.data.early_data.rejected(); + cx.common.early_traffic = false; + } + } + + if was_early_traffic && !cx.common.early_traffic { + // If no early traffic, set the encryption key for handshakes + cx.common + .record_layer + .set_message_encrypter( + self.suite + .derive_encrypter(self.key_schedule.client_key()), + ); + } + + cx.common.peer_certificates = Some( + resuming_session + .server_cert_chain() + .to_vec(), + ); + + // We *don't* reverify the certificate chain here: resumption is a + // continuation of the previous session in terms of security policy. + let cert_verified = verify::ServerCertVerified::assertion(); + let sig_verified = verify::HandshakeSignatureValid::assertion(); + Ok(Box::new(ExpectFinished { + config: self.config, + server_name: self.server_name, + randoms: self.randoms, + suite: self.suite, + transcript: self.transcript, + key_schedule: self.key_schedule, + client_auth: None, + cert_verified, + sig_verified, + })) + } else { + if exts.early_data_extension_offered() { + let msg = "server sent early data extension without resumption".to_string(); + return Err(Error::PeerMisbehavedError(msg)); + } + Ok(Box::new(ExpectCertificateOrCertReq { + config: self.config, + server_name: self.server_name, + randoms: self.randoms, + suite: self.suite, + transcript: self.transcript, + key_schedule: self.key_schedule, + may_send_sct_list: self.hello.server_may_send_sct_list(), + })) + } + } +} + +struct ExpectCertificateOrCertReq { + config: Arc, + server_name: ServerName, + randoms: ConnectionRandoms, + suite: &'static Tls13CipherSuite, + transcript: HandshakeHash, + key_schedule: KeyScheduleHandshake, + may_send_sct_list: bool, +} + +impl State for ExpectCertificateOrCertReq { + fn handle(self: Box, cx: &mut ClientContext<'_>, m: Message) -> hs::NextStateOrError { + match m.payload { + MessagePayload::Handshake { + parsed: + HandshakeMessagePayload { + payload: HandshakePayload::CertificateTLS13(..), + .. + }, + .. + } => Box::new(ExpectCertificate { + config: self.config, + server_name: self.server_name, + randoms: self.randoms, + suite: self.suite, + transcript: self.transcript, + key_schedule: self.key_schedule, + may_send_sct_list: self.may_send_sct_list, + client_auth: None, + }) + .handle(cx, m), + MessagePayload::Handshake { + parsed: + HandshakeMessagePayload { + payload: HandshakePayload::CertificateRequestTLS13(..), + .. + }, + .. + } => Box::new(ExpectCertificateRequest { + config: self.config, + server_name: self.server_name, + randoms: self.randoms, + suite: self.suite, + transcript: self.transcript, + key_schedule: self.key_schedule, + may_send_sct_list: self.may_send_sct_list, + }) + .handle(cx, m), + payload => Err(inappropriate_handshake_message( + &payload, + &[ContentType::Handshake], + &[ + HandshakeType::Certificate, + HandshakeType::CertificateRequest, + ], + )), + } + } +} + +// TLS1.3 version of CertificateRequest handling. We then move to expecting the server +// Certificate. Unfortunately the CertificateRequest type changed in an annoying way +// in TLS1.3. +struct ExpectCertificateRequest { + config: Arc, + server_name: ServerName, + randoms: ConnectionRandoms, + suite: &'static Tls13CipherSuite, + transcript: HandshakeHash, + key_schedule: KeyScheduleHandshake, + may_send_sct_list: bool, +} + +impl State for ExpectCertificateRequest { + fn handle(mut self: Box, cx: &mut ClientContext<'_>, m: Message) -> hs::NextStateOrError { + let certreq = &require_handshake_msg!( + m, + HandshakeType::CertificateRequest, + HandshakePayload::CertificateRequestTLS13 + )?; + self.transcript.add_message(&m); + debug!("Got CertificateRequest {:?}", certreq); + + // Fortunately the problems here in TLS1.2 and prior are corrected in + // TLS1.3. + + // Must be empty during handshake. + if !certreq.context.0.is_empty() { + warn!("Server sent non-empty certreq context"); + cx.common + .send_fatal_alert(AlertDescription::DecodeError); + return Err(Error::CorruptMessagePayload(ContentType::Handshake)); + } + + let tls13_sign_schemes = sign::supported_sign_tls13(); + let no_sigschemes = Vec::new(); + let compat_sigschemes = certreq + .get_sigalgs_extension() + .unwrap_or(&no_sigschemes) + .iter() + .cloned() + .filter(|scheme| tls13_sign_schemes.contains(scheme)) + .collect::>(); + + if compat_sigschemes.is_empty() { + cx.common + .send_fatal_alert(AlertDescription::HandshakeFailure); + return Err(Error::PeerIncompatibleError( + "server sent bad certreq schemes".to_string(), + )); + } + + let client_auth = ClientAuthDetails::resolve( + self.config + .client_auth_cert_resolver + .as_ref(), + certreq.get_authorities_extension(), + &compat_sigschemes, + Some(certreq.context.0.clone()), + ); + + Ok(Box::new(ExpectCertificate { + config: self.config, + server_name: self.server_name, + randoms: self.randoms, + suite: self.suite, + transcript: self.transcript, + key_schedule: self.key_schedule, + may_send_sct_list: self.may_send_sct_list, + client_auth: Some(client_auth), + })) + } +} + +struct ExpectCertificate { + config: Arc, + server_name: ServerName, + randoms: ConnectionRandoms, + suite: &'static Tls13CipherSuite, + transcript: HandshakeHash, + key_schedule: KeyScheduleHandshake, + may_send_sct_list: bool, + client_auth: Option, +} + +impl State for ExpectCertificate { + fn handle(mut self: Box, cx: &mut ClientContext<'_>, m: Message) -> hs::NextStateOrError { + let cert_chain = require_handshake_msg!( + m, + HandshakeType::Certificate, + HandshakePayload::CertificateTLS13 + )?; + self.transcript.add_message(&m); + + // This is only non-empty for client auth. + if !cert_chain.context.0.is_empty() { + warn!("certificate with non-empty context during handshake"); + cx.common + .send_fatal_alert(AlertDescription::DecodeError); + return Err(Error::CorruptMessagePayload(ContentType::Handshake)); + } + + if cert_chain.any_entry_has_duplicate_extension() + || cert_chain.any_entry_has_unknown_extension() + { + warn!("certificate chain contains unsolicited/unknown extension"); + cx.common + .send_fatal_alert(AlertDescription::UnsupportedExtension); + return Err(Error::PeerMisbehavedError( + "bad cert chain extensions".to_string(), + )); + } + + let server_cert = ServerCertDetails::new( + cert_chain.convert(), + cert_chain.get_end_entity_ocsp(), + cert_chain.get_end_entity_scts(), + ); + + if let Some(sct_list) = server_cert.scts.as_ref() { + if hs::sct_list_is_invalid(sct_list) { + let error_msg = "server sent invalid SCT list".to_string(); + return Err(Error::PeerMisbehavedError(error_msg)); + } + + if !self.may_send_sct_list { + let error_msg = "server sent unsolicited SCT list".to_string(); + return Err(Error::PeerMisbehavedError(error_msg)); + } + } + + Ok(Box::new(ExpectCertificateVerify { + config: self.config, + server_name: self.server_name, + randoms: self.randoms, + suite: self.suite, + transcript: self.transcript, + key_schedule: self.key_schedule, + server_cert, + client_auth: self.client_auth, + })) + } +} + +// --- TLS1.3 CertificateVerify --- +struct ExpectCertificateVerify { + config: Arc, + server_name: ServerName, + randoms: ConnectionRandoms, + suite: &'static Tls13CipherSuite, + transcript: HandshakeHash, + key_schedule: KeyScheduleHandshake, + server_cert: ServerCertDetails, + client_auth: Option, +} + +impl State for ExpectCertificateVerify { + fn handle(mut self: Box, cx: &mut ClientContext<'_>, m: Message) -> hs::NextStateOrError { + let cert_verify = require_handshake_msg!( + m, + HandshakeType::CertificateVerify, + HandshakePayload::CertificateVerify + )?; + + trace!("Server cert is {:?}", self.server_cert.cert_chain); + + // 1. Verify the certificate chain. + let (end_entity, intermediates) = self + .server_cert + .cert_chain + .split_first() + .ok_or(Error::NoCertificatesPresented)?; + let now = std::time::SystemTime::now(); + let cert_verified = self + .config + .verifier + .verify_server_cert( + end_entity, + intermediates, + &self.server_name, + &mut self.server_cert.scts(), + &self.server_cert.ocsp_response, + now, + ) + .map_err(|err| hs::send_cert_error_alert(cx.common, err))?; + + // 2. Verify their signature on the handshake. + let handshake_hash = self.transcript.get_current_hash(); + let sig_verified = self + .config + .verifier + .verify_tls13_signature( + &verify::construct_tls13_server_verify_message(&handshake_hash), + &self.server_cert.cert_chain[0], + cert_verify, + ) + .map_err(|err| hs::send_cert_error_alert(cx.common, err))?; + + cx.common.peer_certificates = Some(self.server_cert.cert_chain); + self.transcript.add_message(&m); + + Ok(Box::new(ExpectFinished { + config: self.config, + server_name: self.server_name, + randoms: self.randoms, + suite: self.suite, + transcript: self.transcript, + key_schedule: self.key_schedule, + client_auth: self.client_auth, + cert_verified, + sig_verified, + })) + } +} + +fn emit_certificate_tls13( + transcript: &mut HandshakeHash, + certkey: Option<&CertifiedKey>, + auth_context: Option>, + common: &mut CommonState, +) { + let context = auth_context.unwrap_or_default(); + + let mut cert_payload = CertificatePayloadTLS13 { + context: PayloadU8::new(context), + entries: Vec::new(), + }; + + if let Some(certkey) = certkey { + for cert in &certkey.cert { + cert_payload + .entries + .push(CertificateEntry::new(cert.clone())); + } + } + + let m = Message { + version: ProtocolVersion::TLSv1_3, + payload: MessagePayload::handshake(HandshakeMessagePayload { + typ: HandshakeType::Certificate, + payload: HandshakePayload::CertificateTLS13(cert_payload), + }), + }; + transcript.add_message(&m); + common.send_msg(m, true); +} + +fn emit_certverify_tls13( + transcript: &mut HandshakeHash, + signer: &dyn Signer, + common: &mut CommonState, +) -> Result<(), Error> { + let message = verify::construct_tls13_client_verify_message(&transcript.get_current_hash()); + + let scheme = signer.scheme(); + let sig = signer.sign(&message)?; + let dss = DigitallySignedStruct::new(scheme, sig); + + let m = Message { + version: ProtocolVersion::TLSv1_3, + payload: MessagePayload::handshake(HandshakeMessagePayload { + typ: HandshakeType::CertificateVerify, + payload: HandshakePayload::CertificateVerify(dss), + }), + }; + + transcript.add_message(&m); + common.send_msg(m, true); + Ok(()) +} + +fn emit_finished_tls13( + transcript: &mut HandshakeHash, + verify_data: ring::hmac::Tag, + common: &mut CommonState, +) { + let verify_data_payload = Payload::new(verify_data.as_ref()); + + let m = Message { + version: ProtocolVersion::TLSv1_3, + payload: MessagePayload::handshake(HandshakeMessagePayload { + typ: HandshakeType::Finished, + payload: HandshakePayload::Finished(verify_data_payload), + }), + }; + + transcript.add_message(&m); + common.send_msg(m, true); +} + +fn emit_end_of_early_data_tls13(transcript: &mut HandshakeHash, common: &mut CommonState) { + if common.is_quic() { + return; + } + + let m = Message { + version: ProtocolVersion::TLSv1_3, + payload: MessagePayload::handshake(HandshakeMessagePayload { + typ: HandshakeType::EndOfEarlyData, + payload: HandshakePayload::EndOfEarlyData, + }), + }; + + transcript.add_message(&m); + common.send_msg(m, true); +} + +struct ExpectFinished { + config: Arc, + server_name: ServerName, + randoms: ConnectionRandoms, + suite: &'static Tls13CipherSuite, + transcript: HandshakeHash, + key_schedule: KeyScheduleHandshake, + client_auth: Option, + cert_verified: verify::ServerCertVerified, + sig_verified: verify::HandshakeSignatureValid, +} + +impl State for ExpectFinished { + fn handle(self: Box, cx: &mut ClientContext<'_>, m: Message) -> hs::NextStateOrError { + let mut st = *self; + let finished = + require_handshake_msg!(m, HandshakeType::Finished, HandshakePayload::Finished)?; + + let handshake_hash = st.transcript.get_current_hash(); + let expect_verify_data = st + .key_schedule + .sign_server_finish(&handshake_hash); + + let fin = constant_time::verify_slices_are_equal(expect_verify_data.as_ref(), &finished.0) + .map_err(|_| { + cx.common + .send_fatal_alert(AlertDescription::DecryptError); + Error::DecryptError + }) + .map(|_| verify::FinishedMessageVerified::assertion())?; + + st.transcript.add_message(&m); + + let hash_after_handshake = st.transcript.get_current_hash(); + /* The EndOfEarlyData message to server is still encrypted with early data keys, + * but appears in the transcript after the server Finished. */ + if cx.common.early_traffic { + emit_end_of_early_data_tls13(&mut st.transcript, cx.common); + cx.common.early_traffic = false; + cx.data.early_data.finished(); + cx.common + .record_layer + .set_message_encrypter( + st.suite + .derive_encrypter(st.key_schedule.client_key()), + ); + } + + /* Send our authentication/finished messages. These are still encrypted + * with our handshake keys. */ + if let Some(client_auth) = st.client_auth { + match client_auth { + ClientAuthDetails::Empty { + auth_context_tls13: auth_context, + } => { + emit_certificate_tls13(&mut st.transcript, None, auth_context, cx.common); + } + ClientAuthDetails::Verify { + certkey, + signer, + auth_context_tls13: auth_context, + } => { + emit_certificate_tls13( + &mut st.transcript, + Some(&certkey), + auth_context, + cx.common, + ); + emit_certverify_tls13(&mut st.transcript, signer.as_ref(), cx.common)?; + } + } + } + + let (key_schedule_finished, client_key, server_key) = st + .key_schedule + .into_traffic_with_client_finished_pending( + hash_after_handshake, + &*st.config.key_log, + &st.randoms.client, + ); + let handshake_hash = st.transcript.get_current_hash(); + let (key_schedule_traffic, verify_data, _) = + key_schedule_finished.sign_client_finish(&handshake_hash); + emit_finished_tls13(&mut st.transcript, verify_data, cx.common); + + /* Now move to our application traffic keys. */ + cx.common.check_aligned_handshake()?; + + cx.common + .record_layer + .set_message_decrypter(st.suite.derive_decrypter(&server_key)); + + cx.common + .record_layer + .set_message_encrypter(st.suite.derive_encrypter(&client_key)); + + cx.common.start_traffic(); + + let st = ExpectTraffic { + session_storage: Arc::clone(&st.config.session_storage), + server_name: st.server_name, + suite: st.suite, + transcript: st.transcript, + key_schedule: key_schedule_traffic, + want_write_key_update: false, + _cert_verified: st.cert_verified, + _sig_verified: st.sig_verified, + _fin_verified: fin, + }; + + #[cfg(feature = "quic")] + { + if cx.common.protocol == Protocol::Quic { + cx.common.quic.traffic_secrets = + Some(quic::Secrets::new(client_key, server_key, st.suite, true)); + return Ok(Box::new(ExpectQuicTraffic(st))); + } + } + + Ok(Box::new(st)) + } +} + +// -- Traffic transit state (TLS1.3) -- +// In this state we can be sent tickets, key updates, +// and application data. +struct ExpectTraffic { + session_storage: Arc, + server_name: ServerName, + suite: &'static Tls13CipherSuite, + transcript: HandshakeHash, + key_schedule: KeyScheduleTraffic, + want_write_key_update: bool, + _cert_verified: verify::ServerCertVerified, + _sig_verified: verify::HandshakeSignatureValid, + _fin_verified: verify::FinishedMessageVerified, +} + +impl ExpectTraffic { + #[allow(clippy::unnecessary_wraps)] // returns Err for #[cfg(feature = "quic")] + fn handle_new_ticket_tls13( + &mut self, + cx: &mut ClientContext<'_>, + nst: &NewSessionTicketPayloadTLS13, + ) -> Result<(), Error> { + if nst.has_duplicate_extension() { + cx.common + .send_fatal_alert(AlertDescription::IllegalParameter); + return Err(Error::PeerMisbehavedError( + "peer sent duplicate NewSessionTicket extensions".into(), + )); + } + + let handshake_hash = self.transcript.get_current_hash(); + let secret = self + .key_schedule + .resumption_master_secret_and_derive_ticket_psk(&handshake_hash, &nst.nonce.0); + + let time_now = match TimeBase::now() { + Ok(t) => t, + #[allow(unused_variables)] + Err(e) => { + debug!("Session not saved: {}", e); + return Ok(()); + } + }; + + let value = persist::Tls13ClientSessionValue::new( + self.suite, + nst.ticket.0.clone(), + secret, + cx.common + .peer_certificates + .clone() + .unwrap_or_default(), + time_now, + nst.lifetime, + nst.age_add, + nst.get_max_early_data_size() + .unwrap_or_default(), + ); + + #[cfg(feature = "quic")] + if let Some(sz) = nst.get_max_early_data_size() { + if cx.common.protocol == Protocol::Quic && sz != 0 && sz != 0xffff_ffff { + return Err(Error::PeerMisbehavedError( + "invalid max_early_data_size".into(), + )); + } + } + + let key = persist::ClientSessionKey::session_for_server_name(&self.server_name); + #[allow(unused_mut)] + let mut ticket = value.get_encoding(); + + #[cfg(feature = "quic")] + if let (Protocol::Quic, Some(ref quic_params)) = + (cx.common.protocol, &cx.common.quic.params) + { + PayloadU16::encode_slice(quic_params, &mut ticket); + } + + let worked = self + .session_storage + .put(key.get_encoding(), ticket); + + if worked { + debug!("Ticket saved"); + } else { + debug!("Ticket not saved"); + } + Ok(()) + } + + fn handle_key_update( + &mut self, + common: &mut CommonState, + kur: &KeyUpdateRequest, + ) -> Result<(), Error> { + #[cfg(feature = "quic")] + { + if let Protocol::Quic = common.protocol { + common.send_fatal_alert(AlertDescription::UnexpectedMessage); + let msg = "KeyUpdate received in QUIC connection".to_string(); + warn!("{}", msg); + return Err(Error::PeerMisbehavedError(msg)); + } + } + + // Mustn't be interleaved with other handshake messages. + common.check_aligned_handshake()?; + + match kur { + KeyUpdateRequest::UpdateNotRequested => {} + KeyUpdateRequest::UpdateRequested => { + self.want_write_key_update = true; + } + _ => { + common.send_fatal_alert(AlertDescription::IllegalParameter); + return Err(Error::CorruptMessagePayload(ContentType::Handshake)); + } + } + + // Update our read-side keys. + let new_read_key = self + .key_schedule + .next_server_application_traffic_secret(); + common + .record_layer + .set_message_decrypter( + self.suite + .derive_decrypter(&new_read_key), + ); + + Ok(()) + } +} + +impl State for ExpectTraffic { + fn handle(mut self: Box, cx: &mut ClientContext<'_>, m: Message) -> hs::NextStateOrError { + match m.payload { + MessagePayload::ApplicationData(payload) => cx + .common + .take_received_plaintext(payload), + MessagePayload::Handshake { + parsed: + HandshakeMessagePayload { + payload: HandshakePayload::NewSessionTicketTLS13(ref new_ticket), + .. + }, + .. + } => self.handle_new_ticket_tls13(cx, new_ticket)?, + MessagePayload::Handshake { + parsed: + HandshakeMessagePayload { + payload: HandshakePayload::KeyUpdate(ref key_update), + .. + }, + .. + } => self.handle_key_update(cx.common, key_update)?, + payload => { + return Err(inappropriate_handshake_message( + &payload, + &[ContentType::ApplicationData, ContentType::Handshake], + &[HandshakeType::NewSessionTicket, HandshakeType::KeyUpdate], + )); + } + } + + Ok(self) + } + + fn export_keying_material( + &self, + output: &mut [u8], + label: &[u8], + context: Option<&[u8]>, + ) -> Result<(), Error> { + self.key_schedule + .export_keying_material(output, label, context) + } + + fn perhaps_write_key_update(&mut self, common: &mut CommonState) { + if self.want_write_key_update { + self.want_write_key_update = false; + common.send_msg_encrypt(Message::build_key_update_notify().into()); + + let write_key = self + .key_schedule + .next_client_application_traffic_secret(); + common + .record_layer + .set_message_encrypter(self.suite.derive_encrypter(&write_key)); + } + } + + #[cfg(feature = "secret_extraction")] + fn extract_secrets(&self) -> Result { + self.key_schedule + .extract_secrets(self.suite.common.aead_algorithm, Side::Client) + } +} + +#[cfg(feature = "quic")] +struct ExpectQuicTraffic(ExpectTraffic); + +#[cfg(feature = "quic")] +impl State for ExpectQuicTraffic { + fn handle(mut self: Box, cx: &mut ClientContext<'_>, m: Message) -> hs::NextStateOrError { + let nst = require_handshake_msg!( + m, + HandshakeType::NewSessionTicket, + HandshakePayload::NewSessionTicketTLS13 + )?; + self.0 + .handle_new_ticket_tls13(cx, nst)?; + Ok(self) + } + + fn export_keying_material( + &self, + output: &mut [u8], + label: &[u8], + context: Option<&[u8]>, + ) -> Result<(), Error> { + self.0 + .export_keying_material(output, label, context) + } +} diff --git a/third_party/rustls-fork-shadow-tls/src/conn.rs b/third_party/rustls-fork-shadow-tls/src/conn.rs new file mode 100644 index 0000000..f7d93a8 --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/src/conn.rs @@ -0,0 +1,1466 @@ +use crate::enums::ProtocolVersion; +use crate::error::Error; +use crate::key; +#[cfg(feature = "logging")] +use crate::log::{debug, error, trace, warn}; +use crate::msgs::alert::AlertMessagePayload; +use crate::msgs::base::Payload; +use crate::msgs::deframer::MessageDeframer; +use crate::msgs::enums::HandshakeType; +use crate::msgs::enums::{AlertDescription, AlertLevel, ContentType}; +use crate::msgs::fragmenter::MessageFragmenter; +use crate::msgs::handshake::Random; +use crate::msgs::hsjoiner::{HandshakeJoiner, JoinerError}; +use crate::msgs::message::{ + BorrowedPlainMessage, Message, MessagePayload, OpaqueMessage, PlainMessage, +}; +#[cfg(feature = "quic")] +use crate::quic; +use crate::record_layer; +use crate::suites::SupportedCipherSuite; +#[cfg(feature = "secret_extraction")] +use crate::suites::{ExtractedSecrets, PartiallyExtractedSecrets}; +#[cfg(feature = "tls12")] +use crate::tls12::ConnectionSecrets; +use crate::vecbuf::ChunkVecBuffer; +#[cfg(feature = "quic")] +use std::collections::VecDeque; + +use std::convert::TryFrom; +use std::fmt::Debug; +use std::io; +use std::mem; +use std::ops::{Deref, DerefMut}; + +/// A client or server connection. +#[derive(Debug)] +pub enum Connection { + /// A client connection + Client(crate::client::ClientConnection), + /// A server connection + Server(crate::server::ServerConnection), +} + +impl Connection { + /// Read TLS content from `rd`. + /// + /// See [`ConnectionCommon::read_tls()`] for more information. + pub fn read_tls(&mut self, rd: &mut dyn io::Read) -> Result { + match self { + Self::Client(conn) => conn.read_tls(rd), + Self::Server(conn) => conn.read_tls(rd), + } + } + + /// Returns an object that allows reading plaintext. + pub fn reader(&mut self) -> Reader { + match self { + Self::Client(conn) => conn.reader(), + Self::Server(conn) => conn.reader(), + } + } + + /// Returns an object that allows writing plaintext. + pub fn writer(&mut self) -> Writer { + match self { + Self::Client(conn) => Writer::new(&mut **conn), + Self::Server(conn) => Writer::new(&mut **conn), + } + } + + /// Processes any new packets read by a previous call to [`Connection::read_tls`]. + /// + /// See [`ConnectionCommon::process_new_packets()`] for more information. + pub fn process_new_packets(&mut self) -> Result { + match self { + Self::Client(conn) => conn.process_new_packets(), + Self::Server(conn) => conn.process_new_packets(), + } + } + + /// Derives key material from the agreed connection secrets. + /// + /// See [`ConnectionCommon::export_keying_material()`] for more information. + pub fn export_keying_material( + &self, + output: &mut [u8], + label: &[u8], + context: Option<&[u8]>, + ) -> Result<(), Error> { + match self { + Self::Client(conn) => conn.export_keying_material(output, label, context), + Self::Server(conn) => conn.export_keying_material(output, label, context), + } + } + + /// Extract secrets, to set up kTLS for example + #[cfg(feature = "secret_extraction")] + pub fn extract_secrets(self) -> Result { + match self { + Self::Client(conn) => conn.extract_secrets(), + Self::Server(conn) => conn.extract_secrets(), + } + } + + /// This function uses `io` to complete any outstanding IO for this connection. + /// + /// See [`ConnectionCommon::complete_io()`] for more information. + pub fn complete_io(&mut self, io: &mut T) -> Result<(usize, usize), io::Error> + where + Self: Sized, + T: io::Read + io::Write, + { + match self { + Self::Client(conn) => conn.complete_io(io), + Self::Server(conn) => conn.complete_io(io), + } + } +} + +#[cfg(feature = "quic")] +impl crate::quic::QuicExt for Connection { + fn quic_transport_parameters(&self) -> Option<&[u8]> { + match self { + Self::Client(conn) => conn.quic_transport_parameters(), + Self::Server(conn) => conn.quic_transport_parameters(), + } + } + + fn zero_rtt_keys(&self) -> Option { + match self { + Self::Client(conn) => conn.zero_rtt_keys(), + Self::Server(conn) => conn.zero_rtt_keys(), + } + } + + fn read_hs(&mut self, plaintext: &[u8]) -> Result<(), Error> { + match self { + Self::Client(conn) => conn.read_quic_hs(plaintext), + Self::Server(conn) => conn.read_quic_hs(plaintext), + } + } + + fn write_hs(&mut self, buf: &mut Vec) -> Option { + match self { + Self::Client(conn) => quic::write_hs(conn, buf), + Self::Server(conn) => quic::write_hs(conn, buf), + } + } + + fn alert(&self) -> Option { + match self { + Self::Client(conn) => conn.alert(), + Self::Server(conn) => conn.alert(), + } + } +} + +impl Deref for Connection { + type Target = CommonState; + + fn deref(&self) -> &Self::Target { + match self { + Self::Client(conn) => &conn.common_state, + Self::Server(conn) => &conn.common_state, + } + } +} + +impl DerefMut for Connection { + fn deref_mut(&mut self) -> &mut Self::Target { + match self { + Self::Client(conn) => &mut conn.common_state, + Self::Server(conn) => &mut conn.common_state, + } + } +} + +/// Values of this structure are returned from [`Connection::process_new_packets`] +/// and tell the caller the current I/O state of the TLS connection. +#[derive(Debug, Eq, PartialEq)] +pub struct IoState { + tls_bytes_to_write: usize, + plaintext_bytes_to_read: usize, + peer_has_closed: bool, +} + +impl IoState { + /// How many bytes could be written by [`CommonState::write_tls`] if called + /// right now. A non-zero value implies [`CommonState::wants_write`]. + pub fn tls_bytes_to_write(&self) -> usize { + self.tls_bytes_to_write + } + + /// How many plaintext bytes could be obtained via [`std::io::Read`] + /// without further I/O. + pub fn plaintext_bytes_to_read(&self) -> usize { + self.plaintext_bytes_to_read + } + + /// True if the peer has sent us a close_notify alert. This is + /// the TLS mechanism to securely half-close a TLS connection, + /// and signifies that the peer will not send any further data + /// on this connection. + /// + /// This is also signalled via returning `Ok(0)` from + /// [`std::io::Read`], after all the received bytes have been + /// retrieved. + pub fn peer_has_closed(&self) -> bool { + self.peer_has_closed + } +} + +/// A structure that implements [`std::io::Read`] for reading plaintext. +pub struct Reader<'a> { + received_plaintext: &'a mut ChunkVecBuffer, + peer_cleanly_closed: bool, + has_seen_eof: bool, +} + +impl<'a> io::Read for Reader<'a> { + /// Obtain plaintext data received from the peer over this TLS connection. + /// + /// If the peer closes the TLS session cleanly, this returns `Ok(0)` once all + /// the pending data has been read. No further data can be received on that + /// connection, so the underlying TCP connection should be half-closed too. + /// + /// If the peer closes the TLS session uncleanly (a TCP EOF without sending a + /// `close_notify` alert) this function returns `Err(ErrorKind::UnexpectedEof.into())` + /// once any pending data has been read. + /// + /// Note that support for `close_notify` varies in peer TLS libraries: many do not + /// support it and uncleanly close the TCP connection (this might be + /// vulnerable to truncation attacks depending on the application protocol). + /// This means applications using rustls must both handle EOF + /// from this function, *and* unexpected EOF of the underlying TCP connection. + /// + /// If there are no bytes to read, this returns `Err(ErrorKind::WouldBlock.into())`. + /// + /// You may learn the number of bytes available at any time by inspecting + /// the return of [`Connection::process_new_packets`]. + fn read(&mut self, buf: &mut [u8]) -> io::Result { + let len = self.received_plaintext.read(buf)?; + + if len == 0 && !buf.is_empty() { + // No bytes available: + match (self.peer_cleanly_closed, self.has_seen_eof) { + // cleanly closed; don't care about TCP EOF: express this as Ok(0) + (true, _) => {} + // unclean closure + (false, true) => return Err(io::ErrorKind::UnexpectedEof.into()), + // connection still going, but need more data: signal `WouldBlock` so that + // the caller knows this + (false, false) => return Err(io::ErrorKind::WouldBlock.into()), + } + } + + Ok(len) + } + + /// Obtain plaintext data received from the peer over this TLS connection. + /// + /// If the peer closes the TLS session, this returns `Ok(())` without filling + /// any more of the buffer once all the pending data has been read. No further + /// data can be received on that connection, so the underlying TCP connection + /// should be half-closed too. + /// + /// If the peer closes the TLS session uncleanly (a TCP EOF without sending a + /// `close_notify` alert) this function returns `Err(ErrorKind::UnexpectedEof.into())` + /// once any pending data has been read. + /// + /// Note that support for `close_notify` varies in peer TLS libraries: many do not + /// support it and uncleanly close the TCP connection (this might be + /// vulnerable to truncation attacks depending on the application protocol). + /// This means applications using rustls must both handle EOF + /// from this function, *and* unexpected EOF of the underlying TCP connection. + /// + /// If there are no bytes to read, this returns `Err(ErrorKind::WouldBlock.into())`. + /// + /// You may learn the number of bytes available at any time by inspecting + /// the return of [`Connection::process_new_packets`]. + #[cfg(read_buf)] + fn read_buf(&mut self, mut cursor: io::BorrowedCursor<'_>) -> io::Result<()> { + let before = cursor.written(); + self.received_plaintext + .read_buf(cursor.reborrow())?; + let len = cursor.written() - before; + + if len == 0 && cursor.capacity() > 0 { + // No bytes available: + match (self.peer_cleanly_closed, self.has_seen_eof) { + // cleanly closed; don't care about TCP EOF: express this as Ok(0) + (true, _) => {} + // unclean closure + (false, true) => return Err(io::ErrorKind::UnexpectedEof.into()), + // connection still going, but need more data: signal `WouldBlock` so that + // the caller knows this + (false, false) => return Err(io::ErrorKind::WouldBlock.into()), + } + } + + Ok(()) + } +} + +/// Internal trait implemented by the [`ServerConnection`]/[`ClientConnection`] +/// allowing them to be the subject of a [`Writer`]. +pub trait PlaintextSink { + fn write(&mut self, buf: &[u8]) -> io::Result; + fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result; + fn flush(&mut self) -> io::Result<()>; +} + +impl PlaintextSink for ConnectionCommon { + fn write(&mut self, buf: &[u8]) -> io::Result { + Ok(self.send_some_plaintext(buf)) + } + + fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result { + let mut sz = 0; + for buf in bufs { + sz += self.send_some_plaintext(buf); + } + Ok(sz) + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +/// A structure that implements [`std::io::Write`] for writing plaintext. +pub struct Writer<'a> { + sink: &'a mut dyn PlaintextSink, +} + +impl<'a> Writer<'a> { + /// Create a new Writer. + /// + /// This is not an external interface. Get one of these objects + /// from [`Connection::writer`]. + #[doc(hidden)] + pub fn new(sink: &'a mut dyn PlaintextSink) -> Writer<'a> { + Writer { sink } + } +} + +impl<'a> io::Write for Writer<'a> { + /// Send the plaintext `buf` to the peer, encrypting + /// and authenticating it. Once this function succeeds + /// you should call [`CommonState::write_tls`] which will output the + /// corresponding TLS records. + /// + /// This function buffers plaintext sent before the + /// TLS handshake completes, and sends it as soon + /// as it can. See [`CommonState::set_buffer_limit`] to control + /// the size of this buffer. + fn write(&mut self, buf: &[u8]) -> io::Result { + self.sink.write(buf) + } + + fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result { + self.sink.write_vectored(bufs) + } + + fn flush(&mut self) -> io::Result<()> { + self.sink.flush() + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub(crate) enum Protocol { + Tcp, + #[cfg(feature = "quic")] + Quic, +} + +#[derive(Debug)] +pub(crate) struct ConnectionRandoms { + pub(crate) client: [u8; 32], + pub(crate) server: [u8; 32], +} + +/// How many ChangeCipherSpec messages we accept and drop in TLS1.3 handshakes. +/// The spec says 1, but implementations (namely the boringssl test suite) get +/// this wrong. BoringSSL itself accepts up to 32. +static TLS13_MAX_DROPPED_CCS: u8 = 2u8; + +impl ConnectionRandoms { + pub(crate) fn new(client: Random, server: Random) -> Self { + Self { + client: client.0, + server: server.0, + } + } +} + +// --- Common (to client and server) connection functions --- + +fn is_valid_ccs(msg: &OpaqueMessage) -> bool { + // nb. this is prior to the record layer, so is unencrypted. see + // third paragraph of section 5 in RFC8446. + msg.typ == ContentType::ChangeCipherSpec && msg.payload.0 == [0x01] +} + +enum Limit { + Yes, + No, +} + +/// Interface shared by client and server connections. +pub struct ConnectionCommon { + state: Result>, Error>, + pub(crate) data: Data, + pub(crate) common_state: CommonState, + message_deframer: MessageDeframer, + handshake_joiner: HandshakeJoiner, +} + +impl ConnectionCommon { + pub(crate) fn new(state: Box>, data: Data, common_state: CommonState) -> Self { + Self { + state: Ok(state), + data, + common_state, + message_deframer: MessageDeframer::new(), + handshake_joiner: HandshakeJoiner::new(), + } + } + + /// Returns an object that allows reading plaintext. + pub fn reader(&mut self) -> Reader { + Reader { + received_plaintext: &mut self.common_state.received_plaintext, + /// Are we done? i.e., have we processed all received messages, and received a + /// close_notify to indicate that no new messages will arrive? + peer_cleanly_closed: self + .common_state + .has_received_close_notify + && !self.message_deframer.has_pending(), + has_seen_eof: self.common_state.has_seen_eof, + } + } + + /// Returns an object that allows writing plaintext. + pub fn writer(&mut self) -> Writer { + Writer::new(self) + } + + /// This function uses `io` to complete any outstanding IO for + /// this connection. + /// + /// This is a convenience function which solely uses other parts + /// of the public API. + /// + /// What this means depends on the connection state: + /// + /// - If the connection [`is_handshaking`], then IO is performed until + /// the handshake is complete. + /// - Otherwise, if [`wants_write`] is true, [`write_tls`] is invoked + /// until it is all written. + /// - Otherwise, if [`wants_read`] is true, [`read_tls`] is invoked + /// once. + /// + /// The return value is the number of bytes read from and written + /// to `io`, respectively. + /// + /// This function will block if `io` blocks. + /// + /// Errors from TLS record handling (i.e., from [`process_new_packets`]) + /// are wrapped in an `io::ErrorKind::InvalidData`-kind error. + /// + /// [`is_handshaking`]: CommonState::is_handshaking + /// [`wants_read`]: CommonState::wants_read + /// [`wants_write`]: CommonState::wants_write + /// [`write_tls`]: CommonState::write_tls + /// [`read_tls`]: ConnectionCommon::read_tls + /// [`process_new_packets`]: ConnectionCommon::process_new_packets + pub fn complete_io(&mut self, io: &mut T) -> Result<(usize, usize), io::Error> + where + Self: Sized, + T: io::Read + io::Write, + { + let until_handshaked = self.is_handshaking(); + let mut eof = false; + let mut wrlen = 0; + let mut rdlen = 0; + + loop { + while self.wants_write() { + wrlen += self.write_tls(io)?; + } + + if !until_handshaked && wrlen > 0 { + return Ok((rdlen, wrlen)); + } + + while !eof && self.wants_read() { + let read_size = match self.read_tls(io) { + Ok(0) => { + eof = true; + Some(0) + } + Ok(n) => { + rdlen += n; + Some(n) + } + Err(ref err) if err.kind() == io::ErrorKind::Interrupted => None, // nothing to do + Err(err) => return Err(err), + }; + if read_size.is_some() { + break; + } + } + + match self.process_new_packets() { + Ok(_) => {} + Err(e) => { + // In case we have an alert to send describing this error, + // try a last-gasp write -- but don't predate the primary + // error. + let _ignored = self.write_tls(io); + + return Err(io::Error::new(io::ErrorKind::InvalidData, e)); + } + }; + + match (eof, until_handshaked, self.is_handshaking()) { + (_, true, false) => return Ok((rdlen, wrlen)), + (_, false, _) => return Ok((rdlen, wrlen)), + (true, true, true) => return Err(io::Error::from(io::ErrorKind::UnexpectedEof)), + (..) => {} + } + } + } + + /// Extract the first handshake message. + /// + /// This is a shortcut to the `process_new_packets()` -> `process_msg()` -> + /// `process_handshake_messages()` path, specialized for the first handshake message. + pub(crate) fn first_handshake_message(&mut self) -> Result, Error> { + let msg = match self.message_deframer.pop()? { + Some(msg) => msg, + None => return Ok(None), + }; + + let msg = msg.into_plain_message(); + self.handshake_joiner + .push(msg) + .and_then(|aligned| { + self.common_state.aligned_handshake = aligned; + self.handshake_joiner.pop() + }) + .map_err(|_| { + self.common_state + .send_fatal_alert(AlertDescription::DecodeError); + Error::CorruptMessagePayload(ContentType::Handshake) + }) + } + + pub(crate) fn replace_state(&mut self, new: Box>) { + self.state = Ok(new); + } + + fn process_msg( + &mut self, + msg: OpaqueMessage, + state: Box>, + ) -> Result>, Error> { + // Drop CCS messages during handshake in TLS1.3 + if msg.typ == ContentType::ChangeCipherSpec + && !self + .common_state + .may_receive_application_data + && self.common_state.is_tls13() + { + if !is_valid_ccs(&msg) + || self.common_state.received_middlebox_ccs > TLS13_MAX_DROPPED_CCS + { + // "An implementation which receives any other change_cipher_spec value or + // which receives a protected change_cipher_spec record MUST abort the + // handshake with an "unexpected_message" alert." + self.common_state + .send_fatal_alert(AlertDescription::UnexpectedMessage); + return Err(Error::PeerMisbehavedError( + "illegal middlebox CCS received".into(), + )); + } else { + self.common_state.received_middlebox_ccs += 1; + trace!("Dropping CCS"); + return Ok(state); + } + } + + // Decrypt if demanded by current state. + let msg = match self + .common_state + .record_layer + .is_decrypting() + { + true => match self.common_state.decrypt_incoming(msg) { + Ok(None) => { + // message dropped + return Ok(state); + } + Err(e) => { + return Err(e); + } + Ok(Some(msg)) => msg, + }, + false => msg.into_plain_message(), + }; + + // For handshake messages, we need to join them before parsing and processing. + let msg = match self.handshake_joiner.push(msg) { + // Handshake message, we handle these in another method. + Ok(aligned) => { + self.common_state.aligned_handshake = aligned; + + // First decryptable handshake message concludes trial decryption + self.common_state + .record_layer + .finish_trial_decryption(); + + return self.process_new_handshake_messages(state); + } + // Not a handshake message, continue to handle it here. + Err(JoinerError::Unwanted(msg)) => msg, + // Decoding the handshake message failed, yield an error. + Err(JoinerError::Decode) => { + self.common_state + .send_fatal_alert(AlertDescription::DecodeError); + return Err(Error::CorruptMessagePayload(ContentType::Handshake)); + } + }; + + // Now we can fully parse the message payload. + let msg = Message::try_from(msg)?; + + // For alerts, we have separate logic. + if let MessagePayload::Alert(alert) = &msg.payload { + self.common_state.process_alert(alert)?; + return Ok(state); + } + + self.common_state + .process_main_protocol(msg, state, &mut self.data) + } + + /// Processes any new packets read by a previous call to + /// [`Connection::read_tls`]. + /// + /// Errors from this function relate to TLS protocol errors, and + /// are fatal to the connection. Future calls after an error will do + /// no new work and will return the same error. After an error is + /// received from [`process_new_packets`], you should not call [`read_tls`] + /// any more (it will fill up buffers to no purpose). However, you + /// may call the other methods on the connection, including `write`, + /// `send_close_notify`, and `write_tls`. Most likely you will want to + /// call `write_tls` to send any alerts queued by the error and then + /// close the underlying connection. + /// + /// Success from this function comes with some sundry state data + /// about the connection. + /// + /// [`read_tls`]: Connection::read_tls + /// [`process_new_packets`]: Connection::process_new_packets + pub fn process_new_packets(&mut self) -> Result { + let mut state = match mem::replace(&mut self.state, Err(Error::HandshakeNotComplete)) { + Ok(state) => state, + Err(e) => { + self.state = Err(e.clone()); + return Err(e); + } + }; + + while let Some(msg) = self.message_deframer.pop()? { + match self.process_msg(msg, state) { + Ok(new) => state = new, + Err(e) => { + self.state = Err(e.clone()); + return Err(e); + } + } + } + + self.state = Ok(state); + Ok(self.common_state.current_io_state()) + } + + fn process_new_handshake_messages( + &mut self, + mut state: Box>, + ) -> Result>, Error> { + loop { + match self.handshake_joiner.pop() { + Ok(Some(msg)) => { + state = self + .common_state + .process_main_protocol(msg, state, &mut self.data)?; + } + Ok(None) => return Ok(state), + Err(_) => { + #[cfg(feature = "quic")] + if self.common_state.is_quic() { + self.common_state.quic.alert = Some(AlertDescription::DecodeError); + } + + if !self.common_state.is_quic() { + self.common_state + .send_fatal_alert(AlertDescription::DecodeError); + } + + return Err(Error::CorruptMessagePayload(ContentType::Handshake)); + } + } + } + } + + pub(crate) fn send_some_plaintext(&mut self, buf: &[u8]) -> usize { + if let Ok(st) = &mut self.state { + st.perhaps_write_key_update(&mut self.common_state); + } + self.common_state + .send_some_plaintext(buf) + } + + /// Read TLS content from `rd` into the internal buffer. + /// + /// Due to the internal buffering, `rd` can supply TLS messages in arbitrary-sized chunks (like + /// a socket or pipe might). + /// + /// You should call [`process_new_packets()`] each time a call to this function succeeds in order + /// to empty the incoming TLS data buffer. + /// + /// This function returns `Ok(0)` when the underlying `rd` does so. This typically happens when + /// a socket is cleanly closed, or a file is at EOF. Errors may result from the IO done through + /// `rd`; additionally, errors of `ErrorKind::Other` are emitted to signal backpressure: + /// + /// * In order to empty the incoming TLS data buffer, you should call [`process_new_packets()`] + /// each time a call to this function succeeds. + /// * In order to empty the incoming plaintext data buffer, you should empty it through + /// the [`reader()`] after the call to [`process_new_packets()`]. + /// + /// [`process_new_packets()`]: ConnectionCommon::process_new_packets + /// [`reader()`]: ConnectionCommon::reader + pub fn read_tls(&mut self, rd: &mut dyn io::Read) -> Result { + if self.received_plaintext.is_full() { + return Err(io::Error::new( + io::ErrorKind::Other, + "received plaintext buffer full", + )); + } + + let res = self.message_deframer.read(rd); + if let Ok(0) = res { + self.common_state.has_seen_eof = true; + } + res + } + + /// Derives key material from the agreed connection secrets. + /// + /// This function fills in `output` with `output.len()` bytes of key + /// material derived from the master session secret using `label` + /// and `context` for diversification. + /// + /// See RFC5705 for more details on what this does and is for. + /// + /// For TLS1.3 connections, this function does not use the + /// "early" exporter at any point. + /// + /// This function fails if called prior to the handshake completing; + /// check with [`CommonState::is_handshaking`] first. + pub fn export_keying_material( + &self, + output: &mut [u8], + label: &[u8], + context: Option<&[u8]>, + ) -> Result<(), Error> { + match self.state.as_ref() { + Ok(st) => st.export_keying_material(output, label, context), + Err(e) => Err(e.clone()), + } + } + + /// Extract secrets, so they can be used when configuring kTLS, for example. + #[cfg(feature = "secret_extraction")] + pub fn extract_secrets(self) -> Result { + if !self.enable_secret_extraction { + return Err(Error::General("Secret extraction is disabled".into())); + } + + let st = self.state?; + + let record_layer = self.common_state.record_layer; + let PartiallyExtractedSecrets { tx, rx } = st.extract_secrets()?; + Ok(ExtractedSecrets { + tx: (record_layer.write_seq(), tx), + rx: (record_layer.read_seq(), rx), + }) + } +} + +#[cfg(feature = "quic")] +impl ConnectionCommon { + pub(crate) fn read_quic_hs(&mut self, plaintext: &[u8]) -> Result<(), Error> { + let state = match mem::replace(&mut self.state, Err(Error::HandshakeNotComplete)) { + Ok(state) => state, + Err(e) => { + self.state = Err(e.clone()); + return Err(e); + } + }; + + let msg = PlainMessage { + typ: ContentType::Handshake, + version: ProtocolVersion::TLSv1_3, + payload: Payload::new(plaintext.to_vec()), + }; + + if self.handshake_joiner.push(msg).is_err() { + self.common_state.quic.alert = Some(AlertDescription::DecodeError); + return Err(Error::CorruptMessage); + } + + self.process_new_handshake_messages(state) + .map(|state| self.state = Ok(state)) + } +} + +impl Deref for ConnectionCommon { + type Target = CommonState; + + fn deref(&self) -> &Self::Target { + &self.common_state + } +} + +impl DerefMut for ConnectionCommon { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.common_state + } +} + +/// Connection state common to both client and server connections. +pub struct CommonState { + pub(crate) negotiated_version: Option, + pub(crate) side: Side, + pub(crate) record_layer: record_layer::RecordLayer, + pub(crate) suite: Option, + pub(crate) alpn_protocol: Option>, + aligned_handshake: bool, + pub(crate) may_send_application_data: bool, + pub(crate) may_receive_application_data: bool, + pub(crate) early_traffic: bool, + sent_fatal_alert: bool, + /// If the peer has signaled end of stream. + has_received_close_notify: bool, + has_seen_eof: bool, + received_middlebox_ccs: u8, + pub(crate) peer_certificates: Option>, + message_fragmenter: MessageFragmenter, + received_plaintext: ChunkVecBuffer, + sendable_plaintext: ChunkVecBuffer, + pub(crate) sendable_tls: ChunkVecBuffer, + #[allow(dead_code)] // only read for QUIC + /// Protocol whose key schedule should be used. Unused for TLS < 1.3. + pub(crate) protocol: Protocol, + #[cfg(feature = "quic")] + pub(crate) quic: Quic, + #[cfg(feature = "secret_extraction")] + pub(crate) enable_secret_extraction: bool, +} + +impl CommonState { + pub(crate) fn new(side: Side) -> Self { + Self { + negotiated_version: None, + side, + record_layer: record_layer::RecordLayer::new(), + suite: None, + alpn_protocol: None, + aligned_handshake: true, + may_send_application_data: false, + may_receive_application_data: false, + early_traffic: false, + sent_fatal_alert: false, + has_received_close_notify: false, + has_seen_eof: false, + received_middlebox_ccs: 0, + peer_certificates: None, + message_fragmenter: MessageFragmenter::default(), + received_plaintext: ChunkVecBuffer::new(Some(DEFAULT_RECEIVED_PLAINTEXT_LIMIT)), + sendable_plaintext: ChunkVecBuffer::new(Some(DEFAULT_BUFFER_LIMIT)), + sendable_tls: ChunkVecBuffer::new(Some(DEFAULT_BUFFER_LIMIT)), + + protocol: Protocol::Tcp, + #[cfg(feature = "quic")] + quic: Quic::new(), + #[cfg(feature = "secret_extraction")] + enable_secret_extraction: false, + } + } + + /// Returns true if the caller should call [`CommonState::write_tls`] as soon + /// as possible. + pub fn wants_write(&self) -> bool { + !self.sendable_tls.is_empty() + } + + /// Returns true if the connection is currently performing the TLS handshake. + /// + /// During this time plaintext written to the connection is buffered in memory. After + /// [`Connection::process_new_packets`] has been called, this might start to return `false` + /// while the final handshake packets still need to be extracted from the connection's buffers. + pub fn is_handshaking(&self) -> bool { + !(self.may_send_application_data && self.may_receive_application_data) + } + + /// Retrieves the certificate chain used by the peer to authenticate. + /// + /// The order of the certificate chain is as it appears in the TLS + /// protocol: the first certificate relates to the peer, the + /// second certifies the first, the third certifies the second, and + /// so on. + /// + /// This is made available for both full and resumed handshakes. + /// + /// For clients, this is the certificate chain of the server. + /// + /// For servers, this is the certificate chain of the client, + /// if client authentication was completed. + /// + /// The return value is None until this value is available. + pub fn peer_certificates(&self) -> Option<&[key::Certificate]> { + self.peer_certificates.as_deref() + } + + /// Retrieves the protocol agreed with the peer via ALPN. + /// + /// A return value of `None` after handshake completion + /// means no protocol was agreed (because no protocols + /// were offered or accepted by the peer). + pub fn alpn_protocol(&self) -> Option<&[u8]> { + self.get_alpn_protocol() + } + + /// Retrieves the ciphersuite agreed with the peer. + /// + /// This returns None until the ciphersuite is agreed. + pub fn negotiated_cipher_suite(&self) -> Option { + self.suite + } + + /// Retrieves the protocol version agreed with the peer. + /// + /// This returns `None` until the version is agreed. + pub fn protocol_version(&self) -> Option { + self.negotiated_version + } + + pub(crate) fn is_tls13(&self) -> bool { + matches!(self.negotiated_version, Some(ProtocolVersion::TLSv1_3)) + } + + fn process_main_protocol( + &mut self, + msg: Message, + mut state: Box>, + data: &mut Data, + ) -> Result>, Error> { + // For TLS1.2, outside of the handshake, send rejection alerts for + // renegotiation requests. These can occur any time. + if self.may_receive_application_data && !self.is_tls13() { + let reject_ty = match self.side { + Side::Client => HandshakeType::HelloRequest, + Side::Server => HandshakeType::ClientHello, + }; + if msg.is_handshake_type(reject_ty) { + self.send_warning_alert(AlertDescription::NoRenegotiation); + return Ok(state); + } + } + + let mut cx = Context { common: self, data }; + match state.handle(&mut cx, msg) { + Ok(next) => { + state = next; + Ok(state) + } + Err(e @ Error::InappropriateMessage { .. }) + | Err(e @ Error::InappropriateHandshakeMessage { .. }) => { + self.send_fatal_alert(AlertDescription::UnexpectedMessage); + Err(e) + } + Err(e) => Err(e), + } + } + + /// Send plaintext application data, fragmenting and + /// encrypting it as it goes out. + /// + /// If internal buffers are too small, this function will not accept + /// all the data. + pub(crate) fn send_some_plaintext(&mut self, data: &[u8]) -> usize { + self.send_plain(data, Limit::Yes) + } + + pub(crate) fn send_early_plaintext(&mut self, data: &[u8]) -> usize { + debug_assert!(self.early_traffic); + debug_assert!(self.record_layer.is_encrypting()); + + if data.is_empty() { + // Don't send empty fragments. + return 0; + } + + self.send_appdata_encrypt(data, Limit::Yes) + } + + // Changing the keys must not span any fragmented handshake + // messages. Otherwise the defragmented messages will have + // been protected with two different record layer protections, + // which is illegal. Not mentioned in RFC. + pub(crate) fn check_aligned_handshake(&mut self) -> Result<(), Error> { + if !self.aligned_handshake { + self.send_fatal_alert(AlertDescription::UnexpectedMessage); + Err(Error::PeerMisbehavedError( + "key epoch or handshake flight with pending fragment".to_string(), + )) + } else { + Ok(()) + } + } + + pub(crate) fn illegal_param(&mut self, why: &str) -> Error { + self.send_fatal_alert(AlertDescription::IllegalParameter); + Error::PeerMisbehavedError(why.to_string()) + } + + pub(crate) fn decrypt_incoming( + &mut self, + encr: OpaqueMessage, + ) -> Result, Error> { + if self + .record_layer + .wants_close_before_decrypt() + { + self.send_close_notify(); + } + + let encrypted_len = encr.payload.0.len(); + let plain = self.record_layer.decrypt_incoming(encr); + + match plain { + Err(Error::PeerSentOversizedRecord) => { + self.send_fatal_alert(AlertDescription::RecordOverflow); + Err(Error::PeerSentOversizedRecord) + } + Err(Error::DecryptError) + if self + .record_layer + .doing_trial_decryption(encrypted_len) => + { + trace!("Dropping undecryptable message after aborted early_data"); + Ok(None) + } + Err(Error::DecryptError) => { + self.send_fatal_alert(AlertDescription::BadRecordMac); + Err(Error::DecryptError) + } + Err(e) => Err(e), + Ok(plain) => Ok(Some(plain)), + } + } + + /// Fragment `m`, encrypt the fragments, and then queue + /// the encrypted fragments for sending. + pub(crate) fn send_msg_encrypt(&mut self, m: PlainMessage) { + let iter = self + .message_fragmenter + .fragment_message(&m); + for m in iter { + self.send_single_fragment(m); + } + } + + /// Like send_msg_encrypt, but operate on an appdata directly. + fn send_appdata_encrypt(&mut self, payload: &[u8], limit: Limit) -> usize { + // Here, the limit on sendable_tls applies to encrypted data, + // but we're respecting it for plaintext data -- so we'll + // be out by whatever the cipher+record overhead is. That's a + // constant and predictable amount, so it's not a terrible issue. + let len = match limit { + Limit::Yes => self + .sendable_tls + .apply_limit(payload.len()), + Limit::No => payload.len(), + }; + + let iter = self.message_fragmenter.fragment_slice( + ContentType::ApplicationData, + ProtocolVersion::TLSv1_2, + &payload[..len], + ); + for m in iter { + self.send_single_fragment(m); + } + + len + } + + fn send_single_fragment(&mut self, m: BorrowedPlainMessage) { + // Close connection once we start to run out of + // sequence space. + if self + .record_layer + .wants_close_before_encrypt() + { + self.send_close_notify(); + } + + // Refuse to wrap counter at all costs. This + // is basically untestable unfortunately. + if self.record_layer.encrypt_exhausted() { + return; + } + + let em = self.record_layer.encrypt_outgoing(m); + self.queue_tls_message(em); + } + + /// Writes TLS messages to `wr`. + /// + /// On success, this function returns `Ok(n)` where `n` is a number of bytes written to `wr` + /// (after encoding and encryption). + /// + /// After this function returns, the connection buffer may not yet be fully flushed. The + /// [`CommonState::wants_write`] function can be used to check if the output buffer is empty. + pub fn write_tls(&mut self, wr: &mut dyn io::Write) -> Result { + self.sendable_tls.write_to(wr) + } + + /// Encrypt and send some plaintext `data`. `limit` controls + /// whether the per-connection buffer limits apply. + /// + /// Returns the number of bytes written from `data`: this might + /// be less than `data.len()` if buffer limits were exceeded. + fn send_plain(&mut self, data: &[u8], limit: Limit) -> usize { + if !self.may_send_application_data { + // If we haven't completed handshaking, buffer + // plaintext to send once we do. + let len = match limit { + Limit::Yes => self + .sendable_plaintext + .append_limited_copy(data), + Limit::No => self + .sendable_plaintext + .append(data.to_vec()), + }; + return len; + } + + debug_assert!(self.record_layer.is_encrypting()); + + if data.is_empty() { + // Don't send empty fragments. + return 0; + } + + self.send_appdata_encrypt(data, limit) + } + + pub(crate) fn start_outgoing_traffic(&mut self) { + self.may_send_application_data = true; + self.flush_plaintext(); + } + + pub(crate) fn start_traffic(&mut self) { + self.may_receive_application_data = true; + self.start_outgoing_traffic(); + } + + /// Sets a limit on the internal buffers used to buffer + /// unsent plaintext (prior to completing the TLS handshake) + /// and unsent TLS records. This limit acts only on application + /// data written through [`Connection::writer`]. + /// + /// By default the limit is 64KB. The limit can be set + /// at any time, even if the current buffer use is higher. + /// + /// [`None`] means no limit applies, and will mean that written + /// data is buffered without bound -- it is up to the application + /// to appropriately schedule its plaintext and TLS writes to bound + /// memory usage. + /// + /// For illustration: `Some(1)` means a limit of one byte applies: + /// [`Connection::writer`] will accept only one byte, encrypt it and + /// add a TLS header. Once this is sent via [`CommonState::write_tls`], + /// another byte may be sent. + /// + /// # Internal write-direction buffering + /// rustls has two buffers whose size are bounded by this setting: + /// + /// ## Buffering of unsent plaintext data prior to handshake completion + /// + /// Calls to [`Connection::writer`] before or during the handshake + /// are buffered (up to the limit specified here). Once the + /// handshake completes this data is encrypted and the resulting + /// TLS records are added to the outgoing buffer. + /// + /// ## Buffering of outgoing TLS records + /// + /// This buffer is used to store TLS records that rustls needs to + /// send to the peer. It is used in these two circumstances: + /// + /// - by [`Connection::process_new_packets`] when a handshake or alert + /// TLS record needs to be sent. + /// - by [`Connection::writer`] post-handshake: the plaintext is + /// encrypted and the resulting TLS record is buffered. + /// + /// This buffer is emptied by [`CommonState::write_tls`]. + pub fn set_buffer_limit(&mut self, limit: Option) { + self.sendable_plaintext.set_limit(limit); + self.sendable_tls.set_limit(limit); + } + + /// Send any buffered plaintext. Plaintext is buffered if + /// written during handshake. + fn flush_plaintext(&mut self) { + if !self.may_send_application_data { + return; + } + + while let Some(buf) = self.sendable_plaintext.pop() { + self.send_plain(&buf, Limit::No); + } + } + + // Put m into sendable_tls for writing. + fn queue_tls_message(&mut self, m: OpaqueMessage) { + self.sendable_tls.append(m.encode()); + } + + /// Send a raw TLS message, fragmenting it if needed. + pub(crate) fn send_msg(&mut self, m: Message, must_encrypt: bool) { + #[cfg(feature = "quic")] + { + if let Protocol::Quic = self.protocol { + if let MessagePayload::Alert(alert) = m.payload { + self.quic.alert = Some(alert.description); + } else { + debug_assert!( + matches!(m.payload, MessagePayload::Handshake { .. }), + "QUIC uses TLS for the cryptographic handshake only" + ); + let mut bytes = Vec::new(); + m.payload.encode(&mut bytes); + self.quic + .hs_queue + .push_back((must_encrypt, bytes)); + } + return; + } + } + if !must_encrypt { + let msg = &m.into(); + let iter = self + .message_fragmenter + .fragment_message(msg); + for m in iter { + self.queue_tls_message(m.to_unencrypted_opaque()); + } + } else { + self.send_msg_encrypt(m.into()); + } + } + + pub(crate) fn take_received_plaintext(&mut self, bytes: Payload) { + self.received_plaintext.append(bytes.0); + } + + #[cfg(feature = "tls12")] + pub(crate) fn start_encryption_tls12(&mut self, secrets: &ConnectionSecrets, side: Side) { + let (dec, enc) = secrets.make_cipher_pair(side); + self.record_layer + .prepare_message_encrypter(enc); + self.record_layer + .prepare_message_decrypter(dec); + } + + #[cfg(feature = "quic")] + pub(crate) fn missing_extension(&mut self, why: &str) -> Error { + self.send_fatal_alert(AlertDescription::MissingExtension); + Error::PeerMisbehavedError(why.to_string()) + } + + fn send_warning_alert(&mut self, desc: AlertDescription) { + warn!("Sending warning alert {:?}", desc); + self.send_warning_alert_no_log(desc); + } + + fn process_alert(&mut self, alert: &AlertMessagePayload) -> Result<(), Error> { + // Reject unknown AlertLevels. + if let AlertLevel::Unknown(_) = alert.level { + self.send_fatal_alert(AlertDescription::IllegalParameter); + } + + // If we get a CloseNotify, make a note to declare EOF to our + // caller. + if alert.description == AlertDescription::CloseNotify { + self.has_received_close_notify = true; + return Ok(()); + } + + // Warnings are nonfatal for TLS1.2, but outlawed in TLS1.3 + // (except, for no good reason, user_cancelled). + if alert.level == AlertLevel::Warning { + if self.is_tls13() && alert.description != AlertDescription::UserCanceled { + self.send_fatal_alert(AlertDescription::DecodeError); + } else { + warn!("TLS alert warning received: {:#?}", alert); + return Ok(()); + } + } + + error!("TLS alert received: {:#?}", alert); + Err(Error::AlertReceived(alert.description)) + } + + pub(crate) fn send_fatal_alert(&mut self, desc: AlertDescription) { + warn!("Sending fatal alert {:?}", desc); + debug_assert!(!self.sent_fatal_alert); + let m = Message::build_alert(AlertLevel::Fatal, desc); + self.send_msg(m, self.record_layer.is_encrypting()); + self.sent_fatal_alert = true; + } + + /// Queues a close_notify warning alert to be sent in the next + /// [`CommonState::write_tls`] call. This informs the peer that the + /// connection is being closed. + pub fn send_close_notify(&mut self) { + debug!("Sending warning alert {:?}", AlertDescription::CloseNotify); + self.send_warning_alert_no_log(AlertDescription::CloseNotify); + } + + fn send_warning_alert_no_log(&mut self, desc: AlertDescription) { + let m = Message::build_alert(AlertLevel::Warning, desc); + self.send_msg(m, self.record_layer.is_encrypting()); + } + + pub(crate) fn set_max_fragment_size(&mut self, new: Option) -> Result<(), Error> { + self.message_fragmenter + .set_max_fragment_size(new) + } + + pub(crate) fn get_alpn_protocol(&self) -> Option<&[u8]> { + self.alpn_protocol + .as_ref() + .map(AsRef::as_ref) + } + + /// Returns true if the caller should call [`Connection::read_tls`] as soon + /// as possible. + /// + /// If there is pending plaintext data to read with [`Connection::reader`], + /// this returns false. If your application respects this mechanism, + /// only one full TLS message will be buffered by rustls. + pub fn wants_read(&self) -> bool { + // We want to read more data all the time, except when we have unprocessed plaintext. + // This provides back-pressure to the TCP buffers. We also don't want to read more after + // the peer has sent us a close notification. + // + // In the handshake case we don't have readable plaintext before the handshake has + // completed, but also don't want to read if we still have sendable tls. + self.received_plaintext.is_empty() + && !self.has_received_close_notify + && (self.may_send_application_data || self.sendable_tls.is_empty()) + } + + fn current_io_state(&self) -> IoState { + IoState { + tls_bytes_to_write: self.sendable_tls.len(), + plaintext_bytes_to_read: self.received_plaintext.len(), + peer_has_closed: self.has_received_close_notify, + } + } + + pub(crate) fn is_quic(&self) -> bool { + #[cfg(feature = "quic")] + { + self.protocol == Protocol::Quic + } + #[cfg(not(feature = "quic"))] + false + } +} + +pub(crate) trait State: Send + Sync { + fn handle( + self: Box, + cx: &mut Context<'_, Data>, + message: Message, + ) -> Result>, Error>; + + fn export_keying_material( + &self, + _output: &mut [u8], + _label: &[u8], + _context: Option<&[u8]>, + ) -> Result<(), Error> { + Err(Error::HandshakeNotComplete) + } + + #[cfg(feature = "secret_extraction")] + fn extract_secrets(&self) -> Result { + Err(Error::HandshakeNotComplete) + } + + fn perhaps_write_key_update(&mut self, _cx: &mut CommonState) {} +} + +pub(crate) struct Context<'a, Data> { + pub(crate) common: &'a mut CommonState, + pub(crate) data: &'a mut Data, +} + +#[cfg(feature = "quic")] +pub(crate) struct Quic { + /// QUIC transport parameters received from the peer during the handshake + pub(crate) params: Option>, + pub(crate) alert: Option, + pub(crate) hs_queue: VecDeque<(bool, Vec)>, + pub(crate) early_secret: Option, + pub(crate) hs_secrets: Option, + pub(crate) traffic_secrets: Option, + /// Whether keys derived from traffic_secrets have been passed to the QUIC implementation + pub(crate) returned_traffic_keys: bool, +} + +#[cfg(feature = "quic")] +impl Quic { + fn new() -> Self { + Self { + params: None, + alert: None, + hs_queue: VecDeque::new(), + early_secret: None, + hs_secrets: None, + traffic_secrets: None, + returned_traffic_keys: false, + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub(crate) enum Side { + Client, + Server, +} + +/// Data specific to the peer's side (client or server). +pub trait SideData {} + +const DEFAULT_RECEIVED_PLAINTEXT_LIMIT: usize = 16 * 1024; +const DEFAULT_BUFFER_LIMIT: usize = 64 * 1024; diff --git a/third_party/rustls-fork-shadow-tls/src/enums.rs b/third_party/rustls-fork-shadow-tls/src/enums.rs new file mode 100644 index 0000000..20c39ee --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/src/enums.rs @@ -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 + } +} diff --git a/third_party/rustls-fork-shadow-tls/src/error.rs b/third_party/rustls-fork-shadow-tls/src/error.rs new file mode 100644 index 0000000..32d4903 --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/src/error.rs @@ -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, + /// 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, + /// 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(items: &[T]) -> String { + items + .iter() + .map(|x| format!("{:?}", x)) + .collect::>() + .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::(expect_types) + ), + Self::InappropriateHandshakeMessage { + ref expect_types, + ref got_type, + } => write!( + f, + "received unexpected handshake message: got {:?} when expecting {}", + got_type, + join::(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 for Error { + #[inline] + fn from(_: SystemTimeError) -> Self { + Self::FailedToGetCurrentTime + } +} + +impl StdError for Error {} + +impl From 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); + } +} diff --git a/third_party/rustls-fork-shadow-tls/src/hash_hs.rs b/third_party/rustls-fork-shadow-tls/src/hash_hs.rs new file mode 100644 index 0000000..3dd66b1 --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/src/hash_hs.rs @@ -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, + 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>, +} + +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> { + 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); + } +} diff --git a/third_party/rustls-fork-shadow-tls/src/key.rs b/third_party/rustls-fork-shadow-tls/src/key.rs new file mode 100644 index 0000000..dbd4cc5 --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/src/key.rs @@ -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); + +/// 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); + +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())) + ); + } +} diff --git a/third_party/rustls-fork-shadow-tls/src/key_log.rs b/third_party/rustls-fork-shadow-tls/src/key_log.rs new file mode 100644 index 0000000..1b6b3fe --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/src/key_log.rs @@ -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: + /// + 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 + } +} diff --git a/third_party/rustls-fork-shadow-tls/src/key_log_file.rs b/third_party/rustls-fork-shadow-tls/src/key_log_file.rs new file mode 100644 index 0000000..7907113 --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/src/key_log_file.rs @@ -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, + buf: Vec, +} + +impl KeyLogFileInner { + fn new(var: Result) -> 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); + +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()); + } +} diff --git a/third_party/rustls-fork-shadow-tls/src/kx.rs b/third_party/rustls-fork-shadow-tls/src/kx.rs new file mode 100644 index 0000000..4736570 --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/src/kx.rs @@ -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 { + 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( + self, + peer: &[u8], + f: impl FnOnce(&[u8]) -> Result, + ) -> Result { + 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]; diff --git a/third_party/rustls-fork-shadow-tls/src/lib.rs b/third_party/rustls-fork-shadow-tls/src/lib.rs new file mode 100644 index 0000000..27db2b2 --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/src/lib.rs @@ -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 { panic!() } +//! # } +//! # impl Write for Socket { +//! # fn write(&mut self, buf: &[u8]) -> Result { 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`, 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; +*/ diff --git a/third_party/rustls-fork-shadow-tls/src/limited_cache.rs b/third_party/rustls-fork-shadow-tls/src/limited_cache.rs new file mode 100644 index 0000000..6994d88 --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/src/limited_cache.rs @@ -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 { + map: HashMap, + + // first item is the oldest key + oldest: VecDeque, +} + +impl LimitedCache +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(&self, k: &Q) -> Option<&V> + where + K: Borrow, + Q: Hash + Eq, + { + self.map.get(k) + } + + pub(crate) fn remove(&mut self, k: &Q) -> Option + where + K: Borrow, + 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; + + #[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); + } + } +} diff --git a/third_party/rustls-fork-shadow-tls/src/manual/defaults.rs b/third_party/rustls-fork-shadow-tls/src/manual/defaults.rs new file mode 100644 index 0000000..aa15863 --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/src/manual/defaults.rs @@ -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. + +*/ diff --git a/third_party/rustls-fork-shadow-tls/src/manual/features.rs b/third_party/rustls-fork-shadow-tls/src/manual/features.rs new file mode 100644 index 0000000..639a231 --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/src/manual/features.rs @@ -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. + +*/ diff --git a/third_party/rustls-fork-shadow-tls/src/manual/howto.rs b/third_party/rustls-fork-shadow-tls/src/manual/howto.rs new file mode 100644 index 0000000..aa68a1e --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/src/manual/howto.rs @@ -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. + +*/ diff --git a/third_party/rustls-fork-shadow-tls/src/manual/implvulns.rs b/third_party/rustls-fork-shadow-tls/src/manual/implvulns.rs new file mode 100644 index 0000000..d08e110 --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/src/manual/implvulns.rs @@ -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. + + +*/ diff --git a/third_party/rustls-fork-shadow-tls/src/manual/mod.rs b/third_party/rustls-fork-shadow-tls/src/manual/mod.rs new file mode 100644 index 0000000..778d24b --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/src/manual/mod.rs @@ -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; diff --git a/third_party/rustls-fork-shadow-tls/src/manual/tlsvulns.rs b/third_party/rustls-fork-shadow-tls/src/manual/tlsvulns.rs new file mode 100644 index 0000000..77d5510 --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/src/manual/tlsvulns.rs @@ -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. +*/ diff --git a/third_party/rustls-fork-shadow-tls/src/msgs/alert.rs b/third_party/rustls-fork-shadow-tls/src/msgs/alert.rs new file mode 100644 index 0000000..2e12e6e --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/src/msgs/alert.rs @@ -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) { + self.level.encode(bytes); + self.description.encode(bytes); + } + + fn read(r: &mut Reader) -> Option { + let level = AlertLevel::read(r)?; + let description = AlertDescription::read(r)?; + + Some(Self { level, description }) + } +} diff --git a/third_party/rustls-fork-shadow-tls/src/msgs/base.rs b/third_party/rustls-fork-shadow-tls/src/msgs/base.rs new file mode 100644 index 0000000..8ae0f6d --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/src/msgs/base.rs @@ -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); + +impl Codec for Payload { + fn encode(&self, bytes: &mut Vec) { + bytes.extend_from_slice(&self.0); + } + + fn read(r: &mut Reader) -> Option { + Some(Self::read(r)) + } +} + +impl Payload { + pub fn new(bytes: impl Into>) -> 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) { + codec::u24(self.0.len() as u32).encode(bytes); + bytes.extend_from_slice(&self.0); + } + + fn read(r: &mut Reader) -> Option { + 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); + +impl PayloadU24 { + pub fn new(bytes: Vec) -> Self { + Self(bytes) + } +} + +impl Codec for PayloadU24 { + fn encode(&self, bytes: &mut Vec) { + codec::u24(self.0.len() as u32).encode(bytes); + bytes.extend_from_slice(&self.0); + } + + fn read(r: &mut Reader) -> Option { + 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); + +impl PayloadU16 { + pub fn new(bytes: Vec) -> Self { + Self(bytes) + } + + pub fn empty() -> Self { + Self::new(Vec::new()) + } + + pub fn encode_slice(slice: &[u8], bytes: &mut Vec) { + (slice.len() as u16).encode(bytes); + bytes.extend_from_slice(slice); + } +} + +impl Codec for PayloadU16 { + fn encode(&self, bytes: &mut Vec) { + Self::encode_slice(&self.0, bytes); + } + + fn read(r: &mut Reader) -> Option { + 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); + +impl PayloadU8 { + pub fn new(bytes: Vec) -> Self { + Self(bytes) + } + + pub fn empty() -> Self { + Self(Vec::new()) + } + + pub fn into_inner(self) -> Vec { + self.0 + } +} + +impl Codec for PayloadU8 { + fn encode(&self, bytes: &mut Vec) { + (self.0.len() as u8).encode(bytes); + bytes.extend_from_slice(&self.0); + } + + fn read(r: &mut Reader) -> Option { + 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, +) -> fmt::Result { + for b in payload { + write!(f, "{:02x}", b)? + } + Ok(()) +} diff --git a/third_party/rustls-fork-shadow-tls/src/msgs/ccs.rs b/third_party/rustls-fork-shadow-tls/src/msgs/ccs.rs new file mode 100644 index 0000000..d9b0eb3 --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/src/msgs/ccs.rs @@ -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) { + 1u8.encode(bytes); + } + + fn read(r: &mut Reader) -> Option { + let typ = u8::read(r)?; + + if typ == 1 && !r.any_left() { + Some(Self {}) + } else { + None + } + } +} diff --git a/third_party/rustls-fork-shadow-tls/src/msgs/codec.rs b/third_party/rustls-fork-shadow-tls/src/msgs/codec.rs new file mode 100644 index 0000000..9ad1b12 --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/src/msgs/codec.rs @@ -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 { + 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); + + /// 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; + + /// Convenience function for encoding the implementation + /// into a vec and returning it + fn get_encoding(&self) -> Vec { + 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 { + let mut reader = Reader::init(bytes); + Self::read(&mut reader) + } +} + +fn decode_u8(bytes: &[u8]) -> Option { + let [value]: [u8; 1] = bytes.try_into().ok()?; + Some(value) +} + +impl Codec for u8 { + fn encode(&self, bytes: &mut Vec) { + bytes.push(*self); + } + + fn read(r: &mut Reader) -> Option { + 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 { + Some(u16::from_be_bytes(bytes.try_into().ok()?)) +} + +impl Codec for u16 { + fn encode(&self, bytes: &mut Vec) { + let mut b16 = [0u8; 2]; + put_u16(*self, &mut b16); + bytes.extend_from_slice(&b16); + } + + fn read(r: &mut Reader) -> Option { + 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 { + 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 for usize { + #[inline] + fn from(v: u24) -> Self { + v.0 as Self + } +} + +impl Codec for u24 { + fn encode(&self, bytes: &mut Vec) { + let be_bytes = u32::to_be_bytes(self.0); + bytes.extend_from_slice(&be_bytes[1..]) + } + + fn read(r: &mut Reader) -> Option { + r.take(3).and_then(Self::decode) + } +} + +pub fn decode_u32(bytes: &[u8]) -> Option { + Some(u32::from_be_bytes(bytes.try_into().ok()?)) +} + +impl Codec for u32 { + fn encode(&self, bytes: &mut Vec) { + bytes.extend(Self::to_be_bytes(*self)) + } + + fn read(r: &mut Reader) -> Option { + 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 { + Some(u64::from_be_bytes(bytes.try_into().ok()?)) +} + +impl Codec for u64 { + fn encode(&self, bytes: &mut Vec) { + let mut b64 = [0u8; 8]; + put_u64(*self, &mut b64); + bytes.extend_from_slice(&b64); + } + + fn read(r: &mut Reader) -> Option { + r.take(8).and_then(decode_u64) + } +} + +pub fn encode_vec_u8(bytes: &mut Vec, 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(bytes: &mut Vec, 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(bytes: &mut Vec, 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(r: &mut Reader) -> Option> { + let mut ret: Vec = 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(r: &mut Reader) -> Option> { + let mut ret: Vec = 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(r: &mut Reader, max_bytes: usize) -> Option> { + let mut ret: Vec = 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) +} diff --git a/third_party/rustls-fork-shadow-tls/src/msgs/deframer.rs b/third_party/rustls-fork-shadow-tls/src/msgs/deframer.rs new file mode 100644 index 0000000..2ada2d8 --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/src/msgs/deframer.rs @@ -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, + + /// 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, 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 { + 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 { + 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 { + let mut rd = ByteRead::new(bytes); + d.read(&mut rd) + } + + fn input_bytes_concat( + d: &mut MessageDeframer, + bytes1: &[u8], + bytes2: &[u8], + ) -> io::Result { + 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, + } + + 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 { + 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) { + 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()); + } +} diff --git a/third_party/rustls-fork-shadow-tls/src/msgs/enums.rs b/third_party/rustls-fork-shadow-tls/src/msgs/enums.rs new file mode 100644 index 0000000..333db73 --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/src/msgs/enums.rs @@ -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 + } +} diff --git a/third_party/rustls-fork-shadow-tls/src/msgs/enums_test.rs b/third_party/rustls-fork-shadow-tls/src/msgs/enums_test.rs new file mode 100644 index 0000000..220b5aa --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/src/msgs/enums_test.rs @@ -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(enum_value: &T) -> u8 { + let enc = enum_value.get_encoding(); + assert_eq!(enc.len(), 1); + enc[0] +} + +fn get16(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(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(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::NONE, HashAlgorithm::SHA512); + test_enum8::(SignatureAlgorithm::Anonymous, SignatureAlgorithm::ECDSA); + test_enum8::( + ClientCertificateType::RSASign, + ClientCertificateType::ECDSAFixedECDH, + ); + test_enum8::(Compression::Null, Compression::LSZ); + test_enum8::(ContentType::ChangeCipherSpec, ContentType::Heartbeat); + test_enum8::(HandshakeType::HelloRequest, HandshakeType::MessageHash); + test_enum8::(AlertLevel::Warning, AlertLevel::Fatal); + test_enum8::( + AlertDescription::CloseNotify, + AlertDescription::NoApplicationProtocol, + ); + test_enum8::( + HeartbeatMessageType::Request, + HeartbeatMessageType::Response, + ); + test_enum16::(ExtensionType::ServerName, ExtensionType::RenegotiationInfo); + test_enum8::(ServerNameType::HostName, ServerNameType::HostName); + test_enum16::( + NamedCurve::sect163k1, + NamedCurve::arbitrary_explicit_char2_curves, + ); + test_enum16::(NamedGroup::secp256r1, NamedGroup::FFDHE8192); + test_enum8::( + ECPointFormat::Uncompressed, + ECPointFormat::ANSIX962CompressedChar2, + ); + test_enum8::( + HeartbeatMode::PeerAllowedToSend, + HeartbeatMode::PeerNotAllowedToSend, + ); + test_enum8::(ECCurveType::ExplicitPrime, ECCurveType::NamedCurve); + test_enum8::(PSKKeyExchangeMode::PSK_KE, PSKKeyExchangeMode::PSK_DHE_KE); + test_enum8::( + KeyUpdateRequest::UpdateNotRequested, + KeyUpdateRequest::UpdateRequested, + ); + test_enum8::(CertificateStatusType::OCSP, CertificateStatusType::OCSP); +} diff --git a/third_party/rustls-fork-shadow-tls/src/msgs/fragmenter.rs b/third_party/rustls-fork-shadow-tls/src/msgs/fragmenter.rs new file mode 100644 index 0000000..b649bcf --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/src/msgs/fragmenter.rs @@ -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> + '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> + '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) -> 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 = (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::>(); + 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::>(); + 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", + ); + } +} diff --git a/third_party/rustls-fork-shadow-tls/src/msgs/handshake-test.1.bin b/third_party/rustls-fork-shadow-tls/src/msgs/handshake-test.1.bin new file mode 100644 index 0000000..5c04f3f Binary files /dev/null and b/third_party/rustls-fork-shadow-tls/src/msgs/handshake-test.1.bin differ diff --git a/third_party/rustls-fork-shadow-tls/src/msgs/handshake.rs b/third_party/rustls-fork-shadow-tls/src/msgs/handshake.rs new file mode 100644 index 0000000..74dd5c5 --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/src/msgs/handshake.rs @@ -0,0 +1,2380 @@ +#![allow(non_camel_case_types)] +use crate::enums::{CipherSuite, ProtocolVersion, SignatureScheme}; +use crate::key; +use crate::msgs::base::{Payload, PayloadU16, PayloadU24, PayloadU8}; +use crate::msgs::codec; +use crate::msgs::codec::{Codec, Reader}; +use crate::msgs::enums::{ + CertificateStatusType, ClientCertificateType, Compression, ECCurveType, ECPointFormat, + ExtensionType, HandshakeType, HashAlgorithm, KeyUpdateRequest, NamedGroup, PSKKeyExchangeMode, + ServerNameType, SignatureAlgorithm, +}; +use crate::rand; + +#[cfg(feature = "logging")] +use crate::log::warn; + +use std::collections; +use std::fmt; + +macro_rules! declare_u8_vec( + ($name:ident, $itemtype:ty) => { + pub type $name = Vec<$itemtype>; + + impl Codec for $name { + fn encode(&self, bytes: &mut Vec) { + codec::encode_vec_u8(bytes, self); + } + + fn read(r: &mut Reader) -> Option { + codec::read_vec_u8::<$itemtype>(r) + } + } + } +); + +macro_rules! declare_u16_vec( + ($name:ident, $itemtype:ty) => { + pub type $name = Vec<$itemtype>; + + impl Codec for $name { + fn encode(&self, bytes: &mut Vec) { + codec::encode_vec_u16(bytes, self); + } + + fn read(r: &mut Reader) -> Option { + codec::read_vec_u16::<$itemtype>(r) + } + } + } +); + +declare_u16_vec!(VecU16OfPayloadU8, PayloadU8); +declare_u16_vec!(VecU16OfPayloadU16, PayloadU16); + +#[derive(Clone, Copy, Eq, PartialEq)] +pub struct Random(pub [u8; 32]); + +impl fmt::Debug for Random { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + super::base::hex(f, &self.0) + } +} + +static HELLO_RETRY_REQUEST_RANDOM: Random = Random([ + 0xcf, 0x21, 0xad, 0x74, 0xe5, 0x9a, 0x61, 0x11, 0xbe, 0x1d, 0x8c, 0x02, 0x1e, 0x65, 0xb8, 0x91, + 0xc2, 0xa2, 0x11, 0x16, 0x7a, 0xbb, 0x8c, 0x5e, 0x07, 0x9e, 0x09, 0xe2, 0xc8, 0xa8, 0x33, 0x9c, +]); + +static ZERO_RANDOM: Random = Random([0u8; 32]); + +impl Codec for Random { + fn encode(&self, bytes: &mut Vec) { + bytes.extend_from_slice(&self.0); + } + + fn read(r: &mut Reader) -> Option { + let bytes = r.take(32)?; + let mut opaque = [0; 32]; + opaque.clone_from_slice(bytes); + + Some(Self(opaque)) + } +} + +impl Random { + pub fn new() -> Result { + let mut data = [0u8; 32]; + rand::fill_random(&mut data)?; + Ok(Self(data)) + } + + pub fn write_slice(&self, bytes: &mut [u8]) { + let buf = self.get_encoding(); + bytes.copy_from_slice(&buf); + } +} + +impl From<[u8; 32]> for Random { + #[inline] + fn from(bytes: [u8; 32]) -> Self { + Self(bytes) + } +} + +#[derive(Copy, Clone)] +pub struct SessionID { + // hack: we need to access it + pub(crate) len: usize, + // hack: we need to access it + pub(crate) data: [u8; 32], +} + +impl fmt::Debug for SessionID { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + super::base::hex(f, &self.data[..self.len]) + } +} + +impl PartialEq for SessionID { + fn eq(&self, other: &Self) -> bool { + if self.len != other.len { + return false; + } + + let mut diff = 0u8; + for i in 0..self.len { + diff |= self.data[i] ^ other.data[i] + } + + diff == 0u8 + } +} + +impl Codec for SessionID { + fn encode(&self, bytes: &mut Vec) { + debug_assert!(self.len <= 32); + bytes.push(self.len as u8); + bytes.extend_from_slice(&self.data[..self.len]); + } + + fn read(r: &mut Reader) -> Option { + let len = u8::read(r)? as usize; + if len > 32 { + return None; + } + + let bytes = r.take(len)?; + let mut out = [0u8; 32]; + out[..len].clone_from_slice(&bytes[..len]); + + Some(Self { data: out, len }) + } +} + +impl SessionID { + pub fn random() -> Result { + let mut data = [0u8; 32]; + rand::fill_random(&mut data)?; + Ok(Self { data, len: 32 }) + } + + pub fn zero() -> Self { + Self { + data: [0u8; 32], + len: 32, + } + } + + pub fn empty() -> Self { + Self { + data: [0u8; 32], + len: 0, + } + } + + pub fn len(&self) -> usize { + self.len + } + + pub fn is_empty(&self) -> bool { + self.len == 0 + } +} + +#[derive(Clone, Debug)] +pub struct UnknownExtension { + pub typ: ExtensionType, + pub payload: Payload, +} + +impl UnknownExtension { + fn encode(&self, bytes: &mut Vec) { + self.payload.encode(bytes); + } + + fn read(typ: ExtensionType, r: &mut Reader) -> Self { + let payload = Payload::read(r); + Self { typ, payload } + } +} + +declare_u8_vec!(ECPointFormatList, ECPointFormat); + +pub trait SupportedPointFormats { + fn supported() -> ECPointFormatList; +} + +impl SupportedPointFormats for ECPointFormatList { + fn supported() -> ECPointFormatList { + vec![ECPointFormat::Uncompressed] + } +} + +declare_u16_vec!(NamedGroups, NamedGroup); + +declare_u16_vec!(SupportedSignatureSchemes, SignatureScheme); + +pub trait DecomposedSignatureScheme { + fn sign(&self) -> SignatureAlgorithm; + fn make(alg: SignatureAlgorithm, hash: HashAlgorithm) -> SignatureScheme; +} + +impl DecomposedSignatureScheme for SignatureScheme { + fn sign(&self) -> SignatureAlgorithm { + match *self { + Self::RSA_PKCS1_SHA1 + | Self::RSA_PKCS1_SHA256 + | Self::RSA_PKCS1_SHA384 + | Self::RSA_PKCS1_SHA512 + | Self::RSA_PSS_SHA256 + | Self::RSA_PSS_SHA384 + | Self::RSA_PSS_SHA512 => SignatureAlgorithm::RSA, + Self::ECDSA_NISTP256_SHA256 + | Self::ECDSA_NISTP384_SHA384 + | Self::ECDSA_NISTP521_SHA512 => SignatureAlgorithm::ECDSA, + _ => SignatureAlgorithm::Unknown(0), + } + } + + fn make(alg: SignatureAlgorithm, hash: HashAlgorithm) -> SignatureScheme { + use crate::msgs::enums::HashAlgorithm::{SHA1, SHA256, SHA384, SHA512}; + use crate::msgs::enums::SignatureAlgorithm::{ECDSA, RSA}; + + match (alg, hash) { + (RSA, SHA1) => Self::RSA_PKCS1_SHA1, + (RSA, SHA256) => Self::RSA_PKCS1_SHA256, + (RSA, SHA384) => Self::RSA_PKCS1_SHA384, + (RSA, SHA512) => Self::RSA_PKCS1_SHA512, + (ECDSA, SHA256) => Self::ECDSA_NISTP256_SHA256, + (ECDSA, SHA384) => Self::ECDSA_NISTP384_SHA384, + (ECDSA, SHA512) => Self::ECDSA_NISTP521_SHA512, + (_, _) => unreachable!(), + } + } +} + +#[derive(Clone, Debug)] +pub enum ServerNamePayload { + // Stored twice, bytes so we can round-trip, and DnsName for use + HostName((PayloadU16, webpki::DnsName)), + Unknown(Payload), +} + +impl ServerNamePayload { + pub fn new_hostname(hostname: webpki::DnsName) -> Self { + let raw = { + let s: &str = hostname.as_ref().into(); + PayloadU16::new(s.as_bytes().into()) + }; + Self::HostName((raw, hostname)) + } + + fn read_hostname(r: &mut Reader) -> Option { + let raw = PayloadU16::read(r)?; + + let dns_name = { + match webpki::DnsNameRef::try_from_ascii(&raw.0) { + Ok(dns_name) => dns_name.into(), + Err(_) => { + warn!("Illegal SNI hostname received {:?}", raw.0); + return None; + } + } + }; + Some(Self::HostName((raw, dns_name))) + } + + fn encode(&self, bytes: &mut Vec) { + match *self { + Self::HostName((ref r, _)) => r.encode(bytes), + Self::Unknown(ref r) => r.encode(bytes), + } + } +} + +#[derive(Clone, Debug)] +pub struct ServerName { + pub typ: ServerNameType, + pub payload: ServerNamePayload, +} + +impl Codec for ServerName { + fn encode(&self, bytes: &mut Vec) { + self.typ.encode(bytes); + self.payload.encode(bytes); + } + + fn read(r: &mut Reader) -> Option { + let typ = ServerNameType::read(r)?; + + let payload = match typ { + ServerNameType::HostName => ServerNamePayload::read_hostname(r)?, + _ => ServerNamePayload::Unknown(Payload::read(r)), + }; + + Some(Self { typ, payload }) + } +} + +declare_u16_vec!(ServerNameRequest, ServerName); + +pub trait ConvertServerNameList { + fn has_duplicate_names_for_type(&self) -> bool; + fn get_single_hostname(&self) -> Option; +} + +impl ConvertServerNameList for ServerNameRequest { + /// RFC6066: "The ServerNameList MUST NOT contain more than one name of the same name_type." + fn has_duplicate_names_for_type(&self) -> bool { + let mut seen = collections::HashSet::new(); + + for name in self { + if !seen.insert(name.typ.get_u8()) { + return true; + } + } + + false + } + + fn get_single_hostname(&self) -> Option { + fn only_dns_hostnames(name: &ServerName) -> Option { + if let ServerNamePayload::HostName((_, ref dns)) = name.payload { + Some(dns.as_ref()) + } else { + None + } + } + + self.iter() + .filter_map(only_dns_hostnames) + .next() + } +} + +pub type ProtocolNameList = VecU16OfPayloadU8; + +pub trait ConvertProtocolNameList { + fn from_slices(names: &[&[u8]]) -> Self; + fn to_slices(&self) -> Vec<&[u8]>; + fn as_single_slice(&self) -> Option<&[u8]>; +} + +impl ConvertProtocolNameList for ProtocolNameList { + fn from_slices(names: &[&[u8]]) -> Self { + let mut ret = Self::new(); + + for name in names { + ret.push(PayloadU8::new(name.to_vec())); + } + + ret + } + + fn to_slices(&self) -> Vec<&[u8]> { + self.iter() + .map(|proto| -> &[u8] { &proto.0 }) + .collect::>() + } + + fn as_single_slice(&self) -> Option<&[u8]> { + if self.len() == 1 { + Some(&self[0].0) + } else { + None + } + } +} + +// --- TLS 1.3 Key shares --- +#[derive(Clone, Debug)] +pub struct KeyShareEntry { + pub group: NamedGroup, + pub payload: PayloadU16, +} + +impl KeyShareEntry { + pub fn new(group: NamedGroup, payload: &[u8]) -> Self { + Self { + group, + payload: PayloadU16::new(payload.to_vec()), + } + } +} + +impl Codec for KeyShareEntry { + fn encode(&self, bytes: &mut Vec) { + self.group.encode(bytes); + self.payload.encode(bytes); + } + + fn read(r: &mut Reader) -> Option { + let group = NamedGroup::read(r)?; + let payload = PayloadU16::read(r)?; + + Some(Self { group, payload }) + } +} + +// --- TLS 1.3 PresharedKey offers --- +#[derive(Clone, Debug)] +pub struct PresharedKeyIdentity { + pub identity: PayloadU16, + pub obfuscated_ticket_age: u32, +} + +impl PresharedKeyIdentity { + pub fn new(id: Vec, age: u32) -> Self { + Self { + identity: PayloadU16::new(id), + obfuscated_ticket_age: age, + } + } +} + +impl Codec for PresharedKeyIdentity { + fn encode(&self, bytes: &mut Vec) { + self.identity.encode(bytes); + self.obfuscated_ticket_age.encode(bytes); + } + + fn read(r: &mut Reader) -> Option { + Some(Self { + identity: PayloadU16::read(r)?, + obfuscated_ticket_age: u32::read(r)?, + }) + } +} + +declare_u16_vec!(PresharedKeyIdentities, PresharedKeyIdentity); +pub type PresharedKeyBinder = PayloadU8; +pub type PresharedKeyBinders = VecU16OfPayloadU8; + +#[derive(Clone, Debug)] +pub struct PresharedKeyOffer { + pub identities: PresharedKeyIdentities, + pub binders: PresharedKeyBinders, +} + +impl PresharedKeyOffer { + /// Make a new one with one entry. + pub fn new(id: PresharedKeyIdentity, binder: Vec) -> Self { + Self { + identities: vec![id], + binders: vec![PresharedKeyBinder::new(binder)], + } + } +} + +impl Codec for PresharedKeyOffer { + fn encode(&self, bytes: &mut Vec) { + self.identities.encode(bytes); + self.binders.encode(bytes); + } + + fn read(r: &mut Reader) -> Option { + Some(Self { + identities: PresharedKeyIdentities::read(r)?, + binders: PresharedKeyBinders::read(r)?, + }) + } +} + +// --- RFC6066 certificate status request --- +type ResponderIDs = VecU16OfPayloadU16; + +#[derive(Clone, Debug)] +pub struct OCSPCertificateStatusRequest { + pub responder_ids: ResponderIDs, + pub extensions: PayloadU16, +} + +impl Codec for OCSPCertificateStatusRequest { + fn encode(&self, bytes: &mut Vec) { + CertificateStatusType::OCSP.encode(bytes); + self.responder_ids.encode(bytes); + self.extensions.encode(bytes); + } + + fn read(r: &mut Reader) -> Option { + Some(Self { + responder_ids: ResponderIDs::read(r)?, + extensions: PayloadU16::read(r)?, + }) + } +} + +#[derive(Clone, Debug)] +pub enum CertificateStatusRequest { + OCSP(OCSPCertificateStatusRequest), + Unknown((CertificateStatusType, Payload)), +} + +impl Codec for CertificateStatusRequest { + fn encode(&self, bytes: &mut Vec) { + match self { + Self::OCSP(ref r) => r.encode(bytes), + Self::Unknown((typ, payload)) => { + typ.encode(bytes); + payload.encode(bytes); + } + } + } + + fn read(r: &mut Reader) -> Option { + let typ = CertificateStatusType::read(r)?; + + match typ { + CertificateStatusType::OCSP => { + let ocsp_req = OCSPCertificateStatusRequest::read(r)?; + Some(Self::OCSP(ocsp_req)) + } + _ => { + let data = Payload::read(r); + Some(Self::Unknown((typ, data))) + } + } + } +} + +impl CertificateStatusRequest { + pub fn build_ocsp() -> Self { + let ocsp = OCSPCertificateStatusRequest { + responder_ids: ResponderIDs::new(), + extensions: PayloadU16::empty(), + }; + Self::OCSP(ocsp) + } +} + +// --- +// SCTs + +pub type SCTList = VecU16OfPayloadU16; + +// --- + +declare_u8_vec!(PSKKeyExchangeModes, PSKKeyExchangeMode); +declare_u16_vec!(KeyShareEntries, KeyShareEntry); +declare_u8_vec!(ProtocolVersions, ProtocolVersion); + +#[derive(Clone, Debug)] +pub enum ClientExtension { + ECPointFormats(ECPointFormatList), + NamedGroups(NamedGroups), + SignatureAlgorithms(SupportedSignatureSchemes), + ServerName(ServerNameRequest), + SessionTicket(ClientSessionTicket), + Protocols(ProtocolNameList), + SupportedVersions(ProtocolVersions), + KeyShare(KeyShareEntries), + PresharedKeyModes(PSKKeyExchangeModes), + PresharedKey(PresharedKeyOffer), + Cookie(PayloadU16), + ExtendedMasterSecretRequest, + CertificateStatusRequest(CertificateStatusRequest), + SignedCertificateTimestampRequest, + TransportParameters(Vec), + TransportParametersDraft(Vec), + EarlyData, + Unknown(UnknownExtension), +} + +impl ClientExtension { + pub fn get_type(&self) -> ExtensionType { + match *self { + Self::ECPointFormats(_) => ExtensionType::ECPointFormats, + Self::NamedGroups(_) => ExtensionType::EllipticCurves, + Self::SignatureAlgorithms(_) => ExtensionType::SignatureAlgorithms, + Self::ServerName(_) => ExtensionType::ServerName, + Self::SessionTicket(_) => ExtensionType::SessionTicket, + Self::Protocols(_) => ExtensionType::ALProtocolNegotiation, + Self::SupportedVersions(_) => ExtensionType::SupportedVersions, + Self::KeyShare(_) => ExtensionType::KeyShare, + Self::PresharedKeyModes(_) => ExtensionType::PSKKeyExchangeModes, + Self::PresharedKey(_) => ExtensionType::PreSharedKey, + Self::Cookie(_) => ExtensionType::Cookie, + Self::ExtendedMasterSecretRequest => ExtensionType::ExtendedMasterSecret, + Self::CertificateStatusRequest(_) => ExtensionType::StatusRequest, + Self::SignedCertificateTimestampRequest => ExtensionType::SCT, + Self::TransportParameters(_) => ExtensionType::TransportParameters, + Self::TransportParametersDraft(_) => ExtensionType::TransportParametersDraft, + Self::EarlyData => ExtensionType::EarlyData, + Self::Unknown(ref r) => r.typ, + } + } +} + +impl Codec for ClientExtension { + fn encode(&self, bytes: &mut Vec) { + self.get_type().encode(bytes); + + let mut sub: Vec = Vec::new(); + match *self { + Self::ECPointFormats(ref r) => r.encode(&mut sub), + Self::NamedGroups(ref r) => r.encode(&mut sub), + Self::SignatureAlgorithms(ref r) => r.encode(&mut sub), + Self::ServerName(ref r) => r.encode(&mut sub), + Self::SessionTicket(ClientSessionTicket::Request) + | Self::ExtendedMasterSecretRequest + | Self::SignedCertificateTimestampRequest + | Self::EarlyData => {} + Self::SessionTicket(ClientSessionTicket::Offer(ref r)) => r.encode(&mut sub), + Self::Protocols(ref r) => r.encode(&mut sub), + Self::SupportedVersions(ref r) => r.encode(&mut sub), + Self::KeyShare(ref r) => r.encode(&mut sub), + Self::PresharedKeyModes(ref r) => r.encode(&mut sub), + Self::PresharedKey(ref r) => r.encode(&mut sub), + Self::Cookie(ref r) => r.encode(&mut sub), + Self::CertificateStatusRequest(ref r) => r.encode(&mut sub), + Self::TransportParameters(ref r) | Self::TransportParametersDraft(ref r) => { + sub.extend_from_slice(r) + } + Self::Unknown(ref r) => r.encode(&mut sub), + } + + (sub.len() as u16).encode(bytes); + bytes.append(&mut sub); + } + + fn read(r: &mut Reader) -> Option { + let typ = ExtensionType::read(r)?; + let len = u16::read(r)? as usize; + let mut sub = r.sub(len)?; + + let ext = match typ { + ExtensionType::ECPointFormats => { + Self::ECPointFormats(ECPointFormatList::read(&mut sub)?) + } + ExtensionType::EllipticCurves => Self::NamedGroups(NamedGroups::read(&mut sub)?), + ExtensionType::SignatureAlgorithms => { + let schemes = SupportedSignatureSchemes::read(&mut sub)?; + Self::SignatureAlgorithms(schemes) + } + ExtensionType::ServerName => Self::ServerName(ServerNameRequest::read(&mut sub)?), + ExtensionType::SessionTicket => { + if sub.any_left() { + let contents = Payload::read(&mut sub); + Self::SessionTicket(ClientSessionTicket::Offer(contents)) + } else { + Self::SessionTicket(ClientSessionTicket::Request) + } + } + ExtensionType::ALProtocolNegotiation => { + Self::Protocols(ProtocolNameList::read(&mut sub)?) + } + ExtensionType::SupportedVersions => { + Self::SupportedVersions(ProtocolVersions::read(&mut sub)?) + } + ExtensionType::KeyShare => Self::KeyShare(KeyShareEntries::read(&mut sub)?), + ExtensionType::PSKKeyExchangeModes => { + Self::PresharedKeyModes(PSKKeyExchangeModes::read(&mut sub)?) + } + ExtensionType::PreSharedKey => Self::PresharedKey(PresharedKeyOffer::read(&mut sub)?), + ExtensionType::Cookie => Self::Cookie(PayloadU16::read(&mut sub)?), + ExtensionType::ExtendedMasterSecret if !sub.any_left() => { + Self::ExtendedMasterSecretRequest + } + ExtensionType::StatusRequest => { + let csr = CertificateStatusRequest::read(&mut sub)?; + Self::CertificateStatusRequest(csr) + } + ExtensionType::SCT if !sub.any_left() => Self::SignedCertificateTimestampRequest, + ExtensionType::TransportParameters => Self::TransportParameters(sub.rest().to_vec()), + ExtensionType::TransportParametersDraft => { + Self::TransportParametersDraft(sub.rest().to_vec()) + } + ExtensionType::EarlyData if !sub.any_left() => Self::EarlyData, + _ => Self::Unknown(UnknownExtension::read(typ, &mut sub)), + }; + + if sub.any_left() { + None + } else { + Some(ext) + } + } +} + +fn trim_hostname_trailing_dot_for_sni(dns_name: webpki::DnsNameRef) -> webpki::DnsName { + let dns_name_str: &str = dns_name.into(); + + // RFC6066: "The hostname is represented as a byte string using + // ASCII encoding without a trailing dot" + if dns_name_str.ends_with('.') { + let trimmed = &dns_name_str[0..dns_name_str.len() - 1]; + webpki::DnsNameRef::try_from_ascii_str(trimmed) + .unwrap() + .to_owned() + } else { + dns_name.to_owned() + } +} + +impl ClientExtension { + /// Make a basic SNI ServerNameRequest quoting `hostname`. + pub fn make_sni(dns_name: webpki::DnsNameRef) -> Self { + let name = ServerName { + typ: ServerNameType::HostName, + payload: ServerNamePayload::new_hostname(trim_hostname_trailing_dot_for_sni(dns_name)), + }; + + Self::ServerName(vec![name]) + } +} + +#[derive(Clone, Debug)] +pub enum ClientSessionTicket { + Request, + Offer(Payload), +} + +#[derive(Clone, Debug)] +pub enum ServerExtension { + ECPointFormats(ECPointFormatList), + ServerNameAck, + SessionTicketAck, + RenegotiationInfo(PayloadU8), + Protocols(ProtocolNameList), + KeyShare(KeyShareEntry), + PresharedKey(u16), + ExtendedMasterSecretAck, + CertificateStatusAck, + SignedCertificateTimestamp(SCTList), + SupportedVersions(ProtocolVersion), + TransportParameters(Vec), + TransportParametersDraft(Vec), + EarlyData, + Unknown(UnknownExtension), +} + +impl ServerExtension { + pub fn get_type(&self) -> ExtensionType { + match *self { + Self::ECPointFormats(_) => ExtensionType::ECPointFormats, + Self::ServerNameAck => ExtensionType::ServerName, + Self::SessionTicketAck => ExtensionType::SessionTicket, + Self::RenegotiationInfo(_) => ExtensionType::RenegotiationInfo, + Self::Protocols(_) => ExtensionType::ALProtocolNegotiation, + Self::KeyShare(_) => ExtensionType::KeyShare, + Self::PresharedKey(_) => ExtensionType::PreSharedKey, + Self::ExtendedMasterSecretAck => ExtensionType::ExtendedMasterSecret, + Self::CertificateStatusAck => ExtensionType::StatusRequest, + Self::SignedCertificateTimestamp(_) => ExtensionType::SCT, + Self::SupportedVersions(_) => ExtensionType::SupportedVersions, + Self::TransportParameters(_) => ExtensionType::TransportParameters, + Self::TransportParametersDraft(_) => ExtensionType::TransportParametersDraft, + Self::EarlyData => ExtensionType::EarlyData, + Self::Unknown(ref r) => r.typ, + } + } +} + +impl Codec for ServerExtension { + fn encode(&self, bytes: &mut Vec) { + self.get_type().encode(bytes); + + let mut sub: Vec = Vec::new(); + match *self { + Self::ECPointFormats(ref r) => r.encode(&mut sub), + Self::ServerNameAck + | Self::SessionTicketAck + | Self::ExtendedMasterSecretAck + | Self::CertificateStatusAck + | Self::EarlyData => {} + Self::RenegotiationInfo(ref r) => r.encode(&mut sub), + Self::Protocols(ref r) => r.encode(&mut sub), + Self::KeyShare(ref r) => r.encode(&mut sub), + Self::PresharedKey(r) => r.encode(&mut sub), + Self::SignedCertificateTimestamp(ref r) => r.encode(&mut sub), + Self::SupportedVersions(ref r) => r.encode(&mut sub), + Self::TransportParameters(ref r) | Self::TransportParametersDraft(ref r) => { + sub.extend_from_slice(r) + } + Self::Unknown(ref r) => r.encode(&mut sub), + } + + (sub.len() as u16).encode(bytes); + bytes.append(&mut sub); + } + + fn read(r: &mut Reader) -> Option { + let typ = ExtensionType::read(r)?; + let len = u16::read(r)? as usize; + let mut sub = r.sub(len)?; + + let ext = match typ { + ExtensionType::ECPointFormats => { + Self::ECPointFormats(ECPointFormatList::read(&mut sub)?) + } + ExtensionType::ServerName => Self::ServerNameAck, + ExtensionType::SessionTicket => Self::SessionTicketAck, + ExtensionType::StatusRequest => Self::CertificateStatusAck, + ExtensionType::RenegotiationInfo => Self::RenegotiationInfo(PayloadU8::read(&mut sub)?), + ExtensionType::ALProtocolNegotiation => { + Self::Protocols(ProtocolNameList::read(&mut sub)?) + } + ExtensionType::KeyShare => Self::KeyShare(KeyShareEntry::read(&mut sub)?), + ExtensionType::PreSharedKey => Self::PresharedKey(u16::read(&mut sub)?), + ExtensionType::ExtendedMasterSecret => Self::ExtendedMasterSecretAck, + ExtensionType::SCT => { + let scts = SCTList::read(&mut sub)?; + Self::SignedCertificateTimestamp(scts) + } + ExtensionType::SupportedVersions => { + Self::SupportedVersions(ProtocolVersion::read(&mut sub)?) + } + ExtensionType::TransportParameters => Self::TransportParameters(sub.rest().to_vec()), + ExtensionType::TransportParametersDraft => { + Self::TransportParametersDraft(sub.rest().to_vec()) + } + ExtensionType::EarlyData => Self::EarlyData, + _ => Self::Unknown(UnknownExtension::read(typ, &mut sub)), + }; + + if sub.any_left() { + None + } else { + Some(ext) + } + } +} + +impl ServerExtension { + pub fn make_alpn(proto: &[&[u8]]) -> Self { + Self::Protocols(ProtocolNameList::from_slices(proto)) + } + + pub fn make_empty_renegotiation_info() -> Self { + let empty = Vec::new(); + Self::RenegotiationInfo(PayloadU8::new(empty)) + } + + pub fn make_sct(sctl: Vec) -> Self { + let scts = SCTList::read_bytes(&sctl).expect("invalid SCT list"); + Self::SignedCertificateTimestamp(scts) + } +} + +#[derive(Debug)] +pub struct ClientHelloPayload { + pub client_version: ProtocolVersion, + pub random: Random, + pub session_id: SessionID, + pub cipher_suites: Vec, + pub compression_methods: Vec, + pub extensions: Vec, +} + +impl Codec for ClientHelloPayload { + fn encode(&self, bytes: &mut Vec) { + self.client_version.encode(bytes); + self.random.encode(bytes); + self.session_id.encode(bytes); + codec::encode_vec_u16(bytes, &self.cipher_suites); + codec::encode_vec_u8(bytes, &self.compression_methods); + + if !self.extensions.is_empty() { + codec::encode_vec_u16(bytes, &self.extensions); + } + } + + fn read(r: &mut Reader) -> Option { + let mut ret = Self { + client_version: ProtocolVersion::read(r)?, + random: Random::read(r)?, + session_id: SessionID::read(r)?, + cipher_suites: codec::read_vec_u16::(r)?, + compression_methods: codec::read_vec_u8::(r)?, + extensions: Vec::new(), + }; + + if r.any_left() { + ret.extensions = codec::read_vec_u16::(r)?; + } + + if r.any_left() || ret.extensions.is_empty() { + None + } else { + Some(ret) + } + } +} + +impl ClientHelloPayload { + /// Returns true if there is more than one extension of a given + /// type. + pub fn has_duplicate_extension(&self) -> bool { + let mut seen = collections::HashSet::new(); + + for ext in &self.extensions { + let typ = ext.get_type().get_u16(); + + if seen.contains(&typ) { + return true; + } + seen.insert(typ); + } + + false + } + + pub fn find_extension(&self, ext: ExtensionType) -> Option<&ClientExtension> { + self.extensions + .iter() + .find(|x| x.get_type() == ext) + } + + pub fn get_sni_extension(&self) -> Option<&ServerNameRequest> { + let ext = self.find_extension(ExtensionType::ServerName)?; + match *ext { + ClientExtension::ServerName(ref req) => Some(req), + _ => None, + } + } + + pub fn get_sigalgs_extension(&self) -> Option<&SupportedSignatureSchemes> { + let ext = self.find_extension(ExtensionType::SignatureAlgorithms)?; + match *ext { + ClientExtension::SignatureAlgorithms(ref req) => Some(req), + _ => None, + } + } + + pub fn get_namedgroups_extension(&self) -> Option<&NamedGroups> { + let ext = self.find_extension(ExtensionType::EllipticCurves)?; + match *ext { + ClientExtension::NamedGroups(ref req) => Some(req), + _ => None, + } + } + + pub fn get_ecpoints_extension(&self) -> Option<&ECPointFormatList> { + let ext = self.find_extension(ExtensionType::ECPointFormats)?; + match *ext { + ClientExtension::ECPointFormats(ref req) => Some(req), + _ => None, + } + } + + pub fn get_alpn_extension(&self) -> Option<&ProtocolNameList> { + let ext = self.find_extension(ExtensionType::ALProtocolNegotiation)?; + match *ext { + ClientExtension::Protocols(ref req) => Some(req), + _ => None, + } + } + + pub fn get_quic_params_extension(&self) -> Option> { + let ext = self + .find_extension(ExtensionType::TransportParameters) + .or_else(|| self.find_extension(ExtensionType::TransportParametersDraft))?; + match *ext { + ClientExtension::TransportParameters(ref bytes) + | ClientExtension::TransportParametersDraft(ref bytes) => Some(bytes.to_vec()), + _ => None, + } + } + + pub fn get_ticket_extension(&self) -> Option<&ClientExtension> { + self.find_extension(ExtensionType::SessionTicket) + } + + pub fn get_versions_extension(&self) -> Option<&ProtocolVersions> { + let ext = self.find_extension(ExtensionType::SupportedVersions)?; + match *ext { + ClientExtension::SupportedVersions(ref vers) => Some(vers), + _ => None, + } + } + + pub fn get_keyshare_extension(&self) -> Option<&KeyShareEntries> { + let ext = self.find_extension(ExtensionType::KeyShare)?; + match *ext { + ClientExtension::KeyShare(ref shares) => Some(shares), + _ => None, + } + } + + pub fn has_keyshare_extension_with_duplicates(&self) -> bool { + if let Some(entries) = self.get_keyshare_extension() { + let mut seen = collections::HashSet::new(); + + for kse in entries { + let grp = kse.group.get_u16(); + + if !seen.insert(grp) { + return true; + } + } + } + + false + } + + pub fn get_psk(&self) -> Option<&PresharedKeyOffer> { + let ext = self.find_extension(ExtensionType::PreSharedKey)?; + match *ext { + ClientExtension::PresharedKey(ref psk) => Some(psk), + _ => None, + } + } + + pub fn check_psk_ext_is_last(&self) -> bool { + self.extensions + .last() + .map_or(false, |ext| ext.get_type() == ExtensionType::PreSharedKey) + } + + pub fn get_psk_modes(&self) -> Option<&PSKKeyExchangeModes> { + let ext = self.find_extension(ExtensionType::PSKKeyExchangeModes)?; + match *ext { + ClientExtension::PresharedKeyModes(ref psk_modes) => Some(psk_modes), + _ => None, + } + } + + pub fn psk_mode_offered(&self, mode: PSKKeyExchangeMode) -> bool { + self.get_psk_modes() + .map(|modes| modes.contains(&mode)) + .unwrap_or(false) + } + + pub fn set_psk_binder(&mut self, binder: impl Into>) { + let last_extension = self.extensions.last_mut(); + if let Some(ClientExtension::PresharedKey(ref mut offer)) = last_extension { + offer.binders[0] = PresharedKeyBinder::new(binder.into()); + } + } + + pub fn ems_support_offered(&self) -> bool { + self.find_extension(ExtensionType::ExtendedMasterSecret) + .is_some() + } + + pub fn early_data_extension_offered(&self) -> bool { + self.find_extension(ExtensionType::EarlyData) + .is_some() + } +} + +#[derive(Debug)] +pub enum HelloRetryExtension { + KeyShare(NamedGroup), + Cookie(PayloadU16), + SupportedVersions(ProtocolVersion), + Unknown(UnknownExtension), +} + +impl HelloRetryExtension { + pub fn get_type(&self) -> ExtensionType { + match *self { + Self::KeyShare(_) => ExtensionType::KeyShare, + Self::Cookie(_) => ExtensionType::Cookie, + Self::SupportedVersions(_) => ExtensionType::SupportedVersions, + Self::Unknown(ref r) => r.typ, + } + } +} + +impl Codec for HelloRetryExtension { + fn encode(&self, bytes: &mut Vec) { + self.get_type().encode(bytes); + + let mut sub: Vec = Vec::new(); + match *self { + Self::KeyShare(ref r) => r.encode(&mut sub), + Self::Cookie(ref r) => r.encode(&mut sub), + Self::SupportedVersions(ref r) => r.encode(&mut sub), + Self::Unknown(ref r) => r.encode(&mut sub), + } + + (sub.len() as u16).encode(bytes); + bytes.append(&mut sub); + } + + fn read(r: &mut Reader) -> Option { + let typ = ExtensionType::read(r)?; + let len = u16::read(r)? as usize; + let mut sub = r.sub(len)?; + + let ext = match typ { + ExtensionType::KeyShare => Self::KeyShare(NamedGroup::read(&mut sub)?), + ExtensionType::Cookie => Self::Cookie(PayloadU16::read(&mut sub)?), + ExtensionType::SupportedVersions => { + Self::SupportedVersions(ProtocolVersion::read(&mut sub)?) + } + _ => Self::Unknown(UnknownExtension::read(typ, &mut sub)), + }; + + if sub.any_left() { + None + } else { + Some(ext) + } + } +} + +#[derive(Debug)] +pub struct HelloRetryRequest { + pub legacy_version: ProtocolVersion, + pub session_id: SessionID, + pub cipher_suite: CipherSuite, + pub extensions: Vec, +} + +impl Codec for HelloRetryRequest { + fn encode(&self, bytes: &mut Vec) { + self.legacy_version.encode(bytes); + HELLO_RETRY_REQUEST_RANDOM.encode(bytes); + self.session_id.encode(bytes); + self.cipher_suite.encode(bytes); + Compression::Null.encode(bytes); + codec::encode_vec_u16(bytes, &self.extensions); + } + + fn read(r: &mut Reader) -> Option { + let session_id = SessionID::read(r)?; + let cipher_suite = CipherSuite::read(r)?; + let compression = Compression::read(r)?; + + if compression != Compression::Null { + return None; + } + + Some(Self { + legacy_version: ProtocolVersion::Unknown(0), + session_id, + cipher_suite, + extensions: codec::read_vec_u16::(r)?, + }) + } +} + +impl HelloRetryRequest { + /// Returns true if there is more than one extension of a given + /// type. + pub fn has_duplicate_extension(&self) -> bool { + let mut seen = collections::HashSet::new(); + + for ext in &self.extensions { + let typ = ext.get_type().get_u16(); + + if seen.contains(&typ) { + return true; + } + seen.insert(typ); + } + + false + } + + pub fn has_unknown_extension(&self) -> bool { + self.extensions.iter().any(|ext| { + ext.get_type() != ExtensionType::KeyShare + && ext.get_type() != ExtensionType::SupportedVersions + && ext.get_type() != ExtensionType::Cookie + }) + } + + fn find_extension(&self, ext: ExtensionType) -> Option<&HelloRetryExtension> { + self.extensions + .iter() + .find(|x| x.get_type() == ext) + } + + pub fn get_requested_key_share_group(&self) -> Option { + let ext = self.find_extension(ExtensionType::KeyShare)?; + match *ext { + HelloRetryExtension::KeyShare(grp) => Some(grp), + _ => None, + } + } + + pub fn get_cookie(&self) -> Option<&PayloadU16> { + let ext = self.find_extension(ExtensionType::Cookie)?; + match *ext { + HelloRetryExtension::Cookie(ref ck) => Some(ck), + _ => None, + } + } + + pub fn get_supported_versions(&self) -> Option { + let ext = self.find_extension(ExtensionType::SupportedVersions)?; + match *ext { + HelloRetryExtension::SupportedVersions(ver) => Some(ver), + _ => None, + } + } +} + +#[derive(Debug)] +pub struct ServerHelloPayload { + pub legacy_version: ProtocolVersion, + pub random: Random, + pub session_id: SessionID, + pub cipher_suite: CipherSuite, + pub compression_method: Compression, + pub extensions: Vec, +} + +impl Codec for ServerHelloPayload { + fn encode(&self, bytes: &mut Vec) { + self.legacy_version.encode(bytes); + self.random.encode(bytes); + + self.session_id.encode(bytes); + self.cipher_suite.encode(bytes); + self.compression_method.encode(bytes); + + if !self.extensions.is_empty() { + codec::encode_vec_u16(bytes, &self.extensions); + } + } + + // minus version and random, which have already been read. + fn read(r: &mut Reader) -> Option { + let session_id = SessionID::read(r)?; + let suite = CipherSuite::read(r)?; + let compression = Compression::read(r)?; + + // RFC5246: + // "The presence of extensions can be detected by determining whether + // there are bytes following the compression_method field at the end of + // the ServerHello." + let extensions = if r.any_left() { + codec::read_vec_u16::(r)? + } else { + vec![] + }; + + let ret = Self { + legacy_version: ProtocolVersion::Unknown(0), + random: ZERO_RANDOM, + session_id, + cipher_suite: suite, + compression_method: compression, + extensions, + }; + + if r.any_left() { + None + } else { + Some(ret) + } + } +} + +impl HasServerExtensions for ServerHelloPayload { + fn get_extensions(&self) -> &[ServerExtension] { + &self.extensions + } +} + +impl ServerHelloPayload { + pub fn get_key_share(&self) -> Option<&KeyShareEntry> { + let ext = self.find_extension(ExtensionType::KeyShare)?; + match *ext { + ServerExtension::KeyShare(ref share) => Some(share), + _ => None, + } + } + + pub fn get_psk_index(&self) -> Option { + let ext = self.find_extension(ExtensionType::PreSharedKey)?; + match *ext { + ServerExtension::PresharedKey(ref index) => Some(*index), + _ => None, + } + } + + pub fn get_ecpoints_extension(&self) -> Option<&ECPointFormatList> { + let ext = self.find_extension(ExtensionType::ECPointFormats)?; + match *ext { + ServerExtension::ECPointFormats(ref fmts) => Some(fmts), + _ => None, + } + } + + pub fn ems_support_acked(&self) -> bool { + self.find_extension(ExtensionType::ExtendedMasterSecret) + .is_some() + } + + pub fn get_sct_list(&self) -> Option<&SCTList> { + let ext = self.find_extension(ExtensionType::SCT)?; + match *ext { + ServerExtension::SignedCertificateTimestamp(ref sctl) => Some(sctl), + _ => None, + } + } + + pub fn get_supported_versions(&self) -> Option { + let ext = self.find_extension(ExtensionType::SupportedVersions)?; + match *ext { + ServerExtension::SupportedVersions(vers) => Some(vers), + _ => None, + } + } +} + +pub type CertificatePayload = Vec; + +impl Codec for CertificatePayload { + fn encode(&self, bytes: &mut Vec) { + codec::encode_vec_u24(bytes, self); + } + + fn read(r: &mut Reader) -> Option { + // 64KB of certificates is plenty, 16MB is obviously silly + codec::read_vec_u24_limited(r, 0x10000) + } +} + +// TLS1.3 changes the Certificate payload encoding. +// That's annoying. It means the parsing is not +// context-free any more. + +#[derive(Debug)] +pub enum CertificateExtension { + CertificateStatus(CertificateStatus), + SignedCertificateTimestamp(SCTList), + Unknown(UnknownExtension), +} + +impl CertificateExtension { + pub fn get_type(&self) -> ExtensionType { + match *self { + Self::CertificateStatus(_) => ExtensionType::StatusRequest, + Self::SignedCertificateTimestamp(_) => ExtensionType::SCT, + Self::Unknown(ref r) => r.typ, + } + } + + pub fn make_sct(sct_list: Vec) -> Self { + let sctl = SCTList::read_bytes(&sct_list).expect("invalid SCT list"); + Self::SignedCertificateTimestamp(sctl) + } + + pub fn get_cert_status(&self) -> Option<&Vec> { + match *self { + Self::CertificateStatus(ref cs) => Some(&cs.ocsp_response.0), + _ => None, + } + } + + pub fn get_sct_list(&self) -> Option<&SCTList> { + match *self { + Self::SignedCertificateTimestamp(ref sctl) => Some(sctl), + _ => None, + } + } +} + +impl Codec for CertificateExtension { + fn encode(&self, bytes: &mut Vec) { + self.get_type().encode(bytes); + + let mut sub: Vec = Vec::new(); + match *self { + Self::CertificateStatus(ref r) => r.encode(&mut sub), + Self::SignedCertificateTimestamp(ref r) => r.encode(&mut sub), + Self::Unknown(ref r) => r.encode(&mut sub), + } + + (sub.len() as u16).encode(bytes); + bytes.append(&mut sub); + } + + fn read(r: &mut Reader) -> Option { + let typ = ExtensionType::read(r)?; + let len = u16::read(r)? as usize; + let mut sub = r.sub(len)?; + + let ext = match typ { + ExtensionType::StatusRequest => { + let st = CertificateStatus::read(&mut sub)?; + Self::CertificateStatus(st) + } + ExtensionType::SCT => { + let scts = SCTList::read(&mut sub)?; + Self::SignedCertificateTimestamp(scts) + } + _ => Self::Unknown(UnknownExtension::read(typ, &mut sub)), + }; + + if sub.any_left() { + None + } else { + Some(ext) + } + } +} + +declare_u16_vec!(CertificateExtensions, CertificateExtension); + +#[derive(Debug)] +pub struct CertificateEntry { + pub cert: key::Certificate, + pub exts: CertificateExtensions, +} + +impl Codec for CertificateEntry { + fn encode(&self, bytes: &mut Vec) { + self.cert.encode(bytes); + self.exts.encode(bytes); + } + + fn read(r: &mut Reader) -> Option { + Some(Self { + cert: key::Certificate::read(r)?, + exts: CertificateExtensions::read(r)?, + }) + } +} + +impl CertificateEntry { + pub fn new(cert: key::Certificate) -> Self { + Self { + cert, + exts: Vec::new(), + } + } + + pub fn has_duplicate_extension(&self) -> bool { + let mut seen = collections::HashSet::new(); + + for ext in &self.exts { + let typ = ext.get_type().get_u16(); + + if seen.contains(&typ) { + return true; + } + seen.insert(typ); + } + + false + } + + pub fn has_unknown_extension(&self) -> bool { + self.exts.iter().any(|ext| { + ext.get_type() != ExtensionType::StatusRequest && ext.get_type() != ExtensionType::SCT + }) + } + + pub fn get_ocsp_response(&self) -> Option<&Vec> { + self.exts + .iter() + .find(|ext| ext.get_type() == ExtensionType::StatusRequest) + .and_then(CertificateExtension::get_cert_status) + } + + pub fn get_scts(&self) -> Option<&SCTList> { + self.exts + .iter() + .find(|ext| ext.get_type() == ExtensionType::SCT) + .and_then(CertificateExtension::get_sct_list) + } +} + +#[derive(Debug)] +pub struct CertificatePayloadTLS13 { + pub context: PayloadU8, + pub entries: Vec, +} + +impl Codec for CertificatePayloadTLS13 { + fn encode(&self, bytes: &mut Vec) { + self.context.encode(bytes); + codec::encode_vec_u24(bytes, &self.entries); + } + + fn read(r: &mut Reader) -> Option { + Some(Self { + context: PayloadU8::read(r)?, + entries: codec::read_vec_u24_limited::(r, 0x10000)?, + }) + } +} + +impl CertificatePayloadTLS13 { + pub fn new(entries: Vec) -> Self { + Self { + context: PayloadU8::empty(), + entries, + } + } + + pub fn any_entry_has_duplicate_extension(&self) -> bool { + for entry in &self.entries { + if entry.has_duplicate_extension() { + return true; + } + } + + false + } + + pub fn any_entry_has_unknown_extension(&self) -> bool { + for entry in &self.entries { + if entry.has_unknown_extension() { + return true; + } + } + + false + } + + pub fn any_entry_has_extension(&self) -> bool { + for entry in &self.entries { + if !entry.exts.is_empty() { + return true; + } + } + + false + } + + pub fn get_end_entity_ocsp(&self) -> Vec { + self.entries + .first() + .and_then(CertificateEntry::get_ocsp_response) + .cloned() + .unwrap_or_default() + } + + pub fn get_end_entity_scts(&self) -> Option { + self.entries + .first() + .and_then(CertificateEntry::get_scts) + .cloned() + } + + pub fn convert(&self) -> CertificatePayload { + let mut ret = Vec::new(); + for entry in &self.entries { + ret.push(entry.cert.clone()); + } + ret + } +} + +#[derive(Debug)] +pub enum KeyExchangeAlgorithm { + BulkOnly, + DH, + DHE, + RSA, + ECDH, + ECDHE, +} + +// We don't support arbitrary curves. It's a terrible +// idea and unnecessary attack surface. Please, +// get a grip. +#[derive(Debug)] +pub struct ECParameters { + pub curve_type: ECCurveType, + pub named_group: NamedGroup, +} + +impl Codec for ECParameters { + fn encode(&self, bytes: &mut Vec) { + self.curve_type.encode(bytes); + self.named_group.encode(bytes); + } + + fn read(r: &mut Reader) -> Option { + let ct = ECCurveType::read(r)?; + + if ct != ECCurveType::NamedCurve { + return None; + } + + let grp = NamedGroup::read(r)?; + + Some(Self { + curve_type: ct, + named_group: grp, + }) + } +} + +#[derive(Debug, Clone)] +pub struct DigitallySignedStruct { + pub scheme: SignatureScheme, + #[deprecated(since = "0.20.7", note = "Use signature() accessor")] + pub sig: PayloadU16, +} + +impl DigitallySignedStruct { + #![allow(deprecated)] + pub fn new(scheme: SignatureScheme, sig: Vec) -> Self { + Self { + scheme, + sig: PayloadU16::new(sig), + } + } + + pub fn signature(&self) -> &[u8] { + &self.sig.0 + } +} + +impl Codec for DigitallySignedStruct { + #![allow(deprecated)] + fn encode(&self, bytes: &mut Vec) { + self.scheme.encode(bytes); + self.sig.encode(bytes); + } + + fn read(r: &mut Reader) -> Option { + let scheme = SignatureScheme::read(r)?; + let sig = PayloadU16::read(r)?; + + Some(Self { scheme, sig }) + } +} + +#[derive(Debug)] +pub struct ClientECDHParams { + pub public: PayloadU8, +} + +impl Codec for ClientECDHParams { + fn encode(&self, bytes: &mut Vec) { + self.public.encode(bytes); + } + + fn read(r: &mut Reader) -> Option { + let pb = PayloadU8::read(r)?; + Some(Self { public: pb }) + } +} + +#[derive(Debug)] +pub struct ServerECDHParams { + pub curve_params: ECParameters, + pub public: PayloadU8, +} + +impl ServerECDHParams { + pub fn new(named_group: NamedGroup, pubkey: &[u8]) -> Self { + Self { + curve_params: ECParameters { + curve_type: ECCurveType::NamedCurve, + named_group, + }, + public: PayloadU8::new(pubkey.to_vec()), + } + } +} + +impl Codec for ServerECDHParams { + fn encode(&self, bytes: &mut Vec) { + self.curve_params.encode(bytes); + self.public.encode(bytes); + } + + fn read(r: &mut Reader) -> Option { + let cp = ECParameters::read(r)?; + let pb = PayloadU8::read(r)?; + + Some(Self { + curve_params: cp, + public: pb, + }) + } +} + +#[derive(Debug)] +pub struct ECDHEServerKeyExchange { + pub params: ServerECDHParams, + pub dss: DigitallySignedStruct, +} + +impl Codec for ECDHEServerKeyExchange { + fn encode(&self, bytes: &mut Vec) { + self.params.encode(bytes); + self.dss.encode(bytes); + } + + fn read(r: &mut Reader) -> Option { + let params = ServerECDHParams::read(r)?; + let dss = DigitallySignedStruct::read(r)?; + + Some(Self { params, dss }) + } +} + +#[derive(Debug)] +pub enum ServerKeyExchangePayload { + ECDHE(ECDHEServerKeyExchange), + Unknown(Payload), +} + +impl Codec for ServerKeyExchangePayload { + fn encode(&self, bytes: &mut Vec) { + match *self { + Self::ECDHE(ref x) => x.encode(bytes), + Self::Unknown(ref x) => x.encode(bytes), + } + } + + fn read(r: &mut Reader) -> Option { + // read as Unknown, fully parse when we know the + // KeyExchangeAlgorithm + Some(Self::Unknown(Payload::read(r))) + } +} + +impl ServerKeyExchangePayload { + pub fn unwrap_given_kxa(&self, kxa: &KeyExchangeAlgorithm) -> Option { + if let Self::Unknown(ref unk) = *self { + let mut rd = Reader::init(&unk.0); + + let result = match *kxa { + KeyExchangeAlgorithm::ECDHE => ECDHEServerKeyExchange::read(&mut rd), + _ => None, + }; + + if !rd.any_left() { + return result; + }; + } + + None + } +} + +// -- EncryptedExtensions (TLS1.3 only) -- +declare_u16_vec!(EncryptedExtensions, ServerExtension); + +pub trait HasServerExtensions { + fn get_extensions(&self) -> &[ServerExtension]; + + /// Returns true if there is more than one extension of a given + /// type. + fn has_duplicate_extension(&self) -> bool { + let mut seen = collections::HashSet::new(); + + for ext in self.get_extensions() { + let typ = ext.get_type().get_u16(); + + if seen.contains(&typ) { + return true; + } + seen.insert(typ); + } + + false + } + + fn find_extension(&self, ext: ExtensionType) -> Option<&ServerExtension> { + self.get_extensions() + .iter() + .find(|x| x.get_type() == ext) + } + + fn get_alpn_protocol(&self) -> Option<&[u8]> { + let ext = self.find_extension(ExtensionType::ALProtocolNegotiation)?; + match *ext { + ServerExtension::Protocols(ref protos) => protos.as_single_slice(), + _ => None, + } + } + + fn get_quic_params_extension(&self) -> Option> { + let ext = self + .find_extension(ExtensionType::TransportParameters) + .or_else(|| self.find_extension(ExtensionType::TransportParametersDraft))?; + match *ext { + ServerExtension::TransportParameters(ref bytes) + | ServerExtension::TransportParametersDraft(ref bytes) => Some(bytes.to_vec()), + _ => None, + } + } + + fn early_data_extension_offered(&self) -> bool { + self.find_extension(ExtensionType::EarlyData) + .is_some() + } +} + +impl HasServerExtensions for EncryptedExtensions { + fn get_extensions(&self) -> &[ServerExtension] { + self + } +} + +// -- CertificateRequest and sundries -- +declare_u8_vec!(ClientCertificateTypes, ClientCertificateType); +pub type DistinguishedName = PayloadU16; +/// DistinguishedNames is a `Vec>` wrapped in internal types. Each element contains the +/// DER or BER encoded [`Subject` field from RFC 5280](https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.6) +/// for a single certificate. The Subject field is +/// [encoded as an RFC 5280 `Name`](https://datatracker.ietf.org/doc/html/rfc5280#page-116). +/// It can be decoded using [x509-parser's FromDer trait](https://docs.rs/x509-parser/latest/x509_parser/traits/trait.FromDer.html). +/// +/// ```ignore +/// for name in distinguished_names { +/// use x509_parser::traits::FromDer; +/// println!("{}", x509_parser::x509::X509Name::from_der(&name.0)?.1); +/// } +/// ``` +pub type DistinguishedNames = VecU16OfPayloadU16; + +#[derive(Debug)] +pub struct CertificateRequestPayload { + pub certtypes: ClientCertificateTypes, + pub sigschemes: SupportedSignatureSchemes, + pub canames: DistinguishedNames, +} + +impl Codec for CertificateRequestPayload { + fn encode(&self, bytes: &mut Vec) { + self.certtypes.encode(bytes); + self.sigschemes.encode(bytes); + self.canames.encode(bytes); + } + + fn read(r: &mut Reader) -> Option { + let certtypes = ClientCertificateTypes::read(r)?; + let sigschemes = SupportedSignatureSchemes::read(r)?; + let canames = DistinguishedNames::read(r)?; + + if sigschemes.is_empty() { + warn!("meaningless CertificateRequest message"); + None + } else { + Some(Self { + certtypes, + sigschemes, + canames, + }) + } + } +} + +#[derive(Debug)] +pub enum CertReqExtension { + SignatureAlgorithms(SupportedSignatureSchemes), + AuthorityNames(DistinguishedNames), + Unknown(UnknownExtension), +} + +impl CertReqExtension { + pub fn get_type(&self) -> ExtensionType { + match *self { + Self::SignatureAlgorithms(_) => ExtensionType::SignatureAlgorithms, + Self::AuthorityNames(_) => ExtensionType::CertificateAuthorities, + Self::Unknown(ref r) => r.typ, + } + } +} + +impl Codec for CertReqExtension { + fn encode(&self, bytes: &mut Vec) { + self.get_type().encode(bytes); + + let mut sub: Vec = Vec::new(); + match *self { + Self::SignatureAlgorithms(ref r) => r.encode(&mut sub), + Self::AuthorityNames(ref r) => r.encode(&mut sub), + Self::Unknown(ref r) => r.encode(&mut sub), + } + + (sub.len() as u16).encode(bytes); + bytes.append(&mut sub); + } + + fn read(r: &mut Reader) -> Option { + let typ = ExtensionType::read(r)?; + let len = u16::read(r)? as usize; + let mut sub = r.sub(len)?; + + let ext = match typ { + ExtensionType::SignatureAlgorithms => { + let schemes = SupportedSignatureSchemes::read(&mut sub)?; + if schemes.is_empty() { + return None; + } + Self::SignatureAlgorithms(schemes) + } + ExtensionType::CertificateAuthorities => { + let cas = DistinguishedNames::read(&mut sub)?; + Self::AuthorityNames(cas) + } + _ => Self::Unknown(UnknownExtension::read(typ, &mut sub)), + }; + + if sub.any_left() { + None + } else { + Some(ext) + } + } +} + +declare_u16_vec!(CertReqExtensions, CertReqExtension); + +#[derive(Debug)] +pub struct CertificateRequestPayloadTLS13 { + pub context: PayloadU8, + pub extensions: CertReqExtensions, +} + +impl Codec for CertificateRequestPayloadTLS13 { + fn encode(&self, bytes: &mut Vec) { + self.context.encode(bytes); + self.extensions.encode(bytes); + } + + fn read(r: &mut Reader) -> Option { + let context = PayloadU8::read(r)?; + let extensions = CertReqExtensions::read(r)?; + + Some(Self { + context, + extensions, + }) + } +} + +impl CertificateRequestPayloadTLS13 { + pub fn find_extension(&self, ext: ExtensionType) -> Option<&CertReqExtension> { + self.extensions + .iter() + .find(|x| x.get_type() == ext) + } + + pub fn get_sigalgs_extension(&self) -> Option<&SupportedSignatureSchemes> { + let ext = self.find_extension(ExtensionType::SignatureAlgorithms)?; + match *ext { + CertReqExtension::SignatureAlgorithms(ref sa) => Some(sa), + _ => None, + } + } + + pub fn get_authorities_extension(&self) -> Option<&DistinguishedNames> { + let ext = self.find_extension(ExtensionType::CertificateAuthorities)?; + match *ext { + CertReqExtension::AuthorityNames(ref an) => Some(an), + _ => None, + } + } +} + +// -- NewSessionTicket -- +#[derive(Debug)] +pub struct NewSessionTicketPayload { + pub lifetime_hint: u32, + pub ticket: PayloadU16, +} + +impl NewSessionTicketPayload { + pub fn new(lifetime_hint: u32, ticket: Vec) -> Self { + Self { + lifetime_hint, + ticket: PayloadU16::new(ticket), + } + } +} + +impl Codec for NewSessionTicketPayload { + fn encode(&self, bytes: &mut Vec) { + self.lifetime_hint.encode(bytes); + self.ticket.encode(bytes); + } + + fn read(r: &mut Reader) -> Option { + let lifetime = u32::read(r)?; + let ticket = PayloadU16::read(r)?; + + Some(Self { + lifetime_hint: lifetime, + ticket, + }) + } +} + +// -- NewSessionTicket electric boogaloo -- +#[derive(Debug)] +pub enum NewSessionTicketExtension { + EarlyData(u32), + Unknown(UnknownExtension), +} + +impl NewSessionTicketExtension { + pub fn get_type(&self) -> ExtensionType { + match *self { + Self::EarlyData(_) => ExtensionType::EarlyData, + Self::Unknown(ref r) => r.typ, + } + } +} + +impl Codec for NewSessionTicketExtension { + fn encode(&self, bytes: &mut Vec) { + self.get_type().encode(bytes); + + let mut sub: Vec = Vec::new(); + match *self { + Self::EarlyData(r) => r.encode(&mut sub), + Self::Unknown(ref r) => r.encode(&mut sub), + } + + (sub.len() as u16).encode(bytes); + bytes.append(&mut sub); + } + + fn read(r: &mut Reader) -> Option { + let typ = ExtensionType::read(r)?; + let len = u16::read(r)? as usize; + let mut sub = r.sub(len)?; + + let ext = match typ { + ExtensionType::EarlyData => Self::EarlyData(u32::read(&mut sub)?), + _ => Self::Unknown(UnknownExtension::read(typ, &mut sub)), + }; + + if sub.any_left() { + None + } else { + Some(ext) + } + } +} + +declare_u16_vec!(NewSessionTicketExtensions, NewSessionTicketExtension); + +#[derive(Debug)] +pub struct NewSessionTicketPayloadTLS13 { + pub lifetime: u32, + pub age_add: u32, + pub nonce: PayloadU8, + pub ticket: PayloadU16, + pub exts: NewSessionTicketExtensions, +} + +impl NewSessionTicketPayloadTLS13 { + pub fn new(lifetime: u32, age_add: u32, nonce: Vec, ticket: Vec) -> Self { + Self { + lifetime, + age_add, + nonce: PayloadU8::new(nonce), + ticket: PayloadU16::new(ticket), + exts: vec![], + } + } + + pub fn has_duplicate_extension(&self) -> bool { + let mut seen = collections::HashSet::new(); + + for ext in &self.exts { + let typ = ext.get_type().get_u16(); + + if seen.contains(&typ) { + return true; + } + seen.insert(typ); + } + + false + } + + pub fn find_extension(&self, ext: ExtensionType) -> Option<&NewSessionTicketExtension> { + self.exts + .iter() + .find(|x| x.get_type() == ext) + } + + pub fn get_max_early_data_size(&self) -> Option { + let ext = self.find_extension(ExtensionType::EarlyData)?; + match *ext { + NewSessionTicketExtension::EarlyData(ref sz) => Some(*sz), + _ => None, + } + } +} + +impl Codec for NewSessionTicketPayloadTLS13 { + fn encode(&self, bytes: &mut Vec) { + self.lifetime.encode(bytes); + self.age_add.encode(bytes); + self.nonce.encode(bytes); + self.ticket.encode(bytes); + self.exts.encode(bytes); + } + + fn read(r: &mut Reader) -> Option { + let lifetime = u32::read(r)?; + let age_add = u32::read(r)?; + let nonce = PayloadU8::read(r)?; + let ticket = PayloadU16::read(r)?; + let exts = NewSessionTicketExtensions::read(r)?; + + Some(Self { + lifetime, + age_add, + nonce, + ticket, + exts, + }) + } +} + +// -- RFC6066 certificate status types + +/// Only supports OCSP +#[derive(Debug)] +pub struct CertificateStatus { + pub ocsp_response: PayloadU24, +} + +impl Codec for CertificateStatus { + fn encode(&self, bytes: &mut Vec) { + CertificateStatusType::OCSP.encode(bytes); + self.ocsp_response.encode(bytes); + } + + fn read(r: &mut Reader) -> Option { + let typ = CertificateStatusType::read(r)?; + + match typ { + CertificateStatusType::OCSP => Some(Self { + ocsp_response: PayloadU24::read(r)?, + }), + _ => None, + } + } +} + +impl CertificateStatus { + pub fn new(ocsp: Vec) -> Self { + Self { + ocsp_response: PayloadU24::new(ocsp), + } + } + + pub fn into_inner(self) -> Vec { + self.ocsp_response.0 + } +} + +#[derive(Debug)] +pub enum HandshakePayload { + HelloRequest, + ClientHello(ClientHelloPayload), + ServerHello(ServerHelloPayload), + HelloRetryRequest(HelloRetryRequest), + Certificate(CertificatePayload), + CertificateTLS13(CertificatePayloadTLS13), + ServerKeyExchange(ServerKeyExchangePayload), + CertificateRequest(CertificateRequestPayload), + CertificateRequestTLS13(CertificateRequestPayloadTLS13), + CertificateVerify(DigitallySignedStruct), + ServerHelloDone, + EndOfEarlyData, + ClientKeyExchange(Payload), + NewSessionTicket(NewSessionTicketPayload), + NewSessionTicketTLS13(NewSessionTicketPayloadTLS13), + EncryptedExtensions(EncryptedExtensions), + KeyUpdate(KeyUpdateRequest), + Finished(Payload), + CertificateStatus(CertificateStatus), + MessageHash(Payload), + Unknown(Payload), +} + +impl HandshakePayload { + fn encode(&self, bytes: &mut Vec) { + use self::HandshakePayload::*; + match *self { + HelloRequest | ServerHelloDone | EndOfEarlyData => {} + ClientHello(ref x) => x.encode(bytes), + ServerHello(ref x) => x.encode(bytes), + HelloRetryRequest(ref x) => x.encode(bytes), + Certificate(ref x) => x.encode(bytes), + CertificateTLS13(ref x) => x.encode(bytes), + ServerKeyExchange(ref x) => x.encode(bytes), + ClientKeyExchange(ref x) => x.encode(bytes), + CertificateRequest(ref x) => x.encode(bytes), + CertificateRequestTLS13(ref x) => x.encode(bytes), + CertificateVerify(ref x) => x.encode(bytes), + NewSessionTicket(ref x) => x.encode(bytes), + NewSessionTicketTLS13(ref x) => x.encode(bytes), + EncryptedExtensions(ref x) => x.encode(bytes), + KeyUpdate(ref x) => x.encode(bytes), + Finished(ref x) => x.encode(bytes), + CertificateStatus(ref x) => x.encode(bytes), + MessageHash(ref x) => x.encode(bytes), + Unknown(ref x) => x.encode(bytes), + } + } +} + +#[derive(Debug)] +pub struct HandshakeMessagePayload { + pub typ: HandshakeType, + pub payload: HandshakePayload, +} + +impl Codec for HandshakeMessagePayload { + fn encode(&self, bytes: &mut Vec) { + // encode payload to learn length + let mut sub: Vec = Vec::new(); + self.payload.encode(&mut sub); + + // output type, length, and encoded payload + match self.typ { + HandshakeType::HelloRetryRequest => HandshakeType::ServerHello, + _ => self.typ, + } + .encode(bytes); + codec::u24(sub.len() as u32).encode(bytes); + bytes.append(&mut sub); + } + + fn read(r: &mut Reader) -> Option { + Self::read_version(r, ProtocolVersion::TLSv1_2) + } +} + +impl HandshakeMessagePayload { + pub fn read_version(r: &mut Reader, vers: ProtocolVersion) -> Option { + let mut typ = HandshakeType::read(r)?; + let len = codec::u24::read(r)?.0 as usize; + let mut sub = r.sub(len)?; + + let payload = match typ { + HandshakeType::HelloRequest if sub.left() == 0 => HandshakePayload::HelloRequest, + HandshakeType::ClientHello => { + HandshakePayload::ClientHello(ClientHelloPayload::read(&mut sub)?) + } + HandshakeType::ServerHello => { + let version = ProtocolVersion::read(&mut sub)?; + let random = Random::read(&mut sub)?; + + if random == HELLO_RETRY_REQUEST_RANDOM { + let mut hrr = HelloRetryRequest::read(&mut sub)?; + hrr.legacy_version = version; + typ = HandshakeType::HelloRetryRequest; + HandshakePayload::HelloRetryRequest(hrr) + } else { + let mut shp = ServerHelloPayload::read(&mut sub)?; + shp.legacy_version = version; + shp.random = random; + HandshakePayload::ServerHello(shp) + } + } + HandshakeType::Certificate if vers == ProtocolVersion::TLSv1_3 => { + let p = CertificatePayloadTLS13::read(&mut sub)?; + HandshakePayload::CertificateTLS13(p) + } + HandshakeType::Certificate => { + HandshakePayload::Certificate(CertificatePayload::read(&mut sub)?) + } + HandshakeType::ServerKeyExchange => { + let p = ServerKeyExchangePayload::read(&mut sub)?; + HandshakePayload::ServerKeyExchange(p) + } + HandshakeType::ServerHelloDone => { + if sub.any_left() { + return None; + } + HandshakePayload::ServerHelloDone + } + HandshakeType::ClientKeyExchange => { + HandshakePayload::ClientKeyExchange(Payload::read(&mut sub)) + } + HandshakeType::CertificateRequest if vers == ProtocolVersion::TLSv1_3 => { + let p = CertificateRequestPayloadTLS13::read(&mut sub)?; + HandshakePayload::CertificateRequestTLS13(p) + } + HandshakeType::CertificateRequest => { + let p = CertificateRequestPayload::read(&mut sub)?; + HandshakePayload::CertificateRequest(p) + } + HandshakeType::CertificateVerify => { + HandshakePayload::CertificateVerify(DigitallySignedStruct::read(&mut sub)?) + } + HandshakeType::NewSessionTicket if vers == ProtocolVersion::TLSv1_3 => { + let p = NewSessionTicketPayloadTLS13::read(&mut sub)?; + HandshakePayload::NewSessionTicketTLS13(p) + } + HandshakeType::NewSessionTicket => { + let p = NewSessionTicketPayload::read(&mut sub)?; + HandshakePayload::NewSessionTicket(p) + } + HandshakeType::EncryptedExtensions => { + HandshakePayload::EncryptedExtensions(EncryptedExtensions::read(&mut sub)?) + } + HandshakeType::KeyUpdate => { + HandshakePayload::KeyUpdate(KeyUpdateRequest::read(&mut sub)?) + } + HandshakeType::EndOfEarlyData => { + if sub.any_left() { + return None; + } + HandshakePayload::EndOfEarlyData + } + HandshakeType::Finished => HandshakePayload::Finished(Payload::read(&mut sub)), + HandshakeType::CertificateStatus => { + HandshakePayload::CertificateStatus(CertificateStatus::read(&mut sub)?) + } + HandshakeType::MessageHash => { + // does not appear on the wire + return None; + } + HandshakeType::HelloRetryRequest => { + // not legal on wire + return None; + } + _ => HandshakePayload::Unknown(Payload::read(&mut sub)), + }; + + if sub.any_left() { + None + } else { + Some(Self { typ, payload }) + } + } + + pub fn build_key_update_notify() -> Self { + Self { + typ: HandshakeType::KeyUpdate, + payload: HandshakePayload::KeyUpdate(KeyUpdateRequest::UpdateNotRequested), + } + } + + pub fn get_encoding_for_binder_signing(&self) -> Vec { + let mut ret = self.get_encoding(); + + let binder_len = match self.payload { + HandshakePayload::ClientHello(ref ch) => match ch.extensions.last() { + Some(ClientExtension::PresharedKey(ref offer)) => { + let mut binders_encoding = Vec::new(); + offer + .binders + .encode(&mut binders_encoding); + binders_encoding.len() + } + _ => 0, + }, + _ => 0, + }; + + let ret_len = ret.len() - binder_len; + ret.truncate(ret_len); + ret + } + + pub fn build_handshake_hash(hash: &[u8]) -> Self { + Self { + typ: HandshakeType::MessageHash, + payload: HandshakePayload::MessageHash(Payload::new(hash.to_vec())), + } + } +} diff --git a/third_party/rustls-fork-shadow-tls/src/msgs/handshake_test.rs b/third_party/rustls-fork-shadow-tls/src/msgs/handshake_test.rs new file mode 100644 index 0000000..3e19cc1 --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/src/msgs/handshake_test.rs @@ -0,0 +1,1247 @@ +use crate::enums::{CipherSuite, ProtocolVersion, SignatureScheme}; +use crate::key::Certificate; +use crate::msgs::base::{Payload, PayloadU16, PayloadU24, PayloadU8}; +use crate::msgs::codec::{put_u16, Codec, Reader}; +use crate::msgs::enums::{ + ClientCertificateType, Compression, ECCurveType, ExtensionType, HandshakeType, HashAlgorithm, + KeyUpdateRequest, NamedGroup, PSKKeyExchangeMode, ServerNameType, SignatureAlgorithm, +}; +use crate::msgs::handshake::{ + CertReqExtension, CertificateEntry, CertificateExtension, CertificatePayloadTLS13, + CertificateRequestPayload, CertificateRequestPayloadTLS13, CertificateStatus, + CertificateStatusRequest, ClientExtension, ClientHelloPayload, ClientSessionTicket, + ConvertProtocolNameList, ConvertServerNameList, DecomposedSignatureScheme, + DigitallySignedStruct, ECDHEServerKeyExchange, ECParameters, ECPointFormatList, + EncryptedExtensions, HandshakeMessagePayload, HandshakePayload, HasServerExtensions, + HelloRetryExtension, HelloRetryRequest, KeyShareEntry, NewSessionTicketExtension, + NewSessionTicketPayload, NewSessionTicketPayloadTLS13, PresharedKeyBinder, + PresharedKeyIdentity, PresharedKeyOffer, Random, ServerECDHParams, ServerExtension, + ServerHelloPayload, ServerKeyExchangePayload, SessionID, SupportedPointFormats, + UnknownExtension, +}; +use webpki::DnsNameRef; + +#[test] +fn rejects_short_random() { + let bytes = [0x01; 31]; + let mut rd = Reader::init(&bytes); + assert_eq!(Random::read(&mut rd), None); +} + +#[test] +fn reads_random() { + let bytes = [0x01; 32]; + let mut rd = Reader::init(&bytes); + let rnd = Random::read(&mut rd).unwrap(); + println!("{:?}", rnd); + + assert!(!rd.any_left()); +} + +#[test] +fn debug_random() { + assert_eq!( + "0101010101010101010101010101010101010101010101010101010101010101", + format!("{:?}", Random::from([1; 32])) + ); +} + +#[test] +fn rejects_truncated_sessionid() { + let bytes = [32; 32]; + let mut rd = Reader::init(&bytes); + assert_eq!(SessionID::read(&mut rd), None); +} + +#[test] +fn rejects_sessionid_with_bad_length() { + let bytes = [33; 33]; + let mut rd = Reader::init(&bytes); + assert_eq!(SessionID::read(&mut rd), None); +} + +#[test] +fn sessionid_with_different_lengths_are_unequal() { + let a = SessionID::read(&mut Reader::init(&[1u8, 1])).unwrap(); + let b = SessionID::read(&mut Reader::init(&[2u8, 1, 2])).unwrap(); + assert_ne!(a, b); +} + +#[test] +fn accepts_short_sessionid() { + let bytes = [1; 2]; + let mut rd = Reader::init(&bytes); + let sess = SessionID::read(&mut rd).unwrap(); + println!("{:?}", sess); + + assert!(!sess.is_empty()); + assert_eq!(sess.len(), 1); + assert!(!rd.any_left()); +} + +#[test] +fn accepts_empty_sessionid() { + let bytes = [0; 1]; + let mut rd = Reader::init(&bytes); + let sess = SessionID::read(&mut rd).unwrap(); + println!("{:?}", sess); + + assert!(sess.is_empty()); + assert_eq!(sess.len(), 0); + assert!(!rd.any_left()); +} + +#[test] +fn debug_sessionid() { + let bytes = [ + 32, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, + ]; + let mut rd = Reader::init(&bytes); + let sess = SessionID::read(&mut rd).unwrap(); + assert_eq!( + "0101010101010101010101010101010101010101010101010101010101010101", + format!("{:?}", sess) + ); +} + +#[test] +fn can_roundtrip_unknown_client_ext() { + let bytes = [0x12u8, 0x34u8, 0, 3, 1, 2, 3]; + let mut rd = Reader::init(&bytes); + let ext = ClientExtension::read(&mut rd).unwrap(); + + println!("{:?}", ext); + assert_eq!(ext.get_type(), ExtensionType::Unknown(0x1234)); + assert_eq!(bytes.to_vec(), ext.get_encoding()); +} + +#[test] +fn refuses_client_ext_with_unparsed_bytes() { + let bytes = [0x00u8, 0x0b, 0x00, 0x04, 0x02, 0xf8, 0x01, 0x02]; + let mut rd = Reader::init(&bytes); + assert!(ClientExtension::read(&mut rd).is_none()); +} + +#[test] +fn refuses_server_ext_with_unparsed_bytes() { + let bytes = [0x00u8, 0x0b, 0x00, 0x04, 0x02, 0xf8, 0x01, 0x02]; + let mut rd = Reader::init(&bytes); + assert!(ServerExtension::read(&mut rd).is_none()); +} + +#[test] +fn refuses_certificate_ext_with_unparsed_bytes() { + let bytes = [0x00u8, 0x12, 0x00, 0x03, 0x00, 0x00, 0x01]; + let mut rd = Reader::init(&bytes); + assert!(CertificateExtension::read(&mut rd).is_none()); +} + +#[test] +fn refuses_certificate_req_ext_with_unparsed_bytes() { + let bytes = [0x00u8, 0x0d, 0x00, 0x05, 0x00, 0x02, 0x01, 0x02, 0xff]; + let mut rd = Reader::init(&bytes); + assert!(CertReqExtension::read(&mut rd).is_none()); +} + +#[test] +fn refuses_helloreq_ext_with_unparsed_bytes() { + let bytes = [0x00u8, 0x2b, 0x00, 0x03, 0x00, 0x00, 0x01]; + let mut rd = Reader::init(&bytes); + assert!(HelloRetryExtension::read(&mut rd).is_none()); +} + +#[test] +fn refuses_newsessionticket_ext_with_unparsed_bytes() { + let bytes = [0x00u8, 0x2a, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x01]; + let mut rd = Reader::init(&bytes); + assert!(NewSessionTicketExtension::read(&mut rd).is_none()); +} + +#[test] +fn can_roundtrip_single_sni() { + let bytes = [0, 0, 0, 7, 0, 5, 0, 0, 2, 0x6c, 0x6f]; + let mut rd = Reader::init(&bytes); + let ext = ClientExtension::read(&mut rd).unwrap(); + println!("{:?}", ext); + + assert_eq!(ext.get_type(), ExtensionType::ServerName); + assert_eq!(bytes.to_vec(), ext.get_encoding()); +} + +#[test] +fn can_round_trip_mixed_case_sni() { + let bytes = [0, 0, 0, 7, 0, 5, 0, 0, 2, 0x4c, 0x6f]; + let mut rd = Reader::init(&bytes); + let ext = ClientExtension::read(&mut rd).unwrap(); + println!("{:?}", ext); + + assert_eq!(ext.get_type(), ExtensionType::ServerName); + assert_eq!(bytes.to_vec(), ext.get_encoding()); +} + +#[test] +fn can_roundtrip_other_sni_name_types() { + let bytes = [0, 0, 0, 7, 0, 5, 1, 0, 2, 0x6c, 0x6f]; + let mut rd = Reader::init(&bytes); + let ext = ClientExtension::read(&mut rd).unwrap(); + println!("{:?}", ext); + + assert_eq!(ext.get_type(), ExtensionType::ServerName); + assert_eq!(bytes.to_vec(), ext.get_encoding()); +} + +#[test] +fn get_single_hostname_returns_none_for_other_sni_name_types() { + let bytes = [0, 0, 0, 7, 0, 5, 1, 0, 2, 0x6c, 0x6f]; + let mut rd = Reader::init(&bytes); + let ext = ClientExtension::read(&mut rd).unwrap(); + println!("{:?}", ext); + + assert_eq!(ext.get_type(), ExtensionType::ServerName); + if let ClientExtension::ServerName(snr) = ext { + assert!(!snr.has_duplicate_names_for_type()); + assert!(snr.get_single_hostname().is_none()); + } else { + unreachable!(); + } +} + +#[test] +fn can_roundtrip_multiname_sni() { + let bytes = [0, 0, 0, 12, 0, 10, 0, 0, 2, 0x68, 0x69, 0, 0, 2, 0x6c, 0x6f]; + let mut rd = Reader::init(&bytes); + let ext = ClientExtension::read(&mut rd).unwrap(); + println!("{:?}", ext); + + assert_eq!(ext.get_type(), ExtensionType::ServerName); + assert_eq!(bytes.to_vec(), ext.get_encoding()); + match ext { + ClientExtension::ServerName(req) => { + assert_eq!(2, req.len()); + + assert!(req.has_duplicate_names_for_type()); + + let dns_name_str: &str = req + .get_single_hostname() + .unwrap() + .into(); + assert_eq!(dns_name_str, "hi"); + + assert_eq!(req[0].typ, ServerNameType::HostName); + assert_eq!(req[1].typ, ServerNameType::HostName); + } + _ => unreachable!(), + } +} + +#[test] +fn rejects_truncated_sni() { + let bytes = [0, 0, 0, 1, 0]; + assert!(ClientExtension::read(&mut Reader::init(&bytes)).is_none()); + + let bytes = [0, 0, 0, 2, 0, 1]; + assert!(ClientExtension::read(&mut Reader::init(&bytes)).is_none()); + + let bytes = [0, 0, 0, 3, 0, 1, 0]; + assert!(ClientExtension::read(&mut Reader::init(&bytes)).is_none()); + + let bytes = [0, 0, 0, 4, 0, 2, 0, 0]; + assert!(ClientExtension::read(&mut Reader::init(&bytes)).is_none()); + + let bytes = [0, 0, 0, 5, 0, 3, 0, 0, 0]; + assert!(ClientExtension::read(&mut Reader::init(&bytes)).is_none()); + + let bytes = [0, 0, 0, 5, 0, 3, 0, 0, 1]; + assert!(ClientExtension::read(&mut Reader::init(&bytes)).is_none()); + + let bytes = [0, 0, 0, 6, 0, 4, 0, 0, 2, 0x68]; + assert!(ClientExtension::read(&mut Reader::init(&bytes)).is_none()); +} + +#[test] +fn can_roundtrip_psk_identity() { + let bytes = [0, 0, 0x11, 0x22, 0x33, 0x44]; + let psk_id = PresharedKeyIdentity::read(&mut Reader::init(&bytes)).unwrap(); + println!("{:?}", psk_id); + assert_eq!(psk_id.obfuscated_ticket_age, 0x11223344); + assert_eq!(psk_id.get_encoding(), bytes.to_vec()); + + let bytes = [0, 5, 0x1, 0x2, 0x3, 0x4, 0x5, 0x11, 0x22, 0x33, 0x44]; + let psk_id = PresharedKeyIdentity::read(&mut Reader::init(&bytes)).unwrap(); + println!("{:?}", psk_id); + assert_eq!(psk_id.identity.0, vec![0x1, 0x2, 0x3, 0x4, 0x5]); + assert_eq!(psk_id.obfuscated_ticket_age, 0x11223344); + assert_eq!(psk_id.get_encoding(), bytes.to_vec()); +} + +#[test] +fn can_roundtrip_psk_offer() { + let bytes = [ + 0, 7, 0, 1, 0x99, 0x11, 0x22, 0x33, 0x44, 0, 4, 3, 0x01, 0x02, 0x3, + ]; + let psko = PresharedKeyOffer::read(&mut Reader::init(&bytes)).unwrap(); + println!("{:?}", psko); + + assert_eq!(psko.identities.len(), 1); + assert_eq!(psko.identities[0].identity.0, vec![0x99]); + assert_eq!(psko.identities[0].obfuscated_ticket_age, 0x11223344); + assert_eq!(psko.binders.len(), 1); + assert_eq!(psko.binders[0].0, vec![1, 2, 3]); + assert_eq!(psko.get_encoding(), bytes.to_vec()); +} + +#[test] +fn can_roundtrip_certstatusreq_for_ocsp() { + let ext = ClientExtension::CertificateStatusRequest(CertificateStatusRequest::build_ocsp()); + println!("{:?}", ext); + + let bytes = [ + 0, 5, // CertificateStatusRequest + 0, 11, 1, // OCSP + 0, 5, 0, 3, 0, 1, 1, 0, 1, 2, + ]; + + let csr = ClientExtension::read(&mut Reader::init(&bytes)).unwrap(); + println!("{:?}", csr); + assert_eq!(csr.get_encoding(), bytes.to_vec()); +} + +#[test] +fn can_roundtrip_certstatusreq_for_other() { + let bytes = [ + 0, 5, // CertificateStatusRequest + 0, 5, 2, // !OCSP + 1, 2, 3, 4, + ]; + + let csr = ClientExtension::read(&mut Reader::init(&bytes)).unwrap(); + println!("{:?}", csr); + assert_eq!(csr.get_encoding(), bytes.to_vec()); +} + +#[test] +fn can_roundtrip_multi_proto() { + let bytes = [0, 16, 0, 8, 0, 6, 2, 0x68, 0x69, 2, 0x6c, 0x6f]; + let mut rd = Reader::init(&bytes); + let ext = ClientExtension::read(&mut rd).unwrap(); + println!("{:?}", ext); + + assert_eq!(ext.get_type(), ExtensionType::ALProtocolNegotiation); + assert_eq!(ext.get_encoding(), bytes.to_vec()); + match ext { + ClientExtension::Protocols(prot) => { + assert_eq!(2, prot.len()); + assert_eq!(vec![b"hi", b"lo"], prot.to_slices()); + assert_eq!(prot.as_single_slice(), None); + } + _ => unreachable!(), + } +} + +#[test] +fn can_roundtrip_single_proto() { + let bytes = [0, 16, 0, 5, 0, 3, 2, 0x68, 0x69]; + let mut rd = Reader::init(&bytes); + let ext = ClientExtension::read(&mut rd).unwrap(); + println!("{:?}", ext); + + assert_eq!(ext.get_type(), ExtensionType::ALProtocolNegotiation); + assert_eq!(bytes.to_vec(), ext.get_encoding()); + match ext { + ClientExtension::Protocols(prot) => { + assert_eq!(1, prot.len()); + assert_eq!(vec![b"hi"], prot.to_slices()); + assert_eq!(prot.as_single_slice(), Some(&b"hi"[..])); + } + _ => unreachable!(), + } +} + +#[test] +fn decomposed_signature_scheme_has_correct_mappings() { + assert_eq!( + SignatureScheme::make(SignatureAlgorithm::RSA, HashAlgorithm::SHA1), + SignatureScheme::RSA_PKCS1_SHA1 + ); + assert_eq!( + SignatureScheme::make(SignatureAlgorithm::RSA, HashAlgorithm::SHA256), + SignatureScheme::RSA_PKCS1_SHA256 + ); + assert_eq!( + SignatureScheme::make(SignatureAlgorithm::RSA, HashAlgorithm::SHA384), + SignatureScheme::RSA_PKCS1_SHA384 + ); + assert_eq!( + SignatureScheme::make(SignatureAlgorithm::RSA, HashAlgorithm::SHA512), + SignatureScheme::RSA_PKCS1_SHA512 + ); + + assert_eq!( + SignatureScheme::make(SignatureAlgorithm::ECDSA, HashAlgorithm::SHA256), + SignatureScheme::ECDSA_NISTP256_SHA256 + ); + assert_eq!( + SignatureScheme::make(SignatureAlgorithm::ECDSA, HashAlgorithm::SHA384), + SignatureScheme::ECDSA_NISTP384_SHA384 + ); + assert_eq!( + SignatureScheme::make(SignatureAlgorithm::ECDSA, HashAlgorithm::SHA512), + SignatureScheme::ECDSA_NISTP521_SHA512 + ); +} + +fn get_sample_clienthellopayload() -> ClientHelloPayload { + ClientHelloPayload { + client_version: ProtocolVersion::TLSv1_2, + random: Random::from([0; 32]), + session_id: SessionID::empty(), + cipher_suites: vec![CipherSuite::TLS_NULL_WITH_NULL_NULL], + compression_methods: vec![Compression::Null], + extensions: vec![ + ClientExtension::ECPointFormats(ECPointFormatList::supported()), + ClientExtension::NamedGroups(vec![NamedGroup::X25519]), + ClientExtension::SignatureAlgorithms(vec![SignatureScheme::ECDSA_NISTP256_SHA256]), + ClientExtension::make_sni(DnsNameRef::try_from_ascii_str("hello").unwrap()), + ClientExtension::SessionTicket(ClientSessionTicket::Request), + ClientExtension::SessionTicket(ClientSessionTicket::Offer(Payload(vec![]))), + ClientExtension::Protocols(vec![PayloadU8(vec![0])]), + ClientExtension::SupportedVersions(vec![ProtocolVersion::TLSv1_3]), + ClientExtension::KeyShare(vec![KeyShareEntry::new(NamedGroup::X25519, &[1, 2, 3])]), + ClientExtension::PresharedKeyModes(vec![PSKKeyExchangeMode::PSK_DHE_KE]), + ClientExtension::PresharedKey(PresharedKeyOffer { + identities: vec![ + PresharedKeyIdentity::new(vec![3, 4, 5], 123456), + PresharedKeyIdentity::new(vec![6, 7, 8], 7891011), + ], + binders: vec![ + PresharedKeyBinder::new(vec![1, 2, 3]), + PresharedKeyBinder::new(vec![3, 4, 5]), + ], + }), + ClientExtension::Cookie(PayloadU16(vec![1, 2, 3])), + ClientExtension::ExtendedMasterSecretRequest, + ClientExtension::CertificateStatusRequest(CertificateStatusRequest::build_ocsp()), + ClientExtension::SignedCertificateTimestampRequest, + ClientExtension::TransportParameters(vec![1, 2, 3]), + ClientExtension::Unknown(UnknownExtension { + typ: ExtensionType::Unknown(12345), + payload: Payload(vec![1, 2, 3]), + }), + ], + } +} + +#[test] +fn can_print_all_clientextensions() { + println!("client hello {:?}", get_sample_clienthellopayload()); +} + +#[test] +fn can_clone_all_clientextensions() { + let _ = get_sample_serverhellopayload().extensions; +} + +#[test] +fn client_has_duplicate_extensions_works() { + let mut chp = get_sample_clienthellopayload(); + assert!(chp.has_duplicate_extension()); // due to SessionTicketRequest/SessionTicketOffer + + chp.extensions.drain(1..); + assert!(!chp.has_duplicate_extension()); + + chp.extensions = vec![]; + assert!(!chp.has_duplicate_extension()); +} + +#[test] +fn test_truncated_psk_offer() { + let ext = ClientExtension::PresharedKey(PresharedKeyOffer { + identities: vec![PresharedKeyIdentity::new(vec![3, 4, 5], 123456)], + binders: vec![PresharedKeyBinder::new(vec![1, 2, 3])], + }); + + let mut enc = ext.get_encoding(); + println!("testing {:?} enc {:?}", ext, enc); + for l in 0..enc.len() { + if l == 9 { + continue; + } + put_u16(l as u16, &mut enc[4..]); + let rc = ClientExtension::read_bytes(&enc); + assert!(rc.is_none()); + } +} + +#[test] +fn test_truncated_client_hello_is_detected() { + let ch = get_sample_clienthellopayload(); + let enc = ch.get_encoding(); + println!("testing {:?} enc {:?}", ch, enc); + + for l in 0..enc.len() { + println!("len {:?} enc {:?}", l, &enc[..l]); + if l == 41 { + continue; // where extensions are empty + } + assert!(ClientHelloPayload::read_bytes(&enc[..l]).is_none()); + } +} + +#[test] +fn test_truncated_client_extension_is_detected() { + let chp = get_sample_clienthellopayload(); + + for ext in &chp.extensions { + let mut enc = ext.get_encoding(); + println!("testing {:?} enc {:?}", ext, enc); + + // "outer" truncation, i.e., where the extension-level length is longer than + // the input + for l in 0..enc.len() { + assert!(ClientExtension::read_bytes(&enc[..l]).is_none()); + } + + // these extension types don't have any internal encoding that rustls validates: + match ext.get_type() { + ExtensionType::TransportParameters | ExtensionType::Unknown(_) => { + continue; + } + _ => {} + }; + + // "inner" truncation, where the extension-level length agrees with the input + // length, but isn't long enough for the type of extension + for l in 0..(enc.len() - 4) { + put_u16(l as u16, &mut enc[2..]); + println!(" encoding {:?} len {:?}", enc, l); + assert!(ClientExtension::read_bytes(&enc).is_none()); + } + } +} + +fn test_client_extension_getter(typ: ExtensionType, getter: fn(&ClientHelloPayload) -> bool) { + let mut chp = get_sample_clienthellopayload(); + let ext = chp.find_extension(typ).unwrap().clone(); + + chp.extensions = vec![]; + assert!(!getter(&chp)); + + chp.extensions = vec![ext]; + assert!(getter(&chp)); + + chp.extensions = vec![ClientExtension::Unknown(UnknownExtension { + typ, + payload: Payload(vec![]), + })]; + assert!(!getter(&chp)); +} + +#[test] +fn client_get_sni_extension() { + test_client_extension_getter(ExtensionType::ServerName, |chp| { + chp.get_sni_extension().is_some() + }); +} + +#[test] +fn client_get_sigalgs_extension() { + test_client_extension_getter(ExtensionType::SignatureAlgorithms, |chp| { + chp.get_sigalgs_extension().is_some() + }); +} + +#[test] +fn client_get_namedgroups_extension() { + test_client_extension_getter(ExtensionType::EllipticCurves, |chp| { + chp.get_namedgroups_extension() + .is_some() + }); +} + +#[test] +fn client_get_ecpoints_extension() { + test_client_extension_getter(ExtensionType::ECPointFormats, |chp| { + chp.get_ecpoints_extension().is_some() + }); +} + +#[test] +fn client_get_alpn_extension() { + test_client_extension_getter(ExtensionType::ALProtocolNegotiation, |chp| { + chp.get_alpn_extension().is_some() + }); +} + +#[test] +fn client_get_quic_params_extension() { + test_client_extension_getter(ExtensionType::TransportParameters, |chp| { + chp.get_quic_params_extension() + .is_some() + }); +} + +#[test] +fn client_get_versions_extension() { + test_client_extension_getter(ExtensionType::SupportedVersions, |chp| { + chp.get_versions_extension().is_some() + }); +} + +#[test] +fn client_get_keyshare_extension() { + test_client_extension_getter(ExtensionType::KeyShare, |chp| { + chp.get_keyshare_extension().is_some() + }); +} + +#[test] +fn client_get_psk() { + test_client_extension_getter(ExtensionType::PreSharedKey, |chp| chp.get_psk().is_some()); +} + +#[test] +fn client_get_psk_modes() { + test_client_extension_getter(ExtensionType::PSKKeyExchangeModes, |chp| { + chp.get_psk_modes().is_some() + }); +} + +#[test] +fn test_truncated_helloretry_extension_is_detected() { + let hrr = get_sample_helloretryrequest(); + + for ext in &hrr.extensions { + let mut enc = ext.get_encoding(); + println!("testing {:?} enc {:?}", ext, enc); + + // "outer" truncation, i.e., where the extension-level length is longer than + // the input + for l in 0..enc.len() { + assert!(HelloRetryExtension::read_bytes(&enc[..l]).is_none()); + } + + // these extension types don't have any internal encoding that rustls validates: + if let ExtensionType::Unknown(_) = ext.get_type() { + continue; + } + + // "inner" truncation, where the extension-level length agrees with the input + // length, but isn't long enough for the type of extension + for l in 0..(enc.len() - 4) { + put_u16(l as u16, &mut enc[2..]); + println!(" encoding {:?} len {:?}", enc, l); + assert!(HelloRetryExtension::read_bytes(&enc).is_none()); + } + } +} + +fn test_helloretry_extension_getter(typ: ExtensionType, getter: fn(&HelloRetryRequest) -> bool) { + let mut hrr = get_sample_helloretryrequest(); + let mut exts = std::mem::take(&mut hrr.extensions); + exts.retain(|ext| ext.get_type() == typ); + + assert!(!getter(&hrr)); + + hrr.extensions = exts; + assert!(getter(&hrr)); + + hrr.extensions = vec![HelloRetryExtension::Unknown(UnknownExtension { + typ, + payload: Payload(vec![]), + })]; + assert!(!getter(&hrr)); +} + +#[test] +fn helloretry_get_requested_key_share_group() { + test_helloretry_extension_getter(ExtensionType::KeyShare, |hrr| { + hrr.get_requested_key_share_group() + .is_some() + }); +} + +#[test] +fn helloretry_get_cookie() { + test_helloretry_extension_getter(ExtensionType::Cookie, |hrr| hrr.get_cookie().is_some()); +} + +#[test] +fn helloretry_get_supported_versions() { + test_helloretry_extension_getter(ExtensionType::SupportedVersions, |hrr| { + hrr.get_supported_versions().is_some() + }); +} + +#[test] +fn test_truncated_server_extension_is_detected() { + let shp = get_sample_serverhellopayload(); + + for ext in &shp.extensions { + let mut enc = ext.get_encoding(); + println!("testing {:?} enc {:?}", ext, enc); + + // "outer" truncation, i.e., where the extension-level length is longer than + // the input + for l in 0..enc.len() { + assert!(ServerExtension::read_bytes(&enc[..l]).is_none()); + } + + // these extension types don't have any internal encoding that rustls validates: + match ext.get_type() { + ExtensionType::TransportParameters | ExtensionType::Unknown(_) => { + continue; + } + _ => {} + }; + + // "inner" truncation, where the extension-level length agrees with the input + // length, but isn't long enough for the type of extension + for l in 0..(enc.len() - 4) { + put_u16(l as u16, &mut enc[2..]); + println!(" encoding {:?} len {:?}", enc, l); + assert!(ServerExtension::read_bytes(&enc).is_none()); + } + } +} + +fn test_server_extension_getter(typ: ExtensionType, getter: fn(&ServerHelloPayload) -> bool) { + let mut shp = get_sample_serverhellopayload(); + let ext = shp.find_extension(typ).unwrap().clone(); + + shp.extensions = vec![]; + assert!(!getter(&shp)); + + shp.extensions = vec![ext]; + assert!(getter(&shp)); + + shp.extensions = vec![ServerExtension::Unknown(UnknownExtension { + typ, + payload: Payload(vec![]), + })]; + assert!(!getter(&shp)); +} + +#[test] +fn server_get_key_share() { + test_server_extension_getter(ExtensionType::KeyShare, |shp| shp.get_key_share().is_some()); +} + +#[test] +fn server_get_psk_index() { + test_server_extension_getter(ExtensionType::PreSharedKey, |shp| { + shp.get_psk_index().is_some() + }); +} + +#[test] +fn server_get_ecpoints_extension() { + test_server_extension_getter(ExtensionType::ECPointFormats, |shp| { + shp.get_ecpoints_extension().is_some() + }); +} + +#[test] +fn server_get_sct_list() { + test_server_extension_getter(ExtensionType::SCT, |shp| shp.get_sct_list().is_some()); +} + +#[test] +fn server_get_supported_versions() { + test_server_extension_getter(ExtensionType::SupportedVersions, |shp| { + shp.get_supported_versions().is_some() + }); +} + +fn test_cert_extension_getter(typ: ExtensionType, getter: fn(&CertificateEntry) -> bool) { + let mut ce = get_sample_certificatepayloadtls13() + .entries + .remove(0); + let mut exts = std::mem::take(&mut ce.exts); + exts.retain(|ext| ext.get_type() == typ); + + assert!(!getter(&ce)); + + ce.exts = exts; + assert!(getter(&ce)); + + ce.exts = vec![CertificateExtension::Unknown(UnknownExtension { + typ, + payload: Payload(vec![]), + })]; + assert!(!getter(&ce)); +} + +#[test] +fn certentry_get_ocsp_response() { + test_cert_extension_getter(ExtensionType::StatusRequest, |ce| { + ce.get_ocsp_response().is_some() + }); +} + +#[test] +fn certentry_get_scts() { + test_cert_extension_getter(ExtensionType::SCT, |ce| ce.get_scts().is_some()); +} + +fn get_sample_serverhellopayload() -> ServerHelloPayload { + ServerHelloPayload { + legacy_version: ProtocolVersion::TLSv1_2, + random: Random::from([0; 32]), + session_id: SessionID::empty(), + cipher_suite: CipherSuite::TLS_NULL_WITH_NULL_NULL, + compression_method: Compression::Null, + extensions: vec![ + ServerExtension::ECPointFormats(ECPointFormatList::supported()), + ServerExtension::ServerNameAck, + ServerExtension::SessionTicketAck, + ServerExtension::RenegotiationInfo(PayloadU8(vec![0])), + ServerExtension::Protocols(vec![PayloadU8(vec![0])]), + ServerExtension::KeyShare(KeyShareEntry::new(NamedGroup::X25519, &[1, 2, 3])), + ServerExtension::PresharedKey(3), + ServerExtension::ExtendedMasterSecretAck, + ServerExtension::CertificateStatusAck, + ServerExtension::SignedCertificateTimestamp(vec![PayloadU16(vec![0])]), + ServerExtension::SupportedVersions(ProtocolVersion::TLSv1_2), + ServerExtension::TransportParameters(vec![1, 2, 3]), + ServerExtension::Unknown(UnknownExtension { + typ: ExtensionType::Unknown(12345), + payload: Payload(vec![1, 2, 3]), + }), + ], + } +} + +#[test] +fn can_print_all_serverextensions() { + println!("server hello {:?}", get_sample_serverhellopayload()); +} + +#[test] +fn can_clone_all_serverextensions() { + let _ = get_sample_serverhellopayload().extensions; +} + +fn get_sample_helloretryrequest() -> HelloRetryRequest { + HelloRetryRequest { + legacy_version: ProtocolVersion::TLSv1_2, + session_id: SessionID::empty(), + cipher_suite: CipherSuite::TLS_NULL_WITH_NULL_NULL, + extensions: vec![ + HelloRetryExtension::KeyShare(NamedGroup::X25519), + HelloRetryExtension::Cookie(PayloadU16(vec![0])), + HelloRetryExtension::SupportedVersions(ProtocolVersion::TLSv1_2), + HelloRetryExtension::Unknown(UnknownExtension { + typ: ExtensionType::Unknown(12345), + payload: Payload(vec![1, 2, 3]), + }), + ], + } +} + +fn get_sample_certificatepayloadtls13() -> CertificatePayloadTLS13 { + CertificatePayloadTLS13 { + context: PayloadU8(vec![1, 2, 3]), + entries: vec![CertificateEntry { + cert: Certificate(vec![3, 4, 5]), + exts: vec![ + CertificateExtension::CertificateStatus(CertificateStatus { + ocsp_response: PayloadU24(vec![1, 2, 3]), + }), + CertificateExtension::SignedCertificateTimestamp(vec![PayloadU16(vec![0])]), + CertificateExtension::Unknown(UnknownExtension { + typ: ExtensionType::Unknown(12345), + payload: Payload(vec![1, 2, 3]), + }), + ], + }], + } +} + +fn get_sample_serverkeyexchangepayload_ecdhe() -> ServerKeyExchangePayload { + ServerKeyExchangePayload::ECDHE(ECDHEServerKeyExchange { + params: ServerECDHParams { + curve_params: ECParameters { + curve_type: ECCurveType::NamedCurve, + named_group: NamedGroup::X25519, + }, + public: PayloadU8(vec![1, 2, 3]), + }, + dss: DigitallySignedStruct::new(SignatureScheme::RSA_PSS_SHA256, vec![1, 2, 3]), + }) +} + +fn get_sample_serverkeyexchangepayload_unknown() -> ServerKeyExchangePayload { + ServerKeyExchangePayload::Unknown(Payload(vec![1, 2, 3])) +} + +fn get_sample_certificaterequestpayload() -> CertificateRequestPayload { + CertificateRequestPayload { + certtypes: vec![ClientCertificateType::RSASign], + sigschemes: vec![SignatureScheme::ECDSA_NISTP256_SHA256], + canames: vec![PayloadU16(vec![1, 2, 3])], + } +} + +fn get_sample_certificaterequestpayloadtls13() -> CertificateRequestPayloadTLS13 { + CertificateRequestPayloadTLS13 { + context: PayloadU8(vec![1, 2, 3]), + extensions: vec![ + CertReqExtension::SignatureAlgorithms(vec![SignatureScheme::ECDSA_NISTP256_SHA256]), + CertReqExtension::AuthorityNames(vec![PayloadU16(vec![1, 2, 3])]), + CertReqExtension::Unknown(UnknownExtension { + typ: ExtensionType::Unknown(12345), + payload: Payload(vec![1, 2, 3]), + }), + ], + } +} + +fn get_sample_newsessionticketpayload() -> NewSessionTicketPayload { + NewSessionTicketPayload { + lifetime_hint: 1234, + ticket: PayloadU16(vec![1, 2, 3]), + } +} + +fn get_sample_newsessionticketpayloadtls13() -> NewSessionTicketPayloadTLS13 { + NewSessionTicketPayloadTLS13 { + lifetime: 123, + age_add: 1234, + nonce: PayloadU8(vec![1, 2, 3]), + ticket: PayloadU16(vec![4, 5, 6]), + exts: vec![NewSessionTicketExtension::Unknown(UnknownExtension { + typ: ExtensionType::Unknown(12345), + payload: Payload(vec![1, 2, 3]), + })], + } +} + +fn get_sample_encryptedextensions() -> EncryptedExtensions { + get_sample_serverhellopayload().extensions +} + +fn get_sample_certificatestatus() -> CertificateStatus { + CertificateStatus { + ocsp_response: PayloadU24(vec![1, 2, 3]), + } +} + +fn get_all_tls12_handshake_payloads() -> Vec { + vec![ + HandshakeMessagePayload { + typ: HandshakeType::HelloRequest, + payload: HandshakePayload::HelloRequest, + }, + HandshakeMessagePayload { + typ: HandshakeType::ClientHello, + payload: HandshakePayload::ClientHello(get_sample_clienthellopayload()), + }, + HandshakeMessagePayload { + typ: HandshakeType::ServerHello, + payload: HandshakePayload::ServerHello(get_sample_serverhellopayload()), + }, + HandshakeMessagePayload { + typ: HandshakeType::HelloRetryRequest, + payload: HandshakePayload::HelloRetryRequest(get_sample_helloretryrequest()), + }, + HandshakeMessagePayload { + typ: HandshakeType::Certificate, + payload: HandshakePayload::Certificate(vec![Certificate(vec![1, 2, 3])]), + }, + HandshakeMessagePayload { + typ: HandshakeType::ServerKeyExchange, + payload: HandshakePayload::ServerKeyExchange( + get_sample_serverkeyexchangepayload_ecdhe(), + ), + }, + HandshakeMessagePayload { + typ: HandshakeType::ServerKeyExchange, + payload: HandshakePayload::ServerKeyExchange( + get_sample_serverkeyexchangepayload_unknown(), + ), + }, + HandshakeMessagePayload { + typ: HandshakeType::CertificateRequest, + payload: HandshakePayload::CertificateRequest(get_sample_certificaterequestpayload()), + }, + HandshakeMessagePayload { + typ: HandshakeType::ServerHelloDone, + payload: HandshakePayload::ServerHelloDone, + }, + HandshakeMessagePayload { + typ: HandshakeType::ClientKeyExchange, + payload: HandshakePayload::ClientKeyExchange(Payload(vec![1, 2, 3])), + }, + HandshakeMessagePayload { + typ: HandshakeType::NewSessionTicket, + payload: HandshakePayload::NewSessionTicket(get_sample_newsessionticketpayload()), + }, + HandshakeMessagePayload { + typ: HandshakeType::EncryptedExtensions, + payload: HandshakePayload::EncryptedExtensions(get_sample_encryptedextensions()), + }, + HandshakeMessagePayload { + typ: HandshakeType::KeyUpdate, + payload: HandshakePayload::KeyUpdate(KeyUpdateRequest::UpdateRequested), + }, + HandshakeMessagePayload { + typ: HandshakeType::KeyUpdate, + payload: HandshakePayload::KeyUpdate(KeyUpdateRequest::UpdateNotRequested), + }, + HandshakeMessagePayload { + typ: HandshakeType::Finished, + payload: HandshakePayload::Finished(Payload(vec![1, 2, 3])), + }, + HandshakeMessagePayload { + typ: HandshakeType::CertificateStatus, + payload: HandshakePayload::CertificateStatus(get_sample_certificatestatus()), + }, + HandshakeMessagePayload { + typ: HandshakeType::Unknown(99), + payload: HandshakePayload::Unknown(Payload(vec![1, 2, 3])), + }, + ] +} + +#[test] +fn can_roundtrip_all_tls12_handshake_payloads() { + for ref hm in get_all_tls12_handshake_payloads().iter() { + println!("{:?}", hm.typ); + let bytes = hm.get_encoding(); + let mut rd = Reader::init(&bytes); + let other = HandshakeMessagePayload::read(&mut rd).unwrap(); + assert!(!rd.any_left()); + assert_eq!(hm.get_encoding(), other.get_encoding()); + + println!("{:?}", hm); + println!("{:?}", other); + } +} + +#[test] +fn can_detect_truncation_of_all_tls12_handshake_payloads() { + for hm in get_all_tls12_handshake_payloads().iter() { + let mut enc = hm.get_encoding(); + println!("test {:?} enc {:?}", hm, enc); + + // outer truncation + for l in 0..enc.len() { + assert!(HandshakeMessagePayload::read_bytes(&enc[..l]).is_none()) + } + + // inner truncation + for l in 0..enc.len() - 4 { + put_u24(l as u32, &mut enc[1..]); + println!(" check len {:?} enc {:?}", l, enc); + + match (hm.typ, l) { + (HandshakeType::ClientHello, 41) + | (HandshakeType::ServerHello, 38) + | (HandshakeType::ServerKeyExchange, _) + | (HandshakeType::ClientKeyExchange, _) + | (HandshakeType::Finished, _) + | (HandshakeType::Unknown(_), _) => continue, + _ => {} + }; + + assert!(HandshakeMessagePayload::read_version( + &mut Reader::init(&enc), + ProtocolVersion::TLSv1_2 + ) + .is_none()); + assert!(HandshakeMessagePayload::read_bytes(&enc).is_none()); + } + } +} + +fn get_all_tls13_handshake_payloads() -> Vec { + vec![ + HandshakeMessagePayload { + typ: HandshakeType::HelloRequest, + payload: HandshakePayload::HelloRequest, + }, + HandshakeMessagePayload { + typ: HandshakeType::ClientHello, + payload: HandshakePayload::ClientHello(get_sample_clienthellopayload()), + }, + HandshakeMessagePayload { + typ: HandshakeType::ServerHello, + payload: HandshakePayload::ServerHello(get_sample_serverhellopayload()), + }, + HandshakeMessagePayload { + typ: HandshakeType::HelloRetryRequest, + payload: HandshakePayload::HelloRetryRequest(get_sample_helloretryrequest()), + }, + HandshakeMessagePayload { + typ: HandshakeType::Certificate, + payload: HandshakePayload::CertificateTLS13(get_sample_certificatepayloadtls13()), + }, + HandshakeMessagePayload { + typ: HandshakeType::ServerKeyExchange, + payload: HandshakePayload::ServerKeyExchange( + get_sample_serverkeyexchangepayload_ecdhe(), + ), + }, + HandshakeMessagePayload { + typ: HandshakeType::ServerKeyExchange, + payload: HandshakePayload::ServerKeyExchange( + get_sample_serverkeyexchangepayload_unknown(), + ), + }, + HandshakeMessagePayload { + typ: HandshakeType::CertificateRequest, + payload: HandshakePayload::CertificateRequestTLS13( + get_sample_certificaterequestpayloadtls13(), + ), + }, + HandshakeMessagePayload { + typ: HandshakeType::CertificateVerify, + payload: HandshakePayload::CertificateVerify(DigitallySignedStruct::new( + SignatureScheme::ECDSA_NISTP256_SHA256, + vec![1, 2, 3], + )), + }, + HandshakeMessagePayload { + typ: HandshakeType::ServerHelloDone, + payload: HandshakePayload::ServerHelloDone, + }, + HandshakeMessagePayload { + typ: HandshakeType::ClientKeyExchange, + payload: HandshakePayload::ClientKeyExchange(Payload(vec![1, 2, 3])), + }, + HandshakeMessagePayload { + typ: HandshakeType::NewSessionTicket, + payload: HandshakePayload::NewSessionTicketTLS13( + get_sample_newsessionticketpayloadtls13(), + ), + }, + HandshakeMessagePayload { + typ: HandshakeType::EncryptedExtensions, + payload: HandshakePayload::EncryptedExtensions(get_sample_encryptedextensions()), + }, + HandshakeMessagePayload { + typ: HandshakeType::KeyUpdate, + payload: HandshakePayload::KeyUpdate(KeyUpdateRequest::UpdateRequested), + }, + HandshakeMessagePayload { + typ: HandshakeType::KeyUpdate, + payload: HandshakePayload::KeyUpdate(KeyUpdateRequest::UpdateNotRequested), + }, + HandshakeMessagePayload { + typ: HandshakeType::Finished, + payload: HandshakePayload::Finished(Payload(vec![1, 2, 3])), + }, + HandshakeMessagePayload { + typ: HandshakeType::CertificateStatus, + payload: HandshakePayload::CertificateStatus(get_sample_certificatestatus()), + }, + HandshakeMessagePayload { + typ: HandshakeType::Unknown(99), + payload: HandshakePayload::Unknown(Payload(vec![1, 2, 3])), + }, + ] +} + +#[test] +fn can_roundtrip_all_tls13_handshake_payloads() { + for ref hm in get_all_tls13_handshake_payloads().iter() { + println!("{:?}", hm.typ); + let bytes = hm.get_encoding(); + let mut rd = Reader::init(&bytes); + + let other = + HandshakeMessagePayload::read_version(&mut rd, ProtocolVersion::TLSv1_3).unwrap(); + assert!(!rd.any_left()); + assert_eq!(hm.get_encoding(), other.get_encoding()); + + println!("{:?}", hm); + println!("{:?}", other); + } +} + +fn put_u24(u: u32, b: &mut [u8]) { + b[0] = (u >> 16) as u8; + b[1] = (u >> 8) as u8; + b[2] = u as u8; +} + +#[test] +fn can_detect_truncation_of_all_tls13_handshake_payloads() { + for hm in get_all_tls13_handshake_payloads().iter() { + let mut enc = hm.get_encoding(); + println!("test {:?} enc {:?}", hm, enc); + + // outer truncation + for l in 0..enc.len() { + assert!(HandshakeMessagePayload::read_bytes(&enc[..l]).is_none()) + } + + // inner truncation + for l in 0..enc.len() - 4 { + put_u24(l as u32, &mut enc[1..]); + println!(" check len {:?} enc {:?}", l, enc); + + match (hm.typ, l) { + (HandshakeType::ClientHello, 41) + | (HandshakeType::ServerHello, 38) + | (HandshakeType::ServerKeyExchange, _) + | (HandshakeType::ClientKeyExchange, _) + | (HandshakeType::Finished, _) + | (HandshakeType::Unknown(_), _) => continue, + _ => {} + }; + + assert!(HandshakeMessagePayload::read_version( + &mut Reader::init(&enc), + ProtocolVersion::TLSv1_3 + ) + .is_none()); + } + } +} + +#[test] +fn cannot_read_messagehash_from_network() { + let mh = HandshakeMessagePayload { + typ: HandshakeType::MessageHash, + payload: HandshakePayload::MessageHash(Payload::new(vec![1, 2, 3])), + }; + println!("mh {:?}", mh); + let enc = mh.get_encoding(); + assert!(HandshakeMessagePayload::read_bytes(&enc).is_none()); +} + +#[test] +fn cannot_decode_huge_certificate() { + let mut buf = [0u8; 65 * 1024]; + // exactly 64KB decodes fine + buf[0] = 0x0b; + buf[1] = 0x01; + buf[2] = 0x00; + buf[3] = 0x03; + buf[4] = 0x01; + buf[5] = 0x00; + buf[6] = 0x00; + buf[7] = 0x00; + buf[8] = 0xff; + buf[9] = 0xfd; + HandshakeMessagePayload::read_bytes(&buf).unwrap(); + + // however 64KB + 1 byte does not + buf[1] = 0x01; + buf[2] = 0x00; + buf[3] = 0x04; + buf[4] = 0x01; + buf[5] = 0x00; + buf[6] = 0x01; + assert!(HandshakeMessagePayload::read_bytes(&buf).is_none()); +} + +#[test] +fn can_decode_server_hello_from_api_devicecheck_apple_com() { + let data = include_bytes!("hello-api.devicecheck.apple.com.bin"); + let mut r = Reader::init(data); + let hm = HandshakeMessagePayload::read(&mut r).unwrap(); + println!("msg: {:?}", hm); +} diff --git a/third_party/rustls-fork-shadow-tls/src/msgs/hello-api.devicecheck.apple.com.bin b/third_party/rustls-fork-shadow-tls/src/msgs/hello-api.devicecheck.apple.com.bin new file mode 100644 index 0000000..fcbaaad Binary files /dev/null and b/third_party/rustls-fork-shadow-tls/src/msgs/hello-api.devicecheck.apple.com.bin differ diff --git a/third_party/rustls-fork-shadow-tls/src/msgs/hsjoiner.rs b/third_party/rustls-fork-shadow-tls/src/msgs/hsjoiner.rs new file mode 100644 index 0000000..909f6a1 --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/src/msgs/hsjoiner.rs @@ -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, + + /// 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, + + /// 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 { + 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, 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, 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(); + } +} diff --git a/third_party/rustls-fork-shadow-tls/src/msgs/macros.rs b/third_party/rustls-fork-shadow-tls/src/msgs/macros.rs new file mode 100644 index 0000000..da9ff6a --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/src/msgs/macros.rs @@ -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) { + self.get_u8().encode(bytes); + } + + fn read(r: &mut Reader) -> Option { + u8::read(r).map($enum_name::from) + } + } + impl From 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) { + self.get_u16().encode(bytes); + } + + fn read(r: &mut Reader) -> Option { + u16::read(r).map($enum_name::from) + } + } + impl From for $enum_name { + fn from(x: u16) -> Self { + match x { + $($enum_val => $enum_name::$enum_var),* + , x => $enum_name::Unknown(x), + } + } + } + }; +} diff --git a/third_party/rustls-fork-shadow-tls/src/msgs/message.rs b/third_party/rustls-fork-shadow-tls/src/msgs/message.rs new file mode 100644 index 0000000..a8b8308 --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/src/msgs/message.rs @@ -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) { + 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 { + 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 { + 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 { + 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 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 for Message { + type Error = Error; + + fn try_from(plain: PlainMessage) -> Result { + 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, +} diff --git a/third_party/rustls-fork-shadow-tls/src/msgs/message_test.rs b/third_party/rustls-fork-shadow-tls/src/msgs/message_test.rs new file mode 100644 index 0000000..a50df90 --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/src/msgs/message_test.rs @@ -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]))); +} diff --git a/third_party/rustls-fork-shadow-tls/src/msgs/mod.rs b/third_party/rustls-fork-shadow-tls/src/msgs/mod.rs new file mode 100644 index 0000000..655ff13 --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/src/msgs/mod.rs @@ -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(); + } + } +} diff --git a/third_party/rustls-fork-shadow-tls/src/msgs/persist.rs b/third_party/rustls-fork-shadow-tls/src/msgs/persist.rs new file mode 100644 index 0000000..e9bcbda --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/src/msgs/persist.rs @@ -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, +} + +impl Codec for ClientSessionKey { + fn encode(&self, bytes: &mut Vec) { + bytes.extend_from_slice(self.kind); + bytes.extend_from_slice(&self.name); + } + + // Don't need to read these. + fn read(_r: &mut Reader) -> Option { + 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 { + 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 for ClientSessionValue { + fn from(v: Tls13ClientSessionValue) -> Self { + Self::Tls13(v) + } +} + +#[cfg(feature = "tls12")] +impl From for ClientSessionValue { + fn from(v: Tls12ClientSessionValue) -> Self { + Self::Tls12(v) + } +} + +pub struct Retrieved { + pub value: T, + retrieved_at: TimeBase, +} + +impl Retrieved { + 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 { + pub fn tls13(&self) -> Option> { + 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 std::ops::Deref for Retrieved { + 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, + secret: Vec, + server_cert_chain: Vec, + 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 { + 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 { + 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, + master_secret: Vec, + server_cert_chain: Vec, + 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 { + 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 { + 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 { + 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, + secret: Vec, + time_now: TimeBase, + lifetime_secs: u32, + server_cert_chain: Vec, + ) -> 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 { + 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) { + 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, + pub version: ProtocolVersion, + pub cipher_suite: CipherSuite, + pub master_secret: PayloadU8, + pub extended_ms: bool, + pub client_cert_chain: Option, + pub alpn: Option, + pub application_data: PayloadU16, + pub creation_time_sec: u64, + pub age_obfuscation_offset: u32, + freshness: Option, +} + +impl Codec for ServerSessionValue { + fn encode(&self, bytes: &mut Vec) { + 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 { + 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, + client_cert_chain: Option, + alpn: Option>, + application_data: Vec, + 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() + } +} diff --git a/third_party/rustls-fork-shadow-tls/src/msgs/persist_test.rs b/third_party/rustls-fork-shadow-tls/src/msgs/persist_test.rs new file mode 100644 index 0000000..c4a4165 --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/src/msgs/persist_test.rs @@ -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); +} diff --git a/third_party/rustls-fork-shadow-tls/src/quic.rs b/third_party/rustls-fork-shadow-tls/src/quic.rs new file mode 100644 index 0000000..1621495 --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/src/quic.rs @@ -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; + + /// 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) -> Option; + + /// Emit the TLS description code of a fatal alert, if one has arisen. + /// + /// Check after `read_hs` returns `Err(_)`. + fn alert(&self) -> Option; +} + +/// 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 { + 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 . + #[inline] + pub fn confidentiality_limit(&self) -> u64 { + self.suite.confidentiality_limit + } + + /// Number of times the packet key can be used without sacrificing integrity + /// + /// See . + #[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) -> Option { + 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 + ] + ) + )); + } +} diff --git a/third_party/rustls-fork-shadow-tls/src/rand.rs b/third_party/rustls-fork-shadow-tls/src/rand.rs new file mode 100644 index 0000000..14cde00 --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/src/rand.rs @@ -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 of the given size +/// containing random material. +pub(crate) fn random_vec(len: usize) -> Result, GetRandomFailed> { + let mut v = vec![0; len]; + fill_random(&mut v)?; + Ok(v) +} + +/// Return a uniformly random u32. +pub(crate) fn random_u32() -> Result { + let mut buf = [0u8; 4]; + fill_random(&mut buf)?; + codec::decode_u32(&buf).ok_or(GetRandomFailed) +} + +#[derive(Debug)] +pub struct GetRandomFailed; diff --git a/third_party/rustls-fork-shadow-tls/src/record_layer.rs b/third_party/rustls-fork-shadow-tls/src/record_layer.rs new file mode 100644 index 0000000..e525772 --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/src/record_layer.rs @@ -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, + message_decrypter: Box, + 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, +} + +impl RecordLayer { + pub(crate) fn new() -> Self { + Self { + message_encrypter: ::invalid(), + message_decrypter: ::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) { + 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) { + 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) { + 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) { + 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, + 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 { + 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() + } +} diff --git a/third_party/rustls-fork-shadow-tls/src/server/builder.rs b/third_party/rustls-fork-shadow-tls/src/server/builder.rs new file mode 100644 index 0000000..d32ce44 --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/src/server/builder.rs @@ -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 { + /// Choose how to verify client certificates. + pub fn with_client_cert_verifier( + self, + client_cert_verifier: Arc, + ) -> ConfigBuilder { + 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 { + 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, + kx_groups: Vec<&'static SupportedKxGroup>, + versions: versions::EnabledVersions, + verifier: Arc, +} + +impl ConfigBuilder { + /// 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_der: key::PrivateKey, + ) -> Result { + 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_der: key::PrivateKey, + ocsp: Vec, + scts: Vec, + ) -> Result { + 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) -> 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, + } + } +} diff --git a/third_party/rustls-fork-shadow-tls/src/server/common.rs b/third_party/rustls-fork-shadow-tls/src/server/common.rs new file mode 100644 index 0000000..2e3420c --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/src/server/common.rs @@ -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 + } +} diff --git a/third_party/rustls-fork-shadow-tls/src/server/handy.rs b/third_party/rustls-fork-shadow-tls/src/server/handy.rs new file mode 100644 index 0000000..296d20c --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/src/server/handy.rs @@ -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, _sec: Vec) -> bool { + false + } + fn get(&self, _id: &[u8]) -> Option> { + None + } + fn take(&self, _id: &[u8]) -> Option> { + 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, Vec>>, +} + +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 { + Arc::new(Self { + cache: Mutex::new(limited_cache::LimitedCache::new(size)), + }) + } +} + +impl server::StoresServerSessions for ServerSessionMemoryCache { + fn put(&self, key: Vec, value: Vec) -> bool { + self.cache + .lock() + .unwrap() + .insert(key, value); + true + } + + fn get(&self, key: &[u8]) -> Option> { + self.cache + .lock() + .unwrap() + .get(key) + .cloned() + } + + fn take(&self, key: &[u8]) -> Option> { + 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> { + None + } + fn decrypt(&self, _bytes: &[u8]) -> Option> { + None + } +} + +/// Something which always resolves to the same cert chain. +pub(super) struct AlwaysResolvesChain(Arc); + +impl AlwaysResolvesChain { + /// Creates an `AlwaysResolvesChain`, auto-detecting the underlying private + /// key type and encoding. + pub(super) fn new( + chain: Vec, + priv_key: &key::PrivateKey, + ) -> Result { + 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, + priv_key: &key::PrivateKey, + ocsp: Vec, + scts: Vec, + ) -> Result { + 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> { + 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>, +} + +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> { + 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()); + } +} diff --git a/third_party/rustls-fork-shadow-tls/src/server/hs.rs b/third_party/rustls-fork-shadow-tls/src/server/hs.rs new file mode 100644 index 0000000..6fc3bd8 --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/src/server/hs.rs @@ -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>; +pub(super) type NextStateOrError = Result; +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, + 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, + #[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, + ) -> 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, + pub(super) extra_exts: Vec, + 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, extra_exts: Vec) -> 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, + 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::>(); + + 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 for ExpectClientHello { + fn handle(self: Box, 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), 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 = 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), +} diff --git a/third_party/rustls-fork-shadow-tls/src/server/server_conn.rs b/third_party/rustls-fork-shadow-tls/src/server/server_conn.rs new file mode 100644 index 0000000..cf8669b --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/src/server/server_conn.rs @@ -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, value: Vec) -> bool; + + /// Find a value with the given `key`. Return it, or None + /// if it doesn't exist. + fn get(&self, key: &[u8]) -> Option>; + + /// 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>; + + /// 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>; + + /// 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>; +} + +/// 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>; +} + +/// A struct representing the received Client Hello +pub struct ClientHello<'a> { + server_name: &'a Option, + signature_schemes: &'a [SignatureScheme], + alpn: Option<&'a Vec>, + cipher_suites: &'a [CipherSuite], +} + +impl<'a> ClientHello<'a> { + /// Creates a new ClientHello + pub(super) fn new( + server_name: &'a Option, + signature_schemes: &'a [SignatureScheme], + alpn: Option<&'a Vec>, + 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(>::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 + /// 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 + /// . + /// + /// 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> { + 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, + + /// 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, + + /// How to store client sessions. + pub session_storage: Arc, + + /// How to produce tickets. + pub ticketer: Arc, + + /// How to choose a server cert and key. + pub cert_resolver: Arc, + + /// Protocol names we support, most preferred first. + /// If empty we don't do ALPN at all. + pub alpn_protocols: Vec>, + + /// 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, + + /// How to output key material for debugging. The default + /// does nothing. + pub key_log: Arc, + + /// 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 { + 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 { + 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, +} + +impl ServerConnection { + /// Make a new ServerConnection. `config` controls how + /// we behave in the TLS protocol. + pub fn new(config: Arc) -> Result { + Self::from_config(config, vec![]) + } + + fn from_config( + config: Arc, + extra_exts: Vec, + ) -> Result { + 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 { + 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 { + 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; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl DerefMut for ServerConnection { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } +} + +impl From 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>, +} + +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 { + 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 { + 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, 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, + message: Message, + sig_schemes: Vec, +} + +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) -> Result { + 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 for Accepting { + fn handle( + self: Box, + _cx: &mut hs::ServerContext<'_>, + _m: Message, + ) -> Result>, 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 { + 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, + pub(super) received_resumption_data: Option>, + pub(super) resumption_data: Vec, + 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 { + 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) -> Option { + quic::write_hs(&mut self.inner.common_state, buf) + } + + fn alert(&self) -> Option { + 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, + quic_version: quic::Version, + params: Vec, + ) -> Result { + 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 {} diff --git a/third_party/rustls-fork-shadow-tls/src/server/tls12.rs b/third_party/rustls-fork-shadow-tls/src/server/tls12.rs new file mode 100644 index 0000000..45b6c78 --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/src/server/tls12.rs @@ -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, + 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, + } + + 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, + 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, + ) -> Result { + 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, + skxg: &'static kx::SupportedKxGroup, + signing_key: &dyn sign::SigningKey, + randoms: &ConnectionRandoms, + ) -> Result { + 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 { + 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, + transcript: HandshakeHash, + randoms: ConnectionRandoms, + session_id: SessionID, + suite: &'static Tls12CipherSuite, + using_ems: bool, + server_kx: kx::KeyExchange, + send_ticket: bool, +} + +impl State for ExpectCertificate { + fn handle(mut self: Box, 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, + transcript: HandshakeHash, + randoms: ConnectionRandoms, + session_id: SessionID, + suite: &'static Tls12CipherSuite, + using_ems: bool, + server_kx: kx::KeyExchange, + client_cert: Option>, + send_ticket: bool, +} + +impl State for ExpectClientKx { + fn handle(mut self: Box, 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::(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, + secrets: ConnectionSecrets, + transcript: HandshakeHash, + session_id: SessionID, + using_ems: bool, + client_cert: Vec, + send_ticket: bool, +} + +impl State for ExpectCertificateVerify { + fn handle(mut self: Box, 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, + secrets: ConnectionSecrets, + transcript: HandshakeHash, + session_id: SessionID, + using_ems: bool, + resuming: bool, + send_ticket: bool, +} + +impl State for ExpectCcs { + fn handle(self: Box, 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, + secrets: ConnectionSecrets, + transcript: HandshakeHash, + session_id: SessionID, + using_ems: bool, + resuming: bool, + send_ticket: bool, +} + +impl State for ExpectFinished { + fn handle(mut self: Box, 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 for ExpectTraffic { + fn handle(self: Box, 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 { + self.secrets + .extract_secrets(Side::Server) + } +} diff --git a/third_party/rustls-fork-shadow-tls/src/server/tls13.rs b/third_party/rustls-fork-shadow-tls/src/server/tls13.rs new file mode 100644 index 0000000..c7ec516 --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/src/server/tls13.rs @@ -0,0 +1,1386 @@ +use crate::check::inappropriate_handshake_message; +use crate::conn::{CommonState, ConnectionRandoms, 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, warn}; +use crate::msgs::codec::Codec; +use crate::msgs::enums::{AlertDescription, KeyUpdateRequest}; +use crate::msgs::enums::{ContentType, HandshakeType}; +use crate::msgs::handshake::HandshakeMessagePayload; +use crate::msgs::handshake::HandshakePayload; +use crate::msgs::handshake::{NewSessionTicketExtension, NewSessionTicketPayloadTLS13}; +use crate::msgs::message::{Message, MessagePayload}; +use crate::msgs::persist; +use crate::rand; +use crate::server::ServerConfig; +use crate::ticketer; +use crate::tls13::key_schedule::{KeyScheduleTraffic, KeyScheduleTrafficWithClientFinishedPending}; +use crate::tls13::Tls13CipherSuite; +use crate::verify; +#[cfg(feature = "quic")] +use crate::{check::inappropriate_message, conn::Protocol}; +#[cfg(feature = "secret_extraction")] +use crate::{conn::Side, suites::PartiallyExtractedSecrets}; + +use super::hs::{self, HandshakeHashOrBuffer, ServerContext}; +use super::server_conn::ServerConnectionData; + +use std::sync::Arc; + +use ring::constant_time; + +pub(super) use client_hello::CompleteClientHelloHandling; + +mod client_hello { + use crate::enums::SignatureScheme; + use crate::kx; + use crate::msgs::base::{Payload, PayloadU8}; + use crate::msgs::ccs::ChangeCipherSpecPayload; + use crate::msgs::enums::NamedGroup; + use crate::msgs::enums::{Compression, PSKKeyExchangeMode}; + use crate::msgs::handshake::CertReqExtension; + use crate::msgs::handshake::CertificateEntry; + use crate::msgs::handshake::CertificateExtension; + use crate::msgs::handshake::CertificatePayloadTLS13; + use crate::msgs::handshake::CertificateRequestPayloadTLS13; + use crate::msgs::handshake::CertificateStatus; + use crate::msgs::handshake::ClientHelloPayload; + use crate::msgs::handshake::DigitallySignedStruct; + use crate::msgs::handshake::HelloRetryExtension; + use crate::msgs::handshake::HelloRetryRequest; + use crate::msgs::handshake::KeyShareEntry; + use crate::msgs::handshake::Random; + use crate::msgs::handshake::ServerExtension; + use crate::msgs::handshake::ServerHelloPayload; + use crate::msgs::handshake::SessionID; + #[cfg(feature = "quic")] + use crate::quic; + use crate::server::common::ActiveCertifiedKey; + use crate::sign; + use crate::tls13::key_schedule::{ + KeyScheduleEarly, KeyScheduleHandshake, KeySchedulePreHandshake, + }; + + use super::*; + + #[derive(PartialEq)] + pub(super) enum EarlyDataDecision { + Disabled, + RequestedButRejected, + Accepted, + } + + pub(in crate::server) struct CompleteClientHelloHandling { + pub(in crate::server) config: Arc, + pub(in crate::server) transcript: HandshakeHash, + pub(in crate::server) suite: &'static Tls13CipherSuite, + pub(in crate::server) randoms: ConnectionRandoms, + pub(in crate::server) done_retry: bool, + pub(in crate::server) send_ticket: bool, + pub(in crate::server) extra_exts: Vec, + } + + fn max_early_data_size(configured: u32) -> usize { + if configured != 0 { + configured as usize + } else { + // The relevant max_early_data_size may in fact be unknowable: if + // we (the server) have turned off early_data but the client has + // a stale ticket from when we allowed early_data: we'll naturally + // reject early_data but need an upper bound on the amount of data + // to drop. + // + // Use a single maximum-sized message. + 16384 + } + } + + impl CompleteClientHelloHandling { + fn check_binder( + &self, + suite: &'static Tls13CipherSuite, + client_hello: &Message, + psk: &[u8], + binder: &[u8], + ) -> bool { + let binder_plaintext = match &client_hello.payload { + MessagePayload::Handshake { parsed, .. } => { + parsed.get_encoding_for_binder_signing() + } + _ => unreachable!(), + }; + + let handshake_hash = self + .transcript + .get_hash_given(&binder_plaintext); + + let key_schedule = KeyScheduleEarly::new(suite.hkdf_algorithm, psk); + let real_binder = + key_schedule.resumption_psk_binder_key_and_sign_verify_data(&handshake_hash); + + constant_time::verify_slices_are_equal(real_binder.as_ref(), binder).is_ok() + } + + fn attempt_tls13_ticket_decryption( + &mut self, + ticket: &[u8], + ) -> Option { + if self.config.ticketer.enabled() { + self.config + .ticketer + .decrypt(ticket) + .and_then(|plain| persist::ServerSessionValue::read_bytes(&plain)) + } else { + self.config + .session_storage + .take(ticket) + .and_then(|plain| persist::ServerSessionValue::read_bytes(&plain)) + } + } + + pub(in crate::server) fn handle_client_hello( + mut self, + cx: &mut ServerContext<'_>, + server_key: ActiveCertifiedKey, + chm: &Message, + client_hello: &ClientHelloPayload, + mut sigschemes_ext: Vec, + ) -> hs::NextStateOrError { + if client_hello.compression_methods.len() != 1 { + return Err(cx + .common + .illegal_param("client offered wrong compressions")); + } + + let groups_ext = client_hello + .get_namedgroups_extension() + .ok_or_else(|| hs::incompatible(cx.common, "client didn't describe groups"))?; + + let tls13_schemes = sign::supported_sign_tls13(); + sigschemes_ext.retain(|scheme| tls13_schemes.contains(scheme)); + + let shares_ext = client_hello + .get_keyshare_extension() + .ok_or_else(|| hs::incompatible(cx.common, "client didn't send keyshares"))?; + + if client_hello.has_keyshare_extension_with_duplicates() { + return Err(cx + .common + .illegal_param("client sent duplicate keyshares")); + } + + let early_data_requested = client_hello.early_data_extension_offered(); + + // EarlyData extension is illegal in second ClientHello + if self.done_retry && early_data_requested { + return Err(cx + .common + .illegal_param("client sent EarlyData in second ClientHello")); + } + + // choose a share that we support + let chosen_share = self + .config + .kx_groups + .iter() + .find_map(|group| { + shares_ext + .iter() + .find(|share| share.group == group.name) + }); + + let chosen_share = match chosen_share { + Some(s) => s, + None => { + // We don't have a suitable key share. Choose a suitable group and + // send a HelloRetryRequest. + let retry_group_maybe = self + .config + .kx_groups + .iter() + .find(|group| groups_ext.contains(&group.name)) + .cloned(); + + self.transcript.add_message(chm); + + if let Some(group) = retry_group_maybe { + if self.done_retry { + return Err(cx + .common + .illegal_param("did not follow retry request")); + } + + emit_hello_retry_request( + &mut self.transcript, + self.suite, + cx.common, + group.name, + ); + emit_fake_ccs(cx.common); + + let skip_early_data = max_early_data_size(self.config.max_early_data_size); + + let next = Box::new(hs::ExpectClientHello { + config: self.config, + transcript: HandshakeHashOrBuffer::Hash(self.transcript), + #[cfg(feature = "tls12")] + session_id: SessionID::empty(), + #[cfg(feature = "tls12")] + using_ems: false, + done_retry: true, + send_ticket: self.send_ticket, + extra_exts: self.extra_exts, + }); + + return if early_data_requested { + Ok(Box::new(ExpectAndSkipRejectedEarlyData { + skip_data_left: skip_early_data, + next, + })) + } else { + Ok(next) + }; + } + + return Err(hs::incompatible( + cx.common, + "no kx group overlap with client", + )); + } + }; + + let mut chosen_psk_index = None; + let mut resumedata = None; + let time_now = ticketer::TimeBase::now()?; + + if let Some(psk_offer) = client_hello.get_psk() { + if !client_hello.check_psk_ext_is_last() { + return Err(cx + .common + .illegal_param("psk extension in wrong position")); + } + + if psk_offer.binders.is_empty() { + return Err(hs::decode_error(cx.common, "psk extension missing binder")); + } + + if psk_offer.binders.len() != psk_offer.identities.len() { + return Err(cx + .common + .illegal_param("psk extension mismatched ids/binders")); + } + + for (i, psk_id) in psk_offer.identities.iter().enumerate() { + let resume = match self + .attempt_tls13_ticket_decryption(&psk_id.identity.0) + .map(|resumedata| { + resumedata.set_freshness(psk_id.obfuscated_ticket_age, time_now) + }) + .filter(|resumedata| { + hs::can_resume(self.suite.into(), &cx.data.sni, false, resumedata) + }) { + Some(resume) => resume, + None => continue, + }; + + if !self.check_binder( + self.suite, + chm, + &resume.master_secret.0, + &psk_offer.binders[i].0, + ) { + cx.common + .send_fatal_alert(AlertDescription::DecryptError); + return Err(Error::PeerMisbehavedError( + "client sent wrong binder".to_string(), + )); + } + + chosen_psk_index = Some(i); + resumedata = Some(resume); + break; + } + } + + if !client_hello.psk_mode_offered(PSKKeyExchangeMode::PSK_DHE_KE) { + debug!("Client unwilling to resume, DHE_KE not offered"); + self.send_ticket = false; + chosen_psk_index = None; + resumedata = None; + } else { + self.send_ticket = true; + } + + if let Some(ref resume) = resumedata { + cx.data.received_resumption_data = Some(resume.application_data.0.clone()); + cx.common.peer_certificates = resume.client_cert_chain.clone(); + } + + let full_handshake = resumedata.is_none(); + self.transcript.add_message(chm); + let key_schedule = emit_server_hello( + &mut self.transcript, + &self.randoms, + self.suite, + cx, + &client_hello.session_id, + chosen_share, + chosen_psk_index, + resumedata + .as_ref() + .map(|x| &x.master_secret.0[..]), + &self.config, + )?; + if !self.done_retry { + emit_fake_ccs(cx.common); + } + + let (mut ocsp_response, mut sct_list) = + (server_key.get_ocsp(), server_key.get_sct_list()); + let doing_early_data = emit_encrypted_extensions( + &mut self.transcript, + self.suite, + cx, + &mut ocsp_response, + &mut sct_list, + client_hello, + resumedata.as_ref(), + self.extra_exts, + &self.config, + )?; + + let doing_client_auth = if full_handshake { + let client_auth = + emit_certificate_req_tls13(&mut self.transcript, cx, &self.config)?; + emit_certificate_tls13( + &mut self.transcript, + cx.common, + server_key.get_cert(), + ocsp_response, + sct_list, + ); + emit_certificate_verify_tls13( + &mut self.transcript, + cx.common, + server_key.get_key(), + &sigschemes_ext, + )?; + client_auth + } else { + false + }; + + // If we're not doing early data, then the next messages we receive + // are encrypted with the handshake keys. + match doing_early_data { + EarlyDataDecision::Disabled => { + cx.common + .record_layer + .set_message_decrypter( + self.suite + .derive_decrypter(key_schedule.client_key()), + ); + cx.data.early_data.reject(); + } + EarlyDataDecision::RequestedButRejected => { + debug!("Client requested early_data, but not accepted: switching to handshake keys with trial decryption"); + cx.common + .record_layer + .set_message_decrypter_with_trial_decryption( + self.suite + .derive_decrypter(key_schedule.client_key()), + max_early_data_size(self.config.max_early_data_size), + ); + cx.data.early_data.reject(); + } + EarlyDataDecision::Accepted => { + cx.data + .early_data + .accept(self.config.max_early_data_size as usize); + } + } + + cx.common.check_aligned_handshake()?; + let key_schedule_traffic = emit_finished_tls13( + &mut self.transcript, + self.suite, + &self.randoms, + cx, + key_schedule, + &self.config, + ); + + if !doing_client_auth && self.config.send_half_rtt_data { + // Application data can be sent immediately after Finished, in one + // flight. However, if client auth is enabled, we don't want to send + // application data to an unauthenticated peer. + cx.common.start_outgoing_traffic(); + } + + if doing_client_auth { + Ok(Box::new(ExpectCertificate { + config: self.config, + transcript: self.transcript, + suite: self.suite, + key_schedule: key_schedule_traffic, + send_ticket: self.send_ticket, + })) + } else if doing_early_data == EarlyDataDecision::Accepted && !cx.common.is_quic() { + // Not used for QUIC: RFC 9001 §8.3: Clients MUST NOT send the EndOfEarlyData + // message. A server MUST treat receipt of a CRYPTO frame in a 0-RTT packet as a + // connection error of type PROTOCOL_VIOLATION. + Ok(Box::new(ExpectEarlyData { + config: self.config, + transcript: self.transcript, + suite: self.suite, + key_schedule: key_schedule_traffic, + send_ticket: self.send_ticket, + })) + } else { + Ok(Box::new(ExpectFinished { + config: self.config, + transcript: self.transcript, + suite: self.suite, + key_schedule: key_schedule_traffic, + send_ticket: self.send_ticket, + })) + } + } + } + + fn emit_server_hello( + transcript: &mut HandshakeHash, + randoms: &ConnectionRandoms, + suite: &'static Tls13CipherSuite, + cx: &mut ServerContext<'_>, + session_id: &SessionID, + share: &KeyShareEntry, + chosen_psk_idx: Option, + resuming_psk: Option<&[u8]>, + config: &ServerConfig, + ) -> Result { + let mut extensions = Vec::new(); + + // Prepare key exchange + let kx = kx::KeyExchange::choose(share.group, &config.kx_groups) + .and_then(kx::KeyExchange::start) + .ok_or(Error::FailedToGetRandomBytes)?; + + let kse = KeyShareEntry::new(share.group, kx.pubkey.as_ref()); + extensions.push(ServerExtension::KeyShare(kse)); + extensions.push(ServerExtension::SupportedVersions(ProtocolVersion::TLSv1_3)); + + if let Some(psk_idx) = chosen_psk_idx { + extensions.push(ServerExtension::PresharedKey(psk_idx as u16)); + } + + 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: *session_id, + cipher_suite: suite.common.suite, + compression_method: Compression::Null, + extensions, + }), + }), + }; + + cx.common.check_aligned_handshake()?; + + let client_hello_hash = transcript.get_hash_given(&[]); + + trace!("sending server hello {:?}", sh); + transcript.add_message(&sh); + cx.common.send_msg(sh, false); + + // Start key schedule + let (key_schedule_pre_handshake, early_data_client_key) = if let Some(psk) = resuming_psk { + let early_key_schedule = KeyScheduleEarly::new(suite.hkdf_algorithm, psk); + let client_early_traffic_secret = early_key_schedule.client_early_traffic_secret( + &client_hello_hash, + &*config.key_log, + &randoms.client, + ); + + ( + KeySchedulePreHandshake::from(early_key_schedule), + Some(client_early_traffic_secret), + ) + } else { + (KeySchedulePreHandshake::new(suite.hkdf_algorithm), None) + }; + + // Do key exchange + let key_schedule = kx.complete(&share.payload.0, |secret| { + Ok(key_schedule_pre_handshake.into_handshake(secret)) + })?; + + let handshake_hash = transcript.get_current_hash(); + let (key_schedule, _client_key, server_key) = key_schedule.derive_handshake_secrets( + handshake_hash, + &*config.key_log, + &randoms.client, + ); + + // Set up to encrypt with handshake secrets, but decrypt with early_data keys. + // If not doing early_data after all, this is corrected later to the handshake + // keys (now stored in key_schedule). + cx.common + .record_layer + .set_message_encrypter(suite.derive_encrypter(&server_key)); + + if let Some(key) = &early_data_client_key { + cx.common + .record_layer + .set_message_decrypter(suite.derive_decrypter(key)); + } + + #[cfg(feature = "quic")] + if cx.common.is_quic() { + // If 0-RTT should be rejected, this will be clobbered by ExtensionProcessing + // before the application can see. + cx.common.quic.early_secret = early_data_client_key; + cx.common.quic.hs_secrets = + Some(quic::Secrets::new(_client_key, server_key, suite, false)); + } + + Ok(key_schedule) + } + + fn emit_fake_ccs(common: &mut CommonState) { + if common.is_quic() { + return; + } + let m = Message { + version: ProtocolVersion::TLSv1_2, + payload: MessagePayload::ChangeCipherSpec(ChangeCipherSpecPayload {}), + }; + common.send_msg(m, false); + } + + fn emit_hello_retry_request( + transcript: &mut HandshakeHash, + suite: &'static Tls13CipherSuite, + common: &mut CommonState, + group: NamedGroup, + ) { + let mut req = HelloRetryRequest { + legacy_version: ProtocolVersion::TLSv1_2, + session_id: SessionID::empty(), + cipher_suite: suite.common.suite, + extensions: Vec::new(), + }; + + req.extensions + .push(HelloRetryExtension::KeyShare(group)); + req.extensions + .push(HelloRetryExtension::SupportedVersions( + ProtocolVersion::TLSv1_3, + )); + + let m = Message { + version: ProtocolVersion::TLSv1_2, + payload: MessagePayload::handshake(HandshakeMessagePayload { + typ: HandshakeType::HelloRetryRequest, + payload: HandshakePayload::HelloRetryRequest(req), + }), + }; + + trace!("Requesting retry {:?}", m); + transcript.rollup_for_hrr(); + transcript.add_message(&m); + common.send_msg(m, false); + } + + fn decide_if_early_data_allowed( + cx: &mut ServerContext<'_>, + client_hello: &ClientHelloPayload, + resumedata: Option<&persist::ServerSessionValue>, + suite: &'static Tls13CipherSuite, + config: &ServerConfig, + ) -> EarlyDataDecision { + let early_data_requested = client_hello.early_data_extension_offered(); + let rejected_or_disabled = match early_data_requested { + true => EarlyDataDecision::RequestedButRejected, + false => EarlyDataDecision::Disabled, + }; + + let resume = match resumedata { + Some(resume) => resume, + None => { + // never any early data if not resuming. + return rejected_or_disabled; + } + }; + + /* Non-zero max_early_data_size controls whether early_data is allowed at all. + * We also require stateful resumption. */ + let early_data_configured = config.max_early_data_size > 0 && !config.ticketer.enabled(); + + /* "For PSKs provisioned via NewSessionTicket, a server MUST validate + * that the ticket age for the selected PSK identity (computed by + * subtracting ticket_age_add from PskIdentity.obfuscated_ticket_age + * modulo 2^32) is within a small tolerance of the time since the ticket + * was issued (see Section 8)." -- this is implemented in ServerSessionValue::set_freshness() + * and related. + * + * "In order to accept early data, the server [...] MUST verify that the + * following values are the same as those associated with the + * selected PSK: + * + * - The TLS version number + * - The selected cipher suite + * - The selected ALPN [RFC7301] protocol, if any" + * + * (RFC8446, 4.2.10) */ + let early_data_possible = early_data_requested + && resume.is_fresh() + && Some(resume.version) == cx.common.negotiated_version + && resume.cipher_suite == suite.common.suite + && resume.alpn.as_ref().map(|x| &x.0) == cx.common.alpn_protocol.as_ref(); + + if early_data_configured && early_data_possible && !cx.data.early_data.was_rejected() { + EarlyDataDecision::Accepted + } else { + #[cfg(feature = "quic")] + if cx.common.is_quic() { + // Clobber value set in tls13::emit_server_hello + cx.common.quic.early_secret = None; + } + + rejected_or_disabled + } + } + + fn emit_encrypted_extensions( + transcript: &mut HandshakeHash, + suite: &'static Tls13CipherSuite, + cx: &mut ServerContext<'_>, + ocsp_response: &mut Option<&[u8]>, + sct_list: &mut Option<&[u8]>, + hello: &ClientHelloPayload, + resumedata: Option<&persist::ServerSessionValue>, + extra_exts: Vec, + config: &ServerConfig, + ) -> Result { + let mut ep = hs::ExtensionProcessing::new(); + ep.process_common( + config, + cx, + ocsp_response, + sct_list, + hello, + resumedata, + extra_exts, + )?; + + let early_data = decide_if_early_data_allowed(cx, hello, resumedata, suite, config); + if early_data == EarlyDataDecision::Accepted { + ep.exts.push(ServerExtension::EarlyData); + } + + let ee = Message { + version: ProtocolVersion::TLSv1_3, + payload: MessagePayload::handshake(HandshakeMessagePayload { + typ: HandshakeType::EncryptedExtensions, + payload: HandshakePayload::EncryptedExtensions(ep.exts), + }), + }; + + trace!("sending encrypted extensions {:?}", ee); + transcript.add_message(&ee); + cx.common.send_msg(ee, true); + Ok(early_data) + } + + fn emit_certificate_req_tls13( + transcript: &mut HandshakeHash, + cx: &mut ServerContext<'_>, + config: &ServerConfig, + ) -> Result { + if !config.verifier.offer_client_auth() { + return Ok(false); + } + + let mut cr = CertificateRequestPayloadTLS13 { + context: PayloadU8::empty(), + extensions: Vec::new(), + }; + + let schemes = config + .verifier + .supported_verify_schemes(); + cr.extensions + .push(CertReqExtension::SignatureAlgorithms(schemes.to_vec())); + + let names = config + .verifier + .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()) + })?; + + if !names.is_empty() { + cr.extensions + .push(CertReqExtension::AuthorityNames(names)); + } + + let m = Message { + version: ProtocolVersion::TLSv1_3, + payload: MessagePayload::handshake(HandshakeMessagePayload { + typ: HandshakeType::CertificateRequest, + payload: HandshakePayload::CertificateRequestTLS13(cr), + }), + }; + + trace!("Sending CertificateRequest {:?}", m); + transcript.add_message(&m); + cx.common.send_msg(m, true); + Ok(true) + } + + fn emit_certificate_tls13( + transcript: &mut HandshakeHash, + common: &mut CommonState, + cert_chain: &[Certificate], + ocsp_response: Option<&[u8]>, + sct_list: Option<&[u8]>, + ) { + let mut cert_entries = vec![]; + for cert in cert_chain { + let entry = CertificateEntry { + cert: cert.to_owned(), + exts: Vec::new(), + }; + + cert_entries.push(entry); + } + + if let Some(end_entity_cert) = cert_entries.first_mut() { + // Apply OCSP response to first certificate (we don't support OCSP + // except for leaf certs). + if let Some(ocsp) = ocsp_response { + let cst = CertificateStatus::new(ocsp.to_owned()); + end_entity_cert + .exts + .push(CertificateExtension::CertificateStatus(cst)); + } + + // Likewise, SCT + if let Some(sct_list) = sct_list { + end_entity_cert + .exts + .push(CertificateExtension::make_sct(sct_list.to_owned())); + } + } + + let cert_body = CertificatePayloadTLS13::new(cert_entries); + let c = Message { + version: ProtocolVersion::TLSv1_3, + payload: MessagePayload::handshake(HandshakeMessagePayload { + typ: HandshakeType::Certificate, + payload: HandshakePayload::CertificateTLS13(cert_body), + }), + }; + + trace!("sending certificate {:?}", c); + transcript.add_message(&c); + common.send_msg(c, true); + } + + fn emit_certificate_verify_tls13( + transcript: &mut HandshakeHash, + common: &mut CommonState, + signing_key: &dyn sign::SigningKey, + schemes: &[SignatureScheme], + ) -> Result<(), Error> { + let message = verify::construct_tls13_server_verify_message(&transcript.get_current_hash()); + + let signer = signing_key + .choose_scheme(schemes) + .ok_or_else(|| hs::incompatible(common, "no overlapping sigschemes"))?; + + let scheme = signer.scheme(); + let sig = signer.sign(&message)?; + + let cv = DigitallySignedStruct::new(scheme, sig); + + let m = Message { + version: ProtocolVersion::TLSv1_3, + payload: MessagePayload::handshake(HandshakeMessagePayload { + typ: HandshakeType::CertificateVerify, + payload: HandshakePayload::CertificateVerify(cv), + }), + }; + + trace!("sending certificate-verify {:?}", m); + transcript.add_message(&m); + common.send_msg(m, true); + Ok(()) + } + + fn emit_finished_tls13( + transcript: &mut HandshakeHash, + suite: &'static Tls13CipherSuite, + randoms: &ConnectionRandoms, + cx: &mut ServerContext<'_>, + key_schedule: KeyScheduleHandshake, + config: &ServerConfig, + ) -> KeyScheduleTrafficWithClientFinishedPending { + let handshake_hash = transcript.get_current_hash(); + let verify_data = key_schedule.sign_server_finish(&handshake_hash); + let verify_data_payload = Payload::new(verify_data.as_ref()); + + let m = Message { + version: ProtocolVersion::TLSv1_3, + payload: MessagePayload::handshake(HandshakeMessagePayload { + typ: HandshakeType::Finished, + payload: HandshakePayload::Finished(verify_data_payload), + }), + }; + + trace!("sending finished {:?}", m); + transcript.add_message(&m); + let hash_at_server_fin = transcript.get_current_hash(); + cx.common.send_msg(m, true); + + // Now move to application data keys. Read key change is deferred until + // the Finish message is received & validated. + let (key_schedule_traffic, _client_key, server_key) = key_schedule + .into_traffic_with_client_finished_pending( + hash_at_server_fin, + &*config.key_log, + &randoms.client, + ); + cx.common + .record_layer + .set_message_encrypter(suite.derive_encrypter(&server_key)); + + #[cfg(feature = "quic")] + { + cx.common.quic.traffic_secrets = + Some(quic::Secrets::new(_client_key, server_key, suite, false)); + } + + key_schedule_traffic + } +} + +struct ExpectAndSkipRejectedEarlyData { + skip_data_left: usize, + next: Box, +} + +impl State for ExpectAndSkipRejectedEarlyData { + fn handle(mut self: Box, cx: &mut ServerContext<'_>, m: Message) -> hs::NextStateOrError { + /* "The server then ignores early data by skipping all records with an external + * content type of "application_data" (indicating that they are encrypted), + * up to the configured max_early_data_size." + * (RFC8446, 14.2.10) */ + if let MessagePayload::ApplicationData(ref skip_data) = m.payload { + if skip_data.0.len() <= self.skip_data_left { + self.skip_data_left -= skip_data.0.len(); + return Ok(self); + } + } + + self.next.handle(cx, m) + } +} + +struct ExpectCertificate { + config: Arc, + transcript: HandshakeHash, + suite: &'static Tls13CipherSuite, + key_schedule: KeyScheduleTrafficWithClientFinishedPending, + send_ticket: bool, +} + +impl State for ExpectCertificate { + fn handle(mut self: Box, cx: &mut ServerContext<'_>, m: Message) -> hs::NextStateOrError { + let certp = require_handshake_msg!( + m, + HandshakeType::Certificate, + HandshakePayload::CertificateTLS13 + )?; + self.transcript.add_message(&m); + + // We don't send any CertificateRequest extensions, so any extensions + // here are illegal. + if certp.any_entry_has_extension() { + return Err(Error::PeerMisbehavedError( + "client sent unsolicited cert extension".to_string(), + )); + } + + let client_cert = certp.convert(); + + 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()) + })?; + + let (end_entity, intermediates) = match client_cert.split_first() { + None => { + if !mandatory { + debug!("client auth requested but no certificate supplied"); + self.transcript.abandon_client_auth(); + return Ok(Box::new(ExpectFinished { + config: self.config, + suite: self.suite, + key_schedule: self.key_schedule, + transcript: self.transcript, + send_ticket: self.send_ticket, + })); + } + + cx.common + .send_fatal_alert(AlertDescription::CertificateRequired); + return Err(Error::NoCertificatesPresented); + } + Some(chain) => chain, + }; + + 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 + })?; + + Ok(Box::new(ExpectCertificateVerify { + config: self.config, + suite: self.suite, + transcript: self.transcript, + key_schedule: self.key_schedule, + client_cert, + send_ticket: self.send_ticket, + })) + } +} + +struct ExpectCertificateVerify { + config: Arc, + transcript: HandshakeHash, + suite: &'static Tls13CipherSuite, + key_schedule: KeyScheduleTrafficWithClientFinishedPending, + client_cert: Vec, + send_ticket: bool, +} + +impl State for ExpectCertificateVerify { + fn handle(mut self: Box, cx: &mut ServerContext<'_>, m: Message) -> hs::NextStateOrError { + let rc = { + let sig = require_handshake_msg!( + m, + HandshakeType::CertificateVerify, + HandshakePayload::CertificateVerify + )?; + let handshake_hash = self.transcript.get_current_hash(); + self.transcript.abandon_client_auth(); + let certs = &self.client_cert; + let msg = verify::construct_tls13_client_verify_message(&handshake_hash); + + self.config + .verifier + .verify_tls13_signature(&msg, &certs[0], sig) + }; + + 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(ExpectFinished { + config: self.config, + suite: self.suite, + key_schedule: self.key_schedule, + transcript: self.transcript, + send_ticket: self.send_ticket, + })) + } +} + +// --- Process (any number of) early ApplicationData messages, +// followed by a terminating handshake EndOfEarlyData message --- + +struct ExpectEarlyData { + config: Arc, + transcript: HandshakeHash, + suite: &'static Tls13CipherSuite, + key_schedule: KeyScheduleTrafficWithClientFinishedPending, + send_ticket: bool, +} + +impl State for ExpectEarlyData { + fn handle(mut self: Box, cx: &mut ServerContext<'_>, m: Message) -> hs::NextStateOrError { + match m.payload { + MessagePayload::ApplicationData(payload) => { + match cx + .data + .early_data + .take_received_plaintext(payload) + { + true => Ok(self), + false => { + cx.common + .send_fatal_alert(AlertDescription::UnexpectedMessage); + Err(Error::PeerMisbehavedError( + "too much early_data received".into(), + )) + } + } + } + MessagePayload::Handshake { + parsed: + HandshakeMessagePayload { + typ: HandshakeType::EndOfEarlyData, + payload: HandshakePayload::EndOfEarlyData, + }, + .. + } => { + cx.common + .record_layer + .set_message_decrypter( + self.suite + .derive_decrypter(self.key_schedule.client_key()), + ); + + self.transcript.add_message(&m); + Ok(Box::new(ExpectFinished { + config: self.config, + suite: self.suite, + key_schedule: self.key_schedule, + transcript: self.transcript, + send_ticket: self.send_ticket, + })) + } + payload => Err(inappropriate_handshake_message( + &payload, + &[ContentType::ApplicationData, ContentType::Handshake], + &[HandshakeType::EndOfEarlyData], + )), + } + } +} + +// --- Process client's Finished --- +fn get_server_session_value( + transcript: &mut HandshakeHash, + suite: &'static Tls13CipherSuite, + key_schedule: &KeyScheduleTraffic, + cx: &ServerContext<'_>, + nonce: &[u8], + time_now: ticketer::TimeBase, + age_obfuscation_offset: u32, +) -> persist::ServerSessionValue { + let version = ProtocolVersion::TLSv1_3; + + let handshake_hash = transcript.get_current_hash(); + let secret = + key_schedule.resumption_master_secret_and_derive_ticket_psk(&handshake_hash, nonce); + + persist::ServerSessionValue::new( + cx.data.sni.as_ref(), + version, + suite.common.suite, + secret, + cx.common.peer_certificates.clone(), + cx.common.alpn_protocol.clone(), + cx.data.resumption_data.clone(), + time_now, + age_obfuscation_offset, + ) +} + +struct ExpectFinished { + config: Arc, + transcript: HandshakeHash, + suite: &'static Tls13CipherSuite, + key_schedule: KeyScheduleTrafficWithClientFinishedPending, + send_ticket: bool, +} + +impl ExpectFinished { + fn emit_ticket( + transcript: &mut HandshakeHash, + suite: &'static Tls13CipherSuite, + cx: &mut ServerContext<'_>, + key_schedule: &KeyScheduleTraffic, + config: &ServerConfig, + ) -> Result<(), Error> { + let nonce = rand::random_vec(32)?; + let now = ticketer::TimeBase::now()?; + let age_add = rand::random_u32()?; + let plain = + get_server_session_value(transcript, suite, key_schedule, cx, &nonce, now, age_add) + .get_encoding(); + + let stateless = config.ticketer.enabled(); + let (ticket, lifetime) = if stateless { + let ticket = match config.ticketer.encrypt(&plain) { + Some(t) => t, + None => return Ok(()), + }; + (ticket, config.ticketer.lifetime()) + } else { + let id = rand::random_vec(32)?; + let stored = config + .session_storage + .put(id.clone(), plain); + if !stored { + trace!("resumption not available; not issuing ticket"); + return Ok(()); + } + let stateful_lifetime = 24 * 60 * 60; // this is a bit of a punt + (id, stateful_lifetime) + }; + + let mut payload = NewSessionTicketPayloadTLS13::new(lifetime, age_add, nonce, ticket); + + if config.max_early_data_size > 0 { + if !stateless { + payload + .exts + .push(NewSessionTicketExtension::EarlyData( + config.max_early_data_size, + )); + } else { + // We implement RFC8446 section 8.1: by enforcing that 0-RTT is + // only possible if using stateful resumption + warn!("early_data with stateless resumption is not allowed"); + } + } + + let m = Message { + version: ProtocolVersion::TLSv1_3, + payload: MessagePayload::handshake(HandshakeMessagePayload { + typ: HandshakeType::NewSessionTicket, + payload: HandshakePayload::NewSessionTicketTLS13(payload), + }), + }; + + trace!("sending new ticket {:?} (stateless: {})", m, stateless); + transcript.add_message(&m); + cx.common.send_msg(m, true); + Ok(()) + } +} + +impl State for ExpectFinished { + fn handle(mut self: Box, cx: &mut ServerContext<'_>, m: Message) -> hs::NextStateOrError { + let finished = + require_handshake_msg!(m, HandshakeType::Finished, HandshakePayload::Finished)?; + + let handshake_hash = self.transcript.get_current_hash(); + let (key_schedule_traffic, expect_verify_data, client_key) = self + .key_schedule + .sign_client_finish(&handshake_hash); + + let fin = constant_time::verify_slices_are_equal(expect_verify_data.as_ref(), &finished.0) + .map_err(|_| { + cx.common + .send_fatal_alert(AlertDescription::DecryptError); + warn!("Finished wrong"); + Error::DecryptError + }) + .map(|_| verify::FinishedMessageVerified::assertion())?; + + // nb. future derivations include Client Finished, but not the + // main application data keying. + self.transcript.add_message(&m); + + cx.common.check_aligned_handshake()?; + + // Install keying to read future messages. + cx.common + .record_layer + .set_message_decrypter(self.suite.derive_decrypter(&client_key)); + + if self.send_ticket { + Self::emit_ticket( + &mut self.transcript, + self.suite, + cx, + &key_schedule_traffic, + &self.config, + )?; + } + + // Application data may now flow, even if we have client auth enabled. + cx.common.start_traffic(); + + #[cfg(feature = "quic")] + { + if cx.common.protocol == Protocol::Quic { + return Ok(Box::new(ExpectQuicTraffic { + key_schedule: key_schedule_traffic, + _fin_verified: fin, + })); + } + } + + Ok(Box::new(ExpectTraffic { + suite: self.suite, + key_schedule: key_schedule_traffic, + want_write_key_update: false, + _fin_verified: fin, + })) + } +} + +// --- Process traffic --- +struct ExpectTraffic { + suite: &'static Tls13CipherSuite, + key_schedule: KeyScheduleTraffic, + want_write_key_update: bool, + _fin_verified: verify::FinishedMessageVerified, +} + +impl ExpectTraffic { + fn handle_key_update( + &mut self, + common: &mut CommonState, + kur: &KeyUpdateRequest, + ) -> Result<(), Error> { + #[cfg(feature = "quic")] + { + if let Protocol::Quic = common.protocol { + common.send_fatal_alert(AlertDescription::UnexpectedMessage); + let msg = "KeyUpdate received in QUIC connection".to_string(); + warn!("{}", msg); + return Err(Error::PeerMisbehavedError(msg)); + } + } + + common.check_aligned_handshake()?; + + match kur { + KeyUpdateRequest::UpdateNotRequested => {} + KeyUpdateRequest::UpdateRequested => { + self.want_write_key_update = true; + } + _ => { + common.send_fatal_alert(AlertDescription::IllegalParameter); + return Err(Error::CorruptMessagePayload(ContentType::Handshake)); + } + } + + // Update our read-side keys. + let new_read_key = self + .key_schedule + .next_client_application_traffic_secret(); + common + .record_layer + .set_message_decrypter( + self.suite + .derive_decrypter(&new_read_key), + ); + + Ok(()) + } +} + +impl State for ExpectTraffic { + fn handle(mut self: Box, cx: &mut ServerContext, m: Message) -> hs::NextStateOrError { + match m.payload { + MessagePayload::ApplicationData(payload) => cx + .common + .take_received_plaintext(payload), + MessagePayload::Handshake { + parsed: + HandshakeMessagePayload { + payload: HandshakePayload::KeyUpdate(key_update), + .. + }, + .. + } => self.handle_key_update(cx.common, &key_update)?, + payload => { + return Err(inappropriate_handshake_message( + &payload, + &[ContentType::ApplicationData, ContentType::Handshake], + &[HandshakeType::KeyUpdate], + )); + } + } + + Ok(self) + } + + fn export_keying_material( + &self, + output: &mut [u8], + label: &[u8], + context: Option<&[u8]>, + ) -> Result<(), Error> { + self.key_schedule + .export_keying_material(output, label, context) + } + + fn perhaps_write_key_update(&mut self, common: &mut CommonState) { + if self.want_write_key_update { + self.want_write_key_update = false; + common.send_msg_encrypt(Message::build_key_update_notify().into()); + + let write_key = self + .key_schedule + .next_server_application_traffic_secret(); + common + .record_layer + .set_message_encrypter(self.suite.derive_encrypter(&write_key)); + } + } + + #[cfg(feature = "secret_extraction")] + fn extract_secrets(&self) -> Result { + self.key_schedule + .extract_secrets(self.suite.common.aead_algorithm, Side::Server) + } +} + +#[cfg(feature = "quic")] +struct ExpectQuicTraffic { + key_schedule: KeyScheduleTraffic, + _fin_verified: verify::FinishedMessageVerified, +} + +#[cfg(feature = "quic")] +impl State for ExpectQuicTraffic { + fn handle(self: Box, _cx: &mut ServerContext<'_>, m: Message) -> hs::NextStateOrError { + // reject all messages + Err(inappropriate_message(&m.payload, &[])) + } + + fn export_keying_material( + &self, + output: &mut [u8], + label: &[u8], + context: Option<&[u8]>, + ) -> Result<(), Error> { + self.key_schedule + .export_keying_material(output, label, context) + } +} diff --git a/third_party/rustls-fork-shadow-tls/src/sign.rs b/third_party/rustls-fork-shadow-tls/src/sign.rs new file mode 100644 index 0000000..9c916f6 --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/src/sign.rs @@ -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>; + + /// 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, 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, + + /// The certified key. + pub key: Arc, + + /// An optional OCSP response from the certificate issuer, + /// attesting to its continued validity. + pub ocsp: Option>, + + /// 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>, +} + +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: Arc) -> 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, + ) -> 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, 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, 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, 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, +} + +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 { + 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> { + 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, + scheme: SignatureScheme, + encoding: &'static dyn signature::RsaEncoding, +} + +impl RsaSigner { + fn new(key: Arc, scheme: SignatureScheme) -> Box { + 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, 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, + 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 { + 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 { + 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> { + 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, + scheme: SignatureScheme, +} + +impl Signer for EcdsaSigner { + fn sign(&self, message: &[u8]) -> Result, 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, + 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 { + 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> { + 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, + scheme: SignatureScheme, +} + +impl Signer for Ed25519Signer { + fn sign(&self, message: &[u8]) -> Result, 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()); +} diff --git a/third_party/rustls-fork-shadow-tls/src/stream.rs b/third_party/rustls-fork-shadow-tls/src/stream.rs new file mode 100644 index 0000000..70f0fa4 --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/src/stream.rs @@ -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>, + 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>, + T: 'a + Read + Write, + S: SideData, +{ + fn read(&mut self, buf: &mut [u8]) -> 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 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>, + T: 'a + Read + Write, + S: SideData, +{ + fn write(&mut self, buf: &[u8]) -> Result { + 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 { + 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 { + /// Our connection + pub conn: C, + + /// The underlying transport, like a socket + pub sock: T, +} + +impl StreamOwned +where + C: DerefMut + Deref>, + 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 +where + C: DerefMut + Deref>, + 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 Read for StreamOwned +where + C: DerefMut + Deref>, + T: Read + Write, + S: SideData, +{ + fn read(&mut self, buf: &mut [u8]) -> Result { + 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 Write for StreamOwned +where + C: DerefMut + Deref>, + T: Read + Write, + S: SideData, +{ + fn write(&mut self, buf: &[u8]) -> Result { + 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; + } + + #[test] + fn streamowned_can_be_created_for_server_and_tcpstream() { + type _Test = StreamOwned; + } +} diff --git a/third_party/rustls-fork-shadow-tls/src/suites.rs b/third_party/rustls-fork-shadow-tls/src/suites.rs new file mode 100644 index 0000000..8d3590c --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/src/suites.rs @@ -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 { + 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 { + 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 { + 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 { + 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()); + } +} diff --git a/third_party/rustls-fork-shadow-tls/src/testdata/cert-arstechnica.0.der b/third_party/rustls-fork-shadow-tls/src/testdata/cert-arstechnica.0.der new file mode 100644 index 0000000..ac81cd8 Binary files /dev/null and b/third_party/rustls-fork-shadow-tls/src/testdata/cert-arstechnica.0.der differ diff --git a/third_party/rustls-fork-shadow-tls/src/testdata/cert-arstechnica.1.der b/third_party/rustls-fork-shadow-tls/src/testdata/cert-arstechnica.1.der new file mode 100644 index 0000000..93f1fb0 Binary files /dev/null and b/third_party/rustls-fork-shadow-tls/src/testdata/cert-arstechnica.1.der differ diff --git a/third_party/rustls-fork-shadow-tls/src/testdata/cert-arstechnica.2.der b/third_party/rustls-fork-shadow-tls/src/testdata/cert-arstechnica.2.der new file mode 100644 index 0000000..1dfb0e7 Binary files /dev/null and b/third_party/rustls-fork-shadow-tls/src/testdata/cert-arstechnica.2.der differ diff --git a/third_party/rustls-fork-shadow-tls/src/testdata/cert-arstechnica.3.der b/third_party/rustls-fork-shadow-tls/src/testdata/cert-arstechnica.3.der new file mode 100644 index 0000000..75df0cc Binary files /dev/null and b/third_party/rustls-fork-shadow-tls/src/testdata/cert-arstechnica.3.der differ diff --git a/third_party/rustls-fork-shadow-tls/src/testdata/cert-duckduckgo.0.der b/third_party/rustls-fork-shadow-tls/src/testdata/cert-duckduckgo.0.der new file mode 100644 index 0000000..9f8267c Binary files /dev/null and b/third_party/rustls-fork-shadow-tls/src/testdata/cert-duckduckgo.0.der differ diff --git a/third_party/rustls-fork-shadow-tls/src/testdata/cert-duckduckgo.1.der b/third_party/rustls-fork-shadow-tls/src/testdata/cert-duckduckgo.1.der new file mode 100644 index 0000000..dd6a50f Binary files /dev/null and b/third_party/rustls-fork-shadow-tls/src/testdata/cert-duckduckgo.1.der differ diff --git a/third_party/rustls-fork-shadow-tls/src/testdata/cert-github.0.der b/third_party/rustls-fork-shadow-tls/src/testdata/cert-github.0.der new file mode 100644 index 0000000..86d6fce Binary files /dev/null and b/third_party/rustls-fork-shadow-tls/src/testdata/cert-github.0.der differ diff --git a/third_party/rustls-fork-shadow-tls/src/testdata/cert-github.1.der b/third_party/rustls-fork-shadow-tls/src/testdata/cert-github.1.der new file mode 100644 index 0000000..78a66bb Binary files /dev/null and b/third_party/rustls-fork-shadow-tls/src/testdata/cert-github.1.der differ diff --git a/third_party/rustls-fork-shadow-tls/src/testdata/cert-google.0.der b/third_party/rustls-fork-shadow-tls/src/testdata/cert-google.0.der new file mode 100644 index 0000000..e8c41b2 Binary files /dev/null and b/third_party/rustls-fork-shadow-tls/src/testdata/cert-google.0.der differ diff --git a/third_party/rustls-fork-shadow-tls/src/testdata/cert-google.1.der b/third_party/rustls-fork-shadow-tls/src/testdata/cert-google.1.der new file mode 100644 index 0000000..6671504 Binary files /dev/null and b/third_party/rustls-fork-shadow-tls/src/testdata/cert-google.1.der differ diff --git a/third_party/rustls-fork-shadow-tls/src/testdata/cert-google.2.der b/third_party/rustls-fork-shadow-tls/src/testdata/cert-google.2.der new file mode 100644 index 0000000..fd888ec Binary files /dev/null and b/third_party/rustls-fork-shadow-tls/src/testdata/cert-google.2.der differ diff --git a/third_party/rustls-fork-shadow-tls/src/testdata/cert-hn.0.der b/third_party/rustls-fork-shadow-tls/src/testdata/cert-hn.0.der new file mode 100644 index 0000000..bc42b61 Binary files /dev/null and b/third_party/rustls-fork-shadow-tls/src/testdata/cert-hn.0.der differ diff --git a/third_party/rustls-fork-shadow-tls/src/testdata/cert-hn.1.der b/third_party/rustls-fork-shadow-tls/src/testdata/cert-hn.1.der new file mode 100644 index 0000000..dd6a50f Binary files /dev/null and b/third_party/rustls-fork-shadow-tls/src/testdata/cert-hn.1.der differ diff --git a/third_party/rustls-fork-shadow-tls/src/testdata/cert-reddit.0.der b/third_party/rustls-fork-shadow-tls/src/testdata/cert-reddit.0.der new file mode 100644 index 0000000..3a26d36 Binary files /dev/null and b/third_party/rustls-fork-shadow-tls/src/testdata/cert-reddit.0.der differ diff --git a/third_party/rustls-fork-shadow-tls/src/testdata/cert-reddit.1.der b/third_party/rustls-fork-shadow-tls/src/testdata/cert-reddit.1.der new file mode 100644 index 0000000..dd6a50f Binary files /dev/null and b/third_party/rustls-fork-shadow-tls/src/testdata/cert-reddit.1.der differ diff --git a/third_party/rustls-fork-shadow-tls/src/testdata/cert-rustlang.0.der b/third_party/rustls-fork-shadow-tls/src/testdata/cert-rustlang.0.der new file mode 100644 index 0000000..3af1cad Binary files /dev/null and b/third_party/rustls-fork-shadow-tls/src/testdata/cert-rustlang.0.der differ diff --git a/third_party/rustls-fork-shadow-tls/src/testdata/cert-rustlang.1.der b/third_party/rustls-fork-shadow-tls/src/testdata/cert-rustlang.1.der new file mode 100644 index 0000000..93f1fb0 Binary files /dev/null and b/third_party/rustls-fork-shadow-tls/src/testdata/cert-rustlang.1.der differ diff --git a/third_party/rustls-fork-shadow-tls/src/testdata/cert-rustlang.2.der b/third_party/rustls-fork-shadow-tls/src/testdata/cert-rustlang.2.der new file mode 100644 index 0000000..1dfb0e7 Binary files /dev/null and b/third_party/rustls-fork-shadow-tls/src/testdata/cert-rustlang.2.der differ diff --git a/third_party/rustls-fork-shadow-tls/src/testdata/cert-rustlang.3.der b/third_party/rustls-fork-shadow-tls/src/testdata/cert-rustlang.3.der new file mode 100644 index 0000000..75df0cc Binary files /dev/null and b/third_party/rustls-fork-shadow-tls/src/testdata/cert-rustlang.3.der differ diff --git a/third_party/rustls-fork-shadow-tls/src/testdata/cert-servo.0.der b/third_party/rustls-fork-shadow-tls/src/testdata/cert-servo.0.der new file mode 100644 index 0000000..0b6271f Binary files /dev/null and b/third_party/rustls-fork-shadow-tls/src/testdata/cert-servo.0.der differ diff --git a/third_party/rustls-fork-shadow-tls/src/testdata/cert-servo.1.der b/third_party/rustls-fork-shadow-tls/src/testdata/cert-servo.1.der new file mode 100644 index 0000000..41c7421 Binary files /dev/null and b/third_party/rustls-fork-shadow-tls/src/testdata/cert-servo.1.der differ diff --git a/third_party/rustls-fork-shadow-tls/src/testdata/cert-stackoverflow.0.der b/third_party/rustls-fork-shadow-tls/src/testdata/cert-stackoverflow.0.der new file mode 100644 index 0000000..68068a7 Binary files /dev/null and b/third_party/rustls-fork-shadow-tls/src/testdata/cert-stackoverflow.0.der differ diff --git a/third_party/rustls-fork-shadow-tls/src/testdata/cert-stackoverflow.1.der b/third_party/rustls-fork-shadow-tls/src/testdata/cert-stackoverflow.1.der new file mode 100644 index 0000000..2d66ea7 Binary files /dev/null and b/third_party/rustls-fork-shadow-tls/src/testdata/cert-stackoverflow.1.der differ diff --git a/third_party/rustls-fork-shadow-tls/src/testdata/cert-stackoverflow.2.der b/third_party/rustls-fork-shadow-tls/src/testdata/cert-stackoverflow.2.der new file mode 100644 index 0000000..79a33ba Binary files /dev/null and b/third_party/rustls-fork-shadow-tls/src/testdata/cert-stackoverflow.2.der differ diff --git a/third_party/rustls-fork-shadow-tls/src/testdata/cert-twitter.0.der b/third_party/rustls-fork-shadow-tls/src/testdata/cert-twitter.0.der new file mode 100644 index 0000000..36f4e06 Binary files /dev/null and b/third_party/rustls-fork-shadow-tls/src/testdata/cert-twitter.0.der differ diff --git a/third_party/rustls-fork-shadow-tls/src/testdata/cert-twitter.1.der b/third_party/rustls-fork-shadow-tls/src/testdata/cert-twitter.1.der new file mode 100644 index 0000000..608f16c Binary files /dev/null and b/third_party/rustls-fork-shadow-tls/src/testdata/cert-twitter.1.der differ diff --git a/third_party/rustls-fork-shadow-tls/src/testdata/cert-wapo.0.der b/third_party/rustls-fork-shadow-tls/src/testdata/cert-wapo.0.der new file mode 100644 index 0000000..94d2cd9 Binary files /dev/null and b/third_party/rustls-fork-shadow-tls/src/testdata/cert-wapo.0.der differ diff --git a/third_party/rustls-fork-shadow-tls/src/testdata/cert-wapo.1.der b/third_party/rustls-fork-shadow-tls/src/testdata/cert-wapo.1.der new file mode 100644 index 0000000..99ced21 Binary files /dev/null and b/third_party/rustls-fork-shadow-tls/src/testdata/cert-wapo.1.der differ diff --git a/third_party/rustls-fork-shadow-tls/src/testdata/cert-wikipedia.0.der b/third_party/rustls-fork-shadow-tls/src/testdata/cert-wikipedia.0.der new file mode 100644 index 0000000..5452038 Binary files /dev/null and b/third_party/rustls-fork-shadow-tls/src/testdata/cert-wikipedia.0.der differ diff --git a/third_party/rustls-fork-shadow-tls/src/testdata/cert-wikipedia.1.der b/third_party/rustls-fork-shadow-tls/src/testdata/cert-wikipedia.1.der new file mode 100644 index 0000000..7d8413a Binary files /dev/null and b/third_party/rustls-fork-shadow-tls/src/testdata/cert-wikipedia.1.der differ diff --git a/third_party/rustls-fork-shadow-tls/src/testdata/deframer-empty-applicationdata.bin b/third_party/rustls-fork-shadow-tls/src/testdata/deframer-empty-applicationdata.bin new file mode 100644 index 0000000..ff11f47 Binary files /dev/null and b/third_party/rustls-fork-shadow-tls/src/testdata/deframer-empty-applicationdata.bin differ diff --git a/third_party/rustls-fork-shadow-tls/src/testdata/deframer-invalid-contenttype.bin b/third_party/rustls-fork-shadow-tls/src/testdata/deframer-invalid-contenttype.bin new file mode 100644 index 0000000..813cc67 Binary files /dev/null and b/third_party/rustls-fork-shadow-tls/src/testdata/deframer-invalid-contenttype.bin differ diff --git a/third_party/rustls-fork-shadow-tls/src/testdata/deframer-invalid-empty.bin b/third_party/rustls-fork-shadow-tls/src/testdata/deframer-invalid-empty.bin new file mode 100644 index 0000000..4739075 Binary files /dev/null and b/third_party/rustls-fork-shadow-tls/src/testdata/deframer-invalid-empty.bin differ diff --git a/third_party/rustls-fork-shadow-tls/src/testdata/deframer-invalid-length.bin b/third_party/rustls-fork-shadow-tls/src/testdata/deframer-invalid-length.bin new file mode 100644 index 0000000..675415f Binary files /dev/null and b/third_party/rustls-fork-shadow-tls/src/testdata/deframer-invalid-length.bin differ diff --git a/third_party/rustls-fork-shadow-tls/src/testdata/deframer-invalid-version.bin b/third_party/rustls-fork-shadow-tls/src/testdata/deframer-invalid-version.bin new file mode 100644 index 0000000..5c18e17 Binary files /dev/null and b/third_party/rustls-fork-shadow-tls/src/testdata/deframer-invalid-version.bin differ diff --git a/third_party/rustls-fork-shadow-tls/src/testdata/deframer-test.1.bin b/third_party/rustls-fork-shadow-tls/src/testdata/deframer-test.1.bin new file mode 100644 index 0000000..7040726 Binary files /dev/null and b/third_party/rustls-fork-shadow-tls/src/testdata/deframer-test.1.bin differ diff --git a/third_party/rustls-fork-shadow-tls/src/testdata/deframer-test.2.bin b/third_party/rustls-fork-shadow-tls/src/testdata/deframer-test.2.bin new file mode 100644 index 0000000..ba7a178 Binary files /dev/null and b/third_party/rustls-fork-shadow-tls/src/testdata/deframer-test.2.bin differ diff --git a/third_party/rustls-fork-shadow-tls/src/testdata/eddsakey.der b/third_party/rustls-fork-shadow-tls/src/testdata/eddsakey.der new file mode 100644 index 0000000..8eff00d Binary files /dev/null and b/third_party/rustls-fork-shadow-tls/src/testdata/eddsakey.der differ diff --git a/third_party/rustls-fork-shadow-tls/src/testdata/nistp256key.der b/third_party/rustls-fork-shadow-tls/src/testdata/nistp256key.der new file mode 100644 index 0000000..5755182 Binary files /dev/null and b/third_party/rustls-fork-shadow-tls/src/testdata/nistp256key.der differ diff --git a/third_party/rustls-fork-shadow-tls/src/testdata/nistp256key.pkcs8.der b/third_party/rustls-fork-shadow-tls/src/testdata/nistp256key.pkcs8.der new file mode 100644 index 0000000..8e64b2c Binary files /dev/null and b/third_party/rustls-fork-shadow-tls/src/testdata/nistp256key.pkcs8.der differ diff --git a/third_party/rustls-fork-shadow-tls/src/testdata/nistp384key.der b/third_party/rustls-fork-shadow-tls/src/testdata/nistp384key.der new file mode 100644 index 0000000..80f0769 Binary files /dev/null and b/third_party/rustls-fork-shadow-tls/src/testdata/nistp384key.der differ diff --git a/third_party/rustls-fork-shadow-tls/src/testdata/nistp384key.pkcs8.der b/third_party/rustls-fork-shadow-tls/src/testdata/nistp384key.pkcs8.der new file mode 100644 index 0000000..f85de43 Binary files /dev/null and b/third_party/rustls-fork-shadow-tls/src/testdata/nistp384key.pkcs8.der differ diff --git a/third_party/rustls-fork-shadow-tls/src/testdata/prf-result.1.bin b/third_party/rustls-fork-shadow-tls/src/testdata/prf-result.1.bin new file mode 100644 index 0000000..a066b9c --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/src/testdata/prf-result.1.bin @@ -0,0 +1 @@ +ãò)ºr{á{& U|ÔSª²ÃÔ•2›RÔæÛZk0‘é 5ÉɤkNºù¯ "÷}ï«ý7—ÀVK«O¼‘fnï›—üãOyg‰º¤€‚Ñ"îBŧ.ZQÿ÷‡4{f \ No newline at end of file diff --git a/third_party/rustls-fork-shadow-tls/src/testdata/prf-result.2.bin b/third_party/rustls-fork-shadow-tls/src/testdata/prf-result.2.bin new file mode 100644 index 0000000..799eeca --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/src/testdata/prf-result.2.bin @@ -0,0 +1 @@ +aõˆÇ˜ÅÂÿnzœµíÍãùLfš*F8×Õ²ƒ-öx˜uÇ~m†‹Ç\Eâ´ ô¡q;'7hC%’÷ÜŽ¨ï">ê…„¿he= ü@VØð%Ä]ߦæþÇðT´ ÖòУ#>I¤>uÅcí¾"þ%N3¡°éö¹‚fu¾ÇЄVXÜœ9uE@@¹ôlz@á¸ø ¦ 9z(¿õÒïPfhBû¤v2½µOöc?†»È6æ@ÔØ˜ \ No newline at end of file diff --git a/third_party/rustls-fork-shadow-tls/src/testdata/rsa2048key.pkcs1.der b/third_party/rustls-fork-shadow-tls/src/testdata/rsa2048key.pkcs1.der new file mode 100644 index 0000000..d93402d Binary files /dev/null and b/third_party/rustls-fork-shadow-tls/src/testdata/rsa2048key.pkcs1.der differ diff --git a/third_party/rustls-fork-shadow-tls/src/testdata/rsa2048key.pkcs8.der b/third_party/rustls-fork-shadow-tls/src/testdata/rsa2048key.pkcs8.der new file mode 100644 index 0000000..8e5c2e8 Binary files /dev/null and b/third_party/rustls-fork-shadow-tls/src/testdata/rsa2048key.pkcs8.der differ diff --git a/third_party/rustls-fork-shadow-tls/src/ticketer.rs b/third_party/rustls-fork-shadow-tls/src/ticketer.rs new file mode 100644 index 0000000..9660d71 --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/src/ticketer.rs @@ -0,0 +1,338 @@ +use crate::rand; +use crate::server::ProducesTickets; +use crate::Error; + +use ring::aead; +use std::mem; +use std::sync::{Arc, Mutex, MutexGuard}; +use std::time; + +/// The timebase for expiring and rolling tickets and ticketing +/// keys. This is UNIX wall time in seconds. +/// +/// This is guaranteed to be on or after the UNIX epoch. +#[derive(Clone, Copy, Debug)] +pub struct TimeBase(time::Duration); + +impl TimeBase { + #[inline] + pub fn now() -> Result { + Ok(Self( + time::SystemTime::now().duration_since(time::UNIX_EPOCH)?, + )) + } + + #[inline] + pub fn as_secs(&self) -> u64 { + self.0.as_secs() + } +} + +/// This is a `ProducesTickets` implementation which uses +/// any *ring* `aead::Algorithm` to encrypt and authentication +/// the ticket payload. It does not enforce any lifetime +/// constraint. +struct AeadTicketer { + alg: &'static aead::Algorithm, + key: aead::LessSafeKey, + lifetime: u32, +} + +impl AeadTicketer { + /// Make a ticketer with recommended configuration and a random key. + fn new() -> Result { + let mut key = [0u8; 32]; + rand::fill_random(&mut key)?; + + let alg = &aead::CHACHA20_POLY1305; + let key = aead::UnboundKey::new(alg, &key).unwrap(); + + Ok(Self { + alg, + key: aead::LessSafeKey::new(key), + lifetime: 60 * 60 * 12, + }) + } +} + +impl ProducesTickets for AeadTicketer { + fn enabled(&self) -> bool { + true + } + fn lifetime(&self) -> u32 { + self.lifetime + } + + /// Encrypt `message` and return the ciphertext. + fn encrypt(&self, message: &[u8]) -> Option> { + // Random nonce, because a counter is a privacy leak. + let mut nonce_buf = [0u8; 12]; + rand::fill_random(&mut nonce_buf).ok()?; + let nonce = ring::aead::Nonce::assume_unique_for_key(nonce_buf); + let aad = ring::aead::Aad::empty(); + + let mut ciphertext = + Vec::with_capacity(nonce_buf.len() + message.len() + self.key.algorithm().tag_len()); + ciphertext.extend(nonce_buf); + ciphertext.extend(message); + self.key + .seal_in_place_separate_tag(nonce, aad, &mut ciphertext[nonce_buf.len()..]) + .map(|tag| { + ciphertext.extend(tag.as_ref()); + ciphertext + }) + .ok() + } + + /// Decrypt `ciphertext` and recover the original message. + fn decrypt(&self, ciphertext: &[u8]) -> Option> { + // Non-panicking `let (nonce, ciphertext) = ciphertext.split_at(...)`. + let nonce = ciphertext.get(..self.alg.nonce_len())?; + let ciphertext = ciphertext.get(nonce.len()..)?; + + // This won't fail since `nonce` has the required length. + let nonce = ring::aead::Nonce::try_assume_unique_for_key(nonce).ok()?; + + let mut out = Vec::from(ciphertext); + + let plain_len = self + .key + .open_in_place(nonce, aead::Aad::empty(), &mut out) + .ok()? + .len(); + out.truncate(plain_len); + + Some(out) + } +} + +struct TicketSwitcherState { + next: Option>, + current: Box, + previous: Option>, + next_switch_time: u64, +} + +/// A ticketer that has a 'current' sub-ticketer and a single +/// 'previous' ticketer. It creates a new ticketer every so +/// often, demoting the current ticketer. +struct TicketSwitcher { + generator: fn() -> Result, rand::GetRandomFailed>, + lifetime: u32, + state: Mutex, +} + +impl TicketSwitcher { + /// `lifetime` is in seconds, and is how long the current ticketer + /// is used to generate new tickets. Tickets are accepted for no + /// longer than twice this duration. `generator` produces a new + /// `ProducesTickets` implementation. + fn new( + lifetime: u32, + generator: fn() -> Result, rand::GetRandomFailed>, + ) -> Result { + let now = TimeBase::now()?; + Ok(Self { + generator, + lifetime, + state: Mutex::new(TicketSwitcherState { + next: Some(generator()?), + current: generator()?, + previous: None, + next_switch_time: now + .as_secs() + .saturating_add(u64::from(lifetime)), + }), + }) + } + + /// If it's time, demote the `current` ticketer to `previous` (so it + /// does no new encryptions but can do decryption) and use next for a + /// new `current` ticketer. + /// + /// Calling this regularly will ensure timely key erasure. Otherwise, + /// key erasure will be delayed until the next encrypt/decrypt call. + /// + /// For efficiency, this is also responsible for locking the state mutex + /// and returning the mutexguard. + fn maybe_roll(&self, now: TimeBase) -> Option> { + // The code below aims to make switching as efficient as possible + // in the common case that the generator never fails. To achieve this + // we run the following steps: + // 1. If no switch is necessary, just return the mutexguard + // 2. Shift over all of the ticketers (so current becomes previous, + // and next becomes current). After this, other threads can + // start using the new current ticketer. + // 3. unlock mutex and generate new ticketer. + // 4. Place new ticketer in next and return current + // + // There are a few things to note here. First, we don't check whether + // a new switch might be needed in step 4, even though, due to locking + // and entropy collection, significant amounts of time may have passed. + // This is to guarantee that the thread doing the switch will eventually + // make progress. + // + // Second, because next may be None, step 2 can fail. In that case + // we enter a recovery mode where we generate 2 new ticketers, one for + // next and one for the current ticketer. We then take the mutex a + // second time and redo the time check to see if a switch is still + // necessary. + // + // This somewhat convoluted approach ensures good availability of the + // mutex, by ensuring that the state is usable and the mutex not held + // during generation. It also ensures that, so long as the inner + // ticketer never generates panics during encryption/decryption, + // we are guaranteed to never panic when holding the mutex. + + let now = now.as_secs(); + let mut are_recovering = false; // Are we recovering from previous failure? + { + // Scope the mutex so we only take it for as long as needed + let mut state = self.state.lock().ok()?; + + // Fast path in case we do not need to switch to the next ticketer yet + if now <= state.next_switch_time { + return Some(state); + } + + // Make the switch, or mark for recovery if not possible + if let Some(next) = state.next.take() { + state.previous = Some(mem::replace(&mut state.current, next)); + state.next_switch_time = now.saturating_add(u64::from(self.lifetime)); + } else { + are_recovering = true; + } + } + + // We always need a next, so generate it now + let next = (self.generator)().ok()?; + if !are_recovering { + // Normal path, generate new next and place it in the state + let mut state = self.state.lock().ok()?; + state.next = Some(next); + Some(state) + } else { + // Recovering, generate also a new current ticketer, and modify state + // as needed. (we need to redo the time check, otherwise this might + // result in very rapid switching of ticketers) + let new_current = (self.generator)().ok()?; + let mut state = self.state.lock().ok()?; + state.next = Some(next); + if now > state.next_switch_time { + state.previous = Some(mem::replace(&mut state.current, new_current)); + state.next_switch_time = now.saturating_add(u64::from(self.lifetime)); + } + Some(state) + } + } +} + +impl ProducesTickets for TicketSwitcher { + fn lifetime(&self) -> u32 { + self.lifetime * 2 + } + + fn enabled(&self) -> bool { + true + } + + fn encrypt(&self, message: &[u8]) -> Option> { + let state = self.maybe_roll(TimeBase::now().ok()?)?; + + state.current.encrypt(message) + } + + fn decrypt(&self, ciphertext: &[u8]) -> Option> { + let state = self.maybe_roll(TimeBase::now().ok()?)?; + + // Decrypt with the current key; if that fails, try with the previous. + state + .current + .decrypt(ciphertext) + .or_else(|| { + state + .previous + .as_ref() + .and_then(|previous| previous.decrypt(ciphertext)) + }) + } +} + +/// A concrete, safe ticket creation mechanism. +pub struct Ticketer {} + +fn generate_inner() -> Result, rand::GetRandomFailed> { + Ok(Box::new(AeadTicketer::new()?)) +} + +impl Ticketer { + /// Make the recommended Ticketer. This produces tickets + /// with a 12 hour life and randomly generated keys. + /// + /// The encryption mechanism used in Chacha20Poly1305. + pub fn new() -> Result, Error> { + Ok(Arc::new(TicketSwitcher::new(6 * 60 * 60, generate_inner)?)) + } +} + +#[test] +fn basic_pairwise_test() { + let t = Ticketer::new().unwrap(); + assert!(t.enabled()); + let cipher = t.encrypt(b"hello world").unwrap(); + let plain = t.decrypt(&cipher).unwrap(); + assert_eq!(plain, b"hello world"); +} + +#[test] +fn ticketswitcher_switching_test() { + let t = Arc::new(TicketSwitcher::new(1, generate_inner).unwrap()); + let now = TimeBase::now().unwrap(); + let cipher1 = t.encrypt(b"ticket 1").unwrap(); + assert_eq!(t.decrypt(&cipher1).unwrap(), b"ticket 1"); + { + // Trigger new ticketer + t.maybe_roll(TimeBase(now.0 + std::time::Duration::from_secs(10))); + } + let cipher2 = t.encrypt(b"ticket 2").unwrap(); + assert_eq!(t.decrypt(&cipher1).unwrap(), b"ticket 1"); + assert_eq!(t.decrypt(&cipher2).unwrap(), b"ticket 2"); + { + // Trigger new ticketer + t.maybe_roll(TimeBase(now.0 + std::time::Duration::from_secs(20))); + } + let cipher3 = t.encrypt(b"ticket 3").unwrap(); + assert!(t.decrypt(&cipher1).is_none()); + assert_eq!(t.decrypt(&cipher2).unwrap(), b"ticket 2"); + assert_eq!(t.decrypt(&cipher3).unwrap(), b"ticket 3"); +} + +#[cfg(test)] +fn fail_generator() -> Result, rand::GetRandomFailed> { + Err(rand::GetRandomFailed) +} + +#[test] +fn ticketswitcher_recover_test() { + let mut t = TicketSwitcher::new(1, generate_inner).unwrap(); + let now = TimeBase::now().unwrap(); + let cipher1 = t.encrypt(b"ticket 1").unwrap(); + assert_eq!(t.decrypt(&cipher1).unwrap(), b"ticket 1"); + t.generator = fail_generator; + { + // Failed new ticketer + t.maybe_roll(TimeBase(now.0 + std::time::Duration::from_secs(10))); + } + t.generator = generate_inner; + let cipher2 = t.encrypt(b"ticket 2").unwrap(); + assert_eq!(t.decrypt(&cipher1).unwrap(), b"ticket 1"); + assert_eq!(t.decrypt(&cipher2).unwrap(), b"ticket 2"); + { + // recover + t.maybe_roll(TimeBase(now.0 + std::time::Duration::from_secs(20))); + } + let cipher3 = t.encrypt(b"ticket 3").unwrap(); + assert!(t.decrypt(&cipher1).is_none()); + assert_eq!(t.decrypt(&cipher2).unwrap(), b"ticket 2"); + assert_eq!(t.decrypt(&cipher3).unwrap(), b"ticket 3"); +} diff --git a/third_party/rustls-fork-shadow-tls/src/tls12/cipher.rs b/third_party/rustls-fork-shadow-tls/src/tls12/cipher.rs new file mode 100644 index 0000000..2910c20 --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/src/tls12/cipher.rs @@ -0,0 +1,236 @@ +use crate::cipher::{make_nonce, Iv, MessageDecrypter, MessageEncrypter}; +use crate::enums::ProtocolVersion; +use crate::error::Error; +use crate::msgs::base::Payload; +use crate::msgs::codec; +use crate::msgs::enums::ContentType; +use crate::msgs::fragmenter::MAX_FRAGMENT_LEN; +use crate::msgs::message::{BorrowedPlainMessage, OpaqueMessage, PlainMessage}; + +use ring::aead; + +const TLS12_AAD_SIZE: usize = 8 + 1 + 2 + 2; + +fn make_tls12_aad( + seq: u64, + typ: ContentType, + vers: ProtocolVersion, + len: usize, +) -> ring::aead::Aad<[u8; TLS12_AAD_SIZE]> { + let mut out = [0; TLS12_AAD_SIZE]; + codec::put_u64(seq, &mut out[0..]); + out[8] = typ.get_u8(); + codec::put_u16(vers.get_u16(), &mut out[9..]); + codec::put_u16(len as u16, &mut out[11..]); + ring::aead::Aad::from(out) +} + +pub(crate) struct AesGcm; + +impl Tls12AeadAlgorithm for AesGcm { + fn decrypter(&self, dec_key: aead::LessSafeKey, dec_iv: &[u8]) -> Box { + let mut ret = GcmMessageDecrypter { + dec_key, + dec_salt: [0u8; 4], + }; + + debug_assert_eq!(dec_iv.len(), 4); + ret.dec_salt.copy_from_slice(dec_iv); + Box::new(ret) + } + + fn encrypter( + &self, + enc_key: aead::LessSafeKey, + write_iv: &[u8], + explicit: &[u8], + ) -> Box { + debug_assert_eq!(write_iv.len(), 4); + debug_assert_eq!(explicit.len(), 8); + + // The GCM nonce is constructed from a 32-bit 'salt' derived + // from the master-secret, and a 64-bit explicit part, + // with no specified construction. Thanks for that. + // + // We use the same construction as TLS1.3/ChaCha20Poly1305: + // a starting point extracted from the key block, xored with + // the sequence number. + let mut iv = Iv(Default::default()); + iv.0[..4].copy_from_slice(write_iv); + iv.0[4..].copy_from_slice(explicit); + + Box::new(GcmMessageEncrypter { enc_key, iv }) + } +} + +pub(crate) struct ChaCha20Poly1305; + +impl Tls12AeadAlgorithm for ChaCha20Poly1305 { + fn decrypter(&self, dec_key: aead::LessSafeKey, iv: &[u8]) -> Box { + Box::new(ChaCha20Poly1305MessageDecrypter { + dec_key, + dec_offset: Iv::copy(iv), + }) + } + + fn encrypter( + &self, + enc_key: aead::LessSafeKey, + enc_iv: &[u8], + _: &[u8], + ) -> Box { + Box::new(ChaCha20Poly1305MessageEncrypter { + enc_key, + enc_offset: Iv::copy(enc_iv), + }) + } +} + +pub(crate) trait Tls12AeadAlgorithm: Send + Sync + 'static { + fn decrypter(&self, key: aead::LessSafeKey, iv: &[u8]) -> Box; + fn encrypter( + &self, + key: aead::LessSafeKey, + iv: &[u8], + extra: &[u8], + ) -> Box; +} + +/// A `MessageEncrypter` for AES-GCM AEAD ciphersuites. TLS 1.2 only. +struct GcmMessageEncrypter { + enc_key: aead::LessSafeKey, + iv: Iv, +} + +/// A `MessageDecrypter` for AES-GCM AEAD ciphersuites. TLS1.2 only. +struct GcmMessageDecrypter { + dec_key: aead::LessSafeKey, + dec_salt: [u8; 4], +} + +const GCM_EXPLICIT_NONCE_LEN: usize = 8; +const GCM_OVERHEAD: usize = GCM_EXPLICIT_NONCE_LEN + 16; + +impl MessageDecrypter for GcmMessageDecrypter { + fn decrypt(&self, mut msg: OpaqueMessage, seq: u64) -> Result { + let payload = &mut msg.payload.0; + if payload.len() < GCM_OVERHEAD { + return Err(Error::DecryptError); + } + + let nonce = { + let mut nonce = [0u8; 12]; + nonce[..4].copy_from_slice(&self.dec_salt); + nonce[4..].copy_from_slice(&payload[..8]); + aead::Nonce::assume_unique_for_key(nonce) + }; + + let aad = make_tls12_aad(seq, msg.typ, msg.version, payload.len() - GCM_OVERHEAD); + + let plain_len = self + .dec_key + .open_within(nonce, aad, payload, GCM_EXPLICIT_NONCE_LEN..) + .map_err(|_| Error::DecryptError)? + .len(); + + if plain_len > MAX_FRAGMENT_LEN { + return Err(Error::PeerSentOversizedRecord); + } + + payload.truncate(plain_len); + Ok(msg.into_plain_message()) + } +} + +impl MessageEncrypter for GcmMessageEncrypter { + fn encrypt(&self, msg: BorrowedPlainMessage, seq: u64) -> Result { + let nonce = make_nonce(&self.iv, seq); + let aad = make_tls12_aad(seq, msg.typ, msg.version, msg.payload.len()); + + let total_len = msg.payload.len() + self.enc_key.algorithm().tag_len(); + let mut payload = Vec::with_capacity(GCM_EXPLICIT_NONCE_LEN + total_len); + payload.extend_from_slice(&nonce.as_ref()[4..]); + payload.extend_from_slice(msg.payload); + + self.enc_key + .seal_in_place_separate_tag(nonce, aad, &mut payload[GCM_EXPLICIT_NONCE_LEN..]) + .map(|tag| payload.extend(tag.as_ref())) + .map_err(|_| Error::General("encrypt failed".to_string()))?; + + Ok(OpaqueMessage { + typ: msg.typ, + version: msg.version, + payload: Payload::new(payload), + }) + } +} + +/// The RFC7905/RFC7539 ChaCha20Poly1305 construction. +/// This implementation does the AAD construction required in TLS1.2. +/// TLS1.3 uses `TLS13MessageEncrypter`. +struct ChaCha20Poly1305MessageEncrypter { + enc_key: aead::LessSafeKey, + enc_offset: Iv, +} + +/// The RFC7905/RFC7539 ChaCha20Poly1305 construction. +/// This implementation does the AAD construction required in TLS1.2. +/// TLS1.3 uses `TLS13MessageDecrypter`. +struct ChaCha20Poly1305MessageDecrypter { + dec_key: aead::LessSafeKey, + dec_offset: Iv, +} + +const CHACHAPOLY1305_OVERHEAD: usize = 16; + +impl MessageDecrypter for ChaCha20Poly1305MessageDecrypter { + fn decrypt(&self, mut msg: OpaqueMessage, seq: u64) -> Result { + let payload = &mut msg.payload.0; + + if payload.len() < CHACHAPOLY1305_OVERHEAD { + return Err(Error::DecryptError); + } + + let nonce = make_nonce(&self.dec_offset, seq); + let aad = make_tls12_aad( + seq, + msg.typ, + msg.version, + payload.len() - CHACHAPOLY1305_OVERHEAD, + ); + + let plain_len = self + .dec_key + .open_in_place(nonce, aad, payload) + .map_err(|_| Error::DecryptError)? + .len(); + + if plain_len > MAX_FRAGMENT_LEN { + return Err(Error::PeerSentOversizedRecord); + } + + payload.truncate(plain_len); + Ok(msg.into_plain_message()) + } +} + +impl MessageEncrypter for ChaCha20Poly1305MessageEncrypter { + fn encrypt(&self, msg: BorrowedPlainMessage, seq: u64) -> Result { + let nonce = make_nonce(&self.enc_offset, seq); + let aad = make_tls12_aad(seq, msg.typ, msg.version, msg.payload.len()); + + let total_len = msg.payload.len() + self.enc_key.algorithm().tag_len(); + let mut buf = Vec::with_capacity(total_len); + buf.extend_from_slice(msg.payload); + + self.enc_key + .seal_in_place_append_tag(nonce, aad, &mut buf) + .map_err(|_| Error::General("encrypt failed".to_string()))?; + + Ok(OpaqueMessage { + typ: msg.typ, + version: msg.version, + payload: Payload::new(buf), + }) + } +} diff --git a/third_party/rustls-fork-shadow-tls/src/tls12/mod.rs b/third_party/rustls-fork-shadow-tls/src/tls12/mod.rs new file mode 100644 index 0000000..4024aee --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/src/tls12/mod.rs @@ -0,0 +1,537 @@ +use crate::cipher::{MessageDecrypter, MessageEncrypter}; +use crate::conn::{CommonState, ConnectionRandoms, Side}; +use crate::enums::{CipherSuite, SignatureScheme}; +use crate::kx; +use crate::msgs::codec::{Codec, Reader}; +use crate::msgs::enums::{AlertDescription, ContentType}; +use crate::msgs::handshake::KeyExchangeAlgorithm; +use crate::suites::{BulkAlgorithm, CipherSuiteCommon, SupportedCipherSuite}; +#[cfg(feature = "secret_extraction")] +use crate::suites::{ConnectionTrafficSecrets, PartiallyExtractedSecrets}; +use crate::Error; + +use ring::aead; +use ring::digest::Digest; + +use std::fmt; + +mod cipher; +pub(crate) use cipher::{AesGcm, ChaCha20Poly1305, Tls12AeadAlgorithm}; + +mod prf; + +/// The TLS1.2 ciphersuite TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256. +pub static TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256: SupportedCipherSuite = + SupportedCipherSuite::Tls12(&Tls12CipherSuite { + common: CipherSuiteCommon { + suite: CipherSuite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, + bulk: BulkAlgorithm::Chacha20Poly1305, + aead_algorithm: &ring::aead::CHACHA20_POLY1305, + }, + kx: KeyExchangeAlgorithm::ECDHE, + sign: TLS12_ECDSA_SCHEMES, + fixed_iv_len: 12, + explicit_nonce_len: 0, + aead_alg: &ChaCha20Poly1305, + hmac_algorithm: ring::hmac::HMAC_SHA256, + }); + +/// The TLS1.2 ciphersuite TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 +pub static TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256: SupportedCipherSuite = + SupportedCipherSuite::Tls12(&Tls12CipherSuite { + common: CipherSuiteCommon { + suite: CipherSuite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, + bulk: BulkAlgorithm::Chacha20Poly1305, + aead_algorithm: &ring::aead::CHACHA20_POLY1305, + }, + kx: KeyExchangeAlgorithm::ECDHE, + sign: TLS12_RSA_SCHEMES, + fixed_iv_len: 12, + explicit_nonce_len: 0, + aead_alg: &ChaCha20Poly1305, + hmac_algorithm: ring::hmac::HMAC_SHA256, + }); + +/// The TLS1.2 ciphersuite TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 +pub static TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: SupportedCipherSuite = + SupportedCipherSuite::Tls12(&Tls12CipherSuite { + common: CipherSuiteCommon { + suite: CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + bulk: BulkAlgorithm::Aes128Gcm, + aead_algorithm: &ring::aead::AES_128_GCM, + }, + kx: KeyExchangeAlgorithm::ECDHE, + sign: TLS12_RSA_SCHEMES, + fixed_iv_len: 4, + explicit_nonce_len: 8, + aead_alg: &AesGcm, + hmac_algorithm: ring::hmac::HMAC_SHA256, + }); + +/// The TLS1.2 ciphersuite TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 +pub static TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384: SupportedCipherSuite = + SupportedCipherSuite::Tls12(&Tls12CipherSuite { + common: CipherSuiteCommon { + suite: CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + bulk: BulkAlgorithm::Aes256Gcm, + aead_algorithm: &ring::aead::AES_256_GCM, + }, + kx: KeyExchangeAlgorithm::ECDHE, + sign: TLS12_RSA_SCHEMES, + fixed_iv_len: 4, + explicit_nonce_len: 8, + aead_alg: &AesGcm, + hmac_algorithm: ring::hmac::HMAC_SHA384, + }); + +/// The TLS1.2 ciphersuite TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 +pub static TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: SupportedCipherSuite = + SupportedCipherSuite::Tls12(&Tls12CipherSuite { + common: CipherSuiteCommon { + suite: CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + bulk: BulkAlgorithm::Aes128Gcm, + aead_algorithm: &ring::aead::AES_128_GCM, + }, + kx: KeyExchangeAlgorithm::ECDHE, + sign: TLS12_ECDSA_SCHEMES, + fixed_iv_len: 4, + explicit_nonce_len: 8, + aead_alg: &AesGcm, + hmac_algorithm: ring::hmac::HMAC_SHA256, + }); + +/// The TLS1.2 ciphersuite TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 +pub static TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: SupportedCipherSuite = + SupportedCipherSuite::Tls12(&Tls12CipherSuite { + common: CipherSuiteCommon { + suite: CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + bulk: BulkAlgorithm::Aes256Gcm, + aead_algorithm: &ring::aead::AES_256_GCM, + }, + kx: KeyExchangeAlgorithm::ECDHE, + sign: TLS12_ECDSA_SCHEMES, + fixed_iv_len: 4, + explicit_nonce_len: 8, + aead_alg: &AesGcm, + hmac_algorithm: ring::hmac::HMAC_SHA384, + }); + +static TLS12_ECDSA_SCHEMES: &[SignatureScheme] = &[ + SignatureScheme::ED25519, + SignatureScheme::ECDSA_NISTP521_SHA512, + SignatureScheme::ECDSA_NISTP384_SHA384, + SignatureScheme::ECDSA_NISTP256_SHA256, +]; + +static TLS12_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, +]; + +/// A TLS 1.2 cipher suite supported by rustls. +pub struct Tls12CipherSuite { + /// Common cipher suite fields. + pub common: CipherSuiteCommon, + pub(crate) hmac_algorithm: ring::hmac::Algorithm, + /// How to exchange/agree keys. + pub kx: KeyExchangeAlgorithm, + + /// How to sign messages for authentication. + pub sign: &'static [SignatureScheme], + + /// How long the fixed part of the 'IV' is. + /// + /// This isn't usually an IV, but we continue the + /// terminology misuse to match the standard. + pub fixed_iv_len: usize, + + /// This is a non-standard extension which extends the + /// key block to provide an initial explicit nonce offset, + /// in a deterministic and safe way. GCM needs this, + /// chacha20poly1305 works this way by design. + pub explicit_nonce_len: usize, + + pub(crate) aead_alg: &'static dyn Tls12AeadAlgorithm, +} + +impl Tls12CipherSuite { + /// Resolve the set of supported `SignatureScheme`s from the + /// offered `SupportedSignatureSchemes`. If we return an empty + /// set, the handshake terminates. + pub fn resolve_sig_schemes(&self, offered: &[SignatureScheme]) -> Vec { + self.sign + .iter() + .filter(|pref| offered.contains(pref)) + .cloned() + .collect() + } + + /// Which hash function to use with this suite. + pub fn hash_algorithm(&self) -> &'static ring::digest::Algorithm { + self.hmac_algorithm.digest_algorithm() + } +} + +impl From<&'static Tls12CipherSuite> for SupportedCipherSuite { + fn from(s: &'static Tls12CipherSuite) -> Self { + Self::Tls12(s) + } +} + +impl PartialEq for Tls12CipherSuite { + fn eq(&self, other: &Self) -> bool { + self.common.suite == other.common.suite + } +} + +impl fmt::Debug for Tls12CipherSuite { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Tls12CipherSuite") + .field("suite", &self.common.suite) + .field("bulk", &self.common.bulk) + .finish() + } +} + +/// TLS1.2 per-connection keying material +pub(crate) struct ConnectionSecrets { + pub(crate) randoms: ConnectionRandoms, + suite: &'static Tls12CipherSuite, + pub(crate) master_secret: [u8; 48], +} + +impl ConnectionSecrets { + pub(crate) fn from_key_exchange( + kx: kx::KeyExchange, + peer_pub_key: &[u8], + ems_seed: Option, + randoms: ConnectionRandoms, + suite: &'static Tls12CipherSuite, + ) -> Result { + let mut ret = Self { + randoms, + suite, + master_secret: [0u8; 48], + }; + + let (label, seed) = match ems_seed { + Some(seed) => ("extended master secret", Seed::Ems(seed)), + None => ( + "master secret", + Seed::Randoms(join_randoms(&ret.randoms.client, &ret.randoms.server)), + ), + }; + + kx.complete(peer_pub_key, |secret| { + prf::prf( + &mut ret.master_secret, + suite.hmac_algorithm, + secret, + label.as_bytes(), + seed.as_ref(), + ); + Ok(()) + })?; + + Ok(ret) + } + + pub(crate) fn new_resume( + randoms: ConnectionRandoms, + suite: &'static Tls12CipherSuite, + master_secret: &[u8], + ) -> Self { + let mut ret = Self { + randoms, + suite, + master_secret: [0u8; 48], + }; + ret.master_secret + .copy_from_slice(master_secret); + ret + } + + /// Make a `MessageCipherPair` based on the given supported ciphersuite `scs`, + /// and the session's `secrets`. + pub(crate) fn make_cipher_pair(&self, side: Side) -> MessageCipherPair { + fn split_key<'a>( + key_block: &'a [u8], + alg: &'static aead::Algorithm, + ) -> (aead::LessSafeKey, &'a [u8]) { + // Might panic if the key block is too small. + let (key, rest) = key_block.split_at(alg.key_len()); + // Won't panic because its only prerequisite is that `key` is `alg.key_len()` bytes long. + let key = aead::UnboundKey::new(alg, key).unwrap(); + (aead::LessSafeKey::new(key), rest) + } + + // Make a key block, and chop it up. + // nb. we don't implement any ciphersuites with nonzero mac_key_len. + let key_block = self.make_key_block(); + + let suite = self.suite; + let scs = &suite.common; + + let (client_write_key, key_block) = split_key(&key_block, scs.aead_algorithm); + let (server_write_key, key_block) = split_key(key_block, scs.aead_algorithm); + let (client_write_iv, key_block) = key_block.split_at(suite.fixed_iv_len); + let (server_write_iv, extra) = key_block.split_at(suite.fixed_iv_len); + + let (write_key, write_iv, read_key, read_iv) = match side { + Side::Client => ( + client_write_key, + client_write_iv, + server_write_key, + server_write_iv, + ), + Side::Server => ( + server_write_key, + server_write_iv, + client_write_key, + client_write_iv, + ), + }; + + ( + suite + .aead_alg + .decrypter(read_key, read_iv), + suite + .aead_alg + .encrypter(write_key, write_iv, extra), + ) + } + + fn make_key_block(&self) -> Vec { + let suite = &self.suite; + let common = &self.suite.common; + + let len = + (common.aead_algorithm.key_len() + suite.fixed_iv_len) * 2 + suite.explicit_nonce_len; + + let mut out = Vec::new(); + out.resize(len, 0u8); + + // NOTE: opposite order to above for no good reason. + // Don't design security protocols on drugs, kids. + let randoms = join_randoms(&self.randoms.server, &self.randoms.client); + prf::prf( + &mut out, + self.suite.hmac_algorithm, + &self.master_secret, + b"key expansion", + &randoms, + ); + + out + } + + pub(crate) fn suite(&self) -> &'static Tls12CipherSuite { + self.suite + } + + pub(crate) fn get_master_secret(&self) -> Vec { + let mut ret = Vec::new(); + ret.extend_from_slice(&self.master_secret); + ret + } + + fn make_verify_data(&self, handshake_hash: &Digest, label: &[u8]) -> Vec { + let mut out = Vec::new(); + out.resize(12, 0u8); + + prf::prf( + &mut out, + self.suite.hmac_algorithm, + &self.master_secret, + label, + handshake_hash.as_ref(), + ); + out + } + + pub(crate) fn client_verify_data(&self, handshake_hash: &Digest) -> Vec { + self.make_verify_data(handshake_hash, b"client finished") + } + + pub(crate) fn server_verify_data(&self, handshake_hash: &Digest) -> Vec { + self.make_verify_data(handshake_hash, b"server finished") + } + + pub(crate) fn export_keying_material( + &self, + output: &mut [u8], + label: &[u8], + context: Option<&[u8]>, + ) { + let mut randoms = Vec::new(); + randoms.extend_from_slice(&self.randoms.client); + randoms.extend_from_slice(&self.randoms.server); + if let Some(context) = context { + assert!(context.len() <= 0xffff); + (context.len() as u16).encode(&mut randoms); + randoms.extend_from_slice(context); + } + + prf::prf( + output, + self.suite.hmac_algorithm, + &self.master_secret, + label, + &randoms, + ) + } + + #[cfg(feature = "secret_extraction")] + pub(crate) fn extract_secrets(&self, side: Side) -> Result { + // Make a key block, and chop it up + let key_block = self.make_key_block(); + + let suite = self.suite; + let algo = suite.common.aead_algorithm; + + let (client_key, key_block) = key_block.split_at(algo.key_len()); + let (server_key, key_block) = key_block.split_at(algo.key_len()); + let (client_iv, key_block) = key_block.split_at(suite.fixed_iv_len); + let (server_iv, extra) = key_block.split_at(suite.fixed_iv_len); + + // A key/IV pair (fixed IV len is 4 for GCM, 12 for Chacha) + struct Pair<'a> { + key: &'a [u8], + iv: &'a [u8], + } + + let client_pair = Pair { + key: client_key, + iv: client_iv, + }; + let server_pair = Pair { + key: server_key, + iv: server_iv, + }; + + let (client_secrets, server_secrets) = if algo == &ring::aead::AES_128_GCM { + let extract = |pair: Pair| -> ConnectionTrafficSecrets { + let mut key = [0u8; 16]; + key.copy_from_slice(pair.key); + + let mut salt = [0u8; 4]; + salt.copy_from_slice(pair.iv); + + let mut iv = [0u8; 8]; + iv.copy_from_slice(&extra[..8]); + + ConnectionTrafficSecrets::Aes128Gcm { key, salt, iv } + }; + + (extract(client_pair), extract(server_pair)) + } else if algo == &ring::aead::AES_256_GCM { + let extract = |pair: Pair| -> ConnectionTrafficSecrets { + let mut key = [0u8; 32]; + key.copy_from_slice(pair.key); + + let mut salt = [0u8; 4]; + salt.copy_from_slice(pair.iv); + + let mut iv = [0u8; 8]; + iv.copy_from_slice(&extra[..8]); + + ConnectionTrafficSecrets::Aes256Gcm { key, salt, iv } + }; + + (extract(client_pair), extract(server_pair)) + } else if algo == &ring::aead::CHACHA20_POLY1305 { + let extract = |pair: Pair| -> ConnectionTrafficSecrets { + let mut key = [0u8; 32]; + key.copy_from_slice(pair.key); + + let mut iv = [0u8; 12]; + iv.copy_from_slice(pair.iv); + + ConnectionTrafficSecrets::Chacha20Poly1305 { key, iv } + }; + + (extract(client_pair), extract(server_pair)) + } else { + return Err(Error::General(format!( + "exporting secrets for {:?}: unimplemented", + algo + ))); + }; + + let (tx, rx) = match side { + Side::Client => (client_secrets, server_secrets), + Side::Server => (server_secrets, client_secrets), + }; + Ok(PartiallyExtractedSecrets { tx, rx }) + } +} + +enum Seed { + Ems(Digest), + Randoms([u8; 64]), +} + +impl AsRef<[u8]> for Seed { + fn as_ref(&self) -> &[u8] { + match self { + Self::Ems(seed) => seed.as_ref(), + Self::Randoms(randoms) => randoms.as_ref(), + } + } +} + +fn join_randoms(first: &[u8; 32], second: &[u8; 32]) -> [u8; 64] { + let mut randoms = [0u8; 64]; + randoms[..32].copy_from_slice(first); + randoms[32..].copy_from_slice(second); + randoms +} + +type MessageCipherPair = (Box, Box); + +pub(crate) fn decode_ecdh_params( + common: &mut CommonState, + kx_params: &[u8], +) -> Result { + decode_ecdh_params_::(kx_params).ok_or_else(|| { + common.send_fatal_alert(AlertDescription::DecodeError); + Error::CorruptMessagePayload(ContentType::Handshake) + }) +} + +fn decode_ecdh_params_(kx_params: &[u8]) -> Option { + let mut rd = Reader::init(kx_params); + let ecdh_params = T::read(&mut rd)?; + match rd.any_left() { + false => Some(ecdh_params), + true => None, + } +} + +pub(crate) const DOWNGRADE_SENTINEL: [u8; 8] = [0x44, 0x4f, 0x57, 0x4e, 0x47, 0x52, 0x44, 0x01]; + +#[cfg(test)] +mod tests { + use super::*; + use crate::msgs::handshake::{ClientECDHParams, ServerECDHParams}; + + #[test] + fn server_ecdhe_remaining_bytes() { + let key = kx::KeyExchange::start(&kx::X25519).unwrap(); + let server_params = ServerECDHParams::new(key.group(), key.pubkey.as_ref()); + let mut server_buf = Vec::new(); + server_params.encode(&mut server_buf); + server_buf.push(34); + assert!(decode_ecdh_params_::(&server_buf).is_none()); + } + + #[test] + fn client_ecdhe_invalid() { + assert!(decode_ecdh_params_::(&[34]).is_none()); + } +} diff --git a/third_party/rustls-fork-shadow-tls/src/tls12/prf.rs b/third_party/rustls-fork-shadow-tls/src/tls12/prf.rs new file mode 100644 index 0000000..24ff8ba --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/src/tls12/prf.rs @@ -0,0 +1,67 @@ +use ring::hmac; + +fn concat_sign(key: &hmac::Key, a: &[u8], b: &[u8]) -> hmac::Tag { + let mut ctx = hmac::Context::with_key(key); + ctx.update(a); + ctx.update(b); + ctx.sign() +} + +fn p(out: &mut [u8], alg: hmac::Algorithm, secret: &[u8], seed: &[u8]) { + let hmac_key = hmac::Key::new(alg, secret); + + // A(1) + let mut current_a = hmac::sign(&hmac_key, seed); + let chunk_size = alg.digest_algorithm().output_len; + for chunk in out.chunks_mut(chunk_size) { + // P_hash[i] = HMAC_hash(secret, A(i) + seed) + let p_term = concat_sign(&hmac_key, current_a.as_ref(), seed); + chunk.copy_from_slice(&p_term.as_ref()[..chunk.len()]); + + // A(i+1) = HMAC_hash(secret, A(i)) + current_a = hmac::sign(&hmac_key, current_a.as_ref()); + } +} + +fn concat(a: &[u8], b: &[u8]) -> Vec { + let mut ret = Vec::new(); + ret.extend_from_slice(a); + ret.extend_from_slice(b); + ret +} + +pub(crate) fn prf(out: &mut [u8], alg: hmac::Algorithm, secret: &[u8], label: &[u8], seed: &[u8]) { + let joined_seed = concat(label, seed); + p(out, alg, secret, &joined_seed); +} + +#[cfg(test)] +mod tests { + use ring::hmac::{HMAC_SHA256, HMAC_SHA512}; + + #[test] + fn check_sha256() { + let secret = b"\x9b\xbe\x43\x6b\xa9\x40\xf0\x17\xb1\x76\x52\x84\x9a\x71\xdb\x35"; + let seed = b"\xa0\xba\x9f\x93\x6c\xda\x31\x18\x27\xa6\xf7\x96\xff\xd5\x19\x8c"; + let label = b"test label"; + let expect = include_bytes!("../testdata/prf-result.1.bin"); + let mut output = [0u8; 100]; + + super::prf(&mut output, HMAC_SHA256, secret, label, seed); + assert_eq!(expect.len(), output.len()); + assert_eq!(expect.to_vec(), output.to_vec()); + } + + #[test] + fn check_sha512() { + let secret = b"\xb0\x32\x35\x23\xc1\x85\x35\x99\x58\x4d\x88\x56\x8b\xbb\x05\xeb"; + let seed = b"\xd4\x64\x0e\x12\xe4\xbc\xdb\xfb\x43\x7f\x03\xe6\xae\x41\x8e\xe5"; + let label = b"test label"; + let expect = include_bytes!("../testdata/prf-result.2.bin"); + let mut output = [0u8; 196]; + + super::prf(&mut output, HMAC_SHA512, secret, label, seed); + assert_eq!(expect.len(), output.len()); + assert_eq!(expect.to_vec(), output.to_vec()); + } +} diff --git a/third_party/rustls-fork-shadow-tls/src/tls13/key_schedule.rs b/third_party/rustls-fork-shadow-tls/src/tls13/key_schedule.rs new file mode 100644 index 0000000..098c7b7 --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/src/tls13/key_schedule.rs @@ -0,0 +1,800 @@ +use crate::cipher::{Iv, IvLen}; +use crate::error::Error; +use crate::msgs::base::PayloadU8; +use crate::KeyLog; +#[cfg(feature = "secret_extraction")] +use crate::{ + conn::Side, + suites::{ConnectionTrafficSecrets, PartiallyExtractedSecrets}, +}; + +/// Key schedule maintenance for TLS1.3 +use ring::{ + aead, + digest::{self, Digest}, + hkdf::{self, KeyType as _}, + hmac, +}; + +/// The kinds of secret we can extract from `KeySchedule`. +#[derive(Debug, Clone, Copy, PartialEq)] +enum SecretKind { + ResumptionPskBinderKey, + ClientEarlyTrafficSecret, + ClientHandshakeTrafficSecret, + ServerHandshakeTrafficSecret, + ClientApplicationTrafficSecret, + ServerApplicationTrafficSecret, + ExporterMasterSecret, + ResumptionMasterSecret, + DerivedSecret, +} + +impl SecretKind { + fn to_bytes(self) -> &'static [u8] { + use self::SecretKind::*; + match self { + ResumptionPskBinderKey => b"res binder", + ClientEarlyTrafficSecret => b"c e traffic", + ClientHandshakeTrafficSecret => b"c hs traffic", + ServerHandshakeTrafficSecret => b"s hs traffic", + ClientApplicationTrafficSecret => b"c ap traffic", + ServerApplicationTrafficSecret => b"s ap traffic", + ExporterMasterSecret => b"exp master", + ResumptionMasterSecret => b"res master", + DerivedSecret => b"derived", + } + } + + fn log_label(self) -> Option<&'static str> { + use self::SecretKind::*; + Some(match self { + ClientEarlyTrafficSecret => "CLIENT_EARLY_TRAFFIC_SECRET", + ClientHandshakeTrafficSecret => "CLIENT_HANDSHAKE_TRAFFIC_SECRET", + ServerHandshakeTrafficSecret => "SERVER_HANDSHAKE_TRAFFIC_SECRET", + ClientApplicationTrafficSecret => "CLIENT_TRAFFIC_SECRET_0", + ServerApplicationTrafficSecret => "SERVER_TRAFFIC_SECRET_0", + ExporterMasterSecret => "EXPORTER_SECRET", + _ => { + return None; + } + }) + } +} + +/// This is the TLS1.3 key schedule. It stores the current secret and +/// the type of hash. This isn't used directly; but only through the +/// typestates. +struct KeySchedule { + current: hkdf::Prk, + algorithm: ring::hkdf::Algorithm, +} + +// We express the state of a contained KeySchedule using these +// typestates. This means we can write code that cannot accidentally +// (e.g.) encrypt application data using a KeySchedule solely constructed +// with an empty or trivial secret, or extract the wrong kind of secrets +// at a given point. + +/// KeySchedule for early data stage. +pub(crate) struct KeyScheduleEarly { + ks: KeySchedule, +} + +impl KeyScheduleEarly { + pub(crate) fn new(algorithm: hkdf::Algorithm, secret: &[u8]) -> Self { + Self { + ks: KeySchedule::new(algorithm, secret), + } + } + + pub(crate) fn client_early_traffic_secret( + &self, + hs_hash: &Digest, + key_log: &dyn KeyLog, + client_random: &[u8; 32], + ) -> hkdf::Prk { + self.ks.derive_logged_secret( + SecretKind::ClientEarlyTrafficSecret, + hs_hash.as_ref(), + key_log, + client_random, + ) + } + + pub(crate) fn resumption_psk_binder_key_and_sign_verify_data( + &self, + hs_hash: &Digest, + ) -> hmac::Tag { + let resumption_psk_binder_key = self + .ks + .derive_for_empty_hash(SecretKind::ResumptionPskBinderKey); + self.ks + .sign_verify_data(&resumption_psk_binder_key, hs_hash) + } +} + +/// Pre-handshake key schedule +/// +/// The inner `KeySchedule` is either constructed without any secrets based on ths HKDF algorithm +/// or is extracted from a `KeyScheduleEarly`. This can then be used to derive the `KeyScheduleHandshakeStart`. +pub(crate) struct KeySchedulePreHandshake { + ks: KeySchedule, +} + +impl KeySchedulePreHandshake { + pub(crate) fn new(algorithm: hkdf::Algorithm) -> Self { + Self { + ks: KeySchedule::new_with_empty_secret(algorithm), + } + } + + pub(crate) fn into_handshake(mut self, secret: &[u8]) -> KeyScheduleHandshakeStart { + self.ks.input_secret(secret); + KeyScheduleHandshakeStart { ks: self.ks } + } +} + +impl From for KeySchedulePreHandshake { + fn from(KeyScheduleEarly { ks }: KeyScheduleEarly) -> Self { + Self { ks } + } +} + +/// KeySchedule during handshake. +pub(crate) struct KeyScheduleHandshakeStart { + ks: KeySchedule, +} + +impl KeyScheduleHandshakeStart { + pub(crate) fn derive_handshake_secrets( + self, + hs_hash: Digest, + key_log: &dyn KeyLog, + client_random: &[u8; 32], + ) -> (KeyScheduleHandshake, hkdf::Prk, hkdf::Prk) { + // Use an empty handshake hash for the initial handshake. + let client_secret = self.ks.derive_logged_secret( + SecretKind::ClientHandshakeTrafficSecret, + hs_hash.as_ref(), + key_log, + client_random, + ); + + let server_secret = self.ks.derive_logged_secret( + SecretKind::ServerHandshakeTrafficSecret, + hs_hash.as_ref(), + key_log, + client_random, + ); + + let new = KeyScheduleHandshake { + ks: self.ks, + client_handshake_traffic_secret: client_secret.clone(), + server_handshake_traffic_secret: server_secret.clone(), + }; + + (new, client_secret, server_secret) + } +} + +pub(crate) struct KeyScheduleHandshake { + ks: KeySchedule, + client_handshake_traffic_secret: hkdf::Prk, + server_handshake_traffic_secret: hkdf::Prk, +} + +impl KeyScheduleHandshake { + pub(crate) fn sign_server_finish(&self, hs_hash: &Digest) -> hmac::Tag { + self.ks + .sign_finish(&self.server_handshake_traffic_secret, hs_hash) + } + + pub(crate) fn client_key(&self) -> &hkdf::Prk { + &self.client_handshake_traffic_secret + } + + pub(crate) fn into_traffic_with_client_finished_pending( + self, + hs_hash: Digest, + key_log: &dyn KeyLog, + client_random: &[u8; 32], + ) -> ( + KeyScheduleTrafficWithClientFinishedPending, + hkdf::Prk, + hkdf::Prk, + ) { + let traffic = KeyScheduleTraffic::new(self.ks, hs_hash, key_log, client_random); + + let client_secret = traffic + .current_client_traffic_secret + .clone(); + let server_secret = traffic + .current_server_traffic_secret + .clone(); + + let new = KeyScheduleTrafficWithClientFinishedPending { + handshake_client_traffic_secret: self.client_handshake_traffic_secret, + traffic, + }; + + (new, client_secret, server_secret) + } +} + +/// KeySchedule during traffic stage, retaining the ability to calculate the client's +/// finished verify_data. The traffic stage key schedule can be extracted from it +/// through signing the client finished hash. +pub(crate) struct KeyScheduleTrafficWithClientFinishedPending { + handshake_client_traffic_secret: hkdf::Prk, + traffic: KeyScheduleTraffic, +} + +impl KeyScheduleTrafficWithClientFinishedPending { + pub(crate) fn client_key(&self) -> &hkdf::Prk { + &self.handshake_client_traffic_secret + } + + pub(crate) fn sign_client_finish( + self, + hs_hash: &Digest, + ) -> (KeyScheduleTraffic, hmac::Tag, hkdf::Prk) { + let tag = self + .traffic + .ks + .sign_finish(&self.handshake_client_traffic_secret, hs_hash); + + let client_secret = self + .traffic + .current_client_traffic_secret + .clone(); + + (self.traffic, tag, client_secret) + } +} + +/// KeySchedule during traffic stage. All traffic & exporter keys are guaranteed +/// to be available. +pub(crate) struct KeyScheduleTraffic { + ks: KeySchedule, + current_client_traffic_secret: hkdf::Prk, + current_server_traffic_secret: hkdf::Prk, + current_exporter_secret: hkdf::Prk, +} + +impl KeyScheduleTraffic { + fn new( + mut ks: KeySchedule, + hs_hash: Digest, + key_log: &dyn KeyLog, + client_random: &[u8; 32], + ) -> Self { + ks.input_empty(); + + let current_client_traffic_secret = ks.derive_logged_secret( + SecretKind::ClientApplicationTrafficSecret, + hs_hash.as_ref(), + key_log, + client_random, + ); + + let current_server_traffic_secret = ks.derive_logged_secret( + SecretKind::ServerApplicationTrafficSecret, + hs_hash.as_ref(), + key_log, + client_random, + ); + + let current_exporter_secret = ks.derive_logged_secret( + SecretKind::ExporterMasterSecret, + hs_hash.as_ref(), + key_log, + client_random, + ); + + Self { + ks, + current_client_traffic_secret, + current_server_traffic_secret, + current_exporter_secret, + } + } + + pub(crate) fn next_server_application_traffic_secret(&mut self) -> hkdf::Prk { + let secret = self + .ks + .derive_next(&self.current_server_traffic_secret); + self.current_server_traffic_secret = secret.clone(); + secret + } + + pub(crate) fn next_client_application_traffic_secret(&mut self) -> hkdf::Prk { + let secret = self + .ks + .derive_next(&self.current_client_traffic_secret); + self.current_client_traffic_secret = secret.clone(); + secret + } + + pub(crate) fn resumption_master_secret_and_derive_ticket_psk( + &self, + hs_hash: &Digest, + nonce: &[u8], + ) -> Vec { + let resumption_master_secret = self.ks.derive( + self.ks.algorithm(), + SecretKind::ResumptionMasterSecret, + hs_hash.as_ref(), + ); + self.ks + .derive_ticket_psk(&resumption_master_secret, nonce) + } + + pub(crate) fn export_keying_material( + &self, + out: &mut [u8], + label: &[u8], + context: Option<&[u8]>, + ) -> Result<(), Error> { + self.ks + .export_keying_material(&self.current_exporter_secret, out, label, context) + } + + #[cfg(feature = "secret_extraction")] + pub(crate) fn extract_secrets( + &self, + algo: &ring::aead::Algorithm, + side: Side, + ) -> Result { + fn expand( + secret: &hkdf::Prk, + ) -> Result<([u8; KEY_LEN], [u8; IV_LEN]), Error> { + let mut key = [0u8; KEY_LEN]; + let mut iv = [0u8; IV_LEN]; + + hkdf_expand_info(secret, PayloadU8Len(key.len()), b"key", &[], |okm| { + okm.fill(&mut key) + }) + .map_err(|_| Error::General("hkdf_expand_info failed".to_string()))?; + + hkdf_expand_info(secret, PayloadU8Len(iv.len()), b"iv", &[], |okm| { + okm.fill(&mut iv) + }) + .map_err(|_| Error::General("hkdf_expand_info failed".to_string()))?; + + Ok((key, iv)) + } + + let client_secrets; + let server_secrets; + + if algo == &ring::aead::AES_128_GCM { + let extract = |secret: &hkdf::Prk| -> Result { + let (key, iv_in) = expand::<16, 12>(secret)?; + + let mut salt = [0u8; 4]; + salt.copy_from_slice(&iv_in[..4]); + + let mut iv = [0u8; 8]; + iv.copy_from_slice(&iv_in[4..]); + + Ok(ConnectionTrafficSecrets::Aes128Gcm { key, salt, iv }) + }; + + client_secrets = extract(&self.current_client_traffic_secret)?; + server_secrets = extract(&self.current_server_traffic_secret)?; + } else if algo == &ring::aead::AES_256_GCM { + let extract = |secret: &hkdf::Prk| -> Result { + let (key, iv_in) = expand::<32, 12>(secret)?; + + let mut salt = [0u8; 4]; + salt.copy_from_slice(&iv_in[..4]); + + let mut iv = [0u8; 8]; + iv.copy_from_slice(&iv_in[4..]); + + Ok(ConnectionTrafficSecrets::Aes256Gcm { key, salt, iv }) + }; + + client_secrets = extract(&self.current_client_traffic_secret)?; + server_secrets = extract(&self.current_server_traffic_secret)?; + } else if algo == &ring::aead::CHACHA20_POLY1305 { + let extract = |secret: &hkdf::Prk| -> Result { + let (key, iv) = expand::<32, 12>(secret)?; + Ok(ConnectionTrafficSecrets::Chacha20Poly1305 { key, iv }) + }; + + client_secrets = extract(&self.current_client_traffic_secret)?; + server_secrets = extract(&self.current_server_traffic_secret)?; + } else { + return Err(Error::General(format!( + "exporting secrets for {:?}: unimplemented", + algo + ))); + } + + let (tx, rx) = match side { + crate::conn::Side::Client => (client_secrets, server_secrets), + crate::conn::Side::Server => (server_secrets, client_secrets), + }; + Ok(PartiallyExtractedSecrets { tx, rx }) + } +} + +impl KeySchedule { + fn new(algorithm: hkdf::Algorithm, secret: &[u8]) -> Self { + let zeroes = [0u8; digest::MAX_OUTPUT_LEN]; + let salt = hkdf::Salt::new(algorithm, &zeroes[..algorithm.len()]); + Self { + current: salt.extract(secret), + algorithm, + } + } + + #[inline] + fn algorithm(&self) -> hkdf::Algorithm { + self.algorithm + } + + fn new_with_empty_secret(algorithm: hkdf::Algorithm) -> Self { + let zeroes = [0u8; digest::MAX_OUTPUT_LEN]; + Self::new(algorithm, &zeroes[..algorithm.len()]) + } + + /// Input the empty secret. + fn input_empty(&mut self) { + let zeroes = [0u8; digest::MAX_OUTPUT_LEN]; + self.input_secret(&zeroes[..self.algorithm.len()]); + } + + /// Input the given secret. + fn input_secret(&mut self, secret: &[u8]) { + let salt: hkdf::Salt = self.derive_for_empty_hash(SecretKind::DerivedSecret); + self.current = salt.extract(secret); + } + + /// Derive a secret of given `kind`, using current handshake hash `hs_hash`. + fn derive(&self, key_type: L, kind: SecretKind, hs_hash: &[u8]) -> T + where + T: for<'a> From>, + L: hkdf::KeyType, + { + hkdf_expand(&self.current, key_type, kind.to_bytes(), hs_hash) + } + + fn derive_logged_secret( + &self, + kind: SecretKind, + hs_hash: &[u8], + key_log: &dyn KeyLog, + client_random: &[u8; 32], + ) -> hkdf::Prk { + let log_label = kind + .log_label() + .expect("not a loggable secret"); + if key_log.will_log(log_label) { + let secret = self + .derive::(PayloadU8Len(self.algorithm.len()), kind, hs_hash) + .into_inner(); + key_log.log(log_label, client_random, &secret); + } + self.derive(self.algorithm, kind, hs_hash) + } + + /// Derive a secret of given `kind` using the hash of the empty string + /// for the handshake hash. Useful only for + /// `SecretKind::ResumptionPSKBinderKey` and + /// `SecretKind::DerivedSecret`. + fn derive_for_empty_hash(&self, kind: SecretKind) -> T + where + T: for<'a> From>, + { + let digest_alg = self + .algorithm + .hmac_algorithm() + .digest_algorithm(); + let empty_hash = digest::digest(digest_alg, &[]); + self.derive(self.algorithm, kind, empty_hash.as_ref()) + } + + /// Sign the finished message consisting of `hs_hash` using a current + /// traffic secret. + fn sign_finish(&self, base_key: &hkdf::Prk, hs_hash: &Digest) -> hmac::Tag { + self.sign_verify_data(base_key, hs_hash) + } + + /// Sign the finished message consisting of `hs_hash` using the key material + /// `base_key`. + fn sign_verify_data(&self, base_key: &hkdf::Prk, hs_hash: &Digest) -> hmac::Tag { + let hmac_alg = self.algorithm.hmac_algorithm(); + let hmac_key = hkdf_expand(base_key, hmac_alg, b"finished", &[]); + hmac::sign(&hmac_key, hs_hash.as_ref()) + } + + /// Derive the next application traffic secret, returning it. + fn derive_next(&self, base_key: &hkdf::Prk) -> hkdf::Prk { + hkdf_expand(base_key, self.algorithm, b"traffic upd", &[]) + } + + /// Derive the PSK to use given a resumption_master_secret and + /// ticket_nonce. + fn derive_ticket_psk(&self, rms: &hkdf::Prk, nonce: &[u8]) -> Vec { + let payload: PayloadU8 = hkdf_expand( + rms, + PayloadU8Len(self.algorithm.len()), + b"resumption", + nonce, + ); + payload.into_inner() + } + + fn export_keying_material( + &self, + current_exporter_secret: &hkdf::Prk, + out: &mut [u8], + label: &[u8], + context: Option<&[u8]>, + ) -> Result<(), Error> { + let digest_alg = self + .algorithm + .hmac_algorithm() + .digest_algorithm(); + + let h_empty = digest::digest(digest_alg, &[]); + let secret: hkdf::Prk = hkdf_expand( + current_exporter_secret, + self.algorithm, + label, + h_empty.as_ref(), + ); + + let h_context = digest::digest(digest_alg, context.unwrap_or(&[])); + + // TODO: Test what happens when this fails + hkdf_expand_info( + &secret, + PayloadU8Len(out.len()), + b"exporter", + h_context.as_ref(), + |okm| okm.fill(out), + ) + .map_err(|_| Error::General("exporting too much".to_string())) + } +} + +pub(crate) fn hkdf_expand(secret: &hkdf::Prk, key_type: L, label: &[u8], context: &[u8]) -> T +where + T: for<'a> From>, + L: hkdf::KeyType, +{ + hkdf_expand_info(secret, key_type, label, context, |okm| okm.into()) +} + +fn hkdf_expand_info( + secret: &hkdf::Prk, + key_type: L, + label: &[u8], + context: &[u8], + f: F, +) -> T +where + F: for<'b> FnOnce(hkdf::Okm<'b, L>) -> T, + L: hkdf::KeyType, +{ + const LABEL_PREFIX: &[u8] = b"tls13 "; + + let output_len = u16::to_be_bytes(key_type.len() as u16); + let label_len = u8::to_be_bytes((LABEL_PREFIX.len() + label.len()) as u8); + let context_len = u8::to_be_bytes(context.len() as u8); + + let info = &[ + &output_len[..], + &label_len[..], + LABEL_PREFIX, + label, + &context_len[..], + context, + ]; + let okm = secret.expand(info, key_type).unwrap(); + + f(okm) +} + +pub(crate) struct PayloadU8Len(pub(crate) usize); +impl hkdf::KeyType for PayloadU8Len { + fn len(&self) -> usize { + self.0 + } +} + +impl From> for PayloadU8 { + fn from(okm: hkdf::Okm) -> Self { + let mut r = vec![0u8; okm.len().0]; + okm.fill(&mut r[..]).unwrap(); + Self::new(r) + } +} + +pub(crate) fn derive_traffic_key( + secret: &hkdf::Prk, + aead_algorithm: &'static aead::Algorithm, +) -> aead::UnboundKey { + hkdf_expand(secret, aead_algorithm, b"key", &[]) +} + +pub(crate) fn derive_traffic_iv(secret: &hkdf::Prk) -> Iv { + hkdf_expand(secret, IvLen, b"iv", &[]) +} + +#[cfg(test)] +mod test { + use super::{derive_traffic_iv, derive_traffic_key, KeySchedule, SecretKind}; + use crate::KeyLog; + use ring::{aead, hkdf}; + + #[test] + fn test_vectors() { + /* These test vectors generated with OpenSSL. */ + let hs_start_hash = [ + 0xec, 0x14, 0x7a, 0x06, 0xde, 0xa3, 0xc8, 0x84, 0x6c, 0x02, 0xb2, 0x23, 0x8e, 0x41, + 0xbd, 0xdc, 0x9d, 0x89, 0xf9, 0xae, 0xa1, 0x7b, 0x5e, 0xfd, 0x4d, 0x74, 0x82, 0xaf, + 0x75, 0x88, 0x1c, 0x0a, + ]; + + let hs_full_hash = [ + 0x75, 0x1a, 0x3d, 0x4a, 0x14, 0xdf, 0xab, 0xeb, 0x68, 0xe9, 0x2c, 0xa5, 0x91, 0x8e, + 0x24, 0x08, 0xb9, 0xbc, 0xb0, 0x74, 0x89, 0x82, 0xec, 0x9c, 0x32, 0x30, 0xac, 0x30, + 0xbb, 0xeb, 0x23, 0xe2, + ]; + + let ecdhe_secret = [ + 0xe7, 0xb8, 0xfe, 0xf8, 0x90, 0x3b, 0x52, 0x0c, 0xb9, 0xa1, 0x89, 0x71, 0xb6, 0x9d, + 0xd4, 0x5d, 0xca, 0x53, 0xce, 0x2f, 0x12, 0xbf, 0x3b, 0xef, 0x93, 0x15, 0xe3, 0x12, + 0x71, 0xdf, 0x4b, 0x40, + ]; + + let client_hts = [ + 0x61, 0x7b, 0x35, 0x07, 0x6b, 0x9d, 0x0e, 0x08, 0xcf, 0x73, 0x1d, 0x94, 0xa8, 0x66, + 0x14, 0x78, 0x41, 0x09, 0xef, 0x25, 0x55, 0x51, 0x92, 0x1d, 0xd4, 0x6e, 0x04, 0x01, + 0x35, 0xcf, 0x46, 0xab, + ]; + + let client_hts_key = [ + 0x62, 0xd0, 0xdd, 0x00, 0xf6, 0x96, 0x19, 0xd3, 0xb8, 0x19, 0x3a, 0xb4, 0xa0, 0x95, + 0x85, 0xa7, + ]; + + let client_hts_iv = [ + 0xff, 0xf7, 0x5d, 0xf5, 0xad, 0x35, 0xd5, 0xcb, 0x3c, 0x53, 0xf3, 0xa9, + ]; + + let server_hts = [ + 0xfc, 0xf7, 0xdf, 0xe6, 0x4f, 0xa2, 0xc0, 0x4f, 0x62, 0x35, 0x38, 0x7f, 0x43, 0x4e, + 0x01, 0x42, 0x23, 0x36, 0xd9, 0xc0, 0x39, 0xde, 0x68, 0x47, 0xa0, 0xb9, 0xdd, 0xcf, + 0x29, 0xa8, 0x87, 0x59, + ]; + + let server_hts_key = [ + 0x04, 0x67, 0xf3, 0x16, 0xa8, 0x05, 0xb8, 0xc4, 0x97, 0xee, 0x67, 0x04, 0x7b, 0xbc, + 0xbc, 0x54, + ]; + + let server_hts_iv = [ + 0xde, 0x83, 0xa7, 0x3e, 0x9d, 0x81, 0x4b, 0x04, 0xc4, 0x8b, 0x78, 0x09, + ]; + + let client_ats = [ + 0xc1, 0x4a, 0x6d, 0x79, 0x76, 0xd8, 0x10, 0x2b, 0x5a, 0x0c, 0x99, 0x51, 0x49, 0x3f, + 0xee, 0x87, 0xdc, 0xaf, 0xf8, 0x2c, 0x24, 0xca, 0xb2, 0x14, 0xe8, 0xbe, 0x71, 0xa8, + 0x20, 0x6d, 0xbd, 0xa5, + ]; + + let client_ats_key = [ + 0xcc, 0x9f, 0x5f, 0x98, 0x0b, 0x5f, 0x10, 0x30, 0x6c, 0xba, 0xd7, 0xbe, 0x98, 0xd7, + 0x57, 0x2e, + ]; + + let client_ats_iv = [ + 0xb8, 0x09, 0x29, 0xe8, 0xd0, 0x2c, 0x70, 0xf6, 0x11, 0x62, 0xed, 0x6b, + ]; + + let server_ats = [ + 0x2c, 0x90, 0x77, 0x38, 0xd3, 0xf8, 0x37, 0x02, 0xd1, 0xe4, 0x59, 0x8f, 0x48, 0x48, + 0x53, 0x1d, 0x9f, 0x93, 0x65, 0x49, 0x1b, 0x9f, 0x7f, 0x52, 0xc8, 0x22, 0x29, 0x0d, + 0x4c, 0x23, 0x21, 0x92, + ]; + + let server_ats_key = [ + 0x0c, 0xb2, 0x95, 0x62, 0xd8, 0xd8, 0x8f, 0x48, 0xb0, 0x2c, 0xbf, 0xbe, 0xd7, 0xe6, + 0x2b, 0xb3, + ]; + + let server_ats_iv = [ + 0x0d, 0xb2, 0x8f, 0x98, 0x85, 0x86, 0xa1, 0xb7, 0xe4, 0xd5, 0xc6, 0x9c, + ]; + + let hkdf = hkdf::HKDF_SHA256; + let mut ks = KeySchedule::new_with_empty_secret(hkdf); + ks.input_secret(&ecdhe_secret); + + assert_traffic_secret( + &ks, + SecretKind::ClientHandshakeTrafficSecret, + &hs_start_hash, + &client_hts, + &client_hts_key, + &client_hts_iv, + ); + + assert_traffic_secret( + &ks, + SecretKind::ServerHandshakeTrafficSecret, + &hs_start_hash, + &server_hts, + &server_hts_key, + &server_hts_iv, + ); + + ks.input_empty(); + + assert_traffic_secret( + &ks, + SecretKind::ClientApplicationTrafficSecret, + &hs_full_hash, + &client_ats, + &client_ats_key, + &client_ats_iv, + ); + + assert_traffic_secret( + &ks, + SecretKind::ServerApplicationTrafficSecret, + &hs_full_hash, + &server_ats, + &server_ats_key, + &server_ats_iv, + ); + } + + fn assert_traffic_secret( + ks: &KeySchedule, + kind: SecretKind, + hash: &[u8], + expected_traffic_secret: &[u8], + expected_key: &[u8], + expected_iv: &[u8], + ) { + struct Log<'a>(&'a [u8]); + impl KeyLog for Log<'_> { + fn log(&self, _label: &str, _client_random: &[u8], secret: &[u8]) { + assert_eq!(self.0, secret); + } + } + let log = Log(expected_traffic_secret); + let traffic_secret = ks.derive_logged_secret(kind, hash, &log, &[0; 32]); + + // Since we can't test key equality, we test the output of sealing with the key instead. + let aead_alg = &aead::AES_128_GCM; + let key = derive_traffic_key(&traffic_secret, aead_alg); + let seal_output = seal_zeroes(key); + let expected_key = aead::UnboundKey::new(aead_alg, expected_key).unwrap(); + let expected_seal_output = seal_zeroes(expected_key); + assert_eq!(seal_output, expected_seal_output); + assert!(seal_output.len() >= 48); // Sanity check. + + let iv = derive_traffic_iv(&traffic_secret); + assert_eq!(iv.value(), expected_iv); + } + + fn seal_zeroes(key: aead::UnboundKey) -> Vec { + let key = aead::LessSafeKey::new(key); + let mut seal_output = vec![0; 32]; + key.seal_in_place_append_tag( + aead::Nonce::assume_unique_for_key([0; aead::NONCE_LEN]), + aead::Aad::empty(), + &mut seal_output, + ) + .unwrap(); + seal_output + } +} diff --git a/third_party/rustls-fork-shadow-tls/src/tls13/mod.rs b/third_party/rustls-fork-shadow-tls/src/tls13/mod.rs new file mode 100644 index 0000000..3f5b27d --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/src/tls13/mod.rs @@ -0,0 +1,224 @@ +use crate::cipher::{make_nonce, Iv, MessageDecrypter, MessageEncrypter}; +use crate::enums::{CipherSuite, ProtocolVersion}; +use crate::error::Error; +use crate::msgs::base::Payload; +use crate::msgs::codec::Codec; +use crate::msgs::enums::ContentType; +use crate::msgs::fragmenter::MAX_FRAGMENT_LEN; +use crate::msgs::message::{BorrowedPlainMessage, OpaqueMessage, PlainMessage}; +use crate::suites::{BulkAlgorithm, CipherSuiteCommon, SupportedCipherSuite}; + +use ring::{aead, hkdf}; + +use std::fmt; + +pub(crate) mod key_schedule; +use key_schedule::{derive_traffic_iv, derive_traffic_key}; + +/// The TLS1.3 ciphersuite TLS_CHACHA20_POLY1305_SHA256 +pub static TLS13_CHACHA20_POLY1305_SHA256: SupportedCipherSuite = + SupportedCipherSuite::Tls13(TLS13_CHACHA20_POLY1305_SHA256_INTERNAL); + +pub(crate) static TLS13_CHACHA20_POLY1305_SHA256_INTERNAL: &Tls13CipherSuite = &Tls13CipherSuite { + common: CipherSuiteCommon { + suite: CipherSuite::TLS13_CHACHA20_POLY1305_SHA256, + bulk: BulkAlgorithm::Chacha20Poly1305, + aead_algorithm: &ring::aead::CHACHA20_POLY1305, + }, + hkdf_algorithm: ring::hkdf::HKDF_SHA256, + #[cfg(feature = "quic")] + confidentiality_limit: u64::MAX, + #[cfg(feature = "quic")] + integrity_limit: 1 << 36, +}; + +/// The TLS1.3 ciphersuite TLS_AES_256_GCM_SHA384 +pub static TLS13_AES_256_GCM_SHA384: SupportedCipherSuite = + SupportedCipherSuite::Tls13(&Tls13CipherSuite { + common: CipherSuiteCommon { + suite: CipherSuite::TLS13_AES_256_GCM_SHA384, + bulk: BulkAlgorithm::Aes256Gcm, + aead_algorithm: &ring::aead::AES_256_GCM, + }, + hkdf_algorithm: ring::hkdf::HKDF_SHA384, + #[cfg(feature = "quic")] + confidentiality_limit: 1 << 23, + #[cfg(feature = "quic")] + integrity_limit: 1 << 52, + }); + +/// The TLS1.3 ciphersuite TLS_AES_128_GCM_SHA256 +pub static TLS13_AES_128_GCM_SHA256: SupportedCipherSuite = + SupportedCipherSuite::Tls13(TLS13_AES_128_GCM_SHA256_INTERNAL); + +pub(crate) static TLS13_AES_128_GCM_SHA256_INTERNAL: &Tls13CipherSuite = &Tls13CipherSuite { + common: CipherSuiteCommon { + suite: CipherSuite::TLS13_AES_128_GCM_SHA256, + bulk: BulkAlgorithm::Aes128Gcm, + aead_algorithm: &ring::aead::AES_128_GCM, + }, + hkdf_algorithm: ring::hkdf::HKDF_SHA256, + #[cfg(feature = "quic")] + confidentiality_limit: 1 << 23, + #[cfg(feature = "quic")] + integrity_limit: 1 << 52, +}; + +/// A TLS 1.3 cipher suite supported by rustls. +pub struct Tls13CipherSuite { + /// Common cipher suite fields. + pub common: CipherSuiteCommon, + pub(crate) hkdf_algorithm: ring::hkdf::Algorithm, + #[cfg(feature = "quic")] + pub(crate) confidentiality_limit: u64, + #[cfg(feature = "quic")] + pub(crate) integrity_limit: u64, +} + +impl Tls13CipherSuite { + pub(crate) fn derive_encrypter(&self, secret: &hkdf::Prk) -> Box { + let key = derive_traffic_key(secret, self.common.aead_algorithm); + let iv = derive_traffic_iv(secret); + + Box::new(Tls13MessageEncrypter { + enc_key: aead::LessSafeKey::new(key), + iv, + }) + } + + /// Derive a `MessageDecrypter` object from the concerned TLS 1.3 + /// cipher suite. + pub fn derive_decrypter(&self, secret: &hkdf::Prk) -> Box { + let key = derive_traffic_key(secret, self.common.aead_algorithm); + let iv = derive_traffic_iv(secret); + + Box::new(Tls13MessageDecrypter { + dec_key: aead::LessSafeKey::new(key), + iv, + }) + } + + /// Which hash function to use with this suite. + pub fn hash_algorithm(&self) -> &'static ring::digest::Algorithm { + self.hkdf_algorithm + .hmac_algorithm() + .digest_algorithm() + } + + /// Can a session using suite self resume from suite prev? + pub fn can_resume_from(&self, prev: &'static Self) -> Option<&'static Self> { + (prev.hash_algorithm() == self.hash_algorithm()).then(|| prev) + } +} + +impl From<&'static Tls13CipherSuite> for SupportedCipherSuite { + fn from(s: &'static Tls13CipherSuite) -> Self { + Self::Tls13(s) + } +} + +impl PartialEq for Tls13CipherSuite { + fn eq(&self, other: &Self) -> bool { + self.common.suite == other.common.suite + } +} + +impl fmt::Debug for Tls13CipherSuite { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Tls13CipherSuite") + .field("suite", &self.common.suite) + .field("bulk", &self.common.bulk) + .finish() + } +} + +struct Tls13MessageEncrypter { + enc_key: aead::LessSafeKey, + iv: Iv, +} + +struct Tls13MessageDecrypter { + dec_key: aead::LessSafeKey, + iv: Iv, +} + +fn unpad_tls13(v: &mut Vec) -> ContentType { + loop { + match v.pop() { + Some(0) => {} + Some(content_type) => return ContentType::from(content_type), + None => return ContentType::Unknown(0), + } + } +} + +fn make_tls13_aad(len: usize) -> ring::aead::Aad<[u8; TLS13_AAD_SIZE]> { + ring::aead::Aad::from([ + 0x17, // ContentType::ApplicationData + 0x3, // ProtocolVersion (major) + 0x3, // ProtocolVersion (minor) + (len >> 8) as u8, + len as u8, + ]) +} + +// https://datatracker.ietf.org/doc/html/rfc8446#section-5.2 +const TLS13_AAD_SIZE: usize = 1 + 2 + 2; + +impl MessageEncrypter for Tls13MessageEncrypter { + fn encrypt(&self, msg: BorrowedPlainMessage, seq: u64) -> Result { + let total_len = msg.payload.len() + 1 + self.enc_key.algorithm().tag_len(); + let mut payload = Vec::with_capacity(total_len); + payload.extend_from_slice(msg.payload); + msg.typ.encode(&mut payload); + + let nonce = make_nonce(&self.iv, seq); + let aad = make_tls13_aad(total_len); + + self.enc_key + .seal_in_place_append_tag(nonce, aad, &mut payload) + .map_err(|_| Error::General("encrypt failed".to_string()))?; + + Ok(OpaqueMessage { + typ: ContentType::ApplicationData, + version: ProtocolVersion::TLSv1_2, + payload: Payload::new(payload), + }) + } +} + +impl MessageDecrypter for Tls13MessageDecrypter { + fn decrypt(&self, mut msg: OpaqueMessage, seq: u64) -> Result { + let payload = &mut msg.payload.0; + if payload.len() < self.dec_key.algorithm().tag_len() { + return Err(Error::DecryptError); + } + + let nonce = make_nonce(&self.iv, seq); + let aad = make_tls13_aad(payload.len()); + let plain_len = self + .dec_key + .open_in_place(nonce, aad, payload) + .map_err(|_| Error::DecryptError)? + .len(); + + payload.truncate(plain_len); + + if payload.len() > MAX_FRAGMENT_LEN + 1 { + return Err(Error::PeerSentOversizedRecord); + } + + msg.typ = unpad_tls13(payload); + if msg.typ == ContentType::Unknown(0) { + let msg = "peer sent bad TLSInnerPlaintext".to_string(); + return Err(Error::PeerMisbehavedError(msg)); + } + + if payload.len() > MAX_FRAGMENT_LEN { + return Err(Error::PeerSentOversizedRecord); + } + + msg.version = ProtocolVersion::TLSv1_3; + Ok(msg.into_plain_message()) + } +} diff --git a/third_party/rustls-fork-shadow-tls/src/vecbuf.rs b/third_party/rustls-fork-shadow-tls/src/vecbuf.rs new file mode 100644 index 0000000..6126edd --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/src/vecbuf.rs @@ -0,0 +1,200 @@ +use std::cmp; +use std::collections::VecDeque; +use std::io; +use std::io::Read; + +/// This is a byte buffer that is built from a vector +/// of byte vectors. This avoids extra copies when +/// appending a new byte vector, at the expense of +/// more complexity when reading out. +pub(crate) struct ChunkVecBuffer { + chunks: VecDeque>, + limit: Option, +} + +impl ChunkVecBuffer { + pub(crate) fn new(limit: Option) -> Self { + Self { + chunks: VecDeque::new(), + limit, + } + } + + /// Sets the upper limit on how many bytes this + /// object can store. + /// + /// Setting a lower limit than the currently stored + /// data is not an error. + /// + /// A [`None`] limit is interpreted as no limit. + pub(crate) fn set_limit(&mut self, new_limit: Option) { + self.limit = new_limit; + } + + /// If we're empty + pub(crate) fn is_empty(&self) -> bool { + self.chunks.is_empty() + } + + pub(crate) fn is_full(&self) -> bool { + self.limit + .map(|limit| self.len() > limit) + .unwrap_or_default() + } + + /// How many bytes we're storing + pub(crate) fn len(&self) -> usize { + let mut len = 0; + for ch in &self.chunks { + len += ch.len(); + } + len + } + + /// For a proposed append of `len` bytes, how many + /// bytes should we actually append to adhere to the + /// currently set `limit`? + pub(crate) fn apply_limit(&self, len: usize) -> usize { + if let Some(limit) = self.limit { + let space = limit.saturating_sub(self.len()); + cmp::min(len, space) + } else { + len + } + } + + /// Append a copy of `bytes`, perhaps a prefix if + /// we're near the limit. + pub(crate) fn append_limited_copy(&mut self, bytes: &[u8]) -> usize { + let take = self.apply_limit(bytes.len()); + self.append(bytes[..take].to_vec()); + take + } + + /// Take and append the given `bytes`. + pub(crate) fn append(&mut self, bytes: Vec) -> usize { + let len = bytes.len(); + + if !bytes.is_empty() { + self.chunks.push_back(bytes); + } + + len + } + + /// Take one of the chunks from this object. This + /// function panics if the object `is_empty`. + pub(crate) fn pop(&mut self) -> Option> { + self.chunks.pop_front() + } + + /// Read data out of this object, writing it into `buf` + /// and returning how many bytes were written there. + pub(crate) fn read(&mut self, buf: &mut [u8]) -> io::Result { + let mut offs = 0; + + while offs < buf.len() && !self.is_empty() { + let used = self.chunks[0] + .as_slice() + .read(&mut buf[offs..])?; + + self.consume(used); + offs += used; + } + + Ok(offs) + } + + #[cfg(read_buf)] + /// Read data out of this object, writing it into `cursor`. + pub(crate) fn read_buf(&mut self, mut cursor: io::BorrowedCursor<'_>) -> io::Result<()> { + while !self.is_empty() && cursor.capacity() > 0 { + let chunk = self.chunks[0].as_slice(); + let used = std::cmp::min(chunk.len(), cursor.capacity()); + cursor.append(&chunk[..used]); + self.consume(used); + } + + Ok(()) + } + + fn consume(&mut self, mut used: usize) { + while let Some(mut buf) = self.chunks.pop_front() { + if used < buf.len() { + self.chunks + .push_front(buf.split_off(used)); + break; + } else { + used -= buf.len(); + } + } + } + + /// Read data out of this object, passing it `wr` + pub(crate) fn write_to(&mut self, wr: &mut dyn io::Write) -> io::Result { + if self.is_empty() { + return Ok(0); + } + + let mut bufs = [io::IoSlice::new(&[]); 64]; + for (iov, chunk) in bufs.iter_mut().zip(self.chunks.iter()) { + *iov = io::IoSlice::new(chunk); + } + let len = cmp::min(bufs.len(), self.chunks.len()); + let used = wr.write_vectored(&bufs[..len])?; + self.consume(used); + Ok(used) + } +} + +#[cfg(test)] +mod test { + use super::ChunkVecBuffer; + + #[test] + fn short_append_copy_with_limit() { + let mut cvb = ChunkVecBuffer::new(Some(12)); + assert_eq!(cvb.append_limited_copy(b"hello"), 5); + assert_eq!(cvb.append_limited_copy(b"world"), 5); + assert_eq!(cvb.append_limited_copy(b"hello"), 2); + assert_eq!(cvb.append_limited_copy(b"world"), 0); + + let mut buf = [0u8; 12]; + assert_eq!(cvb.read(&mut buf).unwrap(), 12); + assert_eq!(buf.to_vec(), b"helloworldhe".to_vec()); + } + + #[cfg(read_buf)] + #[test] + fn read_buf() { + use std::{io::BorrowedBuf, mem::MaybeUninit}; + + { + let mut cvb = ChunkVecBuffer::new(None); + cvb.append(b"test ".to_vec()); + cvb.append(b"fixture ".to_vec()); + cvb.append(b"data".to_vec()); + + let mut buf = [MaybeUninit::::uninit(); 8]; + let mut buf: BorrowedBuf<'_> = buf.as_mut_slice().into(); + cvb.read_buf(buf.unfilled()).unwrap(); + assert_eq!(buf.filled(), b"test fix"); + buf.clear(); + cvb.read_buf(buf.unfilled()).unwrap(); + assert_eq!(buf.filled(), b"ture dat"); + buf.clear(); + cvb.read_buf(buf.unfilled()).unwrap(); + assert_eq!(buf.filled(), b"a"); + } + + { + let mut cvb = ChunkVecBuffer::new(None); + cvb.append(b"short message".to_vec()); + + let mut buf = [MaybeUninit::::uninit(); 1024]; + let mut buf: BorrowedBuf<'_> = buf.as_mut_slice().into(); + cvb.read_buf(buf.unfilled()).unwrap(); + assert_eq!(buf.filled(), b"short message"); + } + } +} diff --git a/third_party/rustls-fork-shadow-tls/src/verify.rs b/third_party/rustls-fork-shadow-tls/src/verify.rs new file mode 100644 index 0000000..58fd7dc --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/src/verify.rs @@ -0,0 +1,809 @@ +use std::fmt; + +use crate::anchors::{OwnedTrustAnchor, RootCertStore}; +use crate::client::ServerName; +use crate::enums::SignatureScheme; +use crate::error::Error; +use crate::key::Certificate; +#[cfg(feature = "logging")] +use crate::log::{debug, trace, warn}; +use crate::msgs::handshake::{DigitallySignedStruct, DistinguishedNames}; + +use ring::digest::Digest; + +use std::convert::TryFrom; +use std::sync::Arc; +use std::time::SystemTime; + +type SignatureAlgorithms = &'static [&'static webpki::SignatureAlgorithm]; + +/// Which signature verification mechanisms we support. No particular +/// order. +static SUPPORTED_SIG_ALGS: SignatureAlgorithms = &[ + &webpki::ECDSA_P256_SHA256, + &webpki::ECDSA_P256_SHA384, + &webpki::ECDSA_P384_SHA256, + &webpki::ECDSA_P384_SHA384, + &webpki::ED25519, + &webpki::RSA_PSS_2048_8192_SHA256_LEGACY_KEY, + &webpki::RSA_PSS_2048_8192_SHA384_LEGACY_KEY, + &webpki::RSA_PSS_2048_8192_SHA512_LEGACY_KEY, + &webpki::RSA_PKCS1_2048_8192_SHA256, + &webpki::RSA_PKCS1_2048_8192_SHA384, + &webpki::RSA_PKCS1_2048_8192_SHA512, + &webpki::RSA_PKCS1_3072_8192_SHA384, +]; + +// Marker types. These are used to bind the fact some verification +// (certificate chain or handshake signature) has taken place into +// protocol states. We use this to have the compiler check that there +// are no 'goto fail'-style elisions of important checks before we +// reach the traffic stage. +// +// These types are public, but cannot be directly constructed. This +// means their origins can be precisely determined by looking +// for their `assertion` constructors. + +/// Zero-sized marker type representing verification of a signature. +#[derive(Debug)] +#[cfg_attr(docsrs, doc(cfg(feature = "dangerous_configuration")))] +pub struct HandshakeSignatureValid(()); + +impl HandshakeSignatureValid { + /// Make a `HandshakeSignatureValid` + pub fn assertion() -> Self { + Self(()) + } +} + +#[derive(Debug)] +pub(crate) struct FinishedMessageVerified(()); + +impl FinishedMessageVerified { + pub(crate) fn assertion() -> Self { + Self(()) + } +} + +/// Zero-sized marker type representing verification of a server cert chain. +#[allow(unreachable_pub)] +#[derive(Debug)] +#[cfg_attr(docsrs, doc(cfg(feature = "dangerous_configuration")))] +pub struct ServerCertVerified(()); + +#[allow(unreachable_pub)] +impl ServerCertVerified { + /// Make a `ServerCertVerified` + pub fn assertion() -> Self { + Self(()) + } +} + +/// Zero-sized marker type representing verification of a client cert chain. +#[derive(Debug)] +#[cfg_attr(docsrs, doc(cfg(feature = "dangerous_configuration")))] +pub struct ClientCertVerified(()); + +impl ClientCertVerified { + /// Make a `ClientCertVerified` + pub fn assertion() -> Self { + Self(()) + } +} + +/// Something that can verify a server certificate chain, and verify +/// signatures made by certificates. +#[allow(unreachable_pub)] +#[cfg_attr(docsrs, doc(cfg(feature = "dangerous_configuration")))] +pub trait ServerCertVerifier: Send + Sync { + /// Verify the end-entity certificate `end_entity` is valid for the + /// hostname `dns_name` and chains to at least one trust anchor. + /// + /// `intermediates` contains all certificates other than `end_entity` that + /// were sent as part of the server's [Certificate] message. It is in the + /// same order that the server sent them and may be empty. + /// + /// Note that none of the certificates have been parsed yet, so it is the responsibility of + /// the implementor to handle invalid data. It is recommended that the implementor returns + /// [`Error::InvalidCertificateEncoding`] when these cases are encountered. + /// + /// `scts` contains the Signed Certificate Timestamps (SCTs) the server + /// sent with the end-entity certificate, if any. + /// + /// [Certificate]: https://datatracker.ietf.org/doc/html/rfc8446#section-4.4.2 + fn verify_server_cert( + &self, + end_entity: &Certificate, + intermediates: &[Certificate], + server_name: &ServerName, + scts: &mut dyn Iterator, + ocsp_response: &[u8], + now: SystemTime, + ) -> Result; + + /// Verify a signature allegedly by the given server certificate. + /// + /// `message` is not hashed, and needs hashing during the verification. + /// The signature and algorithm are within `dss`. `cert` contains the + /// public key to use. + /// + /// `cert` has already been validated by [`ServerCertVerifier::verify_server_cert`]. + /// + /// If and only if the signature is valid, return `Ok(HandshakeSignatureValid)`. + /// Otherwise, return an error -- rustls will send an alert and abort the + /// connection. + /// + /// This method is only called for TLS1.2 handshakes. Note that, in TLS1.2, + /// SignatureSchemes such as `SignatureScheme::ECDSA_NISTP256_SHA256` are not + /// in fact bound to the specific curve implied in their name. + /// + /// This trait method has a default implementation that uses webpki to verify + /// the signature. + fn verify_tls12_signature( + &self, + message: &[u8], + cert: &Certificate, + dss: &DigitallySignedStruct, + ) -> Result { + verify_signed_struct(message, cert, dss) + } + + /// Verify a signature allegedly by the given server certificate. + /// + /// This method is only called for TLS1.3 handshakes. + /// + /// This method is very similar to `verify_tls12_signature`: but note the + /// tighter ECDSA SignatureScheme semantics -- e.g. `SignatureScheme::ECDSA_NISTP256_SHA256` + /// must only validate signatures using public keys on the right curve -- + /// rustls does not enforce this requirement for you. + /// + /// `cert` has already been validated by [`ServerCertVerifier::verify_server_cert`]. + /// + /// If and only if the signature is valid, return `Ok(HandshakeSignatureValid)`. + /// Otherwise, return an error -- rustls will send an alert and abort the + /// connection. + /// + /// This trait method has a default implementation that uses webpki to verify + /// the signature. + fn verify_tls13_signature( + &self, + message: &[u8], + cert: &Certificate, + dss: &DigitallySignedStruct, + ) -> Result { + verify_tls13(message, cert, dss) + } + + /// Return the list of SignatureSchemes that this verifier will handle, + /// in `verify_tls12_signature` and `verify_tls13_signature` calls. + /// + /// This should be in priority order, with the most preferred first. + /// + /// This trait method has a default implementation that reflects the schemes + /// supported by webpki. + fn supported_verify_schemes(&self) -> Vec { + WebPkiVerifier::verification_schemes() + } + + /// Returns `true` if Rustls should ask the server to send SCTs. + /// + /// Signed Certificate Timestamps (SCTs) are used for Certificate + /// Transparency validation. + /// + /// The default implementation of this function returns true. + fn request_scts(&self) -> bool { + true + } +} + +impl fmt::Debug for dyn ServerCertVerifier { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "dyn ServerCertVerifier") + } +} + +/// A type which encapsulates a string that is a syntactically valid DNS name. +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +#[cfg_attr(docsrs, doc(cfg(feature = "dangerous_configuration")))] +pub struct DnsName(pub(crate) webpki::DnsName); + +impl AsRef for DnsName { + fn as_ref(&self) -> &str { + AsRef::::as_ref(&self.0) + } +} + +/// Something that can verify a client certificate chain +#[allow(unreachable_pub)] +#[cfg_attr(docsrs, doc(cfg(feature = "dangerous_configuration")))] +pub trait ClientCertVerifier: Send + Sync { + /// Returns `true` to enable the server to request a client certificate and + /// `false` to skip requesting a client certificate. Defaults to `true`. + fn offer_client_auth(&self) -> bool { + true + } + + /// Return `Some(true)` to require a client certificate and `Some(false)` to make + /// client authentication optional. Return `None` to abort the connection. + /// Defaults to `Some(self.offer_client_auth())`. + fn client_auth_mandatory(&self) -> Option { + Some(self.offer_client_auth()) + } + + /// Returns the [Subjects] of the client authentication trust anchors to + /// share with the client when requesting client authentication. + /// + /// These must be DER-encoded X.500 distinguished names, per RFC 5280. + /// They are sent in the [`certificate_authorities`] extension of a + /// [`CertificateRequest`] message. + /// + /// [Subjects]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.6 + /// [`CertificateRequest`]: https://datatracker.ietf.org/doc/html/rfc8446#section-4.3.2 + /// [`certificate_authorities`]: https://datatracker.ietf.org/doc/html/rfc8446#section-4.2.4 + /// + /// Return `None` to abort the connection. Return an empty `Vec` to continue + /// the handshake without sending a CertificateRequest message. + fn client_auth_root_subjects(&self) -> Option; + + /// Verify the end-entity certificate `end_entity` is valid, acceptable, + /// and chains to at least one of the trust anchors trusted by + /// this verifier. + /// + /// `intermediates` contains the intermediate certificates the + /// client sent along with the end-entity certificate; it is in the same + /// order that the peer sent them and may be empty. + /// + /// Note that none of the certificates have been parsed yet, so it is the responsibility of + /// the implementor to handle invalid data. It is recommended that the implementor returns + /// [`Error::InvalidCertificateEncoding`] when these cases are encountered. + fn verify_client_cert( + &self, + end_entity: &Certificate, + intermediates: &[Certificate], + now: SystemTime, + ) -> Result; + + /// Verify a signature allegedly by the given client certificate. + /// + /// `message` is not hashed, and needs hashing during the verification. + /// The signature and algorithm are within `dss`. `cert` contains the + /// public key to use. + /// + /// `cert` has already been validated by [`ClientCertVerifier::verify_client_cert`]. + /// + /// If and only if the signature is valid, return `Ok(HandshakeSignatureValid)`. + /// Otherwise, return an error -- rustls will send an alert and abort the + /// connection. + /// + /// This method is only called for TLS1.2 handshakes. Note that, in TLS1.2, + /// SignatureSchemes such as `SignatureScheme::ECDSA_NISTP256_SHA256` are not + /// in fact bound to the specific curve implied in their name. + /// + /// This trait method has a default implementation that uses webpki to verify + /// the signature. + fn verify_tls12_signature( + &self, + message: &[u8], + cert: &Certificate, + dss: &DigitallySignedStruct, + ) -> Result { + verify_signed_struct(message, cert, dss) + } + + /// Verify a signature allegedly by the given client certificate. + /// + /// This method is only called for TLS1.3 handshakes. + /// + /// This method is very similar to `verify_tls12_signature`, but note the + /// tighter ECDSA SignatureScheme semantics in TLS 1.3. For example, + /// `SignatureScheme::ECDSA_NISTP256_SHA256` + /// must only validate signatures using public keys on the right curve -- + /// rustls does not enforce this requirement for you. + /// + /// This trait method has a default implementation that uses webpki to verify + /// the signature. + fn verify_tls13_signature( + &self, + message: &[u8], + cert: &Certificate, + dss: &DigitallySignedStruct, + ) -> Result { + verify_tls13(message, cert, dss) + } + + /// Return the list of SignatureSchemes that this verifier will handle, + /// in `verify_tls12_signature` and `verify_tls13_signature` calls. + /// + /// This should be in priority order, with the most preferred first. + /// + /// This trait method has a default implementation that reflects the schemes + /// supported by webpki. + fn supported_verify_schemes(&self) -> Vec { + WebPkiVerifier::verification_schemes() + } +} + +impl fmt::Debug for dyn ClientCertVerifier { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "dyn ClientCertVerifier") + } +} + +impl ServerCertVerifier for WebPkiVerifier { + /// Will verify the certificate is valid in the following ways: + /// - Signed by a trusted `RootCertStore` CA + /// - Not Expired + /// - Valid for DNS entry + fn verify_server_cert( + &self, + end_entity: &Certificate, + intermediates: &[Certificate], + server_name: &ServerName, + scts: &mut dyn Iterator, + ocsp_response: &[u8], + now: SystemTime, + ) -> Result { + let (cert, chain, trustroots) = prepare(end_entity, intermediates, &self.roots)?; + let webpki_now = webpki::Time::try_from(now).map_err(|_| Error::FailedToGetCurrentTime)?; + + let dns_name = match server_name { + ServerName::DnsName(dns_name) => dns_name, + ServerName::IpAddress(_) => { + return Err(Error::UnsupportedNameType); + } + }; + + let cert = cert + .verify_is_valid_tls_server_cert( + SUPPORTED_SIG_ALGS, + &webpki::TlsServerTrustAnchors(&trustroots), + &chain, + webpki_now, + ) + .map_err(pki_error) + .map(|_| cert)?; + + if let Some(policy) = &self.ct_policy { + policy.verify(end_entity, now, scts)?; + } + + if !ocsp_response.is_empty() { + trace!("Unvalidated OCSP response: {:?}", ocsp_response.to_vec()); + } + + cert.verify_is_valid_for_dns_name(dns_name.0.as_ref()) + .map_err(pki_error) + .map(|_| ServerCertVerified::assertion()) + } +} + +/// Default `ServerCertVerifier`, see the trait impl for more information. +#[allow(unreachable_pub)] +#[cfg_attr(docsrs, doc(cfg(feature = "dangerous_configuration")))] +pub struct WebPkiVerifier { + roots: RootCertStore, + ct_policy: Option, +} + +#[allow(unreachable_pub)] +impl WebPkiVerifier { + /// Constructs a new `WebPkiVerifier`. + /// + /// `roots` is the set of trust anchors to trust for issuing server certs. + /// + /// `ct_logs` is the list of logs that are trusted for Certificate + /// Transparency. Currently CT log enforcement is opportunistic; see + /// . + pub fn new(roots: RootCertStore, ct_policy: Option) -> Self { + Self { roots, ct_policy } + } + + /// Returns the signature verification methods supported by + /// webpki. + pub fn verification_schemes() -> Vec { + vec![ + SignatureScheme::ECDSA_NISTP384_SHA384, + SignatureScheme::ECDSA_NISTP256_SHA256, + SignatureScheme::ED25519, + SignatureScheme::RSA_PSS_SHA512, + SignatureScheme::RSA_PSS_SHA384, + SignatureScheme::RSA_PSS_SHA256, + SignatureScheme::RSA_PKCS1_SHA512, + SignatureScheme::RSA_PKCS1_SHA384, + SignatureScheme::RSA_PKCS1_SHA256, + ] + } +} + +/// Policy for enforcing Certificate Transparency. +/// +/// 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. +#[allow(unreachable_pub)] +#[cfg_attr(docsrs, doc(cfg(feature = "dangerous_configuration")))] +pub struct CertificateTransparencyPolicy { + logs: &'static [&'static sct::Log<'static>], + validation_deadline: SystemTime, +} + +impl CertificateTransparencyPolicy { + /// Create a new policy. + #[allow(unreachable_pub)] + pub fn new( + logs: &'static [&'static sct::Log<'static>], + validation_deadline: SystemTime, + ) -> Self { + Self { + logs, + validation_deadline, + } + } + + fn verify( + &self, + cert: &Certificate, + now: SystemTime, + scts: &mut dyn Iterator, + ) -> Result<(), Error> { + if self.logs.is_empty() { + return Ok(()); + } else if self + .validation_deadline + .duration_since(now) + .is_err() + { + warn!("certificate transparency logs have expired, validation disabled"); + return Ok(()); + } + + let now = unix_time_millis(now)?; + let mut last_sct_error = None; + for sct in scts { + #[cfg_attr(not(feature = "logging"), allow(unused_variables))] + match sct::verify_sct(&cert.0, sct, now, self.logs) { + Ok(index) => { + debug!( + "Valid SCT signed by {} on {}", + self.logs[index].operated_by, self.logs[index].description + ); + return Ok(()); + } + Err(e) => { + if e.should_be_fatal() { + return Err(Error::InvalidSct(e)); + } + debug!("SCT ignored because {:?}", e); + last_sct_error = Some(e); + } + } + } + + /* If we were supplied with some logs, and some SCTs, + * but couldn't verify any of them, fail the handshake. */ + if let Some(last_sct_error) = last_sct_error { + warn!("No valid SCTs provided"); + return Err(Error::InvalidSct(last_sct_error)); + } + + Ok(()) + } +} + +type CertChainAndRoots<'a, 'b> = ( + webpki::EndEntityCert<'a>, + Vec<&'a [u8]>, + Vec>, +); + +fn prepare<'a, 'b>( + end_entity: &'a Certificate, + intermediates: &'a [Certificate], + roots: &'b RootCertStore, +) -> Result, Error> { + // EE cert must appear first. + let cert = webpki::EndEntityCert::try_from(end_entity.0.as_ref()).map_err(pki_error)?; + + let intermediates: Vec<&'a [u8]> = intermediates + .iter() + .map(|cert| cert.0.as_ref()) + .collect(); + + let trustroots: Vec = roots + .roots + .iter() + .map(OwnedTrustAnchor::to_trust_anchor) + .collect(); + + Ok((cert, intermediates, trustroots)) +} + +/// A `ClientCertVerifier` that will ensure that every client provides a trusted +/// certificate, without any name checking. +pub struct AllowAnyAuthenticatedClient { + roots: RootCertStore, +} + +impl AllowAnyAuthenticatedClient { + /// Construct a new `AllowAnyAuthenticatedClient`. + /// + /// `roots` is the list of trust anchors to use for certificate validation. + pub fn new(roots: RootCertStore) -> Arc { + Arc::new(Self { roots }) + } +} + +impl ClientCertVerifier for AllowAnyAuthenticatedClient { + fn offer_client_auth(&self) -> bool { + true + } + + #[allow(deprecated)] + fn client_auth_root_subjects(&self) -> Option { + Some(self.roots.subjects()) + } + + fn verify_client_cert( + &self, + end_entity: &Certificate, + intermediates: &[Certificate], + now: SystemTime, + ) -> Result { + let (cert, chain, trustroots) = prepare(end_entity, intermediates, &self.roots)?; + let now = webpki::Time::try_from(now).map_err(|_| Error::FailedToGetCurrentTime)?; + cert.verify_is_valid_tls_client_cert( + SUPPORTED_SIG_ALGS, + &webpki::TlsClientTrustAnchors(&trustroots), + &chain, + now, + ) + .map_err(pki_error) + .map(|_| ClientCertVerified::assertion()) + } +} + +/// A `ClientCertVerifier` that will allow both anonymous and authenticated +/// clients, without any name checking. +/// +/// Client authentication will be requested during the TLS handshake. If the +/// client offers a certificate then this acts like +/// `AllowAnyAuthenticatedClient`, otherwise this acts like `NoClientAuth`. +pub struct AllowAnyAnonymousOrAuthenticatedClient { + inner: AllowAnyAuthenticatedClient, +} + +impl AllowAnyAnonymousOrAuthenticatedClient { + /// Construct a new `AllowAnyAnonymousOrAuthenticatedClient`. + /// + /// `roots` is the list of trust anchors to use for certificate validation. + pub fn new(roots: RootCertStore) -> Arc { + Arc::new(Self { + inner: AllowAnyAuthenticatedClient { roots }, + }) + } +} + +impl ClientCertVerifier for AllowAnyAnonymousOrAuthenticatedClient { + fn offer_client_auth(&self) -> bool { + self.inner.offer_client_auth() + } + + fn client_auth_mandatory(&self) -> Option { + Some(false) + } + + fn client_auth_root_subjects(&self) -> Option { + self.inner.client_auth_root_subjects() + } + + fn verify_client_cert( + &self, + end_entity: &Certificate, + intermediates: &[Certificate], + now: SystemTime, + ) -> Result { + self.inner + .verify_client_cert(end_entity, intermediates, now) + } +} + +fn pki_error(error: webpki::Error) -> Error { + use webpki::Error::*; + match error { + BadDer | BadDerTime => Error::InvalidCertificateEncoding, + InvalidSignatureForPublicKey => Error::InvalidCertificateSignature, + UnsupportedSignatureAlgorithm | UnsupportedSignatureAlgorithmForPublicKey => { + Error::InvalidCertificateSignatureType + } + e => Error::InvalidCertificateData(format!("invalid peer certificate: {}", e)), + } +} + +/// Turns off client authentication. +pub struct NoClientAuth; + +impl NoClientAuth { + /// Constructs a `NoClientAuth` and wraps it in an `Arc`. + pub fn new() -> Arc { + Arc::new(Self) + } +} + +impl ClientCertVerifier for NoClientAuth { + fn offer_client_auth(&self) -> bool { + false + } + + fn client_auth_root_subjects(&self) -> Option { + unimplemented!(); + } + + fn verify_client_cert( + &self, + _end_entity: &Certificate, + _intermediates: &[Certificate], + _now: SystemTime, + ) -> Result { + unimplemented!(); + } +} + +static ECDSA_SHA256: SignatureAlgorithms = + &[&webpki::ECDSA_P256_SHA256, &webpki::ECDSA_P384_SHA256]; + +static ECDSA_SHA384: SignatureAlgorithms = + &[&webpki::ECDSA_P256_SHA384, &webpki::ECDSA_P384_SHA384]; + +static ED25519: SignatureAlgorithms = &[&webpki::ED25519]; + +static RSA_SHA256: SignatureAlgorithms = &[&webpki::RSA_PKCS1_2048_8192_SHA256]; +static RSA_SHA384: SignatureAlgorithms = &[&webpki::RSA_PKCS1_2048_8192_SHA384]; +static RSA_SHA512: SignatureAlgorithms = &[&webpki::RSA_PKCS1_2048_8192_SHA512]; +static RSA_PSS_SHA256: SignatureAlgorithms = &[&webpki::RSA_PSS_2048_8192_SHA256_LEGACY_KEY]; +static RSA_PSS_SHA384: SignatureAlgorithms = &[&webpki::RSA_PSS_2048_8192_SHA384_LEGACY_KEY]; +static RSA_PSS_SHA512: SignatureAlgorithms = &[&webpki::RSA_PSS_2048_8192_SHA512_LEGACY_KEY]; + +fn convert_scheme(scheme: SignatureScheme) -> Result { + match scheme { + // nb. for TLS1.2 the curve is not fixed by SignatureScheme. + SignatureScheme::ECDSA_NISTP256_SHA256 => Ok(ECDSA_SHA256), + SignatureScheme::ECDSA_NISTP384_SHA384 => Ok(ECDSA_SHA384), + + SignatureScheme::ED25519 => Ok(ED25519), + + SignatureScheme::RSA_PKCS1_SHA256 => Ok(RSA_SHA256), + SignatureScheme::RSA_PKCS1_SHA384 => Ok(RSA_SHA384), + SignatureScheme::RSA_PKCS1_SHA512 => Ok(RSA_SHA512), + + SignatureScheme::RSA_PSS_SHA256 => Ok(RSA_PSS_SHA256), + SignatureScheme::RSA_PSS_SHA384 => Ok(RSA_PSS_SHA384), + SignatureScheme::RSA_PSS_SHA512 => Ok(RSA_PSS_SHA512), + + _ => { + let error_msg = format!("received unadvertised sig scheme {:?}", scheme); + Err(Error::PeerMisbehavedError(error_msg)) + } + } +} + +fn verify_sig_using_any_alg( + cert: &webpki::EndEntityCert, + algs: SignatureAlgorithms, + message: &[u8], + sig: &[u8], +) -> Result<(), webpki::Error> { + // TLS doesn't itself give us enough info to map to a single webpki::SignatureAlgorithm. + // Therefore, convert_algs maps to several and we try them all. + for alg in algs { + match cert.verify_signature(alg, message, sig) { + Err(webpki::Error::UnsupportedSignatureAlgorithmForPublicKey) => continue, + res => return res, + } + } + + Err(webpki::Error::UnsupportedSignatureAlgorithmForPublicKey) +} + +fn verify_signed_struct( + message: &[u8], + cert: &Certificate, + dss: &DigitallySignedStruct, +) -> Result { + let possible_algs = convert_scheme(dss.scheme)?; + let cert = webpki::EndEntityCert::try_from(cert.0.as_ref()).map_err(pki_error)?; + + verify_sig_using_any_alg(&cert, possible_algs, message, dss.signature()) + .map_err(pki_error) + .map(|_| HandshakeSignatureValid::assertion()) +} + +fn convert_alg_tls13( + scheme: SignatureScheme, +) -> Result<&'static webpki::SignatureAlgorithm, Error> { + use crate::enums::SignatureScheme::*; + + match scheme { + ECDSA_NISTP256_SHA256 => Ok(&webpki::ECDSA_P256_SHA256), + ECDSA_NISTP384_SHA384 => Ok(&webpki::ECDSA_P384_SHA384), + ED25519 => Ok(&webpki::ED25519), + RSA_PSS_SHA256 => Ok(&webpki::RSA_PSS_2048_8192_SHA256_LEGACY_KEY), + RSA_PSS_SHA384 => Ok(&webpki::RSA_PSS_2048_8192_SHA384_LEGACY_KEY), + RSA_PSS_SHA512 => Ok(&webpki::RSA_PSS_2048_8192_SHA512_LEGACY_KEY), + _ => { + let error_msg = format!("received unsupported sig scheme {:?}", scheme); + Err(Error::PeerMisbehavedError(error_msg)) + } + } +} + +/// Constructs the signature message specified in section 4.4.3 of RFC8446. +pub(crate) fn construct_tls13_client_verify_message(handshake_hash: &Digest) -> Vec { + construct_tls13_verify_message(handshake_hash, b"TLS 1.3, client CertificateVerify\x00") +} + +/// Constructs the signature message specified in section 4.4.3 of RFC8446. +pub(crate) fn construct_tls13_server_verify_message(handshake_hash: &Digest) -> Vec { + construct_tls13_verify_message(handshake_hash, b"TLS 1.3, server CertificateVerify\x00") +} + +fn construct_tls13_verify_message( + handshake_hash: &Digest, + context_string_with_0: &[u8], +) -> Vec { + let mut msg = Vec::new(); + msg.resize(64, 0x20u8); + msg.extend_from_slice(context_string_with_0); + msg.extend_from_slice(handshake_hash.as_ref()); + msg +} + +fn verify_tls13( + msg: &[u8], + cert: &Certificate, + dss: &DigitallySignedStruct, +) -> Result { + let alg = convert_alg_tls13(dss.scheme)?; + + let cert = webpki::EndEntityCert::try_from(cert.0.as_ref()).map_err(pki_error)?; + + cert.verify_signature(alg, msg, dss.signature()) + .map_err(pki_error) + .map(|_| HandshakeSignatureValid::assertion()) +} + +fn unix_time_millis(now: SystemTime) -> Result { + now.duration_since(std::time::UNIX_EPOCH) + .map(|dur| dur.as_secs()) + .map_err(|_| Error::FailedToGetCurrentTime) + .and_then(|secs| { + secs.checked_mul(1000) + .ok_or(Error::FailedToGetCurrentTime) + }) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn assertions_are_debug() { + assert_eq!( + format!("{:?}", ClientCertVerified::assertion()), + "ClientCertVerified(())" + ); + assert_eq!( + format!("{:?}", HandshakeSignatureValid::assertion()), + "HandshakeSignatureValid(())" + ); + assert_eq!( + format!("{:?}", FinishedMessageVerified::assertion()), + "FinishedMessageVerified(())" + ); + assert_eq!( + format!("{:?}", ServerCertVerified::assertion()), + "ServerCertVerified(())" + ); + } +} diff --git a/third_party/rustls-fork-shadow-tls/src/verifybench.rs b/third_party/rustls-fork-shadow-tls/src/verifybench.rs new file mode 100644 index 0000000..9ddb271 --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/src/verifybench.rs @@ -0,0 +1,245 @@ +// This program does benchmarking of the functions in verify.rs, +// that do certificate chain validation and signature verification. +// +// 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::time::{Duration, Instant, SystemTime}; + +use crate::key; +use crate::verify; +use crate::verify::ServerCertVerifier; +use crate::{anchors, OwnedTrustAnchor}; + +use webpki_roots; + +fn duration_nanos(d: Duration) -> u64 { + ((d.as_secs() as f64) * 1e9 + (d.subsec_nanos() as f64)) as u64 +} + +#[test] +fn test_reddit_cert() { + Context::new( + "reddit", + "reddit.com", + &[ + include_bytes!("testdata/cert-reddit.0.der"), + include_bytes!("testdata/cert-reddit.1.der"), + ], + ) + .bench(100) +} + +#[test] +fn test_github_cert() { + Context::new( + "github", + "github.com", + &[ + include_bytes!("testdata/cert-github.0.der"), + include_bytes!("testdata/cert-github.1.der"), + ], + ) + .bench(100) +} + +#[test] +fn test_arstechnica_cert() { + Context::new( + "arstechnica", + "arstechnica.com", + &[ + include_bytes!("testdata/cert-arstechnica.0.der"), + include_bytes!("testdata/cert-arstechnica.1.der"), + include_bytes!("testdata/cert-arstechnica.2.der"), + include_bytes!("testdata/cert-arstechnica.3.der"), + ], + ) + .bench(100) +} + +#[test] +fn test_servo_cert() { + Context::new( + "servo", + "servo.org", + &[ + include_bytes!("testdata/cert-servo.0.der"), + include_bytes!("testdata/cert-servo.1.der"), + ], + ) + .bench(100) +} + +#[test] +fn test_twitter_cert() { + Context::new( + "twitter", + "twitter.com", + &[ + include_bytes!("testdata/cert-twitter.0.der"), + include_bytes!("testdata/cert-twitter.1.der"), + ], + ) + .bench(100) +} + +#[test] +fn test_wikipedia_cert() { + Context::new( + "wikipedia", + "wikipedia.org", + &[ + include_bytes!("testdata/cert-wikipedia.0.der"), + include_bytes!("testdata/cert-wikipedia.1.der"), + ], + ) + .bench(100) +} + +#[test] +fn test_google_cert() { + Context::new( + "google", + "www.google.com", + &[ + include_bytes!("testdata/cert-google.0.der"), + include_bytes!("testdata/cert-google.1.der"), + ], + ) + .bench(100) +} + +#[test] +fn test_hn_cert() { + Context::new( + "hn", + "news.ycombinator.com", + &[ + include_bytes!("testdata/cert-hn.0.der"), + include_bytes!("testdata/cert-hn.1.der"), + ], + ) + .bench(100) +} + +#[test] +fn test_stackoverflow_cert() { + Context::new( + "stackoverflow", + "stackoverflow.com", + &[ + include_bytes!("testdata/cert-stackoverflow.0.der"), + include_bytes!("testdata/cert-stackoverflow.1.der"), + ], + ) + .bench(100) +} + +#[test] +fn test_duckduckgo_cert() { + Context::new( + "duckduckgo", + "duckduckgo.com", + &[ + include_bytes!("testdata/cert-duckduckgo.0.der"), + include_bytes!("testdata/cert-duckduckgo.1.der"), + ], + ) + .bench(100) +} + +#[test] +fn test_rustlang_cert() { + Context::new( + "rustlang", + "www.rust-lang.org", + &[ + include_bytes!("testdata/cert-rustlang.0.der"), + include_bytes!("testdata/cert-rustlang.1.der"), + include_bytes!("testdata/cert-rustlang.2.der"), + ], + ) + .bench(100) +} + +#[test] +fn test_wapo_cert() { + Context::new( + "wapo", + "www.washingtonpost.com", + &[ + include_bytes!("testdata/cert-wapo.0.der"), + include_bytes!("testdata/cert-wapo.1.der"), + ], + ) + .bench(100) +} + +struct Context { + name: &'static str, + domain: &'static str, + roots: anchors::RootCertStore, + chain: Vec, + now: SystemTime, +} + +impl Context { + fn new(name: &'static str, domain: &'static str, certs: &[&'static [u8]]) -> Self { + let mut roots = anchors::RootCertStore::empty(); + roots.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, + ) + }), + ); + Self { + name, + domain, + roots, + chain: certs + .iter() + .copied() + .map(|bytes| key::Certificate(bytes.to_vec())) + .collect(), + now: SystemTime::UNIX_EPOCH + Duration::from_secs(1640870720), + } + } + + fn bench(&self, count: usize) { + let verifier = verify::WebPkiVerifier::new(self.roots.clone(), None); + const SCTS: &[&[u8]] = &[]; + const OCSP_RESPONSE: &[u8] = &[]; + let mut times = Vec::new(); + + let (end_entity, intermediates) = self.chain.split_first().unwrap(); + for _ in 0..count { + let start = Instant::now(); + let server_name = self.domain.try_into().unwrap(); + verifier + .verify_server_cert( + end_entity, + intermediates, + &server_name, + &mut SCTS.iter().copied(), + OCSP_RESPONSE, + self.now, + ) + .unwrap(); + times.push(duration_nanos(Instant::now().duration_since(start))); + } + + println!( + "verify_server_cert({}): min {:?}us", + self.name, + times.iter().min().unwrap() / 1000 + ); + } +} diff --git a/third_party/rustls-fork-shadow-tls/src/versions.rs b/third_party/rustls-fork-shadow-tls/src/versions.rs new file mode 100644 index 0000000..738453f --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/src/versions.rs @@ -0,0 +1,99 @@ +use std::fmt; + +use crate::enums::ProtocolVersion; + +/// A TLS protocol version supported by rustls. +/// +/// All possible instances of this class are provided by the library in +/// the [`ALL_VERSIONS`] array, as well as individually as [`TLS12`] +/// and [`TLS13`]. +#[derive(Eq, PartialEq)] +pub struct SupportedProtocolVersion { + /// The TLS enumeration naming this version. + pub version: ProtocolVersion, + is_private: (), +} + +impl fmt::Debug for SupportedProtocolVersion { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.version.fmt(f) + } +} + +/// TLS1.2 +#[cfg(feature = "tls12")] +pub static TLS12: SupportedProtocolVersion = SupportedProtocolVersion { + version: ProtocolVersion::TLSv1_2, + is_private: (), +}; + +/// TLS1.3 +pub static TLS13: SupportedProtocolVersion = SupportedProtocolVersion { + version: ProtocolVersion::TLSv1_3, + is_private: (), +}; + +/// A list of all the protocol versions supported by rustls. +pub static ALL_VERSIONS: &[&SupportedProtocolVersion] = &[ + &TLS13, + #[cfg(feature = "tls12")] + &TLS12, +]; + +/// The version configuration that an application should use by default. +/// +/// This will be [`ALL_VERSIONS`] for now, but gives space in the future +/// to remove a version from here and require users to opt-in to older +/// versions. +pub static DEFAULT_VERSIONS: &[&SupportedProtocolVersion] = ALL_VERSIONS; + +#[derive(Clone)] +pub(crate) struct EnabledVersions { + #[cfg(feature = "tls12")] + tls12: Option<&'static SupportedProtocolVersion>, + tls13: Option<&'static SupportedProtocolVersion>, +} + +impl fmt::Debug for EnabledVersions { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut list = &mut f.debug_list(); + #[cfg(feature = "tls12")] + if let Some(v) = self.tls12 { + list = list.entry(v) + } + if let Some(v) = self.tls13 { + list = list.entry(v) + } + list.finish() + } +} + +impl EnabledVersions { + pub(crate) fn new(versions: &[&'static SupportedProtocolVersion]) -> Self { + let mut ev = Self { + #[cfg(feature = "tls12")] + tls12: None, + tls13: None, + }; + + for v in versions { + match v.version { + #[cfg(feature = "tls12")] + ProtocolVersion::TLSv1_2 => ev.tls12 = Some(v), + ProtocolVersion::TLSv1_3 => ev.tls13 = Some(v), + _ => {} + } + } + + ev + } + + pub(crate) fn contains(&self, version: ProtocolVersion) -> bool { + match version { + #[cfg(feature = "tls12")] + ProtocolVersion::TLSv1_2 => self.tls12.is_some(), + ProtocolVersion::TLSv1_3 => self.tls13.is_some(), + _ => false, + } + } +} diff --git a/third_party/rustls-fork-shadow-tls/src/x509.rs b/third_party/rustls-fork-shadow-tls/src/x509.rs new file mode 100644 index 0000000..17239d7 --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/src/x509.rs @@ -0,0 +1,93 @@ +// Additional x509/asn1 functions to those provided in webpki/ring. + +use ring::io::der; + +pub(crate) fn wrap_in_asn1_len(bytes: &mut Vec) { + let len = bytes.len(); + + if len <= 0x7f { + bytes.insert(0, len as u8); + } else { + bytes.insert(0, 0x80u8); + let mut left = len; + while left > 0 { + let byte = (left & 0xff) as u8; + bytes.insert(1, byte); + bytes[0] += 1; + left >>= 8; + } + } +} + +/// Prepend stuff to `bytes` to put it in a DER SEQUENCE. +pub(crate) fn wrap_in_sequence(bytes: &mut Vec) { + wrap_in_asn1_len(bytes); + bytes.insert(0, der::Tag::Sequence as u8); +} + +#[test] +fn test_empty() { + let mut val = Vec::new(); + wrap_in_sequence(&mut val); + assert_eq!(vec![0x30, 0x00], val); +} + +#[test] +fn test_small() { + let mut val = Vec::new(); + val.insert(0, 0x00); + val.insert(1, 0x11); + val.insert(2, 0x22); + val.insert(3, 0x33); + wrap_in_sequence(&mut val); + assert_eq!(vec![0x30, 0x04, 0x00, 0x11, 0x22, 0x33], val); +} + +#[test] +fn test_medium() { + let mut val = Vec::new(); + val.resize(255, 0x12); + wrap_in_sequence(&mut val); + assert_eq!(vec![0x30, 0x81, 0xff, 0x12, 0x12, 0x12], val[..6].to_vec()); +} + +#[test] +fn test_large() { + let mut val = Vec::new(); + val.resize(4660, 0x12); + wrap_in_sequence(&mut val); + assert_eq!(vec![0x30, 0x82, 0x12, 0x34, 0x12, 0x12], val[..6].to_vec()); +} + +#[test] +fn test_huge() { + let mut val = Vec::new(); + val.resize(0xffff, 0x12); + wrap_in_sequence(&mut val); + assert_eq!(vec![0x30, 0x82, 0xff, 0xff, 0x12, 0x12], val[..6].to_vec()); + assert_eq!(val.len(), 0xffff + 4); +} + +#[test] +fn test_gigantic() { + let mut val = Vec::new(); + val.resize(0x100000, 0x12); + wrap_in_sequence(&mut val); + assert_eq!( + vec![0x30, 0x83, 0x10, 0x00, 0x00, 0x12, 0x12], + val[..7].to_vec() + ); + assert_eq!(val.len(), 0x100000 + 5); +} + +#[test] +fn test_ludicrous() { + let mut val = Vec::new(); + val.resize(0x1000000, 0x12); + wrap_in_sequence(&mut val); + assert_eq!( + vec![0x30, 0x84, 0x01, 0x00, 0x00, 0x00, 0x12, 0x12], + val[..8].to_vec() + ); + assert_eq!(val.len(), 0x1000000 + 6); +} diff --git a/third_party/rustls-fork-shadow-tls/tests/api.rs b/third_party/rustls-fork-shadow-tls/tests/api.rs new file mode 100644 index 0000000..5f47cc9 --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/tests/api.rs @@ -0,0 +1,4382 @@ +//! Assorted public API tests. +use std::cell::RefCell; +use std::convert::TryFrom; +#[cfg(feature = "tls12")] +use std::convert::TryInto; +use std::fmt; +use std::io::{self, IoSlice, Read, Write}; +use std::mem; +use std::ops::{Deref, DerefMut}; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::Arc; +use std::sync::Mutex; + +use log; + +use rustls::client::ResolvesClientCert; +use rustls::internal::msgs::base::Payload; +use rustls::internal::msgs::codec::Codec; +#[cfg(feature = "quic")] +use rustls::quic::{self, ClientQuicExt, QuicExt, ServerQuicExt}; +use rustls::server::{AllowAnyAnonymousOrAuthenticatedClient, ClientHello, ResolvesServerCert}; +#[cfg(feature = "secret_extraction")] +use rustls::ConnectionTrafficSecrets; +use rustls::{sign, ConnectionCommon, Error, KeyLog, SideData}; +use rustls::{CipherSuite, ProtocolVersion, SignatureScheme}; +use rustls::{ClientConfig, ClientConnection}; +use rustls::{ServerConfig, ServerConnection}; +use rustls::{Stream, StreamOwned}; +use rustls::{SupportedCipherSuite, ALL_CIPHER_SUITES}; + +mod common; +use crate::common::*; + +fn alpn_test_error( + server_protos: Vec>, + client_protos: Vec>, + agreed: Option<&[u8]>, + expected_error: Option, +) { + let mut server_config = make_server_config(KeyType::Rsa); + server_config.alpn_protocols = server_protos; + + let server_config = Arc::new(server_config); + + for version in rustls::ALL_VERSIONS { + let mut client_config = make_client_config_with_versions(KeyType::Rsa, &[version]); + client_config.alpn_protocols = client_protos.clone(); + + let (mut client, mut server) = + make_pair_for_arc_configs(&Arc::new(client_config), &server_config); + + assert_eq!(client.alpn_protocol(), None); + assert_eq!(server.alpn_protocol(), None); + let error = do_handshake_until_error(&mut client, &mut server); + assert_eq!(client.alpn_protocol(), agreed); + assert_eq!(server.alpn_protocol(), agreed); + assert_eq!(error.err(), expected_error); + } +} + +fn alpn_test(server_protos: Vec>, client_protos: Vec>, agreed: Option<&[u8]>) { + alpn_test_error(server_protos, client_protos, agreed, None) +} + +#[test] +fn alpn() { + // no support + alpn_test(vec![], vec![], None); + + // server support + alpn_test(vec![b"server-proto".to_vec()], vec![], None); + + // client support + alpn_test(vec![], vec![b"client-proto".to_vec()], None); + + // no overlap + alpn_test_error( + vec![b"server-proto".to_vec()], + vec![b"client-proto".to_vec()], + None, + Some(ErrorFromPeer::Server(Error::NoApplicationProtocol)), + ); + + // server chooses preference + alpn_test( + vec![b"server-proto".to_vec(), b"client-proto".to_vec()], + vec![b"client-proto".to_vec(), b"server-proto".to_vec()], + Some(b"server-proto"), + ); + + // case sensitive + alpn_test_error( + vec![b"PROTO".to_vec()], + vec![b"proto".to_vec()], + None, + Some(ErrorFromPeer::Server(Error::NoApplicationProtocol)), + ); +} + +fn version_test( + client_versions: &[&'static rustls::SupportedProtocolVersion], + server_versions: &[&'static rustls::SupportedProtocolVersion], + result: Option, +) { + let client_versions = if client_versions.is_empty() { + &rustls::ALL_VERSIONS + } else { + client_versions + }; + let server_versions = if server_versions.is_empty() { + &rustls::ALL_VERSIONS + } else { + server_versions + }; + + let client_config = make_client_config_with_versions(KeyType::Rsa, client_versions); + let server_config = make_server_config_with_versions(KeyType::Rsa, server_versions); + + println!( + "version {:?} {:?} -> {:?}", + client_versions, server_versions, result + ); + + let (mut client, mut server) = make_pair_for_configs(client_config, server_config); + + assert_eq!(client.protocol_version(), None); + assert_eq!(server.protocol_version(), None); + if result.is_none() { + let err = do_handshake_until_error(&mut client, &mut server); + assert!(err.is_err()); + } else { + do_handshake(&mut client, &mut server); + assert_eq!(client.protocol_version(), result); + assert_eq!(server.protocol_version(), result); + } +} + +#[test] +fn versions() { + // default -> 1.3 + version_test(&[], &[], Some(ProtocolVersion::TLSv1_3)); + + // client default, server 1.2 -> 1.2 + #[cfg(feature = "tls12")] + version_test( + &[], + &[&rustls::version::TLS12], + Some(ProtocolVersion::TLSv1_2), + ); + + // client 1.2, server default -> 1.2 + #[cfg(feature = "tls12")] + version_test( + &[&rustls::version::TLS12], + &[], + Some(ProtocolVersion::TLSv1_2), + ); + + // client 1.2, server 1.3 -> fail + #[cfg(feature = "tls12")] + version_test(&[&rustls::version::TLS12], &[&rustls::version::TLS13], None); + + // client 1.3, server 1.2 -> fail + #[cfg(feature = "tls12")] + version_test(&[&rustls::version::TLS13], &[&rustls::version::TLS12], None); + + // client 1.3, server 1.2+1.3 -> 1.3 + #[cfg(feature = "tls12")] + version_test( + &[&rustls::version::TLS13], + &[&rustls::version::TLS12, &rustls::version::TLS13], + Some(ProtocolVersion::TLSv1_3), + ); + + // client 1.2+1.3, server 1.2 -> 1.2 + #[cfg(feature = "tls12")] + version_test( + &[&rustls::version::TLS13, &rustls::version::TLS12], + &[&rustls::version::TLS12], + Some(ProtocolVersion::TLSv1_2), + ); +} + +fn check_read(reader: &mut dyn io::Read, bytes: &[u8]) { + let mut buf = vec![0u8; bytes.len() + 1]; + assert_eq!(bytes.len(), reader.read(&mut buf).unwrap()); + assert_eq!(bytes, &buf[..bytes.len()]); +} + +#[test] +fn config_builder_for_client_rejects_empty_kx_groups() { + assert_eq!( + ClientConfig::builder() + .with_safe_default_cipher_suites() + .with_kx_groups(&[]) + .with_safe_default_protocol_versions() + .err(), + Some(Error::General("no kx groups configured".into())) + ); +} + +#[test] +fn config_builder_for_client_rejects_empty_cipher_suites() { + assert_eq!( + ClientConfig::builder() + .with_cipher_suites(&[]) + .with_safe_default_kx_groups() + .with_safe_default_protocol_versions() + .err(), + Some(Error::General("no usable cipher suites configured".into())) + ); +} + +#[cfg(feature = "tls12")] +#[test] +fn config_builder_for_client_rejects_incompatible_cipher_suites() { + assert_eq!( + ClientConfig::builder() + .with_cipher_suites(&[rustls::cipher_suite::TLS13_AES_256_GCM_SHA384]) + .with_safe_default_kx_groups() + .with_protocol_versions(&[&rustls::version::TLS12]) + .err(), + Some(Error::General("no usable cipher suites configured".into())) + ); +} + +#[test] +fn config_builder_for_server_rejects_empty_kx_groups() { + assert_eq!( + ServerConfig::builder() + .with_safe_default_cipher_suites() + .with_kx_groups(&[]) + .with_safe_default_protocol_versions() + .err(), + Some(Error::General("no kx groups configured".into())) + ); +} + +#[test] +fn config_builder_for_server_rejects_empty_cipher_suites() { + assert_eq!( + ServerConfig::builder() + .with_cipher_suites(&[]) + .with_safe_default_kx_groups() + .with_safe_default_protocol_versions() + .err(), + Some(Error::General("no usable cipher suites configured".into())) + ); +} + +#[cfg(feature = "tls12")] +#[test] +fn config_builder_for_server_rejects_incompatible_cipher_suites() { + assert_eq!( + ServerConfig::builder() + .with_cipher_suites(&[rustls::cipher_suite::TLS13_AES_256_GCM_SHA384]) + .with_safe_default_kx_groups() + .with_protocol_versions(&[&rustls::version::TLS12]) + .err(), + Some(Error::General("no usable cipher suites configured".into())) + ); +} + +#[test] +fn buffered_client_data_sent() { + let server_config = Arc::new(make_server_config(KeyType::Rsa)); + + for version in rustls::ALL_VERSIONS { + let client_config = make_client_config_with_versions(KeyType::Rsa, &[version]); + let (mut client, mut server) = + make_pair_for_arc_configs(&Arc::new(client_config), &server_config); + + assert_eq!(5, client.writer().write(b"hello").unwrap()); + + do_handshake(&mut client, &mut server); + transfer(&mut client, &mut server); + server.process_new_packets().unwrap(); + + check_read(&mut server.reader(), b"hello"); + } +} + +#[test] +fn buffered_server_data_sent() { + let server_config = Arc::new(make_server_config(KeyType::Rsa)); + + for version in rustls::ALL_VERSIONS { + let client_config = make_client_config_with_versions(KeyType::Rsa, &[version]); + let (mut client, mut server) = + make_pair_for_arc_configs(&Arc::new(client_config), &server_config); + + assert_eq!(5, server.writer().write(b"hello").unwrap()); + + do_handshake(&mut client, &mut server); + transfer(&mut server, &mut client); + client.process_new_packets().unwrap(); + + check_read(&mut client.reader(), b"hello"); + } +} + +#[test] +fn buffered_both_data_sent() { + let server_config = Arc::new(make_server_config(KeyType::Rsa)); + + for version in rustls::ALL_VERSIONS { + let client_config = make_client_config_with_versions(KeyType::Rsa, &[version]); + let (mut client, mut server) = + make_pair_for_arc_configs(&Arc::new(client_config), &server_config); + + assert_eq!( + 12, + server + .writer() + .write(b"from-server!") + .unwrap() + ); + assert_eq!( + 12, + client + .writer() + .write(b"from-client!") + .unwrap() + ); + + do_handshake(&mut client, &mut server); + + transfer(&mut server, &mut client); + client.process_new_packets().unwrap(); + transfer(&mut client, &mut server); + server.process_new_packets().unwrap(); + + check_read(&mut client.reader(), b"from-server!"); + check_read(&mut server.reader(), b"from-client!"); + } +} + +#[test] +fn client_can_get_server_cert() { + for kt in ALL_KEY_TYPES.iter() { + for version in rustls::ALL_VERSIONS { + let client_config = make_client_config_with_versions(*kt, &[version]); + let (mut client, mut server) = + make_pair_for_configs(client_config, make_server_config(*kt)); + do_handshake(&mut client, &mut server); + + let certs = client.peer_certificates(); + assert_eq!(certs, Some(kt.get_chain().as_slice())); + } + } +} + +#[test] +fn client_can_get_server_cert_after_resumption() { + for kt in ALL_KEY_TYPES.iter() { + let server_config = make_server_config(*kt); + for version in rustls::ALL_VERSIONS { + let client_config = make_client_config_with_versions(*kt, &[version]); + let (mut client, mut server) = + make_pair_for_configs(client_config.clone(), server_config.clone()); + do_handshake(&mut client, &mut server); + + let original_certs = client.peer_certificates(); + + let (mut client, mut server) = + make_pair_for_configs(client_config.clone(), server_config.clone()); + do_handshake(&mut client, &mut server); + + let resumed_certs = client.peer_certificates(); + + assert_eq!(original_certs, resumed_certs); + } + } +} + +#[test] +fn server_can_get_client_cert() { + for kt in ALL_KEY_TYPES.iter() { + let server_config = Arc::new(make_server_config_with_mandatory_client_auth(*kt)); + + for version in rustls::ALL_VERSIONS { + let client_config = make_client_config_with_versions_with_auth(*kt, &[version]); + let (mut client, mut server) = + make_pair_for_arc_configs(&Arc::new(client_config), &server_config); + do_handshake(&mut client, &mut server); + + let certs = server.peer_certificates(); + assert_eq!(certs, Some(kt.get_client_chain().as_slice())); + } + } +} + +#[test] +fn server_can_get_client_cert_after_resumption() { + for kt in ALL_KEY_TYPES.iter() { + let server_config = Arc::new(make_server_config_with_mandatory_client_auth(*kt)); + + for version in rustls::ALL_VERSIONS { + let client_config = make_client_config_with_versions_with_auth(*kt, &[version]); + let client_config = Arc::new(client_config); + let (mut client, mut server) = + make_pair_for_arc_configs(&client_config, &server_config); + do_handshake(&mut client, &mut server); + let original_certs = server.peer_certificates(); + + let (mut client, mut server) = + make_pair_for_arc_configs(&client_config, &server_config); + do_handshake(&mut client, &mut server); + let resumed_certs = server.peer_certificates(); + assert_eq!(original_certs, resumed_certs); + } + } +} + +#[test] +fn test_config_builders_debug() { + let b = ServerConfig::builder(); + assert_eq!( + "ConfigBuilder { state: WantsCipherSuites(()) }", + format!("{:?}", b) + ); + let b = b.with_cipher_suites(&[rustls::cipher_suite::TLS13_CHACHA20_POLY1305_SHA256]); + assert_eq!("ConfigBuilder { state: WantsKxGroups { cipher_suites: [TLS13_CHACHA20_POLY1305_SHA256] } }", format!("{:?}", b)); + let b = b.with_kx_groups(&[&rustls::kx_group::X25519]); + assert_eq!("ConfigBuilder { state: WantsVersions { cipher_suites: [TLS13_CHACHA20_POLY1305_SHA256], kx_groups: [X25519] } }", format!("{:?}", b)); + let b = b + .with_protocol_versions(&[&rustls::version::TLS13]) + .unwrap(); + let b = b.with_no_client_auth(); + assert_eq!("ConfigBuilder { state: WantsServerCert { cipher_suites: [TLS13_CHACHA20_POLY1305_SHA256], kx_groups: [X25519], versions: [TLSv1_3], verifier: dyn ClientCertVerifier } }", format!("{:?}", b)); + + let b = ClientConfig::builder(); + assert_eq!( + "ConfigBuilder { state: WantsCipherSuites(()) }", + format!("{:?}", b) + ); + let b = b.with_cipher_suites(&[rustls::cipher_suite::TLS13_CHACHA20_POLY1305_SHA256]); + assert_eq!("ConfigBuilder { state: WantsKxGroups { cipher_suites: [TLS13_CHACHA20_POLY1305_SHA256] } }", format!("{:?}", b)); + let b = b.with_kx_groups(&[&rustls::kx_group::X25519]); + assert_eq!("ConfigBuilder { state: WantsVersions { cipher_suites: [TLS13_CHACHA20_POLY1305_SHA256], kx_groups: [X25519] } }", format!("{:?}", b)); + let b = b + .with_protocol_versions(&[&rustls::version::TLS13]) + .unwrap(); + assert_eq!("ConfigBuilder { state: WantsVerifier { cipher_suites: [TLS13_CHACHA20_POLY1305_SHA256], kx_groups: [X25519], versions: [TLSv1_3] } }", format!("{:?}", b)); +} + +/// Test that the server handles combination of `offer_client_auth()` returning true +/// and `client_auth_mandatory` returning `Some(false)`. This exercises both the +/// client's and server's ability to "recover" from the server asking for a client +/// certificate and not being given one. This also covers the implementation +/// of `AllowAnyAnonymousOrAuthenticatedClient`. +#[test] +fn server_allow_any_anonymous_or_authenticated_client() { + let kt = KeyType::Rsa; + for client_cert_chain in [None, Some(kt.get_client_chain())].iter() { + let client_auth_roots = get_client_root_store(kt); + let client_auth = AllowAnyAnonymousOrAuthenticatedClient::new(client_auth_roots); + + let server_config = ServerConfig::builder() + .with_safe_defaults() + .with_client_cert_verifier(client_auth) + .with_single_cert(kt.get_chain(), kt.get_key()) + .unwrap(); + let server_config = Arc::new(server_config); + + for version in rustls::ALL_VERSIONS { + let client_config = if client_cert_chain.is_some() { + make_client_config_with_versions_with_auth(kt, &[version]) + } else { + make_client_config_with_versions(kt, &[version]) + }; + let (mut client, mut server) = + make_pair_for_arc_configs(&Arc::new(client_config), &server_config); + do_handshake(&mut client, &mut server); + + let certs = server.peer_certificates(); + assert_eq!(certs, client_cert_chain.as_deref()); + } + } +} + +fn check_read_and_close(reader: &mut dyn io::Read, expect: &[u8]) { + check_read(reader, expect); + assert!(matches!(reader.read(&mut [0u8; 5]), Ok(0))); +} + +#[test] +fn server_close_notify() { + let kt = KeyType::Rsa; + let server_config = Arc::new(make_server_config_with_mandatory_client_auth(kt)); + + for version in rustls::ALL_VERSIONS { + let client_config = make_client_config_with_versions_with_auth(kt, &[version]); + let (mut client, mut server) = + make_pair_for_arc_configs(&Arc::new(client_config), &server_config); + do_handshake(&mut client, &mut server); + + // check that alerts don't overtake appdata + assert_eq!( + 12, + server + .writer() + .write(b"from-server!") + .unwrap() + ); + assert_eq!( + 12, + client + .writer() + .write(b"from-client!") + .unwrap() + ); + server.send_close_notify(); + + transfer(&mut server, &mut client); + let io_state = client.process_new_packets().unwrap(); + assert!(io_state.peer_has_closed()); + check_read_and_close(&mut client.reader(), b"from-server!"); + + transfer(&mut client, &mut server); + server.process_new_packets().unwrap(); + check_read(&mut server.reader(), b"from-client!"); + } +} + +#[test] +fn client_close_notify() { + let kt = KeyType::Rsa; + let server_config = Arc::new(make_server_config_with_mandatory_client_auth(kt)); + + for version in rustls::ALL_VERSIONS { + let client_config = make_client_config_with_versions_with_auth(kt, &[version]); + let (mut client, mut server) = + make_pair_for_arc_configs(&Arc::new(client_config), &server_config); + do_handshake(&mut client, &mut server); + + // check that alerts don't overtake appdata + assert_eq!( + 12, + server + .writer() + .write(b"from-server!") + .unwrap() + ); + assert_eq!( + 12, + client + .writer() + .write(b"from-client!") + .unwrap() + ); + client.send_close_notify(); + + transfer(&mut client, &mut server); + let io_state = server.process_new_packets().unwrap(); + assert!(io_state.peer_has_closed()); + check_read_and_close(&mut server.reader(), b"from-client!"); + + transfer(&mut server, &mut client); + client.process_new_packets().unwrap(); + check_read(&mut client.reader(), b"from-server!"); + } +} + +#[test] +fn server_closes_uncleanly() { + let kt = KeyType::Rsa; + let server_config = Arc::new(make_server_config(kt)); + + for version in rustls::ALL_VERSIONS { + let client_config = make_client_config_with_versions(kt, &[version]); + let (mut client, mut server) = + make_pair_for_arc_configs(&Arc::new(client_config), &server_config); + do_handshake(&mut client, &mut server); + + // check that unclean EOF reporting does not overtake appdata + assert_eq!( + 12, + server + .writer() + .write(b"from-server!") + .unwrap() + ); + assert_eq!( + 12, + client + .writer() + .write(b"from-client!") + .unwrap() + ); + + transfer(&mut server, &mut client); + transfer_eof(&mut client); + let io_state = client.process_new_packets().unwrap(); + assert!(!io_state.peer_has_closed()); + check_read(&mut client.reader(), b"from-server!"); + + assert!(matches!(client.reader().read(&mut [0u8; 1]), + Err(err) if err.kind() == io::ErrorKind::UnexpectedEof)); + + // may still transmit pending frames + transfer(&mut client, &mut server); + server.process_new_packets().unwrap(); + check_read(&mut server.reader(), b"from-client!"); + } +} + +#[test] +fn client_closes_uncleanly() { + let kt = KeyType::Rsa; + let server_config = Arc::new(make_server_config(kt)); + + for version in rustls::ALL_VERSIONS { + let client_config = make_client_config_with_versions(kt, &[version]); + let (mut client, mut server) = + make_pair_for_arc_configs(&Arc::new(client_config), &server_config); + do_handshake(&mut client, &mut server); + + // check that unclean EOF reporting does not overtake appdata + assert_eq!( + 12, + server + .writer() + .write(b"from-server!") + .unwrap() + ); + assert_eq!( + 12, + client + .writer() + .write(b"from-client!") + .unwrap() + ); + + transfer(&mut client, &mut server); + transfer_eof(&mut server); + let io_state = server.process_new_packets().unwrap(); + assert!(!io_state.peer_has_closed()); + check_read(&mut server.reader(), b"from-client!"); + + assert!(matches!(server.reader().read(&mut [0u8; 1]), + Err(err) if err.kind() == io::ErrorKind::UnexpectedEof)); + + // may still transmit pending frames + transfer(&mut server, &mut client); + client.process_new_packets().unwrap(); + check_read(&mut client.reader(), b"from-server!"); + } +} + +#[derive(Default)] +struct ServerCheckCertResolve { + expected_sni: Option, + expected_sigalgs: Option>, + expected_alpn: Option>>, + expected_cipher_suites: Option>, +} + +impl ResolvesServerCert for ServerCheckCertResolve { + fn resolve(&self, client_hello: ClientHello) -> Option> { + if client_hello + .signature_schemes() + .is_empty() + { + panic!("no signature schemes shared by client"); + } + + if client_hello.cipher_suites().is_empty() { + panic!("no cipher suites shared by client"); + } + + if let Some(expected_sni) = &self.expected_sni { + let sni: &str = client_hello + .server_name() + .expect("sni unexpectedly absent"); + assert_eq!(expected_sni, sni); + } + + if let Some(expected_sigalgs) = &self.expected_sigalgs { + assert_eq!( + expected_sigalgs, + client_hello.signature_schemes(), + "unexpected signature schemes" + ); + } + + if let Some(expected_alpn) = &self.expected_alpn { + let alpn = client_hello + .alpn() + .expect("alpn unexpectedly absent") + .collect::>(); + assert_eq!(alpn.len(), expected_alpn.len()); + + for (got, wanted) in alpn.iter().zip(expected_alpn.iter()) { + assert_eq!(got, &wanted.as_slice()); + } + } + + if let Some(expected_cipher_suites) = &self.expected_cipher_suites { + assert_eq!( + expected_cipher_suites, + client_hello.cipher_suites(), + "unexpected cipher suites" + ); + } + + None + } +} + +#[test] +fn server_cert_resolve_with_sni() { + for kt in ALL_KEY_TYPES.iter() { + let client_config = make_client_config(*kt); + let mut server_config = make_server_config(*kt); + + server_config.cert_resolver = Arc::new(ServerCheckCertResolve { + expected_sni: Some("the-value-from-sni".into()), + ..Default::default() + }); + + let mut client = + ClientConnection::new(Arc::new(client_config), dns_name("the-value-from-sni")).unwrap(); + let mut server = ServerConnection::new(Arc::new(server_config)).unwrap(); + + let err = do_handshake_until_error(&mut client, &mut server); + assert!(err.is_err()); + } +} + +#[test] +fn server_cert_resolve_with_alpn() { + for kt in ALL_KEY_TYPES.iter() { + let mut client_config = make_client_config(*kt); + client_config.alpn_protocols = vec!["foo".into(), "bar".into()]; + + let mut server_config = make_server_config(*kt); + server_config.cert_resolver = Arc::new(ServerCheckCertResolve { + expected_alpn: Some(vec![b"foo".to_vec(), b"bar".to_vec()]), + ..Default::default() + }); + + let mut client = + ClientConnection::new(Arc::new(client_config), dns_name("sni-value")).unwrap(); + let mut server = ServerConnection::new(Arc::new(server_config)).unwrap(); + + let err = do_handshake_until_error(&mut client, &mut server); + assert!(err.is_err()); + } +} + +#[test] +fn client_trims_terminating_dot() { + for kt in ALL_KEY_TYPES.iter() { + let client_config = make_client_config(*kt); + let mut server_config = make_server_config(*kt); + + server_config.cert_resolver = Arc::new(ServerCheckCertResolve { + expected_sni: Some("some-host.com".into()), + ..Default::default() + }); + + let mut client = + ClientConnection::new(Arc::new(client_config), dns_name("some-host.com.")).unwrap(); + let mut server = ServerConnection::new(Arc::new(server_config)).unwrap(); + + let err = do_handshake_until_error(&mut client, &mut server); + assert!(err.is_err()); + } +} + +#[cfg(feature = "tls12")] +fn check_sigalgs_reduced_by_ciphersuite( + kt: KeyType, + suite: CipherSuite, + expected_sigalgs: Vec, +) { + let client_config = finish_client_config( + kt, + ClientConfig::builder() + .with_cipher_suites(&[find_suite(suite)]) + .with_safe_default_kx_groups() + .with_safe_default_protocol_versions() + .unwrap(), + ); + + let mut server_config = make_server_config(kt); + + server_config.cert_resolver = Arc::new(ServerCheckCertResolve { + expected_sigalgs: Some(expected_sigalgs), + expected_cipher_suites: Some(vec![suite, CipherSuite::TLS_EMPTY_RENEGOTIATION_INFO_SCSV]), + ..Default::default() + }); + + let mut client = ClientConnection::new(Arc::new(client_config), dns_name("localhost")).unwrap(); + let mut server = ServerConnection::new(Arc::new(server_config)).unwrap(); + + let err = do_handshake_until_error(&mut client, &mut server); + assert!(err.is_err()); +} + +#[cfg(feature = "tls12")] +#[test] +fn server_cert_resolve_reduces_sigalgs_for_rsa_ciphersuite() { + check_sigalgs_reduced_by_ciphersuite( + KeyType::Rsa, + CipherSuite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, + vec![ + SignatureScheme::RSA_PSS_SHA512, + SignatureScheme::RSA_PSS_SHA384, + SignatureScheme::RSA_PSS_SHA256, + SignatureScheme::RSA_PKCS1_SHA512, + SignatureScheme::RSA_PKCS1_SHA384, + SignatureScheme::RSA_PKCS1_SHA256, + ], + ); +} + +#[cfg(feature = "tls12")] +#[test] +fn server_cert_resolve_reduces_sigalgs_for_ecdsa_ciphersuite() { + check_sigalgs_reduced_by_ciphersuite( + KeyType::Ecdsa, + CipherSuite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, + vec![ + SignatureScheme::ECDSA_NISTP384_SHA384, + SignatureScheme::ECDSA_NISTP256_SHA256, + SignatureScheme::ED25519, + ], + ); +} + +struct ServerCheckNoSNI {} + +impl ResolvesServerCert for ServerCheckNoSNI { + fn resolve(&self, client_hello: ClientHello) -> Option> { + assert!(client_hello.server_name().is_none()); + + None + } +} + +#[test] +fn client_with_sni_disabled_does_not_send_sni() { + for kt in ALL_KEY_TYPES.iter() { + let mut server_config = make_server_config(*kt); + server_config.cert_resolver = Arc::new(ServerCheckNoSNI {}); + let server_config = Arc::new(server_config); + + for version in rustls::ALL_VERSIONS { + let mut client_config = make_client_config_with_versions(*kt, &[version]); + client_config.enable_sni = false; + + let mut client = + ClientConnection::new(Arc::new(client_config), dns_name("value-not-sent")).unwrap(); + let mut server = ServerConnection::new(Arc::clone(&server_config)).unwrap(); + + let err = do_handshake_until_error(&mut client, &mut server); + assert!(err.is_err()); + } + } +} + +#[test] +fn client_checks_server_certificate_with_given_name() { + for kt in ALL_KEY_TYPES.iter() { + let server_config = Arc::new(make_server_config(*kt)); + + for version in rustls::ALL_VERSIONS { + let client_config = make_client_config_with_versions(*kt, &[version]); + let mut client = ClientConnection::new( + Arc::new(client_config), + dns_name("not-the-right-hostname.com"), + ) + .unwrap(); + let mut server = ServerConnection::new(Arc::clone(&server_config)).unwrap(); + + let err = do_handshake_until_error(&mut client, &mut server); + assert_eq!( + err, + Err(ErrorFromPeer::Client(Error::InvalidCertificateData( + "invalid peer certificate: CertNotValidForName".into(), + ))) + ); + } + } +} + +struct ClientCheckCertResolve { + query_count: AtomicUsize, + expect_queries: usize, +} + +impl ClientCheckCertResolve { + fn new(expect_queries: usize) -> Self { + ClientCheckCertResolve { + query_count: AtomicUsize::new(0), + expect_queries, + } + } +} + +impl Drop for ClientCheckCertResolve { + fn drop(&mut self) { + if !std::thread::panicking() { + let count = self.query_count.load(Ordering::SeqCst); + assert_eq!(count, self.expect_queries); + } + } +} + +impl ResolvesClientCert for ClientCheckCertResolve { + fn resolve( + &self, + acceptable_issuers: &[&[u8]], + sigschemes: &[SignatureScheme], + ) -> Option> { + self.query_count + .fetch_add(1, Ordering::SeqCst); + + if acceptable_issuers.is_empty() { + panic!("no issuers offered by server"); + } + + if sigschemes.is_empty() { + panic!("no signature schemes shared by server"); + } + + None + } + + fn has_certs(&self) -> bool { + true + } +} + +#[test] +fn client_cert_resolve() { + for kt in ALL_KEY_TYPES.iter() { + let server_config = Arc::new(make_server_config_with_mandatory_client_auth(*kt)); + + for version in rustls::ALL_VERSIONS { + let mut client_config = make_client_config_with_versions(*kt, &[version]); + client_config.client_auth_cert_resolver = Arc::new(ClientCheckCertResolve::new(1)); + + let (mut client, mut server) = + make_pair_for_arc_configs(&Arc::new(client_config), &server_config); + + assert_eq!( + do_handshake_until_error(&mut client, &mut server), + Err(ErrorFromPeer::Server(Error::NoCertificatesPresented)) + ); + } + } +} + +#[test] +fn client_auth_works() { + for kt in ALL_KEY_TYPES.iter() { + let server_config = Arc::new(make_server_config_with_mandatory_client_auth(*kt)); + + for version in rustls::ALL_VERSIONS { + let client_config = make_client_config_with_versions_with_auth(*kt, &[version]); + let (mut client, mut server) = + make_pair_for_arc_configs(&Arc::new(client_config), &server_config); + do_handshake(&mut client, &mut server); + } + } +} + +#[test] +fn client_error_is_sticky() { + let (mut client, _) = make_pair(KeyType::Rsa); + client + .read_tls(&mut b"\x16\x03\x03\x00\x08\x0f\x00\x00\x04junk".as_ref()) + .unwrap(); + let mut err = client.process_new_packets(); + assert!(err.is_err()); + err = client.process_new_packets(); + assert!(err.is_err()); +} + +#[test] +fn server_error_is_sticky() { + let (_, mut server) = make_pair(KeyType::Rsa); + server + .read_tls(&mut b"\x16\x03\x03\x00\x08\x0f\x00\x00\x04junk".as_ref()) + .unwrap(); + let mut err = server.process_new_packets(); + assert!(err.is_err()); + err = server.process_new_packets(); + assert!(err.is_err()); +} + +#[test] +fn server_flush_does_nothing() { + let (_, mut server) = make_pair(KeyType::Rsa); + assert!(matches!(server.writer().flush(), Ok(()))); +} + +#[test] +fn client_flush_does_nothing() { + let (mut client, _) = make_pair(KeyType::Rsa); + assert!(matches!(client.writer().flush(), Ok(()))); +} + +#[test] +fn server_is_send_and_sync() { + let (_, server) = make_pair(KeyType::Rsa); + &server as &dyn Send; + &server as &dyn Sync; +} + +#[test] +fn client_is_send_and_sync() { + let (client, _) = make_pair(KeyType::Rsa); + &client as &dyn Send; + &client as &dyn Sync; +} + +#[test] +fn server_respects_buffer_limit_pre_handshake() { + let (mut client, mut server) = make_pair(KeyType::Rsa); + + server.set_buffer_limit(Some(32)); + + assert_eq!( + server + .writer() + .write(b"01234567890123456789") + .unwrap(), + 20 + ); + assert_eq!( + server + .writer() + .write(b"01234567890123456789") + .unwrap(), + 12 + ); + + do_handshake(&mut client, &mut server); + transfer(&mut server, &mut client); + client.process_new_packets().unwrap(); + + check_read(&mut client.reader(), b"01234567890123456789012345678901"); +} + +#[test] +fn server_respects_buffer_limit_pre_handshake_with_vectored_write() { + let (mut client, mut server) = make_pair(KeyType::Rsa); + + server.set_buffer_limit(Some(32)); + + assert_eq!( + server + .writer() + .write_vectored(&[ + IoSlice::new(b"01234567890123456789"), + IoSlice::new(b"01234567890123456789") + ]) + .unwrap(), + 32 + ); + + do_handshake(&mut client, &mut server); + transfer(&mut server, &mut client); + client.process_new_packets().unwrap(); + + check_read(&mut client.reader(), b"01234567890123456789012345678901"); +} + +#[test] +fn server_respects_buffer_limit_post_handshake() { + let (mut client, mut server) = make_pair(KeyType::Rsa); + + // this test will vary in behaviour depending on the default suites + do_handshake(&mut client, &mut server); + server.set_buffer_limit(Some(48)); + + assert_eq!( + server + .writer() + .write(b"01234567890123456789") + .unwrap(), + 20 + ); + assert_eq!( + server + .writer() + .write(b"01234567890123456789") + .unwrap(), + 6 + ); + + transfer(&mut server, &mut client); + client.process_new_packets().unwrap(); + + check_read(&mut client.reader(), b"01234567890123456789012345"); +} + +#[test] +fn client_respects_buffer_limit_pre_handshake() { + let (mut client, mut server) = make_pair(KeyType::Rsa); + + client.set_buffer_limit(Some(32)); + + assert_eq!( + client + .writer() + .write(b"01234567890123456789") + .unwrap(), + 20 + ); + assert_eq!( + client + .writer() + .write(b"01234567890123456789") + .unwrap(), + 12 + ); + + do_handshake(&mut client, &mut server); + transfer(&mut client, &mut server); + server.process_new_packets().unwrap(); + + check_read(&mut server.reader(), b"01234567890123456789012345678901"); +} + +#[test] +fn client_respects_buffer_limit_pre_handshake_with_vectored_write() { + let (mut client, mut server) = make_pair(KeyType::Rsa); + + client.set_buffer_limit(Some(32)); + + assert_eq!( + client + .writer() + .write_vectored(&[ + IoSlice::new(b"01234567890123456789"), + IoSlice::new(b"01234567890123456789") + ]) + .unwrap(), + 32 + ); + + do_handshake(&mut client, &mut server); + transfer(&mut client, &mut server); + server.process_new_packets().unwrap(); + + check_read(&mut server.reader(), b"01234567890123456789012345678901"); +} + +#[test] +fn client_respects_buffer_limit_post_handshake() { + let (mut client, mut server) = make_pair(KeyType::Rsa); + + do_handshake(&mut client, &mut server); + client.set_buffer_limit(Some(48)); + + assert_eq!( + client + .writer() + .write(b"01234567890123456789") + .unwrap(), + 20 + ); + assert_eq!( + client + .writer() + .write(b"01234567890123456789") + .unwrap(), + 6 + ); + + transfer(&mut client, &mut server); + server.process_new_packets().unwrap(); + + check_read(&mut server.reader(), b"01234567890123456789012345"); +} + +struct OtherSession<'a, C, S> +where + C: DerefMut + Deref>, + S: SideData, +{ + sess: &'a mut C, + pub reads: usize, + pub writevs: Vec>, + fail_ok: bool, + pub short_writes: bool, + pub last_error: Option, +} + +impl<'a, C, S> OtherSession<'a, C, S> +where + C: DerefMut + Deref>, + S: SideData, +{ + fn new(sess: &'a mut C) -> OtherSession<'a, C, S> { + OtherSession { + sess, + reads: 0, + writevs: vec![], + fail_ok: false, + short_writes: false, + last_error: None, + } + } + + fn new_fails(sess: &'a mut C) -> OtherSession<'a, C, S> { + let mut os = OtherSession::new(sess); + os.fail_ok = true; + os + } +} + +impl<'a, C, S> io::Read for OtherSession<'a, C, S> +where + C: DerefMut + Deref>, + S: SideData, +{ + fn read(&mut self, mut b: &mut [u8]) -> io::Result { + self.reads += 1; + self.sess.write_tls(b.by_ref()) + } +} + +impl<'a, C, S> io::Write for OtherSession<'a, C, S> +where + C: DerefMut + Deref>, + S: SideData, +{ + fn write(&mut self, _: &[u8]) -> io::Result { + unreachable!() + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } + + fn write_vectored<'b>(&mut self, b: &[io::IoSlice<'b>]) -> io::Result { + let mut total = 0; + let mut lengths = vec![]; + for bytes in b { + let write_len = if self.short_writes { + if bytes.len() > 5 { + bytes.len() / 2 + } else { + bytes.len() + } + } else { + bytes.len() + }; + + let l = self + .sess + .read_tls(&mut io::Cursor::new(&bytes[..write_len]))?; + lengths.push(l); + total += l; + if bytes.len() != l { + break; + } + } + + let rc = self.sess.process_new_packets(); + if !self.fail_ok { + rc.unwrap(); + } else if rc.is_err() { + self.last_error = rc.err(); + } + + self.writevs.push(lengths); + Ok(total) + } +} + +#[test] +fn server_read_returns_wouldblock_when_no_data() { + let (_, mut server) = make_pair(KeyType::Rsa); + assert!(matches!(server.reader().read(&mut [0u8; 1]), + Err(err) if err.kind() == io::ErrorKind::WouldBlock)); +} + +#[test] +fn client_read_returns_wouldblock_when_no_data() { + let (mut client, _) = make_pair(KeyType::Rsa); + assert!(matches!(client.reader().read(&mut [0u8; 1]), + Err(err) if err.kind() == io::ErrorKind::WouldBlock)); +} + +#[test] +fn new_server_returns_initial_io_state() { + let (_, mut server) = make_pair(KeyType::Rsa); + let io_state = server.process_new_packets().unwrap(); + println!("IoState is Debug {:?}", io_state); + assert_eq!(io_state.plaintext_bytes_to_read(), 0); + assert!(!io_state.peer_has_closed()); + assert_eq!(io_state.tls_bytes_to_write(), 0); +} + +#[test] +fn new_client_returns_initial_io_state() { + let (mut client, _) = make_pair(KeyType::Rsa); + let io_state = client.process_new_packets().unwrap(); + println!("IoState is Debug {:?}", io_state); + assert_eq!(io_state.plaintext_bytes_to_read(), 0); + assert!(!io_state.peer_has_closed()); + assert!(io_state.tls_bytes_to_write() > 200); +} + +#[test] +fn client_complete_io_for_handshake() { + let (mut client, mut server) = make_pair(KeyType::Rsa); + + assert!(client.is_handshaking()); + let (rdlen, wrlen) = client + .complete_io(&mut OtherSession::new(&mut server)) + .unwrap(); + assert!(rdlen > 0 && wrlen > 0); + assert!(!client.is_handshaking()); +} + +#[test] +fn client_complete_io_for_handshake_eof() { + let (mut client, _) = make_pair(KeyType::Rsa); + let mut input = io::Cursor::new(Vec::new()); + + assert!(client.is_handshaking()); + let err = client + .complete_io(&mut input) + .unwrap_err(); + assert_eq!(io::ErrorKind::UnexpectedEof, err.kind()); +} + +#[test] +fn client_complete_io_for_write() { + for kt in ALL_KEY_TYPES.iter() { + let (mut client, mut server) = make_pair(*kt); + + do_handshake(&mut client, &mut server); + + client + .writer() + .write_all(b"01234567890123456789") + .unwrap(); + client + .writer() + .write_all(b"01234567890123456789") + .unwrap(); + { + let mut pipe = OtherSession::new(&mut server); + let (rdlen, wrlen) = client.complete_io(&mut pipe).unwrap(); + assert!(rdlen == 0 && wrlen > 0); + println!("{:?}", pipe.writevs); + assert_eq!(pipe.writevs, vec![vec![42, 42]]); + } + check_read( + &mut server.reader(), + b"0123456789012345678901234567890123456789", + ); + } +} + +#[test] +fn client_complete_io_for_read() { + for kt in ALL_KEY_TYPES.iter() { + let (mut client, mut server) = make_pair(*kt); + + do_handshake(&mut client, &mut server); + + server + .writer() + .write_all(b"01234567890123456789") + .unwrap(); + { + let mut pipe = OtherSession::new(&mut server); + let (rdlen, wrlen) = client.complete_io(&mut pipe).unwrap(); + assert!(rdlen > 0 && wrlen == 0); + assert_eq!(pipe.reads, 1); + } + check_read(&mut client.reader(), b"01234567890123456789"); + } +} + +#[test] +fn server_complete_io_for_handshake() { + for kt in ALL_KEY_TYPES.iter() { + let (mut client, mut server) = make_pair(*kt); + + assert!(server.is_handshaking()); + let (rdlen, wrlen) = server + .complete_io(&mut OtherSession::new(&mut client)) + .unwrap(); + assert!(rdlen > 0 && wrlen > 0); + assert!(!server.is_handshaking()); + } +} + +#[test] +fn server_complete_io_for_handshake_eof() { + let (_, mut server) = make_pair(KeyType::Rsa); + let mut input = io::Cursor::new(Vec::new()); + + assert!(server.is_handshaking()); + let err = server + .complete_io(&mut input) + .unwrap_err(); + assert_eq!(io::ErrorKind::UnexpectedEof, err.kind()); +} + +#[test] +fn server_complete_io_for_write() { + for kt in ALL_KEY_TYPES.iter() { + let (mut client, mut server) = make_pair(*kt); + + do_handshake(&mut client, &mut server); + + server + .writer() + .write_all(b"01234567890123456789") + .unwrap(); + server + .writer() + .write_all(b"01234567890123456789") + .unwrap(); + { + let mut pipe = OtherSession::new(&mut client); + let (rdlen, wrlen) = server.complete_io(&mut pipe).unwrap(); + assert!(rdlen == 0 && wrlen > 0); + assert_eq!(pipe.writevs, vec![vec![42, 42]]); + } + check_read( + &mut client.reader(), + b"0123456789012345678901234567890123456789", + ); + } +} + +#[test] +fn server_complete_io_for_read() { + for kt in ALL_KEY_TYPES.iter() { + let (mut client, mut server) = make_pair(*kt); + + do_handshake(&mut client, &mut server); + + client + .writer() + .write_all(b"01234567890123456789") + .unwrap(); + { + let mut pipe = OtherSession::new(&mut client); + let (rdlen, wrlen) = server.complete_io(&mut pipe).unwrap(); + assert!(rdlen > 0 && wrlen == 0); + assert_eq!(pipe.reads, 1); + } + check_read(&mut server.reader(), b"01234567890123456789"); + } +} + +#[test] +fn client_stream_write() { + for kt in ALL_KEY_TYPES.iter() { + let (mut client, mut server) = make_pair(*kt); + + { + let mut pipe = OtherSession::new(&mut server); + let mut stream = Stream::new(&mut client, &mut pipe); + assert_eq!(stream.write(b"hello").unwrap(), 5); + } + check_read(&mut server.reader(), b"hello"); + } +} + +#[test] +fn client_streamowned_write() { + for kt in ALL_KEY_TYPES.iter() { + let (client, mut server) = make_pair(*kt); + + { + let pipe = OtherSession::new(&mut server); + let mut stream = StreamOwned::new(client, pipe); + assert_eq!(stream.write(b"hello").unwrap(), 5); + } + check_read(&mut server.reader(), b"hello"); + } +} + +#[test] +fn client_stream_read() { + for kt in ALL_KEY_TYPES.iter() { + let (mut client, mut server) = make_pair(*kt); + + server + .writer() + .write_all(b"world") + .unwrap(); + + { + let mut pipe = OtherSession::new(&mut server); + let mut stream = Stream::new(&mut client, &mut pipe); + check_read(&mut stream, b"world"); + } + } +} + +#[test] +fn client_streamowned_read() { + for kt in ALL_KEY_TYPES.iter() { + let (client, mut server) = make_pair(*kt); + + server + .writer() + .write_all(b"world") + .unwrap(); + + { + let pipe = OtherSession::new(&mut server); + let mut stream = StreamOwned::new(client, pipe); + check_read(&mut stream, b"world"); + } + } +} + +#[test] +fn server_stream_write() { + for kt in ALL_KEY_TYPES.iter() { + let (mut client, mut server) = make_pair(*kt); + + { + let mut pipe = OtherSession::new(&mut client); + let mut stream = Stream::new(&mut server, &mut pipe); + assert_eq!(stream.write(b"hello").unwrap(), 5); + } + check_read(&mut client.reader(), b"hello"); + } +} + +#[test] +fn server_streamowned_write() { + for kt in ALL_KEY_TYPES.iter() { + let (mut client, server) = make_pair(*kt); + + { + let pipe = OtherSession::new(&mut client); + let mut stream = StreamOwned::new(server, pipe); + assert_eq!(stream.write(b"hello").unwrap(), 5); + } + check_read(&mut client.reader(), b"hello"); + } +} + +#[test] +fn server_stream_read() { + for kt in ALL_KEY_TYPES.iter() { + let (mut client, mut server) = make_pair(*kt); + + client + .writer() + .write_all(b"world") + .unwrap(); + + { + let mut pipe = OtherSession::new(&mut client); + let mut stream = Stream::new(&mut server, &mut pipe); + check_read(&mut stream, b"world"); + } + } +} + +#[test] +fn server_streamowned_read() { + for kt in ALL_KEY_TYPES.iter() { + let (mut client, server) = make_pair(*kt); + + client + .writer() + .write_all(b"world") + .unwrap(); + + { + let pipe = OtherSession::new(&mut client); + let mut stream = StreamOwned::new(server, pipe); + check_read(&mut stream, b"world"); + } + } +} + +struct FailsWrites { + errkind: io::ErrorKind, + after: usize, +} + +impl io::Read for FailsWrites { + fn read(&mut self, _b: &mut [u8]) -> io::Result { + Ok(0) + } +} + +impl io::Write for FailsWrites { + fn write(&mut self, b: &[u8]) -> io::Result { + if self.after > 0 { + self.after -= 1; + Ok(b.len()) + } else { + Err(io::Error::new(self.errkind, "oops")) + } + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +#[test] +fn stream_write_reports_underlying_io_error_before_plaintext_processed() { + let (mut client, mut server) = make_pair(KeyType::Rsa); + do_handshake(&mut client, &mut server); + + let mut pipe = FailsWrites { + errkind: io::ErrorKind::ConnectionAborted, + after: 0, + }; + client + .writer() + .write_all(b"hello") + .unwrap(); + let mut client_stream = Stream::new(&mut client, &mut pipe); + let rc = client_stream.write(b"world"); + assert!(rc.is_err()); + let err = rc.err().unwrap(); + assert_eq!(err.kind(), io::ErrorKind::ConnectionAborted); +} + +#[test] +fn stream_write_swallows_underlying_io_error_after_plaintext_processed() { + let (mut client, mut server) = make_pair(KeyType::Rsa); + do_handshake(&mut client, &mut server); + + let mut pipe = FailsWrites { + errkind: io::ErrorKind::ConnectionAborted, + after: 1, + }; + client + .writer() + .write_all(b"hello") + .unwrap(); + let mut client_stream = Stream::new(&mut client, &mut pipe); + let rc = client_stream.write(b"world"); + assert_eq!(format!("{:?}", rc), "Ok(5)"); +} + +fn make_disjoint_suite_configs() -> (ClientConfig, ServerConfig) { + let kt = KeyType::Rsa; + let server_config = finish_server_config( + kt, + ServerConfig::builder() + .with_cipher_suites(&[rustls::cipher_suite::TLS13_CHACHA20_POLY1305_SHA256]) + .with_safe_default_kx_groups() + .with_safe_default_protocol_versions() + .unwrap(), + ); + + let client_config = finish_client_config( + kt, + ClientConfig::builder() + .with_cipher_suites(&[rustls::cipher_suite::TLS13_AES_256_GCM_SHA384]) + .with_safe_default_kx_groups() + .with_safe_default_protocol_versions() + .unwrap(), + ); + + (client_config, server_config) +} + +#[test] +fn client_stream_handshake_error() { + let (client_config, server_config) = make_disjoint_suite_configs(); + let (mut client, mut server) = make_pair_for_configs(client_config, server_config); + + { + let mut pipe = OtherSession::new_fails(&mut server); + let mut client_stream = Stream::new(&mut client, &mut pipe); + let rc = client_stream.write(b"hello"); + assert!(rc.is_err()); + assert_eq!( + format!("{:?}", rc), + "Err(Custom { kind: InvalidData, error: AlertReceived(HandshakeFailure) })" + ); + let rc = client_stream.write(b"hello"); + assert!(rc.is_err()); + assert_eq!( + format!("{:?}", rc), + "Err(Custom { kind: InvalidData, error: AlertReceived(HandshakeFailure) })" + ); + } +} + +#[test] +fn client_streamowned_handshake_error() { + let (client_config, server_config) = make_disjoint_suite_configs(); + let (client, mut server) = make_pair_for_configs(client_config, server_config); + + let pipe = OtherSession::new_fails(&mut server); + let mut client_stream = StreamOwned::new(client, pipe); + let rc = client_stream.write(b"hello"); + assert!(rc.is_err()); + assert_eq!( + format!("{:?}", rc), + "Err(Custom { kind: InvalidData, error: AlertReceived(HandshakeFailure) })" + ); + let rc = client_stream.write(b"hello"); + assert!(rc.is_err()); + assert_eq!( + format!("{:?}", rc), + "Err(Custom { kind: InvalidData, error: AlertReceived(HandshakeFailure) })" + ); +} + +#[test] +fn server_stream_handshake_error() { + let (client_config, server_config) = make_disjoint_suite_configs(); + let (mut client, mut server) = make_pair_for_configs(client_config, server_config); + + client + .writer() + .write_all(b"world") + .unwrap(); + + { + let mut pipe = OtherSession::new_fails(&mut client); + let mut server_stream = Stream::new(&mut server, &mut pipe); + let mut bytes = [0u8; 5]; + let rc = server_stream.read(&mut bytes); + assert!(rc.is_err()); + assert_eq!( + format!("{:?}", rc), + "Err(Custom { kind: InvalidData, error: PeerIncompatibleError(\"no ciphersuites in common\") })" + ); + } +} + +#[test] +fn server_streamowned_handshake_error() { + let (client_config, server_config) = make_disjoint_suite_configs(); + let (mut client, server) = make_pair_for_configs(client_config, server_config); + + client + .writer() + .write_all(b"world") + .unwrap(); + + let pipe = OtherSession::new_fails(&mut client); + let mut server_stream = StreamOwned::new(server, pipe); + let mut bytes = [0u8; 5]; + let rc = server_stream.read(&mut bytes); + assert!(rc.is_err()); + assert_eq!( + format!("{:?}", rc), + "Err(Custom { kind: InvalidData, error: PeerIncompatibleError(\"no ciphersuites in common\") })" + ); +} + +#[test] +fn server_config_is_clone() { + let _ = make_server_config(KeyType::Rsa); +} + +#[test] +fn client_config_is_clone() { + let _ = make_client_config(KeyType::Rsa); +} + +#[test] +fn client_connection_is_debug() { + let (client, _) = make_pair(KeyType::Rsa); + println!("{:?}", client); +} + +#[test] +fn server_connection_is_debug() { + let (_, server) = make_pair(KeyType::Rsa); + println!("{:?}", server); +} + +#[test] +fn server_complete_io_for_handshake_ending_with_alert() { + let (client_config, server_config) = make_disjoint_suite_configs(); + let (mut client, mut server) = make_pair_for_configs(client_config, server_config); + + assert!(server.is_handshaking()); + + let mut pipe = OtherSession::new_fails(&mut client); + let rc = server.complete_io(&mut pipe); + assert!(rc.is_err(), "server io failed due to handshake failure"); + assert!(!server.wants_write(), "but server did send its alert"); + assert_eq!( + format!("{:?}", pipe.last_error), + "Some(AlertReceived(HandshakeFailure))", + "which was received by client" + ); +} + +#[test] +fn server_exposes_offered_sni() { + let kt = KeyType::Rsa; + for version in rustls::ALL_VERSIONS { + let client_config = make_client_config_with_versions(kt, &[version]); + let mut client = + ClientConnection::new(Arc::new(client_config), dns_name("second.testserver.com")) + .unwrap(); + let mut server = ServerConnection::new(Arc::new(make_server_config(kt))).unwrap(); + + assert_eq!(None, server.sni_hostname()); + do_handshake(&mut client, &mut server); + assert_eq!(Some("second.testserver.com"), server.sni_hostname()); + } +} + +#[test] +fn server_exposes_offered_sni_smashed_to_lowercase() { + // webpki actually does this for us in its DnsName type + let kt = KeyType::Rsa; + for version in rustls::ALL_VERSIONS { + let client_config = make_client_config_with_versions(kt, &[version]); + let mut client = + ClientConnection::new(Arc::new(client_config), dns_name("SECOND.TESTServer.com")) + .unwrap(); + let mut server = ServerConnection::new(Arc::new(make_server_config(kt))).unwrap(); + + assert_eq!(None, server.sni_hostname()); + do_handshake(&mut client, &mut server); + assert_eq!(Some("second.testserver.com"), server.sni_hostname()); + } +} + +#[test] +fn server_exposes_offered_sni_even_if_resolver_fails() { + let kt = KeyType::Rsa; + let resolver = rustls::server::ResolvesServerCertUsingSni::new(); + + let mut server_config = make_server_config(kt); + server_config.cert_resolver = Arc::new(resolver); + let server_config = Arc::new(server_config); + + for version in rustls::ALL_VERSIONS { + let client_config = make_client_config_with_versions(kt, &[version]); + let mut server = ServerConnection::new(Arc::clone(&server_config)).unwrap(); + let mut client = + ClientConnection::new(Arc::new(client_config), dns_name("thisdoesNOTexist.com")) + .unwrap(); + + assert_eq!(None, server.sni_hostname()); + transfer(&mut client, &mut server); + assert_eq!( + server.process_new_packets(), + Err(Error::General( + "no server certificate chain resolved".to_string() + )) + ); + assert_eq!(Some("thisdoesnotexist.com"), server.sni_hostname()); + } +} + +#[test] +fn sni_resolver_works() { + let kt = KeyType::Rsa; + let mut resolver = rustls::server::ResolvesServerCertUsingSni::new(); + let signing_key = sign::RsaSigningKey::new(&kt.get_key()).unwrap(); + let signing_key: Arc = Arc::new(signing_key); + resolver + .add( + "localhost", + sign::CertifiedKey::new(kt.get_chain(), signing_key.clone()), + ) + .unwrap(); + + let mut server_config = make_server_config(kt); + server_config.cert_resolver = Arc::new(resolver); + let server_config = Arc::new(server_config); + + let mut server1 = ServerConnection::new(Arc::clone(&server_config)).unwrap(); + let mut client1 = + ClientConnection::new(Arc::new(make_client_config(kt)), dns_name("localhost")).unwrap(); + let err = do_handshake_until_error(&mut client1, &mut server1); + assert_eq!(err, Ok(())); + + let mut server2 = ServerConnection::new(Arc::clone(&server_config)).unwrap(); + let mut client2 = + ClientConnection::new(Arc::new(make_client_config(kt)), dns_name("notlocalhost")).unwrap(); + let err = do_handshake_until_error(&mut client2, &mut server2); + assert_eq!( + err, + Err(ErrorFromPeer::Server(Error::General( + "no server certificate chain resolved".into() + ))) + ); +} + +#[test] +fn sni_resolver_rejects_wrong_names() { + let kt = KeyType::Rsa; + let mut resolver = rustls::server::ResolvesServerCertUsingSni::new(); + let signing_key = sign::RsaSigningKey::new(&kt.get_key()).unwrap(); + let signing_key: Arc = Arc::new(signing_key); + + assert_eq!( + Ok(()), + resolver.add( + "localhost", + sign::CertifiedKey::new(kt.get_chain(), signing_key.clone()) + ) + ); + assert_eq!( + Err(Error::General( + "The server certificate is not valid for the given name".into() + )), + resolver.add( + "not-localhost", + sign::CertifiedKey::new(kt.get_chain(), signing_key.clone()) + ) + ); + assert_eq!( + Err(Error::General("Bad DNS name".into())), + resolver.add( + "not ascii 🦀", + sign::CertifiedKey::new(kt.get_chain(), signing_key.clone()) + ) + ); +} + +#[test] +fn sni_resolver_lower_cases_configured_names() { + let kt = KeyType::Rsa; + let mut resolver = rustls::server::ResolvesServerCertUsingSni::new(); + let signing_key = sign::RsaSigningKey::new(&kt.get_key()).unwrap(); + let signing_key: Arc = Arc::new(signing_key); + + assert_eq!( + Ok(()), + resolver.add( + "LOCALHOST", + sign::CertifiedKey::new(kt.get_chain(), signing_key.clone()) + ) + ); + + let mut server_config = make_server_config(kt); + server_config.cert_resolver = Arc::new(resolver); + let server_config = Arc::new(server_config); + + let mut server1 = ServerConnection::new(Arc::clone(&server_config)).unwrap(); + let mut client1 = + ClientConnection::new(Arc::new(make_client_config(kt)), dns_name("localhost")).unwrap(); + let err = do_handshake_until_error(&mut client1, &mut server1); + assert_eq!(err, Ok(())); +} + +#[test] +fn sni_resolver_lower_cases_queried_names() { + // actually, the handshake parser does this, but the effect is the same. + let kt = KeyType::Rsa; + let mut resolver = rustls::server::ResolvesServerCertUsingSni::new(); + let signing_key = sign::RsaSigningKey::new(&kt.get_key()).unwrap(); + let signing_key: Arc = Arc::new(signing_key); + + assert_eq!( + Ok(()), + resolver.add( + "localhost", + sign::CertifiedKey::new(kt.get_chain(), signing_key.clone()) + ) + ); + + let mut server_config = make_server_config(kt); + server_config.cert_resolver = Arc::new(resolver); + let server_config = Arc::new(server_config); + + let mut server1 = ServerConnection::new(Arc::clone(&server_config)).unwrap(); + let mut client1 = + ClientConnection::new(Arc::new(make_client_config(kt)), dns_name("LOCALHOST")).unwrap(); + let err = do_handshake_until_error(&mut client1, &mut server1); + assert_eq!(err, Ok(())); +} + +#[test] +fn sni_resolver_rejects_bad_certs() { + let kt = KeyType::Rsa; + let mut resolver = rustls::server::ResolvesServerCertUsingSni::new(); + let signing_key = sign::RsaSigningKey::new(&kt.get_key()).unwrap(); + let signing_key: Arc = Arc::new(signing_key); + + assert_eq!( + Err(Error::General( + "No end-entity certificate in certificate chain".into() + )), + resolver.add( + "localhost", + sign::CertifiedKey::new(vec![], signing_key.clone()) + ) + ); + + let bad_chain = vec![rustls::Certificate(vec![0xa0])]; + assert_eq!( + Err(Error::General( + "End-entity certificate in certificate chain is syntactically invalid".into() + )), + resolver.add( + "localhost", + sign::CertifiedKey::new(bad_chain, signing_key.clone()) + ) + ); +} + +fn do_exporter_test(client_config: ClientConfig, server_config: ServerConfig) { + let mut client_secret = [0u8; 64]; + let mut server_secret = [0u8; 64]; + + let (mut client, mut server) = make_pair_for_configs(client_config, server_config); + + assert_eq!( + Err(Error::HandshakeNotComplete), + client.export_keying_material(&mut client_secret, b"label", Some(b"context")) + ); + assert_eq!( + Err(Error::HandshakeNotComplete), + server.export_keying_material(&mut server_secret, b"label", Some(b"context")) + ); + do_handshake(&mut client, &mut server); + + assert_eq!( + Ok(()), + client.export_keying_material(&mut client_secret, b"label", Some(b"context")) + ); + assert_eq!( + Ok(()), + server.export_keying_material(&mut server_secret, b"label", Some(b"context")) + ); + assert_eq!(client_secret.to_vec(), server_secret.to_vec()); + + assert_eq!( + Ok(()), + client.export_keying_material(&mut client_secret, b"label", None) + ); + assert_ne!(client_secret.to_vec(), server_secret.to_vec()); + assert_eq!( + Ok(()), + server.export_keying_material(&mut server_secret, b"label", None) + ); + assert_eq!(client_secret.to_vec(), server_secret.to_vec()); +} + +#[cfg(feature = "tls12")] +#[test] +fn test_tls12_exporter() { + for kt in ALL_KEY_TYPES.iter() { + let client_config = make_client_config_with_versions(*kt, &[&rustls::version::TLS12]); + let server_config = make_server_config(*kt); + + do_exporter_test(client_config, server_config); + } +} + +#[test] +fn test_tls13_exporter() { + for kt in ALL_KEY_TYPES.iter() { + let client_config = make_client_config_with_versions(*kt, &[&rustls::version::TLS13]); + let server_config = make_server_config(*kt); + + do_exporter_test(client_config, server_config); + } +} + +fn do_suite_test( + client_config: ClientConfig, + server_config: ServerConfig, + expect_suite: SupportedCipherSuite, + expect_version: ProtocolVersion, +) { + println!( + "do_suite_test {:?} {:?}", + expect_version, + expect_suite.suite() + ); + let (mut client, mut server) = make_pair_for_configs(client_config, server_config); + + assert_eq!(None, client.negotiated_cipher_suite()); + assert_eq!(None, server.negotiated_cipher_suite()); + assert_eq!(None, client.protocol_version()); + assert_eq!(None, server.protocol_version()); + assert!(client.is_handshaking()); + assert!(server.is_handshaking()); + + transfer(&mut client, &mut server); + server.process_new_packets().unwrap(); + + assert!(client.is_handshaking()); + assert!(server.is_handshaking()); + assert_eq!(None, client.protocol_version()); + assert_eq!(Some(expect_version), server.protocol_version()); + assert_eq!(None, client.negotiated_cipher_suite()); + assert_eq!(Some(expect_suite), server.negotiated_cipher_suite()); + + transfer(&mut server, &mut client); + client.process_new_packets().unwrap(); + + assert_eq!(Some(expect_suite), client.negotiated_cipher_suite()); + assert_eq!(Some(expect_suite), server.negotiated_cipher_suite()); + + transfer(&mut client, &mut server); + server.process_new_packets().unwrap(); + transfer(&mut server, &mut client); + client.process_new_packets().unwrap(); + + assert!(!client.is_handshaking()); + assert!(!server.is_handshaking()); + assert_eq!(Some(expect_version), client.protocol_version()); + assert_eq!(Some(expect_version), server.protocol_version()); + assert_eq!(Some(expect_suite), client.negotiated_cipher_suite()); + assert_eq!(Some(expect_suite), server.negotiated_cipher_suite()); +} + +fn find_suite(suite: CipherSuite) -> SupportedCipherSuite { + for scs in ALL_CIPHER_SUITES.iter().copied() { + if scs.suite() == suite { + return scs; + } + } + + panic!("find_suite given unsupported suite"); +} + +static TEST_CIPHERSUITES: &[(&rustls::SupportedProtocolVersion, KeyType, CipherSuite)] = &[ + ( + &rustls::version::TLS13, + KeyType::Rsa, + CipherSuite::TLS13_CHACHA20_POLY1305_SHA256, + ), + ( + &rustls::version::TLS13, + KeyType::Rsa, + CipherSuite::TLS13_AES_256_GCM_SHA384, + ), + ( + &rustls::version::TLS13, + KeyType::Rsa, + CipherSuite::TLS13_AES_128_GCM_SHA256, + ), + #[cfg(feature = "tls12")] + ( + &rustls::version::TLS12, + KeyType::Ecdsa, + CipherSuite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, + ), + #[cfg(feature = "tls12")] + ( + &rustls::version::TLS12, + KeyType::Rsa, + CipherSuite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, + ), + #[cfg(feature = "tls12")] + ( + &rustls::version::TLS12, + KeyType::Ecdsa, + CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + ), + #[cfg(feature = "tls12")] + ( + &rustls::version::TLS12, + KeyType::Ecdsa, + CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + ), + #[cfg(feature = "tls12")] + ( + &rustls::version::TLS12, + KeyType::Rsa, + CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + ), + #[cfg(feature = "tls12")] + ( + &rustls::version::TLS12, + KeyType::Rsa, + CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + ), +]; + +#[test] +fn negotiated_ciphersuite_default() { + for kt in ALL_KEY_TYPES.iter() { + do_suite_test( + make_client_config(*kt), + make_server_config(*kt), + find_suite(CipherSuite::TLS13_AES_256_GCM_SHA384), + ProtocolVersion::TLSv1_3, + ); + } +} + +#[test] +fn all_suites_covered() { + assert_eq!(ALL_CIPHER_SUITES.len(), TEST_CIPHERSUITES.len()); +} + +#[test] +fn negotiated_ciphersuite_client() { + for item in TEST_CIPHERSUITES.iter() { + let (version, kt, suite) = *item; + let scs = find_suite(suite); + let client_config = finish_client_config( + kt, + ClientConfig::builder() + .with_cipher_suites(&[scs]) + .with_safe_default_kx_groups() + .with_protocol_versions(&[version]) + .unwrap(), + ); + + do_suite_test(client_config, make_server_config(kt), scs, version.version); + } +} + +#[test] +fn negotiated_ciphersuite_server() { + for item in TEST_CIPHERSUITES.iter() { + let (version, kt, suite) = *item; + let scs = find_suite(suite); + let server_config = finish_server_config( + kt, + ServerConfig::builder() + .with_cipher_suites(&[scs]) + .with_safe_default_kx_groups() + .with_protocol_versions(&[version]) + .unwrap(), + ); + + do_suite_test(make_client_config(kt), server_config, scs, version.version); + } +} + +#[derive(Debug, PartialEq)] +struct KeyLogItem { + label: String, + client_random: Vec, + secret: Vec, +} + +struct KeyLogToVec { + label: &'static str, + items: Mutex>, +} + +impl KeyLogToVec { + fn new(who: &'static str) -> Self { + KeyLogToVec { + label: who, + items: Mutex::new(vec![]), + } + } + + fn take(&self) -> Vec { + std::mem::take(&mut self.items.lock().unwrap()) + } +} + +impl KeyLog for KeyLogToVec { + fn log(&self, label: &str, client: &[u8], secret: &[u8]) { + let value = KeyLogItem { + label: label.into(), + client_random: client.into(), + secret: secret.into(), + }; + + println!("key log {:?}: {:?}", self.label, value); + + self.items.lock().unwrap().push(value); + } +} + +#[cfg(feature = "tls12")] +#[test] +fn key_log_for_tls12() { + let client_key_log = Arc::new(KeyLogToVec::new("client")); + let server_key_log = Arc::new(KeyLogToVec::new("server")); + + let kt = KeyType::Rsa; + let mut client_config = make_client_config_with_versions(kt, &[&rustls::version::TLS12]); + client_config.key_log = client_key_log.clone(); + let client_config = Arc::new(client_config); + + let mut server_config = make_server_config(kt); + server_config.key_log = server_key_log.clone(); + let server_config = Arc::new(server_config); + + // full handshake + let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config); + do_handshake(&mut client, &mut server); + + let client_full_log = client_key_log.take(); + let server_full_log = server_key_log.take(); + assert_eq!(client_full_log, server_full_log); + assert_eq!(1, client_full_log.len()); + assert_eq!("CLIENT_RANDOM", client_full_log[0].label); + + // resumed + let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config); + do_handshake(&mut client, &mut server); + + let client_resume_log = client_key_log.take(); + let server_resume_log = server_key_log.take(); + assert_eq!(client_resume_log, server_resume_log); + assert_eq!(1, client_resume_log.len()); + assert_eq!("CLIENT_RANDOM", client_resume_log[0].label); + assert_eq!(client_full_log[0].secret, client_resume_log[0].secret); +} + +#[test] +fn key_log_for_tls13() { + let client_key_log = Arc::new(KeyLogToVec::new("client")); + let server_key_log = Arc::new(KeyLogToVec::new("server")); + + let kt = KeyType::Rsa; + let mut client_config = make_client_config_with_versions(kt, &[&rustls::version::TLS13]); + client_config.key_log = client_key_log.clone(); + let client_config = Arc::new(client_config); + + let mut server_config = make_server_config(kt); + server_config.key_log = server_key_log.clone(); + let server_config = Arc::new(server_config); + + // full handshake + let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config); + do_handshake(&mut client, &mut server); + + let client_full_log = client_key_log.take(); + let server_full_log = server_key_log.take(); + + assert_eq!(5, client_full_log.len()); + assert_eq!("CLIENT_HANDSHAKE_TRAFFIC_SECRET", client_full_log[0].label); + assert_eq!("SERVER_HANDSHAKE_TRAFFIC_SECRET", client_full_log[1].label); + assert_eq!("CLIENT_TRAFFIC_SECRET_0", client_full_log[2].label); + assert_eq!("SERVER_TRAFFIC_SECRET_0", client_full_log[3].label); + assert_eq!("EXPORTER_SECRET", client_full_log[4].label); + + assert_eq!(client_full_log[0], server_full_log[0]); + assert_eq!(client_full_log[1], server_full_log[1]); + assert_eq!(client_full_log[2], server_full_log[2]); + assert_eq!(client_full_log[3], server_full_log[3]); + assert_eq!(client_full_log[4], server_full_log[4]); + + // resumed + let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config); + do_handshake(&mut client, &mut server); + + let client_resume_log = client_key_log.take(); + let server_resume_log = server_key_log.take(); + + assert_eq!(5, client_resume_log.len()); + assert_eq!( + "CLIENT_HANDSHAKE_TRAFFIC_SECRET", + client_resume_log[0].label + ); + assert_eq!( + "SERVER_HANDSHAKE_TRAFFIC_SECRET", + client_resume_log[1].label + ); + assert_eq!("CLIENT_TRAFFIC_SECRET_0", client_resume_log[2].label); + assert_eq!("SERVER_TRAFFIC_SECRET_0", client_resume_log[3].label); + assert_eq!("EXPORTER_SECRET", client_resume_log[4].label); + + assert_eq!(6, server_resume_log.len()); + assert_eq!("CLIENT_EARLY_TRAFFIC_SECRET", server_resume_log[0].label); + assert_eq!( + "CLIENT_HANDSHAKE_TRAFFIC_SECRET", + server_resume_log[1].label + ); + assert_eq!( + "SERVER_HANDSHAKE_TRAFFIC_SECRET", + server_resume_log[2].label + ); + assert_eq!("CLIENT_TRAFFIC_SECRET_0", server_resume_log[3].label); + assert_eq!("SERVER_TRAFFIC_SECRET_0", server_resume_log[4].label); + assert_eq!("EXPORTER_SECRET", server_resume_log[5].label); + + assert_eq!(client_resume_log[0], server_resume_log[1]); + assert_eq!(client_resume_log[1], server_resume_log[2]); + assert_eq!(client_resume_log[2], server_resume_log[3]); + assert_eq!(client_resume_log[3], server_resume_log[4]); + assert_eq!(client_resume_log[4], server_resume_log[5]); +} + +#[test] +fn vectored_write_for_server_appdata() { + let (mut client, mut server) = make_pair(KeyType::Rsa); + do_handshake(&mut client, &mut server); + + server + .writer() + .write_all(b"01234567890123456789") + .unwrap(); + server + .writer() + .write_all(b"01234567890123456789") + .unwrap(); + { + let mut pipe = OtherSession::new(&mut client); + let wrlen = server.write_tls(&mut pipe).unwrap(); + assert_eq!(84, wrlen); + assert_eq!(pipe.writevs, vec![vec![42, 42]]); + } + check_read( + &mut client.reader(), + b"0123456789012345678901234567890123456789", + ); +} + +#[test] +fn vectored_write_for_client_appdata() { + let (mut client, mut server) = make_pair(KeyType::Rsa); + do_handshake(&mut client, &mut server); + + client + .writer() + .write_all(b"01234567890123456789") + .unwrap(); + client + .writer() + .write_all(b"01234567890123456789") + .unwrap(); + { + let mut pipe = OtherSession::new(&mut server); + let wrlen = client.write_tls(&mut pipe).unwrap(); + assert_eq!(84, wrlen); + assert_eq!(pipe.writevs, vec![vec![42, 42]]); + } + check_read( + &mut server.reader(), + b"0123456789012345678901234567890123456789", + ); +} + +#[test] +fn vectored_write_for_server_handshake_with_half_rtt_data() { + let mut server_config = make_server_config(KeyType::Rsa); + server_config.send_half_rtt_data = true; + let (mut client, mut server) = + make_pair_for_configs(make_client_config_with_auth(KeyType::Rsa), server_config); + + server + .writer() + .write_all(b"01234567890123456789") + .unwrap(); + server + .writer() + .write_all(b"0123456789") + .unwrap(); + + transfer(&mut client, &mut server); + server.process_new_packets().unwrap(); + { + let mut pipe = OtherSession::new(&mut client); + let wrlen = server.write_tls(&mut pipe).unwrap(); + // don't assert exact sizes here, to avoid a brittle test + assert!(wrlen > 4000); // its pretty big (contains cert chain) + assert_eq!(pipe.writevs.len(), 1); // only one writev + assert_eq!(pipe.writevs[0].len(), 8); // at least a server hello/ccs/cert/serverkx/0.5rtt data + } + + client.process_new_packets().unwrap(); + transfer(&mut client, &mut server); + server.process_new_packets().unwrap(); + { + let mut pipe = OtherSession::new(&mut client); + let wrlen = server.write_tls(&mut pipe).unwrap(); + assert_eq!(wrlen, 103); + assert_eq!(pipe.writevs, vec![vec![103]]); + } + + assert!(!server.is_handshaking()); + assert!(!client.is_handshaking()); + check_read(&mut client.reader(), b"012345678901234567890123456789"); +} + +fn check_half_rtt_does_not_work(server_config: ServerConfig) { + let (mut client, mut server) = + make_pair_for_configs(make_client_config_with_auth(KeyType::Rsa), server_config); + + server + .writer() + .write_all(b"01234567890123456789") + .unwrap(); + server + .writer() + .write_all(b"0123456789") + .unwrap(); + + transfer(&mut client, &mut server); + server.process_new_packets().unwrap(); + { + let mut pipe = OtherSession::new(&mut client); + let wrlen = server.write_tls(&mut pipe).unwrap(); + // don't assert exact sizes here, to avoid a brittle test + assert!(wrlen > 4000); // its pretty big (contains cert chain) + assert_eq!(pipe.writevs.len(), 1); // only one writev + assert!(pipe.writevs[0].len() >= 6); // at least a server hello/ccs/cert/serverkx data + } + + // client second flight + client.process_new_packets().unwrap(); + transfer(&mut client, &mut server); + + // when client auth is enabled, we don't sent 0.5-rtt data, as we'd be sending + // it to an unauthenticated peer. so it happens here, in the server's second + // flight (42 and 32 are lengths of appdata sent above). + server.process_new_packets().unwrap(); + { + let mut pipe = OtherSession::new(&mut client); + let wrlen = server.write_tls(&mut pipe).unwrap(); + assert_eq!(wrlen, 177); + assert_eq!(pipe.writevs, vec![vec![103, 42, 32]]); + } + + assert!(!server.is_handshaking()); + assert!(!client.is_handshaking()); + check_read(&mut client.reader(), b"012345678901234567890123456789"); +} + +#[test] +fn vectored_write_for_server_handshake_no_half_rtt_with_client_auth() { + let mut server_config = make_server_config_with_mandatory_client_auth(KeyType::Rsa); + server_config.send_half_rtt_data = true; // ask even though it will be ignored + check_half_rtt_does_not_work(server_config); +} + +#[test] +fn vectored_write_for_server_handshake_no_half_rtt_by_default() { + let server_config = make_server_config(KeyType::Rsa); + assert!(!server_config.send_half_rtt_data); + check_half_rtt_does_not_work(server_config); +} + +#[test] +fn vectored_write_for_client_handshake() { + let (mut client, mut server) = make_pair(KeyType::Rsa); + + client + .writer() + .write_all(b"01234567890123456789") + .unwrap(); + client + .writer() + .write_all(b"0123456789") + .unwrap(); + { + let mut pipe = OtherSession::new(&mut server); + let wrlen = client.write_tls(&mut pipe).unwrap(); + // don't assert exact sizes here, to avoid a brittle test + assert!(wrlen > 200); // just the client hello + assert_eq!(pipe.writevs.len(), 1); // only one writev + assert!(pipe.writevs[0].len() == 1); // only a client hello + } + + transfer(&mut server, &mut client); + client.process_new_packets().unwrap(); + + { + let mut pipe = OtherSession::new(&mut server); + let wrlen = client.write_tls(&mut pipe).unwrap(); + assert_eq!(wrlen, 154); + // CCS, finished, then two application datas + assert_eq!(pipe.writevs, vec![vec![6, 74, 42, 32]]); + } + + assert!(!server.is_handshaking()); + assert!(!client.is_handshaking()); + check_read(&mut server.reader(), b"012345678901234567890123456789"); +} + +#[test] +fn vectored_write_with_slow_client() { + let (mut client, mut server) = make_pair(KeyType::Rsa); + + client.set_buffer_limit(Some(32)); + + do_handshake(&mut client, &mut server); + server + .writer() + .write_all(b"01234567890123456789") + .unwrap(); + + { + let mut pipe = OtherSession::new(&mut client); + pipe.short_writes = true; + let wrlen = server.write_tls(&mut pipe).unwrap() + + server.write_tls(&mut pipe).unwrap() + + server.write_tls(&mut pipe).unwrap() + + server.write_tls(&mut pipe).unwrap() + + server.write_tls(&mut pipe).unwrap() + + server.write_tls(&mut pipe).unwrap(); + assert_eq!(42, wrlen); + assert_eq!( + pipe.writevs, + vec![vec![21], vec![10], vec![5], vec![3], vec![3]] + ); + } + check_read(&mut client.reader(), b"01234567890123456789"); +} + +struct ServerStorage { + storage: Arc, + put_count: AtomicUsize, + get_count: AtomicUsize, + take_count: AtomicUsize, +} + +impl ServerStorage { + fn new() -> Self { + ServerStorage { + storage: rustls::server::ServerSessionMemoryCache::new(1024), + put_count: AtomicUsize::new(0), + get_count: AtomicUsize::new(0), + take_count: AtomicUsize::new(0), + } + } + + fn puts(&self) -> usize { + self.put_count.load(Ordering::SeqCst) + } + fn gets(&self) -> usize { + self.get_count.load(Ordering::SeqCst) + } + fn takes(&self) -> usize { + self.take_count.load(Ordering::SeqCst) + } +} + +impl fmt::Debug for ServerStorage { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "(put: {:?}, get: {:?}, take: {:?})", + self.put_count, self.get_count, self.take_count + ) + } +} + +impl rustls::server::StoresServerSessions for ServerStorage { + fn put(&self, key: Vec, value: Vec) -> bool { + self.put_count + .fetch_add(1, Ordering::SeqCst); + self.storage.put(key, value) + } + + fn get(&self, key: &[u8]) -> Option> { + self.get_count + .fetch_add(1, Ordering::SeqCst); + self.storage.get(key) + } + + fn take(&self, key: &[u8]) -> Option> { + self.take_count + .fetch_add(1, Ordering::SeqCst); + self.storage.take(key) + } + + fn can_cache(&self) -> bool { + true + } +} + +struct ClientStorage { + storage: Arc, + put_count: AtomicUsize, + get_count: AtomicUsize, + last_put_key: Mutex>>, +} + +impl ClientStorage { + fn new() -> Self { + ClientStorage { + storage: rustls::client::ClientSessionMemoryCache::new(1024), + put_count: AtomicUsize::new(0), + get_count: AtomicUsize::new(0), + last_put_key: Mutex::new(None), + } + } + + fn puts(&self) -> usize { + self.put_count.load(Ordering::SeqCst) + } + fn gets(&self) -> usize { + self.get_count.load(Ordering::SeqCst) + } +} + +impl fmt::Debug for ClientStorage { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "(puts: {:?}, gets: {:?} )", + self.put_count, self.get_count + ) + } +} + +impl rustls::client::StoresClientSessions for ClientStorage { + fn put(&self, key: Vec, value: Vec) -> bool { + self.put_count + .fetch_add(1, Ordering::SeqCst); + *self.last_put_key.lock().unwrap() = Some(key.clone()); + self.storage.put(key, value) + } + + fn get(&self, key: &[u8]) -> Option> { + self.get_count + .fetch_add(1, Ordering::SeqCst); + self.storage.get(key) + } +} + +#[test] +fn tls13_stateful_resumption() { + let kt = KeyType::Rsa; + let client_config = make_client_config_with_versions(kt, &[&rustls::version::TLS13]); + let client_config = Arc::new(client_config); + + let mut server_config = make_server_config(kt); + let storage = Arc::new(ServerStorage::new()); + server_config.session_storage = storage.clone(); + let server_config = Arc::new(server_config); + + // full handshake + let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config); + let (full_c2s, full_s2c) = do_handshake(&mut client, &mut server); + assert_eq!(storage.puts(), 1); + assert_eq!(storage.gets(), 0); + assert_eq!(storage.takes(), 0); + assert_eq!( + client + .peer_certificates() + .map(|certs| certs.len()), + Some(3) + ); + + // resumed + let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config); + let (resume_c2s, resume_s2c) = do_handshake(&mut client, &mut server); + assert!(resume_c2s > full_c2s); + assert!(resume_s2c < full_s2c); + assert_eq!(storage.puts(), 2); + assert_eq!(storage.gets(), 0); + assert_eq!(storage.takes(), 1); + assert_eq!( + client + .peer_certificates() + .map(|certs| certs.len()), + Some(3) + ); + + // resumed again + let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config); + let (resume2_c2s, resume2_s2c) = do_handshake(&mut client, &mut server); + assert_eq!(resume_s2c, resume2_s2c); + assert_eq!(resume_c2s, resume2_c2s); + assert_eq!(storage.puts(), 3); + assert_eq!(storage.gets(), 0); + assert_eq!(storage.takes(), 2); + assert_eq!( + client + .peer_certificates() + .map(|certs| certs.len()), + Some(3) + ); +} + +#[test] +fn tls13_stateless_resumption() { + let kt = KeyType::Rsa; + let client_config = make_client_config_with_versions(kt, &[&rustls::version::TLS13]); + let client_config = Arc::new(client_config); + + let mut server_config = make_server_config(kt); + server_config.ticketer = rustls::Ticketer::new().unwrap(); + let storage = Arc::new(ServerStorage::new()); + server_config.session_storage = storage.clone(); + let server_config = Arc::new(server_config); + + // full handshake + let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config); + let (full_c2s, full_s2c) = do_handshake(&mut client, &mut server); + assert_eq!(storage.puts(), 0); + assert_eq!(storage.gets(), 0); + assert_eq!(storage.takes(), 0); + assert_eq!( + client + .peer_certificates() + .map(|certs| certs.len()), + Some(3) + ); + + // resumed + let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config); + let (resume_c2s, resume_s2c) = do_handshake(&mut client, &mut server); + assert!(resume_c2s > full_c2s); + assert!(resume_s2c < full_s2c); + assert_eq!(storage.puts(), 0); + assert_eq!(storage.gets(), 0); + assert_eq!(storage.takes(), 0); + assert_eq!( + client + .peer_certificates() + .map(|certs| certs.len()), + Some(3) + ); + + // resumed again + let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config); + let (resume2_c2s, resume2_s2c) = do_handshake(&mut client, &mut server); + assert_eq!(resume_s2c, resume2_s2c); + assert_eq!(resume_c2s, resume2_c2s); + assert_eq!(storage.puts(), 0); + assert_eq!(storage.gets(), 0); + assert_eq!(storage.takes(), 0); + assert_eq!( + client + .peer_certificates() + .map(|certs| certs.len()), + Some(3) + ); +} + +#[test] +fn early_data_not_available() { + let (mut client, _) = make_pair(KeyType::Rsa); + assert!(client.early_data().is_none()); +} + +fn early_data_configs() -> (Arc, Arc) { + let kt = KeyType::Rsa; + let mut client_config = make_client_config(kt); + client_config.enable_early_data = true; + client_config.session_storage = Arc::new(ClientStorage::new()); + + let mut server_config = make_server_config(kt); + server_config.max_early_data_size = 1234; + (Arc::new(client_config), Arc::new(server_config)) +} + +#[test] +fn early_data_is_available_on_resumption() { + let (client_config, server_config) = early_data_configs(); + + let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config); + do_handshake(&mut client, &mut server); + + let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config); + assert!(client.early_data().is_some()); + assert_eq!( + client + .early_data() + .unwrap() + .bytes_left(), + 1234 + ); + client + .early_data() + .unwrap() + .flush() + .unwrap(); + assert_eq!( + client + .early_data() + .unwrap() + .write(b"hello") + .unwrap(), + 5 + ); + do_handshake(&mut client, &mut server); + + let mut received_early_data = [0u8; 5]; + assert_eq!( + server + .early_data() + .expect("early_data didn't happen") + .read(&mut received_early_data) + .expect("early_data failed unexpectedly"), + 5 + ); + assert_eq!(&received_early_data[..], b"hello"); +} + +#[test] +fn early_data_not_available_on_server_before_client_hello() { + let mut server = ServerConnection::new(Arc::new(make_server_config(KeyType::Rsa))).unwrap(); + assert!(server.early_data().is_none()); +} + +#[test] +fn early_data_can_be_rejected_by_server() { + let (client_config, server_config) = early_data_configs(); + + let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config); + do_handshake(&mut client, &mut server); + + let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config); + assert!(client.early_data().is_some()); + assert_eq!( + client + .early_data() + .unwrap() + .bytes_left(), + 1234 + ); + client + .early_data() + .unwrap() + .flush() + .unwrap(); + assert_eq!( + client + .early_data() + .unwrap() + .write(b"hello") + .unwrap(), + 5 + ); + server.reject_early_data(); + do_handshake(&mut client, &mut server); + + assert!(!client.is_early_data_accepted()); +} + +#[cfg(feature = "quic")] +mod test_quic { + use super::*; + use rustls::Connection; + + // Returns the sender's next secrets to use, or the receiver's error. + fn step( + send: &mut dyn QuicExt, + recv: &mut dyn QuicExt, + ) -> Result, Error> { + let mut buf = Vec::new(); + let change = loop { + let prev = buf.len(); + if let Some(x) = send.write_hs(&mut buf) { + break Some(x); + } + if prev == buf.len() { + break None; + } + }; + if let Err(e) = recv.read_hs(&buf) { + return Err(e); + } else { + assert_eq!(recv.alert(), None); + } + + Ok(change) + } + + #[test] + fn test_quic_handshake() { + fn equal_packet_keys(x: &quic::PacketKey, y: &quic::PacketKey) -> bool { + // Check that these two sets of keys are equal. + let mut buf = vec![0; 32]; + let (header, payload_tag) = buf.split_at_mut(8); + let (payload, tag_buf) = payload_tag.split_at_mut(8); + let tag = x + .encrypt_in_place(42, &*header, payload) + .unwrap(); + tag_buf.copy_from_slice(tag.as_ref()); + + let result = y.decrypt_in_place(42, &*header, payload_tag); + match result { + Ok(payload) => payload == &[0; 8], + Err(_) => false, + } + } + + fn compatible_keys(x: &quic::KeyChange, y: &quic::KeyChange) -> bool { + fn keys(kc: &quic::KeyChange) -> &quic::Keys { + match kc { + quic::KeyChange::Handshake { keys } => keys, + quic::KeyChange::OneRtt { keys, .. } => keys, + } + } + + let (x, y) = (keys(x), keys(y)); + equal_packet_keys(&x.local.packet, &y.remote.packet) + && equal_packet_keys(&x.remote.packet, &y.local.packet) + } + + let kt = KeyType::Rsa; + let mut client_config = make_client_config_with_versions(kt, &[&rustls::version::TLS13]); + client_config.enable_early_data = true; + let client_config = Arc::new(client_config); + let mut server_config = make_server_config_with_versions(kt, &[&rustls::version::TLS13]); + server_config.max_early_data_size = 0xffffffff; + let server_config = Arc::new(server_config); + let client_params = &b"client params"[..]; + let server_params = &b"server params"[..]; + + // full handshake + let mut client = Connection::from( + ClientConnection::new_quic( + Arc::clone(&client_config), + quic::Version::V1, + dns_name("localhost"), + client_params.into(), + ) + .unwrap(), + ); + + let mut server = Connection::from( + ServerConnection::new_quic( + Arc::clone(&server_config), + quic::Version::V1, + server_params.into(), + ) + .unwrap(), + ); + + let client_initial = step(&mut client, &mut server).unwrap(); + assert!(client_initial.is_none()); + assert!(client.zero_rtt_keys().is_none()); + assert_eq!(server.quic_transport_parameters(), Some(client_params)); + let server_hs = step(&mut server, &mut client) + .unwrap() + .unwrap(); + assert!(server.zero_rtt_keys().is_none()); + let client_hs = step(&mut client, &mut server) + .unwrap() + .unwrap(); + assert!(compatible_keys(&server_hs, &client_hs)); + assert!(client.is_handshaking()); + let server_1rtt = step(&mut server, &mut client) + .unwrap() + .unwrap(); + assert!(!client.is_handshaking()); + assert_eq!(client.quic_transport_parameters(), Some(server_params)); + assert!(server.is_handshaking()); + let client_1rtt = step(&mut client, &mut server) + .unwrap() + .unwrap(); + assert!(!server.is_handshaking()); + assert!(compatible_keys(&server_1rtt, &client_1rtt)); + assert!(!compatible_keys(&server_hs, &server_1rtt)); + assert!(step(&mut client, &mut server) + .unwrap() + .is_none()); + assert!(step(&mut server, &mut client) + .unwrap() + .is_none()); + + // 0-RTT handshake + let mut client = ClientConnection::new_quic( + Arc::clone(&client_config), + quic::Version::V1, + dns_name("localhost"), + client_params.into(), + ) + .unwrap(); + assert!(client + .negotiated_cipher_suite() + .is_some()); + + let mut server = ServerConnection::new_quic( + Arc::clone(&server_config), + quic::Version::V1, + server_params.into(), + ) + .unwrap(); + + step(&mut client, &mut server).unwrap(); + assert_eq!(client.quic_transport_parameters(), Some(server_params)); + { + let client_early = client.zero_rtt_keys().unwrap(); + let server_early = server.zero_rtt_keys().unwrap(); + assert!(equal_packet_keys( + &client_early.packet, + &server_early.packet + )); + } + step(&mut server, &mut client) + .unwrap() + .unwrap(); + step(&mut client, &mut server) + .unwrap() + .unwrap(); + step(&mut server, &mut client) + .unwrap() + .unwrap(); + assert!(client.is_early_data_accepted()); + + // 0-RTT rejection + { + let client_config = (*client_config).clone(); + let mut client = ClientConnection::new_quic( + Arc::new(client_config), + quic::Version::V1, + dns_name("localhost"), + client_params.into(), + ) + .unwrap(); + + let mut server = ServerConnection::new_quic( + Arc::clone(&server_config), + quic::Version::V1, + server_params.into(), + ) + .unwrap(); + + step(&mut client, &mut server).unwrap(); + assert_eq!(client.quic_transport_parameters(), Some(server_params)); + assert!(client.zero_rtt_keys().is_some()); + assert!(server.zero_rtt_keys().is_none()); + step(&mut server, &mut client) + .unwrap() + .unwrap(); + step(&mut client, &mut server) + .unwrap() + .unwrap(); + step(&mut server, &mut client) + .unwrap() + .unwrap(); + assert!(!client.is_early_data_accepted()); + } + + // failed handshake + let mut client = ClientConnection::new_quic( + client_config, + quic::Version::V1, + dns_name("example.com"), + client_params.into(), + ) + .unwrap(); + + let mut server = + ServerConnection::new_quic(server_config, quic::Version::V1, server_params.into()) + .unwrap(); + + step(&mut client, &mut server).unwrap(); + step(&mut server, &mut client) + .unwrap() + .unwrap(); + assert!(step(&mut server, &mut client).is_err()); + assert_eq!( + client.alert(), + Some(rustls::AlertDescription::BadCertificate) + ); + + // Key updates + + let (mut client_secrets, mut server_secrets) = match (client_1rtt, server_1rtt) { + (quic::KeyChange::OneRtt { next: c, .. }, quic::KeyChange::OneRtt { next: s, .. }) => { + (c, s) + } + _ => unreachable!(), + }; + + let mut client_next = client_secrets.next_packet_keys(); + let mut server_next = server_secrets.next_packet_keys(); + assert!(equal_packet_keys(&client_next.local, &server_next.remote)); + assert!(equal_packet_keys(&server_next.local, &client_next.remote)); + + client_next = client_secrets.next_packet_keys(); + server_next = server_secrets.next_packet_keys(); + assert!(equal_packet_keys(&client_next.local, &server_next.remote)); + assert!(equal_packet_keys(&server_next.local, &client_next.remote)); + } + + #[test] + fn test_quic_rejects_missing_alpn() { + let client_params = &b"client params"[..]; + let server_params = &b"server params"[..]; + + for &kt in ALL_KEY_TYPES.iter() { + let client_config = make_client_config_with_versions(kt, &[&rustls::version::TLS13]); + let client_config = Arc::new(client_config); + + let mut server_config = + make_server_config_with_versions(kt, &[&rustls::version::TLS13]); + server_config.alpn_protocols = vec!["foo".into()]; + let server_config = Arc::new(server_config); + + let mut client = ClientConnection::new_quic( + client_config, + quic::Version::V1, + dns_name("localhost"), + client_params.into(), + ) + .unwrap(); + let mut server = + ServerConnection::new_quic(server_config, quic::Version::V1, server_params.into()) + .unwrap(); + + assert_eq!( + step(&mut client, &mut server) + .err() + .unwrap(), + Error::NoApplicationProtocol + ); + + assert_eq!( + server.alert(), + Some(rustls::AlertDescription::NoApplicationProtocol) + ); + } + } + + #[cfg(feature = "tls12")] + #[test] + fn test_quic_no_tls13_error() { + let mut client_config = + make_client_config_with_versions(KeyType::Ed25519, &[&rustls::version::TLS12]); + client_config.alpn_protocols = vec!["foo".into()]; + let client_config = Arc::new(client_config); + + assert!(ClientConnection::new_quic( + client_config, + quic::Version::V1, + dns_name("localhost"), + b"client params".to_vec(), + ) + .is_err()); + + let mut server_config = + make_server_config_with_versions(KeyType::Ed25519, &[&rustls::version::TLS12]); + server_config.alpn_protocols = vec!["foo".into()]; + let server_config = Arc::new(server_config); + + assert!(ServerConnection::new_quic( + server_config, + quic::Version::V1, + b"server params".to_vec(), + ) + .is_err()); + } + + #[test] + fn test_quic_invalid_early_data_size() { + let mut server_config = + make_server_config_with_versions(KeyType::Ed25519, &[&rustls::version::TLS13]); + server_config.alpn_protocols = vec!["foo".into()]; + + let cases = [ + (None, true), + (Some(0u32), true), + (Some(5), false), + (Some(0xffff_ffff), true), + ]; + + for &(size, ok) in cases.iter() { + println!("early data size case: {:?}", size); + if let Some(new) = size { + server_config.max_early_data_size = new; + } + + let wrapped = Arc::new(server_config.clone()); + assert_eq!( + ServerConnection::new_quic(wrapped, quic::Version::V1, b"server params".to_vec(),) + .is_ok(), + ok + ); + } + } + + #[test] + fn test_quic_server_no_params_received() { + let server_config = + make_server_config_with_versions(KeyType::Ed25519, &[&rustls::version::TLS13]); + let server_config = Arc::new(server_config); + + let mut server = + ServerConnection::new_quic(server_config, quic::Version::V1, b"server params".to_vec()) + .unwrap(); + + use ring::rand::SecureRandom; + use rustls::internal::msgs::base::PayloadU16; + use rustls::internal::msgs::enums::{Compression, HandshakeType, NamedGroup}; + use rustls::internal::msgs::handshake::{ + ClientHelloPayload, HandshakeMessagePayload, KeyShareEntry, Random, SessionID, + }; + use rustls::internal::msgs::message::PlainMessage; + use rustls::{CipherSuite, SignatureScheme}; + + let rng = ring::rand::SystemRandom::new(); + let mut random = [0; 32]; + rng.fill(&mut random).unwrap(); + let random = Random::from(random); + + let kx = ring::agreement::EphemeralPrivateKey::generate(&ring::agreement::X25519, &rng) + .unwrap() + .compute_public_key() + .unwrap(); + + let client_hello = Message { + version: ProtocolVersion::TLSv1_3, + payload: MessagePayload::handshake(HandshakeMessagePayload { + typ: HandshakeType::ClientHello, + payload: HandshakePayload::ClientHello(ClientHelloPayload { + client_version: ProtocolVersion::TLSv1_3, + random, + session_id: SessionID::random().unwrap(), + cipher_suites: vec![CipherSuite::TLS13_AES_128_GCM_SHA256], + compression_methods: vec![Compression::Null], + extensions: vec![ + ClientExtension::SupportedVersions(vec![ProtocolVersion::TLSv1_3]), + ClientExtension::NamedGroups(vec![NamedGroup::X25519]), + ClientExtension::SignatureAlgorithms(vec![SignatureScheme::ED25519]), + ClientExtension::KeyShare(vec![KeyShareEntry { + group: NamedGroup::X25519, + payload: PayloadU16::new(kx.as_ref().to_vec()), + }]), + ], + }), + }), + }; + + let buf = PlainMessage::from(client_hello) + .into_unencrypted_opaque() + .encode(); + server + .read_tls(&mut buf.as_slice()) + .unwrap(); + assert_eq!( + server.process_new_packets().err(), + Some(Error::PeerMisbehavedError( + "QUIC transport parameters not found".into(), + )), + ); + } + + #[test] + fn test_quic_server_no_tls12() { + let mut server_config = + make_server_config_with_versions(KeyType::Ed25519, &[&rustls::version::TLS13]); + server_config.alpn_protocols = vec!["foo".into()]; + let server_config = Arc::new(server_config); + + use ring::rand::SecureRandom; + use rustls::internal::msgs::base::PayloadU16; + use rustls::internal::msgs::enums::{Compression, HandshakeType, NamedGroup}; + use rustls::internal::msgs::handshake::{ + ClientHelloPayload, HandshakeMessagePayload, KeyShareEntry, Random, SessionID, + }; + use rustls::internal::msgs::message::PlainMessage; + use rustls::{CipherSuite, SignatureScheme}; + + let rng = ring::rand::SystemRandom::new(); + let mut random = [0; 32]; + rng.fill(&mut random).unwrap(); + let random = Random::from(random); + + let kx = ring::agreement::EphemeralPrivateKey::generate(&ring::agreement::X25519, &rng) + .unwrap() + .compute_public_key() + .unwrap(); + + let mut server = + ServerConnection::new_quic(server_config, quic::Version::V1, b"server params".to_vec()) + .unwrap(); + + let client_hello = Message { + version: ProtocolVersion::TLSv1_2, + payload: MessagePayload::handshake(HandshakeMessagePayload { + typ: HandshakeType::ClientHello, + payload: HandshakePayload::ClientHello(ClientHelloPayload { + client_version: ProtocolVersion::TLSv1_2, + random: random.clone(), + session_id: SessionID::random().unwrap(), + cipher_suites: vec![CipherSuite::TLS13_AES_128_GCM_SHA256], + compression_methods: vec![Compression::Null], + extensions: vec![ + ClientExtension::NamedGroups(vec![NamedGroup::X25519]), + ClientExtension::SignatureAlgorithms(vec![SignatureScheme::ED25519]), + ClientExtension::KeyShare(vec![KeyShareEntry { + group: NamedGroup::X25519, + payload: PayloadU16::new(kx.as_ref().to_vec()), + }]), + ], + }), + }), + }; + + let buf = PlainMessage::from(client_hello) + .into_unencrypted_opaque() + .encode(); + server + .read_tls(&mut buf.as_slice()) + .unwrap(); + assert_eq!( + server.process_new_packets().err(), + Some(Error::PeerIncompatibleError( + "Server requires TLS1.3, but client omitted versions ext".into(), + )), + ); + } + + #[test] + fn packet_key_api() { + use rustls::quic::{Keys, Version}; + + // Test vectors: https://www.rfc-editor.org/rfc/rfc9001.html#name-client-initial + const CONNECTION_ID: &[u8] = &[0x83, 0x94, 0xc8, 0xf0, 0x3e, 0x51, 0x57, 0x08]; + const PACKET_NUMBER: u64 = 2; + const PLAIN_HEADER: &[u8] = &[ + 0xc3, 0x00, 0x00, 0x00, 0x01, 0x08, 0x83, 0x94, 0xc8, 0xf0, 0x3e, 0x51, 0x57, 0x08, + 0x00, 0x00, 0x44, 0x9e, 0x00, 0x00, 0x00, 0x02, + ]; + + const PAYLOAD: &[u8] = &[ + 0x06, 0x00, 0x40, 0xf1, 0x01, 0x00, 0x00, 0xed, 0x03, 0x03, 0xeb, 0xf8, 0xfa, 0x56, + 0xf1, 0x29, 0x39, 0xb9, 0x58, 0x4a, 0x38, 0x96, 0x47, 0x2e, 0xc4, 0x0b, 0xb8, 0x63, + 0xcf, 0xd3, 0xe8, 0x68, 0x04, 0xfe, 0x3a, 0x47, 0xf0, 0x6a, 0x2b, 0x69, 0x48, 0x4c, + 0x00, 0x00, 0x04, 0x13, 0x01, 0x13, 0x02, 0x01, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x0e, 0x00, 0x00, 0x0b, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, + 0x63, 0x6f, 0x6d, 0xff, 0x01, 0x00, 0x01, 0x00, 0x00, 0x0a, 0x00, 0x08, 0x00, 0x06, + 0x00, 0x1d, 0x00, 0x17, 0x00, 0x18, 0x00, 0x10, 0x00, 0x07, 0x00, 0x05, 0x04, 0x61, + 0x6c, 0x70, 0x6e, 0x00, 0x05, 0x00, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x33, + 0x00, 0x26, 0x00, 0x24, 0x00, 0x1d, 0x00, 0x20, 0x93, 0x70, 0xb2, 0xc9, 0xca, 0xa4, + 0x7f, 0xba, 0xba, 0xf4, 0x55, 0x9f, 0xed, 0xba, 0x75, 0x3d, 0xe1, 0x71, 0xfa, 0x71, + 0xf5, 0x0f, 0x1c, 0xe1, 0x5d, 0x43, 0xe9, 0x94, 0xec, 0x74, 0xd7, 0x48, 0x00, 0x2b, + 0x00, 0x03, 0x02, 0x03, 0x04, 0x00, 0x0d, 0x00, 0x10, 0x00, 0x0e, 0x04, 0x03, 0x05, + 0x03, 0x06, 0x03, 0x02, 0x03, 0x08, 0x04, 0x08, 0x05, 0x08, 0x06, 0x00, 0x2d, 0x00, + 0x02, 0x01, 0x01, 0x00, 0x1c, 0x00, 0x02, 0x40, 0x01, 0x00, 0x39, 0x00, 0x32, 0x04, + 0x08, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x05, 0x04, 0x80, 0x00, 0xff, + 0xff, 0x07, 0x04, 0x80, 0x00, 0xff, 0xff, 0x08, 0x01, 0x10, 0x01, 0x04, 0x80, 0x00, + 0x75, 0x30, 0x09, 0x01, 0x10, 0x0f, 0x08, 0x83, 0x94, 0xc8, 0xf0, 0x3e, 0x51, 0x57, + 0x08, 0x06, 0x04, 0x80, 0x00, 0xff, 0xff, + ]; + + let client_keys = Keys::initial(Version::V1, &CONNECTION_ID, true); + assert_eq!( + client_keys + .local + .packet + .confidentiality_limit(), + 2u64.pow(23) + ); + assert_eq!( + client_keys + .local + .packet + .integrity_limit(), + 2u64.pow(52) + ); + assert_eq!(client_keys.local.packet.tag_len(), 16); + + let mut buf = Vec::new(); + buf.extend(PLAIN_HEADER); + buf.extend(PAYLOAD); + let header_len = PLAIN_HEADER.len(); + let tag_len = client_keys.local.packet.tag_len(); + let padding_len = 1200 - header_len - PAYLOAD.len() - tag_len; + buf.extend(std::iter::repeat(0).take(padding_len)); + let (header, payload) = buf.split_at_mut(header_len); + let tag = client_keys + .local + .packet + .encrypt_in_place(PACKET_NUMBER, &*header, payload) + .unwrap(); + + let sample_len = client_keys.local.header.sample_len(); + let sample = &payload[..sample_len]; + let (first, rest) = header.split_at_mut(1); + client_keys + .local + .header + .encrypt_in_place(sample, &mut first[0], &mut rest[17..21]) + .unwrap(); + buf.extend_from_slice(tag.as_ref()); + + const PROTECTED: &[u8] = &[ + 0xc0, 0x00, 0x00, 0x00, 0x01, 0x08, 0x83, 0x94, 0xc8, 0xf0, 0x3e, 0x51, 0x57, 0x08, + 0x00, 0x00, 0x44, 0x9e, 0x7b, 0x9a, 0xec, 0x34, 0xd1, 0xb1, 0xc9, 0x8d, 0xd7, 0x68, + 0x9f, 0xb8, 0xec, 0x11, 0xd2, 0x42, 0xb1, 0x23, 0xdc, 0x9b, 0xd8, 0xba, 0xb9, 0x36, + 0xb4, 0x7d, 0x92, 0xec, 0x35, 0x6c, 0x0b, 0xab, 0x7d, 0xf5, 0x97, 0x6d, 0x27, 0xcd, + 0x44, 0x9f, 0x63, 0x30, 0x00, 0x99, 0xf3, 0x99, 0x1c, 0x26, 0x0e, 0xc4, 0xc6, 0x0d, + 0x17, 0xb3, 0x1f, 0x84, 0x29, 0x15, 0x7b, 0xb3, 0x5a, 0x12, 0x82, 0xa6, 0x43, 0xa8, + 0xd2, 0x26, 0x2c, 0xad, 0x67, 0x50, 0x0c, 0xad, 0xb8, 0xe7, 0x37, 0x8c, 0x8e, 0xb7, + 0x53, 0x9e, 0xc4, 0xd4, 0x90, 0x5f, 0xed, 0x1b, 0xee, 0x1f, 0xc8, 0xaa, 0xfb, 0xa1, + 0x7c, 0x75, 0x0e, 0x2c, 0x7a, 0xce, 0x01, 0xe6, 0x00, 0x5f, 0x80, 0xfc, 0xb7, 0xdf, + 0x62, 0x12, 0x30, 0xc8, 0x37, 0x11, 0xb3, 0x93, 0x43, 0xfa, 0x02, 0x8c, 0xea, 0x7f, + 0x7f, 0xb5, 0xff, 0x89, 0xea, 0xc2, 0x30, 0x82, 0x49, 0xa0, 0x22, 0x52, 0x15, 0x5e, + 0x23, 0x47, 0xb6, 0x3d, 0x58, 0xc5, 0x45, 0x7a, 0xfd, 0x84, 0xd0, 0x5d, 0xff, 0xfd, + 0xb2, 0x03, 0x92, 0x84, 0x4a, 0xe8, 0x12, 0x15, 0x46, 0x82, 0xe9, 0xcf, 0x01, 0x2f, + 0x90, 0x21, 0xa6, 0xf0, 0xbe, 0x17, 0xdd, 0xd0, 0xc2, 0x08, 0x4d, 0xce, 0x25, 0xff, + 0x9b, 0x06, 0xcd, 0xe5, 0x35, 0xd0, 0xf9, 0x20, 0xa2, 0xdb, 0x1b, 0xf3, 0x62, 0xc2, + 0x3e, 0x59, 0x6d, 0x11, 0xa4, 0xf5, 0xa6, 0xcf, 0x39, 0x48, 0x83, 0x8a, 0x3a, 0xec, + 0x4e, 0x15, 0xda, 0xf8, 0x50, 0x0a, 0x6e, 0xf6, 0x9e, 0xc4, 0xe3, 0xfe, 0xb6, 0xb1, + 0xd9, 0x8e, 0x61, 0x0a, 0xc8, 0xb7, 0xec, 0x3f, 0xaf, 0x6a, 0xd7, 0x60, 0xb7, 0xba, + 0xd1, 0xdb, 0x4b, 0xa3, 0x48, 0x5e, 0x8a, 0x94, 0xdc, 0x25, 0x0a, 0xe3, 0xfd, 0xb4, + 0x1e, 0xd1, 0x5f, 0xb6, 0xa8, 0xe5, 0xeb, 0xa0, 0xfc, 0x3d, 0xd6, 0x0b, 0xc8, 0xe3, + 0x0c, 0x5c, 0x42, 0x87, 0xe5, 0x38, 0x05, 0xdb, 0x05, 0x9a, 0xe0, 0x64, 0x8d, 0xb2, + 0xf6, 0x42, 0x64, 0xed, 0x5e, 0x39, 0xbe, 0x2e, 0x20, 0xd8, 0x2d, 0xf5, 0x66, 0xda, + 0x8d, 0xd5, 0x99, 0x8c, 0xca, 0xbd, 0xae, 0x05, 0x30, 0x60, 0xae, 0x6c, 0x7b, 0x43, + 0x78, 0xe8, 0x46, 0xd2, 0x9f, 0x37, 0xed, 0x7b, 0x4e, 0xa9, 0xec, 0x5d, 0x82, 0xe7, + 0x96, 0x1b, 0x7f, 0x25, 0xa9, 0x32, 0x38, 0x51, 0xf6, 0x81, 0xd5, 0x82, 0x36, 0x3a, + 0xa5, 0xf8, 0x99, 0x37, 0xf5, 0xa6, 0x72, 0x58, 0xbf, 0x63, 0xad, 0x6f, 0x1a, 0x0b, + 0x1d, 0x96, 0xdb, 0xd4, 0xfa, 0xdd, 0xfc, 0xef, 0xc5, 0x26, 0x6b, 0xa6, 0x61, 0x17, + 0x22, 0x39, 0x5c, 0x90, 0x65, 0x56, 0xbe, 0x52, 0xaf, 0xe3, 0xf5, 0x65, 0x63, 0x6a, + 0xd1, 0xb1, 0x7d, 0x50, 0x8b, 0x73, 0xd8, 0x74, 0x3e, 0xeb, 0x52, 0x4b, 0xe2, 0x2b, + 0x3d, 0xcb, 0xc2, 0xc7, 0x46, 0x8d, 0x54, 0x11, 0x9c, 0x74, 0x68, 0x44, 0x9a, 0x13, + 0xd8, 0xe3, 0xb9, 0x58, 0x11, 0xa1, 0x98, 0xf3, 0x49, 0x1d, 0xe3, 0xe7, 0xfe, 0x94, + 0x2b, 0x33, 0x04, 0x07, 0xab, 0xf8, 0x2a, 0x4e, 0xd7, 0xc1, 0xb3, 0x11, 0x66, 0x3a, + 0xc6, 0x98, 0x90, 0xf4, 0x15, 0x70, 0x15, 0x85, 0x3d, 0x91, 0xe9, 0x23, 0x03, 0x7c, + 0x22, 0x7a, 0x33, 0xcd, 0xd5, 0xec, 0x28, 0x1c, 0xa3, 0xf7, 0x9c, 0x44, 0x54, 0x6b, + 0x9d, 0x90, 0xca, 0x00, 0xf0, 0x64, 0xc9, 0x9e, 0x3d, 0xd9, 0x79, 0x11, 0xd3, 0x9f, + 0xe9, 0xc5, 0xd0, 0xb2, 0x3a, 0x22, 0x9a, 0x23, 0x4c, 0xb3, 0x61, 0x86, 0xc4, 0x81, + 0x9e, 0x8b, 0x9c, 0x59, 0x27, 0x72, 0x66, 0x32, 0x29, 0x1d, 0x6a, 0x41, 0x82, 0x11, + 0xcc, 0x29, 0x62, 0xe2, 0x0f, 0xe4, 0x7f, 0xeb, 0x3e, 0xdf, 0x33, 0x0f, 0x2c, 0x60, + 0x3a, 0x9d, 0x48, 0xc0, 0xfc, 0xb5, 0x69, 0x9d, 0xbf, 0xe5, 0x89, 0x64, 0x25, 0xc5, + 0xba, 0xc4, 0xae, 0xe8, 0x2e, 0x57, 0xa8, 0x5a, 0xaf, 0x4e, 0x25, 0x13, 0xe4, 0xf0, + 0x57, 0x96, 0xb0, 0x7b, 0xa2, 0xee, 0x47, 0xd8, 0x05, 0x06, 0xf8, 0xd2, 0xc2, 0x5e, + 0x50, 0xfd, 0x14, 0xde, 0x71, 0xe6, 0xc4, 0x18, 0x55, 0x93, 0x02, 0xf9, 0x39, 0xb0, + 0xe1, 0xab, 0xd5, 0x76, 0xf2, 0x79, 0xc4, 0xb2, 0xe0, 0xfe, 0xb8, 0x5c, 0x1f, 0x28, + 0xff, 0x18, 0xf5, 0x88, 0x91, 0xff, 0xef, 0x13, 0x2e, 0xef, 0x2f, 0xa0, 0x93, 0x46, + 0xae, 0xe3, 0x3c, 0x28, 0xeb, 0x13, 0x0f, 0xf2, 0x8f, 0x5b, 0x76, 0x69, 0x53, 0x33, + 0x41, 0x13, 0x21, 0x19, 0x96, 0xd2, 0x00, 0x11, 0xa1, 0x98, 0xe3, 0xfc, 0x43, 0x3f, + 0x9f, 0x25, 0x41, 0x01, 0x0a, 0xe1, 0x7c, 0x1b, 0xf2, 0x02, 0x58, 0x0f, 0x60, 0x47, + 0x47, 0x2f, 0xb3, 0x68, 0x57, 0xfe, 0x84, 0x3b, 0x19, 0xf5, 0x98, 0x40, 0x09, 0xdd, + 0xc3, 0x24, 0x04, 0x4e, 0x84, 0x7a, 0x4f, 0x4a, 0x0a, 0xb3, 0x4f, 0x71, 0x95, 0x95, + 0xde, 0x37, 0x25, 0x2d, 0x62, 0x35, 0x36, 0x5e, 0x9b, 0x84, 0x39, 0x2b, 0x06, 0x10, + 0x85, 0x34, 0x9d, 0x73, 0x20, 0x3a, 0x4a, 0x13, 0xe9, 0x6f, 0x54, 0x32, 0xec, 0x0f, + 0xd4, 0xa1, 0xee, 0x65, 0xac, 0xcd, 0xd5, 0xe3, 0x90, 0x4d, 0xf5, 0x4c, 0x1d, 0xa5, + 0x10, 0xb0, 0xff, 0x20, 0xdc, 0xc0, 0xc7, 0x7f, 0xcb, 0x2c, 0x0e, 0x0e, 0xb6, 0x05, + 0xcb, 0x05, 0x04, 0xdb, 0x87, 0x63, 0x2c, 0xf3, 0xd8, 0xb4, 0xda, 0xe6, 0xe7, 0x05, + 0x76, 0x9d, 0x1d, 0xe3, 0x54, 0x27, 0x01, 0x23, 0xcb, 0x11, 0x45, 0x0e, 0xfc, 0x60, + 0xac, 0x47, 0x68, 0x3d, 0x7b, 0x8d, 0x0f, 0x81, 0x13, 0x65, 0x56, 0x5f, 0xd9, 0x8c, + 0x4c, 0x8e, 0xb9, 0x36, 0xbc, 0xab, 0x8d, 0x06, 0x9f, 0xc3, 0x3b, 0xd8, 0x01, 0xb0, + 0x3a, 0xde, 0xa2, 0xe1, 0xfb, 0xc5, 0xaa, 0x46, 0x3d, 0x08, 0xca, 0x19, 0x89, 0x6d, + 0x2b, 0xf5, 0x9a, 0x07, 0x1b, 0x85, 0x1e, 0x6c, 0x23, 0x90, 0x52, 0x17, 0x2f, 0x29, + 0x6b, 0xfb, 0x5e, 0x72, 0x40, 0x47, 0x90, 0xa2, 0x18, 0x10, 0x14, 0xf3, 0xb9, 0x4a, + 0x4e, 0x97, 0xd1, 0x17, 0xb4, 0x38, 0x13, 0x03, 0x68, 0xcc, 0x39, 0xdb, 0xb2, 0xd1, + 0x98, 0x06, 0x5a, 0xe3, 0x98, 0x65, 0x47, 0x92, 0x6c, 0xd2, 0x16, 0x2f, 0x40, 0xa2, + 0x9f, 0x0c, 0x3c, 0x87, 0x45, 0xc0, 0xf5, 0x0f, 0xba, 0x38, 0x52, 0xe5, 0x66, 0xd4, + 0x45, 0x75, 0xc2, 0x9d, 0x39, 0xa0, 0x3f, 0x0c, 0xda, 0x72, 0x19, 0x84, 0xb6, 0xf4, + 0x40, 0x59, 0x1f, 0x35, 0x5e, 0x12, 0xd4, 0x39, 0xff, 0x15, 0x0a, 0xab, 0x76, 0x13, + 0x49, 0x9d, 0xbd, 0x49, 0xad, 0xab, 0xc8, 0x67, 0x6e, 0xef, 0x02, 0x3b, 0x15, 0xb6, + 0x5b, 0xfc, 0x5c, 0xa0, 0x69, 0x48, 0x10, 0x9f, 0x23, 0xf3, 0x50, 0xdb, 0x82, 0x12, + 0x35, 0x35, 0xeb, 0x8a, 0x74, 0x33, 0xbd, 0xab, 0xcb, 0x90, 0x92, 0x71, 0xa6, 0xec, + 0xbc, 0xb5, 0x8b, 0x93, 0x6a, 0x88, 0xcd, 0x4e, 0x8f, 0x2e, 0x6f, 0xf5, 0x80, 0x01, + 0x75, 0xf1, 0x13, 0x25, 0x3d, 0x8f, 0xa9, 0xca, 0x88, 0x85, 0xc2, 0xf5, 0x52, 0xe6, + 0x57, 0xdc, 0x60, 0x3f, 0x25, 0x2e, 0x1a, 0x8e, 0x30, 0x8f, 0x76, 0xf0, 0xbe, 0x79, + 0xe2, 0xfb, 0x8f, 0x5d, 0x5f, 0xbb, 0xe2, 0xe3, 0x0e, 0xca, 0xdd, 0x22, 0x07, 0x23, + 0xc8, 0xc0, 0xae, 0xa8, 0x07, 0x8c, 0xdf, 0xcb, 0x38, 0x68, 0x26, 0x3f, 0xf8, 0xf0, + 0x94, 0x00, 0x54, 0xda, 0x48, 0x78, 0x18, 0x93, 0xa7, 0xe4, 0x9a, 0xd5, 0xaf, 0xf4, + 0xaf, 0x30, 0x0c, 0xd8, 0x04, 0xa6, 0xb6, 0x27, 0x9a, 0xb3, 0xff, 0x3a, 0xfb, 0x64, + 0x49, 0x1c, 0x85, 0x19, 0x4a, 0xab, 0x76, 0x0d, 0x58, 0xa6, 0x06, 0x65, 0x4f, 0x9f, + 0x44, 0x00, 0xe8, 0xb3, 0x85, 0x91, 0x35, 0x6f, 0xbf, 0x64, 0x25, 0xac, 0xa2, 0x6d, + 0xc8, 0x52, 0x44, 0x25, 0x9f, 0xf2, 0xb1, 0x9c, 0x41, 0xb9, 0xf9, 0x6f, 0x3c, 0xa9, + 0xec, 0x1d, 0xde, 0x43, 0x4d, 0xa7, 0xd2, 0xd3, 0x92, 0xb9, 0x05, 0xdd, 0xf3, 0xd1, + 0xf9, 0xaf, 0x93, 0xd1, 0xaf, 0x59, 0x50, 0xbd, 0x49, 0x3f, 0x5a, 0xa7, 0x31, 0xb4, + 0x05, 0x6d, 0xf3, 0x1b, 0xd2, 0x67, 0xb6, 0xb9, 0x0a, 0x07, 0x98, 0x31, 0xaa, 0xf5, + 0x79, 0xbe, 0x0a, 0x39, 0x01, 0x31, 0x37, 0xaa, 0xc6, 0xd4, 0x04, 0xf5, 0x18, 0xcf, + 0xd4, 0x68, 0x40, 0x64, 0x7e, 0x78, 0xbf, 0xe7, 0x06, 0xca, 0x4c, 0xf5, 0xe9, 0xc5, + 0x45, 0x3e, 0x9f, 0x7c, 0xfd, 0x2b, 0x8b, 0x4c, 0x8d, 0x16, 0x9a, 0x44, 0xe5, 0x5c, + 0x88, 0xd4, 0xa9, 0xa7, 0xf9, 0x47, 0x42, 0x41, 0xe2, 0x21, 0xaf, 0x44, 0x86, 0x00, + 0x18, 0xab, 0x08, 0x56, 0x97, 0x2e, 0x19, 0x4c, 0xd9, 0x34, + ]; + + assert_eq!(&buf, PROTECTED); + + let (header, payload) = buf.split_at_mut(header_len); + let (first, rest) = header.split_at_mut(1); + let sample = &payload[..sample_len]; + + let server_keys = Keys::initial(Version::V1, &CONNECTION_ID, false); + server_keys + .remote + .header + .decrypt_in_place(sample, &mut first[0], &mut rest[17..21]) + .unwrap(); + let payload = server_keys + .remote + .packet + .decrypt_in_place(PACKET_NUMBER, &*header, payload) + .unwrap(); + + assert_eq!(&payload[..PAYLOAD.len()], PAYLOAD); + assert_eq!(payload.len(), buf.len() - header_len - tag_len); + } + + #[test] + fn test_quic_exporter() { + for &kt in ALL_KEY_TYPES.iter() { + let client_config = make_client_config_with_versions(kt, &[&rustls::version::TLS13]); + let server_config = make_server_config_with_versions(kt, &[&rustls::version::TLS13]); + + do_exporter_test(client_config, server_config); + } + } +} // mod test_quic + +#[test] +fn test_client_does_not_offer_sha1() { + use rustls::internal::msgs::{ + codec::Reader, enums::HandshakeType, handshake::HandshakePayload, message::MessagePayload, + message::OpaqueMessage, + }; + + for kt in ALL_KEY_TYPES.iter() { + for version in rustls::ALL_VERSIONS { + let client_config = make_client_config_with_versions(*kt, &[version]); + let (mut client, _) = make_pair_for_configs(client_config, make_server_config(*kt)); + + assert!(client.wants_write()); + let mut buf = [0u8; 262144]; + let sz = client + .write_tls(&mut buf.as_mut()) + .unwrap(); + let msg = OpaqueMessage::read(&mut Reader::init(&buf[..sz])).unwrap(); + let msg = Message::try_from(msg.into_plain_message()).unwrap(); + assert!(msg.is_handshake_type(HandshakeType::ClientHello)); + + let client_hello = match msg.payload { + MessagePayload::Handshake { parsed, .. } => match parsed.payload { + HandshakePayload::ClientHello(ch) => ch, + _ => unreachable!(), + }, + _ => unreachable!(), + }; + + let sigalgs = client_hello + .get_sigalgs_extension() + .unwrap(); + assert!( + !sigalgs.contains(&SignatureScheme::RSA_PKCS1_SHA1), + "sha1 unexpectedly offered" + ); + } + } +} + +#[test] +fn test_client_config_keyshare() { + let client_config = + make_client_config_with_kx_groups(KeyType::Rsa, &[&rustls::kx_group::SECP384R1]); + let server_config = + make_server_config_with_kx_groups(KeyType::Rsa, &[&rustls::kx_group::SECP384R1]); + let (mut client, mut server) = make_pair_for_configs(client_config, server_config); + do_handshake_until_error(&mut client, &mut server).unwrap(); +} + +#[test] +fn test_client_config_keyshare_mismatch() { + let client_config = + make_client_config_with_kx_groups(KeyType::Rsa, &[&rustls::kx_group::SECP384R1]); + let server_config = + make_server_config_with_kx_groups(KeyType::Rsa, &[&rustls::kx_group::X25519]); + let (mut client, mut server) = make_pair_for_configs(client_config, server_config); + assert!(do_handshake_until_error(&mut client, &mut server).is_err()); +} + +#[test] +fn test_client_sends_helloretryrequest() { + // client sends a secp384r1 key share + let mut client_config = make_client_config_with_kx_groups( + KeyType::Rsa, + &[&rustls::kx_group::SECP384R1, &rustls::kx_group::X25519], + ); + + let storage = Arc::new(ClientStorage::new()); + client_config.session_storage = storage.clone(); + + // but server only accepts x25519, so a HRR is required + let server_config = + make_server_config_with_kx_groups(KeyType::Rsa, &[&rustls::kx_group::X25519]); + + let (mut client, mut server) = make_pair_for_configs(client_config, server_config); + + // client sends hello + { + let mut pipe = OtherSession::new(&mut server); + let wrlen = client.write_tls(&mut pipe).unwrap(); + assert!(wrlen > 200); + assert_eq!(pipe.writevs.len(), 1); + assert!(pipe.writevs[0].len() == 1); + } + + // server sends HRR + { + let mut pipe = OtherSession::new(&mut client); + let wrlen = server.write_tls(&mut pipe).unwrap(); + assert!(wrlen < 100); // just the hello retry request + assert_eq!(pipe.writevs.len(), 1); // only one writev + assert!(pipe.writevs[0].len() == 2); // hello retry request and CCS + } + + // client sends fixed hello + { + let mut pipe = OtherSession::new(&mut server); + let wrlen = client.write_tls(&mut pipe).unwrap(); + assert!(wrlen > 200); // just the client hello retry + assert_eq!(pipe.writevs.len(), 1); // only one writev + assert!(pipe.writevs[0].len() == 2); // only a CCS & client hello retry + } + + // server completes handshake + { + let mut pipe = OtherSession::new(&mut client); + let wrlen = server.write_tls(&mut pipe).unwrap(); + assert!(wrlen > 200); + assert_eq!(pipe.writevs.len(), 1); + assert!(pipe.writevs[0].len() == 5); // server hello / encrypted exts / cert / cert-verify / finished + } + + do_handshake_until_error(&mut client, &mut server).unwrap(); + + // client only did two storage queries: one for a session, another for a kx type + assert_eq!(storage.gets(), 2); + assert_eq!(storage.puts(), 2); +} + +#[test] +fn test_client_attempts_to_use_unsupported_kx_group() { + // common to both client configs + let shared_storage = Arc::new(ClientStorage::new()); + + // first, client sends a x25519 and server agrees. x25519 is inserted + // into kx group cache. + let mut client_config_1 = + make_client_config_with_kx_groups(KeyType::Rsa, &[&rustls::kx_group::X25519]); + client_config_1.session_storage = shared_storage.clone(); + + // second, client only supports secp-384 and so kx group cache + // contains an unusable value. + let mut client_config_2 = + make_client_config_with_kx_groups(KeyType::Rsa, &[&rustls::kx_group::SECP384R1]); + client_config_2.session_storage = shared_storage; + + let server_config = make_server_config(KeyType::Rsa); + + // first handshake + let (mut client_1, mut server) = make_pair_for_configs(client_config_1, server_config.clone()); + do_handshake_until_error(&mut client_1, &mut server).unwrap(); + + // second handshake + let (mut client_2, mut server) = make_pair_for_configs(client_config_2, server_config); + do_handshake_until_error(&mut client_2, &mut server).unwrap(); +} + +#[test] +fn test_client_mtu_reduction() { + struct CollectWrites { + writevs: Vec>, + } + + impl io::Write for CollectWrites { + fn write(&mut self, _: &[u8]) -> io::Result { + panic!() + } + fn flush(&mut self) -> io::Result<()> { + panic!() + } + fn write_vectored<'b>(&mut self, b: &[io::IoSlice<'b>]) -> io::Result { + let writes = b + .iter() + .map(|slice| slice.len()) + .collect::>(); + let len = writes.iter().sum(); + self.writevs.push(writes); + Ok(len) + } + } + + fn collect_write_lengths(client: &mut ClientConnection) -> Vec { + let mut collector = CollectWrites { writevs: vec![] }; + + client + .write_tls(&mut collector) + .unwrap(); + assert_eq!(collector.writevs.len(), 1); + collector.writevs[0].clone() + } + + for kt in ALL_KEY_TYPES.iter() { + let mut client_config = make_client_config(*kt); + client_config.max_fragment_size = Some(64); + let mut client = + ClientConnection::new(Arc::new(client_config), dns_name("localhost")).unwrap(); + let writes = collect_write_lengths(&mut client); + println!("writes at mtu=64: {:?}", writes); + assert!(writes.iter().all(|x| *x <= 64)); + assert!(writes.len() > 1); + } +} + +#[test] +fn test_server_mtu_reduction() { + let mut server_config = make_server_config(KeyType::Rsa); + server_config.max_fragment_size = Some(64); + server_config.send_half_rtt_data = true; + let (mut client, mut server) = + make_pair_for_configs(make_client_config(KeyType::Rsa), server_config); + + let big_data = [0u8; 2048]; + server + .writer() + .write_all(&big_data) + .unwrap(); + + let encryption_overhead = 20; // FIXME: see issue #991 + + transfer(&mut client, &mut server); + server.process_new_packets().unwrap(); + { + let mut pipe = OtherSession::new(&mut client); + server.write_tls(&mut pipe).unwrap(); + + assert_eq!(pipe.writevs.len(), 1); + assert!(pipe.writevs[0] + .iter() + .all(|x| *x <= 64 + encryption_overhead)); + } + + client.process_new_packets().unwrap(); + transfer(&mut client, &mut server); + server.process_new_packets().unwrap(); + { + let mut pipe = OtherSession::new(&mut client); + server.write_tls(&mut pipe).unwrap(); + assert_eq!(pipe.writevs.len(), 1); + assert!(pipe.writevs[0] + .iter() + .all(|x| *x <= 64 + encryption_overhead)); + } + + client.process_new_packets().unwrap(); + check_read(&mut client.reader(), &big_data); +} + +fn check_client_max_fragment_size(size: usize) -> Option { + let mut client_config = make_client_config(KeyType::Ed25519); + client_config.max_fragment_size = Some(size); + ClientConnection::new(Arc::new(client_config), dns_name("localhost")).err() +} + +#[test] +fn bad_client_max_fragment_sizes() { + assert_eq!( + check_client_max_fragment_size(31), + Some(Error::BadMaxFragmentSize) + ); + assert_eq!(check_client_max_fragment_size(32), None); + assert_eq!(check_client_max_fragment_size(64), None); + assert_eq!(check_client_max_fragment_size(1460), None); + assert_eq!(check_client_max_fragment_size(0x4000), None); + assert_eq!(check_client_max_fragment_size(0x4005), None); + assert_eq!( + check_client_max_fragment_size(0x4006), + Some(Error::BadMaxFragmentSize) + ); + assert_eq!( + check_client_max_fragment_size(0xffff), + Some(Error::BadMaxFragmentSize) + ); +} + +fn assert_lt(left: usize, right: usize) { + if left >= right { + panic!("expected {} < {}", left, right); + } +} + +#[test] +fn connection_types_are_not_huge() { + // Arbitrary sizes + assert_lt(mem::size_of::(), 1600); + assert_lt(mem::size_of::(), 1600); +} + +use rustls::internal::msgs::{ + handshake::ClientExtension, handshake::HandshakePayload, message::Message, + message::MessagePayload, +}; + +#[test] +fn test_server_rejects_duplicate_sni_names() { + fn duplicate_sni_payload(msg: &mut Message) -> Altered { + if let MessagePayload::Handshake { parsed, encoded } = &mut msg.payload { + if let HandshakePayload::ClientHello(ch) = &mut parsed.payload { + for mut ext in ch.extensions.iter_mut() { + if let ClientExtension::ServerName(snr) = &mut ext { + snr.push(snr[0].clone()); + } + } + } + + *encoded = Payload::new(parsed.get_encoding()); + } + Altered::InPlace + } + + let (client, server) = make_pair(KeyType::Rsa); + let (mut client, mut server) = (client.into(), server.into()); + transfer_altered(&mut client, duplicate_sni_payload, &mut server); + assert_eq!( + server.process_new_packets(), + Err(Error::PeerMisbehavedError( + "ClientHello SNI contains duplicate name types".into() + )) + ); +} + +#[test] +fn test_server_rejects_empty_sni_extension() { + fn empty_sni_payload(msg: &mut Message) -> Altered { + if let MessagePayload::Handshake { parsed, encoded } = &mut msg.payload { + if let HandshakePayload::ClientHello(ch) = &mut parsed.payload { + for mut ext in ch.extensions.iter_mut() { + if let ClientExtension::ServerName(snr) = &mut ext { + snr.clear(); + } + } + } + + *encoded = Payload::new(parsed.get_encoding()); + } + + Altered::InPlace + } + + let (client, server) = make_pair(KeyType::Rsa); + let (mut client, mut server) = (client.into(), server.into()); + transfer_altered(&mut client, empty_sni_payload, &mut server); + assert_eq!( + server.process_new_packets(), + Err(Error::PeerMisbehavedError( + "ClientHello SNI did not contain a hostname".into() + )) + ); +} + +#[test] +fn test_server_rejects_clients_without_any_kx_group_overlap() { + fn different_kx_group(msg: &mut Message) -> Altered { + if let MessagePayload::Handshake { parsed, encoded } = &mut msg.payload { + if let HandshakePayload::ClientHello(ch) = &mut parsed.payload { + for mut ext in ch.extensions.iter_mut() { + if let ClientExtension::NamedGroups(ngs) = &mut ext { + ngs.clear(); + } + if let ClientExtension::KeyShare(ks) = &mut ext { + ks.clear(); + } + } + } + + *encoded = Payload::new(parsed.get_encoding()); + } + Altered::InPlace + } + + let (client, server) = make_pair(KeyType::Rsa); + let (mut client, mut server) = (client.into(), server.into()); + transfer_altered(&mut client, different_kx_group, &mut server); + assert_eq!( + server.process_new_packets(), + Err(Error::PeerIncompatibleError( + "no kx group overlap with client".into() + )) + ); +} + +#[test] +fn test_client_rejects_illegal_tls13_ccs() { + fn corrupt_ccs(msg: &mut Message) -> Altered { + if let MessagePayload::ChangeCipherSpec(_) = &mut msg.payload { + println!("seen CCS {:?}", msg); + return Altered::Raw(vec![0x14, 0x03, 0x03, 0x00, 0x02, 0x01, 0x02]); + } + Altered::InPlace + } + + let (mut client, mut server) = make_pair(KeyType::Rsa); + transfer(&mut client, &mut server); + server.process_new_packets().unwrap(); + + let (mut server, mut client) = (server.into(), client.into()); + + transfer_altered(&mut server, corrupt_ccs, &mut client); + assert_eq!( + client.process_new_packets(), + Err(Error::PeerMisbehavedError( + "illegal middlebox CCS received".into() + )) + ); +} + +/// https://github.com/rustls/rustls/issues/797 +#[cfg(feature = "tls12")] +#[test] +fn test_client_tls12_no_resume_after_server_downgrade() { + let mut client_config = common::make_client_config(KeyType::Ed25519); + let client_storage = Arc::new(ClientStorage::new()); + client_config.session_storage = client_storage.clone(); + let client_config = Arc::new(client_config); + + let server_config_1 = Arc::new(common::finish_server_config( + KeyType::Ed25519, + ServerConfig::builder() + .with_safe_default_cipher_suites() + .with_safe_default_kx_groups() + .with_protocol_versions(&[&rustls::version::TLS13]) + .unwrap(), + )); + + let mut server_config_2 = common::finish_server_config( + KeyType::Ed25519, + ServerConfig::builder() + .with_safe_default_cipher_suites() + .with_safe_default_kx_groups() + .with_protocol_versions(&[&rustls::version::TLS12]) + .unwrap(), + ); + server_config_2.session_storage = Arc::new(rustls::server::NoServerSessionStorage {}); + + dbg!("handshake 1"); + let mut client_1 = + ClientConnection::new(client_config.clone(), "localhost".try_into().unwrap()).unwrap(); + let mut server_1 = ServerConnection::new(server_config_1).unwrap(); + common::do_handshake(&mut client_1, &mut server_1); + assert_eq!(client_storage.puts(), 2); + + dbg!("handshake 2"); + let mut client_2 = + ClientConnection::new(client_config, "localhost".try_into().unwrap()).unwrap(); + let mut server_2 = ServerConnection::new(Arc::new(server_config_2)).unwrap(); + common::do_handshake(&mut client_2, &mut server_2); + assert_eq!(client_storage.puts(), 2); +} + +#[test] +fn test_acceptor() { + use rustls::server::Acceptor; + + let client_config = Arc::new(make_client_config(KeyType::Ed25519)); + let mut client = ClientConnection::new(client_config, dns_name("localhost")).unwrap(); + let mut buf = Vec::new(); + client.write_tls(&mut buf).unwrap(); + + let server_config = Arc::new(make_server_config(KeyType::Ed25519)); + let mut acceptor = Acceptor::default(); + acceptor + .read_tls(&mut buf.as_slice()) + .unwrap(); + let accepted = acceptor.accept().unwrap().unwrap(); + let ch = accepted.client_hello(); + assert_eq!(ch.server_name(), Some("localhost")); + + let server = accepted + .into_connection(server_config) + .unwrap(); + assert!(server.wants_write()); + + // Reusing an acceptor is not allowed + assert_eq!( + acceptor + .read_tls(&mut [0u8].as_ref()) + .err() + .unwrap() + .kind(), + io::ErrorKind::Other, + ); + assert_eq!( + acceptor.accept().err(), + Some(Error::General("Acceptor polled after completion".into())) + ); + + let mut acceptor = Acceptor::default(); + assert!(acceptor.accept().unwrap().is_none()); + acceptor + .read_tls(&mut &buf[..3]) + .unwrap(); // incomplete message + assert!(acceptor.accept().unwrap().is_none()); + acceptor + .read_tls(&mut [0x80, 0x00].as_ref()) + .unwrap(); // invalid message (len = 32k bytes) + assert!(acceptor.accept().is_err()); + + let mut acceptor = Acceptor::default(); + // Minimal valid 1-byte application data message is not a handshake message + acceptor + .read_tls(&mut [0x17, 0x03, 0x03, 0x00, 0x01, 0x00].as_ref()) + .unwrap(); + assert!(acceptor.accept().is_err()); + + let mut acceptor = Acceptor::default(); + // Minimal 1-byte ClientHello message is not a legal handshake message + acceptor + .read_tls(&mut [0x16, 0x03, 0x03, 0x00, 0x05, 0x01, 0x00, 0x00, 0x01, 0x00].as_ref()) + .unwrap(); + assert!(acceptor.accept().is_err()); +} + +#[derive(Default, Debug)] +struct LogCounts { + trace: usize, + debug: usize, + info: usize, + warn: usize, + error: usize, +} + +impl LogCounts { + fn new() -> Self { + Self { + ..Default::default() + } + } + + fn reset(&mut self) { + *self = Self::new(); + } + + fn add(&mut self, level: log::Level) { + match level { + log::Level::Trace => self.trace += 1, + log::Level::Debug => self.debug += 1, + log::Level::Info => self.info += 1, + log::Level::Warn => self.warn += 1, + log::Level::Error => self.error += 1, + } + } +} + +thread_local!(static COUNTS: RefCell = RefCell::new(LogCounts::new())); + +struct CountingLogger; + +static LOGGER: CountingLogger = CountingLogger; + +impl CountingLogger { + fn install() { + log::set_logger(&LOGGER).unwrap(); + log::set_max_level(log::LevelFilter::Trace); + } + + fn reset() { + COUNTS.with(|c| { + c.borrow_mut().reset(); + }); + } +} + +impl log::Log for CountingLogger { + fn enabled(&self, _metadata: &log::Metadata) -> bool { + true + } + + fn log(&self, record: &log::Record) { + println!("logging at {:?}: {:?}", record.level(), record.args()); + + COUNTS.with(|c| { + c.borrow_mut().add(record.level()); + }); + } + + fn flush(&self) {} +} + +#[test] +fn test_no_warning_logging_during_successful_sessions() { + CountingLogger::install(); + CountingLogger::reset(); + + for kt in ALL_KEY_TYPES.iter() { + for version in rustls::ALL_VERSIONS { + let client_config = make_client_config_with_versions(*kt, &[version]); + let (mut client, mut server) = + make_pair_for_configs(client_config, make_server_config(*kt)); + do_handshake(&mut client, &mut server); + } + } + + if cfg!(feature = "logging") { + COUNTS.with(|c| { + println!("After tests: {:?}", c.borrow()); + assert_eq!(c.borrow().warn, 0); + assert_eq!(c.borrow().error, 0); + assert_eq!(c.borrow().info, 0); + assert!(c.borrow().trace > 0); + assert!(c.borrow().debug > 0); + }); + } else { + COUNTS.with(|c| { + println!("After tests: {:?}", c.borrow()); + assert_eq!(c.borrow().warn, 0); + assert_eq!(c.borrow().error, 0); + assert_eq!(c.borrow().info, 0); + assert_eq!(c.borrow().trace, 0); + assert_eq!(c.borrow().debug, 0); + }); + } +} + +/// Test that secrets can be extracted and used for encryption/decryption. +#[cfg(feature = "secret_extraction")] +#[test] +fn test_secret_extraction_enabled() { + // Normally, secret extraction would be used to configure kTLS (TLS offload + // to the kernel). We want this test to run on any platform, though, so + // instead we just compare secrets for equality. + + // TLS 1.2 and 1.3 have different mechanisms for key exchange and handshake, + // and secrets are stored/extracted differently, so we want to test them both. + // We support 3 different AEAD algorithms (AES-128-GCM mode, AES-256-GCM, and + // Chacha20Poly1305), so that's 2*3 = 6 combinations to test. + let kt = KeyType::Rsa; + for suite in [ + rustls::cipher_suite::TLS13_AES_128_GCM_SHA256, + rustls::cipher_suite::TLS13_AES_256_GCM_SHA384, + rustls::cipher_suite::TLS13_CHACHA20_POLY1305_SHA256, + rustls::cipher_suite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + rustls::cipher_suite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + rustls::cipher_suite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, + ] { + let version = suite.version(); + println!("Testing suite {:?}", suite.suite().as_str()); + + // Only offer the cipher suite (and protocol version) that we're testing + let mut server_config = ServerConfig::builder() + .with_cipher_suites(&[suite]) + .with_safe_default_kx_groups() + .with_protocol_versions(&[version]) + .unwrap() + .with_no_client_auth() + .with_single_cert(kt.get_chain(), kt.get_key()) + .unwrap(); + // Opt into secret extraction from both sides + server_config.enable_secret_extraction = true; + let server_config = Arc::new(server_config); + + let mut client_config = make_client_config(kt); + client_config.enable_secret_extraction = true; + + let (mut client, mut server) = + make_pair_for_arc_configs(&Arc::new(client_config), &server_config); + + do_handshake(&mut client, &mut server); + + // The handshake is finished, we're now able to extract traffic secrets + let client_secrets = client.extract_secrets().unwrap(); + let server_secrets = server.extract_secrets().unwrap(); + + // Comparing secrets for equality is something you should never have to + // do in production code, so ConnectionTrafficSecrets doesn't implement + // PartialEq/Eq on purpose. Instead, we have to get creative. + fn explode_secrets(s: &ConnectionTrafficSecrets) -> (&[u8], &[u8], &[u8]) { + match s { + ConnectionTrafficSecrets::Aes128Gcm { key, salt, iv } => (key, salt, iv), + ConnectionTrafficSecrets::Aes256Gcm { key, salt, iv } => (key, salt, iv), + ConnectionTrafficSecrets::Chacha20Poly1305 { key, iv } => (key, &[], iv), + _ => panic!("unexpected secret type"), + } + } + + fn assert_secrets_equal( + (l_seq, l_sec): (u64, ConnectionTrafficSecrets), + (r_seq, r_sec): (u64, ConnectionTrafficSecrets), + ) { + assert_eq!(l_seq, r_seq); + assert_eq!(explode_secrets(&l_sec), explode_secrets(&r_sec)); + } + + assert_secrets_equal(client_secrets.tx, server_secrets.rx); + assert_secrets_equal(client_secrets.rx, server_secrets.tx); + } +} + +/// Test that secrets cannot be extracted unless explicitly enabled, and until +/// the handshake is done. +#[cfg(feature = "secret_extraction")] +#[test] +fn test_secret_extraction_disabled_or_too_early() { + let suite = rustls::cipher_suite::TLS13_AES_128_GCM_SHA256; + let kt = KeyType::Rsa; + + for (server_enable, client_enable) in [(true, false), (false, true)] { + let mut server_config = ServerConfig::builder() + .with_cipher_suites(&[suite]) + .with_safe_default_kx_groups() + .with_safe_default_protocol_versions() + .unwrap() + .with_no_client_auth() + .with_single_cert(kt.get_chain(), kt.get_key()) + .unwrap(); + server_config.enable_secret_extraction = server_enable; + let server_config = Arc::new(server_config); + + let mut client_config = make_client_config(kt); + client_config.enable_secret_extraction = client_enable; + + let client_config = Arc::new(client_config); + + let (client, server) = make_pair_for_arc_configs(&client_config, &server_config); + + assert!( + client.extract_secrets().is_err(), + "extraction should fail until handshake completes" + ); + assert!( + server.extract_secrets().is_err(), + "extraction should fail until handshake completes" + ); + + let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config); + + do_handshake(&mut client, &mut server); + + assert_eq!(server_enable, server.extract_secrets().is_ok()); + assert_eq!(client_enable, client.extract_secrets().is_ok()); + } +} + +#[test] +fn test_received_plaintext_backpressure() { + let suite = rustls::cipher_suite::TLS13_AES_128_GCM_SHA256; + let kt = KeyType::Rsa; + + let server_config = Arc::new( + ServerConfig::builder() + .with_cipher_suites(&[suite]) + .with_safe_default_kx_groups() + .with_safe_default_protocol_versions() + .unwrap() + .with_no_client_auth() + .with_single_cert(kt.get_chain(), kt.get_key()) + .unwrap(), + ); + + let client_config = Arc::new(make_client_config(kt)); + let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config); + do_handshake(&mut client, &mut server); + + // Fill the server's received plaintext buffer with 16k bytes + let client_buf = [0; 16_385]; + dbg!(client + .writer() + .write(&client_buf) + .unwrap()); + let mut network_buf = Vec::with_capacity(32_768); + let sent = dbg!(client + .write_tls(&mut network_buf) + .unwrap()); + assert_eq!( + sent, + dbg!(server + .read_tls(&mut &network_buf[..sent]) + .unwrap()) + ); + server.process_new_packets().unwrap(); + + // Send two more bytes from client to server + dbg!(client + .writer() + .write(&client_buf[..2]) + .unwrap()); + let sent = dbg!(client + .write_tls(&mut network_buf) + .unwrap()); + + // Get an error because the received plaintext buffer is full + assert!(server + .read_tls(&mut &network_buf[..sent]) + .is_err()); + + // Read out some of the plaintext + server + .reader() + .read_exact(&mut [0; 2]) + .unwrap(); + + // Now there's room again in the plaintext buffer + assert_eq!( + server + .read_tls(&mut &network_buf[..sent]) + .unwrap(), + 24 + ); +} diff --git a/third_party/rustls-fork-shadow-tls/tests/bogo.rs b/third_party/rustls-fork-shadow-tls/tests/bogo.rs new file mode 100644 index 0000000..e96073d --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/tests/bogo.rs @@ -0,0 +1,18 @@ +// Runs the bogo test suite, in the form of a rust test. +// Note that bogo requires a golang environment to build +// and run. + +#[test] +#[cfg(all(coverage, feature = "quic", feature = "dangerous_configuration"))] +fn run_bogo_tests() { + use std::process::Command; + + let rc = Command::new("./runme") + .current_dir("../bogo") + .spawn() + .expect("cannot run bogo/runme") + .wait() + .expect("cannot wait for bogo"); + + assert!(rc.success(), "bogo exited non-zero"); +} diff --git a/third_party/rustls-fork-shadow-tls/tests/client_cert_verifier.rs b/third_party/rustls-fork-shadow-tls/tests/client_cert_verifier.rs new file mode 100644 index 0000000..5af9c2a --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/tests/client_cert_verifier.rs @@ -0,0 +1,324 @@ +//! Tests for configuring and using a [`ClientCertVerifier`] for a server. + +#![cfg(feature = "dangerous_configuration")] + +mod common; + +use crate::common::{ + dns_name, do_handshake_until_both_error, do_handshake_until_error, get_client_root_store, + make_client_config_with_versions, make_client_config_with_versions_with_auth, + make_pair_for_arc_configs, ErrorFromPeer, KeyType, ALL_KEY_TYPES, +}; +use rustls::client::WebPkiVerifier; +use rustls::internal::msgs::base::PayloadU16; +use rustls::server::{ClientCertVerified, ClientCertVerifier}; +use rustls::AlertDescription; +use rustls::ContentType; +use rustls::{ + Certificate, ClientConnection, DistinguishedNames, Error, ServerConfig, ServerConnection, + SignatureScheme, +}; +use std::sync::Arc; + +// Client is authorized! +fn ver_ok() -> Result { + Ok(rustls::server::ClientCertVerified::assertion()) +} + +// Use when we shouldn't even attempt verification +fn ver_unreachable() -> Result { + unreachable!() +} + +// Verifier that returns an error that we can expect +fn ver_err() -> Result { + Err(Error::General("test err".to_string())) +} + +fn server_config_with_verifier( + kt: KeyType, + client_cert_verifier: MockClientVerifier, +) -> ServerConfig { + ServerConfig::builder() + .with_safe_defaults() + .with_client_cert_verifier(Arc::new(client_cert_verifier)) + .with_single_cert(kt.get_chain(), kt.get_key()) + .unwrap() +} + +#[test] +// Happy path, we resolve to a root, it is verified OK, should be able to connect +fn client_verifier_works() { + for kt in ALL_KEY_TYPES.iter() { + let client_verifier = MockClientVerifier { + verified: ver_ok, + subjects: Some( + get_client_root_store(*kt) + .roots + .iter() + .map(|r| PayloadU16(r.subject().to_vec())) + .collect(), + ), + mandatory: Some(true), + offered_schemes: None, + }; + + let server_config = server_config_with_verifier(*kt, client_verifier); + let server_config = Arc::new(server_config); + + for version in rustls::ALL_VERSIONS { + let client_config = make_client_config_with_versions_with_auth(*kt, &[version]); + let (mut client, mut server) = + make_pair_for_arc_configs(&Arc::new(client_config.clone()), &server_config); + let err = do_handshake_until_error(&mut client, &mut server); + assert_eq!(err, Ok(())); + } + } +} + +// Server offers no verification schemes +#[test] +fn client_verifier_no_schemes() { + for kt in ALL_KEY_TYPES.iter() { + let client_verifier = MockClientVerifier { + verified: ver_ok, + subjects: Some( + get_client_root_store(*kt) + .roots + .iter() + .map(|r| PayloadU16(r.subject().to_vec())) + .collect(), + ), + mandatory: Some(true), + offered_schemes: Some(vec![]), + }; + + let server_config = server_config_with_verifier(*kt, client_verifier); + let server_config = Arc::new(server_config); + + for version in rustls::ALL_VERSIONS { + let client_config = make_client_config_with_versions_with_auth(*kt, &[version]); + let (mut client, mut server) = + make_pair_for_arc_configs(&Arc::new(client_config.clone()), &server_config); + let err = do_handshake_until_error(&mut client, &mut server); + assert_eq!( + err, + Err(ErrorFromPeer::Client(Error::CorruptMessagePayload( + ContentType::Handshake + ))) + ); + } + } +} + +// Common case, we do not find a root store to resolve to +#[test] +fn client_verifier_no_root() { + for kt in ALL_KEY_TYPES.iter() { + let client_verifier = MockClientVerifier { + verified: ver_ok, + subjects: None, + mandatory: Some(true), + offered_schemes: None, + }; + + let server_config = server_config_with_verifier(*kt, client_verifier); + let server_config = Arc::new(server_config); + + for version in rustls::ALL_VERSIONS { + let client_config = make_client_config_with_versions_with_auth(*kt, &[version]); + let mut server = ServerConnection::new(Arc::clone(&server_config)).unwrap(); + let mut client = + ClientConnection::new(Arc::new(client_config), dns_name("notlocalhost")).unwrap(); + let errs = do_handshake_until_both_error(&mut client, &mut server); + assert_eq!( + errs, + Err(vec![ + ErrorFromPeer::Server(Error::General( + "client rejected by client_auth_root_subjects".into() + )), + ErrorFromPeer::Client(Error::AlertReceived(AlertDescription::AccessDenied)) + ]) + ); + } + } +} + +// If we cannot resolve a root, we cannot decide if auth is mandatory +#[test] +fn client_verifier_no_auth_no_root() { + for kt in ALL_KEY_TYPES.iter() { + let client_verifier = MockClientVerifier { + verified: ver_unreachable, + subjects: None, + mandatory: Some(true), + offered_schemes: None, + }; + + let server_config = server_config_with_verifier(*kt, client_verifier); + let server_config = Arc::new(server_config); + + for version in rustls::ALL_VERSIONS { + let client_config = make_client_config_with_versions(*kt, &[version]); + let mut server = ServerConnection::new(Arc::clone(&server_config)).unwrap(); + let mut client = + ClientConnection::new(Arc::new(client_config), dns_name("notlocalhost")).unwrap(); + let errs = do_handshake_until_both_error(&mut client, &mut server); + assert_eq!( + errs, + Err(vec![ + ErrorFromPeer::Server(Error::General( + "client rejected by client_auth_root_subjects".into() + )), + ErrorFromPeer::Client(Error::AlertReceived(AlertDescription::AccessDenied)) + ]) + ); + } + } +} + +// If we do have a root, we must do auth +#[test] +fn client_verifier_no_auth_yes_root() { + for kt in ALL_KEY_TYPES.iter() { + let client_verifier = MockClientVerifier { + verified: ver_unreachable, + subjects: Some( + get_client_root_store(*kt) + .roots + .iter() + .map(|r| PayloadU16(r.subject().to_vec())) + .collect(), + ), + mandatory: Some(true), + offered_schemes: None, + }; + + let server_config = server_config_with_verifier(*kt, client_verifier); + let server_config = Arc::new(server_config); + + for version in rustls::ALL_VERSIONS { + let client_config = make_client_config_with_versions(*kt, &[version]); + let mut server = ServerConnection::new(Arc::clone(&server_config)).unwrap(); + let mut client = + ClientConnection::new(Arc::new(client_config), dns_name("localhost")).unwrap(); + let errs = do_handshake_until_both_error(&mut client, &mut server); + assert_eq!( + errs, + Err(vec![ + ErrorFromPeer::Server(Error::NoCertificatesPresented), + ErrorFromPeer::Client(Error::AlertReceived( + AlertDescription::CertificateRequired + )) + ]) + ); + } + } +} + +#[test] +// Triple checks we propagate the rustls::Error through +fn client_verifier_fails_properly() { + for kt in ALL_KEY_TYPES.iter() { + let client_verifier = MockClientVerifier { + verified: ver_err, + subjects: Some( + get_client_root_store(*kt) + .roots + .iter() + .map(|r| PayloadU16(r.subject().to_vec())) + .collect(), + ), + mandatory: Some(true), + offered_schemes: None, + }; + + let server_config = server_config_with_verifier(*kt, client_verifier); + let server_config = Arc::new(server_config); + + for version in rustls::ALL_VERSIONS { + let client_config = make_client_config_with_versions_with_auth(*kt, &[version]); + let mut server = ServerConnection::new(Arc::clone(&server_config)).unwrap(); + let mut client = + ClientConnection::new(Arc::new(client_config), dns_name("localhost")).unwrap(); + let err = do_handshake_until_error(&mut client, &mut server); + assert_eq!( + err, + Err(ErrorFromPeer::Server(Error::General("test err".into()))) + ); + } + } +} + +#[test] +// If a verifier returns a None on Mandatory-ness, then we error out +fn client_verifier_must_determine_client_auth_requirement_to_continue() { + for kt in ALL_KEY_TYPES.iter() { + let client_verifier = MockClientVerifier { + verified: ver_ok, + subjects: Some( + get_client_root_store(*kt) + .roots + .iter() + .map(|r| PayloadU16(r.subject().to_vec())) + .collect(), + ), + mandatory: None, + offered_schemes: None, + }; + + let server_config = server_config_with_verifier(*kt, client_verifier); + let server_config = Arc::new(server_config); + + for version in rustls::ALL_VERSIONS { + let client_config = make_client_config_with_versions_with_auth(*kt, &[version]); + let mut server = ServerConnection::new(Arc::clone(&server_config)).unwrap(); + let mut client = + ClientConnection::new(Arc::new(client_config), dns_name("localhost")).unwrap(); + let errs = do_handshake_until_both_error(&mut client, &mut server); + assert_eq!( + errs, + Err(vec![ + ErrorFromPeer::Server(Error::General( + "client rejected by client_auth_mandatory".into() + )), + ErrorFromPeer::Client(Error::AlertReceived(AlertDescription::AccessDenied)) + ]) + ); + } + } +} + +pub struct MockClientVerifier { + pub verified: fn() -> Result, + pub subjects: Option, + pub mandatory: Option, + pub offered_schemes: Option>, +} + +impl ClientCertVerifier for MockClientVerifier { + fn client_auth_mandatory(&self) -> Option { + self.mandatory + } + + fn client_auth_root_subjects(&self) -> Option { + self.subjects.as_ref().cloned() + } + + fn verify_client_cert( + &self, + _end_entity: &Certificate, + _intermediates: &[Certificate], + _now: std::time::SystemTime, + ) -> Result { + (self.verified)() + } + + fn supported_verify_schemes(&self) -> Vec { + if let Some(schemes) = &self.offered_schemes { + schemes.clone() + } else { + WebPkiVerifier::verification_schemes() + } + } +} diff --git a/third_party/rustls-fork-shadow-tls/tests/common/mod.rs b/third_party/rustls-fork-shadow-tls/tests/common/mod.rs new file mode 100644 index 0000000..d176ed8 --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/tests/common/mod.rs @@ -0,0 +1,474 @@ +#![allow(dead_code)] + +use std::convert::{TryFrom, TryInto}; +use std::io; +use std::ops::{Deref, DerefMut}; +use std::sync::Arc; + +use rustls::internal::msgs::codec::Reader; +use rustls::internal::msgs::message::{Message, OpaqueMessage, PlainMessage}; +use rustls::server::AllowAnyAuthenticatedClient; +use rustls::Connection; +use rustls::Error; +use rustls::RootCertStore; +use rustls::{Certificate, PrivateKey}; +use rustls::{ClientConfig, ClientConnection}; +use rustls::{ConnectionCommon, ServerConfig, ServerConnection, SideData}; + +macro_rules! embed_files { + ( + $( + ($name:ident, $keytype:expr, $path:expr); + )+ + ) => { + $( + const $name: &'static [u8] = include_bytes!( + concat!("../../../test-ca/", $keytype, "/", $path)); + )+ + + pub fn bytes_for(keytype: &str, path: &str) -> &'static [u8] { + match (keytype, path) { + $( + ($keytype, $path) => $name, + )+ + _ => panic!("unknown keytype {} with path {}", keytype, path), + } + } + } +} + +embed_files! { + (ECDSA_CA_CERT, "ecdsa", "ca.cert"); + (ECDSA_CA_DER, "ecdsa", "ca.der"); + (ECDSA_CA_KEY, "ecdsa", "ca.key"); + (ECDSA_CLIENT_CERT, "ecdsa", "client.cert"); + (ECDSA_CLIENT_CHAIN, "ecdsa", "client.chain"); + (ECDSA_CLIENT_FULLCHAIN, "ecdsa", "client.fullchain"); + (ECDSA_CLIENT_KEY, "ecdsa", "client.key"); + (ECDSA_CLIENT_REQ, "ecdsa", "client.req"); + (ECDSA_END_CERT, "ecdsa", "end.cert"); + (ECDSA_END_CHAIN, "ecdsa", "end.chain"); + (ECDSA_END_FULLCHAIN, "ecdsa", "end.fullchain"); + (ECDSA_END_KEY, "ecdsa", "end.key"); + (ECDSA_END_REQ, "ecdsa", "end.req"); + (ECDSA_INTER_CERT, "ecdsa", "inter.cert"); + (ECDSA_INTER_KEY, "ecdsa", "inter.key"); + (ECDSA_INTER_REQ, "ecdsa", "inter.req"); + (ECDSA_NISTP256_PEM, "ecdsa", "nistp256.pem"); + (ECDSA_NISTP384_PEM, "ecdsa", "nistp384.pem"); + + (EDDSA_CA_CERT, "eddsa", "ca.cert"); + (EDDSA_CA_DER, "eddsa", "ca.der"); + (EDDSA_CA_KEY, "eddsa", "ca.key"); + (EDDSA_CLIENT_CERT, "eddsa", "client.cert"); + (EDDSA_CLIENT_CHAIN, "eddsa", "client.chain"); + (EDDSA_CLIENT_FULLCHAIN, "eddsa", "client.fullchain"); + (EDDSA_CLIENT_KEY, "eddsa", "client.key"); + (EDDSA_CLIENT_REQ, "eddsa", "client.req"); + (EDDSA_END_CERT, "eddsa", "end.cert"); + (EDDSA_END_CHAIN, "eddsa", "end.chain"); + (EDDSA_END_FULLCHAIN, "eddsa", "end.fullchain"); + (EDDSA_END_KEY, "eddsa", "end.key"); + (EDDSA_END_REQ, "eddsa", "end.req"); + (EDDSA_INTER_CERT, "eddsa", "inter.cert"); + (EDDSA_INTER_KEY, "eddsa", "inter.key"); + (EDDSA_INTER_REQ, "eddsa", "inter.req"); + + (RSA_CA_CERT, "rsa", "ca.cert"); + (RSA_CA_DER, "rsa", "ca.der"); + (RSA_CA_KEY, "rsa", "ca.key"); + (RSA_CLIENT_CERT, "rsa", "client.cert"); + (RSA_CLIENT_CHAIN, "rsa", "client.chain"); + (RSA_CLIENT_FULLCHAIN, "rsa", "client.fullchain"); + (RSA_CLIENT_KEY, "rsa", "client.key"); + (RSA_CLIENT_REQ, "rsa", "client.req"); + (RSA_CLIENT_RSA, "rsa", "client.rsa"); + (RSA_END_CERT, "rsa", "end.cert"); + (RSA_END_CHAIN, "rsa", "end.chain"); + (RSA_END_FULLCHAIN, "rsa", "end.fullchain"); + (RSA_END_KEY, "rsa", "end.key"); + (RSA_END_REQ, "rsa", "end.req"); + (RSA_END_RSA, "rsa", "end.rsa"); + (RSA_INTER_CERT, "rsa", "inter.cert"); + (RSA_INTER_KEY, "rsa", "inter.key"); + (RSA_INTER_REQ, "rsa", "inter.req"); +} + +pub fn transfer( + left: &mut (impl DerefMut + Deref>), + right: &mut (impl DerefMut + Deref>), +) -> usize { + let mut buf = [0u8; 262144]; + let mut total = 0; + + while left.wants_write() { + let sz = { + let into_buf: &mut dyn io::Write = &mut &mut buf[..]; + left.write_tls(into_buf).unwrap() + }; + total += sz; + if sz == 0 { + return total; + } + + let mut offs = 0; + loop { + let from_buf: &mut dyn io::Read = &mut &buf[offs..sz]; + offs += right.read_tls(from_buf).unwrap(); + if sz == offs { + break; + } + } + } + + total +} + +pub fn transfer_eof(conn: &mut (impl DerefMut + Deref>)) { + let empty_buf = [0u8; 0]; + let empty_cursor: &mut dyn io::Read = &mut &empty_buf[..]; + let sz = conn.read_tls(empty_cursor).unwrap(); + assert_eq!(sz, 0); +} + +pub enum Altered { + /// message has been edited in-place (or is unchanged) + InPlace, + /// send these raw bytes instead of the message. + Raw(Vec), +} + +pub fn transfer_altered(left: &mut Connection, filter: F, right: &mut Connection) -> usize +where + F: Fn(&mut Message) -> Altered, +{ + let mut buf = [0u8; 262144]; + let mut total = 0; + + while left.wants_write() { + let sz = { + let into_buf: &mut dyn io::Write = &mut &mut buf[..]; + left.write_tls(into_buf).unwrap() + }; + total += sz; + if sz == 0 { + return total; + } + + let mut reader = Reader::init(&buf[..sz]); + while reader.any_left() { + let message = OpaqueMessage::read(&mut reader).unwrap(); + let mut message = Message::try_from(message.into_plain_message()).unwrap(); + let message_enc = match filter(&mut message) { + Altered::InPlace => PlainMessage::from(message) + .into_unencrypted_opaque() + .encode(), + Altered::Raw(data) => data, + }; + + let message_enc_reader: &mut dyn io::Read = &mut &message_enc[..]; + let len = right + .read_tls(message_enc_reader) + .unwrap(); + assert_eq!(len, message_enc.len()); + } + } + + total +} + +#[derive(Clone, Copy, PartialEq)] +pub enum KeyType { + Rsa, + Ecdsa, + Ed25519, +} + +pub static ALL_KEY_TYPES: [KeyType; 3] = [KeyType::Rsa, KeyType::Ecdsa, KeyType::Ed25519]; + +impl KeyType { + fn bytes_for(&self, part: &str) -> &'static [u8] { + match self { + KeyType::Rsa => bytes_for("rsa", part), + KeyType::Ecdsa => bytes_for("ecdsa", part), + KeyType::Ed25519 => bytes_for("eddsa", part), + } + } + + pub fn get_chain(&self) -> Vec { + rustls_pemfile::certs(&mut io::BufReader::new(self.bytes_for("end.fullchain"))) + .unwrap() + .iter() + .map(|v| Certificate(v.clone())) + .collect() + } + + pub fn get_key(&self) -> PrivateKey { + PrivateKey( + rustls_pemfile::pkcs8_private_keys(&mut io::BufReader::new(self.bytes_for("end.key"))) + .unwrap()[0] + .clone(), + ) + } + + pub fn get_client_chain(&self) -> Vec { + rustls_pemfile::certs(&mut io::BufReader::new(self.bytes_for("client.fullchain"))) + .unwrap() + .iter() + .map(|v| Certificate(v.clone())) + .collect() + } + + fn get_client_key(&self) -> PrivateKey { + PrivateKey( + rustls_pemfile::pkcs8_private_keys(&mut io::BufReader::new( + self.bytes_for("client.key"), + )) + .unwrap()[0] + .clone(), + ) + } +} + +pub fn finish_server_config( + kt: KeyType, + conf: rustls::ConfigBuilder, +) -> ServerConfig { + conf.with_no_client_auth() + .with_single_cert(kt.get_chain(), kt.get_key()) + .unwrap() +} + +pub fn make_server_config(kt: KeyType) -> ServerConfig { + finish_server_config(kt, ServerConfig::builder().with_safe_defaults()) +} + +pub fn make_server_config_with_versions( + kt: KeyType, + versions: &[&'static rustls::SupportedProtocolVersion], +) -> ServerConfig { + finish_server_config( + kt, + ServerConfig::builder() + .with_safe_default_cipher_suites() + .with_safe_default_kx_groups() + .with_protocol_versions(versions) + .unwrap(), + ) +} + +pub fn make_server_config_with_kx_groups( + kt: KeyType, + kx_groups: &[&'static rustls::SupportedKxGroup], +) -> ServerConfig { + finish_server_config( + kt, + ServerConfig::builder() + .with_safe_default_cipher_suites() + .with_kx_groups(kx_groups) + .with_safe_default_protocol_versions() + .unwrap(), + ) +} + +pub fn get_client_root_store(kt: KeyType) -> RootCertStore { + let roots = kt.get_chain(); + let mut client_auth_roots = RootCertStore::empty(); + for root in roots { + client_auth_roots.add(&root).unwrap(); + } + client_auth_roots +} + +pub fn make_server_config_with_mandatory_client_auth(kt: KeyType) -> ServerConfig { + let client_auth_roots = get_client_root_store(kt); + + let client_auth = AllowAnyAuthenticatedClient::new(client_auth_roots); + + ServerConfig::builder() + .with_safe_defaults() + .with_client_cert_verifier(client_auth) + .with_single_cert(kt.get_chain(), kt.get_key()) + .unwrap() +} + +pub fn finish_client_config( + kt: KeyType, + config: rustls::ConfigBuilder, +) -> ClientConfig { + let mut root_store = RootCertStore::empty(); + let mut rootbuf = io::BufReader::new(kt.bytes_for("ca.cert")); + root_store.add_parsable_certificates(&rustls_pemfile::certs(&mut rootbuf).unwrap()); + + config + .with_root_certificates(root_store) + .with_no_client_auth() +} + +pub fn finish_client_config_with_creds( + kt: KeyType, + config: rustls::ConfigBuilder, +) -> ClientConfig { + let mut root_store = RootCertStore::empty(); + let mut rootbuf = io::BufReader::new(kt.bytes_for("ca.cert")); + root_store.add_parsable_certificates(&rustls_pemfile::certs(&mut rootbuf).unwrap()); + + config + .with_root_certificates(root_store) + .with_single_cert(kt.get_client_chain(), kt.get_client_key()) + .unwrap() +} + +pub fn make_client_config(kt: KeyType) -> ClientConfig { + finish_client_config(kt, ClientConfig::builder().with_safe_defaults()) +} + +pub fn make_client_config_with_kx_groups( + kt: KeyType, + kx_groups: &[&'static rustls::SupportedKxGroup], +) -> ClientConfig { + let builder = ClientConfig::builder() + .with_safe_default_cipher_suites() + .with_kx_groups(kx_groups) + .with_safe_default_protocol_versions() + .unwrap(); + finish_client_config(kt, builder) +} + +pub fn make_client_config_with_versions( + kt: KeyType, + versions: &[&'static rustls::SupportedProtocolVersion], +) -> ClientConfig { + let builder = ClientConfig::builder() + .with_safe_default_cipher_suites() + .with_safe_default_kx_groups() + .with_protocol_versions(versions) + .unwrap(); + finish_client_config(kt, builder) +} + +pub fn make_client_config_with_auth(kt: KeyType) -> ClientConfig { + finish_client_config_with_creds(kt, ClientConfig::builder().with_safe_defaults()) +} + +pub fn make_client_config_with_versions_with_auth( + kt: KeyType, + versions: &[&'static rustls::SupportedProtocolVersion], +) -> ClientConfig { + let builder = ClientConfig::builder() + .with_safe_default_cipher_suites() + .with_safe_default_kx_groups() + .with_protocol_versions(versions) + .unwrap(); + finish_client_config_with_creds(kt, builder) +} + +pub fn make_pair(kt: KeyType) -> (ClientConnection, ServerConnection) { + make_pair_for_configs(make_client_config(kt), make_server_config(kt)) +} + +pub fn make_pair_for_configs( + client_config: ClientConfig, + server_config: ServerConfig, +) -> (ClientConnection, ServerConnection) { + make_pair_for_arc_configs(&Arc::new(client_config), &Arc::new(server_config)) +} + +pub fn make_pair_for_arc_configs( + client_config: &Arc, + server_config: &Arc, +) -> (ClientConnection, ServerConnection) { + ( + ClientConnection::new(Arc::clone(client_config), dns_name("localhost")).unwrap(), + ServerConnection::new(Arc::clone(server_config)).unwrap(), + ) +} + +pub fn do_handshake( + client: &mut (impl DerefMut + Deref>), + server: &mut (impl DerefMut + Deref>), +) -> (usize, usize) { + let (mut to_client, mut to_server) = (0, 0); + while server.is_handshaking() || client.is_handshaking() { + to_server += transfer(client, server); + server.process_new_packets().unwrap(); + to_client += transfer(server, client); + client.process_new_packets().unwrap(); + } + (to_server, to_client) +} + +#[derive(PartialEq, Debug)] +pub enum ErrorFromPeer { + Client(Error), + Server(Error), +} + +pub fn do_handshake_until_error( + client: &mut ClientConnection, + server: &mut ServerConnection, +) -> Result<(), ErrorFromPeer> { + while server.is_handshaking() || client.is_handshaking() { + transfer(client, server); + server + .process_new_packets() + .map_err(ErrorFromPeer::Server)?; + transfer(server, client); + client + .process_new_packets() + .map_err(ErrorFromPeer::Client)?; + } + + Ok(()) +} + +pub fn do_handshake_until_both_error( + client: &mut ClientConnection, + server: &mut ServerConnection, +) -> Result<(), Vec> { + match do_handshake_until_error(client, server) { + Err(server_err @ ErrorFromPeer::Server(_)) => { + let mut errors = vec![server_err]; + transfer(server, client); + let client_err = client + .process_new_packets() + .map_err(ErrorFromPeer::Client) + .expect_err("client didn't produce error after server error"); + errors.push(client_err); + Err(errors) + } + + Err(client_err @ ErrorFromPeer::Client(_)) => { + let mut errors = vec![client_err]; + transfer(client, server); + let server_err = server + .process_new_packets() + .map_err(ErrorFromPeer::Server) + .expect_err("server didn't produce error after client error"); + errors.push(server_err); + Err(errors) + } + + Ok(()) => Ok(()), + } +} + +pub fn dns_name(name: &'static str) -> rustls::ServerName { + name.try_into().unwrap() +} + +pub struct FailsReads { + errkind: io::ErrorKind, +} + +impl FailsReads { + pub fn new(errkind: io::ErrorKind) -> Self { + FailsReads { errkind } + } +} + +impl io::Read for FailsReads { + fn read(&mut self, _b: &mut [u8]) -> io::Result { + Err(io::Error::from(self.errkind)) + } +} diff --git a/third_party/rustls-fork-shadow-tls/tests/key_log_file_env.rs b/third_party/rustls-fork-shadow-tls/tests/key_log_file_env.rs new file mode 100644 index 0000000..bc15f53 --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/tests/key_log_file_env.rs @@ -0,0 +1,104 @@ +//! Tests of [`rustls::KeyLogFile`] that require us to set environment variables. +//! +//! vvvv +//! Every test you add to this file MUST execute through `serialized()`. +//! ^^^^ +//! +//! See https://github.com/rust-lang/rust/issues/90308; despite not being marked +//! `unsafe`, `env::var::set_var` is an unsafe function. These tests are separated +//! from the rest of the tests so that their use of `set_ver` is less likely to +//! affect them; as of the time these tests were moved to this file, Cargo will +//! compile each test suite file to a separate executable, so these will be run +//! in a completely separate process. This way, executing every test through +//! `serialized()` will cause them to be run one at a time. +//! +//! Note: If/when we add new constructors to `KeyLogFile` to allow constructing +//! one from a path directly (without using an environment variable), then those +//! tests SHOULD NOT go in this file. +//! +//! XXX: These tests don't actually test the functionality; they just ensure +//! the code coverage doesn't complain it isn't covered. TODO: Verify that the +//! file was created successfully, with the right permissions, etc., and that it +//! contains something like what we expect. + +#[allow(dead_code)] +mod common; + +use crate::common::{ + do_handshake, make_client_config_with_versions, make_pair_for_arc_configs, make_server_config, + transfer, KeyType, +}; +use std::{ + env, + io::Write, + sync::{Arc, Mutex, Once}, +}; + +/// Approximates `#[serial]` from the `serial_test` crate. +/// +/// No attempt is made to recover from a poisoned mutex, which will +/// happen when `f` panics. In other words, all the tests that use +/// `serialized` will start failing after one test panics. +fn serialized(f: impl FnOnce()) { + // Ensure every test is run serialized + // TODO: Use `std::sync::Lazy` once that is stable. + static mut MUTEX: Option> = None; + static ONCE: Once = Once::new(); + ONCE.call_once(|| unsafe { + MUTEX = Some(Mutex::new(())); + }); + let mutex = unsafe { MUTEX.as_mut() }; + + let _guard = mutex.unwrap().lock().unwrap(); + + // XXX: NOT thread safe. + env::set_var("SSLKEYLOGFILE", "./sslkeylogfile.txt"); + + f() +} + +#[test] +fn exercise_key_log_file_for_client() { + serialized(|| { + let server_config = Arc::new(make_server_config(KeyType::Rsa)); + env::set_var("SSLKEYLOGFILE", "./sslkeylogfile.txt"); + + for version in rustls::ALL_VERSIONS { + let mut client_config = make_client_config_with_versions(KeyType::Rsa, &[version]); + client_config.key_log = Arc::new(rustls::KeyLogFile::new()); + + let (mut client, mut server) = + make_pair_for_arc_configs(&Arc::new(client_config), &server_config); + + assert_eq!(5, client.writer().write(b"hello").unwrap()); + + do_handshake(&mut client, &mut server); + transfer(&mut client, &mut server); + server.process_new_packets().unwrap(); + } + }) +} + +#[test] +fn exercise_key_log_file_for_server() { + serialized(|| { + let mut server_config = make_server_config(KeyType::Rsa); + + env::set_var("SSLKEYLOGFILE", "./sslkeylogfile.txt"); + server_config.key_log = Arc::new(rustls::KeyLogFile::new()); + + let server_config = Arc::new(server_config); + + for version in rustls::ALL_VERSIONS { + let client_config = make_client_config_with_versions(KeyType::Rsa, &[version]); + let (mut client, mut server) = + make_pair_for_arc_configs(&Arc::new(client_config), &server_config); + + assert_eq!(5, client.writer().write(b"hello").unwrap()); + + do_handshake(&mut client, &mut server); + transfer(&mut client, &mut server); + server.process_new_packets().unwrap(); + } + }) +} diff --git a/third_party/rustls-fork-shadow-tls/tests/server_cert_verifier.rs b/third_party/rustls-fork-shadow-tls/tests/server_cert_verifier.rs new file mode 100644 index 0000000..65d635c --- /dev/null +++ b/third_party/rustls-fork-shadow-tls/tests/server_cert_verifier.rs @@ -0,0 +1,272 @@ +//! Tests for configuring and using a [`ServerCertVerifier`] for a client. + +#![cfg(feature = "dangerous_configuration")] + +mod common; +use crate::common::{ + do_handshake, do_handshake_until_both_error, make_client_config_with_versions, + make_pair_for_arc_configs, make_server_config, ErrorFromPeer, ALL_KEY_TYPES, +}; +use rustls::client::{ + HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier, WebPkiVerifier, +}; +use rustls::internal::msgs::handshake::DigitallySignedStruct; +use rustls::AlertDescription; +use rustls::{Certificate, Error, SignatureScheme}; +use std::sync::Arc; + +#[test] +fn client_can_override_certificate_verification() { + for kt in ALL_KEY_TYPES.iter() { + let verifier = Arc::new(MockServerVerifier::accepts_anything()); + + let server_config = Arc::new(make_server_config(*kt)); + + for version in rustls::ALL_VERSIONS { + let mut client_config = make_client_config_with_versions(*kt, &[version]); + client_config + .dangerous() + .set_certificate_verifier(verifier.clone()); + + let (mut client, mut server) = + make_pair_for_arc_configs(&Arc::new(client_config), &server_config); + do_handshake(&mut client, &mut server); + } + } +} + +#[test] +fn client_can_override_certificate_verification_and_reject_certificate() { + for kt in ALL_KEY_TYPES.iter() { + let verifier = Arc::new(MockServerVerifier::rejects_certificate( + Error::CorruptMessage, + )); + + let server_config = Arc::new(make_server_config(*kt)); + + for version in rustls::ALL_VERSIONS { + let mut client_config = make_client_config_with_versions(*kt, &[version]); + client_config + .dangerous() + .set_certificate_verifier(verifier.clone()); + + let (mut client, mut server) = + make_pair_for_arc_configs(&Arc::new(client_config), &server_config); + let errs = do_handshake_until_both_error(&mut client, &mut server); + assert_eq!( + errs, + Err(vec![ + ErrorFromPeer::Client(Error::CorruptMessage), + ErrorFromPeer::Server(Error::AlertReceived(AlertDescription::BadCertificate)) + ]) + ); + } + } +} + +#[cfg(feature = "tls12")] +#[test] +fn client_can_override_certificate_verification_and_reject_tls12_signatures() { + for kt in ALL_KEY_TYPES.iter() { + let mut client_config = make_client_config_with_versions(*kt, &[&rustls::version::TLS12]); + let verifier = Arc::new(MockServerVerifier::rejects_tls12_signatures( + Error::CorruptMessage, + )); + + client_config + .dangerous() + .set_certificate_verifier(verifier); + + let server_config = Arc::new(make_server_config(*kt)); + + let (mut client, mut server) = + make_pair_for_arc_configs(&Arc::new(client_config), &server_config); + let errs = do_handshake_until_both_error(&mut client, &mut server); + assert_eq!( + errs, + Err(vec![ + ErrorFromPeer::Client(Error::CorruptMessage), + ErrorFromPeer::Server(Error::AlertReceived(AlertDescription::BadCertificate)) + ]) + ); + } +} + +#[test] +fn client_can_override_certificate_verification_and_reject_tls13_signatures() { + for kt in ALL_KEY_TYPES.iter() { + let mut client_config = make_client_config_with_versions(*kt, &[&rustls::version::TLS13]); + let verifier = Arc::new(MockServerVerifier::rejects_tls13_signatures( + Error::CorruptMessage, + )); + + client_config + .dangerous() + .set_certificate_verifier(verifier); + + let server_config = Arc::new(make_server_config(*kt)); + + let (mut client, mut server) = + make_pair_for_arc_configs(&Arc::new(client_config), &server_config); + let errs = do_handshake_until_both_error(&mut client, &mut server); + assert_eq!( + errs, + Err(vec![ + ErrorFromPeer::Client(Error::CorruptMessage), + ErrorFromPeer::Server(Error::AlertReceived(AlertDescription::BadCertificate)) + ]) + ); + } +} + +#[test] +fn client_can_override_certificate_verification_and_offer_no_signature_schemes() { + for kt in ALL_KEY_TYPES.iter() { + let verifier = Arc::new(MockServerVerifier::offers_no_signature_schemes()); + + let server_config = Arc::new(make_server_config(*kt)); + + for version in rustls::ALL_VERSIONS { + let mut client_config = make_client_config_with_versions(*kt, &[version]); + client_config + .dangerous() + .set_certificate_verifier(verifier.clone()); + + let (mut client, mut server) = + make_pair_for_arc_configs(&Arc::new(client_config), &server_config); + let errs = do_handshake_until_both_error(&mut client, &mut server); + assert_eq!( + errs, + Err(vec![ + ErrorFromPeer::Server(Error::PeerIncompatibleError( + "no overlapping sigschemes".into() + )), + ErrorFromPeer::Client(Error::AlertReceived(AlertDescription::HandshakeFailure)), + ]) + ); + } + } +} + +pub struct MockServerVerifier { + cert_rejection_error: Option, + tls12_signature_error: Option, + tls13_signature_error: Option, + wants_scts: bool, + signature_schemes: Vec, +} + +impl ServerCertVerifier for MockServerVerifier { + fn verify_server_cert( + &self, + end_entity: &rustls::Certificate, + intermediates: &[rustls::Certificate], + server_name: &rustls::ServerName, + scts: &mut dyn Iterator, + oscp_response: &[u8], + now: std::time::SystemTime, + ) -> Result { + let scts: Vec> = scts.map(|x| x.to_owned()).collect(); + println!( + "verify_server_cert({:?}, {:?}, {:?}, {:?}, {:?}, {:?})", + end_entity, intermediates, server_name, scts, oscp_response, now + ); + if let Some(error) = &self.cert_rejection_error { + Err(error.clone()) + } else { + Ok(ServerCertVerified::assertion()) + } + } + + fn verify_tls12_signature( + &self, + message: &[u8], + cert: &Certificate, + dss: &DigitallySignedStruct, + ) -> Result { + println!( + "verify_tls12_signature({:?}, {:?}, {:?})", + message, cert, dss + ); + if let Some(error) = &self.tls12_signature_error { + Err(error.clone()) + } else { + Ok(HandshakeSignatureValid::assertion()) + } + } + + fn verify_tls13_signature( + &self, + message: &[u8], + cert: &Certificate, + dss: &DigitallySignedStruct, + ) -> Result { + println!( + "verify_tls13_signature({:?}, {:?}, {:?})", + message, cert, dss + ); + if let Some(error) = &self.tls13_signature_error { + Err(error.clone()) + } else { + Ok(HandshakeSignatureValid::assertion()) + } + } + + fn supported_verify_schemes(&self) -> Vec { + self.signature_schemes.clone() + } + + fn request_scts(&self) -> bool { + println!("request_scts? {:?}", self.wants_scts); + self.wants_scts + } +} + +impl MockServerVerifier { + pub fn accepts_anything() -> Self { + MockServerVerifier { + cert_rejection_error: None, + ..Default::default() + } + } + + pub fn rejects_certificate(err: Error) -> Self { + MockServerVerifier { + cert_rejection_error: Some(err), + ..Default::default() + } + } + + pub fn rejects_tls12_signatures(err: Error) -> Self { + MockServerVerifier { + tls12_signature_error: Some(err), + ..Default::default() + } + } + + pub fn rejects_tls13_signatures(err: Error) -> Self { + MockServerVerifier { + tls13_signature_error: Some(err), + ..Default::default() + } + } + + pub fn offers_no_signature_schemes() -> Self { + MockServerVerifier { + signature_schemes: vec![], + ..Default::default() + } + } +} + +impl Default for MockServerVerifier { + fn default() -> Self { + MockServerVerifier { + cert_rejection_error: None, + tls12_signature_error: None, + tls13_signature_error: None, + wants_scts: false, + signature_schemes: WebPkiVerifier::verification_schemes(), + } + } +} diff --git a/third_party/tokio-rustls-fork-shadow-tls/Cargo.lock b/third_party/tokio-rustls-fork-shadow-tls/Cargo.lock new file mode 100644 index 0000000..fabac45 --- /dev/null +++ b/third_party/tokio-rustls-fork-shadow-tls/Cargo.lock @@ -0,0 +1,523 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bumpalo" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" + +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + +[[package]] +name = "cc" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "hermit-abi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.139" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" + +[[package]] +name = "lock_api" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[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 = "mio" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.45.0", +] + +[[package]] +name = "num_cpus" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-sys 0.45.0", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "proc-macro2" +version = "1.0.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "227b89ca93267855a168e5293f66c74d43afabcdf2239947d3b5904928388161" +dependencies = [ + "log", + "ring", + "sct", + "webpki", +] + +[[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 = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + +[[package]] +name = "socket2" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" +dependencies = [ + "libc", + "winapi", +] + +[[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.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8e00990ebabbe4c14c08aca901caed183ecd5c09562a12c824bb53d3c3fd3af" +dependencies = [ + "autocfg", + "bytes", + "libc", + "memchr", + "mio", + "num_cpus", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.42.0", +] + +[[package]] +name = "tokio-macros" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-rustls-fork-shadow-tls" +version = "0.0.8" +dependencies = [ + "bytes", + "rustls-fork-shadow-tls", + "thiserror", + "tokio", + "webpki-roots", +] + +[[package]] +name = "unicode-ident" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" + +[[package]] +name = "web-sys" +version = "0.3.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" +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.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" +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-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" diff --git a/third_party/tokio-rustls-fork-shadow-tls/Cargo.toml b/third_party/tokio-rustls-fork-shadow-tls/Cargo.toml new file mode 100644 index 0000000..b8fccbc --- /dev/null +++ b/third_party/tokio-rustls-fork-shadow-tls/Cargo.toml @@ -0,0 +1,57 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2021" +name = "tokio-rustls-fork-shadow-tls" +version = "0.0.8" +authors = ["ChiHai "] +description = "Asynchronous TLS streams wrapper for Tokio based on Rustls." +homepage = "https://github.com/compdzwio/tokio-tls" +readme = "README.md" +categories = [ + "asynchronous", + "cryptography", + "network-programming", +] +license = "MIT/Apache-2.0" +repository = "https://github.com/compdzwio/tokio-tls" +resolver = "1" + +[dependencies.bytes] +version = "1" + +[dependencies.rustls-fork-shadow-tls] +version = "0.20.8" +default-features = false + +[dependencies.thiserror] +version = "1" + +[dependencies.tokio] +version = "1.25.0" +features = ["full"] + +[dev-dependencies.tokio] +version = "1.25.0" + +[dev-dependencies.webpki-roots] +version = "0.22" + +[features] +dangerous_configuration = ["rustls-fork-shadow-tls/dangerous_configuration"] +default = [ + "logging", + "tls12", +] +logging = ["rustls-fork-shadow-tls/logging"] +tls12 = ["rustls-fork-shadow-tls/tls12"] +unsafe_io = [] diff --git a/third_party/tokio-rustls-fork-shadow-tls/Cargo.toml.orig b/third_party/tokio-rustls-fork-shadow-tls/Cargo.toml.orig new file mode 100644 index 0000000..3d930e2 --- /dev/null +++ b/third_party/tokio-rustls-fork-shadow-tls/Cargo.toml.orig @@ -0,0 +1,30 @@ +[package] +authors = ["ChiHai "] +categories = ["asynchronous", "cryptography", "network-programming"] +description = "Asynchronous TLS streams wrapper for Tokio based on Rustls." +edition = "2021" +homepage = "https://github.com/compdzwio/tokio-tls" +license = "MIT/Apache-2.0" +name = "tokio-rustls-fork-shadow-tls" +readme = "README.md" +repository = "https://github.com/compdzwio/tokio-tls" +version = "0.0.8" + +[dependencies] +bytes = {version = "1"} +tokio = {version = "1.25.0", features = ["full"]} +rustls-fork-shadow-tls = {version = "0.20.8", default-features = false} +thiserror = {version = "1"} + +[features] +dangerous_configuration = ["rustls-fork-shadow-tls/dangerous_configuration"] +default = ["logging", "tls12"] +logging = ["rustls-fork-shadow-tls/logging"] +tls12 = ["rustls-fork-shadow-tls/tls12"] +# Once unsafe_io is enabled, you may not drop the future before it returns ready. +# It saves one buffer copy than disabled. +unsafe_io = [] + +[dev-dependencies] +tokio = {version = "1.25.0"} +webpki-roots = "0.22" diff --git a/third_party/tokio-rustls-fork-shadow-tls/README.md b/third_party/tokio-rustls-fork-shadow-tls/README.md new file mode 100644 index 0000000..20f67fd --- /dev/null +++ b/third_party/tokio-rustls-fork-shadow-tls/README.md @@ -0,0 +1 @@ +# Tokio-rustls diff --git a/third_party/tokio-rustls-fork-shadow-tls/examples/connect.rs b/third_party/tokio-rustls-fork-shadow-tls/examples/connect.rs new file mode 100644 index 0000000..50e8711 --- /dev/null +++ b/third_party/tokio-rustls-fork-shadow-tls/examples/connect.rs @@ -0,0 +1,42 @@ +use std::sync::Arc; + +use tokio::{ + io::{AsyncReadExt, AsyncWriteExt}, + net::TcpStream, +}; + +use tokio_rustls_fork_shadow_tls::TlsConnector; +use rustls_fork_shadow_tls::{OwnedTrustAnchor, RootCertStore}; + +#[tokio::main] +async fn main() { + let mut root_store = RootCertStore::empty(); + 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, + ) + })); + let config = rustls_fork_shadow_tls::ClientConfig::builder() + .with_safe_defaults() + .with_root_certificates(root_store) + .with_no_client_auth(); + + let connector = TlsConnector::from(Arc::new(config)); + let stream = TcpStream::connect("rsproxy.cn:443").await.unwrap(); + println!("rsproxy.cn:443 connected"); + + let domain = rustls_fork_shadow_tls::ServerName::try_from("rsproxy.cn").unwrap(); + let mut stream = connector.connect(domain, stream).await.unwrap(); + println!("handshake success"); + + let content = b"GET / HTTP/1.0\r\nHost: rsproxy.cn\r\n\r\n"; + stream.write_all(content).await?; + println!("http request sent"); + + let buf = vec![0_u8; 64]; + let n = stream.read(buf).await?; + let resp = String::from_utf8(buf).unwrap(); + println!("http response recv: \n\n{resp}"); +} diff --git a/third_party/tokio-rustls-fork-shadow-tls/src/client.rs b/third_party/tokio-rustls-fork-shadow-tls/src/client.rs new file mode 100644 index 0000000..1513090 --- /dev/null +++ b/third_party/tokio-rustls-fork-shadow-tls/src/client.rs @@ -0,0 +1,86 @@ +use std::sync::Arc; + +use tokio::io::{AsyncRead, AsyncWrite}; +use rustls_fork_shadow_tls::{ClientConfig, ClientConnection, ClientSessionIdGenerators}; + +use crate::{ + split::{ReadHalf, WriteHalf}, + stream::Stream, + TlsError, +}; + +/// A wrapper around an underlying raw stream which implements the TLS protocol. +pub type TlsStream = Stream; +/// TlsStream for read only. +pub type TlsStreamReadHalf = ReadHalf; +/// TlsStream for write only. +pub type TlsStreamWriteHalf = WriteHalf; + +/// A wrapper around a `rustls::ClientConfig`, providing an async `connect` method. +#[derive(Clone)] +pub struct TlsConnector { + inner: Arc, +} + +impl From> for TlsConnector { + fn from(inner: Arc) -> TlsConnector { + TlsConnector { inner } + } +} + +impl From for TlsConnector { + fn from(inner: ClientConfig) -> TlsConnector { + TlsConnector { + inner: Arc::new(inner), + } + } +} + +impl TlsConnector { + pub async fn connect( + &self, + domain: rustls_fork_shadow_tls::ServerName, + stream: IO, + ) -> Result, TlsError> + where + IO: AsyncRead + AsyncWrite + Unpin, + { + let session = ClientConnection::new(self.inner.clone(), domain)?; + let mut stream = Stream::new(stream, session); + stream.handshake().await?; + Ok(stream) + } + + pub async fn connect_with_session_id_generator( + &self, + domain: rustls_fork_shadow_tls::ServerName, + stream: IO, + generator: F, + ) -> Result, TlsError> + where + IO: AsyncRead + AsyncWrite + Unpin, + F: Fn(&[u8]) -> [u8; 32] + Send + Sync + 'static, + { + let session = + ClientConnection::new_with_session_id_generator(self.inner.clone(), domain, generator)?; + let mut stream = Stream::new(stream, session); + stream.handshake().await?; + Ok(stream) + } + + pub async fn connect_with_session_id_generators( + &self, + domain: rustls_fork_shadow_tls::ServerName, + stream: IO, + generators: ClientSessionIdGenerators, + ) -> Result, TlsError> + where + IO: AsyncRead + AsyncWrite + Unpin, + { + let session = + ClientConnection::new_with_session_id_generators(self.inner.clone(), domain, generators)?; + let mut stream = Stream::new(stream, session); + stream.handshake().await?; + Ok(stream) + } +} diff --git a/third_party/tokio-rustls-fork-shadow-tls/src/error.rs b/third_party/tokio-rustls-fork-shadow-tls/src/error.rs new file mode 100644 index 0000000..b34fad0 --- /dev/null +++ b/third_party/tokio-rustls-fork-shadow-tls/src/error.rs @@ -0,0 +1,20 @@ +use std::io; + +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum TlsError { + #[error("io error")] + Io(#[from] std::io::Error), + #[error("rustls error")] + Rustls(#[from] rustls_fork_shadow_tls::Error), +} + +impl From for io::Error { + fn from(e: TlsError) -> Self { + match e { + TlsError::Io(e) => e, + TlsError::Rustls(e) => io::Error::new(io::ErrorKind::Other, e), + } + } +} diff --git a/third_party/tokio-rustls-fork-shadow-tls/src/lib.rs b/third_party/tokio-rustls-fork-shadow-tls/src/lib.rs new file mode 100644 index 0000000..2998b52 --- /dev/null +++ b/third_party/tokio-rustls-fork-shadow-tls/src/lib.rs @@ -0,0 +1,21 @@ +#![allow(stable_features)] + +mod client; +mod error; +#[cfg(not(feature = "unsafe_io"))] +mod safe_io; +mod server; +mod split; +mod stream; +#[cfg(feature = "unsafe_io")] +mod unsafe_io; + +pub use client::{ + TlsConnector, TlsStream as ClientTlsStream, TlsStreamReadHalf as ClientTlsStreamReadHalf, + TlsStreamWriteHalf as ClientTlsStreamWriteHalf, +}; +pub use error::TlsError; +pub use server::{ + TlsAcceptor, TlsStream as ServerTlsStream, TlsStreamReadHalf as ServerTlsStreamReadHalf, + TlsStreamWriteHalf as ServerTlsStreamWriteHalf, +}; diff --git a/third_party/tokio-rustls-fork-shadow-tls/src/safe_io.rs b/third_party/tokio-rustls-fork-shadow-tls/src/safe_io.rs new file mode 100644 index 0000000..2290d65 --- /dev/null +++ b/third_party/tokio-rustls-fork-shadow-tls/src/safe_io.rs @@ -0,0 +1,227 @@ +use std::{ + fmt::Debug, hint::unreachable_unchecked, io +}; + +use tokio::{ + io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt} +}; + +const BUFFER_SIZE: usize = 16 * 1024; + +struct Buffer { + read: usize, + write: usize, + buf: Box<[u8]>, +} + +impl Buffer { + fn new() -> Self { + Self { + read: 0, + write: 0, + buf: vec![0; BUFFER_SIZE].into_boxed_slice(), + } + } + + fn len(&self) -> usize { + self.write - self.read + } + + fn is_empty(&self) -> bool { + self.len() == 0 + } + + fn available(&self) -> usize { + self.buf.len() - self.write + } + + fn is_full(&self) -> bool { + self.available() == 0 + } + + fn advance(&mut self, n: usize) { + assert!(self.write - self.read >= n); + self.read += n; + if self.read == self.write { + self.read = 0; + self.write = 0; + } + } +} + +pub(crate) struct SafeRead { + // the option is only meant for temporary take, it always should be some + buffer: Option, + status: ReadStatus, +} + +impl Debug for SafeRead { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("SafeRead") + .field("status", &self.status) + .finish() + } +} + +#[derive(Debug)] +enum ReadStatus { + Eof, + Err(io::Error), + Ok, +} + +impl Default for SafeRead { + fn default() -> Self { + Self { + buffer: Some(Buffer::new()), + status: ReadStatus::Ok, + } + } +} + +impl SafeRead { + pub(crate) async fn do_io(&mut self, mut io: IO) -> io::Result { + // if there are some data inside the buffer, just return. + let buffer = self.buffer.as_ref().expect("buffer ref expected"); + if !buffer.is_empty() { + return Ok(buffer.len()); + } + + // read from raw io + let buffer = self.buffer.as_mut().expect("buffer ownership expected"); + let buf = &mut buffer.buf.as_mut()[buffer.write..]; + let result = io.read(buf).await; + match result { + Ok(0) => { + self.status = ReadStatus::Eof; + result + } + Ok(n) => { + buffer.write += n; + self.status = ReadStatus::Ok; + result + } + Err(e) => { + let rerr = e.kind().into(); + self.status = ReadStatus::Err(e); + Err(rerr) + } + } + } +} + +impl io::Read for SafeRead { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + // if buffer is empty, return WoundBlock. + let buffer = self.buffer.as_mut().expect("buffer mut expected"); + if buffer.is_empty() { + if !matches!(self.status, ReadStatus::Ok) { + match std::mem::replace(&mut self.status, ReadStatus::Ok) { + ReadStatus::Eof => return Ok(0), + ReadStatus::Err(e) => return Err(e), + ReadStatus::Ok => unsafe { unreachable_unchecked() }, + } + } + return Err(io::ErrorKind::WouldBlock.into()); + } + + // now buffer is not empty. copy it. + let to_copy = buffer.len().min(buf.len()); + unsafe { std::ptr::copy_nonoverlapping(buffer.buf.as_ptr().add(buffer.read), buf.as_mut_ptr(), to_copy) }; + buffer.advance(to_copy); + + Ok(to_copy) + } +} + +pub(crate) struct SafeWrite { + // the option is only meant for temporary take, it always should be some + buffer: Option, + status: WriteStatus, +} + +impl Debug for SafeWrite { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("SafeWrite") + .field("status", &self.status) + .finish() + } +} + +#[derive(Debug)] +enum WriteStatus { + Err(io::Error), + Ok, +} + +impl Default for SafeWrite { + fn default() -> Self { + Self { + buffer: Some(Buffer::new()), + status: WriteStatus::Ok, + } + } +} + +impl SafeWrite { + pub(crate) async fn do_io(&mut self, mut io: IO) -> io::Result { + // if the buffer is empty, just return. + let buffer = self.buffer.as_ref().expect("buffer ref expected"); + if buffer.is_empty() { + return Ok(0); + } + + // buffer is not empty now. write it. + let buffer = self.buffer.as_mut().expect("buffer ownership expected"); + let buf = &buffer.buf.as_ref()[buffer.read..buffer.write]; + let result = io.write_all(buf).await; + match result { + Ok(_) => { + let n = buffer.write - buffer.read; + buffer.advance(n); + Ok(n) + } + Err(e) => { + let rerr = e.kind().into(); + self.status = WriteStatus::Err(e); + Err(rerr) + } + } + } +} + +impl io::Write for SafeWrite { + fn write(&mut self, buf: &[u8]) -> io::Result { + // if there is too much data inside the buffer, return WoundBlock + let buffer = self.buffer.as_mut().expect("buffer mut expected"); + if !matches!(self.status, WriteStatus::Ok) { + match std::mem::replace(&mut self.status, WriteStatus::Ok) { + WriteStatus::Err(e) => return Err(e), + WriteStatus::Ok => unsafe { unreachable_unchecked() }, + } + } + if buffer.is_full() { + return Err(io::ErrorKind::WouldBlock.into()); + } + + // there is space inside the buffer, copy to it. + let to_copy = buf.len().min(buffer.available()); + unsafe { std::ptr::copy_nonoverlapping(buf.as_ptr(), buffer.buf.as_mut_ptr().add(buffer.write), to_copy); } + buffer.write += to_copy; + Ok(to_copy) + } + + fn flush(&mut self) -> io::Result<()> { + let buffer = self.buffer.as_mut().expect("buffer mut expected"); + if !matches!(self.status, WriteStatus::Ok) { + match std::mem::replace(&mut self.status, WriteStatus::Ok) { + WriteStatus::Err(e) => return Err(e), + WriteStatus::Ok => unsafe { unreachable_unchecked() }, + } + } + if !buffer.is_empty() { + return Err(io::ErrorKind::WouldBlock.into()); + } + Ok(()) + } +} diff --git a/third_party/tokio-rustls-fork-shadow-tls/src/server.rs b/third_party/tokio-rustls-fork-shadow-tls/src/server.rs new file mode 100644 index 0000000..b1db30f --- /dev/null +++ b/third_party/tokio-rustls-fork-shadow-tls/src/server.rs @@ -0,0 +1,49 @@ +use std::sync::Arc; + +use tokio::io::{AsyncRead, AsyncWrite}; +use rustls_fork_shadow_tls::{ServerConfig, ServerConnection}; + +use crate::{ + split::{ReadHalf, WriteHalf}, + stream::Stream, + TlsError, +}; + +/// A wrapper around an underlying raw stream which implements the TLS protocol. +pub type TlsStream = Stream; +/// TlsStream for read only. +pub type TlsStreamReadHalf = ReadHalf; +/// TlsStream for write only. +pub type TlsStreamWriteHalf = WriteHalf; + +/// A wrapper around a `rustls::ServerConfig`, providing an async `accept` method. +#[derive(Clone)] +pub struct TlsAcceptor { + inner: Arc, +} + +impl From> for TlsAcceptor { + fn from(inner: Arc) -> TlsAcceptor { + TlsAcceptor { inner } + } +} + +impl From for TlsAcceptor { + fn from(inner: ServerConfig) -> TlsAcceptor { + TlsAcceptor { + inner: Arc::new(inner), + } + } +} + +impl TlsAcceptor { + pub async fn accept(&self, stream: IO) -> Result, TlsError> + where + IO: AsyncRead + AsyncWrite + Unpin, + { + let session = ServerConnection::new(self.inner.clone())?; + let mut stream = Stream::new(stream, session); + stream.handshake().await?; + Ok(stream) + } +} diff --git a/third_party/tokio-rustls-fork-shadow-tls/src/split.rs b/third_party/tokio-rustls-fork-shadow-tls/src/split.rs new file mode 100644 index 0000000..188b59e --- /dev/null +++ b/third_party/tokio-rustls-fork-shadow-tls/src/split.rs @@ -0,0 +1,139 @@ +//! Split implement for TlsStream. +//! Note: Here we depends on the behavior of monoio TcpStream. +//! Though it is not a good assumption, it can really make it +//! more efficient with less code. The read and write will not +//! interfere each other. +use std::{ + cell::UnsafeCell, + future::Future, + io::IoSlice, + ops::{Deref, DerefMut}, + pin::Pin, + rc::Rc, + task::{Context, Poll}, +}; + +use tokio::{ + pin, + io::{AsyncRead, AsyncWrite, ReadBuf} +}; + +use rustls_fork_shadow_tls::{ConnectionCommon, SideData}; + +use crate::stream::Stream; + +#[derive(Debug)] +pub struct ReadHalf { + pub(crate) inner: Rc>>, +} + +#[derive(Debug)] +pub struct WriteHalf { + pub(crate) inner: Rc>>, +} + +impl AsyncRead + for ReadHalf +where + C: DerefMut + Deref>, +{ + fn poll_read( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut ReadBuf<'_> + ) -> Poll> { + let inner = unsafe { &mut *self.inner.get() }; + let ex = inner.read_inner(buf, true); + pin!(ex); + ex.poll(cx) + } +} + +impl ReadHalf { + pub fn reunite(self, other: WriteHalf) -> Result, ReuniteError> { + reunite(self, other) + } +} + +impl AsyncWrite + for WriteHalf +where + C: DerefMut + Deref>, +{ + fn poll_write( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8] + ) -> Poll> { + let inner = unsafe { &mut *self.inner.get() }; + Pin::new(inner).poll_write(cx, buf) + } + + fn poll_write_vectored( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + bufs: &[IoSlice<'_>] + ) -> Poll> { + let inner = unsafe { &mut *self.inner.get() }; + Pin::new(inner).poll_write_vectored(cx, bufs) + } + + fn poll_flush( + self: Pin<&mut Self>, + cx: &mut Context<'_> + ) -> Poll> { + let inner = unsafe { &mut *self.inner.get() }; + Pin::new(inner).poll_flush(cx) + } + + fn poll_shutdown( + self: Pin<&mut Self>, + cx: &mut Context<'_> + ) -> Poll> { + let inner = unsafe { &mut *self.inner.get() }; + Pin::new(inner).poll_shutdown(cx) + } + + fn is_write_vectored(&self) -> bool { + let inner = unsafe { &mut *self.inner.get() }; + Pin::new(inner).is_write_vectored() + } +} + +impl WriteHalf { + pub fn reunite(self, other: ReadHalf) -> Result, ReuniteError> { + reunite(other, self) + } +} + +pub(crate) fn reunite( + read: ReadHalf, + write: WriteHalf, +) -> Result, ReuniteError> { + if Rc::ptr_eq(&read.inner, &write.inner) { + drop(write); + // This unwrap cannot fail as the api does not allow creating more than two Rcs, + // and we just dropped the other half. + Ok(Rc::try_unwrap(read.inner) + .expect("TlsStream: try_unwrap failed in reunite") + .into_inner()) + } else { + Err(ReuniteError(read, write)) + } +} + +/// Error indicating that two halves were not from the same socket, and thus could +/// not be reunited. +#[derive(Debug)] +pub struct ReuniteError(pub ReadHalf, pub WriteHalf); + +impl std::fmt::Display for ReuniteError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "tried to reunite halves that are not from the same socket" + ) + } +} + +impl std::error::Error for ReuniteError {} diff --git a/third_party/tokio-rustls-fork-shadow-tls/src/stream.rs b/third_party/tokio-rustls-fork-shadow-tls/src/stream.rs new file mode 100644 index 0000000..5dd0f49 --- /dev/null +++ b/third_party/tokio-rustls-fork-shadow-tls/src/stream.rs @@ -0,0 +1,297 @@ +use std::{ + cell::UnsafeCell, + future::Future, + io::{IoSlice, Read, self, Write}, + ops::{Deref, DerefMut}, + pin::Pin, + rc::Rc, + task::{Context, Poll}, +}; + +use tokio::{ + pin, + io::{AsyncRead, AsyncWrite, ReadBuf} +}; + +use rustls_fork_shadow_tls::{ConnectionCommon, SideData}; + +use crate::split::{ReadHalf, WriteHalf}; + +#[derive(Debug)] +pub struct Stream { + pub(crate) io: IO, + pub(crate) session: C, + #[cfg(not(feature = "unsafe_io"))] + r_buffer: crate::safe_io::SafeRead, + #[cfg(not(feature = "unsafe_io"))] + w_buffer: crate::safe_io::SafeWrite, + #[cfg(feature = "unsafe_io")] + r_buffer: crate::unsafe_io::UnsafeRead, + #[cfg(feature = "unsafe_io")] + w_buffer: crate::unsafe_io::UnsafeWrite, +} + +impl Stream { + pub fn new(io: IO, session: C) -> Self { + Self { + io, + session, + r_buffer: Default::default(), + w_buffer: Default::default(), + } + } + + pub fn split(self) -> (ReadHalf, WriteHalf) { + let shared = Rc::new(UnsafeCell::new(self)); + ( + ReadHalf { + inner: shared.clone(), + }, + WriteHalf { inner: shared }, + ) + } + + pub fn into_parts(self) -> (IO, C) { + (self.io, self.session) + } +} + +impl Stream +where + C: DerefMut + Deref>, +{ + pub(crate) async fn read_io(&mut self, splitted: bool) -> io::Result { + let n = loop { + match self.session.read_tls(&mut self.r_buffer) { + Ok(n) => { + break n; + } + Err(ref err) if err.kind() == io::ErrorKind::WouldBlock => (), + Err(err) => return Err(err), + } + #[allow(unused_unsafe)] + unsafe { + self.r_buffer.do_io(&mut self.io).await? + }; + }; + + let state = match self.session.process_new_packets() { + Ok(state) => state, + Err(err) => { + // When to write_io? If we do this in read call, the UnsafeWrite may crash + // when we impl split in an UnsafeCell way. + // Here we choose not to do write when read. + // User should manually shutdown it on error. + if !splitted { + let _ = self.write_io().await; + } + return Err(io::Error::new(io::ErrorKind::InvalidData, err)); + } + }; + + if state.peer_has_closed() && self.session.is_handshaking() { + return Err(io::Error::new( + io::ErrorKind::UnexpectedEof, + "tls handshake alert", + )); + } + + Ok(n) + } + + pub(crate) async fn write_io(&mut self) -> io::Result { + let n = loop { + match self.session.write_tls(&mut self.w_buffer) { + Ok(n) => { + break n; + } + Err(ref err) if err.kind() == io::ErrorKind::WouldBlock => (), + Err(err) => return Err(err), + } + #[allow(unused_unsafe)] + unsafe { + self.w_buffer.do_io(&mut self.io).await? + }; + }; + // Flush buffered data, only needed for safe_io. + #[cfg(not(feature = "unsafe_io"))] + self.w_buffer.do_io(&mut self.io).await?; + + Ok(n) + } + + pub(crate) async fn handshake(&mut self) -> io::Result<(usize, usize)> { + let mut wrlen = 0; + let mut rdlen = 0; + let mut eof = false; + + loop { + while self.session.wants_write() && self.session.is_handshaking() { + wrlen += self.write_io().await?; + } + while !eof && self.session.wants_read() && self.session.is_handshaking() { + let n = self.read_io(false).await?; + rdlen += n; + if n == 0 { + eof = true; + } + } + + match (eof, self.session.is_handshaking()) { + (true, true) => { + let err = io::Error::new(io::ErrorKind::UnexpectedEof, "tls handshake eof"); + return Err(err); + } + (false, true) => (), + (_, false) => { + break; + } + } + } + + // flush buffer + while self.session.wants_write() { + wrlen += self.write_io().await?; + } + + Ok((rdlen, wrlen)) + } + + pub(crate) async fn read_inner( + &mut self, + buf: &mut ReadBuf<'_>, + splitted: bool, + ) -> std::io::Result<()> { + if buf.remaining() == 0 { + return Ok(()); + } + let slice = buf.initialize_unfilled(); + loop { + // read from rustls to buffer + match self.session.reader().read(slice) { + Ok(n) => { + buf.advance(n); + return Ok(()); + } + // we need more data, read something. + Err(ref err) if err.kind() == io::ErrorKind::WouldBlock => (), + Err(e) => { + return Err(e); + } + } + + // now we need data, read something into rustls + match self.read_io(splitted).await { + Ok(0) => { + return + Err(io::Error::new( + io::ErrorKind::UnexpectedEof, + "tls raw stream eof", + ), + ); + } + Ok(_) => (), + Err(e) => { + return Err(e); + } + } + } + } +} + +impl AsyncRead for Stream +where + C: DerefMut + Deref> + Unpin, +{ + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut ReadBuf<'_> + ) -> Poll> { + let ex = self.read_inner(buf, false); + pin!(ex); + ex.poll(cx) + } +} + +impl AsyncWrite for Stream +where + C: DerefMut + Deref> + Unpin, +{ + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8] + ) -> Poll> { + // write buf to rustls + let n = match self.session.writer().write(buf) { + Ok(n) => n, + Err(e) => return Poll::Ready(Err(e)), + }; + + // write from rustls to connection + while self.session.wants_write() { + let ex = self.write_io(); + pin!(ex); + match ex.poll(cx) { + Poll::Ready(Ok(0)) => { + break; + } + Poll::Ready(Ok(_)) => (), + Poll::Pending => return Poll::Pending, + Poll::Ready(Err(e)) => return Poll::Ready(Err(e)), + } + } + Poll::Ready(Ok(n)) + } + + fn poll_write_vectored( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + bufs: &[IoSlice<'_>] + ) -> Poll> { + let buf = bufs + .iter() + .find(|b| !b.is_empty()) + .map_or(&[][..], |b| &**b); + self.poll_write(cx, buf) + } + + fn poll_flush( + mut self: Pin<&mut Self>, + cx: &mut Context<'_> + ) -> Poll> { + self.session.writer().flush()?; + while self.session.wants_write() { + let ex = self.write_io(); + pin!(ex); + match ex.poll(cx) { + Poll::Ready(Ok(_)) => (), + Poll::Pending => return Poll::Pending, + Poll::Ready(Err(e)) => return Poll::Ready(Err(e)), + } + } + Pin::new(&mut self.io).poll_flush(cx) + } + + fn poll_shutdown( + mut self: Pin<&mut Self>, + cx: &mut Context<'_> + ) -> Poll> { + self.session.send_close_notify(); + while self.session.wants_write() { + let ex = self.write_io(); + pin!(ex); + match ex.poll(cx) { + Poll::Ready(Ok(_)) => (), + Poll::Pending => return Poll::Pending, + Poll::Ready(Err(e)) => return Poll::Ready(Err(e)), + } + } + Pin::new(&mut self.io).poll_shutdown(cx) + } + + fn is_write_vectored(&self) -> bool { + Pin::new(&self.io).is_write_vectored() + } +} diff --git a/third_party/tokio-rustls-fork-shadow-tls/src/unsafe_io.rs b/third_party/tokio-rustls-fork-shadow-tls/src/unsafe_io.rs new file mode 100644 index 0000000..df34aa4 --- /dev/null +++ b/third_party/tokio-rustls-fork-shadow-tls/src/unsafe_io.rs @@ -0,0 +1,124 @@ +use std::{ + io, + slice::{from_raw_parts, from_raw_parts_mut} +}; + +use tokio::{ + io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt} +}; + +/// Used by both UnsafeRead and UnsafeWrite. +#[derive(Debug)] +enum Status { + /// We haven't do real io, and maybe the dest is recorded. + WaitFill(Option<(*const u8, usize)>), + /// We have already do real io. The length maybe zero or non-zero. + Filled(usize), +} + +impl Default for Status { + fn default() -> Self { + Status::WaitFill(None) + } +} + +/// UnsafeRead is a wrapper of some meta data. +/// It implements std::io::Read trait. But it do real io in an async way. +/// On the first read, it may returns WouldBlock error, which means the +/// `fullfill` should be called to do real io. +/// The data is read directly into the dest that last std read passes. +/// Note that this action is an unsafe hack to avoid data copy. +/// You can only use this wrapper when you make sure the read dest is always +/// a valid buffer. +#[derive(Default, Debug)] +pub(crate) struct UnsafeRead { + status: Status, +} + +impl UnsafeRead { + /// `do_io` must be called after calling to io::Read::read. + pub(crate) async unsafe fn do_io( + &mut self, + mut io: IO, + ) -> io::Result { + match self.status { + Status::WaitFill(Some((ptr, len))) => { + let buf = unsafe { from_raw_parts_mut(ptr as *mut u8, len) }; + let n = io.read(buf).await?; + self.status = Status::Filled(n); + Ok(n) + } + Status::Filled(len) => Ok(len), + Status::WaitFill(None) => Err(io::ErrorKind::WouldBlock.into()), + } + } +} + +impl io::Read for UnsafeRead { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + match self.status { + Status::WaitFill(_) => { + let ptr = buf.as_ptr(); + let len = buf.len(); + self.status = Status::WaitFill(Some((ptr, len))); + Err(io::ErrorKind::WouldBlock.into()) + } + Status::Filled(len) => { + if len != 0 { + // reset only when not eof + self.status = Status::WaitFill(None); + } + Ok(len) + } + } + } +} + +/// UnsafeWrite behaves like `UnsafeRead`. +#[derive(Default, Debug)] +pub(crate) struct UnsafeWrite { + status: Status, +} + +impl UnsafeWrite { + /// `do_io` must be called after calling to io::Write::write. + pub(crate) async unsafe fn do_io( + &mut self, + mut io: IO, + ) -> io::Result { + match self.status { + Status::WaitFill(Some((ptr, len))) => { + let buf = unsafe { from_raw_parts(ptr, len) }; + let n = io.write(buf).await?; + self.status = Status::Filled(n); + Ok(n) + } + Status::Filled(len) => Ok(len), + Status::WaitFill(None) => Err(io::ErrorKind::WouldBlock.into()), + } + } +} + +impl io::Write for UnsafeWrite { + fn write(&mut self, buf: &[u8]) -> io::Result { + match self.status { + Status::WaitFill(_) => { + let ptr = buf.as_ptr(); + let len = buf.len(); + self.status = Status::WaitFill(Some((ptr, len))); + Err(io::ErrorKind::WouldBlock.into()) + } + Status::Filled(len) => { + if len != 0 { + // reset only when not eof + self.status = Status::WaitFill(None); + } + Ok(len) + } + } + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +}