From d1638726ca40fabb528db23a315d11f46661b3cc Mon Sep 17 00:00:00 2001 From: JettChenT Date: Mon, 1 Jun 2026 15:23:17 +0800 Subject: [PATCH 1/4] Add proxy subscription runtime support Add daemon RPCs, Apple and GTK import flows, packet proxy runtime support, diagnostics, and BEPs for proxy subscription handling. Redact subscription URL secrets from fetch errors before they reach logs or UI surfaces. --- Apple/App/App-macOS.entitlements | 2 + Apple/App/App.xcconfig | 3 +- Apple/App/AppDelegate.swift | 111 +- Apple/App/MainMenu.xib | 5 +- Apple/Burrow.xcodeproj/project.pbxproj | 247 +- Apple/Configuration/Constants/Constants.swift | 7 + Apple/Configuration/Identity.xcconfig | 4 +- Apple/Core/Client.swift | 356 ++ Apple/Core/Client/Generated/burrow.pb.swift | 18 + .../NetworkExtension.xcconfig | 2 + .../PacketTunnelProvider.swift | 35 +- .../NetworkExtension/libburrow/build-rust.sh | 10 +- Apple/UI/BurrowView.swift | 746 ++- Apple/UI/NetworkCarouselView.swift | 47 +- Apple/UI/NetworkExtensionTunnel.swift | 35 +- Apple/UI/Networks/Network.swift | 478 +- Cargo.lock | 108 +- Scripts/burrow-doctor | 434 ++ Scripts/burrow-proxy-ladder | 576 +++ Scripts/burrow-proxy-selftest | 272 + burrow-gtk/Cargo.lock | 4559 ++++++++++++++++- burrow-gtk/src/components/home_screen.rs | 268 +- burrow-gtk/src/daemon_api.rs | 101 + burrow/Cargo.toml | 14 + burrow/src/auth/server/mod.rs | 17 +- burrow/src/auth/server/tailscale.rs | 14 +- burrow/src/control/discovery.rs | 27 +- burrow/src/daemon/apple.rs | 32 + burrow/src/daemon/instance.rs | 295 +- burrow/src/daemon/mod.rs | 41 +- burrow/src/daemon/rpc/client.rs | 7 +- burrow/src/daemon/rpc/response.rs | 4 + ...c__response__response_serialization-4.snap | 2 +- burrow/src/daemon/runtime.rs | 179 +- burrow/src/database.rs | 268 +- burrow/src/lib.rs | 3 + burrow/src/main.rs | 887 ++++ burrow/src/proxy_runtime.rs | 3110 +++++++++++ burrow/src/proxy_subscription.rs | 927 ++++ burrow/src/tracing.rs | 102 +- docs/trojan-mihomo-discrepancies.md | 601 +++ .../BEP-0005-daemon-ipc-and-apple-boundary.md | 7 +- ...09-trojan-proxy-and-subscription-import.md | 131 + ...subscriptions-as-packet-tunnel-networks.md | 271 + ...P-0011-mihomo-compatible-trojan-runtime.md | 110 + proto/burrow.proto | 62 + 46 files changed, 15079 insertions(+), 456 deletions(-) create mode 100755 Scripts/burrow-doctor create mode 100755 Scripts/burrow-proxy-ladder create mode 100755 Scripts/burrow-proxy-selftest create mode 100644 burrow/src/proxy_runtime.rs create mode 100644 burrow/src/proxy_subscription.rs create mode 100644 docs/trojan-mihomo-discrepancies.md create mode 100644 evolution/proposals/BEP-0009-trojan-proxy-and-subscription-import.md create mode 100644 evolution/proposals/BEP-0010-proxy-subscriptions-as-packet-tunnel-networks.md create mode 100644 evolution/proposals/BEP-0011-mihomo-compatible-trojan-runtime.md diff --git a/Apple/App/App-macOS.entitlements b/Apple/App/App-macOS.entitlements index 53fcbb7..595fe08 100644 --- a/Apple/App/App-macOS.entitlements +++ b/Apple/App/App-macOS.entitlements @@ -15,5 +15,7 @@ $(APP_GROUP_IDENTIFIER) + com.apple.security.network.client + diff --git a/Apple/App/App.xcconfig b/Apple/App/App.xcconfig index 4e42ddc..bfdd25d 100644 --- a/Apple/App/App.xcconfig +++ b/Apple/App/App.xcconfig @@ -15,9 +15,10 @@ EXCLUDED_SOURCE_FILE_NAMES = MainMenu.xib EXCLUDED_SOURCE_FILE_NAMES[sdk=macosx*] = INFOPLIST_KEY_LSUIElement[sdk=macosx*] = YES -INFOPLIST_KEY_NSMainNibFile[sdk=macosx*] = MainMenu INFOPLIST_KEY_NSPrincipalClass[sdk=macosx*] = NSApplication INFOPLIST_KEY_LSApplicationCategoryType[sdk=macosx*] = public.app-category.utilities CODE_SIGN_ENTITLEMENTS = App/App-iOS.entitlements CODE_SIGN_ENTITLEMENTS[sdk=macosx*] = App/App-macOS.entitlements + +SWIFT_INCLUDE_PATHS = $(inherited) $(PROJECT_DIR)/NetworkExtension diff --git a/Apple/App/AppDelegate.swift b/Apple/App/AppDelegate.swift index c3cb4cb..a322943 100644 --- a/Apple/App/AppDelegate.swift +++ b/Apple/App/AppDelegate.swift @@ -1,23 +1,17 @@ #if os(macOS) import AppKit +import BurrowConfiguration +import BurrowCore import BurrowUI import SwiftUI +import libburrow -@main @MainActor class AppDelegate: NSObject, NSApplicationDelegate { private var windowController: NSWindowController? + private let logger = Logger.logger(for: AppDelegate.self) - private let quitItem: NSMenuItem = { - let quitItem = NSMenuItem( - title: "Quit Burrow", - action: #selector(NSApplication.terminate(_:)), - keyEquivalent: "q" - ) - quitItem.target = NSApplication.shared - quitItem.keyEquivalentModifierMask = .command - return quitItem - }() + private let quitItem = AppDelegate.makeQuitItem() private lazy var openItem: NSMenuItem = { let item = NSMenuItem( @@ -61,9 +55,85 @@ class AppDelegate: NSObject, NSApplicationDelegate { }() func applicationDidFinishLaunching(_ notification: Notification) { + installMainMenu() + startDaemon() statusItem.menu = menu } + private func startDaemon() { + do { + libburrow.spawnInProcess( + socketPath: try Constants.socketURL.path(percentEncoded: false), + databasePath: try Constants.databaseURL.path(percentEncoded: false) + ) + } catch { + logger.error("Failed to spawn daemon thread: \(error)") + } + } + + private func installMainMenu() { + let mainMenu = NSMenu(title: "Burrow") + + let appItem = NSMenuItem(title: "Burrow", action: nil, keyEquivalent: "") + let appMenu = NSMenu(title: "Burrow") + + let aboutItem = NSMenuItem( + title: "About Burrow", + action: #selector(NSApplication.orderFrontStandardAboutPanel(_:)), + keyEquivalent: "" + ) + aboutItem.target = NSApplication.shared + appMenu.addItem(aboutItem) + appMenu.addItem(.separator()) + appMenu.addItem(Self.makeQuitItem()) + + appItem.submenu = appMenu + mainMenu.addItem(appItem) + + let editItem = NSMenuItem(title: "Edit", action: nil, keyEquivalent: "") + editItem.submenu = makeEditMenu() + mainMenu.addItem(editItem) + + NSApplication.shared.mainMenu = mainMenu + } + + private static func makeQuitItem() -> NSMenuItem { + let quitItem = NSMenuItem( + title: "Quit Burrow", + action: #selector(NSApplication.terminate(_:)), + keyEquivalent: "q" + ) + quitItem.target = NSApplication.shared + quitItem.keyEquivalentModifierMask = .command + return quitItem + } + + private func makeEditMenu() -> NSMenu { + let editMenu = NSMenu(title: "Edit") + + editMenu.addItem(NSMenuItem(title: "Undo", action: Selector(("undo:")), keyEquivalent: "z")) + editMenu.addItem(NSMenuItem(title: "Redo", action: Selector(("redo:")), keyEquivalent: "Z")) + editMenu.addItem(.separator()) + + editMenu.addItem(NSMenuItem(title: "Cut", action: #selector(NSText.cut(_:)), keyEquivalent: "x")) + editMenu.addItem(NSMenuItem(title: "Copy", action: #selector(NSText.copy(_:)), keyEquivalent: "c")) + editMenu.addItem(NSMenuItem(title: "Paste", action: #selector(NSText.paste(_:)), keyEquivalent: "v")) + + let pastePlainItem = NSMenuItem( + title: "Paste and Match Style", + action: #selector(NSTextView.pasteAsPlainText(_:)), + keyEquivalent: "V" + ) + pastePlainItem.keyEquivalentModifierMask = [.command, .option, .shift] + editMenu.addItem(pastePlainItem) + + editMenu.addItem(NSMenuItem(title: "Delete", action: #selector(NSText.delete(_:)), keyEquivalent: "")) + editMenu.addItem(.separator()) + editMenu.addItem(NSMenuItem(title: "Select All", action: #selector(NSText.selectAll(_:)), keyEquivalent: "a")) + + return editMenu + } + @objc private func openWindow() { if let window = windowController?.window { @@ -74,9 +144,13 @@ class AppDelegate: NSObject, NSApplicationDelegate { let contentView = BurrowView() let hostingController = NSHostingController(rootView: contentView) + if #available(macOS 13.0, *) { + hostingController.sizingOptions = [] + } let window = NSWindow(contentViewController: hostingController) window.title = "Burrow" window.setContentSize(NSSize(width: 820, height: 720)) + window.contentMinSize = NSSize(width: 640, height: 560) window.styleMask.insert([.titled, .closable, .miniaturizable, .resizable]) window.center() @@ -87,4 +161,19 @@ class AppDelegate: NSObject, NSApplicationDelegate { NSApplication.shared.activate(ignoringOtherApps: true) } } + +@main +enum BurrowApplication { + @MainActor + private static let delegate = AppDelegate() + + @MainActor + static func main() { + let application = NSApplication.shared + application.delegate = delegate + application.setActivationPolicy(.accessory) + application.finishLaunching() + application.run() + } +} #endif diff --git a/Apple/App/MainMenu.xib b/Apple/App/MainMenu.xib index 50ba431..5c7bc1e 100644 --- a/Apple/App/MainMenu.xib +++ b/Apple/App/MainMenu.xib @@ -1,7 +1,8 @@ - + - + + diff --git a/Apple/Burrow.xcodeproj/project.pbxproj b/Apple/Burrow.xcodeproj/project.pbxproj index 83d32e0..6c64803 100644 --- a/Apple/Burrow.xcodeproj/project.pbxproj +++ b/Apple/Burrow.xcodeproj/project.pbxproj @@ -8,7 +8,6 @@ /* Begin PBXBuildFile section */ D00AA8972A4669BC005C8102 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00AA8962A4669BC005C8102 /* AppDelegate.swift */; }; - D11000012F70000100112233 /* BurrowUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D11000042F70000100112233 /* BurrowUITests.swift */; }; D020F65829E4A697002790F6 /* PacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = D020F65729E4A697002790F6 /* PacketTunnelProvider.swift */; }; D020F65D29E4A697002790F6 /* BurrowNetworkExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = D020F65329E4A697002790F6 /* BurrowNetworkExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; D03383AD2C8E67E300F7C44E /* SwiftProtobuf in Frameworks */ = {isa = PBXBuildFile; productRef = D078F7E22C8DA375008A8CEC /* SwiftProtobuf */; }; @@ -19,6 +18,7 @@ D09150422B9D2AF700BE3CB0 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = D09150412B9D2AF700BE3CB0 /* MainMenu.xib */; platformFilters = (macos, ); }; D0B1D1102C436152004B7823 /* AsyncAlgorithms in Frameworks */ = {isa = PBXBuildFile; productRef = D0B1D10F2C436152004B7823 /* AsyncAlgorithms */; }; D0BCC6092A09A03E00AD070D /* libburrow.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D0BCC6032A09535900AD070D /* libburrow.a */; }; + D0BCC60C2A09A0C200AD070D /* libburrow.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D0BCC6032A09535900AD070D /* libburrow.a */; }; D0BF09522C8E66F6000D8DEC /* BurrowConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0D4E5622C8D9BF4007F820A /* BurrowConfiguration.framework */; }; D0BF09552C8E66FD000D8DEC /* BurrowConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0D4E5622C8D9BF4007F820A /* BurrowConfiguration.framework */; }; D0D4E53A2C8D996F007F820A /* BurrowCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D0D4E5312C8D996F007F820A /* BurrowCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -43,20 +43,14 @@ D0D4E5A62C8D9E65007F820A /* BurrowCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0D4E5312C8D996F007F820A /* BurrowCore.framework */; }; D0F4FAD32C8DC79C0068730A /* BurrowCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0D4E5312C8D996F007F820A /* BurrowCore.framework */; }; D0F7594E2C8DAB6B00126CF3 /* GRPC in Frameworks */ = {isa = PBXBuildFile; productRef = D078F7E02C8DA375008A8CEC /* GRPC */; }; - D0FA10012D10200100112233 /* burrow.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FA10032D10200100112233 /* burrow.pb.swift */; }; - D0FA10022D10200100112233 /* burrow.grpc.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FA10042D10200100112233 /* burrow.grpc.swift */; }; D0F7597E2C8DB30500126CF3 /* CGRPCZlib in Frameworks */ = {isa = PBXBuildFile; productRef = D0F7597D2C8DB30500126CF3 /* CGRPCZlib */; }; D0F7598D2C8DB3DA00126CF3 /* Client.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D4E4992C8D921A007F820A /* Client.swift */; }; + D0FA10012D10200100112233 /* Generated/burrow.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FA10032D10200100112233 /* Generated/burrow.pb.swift */; }; + D0FA10022D10200100112233 /* Generated/burrow.grpc.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FA10042D10200100112233 /* Generated/burrow.grpc.swift */; }; + D11000012F70000100112233 /* BurrowUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D11000042F70000100112233 /* BurrowUITests.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ - D11000022F70000100112233 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = D05B9F6A29E39EEC008CB1F9 /* Project object */; - proxyType = 1; - remoteGlobalIDString = D05B9F7129E39EEC008CB1F9; - remoteInfo = App; - }; D020F65B29E4A697002790F6 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = D05B9F6A29E39EEC008CB1F9 /* Project object */; @@ -106,6 +100,13 @@ remoteGlobalIDString = D0D4E5302C8D996F007F820A; remoteInfo = Core; }; + D11000022F70000100112233 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D05B9F6A29E39EEC008CB1F9 /* Project object */; + proxyType = 1; + remoteGlobalIDString = D05B9F7129E39EEC008CB1F9; + remoteInfo = App; + }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -138,9 +139,6 @@ /* Begin PBXFileReference section */ D00117422B30348D00D87C25 /* Configuration.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Configuration.xcconfig; sourceTree = ""; }; D00AA8962A4669BC005C8102 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - D11000032F70000100112233 /* BurrowUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BurrowUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - D11000042F70000100112233 /* BurrowUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BurrowUITests.swift; sourceTree = ""; }; - D11000052F70000100112233 /* UITests.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = UITests.xcconfig; sourceTree = ""; }; D020F63D29E4A1FF002790F6 /* Identity.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Identity.xcconfig; sourceTree = ""; }; D020F64029E4A1FF002790F6 /* Compiler.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Compiler.xcconfig; sourceTree = ""; }; D020F64229E4A1FF002790F6 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -188,18 +186,14 @@ D0D4E58E2C8D9D0A007F820A /* Constants.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Constants.h; sourceTree = ""; }; D0D4E58F2C8D9D0A007F820A /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; D0D4E5902C8D9D0A007F820A /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; - D0FA10032D10200100112233 /* burrow.pb.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Generated/burrow.pb.swift; sourceTree = ""; }; - D0FA10042D10200100112233 /* burrow.grpc.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Generated/burrow.grpc.swift; sourceTree = ""; }; + D0FA10032D10200100112233 /* Generated/burrow.pb.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Generated/burrow.pb.swift; sourceTree = ""; }; + D0FA10042D10200100112233 /* Generated/burrow.grpc.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Generated/burrow.grpc.swift; sourceTree = ""; }; + D11000032F70000100112233 /* BurrowUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BurrowUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + D11000042F70000100112233 /* BurrowUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BurrowUITests.swift; sourceTree = ""; }; + D11000052F70000100112233 /* UITests.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = UITests.xcconfig; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ - D11000062F70000100112233 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; D020F65029E4A697002790F6 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -218,6 +212,7 @@ D0BF09552C8E66FD000D8DEC /* BurrowConfiguration.framework in Frameworks */, D0F4FAD32C8DC79C0068730A /* BurrowCore.framework in Frameworks */, D0D4E5892C8D9C94007F820A /* BurrowUI.framework in Frameworks */, + D0BCC60C2A09A0C200AD070D /* libburrow.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -242,6 +237,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + D11000062F70000100112233 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -324,14 +326,6 @@ path = App; sourceTree = ""; }; - D11000072F70000100112233 /* AppUITests */ = { - isa = PBXGroup; - children = ( - D11000042F70000100112233 /* BurrowUITests.swift */, - ); - path = AppUITests; - sourceTree = ""; - }; D0B98FD729FDDB57004E7149 /* libburrow */ = { isa = PBXGroup; children = ( @@ -346,8 +340,8 @@ isa = PBXGroup; children = ( D0D4E4952C8D921A007F820A /* burrow.proto */, - D0FA10032D10200100112233 /* burrow.pb.swift */, - D0FA10042D10200100112233 /* burrow.grpc.swift */, + D0FA10032D10200100112233 /* Generated/burrow.pb.swift */, + D0FA10042D10200100112233 /* Generated/burrow.grpc.swift */, ); path = Client; sourceTree = ""; @@ -401,27 +395,17 @@ path = Constants; sourceTree = ""; }; + D11000072F70000100112233 /* AppUITests */ = { + isa = PBXGroup; + children = ( + D11000042F70000100112233 /* BurrowUITests.swift */, + ); + path = AppUITests; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ - D11000082F70000100112233 /* BurrowUITests */ = { - isa = PBXNativeTarget; - buildConfigurationList = D110000E2F70000100112233 /* Build configuration list for PBXNativeTarget "BurrowUITests" */; - buildPhases = ( - D110000A2F70000100112233 /* Sources */, - D11000062F70000100112233 /* Frameworks */, - D11000092F70000100112233 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - D110000B2F70000100112233 /* PBXTargetDependency */, - ); - name = BurrowUITests; - productName = BurrowUITests; - productReference = D11000032F70000100112233 /* BurrowUITests.xctest */; - productType = "com.apple.product-type.bundle.ui-testing"; - }; D020F65229E4A697002790F6 /* NetworkExtension */ = { isa = PBXNativeTarget; buildConfigurationList = D020F65E29E4A697002790F6 /* Build configuration list for PBXNativeTarget "NetworkExtension" */; @@ -527,6 +511,24 @@ productReference = D0D4E5622C8D9BF4007F820A /* BurrowConfiguration.framework */; productType = "com.apple.product-type.framework"; }; + D11000082F70000100112233 /* BurrowUITests */ = { + isa = PBXNativeTarget; + buildConfigurationList = D110000E2F70000100112233 /* Build configuration list for PBXNativeTarget "BurrowUITests" */; + buildPhases = ( + D110000A2F70000100112233 /* Sources */, + D11000062F70000100112233 /* Frameworks */, + D11000092F70000100112233 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + D110000B2F70000100112233 /* PBXTargetDependency */, + ); + name = BurrowUITests; + productName = BurrowUITests; + productReference = D11000032F70000100112233 /* BurrowUITests.xctest */; + productType = "com.apple.product-type.bundle.ui-testing"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -537,19 +539,54 @@ LastSwiftUpdateCheck = 1600; LastUpgradeCheck = 1520; TargetAttributes = { - D11000082F70000100112233 = { - CreatedOnToolsVersion = 16.0; - TestTargetID = D05B9F7129E39EEC008CB1F9; - }; D020F65229E4A697002790F6 = { CreatedOnToolsVersion = 14.3; + DevelopmentTeam = 2KPBQHRJ2D; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.ApplicationGroups.Mac = { + enabled = 1; + }; + com.apple.NetworkExtensions = { + enabled = 1; + }; + com.apple.Sandbox = { + enabled = 1; + }; + }; }; D05B9F7129E39EEC008CB1F9 = { CreatedOnToolsVersion = 14.3; + DevelopmentTeam = 2KPBQHRJ2D; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.ApplicationGroups.Mac = { + enabled = 1; + }; + com.apple.ApplicationGroups.iOS = { + enabled = 1; + }; + com.apple.AssociatedDomains = { + enabled = 1; + }; + com.apple.NetworkExtensions = { + enabled = 1; + }; + com.apple.NetworkExtensions.iOS = { + enabled = 1; + }; + com.apple.Sandbox = { + enabled = 1; + }; + }; }; D0D4E5302C8D996F007F820A = { CreatedOnToolsVersion = 16.0; }; + D11000082F70000100112233 = { + CreatedOnToolsVersion = 16.0; + TestTargetID = D05B9F7129E39EEC008CB1F9; + }; }; }; buildConfigurationList = D05B9F6D29E39EEC008CB1F9 /* Build configuration list for PBXProject "Burrow" */; @@ -583,13 +620,6 @@ /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ - D11000092F70000100112233 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; D05B9F7029E39EEC008CB1F9 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -605,6 +635,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + D11000092F70000100112233 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ @@ -653,14 +690,6 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ - D110000A2F70000100112233 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - D11000012F70000100112233 /* BurrowUITests.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; D020F64F29E4A697002790F6 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -682,8 +711,8 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - D0FA10012D10200100112233 /* burrow.pb.swift in Sources */, - D0FA10022D10200100112233 /* burrow.grpc.swift in Sources */, + D0FA10012D10200100112233 /* Generated/burrow.pb.swift in Sources */, + D0FA10022D10200100112233 /* Generated/burrow.grpc.swift in Sources */, D0F7598D2C8DB3DA00126CF3 /* Client.swift in Sources */, D0D4E56B2C8D9C2F007F820A /* Logging.swift in Sources */, ); @@ -716,14 +745,17 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + D110000A2F70000100112233 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D11000012F70000100112233 /* BurrowUITests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ - D110000B2F70000100112233 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = D05B9F7129E39EEC008CB1F9 /* App */; - targetProxy = D11000022F70000100112233 /* PBXContainerItemProxy */; - }; D020F65C29E4A697002790F6 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = D020F65229E4A697002790F6 /* NetworkExtension */; @@ -763,27 +795,19 @@ isa = PBXTargetDependency; productRef = D0F759892C8DB34200126CF3 /* GRPC */; }; + D110000B2F70000100112233 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = D05B9F7129E39EEC008CB1F9 /* App */; + targetProxy = D11000022F70000100112233 /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ - D110000C2F70000100112233 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = D11000052F70000100112233 /* UITests.xcconfig */; - buildSettings = { - }; - name = Debug; - }; - D110000D2F70000100112233 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = D11000052F70000100112233 /* UITests.xcconfig */; - buildSettings = { - }; - name = Release; - }; D020F65F29E4A697002790F6 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = D020F66229E4A6E5002790F6 /* NetworkExtension.xcconfig */; buildSettings = { + DEVELOPMENT_TEAM = 2KPBQHRJ2D; }; name = Debug; }; @@ -791,6 +815,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = D020F66229E4A6E5002790F6 /* NetworkExtension.xcconfig */; buildSettings = { + DEVELOPMENT_TEAM = 2KPBQHRJ2D; }; name = Release; }; @@ -798,6 +823,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = D020F64029E4A1FF002790F6 /* Compiler.xcconfig */; buildSettings = { + DEVELOPMENT_TEAM = 2KPBQHRJ2D; }; name = Debug; }; @@ -805,6 +831,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = D020F64029E4A1FF002790F6 /* Compiler.xcconfig */; buildSettings = { + DEVELOPMENT_TEAM = 2KPBQHRJ2D; }; name = Release; }; @@ -812,6 +839,10 @@ isa = XCBuildConfiguration; baseConfigurationReference = D020F64929E4A34B002790F6 /* App.xcconfig */; buildSettings = { + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 2KPBQHRJ2D; + PROVISIONING_PROFILE_SPECIFIER = ""; }; name = Debug; }; @@ -819,6 +850,10 @@ isa = XCBuildConfiguration; baseConfigurationReference = D020F64929E4A34B002790F6 /* App.xcconfig */; buildSettings = { + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 2KPBQHRJ2D; + PROVISIONING_PROFILE_SPECIFIER = ""; }; name = Release; }; @@ -864,18 +899,23 @@ }; name = Release; }; + D110000C2F70000100112233 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D11000052F70000100112233 /* UITests.xcconfig */; + buildSettings = { + }; + name = Debug; + }; + D110000D2F70000100112233 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D11000052F70000100112233 /* UITests.xcconfig */; + buildSettings = { + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - D110000E2F70000100112233 /* Build configuration list for PBXNativeTarget "BurrowUITests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - D110000C2F70000100112233 /* Debug */, - D110000D2F70000100112233 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; D020F65E29E4A697002790F6 /* Build configuration list for PBXNativeTarget "NetworkExtension" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -930,6 +970,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + D110000E2F70000100112233 /* Build configuration list for PBXNativeTarget "BurrowUITests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D110000C2F70000100112233 /* Debug */, + D110000D2F70000100112233 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ diff --git a/Apple/Configuration/Constants/Constants.swift b/Apple/Configuration/Constants/Constants.swift index 95d8c78..648ded2 100644 --- a/Apple/Configuration/Constants/Constants.swift +++ b/Apple/Configuration/Constants/Constants.swift @@ -16,6 +16,13 @@ public enum Constants { try groupContainerURL.appending(component: "burrow.sock", directoryHint: .notDirectory) } } + + public static var packetTunnelSocketURL: URL { + get throws { + try groupContainerURL.appending(component: "burrow-packet-tunnel.sock", directoryHint: .notDirectory) + } + } + public static var databaseURL: URL { get throws { try groupContainerURL.appending(component: "burrow.db", directoryHint: .notDirectory) diff --git a/Apple/Configuration/Identity.xcconfig b/Apple/Configuration/Identity.xcconfig index e9ca78d..7318401 100644 --- a/Apple/Configuration/Identity.xcconfig +++ b/Apple/Configuration/Identity.xcconfig @@ -1,2 +1,2 @@ -DEVELOPMENT_TEAM = P6PV2R9443 -APP_BUNDLE_IDENTIFIER = com.hackclub.burrow +DEVELOPMENT_TEAM = 2KPBQHRJ2D +APP_BUNDLE_IDENTIFIER = com.tianruichen.burrow diff --git a/Apple/Core/Client.swift b/Apple/Core/Client.swift index 7d4cfc7..559238f 100644 --- a/Apple/Core/Client.swift +++ b/Apple/Core/Client.swift @@ -108,6 +108,83 @@ public struct Burrow_TailnetLoginStatusResponse: Sendable { public init() {} } +public struct Burrow_ProxySubscriptionImportRequest: Sendable { + public var url: String = "" + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} +} + +public struct Burrow_ProxySubscriptionNodePreview: Sendable { + public var ordinal: Int32 = 0 + public var name: String = "" + public var `protocol`: String = "" + public var server: String = "" + public var port: Int32 = 0 + public var warnings: [String] = [] + public var runtimeSupported: Bool = false + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} +} + +public struct Burrow_ProxySubscriptionRejectedEntry: Sendable { + public var ordinal: Int32 = 0 + public var reason: String = "" + public var scheme: String = "" + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} +} + +public struct Burrow_ProxySubscriptionPreviewResponse: Sendable { + public var suggestedName: String = "" + public var detectedFormat: String = "" + public var compatibleCount: Int32 = 0 + public var rejectedCount: Int32 = 0 + public var node: [Burrow_ProxySubscriptionNodePreview] = [] + public var rejected: [Burrow_ProxySubscriptionRejectedEntry] = [] + public var warnings: [String] = [] + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} +} + +public struct Burrow_ProxySubscriptionApplyRequest: Sendable { + public var id: Int32 = 0 + public var url: String = "" + public var name: String = "" + public var selectedOrdinal: Int32 = 0 + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} +} + +public struct Burrow_ProxySubscriptionApplyResponse: Sendable { + public var id: Int32 = 0 + public var importedCount: Int32 = 0 + public var selectedOrdinal: Int32 = 0 + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} +} + +public struct Burrow_ProxySubscriptionSelectRequest: Sendable { + public var id: Int32 = 0 + public var selectedOrdinal: Int32 = 0 + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} +} + +public struct Burrow_ProxySubscriptionSelectResponse: Sendable { + public var id: Int32 = 0 + public var selectedOrdinal: Int32 = 0 + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} +} + public struct Burrow_TunnelPacket: Sendable { public var payload = Data() public var unknownFields = SwiftProtobuf.UnknownStorage() @@ -417,6 +494,239 @@ extension Burrow_TunnelPacket: SwiftProtobuf.Message, SwiftProtobuf._MessageImpl } } +extension Burrow_ProxySubscriptionImportRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = "burrow.ProxySubscriptionImportRequest" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "url") + ] + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + switch fieldNumber { + case 1: try decoder.decodeSingularStringField(value: &self.url) + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if !self.url.isEmpty { + try visitor.visitSingularStringField(value: self.url, fieldNumber: 1) + } + try unknownFields.traverse(visitor: &visitor) + } +} + +extension Burrow_ProxySubscriptionNodePreview: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = "burrow.ProxySubscriptionNodePreview" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "ordinal"), + 2: .same(proto: "name"), + 3: .same(proto: "protocol"), + 4: .same(proto: "server"), + 5: .same(proto: "port"), + 6: .same(proto: "warnings"), + 7: .standard(proto: "runtime_supported"), + ] + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + switch fieldNumber { + case 1: try decoder.decodeSingularInt32Field(value: &self.ordinal) + case 2: try decoder.decodeSingularStringField(value: &self.name) + case 3: try decoder.decodeSingularStringField(value: &self.`protocol`) + case 4: try decoder.decodeSingularStringField(value: &self.server) + case 5: try decoder.decodeSingularInt32Field(value: &self.port) + case 6: try decoder.decodeRepeatedStringField(value: &self.warnings) + case 7: try decoder.decodeSingularBoolField(value: &self.runtimeSupported) + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if self.ordinal != 0 { try visitor.visitSingularInt32Field(value: self.ordinal, fieldNumber: 1) } + if !self.name.isEmpty { try visitor.visitSingularStringField(value: self.name, fieldNumber: 2) } + if !self.`protocol`.isEmpty { try visitor.visitSingularStringField(value: self.`protocol`, fieldNumber: 3) } + if !self.server.isEmpty { try visitor.visitSingularStringField(value: self.server, fieldNumber: 4) } + if self.port != 0 { try visitor.visitSingularInt32Field(value: self.port, fieldNumber: 5) } + if !self.warnings.isEmpty { try visitor.visitRepeatedStringField(value: self.warnings, fieldNumber: 6) } + if self.runtimeSupported { try visitor.visitSingularBoolField(value: self.runtimeSupported, fieldNumber: 7) } + try unknownFields.traverse(visitor: &visitor) + } +} + +extension Burrow_ProxySubscriptionRejectedEntry: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = "burrow.ProxySubscriptionRejectedEntry" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "ordinal"), + 2: .same(proto: "reason"), + 3: .same(proto: "scheme"), + ] + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + switch fieldNumber { + case 1: try decoder.decodeSingularInt32Field(value: &self.ordinal) + case 2: try decoder.decodeSingularStringField(value: &self.reason) + case 3: try decoder.decodeSingularStringField(value: &self.scheme) + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if self.ordinal != 0 { try visitor.visitSingularInt32Field(value: self.ordinal, fieldNumber: 1) } + if !self.reason.isEmpty { try visitor.visitSingularStringField(value: self.reason, fieldNumber: 2) } + if !self.scheme.isEmpty { try visitor.visitSingularStringField(value: self.scheme, fieldNumber: 3) } + try unknownFields.traverse(visitor: &visitor) + } +} + +extension Burrow_ProxySubscriptionPreviewResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = "burrow.ProxySubscriptionPreviewResponse" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .standard(proto: "suggested_name"), + 2: .standard(proto: "detected_format"), + 3: .standard(proto: "compatible_count"), + 4: .standard(proto: "rejected_count"), + 5: .same(proto: "node"), + 6: .same(proto: "rejected"), + 7: .same(proto: "warnings"), + ] + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + switch fieldNumber { + case 1: try decoder.decodeSingularStringField(value: &self.suggestedName) + case 2: try decoder.decodeSingularStringField(value: &self.detectedFormat) + case 3: try decoder.decodeSingularInt32Field(value: &self.compatibleCount) + case 4: try decoder.decodeSingularInt32Field(value: &self.rejectedCount) + case 5: try decoder.decodeRepeatedMessageField(value: &self.node) + case 6: try decoder.decodeRepeatedMessageField(value: &self.rejected) + case 7: try decoder.decodeRepeatedStringField(value: &self.warnings) + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if !self.suggestedName.isEmpty { try visitor.visitSingularStringField(value: self.suggestedName, fieldNumber: 1) } + if !self.detectedFormat.isEmpty { try visitor.visitSingularStringField(value: self.detectedFormat, fieldNumber: 2) } + if self.compatibleCount != 0 { try visitor.visitSingularInt32Field(value: self.compatibleCount, fieldNumber: 3) } + if self.rejectedCount != 0 { try visitor.visitSingularInt32Field(value: self.rejectedCount, fieldNumber: 4) } + if !self.node.isEmpty { try visitor.visitRepeatedMessageField(value: self.node, fieldNumber: 5) } + if !self.rejected.isEmpty { try visitor.visitRepeatedMessageField(value: self.rejected, fieldNumber: 6) } + if !self.warnings.isEmpty { try visitor.visitRepeatedStringField(value: self.warnings, fieldNumber: 7) } + try unknownFields.traverse(visitor: &visitor) + } +} + +extension Burrow_ProxySubscriptionApplyRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = "burrow.ProxySubscriptionApplyRequest" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "id"), + 2: .same(proto: "url"), + 3: .same(proto: "name"), + 4: .standard(proto: "selected_ordinal"), + ] + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + switch fieldNumber { + case 1: try decoder.decodeSingularInt32Field(value: &self.id) + case 2: try decoder.decodeSingularStringField(value: &self.url) + case 3: try decoder.decodeSingularStringField(value: &self.name) + case 4: try decoder.decodeSingularInt32Field(value: &self.selectedOrdinal) + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if self.id != 0 { try visitor.visitSingularInt32Field(value: self.id, fieldNumber: 1) } + if !self.url.isEmpty { try visitor.visitSingularStringField(value: self.url, fieldNumber: 2) } + if !self.name.isEmpty { try visitor.visitSingularStringField(value: self.name, fieldNumber: 3) } + if self.selectedOrdinal != 0 { try visitor.visitSingularInt32Field(value: self.selectedOrdinal, fieldNumber: 4) } + try unknownFields.traverse(visitor: &visitor) + } +} + +extension Burrow_ProxySubscriptionApplyResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = "burrow.ProxySubscriptionApplyResponse" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "id"), + 2: .standard(proto: "imported_count"), + 3: .standard(proto: "selected_ordinal"), + ] + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + switch fieldNumber { + case 1: try decoder.decodeSingularInt32Field(value: &self.id) + case 2: try decoder.decodeSingularInt32Field(value: &self.importedCount) + case 3: try decoder.decodeSingularInt32Field(value: &self.selectedOrdinal) + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if self.id != 0 { try visitor.visitSingularInt32Field(value: self.id, fieldNumber: 1) } + if self.importedCount != 0 { try visitor.visitSingularInt32Field(value: self.importedCount, fieldNumber: 2) } + if self.selectedOrdinal != 0 { try visitor.visitSingularInt32Field(value: self.selectedOrdinal, fieldNumber: 3) } + try unknownFields.traverse(visitor: &visitor) + } +} + +extension Burrow_ProxySubscriptionSelectRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = "burrow.ProxySubscriptionSelectRequest" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "id"), + 2: .standard(proto: "selected_ordinal"), + ] + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + switch fieldNumber { + case 1: try decoder.decodeSingularInt32Field(value: &self.id) + case 2: try decoder.decodeSingularInt32Field(value: &self.selectedOrdinal) + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if self.id != 0 { try visitor.visitSingularInt32Field(value: self.id, fieldNumber: 1) } + if self.selectedOrdinal != 0 { try visitor.visitSingularInt32Field(value: self.selectedOrdinal, fieldNumber: 2) } + try unknownFields.traverse(visitor: &visitor) + } +} + +extension Burrow_ProxySubscriptionSelectResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = "burrow.ProxySubscriptionSelectResponse" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "id"), + 2: .standard(proto: "selected_ordinal"), + ] + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + switch fieldNumber { + case 1: try decoder.decodeSingularInt32Field(value: &self.id) + case 2: try decoder.decodeSingularInt32Field(value: &self.selectedOrdinal) + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if self.id != 0 { try visitor.visitSingularInt32Field(value: self.id, fieldNumber: 1) } + if self.selectedOrdinal != 0 { try visitor.visitSingularInt32Field(value: self.selectedOrdinal, fieldNumber: 2) } + try unknownFields.traverse(visitor: &visitor) + } +} + public struct TailnetClient: Client, GRPCClient { public let channel: GRPCChannel public var defaultCallOptions: CallOptions @@ -487,6 +797,52 @@ public struct TailnetClient: Client, GRPCClient { } } +public struct ProxySubscriptionClient: Client, GRPCClient { + public let channel: GRPCChannel + public var defaultCallOptions: CallOptions + + public init(channel: any GRPCChannel) { + self.channel = channel + self.defaultCallOptions = .init() + } + + public func previewImport( + _ request: Burrow_ProxySubscriptionImportRequest, + callOptions: CallOptions? = nil + ) async throws -> Burrow_ProxySubscriptionPreviewResponse { + try await self.performAsyncUnaryCall( + path: "/burrow.ProxySubscriptions/PreviewImport", + request: request, + callOptions: callOptions ?? self.defaultCallOptions, + interceptors: [] + ) + } + + public func applyImport( + _ request: Burrow_ProxySubscriptionApplyRequest, + callOptions: CallOptions? = nil + ) async throws -> Burrow_ProxySubscriptionApplyResponse { + try await self.performAsyncUnaryCall( + path: "/burrow.ProxySubscriptions/ApplyImport", + request: request, + callOptions: callOptions ?? self.defaultCallOptions, + interceptors: [] + ) + } + + public func selectNode( + _ request: Burrow_ProxySubscriptionSelectRequest, + callOptions: CallOptions? = nil + ) async throws -> Burrow_ProxySubscriptionSelectResponse { + try await self.performAsyncUnaryCall( + path: "/burrow.ProxySubscriptions/SelectNode", + request: request, + callOptions: callOptions ?? self.defaultCallOptions, + interceptors: [] + ) + } +} + public struct TunnelPacketClient: Client, GRPCClient { public let channel: GRPCChannel public var defaultCallOptions: CallOptions diff --git a/Apple/Core/Client/Generated/burrow.pb.swift b/Apple/Core/Client/Generated/burrow.pb.swift index fccd769..df6603b 100644 --- a/Apple/Core/Client/Generated/burrow.pb.swift +++ b/Apple/Core/Client/Generated/burrow.pb.swift @@ -25,6 +25,8 @@ public enum Burrow_NetworkType: SwiftProtobuf.Enum, Swift.CaseIterable { public typealias RawValue = Int case wireGuard // = 0 case tailnet // = 1 + case trojan // = 2 + case proxySubscription // = 3 case UNRECOGNIZED(Int) public init() { @@ -35,6 +37,8 @@ public enum Burrow_NetworkType: SwiftProtobuf.Enum, Swift.CaseIterable { switch rawValue { case 0: self = .wireGuard case 1: self = .tailnet + case 2: self = .trojan + case 3: self = .proxySubscription default: self = .UNRECOGNIZED(rawValue) } } @@ -43,6 +47,8 @@ public enum Burrow_NetworkType: SwiftProtobuf.Enum, Swift.CaseIterable { switch self { case .wireGuard: return 0 case .tailnet: return 1 + case .trojan: return 2 + case .proxySubscription: return 3 case .UNRECOGNIZED(let i): return i } } @@ -51,6 +57,8 @@ public enum Burrow_NetworkType: SwiftProtobuf.Enum, Swift.CaseIterable { public static let allCases: [Burrow_NetworkType] = [ .wireGuard, .tailnet, + .trojan, + .proxySubscription, ] } @@ -217,6 +225,8 @@ public struct Burrow_TunnelConfigurationResponse: Sendable { public var routes: [String] = [] + public var excludedRoutes: [String] = [] + public var dnsServers: [String] = [] public var searchDomains: [String] = [] @@ -236,6 +246,8 @@ extension Burrow_NetworkType: SwiftProtobuf._ProtoNameProviding { public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 0: .same(proto: "WireGuard"), 1: .same(proto: "Tailnet"), + 2: .same(proto: "Trojan"), + 3: .same(proto: "ProxySubscription"), ] } @@ -544,6 +556,7 @@ extension Burrow_TunnelConfigurationResponse: SwiftProtobuf.Message, SwiftProtob 4: .standard(proto: "dns_servers"), 5: .standard(proto: "search_domains"), 6: .standard(proto: "include_default_route"), + 7: .standard(proto: "excluded_routes"), ] public mutating func decodeMessage(decoder: inout D) throws { @@ -558,6 +571,7 @@ extension Burrow_TunnelConfigurationResponse: SwiftProtobuf.Message, SwiftProtob case 4: try { try decoder.decodeRepeatedStringField(value: &self.dnsServers) }() case 5: try { try decoder.decodeRepeatedStringField(value: &self.searchDomains) }() case 6: try { try decoder.decodeSingularBoolField(value: &self.includeDefaultRoute) }() + case 7: try { try decoder.decodeRepeatedStringField(value: &self.excludedRoutes) }() default: break } } @@ -573,6 +587,9 @@ extension Burrow_TunnelConfigurationResponse: SwiftProtobuf.Message, SwiftProtob if !self.routes.isEmpty { try visitor.visitRepeatedStringField(value: self.routes, fieldNumber: 3) } + if !self.excludedRoutes.isEmpty { + try visitor.visitRepeatedStringField(value: self.excludedRoutes, fieldNumber: 7) + } if !self.dnsServers.isEmpty { try visitor.visitRepeatedStringField(value: self.dnsServers, fieldNumber: 4) } @@ -589,6 +606,7 @@ extension Burrow_TunnelConfigurationResponse: SwiftProtobuf.Message, SwiftProtob if lhs.addresses != rhs.addresses {return false} if lhs.mtu != rhs.mtu {return false} if lhs.routes != rhs.routes {return false} + if lhs.excludedRoutes != rhs.excludedRoutes {return false} if lhs.dnsServers != rhs.dnsServers {return false} if lhs.searchDomains != rhs.searchDomains {return false} if lhs.includeDefaultRoute != rhs.includeDefaultRoute {return false} diff --git a/Apple/NetworkExtension/NetworkExtension.xcconfig b/Apple/NetworkExtension/NetworkExtension.xcconfig index 35c7198..a6a2635 100644 --- a/Apple/NetworkExtension/NetworkExtension.xcconfig +++ b/Apple/NetworkExtension/NetworkExtension.xcconfig @@ -9,3 +9,5 @@ CODE_SIGN_ENTITLEMENTS = NetworkExtension/NetworkExtension-iOS.entitlements CODE_SIGN_ENTITLEMENTS[sdk=macosx*] = NetworkExtension/NetworkExtension-macOS.entitlements SWIFT_INCLUDE_PATHS = $(inherited) $(PROJECT_DIR)/NetworkExtension + +OTHER_LDFLAGS = $(inherited) -framework SystemConfiguration diff --git a/Apple/NetworkExtension/PacketTunnelProvider.swift b/Apple/NetworkExtension/PacketTunnelProvider.swift index 3f3d8b4..12f0cd0 100644 --- a/Apple/NetworkExtension/PacketTunnelProvider.swift +++ b/Apple/NetworkExtension/PacketTunnelProvider.swift @@ -28,13 +28,13 @@ final class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable { get throws { try _client.get() } } private let _client: Result = Result { - try TunnelClient.unix(socketURL: Constants.socketURL) + try TunnelClient.unix(socketURL: Constants.packetTunnelSocketURL) } override init() { do { libburrow.spawnInProcess( - socketPath: try Constants.socketURL.path(percentEncoded: false), + socketPath: try Constants.packetTunnelSocketURL.path(percentEncoded: false), databasePath: try Constants.databaseURL.path(percentEncoded: false) ) } catch { @@ -54,6 +54,11 @@ final class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable { guard let settings = configuration?.settings else { throw Error.missingTunnelConfiguration } + if let configuration { + logger.log( + "Applying tunnel configuration addresses=\(configuration.addresses.joined(separator: ","), privacy: .public) routes=\(configuration.routes.joined(separator: ","), privacy: .public) excludedRoutes=\(configuration.excludedRoutes.joined(separator: ","), privacy: .public) dns=\(configuration.dnsServers.joined(separator: ","), privacy: .public) includeDefaultRoute=\(configuration.includeDefaultRoute, privacy: .public)" + ) + } try await setTunnelNetworkSettings(settings) try startPacketBridge() logger.log("Started tunnel with network settings: \(settings)") @@ -88,15 +93,22 @@ extension PacketTunnelProvider { private func startPacketBridge() throws { stopPacketBridge() - let packetClient = TunnelPacketClient.unix(socketURL: try Constants.socketURL) + let packetClient = TunnelPacketClient.unix(socketURL: try Constants.packetTunnelSocketURL) let call = packetClient.makeTunnelPacketsCall() self.packetCall = call inboundPacketTask = Task { [weak self] in guard let self else { return } + var packetCount = 0 + var byteCount = 0 do { for try await packet in call.responseStream { let payload = packet.payload + packetCount += 1 + byteCount += payload.count + if packetCount == 1 || packetCount % 1024 == 0 { + self.logger.log("Tunnel packet receive progress packets=\(packetCount, privacy: .public) bytes=\(byteCount, privacy: .public) lastBytes=\(payload.count, privacy: .public)") + } self.packetFlow.writePackets( [payload], withProtocols: [Self.protocolNumber(for: payload)] @@ -111,10 +123,17 @@ extension PacketTunnelProvider { outboundPacketTask = Task { [weak self] in guard let self else { return } defer { call.requestStream.finish() } + var packetCount = 0 + var byteCount = 0 do { while !Task.isCancelled { let packets = await self.readPacketsBatch() for (payload, _) in packets { + packetCount += 1 + byteCount += payload.count + if packetCount == 1 || packetCount % 1024 == 0 { + self.logger.log("Tunnel packet send progress packets=\(packetCount, privacy: .public) bytes=\(byteCount, privacy: .public) lastBytes=\(payload.count, privacy: .public)") + } var packet = Burrow_TunnelPacket() packet.payload = payload try await call.requestStream.send(packet) @@ -128,6 +147,7 @@ extension PacketTunnelProvider { } private func stopPacketBridge() { + logger.log("Stopping tunnel packet bridge") inboundPacketTask?.cancel() inboundPacketTask = nil outboundPacketTask?.cancel() @@ -165,6 +185,9 @@ extension Burrow_TunnelConfigurationResponse { let parsedRoutes = routes.compactMap(ParsedTunnelRoute.init(rawValue:)) var ipv4Routes = parsedRoutes.compactMap(\.ipv4Route) var ipv6Routes = parsedRoutes.compactMap(\.ipv6Route) + let parsedExcludedRoutes = excludedRoutes.compactMap(ParsedTunnelRoute.init(rawValue:)) + let excludedIPv4Routes = parsedExcludedRoutes.compactMap(\.ipv4Route) + let excludedIPv6Routes = parsedExcludedRoutes.compactMap(\.ipv6Route) if includeDefaultRoute { ipv4Routes.append(.default()) ipv6Routes.append(.default()) @@ -180,6 +203,9 @@ extension Burrow_TunnelConfigurationResponse { if !ipv4Routes.isEmpty { ipv4Settings.includedRoutes = ipv4Routes } + if !excludedIPv4Routes.isEmpty { + ipv4Settings.excludedRoutes = excludedIPv4Routes + } settings.ipv4Settings = ipv4Settings } if !ipv6Addresses.isEmpty { @@ -190,6 +216,9 @@ extension Burrow_TunnelConfigurationResponse { if !ipv6Routes.isEmpty { ipv6Settings.includedRoutes = ipv6Routes } + if !excludedIPv6Routes.isEmpty { + ipv6Settings.excludedRoutes = excludedIPv6Routes + } settings.ipv6Settings = ipv6Settings } if !dnsServers.isEmpty { diff --git a/Apple/NetworkExtension/libburrow/build-rust.sh b/Apple/NetworkExtension/libburrow/build-rust.sh index 5db2a2b..79a45d6 100755 --- a/Apple/NetworkExtension/libburrow/build-rust.sh +++ b/Apple/NetworkExtension/libburrow/build-rust.sh @@ -3,7 +3,7 @@ # This is a build script. It is run by Xcode as a build step. # The type of build is described in various environment variables set by Xcode. -export PATH="${PATH}:${HOME}/.cargo/bin:/opt/homebrew/bin:/usr/local/bin:/etc/profiles/per-user/${USER}/bin" +export PATH="${PATH}:${HOME}/.cargo/bin:${HOME}/.nix-profile/bin:/opt/homebrew/bin:/usr/local/bin:/etc/profiles/per-user/${USER}/bin:/run/current-system/sw/bin:/nix/var/nix/profiles/default/bin" if ! [[ -x "$(command -v cargo)" ]]; then echo 'error: Unable to find cargo' @@ -63,13 +63,13 @@ else fi if [[ -x "$(command -v rustup)" ]]; then - CARGO_PATH="$(dirname $(rustup which cargo)):/usr/bin" + CARGO_PATH="$(dirname "$(rustup which cargo)"):/usr/bin" else - CARGO_PATH="$(dirname $(readlink -f $(which cargo))):/usr/bin" + CARGO_PATH="$(dirname "$(readlink -f "$(command -v cargo)")"):/usr/bin" fi -PROTOC=$(readlink -f $(which protoc)) -CARGO_PATH="$(dirname $PROTOC):$CARGO_PATH" +PROTOC="$(readlink -f "$(command -v protoc)")" +CARGO_PATH="$(dirname "$PROTOC"):$CARGO_PATH" # Run cargo without the various environment variables set by Xcode. # Those variables can confuse cargo and the build scripts it runs. diff --git a/Apple/UI/BurrowView.swift b/Apple/UI/BurrowView.swift index e15d3f7..bc9e361 100644 --- a/Apple/UI/BurrowView.swift +++ b/Apple/UI/BurrowView.swift @@ -1,5 +1,6 @@ import BurrowConfiguration import Foundation +import GRPC import SwiftUI #if canImport(AuthenticationServices) import AuthenticationServices @@ -15,6 +16,7 @@ public struct BurrowView: View { @State private var accountStore = NetworkAccountStore() @State private var activeSheet: ConfigurationSheet? @State private var didRunAutomation = false + @State private var expandedProxySubscriptionID: Int32? public var body: some View { NavigationStack { @@ -37,6 +39,9 @@ public struct BurrowView: View { Button("Add WireGuard Network") { activeSheet = .wireGuard } + Button("Import Proxy Subscription") { + activeSheet = .proxySubscription + } Button("Save Tor Account") { activeSheet = .tor } @@ -67,8 +72,22 @@ public struct BurrowView: View { Text(connectionError) .font(.footnote) .foregroundStyle(.secondary) + .lineLimit(4) + .truncationMode(.middle) + } + NetworkCarouselView( + networks: networkViewModel.cards, + selectedNetworkID: expandedProxySubscriptionID + ) { network in + guard networkViewModel.proxySubscriptions.contains(where: { $0.id == network.id }) else { + return + } + withAnimation(.snappy) { + expandedProxySubscriptionID = expandedProxySubscriptionID == network.id + ? nil + : network.id + } } - NetworkCarouselView(networks: networkViewModel.cards) } if showsAccountsSection { @@ -119,6 +138,15 @@ public struct BurrowView: View { accountStore: accountStore ) } + .sheet(isPresented: proxySubscriptionSheetBinding) { + ProxySubscriptionManagerView( + networks: networkViewModel.proxySubscriptions, + networkViewModel: networkViewModel, + selectedNetworkID: $expandedProxySubscriptionID + ) + .frame(width: 820, height: 600) + .padding(20) + } .onAppear { runAutomationIfNeeded() } @@ -157,15 +185,29 @@ public struct BurrowView: View { } } + private var proxySubscriptionSheetBinding: Binding { + Binding( + get: { expandedProxySubscriptionID != nil }, + set: { isPresented in + guard !isPresented else { return } + expandedProxySubscriptionID = nil + } + ) + } + @ViewBuilder private func sectionHeader(title: String, detail: String?) -> some View { VStack(alignment: .leading, spacing: 4) { Text(title) .font(.title2.weight(.semibold)) + .lineLimit(1) + .truncationMode(.tail) if let detail, !detail.isEmpty { Text(detail) .font(.subheadline) .foregroundStyle(.secondary) + .lineLimit(3) + .truncationMode(.tail) } } } @@ -190,13 +232,14 @@ public struct BurrowView: View { #if os(iOS) !accountStore.accounts.isEmpty #else - true + !accountStore.accounts.isEmpty || networkViewModel.networks.isEmpty #endif } } private enum ConfigurationSheet: String, CaseIterable, Identifiable { case wireGuard + case proxySubscription case tor case tailnet @@ -205,6 +248,7 @@ private enum ConfigurationSheet: String, CaseIterable, Identifiable { var kind: AccountNetworkKind { switch self { case .wireGuard: .wireGuard + case .proxySubscription: .proxySubscription case .tor: .tor case .tailnet: .tailnet } @@ -214,6 +258,8 @@ private enum ConfigurationSheet: String, CaseIterable, Identifiable { switch self { case .wireGuard: "wave.3.right" + case .proxySubscription: + "link.badge.plus" case .tor: "shield.lefthalf.filled.badge.checkmark" case .tailnet: @@ -225,6 +271,8 @@ private enum ConfigurationSheet: String, CaseIterable, Identifiable { switch self { case .wireGuard: "WireGuard" + case .proxySubscription: + "Proxy Subscription" case .tor: "Tor" case .tailnet: @@ -236,6 +284,8 @@ private enum ConfigurationSheet: String, CaseIterable, Identifiable { switch self { case .wireGuard: "Import a tunnel" + case .proxySubscription: + "Import Trojan or Shadowsocks" case .tor: "Save an Arti profile" case .tailnet: @@ -247,6 +297,8 @@ private enum ConfigurationSheet: String, CaseIterable, Identifiable { switch self { case .wireGuard: .blue + case .proxySubscription: + .teal case .tor, .tailnet: kind.accentColor } @@ -286,6 +338,8 @@ private struct AccountDraft { var accountName = "" var identityName = "" var wireGuardConfig = "" + var proxySubscriptionURL = "" + var proxySubscriptionName = "" var discoveryEmail = "" var authority = "" @@ -304,6 +358,8 @@ private struct AccountDraft { switch sheet { case .wireGuard: break + case .proxySubscription: + title = "Proxy Subscription" case .tor: title = "Default Tor" accountName = "default" @@ -338,6 +394,11 @@ private struct ConfigurationSheetView: View { @State private var tailnetLoginError: String? @State private var tailnetLoginSessionID: String? @State private var isStartingTailnetLogin = false + @State private var proxySubscriptionPreview: ProxySubscriptionPreview? + @State private var proxySubscriptionPreviewError: String? + @State private var isPreviewingProxySubscription = false + @State private var selectedProxySubscriptionOrdinal: Int32? + @State private var proxySubscriptionNodeFilter = "" @State private var tailnetPresentedAuthURL: URL? @State private var preserveTailnetLoginSession = false @State private var usesCustomTailnetAuthority = false @@ -374,31 +435,11 @@ private struct ConfigurationSheetView: View { } } - switch sheet { - case .wireGuard: - Section("WireGuard Configuration") { - TextEditor(text: $draft.wireGuardConfig) - .font(.body.monospaced()) - .frame(minHeight: wireGuardEditorHeight) - .contextMenu { - wireGuardContextActions - } - } - case .tor: - Section("Tor Preferences") { - TextField("Virtual Addresses", text: $draft.torAddresses) - TextField("DNS Resolvers", text: $draft.torDNS) - TextField("MTU", text: $draft.torMTU) - TextField("Transparent Listener", text: $draft.torListen) - } - case .tailnet: - tailnetSections - } + configurationSections if let errorMessage { Section { - Text(errorMessage) - .foregroundStyle(.red) + feedbackText(errorMessage, color: .red) } } } @@ -445,7 +486,8 @@ private struct ConfigurationSheetView: View { } } #if os(macOS) - .frame(minWidth: 520, minHeight: 620) + .frame(width: 520) + .frame(minHeight: 620) #endif .safeAreaInset(edge: .bottom) { if showsBottomActionButton { @@ -473,6 +515,12 @@ private struct ConfigurationSheetView: View { await cancelTailnetLoginIfNeeded() } } + .onChange(of: draft.proxySubscriptionURL) { _, _ in + proxySubscriptionPreview = nil + proxySubscriptionPreviewError = nil + selectedProxySubscriptionOrdinal = nil + proxySubscriptionNodeFilter = "" + } .onDisappear { tailnetLoginPollTask?.cancel() tailnetDiscoveryTask?.cancel() @@ -486,6 +534,71 @@ private struct ConfigurationSheetView: View { } } + @ViewBuilder + private var configurationSections: some View { + switch sheet { + case .wireGuard: + wireGuardConfigurationSection + case .proxySubscription: + proxySubscriptionSections + case .tor: + torPreferenceSection + case .tailnet: + tailnetSections + } + } + + private var wireGuardConfigurationSection: some View { + Section("WireGuard Configuration") { + TextEditor(text: $draft.wireGuardConfig) + .font(.body.monospaced()) + .frame(minHeight: wireGuardEditorHeight) + .contextMenu { + wireGuardContextActions + } + } + } + + @ViewBuilder + private var proxySubscriptionSections: some View { + Section("Subscription") { + TextField("Subscription URL", text: $draft.proxySubscriptionURL) + .autocorrectionDisabled() + TextField("Name", text: $draft.proxySubscriptionName) + } + + Section("Preview") { + proxySubscriptionPreviewButton + + if let proxySubscriptionPreview { + proxySubscriptionPreviewView(proxySubscriptionPreview) + } else if let proxySubscriptionPreviewError { + feedbackText(proxySubscriptionPreviewError, color: .red) + } + } + } + + private var proxySubscriptionPreviewButton: some View { + Button { + previewProxySubscription() + } label: { + Label( + isPreviewingProxySubscription ? "Previewing" : "Preview", + systemImage: isPreviewingProxySubscription ? "hourglass" : "eye" + ) + } + .disabled(isPreviewingProxySubscription || normalizedOptional(draft.proxySubscriptionURL) == nil) + } + + private var torPreferenceSection: some View { + Section("Tor Preferences") { + TextField("Virtual Addresses", text: $draft.torAddresses) + TextField("DNS Resolvers", text: $draft.torDNS) + TextField("MTU", text: $draft.torMTU) + TextField("Transparent Listener", text: $draft.torListen) + } + } + @ViewBuilder private var identityFields: some View { TextField("Title", text: $draft.title) @@ -792,6 +905,143 @@ private struct ConfigurationSheetView: View { ) } + private func proxySubscriptionPreviewView(_ preview: ProxySubscriptionPreview) -> some View { + VStack(alignment: .leading, spacing: 8) { + labeledValue("Format", preview.detectedFormat) + labeledValue("Compatible", "\(preview.compatibleCount)") + labeledValue("Runtime Supported", "\(preview.nodes.filter { $0.runtimeSupported }.count)") + labeledValue("Rejected", "\(preview.rejectedCount)") + + let selectableNodes = supportedProxySubscriptionNodes(in: preview) + if !selectableNodes.isEmpty { + TextField("Filter nodes", text: $proxySubscriptionNodeFilter) + .autocorrectionDisabled() + + let filteredNodes = filteredProxySubscriptionNodes(selectableNodes) + proxySubscriptionNodeList(nodes: filteredNodes, preview: preview) + + if !proxySubscriptionNodeFilter.isEmpty && filteredNodes.isEmpty { + feedbackText("No nodes match the current filter.", color: .orange) + } + } else if !preview.nodes.isEmpty { + Text("No previewed nodes are supported by the packet runtime.") + .font(.caption) + .foregroundStyle(.orange) + } + + ForEach(preview.warnings, id: \.self) { warning in + feedbackText(warning, color: .orange) + } + } + } + + private func proxySubscriptionNodeList( + nodes: [ProxySubscriptionNodePreview], + preview: ProxySubscriptionPreview + ) -> some View { + ScrollView { + LazyVStack(alignment: .leading, spacing: 6) { + ForEach(nodes) { node in + proxySubscriptionNodeRow( + node: node, + isSelected: selectedProxySubscriptionNode(in: preview)?.ordinal == node.ordinal + ) + } + } + .padding(.vertical, 2) + } + .frame(maxHeight: 260) + } + + private func proxySubscriptionNodeRow( + node: ProxySubscriptionNodePreview, + isSelected: Bool + ) -> some View { + Button { + selectedProxySubscriptionOrdinal = node.ordinal + } label: { + HStack(spacing: 8) { + Image(systemName: isSelected ? "checkmark.circle.fill" : "circle") + .foregroundStyle(isSelected ? Color.accentColor : Color.secondary) + .imageScale(.small) + .frame(width: 16) + + VStack(alignment: .leading, spacing: 2) { + HStack(spacing: 6) { + Text(node.name) + .font(.footnote.weight(isSelected ? .semibold : .regular)) + .lineLimit(1) + .truncationMode(.tail) + Text(node.proxyProtocol.uppercased()) + .font(.caption2.weight(.medium)) + .foregroundStyle(.secondary) + .padding(.horizontal, 5) + .padding(.vertical, 2) + .background( + Capsule() + .fill(.secondary.opacity(0.12)) + ) + } + + Text("\(node.server):\(node.port)") + .font(.caption) + .foregroundStyle(.secondary) + .lineLimit(1) + .truncationMode(.middle) + + } + + Spacer(minLength: 0) + } + .padding(.horizontal, 10) + .padding(.vertical, 8) + .frame(maxWidth: .infinity, alignment: .leading) + .background( + RoundedRectangle(cornerRadius: 8) + .fill(isSelected ? Color.accentColor.opacity(0.14) : Color.secondary.opacity(0.08)) + ) + .overlay( + RoundedRectangle(cornerRadius: 8) + .stroke(isSelected ? Color.accentColor.opacity(0.45) : Color.clear, lineWidth: 1) + ) + } + .buttonStyle(.plain) + } + + private func supportedProxySubscriptionNodes( + in preview: ProxySubscriptionPreview + ) -> [ProxySubscriptionNodePreview] { + preview.nodes.filter { $0.runtimeSupported } + } + + private func filteredProxySubscriptionNodes( + _ nodes: [ProxySubscriptionNodePreview] + ) -> [ProxySubscriptionNodePreview] { + let query = proxySubscriptionNodeFilter.trimmingCharacters(in: .whitespacesAndNewlines) + guard !query.isEmpty else { return nodes } + return nodes.filter { node in + node.name.localizedCaseInsensitiveContains(query) + || node.server.localizedCaseInsensitiveContains(query) + || node.proxyProtocol.localizedCaseInsensitiveContains(query) + } + } + + private func selectedProxySubscriptionNode(in preview: ProxySubscriptionPreview) -> ProxySubscriptionNodePreview? { + let ordinal = selectedProxySubscriptionOrdinal + ?? preview.nodes.first(where: { $0.runtimeSupported })?.ordinal + return preview.nodes.first { $0.ordinal == ordinal } + } + + private func feedbackText(_ message: String, color: Color) -> some View { + Text(message) + .font(.caption) + .foregroundStyle(color) + .lineLimit(6) + .truncationMode(.middle) + .frame(maxWidth: .infinity, alignment: .leading) + .fixedSize(horizontal: false, vertical: true) + } + @ViewBuilder private var bottomActionBar: some View { VStack(spacing: 0) { @@ -827,6 +1077,12 @@ private struct ConfigurationSheetView: View { } .disabled(draft.wireGuardConfig.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty) + case .proxySubscription: + Button("Preview Subscription") { + previewProxySubscription() + } + .disabled(normalizedOptional(draft.proxySubscriptionURL) == nil) + case .tor: Menu("Presets") { Button("Recommended Tor Defaults") { @@ -883,6 +1139,8 @@ private struct ConfigurationSheetView: View { switch sheet { case .wireGuard: .blue + case .proxySubscription: + .teal case .tor, .tailnet: sheet.kind.accentColor } @@ -892,6 +1150,8 @@ private struct ConfigurationSheetView: View { switch sheet { case .wireGuard: "Import WireGuard" + case .proxySubscription: + "Import Proxy Subscription" case .tor: "Configure Tor" case .tailnet: @@ -911,6 +1171,8 @@ private struct ConfigurationSheetView: View { switch sheet { case .wireGuard, .tor: return true + case .proxySubscription: + return false case .tailnet: return showsAdvancedTailnetSettings } @@ -928,6 +1190,8 @@ private struct ConfigurationSheetView: View { switch sheet { case .wireGuard: return "Add Network" + case .proxySubscription: + return "Import" case .tor: return "Save Account" case .tailnet: @@ -942,7 +1206,7 @@ private struct ConfigurationSheetView: View { return normalizedOptional(draft.authority) == nil } return false - case .wireGuard, .tor: + case .wireGuard, .tor, .proxySubscription: return true } } @@ -951,6 +1215,9 @@ private struct ConfigurationSheetView: View { switch sheet { case .wireGuard: return draft.wireGuardConfig.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty + case .proxySubscription: + return normalizedOptional(draft.proxySubscriptionURL) == nil + || proxySubscriptionPreview?.nodes.contains(where: { $0.runtimeSupported }) != true case .tor: return normalizedOptional(draft.accountName) == nil || normalizedOptional(draft.identityName) == nil case .tailnet: @@ -1025,6 +1292,9 @@ private struct ConfigurationSheetView: View { case .wireGuard: try await submitWireGuard() dismiss() + case .proxySubscription: + try await submitProxySubscription() + dismiss() case .tor: try submitTor() dismiss() @@ -1032,7 +1302,7 @@ private struct ConfigurationSheetView: View { try await submitTailnet() } } catch { - errorMessage = error.localizedDescription + errorMessage = displayError(error) } } } @@ -1062,6 +1332,44 @@ private struct ConfigurationSheetView: View { try accountStore.upsert(record, secret: nil) } + private func submitProxySubscription() async throws { + let url = normalized(draft.proxySubscriptionURL, fallback: "") + let name = normalized(draft.proxySubscriptionName, fallback: "Proxy Subscription") + let selectedOrdinal = selectedProxySubscriptionOrdinal + ?? proxySubscriptionPreview?.nodes.first(where: { $0.runtimeSupported })?.ordinal + _ = try await networkViewModel.importProxySubscription( + url: url, + name: name, + selectedOrdinal: selectedOrdinal + ) + } + + private func previewProxySubscription() { + guard let url = normalizedOptional(draft.proxySubscriptionURL) else { + proxySubscriptionPreviewError = "Enter a subscription URL before previewing." + return + } + isPreviewingProxySubscription = true + proxySubscriptionPreviewError = nil + Task { @MainActor in + defer { isPreviewingProxySubscription = false } + do { + let preview = try await networkViewModel.previewProxySubscription(url: url) + proxySubscriptionPreview = preview + selectedProxySubscriptionOrdinal = preview.nodes.first(where: { $0.runtimeSupported })?.ordinal + proxySubscriptionNodeFilter = "" + if draft.proxySubscriptionName.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { + draft.proxySubscriptionName = preview.suggestedName + } + } catch { + proxySubscriptionPreview = nil + selectedProxySubscriptionOrdinal = nil + proxySubscriptionNodeFilter = "" + proxySubscriptionPreviewError = displayError(error) + } + } + } + private func submitTor() throws { let title = titleOrFallback("Tor \(normalized(draft.identityName, fallback: "apple"))") let note = [ @@ -1529,6 +1837,8 @@ private struct ConfigurationSheetView: View { .foregroundStyle(.secondary) Text(value) .font(.body.monospaced()) + .lineLimit(3) + .truncationMode(.middle) } } } @@ -1543,9 +1853,11 @@ private struct AccountRowView: View { VStack(alignment: .leading, spacing: 4) { Text(account.title) .font(.headline) + .lineLimit(1) + .truncationMode(.tail) Text(account.kind.title) - .font(.subheadline) - .foregroundStyle(account.kind.accentColor) + .font(.subheadline) + .foregroundStyle(account.kind.accentColor) } Spacer() if hasSecret { @@ -1578,6 +1890,8 @@ private struct AccountRowView: View { Text(note) .font(.footnote) .foregroundStyle(.secondary) + .lineLimit(3) + .truncationMode(.middle) } } .padding() @@ -1596,11 +1910,378 @@ private struct AccountRowView: View { .foregroundStyle(.secondary) Text(value) .font(.body.monospaced()) + .lineLimit(3) + .truncationMode(.middle) + } + } +} + +private struct ProxySubscriptionManagerView: View { + @Environment(\.tunnel) + private var tunnel: any Tunnel + + let networks: [ProxySubscriptionNetwork] + let networkViewModel: NetworkViewModel + @Binding var selectedNetworkID: Int32? + + @State private var nodeFilter = "" + @State private var operationID: String? + @State private var feedback: String? + @State private var errorMessage: String? + @State private var pendingDelete: ProxySubscriptionNetwork? + + var body: some View { + if let network = selectedNetwork { + VStack(alignment: .leading, spacing: 16) { + HStack(alignment: .top, spacing: 12) { + VStack(alignment: .leading, spacing: 4) { + Text("Proxy Subscription") + .font(.caption.weight(.medium)) + .foregroundStyle(.secondary) + Text(network.name) + .font(.title3.weight(.semibold)) + .lineLimit(1) + .truncationMode(.tail) + if let selectedNode = network.selectedNode { + Text("\(selectedNode.proxyProtocol.uppercased()) \(selectedNode.server):\(selectedNode.port)") + .font(.caption) + .foregroundStyle(.secondary) + .lineLimit(1) + .truncationMode(.middle) + } + } + + Spacer(minLength: 0) + + HStack(spacing: 8) { + Button { + refresh(network) + } label: { + Label("Refresh", systemImage: "arrow.clockwise") + } + .disabled(isBusy) + + Button(role: .destructive) { + pendingDelete = network + } label: { + Label("Remove", systemImage: "trash") + } + .disabled(isBusy) + } + .burrowGlassControl() + .controlSize(.small) + + Button { + withAnimation(.snappy) { + selectedNetworkID = nil + } + } label: { + Image(systemName: "xmark.circle.fill") + .font(.title3) + .foregroundStyle(.secondary) + } + .buttonStyle(.plain) + .accessibilityLabel("Close proxy subscription picker") + } + + if networks.count > 1 { + Picker("Subscription", selection: selectedNetworkIDBinding) { + ForEach(networks) { network in + Text(network.name).tag(network.id) + } + } + .pickerStyle(.menu) + } + + HStack(spacing: 16) { + Label(network.detectedFormat, systemImage: "doc.text") + Label("\(network.runtimeSupportedNodes.count) usable of \(network.nodes.count) nodes", systemImage: "circle.grid.2x1") + } + .font(.caption) + .foregroundStyle(.secondary) + + TextField("Filter nodes", text: $nodeFilter) + .textFieldStyle(.roundedBorder) + .autocorrectionDisabled() + + HStack { + Text("Nodes") + .font(.subheadline.weight(.semibold)) + Spacer() + Text("\(filteredNodes(for: network).count) shown") + .font(.caption) + .foregroundStyle(.secondary) + } + + ScrollView { + LazyVGrid(columns: nodeColumns, alignment: .leading, spacing: 10) { + ForEach(filteredNodes(for: network)) { node in + nodeTile(node, in: network) + } + } + .padding(.vertical, 2) + } + .frame(maxHeight: 410) + .scrollIndicators(.visible) + + if let feedback { + Text(feedback) + .font(.caption) + .foregroundStyle(.secondary) + .lineLimit(2) + .truncationMode(.tail) + } + + if let errorMessage { + Text(errorMessage) + .font(.caption) + .foregroundStyle(.red) + .lineLimit(3) + .truncationMode(.middle) + } + } + .frame(maxWidth: .infinity, alignment: .leading) + .onAppear { + ensureSelectedNetwork() + } + .onChange(of: networks) { _, _ in + ensureSelectedNetwork() + } + .confirmationDialog( + "Remove proxy subscription?", + isPresented: Binding( + get: { pendingDelete != nil }, + set: { if !$0 { pendingDelete = nil } } + ), + presenting: pendingDelete + ) { network in + Button("Remove \(network.name)", role: .destructive) { + delete(network) + } + } + } + } + + private var selectedNetwork: ProxySubscriptionNetwork? { + guard let selectedNetworkID else { + return nil + } + return networks.first(where: { $0.id == selectedNetworkID }) + } + + private var selectedNetworkIDBinding: Binding { + Binding( + get: { selectedNetwork?.id ?? networks.first?.id ?? 0 }, + set: { selectedNetworkID = $0 } + ) + } + + private var isBusy: Bool { + operationID != nil + } + + private var nodeColumns: [GridItem] { + [ + GridItem( + .adaptive(minimum: 245, maximum: 320), + spacing: 10, + alignment: .top + ) + ] + } + + private func ensureSelectedNetwork() { + guard let selectedNetworkID else { + return + } + if !networks.contains(where: { $0.id == selectedNetworkID }) { + self.selectedNetworkID = nil + } + } + + private func filteredNodes(for network: ProxySubscriptionNetwork) -> [ProxySubscriptionNetworkNode] { + let query = nodeFilter.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() + guard !query.isEmpty else { + return network.nodes + } + return network.nodes.filter { node in + node.name.lowercased().contains(query) + || node.server.lowercased().contains(query) + || node.proxyProtocol.lowercased().contains(query) + } + } + + private func nodeTile( + _ node: ProxySubscriptionNetworkNode, + in network: ProxySubscriptionNetwork + ) -> some View { + let isSelected = network.selectedNode?.ordinal == node.ordinal + return Button { + select(node, in: network) + } label: { + VStack(alignment: .leading, spacing: 10) { + HStack(alignment: .top, spacing: 8) { + Image(systemName: isSelected ? "checkmark.circle.fill" : "circle") + .foregroundStyle(isSelected ? Color.accentColor : Color.secondary) + .frame(width: 18) + + VStack(alignment: .leading, spacing: 4) { + Text(node.name) + .font(.footnote.weight(isSelected ? .semibold : .regular)) + .lineLimit(1) + .truncationMode(.tail) + + HStack(spacing: 6) { + Text(node.proxyProtocol.uppercased()) + .font(.caption2.weight(.medium)) + .foregroundStyle(.secondary) + .padding(.horizontal, 5) + .padding(.vertical, 2) + .background(Capsule().fill(.secondary.opacity(0.12))) + if !node.runtimeSupported { + Text("UNSUPPORTED") + .font(.caption2.weight(.medium)) + .foregroundStyle(.orange) + } + } + } + + Spacer(minLength: 0) + } + + Text("\(node.server):\(node.port)") + .font(.caption.monospaced()) + .foregroundStyle(.secondary) + .lineLimit(1) + .truncationMode(.middle) + + if operationID == operationIdentifier(networkID: network.id, ordinal: node.ordinal) { + ProgressView() + .controlSize(.small) + .frame(maxWidth: .infinity, alignment: .trailing) + } + } + .padding(12) + .frame(minHeight: 94, alignment: .topLeading) + .frame(maxWidth: .infinity, alignment: .leading) + .background(.thinMaterial, in: RoundedRectangle(cornerRadius: 12)) + .background( + RoundedRectangle(cornerRadius: 12) + .fill(isSelected ? Color.accentColor.opacity(0.10) : Color.clear) + ) + .overlay( + RoundedRectangle(cornerRadius: 12) + .stroke(isSelected ? Color.accentColor.opacity(0.65) : Color.secondary.opacity(0.16), lineWidth: 1) + ) + } + .buttonStyle(.plain) + .disabled(isBusy || !node.runtimeSupported) + } + + private func select( + _ node: ProxySubscriptionNetworkNode, + in network: ProxySubscriptionNetwork + ) { + let operationIdentifier = operationIdentifier(networkID: network.id, ordinal: node.ordinal) + operationID = operationIdentifier + feedback = nil + errorMessage = nil + Task { @MainActor in + defer { operationID = nil } + do { + _ = try await networkViewModel.updateProxySubscriptionSelection( + network: network, + selectedOrdinal: node.ordinal + ) + if isTunnelRunning { + _ = try await networkViewModel.updateActiveProxySubscriptionSelection( + network: network, + selectedOrdinal: node.ordinal + ) + feedback = "Selected \(node.name) for the active tunnel" + } else { + feedback = "Selected \(node.name)" + } + } catch { + errorMessage = displayError(error) + } + } + } + + private func refresh(_ network: ProxySubscriptionNetwork) { + operationID = "refresh-\(network.id)" + feedback = nil + errorMessage = nil + Task { @MainActor in + defer { operationID = nil } + do { + _ = try await networkViewModel.refreshProxySubscription(network: network) + if isTunnelRunning { + _ = try await networkViewModel.refreshActiveProxySubscription(network: network) + feedback = "Refreshed \(network.name) for the active tunnel" + } else { + feedback = "Refreshed \(network.name)" + } + } catch { + errorMessage = displayError(error) + } + } + } + + private func delete(_ network: ProxySubscriptionNetwork) { + operationID = "delete-\(network.id)" + feedback = nil + errorMessage = nil + Task { @MainActor in + defer { operationID = nil } + do { + try await networkViewModel.deleteNetwork(id: network.id) + pendingDelete = nil + selectedNetworkID = nil + feedback = nil + } catch { + errorMessage = displayError(error) + } + } + } + + private func operationIdentifier(networkID: Int32, ordinal: Int32) -> String { + "select-\(networkID)-\(ordinal)" + } + + @MainActor + private var isTunnelRunning: Bool { + switch tunnel.status { + case .connected, .reasserting: + true + default: + false + } + } + + private func labeledValue(_ label: String, _ value: String) -> some View { + VStack(alignment: .leading, spacing: 2) { + Text(label) + .font(.caption) + .foregroundStyle(.secondary) + Text(value) + .font(.caption.monospaced()) + .lineLimit(1) + .truncationMode(.tail) } } } private extension View { + @ViewBuilder + func burrowGlassControl() -> some View { + if #available(macOS 26.0, iOS 26.0, *) { + self.buttonStyle(.glass) + } else { + self.buttonStyle(.bordered) + } + } + @ViewBuilder func burrowLoginField() -> some View { #if os(iOS) @@ -1674,6 +2355,13 @@ private final class TailnetBrowserAuthenticator { } #endif +private func displayError(_ error: Error) -> String { + if let status = error as? GRPCStatus { + return status.message ?? status.description + } + return error.localizedDescription +} + private struct BurrowAutomationConfig { enum Action: String { case tailnetLogin = "tailnet-login" diff --git a/Apple/UI/NetworkCarouselView.swift b/Apple/UI/NetworkCarouselView.swift index e7368db..d109672 100644 --- a/Apple/UI/NetworkCarouselView.swift +++ b/Apple/UI/NetworkCarouselView.swift @@ -2,6 +2,18 @@ import SwiftUI struct NetworkCarouselView: View { var networks: [NetworkCardModel] + var selectedNetworkID: Int32? + var onSelect: ((NetworkCardModel) -> Void)? + + init( + networks: [NetworkCardModel], + selectedNetworkID: Int32? = nil, + onSelect: ((NetworkCardModel) -> Void)? = nil + ) { + self.networks = networks + self.selectedNetworkID = selectedNetworkID + self.onSelect = onSelect + } var body: some View { Group { @@ -29,10 +41,19 @@ struct NetworkCarouselView: View { .frame(maxWidth: .infinity, minHeight: 175) #endif } else { + #if os(macOS) + LazyVStack(alignment: .leading, spacing: 12) { + ForEach(networks) { network in + networkCard(network) + .frame(maxWidth: 560) + } + } + .frame(maxWidth: .infinity, alignment: .leading) + #else ScrollView(.horizontal) { LazyHStack { ForEach(networks) { network in - NetworkView(network: network) + networkCard(network) .containerRelativeFrame(.horizontal, count: 10, span: 7, spacing: 0, alignment: .center) .scrollTransition(.interactive, axis: .horizontal) { content, phase in content @@ -47,9 +68,33 @@ struct NetworkCarouselView: View { .defaultScrollAnchor(.center) .scrollTargetBehavior(.viewAligned) .containerRelativeFrame(.horizontal) + #endif } } } + + @ViewBuilder + private func networkCard(_ network: NetworkCardModel) -> some View { + if let onSelect { + Button { + onSelect(network) + } label: { + NetworkView(network: network) + .overlay(selectionOverlay(for: network)) + } + .buttonStyle(.plain) + } else { + NetworkView(network: network) + } + } + + private func selectionOverlay(for network: NetworkCardModel) -> some View { + RoundedRectangle(cornerRadius: 10) + .stroke( + selectedNetworkID == network.id ? Color.accentColor : Color.clear, + lineWidth: 3 + ) + } } #if DEBUG diff --git a/Apple/UI/NetworkExtensionTunnel.swift b/Apple/UI/NetworkExtensionTunnel.swift index 23559f3..151e683 100644 --- a/Apple/UI/NetworkExtensionTunnel.swift +++ b/Apple/UI/NetworkExtensionTunnel.swift @@ -98,23 +98,33 @@ public final class NetworkExtensionTunnel: Tunnel { } } - guard managers.isEmpty else { return } - - let manager = NETunnelProviderManager() - manager.localizedDescription = "Burrow" + let manager = managers.first ?? NETunnelProviderManager() + var needsSave = managers.isEmpty + if manager.localizedDescription != "Burrow" { + manager.localizedDescription = "Burrow" + needsSave = true + } let proto = NETunnelProviderProtocol() proto.providerBundleIdentifier = bundleIdentifier proto.serverAddress = "burrow.rs" + proto.proxySettings = nil - manager.protocolConfiguration = proto - try await manager.save() + if !manager.protocolConfiguration.isBurrowEquivalent(to: proto) { + manager.protocolConfiguration = proto + needsSave = true + } + + if needsSave { + try await manager.save() + } } public func start() { Task { - guard let manager = try await NETunnelProviderManager.managers.first else { return } do { + try await configure() + guard let manager = try await NETunnelProviderManager.managers.first else { return } if !manager.isEnabled { manager.isEnabled = true try await manager.save() @@ -149,6 +159,17 @@ public final class NetworkExtensionTunnel: Tunnel { } } +private extension Optional where Wrapped == NEVPNProtocol { + func isBurrowEquivalent(to expected: NETunnelProviderProtocol) -> Bool { + guard let current = self as? NETunnelProviderProtocol else { + return false + } + return current.providerBundleIdentifier == expected.providerBundleIdentifier + && current.serverAddress == expected.serverAddress + && current.proxySettings == nil + } +} + extension NEVPNConnection { fileprivate var tunnelStatus: TunnelStatus { switch status { diff --git a/Apple/UI/Networks/Network.swift b/Apple/UI/Networks/Network.swift index 35bd0e1..0f46406 100644 --- a/Apple/UI/Networks/Network.swift +++ b/Apple/UI/Networks/Network.swift @@ -53,6 +53,218 @@ struct TailnetLoginStatus: Sendable { var health: [String] } +struct ProxySubscriptionNodePreview: Sendable, Identifiable { + var id: Int32 { ordinal } + var ordinal: Int32 + var name: String + var proxyProtocol: String + var server: String + var port: Int32 + var warnings: [String] + var runtimeSupported: Bool +} + +struct ProxySubscriptionPreview: Sendable { + var suggestedName: String + var detectedFormat: String + var compatibleCount: Int32 + var rejectedCount: Int32 + var nodes: [ProxySubscriptionNodePreview] + var warnings: [String] +} + +struct ProxySubscriptionNetwork: Sendable, Identifiable, Hashable { + var id: Int32 + var name: String + var sourceURL: String + var detectedFormat: String + var selectedOrdinal: Int32? + var selectedName: String? + var nodes: [ProxySubscriptionNetworkNode] + var warnings: [String] + + var selectedNode: ProxySubscriptionNetworkNode? { + selectedName + .flatMap { name in nodes.first { $0.name == name && $0.runtimeSupported } } + ?? selectedOrdinal + .flatMap { ordinal in nodes.first { $0.ordinal == ordinal && $0.runtimeSupported } } + ?? nodes.first { $0.runtimeSupported } + ?? nodes.first + } + + var runtimeSupportedNodes: [ProxySubscriptionNetworkNode] { + nodes.filter(\.runtimeSupported) + } + + init?(network: Burrow_Network) { + guard network.type == .proxySubscription, + let payload = try? JSONDecoder().decode(StoredProxySubscriptionPayload.self, from: network.payload) + else { + return nil + } + id = network.id + name = payload.name + sourceURL = payload.source.url + detectedFormat = payload.detectedFormat + selectedOrdinal = payload.selectedOrdinal.map(Int32.init) + selectedName = payload.selectedName + nodes = payload.nodes.map(ProxySubscriptionNetworkNode.init) + warnings = payload.warnings + } +} + +struct ProxySubscriptionNetworkNode: Sendable, Identifiable, Hashable { + var id: Int32 { ordinal } + var ordinal: Int32 + var name: String + var proxyProtocol: String + var server: String + var port: Int32 + var warnings: [String] + var runtimeSupported: Bool + + fileprivate init(stored: StoredProxySubscriptionNode) { + ordinal = Int32(stored.ordinal) + name = stored.name + proxyProtocol = stored.proxyProtocol + server = stored.server + port = Int32(stored.port) + warnings = stored.warnings + runtimeSupported = stored.config.runtimeSupported + } +} + +private struct StoredProxySubscriptionPayload: Decodable { + var name: String + var source: StoredProxySubscriptionSource + var detectedFormat: String + var selectedOrdinal: Int? + var selectedName: String? + var nodes: [StoredProxySubscriptionNode] + var warnings: [String] + + enum CodingKeys: String, CodingKey { + case name + case source + case detectedFormat = "detected_format" + case selectedOrdinal = "selected_ordinal" + case selectedName = "selected_name" + case nodes + case warnings + } +} + +private struct StoredProxySubscriptionSource: Decodable { + var url: String +} + +private struct StoredProxySubscriptionNode: Decodable { + var ordinal: Int + var name: String + var proxyProtocol: String + var server: String + var port: Int + var warnings: [String] + var config: StoredProxySubscriptionNodeConfig + + enum CodingKeys: String, CodingKey { + case ordinal + case name + case proxyProtocol = "protocol" + case server + case port + case warnings + case config + } +} + +private struct StoredProxySubscriptionNodeConfig: Decodable { + var type: String + var network: StoredProxySubscriptionNetworkTransport? + var cipher: String? + var plugin: StoredJSONValue? + var udpOverTCP: Bool? + + var runtimeSupported: Bool { + switch type { + case "trojan": + return network?.isTrojanTCP ?? true + case "shadowsocks": + return plugin == nil + && udpOverTCP != true + && Self.supportedShadowsocksCiphers.contains(cipher?.lowercased() ?? "") + default: + return false + } + } + + enum CodingKeys: String, CodingKey { + case type + case network + case cipher + case plugin + case udpOverTCP = "udp_over_tcp" + } + + private static let supportedShadowsocksCiphers: Set = [ + "aes-128-gcm", + "aead_aes_128_gcm", + "aes-256-gcm", + "aead_aes_256_gcm", + "chacha20-ietf-poly1305", + "chacha20-poly1305", + "aead_chacha20_poly1305", + ] +} + +private enum StoredProxySubscriptionNetworkTransport: Decodable, Hashable { + case named(String) + case keyed(String) + + var isTrojanTCP: Bool { + switch self { + case .named(let value), .keyed(let value): + return value == "tcp" + } + } + + init(from decoder: Swift.Decoder) throws { + let container = try decoder.singleValueContainer() + if let value = try? container.decode(String.self) { + self = .named(value) + return + } + let object = try container.decode([String: StoredJSONValue].self) + self = .keyed(object.keys.first ?? "") + } +} + +private enum StoredJSONValue: Decodable, Hashable { + case string(String) + case number(Double) + case bool(Bool) + case object([String: StoredJSONValue]) + case array([StoredJSONValue]) + case null + + init(from decoder: Swift.Decoder) throws { + let container = try decoder.singleValueContainer() + if container.decodeNil() { + self = .null + } else if let value = try? container.decode(Bool.self) { + self = .bool(value) + } else if let value = try? container.decode(Double.self) { + self = .number(value) + } else if let value = try? container.decode(String.self) { + self = .string(value) + } else if let value = try? container.decode([StoredJSONValue].self) { + self = .array(value) + } else { + self = .object(try container.decode([String: StoredJSONValue].self)) + } + } +} + enum TailnetDiscoveryClient { static func discover(email: String, socketURL: URL) async throws -> TailnetDiscoveryResponse { var request = Burrow_TailnetDiscoverRequest() @@ -139,6 +351,76 @@ enum TailnetLoginClient { } } +enum ProxySubscriptionImportClient { + static func preview(url: String, socketURL: URL) async throws -> ProxySubscriptionPreview { + var request = Burrow_ProxySubscriptionImportRequest() + request.url = url + + let response = try await ProxySubscriptionClient.unix(socketURL: socketURL) + .previewImport(request) + return ProxySubscriptionPreview( + suggestedName: response.suggestedName, + detectedFormat: response.detectedFormat, + compatibleCount: response.compatibleCount, + rejectedCount: response.rejectedCount, + nodes: response.node.map { node in + ProxySubscriptionNodePreview( + ordinal: node.ordinal, + name: node.name, + proxyProtocol: node.`protocol`, + server: node.server, + port: node.port, + warnings: node.warnings, + runtimeSupported: node.runtimeSupported + ) + }, + warnings: response.warnings + ) + } + + static func apply( + id: Int32, + url: String, + name: String, + selectedOrdinal: Int32?, + socketURL: URL + ) async throws -> Int32 { + var request = Burrow_ProxySubscriptionApplyRequest() + request.id = id + request.url = url + request.name = name + request.selectedOrdinal = selectedOrdinal ?? -1 + let response = try await ProxySubscriptionClient.unix(socketURL: socketURL) + .applyImport(request) + return response.id + } + + static func selectNode( + id: Int32, + selectedOrdinal: Int32, + socketURL: URL + ) async throws -> Int32 { + var request = Burrow_ProxySubscriptionSelectRequest() + request.id = id + request.selectedOrdinal = selectedOrdinal + let response = try await ProxySubscriptionClient.unix(socketURL: socketURL) + .selectNode(request) + return response.selectedOrdinal + } +} + +private struct DaemonUnavailableError: LocalizedError { + var socketPath: String + + var errorDescription: String? { + "Burrow daemon is not reachable yet. Enable the tunnel or start the daemon, then try again." + } + + var failureReason: String? { + "The daemon socket does not exist at \(socketPath)." + } +} + @Observable @MainActor final class NetworkViewModel: Sendable { @@ -161,6 +443,16 @@ final class NetworkViewModel: Sendable { networks.map(Self.makeCard(for:)) } + var nonProxyCards: [NetworkCardModel] { + networks + .filter { $0.type != .proxySubscription } + .map(Self.makeCard(for:)) + } + + var proxySubscriptions: [ProxySubscriptionNetwork] { + networks.compactMap(ProxySubscriptionNetwork.init) + } + var nextNetworkID: Int32 { (networks.map(\.id).max() ?? 0) + 1 } @@ -173,13 +465,83 @@ final class NetworkViewModel: Sendable { try await addNetwork(type: .tailnet, payload: payload.encoded()) } + func previewProxySubscription(url: String) async throws -> ProxySubscriptionPreview { + let socketURL = try daemonSocketURL() + return try await ProxySubscriptionImportClient.preview(url: url, socketURL: socketURL) + } + + func importProxySubscription(url: String, name: String, selectedOrdinal: Int32?) async throws -> Int32 { + let socketURL = try daemonSocketURL() + let networkID = nextNetworkID + return try await ProxySubscriptionImportClient.apply( + id: networkID, + url: url, + name: name, + selectedOrdinal: selectedOrdinal, + socketURL: socketURL + ) + } + + func updateProxySubscriptionSelection( + network: ProxySubscriptionNetwork, + selectedOrdinal: Int32 + ) async throws -> Int32 { + let socketURL = try daemonSocketURL() + return try await ProxySubscriptionImportClient.selectNode( + id: network.id, + selectedOrdinal: selectedOrdinal, + socketURL: socketURL + ) + } + + func updateActiveProxySubscriptionSelection( + network: ProxySubscriptionNetwork, + selectedOrdinal: Int32 + ) async throws -> Int32 { + let socketURL = try Constants.packetTunnelSocketURL + return try await ProxySubscriptionImportClient.selectNode( + id: network.id, + selectedOrdinal: selectedOrdinal, + socketURL: socketURL + ) + } + + func refreshProxySubscription(network: ProxySubscriptionNetwork) async throws -> Int32 { + let socketURL = try daemonSocketURL() + return try await ProxySubscriptionImportClient.apply( + id: network.id, + url: network.sourceURL, + name: network.name, + selectedOrdinal: network.selectedNode?.ordinal, + socketURL: socketURL + ) + } + + func refreshActiveProxySubscription(network: ProxySubscriptionNetwork) async throws -> Int32 { + let socketURL = try Constants.packetTunnelSocketURL + return try await ProxySubscriptionImportClient.apply( + id: network.id, + url: network.sourceURL, + name: network.name, + selectedOrdinal: network.selectedNode?.ordinal, + socketURL: socketURL + ) + } + + func deleteNetwork(id: Int32) async throws { + let socketURL = try daemonSocketURL() + var request = Burrow_NetworkDeleteRequest() + request.id = id + _ = try await NetworksClient.unix(socketURL: socketURL).networkDelete(request) + } + func discoverTailnet(email: String) async throws -> TailnetDiscoveryResponse { - let socketURL = try socketURLResult.get() + let socketURL = try daemonSocketURL() return try await TailnetDiscoveryClient.discover(email: email, socketURL: socketURL) } func probeTailnetAuthority(_ authority: String) async throws -> TailnetAuthorityProbeStatus { - let socketURL = try socketURLResult.get() + let socketURL = try daemonSocketURL() return try await TailnetAuthorityProbeClient.probe(authority: authority, socketURL: socketURL) } @@ -189,7 +551,7 @@ final class NetworkViewModel: Sendable { hostname: String?, authority: String ) async throws -> TailnetLoginStatus { - let socketURL = try socketURLResult.get() + let socketURL = try daemonSocketURL() return try await TailnetLoginClient.start( accountName: accountName, identityName: identityName, @@ -200,17 +562,17 @@ final class NetworkViewModel: Sendable { } func tailnetLoginStatus(sessionID: String) async throws -> TailnetLoginStatus { - let socketURL = try socketURLResult.get() + let socketURL = try daemonSocketURL() return try await TailnetLoginClient.status(sessionID: sessionID, socketURL: socketURL) } func cancelTailnetLogin(sessionID: String) async throws { - let socketURL = try socketURLResult.get() + let socketURL = try daemonSocketURL() try await TailnetLoginClient.cancel(sessionID: sessionID, socketURL: socketURL) } private func addNetwork(type: Burrow_NetworkType, payload: Data) async throws -> Int32 { - let socketURL = try socketURLResult.get() + let socketURL = try daemonSocketURL() let networkID = nextNetworkID let request = Burrow_Network.with { $0.id = networkID @@ -223,12 +585,25 @@ final class NetworkViewModel: Sendable { return networkID } + private func daemonSocketURL() throws -> URL { + try Self.daemonSocketURL(from: socketURLResult) + } + + private static func daemonSocketURL(from result: Result) throws -> URL { + let socketURL = try result.get() + let socketPath = socketURL.path(percentEncoded: false) + guard FileManager.default.fileExists(atPath: socketPath) else { + throw DaemonUnavailableError(socketPath: socketPath) + } + return socketURL + } + private func startStreaming() { task?.cancel() let socketURLResult = self.socketURLResult task = Task { [weak self] in do { - let socketURL = try socketURLResult.get() + let socketURL = try Self.daemonSocketURL(from: socketURLResult) let client = NetworksClient.unix(socketURL: socketURL) for try await response in client.networkList(.init()) { guard !Task.isCancelled else { return } @@ -254,6 +629,14 @@ final class NetworkViewModel: Sendable { WireGuardCard(network: network).card case .tailnet: TailnetCard(network: network).card + case .trojan: + unsupportedCard( + id: network.id, + title: "Legacy Trojan Proxy", + detail: "Unsupported SOCKS-era profile. Import a proxy subscription instead." + ) + case .proxySubscription: + proxySubscriptionCard(network: network) case .UNRECOGNIZED(let rawValue): unsupportedCard( id: network.id, @@ -269,6 +652,76 @@ final class NetworkViewModel: Sendable { } } + private static func proxySubscriptionCard(network: Burrow_Network) -> NetworkCardModel { + guard let subscription = ProxySubscriptionNetwork(network: network) else { + return unsupportedCard( + id: network.id, + title: "Proxy Subscription", + detail: "Imported proxy subscription." + ) + } + return NetworkCardModel( + id: subscription.id, + backgroundColor: .teal, + label: AnyView( + VStack(alignment: .leading, spacing: 10) { + HStack(alignment: .top) { + VStack(alignment: .leading, spacing: 4) { + Text("Proxy Subscription") + .font(.headline) + .foregroundStyle(.white.opacity(0.85)) + Text(subscription.name) + .font(.title3.weight(.semibold)) + .foregroundStyle(.white) + .lineLimit(1) + .truncationMode(.tail) + } + Spacer() + } + Spacer() + if let selectedNode = subscription.selectedNode { + VStack(alignment: .leading, spacing: 6) { + Text("Selected node") + .font(.caption.weight(.medium)) + .foregroundStyle(.white.opacity(0.72)) + + HStack(spacing: 8) { + Text(selectedNode.name) + .font(.headline.weight(.semibold)) + .foregroundStyle(.white) + .lineLimit(1) + .truncationMode(.tail) + + Text(selectedNode.proxyProtocol.uppercased()) + .font(.caption2.weight(.bold)) + .foregroundStyle(.white.opacity(0.78)) + .padding(.horizontal, 7) + .padding(.vertical, 3) + .background( + Capsule() + .fill(.white.opacity(0.18)) + ) + } + + Text("\(selectedNode.server):\(selectedNode.port)") + .font(.caption.monospaced()) + .foregroundStyle(.white.opacity(0.78)) + .lineLimit(1) + .truncationMode(.middle) + } + } + Text("\(subscription.runtimeSupportedNodes.count) usable of \(subscription.nodes.count) nodes · \(subscription.detectedFormat)") + .font(.footnote) + .foregroundStyle(.white.opacity(0.78)) + .lineLimit(1) + .truncationMode(.tail) + } + .padding() + .frame(maxWidth: .infinity, alignment: .leading) + ) + ) + } + private static func unsupportedCard(id: Int32, title: String, detail: String) -> NetworkCardModel { NetworkCardModel( id: id, @@ -278,9 +731,13 @@ final class NetworkViewModel: Sendable { Text(title) .font(.title3.weight(.semibold)) .foregroundStyle(.white) + .lineLimit(2) + .truncationMode(.tail) Text(detail) .font(.body) .foregroundStyle(.white.opacity(0.9)) + .lineLimit(4) + .truncationMode(.tail) Spacer() Text("Network #\(id)") .font(.footnote.monospaced()) @@ -358,6 +815,7 @@ enum TailnetProvider: String, CaseIterable, Codable, Identifiable, Sendable { enum AccountNetworkKind: String, CaseIterable, Codable, Identifiable, Sendable { case wireGuard + case proxySubscription case tor case tailnet @@ -366,6 +824,7 @@ enum AccountNetworkKind: String, CaseIterable, Codable, Identifiable, Sendable { var title: String { switch self { case .wireGuard: "WireGuard" + case .proxySubscription: "Proxy Subscription" case .tor: "Tor" case .tailnet: "Tailnet" } @@ -374,6 +833,7 @@ enum AccountNetworkKind: String, CaseIterable, Codable, Identifiable, Sendable { var subtitle: String { switch self { case .wireGuard: "Import a tunnel and optional account metadata." + case .proxySubscription: "Import Trojan and Shadowsocks subscription nodes." case .tor: "Store Arti account and identity preferences." case .tailnet: "Save Tailnet authority, identity defaults, and login material." } @@ -382,6 +842,7 @@ enum AccountNetworkKind: String, CaseIterable, Codable, Identifiable, Sendable { var accentColor: Color { switch self { case .wireGuard: .init("WireGuard") + case .proxySubscription: .teal case .tor: .orange case .tailnet: .mint } @@ -390,6 +851,7 @@ enum AccountNetworkKind: String, CaseIterable, Codable, Identifiable, Sendable { var actionTitle: String { switch self { case .wireGuard: "Add Network" + case .proxySubscription: "Import" case .tor: "Save Account" case .tailnet: "Save Account" } @@ -397,7 +859,7 @@ enum AccountNetworkKind: String, CaseIterable, Codable, Identifiable, Sendable { var availabilityNote: String? { switch self { - case .wireGuard: + case .wireGuard, .proxySubscription: nil case .tor: "Tor account preferences are stored on Apple now. The managed Tor runtime is not wired on Apple in this branch yet." diff --git a/Cargo.lock b/Cargo.lock index 2950701..53e4faf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -437,6 +437,28 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "aws-lc-rs" +version = "1.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec6fb3fe69024a75fa7e1bfb48aa6cf59706a101658ea01bfd33b2b248a038f" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f50037ee5e1e41e7b8f9d161680a725bd1626cb6f8c7e901f91f942850852fe7" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", +] + [[package]] name = "axum" version = "0.6.20" @@ -733,6 +755,8 @@ dependencies = [ "libc", "libsystemd", "log", + "md-5", + "native-tls", "netstack-smoltcp", "nix 0.27.1", "once_cell", @@ -745,12 +769,17 @@ dependencies = [ "ring", "rusqlite", "rust-ini", + "rustls", "schemars 0.8.22", "serde", "serde_json", + "serde_yaml", + "sha2", "subtle", "tempfile", "tokio", + "tokio-native-tls", + "tokio-rustls", "tokio-stream", "tokio-util", "toml 0.8.23", @@ -764,6 +793,7 @@ dependencies = [ "tracing-oslog", "tracing-subscriber", "tun", + "webpki-roots 0.26.11", "x25519-dalek", ] @@ -835,9 +865,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.38" +version = "1.2.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80f41ae168f955c12fb8960b057d70d0ca153fb83182b57d86380443527be7e9" +checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d" dependencies = [ "find-msvc-tools", "jobserver", @@ -991,6 +1021,15 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" +[[package]] +name = "cmake" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" +dependencies = [ + "cc", +] + [[package]] name = "coarsetime" version = "0.1.37" @@ -1669,6 +1708,12 @@ version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "117240f60069e65410b3ae1bb213295bd828f707b5bec6596a1afc8793ce0cbc" +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + [[package]] name = "dyn-clone" version = "1.0.20" @@ -1951,9 +1996,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.2" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ced73b1dacfc750a6db6c0a0c3a3853c8b41997e2e2c563dc90804ae6867959" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "fixedbitset" @@ -2034,6 +2079,12 @@ dependencies = [ "walkdir", ] +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "fslock" version = "0.2.1" @@ -2566,7 +2617,7 @@ dependencies = [ "tokio", "tokio-rustls", "tower-service", - "webpki-roots", + "webpki-roots 1.0.3", ] [[package]] @@ -3170,6 +3221,16 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + [[package]] name = "memchr" version = "2.7.5" @@ -4484,7 +4545,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots", + "webpki-roots 1.0.3", ] [[package]] @@ -4645,6 +4706,8 @@ version = "0.23.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a9586e9ee2b4f8fab52a0048ca7334d7024eef48e2cb9407e3497bb7cab7fa7" dependencies = [ + "aws-lc-rs", + "log", "once_cell", "ring", "rustls-pki-types", @@ -4678,6 +4741,7 @@ version = "0.103.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" dependencies = [ + "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", @@ -4984,6 +5048,19 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap 2.11.4", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + [[package]] name = "sha-1" version = "0.10.1" @@ -6954,9 +7031,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.18.0" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" [[package]] name = "uncased" @@ -7007,6 +7084,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + [[package]] name = "untrusted" version = "0.9.0" @@ -7265,6 +7348,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki-roots" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.3", +] + [[package]] name = "webpki-roots" version = "1.0.3" diff --git a/Scripts/burrow-doctor b/Scripts/burrow-doctor new file mode 100755 index 0000000..a364e59 --- /dev/null +++ b/Scripts/burrow-doctor @@ -0,0 +1,434 @@ +#!/usr/bin/env bash +set -euo pipefail + +usage() { + cat <<'EOF' +Usage: Scripts/burrow-doctor [--last 10m] + +Print a single stdout diagnostics report for the CURRENT macOS networking state. + +Run this while Burrow is the active VPN but routing/websites are broken, then +redirect stdout to a file before switching to a working VPN: + + Scripts/burrow-doctor --last 10m > burrow-broken.txt + +The report focuses on live VPN/proxy/DNS/route state, Burrow NetworkExtension +state, socket ownership, the selected Burrow node, and recent Burrow logs. +It intentionally does not redact local output, because broken proxy state often +depends on exact subscription and node configuration. +EOF +} + +last="10m" +while [[ $# -gt 0 ]]; do + case "$1" in + --last) + [[ $# -ge 2 ]] || { echo "--last requires a value" >&2; exit 2; } + last="$2" + shift 2 + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "unknown argument: $1" >&2 + usage >&2 + exit 2 + ;; + esac +done + +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +group_id="${BURROW_APP_GROUP_ID:-2KPBQHRJ2D.com.tianruichen.burrow}" +group_dir="${BURROW_APP_GROUP_DIR:-$HOME/Library/Group Containers/$group_id}" +db_path="${BURROW_DB_PATH:-$group_dir/burrow.db}" +runtime_log="${BURROW_RUNTIME_LOG:-$group_dir/burrow-runtime.log}" +bundle_id="${BURROW_BUNDLE_ID:-com.tianruichen.burrow}" +extension_process="${BURROW_EXTENSION_PROCESS:-BurrowNetworkExtension}" + +section() { + printf '\n## %s\n\n' "$1" +} + +cmd() { + printf '$' + printf ' %q' "$@" + printf '\n' +} + +run() { + local title="$1" + shift + section "$title" + cmd "$@" + printf '\n' + "$@" 2>&1 || printf '\n[burrow-doctor] command exited with status %s\n' "$?" +} + +run_zsh() { + local title="$1" + local script="$2" + section "$title" + printf '$ %s\n\n' "$script" + /bin/zsh -lc "$script" 2>&1 || printf '\n[burrow-doctor] command exited with status %s\n' "$?" +} + +run_python() { + if command -v uv >/dev/null 2>&1; then + uv run python "$@" + elif command -v python3 >/dev/null 2>&1; then + python3 "$@" + else + return 127 + fi +} + +capture() { + "$@" 2>&1 || true +} + +nc_list="$(capture scutil --nc list)" +proxy_state="$(capture scutil --proxy)" +dns_state="$(capture scutil --dns)" +inet_routes="$(capture netstat -rn -f inet)" +proxy_listeners="$(capture /bin/zsh -lc "lsof -nP -iTCP:6152 -iTCP:6153 -iTCP:7890 -iTCP:7891 -iTCP:7897 -iTCP:1080 -iTCP:1087 -sTCP:LISTEN")" + +cat < dict | None: + nodes = payload.get("nodes") or [] + selected_name = payload.get("selected_name") + selected_ordinal = payload.get("selected_ordinal") + for node in nodes: + if selected_name is not None and node.get("name") == selected_name: + return node + if selected_ordinal is not None and node.get("ordinal") == selected_ordinal: + return node + return nodes[0] if nodes else None + + +conn = sqlite3.connect(db_path) +conn.row_factory = sqlite3.Row +row = conn.execute( + "select payload from network where type = 'ProxySubscription' order by idx limit 1" +).fetchone() +if row is None: + print("No ProxySubscription network in database.") + raise SystemExit + +payload_blob = row["payload"] +payload_text = payload_blob.decode("utf-8", errors="replace") if isinstance(payload_blob, bytes) else payload_blob +payload = json.loads(payload_text) +node = choose_node(payload) +if not node: + print("ProxySubscription has no nodes.") + raise SystemExit + +server = node.get("server") +port = int(node.get("port")) +print(f"selected_proxy_server={server}:{port}") + +addresses = [] +try: + for family, socktype, proto, canonname, sockaddr in socket.getaddrinfo(server, port, type=socket.SOCK_STREAM): + host, resolved_port = sockaddr[:2] + key = (host, resolved_port) + if key not in addresses: + addresses.append(key) +except Exception as exc: + print(f"resolve_error={type(exc).__name__}: {exc}") + raise SystemExit + +print("resolved_addresses=" + ",".join(f"{host}:{resolved_port}" for host, resolved_port in addresses)) + +for host, _ in addresses: + print(f"\n$ route -n get {host}") + try: + result = subprocess.run( + ["route", "-n", "get", host], + text=True, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + timeout=5, + ) + print(result.stdout.rstrip()) + print(f"[exit={result.returncode}]") + except Exception as exc: + print(f"route_probe_error={type(exc).__name__}: {exc}") + +for host, resolved_port in addresses: + started = time.monotonic() + try: + with socket.create_connection((host, resolved_port), timeout=5): + elapsed_ms = (time.monotonic() - started) * 1000 + print(f"tcp_connect_ok address={host}:{resolved_port} elapsed_ms={elapsed_ms:.1f}") + except Exception as exc: + elapsed_ms = (time.monotonic() - started) * 1000 + print(f"tcp_connect_error address={host}:{resolved_port} elapsed_ms={elapsed_ms:.1f} error={type(exc).__name__}: {exc}") +PY +else + echo "DB not found: $db_path" +fi + +run_zsh "Route Probes" "for host in 1.1.1.1 8.8.8.8 google.com; do echo; echo \"\$ route -n get \$host\"; route -n get \"\$host\" 2>&1 || true; done" + +run_zsh "DNS Probes" "if command -v dig >/dev/null 2>&1; then dig +time=3 +tries=1 google.com A; echo; dig +time=3 +tries=1 @1.1.1.1 google.com A; else dscacheutil -q host -a name google.com; fi" + +run_zsh "Scoped Physical DNS Probes" "if command -v dig >/dev/null 2>&1; then servers=\$(scutil --dns | awk 'BEGIN { scoped=0; current=\"\" } /^DNS configuration \\(for scoped queries\\)/ { scoped=1; next } scoped && /^resolver #/ { current=\"\"; next } scoped && /nameserver\\[[0-9]+\\]/ { current=\$3 } scoped && /if_index/ && \$0 !~ /utun/ && current != \"\" { print current; current=\"\" }'); if [[ -z \"\$servers\" ]]; then echo 'No scoped non-utun DNS servers found.'; else for server in \$servers; do echo; echo \"\$ dig +time=3 +tries=1 @\$server google.com A\"; dig +time=3 +tries=1 @\"\$server\" google.com A; done; fi; else echo 'dig not found'; fi" + +run_zsh "TCP Probes" "for target in '1.1.1.1 53' '1.1.1.1 443' 'google.com 443'; do set -- \$target; echo; echo \"\$ nc -vz -w 5 \$1 \$2\"; nc -vz -w 5 \"\$1\" \"\$2\" 2>&1 || true; done" + +run_zsh "HTTP Probes" "for url in 'http://www.gstatic.com/generate_204' 'https://www.google.com/generate_204' 'https://www.cloudflare.com/cdn-cgi/trace'; do echo; echo \"\$ curl -4 -I --connect-timeout 5 --max-time 10 \$url\"; curl -4 -I --connect-timeout 5 --max-time 10 \"\$url\" 2>&1 || true; done" + +run_zsh "Burrow Controlled Proxy Self-Test" "if [[ -x '$repo_root/Scripts/burrow-proxy-selftest' ]]; then '$repo_root/Scripts/burrow-proxy-selftest'; else echo 'Scripts/burrow-proxy-selftest not found or not executable'; fi" + +run_zsh "Burrow Direct Provider Proxy Ladder" "if [[ -x '$repo_root/Scripts/burrow-proxy-ladder' ]]; then '$repo_root/Scripts/burrow-proxy-ladder' --db '$db_path' --resolve-server doh-all --target google.com:80 --http-host google.com --skip-udp --timeout 8; else echo 'Scripts/burrow-proxy-ladder not found or not executable'; fi" + +run_zsh "Burrow Daemon Packet Proxy Probe" "if [[ -x '$repo_root/target/debug/burrow' ]]; then BURROW_SOCKET_PATH=\"\${BURROW_SOCKET_PATH:-burrow.sock}\" '$repo_root/target/debug/burrow' proxy-tcp-probe --dns-name google.com --remote 1.1.1.1:80 --timeout-ms 12000; else echo 'target/debug/burrow not found; run cargo build -p burrow first'; fi" + +run_zsh "Burrow Daemon HTTPS Packet Proxy Probe" "if [[ -x '$repo_root/target/debug/burrow' ]]; then BURROW_SOCKET_PATH=\"\${BURROW_SOCKET_PATH:-burrow.sock}\" '$repo_root/target/debug/burrow' proxy-https-probe --dns-name news.ycombinator.com --remote 1.1.1.1:443 --timeout-ms 20000; else echo 'target/debug/burrow not found; run cargo build -p burrow first'; fi" + +section "Proxy Subscription Endpoint Status" +if [[ -f "$db_path" ]]; then + run_python - "$db_path" <<'PY' +from __future__ import annotations + +import json +import sqlite3 +import sys +import urllib.error +import urllib.request + +db_path = sys.argv[1] + +conn = sqlite3.connect(db_path) +conn.row_factory = sqlite3.Row +row = conn.execute( + "select payload from network where type = 'ProxySubscription' order by idx limit 1" +).fetchone() +if row is None: + print("No ProxySubscription network in database.") + raise SystemExit + +payload_blob = row["payload"] +payload_text = payload_blob.decode("utf-8", errors="replace") if isinstance(payload_blob, bytes) else payload_blob +payload = json.loads(payload_text) +url = (payload.get("source") or {}).get("url") +if not url: + print("ProxySubscription has no source URL.") + raise SystemExit + +print("source_url_redacted=yes") +request = urllib.request.Request(url, headers={"User-Agent": "mihomo/1.18.3"}) +try: + with urllib.request.urlopen(request, timeout=20) as response: + body = response.read(1024 * 1024) + print(f"status={response.status}") + for key, value in response.headers.items(): + lower = key.lower() + if ( + lower in {"subscription-userinfo", "profile-update-interval", "profile-title", "content-type"} + or "expire" in lower + or "subscription" in lower + ): + print(f"header_{key}={value}") + print(f"body_bytes={len(body)}") +except urllib.error.HTTPError as exc: + print(f"status={exc.code}") + print(f"reason={exc.reason}") + for key, value in exc.headers.items(): + lower = key.lower() + if ( + lower in {"subscription-userinfo", "profile-update-interval", "profile-title", "content-type"} + or "expire" in lower + or "subscription" in lower + ): + print(f"header_{key}={value}") + body = exc.read(1000).decode("utf-8", errors="replace").strip() + if body: + print(f"body_prefix={body[:200]}") +except Exception as exc: + print(f"fetch_error={type(exc).__name__}: {exc}") +PY +else + echo "DB not found: $db_path" +fi + +section "Burrow Runtime Log File" +if [[ -f "$runtime_log" ]]; then + cmd tail -400 "$runtime_log" + printf '\n' + tail -400 "$runtime_log" 2>&1 || printf '\n[burrow-doctor] command exited with status %s\n' "$?" +else + echo "Runtime log not found: $runtime_log" +fi + +section "Recent Burrow NetworkExtension Logs" +cmd /usr/bin/log show --last "$last" --style compact --info --debug --predicate "process == \"nesessionmanager\" AND eventMessage CONTAINS[c] \"Burrow\"" +printf '\n' +{ /usr/bin/log show --last "$last" --style compact --info --debug --predicate "process == \"nesessionmanager\" AND eventMessage CONTAINS[c] \"Burrow\"" 2>&1 \ + || printf '\n[burrow-doctor] log command exited with status %s\n' "$?"; } + +section "Recent Burrow Extension Logs" +cmd /usr/bin/log show --last "$last" --style compact --info --debug --predicate "process == \"$extension_process\" OR eventMessage CONTAINS[c] \"$bundle_id.network\"" +printf '\n' +{ /usr/bin/log show --last "$last" --style compact --info --debug --predicate "process == \"$extension_process\" OR eventMessage CONTAINS[c] \"$bundle_id.network\"" 2>&1 \ + || printf '\n[burrow-doctor] log command exited with status %s\n' "$?"; } + +section "Recent Burrow Errors" +cmd /usr/bin/log show --last "$last" --style compact --info --debug --predicate "((process CONTAINS[c] \"Burrow\") OR (eventMessage CONTAINS[c] \"$bundle_id\") OR (eventMessage CONTAINS[c] \"Trojan\") OR (eventMessage CONTAINS[c] \"proxy\")) AND (messageType == error OR messageType == fault OR eventMessage CONTAINS[c] \"failed\" OR eventMessage CONTAINS[c] \"error\" OR eventMessage CONTAINS[c] \"panic\")" +printf '\n' +{ /usr/bin/log show --last "$last" --style compact --info --debug --predicate "((process CONTAINS[c] \"Burrow\") OR (eventMessage CONTAINS[c] \"$bundle_id\") OR (eventMessage CONTAINS[c] \"Trojan\") OR (eventMessage CONTAINS[c] \"proxy\")) AND (messageType == error OR messageType == fault OR eventMessage CONTAINS[c] \"failed\" OR eventMessage CONTAINS[c] \"error\" OR eventMessage CONTAINS[c] \"panic\")" 2>&1 \ + || printf '\n[burrow-doctor] log command exited with status %s\n' "$?"; } diff --git a/Scripts/burrow-proxy-ladder b/Scripts/burrow-proxy-ladder new file mode 100755 index 0000000..9c1da3d --- /dev/null +++ b/Scripts/burrow-proxy-ladder @@ -0,0 +1,576 @@ +#!/usr/bin/env python3 +"""Probe a Burrow proxy subscription node without enabling system proxying. + +This script intentionally bypasses the Burrow daemon, Network Extension, +routes, DNS settings, and browsers. It loads the selected proxy node from the +Burrow SQLite database and speaks the provider protocol directly. +""" + +from __future__ import annotations + +import argparse +import hashlib +import ipaddress +import json +import os +import socket +import sqlite3 +import ssl +import struct +import sys +import time +import urllib.parse +import urllib.request +from dataclasses import dataclass +from pathlib import Path +from typing import Any + + +DEFAULT_APP_GROUP = "2KPBQHRJ2D.com.tianruichen.burrow" +DEFAULT_DB = ( + Path.home() + / "Library" + / "Group Containers" + / DEFAULT_APP_GROUP + / "burrow.db" +) +DEFAULT_TARGET = "1.1.1.1:80" +DEFAULT_HTTP_HOST = "one.one.one.one" +DEFAULT_DNS_SERVER = "1.1.1.1:53" +DEFAULT_DNS_NAME = "google.com" +DEFAULT_TIMEOUT = 5.0 +DOH_PROVIDERS = { + "doh-google": "https://dns.google/resolve", + "doh-cloudflare": "https://cloudflare-dns.com/dns-query", +} + +TROJAN_CMD_CONNECT = 0x01 +TROJAN_CMD_UDP_ASSOCIATE = 0x03 + + +@dataclass(frozen=True) +class Endpoint: + host: str + port: int + + +@dataclass(frozen=True) +class ProbeResult: + ok: bool + detail: str + + +def parse_endpoint(value: str, default_port: int | None = None) -> Endpoint: + text = value.strip() + if not text: + raise ValueError("endpoint must not be empty") + + if text.startswith("["): + end = text.find("]") + if end == -1: + raise ValueError(f"invalid bracketed IPv6 endpoint: {value}") + host = text[1:end] + rest = text[end + 1 :] + if rest.startswith(":"): + port = int(rest[1:]) + elif default_port is not None: + port = default_port + else: + raise ValueError(f"endpoint is missing a port: {value}") + return Endpoint(host, port) + + if text.count(":") == 1: + host, port_text = text.rsplit(":", 1) + return Endpoint(host, int(port_text)) + + try: + ipaddress.ip_address(text) + except ValueError: + if ":" in text: + if default_port is None: + raise ValueError(f"IPv6 endpoint is missing a port: {value}") + return Endpoint(text, default_port) + if default_port is None: + raise ValueError(f"endpoint is missing a port: {value}") + return Endpoint(text, default_port) + + if default_port is None: + raise ValueError(f"endpoint is missing a port: {value}") + return Endpoint(text, default_port) + + +def encode_socks_addr(endpoint: Endpoint) -> bytes: + try: + ip = ipaddress.ip_address(endpoint.host) + except ValueError: + host = endpoint.host.encode("idna") + if len(host) > 255: + raise ValueError(f"SOCKS domain is too long: {endpoint.host}") + return bytes([3, len(host)]) + host + struct.pack("!H", endpoint.port) + + if ip.version == 4: + return bytes([1]) + ip.packed + struct.pack("!H", endpoint.port) + return bytes([4]) + ip.packed + struct.pack("!H", endpoint.port) + + +def read_exact(sock: ssl.SSLSocket, 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: ssl.SSLSocket) -> Endpoint: + atyp = read_exact(sock, 1)[0] + if atyp == 1: + host = str(ipaddress.IPv4Address(read_exact(sock, 4))) + elif atyp == 4: + host = str(ipaddress.IPv6Address(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 in response: {atyp}") + port = struct.unpack("!H", read_exact(sock, 2))[0] + return Endpoint(host, port) + + +def trojan_password_hash(password: str) -> bytes: + return hashlib.sha224(password.encode()).hexdigest().encode() + + +def trojan_header(password: str, command: int, target: Endpoint) -> bytes: + return ( + trojan_password_hash(password) + + b"\r\n" + + bytes([command]) + + encode_socks_addr(target) + + b"\r\n" + ) + + +def trojan_udp_frame(target: Endpoint, payload: bytes) -> bytes: + return encode_socks_addr(target) + struct.pack("!H", len(payload)) + b"\r\n" + payload + + +def build_dns_query(name: str) -> tuple[int, bytes]: + query_id = int(time.time() * 1000) & 0xFFFF + labels = [label for label in name.rstrip(".").split(".") if label] + qname = b"".join(bytes([len(label)]) + label.encode("ascii") for label in labels) + b"\x00" + header = struct.pack("!HHHHHH", query_id, 0x0100, 1, 0, 0, 0) + question = qname + struct.pack("!HH", 1, 1) + return query_id, header + question + + +def parse_dns_response(query_id: int, payload: bytes) -> str: + if len(payload) < 12: + return f"truncated DNS response: {len(payload)} bytes" + response_id, flags, questions, answers, authority, additional = struct.unpack( + "!HHHHHH", payload[:12] + ) + rcode = flags & 0x000F + qr = bool(flags & 0x8000) + id_note = "matching id" if response_id == query_id else f"id mismatch {response_id:#06x}" + return ( + f"{len(payload)} bytes, qr={qr}, rcode={rcode}, answers={answers}, " + f"authority={authority}, additional={additional}, questions={questions}, {id_note}" + ) + + +def load_payload(db_path: Path, network_id: int | None) -> tuple[int, dict[str, Any]]: + if not db_path.exists(): + raise FileNotFoundError(f"Burrow database not found: {db_path}") + conn = sqlite3.connect(str(db_path)) + conn.row_factory = sqlite3.Row + try: + if network_id is None: + row = conn.execute( + "select id,payload from network where type = 'ProxySubscription' order by idx limit 1" + ).fetchone() + else: + row = conn.execute( + "select id,payload from network where id = ? and type = 'ProxySubscription'", + (network_id,), + ).fetchone() + finally: + conn.close() + if row is None: + selector = "first ProxySubscription" if network_id is None else f"ProxySubscription id={network_id}" + raise LookupError(f"could not find {selector} in {db_path}") + payload_blob = row["payload"] + if isinstance(payload_blob, bytes): + payload_text = payload_blob.decode("utf-8", errors="replace") + else: + payload_text = payload_blob or "{}" + return int(row["id"]), json.loads(payload_text) + + +def choose_node( + payload: dict[str, Any], + node_name: str | None = None, + node_ordinal: int | None = None, +) -> dict[str, Any]: + nodes = payload.get("nodes") or [] + if node_name is not None: + for node in nodes: + if node.get("name") == node_name: + return node + raise LookupError(f"ProxySubscription payload has no node named {node_name!r}") + if node_ordinal is not None: + for node in nodes: + if node.get("ordinal") == node_ordinal: + return node + raise LookupError(f"ProxySubscription payload has no node with ordinal {node_ordinal}") + + selected_name = payload.get("selected_name") + selected_ordinal = payload.get("selected_ordinal") + for node in nodes: + if selected_name is not None and node.get("name") == selected_name: + return node + if selected_ordinal is not None and node.get("ordinal") == selected_ordinal: + return node + if nodes: + return nodes[0] + raise LookupError("ProxySubscription payload has no compatible nodes") + + +def resolved_stream_addresses(endpoint: Endpoint) -> list[tuple[int, tuple[Any, ...]]]: + addresses: list[tuple[int, tuple[Any, ...]]] = [] + seen: set[tuple[Any, ...]] = set() + for family, _, _, _, sockaddr in socket.getaddrinfo( + endpoint.host, endpoint.port, type=socket.SOCK_STREAM + ): + key = (family, sockaddr) + if key not in seen: + seen.add(key) + addresses.append((family, sockaddr)) + return addresses + + +def resolve_doh(host: str, port: int, provider: str, timeout: float) -> list[Endpoint]: + base = DOH_PROVIDERS[provider] + url = base + "?" + urllib.parse.urlencode({"name": host, "type": "A"}) + request = urllib.request.Request( + url, + headers={"Accept": "application/dns-json", "User-Agent": "burrow-proxy-ladder"}, + ) + with urllib.request.urlopen(request, timeout=timeout) as response: + body = json.load(response) + endpoints: list[Endpoint] = [] + for answer in body.get("Answer") or []: + if answer.get("type") != 1: + continue + address = str(answer.get("data") or "") + try: + ipaddress.IPv4Address(address) + except ValueError: + continue + endpoint = Endpoint(address, port) + if endpoint not in endpoints: + endpoints.append(endpoint) + return endpoints + + +def resolve_doh_all(host: str, port: int, timeout: float) -> list[Endpoint]: + endpoints: list[Endpoint] = [] + errors: list[str] = [] + for provider in DOH_PROVIDERS: + try: + provider_endpoints = resolve_doh(host, port, provider, timeout) + except BaseException as exc: + errors.append(f"{provider}={type(exc).__name__}: {exc}") + continue + print( + f"{provider}_answers=" + + ",".join(f"{endpoint.host}:{endpoint.port}" for endpoint in provider_endpoints) + ) + for endpoint in provider_endpoints: + if endpoint not in endpoints: + endpoints.append(endpoint) + if errors: + print("doh_warnings=" + "; ".join(errors)) + return endpoints + + +def is_fake_ip(host: str) -> bool: + try: + ip = ipaddress.ip_address(host) + except ValueError: + return False + return ip in ipaddress.ip_network("198.18.0.0/15") + + +def server_hostname(server: str, sni: str | None) -> str | None: + value = sni or server + if not value: + return None + return value + + +def tls_context(config: dict[str, Any]) -> ssl.SSLContext: + skip_verify = bool(config.get("skip_cert_verify")) + if skip_verify: + context = ssl._create_unverified_context() + else: + context = ssl.create_default_context() + + alpn_present = bool(config.get("alpn_present")) + alpn = config.get("alpn") or [] + if not alpn_present: + alpn = ["h2", "http/1.1"] + if alpn: + context.set_alpn_protocols([str(value) for value in alpn]) + return context + + +def connect_tls( + connect_endpoint: Endpoint, + tls_server_name: str, + node: dict[str, Any], + timeout: float, +) -> ssl.SSLSocket: + config = node["config"] + context = tls_context(config) + sni = server_hostname(tls_server_name, config.get("sni")) + last_error: BaseException | None = None + for _family, sockaddr in resolved_stream_addresses(connect_endpoint): + started = time.monotonic() + raw_sock: socket.socket | None = None + try: + raw_sock = socket.create_connection(sockaddr, timeout=timeout) + raw_sock.settimeout(timeout) + tls_sock = context.wrap_socket(raw_sock, server_hostname=sni) + tls_sock.settimeout(timeout) + elapsed_ms = (time.monotonic() - started) * 1000 + peer = sockaddr[0] if isinstance(sockaddr, tuple) else str(sockaddr) + print( + f" tls_connect_ok address={peer}:{connect_endpoint.port} " + f"elapsed_ms={elapsed_ms:.1f} alpn={tls_sock.selected_alpn_protocol()!r}" + ) + return tls_sock + except BaseException as exc: + last_error = exc + elapsed_ms = (time.monotonic() - started) * 1000 + peer = sockaddr[0] if isinstance(sockaddr, tuple) else str(sockaddr) + print( + f" tls_connect_error address={peer}:{connect_endpoint.port} " + f"elapsed_ms={elapsed_ms:.1f} error={type(exc).__name__}: {exc}" + ) + if raw_sock is not None: + raw_sock.close() + assert last_error is not None + raise last_error + + +def probe_trojan_tcp( + connect_endpoint: Endpoint, + tls_server_name: str, + node: dict[str, Any], + target: Endpoint, + http_host: str, + timeout: float, +) -> ProbeResult: + request = ( + f"GET / HTTP/1.1\r\n" + f"Host: {http_host}\r\n" + f"Connection: close\r\n" + f"User-Agent: burrow-proxy-ladder\r\n\r\n" + ).encode() + try: + with connect_tls(connect_endpoint, tls_server_name, node, timeout) as sock: + sock.sendall(trojan_header(node["config"]["password"], TROJAN_CMD_CONNECT, target)) + sock.sendall(request) + data = sock.recv(2048) + except BaseException as exc: + return ProbeResult(False, f"{type(exc).__name__}: {exc}") + if not data: + return ProbeResult(False, "proxy returned EOF before any HTTP response bytes") + first_line = data.splitlines()[0].decode("utf-8", errors="replace") + return ProbeResult(True, f"received {len(data)} bytes; first_line={first_line!r}") + + +def probe_trojan_udp_dns( + connect_endpoint: Endpoint, + tls_server_name: str, + node: dict[str, Any], + dns_server: Endpoint, + dns_name: str, + timeout: float, +) -> ProbeResult: + if node["config"].get("udp") is False: + return ProbeResult(False, "selected Trojan node has udp=false") + query_id, query = build_dns_query(dns_name) + try: + with connect_tls(connect_endpoint, tls_server_name, node, timeout) as sock: + sock.sendall( + trojan_header(node["config"]["password"], TROJAN_CMD_UDP_ASSOCIATE, dns_server) + ) + sock.sendall(trojan_udp_frame(dns_server, query)) + source = read_socks_addr(sock) + payload_len = struct.unpack("!H", read_exact(sock, 2))[0] + delimiter = read_exact(sock, 2) + if delimiter != b"\r\n": + return ProbeResult(False, f"invalid Trojan UDP delimiter: {delimiter!r}") + payload = read_exact(sock, payload_len) + except BaseException as exc: + return ProbeResult(False, f"{type(exc).__name__}: {exc}") + dns_summary = parse_dns_response(query_id, payload) + return ProbeResult( + True, + f"received UDP frame from {source.host}:{source.port}; DNS response {dns_summary}", + ) + + +def print_node_summary(network_id: int, payload: dict[str, Any], node: dict[str, Any]) -> None: + config = node.get("config") or {} + print(f"network_id={network_id}") + print(f"subscription={payload.get('name')!r}") + print(f"selected_node={node.get('name')!r}") + print(f"protocol={node.get('protocol')!r}") + print(f"server={node.get('server')}:{node.get('port')}") + print(f"sni={config.get('sni')!r}") + print(f"skip_cert_verify={bool(config.get('skip_cert_verify'))}") + print(f"alpn_present={bool(config.get('alpn_present'))} alpn={config.get('alpn') or []!r}") + print(f"udp={config.get('udp', True)!r}") + + +def main() -> int: + parser = argparse.ArgumentParser( + description=( + "Directly probe the selected Burrow proxy subscription node without " + "using the daemon, Network Extension, system proxy, routes, or DNS settings." + ) + ) + parser.add_argument("--db", type=Path, default=Path(os.environ.get("BURROW_DB", DEFAULT_DB))) + parser.add_argument("--network-id", type=int) + parser.add_argument("--node-name", help="Probe this subscription node instead of the selected node.") + parser.add_argument("--node-ordinal", type=int, help="Probe this subscription ordinal instead of the selected node.") + parser.add_argument( + "--server-address", + help=( + "Override the proxy connect address, as host or host:port. " + "This bypasses system DNS for the proxy hostname while preserving SNI." + ), + ) + parser.add_argument( + "--resolve-server", + choices=["system", "doh-google", "doh-cloudflare", "doh-all"], + default="system", + help="How to resolve the proxy server when --server-address is not provided.", + ) + parser.add_argument( + "--all-server-addresses", + action="store_true", + help="Probe every resolved proxy server address and pass if any address works.", + ) + parser.add_argument("--target", default=DEFAULT_TARGET, help="TCP HTTP target, host:port") + parser.add_argument("--http-host", default=DEFAULT_HTTP_HOST) + parser.add_argument("--dns-server", default=DEFAULT_DNS_SERVER, help="UDP DNS target, host:port") + parser.add_argument("--dns-name", default=DEFAULT_DNS_NAME) + parser.add_argument("--timeout", type=float, default=DEFAULT_TIMEOUT) + parser.add_argument("--skip-udp", action="store_true") + parser.add_argument("--require-udp", action="store_true") + args = parser.parse_args() + + print("Burrow proxy ladder: direct provider/protocol probe") + print("bypasses=daemon,NetworkExtension,system_proxy,routes,system_dns,browser") + print(f"db={args.db}") + + try: + if args.node_name is not None and args.node_ordinal is not None: + raise ValueError("--node-name and --node-ordinal are mutually exclusive") + network_id, payload = load_payload(args.db, args.network_id) + node = choose_node(payload, args.node_name, args.node_ordinal) + print_node_summary(network_id, payload, node) + if node.get("protocol") != "trojan": + print(f"unsupported protocol for this direct probe: {node.get('protocol')!r}") + return 2 + server = Endpoint(str(node["server"]), int(node["port"])) + if args.server_address: + connect_endpoints = [parse_endpoint(args.server_address, default_port=server.port)] + elif args.resolve_server == "system": + system_addresses = resolved_stream_addresses(server) + connect_endpoints = [ + Endpoint(str(sockaddr[0]), int(sockaddr[1])) for _family, sockaddr in system_addresses + ] + if not args.all_server_addresses and connect_endpoints: + connect_endpoints = connect_endpoints[:1] + if not connect_endpoints: + connect_endpoints = [server] + elif args.resolve_server == "doh-all": + connect_endpoints = resolve_doh_all(server.host, server.port, args.timeout) + if not args.all_server_addresses and connect_endpoints: + connect_endpoints = connect_endpoints[:1] + else: + connect_endpoints = resolve_doh(server.host, server.port, args.resolve_server, args.timeout) + print( + f"{args.resolve_server}_answers=" + + ",".join(f"{endpoint.host}:{endpoint.port}" for endpoint in connect_endpoints) + ) + if not args.all_server_addresses and connect_endpoints: + connect_endpoints = connect_endpoints[:1] + if not connect_endpoints: + raise LookupError("proxy server resolution returned no usable A records") + target = parse_endpoint(args.target) + dns_server = parse_endpoint(args.dns_server) + except BaseException as exc: + print(f"setup_error={type(exc).__name__}: {exc}", file=sys.stderr) + return 2 + + print( + "connect_endpoints=" + + ",".join(f"{endpoint.host}:{endpoint.port}" for endpoint in connect_endpoints) + ) + if any(endpoint != server for endpoint in connect_endpoints): + print(f"tls_server_name_source={server.host!r} sni_preserved=True") + if any(is_fake_ip(endpoint.host) for endpoint in connect_endpoints): + print( + "warning=proxy hostname resolved into 198.18.0.0/15; " + "rerun with --resolve-server doh-all or --server-address REAL_IP to avoid fake-IP DNS" + ) + + any_tcp_ok = False + any_required_udp_ok = False + for idx, connect_endpoint in enumerate(connect_endpoints, start=1): + if len(connect_endpoints) > 1: + print(f"\n=== proxy server candidate {idx}/{len(connect_endpoints)} ===") + print(f"connect_endpoint={connect_endpoint.host}:{connect_endpoint.port}") + + print("\n[1/2] Trojan TCP CONNECT probe") + print(f"target={target.host}:{target.port} http_host={args.http_host!r}") + tcp_result = probe_trojan_tcp( + connect_endpoint, server.host, node, target, args.http_host, args.timeout + ) + print(("ok: " if tcp_result.ok else "fail: ") + tcp_result.detail) + any_tcp_ok = any_tcp_ok or tcp_result.ok + + udp_result = ProbeResult(True, "skipped") + if not args.skip_udp: + print("\n[2/2] Trojan UDP DNS probe") + print(f"dns_server={dns_server.host}:{dns_server.port} dns_name={args.dns_name!r}") + udp_result = probe_trojan_udp_dns( + connect_endpoint, server.host, node, dns_server, args.dns_name, args.timeout + ) + print(("ok: " if udp_result.ok else "fail: ") + udp_result.detail) + else: + print("\n[2/2] Trojan UDP DNS probe") + print("skipped by --skip-udp") + any_required_udp_ok = any_required_udp_ok or udp_result.ok + + if tcp_result.ok and (not args.require_udp or udp_result.ok): + break + + if not any_tcp_ok: + return 1 + if args.require_udp and not any_required_udp_ok: + return 1 + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/Scripts/burrow-proxy-selftest b/Scripts/burrow-proxy-selftest new file mode 100755 index 0000000..bc980c9 --- /dev/null +++ b/Scripts/burrow-proxy-selftest @@ -0,0 +1,272 @@ +#!/usr/bin/env bash +set -euo pipefail + +usage() { + cat <<'EOF' +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. +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}" + +if [[ ! -x "$burrow_bin" ]]; then + (cd "$repo_root" && cargo build -p burrow) +fi + +if ! command -v openssl >/dev/null 2>&1; then + echo "openssl is required for the local TLS server certificate" >&2 + exit 2 +fi + +tmpdir="$(mktemp -d "${TMPDIR:-/tmp}/burrow-proxy-selftest.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 self-test artifacts: $tmpdir" >&2 + else + rm -rf "$tmpdir" + fi +} +trap 'status=$?; cleanup' EXIT + +cert="$tmpdir/cert.pem" +key="$tmpdir/key.pem" +socket="$tmpdir/burrow.sock" +port_file="$tmpdir/server.port" +server_result="$tmpdir/server-result.json" +payload="$tmpdir/proxy-payload.json" +daemon_log="$tmpdir/daemon.log" +server_log="$tmpdir/server.log" + +openssl req \ + -x509 \ + -newkey rsa:2048 \ + -nodes \ + -subj /CN=localhost \ + -keyout "$key" \ + -out "$cert" \ + -days 1 >/dev/null 2>&1 + +python3 - "$port_file" "$server_result" "$cert" "$key" >"$server_log" 2>&1 <<'PY' & +from __future__ import annotations + +import hashlib +import json +import socket +import ssl +import struct +import sys + +port_file, result_file, cert_file, key_file = sys.argv[1:] +password = "secret" + + +def read_exact(sock: ssl.SSLSocket, 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: ssl.SSLSocket) -> 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 read_http_headers(sock: ssl.SSLSocket) -> 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("HTTP request headers exceeded 64 KiB") + return bytes(data) + + +ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) +ctx.load_cert_chain(cert_file, key_file) +ctx.set_alpn_protocols(["h2", "http/1.1"]) + +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(1) +port = listener.getsockname()[1] +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] = {"peer": str(peer)} +try: + with ctx.wrap_socket(raw, server_side=True) as tls: + tls.settimeout(10) + result["alpn"] = tls.selected_alpn_protocol() + got_hash = read_exact(tls, 56) + expected_hash = hashlib.sha224(password.encode()).hexdigest().encode() + result["password_hash_ok"] = got_hash == expected_hash + if not result["password_hash_ok"]: + raise ValueError("unexpected Trojan password hash") + if read_exact(tls, 2) != b"\r\n": + raise ValueError("missing Trojan password delimiter") + command = read_exact(tls, 1)[0] + result["command"] = command + target = read_socks_addr(tls) + result["target"] = target + if read_exact(tls, 2) != b"\r\n": + raise ValueError("missing Trojan header delimiter") + request = read_http_headers(tls) + result["request_first_line"] = request.splitlines()[0].decode("utf-8", "replace") + body = json.dumps( + { + "ok": True, + "target": target, + "request_first_line": result["request_first_line"], + }, + sort_keys=True, + ).encode() + tls.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: + 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 +server_pid=$! + +for _ in {1..100}; do + if [[ -s "$port_file" ]]; then + break + fi + if ! kill -0 "$server_pid" >/dev/null 2>&1; then + echo "local Trojan server 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 local Trojan server" >&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 +) + +wait "$server_pid" +server_pid="" + +echo +echo "Local Trojan server observed:" +cat "$server_result" +echo diff --git a/burrow-gtk/Cargo.lock b/burrow-gtk/Cargo.lock index a1a9ebd..f64b325 100644 --- a/burrow-gtk/Cargo.lock +++ b/burrow-gtk/Cargo.lock @@ -36,18 +36,7 @@ dependencies = [ "cfg-if", "cipher", "cpufeatures", -] - -[[package]] -name = "ahash" -version = "0.8.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" -dependencies = [ - "cfg-if", - "once_cell", - "version_check", - "zerocopy", + "zeroize", ] [[package]] @@ -59,6 +48,76 @@ dependencies = [ "memchr", ] +[[package]] +name = "alloca" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7d05ea6aea7e9e64d25b9156ba2fee3fdd659e34e41063cd2fc7cd020d7f4" +dependencies = [ + "cc", +] + +[[package]] +name = "amplify" +version = "4.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f7fb4ac7c881e54a8e7015e399b6112a2a5bc958b6c89ac510840ff20273b31" +dependencies = [ + "amplify_derive", + "amplify_num", + "ascii", + "getrandom 0.2.12", + "getrandom 0.3.3", + "wasm-bindgen", +] + +[[package]] +name = "amplify_derive" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a6309e6b8d89b36b9f959b7a8fa093583b94922a0f6438a24fb08936de4d428" +dependencies = [ + "amplify_syn", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "amplify_num" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afed304556696656d2d71495e1e5f2c4b524a3fb6eb0f2f3778ffc482a40b8a8" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "amplify_syn" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7736fb8d473c0d83098b5bac44df6a561e20470375cd8bcae30516dc889fd62a" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + [[package]] name = "anstream" version = "0.6.11" @@ -75,9 +134,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.4" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anstyle-parse" @@ -113,6 +172,123 @@ version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" +[[package]] +name = "argon2" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" +dependencies = [ + "base64ct", + "blake2", + "cpufeatures", + "password-hash 0.5.0", +] + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "arti-client" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89842cae6e3bda0fd128a5c66eb3392ed412065dc698c77d9fcc4b77e4159f2" +dependencies = [ + "async-trait", + "cfg-if", + "derive-deftly", + "derive_builder_fork_arti", + "derive_more", + "educe", + "fs-mistrust", + "futures", + "hostname-validator", + "humantime", + "humantime-serde", + "libc", + "once_cell", + "postage", + "rand 0.9.2", + "safelog", + "serde", + "thiserror 2.0.16", + "time", + "tor-async-utils", + "tor-basic-utils", + "tor-chanmgr", + "tor-circmgr", + "tor-config", + "tor-config-path", + "tor-dircommon", + "tor-dirmgr", + "tor-error", + "tor-guardmgr", + "tor-keymgr", + "tor-linkspec", + "tor-llcrypto", + "tor-memquota", + "tor-netdir", + "tor-netdoc", + "tor-persist", + "tor-proto", + "tor-protover", + "tor-rtcompat", + "tracing", + "void", +] + +[[package]] +name = "ascii" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" + +[[package]] +name = "asn1-rs" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f43a50ac4fdca5df8e885c21b835997f0a1cdee65494a6847694a98652d9d8" +dependencies = [ + "asn1-rs-derive", + "asn1-rs-impl", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror 2.0.16", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", + "synstructure", +] + +[[package]] +name = "asn1-rs-impl" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "assert_matches" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" + [[package]] name = "async-channel" version = "2.1.1" @@ -120,12 +296,40 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ca33f4bc4ed1babef42cad36cc1f51fa88be00420404e5b1e80ab1b18f7678c" dependencies = [ "concurrent-queue", - "event-listener", + "event-listener 4.0.3", "event-listener-strategy", "futures-core", "pin-project-lite", ] +[[package]] +name = "async-compression" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd066d0b4ef8ecb03a55319dc13aa6910616d0f44008a045bb1835af830abff5" +dependencies = [ + "flate2", + "futures-core", + "futures-io", + "memchr", + "pin-project-lite", + "xz2", + "zstd 0.13.0", + "zstd-safe 7.0.0", +] + +[[package]] +name = "async-native-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9343dc5acf07e79ff82d0c37899f079db3534d99f189a1837c8e549c99405bec" +dependencies = [ + "futures-util", + "native-tls", + "thiserror 1.0.56", + "url", +] + [[package]] name = "async-stream" version = "0.2.1" @@ -180,6 +384,49 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "async_executors" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a982d2f86de6137cc05c9db9a915a19886c97911f9790d04f174cede74be01a5" +dependencies = [ + "blanket", + "futures-core", + "futures-task", + "futures-util", + "pin-project", + "rustc_version", + "tokio", +] + +[[package]] +name = "asynchronous-codec" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a860072022177f903e59730004fb5dc13db9275b79bb2aef7ba8ce831956c233" +dependencies = [ + "bytes", + "futures-sink", + "futures-util", + "memchr", + "pin-project-lite", +] + +[[package]] +name = "atomic" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba" + +[[package]] +name = "atomic" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89cbf775b137e9b968e67227ef7f775587cde3fd31b0d8599dbd0f598a48340" +dependencies = [ + "bytemuck", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -192,6 +439,29 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "aws-lc-rs" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c953fe1ba023e6b7730c0d4b031d06f267f23a46167dcbd40316644b10a17ba" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbfd150b5dbdb988bcc8fb1fe787eb6b7ee6180ca24da683b61ea5405f3d43ff" +dependencies = [ + "bindgen 0.69.5", + "cc", + "cmake", + "dunce", + "fs_extra", +] + [[package]] name = "axum" version = "0.7.5" @@ -262,6 +532,12 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + [[package]] name = "base64" version = "0.21.7" @@ -276,9 +552,19 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.6.0" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "bincode" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740" +dependencies = [ + "serde", + "unty", +] [[package]] name = "bindgen" @@ -325,6 +611,29 @@ dependencies = [ "which", ] +[[package]] +name = "bindgen" +version = "0.69.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" +dependencies = [ + "bitflags 2.11.1", + "cexpr", + "clang-sys", + "itertools 0.12.1", + "lazy_static", + "lazycell", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash 1.1.0", + "shlex", + "syn 2.0.106", + "which", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -333,9 +642,21 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.2" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] [[package]] name = "blake2" @@ -346,6 +667,17 @@ dependencies = [ "digest", ] +[[package]] +name = "blanket" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0b121a9fe0df916e362fb3271088d071159cdf11db0e4182d02152850756eff" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "block" version = "0.1.6" @@ -361,6 +693,26 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "bstr" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" +dependencies = [ + "memchr", + "regex-automata 0.4.14", + "serde", +] + [[package]] name = "bumpalo" version = "3.14.0" @@ -373,11 +725,14 @@ version = "0.1.0" dependencies = [ "aead", "anyhow", + "argon2", + "arti-client", "async-channel", "async-stream 0.2.1", "axum", "base64 0.21.7", "blake2", + "bytes", "caps", "chacha20poly1305", "clap", @@ -385,12 +740,17 @@ dependencies = [ "dotenv", "fehler", "futures", + "hickory-proto", "hmac", "hyper-util", "ip_network", "ip_network_table", + "ipnetwork", + "libc", "libsystemd", "log", + "md-5", + "netstack-smoltcp", "nix 0.27.1", "once_cell", "parking_lot", @@ -402,14 +762,21 @@ dependencies = [ "ring", "rusqlite", "rust-ini", - "schemars", + "rustls", + "schemars 0.8.16", "serde", "serde_json", + "serde_yaml", + "sha2", + "subtle", "tokio", + "tokio-rustls", "tokio-stream", - "toml", + "tokio-util", + "toml 0.8.23", "tonic", "tonic-build", + "tor-rtcompat", "tower", "tracing", "tracing-journald", @@ -417,6 +784,7 @@ dependencies = [ "tracing-oslog", "tracing-subscriber", "tun", + "webpki-roots 0.26.11", "x25519-dalek", ] @@ -429,9 +797,23 @@ dependencies = [ "gettext-rs", "glib-build-tools", "relm4", + "serde", + "serde_json", "tokio", ] +[[package]] +name = "by_address" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06" + +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" + [[package]] name = "byteorder" version = "1.5.0" @@ -501,13 +883,27 @@ dependencies = [ ] [[package]] -name = "cc" -version = "1.0.83" +name = "caret" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +checksum = "beae2cb9f60bc3f21effaaf9c64e51f6627edd54eedc9199ba07f519ef2a2101" + +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + +[[package]] +name = "cc" +version = "1.2.62" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98" dependencies = [ + "find-msvc-tools", "jobserver", "libc", + "shlex", ] [[package]] @@ -565,6 +961,45 @@ dependencies = [ "zeroize", ] +[[package]] +name = "chrono" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +dependencies = [ + "iana-time-zone", + "num-traits", + "serde", + "windows-link 0.2.1", +] + +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + [[package]] name = "cipher" version = "0.4.4" @@ -589,9 +1024,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.18" +version = "4.5.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e578d6ec4194633722ccf9544794b71b1385c3c027efe0c55db226fc880865c" +checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a" dependencies = [ "clap_builder", "clap_derive", @@ -599,23 +1034,23 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.18" +version = "4.5.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4df4df40ec50c46000231c914968278b1eb05098cf8f1b3a518a95030e71d1c7" +checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876" dependencies = [ "anstream", "anstyle", "clap_lex", - "strsim", + "strsim 0.11.1", ] [[package]] name = "clap_derive" -version = "4.4.7" +version = "4.5.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" +checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", "syn 2.0.106", @@ -623,9 +1058,29 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.6.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "cmake" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" +dependencies = [ + "cc", +] + +[[package]] +name = "coarsetime" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e58eb270476aa4fc7843849f8a35063e8743b4dbcdf6dd0f8ea0886980c204c2" +dependencies = [ + "libc", + "wasix", + "wasm-bindgen", +] [[package]] name = "colorchoice" @@ -655,6 +1110,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + [[package]] name = "const-random" version = "0.1.18" @@ -681,6 +1142,24 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "cookie-factory" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9885fa71e26b8ab7855e2ec7cae6e9b380edff76cd052e07c683a0319d51b3a2" +dependencies = [ + "futures", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -715,6 +1194,85 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "criterion" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "950046b2aa2492f9a536f5f4f9a3de7b9e2476e575e05bd6c333371add4d98f3" +dependencies = [ + "alloca", + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "itertools 0.13.0", + "num-traits", + "oorandom", + "page_size", + "plotters", + "rayon", + "regex", + "serde", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-cycles-per-byte" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5396de42a52e9e5d8f67ef0702dae30451f310a9ba1c3094dcf228f0be0e54bc" +dependencies = [ + "cfg-if", + "criterion", +] + +[[package]] +name = "criterion-plot" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8d80a2f4f5b554395e47b5d8305bc3d27813bacb73493eb1001e8f76dae29ea" +dependencies = [ + "cast", + "itertools 0.13.0", +] + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.19" @@ -727,6 +1285,18 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -738,6 +1308,15 @@ dependencies = [ "typenum", ] +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + [[package]] name = "curve25519-dalek" version = "4.1.1" @@ -747,6 +1326,7 @@ dependencies = [ "cfg-if", "cpufeatures", "curve25519-dalek-derive", + "digest", "fiat-crypto", "platforms", "rustc_version", @@ -766,12 +1346,270 @@ dependencies = [ ] [[package]] -name = "deranged" -version = "0.3.11" +name = "darling" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" +dependencies = [ + "darling_core 0.14.4", + "darling_macro 0.14.4", +] + +[[package]] +name = "darling" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +dependencies = [ + "darling_core 0.21.3", + "darling_macro 0.21.3", +] + +[[package]] +name = "darling" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +dependencies = [ + "darling_core 0.23.0", + "darling_macro 0.23.0", +] + +[[package]] +name = "darling_core" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim 0.10.0", + "syn 1.0.109", +] + +[[package]] +name = "darling_core" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "darling_core" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim 0.11.1", + "syn 2.0.106", +] + +[[package]] +name = "darling_macro" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" +dependencies = [ + "darling_core 0.14.4", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "darling_macro" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +dependencies = [ + "darling_core 0.21.3", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "darling_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core 0.23.0", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "data-encoding" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" + +[[package]] +name = "defmt" +version = "0.3.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0963443817029b2024136fc4dd07a5107eb8f977eaf18fcd1fdeb11306b64ad" +dependencies = [ + "defmt 1.1.0", +] + +[[package]] +name = "defmt" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6e524506490a1953d237cb87b1cfc1e46f88c18f10a22dfe0f507dc6bfc7f7f" +dependencies = [ + "bitflags 1.3.2", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0a27770e9c8f719a79d8b638281f4d828f77d8fd61e0bd94451b9b85e576a0b" +dependencies = [ + "defmt-parser", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "defmt-parser" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +dependencies = [ + "thiserror 2.0.16", +] + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "der-parser" +version = "10.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6" +dependencies = [ + "asn1-rs", + "cookie-factory", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ "powerfmt", + "serde_core", +] + +[[package]] +name = "derive-deftly" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "284db66a66f03c3dafbe17360d959eb76b83f77cfe191677e2a7899c0da291f3" +dependencies = [ + "derive-deftly-macros", + "heck 0.5.0", +] + +[[package]] +name = "derive-deftly-macros" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caef6056a5788d05d173cdc3c562ac28ae093828f851f69378b74e4e3d578e41" +dependencies = [ + "heck 0.5.0", + "indexmap 2.11.4", + "itertools 0.14.0", + "proc-macro-crate", + "proc-macro2", + "quote", + "sha3", + "strum", + "syn 2.0.106", + "void", +] + +[[package]] +name = "derive_builder_core_fork_arti" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24c1b715c79be6328caa9a5e1a387a196ea503740f0722ec3dd8f67a9e72314d" +dependencies = [ + "darling 0.14.4", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_builder_fork_arti" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3eae24d595f4d0ecc90a9a5a6d11c2bd8dafe2375ec4a1ec63250e5ade7d228" +dependencies = [ + "derive_builder_macro_fork_arti", +] + +[[package]] +name = "derive_builder_macro_fork_arti" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69887769a2489cd946bf782eb2b1bb2cb7bc88551440c94a765d4f040c08ebf3" +dependencies = [ + "derive_builder_core_fork_arti", + "syn 1.0.109", +] + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.106", + "unicode-xid", ] [[package]] @@ -781,10 +1619,52 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", + "const-oid", "crypto-common", "subtle", ] +[[package]] +name = "directories" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16f5094c54661b38d03bd7e50df373292118db60b585c08a411c6d840017fe7d" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.61.2", +] + +[[package]] +name = "displaydoc" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "dlv-list" version = "0.5.2" @@ -801,10 +1681,74 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" [[package]] -name = "dyn-clone" -version = "1.0.16" +name = "downcast-rs" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "545b22097d44f8a9581187cdf93de7a71e4722bf51200cfaba810865b49a495d" +checksum = "117240f60069e65410b3ae1bb213295bd828f707b5bec6596a1afc8793ce0cbc" + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +dependencies = [ + "curve25519-dalek", + "ed25519", + "merlin", + "rand_core 0.6.4", + "serde", + "sha2", + "subtle", + "zeroize", +] + +[[package]] +name = "educe" +version = "0.4.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f0042ff8246a363dbe77d2ceedb073339e85a804b9a47636c6e016a9a32c05f" +dependencies = [ + "enum-ordinalize", + "proc-macro2", + "quote", + "syn 1.0.109", +] [[package]] name = "either" @@ -812,6 +1756,25 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "encode_unicode" version = "0.3.6" @@ -827,6 +1790,64 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "enum-as-inner" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "enum-ordinalize" +version = "3.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bf1fa3f06bbff1ea5b1a9c7b14aa992a39657db60a2759457328d7e058f49ee" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "enum_dispatch" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd" +dependencies = [ + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "enumset" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "839c4174b41e75c8f7306110b2c51996a293b8d1d850edd529011841d9fede7d" +dependencies = [ + "enumset_derive", +] + +[[package]] +name = "enumset_derive" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bd536557b58c682b217b8fb199afdff47cd3eff260623f19e77074eb073d63a" +dependencies = [ + "darling 0.21.3", + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -843,6 +1864,15 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "etherparse" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8d8a704b617484e9d867a0423cd45f7577f008c4068e2e33378f8d3860a6d73" +dependencies = [ + "arrayvec", +] + [[package]] name = "event-listener" version = "4.0.3" @@ -854,13 +1884,24 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + [[package]] name = "event-listener-strategy" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3" dependencies = [ - "event-listener", + "event-listener 4.0.3", "pin-project-lite", ] @@ -878,9 +1919,9 @@ checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" [[package]] name = "fastrand" -version = "2.0.1" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" [[package]] name = "fehler" @@ -902,6 +1943,16 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "fiat-crypto" version = "0.2.5" @@ -918,6 +1969,35 @@ dependencies = [ "rustc_version", ] +[[package]] +name = "figment" +version = "0.10.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cb01cd46b0cf372153850f4c6c272d9cbea2da513e07538405148f95bd789f3" +dependencies = [ + "atomic 0.6.1", + "serde", + "toml 0.8.23", + "uncased", + "version_check", +] + +[[package]] +name = "filetime" +version = "0.2.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759" +dependencies = [ + "cfg-if", + "libc", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + [[package]] name = "fixedbitset" version = "0.5.7" @@ -934,6 +2014,12 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "fluid-let" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "749cff877dc1af878a0b31a41dd221a753634401ea0ef2f87b62d3171522485a" + [[package]] name = "flume" version = "0.10.14" @@ -944,7 +2030,7 @@ dependencies = [ "futures-sink", "nanorand", "pin-project", - "spin", + "spin 0.9.8", ] [[package]] @@ -953,6 +2039,18 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "foreign-types" version = "0.3.2" @@ -970,9 +2068,9 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" dependencies = [ "percent-encoding", ] @@ -983,6 +2081,43 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa" +[[package]] +name = "fs-mistrust" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f5ac9f88fd18733e0f9ce1f4a95c40eb1d4f83131bf1472e81d1f128fefb7c2" +dependencies = [ + "derive_builder_fork_arti", + "dirs", + "libc", + "pwd-grp", + "serde", + "thiserror 2.0.16", + "walkdir", +] + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "fslock" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04412b8935272e3a9bae6f48c7bfff74c2911f60525404edfdd28e49884c3bfb" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures" version = "0.3.30" @@ -1000,9 +2135,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.30" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", "futures-sink", @@ -1010,9 +2145,9 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-executor" @@ -1027,15 +2162,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.30" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" [[package]] name = "futures-macro" -version = "0.3.30" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", @@ -1044,21 +2179,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.30" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] name = "futures-task" -version = "0.3.30" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-util" -version = "0.3.30" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-channel", "futures-core", @@ -1068,7 +2203,6 @@ dependencies = [ "futures-task", "memchr", "pin-project-lite", - "pin-utils", "slab", ] @@ -1140,6 +2274,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -1164,11 +2299,36 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "r-efi", + "r-efi 5.3.0", "wasi 0.14.7+wasi-0.2.4", "wasm-bindgen", ] +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "wasip2", + "wasip3", +] + +[[package]] +name = "getset" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf0fc11e47561d47397154977bc219f4cf809b2974facc3ccb3b89e2436f912" +dependencies = [ + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "gettext-rs" version = "0.7.0" @@ -1264,7 +2424,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eca5c79337338391f1ab8058d6698125034ce8ef31b72a442437fa6c8580de26" dependencies = [ "anyhow", - "heck", + "heck 0.4.1", "proc-macro-crate", "proc-macro-error", "proc-macro2", @@ -1288,6 +2448,12 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +[[package]] +name = "glob-match" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9985c9503b412198aa4197559e9a318524ebc4519c229bfa05a535828c950b9d" + [[package]] name = "gobject-sys" version = "0.17.10" @@ -1322,6 +2488,17 @@ dependencies = [ "system-deps", ] +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "gsk4" version = "0.6.3" @@ -1448,6 +2625,26 @@ dependencies = [ "tracing", ] +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] + +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -1459,23 +2656,42 @@ name = "hashbrown" version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ - "ahash", + "foldhash 0.1.5", ] [[package]] name = "hashbrown" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "foldhash 0.2.0", +] [[package]] name = "hashlink" -version = "0.9.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" +checksum = "ea0b22561a9c04a7cb1a302c013e0259cd3b4bb619f145b32f72b8b4bcbed230" dependencies = [ - "hashbrown 0.14.3", + "hashbrown 0.16.1", +] + +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "hash32", + "stable_deref_trait", ] [[package]] @@ -1484,12 +2700,52 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hickory-proto" +version = "0.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8a6fe56c0038198998a6f217ca4e7ef3a5e51f46163bd6dd60b5c71ca6c6502" +dependencies = [ + "async-trait", + "cfg-if", + "data-encoding", + "enum-as-inner", + "futures-channel", + "futures-io", + "futures-util", + "idna", + "ipnet", + "once_cell", + "rand 0.9.2", + "ring", + "thiserror 2.0.16", + "tinyvec", + "tokio", + "tracing", + "url", +] + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + [[package]] name = "hmac" version = "0.12.1" @@ -1508,6 +2764,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "hostname-validator" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f558a64ac9af88b5ba400d99b579451af0d39c6d360980045b91aac966d705e2" + [[package]] name = "http" version = "0.2.11" @@ -1576,6 +2838,22 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "humantime" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" + +[[package]] +name = "humantime-serde" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57a3db5ea5923d99402c94e9feb261dc5ee9b4efa158b0315f788cf549cc200c" +dependencies = [ + "humantime", + "serde", +] + [[package]] name = "hyper" version = "0.14.28" @@ -1593,7 +2871,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.4.10", + "socket2 0.5.10", "tokio", "tower-service", "tracing", @@ -1679,20 +2957,149 @@ dependencies = [ "hyper 1.6.0", "libc", "pin-project-lite", - "socket2 0.5.10", + "socket2 0.6.3", "tokio", "tower-service", "tracing", ] [[package]] -name = "idna" -version = "0.5.0" +name = "iana-time-zone" +version = "0.1.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core 0.62.2", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" + +[[package]] +name = "icu_properties" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" + +[[package]] +name = "icu_provider" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", ] [[package]] @@ -1703,6 +3110,7 @@ checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown 0.12.3", + "serde", ] [[package]] @@ -1712,7 +3120,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" dependencies = [ "equivalent", - "hashbrown 0.16.0", + "hashbrown 0.16.1", + "serde", + "serde_core", +] + +[[package]] +name = "inotify" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd5b3eaf1a28b758ac0faa5a4254e8ab2705605496f1b1f3fbbc3988ad73d199" +dependencies = [ + "bitflags 2.11.1", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +dependencies = [ + "libc", ] [[package]] @@ -1724,13 +3154,22 @@ dependencies = [ "generic-array", ] +[[package]] +name = "inventory" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4f0c30c76f2f4ccee3fe55a2435f691ca00c0e4bd87abe4f4a851b1d4dac39b" +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.4.2", + "bitflags 2.11.1", "cfg-if", "libc", ] @@ -1763,6 +3202,33 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +[[package]] +name = "ipnetwork" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf370abdafd54d13e54a620e8c3e1145f28e46cc9d704bc6d94414559df41763" +dependencies = [ + "serde", +] + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.14.0" @@ -1780,28 +3246,63 @@ checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "jobserver" -version = "0.1.27" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ + "getrandom 0.3.3", "libc", ] [[package]] name = "js-sys" -version = "0.3.80" +version = "0.3.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852f13bec5eba4ba9afbeb93fd7c13fe56147f055939ae21c43a29a0ecb2702e" +checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11" dependencies = [ + "cfg-if", + "futures-util", "once_cell", "wasm-bindgen", ] +[[package]] +name = "keccak" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "kqueue" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a" +dependencies = [ + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07293a4e297ac234359b510362495713f75ea345d5307140414f20c69ffeb087" +dependencies = [ + "bitflags 2.11.1", + "libc", +] + [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin 0.5.2", +] [[package]] name = "lazycell" @@ -1809,6 +3310,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + [[package]] name = "libadwaita" version = "0.4.4" @@ -1869,10 +3376,25 @@ dependencies = [ ] [[package]] -name = "libsqlite3-sys" -version = "0.28.0" +name = "libm" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c10584274047cb335c23d3e61bcef8e323adae7c5c8c760540f73610177fc3f" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "libredox" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f02ab6bace2054fb888a3c16f990117b579d14a3088e472d63c6011fa185c9d3" +dependencies = [ + "libc", +] + +[[package]] +name = "libsqlite3-sys" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95b4103cffefa72eb8428cb6b47d6627161e51c2739fc5e3b734584157bc642a" dependencies = [ "cc", "pkg-config", @@ -1903,6 +3425,12 @@ version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + [[package]] name = "locale_config" version = "0.3.0" @@ -1938,6 +3466,17 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" +[[package]] +name = "lzma-sys" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fda04ab3764e6cde78b9974eec4f779acaba7c4e84b36eca3cf77c581b85d27" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + [[package]] name = "malloc_buf" version = "0.0.6" @@ -1947,6 +3486,12 @@ dependencies = [ "libc", ] +[[package]] +name = "managed" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ca88d725a0a943b096803bd34e73a4437208b6077654cc4ecb2947a5f91618d" + [[package]] name = "matchers" version = "0.1.0" @@ -1962,12 +3507,31 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + [[package]] name = "memchr" version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +[[package]] +name = "memmap2" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3" +dependencies = [ + "libc", +] + [[package]] name = "memoffset" version = "0.7.1" @@ -1986,6 +3550,18 @@ dependencies = [ "autocfg", ] +[[package]] +name = "merlin" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58c38e2799fc0978b65dfff8023ec7843e2330bb462f19198840b34b6582397d" +dependencies = [ + "byteorder", + "keccak", + "rand_core 0.6.4", + "zeroize", +] + [[package]] name = "miette" version = "5.10.0" @@ -2037,6 +3613,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ "libc", + "log", "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.52.0", ] @@ -2074,6 +3651,22 @@ dependencies = [ "tempfile", ] +[[package]] +name = "netstack-smoltcp" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "398691cef792b89eb5d29e6ea6b3999def706b908d355e29815ba8101cf5c4c8" +dependencies = [ + "etherparse", + "futures", + "rand 0.8.5", + "smoltcp", + "spin 0.9.8", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "nix" version = "0.26.4" @@ -2093,7 +3686,7 @@ version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.11.1", "cfg-if", "libc", "memoffset 0.9.0", @@ -2109,6 +3702,47 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nonany" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6b8866ec53810a9a4b3d434a29801e78c707430a9ae11c2db4b8b62bb9675a0" + +[[package]] +name = "notify" +version = "8.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" +dependencies = [ + "bitflags 2.11.1", + "inotify", + "kqueue", + "libc", + "log", + "mio", + "notify-types", + "walkdir", + "windows-sys 0.60.2", +] + +[[package]] +name = "notify-types" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42b8cfee0e339a0337359f3c88165702ac6e600dc01c0cc9579a92d62b08477a" +dependencies = [ + "bitflags 2.11.1", +] + +[[package]] +name = "ntapi" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3b335231dfd352ffb0f8017f3b6027a4917f7df785ea2143d8af2adc66980ae" +dependencies = [ + "winapi", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -2119,6 +3753,90 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-bigint-dig" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" +dependencies = [ + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand 0.8.5", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "num_enum" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "objc" version = "0.2.7" @@ -2139,6 +3857,25 @@ dependencies = [ "objc_id", ] +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags 2.11.1", +] + +[[package]] +name = "objc2-io-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15" +dependencies = [ + "libc", + "objc2-core-foundation", +] + [[package]] name = "objc_id" version = "0.1.1" @@ -2159,9 +3896,28 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.19.0" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +dependencies = [ + "critical-section", + "portable-atomic", +] + +[[package]] +name = "oneshot-fused-workaround" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17b52d0e4a06a4c7eb8d2943c0015fa628cf4ccc409429cebc0f5bed6d33a82" +dependencies = [ + "futures", +] + +[[package]] +name = "oorandom" +version = "11.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" [[package]] name = "opaque-debug" @@ -2175,7 +3931,7 @@ version = "0.10.63" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "15c9d69dd87a29568d4d017cfe8ec518706046a05184e5aea92d0af890b803c8" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.11.1", "cfg-if", "foreign-types", "libc", @@ -2213,6 +3969,21 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "ordered-float" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" +dependencies = [ + "num-traits", +] + [[package]] name = "ordered-multimap" version = "0.7.3" @@ -2223,12 +3994,69 @@ dependencies = [ "hashbrown 0.14.3", ] +[[package]] +name = "os_str_bytes" +version = "6.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" +dependencies = [ + "memchr", +] + [[package]] name = "overload" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "p384" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "p521" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc9e2161f1f215afdfce23677034ae137bbd45016a880c2eb3ba8eb95f085b2" +dependencies = [ + "base16ct", + "ecdsa", + "elliptic-curve", + "primeorder", + "rand_core 0.6.4", + "sha2", +] + +[[package]] +name = "page_size" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "pango" version = "0.17.10" @@ -2295,6 +4123,23 @@ dependencies = [ "subtle", ] +[[package]] +name = "password-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +dependencies = [ + "base64ct", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "pbkdf2" version = "0.11.0" @@ -2303,7 +4148,7 @@ checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" dependencies = [ "digest", "hmac", - "password-hash", + "password-hash 0.4.2", "sha2", ] @@ -2314,10 +4159,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" [[package]] -name = "percent-encoding" -version = "2.3.1" +name = "pem-rfc7468" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "petgraph" @@ -2329,6 +4183,49 @@ dependencies = [ "indexmap 2.11.4", ] +[[package]] +name = "phf" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" +dependencies = [ + "phf_macros", + "phf_shared", + "serde", +] + +[[package]] +name = "phf_generator" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" +dependencies = [ + "fastrand", + "phf_shared", +] + +[[package]] +name = "phf_macros" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "phf_shared" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project" version = "1.1.4" @@ -2361,6 +4258,27 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + [[package]] name = "pkg-config" version = "0.3.29" @@ -2373,6 +4291,34 @@ version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "626dec3cac7cc0e1577a2ec3fc496277ec2baa084bebad95bb6fdbfae235f84c" +[[package]] +name = "plotters" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" + +[[package]] +name = "plotters-svg" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" +dependencies = [ + "plotters-backend", +] + [[package]] name = "poly1305" version = "0.8.0" @@ -2384,6 +4330,36 @@ dependencies = [ "universal-hash", ] +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "postage" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af3fb618632874fb76937c2361a7f22afd393c982a2165595407edc75b06d3c1" +dependencies = [ + "atomic 0.5.3", + "crossbeam-queue", + "futures", + "parking_lot", + "pin-project", + "static_assertions", + "thiserror 1.0.56", +] + +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -2398,14 +4374,34 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "prettyplease" -version = "0.2.16" +version = "0.2.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a41cf62165e97c7f814d2221421dbb9afcbcdb0a88068e5ea206e19951c2cbb5" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", "syn 2.0.106", ] +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + +[[package]] +name = "priority-queue" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93980406f12d9f8140ed5abe7155acb10bb1e69ea55c88960b9c2f117445ef96" +dependencies = [ + "equivalent", + "indexmap 2.11.4", + "serde", +] + [[package]] name = "proc-macro-crate" version = "1.3.1" @@ -2440,6 +4436,28 @@ dependencies = [ "version_check", ] +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "proc-macro2" version = "1.0.101" @@ -2465,8 +4483,8 @@ version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be769465445e8c1474e9c5dac2018218498557af32d9ed057325ec9a41ae81bf" dependencies = [ - "heck", - "itertools", + "heck 0.5.0", + "itertools 0.14.0", "log", "multimap", "once_cell", @@ -2486,7 +4504,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" dependencies = [ "anyhow", - "itertools", + "itertools 0.14.0", "proc-macro2", "quote", "syn 2.0.106", @@ -2501,6 +4519,18 @@ dependencies = [ "prost", ] +[[package]] +name = "pwd-grp" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e2023f41b5fcb7c30eb5300a5733edfaa9e0e0d502d51b586f65633fd39e40c" +dependencies = [ + "derive-deftly", + "libc", + "paste", + "thiserror 2.0.16", +] + [[package]] name = "quinn" version = "0.11.9" @@ -2514,7 +4544,7 @@ dependencies = [ "quinn-udp", "rustc-hash 2.1.1", "rustls", - "socket2 0.5.10", + "socket2 0.6.3", "thiserror 2.0.16", "tokio", "tracing", @@ -2551,16 +4581,16 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.5.10", + "socket2 0.6.3", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.60.2", ] [[package]] name = "quote" -version = "1.0.35" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] @@ -2571,6 +4601,18 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.8.5" @@ -2630,6 +4672,46 @@ dependencies = [ "getrandom 0.3.3", ] +[[package]] +name = "rand_jitter" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b16df48f071248e67b8fc5e866d9448d45c08ad8b672baaaf796e2f15e606ff0" +dependencies = [ + "libc", + "rand_core 0.9.3", + "winapi", +] + +[[package]] +name = "rayon" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "rdrand" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d92195228612ac8eed47adbc2ed0f04e513a4ccb98175b6f2bd04d963b533655" +dependencies = [ + "rand_core 0.6.4", +] + [[package]] name = "redox_syscall" version = "0.4.1" @@ -2640,15 +4722,46 @@ dependencies = [ ] [[package]] -name = "regex" -version = "1.10.3" +name = "redox_users" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +dependencies = [ + "getrandom 0.2.12", + "libredox", + "thiserror 2.0.16", +] + +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.4", - "regex-syntax 0.8.2", + "regex-automata 0.4.14", + "regex-syntax 0.8.10", ] [[package]] @@ -2662,13 +4775,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.4" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b7fa1134405e2ec9353fd416b17f8dacd46c473d7d3fd1cf202706a14eb792a" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.2", + "regex-syntax 0.8.10", ] [[package]] @@ -2679,9 +4792,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.2" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "relm4" @@ -2792,6 +4905,25 @@ dependencies = [ "winreg 0.52.0", ] +[[package]] +name = "retry-error" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c322ea521636c5a3f13685a6266055b2dda7e54e2be35214d7c2a5d0672a5db" +dependencies = [ + "humantime", +] + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + [[package]] name = "ring" version = "0.17.7" @@ -2801,23 +4933,56 @@ dependencies = [ "cc", "getrandom 0.2.12", "libc", - "spin", + "spin 0.9.8", "untrusted", "windows-sys 0.48.0", ] [[package]] -name = "rusqlite" -version = "0.31.0" +name = "rsa" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b838eba278d213a8beaf485bd313fd580ca4505a00d5871caeb1457c55322cae" +checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" dependencies = [ - "bitflags 2.4.2", + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core 0.6.4", + "sha2", + "signature", + "spki", + "subtle", + "zeroize", +] + +[[package]] +name = "rsqlite-vfs" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c51c9ae4df8a7fba42103df5c621fa3c37eccf3a3c650879e90fc48b11cc192c" +dependencies = [ + "hashbrown 0.16.1", + "thiserror 2.0.16", +] + +[[package]] +name = "rusqlite" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1c93dd1c9683b438c392c492109cb702b8090b2bfc8fed6f6e4eb4523f17af3" +dependencies = [ + "bitflags 2.11.1", "fallible-iterator", "fallible-streaming-iterator", "hashlink", "libsqlite3-sys", "smallvec", + "sqlite-wasm-rs", + "time", ] [[package]] @@ -2857,13 +5022,22 @@ dependencies = [ "semver", ] +[[package]] +name = "rusticata-macros" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" +dependencies = [ + "nom", +] + [[package]] name = "rustix" version = "0.38.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.11.1", "errno", "libc", "linux-raw-sys", @@ -2876,6 +5050,8 @@ version = "0.23.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc" dependencies = [ + "aws-lc-rs", + "log", "once_cell", "ring", "rustls-pki-types", @@ -2905,10 +5081,11 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.6" +version = "0.103.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8572f3c2cb9934231157b45499fc41e1f58c589fdfb81a844ba873265e80f8eb" +checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" dependencies = [ + "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", @@ -2926,6 +5103,43 @@ version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" +[[package]] +name = "safelog" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97a907e0d82c61b1b06a2030c968eb313dcf432686b77801a26bc4b206f96573" +dependencies = [ + "derive_more", + "educe", + "either", + "fluid-let", + "thiserror 2.0.16", +] + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "sanitize-filename" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc984f4f9ceb736a7bb755c3e3bd17dc56370af2600c9780dcc48c66453da34d" +dependencies = [ + "regex", +] + +[[package]] +name = "saturating-time" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63583a1dd0647d1484228529ab4ecaa874048d2956f117362aa5f5826456230" + [[package]] name = "schannel" version = "0.1.23" @@ -2947,6 +5161,30 @@ dependencies = [ "serde_json", ] +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + [[package]] name = "schemars_derive" version = "0.8.16" @@ -2965,6 +5203,20 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + [[package]] name = "security-framework" version = "2.9.2" @@ -2996,28 +5248,38 @@ checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" [[package]] name = "serde" -version = "1.0.226" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dca6411025b24b60bfa7ec1fe1f8e710ac09782dca409ee8237ba74b51295fd" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", "serde_derive", ] [[package]] -name = "serde_core" -version = "1.0.226" +name = "serde-value" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba2ba63999edb9dac981fb34b3e5c0d111a69b0924e253ed29d83f7c99e966a4" +checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" +dependencies = [ + "ordered-float", + "serde", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.226" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8db53ae22f34573731bafa1db20f04027b2d25e02d8205921b569171699cdb33" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -3036,14 +5298,26 @@ dependencies = [ ] [[package]] -name = "serde_json" -version = "1.0.111" +name = "serde_ignored" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4" +checksum = "115dffd5f3853e06e746965a20dcbae6ee747ae30b543d91b0e089668bb07798" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "serde_json" +version = "1.0.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" dependencies = [ "itoa", - "ryu", + "memchr", "serde", + "serde_core", + "zmij", ] [[package]] @@ -3066,6 +5340,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" +dependencies = [ + "serde_core", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -3078,6 +5361,51 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_with" +version = "3.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e72c1c2cb7b223fafb600a619537a871c2818583d619401b785e7c0b746ccde2" +dependencies = [ + "base64 0.22.1", + "bs58", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.11.4", + "schemars 0.9.0", + "schemars 1.2.1", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b90c488738ecb4fb0262f41f43bc40efc5868d9fb744319ddf5f5317f417bfac" +dependencies = [ + "darling 0.23.0", + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap 2.11.4", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + [[package]] name = "sha-1" version = "0.10.1" @@ -3111,6 +5439,16 @@ dependencies = [ "digest", ] +[[package]] +name = "sha3" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77fd7028345d415a4034cf8777cd4f8ab1851274233b45f84e3d955502d93874" +dependencies = [ + "digest", + "keccak", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -3120,6 +5458,17 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shellexpand" +version = "3.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32824fab5e16e6c4d86dc1ba84489390419a39f97699852b66480bb87d297ed8" +dependencies = [ + "bstr", + "dirs", + "os_str_bytes", +] + [[package]] name = "shlex" version = "1.3.0" @@ -3135,6 +5484,22 @@ dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core 0.6.4", +] + +[[package]] +name = "siphasher" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" + [[package]] name = "slab" version = "0.4.9" @@ -3144,6 +5509,29 @@ dependencies = [ "autocfg", ] +[[package]] +name = "slotmap" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038" +dependencies = [ + "serde", + "version_check", +] + +[[package]] +name = "slotmap-careful" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed92816c1fbb29891a525b92d5fa95757c9dee47044f76c8e06ceb1e052a8d64" +dependencies = [ + "paste", + "serde", + "slotmap", + "thiserror 2.0.16", + "void", +] + [[package]] name = "smallvec" version = "1.13.1" @@ -3151,13 +5539,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" [[package]] -name = "socket2" -version = "0.4.10" +name = "smoltcp" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" +checksum = "dad095989c1533c1c266d9b1e8d70a1329dd3723c3edac6d03bbd67e7bf6f4bb" dependencies = [ - "libc", - "winapi", + "bitflags 1.3.2", + "byteorder", + "cfg-if", + "defmt 0.3.100", + "heapless", + "log", + "managed", ] [[package]] @@ -3170,6 +5563,22 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "socket2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[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" @@ -3179,6 +5588,70 @@ dependencies = [ "lock_api", ] +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "sqlite-wasm-rs" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc3efc0da82635d7e1ced0053bbbfa8c7ab9645d0bf36ceb4f7127bb85315d75" +dependencies = [ + "cc", + "js-sys", + "rsqlite-vfs", + "wasm-bindgen", +] + +[[package]] +name = "ssh-cipher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caac132742f0d33c3af65bfcde7f6aa8f62f0e991d80db99149eb9d44708784f" +dependencies = [ + "cipher", + "ssh-encoding", +] + +[[package]] +name = "ssh-encoding" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9242b9ef4108a78e8cd1a2c98e193ef372437f8c22be363075233321dd4a15" +dependencies = [ + "base64ct", + "pem-rfc7468", + "sha2", +] + +[[package]] +name = "ssh-key" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b86f5297f0f04d08cabaa0f6bff7cb6aec4d9c3b49d87990d63da9d9156a8c3" +dependencies = [ + "num-bigint-dig", + "p256", + "p384", + "p521", + "rand_core 0.6.4", + "rsa", + "sec1", + "sha2", + "signature", + "ssh-cipher", + "ssh-encoding", + "subtle", + "zeroize", +] + [[package]] name = "ssri" version = "9.2.0" @@ -3195,6 +5668,18 @@ dependencies = [ "xxhash-rust", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strsim" version = "0.10.0" @@ -3202,10 +5687,37 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] -name = "subtle" -version = "2.5.0" +name = "strsim" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "strum" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" @@ -3235,6 +5747,31 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "sysinfo" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "252800745060e7b9ffb7b2badbd8b31cfa4aa2e61af879d0a3bf2a317c20217d" +dependencies = [ + "libc", + "memchr", + "ntapi", + "objc2-core-foundation", + "objc2-io-kit", + "windows 0.61.3", +] + [[package]] name = "system-configuration" version = "0.5.1" @@ -3263,12 +5800,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a2d580ff6a20c55dfb86be5f9c238f67835d0e81cbdea8bf5680e0897320331" dependencies = [ "cfg-expr", - "heck", + "heck 0.4.1", "pkg-config", - "toml", + "toml 0.8.23", "version-compare", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "target-lexicon" version = "0.12.13" @@ -3346,21 +5889,35 @@ dependencies = [ [[package]] name = "time" -version = "0.3.31" +version = "0.3.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f657ba42c3f86e7680e53c8cd3af8abbe56b5491790b46e22e19c0d57463583e" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" dependencies = [ "deranged", + "itoa", + "js-sys", + "num-conv", "powerfmt", - "serde", + "serde_core", "time-core", + "time-macros", ] [[package]] name = "time-core" -version = "0.1.2" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "time-macros" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +dependencies = [ + "num-conv", + "time-core", +] [[package]] name = "tiny-keccak" @@ -3371,6 +5928,27 @@ dependencies = [ "crunchy", ] +[[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "serde_core", + "zerovec", +] + +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "tinyvec" version = "1.6.0" @@ -3450,16 +6028,16 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.10" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" dependencies = [ "bytes", "futures-core", + "futures-io", "futures-sink", "pin-project-lite", "tokio", - "tracing", ] [[package]] @@ -3469,11 +6047,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" dependencies = [ "serde", - "serde_spanned", - "toml_datetime", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", "toml_edit 0.22.27", ] +[[package]] +name = "toml" +version = "0.9.12+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" +dependencies = [ + "indexmap 2.11.4", + "serde_core", + "serde_spanned 1.1.1", + "toml_datetime 0.7.5+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 0.7.13", +] + [[package]] name = "toml_datetime" version = "0.6.11" @@ -3483,6 +6076,15 @@ dependencies = [ "serde", ] +[[package]] +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", +] + [[package]] name = "toml_edit" version = "0.19.15" @@ -3490,7 +6092,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ "indexmap 2.11.4", - "toml_datetime", + "toml_datetime 0.6.11", "winnow 0.5.34", ] @@ -3502,18 +6104,33 @@ checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ "indexmap 2.11.4", "serde", - "serde_spanned", - "toml_datetime", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", "toml_write", "winnow 0.7.13", ] +[[package]] +name = "toml_parser" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +dependencies = [ + "winnow 1.0.3", +] + [[package]] name = "toml_write" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" +[[package]] +name = "toml_writer" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" + [[package]] name = "tonic" version = "0.12.3" @@ -3558,6 +6175,959 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "tor-async-utils" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "895c61a46909134501c6815eceeb66c9c80fc494ee89429821b0f05ccf34b4f5" +dependencies = [ + "derive-deftly", + "educe", + "futures", + "oneshot-fused-workaround", + "pin-project", + "postage", + "thiserror 2.0.16", + "void", +] + +[[package]] +name = "tor-basic-utils" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fac6e4d7e131b7d69bc85558383cd4ac61e46b4dd0d4ed51632f28fac98cac0c" +dependencies = [ + "derive_more", + "hex", + "itertools 0.14.0", + "libc", + "paste", + "rand 0.9.2", + "rand_chacha 0.9.0", + "serde", + "slab", + "smallvec", + "thiserror 2.0.16", + "weak-table", +] + +[[package]] +name = "tor-bytes" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64454947258e49f238a5f06a06250a0c54598a1c7409898b5c79505e6a99e7af" +dependencies = [ + "bytes", + "derive-deftly", + "digest", + "educe", + "getrandom 0.4.2", + "safelog", + "thiserror 2.0.16", + "tor-error", + "tor-llcrypto", + "zeroize", +] + +[[package]] +name = "tor-cell" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ab0c79bc92a957e85959cf397a2d8f9c8294c35fa4f65247a9393b20ac95551" +dependencies = [ + "amplify", + "bitflags 2.11.1", + "bytes", + "caret", + "derive-deftly", + "derive_more", + "educe", + "itertools 0.14.0", + "paste", + "rand 0.9.2", + "smallvec", + "thiserror 2.0.16", + "tor-basic-utils", + "tor-bytes", + "tor-cert", + "tor-error", + "tor-linkspec", + "tor-llcrypto", + "tor-memquota", + "tor-protover", + "tor-units", + "void", +] + +[[package]] +name = "tor-cert" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "debc911738298ee801fce4577c36a50c55295b0bb9c5519461b83cc486a1f86e" +dependencies = [ + "caret", + "derive_builder_fork_arti", + "derive_more", + "digest", + "thiserror 2.0.16", + "tor-bytes", + "tor-checkable", + "tor-error", + "tor-llcrypto", +] + +[[package]] +name = "tor-chanmgr" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7af5b7c2f1e16d1304b5185fcbc91ca5c8df991c21be00702f925f055573eea1" +dependencies = [ + "async-trait", + "caret", + "cfg-if", + "derive-deftly", + "derive_more", + "educe", + "futures", + "oneshot-fused-workaround", + "percent-encoding", + "postage", + "rand 0.9.2", + "safelog", + "serde", + "serde_with", + "thiserror 2.0.16", + "tor-async-utils", + "tor-basic-utils", + "tor-cell", + "tor-config", + "tor-error", + "tor-keymgr", + "tor-linkspec", + "tor-llcrypto", + "tor-memquota", + "tor-netdir", + "tor-proto", + "tor-rtcompat", + "tor-socksproto", + "tor-units", + "tracing", + "url", + "void", +] + +[[package]] +name = "tor-checkable" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15b13a5b50bb55037f2e81b25dde42f420d57c75154216b8ef989006cea3ebee" +dependencies = [ + "humantime", + "signature", + "thiserror 2.0.16", + "tor-llcrypto", +] + +[[package]] +name = "tor-circmgr" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b878f3f7c6be0c7f130d90b347ada2e7c46519dfbdde8273eae2e5d1792caa87" +dependencies = [ + "amplify", + "async-trait", + "cfg-if", + "derive-deftly", + "derive_builder_fork_arti", + "derive_more", + "downcast-rs", + "dyn-clone", + "educe", + "futures", + "humantime-serde", + "itertools 0.14.0", + "once_cell", + "oneshot-fused-workaround", + "pin-project", + "rand 0.9.2", + "retry-error", + "safelog", + "serde", + "thiserror 2.0.16", + "tor-async-utils", + "tor-basic-utils", + "tor-cell", + "tor-chanmgr", + "tor-config", + "tor-dircommon", + "tor-error", + "tor-guardmgr", + "tor-linkspec", + "tor-memquota", + "tor-netdir", + "tor-netdoc", + "tor-persist", + "tor-proto", + "tor-protover", + "tor-relay-selection", + "tor-rtcompat", + "tor-units", + "tracing", + "void", + "weak-table", +] + +[[package]] +name = "tor-config" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cbc74a00ab15bb986e3747c6969e40a58a63065d6f99077e7ee2f4657bb8b03" +dependencies = [ + "amplify", + "cfg-if", + "derive-deftly", + "derive_builder_fork_arti", + "educe", + "either", + "figment", + "fs-mistrust", + "futures", + "humantime-serde", + "itertools 0.14.0", + "notify", + "paste", + "postage", + "regex", + "serde", + "serde-value", + "serde_ignored", + "strum", + "thiserror 2.0.16", + "toml 0.9.12+spec-1.1.0", + "tor-basic-utils", + "tor-error", + "tor-rtcompat", + "tracing", + "void", +] + +[[package]] +name = "tor-config-path" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3005ab7b9a26a7271e5adf3dfb4ae18c09a943e32aeccc4f6d1c53a60de74b8d" +dependencies = [ + "directories", + "serde", + "shellexpand", + "thiserror 2.0.16", + "tor-error", + "tor-general-addr", +] + +[[package]] +name = "tor-consdiff" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bfa2b7b71c72830f61c48da4bb3e13191e0c0e1404b9c5168c795e4f5feb4a8" +dependencies = [ + "digest", + "hex", + "thiserror 2.0.16", + "tor-llcrypto", +] + +[[package]] +name = "tor-dirclient" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccd6fac844ac77c33ccdfcb56bf23ff40ebbb821ea708be79a481ec30e8c39c" +dependencies = [ + "async-compression", + "base64ct", + "derive_more", + "futures", + "hex", + "http 1.3.1", + "httparse", + "httpdate", + "itertools 0.14.0", + "memchr", + "thiserror 2.0.16", + "tor-circmgr", + "tor-error", + "tor-linkspec", + "tor-llcrypto", + "tor-netdoc", + "tor-proto", + "tor-rtcompat", + "tracing", +] + +[[package]] +name = "tor-dircommon" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0cf39a3c30321d145a4d60753ae7ef5bb58a66a00ac9e2bfc30bd823faf2a4" +dependencies = [ + "base64ct", + "derive-deftly", + "getset", + "humantime", + "humantime-serde", + "serde", + "tor-basic-utils", + "tor-checkable", + "tor-config", + "tor-linkspec", + "tor-llcrypto", + "tor-netdoc", + "tracing", +] + +[[package]] +name = "tor-dirmgr" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b52919aa9dbb82a354c5b904bef82e91beb702b9f8ad14e6eac4237d6128bf67" +dependencies = [ + "async-trait", + "base64ct", + "derive_builder_fork_arti", + "derive_more", + "digest", + "educe", + "event-listener 5.4.1", + "fs-mistrust", + "fslock", + "futures", + "hex", + "humantime", + "humantime-serde", + "itertools 0.14.0", + "memmap2", + "oneshot-fused-workaround", + "paste", + "postage", + "rand 0.9.2", + "rusqlite", + "safelog", + "scopeguard", + "serde", + "serde_json", + "signature", + "static_assertions", + "strum", + "thiserror 2.0.16", + "time", + "tor-async-utils", + "tor-basic-utils", + "tor-checkable", + "tor-circmgr", + "tor-config", + "tor-consdiff", + "tor-dirclient", + "tor-dircommon", + "tor-error", + "tor-guardmgr", + "tor-llcrypto", + "tor-netdir", + "tor-netdoc", + "tor-persist", + "tor-proto", + "tor-protover", + "tor-rtcompat", + "tracing", +] + +[[package]] +name = "tor-error" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "595b005e6f571ac3890a34a00f361200aab781fd0218f2c528c86fc7af088df5" +dependencies = [ + "derive_more", + "futures", + "paste", + "retry-error", + "static_assertions", + "strum", + "thiserror 2.0.16", + "tracing", + "void", +] + +[[package]] +name = "tor-general-addr" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "727b8c8bc01c1587486055edab5c2cd0d5c960f5bb3fac796fc9911872b8b397" +dependencies = [ + "derive_more", + "thiserror 2.0.16", + "void", +] + +[[package]] +name = "tor-guardmgr" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d337f465a477c0fb3b2faafa4654d70ff9df3590e57d22707591dddb4e4450c1" +dependencies = [ + "amplify", + "base64ct", + "derive-deftly", + "derive_builder_fork_arti", + "derive_more", + "dyn-clone", + "educe", + "futures", + "humantime", + "humantime-serde", + "itertools 0.14.0", + "num_enum", + "oneshot-fused-workaround", + "pin-project", + "postage", + "rand 0.9.2", + "safelog", + "serde", + "strum", + "thiserror 2.0.16", + "tor-async-utils", + "tor-basic-utils", + "tor-config", + "tor-dircommon", + "tor-error", + "tor-linkspec", + "tor-llcrypto", + "tor-netdir", + "tor-netdoc", + "tor-persist", + "tor-proto", + "tor-relay-selection", + "tor-rtcompat", + "tor-units", + "tracing", +] + +[[package]] +name = "tor-hscrypto" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3693cd43f05cd01ac0aaa060dae5c5e53c4364f89e0d769e33cd629a2fd3118" +dependencies = [ + "data-encoding", + "derive-deftly", + "derive_more", + "digest", + "hex", + "humantime", + "itertools 0.14.0", + "paste", + "rand 0.9.2", + "safelog", + "serde", + "signature", + "subtle", + "thiserror 2.0.16", + "tor-basic-utils", + "tor-bytes", + "tor-error", + "tor-key-forge", + "tor-llcrypto", + "tor-units", + "void", +] + +[[package]] +name = "tor-key-forge" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ade9065ae49cfe2ab020ca9ca9f2b3c5c9b5fc0d8980fa681d8b3a0668e042f" +dependencies = [ + "derive-deftly", + "derive_more", + "downcast-rs", + "paste", + "rand 0.9.2", + "rsa", + "signature", + "ssh-key", + "thiserror 2.0.16", + "tor-bytes", + "tor-cert", + "tor-checkable", + "tor-error", + "tor-llcrypto", +] + +[[package]] +name = "tor-keymgr" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "243c3163d376c4723cd67271fcd6e5d6b498a6865c6b98299640e1be01c38826" +dependencies = [ + "amplify", + "arrayvec", + "cfg-if", + "derive-deftly", + "derive_builder_fork_arti", + "derive_more", + "downcast-rs", + "dyn-clone", + "fs-mistrust", + "glob-match", + "humantime", + "inventory", + "itertools 0.14.0", + "rand 0.9.2", + "safelog", + "serde", + "signature", + "ssh-key", + "thiserror 2.0.16", + "tor-basic-utils", + "tor-bytes", + "tor-config", + "tor-config-path", + "tor-error", + "tor-hscrypto", + "tor-key-forge", + "tor-llcrypto", + "tor-persist", + "tracing", + "visibility", + "walkdir", + "zeroize", +] + +[[package]] +name = "tor-linkspec" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f1ea8786900d6fbe4c9f775d341b1ba01bbd1f750d89bd63be78b6b01e1836" +dependencies = [ + "base64ct", + "by_address", + "caret", + "derive-deftly", + "derive_builder_fork_arti", + "derive_more", + "hex", + "itertools 0.14.0", + "safelog", + "serde", + "serde_with", + "strum", + "thiserror 2.0.16", + "tor-basic-utils", + "tor-bytes", + "tor-config", + "tor-llcrypto", + "tor-memquota", + "tor-protover", +] + +[[package]] +name = "tor-llcrypto" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c6989a1c6d06ffd6835e2917edaae4aeef544f8e5fdd68b54cc365f2af523de" +dependencies = [ + "aes", + "base64ct", + "ctr", + "curve25519-dalek", + "der-parser", + "derive-deftly", + "derive_more", + "digest", + "ed25519-dalek", + "educe", + "getrandom 0.4.2", + "hex", + "rand 0.9.2", + "rand_chacha 0.9.0", + "rand_core 0.6.4", + "rand_core 0.9.3", + "rand_jitter", + "rdrand", + "rsa", + "safelog", + "serde", + "sha1", + "sha2", + "sha3", + "signature", + "subtle", + "thiserror 2.0.16", + "tor-error", + "tor-memquota-cost", + "visibility", + "x25519-dalek", + "zeroize", +] + +[[package]] +name = "tor-log-ratelim" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f1cd642180923d12e3fab5996b4aa2189718da7f465df6eb196ce2b9c70e293" +dependencies = [ + "futures", + "humantime", + "thiserror 2.0.16", + "tor-error", + "tor-rtcompat", + "tracing", + "weak-table", +] + +[[package]] +name = "tor-memquota" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "599daea60fd3272eb72a795d1c593b45bbe15343cbc702340a81db124c06eed5" +dependencies = [ + "cfg-if", + "derive-deftly", + "derive_more", + "dyn-clone", + "educe", + "futures", + "itertools 0.14.0", + "paste", + "pin-project", + "serde", + "slotmap-careful", + "static_assertions", + "sysinfo", + "thiserror 2.0.16", + "tor-async-utils", + "tor-basic-utils", + "tor-config", + "tor-error", + "tor-log-ratelim", + "tor-memquota-cost", + "tor-rtcompat", + "tracing", + "void", +] + +[[package]] +name = "tor-memquota-cost" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd92b07c0fc24e6d8166a5ff45e5b8654e68d89743c46d01889a16ab74c0b578" +dependencies = [ + "derive-deftly", + "itertools 0.14.0", + "paste", + "void", +] + +[[package]] +name = "tor-netdir" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41be8f47f521fc95206d2ba5facac8fb1a5b5b82169bd41ebeecdf46d1e77246" +dependencies = [ + "async-trait", + "bitflags 2.11.1", + "derive_more", + "futures", + "humantime", + "itertools 0.14.0", + "num_enum", + "rand 0.9.2", + "serde", + "strum", + "thiserror 2.0.16", + "tor-basic-utils", + "tor-error", + "tor-linkspec", + "tor-llcrypto", + "tor-netdoc", + "tor-protover", + "tor-units", + "tracing", + "typed-index-collections", +] + +[[package]] +name = "tor-netdoc" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea8bce73d2c78bd78a2a927336ca639cf6bd5d8ad092ebcd0b3fdeaa47dcc77e" +dependencies = [ + "amplify", + "base64ct", + "cipher", + "derive-deftly", + "derive_builder_fork_arti", + "derive_more", + "digest", + "educe", + "enumset", + "hex", + "humantime", + "itertools 0.14.0", + "memchr", + "paste", + "phf", + "saturating-time", + "serde", + "serde_with", + "signature", + "smallvec", + "strum", + "subtle", + "thiserror 2.0.16", + "time", + "tinystr", + "tor-basic-utils", + "tor-bytes", + "tor-cell", + "tor-cert", + "tor-checkable", + "tor-error", + "tor-llcrypto", + "tor-protover", + "void", + "zeroize", +] + +[[package]] +name = "tor-persist" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507ab4b6a3d59ed0df5804eeed66dcacde75e3be13d3694216cdfdb666bce625" +dependencies = [ + "derive-deftly", + "derive_more", + "filetime", + "fs-mistrust", + "fslock", + "futures", + "itertools 0.14.0", + "oneshot-fused-workaround", + "paste", + "sanitize-filename", + "serde", + "serde_json", + "thiserror 2.0.16", + "time", + "tor-async-utils", + "tor-basic-utils", + "tor-error", + "tracing", + "void", +] + +[[package]] +name = "tor-proto" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbfc552d535d36539d5782bb02028590bc472d219e49da51a96810725e80ff56" +dependencies = [ + "amplify", + "async-trait", + "asynchronous-codec", + "bitvec", + "bytes", + "caret", + "cfg-if", + "cipher", + "coarsetime", + "criterion-cycles-per-byte", + "derive-deftly", + "derive_builder_fork_arti", + "derive_more", + "digest", + "educe", + "enum_dispatch", + "futures", + "futures-util", + "hkdf", + "hmac", + "itertools 0.14.0", + "nonany", + "oneshot-fused-workaround", + "pin-project", + "postage", + "rand 0.9.2", + "rand_core 0.9.3", + "safelog", + "slotmap-careful", + "smallvec", + "static_assertions", + "subtle", + "sync_wrapper", + "thiserror 2.0.16", + "tokio", + "tokio-util", + "tor-async-utils", + "tor-basic-utils", + "tor-bytes", + "tor-cell", + "tor-cert", + "tor-checkable", + "tor-config", + "tor-error", + "tor-linkspec", + "tor-llcrypto", + "tor-log-ratelim", + "tor-memquota", + "tor-protover", + "tor-relay-crypto", + "tor-rtcompat", + "tor-rtmock", + "tor-units", + "tracing", + "typenum", + "visibility", + "void", + "zeroize", +] + +[[package]] +name = "tor-protover" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aed88527d070c4b7ea4e55a36d2d56d0500e30ca66298b5264f047f7f2f89cfa" +dependencies = [ + "caret", + "paste", + "serde_with", + "thiserror 2.0.16", + "tor-basic-utils", + "tor-bytes", +] + +[[package]] +name = "tor-relay-crypto" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7e57e9f71b22ae1df63dbccc8e428cb07feec0abd654735109fa563c10bbb90" +dependencies = [ + "derive-deftly", + "derive_more", + "humantime", + "tor-cert", + "tor-checkable", + "tor-error", + "tor-key-forge", + "tor-keymgr", + "tor-llcrypto", + "tor-persist", +] + +[[package]] +name = "tor-relay-selection" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a372072ac9dea7d17e49693cc3f3ae77b3abf8125630516c9f2d622239b1920a" +dependencies = [ + "rand 0.9.2", + "serde", + "tor-basic-utils", + "tor-linkspec", + "tor-netdir", + "tor-netdoc", +] + +[[package]] +name = "tor-rtcompat" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14428b930e59003e801c0c32697c0aeb9b0495ad33ecbe8c6753bdb596233270" +dependencies = [ + "async-native-tls", + "async-trait", + "async_executors", + "asynchronous-codec", + "cfg-if", + "coarsetime", + "derive_more", + "dyn-clone", + "educe", + "futures", + "hex", + "libc", + "native-tls", + "paste", + "pin-project", + "socket2 0.6.3", + "thiserror 2.0.16", + "tokio", + "tokio-util", + "tor-error", + "tor-general-addr", + "tracing", + "void", + "zeroize", +] + +[[package]] +name = "tor-rtmock" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2da91a432cdaee8a93e0bb21b02f3e9c7667832ccbb4b54e00d9c1214638e70" +dependencies = [ + "amplify", + "assert_matches", + "async-trait", + "derive-deftly", + "derive_more", + "educe", + "futures", + "humantime", + "itertools 0.14.0", + "oneshot-fused-workaround", + "pin-project", + "priority-queue", + "slotmap-careful", + "strum", + "thiserror 2.0.16", + "tor-error", + "tor-general-addr", + "tor-rtcompat", + "tracing", + "tracing-test", + "void", +] + +[[package]] +name = "tor-socksproto" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adbc9115a2f506d9bb86ae4446f0ca70eb523dc2f5e900a33582e7c39decc23a" +dependencies = [ + "amplify", + "caret", + "derive-deftly", + "educe", + "safelog", + "subtle", + "thiserror 2.0.16", + "tor-bytes", + "tor-error", +] + +[[package]] +name = "tor-units" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da90e93b4b4aa4ec356ecbe9e19aced36fdd655e94ca459d1915120d873363f0" +dependencies = [ + "derive-deftly", + "derive_more", + "serde", + "thiserror 2.0.16", + "tor-memquota", +] + [[package]] name = "tower" version = "0.4.13" @@ -3592,9 +7162,9 @@ checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" -version = "0.1.40" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "log", "pin-project-lite", @@ -3604,9 +7174,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.27" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", @@ -3615,9 +7185,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.32" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", "valuable", @@ -3689,6 +7259,27 @@ dependencies = [ "tracing-log 0.2.0", ] +[[package]] +name = "tracing-test" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19a4c448db514d4f24c5ddb9f73f2ee71bfb24c526cf0c570ba142d1119e0051" +dependencies = [ + "tracing-core", + "tracing-subscriber", + "tracing-test-macro", +] + +[[package]] +name = "tracing-test-macro" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad06847b7afb65c7866a36664b75c40b895e318cea4f71299f013fb22965329d" +dependencies = [ + "quote", + "syn 2.0.106", +] + [[package]] name = "try-lock" version = "0.2.5" @@ -3710,7 +7301,7 @@ dependencies = [ "log", "nix 0.26.4", "reqwest 0.11.23", - "schemars", + "schemars 0.8.16", "serde", "socket2 0.5.10", "ssri", @@ -3718,10 +7309,20 @@ dependencies = [ "tokio", "tracing", "widestring", - "windows", + "windows 0.48.0", "zip", ] +[[package]] +name = "typed-index-collections" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "898160f1dfd383b4e92e17f0512a7d62f3c51c44937b23b6ffc3a1614a8eaccd" +dependencies = [ + "bincode", + "serde", +] + [[package]] name = "typenum" version = "1.17.0" @@ -3729,10 +7330,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] -name = "unicode-bidi" -version = "0.3.15" +name = "uncased" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" +checksum = "e1b88fcfe09e89d3866a5c11019378088af2d24c3fbd4f0543f96b479ec90697" +dependencies = [ + "version_check", +] [[package]] name = "unicode-ident" @@ -3741,13 +7345,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] -name = "unicode-normalization" -version = "0.1.22" +name = "unicode-segmentation" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" -dependencies = [ - "tinyvec", -] +checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" [[package]] name = "unicode-width" @@ -3755,6 +7356,12 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "universal-hash" version = "0.5.1" @@ -3765,6 +7372,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + [[package]] name = "untrusted" version = "0.9.0" @@ -3772,16 +7385,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] -name = "url" -version = "2.5.0" +name = "unty" +version = "0.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" dependencies = [ "form_urlencoded", "idna", "percent-encoding", + "serde", ] +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "utf8parse" version = "0.2.1" @@ -3821,6 +7447,33 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "visibility" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d674d135b4a8c1d7e813e2f8d1c9a58308aee4a680323066025e53132218bd91" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.1" @@ -3851,14 +7504,32 @@ version = "1.0.1+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.46.0", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen 0.51.0", +] + +[[package]] +name = "wasix" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1757e0d1f8456693c7e5c6c629bdb54884e032aa0bb53c155f6a39f94440d332" +dependencies = [ + "wasi 0.11.0+wasi-snapshot-preview1", ] [[package]] name = "wasm-bindgen" -version = "0.2.103" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab10a69fbd0a177f5f649ad4d8d3305499c42bab9aef2f7ff592d0ec8f833819" +checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409" dependencies = [ "cfg-if", "once_cell", @@ -3867,20 +7538,6 @@ dependencies = [ "wasm-bindgen-shared", ] -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.103" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb702423545a6007bbc368fde243ba47ca275e549c8a28617f56f6ba53b1d1c" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn 2.0.106", - "wasm-bindgen-shared", -] - [[package]] name = "wasm-bindgen-futures" version = "0.4.40" @@ -3895,9 +7552,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.103" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc65f4f411d91494355917b605e1480033152658d71f722a90647f56a70c88a0" +checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3905,26 +7562,66 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.103" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffc003a991398a8ee604a401e194b6b3a39677b3173d6e74495eb51b82e99a32" +checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e" dependencies = [ + "bumpalo", "proc-macro2", "quote", "syn 2.0.106", - "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.103" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "293c37f4efa430ca14db3721dfbe48d8c33308096bd44d80ebaa775ab71ba1cf" +checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437" dependencies = [ "unicode-ident", ] +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap 2.11.4", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags 2.11.1", + "hashbrown 0.15.5", + "indexmap 2.11.4", + "semver", +] + +[[package]] +name = "weak-table" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "323f4da9523e9a669e1eaf9c6e763892769b1d38c623913647bfdc1532fe4549" + [[package]] name = "web-sys" version = "0.3.67" @@ -3997,6 +7694,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -4012,6 +7718,145 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections", + "windows-core 0.61.2", + "windows-future", + "windows-link 0.1.3", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core 0.61.2", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.1.3", + "windows-result 0.3.4", + "windows-strings 0.4.2", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link 0.2.1", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -4030,6 +7875,24 @@ dependencies = [ "windows-targets 0.52.0", ] +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link 0.2.1", +] + [[package]] name = "windows-targets" version = "0.48.5" @@ -4060,6 +7923,32 @@ dependencies = [ "windows_x86_64_msvc 0.52.0", ] +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link 0.2.1", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link 0.1.3", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -4072,6 +7961,12 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -4084,6 +7979,12 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -4096,6 +7997,18 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -4108,6 +8021,12 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -4120,6 +8039,12 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -4132,6 +8057,12 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -4144,6 +8075,12 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + [[package]] name = "winnow" version = "0.5.34" @@ -4162,6 +8099,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "winnow" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" + [[package]] name = "winreg" version = "0.50.0" @@ -4188,6 +8131,109 @@ version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck 0.5.0", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck 0.5.0", + "indexmap 2.11.4", + "prettyplease", + "syn 2.0.106", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.106", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags 2.11.1", + "indexmap 2.11.4", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap 2.11.4", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + [[package]] name = "x25519-dalek" version = "2.0.0" @@ -4206,6 +8252,38 @@ version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53be06678ed9e83edb1745eb72efc0bbcd7b5c3c35711a860906aed827a13d61" +[[package]] +name = "xz2" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388c44dc09d76f1536602ead6d325eb532f5c122f17782bd57fb47baeeb767e2" +dependencies = [ + "lzma-sys", +] + +[[package]] +name = "yoke" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", + "synstructure", +] + [[package]] name = "zerocopy" version = "0.8.27" @@ -4226,6 +8304,27 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", + "synstructure", +] + [[package]] name = "zeroize" version = "1.7.0" @@ -4246,6 +8345,40 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "serde", + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "zip" version = "0.6.6" @@ -4263,16 +8396,31 @@ dependencies = [ "pbkdf2", "sha1", "time", - "zstd", + "zstd 0.11.2+zstd.1.5.2", ] +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" + [[package]] name = "zstd" version = "0.11.2+zstd.1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" dependencies = [ - "zstd-safe", + "zstd-safe 5.0.2+zstd.1.5.2", +] + +[[package]] +name = "zstd" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bffb3309596d527cfcba7dfc6ed6052f1d39dfbd7c867aa2e865e4a449c10110" +dependencies = [ + "zstd-safe 7.0.0", ] [[package]] @@ -4285,6 +8433,15 @@ dependencies = [ "zstd-sys", ] +[[package]] +name = "zstd-safe" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43747c7422e2924c11144d5229878b98180ef8b06cca4ab5af37afc8a8d8ea3e" +dependencies = [ + "zstd-sys", +] + [[package]] name = "zstd-sys" version = "2.0.9+zstd.1.5.5" diff --git a/burrow-gtk/src/components/home_screen.rs b/burrow-gtk/src/components/home_screen.rs index 0bfdda2..655cad3 100644 --- a/burrow-gtk/src/components/home_screen.rs +++ b/burrow-gtk/src/components/home_screen.rs @@ -1,6 +1,6 @@ use super::*; use crate::account_store::{self, AccountKind, AccountRecord}; -use std::time::Duration; +use std::{cell::RefCell, rc::Rc, time::Duration}; pub struct HomeScreen { daemon_banner: adw::Banner, @@ -23,6 +23,7 @@ pub enum HomeScreenMsg { OpenWireGuard, OpenTor, OpenTailnet, + OpenProxySubscription, AddWireGuard { title: String, account: String, @@ -52,6 +53,14 @@ pub enum HomeScreenMsg { hostname: Option, tailnet: Option, }, + PreviewProxySubscription { + url: String, + }, + AddProxySubscription { + url: String, + name: String, + selected_ordinal: Option, + }, } #[relm4::component(pub, async)] @@ -234,7 +243,7 @@ impl AsyncComponent for HomeScreen { configure_add_popover(&widgets.add_button, &sender); let refresh_sender = sender.input_sender().clone(); - relm4::spawn(async move { + gtk::glib::MainContext::default().spawn_local(async move { loop { tokio::time::sleep(Duration::from_secs(5)).await; refresh_sender.emit(HomeScreenMsg::Refresh); @@ -272,6 +281,7 @@ impl AsyncComponent for HomeScreen { HomeScreenMsg::OpenWireGuard => open_wireguard_window(root, &sender), HomeScreenMsg::OpenTor => open_tor_window(root, &sender), HomeScreenMsg::OpenTailnet => open_tailnet_window(root, &sender), + HomeScreenMsg::OpenProxySubscription => open_proxy_subscription_window(root, &sender), HomeScreenMsg::AddWireGuard { title, account, @@ -304,6 +314,13 @@ impl AsyncComponent for HomeScreen { self.add_tailnet(authority, account, identity, hostname, tailnet) .await; } + HomeScreenMsg::PreviewProxySubscription { url } => { + self.preview_proxy_subscription(url).await; + } + HomeScreenMsg::AddProxySubscription { url, name, selected_ordinal } => { + self.add_proxy_subscription(url, name, selected_ordinal) + .await; + } } } } @@ -667,6 +684,85 @@ impl HomeScreen { } } + async fn preview_proxy_subscription(&mut self, url: String) { + let Ok(url) = daemon_api::require_value(&url, "Subscription URL") else { + self.network_status + .set_label("Enter a subscription URL before previewing."); + return; + }; + + self.network_status + .set_label("Previewing proxy subscription..."); + match daemon_api::preview_proxy_subscription(url).await { + Ok(preview) => { + let first_nodes = preview + .nodes + .iter() + .take(3) + .map(|node| { + let warning = if node.warnings.is_empty() { + String::new() + } else { + format!(" - {}", node.warnings.join(", ")) + }; + format!( + "{}: {} {}:{}{}", + node.protocol, node.name, node.server, node.port, warning + ) + }) + .collect::>() + .join("\n"); + let warnings = if preview.warnings.is_empty() { + String::new() + } else { + format!("\n{}", preview.warnings.join("\n")) + }; + self.network_status.set_label(&format!( + "Found {} compatible nodes and {} rejected entries in {}. Suggested name: {}.{}{}", + preview.compatible_count, + preview.rejected_count, + preview.detected_format, + preview.suggested_name, + warnings, + if first_nodes.is_empty() { + String::new() + } else { + format!("\n{first_nodes}") + } + )); + } + Err(error) => self + .network_status + .set_label(&format!("Proxy subscription preview failed: {error}")), + } + } + + async fn add_proxy_subscription( + &mut self, + url: String, + name: String, + selected_ordinal: Option, + ) { + let Ok(url) = daemon_api::require_value(&url, "Subscription URL") else { + self.network_status + .set_label("Enter a subscription URL before importing."); + return; + }; + + self.network_status + .set_label("Importing proxy subscription..."); + match daemon_api::add_proxy_subscription(url, name, selected_ordinal).await { + Ok(id) => { + self.network_status + .set_label(&format!("Imported proxy subscription network #{id}.")); + self.refresh().await; + } + Err(error) => self + .network_status + .set_label(&format!("Unable to import proxy subscription: {error}")), + } + } + fn apply_login_status(&mut self, status: &daemon_api::TailnetLoginStatus) { self.tailnet_session_id = Some(status.session_id.clone()); self.tailnet_running = status.running; @@ -735,6 +831,10 @@ fn configure_add_popover(button: >k::MenuButton, sender: &AsyncComponentSender for (label, msg) in [ ("Add WireGuard Network", HomeScreenMsg::OpenWireGuard), + ( + "Import Proxy Subscription", + HomeScreenMsg::OpenProxySubscription, + ), ("Save Tor Account", HomeScreenMsg::OpenTor), ("Add Tailnet Account", HomeScreenMsg::OpenTailnet), ] { @@ -755,6 +855,7 @@ fn msg_from_template(msg: &HomeScreenMsg) -> HomeScreenMsg { HomeScreenMsg::OpenWireGuard => HomeScreenMsg::OpenWireGuard, HomeScreenMsg::OpenTor => HomeScreenMsg::OpenTor, HomeScreenMsg::OpenTailnet => HomeScreenMsg::OpenTailnet, + HomeScreenMsg::OpenProxySubscription => HomeScreenMsg::OpenProxySubscription, _ => unreachable!(), } } @@ -1091,6 +1192,169 @@ fn open_tailnet_window(root: >k::ScrolledWindow, sender: &AsyncComponentSender window.present(); } +fn open_proxy_subscription_window( + root: >k::ScrolledWindow, + sender: &AsyncComponentSender, +) { + let window = sheet_window(root, "Proxy Subscription", 560, 520); + let content = sheet_content( + &window, + "Import Proxy Subscription", + "Import Trojan and Shadowsocks nodes through the Burrow daemon.", + ); + + let url = gtk::Entry::new(); + url.set_placeholder_text(Some("Subscription URL")); + let name = gtk::Entry::new(); + name.set_placeholder_text(Some("Name")); + let node_list = gtk::ListBox::new(); + node_list.set_selection_mode(gtk::SelectionMode::Single); + node_list.set_sensitive(false); + node_list.add_css_class("boxed-list"); + let node_scroll = gtk::ScrolledWindow::builder() + .min_content_height(220) + .vexpand(true) + .child(&node_list) + .build(); + let preview_status = gtk::Label::new(Some("Preview the subscription to choose a node.")); + preview_status.add_css_class("dim-label"); + preview_status.set_xalign(0.0); + preview_status.set_wrap(true); + let selected_ordinal = Rc::new(RefCell::new(None::)); + let node_ordinals = Rc::new(RefCell::new(Vec::::new())); + + content.append(§ion_label("Subscription")); + content.append(&url); + content.append(&name); + content.append(§ion_label("Node")); + content.append(&node_scroll); + content.append(&preview_status); + + let actions = gtk::Box::new(gtk::Orientation::Horizontal, 8); + let preview = gtk::Button::with_label("Preview"); + let import = gtk::Button::with_label("Import"); + import.add_css_class("suggested-action"); + actions.append(&preview); + actions.append(&import); + content.append(&actions); + + let url_for_preview = url.clone(); + let name_for_preview = name.clone(); + let list_for_preview = node_list.clone(); + let status_for_preview = preview_status.clone(); + let selected_for_preview = selected_ordinal.clone(); + let ordinals_for_preview = node_ordinals.clone(); + preview.connect_clicked(move |_| { + let url = url_for_preview.text().to_string(); + let name = name_for_preview.clone(); + let list = list_for_preview.clone(); + let status = status_for_preview.clone(); + let selected = selected_for_preview.clone(); + let ordinals = ordinals_for_preview.clone(); + status.set_label("Previewing proxy subscription..."); + while let Some(child) = list.first_child() { + list.remove(&child); + } + list.set_sensitive(false); + *selected.borrow_mut() = None; + ordinals.borrow_mut().clear(); + gtk::glib::MainContext::default().spawn_local(async move { + match daemon_api::preview_proxy_subscription(url).await { + Ok(preview) => { + if name.text().trim().is_empty() { + name.set_text(&preview.suggested_name); + } + for node in preview.nodes.iter().filter(|node| node.runtime_supported) { + ordinals.borrow_mut().push(node.ordinal); + let row = gtk::ListBoxRow::new(); + row.set_selectable(true); + row.set_activatable(true); + + let row_content = gtk::Box::new(gtk::Orientation::Vertical, 2); + row_content.set_margin_top(8); + row_content.set_margin_bottom(8); + row_content.set_margin_start(10); + row_content.set_margin_end(10); + + let title = gtk::Label::new(Some(&format!( + "{} {}", + node.protocol.to_uppercase(), + node.name + ))); + title.set_xalign(0.0); + title.set_ellipsize(gtk::pango::EllipsizeMode::End); + let endpoint = gtk::Label::new(Some(&format!("{}:{}", node.server, node.port))); + endpoint.add_css_class("dim-label"); + endpoint.set_xalign(0.0); + endpoint.set_ellipsize(gtk::pango::EllipsizeMode::Middle); + + row_content.append(&title); + row_content.append(&endpoint); + row.set_child(Some(&row_content)); + list.append(&row); + } + if let Some(first) = preview.nodes.iter().find(|node| node.runtime_supported) { + if let Some(row) = list.row_at_index(0) { + list.select_row(Some(&row)); + } + list.set_sensitive(true); + *selected.borrow_mut() = Some(first.ordinal); + } + let unsupported = preview + .nodes + .iter() + .filter(|node| !node.runtime_supported) + .count(); + let supported = preview.nodes.len().saturating_sub(unsupported); + let warnings = if preview.warnings.is_empty() { + String::new() + } else { + format!(" {}", preview.warnings.join(" ")) + }; + status.set_label(&format!( + "Found {} runtime-supported nodes, {} unsupported nodes, and {} rejected entries in {}.{}", + supported, + unsupported, + preview.rejected_count, + preview.detected_format, + warnings + )); + } + Err(error) => { + status.set_label(&format!("Proxy subscription preview failed: {error}")); + } + } + }); + }); + + let selected_for_list = selected_ordinal.clone(); + let ordinals_for_list = node_ordinals.clone(); + node_list.connect_row_selected(move |_, row| { + *selected_for_list.borrow_mut() = row.and_then(|row| { + let index = row.index(); + if index < 0 { + return None; + } + ordinals_for_list.borrow().get(index as usize).copied() + }); + }); + + let input = sender.input_sender().clone(); + let window_for_click = window.clone(); + let selected_for_import = selected_ordinal.clone(); + import.connect_clicked(move |_| { + input.emit(HomeScreenMsg::AddProxySubscription { + url: url.text().to_string(), + name: name.text().to_string(), + selected_ordinal: *selected_for_import.borrow(), + }); + window_for_click.close(); + }); + + window.set_child(Some(&content)); + window.present(); +} + fn sheet_window(root: >k::ScrolledWindow, title: &str, width: i32, height: i32) -> gtk::Window { let window = gtk::Window::builder() .title(title) diff --git a/burrow-gtk/src/daemon_api.rs b/burrow-gtk/src/daemon_api.rs index 4ff8bf5..3b1c5db 100644 --- a/burrow-gtk/src/daemon_api.rs +++ b/burrow-gtk/src/daemon_api.rs @@ -4,7 +4,9 @@ use burrow::{ grpc_defs::{ Empty, Network, NetworkType, State, TailnetDiscoverRequest, TailnetLoginCancelRequest, TailnetLoginStartRequest, TailnetLoginStatusRequest, TailnetProbeRequest, + ProxySubscriptionApplyRequest, ProxySubscriptionImportRequest, }, + proxy_subscription::ProxySubscriptionPayload, BurrowClient, }; use std::{path::PathBuf, sync::OnceLock}; @@ -54,6 +56,27 @@ pub struct TailnetLoginStatus { pub health: Vec, } +#[derive(Debug, Clone)] +pub struct ProxySubscriptionPreview { + pub suggested_name: String, + pub detected_format: String, + pub compatible_count: i32, + pub rejected_count: i32, + pub nodes: Vec, + pub warnings: Vec, +} + +#[derive(Debug, Clone)] +pub struct ProxyNodePreview { + pub ordinal: i32, + pub name: String, + pub protocol: String, + pub server: String, + pub port: i32, + pub warnings: Vec, + pub runtime_supported: bool, +} + pub fn default_tailnet_authority() -> &'static str { MANAGED_TAILSCALE_AUTHORITY } @@ -216,6 +239,63 @@ pub async fn add_tailnet( add_network(NetworkType::Tailnet, payload).await } +pub async fn preview_proxy_subscription(url: String) -> Result { + let mut client = BurrowClient::from_uds().await?; + let response = timeout( + Duration::from_secs(20), + client + .proxy_subscription_client + .preview_import(ProxySubscriptionImportRequest { url }), + ) + .await + .context("timed out previewing proxy subscription")?? + .into_inner(); + + Ok(ProxySubscriptionPreview { + suggested_name: response.suggested_name, + detected_format: response.detected_format, + compatible_count: response.compatible_count, + rejected_count: response.rejected_count, + nodes: response + .node + .into_iter() + .map(|node| ProxyNodePreview { + ordinal: node.ordinal, + name: node.name, + protocol: node.protocol, + server: node.server, + port: node.port, + warnings: node.warnings, + runtime_supported: node.runtime_supported, + }) + .collect(), + warnings: response.warnings, + }) +} + +pub async fn add_proxy_subscription( + url: String, + name: String, + selected_ordinal: Option, +) -> Result { + let id = next_network_id().await?; + let mut client = BurrowClient::from_uds().await?; + timeout( + Duration::from_secs(25), + client + .proxy_subscription_client + .apply_import(ProxySubscriptionApplyRequest { + id, + url, + name, + selected_ordinal: selected_ordinal.unwrap_or(-1), + }), + ) + .await + .context("timed out importing proxy subscription")??; + Ok(id) +} + pub async fn discover_tailnet(email: String) -> Result { let mut client = BurrowClient::from_uds().await?; let response = timeout( @@ -328,6 +408,12 @@ fn summarize_network(network: &Network) -> NetworkSummary { match network.r#type() { NetworkType::WireGuard => summarize_wireguard(network), NetworkType::Tailnet => summarize_tailnet(network), + NetworkType::Trojan => NetworkSummary { + id: network.id, + title: "Legacy Trojan Proxy".to_owned(), + detail: "Unsupported SOCKS-era profile. Import a proxy subscription instead.".to_owned(), + }, + NetworkType::ProxySubscription => summarize_proxy_subscription(network), } } @@ -372,6 +458,21 @@ fn summarize_tailnet(network: &Network) -> NetworkSummary { } } +fn summarize_proxy_subscription(network: &Network) -> NetworkSummary { + match serde_json::from_slice::(&network.payload) { + Ok(payload) => NetworkSummary { + id: network.id, + title: payload.name.clone(), + detail: burrow::proxy_subscription::summarize_payload(&payload), + }, + Err(error) => NetworkSummary { + id: network.id, + title: "Proxy Subscription".to_owned(), + detail: format!("Unable to read proxy subscription payload: {error}"), + }, + } +} + fn decode_tailnet_status( response: burrow::grpc_defs::TailnetLoginStatusResponse, ) -> TailnetLoginStatus { diff --git a/burrow/Cargo.toml b/burrow/Cargo.toml index 22f3d25..27d072d 100644 --- a/burrow/Cargo.toml +++ b/burrow/Cargo.toml @@ -31,6 +31,7 @@ tracing-subscriber = { version = "0.3", features = ["std", "env-filter"] } log = "0.4" serde = { version = "1", features = ["derive"] } serde_json = "1.0" +serde_yaml = "0.9" blake2 = "0.10" chacha20poly1305 = "0.10" rand = "0.8" @@ -44,6 +45,7 @@ x25519-dalek = { version = "2.0", features = [ ring = "0.17" parking_lot = "0.12" hmac = "0.12" +md-5 = "0.10" base64 = "0.21" fehler = "1.0" ip_network_table = "0.2" @@ -78,6 +80,15 @@ hyper-util = "0.1.6" toml = "0.8.15" rust-ini = "0.21.0" subtle = "2.6" +rustls = { version = "0.23", default-features = false, features = [ + "aws_lc_rs", + "logging", + "std", + "tls12", +] } +sha2 = "0.10" +tokio-rustls = "0.26" +webpki-roots = "0.26" [target.'cfg(target_os = "linux")'.dependencies] caps = "0.5" @@ -87,8 +98,11 @@ nix = { version = "0.27", features = ["fs", "socket", "uio"] } tracing-journald = "0.3" [target.'cfg(target_vendor = "apple")'.dependencies] +libc = "0.2" +native-tls = { version = "0.2", features = ["alpn"] } nix = { version = "0.27" } rusqlite = { version = "0.38.0", features = ["bundled", "blob"] } +tokio-native-tls = "0.3" [target.'cfg(target_os = "macos")'.dependencies] tracing-oslog = { git = "https://github.com/Stormshield-robinc/tracing-oslog" } diff --git a/burrow/src/auth/server/mod.rs b/burrow/src/auth/server/mod.rs index fdffce3..d14e9a3 100644 --- a/burrow/src/auth/server/mod.rs +++ b/burrow/src/auth/server/mod.rs @@ -147,7 +147,10 @@ pub fn build_router(config: AuthServerConfig) -> Router { .route("/v1/control/map", post(control_map)) .route("/v1/tailnet/discover", get(tailnet_discover)) .route("/v1/tailscale/login/start", post(tailscale_login_start)) - .route("/v1/tailscale/login/:session_id", get(tailscale_login_status)) + .route( + "/v1/tailscale/login/:session_id", + get(tailscale_login_status), + ) .with_state(AppState { config, tailscale: tailscale::TailscaleBridgeManager::default(), @@ -247,7 +250,12 @@ async fn tailscale_login_status( .await .map_err(internal_error)? .map(Json) - .ok_or_else(|| (StatusCode::NOT_FOUND, "unknown tailscale login session".to_owned())) + .ok_or_else(|| { + ( + StatusCode::NOT_FOUND, + "unknown tailscale login session".to_owned(), + ) + }) } async fn healthz() -> impl IntoResponse { @@ -419,10 +427,7 @@ mod tests { async fn tailnet_discover_requires_email() -> Result<()> { let app = build_router(AuthServerConfig::default()); let response = app - .oneshot( - Request::get("/v1/tailnet/discover?email=") - .body(Body::empty())?, - ) + .oneshot(Request::get("/v1/tailnet/discover?email=").body(Body::empty())?) .await?; assert_eq!(response.status(), StatusCode::BAD_REQUEST); Ok(()) diff --git a/burrow/src/auth/server/tailscale.rs b/burrow/src/auth/server/tailscale.rs index d08c807..85bb1f2 100644 --- a/burrow/src/auth/server/tailscale.rs +++ b/burrow/src/auth/server/tailscale.rs @@ -291,7 +291,10 @@ impl TailscaleHelperProcess { } async fn shutdown_with_client(&self, client: &Client) -> Result<()> { - let _ = client.post(format!("{}/shutdown", self.listen_url)).send().await; + let _ = client + .post(format!("{}/shutdown", self.listen_url)) + .send() + .await; for _ in 0..10 { let mut child = self.child.lock().await; @@ -402,7 +405,9 @@ fn helper_command(request: &TailscaleLoginStartRequest, state_dir: &Path) -> Res } pub(crate) fn packet_socket_path(request: &TailscaleLoginStartRequest) -> PathBuf { - state_root().join(session_dir_name(request)).join("packet.sock") + state_root() + .join(session_dir_name(request)) + .join("packet.sock") } pub(crate) fn state_root() -> PathBuf { @@ -504,7 +509,10 @@ mod tests { control_url: None, packet_socket: None, }; - assert_eq!(session_dir_name(&request), "default-apple-tailscale-managed"); + assert_eq!( + session_dir_name(&request), + "default-apple-tailscale-managed" + ); assert_eq!(default_hostname(&request), "burrow-apple"); let custom_request = TailscaleLoginStartRequest { diff --git a/burrow/src/control/discovery.rs b/burrow/src/control/discovery.rs index d044a62..b86aedb 100644 --- a/burrow/src/control/discovery.rs +++ b/burrow/src/control/discovery.rs @@ -92,8 +92,13 @@ pub async fn probe_tailnet_authority(authority: &str) -> Result Result Ok(None), - status => Err(anyhow!("tailnet well-known lookup failed with HTTP {status}")), + status => Err(anyhow!( + "tailnet well-known lookup failed with HTTP {status}" + )), } } -async fn discover_webfinger(client: &Client, email: &str, base_url: &Url) -> Result> { +async fn discover_webfinger( + client: &Client, + email: &str, + base_url: &Url, +) -> Result> { let mut url = base_url .join(WEBFINGER_PATH) .context("failed to build webfinger URL")?; @@ -222,7 +233,9 @@ async fn discover_webfinger(client: &Client, email: &str, base_url: &Url) -> Res .filter(|href| !href.trim().is_empty())) } StatusCode::NOT_FOUND => Ok(None), - status => Err(anyhow!("tailnet webfinger lookup failed with HTTP {status}")), + status => Err(anyhow!( + "tailnet webfinger lookup failed with HTTP {status}" + )), } } @@ -274,7 +287,9 @@ mod tests { #[test] fn detects_managed_tailscale_authority() { assert!(is_managed_tailscale_authority("controlplane.tailscale.com")); - assert!(is_managed_tailscale_authority("https://controlplane.tailscale.com/")); + assert!(is_managed_tailscale_authority( + "https://controlplane.tailscale.com/" + )); assert!(!is_managed_tailscale_authority("https://ts.burrow.net")); } diff --git a/burrow/src/daemon/apple.rs b/burrow/src/daemon/apple.rs index f369ea9..45a2047 100644 --- a/burrow/src/daemon/apple.rs +++ b/burrow/src/daemon/apple.rs @@ -1,3 +1,5 @@ +#[cfg(unix)] +use std::os::unix::net::UnixStream; use std::{ ffi::{c_char, CStr}, path::PathBuf, @@ -34,12 +36,17 @@ pub unsafe extern "C" fn spawn_in_process(path: *const c_char, db_path: *const c } pub fn spawn_in_process_with_paths(path_buf: Option, db_path_buf: Option) { + configure_in_process_logging(db_path_buf.as_ref()); crate::tracing::initialize(); let _guard = BURROW_SPAWN_LOCK.lock().unwrap(); if BURROW_READY.get().is_some() { return; } + if socket_is_reachable(path_buf.as_ref()) { + let _ = BURROW_READY.set(()); + return; + } let notify = Arc::new(Notify::new()); let handle = BURROW_HANDLE.get_or_init(|| { @@ -74,3 +81,28 @@ pub fn spawn_in_process_with_paths(path_buf: Option, db_path_buf: Optio handle.block_on(async move { receiver.notified().await }); let _ = BURROW_READY.set(()); } + +fn configure_in_process_logging(db_path_buf: Option<&PathBuf>) { + if std::env::var_os("BURROW_LOG_FILE").is_none() { + if let Some(log_dir) = db_path_buf.and_then(|path| path.parent()) { + std::env::set_var("BURROW_LOG_FILE", log_dir.join("burrow-runtime.log")); + } + } + + if std::env::var_os("BURROW_LOG").is_none() && std::env::var_os("RUST_LOG").is_none() { + std::env::set_var( + "BURROW_LOG", + "burrow=debug,h2=warn,hyper=warn,tower=warn,tonic=warn", + ); + } +} + +#[cfg(unix)] +fn socket_is_reachable(path: Option<&PathBuf>) -> bool { + path.is_some_and(|path| UnixStream::connect(path).is_ok()) +} + +#[cfg(not(unix))] +fn socket_is_reachable(_path: Option<&PathBuf>) -> bool { + false +} diff --git a/burrow/src/daemon/instance.rs b/burrow/src/daemon/instance.rs index 9b2e138..b1d5084 100644 --- a/burrow/src/daemon/instance.rs +++ b/burrow/src/daemon/instance.rs @@ -8,16 +8,20 @@ use rusqlite::Connection; use tokio::sync::{mpsc, watch, RwLock}; use tokio_stream::wrappers::ReceiverStream; use tonic::{Request, Response, Status as RspStatus}; -use tracing::{debug, info, warn}; +use tracing::{debug, error, info, warn}; use tun::tokio::TunInterface; use super::{ rpc::grpc_defs::{ - networks_server::Networks, tailnet_control_server::TailnetControl, tunnel_server::Tunnel, - Empty, Network, NetworkDeleteRequest, NetworkListResponse, NetworkReorderRequest, - State as RPCTunnelState, TailnetDiscoverRequest, TailnetDiscoverResponse, - TailnetProbeRequest, TailnetProbeResponse, TunnelConfigurationResponse, TunnelPacket, - TunnelStatusResponse, + networks_server::Networks, proxy_subscriptions_server::ProxySubscriptions, + tailnet_control_server::TailnetControl, tunnel_server::Tunnel, Empty, Network, + NetworkDeleteRequest, NetworkListResponse, NetworkReorderRequest, NetworkType, + ProxySubscriptionApplyRequest, ProxySubscriptionApplyResponse, + ProxySubscriptionImportRequest, ProxySubscriptionNodePreview, + ProxySubscriptionPreviewResponse, ProxySubscriptionRejectedEntry, + ProxySubscriptionSelectRequest, ProxySubscriptionSelectResponse, State as RPCTunnelState, + TailnetDiscoverRequest, TailnetDiscoverResponse, TailnetProbeRequest, TailnetProbeResponse, + TunnelConfigurationResponse, TunnelPacket, TunnelStatusResponse, }, runtime::{tailnet_helper_request, ActiveTunnel, ResolvedTunnel}, }; @@ -28,7 +32,11 @@ use crate::{ }, control::discovery, daemon::rpc::ServerConfig, - database::{add_network, delete_network, get_connection, list_networks, reorder_network}, + database::{ + add_network, delete_network, get_connection, list_networks, proxy_subscription_payload, + reorder_network, select_proxy_subscription_node, upsert_network, + }, + proxy_subscription, }; #[derive(Debug, Clone)] @@ -89,9 +97,7 @@ impl DaemonRPCServer { async fn current_tunnel_configuration(&self) -> Result { let config = { let active = self.active_tunnel.read().await; - active - .as_ref() - .map(|tunnel| tunnel.server_config().clone()) + active.as_ref().map(|tunnel| tunnel.server_config().clone()) }; let config = match config { Some(config) => config, @@ -104,6 +110,22 @@ impl DaemonRPCServer { Ok(configuration_rsp(config)) } + async fn update_active_proxy_subscription_runtime( + &self, + id: i32, + payload: &proxy_subscription::ProxySubscriptionPayload, + ) -> Result<(), RspStatus> { + let mut active = self.active_tunnel.write().await; + let Some(active) = active.as_mut() else { + return Ok(()); + }; + active + .apply_proxy_subscription_payload(id, payload) + .await + .map_err(proc_err)?; + Ok(()) + } + async fn stop_active_tunnel(&self) -> Result { let current = { self.active_tunnel.write().await.take() }; let Some(current) = current else { @@ -218,9 +240,7 @@ impl Tunnel for DaemonRPCServer { return Err(RspStatus::failed_precondition("no active tunnel")); }; active.packet_stream().ok_or_else(|| { - RspStatus::failed_precondition( - "active tunnel does not support packet streaming", - ) + RspStatus::failed_precondition("active tunnel does not support packet streaming") })? }; @@ -373,6 +393,98 @@ impl Networks for DaemonRPCServer { } } +#[tonic::async_trait] +impl ProxySubscriptions for DaemonRPCServer { + async fn preview_import( + &self, + request: Request, + ) -> Result, RspStatus> { + let request = request.into_inner(); + let body = proxy_subscription::fetch_subscription(&request.url) + .await + .map_err(proc_err)?; + let preview = proxy_subscription::parse_subscription_body(&body, Some(&request.url)); + Ok(Response::new(proxy_subscription_preview_rsp(preview))) + } + + async fn apply_import( + &self, + request: Request, + ) -> Result, RspStatus> { + let request = request.into_inner(); + let body = proxy_subscription::fetch_subscription(&request.url) + .await + .map_err(proc_err)?; + let preview = proxy_subscription::parse_subscription_body(&body, Some(&request.url)); + if preview.nodes.is_empty() { + return Err(RspStatus::invalid_argument( + "subscription contains no compatible Trojan or Shadowsocks nodes", + )); + } + let conn = self.get_connection()?; + let previous_payload = proxy_subscription_payload(&conn, request.id).map_err(proc_err)?; + let selected_ordinal = if let Some(previous_payload) = previous_payload.as_ref() { + let previous_name = selected_proxy_node_name(previous_payload); + supported_selected_name(&preview.nodes, previous_name.as_deref()) + .or_else(|| first_supported_ordinal(&preview.nodes)) + } else { + supported_selected_ordinal(&preview.nodes, request.selected_ordinal) + .or_else(|| first_supported_ordinal(&preview.nodes)) + }; + let payload = proxy_subscription::build_payload( + preview, + &request.url, + (!request.name.trim().is_empty()).then_some(request.name), + selected_ordinal, + ); + crate::proxy_runtime::proxy_runtime_config(&payload) + .map_err(|err| RspStatus::invalid_argument(err.to_string()))?; + let imported_count = payload.nodes.len() as i32; + let selected_ordinal = payload + .selected_ordinal + .or_else(|| payload.nodes.first().map(|node| node.ordinal)) + .unwrap_or_default() as i32; + let network = Network { + id: request.id, + r#type: NetworkType::ProxySubscription.into(), + payload: serde_json::to_vec_pretty(&payload).map_err(proc_err)?, + }; + upsert_network(&conn, &network).map_err(proc_err)?; + self.update_active_proxy_subscription_runtime(request.id, &payload) + .await?; + self.notify_network_update().await?; + #[cfg(not(target_vendor = "apple"))] + self.reconcile_runtime().await?; + Ok(Response::new(ProxySubscriptionApplyResponse { + id: request.id, + imported_count, + selected_ordinal, + })) + } + + async fn select_node( + &self, + request: Request, + ) -> Result, RspStatus> { + let request = request.into_inner(); + let conn = self.get_connection()?; + let selected_ordinal = + select_proxy_subscription_node(&conn, request.id, request.selected_ordinal) + .map_err(proc_err)?; + if let Some(payload) = proxy_subscription_payload(&conn, request.id).map_err(proc_err)? { + self.update_active_proxy_subscription_runtime(request.id, &payload) + .await?; + } + self.notify_network_update().await?; + #[cfg(not(target_vendor = "apple"))] + self.reconcile_runtime().await?; + Ok(Response::new(ProxySubscriptionSelectResponse { + id: request.id, + selected_ordinal, + })) + } +} + #[tonic::async_trait] impl TailnetControl for DaemonRPCServer { async fn discover( @@ -500,7 +612,9 @@ impl TailnetControl for DaemonRPCServer { } fn proc_err(err: impl ToString) -> RspStatus { - RspStatus::internal(err.to_string()) + let message = err.to_string(); + error!(error = %message, "daemon RPC failed"); + RspStatus::internal(message) } fn configuration_rsp(config: ServerConfig) -> TunnelConfigurationResponse { @@ -508,6 +622,7 @@ fn configuration_rsp(config: ServerConfig) -> TunnelConfigurationResponse { addresses: config.address, mtu: config.mtu.unwrap_or(1000), routes: config.routes, + excluded_routes: config.excluded_routes, dns_servers: config.dns_servers, search_domains: config.search_domains, include_default_route: config.include_default_route, @@ -521,6 +636,107 @@ fn status_rsp(state: RunState) -> TunnelStatusResponse { } } +fn proxy_subscription_preview_rsp( + preview: proxy_subscription::ProxySubscriptionPreview, +) -> ProxySubscriptionPreviewResponse { + ProxySubscriptionPreviewResponse { + suggested_name: preview.suggested_name, + detected_format: preview.detected_format.as_str().to_owned(), + compatible_count: preview.nodes.len() as i32, + rejected_count: preview.rejected.len() as i32, + node: preview + .nodes + .into_iter() + .map(|node| { + let runtime_error = crate::proxy_runtime::runtime_support_error(&node); + let mut warnings = node.warnings; + if let Some(error) = runtime_error.as_deref() { + warnings.push(error.to_owned()); + } + ProxySubscriptionNodePreview { + ordinal: node.ordinal as i32, + name: node.name, + protocol: node.protocol.as_str().to_owned(), + server: node.server, + port: node.port.into(), + warnings, + runtime_supported: runtime_error.is_none(), + } + }) + .collect(), + rejected: preview + .rejected + .into_iter() + .map(|entry| ProxySubscriptionRejectedEntry { + ordinal: entry.ordinal as i32, + reason: entry.reason, + scheme: entry.scheme.unwrap_or_default(), + }) + .collect(), + warnings: preview.warnings, + } +} + +fn supported_selected_ordinal( + nodes: &[proxy_subscription::ProxyNode], + selected_ordinal: i32, +) -> Option { + if selected_ordinal < 0 { + return None; + } + let selected_ordinal = selected_ordinal as usize; + nodes + .iter() + .find(|node| { + node.ordinal == selected_ordinal + && crate::proxy_runtime::runtime_support_error(node).is_none() + }) + .map(|node| node.ordinal) +} + +fn supported_selected_name( + nodes: &[proxy_subscription::ProxyNode], + selected_name: Option<&str>, +) -> Option { + let selected_name = selected_name?; + nodes + .iter() + .find(|node| { + node.name == selected_name + && crate::proxy_runtime::runtime_support_error(node).is_none() + }) + .map(|node| node.ordinal) +} + +fn first_supported_ordinal(nodes: &[proxy_subscription::ProxyNode]) -> Option { + nodes + .iter() + .find(|node| crate::proxy_runtime::runtime_support_error(node).is_none()) + .map(|node| node.ordinal) +} + +fn selected_proxy_node_name( + payload: &proxy_subscription::ProxySubscriptionPayload, +) -> Option { + payload + .selected_name + .clone() + .or_else(|| { + payload.selected_ordinal.and_then(|ordinal| { + payload + .nodes + .iter() + .find(|node| node.ordinal == ordinal) + .map(|node| node.name.clone()) + }) + }) + .or_else(|| { + crate::proxy_runtime::selected_node(payload) + .ok() + .map(|node| node.name.clone()) + }) +} + fn tailnet_login_rsp( session_id: String, status: TailscaleLoginStatus, @@ -538,3 +754,54 @@ fn tailnet_login_rsp( health: status.health, } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn supported_selected_name_survives_subscription_reorder() { + let preview = proxy_subscription::parse_subscription_body( + r#" +proxies: + - name: fallback + type: ss + server: fallback.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"), + ); + + assert_eq!( + supported_selected_name(&preview.nodes, Some("selected")), + Some(1) + ); + } + + #[test] + fn selected_proxy_node_name_reads_legacy_ordinal_payloads() { + let preview = proxy_subscription::parse_subscription_body( + "ss://YWVzLTEyOC1nY206cGFzcw@example.net:8388#SS%20Node\n", + Some("https://example.com/sub"), + ); + let payload = proxy_subscription::build_payload( + preview, + "https://example.com/sub", + Some("Proxy Subscription".to_owned()), + Some(0), + ); + + assert_eq!( + selected_proxy_node_name(&payload).as_deref(), + Some("SS Node") + ); + } +} diff --git a/burrow/src/daemon/mod.rs b/burrow/src/daemon/mod.rs index 724e3bb..ccb9b86 100644 --- a/burrow/src/daemon/mod.rs +++ b/burrow/src/daemon/mod.rs @@ -1,4 +1,4 @@ -use std::{path::Path, sync::Arc}; +use std::{io, os::unix::net::UnixStream as StdUnixStream, path::Path, sync::Arc}; pub mod apple; mod instance; @@ -17,8 +17,8 @@ use tracing::info; use crate::{ daemon::rpc::grpc_defs::{ - networks_server::NetworksServer, tailnet_control_server::TailnetControlServer, - tunnel_server::TunnelServer, + networks_server::NetworksServer, proxy_subscriptions_server::ProxySubscriptionsServer, + tailnet_control_server::TailnetControlServer, tunnel_server::TunnelServer, }, database::get_connection, }; @@ -33,16 +33,23 @@ pub async fn daemon_main( let spp = socket_path.clone(); let tmp = get_socket_path(); let sock_path = spp.unwrap_or(Path::new(tmp.as_str())); - if sock_path.exists() { - std::fs::remove_file(sock_path)?; - } - let uds = UnixListener::bind(sock_path)?; + let uds = match bind_daemon_socket(sock_path)? { + Some(uds) => uds, + None => { + if let Some(n) = notify_ready { + n.notify_one(); + } + info!(path = %sock_path.display(), "daemon socket already served by another process"); + return Ok(()); + } + }; let serve_job = tokio::spawn(async move { let uds_stream = UnixListenerStream::new(uds); let tailnet_server = burrow_server.clone(); let _srv = Server::builder() .add_service(TunnelServer::new(burrow_server.clone())) - .add_service(NetworksServer::new(burrow_server)) + .add_service(NetworksServer::new(burrow_server.clone())) + .add_service(ProxySubscriptionsServer::new(burrow_server.clone())) .add_service(TailnetControlServer::new(tailnet_server)) .serve_with_incoming(uds_stream) .await?; @@ -60,6 +67,24 @@ pub async fn daemon_main( .map_err(|e| e.into()) } +fn bind_daemon_socket(sock_path: &Path) -> Result> { + if let Some(parent) = sock_path.parent() { + std::fs::create_dir_all(parent)?; + } + + match UnixListener::bind(sock_path) { + Ok(listener) => Ok(Some(listener)), + Err(error) if error.kind() == io::ErrorKind::AddrInUse => { + if StdUnixStream::connect(sock_path).is_ok() { + return Ok(None); + } + std::fs::remove_file(sock_path)?; + Ok(Some(UnixListener::bind(sock_path)?)) + } + Err(error) => Err(error.into()), + } +} + #[cfg(test)] mod tests { use std::{ diff --git a/burrow/src/daemon/rpc/client.rs b/burrow/src/daemon/rpc/client.rs index aa84c64..1f3cfa2 100644 --- a/burrow/src/daemon/rpc/client.rs +++ b/burrow/src/daemon/rpc/client.rs @@ -6,13 +6,14 @@ use tonic::transport::{Endpoint, Uri}; use tower::service_fn; use super::grpc_defs::{ - networks_client::NetworksClient, tailnet_control_client::TailnetControlClient, - tunnel_client::TunnelClient, + networks_client::NetworksClient, proxy_subscriptions_client::ProxySubscriptionsClient, + tailnet_control_client::TailnetControlClient, tunnel_client::TunnelClient, }; use crate::daemon::get_socket_path; pub struct BurrowClient { pub networks_client: NetworksClient, + pub proxy_subscription_client: ProxySubscriptionsClient, pub tailnet_client: TailnetControlClient, pub tunnel_client: TunnelClient, } @@ -35,10 +36,12 @@ impl BurrowClient { })) .await?; let nw_client = NetworksClient::new(channel.clone()); + let proxy_subscription_client = ProxySubscriptionsClient::new(channel.clone()); let tailnet_client = TailnetControlClient::new(channel.clone()); let tun_client = TunnelClient::new(channel.clone()); Ok(BurrowClient { networks_client: nw_client, + proxy_subscription_client, tailnet_client, tunnel_client: tun_client, }) diff --git a/burrow/src/daemon/rpc/response.rs b/burrow/src/daemon/rpc/response.rs index 6d03581..d72fcd3 100644 --- a/burrow/src/daemon/rpc/response.rs +++ b/burrow/src/daemon/rpc/response.rs @@ -71,6 +71,8 @@ pub struct ServerConfig { #[serde(default)] pub routes: Vec, #[serde(default)] + pub excluded_routes: Vec, + #[serde(default)] pub dns_servers: Vec, #[serde(default)] pub search_domains: Vec, @@ -91,6 +93,7 @@ impl TryFrom<&Config> for ServerConfig { .iter() .flat_map(|peer| peer.allowed_ips.iter().cloned()) .collect(), + excluded_routes: Vec::new(), dns_servers: config.interface.dns.clone(), search_domains: Vec::new(), include_default_route: false, @@ -105,6 +108,7 @@ impl Default for ServerConfig { Self { address: vec!["10.13.13.2".to_string()], // Dummy remote address routes: Vec::new(), + excluded_routes: Vec::new(), dns_servers: Vec::new(), search_domains: Vec::new(), include_default_route: false, diff --git a/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__response__response_serialization-4.snap b/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__response__response_serialization-4.snap index 68b4195..6bc3ce1 100644 --- a/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__response__response_serialization-4.snap +++ b/burrow/src/daemon/rpc/snapshots/burrow__daemon__rpc__response__response_serialization-4.snap @@ -2,4 +2,4 @@ source: burrow/src/daemon/rpc/response.rs expression: "serde_json::to_string(&DaemonResponse::new(Ok::(DaemonResponseData::ServerConfig(ServerConfig::default()))))?" --- -{"result":{"Ok":{"type":"ServerConfig","address":["10.13.13.2"],"routes":[],"dns_servers":[],"search_domains":[],"include_default_route":false,"name":null,"mtu":null}},"id":0} +{"result":{"Ok":{"type":"ServerConfig","address":["10.13.13.2"],"routes":[],"excluded_routes":[],"dns_servers":[],"search_domains":[],"include_default_route":false,"name":null,"mtu":null}},"id":0} diff --git a/burrow/src/daemon/runtime.rs b/burrow/src/daemon/runtime.rs index 31821a2..f240b95 100644 --- a/burrow/src/daemon/runtime.rs +++ b/burrow/src/daemon/runtime.rs @@ -20,6 +20,8 @@ use crate::{ TailscaleLoginStartRequest, TailscaleLoginStatus, }, control::{discovery, TailnetConfig}, + proxy_runtime, + proxy_subscription::ProxySubscriptionPayload, wireguard::{Config, Interface as WireGuardInterface}, }; @@ -46,6 +48,13 @@ pub enum ResolvedTunnel { identity: RuntimeIdentity, config: Config, }, + Trojan { + identity: RuntimeIdentity, + }, + ProxySubscription { + identity: RuntimeIdentity, + payload: ProxySubscriptionPayload, + }, } impl ResolvedTunnel { @@ -73,6 +82,13 @@ impl ResolvedTunnel { let config = Config::from_content_fmt(&payload, "ini")?; Ok(Self::WireGuard { identity, config }) } + NetworkType::Trojan => Ok(Self::Trojan { identity }), + NetworkType::ProxySubscription => { + let payload: ProxySubscriptionPayload = serde_json::from_slice(&network.payload) + .context("proxy subscription payload must be valid JSON")?; + crate::proxy_subscription::validate_payload(&payload)?; + Ok(Self::ProxySubscription { identity, payload }) + } } } @@ -80,7 +96,9 @@ impl ResolvedTunnel { match self { Self::Passthrough { identity } | Self::Tailnet { identity, .. } - | Self::WireGuard { identity, .. } => identity, + | Self::WireGuard { identity, .. } + | Self::Trojan { identity, .. } + | Self::ProxySubscription { identity, .. } => identity, } } @@ -89,6 +107,7 @@ impl ResolvedTunnel { Self::Passthrough { .. } => Ok(ServerConfig { address: Vec::new(), routes: Vec::new(), + excluded_routes: Vec::new(), dns_servers: Vec::new(), search_domains: Vec::new(), include_default_route: false, @@ -98,6 +117,7 @@ impl ResolvedTunnel { Self::Tailnet { .. } => Ok(ServerConfig { address: Vec::new(), routes: tailnet_routes(), + excluded_routes: Vec::new(), dns_servers: tailnet_dns_servers(), search_domains: Vec::new(), include_default_route: false, @@ -105,6 +125,10 @@ impl ResolvedTunnel { mtu: Some(1280), }), Self::WireGuard { config, .. } => ServerConfig::try_from(config), + Self::Trojan { .. } => Err(anyhow::anyhow!( + "legacy Trojan SOCKS proxy networks are no longer supported; import a proxy subscription instead" + )), + Self::ProxySubscription { payload, .. } => proxy_subscription_server_config(payload), } } @@ -119,6 +143,7 @@ impl ResolvedTunnel { server_config: ServerConfig { address: Vec::new(), routes: Vec::new(), + excluded_routes: Vec::new(), dns_servers: Vec::new(), search_domains: Vec::new(), include_default_route: false, @@ -137,10 +162,9 @@ impl ResolvedTunnel { }; let status = wait_for_tailnet_ready(helper.as_ref()).await?; let server_config = tailnet_server_config(&status); - let packet_socket = helper - .packet_socket() - .map(PathBuf::from) - .ok_or_else(|| anyhow::anyhow!("tailnet helper did not report a packet socket"))?; + let packet_socket = helper.packet_socket().map(PathBuf::from).ok_or_else(|| { + anyhow::anyhow!("tailnet helper did not report a packet socket") + })?; let packet_bridge = connect_tailnet_packet_bridge(packet_socket).await?; #[cfg(target_vendor = "apple")] let tun_task = None; @@ -182,10 +206,64 @@ impl ResolvedTunnel { } } } + Self::Trojan { .. } => Err(anyhow::anyhow!( + "legacy Trojan SOCKS proxy networks are no longer supported; import a proxy subscription instead" + )), + Self::ProxySubscription { identity, payload } => { + let runtime_config = proxy_runtime::proxy_runtime_config(&payload)?; + let server_config = proxy_subscription_server_config_for_selected( + &payload, + &runtime_config.selected_name, + )?; + let packet_bridge = + proxy_runtime::start_proxy_packet_bridge(runtime_config.outbound); + #[cfg(target_vendor = "apple")] + let tun_task = None; + #[cfg(not(target_vendor = "apple"))] + let tun_task = { + let tun = TunOptions::new().open()?; + tun_interface.write().await.replace(tun); + Some(tokio::spawn(proxy_runtime::run_proxy_tun_bridge( + tun_interface.clone(), + packet_bridge.outbound_sender(), + packet_bridge.subscribe(), + ))) + }; + Ok(ActiveTunnel::ProxySubscription { + identity, + server_config, + packet_bridge, + tun_task, + }) + } } } } +fn proxy_subscription_server_config(payload: &ProxySubscriptionPayload) -> Result { + let runtime_config = proxy_runtime::proxy_runtime_config(payload)?; + proxy_subscription_server_config_for_selected(payload, &runtime_config.selected_name) +} + +fn proxy_subscription_server_config_for_selected( + payload: &ProxySubscriptionPayload, + selected_name: &str, +) -> Result { + let dns_servers = proxy_runtime::proxy_dns_servers(); + let mut excluded_routes = proxy_runtime::proxy_excluded_routes(payload)?; + excluded_routes.extend(proxy_runtime::proxy_dns_excluded_routes(&dns_servers)); + Ok(ServerConfig { + address: proxy_runtime::proxy_server_addresses(), + routes: Vec::new(), + excluded_routes, + dns_servers, + search_domains: Vec::new(), + include_default_route: true, + name: Some(format!("{} ({})", payload.name, selected_name)), + mtu: Some(1500), + }) +} + pub enum ActiveTunnel { Passthrough { identity: RuntimeIdentity, @@ -205,6 +283,16 @@ pub enum ActiveTunnel { interface: Arc>, task: JoinHandle>, }, + Trojan { + identity: RuntimeIdentity, + server_config: ServerConfig, + }, + ProxySubscription { + identity: RuntimeIdentity, + server_config: ServerConfig, + packet_bridge: proxy_runtime::ProxyPacketBridge, + tun_task: Option>>, + }, } impl ActiveTunnel { @@ -212,7 +300,9 @@ impl ActiveTunnel { match self { Self::Passthrough { identity, .. } | Self::Tailnet { identity, .. } - | Self::WireGuard { identity, .. } => identity, + | Self::WireGuard { identity, .. } + | Self::Trojan { identity, .. } + | Self::ProxySubscription { identity, .. } => identity, } } @@ -220,22 +310,55 @@ impl ActiveTunnel { match self { Self::Passthrough { server_config, .. } | Self::Tailnet { server_config, .. } - | Self::WireGuard { server_config, .. } => server_config, + | Self::WireGuard { server_config, .. } + | Self::Trojan { server_config, .. } + | Self::ProxySubscription { server_config, .. } => server_config, } } - pub fn packet_stream( - &self, - ) -> Option<(mpsc::Sender>, broadcast::Receiver>)> { + pub fn packet_stream(&self) -> Option<(mpsc::Sender>, broadcast::Receiver>)> { match self { - Self::Tailnet { packet_bridge, .. } => Some(( - packet_bridge.outbound_sender(), - packet_bridge.subscribe(), - )), + Self::Tailnet { packet_bridge, .. } => { + Some((packet_bridge.outbound_sender(), packet_bridge.subscribe())) + } + Self::ProxySubscription { packet_bridge, .. } => { + Some((packet_bridge.outbound_sender(), packet_bridge.subscribe())) + } _ => None, } } + pub async fn apply_proxy_subscription_payload( + &mut self, + network_id: i32, + payload: &ProxySubscriptionPayload, + ) -> Result { + let Self::ProxySubscription { + identity, + server_config, + packet_bridge, + .. + } = self + else { + return Ok(false); + }; + + let RuntimeIdentity::Network { id, network_type, .. } = identity else { + return Ok(false); + }; + if *id != network_id || *network_type != NetworkType::ProxySubscription { + return Ok(false); + } + + let runtime_config = proxy_runtime::proxy_runtime_config(payload)?; + *server_config = + proxy_subscription_server_config_for_selected(payload, &runtime_config.selected_name)?; + packet_bridge + .set_proxy_outbound(runtime_config.outbound) + .await; + Ok(true) + } + pub async fn shutdown(self, tun_interface: &Arc>>) -> Result<()> { match self { Self::Passthrough { .. } => Ok(()), @@ -268,17 +391,29 @@ impl ActiveTunnel { } Ok(()) } - Self::WireGuard { - interface, - task, - .. - } => { + Self::WireGuard { interface, task, .. } => { interface.read().await.remove_tun().await; let task_result = task.await; tun_interface.write().await.take(); task_result??; Ok(()) } + Self::Trojan { .. } => Ok(()), + Self::ProxySubscription { packet_bridge, tun_task, .. } => { + if let Some(tun_task) = tun_task { + tun_task.abort(); + match tun_task.await { + Ok(Ok(())) => {} + Ok(Err(err)) => return Err(err), + Err(err) if err.is_cancelled() => {} + Err(err) => return Err(err.into()), + } + } + packet_bridge.abort(); + packet_bridge.join().await?; + tun_interface.write().await.take(); + Ok(()) + } } } } @@ -396,6 +531,7 @@ fn tailnet_server_config(status: &TailscaleLoginStatus) -> ServerConfig { .map(|ip| tailnet_cidr(ip)) .collect(), routes: tailnet_routes(), + excluded_routes: Vec::new(), dns_servers: tailnet_dns_servers(), search_domains, include_default_route: false, @@ -605,7 +741,10 @@ mod tests { fn tailnet_server_config_uses_host_prefixes() { let status = TailscaleLoginStatus { running: true, - tailscale_ips: vec!["100.101.102.103".to_owned(), "fd7a:115c:a1e0::123".to_owned()], + tailscale_ips: vec![ + "100.101.102.103".to_owned(), + "fd7a:115c:a1e0::123".to_owned(), + ], ..Default::default() }; let config = tailnet_server_config(&status); diff --git a/burrow/src/database.rs b/burrow/src/database.rs index fe9a3c7..b885947 100644 --- a/burrow/src/database.rs +++ b/burrow/src/database.rs @@ -1,6 +1,6 @@ use std::path::Path; -use anyhow::Result; +use anyhow::{anyhow, Result}; use rusqlite::{params, Connection}; use crate::{ @@ -8,6 +8,7 @@ use crate::{ daemon::rpc::grpc_defs::{ Network as RPCNetwork, NetworkDeleteRequest, NetworkReorderRequest, NetworkType, }, + proxy_subscription::ProxySubscriptionPayload, wireguard::config::{Config, Interface, Peer}, }; @@ -138,6 +139,18 @@ pub fn add_network(conn: &Connection, network: &RPCNetwork) -> Result<()> { Ok(()) } +pub fn upsert_network(conn: &Connection, network: &RPCNetwork) -> Result<()> { + validate_network_payload(network)?; + let updated = conn.execute( + "UPDATE network SET type = ?, payload = ? WHERE id = ?", + params![network.r#type().as_str_name(), &network.payload, network.id], + )?; + if updated == 0 { + add_network(conn, network)?; + } + Ok(()) +} + pub fn list_networks(conn: &Connection) -> Result> { let mut stmt = conn.prepare("SELECT id, type, payload FROM network ORDER BY idx, id")?; let networks: Vec = stmt @@ -183,6 +196,70 @@ pub fn delete_network(conn: &Connection, req: NetworkDeleteRequest) -> Result<() renumber_networks(conn, &ordered_ids) } +pub fn proxy_subscription_payload( + conn: &Connection, + id: i32, +) -> Result> { + let row = conn.query_row( + "SELECT type, payload FROM network WHERE id = ?", + params![id], + |row| Ok((row.get::<_, String>(0)?, row.get::<_, Vec>(1)?)), + ); + let (network_type, payload) = match row { + Ok(row) => row, + Err(rusqlite::Error::QueryReturnedNoRows) => return Ok(None), + Err(error) => return Err(error.into()), + }; + let network_type = NetworkType::from_str_name(network_type.as_str()) + .ok_or_else(|| anyhow!("stored network type {network_type} is not recognized"))?; + if network_type != NetworkType::ProxySubscription { + return Ok(None); + } + Ok(Some(serde_json::from_slice(&payload)?)) +} + +pub fn select_proxy_subscription_node( + conn: &Connection, + id: i32, + selected_ordinal: i32, +) -> Result { + if selected_ordinal < 0 { + return Err(anyhow!("selected proxy node ordinal must be non-negative")); + } + + let (network_type, payload): (String, Vec) = conn.query_row( + "SELECT type, payload FROM network WHERE id = ?", + params![id], + |row| Ok((row.get(0)?, row.get(1)?)), + )?; + let network_type = NetworkType::from_str_name(network_type.as_str()) + .ok_or_else(|| anyhow!("stored network type {network_type} is not recognized"))?; + if network_type != NetworkType::ProxySubscription { + return Err(anyhow!("network {id} is not a proxy subscription")); + } + + let mut payload: ProxySubscriptionPayload = serde_json::from_slice(&payload)?; + let selected_ordinal = selected_ordinal as usize; + let node = payload + .nodes + .iter() + .find(|node| node.ordinal == selected_ordinal) + .ok_or_else(|| anyhow!("selected proxy node ordinal {selected_ordinal} was not found"))?; + if let Some(error) = crate::proxy_runtime::runtime_support_error(node) { + return Err(anyhow!(error)); + } + + let selected_name = node.name.clone(); + payload.selected_ordinal = Some(selected_ordinal); + payload.selected_name = Some(selected_name); + crate::proxy_subscription::validate_payload(&payload)?; + conn.execute( + "UPDATE network SET payload = ? WHERE id = ?", + params![serde_json::to_vec_pretty(&payload)?, id], + )?; + Ok(selected_ordinal as i32) +} + fn parse_lst(s: &str) -> Vec { if s.is_empty() { return vec![]; @@ -206,6 +283,15 @@ fn validate_network_payload(network: &RPCNetwork) -> Result<()> { NetworkType::Tailnet => { TailnetConfig::from_slice(&network.payload)?; } + NetworkType::Trojan => { + return Err(anyhow::anyhow!( + "legacy Trojan SOCKS proxy networks are no longer supported; import a proxy subscription instead" + )); + } + NetworkType::ProxySubscription => { + let payload: ProxySubscriptionPayload = serde_json::from_slice(&network.payload)?; + crate::proxy_subscription::validate_payload(&payload)?; + } } Ok(()) } @@ -278,6 +364,79 @@ Endpoint = wg.burrow.rs:51820 .to_vec() } + fn sample_proxy_subscription_payload() -> Vec { + br#"{ + "name":"example proxy subscription", + "source":{"url":"https://example.com/sub"}, + "detected_format":"uri-list", + "selected_ordinal":0, + "nodes":[{ + "ordinal":0, + "name":"example trojan", + "protocol":"trojan", + "server":"example.com", + "port":443, + "warnings":[], + "config":{ + "type":"trojan", + "password":"secret", + "sni":"front.example", + "alpn":[], + "skip_cert_verify":false, + "network":"tcp", + "client_fingerprint":"chrome", + "fingerprint":null + } + }], + "warnings":[] +}"# + .to_vec() + } + + fn sample_proxy_subscription_payload_with_two_nodes() -> Vec { + br#"{ + "name":"example proxy subscription", + "source":{"url":"https://example.com/sub"}, + "detected_format":"uri-list", + "selected_ordinal":0, + "nodes":[{ + "ordinal":0, + "name":"example trojan", + "protocol":"trojan", + "server":"example.com", + "port":443, + "warnings":[], + "config":{ + "type":"trojan", + "password":"secret", + "sni":"front.example", + "alpn":[], + "skip_cert_verify":false, + "network":"tcp", + "client_fingerprint":"chrome", + "fingerprint":null + } + },{ + "ordinal":1, + "name":"example shadowsocks", + "protocol":"shadowsocks", + "server":"ss.example.com", + "port":8388, + "warnings":[], + "config":{ + "type":"shadowsocks", + "cipher":"aes-256-gcm", + "password":"secret", + "udp":true, + "udp_over_tcp":false, + "plugin":null + } + }], + "warnings":[] +}"# + .to_vec() + } + #[test] fn test_db() { let conn = Connection::open_in_memory().unwrap(); @@ -317,6 +476,16 @@ Endpoint = wg.burrow.rs:51820 &conn, &RPCNetwork { id: 3, + r#type: NetworkType::ProxySubscription.into(), + payload: sample_proxy_subscription_payload(), + }, + ) + .unwrap(); + + add_network( + &conn, + &RPCNetwork { + id: 4, r#type: NetworkType::WireGuard.into(), payload: sample_wireguard_payload_with_address("10.42.0.2/32", 1380), }, @@ -326,7 +495,7 @@ Endpoint = wg.burrow.rs:51820 assert!(add_network( &conn, &RPCNetwork { - id: 4, + id: 5, r#type: NetworkType::WireGuard.into(), payload: b"not-a-config".to_vec(), }, @@ -336,19 +505,29 @@ Endpoint = wg.burrow.rs:51820 assert!(add_network( &conn, &RPCNetwork { - id: 5, + id: 6, r#type: NetworkType::Tailnet.into(), payload: b"not-a-tailnet-config".to_vec(), }, ) .is_err()); + assert!(add_network( + &conn, + &RPCNetwork { + id: 7, + r#type: NetworkType::ProxySubscription.into(), + payload: b"not-a-trojan-config".to_vec(), + }, + ) + .is_err()); + let ids: Vec = list_networks(&conn) .unwrap() .into_iter() .map(|n| n.id) .collect(); - assert_eq!(ids, vec![1, 2, 3]); + assert_eq!(ids, vec![1, 2, 3, 4]); } #[test] @@ -389,6 +568,87 @@ Endpoint = wg.burrow.rs:51820 assert_eq!(ids, vec![3, 2]); } + #[test] + fn upsert_network_preserves_priority() { + let conn = Connection::open_in_memory().unwrap(); + initialize_tables(&conn).unwrap(); + + add_network( + &conn, + &RPCNetwork { + id: 1, + r#type: NetworkType::WireGuard.into(), + payload: sample_wireguard_payload_with_address("10.42.0.2/32", 1380), + }, + ) + .unwrap(); + add_network( + &conn, + &RPCNetwork { + id: 2, + r#type: NetworkType::WireGuard.into(), + payload: sample_wireguard_payload_with_address("10.42.0.3/32", 1381), + }, + ) + .unwrap(); + + upsert_network( + &conn, + &RPCNetwork { + id: 1, + r#type: NetworkType::WireGuard.into(), + payload: sample_wireguard_payload_with_address("10.99.0.2/32", 1400), + }, + ) + .unwrap(); + + let networks = list_networks(&conn).unwrap(); + let ids: Vec = networks.iter().map(|n| n.id).collect(); + assert_eq!(ids, vec![1, 2]); + + let updated = String::from_utf8(networks[0].payload.clone()).unwrap(); + assert!(updated.contains("10.99.0.2/32")); + } + + #[test] + fn select_proxy_subscription_node_updates_payload_without_reorder() { + let conn = Connection::open_in_memory().unwrap(); + initialize_tables(&conn).unwrap(); + + add_network( + &conn, + &RPCNetwork { + id: 1, + r#type: NetworkType::ProxySubscription.into(), + payload: sample_proxy_subscription_payload_with_two_nodes(), + }, + ) + .unwrap(); + add_network( + &conn, + &RPCNetwork { + id: 2, + r#type: NetworkType::WireGuard.into(), + payload: sample_wireguard_payload_with_address("10.42.0.3/32", 1381), + }, + ) + .unwrap(); + + assert_eq!(select_proxy_subscription_node(&conn, 1, 1).unwrap(), 1); + + let networks = list_networks(&conn).unwrap(); + let ids: Vec = networks.iter().map(|n| n.id).collect(); + assert_eq!(ids, vec![1, 2]); + + let payload: ProxySubscriptionPayload = + serde_json::from_slice(&networks[0].payload).unwrap(); + assert_eq!(payload.selected_ordinal, Some(1)); + assert_eq!( + payload.selected_name.as_deref(), + Some("example shadowsocks") + ); + } + #[test] fn get_connection_does_not_seed_a_default_interface() { let dir = tempdir().unwrap(); diff --git a/burrow/src/lib.rs b/burrow/src/lib.rs index 7867d18..f0b24d5 100644 --- a/burrow/src/lib.rs +++ b/burrow/src/lib.rs @@ -1,6 +1,9 @@ #[cfg(any(target_os = "linux", target_vendor = "apple"))] pub mod control; +pub mod proxy_runtime; +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +pub mod proxy_subscription; #[cfg(any(target_os = "linux", target_vendor = "apple"))] pub mod wireguard; diff --git a/burrow/src/main.rs b/burrow/src/main.rs index cfa2085..4183a75 100644 --- a/burrow/src/main.rs +++ b/burrow/src/main.rs @@ -5,6 +5,9 @@ use clap::{Args, Parser, Subcommand}; mod control; #[cfg(any(target_os = "linux", target_vendor = "apple"))] mod daemon; +mod proxy_runtime; +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +mod proxy_subscription; pub(crate) mod tracing; #[cfg(any(target_os = "linux", target_vendor = "apple"))] mod wireguard; @@ -80,6 +83,14 @@ enum Commands { TailnetPing(TailnetPingArgs), /// Send a UDP echo probe through the active Tailnet tunnel over daemon packet streaming TailnetUdpEcho(TailnetUdpEchoArgs), + /// Send an HTTP probe through the active proxy tunnel over daemon packet streaming + ProxyTcpProbe(ProxyTcpProbeArgs), + /// Send an HTTPS probe through the active proxy tunnel over daemon packet streaming + ProxyHttpsProbe(ProxyHttpsProbeArgs), + /// Select a proxy subscription node through the daemon + ProxySubscriptionSelect(ProxySubscriptionSelectArgs), + /// Preview proxy nodes from a subscription URL + ProxySubscriptionPreview(ProxySubscriptionPreviewArgs), #[cfg(target_os = "linux")] /// Run a command in an unshared Linux namespace using a Burrow backend Exec(ExecArgs), @@ -148,6 +159,49 @@ struct TailnetUdpEchoArgs { timeout_ms: u64, } +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +#[derive(Args)] +struct ProxyTcpProbeArgs { + #[arg(long, default_value = "1.1.1.1:80")] + remote: String, + #[arg(long, default_value = "one.one.one.one")] + host: String, + #[arg(long)] + dns_name: Option, + #[arg(long, default_value = "100.64.0.2:53")] + dns_server: String, + #[arg(long, default_value_t = 8000)] + timeout_ms: u64, +} + +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +#[derive(Args)] +struct ProxyHttpsProbeArgs { + #[arg(long, default_value = "1.1.1.1:443")] + remote: String, + #[arg(long)] + host: Option, + #[arg(long)] + dns_name: Option, + #[arg(long, default_value = "100.64.0.2:53")] + dns_server: String, + #[arg(long, default_value_t = 15000)] + timeout_ms: u64, +} + +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +#[derive(Args)] +struct ProxySubscriptionSelectArgs { + id: i32, + selected_ordinal: i32, +} + +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +#[derive(Args)] +struct ProxySubscriptionPreviewArgs { + url: String, +} + #[cfg(target_os = "linux")] #[derive(Args)] struct TorExecArgs { @@ -506,6 +560,688 @@ async fn try_tailnet_udp_echo(remote: &str, message: &str, timeout_ms: u64) -> R Ok(()) } +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +async fn try_proxy_subscription_preview(url: &str) -> Result<()> { + let body = proxy_subscription::fetch_subscription(url).await?; + let preview = proxy_subscription::parse_subscription_body(&body, Some(url)); + + println!("Proxy subscription nodes: {}", preview.nodes.len()); + println!("Rejected entries: {}", preview.rejected.len()); + println!("Detected format: {}", preview.detected_format.as_str()); + for node in preview.nodes.iter() { + println!( + "{}: {} {}:{} ({})", + node.ordinal, + node.name, + node.server, + node.port, + node.protocol.as_str() + ); + } + Ok(()) +} + +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +async fn proxy_dns_probe( + dns_name: &str, + dns_server: &str, + timeout_ms: u64, +) -> Result { + use std::net::{IpAddr, SocketAddr}; + + use anyhow::{bail, Context}; + use futures::{SinkExt, StreamExt}; + use netstack_smoltcp::StackBuilder; + use rand::Rng; + use tokio::{ + sync::mpsc, + time::{timeout, Duration}, + }; + use tokio_stream::wrappers::ReceiverStream; + + use crate::daemon::rpc::grpc_defs::{Empty, TunnelPacket}; + + let dns_server_addr: SocketAddr = dns_server + .parse() + .with_context(|| format!("invalid DNS server socket address {dns_server}"))?; + + let mut query = proxy_runtime::build_dns_query(dns_name, 1)?; + let query_id = rand::thread_rng().gen::(); + query[0..2].copy_from_slice(&query_id.to_be_bytes()); + + let mut client = BurrowClient::from_uds().await?; + client.tunnel_client.tunnel_start(Empty {}).await?; + let mut config_stream = client + .tunnel_client + .tunnel_configuration(Empty {}) + .await? + .into_inner(); + let config = config_stream + .message() + .await? + .context("tunnel configuration stream ended before yielding a config")?; + let local_addr = select_tailnet_local_socket(&config.addresses, dns_server_addr.ip())?; + + let (stack, runner, udp_socket, _) = StackBuilder::default() + .enable_udp(true) + .enable_tcp(true) + .build() + .context("failed to build userspace DNS probe stack")?; + let runner = runner.context("userspace DNS probe stack runner unavailable")?; + let udp_socket = udp_socket.context("userspace DNS probe socket unavailable")?; + let (mut stack_sink, mut stack_stream) = stack.split(); + let (mut udp_reader, mut udp_writer) = udp_socket.split(); + + let (outbound_tx, outbound_rx) = mpsc::channel::(128); + let mut tunnel_packets = client + .tunnel_client + .tunnel_packets(ReceiverStream::new(outbound_rx)) + .await? + .into_inner(); + + let ingress_task = tokio::spawn(async move { + while let Some(packet) = tunnel_packets.message().await? { + stack_sink + .send(packet.payload) + .await + .context("failed to feed inbound DNS probe packet into userspace stack")?; + } + Result::<()>::Ok(()) + }); + let egress_task = tokio::spawn(async move { + while let Some(packet) = stack_stream.next().await { + let payload = packet.context("failed to read outbound DNS probe packet")?; + outbound_tx + .send(TunnelPacket { payload }) + .await + .context("failed to forward outbound DNS probe packet to daemon")?; + } + Result::<()>::Ok(()) + }); + let runner_task = tokio::spawn(async move { runner.await.map_err(anyhow::Error::from) }); + + udp_writer + .send((query, local_addr, dns_server_addr)) + .await + .context("failed to send DNS probe query")?; + + let response = timeout(Duration::from_millis(timeout_ms), udp_reader.next()) + .await + .with_context(|| format!("timed out waiting for DNS response from {dns_server_addr}"))? + .context("userspace DNS probe stack ended before returning a reply")?; + let (payload, reply_source, _reply_destination) = response; + + ingress_task.abort(); + egress_task.abort(); + runner_task.abort(); + + if reply_source != dns_server_addr { + bail!("received DNS response from unexpected source {reply_source}"); + } + for ip in proxy_runtime::parse_dns_response(&payload, query_id)? { + if let IpAddr::V4(ip) = ip { + return Ok(ip); + } + } + bail!("DNS response for {dns_name} did not include an A record") +} + +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +async fn try_proxy_tcp_probe( + remote: &str, + host: &str, + dns_name: Option<&str>, + dns_server: &str, + timeout_ms: u64, +) -> Result<()> { + use std::net::{IpAddr, SocketAddr}; + + use anyhow::{bail, Context}; + use rand::Rng; + use tokio::{ + sync::mpsc, + time::{timeout, Duration}, + }; + use tokio_stream::wrappers::ReceiverStream; + + use crate::daemon::rpc::grpc_defs::{Empty, TunnelPacket}; + + let mut remote_addr: SocketAddr = remote + .parse() + .with_context(|| format!("invalid remote socket address {remote}"))?; + let mut http_host = host.to_owned(); + if let Some(dns_name) = dns_name { + let resolved_ip = proxy_dns_probe(dns_name, dns_server, timeout_ms).await?; + remote_addr = SocketAddr::new(IpAddr::V4(resolved_ip), remote_addr.port()); + if host == "one.one.one.one" { + http_host = dns_name.to_owned(); + } + println!("Proxy TCP Probe DNS Name: {dns_name}"); + println!("Proxy TCP Probe DNS Resolved: {resolved_ip}"); + } + let remote_ip = match remote_addr.ip() { + IpAddr::V4(ip) => ip, + IpAddr::V6(_) => bail!("proxy tcp probe currently supports IPv4 targets only"), + }; + + let mut client = BurrowClient::from_uds().await?; + client.tunnel_client.tunnel_start(Empty {}).await?; + + let mut config_stream = client + .tunnel_client + .tunnel_configuration(Empty {}) + .await? + .into_inner(); + let config = config_stream + .message() + .await? + .context("tunnel configuration stream ended before yielding a config")?; + let local_addr = select_tailnet_local_socket(&config.addresses, remote_addr.ip())?; + let local_ip = match local_addr.ip() { + IpAddr::V4(ip) => ip, + IpAddr::V6(_) => bail!("proxy tcp probe selected an IPv6 local address"), + }; + let local_port = local_addr.port(); + + let (outbound_tx, outbound_rx) = mpsc::channel::(128); + let mut tunnel_packets = client + .tunnel_client + .tunnel_packets(ReceiverStream::new(outbound_rx)) + .await? + .into_inner(); + + let initial_seq = rand::thread_rng().gen::(); + let syn = build_tcp_ipv4_packet( + local_ip, + remote_ip, + local_port, + remote_addr.port(), + initial_seq, + 0, + TCP_SYN, + &[], + ); + outbound_tx + .send(TunnelPacket { payload: syn }) + .await + .context("failed to send proxy tcp SYN into daemon packet stream")?; + + let syn_ack = timeout(Duration::from_millis(timeout_ms), async { + loop { + let packet = tunnel_packets + .message() + .await + .context("failed to read packet from daemon packet stream")? + .context("daemon packet stream ended before SYN-ACK")?; + if let Some(segment) = parse_tcp_ipv4_segment( + &packet.payload, + local_ip, + remote_ip, + local_port, + remote_addr.port(), + )? { + if segment.flags & TCP_SYN != 0 && segment.flags & TCP_ACK != 0 { + break Ok::<_, anyhow::Error>(segment); + } + if segment.flags & TCP_RST != 0 { + bail!("proxy tcp probe received RST before SYN-ACK"); + } + } + } + }) + .await + .with_context(|| format!("timed out waiting for proxy tcp SYN-ACK from {remote_addr}"))??; + + let mut client_seq = initial_seq.wrapping_add(1); + let mut peer_ack = syn_ack.sequence.wrapping_add(1); + let ack = build_tcp_ipv4_packet( + local_ip, + remote_ip, + local_port, + remote_addr.port(), + client_seq, + peer_ack, + TCP_ACK, + &[], + ); + outbound_tx + .send(TunnelPacket { payload: ack }) + .await + .context("failed to send proxy tcp handshake ACK")?; + + let request = format!( + "GET / HTTP/1.1\r\nHost: {http_host}\r\nConnection: close\r\nUser-Agent: burrow-proxy-tcp-probe\r\n\r\n" + ) + .into_bytes(); + let request_packet = build_tcp_ipv4_packet( + local_ip, + remote_ip, + local_port, + remote_addr.port(), + client_seq, + peer_ack, + TCP_PSH | TCP_ACK, + &request, + ); + outbound_tx + .send(TunnelPacket { payload: request_packet }) + .await + .context("failed to send proxy tcp HTTP request")?; + client_seq = client_seq.wrapping_add(request.len() as u32); + + let response = timeout(Duration::from_millis(timeout_ms), async { + loop { + let packet = tunnel_packets + .message() + .await + .context("failed to read packet from daemon packet stream")? + .context("daemon packet stream ended before HTTP response")?; + let Some(segment) = parse_tcp_ipv4_segment( + &packet.payload, + local_ip, + remote_ip, + local_port, + remote_addr.port(), + )? + else { + continue; + }; + + if segment.flags & TCP_RST != 0 { + bail!("proxy tcp probe received RST before HTTP response"); + } + if !segment.payload.is_empty() { + peer_ack = segment.sequence.wrapping_add(segment.payload.len() as u32); + let ack = build_tcp_ipv4_packet( + local_ip, + remote_ip, + local_port, + remote_addr.port(), + client_seq, + peer_ack, + TCP_ACK, + &[], + ); + outbound_tx + .send(TunnelPacket { payload: ack }) + .await + .context("failed to ACK proxy tcp response")?; + break Ok::<_, anyhow::Error>(segment.payload); + } + if segment.flags & TCP_FIN != 0 { + bail!("proxy tcp probe received FIN before HTTP response bytes"); + } + } + }) + .await + .with_context(|| { + format!("timed out waiting for HTTP response through proxy to {remote_addr}") + })??; + + let first_line = String::from_utf8_lossy(&response) + .lines() + .next() + .unwrap_or("") + .to_owned(); + println!("Proxy TCP Probe Local: {local_addr}"); + println!("Proxy TCP Probe Remote: {remote_addr}"); + println!("Proxy TCP Probe Response Bytes: {}", response.len()); + println!("Proxy TCP Probe First Line: {first_line}"); + Ok(()) +} + +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +async fn open_proxy_packet_tcp_stream( + remote_addr: std::net::SocketAddr, + timeout_ms: u64, +) -> Result<(tokio::io::DuplexStream, std::net::SocketAddr)> { + use std::net::IpAddr; + + use anyhow::{bail, Context}; + use rand::Rng; + use tokio::{ + io::{AsyncReadExt, AsyncWriteExt}, + sync::mpsc, + time::{timeout, Duration}, + }; + use tokio_stream::wrappers::ReceiverStream; + + use crate::daemon::rpc::grpc_defs::{Empty, TunnelPacket}; + + let remote_ip = match remote_addr.ip() { + IpAddr::V4(ip) => ip, + IpAddr::V6(_) => bail!("proxy packet tcp stream currently supports IPv4 targets only"), + }; + + let mut client = BurrowClient::from_uds().await?; + client.tunnel_client.tunnel_start(Empty {}).await?; + + let mut config_stream = client + .tunnel_client + .tunnel_configuration(Empty {}) + .await? + .into_inner(); + let config = config_stream + .message() + .await? + .context("tunnel configuration stream ended before yielding a config")?; + let local_addr = select_tailnet_local_socket(&config.addresses, remote_addr.ip())?; + let local_ip = match local_addr.ip() { + IpAddr::V4(ip) => ip, + IpAddr::V6(_) => bail!("proxy packet tcp stream selected an IPv6 local address"), + }; + let local_port = local_addr.port(); + + let (outbound_tx, outbound_rx) = mpsc::channel::(128); + let mut tunnel_packets = client + .tunnel_client + .tunnel_packets(ReceiverStream::new(outbound_rx)) + .await? + .into_inner(); + + let initial_seq = rand::thread_rng().gen::(); + let syn = build_tcp_ipv4_packet( + local_ip, + remote_ip, + local_port, + remote_addr.port(), + initial_seq, + 0, + TCP_SYN, + &[], + ); + outbound_tx + .send(TunnelPacket { payload: syn }) + .await + .context("failed to send proxy packet TCP SYN into daemon packet stream")?; + + let syn_ack = timeout(Duration::from_millis(timeout_ms), async { + loop { + let packet = tunnel_packets + .message() + .await + .context("failed to read packet from daemon packet stream")? + .context("daemon packet stream ended before SYN-ACK")?; + if let Some(segment) = parse_tcp_ipv4_segment( + &packet.payload, + local_ip, + remote_ip, + local_port, + remote_addr.port(), + )? { + if segment.flags & TCP_SYN != 0 && segment.flags & TCP_ACK != 0 { + break Ok::<_, anyhow::Error>(segment); + } + if segment.flags & TCP_RST != 0 { + bail!("proxy packet TCP stream received RST before SYN-ACK"); + } + } + } + }) + .await + .with_context(|| format!("timed out waiting for proxy TCP SYN-ACK from {remote_addr}"))??; + + let mut client_seq = initial_seq.wrapping_add(1); + let mut peer_ack = syn_ack.sequence.wrapping_add(1); + let ack = build_tcp_ipv4_packet( + local_ip, + remote_ip, + local_port, + remote_addr.port(), + client_seq, + peer_ack, + TCP_ACK, + &[], + ); + outbound_tx + .send(TunnelPacket { payload: ack }) + .await + .context("failed to send proxy packet TCP handshake ACK")?; + + let (client_stream, manager_stream) = tokio::io::duplex(128 * 1024); + let (mut app_reader, mut app_writer) = tokio::io::split(manager_stream); + tokio::spawn(async move { + let mut app_buf = vec![0u8; 8192]; + loop { + tokio::select! { + read = app_reader.read(&mut app_buf) => { + let read = match read { + Ok(read) => read, + Err(error) => { + ::tracing::debug!(%error, "proxy packet TCP stream app read failed"); + break; + } + }; + if read == 0 { + let fin = build_tcp_ipv4_packet( + local_ip, + remote_ip, + local_port, + remote_addr.port(), + client_seq, + peer_ack, + TCP_FIN | TCP_ACK, + &[], + ); + let _ = outbound_tx.send(TunnelPacket { payload: fin }).await; + break; + } + + for chunk in app_buf[..read].chunks(1200) { + let packet = build_tcp_ipv4_packet( + local_ip, + remote_ip, + local_port, + remote_addr.port(), + client_seq, + peer_ack, + TCP_PSH | TCP_ACK, + chunk, + ); + if let Err(error) = outbound_tx.send(TunnelPacket { payload: packet }).await { + ::tracing::debug!(%error, "proxy packet TCP stream outbound send failed"); + return; + } + client_seq = client_seq.wrapping_add(chunk.len() as u32); + } + } + packet = tunnel_packets.message() => { + let packet = match packet { + Ok(Some(packet)) => packet, + Ok(None) => break, + Err(error) => { + ::tracing::debug!(%error, "proxy packet TCP stream inbound read failed"); + break; + } + }; + let segment = match parse_tcp_ipv4_segment( + &packet.payload, + local_ip, + remote_ip, + local_port, + remote_addr.port(), + ) { + Ok(Some(segment)) => segment, + Ok(None) => continue, + Err(error) => { + ::tracing::debug!(%error, "proxy packet TCP stream segment parse failed"); + break; + } + }; + if segment.flags & TCP_RST != 0 { + ::tracing::debug!("proxy packet TCP stream received RST"); + break; + } + + let mut ack_increment = segment.payload.len() as u32; + if segment.flags & TCP_FIN != 0 { + ack_increment = ack_increment.wrapping_add(1); + } + if ack_increment > 0 { + peer_ack = segment.sequence.wrapping_add(ack_increment); + let ack = build_tcp_ipv4_packet( + local_ip, + remote_ip, + local_port, + remote_addr.port(), + client_seq, + peer_ack, + TCP_ACK, + &[], + ); + if let Err(error) = outbound_tx.send(TunnelPacket { payload: ack }).await { + ::tracing::debug!(%error, "proxy packet TCP stream ACK send failed"); + return; + } + } + + if !segment.payload.is_empty() { + if let Err(error) = app_writer.write_all(&segment.payload).await { + ::tracing::debug!(%error, "proxy packet TCP stream app write failed"); + break; + } + } + if segment.flags & TCP_FIN != 0 { + let _ = app_writer.shutdown().await; + break; + } + } + } + } + }); + + Ok((client_stream, local_addr)) +} + +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +async fn try_proxy_https_probe( + remote: &str, + host: Option<&str>, + dns_name: Option<&str>, + dns_server: &str, + timeout_ms: u64, +) -> Result<()> { + use std::{ + net::{IpAddr, SocketAddr}, + sync::{Arc, Once}, + }; + + use anyhow::{bail, Context}; + use rustls::{pki_types::ServerName, ClientConfig, RootCertStore}; + use tokio::{ + io::{AsyncReadExt, AsyncWriteExt}, + time::{timeout, Duration}, + }; + use tokio_rustls::TlsConnector; + + static INSTALL_RUSTLS_PROVIDER: Once = Once::new(); + INSTALL_RUSTLS_PROVIDER.call_once(|| { + let _ = rustls::crypto::aws_lc_rs::default_provider().install_default(); + }); + + let mut remote_addr: SocketAddr = remote + .parse() + .with_context(|| format!("invalid remote socket address {remote}"))?; + let tls_host = host + .or(dns_name) + .context("proxy HTTPS probe requires --host or --dns-name for TLS SNI")? + .to_owned(); + if let Some(dns_name) = dns_name { + let resolved_ip = proxy_dns_probe(dns_name, dns_server, timeout_ms).await?; + remote_addr = SocketAddr::new(IpAddr::V4(resolved_ip), remote_addr.port()); + println!("Proxy HTTPS Probe DNS Name: {dns_name}"); + println!("Proxy HTTPS Probe DNS Resolved: {resolved_ip}"); + } + + if !remote_addr.is_ipv4() { + bail!("proxy HTTPS probe currently supports IPv4 targets only"); + } + + let mut root_store = RootCertStore::empty(); + root_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned()); + let mut tls_config = ClientConfig::builder() + .with_root_certificates(root_store) + .with_no_client_auth(); + tls_config.alpn_protocols = vec![b"http/1.1".to_vec()]; + + let connector = TlsConnector::from(Arc::new(tls_config)); + let server_name = ServerName::try_from(tls_host.clone()) + .with_context(|| format!("invalid TLS server name {tls_host}"))?; + + let (stream, local_addr) = open_proxy_packet_tcp_stream(remote_addr, timeout_ms).await?; + let mut tls_stream = timeout( + Duration::from_millis(timeout_ms), + connector.connect(server_name, stream), + ) + .await + .with_context(|| format!("timed out waiting for TLS handshake with {tls_host}"))? + .with_context(|| format!("TLS handshake with {tls_host} failed"))?; + + let request = format!( + "GET / HTTP/1.1\r\nHost: {tls_host}\r\nConnection: close\r\nUser-Agent: burrow-proxy-https-probe\r\n\r\n" + ); + timeout( + Duration::from_millis(timeout_ms), + tls_stream.write_all(request.as_bytes()), + ) + .await + .with_context(|| format!("timed out sending HTTPS request to {tls_host}"))? + .with_context(|| format!("failed to send HTTPS request to {tls_host}"))?; + + let response = timeout(Duration::from_millis(timeout_ms), async { + let mut response = Vec::new(); + let mut buf = [0u8; 8192]; + loop { + let read = tls_stream + .read(&mut buf) + .await + .context("failed to read HTTPS response")?; + if read == 0 { + break; + } + response.extend_from_slice(&buf[..read]); + if response.iter().any(|byte| *byte == b'\n') { + break; + } + } + if response.is_empty() { + bail!("HTTPS response ended before returning bytes"); + } + Ok::<_, anyhow::Error>(response) + }) + .await + .with_context(|| format!("timed out waiting for HTTPS response from {tls_host}"))??; + + let first_line = String::from_utf8_lossy(&response) + .lines() + .next() + .unwrap_or("") + .to_owned(); + println!("Proxy HTTPS Probe Local: {local_addr}"); + println!("Proxy HTTPS Probe Remote: {remote_addr}"); + println!("Proxy HTTPS Probe Host: {tls_host}"); + println!("Proxy HTTPS Probe Response Bytes: {}", response.len()); + println!("Proxy HTTPS Probe First Line: {first_line}"); + Ok(()) +} + +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +async fn try_proxy_subscription_select(id: i32, selected_ordinal: i32) -> Result<()> { + use crate::daemon::rpc::grpc_defs::ProxySubscriptionSelectRequest; + + let mut client = BurrowClient::from_uds().await?; + let response = client + .proxy_subscription_client + .select_node(ProxySubscriptionSelectRequest { id, selected_ordinal }) + .await? + .into_inner(); + println!( + "Proxy Subscription Selected: id={} selected_ordinal={}", + response.id, response.selected_ordinal + ); + Ok(()) +} + #[cfg(any(target_os = "linux", target_vendor = "apple"))] fn select_tailnet_local_ip( addresses: &[String], @@ -547,6 +1283,24 @@ struct IcmpEchoReply { payload: Vec, } +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +const TCP_FIN: u16 = 0x01; +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +const TCP_SYN: u16 = 0x02; +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +const TCP_RST: u16 = 0x04; +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +const TCP_PSH: u16 = 0x08; +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +const TCP_ACK: u16 = 0x10; + +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +struct TcpSegment { + sequence: u32, + flags: u16, + payload: Vec, +} + #[cfg(any(target_os = "linux", target_vendor = "apple"))] fn build_icmp_echo_request( source: std::net::IpAddr, @@ -643,6 +1397,113 @@ fn parse_icmp_echo_reply( })) } +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +fn build_tcp_ipv4_packet( + source: std::net::Ipv4Addr, + destination: std::net::Ipv4Addr, + source_port: u16, + destination_port: u16, + sequence: u32, + ack: u32, + flags: u16, + payload: &[u8], +) -> Vec { + let tcp_len = 20 + payload.len(); + let total_len = 20 + tcp_len; + let mut packet = Vec::with_capacity(total_len); + packet.push(0x45); + packet.push(0); + packet.extend_from_slice(&(total_len as u16).to_be_bytes()); + packet.extend_from_slice(&0u16.to_be_bytes()); + packet.extend_from_slice(&0x4000u16.to_be_bytes()); + packet.push(64); + packet.push(6); + packet.extend_from_slice(&[0, 0]); + packet.extend_from_slice(&source.octets()); + packet.extend_from_slice(&destination.octets()); + let header_checksum = internet_checksum(&packet); + packet[10..12].copy_from_slice(&header_checksum.to_be_bytes()); + + let tcp_start = packet.len(); + packet.extend_from_slice(&source_port.to_be_bytes()); + packet.extend_from_slice(&destination_port.to_be_bytes()); + packet.extend_from_slice(&sequence.to_be_bytes()); + packet.extend_from_slice(&ack.to_be_bytes()); + packet.push(5 << 4); + packet.push((flags & 0xff) as u8); + packet.extend_from_slice(&64240u16.to_be_bytes()); + packet.extend_from_slice(&[0, 0]); + packet.extend_from_slice(&0u16.to_be_bytes()); + packet.extend_from_slice(payload); + + let checksum = tcp_ipv4_checksum(source, destination, &packet[tcp_start..]); + packet[tcp_start + 16..tcp_start + 18].copy_from_slice(&checksum.to_be_bytes()); + packet +} + +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +fn parse_tcp_ipv4_segment( + packet: &[u8], + local_ip: std::net::Ipv4Addr, + remote_ip: std::net::Ipv4Addr, + local_port: u16, + remote_port: u16, +) -> Result> { + use anyhow::bail; + + if packet.len() < 40 { + return Ok(None); + } + let version = packet[0] >> 4; + if version != 4 { + return Ok(None); + } + let ihl = (packet[0] & 0x0f) as usize * 4; + if packet.len() < ihl + 20 || packet[9] != 6 { + return Ok(None); + } + + let source = std::net::Ipv4Addr::new(packet[12], packet[13], packet[14], packet[15]); + let destination = std::net::Ipv4Addr::new(packet[16], packet[17], packet[18], packet[19]); + if source != remote_ip || destination != local_ip { + return Ok(None); + } + + let tcp = &packet[ihl..]; + let source_port = u16::from_be_bytes([tcp[0], tcp[1]]); + let destination_port = u16::from_be_bytes([tcp[2], tcp[3]]); + if source_port != remote_port || destination_port != local_port { + return Ok(None); + } + let data_offset = ((tcp[12] >> 4) as usize) * 4; + if data_offset < 20 || tcp.len() < data_offset { + bail!("invalid TCP data offset in proxy tcp probe response"); + } + let sequence = u32::from_be_bytes([tcp[4], tcp[5], tcp[6], tcp[7]]); + let flags = (tcp[13] as u16) & 0x3f; + Ok(Some(TcpSegment { + sequence, + flags, + payload: tcp[data_offset..].to_vec(), + })) +} + +#[cfg(any(target_os = "linux", target_vendor = "apple"))] +fn tcp_ipv4_checksum( + source: std::net::Ipv4Addr, + destination: std::net::Ipv4Addr, + tcp_segment: &[u8], +) -> u16 { + let mut pseudo = Vec::with_capacity(12 + tcp_segment.len()); + pseudo.extend_from_slice(&source.octets()); + pseudo.extend_from_slice(&destination.octets()); + pseudo.push(0); + pseudo.push(6); + pseudo.extend_from_slice(&(tcp_segment.len() as u16).to_be_bytes()); + pseudo.extend_from_slice(tcp_segment); + internet_checksum(&pseudo) +} + #[cfg(any(target_os = "linux", target_vendor = "apple"))] fn internet_checksum(bytes: &[u8]) -> u16 { let mut sum = 0u32; @@ -775,6 +1636,32 @@ async fn main() -> Result<()> { Commands::TailnetUdpEcho(args) => { try_tailnet_udp_echo(&args.remote, &args.message, args.timeout_ms).await? } + Commands::ProxyTcpProbe(args) => { + try_proxy_tcp_probe( + &args.remote, + &args.host, + args.dns_name.as_deref(), + &args.dns_server, + args.timeout_ms, + ) + .await? + } + Commands::ProxyHttpsProbe(args) => { + try_proxy_https_probe( + &args.remote, + args.host.as_deref(), + args.dns_name.as_deref(), + &args.dns_server, + args.timeout_ms, + ) + .await? + } + Commands::ProxySubscriptionSelect(args) => { + try_proxy_subscription_select(args.id, args.selected_ordinal).await? + } + Commands::ProxySubscriptionPreview(args) => { + try_proxy_subscription_preview(&args.url).await? + } #[cfg(target_os = "linux")] Commands::Exec(args) => { try_exec( diff --git a/burrow/src/proxy_runtime.rs b/burrow/src/proxy_runtime.rs new file mode 100644 index 0000000..46345d5 --- /dev/null +++ b/burrow/src/proxy_runtime.rs @@ -0,0 +1,3110 @@ +#[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, + net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, ToSocketAddrs}, + os::fd::AsRawFd, + sync::Arc, + time::Duration, +}; + +use anyhow::{anyhow, bail, Context, Result}; +use futures::{SinkExt, StreamExt}; +use md5::{Digest as Md5Digest, Md5}; +#[cfg(target_vendor = "apple")] +use native_tls::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 sha2::{Sha224, Sha256}; +use tokio::{ + io::{split, AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}, + net::{TcpSocket, TcpStream, UdpSocket}, + sync::{broadcast, mpsc, 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 tun::tokio::TunInterface; + +use crate::proxy_subscription::{ + ProxyNode, ProxyNodeConfig, ProxySubscriptionPayload, ShadowsocksNode, TrojanNetwork, + TrojanNode, +}; + +const PROXY_TUN_V4: &str = "100.64.0.2/24"; +const PROXY_TUN_V6: &str = "fd00:64::2/64"; +const PROXY_DNS_V4: &str = "100.64.0.2"; +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 PUBLIC_DNS_SERVERS: &[&str] = &["1.1.1.1", "8.8.8.8"]; +const DIRECT_DNS_TIMEOUT: Duration = Duration::from_secs(2); +const FAKE_DNS_BASE: u32 = u32::from_be_bytes([198, 18, 64, 1]); + +pub struct ProxyRuntimeConfig { + pub selected_name: String, + pub outbound: ProxyOutbound, +} + +pub struct ProxyPacketBridge { + outbound: mpsc::Sender>, + inbound: broadcast::Sender>, + proxy_outbound: Arc>, + task: JoinHandle>, +} + +impl ProxyPacketBridge { + pub fn outbound_sender(&self) -> mpsc::Sender> { + self.outbound.clone() + } + + pub fn subscribe(&self) -> broadcast::Receiver> { + self.inbound.subscribe() + } + + pub async fn set_proxy_outbound(&self, outbound: ProxyOutbound) { + *self.proxy_outbound.write().await = outbound; + } + + pub fn abort(&self) { + self.task.abort(); + } + + pub async fn join(self) -> Result<()> { + match self.task.await { + Ok(Ok(())) => Ok(()), + Ok(Err(err)) => Err(err), + Err(err) if err.is_cancelled() => Ok(()), + Err(err) => Err(err.into()), + } + } +} + +#[derive(Clone)] +pub enum ProxyOutbound { + Trojan(TrojanOutbound), + Shadowsocks(ShadowsocksOutbound), +} + +type DnsHostnameCache = Arc>; + +#[derive(Debug)] +struct DnsHostnameCacheState { + reverse: HashMap, + fake_by_hostname: HashMap, + next_fake_offset: u32, +} + +impl DnsHostnameCacheState { + fn new() -> Self { + Self { + reverse: HashMap::new(), + fake_by_hostname: HashMap::new(), + next_fake_offset: 0, + } + } + + fn lookup_hostname(&self, ip: IpAddr) -> Option { + self.reverse.get(&ip).cloned() + } + + fn insert_mapping(&mut self, ip: IpAddr, hostname: String) { + self.reverse.insert(ip, hostname); + } + + fn fake_ip_for_hostname(&mut self, hostname: &str) -> Ipv4Addr { + if let Some(ip) = self.fake_by_hostname.get(hostname) { + return *ip; + } + + let raw = FAKE_DNS_BASE.wrapping_add(self.next_fake_offset); + self.next_fake_offset = self.next_fake_offset.wrapping_add(1); + let ip = Ipv4Addr::from(raw); + self.fake_by_hostname.insert(hostname.to_owned(), ip); + self.reverse.insert(IpAddr::V4(ip), hostname.to_owned()); + ip + } +} + +#[derive(Debug, Clone)] +struct ProxyTcpTarget { + address: SocketAddr, + hostname: Option, +} + +impl ProxyOutbound { + async fn bridge_tcp(&self, inbound: StackTcpStream, target: ProxyTcpTarget) -> Result<()> { + match self { + Self::Trojan(outbound) => outbound.bridge_tcp(inbound, target).await, + Self::Shadowsocks(outbound) => outbound.bridge_tcp(inbound, target).await, + } + } + + async fn start_udp_session( + &self, + key: UdpFlowKey, + outbound_rx: mpsc::Receiver>, + reply_tx: mpsc::Sender, + ) -> Result<()> { + match self { + Self::Trojan(outbound) => outbound.run_udp_session(key, outbound_rx, reply_tx).await, + Self::Shadowsocks(outbound) => { + outbound.run_udp_session(key, outbound_rx, reply_tx).await + } + } + } +} + +#[derive(Clone)] +pub struct TrojanOutbound { + endpoint: ProxyServerEndpoint, + password: String, + sni: String, + alpn: Vec, + skip_cert_verify: bool, + udp_enabled: bool, + certificate_fingerprint: Option, +} + +#[derive(Clone)] +pub struct ShadowsocksOutbound { + endpoint: ProxyServerEndpoint, + cipher: SsCipherKind, + master_key: Vec, +} + +#[derive(Clone)] +struct ProxyServerEndpoint { + host: String, + port: u16, + addresses: Arc<[SocketAddr]>, +} + +#[derive(Debug, Clone, Copy)] +enum SsCipherKind { + Aes128Gcm, + Aes256Gcm, + Chacha20IetfPoly1305, +} + +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}"), + } + } + + fn key_len(self) -> usize { + match self { + Self::Aes128Gcm => 16, + Self::Aes256Gcm | Self::Chacha20IetfPoly1305 => 32, + } + } + + fn salt_len(self) -> usize { + self.key_len() + } + + fn algorithm(self) -> &'static aead::Algorithm { + match self { + Self::Aes128Gcm => &aead::AES_128_GCM, + Self::Aes256Gcm => &aead::AES_256_GCM, + Self::Chacha20IetfPoly1305 => &aead::CHACHA20_POLY1305, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +struct CertificateFingerprint([u8; 32]); + +#[derive(Debug)] +struct UdpReply { + payload: Vec, + source: SocketAddr, + destination: SocketAddr, +} + +#[derive(Debug, Clone, Eq, Hash, PartialEq)] +struct UdpFlowKey { + local: SocketAddr, + remote: SocketAddr, +} + +pub fn proxy_server_config_name(payload: &ProxySubscriptionPayload) -> String { + match selected_node(payload) { + Ok(node) => format!("{} ({})", payload.name, node.name), + Err(_) => payload.name.clone(), + } +} + +pub fn proxy_server_addresses() -> Vec { + vec![PROXY_TUN_V4.to_owned(), PROXY_TUN_V6.to_owned()] +} + +pub fn proxy_dns_servers() -> Vec { + let servers = vec![PROXY_DNS_V4.to_owned()]; + tracing::info!( + dns_servers = ?servers, + "configured proxy subscription DNS servers from daemon fake-IP resolver" + ); + servers +} + +pub fn proxy_dns_excluded_routes(dns_servers: &[String]) -> Vec { + let mut routes = Vec::new(); + for server in dns_servers { + let Ok(ip) = server.parse::() else { + continue; + }; + if ip == IpAddr::V4(Ipv4Addr::new(100, 64, 0, 2)) { + continue; + } + let route = host_route_for_ip(ip); + if !routes.contains(&route) { + routes.push(route); + } + } + tracing::info!( + excluded_routes = ?routes, + "configured proxy DNS excluded routes" + ); + routes +} + +pub fn proxy_excluded_routes(payload: &ProxySubscriptionPayload) -> Result> { + let node = selected_node(payload)?; + let routes = excluded_routes_for_server(&node.server, node.port)?; + tracing::info!( + server = %node.server, + port = node.port, + excluded_routes = ?routes, + "configured proxy server excluded routes" + ); + Ok(routes) +} + +pub fn proxy_runtime_config(payload: &ProxySubscriptionPayload) -> Result { + let node = selected_node(payload)?; + tracing::info!( + subscription = %payload.name, + node = %node.name, + protocol = %node.protocol.as_str(), + server = %node.server, + port = node.port, + "selected proxy subscription node" + ); + let outbound = match &node.config { + ProxyNodeConfig::Trojan(config) => { + ProxyOutbound::Trojan(TrojanOutbound::from_node(node, config)?) + } + ProxyNodeConfig::Shadowsocks(config) => { + ProxyOutbound::Shadowsocks(ShadowsocksOutbound::from_node(node, config)?) + } + }; + Ok(ProxyRuntimeConfig { + selected_name: node.name.clone(), + outbound, + }) +} + +pub fn selected_node(payload: &ProxySubscriptionPayload) -> Result<&ProxyNode> { + if let Some(name) = payload.selected_name.as_deref() { + let node = payload + .nodes + .iter() + .find(|node| node.name == name) + .ok_or_else(|| anyhow!("selected proxy subscription node {name} was not found"))?; + validate_runtime_supported_node(node)?; + return Ok(node); + } + + if let Some(ordinal) = payload.selected_ordinal { + let node = payload + .nodes + .iter() + .find(|node| node.ordinal == ordinal) + .ok_or_else(|| anyhow!("selected proxy subscription node {ordinal} was not found"))?; + validate_runtime_supported_node(node)?; + return Ok(node); + } + + payload + .nodes + .iter() + .find(|node| validate_runtime_supported_node(node).is_ok()) + .ok_or_else(|| { + anyhow!("proxy subscription contains no runtime-supported Trojan or Shadowsocks nodes") + }) +} + +fn validate_runtime_supported_node(node: &ProxyNode) -> Result<()> { + match &node.config { + ProxyNodeConfig::Trojan(config) => { + if !matches!(config.network, TrojanNetwork::Tcp) { + bail!( + "Trojan node {} uses {:?}; only tcp transport is supported by the packet runtime", + node.name, + config.network + ); + } + } + 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 + ); + } + if config.udp_over_tcp { + bail!( + "Shadowsocks node {} uses udp-over-tcp; native UDP relay is used by the packet runtime", + node.name + ); + } + SsCipherKind::from_name(&config.cipher)?; + } + } + Ok(()) +} + +pub fn runtime_support_error(node: &ProxyNode) -> Option { + validate_runtime_supported_node(node) + .err() + .map(|err| err.to_string()) +} + +pub fn start_proxy_packet_bridge(outbound: ProxyOutbound) -> ProxyPacketBridge { + let (outbound_tx, outbound_rx) = mpsc::channel(128); + let (inbound_tx, _) = broadcast::channel(128); + let proxy_outbound = Arc::new(RwLock::new(outbound)); + let task = tokio::spawn(run_proxy_packet_stack( + outbound_rx, + inbound_tx.clone(), + proxy_outbound.clone(), + )); + ProxyPacketBridge { + outbound: outbound_tx, + inbound: inbound_tx, + proxy_outbound, + task, + } +} + +pub async fn run_proxy_tun_bridge( + tun: Arc>>, + outbound_tx: mpsc::Sender>, + mut inbound_rx: broadcast::Receiver>, +) -> Result<()> { + let inbound_tun = tun.clone(); + let inbound = tokio::spawn(async move { + loop { + let packet = match inbound_rx.recv().await { + Ok(packet) => packet, + Err(broadcast::error::RecvError::Lagged(_)) => continue, + Err(broadcast::error::RecvError::Closed) => break, + }; + let guard = inbound_tun.read().await; + let Some(tun) = guard.as_ref() else { + bail!("proxy subscription tun interface unavailable"); + }; + tun.send(&packet) + .await + .context("failed to write proxy packet to tun")?; + } + Result::<()>::Ok(()) + }); + + let outbound_tun = tun.clone(); + let outbound = tokio::spawn(async move { + let mut buf = vec![0u8; 65_535]; + loop { + let len = { + let guard = outbound_tun.read().await; + let Some(tun) = guard.as_ref() else { + bail!("proxy subscription tun interface unavailable"); + }; + tun.recv(&mut buf) + .await + .context("failed to read proxy packet from tun")? + }; + outbound_tx + .send(buf[..len].to_vec()) + .await + .context("failed to forward packet to proxy stack")?; + } + #[allow(unreachable_code)] + Result::<()>::Ok(()) + }); + + let (inbound_result, outbound_result) = tokio::try_join!(inbound, outbound)?; + inbound_result?; + outbound_result?; + Ok(()) +} + +async fn run_proxy_packet_stack( + mut packet_rx: mpsc::Receiver>, + packet_tx: broadcast::Sender>, + outbound: Arc>, +) -> Result<()> { + let (stack, runner, udp_socket, tcp_listener) = StackBuilder::default() + .stack_buffer_size(1024) + .udp_buffer_size(1024) + .tcp_buffer_size(1024) + .enable_udp(true) + .enable_tcp(true) + .enable_icmp(true) + .build() + .context("failed to build proxy userspace netstack")?; + let (mut stack_sink, mut stack_stream) = stack.split(); + + let mut tasks = JoinSet::new(); + if let Some(runner) = runner { + tasks.spawn(async move { runner.await.map_err(anyhow::Error::from) }); + } + + tasks.spawn(async move { + while let Some(packet) = packet_rx.recv().await { + stack_sink + .send(packet) + .await + .context("failed to send tunnel packet into proxy netstack")?; + } + Result::<()>::Ok(()) + }); + + tasks.spawn(async move { + while let Some(packet) = stack_stream.next().await { + let packet = packet.context("failed to receive packet from proxy netstack")?; + let _ = packet_tx.send(packet); + } + Result::<()>::Ok(()) + }); + + if let Some(tcp_listener) = tcp_listener { + let tcp_outbound = outbound.clone(); + let dns_cache = Arc::new(RwLock::new(DnsHostnameCacheState::new())); + let tcp_dns_cache = dns_cache.clone(); + tasks.spawn( + async move { tcp_dispatch_loop(tcp_listener, tcp_outbound, tcp_dns_cache).await }, + ); + + if let Some(udp_socket) = udp_socket { + tasks.spawn(async move { udp_dispatch_loop(udp_socket, outbound, dns_cache).await }); + } + } else if let Some(udp_socket) = udp_socket { + let dns_cache = Arc::new(RwLock::new(DnsHostnameCacheState::new())); + tasks.spawn(async move { udp_dispatch_loop(udp_socket, outbound, dns_cache).await }); + } + + while let Some(result) = tasks.join_next().await { + match result { + Ok(Ok(())) => {} + Ok(Err(err)) => return Err(err), + Err(err) if err.is_cancelled() => {} + Err(err) => return Err(err.into()), + } + } + Ok(()) +} + +async fn tcp_dispatch_loop( + mut listener: StackTcpListener, + outbound: Arc>, + dns_cache: DnsHostnameCache, +) -> Result<()> { + let mut tasks = JoinSet::new(); + loop { + tokio::select! { + Some(result) = tasks.join_next(), if !tasks.is_empty() => { + match result { + Ok(Ok(())) => {} + Ok(Err(err)) => tracing::warn!(?err, "proxy tcp bridge task failed"), + Err(err) if err.is_cancelled() => {} + Err(err) => tracing::warn!(?err, "proxy tcp bridge task panicked"), + } + } + next = listener.next() => match next { + Some((stream, local_addr, remote_addr)) => { + let hostname = dns_cache.read().await.lookup_hostname(remote_addr.ip()); + tracing::debug!( + %local_addr, + %remote_addr, + hostname = hostname.as_deref(), + "accepted proxy tcp stream" + ); + let outbound = outbound.read().await.clone(); + let target = ProxyTcpTarget { + address: remote_addr, + hostname, + }; + tasks.spawn(async move { + outbound.bridge_tcp(stream, target).await + }); + } + None => break, + } + } + } + + tasks.abort_all(); + Ok(()) +} + +async fn udp_dispatch_loop( + socket: StackUdpSocket, + outbound: Arc>, + dns_cache: DnsHostnameCache, +) -> Result<()> { + let (mut udp_reader, mut udp_writer) = socket.split(); + let (reply_tx, mut reply_rx) = mpsc::channel::(128); + let sessions = Arc::new(Mutex::new( + HashMap::>>::new(), + )); + let mut session_tasks = JoinSet::new(); + + loop { + tokio::select! { + Some(result) = session_tasks.join_next(), if !session_tasks.is_empty() => { + match result { + Ok(Ok(())) => {} + Ok(Err(err)) => tracing::warn!(?err, "proxy udp session failed"), + Err(err) if err.is_cancelled() => {} + Err(err) => tracing::warn!(?err, "proxy udp session panicked"), + } + } + maybe_reply = reply_rx.recv() => match maybe_reply { + Some(reply) => { + record_dns_mappings(&dns_cache, &reply.payload).await; + udp_writer + .send((reply.payload, reply.source, reply.destination)) + .await + .context("failed to write udp reply into proxy netstack")?; + } + None => break, + }, + maybe_datagram = udp_reader.next() => match maybe_datagram { + Some((payload, local_addr, remote_addr)) => { + if is_proxy_dns_request(remote_addr) { + if let Some(response) = handle_fake_dns_request(&dns_cache, &payload).await { + udp_writer + .send((response, remote_addr, local_addr)) + .await + .context("failed to write fake DNS reply into proxy netstack")?; + continue; + } + } + dispatch_udp( + outbound.clone(), + payload, + local_addr, + remote_addr, + reply_tx.clone(), + sessions.clone(), + &mut session_tasks, + ).await?; + } + None => break, + } + } + } + + session_tasks.abort_all(); + Ok(()) +} + +fn is_proxy_dns_request(remote_addr: SocketAddr) -> bool { + remote_addr.port() == 53 && remote_addr.ip() == IpAddr::V4(Ipv4Addr::new(100, 64, 0, 2)) +} + +async fn handle_fake_dns_request(cache: &DnsHostnameCache, payload: &[u8]) -> Option> { + match build_fake_dns_response(cache, payload).await { + Ok(response) => Some(response), + Err(err) => { + tracing::debug!(error = ?err, "failed to answer fake proxy DNS request"); + None + } + } +} + +async fn build_fake_dns_response(cache: &DnsHostnameCache, query: &[u8]) -> Result> { + let questions = parse_dns_query(query)?; + let mut answers = Vec::new(); + { + let mut guard = cache.write().await; + for question in &questions { + if question.class != 1 || question.name.is_empty() { + continue; + } + if question.record_type == 1 { + let ip = guard.fake_ip_for_hostname(&question.name); + tracing::debug!( + hostname = %question.name, + %ip, + "answered proxy fake DNS A query" + ); + answers.push((question.name.clone(), ip)); + } + } + } + Ok(encode_fake_dns_response(query, &questions, &answers)) +} + +#[derive(Debug)] +struct DnsQuestion { + name: String, + record_type: u16, + class: u16, +} + +fn parse_dns_query(query: &[u8]) -> Result> { + if query.len() < 12 { + bail!("truncated DNS query"); + } + let flags = u16::from_be_bytes([query[2], query[3]]); + if flags & 0x8000 != 0 { + bail!("DNS packet is not a query"); + } + let question_count = u16::from_be_bytes([query[4], query[5]]) as usize; + let mut offset = 12; + let mut questions = Vec::new(); + for _ in 0..question_count { + let (name, next_offset) = read_dns_name(query, offset)?; + offset = next_offset; + if query.len() < offset + 4 { + bail!("truncated DNS question"); + } + let record_type = u16::from_be_bytes([query[offset], query[offset + 1]]); + let class = u16::from_be_bytes([query[offset + 2], query[offset + 3]]); + offset += 4; + questions.push(DnsQuestion { name, record_type, class }); + } + Ok(questions) +} + +fn encode_fake_dns_response( + query: &[u8], + questions: &[DnsQuestion], + answers: &[(String, Ipv4Addr)], +) -> Vec { + let question_end = dns_question_section_end(query).unwrap_or(query.len()); + let mut response = Vec::with_capacity(question_end + answers.len() * 32); + response.extend_from_slice(&query[..2]); + response.extend_from_slice(&0x8180u16.to_be_bytes()); + response.extend_from_slice(&(questions.len() as u16).to_be_bytes()); + response.extend_from_slice(&(answers.len() as u16).to_be_bytes()); + response.extend_from_slice(&0u16.to_be_bytes()); + response.extend_from_slice(&0u16.to_be_bytes()); + response.extend_from_slice(&query[12..question_end]); + + for (hostname, ip) in answers { + encode_dns_name(&mut response, hostname); + 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(&ip.octets()); + } + response +} + +fn dns_question_section_end(query: &[u8]) -> Result { + if query.len() < 12 { + bail!("truncated DNS query"); + } + let question_count = u16::from_be_bytes([query[4], query[5]]) as usize; + let mut offset = 12; + for _ in 0..question_count { + offset = skip_dns_name(query, offset)?; + if query.len() < offset + 4 { + bail!("truncated DNS question"); + } + offset += 4; + } + Ok(offset) +} + +fn encode_dns_name(out: &mut Vec, hostname: &str) { + for label in hostname.trim_end_matches('.').split('.') { + out.push(label.len().min(63) as u8); + out.extend_from_slice( + label + .as_bytes() + .get(..label.len().min(63)) + .unwrap_or_default(), + ); + } + out.push(0); +} + +async fn dispatch_udp( + outbound: Arc>, + payload: Vec, + local_addr: SocketAddr, + remote_addr: SocketAddr, + reply_tx: mpsc::Sender, + sessions: Arc>>>>, + session_tasks: &mut JoinSet>, +) -> Result<()> { + let key = UdpFlowKey { + local: local_addr, + remote: remote_addr, + }; + let existing = { sessions.lock().await.get(&key).cloned() }; + if let Some(sender) = existing { + if sender.send(payload.clone()).await.is_ok() { + return Ok(()); + } + sessions.lock().await.remove(&key); + } + + let (tx, rx) = mpsc::channel::>(32); + tx.send(payload) + .await + .context("failed to enqueue proxy udp payload")?; + sessions.lock().await.insert(key.clone(), tx); + + session_tasks.spawn(async move { + let outbound = outbound.read().await.clone(); + let result = outbound + .start_udp_session(key.clone(), rx, reply_tx) + .await + .with_context(|| format!("proxy udp session {} -> {} failed", key.local, key.remote)); + sessions.lock().await.remove(&key); + result + }); + Ok(()) +} + +impl TrojanOutbound { + fn from_node(node: &ProxyNode, config: &TrojanNode) -> Result { + if !matches!(config.network, TrojanNetwork::Tcp) { + bail!( + "Trojan node {} uses {:?}; only tcp transport is supported by the packet runtime", + node.name, + config.network + ); + } + let sni = config.sni.clone().unwrap_or_else(|| node.server.clone()); + let certificate_fingerprint = config + .fingerprint + .as_deref() + .map(parse_certificate_fingerprint) + .transpose()?; + let endpoint = ProxyServerEndpoint::resolve(&node.server, node.port) + .with_context(|| format!("failed to resolve Trojan server for node {}", node.name))?; + if let Some(client_fingerprint) = unsupported_client_fingerprint(config) { + tracing::warn!( + node = %node.name, + client_fingerprint, + "Trojan client-fingerprint is stored but uTLS ClientHello impersonation is not implemented; using platform TLS ClientHello" + ); + } + tracing::info!( + node = %node.name, + server = %endpoint.host, + port = endpoint.port, + sni = %sni, + alpn = ?trojan_alpn(config), + alpn_present = config.alpn_present, + skip_cert_verify = config.skip_cert_verify, + udp = config.udp, + certificate_fingerprint = config.fingerprint.is_some(), + resolved_addresses = ?endpoint.addresses, + "configured Trojan proxy outbound" + ); + Ok(Self { + endpoint, + password: config.password.clone(), + sni, + alpn: trojan_alpn(config), + skip_cert_verify: config.skip_cert_verify, + udp_enabled: config.udp, + certificate_fingerprint, + }) + } + + async fn bridge_tcp(&self, inbound: StackTcpStream, target: ProxyTcpTarget) -> Result<()> { + let remote_addr = target.address; + let mut outbound = self.connect_tls().await?; + let socks_target = socks_addr_for_target(&target)?; + write_trojan_header(&mut outbound, &self.password, 0x01, &socks_target).await?; + tracing::debug!( + %remote_addr, + hostname = target.hostname.as_deref(), + "wrote Trojan TCP request header" + ); + bridge_tcp_streams(inbound, outbound, remote_addr, target.hostname).await?; + Ok(()) + } + + async fn run_udp_session( + &self, + key: UdpFlowKey, + mut outbound_rx: mpsc::Receiver>, + reply_tx: mpsc::Sender, + ) -> Result<()> { + if !self.udp_enabled { + bail!("Trojan UDP is disabled for this node"); + } + let mut stream = self.connect_tls().await?; + write_trojan_header(&mut stream, &self.password, 0x03, &socks_addr(key.remote)?).await?; + let (mut reader, mut writer) = split(stream); + let remote_socks = socks_addr(key.remote)?; + + let mut write_task = tokio::spawn(async move { + while let Some(payload) = outbound_rx.recv().await { + write_trojan_udp_packet(&mut writer, &remote_socks, &payload).await?; + } + Result::<()>::Ok(()) + }); + + let mut read_task = tokio::spawn(async move { + let mut buf = vec![0u8; 65_535]; + loop { + let (source, payload) = read_trojan_udp_packet(&mut reader, &mut buf).await?; + reply_tx + .send(UdpReply { + payload, + source, + destination: key.local, + }) + .await + .context("failed to enqueue Trojan udp reply")?; + } + #[allow(unreachable_code)] + Result::<()>::Ok(()) + }); + + tokio::select! { + result = &mut write_task => { + read_task.abort(); + result??; + } + result = &mut read_task => { + write_task.abort(); + result??; + } + } + Ok(()) + } + + async fn connect_tls(&self) -> Result { + let (tcp, address) = connect_proxy_tcp(&self.endpoint).await.with_context(|| { + format!( + "failed to connect Trojan server {}:{}", + self.endpoint.host, self.endpoint.port + ) + })?; + #[cfg(target_vendor = "apple")] + { + return self.connect_native_tls(tcp, address).await; + } + #[cfg(not(target_vendor = "apple"))] + { + self.connect_rustls(tcp, address).await + } + } + + #[cfg(not(target_vendor = "apple"))] + async fn connect_rustls(&self, tcp: TcpStream, address: SocketAddr) -> Result { + let config = trojan_tls_config( + &self.alpn, + self.skip_cert_verify, + self.certificate_fingerprint.as_ref(), + )?; + let connector = TlsConnector::from(Arc::new(config)); + let name = ServerName::try_from(self.sni.clone()) + .with_context(|| format!("invalid Trojan SNI {}", self.sni))?; + tracing::debug!( + server = %self.endpoint.host, + port = self.endpoint.port, + %address, + sni = %self.sni, + alpn = ?self.alpn, + skip_cert_verify = self.skip_cert_verify, + "starting Trojan TLS handshake" + ); + let tls = match connector.connect(name, tcp).await { + Ok(tls) => { + let negotiated_alpn = tls + .get_ref() + .1 + .alpn_protocol() + .map(|protocol| String::from_utf8_lossy(protocol).into_owned()); + tracing::debug!( + server = %self.endpoint.host, + port = self.endpoint.port, + %address, + sni = %self.sni, + alpn = ?negotiated_alpn, + "completed Trojan TLS handshake" + ); + tls + } + Err(err) => { + tracing::warn!( + server = %self.endpoint.host, + port = self.endpoint.port, + %address, + sni = %self.sni, + error = %err, + "failed Trojan TLS handshake" + ); + return Err(err).with_context(|| { + format!("failed to complete Trojan TLS handshake with {}", self.sni) + }); + } + }; + Ok(Box::new(tls)) + } + + #[cfg(target_vendor = "apple")] + async fn connect_native_tls( + &self, + tcp: TcpStream, + address: SocketAddr, + ) -> Result { + let mut builder = NativeTlsConnector::builder(); + if self.skip_cert_verify || self.certificate_fingerprint.is_some() { + builder.danger_accept_invalid_certs(true); + } + let alpn = self.alpn.iter().map(String::as_str).collect::>(); + if !alpn.is_empty() { + builder.request_alpns(&alpn); + } + let connector = TokioNativeTlsConnector::from( + builder + .build() + .context("failed to build native TLS connector for Trojan")?, + ); + tracing::debug!( + server = %self.endpoint.host, + port = self.endpoint.port, + %address, + sni = %self.sni, + alpn = ?self.alpn, + skip_cert_verify = self.skip_cert_verify, + certificate_fingerprint = self.certificate_fingerprint.is_some(), + "starting Trojan native TLS handshake" + ); + 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)?; + } + let negotiated_alpn = tls + .get_ref() + .negotiated_alpn() + .ok() + .flatten() + .map(|protocol| String::from_utf8_lossy(&protocol).into_owned()); + tracing::debug!( + server = %self.endpoint.host, + port = self.endpoint.port, + %address, + sni = %self.sni, + alpn = ?negotiated_alpn, + "completed Trojan native TLS handshake" + ); + tls + } + Err(err) => { + tracing::warn!( + server = %self.endpoint.host, + port = self.endpoint.port, + %address, + sni = %self.sni, + error = %err, + "failed Trojan native TLS handshake" + ); + return Err(err).with_context(|| { + format!( + "failed to complete Trojan native TLS handshake with {}", + self.sni + ) + }); + } + }; + Ok(Box::new(tls)) + } +} + +async fn bridge_tcp_streams( + inbound: StackTcpStream, + outbound: TrojanStream, + remote_addr: SocketAddr, + hostname: Option, +) -> Result<()> { + let (inbound_reader, inbound_writer) = split(inbound); + let (outbound_reader, outbound_writer) = split(outbound); + let hostname_for_client = hostname.clone(); + let mut client_to_proxy = tokio::spawn(async move { + copy_tcp_direction( + "client_to_proxy", + inbound_reader, + outbound_writer, + remote_addr, + hostname_for_client, + ) + .await + }); + let mut proxy_to_client = tokio::spawn(async move { + copy_tcp_direction( + "proxy_to_client", + outbound_reader, + inbound_writer, + remote_addr, + hostname, + ) + .await + }); + + tokio::select! { + result = &mut client_to_proxy => { + proxy_to_client.abort(); + let bytes = result.context("client-to-proxy bridge task panicked")??; + tracing::debug!(%remote_addr, bytes, "Trojan tcp client-to-proxy bridge completed first"); + } + result = &mut proxy_to_client => { + client_to_proxy.abort(); + let bytes = result.context("proxy-to-client bridge task panicked")??; + tracing::debug!(%remote_addr, bytes, "Trojan tcp proxy-to-client bridge completed first"); + } + } + Ok(()) +} + +async fn copy_tcp_direction( + direction: &'static str, + mut reader: R, + mut writer: W, + remote_addr: SocketAddr, + hostname: Option, +) -> Result +where + R: AsyncRead + Unpin, + W: AsyncWrite + Unpin, +{ + let mut total = 0u64; + let mut logged_first_chunk = false; + let mut buf = vec![0u8; 16 * 1024]; + loop { + let len = reader + .read(&mut buf) + .await + .with_context(|| format!("failed to read {direction} proxy tcp bytes"))?; + if len == 0 { + tracing::debug!( + %remote_addr, + hostname = hostname.as_deref(), + direction, + total, + "proxy tcp direction reached EOF" + ); + writer + .shutdown() + .await + .with_context(|| format!("failed to shutdown {direction} proxy tcp writer"))?; + return Ok(total); + } + if !logged_first_chunk { + logged_first_chunk = true; + tracing::debug!( + %remote_addr, + hostname = hostname.as_deref(), + direction, + len, + "proxy tcp direction forwarding first bytes" + ); + } + writer + .write_all(&buf[..len]) + .await + .with_context(|| format!("failed to write {direction} proxy tcp bytes"))?; + total += len as u64; + } +} + +fn trojan_alpn(config: &TrojanNode) -> Vec { + if config.alpn_present { + config.alpn.clone() + } else { + MIHOMO_TROJAN_TCP_DEFAULT_ALPN + .iter() + .map(|value| (*value).to_owned()) + .collect() + } +} + +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", + 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 endpoint = + ProxyServerEndpoint::resolve(&node.server, node.port).with_context(|| { + format!( + "failed to resolve Shadowsocks server for node {}", + node.name + ) + })?; + tracing::info!( + node = %node.name, + server = %endpoint.host, + port = endpoint.port, + cipher = %config.cipher, + resolved_addresses = ?endpoint.addresses, + "configured Shadowsocks proxy outbound" + ); + Ok(Self { endpoint, cipher, master_key }) + } + + 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(|| { + 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 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}") + })?; + + 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?; + 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?; + Result::<()>::Ok(()) + }); + + tokio::select! { + result = &mut upload => { + download.abort(); + result??; + } + result = &mut download => { + upload.abort(); + result??; + } + } + Ok(()) + } + + async fn run_udp_session( + &self, + key: UdpFlowKey, + mut outbound_rx: mpsc::Receiver>, + reply_tx: mpsc::Sender, + ) -> Result<()> { + let (socket, server) = connect_proxy_udp(&self.endpoint).await.with_context(|| { + format!( + "failed to connect Shadowsocks udp socket to {}:{}", + self.endpoint.host, self.endpoint.port + ) + })?; + socket.connect(server).await.with_context(|| { + format!("failed to connect udp socket to Shadowsocks server {server}") + })?; + 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)?; + socket + .send(&packet) + .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(len)) => { + let (source, payload) = + decrypt_ss_udp_packet(self.cipher, &self.master_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(()) + } +} + +trait ProxyIo: AsyncRead + AsyncWrite + Unpin + Send {} + +impl ProxyIo for T where T: AsyncRead + AsyncWrite + Unpin + Send {} + +type TrojanStream = Box; + +#[cfg(target_vendor = "apple")] +fn verify_native_tls_peer_certificate( + tls: &tokio_native_tls::TlsStream, + fingerprint: &CertificateFingerprint, +) -> 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"))?; + let digest = Sha256::digest( + cert.to_der() + .context("failed to encode Trojan native TLS peer certificate")?, + ); + if digest.as_slice() != fingerprint.0 { + bail!("Trojan certificate fingerprint mismatch"); + } + Ok(()) +} + +#[cfg(not(target_vendor = "apple"))] +fn trojan_tls_config( + alpn: &[String], + skip_cert_verify: bool, + certificate_fingerprint: Option<&CertificateFingerprint>, +) -> 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(); + config.alpn_protocols = alpn.iter().map(|value| value.as_bytes().to_vec()).collect(); + if let Some(fingerprint) = certificate_fingerprint { + config + .dangerous() + .set_certificate_verifier(Arc::new(PinnedCertificateVerifier { + fingerprint: fingerprint.clone(), + })); + } else if skip_cert_verify { + config + .dangerous() + .set_certificate_verifier(Arc::new(NoCertificateVerifier)); + } + Ok(config) +} + +#[cfg(not(target_vendor = "apple"))] +fn install_rustls_crypto_provider() { + static INSTALL: Once = Once::new(); + INSTALL.call_once( + || match rustls::crypto::aws_lc_rs::default_provider().install_default() { + Ok(()) => tracing::debug!("installed aws-lc-rs rustls crypto provider"), + Err(_) => tracing::debug!("rustls crypto provider was already installed"), + }, + ); +} + +fn parse_certificate_fingerprint(value: &str) -> Result { + let normalized = value + .chars() + .filter(|ch| *ch != ':' && *ch != '-' && !ch.is_whitespace()) + .collect::(); + if matches!( + normalized.to_ascii_lowercase().as_str(), + "chrome" | "firefox" | "safari" | "ios" | "android" | "edge" | "360" | "qq" + ) { + bail!( + "`fingerprint` is for TLS certificate pinning; use `client-fingerprint` for browser fingerprints" + ); + } + if normalized.len() != 64 { + bail!("Trojan 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() { + let hi = hex_nibble(chunk[0]).ok_or_else(|| anyhow!("invalid certificate fingerprint"))?; + let lo = hex_nibble(chunk[1]).ok_or_else(|| anyhow!("invalid certificate fingerprint"))?; + bytes[idx] = (hi << 4) | lo; + } + Ok(CertificateFingerprint(bytes)) +} + +fn unsupported_client_fingerprint(config: &TrojanNode) -> Option<&str> { + let value = config.client_fingerprint.as_deref()?.trim(); + if value.is_empty() || value.eq_ignore_ascii_case("none") { + None + } else { + Some(value) + } +} + +fn hex_nibble(byte: u8) -> Option { + match byte { + b'0'..=b'9' => Some(byte - b'0'), + b'a'..=b'f' => Some(byte - b'a' + 10), + b'A'..=b'F' => Some(byte - b'A' + 10), + _ => None, + } +} + +#[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, + end_entity: &CertificateDer<'_>, + intermediates: &[CertificateDer<'_>], + _server_name: &ServerName<'_>, + _ocsp_response: &[u8], + _now: UnixTime, + ) -> Result { + let mut certificates = std::iter::once(end_entity).chain(intermediates.iter()); + if certificates + .any(|certificate| certificate_fingerprint_matches(certificate, &self.fingerprint)) + { + Ok(ServerCertVerified::assertion()) + } else { + Err(RustlsError::General( + "certificate chain does not match pinned fingerprint".to_owned(), + )) + } + } + + fn verify_tls12_signature( + &self, + _message: &[u8], + _cert: &CertificateDer<'_>, + _dss: &DigitallySignedStruct, + ) -> Result { + Ok(HandshakeSignatureValid::assertion()) + } + + fn verify_tls13_signature( + &self, + _message: &[u8], + _cert: &CertificateDer<'_>, + _dss: &DigitallySignedStruct, + ) -> Result { + Ok(HandshakeSignatureValid::assertion()) + } + + fn supported_verify_schemes(&self) -> Vec { + supported_verify_schemes() + } +} + +#[cfg(not(target_vendor = "apple"))] +fn certificate_fingerprint_matches( + certificate: &CertificateDer<'_>, + fingerprint: &CertificateFingerprint, +) -> bool { + let digest = Sha256::digest(certificate.as_ref()); + digest[..] == fingerprint.0[..] +} + +#[derive(Debug)] +#[cfg(not(target_vendor = "apple"))] +struct NoCertificateVerifier; + +#[cfg(not(target_vendor = "apple"))] +impl ServerCertVerifier for NoCertificateVerifier { + fn verify_server_cert( + &self, + _end_entity: &CertificateDer<'_>, + _intermediates: &[CertificateDer<'_>], + _server_name: &ServerName<'_>, + _ocsp_response: &[u8], + _now: UnixTime, + ) -> Result { + Ok(ServerCertVerified::assertion()) + } + + fn verify_tls12_signature( + &self, + _message: &[u8], + _cert: &CertificateDer<'_>, + _dss: &DigitallySignedStruct, + ) -> Result { + Ok(HandshakeSignatureValid::assertion()) + } + + fn verify_tls13_signature( + &self, + _message: &[u8], + _cert: &CertificateDer<'_>, + _dss: &DigitallySignedStruct, + ) -> Result { + Ok(HandshakeSignatureValid::assertion()) + } + + fn supported_verify_schemes(&self) -> Vec { + supported_verify_schemes() + } +} + +#[cfg(not(target_vendor = "apple"))] +fn supported_verify_schemes() -> Vec { + vec![ + SignatureScheme::ECDSA_NISTP256_SHA256, + SignatureScheme::ECDSA_NISTP384_SHA384, + SignatureScheme::ED25519, + SignatureScheme::RSA_PSS_SHA256, + SignatureScheme::RSA_PSS_SHA384, + SignatureScheme::RSA_PSS_SHA512, + SignatureScheme::RSA_PKCS1_SHA256, + SignatureScheme::RSA_PKCS1_SHA384, + SignatureScheme::RSA_PKCS1_SHA512, + ] +} + +async fn write_trojan_header( + writer: &mut W, + password: &str, + command: u8, + socks_address: &[u8], +) -> Result<()> +where + W: AsyncWrite + Unpin, +{ + let password_hash = trojan_password_hash(password); + let mut header = Vec::with_capacity(password_hash.len() + 2 + 1 + socks_address.len() + 2); + header.extend_from_slice(password_hash.as_bytes()); + header.extend_from_slice(b"\r\n"); + header.push(command); + header.extend_from_slice(socks_address); + header.extend_from_slice(b"\r\n"); + writer.write_all(&header).await?; + writer.flush().await?; + Ok(()) +} + +fn trojan_password_hash(password: &str) -> String { + let digest = Sha224::digest(password.as_bytes()); + hex_lower(&digest) +} + +async fn write_trojan_udp_packet( + writer: &mut W, + socks_address: &[u8], + payload: &[u8], +) -> Result<()> +where + W: AsyncWrite + Unpin, +{ + if payload.len() <= TROJAN_MAX_UDP_PAYLOAD { + write_trojan_udp_frame(writer, socks_address, payload).await?; + } else { + for chunk in payload.chunks(TROJAN_MAX_UDP_PAYLOAD) { + write_trojan_udp_frame(writer, socks_address, chunk).await?; + } + } + writer.flush().await?; + Ok(()) +} + +async fn write_trojan_udp_frame( + writer: &mut W, + socks_address: &[u8], + payload: &[u8], +) -> Result<()> +where + W: AsyncWrite + Unpin, +{ + writer.write_all(socks_address).await?; + writer + .write_all(&(payload.len() as u16).to_be_bytes()) + .await?; + writer.write_all(b"\r\n").await?; + writer.write_all(payload).await?; + Ok(()) +} + +async fn read_trojan_udp_packet(reader: &mut R, buf: &mut [u8]) -> Result<(SocketAddr, Vec)> +where + R: AsyncRead + Unpin, +{ + let source = read_socks_addr_async(reader).await?; + let mut len = [0u8; 2]; + reader.read_exact(&mut len).await?; + let payload_len = u16::from_be_bytes(len) as usize; + let mut crlf = [0u8; 2]; + reader.read_exact(&mut crlf).await?; + if crlf != *b"\r\n" { + bail!("invalid Trojan UDP packet delimiter"); + } + if payload_len > buf.len() { + bail!("Trojan UDP packet too large"); + } + reader.read_exact(&mut buf[..payload_len]).await?; + Ok((source, buf[..payload_len].to_vec())) +} + +struct SsWriter { + writer: W, + key: aead::LessSafeKey, + nonce: [u8; 12], +} + +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(()) + } +} + +struct SsReader { + reader: R, + cipher: SsCipherKind, + master_key: Vec, + key: Option, + nonce: [u8; 12], +} + +impl SsReader +where + R: AsyncRead + Unpin, +{ + fn new(reader: R, cipher: SsCipherKind, master_key: Vec) -> Self { + Self { + reader, + cipher, + master_key, + key: None, + nonce: [0u8; 12], + } + } + + 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 increment_nonce(nonce: &mut [u8; 12]) { + for byte in nonce { + let (next, overflow) = byte.overflowing_add(1); + *byte = next; + if !overflow { + break; + } + } +} + +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"))?; + } + 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> { + let mut encoded = Vec::with_capacity(1 + 16 + 2); + match addr.ip() { + IpAddr::V4(ip) => { + encoded.push(1); + encoded.extend_from_slice(&ip.octets()); + } + IpAddr::V6(ip) => { + encoded.push(4); + encoded.extend_from_slice(&ip.octets()); + } + } + encoded.extend_from_slice(&addr.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); + } + } + socks_addr(target.address) +} + +async fn read_socks_addr_async(reader: &mut R) -> Result +where + R: AsyncRead + Unpin, +{ + let atyp = reader.read_u8().await?; + match atyp { + 1 => { + 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)) + } + 3 => { + 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 SOCKS domain")?; + resolve_server(&domain, port) + } + 4 => { + 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)) + } + other => bail!("unsupported SOCKS address type {other}"), + } +} + +fn parse_socks_addr(buf: &[u8]) -> Result<(SocketAddr, usize)> { + if buf.is_empty() { + bail!("missing SOCKS address"); + } + match buf[0] { + 1 => { + if buf.len() < 7 { + bail!("truncated IPv4 SOCKS address"); + } + let ip = Ipv4Addr::new(buf[1], buf[2], buf[3], buf[4]); + let port = u16::from_be_bytes([buf[5], buf[6]]); + Ok((SocketAddr::new(IpAddr::V4(ip), port), 7)) + } + 3 => { + if buf.len() < 2 { + bail!("truncated domain SOCKS address"); + } + let len = buf[1] as usize; + if buf.len() < 2 + len + 2 { + bail!("truncated domain SOCKS address"); + } + let domain = std::str::from_utf8(&buf[2..2 + len]).context("invalid SOCKS domain")?; + let port = u16::from_be_bytes([buf[2 + len], buf[3 + len]]); + Ok((resolve_server(domain, port)?, 2 + len + 2)) + } + 4 => { + if buf.len() < 19 { + bail!("truncated IPv6 SOCKS address"); + } + let mut ip = [0u8; 16]; + ip.copy_from_slice(&buf[1..17]); + let port = u16::from_be_bytes([buf[17], buf[18]]); + Ok((SocketAddr::new(IpAddr::V6(Ipv6Addr::from(ip)), port), 19)) + } + other => bail!("unsupported SOCKS address type {other}"), + } +} + +fn resolve_server(host: &str, port: u16) -> Result { + (host, port) + .to_socket_addrs() + .with_context(|| format!("failed to resolve {host}:{port}"))? + .next() + .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"), + } +} + +fn hex_lower(bytes: &[u8]) -> String { + const HEX: &[u8; 16] = b"0123456789abcdef"; + let mut out = String::with_capacity(bytes.len() * 2); + for byte in bytes { + out.push(HEX[(byte >> 4) as usize] as char); + out.push(HEX[(byte & 0x0f) as usize] as char); + } + out +} + +#[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"]); + } +} diff --git a/burrow/src/proxy_subscription.rs b/burrow/src/proxy_subscription.rs new file mode 100644 index 0000000..790f57a --- /dev/null +++ b/burrow/src/proxy_subscription.rs @@ -0,0 +1,927 @@ +use std::{collections::HashMap, time::Duration}; + +use anyhow::{anyhow, bail, Context, Result}; +use base64::{ + engine::general_purpose::{STANDARD, STANDARD_NO_PAD, URL_SAFE, URL_SAFE_NO_PAD}, + Engine as _, +}; +use reqwest::Url; +use serde::{Deserialize, Serialize}; +use serde_yaml::Value; + +const MAX_SUBSCRIPTION_BYTES: usize = 1024 * 1024; +const SUBSCRIPTION_USER_AGENT: &str = "mihomo/1.18.3"; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct ProxySubscriptionPayload { + pub name: String, + pub source: ProxySubscriptionSource, + pub detected_format: ProxySubscriptionFormat, + pub selected_ordinal: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub selected_name: Option, + pub nodes: Vec, + pub warnings: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct ProxySubscriptionSource { + pub url: String, +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "kebab-case")] +pub enum ProxySubscriptionFormat { + ClashYaml, + UriList, +} + +impl ProxySubscriptionFormat { + pub fn as_str(self) -> &'static str { + match self { + Self::ClashYaml => "clash-yaml", + Self::UriList => "uri-list", + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct ProxyNode { + pub ordinal: usize, + pub name: String, + pub protocol: ProxyProtocol, + pub server: String, + pub port: u16, + pub warnings: Vec, + pub config: ProxyNodeConfig, +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "lowercase")] +pub enum ProxyProtocol { + Trojan, + Shadowsocks, +} + +impl ProxyProtocol { + pub fn as_str(self) -> &'static str { + match self { + Self::Trojan => "trojan", + Self::Shadowsocks => "ss", + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(tag = "type", rename_all = "kebab-case")] +pub enum ProxyNodeConfig { + Trojan(TrojanNode), + Shadowsocks(ShadowsocksNode), +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct TrojanNode { + pub password: String, + pub sni: Option, + pub alpn: Vec, + #[serde(default)] + pub alpn_present: bool, + pub skip_cert_verify: bool, + pub network: TrojanNetwork, + #[serde(default = "default_true")] + pub udp: bool, + pub client_fingerprint: Option, + pub fingerprint: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "lowercase")] +pub enum TrojanNetwork { + Tcp, + Ws { + path: Option, + host: Option, + }, + Grpc { + service_name: Option, + }, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct ShadowsocksNode { + pub cipher: String, + pub password: String, + pub udp: bool, + pub udp_over_tcp: bool, + pub plugin: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct ShadowsocksPlugin { + pub name: String, + pub opts: HashMap, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ProxySubscriptionPreview { + pub suggested_name: String, + pub detected_format: ProxySubscriptionFormat, + pub nodes: Vec, + pub rejected: Vec, + pub warnings: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ProxyRejectedEntry { + pub ordinal: usize, + pub scheme: Option, + pub reason: String, +} + +#[derive(Debug, Deserialize)] +struct ProxySchema { + proxies: Option>>, +} + +pub async fn fetch_subscription(url: &str) -> Result { + let redacted_url = redacted_subscription_url(url); + let client = reqwest::Client::builder() + .user_agent(SUBSCRIPTION_USER_AGENT) + .redirect(reqwest::redirect::Policy::limited(5)) + .timeout(Duration::from_secs(20)) + .build()?; + let response = client + .get(url) + .send() + .await + .with_context(|| format!("failed to fetch {redacted_url}"))?; + let status = response.status(); + let body = response + .bytes() + .await + .with_context(|| format!("failed to read subscription body from {redacted_url}"))?; + if !status.is_success() { + bail!( + "subscription request failed for {}: HTTP {}{}", + redacted_url, + status.as_u16(), + subscription_error_body(&body) + ); + } + if body.len() > MAX_SUBSCRIPTION_BYTES { + bail!("subscription body exceeds {} bytes", MAX_SUBSCRIPTION_BYTES); + } + Ok(String::from_utf8_lossy(&body).into_owned()) +} + +fn redacted_subscription_url(raw: &str) -> String { + let Ok(mut url) = Url::parse(raw) else { + return "".to_owned(); + }; + + if !url.username().is_empty() { + let _ = url.set_username("REDACTED"); + } + if url.password().is_some() { + let _ = url.set_password(Some("REDACTED")); + } + if url.query().is_some() { + url.set_query(Some("REDACTED")); + } + if url.fragment().is_some() { + url.set_fragment(Some("REDACTED")); + } + url.to_string() +} + +fn subscription_error_body(body: &[u8]) -> String { + let excerpt = String::from_utf8_lossy(&body[..body.len().min(160)]) + .chars() + .map(|ch| if ch.is_control() { ' ' } else { ch }) + .collect::() + .trim() + .to_owned(); + if excerpt.is_empty() { + String::new() + } else { + format!(": {excerpt}") + } +} + +pub fn build_payload( + preview: ProxySubscriptionPreview, + source_url: &str, + name: Option, + selected_ordinal: Option, +) -> ProxySubscriptionPayload { + let selected_name = selected_ordinal.and_then(|ordinal| { + preview + .nodes + .iter() + .find(|node| node.ordinal == ordinal) + .map(|node| node.name.clone()) + }); + ProxySubscriptionPayload { + name: name + .and_then(|value| non_empty(value.trim()).map(ToOwned::to_owned)) + .unwrap_or_else(|| preview.suggested_name.clone()), + source: ProxySubscriptionSource { url: source_url.to_owned() }, + detected_format: preview.detected_format, + selected_ordinal, + selected_name, + nodes: preview.nodes, + warnings: preview.warnings, + } +} + +pub fn validate_payload(payload: &ProxySubscriptionPayload) -> Result<()> { + if payload.name.trim().is_empty() { + bail!("proxy subscription name must not be empty"); + } + if payload.nodes.is_empty() { + bail!("proxy subscription must include at least one compatible node"); + } + if let Some(selected) = payload.selected_ordinal { + if !payload.nodes.iter().any(|node| node.ordinal == selected) { + bail!("selected proxy node ordinal {selected} is not present in subscription"); + } + } + if let Some(selected) = &payload.selected_name { + if !payload.nodes.iter().any(|node| node.name == *selected) { + bail!("selected proxy node {selected} is not present in subscription"); + } + } + for node in &payload.nodes { + validate_node(node)?; + } + Ok(()) +} + +pub fn parse_subscription_body(body: &str, source_url: Option<&str>) -> ProxySubscriptionPreview { + match parse_yaml_subscription(body, source_url) { + Ok(preview) => preview, + Err(yaml_error) => { + let mut preview = parse_uri_subscription(body, source_url); + if preview.nodes.is_empty() && preview.rejected.is_empty() { + preview.warnings.push(format!( + "subscription is not Mihomo YAML and did not contain proxy URIs: {yaml_error}" + )); + } + preview + } + } +} + +pub fn summarize_payload(payload: &ProxySubscriptionPayload) -> String { + let selected = payload + .selected_name + .as_deref() + .and_then(|name| payload.nodes.iter().find(|node| node.name == name)) + .or_else(|| { + payload + .selected_ordinal + .and_then(|ordinal| payload.nodes.iter().find(|node| node.ordinal == ordinal)) + }) + .or_else(|| payload.nodes.first()); + match selected { + Some(node) => format!( + "{} - {} {}:{} ({} nodes)", + payload.name, + node.protocol.as_str(), + node.server, + node.port, + payload.nodes.len() + ), + None => format!("{} - no compatible nodes", payload.name), + } +} + +fn parse_yaml_subscription( + body: &str, + source_url: Option<&str>, +) -> Result { + let schema: ProxySchema = + serde_yaml::from_str(body).context("subscription is not proxy YAML")?; + let proxies = schema + .proxies + .ok_or_else(|| anyhow!("YAML subscription must include a `proxies` field"))?; + let mut nodes = Vec::new(); + let mut rejected = Vec::new(); + let mut warnings = Vec::new(); + + for (ordinal, mapping) in proxies.into_iter().enumerate() { + let scheme = yaml_string(&mapping, "type").map(str::to_owned); + match parse_yaml_proxy(ordinal, mapping) { + Ok(node) => nodes.push(node), + Err(error) => rejected.push(ProxyRejectedEntry { + ordinal, + scheme, + reason: error.to_string(), + }), + } + } + + if nodes.is_empty() { + warnings.push("no compatible Trojan or Shadowsocks nodes found in YAML".to_owned()); + } + + Ok(ProxySubscriptionPreview { + suggested_name: suggested_name(source_url), + detected_format: ProxySubscriptionFormat::ClashYaml, + nodes, + rejected, + warnings, + }) +} + +fn parse_uri_subscription(body: &str, source_url: Option<&str>) -> ProxySubscriptionPreview { + let decoded = decode_subscription_body(body).unwrap_or_else(|| body.to_owned()); + let mut nodes = Vec::new(); + let mut rejected = Vec::new(); + + for (ordinal, raw) in decoded + .lines() + .map(str::trim) + .filter(|line| !line.is_empty()) + .enumerate() + { + let scheme = raw + .split_once("://") + .map(|(scheme, _)| scheme.to_ascii_lowercase()); + let parsed = match scheme.as_deref() { + Some("trojan") => parse_trojan_uri(raw, ordinal), + Some("ss") => parse_shadowsocks_uri(raw, ordinal), + Some(other) => Err(anyhow!("unsupported proxy URI scheme {other}")), + None => Err(anyhow!("missing proxy URI scheme")), + }; + match parsed { + Ok(node) => nodes.push(node), + Err(error) => rejected.push(ProxyRejectedEntry { + ordinal, + scheme, + reason: error.to_string(), + }), + } + } + + ProxySubscriptionPreview { + suggested_name: suggested_name(source_url), + detected_format: ProxySubscriptionFormat::UriList, + nodes, + rejected, + warnings: Vec::new(), + } +} + +fn parse_yaml_proxy(ordinal: usize, mapping: HashMap) -> Result { + let proxy_type = required_yaml_string(&mapping, "type")?.to_ascii_lowercase(); + match proxy_type.as_str() { + "trojan" => parse_yaml_trojan(ordinal, &mapping), + "ss" | "shadowsocks" => parse_yaml_shadowsocks(ordinal, &mapping), + _ => bail!("unsupported proxy type {proxy_type}"), + } +} + +fn parse_yaml_trojan(ordinal: usize, mapping: &HashMap) -> Result { + let name = required_yaml_string(mapping, "name")?.to_owned(); + let server = required_yaml_string(mapping, "server")?.to_owned(); + let port = required_yaml_u16(mapping, "port")?; + let password = required_yaml_string(mapping, "password")?.to_owned(); + let alpn = yaml_string(mapping, "alpn") + .map(split_csv) + .or_else(|| yaml_string_vec(mapping, "alpn")) + .unwrap_or_default(); + let alpn_present = mapping.contains_key("alpn"); + let skip_cert_verify = yaml_bool(mapping, "skip-cert-verify").unwrap_or(false); + let network = match yaml_string(mapping, "network") + .unwrap_or("tcp") + .to_ascii_lowercase() + .as_str() + { + "tcp" | "" => TrojanNetwork::Tcp, + "ws" => { + let ws_opts = yaml_mapping(mapping, "ws-opts"); + let host = ws_opts + .as_ref() + .and_then(|opts| yaml_mapping(opts, "headers")) + .and_then(|headers| { + yaml_string(&headers, "Host") + .or_else(|| yaml_string(&headers, "host")) + .map(ToOwned::to_owned) + }); + TrojanNetwork::Ws { + path: ws_opts + .as_ref() + .and_then(|opts| yaml_string(opts, "path").map(ToOwned::to_owned)), + host, + } + } + "grpc" => { + let grpc_opts = yaml_mapping(mapping, "grpc-opts"); + TrojanNetwork::Grpc { + service_name: grpc_opts + .as_ref() + .and_then(|opts| yaml_string(opts, "grpc-service-name")) + .map(ToOwned::to_owned), + } + } + other => bail!("unsupported Trojan network {other}"), + }; + Ok(ProxyNode { + ordinal, + name, + protocol: ProxyProtocol::Trojan, + server, + port, + warnings: Vec::new(), + config: ProxyNodeConfig::Trojan(TrojanNode { + password, + sni: yaml_string(mapping, "sni").map(ToOwned::to_owned), + alpn, + alpn_present, + skip_cert_verify, + network, + udp: yaml_bool(mapping, "udp").unwrap_or(false), + client_fingerprint: yaml_string(mapping, "client-fingerprint").map(ToOwned::to_owned), + fingerprint: yaml_string(mapping, "fingerprint").map(ToOwned::to_owned), + }), + }) +} + +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 port = required_yaml_u16(mapping, "port")?; + let cipher = required_yaml_string(mapping, "cipher")?.to_owned(); + let password = required_yaml_string(mapping, "password")?.to_owned(); + Ok(ProxyNode { + ordinal, + name, + protocol: ProxyProtocol::Shadowsocks, + server, + port, + warnings: Vec::new(), + config: ProxyNodeConfig::Shadowsocks(ShadowsocksNode { + cipher, + password, + udp: yaml_bool(mapping, "udp").unwrap_or(true), + udp_over_tcp: yaml_bool(mapping, "udp-over-tcp").unwrap_or(false), + plugin: yaml_plugin(mapping), + }), + }) +} + +fn parse_trojan_uri(uri: &str, ordinal: usize) -> Result { + let url = Url::parse(uri).context("invalid Trojan URI")?; + let server = url + .host_str() + .map(ToOwned::to_owned) + .ok_or_else(|| anyhow!("missing Trojan server host"))?; + let port = url + .port() + .ok_or_else(|| anyhow!("missing Trojan server port"))?; + let password = percent_decode(url.username())?; + if password.is_empty() { + bail!("missing Trojan password"); + } + + let query = url.query_pairs().collect::>(); + let skip_cert_verify = query + .get("allowInsecure") + .or_else(|| query.get("skip-cert-verify")) + .or_else(|| query.get("skipCertVerify")) + .map(|value| is_truthy(value)) + .unwrap_or(false); + let network = query + .get("type") + .or_else(|| query.get("network")) + .map(|value| value.to_ascii_lowercase()) + .unwrap_or_else(|| "tcp".to_owned()); + let trojan_network = match network.as_str() { + "" | "tcp" => TrojanNetwork::Tcp, + "ws" => TrojanNetwork::Ws { + path: query + .get("path") + .and_then(|value| non_empty(value).map(ToOwned::to_owned)), + host: query + .get("host") + .or_else(|| query.get("Host")) + .and_then(|value| non_empty(value).map(ToOwned::to_owned)), + }, + "grpc" => TrojanNetwork::Grpc { + service_name: query + .get("serviceName") + .or_else(|| query.get("grpc-service-name")) + .and_then(|value| non_empty(value).map(ToOwned::to_owned)), + }, + other => bail!("unsupported Trojan network {other}"), + }; + let fingerprint = query + .get("fp") + .and_then(|value| non_empty(value).map(ToOwned::to_owned)) + .or_else(|| Some("chrome".to_owned())); + let alpn = query + .get("alpn") + .and_then(|value| non_empty(value).map(split_csv)) + .unwrap_or_default(); + let alpn_present = !alpn.is_empty(); + + Ok(ProxyNode { + ordinal, + name: uri_fragment_name(&url, "Proxy Node", ordinal)?, + protocol: ProxyProtocol::Trojan, + server, + port, + warnings: Vec::new(), + config: ProxyNodeConfig::Trojan(TrojanNode { + password, + sni: query + .get("sni") + .or_else(|| query.get("peer")) + .or_else(|| query.get("host")) + .and_then(|value| non_empty(value).map(ToOwned::to_owned)), + alpn, + alpn_present, + skip_cert_verify, + network: trojan_network, + udp: true, + client_fingerprint: fingerprint, + fingerprint: query + .get("pcs") + .and_then(|value| non_empty(value).map(ToOwned::to_owned)), + }), + }) +} + +fn parse_shadowsocks_uri(uri: &str, ordinal: usize) -> Result { + let mut url = Url::parse(uri).context("invalid Shadowsocks URI")?; + if url.port().is_none() { + let decoded_host = decode_base64_segment(url.host_str().unwrap_or_default()) + .context("invalid legacy Shadowsocks host encoding")?; + url = Url::parse(&format!("ss://{decoded_host}")) + .context("invalid decoded legacy Shadowsocks URI")?; + } + let server = url + .host_str() + .map(ToOwned::to_owned) + .ok_or_else(|| anyhow!("missing Shadowsocks server host"))?; + let port = url + .port() + .ok_or_else(|| anyhow!("missing Shadowsocks server port"))?; + let user = url.username(); + let (cipher, password) = match url.password() { + Some(password) => (user.to_owned(), password.to_owned()), + None => decode_base64_segment(user) + .ok() + .and_then(|decoded| { + decoded + .split_once(':') + .map(|(a, b)| (a.to_owned(), b.to_owned())) + }) + .ok_or_else(|| anyhow!("missing Shadowsocks cipher/password"))?, + }; + let query = url.query_pairs().collect::>(); + let udp_over_tcp = query + .get("udp-over-tcp") + .map(|value| value == "true") + .unwrap_or(false) + || query.get("uot").map(|value| value == "1").unwrap_or(false); + + Ok(ProxyNode { + ordinal, + name: uri_fragment_name(&url, "Proxy Node", ordinal)?, + protocol: ProxyProtocol::Shadowsocks, + server, + port, + warnings: Vec::new(), + config: ProxyNodeConfig::Shadowsocks(ShadowsocksNode { + cipher, + password, + udp: true, + udp_over_tcp, + plugin: query.get("plugin").and_then(|value| parse_ss_plugin(value)), + }), + }) +} + +fn validate_node(node: &ProxyNode) -> Result<()> { + if node.name.trim().is_empty() { + bail!("proxy node name must not be empty"); + } + if node.server.trim().is_empty() { + bail!("proxy node server must not be empty"); + } + match &node.config { + ProxyNodeConfig::Trojan(config) if config.password.is_empty() => { + bail!("Trojan node password must not be empty") + } + ProxyNodeConfig::Shadowsocks(config) + if config.cipher.is_empty() || config.password.is_empty() => + { + bail!("Shadowsocks node cipher and password must not be empty") + } + _ => Ok(()), + } +} + +fn yaml_string<'a>(mapping: &'a HashMap, key: &str) -> Option<&'a str> { + mapping.get(key).and_then(Value::as_str) +} + +fn yaml_string_vec(mapping: &HashMap, key: &str) -> Option> { + let values = mapping.get(key)?.as_sequence()?; + Some( + values + .iter() + .filter_map(Value::as_str) + .map(ToOwned::to_owned) + .collect(), + ) +} + +fn yaml_mapping<'a>( + mapping: &'a HashMap, + key: &str, +) -> Option> { + mapping.get(key)?.as_mapping().and_then(|map| { + map.iter() + .map(|(key, value)| Some((key.as_str()?.to_owned(), value.clone()))) + .collect::>>() + }) +} + +fn required_yaml_string<'a>(mapping: &'a HashMap, key: &str) -> Result<&'a str> { + yaml_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}`")); + } + let value = required_yaml_string(mapping, key)?; + value + .parse::() + .with_context(|| format!("invalid `{key}`")) +} + +fn yaml_bool(mapping: &HashMap, key: &str) -> Option { + mapping + .get(key) + .and_then(Value::as_bool) + .or_else(|| yaml_string(mapping, key).map(is_truthy)) +} + +fn yaml_plugin(mapping: &HashMap) -> Option { + let name = yaml_string(mapping, "plugin")?.to_owned(); + let opts = 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())) + }) + }) + .collect() + }) + .unwrap_or_default(); + Some(ShadowsocksPlugin { name, opts }) +} + +fn parse_ss_plugin(plugin: &str) -> Option { + 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()); + } + } + non_empty(name).map(|name| ShadowsocksPlugin { name: name.to_owned(), opts }) +} + +fn decode_subscription_body(body: &str) -> Option { + let compact: String = body.chars().filter(|ch| !ch.is_whitespace()).collect(); + decode_base64_segment(&compact).ok() +} + +fn decode_base64_segment(input: &str) -> Result { + if let Ok(decoded) = STANDARD_NO_PAD.decode(input.as_bytes()) { + return String::from_utf8(decoded).context("base64 decoded text is not valid UTF-8"); + } + if let Ok(decoded) = STANDARD.decode(input.as_bytes()) { + return String::from_utf8(decoded).context("base64 decoded text is not valid UTF-8"); + } + if let Ok(decoded) = URL_SAFE_NO_PAD.decode(input.as_bytes()) { + return String::from_utf8(decoded).context("base64 decoded text is not valid UTF-8"); + } + if let Ok(decoded) = URL_SAFE.decode(input.as_bytes()) { + return String::from_utf8(decoded).context("base64 decoded text is not valid UTF-8"); + } + bail!("invalid base64 text") +} + +fn uri_fragment_name(url: &Url, fallback: &str, ordinal: usize) -> Result { + Ok(url + .fragment() + .map(percent_decode) + .transpose()? + .and_then(|fragment| non_empty(&fragment).map(ToOwned::to_owned)) + .or_else(|| non_empty(url.host_str().unwrap_or_default()).map(ToOwned::to_owned)) + .unwrap_or_else(|| format!("{fallback} {}", ordinal + 1))) +} + +fn suggested_name(source_url: Option<&str>) -> String { + source_url + .and_then(|url| Url::parse(url).ok()) + .and_then(|url| url.host_str().map(ToOwned::to_owned)) + .unwrap_or_else(|| "Proxy Subscription".to_owned()) +} + +fn split_csv(value: &str) -> Vec { + value + .split(',') + .map(str::trim) + .filter(|value| !value.is_empty()) + .map(ToOwned::to_owned) + .collect() +} + +fn is_truthy(value: impl AsRef) -> bool { + matches!( + value.as_ref().to_ascii_lowercase().as_str(), + "1" | "true" | "yes" | "y" + ) +} + +fn default_true() -> bool { + true +} + +fn non_empty(value: &str) -> Option<&str> { + let value = value.trim(); + (!value.is_empty()).then_some(value) +} + +fn percent_decode(input: &str) -> Result { + let mut out = Vec::with_capacity(input.len()); + let bytes = input.as_bytes(); + let mut i = 0; + while i < bytes.len() { + if bytes[i] == b'%' { + if i + 2 >= bytes.len() { + bail!("invalid percent encoding"); + } + let hi = hex_value(bytes[i + 1]).ok_or_else(|| anyhow!("invalid percent encoding"))?; + let lo = hex_value(bytes[i + 2]).ok_or_else(|| anyhow!("invalid percent encoding"))?; + out.push((hi << 4) | lo); + i += 3; + } else { + out.push(bytes[i]); + i += 1; + } + } + String::from_utf8(out).context("percent-decoded text is not valid UTF-8") +} + +fn hex_value(byte: u8) -> Option { + match byte { + b'0'..=b'9' => Some(byte - b'0'), + b'a'..=b'f' => Some(byte - b'a' + 10), + b'A'..=b'F' => Some(byte - b'A' + 10), + _ => None, + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[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", + ); + 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(), 2); + assert_eq!(preview.rejected.len(), 0); + assert_eq!(preview.nodes[0].name, "US Trojan"); + assert!(preview.nodes[0].warnings.is_empty()); + let ProxyNodeConfig::Trojan(trojan) = &preview.nodes[0].config else { + panic!("expected Trojan node"); + }; + assert!(!trojan.alpn_present); + assert_eq!(trojan_alpn_for_test(trojan), Vec::::new()); + assert!(trojan.udp); + assert_eq!(preview.nodes[1].protocol, ProxyProtocol::Shadowsocks); + } + + #[test] + fn parses_mihomo_yaml_proxies() { + let preview = parse_subscription_body( + r#" +proxies: + - name: trojan-ws + type: trojan + server: example.com + port: 443 + password: secret + sni: edge.example.com + network: ws + ws-opts: + path: /ws + headers: + Host: edge.example.com + - name: ss + type: ss + server: example.net + port: 8388 + cipher: aes-128-gcm + password: pass +"#, + None, + ); + assert_eq!(preview.detected_format, ProxySubscriptionFormat::ClashYaml); + assert_eq!(preview.nodes.len(), 2); + 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); + } + + #[test] + fn tracks_explicit_trojan_alpn_presence() { + let preview = parse_subscription_body( + r#" +proxies: + - name: trojan-empty-alpn + type: trojan + server: example.com + port: 443 + password: secret + alpn: [] + - name: trojan-custom-alpn + type: trojan + server: example.net + port: 443 + password: secret + alpn: + - h3 +"#, + None, + ); + let ProxyNodeConfig::Trojan(empty_alpn) = &preview.nodes[0].config else { + panic!("expected Trojan node"); + }; + assert!(empty_alpn.alpn_present); + assert!(empty_alpn.alpn.is_empty()); + + let ProxyNodeConfig::Trojan(custom_alpn) = &preview.nodes[1].config else { + panic!("expected Trojan node"); + }; + assert!(custom_alpn.alpn_present); + assert_eq!(custom_alpn.alpn, vec!["h3"]); + } + + #[test] + fn build_payload_records_selected_node_name() { + let preview = parse_subscription_body( + "ss://YWVzLTEyOC1nY206cGFzcw@example.net:8388#SS%20Node\n", + Some("https://sub.example/path"), + ); + let payload = build_payload( + preview, + "https://sub.example/path", + Some("sub".to_owned()), + Some(0), + ); + + assert_eq!(payload.selected_ordinal, Some(0)); + assert_eq!(payload.selected_name.as_deref(), Some("SS Node")); + } + + #[test] + fn redacts_subscription_url_secrets_for_errors() { + let redacted = redacted_subscription_url( + "https://user:password@sub.example/path?token=secret&user=alice#fragment", + ); + + assert_eq!( + redacted, + "https://REDACTED:REDACTED@sub.example/path?REDACTED#REDACTED" + ); + assert!(!redacted.contains("password")); + assert!(!redacted.contains("token=secret")); + assert!(!redacted.contains("fragment")); + } + + fn trojan_alpn_for_test(node: &TrojanNode) -> Vec { + node.alpn.clone() + } +} diff --git a/burrow/src/tracing.rs b/burrow/src/tracing.rs index 8a245ef..fb88100 100644 --- a/burrow/src/tracing.rs +++ b/burrow/src/tracing.rs @@ -1,7 +1,13 @@ -use std::sync::Once; +use std::{ + fs::{File, OpenOptions}, + io::{self, Write}, + path::PathBuf, + sync::{Arc, Mutex, Once}, +}; use tracing::{error, info}; use tracing_subscriber::{ + fmt::writer::MakeWriter, layer::{Layer, SubscriberExt}, EnvFilter, Registry, }; @@ -20,14 +26,30 @@ pub fn initialize() { .with_writer(std::io::stderr) .with_line_number(true) .compact() - .with_filter(EnvFilter::from_default_env()) + .with_filter(env_filter()) + }; + + let make_file = || { + tracing_log_file().map(|writer| { + tracing_subscriber::fmt::layer() + .with_ansi(false) + .with_level(true) + .with_line_number(true) + .compact() + .with_writer(writer) + .with_filter(env_filter()) + }) }; #[cfg(target_os = "windows")] let subscriber = { let system_log = Some(tracing_subscriber::fmt::layer()); - let stderr = (console::user_attended_stderr() || system_log.is_none()).then(make_stderr); - Registry::default().with(stderr).with(system_log) + let stderr = + (console::user_attended_stderr() || system_log.is_none()).then(make_stderr); + Registry::default() + .with(stderr) + .with(system_log) + .with(make_file()) }; #[cfg(target_os = "linux")] @@ -41,8 +63,12 @@ pub fn initialize() { None } }; - let stderr = (console::user_attended_stderr() || system_log.is_none()).then(make_stderr); - Registry::default().with(stderr).with(system_log) + let stderr = + (console::user_attended_stderr() || system_log.is_none()).then(make_stderr); + Registry::default() + .with(stderr) + .with(system_log) + .with(make_file()) }; #[cfg(target_os = "macos")] @@ -54,15 +80,20 @@ pub fn initialize() { std::env::var("BURROW_ENABLE_OSLOG").as_deref(), Ok("1" | "true" | "TRUE" | "yes" | "YES") ); - let system_log = enable_oslog.then(|| { - tracing_oslog::OsLogger::new("com.hackclub.burrow", "tracing") - }); - let stderr = (console::user_attended_stderr() || system_log.is_none()).then(make_stderr); - Registry::default().with(stderr).with(system_log) + let system_log = enable_oslog + .then(|| tracing_oslog::OsLogger::new("com.hackclub.burrow", "tracing")); + let stderr = + (console::user_attended_stderr() || system_log.is_none()).then(make_stderr); + Registry::default() + .with(stderr) + .with(system_log) + .with(make_file()) }; #[cfg(not(any(target_os = "windows", target_os = "linux", target_os = "macos")))] - let subscriber = Registry::default().with(Some(make_stderr())); + let subscriber = Registry::default() + .with(Some(make_stderr())) + .with(make_file()); #[cfg(feature = "tokio-console")] let subscriber = subscriber.with( @@ -80,3 +111,50 @@ pub fn initialize() { info!("Initialized logging") }); } + +fn env_filter() -> EnvFilter { + EnvFilter::try_from_env("BURROW_LOG") + .or_else(|_| EnvFilter::try_from_default_env()) + .unwrap_or_else(|_| EnvFilter::new("info")) +} + +fn tracing_log_file() -> Option { + let path = std::env::var_os("BURROW_LOG_FILE").map(PathBuf::from)?; + let file = match OpenOptions::new().create(true).append(true).open(&path) { + Ok(file) => file, + Err(err) => { + error!(path = %path.display(), error = %err, "failed to open Burrow log file"); + return None; + } + }; + Some(SharedLogWriter { + file: Arc::new(Mutex::new(file)), + }) +} + +#[derive(Clone)] +struct SharedLogWriter { + file: Arc>, +} + +struct SharedLogGuard { + file: Arc>, +} + +impl<'a> MakeWriter<'a> for SharedLogWriter { + type Writer = SharedLogGuard; + + fn make_writer(&'a self) -> Self::Writer { + SharedLogGuard { file: self.file.clone() } + } +} + +impl Write for SharedLogGuard { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.file.lock().unwrap().write(buf) + } + + fn flush(&mut self) -> io::Result<()> { + self.file.lock().unwrap().flush() + } +} diff --git a/docs/trojan-mihomo-discrepancies.md b/docs/trojan-mihomo-discrepancies.md new file mode 100644 index 0000000..606fcbb --- /dev/null +++ b/docs/trojan-mihomo-discrepancies.md @@ -0,0 +1,601 @@ +# Burrow vs Mihomo Trojan Implementation Discrepancies + +Date: 2026-05-31 + +Scope: code-path comparison between the local Burrow checkout at +`/Users/jettchen/dev/burrow` and the local Mihomo checkout at +`/Users/jettchen/dev/vpn-ref/mihomo`. This is not based on current live system +network state. + +## Executive Summary + +The most important differences for the stored Hong Kong Trojan node are: + +1. Mihomo uses `client-fingerprint: chrome` for Trojan URIs when no `fp` query is + present and switches to uTLS when that fingerprint is configured. Burrow + stores `client_fingerprint` but the Trojan runtime does not use it. +2. Mihomo distinguishes absent ALPN from explicitly empty ALPN. For Trojan TCP, + absent ALPN defaults to `["h2", "http/1.1"]`; explicitly empty ALPN remains + empty. Burrow now carries an `alpn_present` marker for new imports and treats + legacy missing metadata as absent. +3. Mihomo has a dedicated proxy-server DNS path (`proxy-server-nameserver` / + `ProxyServerHostResolver`). Burrow resolves proxy server names with the + process/system resolver through `ToSocketAddrs`. +4. Mihomo preserves and reconstructs destination hostname metadata in fake-IP + and mapping modes. Burrow's packet runtime receives `SocketAddr` values from + its userspace stack and sends IP-form SOCKS addresses to Trojan, not domain + names. +5. Mihomo supports Trojan `tcp`, `ws`, and `grpc` runtimes, plus additional TLS + features such as ECH, REALITY, pinned certificate fingerprint, and optional + Trojan-over-Shadowsocks wrapping. Burrow parses some of that metadata but the + packet runtime only supports Trojan TCP. +6. Mihomo's dial path has timeout, retry, dual-stack fallback, optional parallel + dialing, interface/routing-mark controls, DNS cache/fallback, and UDP + fragmentation behavior. Burrow now matches Trojan UDP fragmentation, but its + dial path remains a much narrower serial dialer plus a fixed packet bridge. + +Implementation note: + +- `BEP-0011` and the current runtime patch address ALPN absence/defaulting, + Trojan UDP import/runtime gating, Trojan UDP fragmentation, and certificate + fingerprint pinning. +- Burrow still does not implement Mihomo's uTLS/browser ClientHello + impersonation, ws/grpc Trojan transports, REALITY/ECH, proxy-server DNS plane, + fake-IP metadata, or policy/rule pipeline. + +## Stored Node Context + +Current Burrow database state inspected from the app group DB: + +```text +network type: ProxySubscription +subscription: bitz +selected ordinal: 2 +selected name: Hong Kong Guangdong BGP 2 +protocol: trojan +server: ce1081d2-df04-48d7-80bf-46b8c50b4f33.bnsepserv.com +port: 32445 +network: tcp +sni: pull-flv-q1-admin.douyincdn.com +skip_cert_verify: true +alpn: [] +client_fingerprint: chrome +fingerprint: null +``` + +Evidence: + +- Burrow stores Trojan node fields in `TrojanNode`: `password`, `sni`, `alpn`, + `alpn_present`, `skip_cert_verify`, `network`, `udp`, + `client_fingerprint`, and `fingerprint` + (`burrow/src/proxy_subscription.rs:82`). +- The runtime `TrojanOutbound` carries endpoint, password, SNI, ALPN, skip-cert, + UDP enablement, and parsed certificate fingerprint. It still does not apply + `client_fingerprint`. + +## 1. Subscription and Config Parsing + +### Supported subscription shapes + +Mihomo: + +- Parses individual proxies through a generic adapter parser + (`adapter/parser.go:11`). +- For `type: trojan`, decodes the full `TrojanOption` with the common structure + decoder and constructs `NewTrojan` (`adapter/parser.go:79`). +- Supports proxy providers with `file`, `http`, and `inline` vehicles + (`adapter/provider/parser.go:77`). +- Provider schemas include filter, exclude-filter, exclude-type, dialer-proxy, + size-limit, headers, override, payload, health-check, and refresh interval + (`adapter/provider/parser.go:28`). + +Burrow: + +- Parses either a top-level YAML `proxies` field or a plaintext/base64 URI list + (`burrow/src/proxy_subscription.rs:235`, `burrow/src/proxy_subscription.rs:274`, + `burrow/src/proxy_subscription.rs:312`). +- Does not parse Mihomo `proxy-providers`, `proxy-groups`, provider refresh + options, health-checks, overrides, or group selectors. +- For URI-list content, only `trojan://` and `ss://` are accepted; other schemes + become rejected entries (`burrow/src/proxy_subscription.rs:323`). + +Impact: + +- Burrow can import the selected node, but it does not preserve Mihomo's full + provider/group behavior. Selection is Burrow-owned, not Mihomo group semantics. + +### URI conversion + +Mihomo: + +- Trojan URI conversion maps fragment to name, host/port/user to server/port/ + password, `allowInsecure` to `skip-cert-verify`, optional `sni`, optional + `alpn`, optional `type`, ws/grpc options, `pcs` to certificate fingerprint, + and `fp` to `client-fingerprint` (`common/convert/converter.go:149`). +- The URI converter also sets `udp: true` for Trojan URI imports + (`common/convert/converter.go:162`). +- If `fp` is absent, Mihomo sets `client-fingerprint` to `chrome` + (`common/convert/converter.go:198`). + +Burrow: + +- Maps similar basics and also defaults missing `fp` to `chrome` + (`burrow/src/proxy_subscription.rs:491`). +- Stores `pcs` as `fingerprint` (`burrow/src/proxy_subscription.rs:517`). +- Stores ALPN with an `alpn_present` marker, so new imports can distinguish + missing ALPN from explicit empty ALPN (`burrow/src/proxy_subscription.rs:83`). + +Discrepancy: + +- Mihomo can distinguish "ALPN field absent" from "ALPN field present but empty" + at the decoded option/runtime layer because the option slice can be nil or + non-nil. Burrow now records that distinction for new imports. +- Mihomo URI conversion only sets `alpn` when the URI query value is non-empty; + this is still different from Burrow because Burrow imports absent URI ALPN as + a concrete empty vector that the runtime later treats as intentional. +- Mihomo carries Trojan's `udp` option through the base outbound model. Burrow + now stores the Trojan `udp` field and gates Trojan UDP sessions on it. + +## 2. Runtime Transport Support + +Mihomo: + +- `TrojanOption.Network` supports default TCP, `ws`, and `grpc` + (`adapter/outbound/trojan.go:51`). +- Runtime branches: + - `ws`: wraps the TCP connection in websocket over TLS + (`adapter/outbound/trojan.go:67`). + - `grpc`: uses gun transport and a gRPC client pool + (`adapter/outbound/trojan.go:175`, `adapter/outbound/trojan.go:298`). + - default TCP: TLS, then Trojan header (`adapter/outbound/trojan.go:119`). +- Trojan options also include `ECHOpts`, `RealityOpts`, `SSOpts`, and + `ClientFingerprint` (`adapter/outbound/trojan.go:52`). +- If `ss-opts.enabled` is set, Mihomo wraps Trojan streams in Shadowsocks using + the configured method/password, defaulting the method to `AES-128-GCM` + (`adapter/outbound/trojan.go:284`). + +Burrow: + +- Parser represents `Tcp`, `Ws`, and `Grpc` (`burrow/src/proxy_subscription.rs:93`). +- Runtime rejects any Trojan network except `Tcp` + (`burrow/src/proxy_runtime.rs:553`). +- Runtime preview can mark unsupported transports via `runtime_support_error` + (`burrow/src/proxy_runtime.rs:279`). + +Discrepancy: + +- Burrow parses ws/grpc metadata but cannot run ws/grpc Trojan nodes. +- Mihomo can run ws and grpc Trojan nodes. +- Burrow has no Trojan-over-Shadowsocks wrapper equivalent for Mihomo + `ss-opts`. +- Burrow has no Trojan REALITY or ECH runtime support. + +## 3. ALPN Behavior + +Mihomo: + +- Trojan TCP default ALPN is `["h2", "http/1.1"]` + (`transport/trojan/trojan.go:23`). +- For default TCP, Mihomo starts with that default, then overrides only if + `option.ALPN != nil` (`adapter/outbound/trojan.go:122`). +- For websocket, default ALPN is `["http/1.1"]` and is overridden only if + `option.ALPN != nil` (`adapter/outbound/trojan.go:95`). +- For grpc, TLS `NextProtos` is fixed to `["h2"]` + (`adapter/outbound/trojan.go:307`). + +Burrow: + +- Runtime ALPN uses Mihomo's TCP default when `alpn_present` is false, and uses + `TrojanNode.alpn` exactly when `alpn_present` is true + (`burrow/src/proxy_runtime.rs:695`). +- TLS config writes those strings directly into rustls `alpn_protocols` + (`burrow/src/proxy_runtime.rs:820`). +- Parser stores missing ALPN as `alpn_present: false`; legacy stored nodes that + lack `alpn_present` deserialize the same way. + +Discrepancy: + +- For a Trojan URI that omits `alpn`, Mihomo sends `h2,http/1.1`; Burrow now + applies the same default through the presence marker. +- Explicit empty ALPN is possible through Mihomo's decoded YAML/options path, + but not through its URI converter because empty URI `alpn` is not emitted. + +Likely relevance: + +- The selected node has `alpn: []` in Burrow. If the subscription omitted ALPN, + Mihomo would use the default ALPN list, while Burrow now cannot know that and + sends none. + +## 4. Client Fingerprint and uTLS + +Mihomo: + +- Trojan options include `ClientFingerprint` (`adapter/outbound/trojan.go:57`). +- URI conversion defaults missing `fp` to `chrome` + (`common/convert/converter.go:198`). +- TLS connection code checks `GetFingerprint`; when recognized, it converts the + TLS config to a uTLS config and creates a uTLS client + (`transport/vmess/tls.go:46`). +- uTLS preserves `NextProtos`, `ServerName`, cert settings, and version bounds + from the config (`component/tls/utls.go:175`). +- `GetFingerprint` falls back to a global fingerprint if the node omits one, + returns no uTLS for empty/`none`, supports `random`, and recognizes browser + profiles such as `chrome`, `firefox`, `safari`, `ios`, `android`, `edge`, + `360`, and `qq` (`component/tls/utls.go:42`, `component/tls/utls.go:65`, + `component/tls/utls.go:81`). +- For websocket, Mihomo forces the uTLS ALPN extension to `http/1.1` + (`component/tls/utls.go:258`). + +Burrow: + +- Parser stores `client_fingerprint` (`burrow/src/proxy_subscription.rs:89`). +- Runtime logs it when present, but does not use it to build a uTLS ClientHello. +- Runtime uses OpenSSL on macOS and rustls/tokio-rustls elsewhere, not Mihomo's + uTLS implementation. + +Discrepancy: + +- Mihomo sends a Chrome-like TLS ClientHello for this selected node + (`client_fingerprint: chrome`). +- Burrow sends the platform TLS stack's ClientHello. + +Likely relevance: + +- Many Trojan subscriptions use `fp=chrome` or Mihomo's default `chrome` because + the server or fronting path expects that TLS fingerprint. Burrow ignoring it is + a high-probability compatibility gap. + +## 5. Certificate Verification and Pinning + +Mihomo: + +- Trojan options include `SkipCertVerify`, `Fingerprint`, `Certificate`, and + `PrivateKey` (`adapter/outbound/trojan.go:46`). +- TLS setup passes those into `ca.GetTLSConfig` + (`adapter/outbound/trojan.go:101`, `transport/vmess/tls.go:27`). +- URI conversion maps `pcs` to `fingerprint` + (`common/convert/converter.go:204`). +- Mihomo treats `fingerprint` as SHA-256 certificate pinning. Browser names such + as `chrome` are rejected there with an instruction to use + `client-fingerprint` instead (`component/ca/fingerprint.go:13`). +- A pinned fingerprint may match any certificate in the chain; if it matches a + non-leaf certificate, Mihomo verifies the leaf against that pinned certificate + as a trusted root (`component/ca/fingerprint.go:29`). +- `GetTLSConfig` installs `VerifyConnection` and sets `InsecureSkipVerify` when + pinning is enabled, so Go's default verifier is bypassed in favor of the pin + verifier (`component/ca/config.go:100`). +- Mihomo supports TLS client certificates through `certificate`/`private-key`, + including inline material, safe file paths, file watching, or generated random + key material when both are empty (`component/ca/config.go:114`, + `component/ca/keypair.go:25`). + +Burrow: + +- Parser stores `fingerprint` (`burrow/src/proxy_subscription.rs:90`). +- Runtime parses `fingerprint` as a SHA-256 certificate pin and checks the + presented certificate chain against it (`burrow/src/proxy_runtime.rs:865`). +- Runtime root store is webpki roots; `skip_cert_verify` installs a verifier + that accepts the certificate/signature checks (`burrow/src/proxy_runtime.rs:820`, + `burrow/src/proxy_runtime.rs:835`). + +Discrepancy: + +- Mihomo supports pinned certificate fingerprint and custom certificate material. +- Burrow now supports SHA-256 certificate fingerprint pinning, normal root + validation, or all-cert skip verification. +- Burrow has no client certificate fields for Trojan. + +## 6. SNI Behavior + +Mihomo: + +- `NewTrojan` defaults empty SNI to the server host + (`adapter/outbound/trojan.go:251`). +- Trojan URI conversion sets SNI only when the `sni` query is present + (`common/convert/converter.go:168`). +- TLS config uses `option.SNI` as host/server name + (`adapter/outbound/trojan.go:126`). + +Burrow: + +- Parser accepts URI `sni`, `peer`, or `host` as SNI + (`burrow/src/proxy_subscription.rs:505`). +- Runtime defaults SNI to node server if absent + (`burrow/src/proxy_runtime.rs:561`). + +Discrepancy: + +- Burrow accepts more SNI aliases in URI parsing than Mihomo's converter path. +- Runtime defaulting is effectively aligned for TCP. + +## 7. Trojan Header and Password Hash + +Mihomo: + +- Password is converted with `trojan.Key` (`adapter/outbound/trojan.go:269`). +- Trojan header writes hex password, CRLF, command byte, SOCKS address, CRLF + (`transport/trojan/trojan.go:39`). +- TCP command is `1`, UDP command is `3` + (`transport/trojan/trojan.go:31`). + +Burrow: + +- Password hash is SHA-224 hex (`burrow/src/proxy_runtime.rs:903`). +- Header writes hash, CRLF, command byte, SOCKS address, CRLF as a single + coalesced buffer, matching Mihomo's one-write `trojan.WriteHeader` behavior. +- TCP uses command `0x01`; UDP uses `0x03` + (`burrow/src/proxy_runtime.rs:583`, `burrow/src/proxy_runtime.rs:604`). + +Discrepancy: + +- No remaining framing discrepancy for basic TCP/UDP header shape. A live + provider check showed that splitting the Trojan TCP header across multiple TLS + writes could produce EOF before any target response bytes; Burrow now + coalesces the TCP header before writing it. + +## 8. Destination Address and Hostname Metadata + +Mihomo: + +- `serializesSocksAddr` writes a domain-form SOCKS address when + `metadata.AddrType()` is domain (`adapter/outbound/util.go:15`). +- Fake-IP and mapping preprocessing can translate a destination fake IP back to + hostname, clear `DstIP`, and mark `DNSFakeIP` + (`tunnel/tunnel.go:287`). +- If fake-IP metadata is missing, Mihomo may sniff TCP to recover a domain + (`tunnel/tunnel.go:511`). + +Burrow: + +- Netstack TCP accept yields `remote_addr: SocketAddr`; Burrow now checks its + daemon-owned fake-IP DNS cache and writes a domain-form SOCKS address when the + accepted destination IP maps back to a hostname. It falls back to IP-form when + no hostname is known. +- UDP sessions key on local/remote `SocketAddr`. DNS queries to Burrow's + daemon-owned resolver are answered locally before they become proxy UDP flows. + Other UDP flows still use socket-address metadata. +- Burrow can parse a domain-form SOCKS address in Trojan UDP responses, but it + resolves that domain immediately through the process/system resolver and + returns a `SocketAddr` (`burrow/src/proxy_runtime.rs:1187`). +- Burrow has a basic fake-IP map for A queries answered by the daemon resolver, + but no TCP sniffer or rules engine. + +Discrepancy: + +- Mihomo can preserve domain targets through fake-IP/mapping paths. +- Burrow now preserves DNS-backed domain targets for TCP, but only for domains + resolved through its daemon-owned A-query fake DNS path. + +Compatibility impact: + +- For ordinary HTTPS this may still work because the application supplies TLS + SNI to the target site. It breaks or weakens Mihomo-style rule selection, + fake-IP DNS, and any proxy behavior that needs the destination hostname at the + outbound layer. + +## 9. Proxy Server DNS Resolution + +Mihomo: + +- The generic dialer resolves proxy server hostnames through + `resolver.ProxyServerHostResolver` by default (`component/dialer/dialer.go:358`). +- `ProxyServerHostResolver` is wired from `proxy-server-nameserver` when present, + otherwise from the normal resolver (`hub/executor/executor.go:290`). +- Resolver lookup checks hosts first, IP literals second, then configured + resolver or system resolver fallback (`component/resolver/resolver.go:154`). +- DNS exchange uses cache, singleflight, retry monitor, main/fallback + nameservers, and policy routing (`dns/resolver.go:150`, + `dns/resolver.go:220`, `dns/resolver.go:298`). +- Resolver caches are TTL based, can serve stale records with background refresh, + and use a default max size of 4096 (`dns/resolver.go:150`, + `dns/resolver.go:452`, `dns/util.go:48`). +- The system resolver refreshes `/etc/resolv.conf` periodically and can fall + back to public DNS servers if no system DNS clients are available + (`dns/system_common.go:15`, `dns/system.go:68`). + +Burrow: + +- Proxy endpoint resolution starts with `(host, port).to_socket_addrs()` via the + process/system resolver. +- The current workspace caches resolved proxy server addresses in + `ProxyServerEndpoint` when the outbound is configured + (`burrow/src/proxy_runtime.rs:1263`). +- If system DNS returns only `198.18.0.0/15` fake-IP addresses, Burrow falls + back to direct DNS using physical/public DNS servers and excludes the resolved + real proxy-server host routes from the tunnel. +- There is no user-configurable proxy-server resolver equivalent. +- There is no DNS TTL refresh or resolver cache for proxy server hosts beyond + the current cached address list. + +Discrepancy: + +- Mihomo has a first-class proxy-server DNS plane; Burrow does not. +- Mihomo has richer policy and cache controls for proxy-server DNS. Burrow now + has a narrower fake-IP fallback that avoids recursive proxy-server dials for + observed `198.18.0.0/15` answers. + +## 10. Dialing Strategy, Interface Binding, and Socket Options + +Mihomo: + +- Dialer parses all resolved IPs, normalizes IPv4-in-IPv6, and supports IPv4/ + IPv6 preference (`component/dialer/dialer.go:358`). +- It can dial serially, in dual-stack fallback mode, or in parallel depending on + `tcpConcurrent` (`component/dialer/dialer.go:58`, + `component/dialer/dialer.go:207`). +- Dialer applies keepalive and MPTCP settings (`component/dialer/dialer.go:139`). +- Dialer supports explicit `interface-name`, default interface, interface + finder, routing mark, TFO, MPTCP, and custom net dialers + (`component/dialer/dialer.go:143`, `adapter/outbound/base.go:145`). +- The dialer option model includes `interfaceName`, `fallbackBind`, + `routingMark`, preferred IP family, TFO, MPTCP, resolver, and custom net + dialer (`component/dialer/options.go:33`). +- Darwin interface binding uses `IP_BOUND_IF` / `IPV6_BOUND_IF` + (`component/dialer/bind_darwin.go:14`). +- Linux interface binding uses bind-to-device, and Linux routing marks use + `SO_MARK` (`component/dialer/bind_linux.go:12`, + `component/dialer/mark_linux.go:20`). +- Mihomo wraps outbound connection creation in a 5 second context and a retry + loop with up to 10 attempts, stopping early for non-retryable DNS/reject + errors (`tunnel/tunnel.go:560`, `tunnel/tunnel.go:702`). +- The dialer has a 300 ms dual-stack fallback window and optional parallel dial + mode (`component/dialer/dialer.go:25`, `component/dialer/dialer.go:58`, + `component/dialer/dialer.go:207`). + +Burrow: + +- Dials cached addresses sequentially (`burrow/src/proxy_runtime.rs:1294`). +- No explicit TCP connect timeout wrapper is applied in this path. +- No TFO, MPTCP, routing mark, custom dialer, or configurable interface name. +- On Apple, Burrow chooses a default physical interface by scanning interfaces, + preferring `en*` and skipping `utun`, loopback, bridge, awdl, etc. + (`burrow/src/proxy_runtime.rs:1501`). +- Burrow skips physical-interface binding for loopback proxy-server addresses so + controlled local proxy runtime tests can use `127.0.0.1`. +- On non-Apple, `bind_proxy_outbound_socket` is a no-op + (`burrow/src/proxy_runtime.rs:1495`). + +Discrepancy: + +- Mihomo has a mature, configurable dialer. Burrow has a narrow Apple-focused + physical-interface bind path and a sequential dial loop. +- Burrow has no high-level per-flow retry loop comparable to Mihomo's tunnel + retry. + +## 11. UDP Behavior + +Mihomo: + +- Trojan supports UDP through `ListenPacketContext` + (`adapter/outbound/trojan.go:202`). +- UDP exposure is gated by the node's `udp` option through `Base.SupportUDP` + (`adapter/outbound/trojan.go:50`, `adapter/outbound/base.go:105`). +- Before UDP, it resolves destination metadata if needed + (`adapter/outbound/trojan.go:203`, `adapter/outbound/base.go:178`). +- `SupportUOT` returns true (`adapter/outbound/trojan.go:225`). +- UDP packet handling maintains mappings from origin metadata to target address + (`tunnel/connection.go:70`). +- Trojan UDP packet writes fragment payloads larger than 8192 bytes into + multiple Trojan UDP frames (`transport/trojan/trojan.go:54`, + `transport/trojan/trojan.go:66`). + +Burrow: + +- UDP is enabled in the userspace netstack (`burrow/src/proxy_runtime.rs:379`). +- Burrow's import/runtime model has no Trojan `udp` enable flag, so the runtime + does not mirror Mihomo's per-node UDP gating. +- UDP sessions are keyed by local/remote socket address and use a channel per + flow (`burrow/src/proxy_runtime.rs:516`). +- Trojan UDP opens a TLS connection per UDP flow, writes a Trojan UDP header, + and relays Trojan UDP packets over that stream + (`burrow/src/proxy_runtime.rs:598`). +- UDP flow idle timeout is 30 seconds (`burrow/src/proxy_runtime.rs:45`). +- No UOT support is exposed. +- Burrow now fragments single Trojan UDP payloads above 8192 bytes into multiple + Trojan UDP frames (`burrow/src/proxy_runtime.rs:1050`). +- Burrow now carries a Trojan `udp` enable/disable flag. + +Discrepancy: + +- Mihomo has metadata-aware UDP resolution/mapping and UOT support. Burrow has + direct per-flow Trojan UDP over TLS without hostname metadata. +- Mihomo and Burrow both fragment oversized Trojan UDP payloads after + `BEP-0011`. + +## 12. Packet Tunnel and DNS Model + +Mihomo: + +- TUN mode has metadata preprocessing, fake-IP reverse mapping, sniffing, rule + resolution, and then proxy dial (`tunnel/tunnel.go:287`, + `tunnel/tunnel.go:540`). +- DNS service and resolver enhancer support fake-IP/mapping modes + (`dns/service.go:27`, `dns/enhancer.go:23`). + +Burrow: + +- Apple Network Extension forwards packets to a daemon packet stream and writes + daemon responses back to `packetFlow` (`Apple/NetworkExtension/PacketTunnelProvider.swift:91`). +- Burrow's proxy tunnel settings use virtual addresses and point DNS at the + daemon-owned resolver address `100.64.0.2`. +- The daemon answers A queries with stable fake IPs and records fake-IP to + hostname mappings for later TCP proxy requests. There is still no rules engine + or TCP sniffer in the packet runtime. + +Discrepancy: + +- Mihomo is a policy-aware packet tunnel with DNS hijack/fake-IP metadata. +- Burrow is a simpler packet-to-selected-outbound bridge with daemon-owned fake + DNS for A queries. It does not yet implement Mihomo's policy routing, + sniffing, resolver modes, or per-rule metadata pipeline. + +## 13. Logging and Observability + +Mihomo: + +- DNS cache hits, DNS resolution, metadata preprocessing failures, process lookup + failures, and UDP resolution failures are logged in the relevant paths + (`dns/resolver.go:170`, `tunnel/tunnel.go:339`, `tunnel/tunnel.go:506`, + `tunnel/connection.go:88`). + +Burrow: + +- Runtime now logs selected node, configured Trojan outbound metadata, resolved + proxy addresses, fake-IP/benchmark-range warning, and socket bind/connect + failures (`burrow/src/proxy_runtime.rs:215`, + `burrow/src/proxy_runtime.rs:552`, `burrow/src/proxy_runtime.rs:1263`, + `burrow/src/proxy_runtime.rs:1294`). +- `Scripts/burrow-proxy-selftest` provides a controlled daemon packet-runtime + test against a local Trojan server, and `proxy-tcp-probe --dns-name` exercises + daemon DNS plus raw TCP packet streaming without changing system proxy state. +- On macOS, Rust tracing goes to stderr by default; OSLog is opt-in via + `BURROW_ENABLE_OSLOG=1` (`burrow/src/tracing.rs:50`). + +Discrepancy: + +- Mihomo logs more of the full metadata/rule/DNS pipeline. +- Burrow now logs the outbound path and has standalone packet-runtime probes, + but still lacks Mihomo's full metadata/rule diagnostics. + +## 14. Likely Compatibility Gaps for the Selected Node + +Highest probability: + +1. `client_fingerprint: chrome` is stored but not implemented by Burrow runtime. + Mihomo would use uTLS Chrome, so this remains a compatibility gap for + providers that strictly require browser ClientHello impersonation. +2. The selected provider node succeeds through Mihomo when Mihomo is configured + from Burrow's stored DB payload, pinned to real proxy-server IPs, and bound to + `en0`. Burrow's daemon packet probe now also succeeds for `google.com:80` + after macOS OpenSSL TLS and coalesced Trojan header writes. +3. Older stored nodes may have `alpn: []` from pre-`BEP-0011` imports. Burrow now + treats missing `alpn_present` as absent ALPN and applies Mihomo's TCP default. +4. Burrow's proxy-server resolver is narrower than Mihomo's configurable + proxy-server DNS plane. + +Medium probability: + +5. Burrow only preserves domain metadata for TCP flows that went through its + daemon-owned A-query fake DNS path. +6. Burrow has no Mihomo-style sniffing or rule metadata. This is + less likely to break simple all-traffic proxying, but it is a major semantic + difference. + +Lower probability for this exact selected node: + +7. ws/grpc transport is unsupported by Burrow runtime. The selected node is TCP, + so this is not the immediate failure for that node. +8. Certificate pinning is now implemented by Burrow. The selected node has no + stored pinned fingerprint, so this does not explain that exact node. + +## Suggested Burrow Follow-Ups + +1. Implement `client_fingerprint` for Trojan, at least `chrome`, or reject nodes + requiring runtime fingerprints until uTLS-style support exists. +2. Expand the proxy-server resolver path toward Mihomo's configurable + `proxy-server-nameserver` behavior and TTL/cache controls. +3. Add sniffing or broader DNS metadata handling for flows that do not use the + daemon-owned A-query fake DNS path. +4. Either implement Trojan ws/grpc or make unsupported transport status more + prominent when importing/selecting nodes. +5. Add Trojan client certificate/private-key support if subscriptions need it. diff --git a/evolution/proposals/BEP-0005-daemon-ipc-and-apple-boundary.md b/evolution/proposals/BEP-0005-daemon-ipc-and-apple-boundary.md index a34a609..93c2a26 100644 --- a/evolution/proposals/BEP-0005-daemon-ipc-and-apple-boundary.md +++ b/evolution/proposals/BEP-0005-daemon-ipc-and-apple-boundary.md @@ -31,6 +31,11 @@ Burrow should formalize one Apple/runtime boundary: Apple clients speak only to - login/session lifecycle brokering - runtime start/stop/reconcile - translating helper or bridge processes into stable daemon RPCs +- Apple uses two daemon sockets with one shared app-group database: + - the app-facing control socket, `burrow.sock`, is for UI management RPCs such as network list, import, preview, refresh, node selection, account/login control, and other non-packet workflows + - the packet-tunnel socket, `burrow-packet-tunnel.sock`, is opened by `PacketTunnelProvider` and owns active tunnel runtime RPCs such as `TunnelStart`, `TunnelConfiguration`, `TunnelPackets`, and `TunnelStop` +- The Network Extension must host or connect to the packet-tunnel daemon socket while a VPN session is active. The app must not hot-swap packet runtime state underneath an already-running Apple `NEPacketTunnelProvider`. +- UI-side network mutations may persist desired state through the control daemon. If those mutations affect active packet-tunnel settings or runtime, the Apple client must restart or otherwise reassert the Network Extension so settings and packet runtime are installed from the same selected network state. - `burrow/src/control/` owns transport-neutral control-plane semantics such as discovery, authority normalization, and request/response shaping. - Apple UI owns presentation only: - forms @@ -43,7 +48,7 @@ Burrow should formalize one Apple/runtime boundary: Apple clients speak only to - Keeping control-plane I/O out of Swift UI reduces accidental secret, token, and callback sprawl across app code. - The daemon boundary makes testing and kill-switch behavior tractable because runtime integration is localized. -- Apple daemon lifecycle ownership must be explicit: either the app ensures the daemon is running before RPC or the extension owns it and the UI surfaces daemon-unavailable state clearly. +- Apple daemon lifecycle ownership must be explicit: the app owns the control daemon used for presentation workflows, and the Network Extension owns the packet daemon used for active tunnel runtime. Both use the app-group database as the durable desired-state store. - Non-Apple presentation clients should follow the same daemon-first lifecycle pattern: connect to a managed daemon when present, or start a user-scoped embedded daemon before issuing RPCs, without adding platform-local control-plane paths. ## Contributor Playbook diff --git a/evolution/proposals/BEP-0009-trojan-proxy-and-subscription-import.md b/evolution/proposals/BEP-0009-trojan-proxy-and-subscription-import.md new file mode 100644 index 0000000..c7906ad --- /dev/null +++ b/evolution/proposals/BEP-0009-trojan-proxy-and-subscription-import.md @@ -0,0 +1,131 @@ +# `BEP-0009` - Trojan Proxy and Subscription Import + +```text +Status: Superseded +Proposal: BEP-0009 +Authors: gpt-5.5 +Coordinator: gpt-5.5 +Reviewers: Pending +Constitution Sections: II, IV, V +Implementation PRs: Pending +Decision Date: 2026-05-27 +``` + +## Summary + +Superseded by `evolution/proposals/BEP-0010-proxy-subscriptions-as-packet-tunnel-networks.md`. + +Burrow should add daemon-owned Trojan proxy support as a proxy egress transport, plus a subscription importer that can parse base64 URI subscriptions containing `trojan://` nodes. This proposal covers Trojan profile ingestion, validation, storage, runtime boundaries, and user-facing import behavior without claiming broader Clash or Mihomo protocol parity. + +Trojan support should be implemented as an explicit Burrow protocol family with tested URI parsing and daemon-managed runtime behavior. Apple clients may present imported profiles and statuses, but they must continue to talk only to the daemon over gRPC. + +## Motivation + +- Real-world proxy subscriptions commonly expose Trojan nodes as base64-encoded lists of `trojan://` URIs rather than static Clash YAML. +- Users should be able to import a subscription URL and let Burrow identify compatible nodes without leaking tokens into logs, source files, or UI copy. +- Trojan is different from WireGuard, Tailnet, and future MASQUE `CONNECT-IP`; it needs an explicit profile model, TLS validation policy, and proxy egress runtime. +- Supporting one proxy protocol first gives Burrow a concrete path for future proxy families without adding an unbounded "Clash compatible" claim. + +## Detailed Design + +- Add a new daemon-owned network/profile type for Trojan proxy egress. + - The stable code/protocol identifier should use `trojan`. + - User-facing copy should spell the project name as `Burrow` and should name the protocol as `Trojan`. + - The profile type should be distinct from `WireGuard` and `Tailnet`. +- Define a `TrojanProfile` payload with at least: + - display name + - server host + - server port + - password or credential reference + - TLS server name indication + - optional peer/host metadata when present in imported subscriptions + - transport, initially restricted to `tcp` + - certificate verification policy + - source metadata such as imported subscription name and refresh timestamp +- Add a subscription import flow owned by the daemon. + - Fetch subscription URLs in daemon code, not Swift UI. + - Redact query tokens and credentials from logs and errors. + - Accept base64 URI subscriptions whose decoded body is a newline-separated URI list. + - Parse `trojan://` URIs and reject unknown schemes unless future proposals add them. + - Preserve node labels from URI fragments as display names after normalization. + - Treat `allowInsecure=1`, `skip-cert-verify=true`, or equivalent flags as an explicit insecure TLS policy. + - Deduplicate imported nodes by stable profile fields rather than display label alone. +- Add daemon gRPC methods before any Apple UI work. + - A minimal shape is `SubscriptionImportPreview`, `SubscriptionImportApply`, and `SubscriptionRefresh`. + - Preview should return compatible profile counts, rejected entry counts, redacted warnings, and normalized display labels. + - Apply should write only selected compatible profiles. + - Refresh should preserve local enable/order choices where possible. +- Add runtime support in stages. + - Stage 1: parser, preview, storage, and tests only. + - Stage 2: daemon-managed Trojan outbound connector for TCP proxy egress. + - Evaluate `trojan-rust/trojan-rust` as the first implementation dependency for this stage. + - Prefer the narrowest usable crate, likely `trojan-proto` for protocol parsing/serialization or `trojan-client` for a SOCKS5-backed client runtime. + - Avoid depending on the top-level `trojan` crate with default features unless Burrow needs the bundled CLI, certificate, SQL, or agent features. + - Wrap any external runtime API behind a Burrow-owned adapter so profile storage, logging, kill-switch behavior, and daemon lifecycle remain local. + - Stage 3: route/DNS integration with Burrow's tunnel and proxy selection model. + - Stage 4: UI controls for import, refresh, certificate policy warnings, and per-profile status. +- Keep the runtime boundary explicit. + - Apple UI may display forms, previews, warning states, and daemon statuses. + - Apple UI must not fetch subscription URLs directly. + - Apple UI must not open Trojan sockets directly. + - Any helper process must be brokered and supervised by the daemon. + +## Security and Operational Considerations + +- Subscription URLs often contain bearer tokens. Burrow must treat full subscription URLs as secrets. +- Imported Trojan URIs contain passwords. Burrow must avoid writing decoded raw subscriptions to logs, crash reports, test snapshots, or telemetry. +- Certificate verification defaults should be secure. Insecure imported nodes may be stored only with an explicit warning field and UI-visible risk state. +- Passwords should move into the same secret-storage path Burrow uses for other long-lived credentials instead of remaining in plain payload blobs. +- The importer must set fetch limits for response size, redirect depth, and timeout. +- The parser must be independent and fuzzable enough to handle malformed URI lists without panics. +- Runtime support should include a kill-switch path so disabling a profile tears down active sockets and route state. +- Burrow must not present Trojan as a VPN with the same security or routing semantics as WireGuard. It is a proxy egress transport unless later proposals define full-device routing behavior. + +## Contributor Playbook + +1. Add parser-only fixtures for representative `trojan://` URIs, including TLS SNI, host, peer, TCP transport, fragments, duplicate labels, malformed ports, unsupported schemes, and insecure certificate flags. +2. Add a redacted subscription fixture that mirrors a real base64 URI-list response without preserving live tokens, passwords, or hostnames that identify a paid account. +3. Extend `proto/burrow.proto` with daemon-owned subscription preview/apply/refresh RPCs before adding Apple UI. +4. Add daemon storage for `trojan` profiles and migrate old payload storage only if the profile model requires it. +5. Verify generated Swift and Rust bindings compile after proto changes. +6. Implement runtime behind a feature flag or internal capability gate until end-to-end proxy egress tests exist. +7. Run: + +```bash +cargo test --workspace --all-features +python3 Scripts/check-bep-metadata.py +``` + +8. For Apple work, verify no Swift UI or support code fetches subscription URLs or talks to Trojan endpoints directly. + +## Alternatives Considered + +- Import full Clash or Mihomo YAML first. Rejected for this proposal because it would imply a broad compatibility contract across many protocols before Burrow has a proxy abstraction. +- Shell out to Mihomo as a helper. Rejected as the default path because it would create a large external runtime dependency and make Burrow's protocol guarantees harder to test. A future proposal may revisit helper-based compatibility as an explicit adapter. +- Vendor or reimplement the Trojan wire protocol immediately. Rejected as the default path while `trojan-rust/trojan-rust` appears to provide GPL-compatible Rust crates for protocol parsing and client behavior. Burrow should still keep an adapter boundary so it can replace the dependency if maintenance, API, or security review fails. +- Treat Trojan nodes as WireGuard-like networks. Rejected because Trojan is proxy egress over TLS, not a packet VPN with WireGuard peer semantics. +- Let Apple UI fetch and parse subscriptions for convenience. Rejected because it violates the daemon IPC boundary and spreads subscription tokens into UI code. + +## Impact on Other Work + +- Depends on the daemon boundary in BEP-0005. +- Should align with the transport-neutral route and policy work described in BEP-0003, but should not block parser-only subscription import. +- Creates a path for future Shadowsocks or VLESS proposals by forcing shared subscription parsing and redaction rules. +- May require a follow-up secret-storage proposal if current network payload storage cannot safely hold proxy credentials. + +## Decision + +Superseded by `evolution/proposals/BEP-0010-proxy-subscriptions-as-packet-tunnel-networks.md` on 2026-05-27. + +## References + +- Trojan protocol: https://trojan-gfw.github.io/trojan/protocol.html +- Trojan config: https://trojan-gfw.github.io/trojan/config.html +- Trojan reference implementation: https://github.com/trojan-gfw/trojan +- Rust Trojan implementation candidate: https://github.com/trojan-rust/trojan-rust +- Rust `trojan-client` crate: https://crates.io/crates/trojan-client +- Rust `trojan-proto` crate: https://crates.io/crates/trojan-proto +- Mihomo Trojan proxy configuration: https://wiki.metacubex.one/en/config/proxies/trojan/ +- BEP-0003: `evolution/proposals/BEP-0003-connect-ip-and-negotiation-roadmap.md` +- BEP-0005: `evolution/proposals/BEP-0005-daemon-ipc-and-apple-boundary.md` +- Burrow protocol schema: `proto/burrow.proto` diff --git a/evolution/proposals/BEP-0010-proxy-subscriptions-as-packet-tunnel-networks.md b/evolution/proposals/BEP-0010-proxy-subscriptions-as-packet-tunnel-networks.md new file mode 100644 index 0000000..3e33ce7 --- /dev/null +++ b/evolution/proposals/BEP-0010-proxy-subscriptions-as-packet-tunnel-networks.md @@ -0,0 +1,271 @@ +# `BEP-0010` - Proxy Subscriptions as Packet Tunnel Networks + +```text +Status: Draft +Proposal: BEP-0010 +Authors: Codex +Coordinator: Codex +Reviewers: Pending +Constitution Sections: II, IV, V +Implementation PRs: Pending +Decision Date: Pending +``` + +## Summary + +Burrow should support Trojan and Shadowsocks subscription imports as packet-tunnel networks, not as local SOCKS or system proxy mode. The user-facing add action should be named `Import Proxy Subscription` on every UI surface. Imported proxy subscriptions should participate in Burrow's existing tunnel model: platform tunnel capture feeds daemon-owned routing and proxy execution, and a selected proxy outbound carries TCP and UDP flows. + +This proposal supersedes `BEP-0009`. The earlier SOCKS-backed Trojan runtime was useful as a smoke-test path, but Burrow's product semantics are VPN-shaped. Keeping a local SOCKS mode would add another behavior surface, confuse how proxy networks relate to WireGuard and Tailnet, and delay the packet-tunnel work Burrow actually needs. + +## Motivation + +- Users expect the main Burrow tunnel switch to affect full-device traffic, as it does for packet-shaped WireGuard and Tailnet integrations. +- Trojan and Shadowsocks subscriptions are commonly distributed as Clash/Mihomo YAML, proxy-provider content, plain URI lists, or base64 URI lists. Burrow's current Trojan parser only covers a narrow `trojan://` URI-list subset. +- Mihomo's TUN mode is the closest reference behavior: it captures packets, runs a userspace IP stack, hijacks DNS, resolves rules and proxy selections, then dials protocol-specific outbounds. +- Burrow should avoid a second user-facing "proxy mode" based on `HTTP_PROXY`, SOCKS settings, or a loopback listener because that is not equivalent to VPN behavior and creates platform-specific state outside Burrow's tunnel contract. +- Subscription URLs and proxy node credentials are secret-bearing. Fetching, parsing, storing, refreshing, and redacting them belong in the daemon boundary described by `BEP-0005`. + +## Detailed Design + +### User Model + +- Add a UI action named `Import Proxy Subscription`. +- Imported subscriptions are stored as `Network` records because they represent routeable connectivity sources. +- Proxy nodes inside a subscription are selectable endpoints within that network. +- Node selection should mirror Mihomo selector behavior in `adapter/outboundgroup/selector.go`: persist an explicit selected node when the user chooses one, validate that it exists, and fall back to the first runtime-supported node only when no explicit supported selection is available. +- Proxy subscriptions do not create `Account` records by default. Accounts remain for identity and sign-in state such as Tailnet login or future provider identity flows. +- Apple SwiftUI, Linux GTK, and future UI surfaces should expose the same import, preview, warning, selection, refresh, and node status behavior over the daemon API. +- UI clients may show subscription name, refresh state, selected node, node health, warnings, and import preview results. They must not fetch subscription URLs or open Trojan/Shadowsocks sockets directly. +- Import preview UIs must expose the runtime-supported Trojan and Shadowsocks nodes as a selectable list and pass the selected ordinal to the daemon `ApplyImport` request. Parsed-but-unsupported nodes should remain visible as warnings or counts, not selectable runtime choices. + +### Removed SOCKS Mode + +- Remove the daemon runtime path that starts a stored Trojan profile as a local SOCKS5 proxy. +- Remove CLI and smoke entrypoints whose primary purpose is operating Burrow as a SOCKS proxy. +- Keep parser tests and sanitized live-comparison tooling only when they validate subscription import or packet-tunnel behavior. +- Do not expose system `HTTP_PROXY`, system SOCKS, PAC, or "set system proxy" behavior as part of this proposal. +- If a temporary developer-only loopback proxy is needed for debugging, it must be clearly separate from product behavior and must not be the stored network runtime. + +### Subscription Import Compatibility + +Implement the importer in the daemon and mirror Mihomo's behavior as closely as Burrow's narrower protocol scope allows. Path references in this section are relative to the Mihomo repository root. When behavior is ambiguous, contributors should inspect the referenced Mihomo code path and either match it or document a deliberate deviation in this BEP before implementation. + +- Full config parsing should recognize subscription-relevant top-level fields analogous to `proxies`, `proxy-providers`, and `proxy-groups` in `config/config.go`. +- Provider parsing should support `file`, `http`, and `inline` style sources analogous to `adapter/provider/parser.go`. +- Provider content parsing should first try YAML with a `proxies` field. If YAML cannot be parsed, fall back to V2Ray-style URI conversion, matching the shape in `adapter/provider/provider.go`. +- Plain and base64 URI-list inputs should follow Mihomo's behavior in `common/convert/converter.go` and `common/convert/base64.go`: attempt raw/std base64 decoding, otherwise treat the body as plaintext. +- Trojan URI conversion should closely follow Mihomo's `trojan` case in `common/convert/converter.go` and the accepted outbound fields in `adapter/outbound/trojan.go`. Burrow should preserve Mihomo-compatible names and semantics internally where practical, then adapt them into Burrow's storage model. It should map at least: + - URI fragment to display name + - host, port, and username to server, port, and password + - `allowInsecure` to certificate verification policy + - `sni`, `alpn`, `type=ws`, `type=grpc`, client fingerprint, and pinned certificate fingerprint where supported + - Mihomo's default client fingerprint behavior where applicable + - Mihomo's ws and grpc option shapes before translating them into Burrow-owned structs +- Shadowsocks URI conversion should map at least: + - legacy base64 host form + - base64 `cipher:password` userinfo + - server, port, cipher, password, and node name + - `udp-over-tcp` or `uot` + - common `obfs` and `v2ray-plugin` plugin options when support is implemented +- Unsupported protocols, unsupported plugin modes, malformed nodes, duplicate node names, and insecure TLS flags must be returned in import preview warnings without logging credentials. + +Burrow should not claim full Clash or full Mihomo compatibility in this proposal. The compatibility target is subscription import for Trojan and Shadowsocks nodes plus the minimum provider and group metadata needed to select and refresh outbounds. Within that target, subscription parsing and Trojan normalization should be intentionally Mihomo-like so a subscription that imports cleanly in Mihomo has the same node set, labels, security warnings, and supported Trojan transport metadata in Burrow unless the BEP records a specific exception. + +### Data Model + +Add a proxy-subscription network payload with: + +- subscription display name +- redacted subscription URL or a credential reference to the full URL +- provider format detected during import +- refresh policy and last refresh result +- ordered proxy nodes +- selected node or selection strategy +- certificate and transport warnings +- per-node protocol type, server metadata, transport metadata, and secret references + +Proxy node secrets must move out of plain network payload blobs when Burrow's secret-storage path exists. Until then, payload storage must be treated as sensitive and logs/tests must use redacted fixtures. + +### Daemon APIs + +Add daemon gRPC APIs before platform UI work: + +- preview import from subscription URL +- apply selected preview result as a network +- refresh an existing subscription network +- list nodes for a proxy subscription network +- set selected node or selection strategy + +Preview responses should include compatible count, rejected count, detected format, sanitized warnings, suggested network name, and redacted node labels. Apply and refresh must preserve local node order and selected-node state where possible. + +### Packet Tunnel Runtime + +Burrow should route proxy subscriptions through the existing tunnel architecture: + +1. A platform tunnel provider starts the tunnel and applies daemon-provided network settings. +2. Platform packet capture forwards packets through the daemon packet interface. Apple uses `PacketTunnelProvider` and the existing `TunnelPackets` gRPC stream against the packet-tunnel daemon socket owned by the Network Extension; Linux should use the daemon-owned TUN path already aligned with the GTK client. +3. The daemon owns a proxy packet engine for active proxy-subscription networks. +4. The packet engine reconstructs TCP and UDP flows from IP packets. +5. DNS packets are handled by a daemon-owned DNS path so hostname metadata can be preserved for rule selection and proxy dialing. +6. Each flow is matched to the selected proxy node or selection strategy. +7. The daemon opens a Trojan or Shadowsocks outbound connection and relays flow bytes. +8. Return packets are serialized back to Apple through `TunnelPackets`. + +Mihomo's equivalent kernel path is: + +- TUN recreation and listener lifecycle in `listener/listener.go` +- TUN option construction and stack startup in `listener/sing_tun/server.go` +- DNS hijack in `listener/sing_tun/dns.go` +- metadata conversion in `listener/sing/sing.go` +- TCP and UDP handling in `tunnel/tunnel.go` +- outbound protocol construction in `adapter/parser.go`, `adapter/outbound/trojan.go`, and `adapter/outbound/shadowsocks.go` + +Burrow should use these as architecture references, not as code to vendor blindly. The Burrow implementation must fit the daemon IPC boundary, Rust runtime, Apple NetworkExtension path, Linux daemon/TUN path, GTK client, and existing network selection model. + +### Implemented Runtime Shape + +The first packet-tunnel implementation follows the Mihomo adapter shape but keeps Burrow's narrower protocol surface: + +- `burrow/src/proxy_runtime.rs` owns the daemon packet engine. +- Apple uses the existing `TunnelPackets` stream, matching the Tailnet packet-stream boundary. +- Linux uses the daemon TUN bridge path. +- Tunnel configuration excludes resolved selected proxy server host routes from the captured default route so proxy-server dials cannot be trapped by the full tunnel. +- On Apple, the proxy packet runtime binds Trojan and Shadowsocks outbound sockets to a non-tunnel physical interface before connect and also installs resolved Network Extension excluded routes for the selected proxy server. Loopback proxy-server addresses are not physical-interface bound so controlled local tests and developer-only local endpoints can still connect. The excluded route is intentionally redundant: it prevents proxy-server dials and server DNS artifacts from recursively entering the full tunnel when socket binding is unavailable, delayed, or ignored by a platform path. +- On Apple, node selection and refresh are applied to both the app-facing control daemon and, while connected, the packet-tunnel daemon over `burrow-packet-tunnel.sock`. The packet daemon swaps the active proxy outbound inside the existing packet bridge, so new TCP and UDP flows use the selected node without restarting the Network Extension. Existing flows may continue on the outbound they opened with until they close. +- TCP and UDP packets are reconstructed with the existing smoltcp userspace stack. +- Trojan TCP nodes are dialed over TLS and use Trojan TCP and UDP request framing directly. +- Shadowsocks AEAD nodes are dialed directly for TCP and UDP using SIP004-style salt, HKDF subkeys, and AEAD chunks/datagrams. +- Unsupported runtime transports such as Trojan `ws`/`grpc`, Shadowsocks plugins, and Shadowsocks UDP-over-TCP are still parsed and preserved from subscriptions, but they are rejected by runtime selection until those adapter transports are implemented. If no node is explicitly selected, Burrow chooses the first runtime-supported node instead of starting an unsupported transport. +- Apple SwiftUI and Linux GTK import flows preview nodes, restrict the picker to runtime-supported nodes, and pass the chosen node ordinal to the daemon when applying an import. +- Proxy-subscription tunnel settings point DNS at the daemon-owned tunnel address. The packet runtime answers ordinary A queries with fake IPs and records the fake-IP to hostname mapping so later TCP flows can send hostname-form proxy requests. This avoids making website DNS depend on outbound UDP support while preserving hostname metadata for Trojan and Shadowsocks dialing. + +### Routing, DNS, and Apple Runtime Ownership + +For full-tunnel proxy networks, daemon tunnel configuration should include: + +- a virtual IPv4 address from a documentation or carrier-grade NAT range suitable for the packet engine +- a virtual IPv6 address once the packet engine is enabled for IPv6 flows +- MTU selected for userspace packet handling +- default IPv4 route when full-tunnel is enabled +- default IPv6 route when the platform tunnel surface supports it +- a daemon-owned DNS server address reachable inside the tunnel + +DNS handling should be explicit. Proxy-subscription tunnel configuration should use the daemon-owned DNS address inside the tunnel, not ambient physical resolvers and not a public upstream that requires outbound UDP before TCP can start. The daemon DNS path may answer with fake IPs and must keep those mappings scoped to the active proxy runtime so hostname-preserving proxy dialing does not leak between networks. + +On Apple, the app-facing daemon and packet-tunnel daemon are intentionally separate in-process daemon instances over different app-group Unix sockets. The app-facing daemon handles subscription preview, import, refresh, node selection, network list, and account/login control through `burrow.sock`. The Network Extension hosts the packet-tunnel daemon over `burrow-packet-tunnel.sock` while the VPN session is active; `TunnelStart`, `TunnelConfiguration`, `TunnelPackets`, and `TunnelStop` for the active session use that socket. + +This split keeps macOS packet runtime ownership aligned with `NEPacketTunnelProvider`: routes, DNS settings, packet stream lifetime, and proxy runtime are created from the same selected network state when the extension starts. UI mutations persist desired state in the shared app-group database. If a mutation changes active packet-tunnel behavior, the Apple client should update the packet daemon's desired state and only restart or reassert the Network Extension when the platform tunnel settings themselves change. + +Proxy-subscription routing must avoid recursively routing the proxy outbound through the full tunnel. Burrow uses daemon-side outbound socket binding, analogous to Mihomo's default-interface outbound behavior, and installs resolved proxy-server excluded routes as a second guardrail. Selector changes remain daemon-local when they do not require platform route changes; if the selected node's resolved proxy-server routes change, the Network Extension should be restarted or have its settings reapplied so the excluded routes match the active outbound. + +Selector changes should be daemon-local whenever socket binding is available. The Network Extension packet stream and route/DNS settings should stay up; the packet daemon updates its proxy outbound handle, and future flows read the new selection from that shared handle. This mirrors Mihomo's selector model more closely than restarting TUN for every node change. + +Daemon-owned DNS/fake-IP metadata is part of the proxy packet runtime. The proxy-server route exclusion protects the selected outbound itself; ordinary website DNS is answered by the daemon and converted into hostname metadata for future flows. Mihomo-style rule selection can build on the same mapping without depending on ambient system DNS after the tunnel is active. + +### Rollout Stages + +1. Proposal and model cleanup. + - Supersede `BEP-0009`. + - Remove SOCKS runtime from the target design. +2. Parser and preview. + - Implement daemon-owned import preview for Mihomo-compatible YAML and URI-list inputs. + - Add fixtures for Trojan and Shadowsocks. +3. Storage and refresh. + - Store proxy subscriptions as networks. + - Preserve selected node and local ordering across refreshes. +4. Packet engine foundation. + - Add daemon packet-stream support for proxy networks. + - Reconstruct TCP flows first with controlled DNS behavior. +5. Protocol outbounds. + - Add Trojan outbound. + - Add Shadowsocks outbound. + - Add UDP once NAT, timeout, and protocol support are explicit. +6. Platform UI. + - Add `Import Proxy Subscription` to Apple SwiftUI and Linux GTK add flows. + - Add preview, warning, selection, refresh, and node status views to Apple SwiftUI, Linux GTK, and any future first-party UI surface. +7. Compatibility and operations. + - Add Mihomo comparison fixtures and sanitized live-check scripts. + - Document remaining incompatibilities. + +## Security and Operational Considerations + +- Subscription URLs frequently contain bearer tokens. Full URLs must not appear in logs, screenshots, fixtures, crash reports, or generated docs. +- Proxy node credentials must be redacted in all errors and diagnostics. +- Insecure TLS settings imported from subscriptions must be visible in preview and stored as explicit risk metadata. +- Refresh must have timeout, redirect, response-size, and content-type limits. +- Parser failures must be non-fatal per node; malformed input should never panic the daemon. +- Packet-tunnel activation must have a kill-switch path: when the tunnel stops or a proxy network is disabled, packet handling, DNS state, NAT state, and outbound sockets must be torn down. +- If DNS mapping is fake-IP based, stale mappings must expire and must not leak between networks in ways that route traffic through the wrong node. +- Full-tunnel mode must avoid recursive routing of the proxy server connection through the tunnel. On Apple, preferred bypass behavior is outbound socket binding to a non-tunnel interface. Route exclusions are fallback behavior for platforms or environments where socket binding is not available. +- Removing SOCKS mode simplifies the threat model by leaving one supported runtime behavior for proxy networks. + +## Contributor Playbook + +1. Keep all UI subscription operations behind daemon gRPC. +2. Update or remove current Trojan SOCKS runtime code instead of layering the packet-tunnel runtime beside it. +3. Add parser fixtures for: + - Clash/Mihomo YAML with `proxies` + - proxy-provider content + - base64 URI list + - plaintext URI list + - Trojan TCP, ws, grpc metadata + - Shadowsocks legacy and SIP002-style links + - unsupported protocols and malformed nodes + - Mihomo parity cases copied from the behavior of `common/convert/converter.go`, especially Trojan `allowInsecure`, `sni`, `alpn`, `type=ws`, `type=grpc`, `fp`, and `pcs` +4. Add packet-engine tests with synthetic TCP and DNS packets before full platform UI integration. +5. Add a migration or cleanup path for any existing stored `NetworkType::Trojan` payloads created by the SOCKS-era implementation. +6. Validate metadata and affected builds: + +```bash +uv run python Scripts/check-bep-metadata.py +cargo test -p burrow proxy_subscription +cargo check -p burrow +``` + +7. For platform UI work, regenerate bindings after proto changes and verify Apple SwiftUI and Linux GTK still talk only to daemon gRPC for subscription operations. + +## Alternatives Considered + +- Keep local SOCKS mode. Rejected because it is not Burrow's VPN behavior, adds another support surface, and makes imported proxy subscriptions feel unlike WireGuard and Tailnet. +- Use system `HTTP_PROXY` or SOCKS settings. Rejected because only proxy-aware apps participate, DNS behavior is inconsistent, and cleanup/rollback becomes OS-setting specific. +- Shell out to Mihomo. Rejected as the default implementation because Burrow would inherit a large external runtime and a separate control plane. Mihomo remains the behavioral reference for import and TUN semantics. +- Claim full Mihomo compatibility immediately. Rejected because Burrow should first support Trojan and Shadowsocks imports plus packet-tunnel routing correctly. +- Treat every proxy node as a separate account. Rejected because nodes are endpoints under one imported network source, not identities. + +## Impact on Other Work + +- Supersedes `BEP-0009`. +- Depends on the daemon IPC boundary in `BEP-0005`. +- Aligns with the route and policy direction in `BEP-0003`. +- May require a follow-up secret-storage BEP if existing network payload storage remains insufficient for long-lived proxy credentials. +- Creates shared import and packet-engine foundations for future proxy protocols, but this proposal only covers Trojan and Shadowsocks. + +## Decision + +Pending. + +## References + +- Burrow daemon IPC boundary: `evolution/proposals/BEP-0005-daemon-ipc-and-apple-boundary.md` +- Burrow route and negotiation roadmap: `evolution/proposals/BEP-0003-connect-ip-and-negotiation-roadmap.md` +- Superseded Trojan SOCKS proposal: `evolution/proposals/BEP-0009-trojan-proxy-and-subscription-import.md` +- Burrow tunnel schema: `proto/burrow.proto` +- Burrow Apple tunnel provider: `Apple/NetworkExtension/PacketTunnelProvider.swift` +- Burrow Apple UI: `Apple/UI/` +- Burrow GTK UI: `burrow-gtk/src/` +- Burrow current Trojan parser/runtime: `burrow/src/trojan.rs` +- Burrow daemon runtime: `burrow/src/daemon/runtime.rs` +- Mihomo config model: `config/config.go` +- Mihomo proxy provider parsing: `adapter/provider/parser.go` +- Mihomo provider content parsing: `adapter/provider/provider.go` +- Mihomo URI conversion: `common/convert/converter.go` +- Mihomo base64 fallback: `common/convert/base64.go` +- Mihomo TUN listener lifecycle: `listener/listener.go` +- Mihomo TUN stack setup: `listener/sing_tun/server.go` +- Mihomo DNS hijack: `listener/sing_tun/dns.go` +- Mihomo listener metadata bridge: `listener/sing/sing.go` +- Mihomo TCP/UDP tunnel handling: `tunnel/tunnel.go` +- Mihomo outbound parser: `adapter/parser.go` +- Mihomo selector group: `adapter/outboundgroup/selector.go` +- Mihomo Trojan outbound: `adapter/outbound/trojan.go` +- Mihomo Shadowsocks outbound: `adapter/outbound/shadowsocks.go` diff --git a/evolution/proposals/BEP-0011-mihomo-compatible-trojan-runtime.md b/evolution/proposals/BEP-0011-mihomo-compatible-trojan-runtime.md new file mode 100644 index 0000000..360104e --- /dev/null +++ b/evolution/proposals/BEP-0011-mihomo-compatible-trojan-runtime.md @@ -0,0 +1,110 @@ +# `BEP-0011` - Mihomo-Compatible Trojan Runtime + +```text +Status: Draft +Proposal: BEP-0011 +Authors: Codex +Coordinator: Jett Chen +Reviewers: Pending +Constitution Sections: 2, 4, 5 +Implementation PRs: Pending +Decision Date: Pending +``` + +## Summary + +Burrow's proxy subscription runtime should match Mihomo's Trojan wire behavior +for imported nodes wherever Burrow already claims runtime support. This proposal +covers TCP Trojan nodes in the packet tunnel runtime: ALPN defaults, DNS-backed +hostname preservation, UDP gating, UDP packet fragmentation, certificate +pinning, and diagnostics for Mihomo-only TLS fingerprint behavior. + +The goal is compatibility without overstating support. Burrow should apply +Mihomo semantics that can be implemented correctly today, and it should make +unsupported Mihomo features visible instead of silently pretending to support +them. + +## Motivation + +- Imported Trojan subscriptions commonly target Mihomo-compatible clients. +- A stored node can carry `client-fingerprint`, ALPN, UDP, and certificate + pinning metadata that materially changes whether the proxy server accepts the + connection. +- Burrow previously flattened absent ALPN and explicit empty ALPN into the same + value, ignored Trojan UDP enablement, rejected oversized Trojan UDP payloads + instead of fragmenting them, and stored certificate pins without enforcing + them. + +## Detailed Design + +- Store an `alpn_present` marker alongside the existing Trojan `alpn` vector. + Missing ALPN uses Mihomo's TCP Trojan default `h2,http/1.1`; explicit ALPN, + including an explicit empty YAML list, is honored as written. +- Store Trojan `udp`. URI imports default to enabled to match Mihomo's URI + converter. YAML imports default to disabled unless `udp` is present. +- Point proxy-network DNS at the daemon-owned tunnel address. The packet runtime + answers A queries with fake IPs, records fake-IP to hostname mappings, and + uses hostname-form SOCKS addresses in Trojan TCP requests when a later TCP + flow targets one of those fake IPs. +- Enforce Trojan UDP gating at runtime and fragment UDP payloads above 8192 + bytes into multiple Trojan UDP frames. +- Treat `fingerprint` as SHA-256 certificate pinning. Browser fingerprint names + are rejected in `fingerprint` with guidance to use `client-fingerprint`. +- Keep using rustls for non-Apple TLS and OpenSSL for the macOS Trojan TLS path. + `client-fingerprint` is logged when present because Mihomo's uTLS ClientHello + impersonation is not implemented in Burrow yet. +- Coalesce the Trojan request header into one TLS write, matching Mihomo's + `trojan.WriteHeader` behavior. +- Continue rejecting Trojan `ws` and `grpc` at runtime until those transports + are implemented with their complete TLS and framing behavior. + +## Security and Operational Considerations + +- Certificate pinning changes TLS verification semantics. When a pin is + configured, Burrow verifies the presented chain against the pin instead of the + WebPKI root store, matching Mihomo's pinning model. +- The runtime must not log proxy passwords or subscription tokens. +- uTLS/browser fingerprinting should not be faked with partial ClientHello + tweaks. Until Burrow has a complete implementation, the runtime should say + that it is using platform TLS rather than a Mihomo uTLS impersonation. +- Rollback is a code rollback plus re-importing any subscriptions whose stored + JSON schema has changed during testing. + +## Contributor Playbook + +1. Compare Burrow behavior against `/Users/jettchen/dev/vpn-ref/mihomo` before + changing protocol details. +2. Run `cargo fmt -p burrow`. +3. Run `cargo test -p burrow proxy_subscription::tests::`. +4. Run `cargo test -p burrow proxy_runtime::tests::`. +5. Run `cargo check -p burrow`. +6. For live validation, use a Trojan node with omitted ALPN and + `client-fingerprint: chrome`; confirm the logs show Mihomo default ALPN, the + physical-interface bind, the macOS OpenSSL handshake, and the platform TLS + fingerprint warning. + +## Alternatives Considered + +- Implement full uTLS immediately. Rejected for this change because rustls does + not expose enough ClientHello control, and adding a second TLS stack needs + separate review for Apple builds and static library consumers. +- Treat all empty ALPN vectors as explicit empty ALPN. Rejected because older + Burrow imports already stored omitted URI ALPN as `[]`, which would preserve + the compatibility bug. +- Ignore `fingerprint` until uTLS exists. Rejected because certificate pinning + is independent of browser ClientHello impersonation. + +## Impact on Other Work + +- BEP-0010 remains the packet tunnel direction for proxy subscriptions. +- A future BEP or revision should cover uTLS/browser fingerprint support, + websocket Trojan, gRPC Trojan, and daemon-owned proxy-server DNS resolution. + +## Decision + +Pending review. + +## References + +- `docs/trojan-mihomo-discrepancies.md` +- `evolution/proposals/BEP-0010-proxy-subscriptions-as-packet-tunnel-networks.md` diff --git a/proto/burrow.proto b/proto/burrow.proto index ed1f89e..eb508d8 100644 --- a/proto/burrow.proto +++ b/proto/burrow.proto @@ -26,6 +26,12 @@ service TailnetControl { rpc LoginCancel (TailnetLoginCancelRequest) returns (Empty); } +service ProxySubscriptions { + rpc PreviewImport (ProxySubscriptionImportRequest) returns (ProxySubscriptionPreviewResponse); + rpc ApplyImport (ProxySubscriptionApplyRequest) returns (ProxySubscriptionApplyResponse); + rpc SelectNode (ProxySubscriptionSelectRequest) returns (ProxySubscriptionSelectResponse); +} + message NetworkReorderRequest { int32 id = 1; int32 index = 2; @@ -55,6 +61,61 @@ message Network { enum NetworkType { WireGuard = 0; Tailnet = 1; + Trojan = 2; + ProxySubscription = 3; +} + +message ProxySubscriptionImportRequest { + string url = 1; +} + +message ProxySubscriptionRejectedEntry { + int32 ordinal = 1; + string reason = 2; + string scheme = 3; +} + +message ProxySubscriptionNodePreview { + int32 ordinal = 1; + string name = 2; + string protocol = 3; + string server = 4; + int32 port = 5; + repeated string warnings = 6; + bool runtime_supported = 7; +} + +message ProxySubscriptionPreviewResponse { + string suggested_name = 1; + string detected_format = 2; + int32 compatible_count = 3; + int32 rejected_count = 4; + repeated ProxySubscriptionNodePreview node = 5; + repeated ProxySubscriptionRejectedEntry rejected = 6; + repeated string warnings = 7; +} + +message ProxySubscriptionApplyRequest { + int32 id = 1; + string url = 2; + string name = 3; + int32 selected_ordinal = 4; +} + +message ProxySubscriptionApplyResponse { + int32 id = 1; + int32 imported_count = 2; + int32 selected_ordinal = 3; +} + +message ProxySubscriptionSelectRequest { + int32 id = 1; + int32 selected_ordinal = 2; +} + +message ProxySubscriptionSelectResponse { + int32 id = 1; + int32 selected_ordinal = 2; } message NetworkListResponse { @@ -133,6 +194,7 @@ message TunnelConfigurationResponse { repeated string dns_servers = 4; repeated string search_domains = 5; bool include_default_route = 6; + repeated string excluded_routes = 7; } message TunnelPacket { From 4d1f5892809603ba01199cae6a45fa4711295502 Mon Sep 17 00:00:00 2001 From: JettChenT Date: Fri, 5 Jun 2026 10:33:41 -0700 Subject: [PATCH 2/4] Expand Shadowsocks runtime compatibility --- Apple/UI/Networks/Network.swift | 242 +- Cargo.lock | 1601 +++- Cargo.toml | 4 + Scripts/burrow-proxy-selftest | 1159 ++- Scripts/burrow-shadowsocks-singmux-interop | 504 + burrow/Cargo.toml | 54 + burrow/src/main.rs | 74 +- burrow/src/proxy_runtime.rs | 8258 ++++++++++++++--- burrow/src/proxy_runtime/networking.rs | 961 ++ .../src/proxy_runtime/shadowsocks_crypto.rs | 2004 ++++ .../src/proxy_runtime/shadowsocks_plugins.rs | 3061 ++++++ burrow/src/proxy_runtime/tests.rs | 3855 ++++++++ burrow/src/proxy_subscription.rs | 566 +- deploy/railway-sing-box/Dockerfile | 6 + deploy/railway-sing-box/README.md | 68 + deploy/railway-sing-box/entrypoint.sh | 47 + deploy/railway-sing-box/railway.json | 10 + ...subscriptions-as-packet-tunnel-networks.md | 2 +- ...2-mihomo-compatible-shadowsocks-runtime.md | 356 + third_party/rustls-fork-shadow-tls/Cargo.lock | 729 ++ third_party/rustls-fork-shadow-tls/Cargo.toml | 107 + .../rustls-fork-shadow-tls/Cargo.toml.orig | 62 + .../rustls-fork-shadow-tls/LICENSE-APACHE | 201 + .../rustls-fork-shadow-tls/LICENSE-ISC | 15 + .../rustls-fork-shadow-tls/LICENSE-MIT | 25 + third_party/rustls-fork-shadow-tls/README.md | 298 + .../benches/benchmarks.rs | 26 + third_party/rustls-fork-shadow-tls/build.rs | 13 + .../examples/internal/bench.rs | 664 ++ .../examples/internal/bogo_shim.rs | 1195 +++ .../examples/internal/trytls_shim.rs | 120 + .../rustls-fork-shadow-tls/src/anchors.rs | 154 + .../rustls-fork-shadow-tls/src/bs_debug.rs | 77 + .../rustls-fork-shadow-tls/src/builder.rs | 268 + .../rustls-fork-shadow-tls/src/check.rs | 77 + .../rustls-fork-shadow-tls/src/cipher.rs | 101 + .../src/client/builder.rs | 192 + .../src/client/client_conn.rs | 742 ++ .../src/client/common.rs | 114 + .../src/client/handy.rs | 161 + .../rustls-fork-shadow-tls/src/client/hs.rs | 891 ++ .../src/client/tls12.rs | 1127 +++ .../src/client/tls13.rs | 1191 +++ .../rustls-fork-shadow-tls/src/conn.rs | 1466 +++ .../rustls-fork-shadow-tls/src/enums.rs | 431 + .../rustls-fork-shadow-tls/src/error.rs | 245 + .../rustls-fork-shadow-tls/src/hash_hs.rs | 240 + third_party/rustls-fork-shadow-tls/src/key.rs | 52 + .../rustls-fork-shadow-tls/src/key_log.rs | 55 + .../src/key_log_file.rs | 154 + third_party/rustls-fork-shadow-tls/src/kx.rs | 100 + third_party/rustls-fork-shadow-tls/src/lib.rs | 534 ++ .../src/limited_cache.rs | 175 + .../src/manual/defaults.rs | 29 + .../src/manual/features.rs | 50 + .../src/manual/howto.rs | 36 + .../src/manual/implvulns.rs | 104 + .../rustls-fork-shadow-tls/src/manual/mod.rs | 30 + .../src/manual/tlsvulns.rs | 173 + .../rustls-fork-shadow-tls/src/msgs/alert.rs | 22 + .../rustls-fork-shadow-tls/src/msgs/base.rs | 170 + .../rustls-fork-shadow-tls/src/msgs/ccs.rs | 20 + .../rustls-fork-shadow-tls/src/msgs/codec.rs | 290 + .../src/msgs/deframer.rs | 425 + .../rustls-fork-shadow-tls/src/msgs/enums.rs | 375 + .../src/msgs/enums_test.rs | 88 + .../src/msgs/fragmenter.rs | 162 + .../src/msgs/handshake-test.1.bin | Bin 0 -> 4280 bytes .../src/msgs/handshake.rs | 2380 +++++ .../src/msgs/handshake_test.rs | 1247 +++ .../msgs/hello-api.devicecheck.apple.com.bin | Bin 0 -> 74 bytes .../src/msgs/hsjoiner.rs | 281 + .../rustls-fork-shadow-tls/src/msgs/macros.rs | 88 + .../src/msgs/message.rs | 292 + .../src/msgs/message_test.rs | 113 + .../rustls-fork-shadow-tls/src/msgs/mod.rs | 51 + .../src/msgs/persist.rs | 543 ++ .../src/msgs/persist_test.rs | 78 + .../rustls-fork-shadow-tls/src/quic.rs | 620 ++ .../rustls-fork-shadow-tls/src/rand.rs | 30 + .../src/record_layer.rs | 192 + .../src/server/builder.rs | 116 + .../src/server/common.rs | 41 + .../src/server/handy.rs | 278 + .../rustls-fork-shadow-tls/src/server/hs.rs | 522 ++ .../src/server/server_conn.rs | 849 ++ .../src/server/tls12.rs | 941 ++ .../src/server/tls13.rs | 1386 +++ .../rustls-fork-shadow-tls/src/sign.rs | 523 ++ .../rustls-fork-shadow-tls/src/stream.rs | 254 + .../rustls-fork-shadow-tls/src/suites.rs | 340 + .../src/testdata/cert-arstechnica.0.der | Bin 0 -> 1517 bytes .../src/testdata/cert-arstechnica.1.der | Bin 0 -> 1101 bytes .../src/testdata/cert-arstechnica.2.der | Bin 0 -> 1174 bytes .../src/testdata/cert-arstechnica.3.der | Bin 0 -> 1145 bytes .../src/testdata/cert-duckduckgo.0.der | Bin 0 -> 1741 bytes .../src/testdata/cert-duckduckgo.1.der | Bin 0 -> 1218 bytes .../src/testdata/cert-github.0.der | Bin 0 -> 1290 bytes .../src/testdata/cert-github.1.der | Bin 0 -> 1055 bytes .../src/testdata/cert-google.0.der | Bin 0 -> 1164 bytes .../src/testdata/cert-google.1.der | Bin 0 -> 1434 bytes .../src/testdata/cert-google.2.der | Bin 0 -> 1382 bytes .../src/testdata/cert-hn.0.der | Bin 0 -> 1740 bytes .../src/testdata/cert-hn.1.der | Bin 0 -> 1218 bytes .../src/testdata/cert-reddit.0.der | Bin 0 -> 1730 bytes .../src/testdata/cert-reddit.1.der | Bin 0 -> 1218 bytes .../src/testdata/cert-rustlang.0.der | Bin 0 -> 1540 bytes .../src/testdata/cert-rustlang.1.der | Bin 0 -> 1101 bytes .../src/testdata/cert-rustlang.2.der | Bin 0 -> 1174 bytes .../src/testdata/cert-rustlang.3.der | Bin 0 -> 1145 bytes .../src/testdata/cert-servo.0.der | Bin 0 -> 1335 bytes .../src/testdata/cert-servo.1.der | Bin 0 -> 977 bytes .../src/testdata/cert-stackoverflow.0.der | Bin 0 -> 1785 bytes .../src/testdata/cert-stackoverflow.1.der | Bin 0 -> 1306 bytes .../src/testdata/cert-stackoverflow.2.der | Bin 0 -> 1380 bytes .../src/testdata/cert-twitter.0.der | Bin 0 -> 1606 bytes .../src/testdata/cert-twitter.1.der | Bin 0 -> 1262 bytes .../src/testdata/cert-wapo.0.der | Bin 0 -> 2211 bytes .../src/testdata/cert-wapo.1.der | Bin 0 -> 1329 bytes .../src/testdata/cert-wikipedia.0.der | Bin 0 -> 2086 bytes .../src/testdata/cert-wikipedia.1.der | Bin 0 -> 1051 bytes .../deframer-empty-applicationdata.bin | Bin 0 -> 5 bytes .../testdata/deframer-invalid-contenttype.bin | Bin 0 -> 7 bytes .../src/testdata/deframer-invalid-empty.bin | Bin 0 -> 5 bytes .../src/testdata/deframer-invalid-length.bin | Bin 0 -> 6 bytes .../src/testdata/deframer-invalid-version.bin | Bin 0 -> 7 bytes .../src/testdata/deframer-test.1.bin | Bin 0 -> 334 bytes .../src/testdata/deframer-test.2.bin | Bin 0 -> 7 bytes .../src/testdata/eddsakey.der | Bin 0 -> 48 bytes .../src/testdata/nistp256key.der | Bin 0 -> 121 bytes .../src/testdata/nistp256key.pkcs8.der | Bin 0 -> 138 bytes .../src/testdata/nistp384key.der | Bin 0 -> 167 bytes .../src/testdata/nistp384key.pkcs8.der | Bin 0 -> 185 bytes .../src/testdata/prf-result.1.bin | 1 + .../src/testdata/prf-result.2.bin | 1 + .../src/testdata/rsa2048key.pkcs1.der | Bin 0 -> 1190 bytes .../src/testdata/rsa2048key.pkcs8.der | Bin 0 -> 1216 bytes .../rustls-fork-shadow-tls/src/ticketer.rs | 338 + .../src/tls12/cipher.rs | 236 + .../rustls-fork-shadow-tls/src/tls12/mod.rs | 537 ++ .../rustls-fork-shadow-tls/src/tls12/prf.rs | 67 + .../src/tls13/key_schedule.rs | 800 ++ .../rustls-fork-shadow-tls/src/tls13/mod.rs | 224 + .../rustls-fork-shadow-tls/src/vecbuf.rs | 200 + .../rustls-fork-shadow-tls/src/verify.rs | 809 ++ .../rustls-fork-shadow-tls/src/verifybench.rs | 245 + .../rustls-fork-shadow-tls/src/versions.rs | 99 + .../rustls-fork-shadow-tls/src/x509.rs | 93 + .../rustls-fork-shadow-tls/tests/api.rs | 4382 +++++++++ .../rustls-fork-shadow-tls/tests/bogo.rs | 18 + .../tests/client_cert_verifier.rs | 324 + .../tests/common/mod.rs | 474 + .../tests/key_log_file_env.rs | 104 + .../tests/server_cert_verifier.rs | 272 + .../tokio-rustls-fork-shadow-tls/Cargo.lock | 523 ++ .../tokio-rustls-fork-shadow-tls/Cargo.toml | 57 + .../Cargo.toml.orig | 30 + .../tokio-rustls-fork-shadow-tls/README.md | 1 + .../examples/connect.rs | 42 + .../src/client.rs | 86 + .../tokio-rustls-fork-shadow-tls/src/error.rs | 20 + .../tokio-rustls-fork-shadow-tls/src/lib.rs | 21 + .../src/safe_io.rs | 227 + .../src/server.rs | 49 + .../tokio-rustls-fork-shadow-tls/src/split.rs | 139 + .../src/stream.rs | 297 + .../src/unsafe_io.rs | 124 + 167 files changed, 57173 insertions(+), 1640 deletions(-) create mode 100755 Scripts/burrow-shadowsocks-singmux-interop create mode 100644 burrow/src/proxy_runtime/networking.rs create mode 100644 burrow/src/proxy_runtime/shadowsocks_crypto.rs create mode 100644 burrow/src/proxy_runtime/shadowsocks_plugins.rs create mode 100644 burrow/src/proxy_runtime/tests.rs create mode 100644 deploy/railway-sing-box/Dockerfile create mode 100644 deploy/railway-sing-box/README.md create mode 100644 deploy/railway-sing-box/entrypoint.sh create mode 100644 deploy/railway-sing-box/railway.json create mode 100644 evolution/proposals/BEP-0012-mihomo-compatible-shadowsocks-runtime.md create mode 100644 third_party/rustls-fork-shadow-tls/Cargo.lock create mode 100644 third_party/rustls-fork-shadow-tls/Cargo.toml create mode 100644 third_party/rustls-fork-shadow-tls/Cargo.toml.orig create mode 100644 third_party/rustls-fork-shadow-tls/LICENSE-APACHE create mode 100644 third_party/rustls-fork-shadow-tls/LICENSE-ISC create mode 100644 third_party/rustls-fork-shadow-tls/LICENSE-MIT create mode 100644 third_party/rustls-fork-shadow-tls/README.md create mode 100644 third_party/rustls-fork-shadow-tls/benches/benchmarks.rs create mode 100644 third_party/rustls-fork-shadow-tls/build.rs create mode 100644 third_party/rustls-fork-shadow-tls/examples/internal/bench.rs create mode 100644 third_party/rustls-fork-shadow-tls/examples/internal/bogo_shim.rs create mode 100644 third_party/rustls-fork-shadow-tls/examples/internal/trytls_shim.rs create mode 100644 third_party/rustls-fork-shadow-tls/src/anchors.rs create mode 100644 third_party/rustls-fork-shadow-tls/src/bs_debug.rs create mode 100644 third_party/rustls-fork-shadow-tls/src/builder.rs create mode 100644 third_party/rustls-fork-shadow-tls/src/check.rs create mode 100644 third_party/rustls-fork-shadow-tls/src/cipher.rs create mode 100644 third_party/rustls-fork-shadow-tls/src/client/builder.rs create mode 100644 third_party/rustls-fork-shadow-tls/src/client/client_conn.rs create mode 100644 third_party/rustls-fork-shadow-tls/src/client/common.rs create mode 100644 third_party/rustls-fork-shadow-tls/src/client/handy.rs create mode 100644 third_party/rustls-fork-shadow-tls/src/client/hs.rs create mode 100644 third_party/rustls-fork-shadow-tls/src/client/tls12.rs create mode 100644 third_party/rustls-fork-shadow-tls/src/client/tls13.rs create mode 100644 third_party/rustls-fork-shadow-tls/src/conn.rs create mode 100644 third_party/rustls-fork-shadow-tls/src/enums.rs create mode 100644 third_party/rustls-fork-shadow-tls/src/error.rs create mode 100644 third_party/rustls-fork-shadow-tls/src/hash_hs.rs create mode 100644 third_party/rustls-fork-shadow-tls/src/key.rs create mode 100644 third_party/rustls-fork-shadow-tls/src/key_log.rs create mode 100644 third_party/rustls-fork-shadow-tls/src/key_log_file.rs create mode 100644 third_party/rustls-fork-shadow-tls/src/kx.rs create mode 100644 third_party/rustls-fork-shadow-tls/src/lib.rs create mode 100644 third_party/rustls-fork-shadow-tls/src/limited_cache.rs create mode 100644 third_party/rustls-fork-shadow-tls/src/manual/defaults.rs create mode 100644 third_party/rustls-fork-shadow-tls/src/manual/features.rs create mode 100644 third_party/rustls-fork-shadow-tls/src/manual/howto.rs create mode 100644 third_party/rustls-fork-shadow-tls/src/manual/implvulns.rs create mode 100644 third_party/rustls-fork-shadow-tls/src/manual/mod.rs create mode 100644 third_party/rustls-fork-shadow-tls/src/manual/tlsvulns.rs create mode 100644 third_party/rustls-fork-shadow-tls/src/msgs/alert.rs create mode 100644 third_party/rustls-fork-shadow-tls/src/msgs/base.rs create mode 100644 third_party/rustls-fork-shadow-tls/src/msgs/ccs.rs create mode 100644 third_party/rustls-fork-shadow-tls/src/msgs/codec.rs create mode 100644 third_party/rustls-fork-shadow-tls/src/msgs/deframer.rs create mode 100644 third_party/rustls-fork-shadow-tls/src/msgs/enums.rs create mode 100644 third_party/rustls-fork-shadow-tls/src/msgs/enums_test.rs create mode 100644 third_party/rustls-fork-shadow-tls/src/msgs/fragmenter.rs create mode 100644 third_party/rustls-fork-shadow-tls/src/msgs/handshake-test.1.bin create mode 100644 third_party/rustls-fork-shadow-tls/src/msgs/handshake.rs create mode 100644 third_party/rustls-fork-shadow-tls/src/msgs/handshake_test.rs create mode 100644 third_party/rustls-fork-shadow-tls/src/msgs/hello-api.devicecheck.apple.com.bin create mode 100644 third_party/rustls-fork-shadow-tls/src/msgs/hsjoiner.rs create mode 100644 third_party/rustls-fork-shadow-tls/src/msgs/macros.rs create mode 100644 third_party/rustls-fork-shadow-tls/src/msgs/message.rs create mode 100644 third_party/rustls-fork-shadow-tls/src/msgs/message_test.rs create mode 100644 third_party/rustls-fork-shadow-tls/src/msgs/mod.rs create mode 100644 third_party/rustls-fork-shadow-tls/src/msgs/persist.rs create mode 100644 third_party/rustls-fork-shadow-tls/src/msgs/persist_test.rs create mode 100644 third_party/rustls-fork-shadow-tls/src/quic.rs create mode 100644 third_party/rustls-fork-shadow-tls/src/rand.rs create mode 100644 third_party/rustls-fork-shadow-tls/src/record_layer.rs create mode 100644 third_party/rustls-fork-shadow-tls/src/server/builder.rs create mode 100644 third_party/rustls-fork-shadow-tls/src/server/common.rs create mode 100644 third_party/rustls-fork-shadow-tls/src/server/handy.rs create mode 100644 third_party/rustls-fork-shadow-tls/src/server/hs.rs create mode 100644 third_party/rustls-fork-shadow-tls/src/server/server_conn.rs create mode 100644 third_party/rustls-fork-shadow-tls/src/server/tls12.rs create mode 100644 third_party/rustls-fork-shadow-tls/src/server/tls13.rs create mode 100644 third_party/rustls-fork-shadow-tls/src/sign.rs create mode 100644 third_party/rustls-fork-shadow-tls/src/stream.rs create mode 100644 third_party/rustls-fork-shadow-tls/src/suites.rs create mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/cert-arstechnica.0.der create mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/cert-arstechnica.1.der create mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/cert-arstechnica.2.der create mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/cert-arstechnica.3.der create mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/cert-duckduckgo.0.der create mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/cert-duckduckgo.1.der create mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/cert-github.0.der create mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/cert-github.1.der create mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/cert-google.0.der create mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/cert-google.1.der create mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/cert-google.2.der create mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/cert-hn.0.der create mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/cert-hn.1.der create mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/cert-reddit.0.der create mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/cert-reddit.1.der create mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/cert-rustlang.0.der create mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/cert-rustlang.1.der create mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/cert-rustlang.2.der create mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/cert-rustlang.3.der create mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/cert-servo.0.der create mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/cert-servo.1.der create mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/cert-stackoverflow.0.der create mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/cert-stackoverflow.1.der create mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/cert-stackoverflow.2.der create mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/cert-twitter.0.der create mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/cert-twitter.1.der create mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/cert-wapo.0.der create mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/cert-wapo.1.der create mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/cert-wikipedia.0.der create mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/cert-wikipedia.1.der create mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/deframer-empty-applicationdata.bin create mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/deframer-invalid-contenttype.bin create mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/deframer-invalid-empty.bin create mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/deframer-invalid-length.bin create mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/deframer-invalid-version.bin create mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/deframer-test.1.bin create mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/deframer-test.2.bin create mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/eddsakey.der create mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/nistp256key.der create mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/nistp256key.pkcs8.der create mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/nistp384key.der create mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/nistp384key.pkcs8.der create mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/prf-result.1.bin create mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/prf-result.2.bin create mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/rsa2048key.pkcs1.der create mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/rsa2048key.pkcs8.der create mode 100644 third_party/rustls-fork-shadow-tls/src/ticketer.rs create mode 100644 third_party/rustls-fork-shadow-tls/src/tls12/cipher.rs create mode 100644 third_party/rustls-fork-shadow-tls/src/tls12/mod.rs create mode 100644 third_party/rustls-fork-shadow-tls/src/tls12/prf.rs create mode 100644 third_party/rustls-fork-shadow-tls/src/tls13/key_schedule.rs create mode 100644 third_party/rustls-fork-shadow-tls/src/tls13/mod.rs create mode 100644 third_party/rustls-fork-shadow-tls/src/vecbuf.rs create mode 100644 third_party/rustls-fork-shadow-tls/src/verify.rs create mode 100644 third_party/rustls-fork-shadow-tls/src/verifybench.rs create mode 100644 third_party/rustls-fork-shadow-tls/src/versions.rs create mode 100644 third_party/rustls-fork-shadow-tls/src/x509.rs create mode 100644 third_party/rustls-fork-shadow-tls/tests/api.rs create mode 100644 third_party/rustls-fork-shadow-tls/tests/bogo.rs create mode 100644 third_party/rustls-fork-shadow-tls/tests/client_cert_verifier.rs create mode 100644 third_party/rustls-fork-shadow-tls/tests/common/mod.rs create mode 100644 third_party/rustls-fork-shadow-tls/tests/key_log_file_env.rs create mode 100644 third_party/rustls-fork-shadow-tls/tests/server_cert_verifier.rs create mode 100644 third_party/tokio-rustls-fork-shadow-tls/Cargo.lock create mode 100644 third_party/tokio-rustls-fork-shadow-tls/Cargo.toml create mode 100644 third_party/tokio-rustls-fork-shadow-tls/Cargo.toml.orig create mode 100644 third_party/tokio-rustls-fork-shadow-tls/README.md create mode 100644 third_party/tokio-rustls-fork-shadow-tls/examples/connect.rs create mode 100644 third_party/tokio-rustls-fork-shadow-tls/src/client.rs create mode 100644 third_party/tokio-rustls-fork-shadow-tls/src/error.rs create mode 100644 third_party/tokio-rustls-fork-shadow-tls/src/lib.rs create mode 100644 third_party/tokio-rustls-fork-shadow-tls/src/safe_io.rs create mode 100644 third_party/tokio-rustls-fork-shadow-tls/src/server.rs create mode 100644 third_party/tokio-rustls-fork-shadow-tls/src/split.rs create mode 100644 third_party/tokio-rustls-fork-shadow-tls/src/stream.rs create mode 100644 third_party/tokio-rustls-fork-shadow-tls/src/unsafe_io.rs 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 0000000000000000000000000000000000000000..5c04f3f6064a55c0160bebfa7ec62d259a476722 GIT binary patch literal 4280 zcmWe*W@PkaWMFh44DiW43-QQ4CV}G3>^&Z3~dap2aFEr9nd)5@Eu@bn8RShpwBRk!SR6L0rmqt2bdUG8CVVo z9pGRPV-RK#VBkH#dw`jNi$Rn@h=HGhhk=vfKO+M}1_J{FKLalV16O)}etJ%-UUGge z12+Q;GXo=RV=PSOpbXuiKpx5H(XI)ay)$C$8pv}M_{GXA5kpT=q)^Ia0F*1lTGxIfYGw{_g z@KrLf{WfS~`)<(0dVc}f9uDc6xjXlLnRLm3myJ`a&7X)r-?-UQ*fZ{lCQs$6sT@}nc=@)Hdz zlr|fHX9==D?2kI8;r$d z(8TNqQXt6E#O!L&#O&CF;=m?eExp9Nl%o916i|X{64TO4EGS6LOV7+pMe;|Jkd|I@ zPJU?$vX~gmr@D!Gi8+-enaRaqGq_>mddZ1RNSvG|K7>rZUS=LDKdS(hUz**-kEA|V zFR=(+pcG9Y30*~UKAMW$3N(QdbS-6hNN!F^MdG9uBXQD-kT@BoNSw?PBu-uq5~l!# zQvwQ9NK#2mNi4{O1RmVW5Rtql5m*=`=jZ087A1o-ev=?fv@A0vH6I-8pm;4VNi4}s z)=SO<#cMi@m!I3jrKOi%R$>TN2w@w6*&ZnaO&{s(G~ZN{f;+GV{P{1VFqT zw3x5VFD)reO4ZHFPtMQJ&V;y%S4$5n4%R3J=ccBVCMT9;=0gnR*3zpi$;?d$E0TgI zznsj}ypm#Md${3p`MFIjAoWdb>G}D3={ZdzkdQ-5dPrqslMqA&O0aPud5f#Oyc}d1 z$TSYHv-Ofvo4CQOB#3`Rpd1wE8*qd26F&)WJoX=BJ0RcE-rxNb3PU^7LgOGO1rvP z%-L_wOWKw%`%NA=AAi{v6g?k4#foOxR_tx#)Yvw|S+tmP`w4c)uG z1ou5EZ{m+_+}K)hbyI&`;H8foo>CvH9^Fl!9=T`)oVBVPT1!N6Y3ZwcFKHntyU>^El0O^q=$aUO>04<*M%;vN_tJ*AyIO zO&vqyzizlX@!kL8+lzP>t#>;d*dxAw+g~9D<}U_K%pVMzm~KF7KV~MYX0(7Za6+w( zKt(xoC<~V`uX}2KNKt8V38?DRGn6)v1lKXbLU3{SocyH390g}baE)hdU}9iuXliI` zY6h>&w5_#oi3ezbtFq_)0yDAqTb!|CnOW)&(2+8 zm#p1lnwi>iLh`7a(vRS&`yMxV2^`aUvunD7;Bm{D0xSzYBvwsT?ePmLeYEW@$0?CN z=ajNG`<-`Vo<5zq?!d--)$3i2&+oD;+z4)aD{Fl1kw*K7B~#p7#aUFF)|pKg2d!m^bB+i zv=(SAP-{~`(U=4(m-Um2a*FlSOVYs!OfR`8$3PsUO@T$gfDf(6KyAi>v;C@HD;3UW zr8mEgyHa;g_DKB$RW^pBD!*5r(9Ny8H@7KOr!mRez9xDn@+Mppkek_3ELHI7Z7AP6 zakf|K?~GpS+)Zb<|JHq|;9fMtv)$5{&7_oaayFPc44UnQc0WVTG_0%)27aj7Ucdkp*(_Yb?5>5SnQ4g?hPnpYpbXC~ ztO6BL2u@8dElO2zPAw|QOv_A8EJ;PQdqG8jfsui!k)eT!fq{Vms0gqyuz+)^Qp^y_ z`?t>smR>5Gu$TW`JejO?tD;e zYPfE|`%c}4s_?a+-oHe~<3J0A;g3Y0pmgEwPkj*gloXaw0e1KMTE!vaGYd z`P$|4v&*j6k?T*eeDupKSlIGPoSOD++Z7^vjb2MIYXq?rK3#2W`_N)HyID)poXS^X z24CV2Xul}Te)1EV_dggkzD3FV9?vp7+8T6$g>z57#ip=&|RS2rU}i+$wfJO>8bf8pt=K*n~TATCpi(E zo58&T1r~P$R|97@mQYy*1_NsYOIT^b#3-f@=vAxDeXBDC@MD;G7p8X^V5~cL-l`|JNNAQ~YP$O=%-Zi`COjL{?3^^m?(32#4pk z-A^aJZ#H`^8`Wf#MKegBkfjY92c^DY_ znHj_#SuXP|;5;BY+x>p~k(gg^o8MJ0zh1H_^w_ijpO;U3HuwlfFTKPU$IZQcU!q!4 z;B?O?6E)Yb6unidRFdj|uMew0xMS { + 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 0000000000000000000000000000000000000000..fcbaaadee71934e744fe9044c344f077d1f2dbcc GIT binary patch literal 74 zcmZQ#U~pq*PV$?xP$fKC^3Yx{!&N)V(jylHbO)_C=, + + /// 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 0000000000000000000000000000000000000000..ac81cd85ec8d9eaddab3eedc92cfbf72ae643905 GIT binary patch literal 1517 zcmXqLVtr}Q#By-~GZP~dlK}U^iNA!7xlUPd@`K^7gN3RFylk9WZ60mkc^MhGSs4u6 z47m+B*_cCF*o2uvgAMr&ctIR4VK&Fy#H#!}Ls0`^kN~$ZPjG5cS!$7jv!jBc6HFa5 zOr3$8IIoeRp`nqb0T@Jy^BNf$8X1`z8Jk*~T1FYj7)lvPfJ|cM5!BL4EGjNZP0q;6 zOit8G&d)VyVpKwQ7$YkKa}y&!gFzD`7gG}>Bf~7qHl9aLUFAQOWme^Df1ajpXHyrw zKrYxM=EvQ5#kpU$D3q-fz4dU(*LeB)&d1_4k7wnH=_s>jt1aU{b&AbvVQALW-{Hc$ ziXY!=clciTf4*s&!-rc{pYLAeJ#zGim_&Ds>cqBtueLDCeBLte-4Tmjx6Zrm@0qzy zO!Qk(%;6`D3ZL1huL`L?x=3ShZuHhrrfvr%MkOb~4kp#7RoB05`Sn`G_Q^`go7tI1 zoHX@+%AAz_!qpoV_A&j7+k@s>+lp1D%&(Sh=DE1-rqPO}wngkgPWid#X7Lnu^=T#1KW#4<$buB`v52vVOcq@EY(wK6 z16Qd@(FyxsiTYOy+Z*VDqy<^j3{;vh8(VrK4?VJRzA3uF#)nZg*kGC)|FxF=B zcCTSzU}0u(GjL&22sxb+5$7DP{AUx(9;MdKBN5&+KkZ}|p4o59w2m>9rH)BKcFv5l z_E-Dct<~iA#7x$IYVLIVQbEl2gq>T|Px^!laWa%K1TNn2^)F-PoUq&-<`w-sORCOD ztDpWpufu5;@4qYEtWTlV&H`KOZs5wK;Q4xj&1gN7BE!2I3WrT+hPD|sNxW^TnsNHkuG9bWI(TDtgv`&L9{yr6Ln(vfiBlWz zE4g_I99w9`>C_(QXV@*x{Q9-`@r(}>lBThJgW8)4wioPOh1}?#;!DaVao^9Ul=U4h zSP}ehj@ctE1CuR97H?QNFEJ@3Khi4>i(Peg-=Y;;KdrW1&Y63?b4_Iag1?c|T{tHE z|AbsXaDxj7Mg}+WE&q<1OPc)AlDZ?vWMUjuRK&JcI#PaCkv7kfqrJ`6iUI4|OFhrO z<5s$LW*zsOmd)iM(9c@y-87MOu>IXJy~m`Td4HlQFaD zO!lJARcB_bY?@Me!i9n1P4k+><7zYQB`q@5UFQlPv=z77IzxEVl!s4*1y^h_FSe8O VYIt>gIdiYf;b1dSwhn8i<1gn1ux(0t!L-`6UX@jtYhb za^k#(riKPaMh0LICC+PPYG`0+3Fa9@8MqNJTGT)oWHh%hPjG5cS!xm3R414dm|;#Z zXkt`Cb~Pg_19KB2KZ8LNBNtN>BO}8hKe6KnVDsuhnl#n^Y|1)TpKZ}>xY7qeGVC-XWx_fP!l6CaBxwEp<@vdi>q{JfdI z&$G?nFA1=7dGjl(HI02vop@I0XYnpmxpyzu94_^X2(1my=E|Ne;&lD#8ph7euNMD1 ze?Xh3<QhU6v9MfjmfBnMJ}ttU;tj;!LZ_n>{>Jrs#1*&N;qGz>kZ=pqh=CPw50 z#tlwjj0~-l_L=UkJioZnN#@Dp=KDUSjJ8CA+ns;ubSL zKD_zoX~(E%K@)dBF>CkNUAcrk!*th_qO*Lj_-eyHRsJzQTAeU&)9VAe6Uuo#1=N>X ztUV&Mc+27w9-BqE1=)|5b$BKfRxzjg$byw)dZQ)<+lW^*x`{@c@A@Pkt U-KQ*d_HNZbWXv>e^{pxO0B){}_W%F@ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..1dfb0e70faadbce1e2af09df6a528e061e25c471 GIT binary patch literal 1174 zcmXqLVwq&n#9Xz2nTe5!NtmsEikFu9+0VZYtBDY>v5!Rrz^_k_O@+0cK$Vhyc_=M+HNJCPpRX zfM8^0U~XdMXE11D(NK7*;_BnV_3jM;CAE1su6LAunVIoDLNVcINeiR? z8YbPg%L|;V{5CDQV77~KU+-;?6YSzvO11#9@ENZum__6Ei z%AX$T2Gib6sff$(TpW17pe=2oYuc`m*NYEN{ONJ$|HT&uy0y9`1*X?KRKCwU5}CKV zxLy2{k(}pSDo7OX}$`duJ`xCn7GXUuck7cw&+?HV3B|Ow{DX3(c^L<56d5< zpWvU_4AX93-pTe^Gni=^b)}lTvAk8T%wnppKD+NSI^X@167|~RDwgfKGc-t)FK00 zkkjN@%nVEnj20Ly&_mLmLr8m34mfAXDzI=Fa2T+$v4qMhFd!!@Zg8?kLaPQ>kv3t8&)n;pcz>~A4S<5GzOfynk>EW_^&Rg3ptBP}< z)QdHA=A74HmS}dbTr5y$5WU8FL&AgdD;&!n%GSgk*!^=>wbI<>44idq+{Byj%ux%U zX<(T&t^e((|;@d`W>zv{oMZX);$) F0s!GfpzHtu literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..75df0cc7c0be230aca9aec4f9d5e958ea3b268ac GIT binary patch literal 1145 zcmXqLVktFfVvb$F%*4pV#L2Ln&&$W8X?wi^FB_*;n@8JsUPeZ4RtAF%Lv903Hs(+k zHesgFU_(^{We|r;SSGk6u_!GwH77+OBsDoBFFz+gJu|gfN5L~MSs&kIz_;M4`c_2FuP+>W>tP(qM@*XAV`E=m@7Crzoeu%B{3(JSQlFx zSc3F23mYL^9GqHImYJMdtPqr+UxMWBRD`P)bQRo<44N2~ki(6Um4Ug5k)Oe!iIIz` ziII`vDv#9>t)G66zV4Hp9^cmyyiwl%^!fP;!nvAOC-`e^zGs(NB;PbkSTQ>;hM{4T zeq{Z(_MfWGZO=2MgJw;5SorvcF0Wtyw-cT>w|$lhTc^FPMCA4P-)fJgcF9iD%d(D` z(&--dUvU@2YFpSv}ul_|x^< zp@j3okxbeRXH4Ah@4Po5HJ9nU&ctQQp5JSg@T^JmTWpoPa_c)2yLDI@B5(Z)oBKzaFUp#kLzU@nN z=$bp%1rG78?09bA&&Hw6#>mRb&dA8(Y~W}h1LGSowh3gEloVL$>*wng7aQr77V8@* z!eyD-M4_@3Fxd*d87l^~v6~ojO=_MECfD@mr0t=S`hXETK zOQ@^@19GC^1}6$e24&gDUE!x{Uy4`ye3T3QdnP!~&Yl1MvCvPOSj^rw$Oj*ZHPOi4 zcRch@`m59S^%FK3pOuafXkz;DL;tIA!WGtY9~(Ii-d$cg-Cts7?!E7mCvxnnbI^Hv z@YU|JdB$wzvb-Gi!FQ!oRyV1C%)IIwx9-cr!==k3Ty#I#6wFw&D|6e_@_K&5IGsNW z+O6C(KUBtl5eZo`HBPWXC_(+){6ppcm4nM;cm01A`TX|z^^>N(SD9rhP&H@bF}~WF z-|Qvte=Q1mD7un8vbuSHke*x5CWe$U8IQ|1DqrMDJmI>e#mpzCI3=V1vrpTw bqTa;we%Q+I8{*#@zwL3~ZonM-g}Da+m?WBn literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..9f8267ca9259269393b3af3301adc69efbeee840 GIT binary patch literal 1741 zcmXqLVmoQj#JX_-GZP~dlK}VA=WBzio=e;6+wHxozrojlmyJ`a&7PgVlsK;uh;L?OY-(w08D)@z)j|$oo`BT6 zyyD87vc$a1L_X$@#ekO^iy&K4xTPU~XdMXE11Dx9 zt-*M5vl46J58-6+~e-YnS^s(0mqlU>$zrX_YO z7e>lnaX9_|^XoMm-bj0yRd4FqqAazQ`Oxm=b0@c)-_W)uq+aE>*Ws&5mY*9F7z~cA z|Ms~q_5R+K6_w9tJlVG7_Ua2Q8s`+&1sh%AJa1J{zu`%`!9vBG1L+fv86ZiFfuV2fN~1d`Am#r(ohGMmzSfO?i>(oP!IDRBO^<(L7{;PjBmi$CIQu$ zpIlskqSL?=u8OJ67OE;a5#;e=6oq($z)&x_s05tFWQ9R7z+?c*618kx+H8z0jMple zSeRIv7;{;g7&95u8Oj(mEBAevXKAVUA*^_Q^3w9B`@(DIByj!s=^LvSqZ)g>g@J*w zwnJgc8U_XyW(IcyS0;t*XC*7D*1r6>hIR3IhWnEZ5*4_TgS_&!8t(dkmEYL)hDnj( zb4y~2MY@34Ba1k`iq!919&hh3nf3h=lzA0&>cB(;28J>Q#}lVE-dA$-5;(Tdiqok* z&d;!0n)&r>@8cOCCL~Q``v$f57TjJ%h9&pccpC1Hdp>ieW7my2@mr$R*I9KJTl-yz zoqwU8m9LaZK`B2V_@+gH-R%n1RYfz-JZ;@s9BXx#e=Co;n$90(L05)ShWkt7R%bN% z%jc|pwPx)w4`w2s5PB-Blfn{fzq`SF6uueS5Enx z+lE|Daf8b#MuxR<$tPcQ{mr^lx$+^~>ysUM^_Ko8Y^9bwcQIP=XWEh@`MitY<$n3b z-mPF?+bXhj%Jqw%NY0H*9$$oFR0vTes9}~g{;Mk7{f0ctjg3}waVh3 zjgf=-P63gX$pSNkyBka{KYA`Mej?(DP{8}5k6b$peKuxyw26H?ky@S1u*+_>VymDp Z%XhUZdFO_w&9c&7T&fi-P0mFN00878Z`%L> literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..dd6a50f3273eab884f2245df8b0ad90967850380 GIT binary patch literal 1218 zcmXqLV%cZV#Jp?)GZP~dlK|U|n=Dr&oZY^4=`Rmasykx9%f_kI=F#?@mywa1mBAp< zklTQhjX9KsO_(V(*ih6!7{uWc=5fhP&vZ^LDpByvOE#1=5C;i!3k#H&m+Pfure`Ln z7M18F=jR$K7|4O7n1#jRI^1*elM-_jg7WiA6r3FmSO$@*wN}Sgi#5FcD zHnlXhj56>i$_bhV>JWPr;P!<01SzMHBU7GYT?DWUV_|3c9PpL9j&kAx(6G-q6 z7xw({!aTkC{`1S;={`z<%kJ~d6nGmRSfci*_V}qJJz?p3)Dx8{$`wlsMsZS|b>=6L$G$97*IUlWRR`MAH(sWzg8 zxnW}?_bEm) zJU6>?x%MJ;1>vn1Y#lBu8n91(HS;SIGb01z;wHu>gC@p010go%P+4I{#{VoF25gLs z|CtyW3}iuKd@N!tBHObUy;`xVrKj@MmRtNYHXWT>w)2aDJV;uZMZ!R=L4^5UfN9u8 zpWSI67C(}fK0JMs?Zr?7K9B-_keMvZOpI+1^Hf>H3`E#CwAmP0S=pHx;VdSDG8mhY zktNF@!$1YbH(+d&$S5f(u+rDhPcAM%NqGhia8*ogCQwz$iJ*j8jG|BUtqexxXln|Oi_+8QD)>A*n-SZU@a4}}&bjl4*r;H2@M(5(V@8(7Ea;<-Ot>yHAzF(~c zZ*RqJ=J~E&>hQ0X>(^()itPp7Qk@aI8uot9mVH@wo_klsoX9H^F8|H>dtpZI_QIy< z^SRtnoENI^9dW*)wdCGBN45Cf{WDMBUH5QB{`~fTCEMkt6;^NbKlU{}Hg>kn%m10* z6LYTJ{QTGK*lOmEvP<52rEQP@+z72zJ0w_s-;QZp&F9tCRc&VZJHFqTTzJ0iezC!? z>eX+xB2PHASPP0v1${orCsMm8x+r~D;oETcw4*ziyi!f#x-V$iD9j0*>GM2j-uG-l tZQerPW)-(n6}S=i;Y98&EuRc3p0~Jx*@j# zCmVAp3!5-gXt1HUfhdT>CCulNnV#vKT2!K=;F*`KXJ~I=3zA?KHiJnhcx0w$C^!}u zmlh@FC8sKc_yj9>R3;T=rYN{NJ1YcxI2xInDHs_T87MeA8XCxn^BNf%7#o=yfI*Zv zuaS{~v9W=Xv8ko0Wt2f0szZbg1VQ$32y;0n=47Vj7v*Ip8j2Z+fCSlvd4m)46x=}e zW)>&s8;TkTgT%RndE7HgJW7+GZibq{EXSpYVZ{@iQ|1XJKJxVsC($s>%WiBo1vhMpjmKW=1v`i^-sIBS^hGOXEs| z#$^V93;Y-Ows~iiloVL$>n9iG80)2Ere`Ln7L|a4Ngo!=ph$+tafnYaB!*p`ogwiH zie6_&L%rmp984okC^pg#Y(*Mjl2cCId|ab(pu97{#QaUH}CSiVvIv zf(;rc!BjFbvNX0DG&UKiz(fof+a#cR^OK7UP~2(|1y{w?<_A@koCpe?Vibj>hf|3G z56Dx(pm<|4U@&N6WM$*hW@BVwyjID?!oP_r9&ce_V64d6zgLQZfrXjD-N2PekzwAm4ww2rCa!ageCMy8 z(#E##ZnEy9ukp##-MYA!THd(Mq~H;ve({*m*8Pp;*I$cftod}}-ATr^@@GtV-klUE za+wjyP|l#_>dGD&suDKMexd%y@40Ocl+}{g9`LS@J=PqP_lTbfYOf{OUJnB|CPjuB z3_@O{%?Y`HVQy;PN^1W%B#Ij}ztkeIL$)w0|_vnwcdp~4sIK8@I$A>E` zg3=A1FaBWtbK7pt3(L0sJ0*);Dlxk=7`QMgXg^y2CV#({>k`u$=C(&zm!I2}zH!&4 zo{a4FwVv}QxrH$)h%AcL-4c0zQ;%)wwc^ivS()k%|M+1v^UnHh602EU?B)UhfvKOl literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..78a66bb47b43ec0f159ad7b272bac79c6ca9a5fc GIT binary patch literal 1055 zcmXqLVv#mzVrE{z%*4pVB*2!=9KBoQZ}R#B*H#4k{S!0bW#iOp^Jx3d%gD&h%3zRV z$Zf#M#vIDRCd?EXY$$3V4B~JJ^SETDXF8`Al_+@TB^yc_h=YW=g$2sX%k@$+(=(G( zi%Rs8^K%Wg4KzVg%)&}=9UhtK848ZY#id1wdC92?u3-v6`S~RZ&W;9h;=D!%hDL_w z24D~+&T9%7!nWr*!mF>(fBEgM=+piQuVw0{1W!D%PR{O1(aQ-QwNKAEA5oU&TKZZn z`}oRD-qy~Xk`KSy`?>#J+{D;!(8SnmAjHNTDl5#$_@9NtfQ^yyKNBN^fh2?c6dM#8sKEFJjBOGb zB_#z``uh3F#RVuaYTyZ1#nfgCRh66witu6-h5E3t2L(Mmx?RISk?riLmt0h0;0@BM&1J6P(3tz|O`B5@Uo5 zF`}h)<|al)hGGHv6Nw$-XY*xP4vXFP;e6NgXG;4or3RUFWQKGw*JI8_pwb}G59bd}VvZ$0~N>RH=4 z7aqBZd@eMVRX50jnas$@5@!%&pbq03Ft$l&l#~=$>Fehw7Z>OiWM_gyLqENw*f81H zz!0vGsZABC5U!>uwIE+VIkl*y7^++^CAG*v666>`79j(HCiK8n1W75dNEt{NaImqZ zH}WttfqcrP&Bn;Wc&(C&mD#`sq(q*@)WF!laDn~;-8OBgt;t0>#puq{4=i-6NXoSI zHL*z6OD@VWXkuhxgPX>}#M1bcrSTKP2ZmCHz{MNB{$;G36PCNfyrQ3HN!1x?_0!+y zbvVu9{dc9C^(g}bV{PWIKRgT!EX)jU1};nriM!?*zQ~*F(KNZvY3J1Ij1q5OxqZ?4 z7ImyjKNyv4QEO|+`c{O5bLS^MTZ#588j>R zeVAuysrVtRcz*KI@~8X4Yv&|z{rKq{s}`dgd%Oi|Ej!p+4+A$QMTQk?R17D7kK#Z2 z<<74Kf2z3NFzE8OuQO~8yIRX$^!U6KlOn_PIX3%j4%j$VZYWP#$hK8k;mPeUTJ=9v zs;2!>`xU-!5?a1uZenC)D4)1ghF-`gW$rS5dWw+0E6Yq1(Pc3T6FynF0oyEh3k7v z-p^)j!Ood8@?^Vh77OScm*&6ce{`lo+^g!P@0B%M^1iNQ%?OSNUUzxsSEKcI+wLY# zS@+}Wi3L|`3k!;6N~O8?ajmTMSz~F-G@;^o_UX&Y2V~Wzv|s(;y_C z*_cCF*o2uvgAJ7o6hIs*N0~4btab6>H14}Ru#5Hgu-6CNFL5M|MAd8$G z6%3t?4VoC0ko~~O%D~*j$j@NV#K^_e#K_3-wd4Ntq#il*ALdRI>T}vJ{7!9B{rlo{ z_LBd(FQb$n&HN_wauV}Q;Tle^u3t{q;=)u`RT;O)UwoOlduPd;aG$h{$>rvu!T&4; ze7BqIj#n)=w-%rB%I4|Zt9uS_ael40-2MFq7s2EkTO3>OIvRLPmfpYp+XGi7#Rt=z z#LwTGs0O+;E0tK1}Wz-U}I$b&&0?8 z(Za_f#v;;HQop|S?7OLFZRcEgw_0se6)AiF!ii->l z4B+aR+Ek$G;7W>83-a}oQ;SN9A*%IKQi}{sKu(co(KXOE&|ILtK($R7sx7%F2hDV- zuH>Q|gK&^81(pB#e|$nxk0IviIJhRc8x*|kIlYWE5fHrZ&~+i_0pokEoM{y-ggjW z_BrGxvRg!D^@o5$rQ2(dF40;6Lc+cW zgUvoWajXyd)mI)YonoOb&bc?@q;!p?ZuIAKSKdy)EyBE?Ggw9b5D!b`47E>*AA^Jb zI`v4WY1@k~-2MOC?8l9>7}*LtO*K^RKHU3SQum?9Y1_!=z%3=m=GGkB*cQG1jLDJ9 zXRb5~PGwm5MM#P9*OunO^*!5ridEK_2OnaXU@!PD=B?Qj0qeb*>mD7qO0v#LzS*!p zvT#kZ`c5D51-HHxi|*ID*Z+BGj!~mA^M$s+!kH$!Cili}4UG7{Y4QukZ;|^7XaDup zICnnxxY(WV7q7X^UVbY3WjX)f1>2;Rn`H&0ZgMiTJaD@f;HN+ZO`3)klSa}uy5M@Ny&Sr?~dL1Y9g((znRZolmGMRVYa}}kLr(Z6xbE`yhauVhDH`pu7Nv}#i7B5N(Krb zv$%vM-ShL)b5a#Tib{)16oONW$}*Evixqr)oDD?`gg|PTg}L2Bf)&6P1sNJNF)1N? zijkFpxrvFN!JvtWi>Zl;kzt!4Q@7mXh2^uY@9dvqw|(kz2b+a|n|{_Bu{9Nb`_@zc z`^D`&S$fl_FYfXFsr_x^&vPxZ8-E_1e((LxPm5pf5$cRvzA9+-4`ub!OE%6)zI}aW z-3P9?*BQ&c9E)=2^%XFF?6*;P@d=3>?>B!qC)}T#qN%FPzHrMWTfKw_=Pz-#?>_7C z!S~Ris*bMRyUs^SUR&S4fqla~!;eh|58T&VmYXBVxAjJynv^TE?S{%FQ#97{*Q-5U z!SQd}3!987!S9-_IZO-fEVJZF7{4>_sPCwMre_)Tp0q&T+-`Y2u z=B$wjsBd}Tb9T~Yg_p*Af@ZEa+N!x(RcnjK&!UH?QocWH66k#~_fS{IFN?IUKgmBC ze^&ghjw%0D*8c0w{f&H;Q^U9HReOJp@#1SSuHAjyTaO;?3SE79&8vytnr9@J7^(Vc zeN1(^z;tfaInOPx=GQcDaHxLnyn1=g;{Mvs#$P^5|6lDpe9rs9)*GAqT+f#scYK8i}I8PCVMGW<`Rw!;;xpC@-r8{}W1aBSB@vw1tP{$i}Hq7SYbk?1x zz291T&pBd$DO5O5Wy7_?UtbRHI`dZg>%T_F8w@jStxl|~nqe-%I+cl;k%4h>6QhMe z6Qc<@4a@Q~GX7^_VP;}%GvH@q4wV%K@mUR+85#c@$btm;Sj1RFo~+kaE0ogqP;b=_ zO4E7&p;Nff&Oja{t;`}}Al4v~P@N{_$~*m3XMlb!%UP2Z|Ns2;Hb`LO&}L&~Wo2h% zWC<|{GEjx_4H(-bGfGMdtn~HslZy-V3bHfx()07v_0x-s3=K5l%9z?@p~_HHfR*Vb z7nK+oft(=EqHUmQpuRwLfpVK7RDE($4yvgzJvj!}AUz5!CI&_Z9BgdqjXaD@jBpl{ z0WTZ3HX9=g=1D=T4LLe9IKtpZ?J3`en8K1&?jNr03e!JT>WfZ1ih+ z9$Qxa-tLL-#JpcvuiR|@{IJWCx*IvykGp-HH2e8g#eHd$B&UStT+!UbdoV5fQMspw p#@o5qrHqS$H#eD{PwGCY(!F%MCC~0UEyw*q=l?94w(U@pJpk9OB0>NF literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..bc42b61abcbe360423cb87a967b31ce7ce68cfa2 GIT binary patch literal 1740 zcmXqLVmo2b#JXVtGZP~dlK|7^6&u3YTxDt+eqVcG_&CmhmyJ`a&7YmWlsK=Ek)eTsg^{tTrKx3 zyu_0HA{~$u^$cYVq(PF*!XkO8<;8lH$#4aF$@#ekO^iy&erIH5U~XdMXE11Dy7l62WO^;hzj_b1?N3`bL7XzpalWPvOP?8 zE5>Ilam;%vQ*xkK_io%vjyuw(KX*CbtYdu?`Q)eD?+sX1ndm8cHlfC zTaci*&hUV%%assL-mUD1El)%#E$a+1U$@*{nvX@j#zSP+uZjzYwCi-HTosP|#-4NR z>-3s7PG|jB3$TP!R_Yk1!~Wy_ti03Oj&zPc53rAwFOn*J_Rq} zoA{&HOS7_IPgqk!^U)qn=GVes+i$Ba^wca)m~yTmYxdJW&TlK(?|kWC+oO8y!}gO_ zP8;4XC~cbe(C+#}CT2zk#>Gv{s$)s|cQ zGd3NaTDJ3xfhE~x; z{LjL|%*46?VxB4sDCRk|*%(<_*_j#HU@Rts#(t1`d6vd@gT@vEhXr;EY}%|cN=gc> z^!1aAa*Xv-GSf4YQ;SMKajg$aq#-`RLBWoY6bee9&W?t<#(K#`IhZDxkZOV*$UzD$ z<_4w)#%yfqjXaD@Oa`EQ0(CSKqnI?*f#v1psHQsy1RK=De8m2W zt&)j_iKU4#kEMw*i!pmRv1&1@vBz5& z7#OP$+9*9_U|?Zpa5r#eQe;@WP-}&BzV(WL%lf4?&Ig==j&5Q3crV-4%kQt{)F`iS zObWUxzeHR`Rq9uLasP12a6-95qEb-wg;%|k8N15EYo#<8$`}F{Z}|F`v2spW?hf;c zex4;&XQb6nf1lUkG>iA&m2TFj5POx4k?dWqu>P!gwIHN;$H6f`fDwE z!ORRQlN=tVo#T&5`tpqFr>&nH+w1O3+w-fs@9aoS&p8{^S$=bC%kMYkhxH0fu6Vi1M)mG>X{P543c6QB-b&5gd4)wxf9}IiTB55mp9ZSQ z-MbgxaC??ov~Slu&m~%3&!0!ie=2p<%YN%{$<%p^$xZc~nyG?m-^#4M7u@sgopb1B zRYGq|+A{akEtme6>|N|!obZa{_B6qH4!S1}T@&8?bg6lxY2NJz?|em1^psyXIB%h+ znPb@Z?J7G~)n7gByIiWKwzbX9f920vlMfvdtXper`r=%$_h$VzC+3#7*VG(kUYV7i URC^Hj=z{xm{@brOo~Q%>0OqlF_5c6? literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..dd6a50f3273eab884f2245df8b0ad90967850380 GIT binary patch literal 1218 zcmXqLV%cZV#Jp?)GZP~dlK|U|n=Dr&oZY^4=`Rmasykx9%f_kI=F#?@mywa1mBAp< zklTQhjX9KsO_(V(*ih6!7{uWc=5fhP&vZ^LDpByvOE#1=5C;i!3k#H&m+Pfure`Ln z7M18F=jR$K7|4O7n1#jRI^1*elM-_jg7WiA6r3FmSO$@*wN}Sgi#5FcD zHnlXhj56>i$_bhV>JWPr;P!<01SzMHBU7GYT?DWUV_|3c9PpL9j&kAx(6G-q6 z7xw({!aTkC{`1S;={`z<%kJ~d6nGmRSfci*_V}qJJz?p3)Dx8{$`wlsMsZS|b>=6L$G$97*IUlWRR`MAH(sWzg8 zxnW}?_bEm) zJU6>?x%MJ;1>vn1Y#lBu8n91(HS;SIGb01z;wHu>gC@p010go%P+4I{#{VoF25gLs z|CtyW3}iuKd@N!tBHObUy;`xVrKj@MmRtNYHXWT>w)2aDJV;uZMZ!R=L4^5UfN9u8 zpWSI67C(}fK0JMs?Zr?7K9B-_keMvZOpI+1^Hf>H3`E#CwAmP0S=pHx;VdSDG8mhY zktNF@!$1YbH(+d&$S5f(u+rDhPcAM%NqGhia8*ogCQwz$iJ*j8jG|BUtqexxXln|Oi_+8QD)>A*n-SZU@a4}}&bjl4*r;H2@M(5(V@8(7Ea;<-Ot>yHAzF(~c zZ*RqJ=J~E&>hQ0X>(^()itPp7Qk@aI8uot9mVH@wo_klsoX9H^F8|H>dtpZI_QIy< z^SRtnoENI^9dW*)wdCGBN45Cf{WDMBUH5QB{`~fTCEMkt6;^NbKlU{}Hg>kn%m10* z6LYTJ{QTGK*lOmEvP<52rEQP@+z72zJ0w_s-;QZp&F9tCRc&VZJHFqTTzJ0iezC!? z>eX+xB2PHASPP0v1${orCsMm8x+r~D;oETcw4*ziyi!f#x-V$iD9j0*>GM2j-uG-l tZQeqBc{&=38Hj)c*@bz79sLyCf*k#vJ%gS74MhxuK;m4&+(D@+DVbom z>A~F0%)_IlR|FQBO}B4m$O0^FOhnrsq5NU zw0T9h&pGYgi>}+w%gp&~bTMNMv#eA>=#RE2r#lm#zi+Gi8=Uj%fW=bvEvKrcJ&xmw zdY7uOdD`_SRx@oYifUsMMeKIDiYVP?joy3hWQkz+-KbNkL4gWg!CTK>xW3+1>7=jb zcH4@qt@D}pyLfm!z2nSLpdruna{1Hyd6tYSIZqoFF_sA4D_?g?G?Yv1N2zPr@{p;# z(u-6WOP||LS=7va;5LK%T@F3Vvk}M6@Uyq`$jMb-*fagII_9U7ymXVRMotG>+d+l;@j1)ck(ovGEKfwH2n}e6Eh76}8f29fRAi(ai*)zVY>YRfJD8JmtyE!+9UKo+Eck420{ zr0_w&#pew{zm-K&=Q=O%HCz>zsb-)Ik``ocd|?97a8FcyfJB_#z``ufR5ImUV^ zndzCysYNBA_|}J|%n+a8pkPNxvIM0{XGcR_6TRf39842TNHxI@Y(* zMjl2cCIe9BfI6CqQA`@@!1D5PRMVXUf(`0nzGGx$DK;oHP=WCc7~3SE8uOEj3s7_# zc*0dNwb?>dB`1PBUW}p;Zx9&jB^Q+#@PIrZ42l6J15j3|XXDalV`O2xR>{P|#L~o= z&(g%0&6vqh&Y)Sj@54MxOT`ai#q*PwmOtGWUOOj&>&H*uShX0{*yAk>42(4ayKks7 zFt9K)co?`bDKb2MKOt+g<=0CcpS@N|t|@l6HqNbg@_K0=5u@B%cImb^lOjXg^7K_A z1vy2Bj9-V`>ty12Q(_zEy(RA6_Z1S_cZK(EV<=|`T)g4yU&hKgVYxfZEBbksRGpDl zKmC1Phtn+He^MY4FG^*3?3<&`!b2UW|&6+^d%Wh|erSa;tz-L}^6>8F-h zCPjt~ljo@8cOCCL~Q``v$T2`X;c&ZU!z)3dzSl|69~I$@x^)!jeV1YA!B# z`z`!rlh?ZAKlz0xEz#b^q@d#HFVb}^Eq$+CIiteE!)Kr_lVj>ZT0;_+-`}T-K1J6aIJ4&Ab|k^gRB! zMY~g;d(8;Ey6rF5A5p1K`=5*JOqjl!WmSyFwXk)!Z|m9Q{0o>6q$Bw*s#xrp)HU8u z(t8UFuGN&YCHzp6Y!hBFN$gF|uK)#2!>N~;XRK1-yBswm#bHLD)9Z@IGi6^p$%=Ds zzr1z!*DlFl@-ZT%DvlZA_h%cF?)YWJUl0 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..dd6a50f3273eab884f2245df8b0ad90967850380 GIT binary patch literal 1218 zcmXqLV%cZV#Jp?)GZP~dlK|U|n=Dr&oZY^4=`Rmasykx9%f_kI=F#?@mywa1mBAp< zklTQhjX9KsO_(V(*ih6!7{uWc=5fhP&vZ^LDpByvOE#1=5C;i!3k#H&m+Pfure`Ln z7M18F=jR$K7|4O7n1#jRI^1*elM-_jg7WiA6r3FmSO$@*wN}Sgi#5FcD zHnlXhj56>i$_bhV>JWPr;P!<01SzMHBU7GYT?DWUV_|3c9PpL9j&kAx(6G-q6 z7xw({!aTkC{`1S;={`z<%kJ~d6nGmRSfci*_V}qJJz?p3)Dx8{$`wlsMsZS|b>=6L$G$97*IUlWRR`MAH(sWzg8 zxnW}?_bEm) zJU6>?x%MJ;1>vn1Y#lBu8n91(HS;SIGb01z;wHu>gC@p010go%P+4I{#{VoF25gLs z|CtyW3}iuKd@N!tBHObUy;`xVrKj@MmRtNYHXWT>w)2aDJV;uZMZ!R=L4^5UfN9u8 zpWSI67C(}fK0JMs?Zr?7K9B-_keMvZOpI+1^Hf>H3`E#CwAmP0S=pHx;VdSDG8mhY zktNF@!$1YbH(+d&$S5f(u+rDhPcAM%NqGhia8*ogCQwz$iJ*j8jG|BUtqexxXln|Oi_+8QD)>A*n-SZU@a4}}&bjl4*r;H2@M(5(V@8(7Ea;<-Ot>yHAzF(~c zZ*RqJ=J~E&>hQ0X>(^()itPp7Qk@aI8uot9mVH@wo_klsoX9H^F8|H>dtpZI_QIy< z^SRtnoENI^9dW*)wdCGBN45Cf{WDMBUH5QB{`~fTCEMkt6;^NbKlU{}Hg>kn%m10* z6LYTJ{QTGK*lOmEvP<52rEQP@+z72zJ0w_s-;QZp&F9tCRc&VZJHFqTTzJ0iezC!? z>eX+xB2PHASPP0v1${orCsMm8x+r~D;oETcw4*ziyi!f#x-V$iD9j0*>GM2j-uG-l tZQeVpiQi~Lv9Tg0nVCtA* z>I~$>d5sJWEDenez#vMT*T~4wz|hdh*woV0GRi>4P|838WD>KmV0n4DUQuariEd6} zUbQMy4BqY|>i7+D#Zn;7{S44N3Zn3@d@ypgzX1`DTp3K3K{pOmtX4;Wo z*EL>hAL%H+z3KHXgEea+xs%-9JuR8xI%l5A(jB{+ewr+4Y!f{E>amewCd-V+$`oTD9f(;>$Urkdv4yYxb}i3y~Kb$mlr2F zFI~1u`qjio3hW;1OE&LsF75KsILsEeFF16$_VR3x@>Tvz4$QDKN`6+i_rdOe3>U(} z9=B9{T=wj$WcG7Cx#bRS`vhOh|L4qIU+wYc$_#4M(4-2@Nm>C%u7dJ5r88k8T z8OXCShsr9mNEnDUh(s<)V+&ePJ#~`J;wkLv(@LU$+Fme_1u5WT5n~atRkLYRoL&{e zwffT{TQyC+*{ih74O~Fdf-JTM)=ijk(Ik|VT9SwwKTSdfMX6<|Vg`I5Q}`Jf|Ff_# zGqEm!SgOh*W+1}Gq0Pp~%F52n2xl=FSc85PmlnFi?w zx-h;0W1Aw>@ciWB0_>(6n8B4ZA-SalyK1;wN(^{F9uWrlo5=u_LhIPLwAmP07_U__ zu`sbTG3K!}F=jDlFqAQ9R_^;S&(c!yLs;?rGQvMm6?$3j+gV z^$iEBy$lR2%na@Zu1pG$IBSX7#vTW+IU~d z%}e0eLMu+E_BcPoZfWM%uf30Fe3+0ljqMxMUKWJC${{b98|_YtNp7mF4!M)UCK)&1 zH+Rp=efO4{%&Og>)WW35aH~NjqUqtTRD-#KB58lz_l2*h5jR}KBVxLQbx|7sK`G>N zgBx6KFf!~t>n?SeGq35?0);6j4n>IWOAX>w>-->jx%BFOljiaRKc=?*c*11#HfZ(h znQi7Rj~?w}uwS;P8s=Wqs8(caIZLd+)QRuJF6mu84%>s^;8h;TIqNQen23v{5~w!zeB1nD&{#yIX&M zyHm{;SblaE%heuDnFE$@zs@szTI_y*#+teLFJ?)-5Al|aSk7R2euiX;-IV!Z(Z4>< zpPJdwsWgLuL(%2sqc=5Qv)(maHfPDRwSJJaP$yWXV9$%4C;#*Wvc%X5|9NyaYhq<# Vpx13Z8-LE(j(5H5K5lCZ2LOU$IgtPW literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..93f1fb0c6b5686ff607c1297cd0adbdd08b28f54 GIT binary patch literal 1101 zcmXqLV(~O+Vm4gB%*4pVB+OPnCA_u$#hUVan}Us^_sr}L<2B%Awhn8i<1gn1ux(0t!L-`6UX@jtYhb za^k#(riKPaMh0LICC+PPYG`0+3Fa9@8MqNJTGT)oWHh%hPjG5cS!xm3R414dm|;#Z zXkt`Cb~Pg_19KB2KZ8LNBNtN>BO}8hKe6KnVDsuhnl#n^Y|1)TpKZ}>xY7qeGVC-XWx_fP!l6CaBxwEp<@vdi>q{JfdI z&$G?nFA1=7dGjl(HI02vop@I0XYnpmxpyzu94_^X2(1my=E|Ne;&lD#8ph7euNMD1 ze?Xh3<QhU6v9MfjmfBnMJ}ttU;tj;!LZ_n>{>Jrs#1*&N;qGz>kZ=pqh=CPw50 z#tlwjj0~-l_L=UkJioZnN#@Dp=KDUSjJ8CA+ns;ubSL zKD_zoX~(E%K@)dBF>CkNUAcrk!*th_qO*Lj_-eyHRsJzQTAeU&)9VAe6Uuo#1=N>X ztUV&Mc+27w9-BqE1=)|5b$BKfRxzjg$byw)dZQ)<+lW^*x`{@c@A@Pkt U-KQ*d_HNZbWXv>e^{pxO0B){}_W%F@ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..1dfb0e70faadbce1e2af09df6a528e061e25c471 GIT binary patch literal 1174 zcmXqLVwq&n#9Xz2nTe5!NtmsEikFu9+0VZYtBDY>v5!Rrz^_k_O@+0cK$Vhyc_=M+HNJCPpRX zfM8^0U~XdMXE11D(NK7*;_BnV_3jM;CAE1su6LAunVIoDLNVcINeiR? z8YbPg%L|;V{5CDQV77~KU+-;?6YSzvO11#9@ENZum__6Ei z%AX$T2Gib6sff$(TpW17pe=2oYuc`m*NYEN{ONJ$|HT&uy0y9`1*X?KRKCwU5}CKV zxLy2{k(}pSDo7OX}$`duJ`xCn7GXUuck7cw&+?HV3B|Ow{DX3(c^L<56d5< zpWvU_4AX93-pTe^Gni=^b)}lTvAk8T%wnppKD+NSI^X@167|~RDwgfKGc-t)FK00 zkkjN@%nVEnj20Ly&_mLmLr8m34mfAXDzI=Fa2T+$v4qMhFd!!@Zg8?kLaPQ>kv3t8&)n;pcz>~A4S<5GzOfynk>EW_^&Rg3ptBP}< z)QdHA=A74HmS}dbTr5y$5WU8FL&AgdD;&!n%GSgk*!^=>wbI<>44idq+{Byj%ux%U zX<(T&t^e((|;@d`W>zv{oMZX);$) F0s!GfpzHtu literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..75df0cc7c0be230aca9aec4f9d5e958ea3b268ac GIT binary patch literal 1145 zcmXqLVktFfVvb$F%*4pV#L2Ln&&$W8X?wi^FB_*;n@8JsUPeZ4RtAF%Lv903Hs(+k zHesgFU_(^{We|r;SSGk6u_!GwH77+OBsDoBFFz+gJu|gfN5L~MSs&kIz_;M4`c_2FuP+>W>tP(qM@*XAV`E=m@7Crzoeu%B{3(JSQlFx zSc3F23mYL^9GqHImYJMdtPqr+UxMWBRD`P)bQRo<44N2~ki(6Um4Ug5k)Oe!iIIz` ziII`vDv#9>t)G66zV4Hp9^cmyyiwl%^!fP;!nvAOC-`e^zGs(NB;PbkSTQ>;hM{4T zeq{Z(_MfWGZO=2MgJw;5SorvcF0Wtyw-cT>w|$lhTc^FPMCA4P-)fJgcF9iD%d(D` z(&--dUvU@2YFpSv}ul_|x^< zp@j3okxbeRXH4Ah@4Po5HJ9nU&ctQQp5JSg@T^JmTWpoPa_c)2yLDI@B5(Z)oBKzaFUp#kLzU@nN z=$bp%1rG78?09bA&&Hw6#>mRb&dA8(Y~W}h1LGSowh3gEloVL$>*wng7aQr77V8@* z!eyD-M4_@3Fxd*d87l^~v6~ojO=_MECfD@mr0t=S`hXETK zOQ@^@19GC^1}6$e24&gDUE!x{Uy4`ye3T3QdnP!~&Yl1MvCvPOSj^rw$Oj*ZHPOi4 zcRch@`m59S^%FK3pOuafXkz;DL;tIA!WGtY9~(Ii-d$cg-Cts7?!E7mCvxnnbI^Hv z@YU|JdB$wzvb-Gi!FQ!oRyV1C%)IIwx9-cr!==k3Ty#I#6wFw&D|6e_@_K&5IGsNW z+O6C(KUBtl5eZo`HBPWXC_(+){6ppcm4nM;cm01A`TX|z^^>N(SD9rhP&H@bF}~WF z-|Qvte=Q1mD7un8vbuSHke*x5CWe$U8IQ|1DqrMDJmI>e#mpzCI3=V1vrpTw bqTa;we%Q+I8{*#@zwL3~ZonM-g}Da+m?WBn literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..0b6271ff545ded2d9e9ee2b7c439789809da1479 GIT binary patch literal 1335 zcmXqLVl_5sV!5$^nTe5!Nr2C6n)c;ilZx-ie{k-d@^z;H7aNCGo5wj@7G@>`FGFqv zPB!LH7B*p~&|pJJ191?COIW};C%-f$Ehn)kRY$=yFImq}!9Wfq#Vjn2kOIjlxH>y4 zI6LYZ8_0?C8W|dx8JQS>L6kVJk&%I!k+G4nsimo9ltC$~UBU)}AOkssxttSoGSl*l z@-h<*#SBD1g6zV)!HIbaZbgZC$(hB;`S=_rXCMR8!YnLWoR_JW40m^NagJVcey%~J zfiN38*uzYWP@gk1vNJm|usmjyk!UDb)Xv4vWc<&rhz)leN2pE(opA= zmzSd%8W3zy22;q$$dYA{VW0xz8!)y>KsDwk7Z;%DG;n~cVrnyis!C1-d9xTrAubP> z81R7HEer}{CIbe8CdPU;E^RhO7RGCpOe{<+O^o?0O^n%$nGEF&Zc9U7m;RclV6b&j zXXcmBwSqd1CHww*?r2^8=}*DGTz3Wr#wt6HCnp#fSeO|+4BVI$8TwtnzqhL^j!{_0 zt+<+kZ!k?h^S>BVyU$6;!|`T03)7c(DlO^Xil*Z-Hi?0BR1ztq>VObWpfsf9Cx zo>XhRnEOC|hOxr!Do;&$249Db9~an&YD`K)F2I;Q8Nk7KC8~RhR6~G6)szz@2|rSL z9VTDx^1J_XVxY}dlZ=*YpO_RGdd2$F*L=Hir9s%PUCHc#%&Ejmp$A6oe>3hexzD~X G{Tcuv^sqPp literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..41c742136ce864a9d7a90253af5b9f1642672d5f GIT binary patch literal 977 zcmXqLVm@oo#I$t*GZP~dlK_`_drDmVmL4U)ztV3-cx()K**LY@JlekVGBR?rG8jY| zavN~6F^96S2{U=R8VVT*fH+*joKA^3C7HSTMX83u27(|lZecFx%B0kykfPG!5R>Q7qtT9<}!=wn+VnFPTSF8F}pRs zfB$aZ_la&tP8MHL@qh46=3LI^z(W13cRs9~<(G?+mdw7sxQQ{tpouZXK$eX;RF;oL zj74PWIrCSqHCN$=R=`s=2OK>IX#19_0NGK++PScAyZxse7Pk?}th zBZGknOrDXEMZ-YNKn2D(U~H4fC@Cqh($~*VE-uhZ$xP2oPAw|YOU}UFYSt@W=_!@tnXGog&+P#=}7G9>=5Eo=OKM@d(CE zn>6L>AB8(%IZr1|-d`ni!2EHmr|2*3hb5L0{M&DRUKAdY;D4rX3d0|_Y7;{{U-za) ztJ7aPWBK0u^5)HvZc@2xq#7$&5h!50z*LztFuuCj?MxwK{@0AUs?wEzGB literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..68068a77c5ba02d9b45f91bbc9593d87b8e9c7b0 GIT binary patch literal 1785 zcmXqLV*6^)#CmrDGZP~dlMu_6t(TYnU$W=*ix=EinFKN-jvMf@acZ@Bw0-AgWaMUL zFfcOYHsEAq4rO5zW(o~96f+P3akzweeNs!*ixph+l8Y(}N??kZg_(kk4dldmjSLNq z3``A83{6dqqQrTPj0}vST*D{>IYSu(DUdPDJi=Of#U+W!*{K!D8Hsu6sd~xzxdu&) zO32P&WMyD(V&rEqXkz4IYGPz$m{p#-$?xRGcrK&!KPGtE*~UF-C{kSgA>AnG*1b)a z_S}lollyA&_raQDu2OUDHZIHCGX3%_#)8Evg{t4*YH2(b`25iG+&-}{zZWrVG!94kV8z4z@_Tj(J^>)Pf1eNr14+m~!rep{_LC%w{GX3?IK zjh)lOP2Bd|&AU`QKdx`qXAcK4Zo9oFwg)B}SMPq!#LURRxVVW$+@Og?*np3XIaHRP zk?}tZ3o{ez0s~nPUzG(EQykiCjI6Be%#3U>7Lx%FNLm=AoXLOzqL`0Gj78+w1#_3n zFSloZP_T%aVjIlzKW52I19^~QWflnou?7(lz0Km=Bf?Iv3*dBkCcl1ex5%Mm2B9#M z85vnT4crYBVSEF|Hqnfdk^(Dz{UT$%e7&61yyPOi{GxONCAbo%HZg>fOoS4BgC@o& zAe#kQniy{zG%;Rp;@8qkEY2=XD$Oe?1*MTDAuYY6oc#3svecrqocwZ_SZ-oT23#yJ zwWLWxOD{LIBoQ-}p~}GZB+P%ccbAXpYTFE$B*h2X9SIjaex3@!!=GGSC@5KltX73XCZ z6r`3Eg9DBi#n%QtpjcL5aW!x@;9z4*Z{%TQVl*&kl-0z@%EkrOdaaU)g^8u{8%yJ7hK~&84ELADt zCE4ZDVTF>7=?j)9FWHng)3;N&w_ur4f7%r$MTQ@5eg~g7+kb0M%%zhzLR=o%x&7LB zNi8ehIOcZq3CoIu3}p=EQH?#`0=3u$ zY_Yq6E0ZF_rKXDOC(AQTA3tz)*lzxFapslMj+q^GFZDi{w%)txv6M+cF^)m`>HA_| zU7d^XwYUW1mRoO2o!oV${QH%Y!ZYqq?m;d*xxs}eBST%=&)I?tkM?*9rKvofa(!QH zcKrDzk0QDkEL!fYzwXwudA6x0H@|mryW|w*)*ikcg^ykYtBJ+e3F~qJ?}bUy}Ix2X)_=H>%zG!?(={7)fW3@PVpv{=;x|1|Jm4yUGTulA!QhV*l-VcGdTO{lal~qn~?l{JN|JgQX0FH#8)&Kwi literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..2d66ea723ea4f983874b3ffacdf612bd90e150d7 GIT binary patch literal 1306 zcmXqLVihxJV*0m$nTe5!NswWpHiy@F9*O0_->&JYE{~5g;AP{~YV&CO&dbQi&B|cl zZ^&)H$;KSY!Y0fV8f>U(pbp}22`hN!m82HsrIsiJrzV#cWtLPb1f>?ICKe@UD7Y8p zmlha`8VG}wG7Ix~1_!w-1m)+KC`1?<$cghB85md^m>7UTlsK=Esez@Tsi7H&A7x;K zYN42c2*?yJVP2oq67^yQ*SzGS%7PM@B4%NxAY+3jMkQo-GqN%;H!<=v7&I|*F*PwV zGVEp&)j0EQ!IaDVLT`FQCmsIL^l|30DyNR+v1+TfYVSPivvGZ2_RrE2i?8#$PZ0?< z4eyz^Vv(if*CrdMm410&tq<$n?me&}=5wUQ^S9?SKSXg$?>jI>MYa8xdEsW^=Q3*^ z&VGOUiM4Bx>$H^zgU^|q2zs}5-P`_q2?dXqhOFL}%Qt^wPA=A0@so zig_0N->PGq`_liy60c@(mfOz_{&}A9>DOfi;v(ylm;9f(dGg#l!OPkHd9=zd%v`5h zx?A{65cAi!feX8Di!w1YGB7S~V&pJrVq`JkV`C1L zn~jl`m7ST14aQ@_%pzeR)*w>3CGuJIwx>@RiyJsPPDn+3dpSGLzzAkOBO{BNfr^0=jBmiy zCYDiBQedU8Uty@1sh5+Qmt3TmUzDy7PI$8NED8p41~Lnz7Dyl~Oh#6y1hQR$McP2p zfP;-Ky^)8JiP3M)cnVXmx8Cp;Ix!W!N-s;l2=W2SzrWwg= zOMT_0*&3%7h3Gaecy;*g4~6~lXSNqGY|pd)7B}VI6NUN9-gj?ee!gg{n9amW8&X%-(q}=2Oav1N$lu`1j?y@Wf5pt@piK;Nc5d7tPy|3U8BlD*g~s zn=(0kfov+vK`y0r0=&-C7fQ(2J$TS&zBOv&UW5JZdD>e475$`H4}X=I{vmG7;iWsW zKIrYSHs1Np``(#9Pu90^x7i;EbvFB!@z6{>t8eDT|4SW~n`}RDA=%Q@vNZ40uCf!8 znO?5+&JjPy!*p|R$I5B||t$-I`!kiAsP`9|pUq9dAo-;c!loml7A zVsQOaYrMq5%H7Z73cA@JwoN{~v;R(Fp{myU`)^ePf-<@%-FbR#>*HIs7us`L6b;uk zef_<2^@&b#+lM|+E%?6e)!sX;QRMa2+qMeJ>mn~d`VsLndWXl^e=+`In*ZcNmDisT z+|c`~X7U7a{l9A#ak{(Ne|WiJ`+p7J45Mr5adMf9C-3+=w_Bh4Qjqhqe53GGU!%tR z7QwBtb+KuhuXfyhGIi_Otzkk=XOH+DQ?+mj$bEB;AyneuOV5-mey66-*%E!Ac*W`+ G?uP)U>;Po| literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..79a33ba5908c84a1d790715853f435daee569995 GIT binary patch literal 1380 zcmXqLVofk;V)0nO%*4pVB;deUu4sPcrPGdgWvgsbxV9VcvT2q`KpE>UpK*E1A15Cy4X7Upva4p9io z&o5DMc2tNkHjoqNH8M0XG%_%>G&C_VjuPiJGBL0;HZZgRa}E3rxeYi$TG@n|LW2!8 z4b(v#E@1`Fypq(Sywnnf;MC;OqRf&?g`m{p)Wo9X3+4Ho+HDucN-PwNMVnR=z z&9m?Y72it_E&BI0EAJbFzsBu*8ErW(lecY5{wpGsy}o5_^h!(1>94aX*uPd&iS6@~1J4@PJ@W3~KC^BL^Zd*1(`#mWCi4hGrXsvWZNzM3l_cA%ms?lrJoFJe)X@rdn@sGRo)2Sd?&eIV6#_`edprDz8`XY`wKOnv^yd>XG=>Ac^3 zMH^*j?_vJw$$F#lF6$PpYmP71FqTc_;=ghK>RusvQ+*BboJ%tfuHZ~5&vQ55b#6^$ zT*ln`qHCQ;*&H?PBJJ*~eN{OrxIAjHkAuA1!kJr_TLtE|S0tK&}Dtg`*`Sxapt`(Imp zPPunNo0m)D|M%OEM4hfET~LpGSou%lc|pfozh^hQt&ZJQ56t_&C*`ZLvp*9vBLm~& zCPp`dCPpU%em3S%Sz$)T|17Kq%#4ix4fsF;{2&1qW+p~918+7CZ8k<$R(3{47JCC* z18W%HfT>MCqokz3N?$**prBYUGbJ^z1e722lJj%*i}LeJiuF^9OF)d|#0q1*0`p`8 zd641CED{D{4I)RDZ(LHXQ~2xlL+=Tt|Br|!v~~&@gn$$%u=pBy8*s3(r8n|0GBFz1 zvvF&)F|sf=-4|wLWHc}^(1W>wiBU`m>W1WkVm**ux)p|cIjJSZsd>pol?5ew`9LIP^5fX?OnYFI)T%xpZ)po_WnaTO=v3;@%teU6wIl7cI;Sn^GVUym9xF^%oW$ z)@Hmi?aNUGQ{$gE63reI_HNm+bybt=)B~SZDr-Eyw(xX1$E^j4}8k2{&q~i@HmEVis#m?;-;7M z_WQNImw3=R={1{mj;1na!W~y8A0Y|rm%sV$-EsL5F=O4Uy{@hcJ7UvL{S$bZ-*iII riTC%*H;>yG?#&f=Z1HPJ?Zz7~JeykBW^2F5_Gjd=DA)UW_qini=mHuR literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..36f4e06d476e3cb7570a58da95ac979b4915f503 GIT binary patch literal 1606 zcmXqLVskQRV%1u}%*4pVB)~5hC>6iLG1bWZ;gXLpuap_^vT-8TMhD5rn?_kfg zQ=7NiTOx7g_18_6$%kyXCfGW@di;CFo#fw#w(Jr6cjZIij9|5gj%R1=`+bvpO_|gJ zx9>kqyZ2SRJa(mNwNT2MIVr{q78q9FZkw^OH?iR^AD^DKSk0C{$|gN0HyKZCzoDe4 zueEE3?E7Pmq1K$<4(igU+B~P9j-PvEah|bm^>gM4amtA~ziNM7nUrXk^;t~jWZkW- zXDb8$yUn-qT6+3Z=hx@#6MbSA?2tHhTH=;h`UCOr4|?^jGz#>N6ttf&IG9`VgBzFp1Sb8kp`-Jv_-jFN-+njMN>PrUu(m3hPrwkMjm|NCh9SD%TQ zk%4h>6Vrc#CZ^v8@@&kZvdSzH24W2&+p`zFTCu97r}EX7Tl_ON9i3XX^NWEjNC6*< z7>h_WtHhuE+rN9>`njKfYlg=^)+tv7zzIZFkVU~jt_e97n)u7h%k_|>g%4y7KO^IR z78YhE)&&rARaroB&Y{i5$jZvj%*X~~F&Q*=gVf8jG&UPFHW=6~uvuW$W|2`+QedU8 zpInqc^H|P3_!U9>R={DF=?pdK>kK?n{z<0K`qQ@jEpRW2KfdmFunm}n*>y2esXaE zimMFV;i{P0tf8us6G5IXMo~z>`y~cEAmf!RWhdymuq zv!>gBa=&Eb6cJjxRKTMuY?@TrI@3A^Hij|=C0AGW$WWEAY4!{CKYq_`d!VeAy!L>1 zeeAL3n7l{)Oi+8*BH6paI-pd0m+ zzJB_NDNkp9FE`m_-tnh@^QGS`!d|iu^n}lF>IzR?))q0{Imth(!sky&c4pa*hu2H> zCpJ$zvy6lP#>}Z~4HJ*?ly6TtEMDIjF0(jFYxAu%y}9j=rKbM!D!-H2d9rrGY4I`+ zj!iSKS|6GGr%T^a`)bX%Sf_nV0gV;zt1n7?tvIB2^_6Dko^OI?N=XlY{rtxj&C)A* z!s&b7Q?7&70&Ld=^(H*tyDso&l=GuzlUawiT3vd#+kN-V!x9f}&u58Du6^|3^-?wf DgYZJ0 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..608f16c792172d34c2fe19cb77905975a6582521 GIT binary patch literal 1262 zcmXqLVtHlI#C&N1GZP~dlK__~$JH23ZN}OkYf}&Ke}7`Y%f_kI=F#?@mywa1mBAp< zklTQhjX9KsO_(V(*ih6!7{uWc=5fhP&vZ^LDpByvOE#1=5C;i!3k#H&m+Pfure`Ln z7M18F=jR$K7|4O7n1#jRI^1*elM-_jg7WiA6r3Fmi$_bhV>JWPr;P!<01SzMHBU7GYT?DWUV_|3c9PpL9j&kAx(6G-q6 z7xw({!aTkC{`1S;={`z<%kJ~d6nGmRSfci*_V}qJJz?p3)Dx8{$`wlsMsZS|b>=6L$G$97*IUlWRR`MAH(sWzg8 zxnW}?_bEm) zJU6>?x%MJ;1>vn1Y#lBu8n91(HS;SIGb01z;wHv*22G5s3}o4uLuL6`#8^bOXD@oS zVpU5|<*O~X_-AZ7I<;))7Xx{av@(l?fmnkG^SuDmu!}ys(>^SIBrSb-`X<|pp$2>) z1^kSR|5;d=nHbw3=BcuX8HliPXtOc0va&NX!dXlPLLlYBAmtneY>bTmnHU)i%3$)0 zj4W9O83rmaz5!#KL`F$Tft9{~esXaEO3E{EfU9C^Gl8l~P6Q>)Vibk?uml51m!M?n z?5LMqRANvKa*I4mi9wNp`2y1g#%+c$V~TQ&i83PxQ==|OG$TX#>g`qPw(E9(tA7v`cCuyFspvxSyGrhV{N}&A zaBvCB!^N+UdLI0H@#OuU^;2AR+_V-aTPpmV=cO{m@x;oTkLHB1vYn2R*~cKwzar|t zkDn5Og3 z?a$Sn%JJL(*2jq!st@PnB}%kso!hvZ!60k)_Vu?u9-aQLt3qYftH}+!j8m)jEG&On lpDDu4U|&9Iqi=L^JD2c7#Kn>Z1XeVW#iOp^Jx3d%gD&h%3#pA z%aGfElZ`o)g-w_#G}utgKm^3$66STyD=8{1F40l&%uCiY)G$y3iE|6f2dAbgl$V$5 zr9$NO@={Cmb5he2b975mi*kz%Ee*^eDh)J}H5H5u3{4bJj8f25NXsu$NGvVM$S=yQ zN=;EHElyR)&&#Pa)HBclX=N5xh3ZgnPAw|QOv_A8EXmBzQ*eanDXCP@Rq!$NHINhM zH8LN!~hX^{CG!Xhr2#U(|V$t4Q; zX$sEyIibnHCC#&tJ}IWcGDs$jJ>AArw&in<3txL&b;@h0Z725qIFSD=v**=1^$YC3L|?JL3B8_>#azKUJ%kQl2)d|bQ{Vx}JuHuwlyhY&;-_#@ngCNIlXd)IpJ zPxR$_rl0eq=*7%}CDjI=;Z@f*?l+iK7c=4Tf5SVwc+RC=Q`yYK%*epFxQQjrpot~P zpox)zjX6|Skfrg5LF3mZ9D&*-gc;CHGR37y#mPmPNvV2RWW{qb%dkr%78Dd?(;=FL zRah*!7`u38ZaOw~f#jc`dAW05duV*JX+1y14DDw$ZASeh80vNSP1WW3K%$`IOk z<&f9}Ge)mh+_S@Nf@MRtb2FCo{2;rj)3@pqHZU!z)3Qi$L zmO8zRGZT2Dce+FvH$5@)bKrV3FM4ZttZb*aZv&Hp$IMS!n@%h`Jtc9%tv?={x8C^q zpV8&$n@19tYUVZt?SH~h#?W56|9NE1j~?fl@q0h#{SWJP4>SAkwKd`3mRJAd7Buri zt$hTx*4@CBNs(cLTPjaM|H~yjcb|LjI;!M$`19RgQ!jECmCxMzD1FOBCMJdCFZ0Uw zS~$LZwC8R0*E`39G;^DZy-Z9U1-d@m+c8CMDnl7VmkoSugfO8<8Ea; z_skTGIq-hUjL>VH7w$sr6=6oQcjfAR6~1Me>p3}Oe79`gz}6%XSv%J(OUv~7+(Ng6 zbNNgP=AN0C|0ZAGceye@PjJUnQSSd6rUgGzy>zB0=jZZi+3y(27?$khoUlbALZp4S z@S-#61za|!Ggy8~-{0}L{4C!I-a7&idxf_k*}MDMzd4K@$*VI$wQo!bwOjeebM(q)_a+w93;+YKi8n>E5%w=WjTE=gsxmCOv+JzOt1atS>zc_&`aY zpONuD3kx$7>jH4eBCE;*sscE)*%(<_*_j#HU@Rts3>ce{ktN0;%0L;$H(+cN&nPJ= zu+rDhPcAM%t{DuB;fk2r)S-$JGZT^3=;s*b>LzC-X66~0n&~B{78w|W9461AW1wZA zu|REsN}Cc?XL3;vs?OB1)EvWHz2u@C122#!1r}!mM*}l9u7oxZ#`|GrOYB>Al4vq`1^&eS_W&^^G;MOunf7o zr?cR=6(p$mSj1RFZf32DUllI)c3qviW^B>EOVVy9r-Ca~Sz#6?0|w->m>XOcGcs)H zG&zE#kDFAefejJq+EAAb34iPTA}qN z6|ar2FSE+1det7aHBy*Twga{Ig1=@7s5qUs+IfW~0SVf%u$krA9Zq KWJBpCyix$=YaWpR literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..99ced211ba8a4465076a867f21a3b1792ff893d3 GIT binary patch literal 1329 zcmXqLV%0ThVi8@y%*4pV#FM!2`6UJh28O`PY0C_F**LY@JlekVGBR?rG8i=OGvqel zWMd9xVH0Kw4K@@r5CL(xgn3=_N{UL0OLP=G^OE%pH4M~1;@raW!KtYV<>lpisStU+ zywno?oYeHh9Nm)CqTFIbO9OL=N&}5#O$8$Z14{)IqZD)%((;QG5=%=m@{2O7Qd1O4 zi&GWy^KvQ;jSLJxTA774p*j?T^7Bg+oKuTRGSf1X6H7Al^AsE*8cQk_bQRo<4CKUl z4NVM<3{4G9jZ6)Uqr`cQ4GaxHTvHI&pm7(iJ!oh`v@9{xQ|#`JsA+n&9ig_oS27cP9PsHI%6qSSU%jvHHIzC`*H&ALrC2am>c%H_HE zy-}PQA?ooZ)I)2#$rjefHJ&{T5%;X7ow2-=XdHS@OV9q|%%m5qo|%*xu3BHIIj!P! zqsaQqTfbR$MmD>w;`HBgMTc?j>?wxXx(m-Ae9FYk$iTR`iBa32iBa8vkBvE0mYx+H8!htnAE8Y%mt1fe=Vq7^IxTfQ^yyKNBN^fiX;;k+ zK-EAQ#y4PW6VE6qDX`Ml&rdEcKu-POL@O)LqG_ORpt?YLfnu9HRAq8e4zfc1bfe@% zz2u@C18b1g3M?iDpcKy%DyzU?0Loxcw=pq_NkL6O&uB#ji4bq`v52vV9R7Y`tCqpq z^}G`m3oJwK?&&NzZe<`3GFzEN!a%G+B&$fR>Vn+++UM4pfmd>R=T8tyTaBEcxWNgE zkzq^w@fIa1%>zp+-hB0!-qJEXzx>^m^O5UUI8W1;jkve$vuL#5P3Jj2PCLcL1=nub z=y*JhN2!f<`4*+$M>8_II*e`_ z>kXCV^;(B+P4-UNQBV`2dATX%>iMNp4ja$&HA#P9QI^KgdqMx$YR*+fzs~8KcK$i= zmW_G){T|iukH6n*blhyxIKaX`r7=_a!94D>+4%);t$;Sir-1&_+4qRbQp zS7&F1U=K%Q3ljw+10w?kXGcQ=IdNViLqh{YO9L>766ZBCGBh+aH!?Q0G_{N}s6@3_ z*gz0u0f#V`b7D?rT7FSpW}=~(fe1*DU6?mGF;Bs*C^0WNvp6~5P}x8cB+ex)6`q-$ znVXuDnW*5FUz(SaSdy8arvq}ao}q++7)TK_55JaPd1iKI0Z5r%eo?wXq=7ISJ2(KC z7@;A-%*f8{#K7_*K7XDd1xfV-e|8?fcmJvp(ixyT}Z$xGyqBvVkrJO-x5Y z3Iti2nD!VnG3{tV_e2vf$WLIegZ$LQ3uc1EAY2eDDL+5E7|i7XaZ?gY5)r(-)N+U% z*r3AF{E}3#Bp*m=aeir0GK9|$;+Lfs6=#-IA{3P6S0<)I6o52%t*o9y=A&Ch>R3S%(FiaV;Ak;LZID(soBnDB19CI*L$YPQZJHbJR7M~(odRSr- zoSfjXXut0Xrul(RXs*%(<_*_j#HU@Rts#@QhC@+^&$4H_pH zxGiv5;MC@jQBqQ1rLUh{lw+)yl9`^FoLW?(mz%asezUNBnO&?NmI- z#8Ac%xOl_Yzl@c0!g6<*SM>8NsX8OAe){{o4yReX|E_eiK84!*4Q{U@!<)Aob(h)g ziCxF8x1_O>9| z`%n0n9k;&BnnJdVK`G6fcdAHUTl`dM0l#nnOU{GixqVCuERXgu{fjv_$+Ym*Iq72? zx>O&BR@NK-?bUoTg}>~TydH9W%$&(!kj7-laCX%TF$wOtdn$We%04fCDz}tbGN|rf zxDNNfN9(tw+hsLzn4M!xFzAk6>=&Ree~*dDkfHClf=1oe^Y`T^%;ap|P+pM#V2b_R h$l6mmikW(um(K9NToZg`@}W7`nx{Q2wk)ch0|0cx$}0c> literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..7d8413a3474b4cfb6a84d46dc35f75628d29ccc6 GIT binary patch literal 1051 zcmXqLVi7lJV*0;;nTe5!Nr3&+=a}{tg;~PeAxNve*NObO^i(jO^kI0LTt>Tvcimv|5-Q;*cciAGcht4$b!W9 zSj1RFxb|>piuWwF%&p=(XM9|5^V{Ets|@5p(#k9n24W2&%=ZFJ!!G*lPW!O6>gXh8pmJ6!3%0WMO7vY=fAm$|7bU!p5P^#>mRb&ddmBF&UJ>*o=%USq2#fDlonQ zW1B=qNlAf~zJ7jkaREw{8aTjJF}0aMRV62aV!Rkdp*}1oAQ29VZ)Zon^MGZa|N3``8T**Fv0JQ&;l<}fiDu(PqIH}Wtt z8gQ_&ff!5(786{I87-YNH!(6YxJNeDUy+vIQ21>|ti-Mv?lU&jm1)2FFV($9ZL?Fa z;WMunk{90J5_x`IWYWS(pO)+CuKz9j?RW5oF8e<>#JFB=(A&0@TVjH#+M-+ER%|!= zGgaC9nCs3X=bzNPdv@~2guO+NXC9X2X06GZp1Wx^li7_!dvq8l*|RL}E)_h!Q*)2t zg|=Oj_Ne_#Jz;)U?OU5L56h~~rxhCy`c>{JTg)B!;;i#Q# zbm5STeATiV%-Wn?Wv9yTv{$XE4pIwUUc^85QiuP)jfzL57Ot}V8g~0o%Mnxg kf?K=YKD6ymVK`#44DiW43-QQ4CV}G3>^&Z3~dap2aFEr9nd)5@Eu@bn8RShpwBRk!SR6L0rmqt2bdUG8CVVo z9pGRPV-RK#VBkH#dw`jNi$Rn@h=HGhhk=vfKO+M}1_J{FKLalV16O)}etJ%-UUGge z12+Q;GXo0!Yk#vZz?1#y dT&ew!ytZ4oZkqAD;nLN{bmb|$-cOjkO91*BHT(bo literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..8e64b2c81f3c56d779dfb7a6ee4c3ee9d0cec000 GIT binary patch literal 138 zcmXqLY-eI*Fc4;A*J|@PXUoLM#sOw9GqSVf8e}suGO{QfYk0dcyT{<5m!`?{Z6~z) zt~eG&nlqjH!f_*&?~`I;>OvQ0CkB>dZzccpt2<0{hQ_62ydKbMg0^?b3acXm|p9-Xb0f2+DJrlw~b6rHZj aj}2`<&nah^frY$irV7K4f089%fq1x^-G;^hsR zR=#i+W4;ORp})oVtF_GTHApX-QJlX>vH9on`)^(4ZdlHlwJ?P_iGk%_sHK;U?TW0{AwxsM=(#9(tX^#a?1umHXWYU$yeJ4~`G`f7NUU=2>#j4)f sQNeq3wp#wJ>b97go^4Qcx-vgDwEaA{^i^?3y=7eu42zuS?3H>10A(dl8~^|S literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..d93402d2c9c4339d38407543b92be1cf32beb9a6 GIT binary patch literal 1190 zcmXqLVp+t*$iURZ$jETu=k*JXepjNBHT4(V5PkVxMzieG@BRK)WFD!vt!n2GeEH&S zMueqU_tJm^RV8LGy2@)d%T=sySU8#O^)5xz?cU)R-}B^sbLz3!8+BUn`HA04XMfYu z*`8oBv8t}{q~p>p7j>K079Ms#B5?d51HX=q#KY<*r)3Ya7vJd(jCFO`;2>q$pIyyf{F-#CqkDdAP{obC95N54 zcYV%MeWkhcrGAfo-1D{Ww~tkq?Cnu94_0Qhcd(vgxiH7-Z!6DIev5pw!as`M3%lCS zYCc+deMaN&ly!4o*)NPz5WUH(%=hCn6Ehn4 zOpDHyWS;R@9B|@EU$*!~RoB%E^;k5T+C-OG3frjkOZt6YZ|BqV4|lxe zyE0Mj{T!K9TLYud-1+qM<$03_>0&0+OprYA>*Bw@mfAoANt()1P2_ z7dy9`5hoV=Y%%^f>G|q{K)1pme$y?!w-@}n?^d>Q6~p}Pb+P^r-R4y+liBv)E^apW z)ENpvX-5v}cTW1fS%W#`&DOGuqOPXK1*hBG17{uj$yz0pA>kKqqG!p}*vRm9sy*A% z?UOg$d5~Y+yomV;>#e6f47{~N=S^wJ$c(tv zuPRjB@blLKRleElZB}USy?=@0&3k`N>-i`0ci(?K=fX=-*%;4?TP! z;Jo;}=N8W|iOOazhK14`bJ{*lOMWBG$PaSE`S(e4GI|~VdmSnNGha4-PTk%2{|vfc zD~fx#U0he9^xr`4|L^|>lIx8havRufN!eWfSlBOVMoWb0Mh};CuHrwU4jwN39G9zG z4JS7&^VxYq)iLpJR&hx5J^4lFGN1ihuO6~(Lf@yLjob8Hdd{Cr;m#{v!1pD7lfV_Z zppVH(jCYwD8ziQ^xy9mk?Q}}QFAwgNZ3zishbD$T;jvzyQ+MgbhdDYj$E2^$=HzDb zSvk4-0QZR$g_!?u>!Xj)`oH4Q-kM!9)dwfLwLFayo9y0h7T9q`@>lEGzbb62|13Uv zQPK6xo_a?Cuh)%9f;Rgm8P1pAbim=@O(u=9!rsi?2j^e@_qO-rXQsvm5vR9qUlQfR zjs8FVX}bT*Z;6|Wi}{m3z0T>5$+lDLdsTG#Q{~E4B4!I*o13TpnE5~Jsqv)Lqf!@K zXGjhe0ra$t6pIeKIyUiz|F*;OpOh3-m(oU6Y`fFoG&;1-d`;yjmdx9vp2}^ zSZNWtDSy+X=c&d|D*d;~J=^pAypCAylXd+Qm?GZaoyDMNE+*kBwMtb)@>vh#I}dqI z+43!wY&VmG4u`B3S>1TavdrdIO4gjbRoof#4v4g~8%ZwrzxUvL(8+S6Lyw9(nW_@+ KD#=Kt{{{f1Q$@xA literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..8e5c2e817ddfe06b75a1be432f600f96c25834cf GIT binary patch literal 1216 zcmXqLV%fvQ$Y8+B#;Mij(e|B}k&%&=fu)IMnL!iFA|^%#rY1&4h66vZUug8Z5|yl} zzu<=G%l9&xWuJcU_rD_ZNWE=UJBQ%Q7jH8nEXBH)1{|m=F?-QfUb9)QVs*p9$!xE8 zDVlEg4!`)GC-0k6kHy}o(}K@W{9Zcyo0iV@1e1wXb%iG#mu|VJ+q|~$u=^2#;|Ce| zb!;RaRzEo{dyu{OPH$kWtHTBdDa$UW&~~+XVT!&{GBI}Q)oe@~a}ILOxOp#|W!(m; zV?mQXX3YESYWCvSq*EQ;^J9Z5ZtUfdc`&`}bC&8W&7Cjxd-UU;uXVqDth!`xkCJ(? zGNZkN^&HEEIaYsLd6x29r>8H^n>8GjvT{#iz~F zL+6EE)b;$k>aSIMfnB?0s@L6=-^rW)1lzmVx!sI7vDjye@xMvWR~H1j6$bH}Zt=an z;MaY(vX!eC=5Mcy^?&F#uVR_Zw*Pi(EcuDxnMszjzZpOQyy~hPPAg*_Ljfyy4D+{NmgwPnwg_>-gX6Nco@nvhj23?!NzL(EVCb+{5kSx)P=T26F#@|2L3aZ~Tzkz-~** z=JLnFen~T0B1|`WxTJFx{}FZYaOvl`T-|CoxnY^l&J(JRiGQ<-L!$4=FFKd`?B9Cz zkZlwCJ_T*urti{o{$vVwUg-k9FY%iMuE+&_Oip6F%hcE)G40JQ7Po7sQxbl8aHnib zNC-PLG4u(K_4=H;OD{gm(UCbOeRVb`HzWk;G4hL^C zX_OWAX6`;X|MI`Ly&pd_H8zMiy>hy5Krn>frndhYgw~XB8?RdzLce_SdVOad#%S z$zFOeVZ{U2>eSMsr`>;iHSvA*#9VXXO5@DpO+8Nz+4xgZBUtzzvSS2x#{=*YB6a{{^Op#L4Lz}|B@&4{C21Ro*30J9Asv?rldKlk%$aBh;Z>eOvnH+RDWVOiZ#!HrEHn&o;=H#v7 k&X{*Vq@CSJa=HJ#2j_!MmKz;z>% literal 0 HcmV?d00001 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(()) + } +} From 6c6bfe5434e77c667b9b5a4d2e0978b208574a21 Mon Sep 17 00:00:00 2001 From: JettChenT Date: Fri, 5 Jun 2026 17:06:01 -0700 Subject: [PATCH 3/4] Remove unused NetworkType::Trojan tombstone The Trojan network type only ever existed as a non-runnable tombstone: the SOCKS-era Trojan runtime it guarded against (BEP-0009) was never merged to main, and no stored network uses it. Drop the enum value and its dead match arms across the daemon, GTK summary, and Apple bindings. The Trojan *protocol* outbound used by proxy-subscription nodes (TrojanOutbound / ProxyOutbound::Trojan / isTrojanTCP) is unaffected. - proto: remove `Trojan = 2`, add `reserved 2; reserved "Trojan";` - daemon: drop ResolvedTunnel/ActiveTunnel::Trojan variants + match arms (runtime.rs) and the validate_network_payload arm (database.rs) - gtk: drop the "Legacy Trojan Proxy" summary arm - apple: drop the makeCard .trojan arm and regenerate the Swift NetworkType enum (case/rawValue/init/allCases/nameMap) An unknown stored type now surfaces via the existing UNRECOGNIZED path. Co-Authored-By: Claude Opus 4.8 (1M context) --- Apple/Core/Client/Generated/burrow.pb.swift | 5 ----- Apple/UI/Networks/Network.swift | 6 ------ burrow-gtk/src/daemon_api.rs | 5 ----- burrow/src/daemon/runtime.rs | 18 ------------------ burrow/src/database.rs | 5 ----- proto/burrow.proto | 3 ++- 6 files changed, 2 insertions(+), 40 deletions(-) diff --git a/Apple/Core/Client/Generated/burrow.pb.swift b/Apple/Core/Client/Generated/burrow.pb.swift index df6603b..d235078 100644 --- a/Apple/Core/Client/Generated/burrow.pb.swift +++ b/Apple/Core/Client/Generated/burrow.pb.swift @@ -25,7 +25,6 @@ public enum Burrow_NetworkType: SwiftProtobuf.Enum, Swift.CaseIterable { public typealias RawValue = Int case wireGuard // = 0 case tailnet // = 1 - case trojan // = 2 case proxySubscription // = 3 case UNRECOGNIZED(Int) @@ -37,7 +36,6 @@ public enum Burrow_NetworkType: SwiftProtobuf.Enum, Swift.CaseIterable { switch rawValue { case 0: self = .wireGuard case 1: self = .tailnet - case 2: self = .trojan case 3: self = .proxySubscription default: self = .UNRECOGNIZED(rawValue) } @@ -47,7 +45,6 @@ public enum Burrow_NetworkType: SwiftProtobuf.Enum, Swift.CaseIterable { switch self { case .wireGuard: return 0 case .tailnet: return 1 - case .trojan: return 2 case .proxySubscription: return 3 case .UNRECOGNIZED(let i): return i } @@ -57,7 +54,6 @@ public enum Burrow_NetworkType: SwiftProtobuf.Enum, Swift.CaseIterable { public static let allCases: [Burrow_NetworkType] = [ .wireGuard, .tailnet, - .trojan, .proxySubscription, ] @@ -246,7 +242,6 @@ extension Burrow_NetworkType: SwiftProtobuf._ProtoNameProviding { public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 0: .same(proto: "WireGuard"), 1: .same(proto: "Tailnet"), - 2: .same(proto: "Trojan"), 3: .same(proto: "ProxySubscription"), ] } diff --git a/Apple/UI/Networks/Network.swift b/Apple/UI/Networks/Network.swift index 57ba7a5..4b68430 100644 --- a/Apple/UI/Networks/Network.swift +++ b/Apple/UI/Networks/Network.swift @@ -867,12 +867,6 @@ final class NetworkViewModel: Sendable { WireGuardCard(network: network).card case .tailnet: TailnetCard(network: network).card - case .trojan: - unsupportedCard( - id: network.id, - title: "Legacy Trojan Proxy", - detail: "Unsupported SOCKS-era profile. Import a proxy subscription instead." - ) case .proxySubscription: proxySubscriptionCard(network: network) case .UNRECOGNIZED(let rawValue): diff --git a/burrow-gtk/src/daemon_api.rs b/burrow-gtk/src/daemon_api.rs index 3b1c5db..1a163bf 100644 --- a/burrow-gtk/src/daemon_api.rs +++ b/burrow-gtk/src/daemon_api.rs @@ -408,11 +408,6 @@ fn summarize_network(network: &Network) -> NetworkSummary { match network.r#type() { NetworkType::WireGuard => summarize_wireguard(network), NetworkType::Tailnet => summarize_tailnet(network), - NetworkType::Trojan => NetworkSummary { - id: network.id, - title: "Legacy Trojan Proxy".to_owned(), - detail: "Unsupported SOCKS-era profile. Import a proxy subscription instead.".to_owned(), - }, NetworkType::ProxySubscription => summarize_proxy_subscription(network), } } diff --git a/burrow/src/daemon/runtime.rs b/burrow/src/daemon/runtime.rs index f240b95..ae5fa99 100644 --- a/burrow/src/daemon/runtime.rs +++ b/burrow/src/daemon/runtime.rs @@ -48,9 +48,6 @@ pub enum ResolvedTunnel { identity: RuntimeIdentity, config: Config, }, - Trojan { - identity: RuntimeIdentity, - }, ProxySubscription { identity: RuntimeIdentity, payload: ProxySubscriptionPayload, @@ -82,7 +79,6 @@ impl ResolvedTunnel { let config = Config::from_content_fmt(&payload, "ini")?; Ok(Self::WireGuard { identity, config }) } - NetworkType::Trojan => Ok(Self::Trojan { identity }), NetworkType::ProxySubscription => { let payload: ProxySubscriptionPayload = serde_json::from_slice(&network.payload) .context("proxy subscription payload must be valid JSON")?; @@ -97,7 +93,6 @@ impl ResolvedTunnel { Self::Passthrough { identity } | Self::Tailnet { identity, .. } | Self::WireGuard { identity, .. } - | Self::Trojan { identity, .. } | Self::ProxySubscription { identity, .. } => identity, } } @@ -125,9 +120,6 @@ impl ResolvedTunnel { mtu: Some(1280), }), Self::WireGuard { config, .. } => ServerConfig::try_from(config), - Self::Trojan { .. } => Err(anyhow::anyhow!( - "legacy Trojan SOCKS proxy networks are no longer supported; import a proxy subscription instead" - )), Self::ProxySubscription { payload, .. } => proxy_subscription_server_config(payload), } } @@ -206,9 +198,6 @@ impl ResolvedTunnel { } } } - Self::Trojan { .. } => Err(anyhow::anyhow!( - "legacy Trojan SOCKS proxy networks are no longer supported; import a proxy subscription instead" - )), Self::ProxySubscription { identity, payload } => { let runtime_config = proxy_runtime::proxy_runtime_config(&payload)?; let server_config = proxy_subscription_server_config_for_selected( @@ -283,10 +272,6 @@ pub enum ActiveTunnel { interface: Arc>, task: JoinHandle>, }, - Trojan { - identity: RuntimeIdentity, - server_config: ServerConfig, - }, ProxySubscription { identity: RuntimeIdentity, server_config: ServerConfig, @@ -301,7 +286,6 @@ impl ActiveTunnel { Self::Passthrough { identity, .. } | Self::Tailnet { identity, .. } | Self::WireGuard { identity, .. } - | Self::Trojan { identity, .. } | Self::ProxySubscription { identity, .. } => identity, } } @@ -311,7 +295,6 @@ impl ActiveTunnel { Self::Passthrough { server_config, .. } | Self::Tailnet { server_config, .. } | Self::WireGuard { server_config, .. } - | Self::Trojan { server_config, .. } | Self::ProxySubscription { server_config, .. } => server_config, } } @@ -398,7 +381,6 @@ impl ActiveTunnel { task_result??; Ok(()) } - Self::Trojan { .. } => Ok(()), Self::ProxySubscription { packet_bridge, tun_task, .. } => { if let Some(tun_task) = tun_task { tun_task.abort(); diff --git a/burrow/src/database.rs b/burrow/src/database.rs index b885947..4b2350a 100644 --- a/burrow/src/database.rs +++ b/burrow/src/database.rs @@ -283,11 +283,6 @@ fn validate_network_payload(network: &RPCNetwork) -> Result<()> { NetworkType::Tailnet => { TailnetConfig::from_slice(&network.payload)?; } - NetworkType::Trojan => { - return Err(anyhow::anyhow!( - "legacy Trojan SOCKS proxy networks are no longer supported; import a proxy subscription instead" - )); - } NetworkType::ProxySubscription => { let payload: ProxySubscriptionPayload = serde_json::from_slice(&network.payload)?; crate::proxy_subscription::validate_payload(&payload)?; diff --git a/proto/burrow.proto b/proto/burrow.proto index eb508d8..3e3307d 100644 --- a/proto/burrow.proto +++ b/proto/burrow.proto @@ -61,7 +61,8 @@ message Network { enum NetworkType { WireGuard = 0; Tailnet = 1; - Trojan = 2; + reserved 2; + reserved "Trojan"; ProxySubscription = 3; } From a7cf38c382d546b4a84d1ee6c0d365a4784d3444 Mon Sep 17 00:00:00 2001 From: JettChenT Date: Sun, 7 Jun 2026 21:02:45 -0700 Subject: [PATCH 4/4] Remove vendored ShadowTLS rustls forks --- Cargo.lock | 79 +- Cargo.toml | 4 - burrow/Cargo.toml | 4 - burrow/src/proxy_runtime.rs | 104 - .../src/proxy_runtime/shadowsocks_plugins.rs | 328 +- burrow/src/proxy_runtime/tests.rs | 188 +- third_party/rustls-fork-shadow-tls/Cargo.lock | 729 --- third_party/rustls-fork-shadow-tls/Cargo.toml | 107 - .../rustls-fork-shadow-tls/Cargo.toml.orig | 62 - .../rustls-fork-shadow-tls/LICENSE-APACHE | 201 - .../rustls-fork-shadow-tls/LICENSE-ISC | 15 - .../rustls-fork-shadow-tls/LICENSE-MIT | 25 - third_party/rustls-fork-shadow-tls/README.md | 298 -- .../benches/benchmarks.rs | 26 - third_party/rustls-fork-shadow-tls/build.rs | 13 - .../examples/internal/bench.rs | 664 --- .../examples/internal/bogo_shim.rs | 1195 ----- .../examples/internal/trytls_shim.rs | 120 - .../rustls-fork-shadow-tls/src/anchors.rs | 154 - .../rustls-fork-shadow-tls/src/bs_debug.rs | 77 - .../rustls-fork-shadow-tls/src/builder.rs | 268 - .../rustls-fork-shadow-tls/src/check.rs | 77 - .../rustls-fork-shadow-tls/src/cipher.rs | 101 - .../src/client/builder.rs | 192 - .../src/client/client_conn.rs | 742 --- .../src/client/common.rs | 114 - .../src/client/handy.rs | 161 - .../rustls-fork-shadow-tls/src/client/hs.rs | 891 ---- .../src/client/tls12.rs | 1127 ----- .../src/client/tls13.rs | 1191 ----- .../rustls-fork-shadow-tls/src/conn.rs | 1466 ------ .../rustls-fork-shadow-tls/src/enums.rs | 431 -- .../rustls-fork-shadow-tls/src/error.rs | 245 - .../rustls-fork-shadow-tls/src/hash_hs.rs | 240 - third_party/rustls-fork-shadow-tls/src/key.rs | 52 - .../rustls-fork-shadow-tls/src/key_log.rs | 55 - .../src/key_log_file.rs | 154 - third_party/rustls-fork-shadow-tls/src/kx.rs | 100 - third_party/rustls-fork-shadow-tls/src/lib.rs | 534 -- .../src/limited_cache.rs | 175 - .../src/manual/defaults.rs | 29 - .../src/manual/features.rs | 50 - .../src/manual/howto.rs | 36 - .../src/manual/implvulns.rs | 104 - .../rustls-fork-shadow-tls/src/manual/mod.rs | 30 - .../src/manual/tlsvulns.rs | 173 - .../rustls-fork-shadow-tls/src/msgs/alert.rs | 22 - .../rustls-fork-shadow-tls/src/msgs/base.rs | 170 - .../rustls-fork-shadow-tls/src/msgs/ccs.rs | 20 - .../rustls-fork-shadow-tls/src/msgs/codec.rs | 290 -- .../src/msgs/deframer.rs | 425 -- .../rustls-fork-shadow-tls/src/msgs/enums.rs | 375 -- .../src/msgs/enums_test.rs | 88 - .../src/msgs/fragmenter.rs | 162 - .../src/msgs/handshake-test.1.bin | Bin 4280 -> 0 bytes .../src/msgs/handshake.rs | 2380 --------- .../src/msgs/handshake_test.rs | 1247 ----- .../msgs/hello-api.devicecheck.apple.com.bin | Bin 74 -> 0 bytes .../src/msgs/hsjoiner.rs | 281 -- .../rustls-fork-shadow-tls/src/msgs/macros.rs | 88 - .../src/msgs/message.rs | 292 -- .../src/msgs/message_test.rs | 113 - .../rustls-fork-shadow-tls/src/msgs/mod.rs | 51 - .../src/msgs/persist.rs | 543 -- .../src/msgs/persist_test.rs | 78 - .../rustls-fork-shadow-tls/src/quic.rs | 620 --- .../rustls-fork-shadow-tls/src/rand.rs | 30 - .../src/record_layer.rs | 192 - .../src/server/builder.rs | 116 - .../src/server/common.rs | 41 - .../src/server/handy.rs | 278 -- .../rustls-fork-shadow-tls/src/server/hs.rs | 522 -- .../src/server/server_conn.rs | 849 ---- .../src/server/tls12.rs | 941 ---- .../src/server/tls13.rs | 1386 ------ .../rustls-fork-shadow-tls/src/sign.rs | 523 -- .../rustls-fork-shadow-tls/src/stream.rs | 254 - .../rustls-fork-shadow-tls/src/suites.rs | 340 -- .../src/testdata/cert-arstechnica.0.der | Bin 1517 -> 0 bytes .../src/testdata/cert-arstechnica.1.der | Bin 1101 -> 0 bytes .../src/testdata/cert-arstechnica.2.der | Bin 1174 -> 0 bytes .../src/testdata/cert-arstechnica.3.der | Bin 1145 -> 0 bytes .../src/testdata/cert-duckduckgo.0.der | Bin 1741 -> 0 bytes .../src/testdata/cert-duckduckgo.1.der | Bin 1218 -> 0 bytes .../src/testdata/cert-github.0.der | Bin 1290 -> 0 bytes .../src/testdata/cert-github.1.der | Bin 1055 -> 0 bytes .../src/testdata/cert-google.0.der | Bin 1164 -> 0 bytes .../src/testdata/cert-google.1.der | Bin 1434 -> 0 bytes .../src/testdata/cert-google.2.der | Bin 1382 -> 0 bytes .../src/testdata/cert-hn.0.der | Bin 1740 -> 0 bytes .../src/testdata/cert-hn.1.der | Bin 1218 -> 0 bytes .../src/testdata/cert-reddit.0.der | Bin 1730 -> 0 bytes .../src/testdata/cert-reddit.1.der | Bin 1218 -> 0 bytes .../src/testdata/cert-rustlang.0.der | Bin 1540 -> 0 bytes .../src/testdata/cert-rustlang.1.der | Bin 1101 -> 0 bytes .../src/testdata/cert-rustlang.2.der | Bin 1174 -> 0 bytes .../src/testdata/cert-rustlang.3.der | Bin 1145 -> 0 bytes .../src/testdata/cert-servo.0.der | Bin 1335 -> 0 bytes .../src/testdata/cert-servo.1.der | Bin 977 -> 0 bytes .../src/testdata/cert-stackoverflow.0.der | Bin 1785 -> 0 bytes .../src/testdata/cert-stackoverflow.1.der | Bin 1306 -> 0 bytes .../src/testdata/cert-stackoverflow.2.der | Bin 1380 -> 0 bytes .../src/testdata/cert-twitter.0.der | Bin 1606 -> 0 bytes .../src/testdata/cert-twitter.1.der | Bin 1262 -> 0 bytes .../src/testdata/cert-wapo.0.der | Bin 2211 -> 0 bytes .../src/testdata/cert-wapo.1.der | Bin 1329 -> 0 bytes .../src/testdata/cert-wikipedia.0.der | Bin 2086 -> 0 bytes .../src/testdata/cert-wikipedia.1.der | Bin 1051 -> 0 bytes .../deframer-empty-applicationdata.bin | Bin 5 -> 0 bytes .../testdata/deframer-invalid-contenttype.bin | Bin 7 -> 0 bytes .../src/testdata/deframer-invalid-empty.bin | Bin 5 -> 0 bytes .../src/testdata/deframer-invalid-length.bin | Bin 6 -> 0 bytes .../src/testdata/deframer-invalid-version.bin | Bin 7 -> 0 bytes .../src/testdata/deframer-test.1.bin | Bin 334 -> 0 bytes .../src/testdata/deframer-test.2.bin | Bin 7 -> 0 bytes .../src/testdata/eddsakey.der | Bin 48 -> 0 bytes .../src/testdata/nistp256key.der | Bin 121 -> 0 bytes .../src/testdata/nistp256key.pkcs8.der | Bin 138 -> 0 bytes .../src/testdata/nistp384key.der | Bin 167 -> 0 bytes .../src/testdata/nistp384key.pkcs8.der | Bin 185 -> 0 bytes .../src/testdata/prf-result.1.bin | 1 - .../src/testdata/prf-result.2.bin | 1 - .../src/testdata/rsa2048key.pkcs1.der | Bin 1190 -> 0 bytes .../src/testdata/rsa2048key.pkcs8.der | Bin 1216 -> 0 bytes .../rustls-fork-shadow-tls/src/ticketer.rs | 338 -- .../src/tls12/cipher.rs | 236 - .../rustls-fork-shadow-tls/src/tls12/mod.rs | 537 -- .../rustls-fork-shadow-tls/src/tls12/prf.rs | 67 - .../src/tls13/key_schedule.rs | 800 --- .../rustls-fork-shadow-tls/src/tls13/mod.rs | 224 - .../rustls-fork-shadow-tls/src/vecbuf.rs | 200 - .../rustls-fork-shadow-tls/src/verify.rs | 809 --- .../rustls-fork-shadow-tls/src/verifybench.rs | 245 - .../rustls-fork-shadow-tls/src/versions.rs | 99 - .../rustls-fork-shadow-tls/src/x509.rs | 93 - .../rustls-fork-shadow-tls/tests/api.rs | 4382 ----------------- .../rustls-fork-shadow-tls/tests/bogo.rs | 18 - .../tests/client_cert_verifier.rs | 324 -- .../tests/common/mod.rs | 474 -- .../tests/key_log_file_env.rs | 104 - .../tests/server_cert_verifier.rs | 272 - .../tokio-rustls-fork-shadow-tls/Cargo.lock | 523 -- .../tokio-rustls-fork-shadow-tls/Cargo.toml | 57 - .../Cargo.toml.orig | 30 - .../tokio-rustls-fork-shadow-tls/README.md | 1 - .../examples/connect.rs | 42 - .../src/client.rs | 86 - .../tokio-rustls-fork-shadow-tls/src/error.rs | 20 - .../tokio-rustls-fork-shadow-tls/src/lib.rs | 21 - .../src/safe_io.rs | 227 - .../src/server.rs | 49 - .../tokio-rustls-fork-shadow-tls/src/split.rs | 139 - .../src/stream.rs | 297 -- .../src/unsafe_io.rs | 124 - 154 files changed, 16 insertions(+), 36672 deletions(-) delete mode 100644 third_party/rustls-fork-shadow-tls/Cargo.lock delete mode 100644 third_party/rustls-fork-shadow-tls/Cargo.toml delete mode 100644 third_party/rustls-fork-shadow-tls/Cargo.toml.orig delete mode 100644 third_party/rustls-fork-shadow-tls/LICENSE-APACHE delete mode 100644 third_party/rustls-fork-shadow-tls/LICENSE-ISC delete mode 100644 third_party/rustls-fork-shadow-tls/LICENSE-MIT delete mode 100644 third_party/rustls-fork-shadow-tls/README.md delete mode 100644 third_party/rustls-fork-shadow-tls/benches/benchmarks.rs delete mode 100644 third_party/rustls-fork-shadow-tls/build.rs delete mode 100644 third_party/rustls-fork-shadow-tls/examples/internal/bench.rs delete mode 100644 third_party/rustls-fork-shadow-tls/examples/internal/bogo_shim.rs delete mode 100644 third_party/rustls-fork-shadow-tls/examples/internal/trytls_shim.rs delete mode 100644 third_party/rustls-fork-shadow-tls/src/anchors.rs delete mode 100644 third_party/rustls-fork-shadow-tls/src/bs_debug.rs delete mode 100644 third_party/rustls-fork-shadow-tls/src/builder.rs delete mode 100644 third_party/rustls-fork-shadow-tls/src/check.rs delete mode 100644 third_party/rustls-fork-shadow-tls/src/cipher.rs delete mode 100644 third_party/rustls-fork-shadow-tls/src/client/builder.rs delete mode 100644 third_party/rustls-fork-shadow-tls/src/client/client_conn.rs delete mode 100644 third_party/rustls-fork-shadow-tls/src/client/common.rs delete mode 100644 third_party/rustls-fork-shadow-tls/src/client/handy.rs delete mode 100644 third_party/rustls-fork-shadow-tls/src/client/hs.rs delete mode 100644 third_party/rustls-fork-shadow-tls/src/client/tls12.rs delete mode 100644 third_party/rustls-fork-shadow-tls/src/client/tls13.rs delete mode 100644 third_party/rustls-fork-shadow-tls/src/conn.rs delete mode 100644 third_party/rustls-fork-shadow-tls/src/enums.rs delete mode 100644 third_party/rustls-fork-shadow-tls/src/error.rs delete mode 100644 third_party/rustls-fork-shadow-tls/src/hash_hs.rs delete mode 100644 third_party/rustls-fork-shadow-tls/src/key.rs delete mode 100644 third_party/rustls-fork-shadow-tls/src/key_log.rs delete mode 100644 third_party/rustls-fork-shadow-tls/src/key_log_file.rs delete mode 100644 third_party/rustls-fork-shadow-tls/src/kx.rs delete mode 100644 third_party/rustls-fork-shadow-tls/src/lib.rs delete mode 100644 third_party/rustls-fork-shadow-tls/src/limited_cache.rs delete mode 100644 third_party/rustls-fork-shadow-tls/src/manual/defaults.rs delete mode 100644 third_party/rustls-fork-shadow-tls/src/manual/features.rs delete mode 100644 third_party/rustls-fork-shadow-tls/src/manual/howto.rs delete mode 100644 third_party/rustls-fork-shadow-tls/src/manual/implvulns.rs delete mode 100644 third_party/rustls-fork-shadow-tls/src/manual/mod.rs delete mode 100644 third_party/rustls-fork-shadow-tls/src/manual/tlsvulns.rs delete mode 100644 third_party/rustls-fork-shadow-tls/src/msgs/alert.rs delete mode 100644 third_party/rustls-fork-shadow-tls/src/msgs/base.rs delete mode 100644 third_party/rustls-fork-shadow-tls/src/msgs/ccs.rs delete mode 100644 third_party/rustls-fork-shadow-tls/src/msgs/codec.rs delete mode 100644 third_party/rustls-fork-shadow-tls/src/msgs/deframer.rs delete mode 100644 third_party/rustls-fork-shadow-tls/src/msgs/enums.rs delete mode 100644 third_party/rustls-fork-shadow-tls/src/msgs/enums_test.rs delete mode 100644 third_party/rustls-fork-shadow-tls/src/msgs/fragmenter.rs delete mode 100644 third_party/rustls-fork-shadow-tls/src/msgs/handshake-test.1.bin delete mode 100644 third_party/rustls-fork-shadow-tls/src/msgs/handshake.rs delete mode 100644 third_party/rustls-fork-shadow-tls/src/msgs/handshake_test.rs delete mode 100644 third_party/rustls-fork-shadow-tls/src/msgs/hello-api.devicecheck.apple.com.bin delete mode 100644 third_party/rustls-fork-shadow-tls/src/msgs/hsjoiner.rs delete mode 100644 third_party/rustls-fork-shadow-tls/src/msgs/macros.rs delete mode 100644 third_party/rustls-fork-shadow-tls/src/msgs/message.rs delete mode 100644 third_party/rustls-fork-shadow-tls/src/msgs/message_test.rs delete mode 100644 third_party/rustls-fork-shadow-tls/src/msgs/mod.rs delete mode 100644 third_party/rustls-fork-shadow-tls/src/msgs/persist.rs delete mode 100644 third_party/rustls-fork-shadow-tls/src/msgs/persist_test.rs delete mode 100644 third_party/rustls-fork-shadow-tls/src/quic.rs delete mode 100644 third_party/rustls-fork-shadow-tls/src/rand.rs delete mode 100644 third_party/rustls-fork-shadow-tls/src/record_layer.rs delete mode 100644 third_party/rustls-fork-shadow-tls/src/server/builder.rs delete mode 100644 third_party/rustls-fork-shadow-tls/src/server/common.rs delete mode 100644 third_party/rustls-fork-shadow-tls/src/server/handy.rs delete mode 100644 third_party/rustls-fork-shadow-tls/src/server/hs.rs delete mode 100644 third_party/rustls-fork-shadow-tls/src/server/server_conn.rs delete mode 100644 third_party/rustls-fork-shadow-tls/src/server/tls12.rs delete mode 100644 third_party/rustls-fork-shadow-tls/src/server/tls13.rs delete mode 100644 third_party/rustls-fork-shadow-tls/src/sign.rs delete mode 100644 third_party/rustls-fork-shadow-tls/src/stream.rs delete mode 100644 third_party/rustls-fork-shadow-tls/src/suites.rs delete mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/cert-arstechnica.0.der delete mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/cert-arstechnica.1.der delete mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/cert-arstechnica.2.der delete mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/cert-arstechnica.3.der delete mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/cert-duckduckgo.0.der delete mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/cert-duckduckgo.1.der delete mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/cert-github.0.der delete mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/cert-github.1.der delete mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/cert-google.0.der delete mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/cert-google.1.der delete mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/cert-google.2.der delete mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/cert-hn.0.der delete mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/cert-hn.1.der delete mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/cert-reddit.0.der delete mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/cert-reddit.1.der delete mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/cert-rustlang.0.der delete mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/cert-rustlang.1.der delete mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/cert-rustlang.2.der delete mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/cert-rustlang.3.der delete mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/cert-servo.0.der delete mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/cert-servo.1.der delete mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/cert-stackoverflow.0.der delete mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/cert-stackoverflow.1.der delete mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/cert-stackoverflow.2.der delete mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/cert-twitter.0.der delete mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/cert-twitter.1.der delete mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/cert-wapo.0.der delete mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/cert-wapo.1.der delete mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/cert-wikipedia.0.der delete mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/cert-wikipedia.1.der delete mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/deframer-empty-applicationdata.bin delete mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/deframer-invalid-contenttype.bin delete mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/deframer-invalid-empty.bin delete mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/deframer-invalid-length.bin delete mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/deframer-invalid-version.bin delete mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/deframer-test.1.bin delete mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/deframer-test.2.bin delete mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/eddsakey.der delete mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/nistp256key.der delete mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/nistp256key.pkcs8.der delete mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/nistp384key.der delete mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/nistp384key.pkcs8.der delete mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/prf-result.1.bin delete mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/prf-result.2.bin delete mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/rsa2048key.pkcs1.der delete mode 100644 third_party/rustls-fork-shadow-tls/src/testdata/rsa2048key.pkcs8.der delete mode 100644 third_party/rustls-fork-shadow-tls/src/ticketer.rs delete mode 100644 third_party/rustls-fork-shadow-tls/src/tls12/cipher.rs delete mode 100644 third_party/rustls-fork-shadow-tls/src/tls12/mod.rs delete mode 100644 third_party/rustls-fork-shadow-tls/src/tls12/prf.rs delete mode 100644 third_party/rustls-fork-shadow-tls/src/tls13/key_schedule.rs delete mode 100644 third_party/rustls-fork-shadow-tls/src/tls13/mod.rs delete mode 100644 third_party/rustls-fork-shadow-tls/src/vecbuf.rs delete mode 100644 third_party/rustls-fork-shadow-tls/src/verify.rs delete mode 100644 third_party/rustls-fork-shadow-tls/src/verifybench.rs delete mode 100644 third_party/rustls-fork-shadow-tls/src/versions.rs delete mode 100644 third_party/rustls-fork-shadow-tls/src/x509.rs delete mode 100644 third_party/rustls-fork-shadow-tls/tests/api.rs delete mode 100644 third_party/rustls-fork-shadow-tls/tests/bogo.rs delete mode 100644 third_party/rustls-fork-shadow-tls/tests/client_cert_verifier.rs delete mode 100644 third_party/rustls-fork-shadow-tls/tests/common/mod.rs delete mode 100644 third_party/rustls-fork-shadow-tls/tests/key_log_file_env.rs delete mode 100644 third_party/rustls-fork-shadow-tls/tests/server_cert_verifier.rs delete mode 100644 third_party/tokio-rustls-fork-shadow-tls/Cargo.lock delete mode 100644 third_party/tokio-rustls-fork-shadow-tls/Cargo.toml delete mode 100644 third_party/tokio-rustls-fork-shadow-tls/Cargo.toml.orig delete mode 100644 third_party/tokio-rustls-fork-shadow-tls/README.md delete mode 100644 third_party/tokio-rustls-fork-shadow-tls/examples/connect.rs delete mode 100644 third_party/tokio-rustls-fork-shadow-tls/src/client.rs delete mode 100644 third_party/tokio-rustls-fork-shadow-tls/src/error.rs delete mode 100644 third_party/tokio-rustls-fork-shadow-tls/src/lib.rs delete mode 100644 third_party/tokio-rustls-fork-shadow-tls/src/safe_io.rs delete mode 100644 third_party/tokio-rustls-fork-shadow-tls/src/server.rs delete mode 100644 third_party/tokio-rustls-fork-shadow-tls/src/split.rs delete mode 100644 third_party/tokio-rustls-fork-shadow-tls/src/stream.rs delete mode 100644 third_party/tokio-rustls-fork-shadow-tls/src/unsafe_io.rs diff --git a/Cargo.lock b/Cargo.lock index fc9a5ca..7a24f95 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -918,12 +918,11 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.4", "reqwest 0.12.23", - "ring 0.17.14", + "ring", "rusqlite", "rust-ini", "rust_tokio_kcp", "rustls", - "rustls-fork-shadow-tls", "rustls-pemfile 2.2.0", "schemars 0.8.22", "serde", @@ -937,7 +936,6 @@ dependencies = [ "tokio", "tokio-native-tls", "tokio-rustls", - "tokio-rustls-fork-shadow-tls", "tokio-snappy", "tokio-stream", "tokio-util", @@ -2846,7 +2844,7 @@ dependencies = [ "ipnet", "once_cell", "rand 0.9.2", - "ring 0.17.14", + "ring", "thiserror 2.0.16", "tinyvec", "tokio", @@ -4852,7 +4850,7 @@ dependencies = [ "getrandom 0.3.3", "lru-slab", "rand 0.9.2", - "ring 0.17.14", + "ring", "rustc-hash 2.1.1", "rustls", "rustls-pki-types", @@ -5468,21 +5466,6 @@ 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" @@ -5493,7 +5476,7 @@ dependencies = [ "cfg-if", "getrandom 0.2.16", "libc", - "untrusted 0.9.0", + "untrusted", "windows-sys 0.52.0", ] @@ -5507,7 +5490,7 @@ dependencies = [ "ed25519", "generic-array", "pkcs8", - "ring 0.17.14", + "ring", ] [[package]] @@ -5663,23 +5646,13 @@ dependencies = [ "aws-lc-rs", "log", "once_cell", - "ring 0.17.14", + "ring", "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" @@ -5715,9 +5688,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" dependencies = [ "aws-lc-rs", - "ring 0.17.14", + "ring", "rustls-pki-types", - "untrusted 0.9.0", + "untrusted", ] [[package]] @@ -5848,16 +5821,6 @@ 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" @@ -6388,12 +6351,6 @@ 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" @@ -6848,16 +6805,6 @@ 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" @@ -8358,12 +8305,6 @@ 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" @@ -8629,8 +8570,8 @@ 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", + "ring", + "untrusted", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 9c99d53..362ba2b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,10 +3,6 @@ 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/burrow/Cargo.toml b/burrow/Cargo.toml index f3cccb9..a7d5cea 100644 --- a/burrow/Cargo.toml +++ b/burrow/Cargo.toml @@ -98,13 +98,9 @@ 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", diff --git a/burrow/src/proxy_runtime.rs b/burrow/src/proxy_runtime.rs index 09e49f9..7eacdda 100644 --- a/burrow/src/proxy_runtime.rs +++ b/burrow/src/proxy_runtime.rs @@ -120,7 +120,6 @@ use rustls::{ version, ClientConfig, DigitallySignedStruct, Error as RustlsError, RootCertStore, SignatureScheme, }; -use rustls_fork_shadow_tls as shadow_rustls; use sha2::{Digest, Sha224, Sha256}; use shadowsocks::{ config::{ @@ -148,7 +147,6 @@ use tokio::{ #[cfg(target_vendor = "apple")] use tokio_native_tls::TlsConnector as TokioNativeTlsConnector; 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; @@ -407,7 +405,6 @@ enum ShadowsocksPluginTransport { WebSocket(ShadowsocksWebSocketTransport), ShadowTls(ShadowsocksShadowTlsTransport), Kcptun(ShadowsocksKcptunTransport), - Restls(ShadowsocksRestlsTransport), } #[derive(Debug, Clone, PartialEq, Eq)] @@ -2227,9 +2224,6 @@ impl ShadowsocksOutbound { 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") } @@ -7178,34 +7172,6 @@ fn load_rustls_client_identity( 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, @@ -7330,10 +7296,6 @@ impl MihomoClientFingerprint { ) } - #[cfg_attr(not(feature = "boring-browser-fingerprints"), allow(dead_code))] - fn shadow_tls_v3_boring_supported(self) -> bool { - self.shadow_tls_v2_boring_supported() - } } fn hex_nibble(byte: u8) -> Option { @@ -7454,72 +7416,6 @@ 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, diff --git a/burrow/src/proxy_runtime/shadowsocks_plugins.rs b/burrow/src/proxy_runtime/shadowsocks_plugins.rs index cd253e1..7f765d9 100644 --- a/burrow/src/proxy_runtime/shadowsocks_plugins.rs +++ b/burrow/src/proxy_runtime/shadowsocks_plugins.rs @@ -540,17 +540,6 @@ async fn shadow_tls_connect( 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}"), } } @@ -798,210 +787,6 @@ fn boring_certificate_fingerprint_matches( 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, @@ -1065,46 +850,6 @@ async fn restls_boring_connect( 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(); @@ -1143,49 +888,6 @@ fn shadow_tls_client_config(config: &ShadowsocksShadowTlsTransport) -> Result 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]; @@ -1492,26 +1194,12 @@ fn shadowsocks_plugin_transport( .to_owned(); let version = plugin_u8_opt(&plugin.opts, "version").unwrap_or(2); match version { - 1 | 2 | 3 => {} + 1 | 2 => {} 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", @@ -1553,20 +1241,6 @@ fn shadowsocks_plugin_transport( }, ))); } - 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") { diff --git a/burrow/src/proxy_runtime/tests.rs b/burrow/src/proxy_runtime/tests.rs index dbe5bca..9eb046b 100644 --- a/burrow/src/proxy_runtime/tests.rs +++ b/burrow/src/proxy_runtime/tests.rs @@ -1201,40 +1201,6 @@ mod tests { 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 { @@ -1263,23 +1229,12 @@ mod tests { 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()), + ("version".to_owned(), "2".to_owned()), ]), }; #[cfg(not(feature = "boring-browser-fingerprints"))] - let rejected_nodes = [ - tls_plugin_node(shadow_tls.clone()), - tls_plugin_node(restls.clone()), - ]; + let rejected_nodes = [tls_plugin_node(shadow_tls.clone())]; #[cfg(feature = "boring-browser-fingerprints")] let rejected_nodes: [ProxyNode; 0] = []; @@ -1287,11 +1242,9 @@ mod tests { 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}"); + assert!(err.contains("browser TLS profile"), "{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()); @@ -1309,7 +1262,7 @@ mod tests { opts: HashMap::from([ ("host".to_owned(), "cover.example".to_owned()), ("password".to_owned(), "secret".to_owned()), - ("version".to_owned(), "3".to_owned()), + ("version".to_owned(), "2".to_owned()), ]), }); if let ProxyNodeConfig::Shadowsocks(shadowsocks) = &mut none_node.config { @@ -1834,29 +1787,7 @@ OW4mfN8jmXq6wTtOv85mBGE= ("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() - ); + assert!(shadowsocks_plugin_transport(Some(&shadow_tls_v3_plugin), None).is_err()); let kcptun = ShadowsocksPlugin { name: "kcptun".to_owned(), @@ -1894,115 +1825,6 @@ OW4mfN8jmXq6wTtOv85mBGE= 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] diff --git a/third_party/rustls-fork-shadow-tls/Cargo.lock b/third_party/rustls-fork-shadow-tls/Cargo.lock deleted file mode 100644 index 16aefc7..0000000 --- a/third_party/rustls-fork-shadow-tls/Cargo.lock +++ /dev/null @@ -1,729 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "aho-corasick" -version = "0.7.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" -dependencies = [ - "memchr", -] - -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi 0.1.19", - "libc", - "winapi", -] - -[[package]] -name = "autocfg" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - -[[package]] -name = "base64" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bstr" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" -dependencies = [ - "lazy_static", - "memchr", - "regex-automata", - "serde", -] - -[[package]] -name = "bumpalo" -version = "3.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d" - -[[package]] -name = "cast" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" - -[[package]] -name = "cc" -version = "1.0.73" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "clap" -version = "2.34.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" -dependencies = [ - "bitflags", - "textwrap", - "unicode-width", -] - -[[package]] -name = "criterion" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b01d6de93b2b6c65e17c634a26653a29d107b3c98c607c765bf38d041531cd8f" -dependencies = [ - "atty", - "cast", - "clap", - "criterion-plot", - "csv", - "itertools", - "lazy_static", - "num-traits", - "oorandom", - "plotters", - "rayon", - "regex", - "serde", - "serde_cbor", - "serde_derive", - "serde_json", - "tinytemplate", - "walkdir", -] - -[[package]] -name = "criterion-plot" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2673cc8207403546f45f5fd319a974b1e6983ad1a3ee7e6041650013be041876" -dependencies = [ - "cast", - "itertools", -] - -[[package]] -name = "crossbeam-channel" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" -dependencies = [ - "cfg-if", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-deque" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" -dependencies = [ - "cfg-if", - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01a9af1f4c2ef74bb8aa1f7e19706bc72d03598c8a570bb5de72243c7a9d9d5a" -dependencies = [ - "autocfg", - "cfg-if", - "crossbeam-utils", - "memoffset", - "scopeguard", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "csv" -version = "1.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1" -dependencies = [ - "bstr", - "csv-core", - "itoa 0.4.8", - "ryu", - "serde", -] - -[[package]] -name = "csv-core" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" -dependencies = [ - "memchr", -] - -[[package]] -name = "either" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" - -[[package]] -name = "env_logger" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c90bf5f19754d10198ccb95b70664fc925bd1fc090a0fd9a6ebc54acc8cd6272" -dependencies = [ - "atty", - "humantime", - "log", - "regex", - "termcolor", -] - -[[package]] -name = "half" -version = "1.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" - -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - -[[package]] -name = "hermit-abi" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" -dependencies = [ - "libc", -] - -[[package]] -name = "humantime" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" - -[[package]] -name = "itertools" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -dependencies = [ - "either", -] - -[[package]] -name = "itoa" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" - -[[package]] -name = "itoa" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" - -[[package]] -name = "js-sys" -version = "0.3.60" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" -dependencies = [ - "wasm-bindgen", -] - -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - -[[package]] -name = "libc" -version = "0.2.135" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68783febc7782c6c5cb401fbda4de5a9898be1762314da0bb2c10ced61f18b0c" - -[[package]] -name = "log" -version = "0.4.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "memchr" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" - -[[package]] -name = "memoffset" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" -dependencies = [ - "autocfg", -] - -[[package]] -name = "num-traits" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" -dependencies = [ - "autocfg", -] - -[[package]] -name = "num_cpus" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" -dependencies = [ - "hermit-abi 0.2.6", - "libc", -] - -[[package]] -name = "once_cell" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" - -[[package]] -name = "oorandom" -version = "11.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" - -[[package]] -name = "plotters" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2538b639e642295546c50fcd545198c9d64ee2a38620a628724a3b266d5fbf97" -dependencies = [ - "num-traits", - "plotters-backend", - "plotters-svg", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "plotters-backend" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "193228616381fecdc1224c62e96946dfbc73ff4384fba576e052ff8c1bea8142" - -[[package]] -name = "plotters-svg" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9a81d2759aae1dae668f783c308bc5c8ebd191ff4184aaa1b37f65a6ae5a56f" -dependencies = [ - "plotters-backend", -] - -[[package]] -name = "proc-macro2" -version = "1.0.47" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "rayon" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db3a213adf02b3bcfd2d3846bb41cb22857d131789e01df434fb7e7bc0759b7" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "356a0625f1954f730c0201cdab48611198dc6ce21f4acff55089b5a78e6e835b" -dependencies = [ - "crossbeam-channel", - "crossbeam-deque", - "crossbeam-utils", - "num_cpus", -] - -[[package]] -name = "regex" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" - -[[package]] -name = "regex-syntax" -version = "0.6.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" - -[[package]] -name = "ring" -version = "0.16.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" -dependencies = [ - "cc", - "libc", - "once_cell", - "spin", - "untrusted", - "web-sys", - "winapi", -] - -[[package]] -name = "rustls-fork-shadow-tls" -version = "0.20.8" -dependencies = [ - "base64", - "criterion", - "env_logger", - "log", - "ring", - "rustls-pemfile", - "rustversion", - "sct", - "webpki", - "webpki-roots", -] - -[[package]] -name = "rustls-pemfile" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0864aeff53f8c05aa08d86e5ef839d3dfcf07aeba2db32f12db0ef716e87bd55" -dependencies = [ - "base64", -] - -[[package]] -name = "rustversion" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8" - -[[package]] -name = "ryu" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" - -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "scopeguard" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" - -[[package]] -name = "sct" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "serde" -version = "1.0.145" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728eb6351430bccb993660dfffc5a72f91ccc1295abaa8ce19b27ebe4f75568b" - -[[package]] -name = "serde_cbor" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" -dependencies = [ - "half", - "serde", -] - -[[package]] -name = "serde_derive" -version = "1.0.145" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fa1584d3d1bcacd84c277a0dfe21f5b0f6accf4a23d04d4c6d61f1af522b4c" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.93" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cad406b69c91885b5107daf2c29572f6c8cdb3c66826821e286c533490c0bc76" -dependencies = [ - "itoa 1.0.5", - "ryu", - "serde", -] - -[[package]] -name = "spin" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" - -[[package]] -name = "syn" -version = "1.0.102" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fcd952facd492f9be3ef0d0b7032a6e442ee9b361d4acc2b1d0c4aaa5f613a1" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "termcolor" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "textwrap" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" -dependencies = [ - "unicode-width", -] - -[[package]] -name = "tinytemplate" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" -dependencies = [ - "serde", - "serde_json", -] - -[[package]] -name = "unicode-ident" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" - -[[package]] -name = "unicode-width" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" - -[[package]] -name = "untrusted" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" - -[[package]] -name = "walkdir" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" -dependencies = [ - "same-file", - "winapi", - "winapi-util", -] - -[[package]] -name = "wasm-bindgen" -version = "0.2.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" -dependencies = [ - "cfg-if", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" -dependencies = [ - "bumpalo", - "log", - "once_cell", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" - -[[package]] -name = "web-sys" -version = "0.3.60" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "webpki" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "webpki-roots" -version = "0.22.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368bfe657969fb01238bb756d351dcade285e0f6fcbd36dcb23359a5169975be" -dependencies = [ - "webpki", -] - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" -dependencies = [ - "winapi", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/third_party/rustls-fork-shadow-tls/Cargo.toml b/third_party/rustls-fork-shadow-tls/Cargo.toml deleted file mode 100644 index de79f4a..0000000 --- a/third_party/rustls-fork-shadow-tls/Cargo.toml +++ /dev/null @@ -1,107 +0,0 @@ -# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO -# -# When uploading crates to the registry Cargo will automatically -# "normalize" Cargo.toml files for maximal compatibility -# with all versions of Cargo and also rewrite `path` dependencies -# to registry (e.g., crates.io) dependencies. -# -# If you are reading this file be aware that the original Cargo.toml -# will likely look very different (and much more reasonable). -# See Cargo.toml.orig for the original contents. - -[package] -edition = "2018" -rust-version = "1.57" -name = "rustls-fork-shadow-tls" -version = "0.20.8" -build = "build.rs" -autobenches = false -description = "Rustls is a modern TLS library written in Rust." -homepage = "https://github.com/rustls/rustls" -readme = "README.md" -categories = [ - "network-programming", - "cryptography", -] -license = "Apache-2.0/ISC/MIT" -repository = "https://github.com/rustls/rustls" -resolver = "2" - -[package.metadata.docs.rs] -all-features = true -rustdoc-args = [ - "--cfg", - "docsrs", -] - -[[example]] -name = "bogo_shim" -path = "examples/internal/bogo_shim.rs" -required-features = [ - "dangerous_configuration", - "quic", -] - -[[example]] -name = "trytls_shim" -path = "examples/internal/trytls_shim.rs" - -[[example]] -name = "bench" -path = "examples/internal/bench.rs" - -[[bench]] -name = "benchmarks" -path = "benches/benchmarks.rs" -harness = false - -[dependencies.log] -version = "0.4.4" -optional = true - -[dependencies.ring] -version = "0.16.20" - -[dependencies.sct] -version = "0.7.0" - -[dependencies.webpki] -version = "0.22.0" -features = [ - "alloc", - "std", -] - -[dev-dependencies.base64] -version = "0.13.0" - -[dev-dependencies.criterion] -version = "0.3.0" - -[dev-dependencies.env_logger] -version = "0.9.0" - -[dev-dependencies.log] -version = "0.4.4" - -[dev-dependencies.rustls-pemfile] -version = "1.0.0" - -[dev-dependencies.webpki-roots] -version = "0.22.0" - -[build-dependencies.rustversion] -version = "1.0.6" -optional = true - -[features] -dangerous_configuration = [] -default = [ - "logging", - "tls12", -] -logging = ["log"] -quic = [] -read_buf = ["rustversion"] -secret_extraction = [] -tls12 = [] diff --git a/third_party/rustls-fork-shadow-tls/Cargo.toml.orig b/third_party/rustls-fork-shadow-tls/Cargo.toml.orig deleted file mode 100644 index 86d90c7..0000000 --- a/third_party/rustls-fork-shadow-tls/Cargo.toml.orig +++ /dev/null @@ -1,62 +0,0 @@ -[package] -name = "rustls-fork-shadow-tls" -version = "0.20.8" -edition = "2018" -rust-version = "1.57" -license = "Apache-2.0/ISC/MIT" -readme = "../README.md" -description = "Rustls is a modern TLS library written in Rust." -homepage = "https://github.com/rustls/rustls" -repository = "https://github.com/rustls/rustls" -categories = ["network-programming", "cryptography"] -autobenches = false -build = "build.rs" -resolver = "2" - -[build-dependencies] -rustversion = { version = "1.0.6", optional = true } - -[dependencies] -log = { version = "0.4.4", optional = true } -ring = "0.16.20" -sct = "0.7.0" -webpki = { version = "0.22.0", features = ["alloc", "std"] } - -[features] -default = ["logging", "tls12"] -logging = ["log"] -dangerous_configuration = [] -secret_extraction = [] -quic = [] -tls12 = [] -read_buf = ["rustversion"] - -[dev-dependencies] -env_logger = "0.9.0" -log = "0.4.4" -webpki-roots = "0.22.0" -criterion = "0.3.0" -rustls-pemfile = "1.0.0" -base64 = "0.13.0" - -[[example]] -name = "bogo_shim" -path = "examples/internal/bogo_shim.rs" -required-features = ["dangerous_configuration", "quic"] - -[[example]] -name = "trytls_shim" -path = "examples/internal/trytls_shim.rs" - -[[example]] -name = "bench" -path = "examples/internal/bench.rs" - -[[bench]] -name = "benchmarks" -path = "benches/benchmarks.rs" -harness = false - -[package.metadata.docs.rs] -all-features = true -rustdoc-args = ["--cfg", "docsrs"] diff --git a/third_party/rustls-fork-shadow-tls/LICENSE-APACHE b/third_party/rustls-fork-shadow-tls/LICENSE-APACHE deleted file mode 100644 index 16fe87b..0000000 --- a/third_party/rustls-fork-shadow-tls/LICENSE-APACHE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - -Copyright [yyyy] [name of copyright owner] - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/third_party/rustls-fork-shadow-tls/LICENSE-ISC b/third_party/rustls-fork-shadow-tls/LICENSE-ISC deleted file mode 100644 index 03acf1b..0000000 --- a/third_party/rustls-fork-shadow-tls/LICENSE-ISC +++ /dev/null @@ -1,15 +0,0 @@ -ISC License (ISC) -Copyright (c) 2016, Joseph Birr-Pixton - -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 deleted file mode 100644 index ef480e6..0000000 --- a/third_party/rustls-fork-shadow-tls/LICENSE-MIT +++ /dev/null @@ -1,25 +0,0 @@ -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 deleted file mode 100644 index 5ddb46d..0000000 --- a/third_party/rustls-fork-shadow-tls/README.md +++ /dev/null @@ -1,298 +0,0 @@ -

- -

- -
- -# 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 deleted file mode 100644 index 08191be..0000000 --- a/third_party/rustls-fork-shadow-tls/benches/benchmarks.rs +++ /dev/null @@ -1,26 +0,0 @@ -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 deleted file mode 100644 index 9c73252..0000000 --- a/third_party/rustls-fork-shadow-tls/build.rs +++ /dev/null @@ -1,13 +0,0 @@ -/// 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 deleted file mode 100644 index 5ef69d0..0000000 --- a/third_party/rustls-fork-shadow-tls/examples/internal/bench.rs +++ /dev/null @@ -1,664 +0,0 @@ -// 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 deleted file mode 100644 index 6e4de62..0000000 --- a/third_party/rustls-fork-shadow-tls/examples/internal/bogo_shim.rs +++ /dev/null @@ -1,1195 +0,0 @@ -// 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 deleted file mode 100644 index f47bbd0..0000000 --- a/third_party/rustls-fork-shadow-tls/examples/internal/trytls_shim.rs +++ /dev/null @@ -1,120 +0,0 @@ -// 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 deleted file mode 100644 index 4caf3e7..0000000 --- a/third_party/rustls-fork-shadow-tls/src/anchors.rs +++ /dev/null @@ -1,154 +0,0 @@ -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 deleted file mode 100644 index ad73ee6..0000000 --- a/third_party/rustls-fork-shadow-tls/src/bs_debug.rs +++ /dev/null @@ -1,77 +0,0 @@ -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 deleted file mode 100644 index 8b63f8c..0000000 --- a/third_party/rustls-fork-shadow-tls/src/builder.rs +++ /dev/null @@ -1,268 +0,0 @@ -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 deleted file mode 100644 index d318343..0000000 --- a/third_party/rustls-fork-shadow-tls/src/check.rs +++ /dev/null @@ -1,77 +0,0 @@ -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 deleted file mode 100644 index b595ca6..0000000 --- a/third_party/rustls-fork-shadow-tls/src/cipher.rs +++ /dev/null @@ -1,101 +0,0 @@ -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 deleted file mode 100644 index 464bfb9..0000000 --- a/third_party/rustls-fork-shadow-tls/src/client/builder.rs +++ /dev/null @@ -1,192 +0,0 @@ -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 deleted file mode 100644 index 59244a7..0000000 --- a/third_party/rustls-fork-shadow-tls/src/client/client_conn.rs +++ /dev/null @@ -1,742 +0,0 @@ -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 deleted file mode 100644 index ac9094d..0000000 --- a/third_party/rustls-fork-shadow-tls/src/client/common.rs +++ /dev/null @@ -1,114 +0,0 @@ -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 deleted file mode 100644 index 804887a..0000000 --- a/third_party/rustls-fork-shadow-tls/src/client/handy.rs +++ /dev/null @@ -1,161 +0,0 @@ -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 deleted file mode 100644 index a4bec64..0000000 --- a/third_party/rustls-fork-shadow-tls/src/client/hs.rs +++ /dev/null @@ -1,891 +0,0 @@ -#[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 deleted file mode 100644 index 2956e28..0000000 --- a/third_party/rustls-fork-shadow-tls/src/client/tls12.rs +++ /dev/null @@ -1,1127 +0,0 @@ -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 deleted file mode 100644 index 4c118bb..0000000 --- a/third_party/rustls-fork-shadow-tls/src/client/tls13.rs +++ /dev/null @@ -1,1191 +0,0 @@ -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 deleted file mode 100644 index f7d93a8..0000000 --- a/third_party/rustls-fork-shadow-tls/src/conn.rs +++ /dev/null @@ -1,1466 +0,0 @@ -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 deleted file mode 100644 index 20c39ee..0000000 --- a/third_party/rustls-fork-shadow-tls/src/enums.rs +++ /dev/null @@ -1,431 +0,0 @@ -#![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 deleted file mode 100644 index 32d4903..0000000 --- a/third_party/rustls-fork-shadow-tls/src/error.rs +++ /dev/null @@ -1,245 +0,0 @@ -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 deleted file mode 100644 index 3dd66b1..0000000 --- a/third_party/rustls-fork-shadow-tls/src/hash_hs.rs +++ /dev/null @@ -1,240 +0,0 @@ -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 deleted file mode 100644 index dbd4cc5..0000000 --- a/third_party/rustls-fork-shadow-tls/src/key.rs +++ /dev/null @@ -1,52 +0,0 @@ -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 deleted file mode 100644 index 1b6b3fe..0000000 --- a/third_party/rustls-fork-shadow-tls/src/key_log.rs +++ /dev/null @@ -1,55 +0,0 @@ -/// 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 deleted file mode 100644 index 7907113..0000000 --- a/third_party/rustls-fork-shadow-tls/src/key_log_file.rs +++ /dev/null @@ -1,154 +0,0 @@ -#[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 deleted file mode 100644 index 4736570..0000000 --- a/third_party/rustls-fork-shadow-tls/src/kx.rs +++ /dev/null @@ -1,100 +0,0 @@ -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 deleted file mode 100644 index 27db2b2..0000000 --- a/third_party/rustls-fork-shadow-tls/src/lib.rs +++ /dev/null @@ -1,534 +0,0 @@ -//! # 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 deleted file mode 100644 index 6994d88..0000000 --- a/third_party/rustls-fork-shadow-tls/src/limited_cache.rs +++ /dev/null @@ -1,175 +0,0 @@ -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 deleted file mode 100644 index aa15863..0000000 --- a/third_party/rustls-fork-shadow-tls/src/manual/defaults.rs +++ /dev/null @@ -1,29 +0,0 @@ -/*! - -## 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 deleted file mode 100644 index 639a231..0000000 --- a/third_party/rustls-fork-shadow-tls/src/manual/features.rs +++ /dev/null @@ -1,50 +0,0 @@ -/*! - -## 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 deleted file mode 100644 index aa68a1e..0000000 --- a/third_party/rustls-fork-shadow-tls/src/manual/howto.rs +++ /dev/null @@ -1,36 +0,0 @@ -/*! # 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 deleted file mode 100644 index d08e110..0000000 --- a/third_party/rustls-fork-shadow-tls/src/manual/implvulns.rs +++ /dev/null @@ -1,104 +0,0 @@ -/*! # 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 deleted file mode 100644 index 778d24b..0000000 --- a/third_party/rustls-fork-shadow-tls/src/manual/mod.rs +++ /dev/null @@ -1,30 +0,0 @@ -/*! - -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 deleted file mode 100644 index 77d5510..0000000 --- a/third_party/rustls-fork-shadow-tls/src/manual/tlsvulns.rs +++ /dev/null @@ -1,173 +0,0 @@ -/*! # 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 deleted file mode 100644 index 2e12e6e..0000000 --- a/third_party/rustls-fork-shadow-tls/src/msgs/alert.rs +++ /dev/null @@ -1,22 +0,0 @@ -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 deleted file mode 100644 index 8ae0f6d..0000000 --- a/third_party/rustls-fork-shadow-tls/src/msgs/base.rs +++ /dev/null @@ -1,170 +0,0 @@ -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 deleted file mode 100644 index d9b0eb3..0000000 --- a/third_party/rustls-fork-shadow-tls/src/msgs/ccs.rs +++ /dev/null @@ -1,20 +0,0 @@ -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 deleted file mode 100644 index 9ad1b12..0000000 --- a/third_party/rustls-fork-shadow-tls/src/msgs/codec.rs +++ /dev/null @@ -1,290 +0,0 @@ -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 deleted file mode 100644 index 2ada2d8..0000000 --- a/third_party/rustls-fork-shadow-tls/src/msgs/deframer.rs +++ /dev/null @@ -1,425 +0,0 @@ -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 deleted file mode 100644 index 333db73..0000000 --- a/third_party/rustls-fork-shadow-tls/src/msgs/enums.rs +++ /dev/null @@ -1,375 +0,0 @@ -#![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 deleted file mode 100644 index 220b5aa..0000000 --- a/third_party/rustls-fork-shadow-tls/src/msgs/enums_test.rs +++ /dev/null @@ -1,88 +0,0 @@ -/// 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 deleted file mode 100644 index b649bcf..0000000 --- a/third_party/rustls-fork-shadow-tls/src/msgs/fragmenter.rs +++ /dev/null @@ -1,162 +0,0 @@ -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 deleted file mode 100644 index 5c04f3f6064a55c0160bebfa7ec62d259a476722..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4280 zcmWe*W@PkaWMFh44DiW43-QQ4CV}G3>^&Z3~dap2aFEr9nd)5@Eu@bn8RShpwBRk!SR6L0rmqt2bdUG8CVVo z9pGRPV-RK#VBkH#dw`jNi$Rn@h=HGhhk=vfKO+M}1_J{FKLalV16O)}etJ%-UUGge z12+Q;GXo=RV=PSOpbXuiKpx5H(XI)ay)$C$8pv}M_{GXA5kpT=q)^Ia0F*1lTGxIfYGw{_g z@KrLf{WfS~`)<(0dVc}f9uDc6xjXlLnRLm3myJ`a&7X)r-?-UQ*fZ{lCQs$6sT@}nc=@)Hdz zlr|fHX9==D?2kI8;r$d z(8TNqQXt6E#O!L&#O&CF;=m?eExp9Nl%o916i|X{64TO4EGS6LOV7+pMe;|Jkd|I@ zPJU?$vX~gmr@D!Gi8+-enaRaqGq_>mddZ1RNSvG|K7>rZUS=LDKdS(hUz**-kEA|V zFR=(+pcG9Y30*~UKAMW$3N(QdbS-6hNN!F^MdG9uBXQD-kT@BoNSw?PBu-uq5~l!# zQvwQ9NK#2mNi4{O1RmVW5Rtql5m*=`=jZ087A1o-ev=?fv@A0vH6I-8pm;4VNi4}s z)=SO<#cMi@m!I3jrKOi%R$>TN2w@w6*&ZnaO&{s(G~ZN{f;+GV{P{1VFqT zw3x5VFD)reO4ZHFPtMQJ&V;y%S4$5n4%R3J=ccBVCMT9;=0gnR*3zpi$;?d$E0TgI zznsj}ypm#Md${3p`MFIjAoWdb>G}D3={ZdzkdQ-5dPrqslMqA&O0aPud5f#Oyc}d1 z$TSYHv-Ofvo4CQOB#3`Rpd1wE8*qd26F&)WJoX=BJ0RcE-rxNb3PU^7LgOGO1rvP z%-L_wOWKw%`%NA=AAi{v6g?k4#foOxR_tx#)Yvw|S+tmP`w4c)uG z1ou5EZ{m+_+}K)hbyI&`;H8foo>CvH9^Fl!9=T`)oVBVPT1!N6Y3ZwcFKHntyU>^El0O^q=$aUO>04<*M%;vN_tJ*AyIO zO&vqyzizlX@!kL8+lzP>t#>;d*dxAw+g~9D<}U_K%pVMzm~KF7KV~MYX0(7Za6+w( zKt(xoC<~V`uX}2KNKt8V38?DRGn6)v1lKXbLU3{SocyH390g}baE)hdU}9iuXliI` zY6h>&w5_#oi3ezbtFq_)0yDAqTb!|CnOW)&(2+8 zm#p1lnwi>iLh`7a(vRS&`yMxV2^`aUvunD7;Bm{D0xSzYBvwsT?ePmLeYEW@$0?CN z=ajNG`<-`Vo<5zq?!d--)$3i2&+oD;+z4)aD{Fl1kw*K7B~#p7#aUFF)|pKg2d!m^bB+i zv=(SAP-{~`(U=4(m-Um2a*FlSOVYs!OfR`8$3PsUO@T$gfDf(6KyAi>v;C@HD;3UW zr8mEgyHa;g_DKB$RW^pBD!*5r(9Ny8H@7KOr!mRez9xDn@+Mppkek_3ELHI7Z7AP6 zakf|K?~GpS+)Zb<|JHq|;9fMtv)$5{&7_oaayFPc44UnQc0WVTG_0%)27aj7Ucdkp*(_Yb?5>5SnQ4g?hPnpYpbXC~ ztO6BL2u@8dElO2zPAw|QOv_A8EJ;PQdqG8jfsui!k)eT!fq{Vms0gqyuz+)^Qp^y_ z`?t>smR>5Gu$TW`JejO?tD;e zYPfE|`%c}4s_?a+-oHe~<3J0A;g3Y0pmgEwPkj*gloXaw0e1KMTE!vaGYd z`P$|4v&*j6k?T*eeDupKSlIGPoSOD++Z7^vjb2MIYXq?rK3#2W`_N)HyID)poXS^X z24CV2Xul}Te)1EV_dggkzD3FV9?vp7+8T6$g>z57#ip=&|RS2rU}i+$wfJO>8bf8pt=K*n~TATCpi(E zo58&T1r~P$R|97@mQYy*1_NsYOIT^b#3-f@=vAxDeXBDC@MD;G7p8X^V5~cL-l`|JNNAQ~YP$O=%-Zi`COjL{?3^^m?(32#4pk z-A^aJZ#H`^8`Wf#MKegBkfjY92c^DY_ znHj_#SuXP|;5;BY+x>p~k(gg^o8MJ0zh1H_^w_ijpO;U3HuwlfFTKPU$IZQcU!q!4 z;B?O?6E)Yb6unidRFdj|uMew0xMS { - 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 deleted file mode 100644 index 3e19cc1..0000000 --- a/third_party/rustls-fork-shadow-tls/src/msgs/handshake_test.rs +++ /dev/null @@ -1,1247 +0,0 @@ -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 deleted file mode 100644 index fcbaaadee71934e744fe9044c344f077d1f2dbcc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 74 zcmZQ#U~pq*PV$?xP$fKC^3Yx{!&N)V(jylHbO)_C=, - - /// 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 deleted file mode 100644 index da9ff6a..0000000 --- a/third_party/rustls-fork-shadow-tls/src/msgs/macros.rs +++ /dev/null @@ -1,88 +0,0 @@ -/// 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 deleted file mode 100644 index a8b8308..0000000 --- a/third_party/rustls-fork-shadow-tls/src/msgs/message.rs +++ /dev/null @@ -1,292 +0,0 @@ -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 deleted file mode 100644 index a50df90..0000000 --- a/third_party/rustls-fork-shadow-tls/src/msgs/message_test.rs +++ /dev/null @@ -1,113 +0,0 @@ -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 deleted file mode 100644 index 655ff13..0000000 --- a/third_party/rustls-fork-shadow-tls/src/msgs/mod.rs +++ /dev/null @@ -1,51 +0,0 @@ -#![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 deleted file mode 100644 index e9bcbda..0000000 --- a/third_party/rustls-fork-shadow-tls/src/msgs/persist.rs +++ /dev/null @@ -1,543 +0,0 @@ -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 deleted file mode 100644 index c4a4165..0000000 --- a/third_party/rustls-fork-shadow-tls/src/msgs/persist_test.rs +++ /dev/null @@ -1,78 +0,0 @@ -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 deleted file mode 100644 index 1621495..0000000 --- a/third_party/rustls-fork-shadow-tls/src/quic.rs +++ /dev/null @@ -1,620 +0,0 @@ -/// 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 deleted file mode 100644 index 14cde00..0000000 --- a/third_party/rustls-fork-shadow-tls/src/rand.rs +++ /dev/null @@ -1,30 +0,0 @@ -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 deleted file mode 100644 index e525772..0000000 --- a/third_party/rustls-fork-shadow-tls/src/record_layer.rs +++ /dev/null @@ -1,192 +0,0 @@ -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 deleted file mode 100644 index d32ce44..0000000 --- a/third_party/rustls-fork-shadow-tls/src/server/builder.rs +++ /dev/null @@ -1,116 +0,0 @@ -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 deleted file mode 100644 index 2e3420c..0000000 --- a/third_party/rustls-fork-shadow-tls/src/server/common.rs +++ /dev/null @@ -1,41 +0,0 @@ -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 deleted file mode 100644 index 296d20c..0000000 --- a/third_party/rustls-fork-shadow-tls/src/server/handy.rs +++ /dev/null @@ -1,278 +0,0 @@ -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 deleted file mode 100644 index 6fc3bd8..0000000 --- a/third_party/rustls-fork-shadow-tls/src/server/hs.rs +++ /dev/null @@ -1,522 +0,0 @@ -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 deleted file mode 100644 index cf8669b..0000000 --- a/third_party/rustls-fork-shadow-tls/src/server/server_conn.rs +++ /dev/null @@ -1,849 +0,0 @@ -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 deleted file mode 100644 index 45b6c78..0000000 --- a/third_party/rustls-fork-shadow-tls/src/server/tls12.rs +++ /dev/null @@ -1,941 +0,0 @@ -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 deleted file mode 100644 index c7ec516..0000000 --- a/third_party/rustls-fork-shadow-tls/src/server/tls13.rs +++ /dev/null @@ -1,1386 +0,0 @@ -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 deleted file mode 100644 index 9c916f6..0000000 --- a/third_party/rustls-fork-shadow-tls/src/sign.rs +++ /dev/null @@ -1,523 +0,0 @@ -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 deleted file mode 100644 index 70f0fa4..0000000 --- a/third_party/rustls-fork-shadow-tls/src/stream.rs +++ /dev/null @@ -1,254 +0,0 @@ -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 deleted file mode 100644 index 8d3590c..0000000 --- a/third_party/rustls-fork-shadow-tls/src/suites.rs +++ /dev/null @@ -1,340 +0,0 @@ -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 deleted file mode 100644 index ac81cd85ec8d9eaddab3eedc92cfbf72ae643905..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1517 zcmXqLVtr}Q#By-~GZP~dlK}U^iNA!7xlUPd@`K^7gN3RFylk9WZ60mkc^MhGSs4u6 z47m+B*_cCF*o2uvgAMr&ctIR4VK&Fy#H#!}Ls0`^kN~$ZPjG5cS!$7jv!jBc6HFa5 zOr3$8IIoeRp`nqb0T@Jy^BNf$8X1`z8Jk*~T1FYj7)lvPfJ|cM5!BL4EGjNZP0q;6 zOit8G&d)VyVpKwQ7$YkKa}y&!gFzD`7gG}>Bf~7qHl9aLUFAQOWme^Df1ajpXHyrw zKrYxM=EvQ5#kpU$D3q-fz4dU(*LeB)&d1_4k7wnH=_s>jt1aU{b&AbvVQALW-{Hc$ ziXY!=clciTf4*s&!-rc{pYLAeJ#zGim_&Ds>cqBtueLDCeBLte-4Tmjx6Zrm@0qzy zO!Qk(%;6`D3ZL1huL`L?x=3ShZuHhrrfvr%MkOb~4kp#7RoB05`Sn`G_Q^`go7tI1 zoHX@+%AAz_!qpoV_A&j7+k@s>+lp1D%&(Sh=DE1-rqPO}wngkgPWid#X7Lnu^=T#1KW#4<$buB`v52vVOcq@EY(wK6 z16Qd@(FyxsiTYOy+Z*VDqy<^j3{;vh8(VrK4?VJRzA3uF#)nZg*kGC)|FxF=B zcCTSzU}0u(GjL&22sxb+5$7DP{AUx(9;MdKBN5&+KkZ}|p4o59w2m>9rH)BKcFv5l z_E-Dct<~iA#7x$IYVLIVQbEl2gq>T|Px^!laWa%K1TNn2^)F-PoUq&-<`w-sORCOD ztDpWpufu5;@4qYEtWTlV&H`KOZs5wK;Q4xj&1gN7BE!2I3WrT+hPD|sNxW^TnsNHkuG9bWI(TDtgv`&L9{yr6Ln(vfiBlWz zE4g_I99w9`>C_(QXV@*x{Q9-`@r(}>lBThJgW8)4wioPOh1}?#;!DaVao^9Ul=U4h zSP}ehj@ctE1CuR97H?QNFEJ@3Khi4>i(Peg-=Y;;KdrW1&Y63?b4_Iag1?c|T{tHE z|AbsXaDxj7Mg}+WE&q<1OPc)AlDZ?vWMUjuRK&JcI#PaCkv7kfqrJ`6iUI4|OFhrO z<5s$LW*zsOmd)iM(9c@y-87MOu>IXJy~m`Td4HlQFaD zO!lJARcB_bY?@Me!i9n1P4k+><7zYQB`q@5UFQlPv=z77IzxEVl!s4*1y^h_FSe8O VYIt>gIdiYf;b1dSwhn8i<1gn1ux(0t!L-`6UX@jtYhb za^k#(riKPaMh0LICC+PPYG`0+3Fa9@8MqNJTGT)oWHh%hPjG5cS!xm3R414dm|;#Z zXkt`Cb~Pg_19KB2KZ8LNBNtN>BO}8hKe6KnVDsuhnl#n^Y|1)TpKZ}>xY7qeGVC-XWx_fP!l6CaBxwEp<@vdi>q{JfdI z&$G?nFA1=7dGjl(HI02vop@I0XYnpmxpyzu94_^X2(1my=E|Ne;&lD#8ph7euNMD1 ze?Xh3<QhU6v9MfjmfBnMJ}ttU;tj;!LZ_n>{>Jrs#1*&N;qGz>kZ=pqh=CPw50 z#tlwjj0~-l_L=UkJioZnN#@Dp=KDUSjJ8CA+ns;ubSL zKD_zoX~(E%K@)dBF>CkNUAcrk!*th_qO*Lj_-eyHRsJzQTAeU&)9VAe6Uuo#1=N>X ztUV&Mc+27w9-BqE1=)|5b$BKfRxzjg$byw)dZQ)<+lW^*x`{@c@A@Pkt U-KQ*d_HNZbWXv>e^{pxO0B){}_W%F@ 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 deleted file mode 100644 index 1dfb0e70faadbce1e2af09df6a528e061e25c471..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1174 zcmXqLVwq&n#9Xz2nTe5!NtmsEikFu9+0VZYtBDY>v5!Rrz^_k_O@+0cK$Vhyc_=M+HNJCPpRX zfM8^0U~XdMXE11D(NK7*;_BnV_3jM;CAE1su6LAunVIoDLNVcINeiR? z8YbPg%L|;V{5CDQV77~KU+-;?6YSzvO11#9@ENZum__6Ei z%AX$T2Gib6sff$(TpW17pe=2oYuc`m*NYEN{ONJ$|HT&uy0y9`1*X?KRKCwU5}CKV zxLy2{k(}pSDo7OX}$`duJ`xCn7GXUuck7cw&+?HV3B|Ow{DX3(c^L<56d5< zpWvU_4AX93-pTe^Gni=^b)}lTvAk8T%wnppKD+NSI^X@167|~RDwgfKGc-t)FK00 zkkjN@%nVEnj20Ly&_mLmLr8m34mfAXDzI=Fa2T+$v4qMhFd!!@Zg8?kLaPQ>kv3t8&)n;pcz>~A4S<5GzOfynk>EW_^&Rg3ptBP}< z)QdHA=A74HmS}dbTr5y$5WU8FL&AgdD;&!n%GSgk*!^=>wbI<>44idq+{Byj%ux%U zX<(T&t^e((|;@d`W>zv{oMZX);$) F0s!GfpzHtu 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 deleted file mode 100644 index 75df0cc7c0be230aca9aec4f9d5e958ea3b268ac..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1145 zcmXqLVktFfVvb$F%*4pV#L2Ln&&$W8X?wi^FB_*;n@8JsUPeZ4RtAF%Lv903Hs(+k zHesgFU_(^{We|r;SSGk6u_!GwH77+OBsDoBFFz+gJu|gfN5L~MSs&kIz_;M4`c_2FuP+>W>tP(qM@*XAV`E=m@7Crzoeu%B{3(JSQlFx zSc3F23mYL^9GqHImYJMdtPqr+UxMWBRD`P)bQRo<44N2~ki(6Um4Ug5k)Oe!iIIz` ziII`vDv#9>t)G66zV4Hp9^cmyyiwl%^!fP;!nvAOC-`e^zGs(NB;PbkSTQ>;hM{4T zeq{Z(_MfWGZO=2MgJw;5SorvcF0Wtyw-cT>w|$lhTc^FPMCA4P-)fJgcF9iD%d(D` z(&--dUvU@2YFpSv}ul_|x^< zp@j3okxbeRXH4Ah@4Po5HJ9nU&ctQQp5JSg@T^JmTWpoPa_c)2yLDI@B5(Z)oBKzaFUp#kLzU@nN z=$bp%1rG78?09bA&&Hw6#>mRb&dA8(Y~W}h1LGSowh3gEloVL$>*wng7aQr77V8@* z!eyD-M4_@3Fxd*d87l^~v6~ojO=_MECfD@mr0t=S`hXETK zOQ@^@19GC^1}6$e24&gDUE!x{Uy4`ye3T3QdnP!~&Yl1MvCvPOSj^rw$Oj*ZHPOi4 zcRch@`m59S^%FK3pOuafXkz;DL;tIA!WGtY9~(Ii-d$cg-Cts7?!E7mCvxnnbI^Hv z@YU|JdB$wzvb-Gi!FQ!oRyV1C%)IIwx9-cr!==k3Ty#I#6wFw&D|6e_@_K&5IGsNW z+O6C(KUBtl5eZo`HBPWXC_(+){6ppcm4nM;cm01A`TX|z^^>N(SD9rhP&H@bF}~WF z-|Qvte=Q1mD7un8vbuSHke*x5CWe$U8IQ|1DqrMDJmI>e#mpzCI3=V1vrpTw bqTa;we%Q+I8{*#@zwL3~ZonM-g}Da+m?WBn 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 deleted file mode 100644 index 9f8267ca9259269393b3af3301adc69efbeee840..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1741 zcmXqLVmoQj#JX_-GZP~dlK}VA=WBzio=e;6+wHxozrojlmyJ`a&7PgVlsK;uh;L?OY-(w08D)@z)j|$oo`BT6 zyyD87vc$a1L_X$@#ekO^iy&K4xTPU~XdMXE11Dx9 zt-*M5vl46J58-6+~e-YnS^s(0mqlU>$zrX_YO z7e>lnaX9_|^XoMm-bj0yRd4FqqAazQ`Oxm=b0@c)-_W)uq+aE>*Ws&5mY*9F7z~cA z|Ms~q_5R+K6_w9tJlVG7_Ua2Q8s`+&1sh%AJa1J{zu`%`!9vBG1L+fv86ZiFfuV2fN~1d`Am#r(ohGMmzSfO?i>(oP!IDRBO^<(L7{;PjBmi$CIQu$ zpIlskqSL?=u8OJ67OE;a5#;e=6oq($z)&x_s05tFWQ9R7z+?c*618kx+H8z0jMple zSeRIv7;{;g7&95u8Oj(mEBAevXKAVUA*^_Q^3w9B`@(DIByj!s=^LvSqZ)g>g@J*w zwnJgc8U_XyW(IcyS0;t*XC*7D*1r6>hIR3IhWnEZ5*4_TgS_&!8t(dkmEYL)hDnj( zb4y~2MY@34Ba1k`iq!919&hh3nf3h=lzA0&>cB(;28J>Q#}lVE-dA$-5;(Tdiqok* z&d;!0n)&r>@8cOCCL~Q``v$f57TjJ%h9&pccpC1Hdp>ieW7my2@mr$R*I9KJTl-yz zoqwU8m9LaZK`B2V_@+gH-R%n1RYfz-JZ;@s9BXx#e=Co;n$90(L05)ShWkt7R%bN% z%jc|pwPx)w4`w2s5PB-Blfn{fzq`SF6uueS5Enx z+lE|Daf8b#MuxR<$tPcQ{mr^lx$+^~>ysUM^_Ko8Y^9bwcQIP=XWEh@`MitY<$n3b z-mPF?+bXhj%Jqw%NY0H*9$$oFR0vTes9}~g{;Mk7{f0ctjg3}waVh3 zjgf=-P63gX$pSNkyBka{KYA`Mej?(DP{8}5k6b$peKuxyw26H?ky@S1u*+_>VymDp Z%XhUZdFO_w&9c&7T&fi-P0mFN00878Z`%L> 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 deleted file mode 100644 index dd6a50f3273eab884f2245df8b0ad90967850380..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1218 zcmXqLV%cZV#Jp?)GZP~dlK|U|n=Dr&oZY^4=`Rmasykx9%f_kI=F#?@mywa1mBAp< zklTQhjX9KsO_(V(*ih6!7{uWc=5fhP&vZ^LDpByvOE#1=5C;i!3k#H&m+Pfure`Ln z7M18F=jR$K7|4O7n1#jRI^1*elM-_jg7WiA6r3FmSO$@*wN}Sgi#5FcD zHnlXhj56>i$_bhV>JWPr;P!<01SzMHBU7GYT?DWUV_|3c9PpL9j&kAx(6G-q6 z7xw({!aTkC{`1S;={`z<%kJ~d6nGmRSfci*_V}qJJz?p3)Dx8{$`wlsMsZS|b>=6L$G$97*IUlWRR`MAH(sWzg8 zxnW}?_bEm) zJU6>?x%MJ;1>vn1Y#lBu8n91(HS;SIGb01z;wHu>gC@p010go%P+4I{#{VoF25gLs z|CtyW3}iuKd@N!tBHObUy;`xVrKj@MmRtNYHXWT>w)2aDJV;uZMZ!R=L4^5UfN9u8 zpWSI67C(}fK0JMs?Zr?7K9B-_keMvZOpI+1^Hf>H3`E#CwAmP0S=pHx;VdSDG8mhY zktNF@!$1YbH(+d&$S5f(u+rDhPcAM%NqGhia8*ogCQwz$iJ*j8jG|BUtqexxXln|Oi_+8QD)>A*n-SZU@a4}}&bjl4*r;H2@M(5(V@8(7Ea;<-Ot>yHAzF(~c zZ*RqJ=J~E&>hQ0X>(^()itPp7Qk@aI8uot9mVH@wo_klsoX9H^F8|H>dtpZI_QIy< z^SRtnoENI^9dW*)wdCGBN45Cf{WDMBUH5QB{`~fTCEMkt6;^NbKlU{}Hg>kn%m10* z6LYTJ{QTGK*lOmEvP<52rEQP@+z72zJ0w_s-;QZp&F9tCRc&VZJHFqTTzJ0iezC!? z>eX+xB2PHASPP0v1${orCsMm8x+r~D;oETcw4*ziyi!f#x-V$iD9j0*>GM2j-uG-l tZQerPW)-(n6}S=i;Y98&EuRc3p0~Jx*@j# zCmVAp3!5-gXt1HUfhdT>CCulNnV#vKT2!K=;F*`KXJ~I=3zA?KHiJnhcx0w$C^!}u zmlh@FC8sKc_yj9>R3;T=rYN{NJ1YcxI2xInDHs_T87MeA8XCxn^BNf%7#o=yfI*Zv zuaS{~v9W=Xv8ko0Wt2f0szZbg1VQ$32y;0n=47Vj7v*Ip8j2Z+fCSlvd4m)46x=}e zW)>&s8;TkTgT%RndE7HgJW7+GZibq{EXSpYVZ{@iQ|1XJKJxVsC($s>%WiBo1vhMpjmKW=1v`i^-sIBS^hGOXEs| z#$^V93;Y-Ows~iiloVL$>n9iG80)2Ere`Ln7L|a4Ngo!=ph$+tafnYaB!*p`ogwiH zie6_&L%rmp984okC^pg#Y(*Mjl2cCId|ab(pu97{#QaUH}CSiVvIv zf(;rc!BjFbvNX0DG&UKiz(fof+a#cR^OK7UP~2(|1y{w?<_A@koCpe?Vibj>hf|3G z56Dx(pm<|4U@&N6WM$*hW@BVwyjID?!oP_r9&ce_V64d6zgLQZfrXjD-N2PekzwAm4ww2rCa!ageCMy8 z(#E##ZnEy9ukp##-MYA!THd(Mq~H;ve({*m*8Pp;*I$cftod}}-ATr^@@GtV-klUE za+wjyP|l#_>dGD&suDKMexd%y@40Ocl+}{g9`LS@J=PqP_lTbfYOf{OUJnB|CPjuB z3_@O{%?Y`HVQy;PN^1W%B#Ij}ztkeIL$)w0|_vnwcdp~4sIK8@I$A>E` zg3=A1FaBWtbK7pt3(L0sJ0*);Dlxk=7`QMgXg^y2CV#({>k`u$=C(&zm!I2}zH!&4 zo{a4FwVv}QxrH$)h%AcL-4c0zQ;%)wwc^ivS()k%|M+1v^UnHh602EU?B)UhfvKOl 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 deleted file mode 100644 index 78a66bb47b43ec0f159ad7b272bac79c6ca9a5fc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1055 zcmXqLVv#mzVrE{z%*4pVB*2!=9KBoQZ}R#B*H#4k{S!0bW#iOp^Jx3d%gD&h%3zRV z$Zf#M#vIDRCd?EXY$$3V4B~JJ^SETDXF8`Al_+@TB^yc_h=YW=g$2sX%k@$+(=(G( zi%Rs8^K%Wg4KzVg%)&}=9UhtK848ZY#id1wdC92?u3-v6`S~RZ&W;9h;=D!%hDL_w z24D~+&T9%7!nWr*!mF>(fBEgM=+piQuVw0{1W!D%PR{O1(aQ-QwNKAEA5oU&TKZZn z`}oRD-qy~Xk`KSy`?>#J+{D;!(8SnmAjHNTDl5#$_@9NtfQ^yyKNBN^fh2?c6dM#8sKEFJjBOGb zB_#z``uh3F#RVuaYTyZ1#nfgCRh66witu6-h5E3t2L(Mmx?RISk?riLmt0h0;0@BM&1J6P(3tz|O`B5@Uo5 zF`}h)<|al)hGGHv6Nw$-XY*xP4vXFP;e6NgXG;4or3RUFWQKGw*JI8_pwb}G59bd}VvZ$0~N>RH=4 z7aqBZd@eMVRX50jnas$@5@!%&pbq03Ft$l&l#~=$>Fehw7Z>OiWM_gyLqENw*f81H zz!0vGsZABC5U!>uwIE+VIkl*y7^++^CAG*v666>`79j(HCiK8n1W75dNEt{NaImqZ zH}WttfqcrP&Bn;Wc&(C&mD#`sq(q*@)WF!laDn~;-8OBgt;t0>#puq{4=i-6NXoSI zHL*z6OD@VWXkuhxgPX>}#M1bcrSTKP2ZmCHz{MNB{$;G36PCNfyrQ3HN!1x?_0!+y zbvVu9{dc9C^(g}bV{PWIKRgT!EX)jU1};nriM!?*zQ~*F(KNZvY3J1Ij1q5OxqZ?4 z7ImyjKNyv4QEO|+`c{O5bLS^MTZ#588j>R zeVAuysrVtRcz*KI@~8X4Yv&|z{rKq{s}`dgd%Oi|Ej!p+4+A$QMTQk?R17D7kK#Z2 z<<74Kf2z3NFzE8OuQO~8yIRX$^!U6KlOn_PIX3%j4%j$VZYWP#$hK8k;mPeUTJ=9v zs;2!>`xU-!5?a1uZenC)D4)1ghF-`gW$rS5dWw+0E6Yq1(Pc3T6FynF0oyEh3k7v z-p^)j!Ood8@?^Vh77OScm*&6ce{`lo+^g!P@0B%M^1iNQ%?OSNUUzxsSEKcI+wLY# zS@+}Wi3L|`3k!;6N~O8?ajmTMSz~F-G@;^o_UX&Y2V~Wzv|s(;y_C z*_cCF*o2uvgAJ7o6hIs*N0~4btab6>H14}Ru#5Hgu-6CNFL5M|MAd8$G z6%3t?4VoC0ko~~O%D~*j$j@NV#K^_e#K_3-wd4Ntq#il*ALdRI>T}vJ{7!9B{rlo{ z_LBd(FQb$n&HN_wauV}Q;Tle^u3t{q;=)u`RT;O)UwoOlduPd;aG$h{$>rvu!T&4; ze7BqIj#n)=w-%rB%I4|Zt9uS_ael40-2MFq7s2EkTO3>OIvRLPmfpYp+XGi7#Rt=z z#LwTGs0O+;E0tK1}Wz-U}I$b&&0?8 z(Za_f#v;;HQop|S?7OLFZRcEgw_0se6)AiF!ii->l z4B+aR+Ek$G;7W>83-a}oQ;SN9A*%IKQi}{sKu(co(KXOE&|ILtK($R7sx7%F2hDV- zuH>Q|gK&^81(pB#e|$nxk0IviIJhRc8x*|kIlYWE5fHrZ&~+i_0pokEoM{y-ggjW z_BrGxvRg!D^@o5$rQ2(dF40;6Lc+cW zgUvoWajXyd)mI)YonoOb&bc?@q;!p?ZuIAKSKdy)EyBE?Ggw9b5D!b`47E>*AA^Jb zI`v4WY1@k~-2MOC?8l9>7}*LtO*K^RKHU3SQum?9Y1_!=z%3=m=GGkB*cQG1jLDJ9 zXRb5~PGwm5MM#P9*OunO^*!5ridEK_2OnaXU@!PD=B?Qj0qeb*>mD7qO0v#LzS*!p zvT#kZ`c5D51-HHxi|*ID*Z+BGj!~mA^M$s+!kH$!Cili}4UG7{Y4QukZ;|^7XaDup zICnnxxY(WV7q7X^UVbY3WjX)f1>2;Rn`H&0ZgMiTJaD@f;HN+ZO`3)klSa}uy5M@Ny&Sr?~dL1Y9g((znRZolmGMRVYa}}kLr(Z6xbE`yhauVhDH`pu7Nv}#i7B5N(Krb zv$%vM-ShL)b5a#Tib{)16oONW$}*Evixqr)oDD?`gg|PTg}L2Bf)&6P1sNJNF)1N? zijkFpxrvFN!JvtWi>Zl;kzt!4Q@7mXh2^uY@9dvqw|(kz2b+a|n|{_Bu{9Nb`_@zc z`^D`&S$fl_FYfXFsr_x^&vPxZ8-E_1e((LxPm5pf5$cRvzA9+-4`ub!OE%6)zI}aW z-3P9?*BQ&c9E)=2^%XFF?6*;P@d=3>?>B!qC)}T#qN%FPzHrMWTfKw_=Pz-#?>_7C z!S~Ris*bMRyUs^SUR&S4fqla~!;eh|58T&VmYXBVxAjJynv^TE?S{%FQ#97{*Q-5U z!SQd}3!987!S9-_IZO-fEVJZF7{4>_sPCwMre_)Tp0q&T+-`Y2u z=B$wjsBd}Tb9T~Yg_p*Af@ZEa+N!x(RcnjK&!UH?QocWH66k#~_fS{IFN?IUKgmBC ze^&ghjw%0D*8c0w{f&H;Q^U9HReOJp@#1SSuHAjyTaO;?3SE79&8vytnr9@J7^(Vc zeN1(^z;tfaInOPx=GQcDaHxLnyn1=g;{Mvs#$P^5|6lDpe9rs9)*GAqT+f#scYK8i}I8PCVMGW<`Rw!;;xpC@-r8{}W1aBSB@vw1tP{$i}Hq7SYbk?1x zz291T&pBd$DO5O5Wy7_?UtbRHI`dZg>%T_F8w@jStxl|~nqe-%I+cl;k%4h>6QhMe z6Qc<@4a@Q~GX7^_VP;}%GvH@q4wV%K@mUR+85#c@$btm;Sj1RFo~+kaE0ogqP;b=_ zO4E7&p;Nff&Oja{t;`}}Al4v~P@N{_$~*m3XMlb!%UP2Z|Ns2;Hb`LO&}L&~Wo2h% zWC<|{GEjx_4H(-bGfGMdtn~HslZy-V3bHfx()07v_0x-s3=K5l%9z?@p~_HHfR*Vb z7nK+oft(=EqHUmQpuRwLfpVK7RDE($4yvgzJvj!}AUz5!CI&_Z9BgdqjXaD@jBpl{ z0WTZ3HX9=g=1D=T4LLe9IKtpZ?J3`en8K1&?jNr03e!JT>WfZ1ih+ z9$Qxa-tLL-#JpcvuiR|@{IJWCx*IvykGp-HH2e8g#eHd$B&UStT+!UbdoV5fQMspw p#@o5qrHqS$H#eD{PwGCY(!F%MCC~0UEyw*q=l?94w(U@pJpk9OB0>NF 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 deleted file mode 100644 index bc42b61abcbe360423cb87a967b31ce7ce68cfa2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1740 zcmXqLVmo2b#JXVtGZP~dlK|7^6&u3YTxDt+eqVcG_&CmhmyJ`a&7YmWlsK=Ek)eTsg^{tTrKx3 zyu_0HA{~$u^$cYVq(PF*!XkO8<;8lH$#4aF$@#ekO^iy&erIH5U~XdMXE11Dy7l62WO^;hzj_b1?N3`bL7XzpalWPvOP?8 zE5>Ilam;%vQ*xkK_io%vjyuw(KX*CbtYdu?`Q)eD?+sX1ndm8cHlfC zTaci*&hUV%%assL-mUD1El)%#E$a+1U$@*{nvX@j#zSP+uZjzYwCi-HTosP|#-4NR z>-3s7PG|jB3$TP!R_Yk1!~Wy_ti03Oj&zPc53rAwFOn*J_Rq} zoA{&HOS7_IPgqk!^U)qn=GVes+i$Ba^wca)m~yTmYxdJW&TlK(?|kWC+oO8y!}gO_ zP8;4XC~cbe(C+#}CT2zk#>Gv{s$)s|cQ zGd3NaTDJ3xfhE~x; z{LjL|%*46?VxB4sDCRk|*%(<_*_j#HU@Rts#(t1`d6vd@gT@vEhXr;EY}%|cN=gc> z^!1aAa*Xv-GSf4YQ;SMKajg$aq#-`RLBWoY6bee9&W?t<#(K#`IhZDxkZOV*$UzD$ z<_4w)#%yfqjXaD@Oa`EQ0(CSKqnI?*f#v1psHQsy1RK=De8m2W zt&)j_iKU4#kEMw*i!pmRv1&1@vBz5& z7#OP$+9*9_U|?Zpa5r#eQe;@WP-}&BzV(WL%lf4?&Ig==j&5Q3crV-4%kQt{)F`iS zObWUxzeHR`Rq9uLasP12a6-95qEb-wg;%|k8N15EYo#<8$`}F{Z}|F`v2spW?hf;c zex4;&XQb6nf1lUkG>iA&m2TFj5POx4k?dWqu>P!gwIHN;$H6f`fDwE z!ORRQlN=tVo#T&5`tpqFr>&nH+w1O3+w-fs@9aoS&p8{^S$=bC%kMYkhxH0fu6Vi1M)mG>X{P543c6QB-b&5gd4)wxf9}IiTB55mp9ZSQ z-MbgxaC??ov~Slu&m~%3&!0!ie=2p<%YN%{$<%p^$xZc~nyG?m-^#4M7u@sgopb1B zRYGq|+A{akEtme6>|N|!obZa{_B6qH4!S1}T@&8?bg6lxY2NJz?|em1^psyXIB%h+ znPb@Z?J7G~)n7gByIiWKwzbX9f920vlMfvdtXper`r=%$_h$VzC+3#7*VG(kUYV7i URC^Hj=z{xm{@brOo~Q%>0OqlF_5c6? 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 deleted file mode 100644 index dd6a50f3273eab884f2245df8b0ad90967850380..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1218 zcmXqLV%cZV#Jp?)GZP~dlK|U|n=Dr&oZY^4=`Rmasykx9%f_kI=F#?@mywa1mBAp< zklTQhjX9KsO_(V(*ih6!7{uWc=5fhP&vZ^LDpByvOE#1=5C;i!3k#H&m+Pfure`Ln z7M18F=jR$K7|4O7n1#jRI^1*elM-_jg7WiA6r3FmSO$@*wN}Sgi#5FcD zHnlXhj56>i$_bhV>JWPr;P!<01SzMHBU7GYT?DWUV_|3c9PpL9j&kAx(6G-q6 z7xw({!aTkC{`1S;={`z<%kJ~d6nGmRSfci*_V}qJJz?p3)Dx8{$`wlsMsZS|b>=6L$G$97*IUlWRR`MAH(sWzg8 zxnW}?_bEm) zJU6>?x%MJ;1>vn1Y#lBu8n91(HS;SIGb01z;wHu>gC@p010go%P+4I{#{VoF25gLs z|CtyW3}iuKd@N!tBHObUy;`xVrKj@MmRtNYHXWT>w)2aDJV;uZMZ!R=L4^5UfN9u8 zpWSI67C(}fK0JMs?Zr?7K9B-_keMvZOpI+1^Hf>H3`E#CwAmP0S=pHx;VdSDG8mhY zktNF@!$1YbH(+d&$S5f(u+rDhPcAM%NqGhia8*ogCQwz$iJ*j8jG|BUtqexxXln|Oi_+8QD)>A*n-SZU@a4}}&bjl4*r;H2@M(5(V@8(7Ea;<-Ot>yHAzF(~c zZ*RqJ=J~E&>hQ0X>(^()itPp7Qk@aI8uot9mVH@wo_klsoX9H^F8|H>dtpZI_QIy< z^SRtnoENI^9dW*)wdCGBN45Cf{WDMBUH5QB{`~fTCEMkt6;^NbKlU{}Hg>kn%m10* z6LYTJ{QTGK*lOmEvP<52rEQP@+z72zJ0w_s-;QZp&F9tCRc&VZJHFqTTzJ0iezC!? z>eX+xB2PHASPP0v1${orCsMm8x+r~D;oETcw4*ziyi!f#x-V$iD9j0*>GM2j-uG-l tZQeqBc{&=38Hj)c*@bz79sLyCf*k#vJ%gS74MhxuK;m4&+(D@+DVbom z>A~F0%)_IlR|FQBO}B4m$O0^FOhnrsq5NU zw0T9h&pGYgi>}+w%gp&~bTMNMv#eA>=#RE2r#lm#zi+Gi8=Uj%fW=bvEvKrcJ&xmw zdY7uOdD`_SRx@oYifUsMMeKIDiYVP?joy3hWQkz+-KbNkL4gWg!CTK>xW3+1>7=jb zcH4@qt@D}pyLfm!z2nSLpdruna{1Hyd6tYSIZqoFF_sA4D_?g?G?Yv1N2zPr@{p;# z(u-6WOP||LS=7va;5LK%T@F3Vvk}M6@Uyq`$jMb-*fagII_9U7ymXVRMotG>+d+l;@j1)ck(ovGEKfwH2n}e6Eh76}8f29fRAi(ai*)zVY>YRfJD8JmtyE!+9UKo+Eck420{ zr0_w&#pew{zm-K&=Q=O%HCz>zsb-)Ik``ocd|?97a8FcyfJB_#z``ufR5ImUV^ zndzCysYNBA_|}J|%n+a8pkPNxvIM0{XGcR_6TRf39842TNHxI@Y(* zMjl2cCIe9BfI6CqQA`@@!1D5PRMVXUf(`0nzGGx$DK;oHP=WCc7~3SE8uOEj3s7_# zc*0dNwb?>dB`1PBUW}p;Zx9&jB^Q+#@PIrZ42l6J15j3|XXDalV`O2xR>{P|#L~o= z&(g%0&6vqh&Y)Sj@54MxOT`ai#q*PwmOtGWUOOj&>&H*uShX0{*yAk>42(4ayKks7 zFt9K)co?`bDKb2MKOt+g<=0CcpS@N|t|@l6HqNbg@_K0=5u@B%cImb^lOjXg^7K_A z1vy2Bj9-V`>ty12Q(_zEy(RA6_Z1S_cZK(EV<=|`T)g4yU&hKgVYxfZEBbksRGpDl zKmC1Phtn+He^MY4FG^*3?3<&`!b2UW|&6+^d%Wh|erSa;tz-L}^6>8F-h zCPjt~ljo@8cOCCL~Q``v$T2`X;c&ZU!z)3dzSl|69~I$@x^)!jeV1YA!B# z`z`!rlh?ZAKlz0xEz#b^q@d#HFVb}^Eq$+CIiteE!)Kr_lVj>ZT0;_+-`}T-K1J6aIJ4&Ab|k^gRB! zMY~g;d(8;Ey6rF5A5p1K`=5*JOqjl!WmSyFwXk)!Z|m9Q{0o>6q$Bw*s#xrp)HU8u z(t8UFuGN&YCHzp6Y!hBFN$gF|uK)#2!>N~;XRK1-yBswm#bHLD)9Z@IGi6^p$%=Ds zzr1z!*DlFl@-ZT%DvlZA_h%cF?)YWJUl0 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 deleted file mode 100644 index dd6a50f3273eab884f2245df8b0ad90967850380..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1218 zcmXqLV%cZV#Jp?)GZP~dlK|U|n=Dr&oZY^4=`Rmasykx9%f_kI=F#?@mywa1mBAp< zklTQhjX9KsO_(V(*ih6!7{uWc=5fhP&vZ^LDpByvOE#1=5C;i!3k#H&m+Pfure`Ln z7M18F=jR$K7|4O7n1#jRI^1*elM-_jg7WiA6r3FmSO$@*wN}Sgi#5FcD zHnlXhj56>i$_bhV>JWPr;P!<01SzMHBU7GYT?DWUV_|3c9PpL9j&kAx(6G-q6 z7xw({!aTkC{`1S;={`z<%kJ~d6nGmRSfci*_V}qJJz?p3)Dx8{$`wlsMsZS|b>=6L$G$97*IUlWRR`MAH(sWzg8 zxnW}?_bEm) zJU6>?x%MJ;1>vn1Y#lBu8n91(HS;SIGb01z;wHu>gC@p010go%P+4I{#{VoF25gLs z|CtyW3}iuKd@N!tBHObUy;`xVrKj@MmRtNYHXWT>w)2aDJV;uZMZ!R=L4^5UfN9u8 zpWSI67C(}fK0JMs?Zr?7K9B-_keMvZOpI+1^Hf>H3`E#CwAmP0S=pHx;VdSDG8mhY zktNF@!$1YbH(+d&$S5f(u+rDhPcAM%NqGhia8*ogCQwz$iJ*j8jG|BUtqexxXln|Oi_+8QD)>A*n-SZU@a4}}&bjl4*r;H2@M(5(V@8(7Ea;<-Ot>yHAzF(~c zZ*RqJ=J~E&>hQ0X>(^()itPp7Qk@aI8uot9mVH@wo_klsoX9H^F8|H>dtpZI_QIy< z^SRtnoENI^9dW*)wdCGBN45Cf{WDMBUH5QB{`~fTCEMkt6;^NbKlU{}Hg>kn%m10* z6LYTJ{QTGK*lOmEvP<52rEQP@+z72zJ0w_s-;QZp&F9tCRc&VZJHFqTTzJ0iezC!? z>eX+xB2PHASPP0v1${orCsMm8x+r~D;oETcw4*ziyi!f#x-V$iD9j0*>GM2j-uG-l tZQeVpiQi~Lv9Tg0nVCtA* z>I~$>d5sJWEDenez#vMT*T~4wz|hdh*woV0GRi>4P|838WD>KmV0n4DUQuariEd6} zUbQMy4BqY|>i7+D#Zn;7{S44N3Zn3@d@ypgzX1`DTp3K3K{pOmtX4;Wo z*EL>hAL%H+z3KHXgEea+xs%-9JuR8xI%l5A(jB{+ewr+4Y!f{E>amewCd-V+$`oTD9f(;>$Urkdv4yYxb}i3y~Kb$mlr2F zFI~1u`qjio3hW;1OE&LsF75KsILsEeFF16$_VR3x@>Tvz4$QDKN`6+i_rdOe3>U(} z9=B9{T=wj$WcG7Cx#bRS`vhOh|L4qIU+wYc$_#4M(4-2@Nm>C%u7dJ5r88k8T z8OXCShsr9mNEnDUh(s<)V+&ePJ#~`J;wkLv(@LU$+Fme_1u5WT5n~atRkLYRoL&{e zwffT{TQyC+*{ih74O~Fdf-JTM)=ijk(Ik|VT9SwwKTSdfMX6<|Vg`I5Q}`Jf|Ff_# zGqEm!SgOh*W+1}Gq0Pp~%F52n2xl=FSc85PmlnFi?w zx-h;0W1Aw>@ciWB0_>(6n8B4ZA-SalyK1;wN(^{F9uWrlo5=u_LhIPLwAmP07_U__ zu`sbTG3K!}F=jDlFqAQ9R_^;S&(c!yLs;?rGQvMm6?$3j+gV z^$iEBy$lR2%na@Zu1pG$IBSX7#vTW+IU~d z%}e0eLMu+E_BcPoZfWM%uf30Fe3+0ljqMxMUKWJC${{b98|_YtNp7mF4!M)UCK)&1 zH+Rp=efO4{%&Og>)WW35aH~NjqUqtTRD-#KB58lz_l2*h5jR}KBVxLQbx|7sK`G>N zgBx6KFf!~t>n?SeGq35?0);6j4n>IWOAX>w>-->jx%BFOljiaRKc=?*c*11#HfZ(h znQi7Rj~?w}uwS;P8s=Wqs8(caIZLd+)QRuJF6mu84%>s^;8h;TIqNQen23v{5~w!zeB1nD&{#yIX&M zyHm{;SblaE%heuDnFE$@zs@szTI_y*#+teLFJ?)-5Al|aSk7R2euiX;-IV!Z(Z4>< zpPJdwsWgLuL(%2sqc=5Qv)(maHfPDRwSJJaP$yWXV9$%4C;#*Wvc%X5|9NyaYhq<# Vpx13Z8-LE(j(5H5K5lCZ2LOU$IgtPW 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 deleted file mode 100644 index 93f1fb0c6b5686ff607c1297cd0adbdd08b28f54..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1101 zcmXqLV(~O+Vm4gB%*4pVB+OPnCA_u$#hUVan}Us^_sr}L<2B%Awhn8i<1gn1ux(0t!L-`6UX@jtYhb za^k#(riKPaMh0LICC+PPYG`0+3Fa9@8MqNJTGT)oWHh%hPjG5cS!xm3R414dm|;#Z zXkt`Cb~Pg_19KB2KZ8LNBNtN>BO}8hKe6KnVDsuhnl#n^Y|1)TpKZ}>xY7qeGVC-XWx_fP!l6CaBxwEp<@vdi>q{JfdI z&$G?nFA1=7dGjl(HI02vop@I0XYnpmxpyzu94_^X2(1my=E|Ne;&lD#8ph7euNMD1 ze?Xh3<QhU6v9MfjmfBnMJ}ttU;tj;!LZ_n>{>Jrs#1*&N;qGz>kZ=pqh=CPw50 z#tlwjj0~-l_L=UkJioZnN#@Dp=KDUSjJ8CA+ns;ubSL zKD_zoX~(E%K@)dBF>CkNUAcrk!*th_qO*Lj_-eyHRsJzQTAeU&)9VAe6Uuo#1=N>X ztUV&Mc+27w9-BqE1=)|5b$BKfRxzjg$byw)dZQ)<+lW^*x`{@c@A@Pkt U-KQ*d_HNZbWXv>e^{pxO0B){}_W%F@ 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 deleted file mode 100644 index 1dfb0e70faadbce1e2af09df6a528e061e25c471..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1174 zcmXqLVwq&n#9Xz2nTe5!NtmsEikFu9+0VZYtBDY>v5!Rrz^_k_O@+0cK$Vhyc_=M+HNJCPpRX zfM8^0U~XdMXE11D(NK7*;_BnV_3jM;CAE1su6LAunVIoDLNVcINeiR? z8YbPg%L|;V{5CDQV77~KU+-;?6YSzvO11#9@ENZum__6Ei z%AX$T2Gib6sff$(TpW17pe=2oYuc`m*NYEN{ONJ$|HT&uy0y9`1*X?KRKCwU5}CKV zxLy2{k(}pSDo7OX}$`duJ`xCn7GXUuck7cw&+?HV3B|Ow{DX3(c^L<56d5< zpWvU_4AX93-pTe^Gni=^b)}lTvAk8T%wnppKD+NSI^X@167|~RDwgfKGc-t)FK00 zkkjN@%nVEnj20Ly&_mLmLr8m34mfAXDzI=Fa2T+$v4qMhFd!!@Zg8?kLaPQ>kv3t8&)n;pcz>~A4S<5GzOfynk>EW_^&Rg3ptBP}< z)QdHA=A74HmS}dbTr5y$5WU8FL&AgdD;&!n%GSgk*!^=>wbI<>44idq+{Byj%ux%U zX<(T&t^e((|;@d`W>zv{oMZX);$) F0s!GfpzHtu 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 deleted file mode 100644 index 75df0cc7c0be230aca9aec4f9d5e958ea3b268ac..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1145 zcmXqLVktFfVvb$F%*4pV#L2Ln&&$W8X?wi^FB_*;n@8JsUPeZ4RtAF%Lv903Hs(+k zHesgFU_(^{We|r;SSGk6u_!GwH77+OBsDoBFFz+gJu|gfN5L~MSs&kIz_;M4`c_2FuP+>W>tP(qM@*XAV`E=m@7Crzoeu%B{3(JSQlFx zSc3F23mYL^9GqHImYJMdtPqr+UxMWBRD`P)bQRo<44N2~ki(6Um4Ug5k)Oe!iIIz` ziII`vDv#9>t)G66zV4Hp9^cmyyiwl%^!fP;!nvAOC-`e^zGs(NB;PbkSTQ>;hM{4T zeq{Z(_MfWGZO=2MgJw;5SorvcF0Wtyw-cT>w|$lhTc^FPMCA4P-)fJgcF9iD%d(D` z(&--dUvU@2YFpSv}ul_|x^< zp@j3okxbeRXH4Ah@4Po5HJ9nU&ctQQp5JSg@T^JmTWpoPa_c)2yLDI@B5(Z)oBKzaFUp#kLzU@nN z=$bp%1rG78?09bA&&Hw6#>mRb&dA8(Y~W}h1LGSowh3gEloVL$>*wng7aQr77V8@* z!eyD-M4_@3Fxd*d87l^~v6~ojO=_MECfD@mr0t=S`hXETK zOQ@^@19GC^1}6$e24&gDUE!x{Uy4`ye3T3QdnP!~&Yl1MvCvPOSj^rw$Oj*ZHPOi4 zcRch@`m59S^%FK3pOuafXkz;DL;tIA!WGtY9~(Ii-d$cg-Cts7?!E7mCvxnnbI^Hv z@YU|JdB$wzvb-Gi!FQ!oRyV1C%)IIwx9-cr!==k3Ty#I#6wFw&D|6e_@_K&5IGsNW z+O6C(KUBtl5eZo`HBPWXC_(+){6ppcm4nM;cm01A`TX|z^^>N(SD9rhP&H@bF}~WF z-|Qvte=Q1mD7un8vbuSHke*x5CWe$U8IQ|1DqrMDJmI>e#mpzCI3=V1vrpTw bqTa;we%Q+I8{*#@zwL3~ZonM-g}Da+m?WBn 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 deleted file mode 100644 index 0b6271ff545ded2d9e9ee2b7c439789809da1479..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1335 zcmXqLVl_5sV!5$^nTe5!Nr2C6n)c;ilZx-ie{k-d@^z;H7aNCGo5wj@7G@>`FGFqv zPB!LH7B*p~&|pJJ191?COIW};C%-f$Ehn)kRY$=yFImq}!9Wfq#Vjn2kOIjlxH>y4 zI6LYZ8_0?C8W|dx8JQS>L6kVJk&%I!k+G4nsimo9ltC$~UBU)}AOkssxttSoGSl*l z@-h<*#SBD1g6zV)!HIbaZbgZC$(hB;`S=_rXCMR8!YnLWoR_JW40m^NagJVcey%~J zfiN38*uzYWP@gk1vNJm|usmjyk!UDb)Xv4vWc<&rhz)leN2pE(opA= zmzSd%8W3zy22;q$$dYA{VW0xz8!)y>KsDwk7Z;%DG;n~cVrnyis!C1-d9xTrAubP> z81R7HEer}{CIbe8CdPU;E^RhO7RGCpOe{<+O^o?0O^n%$nGEF&Zc9U7m;RclV6b&j zXXcmBwSqd1CHww*?r2^8=}*DGTz3Wr#wt6HCnp#fSeO|+4BVI$8TwtnzqhL^j!{_0 zt+<+kZ!k?h^S>BVyU$6;!|`T03)7c(DlO^Xil*Z-Hi?0BR1ztq>VObWpfsf9Cx zo>XhRnEOC|hOxr!Do;&$249Db9~an&YD`K)F2I;Q8Nk7KC8~RhR6~G6)szz@2|rSL z9VTDx^1J_XVxY}dlZ=*YpO_RGdd2$F*L=Hir9s%PUCHc#%&Ejmp$A6oe>3hexzD~X G{Tcuv^sqPp 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 deleted file mode 100644 index 41c742136ce864a9d7a90253af5b9f1642672d5f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 977 zcmXqLVm@oo#I$t*GZP~dlK_`_drDmVmL4U)ztV3-cx()K**LY@JlekVGBR?rG8jY| zavN~6F^96S2{U=R8VVT*fH+*joKA^3C7HSTMX83u27(|lZecFx%B0kykfPG!5R>Q7qtT9<}!=wn+VnFPTSF8F}pRs zfB$aZ_la&tP8MHL@qh46=3LI^z(W13cRs9~<(G?+mdw7sxQQ{tpouZXK$eX;RF;oL zj74PWIrCSqHCN$=R=`s=2OK>IX#19_0NGK++PScAyZxse7Pk?}th zBZGknOrDXEMZ-YNKn2D(U~H4fC@Cqh($~*VE-uhZ$xP2oPAw|YOU}UFYSt@W=_!@tnXGog&+P#=}7G9>=5Eo=OKM@d(CE zn>6L>AB8(%IZr1|-d`ni!2EHmr|2*3hb5L0{M&DRUKAdY;D4rX3d0|_Y7;{{U-za) ztJ7aPWBK0u^5)HvZc@2xq#7$&5h!50z*LztFuuCj?MxwK{@0AUs?wEzGB 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 deleted file mode 100644 index 68068a77c5ba02d9b45f91bbc9593d87b8e9c7b0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1785 zcmXqLV*6^)#CmrDGZP~dlMu_6t(TYnU$W=*ix=EinFKN-jvMf@acZ@Bw0-AgWaMUL zFfcOYHsEAq4rO5zW(o~96f+P3akzweeNs!*ixph+l8Y(}N??kZg_(kk4dldmjSLNq z3``A83{6dqqQrTPj0}vST*D{>IYSu(DUdPDJi=Of#U+W!*{K!D8Hsu6sd~xzxdu&) zO32P&WMyD(V&rEqXkz4IYGPz$m{p#-$?xRGcrK&!KPGtE*~UF-C{kSgA>AnG*1b)a z_S}lollyA&_raQDu2OUDHZIHCGX3%_#)8Evg{t4*YH2(b`25iG+&-}{zZWrVG!94kV8z4z@_Tj(J^>)Pf1eNr14+m~!rep{_LC%w{GX3?IK zjh)lOP2Bd|&AU`QKdx`qXAcK4Zo9oFwg)B}SMPq!#LURRxVVW$+@Og?*np3XIaHRP zk?}tZ3o{ez0s~nPUzG(EQykiCjI6Be%#3U>7Lx%FNLm=AoXLOzqL`0Gj78+w1#_3n zFSloZP_T%aVjIlzKW52I19^~QWflnou?7(lz0Km=Bf?Iv3*dBkCcl1ex5%Mm2B9#M z85vnT4crYBVSEF|Hqnfdk^(Dz{UT$%e7&61yyPOi{GxONCAbo%HZg>fOoS4BgC@o& zAe#kQniy{zG%;Rp;@8qkEY2=XD$Oe?1*MTDAuYY6oc#3svecrqocwZ_SZ-oT23#yJ zwWLWxOD{LIBoQ-}p~}GZB+P%ccbAXpYTFE$B*h2X9SIjaex3@!!=GGSC@5KltX73XCZ z6r`3Eg9DBi#n%QtpjcL5aW!x@;9z4*Z{%TQVl*&kl-0z@%EkrOdaaU)g^8u{8%yJ7hK~&84ELADt zCE4ZDVTF>7=?j)9FWHng)3;N&w_ur4f7%r$MTQ@5eg~g7+kb0M%%zhzLR=o%x&7LB zNi8ehIOcZq3CoIu3}p=EQH?#`0=3u$ zY_Yq6E0ZF_rKXDOC(AQTA3tz)*lzxFapslMj+q^GFZDi{w%)txv6M+cF^)m`>HA_| zU7d^XwYUW1mRoO2o!oV${QH%Y!ZYqq?m;d*xxs}eBST%=&)I?tkM?*9rKvofa(!QH zcKrDzk0QDkEL!fYzwXwudA6x0H@|mryW|w*)*ikcg^ykYtBJ+e3F~qJ?}bUy}Ix2X)_=H>%zG!?(={7)fW3@PVpv{=;x|1|Jm4yUGTulA!QhV*l-VcGdTO{lal~qn~?l{JN|JgQX0FH#8)&Kwi 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 deleted file mode 100644 index 2d66ea723ea4f983874b3ffacdf612bd90e150d7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1306 zcmXqLVihxJV*0m$nTe5!NswWpHiy@F9*O0_->&JYE{~5g;AP{~YV&CO&dbQi&B|cl zZ^&)H$;KSY!Y0fV8f>U(pbp}22`hN!m82HsrIsiJrzV#cWtLPb1f>?ICKe@UD7Y8p zmlha`8VG}wG7Ix~1_!w-1m)+KC`1?<$cghB85md^m>7UTlsK=Esez@Tsi7H&A7x;K zYN42c2*?yJVP2oq67^yQ*SzGS%7PM@B4%NxAY+3jMkQo-GqN%;H!<=v7&I|*F*PwV zGVEp&)j0EQ!IaDVLT`FQCmsIL^l|30DyNR+v1+TfYVSPivvGZ2_RrE2i?8#$PZ0?< z4eyz^Vv(if*CrdMm410&tq<$n?me&}=5wUQ^S9?SKSXg$?>jI>MYa8xdEsW^=Q3*^ z&VGOUiM4Bx>$H^zgU^|q2zs}5-P`_q2?dXqhOFL}%Qt^wPA=A0@so zig_0N->PGq`_liy60c@(mfOz_{&}A9>DOfi;v(ylm;9f(dGg#l!OPkHd9=zd%v`5h zx?A{65cAi!feX8Di!w1YGB7S~V&pJrVq`JkV`C1L zn~jl`m7ST14aQ@_%pzeR)*w>3CGuJIwx>@RiyJsPPDn+3dpSGLzzAkOBO{BNfr^0=jBmiy zCYDiBQedU8Uty@1sh5+Qmt3TmUzDy7PI$8NED8p41~Lnz7Dyl~Oh#6y1hQR$McP2p zfP;-Ky^)8JiP3M)cnVXmx8Cp;Ix!W!N-s;l2=W2SzrWwg= zOMT_0*&3%7h3Gaecy;*g4~6~lXSNqGY|pd)7B}VI6NUN9-gj?ee!gg{n9amW8&X%-(q}=2Oav1N$lu`1j?y@Wf5pt@piK;Nc5d7tPy|3U8BlD*g~s zn=(0kfov+vK`y0r0=&-C7fQ(2J$TS&zBOv&UW5JZdD>e475$`H4}X=I{vmG7;iWsW zKIrYSHs1Np``(#9Pu90^x7i;EbvFB!@z6{>t8eDT|4SW~n`}RDA=%Q@vNZ40uCf!8 znO?5+&JjPy!*p|R$I5B||t$-I`!kiAsP`9|pUq9dAo-;c!loml7A zVsQOaYrMq5%H7Z73cA@JwoN{~v;R(Fp{myU`)^ePf-<@%-FbR#>*HIs7us`L6b;uk zef_<2^@&b#+lM|+E%?6e)!sX;QRMa2+qMeJ>mn~d`VsLndWXl^e=+`In*ZcNmDisT z+|c`~X7U7a{l9A#ak{(Ne|WiJ`+p7J45Mr5adMf9C-3+=w_Bh4Qjqhqe53GGU!%tR z7QwBtb+KuhuXfyhGIi_Otzkk=XOH+DQ?+mj$bEB;AyneuOV5-mey66-*%E!Ac*W`+ G?uP)U>;Po| 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 deleted file mode 100644 index 79a33ba5908c84a1d790715853f435daee569995..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1380 zcmXqLVofk;V)0nO%*4pVB;deUu4sPcrPGdgWvgsbxV9VcvT2q`KpE>UpK*E1A15Cy4X7Upva4p9io z&o5DMc2tNkHjoqNH8M0XG%_%>G&C_VjuPiJGBL0;HZZgRa}E3rxeYi$TG@n|LW2!8 z4b(v#E@1`Fypq(Sywnnf;MC;OqRf&?g`m{p)Wo9X3+4Ho+HDucN-PwNMVnR=z z&9m?Y72it_E&BI0EAJbFzsBu*8ErW(lecY5{wpGsy}o5_^h!(1>94aX*uPd&iS6@~1J4@PJ@W3~KC^BL^Zd*1(`#mWCi4hGrXsvWZNzM3l_cA%ms?lrJoFJe)X@rdn@sGRo)2Sd?&eIV6#_`edprDz8`XY`wKOnv^yd>XG=>Ac^3 zMH^*j?_vJw$$F#lF6$PpYmP71FqTc_;=ghK>RusvQ+*BboJ%tfuHZ~5&vQ55b#6^$ zT*ln`qHCQ;*&H?PBJJ*~eN{OrxIAjHkAuA1!kJr_TLtE|S0tK&}Dtg`*`Sxapt`(Imp zPPunNo0m)D|M%OEM4hfET~LpGSou%lc|pfozh^hQt&ZJQ56t_&C*`ZLvp*9vBLm~& zCPp`dCPpU%em3S%Sz$)T|17Kq%#4ix4fsF;{2&1qW+p~918+7CZ8k<$R(3{47JCC* z18W%HfT>MCqokz3N?$**prBYUGbJ^z1e722lJj%*i}LeJiuF^9OF)d|#0q1*0`p`8 zd641CED{D{4I)RDZ(LHXQ~2xlL+=Tt|Br|!v~~&@gn$$%u=pBy8*s3(r8n|0GBFz1 zvvF&)F|sf=-4|wLWHc}^(1W>wiBU`m>W1WkVm**ux)p|cIjJSZsd>pol?5ew`9LIP^5fX?OnYFI)T%xpZ)po_WnaTO=v3;@%teU6wIl7cI;Sn^GVUym9xF^%oW$ z)@Hmi?aNUGQ{$gE63reI_HNm+bybt=)B~SZDr-Eyw(xX1$E^j4}8k2{&q~i@HmEVis#m?;-;7M z_WQNImw3=R={1{mj;1na!W~y8A0Y|rm%sV$-EsL5F=O4Uy{@hcJ7UvL{S$bZ-*iII riTC%*H;>yG?#&f=Z1HPJ?Zz7~JeykBW^2F5_Gjd=DA)UW_qini=mHuR 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 deleted file mode 100644 index 36f4e06d476e3cb7570a58da95ac979b4915f503..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1606 zcmXqLVskQRV%1u}%*4pVB)~5hC>6iLG1bWZ;gXLpuap_^vT-8TMhD5rn?_kfg zQ=7NiTOx7g_18_6$%kyXCfGW@di;CFo#fw#w(Jr6cjZIij9|5gj%R1=`+bvpO_|gJ zx9>kqyZ2SRJa(mNwNT2MIVr{q78q9FZkw^OH?iR^AD^DKSk0C{$|gN0HyKZCzoDe4 zueEE3?E7Pmq1K$<4(igU+B~P9j-PvEah|bm^>gM4amtA~ziNM7nUrXk^;t~jWZkW- zXDb8$yUn-qT6+3Z=hx@#6MbSA?2tHhTH=;h`UCOr4|?^jGz#>N6ttf&IG9`VgBzFp1Sb8kp`-Jv_-jFN-+njMN>PrUu(m3hPrwkMjm|NCh9SD%TQ zk%4h>6Vrc#CZ^v8@@&kZvdSzH24W2&+p`zFTCu97r}EX7Tl_ON9i3XX^NWEjNC6*< z7>h_WtHhuE+rN9>`njKfYlg=^)+tv7zzIZFkVU~jt_e97n)u7h%k_|>g%4y7KO^IR z78YhE)&&rARaroB&Y{i5$jZvj%*X~~F&Q*=gVf8jG&UPFHW=6~uvuW$W|2`+QedU8 zpInqc^H|P3_!U9>R={DF=?pdK>kK?n{z<0K`qQ@jEpRW2KfdmFunm}n*>y2esXaE zimMFV;i{P0tf8us6G5IXMo~z>`y~cEAmf!RWhdymuq zv!>gBa=&Eb6cJjxRKTMuY?@TrI@3A^Hij|=C0AGW$WWEAY4!{CKYq_`d!VeAy!L>1 zeeAL3n7l{)Oi+8*BH6paI-pd0m+ zzJB_NDNkp9FE`m_-tnh@^QGS`!d|iu^n}lF>IzR?))q0{Imth(!sky&c4pa*hu2H> zCpJ$zvy6lP#>}Z~4HJ*?ly6TtEMDIjF0(jFYxAu%y}9j=rKbM!D!-H2d9rrGY4I`+ zj!iSKS|6GGr%T^a`)bX%Sf_nV0gV;zt1n7?tvIB2^_6Dko^OI?N=XlY{rtxj&C)A* z!s&b7Q?7&70&Ld=^(H*tyDso&l=GuzlUawiT3vd#+kN-V!x9f}&u58Du6^|3^-?wf DgYZJ0 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 deleted file mode 100644 index 608f16c792172d34c2fe19cb77905975a6582521..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1262 zcmXqLVtHlI#C&N1GZP~dlK__~$JH23ZN}OkYf}&Ke}7`Y%f_kI=F#?@mywa1mBAp< zklTQhjX9KsO_(V(*ih6!7{uWc=5fhP&vZ^LDpByvOE#1=5C;i!3k#H&m+Pfure`Ln z7M18F=jR$K7|4O7n1#jRI^1*elM-_jg7WiA6r3Fmi$_bhV>JWPr;P!<01SzMHBU7GYT?DWUV_|3c9PpL9j&kAx(6G-q6 z7xw({!aTkC{`1S;={`z<%kJ~d6nGmRSfci*_V}qJJz?p3)Dx8{$`wlsMsZS|b>=6L$G$97*IUlWRR`MAH(sWzg8 zxnW}?_bEm) zJU6>?x%MJ;1>vn1Y#lBu8n91(HS;SIGb01z;wHv*22G5s3}o4uLuL6`#8^bOXD@oS zVpU5|<*O~X_-AZ7I<;))7Xx{av@(l?fmnkG^SuDmu!}ys(>^SIBrSb-`X<|pp$2>) z1^kSR|5;d=nHbw3=BcuX8HliPXtOc0va&NX!dXlPLLlYBAmtneY>bTmnHU)i%3$)0 zj4W9O83rmaz5!#KL`F$Tft9{~esXaEO3E{EfU9C^Gl8l~P6Q>)Vibk?uml51m!M?n z?5LMqRANvKa*I4mi9wNp`2y1g#%+c$V~TQ&i83PxQ==|OG$TX#>g`qPw(E9(tA7v`cCuyFspvxSyGrhV{N}&A zaBvCB!^N+UdLI0H@#OuU^;2AR+_V-aTPpmV=cO{m@x;oTkLHB1vYn2R*~cKwzar|t zkDn5Og3 z?a$Sn%JJL(*2jq!st@PnB}%kso!hvZ!60k)_Vu?u9-aQLt3qYftH}+!j8m)jEG&On lpDDu4U|&9Iqi=L^JD2c7#Kn>Z1XeVW#iOp^Jx3d%gD&h%3#pA z%aGfElZ`o)g-w_#G}utgKm^3$66STyD=8{1F40l&%uCiY)G$y3iE|6f2dAbgl$V$5 zr9$NO@={Cmb5he2b975mi*kz%Ee*^eDh)J}H5H5u3{4bJj8f25NXsu$NGvVM$S=yQ zN=;EHElyR)&&#Pa)HBclX=N5xh3ZgnPAw|QOv_A8EXmBzQ*eanDXCP@Rq!$NHINhM zH8LN!~hX^{CG!Xhr2#U(|V$t4Q; zX$sEyIibnHCC#&tJ}IWcGDs$jJ>AArw&in<3txL&b;@h0Z725qIFSD=v**=1^$YC3L|?JL3B8_>#azKUJ%kQl2)d|bQ{Vx}JuHuwlyhY&;-_#@ngCNIlXd)IpJ zPxR$_rl0eq=*7%}CDjI=;Z@f*?l+iK7c=4Tf5SVwc+RC=Q`yYK%*epFxQQjrpot~P zpox)zjX6|Skfrg5LF3mZ9D&*-gc;CHGR37y#mPmPNvV2RWW{qb%dkr%78Dd?(;=FL zRah*!7`u38ZaOw~f#jc`dAW05duV*JX+1y14DDw$ZASeh80vNSP1WW3K%$`IOk z<&f9}Ge)mh+_S@Nf@MRtb2FCo{2;rj)3@pqHZU!z)3Qi$L zmO8zRGZT2Dce+FvH$5@)bKrV3FM4ZttZb*aZv&Hp$IMS!n@%h`Jtc9%tv?={x8C^q zpV8&$n@19tYUVZt?SH~h#?W56|9NE1j~?fl@q0h#{SWJP4>SAkwKd`3mRJAd7Buri zt$hTx*4@CBNs(cLTPjaM|H~yjcb|LjI;!M$`19RgQ!jECmCxMzD1FOBCMJdCFZ0Uw zS~$LZwC8R0*E`39G;^DZy-Z9U1-d@m+c8CMDnl7VmkoSugfO8<8Ea; z_skTGIq-hUjL>VH7w$sr6=6oQcjfAR6~1Me>p3}Oe79`gz}6%XSv%J(OUv~7+(Ng6 zbNNgP=AN0C|0ZAGceye@PjJUnQSSd6rUgGzy>zB0=jZZi+3y(27?$khoUlbALZp4S z@S-#61za|!Ggy8~-{0}L{4C!I-a7&idxf_k*}MDMzd4K@$*VI$wQo!bwOjeebM(q)_a+w93;+YKi8n>E5%w=WjTE=gsxmCOv+JzOt1atS>zc_&`aY zpONuD3kx$7>jH4eBCE;*sscE)*%(<_*_j#HU@Rts3>ce{ktN0;%0L;$H(+cN&nPJ= zu+rDhPcAM%t{DuB;fk2r)S-$JGZT^3=;s*b>LzC-X66~0n&~B{78w|W9461AW1wZA zu|REsN}Cc?XL3;vs?OB1)EvWHz2u@C122#!1r}!mM*}l9u7oxZ#`|GrOYB>Al4vq`1^&eS_W&^^G;MOunf7o zr?cR=6(p$mSj1RFZf32DUllI)c3qviW^B>EOVVy9r-Ca~Sz#6?0|w->m>XOcGcs)H zG&zE#kDFAefejJq+EAAb34iPTA}qN z6|ar2FSE+1det7aHBy*Twga{Ig1=@7s5qUs+IfW~0SVf%u$krA9Zq KWJBpCyix$=YaWpR 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 deleted file mode 100644 index 99ced211ba8a4465076a867f21a3b1792ff893d3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1329 zcmXqLV%0ThVi8@y%*4pV#FM!2`6UJh28O`PY0C_F**LY@JlekVGBR?rG8i=OGvqel zWMd9xVH0Kw4K@@r5CL(xgn3=_N{UL0OLP=G^OE%pH4M~1;@raW!KtYV<>lpisStU+ zywno?oYeHh9Nm)CqTFIbO9OL=N&}5#O$8$Z14{)IqZD)%((;QG5=%=m@{2O7Qd1O4 zi&GWy^KvQ;jSLJxTA774p*j?T^7Bg+oKuTRGSf1X6H7Al^AsE*8cQk_bQRo<4CKUl z4NVM<3{4G9jZ6)Uqr`cQ4GaxHTvHI&pm7(iJ!oh`v@9{xQ|#`JsA+n&9ig_oS27cP9PsHI%6qSSU%jvHHIzC`*H&ALrC2am>c%H_HE zy-}PQA?ooZ)I)2#$rjefHJ&{T5%;X7ow2-=XdHS@OV9q|%%m5qo|%*xu3BHIIj!P! zqsaQqTfbR$MmD>w;`HBgMTc?j>?wxXx(m-Ae9FYk$iTR`iBa32iBa8vkBvE0mYx+H8!htnAE8Y%mt1fe=Vq7^IxTfQ^yyKNBN^fiX;;k+ zK-EAQ#y4PW6VE6qDX`Ml&rdEcKu-POL@O)LqG_ORpt?YLfnu9HRAq8e4zfc1bfe@% zz2u@C18b1g3M?iDpcKy%DyzU?0Loxcw=pq_NkL6O&uB#ji4bq`v52vV9R7Y`tCqpq z^}G`m3oJwK?&&NzZe<`3GFzEN!a%G+B&$fR>Vn+++UM4pfmd>R=T8tyTaBEcxWNgE zkzq^w@fIa1%>zp+-hB0!-qJEXzx>^m^O5UUI8W1;jkve$vuL#5P3Jj2PCLcL1=nub z=y*JhN2!f<`4*+$M>8_II*e`_ z>kXCV^;(B+P4-UNQBV`2dATX%>iMNp4ja$&HA#P9QI^KgdqMx$YR*+fzs~8KcK$i= zmW_G){T|iukH6n*blhyxIKaX`r7=_a!94D>+4%);t$;Sir-1&_+4qRbQp zS7&F1U=K%Q3ljw+10w?kXGcQ=IdNViLqh{YO9L>766ZBCGBh+aH!?Q0G_{N}s6@3_ z*gz0u0f#V`b7D?rT7FSpW}=~(fe1*DU6?mGF;Bs*C^0WNvp6~5P}x8cB+ex)6`q-$ znVXuDnW*5FUz(SaSdy8arvq}ao}q++7)TK_55JaPd1iKI0Z5r%eo?wXq=7ISJ2(KC z7@;A-%*f8{#K7_*K7XDd1xfV-e|8?fcmJvp(ixyT}Z$xGyqBvVkrJO-x5Y z3Iti2nD!VnG3{tV_e2vf$WLIegZ$LQ3uc1EAY2eDDL+5E7|i7XaZ?gY5)r(-)N+U% z*r3AF{E}3#Bp*m=aeir0GK9|$;+Lfs6=#-IA{3P6S0<)I6o52%t*o9y=A&Ch>R3S%(FiaV;Ak;LZID(soBnDB19CI*L$YPQZJHbJR7M~(odRSr- zoSfjXXut0Xrul(RXs*%(<_*_j#HU@Rts#@QhC@+^&$4H_pH zxGiv5;MC@jQBqQ1rLUh{lw+)yl9`^FoLW?(mz%asezUNBnO&?NmI- z#8Ac%xOl_Yzl@c0!g6<*SM>8NsX8OAe){{o4yReX|E_eiK84!*4Q{U@!<)Aob(h)g ziCxF8x1_O>9| z`%n0n9k;&BnnJdVK`G6fcdAHUTl`dM0l#nnOU{GixqVCuERXgu{fjv_$+Ym*Iq72? zx>O&BR@NK-?bUoTg}>~TydH9W%$&(!kj7-laCX%TF$wOtdn$We%04fCDz}tbGN|rf zxDNNfN9(tw+hsLzn4M!xFzAk6>=&Ree~*dDkfHClf=1oe^Y`T^%;ap|P+pM#V2b_R h$l6mmikW(um(K9NToZg`@}W7`nx{Q2wk)ch0|0cx$}0c> 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 deleted file mode 100644 index 7d8413a3474b4cfb6a84d46dc35f75628d29ccc6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1051 zcmXqLVi7lJV*0;;nTe5!Nr3&+=a}{tg;~PeAxNve*NObO^i(jO^kI0LTt>Tvcimv|5-Q;*cciAGcht4$b!W9 zSj1RFxb|>piuWwF%&p=(XM9|5^V{Ets|@5p(#k9n24W2&%=ZFJ!!G*lPW!O6>gXh8pmJ6!3%0WMO7vY=fAm$|7bU!p5P^#>mRb&ddmBF&UJ>*o=%USq2#fDlonQ zW1B=qNlAf~zJ7jkaREw{8aTjJF}0aMRV62aV!Rkdp*}1oAQ29VZ)Zon^MGZa|N3``8T**Fv0JQ&;l<}fiDu(PqIH}Wtt z8gQ_&ff!5(786{I87-YNH!(6YxJNeDUy+vIQ21>|ti-Mv?lU&jm1)2FFV($9ZL?Fa z;WMunk{90J5_x`IWYWS(pO)+CuKz9j?RW5oF8e<>#JFB=(A&0@TVjH#+M-+ER%|!= zGgaC9nCs3X=bzNPdv@~2guO+NXC9X2X06GZp1Wx^li7_!dvq8l*|RL}E)_h!Q*)2t zg|=Oj_Ne_#Jz;)U?OU5L56h~~rxhCy`c>{JTg)B!;;i#Q# zbm5STeATiV%-Wn?Wv9yTv{$XE4pIwUUc^85QiuP)jfzL57Ot}V8g~0o%Mnxg kf?K=YKD6ymVK`#44DiW43-QQ4CV}G3>^&Z3~dap2aFEr9nd)5@Eu@bn8RShpwBRk!SR6L0rmqt2bdUG8CVVo z9pGRPV-RK#VBkH#dw`jNi$Rn@h=HGhhk=vfKO+M}1_J{FKLalV16O)}etJ%-UUGge z12+Q;GXo0!Yk#vZz?1#y dT&ew!ytZ4oZkqAD;nLN{bmb|$-cOjkO91*BHT(bo 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 deleted file mode 100644 index 8e64b2c81f3c56d779dfb7a6ee4c3ee9d0cec000..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 138 zcmXqLY-eI*Fc4;A*J|@PXUoLM#sOw9GqSVf8e}suGO{QfYk0dcyT{<5m!`?{Z6~z) zt~eG&nlqjH!f_*&?~`I;>OvQ0CkB>dZzccpt2<0{hQ_62ydKbMg0^?b3acXm|p9-Xb0f2+DJrlw~b6rHZj aj}2`<&nah^frY$irV7K4f089%fq1x^-G;^hsR zR=#i+W4;ORp})oVtF_GTHApX-QJlX>vH9on`)^(4ZdlHlwJ?P_iGk%_sHK;U?TW0{AwxsM=(#9(tX^#a?1umHXWYU$yeJ4~`G`f7NUU=2>#j4)f sQNeq3wp#wJ>b97go^4Qcx-vgDwEaA{^i^?3y=7eu42zuS?3H>10A(dl8~^|S 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 deleted file mode 100644 index a066b9c..0000000 --- a/third_party/rustls-fork-shadow-tls/src/testdata/prf-result.1.bin +++ /dev/null @@ -1 +0,0 @@ -ãò)º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 deleted file mode 100644 index 799eeca..0000000 --- a/third_party/rustls-fork-shadow-tls/src/testdata/prf-result.2.bin +++ /dev/null @@ -1 +0,0 @@ -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 deleted file mode 100644 index d93402d2c9c4339d38407543b92be1cf32beb9a6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1190 zcmXqLVp+t*$iURZ$jETu=k*JXepjNBHT4(V5PkVxMzieG@BRK)WFD!vt!n2GeEH&S zMueqU_tJm^RV8LGy2@)d%T=sySU8#O^)5xz?cU)R-}B^sbLz3!8+BUn`HA04XMfYu z*`8oBv8t}{q~p>p7j>K079Ms#B5?d51HX=q#KY<*r)3Ya7vJd(jCFO`;2>q$pIyyf{F-#CqkDdAP{obC95N54 zcYV%MeWkhcrGAfo-1D{Ww~tkq?Cnu94_0Qhcd(vgxiH7-Z!6DIev5pw!as`M3%lCS zYCc+deMaN&ly!4o*)NPz5WUH(%=hCn6Ehn4 zOpDHyWS;R@9B|@EU$*!~RoB%E^;k5T+C-OG3frjkOZt6YZ|BqV4|lxe zyE0Mj{T!K9TLYud-1+qM<$03_>0&0+OprYA>*Bw@mfAoANt()1P2_ z7dy9`5hoV=Y%%^f>G|q{K)1pme$y?!w-@}n?^d>Q6~p}Pb+P^r-R4y+liBv)E^apW z)ENpvX-5v}cTW1fS%W#`&DOGuqOPXK1*hBG17{uj$yz0pA>kKqqG!p}*vRm9sy*A% z?UOg$d5~Y+yomV;>#e6f47{~N=S^wJ$c(tv zuPRjB@blLKRleElZB}USy?=@0&3k`N>-i`0ci(?K=fX=-*%;4?TP! z;Jo;}=N8W|iOOazhK14`bJ{*lOMWBG$PaSE`S(e4GI|~VdmSnNGha4-PTk%2{|vfc zD~fx#U0he9^xr`4|L^|>lIx8havRufN!eWfSlBOVMoWb0Mh};CuHrwU4jwN39G9zG z4JS7&^VxYq)iLpJR&hx5J^4lFGN1ihuO6~(Lf@yLjob8Hdd{Cr;m#{v!1pD7lfV_Z zppVH(jCYwD8ziQ^xy9mk?Q}}QFAwgNZ3zishbD$T;jvzyQ+MgbhdDYj$E2^$=HzDb zSvk4-0QZR$g_!?u>!Xj)`oH4Q-kM!9)dwfLwLFayo9y0h7T9q`@>lEGzbb62|13Uv zQPK6xo_a?Cuh)%9f;Rgm8P1pAbim=@O(u=9!rsi?2j^e@_qO-rXQsvm5vR9qUlQfR zjs8FVX}bT*Z;6|Wi}{m3z0T>5$+lDLdsTG#Q{~E4B4!I*o13TpnE5~Jsqv)Lqf!@K zXGjhe0ra$t6pIeKIyUiz|F*;OpOh3-m(oU6Y`fFoG&;1-d`;yjmdx9vp2}^ zSZNWtDSy+X=c&d|D*d;~J=^pAypCAylXd+Qm?GZaoyDMNE+*kBwMtb)@>vh#I}dqI z+43!wY&VmG4u`B3S>1TavdrdIO4gjbRoof#4v4g~8%ZwrzxUvL(8+S6Lyw9(nW_@+ KD#=Kt{{{f1Q$@xA 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 deleted file mode 100644 index 8e5c2e817ddfe06b75a1be432f600f96c25834cf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1216 zcmXqLV%fvQ$Y8+B#;Mij(e|B}k&%&=fu)IMnL!iFA|^%#rY1&4h66vZUug8Z5|yl} zzu<=G%l9&xWuJcU_rD_ZNWE=UJBQ%Q7jH8nEXBH)1{|m=F?-QfUb9)QVs*p9$!xE8 zDVlEg4!`)GC-0k6kHy}o(}K@W{9Zcyo0iV@1e1wXb%iG#mu|VJ+q|~$u=^2#;|Ce| zb!;RaRzEo{dyu{OPH$kWtHTBdDa$UW&~~+XVT!&{GBI}Q)oe@~a}ILOxOp#|W!(m; zV?mQXX3YESYWCvSq*EQ;^J9Z5ZtUfdc`&`}bC&8W&7Cjxd-UU;uXVqDth!`xkCJ(? zGNZkN^&HEEIaYsLd6x29r>8H^n>8GjvT{#iz~F zL+6EE)b;$k>aSIMfnB?0s@L6=-^rW)1lzmVx!sI7vDjye@xMvWR~H1j6$bH}Zt=an z;MaY(vX!eC=5Mcy^?&F#uVR_Zw*Pi(EcuDxnMszjzZpOQyy~hPPAg*_Ljfyy4D+{NmgwPnwg_>-gX6Nco@nvhj23?!NzL(EVCb+{5kSx)P=T26F#@|2L3aZ~Tzkz-~** z=JLnFen~T0B1|`WxTJFx{}FZYaOvl`T-|CoxnY^l&J(JRiGQ<-L!$4=FFKd`?B9Cz zkZlwCJ_T*urti{o{$vVwUg-k9FY%iMuE+&_Oip6F%hcE)G40JQ7Po7sQxbl8aHnib zNC-PLG4u(K_4=H;OD{gm(UCbOeRVb`HzWk;G4hL^C zX_OWAX6`;X|MI`Ly&pd_H8zMiy>hy5Krn>frndhYgw~XB8?RdzLce_SdVOad#%S z$zFOeVZ{U2>eSMsr`>;iHSvA*#9VXXO5@DpO+8Nz+4xgZBUtzzvSS2x#{=*YB6a{{^Op#L4Lz}|B@&4{C21Ro*30J9Asv?rldKlk%$aBh;Z>eOvnH+RDWVOiZ#!HrEHn&o;=H#v7 k&X{*Vq@CSJa=HJ#2j_!MmKz;z>% diff --git a/third_party/rustls-fork-shadow-tls/src/ticketer.rs b/third_party/rustls-fork-shadow-tls/src/ticketer.rs deleted file mode 100644 index 9660d71..0000000 --- a/third_party/rustls-fork-shadow-tls/src/ticketer.rs +++ /dev/null @@ -1,338 +0,0 @@ -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 deleted file mode 100644 index 2910c20..0000000 --- a/third_party/rustls-fork-shadow-tls/src/tls12/cipher.rs +++ /dev/null @@ -1,236 +0,0 @@ -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 deleted file mode 100644 index 4024aee..0000000 --- a/third_party/rustls-fork-shadow-tls/src/tls12/mod.rs +++ /dev/null @@ -1,537 +0,0 @@ -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 deleted file mode 100644 index 24ff8ba..0000000 --- a/third_party/rustls-fork-shadow-tls/src/tls12/prf.rs +++ /dev/null @@ -1,67 +0,0 @@ -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 deleted file mode 100644 index 098c7b7..0000000 --- a/third_party/rustls-fork-shadow-tls/src/tls13/key_schedule.rs +++ /dev/null @@ -1,800 +0,0 @@ -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 deleted file mode 100644 index 3f5b27d..0000000 --- a/third_party/rustls-fork-shadow-tls/src/tls13/mod.rs +++ /dev/null @@ -1,224 +0,0 @@ -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 deleted file mode 100644 index 6126edd..0000000 --- a/third_party/rustls-fork-shadow-tls/src/vecbuf.rs +++ /dev/null @@ -1,200 +0,0 @@ -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 deleted file mode 100644 index 58fd7dc..0000000 --- a/third_party/rustls-fork-shadow-tls/src/verify.rs +++ /dev/null @@ -1,809 +0,0 @@ -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 deleted file mode 100644 index 9ddb271..0000000 --- a/third_party/rustls-fork-shadow-tls/src/verifybench.rs +++ /dev/null @@ -1,245 +0,0 @@ -// 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 deleted file mode 100644 index 738453f..0000000 --- a/third_party/rustls-fork-shadow-tls/src/versions.rs +++ /dev/null @@ -1,99 +0,0 @@ -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 deleted file mode 100644 index 17239d7..0000000 --- a/third_party/rustls-fork-shadow-tls/src/x509.rs +++ /dev/null @@ -1,93 +0,0 @@ -// 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 deleted file mode 100644 index 5f47cc9..0000000 --- a/third_party/rustls-fork-shadow-tls/tests/api.rs +++ /dev/null @@ -1,4382 +0,0 @@ -//! 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 deleted file mode 100644 index e96073d..0000000 --- a/third_party/rustls-fork-shadow-tls/tests/bogo.rs +++ /dev/null @@ -1,18 +0,0 @@ -// 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 deleted file mode 100644 index 5af9c2a..0000000 --- a/third_party/rustls-fork-shadow-tls/tests/client_cert_verifier.rs +++ /dev/null @@ -1,324 +0,0 @@ -//! 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 deleted file mode 100644 index d176ed8..0000000 --- a/third_party/rustls-fork-shadow-tls/tests/common/mod.rs +++ /dev/null @@ -1,474 +0,0 @@ -#![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 deleted file mode 100644 index bc15f53..0000000 --- a/third_party/rustls-fork-shadow-tls/tests/key_log_file_env.rs +++ /dev/null @@ -1,104 +0,0 @@ -//! 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 deleted file mode 100644 index 65d635c..0000000 --- a/third_party/rustls-fork-shadow-tls/tests/server_cert_verifier.rs +++ /dev/null @@ -1,272 +0,0 @@ -//! 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 deleted file mode 100644 index fabac45..0000000 --- a/third_party/tokio-rustls-fork-shadow-tls/Cargo.lock +++ /dev/null @@ -1,523 +0,0 @@ -# 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 deleted file mode 100644 index b8fccbc..0000000 --- a/third_party/tokio-rustls-fork-shadow-tls/Cargo.toml +++ /dev/null @@ -1,57 +0,0 @@ -# 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 deleted file mode 100644 index 3d930e2..0000000 --- a/third_party/tokio-rustls-fork-shadow-tls/Cargo.toml.orig +++ /dev/null @@ -1,30 +0,0 @@ -[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 deleted file mode 100644 index 20f67fd..0000000 --- a/third_party/tokio-rustls-fork-shadow-tls/README.md +++ /dev/null @@ -1 +0,0 @@ -# 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 deleted file mode 100644 index 50e8711..0000000 --- a/third_party/tokio-rustls-fork-shadow-tls/examples/connect.rs +++ /dev/null @@ -1,42 +0,0 @@ -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 deleted file mode 100644 index 1513090..0000000 --- a/third_party/tokio-rustls-fork-shadow-tls/src/client.rs +++ /dev/null @@ -1,86 +0,0 @@ -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 deleted file mode 100644 index b34fad0..0000000 --- a/third_party/tokio-rustls-fork-shadow-tls/src/error.rs +++ /dev/null @@ -1,20 +0,0 @@ -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 deleted file mode 100644 index 2998b52..0000000 --- a/third_party/tokio-rustls-fork-shadow-tls/src/lib.rs +++ /dev/null @@ -1,21 +0,0 @@ -#![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 deleted file mode 100644 index 2290d65..0000000 --- a/third_party/tokio-rustls-fork-shadow-tls/src/safe_io.rs +++ /dev/null @@ -1,227 +0,0 @@ -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 deleted file mode 100644 index b1db30f..0000000 --- a/third_party/tokio-rustls-fork-shadow-tls/src/server.rs +++ /dev/null @@ -1,49 +0,0 @@ -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 deleted file mode 100644 index 188b59e..0000000 --- a/third_party/tokio-rustls-fork-shadow-tls/src/split.rs +++ /dev/null @@ -1,139 +0,0 @@ -//! 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 deleted file mode 100644 index 5dd0f49..0000000 --- a/third_party/tokio-rustls-fork-shadow-tls/src/stream.rs +++ /dev/null @@ -1,297 +0,0 @@ -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 deleted file mode 100644 index df34aa4..0000000 --- a/third_party/tokio-rustls-fork-shadow-tls/src/unsafe_io.rs +++ /dev/null @@ -1,124 +0,0 @@ -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(()) - } -}

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