Skip to content

Enabling TLS 1.3 on Windows #732

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions .builder/actions/tls_server_setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"""
Setup local TLS server for tests
"""

import Builder

import os
import sys
import subprocess
import atexit
import time


class TlsServerSetup(Builder.Action):
"""
Set up this machine for running the mock server test

This action should be run in the 'pre_build_steps' or 'build_steps' stage.
"""

def run(self, env):
if not env.project.needs_tests(env):
print("Skipping TLS server setup because tests disabled for project")
return

self.env = env

base_dir = os.path.dirname(os.path.realpath(__file__))
dir = os.path.join(base_dir, "..", "..", "tests", "tls_server")

print("Running openssl TLS server")

python_path = sys.executable
p = subprocess.Popen([python_path, "tls_server.py",
], cwd=dir, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

@atexit.register
def close_tls_server():
print("Terminating openssl TLS server")
p.terminate()
out, err = p.communicate()
print("TLS server stdout:\n{}".format(out))
print("TLS server stderr:\n{}".format(err))
4 changes: 4 additions & 0 deletions builder.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
"linux": {
"_comment": "set up SoftHSM2 for PKCS#11 tests (see: ./builder/actions/pkcs11_test_setup.py)",
"+pre_build_steps": ["pkcs11-test-setup"]
},
"windows": {
"+pre_build_steps": ["tls-server-setup"]

}
},
"build_env": {
Expand Down
3 changes: 2 additions & 1 deletion include/aws/io/private/pki_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,8 @@ AWS_IO_API int aws_import_key_pair_to_cert_context(
HCERTSTORE *cert_store,
PCCERT_CONTEXT *certs,
HCRYPTPROV *crypto_provider,
HCRYPTKEY *private_key_handle);
HCRYPTKEY *private_key_handle,
bool *tls13_disabled);

#endif /* _WIN32 */

Expand Down
22 changes: 22 additions & 0 deletions include/aws/io/private/tls_channel_handler_private.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#ifndef AWS_IO_TLS_CHANNEL_HANDLER_PRIVATE_H
#define AWS_IO_TLS_CHANNEL_HANDLER_PRIVATE_H

/**
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0.
*/
#include <aws/io/io.h>

AWS_EXTERN_C_BEGIN

#ifdef _WIN32
/**
* Force to use schannel creds. Default to false.
* For windows build above WINDOWS_BUILD_1809, we have deprecated CHANNEL_CREDS.
* Set the value to true to force to use CHANNEL_CREDS.
*/
AWS_IO_API void aws_windows_force_schannel_creds(bool use_schannel_creds);
#endif

AWS_EXTERN_C_END
#endif /* AWS_IO_TLS_CHANNEL_HANDLER_PRIVATE_H */
633 changes: 511 additions & 122 deletions source/windows/secure_channel_tls_handler.c

Large diffs are not rendered by default.

152 changes: 114 additions & 38 deletions source/windows/windows_pki_utils.c
Original file line number Diff line number Diff line change
Expand Up @@ -269,83 +269,106 @@ void aws_close_cert_store(HCERTSTORE cert_store) {
CertCloseStore(cert_store, 0);
}

