Merge mbed-crypto into mbedtls: the merge commit

Merge `unremove-non-crypto` into `mbedtls/development`. The branch
`unremove-non-crypto` was obtained by starting from `mbed-crypto/development`,
then reverting many commits that removed X.509 and TLS functionality when Mbed
Crypto forked from Mbed TLS (the “unremoval”), then make a few tweaks to
facilitate the merge.

The unremoval step restored old versions of some tls files. If a file doesn't
exist in mbed-crypto, check out the mbedtls version, regardless of what
happened during the unremoval of tls files in the crypto tree. Also
unconditionally take the mbedtls version of a few files where the
modifications are completely project-specific and are not relevant in
mbed-crypto:

* `.github/issue_template.md`: completely different. We may want to reconcile
  them independently as a follow-up.
* `.travis.yml`: would only be reverted to an earlier tls version.
* `README.md`: completely different. We may want to reconcile them
  independently as a follow-up.
* `doxygen/input/doc_mainpage.h`: the changes in crypto were minimal and not
  relevant except as a stopgap as mbed-crypto did not have its own product
  versioning in the Doxygen documentation.
* `tests/.jenkins/Jenkinsfile`: completely different.
* `tests/data_files/Makefile`: there were no changes in mbed-crypto,
  but the unremoval step restored an old version.

Shell script for everything to do after the merge apart from the conflict
resolution:
```
tls_files=($(comm -23 <(git ls-tree -r --name-only HEAD) <(git ls-tree -r --name-only $(git merge-base upstream-crypto/development MERGE_HEAD))))
tls_files+=($tls_files .github/issue_template.md .travis.yml README.md doxygen/input/doc_mainpage.h tests/.jenkins/Jenkinsfile tests/data_files/Makefile)
git checkout --theirs HEAD -- $tls_files
git add -- $tls_files
```

Resolve the remaining conflicts:

* `library/CMakeLists.txt`:
    * Keep the TLS definition of `src_crypto`
    * `USE_SHARED_MBEDTLS_LIBRARY`: keep all three libraries, with both
      `include` and `crypto/include` in `target_include_directories`, all with
      version `2.21.0`.
* `programs/Makefile`:
    * Reconcile the APPS lists (add/add from a differently-formatted common
      ancestor): insert the `psa/*` from crypto into the tls list.
    * Keep the `fuzz` target defined only in tls version.
    * Keep the recipe (only in tls version) cleaning `ssl_pthread_server`
      stuff for the `clean` target.
* `scripts/config.py`:
    * `include_in_full`: add/add conflict. Keep both.
* `tests/scripts/all.sh`:
    * `component_test_no_use_psa_crypto_full_cmake_asan`: partially old
      version in crypto. Take the tls version.
    * `component_test_malloc_0_null` and more: take
      `component_test_malloc_0_null` from crypto (with `config.py` rather than
      `config.pl`, and with `$ASAN_FLAGS` rather than an explicit list), but
      add the call to `ssl-opt.sh` from tls. Take the other components from
      crypto.

With this commit, building and running the unit tests with both `make ` and
`cmake` work in the default configuration on Linux. Other platforms, build
systems and configurations are likely not to work, and there is some
regression in test coverage.

There is some loss of functionality because the unremoval step restored older
versions of tls content. This commit contains the latest tls version of
tls-only files, but some changes from the tls side in files that existed on
both sides have regressed. Most problematic changes are hunks that remove some
tls-specific feature and contain either a C preprocessor symbol identifying a
tls-specific module or option, or the name of a tls-specific file. Hunks
that remove a tls-specific preprocessor symbol can be identified with the
regular expression `^-.*MBEDTLS_(ERR_)?(PKCS11|X509|NET|SSL)_`.

Subsequent commits will revert a few parts of the patch from this merge commit
in order to restore the tls functionality that it removes, ensure that the
test coverage includes what was covered in either branch, and fix test
failures.
This commit is contained in:
Gilles Peskine 2020-03-19 15:38:54 +01:00
commit b99bd39b4e
397 changed files with 168444 additions and 995 deletions

View file

