Skip to content

Commit 487723d

Browse files
authored
Support s2n-tls on macOS (#741)
1 parent 90a2306 commit 487723d

8 files changed

Lines changed: 120 additions & 11 deletions

File tree

.github/workflows/ci.yml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,42 @@ jobs:
340340
chmod a+x builder
341341
./builder build -p ${{ env.PACKAGE_NAME }}
342342
343+
macos-s2n:
344+
runs-on: macos-14 # latest
345+
env:
346+
AWS_CRT_USE_NON_FIPS_TLS_13: 1
347+
permissions:
348+
id-token: write # This is required for requesting the JWT
349+
steps:
350+
- name: configure AWS credentials (containers)
351+
uses: aws-actions/configure-aws-credentials@v4
352+
with:
353+
role-to-assume: ${{ env.CRT_CI_ROLE }}
354+
aws-region: ${{ env.AWS_DEFAULT_REGION }}
355+
- name: Build ${{ env.PACKAGE_NAME }} + consumers
356+
run: |
357+
python3 -c "from urllib.request import urlretrieve; urlretrieve('${{ env.BUILDER_HOST }}/${{ env.BUILDER_SOURCE }}/${{ env.BUILDER_VERSION }}/builder.pyz?run=${{ env.RUN }}', 'builder')"
358+
chmod a+x builder
359+
./builder build -p ${{ env.PACKAGE_NAME }}
360+
361+
macos-x64-s2n:
362+
runs-on: macos-14-large # latest
363+
env:
364+
AWS_CRT_USE_NON_FIPS_TLS_13: 1
365+
permissions:
366+
id-token: write # This is required for requesting the JWT
367+
steps:
368+
- name: configure AWS credentials (containers)
369+
uses: aws-actions/configure-aws-credentials@v4
370+
with:
371+
role-to-assume: ${{ env.CRT_CI_ROLE }}
372+
aws-region: ${{ env.AWS_DEFAULT_REGION }}
373+
- name: Build ${{ env.PACKAGE_NAME }} + consumers
374+
run: |
375+
python3 -c "from urllib.request import urlretrieve; urlretrieve('${{ env.BUILDER_HOST }}/${{ env.BUILDER_SOURCE }}/${{ env.BUILDER_VERSION }}/builder.pyz?run=${{ env.RUN }}', 'builder')"
376+
chmod a+x builder
377+
./builder build -p ${{ env.PACKAGE_NAME }}
378+
343379
openbsd:
344380
runs-on: ubuntu-24.04 # latest
345381
strategy:

README.md

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,33 @@ If you **must** use fork with aws-crt-python, you may be able to avoid hangs and
4949

5050
For an example, see `test.test_s3.py.S3RequestTest.test_fork_workaround` .
5151

52-
## Mac-Only TLS Behavior
52+
## macOS TLS Configuration
5353

54-
Please note that on Mac, once a private key is used with a certificate, that certificate-key pair is imported into the Mac Keychain. All subsequent uses of that certificate will use the stored private key and ignore anything passed in programmatically. Beginning in v0.6.2, when a stored private key from the Keychain is used, the following will be logged at the "info" log level:
54+
By default on macOS, aws-crt-python uses Apple Secure Transport for TLS. This provides FIPS-compliant cryptography
55+
and integration with the macOS Keychain (e.g. PKCS#12 credentials), but is limited to TLS 1.2.
56+
57+
To enable TLS 1.3 on macOS, set the environment variable:
58+
59+
```
60+
export AWS_CRT_USE_NON_FIPS_TLS_13=1
61+
```
62+
63+
This switches the TLS backend from Apple Secure Transport to [s2n-tls](https://github.com/aws/s2n-tls) with
64+
[aws-lc](https://github.com/aws/aws-lc) as the underlying libcrypto. The tradeoffs are:
65+
66+
| | Secure Transport (default) | s2n-tls (`AWS_CRT_USE_NON_FIPS_TLS_13=1`) |
67+
|---|---|---|
68+
| TLS versions | Up to TLS 1.2 | Up to TLS 1.3 |
69+
| FIPS compliance | Yes | No |
70+
| macOS Keychain integration | Yes (PKCS#12, system certs) | No |
71+
72+
This variable is checked at runtime and only affects macOS. It has no effect on Linux (which always uses s2n-tls)
73+
or Windows (which always uses Schannel). Both TLS backends are compiled into the binary when building on macOS;
74+
the environment variable selects which one is used.
75+
76+
### Keychain Behavior
77+
78+
Please note that on Mac, once a private key is used with a certificate, that certificate-key pair is imported into the Mac Keychain. All subsequent uses of that certificate will use the stored private key and ignore anything passed in programmatically. Beginning in v0.6.2, when a stored private key from the Keychain is used, the following will be logged at the "info" log level:
5579

5680
```
5781
static: certificate has an existing certificate-key pair that was previously imported into the Keychain. Using key from Keychain instead of the one provided.
@@ -110,8 +134,9 @@ You can enable the crash handler by setting the environment variable `AWS_CRT_CR
110134
### OpenSSL and LibCrypto
111135

112136
aws-crt-python does not use OpenSSL for TLS.
113-
On Apple and Windows devices, the OS's default TLS library is used.
114-
On Unix devices, [s2n-tls](https://github.com/aws/s2n-tls) is used.
137+
On Windows, the OS's default TLS library (Schannel) is used.
138+
On Apple (macOS), both Secure Transport and s2n-tls are compiled in; the backend is selected at runtime (see [macOS TLS Configuration](#macos-tls-configuration) below).
139+
On other Unix devices, [s2n-tls](https://github.com/aws/s2n-tls) is used.
115140
But s2n-tls uses libcrypto, the cryptography math library bundled with OpenSSL.
116141

117142
To simplify installation, aws-crt-python has its own copy of libcrypto.

crt/CMakeLists.txt

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,10 @@ option(AWS_USE_LIBCRYPTO_TO_SUPPORT_ED25519_EVERYWHERE "Set this if you want to
3232
string(REPLACE "-g" "-g1" CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}")
3333
string(REPLACE "-g" "-g1" CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO}")
3434

35-
# On Unix we use S2N for TLS and AWS-LC crypto.
36-
# (On Windows and Apple we use the default OS libraries)
37-
if ((UNIX AND NOT APPLE) OR AWS_USE_LIBCRYPTO_TO_SUPPORT_ED25519_EVERYWHERE)
35+
# On Linux and BSD, we use S2N for TLS and AWS-LC crypto.
36+
# On Windows, we use the default OS libraries.
37+
# On Apple, we use the default OS libraries by default, but support S2N usage.
38+
if (UNIX OR AWS_USE_LIBCRYPTO_TO_SUPPORT_ED25519_EVERYWHERE)
3839
option(USE_OPENSSL "Set this if you want to use your system's OpenSSL compatible libcrypto" OFF)
3940
include(AwsPrebuildDependency)
4041

@@ -48,7 +49,7 @@ if ((UNIX AND NOT APPLE) OR AWS_USE_LIBCRYPTO_TO_SUPPORT_ED25519_EVERYWHERE)
4849
-DCMAKE_BUILD_TYPE=RelWithDebInfo # Use the same build type as the rest of the project
4950
)
5051

51-
if (APPLE OR WIN32)
52+
if (WIN32)
5253
# Libcrypto implementations typically have several chunky pregenerated tables that add a lot
5354
# to artifact size. We dont really need them for ed25519 case on win/mac, so favor
5455
# smaller binary over perf here.
@@ -72,14 +73,22 @@ if ((UNIX AND NOT APPLE) OR AWS_USE_LIBCRYPTO_TO_SUPPORT_ED25519_EVERYWHERE)
7273

7374
endif()
7475

75-
if(UNIX AND NOT APPLE)
76+
# Build s2n-tls on all Unix platforms (Linux, BSD, macOS).
77+
# On macOS (Darwin), both Secure Transport and s2n are built; the TLS backend
78+
# is selected at runtime via the AWS_CRT_USE_NON_FIPS_TLS_13 environment variable.
79+
if(UNIX)
7680
# prebuild s2n-tls.
7781
aws_prebuild_dependency(
7882
DEPENDENCY_NAME S2N
7983
SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/s2n
8084
CMAKE_ARGUMENTS
8185
-DUNSAFE_TREAT_WARNINGS_AS_ERRORS=OFF
8286
-DBUILD_TESTING=OFF
87+
# On Intel Macs, Homebrew installs to /usr/local, which is in the default
88+
# system header search path. Without this flag, s2n picks up Homebrew's OpenSSL
89+
# headers instead of the bundled aws-lc headers. Not needed on ARM where Homebrew
90+
# uses /opt/homebrew (not in default search paths), but harmless to set everywhere.
91+
-DCMAKE_NO_SYSTEM_FROM_IMPORTED=ON
8392
)
8493
endif()
8594

crt/aws-c-io

Submodule aws-c-io updated 47 files

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,7 @@ def __init__(self, name, extra_cmake_args=[], libname=None):
301301
# aws-lc produces libcrypto.a
302302
AWS_LIBS.append(AwsLib('aws-lc', libname='crypto'))
303303

304-
if sys.platform != 'darwin' and sys.platform != 'win32':
304+
if sys.platform != 'win32':
305305
AWS_LIBS.append(AwsLib('s2n'))
306306

307307
AWS_LIBS.append(AwsLib('aws-c-common'))

test/test_mqtt5.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from awscrt import mqtt5, io, http, exceptions
66
from test import test_retry_wrapper, NativeResourceTest
77
import os
8+
import sys
89
import unittest
910
import uuid
1011
import time
@@ -303,6 +304,38 @@ def _test_direct_connect_mutual_tls(self):
303304
def test_direct_connect_mutual_tls(self):
304305
test_retry_wrapper(self._test_direct_connect_mutual_tls)
305306

307+
def _test_direct_connect_mutual_tls13(self):
308+
input_host_name = _get_env_variable("AWS_TEST_MQTT5_IOT_CORE_TLS13_HOST")
309+
input_cert = _get_env_variable("AWS_TEST_MQTT5_IOT_CORE_RSA_CERT")
310+
input_key = _get_env_variable("AWS_TEST_MQTT5_IOT_CORE_RSA_KEY")
311+
312+
client_options = mqtt5.ClientOptions(
313+
host_name=input_host_name,
314+
port=8883
315+
)
316+
tls_ctx_options = io.TlsContextOptions.create_client_with_mtls_from_path(
317+
input_cert,
318+
input_key
319+
)
320+
client_options.tls_ctx = io.ClientTlsContext(tls_ctx_options)
321+
322+
callbacks = Mqtt5TestCallbacks()
323+
client = self._create_client(client_options=client_options, callbacks=callbacks)
324+
client.start()
325+
326+
# On macOS with Secure Transport (the default), TLS 1.3 is not supported,
327+
# so the connection to a TLS-1.3-only host must fail.
328+
if sys.platform == 'darwin' and not os.environ.get('AWS_CRT_USE_NON_FIPS_TLS_13'):
329+
callbacks.future_connection_failure.result(TIMEOUT)
330+
else:
331+
callbacks.future_connection_success.result(TIMEOUT)
332+
333+
client.stop()
334+
callbacks.future_stopped.result(TIMEOUT)
335+
336+
def test_direct_connect_mutual_tls13(self):
337+
test_retry_wrapper(self._test_direct_connect_mutual_tls13)
338+
306339
def _test_direct_connect_http_proxy_tls(self):
307340
input_host_name = _get_env_variable("AWS_TEST_MQTT5_DIRECT_MQTT_TLS_HOST")
308341
input_port = int(_get_env_variable("AWS_TEST_MQTT5_DIRECT_MQTT_TLS_PORT"))

test/test_mqtt5_credentials.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,9 @@ def _test_mqtt5_cred_pkcs12(self):
130130
client.stop()
131131
callbacks.future_stopped.result(TIMEOUT)
132132

133+
# When AWS_CRT_USE_NON_FIPS_TLS_13 is set, the TLS backend on macOS switches from
134+
# Secure Transport to s2n-tls, which doesn't support PKCS#12.
135+
@unittest.skipIf(os.environ.get('AWS_CRT_USE_NON_FIPS_TLS_13'), "PKCS12 not supported with non-FIPS TLS 1.3")
133136
def test_mqtt5_cred_pkcs12(self):
134137
test_retry_wrapper(self._test_mqtt5_cred_pkcs12)
135138

test/test_mqtt_credentials.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ def _test_mqtt311_cred_pkcs12(self):
4646
connection.connect().result(TIMEOUT)
4747
connection.disconnect().result(TIMEOUT)
4848

49+
# When AWS_CRT_USE_NON_FIPS_TLS_13 is set, the TLS backend on macOS switches from
50+
# Secure Transport to s2n-tls, which doesn't support PKCS#12.
51+
@unittest.skipIf(os.environ.get('AWS_CRT_USE_NON_FIPS_TLS_13'), "PKCS12 not supported with non-FIPS TLS 1.3")
4952
def test_mqtt311_cred_pkcs12(self):
5053
test_retry_wrapper(self._test_mqtt311_cred_pkcs12)
5154

0 commit comments

Comments
 (0)