[修改] 增加freeRTOS

1. 版本FreeRTOSv202212.01,命名为kernel;
This commit is contained in:
2023-05-06 16:43:01 +00:00
commit a345df017b
20944 changed files with 11094377 additions and 0 deletions

View File

@ -0,0 +1,35 @@
#!/bin/bash -eu
# all-in-docker.sh
#
# Purpose
# -------
# This runs all.sh (except for armcc) in a Docker container.
#
# Notes for users
# ---------------
# See docker_env.sh for prerequisites and other information.
#
# See also all.sh for notes about invocation of that script.
# Copyright The Mbed TLS Contributors
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
source tests/scripts/docker_env.sh
# Run tests that are possible with openly available compilers
run_in_docker tests/scripts/all.sh \
--no-armcc \
$@

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,110 @@
#!/usr/bin/env python3
"""Analyze the test outcomes from a full CI run.
This script can also run on outcomes from a partial run, but the results are
less likely to be useful.
"""
import argparse
import sys
import traceback
import check_test_cases
class Results:
"""Process analysis results."""
def __init__(self):
self.error_count = 0
self.warning_count = 0
@staticmethod
def log(fmt, *args, **kwargs):
sys.stderr.write((fmt + '\n').format(*args, **kwargs))
def error(self, fmt, *args, **kwargs):
self.log('Error: ' + fmt, *args, **kwargs)
self.error_count += 1
def warning(self, fmt, *args, **kwargs):
self.log('Warning: ' + fmt, *args, **kwargs)
self.warning_count += 1
class TestCaseOutcomes:
"""The outcomes of one test case across many configurations."""
# pylint: disable=too-few-public-methods
def __init__(self):
# Collect a list of witnesses of the test case succeeding or failing.
# Currently we don't do anything with witnesses except count them.
# The format of a witness is determined by the read_outcome_file
# function; it's the platform and configuration joined by ';'.
self.successes = []
self.failures = []
def hits(self):
"""Return the number of times a test case has been run.
This includes passes and failures, but not skips.
"""
return len(self.successes) + len(self.failures)
def analyze_coverage(results, outcomes):
"""Check that all available test cases are executed at least once."""
available = check_test_cases.collect_available_test_cases()
for key in available:
hits = outcomes[key].hits() if key in outcomes else 0
if hits == 0:
# Make this a warning, not an error, as long as we haven't
# fixed this branch to have full coverage of test cases.
results.warning('Test case not executed: {}', key)
def analyze_outcomes(outcomes):
"""Run all analyses on the given outcome collection."""
results = Results()
analyze_coverage(results, outcomes)
return results
def read_outcome_file(outcome_file):
"""Parse an outcome file and return an outcome collection.
An outcome collection is a dictionary mapping keys to TestCaseOutcomes objects.
The keys are the test suite name and the test case description, separated
by a semicolon.
"""
outcomes = {}
with open(outcome_file, 'r', encoding='utf-8') as input_file:
for line in input_file:
(platform, config, suite, case, result, _cause) = line.split(';')
key = ';'.join([suite, case])
setup = ';'.join([platform, config])
if key not in outcomes:
outcomes[key] = TestCaseOutcomes()
if result == 'PASS':
outcomes[key].successes.append(setup)
elif result == 'FAIL':
outcomes[key].failures.append(setup)
return outcomes
def analyze_outcome_file(outcome_file):
"""Analyze the given outcome file."""
outcomes = read_outcome_file(outcome_file)
return analyze_outcomes(outcomes)
def main():
try:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('outcomes', metavar='OUTCOMES.CSV',
help='Outcome file to analyze')
options = parser.parse_args()
results = analyze_outcome_file(options.outcomes)
if results.error_count > 0:
sys.exit(1)
except Exception: # pylint: disable=broad-except
# Print the backtrace and exit explicitly with our chosen status.
traceback.print_exc()
sys.exit(120)
if __name__ == '__main__':
main()

View File

@ -0,0 +1,289 @@
#!/bin/sh
# basic-build-tests.sh
#
# Copyright The Mbed TLS Contributors
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Purpose
#
# Executes the basic test suites, captures the results, and generates a simple
# test report and code coverage report.
#
# The tests include:
# * Unit tests - executed using tests/scripts/run-test-suite.pl
# * Self-tests - executed using the test suites above
# * System tests - executed using tests/ssl-opt.sh
# * Interoperability tests - executed using tests/compat.sh
#
# The tests focus on functionality and do not consider performance.
#
# Note the tests self-adapt due to configurations in include/mbedtls/mbedtls_config.h
# which can lead to some tests being skipped, and can cause the number of
# available tests to fluctuate.
#
# This script has been written to be generic and should work on any shell.
#
# Usage: basic-build-tests.sh
#
# Abort on errors (and uninitiliased variables)
set -eu
if [ -d library -a -d include -a -d tests ]; then :; else
echo "Must be run from mbed TLS root" >&2
exit 1
fi
: ${OPENSSL:="openssl"}
: ${OPENSSL_LEGACY:="$OPENSSL"}
: ${GNUTLS_CLI:="gnutls-cli"}
: ${GNUTLS_SERV:="gnutls-serv"}
: ${GNUTLS_LEGACY_CLI:="$GNUTLS_CLI"}
: ${GNUTLS_LEGACY_SERV:="$GNUTLS_SERV"}
# Used to make ssl-opt.sh deterministic.
#
# See also RELEASE_SEED in all.sh. Debugging is easier if both values are kept
# in sync. If you change the value here because it breaks some tests, you'll
# definitely want to change it in all.sh as well.
: ${SEED:=1}
export SEED
# if MAKEFLAGS is not set add the -j option to speed up invocations of make
if [ -z "${MAKEFLAGS+set}" ]; then
export MAKEFLAGS="-j"
fi
# To avoid setting OpenSSL and GnuTLS for each call to compat.sh and ssl-opt.sh
# we just export the variables they require
export OPENSSL_CMD="$OPENSSL"
export GNUTLS_CLI="$GNUTLS_CLI"
export GNUTLS_SERV="$GNUTLS_SERV"
CONFIG_H='include/mbedtls/mbedtls_config.h'
CONFIG_BAK="$CONFIG_H.bak"
# Step 0 - print build environment info
OPENSSL="$OPENSSL" \
OPENSSL_LEGACY="$OPENSSL_LEGACY" \
GNUTLS_CLI="$GNUTLS_CLI" \
GNUTLS_SERV="$GNUTLS_SERV" \
GNUTLS_LEGACY_CLI="$GNUTLS_LEGACY_CLI" \
GNUTLS_LEGACY_SERV="$GNUTLS_LEGACY_SERV" \
scripts/output_env.sh
echo
# Step 1 - Make and instrumented build for code coverage
export CFLAGS=' --coverage -g3 -O0 '
export LDFLAGS=' --coverage'
make clean
cp "$CONFIG_H" "$CONFIG_BAK"
scripts/config.py full
make
# Step 2 - Execute the tests
TEST_OUTPUT=out_${PPID}
cd tests
if [ ! -f "seedfile" ]; then
dd if=/dev/urandom of="seedfile" bs=64 count=1
fi
echo
# Step 2a - Unit Tests (keep going even if some tests fail)
echo '################ Unit tests ################'
perl scripts/run-test-suites.pl -v 2 |tee unit-test-$TEST_OUTPUT
echo '^^^^^^^^^^^^^^^^ Unit tests ^^^^^^^^^^^^^^^^'
echo
# Step 2b - System Tests (keep going even if some tests fail)
echo
echo '################ ssl-opt.sh ################'
echo "ssl-opt.sh will use SEED=$SEED for udp_proxy"
sh ssl-opt.sh |tee sys-test-$TEST_OUTPUT
echo '^^^^^^^^^^^^^^^^ ssl-opt.sh ^^^^^^^^^^^^^^^^'
echo
# Step 2c - Compatibility tests (keep going even if some tests fail)
echo '################ compat.sh ################'
{
echo '#### compat.sh: Default versions'
sh compat.sh
echo
echo '#### compat.sh: legacy (null)'
OPENSSL_CMD="$OPENSSL_LEGACY" \
GNUTLS_CLI="$GNUTLS_LEGACY_CLI" GNUTLS_SERV="$GNUTLS_LEGACY_SERV" \
sh compat.sh -e '^$' -f 'NULL'
echo
echo '#### compat.sh: next (ARIA, ChaCha)'
OPENSSL_CMD="$OPENSSL_NEXT" sh compat.sh -e '^$' -f 'ARIA\|CHACHA'
echo
} | tee compat-test-$TEST_OUTPUT
echo '^^^^^^^^^^^^^^^^ compat.sh ^^^^^^^^^^^^^^^^'
echo
# Step 3 - Process the coverage report
cd ..
{
make lcov
echo SUCCESS
} | tee tests/cov-$TEST_OUTPUT
if [ "$(tail -n1 tests/cov-$TEST_OUTPUT)" != "SUCCESS" ]; then
echo >&2 "Fatal: 'make lcov' failed"
exit 2
fi
# Step 4 - Summarise the test report
echo
echo "========================================================================="
echo "Test Report Summary"
echo
# A failure of the left-hand side of a pipe is ignored (this is a limitation
# of sh). We'll use the presence of this file as a marker that the generation
# of the report succeeded.
rm -f "tests/basic-build-test-$$.ok"
{
cd tests
# Step 4a - Unit tests
echo "Unit tests - tests/scripts/run-test-suites.pl"
PASSED_TESTS=$(tail -n6 unit-test-$TEST_OUTPUT|sed -n -e 's/test cases passed :[\t]*\([0-9]*\)/\1/p'| tr -d ' ')
SKIPPED_TESTS=$(tail -n6 unit-test-$TEST_OUTPUT|sed -n -e 's/skipped :[ \t]*\([0-9]*\)/\1/p'| tr -d ' ')
TOTAL_SUITES=$(tail -n6 unit-test-$TEST_OUTPUT|sed -n -e 's/.* (\([0-9]*\) .*, [0-9]* tests run)/\1/p'| tr -d ' ')
FAILED_TESTS=$(tail -n6 unit-test-$TEST_OUTPUT|sed -n -e 's/failed :[\t]*\([0-9]*\)/\1/p' |tr -d ' ')
echo "No test suites : $TOTAL_SUITES"
echo "Passed : $PASSED_TESTS"
echo "Failed : $FAILED_TESTS"
echo "Skipped : $SKIPPED_TESTS"
echo "Total exec'd tests : $(($PASSED_TESTS + $FAILED_TESTS))"
echo "Total avail tests : $(($PASSED_TESTS + $FAILED_TESTS + $SKIPPED_TESTS))"
echo
TOTAL_PASS=$PASSED_TESTS
TOTAL_FAIL=$FAILED_TESTS
TOTAL_SKIP=$SKIPPED_TESTS
TOTAL_AVAIL=$(($PASSED_TESTS + $FAILED_TESTS + $SKIPPED_TESTS))
TOTAL_EXED=$(($PASSED_TESTS + $FAILED_TESTS))
# Step 4b - TLS Options tests
echo "TLS Options tests - tests/ssl-opt.sh"
PASSED_TESTS=$(tail -n5 sys-test-$TEST_OUTPUT|sed -n -e 's/.* (\([0-9]*\) \/ [0-9]* tests ([0-9]* skipped))$/\1/p')
SKIPPED_TESTS=$(tail -n5 sys-test-$TEST_OUTPUT|sed -n -e 's/.* ([0-9]* \/ [0-9]* tests (\([0-9]*\) skipped))$/\1/p')
TOTAL_TESTS=$(tail -n5 sys-test-$TEST_OUTPUT|sed -n -e 's/.* ([0-9]* \/ \([0-9]*\) tests ([0-9]* skipped))$/\1/p')
FAILED_TESTS=$(($TOTAL_TESTS - $PASSED_TESTS))
echo "Passed : $PASSED_TESTS"
echo "Failed : $FAILED_TESTS"
echo "Skipped : $SKIPPED_TESTS"
echo "Total exec'd tests : $TOTAL_TESTS"
echo "Total avail tests : $(($TOTAL_TESTS + $SKIPPED_TESTS))"
echo
TOTAL_PASS=$(($TOTAL_PASS+$PASSED_TESTS))
TOTAL_FAIL=$(($TOTAL_FAIL+$FAILED_TESTS))
TOTAL_SKIP=$(($TOTAL_SKIP+$SKIPPED_TESTS))
TOTAL_AVAIL=$(($TOTAL_AVAIL + $TOTAL_TESTS + $SKIPPED_TESTS))
TOTAL_EXED=$(($TOTAL_EXED + $TOTAL_TESTS))
# Step 4c - System Compatibility tests
echo "System/Compatibility tests - tests/compat.sh"
PASSED_TESTS=$(cat compat-test-$TEST_OUTPUT | sed -n -e 's/.* (\([0-9]*\) \/ [0-9]* tests ([0-9]* skipped))$/\1/p' | awk 'BEGIN{ s = 0 } { s += $1 } END{ print s }')
SKIPPED_TESTS=$(cat compat-test-$TEST_OUTPUT | sed -n -e 's/.* ([0-9]* \/ [0-9]* tests (\([0-9]*\) skipped))$/\1/p' | awk 'BEGIN{ s = 0 } { s += $1 } END{ print s }')
EXED_TESTS=$(cat compat-test-$TEST_OUTPUT | sed -n -e 's/.* ([0-9]* \/ \([0-9]*\) tests ([0-9]* skipped))$/\1/p' | awk 'BEGIN{ s = 0 } { s += $1 } END{ print s }')
FAILED_TESTS=$(($EXED_TESTS - $PASSED_TESTS))
echo "Passed : $PASSED_TESTS"
echo "Failed : $FAILED_TESTS"
echo "Skipped : $SKIPPED_TESTS"
echo "Total exec'd tests : $EXED_TESTS"
echo "Total avail tests : $(($EXED_TESTS + $SKIPPED_TESTS))"
echo
TOTAL_PASS=$(($TOTAL_PASS+$PASSED_TESTS))
TOTAL_FAIL=$(($TOTAL_FAIL+$FAILED_TESTS))
TOTAL_SKIP=$(($TOTAL_SKIP+$SKIPPED_TESTS))
TOTAL_AVAIL=$(($TOTAL_AVAIL + $EXED_TESTS + $SKIPPED_TESTS))
TOTAL_EXED=$(($TOTAL_EXED + $EXED_TESTS))
# Step 4d - Grand totals
echo "-------------------------------------------------------------------------"
echo "Total tests"
echo "Total Passed : $TOTAL_PASS"
echo "Total Failed : $TOTAL_FAIL"
echo "Total Skipped : $TOTAL_SKIP"
echo "Total exec'd tests : $TOTAL_EXED"
echo "Total avail tests : $TOTAL_AVAIL"
echo
# Step 4e - Coverage
echo "Coverage"
LINES_TESTED=$(tail -n4 cov-$TEST_OUTPUT|sed -n -e 's/ lines......: [0-9]*.[0-9]% (\([0-9]*\) of [0-9]* lines)/\1/p')
LINES_TOTAL=$(tail -n4 cov-$TEST_OUTPUT|sed -n -e 's/ lines......: [0-9]*.[0-9]% ([0-9]* of \([0-9]*\) lines)/\1/p')
FUNCS_TESTED=$(tail -n4 cov-$TEST_OUTPUT|sed -n -e 's/ functions..: [0-9]*.[0-9]% (\([0-9]*\) of [0-9]* functions)$/\1/p')
FUNCS_TOTAL=$(tail -n4 cov-$TEST_OUTPUT|sed -n -e 's/ functions..: [0-9]*.[0-9]% ([0-9]* of \([0-9]*\) functions)$/\1/p')
BRANCHES_TESTED=$(tail -n4 cov-$TEST_OUTPUT|sed -n -e 's/ branches...: [0-9]*.[0-9]% (\([0-9]*\) of [0-9]* branches)$/\1/p')
BRANCHES_TOTAL=$(tail -n4 cov-$TEST_OUTPUT|sed -n -e 's/ branches...: [0-9]*.[0-9]% ([0-9]* of \([0-9]*\) branches)$/\1/p')
LINES_PERCENT=$((1000*$LINES_TESTED/$LINES_TOTAL))
LINES_PERCENT="$(($LINES_PERCENT/10)).$(($LINES_PERCENT-($LINES_PERCENT/10)*10))"
FUNCS_PERCENT=$((1000*$FUNCS_TESTED/$FUNCS_TOTAL))
FUNCS_PERCENT="$(($FUNCS_PERCENT/10)).$(($FUNCS_PERCENT-($FUNCS_PERCENT/10)*10))"
BRANCHES_PERCENT=$((1000*$BRANCHES_TESTED/$BRANCHES_TOTAL))
BRANCHES_PERCENT="$(($BRANCHES_PERCENT/10)).$(($BRANCHES_PERCENT-($BRANCHES_PERCENT/10)*10))"
rm unit-test-$TEST_OUTPUT
rm sys-test-$TEST_OUTPUT
rm compat-test-$TEST_OUTPUT
rm cov-$TEST_OUTPUT
echo "Lines Tested : $LINES_TESTED of $LINES_TOTAL $LINES_PERCENT%"
echo "Functions Tested : $FUNCS_TESTED of $FUNCS_TOTAL $FUNCS_PERCENT%"
echo "Branches Tested : $BRANCHES_TESTED of $BRANCHES_TOTAL $BRANCHES_PERCENT%"
echo
# Mark the report generation as having succeeded. This must be the
# last thing in the report generation.
touch "basic-build-test-$$.ok"
} | tee coverage-summary.txt
make clean
if [ -f "$CONFIG_BAK" ]; then
mv "$CONFIG_BAK" "$CONFIG_H"
fi
# The file must exist, otherwise it means something went wrong while generating
# the coverage report. If something did go wrong, rm will complain so this
# script will exit with a failure status.
rm "tests/basic-build-test-$$.ok"

View File

@ -0,0 +1,45 @@
#!/bin/bash -eu
# basic-in-docker.sh
#
# Purpose
# -------
# This runs sanity checks and library tests in a Docker container. The tests
# are run for both clang and gcc. The testing includes a full test run
# in the default configuration, partial test runs in the reference
# configurations, and some dependency tests.
#
# Notes for users
# ---------------
# See docker_env.sh for prerequisites and other information.
# Copyright The Mbed TLS Contributors
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
source tests/scripts/docker_env.sh
run_in_docker tests/scripts/all.sh 'check_*'
for compiler in clang gcc; do
run_in_docker -e CC=${compiler} cmake -D CMAKE_BUILD_TYPE:String="Check" .
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