@ -24,8 +24,7 @@
# configurations, and can and will arbitrarily change the current CMake
# configuration. The following files must be committed into git:
# * include/mbedtls/config.h
# * Makefile, library/Makefile, programs/Makefile, tests/Makefile,
# programs/fuzz/Makefile
# * Makefile, library/Makefile, programs/Makefile, tests/Makefile
# After running this script, the CMake cache will be lost and CMake
# will no longer be initialised.
#
@ -76,9 +75,9 @@
# * Run `make clean`.
# * Restore `include/mbedtks/config.h` from a backup made before running
# the component.
# * Check out `Makefile`, `library/Makefile`, `programs/Makefile`,
# `tests/Makefile` and `programs/fuzz/Makefile` from git.
# This cleans up after an in-tree use of CMake.
# * Check out `Makefile`, `library/Makefile`, `programs/Makefile` and
# `tests/Makefile` from git. This cleans up after an in-tree use of
# CMake.
#
# Any command that is expected to fail must be protected so that the
# script keeps running in --keep-going mode despite `set -e`. In keep-going
@ -144,6 +143,9 @@ pre_initialize_variables () {
export MAKEFLAGS="-j"
fi
# Include more verbose output for failing tests run by CMake
export CTEST_OUTPUT_ON_FAILURE=1
# CFLAGS and LDFLAGS for Asan builds that don't use CMake
ASAN_CFLAGS='-Werror -Wall -Wextra -fsanitize=address,undefined -fno-sanitize-recover=all'
@ -236,9 +238,6 @@ cleanup()
fi
command make clean
cd crypto
command make clean
cd ..
# Remove CMake artefacts
find . -name .git -prune -o \
@ -248,13 +247,13 @@ cleanup()
-iname CMakeCache.txt \) -exec rm {} \+
# Recover files overwritten by in-tree CMake builds
rm -f include/Makefile include/mbedtls/Makefile programs/*/Makefile
git update-index --no-skip-worktree Makefile library/Makefile programs/Makefile tests/Makefile programs/fuzz/Makefile
git checkout -- Makefile library/Makefile programs/Makefile tests/Makefile programs/fuzz/Makefile
cd crypto
rm -f include/Makefile include/mbedtls/Makefile programs/*/Makefile
git update-index --no-skip-worktree Makefile library/Makefile programs/Makefile tests/Makefile
git checkout -- Makefile library/Makefile programs/Makefile tests/Makefile
cd ..
# Remove any artifacts from the component_test_cmake_as_subdirectory test.
rm -rf programs/test/cmake_subproject/build
rm -f programs/test/cmake_subproject/Makefile
rm -f programs/test/cmake_subproject/cmake_subproject
# Remove any artifacts from the component_test_cmake_as_subdirectory test.
rm -rf programs/test/cmake_subproject/build
@ -334,6 +333,9 @@ pre_parse_command_line () {
all_except=0
no_armcc=
# Note that legacy options are ignored instead of being omitted from this
# list of options, so invocations that worked with previous version of
# all.sh will still run and work properly.
while [ $# -gt 0 ]; do
case "$1" in
--append-outcome) append_outcome=1;;
@ -418,19 +420,6 @@ pre_check_git () {
exit 1
fi
fi
if ! [ -f crypto/Makefile ]; then
echo "Please initialize the crypto submodule" >&2
exit 1
fi
}
pre_check_seedfile () {
if [ ! -f "./tests/seedfile" ]; then
dd if=/dev/urandom of=./tests/seedfile bs=32 count=1
fi
if [ ! -f "./crypto/tests/seedfile" ]; then
dd if=/dev/urandom of=./crypto/tests/seedfile bs=32 count=1
fi
}
pre_setup_keep_going () {
@ -516,7 +505,6 @@ pre_print_configuration () {
echo "FORCE: $FORCE"
echo "MBEDTLS_TEST_OUTCOME_FILE: ${MBEDTLS_TEST_OUTCOME_FILE:-(none)}"
echo "SEED: ${SEED-"UNSET"}"
echo
echo "OPENSSL: $OPENSSL"
echo "OPENSSL_LEGACY: $OPENSSL_LEGACY"
echo "OPENSSL_NEXT: $OPENSSL_NEXT"
@ -644,7 +632,6 @@ component_check_doxygen_warnings () {
}
################################################################
#### Build and test many configurations and targets
################################################################
@ -689,51 +676,6 @@ component_test_full_cmake_gcc_asan () {
msg "test: main suites (inc. selftests) (full config, ASan build)"
make test
msg "test: ssl-opt.sh (full config, ASan build)"
if_build_succeeded tests/ssl-opt.sh
msg "test: compat.sh (full config, ASan build)"
if_build_succeeded tests/compat.sh
}
component_test_zlib_make() {
msg "build: zlib enabled, make"
scripts/config.py set MBEDTLS_ZLIB_SUPPORT
make ZLIB=1 CFLAGS='-Werror -O1'
msg "test: main suites (zlib, make)"
make test
msg "test: ssl-opt.sh (zlib, make)"
if_build_succeeded tests/ssl-opt.sh
}
support_test_zlib_make () {
base=support_test_zlib_$$
cat <<'EOF' > ${base}.c
#include "zlib.h"
int main(void) { return 0; }
EOF
gcc -o ${base}.exe ${base}.c -lz 2>/dev/null
ret=$?
rm -f ${base}.*
return $ret
}
component_test_zlib_cmake() {
msg "build: zlib enabled, cmake"
scripts/config.py set MBEDTLS_ZLIB_SUPPORT
cmake -D ENABLE_ZLIB_SUPPORT=On -D CMAKE_BUILD_TYPE:String=Check .
make
msg "test: main suites (zlib, cmake)"
make test
msg "test: ssl-opt.sh (zlib, cmake)"
if_build_succeeded tests/ssl-opt.sh
}
support_test_zlib_cmake () {
support_test_zlib_make "$@"
}
component_test_ref_configs () {
@ -742,6 +684,19 @@ component_test_ref_configs () {
record_status tests/scripts/test-ref-configs.pl
}
component_test_no_pem_no_fs () {
msg "build: Default + !MBEDTLS_PEM_PARSE_C + !MBEDTLS_FS_IO (ASan build)"
scripts/config.py unset MBEDTLS_PEM_PARSE_C
scripts/config.py unset MBEDTLS_FS_IO
scripts/config.py unset MBEDTLS_PSA_ITS_FILE_C # requires a filesystem
scripts/config.py unset MBEDTLS_PSA_CRYPTO_STORAGE_C # requires PSA ITS
CC=gcc cmake -D CMAKE_BUILD_TYPE:String=Asan .
make
msg "test: !MBEDTLS_PEM_PARSE_C !MBEDTLS_FS_IO - main suites (inc. selftests) (ASan build)" # ~ 50s
make test
}
component_test_sslv3 () {
msg "build: Default + SSLv3 (ASan build)" # ~ 6 min
scripts/config.py set MBEDTLS_SSL_PROTO_SSL3
@ -772,20 +727,6 @@ component_test_no_renegotiation () {
if_build_succeeded tests/ssl-opt.sh
}
component_test_no_pem_no_fs () {
msg "build: Default + !MBEDTLS_PEM_PARSE_C + !MBEDTLS_FS_IO (ASan build)"
scripts/config.py unset MBEDTLS_PEM_PARSE_C
scripts/config.py unset MBEDTLS_FS_IO
CC=gcc cmake -D CMAKE_BUILD_TYPE:String=Asan .
make
msg "test: !MBEDTLS_PEM_PARSE_C !MBEDTLS_FS_IO - main suites (inc. selftests) (ASan build)" # ~ 50s
make test
msg "test: !MBEDTLS_PEM_PARSE_C !MBEDTLS_FS_IO - ssl-opt.sh (ASan build)" # ~ 6 min
if_build_succeeded tests/ssl-opt.sh
}
component_test_rsa_no_crt () {
msg "build: Default + RSA_NO_CRT (ASan build)" # ~ 6 min
scripts/config.py set MBEDTLS_RSA_NO_CRT
@ -802,24 +743,6 @@ component_test_rsa_no_crt () {
if_build_succeeded tests/compat.sh -t RSA
}
component_test_everest () {
msg "build: Everest ECDH context (ASan build)" # ~ 6 min
scripts/config.py unset MBEDTLS_ECDH_LEGACY_CONTEXT
scripts/config.py set MBEDTLS_ECDH_VARIANT_EVEREST_ENABLED
CC=clang cmake -D CMAKE_BUILD_TYPE:String=Asan .
make
msg "test: Everest ECDH context - main suites (inc. selftests) (ASan build)" # ~ 50s
make test
msg "test: Everest ECDH context - ECDH-related part of ssl-opt.sh (ASan build)" # ~ 5s
if_build_succeeded tests/ssl-opt.sh -f ECDH
msg "test: Everest ECDH context - compat.sh with some ECDH ciphersuites (ASan build)" # ~ 3 min
# Exclude some symmetric ciphers that are redundant here to gain time.
if_build_succeeded tests/compat.sh -f ECDH -V NO -e 'ARCFOUR\|ARIA\|CAMELLIA\|CHACHA\|DES\|RC4'
}
component_test_small_ssl_out_content_len () {
msg "build: small SSL_OUT_CONTENT_LEN (ASan build)"
scripts/config.py set MBEDTLS_SSL_IN_CONTENT_LEN 16384
@ -854,7 +777,7 @@ component_test_small_ssl_dtls_max_buffering () {
component_test_small_mbedtls_ssl_dtls_max_buffering () {
msg "build: small MBEDTLS_SSL_DTLS_MAX_BUFFERING #1"
scripts/config.py set MBEDTLS_SSL_DTLS_MAX_BUFFERING 190
scripts/config.py set MBEDTLS_SSL_DTLS_MAX_BUFFERING 240
CC=gcc cmake -D CMAKE_BUILD_TYPE:String=Asan .
make
@ -862,15 +785,48 @@ component_test_small_mbedtls_ssl_dtls_max_buffering () {
if_build_succeeded tests/ssl-opt.sh -f "DTLS reordering: Buffer encrypted Finished message, drop for fragmented NewSessionTicket"
}
component_test_new_ecdh_context () {
msg "build: new ECDH context (ASan build)" # ~ 6 min
scripts/config.py unset MBEDTLS_ECDH_LEGACY_CONTEXT
CC=gcc cmake -D CMAKE_BUILD_TYPE:String=Asan .
make
msg "test: new ECDH context - main suites (inc. selftests) (ASan build)" # ~ 50s
make test
}
component_test_everest () {
msg "build: Everest ECDH context (ASan build)" # ~ 6 min
scripts/config.py unset MBEDTLS_ECDH_LEGACY_CONTEXT
scripts/config.py set MBEDTLS_ECDH_VARIANT_EVEREST_ENABLED
CC=clang cmake -D CMAKE_BUILD_TYPE:String=Asan .
make
msg "test: Everest ECDH context - main suites (inc. selftests) (ASan build)" # ~ 50s
make test
}
component_test_psa_collect_statuses () {
msg "build+test: psa_collect_statuses" # ~30s
scripts/config.py full
record_status tests/scripts/psa_collect_statuses.py
# Check that psa_crypto_init() succeeded at least once
record_status grep -q '^0:psa_crypto_init:' tests/statuses.log
rm -f tests/statuses.log
}
component_test_full_cmake_clang () {
msg "build: cmake, full config, clang" # ~ 50s
scripts/config.py full
CC=clang cmake -D CMAKE_BUILD_TYPE:String=Check -D ENABLE_TESTING=On .
make
msg "test: main suites (full config)" # ~ 5s
msg "test: main suites (full config, clang)" # ~ 5s
make test
msg "test: psa_constant_names (full config, clang)" # ~ 1s
record_status tests/scripts/test_psa_constant_names.py
msg "test: ssl-opt.sh default, ECJPAKE, SSL async (full config)" # ~ 1s
if_build_succeeded tests/ssl-opt.sh -f 'Default\|ECJPAKE\|SSL async private'
@ -881,6 +837,15 @@ component_test_full_cmake_clang () {
if_build_succeeded env OPENSSL_CMD="$OPENSSL_NEXT" tests/compat.sh -e '^$' -f 'ARIA\|CHACHA'
}
component_test_full_make_gcc_o0 () {
msg "build: make, full config, gcc -O0" # ~ 50s
scripts/config.py full
make CC=gcc CFLAGS='-O0'
msg "test: main suites (full config, gcc -O0)" # ~ 5s
make test
}
component_build_deprecated () {
msg "build: make, full config + DEPRECATED_WARNING, gcc -O" # ~ 30s
scripts/config.py full
@ -889,6 +854,10 @@ component_build_deprecated () {
make CC=gcc CFLAGS='-O -Werror -Wall -Wextra' lib programs
make CC=gcc CFLAGS='-O -Werror -Wall -Wextra -Wno-unused-function' tests
msg "test: make, full config + DEPRECATED_WARNING, expect warnings" # ~ 30s
make -C tests clean
make CC=gcc CFLAGS='-O -Werror -Wall -Wextra -Wno-error=deprecated-declarations -DMBEDTLS_TEST_DEPRECATED' tests
msg "build: make, full config + DEPRECATED_REMOVED, clang -O" # ~ 30s
# No cleanup, just tweak the configuration and rebuild
make clean
@ -1034,6 +1003,7 @@ component_test_no_platform () {
scripts/config.py unset MBEDTLS_PLATFORM_EXIT_ALT
scripts/config.py unset MBEDTLS_ENTROPY_NV_SEED
scripts/config.py unset MBEDTLS_FS_IO
scripts/config.py unset MBEDTLS_PSA_CRYPTO_SE_C
scripts/config.py unset MBEDTLS_PSA_CRYPTO_STORAGE_C
scripts/config.py unset MBEDTLS_PSA_ITS_FILE_C
# Note, _DEFAULT_SOURCE needs to be defined for platforms using glibc version >2.19,
@ -1097,10 +1067,6 @@ component_test_memory_buffer_allocator () {
msg "test: MBEDTLS_MEMORY_BUFFER_ALLOC_C"
make test
msg "test: ssl-opt.sh, MBEDTLS_MEMORY_BUFFER_ALLOC_C"
# MBEDTLS_MEMORY_BUFFER_ALLOC is slow. Skip tests that tend to time out.
if_build_succeeded tests/ssl-opt.sh -e '^DTLS proxy'
}
component_test_no_max_fragment_length () {
@ -1142,20 +1108,6 @@ component_test_no_max_fragment_length_small_ssl_out_content_len () {
if_build_succeeded tests/ssl-opt.sh -f "Max fragment length\|Large buffer"
}
component_test_when_no_ciphersuites_have_mac () {
msg "build: when no ciphersuites have MAC"
scripts/config.py unset MBEDTLS_CIPHER_NULL_CIPHER
scripts/config.py unset MBEDTLS_ARC4_C
scripts/config.py unset MBEDTLS_CIPHER_MODE_CBC
make
msg "test: !MBEDTLS_SSL_SOME_MODES_USE_MAC"
make test
msg "test ssl-opt.sh: !MBEDTLS_SSL_SOME_MODES_USE_MAC"
if_build_succeeded tests/ssl-opt.sh -f 'Default\|EtM' -e 'without EtM'
}
component_test_null_entropy () {
msg "build: default config with MBEDTLS_TEST_NULL_ENTROPY (ASan build)"
scripts/config.py set MBEDTLS_TEST_NULL_ENTROPY
@ -1186,8 +1138,7 @@ component_test_platform_calloc_macro () {
component_test_malloc_0_null () {
msg "build: malloc(0) returns NULL (ASan+UBSan build)"
scripts/config.py full
scripts/config.py unset MBEDTLS_MEMORY_BUFFER_ALLOC_C
make CC=gcc CFLAGS="'-DMBEDTLS_CONFIG_FILE=\"$PWD/tests/configs/config-wrapper-malloc-0-null.h\"' -O -Werror -Wall -Wextra -fsanitize=address,undefined" LDFLAGS='-fsanitize=address,undefined'
make CC=gcc CFLAGS="'-DMBEDTLS_CONFIG_FILE=\"$PWD/tests/configs/config-wrapper-malloc-0-null.h\"' $ASAN_CFLAGS -O" LDFLAGS="$ASAN_CFLAGS"
msg "test: malloc(0) returns NULL (ASan+UBSan build)"
make test
@ -1206,6 +1157,90 @@ component_test_malloc_0_null () {
if_build_succeeded tests/ssl-opt.sh -e 'proxy'
}
component_test_aes_fewer_tables () {
msg "build: default config with AES_FEWER_TABLES enabled"
scripts/config.py set MBEDTLS_AES_FEWER_TABLES
make CC=gcc CFLAGS='-Werror -Wall -Wextra'
msg "test: AES_FEWER_TABLES"
make test
}
component_test_aes_rom_tables () {
msg "build: default config with AES_ROM_TABLES enabled"
scripts/config.py set MBEDTLS_AES_ROM_TABLES
make CC=gcc CFLAGS='-Werror -Wall -Wextra'
msg "test: AES_ROM_TABLES"
make test
}
component_test_aes_fewer_tables_and_rom_tables () {
msg "build: default config with AES_ROM_TABLES and AES_FEWER_TABLES enabled"
scripts/config.py set MBEDTLS_AES_FEWER_TABLES
scripts/config.py set MBEDTLS_AES_ROM_TABLES
make CC=gcc CFLAGS='-Werror -Wall -Wextra'
msg "test: AES_FEWER_TABLES + AES_ROM_TABLES"
make test
}
component_test_ctr_drbg_aes_256_sha_256 () {
msg "build: full + MBEDTLS_ENTROPY_FORCE_SHA256 (ASan build)"
scripts/config.py full
scripts/config.py unset MBEDTLS_MEMORY_BUFFER_ALLOC_C
scripts/config.py set MBEDTLS_ENTROPY_FORCE_SHA256
CC=gcc cmake -D CMAKE_BUILD_TYPE:String=Asan .
make
msg "test: full + MBEDTLS_ENTROPY_FORCE_SHA256 (ASan build)"
make test
}
component_test_ctr_drbg_aes_128_sha_512 () {
msg "build: full + MBEDTLS_CTR_DRBG_USE_128_BIT_KEY (ASan build)"
scripts/config.py full
scripts/config.py unset MBEDTLS_MEMORY_BUFFER_ALLOC_C
scripts/config.py set MBEDTLS_CTR_DRBG_USE_128_BIT_KEY
CC=gcc cmake -D CMAKE_BUILD_TYPE:String=Asan .
make
msg "test: full + MBEDTLS_CTR_DRBG_USE_128_BIT_KEY (ASan build)"
make test
}
component_test_ctr_drbg_aes_128_sha_256 () {
msg "build: full + MBEDTLS_CTR_DRBG_USE_128_BIT_KEY + MBEDTLS_ENTROPY_FORCE_SHA256 (ASan build)"
scripts/config.py full
scripts/config.py unset MBEDTLS_MEMORY_BUFFER_ALLOC_C
scripts/config.py set MBEDTLS_CTR_DRBG_USE_128_BIT_KEY
scripts/config.py set MBEDTLS_ENTROPY_FORCE_SHA256
CC=gcc cmake -D CMAKE_BUILD_TYPE:String=Asan .
make
msg "test: full + MBEDTLS_CTR_DRBG_USE_128_BIT_KEY + MBEDTLS_ENTROPY_FORCE_SHA256 (ASan build)"
make test
}
component_test_se_default () {
msg "build: default config + MBEDTLS_PSA_CRYPTO_SE_C"
scripts/config.py set MBEDTLS_PSA_CRYPTO_SE_C
make CC=clang CFLAGS="$ASAN_CFLAGS -Os" LDFLAGS="$ASAN_CFLAGS"
msg "test: default config + MBEDTLS_PSA_CRYPTO_SE_C"
make test
}
component_test_se_full () {
msg "build: full config + MBEDTLS_PSA_CRYPTO_SE_C"
scripts/config.py full
scripts/config.py set MBEDTLS_PSA_CRYPTO_SE_C
make CC=gcc CFLAGS="$ASAN_CFLAGS -O2" LDFLAGS="$ASAN_CFLAGS"
msg "test: full config + MBEDTLS_PSA_CRYPTO_SE_C"
make test
}
component_test_make_shared () {
msg "build/test: make shared" # ~ 40s
make SHARED=1 all check
@ -1279,9 +1314,6 @@ component_test_m32_o1 () {
msg "test: i386, make, gcc -O1 (ASan build)"
make test
msg "test ssl-opt.sh, i386, make, gcc-O1"
if_build_succeeded tests/ssl-opt.sh
}
support_test_m32_o1 () {
support_test_m32_o0 "$@"
@ -1295,13 +1327,6 @@ component_test_m32_everest () {
msg "test: i386, Everest ECDH context - main suites (inc. selftests) (ASan build)" # ~ 50s
make test
msg "test: i386, Everest ECDH context - ECDH-related part of ssl-opt.sh (ASan build)" # ~ 5s
if_build_succeeded tests/ssl-opt.sh -f ECDH
msg "test: i386, Everest ECDH context - compat.sh with some ECDH ciphersuites (ASan build)" # ~ 3 min
# Exclude some symmetric ciphers that are redundant here to gain time.
if_build_succeeded tests/compat.sh -f ECDH -V NO -e 'ARCFOUR\|ARIA\|CAMELLIA\|CHACHA\|DES\|RC4'
}
support_test_m32_everest () {
support_test_m32_o0 "$@"
@ -1322,6 +1347,58 @@ support_test_mx32 () {
esac
}
component_test_min_mpi_window_size () {
msg "build: Default + MBEDTLS_MPI_WINDOW_SIZE=1 (ASan build)" # ~ 10s
scripts/config.py set MBEDTLS_MPI_WINDOW_SIZE 1
CC=gcc cmake -D CMAKE_BUILD_TYPE:String=Asan .
make
msg "test: MBEDTLS_MPI_WINDOW_SIZE=1 - main suites (inc. selftests) (ASan build)" # ~ 10s
make test
}
component_test_have_int32 () {
msg "build: gcc, force 32-bit bignum limbs"
scripts/config.py unset MBEDTLS_HAVE_ASM
scripts/config.py unset MBEDTLS_AESNI_C
scripts/config.py unset MBEDTLS_PADLOCK_C
make CC=gcc CFLAGS='-Werror -Wall -Wextra -DMBEDTLS_HAVE_INT32'
msg "test: gcc, force 32-bit bignum limbs"
make test
}
component_test_have_int64 () {
msg "build: gcc, force 64-bit bignum limbs"
scripts/config.py unset MBEDTLS_HAVE_ASM
scripts/config.py unset MBEDTLS_AESNI_C
scripts/config.py unset MBEDTLS_PADLOCK_C
make CC=gcc CFLAGS='-Werror -Wall -Wextra -DMBEDTLS_HAVE_INT64'
msg "test: gcc, force 64-bit bignum limbs"
make test
}
component_test_no_udbl_division () {
msg "build: MBEDTLS_NO_UDBL_DIVISION native" # ~ 10s
scripts/config.py full
scripts/config.py set MBEDTLS_NO_UDBL_DIVISION
make CFLAGS='-Werror -O1'
msg "test: MBEDTLS_NO_UDBL_DIVISION native" # ~ 10s
make test
}
component_test_no_64bit_multiplication () {
msg "build: MBEDTLS_NO_64BIT_MULTIPLICATION native" # ~ 10s
scripts/config.py full
scripts/config.py set MBEDTLS_NO_64BIT_MULTIPLICATION
make CFLAGS='-Werror -O1'
msg "test: MBEDTLS_NO_64BIT_MULTIPLICATION native" # ~ 10s
make test
}
component_build_arm_none_eabi_gcc () {
msg "build: arm-none-eabi-gcc, make" # ~ 10s
scripts/config.py baremetal
@ -1437,8 +1514,10 @@ component_test_valgrind () {
msg "test: main suites valgrind (Release)"
make memcheck
# Optional parts (slow; currently broken on OS X because programs don't
# seem to receive signals under valgrind on OS X).
# Optional part(s)
# Currently broken, programs don't seem to receive signals
# under valgrind on OS X
if [ "$MEMORY" -gt 0 ]; then
msg "test: ssl-opt.sh --memcheck (Release)"
if_build_succeeded tests/ssl-opt.sh --memcheck
@ -1556,7 +1635,16 @@ run_component () {
cp -p "$CONFIG_H" "$CONFIG_BAK"
current_component="$1"
export MBEDTLS_TEST_CONFIGURATION="$current_component"
# Unconditionally create a seedfile that's sufficiently long.
# Do this before each component, because a previous component may
# have messed it up or shortened it.
dd if=/dev/urandom of=./tests/seedfile bs=64 count=1
# Run the component code.
"$@"
# Restore the build tree to a clean state.
cleanup
}
@ -1566,7 +1654,6 @@ pre_initialize_variables
pre_parse_command_line "$@"
pre_check_git
pre_check_seedfile
build_status=0
if [ $KEEP_GOING -eq 1 ]; then

View file

@ -42,9 +42,6 @@ for compiler in clang gcc; do
run_in_docker -e CC=${compiler} make
run_in_docker -e CC=${compiler} make test
run_in_docker programs/test/selftest
run_in_docker -e OSSL_NO_DTLS=1 tests/compat.sh
run_in_docker tests/ssl-opt.sh -e '\(DTLS\|SCSV\).*openssl'
run_in_docker tests/scripts/test-ref-configs.pl
run_in_docker tests/scripts/curves.pl
run_in_docker tests/scripts/key-exchanges.pl
done

View file

@ -56,12 +56,15 @@ diff macros identifiers | sed -n -e 's/< //p' > actual-macros
for THING in actual-macros enum-consts; do
printf "Names of $THING: "
test -r $THING
BAD=$( grep -v '^MBEDTLS_[0-9A-Z_]*[0-9A-Z]$' $THING || true )
if [ "x$BAD" = "x" ]; then
BAD=$( grep -E -v '^(MBEDTLS|PSA)_[0-9A-Z_]*[0-9A-Z]$' $THING || true )
UNDERSCORES=$( grep -E '.*__.*' $THING || true )
if [ "x$BAD" = "x" ] && [ "x$UNDERSCORES" = "x" ]; then
echo "PASS"
else
echo "FAIL"
echo "$BAD"
echo "$UNDERSCORES"
FAIL=1
fi
done
@ -69,7 +72,7 @@ done
for THING in identifiers; do
printf "Names of $THING: "
test -r $THING
BAD=$( grep -v '^mbedtls_[0-9a-z_]*[0-9a-z]$' $THING || true )
BAD=$( grep -E -v '^(mbedtls|psa)_[0-9a-z_]*[0-9a-z]$' $THING || true )
if [ "x$BAD" = "x" ]; then
echo "PASS"
else
@ -81,11 +84,14 @@ done
printf "Likely typos: "
sort -u actual-macros enum-consts > _caps
HEADERS=$( ls include/mbedtls/*.h crypto/include/mbedtls/*.h | egrep -v 'compat-1\.3\.h' )
HEADERS=$( ls include/mbedtls/*.h include/psa/*.h | egrep -v 'compat-1\.3\.h' )
HEADERS="$HEADERS 3rdparty/everest/include/everest/everest.h 3rdparty/everest/include/everest/x25519.h"
LIBRARY="$( ls library/*.c )"
LIBRARY="$LIBRARY 3rdparty/everest/library/everest.c 3rdparty/everest/library/x25519.c"
NL='
'
sed -n 's/MBED..._[A-Z0-9_]*/\'"$NL"'&\'"$NL"/gp \
$HEADERS library/*.c \
$HEADERS $LIBRARY \
| grep MBEDTLS | sort -u > _MBEDTLS_XXX
TYPOS=$( diff _caps _MBEDTLS_XXX | sed -n 's/^> //p' \
| egrep -v 'XXX|__|_$|^MBEDTLS_.*CONFIG_FILE$' || true )

View file

@ -20,6 +20,7 @@
#
# This file is part of Mbed TLS (https://tls.mbed.org)
import argparse
import glob
import os
import re
@ -27,9 +28,11 @@ import sys
class Results:
"""Store file and line information about errors or warnings in test suites."""
def __init__(self):
def __init__(self, options):
self.errors = 0
self.warnings = 0
self.ignore_warnings = options.quiet
def error(self, file_name, line_number, fmt, *args):
sys.stderr.write(('{}:{}:ERROR:' + fmt + '\n').
@ -37,9 +40,10 @@ class Results:
self.errors += 1
def warning(self, file_name, line_number, fmt, *args):
sys.stderr.write(('{}:{}:Warning:' + fmt + '\n')
.format(file_name, line_number, *args))
self.warnings += 1
if not self.ignore_warnings:
sys.stderr.write(('{}:{}:Warning:' + fmt + '\n')
.format(file_name, line_number, *args))
self.warnings += 1
def collect_test_directories():
"""Get the relative path for the TLS and Crypto test directories."""
@ -108,8 +112,16 @@ def check_ssl_opt_sh(results, file_name):
file_name, line_number, description)
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--quiet', '-q',
action='store_true',
help='Hide warnings')
parser.add_argument('--verbose', '-v',
action='store_false', dest='quiet',
help='Show warnings (default: on; undoes --quiet)')
options = parser.parse_args()
test_directories = collect_test_directories()
results = Results()
results = Results(options)
for directory in test_directories:
for data_file_name in glob.glob(os.path.join(directory, 'suites',
'*.data')):
@ -117,7 +129,7 @@ def main():
ssl_opt_sh = os.path.join(directory, 'ssl-opt.sh')
if os.path.exists(ssl_opt_sh):
check_ssl_opt_sh(results, ssl_opt_sh)
if results.warnings or results.errors:
if (results.warnings or results.errors) and not options.quiet:
sys.stderr.write('{}: {} errors, {} warnings\n'
.format(sys.argv[0], results.errors, results.warnings))
sys.exit(1 if results.errors else 0)

View file

@ -40,8 +40,18 @@ my @ssl = split( /\s+/, `sed -n -e '$ssl_sed_cmd' $config_h` );
# for md we want to catch MD5_C but not MD_C, hence the extra dot
my $mdx_sed_cmd = 's/^#define \(MBEDTLS_MD..*_C\)/\1/p';
my $sha_sed_cmd = 's/^#define \(MBEDTLS_SHA.*_C\)/\1/p';
my @hashes = split( /\s+/,
my @hash_modules = split( /\s+/,
`sed -n -e '$mdx_sed_cmd' -e '$sha_sed_cmd' $config_h` );
# there are also negative options for truncated variants, disabled by default
my $sha_trunc_sed_cmd = 's/^\/\/#define \(MBEDTLS_SHA..._NO_.*\)/\1/p';
my @hash_negatives = split( /\s+/,
`sed -n -e '$sha_trunc_sed_cmd' $config_h` );
# list hash options with corresponding actions
my @hashes = ((map { "unset $_" } @hash_modules),
(map { "set $_" } @hash_negatives));
system( "cp $config_h $config_h.bak" ) and die;
sub abort {
system( "mv $config_h.bak $config_h" ) and warn "$config_h not restored\n";
@ -55,12 +65,12 @@ for my $hash (@hashes) {
system( "make clean" ) and die;
print "\n******************************************\n";
print "* Testing without hash: $hash\n";
print "* Testing hash option: $hash\n";
print "******************************************\n";
$ENV{MBEDTLS_TEST_CONFIGURATION} = "-$hash";
system( "scripts/config.py unset $hash" )
and abort "Failed to disable $hash\n";
system( "scripts/config.py $hash" )
and abort "Failed to $hash\n";
for my $opt (@ssl) {
system( "scripts/config.py unset $opt" )

View file

@ -8,7 +8,10 @@ use open qw(:std utf8);
-d 'include/mbedtls' or die "$0: must be run from root\n";
@ARGV = grep { ! /compat-1\.3\.h/ } <include/mbedtls/*.h crypto/include/mbedtls/*.h>;
@ARGV = grep { ! /compat-1\.3\.h/ } <include/mbedtls/*.h>;
push @ARGV, "3rdparty/everest/include/everest/everest.h";
push @ARGV, "3rdparty/everest/include/everest/x25519.h";
my @consts;
my $state = 'out';

View file

@ -32,16 +32,17 @@ done
if [ $INTERNAL ]
then
HEADERS=$( ls include/mbedtls/*_internal.h crypto/include/mbedtls/*_internal.h | egrep -v 'compat-1\.3\.h|bn_mul' )
HEADERS=$( ls include/mbedtls/*_internal.h library/*.h | egrep -v 'compat-1\.3\.h|bn_mul' )
else
HEADERS=$( ls include/mbedtls/*.h crypto/include/mbedtls/*.h | egrep -v 'compat-1\.3\.h|bn_mul' )
HEADERS=$( ls include/mbedtls/*.h include/psa/*.h library/*.h | egrep -v 'compat-1\.3\.h|bn_mul' )
HEADERS="$HEADERS 3rdparty/everest/include/everest/everest.h 3rdparty/everest/include/everest/x25519.h"
fi
rm -f identifiers
grep '^[^ /#{]' $HEADERS | \
sed -e 's/^[^:]*://' | \
egrep -v '^(extern "C"|(typedef )?(struct|enum)( {)?$|};?$)' \
egrep -v '^(extern "C"|(typedef )?(struct|union|enum)( {)?$|};?$)' \
> _decls
if true; then

View file

@ -7,7 +7,8 @@ if [ -d include/mbedtls ]; then :; else
exit 1
fi
HEADERS=$( ls include/mbedtls/*.h crypto/include/mbedtls/*.h | egrep -v 'compat-1\.3\.h' )
HEADERS=$( ls include/mbedtls/*.h include/psa/*.h | egrep -v 'compat-1\.3\.h' )
HEADERS="$HEADERS 3rdparty/everest/include/everest/everest.h 3rdparty/everest/include/everest/x25519.h"
sed -n -e 's/.*#define \([a-zA-Z0-9_]*\).*/\1/p' $HEADERS \
| egrep -v '^(asm|inline|EMIT|_CRT_SECURE_NO_DEPRECATE)$|^MULADDC_' \

View file

@ -0,0 +1,125 @@
#!/usr/bin/env python3
"""Describe the test coverage of PSA functions in terms of return statuses.
1. Build Mbed Crypto with -DRECORD_PSA_STATUS_COVERAGE_LOG
2. Run psa_collect_statuses.py
The output is a series of line of the form "psa_foo PSA_ERROR_XXX". Each
function/status combination appears only once.
This script must be run from the top of an Mbed Crypto source tree.
The build command is "make -DRECORD_PSA_STATUS_COVERAGE_LOG", which is
only supported with make (as opposed to CMake or other build methods).
"""
import argparse
import os
import subprocess
import sys
DEFAULT_STATUS_LOG_FILE = 'tests/statuses.log'
DEFAULT_PSA_CONSTANT_NAMES = 'programs/psa/psa_constant_names'
class Statuses:
"""Information about observed return statues of API functions."""
def __init__(self):
self.functions = {}
self.codes = set()
self.status_names = {}
def collect_log(self, log_file_name):
"""Read logs from RECORD_PSA_STATUS_COVERAGE_LOG.
Read logs produced by running Mbed Crypto test suites built with
-DRECORD_PSA_STATUS_COVERAGE_LOG.
"""
with open(log_file_name) as log:
for line in log:
value, function, tail = line.split(':', 2)
if function not in self.functions:
self.functions[function] = {}
fdata = self.functions[function]
if value not in self.functions[function]:
fdata[value] = []
fdata[value].append(tail)
self.codes.add(int(value))
def get_constant_names(self, psa_constant_names):
"""Run psa_constant_names to obtain names for observed numerical values."""
values = [str(value) for value in self.codes]
cmd = [psa_constant_names, 'status'] + values
output = subprocess.check_output(cmd).decode('ascii')
for value, name in zip(values, output.rstrip().split('\n')):
self.status_names[value] = name
def report(self):
"""Report observed return values for each function.
The report is a series of line of the form "psa_foo PSA_ERROR_XXX".
"""
for function in sorted(self.functions.keys()):
fdata = self.functions[function]
names = [self.status_names[value] for value in fdata.keys()]
for name in sorted(names):
sys.stdout.write('{} {}\n'.format(function, name))
def collect_status_logs(options):
"""Build and run unit tests and report observed function return statuses.
Build Mbed Crypto with -DRECORD_PSA_STATUS_COVERAGE_LOG, run the
test suites and display information about observed return statuses.
"""
rebuilt = False
if not options.use_existing_log and os.path.exists(options.log_file):
os.remove(options.log_file)
if not os.path.exists(options.log_file):
if options.clean_before:
subprocess.check_call(['make', 'clean'],
cwd='tests',
stdout=sys.stderr)
with open(os.devnull, 'w') as devnull:
make_q_ret = subprocess.call(['make', '-q', 'lib', 'tests'],
stdout=devnull, stderr=devnull)
if make_q_ret != 0:
subprocess.check_call(['make', 'RECORD_PSA_STATUS_COVERAGE_LOG=1'],
stdout=sys.stderr)
rebuilt = True
subprocess.check_call(['make', 'test'],
stdout=sys.stderr)
data = Statuses()
data.collect_log(options.log_file)
data.get_constant_names(options.psa_constant_names)
if rebuilt and options.clean_after:
subprocess.check_call(['make', 'clean'],
cwd='tests',
stdout=sys.stderr)
return data
def main():
parser = argparse.ArgumentParser(description=globals()['__doc__'])
parser.add_argument('--clean-after',
action='store_true',
help='Run "make clean" after rebuilding')
parser.add_argument('--clean-before',
action='store_true',
help='Run "make clean" before regenerating the log file)')
parser.add_argument('--log-file', metavar='FILE',
default=DEFAULT_STATUS_LOG_FILE,
help='Log file location (default: {})'.format(
DEFAULT_STATUS_LOG_FILE
))
parser.add_argument('--psa-constant-names', metavar='PROGRAM',
default=DEFAULT_PSA_CONSTANT_NAMES,
help='Path to psa_constant_names (default: {})'.format(
DEFAULT_PSA_CONSTANT_NAMES
))
parser.add_argument('--use-existing-log', '-e',
action='store_true',
help='Don\'t regenerate the log file if it exists')
options = parser.parse_args()
data = collect_status_logs(options)
data.report()
if __name__ == '__main__':
main()

View file

@ -58,8 +58,8 @@ my $skip_re =
')(\z|\.)' );
# in case test suites are linked dynamically
$ENV{'LD_LIBRARY_PATH'} = '../library:../crypto/library';
$ENV{'DYLD_LIBRARY_PATH'} = '../library:../crypto/library';
$ENV{'LD_LIBRARY_PATH'} = '../library';
$ENV{'DYLD_LIBRARY_PATH'} = '../library';
my $prefix = $^O eq "MSWin32" ? '' : './';

View file

@ -17,12 +17,18 @@ use warnings;
use strict;
my %configs = (
'config-default.h' => {
'opt' => '-f Default',
'compat' => '-m tls1_2 -V NO',
},
'config-mini-tls1_1.h' => {
'compat' => '-m tls1_1 -f \'^DES-CBC3-SHA$\|^TLS-RSA-WITH-3DES-EDE-CBC-SHA$\'', #'
'compat' => '-m tls1_1 -f \'^DES-CBC3-SHA$\|^TLS-RSA-WITH-3DES-EDE-CBC-SHA$\'',
},
'config-suite-b.h' => {
'compat' => "-m tls1_2 -f 'ECDHE-ECDSA.*AES.*GCM' -p mbedTLS",
},
'config-symmetric-only.h' => {
},
'config-ccm-psk-tls1_2.h' => {
'compat' => '-m tls1_2 -f \'^TLS-PSK-WITH-AES-...-CCM-8\'',
},
@ -58,6 +64,15 @@ sub abort {
exit 1;
}
# Create a seedfile for configurations that enable MBEDTLS_ENTROPY_NV_SEED.
# For test purposes, this doesn't have to be cryptographically random.
if (!-e "tests/seedfile" || -s "tests/seedfile" < 64) {
local *SEEDFILE;
open SEEDFILE, ">tests/seedfile" or die;
print SEEDFILE "*" x 64 or die;
close SEEDFILE or die;
}
while( my ($conf, $data) = each %configs ) {
system( "cp $config_h.bak $config_h" ) and die;
system( "make clean" ) and die;

View file

@ -0,0 +1,459 @@
#!/usr/bin/env python3
"""Test the program psa_constant_names.
Gather constant names from header files and test cases. Compile a C program
to print out their numerical values, feed these numerical values to
psa_constant_names, and check that the output is the original name.
Return 0 if all test cases pass, 1 if the output was not always as expected,
or 1 (with a Python backtrace) if there was an operational error.
"""
import argparse
from collections import namedtuple
import itertools
import os
import platform
import re
import subprocess
import sys
import tempfile
class ReadFileLineException(Exception):
def __init__(self, filename, line_number):
message = 'in {} at {}'.format(filename, line_number)
super(ReadFileLineException, self).__init__(message)
self.filename = filename
self.line_number = line_number
class read_file_lines:
# Dear Pylint, conventionally, a context manager class name is lowercase.
# pylint: disable=invalid-name,too-few-public-methods
"""Context manager to read a text file line by line.
```
with read_file_lines(filename) as lines:
for line in lines:
process(line)
```
is equivalent to
```
with open(filename, 'r') as input_file:
for line in input_file:
process(line)
```
except that if process(line) raises an exception, then the read_file_lines
snippet annotates the exception with the file name and line number.
"""
def __init__(self, filename, binary=False):
self.filename = filename
self.line_number = 'entry'
self.generator = None
self.binary = binary
def __enter__(self):
self.generator = enumerate(open(self.filename,
'rb' if self.binary else 'r'))
return self
def __iter__(self):
for line_number, content in self.generator:
self.line_number = line_number
yield content
self.line_number = 'exit'
def __exit__(self, exc_type, exc_value, exc_traceback):
if exc_type is not None:
raise ReadFileLineException(self.filename, self.line_number) \
from exc_value
class Inputs:
# pylint: disable=too-many-instance-attributes
"""Accumulate information about macros to test.
This includes macro names as well as information about their arguments
when applicable.
"""
def __init__(self):
self.all_declared = set()
# Sets of names per type
self.statuses = set(['PSA_SUCCESS'])
self.algorithms = set(['0xffffffff'])
self.ecc_curves = set(['0xff'])
self.dh_groups = set(['0xff'])
self.key_types = set(['0xffff'])
self.key_usage_flags = set(['0x80000000'])
# Hard-coded value for unknown algorithms
self.hash_algorithms = set(['0x010000fe'])
self.mac_algorithms = set(['0x02ff00ff'])
self.ka_algorithms = set(['0x30fc0000'])
self.kdf_algorithms = set(['0x200000ff'])
# For AEAD algorithms, the only variability is over the tag length,
# and this only applies to known algorithms, so don't test an
# unknown algorithm.
self.aead_algorithms = set()
# Identifier prefixes
self.table_by_prefix = {
'ERROR': self.statuses,
'ALG': self.algorithms,
'ECC_CURVE': self.ecc_curves,
'DH_GROUP': self.dh_groups,
'KEY_TYPE': self.key_types,
'KEY_USAGE': self.key_usage_flags,
}
# Test functions
self.table_by_test_function = {
# Any function ending in _algorithm also gets added to
# self.algorithms.
'key_type': [self.key_types],
'block_cipher_key_type': [self.key_types],
'stream_cipher_key_type': [self.key_types],
'ecc_key_family': [self.ecc_curves],
'ecc_key_types': [self.ecc_curves],
'dh_key_family': [self.dh_groups],
'dh_key_types': [self.dh_groups],
'hash_algorithm': [self.hash_algorithms],
'mac_algorithm': [self.mac_algorithms],
'cipher_algorithm': [],
'hmac_algorithm': [self.mac_algorithms],
'aead_algorithm': [self.aead_algorithms],
'key_derivation_algorithm': [self.kdf_algorithms],
'key_agreement_algorithm': [self.ka_algorithms],
'asymmetric_signature_algorithm': [],
'asymmetric_signature_wildcard': [self.algorithms],
'asymmetric_encryption_algorithm': [],
'other_algorithm': [],
}
# macro name -> list of argument names
self.argspecs = {}
# argument name -> list of values
self.arguments_for = {
'mac_length': ['1', '63'],
'tag_length': ['1', '63'],
}
def get_names(self, type_word):
"""Return the set of known names of values of the given type."""
return {
'status': self.statuses,
'algorithm': self.algorithms,
'ecc_curve': self.ecc_curves,
'dh_group': self.dh_groups,
'key_type': self.key_types,
'key_usage': self.key_usage_flags,
}[type_word]
def gather_arguments(self):
"""Populate the list of values for macro arguments.
Call this after parsing all the inputs.
"""
self.arguments_for['hash_alg'] = sorted(self.hash_algorithms)
self.arguments_for['mac_alg'] = sorted(self.mac_algorithms)
self.arguments_for['ka_alg'] = sorted(self.ka_algorithms)
self.arguments_for['kdf_alg'] = sorted(self.kdf_algorithms)
self.arguments_for['aead_alg'] = sorted(self.aead_algorithms)
self.arguments_for['curve'] = sorted(self.ecc_curves)
self.arguments_for['group'] = sorted(self.dh_groups)
@staticmethod
def _format_arguments(name, arguments):
"""Format a macro call with arguments.."""
return name + '(' + ', '.join(arguments) + ')'
def distribute_arguments(self, name):
"""Generate macro calls with each tested argument set.
If name is a macro without arguments, just yield "name".
If name is a macro with arguments, yield a series of
"name(arg1,...,argN)" where each argument takes each possible
value at least once.
"""
try:
if name not in self.argspecs:
yield name
return
argspec = self.argspecs[name]
if argspec == []:
yield name + '()'
return
argument_lists = [self.arguments_for[arg] for arg in argspec]
arguments = [values[0] for values in argument_lists]
yield self._format_arguments(name, arguments)
# Dear Pylint, enumerate won't work here since we're modifying
# the array.
# pylint: disable=consider-using-enumerate
for i in range(len(arguments)):
for value in argument_lists[i][1:]:
arguments[i] = value
yield self._format_arguments(name, arguments)
arguments[i] = argument_lists[0][0]
except BaseException as e:
raise Exception('distribute_arguments({})'.format(name)) from e
def generate_expressions(self, names):
return itertools.chain(*map(self.distribute_arguments, names))
_argument_split_re = re.compile(r' *, *')
@classmethod
def _argument_split(cls, arguments):
return re.split(cls._argument_split_re, arguments)
# Regex for interesting header lines.
# Groups: 1=macro name, 2=type, 3=argument list (optional).
_header_line_re = \
re.compile(r'#define +' +
r'(PSA_((?:(?:DH|ECC|KEY)_)?[A-Z]+)_\w+)' +
r'(?:\(([^\n()]*)\))?')
# Regex of macro names to exclude.
_excluded_name_re = re.compile(r'_(?:GET|IS|OF)_|_(?:BASE|FLAG|MASK)\Z')
# Additional excluded macros.
_excluded_names = set([
# Macros that provide an alternative way to build the same
# algorithm as another macro.
'PSA_ALG_AEAD_WITH_DEFAULT_TAG_LENGTH',
'PSA_ALG_FULL_LENGTH_MAC',
# Auxiliary macro whose name doesn't fit the usual patterns for
# auxiliary macros.
'PSA_ALG_AEAD_WITH_DEFAULT_TAG_LENGTH_CASE',
])
def parse_header_line(self, line):
"""Parse a C header line, looking for "#define PSA_xxx"."""
m = re.match(self._header_line_re, line)
if not m:
return
name = m.group(1)
self.all_declared.add(name)
if re.search(self._excluded_name_re, name) or \
name in self._excluded_names:
return
dest = self.table_by_prefix.get(m.group(2))
if dest is None:
return
dest.add(name)
if m.group(3):
self.argspecs[name] = self._argument_split(m.group(3))
_nonascii_re = re.compile(rb'[^\x00-\x7f]+')
def parse_header(self, filename):
"""Parse a C header file, looking for "#define PSA_xxx"."""
with read_file_lines(filename, binary=True) as lines:
for line in lines:
line = re.sub(self._nonascii_re, rb'', line).decode('ascii')
self.parse_header_line(line)
_macro_identifier_re = re.compile(r'[A-Z]\w+')
def generate_undeclared_names(self, expr):
for name in re.findall(self._macro_identifier_re, expr):
if name not in self.all_declared:
yield name
def accept_test_case_line(self, function, argument):
#pylint: disable=unused-argument
undeclared = list(self.generate_undeclared_names(argument))
if undeclared:
raise Exception('Undeclared names in test case', undeclared)
return True
def add_test_case_line(self, function, argument):
"""Parse a test case data line, looking for algorithm metadata tests."""
sets = []
if function.endswith('_algorithm'):
sets.append(self.algorithms)
if function == 'key_agreement_algorithm' and \
argument.startswith('PSA_ALG_KEY_AGREEMENT('):
# We only want *raw* key agreement algorithms as such, so
# exclude ones that are already chained with a KDF.
# Keep the expression as one to test as an algorithm.
function = 'other_algorithm'
sets += self.table_by_test_function[function]
if self.accept_test_case_line(function, argument):
for s in sets:
s.add(argument)
# Regex matching a *.data line containing a test function call and
# its arguments. The actual definition is partly positional, but this
# regex is good enough in practice.
_test_case_line_re = re.compile(r'(?!depends_on:)(\w+):([^\n :][^:\n]*)')
def parse_test_cases(self, filename):
"""Parse a test case file (*.data), looking for algorithm metadata tests."""
with read_file_lines(filename) as lines:
for line in lines:
m = re.match(self._test_case_line_re, line)
if m:
self.add_test_case_line(m.group(1), m.group(2))
def gather_inputs(headers, test_suites, inputs_class=Inputs):
"""Read the list of inputs to test psa_constant_names with."""
inputs = inputs_class()
for header in headers:
inputs.parse_header(header)
for test_cases in test_suites:
inputs.parse_test_cases(test_cases)
inputs.gather_arguments()
return inputs
def remove_file_if_exists(filename):
"""Remove the specified file, ignoring errors."""
if not filename:
return
try:
os.remove(filename)
except OSError:
pass
def run_c(type_word, expressions, include_path=None, keep_c=False):
"""Generate and run a program to print out numerical values for expressions."""
if include_path is None:
include_path = []
if type_word == 'status':
cast_to = 'long'
printf_format = '%ld'
else:
cast_to = 'unsigned long'
printf_format = '0x%08lx'
c_name = None
exe_name = None
try:
c_fd, c_name = tempfile.mkstemp(prefix='tmp-{}-'.format(type_word),
suffix='.c',
dir='programs/psa')
exe_suffix = '.exe' if platform.system() == 'Windows' else ''
exe_name = c_name[:-2] + exe_suffix
remove_file_if_exists(exe_name)
c_file = os.fdopen(c_fd, 'w', encoding='ascii')
c_file.write('/* Generated by test_psa_constant_names.py for {} values */'
.format(type_word))
c_file.write('''
#include <stdio.h>
#include <psa/crypto.h>
int main(void)
{
''')
for expr in expressions:
c_file.write(' printf("{}\\n", ({}) {});\n'
.format(printf_format, cast_to, expr))
c_file.write(''' return 0;
}
''')
c_file.close()
cc = os.getenv('CC', 'cc')
subprocess.check_call([cc] +
['-I' + dir for dir in include_path] +
['-o', exe_name, c_name])
if keep_c:
sys.stderr.write('List of {} tests kept at {}\n'
.format(type_word, c_name))
else:
os.remove(c_name)
output = subprocess.check_output([exe_name])
return output.decode('ascii').strip().split('\n')
finally:
remove_file_if_exists(exe_name)
NORMALIZE_STRIP_RE = re.compile(r'\s+')
def normalize(expr):
"""Normalize the C expression so as not to care about trivial differences.
Currently "trivial differences" means whitespace.
"""
return re.sub(NORMALIZE_STRIP_RE, '', expr)
def collect_values(inputs, type_word, include_path=None, keep_c=False):
"""Generate expressions using known macro names and calculate their values.
Return a list of pairs of (expr, value) where expr is an expression and
value is a string representation of its integer value.
"""
names = inputs.get_names(type_word)
expressions = sorted(inputs.generate_expressions(names))
values = run_c(type_word, expressions,
include_path=include_path, keep_c=keep_c)
return expressions, values
class Tests:
"""An object representing tests and their results."""
Error = namedtuple('Error',
['type', 'expression', 'value', 'output'])
def __init__(self, options):
self.options = options
self.count = 0
self.errors = []
def run_one(self, inputs, type_word):
"""Test psa_constant_names for the specified type.
Run the program on the names for this type.
Use the inputs to figure out what arguments to pass to macros that
take arguments.
"""
expressions, values = collect_values(inputs, type_word,
include_path=self.options.include,
keep_c=self.options.keep_c)
output = subprocess.check_output([self.options.program, type_word] +
values)
outputs = output.decode('ascii').strip().split('\n')
self.count += len(expressions)
for expr, value, output in zip(expressions, values, outputs):
if self.options.show:
sys.stdout.write('{} {}\t{}\n'.format(type_word, value, output))
if normalize(expr) != normalize(output):
self.errors.append(self.Error(type=type_word,
expression=expr,
value=value,
output=output))
def run_all(self, inputs):
"""Run psa_constant_names on all the gathered inputs."""
for type_word in ['status', 'algorithm', 'ecc_curve', 'dh_group',
'key_type', 'key_usage']:
self.run_one(inputs, type_word)
def report(self, out):
"""Describe each case where the output is not as expected.
Write the errors to ``out``.
Also write a total.
"""
for error in self.errors:
out.write('For {} "{}", got "{}" (value: {})\n'
.format(error.type, error.expression,
error.output, error.value))
out.write('{} test cases'.format(self.count))
if self.errors:
out.write(', {} FAIL\n'.format(len(self.errors)))
else:
out.write(' PASS\n')
HEADERS = ['psa/crypto.h', 'psa/crypto_extra.h', 'psa/crypto_values.h']
TEST_SUITES = ['tests/suites/test_suite_psa_crypto_metadata.data']
def main():
parser = argparse.ArgumentParser(description=globals()['__doc__'])
parser.add_argument('--include', '-I',
action='append', default=['include'],
help='Directory for header files')
parser.add_argument('--keep-c',
action='store_true', dest='keep_c', default=False,
help='Keep the intermediate C file')
parser.add_argument('--no-keep-c',
action='store_false', dest='keep_c',
help='Don\'t keep the intermediate C file (default)')
parser.add_argument('--program',
default='programs/psa/psa_constant_names',
help='Program to test')
parser.add_argument('--show',
action='store_true',
help='Keep the intermediate C file')
parser.add_argument('--no-show',
action='store_false', dest='show',
help='Don\'t show tested values (default)')
options = parser.parse_args()
headers = [os.path.join(options.include[0], h) for h in HEADERS]
inputs = gather_inputs(headers, TEST_SUITES)
tests = Tests(options)
tests.run_all(inputs)
tests.report(sys.stdout)
if tests.errors:
exit(1)
if __name__ == '__main__':
main()