static int s_cert_context_import_rsa_private_key(
enum aws_rsa_private_key_container_type {
AWS_RPKCT_PERSIST_TO_USER_PROFILE,
AWS_RPKCT_PERSIST_TO_GLOBAL,
AWS_RPKCT_EPHEMERAL,
};

static int s_cert_context_import_rsa_private_key_to_key_container(
PCCERT_CONTEXT certs,
const BYTE *key,
DWORD decoded_len,
bool is_client_mode,
wchar_t uuid_wstr[AWS_UUID_STR_LEN],
enum aws_rsa_private_key_container_type key_container_type,
HCRYPTPROV *out_crypto_provider,
HCRYPTKEY *out_private_key_handle) {
HCRYPTKEY *out_private_key_handle,
bool *tls13_disabled) {

/* out-params will adopt these resources if the function is successful.
* if function fails these resources will be cleaned up before returning */
HCRYPTPROV crypto_prov = 0;
HCRYPTKEY h_key = 0;

if (is_client_mode) {
/* use CRYPT_VERIFYCONTEXT so that keys are ephemeral (not stored to disk, registry, etc) */
if (!CryptAcquireContextW(&crypto_prov, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) {
AWS_LOGF_ERROR(
AWS_LS_IO_PKI,
"static: error creating a new rsa crypto context for key with errno %d",
(int)GetLastError());
aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE);
goto on_error;
}
const wchar_t *container_name = NULL;
DWORD acquire_context_flags = 0;

if (!CryptImportKey(crypto_prov, key, decoded_len, 0, 0, &h_key)) {
AWS_LOGF_ERROR(
AWS_LS_IO_PKI, "static: failed to import rsa key into crypto provider, error code %d", GetLastError());
aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE);
goto on_error;
}
switch (key_container_type) {
case AWS_RPKCT_PERSIST_TO_USER_PROFILE:
container_name = uuid_wstr;
acquire_context_flags = CRYPT_NEWKEYSET;
break;
case AWS_RPKCT_PERSIST_TO_GLOBAL:
container_name = uuid_wstr;
acquire_context_flags = CRYPT_NEWKEYSET | CRYPT_MACHINE_KEYSET;
break;
case AWS_RPKCT_EPHEMERAL:
break;
}

if (!CryptAcquireContextW(&crypto_prov, container_name, NULL, PROV_RSA_FULL, acquire_context_flags)) {
AWS_LOGF_WARN(
AWS_LS_IO_PKI,
"static: error creating a new rsa crypto context for key: key container type %d; error code %d",
(int)key_container_type,
(int)GetLastError());
aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE);
goto on_error;
}

if (!CryptImportKey(crypto_prov, key, decoded_len, 0, 0, &h_key)) {
AWS_LOGF_ERROR(
AWS_LS_IO_PKI,
"static: failed to import rsa key into crypto provider: key container type %d; error code %d",
(int)key_container_type,
GetLastError());
aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE);
goto on_error;
}

if (key_container_type == AWS_RPKCT_EPHEMERAL) {
if (!CertSetCertificateContextProperty(certs, CERT_KEY_PROV_HANDLE_PROP_ID, 0, (void *)crypto_prov)) {
AWS_LOGF_ERROR(
AWS_LS_IO_PKI,
"static: error creating a new certificate context for rsa key with errno %d",
"static: error setting a certificate context property for rsa key: key container type %d; error code "
"%d",
(int)key_container_type,
(int)GetLastError());
aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE);
goto on_error;
}
/* Secure Channel doesn't support TLS 1.3 with ephemeral keys. */
AWS_LOGF_INFO(AWS_LS_IO_PKI, "static: TLS 1.3 does not support ephemeral keys, disabling TLS 1.3");
*tls13_disabled = true;
} else {
if (!CryptAcquireContextW(&crypto_prov, uuid_wstr, NULL, PROV_RSA_FULL, CRYPT_NEWKEYSET)) {
AWS_LOGF_ERROR(
AWS_LS_IO_PKI, "static: error creating a new rsa crypto context with errno %d", (int)GetLastError());
aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE);
goto on_error;
}

if (!CryptImportKey(crypto_prov, key, decoded_len, 0, 0, &h_key)) {
AWS_LOGF_ERROR(
AWS_LS_IO_PKI, "static: failed to import rsa key into crypto provider, error code %d", GetLastError());
aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE);
goto on_error;
}

CRYPT_KEY_PROV_INFO key_prov_info;
AWS_ZERO_STRUCT(key_prov_info);
key_prov_info.pwszContainerName = uuid_wstr;
key_prov_info.dwProvType = PROV_RSA_FULL;
if (key_container_type == AWS_RPKCT_PERSIST_TO_GLOBAL) {
key_prov_info.dwFlags = CRYPT_MACHINE_KEYSET;
}
key_prov_info.dwKeySpec = AT_KEYEXCHANGE;

if (!CertSetCertificateContextProperty(certs, CERT_KEY_PROV_INFO_PROP_ID, 0, &key_prov_info)) {
AWS_LOGF_ERROR(
AWS_LS_IO_PKI,
"static: error creating a new certificate context for key with errno %d",
"static: error setting a certificate context property: key container type %d; error code %d",
(int)key_container_type,
(int)GetLastError());
aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE);
goto on_error;
}
}