@ -0,0 +1,79 @@
#!/usr/bin/env perl
# Detect comment blocks that are likely meant to be doxygen blocks but aren't.
#
# More precisely, look for normal comment block containing '\'.
# Of course one could use doxygen warnings, eg with:
# sed -e '/EXTRACT/s/YES/NO/' doxygen/mbedtls.doxyfile | doxygen -
# but that would warn about any undocumented item, while our goal is to find
# items that are documented, but not marked as such by mistake.
#
# Copyright The Mbed TLS Contributors
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
use warnings;
use strict;
use File::Basename;
# C/header files in the following directories will be checked
my @directories = qw(include/mbedtls library doxygen/input);
# very naive pattern to find directives:
# everything with a backslach except '\0' and backslash at EOL
my $doxy_re = qr/\\(?!0|\n)/;
# Return an error code to the environment if a potential error in the
# source code is found.
my $exit_code = 0;
sub check_file {
my ($fname) = @_;
open my $fh, '<', $fname or die "Failed to open '$fname': $!\n";
# first line of the last normal comment block,
# or 0 if not in a normal comment block
my $block_start = 0;
while (my $line = <$fh>) {
$block_start = $. if $line =~ m/\/\*(?![*!])/;
$block_start = 0 if $line =~ m/\*\//;
if ($block_start and $line =~ m/$doxy_re/) {
print "$fname:$block_start: directive on line $.\n";
$block_start = 0; # report only one directive per block
$exit_code = 1;
}
}
close $fh;
}
sub check_dir {
my ($dirname) = @_;
for my $file (<$dirname/*.[ch]>) {
check_file($file);
}
}
# Check that the script is being run from the project's root directory.
for my $dir (@directories) {
if (! -d $dir) {
die "This script must be run from the mbed TLS root directory";
} else {
check_dir($dir)
}
}
exit $exit_code;
__END__

View File

@ -0,0 +1,129 @@
#! /usr/bin/env sh
# Copyright The Mbed TLS Contributors
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Purpose
#
# Check if generated files are up-to-date.
set -eu
if [ $# -ne 0 ] && [ "$1" = "--help" ]; then
cat <<EOF
$0 [-l | -u]
This script checks that all generated file are up-to-date. If some aren't, by
default the scripts reports it and exits in error; with the -u option, it just
updates them instead.
-u Update the files rather than return an error for out-of-date files.
-l List generated files, but do not update them.
EOF
exit
fi
if [ -d library -a -d include -a -d tests ]; then :; else
echo "Must be run from mbed TLS root" >&2
exit 1
fi
UPDATE=
LIST=
while getopts lu OPTLET; do
case $OPTLET in
l) LIST=1;;
u) UPDATE=1;;
esac
done
# check SCRIPT FILENAME[...]
# check SCRIPT DIRECTORY
# Run SCRIPT and check that it does not modify any of the specified files.
# In the first form, there can be any number of FILENAMEs, which must be
# regular files.
# In the second form, there must be a single DIRECTORY, standing for the
# list of files in the directory. Running SCRIPT must not modify any file
# in the directory and must not add or remove files either.
# If $UPDATE is empty, abort with an error status if a file is modified.
check()
{
SCRIPT=$1
shift
if [ -n "$LIST" ]; then
printf '%s\n' "$@"
return
fi
directory=
if [ -d "$1" ]; then
directory="$1"
rm -f "$directory"/*.bak
set -- "$1"/*
fi
for FILE in "$@"; do
if [ -e "$FILE" ]; then
cp -p "$FILE" "$FILE.bak"
else
rm -f "$FILE.bak"
fi
done
"$SCRIPT"
# Compare the script output to the old files and remove backups
for FILE in "$@"; do
if diff "$FILE" "$FILE.bak" >/dev/null 2>&1; then
# Move the original file back so that $FILE's timestamp doesn't
# change (avoids spurious rebuilds with make).
mv "$FILE.bak" "$FILE"
else
echo "'$FILE' was either modified or deleted by '$SCRIPT'"
if [ -z "$UPDATE" ]; then
exit 1
else
rm -f "$FILE.bak"
fi
fi
done
if [ -n "$directory" ]; then
old_list="$*"
set -- "$directory"/*
new_list="$*"
# Check if there are any new files
if [ "$old_list" != "$new_list" ]; then
echo "Files were deleted or created by '$SCRIPT'"
echo "Before: $old_list"
echo "After: $new_list"
if [ -z "$UPDATE" ]; then
exit 1
fi
fi
fi
}
check scripts/generate_errors.pl library/error.c
check scripts/generate_query_config.pl programs/test/query_config.c
check scripts/generate_driver_wrappers.py library/psa_crypto_driver_wrappers.c
check scripts/generate_features.pl library/version_features.c
check scripts/generate_ssl_debug_helpers.py library/ssl_debug_helpers_generated.c
# generate_visualc_files enumerates source files (library/*.c). It doesn't
# care about their content, but the files must exist. So it must run after
# the step that creates or updates these files.
check scripts/generate_visualc_files.pl visualc/VS2010
check scripts/generate_psa_constants.py programs/psa/psa_constant_names_generated.c
check tests/scripts/generate_psa_tests.py $(tests/scripts/generate_psa_tests.py --list)

View File

@ -0,0 +1,80 @@
#! /usr/bin/env sh
# Copyright The Mbed TLS Contributors
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Purpose: check Python files for potential programming errors or maintenance
# hurdles. Run pylint to detect some potential mistakes and enforce PEP8
# coding standards. Run mypy to perform static type checking.
# We'll keep going on errors and report the status at the end.
ret=0
if type python3 >/dev/null 2>/dev/null; then
PYTHON=python3
else
PYTHON=python
fi
check_version () {
$PYTHON - "$2" <<EOF
import packaging.version
import sys
import $1 as package
actual = package.__version__
wanted = sys.argv[1]
if packaging.version.parse(actual) < packaging.version.parse(wanted):
sys.stderr.write("$1: version %s is too old (want %s)\n" % (actual, wanted))
exit(1)
EOF
}
can_pylint () {
# Pylint 1.5.2 from Ubuntu 16.04 is too old:
# E: 34, 0: Unable to import 'mbedtls_dev' (import-error)
# Pylint 1.8.3 from Ubuntu 18.04 passed on the first commit containing this line.
check_version pylint 1.8.3
}
can_mypy () {
# mypy 0.770 is too old:
# tests/scripts/test_psa_constant_names.py:34: error: Cannot find implementation or library stub for module named 'mbedtls_dev'
# mypy 0.780 from pip passed on the first commit containing this line.
check_version mypy.version 0.780
}
# With just a --can-xxx option, check whether the tool for xxx is available
# with an acceptable version, and exit without running any checks. The exit
# status is true if the tool is available and acceptable and false otherwise.
if [ "$1" = "--can-pylint" ]; then
can_pylint
exit
elif [ "$1" = "--can-mypy" ]; then
can_mypy
exit
fi
echo 'Running pylint ...'
$PYTHON -m pylint -j 2 scripts/mbedtls_dev/*.py scripts/*.py tests/scripts/*.py || {
echo >&2 "pylint reported errors"
ret=1
}
echo
echo 'Running mypy ...'
$PYTHON -m mypy scripts/*.py tests/scripts/*.py ||
ret=1
exit $ret

View File

@ -0,0 +1,410 @@
#!/usr/bin/env python3
# Copyright The Mbed TLS Contributors
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
This script checks the current state of the source code for minor issues,
including incorrect file permissions, presence of tabs, non-Unix line endings,
trailing whitespace, and presence of UTF-8 BOM.
Note: requires python 3, must be run from Mbed TLS root.
"""
import os
import argparse
import logging
import codecs
import re
import subprocess
import sys
try:
from typing import FrozenSet, Optional, Pattern # pylint: disable=unused-import
except ImportError:
pass
class FileIssueTracker:
"""Base class for file-wide issue tracking.
To implement a checker that processes a file as a whole, inherit from
this class and implement `check_file_for_issue` and define ``heading``.
``suffix_exemptions``: files whose name ends with a string in this set
will not be checked.
``path_exemptions``: files whose path (relative to the root of the source
tree) matches this regular expression will not be checked. This can be
``None`` to match no path. Paths are normalized and converted to ``/``
separators before matching.
``heading``: human-readable description of the issue
"""
suffix_exemptions = frozenset() #type: FrozenSet[str]
path_exemptions = None #type: Optional[Pattern[str]]
# heading must be defined in derived classes.
# pylint: disable=no-member
def __init__(self):
self.files_with_issues = {}
@staticmethod
def normalize_path(filepath):
"""Normalize ``filepath`` with / as the directory separator."""
filepath = os.path.normpath(filepath)
# On Windows, we may have backslashes to separate directories.
# We need slashes to match exemption lists.
seps = os.path.sep
if os.path.altsep is not None:
seps += os.path.altsep
return '/'.join(filepath.split(seps))
def should_check_file(self, filepath):
"""Whether the given file name should be checked.
Files whose name ends with a string listed in ``self.suffix_exemptions``
or whose path matches ``self.path_exemptions`` will not be checked.
"""
for files_exemption in self.suffix_exemptions:
if filepath.endswith(files_exemption):
return False
if self.path_exemptions and \
re.match(self.path_exemptions, self.normalize_path(filepath)):
return False
return True
def check_file_for_issue(self, filepath):
"""Check the specified file for the issue that this class is for.
Subclasses must implement this method.
"""
raise NotImplementedError
def record_issue(self, filepath, line_number):
"""Record that an issue was found at the specified location."""
if filepath not in self.files_with_issues.keys():
self.files_with_issues[filepath] = []
self.files_with_issues[filepath].append(line_number)
def output_file_issues(self, logger):
"""Log all the locations where the issue was found."""
if self.files_with_issues.values():
logger.info(self.heading)
for filename, lines in sorted(self.files_with_issues.items()):
if lines:
logger.info("{}: {}".format(
filename, ", ".join(str(x) for x in lines)
))
else:
logger.info(filename)
logger.info("")
BINARY_FILE_PATH_RE_LIST = [
r'docs/.*\.pdf\Z',
r'programs/fuzz/corpuses/[^.]+\Z',
r'tests/data_files/[^.]+\Z',
r'tests/data_files/.*\.(crt|csr|db|der|key|pubkey)\Z',
r'tests/data_files/.*\.req\.[^/]+\Z',
r'tests/data_files/.*malformed[^/]+\Z',
r'tests/data_files/format_pkcs12\.fmt\Z',
]
BINARY_FILE_PATH_RE = re.compile('|'.join(BINARY_FILE_PATH_RE_LIST))
class LineIssueTracker(FileIssueTracker):
"""Base class for line-by-line issue tracking.
To implement a checker that processes files line by line, inherit from
this class and implement `line_with_issue`.
"""
# Exclude binary files.
path_exemptions = BINARY_FILE_PATH_RE
def issue_with_line(self, line, filepath):
"""Check the specified line for the issue that this class is for.
Subclasses must implement this method.
"""
raise NotImplementedError
def check_file_line(self, filepath, line, line_number):
if self.issue_with_line(line, filepath):
self.record_issue(filepath, line_number)
def check_file_for_issue(self, filepath):
"""Check the lines of the specified file.
Subclasses must implement the ``issue_with_line`` method.
"""
with open(filepath, "rb") as f:
for i, line in enumerate(iter(f.readline, b"")):
self.check_file_line(filepath, line, i + 1)
def is_windows_file(filepath):
_root, ext = os.path.splitext(filepath)
return ext in ('.bat', '.dsp', '.dsw', '.sln', '.vcxproj')
class PermissionIssueTracker(FileIssueTracker):
"""Track files with bad permissions.
Files that are not executable scripts must not be executable."""
heading = "Incorrect permissions:"
# .py files can be either full scripts or modules, so they may or may
# not be executable.
suffix_exemptions = frozenset({".py"})
def check_file_for_issue(self, filepath):
is_executable = os.access(filepath, os.X_OK)
should_be_executable = filepath.endswith((".sh", ".pl"))
if is_executable != should_be_executable:
self.files_with_issues[filepath] = None
class ShebangIssueTracker(FileIssueTracker):
"""Track files with a bad, missing or extraneous shebang line.
Executable scripts must start with a valid shebang (#!) line.
"""
heading = "Invalid shebang line:"
# Allow either /bin/sh, /bin/bash, or /usr/bin/env.
# Allow at most one argument (this is a Linux limitation).
# For sh and bash, the argument if present must be options.
# For env, the argument must be the base name of the interpreter.
_shebang_re = re.compile(rb'^#! ?(?:/bin/(bash|sh)(?: -[^\n ]*)?'
rb'|/usr/bin/env ([^\n /]+))$')
_extensions = {
b'bash': 'sh',
b'perl': 'pl',
b'python3': 'py',
b'sh': 'sh',
}
def is_valid_shebang(self, first_line, filepath):
m = re.match(self._shebang_re, first_line)
if not m:
return False
interpreter = m.group(1) or m.group(2)
if interpreter not in self._extensions:
return False
if not filepath.endswith('.' + self._extensions[interpreter]):
return False
return True
def check_file_for_issue(self, filepath):
is_executable = os.access(filepath, os.X_OK)
with open(filepath, "rb") as f:
first_line = f.readline()
if first_line.startswith(b'#!'):
if not is_executable:
# Shebang on a non-executable file
self.files_with_issues[filepath] = None
elif not self.is_valid_shebang(first_line, filepath):
self.files_with_issues[filepath] = [1]
elif is_executable:
# Executable without a shebang
self.files_with_issues[filepath] = None
class EndOfFileNewlineIssueTracker(FileIssueTracker):
"""Track files that end with an incomplete line
(no newline character at the end of the last line)."""
heading = "Missing newline at end of file:"
path_exemptions = BINARY_FILE_PATH_RE
def check_file_for_issue(self, filepath):
with open(filepath, "rb") as f:
try:
f.seek(-1, 2)
except OSError:
# This script only works on regular files. If we can't seek
# 1 before the end, it means that this position is before
# the beginning of the file, i.e. that the file is empty.
return
if f.read(1) != b"\n":
self.files_with_issues[filepath] = None
class Utf8BomIssueTracker(FileIssueTracker):
"""Track files that start with a UTF-8 BOM.
Files should be ASCII or UTF-8. Valid UTF-8 does not start with a BOM."""
heading = "UTF-8 BOM present:"
suffix_exemptions = frozenset([".vcxproj", ".sln"])
path_exemptions = BINARY_FILE_PATH_RE
def check_file_for_issue(self, filepath):
with open(filepath, "rb") as f:
if f.read().startswith(codecs.BOM_UTF8):
self.files_with_issues[filepath] = None
class UnixLineEndingIssueTracker(LineIssueTracker):
"""Track files with non-Unix line endings (i.e. files with CR)."""
heading = "Non-Unix line endings:"
def should_check_file(self, filepath):
if not super().should_check_file(filepath):
return False
return not is_windows_file(filepath)
def issue_with_line(self, line, _filepath):
return b"\r" in line
class WindowsLineEndingIssueTracker(LineIssueTracker):
"""Track files with non-Windows line endings (i.e. CR or LF not in CRLF)."""
heading = "Non-Windows line endings:"
def should_check_file(self, filepath):
if not super().should_check_file(filepath):
return False
return is_windows_file(filepath)
def issue_with_line(self, line, _filepath):
return not line.endswith(b"\r\n") or b"\r" in line[:-2]
class TrailingWhitespaceIssueTracker(LineIssueTracker):
"""Track lines with trailing whitespace."""
heading = "Trailing whitespace:"
suffix_exemptions = frozenset([".dsp", ".md"])
def issue_with_line(self, line, _filepath):
return line.rstrip(b"\r\n") != line.rstrip()
class TabIssueTracker(LineIssueTracker):
"""Track lines with tabs."""
heading = "Tabs present:"
suffix_exemptions = frozenset([
".pem", # some openssl dumps have tabs
".sln",
"/Makefile",
"/Makefile.inc",
"/generate_visualc_files.pl",
])
def issue_with_line(self, line, _filepath):
return b"\t" in line
class MergeArtifactIssueTracker(LineIssueTracker):
"""Track lines with merge artifacts.
These are leftovers from a ``git merge`` that wasn't fully edited."""
heading = "Merge artifact:"
def issue_with_line(self, line, _filepath):
# Detect leftover git conflict markers.
if line.startswith(b'<<<<<<< ') or line.startswith(b'>>>>>>> '):
return True
if line.startswith(b'||||||| '): # from merge.conflictStyle=diff3
return True
if line.rstrip(b'\r\n') == b'=======' and \
not _filepath.endswith('.md'):
return True
return False
class IntegrityChecker:
"""Sanity-check files under the current directory."""
def __init__(self, log_file):
"""Instantiate the sanity checker.
Check files under the current directory.
Write a report of issues to log_file."""
self.check_repo_path()
self.logger = None
self.setup_logger(log_file)
self.issues_to_check = [
PermissionIssueTracker(),
ShebangIssueTracker(),
EndOfFileNewlineIssueTracker(),
Utf8BomIssueTracker(),
UnixLineEndingIssueTracker(),
WindowsLineEndingIssueTracker(),
TrailingWhitespaceIssueTracker(),
TabIssueTracker(),
MergeArtifactIssueTracker(),
]
@staticmethod
def check_repo_path():
if not all(os.path.isdir(d) for d in ["include", "library", "tests"]):
raise Exception("Must be run from Mbed TLS root")
def setup_logger(self, log_file, level=logging.INFO):
self.logger = logging.getLogger()
self.logger.setLevel(level)
if log_file:
handler = logging.FileHandler(log_file)
self.logger.addHandler(handler)
else:
console = logging.StreamHandler()
self.logger.addHandler(console)
@staticmethod
def collect_files():
bytes_output = subprocess.check_output(['git', 'ls-files', '-z'])
bytes_filepaths = bytes_output.split(b'\0')[:-1]
ascii_filepaths = map(lambda fp: fp.decode('ascii'), bytes_filepaths)
# Prepend './' to files in the top-level directory so that
# something like `'/Makefile' in fp` matches in the top-level
# directory as well as in subdirectories.
return [fp if os.path.dirname(fp) else os.path.join(os.curdir, fp)
for fp in ascii_filepaths]
def check_files(self):
for issue_to_check in self.issues_to_check:
for filepath in self.collect_files():
if issue_to_check.should_check_file(filepath):
issue_to_check.check_file_for_issue(filepath)
def output_issues(self):
integrity_return_code = 0
for issue_to_check in self.issues_to_check:
if issue_to_check.files_with_issues:
integrity_return_code = 1
issue_to_check.output_file_issues(self.logger)
return integrity_return_code
def run_main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"-l", "--log_file", type=str, help="path to optional output log",
)
check_args = parser.parse_args()
integrity_check = IntegrityChecker(check_args.log_file)
integrity_check.check_files()
return_code = integrity_check.output_issues()
sys.exit(return_code)
if __name__ == "__main__":
run_main()

View File

@ -0,0 +1,910 @@
#!/usr/bin/env python3
#
# Copyright The Mbed TLS Contributors
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
This script confirms that the naming of all symbols and identifiers in Mbed TLS
are consistent with the house style and are also self-consistent. It only runs
on Linux and macOS since it depends on nm.
It contains two major Python classes, CodeParser and NameChecker. They both have
a comprehensive "run-all" function (comprehensive_parse() and perform_checks())
but the individual functions can also be used for specific needs.
CodeParser makes heavy use of regular expressions to parse the code, and is
dependent on the current code formatting. Many Python C parser libraries require
preprocessed C code, which means no macro parsing. Compiler tools are also not
very helpful when we want the exact location in the original source (which
becomes impossible when e.g. comments are stripped).
NameChecker performs the following checks:
- All exported and available symbols in the library object files, are explicitly
declared in the header files. This uses the nm command.
- All macros, constants, and identifiers (function names, struct names, etc)
follow the required regex pattern.
- Typo checking: All words that begin with MBED exist as macros or constants.
The script returns 0 on success, 1 on test failure, and 2 if there is a script
error. It must be run from Mbed TLS root.
"""
import abc
import argparse
import fnmatch
import glob
import textwrap
import os
import sys
import traceback
import re
import enum
import shutil
import subprocess
import logging
# Naming patterns to check against. These are defined outside the NameCheck
# class for ease of modification.
MACRO_PATTERN = r"^(MBEDTLS|PSA)_[0-9A-Z_]*[0-9A-Z]$"
CONSTANTS_PATTERN = MACRO_PATTERN
IDENTIFIER_PATTERN = r"^(mbedtls|psa)_[0-9a-z_]*[0-9a-z]$"
class Match(): # pylint: disable=too-few-public-methods
"""
A class representing a match, together with its found position.
Fields:
* filename: the file that the match was in.
* line: the full line containing the match.
* line_no: the line number.
* pos: a tuple of (start, end) positions on the line where the match is.
* name: the match itself.
"""
def __init__(self, filename, line, line_no, pos, name):
# pylint: disable=too-many-arguments
self.filename = filename
self.line = line
self.line_no = line_no
self.pos = pos
self.name = name
def __str__(self):
"""
Return a formatted code listing representation of the erroneous line.
"""
gutter = format(self.line_no, "4d")
underline = self.pos[0] * " " + (self.pos[1] - self.pos[0]) * "^"
return (
" {0} |\n".format(" " * len(gutter)) +
" {0} | {1}".format(gutter, self.line) +
" {0} | {1}\n".format(" " * len(gutter), underline)
)
class Problem(abc.ABC): # pylint: disable=too-few-public-methods
"""
An abstract parent class representing a form of static analysis error.
It extends an Abstract Base Class, which means it is not instantiable, and
it also mandates certain abstract methods to be implemented in subclasses.
"""
# Class variable to control the quietness of all problems
quiet = False
def __init__(self):
self.textwrapper = textwrap.TextWrapper()
self.textwrapper.width = 80
self.textwrapper.initial_indent = " > "
self.textwrapper.subsequent_indent = " "
def __str__(self):
"""
Unified string representation method for all Problems.
"""
if self.__class__.quiet:
return self.quiet_output()
return self.verbose_output()
@abc.abstractmethod
def quiet_output(self):
"""
The output when --quiet is enabled.
"""
pass
@abc.abstractmethod
def verbose_output(self):
"""
The default output with explanation and code snippet if appropriate.
"""
pass
class SymbolNotInHeader(Problem): # pylint: disable=too-few-public-methods
"""
A problem that occurs when an exported/available symbol in the object file
is not explicitly declared in header files. Created with
NameCheck.check_symbols_declared_in_header()
Fields:
* symbol_name: the name of the symbol.
"""
def __init__(self, symbol_name):
self.symbol_name = symbol_name
Problem.__init__(self)
def quiet_output(self):
return "{0}".format(self.symbol_name)
def verbose_output(self):
return self.textwrapper.fill(
"'{0}' was found as an available symbol in the output of nm, "
"however it was not declared in any header files."
.format(self.symbol_name))
class PatternMismatch(Problem): # pylint: disable=too-few-public-methods
"""
A problem that occurs when something doesn't match the expected pattern.
Created with NameCheck.check_match_pattern()
Fields:
* pattern: the expected regex pattern
* match: the Match object in question
"""
def __init__(self, pattern, match):
self.pattern = pattern
self.match = match
Problem.__init__(self)
def quiet_output(self):
return (
"{0}:{1}:{2}"
.format(self.match.filename, self.match.line_no, self.match.name)
)
def verbose_output(self):
return self.textwrapper.fill(
"{0}:{1}: '{2}' does not match the required pattern '{3}'."
.format(
self.match.filename,
self.match.line_no,
self.match.name,
self.pattern
)
) + "\n" + str(self.match)
class Typo(Problem): # pylint: disable=too-few-public-methods
"""
A problem that occurs when a word using MBED doesn't appear to be defined as
constants nor enum values. Created with NameCheck.check_for_typos()
Fields:
* match: the Match object of the MBED name in question.
"""
def __init__(self, match):
self.match = match
Problem.__init__(self)
def quiet_output(self):
return (
"{0}:{1}:{2}"
.format(self.match.filename, self.match.line_no, self.match.name)
)
def verbose_output(self):
return self.textwrapper.fill(
"{0}:{1}: '{2}' looks like a typo. It was not found in any "
"macros or any enums. If this is not a typo, put "
"//no-check-names after it."
.format(self.match.filename, self.match.line_no, self.match.name)
) + "\n" + str(self.match)
class CodeParser():
"""
Class for retrieving files and parsing the code. This can be used
independently of the checks that NameChecker performs, for example for
list_internal_identifiers.py.
"""
def __init__(self, log):
self.log = log
self.check_repo_path()
# Memo for storing "glob expression": set(filepaths)
self.files = {}
# Globally excluded filenames.
# Note that "*" can match directory separators in exclude lists.
self.excluded_files = ["*/bn_mul", "*/compat-2.x.h"]
@staticmethod
def check_repo_path():
"""
Check that the current working directory is the project root, and throw
an exception if not.
"""
if not all(os.path.isdir(d) for d in ["include", "library", "tests"]):
raise Exception("This script must be run from Mbed TLS root")
def comprehensive_parse(self):
"""
Comprehensive ("default") function to call each parsing function and
retrieve various elements of the code, together with the source location.
Returns a dict of parsed item key to the corresponding List of Matches.
"""
self.log.info("Parsing source code...")
self.log.debug(
"The following files are excluded from the search: {}"
.format(str(self.excluded_files))
)
all_macros = self.parse_macros([
"include/mbedtls/*.h",
"include/psa/*.h",
"library/*.h",
"tests/include/test/drivers/*.h",
"3rdparty/everest/include/everest/everest.h",
"3rdparty/everest/include/everest/x25519.h"
])
enum_consts = self.parse_enum_consts([
"include/mbedtls/*.h",
"library/*.h",
"3rdparty/everest/include/everest/everest.h",
"3rdparty/everest/include/everest/x25519.h"
])
identifiers = self.parse_identifiers([
"include/mbedtls/*.h",
"include/psa/*.h",
"library/*.h",
"3rdparty/everest/include/everest/everest.h",
"3rdparty/everest/include/everest/x25519.h"
])
mbed_words = self.parse_mbed_words([
"include/mbedtls/*.h",
"include/psa/*.h",
"library/*.h",
"3rdparty/everest/include/everest/everest.h",
"3rdparty/everest/include/everest/x25519.h",
"library/*.c",
"3rdparty/everest/library/everest.c",
"3rdparty/everest/library/x25519.c"
])
symbols = self.parse_symbols()
# Remove identifier macros like mbedtls_printf or mbedtls_calloc
identifiers_justname = [x.name for x in identifiers]
actual_macros = []
for macro in all_macros:
if macro.name not in identifiers_justname:
actual_macros.append(macro)
self.log.debug("Found:")
# Aligns the counts on the assumption that none exceeds 4 digits
self.log.debug(" {:4} Total Macros".format(len(all_macros)))
self.log.debug(" {:4} Non-identifier Macros".format(len(actual_macros)))
self.log.debug(" {:4} Enum Constants".format(len(enum_consts)))
self.log.debug(" {:4} Identifiers".format(len(identifiers)))
self.log.debug(" {:4} Exported Symbols".format(len(symbols)))
return {
"macros": actual_macros,
"enum_consts": enum_consts,
"identifiers": identifiers,
"symbols": symbols,
"mbed_words": mbed_words
}
def is_file_excluded(self, path, exclude_wildcards):
"""Whether the given file path is excluded."""
# exclude_wildcards may be None. Also, consider the global exclusions.
exclude_wildcards = (exclude_wildcards or []) + self.excluded_files
for pattern in exclude_wildcards:
if fnmatch.fnmatch(path, pattern):
return True
return False
def get_files(self, include_wildcards, exclude_wildcards):
"""
Get all files that match any of the UNIX-style wildcards. While the
check_names script is designed only for use on UNIX/macOS (due to nm),
this function alone would work fine on Windows even with forward slashes
in the wildcard.
Args:
* include_wildcards: a List of shell-style wildcards to match filepaths.
* exclude_wildcards: a List of shell-style wildcards to exclude.
Returns a List of relative filepaths.
"""
accumulator = set()
for include_wildcard in include_wildcards:
accumulator = accumulator.union(glob.iglob(include_wildcard))
return list(path for path in accumulator
if not self.is_file_excluded(path, exclude_wildcards))
def parse_macros(self, include, exclude=None):
"""
Parse all macros defined by #define preprocessor directives.
Args:
* include: A List of glob expressions to look for files through.
* exclude: A List of glob expressions for excluding files.
Returns a List of Match objects for the found macros.
"""
macro_regex = re.compile(r"# *define +(?P<macro>\w+)")
exclusions = (
"asm", "inline", "EMIT", "_CRT_SECURE_NO_DEPRECATE", "MULADDC_"
)
files = self.get_files(include, exclude)
self.log.debug("Looking for macros in {} files".format(len(files)))
macros = []
for header_file in files:
with open(header_file, "r", encoding="utf-8") as header:
for line_no, line in enumerate(header):
for macro in macro_regex.finditer(line):
if macro.group("macro").startswith(exclusions):
continue
macros.append(Match(
header_file,
line,
line_no,
macro.span("macro"),
macro.group("macro")))
return macros
def parse_mbed_words(self, include, exclude=None):
"""
Parse all words in the file that begin with MBED, in and out of macros,
comments, anything.
Args:
* include: A List of glob expressions to look for files through.
* exclude: A List of glob expressions for excluding files.
Returns a List of Match objects for words beginning with MBED.
"""
# Typos of TLS are common, hence the broader check below than MBEDTLS.
mbed_regex = re.compile(r"\bMBED.+?_[A-Z0-9_]*")
exclusions = re.compile(r"// *no-check-names|#error")
files = self.get_files(include, exclude)
self.log.debug("Looking for MBED words in {} files".format(len(files)))
mbed_words = []
for filename in files:
with open(filename, "r", encoding="utf-8") as fp:
for line_no, line in enumerate(fp):
if exclusions.search(line):
continue
for name in mbed_regex.finditer(line):
mbed_words.append(Match(
filename,
line,
line_no,
name.span(0),
name.group(0)))
return mbed_words
def parse_enum_consts(self, include, exclude=None):
"""
Parse all enum value constants that are declared.
Args:
* include: A List of glob expressions to look for files through.
* exclude: A List of glob expressions for excluding files.
Returns a List of Match objects for the findings.
"""
files = self.get_files(include, exclude)
self.log.debug("Looking for enum consts in {} files".format(len(files)))
# Emulate a finite state machine to parse enum declarations.
# OUTSIDE_KEYWORD = outside the enum keyword
# IN_BRACES = inside enum opening braces
# IN_BETWEEN = between enum keyword and opening braces
states = enum.Enum("FSM", ["OUTSIDE_KEYWORD", "IN_BRACES", "IN_BETWEEN"])
enum_consts = []
for header_file in files:
state = states.OUTSIDE_KEYWORD
with open(header_file, "r", encoding="utf-8") as header:
for line_no, line in enumerate(header):
# Match typedefs and brackets only when they are at the
# beginning of the line -- if they are indented, they might
# be sub-structures within structs, etc.
if (state == states.OUTSIDE_KEYWORD and
re.search(r"^(typedef +)?enum +{", line)):
state = states.IN_BRACES
elif (state == states.OUTSIDE_KEYWORD and
re.search(r"^(typedef +)?enum", line)):
state = states.IN_BETWEEN
elif (state == states.IN_BETWEEN and
re.search(r"^{", line)):
state = states.IN_BRACES
elif (state == states.IN_BRACES and
re.search(r"^}", line)):
state = states.OUTSIDE_KEYWORD
elif (state == states.IN_BRACES and
not re.search(r"^ *#", line)):
enum_const = re.search(r"^ *(?P<enum_const>\w+)", line)
if not enum_const:
continue
enum_consts.append(Match(
header_file,
line,
line_no,
enum_const.span("enum_const"),
enum_const.group("enum_const")))
return enum_consts
IGNORED_CHUNK_REGEX = re.compile('|'.join([
r'/\*.*?\*/', # block comment entirely on one line
r'//.*', # line comment
r'(?P<string>")(?:[^\\\"]|\\.)*"', # string literal
]))
def strip_comments_and_literals(self, line, in_block_comment):
"""Strip comments and string literals from line.
Continuation lines are not supported.
If in_block_comment is true, assume that the line starts inside a
block comment.
Return updated values of (line, in_block_comment) where:
* Comments in line have been replaced by a space (or nothing at the
start or end of the line).
* String contents have been removed.
* in_block_comment indicates whether the line ends inside a block
comment that continues on the next line.
"""
# Terminate current multiline comment?
if in_block_comment:
m = re.search(r"\*/", line)
if m:
in_block_comment = False
line = line[m.end(0):]
else:
return '', True
# Remove full comments and string literals.
# Do it all together to handle cases like "/*" correctly.
# Note that continuation lines are not supported.
line = re.sub(self.IGNORED_CHUNK_REGEX,
lambda s: '""' if s.group('string') else ' ',
line)
# Start an unfinished comment?
# (If `/*` was part of a complete comment, it's already been removed.)
m = re.search(r"/\*", line)
if m:
in_block_comment = True
line = line[:m.start(0)]
return line, in_block_comment
IDENTIFIER_REGEX = re.compile('|'.join([
# Match " something(a" or " *something(a". Functions.
# Assumptions:
# - function definition from return type to one of its arguments is
# all on one line
# - function definition line only contains alphanumeric, asterisk,
# underscore, and open bracket
r".* \**(\w+) *\( *\w",
# Match "(*something)(".
r".*\( *\* *(\w+) *\) *\(",
# Match names of named data structures.
r"(?:typedef +)?(?:struct|union|enum) +(\w+)(?: *{)?$",
# Match names of typedef instances, after closing bracket.
r"}? *(\w+)[;[].*",
]))
# The regex below is indented for clarity.
EXCLUSION_LINES = re.compile("|".join([
r"extern +\"C\"",
r"(typedef +)?(struct|union|enum)( *{)?$",
r"} *;?$",
r"$",
r"//",
r"#",
]))
def parse_identifiers_in_file(self, header_file, identifiers):
"""
Parse all lines of a header where a function/enum/struct/union/typedef
identifier is declared, based on some regex and heuristics. Highly
dependent on formatting style.
Append found matches to the list ``identifiers``.
"""
with open(header_file, "r", encoding="utf-8") as header:
in_block_comment = False
# The previous line variable is used for concatenating lines
# when identifiers are formatted and spread across multiple
# lines.
previous_line = ""
for line_no, line in enumerate(header):
line, in_block_comment = \
self.strip_comments_and_literals(line, in_block_comment)
if self.EXCLUSION_LINES.match(line):
previous_line = ""
continue
# If the line contains only space-separated alphanumeric
# characters (or underscore, asterisk, or open parenthesis),
# and nothing else, high chance it's a declaration that
# continues on the next line
if re.search(r"^([\w\*\(]+\s+)+$", line):
previous_line += line
continue
# If previous line seemed to start an unfinished declaration
# (as above), concat and treat them as one.
if previous_line:
line = previous_line.strip() + " " + line.strip() + "\n"
previous_line = ""
# Skip parsing if line has a space in front = heuristic to
# skip function argument lines (highly subject to formatting
# changes)
if line[0] == " ":
continue
identifier = self.IDENTIFIER_REGEX.search(line)
if not identifier:
continue
# Find the group that matched, and append it
for group in identifier.groups():
if not group:
continue
identifiers.append(Match(
header_file,
line,
line_no,
identifier.span(),
group))
def parse_identifiers(self, include, exclude=None):
"""
Parse all lines of a header where a function/enum/struct/union/typedef
identifier is declared, based on some regex and heuristics. Highly
dependent on formatting style.
Args:
* include: A List of glob expressions to look for files through.
* exclude: A List of glob expressions for excluding files.
Returns a List of Match objects with identifiers.
"""
files = self.get_files(include, exclude)
self.log.debug("Looking for identifiers in {} files".format(len(files)))
identifiers = []
for header_file in files:
self.parse_identifiers_in_file(header_file, identifiers)
return identifiers
def parse_symbols(self):
"""
Compile the Mbed TLS libraries, and parse the TLS, Crypto, and x509
object files using nm to retrieve the list of referenced symbols.
Exceptions thrown here are rethrown because they would be critical
errors that void several tests, and thus needs to halt the program. This
is explicitly done for clarity.
Returns a List of unique symbols defined and used in the libraries.
"""
self.log.info("Compiling...")
symbols = []
# Back up the config and atomically compile with the full configratuion.
shutil.copy(
"include/mbedtls/mbedtls_config.h",
"include/mbedtls/mbedtls_config.h.bak"
)
try:
# Use check=True in all subprocess calls so that failures are raised
# as exceptions and logged.
subprocess.run(
["python3", "scripts/config.py", "full"],
universal_newlines=True,
check=True
)
my_environment = os.environ.copy()
my_environment["CFLAGS"] = "-fno-asynchronous-unwind-tables"
# Run make clean separately to lib to prevent unwanted behavior when
# make is invoked with parallelism.
subprocess.run(
["make", "clean"],
universal_newlines=True,
check=True
)
subprocess.run(
["make", "lib"],
env=my_environment,
universal_newlines=True,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
check=True
)
# Perform object file analysis using nm
symbols = self.parse_symbols_from_nm([
"library/libmbedcrypto.a",
"library/libmbedtls.a",
"library/libmbedx509.a"
])
subprocess.run(
["make", "clean"],
universal_newlines=True,
check=True
)
except subprocess.CalledProcessError as error:
self.log.debug(error.output)
raise error
finally:
# Put back the original config regardless of there being errors.
# Works also for keyboard interrupts.
shutil.move(
"include/mbedtls/mbedtls_config.h.bak",
"include/mbedtls/mbedtls_config.h"
)
return symbols
def parse_symbols_from_nm(self, object_files):
"""
Run nm to retrieve the list of referenced symbols in each object file.
Does not return the position data since it is of no use.
Args:
* object_files: a List of compiled object filepaths to search through.
Returns a List of unique symbols defined and used in any of the object
files.
"""
nm_undefined_regex = re.compile(r"^\S+: +U |^$|^\S+:$")
nm_valid_regex = re.compile(r"^\S+( [0-9A-Fa-f]+)* . _*(?P<symbol>\w+)")
exclusions = ("FStar", "Hacl")
symbols = []
# Gather all outputs of nm
nm_output = ""
for lib in object_files:
nm_output += subprocess.run(
["nm", "-og", lib],
universal_newlines=True,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
check=True
).stdout
for line in nm_output.splitlines():
if not nm_undefined_regex.search(line):
symbol = nm_valid_regex.search(line)
if (symbol and not symbol.group("symbol").startswith(exclusions)):
symbols.append(symbol.group("symbol"))
else:
self.log.error(line)
return symbols
class NameChecker():
"""
Representation of the core name checking operation performed by this script.
"""
def __init__(self, parse_result, log):
self.parse_result = parse_result
self.log = log
def perform_checks(self, quiet=False):
"""
A comprehensive checker that performs each check in order, and outputs
a final verdict.
Args:
* quiet: whether to hide detailed problem explanation.
"""
self.log.info("=============")
Problem.quiet = quiet
problems = 0
problems += self.check_symbols_declared_in_header()
pattern_checks = [
("macros", MACRO_PATTERN),
("enum_consts", CONSTANTS_PATTERN),
("identifiers", IDENTIFIER_PATTERN)
]
for group, check_pattern in pattern_checks:
problems += self.check_match_pattern(group, check_pattern)
problems += self.check_for_typos()
self.log.info("=============")
if problems > 0:
self.log.info("FAIL: {0} problem(s) to fix".format(str(problems)))
if quiet:
self.log.info("Remove --quiet to see explanations.")
else:
self.log.info("Use --quiet for minimal output.")
return 1
else:
self.log.info("PASS")
return 0
def check_symbols_declared_in_header(self):
"""
Perform a check that all detected symbols in the library object files
are properly declared in headers.
Assumes parse_names_in_source() was called before this.
Returns the number of problems that need fixing.
"""
problems = []
for symbol in self.parse_result["symbols"]:
found_symbol_declared = False
for identifier_match in self.parse_result["identifiers"]:
if symbol == identifier_match.name:
found_symbol_declared = True
break
if not found_symbol_declared:
problems.append(SymbolNotInHeader(symbol))
self.output_check_result("All symbols in header", problems)
return len(problems)
def check_match_pattern(self, group_to_check, check_pattern):
"""
Perform a check that all items of a group conform to a regex pattern.
Assumes parse_names_in_source() was called before this.
Args:
* group_to_check: string key to index into self.parse_result.
* check_pattern: the regex to check against.
Returns the number of problems that need fixing.
"""
problems = []
for item_match in self.parse_result[group_to_check]:
if not re.search(check_pattern, item_match.name):
problems.append(PatternMismatch(check_pattern, item_match))
# Double underscore should not be used for names
if re.search(r".*__.*", item_match.name):
problems.append(
PatternMismatch("no double underscore allowed", item_match))
self.output_check_result(
"Naming patterns of {}".format(group_to_check),
problems)
return len(problems)
def check_for_typos(self):
"""
Perform a check that all words in the source code beginning with MBED are
either defined as macros, or as enum constants.
Assumes parse_names_in_source() was called before this.
Returns the number of problems that need fixing.
"""
problems = []
# Set comprehension, equivalent to a list comprehension wrapped by set()
all_caps_names = {
match.name
for match
in self.parse_result["macros"] + self.parse_result["enum_consts"]}
typo_exclusion = re.compile(r"XXX|__|_$|^MBEDTLS_.*CONFIG_FILE$|"
r"MBEDTLS_TEST_LIBTESTDRIVER*")
for name_match in self.parse_result["mbed_words"]:
found = name_match.name in all_caps_names
# Since MBEDTLS_PSA_ACCEL_XXX defines are defined by the
# PSA driver, they will not exist as macros. However, they
# should still be checked for typos using the equivalent
# BUILTINs that exist.
if "MBEDTLS_PSA_ACCEL_" in name_match.name:
found = name_match.name.replace(
"MBEDTLS_PSA_ACCEL_",
"MBEDTLS_PSA_BUILTIN_") in all_caps_names
if not found and not typo_exclusion.search(name_match.name):
problems.append(Typo(name_match))
self.output_check_result("Likely typos", problems)
return len(problems)
def output_check_result(self, name, problems):
"""
Write out the PASS/FAIL status of a performed check depending on whether
there were problems.
Args:
* name: the name of the test
* problems: a List of encountered Problems
"""
if problems:
self.log.info("{}: FAIL\n".format(name))
for problem in problems:
self.log.warning(str(problem))
else:
self.log.info("{}: PASS".format(name))
def main():
"""
Perform argument parsing, and create an instance of CodeParser and
NameChecker to begin the core operation.
"""
parser = argparse.ArgumentParser(
formatter_class=argparse.RawDescriptionHelpFormatter,
description=(
"This script confirms that the naming of all symbols and identifiers "
"in Mbed TLS are consistent with the house style and are also "
"self-consistent.\n\n"
"Expected to be run from the MbedTLS root directory.")
)
parser.add_argument(
"-v", "--verbose",
action="store_true",
help="show parse results"
)
parser.add_argument(
"-q", "--quiet",
action="store_true",
help="hide unnecessary text, explanations, and highlighs"
)
args = parser.parse_args()
# Configure the global logger, which is then passed to the classes below
log = logging.getLogger()
log.setLevel(logging.DEBUG if args.verbose else logging.INFO)
log.addHandler(logging.StreamHandler())
try:
code_parser = CodeParser(log)
parse_result = code_parser.comprehensive_parse()
except Exception: # pylint: disable=broad-except
traceback.print_exc()
sys.exit(2)
name_checker = NameChecker(parse_result, log)
return_code = name_checker.perform_checks(quiet=args.quiet)
sys.exit(return_code)
if __name__ == "__main__":
main()

View File

@ -0,0 +1,222 @@
#!/usr/bin/env python3
"""Sanity checks for test data.
This program contains a class for traversing test cases that can be used
independently of the checks.
"""
# Copyright The Mbed TLS Contributors
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import argparse
import glob
import os
import re
import sys
class Results:
"""Store file and line information about errors or warnings in test suites."""
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').
format(file_name, line_number, *args))
self.errors += 1
def warning(self, file_name, line_number, fmt, *args):
if not self.ignore_warnings:
sys.stderr.write(('{}:{}:Warning:' + fmt + '\n')
.format(file_name, line_number, *args))
self.warnings += 1
class TestDescriptionExplorer:
"""An iterator over test cases with descriptions.
The test cases that have descriptions are:
* Individual unit tests (entries in a .data file) in test suites.
* Individual test cases in ssl-opt.sh.
This is an abstract class. To use it, derive a class that implements
the process_test_case method, and call walk_all().
"""
def process_test_case(self, per_file_state,
file_name, line_number, description):
"""Process a test case.
per_file_state: an object created by new_per_file_state() at the beginning
of each file.
file_name: a relative path to the file containing the test case.
line_number: the line number in the given file.
description: the test case description as a byte string.
"""
raise NotImplementedError
def new_per_file_state(self):
"""Return a new per-file state object.
The default per-file state object is None. Child classes that require per-file
state may override this method.
"""
#pylint: disable=no-self-use
return None
def walk_test_suite(self, data_file_name):
"""Iterate over the test cases in the given unit test data file."""
in_paragraph = False
descriptions = self.new_per_file_state() # pylint: disable=assignment-from-none
with open(data_file_name, 'rb') as data_file:
for line_number, line in enumerate(data_file, 1):
line = line.rstrip(b'\r\n')
if not line:
in_paragraph = False
continue
if line.startswith(b'#'):
continue
if not in_paragraph:
# This is a test case description line.
self.process_test_case(descriptions,
data_file_name, line_number, line)
in_paragraph = True
def walk_ssl_opt_sh(self, file_name):
"""Iterate over the test cases in ssl-opt.sh or a file with a similar format."""
descriptions = self.new_per_file_state() # pylint: disable=assignment-from-none
with open(file_name, 'rb') as file_contents:
for line_number, line in enumerate(file_contents, 1):
# Assume that all run_test calls have the same simple form
# with the test description entirely on the same line as the
# function name.
m = re.match(br'\s*run_test\s+"((?:[^\\"]|\\.)*)"', line)
if not m:
continue
description = m.group(1)
self.process_test_case(descriptions,
file_name, line_number, description)
@staticmethod
def collect_test_directories():
"""Get the relative path for the TLS and Crypto test directories."""
if os.path.isdir('tests'):
tests_dir = 'tests'
elif os.path.isdir('suites'):
tests_dir = '.'
elif os.path.isdir('../suites'):
tests_dir = '..'
directories = [tests_dir]
return directories
def walk_all(self):
"""Iterate over all named test cases."""
test_directories = self.collect_test_directories()
for directory in test_directories:
for data_file_name in glob.glob(os.path.join(directory, 'suites',
'*.data')):
self.walk_test_suite(data_file_name)
ssl_opt_sh = os.path.join(directory, 'ssl-opt.sh')
if os.path.exists(ssl_opt_sh):
self.walk_ssl_opt_sh(ssl_opt_sh)
for ssl_opt_file_name in glob.glob(os.path.join(directory, 'opt-testcases',
'*.sh')):
self.walk_ssl_opt_sh(ssl_opt_file_name)
class TestDescriptions(TestDescriptionExplorer):
"""Collect the available test cases."""
def __init__(self):
super().__init__()
self.descriptions = set()
def process_test_case(self, _per_file_state,
file_name, _line_number, description):
"""Record an available test case."""
base_name = re.sub(r'\.[^.]*$', '', re.sub(r'.*/', '', file_name))
key = ';'.join([base_name, description.decode('utf-8')])
self.descriptions.add(key)
def collect_available_test_cases():
"""Collect the available test cases."""
explorer = TestDescriptions()
explorer.walk_all()
return sorted(explorer.descriptions)
class DescriptionChecker(TestDescriptionExplorer):
"""Check all test case descriptions.
* Check that each description is valid (length, allowed character set, etc.).
* Check that there is no duplicated description inside of one test suite.
"""
def __init__(self, results):
self.results = results
def new_per_file_state(self):
"""Dictionary mapping descriptions to their line number."""
return {}
def process_test_case(self, per_file_state,
file_name, line_number, description):
"""Check test case descriptions for errors."""
results = self.results
seen = per_file_state
if description in seen:
results.error(file_name, line_number,
'Duplicate description (also line {})',
seen[description])
return
if re.search(br'[\t;]', description):
results.error(file_name, line_number,
'Forbidden character \'{}\' in description',
re.search(br'[\t;]', description).group(0).decode('ascii'))
if re.search(br'[^ -~]', description):
results.error(file_name, line_number,
'Non-ASCII character in description')
if len(description) > 66:
results.warning(file_name, line_number,
'Test description too long ({} > 66)',
len(description))
seen[description] = line_number
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--list-all',
action='store_true',
help='List all test cases, without doing checks')
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()
if options.list_all:
descriptions = collect_available_test_cases()
sys.stdout.write('\n'.join(descriptions + ['']))
return
results = Results(options)
checker = DescriptionChecker(results)
checker.walk_all()
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)
if __name__ == '__main__':
main()

