diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index ad470109a..662b81bef 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -14,7 +14,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] - crypto: [internal, openssl, openssl3, nss, mbedtls] + crypto: [internal, openssl, openssl3, nss, mbedtls, mbedtls4] exclude: - os: windows-latest crypto: openssl @@ -24,6 +24,8 @@ jobs: crypto: nss - os: windows-latest crypto: mbedtls + - os: windows-latest + crypto: mbedtls4 - os: ubuntu-latest crypto: openssl3 include: @@ -37,6 +39,8 @@ jobs: cmake-crypto-enable: "-DENABLE_NSS=ON" - crypto: mbedtls cmake-crypto-enable: "-DENABLE_MBEDTLS=ON" + - crypto: mbedtls4 + cmake-crypto-enable: "-DENABLE_MBEDTLS=ON" runs-on: ${{ matrix.os }} @@ -54,6 +58,23 @@ jobs: if: matrix.os == 'ubuntu-latest' && matrix.crypto == 'mbedtls' run: sudo apt-get install libmbedtls-dev + - name: Setup Ubuntu MbedTLS 4 + if: matrix.os == 'ubuntu-latest' && matrix.crypto == 'mbedtls4' + run: | + python3 -m pip install --break-system-packages --upgrade jsonschema jinja2 + git clone https://github.com/Mbed-TLS/mbedtls.git /tmp/mbedtls4-src + cd /tmp/mbedtls4-src + git checkout mbedtls-4.0.0 + git submodule update --init --recursive + # PIC is required to link the static libtfpsacrypto into libsrtp2's + # shared library; without it the final shared link fails with + # "relocation R_X86_64_PC32 ... can not be used when making a shared + # object; recompile with -fPIC". + cmake -S . -B build -DENABLE_TESTING=OFF -DENABLE_PROGRAMS=OFF \ + -DCMAKE_POSITION_INDEPENDENT_CODE=ON + cmake --build build -j + sudo cmake --install build + - name: Setup macOS OpenSSL if: matrix.os == 'macos-latest' && matrix.crypto == 'openssl' run: echo "cmake-crypto-dir=-DOPENSSL_ROOT_DIR=$(brew --prefix openssl@1.1)" >> $GITHUB_ENV @@ -74,6 +95,24 @@ jobs: brew install mbedtls@3 echo "CMAKE_PREFIX_PATH=$(brew --prefix mbedtls@3)" >> $GITHUB_ENV + - name: Setup macOS MbedTLS 4 + if: matrix.os == 'macos-latest' && matrix.crypto == 'mbedtls4' + run: | + # Install under /tmp/ (outside $GITHUB_WORKSPACE) so the install + # survives the actions/checkout@v2 step that runs later in this job + # and clears the workspace. + python3 -m pip install --break-system-packages --upgrade jsonschema jinja2 + git clone https://github.com/Mbed-TLS/mbedtls.git /tmp/mbedtls4-src + cd /tmp/mbedtls4-src + git checkout mbedtls-4.0.0 + git submodule update --init --recursive + cmake -S . -B build -DENABLE_TESTING=OFF -DENABLE_PROGRAMS=OFF \ + -DCMAKE_POSITION_INDEPENDENT_CODE=ON \ + -DCMAKE_INSTALL_PREFIX=/tmp/mbedtls4-prefix + cmake --build build -j + cmake --install build + echo "CMAKE_PREFIX_PATH=/tmp/mbedtls4-prefix" >> $GITHUB_ENV + - uses: actions/checkout@v2 - name: Create Build Environment diff --git a/.github/workflows/meson.yml b/.github/workflows/meson.yml index 988eccbff..c45059eb3 100644 --- a/.github/workflows/meson.yml +++ b/.github/workflows/meson.yml @@ -14,7 +14,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] - crypto: [internal, openssl, openssl3, nss, mbedtls] + crypto: [internal, openssl, openssl3, nss, mbedtls, mbedtls4] exclude: - os: windows-latest crypto: openssl @@ -24,6 +24,8 @@ jobs: crypto: nss - os: windows-latest crypto: mbedtls + - os: windows-latest + crypto: mbedtls4 - os: ubuntu-latest crypto: openssl3 include: @@ -37,6 +39,8 @@ jobs: meson-crypto-enable: "-Dcrypto-library=nss" - crypto: mbedtls meson-crypto-enable: "-Dcrypto-library=mbedtls" + - crypto: mbedtls4 + meson-crypto-enable: "-Dcrypto-library=mbedtls" runs-on: ${{ matrix.os }} @@ -71,6 +75,20 @@ jobs: if: matrix.os == 'ubuntu-latest' && matrix.crypto == 'mbedtls' run: sudo apt-get install libmbedtls-dev + - name: Setup Ubuntu MbedTLS 4 + if: matrix.os == 'ubuntu-latest' && matrix.crypto == 'mbedtls4' + run: | + python3 -m pip install --break-system-packages --upgrade jsonschema jinja2 + git clone https://github.com/Mbed-TLS/mbedtls.git /tmp/mbedtls4-src + cd /tmp/mbedtls4-src + git checkout mbedtls-4.0.0 + git submodule update --init --recursive + # PIC: static libtfpsacrypto must be linkable into libsrtp2.so. + cmake -S . -B build -DENABLE_TESTING=OFF -DENABLE_PROGRAMS=OFF \ + -DCMAKE_POSITION_INDEPENDENT_CODE=ON + cmake --build build -j + sudo cmake --install build + - name: Setup macOS OpenSSL if: matrix.os == 'macos-latest' && matrix.crypto == 'openssl' run: echo "PKG_CONFIG_PATH=$(brew --prefix openssl@1.1)/lib/pkgconfig" >> $GITHUB_ENV @@ -91,6 +109,23 @@ jobs: brew install mbedtls@3 echo "PKG_CONFIG_PATH=$(brew --prefix mbedtls@3)/lib/pkgconfig" >> $GITHUB_ENV + - name: Setup macOS MbedTLS 4 + if: matrix.os == 'macos-latest' && matrix.crypto == 'mbedtls4' + run: | + # Install outside $GITHUB_WORKSPACE so the install survives the + # actions/checkout@v2 step that wipes the workspace later in the job. + python3 -m pip install --break-system-packages --upgrade jsonschema jinja2 + git clone https://github.com/Mbed-TLS/mbedtls.git /tmp/mbedtls4-src + cd /tmp/mbedtls4-src + git checkout mbedtls-4.0.0 + git submodule update --init --recursive + cmake -S . -B build -DENABLE_TESTING=OFF -DENABLE_PROGRAMS=OFF \ + -DCMAKE_POSITION_INDEPENDENT_CODE=ON \ + -DCMAKE_INSTALL_PREFIX=/tmp/mbedtls4-prefix + cmake --build build -j + cmake --install build + echo "PKG_CONFIG_PATH=/tmp/mbedtls4-prefix/lib/pkgconfig" >> $GITHUB_ENV + - uses: actions/checkout@v2 - name: Create Build Environment diff --git a/cmake/FindMbedTLS.cmake b/cmake/FindMbedTLS.cmake index a6e8a365e..384a84a60 100644 --- a/cmake/FindMbedTLS.cmake +++ b/cmake/FindMbedTLS.cmake @@ -4,13 +4,22 @@ find_library(MBEDTLS_LIBRARY mbedtls) find_library(MBEDX509_LIBRARY mbedx509) find_library(MBEDCRYPTO_LIBRARY mbedcrypto) +# mbedTLS 4.x splits the PSA Crypto implementation into its own library +# (libtfpsacrypto). libmbedcrypto remains as a thin wrapper but the actual +# psa_* symbols live in tfpsacrypto, so we must link it when present. Older +# 3.x installs do not ship this library; the find is best-effort. +find_library(MBEDTFPSACRYPTO_LIBRARY tfpsacrypto) + set(MBEDTLS_LIBRARIES "${MBEDTLS_LIBRARY}" "${MBEDX509_LIBRARY}" "${MBEDCRYPTO_LIBRARY}") +if(MBEDTFPSACRYPTO_LIBRARY) + list(APPEND MBEDTLS_LIBRARIES "${MBEDTFPSACRYPTO_LIBRARY}") +endif() include(FindPackageHandleStandardArgs) find_package_handle_standard_args(MbedTLS DEFAULT_MSG MBEDTLS_LIBRARY MBEDTLS_INCLUDE_DIRS MBEDX509_LIBRARY MBEDCRYPTO_LIBRARY) -mark_as_advanced(MBEDTLS_INCLUDE_DIRS MBEDTLS_LIBRARY MBEDX509_LIBRARY MBEDCRYPTO_LIBRARY) +mark_as_advanced(MBEDTLS_INCLUDE_DIRS MBEDTLS_LIBRARY MBEDX509_LIBRARY MBEDCRYPTO_LIBRARY MBEDTFPSACRYPTO_LIBRARY) if(NOT TARGET MbedTLS) message("in mbedtls ${MBEDTLS_LIBRARY}") diff --git a/crypto/cipher/aes_gcm_mbedtls.c b/crypto/cipher/aes_gcm_mbedtls.c index 05b83804b..83389abd9 100644 --- a/crypto/cipher/aes_gcm_mbedtls.c +++ b/crypto/cipher/aes_gcm_mbedtls.c @@ -46,7 +46,19 @@ #ifdef HAVE_CONFIG_H #include #endif +/* build_info.h was added in mbedtls 3.0; mbedtls 2.x ships version.h only. + * Use __has_include so the gate still resolves on 2.x (the legacy code path). + */ +#if defined(__has_include) && __has_include() +#include +#else +#include +#endif +#if MBEDTLS_VERSION_MAJOR >= 4 +#include +#else #include +#endif #include "aes_gcm.h" #include "alloc.h" #include "err.h" /* for srtp_debug */ @@ -133,6 +145,18 @@ static srtp_err_status_t srtp_aes_gcm_mbedtls_alloc(srtp_cipher_t **c, return (srtp_err_status_alloc_fail); } +#if MBEDTLS_VERSION_MAJOR >= 4 + gcm->ctx = + (psa_aes_gcm_ctx_t *)srtp_crypto_alloc(sizeof(psa_aes_gcm_ctx_t)); + if (gcm->ctx == NULL) { + srtp_crypto_free(gcm); + srtp_crypto_free(*c); + *c = NULL; + return srtp_err_status_alloc_fail; + } + gcm->ctx->key_id = PSA_KEY_ID_NULL; + gcm->ctx->op = psa_aead_operation_init(); +#else gcm->ctx = (mbedtls_gcm_context *)srtp_crypto_alloc(sizeof(mbedtls_gcm_context)); if (gcm->ctx == NULL) { @@ -142,6 +166,7 @@ static srtp_err_status_t srtp_aes_gcm_mbedtls_alloc(srtp_cipher_t **c, return srtp_err_status_alloc_fail; } mbedtls_gcm_init(gcm->ctx); +#endif /* set pointers */ (*c)->state = gcm; @@ -177,7 +202,12 @@ static srtp_err_status_t srtp_aes_gcm_mbedtls_dealloc(srtp_cipher_t *c) FUNC_ENTRY(); ctx = (srtp_aes_gcm_ctx_t *)c->state; if (ctx) { +#if MBEDTLS_VERSION_MAJOR >= 4 + psa_aead_abort(&ctx->ctx->op); + psa_destroy_key(ctx->ctx->key_id); +#else mbedtls_gcm_free(ctx->ctx); +#endif srtp_crypto_free(ctx->ctx); /* zeroize the key material */ octet_string_set_to_zero(ctx, sizeof(srtp_aes_gcm_ctx_t)); @@ -196,7 +226,11 @@ static srtp_err_status_t srtp_aes_gcm_mbedtls_context_init(void *cv, FUNC_ENTRY(); srtp_aes_gcm_ctx_t *c = (srtp_aes_gcm_ctx_t *)cv; uint32_t key_len_in_bits; +#if MBEDTLS_VERSION_MAJOR >= 4 + psa_status_t status = PSA_SUCCESS; +#else int errCode = 0; +#endif c->dir = srtp_direction_any; c->aad_size = 0; @@ -212,12 +246,42 @@ static srtp_err_status_t srtp_aes_gcm_mbedtls_context_init(void *cv, break; } +#if MBEDTLS_VERSION_MAJOR >= 4 + status = psa_crypto_init(); + if (status != PSA_SUCCESS) { + debug_print(srtp_mod_aes_gcm, "psa_crypto_init failed: %d", status); + return srtp_err_status_init_fail; + } + + { + psa_key_attributes_t attr = PSA_KEY_ATTRIBUTES_INIT; + psa_set_key_usage_flags(&attr, + PSA_KEY_USAGE_ENCRYPT | PSA_KEY_USAGE_DECRYPT); + psa_set_key_algorithm( + &attr, PSA_ALG_AEAD_WITH_SHORTENED_TAG(PSA_ALG_GCM, c->tag_len)); + psa_set_key_type(&attr, PSA_KEY_TYPE_AES); + psa_set_key_bits(&attr, key_len_in_bits); + + if (c->ctx->key_id != PSA_KEY_ID_NULL) { + psa_destroy_key(c->ctx->key_id); + c->ctx->key_id = PSA_KEY_ID_NULL; + } + + status = + psa_import_key(&attr, key, key_len_in_bits / 8, &c->ctx->key_id); + if (status != PSA_SUCCESS) { + debug_print(srtp_mod_aes_gcm, "psa_import_key failed: %d", status); + return srtp_err_status_init_fail; + } + } +#else errCode = mbedtls_gcm_setkey(c->ctx, MBEDTLS_CIPHER_ID_AES, (const unsigned char *)key, key_len_in_bits); if (errCode != 0) { debug_print(srtp_mod_aes_gcm, "mbedtls error code: %d", errCode); return srtp_err_status_init_fail; } +#endif return (srtp_err_status_ok); } @@ -236,6 +300,13 @@ static srtp_err_status_t srtp_aes_gcm_mbedtls_set_iv( } c->dir = direction; + /* set_iv marks the start of a fresh packet. Reset accumulated AAD so + * a prior packet's set_aad() bytes do not bleed into this packet's + * auth tag (legitimate caller sequence: set_iv -> set_aad -> drop -> + * set_iv -> set_aad -> encrypt; without this reset the second + * packet's tag covers the first packet's AAD too). */ + c->aad_size = 0; + debug_print(srtp_mod_aes_gcm, "setting iv: %s", srtp_octet_string_hex_string(iv, GCM_IV_LEN)); c->iv_len = GCM_IV_LEN; @@ -285,23 +356,141 @@ static srtp_err_status_t srtp_aes_gcm_mbedtls_encrypt(void *cv, { FUNC_ENTRY(); srtp_aes_gcm_ctx_t *c = (srtp_aes_gcm_ctx_t *)cv; - int errCode = 0; - if (c->dir != srtp_direction_encrypt && c->dir != srtp_direction_decrypt) { + /* Require dir == encrypt to catch cross-call bugs (caller passing a + * decrypt-configured ctx into the encrypt entry point). PSA does not + * catch this on its own since the imported key has both ENCRYPT and + * DECRYPT usage flags. */ + if (c->dir != srtp_direction_encrypt) { return (srtp_err_status_bad_param); } - errCode = mbedtls_gcm_crypt_and_tag(c->ctx, MBEDTLS_GCM_ENCRYPT, *enc_len, - c->iv, c->iv_len, c->aad, c->aad_size, - buf, buf, c->tag_len, c->tag); - - c->aad_size = 0; - if (errCode != 0) { - debug_print(srtp_mod_aes_gcm, "mbedtls error code: %d", errCode); - return srtp_err_status_bad_param; +#if MBEDTLS_VERSION_MAJOR >= 4 + { + /* + * libsrtp 2.x AES-GCM encrypt semantics: + * - ciphertext is written in-place into `buf` (no tag appended) + * - the auth tag is stashed in c->tag and later returned via + * get_tag() PSA's psa_aead_encrypt writes ciphertext+tag concatenated, + * so we use the multipart API which exposes the tag as a separate + * output, allowing us to keep the in-place ciphertext layout the 2.x + * callers expect. + */ + psa_status_t status; + psa_aead_operation_t op = psa_aead_operation_init(); + size_t out_off = 0; + size_t out_len = 0; + size_t tag_out_len = 0; + + status = psa_aead_encrypt_setup( + &op, c->ctx->key_id, + PSA_ALG_AEAD_WITH_SHORTENED_TAG(PSA_ALG_GCM, c->tag_len)); + if (status != PSA_SUCCESS) { + debug_print(srtp_mod_aes_gcm, "psa_aead_encrypt_setup failed: %d", + status); + goto enc_fail; + } + + status = psa_aead_set_lengths(&op, c->aad_size, *enc_len); + if (status != PSA_SUCCESS) { + debug_print(srtp_mod_aes_gcm, "psa_aead_set_lengths failed: %d", + status); + goto enc_fail; + } + + status = psa_aead_set_nonce(&op, c->iv, c->iv_len); + if (status != PSA_SUCCESS) { + debug_print(srtp_mod_aes_gcm, "psa_aead_set_nonce failed: %d", + status); + goto enc_fail; + } + + if (c->aad_size > 0) { + status = psa_aead_update_ad(&op, c->aad, c->aad_size); + if (status != PSA_SUCCESS) { + debug_print(srtp_mod_aes_gcm, "psa_aead_update_ad failed: %d", + status); + goto enc_fail; + } + } + + /* + * psa_aead_update output buffer must be at least + * PSA_AEAD_UPDATE_OUTPUT_SIZE bytes (block-aligned for GCM); pass the + * full payload length which is always sufficient. In-place src==dst is + * permitted for PSA AEAD. + * + * AAD-only authentication is legal: *enc_len == 0 and buf may be + * NULL. Some PSA backends reject a NULL output pointer even when + * output_size is 0, so guard symmetrically with the decrypt path. + */ + out_len = 0; + if (*enc_len > 0) { + status = + psa_aead_update(&op, buf, *enc_len, buf, *enc_len, &out_len); + if (status != PSA_SUCCESS) { + debug_print(srtp_mod_aes_gcm, "psa_aead_update failed: %d", + status); + goto enc_fail; + } + } + out_off = out_len; + + /* + * psa_aead_finish writes any trailing ciphertext to a separate buffer. + * For GCM the trailing part is always 0 bytes, but the spec allows the + * implementation to defer up to one block, so we route it through a + * 16-byte scratch and copy any returned bytes back into buf at out_off. + */ + { + uint8_t finish_scratch[16]; + status = + psa_aead_finish(&op, finish_scratch, sizeof(finish_scratch), + &out_len, c->tag, sizeof(c->tag), &tag_out_len); + if (status != PSA_SUCCESS) { + debug_print(srtp_mod_aes_gcm, "psa_aead_finish failed: %d", + status); + goto enc_fail; + } + if (out_len > 0) { + if (out_off + out_len > *enc_len) { + psa_aead_abort(&op); + c->aad_size = 0; + return srtp_err_status_cipher_fail; + } + memcpy(buf + out_off, finish_scratch, out_len); + } + } + + if (tag_out_len != (size_t)c->tag_len) { + psa_aead_abort(&op); + c->aad_size = 0; + return srtp_err_status_cipher_fail; + } + + c->aad_size = 0; + return srtp_err_status_ok; + + enc_fail: + psa_aead_abort(&op); + c->aad_size = 0; + return srtp_err_status_cipher_fail; } - - return (srtp_err_status_ok); +#else + { + int errCode = mbedtls_gcm_crypt_and_tag( + c->ctx, MBEDTLS_GCM_ENCRYPT, *enc_len, c->iv, c->iv_len, c->aad, + c->aad_size, buf, buf, c->tag_len, c->tag); + + c->aad_size = 0; + if (errCode != 0) { + debug_print(srtp_mod_aes_gcm, "mbedtls error code: %d", errCode); + return srtp_err_status_bad_param; + } + + return (srtp_err_status_ok); + } +#endif } /* @@ -341,30 +530,131 @@ static srtp_err_status_t srtp_aes_gcm_mbedtls_decrypt(void *cv, { FUNC_ENTRY(); srtp_aes_gcm_ctx_t *c = (srtp_aes_gcm_ctx_t *)cv; - int errCode = 0; - if (c->dir != srtp_direction_encrypt && c->dir != srtp_direction_decrypt) { + /* Require dir == decrypt to catch cross-call bugs (see encrypt for + * rationale). */ + if (c->dir != srtp_direction_decrypt) { return (srtp_err_status_bad_param); } debug_print(srtp_mod_aes_gcm, "AAD: %s", srtp_octet_string_hex_string(c->aad, c->aad_size)); - errCode = mbedtls_gcm_auth_decrypt( - c->ctx, (*enc_len - c->tag_len), c->iv, c->iv_len, c->aad, c->aad_size, - buf + (*enc_len - c->tag_len), c->tag_len, buf, buf); - c->aad_size = 0; - if (errCode != 0) { - return (srtp_err_status_auth_fail); +#if MBEDTLS_VERSION_MAJOR >= 4 + { + /* + * libsrtp 2.x AES-GCM decrypt semantics: + * - input buf holds ciphertext || tag of total length *enc_len + * - plaintext is written in-place starting at buf + * - on success *enc_len is reduced by c->tag_len + * + * PSA exposes the tag as a separate input to psa_aead_verify, so we + * use the multipart API and feed buf[..ct_len] to update and the + * trailing c->tag_len bytes to verify. + */ + psa_status_t status; + psa_aead_operation_t op = psa_aead_operation_init(); + size_t ct_len; + size_t out_off = 0; + size_t out_len = 0; + + if ((unsigned int)c->tag_len > *enc_len) { + c->aad_size = 0; + return srtp_err_status_bad_param; + } + ct_len = (size_t)(*enc_len - (unsigned int)c->tag_len); + + status = psa_aead_decrypt_setup( + &op, c->ctx->key_id, + PSA_ALG_AEAD_WITH_SHORTENED_TAG(PSA_ALG_GCM, c->tag_len)); + if (status != PSA_SUCCESS) { + debug_print(srtp_mod_aes_gcm, "psa_aead_decrypt_setup failed: %d", + status); + goto dec_fail; + } + + status = psa_aead_set_lengths(&op, c->aad_size, ct_len); + if (status != PSA_SUCCESS) { + debug_print(srtp_mod_aes_gcm, "psa_aead_set_lengths failed: %d", + status); + goto dec_fail; + } + + status = psa_aead_set_nonce(&op, c->iv, c->iv_len); + if (status != PSA_SUCCESS) { + debug_print(srtp_mod_aes_gcm, "psa_aead_set_nonce failed: %d", + status); + goto dec_fail; + } + + if (c->aad_size > 0) { + status = psa_aead_update_ad(&op, c->aad, c->aad_size); + if (status != PSA_SUCCESS) { + debug_print(srtp_mod_aes_gcm, "psa_aead_update_ad failed: %d", + status); + goto dec_fail; + } + } + + /* In-place src==dst is permitted for PSA AEAD. */ + if (ct_len > 0) { + status = psa_aead_update(&op, buf, ct_len, buf, ct_len, &out_len); + if (status != PSA_SUCCESS) { + debug_print(srtp_mod_aes_gcm, "psa_aead_update failed: %d", + status); + goto dec_fail; + } + out_off = out_len; + } + + { + uint8_t finish_scratch[16]; + status = + psa_aead_verify(&op, finish_scratch, sizeof(finish_scratch), + &out_len, buf + ct_len, (size_t)c->tag_len); + if (status != PSA_SUCCESS) { + psa_aead_abort(&op); + c->aad_size = 0; + return srtp_err_status_auth_fail; + } + if (out_len > 0) { + if (out_off + out_len > ct_len) { + psa_aead_abort(&op); + c->aad_size = 0; + return srtp_err_status_cipher_fail; + } + memcpy(buf + out_off, finish_scratch, out_len); + } + } + + c->aad_size = 0; + *enc_len -= c->tag_len; + return srtp_err_status_ok; + + dec_fail: + psa_aead_abort(&op); + c->aad_size = 0; + return srtp_err_status_auth_fail; } - - /* - * Reduce the buffer size by the tag length since the tag - * is not part of the original payload - */ - *enc_len -= c->tag_len; - - return (srtp_err_status_ok); +#else + { + int errCode = mbedtls_gcm_auth_decrypt( + c->ctx, (*enc_len - c->tag_len), c->iv, c->iv_len, c->aad, + c->aad_size, buf + (*enc_len - c->tag_len), c->tag_len, buf, buf); + c->aad_size = 0; + if (errCode != 0) { + return (srtp_err_status_auth_fail); + } + + /* + * Reduce the buffer size by the tag length since the tag + * is not part of the original payload + */ + *enc_len -= c->tag_len; + + return (srtp_err_status_ok); + } +#endif } /* diff --git a/crypto/cipher/aes_icm_mbedtls.c b/crypto/cipher/aes_icm_mbedtls.c index bb5c4b79f..3e6046e06 100644 --- a/crypto/cipher/aes_icm_mbedtls.c +++ b/crypto/cipher/aes_icm_mbedtls.c @@ -45,7 +45,20 @@ #ifdef HAVE_CONFIG_H #include #endif +/* build_info.h was added in mbedtls 3.0; mbedtls 2.x ships version.h only. + * Use __has_include so the gate still resolves on 2.x (the legacy code path). + */ +#if defined(__has_include) && __has_include() +#include +#else +#include +#endif +#if MBEDTLS_VERSION_MAJOR >= 4 +#include +#include +#else #include +#endif #include "aes_icm_ext.h" #include "crypto_types.h" #include "err.h" /* for srtp_debug */ @@ -145,6 +158,19 @@ static srtp_err_status_t srtp_aes_icm_mbedtls_alloc(srtp_cipher_t **c, return srtp_err_status_alloc_fail; } +#if MBEDTLS_VERSION_MAJOR >= 4 + icm->ctx = + (psa_aes_icm_ctx_t *)srtp_crypto_alloc(sizeof(psa_aes_icm_ctx_t)); + if (icm->ctx == NULL) { + srtp_crypto_free(icm); + srtp_crypto_free(*c); + *c = NULL; + return srtp_err_status_alloc_fail; + } + + icm->ctx->key_id = PSA_KEY_ID_NULL; + icm->ctx->op = psa_cipher_operation_init(); +#else icm->ctx = (mbedtls_aes_context *)srtp_crypto_alloc(sizeof(mbedtls_aes_context)); if (icm->ctx == NULL) { @@ -155,6 +181,7 @@ static srtp_err_status_t srtp_aes_icm_mbedtls_alloc(srtp_cipher_t **c, } mbedtls_aes_init(icm->ctx); +#endif /* set pointers */ (*c)->state = icm; @@ -200,7 +227,12 @@ static srtp_err_status_t srtp_aes_icm_mbedtls_dealloc(srtp_cipher_t *c) */ ctx = (srtp_aes_icm_ctx_t *)c->state; if (ctx != NULL) { +#if MBEDTLS_VERSION_MAJOR >= 4 + psa_cipher_abort(&ctx->ctx->op); + psa_destroy_key(ctx->ctx->key_id); +#else mbedtls_aes_free(ctx->ctx); +#endif srtp_crypto_free(ctx->ctx); /* zeroize the key material */ octet_string_set_to_zero(ctx, sizeof(srtp_aes_icm_ctx_t)); @@ -218,7 +250,18 @@ static srtp_err_status_t srtp_aes_icm_mbedtls_context_init(void *cv, { srtp_aes_icm_ctx_t *c = (srtp_aes_icm_ctx_t *)cv; uint32_t key_size_in_bits = (c->key_size << 3); +#if MBEDTLS_VERSION_MAJOR >= 4 + psa_status_t status = PSA_SUCCESS; + + /* psa_crypto_init() is idempotent and required before any PSA use. */ + status = psa_crypto_init(); + if (status != PSA_SUCCESS) { + debug_print(srtp_mod_aes_icm, "psa_crypto_init failed: %d", status); + return srtp_err_status_init_fail; + } +#else int errcode = 0; +#endif /* * set counter and initial values to 'offset' value, being careful not to @@ -246,10 +289,46 @@ static srtp_err_status_t srtp_aes_icm_mbedtls_context_init(void *cv, break; } +#if MBEDTLS_VERSION_MAJOR >= 4 + { + psa_key_attributes_t attr = PSA_KEY_ATTRIBUTES_INIT; + psa_set_key_type(&attr, PSA_KEY_TYPE_AES); + psa_set_key_bits(&attr, key_size_in_bits); + psa_set_key_usage_flags(&attr, + PSA_KEY_USAGE_ENCRYPT | PSA_KEY_USAGE_DECRYPT); + /* SRTP AES-ICM spec only allows the low 16 bits of the counter to + * vary per packet (top 14 bytes are the salted nonce, see RFC 3711). + * PSA_ALG_CTR increments the full 128-bit counter, identical to the + * legacy mbedtls_aes_crypt_ctr path. For SRTP packet sizes + * (< 2^16 blocks = 1 MiB) the keystreams agree, so all conformant + * RTP/RTCP payloads are correct. Buffers > 1 MiB are not + * SRTP-conformant on either backend. */ + psa_set_key_algorithm(&attr, PSA_ALG_CTR); + + if (c->ctx->key_id != PSA_KEY_ID_NULL) { + /* Abort any in-flight cipher op before destroying its key: + * a leftover op from a prior set_iv() still references key_id, + * and on PSA implementations that eagerly invalidate op key + * handles, destroy_key would leave the op in a bad state. */ + psa_cipher_abort(&c->ctx->op); + c->ctx->op = psa_cipher_operation_init(); + psa_destroy_key(c->ctx->key_id); + c->ctx->key_id = PSA_KEY_ID_NULL; + } + + status = + psa_import_key(&attr, key, key_size_in_bits / 8, &(c->ctx->key_id)); + if (status != PSA_SUCCESS) { + debug_print(srtp_mod_aes_icm, "psa_import_key failed: %d", status); + return srtp_err_status_init_fail; + } + } +#else errcode = mbedtls_aes_setkey_enc(c->ctx, key, key_size_in_bits); if (errcode != 0) { debug_print(srtp_mod_aes_icm, "errCode: %d", errcode); } +#endif return srtp_err_status_ok; } @@ -278,6 +357,33 @@ static srtp_err_status_t srtp_aes_icm_mbedtls_set_iv( debug_print(srtp_mod_aes_icm, "set_counter: %s", v128_hex_string(&c->counter)); +#if MBEDTLS_VERSION_MAJOR >= 4 + { + psa_status_t status; + + /* Reset any prior in-flight cipher operation. */ + psa_cipher_abort(&c->ctx->op); + c->ctx->op = psa_cipher_operation_init(); + + status = + psa_cipher_encrypt_setup(&c->ctx->op, c->ctx->key_id, PSA_ALG_CTR); + if (status != PSA_SUCCESS) { + debug_print(srtp_mod_aes_icm, "psa_cipher_encrypt_setup failed: %d", + status); + psa_cipher_abort(&c->ctx->op); + return srtp_err_status_cipher_fail; + } + + status = psa_cipher_set_iv(&c->ctx->op, c->counter.v8, 16); + if (status != PSA_SUCCESS) { + debug_print(srtp_mod_aes_icm, "psa_cipher_set_iv failed: %d", + status); + psa_cipher_abort(&c->ctx->op); + return srtp_err_status_cipher_fail; + } + } +#endif + return srtp_err_status_ok; } @@ -295,16 +401,55 @@ static srtp_err_status_t srtp_aes_icm_mbedtls_encrypt(void *cv, { srtp_aes_icm_ctx_t *c = (srtp_aes_icm_ctx_t *)cv; - int errCode = 0; debug_print(srtp_mod_aes_icm, "rs0: %s", v128_hex_string(&c->counter)); - errCode = - mbedtls_aes_crypt_ctr(c->ctx, *enc_len, &(c->nc_off), c->counter.v8, - c->stream_block.v8, buf, buf); - if (errCode != 0) { - debug_print(srtp_mod_aes_icm, "encrypt error: %d", errCode); - return srtp_err_status_cipher_fail; +#if MBEDTLS_VERSION_MAJOR >= 4 + { + psa_status_t status; + size_t out_len = 0; + + /* + * libsrtp's AES-ICM interface is incremental: encrypt() may be called + * multiple times after a single set_iv(). psa_cipher_update can be + * called repeatedly on the same operation handle to match that + * semantics; psa_cipher_finish is only invoked at dealloc/set_iv-reset + * time via psa_cipher_abort. In-place src==dst is supported by PSA. + */ + status = psa_cipher_update(&c->ctx->op, buf, *enc_len, buf, *enc_len, + &out_len); + if (status != PSA_SUCCESS) { + debug_print(srtp_mod_aes_icm, "psa_cipher_update failed: %d", + status); + psa_cipher_abort(&c->ctx->op); + return srtp_err_status_cipher_fail; + } + /* Mainline mbedTLS PSA implements CTR as a stream cipher and returns + * out_len == in_len, but the PSA spec lets a backend defer bytes to + * psa_cipher_finish. libsrtp's set_iv-then-encrypt model has no + * finish() call between packets, so a deferring backend would leak + * partial output. If this ever fires in the wild, the proper fix is + * to call psa_cipher_finish() and accumulate; until then, log and + * fail loudly so we notice rather than silently corrupting data. */ + if (out_len != *enc_len) { + debug_print2(srtp_mod_aes_icm, + "psa_cipher_update short write: %zu of %u " + "(PSA backend defers stream-cipher output; " + "unsupported in this wrapper)", + out_len, *enc_len); + return srtp_err_status_cipher_fail; + } } +#else + { + int errCode = + mbedtls_aes_crypt_ctr(c->ctx, *enc_len, &(c->nc_off), c->counter.v8, + c->stream_block.v8, buf, buf); + if (errCode != 0) { + debug_print(srtp_mod_aes_icm, "encrypt error: %d", errCode); + return srtp_err_status_cipher_fail; + } + } +#endif return srtp_err_status_ok; } diff --git a/crypto/hash/hmac_mbedtls.c b/crypto/hash/hmac_mbedtls.c index d109791fa..ff86c62c8 100644 --- a/crypto/hash/hmac_mbedtls.c +++ b/crypto/hash/hmac_mbedtls.c @@ -49,7 +49,24 @@ #include "alloc.h" #include "err.h" /* for srtp_debug */ #include "auth_test_cases.h" +/* build_info.h was added in mbedtls 3.0; mbedtls 2.x ships version.h only. + * Use __has_include so the gate still resolves on 2.x (the legacy code path). + */ +#if defined(__has_include) && __has_include() +#include +#else +#include +#endif +#if MBEDTLS_VERSION_MAJOR >= 4 +#include + +typedef struct { + psa_mac_operation_t op; + psa_key_id_t key_id; +} psa_hmac_ctx_t; +#else #include +#endif #define SHA1_DIGEST_SIZE 20 @@ -80,6 +97,27 @@ static srtp_err_status_t srtp_hmac_mbedtls_alloc(srtp_auth_t **a, if (*a == NULL) { return srtp_err_status_alloc_fail; } +#if MBEDTLS_VERSION_MAJOR >= 4 + { + psa_hmac_ctx_t *state; + psa_status_t status = psa_crypto_init(); + if (status != PSA_SUCCESS) { + debug_print(srtp_mod_hmac, "psa_crypto_init failed: %d", status); + srtp_crypto_free(*a); + *a = NULL; + return srtp_err_status_init_fail; + } + state = (psa_hmac_ctx_t *)srtp_crypto_alloc(sizeof(psa_hmac_ctx_t)); + if (state == NULL) { + srtp_crypto_free(*a); + *a = NULL; + return srtp_err_status_alloc_fail; + } + state->key_id = PSA_KEY_ID_NULL; + state->op = psa_mac_operation_init(); + (*a)->state = state; + } +#else // allocate the buffer of mbedtls context. (*a)->state = srtp_crypto_alloc(sizeof(mbedtls_md_context_t)); if ((*a)->state == NULL) { @@ -88,6 +126,7 @@ static srtp_err_status_t srtp_hmac_mbedtls_alloc(srtp_auth_t **a, return srtp_err_status_alloc_fail; } mbedtls_md_init((mbedtls_md_context_t *)(*a)->state); +#endif /* set pointers */ (*a)->type = &srtp_hmac; @@ -100,10 +139,19 @@ static srtp_err_status_t srtp_hmac_mbedtls_alloc(srtp_auth_t **a, static srtp_err_status_t srtp_hmac_mbedtls_dealloc(srtp_auth_t *a) { +#if MBEDTLS_VERSION_MAJOR >= 4 + psa_hmac_ctx_t *hmac_ctx = (psa_hmac_ctx_t *)a->state; + if (hmac_ctx) { + psa_mac_abort(&hmac_ctx->op); + psa_destroy_key(hmac_ctx->key_id); + srtp_crypto_free(hmac_ctx); + } +#else mbedtls_md_context_t *hmac_ctx; hmac_ctx = (mbedtls_md_context_t *)a->state; mbedtls_md_free(hmac_ctx); srtp_crypto_free(hmac_ctx); +#endif /* zeroize entire state*/ octet_string_set_to_zero(a, sizeof(srtp_auth_t)); @@ -115,17 +163,81 @@ static srtp_err_status_t srtp_hmac_mbedtls_dealloc(srtp_auth_t *a) static srtp_err_status_t srtp_hmac_mbedtls_start(void *statev) { +#if MBEDTLS_VERSION_MAJOR >= 4 + psa_hmac_ctx_t *state = (psa_hmac_ctx_t *)statev; + /* + * Reset any in-flight MAC operation before re-priming. psa_mac_abort is + * idempotent on a freshly-initialised handle so this is safe on first use. + */ + psa_mac_abort(&state->op); + state->op = psa_mac_operation_init(); + + if (psa_mac_sign_setup(&state->op, state->key_id, + PSA_ALG_HMAC(PSA_ALG_SHA_1)) != PSA_SUCCESS) { + psa_mac_abort(&state->op); + return srtp_err_status_auth_fail; + } + return srtp_err_status_ok; +#else mbedtls_md_context_t *state = (mbedtls_md_context_t *)statev; if (mbedtls_md_hmac_reset(state) != 0) return srtp_err_status_auth_fail; return srtp_err_status_ok; +#endif } static srtp_err_status_t srtp_hmac_mbedtls_init(void *statev, const uint8_t *key, int key_len) { +#if MBEDTLS_VERSION_MAJOR >= 4 + psa_hmac_ctx_t *state = (psa_hmac_ctx_t *)statev; + psa_status_t status; + psa_key_attributes_t attr = PSA_KEY_ATTRIBUTES_INIT; + + if (key_len < 0) { + return srtp_err_status_bad_param; + } + + psa_set_key_type(&attr, PSA_KEY_TYPE_HMAC); + /* SIGN_HASH is the PSA 1.0 baseline accepted by psa_mac_sign_setup; + * SIGN_MESSAGE was added in 1.1 as a superset. Set both so the import + * works on PSA 1.0 drivers (TF-M secure-image builds) as well as on + * mainline mbedTLS 4. */ + psa_set_key_usage_flags(&attr, PSA_KEY_USAGE_SIGN_HASH | + PSA_KEY_USAGE_SIGN_MESSAGE); + psa_set_key_algorithm(&attr, PSA_ALG_HMAC(PSA_ALG_SHA_1)); + + if (state->key_id != PSA_KEY_ID_NULL) { + psa_destroy_key(state->key_id); + state->key_id = PSA_KEY_ID_NULL; + } + + status = psa_import_key(&attr, key, (size_t)key_len, &state->key_id); + if (status != PSA_SUCCESS) { + debug_print(srtp_mod_hmac, "psa_import_key failed: %d", status); + return srtp_err_status_auth_fail; + } + + /* Prime the operation so update()/finish() can be called without start(). + */ + psa_mac_abort(&state->op); + state->op = psa_mac_operation_init(); + status = psa_mac_sign_setup(&state->op, state->key_id, + PSA_ALG_HMAC(PSA_ALG_SHA_1)); + if (status != PSA_SUCCESS) { + psa_mac_abort(&state->op); + /* Free the freshly imported key so we don't leak the slot if the + * caller treats auth_fail as fatal and skips dealloc. */ + psa_destroy_key(state->key_id); + state->key_id = PSA_KEY_ID_NULL; + debug_print(srtp_mod_hmac, "psa_mac_sign_setup failed: %d", status); + return srtp_err_status_auth_fail; + } + + return srtp_err_status_ok; +#else mbedtls_md_context_t *state = (mbedtls_md_context_t *)statev; const mbedtls_md_info_t *info = NULL; @@ -145,12 +257,30 @@ static srtp_err_status_t srtp_hmac_mbedtls_init(void *statev, return srtp_err_status_auth_fail; return srtp_err_status_ok; +#endif } static srtp_err_status_t srtp_hmac_mbedtls_update(void *statev, const uint8_t *message, int msg_octets) { +#if MBEDTLS_VERSION_MAJOR >= 4 + psa_hmac_ctx_t *state = (psa_hmac_ctx_t *)statev; + + if (msg_octets < 0) { + return srtp_err_status_bad_param; + } + + debug_print(srtp_mod_hmac, "input: %s", + srtp_octet_string_hex_string(message, msg_octets)); + + if (psa_mac_update(&state->op, message, (size_t)msg_octets) != + PSA_SUCCESS) { + psa_mac_abort(&state->op); + return srtp_err_status_auth_fail; + } + return srtp_err_status_ok; +#else mbedtls_md_context_t *state = (mbedtls_md_context_t *)statev; debug_print(srtp_mod_hmac, "input: %s", @@ -160,6 +290,7 @@ static srtp_err_status_t srtp_hmac_mbedtls_update(void *statev, return srtp_err_status_auth_fail; return srtp_err_status_ok; +#endif } static srtp_err_status_t srtp_hmac_mbedtls_compute(void *statev, @@ -168,7 +299,6 @@ static srtp_err_status_t srtp_hmac_mbedtls_compute(void *statev, int tag_len, uint8_t *result) { - mbedtls_md_context_t *state = (mbedtls_md_context_t *)statev; uint8_t hash_value[SHA1_DIGEST_SIZE]; int i; @@ -177,12 +307,34 @@ static srtp_err_status_t srtp_hmac_mbedtls_compute(void *statev, return srtp_err_status_bad_param; } - /* hash message, copy output into H */ - if (mbedtls_md_hmac_update(statev, message, msg_octets) != 0) - return srtp_err_status_auth_fail; - - if (mbedtls_md_hmac_finish(state, hash_value) != 0) - return srtp_err_status_auth_fail; +#if MBEDTLS_VERSION_MAJOR >= 4 + { + psa_hmac_ctx_t *state = (psa_hmac_ctx_t *)statev; + size_t out_len = 0; + + if (psa_mac_update(&state->op, message, (size_t)msg_octets) != + PSA_SUCCESS) { + psa_mac_abort(&state->op); + return srtp_err_status_auth_fail; + } + + if (psa_mac_sign_finish(&state->op, hash_value, sizeof(hash_value), + &out_len) != PSA_SUCCESS) { + psa_mac_abort(&state->op); + return srtp_err_status_auth_fail; + } + } +#else + { + mbedtls_md_context_t *state = (mbedtls_md_context_t *)statev; + /* hash message, copy output into H */ + if (mbedtls_md_hmac_update(statev, message, msg_octets) != 0) + return srtp_err_status_auth_fail; + + if (mbedtls_md_hmac_finish(state, hash_value) != 0) + return srtp_err_status_auth_fail; + } +#endif /* copy hash_value to *result */ for (i = 0; i < tag_len; i++) { diff --git a/crypto/include/aes_gcm.h b/crypto/include/aes_gcm.h index cd0dceed3..86d910a3b 100644 --- a/crypto/include/aes_gcm.h +++ b/crypto/include/aes_gcm.h @@ -66,6 +66,37 @@ typedef struct { #ifdef MBEDTLS #define MAX_AD_SIZE 2048 +/* build_info.h was added in mbedtls 3.0; mbedtls 2.x ships version.h only. + * Use __has_include so the gate still resolves on 2.x (the legacy code path). + */ +#if defined(__has_include) && __has_include() +#include +#else +#include +#endif + +#if MBEDTLS_VERSION_MAJOR >= 4 +#include + +typedef struct { + psa_key_id_t key_id; + psa_aead_operation_t op; +} psa_aes_gcm_ctx_t; + +typedef struct { + int key_size; + int tag_len; + int aad_size; + int iv_len; + uint8_t iv[12]; + uint8_t tag[16]; + uint8_t aad[MAX_AD_SIZE]; + psa_aes_gcm_ctx_t *ctx; + srtp_cipher_direction_t dir; +} srtp_aes_gcm_ctx_t; + +#else /* MBEDTLS_VERSION_MAJOR >= 4 */ + #include #include @@ -81,6 +112,8 @@ typedef struct { srtp_cipher_direction_t dir; } srtp_aes_gcm_ctx_t; +#endif /* MBEDTLS_VERSION_MAJOR >= 4 */ + #endif /* MBEDTLS */ #ifdef NSS diff --git a/crypto/include/aes_icm_ext.h b/crypto/include/aes_icm_ext.h index 5b21c95dd..406ad782e 100644 --- a/crypto/include/aes_icm_ext.h +++ b/crypto/include/aes_icm_ext.h @@ -65,6 +65,34 @@ typedef struct { #ifdef MBEDTLS +/* build_info.h was added in mbedtls 3.0; mbedtls 2.x ships version.h only. + * Use __has_include so the gate still resolves on 2.x (the legacy code path). + */ +#if defined(__has_include) && __has_include() +#include +#else +#include +#endif + +#if MBEDTLS_VERSION_MAJOR >= 4 +#include + +typedef struct { + psa_key_id_t key_id; + psa_cipher_operation_t op; +} psa_aes_icm_ctx_t; + +typedef struct { + v128_t counter; /* holds the counter value */ + v128_t offset; /* initial offset value */ + v128_t stream_block; + size_t nc_off; + int key_size; + psa_aes_icm_ctx_t *ctx; +} srtp_aes_icm_ctx_t; + +#else /* MBEDTLS_VERSION_MAJOR >= 4 */ + #include typedef struct { v128_t counter; /* holds the counter value */ @@ -75,6 +103,8 @@ typedef struct { mbedtls_aes_context *ctx; } srtp_aes_icm_ctx_t; +#endif /* MBEDTLS_VERSION_MAJOR >= 4 */ + #endif /* MBEDTLS */ #ifdef NSS diff --git a/meson.build b/meson.build index ff8a21009..08b00697b 100644 --- a/meson.build +++ b/meson.build @@ -159,6 +159,14 @@ elif crypto_library == 'mbedtls' mbedtls_dep = cc.find_library('mbedcrypto', has_headers: ['mbedtls/aes.h'], required: true) endif srtp2_deps += [mbedtls_dep] + # mbedTLS 4.x ships the PSA Crypto implementation as a separate library + # (libtfpsacrypto); libmbedcrypto on 4.x is a thin wrapper that does not + # contain the psa_* symbols. Link it when present; older 3.x installs + # simply won't have it and this is a no-op. + mbedtls_psa_dep = cc.find_library('tfpsacrypto', required: false) + if mbedtls_psa_dep.found() + srtp2_deps += [mbedtls_psa_dep] + endif cdata.set('GCM', true) cdata.set('MBEDTLS', true) cdata.set('USE_EXTERNAL_CRYPTO', true)