AWS_LOGF_DEBUG(
AWS_LS_IO_PKI, "static: successfully imported rsa private key, key container type %d", (int)key_container_type);

*out_crypto_provider = crypto_prov;
*out_private_key_handle = h_key;
return AWS_OP_SUCCESS;

on_error:

if (h_key != 0) {
CryptDestroyKey(h_key);
}
Expand All @@ -357,6 +380,51 @@ static int s_cert_context_import_rsa_private_key(
return AWS_OP_ERR;
}

static int s_cert_context_import_rsa_private_key(
PCCERT_CONTEXT certs,
const BYTE *key,
DWORD decoded_len,
bool is_client_mode,
wchar_t uuid_wstr[AWS_UUID_STR_LEN],
HCRYPTPROV *out_crypto_provider,
HCRYPTKEY *out_private_key_handle,
bool *tls13_disabled) {

const enum aws_rsa_private_key_container_type client_available_key_container_types[] = {
AWS_RPKCT_PERSIST_TO_USER_PROFILE,
AWS_RPKCT_PERSIST_TO_GLOBAL,
AWS_RPKCT_EPHEMERAL,
};

/* NOTE We didn't verify server-side with ephemeral keys, so use only persistent key containers. */
const enum aws_rsa_private_key_container_type server_available_key_container_types[] = {
AWS_RPKCT_PERSIST_TO_USER_PROFILE,
AWS_RPKCT_PERSIST_TO_GLOBAL,
};

size_t key_container_types_num = is_client_mode ? AWS_ARRAY_SIZE(client_available_key_container_types)
: AWS_ARRAY_SIZE(server_available_key_container_types);
const enum aws_rsa_private_key_container_type *available_key_container_types =
is_client_mode ? client_available_key_container_types : server_available_key_container_types;

/* Try importing into various Windows key containers until we succeed or exhaust all possible options. */
for (size_t i = 0; i < key_container_types_num; ++i) {
if (s_cert_context_import_rsa_private_key_to_key_container(
certs,
key,
decoded_len,
uuid_wstr,
available_key_container_types[i],
out_crypto_provider,
out_private_key_handle,
tls13_disabled) == AWS_OP_SUCCESS) {
return AWS_OP_SUCCESS;
}
}

return AWS_OP_ERR;
}

#define ECC_256_MAGIC_NUMBER 0x20
#define ECC_384_MAGIC_NUMBER 0x30