View File

@ -0,0 +1,126 @@
#!/usr/bin/env perl
# curves.pl
#
# Copyright The Mbed TLS Contributors
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Purpose
#
# The purpose of this test script is to validate that the library works
# when only a single curve is enabled. In particular, this validates that
# curve-specific code is guarded by the proper preprocessor conditionals,
# both in the library and in tests.
#
# Since this script only tests builds with a single curve, it can't detect
# bugs that are only triggered when multiple curves are present. We do
# also test in many configurations where all curves are enabled, as well
# as a few configurations in configs/*.h with a restricted subset of curves.
#
# Here are some known test gaps that could be addressed by testing all
# 2^n combinations of support for n curves, which is impractical:
# * There could be product bugs when curves A and B are enabled but not C.
# For example, a MAX_SIZE calculation that forgets B, where
# size(A) < size(B) < size(C).
# * For test cases that require three or more curves, validate that they're
# not missing dependencies. This is extremely rare. (For test cases that
# require curves A and B but are missing a dependency on B, this is
# detected in the A-only build.)
# Usage: tests/scripts/curves.pl
#
# This script should be executed from the root of the project directory.
#
# Only curves that are enabled in mbedtls_config.h will be tested.
#
# For best effect, run either with cmake disabled, or cmake enabled in a mode
# that includes -Werror.
use warnings;
use strict;
-d 'library' && -d 'include' && -d 'tests' or die "Must be run from root\n";
my $sed_cmd = 's/^#define \(MBEDTLS_ECP_DP.*_ENABLED\)/\1/p';
my $config_h = 'include/mbedtls/mbedtls_config.h';
my @curves = split( /\s+/, `sed -n -e '$sed_cmd' $config_h` );
# Determine which curves support ECDSA by checking the dependencies of
# ECDSA in check_config.h.
my %curve_supports_ecdsa = ();
{
local $/ = "";
local *CHECK_CONFIG;
open(CHECK_CONFIG, '<', 'include/mbedtls/check_config.h')
or die "open include/mbedtls/check_config.h: $!";
while (my $stanza = <CHECK_CONFIG>) {
if ($stanza =~ /\A#if defined\(MBEDTLS_ECDSA_C\)/) {
for my $curve ($stanza =~ /(?<=\()MBEDTLS_ECP_DP_\w+_ENABLED(?=\))/g) {
$curve_supports_ecdsa{$curve} = 1;
}
last;
}
}
close(CHECK_CONFIG);
}
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";
# use an exit code between 1 and 124 for git bisect (die returns 255)
warn $_[0];
exit 1;
}
# Disable all the curves. We'll then re-enable them one by one.
for my $curve (@curves) {
system( "scripts/config.pl unset $curve" )
and abort "Failed to disable $curve\n";
}
# Depends on a specific curve. Also, ignore error if it wasn't enabled.
system( "scripts/config.pl unset MBEDTLS_KEY_EXCHANGE_ECJPAKE_ENABLED" );
system( "scripts/config.pl unset MBEDTLS_ECJPAKE_C" );
# Test with only $curve enabled, for each $curve.
for my $curve (@curves) {
system( "make clean" ) and die;
print "\n******************************************\n";
print "* Testing with only curve: $curve\n";
print "******************************************\n";
$ENV{MBEDTLS_TEST_CONFIGURATION} = "$curve";
system( "scripts/config.pl set $curve" )
and abort "Failed to enable $curve\n";
my $ecdsa = $curve_supports_ecdsa{$curve} ? "set" : "unset";
for my $dep (qw(MBEDTLS_ECDSA_C
MBEDTLS_KEY_EXCHANGE_ECDH_ECDSA_ENABLED
MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED)) {
system( "scripts/config.pl $ecdsa $dep" )
and abort "Failed to $ecdsa $dep\n";
}
system( "CFLAGS='-Werror -Wall -Wextra' make" )
and abort "Failed to build: only $curve\n";
system( "make test" )
and abort "Failed test suite: only $curve\n";
system( "scripts/config.pl unset $curve" )
and abort "Failed to disable $curve\n";
}
system( "mv $config_h.bak $config_h" ) and die "$config_h not restored\n";
system( "make clean" ) and die;
exit 0;

View File

@ -0,0 +1,101 @@
#!/usr/bin/env perl
# depends-hashes.pl
#
# Copyright The Mbed TLS Contributors
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Purpose
#
# To test the code dependencies on individual hashes in each test suite. This
# is a verification step to ensure we don't ship test suites that do not work
# for some build options.
#
# The process is:
# for each possible hash
# build the library and test suites with the hash disabled
# execute the test suites
#
# And any test suite with the wrong dependencies will fail.
#
# Usage: tests/scripts/depends-hashes.pl
#
# This script should be executed from the root of the project directory.
#
# For best effect, run either with cmake disabled, or cmake enabled in a mode
# that includes -Werror.
use warnings;
use strict;
-d 'library' && -d 'include' && -d 'tests' or die "Must be run from root\n";
my $config_h = 'include/mbedtls/mbedtls_config.h';
# as many SSL options depend on specific hashes,
# and SSL is not in the test suites anyways,
# disable it to avoid dependency issues
my $ssl_sed_cmd = 's/^#define \(MBEDTLS_SSL.*\)/\1/p';
my @ssl = split( /\s+/, `sed -n -e '$ssl_sed_cmd' $config_h` );
# Each element of this array holds list of configuration options that
# should be tested together. Certain options depend on each other and
# separating them would generate invalid configurations.
my @hash_configs = (
['unset MBEDTLS_MD5_C'],
['unset MBEDTLS_SHA512_C', 'unset MBEDTLS_SHA384_C '],
['unset MBEDTLS_SHA384_C'],
['unset MBEDTLS_SHA256_C', 'unset MBEDTLS_SHA224_C'],
['unset MBEDTLS_SHA1_C'],
);
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";
# use an exit code between 1 and 124 for git bisect (die returns 255)
warn $_[0];
exit 1;
}
for my $hash_config (@hash_configs) {
system( "cp $config_h.bak $config_h" ) and die "$config_h not restored\n";
system( "make clean" ) and die;
my $hash_config_string = join(', ', @$hash_config);
print "\n******************************************\n";
print "* Testing hash options: $hash_config_string\n";
print "******************************************\n";
$ENV{MBEDTLS_TEST_CONFIGURATION} = "-$hash_config_string";
for my $hash (@$hash_config) {
system( "scripts/config.py $hash" )
and abort "Failed to $hash\n";
}
for my $opt (@ssl) {
system( "scripts/config.py unset $opt" )
and abort "Failed to disable $opt\n";
}
system( "CFLAGS='-Werror -Wall -Wextra' make lib" )
and abort "Failed to build lib: $hash_config_string\n";
system( "cd tests && make" ) and abort "Failed to build tests: $hash_config_string\n";
system( "make test" ) and abort "Failed test suite: $hash_config_string\n";
}
system( "mv $config_h.bak $config_h" ) and die "$config_h not restored\n";
system( "make clean" ) and die;
exit 0;

View File

