diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d0e25f7f8..0569bb6ea 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,19 +6,21 @@ on: - 'main' env: - BUILDER_VERSION: v0.9.62 + BUILDER_VERSION: v0.9.72 BUILDER_SOURCE: releases BUILDER_HOST: https://d19elf31gohf1l.cloudfront.net PACKAGE_NAME: aws-c-io LINUX_BASE_IMAGE: ubuntu-18-x64 RUN: ${{ github.run_id }}-${{ github.run_number }} - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - AWS_REGION: us-east-1 + CRT_CI_ROLE: ${{ secrets.CRT_CI_ROLE_ARN }} + AWS_DEFAULT_REGION: us-east-1 + +permissions: + id-token: write # This is required for requesting the JWT jobs: linux-compat: - runs-on: ubuntu-22.04 # latest + runs-on: ubuntu-24.04 # latest strategy: fail-fast: false matrix: @@ -32,6 +34,10 @@ jobs: - rhel8-x64 - al2-x64 steps: + - uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ env.CRT_CI_ROLE }} + aws-region: ${{ env.AWS_DEFAULT_REGION }} - name: Build ${{ env.PACKAGE_NAME }} run: | aws s3 cp s3://aws-crt-test-stuff/ci/${{ env.BUILDER_VERSION }}/linux-container-ci.sh ./linux-container-ci.sh && chmod a+x ./linux-container-ci.sh @@ -40,6 +46,10 @@ jobs: linux-byo-crypto: runs-on: ubuntu-22.04 # latest steps: + - uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ env.CRT_CI_ROLE }} + aws-region: ${{ env.AWS_DEFAULT_REGION }} # We can't use the `uses: docker://image` version yet, GitHub lacks authentication for actions -> packages - name: Build ${{ env.PACKAGE_NAME }} + consumers run: | @@ -47,43 +57,76 @@ jobs: ./linux-container-ci.sh ${{ env.BUILDER_VERSION }} aws-crt-al2-x64 build -p ${{ env.PACKAGE_NAME }} --cmake-extra=-DBYO_CRYPTO=ON linux-compiler-compat: - runs-on: ubuntu-22.04 # latest + runs-on: ubuntu-24.04 # latest strategy: matrix: compiler: - - clang-3 - - clang-6 - - clang-8 - - clang-9 - - clang-10 - - clang-11 - - gcc-4.8 - - gcc-5 - - gcc-6 - - gcc-7 - - gcc-8 + - name: clang-3 + - name: clang-6 + - name: clang-8 + - name: clang-9 + - name: clang-10 + - name: clang-11 + - name: clang-15 + - name: clang-17 + - name: gcc-4.8 + - name: gcc-5 + - name: gcc-6 + - name: gcc-7 + - name: gcc-8 + - name: gcc-11 + - name: gcc-13 + # See Issue: https://github.com/llvm/llvm-project/issues/59007. Although this issue + # has been fixed in LLVM, the fix will probably not propagate to older versions of Ubuntu and GCC 13.1. + # + # Starting with GLIBC version 2.34, the `dn_expand` function, previously found in `libresolv.so`, was moved to `libc.so`. This + # function is used internally by the `getaddrinfo()` system call. + # + # In our setup (As of December 2024), we are using an Ubuntu 18 Docker image on a newer Ubuntu host. + # However, due to compatibility issues between newer libasan.so in GCC 13.1 + # and the older Ubuntu image, the linker does not link with `libresolv.so`. + # This results in crashes in `getaddrinfo()` since Ubuntu-18 GLIBC is 2.31. + # + # This problem does not occur on Ubuntu 22 and newer because GLIBC versions 2.34 + # and above include `dn_expand` in `libc.so`, eliminating the dependency on + # `libresolv.so`. + # + # We can bypass this problem by linking with "resolv" manually until we bump + # our base Linux image to Ubuntu 22. + extra-build-flag: --cmake-extra=-DCMAKE_EXE_LINKER_FLAGS="-lresolv" steps: - # We can't use the `uses: docker://image` version yet, GitHub lacks authentication for actions -> packages - - name: Build ${{ env.PACKAGE_NAME }} + - uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ env.CRT_CI_ROLE }} + aws-region: ${{ env.AWS_DEFAULT_REGION }} + - name: Build ${{ matrix.compiler.name }} run: | aws s3 cp s3://aws-crt-test-stuff/ci/${{ env.BUILDER_VERSION }}/linux-container-ci.sh ./linux-container-ci.sh && chmod a+x ./linux-container-ci.sh - ./linux-container-ci.sh ${{ env.BUILDER_VERSION }} aws-crt-${{ env.LINUX_BASE_IMAGE }} build -p ${{ env.PACKAGE_NAME }} --compiler=${{ matrix.compiler }} + ./linux-container-ci.sh ${{ env.BUILDER_VERSION }} aws-crt-${{ env.LINUX_BASE_IMAGE }} build -p ${{ env.PACKAGE_NAME }} --compiler=${{ matrix.compiler.name }} ${{ matrix.compiler.extra-build-flag }} linux-debug: runs-on: ubuntu-24.04 # latest steps: + - uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ env.CRT_CI_ROLE }} + aws-region: ${{ env.AWS_DEFAULT_REGION }} - name: Build ${{ env.PACKAGE_NAME }} run: | aws s3 cp s3://aws-crt-test-stuff/ci/${{ env.BUILDER_VERSION }}/linux-container-ci.sh ./linux-container-ci.sh && chmod a+x ./linux-container-ci.sh ./linux-container-ci.sh ${{ env.BUILDER_VERSION }} aws-crt-${{ env.LINUX_BASE_IMAGE }} build -p ${{ env.PACKAGE_NAME }} --config Debug clang-sanitizers: - runs-on: ubuntu-22.04 # latest + runs-on: ubuntu-24.04 # latest strategy: fail-fast: false matrix: sanitizers: [",thread", ",address,undefined"] steps: + - uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ env.CRT_CI_ROLE }} + aws-region: ${{ env.AWS_DEFAULT_REGION }} # We can't use the `uses: docker://image` version yet, GitHub lacks authentication for actions -> packages - name: Build ${{ env.PACKAGE_NAME }} run: | @@ -91,8 +134,12 @@ jobs: ./linux-container-ci.sh ${{ env.BUILDER_VERSION }} aws-crt-${{ env.LINUX_BASE_IMAGE }} build -p ${{ env.PACKAGE_NAME }} --compiler=clang-11 --cmake-extra=-DENABLE_SANITIZERS=ON --cmake-extra=-DSANITIZERS="${{ matrix.sanitizers }}" linux-shared-libs: - runs-on: ubuntu-22.04 # latest + runs-on: ubuntu-24.04 # latest steps: + - uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ env.CRT_CI_ROLE }} + aws-region: ${{ env.AWS_DEFAULT_REGION }} # We can't use the `uses: docker://image` version yet, GitHub lacks authentication for actions -> packages - name: Build ${{ env.PACKAGE_NAME }} run: | @@ -102,17 +149,25 @@ jobs: # Test downstream repos. # This should not be required because we can run into a chicken and egg problem if there is a change that needs some fix in a downstream repo. downstream: - runs-on: ubuntu-22.04 # latest + runs-on: ubuntu-24.04 # latest steps: - # We can't use the `uses: docker://image` version yet, GitHub lacks authentication for actions -> packages - - name: Build ${{ env.PACKAGE_NAME }} - run: | - aws s3 cp s3://aws-crt-test-stuff/ci/${{ env.BUILDER_VERSION }}/linux-container-ci.sh ./linux-container-ci.sh && chmod a+x ./linux-container-ci.sh - ./linux-container-ci.sh ${{ env.BUILDER_VERSION }} aws-crt-${{ env.LINUX_BASE_IMAGE }} build downstream -p ${{ env.PACKAGE_NAME }} + - uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ env.CRT_CI_ROLE }} + aws-region: ${{ env.AWS_DEFAULT_REGION }} + # We can't use the `uses: docker://image` version yet, GitHub lacks authentication for actions -> packages + - name: Build ${{ env.PACKAGE_NAME }} + run: | + aws s3 cp s3://aws-crt-test-stuff/ci/${{ env.BUILDER_VERSION }}/linux-container-ci.sh ./linux-container-ci.sh && chmod a+x ./linux-container-ci.sh + ./linux-container-ci.sh ${{ env.BUILDER_VERSION }} aws-crt-${{ env.LINUX_BASE_IMAGE }} build downstream -p ${{ env.PACKAGE_NAME }} windows: runs-on: windows-2022 # latest steps: + - uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ env.CRT_CI_ROLE }} + aws-region: ${{ env.AWS_DEFAULT_REGION }} - name: Build ${{ env.PACKAGE_NAME }} + consumers run: | python -c "from urllib.request import urlretrieve; urlretrieve('${{ env.BUILDER_HOST }}/${{ env.BUILDER_SOURCE }}/${{ env.BUILDER_VERSION }}/builder.pyz?run=${{ env.RUN }}', 'builder.pyz')" @@ -121,6 +176,10 @@ jobs: windows-debug: runs-on: windows-2022 # latest steps: + - uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ env.CRT_CI_ROLE }} + aws-region: ${{ env.AWS_DEFAULT_REGION }} - name: Build ${{ env.PACKAGE_NAME }} + consumers run: | python -c "from urllib.request import urlretrieve; urlretrieve('${{ env.BUILDER_HOST }}/${{ env.BUILDER_SOURCE }}/${{ env.BUILDER_VERSION }}/builder.pyz?run=${{ env.RUN }}', 'builder.pyz')" @@ -132,6 +191,10 @@ jobs: matrix: arch: [x86, x64] steps: + - uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ env.CRT_CI_ROLE }} + aws-region: ${{ env.AWS_DEFAULT_REGION }} - name: Build ${{ env.PACKAGE_NAME }} + consumers run: | python -c "from urllib.request import urlretrieve; urlretrieve('${{ env.BUILDER_HOST }}/${{ env.BUILDER_SOURCE }}/${{ env.BUILDER_VERSION }}/builder.pyz?run=${{ env.RUN }}', 'builder.pyz')" @@ -140,6 +203,10 @@ jobs: windows-shared-libs: runs-on: windows-2022 # latest steps: + - uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ env.CRT_CI_ROLE }} + aws-region: ${{ env.AWS_DEFAULT_REGION }} - name: Build ${{ env.PACKAGE_NAME }} + consumers run: | python -c "from urllib.request import urlretrieve; urlretrieve('${{ env.BUILDER_HOST }}/${{ env.BUILDER_SOURCE }}/${{ env.BUILDER_VERSION }}/builder.pyz?run=${{ env.RUN }}', 'builder.pyz')" @@ -148,6 +215,10 @@ jobs: windows-app-verifier: runs-on: windows-2022 # latest steps: + - uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ env.CRT_CI_ROLE }} + aws-region: ${{ env.AWS_DEFAULT_REGION }} - name: Build ${{ env.PACKAGE_NAME }} + consumers run: | python -c "from urllib.request import urlretrieve; urlretrieve('${{ env.BUILDER_HOST }}/${{ env.BUILDER_SOURCE }}/${{ env.BUILDER_VERSION }}/builder.pyz?run=${{ env.RUN }}', 'builder.pyz')" @@ -163,15 +234,36 @@ jobs: matrix: eventloop: ["-DAWS_USE_APPLE_NETWORK_FRAMEWORK=ON", "-DAWS_USE_APPLE_NETWORK_FRAMEWORK=OFF"] steps: + - uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ env.CRT_CI_ROLE }} + aws-region: ${{ env.AWS_DEFAULT_REGION }} - name: Build ${{ env.PACKAGE_NAME }} + consumers run: | python3 -c "from urllib.request import urlretrieve; urlretrieve('${{ env.BUILDER_HOST }}/${{ env.BUILDER_SOURCE }}/${{ env.BUILDER_VERSION }}/builder.pyz?run=${{ env.RUN }}', 'builder')" chmod a+x builder ./builder build -p ${{ env.PACKAGE_NAME }} --cmake-extra=${{ matrix.eventloop }} + + macos-secitem: + runs-on: macos-14 # latest + steps: + - uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ env.CRT_CI_ROLE }} + aws-region: ${{ env.AWS_DEFAULT_REGION }} + - name: Build ${{ env.PACKAGE_NAME }} + consumers + run: | + python3 -c "from urllib.request import urlretrieve; urlretrieve('${{ env.BUILDER_HOST }}/${{ env.BUILDER_SOURCE }}/${{ env.BUILDER_VERSION }}/builder.pyz?run=${{ env.RUN }}', 'builder')" + chmod a+x builder + ./builder build -p ${{ env.PACKAGE_NAME }} --cmake-extra=-DAWS_USE_APPLE_NETWORK_FRAMEWORK=ON --cmake-extra=-DAWS_USE_SECITEM=ON macos-x64: runs-on: macos-14-large # latest steps: + - uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ env.CRT_CI_ROLE }} + aws-region: ${{ env.AWS_DEFAULT_REGION }} - name: Build ${{ env.PACKAGE_NAME }} + consumers run: | python3 -c "from urllib.request import urlretrieve; urlretrieve('${{ env.BUILDER_HOST }}/${{ env.BUILDER_SOURCE }}/${{ env.BUILDER_VERSION }}/builder.pyz?run=${{ env.RUN }}', 'builder')" @@ -185,6 +277,10 @@ jobs: matrix: eventloop: ["-DAWS_USE_APPLE_NETWORK_FRAMEWORK=ON", "-DAWS_USE_APPLE_NETWORK_FRAMEWORK=OFF"] steps: + - uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ env.CRT_CI_ROLE }} + aws-region: ${{ env.AWS_DEFAULT_REGION }} - name: Build ${{ env.PACKAGE_NAME }} + consumers run: | python3 -c "from urllib.request import urlretrieve; urlretrieve('${{ env.BUILDER_HOST }}/${{ env.BUILDER_SOURCE }}/${{ env.BUILDER_VERSION }}/builder.pyz?run=${{ env.RUN }}', 'builder')" @@ -192,39 +288,47 @@ jobs: ./builder build -p ${{ env.PACKAGE_NAME }} --cmake-extra=${{ matrix.eventloop }} --config Debug freebsd: - runs-on: ubuntu-22.04 # latest + runs-on: ubuntu-24.04 # latest steps: - - uses: actions/checkout@v4 - - name: Build ${{ env.PACKAGE_NAME }} + consumers - id: test - uses: cross-platform-actions/action@v0.23.0 - with: - operating_system: freebsd - architecture: x86-64 - version: '14.0' - cpu_count: 4 - shell: bash - run: | - sudo pkg install -y python3 net/py-urllib3 - python3 -c "from urllib.request import urlretrieve; urlretrieve('${{ env.BUILDER_HOST }}/${{ env.BUILDER_SOURCE }}/${{ env.BUILDER_VERSION }}/builder.pyz?run=${{ env.RUN }}', 'builder')" - chmod a+x builder - ./builder build -p ${{ env.PACKAGE_NAME }} + - uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ env.CRT_CI_ROLE }} + aws-region: ${{ env.AWS_DEFAULT_REGION }} + - uses: actions/checkout@v4 + - name: Build ${{ env.PACKAGE_NAME }} + consumers + id: test + uses: cross-platform-actions/action@v0.23.0 + with: + operating_system: freebsd + architecture: x86-64 + version: '14.0' + cpu_count: 4 + shell: bash + run: | + sudo pkg install -y python3 net/py-urllib3 + python3 -c "from urllib.request import urlretrieve; urlretrieve('${{ env.BUILDER_HOST }}/${{ env.BUILDER_SOURCE }}/${{ env.BUILDER_VERSION }}/builder.pyz?run=${{ env.RUN }}', 'builder')" + chmod a+x builder + ./builder build -p ${{ env.PACKAGE_NAME }} openbsd: - runs-on: ubuntu-22.04 # latest + runs-on: ubuntu-24.04 # latest steps: - - uses: actions/checkout@v4 - - name: Build ${{ env.PACKAGE_NAME }} + consumers - id: test - uses: cross-platform-actions/action@v0.23.0 - with: - operating_system: openbsd - architecture: x86-64 - version: '7.4' - cpu_count: 4 - shell: bash - run: | - sudo pkg_add py3-urllib3 - python3 -c "from urllib.request import urlretrieve; urlretrieve('${{ env.BUILDER_HOST }}/${{ env.BUILDER_SOURCE }}/${{ env.BUILDER_VERSION }}/builder.pyz?run=${{ env.RUN }}', 'builder')" - chmod a+x builder - ./builder build -p ${{ env.PACKAGE_NAME }} + - uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ env.CRT_CI_ROLE }} + aws-region: ${{ env.AWS_DEFAULT_REGION }} + - uses: actions/checkout@v4 + - name: Build ${{ env.PACKAGE_NAME }} + consumers + id: test + uses: cross-platform-actions/action@v0.23.0 + with: + operating_system: openbsd + architecture: x86-64 + version: '7.4' + cpu_count: 4 + shell: bash + run: | + sudo pkg_add py3-urllib3 + python3 -c "from urllib.request import urlretrieve; urlretrieve('${{ env.BUILDER_HOST }}/${{ env.BUILDER_SOURCE }}/${{ env.BUILDER_VERSION }}/builder.pyz?run=${{ env.RUN }}', 'builder')" + chmod a+x builder + ./builder build -p ${{ env.PACKAGE_NAME }} diff --git a/CMakeLists.txt b/CMakeLists.txt index c4660cb80..3947cde55 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -124,8 +124,8 @@ elseif (APPLE) list(APPEND EVENT_LOOP_DEFINES "DISPATCH_QUEUE") endif () - # Enable KQUEUE on MacOS - if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + # Enable KQUEUE on MacOS only if AWS_USE_SECITEM is not declared. SecItem requires Dispatch Queue. + if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin" AND NOT DEFINED AWS_USE_SECITEM) list(APPEND EVENT_LOOP_DEFINES "KQUEUE") endif() @@ -198,6 +198,10 @@ foreach(EVENT_LOOP_DEFINE IN LISTS EVENT_LOOP_DEFINES) target_compile_definitions(${PROJECT_NAME} PUBLIC "-DAWS_ENABLE_${EVENT_LOOP_DEFINE}") endforeach() +if (AWS_USE_SECITEM) + target_compile_definitions(${PROJECT_NAME} PUBLIC "-DAWS_USE_SECITEM") +endif() + if (BYO_CRYPTO) target_compile_definitions(${PROJECT_NAME} PUBLIC "-DBYO_CRYPTO") endif() diff --git a/include/aws/io/channel_bootstrap.h b/include/aws/io/channel_bootstrap.h index 81ce0696f..db41d16cc 100644 --- a/include/aws/io/channel_bootstrap.h +++ b/include/aws/io/channel_bootstrap.h @@ -214,6 +214,7 @@ struct aws_server_socket_channel_bootstrap_options { aws_server_bootstrap_on_accept_channel_shutdown_fn *shutdown_callback; aws_server_bootstrap_on_server_listener_destroy_fn *destroy_callback; bool enable_read_back_pressure; + struct aws_event_loop *requested_event_loop; void *user_data; }; diff --git a/include/aws/io/io.h b/include/aws/io/io.h index a9cc2618b..d35975a37 100644 --- a/include/aws/io/io.h +++ b/include/aws/io/io.h @@ -102,13 +102,6 @@ enum aws_io_errors { AWS_IO_CHANNEL_READ_WOULD_EXCEED_WINDOW, AWS_IO_EVENT_LOOP_ALREADY_ASSIGNED, AWS_IO_EVENT_LOOP_SHUTDOWN, - AWS_IO_TLS_ERROR_NEGOTIATION_FAILURE, - AWS_IO_TLS_ERROR_NOT_NEGOTIATED, - AWS_IO_TLS_ERROR_WRITE_FAILURE, - AWS_IO_TLS_ERROR_ALERT_RECEIVED, - AWS_IO_TLS_CTX_ERROR, - AWS_IO_TLS_VERSION_UNSUPPORTED, - AWS_IO_TLS_CIPHER_PREF_UNSUPPORTED, AWS_IO_MISSING_ALPN_MESSAGE, AWS_IO_UNHANDLED_ALPN_PROTOCOL_MESSAGE, AWS_IO_FILE_VALIDATION_FAILURE, @@ -140,12 +133,35 @@ enum aws_io_errors { DEPRECATED_AWS_IO_INVALID_FILE_HANDLE, AWS_IO_SHARED_LIBRARY_LOAD_FAILURE, AWS_IO_SHARED_LIBRARY_FIND_SYMBOL_FAILURE, - AWS_IO_TLS_NEGOTIATION_TIMEOUT, - AWS_IO_TLS_ALERT_NOT_GRACEFUL, AWS_IO_MAX_RETRIES_EXCEEDED, AWS_IO_RETRY_PERMISSION_DENIED, + + AWS_IO_TLS_ERROR_NEGOTIATION_FAILURE, + AWS_IO_TLS_ERROR_NOT_NEGOTIATED, + AWS_IO_TLS_ERROR_WRITE_FAILURE, + AWS_IO_TLS_ERROR_ALERT_RECEIVED, + AWS_IO_TLS_CTX_ERROR, + AWS_IO_TLS_VERSION_UNSUPPORTED, + AWS_IO_TLS_CIPHER_PREF_UNSUPPORTED, + AWS_IO_TLS_NEGOTIATION_TIMEOUT, + AWS_IO_TLS_ALERT_NOT_GRACEFUL, AWS_IO_TLS_DIGEST_ALGORITHM_UNSUPPORTED, AWS_IO_TLS_SIGNATURE_ALGORITHM_UNSUPPORTED, + AWS_IO_TLS_ERROR_READ_FAILURE, + AWS_IO_TLS_UNKNOWN_ROOT_CERTIFICATE, + AWS_IO_TLS_NO_ROOT_CERTIFICATE_FOUND, + AWS_IO_TLS_CERTIFICATE_EXPIRED, + AWS_IO_TLS_CERTIFICATE_NOT_YET_VALID, + AWS_IO_TLS_BAD_CERTIFICATE, + AWS_IO_TLS_PEER_CERTIFICATE_EXPIRED, + AWS_IO_TLS_BAD_PEER_CERTIFICATE, + AWS_IO_TLS_PEER_CERTIFICATE_REVOKED, + AWS_IO_TLS_PEER_CERTIFICATE_UNKNOWN, + AWS_IO_TLS_INTERNAL_ERROR, + AWS_IO_TLS_CLOSED_GRACEFUL, + AWS_IO_TLS_CLOSED_ABORT, + AWS_IO_TLS_INVALID_CERTIFICATE_CHAIN, + AWS_IO_TLS_HOST_NAME_MISSMATCH, AWS_ERROR_PKCS11_VERSION_UNSUPPORTED, AWS_ERROR_PKCS11_TOKEN_NOT_FOUND, @@ -258,8 +274,6 @@ enum aws_io_errors { AWS_IO_STREAM_SEEK_UNSUPPORTED, AWS_IO_STREAM_GET_LENGTH_UNSUPPORTED, - AWS_IO_TLS_ERROR_READ_FAILURE, - AWS_ERROR_PEM_MALFORMED, AWS_IO_ERROR_END_RANGE = AWS_ERROR_ENUM_END_RANGE(AWS_C_IO_PACKAGE_ID), diff --git a/include/aws/io/private/pki_utils.h b/include/aws/io/private/pki_utils.h index af0465560..5b1b56e70 100644 --- a/include/aws/io/private/pki_utils.h +++ b/include/aws/io/private/pki_utils.h @@ -15,8 +15,10 @@ #ifdef AWS_OS_APPLE /* It's ok to include external headers because this is a PRIVATE header file */ # include +# include #endif /* AWS_OS_APPLE */ +struct aws_secitem_options; struct aws_string; AWS_EXTERN_C_BEGIN @@ -29,7 +31,6 @@ AWS_IO_API const char *aws_determine_default_pki_dir(void); AWS_IO_API const char *aws_determine_default_pki_ca_file(void); #ifdef AWS_OS_APPLE -# if !defined(AWS_OS_IOS) /** * Imports a PEM armored PKCS#7 public/private key pair * into identity for use with SecurityFramework. @@ -41,7 +42,6 @@ int aws_import_public_and_private_keys_to_identity( const struct aws_byte_cursor *private_key, CFArrayRef *identity, const struct aws_string *keychain_path); -# endif /* AWS_OS_IOS */ /** * Imports a PKCS#12 file into identity for use with @@ -54,24 +54,38 @@ int aws_import_pkcs12_to_identity( CFArrayRef *identity); /** - * Loads PRM armored PKCS#7 certificates into certs - * for use with custom CA. + * Imports a PEM armored PKCS#7 public/private key pair + * into protected data keychain for use with Apple Network Framework. + * Currently only implemented for iOS. */ -int aws_import_trusted_certificates( +int aws_secitem_import_cert_and_key( struct aws_allocator *alloc, CFAllocatorRef cf_alloc, - const struct aws_byte_cursor *certificates_blob, - CFArrayRef *certs); + const struct aws_byte_cursor *public_cert_chain, + const struct aws_byte_cursor *private_key, + sec_identity_t *secitem_identity, + const struct aws_secitem_options *secitem_options); /** - * Releases identity (the output of the aws_import_* functions). + * Imports a PKCS#12 file into protected data keychain for use with + * Apple Network Framework. + * Currently only implemented for iOS. */ -void aws_release_identity(CFArrayRef identity); +int aws_secitem_import_pkcs12( + CFAllocatorRef cf_alloc, + const struct aws_byte_cursor *pkcs12_cursor, + const struct aws_byte_cursor *password, + sec_identity_t *out_identity); /** - * releases the output of aws_import_trusted_certificates. + * Loads PRM armored PKCS#7 certificates into certs + * for use with custom CA. */ -void aws_release_certificates(CFArrayRef certs); +int aws_import_trusted_certificates( + struct aws_allocator *alloc, + CFAllocatorRef cf_alloc, + const struct aws_byte_cursor *certificates_blob, + CFArrayRef *certs); #endif /* AWS_OS_APPLE */ diff --git a/include/aws/io/private/socket_impl.h b/include/aws/io/private/socket_impl.h index 2cfcf7ff1..25d0a8e57 100644 --- a/include/aws/io/private/socket_impl.h +++ b/include/aws/io/private/socket_impl.h @@ -35,6 +35,9 @@ int aws_socket_init_apple_nw_socket( struct aws_allocator *alloc, const struct aws_socket_options *options); +struct aws_byte_cursor; +struct aws_string; + struct aws_socket_vtable { void (*socket_cleanup_fn)(struct aws_socket *socket); int (*socket_connect_fn)( @@ -42,8 +45,13 @@ struct aws_socket_vtable { const struct aws_socket_endpoint *remote_endpoint, struct aws_event_loop *event_loop, aws_socket_on_connection_result_fn *on_connection_result, + aws_socket_retrieve_tls_options_fn *retrieve_tls_options, + void *user_data); + int (*socket_bind_fn)( + struct aws_socket *socket, + const struct aws_socket_endpoint *local_endpoint, + aws_socket_retrieve_tls_options_fn *retrieve_tls_options, void *user_data); - int (*socket_bind_fn)(struct aws_socket *socket, const struct aws_socket_endpoint *local_endpoint); int (*socket_listen_fn)(struct aws_socket *socket, int backlog_size); int (*socket_start_accept_fn)( struct aws_socket *socket, @@ -67,6 +75,8 @@ struct aws_socket_vtable { void *user_data); int (*socket_get_error_fn)(struct aws_socket *socket); bool (*socket_is_open_fn)(struct aws_socket *socket); + struct aws_byte_buf (*socket_get_protocol_fn)(const struct aws_socket *socket); + struct aws_string *(*socket_get_server_name_fn)(const struct aws_socket *socket); }; #endif // AWS_IO_SOCKET_IMPL_H diff --git a/include/aws/io/private/tls_channel_handler_shared.h b/include/aws/io/private/tls_channel_handler_shared.h index faf7e43b4..9aec14067 100644 --- a/include/aws/io/private/tls_channel_handler_shared.h +++ b/include/aws/io/private/tls_channel_handler_shared.h @@ -25,6 +25,19 @@ enum aws_tls_handler_read_state { AWS_TLS_HANDLER_READ_SHUT_DOWN_COMPLETE, }; +/* Apple Network socket connections when using secitem handles both the TCP and TLS + * handshakes with a singular completion state change/callback. + * Various TLS related elements must be accessible during the socket creation + * and listener binding to fit within the framework around TCP, TLS, and ALPN. + * This struct is used as a container that can retrieve the necessary elements when + * they are needed. */ +struct tls_connection_context { + struct aws_string *host_name; + struct aws_string *alpn_list; + struct aws_tls_ctx *tls_ctx; + struct aws_event_loop *event_loop; +}; + AWS_EXTERN_C_BEGIN AWS_IO_API void aws_tls_channel_handler_shared_init( diff --git a/include/aws/io/socket.h b/include/aws/io/socket.h index 3506f7f1b..65c418802 100644 --- a/include/aws/io/socket.h +++ b/include/aws/io/socket.h @@ -81,6 +81,7 @@ struct aws_socket_options { struct aws_socket; struct aws_event_loop; +struct tls_connection_context; /** * Called in client mode when an outgoing connection has succeeded or an error has occurred. @@ -91,6 +92,16 @@ struct aws_event_loop; */ typedef void(aws_socket_on_connection_result_fn)(struct aws_socket *socket, int error_code, void *user_data); +struct aws_tls_connection_options; + +/** + * Called to retrieve TLS related options during socket creation/initialization and socket listener binding. + * Typically the TLS handshake occurs after a socket connection is established but Apple Network Framework requires + * the setup of TLS related parameters at creation of the connection as its internal framework + * handles both the socket connection and the TLS handshake. + */ +typedef void(aws_socket_retrieve_tls_options_fn)(struct tls_connection_context *context, void *user_data); + /** * Called by a listening socket when either an incoming connection has been received or an error occurred. * @@ -158,8 +169,8 @@ struct aws_socket { void *impl; }; -struct aws_byte_buf; -struct aws_byte_cursor; +// struct aws_byte_buf; +// struct aws_byte_cursor; AWS_EXTERN_C_BEGIN @@ -203,6 +214,7 @@ AWS_IO_API int aws_socket_connect( const struct aws_socket_endpoint *remote_endpoint, struct aws_event_loop *event_loop, aws_socket_on_connection_result_fn *on_connection_result, + aws_socket_retrieve_tls_options_fn *retrieve_tls_options, void *user_data); /** @@ -210,7 +222,11 @@ AWS_IO_API int aws_socket_connect( * connection oriented modes, you still must call `aws_socket_listen()` and `aws_socket_start_accept()` before using the * socket. local_endpoint is copied. */ -AWS_IO_API int aws_socket_bind(struct aws_socket *socket, const struct aws_socket_endpoint *local_endpoint); +AWS_IO_API int aws_socket_bind( + struct aws_socket *socket, + const struct aws_socket_endpoint *local_endpoint, + aws_socket_retrieve_tls_options_fn *retrieve_tls_options, + void *user_data); /** * Get the local address which the socket is bound to. diff --git a/include/aws/io/tls_channel_handler.h b/include/aws/io/tls_channel_handler.h index 087b333a3..ea014ed02 100644 --- a/include/aws/io/tls_channel_handler.h +++ b/include/aws/io/tls_channel_handler.h @@ -88,6 +88,8 @@ struct aws_tls_ctx { /** * Invoked upon completion of the TLS handshake. If successful error_code will be AWS_OP_SUCCESS, otherwise * the negotiation failed and immediately after this function is invoked, the channel will be shutting down. + * + * NOTE: When using SecItem, the handler and slot will be related to sockets and not tls. */ typedef void(aws_tls_on_negotiation_result_fn)( struct aws_channel_handler *handler, @@ -146,6 +148,26 @@ struct aws_tls_connection_options { */ struct aws_tls_key_operation; +/** + * A struct containing parameters used during import of Certificate and Private Key into a + * data protection keychain using Apple's SecItem API. + */ +struct aws_secitem_options { + /** + * Human-Readable identifier tag for certificate being used in keychain. + * Value will be used with kSecAttrLabel Key in SecItem functions. + * If one is not provided, we generate it ourselves. + */ + struct aws_string *cert_label; + + /** + * Human-Readable identifier tag for private key being used in keychain. + * Value will be used with kSecAttrLabel Key in SecItem functions. + * If one is not provided, we generate it ourselves. + */ + struct aws_string *key_label; +}; + struct aws_tls_ctx_options { struct aws_allocator *allocator; @@ -214,15 +236,19 @@ struct aws_tls_ctx_options { */ struct aws_byte_buf pkcs12_password; -# if !defined(AWS_OS_IOS) /** - * On Apple OS you can also use a custom keychain instead of - * the default keychain of the account. + * On iOS/tvOS the available settings when adding items to the keychain using + * SecItem are contained within this struct. This is NOT supported on MacOS. + */ + struct aws_secitem_options *secitem_options; + + /** + * On MacOS you can also use a custom keychain instead of + * the default keychain of the account. This is NOT supported on iOS. */ struct aws_string *keychain_path; -# endif -#endif +#endif /* __APPLE__ */ /** max tls fragment size. Default is the value of g_aws_channel_max_fragment_size. */ size_t max_fragment_size; @@ -318,8 +344,6 @@ AWS_IO_API void aws_tls_ctx_options_clean_up(struct aws_tls_ctx_options *options * cert_path and pkey_path are paths to files on disk. cert_path * and pkey_path are treated as PKCS#7 PEM armored. They are loaded * from disk and stored in buffers internally. - * - * NOTE: This is unsupported on iOS. */ AWS_IO_API int aws_tls_ctx_options_init_client_mtls_from_path( struct aws_tls_ctx_options *options, @@ -331,8 +355,6 @@ AWS_IO_API int aws_tls_ctx_options_init_client_mtls_from_path( * Initializes options for use with mutual tls in client mode. * cert and pkey are copied. cert and pkey are treated as PKCS#7 PEM * armored. - * - * NOTE: This is unsupported on iOS. */ AWS_IO_API int aws_tls_ctx_options_init_client_mtls( struct aws_tls_ctx_options *options, @@ -509,6 +531,24 @@ AWS_IO_API int aws_tls_ctx_options_set_keychain_path( struct aws_tls_ctx_options *options, struct aws_byte_cursor *keychain_path_cursor); +/** + * Applies provided SecItem options to certificate and private key being + * added to the iOS/tvOS KeyChain. + * + * NOTE: This only works on iOS and tvOS. + * + * @param options aws_tls_ctx_options to be modified. + * @param secitem_options Options for SecItems + */ +AWS_IO_API int aws_tls_ctx_options_set_secitem_options( + struct aws_tls_ctx_options *tls_ctx_options, + const struct aws_secitem_options *secitem_options); + +/** + * Cleans up resources in secitem_options. + */ +AWS_IO_API void aws_tls_secitem_options_clean_up(struct aws_secitem_options *secitem_options); + /** * Initializes options for use with in server mode. * cert_path and pkey_path are paths to files on disk. cert_path @@ -903,6 +943,12 @@ const char *aws_tls_signature_algorithm_str(enum aws_tls_signature_algorithm sig AWS_IO_API const char *aws_tls_key_operation_type_str(enum aws_tls_key_operation_type operation_type); +/** + * Returns true if provided error_code is a TLS Negotiation related error. Use this to determine if + * the cause related to an error originated from a TLS handshake. + */ +AWS_IO_API bool aws_tls_error_code_check(int error_code); + AWS_EXTERN_C_END AWS_POP_SANE_WARNING_LEVEL diff --git a/source/channel_bootstrap.c b/source/channel_bootstrap.c index 6fb2a9461..496bde76a 100644 --- a/source/channel_bootstrap.c +++ b/source/channel_bootstrap.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -128,6 +129,13 @@ struct client_connection_args { bool enable_read_back_pressure; struct aws_event_loop *requested_event_loop; + /* + * Apple network framework's establishment of a network connection combines both TCP and TLS related + * operations into a singular connection callback. This is used to store a previously received + * TLS error_code that can be reported at a later time. + */ + int tls_error_code; + /* * It is likely that all reference adjustments to the connection args take place in a single event loop * thread and are thus thread-safe. I can imagine some complex future scenarios where that might not hold true @@ -475,6 +483,15 @@ static void s_on_client_channel_on_setup_completed(struct aws_channel *channel, } if (connection_args->channel_data.use_tls) { +#if defined(AWS_USE_SECITEM) + /* AWS_USE_SECITEM is using Apple Network Framework's implementation of TLS handling. + * The TCP and TLS handshakes are both handled by the network parameters, its options, and verification + * block. We do not need to set up a separate TLS slot in the channel when using SecItem. We only get to + * here if a TLS connection is successfully established so we trigger a success using the TLS handshake + * completion path to provide access to the server name and protocol if one exists. */ + s_tls_client_on_negotiation_result(socket_channel_handler, socket_slot, err_code, connection_args); + return; +#endif /* AWS_USE_SECITEM */ /* we don't want to notify the user that the channel is ready yet, since tls is still negotiating, wait * for the negotiation callback and handle it then.*/ if (s_setup_client_tls(connection_args, channel)) { @@ -484,7 +501,6 @@ static void s_on_client_channel_on_setup_completed(struct aws_channel *channel, } else { s_connection_args_setup_callback(connection_args, AWS_OP_SUCCESS, channel); } - return; } @@ -572,6 +588,22 @@ static void s_on_client_connection_established(struct aws_socket *socket, int er aws_socket_clean_up(socket); aws_mem_release(connection_args->bootstrap->allocator, socket); +#if defined(AWS_USE_SECITEM) + /* If Apple Network Framework Secitem is being used, it's possible at this point that + * a TCP connection was successful but it failed a TLS negotiation handshake. If the error + * indicates a TLS negotiation error, we store it to report the TLS failure once all connection + * addresses fail rather than the final TCP Socket timeout failure error code. */ + if (aws_tls_error_code_check(error_code)) { + AWS_LOGF_ERROR( + AWS_LS_IO_CHANNEL_BOOTSTRAP, + "id=%p: Connection failed with TLS error_code %d.", + (void *)connection_args->bootstrap, + error_code); + connection_args->tls_error_code = error_code; + connection_args->channel_data.socket = socket; + } +#endif /* AWS_USE_SECITEM */ + /* if this is the last attempted connection and it failed, notify the user */ if (connection_args->failed_count == connection_args->addresses_count) { AWS_LOGF_ERROR( @@ -579,6 +611,11 @@ static void s_on_client_connection_established(struct aws_socket *socket, int er "id=%p: Connection failed with error_code %d.", (void *)connection_args->bootstrap, error_code); + + if (connection_args->tls_error_code != AWS_ERROR_SUCCESS) { + error_code = connection_args->tls_error_code; + } + /* connection_args will be released after setup_callback */ s_connection_args_setup_callback(connection_args, error_code, NULL); } @@ -623,6 +660,16 @@ static void s_on_client_connection_established(struct aws_socket *socket, int er } } +/* Called when a socket connection attempt or socket bind requires access to TLS options. + * This is only necessary when Apple Network Framework is using Secitem + * where the parameters used to create the Apple Network Framework socket require TLS options. */ +static void s_retrieve_client_tls_options(struct tls_connection_context *context, void *user_data) { + struct client_connection_args *connection_args = user_data; + context->host_name = connection_args->channel_data.tls_options.server_name; + context->alpn_list = connection_args->channel_data.tls_options.alpn_list; + context->tls_ctx = connection_args->channel_data.tls_options.ctx; +} + struct connection_task_data { struct aws_task task; struct aws_socket_endpoint endpoint; @@ -652,6 +699,7 @@ static void s_attempt_connection(struct aws_task *task, void *arg, enum aws_task &task_data->endpoint, task_data->connect_loop, s_on_client_connection_established, + s_retrieve_client_tls_options, task_data->args)) { goto socket_connect_failed; @@ -858,6 +906,7 @@ int aws_client_bootstrap_new_socket_channel(struct aws_socket_channel_bootstrap_ client_connection_args->outgoing_port = port; client_connection_args->enable_read_back_pressure = options->enable_read_back_pressure; client_connection_args->requested_event_loop = options->requested_event_loop; + client_connection_args->tls_error_code = AWS_ERROR_SUCCESS; if (tls_options) { if (aws_tls_connection_options_copy(&client_connection_args->channel_data.tls_options, tls_options)) { @@ -946,7 +995,12 @@ int aws_client_bootstrap_new_socket_channel(struct aws_socket_channel_bootstrap_ s_client_connection_args_acquire(client_connection_args); if (aws_socket_connect( - outgoing_socket, &endpoint, connect_loop, s_on_client_connection_established, client_connection_args)) { + outgoing_socket, + &endpoint, + connect_loop, + s_on_client_connection_established, + s_retrieve_client_tls_options, + client_connection_args)) { aws_socket_clean_up(outgoing_socket); aws_mem_release(client_connection_args->bootstrap->allocator, outgoing_socket); s_client_connection_args_release(client_connection_args); @@ -1013,13 +1067,13 @@ struct aws_server_bootstrap *aws_server_bootstrap_new( return bootstrap; } - struct server_connection_args { struct aws_server_bootstrap *bootstrap; struct aws_socket listener; aws_server_bootstrap_on_accept_channel_setup_fn *incoming_callback; aws_server_bootstrap_on_accept_channel_shutdown_fn *shutdown_callback; aws_server_bootstrap_on_server_listener_destroy_fn *destroy_callback; + aws_socket_retrieve_tls_options_fn *retrieve_tls_options; struct aws_tls_connection_options tls_options; aws_channel_on_protocol_negotiated_fn *on_protocol_negotiated; aws_tls_on_data_read_fn *user_on_data_read; @@ -1030,9 +1084,22 @@ struct server_connection_args { void *user_data; bool use_tls; bool enable_read_back_pressure; + struct aws_event_loop *requested_event_loop; struct aws_ref_count ref_count; }; +static void s_retrieve_server_tls_options(struct tls_connection_context *context, void *user_data) { + struct server_connection_args *connection_args = user_data; + context->host_name = connection_args->tls_options.server_name; + context->alpn_list = connection_args->tls_options.alpn_list; + context->tls_ctx = connection_args->tls_options.ctx; + // verify block in an Apple Network TLS negotiation requires a dispatch loop contained within an event loop. + context->event_loop = connection_args->requested_event_loop; + if (context->event_loop == NULL) { + context->event_loop = aws_event_loop_group_get_next_loop(connection_args->bootstrap->event_loop_group); + } +} + struct server_channel_data { struct aws_channel *channel; struct aws_socket *socket; @@ -1290,6 +1357,14 @@ static void s_on_server_channel_on_setup_completed(struct aws_channel *channel, } if (channel_data->server_connection_args->use_tls) { +/* AWS_USE_SECITEM is using Apple Network Framework's implementation of TLS handling. + * The TCP and TLS handshake are both handled by the network parameters and its options and verification block. + * We do not need to set up a separate TLS slot in the channel for iOS. */ +#if defined(AWS_USE_SECITEM) + s_tls_server_on_negotiation_result(socket_channel_handler, socket_slot, err_code, channel_data); + return; + +#endif /* AWS_USE_SECITEM */ /* incoming callback will be invoked upon the negotiation completion so don't do it * here. */ if (s_setup_server_tls(channel_data, channel)) { @@ -1299,6 +1374,7 @@ static void s_on_server_channel_on_setup_completed(struct aws_channel *channel, } else { s_server_incoming_callback(channel_data, AWS_OP_SUCCESS, channel); } + return; error: @@ -1423,6 +1499,15 @@ struct aws_socket *aws_server_bootstrap_new_socket_listener( AWS_PRECONDITION(bootstrap_options->incoming_callback); AWS_PRECONDITION(bootstrap_options->shutdown_callback); + if (bootstrap_options->requested_event_loop != NULL) { + /* If we're asking for a specific event loop, verify it belongs to the bootstrap's event loop group */ + if (!(s_does_event_loop_belong_to_event_loop_group( + bootstrap_options->requested_event_loop, bootstrap_options->bootstrap->event_loop_group))) { + aws_raise_error(AWS_ERROR_IO_PINNED_EVENT_LOOP_MISMATCH); + return NULL; + } + } + struct server_connection_args *server_connection_args = aws_mem_calloc(bootstrap_options->bootstrap->allocator, 1, sizeof(struct server_connection_args)); if (!server_connection_args) { @@ -1448,6 +1533,8 @@ struct aws_socket *aws_server_bootstrap_new_socket_listener( server_connection_args->destroy_callback = bootstrap_options->destroy_callback; server_connection_args->on_protocol_negotiated = bootstrap_options->bootstrap->on_protocol_negotiated; server_connection_args->enable_read_back_pressure = bootstrap_options->enable_read_back_pressure; + server_connection_args->retrieve_tls_options = s_retrieve_server_tls_options; + server_connection_args->requested_event_loop = bootstrap_options->requested_event_loop; aws_task_init( &server_connection_args->listener_destroy_task, @@ -1510,7 +1597,8 @@ struct aws_socket *aws_server_bootstrap_new_socket_listener( memcpy(endpoint.address, bootstrap_options->host_name, host_name_len); endpoint.port = bootstrap_options->port; - if (aws_socket_bind(&server_connection_args->listener, &endpoint)) { + if (aws_socket_bind( + &server_connection_args->listener, &endpoint, s_retrieve_server_tls_options, server_connection_args)) { goto cleanup_listener; } diff --git a/source/darwin/dispatch_queue.h b/source/darwin/aws_apple_network_framework.h similarity index 85% rename from source/darwin/dispatch_queue.h rename to source/darwin/aws_apple_network_framework.h index 6b0b68f31..b67fbb1b0 100644 --- a/source/darwin/dispatch_queue.h +++ b/source/darwin/aws_apple_network_framework.h @@ -1,23 +1,25 @@ -#ifndef AWS_IO_DARWIN_DISPATCH_QUEUE_H -#define AWS_IO_DARWIN_DISPATCH_QUEUE_H +#ifndef AWS_IO_PRIVATE_AWS_APPLE_NETWORK_FRAMEWORK_H +#define AWS_IO_PRIVATE_AWS_APPLE_NETWORK_FRAMEWORK_H /** * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0. */ -#include #include #include #include + +/* This Header will only be compiled on Apple Platforms where therse are available. */ +#include #include struct secure_transport_ctx { struct aws_tls_ctx ctx; CFAllocatorRef wrapped_allocator; CFArrayRef certs; - SecIdentityRef secitem_identity; + sec_identity_t secitem_identity; CFArrayRef ca_cert; - enum aws_tls_versions minimum_version; + enum aws_tls_versions minimum_tls_version; struct aws_string *alpn_list; bool verify_peer; }; @@ -71,4 +73,4 @@ struct dispatch_loop { bool is_destroying; }; -#endif /* #ifndef AWS_IO_DARWIN_DISPATCH_QUEUE_H */ +#endif /* #ifndef AWS_IO_PRIVATE_AWS_APPLE_NETWORK_FRAMEWORK_H */ diff --git a/source/darwin/darwin_pki_utils.c b/source/darwin/darwin_pki_utils.c index 20fcb82e0..77a3d6f42 100644 --- a/source/darwin/darwin_pki_utils.c +++ b/source/darwin/darwin_pki_utils.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -332,6 +333,576 @@ int aws_import_pkcs12_to_identity( return AWS_OP_ERR; } +/* + * Apple's Network framework and SecItem API use of the data protection keychain is currently only implemented + * on iOS and tvOS. We may add support for MacOS at a later date. + * + * MacOS migration from currently deprecated Secure Transport API and file based keychain to + * Network framework will require it we also migrate from BSD Sockets to Apple's Network Framework. + * + * From a breaking existing users perspective, we must also find a way to continue support for the + * keychain_path field which is currently only bound out to aws-crt-cpp. + */ + +int aws_secitem_add_certificate_to_keychain( + CFAllocatorRef cf_alloc, + SecCertificateRef cert_ref, + CFDataRef serial_data, + CFStringRef label) { + + int result = AWS_OP_ERR; + OSStatus status; + + CFMutableDictionaryRef add_attributes = NULL; + CFMutableDictionaryRef delete_query = NULL; + + add_attributes = + CFDictionaryCreateMutable(cf_alloc, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + CFDictionaryAddValue(add_attributes, kSecClass, kSecClassCertificate); + CFDictionaryAddValue(add_attributes, kSecAttrSerialNumber, serial_data); + CFDictionaryAddValue(add_attributes, kSecAttrLabel, label); + CFDictionaryAddValue(add_attributes, kSecValueRef, cert_ref); + status = SecItemAdd(add_attributes, NULL); + + // A duplicate item is handled. All other errors are unhandled. + if (status != errSecSuccess && status != errSecDuplicateItem) { + AWS_LOGF_ERROR(AWS_LS_IO_PKI, "SecItemAdd certificate failed with OSStatus %d", (int)status); + result = aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE); + goto done; + } + + /* A duplicate item error indicates the certificate already exists in the keychain. We delete the + * existing certificate and re-add the certificate in case there are differences that need to be applied. + * + * query should be made up of primary keys only. Optional/non-unique attributes in the query + * can result in not finding the matching certificate and cause the update operation to fail. + * + * Certificate item primary keys we use for the query: + * kSecAttrSerialNumber: (CFStringRef) value indicates the item's serial number + * - We explicity set this value, extracted from the certificate itself as our primary method of determining + * uniqueness of the certificate. + * + * Certificate primary keys we do not use for the query: + * These can be added in the future if we require a more specified search query. + * kSecAttrCertificateType: (CFNumberRef) value indicates the item's certificate type + * - values see the CSSM_CERT_TYPE enumeration in cssmtype.h + * https://opensource.apple.com/source/Security/Security-55471/libsecurity_cssm/lib/cssmtype.h.auto.html + * - default will try to add common value such as X.509. We do not pass this attribute and allow default value + * to be used. If we decide to support other types of certificates, we should set and use this value explicitly. + * kSecAttrIssuer: (CFStringRef) value indicates the item's issuer + * - default will try to extract issuer from the certificate itself. + * We will not set this attribute and allow default value to be used. + */ + if (status == errSecDuplicateItem) { + AWS_LOGF_INFO( + AWS_LS_IO_PKI, + "static: Keychain contains existing certificate that was previously imported into the Keychain. " + "Deleting existing certificate in keychain."); + + delete_query = + CFDictionaryCreateMutable(cf_alloc, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + CFDictionaryAddValue(delete_query, kSecClass, kSecClassCertificate); + CFDictionaryAddValue(delete_query, kSecAttrSerialNumber, serial_data); + + // delete the existing certificate from keychain + status = SecItemDelete(delete_query); + if (status != errSecSuccess) { + AWS_LOGF_ERROR(AWS_LS_IO_PKI, "SecItemDelete certificate failed with OSStatus %d", (int)status); + result = aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE); + goto done; + } + + // now try adding it again + status = SecItemAdd(add_attributes, NULL); + if (status != errSecSuccess) { + AWS_LOGF_ERROR(AWS_LS_IO_PKI, "SecItemAdd certificate failed with OSStatus %d", (int)status); + result = aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE); + goto done; + } + } + + AWS_LOGF_INFO(AWS_LS_IO_PKI, "static: Successfully imported certificate into SecItem keychain."); + + result = AWS_OP_SUCCESS; + +done: + // cleanup + if (add_attributes) + CFRelease(add_attributes); + if (delete_query) + CFRelease(delete_query); + + return result; +} + +int aws_secitem_add_private_key_to_keychain( + CFAllocatorRef cf_alloc, + SecKeyRef key_ref, + CFStringRef label, + CFDataRef application_label) { + + int result = AWS_OP_ERR; + OSStatus status; + + CFMutableDictionaryRef add_attributes = NULL; + CFMutableDictionaryRef delete_query = NULL; + + add_attributes = + CFDictionaryCreateMutable(cf_alloc, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + CFDictionaryAddValue(add_attributes, kSecClass, kSecClassKey); + CFDictionaryAddValue(add_attributes, kSecAttrKeyClass, kSecAttrKeyClassPrivate); + CFDictionaryAddValue(add_attributes, kSecAttrApplicationLabel, application_label); + CFDictionaryAddValue(add_attributes, kSecAttrLabel, label); + CFDictionaryAddValue(add_attributes, kSecValueRef, key_ref); + status = SecItemAdd(add_attributes, NULL); + + // A duplicate item is handled. All other errors are unhandled. + if (status != errSecSuccess && status != errSecDuplicateItem) { + AWS_LOGF_ERROR(AWS_LS_IO_PKI, "SecItemAdd private key failed with OSStatus %d", (int)status); + result = aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE); + goto done; + } + + /* A duplicate item error indicates the private key already exists in the keychain. We delete the + * existing private key and re-add the private-key in case there are differences that need to be applied. + * + * query should be made up of primary keys only. Optional/non-unique attributes in the query + * can result in not finding the matching private key and cause the update operation to fail. + * + * Private Key item primary keys we use for the query: + * kSecAttrKeyClass: (CFTypeRef) value indicates item's cryptographic key class + * - We explicitly set this value to kSecAttrKeyClassPrivate + * kSecAttrApplicationLabel: (CFStringRef) value indicates item's application label. + * - We pull this value out of the SecKeyRef. It's the hash of the public key stored within. + * + * Private Key primary keys we do not use for the query: + * These can be added in the future if we require a more specified search query. + * kSecAttrApplicationTag: (CFDataRef) value indicates the item's private tag. + * kSecAttrKeySizeInBits: (CFNumberRef) value indicates the number of bits in a cryptographic key. + * kSecAttrEffectiveKeySize: (CFNumberRef) value indicates the effective number of bits in a crytographic key. + */ + + if (status == errSecDuplicateItem) { + AWS_LOGF_INFO( + AWS_LS_IO_PKI, + "static: Keychain contains existing private key that was previously imported into the Keychain. " + "Deleting private key in keychain."); + + delete_query = + CFDictionaryCreateMutable(cf_alloc, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + CFDictionaryAddValue(delete_query, kSecClass, kSecClassKey); + CFDictionaryAddValue(delete_query, kSecAttrKeyClass, kSecAttrKeyClassPrivate); + CFDictionaryAddValue(delete_query, kSecAttrApplicationLabel, application_label); + + // delete the existing private key from keychain + status = SecItemDelete(delete_query); + if (status != errSecSuccess) { + AWS_LOGF_ERROR(AWS_LS_IO_PKI, "SecItemDelete private key failed with OSStatus %d", (int)status); + result = aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE); + goto done; + } + + // now try adding it again + status = SecItemAdd(add_attributes, NULL); + if (status != errSecSuccess) { + AWS_LOGF_ERROR(AWS_LS_IO_PKI, "SecItemAdd private key failed with OSStatus %d", (int)status); + result = aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE); + goto done; + } + } + + AWS_LOGF_INFO(AWS_LS_IO_PKI, "static: Successfully imported private key into SecItem keychain."); + + result = AWS_OP_SUCCESS; +done: + // cleanup + if (add_attributes) + CFRelease(add_attributes); + if (delete_query) + CFRelease(delete_query); + + return result; +} + +int aws_secitem_get_identity(CFAllocatorRef cf_alloc, CFDataRef serial_data, sec_identity_t *out_identity) { +#if !defined(AWS_USE_SECITEM) + (void)cf_alloc; + (void)serial_data; + (void)out_identity; + AWS_LOGF_ERROR(AWS_LS_IO_PKI, "static: Secitem not supported on this platform."); + return aws_raise_error(AWS_ERROR_PLATFORM_NOT_SUPPORTED); +#else + int result = AWS_OP_ERR; + OSStatus status; + CFMutableDictionaryRef search_query = NULL; + SecIdentityRef sec_identity_ref = NULL; + + /* + * SecItem identity is created when a certificate matches a private key in the keychain. + * Since a private key may be associated with multiple certificates, searching for the + * identity using a unique attribute of the certificate is required. This is why we use + * the serial_data from the certificate as the search parameter. + */ + search_query = + CFDictionaryCreateMutable(cf_alloc, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + CFDictionaryAddValue(search_query, kSecClass, kSecClassIdentity); + CFDictionaryAddValue(search_query, kSecAttrSerialNumber, serial_data); + CFDictionaryAddValue(search_query, kSecReturnRef, kCFBooleanTrue); + + /* + * Copied or created CF items must have CFRelease called on them or you leak memory. This identity needs to + * have CFRelease called on it at some point or it will leak. + */ + status = SecItemCopyMatching(search_query, (CFTypeRef *)&sec_identity_ref); + + if (status != errSecSuccess) { + AWS_LOGF_ERROR(AWS_LS_IO_PKI, "SecItemCopyMatching identity failed with OSStatus %d", (int)status); + result = aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE); + goto done; + } + + *out_identity = sec_identity_create(sec_identity_ref); + + AWS_LOGF_INFO(AWS_LS_IO_PKI, "static: Successfully retrieved identity from keychain."); + + result = AWS_OP_SUCCESS; + +done: + // cleanup + if (search_query) + CFRelease(search_query); + if (sec_identity_ref) + CFRelease(sec_identity_ref); + + return result; +#endif +} + +int aws_secitem_import_cert_and_key( + struct aws_allocator *alloc, + CFAllocatorRef cf_alloc, + const struct aws_byte_cursor *public_cert_chain, + const struct aws_byte_cursor *private_key, + sec_identity_t *secitem_identity, + const struct aws_secitem_options *secitem_options) { + +// We currently only support Apple's network framework and SecItem keychain API on iOS. +#if !defined(AWS_USE_SECITEM) + (void)alloc; + (void)cf_alloc; + (void)public_cert_chain; + (void)private_key; + (void)secitem_identity; + (void)secitem_options; + AWS_LOGF_ERROR(AWS_LS_IO_PKI, "static: Secitem not supported on this platform."); + return aws_raise_error(AWS_ERROR_PLATFORM_NOT_SUPPORTED); +#else + + AWS_PRECONDITION(public_cert_chain != NULL); + AWS_PRECONDITION(private_key != NULL); + + int result = AWS_OP_ERR; + + CFErrorRef error = NULL; + + CFDataRef cert_data = NULL; + SecCertificateRef cert_ref = NULL; + CFDataRef cert_serial_data = NULL; + CFStringRef cert_label_ref = NULL; + + CFMutableDictionaryRef key_attributes = NULL; + CFDictionaryRef key_copied_attributes = NULL; + CFDataRef key_data = NULL; + SecKeyRef key_ref = NULL; + CFStringRef key_type = NULL; + CFStringRef key_label_ref = NULL; + CFDataRef application_label_ref = NULL; + + struct aws_array_list decoded_cert_buffer_list; + AWS_ZERO_STRUCT(decoded_cert_buffer_list); + struct aws_array_list decoded_key_buffer_list; + AWS_ZERO_STRUCT(decoded_key_buffer_list); + + /* + * iOS SecItem requires DER encoded files so we first convert the provided PEM encoded + * cert and key into a list of aws_pem_object that strips headers/footers and Base64 decodes + * the data into a byte buf. + */ + if (aws_pem_objects_init_from_file_contents(&decoded_cert_buffer_list, alloc, *public_cert_chain)) { + AWS_LOGF_ERROR(AWS_LS_IO_PKI, "static: Failed to decode PEM certificate to DER format."); + goto done; + } + AWS_ASSERT(aws_array_list_is_valid(&decoded_cert_buffer_list)); + + if (aws_pem_objects_init_from_file_contents(&decoded_key_buffer_list, alloc, *private_key)) { + AWS_LOGF_ERROR(AWS_LS_IO_PKI, "static: Failed to decode PEM certificate to DER format."); + goto done; + } + AWS_ASSERT(aws_array_list_is_valid(&decoded_key_buffer_list)); + + /* + * A PEM certificate file could contains multiple PEM data sections. We currently decode and + * use the first certificate data only. Certificate chaining support could be added for iOS + * in the future. + * */ + if (aws_array_list_length(&decoded_cert_buffer_list) > 1) { + AWS_LOGF_ERROR(AWS_LS_IO_PKI, "Certificate chains not currently supported on iOS."); + result = aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + goto done; + } + + /* + * The aws_pem_object preserves the type of encoding found in the PEM file. We can use the + * type_string member to set the appropriate attribute on storage. + */ + struct aws_pem_object *pem_cert_ptr = NULL; + aws_array_list_get_at_ptr(&decoded_cert_buffer_list, (void **)&pem_cert_ptr, 0); + AWS_ASSERT(pem_cert_ptr); + + struct aws_pem_object *pem_key_ptr = NULL; + aws_array_list_get_at_ptr(&decoded_key_buffer_list, (void **)&pem_key_ptr, 0); + AWS_ASSERT(pem_key_ptr); + + /* CFDataRef is used to generate the value to pair with kSecValueRef in the SecItemAdd */ + cert_data = CFDataCreate(cf_alloc, pem_cert_ptr->data.buffer, pem_cert_ptr->data.len); + if (!cert_data) { + AWS_LOGF_ERROR(AWS_LS_IO_PKI, "Error creating certificate data system call."); + result = aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE); + goto done; + } + + key_data = CFDataCreate(cf_alloc, pem_key_ptr->data.buffer, pem_key_ptr->data.len); + if (!key_data) { + AWS_LOGF_ERROR(AWS_LS_IO_PKI, "Error creating private key data system call."); + result = aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE); + goto done; + } + + /* Set the format of the key */ + switch (pem_key_ptr->type) { + case AWS_PEM_TYPE_PRIVATE_RSA_PKCS1: + key_type = kSecAttrKeyTypeRSA; + break; + + case AWS_PEM_TYPE_EC_PRIVATE: + key_type = kSecAttrKeyTypeEC; + break; + + case AWS_PEM_TYPE_PRIVATE_PKCS8: + /* PKCS8 is not supported on iOS/tvOS (the framework doesn't allow it) and is + * currently NOT supported by us on macOS PKCS8 support for macOS using SecItem + * can be added later for macOS only but will require a different import strategy + * than the currently shared one. */ + key_type = kSecAttrKeyTypeRSA; + AWS_LOGF_ERROR( + AWS_LS_IO_PKI, "The PKCS8 private key format is currently unsupported for use with SecItem."); + result = aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + goto done; + break; + + case AWS_PEM_TYPE_UNKNOWN: + default: + AWS_LOGF_ERROR(AWS_LS_IO_PKI, "Unsupported private key format."); + result = aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + goto done; + } + + /* Attributes used for query and adding of cert/key SecItems */ + + /* + * We create a SecCertificateRef here to use with the kSecValueRef key as well as to + * extract the serial number for use as a unique identifier when storing the + * certificate in the keychain. The serial number is also used as the identifier + * when retrieving the identity + */ + cert_ref = SecCertificateCreateWithData(cf_alloc, cert_data); + if (!cert_ref) { + AWS_LOGF_ERROR(AWS_LS_IO_PKI, "Failed creating SecCertificateRef from cert_data."); + result = aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE); + goto done; + } + + cert_serial_data = SecCertificateCopySerialNumberData(cert_ref, &error); + if (error) { + AWS_LOGF_ERROR(AWS_LS_IO_PKI, "Failed extracting serial number data from cert_ref."); + result = aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE); + goto done; + } + + cert_label_ref = CFStringCreateWithBytes( + cf_alloc, + (const UInt8 *)aws_string_bytes(secitem_options->cert_label), + secitem_options->cert_label->len, + kCFStringEncodingUTF8, + false); + if (!cert_label_ref) { + AWS_LOGF_ERROR(AWS_LS_IO_PKI, "Failed creating certificate label."); + result = aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE); + goto done; + } + + /* + * We create a SecKeyRef here to use with the kSecValueRef key as well as to extract + * the application label for use as a unique identifier when storing the private key + * in the keychain. + */ + key_attributes = + CFDictionaryCreateMutable(cf_alloc, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + CFDictionaryAddValue(key_attributes, kSecAttrKeyClass, kSecAttrKeyClassPrivate); + CFDictionaryAddValue(key_attributes, kSecAttrKeyType, key_type); + key_ref = SecKeyCreateWithData(key_data, key_attributes, &error); + + // Get the hash of the public key stored within the private key + key_copied_attributes = SecKeyCopyAttributes(key_ref); + // application_label_ref gets released when key_copied_attributes is released. + application_label_ref = (CFDataRef)CFDictionaryGetValue(key_copied_attributes, kSecAttrApplicationLabel); + if (!application_label_ref) { + AWS_LOGF_ERROR(AWS_LS_IO_PKI, "Failed creating private key application label."); + result = aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE); + goto done; + } + + key_label_ref = CFStringCreateWithBytes( + cf_alloc, + (const UInt8 *)aws_string_bytes(secitem_options->key_label), + secitem_options->key_label->len, + kCFStringEncodingUTF8, + false); + if (!key_label_ref) { + AWS_LOGF_ERROR(AWS_LS_IO_PKI, "Failed creating private key label."); + result = aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE); + goto done; + } + + // Add the certificate and private key to keychain then retrieve identity +# if !defined(AWS_OS_IOS) + aws_mutex_lock(&s_sec_mutex); +# endif /* !AWS_OS_IOS */ + + if (aws_secitem_add_certificate_to_keychain(cf_alloc, cert_ref, cert_serial_data, cert_label_ref)) { + goto done; + } + + if (aws_secitem_add_private_key_to_keychain(cf_alloc, key_ref, key_label_ref, application_label_ref)) { + goto done; + } + + if (aws_secitem_get_identity(cf_alloc, cert_serial_data, secitem_identity)) { + goto done; + } + + result = AWS_OP_SUCCESS; + +done: +# if !defined(AWS_OS_IOS) + aws_mutex_unlock(&s_sec_mutex); +# endif /* !AWS_OS_IOS */ + + // cleanup + if (error != NULL) + CFRelease(error); + if (cert_data != NULL) + CFRelease(cert_data); + if (cert_ref != NULL) + CFRelease(cert_ref); + if (cert_serial_data != NULL) + CFRelease(cert_serial_data); + if (cert_label_ref) + CFRelease(cert_label_ref); + if (key_attributes) + CFRelease(key_attributes); + if (key_copied_attributes) + CFRelease(key_copied_attributes); + if (key_data != NULL) + CFRelease(key_data); + if (key_ref != NULL) + CFRelease(key_ref); + if (key_type != NULL) + CFRelease(key_type); + if (key_label_ref) + CFRelease(key_label_ref); + + // Zero out the array list and release it + aws_pem_objects_clean_up(&decoded_cert_buffer_list); + aws_pem_objects_clean_up(&decoded_key_buffer_list); + + return result; +#endif +} + +int aws_secitem_import_pkcs12( + CFAllocatorRef cf_alloc, + const struct aws_byte_cursor *pkcs12_cursor, + const struct aws_byte_cursor *password, + sec_identity_t *out_identity) { +// We currently only support Apple's network framework and SecItem keychain API on iOS. +#if !defined(AWS_USE_SECITEM) + (void)cf_alloc; + (void)pkcs12_cursor; + (void)password; + (void)out_identity; + AWS_LOGF_ERROR(AWS_LS_IO_PKI, "static: Secitem not supported on this platform."); + return aws_raise_error(AWS_ERROR_PLATFORM_NOT_SUPPORTED); +#else + int result = AWS_OP_ERR; + CFArrayRef items = NULL; + CFDataRef pkcs12_data = NULL; + CFMutableDictionaryRef dictionary = NULL; + SecIdentityRef sec_identity_ref = NULL; + CFStringRef password_ref = NULL; + bool should_release_password = true; + + pkcs12_data = CFDataCreate(cf_alloc, pkcs12_cursor->ptr, pkcs12_cursor->len); + if (password->len) { + password_ref = CFStringCreateWithBytes(cf_alloc, password->ptr, password->len, kCFStringEncodingUTF8, false); + } else { + should_release_password = false; + password_ref = CFSTR(""); + } + + dictionary = CFDictionaryCreateMutable(cf_alloc, 0, NULL, NULL); + CFDictionaryAddValue(dictionary, kSecImportExportPassphrase, password_ref); + + OSStatus status = SecPKCS12Import(pkcs12_data, dictionary, &items); + + if (status != errSecSuccess || CFArrayGetCount(items) == 0) { + AWS_LOGF_ERROR(AWS_LS_IO_PKI, "Failed to import PKCS#12 file with OSStatus:%d", (int)status); + result = aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE); + goto done; + } + + // Extract the identity from the first item in the array + // identity_and_trust does not need to be released as it is not a copy or created CF object. + CFDictionaryRef identity_and_trust = CFArrayGetValueAtIndex(items, 0); + sec_identity_ref = (SecIdentityRef)CFDictionaryGetValue(identity_and_trust, kSecImportItemIdentity); + + if (sec_identity_ref != NULL) { + AWS_LOGF_INFO( + AWS_LS_IO_PKI, "static: Successfully imported PKCS#12 file into keychain and retrieved identity."); + } else { + status = errSecItemNotFound; + AWS_LOGF_ERROR(AWS_LS_IO_PKI, "Failed to retrieve identity from PKCS#12 with OSStatus %d", (int)status); + goto done; + } + + *out_identity = sec_identity_create(sec_identity_ref); + + result = AWS_OP_SUCCESS; + +done: + // cleanup + if (pkcs12_data) + CFRelease(pkcs12_data); + if (dictionary) + CFRelease(dictionary); + if (should_release_password) + CFRelease(password_ref); + if (items) + CFRelease(items); + return result; +#endif +} + int aws_import_trusted_certificates( struct aws_allocator *alloc, CFAllocatorRef cf_alloc, @@ -374,11 +945,3 @@ int aws_import_trusted_certificates( aws_array_list_clean_up(&certificates); return err; } - -void aws_release_identity(CFArrayRef identity) { - CFRelease(identity); -} - -void aws_release_certificates(CFArrayRef certs) { - CFRelease(certs); -} diff --git a/source/darwin/dispatch_queue_event_loop.c b/source/darwin/dispatch_queue_event_loop.c index 7b4671316..59b28dca7 100644 --- a/source/darwin/dispatch_queue_event_loop.c +++ b/source/darwin/dispatch_queue_event_loop.c @@ -15,7 +15,7 @@ #include -#include "dispatch_queue.h" +#include "aws_apple_network_framework.h" #include #include #include @@ -153,6 +153,7 @@ static struct aws_string *s_get_unique_dispatch_queue_id(struct aws_allocator *a return result; } +#if defined(AWS_ENABLE_DISPATCH_QUEUE) /* Setup a dispatch_queue with a scheduler. */ struct aws_event_loop *aws_event_loop_new_with_dispatch_queue( struct aws_allocator *alloc, @@ -222,6 +223,8 @@ struct aws_event_loop *aws_event_loop_new_with_dispatch_queue( return NULL; } +#endif // AWS_ENABLE_DISPATCH_QUEUE + static void s_dispatch_queue_destroy_task(void *context) { struct dispatch_loop *dispatch_loop = context; diff --git a/source/darwin/nw_socket.c b/source/darwin/nw_socket.c index 14f3ee30a..055063ae6 100644 --- a/source/darwin/nw_socket.c +++ b/source/darwin/nw_socket.c @@ -10,18 +10,39 @@ #include #include #include -#include #include +#include #include +#include "aws_apple_network_framework.h" #include #include -#include "dispatch_queue.h" +const char *aws_sec_trust_result_type_to_string(SecTrustResultType trust_result) { + switch (trust_result) { + case kSecTrustResultInvalid: + return "kSecTrustResultInvalid"; + case kSecTrustResultProceed: + return "kSecTrustResultProceed"; + case kSecTrustResultDeny: + return "kSecTrustResultDeny"; + case kSecTrustResultUnspecified: + return "kSecTrustResultUnspecified"; + case kSecTrustResultRecoverableTrustFailure: + return "kSecTrustResultRecoverableTrustFailure"; + case kSecTrustResultFatalTrustFailure: + return "kSecTrustResultFatalTrustFailure"; + case kSecTrustResultOtherError: + return "kSecTrustResultOtherError"; + default: + return "Unknown SecTrustResultType"; + } +} static int s_determine_socket_error(int error) { switch (error) { + /* POSIX Errors */ case ECONNREFUSED: return AWS_IO_SOCKET_CONNECTION_REFUSED; case ETIMEDOUT: @@ -52,6 +73,42 @@ static int s_determine_socket_error(int error) { return AWS_IO_SOCKET_UNSUPPORTED_ADDRESS_FAMILY; case EACCES: return AWS_ERROR_NO_PERMISSION; + + /* SSL/TLS Errors */ + case errSSLUnknownRootCert: + return AWS_IO_TLS_UNKNOWN_ROOT_CERTIFICATE; + case errSSLNoRootCert: + return AWS_IO_TLS_NO_ROOT_CERTIFICATE_FOUND; + case errSSLCertExpired: + return AWS_IO_TLS_CERTIFICATE_EXPIRED; + case errSSLCertNotYetValid: + return AWS_IO_TLS_CERTIFICATE_NOT_YET_VALID; + case errSSLPeerHandshakeFail: + return AWS_IO_TLS_ERROR_NEGOTIATION_FAILURE; + case errSSLBadCert: + return AWS_IO_TLS_BAD_CERTIFICATE; + case errSSLPeerCertExpired: + return AWS_IO_TLS_PEER_CERTIFICATE_EXPIRED; + case errSSLPeerBadCert: + return AWS_IO_TLS_BAD_PEER_CERTIFICATE; + case errSSLPeerCertRevoked: + return AWS_IO_TLS_PEER_CERTIFICATE_REVOKED; + case errSSLPeerCertUnknown: + return AWS_IO_TLS_PEER_CERTIFICATE_UNKNOWN; + case errSSLInternal: + return AWS_IO_TLS_INTERNAL_ERROR; + case errSSLClosedGraceful: + return AWS_IO_TLS_CLOSED_GRACEFUL; + case errSSLClosedAbort: + return AWS_IO_TLS_CLOSED_ABORT; + case errSSLXCertChainInvalid: + return AWS_IO_TLS_INVALID_CERTIFICATE_CHAIN; + case errSSLHostNameMismatch: + return AWS_IO_TLS_HOST_NAME_MISSMATCH; + case errSecNotTrusted: + case errSSLPeerProtocolVersion: + return AWS_IO_TLS_ERROR_NEGOTIATION_FAILURE; + default: return AWS_IO_SOCKET_NOT_CONNECTED; } @@ -120,8 +177,8 @@ struct nw_socket { struct aws_allocator *allocator; struct aws_ref_count ref_count; nw_connection_t nw_connection; + nw_parameters_t nw_parameters; nw_listener_t nw_listener; - nw_parameters_t socket_options_to_params; struct aws_linked_list read_queue; int last_error; aws_socket_on_readable_fn *on_readable; @@ -133,6 +190,10 @@ struct nw_socket { struct nw_socket_timeout_args *timeout_args; aws_socket_on_connection_result_fn *on_connection_result_fn; void *connect_accept_user_data; + struct aws_string *host_name; + struct aws_string *alpn_list; + struct aws_tls_ctx *tls_ctx; + struct aws_byte_buf protocol_buf; struct { struct aws_mutex lock; @@ -151,46 +212,326 @@ struct socket_address { static size_t KB_16 = 16 * 1024; -static int s_setup_socket_params(struct nw_socket *nw_socket, const struct aws_socket_options *options) { - if (options->type == AWS_SOCKET_STREAM) { - /* if TCP, setup all the tcp options */ - if (options->domain == AWS_SOCKET_IPV4 || options->domain == AWS_SOCKET_IPV6) { - // DEBUG WIP NW_PARAMETERS_DISABLE_PROTOCOL will need to be changed to use MTLS With SecItem - nw_socket->socket_options_to_params = - nw_parameters_create_secure_tcp(NW_PARAMETERS_DISABLE_PROTOCOL, ^(nw_protocol_options_t nw_options) { - if (options->connect_timeout_ms) { - /* this value gets set in seconds. */ - nw_tcp_options_set_connection_timeout( - nw_options, options->connect_timeout_ms / AWS_TIMESTAMP_MILLIS); - } +/* setup the TCP options Block for use in socket parameters */ +static void s_setup_tcp_options(nw_protocol_options_t tcp_options, const struct aws_socket_options *options) { + if (options->connect_timeout_ms) { + /* this value gets set in seconds. */ + nw_tcp_options_set_connection_timeout(tcp_options, options->connect_timeout_ms / AWS_TIMESTAMP_MILLIS); + } - // Only change default keepalive values if keepalive is true and both interval and timeout - // are not zero. - if (options->keepalive && options->keep_alive_interval_sec != 0 && - options->keep_alive_timeout_sec != 0) { - nw_tcp_options_set_enable_keepalive(nw_options, options->keepalive); - nw_tcp_options_set_keepalive_idle_time(nw_options, options->keep_alive_timeout_sec); - nw_tcp_options_set_keepalive_interval(nw_options, options->keep_alive_interval_sec); - } + /* Only change default keepalive values if keepalive is true and both interval and timeout + * are not zero. */ + if (options->keepalive && options->keep_alive_interval_sec != 0 && options->keep_alive_timeout_sec != 0) { + nw_tcp_options_set_enable_keepalive(tcp_options, options->keepalive); + nw_tcp_options_set_keepalive_idle_time(tcp_options, options->keep_alive_timeout_sec); + nw_tcp_options_set_keepalive_interval(tcp_options, options->keep_alive_interval_sec); + } - if (options->keep_alive_max_failed_probes) { - nw_tcp_options_set_keepalive_count(nw_options, options->keep_alive_max_failed_probes); - } + if (options->keep_alive_max_failed_probes) { + nw_tcp_options_set_keepalive_count(tcp_options, options->keep_alive_max_failed_probes); + } + + if (g_aws_channel_max_fragment_size < KB_16) { + nw_tcp_options_set_maximum_segment_size(tcp_options, g_aws_channel_max_fragment_size); + } +} + +static void s_setup_tcp_options_local(nw_protocol_options_t tcp_options, const struct aws_socket_options *options) { + (void)tcp_options; + (void)options; +} + +static void s_setup_tls_options( + nw_protocol_options_t tls_options, + const struct aws_socket_options *options, + struct nw_socket *nw_socket, + struct secure_transport_ctx *transport_ctx) { + /* Obtain the security protocol options from the tls_options. Changes made directly + * to the copy will impact the protocol options within the tls_options */ + sec_protocol_options_t sec_options = nw_tls_copy_sec_protocol_options(tls_options); + + sec_protocol_options_set_local_identity(sec_options, transport_ctx->secitem_identity); + + // Set the minimum TLS version + switch (transport_ctx->minimum_tls_version) { + case AWS_IO_TLSv1_2: + sec_protocol_options_set_min_tls_protocol_version(sec_options, tls_protocol_version_TLSv12); + break; + case AWS_IO_TLSv1_3: + sec_protocol_options_set_min_tls_protocol_version(sec_options, tls_protocol_version_TLSv13); + break; + case AWS_IO_TLS_VER_SYS_DEFAULTS: + /* not assigning a min tls protocol version automatically uses the + * system default version. */ + break; + default: + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKET, + "id=%p options=%p: Unrecognized minimum TLS version used for parameter creation. " + "System default minimum TLS version will be used.", + (void *)nw_socket, + (void *)options); + break; + } + + /* Enable/Disable peer authentication. This setting is ignored by network framework due to our + * implementation of the verification block below but we set it in case anything else checks this + * value and/or in case we decide to remove the verify block in the future. */ + sec_protocol_options_set_peer_authentication_required(sec_options, transport_ctx->verify_peer); + + if (nw_socket->host_name != NULL) { + sec_protocol_options_set_tls_server_name(sec_options, (const char *)nw_socket->host_name->bytes); + } + + // Add alpn protocols + if (nw_socket->alpn_list != NULL) { + AWS_LOGF_DEBUG( + AWS_LS_IO_TLS, "id=%p: Setting ALPN list %s", (void *)nw_socket, aws_string_c_str(nw_socket->alpn_list)); + + struct aws_byte_cursor alpn_data = aws_byte_cursor_from_string(nw_socket->alpn_list); + struct aws_array_list alpn_list_array; + if (aws_array_list_init_dynamic(&alpn_list_array, nw_socket->allocator, 2, sizeof(struct aws_byte_cursor))) { + AWS_LOGF_ERROR(AWS_LS_IO_TLS, "id=%p: Failed to setup array list for ALPN setup.", (void *)nw_socket); + return; + } + + if (aws_byte_cursor_split_on_char(&alpn_data, ';', &alpn_list_array)) { + AWS_LOGF_ERROR(AWS_LS_IO_TLS, "id=%p: Failed to split alpn_list on character ';'.", (void *)nw_socket); + return; + } + + for (size_t i = 0; i < aws_array_list_length(&alpn_list_array); ++i) { + struct aws_byte_cursor protocol_cursor; + aws_array_list_get_at(&alpn_list_array, &protocol_cursor, i); + + struct aws_string *protocol_string = aws_string_new_from_cursor(nw_socket->allocator, &protocol_cursor); + + sec_protocol_options_add_tls_application_protocol(sec_options, aws_string_c_str(protocol_string)); + aws_string_destroy(protocol_string); + } + aws_array_list_clean_up(&alpn_list_array); + } + + aws_mutex_lock(&nw_socket->synced_data.lock); + struct dispatch_loop *dispatch_loop = nw_socket->synced_data.event_loop->impl_data; + aws_mutex_unlock(&nw_socket->synced_data.lock); + + /* We handle the verification of the remote end here. */ + sec_protocol_options_set_verify_block( + sec_options, + ^(sec_protocol_metadata_t metadata, sec_trust_t trust, sec_protocol_verify_complete_t complete) { + (void)metadata; + + CFErrorRef error = NULL; + SecPolicyRef policy = NULL; + int error_code = AWS_ERROR_SUCCESS; + SecTrustRef trust_ref = NULL; + OSStatus status; + bool verification_successful = false; + + /* Since we manually handle the verification of the peer, the value set using + * sec_protocol_options_set_peer_authentication_required is ignored and this block is + * run instead. We manually skip the verification at this point if verify_peer is false. */ + if (!transport_ctx->verify_peer) { + AWS_LOGF_WARN( + AWS_LS_IO_TLS, + "id=%p: x.509 validation has been disabled. " + "If this is not running in a test environment, this is likely a security " + "vulnerability.", + (void *)nw_socket); + verification_successful = true; + goto verification_done; + } + + trust_ref = sec_trust_copy_ref(trust); + + /* Use root ca if provided. */ + if (transport_ctx->ca_cert != NULL) { + AWS_LOGF_DEBUG( + AWS_LS_IO_TLS, + "id=%p: nw_socket verify block applying provided root CA for remote verification.", + (void *)nw_socket); + // We add the ca certificate as a anchor certificate in the trust_ref + status = SecTrustSetAnchorCertificates(trust_ref, transport_ctx->ca_cert); + if (status != errSecSuccess) { + AWS_LOGF_ERROR( + AWS_LS_IO_TLS, + "id=%p: nw_socket verify block SecTrustSetAnchorCertificates failed with " + "OSStatus: %d", + (void *)nw_socket, + (int)status); + error_code = aws_raise_error(AWS_IO_TLS_ERROR_NEGOTIATION_FAILURE); + goto verification_done; + } + } + + /* Add the host name to be checked against the available Certificate Authorities */ + if (nw_socket->host_name != NULL) { + CFStringRef server_name = CFStringCreateWithCString( + transport_ctx->wrapped_allocator, aws_string_c_str(nw_socket->host_name), kCFStringEncodingUTF8); + policy = SecPolicyCreateSSL(true, server_name); + CFRelease(server_name); + } else { + policy = SecPolicyCreateBasicX509(); + } + + status = SecTrustSetPolicies(trust_ref, policy); + if (status != errSecSuccess) { + AWS_LOGF_ERROR(AWS_LS_IO_TLS, "id=%p: Failed to set trust policy %d\n", (void *)nw_socket, (int)status); + error_code = aws_raise_error(AWS_IO_TLS_ERROR_NEGOTIATION_FAILURE); + goto verification_done; + } - if (g_aws_channel_max_fragment_size < KB_16) { - nw_tcp_options_set_maximum_segment_size(nw_options, g_aws_channel_max_fragment_size); + SecTrustResultType trust_result; + + /* verify peer */ + bool success = SecTrustEvaluateWithError(trust_ref, &error); + if (success) { + status = SecTrustGetTrustResult(trust_ref, &trust_result); + if (status == errSecSuccess) { + AWS_LOGF_DEBUG( + AWS_LS_IO_TLS, + "id=%p: nw_socket verify block trust result: %s", + (void *)nw_socket, + aws_sec_trust_result_type_to_string(trust_result)); + + // Proceed based on the trust_result if necessary + if (trust_result == kSecTrustResultProceed || trust_result == kSecTrustResultUnspecified) { + verification_successful = true; + } else { + verification_successful = false; } - }); + } else { + AWS_LOGF_DEBUG( + AWS_LS_IO_TLS, + "id=%p: nw_socket SecTrustGetTrustResult failed with OSStatus: %d", + (void *)nw_socket, + (int)status); + verification_successful = false; + } + } else { + CFStringRef error_description = CFErrorCopyDescription(error); + char description_buffer[256]; + CFStringGetCString( + error_description, description_buffer, sizeof(description_buffer), kCFStringEncodingUTF8); + int crt_error_code = s_determine_socket_error(CFErrorGetCode(error)); + CFRelease(error_description); + AWS_LOGF_DEBUG( + AWS_LS_IO_TLS, + "id=%p: nw_socket SecTrustEvaluateWithError failed with error code: %d CF error " + "code: %ld : %s", + (void *)nw_socket, + crt_error_code, + (long)CFErrorGetCode(error), + description_buffer); + verification_successful = false; + } + + verification_done: + if (policy) { + CFRelease(policy); + } + if (trust_ref) { + CFRelease(trust_ref); + } + if (error) { + error_code = CFErrorGetCode(error); + error_code = s_determine_socket_error(error_code); + nw_socket->last_error = error_code; + aws_raise_error(error_code); + CFRelease(error); + } + complete(verification_successful); + }, + dispatch_loop->dispatch_queue); +} + +static int s_setup_socket_params(struct nw_socket *nw_socket, const struct aws_socket_options *options) { + + /* If we already have parameters set, release them before re-establishing new parameters */ + if (nw_socket->nw_parameters != NULL) { + nw_release(nw_socket->nw_parameters); + nw_socket->nw_parameters = NULL; + } + + bool setup_tls = false; + struct secure_transport_ctx *transport_ctx = NULL; + +#ifdef AWS_USE_SECITEM + if (nw_socket->tls_ctx) { + setup_tls = true; + } +#endif /* AWS_USE_SECITEM*/ + + if (setup_tls) { + transport_ctx = nw_socket->tls_ctx->impl; + + /* This check cannot be done within the TLS options block and must be handled here. */ + if (transport_ctx->minimum_tls_version == AWS_IO_SSLv3 || transport_ctx->minimum_tls_version == AWS_IO_TLSv1 || + transport_ctx->minimum_tls_version == AWS_IO_TLSv1_1) { + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKET, + "id=%p options=%p: Selected minimum tls version not supported by Apple Network Framework due " + "to deprecated status and known security flaws.", + (void *)nw_socket, + (void *)options); + return aws_raise_error(AWS_IO_SOCKET_INVALID_OPTIONS); + } + } + + if (options->type == AWS_SOCKET_STREAM) { + if (options->domain == AWS_SOCKET_IPV4 || options->domain == AWS_SOCKET_IPV6) { + if (setup_tls) { + nw_socket->nw_parameters = nw_parameters_create_secure_tcp( + // TLS options block + ^(nw_protocol_options_t tls_options) { + s_setup_tls_options(tls_options, options, nw_socket, transport_ctx); + }, + // TCP options block + ^(nw_protocol_options_t tcp_options) { + s_setup_tcp_options(tcp_options, options); + }); + } else { + // TLS options are not set and the TLS options block should be disabled. + nw_socket->nw_parameters = nw_parameters_create_secure_tcp( + // TLS options Block disabled + NW_PARAMETERS_DISABLE_PROTOCOL, + // TCP options Block + ^(nw_protocol_options_t tcp_options) { + s_setup_tcp_options(tcp_options, options); + }); + } } else if (options->domain == AWS_SOCKET_LOCAL) { - nw_socket->socket_options_to_params = - nw_parameters_create_secure_tcp(NW_PARAMETERS_DISABLE_PROTOCOL, NW_PARAMETERS_DEFAULT_CONFIGURATION); + if (setup_tls) { + nw_socket->nw_parameters = nw_parameters_create_secure_tcp( + // TLS options block + ^(nw_protocol_options_t tls_options) { + s_setup_tls_options(tls_options, options, nw_socket, transport_ctx); + }, + // TCP options block + ^(nw_protocol_options_t tcp_options) { + s_setup_tcp_options_local(tcp_options, options); + }); + + } else { + nw_socket->nw_parameters = nw_parameters_create_secure_tcp( + NW_PARAMETERS_DISABLE_PROTOCOL, + // TCP options Block + ^(nw_protocol_options_t tcp_options) { + s_setup_tcp_options_local(tcp_options, options); + }); + } } } else if (options->type == AWS_SOCKET_DGRAM) { - nw_socket->socket_options_to_params = - nw_parameters_create_secure_udp(NW_PARAMETERS_DISABLE_PROTOCOL, NW_PARAMETERS_DEFAULT_CONFIGURATION); + nw_socket->nw_parameters = nw_parameters_create_secure_udp( + NW_PARAMETERS_DISABLE_PROTOCOL, + // TCP options Block + ^(nw_protocol_options_t tcp_options) { + s_setup_tcp_options_local(tcp_options, options); + }); } - if (!nw_socket->socket_options_to_params) { + if (!nw_socket->nw_parameters) { AWS_LOGF_ERROR( AWS_LS_IO_SOCKET, "id=%p options=%p: failed to create nw_parameters_t for nw_socket.", @@ -198,8 +539,8 @@ static int s_setup_socket_params(struct nw_socket *nw_socket, const struct aws_s (void *)options); return aws_raise_error(AWS_IO_SOCKET_INVALID_OPTIONS); } - - nw_parameters_set_reuse_local_address(nw_socket->socket_options_to_params, true); + /* allow a local address to be used by multiple parameters. */ + nw_parameters_set_reuse_local_address(nw_socket->nw_parameters, true); return AWS_OP_SUCCESS; } @@ -210,8 +551,13 @@ static int s_socket_connect_fn( const struct aws_socket_endpoint *remote_endpoint, struct aws_event_loop *event_loop, aws_socket_on_connection_result_fn *on_connection_result, + aws_socket_retrieve_tls_options_fn *retrieve_tls_options, + void *user_data); +static int s_socket_bind_fn( + struct aws_socket *socket, + const struct aws_socket_endpoint *local_endpoint, + aws_socket_retrieve_tls_options_fn *retrieve_tls_options, void *user_data); -static int s_socket_bind_fn(struct aws_socket *socket, const struct aws_socket_endpoint *local_endpoint); static int s_socket_listen_fn(struct aws_socket *socket, int backlog_size); static int s_socket_start_accept_fn( struct aws_socket *socket, @@ -235,6 +581,8 @@ static int s_socket_write_fn( void *user_data); static int s_socket_get_error_fn(struct aws_socket *socket); static bool s_socket_is_open_fn(struct aws_socket *socket); +static struct aws_byte_buf s_socket_get_protocol_fn(const struct aws_socket *socket); +static struct aws_string *s_socket_get_server_name_fn(const struct aws_socket *socket); static struct aws_socket_vtable s_vtable = { .socket_cleanup_fn = s_socket_cleanup_fn, @@ -252,6 +600,8 @@ static struct aws_socket_vtable s_vtable = { .socket_write_fn = s_socket_write_fn, .socket_get_error_fn = s_socket_get_error_fn, .socket_is_open_fn = s_socket_is_open_fn, + .socket_get_protocol_fn = s_socket_get_protocol_fn, + .socket_get_server_name_fn = s_socket_get_server_name_fn, }; static void s_schedule_next_read(struct nw_socket *socket); @@ -304,9 +654,9 @@ static void s_socket_impl_destroy(void *sock_ptr) { } /* Network Framework cleanup */ - if (nw_socket->socket_options_to_params) { - nw_release(nw_socket->socket_options_to_params); - nw_socket->socket_options_to_params = NULL; + if (nw_socket->nw_parameters) { + nw_release(nw_socket->nw_parameters); + nw_socket->nw_parameters = NULL; } if (nw_socket->nw_connection) { @@ -314,6 +664,21 @@ static void s_socket_impl_destroy(void *sock_ptr) { nw_socket->nw_connection = NULL; } + if (nw_socket->host_name) { + aws_string_destroy(nw_socket->host_name); + } + + if (nw_socket->alpn_list) { + aws_string_destroy(nw_socket->alpn_list); + } + + aws_byte_buf_clean_up(&nw_socket->protocol_buf); + + if (nw_socket->tls_ctx) { + aws_tls_ctx_release(nw_socket->tls_ctx); + nw_socket->tls_ctx = NULL; + } + if (nw_socket->nw_listener) { nw_release(nw_socket->nw_listener); nw_socket->nw_listener = NULL; @@ -325,6 +690,7 @@ static void s_socket_impl_destroy(void *sock_ptr) { nw_socket = NULL; } +#if defined(AWS_ENABLE_DISPATCH_QUEUE) int aws_socket_init_apple_nw_socket( struct aws_socket *socket, struct aws_allocator *alloc, @@ -366,15 +732,13 @@ int aws_socket_init_apple_nw_socket( nw_socket->synced_data.base_socket = socket; aws_mutex_unlock(&nw_socket->synced_data.lock); - if (s_setup_socket_params(nw_socket, options)) { - aws_mem_release(alloc, nw_socket); - return AWS_OP_ERR; - } aws_ref_count_init(&nw_socket->ref_count, nw_socket, s_socket_impl_destroy); + aws_linked_list_init(&nw_socket->read_queue); return AWS_OP_SUCCESS; } +#endif // AWS_ENABLE_DISPATCH_QUEUE static void s_client_set_dispatch_queue(struct aws_io_handle *handle, void *queue) { nw_connection_set_queue(handle->data.handle, queue); @@ -480,14 +844,14 @@ static void s_schedule_on_readable(struct nw_socket *nw_socket, int error_code, } aws_ref_count_acquire(&nw_socket->ref_count); - aws_task_init(task, s_process_readable_task, args, "readableTask"); + aws_task_init(task, s_process_readable_task, args, "process_readable"); aws_event_loop_schedule_task_now(nw_socket->synced_data.event_loop, task); } aws_mutex_unlock(&nw_socket->synced_data.lock); } -static void s_process_connection_success_task(struct aws_task *task, void *arg, enum aws_task_status status) { +static void s_process_connection_result_task(struct aws_task *task, void *arg, enum aws_task_status status) { (void)status; struct nw_socket_scheduled_task_args *task_args = arg; @@ -506,7 +870,7 @@ static void s_process_connection_success_task(struct aws_task *task, void *arg, aws_mem_release(task_args->allocator, task_args); } -static void s_schedule_on_connection_success(struct nw_socket *nw_socket, int error_code) { +static void s_schedule_on_connection_result(struct nw_socket *nw_socket, int error_code) { aws_mutex_lock(&nw_socket->synced_data.lock); struct aws_socket *socket = nw_socket->synced_data.base_socket; @@ -520,7 +884,7 @@ static void s_schedule_on_connection_success(struct nw_socket *nw_socket, int er args->allocator = socket->allocator; args->error_code = error_code; aws_ref_count_acquire(&nw_socket->ref_count); - aws_task_init(task, s_process_connection_success_task, args, "connectionSuccessTask"); + aws_task_init(task, s_process_connection_result_task, args, "on_connection_result"); aws_event_loop_schedule_task_now(nw_socket->synced_data.event_loop, task); } @@ -631,7 +995,7 @@ static void s_schedule_on_listener_success( aws_ref_count_acquire(&nw_socket->ref_count); nw_retain(new_connection); - aws_task_init(task, s_process_listener_success_task, args, "listenerSuccessTask"); + aws_task_init(task, s_process_listener_success_task, args, "on_listener_success"); aws_event_loop_schedule_task_now(nw_socket->synced_data.event_loop, task); } aws_mutex_unlock(&nw_socket->synced_data.lock); @@ -668,8 +1032,13 @@ static void s_schedule_cancel_task(struct nw_socket *nw_socket, struct aws_task args->allocator = nw_socket->allocator; args->task_to_cancel = task_to_cancel; aws_ref_count_acquire(&nw_socket->ref_count); - aws_task_init(task, s_process_cancel_task, args, "cancelTaskTask"); - AWS_LOGF_TRACE(AWS_LS_IO_SOCKET, "id=%p: Schedule cancel %s task", (void *)task_to_cancel, task->type_tag); + aws_task_init(task, s_process_cancel_task, args, "cancel_socket_timeout"); + AWS_LOGF_TRACE( + AWS_LS_IO_SOCKET, + "id=%p: Schedule %s task to cancel %s task", + (void *)task_to_cancel, + task->type_tag, + task_to_cancel->type_tag); aws_event_loop_schedule_task_now(nw_socket->synced_data.event_loop, task); } @@ -715,7 +1084,7 @@ static void s_schedule_write_fn( args->user_data = user_data; args->bytes_written = bytes_written; aws_ref_count_acquire(&nw_socket->ref_count); - aws_task_init(task, s_process_write_task, args, "writtenTask"); + aws_task_init(task, s_process_write_task, args, "process_write"); aws_mutex_lock(&nw_socket->synced_data.lock); if (nw_socket->synced_data.event_loop) { aws_event_loop_schedule_task_now(nw_socket->synced_data.event_loop, task); @@ -724,24 +1093,91 @@ static void s_schedule_write_fn( aws_mutex_unlock(&nw_socket->synced_data.lock); } +static int s_setup_tls_options_from_context( + struct nw_socket *nw_socket, + struct tls_connection_context *tls_connection_context) { + if (tls_connection_context->host_name != NULL) { + if (nw_socket->host_name != NULL) { + aws_string_destroy(nw_socket->host_name); + nw_socket->host_name = NULL; + } + nw_socket->host_name = + aws_string_new_from_string(tls_connection_context->host_name->allocator, tls_connection_context->host_name); + if (nw_socket->host_name == NULL) { + return AWS_OP_ERR; + } + } + + if (tls_connection_context->tls_ctx != NULL) { + struct aws_string *alpn_list = NULL; + struct secure_transport_ctx *transport_ctx = tls_connection_context->tls_ctx->impl; + if (tls_connection_context->alpn_list != NULL) { + alpn_list = tls_connection_context->alpn_list; + } else if (transport_ctx->alpn_list != NULL) { + alpn_list = transport_ctx->alpn_list; + } + + if (alpn_list != NULL) { + if (nw_socket->alpn_list != NULL) { + aws_string_destroy(nw_socket->alpn_list); + nw_socket->alpn_list = NULL; + } + nw_socket->alpn_list = aws_string_new_from_string(alpn_list->allocator, alpn_list); + if (nw_socket->alpn_list == NULL) { + return AWS_OP_ERR; + } + } + + if (nw_socket->tls_ctx) { + aws_tls_ctx_release(nw_socket->tls_ctx); + nw_socket->tls_ctx = NULL; + } + nw_socket->tls_ctx = tls_connection_context->tls_ctx; + aws_tls_ctx_acquire(nw_socket->tls_ctx); + } + + return AWS_OP_SUCCESS; +} + static int s_socket_connect_fn( struct aws_socket *socket, const struct aws_socket_endpoint *remote_endpoint, struct aws_event_loop *event_loop, aws_socket_on_connection_result_fn *on_connection_result, + aws_socket_retrieve_tls_options_fn *retrieve_tls_options, void *user_data) { struct nw_socket *nw_socket = socket->impl; AWS_ASSERT(event_loop); AWS_ASSERT(!socket->event_loop); - AWS_LOGF_DEBUG( - AWS_LS_IO_SOCKET, "id=%p handle=%p: beginning connect.", (void *)socket, socket->io_handle.data.handle); - if (socket->event_loop) { return aws_raise_error(AWS_IO_EVENT_LOOP_ALREADY_ASSIGNED); } + if (retrieve_tls_options != NULL) { + struct tls_connection_context tls_connection_context; + AWS_ZERO_STRUCT(tls_connection_context); + retrieve_tls_options(&tls_connection_context, user_data); + + if (s_setup_tls_options_from_context(nw_socket, &tls_connection_context)) { + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKET, "id=%p: Error encounterd during setup of tls options from context.", (void *)socket); + return aws_last_error(); + } + } + + aws_mutex_lock(&nw_socket->synced_data.lock); + nw_socket->synced_data.event_loop = event_loop; + aws_mutex_unlock(&nw_socket->synced_data.lock); + + if (s_setup_socket_params(nw_socket, &socket->options)) { + return AWS_OP_ERR; + } + + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKET, "id=%p handle=%p: beginning connect.", (void *)socket, socket->io_handle.data.handle); + if (socket->options.type != AWS_SOCKET_DGRAM) { AWS_ASSERT(on_connection_result); if (socket->state != INIT) { @@ -778,7 +1214,6 @@ static int s_socket_connect_fn( address.sock_addr_types.un_addr.sun_family = AF_UNIX; strncpy(address.sock_addr_types.un_addr.sun_path, remote_endpoint->address, AWS_ADDRESS_MAX_LEN); address.sock_addr_types.un_addr.sun_len = sizeof(struct sockaddr_un); - } else { AWS_ASSERT(0); return aws_raise_error(AWS_IO_SOCKET_UNSUPPORTED_ADDRESS_FAMILY); @@ -821,7 +1256,7 @@ static int s_socket_connect_fn( return aws_raise_error(AWS_IO_SOCKET_INVALID_ADDRESS); } - socket->io_handle.data.handle = nw_connection_create(endpoint, nw_socket->socket_options_to_params); + socket->io_handle.data.handle = nw_connection_create(endpoint, nw_socket->nw_parameters); nw_socket->nw_connection = socket->io_handle.data.handle; nw_release(endpoint); @@ -853,11 +1288,7 @@ static int s_socket_connect_fn( &nw_socket->timeout_args->task, s_handle_socket_timeout, nw_socket->timeout_args, - "NWSocketConnectionTimeoutTask"); - - aws_mutex_lock(&nw_socket->synced_data.lock); - nw_socket->synced_data.event_loop = event_loop; - aws_mutex_unlock(&nw_socket->synced_data.lock); + "NW_socket_connection_timeout"); /* set a handler for socket state changes. This is where we find out if the connection timed out, was successful, * was disconnected etc .... */ @@ -898,10 +1329,40 @@ static int s_socket_connect_fn( nw_socket->timeout_args->connection_succeed = true; s_schedule_cancel_task(nw_socket, &nw_socket->timeout_args->task); } + + /* Check and store protocol for connection */ + if (nw_socket->tls_ctx) { + nw_protocol_metadata_t metadata = nw_connection_copy_protocol_metadata( + socket->io_handle.data.handle, nw_protocol_copy_tls_definition()); + if (metadata != NULL) { + sec_protocol_metadata_t sec_metadata = (sec_protocol_metadata_t)metadata; + + const char *negotiated_protocol = sec_protocol_metadata_get_negotiated_protocol(sec_metadata); + if (negotiated_protocol) { + nw_socket->protocol_buf.allocator = nw_socket->allocator; + size_t protocol_len = strlen(negotiated_protocol); + nw_socket->protocol_buf.buffer = + (uint8_t *)aws_mem_acquire(nw_socket->allocator, protocol_len + 1); + nw_socket->protocol_buf.len = protocol_len; + nw_socket->protocol_buf.capacity = protocol_len + 1; + memcpy(nw_socket->protocol_buf.buffer, negotiated_protocol, protocol_len); + nw_socket->protocol_buf.buffer[protocol_len] = '\0'; + + AWS_LOGF_DEBUG( + AWS_LS_IO_TLS, + "id=%p handle=%p: ALPN protocol set to: '%s'", + (void *)socket, + socket->io_handle.data.handle, + nw_socket->protocol_buf.buffer); + } + nw_release(metadata); + } + } + socket->state = CONNECTED_WRITE | CONNECTED_READ; nw_socket->setup_run = true; aws_ref_count_acquire(&nw_socket->ref_count); - s_schedule_on_connection_success(nw_socket, AWS_OP_SUCCESS); + s_schedule_on_connection_result(nw_socket, AWS_OP_SUCCESS); s_schedule_next_read(nw_socket); aws_ref_count_release(&nw_socket->ref_count); @@ -924,7 +1385,7 @@ static int s_socket_connect_fn( aws_raise_error(error_code); socket->state = ERROR; if (!nw_socket->setup_run) { - s_schedule_on_connection_success(nw_socket, error_code); + s_schedule_on_connection_result(nw_socket, error_code); nw_socket->setup_run = true; } else if (socket->readable_fn) { s_schedule_on_readable(nw_socket, nw_socket->last_error, NULL); @@ -946,7 +1407,7 @@ static int s_socket_connect_fn( socket->state = CLOSED; aws_raise_error(AWS_IO_SOCKET_CLOSED); if (!nw_socket->setup_run) { - s_schedule_on_connection_success(nw_socket, AWS_IO_SOCKET_CLOSED); + s_schedule_on_connection_result(nw_socket, AWS_IO_SOCKET_CLOSED); nw_socket->setup_run = true; } else if (socket->readable_fn) { s_schedule_on_readable(nw_socket, AWS_IO_SOCKET_CLOSED, NULL); @@ -996,7 +1457,11 @@ static int s_socket_connect_fn( return AWS_OP_SUCCESS; } -static int s_socket_bind_fn(struct aws_socket *socket, const struct aws_socket_endpoint *local_endpoint) { +static int s_socket_bind_fn( + struct aws_socket *socket, + const struct aws_socket_endpoint *local_endpoint, + aws_socket_retrieve_tls_options_fn *retrieve_tls_options, + void *user_data) { struct nw_socket *nw_socket = socket->impl; if (socket->state != INIT) { @@ -1004,6 +1469,15 @@ static int s_socket_bind_fn(struct aws_socket *socket, const struct aws_socket_e return aws_raise_error(AWS_IO_SOCKET_ILLEGAL_OPERATION_FOR_STATE); } + size_t address_strlen; + if (aws_secure_strlen(local_endpoint->address, AWS_ADDRESS_MAX_LEN, &address_strlen)) { + return AWS_OP_ERR; + } + + if (aws_socket_validate_port_for_bind(local_endpoint->port, socket->options.domain)) { + return AWS_OP_ERR; + } + socket->local_endpoint = *local_endpoint; AWS_LOGF_INFO( AWS_LS_IO_SOCKET, @@ -1012,6 +1486,29 @@ static int s_socket_bind_fn(struct aws_socket *socket, const struct aws_socket_e local_endpoint->address, (int)local_endpoint->port); + if (nw_socket->nw_parameters == NULL) { + if (retrieve_tls_options) { + struct tls_connection_context tls_connection_context; + AWS_ZERO_STRUCT(tls_connection_context); + retrieve_tls_options(&tls_connection_context, user_data); + + if (s_setup_tls_options_from_context(nw_socket, &tls_connection_context)) { + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKET, + "id=%p: Error encounterd during setup of tls options from context.", + (void *)socket); + return aws_last_error(); + } + + if (tls_connection_context.event_loop) { + aws_mutex_lock(&nw_socket->synced_data.lock); + nw_socket->synced_data.event_loop = tls_connection_context.event_loop; + aws_mutex_unlock(&nw_socket->synced_data.lock); + } + } + s_setup_socket_params(nw_socket, &socket->options); + } + struct socket_address address; AWS_ZERO_STRUCT(address); int pton_err = 1; @@ -1051,13 +1548,19 @@ static int s_socket_bind_fn(struct aws_socket *socket, const struct aws_socket_e return aws_raise_error(AWS_IO_SOCKET_INVALID_ADDRESS); } - nw_parameters_set_local_endpoint(nw_socket->socket_options_to_params, endpoint); + nw_parameters_set_local_endpoint(nw_socket->nw_parameters, endpoint); nw_release(endpoint); // Apple network framework requires connection besides bind. socket->state = BOUND; - AWS_LOGF_DEBUG(AWS_LS_IO_SOCKET, "id=%p: successfully bound", (void *)socket); + AWS_LOGF_DEBUG( + AWS_LS_IO_SOCKET, + "id=%p fd=%d: successfully bound to %s:%u", + (void *)socket, + socket->io_handle.data.fd, + socket->local_endpoint.address, + socket->local_endpoint.port); return AWS_OP_SUCCESS; } @@ -1083,17 +1586,26 @@ static int s_socket_listen_fn(struct aws_socket *socket, int backlog_size) { return aws_raise_error(AWS_IO_SOCKET_ILLEGAL_OPERATION_FOR_STATE); } - socket->io_handle.data.handle = nw_listener_create(nw_socket->socket_options_to_params); - nw_socket->nw_listener = socket->io_handle.data.handle; - nw_retain(socket->io_handle.data.handle); - nw_socket->is_listener = true; + if (nw_socket->nw_parameters == NULL) { + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKET, + "id=%p: socket nw_parameters needs to be set before creating a listener from socket.", + (void *)socket); + return aws_raise_error(AWS_IO_SOCKET_INVALID_OPTIONS); + } + socket->io_handle.data.handle = nw_listener_create(nw_socket->nw_parameters); if (!socket->io_handle.data.handle) { - AWS_LOGF_ERROR(AWS_LS_IO_SOCKET, "id=%p: listen failed with error code %d", (void *)socket, aws_last_error()); + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKET, "id=%p: listener creation failed with error code %d", (void *)socket, aws_last_error()); socket->state = ERROR; return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); } + nw_socket->nw_listener = socket->io_handle.data.handle; + nw_retain(socket->io_handle.data.handle); + nw_socket->is_listener = true; + socket->io_handle.set_queue = s_listener_set_dispatch_queue; socket->io_handle.clear_queue = s_listener_clear_dispatch_queue; @@ -1171,7 +1683,7 @@ static int s_socket_start_accept_fn( } else if (state == nw_listener_state_failed) { AWS_LOGF_DEBUG( AWS_LS_IO_SOCKET, - "id=%p handle=%p: lisnter on port failed ", + "id=%p handle=%p: listener on port failed ", (void *)socket, socket->io_handle.data.handle); /* any error, including if closed remotely in error */ @@ -1185,7 +1697,7 @@ static int s_socket_start_accept_fn( } else if (state == nw_listener_state_ready) { AWS_LOGF_DEBUG( AWS_LS_IO_SOCKET, - "id=%p handle=%p: lisnter on port ready ", + "id=%p handle=%p: listener on port ready ", (void *)socket, (void *)nw_socket->nw_connection); @@ -1199,7 +1711,7 @@ static int s_socket_start_accept_fn( // acquire ref count for the task aws_ref_count_acquire(&nw_socket->ref_count); - aws_task_init(task, s_process_set_listener_endpoint_task, args, "listenerSuccessTask"); + aws_task_init(task, s_process_set_listener_endpoint_task, args, "set_listener_endpoint"); aws_event_loop_schedule_task_now(socket->event_loop, task); } else if (state == nw_listener_state_cancelled) { @@ -1276,7 +1788,8 @@ static int s_socket_shutdown_dir_fn(struct aws_socket *socket, enum aws_channel_ static int s_socket_set_options_fn(struct aws_socket *socket, const struct aws_socket_options *options) { if (socket->options.domain != options->domain || socket->options.type != options->type) { - return aws_raise_error(AWS_IO_SOCKET_INVALID_OPTIONS); + aws_raise_error(AWS_IO_SOCKET_INVALID_OPTIONS); + return AWS_OP_ERR; } AWS_LOGF_DEBUG( @@ -1293,15 +1806,7 @@ static int s_socket_set_options_fn(struct aws_socket *socket, const struct aws_s socket->options = *options; - struct nw_socket *nw_socket = socket->impl; - - /* If nw_parameters_t has been previously set, they need to be released prior to assinging a new one */ - if (nw_socket->socket_options_to_params) { - nw_release(nw_socket->socket_options_to_params); - nw_socket->socket_options_to_params = NULL; - } - - return s_setup_socket_params(nw_socket, options); + return AWS_OP_SUCCESS; } static int s_socket_assign_to_event_loop_fn(struct aws_socket *socket, struct aws_event_loop *event_loop) { @@ -1591,3 +2096,13 @@ static bool s_socket_is_open_fn(struct aws_socket *socket) { return nw_socket->last_error == AWS_OP_SUCCESS; } + +static struct aws_byte_buf s_socket_get_protocol_fn(const struct aws_socket *socket) { + struct nw_socket *nw_socket = socket->impl; + return nw_socket->protocol_buf; +} + +static struct aws_string *s_socket_get_server_name_fn(const struct aws_socket *socket) { + struct nw_socket *nw_socket = socket->impl; + return nw_socket->host_name; +} diff --git a/source/darwin/secure_transport_tls_channel_handler.c b/source/darwin/secure_transport_tls_channel_handler.c index cb1277f72..b0eedf205 100644 --- a/source/darwin/secure_transport_tls_channel_handler.c +++ b/source/darwin/secure_transport_tls_channel_handler.c @@ -7,7 +7,10 @@ #include #include #include +#include #include +#include +#include #include #include @@ -24,7 +27,7 @@ #include #include -#include "dispatch_queue.h" +#include "aws_apple_network_framework.h" #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-variable" @@ -828,17 +831,35 @@ static void s_gather_statistics(struct aws_channel_handler *handler, struct aws_ } struct aws_byte_buf aws_tls_handler_protocol(struct aws_channel_handler *handler) { +#if defined(AWS_USE_SECITEM) + /* Apple Network Framework's SecItem API handles both TCP and TLS aspects of a connection + * and an aws_channel using it does not have a TLS. The negotiated protocol is stored + * in the nw_socket and must be retrieved from the socket rather than a secure_transport_handler. */ + const struct aws_socket *socket = aws_socket_handler_get_socket(handler); + return socket->vtable->socket_get_protocol_fn(socket); +#endif /* AWS_USE_SECITEM */ struct secure_transport_handler *secure_transport_handler = handler->impl; + return secure_transport_handler->protocol; } struct aws_byte_buf aws_tls_handler_server_name(struct aws_channel_handler *handler) { + struct aws_string *server_name = NULL; +#if defined(AWS_USE_SECITEM) + /* Apple Network Framework's SecItem API handles both TCP and TLS aspects of a connection + * and an aws_channel using it does not have a TLS slot. The server_name is stored + * in the nw_socket and must be retrieved from the socket rather than a secure_transport_handler. */ + const struct aws_socket *socket = aws_socket_handler_get_socket(handler); + server_name = socket->vtable->socket_get_server_name_fn(socket); +#else struct secure_transport_handler *secure_transport_handler = handler->impl; + server_name = secure_transport_handler->server_name; +#endif const uint8_t *bytes = NULL; size_t len = 0; - if (secure_transport_handler->server_name) { - bytes = secure_transport_handler->server_name->bytes; - len = secure_transport_handler->server_name->len; + if (server_name) { + bytes = server_name->bytes; + len = server_name->len; } return aws_byte_buf_from_array(bytes, len); } @@ -865,9 +886,6 @@ static struct aws_channel_handler *s_tls_handler_new( struct secure_transport_handler *secure_transport_handler = (struct secure_transport_handler *)aws_mem_calloc(allocator, 1, sizeof(struct secure_transport_handler)); - if (!secure_transport_handler) { - return NULL; - } secure_transport_handler->handler.alloc = allocator; secure_transport_handler->handler.impl = secure_transport_handler; @@ -893,7 +911,7 @@ static struct aws_channel_handler *s_tls_handler_new( goto cleanup_st_handler; } - switch (secure_transport_ctx->minimum_version) { + switch (secure_transport_ctx->minimum_tls_version) { case AWS_IO_SSLv3: SSLSetProtocolVersionMin(secure_transport_handler->ctx, kSSLProtocol3); break; @@ -1027,11 +1045,15 @@ static void s_aws_secure_transport_ctx_destroy(struct secure_transport_ctx *secu } if (secure_transport_ctx->certs) { - aws_release_identity(secure_transport_ctx->certs); + CFRelease(secure_transport_ctx->certs); + } + + if (secure_transport_ctx->secitem_identity) { + CFRelease(secure_transport_ctx->secitem_identity); } if (secure_transport_ctx->ca_cert) { - aws_release_certificates(secure_transport_ctx->ca_cert); + CFRelease(secure_transport_ctx->ca_cert); } if (secure_transport_ctx->alpn_list) { @@ -1044,9 +1066,6 @@ static void s_aws_secure_transport_ctx_destroy(struct secure_transport_ctx *secu static struct aws_tls_ctx *s_tls_ctx_new(struct aws_allocator *alloc, const struct aws_tls_ctx_options *options) { struct secure_transport_ctx *secure_transport_ctx = aws_mem_calloc(alloc, 1, sizeof(struct secure_transport_ctx)); - if (!secure_transport_ctx) { - return NULL; - } if (!aws_tls_is_cipher_pref_supported(options->cipher_pref)) { aws_raise_error(AWS_IO_TLS_CIPHER_PREF_UNSUPPORTED); @@ -1055,12 +1074,12 @@ static struct aws_tls_ctx *s_tls_ctx_new(struct aws_allocator *alloc, const stru } secure_transport_ctx->wrapped_allocator = aws_wrapped_cf_allocator_new(alloc); - secure_transport_ctx->minimum_version = options->minimum_tls_version; - if (!secure_transport_ctx->wrapped_allocator) { goto cleanup_secure_transport_ctx; } + secure_transport_ctx->minimum_tls_version = options->minimum_tls_version; + if (options->alpn_list) { secure_transport_ctx->alpn_list = aws_string_new_from_string(alloc, options->alpn_list); @@ -1072,6 +1091,7 @@ static struct aws_tls_ctx *s_tls_ctx_new(struct aws_allocator *alloc, const stru secure_transport_ctx->verify_peer = options->verify_peer; secure_transport_ctx->ca_cert = NULL; secure_transport_ctx->certs = NULL; + secure_transport_ctx->secitem_identity = NULL; secure_transport_ctx->ctx.alloc = alloc; secure_transport_ctx->ctx.impl = secure_transport_ctx; aws_ref_count_init( @@ -1080,7 +1100,6 @@ static struct aws_tls_ctx *s_tls_ctx_new(struct aws_allocator *alloc, const stru (aws_simple_completion_callback *)s_aws_secure_transport_ctx_destroy); if (aws_tls_options_buf_is_set(&options->certificate) && aws_tls_options_buf_is_set(&options->private_key)) { -#if !defined(AWS_OS_IOS) AWS_LOGF_DEBUG(AWS_LS_IO_TLS, "static: certificate and key have been set, setting them up now."); if (!aws_text_is_utf8(options->certificate.buffer, options->certificate.len)) { @@ -1097,6 +1116,7 @@ static struct aws_tls_ctx *s_tls_ctx_new(struct aws_allocator *alloc, const stru struct aws_byte_cursor cert_chain_cur = aws_byte_cursor_from_buf(&options->certificate); struct aws_byte_cursor private_key_cur = aws_byte_cursor_from_buf(&options->private_key); +#if !defined(AWS_USE_SECITEM) if (aws_import_public_and_private_keys_to_identity( alloc, secure_transport_ctx->wrapped_allocator, @@ -1108,12 +1128,27 @@ static struct aws_tls_ctx *s_tls_ctx_new(struct aws_allocator *alloc, const stru AWS_LS_IO_TLS, "static: failed to import certificate and private key with error %d.", aws_last_error()); goto cleanup_wrapped_allocator; } -#endif +#endif /* !AWS_USE_SECITEM */ +#if defined(AWS_USE_SECITEM) + + if (aws_secitem_import_cert_and_key( + alloc, + secure_transport_ctx->wrapped_allocator, + &cert_chain_cur, + &private_key_cur, + &secure_transport_ctx->secitem_identity, + options->secitem_options)) { + AWS_LOGF_ERROR( + AWS_LS_IO_TLS, "static: failed to import certificate and private key with error %d.", aws_last_error()); + goto cleanup_wrapped_allocator; + } +#endif /* AWS_USE_SECITEM */ } else if (aws_tls_options_buf_is_set(&options->pkcs12)) { - AWS_LOGF_DEBUG(AWS_LS_IO_TLS, "static: a pkcs$12 certificate and key has been set, setting it up now."); + AWS_LOGF_DEBUG(AWS_LS_IO_TLS, "static: a pkcs#12 certificate and key has been set, setting it up now."); struct aws_byte_cursor pkcs12_blob_cur = aws_byte_cursor_from_buf(&options->pkcs12); struct aws_byte_cursor password_cur = aws_byte_cursor_from_buf(&options->pkcs12_password); +#if !defined(AWS_USE_SECITEM) if (aws_import_pkcs12_to_identity( secure_transport_ctx->wrapped_allocator, &pkcs12_blob_cur, @@ -1123,6 +1158,18 @@ static struct aws_tls_ctx *s_tls_ctx_new(struct aws_allocator *alloc, const stru AWS_LS_IO_TLS, "static: failed to import pkcs#12 certificate with error %d.", aws_last_error()); goto cleanup_wrapped_allocator; } +#endif /* !AWS_USE_SECITEM */ +#if defined(AWS_USE_SECITEM) + if (aws_secitem_import_pkcs12( + secure_transport_ctx->wrapped_allocator, + &pkcs12_blob_cur, + &password_cur, + &secure_transport_ctx->secitem_identity)) { + AWS_LOGF_ERROR( + AWS_LS_IO_TLS, "static: failed to import pkcs#12 certificate with error %d.", aws_last_error()); + goto cleanup_wrapped_allocator; + } +#endif /* AWS_USE_SECITEM */ } if (aws_tls_options_buf_is_set(&options->ca_file)) { diff --git a/source/event_loop.c b/source/event_loop.c index c5cf90095..1a053abc1 100644 --- a/source/event_loop.c +++ b/source/event_loop.c @@ -107,6 +107,7 @@ enum aws_event_loop_type aws_event_loop_get_default_type(void) { AWS_LOGF_ERROR( AWS_LS_IO_EVENT_LOOP, "Failed to get default event loop type. The library is not built correctly on the platform."); + return AWS_EVENT_LOOP_PLATFORM_DEFAULT; #endif } diff --git a/source/io.c b/source/io.c index 380ece11b..d52b7129f 100644 --- a/source/io.c +++ b/source/io.c @@ -306,6 +306,50 @@ static struct aws_error_info s_errors[] = { "Failure during TLS read."), AWS_DEFINE_ERROR_INFO_IO(AWS_ERROR_PEM_MALFORMED, "Malformed PEM object encountered."), + AWS_DEFINE_ERROR_INFO_IO( + AWS_IO_TLS_UNKNOWN_ROOT_CERTIFICATE, + "Channel shutdown due to tls unknown root certificate."), + AWS_DEFINE_ERROR_INFO_IO( + AWS_IO_TLS_NO_ROOT_CERTIFICATE_FOUND, + "Channel shutdown due to tls no root certificate found."), + AWS_DEFINE_ERROR_INFO_IO( + AWS_IO_TLS_CERTIFICATE_EXPIRED, + "Channel shutdown due to tls certificate expired."), + AWS_DEFINE_ERROR_INFO_IO( + AWS_IO_TLS_CERTIFICATE_NOT_YET_VALID, + "Channel shutdown due to tls certificate not yet valid."), + AWS_DEFINE_ERROR_INFO_IO( + AWS_IO_TLS_BAD_CERTIFICATE, + "Channel shutdown due to tls certificate is malformed or not correctly formatted."), + AWS_DEFINE_ERROR_INFO_IO( + AWS_IO_TLS_PEER_CERTIFICATE_EXPIRED, + "Channel shutdown due to peer tls certificate is malformed or not correctly formatted."), + AWS_DEFINE_ERROR_INFO_IO( + AWS_IO_TLS_BAD_PEER_CERTIFICATE, + "Channel shutdown due to peer tls certificate is malformed or not correctly formatted."), + AWS_DEFINE_ERROR_INFO_IO( + AWS_IO_TLS_PEER_CERTIFICATE_REVOKED, + "Channel shutdown due to peer tls certificate has been revoked."), + AWS_DEFINE_ERROR_INFO_IO( + AWS_IO_TLS_PEER_CERTIFICATE_UNKNOWN, + "Channel shutdown due to peer tls certificate is unknown."), + AWS_DEFINE_ERROR_INFO_IO( + AWS_IO_TLS_INTERNAL_ERROR, + "Channel shutdown due to internal SSL error."), + AWS_DEFINE_ERROR_INFO_IO( + AWS_IO_TLS_CLOSED_GRACEFUL, + "Channel shutdown due to connection closed gracefully."), + AWS_DEFINE_ERROR_INFO_IO( + AWS_IO_TLS_CLOSED_ABORT, + "Channel shutdown due to connection closed due to an error."), + AWS_DEFINE_ERROR_INFO_IO( + AWS_IO_TLS_INVALID_CERTIFICATE_CHAIN, + "Channel shutdown due to invalid certificate chain."), + AWS_DEFINE_ERROR_INFO_IO( + AWS_IO_TLS_HOST_NAME_MISSMATCH, + "Channel shutdown due to certificate's host name does not match the endpoint host name."), + + }; /* clang-format on */ diff --git a/source/posix/socket.c b/source/posix/socket.c index 266ad2de2..88ee14334 100644 --- a/source/posix/socket.c +++ b/source/posix/socket.c @@ -195,8 +195,13 @@ static int s_socket_connect( const struct aws_socket_endpoint *remote_endpoint, struct aws_event_loop *event_loop, aws_socket_on_connection_result_fn *on_connection_result, + aws_socket_retrieve_tls_options_fn retrieve_tls_options, + void *user_data); +static int s_socket_bind( + struct aws_socket *socket, + const struct aws_socket_endpoint *local_endpoint, + aws_socket_retrieve_tls_options_fn *retrieve_tls_options, void *user_data); -static int s_socket_bind(struct aws_socket *socket, const struct aws_socket_endpoint *local_endpoint); static int s_socket_listen(struct aws_socket *socket, int backlog_size); static int s_socket_start_accept( struct aws_socket *socket, @@ -220,6 +225,8 @@ static int s_socket_write( void *user_data); static int s_socket_get_error(struct aws_socket *socket); static bool s_socket_is_open(struct aws_socket *socket); +static struct aws_byte_buf s_socket_get_protocol_fn(const struct aws_socket *socket); +static struct aws_string *s_socket_get_server_name_fn(const struct aws_socket *socket); struct aws_socket_vtable s_posix_socket_vtable = { .socket_cleanup_fn = s_socket_clean_up, @@ -237,6 +244,8 @@ struct aws_socket_vtable s_posix_socket_vtable = { .socket_write_fn = s_socket_write, .socket_get_error_fn = s_socket_get_error, .socket_is_open_fn = s_socket_is_open, + .socket_get_protocol_fn = s_socket_get_protocol_fn, + .socket_get_server_name_fn = s_socket_get_server_name_fn, }; static void s_socket_destroy_impl(void *user_data) { @@ -292,7 +301,7 @@ static int s_socket_init( return AWS_OP_SUCCESS; } - +#if defined(AWS_ENABLE_KQUEUE) || defined(AWS_ENABLE_EPOLL) int aws_socket_init_posix( struct aws_socket *socket, struct aws_allocator *alloc, @@ -300,6 +309,7 @@ int aws_socket_init_posix( AWS_ASSERT(options); return s_socket_init(socket, alloc, options, -1); } +#endif // AWS_ENABLE_KQUEUE || AWS_ENABLE_EPOLL static void s_socket_clean_up(struct aws_socket *socket) { if (!socket->impl) { @@ -663,7 +673,9 @@ static int s_socket_connect( const struct aws_socket_endpoint *remote_endpoint, struct aws_event_loop *event_loop, aws_socket_on_connection_result_fn *on_connection_result, + aws_socket_retrieve_tls_options_fn *retrieve_tls_options, void *user_data) { + (void)retrieve_tls_options; AWS_ASSERT(event_loop); AWS_ASSERT(!socket->event_loop); @@ -843,7 +855,13 @@ static int s_socket_connect( return AWS_OP_ERR; } -static int s_socket_bind(struct aws_socket *socket, const struct aws_socket_endpoint *local_endpoint) { +static int s_socket_bind( + struct aws_socket *socket, + const struct aws_socket_endpoint *local_endpoint, + aws_socket_retrieve_tls_options_fn *retrieve_tls_options, + void *user_data) { + (void)user_data; + (void)retrieve_tls_options; if (socket->state != INIT) { AWS_LOGF_ERROR( AWS_LS_IO_SOCKET, @@ -2041,6 +2059,24 @@ static bool s_socket_is_open(struct aws_socket *socket) { return socket->io_handle.data.fd >= 0; } +static struct aws_byte_buf s_socket_get_protocol_fn(const struct aws_socket *socket) { + struct aws_byte_buf empty; + AWS_ZERO_STRUCT(empty); + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKET, + "id=%p socket_get_protocol_fn should only be called on a socket using secitem.", + (void *)socket); + return empty; +} + +static struct aws_string *s_socket_get_server_name_fn(const struct aws_socket *socket) { + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKET, + "id=%p socket_get_server_name_fn should only be called on a socket using secitem.", + (void *)socket); + return NULL; +} + bool aws_is_network_interface_name_valid(const char *interface_name) { if (if_nametoindex(interface_name) == 0) { AWS_LOGF_ERROR(AWS_LS_IO_SOCKET, "network_interface_name(%s) is invalid with errno: %d", interface_name, errno); diff --git a/source/socket.c b/source/socket.c index a4d620204..15b1cc3f6 100644 --- a/source/socket.c +++ b/source/socket.c @@ -19,14 +19,20 @@ int aws_socket_connect( const struct aws_socket_endpoint *remote_endpoint, struct aws_event_loop *event_loop, aws_socket_on_connection_result_fn *on_connection_result, + aws_socket_retrieve_tls_options_fn *retrieve_tls_options, void *user_data) { AWS_PRECONDITION(socket->vtable && socket->vtable->socket_connect_fn); - return socket->vtable->socket_connect_fn(socket, remote_endpoint, event_loop, on_connection_result, user_data); + return socket->vtable->socket_connect_fn( + socket, remote_endpoint, event_loop, on_connection_result, retrieve_tls_options, user_data); } -int aws_socket_bind(struct aws_socket *socket, const struct aws_socket_endpoint *local_endpoint) { +int aws_socket_bind( + struct aws_socket *socket, + const struct aws_socket_endpoint *local_endpoint, + aws_socket_retrieve_tls_options_fn *retrieve_tls_options, + void *user_data) { AWS_PRECONDITION(socket->vtable && socket->vtable->socket_bind_fn); - return socket->vtable->socket_bind_fn(socket, local_endpoint); + return socket->vtable->socket_bind_fn(socket, local_endpoint, retrieve_tls_options, user_data); } int aws_socket_listen(struct aws_socket *socket, int backlog_size) { @@ -39,7 +45,7 @@ int aws_socket_start_accept( struct aws_event_loop *accept_loop, aws_socket_on_accept_result_fn *on_accept_result, void *user_data) { - AWS_PRECONDITION(socket->vtable && socket->vtable->socket_listen_fn); + AWS_PRECONDITION(socket->vtable && socket->vtable->socket_start_accept_fn); return socket->vtable->socket_start_accept_fn(socket, accept_loop, on_accept_result, user_data); } @@ -232,7 +238,7 @@ int aws_socket_init_posix( AWS_LOGF_DEBUG(AWS_LS_IO_SOCKET, "Posix socket is not supported on the platform."); return aws_raise_error(AWS_ERROR_PLATFORM_NOT_SUPPORTED); } -#endif +#endif // !AWS_ENABLE_EPOLL && !AWS_ENABLE_KQUEUE #ifndef AWS_ENABLE_IO_COMPLETION_PORTS int aws_socket_init_winsock( diff --git a/source/tls_channel_handler.c b/source/tls_channel_handler.c index 5c6426872..2b45412a4 100644 --- a/source/tls_channel_handler.c +++ b/source/tls_channel_handler.c @@ -24,6 +24,14 @@ void aws_tls_ctx_options_init_default_client(struct aws_tls_ctx_options *options options->cipher_pref = AWS_IO_TLS_CIPHER_PREF_SYSTEM_DEFAULT; options->verify_peer = true; options->max_fragment_size = g_aws_channel_max_fragment_size; + +#ifdef __APPLE__ + + options->secitem_options = aws_mem_calloc(allocator, 1, sizeof(struct aws_secitem_options)); + options->secitem_options->cert_label = aws_string_new_from_c_str(allocator, "aws-crt-default-certificate-label"); + options->secitem_options->key_label = aws_string_new_from_c_str(allocator, "aws-crt-default-key-label"); + +#endif /* __APPLE__ */ } void aws_tls_ctx_options_clean_up(struct aws_tls_ctx_options *options) { @@ -33,13 +41,18 @@ void aws_tls_ctx_options_clean_up(struct aws_tls_ctx_options *options) { aws_byte_buf_clean_up_secure(&options->private_key); #ifdef __APPLE__ + aws_byte_buf_clean_up_secure(&options->pkcs12); aws_byte_buf_clean_up_secure(&options->pkcs12_password); -# if !defined(AWS_OS_IOS) + if (options->secitem_options) { + aws_tls_secitem_options_clean_up(options->secitem_options); + aws_mem_release(options->allocator, options->secitem_options); + } + aws_string_destroy(options->keychain_path); -# endif -#endif + +#endif /* __APPLE__ */ aws_string_destroy(options->alpn_list); aws_custom_key_op_handler_release(options->custom_key_op_handler); @@ -53,8 +66,6 @@ int aws_tls_ctx_options_init_client_mtls( const struct aws_byte_cursor *cert, const struct aws_byte_cursor *pkey) { -#if !defined(AWS_OS_IOS) - aws_tls_ctx_options_init_default_client(options, allocator); if (aws_byte_buf_init_copy_from_cursor(&options->certificate, allocator, *cert)) { @@ -79,15 +90,6 @@ int aws_tls_ctx_options_init_client_mtls( error: aws_tls_ctx_options_clean_up(options); return AWS_OP_ERR; - -#else - (void)allocator; - (void)cert; - (void)pkey; - AWS_ZERO_STRUCT(*options); - AWS_LOGF_ERROR(AWS_LS_IO_TLS, "static: This platform does not support PEM certificates"); - return aws_raise_error(AWS_ERROR_PLATFORM_NOT_SUPPORTED); -#endif } int aws_tls_ctx_options_init_client_mtls_from_path( @@ -95,8 +97,6 @@ int aws_tls_ctx_options_init_client_mtls_from_path( struct aws_allocator *allocator, const char *cert_path, const char *pkey_path) { - -#if !defined(AWS_OS_IOS) aws_tls_ctx_options_init_default_client(options, allocator); if (aws_byte_buf_init_from_file(&options->certificate, allocator, cert_path)) { @@ -121,15 +121,6 @@ int aws_tls_ctx_options_init_client_mtls_from_path( error: aws_tls_ctx_options_clean_up(options); return AWS_OP_ERR; - -#else - (void)allocator; - (void)cert_path; - (void)pkey_path; - AWS_ZERO_STRUCT(*options); - AWS_LOGF_ERROR(AWS_LS_IO_TLS, "static: This platform does not support PEM certificates"); - return aws_raise_error(AWS_ERROR_PLATFORM_NOT_SUPPORTED); -#endif } int aws_tls_ctx_options_init_client_mtls_with_custom_key_operations( @@ -263,7 +254,7 @@ int aws_tls_ctx_options_set_keychain_path( struct aws_tls_ctx_options *options, struct aws_byte_cursor *keychain_path_cursor) { -#if defined(__APPLE__) && !defined(AWS_OS_IOS) +#if defined(__APPLE__) && !defined(AWS_USE_SECITEM) AWS_LOGF_WARN(AWS_LS_IO_TLS, "static: Keychain path is deprecated."); options->keychain_path = aws_string_new_from_cursor(options->allocator, keychain_path_cursor); @@ -272,12 +263,67 @@ int aws_tls_ctx_options_set_keychain_path( } return AWS_OP_SUCCESS; -#else +#endif /* __APPLE__ || AWS_USE_SECITEM */ + (void)options; (void)keychain_path_cursor; AWS_LOGF_ERROR(AWS_LS_IO_TLS, "static: Keychain path can only be set on MacOS."); return aws_raise_error(AWS_ERROR_PLATFORM_NOT_SUPPORTED); -#endif +} + +int aws_tls_ctx_options_set_secitem_options( + struct aws_tls_ctx_options *tls_ctx_options, + const struct aws_secitem_options *secitem_options) { +#ifdef __APPLE__ + + if (secitem_options->cert_label != NULL) { + aws_string_destroy(tls_ctx_options->secitem_options->cert_label); + tls_ctx_options->secitem_options->cert_label = NULL; + tls_ctx_options->secitem_options->cert_label = + aws_string_new_from_string(tls_ctx_options->allocator, secitem_options->cert_label); + if (tls_ctx_options->secitem_options->cert_label == NULL) { + AWS_LOGF_ERROR(AWS_LS_IO_TLS, "static: Secitem option certificate label is invalid."); + return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + } + AWS_LOGF_DEBUG(AWS_LS_IO_TLS, "static: Secitem option certificate label set."); + } + + if (secitem_options->key_label != NULL) { + aws_string_destroy(tls_ctx_options->secitem_options->key_label); + tls_ctx_options->secitem_options->key_label = NULL; + tls_ctx_options->secitem_options->key_label = + aws_string_new_from_string(tls_ctx_options->allocator, secitem_options->key_label); + if (tls_ctx_options->secitem_options->key_label == NULL) { + AWS_LOGF_ERROR(AWS_LS_IO_TLS, "static: Secitem option key label is invalid."); + return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + } + AWS_LOGF_DEBUG(AWS_LS_IO_TLS, "static: Secitem option key label set."); + } + + return AWS_OP_SUCCESS; + +#endif /* __APPLE__ */ + + (void)tls_ctx_options; + (void)secitem_options; + AWS_LOGF_ERROR(AWS_LS_IO_TLS, "static: Setting of secitem options only supported on Apple."); + return aws_raise_error(AWS_ERROR_PLATFORM_NOT_SUPPORTED); +} + +void aws_tls_secitem_options_clean_up(struct aws_secitem_options *secitem_options) { + if (secitem_options == NULL) { + return; + } + + if (secitem_options->cert_label) { + aws_string_destroy(secitem_options->cert_label); + secitem_options->cert_label = NULL; + } + + if (secitem_options->key_label) { + aws_string_destroy(secitem_options->key_label); + secitem_options->key_label = NULL; + } } int aws_tls_ctx_options_init_client_mtls_from_system_path( @@ -877,3 +923,37 @@ void aws_custom_key_op_handler_perform_operation( struct aws_tls_key_operation *operation) { key_op_handler->vtable->on_key_operation(key_op_handler, operation); } + +bool aws_tls_error_code_check(int error_code) { + switch (error_code) { + case AWS_IO_TLS_ERROR_NEGOTIATION_FAILURE: + case AWS_IO_TLS_ERROR_NOT_NEGOTIATED: + case AWS_IO_TLS_ERROR_WRITE_FAILURE: + case AWS_IO_TLS_ERROR_ALERT_RECEIVED: + case AWS_IO_TLS_CTX_ERROR: + case AWS_IO_TLS_VERSION_UNSUPPORTED: + case AWS_IO_TLS_CIPHER_PREF_UNSUPPORTED: + case AWS_IO_TLS_NEGOTIATION_TIMEOUT: + case AWS_IO_TLS_ALERT_NOT_GRACEFUL: + case AWS_IO_TLS_DIGEST_ALGORITHM_UNSUPPORTED: + case AWS_IO_TLS_SIGNATURE_ALGORITHM_UNSUPPORTED: + case AWS_IO_TLS_ERROR_READ_FAILURE: + case AWS_IO_TLS_UNKNOWN_ROOT_CERTIFICATE: + case AWS_IO_TLS_NO_ROOT_CERTIFICATE_FOUND: + case AWS_IO_TLS_CERTIFICATE_EXPIRED: + case AWS_IO_TLS_CERTIFICATE_NOT_YET_VALID: + case AWS_IO_TLS_BAD_CERTIFICATE: + case AWS_IO_TLS_PEER_CERTIFICATE_EXPIRED: + case AWS_IO_TLS_BAD_PEER_CERTIFICATE: + case AWS_IO_TLS_PEER_CERTIFICATE_REVOKED: + case AWS_IO_TLS_PEER_CERTIFICATE_UNKNOWN: + case AWS_IO_TLS_INTERNAL_ERROR: + case AWS_IO_TLS_CLOSED_GRACEFUL: + case AWS_IO_TLS_CLOSED_ABORT: + case AWS_IO_TLS_INVALID_CERTIFICATE_CHAIN: + case AWS_IO_TLS_HOST_NAME_MISSMATCH: + return true; + default: + return false; + } +} diff --git a/source/windows/iocp/socket.c b/source/windows/iocp/socket.c index b2d8ad16a..65a4ff482 100644 --- a/source/windows/iocp/socket.c +++ b/source/windows/iocp/socket.c @@ -151,8 +151,13 @@ static int s_socket_connect( const struct aws_socket_endpoint *remote_endpoint, struct aws_event_loop *event_loop, aws_socket_on_connection_result_fn *on_connection_result, + aws_socket_retrieve_tls_options_fn *retrieve_tls_options, + void *user_data); +static int s_socket_bind( + struct aws_socket *socket, + const struct aws_socket_endpoint *local_endpoint, + aws_socket_retrieve_tls_options_fn *retrieve_tls_options, void *user_data); -static int s_socket_bind(struct aws_socket *socket, const struct aws_socket_endpoint *local_endpoint); static int s_socket_listen(struct aws_socket *socket, int backlog_size); static int s_socket_start_accept( struct aws_socket *socket, @@ -176,6 +181,8 @@ static int s_socket_write( void *user_data); static int s_socket_get_error(struct aws_socket *socket); static bool s_socket_is_open(struct aws_socket *socket); +static struct aws_byte_buf s_socket_get_protocol_fn(const struct aws_socket *socket); +static struct aws_string *s_socket_get_server_name_fn(const struct aws_socket *socket); static int s_stream_subscribe_to_read( struct aws_socket *socket, @@ -287,6 +294,8 @@ struct aws_socket_vtable s_winsock_vtable = { .socket_write_fn = s_socket_write, .socket_get_error_fn = s_socket_get_error, .socket_is_open_fn = s_socket_is_open, + .socket_get_protocol_fn = s_socket_get_protocol_fn, + .socket_get_server_name_fn = s_socket_get_server_name_fn, }; /* When socket is connected, any of the CONNECT_*** flags might be set. @@ -490,7 +499,9 @@ static int s_socket_connect( const struct aws_socket_endpoint *remote_endpoint, struct aws_event_loop *event_loop, aws_socket_on_connection_result_fn *on_connection_result, + aws_socket_retrieve_tls_options_fn *retrieve_tls_options, void *user_data) { + (void)retrieve_tls_options; struct iocp_socket *socket_impl = socket->impl; if (socket->options.type != AWS_SOCKET_DGRAM) { AWS_ASSERT(on_connection_result); @@ -513,7 +524,13 @@ static int s_socket_connect( return socket_impl->winsock_vtable->connect(socket, remote_endpoint, event_loop, on_connection_result, user_data); } -static int s_socket_bind(struct aws_socket *socket, const struct aws_socket_endpoint *local_endpoint) { +static int s_socket_bind( + struct aws_socket *socket, + const struct aws_socket_endpoint *local_endpoint, + aws_socket_retrieve_tls_options_fn *retrieve_tls_options, + void *user_data) { + (void)retrieve_tls_options; + (void)user_data; if (socket->state != INIT) { socket->state = ERRORED; return aws_raise_error(AWS_IO_SOCKET_ILLEGAL_OPERATION_FOR_STATE); @@ -3301,6 +3318,24 @@ static bool s_socket_is_open(struct aws_socket *socket) { return socket->io_handle.data.handle != INVALID_HANDLE_VALUE; } +static struct aws_byte_buf s_socket_get_protocol_fn(const struct aws_socket *socket) { + struct aws_byte_buf empty; + AWS_ZERO_STRUCT(empty); + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKET, + "id=%p socket_get_protocol_fn should only be called on a socket using secitem.", + (void *)socket); + return empty; +} + +static struct aws_string *s_socket_get_server_name_fn(const struct aws_socket *socket) { + AWS_LOGF_ERROR( + AWS_LS_IO_SOCKET, + "id=%p socket_get_server_name_fn should only be called on a socket using secitem.", + (void *)socket); + return NULL; +} + bool aws_is_network_interface_name_valid(const char *interface_name) { (void)interface_name; AWS_LOGF_ERROR(AWS_LS_IO_SOCKET, "network_interface_names are not supported on Windows"); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 1e0c1cedb..9eba2c86e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -95,7 +95,6 @@ add_test_case(wrong_thread_read_write_fails) add_test_case(incoming_duplicate_tcp_bind_errors) endif() - if(WIN32) add_test_case(local_socket_pipe_connected_race) endif() @@ -139,8 +138,16 @@ add_test_case(pem_sanitize_comments_around_pem_object_removed) add_test_case(pem_sanitize_empty_file_rejected) add_test_case(pem_sanitize_wrong_format_rejected) + +if(NOT AWS_USE_SECITEM) +# These tests require the test binary to be codesigned with an Apple Developer account with entitlements. +# The entitlements also require a provisioning profile and require the binary to be run from within XCode or a +# valid app bundle. add_test_case(socket_handler_echo_and_backpressure) add_test_case(socket_handler_close) +endif() + + # These tests fail on Windows due to some bug in our server code where, if the socket is closed # immediately after data is written, that data does not flush cleanly to the client. # I've lost days to this bug, and no one is using our Windows server funcionality, @@ -159,9 +166,14 @@ add_test_case(socket_handler_close) # before calling closesocket() but shutdown() gets error WSAENOTCONN, even # though, at that moment, the socket should be connected just fine. if(NOT WIN32) + if(NOT AWS_USE_SECITEM) + # These tests require the test binary to be codesigned with an Apple Developer account with entitlements. + # The entitlements also require a provisioning profile and require the binary to be run from within XCode or a + # valid app bundle. add_net_test_case(socket_handler_read_to_eof_after_peer_hangup) add_net_test_case(socket_handler_ipv4_read_to_eof_after_peer_hangup) add_net_test_case(socket_handler_ipv6_read_to_eof_after_peer_hangup) + endif() endif() add_test_case(socket_pinned_event_loop) add_net_test_case(socket_pinned_event_loop_dns_failure) @@ -215,10 +227,13 @@ if(NOT BYO_CRYPTO) # to be a way to disable it if(NOT(WIN32 AND NOT CMAKE_SYSTEM_VERSION MATCHES "10\.0\.1.*")) # Skip TLS 1.0 and TLS 1.1 test for windows later than windows server 2022, as they droped old TLS + if(NOT AWS_USE_SECITEM) add_net_test_case(tls_client_channel_negotiation_error_legacy_crypto_tls10) + # SecItem does not allow use of depricated TLS versions add_net_test_case(tls_client_channel_negotiation_override_legacy_crypto_tls10) - add_net_test_case(tls_client_channel_negotiation_error_override_legacy_crypto_tls11) add_net_test_case(tls_client_channel_negotiation_success_legacy_crypto_tls11) + endif() + add_net_test_case(tls_client_channel_negotiation_error_override_legacy_crypto_tls11) endif() # Badssl - Secure uncommon suite @@ -248,12 +263,10 @@ if(NOT BYO_CRYPTO) add_net_test_case(tls_client_channel_negotiation_success_mozilla_modern) # Misc non-badssl tls tests - add_net_test_case(test_concurrent_cert_import) add_net_test_case(test_duplicate_cert_import) add_net_test_case(tls_channel_echo_and_backpressure_test) add_net_test_case(tls_channel_shutdown_with_cache_test) add_net_test_case(tls_channel_shutdown_with_cache_window_update_after_shutdown_test) - add_net_test_case(tls_client_channel_negotiation_error_socket_closed) add_net_test_case(tls_client_channel_negotiation_success) add_net_test_case(tls_server_multiple_connections) add_net_test_case(tls_server_hangup_during_negotiation) @@ -262,12 +275,32 @@ if(NOT BYO_CRYPTO) add_net_test_case(alpn_successfully_negotiates) add_net_test_case(alpn_no_protocol_message) add_net_test_case(test_ecc_cert_import) +if(NOT AWS_USE_SECITEM) + # These tests require the test binary to be codesigned with an Apple Developer account with entitlements. + # The entitlements also require a provisioning profile and require the binary to be run from within XCode or a + # valid app bundle. + add_net_test_case(test_concurrent_cert_import) + + # PKCS8 is not supported on iOS. We will not support PKCS8 on macOS using SecItem. + # PKCS8 support for SecItem can be added in the future but it will require macOS + # specific branching of logic and import of the key into the keychain. add_net_test_case(test_pkcs8_import) + # This test shuts down the channel after a socket is established but while the TLS handshake is taking place + # further up the channel. Apple Network Framework's connection handles both the socket connection as well + # as the TLS handshake within the same create connection call without external notification that the socket + # has succeeded prior to the TLS negotiation. As such, this test will not work for Secitem. + add_net_test_case(tls_client_channel_negotiation_error_socket_closed) + + # TLS statistics tracks and handles the timeout of TLS. Using SecItem, the TLS handshake takes place within + # the socket establishment and does not need a separate timeout task for TLS. + add_net_test_case(tls_channel_statistics_test) +endif() + add_test_case(alpn_error_creating_handler) add_test_case(tls_destroy_null_context) - add_net_test_case(tls_channel_statistics_test) add_net_test_case(tls_certificate_chain_test) + else() add_test_case(byo_tls_handler_test) endif() diff --git a/tests/socket_test.c b/tests/socket_test.c index 42d7b5592..5049f0380 100644 --- a/tests/socket_test.c +++ b/tests/socket_test.c @@ -207,7 +207,7 @@ static bool s_test_running_as_root(struct aws_allocator *alloc) { int err = aws_socket_init(&socket, alloc, &options); AWS_FATAL_ASSERT(!err); - err = aws_socket_bind(&socket, &endpoint); + err = aws_socket_bind(&socket, &endpoint, NULL, NULL); err |= aws_socket_listen(&socket, 1024); bool is_root = !err; aws_socket_clean_up(&socket); @@ -238,7 +238,7 @@ static int s_test_socket_ex( struct aws_socket listener; ASSERT_SUCCESS(aws_socket_init(&listener, allocator, options)); - ASSERT_SUCCESS(aws_socket_bind(&listener, endpoint)); + ASSERT_SUCCESS(aws_socket_bind(&listener, endpoint, NULL, NULL)); struct aws_socket_endpoint bound_endpoint; ASSERT_SUCCESS(aws_socket_get_bound_address(&listener, &bound_endpoint)); @@ -258,9 +258,10 @@ static int s_test_socket_ex( struct aws_socket outgoing; ASSERT_SUCCESS(aws_socket_init(&outgoing, allocator, options)); if (local && (strcmp(local->address, endpoint->address) != 0 || local->port != endpoint->port)) { - ASSERT_SUCCESS(aws_socket_bind(&outgoing, local)); + ASSERT_SUCCESS(aws_socket_bind(&outgoing, local, NULL, NULL)); } - ASSERT_SUCCESS(aws_socket_connect(&outgoing, endpoint, event_loop, s_local_outgoing_connection, &outgoing_args)); + ASSERT_SUCCESS( + aws_socket_connect(&outgoing, endpoint, event_loop, s_local_outgoing_connection, NULL, &outgoing_args)); if (listener.options.type == AWS_SOCKET_STREAM || aws_event_loop_get_default_type() == AWS_EVENT_LOOP_DISPATCH_QUEUE) { @@ -426,7 +427,7 @@ static int s_test_socket_udp_dispatch_queue( struct aws_socket listener; ASSERT_SUCCESS(aws_socket_init(&listener, allocator, options)); - ASSERT_SUCCESS(aws_socket_bind(&listener, endpoint)); + ASSERT_SUCCESS(aws_socket_bind(&listener, endpoint, NULL, NULL)); struct aws_socket_endpoint bound_endpoint; ASSERT_SUCCESS(aws_socket_get_bound_address(&listener, &bound_endpoint)); @@ -443,7 +444,8 @@ static int s_test_socket_udp_dispatch_queue( struct aws_socket outgoing; ASSERT_SUCCESS(aws_socket_init(&outgoing, allocator, options)); - ASSERT_SUCCESS(aws_socket_connect(&outgoing, endpoint, event_loop, s_local_outgoing_connection, &outgoing_args)); + ASSERT_SUCCESS( + aws_socket_connect(&outgoing, endpoint, event_loop, s_local_outgoing_connection, NULL, &outgoing_args)); ASSERT_SUCCESS(aws_mutex_lock(&mutex)); ASSERT_SUCCESS(aws_condition_variable_wait_pred( @@ -928,7 +930,8 @@ static int s_test_connect_timeout(struct aws_allocator *allocator, void *ctx) { struct aws_socket outgoing; ASSERT_SUCCESS(aws_socket_init(&outgoing, allocator, &options)); - ASSERT_SUCCESS(aws_socket_connect(&outgoing, &endpoint, event_loop, s_local_outgoing_connection, &outgoing_args)); + ASSERT_SUCCESS( + aws_socket_connect(&outgoing, &endpoint, event_loop, s_local_outgoing_connection, NULL, &outgoing_args)); aws_mutex_lock(&mutex); ASSERT_SUCCESS(aws_condition_variable_wait_pred( &condition_variable, &mutex, s_connection_completed_predicate, &outgoing_args)); @@ -1024,7 +1027,8 @@ static int s_test_connect_timeout_cancelation(struct aws_allocator *allocator, v struct aws_socket outgoing; ASSERT_SUCCESS(aws_socket_init(&outgoing, allocator, &options)); - ASSERT_SUCCESS(aws_socket_connect(&outgoing, &endpoint, event_loop, s_local_outgoing_connection, &outgoing_args)); + ASSERT_SUCCESS( + aws_socket_connect(&outgoing, &endpoint, event_loop, s_local_outgoing_connection, NULL, &outgoing_args)); aws_event_loop_group_release(el_group); @@ -1090,7 +1094,8 @@ static int s_test_outgoing_local_sock_errors(struct aws_allocator *allocator, vo struct aws_socket outgoing; ASSERT_SUCCESS(aws_socket_init(&outgoing, allocator, &options)); - int socket_connect_result = aws_socket_connect(&outgoing, &endpoint, event_loop, s_null_sock_connection, &args); + int socket_connect_result = + aws_socket_connect(&outgoing, &endpoint, event_loop, s_null_sock_connection, NULL, &args); // As Apple network framework has a async API design, we would not get the error back on connect if (aws_event_loop_get_default_type() != AWS_EVENT_LOOP_DISPATCH_QUEUE) { ASSERT_FAILS(socket_connect_result); @@ -1145,7 +1150,7 @@ static int s_test_outgoing_tcp_sock_error(struct aws_allocator *allocator, void struct aws_socket outgoing; ASSERT_SUCCESS(aws_socket_init(&outgoing, allocator, &options)); - int result = aws_socket_connect(&outgoing, &endpoint, event_loop, s_null_sock_connection, &args); + int result = aws_socket_connect(&outgoing, &endpoint, event_loop, s_null_sock_connection, NULL, &args); #ifdef __FreeBSD__ /** * FreeBSD doesn't seem to respect the O_NONBLOCK or SOCK_NONBLOCK flag. It fails immediately when trying to @@ -1195,7 +1200,7 @@ static int s_test_incoming_tcp_sock_errors(struct aws_allocator *allocator, void struct aws_socket incoming; ASSERT_SUCCESS(aws_socket_init(&incoming, allocator, &options)); - ASSERT_ERROR(AWS_ERROR_NO_PERMISSION, aws_socket_bind(&incoming, &endpoint)); + ASSERT_ERROR(AWS_ERROR_NO_PERMISSION, aws_socket_bind(&incoming, &endpoint, NULL, NULL)); aws_socket_clean_up(&incoming); aws_event_loop_destroy(event_loop); @@ -1225,11 +1230,11 @@ static int s_test_incoming_duplicate_tcp_bind_errors(struct aws_allocator *alloc struct aws_socket incoming; ASSERT_SUCCESS(aws_socket_init(&incoming, allocator, &options)); - ASSERT_SUCCESS(aws_socket_bind(&incoming, &endpoint)); + ASSERT_SUCCESS(aws_socket_bind(&incoming, &endpoint, NULL, NULL)); ASSERT_SUCCESS(aws_socket_listen(&incoming, 1024)); struct aws_socket duplicate_bind; ASSERT_SUCCESS(aws_socket_init(&duplicate_bind, allocator, &options)); - ASSERT_ERROR(AWS_IO_SOCKET_ADDRESS_IN_USE, aws_socket_bind(&duplicate_bind, &endpoint)); + ASSERT_ERROR(AWS_IO_SOCKET_ADDRESS_IN_USE, aws_socket_bind(&duplicate_bind, &endpoint, NULL, NULL)); aws_socket_close(&duplicate_bind); aws_socket_clean_up(&duplicate_bind); @@ -1302,7 +1307,7 @@ static int s_test_bind_on_zero_port( struct aws_socket_endpoint local_address1; ASSERT_FAILS(aws_socket_get_bound_address(&incoming, &local_address1)); - ASSERT_SUCCESS(aws_socket_bind(&incoming, &endpoint)); + ASSERT_SUCCESS(aws_socket_bind(&incoming, &endpoint, NULL, NULL)); ASSERT_SUCCESS(aws_socket_get_bound_address(&incoming, &local_address1)); @@ -1383,7 +1388,7 @@ static int s_test_incoming_udp_sock_errors(struct aws_allocator *allocator, void struct aws_socket incoming; ASSERT_SUCCESS(aws_socket_init(&incoming, allocator, &options)); - ASSERT_FAILS(aws_socket_bind(&incoming, &endpoint)); + ASSERT_FAILS(aws_socket_bind(&incoming, &endpoint, NULL, NULL)); int error = aws_last_error(); ASSERT_TRUE(AWS_IO_SOCKET_INVALID_ADDRESS == error || AWS_ERROR_NO_PERMISSION == error); @@ -1421,7 +1426,7 @@ static int s_test_wrong_thread_read_write_fails(struct aws_allocator *allocator, struct aws_socket socket; ASSERT_SUCCESS(aws_socket_init(&socket, allocator, &options)); - aws_socket_bind(&socket, &endpoint); + aws_socket_bind(&socket, &endpoint, NULL, NULL); aws_socket_assign_to_event_loop(&socket, event_loop); aws_socket_subscribe_to_readable_events(&socket, s_on_null_readable_notification, NULL); size_t amount_read = 0; @@ -1535,7 +1540,8 @@ static int s_cleanup_before_connect_or_timeout_doesnt_explode(struct aws_allocat }; ASSERT_SUCCESS(aws_socket_init(&outgoing, allocator, &options)); - ASSERT_SUCCESS(aws_socket_connect(&outgoing, &endpoint, event_loop, s_local_outgoing_connection, &outgoing_args)); + ASSERT_SUCCESS( + aws_socket_connect(&outgoing, &endpoint, event_loop, s_local_outgoing_connection, NULL, &outgoing_args)); aws_event_loop_schedule_task_now(event_loop, &destroy_task); ASSERT_SUCCESS(aws_mutex_lock(&mutex)); ASSERT_ERROR( @@ -1610,7 +1616,7 @@ static int s_cleanup_in_accept_doesnt_explode(struct aws_allocator *allocator, v struct aws_socket listener; ASSERT_SUCCESS(aws_socket_init(&listener, allocator, &options)); - ASSERT_SUCCESS(aws_socket_bind(&listener, &endpoint)); + ASSERT_SUCCESS(aws_socket_bind(&listener, &endpoint, NULL, NULL)); ASSERT_SUCCESS(aws_socket_listen(&listener, 1024)); ASSERT_SUCCESS( @@ -1621,7 +1627,8 @@ static int s_cleanup_in_accept_doesnt_explode(struct aws_allocator *allocator, v struct aws_socket outgoing; ASSERT_SUCCESS(aws_socket_init(&outgoing, allocator, &options)); - ASSERT_SUCCESS(aws_socket_connect(&outgoing, &endpoint, event_loop, s_local_outgoing_connection, &outgoing_args)); + ASSERT_SUCCESS( + aws_socket_connect(&outgoing, &endpoint, event_loop, s_local_outgoing_connection, NULL, &outgoing_args)); ASSERT_SUCCESS(aws_mutex_lock(&mutex)); ASSERT_SUCCESS(aws_condition_variable_wait_pred(&condition_variable, &mutex, s_incoming_predicate, &listener_args)); @@ -1741,7 +1748,7 @@ static int s_cleanup_in_write_cb_doesnt_explode(struct aws_allocator *allocator, struct aws_socket listener; ASSERT_SUCCESS(aws_socket_init(&listener, allocator, &options)); - ASSERT_SUCCESS(aws_socket_bind(&listener, &endpoint)); + ASSERT_SUCCESS(aws_socket_bind(&listener, &endpoint, NULL, NULL)); ASSERT_SUCCESS(aws_socket_listen(&listener, 1024)); ASSERT_SUCCESS(aws_socket_start_accept(&listener, event_loop, s_local_listener_incoming, &listener_args)); @@ -1750,7 +1757,8 @@ static int s_cleanup_in_write_cb_doesnt_explode(struct aws_allocator *allocator, struct aws_socket outgoing; ASSERT_SUCCESS(aws_socket_init(&outgoing, allocator, &options)); - ASSERT_SUCCESS(aws_socket_connect(&outgoing, &endpoint, event_loop, s_local_outgoing_connection, &outgoing_args)); + ASSERT_SUCCESS( + aws_socket_connect(&outgoing, &endpoint, event_loop, s_local_outgoing_connection, NULL, &outgoing_args)); ASSERT_SUCCESS(aws_mutex_lock(&mutex)); ASSERT_SUCCESS(aws_condition_variable_wait_pred(&condition_variable, &mutex, s_incoming_predicate, &listener_args)); @@ -2010,7 +2018,7 @@ static int s_sock_write_cb_is_async(struct aws_allocator *allocator, void *ctx) struct aws_socket listener; ASSERT_SUCCESS(aws_socket_init(&listener, allocator, &options)); - ASSERT_SUCCESS(aws_socket_bind(&listener, &endpoint)); + ASSERT_SUCCESS(aws_socket_bind(&listener, &endpoint, NULL, NULL)); ASSERT_SUCCESS(aws_socket_listen(&listener, 1024)); ASSERT_SUCCESS(aws_socket_start_accept(&listener, event_loop, s_local_listener_incoming, &listener_args)); @@ -2019,7 +2027,8 @@ static int s_sock_write_cb_is_async(struct aws_allocator *allocator, void *ctx) struct aws_socket outgoing; ASSERT_SUCCESS(aws_socket_init(&outgoing, allocator, &options)); - ASSERT_SUCCESS(aws_socket_connect(&outgoing, &endpoint, event_loop, s_local_outgoing_connection, &outgoing_args)); + ASSERT_SUCCESS( + aws_socket_connect(&outgoing, &endpoint, event_loop, s_local_outgoing_connection, NULL, &outgoing_args)); ASSERT_SUCCESS(aws_mutex_lock(&mutex)); ASSERT_SUCCESS(aws_condition_variable_wait_pred(&condition_variable, &mutex, s_incoming_predicate, &listener_args)); @@ -2101,7 +2110,7 @@ static int s_local_socket_pipe_connected_race(struct aws_allocator *allocator, v struct aws_socket listener; ASSERT_SUCCESS(aws_socket_init(&listener, allocator, &options)); - ASSERT_SUCCESS(aws_socket_bind(&listener, &endpoint)); + ASSERT_SUCCESS(aws_socket_bind(&listener, &endpoint, NULL, NULL)); ASSERT_SUCCESS(aws_socket_listen(&listener, 1024)); @@ -2113,7 +2122,8 @@ static int s_local_socket_pipe_connected_race(struct aws_allocator *allocator, v struct aws_socket outgoing; ASSERT_SUCCESS(aws_socket_init(&outgoing, allocator, &options)); - ASSERT_SUCCESS(aws_socket_connect(&outgoing, &endpoint, event_loop, s_local_outgoing_connection, &outgoing_args)); + ASSERT_SUCCESS( + aws_socket_connect(&outgoing, &endpoint, event_loop, s_local_outgoing_connection, NULL, &outgoing_args)); ASSERT_SUCCESS(aws_socket_start_accept(&listener, event_loop, s_local_listener_incoming, &listener_args)); aws_mutex_lock(&mutex); diff --git a/tests/tls_handler_test.c b/tests/tls_handler_test.c index 2e0dbda42..5ad701f44 100644 --- a/tests/tls_handler_test.c +++ b/tests/tls_handler_test.c @@ -1003,7 +1003,7 @@ static int s_verify_negotiation_fails_helper( return AWS_OP_SKIP; } - ASSERT_INT_EQUALS(AWS_IO_TLS_ERROR_NEGOTIATION_FAILURE, outgoing_args.last_error_code); + ASSERT_TRUE(aws_tls_error_code_check(outgoing_args.last_error_code)); aws_client_bootstrap_release(client_bootstrap); @@ -1523,7 +1523,7 @@ AWS_TEST_CASE( tls_client_channel_negotiation_no_verify_untrusted_root, s_tls_client_channel_negotiation_no_verify_untrusted_root_fn) -static void s_lower_tls_version(struct aws_tls_ctx_options *options) { +static void s_lower_tls_version_to_tls10(struct aws_tls_ctx_options *options) { aws_tls_ctx_options_set_minimum_tls_version(options, AWS_IO_TLSv1); } @@ -1531,7 +1531,7 @@ static int s_tls_client_channel_negotiation_override_legacy_crypto_tls10_fn( struct aws_allocator *allocator, void *ctx) { (void)ctx; - return s_verify_good_host(allocator, s_legacy_crypto_tls10_host_name, 1010, &s_lower_tls_version); + return s_verify_good_host(allocator, s_legacy_crypto_tls10_host_name, 1010, &s_lower_tls_version_to_tls10); } AWS_TEST_CASE( @@ -1845,6 +1845,7 @@ static int s_tls_server_hangup_during_negotiation_fn(struct aws_allocator *alloc &local_server_tester.endpoint, aws_event_loop_group_get_next_loop(c_tester.el_group), s_on_client_connected_do_hangup, + NULL, shutdown_tester)); /* Wait for client socket to close */ @@ -2365,7 +2366,6 @@ struct import_info { static void s_import_cert(void *ctx) { (void)ctx; -# if !defined(AWS_OS_IOS) struct import_info *import = ctx; struct aws_byte_cursor cert_cur = aws_byte_cursor_from_buf(&import->cert_buf); struct aws_byte_cursor key_cur = aws_byte_cursor_from_buf(&import->key_buf); @@ -2378,7 +2378,6 @@ static void s_import_cert(void *ctx) { AWS_FATAL_ASSERT(import->tls); aws_tls_ctx_options_clean_up(&tls_options); -# endif /* !AWS_OS_IOS */ } # define NUM_PAIRS 2 @@ -2437,7 +2436,7 @@ static int s_test_duplicate_cert_import(struct aws_allocator *allocator, void *c struct aws_byte_buf cert_buf = {0}; struct aws_byte_buf key_buf = {0}; -# if !defined(AWS_OS_IOS) +# if !defined(AWS_USE_SECITEM) ASSERT_SUCCESS(aws_byte_buf_init_from_file(&cert_buf, allocator, "testcert0.pem")); ASSERT_SUCCESS(aws_byte_buf_init_from_file(&key_buf, allocator, "testkey.pem")); @@ -2457,7 +2456,7 @@ static int s_test_duplicate_cert_import(struct aws_allocator *allocator, void *c aws_tls_ctx_release(tls); aws_tls_ctx_options_clean_up(&tls_options); -# endif /* !AWS_OS_IOS */ +# endif /* !AWS_USE_SECITEM */ /* clean up */ aws_byte_buf_clean_up(&cert_buf);