[client/idd] network: allow unencrypted 1-RTT payloads

Expose the insecure MsQuic draft API and add an opt-in setting on both
peers. Query the negotiated state after connection establishment so
one-sided opt-in remains encrypted and is logged accurately.
This commit is contained in:
Geoffrey McRae
2026-11-06 19:28:18 +11:00
parent ea24c288ad
commit 8bbf9c3056
9 changed files with 166 additions and 44 deletions

View File

@@ -37,6 +37,10 @@ endif()
add_library(transport_Network STATIC ${TRANSPORT_NETWORK_SOURCES})
target_compile_definitions(transport_Network PRIVATE
QUIC_API_ENABLE_INSECURE_FEATURES=1
)
target_include_directories(transport_Network PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/../../src
)

View File

@@ -213,6 +213,7 @@ struct NetworkConnection
uint16_t port;
char * password;
char * endpoint;
bool allowUnencrypted;
NetworkKnownHosts * knownHosts;
NetworkTrustPromptFn trustPrompt;
@@ -2922,8 +2923,28 @@ static QUIC_STATUS QUIC_API networkConnectionCallback(HQUIC handle,
return QUIC_STATUS_SUCCESS;
}
HQUIC stream = NULL;
QUIC_STATUS status = this->api->StreamOpen(handle,
BOOLEAN encryptionDisabled = FALSE;
uint32_t encryptionDisabledLength = sizeof(encryptionDisabled);
QUIC_STATUS status = this->api->GetParam(handle,
QUIC_PARAM_CONN_DISABLE_1RTT_ENCRYPTION,
&encryptionDisabledLength, &encryptionDisabled);
if (QUIC_FAILED(status))
{
DEBUG_ERROR("Failed to query 1-RTT encryption for %s: 0x%08x",
this->endpoint, (unsigned)status);
networkConnectionFail(this, LG_TRANSPORT_ERROR);
this->api->ConnectionShutdown(handle,
QUIC_CONNECTION_SHUTDOWN_FLAG_SILENT, 0);
return QUIC_STATUS_SUCCESS;
}
DEBUG_INFO("Network connection to %s established in %s mode "
"(%s 1-RTT payloads)", this->endpoint,
encryptionDisabled ? "insecure" : "secure",
encryptionDisabled ? "unencrypted" : "encrypted");
HQUIC stream = NULL;
status = this->api->StreamOpen(handle,
QUIC_STREAM_OPEN_FLAG_NONE, networkStreamCallback, this, &stream);
if (QUIC_FAILED(status))
{
@@ -3585,15 +3606,16 @@ bool networkConnectionCreate(NetworkConnection ** result,
goto fail;
}
this->port = params->port;
this->knownHosts = params->knownHosts;
this->trustPrompt = params->trustPrompt;
this->trustCancel = params->trustCancel;
this->packet = params->packet;
this->state = params->state;
this->sendReady = params->sendReady;
this->datagramLost = params->datagramLost;
this->opaque = params->opaque;
this->port = params->port;
this->allowUnencrypted = params->allowUnencrypted;
this->knownHosts = params->knownHosts;
this->trustPrompt = params->trustPrompt;
this->trustCancel = params->trustCancel;
this->packet = params->packet;
this->state = params->state;
this->sendReady = params->sendReady;
this->datagramLost = params->datagramLost;
this->opaque = params->opaque;
this->connectEvent = lgCreateEvent(false, 0);
this->closeEvent = lgCreateEvent(false, 0);
@@ -3863,6 +3885,26 @@ LG_TransportStatus networkConnectionConnect(NetworkConnection * this,
return LG_TRANSPORT_ERROR;
}
if (this->allowUnencrypted)
{
const BOOLEAN disableEncryption = TRUE;
status = this->api->SetParam(this->connection,
QUIC_PARAM_CONN_DISABLE_1RTT_ENCRYPTION,
sizeof(disableEncryption), &disableEncryption);
if (QUIC_FAILED(status))
{
DEBUG_ERROR("Failed to allow unencrypted 1-RTT payloads for %s: "
"0x%08x", this->endpoint, (unsigned)status);
this->api->ConnectionClose(this->connection);
this->connection = NULL;
LG_LOCK(this->lock);
this->connecting = false;
this->connectStatus = LG_TRANSPORT_ERROR;
LG_UNLOCK(this->lock);
return LG_TRANSPORT_ERROR;
}
}
status = this->api->ConnectionStart(this->connection, this->configuration,
QUIC_ADDRESS_FAMILY_UNSPEC, this->address, this->port);
if (QUIC_FAILED(status))

View File

@@ -59,6 +59,7 @@ typedef struct NetworkConnectionParams
const char * address;
uint16_t port;
const char * password;
bool allowUnencrypted;
NetworkKnownHosts * knownHosts;
NetworkTrustPromptFn trustPrompt;
NetworkTrustCancelFn trustCancel;

View File

@@ -246,6 +246,13 @@ static void networkSetup(void)
.sensitive = true,
.value.x_string = NULL,
},
{
.module = "network",
.name = "allowUnencrypted",
.description = "Allow unencrypted 1-RTT network payloads",
.type = OPTION_TYPE_BOOL,
.value.x_bool = false,
},
{
.module = "network",
.name = "forgetCertificate",
@@ -555,20 +562,21 @@ static bool networkCreate(LG_Transport ** result)
const NetworkConnectionParams params =
{
.address = address,
.port = port,
.password = option_get_string("network", "password"),
.knownHosts = &this->knownHosts,
.trustPrompt = networkTrustPrompt,
.trustCancel = networkCancelTrustPrompt,
.packet = networkPacket,
.state = networkConnectionState,
.sendReady = networkSendReady,
.datagramLost = networkDatagramLost,
.capabilities = NETWORK_CAPABILITIES,
.capabilityCount = sizeof(NETWORK_CAPABILITIES) /
.address = address,
.port = port,
.password = option_get_string("network", "password"),
.allowUnencrypted = option_get_bool("network", "allowUnencrypted"),
.knownHosts = &this->knownHosts,
.trustPrompt = networkTrustPrompt,
.trustCancel = networkCancelTrustPrompt,
.packet = networkPacket,
.state = networkConnectionState,
.sendReady = networkSendReady,
.datagramLost = networkDatagramLost,
.capabilities = NETWORK_CAPABILITIES,
.capabilityCount = sizeof(NETWORK_CAPABILITIES) /
sizeof(NETWORK_CAPABILITIES[0]),
.opaque = this,
.opaque = this,
};
if (!networkConnectionCreate(&this->connection, &params))
{

View File

@@ -13,7 +13,7 @@
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>$(LGMsQuicSourceDir)src\inc;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>QUIC_BUILD_STATIC;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>QUIC_BUILD_STATIC;QUIC_API_ENABLE_INSECURE_FEATURES=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<AdditionalDependencies>$(LGMsQuicStaticLibrary);$(LGMsQuicCoreLibrary);$(LGMsQuicPlatformLibrary);ws2_32.lib;schannel.lib;secur32.lib;OneCore.lib;wbemuuid.lib;winmm.lib;ntdll.lib;bcrypt.lib;ncrypt.lib;crypt32.lib;iphlpapi.lib;advapi32.lib;%(AdditionalDependencies)</AdditionalDependencies>
@@ -34,7 +34,7 @@
<!-- NormalizeDirectory adds a trailing slash. The dot prevents that slash
from escaping the closing quote in the command line. -->
<Exec
Command="cmake -S &quot;$(LGMsQuicSourceDir).&quot; -B &quot;$(LGMsQuicBuildDir).&quot; -G &quot;Visual Studio 17 2022&quot; -A &quot;$(LGMsQuicArchitecture)&quot; -T v143 -DCMAKE_SYSTEM_VERSION=&quot;$(WindowsTargetPlatformVersion)&quot; -DQUIC_BUILD_SHARED=OFF -DQUIC_BUILD_TOOLS=OFF -DQUIC_BUILD_TEST=OFF -DQUIC_BUILD_PERF=OFF -DQUIC_TLS_LIB=schannel -DQUIC_ENABLE_LOGGING=OFF -DQUIC_STATIC_LINK_CRT=OFF -DQUIC_STATIC_LINK_PARTIAL_CRT=OFF -DQUIC_SOURCE_LINK=OFF -DQUIC_PDBALTPATH=OFF -DQUIC_EMBED_GIT_HASH=OFF -DCMAKE_PROJECT_INCLUDE=&quot;$(MSBuildThisFileDirectory)MsQuic.cmake&quot; -DLG_MSQUIC_DISABLE_CODE_ANALYSIS=ON" />
Command="cmake -S &quot;$(LGMsQuicSourceDir).&quot; -B &quot;$(LGMsQuicBuildDir).&quot; -G &quot;Visual Studio 17 2022&quot; -A &quot;$(LGMsQuicArchitecture)&quot; -T v143 -DCMAKE_SYSTEM_VERSION=&quot;$(WindowsTargetPlatformVersion)&quot; -DQUIC_BUILD_SHARED=OFF -DQUIC_BUILD_TOOLS=OFF -DQUIC_BUILD_TEST=OFF -DQUIC_BUILD_PERF=OFF -DQUIC_TLS_LIB=schannel -DQUIC_ENABLE_LOGGING=OFF -DQUIC_STATIC_LINK_CRT=OFF -DQUIC_STATIC_LINK_PARTIAL_CRT=OFF -DQUIC_SOURCE_LINK=OFF -DQUIC_PDBALTPATH=OFF -DQUIC_EMBED_GIT_HASH=OFF -DCMAKE_PROJECT_INCLUDE=&quot;$(MSBuildThisFileDirectory)MsQuic.cmake&quot; -DLG_MSQUIC_DISABLE_CODE_ANALYSIS=ON -DLG_MSQUIC_ENABLE_INSECURE_FEATURES=ON" />
</Target>
<Target
Name="BuildLGMsQuic"

View File

@@ -1,5 +1,9 @@
include(ExternalProject)
if(LG_MSQUIC_ENABLE_INSECURE_FEATURES)
add_compile_definitions(QUIC_API_ENABLE_INSECURE_FEATURES=1)
endif()
# MsQuic's static archive flattener must be able to see this imported target
# from outside the directory where FindOpenSSL is invoked.
if(LG_MSQUIC_OPENSSL_CRYPTO_LIBRARY AND NOT TARGET OpenSSL::Crypto)
@@ -125,6 +129,7 @@ function(lg_add_static_msquic)
"-DQUIC_SOURCE_LINK=OFF"
"-DQUIC_PDBALTPATH=OFF"
"-DQUIC_EMBED_GIT_HASH=OFF"
"-DLG_MSQUIC_ENABLE_INSECURE_FEATURES=ON"
)
if(NOT EXISTS "${LG_MSQUIC_SOURCE_DIR}/CMakeLists.txt")
@@ -495,7 +500,7 @@ function(lg_add_static_msquic)
IMPORTED_LOCATION_RELEASE "${LG_MSQUIC_RELEASE_LIBRARY}"
IMPORTED_LOCATION_RELWITHDEBINFO "${LG_MSQUIC_RELEASE_LIBRARY}"
IMPORTED_LOCATION_MINSIZEREL "${LG_MSQUIC_RELEASE_LIBRARY}"
INTERFACE_COMPILE_DEFINITIONS QUIC_BUILD_STATIC
INTERFACE_COMPILE_DEFINITIONS "QUIC_BUILD_STATIC;QUIC_API_ENABLE_INSECURE_FEATURES=1"
INTERFACE_INCLUDE_DIRECTORIES "${LG_MSQUIC_SOURCE_DIR}/src/inc"
INTERFACE_LINK_LIBRARIES "${LG_MSQUIC_LINK_LIBRARIES}"
)

View File

@@ -5695,10 +5695,26 @@ retry:
ResetEvent(owner.drainedEvent);
}
owner.api->SetCallbackHandler(event->NEW_CONNECTION.Connection,
reinterpret_cast<void *>(ConnectionCallback), connection);
const QUIC_STATUS status = owner.api->ConnectionSetConfiguration(
event->NEW_CONNECTION.Connection, owner.configuration);
QUIC_STATUS status = QUIC_STATUS_SUCCESS;
if (owner.settings.allowUnencrypted)
{
const BOOLEAN disableEncryption = TRUE;
status = owner.api->SetParam(event->NEW_CONNECTION.Connection,
QUIC_PARAM_CONN_DISABLE_1RTT_ENCRYPTION,
sizeof(disableEncryption), &disableEncryption);
if (QUIC_FAILED(status))
DEBUG_ERROR("Failed to allow unencrypted 1-RTT payloads for "
"network client %u: 0x%x", connection->client,
static_cast<unsigned>(status));
}
if (QUIC_SUCCEEDED(status))
{
owner.api->SetCallbackHandler(event->NEW_CONNECTION.Connection,
reinterpret_cast<void *>(ConnectionCallback), connection);
status = owner.api->ConnectionSetConfiguration(
event->NEW_CONNECTION.Connection, owner.configuration);
}
if (QUIC_FAILED(status))
{
{
@@ -5723,8 +5739,28 @@ retry:
switch (event->Type)
{
case QUIC_CONNECTION_EVENT_CONNECTED:
DEBUG_INFO("Network client %u connected", connection.client);
{
BOOLEAN encryptionDisabled = FALSE;
uint32_t encryptionDisabledLength = sizeof(encryptionDisabled);
const QUIC_STATUS status = owner.api->GetParam(handle,
QUIC_PARAM_CONN_DISABLE_1RTT_ENCRYPTION,
&encryptionDisabledLength, &encryptionDisabled);
if (QUIC_FAILED(status))
{
DEBUG_ERROR("Failed to query 1-RTT encryption for network "
"client %u: 0x%x", connection.client,
static_cast<unsigned>(status));
owner.Shutdown(connection, NETWORK_ERROR_PROTOCOL);
return QUIC_STATUS_SUCCESS;
}
DEBUG_INFO("Network client %u connected in %s mode "
"(%s 1-RTT payloads)",
connection.client,
encryptionDisabled ? "insecure" : "secure",
encryptionDisabled ? "unencrypted" : "encrypted");
return QUIC_STATUS_SUCCESS;
}
case QUIC_CONNECTION_EVENT_PEER_STREAM_STARTED:
{

View File

@@ -29,13 +29,14 @@ namespace
{
enum Field : unsigned
{
FIELD_BIND = 1U << 0,
FIELD_PORT = 1U << 1,
FIELD_MAX_CLIENTS = 1U << 2,
FIELD_AUTH = 1U << 3,
FIELD_PASSWORD = 1U << 4,
FIELD_VIDEO_CHROMA = 1U << 5,
FIELD_VIDEO_BITRATE = 1U << 6,
FIELD_BIND = 1U << 0,
FIELD_PORT = 1U << 1,
FIELD_MAX_CLIENTS = 1U << 2,
FIELD_AUTH = 1U << 3,
FIELD_PASSWORD = 1U << 4,
FIELD_VIDEO_CHROMA = 1U << 5,
FIELD_VIDEO_BITRATE = 1U << 6,
FIELD_ALLOW_UNENCRYPTED = 1U << 7,
};
std::wstring Trim(const std::wstring& value)
@@ -93,6 +94,23 @@ namespace
return true;
}
bool ParseBoolean(const std::wstring& value, bool& result)
{
if (Equal(value, L"true") || Equal(value, L"yes") ||
Equal(value, L"1"))
{
result = true;
return true;
}
if (Equal(value, L"false") || Equal(value, L"no") ||
Equal(value, L"0"))
{
result = false;
return true;
}
return false;
}
bool SetField(const std::wstring& key, const std::wstring& rawValue,
NetworkSettings& settings, unsigned& fields,
std::wstring * error)
@@ -139,6 +157,12 @@ namespace
return Fail(error, L"invalid password");
settings.password = value;
}
else if (Equal(key, L"allowUnencrypted"))
{
field = FIELD_ALLOW_UNENCRYPTED;
if (!ParseBoolean(value, settings.allowUnencrypted))
return Fail(error, L"invalid allowUnencrypted value");
}
else if (Equal(key, L"videoChroma"))
{
field = FIELD_VIDEO_CHROMA;

View File

@@ -40,6 +40,7 @@ struct NetworkSettings
uint16_t maxClients = NETWORK_DEFAULT_MAX_CLIENTS;
LGNetAuthMode auth = LG_NET_AUTH_MODE_DEFAULT;
std::wstring password;
bool allowUnencrypted = false;
uint32_t videoBitrateMbps =
NETWORK_DEFAULT_VIDEO_BITRATE_MBPS;
LGNetVideoChromaSubsampling videoChroma =
@@ -47,10 +48,11 @@ struct NetworkSettings
};
// Settings use a semicolon-separated key=value list. Supported keys are
// bind, port, maxClients, auth, password, videoBitrateMbps, and videoChroma.
// videoBitrateMbps uses decimal Mbit/s and defaults to 200. videoChroma accepts
// 420 or 444 and defaults to 444. Password authentication requires a non-empty
// password. The default address requests a dual-stack wildcard listener from
// the eventual QUIC provider.
// bind, port, maxClients, auth, password, allowUnencrypted, videoBitrateMbps,
// and videoChroma. allowUnencrypted accepts true, false, yes, no, 1, or 0 and
// defaults to false. videoBitrateMbps uses decimal Mbit/s and defaults to 200.
// videoChroma accepts 420 or 444 and defaults to 444. Password authentication
// requires a non-empty password. The default address requests a dual-stack
// wildcard listener from the eventual QUIC provider.
bool ParseNetworkSettings(const std::wstring& source,
NetworkSettings& settings, std::wstring * error = nullptr);