@ -0,0 +1,107 @@
#!/usr/bin/env perl
# depends-pkalgs.pl
#
# Copyright The Mbed TLS Contributors
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Purpose
#
# To test the code dependencies on individual PK algs (those that can be used
# from the PK layer, so currently signature and encryption but not key
# exchange) in each test suite. This is a verification step to ensure we don't
# ship test suites that do not work for some build options.
#
# The process is:
# for each possible PK alg
# build the library and test suites with that alg disabled
# execute the test suites
#
# And any test suite with the wrong dependencies will fail.
#
# Usage: tests/scripts/depends-pkalgs.pl
#
# This script should be executed from the root of the project directory.
#
# For best effect, run either with cmake disabled, or cmake enabled in a mode
# that includes -Werror.
use warnings;
use strict;
-d 'library' && -d 'include' && -d 'tests' or die "Must be run from root\n";
my $config_h = 'include/mbedtls/mbedtls_config.h';
# Some algorithms can't be disabled on their own as others depend on them, so
# we list those reverse-dependencies here to keep check_config.h happy.
my %algs = (
'MBEDTLS_ECDSA_C' => ['MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED',
'MBEDTLS_KEY_EXCHANGE_ECDH_ECDSA_ENABLED'],
'MBEDTLS_ECP_C' => ['MBEDTLS_ECDSA_C',
'MBEDTLS_ECDH_C',
'MBEDTLS_ECJPAKE_C',
'MBEDTLS_KEY_EXCHANGE_ECDH_ECDSA_ENABLED',
'MBEDTLS_KEY_EXCHANGE_ECDH_RSA_ENABLED',
'MBEDTLS_KEY_EXCHANGE_ECDHE_PSK_ENABLED',
'MBEDTLS_KEY_EXCHANGE_ECDHE_RSA_ENABLED',
'MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED'],
'MBEDTLS_X509_RSASSA_PSS_SUPPORT' => [],
'MBEDTLS_PKCS1_V21' => ['MBEDTLS_X509_RSASSA_PSS_SUPPORT'],
'MBEDTLS_PKCS1_V15' => ['MBEDTLS_KEY_EXCHANGE_DHE_RSA_ENABLED',
'MBEDTLS_KEY_EXCHANGE_ECDHE_RSA_ENABLED',
'MBEDTLS_KEY_EXCHANGE_RSA_PSK_ENABLED',
'MBEDTLS_KEY_EXCHANGE_RSA_ENABLED'],
'MBEDTLS_RSA_C' => ['MBEDTLS_X509_RSASSA_PSS_SUPPORT',
'MBEDTLS_KEY_EXCHANGE_DHE_RSA_ENABLED',
'MBEDTLS_KEY_EXCHANGE_ECDHE_RSA_ENABLED',
'MBEDTLS_KEY_EXCHANGE_ECDH_RSA_ENABLED',
'MBEDTLS_KEY_EXCHANGE_RSA_PSK_ENABLED',
'MBEDTLS_KEY_EXCHANGE_RSA_ENABLED'],
);
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";
# use an exit code between 1 and 124 for git bisect (die returns 255)
warn $_[0];
exit 1;
}
while( my ($alg, $extras) = each %algs ) {
system( "cp $config_h.bak $config_h" ) and die "$config_h not restored\n";
system( "make clean" ) and die;
print "\n******************************************\n";
print "* Testing without alg: $alg\n";
print "******************************************\n";
$ENV{MBEDTLS_TEST_CONFIGURATION} = "-$alg";
system( "scripts/config.py unset $alg" )
and abort "Failed to disable $alg\n";
for my $opt (@$extras) {
system( "scripts/config.py unset $opt" )
and abort "Failed to disable $opt\n";
}
system( "CFLAGS='-Werror -Wall -Wextra' make lib" )
and abort "Failed to build lib: $alg\n";
system( "cd tests && make" ) and abort "Failed to build tests: $alg\n";
system( "make test" ) and abort "Failed test suite: $alg\n";
}
system( "mv $config_h.bak $config_h" ) and die "$config_h not restored\n";
system( "make clean" ) and die;
exit 0;

View File

@ -0,0 +1,98 @@
#!/bin/bash -eu
# docker_env.sh
#
# Purpose
# -------
#
# This is a helper script to enable running tests under a Docker container,
# thus making it easier to get set up as well as isolating test dependencies
# (which include legacy/insecure configurations of openssl and gnutls).
#
# Notes for users
# ---------------
# This script expects a Linux x86_64 system with a recent version of Docker
# installed and available for use, as well as http/https access. If a proxy
# server must be used, invoke this script with the usual environment variables
# (http_proxy and https_proxy) set appropriately. If an alternate Docker
# registry is needed, specify MBEDTLS_DOCKER_REGISTRY to point at the
# host name.
#
#
# Running this script directly will check for Docker availability and set up
# the Docker image.
# Copyright The Mbed TLS Contributors
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# default values, can be overridden by the environment
: ${MBEDTLS_DOCKER_GUEST:=bionic}
DOCKER_IMAGE_TAG="armmbed/mbedtls-test:${MBEDTLS_DOCKER_GUEST}"
# Make sure docker is available
if ! which docker > /dev/null; then
echo "Docker is required but doesn't seem to be installed. See https://www.docker.com/ to get started"
exit 1
fi
# Figure out if we need to 'sudo docker'
if groups | grep docker > /dev/null; then
DOCKER="docker"
else
echo "Using sudo to invoke docker since you're not a member of the docker group..."
DOCKER="sudo docker"
fi
# Figure out the number of processors available
if [ "$(uname)" == "Darwin" ]; then
NUM_PROC="$(sysctl -n hw.logicalcpu)"
else
NUM_PROC="$(nproc)"
fi
# Build the Docker image
echo "Getting docker image up to date (this may take a few minutes)..."
${DOCKER} image build \
-t ${DOCKER_IMAGE_TAG} \
--cache-from=${DOCKER_IMAGE_TAG} \
--build-arg MAKEFLAGS_PARALLEL="-j ${NUM_PROC}" \
--network host \
${http_proxy+--build-arg http_proxy=${http_proxy}} \
${https_proxy+--build-arg https_proxy=${https_proxy}} \
${MBEDTLS_DOCKER_REGISTRY+--build-arg MY_REGISTRY="${MBEDTLS_DOCKER_REGISTRY}/"} \
tests/docker/${MBEDTLS_DOCKER_GUEST}
run_in_docker()
{
ENV_ARGS=""
while [ "$1" == "-e" ]; do
ENV_ARGS="${ENV_ARGS} $1 $2"
shift 2
done
${DOCKER} container run -it --rm \
--cap-add SYS_PTRACE \
--user "$(id -u):$(id -g)" \
--volume $PWD:$PWD \
--workdir $PWD \
-e MAKEFLAGS \
-e PYLINTHOME=/tmp/.pylintd \
${ENV_ARGS} \
${DOCKER_IMAGE_TAG} \
$@
}

View File

@ -0,0 +1,44 @@
#!/bin/sh
# Make sure the doxygen documentation builds without warnings
#
# Copyright The Mbed TLS Contributors
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Abort on errors (and uninitiliased variables)
set -eu
if [ -d library -a -d include -a -d tests ]; then :; else
echo "Must be run from mbed TLS root" >&2
exit 1
fi
if scripts/apidoc_full.sh > doc.out 2>doc.err; then :; else
cat doc.err
echo "FAIL" >&2
exit 1;
fi
cat doc.out doc.err | \
grep -v "warning: ignoring unsupported tag" \
> doc.filtered
if egrep "(warning|error):" doc.filtered; then
echo "FAIL" >&2
exit 1;
fi
make apidoc_clean
rm -f doc.out doc.err doc.filtered

View File