Expand Down Expand Up @@ -546,7 +614,8 @@ int aws_import_key_pair_to_cert_context(
HCERTSTORE *store,
PCCERT_CONTEXT *certs,
HCRYPTPROV *crypto_provider,
HCRYPTKEY *private_key_handle) {
HCRYPTKEY *private_key_handle,
bool *tls13_disabled) {

struct aws_array_list certificates, private_keys;
AWS_ZERO_STRUCT(certificates);
Expand Down Expand Up @@ -724,7 +793,14 @@ int aws_import_key_pair_to_cert_context(
switch (cert_type) {
case AWS_CT_X509_RSA:
result = s_cert_context_import_rsa_private_key(
*certs, key, decoded_len, is_client_mode, uuid_wstr, crypto_provider, private_key_handle);
*certs,
key,
decoded_len,
is_client_mode,
uuid_wstr,
crypto_provider,
private_key_handle,
tls13_disabled);
break;

#ifndef AWS_SUPPORT_WIN7
Expand Down
11 changes: 11 additions & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,18 @@ add_net_test_case(cleanup_before_connect_or_timeout_doesnt_explode)
endif()

if(WIN32)
set(WIN_VERSION ${CMAKE_SYSTEM_VERSION})
Copy link
Contributor

@xiazhvera xiazhvera May 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check might no longer works after cmake 3.27, as CMAKE_SYSTEM_VERSION will be disabled after cmake 3.27. https://cmake.org/cmake/help/latest/variable/CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION.html
Probably added a comment that this check might not work after 3.27.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice catch! So, it seems CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION should be used here instead

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Though, thinking more about it:
CMAKE_SYSTEM_VERSION is the version of the OS where tests are running.
CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION is the version of Windows SDK that is used.

TLS 1.3 availability is determined by both the Windows OS version and Windows SDK version.

We can add extra check for CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION . But considering that our CI uses modern Windows SDKs, it might be overkill.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is possible that CMAKE_SYSTEM_VERSION is overwrite by build configuration. CMAKE_SYSTEM_VERSION could be used to set to a target build system version instead of the OS version it actually running on the host. Probably use CMAKE_HOST_SYSTEM_VERSION if we would like to check the actual OS version?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, CMAKE_HOST_SYSTEM_VERSION is better suited

string(REPLACE "." ";" BUILD_VERSION ${CMAKE_SYSTEM_VERSION})
separate_arguments(BUILD_VERSION)
list(GET BUILD_VERSION 2 BUILD_V)
message("Windows Version " ${CMAKE_SYSTEM_VERSION})

if(${BUILD_V} GREATER_EQUAL 20348)
message("Building for version 20348 or higher: supporting TLS1.3")
add_net_test_case(tls_client_channel_negotiation_success_mtls_tls1_3)
endif()
add_test_case(local_socket_pipe_connected_race)
add_test_case(tls_client_channel_negotiation_success_ecc384_deprecated)
endif()

add_test_case(channel_setup)
Expand Down
28 changes: 28 additions & 0 deletions tests/resources/tls13_device.key
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC+g+9xsahL85TU
Gtvib/yIZsTO0qNj2AorItR+nZ0M2r4X4M45NdQsUKMpkcJ6mGMmpNW28RC1+DI+
VnxKAlY+Bztr1C2SMkZej3fRUMvdix1mKFWjm3M9w7YUFdasSKvAEuioL0/23agz
3eGiLhN8GxZ9BhjvuRPb2BD1QNt2QeMM/dR5B05zXxvvkZ8xhXCzu601EG8/2cwL
59wH7wVzKlneCxQkbXIB1RUgQLSVLZ9+v2vxJlBFtHUWenAJDnnKKe+FxCYJ3lgw
JvH0QnnTGiNWxd6QZnqWbQ5RiWikK2cJTfhNw6rV/qZ6rJslUGJhp3hlXqktmwbb
A82hbK+7AgMBAAECggEAKwg8Bm89d2JehIZIkl3/KYQlAh5b34qFyXaFjs/lEGk8
NMKHci6xbQ0Nva75wZ04EBt9VmQfBSs5lhEM496hQkN57YIwhOwsLuGzC6l3UuRA
ULndliXfotzY/LJuGd4URT9AI/UD19v7STPd6jcEGa04qCa/bS5dyPOF6Q+sQop8
2omzQ1HBIA+bQz/XNb/APgS3Cz1DUG5KpLZ/JdMbEVbZZv1uMq/vtopri2mVrF1i
2hrO+Rzchgko0prpM/CJECPUq/tzf9vKJJPQTPjSq6gW0w7iDcwwvZ53e/eevwE8
hgddW9OH9VdzlVUiYUP9Mo/o2LpblRleYy5oiRG0QQKBgQD4kM2qSH8ujTmwcQR3
YBcQkWXP7M6tQnsvAis+hJVuRUE5IK0ptLKnNOUNtVWaXmjjTx4M6wq39LP+gjhP
z1aD2HIxq6S94wzkUBd5UQ25qRE+sm0/blWWjTiUTsZljH9kP+dt4ehNlLH1+IWr
UdWoFy0iXJ/RfJq3Ix2G5RIW4QKBgQDENqhTVMd7OxA2TRbgFy19RzTd42lVbLJQ
aL8XOKLrB3w7vPRnMRtEb9NWTCrzGKRFWhetOk2mRjdJdrUzvEYiCB1zHIvHWT3r
VZtqKpRopjnBz0quTiTE8eyIW2zb/2i2K67funEY/6mcPmrNeUTDV+VgqKha8YIG
EmbGKlsGGwKBgQDYErhV0FOOmgGOGZCyXPtv0ZTZnJdFEceXY6FH5WUoyFccXAVr
fcLXiymaMwnI+UzgXERZInDc3IEjOvvMlQY18o/CEd1Rm+/3AJCHSyhNPmTZRa2m
ODl6eCS494mLeQi8kriRUpHn7tyMX4+DD85VImB/wFjFeDXeAU6HltWUwQKBgFFP
8UuvS4em/13xbnRpRoGvO5pkfqdXB0JJVj9yShmqHXLIKGSHNiyTRvpxUC8z3Aed
gUhZbApm+Mxp9Ee/UzURNdsZTlfLhNx8uo4xnRUNJcxKckFvws8XDpWnhaJZ4vPz
gGHY6ruYX5qPR0VlZvio+TaoTjR59w87GvouXVBXAoGAegBMrk0AnDrkp24qLIou
Wuex7zPT0ch8m3oAO2HTw5+eQsRq3jHIsGe5bWmYmb38JdMX9hDoG6xoA9KtZ5GR
p9tvssL61lcjVHKqPMB0lGbHUYkV1dFHQLyST1rU2dGRLrasOfA5+O+JIvcUfXkt
fDPgrVwKYwFMF3lHfqurlv4=
-----END PRIVATE KEY-----
24 changes: 24 additions & 0 deletions tests/resources/tls13_device.pem.crt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
-----BEGIN CERTIFICATE-----
MIIEBjCCAu6gAwIBAgIUS65CZ7+pv9CHaRQf1/uFEpbc8gUwDQYJKoZIhvcNAQEL
BQAwgZoxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApXYXNoaW5ndG9uMRAwDgYDVQQH
DAdTZWF0dGxlMQ8wDQYDVQQKDAZBbWF6b24xDTALBgNVBAsMBFNES3MxEjAQBgNV
BAMMCWxvY2FsaG9zdDEwMC4GCSqGSIb3DQEJARYhYXdzLXNkay1jb21tb24tcnVu
dGltZUBhbWF6b24uY29tMB4XDTI0MTIwOTE4MTgyMloXDTI2MDQyMzE4MTgyMlow
gZoxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApXYXNoaW5ndG9uMRAwDgYDVQQHDAdT
ZWF0dGxlMQ8wDQYDVQQKDAZBbWF6b24xDTALBgNVBAsMBFNES3MxEjAQBgNVBAMM
CWxvY2FsaG9zdDEwMC4GCSqGSIb3DQEJARYhYXdzLXNkay1jb21tb24tcnVudGlt
ZUBhbWF6b24uY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvoPv
cbGoS/OU1Brb4m/8iGbEztKjY9gKKyLUfp2dDNq+F+DOOTXULFCjKZHCephjJqTV
tvEQtfgyPlZ8SgJWPgc7a9QtkjJGXo930VDL3YsdZihVo5tzPcO2FBXWrEirwBLo
qC9P9t2oM93hoi4TfBsWfQYY77kT29gQ9UDbdkHjDP3UeQdOc18b75GfMYVws7ut
NRBvP9nMC+fcB+8FcypZ3gsUJG1yAdUVIEC0lS2ffr9r8SZQRbR1FnpwCQ55yinv
hcQmCd5YMCbx9EJ50xojVsXekGZ6lm0OUYlopCtnCU34TcOq1f6meqybJVBiYad4
ZV6pLZsG2wPNoWyvuwIDAQABo0IwQDAdBgNVHQ4EFgQU9e1gHOdRMBOQfOm+Iv/8
1O1nERkwHwYDVR0jBBgwFoAUsScN2gc75tU4sIBIyoSZ7jk8isAwDQYJKoZIhvcN
AQELBQADggEBAF37+XsR7k+Lx//10DwJvGU9WlQ+q+jQ0US3XPX7/QPkTY8fDeGE
sGzVZ8WzmZQ10sP/Ac3MSMcG2Wp8aDZ52NSxyW6q7BQWLUSk+HQLkPyfULZ5oxYT
JZ5iiqIswIJxGEl+hAtm/plxY9ndRnN9Gn7JQqj/Yjw1yvIoge/mQ5GyLjv4IPIi
JqF1mwbmI0GOFGJNExvDjFrtKNYMloYRrSEjeUkr7hRKyoKo5/HNfuqFQz4DE9wq
LYPYo3Ul/WyoduSG2WtXOLMd8zGEnXDfdwB0oCJrEMwQQbPk4rUbvHDEu3zkpqls
MbxduaL9hTEgkNczKi5QHp60u3njrpvvWW0=
-----END CERTIFICATE-----
Loading