@ -0,0 +1,108 @@
#!/usr/bin/env perl
#
# Based on NIST CTR_DRBG.rsp validation file
# Only uses AES-256-CTR cases that use a Derivation function
# and concats nonce and personalization for initialization.
#
# Copyright The Mbed TLS Contributors
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
use strict;
my $file = shift;
open(TEST_DATA, "$file") or die "Opening test cases '$file': $!";
sub get_suite_val($)
{
my $name = shift;
my $val = "";
my $line = <TEST_DATA>;
($val) = ($line =~ /\[$name\s\=\s(\w+)\]/);
return $val;
}
sub get_val($)
{
my $name = shift;
my $val = "";
my $line;
while($line = <TEST_DATA>)
{
next if($line !~ /=/);
last;
}
($val) = ($line =~ /^$name = (\w+)/);
return $val;
}
my $cnt = 1;;
while (my $line = <TEST_DATA>)
{
next if ($line !~ /^\[AES-256 use df/);
my $PredictionResistanceStr = get_suite_val("PredictionResistance");
my $PredictionResistance = 0;
$PredictionResistance = 1 if ($PredictionResistanceStr eq 'True');
my $EntropyInputLen = get_suite_val("EntropyInputLen");
my $NonceLen = get_suite_val("NonceLen");
my $PersonalizationStringLen = get_suite_val("PersonalizationStringLen");
my $AdditionalInputLen = get_suite_val("AdditionalInputLen");
for ($cnt = 0; $cnt < 15; $cnt++)
{
my $Count = get_val("COUNT");
my $EntropyInput = get_val("EntropyInput");
my $Nonce = get_val("Nonce");
my $PersonalizationString = get_val("PersonalizationString");
my $AdditionalInput1 = get_val("AdditionalInput");
my $EntropyInputPR1 = get_val("EntropyInputPR") if ($PredictionResistance == 1);
my $EntropyInputReseed = get_val("EntropyInputReseed") if ($PredictionResistance == 0);
my $AdditionalInputReseed = get_val("AdditionalInputReseed") if ($PredictionResistance == 0);
my $AdditionalInput2 = get_val("AdditionalInput");
my $EntropyInputPR2 = get_val("EntropyInputPR") if ($PredictionResistance == 1);
my $ReturnedBits = get_val("ReturnedBits");
if ($PredictionResistance == 1)
{
print("CTR_DRBG NIST Validation (AES-256 use df,$PredictionResistanceStr,$EntropyInputLen,$NonceLen,$PersonalizationStringLen,$AdditionalInputLen) #$Count\n");
print("ctr_drbg_validate_pr");
print(":\"$Nonce$PersonalizationString\"");
print(":\"$EntropyInput$EntropyInputPR1$EntropyInputPR2\"");
print(":\"$AdditionalInput1\"");
print(":\"$AdditionalInput2\"");
print(":\"$ReturnedBits\"");
print("\n\n");
}
else
{
print("CTR_DRBG NIST Validation (AES-256 use df,$PredictionResistanceStr,$EntropyInputLen,$NonceLen,$PersonalizationStringLen,$AdditionalInputLen) #$Count\n");
print("ctr_drbg_validate_nopr");
print(":\"$Nonce$PersonalizationString\"");
print(":\"$EntropyInput$EntropyInputReseed\"");
print(":\"$AdditionalInput1\"");
print(":\"$AdditionalInputReseed\"");
print(":\"$AdditionalInput2\"");
print(":\"$ReturnedBits\"");
print("\n\n");
}
}
}
close(TEST_DATA);

View File

@ -0,0 +1,113 @@
#!/usr/bin/env perl
#
# Based on NIST gcmDecryptxxx.rsp validation files
# Only first 3 of every set used for compile time saving
#
# Copyright The Mbed TLS Contributors
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
use strict;
my $file = shift;
open(TEST_DATA, "$file") or die "Opening test cases '$file': $!";
sub get_suite_val($)
{
my $name = shift;
my $val = "";
while(my $line = <TEST_DATA>)
{
next if ($line !~ /^\[/);
($val) = ($line =~ /\[$name\s\=\s(\w+)\]/);
last;
}
return $val;
}
sub get_val($)
{
my $name = shift;
my $val = "";
my $line;
while($line = <TEST_DATA>)
{
next if($line !~ /=/);
last;
}
($val) = ($line =~ /^$name = (\w+)/);
return $val;
}
sub get_val_or_fail($)
{
my $name = shift;
my $val = "FAIL";
my $line;
while($line = <TEST_DATA>)
{
next if($line !~ /=/ && $line !~ /FAIL/);
last;
}
($val) = ($line =~ /^$name = (\w+)/) if ($line =~ /=/);
return $val;
}
my $cnt = 1;;
while (my $line = <TEST_DATA>)
{
my $key_len = get_suite_val("Keylen");
next if ($key_len !~ /\d+/);
my $iv_len = get_suite_val("IVlen");
my $pt_len = get_suite_val("PTlen");
my $add_len = get_suite_val("AADlen");
my $tag_len = get_suite_val("Taglen");
for ($cnt = 0; $cnt < 3; $cnt++)
{
my $Count = get_val("Count");
my $key = get_val("Key");
my $iv = get_val("IV");
my $ct = get_val("CT");
my $add = get_val("AAD");
my $tag = get_val("Tag");
my $pt = get_val_or_fail("PT");
print("GCM NIST Validation (AES-$key_len,$iv_len,$pt_len,$add_len,$tag_len) #$Count\n");
print("gcm_decrypt_and_verify");
print(":\"$key\"");
print(":\"$ct\"");
print(":\"$iv\"");
print(":\"$add\"");
print(":$tag_len");
print(":\"$tag\"");
print(":\"$pt\"");
print(":0");
print("\n\n");
}
}
print("GCM Selftest\n");
print("gcm_selftest:\n\n");
close(TEST_DATA);

View File

@ -0,0 +1,96 @@
#!/usr/bin/env perl
#
# Based on NIST gcmEncryptIntIVxxx.rsp validation files
# Only first 3 of every set used for compile time saving
#
# Copyright The Mbed TLS Contributors
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
use strict;
my $file = shift;
open(TEST_DATA, "$file") or die "Opening test cases '$file': $!";
sub get_suite_val($)
{
my $name = shift;
my $val = "";
while(my $line = <TEST_DATA>)
{
next if ($line !~ /^\[/);
($val) = ($line =~ /\[$name\s\=\s(\w+)\]/);
last;
}
return $val;
}
sub get_val($)
{
my $name = shift;
my $val = "";
my $line;
while($line = <TEST_DATA>)
{
next if($line !~ /=/);
last;
}
($val) = ($line =~ /^$name = (\w+)/);
return $val;
}
my $cnt = 1;;
while (my $line = <TEST_DATA>)
{
my $key_len = get_suite_val("Keylen");
next if ($key_len !~ /\d+/);
my $iv_len = get_suite_val("IVlen");
my $pt_len = get_suite_val("PTlen");
my $add_len = get_suite_val("AADlen");
my $tag_len = get_suite_val("Taglen");
for ($cnt = 0; $cnt < 3; $cnt++)
{
my $Count = get_val("Count");
my $key = get_val("Key");
my $pt = get_val("PT");
my $add = get_val("AAD");
my $iv = get_val("IV");
my $ct = get_val("CT");
my $tag = get_val("Tag");
print("GCM NIST Validation (AES-$key_len,$iv_len,$pt_len,$add_len,$tag_len) #$Count\n");
print("gcm_encrypt_and_tag");
print(":\"$key\"");
print(":\"$pt\"");
print(":\"$iv\"");
print(":\"$add\"");
print(":\"$ct\"");
print(":$tag_len");
print(":\"$tag\"");
print(":0");
print("\n\n");
}
}
print("GCM Selftest\n");
print("gcm_selftest:\n\n");
close(TEST_DATA);

View File

@ -0,0 +1,86 @@
#!/usr/bin/env perl
#
# Copyright The Mbed TLS Contributors
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
use strict;
my $file = shift;
open(TEST_DATA, "$file") or die "Opening test cases '$file': $!";
sub get_val($$)
{
my $str = shift;
my $name = shift;
my $val = "";
while(my $line = <TEST_DATA>)
{
next if($line !~ /^# $str/);
last;
}
while(my $line = <TEST_DATA>)
{
last if($line eq "\r\n");
$val .= $line;
}
$val =~ s/[ \r\n]//g;
return $val;
}
my $state = 0;
my $val_n = "";
my $val_e = "";
my $val_p = "";
my $val_q = "";
my $mod = 0;
my $cnt = 1;
while (my $line = <TEST_DATA>)
{
next if ($line !~ /^# Example/);
( $mod ) = ($line =~ /A (\d+)/);
$val_n = get_val("RSA modulus n", "N");
$val_e = get_val("RSA public exponent e", "E");
$val_p = get_val("Prime p", "P");
$val_q = get_val("Prime q", "Q");
for(my $i = 1; $i <= 6; $i++)
{
my $val_m = get_val("Message to be", "M");
my $val_salt = get_val("Salt", "Salt");
my $val_sig = get_val("Signature", "Sig");
print("RSASSA-PSS Signature Example ${cnt}_${i}\n");
print("pkcs1_rsassa_pss_sign:$mod:16:\"$val_p\":16:\"$val_q\":16:\"$val_n\":16:\"$val_e\":SIG_RSA_SHA1:MBEDTLS_MD_SHA1");
print(":\"$val_m\"");
print(":\"$val_salt\"");
print(":\"$val_sig\":0");
print("\n\n");
print("RSASSA-PSS Signature Example ${cnt}_${i} (verify)\n");
print("pkcs1_rsassa_pss_verify:$mod:16:\"$val_n\":16:\"$val_e\":SIG_RSA_SHA1:MBEDTLS_MD_SHA1");
print(":\"$val_m\"");
print(":\"$val_salt\"");
print(":\"$val_sig\":0");
print("\n\n");
}
$cnt++;
}
close(TEST_DATA);

View File

@ -0,0 +1,83 @@
#!/bin/sh
# This script splits the data test files containing the test cases into
# individual files (one test case per file) suitable for use with afl
# (American Fuzzy Lop). http://lcamtuf.coredump.cx/afl/
#
# Usage: generate-afl-tests.sh <test data file path>
# <test data file path> - should be the path to one of the test suite files
# such as 'test_suite_mpi.data'
#
# Copyright The Mbed TLS Contributors
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Abort on errors
set -e
if [ -z $1 ]
then
echo " [!] No test file specified" >&2
echo "Usage: $0 <test data file>" >&2
exit 1
fi
SRC_FILEPATH=$(dirname $1)/$(basename $1)
TESTSUITE=$(basename $1 .data)
THIS_DIR=$(basename $PWD)
if [ -d ../library -a -d ../include -a -d ../tests -a $THIS_DIR == "tests" ];
then :;
else
echo " [!] Must be run from mbed TLS tests directory" >&2
exit 1
fi
DEST_TESTCASE_DIR=$TESTSUITE-afl-tests
DEST_OUTPUT_DIR=$TESTSUITE-afl-out
echo " [+] Creating output directories" >&2
if [ -e $DEST_OUTPUT_DIR/* ];
then :
echo " [!] Test output files already exist." >&2
exit 1
else
mkdir -p $DEST_OUTPUT_DIR
fi
if [ -e $DEST_TESTCASE_DIR/* ];
then :
echo " [!] Test output files already exist." >&2
else
mkdir -p $DEST_TESTCASE_DIR
fi
echo " [+] Creating test cases" >&2
cd $DEST_TESTCASE_DIR
split -p '^\s*$' ../$SRC_FILEPATH
for f in *;
do
# Strip out any blank lines (no trim on OS X)
sed '/^\s*$/d' $f >testcase_$f
rm $f
done
cd ..
echo " [+] Test cases in $DEST_TESTCASE_DIR" >&2

View File

@ -0,0 +1,981 @@
#!/usr/bin/env python3
"""Generate test data for PSA cryptographic mechanisms.
With no arguments, generate all test data. With non-option arguments,
generate only the specified files.
"""
# Copyright The Mbed TLS Contributors
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import argparse
import enum
import os
import posixpath
import re
import sys
from typing import Callable, Dict, FrozenSet, Iterable, Iterator, List, Optional, TypeVar
import scripts_path # pylint: disable=unused-import
from mbedtls_dev import build_tree
from mbedtls_dev import crypto_knowledge
from mbedtls_dev import macro_collector
from mbedtls_dev import psa_storage
from mbedtls_dev import test_case
T = TypeVar('T') #pylint: disable=invalid-name
def psa_want_symbol(name: str) -> str:
"""Return the PSA_WANT_xxx symbol associated with a PSA crypto feature."""
if name.startswith('PSA_'):
return name[:4] + 'WANT_' + name[4:]
else:
raise ValueError('Unable to determine the PSA_WANT_ symbol for ' + name)
def finish_family_dependency(dep: str, bits: int) -> str:
"""Finish dep if it's a family dependency symbol prefix.
A family dependency symbol prefix is a PSA_WANT_ symbol that needs to be
qualified by the key size. If dep is such a symbol, finish it by adjusting
the prefix and appending the key size. Other symbols are left unchanged.
"""
return re.sub(r'_FAMILY_(.*)', r'_\1_' + str(bits), dep)
def finish_family_dependencies(dependencies: List[str], bits: int) -> List[str]:
"""Finish any family dependency symbol prefixes.
Apply `finish_family_dependency` to each element of `dependencies`.
"""
return [finish_family_dependency(dep, bits) for dep in dependencies]
SYMBOLS_WITHOUT_DEPENDENCY = frozenset([
'PSA_ALG_AEAD_WITH_AT_LEAST_THIS_LENGTH_TAG', # modifier, only in policies
'PSA_ALG_AEAD_WITH_SHORTENED_TAG', # modifier
'PSA_ALG_ANY_HASH', # only in policies
'PSA_ALG_AT_LEAST_THIS_LENGTH_MAC', # modifier, only in policies
'PSA_ALG_KEY_AGREEMENT', # chaining
'PSA_ALG_TRUNCATED_MAC', # modifier
])
def automatic_dependencies(*expressions: str) -> List[str]:
"""Infer dependencies of a test case by looking for PSA_xxx symbols.
The arguments are strings which should be C expressions. Do not use
string literals or comments as this function is not smart enough to
skip them.
"""
used = set()
for expr in expressions:
used.update(re.findall(r'PSA_(?:ALG|ECC_FAMILY|KEY_TYPE)_\w+', expr))
used.difference_update(SYMBOLS_WITHOUT_DEPENDENCY)
return sorted(psa_want_symbol(name) for name in used)
# A temporary hack: at the time of writing, not all dependency symbols
# are implemented yet. Skip test cases for which the dependency symbols are
# not available. Once all dependency symbols are available, this hack must
# be removed so that a bug in the dependency symbols proprely leads to a test
# failure.
def read_implemented_dependencies(filename: str) -> FrozenSet[str]:
return frozenset(symbol
for line in open(filename)
for symbol in re.findall(r'\bPSA_WANT_\w+\b', line))
_implemented_dependencies = None #type: Optional[FrozenSet[str]] #pylint: disable=invalid-name
def hack_dependencies_not_implemented(dependencies: List[str]) -> None:
global _implemented_dependencies #pylint: disable=global-statement,invalid-name
if _implemented_dependencies is None:
_implemented_dependencies = \
read_implemented_dependencies('include/psa/crypto_config.h')
if not all((dep.lstrip('!') in _implemented_dependencies or 'PSA_WANT' not in dep)
for dep in dependencies):
dependencies.append('DEPENDENCY_NOT_IMPLEMENTED_YET')
class Information:
"""Gather information about PSA constructors."""
def __init__(self) -> None:
self.constructors = self.read_psa_interface()
@staticmethod
def remove_unwanted_macros(
constructors: macro_collector.PSAMacroEnumerator
) -> None:
# Mbed TLS doesn't support finite-field DH yet and will not support
# finite-field DSA. Don't attempt to generate any related test case.
constructors.key_types.discard('PSA_KEY_TYPE_DH_KEY_PAIR')
constructors.key_types.discard('PSA_KEY_TYPE_DH_PUBLIC_KEY')
constructors.key_types.discard('PSA_KEY_TYPE_DSA_KEY_PAIR')
constructors.key_types.discard('PSA_KEY_TYPE_DSA_PUBLIC_KEY')
def read_psa_interface(self) -> macro_collector.PSAMacroEnumerator:
"""Return the list of known key types, algorithms, etc."""
constructors = macro_collector.InputsForTest()
header_file_names = ['include/psa/crypto_values.h',
'include/psa/crypto_extra.h']
test_suites = ['tests/suites/test_suite_psa_crypto_metadata.data']
for header_file_name in header_file_names:
constructors.parse_header(header_file_name)
for test_cases in test_suites:
constructors.parse_test_cases(test_cases)
self.remove_unwanted_macros(constructors)
constructors.gather_arguments()
return constructors
def test_case_for_key_type_not_supported(
verb: str, key_type: str, bits: int,
dependencies: List[str],
*args: str,
param_descr: str = ''
) -> test_case.TestCase:
"""Return one test case exercising a key creation method
for an unsupported key type or size.
"""
hack_dependencies_not_implemented(dependencies)
tc = test_case.TestCase()
short_key_type = crypto_knowledge.short_expression(key_type)
adverb = 'not' if dependencies else 'never'
if param_descr:
adverb = param_descr + ' ' + adverb
tc.set_description('PSA {} {} {}-bit {} supported'
.format(verb, short_key_type, bits, adverb))
tc.set_dependencies(dependencies)
tc.set_function(verb + '_not_supported')
tc.set_arguments([key_type] + list(args))
return tc
class NotSupported:
"""Generate test cases for when something is not supported."""
def __init__(self, info: Information) -> None:
self.constructors = info.constructors
ALWAYS_SUPPORTED = frozenset([
'PSA_KEY_TYPE_DERIVE',
'PSA_KEY_TYPE_RAW_DATA',
'PSA_KEY_TYPE_HMAC'
])
def test_cases_for_key_type_not_supported(
self,
kt: crypto_knowledge.KeyType,
param: Optional[int] = None,
param_descr: str = '',
) -> Iterator[test_case.TestCase]:
"""Return test cases exercising key creation when the given type is unsupported.
If param is present and not None, emit test cases conditioned on this
parameter not being supported. If it is absent or None, emit test cases
conditioned on the base type not being supported.
"""
if kt.name in self.ALWAYS_SUPPORTED:
# Don't generate test cases for key types that are always supported.
# They would be skipped in all configurations, which is noise.
return
import_dependencies = [('!' if param is None else '') +
psa_want_symbol(kt.name)]
if kt.params is not None:
import_dependencies += [('!' if param == i else '') +
psa_want_symbol(sym)
for i, sym in enumerate(kt.params)]
if kt.name.endswith('_PUBLIC_KEY'):
generate_dependencies = []
else:
generate_dependencies = import_dependencies
for bits in kt.sizes_to_test():
yield test_case_for_key_type_not_supported(
'import', kt.expression, bits,
finish_family_dependencies(import_dependencies, bits),
test_case.hex_string(kt.key_material(bits)),
param_descr=param_descr,
)
if not generate_dependencies and param is not None:
# If generation is impossible for this key type, rather than
# supported or not depending on implementation capabilities,
# only generate the test case once.
continue
# For public key we expect that key generation fails with
# INVALID_ARGUMENT. It is handled by KeyGenerate class.
if not kt.is_public():
yield test_case_for_key_type_not_supported(
'generate', kt.expression, bits,
finish_family_dependencies(generate_dependencies, bits),
str(bits),
param_descr=param_descr,
)
# To be added: derive
ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
def test_cases_for_not_supported(self) -> Iterator[test_case.TestCase]:
"""Generate test cases that exercise the creation of keys of unsupported types."""
for key_type in sorted(self.constructors.key_types):
if key_type in self.ECC_KEY_TYPES:
continue
kt = crypto_knowledge.KeyType(key_type)
yield from self.test_cases_for_key_type_not_supported(kt)
for curve_family in sorted(self.constructors.ecc_curves):
for constr in self.ECC_KEY_TYPES:
kt = crypto_knowledge.KeyType(constr, [curve_family])
yield from self.test_cases_for_key_type_not_supported(
kt, param_descr='type')
yield from self.test_cases_for_key_type_not_supported(
kt, 0, param_descr='curve')
def test_case_for_key_generation(
key_type: str, bits: int,
dependencies: List[str],
*args: str,
result: str = ''
) -> test_case.TestCase:
"""Return one test case exercising a key generation.
"""
hack_dependencies_not_implemented(dependencies)
tc = test_case.TestCase()
short_key_type = crypto_knowledge.short_expression(key_type)
tc.set_description('PSA {} {}-bit'
.format(short_key_type, bits))
tc.set_dependencies(dependencies)
tc.set_function('generate_key')
tc.set_arguments([key_type] + list(args) + [result])
return tc
class KeyGenerate:
"""Generate positive and negative (invalid argument) test cases for key generation."""
def __init__(self, info: Information) -> None:
self.constructors = info.constructors
ECC_KEY_TYPES = ('PSA_KEY_TYPE_ECC_KEY_PAIR',
'PSA_KEY_TYPE_ECC_PUBLIC_KEY')
@staticmethod
def test_cases_for_key_type_key_generation(
kt: crypto_knowledge.KeyType
) -> Iterator[test_case.TestCase]:
"""Return test cases exercising key generation.
All key types can be generated except for public keys. For public key
PSA_ERROR_INVALID_ARGUMENT status is expected.
"""
result = 'PSA_SUCCESS'
import_dependencies = [psa_want_symbol(kt.name)]
if kt.params is not None:
import_dependencies += [psa_want_symbol(sym)
for i, sym in enumerate(kt.params)]
if kt.name.endswith('_PUBLIC_KEY'):
# The library checks whether the key type is a public key generically,
# before it reaches a point where it needs support for the specific key
# type, so it returns INVALID_ARGUMENT for unsupported public key types.
generate_dependencies = []
result = 'PSA_ERROR_INVALID_ARGUMENT'
else:
generate_dependencies = import_dependencies
if kt.name == 'PSA_KEY_TYPE_RSA_KEY_PAIR':
generate_dependencies.append("MBEDTLS_GENPRIME")
for bits in kt.sizes_to_test():
yield test_case_for_key_generation(
kt.expression, bits,
finish_family_dependencies(generate_dependencies, bits),
str(bits),
result
)
def test_cases_for_key_generation(self) -> Iterator[test_case.TestCase]:
"""Generate test cases that exercise the generation of keys."""
for key_type in sorted(self.constructors.key_types):
if key_type in self.ECC_KEY_TYPES:
continue
kt = crypto_knowledge.KeyType(key_type)
yield from self.test_cases_for_key_type_key_generation(kt)
for curve_family in sorted(self.constructors.ecc_curves):
for constr in self.ECC_KEY_TYPES:
kt = crypto_knowledge.KeyType(constr, [curve_family])
yield from self.test_cases_for_key_type_key_generation(kt)
class OpFail:
"""Generate test cases for operations that must fail."""
#pylint: disable=too-few-public-methods
class Reason(enum.Enum):
NOT_SUPPORTED = 0
INVALID = 1
INCOMPATIBLE = 2
PUBLIC = 3
def __init__(self, info: Information) -> None:
self.constructors = info.constructors
key_type_expressions = self.constructors.generate_expressions(
sorted(self.constructors.key_types)
)
self.key_types = [crypto_knowledge.KeyType(kt_expr)
for kt_expr in key_type_expressions]
def make_test_case(
self,
alg: crypto_knowledge.Algorithm,
category: crypto_knowledge.AlgorithmCategory,
reason: 'Reason',
kt: Optional[crypto_knowledge.KeyType] = None,
not_deps: FrozenSet[str] = frozenset(),
) -> test_case.TestCase:
"""Construct a failure test case for a one-key or keyless operation."""
#pylint: disable=too-many-arguments,too-many-locals
tc = test_case.TestCase()
pretty_alg = alg.short_expression()
if reason == self.Reason.NOT_SUPPORTED:
short_deps = [re.sub(r'PSA_WANT_ALG_', r'', dep)
for dep in not_deps]
pretty_reason = '!' + '&'.join(sorted(short_deps))
else:
pretty_reason = reason.name.lower()
if kt:
key_type = kt.expression
pretty_type = kt.short_expression()
else:
key_type = ''
pretty_type = ''
tc.set_description('PSA {} {}: {}{}'
.format(category.name.lower(),
pretty_alg,
pretty_reason,
' with ' + pretty_type if pretty_type else ''))
dependencies = automatic_dependencies(alg.base_expression, key_type)
for i, dep in enumerate(dependencies):
if dep in not_deps:
dependencies[i] = '!' + dep
tc.set_dependencies(dependencies)
tc.set_function(category.name.lower() + '_fail')
arguments = []
if kt:
key_material = kt.key_material(kt.sizes_to_test()[0])
arguments += [key_type, test_case.hex_string(key_material)]
arguments.append(alg.expression)
if category.is_asymmetric():
arguments.append('1' if reason == self.Reason.PUBLIC else '0')
error = ('NOT_SUPPORTED' if reason == self.Reason.NOT_SUPPORTED else
'INVALID_ARGUMENT')
arguments.append('PSA_ERROR_' + error)
tc.set_arguments(arguments)
return tc
def no_key_test_cases(
self,
alg: crypto_knowledge.Algorithm,
category: crypto_knowledge.AlgorithmCategory,
) -> Iterator[test_case.TestCase]:
"""Generate failure test cases for keyless operations with the specified algorithm."""
if alg.can_do(category):
# Compatible operation, unsupported algorithm
for dep in automatic_dependencies(alg.base_expression):
yield self.make_test_case(alg, category,
self.Reason.NOT_SUPPORTED,
not_deps=frozenset([dep]))
else:
# Incompatible operation, supported algorithm
yield self.make_test_case(alg, category, self.Reason.INVALID)
def one_key_test_cases(
self,
alg: crypto_knowledge.Algorithm,
category: crypto_knowledge.AlgorithmCategory,
) -> Iterator[test_case.TestCase]:
"""Generate failure test cases for one-key operations with the specified algorithm."""
for kt in self.key_types:
key_is_compatible = kt.can_do(alg)
if key_is_compatible and alg.can_do(category):
# Compatible key and operation, unsupported algorithm
for dep in automatic_dependencies(alg.base_expression):
yield self.make_test_case(alg, category,
self.Reason.NOT_SUPPORTED,
kt=kt, not_deps=frozenset([dep]))
# Public key for a private-key operation
if category.is_asymmetric() and kt.is_public():
yield self.make_test_case(alg, category,
self.Reason.PUBLIC,
kt=kt)
elif key_is_compatible:
# Compatible key, incompatible operation, supported algorithm
yield self.make_test_case(alg, category,
self.Reason.INVALID,
kt=kt)
elif alg.can_do(category):
# Incompatible key, compatible operation, supported algorithm
yield self.make_test_case(alg, category,
self.Reason.INCOMPATIBLE,
kt=kt)
else:
# Incompatible key and operation. Don't test cases where
# multiple things are wrong, to keep the number of test
# cases reasonable.
pass
def test_cases_for_algorithm(
self,
alg: crypto_knowledge.Algorithm,
) -> Iterator[test_case.TestCase]:
"""Generate operation failure test cases for the specified algorithm."""
for category in crypto_knowledge.AlgorithmCategory:
if category == crypto_knowledge.AlgorithmCategory.PAKE:
# PAKE operations are not implemented yet
pass
elif category.requires_key():
yield from self.one_key_test_cases(alg, category)
else:
yield from self.no_key_test_cases(alg, category)
def all_test_cases(self) -> Iterator[test_case.TestCase]:
"""Generate all test cases for operations that must fail."""
algorithms = sorted(self.constructors.algorithms)
for expr in self.constructors.generate_expressions(algorithms):
alg = crypto_knowledge.Algorithm(expr)
yield from self.test_cases_for_algorithm(alg)
class StorageKey(psa_storage.Key):
"""Representation of a key for storage format testing."""
IMPLICIT_USAGE_FLAGS = {
'PSA_KEY_USAGE_SIGN_HASH': 'PSA_KEY_USAGE_SIGN_MESSAGE',
'PSA_KEY_USAGE_VERIFY_HASH': 'PSA_KEY_USAGE_VERIFY_MESSAGE'
} #type: Dict[str, str]
"""Mapping of usage flags to the flags that they imply."""
def __init__(
self,
usage: Iterable[str],
without_implicit_usage: Optional[bool] = False,
**kwargs
) -> None:
"""Prepare to generate a key.
* `usage` : The usage flags used for the key.
* `without_implicit_usage`: Flag to defide to apply the usage extension
"""
usage_flags = set(usage)
if not without_implicit_usage:
for flag in sorted(usage_flags):
if flag in self.IMPLICIT_USAGE_FLAGS:
usage_flags.add(self.IMPLICIT_USAGE_FLAGS[flag])
if usage_flags:
usage_expression = ' | '.join(sorted(usage_flags))
else:
usage_expression = '0'
super().__init__(usage=usage_expression, **kwargs)
class StorageTestData(StorageKey):
"""Representation of test case data for storage format testing."""
def __init__(
self,
description: str,
expected_usage: Optional[List[str]] = None,
**kwargs
) -> None:
"""Prepare to generate test data
* `description` : used for the the test case names
* `expected_usage`: the usage flags generated as the expected usage flags
in the test cases. CAn differ from the usage flags
stored in the keys because of the usage flags extension.
"""
super().__init__(**kwargs)
self.description = description #type: str
if expected_usage is None:
self.expected_usage = self.usage #type: psa_storage.Expr
elif expected_usage:
self.expected_usage = psa_storage.Expr(' | '.join(expected_usage))
else:
self.expected_usage = psa_storage.Expr(0)
class StorageFormat:
"""Storage format stability test cases."""
def __init__(self, info: Information, version: int, forward: bool) -> None:
"""Prepare to generate test cases for storage format stability.
* `info`: information about the API. See the `Information` class.
* `version`: the storage format version to generate test cases for.
* `forward`: if true, generate forward compatibility test cases which
save a key and check that its representation is as intended. Otherwise
generate backward compatibility test cases which inject a key
representation and check that it can be read and used.
"""
self.constructors = info.constructors #type: macro_collector.PSAMacroEnumerator
self.version = version #type: int
self.forward = forward #type: bool
RSA_OAEP_RE = re.compile(r'PSA_ALG_RSA_OAEP\((.*)\)\Z')
BRAINPOOL_RE = re.compile(r'PSA_KEY_TYPE_\w+\(PSA_ECC_FAMILY_BRAINPOOL_\w+\)\Z')
@classmethod
def exercise_key_with_algorithm(
cls,
key_type: psa_storage.Expr, bits: int,
alg: psa_storage.Expr
) -> bool:
"""Whether to the given key with the given algorithm.
Normally only the type and algorithm matter for compatibility, and
this is handled in crypto_knowledge.KeyType.can_do(). This function
exists to detect exceptional cases. Exceptional cases detected here
are not tested in OpFail and should therefore have manually written
test cases.
"""
# Some test keys have the RAW_DATA type and attributes that don't
# necessarily make sense. We do this to validate numerical
# encodings of the attributes.
# Raw data keys have no useful exercise anyway so there is no
# loss of test coverage.
if key_type.string == 'PSA_KEY_TYPE_RAW_DATA':
return False
# OAEP requires room for two hashes plus wrapping
m = cls.RSA_OAEP_RE.match(alg.string)
if m:
hash_alg = m.group(1)
hash_length = crypto_knowledge.Algorithm.hash_length(hash_alg)
key_length = (bits + 7) // 8
# Leave enough room for at least one byte of plaintext
return key_length > 2 * hash_length + 2
# There's nothing wrong with ECC keys on Brainpool curves,
# but operations with them are very slow. So we only exercise them
# with a single algorithm, not with all possible hashes. We do
# exercise other curves with all algorithms so test coverage is
# perfectly adequate like this.
m = cls.BRAINPOOL_RE.match(key_type.string)
if m and alg.string != 'PSA_ALG_ECDSA_ANY':
return False
return True
def make_test_case(self, key: StorageTestData) -> test_case.TestCase:
"""Construct a storage format test case for the given key.
If ``forward`` is true, generate a forward compatibility test case:
create a key and validate that it has the expected representation.
Otherwise generate a backward compatibility test case: inject the
key representation into storage and validate that it can be read
correctly.
"""
verb = 'save' if self.forward else 'read'
tc = test_case.TestCase()
tc.set_description(verb + ' ' + key.description)
dependencies = automatic_dependencies(
key.lifetime.string, key.type.string,
key.alg.string, key.alg2.string,
)
dependencies = finish_family_dependencies(dependencies, key.bits)
tc.set_dependencies(dependencies)
tc.set_function('key_storage_' + verb)
if self.forward:
extra_arguments = []
else:
flags = []
if self.exercise_key_with_algorithm(key.type, key.bits, key.alg):
flags.append('TEST_FLAG_EXERCISE')
if 'READ_ONLY' in key.lifetime.string:
flags.append('TEST_FLAG_READ_ONLY')
extra_arguments = [' | '.join(flags) if flags else '0']
tc.set_arguments([key.lifetime.string,
key.type.string, str(key.bits),
key.expected_usage.string,
key.alg.string, key.alg2.string,
'"' + key.material.hex() + '"',
'"' + key.hex() + '"',
*extra_arguments])
return tc
def key_for_lifetime(
self,
lifetime: str,
) -> StorageTestData:
"""Construct a test key for the given lifetime."""
short = lifetime
short = re.sub(r'PSA_KEY_LIFETIME_FROM_PERSISTENCE_AND_LOCATION',
r'', short)
short = crypto_knowledge.short_expression(short)
description = 'lifetime: ' + short
key = StorageTestData(version=self.version,
id=1, lifetime=lifetime,
type='PSA_KEY_TYPE_RAW_DATA', bits=8,
usage=['PSA_KEY_USAGE_EXPORT'], alg=0, alg2=0,
material=b'L',
description=description)
return key
def all_keys_for_lifetimes(self) -> Iterator[StorageTestData]:
"""Generate test keys covering lifetimes."""
lifetimes = sorted(self.constructors.lifetimes)
expressions = self.constructors.generate_expressions(lifetimes)
for lifetime in expressions:
# Don't attempt to create or load a volatile key in storage
if 'VOLATILE' in lifetime:
continue
# Don't attempt to create a read-only key in storage,
# but do attempt to load one.
if 'READ_ONLY' in lifetime and self.forward:
continue
yield self.key_for_lifetime(lifetime)
def key_for_usage_flags(
self,
usage_flags: List[str],
short: Optional[str] = None,
test_implicit_usage: Optional[bool] = True
) -> StorageTestData:
"""Construct a test key for the given key usage."""
extra_desc = ' without implication' if test_implicit_usage else ''
description = 'usage' + extra_desc + ': '
key1 = StorageTestData(version=self.version,
id=1, lifetime=0x00000001,
type='PSA_KEY_TYPE_RAW_DATA', bits=8,
expected_usage=usage_flags,
without_implicit_usage=not test_implicit_usage,
usage=usage_flags, alg=0, alg2=0,
material=b'K',
description=description)
if short is None:
usage_expr = key1.expected_usage.string
key1.description += crypto_knowledge.short_expression(usage_expr)
else:
key1.description += short
return key1
def generate_keys_for_usage_flags(self, **kwargs) -> Iterator[StorageTestData]:
"""Generate test keys covering usage flags."""
known_flags = sorted(self.constructors.key_usage_flags)
yield self.key_for_usage_flags(['0'], **kwargs)
for usage_flag in known_flags:
yield self.key_for_usage_flags([usage_flag], **kwargs)
for flag1, flag2 in zip(known_flags,
known_flags[1:] + [known_flags[0]]):
yield self.key_for_usage_flags([flag1, flag2], **kwargs)
def generate_key_for_all_usage_flags(self) -> Iterator[StorageTestData]:
known_flags = sorted(self.constructors.key_usage_flags)
yield self.key_for_usage_flags(known_flags, short='all known')
def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
yield from self.generate_keys_for_usage_flags()
yield from self.generate_key_for_all_usage_flags()
def key_for_type_and_alg(
self,
kt: crypto_knowledge.KeyType,
bits: int,
alg: Optional[crypto_knowledge.Algorithm] = None,
) -> StorageTestData:
"""Construct a test key of the given type.
If alg is not None, this key allows it.
"""
usage_flags = ['PSA_KEY_USAGE_EXPORT']
alg1 = 0 #type: psa_storage.Exprable
alg2 = 0
if alg is not None:
alg1 = alg.expression
usage_flags += alg.usage_flags(public=kt.is_public())
key_material = kt.key_material(bits)
description = 'type: {} {}-bit'.format(kt.short_expression(1), bits)
if alg is not None:
description += ', ' + alg.short_expression(1)
key = StorageTestData(version=self.version,
id=1, lifetime=0x00000001,
type=kt.expression, bits=bits,
usage=usage_flags, alg=alg1, alg2=alg2,
material=key_material,
description=description)
return key
def keys_for_type(
self,
key_type: str,
all_algorithms: List[crypto_knowledge.Algorithm],
) -> Iterator[StorageTestData]:
"""Generate test keys for the given key type."""
kt = crypto_knowledge.KeyType(key_type)
for bits in kt.sizes_to_test():
# Test a non-exercisable key, as well as exercisable keys for
# each compatible algorithm.
# To do: test reading a key from storage with an incompatible
# or unsupported algorithm.
yield self.key_for_type_and_alg(kt, bits)
compatible_algorithms = [alg for alg in all_algorithms
if kt.can_do(alg)]
for alg in compatible_algorithms:
yield self.key_for_type_and_alg(kt, bits, alg)
def all_keys_for_types(self) -> Iterator[StorageTestData]:
"""Generate test keys covering key types and their representations."""
key_types = sorted(self.constructors.key_types)
all_algorithms = [crypto_knowledge.Algorithm(alg)
for alg in self.constructors.generate_expressions(
sorted(self.constructors.algorithms)
)]
for key_type in self.constructors.generate_expressions(key_types):
yield from self.keys_for_type(key_type, all_algorithms)
def keys_for_algorithm(self, alg: str) -> Iterator[StorageTestData]:
"""Generate test keys for the encoding of the specified algorithm."""
# These test cases only validate the encoding of algorithms, not
# whether the key read from storage is suitable for an operation.
# `keys_for_types` generate read tests with an algorithm and a
# compatible key.
descr = crypto_knowledge.short_expression(alg, 1)
usage = ['PSA_KEY_USAGE_EXPORT']
key1 = StorageTestData(version=self.version,
id=1, lifetime=0x00000001,
type='PSA_KEY_TYPE_RAW_DATA', bits=8,
usage=usage, alg=alg, alg2=0,
material=b'K',
description='alg: ' + descr)
yield key1
key2 = StorageTestData(version=self.version,
id=1, lifetime=0x00000001,
type='PSA_KEY_TYPE_RAW_DATA', bits=8,
usage=usage, alg=0, alg2=alg,
material=b'L',
description='alg2: ' + descr)
yield key2
def all_keys_for_algorithms(self) -> Iterator[StorageTestData]:
"""Generate test keys covering algorithm encodings."""
algorithms = sorted(self.constructors.algorithms)
for alg in self.constructors.generate_expressions(algorithms):
yield from self.keys_for_algorithm(alg)
def generate_all_keys(self) -> Iterator[StorageTestData]:
"""Generate all keys for the test cases."""
yield from self.all_keys_for_lifetimes()
yield from self.all_keys_for_usage_flags()
yield from self.all_keys_for_types()
yield from self.all_keys_for_algorithms()
def all_test_cases(self) -> Iterator[test_case.TestCase]:
"""Generate all storage format test cases."""
# First build a list of all keys, then construct all the corresponding
# test cases. This allows all required information to be obtained in
# one go, which is a significant performance gain as the information
# includes numerical values obtained by compiling a C program.
all_keys = list(self.generate_all_keys())
for key in all_keys:
if key.location_value() != 0:
# Skip keys with a non-default location, because they
# require a driver and we currently have no mechanism to
# determine whether a driver is available.
continue
yield self.make_test_case(key)
class StorageFormatForward(StorageFormat):
"""Storage format stability test cases for forward compatibility."""
def __init__(self, info: Information, version: int) -> None:
super().__init__(info, version, True)
class StorageFormatV0(StorageFormat):
"""Storage format stability test cases for version 0 compatibility."""
def __init__(self, info: Information) -> None:
super().__init__(info, 0, False)
def all_keys_for_usage_flags(self) -> Iterator[StorageTestData]:
"""Generate test keys covering usage flags."""
yield from super().all_keys_for_usage_flags()
yield from self.generate_keys_for_usage_flags(test_implicit_usage=False)
def keys_for_implicit_usage(
self,
implyer_usage: str,
alg: str,
key_type: crypto_knowledge.KeyType
) -> StorageTestData:
# pylint: disable=too-many-locals
"""Generate test keys for the specified implicit usage flag,
algorithm and key type combination.
"""
bits = key_type.sizes_to_test()[0]
implicit_usage = StorageKey.IMPLICIT_USAGE_FLAGS[implyer_usage]
usage_flags = ['PSA_KEY_USAGE_EXPORT']
material_usage_flags = usage_flags + [implyer_usage]
expected_usage_flags = material_usage_flags + [implicit_usage]
alg2 = 0
key_material = key_type.key_material(bits)
usage_expression = crypto_knowledge.short_expression(implyer_usage, 1)
alg_expression = crypto_knowledge.short_expression(alg, 1)
key_type_expression = key_type.short_expression(1)
description = 'implied by {}: {} {} {}-bit'.format(
usage_expression, alg_expression, key_type_expression, bits)
key = StorageTestData(version=self.version,
id=1, lifetime=0x00000001,
type=key_type.expression, bits=bits,
usage=material_usage_flags,
expected_usage=expected_usage_flags,
without_implicit_usage=True,
alg=alg, alg2=alg2,
material=key_material,
description=description)
return key
def gather_key_types_for_sign_alg(self) -> Dict[str, List[str]]:
# pylint: disable=too-many-locals
"""Match possible key types for sign algorithms."""
# To create a valid combination both the algorithms and key types
# must be filtered. Pair them with keywords created from its names.
incompatible_alg_keyword = frozenset(['RAW', 'ANY', 'PURE'])
incompatible_key_type_keywords = frozenset(['MONTGOMERY'])
keyword_translation = {
'ECDSA': 'ECC',
'ED[0-9]*.*' : 'EDWARDS'
}
exclusive_keywords = {
'EDWARDS': 'ECC'
}
key_types = set(self.constructors.generate_expressions(self.constructors.key_types))
algorithms = set(self.constructors.generate_expressions(self.constructors.sign_algorithms))
alg_with_keys = {} #type: Dict[str, List[str]]
translation_table = str.maketrans('(', '_', ')')
for alg in algorithms:
# Generate keywords from the name of the algorithm
alg_keywords = set(alg.partition('(')[0].split(sep='_')[2:])
# Translate keywords for better matching with the key types
for keyword in alg_keywords.copy():
for pattern, replace in keyword_translation.items():
if re.match(pattern, keyword):
alg_keywords.remove(keyword)
alg_keywords.add(replace)
# Filter out incompatible algorithms
if not alg_keywords.isdisjoint(incompatible_alg_keyword):
continue
for key_type in key_types:
# Generate keywords from the of the key type
key_type_keywords = set(key_type.translate(translation_table).split(sep='_')[3:])
# Remove ambiguous keywords
for keyword1, keyword2 in exclusive_keywords.items():
if keyword1 in key_type_keywords:
key_type_keywords.remove(keyword2)
if key_type_keywords.isdisjoint(incompatible_key_type_keywords) and\
not key_type_keywords.isdisjoint(alg_keywords):
if alg in alg_with_keys:
alg_with_keys[alg].append(key_type)
else:
alg_with_keys[alg] = [key_type]
return alg_with_keys
def all_keys_for_implicit_usage(self) -> Iterator[StorageTestData]:
"""Generate test keys for usage flag extensions."""
# Generate a key type and algorithm pair for each extendable usage
# flag to generate a valid key for exercising. The key is generated
# without usage extension to check the extension compatibility.
alg_with_keys = self.gather_key_types_for_sign_alg()
for usage in sorted(StorageKey.IMPLICIT_USAGE_FLAGS, key=str):
for alg in sorted(alg_with_keys):
for key_type in sorted(alg_with_keys[alg]):
# The key types must be filtered to fit the specific usage flag.
kt = crypto_knowledge.KeyType(key_type)
if kt.is_public() and '_SIGN_' in usage:
# Can't sign with a public key
continue
yield self.keys_for_implicit_usage(usage, alg, kt)
def generate_all_keys(self) -> Iterator[StorageTestData]:
yield from super().generate_all_keys()
yield from self.all_keys_for_implicit_usage()
class TestGenerator:
"""Generate test data."""
def __init__(self, options) -> None:
self.test_suite_directory = self.get_option(options, 'directory',
'tests/suites')
self.info = Information()
@staticmethod
def get_option(options, name: str, default: T) -> T:
value = getattr(options, name, None)
return default if value is None else value
def filename_for(self, basename: str) -> str:
"""The location of the data file with the specified base name."""
return posixpath.join(self.test_suite_directory, basename + '.data')
def write_test_data_file(self, basename: str,
test_cases: Iterable[test_case.TestCase]) -> None:
"""Write the test cases to a .data file.
The output file is ``basename + '.data'`` in the test suite directory.
"""
filename = self.filename_for(basename)
test_case.write_data_file(filename, test_cases)
# Note that targets whose names contain 'test_format' have their content
# validated by `abi_check.py`.
TARGETS = {
'test_suite_psa_crypto_generate_key.generated':
lambda info: KeyGenerate(info).test_cases_for_key_generation(),
'test_suite_psa_crypto_not_supported.generated':
lambda info: NotSupported(info).test_cases_for_not_supported(),
'test_suite_psa_crypto_op_fail.generated':
lambda info: OpFail(info).all_test_cases(),
'test_suite_psa_crypto_storage_format.current':
lambda info: StorageFormatForward(info, 0).all_test_cases(),
'test_suite_psa_crypto_storage_format.v0':
lambda info: StorageFormatV0(info).all_test_cases(),
} #type: Dict[str, Callable[[Information], Iterable[test_case.TestCase]]]
def generate_target(self, name: str) -> None:
test_cases = self.TARGETS[name](self.info)
self.write_test_data_file(name, test_cases)
def main(args):
"""Command line entry point."""
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--list', action='store_true',
help='List available targets and exit')
parser.add_argument('--list-for-cmake', action='store_true',
help='Print \';\'-separated list of available targets and exit')
parser.add_argument('--directory', metavar='DIR',
help='Output directory (default: tests/suites)')
parser.add_argument('targets', nargs='*', metavar='TARGET',
help='Target file to generate (default: all; "-": none)')
options = parser.parse_args(args)
build_tree.chdir_to_root()
generator = TestGenerator(options)
if options.list:
for name in sorted(generator.TARGETS):
print(generator.filename_for(name))
return
# List in a cmake list format (i.e. ';'-separated)
if options.list_for_cmake:
print(';'.join(generator.filename_for(name)
for name in sorted(generator.TARGETS)), end='')
return
if options.targets:
# Allow "-" as a special case so you can run
# ``generate_psa_tests.py - $targets`` and it works uniformly whether
# ``$targets`` is empty or not.
options.targets = [os.path.basename(re.sub(r'\.data\Z', r'', target))
for target in options.targets
if target != '-']
else:
options.targets = sorted(generator.TARGETS)
for target in options.targets:
generator.generate_target(target)
if __name__ == '__main__':
main(sys.argv[1:])

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,647 @@
#!/usr/bin/env python3
# generate_tls13_compat_tests.py
#
# Copyright The Mbed TLS Contributors
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Generate TLSv1.3 Compat test cases
"""
import sys
import os
import argparse
import itertools
from collections import namedtuple
# define certificates configuration entry
Certificate = namedtuple("Certificate", ['cafile', 'certfile', 'keyfile'])
# define the certificate parameters for signature algorithms
CERTIFICATES = {
'ecdsa_secp256r1_sha256': Certificate('data_files/test-ca2.crt',
'data_files/ecdsa_secp256r1.crt',
'data_files/ecdsa_secp256r1.key'),
'ecdsa_secp384r1_sha384': Certificate('data_files/test-ca2.crt',
'data_files/ecdsa_secp384r1.crt',
'data_files/ecdsa_secp384r1.key'),
'ecdsa_secp521r1_sha512': Certificate('data_files/test-ca2.crt',
'data_files/ecdsa_secp521r1.crt',
'data_files/ecdsa_secp521r1.key'),
'rsa_pss_rsae_sha256': Certificate('data_files/test-ca_cat12.crt',
'data_files/server2-sha256.crt', 'data_files/server2.key'
)
}
CIPHER_SUITE_IANA_VALUE = {
"TLS_AES_128_GCM_SHA256": 0x1301,
"TLS_AES_256_GCM_SHA384": 0x1302,
"TLS_CHACHA20_POLY1305_SHA256": 0x1303,
"TLS_AES_128_CCM_SHA256": 0x1304,
"TLS_AES_128_CCM_8_SHA256": 0x1305
}
SIG_ALG_IANA_VALUE = {
"ecdsa_secp256r1_sha256": 0x0403,
"ecdsa_secp384r1_sha384": 0x0503,
"ecdsa_secp521r1_sha512": 0x0603,
'rsa_pss_rsae_sha256': 0x0804,
}
NAMED_GROUP_IANA_VALUE = {
'secp256r1': 0x17,
'secp384r1': 0x18,
'secp521r1': 0x19,
'x25519': 0x1d,
'x448': 0x1e,
}
class TLSProgram:
"""
Base class for generate server/client command.
"""
# pylint: disable=too-many-arguments
def __init__(self, ciphersuite=None, signature_algorithm=None, named_group=None,
cert_sig_alg=None, compat_mode=True):
self._ciphers = []
self._sig_algs = []
self._named_groups = []
self._cert_sig_algs = []
if ciphersuite:
self.add_ciphersuites(ciphersuite)
if named_group:
self.add_named_groups(named_group)
if signature_algorithm:
self.add_signature_algorithms(signature_algorithm)
if cert_sig_alg:
self.add_cert_signature_algorithms(cert_sig_alg)
self._compat_mode = compat_mode
# add_ciphersuites should not override by sub class
def add_ciphersuites(self, *ciphersuites):
self._ciphers.extend(
[cipher for cipher in ciphersuites if cipher not in self._ciphers])
# add_signature_algorithms should not override by sub class
def add_signature_algorithms(self, *signature_algorithms):
self._sig_algs.extend(
[sig_alg for sig_alg in signature_algorithms if sig_alg not in self._sig_algs])
# add_named_groups should not override by sub class
def add_named_groups(self, *named_groups):
self._named_groups.extend(
[named_group for named_group in named_groups if named_group not in self._named_groups])
# add_cert_signature_algorithms should not override by sub class
def add_cert_signature_algorithms(self, *signature_algorithms):
self._cert_sig_algs.extend(
[sig_alg for sig_alg in signature_algorithms if sig_alg not in self._cert_sig_algs])
# pylint: disable=no-self-use
def pre_checks(self):
return []
# pylint: disable=no-self-use
def cmd(self):
if not self._cert_sig_algs:
self._cert_sig_algs = list(CERTIFICATES.keys())
return self.pre_cmd()
# pylint: disable=no-self-use
def post_checks(self):
return []
# pylint: disable=no-self-use
def pre_cmd(self):
return ['false']
# pylint: disable=unused-argument,no-self-use
def hrr_post_checks(self, named_group):
return []
class OpenSSLBase(TLSProgram):
"""
Generate base test commands for OpenSSL.
"""
NAMED_GROUP = {
'secp256r1': 'P-256',
'secp384r1': 'P-384',
'secp521r1': 'P-521',
'x25519': 'X25519',
'x448': 'X448',
}
def cmd(self):
ret = super().cmd()
if self._ciphers:
ciphersuites = ':'.join(self._ciphers)
ret += ["-ciphersuites {ciphersuites}".format(ciphersuites=ciphersuites)]
if self._sig_algs:
signature_algorithms = set(self._sig_algs + self._cert_sig_algs)
signature_algorithms = ':'.join(signature_algorithms)
ret += ["-sigalgs {signature_algorithms}".format(
signature_algorithms=signature_algorithms)]
if self._named_groups:
named_groups = ':'.join(
map(lambda named_group: self.NAMED_GROUP[named_group], self._named_groups))
ret += ["-groups {named_groups}".format(named_groups=named_groups)]
ret += ['-msg -tls1_3']
if not self._compat_mode:
ret += ['-no_middlebox']
return ret
def pre_checks(self):
return ["requires_openssl_tls1_3"]
class OpenSSLServ(OpenSSLBase):
"""
Generate test commands for OpenSSL server.
"""
def cmd(self):
ret = super().cmd()
ret += ['-num_tickets 0 -no_resume_ephemeral -no_cache']
return ret
def post_checks(self):
return ['-c "HTTP/1.0 200 ok"']
def pre_cmd(self):
ret = ['$O_NEXT_SRV_NO_CERT']
for _, cert, key in map(lambda sig_alg: CERTIFICATES[sig_alg], self._cert_sig_algs):
ret += ['-cert {cert} -key {key}'.format(cert=cert, key=key)]
return ret
class OpenSSLCli(OpenSSLBase):
"""
Generate test commands for OpenSSL client.
"""
def pre_cmd(self):
return ['$O_NEXT_CLI_NO_CERT',
'-CAfile {cafile}'.format(cafile=CERTIFICATES[self._cert_sig_algs[0]].cafile)]
class GnuTLSBase(TLSProgram):
"""
Generate base test commands for GnuTLS.
"""
CIPHER_SUITE = {
'TLS_AES_256_GCM_SHA384': [
'AES-256-GCM',
'SHA384',
'AEAD'],
'TLS_AES_128_GCM_SHA256': [
'AES-128-GCM',
'SHA256',
'AEAD'],
'TLS_CHACHA20_POLY1305_SHA256': [
'CHACHA20-POLY1305',
'SHA256',
'AEAD'],
'TLS_AES_128_CCM_SHA256': [
'AES-128-CCM',
'SHA256',
'AEAD'],
'TLS_AES_128_CCM_8_SHA256': [
'AES-128-CCM-8',
'SHA256',
'AEAD']}
SIGNATURE_ALGORITHM = {
'ecdsa_secp256r1_sha256': ['SIGN-ECDSA-SECP256R1-SHA256'],
'ecdsa_secp521r1_sha512': ['SIGN-ECDSA-SECP521R1-SHA512'],
'ecdsa_secp384r1_sha384': ['SIGN-ECDSA-SECP384R1-SHA384'],
'rsa_pss_rsae_sha256': ['SIGN-RSA-PSS-RSAE-SHA256']}
NAMED_GROUP = {
'secp256r1': ['GROUP-SECP256R1'],
'secp384r1': ['GROUP-SECP384R1'],
'secp521r1': ['GROUP-SECP521R1'],
'x25519': ['GROUP-X25519'],
'x448': ['GROUP-X448'],
}
def pre_checks(self):
return ["requires_gnutls_tls1_3",
"requires_gnutls_next_no_ticket",
"requires_gnutls_next_disable_tls13_compat", ]
def cmd(self):
ret = super().cmd()
priority_string_list = []
def update_priority_string_list(items, map_table):
for item in items:
for i in map_table[item]:
if i not in priority_string_list:
yield i
if self._ciphers:
priority_string_list.extend(update_priority_string_list(
self._ciphers, self.CIPHER_SUITE))
else:
priority_string_list.extend(['CIPHER-ALL', 'MAC-ALL'])
if self._sig_algs:
signature_algorithms = set(self._sig_algs + self._cert_sig_algs)
priority_string_list.extend(update_priority_string_list(
signature_algorithms, self.SIGNATURE_ALGORITHM))
else:
priority_string_list.append('SIGN-ALL')
if self._named_groups:
priority_string_list.extend(update_priority_string_list(
self._named_groups, self.NAMED_GROUP))
else:
priority_string_list.append('GROUP-ALL')
priority_string_list = ['NONE'] + \
priority_string_list + ['VERS-TLS1.3']
priority_string = ':+'.join(priority_string_list)
priority_string += ':%NO_TICKETS'
if not self._compat_mode:
priority_string += [':%DISABLE_TLS13_COMPAT_MODE']
ret += ['--priority={priority_string}'.format(
priority_string=priority_string)]
return ret
class GnuTLSServ(GnuTLSBase):
"""
Generate test commands for GnuTLS server.
"""
def pre_cmd(self):
ret = ['$G_NEXT_SRV_NO_CERT', '--http', '--disable-client-cert', '--debug=4']
for _, cert, key in map(lambda sig_alg: CERTIFICATES[sig_alg], self._cert_sig_algs):
ret += ['--x509certfile {cert} --x509keyfile {key}'.format(
cert=cert, key=key)]
return ret
def post_checks(self):
return ['-c "HTTP/1.0 200 OK"']
class GnuTLSCli(GnuTLSBase):
"""
Generate test commands for GnuTLS client.
"""
def pre_cmd(self):
return ['$G_NEXT_CLI_NO_CERT', '--debug=4', '--single-key-share',
'--x509cafile {cafile}'.format(cafile=CERTIFICATES[self._cert_sig_algs[0]].cafile)]
class MbedTLSBase(TLSProgram):
"""
Generate base test commands for mbedTLS.
"""
CIPHER_SUITE = {
'TLS_AES_256_GCM_SHA384': 'TLS1-3-AES-256-GCM-SHA384',
'TLS_AES_128_GCM_SHA256': 'TLS1-3-AES-128-GCM-SHA256',
'TLS_CHACHA20_POLY1305_SHA256': 'TLS1-3-CHACHA20-POLY1305-SHA256',
'TLS_AES_128_CCM_SHA256': 'TLS1-3-AES-128-CCM-SHA256',
'TLS_AES_128_CCM_8_SHA256': 'TLS1-3-AES-128-CCM-8-SHA256'}
def cmd(self):
ret = super().cmd()
ret += ['debug_level=4']
if self._ciphers:
ciphers = ','.join(
map(lambda cipher: self.CIPHER_SUITE[cipher], self._ciphers))
ret += ["force_ciphersuite={ciphers}".format(ciphers=ciphers)]
if self._sig_algs + self._cert_sig_algs:
ret += ['sig_algs={sig_algs}'.format(
sig_algs=','.join(set(self._sig_algs + self._cert_sig_algs)))]
if self._named_groups:
named_groups = ','.join(self._named_groups)
ret += ["curves={named_groups}".format(named_groups=named_groups)]
ret += ['force_version=tls13']
return ret
def pre_checks(self):
ret = ['requires_config_enabled MBEDTLS_DEBUG_C',
'requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_3']
if self._compat_mode:
ret += ['requires_config_enabled MBEDTLS_SSL_TLS1_3_COMPATIBILITY_MODE']
if 'rsa_pss_rsae_sha256' in self._sig_algs + self._cert_sig_algs:
ret.append(
'requires_config_enabled MBEDTLS_X509_RSASSA_PSS_SUPPORT')
return ret
class MbedTLSServ(MbedTLSBase):
"""
Generate test commands for mbedTLS server.
"""
def cmd(self):
ret = super().cmd()
ret += ['tls13_kex_modes=ephemeral cookies=0 tickets=0']
return ret
def pre_checks(self):
return ['requires_config_enabled MBEDTLS_SSL_SRV_C'] + super().pre_checks()
def post_checks(self):
check_strings = ["Protocol is TLSv1.3"]
if self._ciphers:
check_strings.append(
"server hello, chosen ciphersuite: {} ( id={:04d} )".format(
self.CIPHER_SUITE[self._ciphers[0]],
CIPHER_SUITE_IANA_VALUE[self._ciphers[0]]))
if self._sig_algs:
check_strings.append(
"received signature algorithm: 0x{:x}".format(
SIG_ALG_IANA_VALUE[self._sig_algs[0]]))
for named_group in self._named_groups:
check_strings += ['got named group: {named_group}({iana_value:04x})'.format(
named_group=named_group,
iana_value=NAMED_GROUP_IANA_VALUE[named_group])]
check_strings.append("Verifying peer X.509 certificate... ok")
return ['-s "{}"'.format(i) for i in check_strings]
def pre_cmd(self):
ret = ['$P_SRV']
for _, cert, key in map(lambda sig_alg: CERTIFICATES[sig_alg], self._cert_sig_algs):
ret += ['crt_file={cert} key_file={key}'.format(cert=cert, key=key)]
return ret
def hrr_post_checks(self, named_group):
return ['-s "HRR selected_group: {:s}"'.format(named_group)]
class MbedTLSCli(MbedTLSBase):
"""
Generate test commands for mbedTLS client.
"""
def pre_cmd(self):
return ['$P_CLI',
'ca_file={cafile}'.format(cafile=CERTIFICATES[self._cert_sig_algs[0]].cafile)]
def pre_checks(self):
return ['requires_config_enabled MBEDTLS_SSL_CLI_C'] + super().pre_checks()
def hrr_post_checks(self, named_group):
ret = ['-c "received HelloRetryRequest message"']
ret += ['-c "selected_group ( {:d} )"'.format(NAMED_GROUP_IANA_VALUE[named_group])]
return ret
def post_checks(self):
check_strings = ["Protocol is TLSv1.3"]
if self._ciphers:
check_strings.append(
"server hello, chosen ciphersuite: ( {:04x} ) - {}".format(
CIPHER_SUITE_IANA_VALUE[self._ciphers[0]],
self.CIPHER_SUITE[self._ciphers[0]]))
if self._sig_algs:
check_strings.append(
"Certificate Verify: Signature algorithm ( {:04x} )".format(
SIG_ALG_IANA_VALUE[self._sig_algs[0]]))
for named_group in self._named_groups:
check_strings += ['NamedGroup: {named_group} ( {iana_value:x} )'.format(
named_group=named_group,
iana_value=NAMED_GROUP_IANA_VALUE[named_group])]
check_strings.append("Verifying peer X.509 certificate... ok")
return ['-c "{}"'.format(i) for i in check_strings]
SERVER_CLASSES = {'OpenSSL': OpenSSLServ, 'GnuTLS': GnuTLSServ, 'mbedTLS': MbedTLSServ}
CLIENT_CLASSES = {'OpenSSL': OpenSSLCli, 'GnuTLS': GnuTLSCli, 'mbedTLS': MbedTLSCli}
def generate_compat_test(client=None, server=None, cipher=None, named_group=None, sig_alg=None):
"""
Generate test case with `ssl-opt.sh` format.
"""
name = 'TLS 1.3 {client[0]}->{server[0]}: {cipher},{named_group},{sig_alg}'.format(
client=client, server=server, cipher=cipher[4:], sig_alg=sig_alg, named_group=named_group)
server_object = SERVER_CLASSES[server](ciphersuite=cipher,
named_group=named_group,
signature_algorithm=sig_alg,
cert_sig_alg=sig_alg)
client_object = CLIENT_CLASSES[client](ciphersuite=cipher,
named_group=named_group,
signature_algorithm=sig_alg,
cert_sig_alg=sig_alg)
cmd = ['run_test "{}"'.format(name),
'"{}"'.format(' '.join(server_object.cmd())),
'"{}"'.format(' '.join(client_object.cmd())),
'0']
cmd += server_object.post_checks()
cmd += client_object.post_checks()
cmd += ['-C "received HelloRetryRequest message"']
prefix = ' \\\n' + (' '*9)
cmd = prefix.join(cmd)
return '\n'.join(server_object.pre_checks() + client_object.pre_checks() + [cmd])
def generate_hrr_compat_test(client=None, server=None,
client_named_group=None, server_named_group=None,
cert_sig_alg=None):
"""
Generate Hello Retry Request test case with `ssl-opt.sh` format.
"""
name = 'TLS 1.3 {client[0]}->{server[0]}: HRR {c_named_group} -> {s_named_group}'.format(
client=client, server=server, c_named_group=client_named_group,
s_named_group=server_named_group)
server_object = SERVER_CLASSES[server](named_group=server_named_group,
cert_sig_alg=cert_sig_alg)
client_object = CLIENT_CLASSES[client](named_group=client_named_group,
cert_sig_alg=cert_sig_alg)
client_object.add_named_groups(server_named_group)
cmd = ['run_test "{}"'.format(name),
'"{}"'.format(' '.join(server_object.cmd())),
'"{}"'.format(' '.join(client_object.cmd())),
'0']
cmd += server_object.post_checks()
cmd += client_object.post_checks()
cmd += server_object.hrr_post_checks(server_named_group)
cmd += client_object.hrr_post_checks(server_named_group)
prefix = ' \\\n' + (' '*9)
cmd = prefix.join(cmd)
return '\n'.join(server_object.pre_checks() +
client_object.pre_checks() +
[cmd])
SSL_OUTPUT_HEADER = '''#!/bin/sh
# {filename}
#
# Copyright The Mbed TLS Contributors
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Purpose
#
# List TLS1.3 compat test cases. They are generated by
# `{cmd}`.
#
# PLEASE DO NOT EDIT THIS FILE. IF NEEDED, PLEASE MODIFY `generate_tls13_compat_tests.py`
# AND REGENERATE THIS FILE.
#
'''
def main():
"""
Main function of this program
"""
parser = argparse.ArgumentParser()
parser.add_argument('-o', '--output', nargs='?',
default=None, help='Output file path if `-a` was set')
parser.add_argument('-a', '--generate-all-tls13-compat-tests', action='store_true',
default=False, help='Generate all available tls13 compat tests')
parser.add_argument('--list-ciphers', action='store_true',
default=False, help='List supported ciphersuites')
parser.add_argument('--list-sig-algs', action='store_true',
default=False, help='List supported signature algorithms')
parser.add_argument('--list-named-groups', action='store_true',
default=False, help='List supported named groups')
parser.add_argument('--list-servers', action='store_true',
default=False, help='List supported TLS servers')
parser.add_argument('--list-clients', action='store_true',
default=False, help='List supported TLS Clients')
parser.add_argument('server', choices=SERVER_CLASSES.keys(), nargs='?',
default=list(SERVER_CLASSES.keys())[0],
help='Choose TLS server program for test')
parser.add_argument('client', choices=CLIENT_CLASSES.keys(), nargs='?',
default=list(CLIENT_CLASSES.keys())[0],
help='Choose TLS client program for test')
parser.add_argument('cipher', choices=CIPHER_SUITE_IANA_VALUE.keys(), nargs='?',
default=list(CIPHER_SUITE_IANA_VALUE.keys())[0],
help='Choose cipher suite for test')
parser.add_argument('sig_alg', choices=SIG_ALG_IANA_VALUE.keys(), nargs='?',
default=list(SIG_ALG_IANA_VALUE.keys())[0],
help='Choose cipher suite for test')
parser.add_argument('named_group', choices=NAMED_GROUP_IANA_VALUE.keys(), nargs='?',
default=list(NAMED_GROUP_IANA_VALUE.keys())[0],
help='Choose cipher suite for test')
args = parser.parse_args()
def get_all_test_cases():
# Generate normal compat test cases
for client, server, cipher, named_group, sig_alg in \
itertools.product(CLIENT_CLASSES.keys(),
SERVER_CLASSES.keys(),
CIPHER_SUITE_IANA_VALUE.keys(),
NAMED_GROUP_IANA_VALUE.keys(),
SIG_ALG_IANA_VALUE.keys()):
if server == 'mbedTLS' or client == 'mbedTLS':
yield generate_compat_test(client=client, server=server,
cipher=cipher, named_group=named_group,
sig_alg=sig_alg)
# Generate Hello Retry Request compat test cases
for client, server, client_named_group, server_named_group in \
itertools.product(CLIENT_CLASSES.keys(),
SERVER_CLASSES.keys(),
NAMED_GROUP_IANA_VALUE.keys(),
NAMED_GROUP_IANA_VALUE.keys()):
if (client == 'mbedTLS' or server == 'mbedTLS') and \
client_named_group != server_named_group:
yield generate_hrr_compat_test(client=client, server=server,
client_named_group=client_named_group,
server_named_group=server_named_group,
cert_sig_alg="ecdsa_secp256r1_sha256")
if args.generate_all_tls13_compat_tests:
if args.output:
with open(args.output, 'w', encoding="utf-8") as f:
f.write(SSL_OUTPUT_HEADER.format(
filename=os.path.basename(args.output), cmd=' '.join(sys.argv)))
f.write('\n\n'.join(get_all_test_cases()))
f.write('\n')
else:
print('\n\n'.join(get_all_test_cases()))
return 0
if args.list_ciphers or args.list_sig_algs or args.list_named_groups \
or args.list_servers or args.list_clients:
if args.list_ciphers:
print(*CIPHER_SUITE_IANA_VALUE.keys())
if args.list_sig_algs:
print(*SIG_ALG_IANA_VALUE.keys())
if args.list_named_groups:
print(*NAMED_GROUP_IANA_VALUE.keys())
if args.list_servers:
print(*SERVER_CLASSES.keys())
if args.list_clients:
print(*CLIENT_CLASSES.keys())
return 0
print(generate_compat_test(server=args.server, client=args.client, sig_alg=args.sig_alg,
cipher=args.cipher, named_group=args.named_group))
return 0
if __name__ == "__main__":
sys.exit(main())

View File

@ -0,0 +1,76 @@
#!/usr/bin/env perl
# key-exchanges.pl
#
# Copyright The Mbed TLS Contributors
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Purpose
#
# To test the code dependencies on individual key exchanges in the SSL module.
# is a verification step to ensure we don't ship SSL code that do not work
# for some build options.
#
# The process is:
# for each possible key exchange
# build the library with all but that key exchange disabled
#
# Usage: tests/scripts/key-exchanges.pl
#
# This script should be executed from the root of the project directory.
#
# For best effect, run either with cmake disabled, or cmake enabled in a mode
# that includes -Werror.
use warnings;
use strict;
-d 'library' && -d 'include' && -d 'tests' or die "Must be run from root\n";
my $sed_cmd = 's/^#define \(MBEDTLS_KEY_EXCHANGE_.*_ENABLED\)/\1/p';
my $config_h = 'include/mbedtls/mbedtls_config.h';
my @kexes = split( /\s+/, `sed -n -e '$sed_cmd' $config_h` );
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";
# use an exit code between 1 and 124 for git bisect (die returns 255)
warn $_[0];
exit 1;
}
for my $kex (@kexes) {
system( "cp $config_h.bak $config_h" ) and die "$config_h not restored\n";
system( "make clean" ) and die;
print "\n******************************************\n";
print "* Testing with key exchange: $kex\n";
print "******************************************\n";
$ENV{MBEDTLS_TEST_CONFIGURATION} = $kex;
# full config with all key exchanges disabled except one
system( "scripts/config.py full" ) and abort "Failed config full\n";
for my $k (@kexes) {
next if $k eq $kex;
system( "scripts/config.py unset $k" )
and abort "Failed to disable $k\n";
}
system( "make lib CFLAGS='-Os -Werror'" ) and abort "Failed to build lib: $kex\n";
}
system( "mv $config_h.bak $config_h" ) and die "$config_h not restored\n";
system( "make clean" ) and die;
exit 0;

View File

@ -0,0 +1,66 @@
#!/bin/bash
#
# Create a file named identifiers containing identifiers from internal header
# files, based on the --internal flag.
# Outputs the line count of the file to stdout.
# A very thin wrapper around list_internal_identifiers.py for backwards
# compatibility.
# Must be run from Mbed TLS root.
#
# Usage: list-identifiers.sh [ -i | --internal ]
#
# Copyright The Mbed TLS Contributors
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
set -eu
if [ -d include/mbedtls ]; then :; else
echo "$0: Must be run from Mbed TLS root" >&2
exit 1
fi
INTERNAL=""
until [ -z "${1-}" ]
do
case "$1" in
-i|--internal)
INTERNAL="1"
;;
*)
# print error
echo "Unknown argument: '$1'"
exit 1
;;
esac
shift
done
if [ $INTERNAL ]
then
tests/scripts/list_internal_identifiers.py
wc -l identifiers
else
cat <<EOF
Sorry, this script has to be called with --internal.
This script exists solely for backwards compatibility with the previous
iteration of list-identifiers.sh, of which only the --internal option remains in
use. It is a thin wrapper around list_internal_identifiers.py.
check-names.sh, which used to depend on this script, has been replaced with
check_names.py and is now self-complete.
EOF
fi

View File

@ -0,0 +1,57 @@
#!/usr/bin/env python3
#
# Copyright The Mbed TLS Contributors
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
This script generates a file called identifiers that contains all Mbed TLS
identifiers found on internal headers. This is the equivalent of what was
previously `list-identifiers.sh --internal`, and is useful for generating an
exclusion file list for ABI/API checking, since we do not promise compatibility
for them.
It uses the CodeParser class from check_names.py to perform the parsing.
The script returns 0 on success, 1 if there is a script error.
Must be run from Mbed TLS root.
"""
import argparse
import logging
from check_names import CodeParser
def main():
parser = argparse.ArgumentParser(
formatter_class=argparse.RawDescriptionHelpFormatter,
description=(
"This script writes a list of parsed identifiers in internal "
"headers to \"identifiers\". This is useful for generating a list "
"of names to exclude from API/ABI compatibility checking. "))
parser.parse_args()
name_check = CodeParser(logging.getLogger())
result = name_check.parse_identifiers([
"include/mbedtls/*_internal.h",
"library/*.h"
])
result.sort(key=lambda x: x.name)
identifiers = ["{}\n".format(match.name) for match in result]
with open("identifiers", "w", encoding="utf-8") as f:
f.writelines(identifiers)
if __name__ == "__main__":
main()

View File

@ -0,0 +1,140 @@
#!/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).
"""
# Copyright The Mbed TLS Contributors
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
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

@ -0,0 +1,59 @@
#!/usr/bin/env perl
# Find functions making recursive calls to themselves.
# (Multiple recursion where a() calls b() which calls a() not covered.)
#
# When the recursion depth might depend on data controlled by the attacker in
# an unbounded way, those functions should use iteration instead.
#
# Typical usage: scripts/recursion.pl library/*.c
#
# Copyright The Mbed TLS Contributors
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
use warnings;
use strict;
use utf8;
use open qw(:std utf8);
# exclude functions that are ok:
# - mpi_write_hlp: bounded by size of mbedtls_mpi, a compile-time constant
# - x509_crt_verify_child: bounded by MBEDTLS_X509_MAX_INTERMEDIATE_CA
my $known_ok = qr/mpi_write_hlp|x509_crt_verify_child/;
my $cur_name;
my $inside;
my @funcs;
die "Usage: $0 file.c [...]\n" unless @ARGV;
while (<>)
{
if( /^[^\/#{}\s]/ && ! /\[.*]/ ) {
chomp( $cur_name = $_ ) unless $inside;
} elsif( /^{/ && $cur_name ) {
$inside = 1;
$cur_name =~ s/.* ([^ ]*)\(.*/$1/;
} elsif( /^}/ && $inside ) {
undef $inside;
undef $cur_name;
} elsif( $inside && /\b\Q$cur_name\E\([^)]/ ) {
push @funcs, $cur_name unless /$known_ok/;
}
}
print "$_\n" for @funcs;
exit @funcs;

View File

@ -0,0 +1,163 @@
#!/usr/bin/env perl
# run-test-suites.pl
#
# Copyright The Mbed TLS Contributors
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
=head1 SYNOPSIS
Execute all the test suites and print a summary of the results.
run-test-suites.pl [[-v|--verbose] [VERBOSITY]] [--skip=SUITE[...]]
Options:
-v|--verbose Print detailed failure information.
-v 2|--verbose=2 Print detailed failure information and summary messages.
-v 3|--verbose=3 Print detailed information about every test case.
--skip=SUITE[,SUITE...]
Skip the specified SUITE(s). This option can be used
multiple times.
=cut
use warnings;
use strict;
use utf8;
use open qw(:std utf8);
use Getopt::Long qw(:config auto_help gnu_compat);
use Pod::Usage;
my $verbose = 0;
my @skip_patterns = ();
GetOptions(
'skip=s' => \@skip_patterns,
'verbose|v:1' => \$verbose,
) or die;
# All test suites = executable files, excluding source files, debug
# and profiling information, etc. We can't just grep {! /\./} because
# some of our test cases' base names contain a dot.
my @suites = grep { -x $_ || /\.exe$/ } glob 'test_suite_*';
@suites = grep { !/\.c$/ && !/\.data$/ && -f } @suites;
die "$0: no test suite found\n" unless @suites;
# "foo" as a skip pattern skips "test_suite_foo" and "test_suite_foo.bar"
# but not "test_suite_foobar".
my $skip_re =
( '\Atest_suite_(' .
join('|', map {
s/[ ,;]/|/g; # allow any of " ,;|" as separators
s/\./\./g; # "." in the input means ".", not "any character"
$_
} @skip_patterns) .
')(\z|\.)' );
# in case test suites are linked dynamically
$ENV{'LD_LIBRARY_PATH'} = '../library';
$ENV{'DYLD_LIBRARY_PATH'} = '../library';
my $prefix = $^O eq "MSWin32" ? '' : './';
my ($failed_suites, $total_tests_run, $failed, $suite_cases_passed,
$suite_cases_failed, $suite_cases_skipped, $total_cases_passed,
$total_cases_failed, $total_cases_skipped );
my $suites_skipped = 0;
sub pad_print_center {
my( $width, $padchar, $string ) = @_;
my $padlen = ( $width - length( $string ) - 2 ) / 2;
print $padchar x( $padlen ), " $string ", $padchar x( $padlen ), "\n";
}
for my $suite (@suites)
{
print "$suite ", "." x ( 72 - length($suite) - 2 - 4 ), " ";
if( $suite =~ /$skip_re/o ) {
print "SKIP\n";
++$suites_skipped;
next;
}
my $command = "$prefix$suite";
if( $verbose ) {
$command .= ' -v';
}
my $result = `$command`;
$suite_cases_passed = () = $result =~ /.. PASS/g;
$suite_cases_failed = () = $result =~ /.. FAILED/g;
$suite_cases_skipped = () = $result =~ /.. ----/g;
if( $? == 0 ) {
print "PASS\n";
if( $verbose > 2 ) {
pad_print_center( 72, '-', "Begin $suite" );
print $result;
pad_print_center( 72, '-', "End $suite" );
}
} else {
$failed_suites++;
print "FAIL\n";
if( $verbose ) {
pad_print_center( 72, '-', "Begin $suite" );
print $result;
pad_print_center( 72, '-', "End $suite" );
}
}
my ($passed, $tests, $skipped) = $result =~ /([0-9]*) \/ ([0-9]*) tests.*?([0-9]*) skipped/;
$total_tests_run += $tests - $skipped;
if( $verbose > 1 ) {
print "(test cases passed:", $suite_cases_passed,
" failed:", $suite_cases_failed,
" skipped:", $suite_cases_skipped,
" of total:", ($suite_cases_passed + $suite_cases_failed +
$suite_cases_skipped),
")\n"
}
$total_cases_passed += $suite_cases_passed;
$total_cases_failed += $suite_cases_failed;
$total_cases_skipped += $suite_cases_skipped;
}
print "-" x 72, "\n";
print $failed_suites ? "FAILED" : "PASSED";
printf( " (%d suites, %d tests run%s)\n",
scalar(@suites) - $suites_skipped,
$total_tests_run,
$suites_skipped ? ", $suites_skipped suites skipped" : "" );
if( $verbose > 1 ) {
print " test cases passed :", $total_cases_passed, "\n";
print " failed :", $total_cases_failed, "\n";
print " skipped :", $total_cases_skipped, "\n";
print " of tests executed :", ( $total_cases_passed + $total_cases_failed ),
"\n";
print " of available tests :",
( $total_cases_passed + $total_cases_failed + $total_cases_skipped ),
"\n";
if( $suites_skipped != 0 ) {
print "Note: $suites_skipped suites were skipped.\n";
}
}
exit( $failed_suites ? 1 : 0 );

View File

@ -0,0 +1,28 @@
"""Add our Python library directory to the module search path.
Usage:
import scripts_path # pylint: disable=unused-import
"""
# Copyright The Mbed TLS Contributors
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import sys
sys.path.append(os.path.join(os.path.dirname(__file__),
os.path.pardir, os.path.pardir,
'scripts'))

View File

@ -0,0 +1,296 @@
#!/usr/bin/env python3
"""Edit test cases to use PSA dependencies instead of classic dependencies.
"""
# Copyright The Mbed TLS Contributors
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import re
import sys
CLASSIC_DEPENDENCIES = frozenset([
# This list is manually filtered from mbedtls_config.h.
# Mbed TLS feature support.
# Only features that affect what can be done are listed here.
# Options that control optimizations or alternative implementations
# are omitted.
'MBEDTLS_CIPHER_MODE_CBC',
'MBEDTLS_CIPHER_MODE_CFB',
'MBEDTLS_CIPHER_MODE_CTR',
'MBEDTLS_CIPHER_MODE_OFB',
'MBEDTLS_CIPHER_MODE_XTS',
'MBEDTLS_CIPHER_NULL_CIPHER',
'MBEDTLS_CIPHER_PADDING_PKCS7',
'MBEDTLS_CIPHER_PADDING_ONE_AND_ZEROS',
'MBEDTLS_CIPHER_PADDING_ZEROS_AND_LEN',
'MBEDTLS_CIPHER_PADDING_ZEROS',
#curve#'MBEDTLS_ECP_DP_SECP192R1_ENABLED',
#curve#'MBEDTLS_ECP_DP_SECP224R1_ENABLED',
#curve#'MBEDTLS_ECP_DP_SECP256R1_ENABLED',
#curve#'MBEDTLS_ECP_DP_SECP384R1_ENABLED',
#curve#'MBEDTLS_ECP_DP_SECP521R1_ENABLED',
#curve#'MBEDTLS_ECP_DP_SECP192K1_ENABLED',
#curve#'MBEDTLS_ECP_DP_SECP224K1_ENABLED',
#curve#'MBEDTLS_ECP_DP_SECP256K1_ENABLED',
#curve#'MBEDTLS_ECP_DP_BP256R1_ENABLED',
#curve#'MBEDTLS_ECP_DP_BP384R1_ENABLED',
#curve#'MBEDTLS_ECP_DP_BP512R1_ENABLED',
#curve#'MBEDTLS_ECP_DP_CURVE25519_ENABLED',
#curve#'MBEDTLS_ECP_DP_CURVE448_ENABLED',
'MBEDTLS_ECDSA_DETERMINISTIC',
#'MBEDTLS_GENPRIME', #needed for RSA key generation
'MBEDTLS_PKCS1_V15',
'MBEDTLS_PKCS1_V21',
# Mbed TLS modules.
# Only modules that provide cryptographic mechanisms are listed here.
# Platform, data formatting, X.509 or TLS modules are omitted.
'MBEDTLS_AES_C',
'MBEDTLS_BIGNUM_C',
'MBEDTLS_CAMELLIA_C',
'MBEDTLS_ARIA_C',
'MBEDTLS_CCM_C',
'MBEDTLS_CHACHA20_C',
'MBEDTLS_CHACHAPOLY_C',
'MBEDTLS_CMAC_C',
'MBEDTLS_CTR_DRBG_C',
'MBEDTLS_DES_C',
'MBEDTLS_DHM_C',
'MBEDTLS_ECDH_C',
'MBEDTLS_ECDSA_C',
'MBEDTLS_ECJPAKE_C',
'MBEDTLS_ECP_C',
'MBEDTLS_ENTROPY_C',
'MBEDTLS_GCM_C',
'MBEDTLS_HKDF_C',
'MBEDTLS_HMAC_DRBG_C',
'MBEDTLS_NIST_KW_C',
'MBEDTLS_MD5_C',
'MBEDTLS_PKCS5_C',
'MBEDTLS_PKCS12_C',
'MBEDTLS_POLY1305_C',
'MBEDTLS_RIPEMD160_C',
'MBEDTLS_RSA_C',
'MBEDTLS_SHA1_C',
'MBEDTLS_SHA256_C',
'MBEDTLS_SHA512_C',
])
def is_classic_dependency(dep):
"""Whether dep is a classic dependency that PSA test cases should not use."""
if dep.startswith('!'):
dep = dep[1:]
return dep in CLASSIC_DEPENDENCIES
def is_systematic_dependency(dep):
"""Whether dep is a PSA dependency which is determined systematically."""
if dep.startswith('PSA_WANT_ECC_'):
return False
return dep.startswith('PSA_WANT_')
WITHOUT_SYSTEMATIC_DEPENDENCIES = frozenset([
'PSA_ALG_AEAD_WITH_SHORTENED_TAG', # only a modifier
'PSA_ALG_ANY_HASH', # only meaningful in policies
'PSA_ALG_KEY_AGREEMENT', # only a way to combine algorithms
'PSA_ALG_TRUNCATED_MAC', # only a modifier
'PSA_KEY_TYPE_NONE', # not a real key type
'PSA_KEY_TYPE_DERIVE', # always supported, don't list it to reduce noise
'PSA_KEY_TYPE_RAW_DATA', # always supported, don't list it to reduce noise
'PSA_ALG_AT_LEAST_THIS_LENGTH_MAC', #only a modifier
'PSA_ALG_AEAD_WITH_AT_LEAST_THIS_LENGTH_TAG', #only a modifier
])
SPECIAL_SYSTEMATIC_DEPENDENCIES = {
'PSA_ALG_ECDSA_ANY': frozenset(['PSA_WANT_ALG_ECDSA']),
'PSA_ALG_RSA_PKCS1V15_SIGN_RAW': frozenset(['PSA_WANT_ALG_RSA_PKCS1V15_SIGN']),
}
def dependencies_of_symbol(symbol):
"""Return the dependencies for a symbol that designates a cryptographic mechanism."""
if symbol in WITHOUT_SYSTEMATIC_DEPENDENCIES:
return frozenset()
if symbol in SPECIAL_SYSTEMATIC_DEPENDENCIES:
return SPECIAL_SYSTEMATIC_DEPENDENCIES[symbol]
if symbol.startswith('PSA_ALG_CATEGORY_') or \
symbol.startswith('PSA_KEY_TYPE_CATEGORY_'):
# Categories are used in test data when an unsupported but plausible
# mechanism number needed. They have no associated dependency.
return frozenset()
return {symbol.replace('_', '_WANT_', 1)}
def systematic_dependencies(file_name, function_name, arguments):
"""List the systematically determined dependency for a test case."""
deps = set()
# Run key policy negative tests even if the algorithm to attempt performing
# is not supported but in the case where the test is to check an
# incompatibility between a requested algorithm for a cryptographic
# operation and a key policy. In the latter, we want to filter out the
# cases # where PSA_ERROR_NOT_SUPPORTED is returned instead of
# PSA_ERROR_NOT_PERMITTED.
if function_name.endswith('_key_policy') and \
arguments[-1].startswith('PSA_ERROR_') and \
arguments[-1] != ('PSA_ERROR_NOT_PERMITTED'):
arguments[-2] = ''
if function_name == 'copy_fail' and \
arguments[-1].startswith('PSA_ERROR_'):
arguments[-2] = ''
arguments[-3] = ''
# Storage format tests that only look at how the file is structured and
# don't care about the format of the key material don't depend on any
# cryptographic mechanisms.
if os.path.basename(file_name) == 'test_suite_psa_crypto_persistent_key.data' and \
function_name in {'format_storage_data_check',
'parse_storage_data_check'}:
return []
for arg in arguments:
for symbol in re.findall(r'PSA_(?:ALG|KEY_TYPE)_\w+', arg):
deps.update(dependencies_of_symbol(symbol))
return sorted(deps)
def updated_dependencies(file_name, function_name, arguments, dependencies):
"""Rework the list of dependencies into PSA_WANT_xxx.
Remove classic crypto dependencies such as MBEDTLS_RSA_C,
MBEDTLS_PKCS1_V15, etc.
Add systematic PSA_WANT_xxx dependencies based on the called function and
its arguments, replacing existing PSA_WANT_xxx dependencies.
"""
automatic = systematic_dependencies(file_name, function_name, arguments)
manual = [dep for dep in dependencies
if not (is_systematic_dependency(dep) or
is_classic_dependency(dep))]
return automatic + manual
def keep_manual_dependencies(file_name, function_name, arguments):
#pylint: disable=unused-argument
"""Declare test functions with unusual dependencies here."""
# If there are no arguments, we can't do any useful work. Assume that if
# there are dependencies, they are warranted.
if not arguments:
return True
# When PSA_ERROR_NOT_SUPPORTED is expected, usually, at least one of the
# constants mentioned in the test should not be supported. It isn't
# possible to determine which one in a systematic way. So let the programmer
# decide.
if arguments[-1] == 'PSA_ERROR_NOT_SUPPORTED':
return True
return False
def process_data_stanza(stanza, file_name, test_case_number):
"""Update PSA crypto dependencies in one Mbed TLS test case.
stanza is the test case text (including the description, the dependencies,
the line with the function and arguments, and optionally comments). Return
a new stanza with an updated dependency line, preserving everything else
(description, comments, arguments, etc.).
"""
if not stanza.lstrip('\n'):
# Just blank lines
return stanza
# Expect 2 or 3 non-comment lines: description, optional dependencies,
# function-and-arguments.
content_matches = list(re.finditer(r'^[\t ]*([^\t #].*)$', stanza, re.M))
if len(content_matches) < 2:
raise Exception('Not enough content lines in paragraph {} in {}'
.format(test_case_number, file_name))
if len(content_matches) > 3:
raise Exception('Too many content lines in paragraph {} in {}'
.format(test_case_number, file_name))
arguments = content_matches[-1].group(0).split(':')
function_name = arguments.pop(0)
if keep_manual_dependencies(file_name, function_name, arguments):
return stanza
if len(content_matches) == 2:
# Insert a line for the dependencies. If it turns out that there are
# no dependencies, we'll remove that empty line below.
dependencies_location = content_matches[-1].start()
text_before = stanza[:dependencies_location]
text_after = '\n' + stanza[dependencies_location:]
old_dependencies = []
dependencies_leader = 'depends_on:'
else:
dependencies_match = content_matches[-2]
text_before = stanza[:dependencies_match.start()]
text_after = stanza[dependencies_match.end():]
old_dependencies = dependencies_match.group(0).split(':')
dependencies_leader = old_dependencies.pop(0) + ':'
if dependencies_leader != 'depends_on:':
raise Exception('Next-to-last line does not start with "depends_on:"'
' in paragraph {} in {}'
.format(test_case_number, file_name))
new_dependencies = updated_dependencies(file_name, function_name, arguments,
old_dependencies)
if new_dependencies:
stanza = (text_before +
dependencies_leader + ':'.join(new_dependencies) +
text_after)
else:
# The dependencies have become empty. Remove the depends_on: line.
assert text_after[0] == '\n'
stanza = text_before + text_after[1:]
return stanza
def process_data_file(file_name, old_content):
"""Update PSA crypto dependencies in an Mbed TLS test suite data file.
Process old_content (the old content of the file) and return the new content.
"""
old_stanzas = old_content.split('\n\n')
new_stanzas = [process_data_stanza(stanza, file_name, n)
for n, stanza in enumerate(old_stanzas, start=1)]
return '\n\n'.join(new_stanzas)
def update_file(file_name, old_content, new_content):
"""Update the given file with the given new content.
Replace the existing file. The previous version is renamed to *.bak.
Don't modify the file if the content was unchanged.
"""
if new_content == old_content:
return
backup = file_name + '.bak'
tmp = file_name + '.tmp'
with open(tmp, 'w', encoding='utf-8') as new_file:
new_file.write(new_content)
os.replace(file_name, backup)
os.replace(tmp, file_name)
def process_file(file_name):
"""Update PSA crypto dependencies in an Mbed TLS test suite data file.
Replace the existing file. The previous version is renamed to *.bak.
Don't modify the file if the content was unchanged.
"""
old_content = open(file_name, encoding='utf-8').read()
if file_name.endswith('.data'):
new_content = process_data_file(file_name, old_content)
else:
raise Exception('File type not recognized: {}'
.format(file_name))
update_file(file_name, old_content, new_content)
def main(args):
for file_name in args:
process_file(file_name)
if __name__ == '__main__':
main(sys.argv[1:])

View File

@ -0,0 +1,101 @@
#!/usr/bin/env perl
# A simple TCP client that sends some data and expects a response.
# Usage: tcp_client.pl HOSTNAME PORT DATA1 RESPONSE1
# DATA: hex-encoded data to send to the server
# RESPONSE: regexp that must match the server's response
#
# Copyright The Mbed TLS Contributors
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
use warnings;
use strict;
use IO::Socket::INET;
# Pack hex digits into a binary string, ignoring whitespace.
sub parse_hex {
my ($hex) = @_;
$hex =~ s/\s+//g;
return pack('H*', $hex);
}
## Open a TCP connection to the specified host and port.
sub open_connection {
my ($host, $port) = @_;
my $socket = IO::Socket::INET->new(PeerAddr => $host,
PeerPort => $port,
Proto => 'tcp',
Timeout => 1);
die "Cannot connect to $host:$port: $!" unless $socket;
return $socket;
}
## Close the TCP connection.
sub close_connection {
my ($connection) = @_;
$connection->shutdown(2);
# Ignore shutdown failures (at least for now)
return 1;
}
## Write the given data, expressed as hexadecimal
sub write_data {
my ($connection, $hexdata) = @_;
my $data = parse_hex($hexdata);
my $total_sent = 0;
while ($total_sent < length($data)) {
my $sent = $connection->send($data, 0);
if (!defined $sent) {
die "Unable to send data: $!";
}
$total_sent += $sent;
}
return 1;
}
## Read a response and check it against an expected prefix
sub read_response {
my ($connection, $expected_hex) = @_;
my $expected_data = parse_hex($expected_hex);
my $start_offset = 0;
while ($start_offset < length($expected_data)) {
my $actual_data;
my $ok = $connection->recv($actual_data, length($expected_data));
if (!defined $ok) {
die "Unable to receive data: $!";
}
if (($actual_data ^ substr($expected_data, $start_offset)) =~ /[^\000]/) {
printf STDERR ("Received \\x%02x instead of \\x%02x at offset %d\n",
ord(substr($actual_data, $-[0], 1)),
ord(substr($expected_data, $start_offset + $-[0], 1)),
$start_offset + $-[0]);
return 0;
}
$start_offset += length($actual_data);
}
return 1;
}
if (@ARGV != 4) {
print STDERR "Usage: $0 HOSTNAME PORT DATA1 RESPONSE1\n";
exit(3);
}
my ($host, $port, $data1, $response1) = @ARGV;
my $connection = open_connection($host, $port);
write_data($connection, $data1);
if (!read_response($connection, $response1)) {
exit(1);
}
close_connection($connection);

View File

@ -0,0 +1,170 @@
#!/usr/bin/env perl
# test-ref-configs.pl
#
# Copyright The Mbed TLS Contributors
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Purpose
#
# For each reference configuration file in the configs directory, build the
# configuration, run the test suites and compat.sh
#
# Usage: tests/scripts/test-ref-configs.pl [config-name [...]]
use warnings;
use strict;
my %configs = (
'config-ccm-psk-tls1_2.h' => {
'compat' => '-m tls12 -f \'^TLS-PSK-WITH-AES-...-CCM-8\'',
'test_again_with_use_psa' => 1
},
'config-ccm-psk-dtls1_2.h' => {
'compat' => '-m dtls12 -f \'^TLS-PSK-WITH-AES-...-CCM-8\'',
'opt' => ' ',
'opt_needs_debug' => 1,
'test_again_with_use_psa' => 1
},
'config-no-entropy.h' => {
},
'config-suite-b.h' => {
'compat' => "-m tls12 -f 'ECDHE-ECDSA.*AES.*GCM' -p mbedTLS",
'test_again_with_use_psa' => 1,
'opt' => ' ',
'opt_needs_debug' => 1,
},
'config-symmetric-only.h' => {
'test_again_with_use_psa' => 0, # Uses PSA by default, no need to test it twice
},
'config-thread.h' => {
'opt' => '-f ECJPAKE.*nolog',
'test_again_with_use_psa' => 1,
},
);
# If no config-name is provided, use all known configs.
# Otherwise, use the provided names only.
my @configs_to_test = sort keys %configs;
if ($#ARGV >= 0) {
foreach my $conf_name ( @ARGV ) {
if( ! exists $configs{$conf_name} ) {
die "Unknown configuration: $conf_name\n";
}
}
@configs_to_test = @ARGV;
}
-d 'library' && -d 'include' && -d 'tests' or die "Must be run from root\n";
my $config_h = 'include/mbedtls/mbedtls_config.h';
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";
# use an exit code between 1 and 124 for git bisect (die returns 255)
warn $_[0];
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;
}
sub perform_test {
my $conf_file = $_[0];
my $data = $_[1];
my $test_with_psa = $_[2];
my $conf_name = $conf_file;
if ( $test_with_psa )
{
$conf_name .= "+PSA";
}
system( "cp $config_h.bak $config_h" ) and die;
system( "make clean" ) and die;
print "\n******************************************\n";
print "* Testing configuration: $conf_name\n";
print "******************************************\n";
$ENV{MBEDTLS_TEST_CONFIGURATION} = $conf_name;
system( "cp configs/$conf_file $config_h" )
and abort "Failed to activate $conf_file\n";
if ( $test_with_psa )
{
system( "scripts/config.py set MBEDTLS_PSA_CRYPTO_C" );
system( "scripts/config.py set MBEDTLS_USE_PSA_CRYPTO" );
}
system( "CFLAGS='-Os -Werror -Wall -Wextra' make" ) and abort "Failed to build: $conf_name\n";
system( "make test" ) and abort "Failed test suite: $conf_name\n";
my $compat = $data->{'compat'};
if( $compat )
{
print "\nrunning compat.sh $compat ($conf_name)\n";
system( "tests/compat.sh $compat" )
and abort "Failed compat.sh: $conf_name\n";
}
else
{
print "\nskipping compat.sh ($conf_name)\n";
}
my $opt = $data->{'opt'};
if( $opt )
{
if( $data->{'opt_needs_debug'} )
{
print "\nrebuilding with debug traces for ssl-opt ($conf_name)\n";
$conf_name .= '+DEBUG';
$ENV{MBEDTLS_TEST_CONFIGURATION} = $conf_name;
system( "make clean" );
system( "scripts/config.py set MBEDTLS_DEBUG_C" );
system( "scripts/config.py set MBEDTLS_ERROR_C" );
system( "CFLAGS='-Os -Werror -Wall -Wextra' make" ) and abort "Failed to build: $conf_name\n";
}
print "\nrunning ssl-opt.sh $opt ($conf_name)\n";
system( "tests/ssl-opt.sh $opt" )
and abort "Failed ssl-opt.sh: $conf_name\n";
}
else
{
print "\nskipping ssl-opt.sh ($conf_name)\n";
}
}
foreach my $conf ( @configs_to_test ) {
my $test_with_psa = $configs{$conf}{'test_again_with_use_psa'};
if ( $test_with_psa )
{
perform_test( $conf, $configs{$conf}, $test_with_psa );
}
perform_test( $conf, $configs{$conf}, 0 );
}
system( "mv $config_h.bak $config_h" ) and warn "$config_h not restored\n";
system( "make clean" );
exit 0;

View File

@ -0,0 +1,186 @@
#!/usr/bin/env python3
"""Test helper for the Mbed TLS configuration file tool
Run config.py with various parameters and write the results to files.
This is a harness to help regression testing, not a functional tester.
Sample usage:
test_config_script.py -d old
## Modify config.py and/or mbedtls_config.h ##
test_config_script.py -d new
diff -ru old new
"""
## Copyright The Mbed TLS Contributors
## SPDX-License-Identifier: Apache-2.0
##
## Licensed under the Apache License, Version 2.0 (the "License"); you may
## not use this file except in compliance with the License.
## You may obtain a copy of the License at
##
## http://www.apache.org/licenses/LICENSE-2.0
##
## Unless required by applicable law or agreed to in writing, software
## distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
## WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
## See the License for the specific language governing permissions and
## limitations under the License.
import argparse
import glob
import os
import re
import shutil
import subprocess
OUTPUT_FILE_PREFIX = 'config-'
def output_file_name(directory, stem, extension):
return os.path.join(directory,
'{}{}.{}'.format(OUTPUT_FILE_PREFIX,
stem, extension))
def cleanup_directory(directory):
"""Remove old output files."""
for extension in []:
pattern = output_file_name(directory, '*', extension)
filenames = glob.glob(pattern)
for filename in filenames:
os.remove(filename)
def prepare_directory(directory):
"""Create the output directory if it doesn't exist yet.
If there are old output files, remove them.
"""
if os.path.exists(directory):
cleanup_directory(directory)
else:
os.makedirs(directory)
def guess_presets_from_help(help_text):
"""Figure out what presets the script supports.
help_text should be the output from running the script with --help.
"""
# Try the output format from config.py
hits = re.findall(r'\{([-\w,]+)\}', help_text)
for hit in hits:
words = set(hit.split(','))
if 'get' in words and 'set' in words and 'unset' in words:
words.remove('get')
words.remove('set')
words.remove('unset')
return words
# Try the output format from config.pl
hits = re.findall(r'\n +([-\w]+) +- ', help_text)
if hits:
return hits
raise Exception("Unable to figure out supported presets. Pass the '-p' option.")
def list_presets(options):
"""Return the list of presets to test.
The list is taken from the command line if present, otherwise it is
extracted from running the config script with --help.
"""
if options.presets:
return re.split(r'[ ,]+', options.presets)
else:
help_text = subprocess.run([options.script, '--help'],
check=False, # config.pl --help returns 255
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT).stdout
return guess_presets_from_help(help_text.decode('ascii'))
def run_one(options, args, stem_prefix='', input_file=None):
"""Run the config script with the given arguments.
Take the original content from input_file if specified, defaulting
to options.input_file if input_file is None.
Write the following files, where xxx contains stem_prefix followed by
a filename-friendly encoding of args:
* config-xxx.h: modified file.
* config-xxx.out: standard output.
* config-xxx.err: standard output.
* config-xxx.status: exit code.
Return ("xxx+", "path/to/config-xxx.h") which can be used as
stem_prefix and input_file to call this function again with new args.
"""
if input_file is None:
input_file = options.input_file
stem = stem_prefix + '-'.join(args)
data_filename = output_file_name(options.output_directory, stem, 'h')
stdout_filename = output_file_name(options.output_directory, stem, 'out')
stderr_filename = output_file_name(options.output_directory, stem, 'err')
status_filename = output_file_name(options.output_directory, stem, 'status')
shutil.copy(input_file, data_filename)
# Pass only the file basename, not the full path, to avoid getting the
# directory name in error messages, which would make comparisons
# between output directories more difficult.
cmd = [os.path.abspath(options.script),
'-f', os.path.basename(data_filename)]
with open(stdout_filename, 'wb') as out:
with open(stderr_filename, 'wb') as err:
status = subprocess.call(cmd + args,
cwd=options.output_directory,
stdin=subprocess.DEVNULL,
stdout=out, stderr=err)
with open(status_filename, 'w') as status_file:
status_file.write('{}\n'.format(status))
return stem + "+", data_filename
### A list of symbols to test with.
### This script currently tests what happens when you change a symbol from
### having a value to not having a value or vice versa. This is not
### necessarily useful behavior, and we may not consider it a bug if
### config.py stops handling that case correctly.
TEST_SYMBOLS = [
'CUSTOM_SYMBOL', # does not exist
'MBEDTLS_AES_C', # set, no value
'MBEDTLS_MPI_MAX_SIZE', # unset, has a value
'MBEDTLS_NO_UDBL_DIVISION', # unset, in "System support"
'MBEDTLS_PLATFORM_ZEROIZE_ALT', # unset, in "Customisation configuration options"
]
def run_all(options):
"""Run all the command lines to test."""
presets = list_presets(options)
for preset in presets:
run_one(options, [preset])
for symbol in TEST_SYMBOLS:
run_one(options, ['get', symbol])
(stem, filename) = run_one(options, ['set', symbol])
run_one(options, ['get', symbol], stem_prefix=stem, input_file=filename)
run_one(options, ['--force', 'set', symbol])
(stem, filename) = run_one(options, ['set', symbol, 'value'])
run_one(options, ['get', symbol], stem_prefix=stem, input_file=filename)
run_one(options, ['--force', 'set', symbol, 'value'])
run_one(options, ['unset', symbol])
def main():
"""Command line entry point."""
parser = argparse.ArgumentParser(description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument('-d', metavar='DIR',
dest='output_directory', required=True,
help="""Output directory.""")
parser.add_argument('-f', metavar='FILE',
dest='input_file', default='include/mbedtls/mbedtls_config.h',
help="""Config file (default: %(default)s).""")
parser.add_argument('-p', metavar='PRESET,...',
dest='presets',
help="""Presets to test (default: guessed from --help).""")
parser.add_argument('-s', metavar='FILE',
dest='script', default='scripts/config.py',
help="""Configuration script (default: %(default)s).""")
options = parser.parse_args()
prepare_directory(options.output_directory)
run_all(options)
if __name__ == '__main__':
main()

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,142 @@
#!/usr/bin/env python3
"""Run the PSA Crypto API compliance test suite.
Clone the repo and check out the commit specified by PSA_ARCH_TEST_REPO and PSA_ARCH_TEST_REF,
then complie and run the test suite. The clone is stored at <Mbed TLS root>/psa-arch-tests.
Known defects in either the test suite or mbedtls - identified by their test number - are ignored,
while unexpected failures AND successes are reported as errors,
to help keep the list of known defects as up to date as possible.
"""
# Copyright The Mbed TLS Contributors
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import re
import shutil
import subprocess
import sys
# PSA Compliance tests we expect to fail due to known defects in Mbed TLS (or the test suite)
# The test numbers correspond to the numbers used by the console output of the test suite.
# Test number 2xx corresponds to the files in the folder
# psa-arch-tests/api-tests/dev_apis/crypto/test_c0xx
EXPECTED_FAILURES = {
# psa_hash_suspend() and psa_hash_resume() are not supported.
# - Tracked in issue #3274
262, 263
}
# We currently use a fork of ARM-software/psa-arch-tests, with a couple of downstream patches
# that allow it to build with MbedTLS 3, and fixes a couple of issues in the compliance test suite.
# These fixes allow the tests numbered 216, 248 and 249 to complete successfully.
#
# Once all the fixes are upstreamed, this fork should be replaced with an upstream commit/tag.
# - Tracked in issue #5145
#
# Web URL: https://github.com/bensze01/psa-arch-tests/tree/fixes-for-mbedtls-3
PSA_ARCH_TESTS_REPO = 'https://github.com/bensze01/psa-arch-tests.git'
PSA_ARCH_TESTS_REF = 'fix-pr-5736'
#pylint: disable=too-many-branches,too-many-statements
def main():
mbedtls_dir = os.getcwd()
if not os.path.exists('library/libmbedcrypto.a'):
subprocess.check_call(['make', '-C', 'library', 'libmbedcrypto.a'])
psa_arch_tests_dir = 'psa-arch-tests'
os.makedirs(psa_arch_tests_dir, exist_ok=True)
try:
os.chdir(psa_arch_tests_dir)
# Reuse existing local clone
subprocess.check_call(['git', 'init'])
subprocess.check_call(['git', 'fetch', PSA_ARCH_TESTS_REPO, PSA_ARCH_TESTS_REF])
subprocess.check_call(['git', 'checkout', 'FETCH_HEAD'])
build_dir = 'api-tests/build'
try:
shutil.rmtree(build_dir)
except FileNotFoundError:
pass
os.mkdir(build_dir)
os.chdir(build_dir)
#pylint: disable=bad-continuation
subprocess.check_call([
'cmake', '..',
'-GUnix Makefiles',
'-DTARGET=tgt_dev_apis_stdc',
'-DTOOLCHAIN=HOST_GCC',
'-DSUITE=CRYPTO',
'-DPSA_CRYPTO_LIB_FILENAME={}/library/libmbedcrypto.a'.format(mbedtls_dir),
'-DPSA_INCLUDE_PATHS={}/include'.format(mbedtls_dir)
])
subprocess.check_call(['cmake', '--build', '.'])
proc = subprocess.Popen(['./psa-arch-tests-crypto'],
bufsize=1, stdout=subprocess.PIPE, universal_newlines=True)
test_re = re.compile(
'^TEST: (?P<test_num>[0-9]*)|'
'^TEST RESULT: (?P<test_result>FAILED|PASSED)'
)
test = -1
unexpected_successes = set(EXPECTED_FAILURES)
expected_failures = []
unexpected_failures = []
for line in proc.stdout:
print(line, end='')
match = test_re.match(line)
if match is not None:
groupdict = match.groupdict()
test_num = groupdict['test_num']
if test_num is not None:
test = int(test_num)
elif groupdict['test_result'] == 'FAILED':
try:
unexpected_successes.remove(test)
expected_failures.append(test)
print('Expected failure, ignoring')
except KeyError:
unexpected_failures.append(test)
print('ERROR: Unexpected failure')
elif test in unexpected_successes:
print('ERROR: Unexpected success')
proc.wait()
print()
print('***** test_psa_compliance.py report ******')
print()
print('Expected failures:', ', '.join(str(i) for i in expected_failures))
print('Unexpected failures:', ', '.join(str(i) for i in unexpected_failures))
print('Unexpected successes:', ', '.join(str(i) for i in sorted(unexpected_successes)))
print()
if unexpected_successes or unexpected_failures:
if unexpected_successes:
print('Unexpected successes encountered.')
print('Please remove the corresponding tests from '
'EXPECTED_FAILURES in tests/scripts/compliance_test.py')
print()
print('FAILED')
return 1
else:
print('SUCCESS')
return 0
finally:
os.chdir(mbedtls_dir)
if __name__ == '__main__':
sys.exit(main())

View File

@ -0,0 +1,203 @@
#!/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.
"""
# Copyright The Mbed TLS Contributors
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import argparse
from collections import namedtuple
import os
import re
import subprocess
import sys
from typing import Iterable, List, Optional, Tuple
import scripts_path # pylint: disable=unused-import
from mbedtls_dev import c_build_helper
from mbedtls_dev.macro_collector import InputsForTest, PSAMacroEnumerator
from mbedtls_dev import typing_util
def gather_inputs(headers: Iterable[str],
test_suites: Iterable[str],
inputs_class=InputsForTest) -> PSAMacroEnumerator:
"""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.add_numerical_values()
inputs.gather_arguments()
return inputs
def run_c(type_word: str,
expressions: Iterable[str],
include_path: Optional[str] = None,
keep_c: bool = False) -> List[str]:
"""Generate and run a program to print out numerical values of C expressions."""
if type_word == 'status':
cast_to = 'long'
printf_format = '%ld'
else:
cast_to = 'unsigned long'
printf_format = '0x%08lx'
return c_build_helper.get_c_expression_values(
cast_to, printf_format,
expressions,
caller='test_psa_constant_names.py for {} values'.format(type_word),
file_label=type_word,
header='#include <psa/crypto.h>',
include_path=include_path,
keep_c=keep_c
)
NORMALIZE_STRIP_RE = re.compile(r'\s+')
def normalize(expr: str) -> str:
"""Normalize the C expression so as not to care about trivial differences.
Currently "trivial differences" means whitespace.
"""
return re.sub(NORMALIZE_STRIP_RE, '', expr)
ALG_TRUNCATED_TO_SELF_RE = \
re.compile(r'PSA_ALG_AEAD_WITH_SHORTENED_TAG\('
r'PSA_ALG_(?:CCM|CHACHA20_POLY1305|GCM)'
r', *16\)\Z')
def is_simplifiable(expr: str) -> bool:
"""Determine whether an expression is simplifiable.
Simplifiable expressions can't be output in their input form, since
the output will be the simple form. Therefore they must be excluded
from testing.
"""
if ALG_TRUNCATED_TO_SELF_RE.match(expr):
return True
return False
def collect_values(inputs: InputsForTest,
type_word: str,
include_path: Optional[str] = None,
keep_c: bool = False) -> Tuple[List[str], List[str]]:
"""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(expr
for expr in inputs.generate_expressions(names)
if not is_simplifiable(expr))
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) -> None:
self.options = options
self.count = 0
self.errors = [] #type: List[Tests.Error]
def run_one(self, inputs: InputsForTest, type_word: str) -> None:
"""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_bytes = subprocess.check_output([self.options.program,
type_word] + values)
output = output_bytes.decode('ascii')
outputs = output.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: InputsForTest) -> None:
"""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: typing_util.Writable) -> None:
"""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='Show tested values on stdout')
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:
sys.exit(1)
if __name__ == '__main__':
main()

View File

@ -0,0 +1,76 @@
# test_zeroize.gdb
#
# Copyright The Mbed TLS Contributors
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Purpose
#
# Run a test using the debugger to check that the mbedtls_platform_zeroize()
# function in platform_util.h is not being optimized out by the compiler. To do
# so, the script loads the test program at programs/test/zeroize.c and sets a
# breakpoint at the last return statement in main(). When the breakpoint is
# hit, the debugger manually checks the contents to be zeroized and checks that
# it is actually cleared.
#
# The mbedtls_platform_zeroize() test is debugger driven because there does not
# seem to be a mechanism to reliably check whether the zeroize calls are being
# eliminated by compiler optimizations from within the compiled program. The
# problem is that a compiler would typically remove what it considers to be
# "unnecessary" assignments as part of redundant code elimination. To identify
# such code, the compilar will create some form dependency graph between
# reads and writes to variables (among other situations). It will then use this
# data structure to remove redundant code that does not have an impact on the
# program's observable behavior. In the case of mbedtls_platform_zeroize(), an
# intelligent compiler could determine that this function clears a block of
# memory that is not accessed later in the program, so removing the call to
# mbedtls_platform_zeroize() does not have an observable behavior. However,
# inserting a test after a call to mbedtls_platform_zeroize() to check whether
# the block of memory was correctly zeroed would force the compiler to not
# eliminate the mbedtls_platform_zeroize() call. If this does not occur, then
# the compiler potentially has a bug.
#
# Note: This test requires that the test program is compiled with -g3.
set confirm off
file ./programs/test/zeroize
search GDB_BREAK_HERE
break $_
set args ./programs/test/zeroize.c
run
set $i = 0
set $len = sizeof(buf)
set $buf = buf
while $i < $len
if $buf[$i++] != 0
echo The buffer at was not zeroized\n
quit 1
end
end
echo The buffer was correctly zeroized\n
continue
if $_exitcode != 0
echo The program did not terminate correctly\n
quit 1
end
quit 0

View File

@ -0,0 +1,159 @@
#!/usr/bin/env python3
# translate_ciphers.py
#
# Copyright The Mbed TLS Contributors
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Translate ciphersuite names in Mbed TLS format to OpenSSL and GNUTLS
standards.
To test the translation functions run:
python3 -m unittest translate_cipher.py
"""
import re
import argparse
import unittest
class TestTranslateCiphers(unittest.TestCase):
"""
Ensure translate_ciphers.py translates and formats ciphersuite names
correctly
"""
def test_translate_all_cipher_names(self):
"""
Translate MbedTLS ciphersuite names to their OpenSSL and
GnuTLS counterpart. Use only a small subset of ciphers
that exercise each step of the translate functions
"""
ciphers = [
("TLS-ECDHE-ECDSA-WITH-NULL-SHA",
"+ECDHE-ECDSA:+NULL:+SHA1",
"ECDHE-ECDSA-NULL-SHA"),
("TLS-ECDHE-ECDSA-WITH-AES-128-GCM-SHA256",
"+ECDHE-ECDSA:+AES-128-GCM:+AEAD",
"ECDHE-ECDSA-AES128-GCM-SHA256"),
("TLS-DHE-RSA-WITH-3DES-EDE-CBC-SHA",
"+DHE-RSA:+3DES-CBC:+SHA1",
"EDH-RSA-DES-CBC3-SHA"),
("TLS-RSA-WITH-AES-256-CBC-SHA",
"+RSA:+AES-256-CBC:+SHA1",
"AES256-SHA"),
("TLS-PSK-WITH-3DES-EDE-CBC-SHA",
"+PSK:+3DES-CBC:+SHA1",
"PSK-3DES-EDE-CBC-SHA"),
("TLS-ECDHE-ECDSA-WITH-CHACHA20-POLY1305-SHA256",
None,
"ECDHE-ECDSA-CHACHA20-POLY1305"),
("TLS-ECDHE-ECDSA-WITH-AES-128-CCM",
"+ECDHE-ECDSA:+AES-128-CCM:+AEAD",
None),
("TLS-ECDHE-RSA-WITH-ARIA-256-GCM-SHA384",
None,
"ECDHE-ARIA256-GCM-SHA384"),
]
for m, g_exp, o_exp in ciphers:
if g_exp is not None:
g = translate_gnutls(m)
self.assertEqual(g, g_exp)
if o_exp is not None:
o = translate_ossl(m)
self.assertEqual(o, o_exp)
def translate_gnutls(m_cipher):
"""
Translate m_cipher from Mbed TLS ciphersuite naming convention
and return the GnuTLS naming convention
"""
m_cipher = re.sub(r'\ATLS-', '+', m_cipher)
m_cipher = m_cipher.replace("-WITH-", ":+")
m_cipher = m_cipher.replace("-EDE", "")
# SHA in Mbed TLS == SHA1 GnuTLS,
# if the last 3 chars are SHA append 1
if m_cipher[-3:] == "SHA":
m_cipher = m_cipher+"1"
# CCM or CCM-8 should be followed by ":+AEAD"
# Replace "GCM:+SHAxyz" with "GCM:+AEAD"
if "CCM" in m_cipher or "GCM" in m_cipher:
m_cipher = re.sub(r"GCM-SHA\d\d\d", "GCM", m_cipher)
m_cipher = m_cipher+":+AEAD"
# Replace the last "-" with ":+"
else:
index = m_cipher.rindex("-")
m_cipher = m_cipher[:index] + ":+" + m_cipher[index+1:]
return m_cipher
def translate_ossl(m_cipher):
"""
Translate m_cipher from Mbed TLS ciphersuite naming convention
and return the OpenSSL naming convention
"""
m_cipher = re.sub(r'^TLS-', '', m_cipher)
m_cipher = m_cipher.replace("-WITH", "")
# Remove the "-" from "ABC-xyz"
m_cipher = m_cipher.replace("AES-", "AES")
m_cipher = m_cipher.replace("CAMELLIA-", "CAMELLIA")
m_cipher = m_cipher.replace("ARIA-", "ARIA")
# Remove "RSA" if it is at the beginning
m_cipher = re.sub(r'^RSA-', r'', m_cipher)
# For all circumstances outside of PSK
if "PSK" not in m_cipher:
m_cipher = m_cipher.replace("-EDE", "")
m_cipher = m_cipher.replace("3DES-CBC", "DES-CBC3")
# Remove "CBC" if it is not prefixed by DES
m_cipher = re.sub(r'(?<!DES-)CBC-', r'', m_cipher)
# ECDHE-RSA-ARIA does not exist in OpenSSL
m_cipher = m_cipher.replace("ECDHE-RSA-ARIA", "ECDHE-ARIA")
# POLY1305 should not be followed by anything
if "POLY1305" in m_cipher:
index = m_cipher.rindex("POLY1305")
m_cipher = m_cipher[:index+8]
# If DES is being used, Replace DHE with EDH
if "DES" in m_cipher and "DHE" in m_cipher and "ECDHE" not in m_cipher:
m_cipher = m_cipher.replace("DHE", "EDH")
return m_cipher
def format_ciphersuite_names(mode, names):
t = {"g": translate_gnutls, "o": translate_ossl}[mode]
return " ".join(t(c) for c in names)
def main(target, names):
print(format_ciphersuite_names(target, names))
if __name__ == "__main__":
PARSER = argparse.ArgumentParser()
PARSER.add_argument('target', metavar='TARGET', choices=['o', 'g'])
PARSER.add_argument('names', metavar='NAMES', nargs='+')
ARGS = PARSER.parse_args()
main(ARGS.target, ARGS.names)

View File

@ -0,0 +1,47 @@
#!/bin/sh
# travis-log-failure.sh
#
# Copyright The Mbed TLS Contributors
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Purpose
#
# List the server and client logs on failed ssl-opt.sh and compat.sh tests.
# This script is used to make the logs show up in the Travis test results.
#
# Some of the logs can be very long: this means usually a couple of megabytes
# but it can be much more. For example, the client log of test 273 in ssl-opt.sh
# is more than 630 Megabytes long.
if [ -d include/mbedtls ]; then :; else
echo "$0: must be run from root" >&2
exit 1
fi
FILES="o-srv-*.log o-cli-*.log c-srv-*.log c-cli-*.log o-pxy-*.log"
MAX_LOG_SIZE=1048576
for PATTERN in $FILES; do
for LOG in $( ls tests/$PATTERN 2>/dev/null ); do
echo
echo "****** BEGIN file: $LOG ******"
echo
tail -c $MAX_LOG_SIZE $LOG
echo "****** END file: $LOG ******"
echo
rm $LOG
done
done