From 61f4c4c1178c9c6cf3614284b551f13485636517 Mon Sep 17 00:00:00 2001 From: Alex Chew Date: Thu, 27 May 2021 11:37:07 -0700 Subject: [PATCH] feat: Improvements to the message decryption process. (#707) See . Co-authored-by: Robin Salkeld --- CHANGELOG.md | 6 + CMakeLists.txt | 2 +- aws-encryption-sdk-cpp/CMakeLists.txt | 10 + .../integration/t_commitment_known_answer.cpp | 66 +---- .../integration/t_integration_kms_keyring.cpp | 65 +--- .../integration/t_max_encrypted_data_keys.cpp | 202 +++++++++++++ aws-encryption-sdk-cpp/tests/lib/logutils.h | 66 +++++ .../aws-encryption-sdk-test-vectors | 2 +- .../test_vectors/static_test_vectors.cpp | 194 ++++++++---- codebuild/bin/codebuild-test.sh | 2 +- examples/caching_cmm.cpp | 5 + examples/file_streaming.cpp | 38 ++- examples/kms_discovery.cpp | 32 +- examples/multi_keyring.cpp | 9 +- examples/raw_aes_keyring.c | 9 +- examples/raw_rsa_keyring.c | 9 +- examples/string.cpp | 29 +- include/aws/cryptosdk/error.h | 2 + include/aws/cryptosdk/private/header.h | 9 +- include/aws/cryptosdk/private/session.h | 9 + include/aws/cryptosdk/session.h | 74 ++++- source/default_cmm.c | 10 +- source/error.c | 4 + source/framefmt.c | 35 +-- source/header.c | 12 +- source/session.c | 96 +++++- source/session_decrypt.c | 14 +- source/session_encrypt.c | 9 +- tests/CMakeLists.txt | 1 + tests/unit/runner.c | 1 + tests/unit/t_encrypt.c | 209 +++++++++++++ tests/unit/t_header.c | 12 +- tests/unit/t_max_encrypted_data_keys.c | 280 ++++++++++++++++++ tests/unit/testing.h | 1 + .../aws_cryptosdk_enc_ctx_clone/Makefile | 3 + .../aws_cryptosdk_enc_ctx_serialize/Makefile | 3 + .../proofs/aws_cryptosdk_hdr_write/Makefile | 3 + ...ws_cryptosdk_priv_hdr_parse_edks_harness.c | 3 +- .../aws_cryptosdk_serialize_frame/Makefile | 9 +- .../aws_cryptosdk_serialize_frame_harness.c | 2 - 40 files changed, 1223 insertions(+), 324 deletions(-) create mode 100644 aws-encryption-sdk-cpp/tests/integration/t_max_encrypted_data_keys.cpp create mode 100644 aws-encryption-sdk-cpp/tests/lib/logutils.h create mode 100644 tests/unit/t_max_encrypted_data_keys.c diff --git a/CHANGELOG.md b/CHANGELOG.md index bcf783120..aa4d0c37b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 1.9.0 -- 2021-05-27 + +* Improvements to the message decryption process. + + See + ## 1.7.0 -- 2020-09-24 * Updates to the AWS Encryption SDK. 4ba5825 diff --git a/CMakeLists.txt b/CMakeLists.txt index 9a27262aa..f3e69330d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -50,7 +50,7 @@ set(PROJECT_NAME aws-encryption-sdk) # Version number of the SDK to be consumed by C code and Doxygen set(MAJOR 1) -set(MINOR 7) +set(MINOR 9) set(PATCH 0) # Compiler feature tests and feature flags diff --git a/aws-encryption-sdk-cpp/CMakeLists.txt b/aws-encryption-sdk-cpp/CMakeLists.txt index 44d5ba32b..42976df8f 100644 --- a/aws-encryption-sdk-cpp/CMakeLists.txt +++ b/aws-encryption-sdk-cpp/CMakeLists.txt @@ -84,6 +84,16 @@ if (AWS_ENC_SDK_END_TO_END_TESTS) ) set_target_properties(t_commitment_known_answer PROPERTIES CXX_STANDARD 11 C_STANDARD 99) aws_add_test(commitment_known_answer ${VALGRIND} ${CMAKE_CURRENT_BINARY_DIR}/t_commitment_known_answer ${TEST_DATA}/commitment_known_answer_tests.json) + + add_executable(t_max_encrypted_data_keys tests/integration/t_max_encrypted_data_keys.cpp) + target_link_libraries(t_max_encrypted_data_keys testlibcpp) + target_include_directories(t_max_encrypted_data_keys PUBLIC ${PROJECT_SOURCE_DIR}/tests/lib + ${PROJECT_SOURCE_DIR}/tests/unit + ${PROJECT_SOURCE_DIR}/tests/integration + $ + ) + set_target_properties(t_max_encrypted_data_keys PROPERTIES CXX_STANDARD 11 C_STANDARD 99) + aws_add_test(integration_max_edks ${VALGRIND} ${CMAKE_CURRENT_BINARY_DIR}/t_max_encrypted_data_keys) else() message(STATUS "End to end tests off") endif() diff --git a/aws-encryption-sdk-cpp/tests/integration/t_commitment_known_answer.cpp b/aws-encryption-sdk-cpp/tests/integration/t_commitment_known_answer.cpp index 77b63dab6..bbd315438 100644 --- a/aws-encryption-sdk-cpp/tests/integration/t_commitment_known_answer.cpp +++ b/aws-encryption-sdk-cpp/tests/integration/t_commitment_known_answer.cpp @@ -32,6 +32,7 @@ #include #include "edks_utils.h" +#include "logutils.h" #include "test_crypto.h" #include "testutil.h" @@ -49,67 +50,6 @@ const char *CLASS_CTAG = "Test KMS"; const char *KEY_ARN_STR1 = "arn:aws:kms:us-west-2:658956600833:key/b3537ef1-d8dc-4780-9f5a-55776cbb2f7f"; const char *KEY_ARN_STR1_REGION = Aws::Region::US_WEST_2; -/* - * These RAII-style logging classes will buffer log entries until .clear() is called on the LoggingRAII object. - * If a test fails, RUN_TEST will return from main without calling clear, and the destructor on LoggingRAII will dump - * the buffered log entries for the specific failed test to stderr before exiting. - */ -namespace { -class BufferedLogSystem : public Aws::Utils::Logging::FormattedLogSystem { - private: - std::mutex logMutex; - std::vector buffer; - - public: - void clear() { - std::lock_guard guard(logMutex); - - buffer.clear(); - } - - void dump() { - std::lock_guard guard(logMutex); - - for (auto &str : buffer) { - std::cerr << str; - } - } - - void Flush() {} - - BufferedLogSystem(Aws::Utils::Logging::LogLevel logLevel) : FormattedLogSystem(logLevel) {} - - protected: - // Overrides FormattedLogSystem pure virtual function - virtual void ProcessFormattedStatement(Aws::String &&statement) { - std::lock_guard guard(logMutex); - - buffer.push_back(std::move(statement)); - } -}; - -class LoggingRAII { - std::shared_ptr logSystem; - - public: - LoggingRAII() { - logSystem = Aws::MakeShared("LoggingRAII", Aws::Utils::Logging::LogLevel::Info); - - Aws::Utils::Logging::InitializeAWSLogging(logSystem); - } - - void clear() { - logSystem->clear(); - } - - ~LoggingRAII() { - Aws::Utils::Logging::ShutdownAWSLogging(); - - logSystem->dump(); - } -}; -} // namespace - Aws::String run_single_test(aws_cryptosdk_keyring *kr, const JsonView &test) { auto pt_frames_obj = test.GetObject("plaintext-frames"); bool have_pt_frames = pt_frames_obj.IsListType(); @@ -228,7 +168,7 @@ AWS_STRING_FROM_LITERAL(PROVIDER_NAME, "ProviderName"); AWS_STRING_FROM_LITERAL(KEY_ID, "KeyId"); static uint8_t ZERO_KEY[32] = { 0 }; -bool known_answer_tests(LoggingRAII &logging, const char *filename) { +bool known_answer_tests(Aws::Cryptosdk::Testing::LoggingRAII &logging, const char *filename) { std::fstream file(filename); JsonValue test_dataset(file); JsonView dataset_view = test_dataset.View(); @@ -276,7 +216,7 @@ int main(int argc, char **argv) { aws_common_library_init(aws_default_allocator()); aws_cryptosdk_load_error_strings(); - LoggingRAII logging; + Aws::Cryptosdk::Testing::LoggingRAII logging; SDKOptions options; Aws::InitAPI(options); diff --git a/aws-encryption-sdk-cpp/tests/integration/t_integration_kms_keyring.cpp b/aws-encryption-sdk-cpp/tests/integration/t_integration_kms_keyring.cpp index 419eeea4e..c40d58d58 100644 --- a/aws-encryption-sdk-cpp/tests/integration/t_integration_kms_keyring.cpp +++ b/aws-encryption-sdk-cpp/tests/integration/t_integration_kms_keyring.cpp @@ -1,6 +1,5 @@ /* * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * * Licensed under the Apache License, Version 2.0 (the "License"). You may not use * this file except in compliance with the License. A copy of the License is * located at @@ -21,6 +20,7 @@ #include #include "edks_utils.h" +#include "logutils.h" #include "test_crypto.h" #include "testutil.h" @@ -603,71 +603,10 @@ int dataKeyDecrypt_discoveryFilterPartitionMismatch_returnErr() { // todo add more tests for grantTokens -/* - * These RAII-style logging classes will buffer log entries until .clear() is called on the LoggingRAII object. - * If a test fails, RUN_TEST will return from main without calling clear, and the destructor on LoggingRAII will dump - * the buffered log entries for the specific failed test to stderr before exiting. - */ -namespace { -class BufferedLogSystem : public Aws::Utils::Logging::FormattedLogSystem { - private: - std::mutex logMutex; - std::vector buffer; - - public: - void clear() { - std::lock_guard guard(logMutex); - - buffer.clear(); - } - - void dump() { - std::lock_guard guard(logMutex); - - for (auto &str : buffer) { - std::cerr << str; - } - } - - void Flush() {} - - BufferedLogSystem(Aws::Utils::Logging::LogLevel logLevel) : FormattedLogSystem(logLevel) {} - - protected: - // Overrides FormattedLogSystem pure virtual function - virtual void ProcessFormattedStatement(Aws::String &&statement) { - std::lock_guard guard(logMutex); - - buffer.push_back(std::move(statement)); - } -}; - -class LoggingRAII { - std::shared_ptr logSystem; - - public: - LoggingRAII() { - logSystem = Aws::MakeShared("LoggingRAII", Aws::Utils::Logging::LogLevel::Trace); - - Aws::Utils::Logging::InitializeAWSLogging(logSystem); - } - - void clear() { - logSystem->clear(); - } - - ~LoggingRAII() { - Aws::Utils::Logging::ShutdownAWSLogging(); - - logSystem->dump(); - } -}; -} // namespace - int main() { aws_cryptosdk_load_error_strings(); - LoggingRAII logging; + Aws::Cryptosdk::Testing::LoggingRAII logging; SDKOptions options; Aws::InitAPI(options); diff --git a/aws-encryption-sdk-cpp/tests/integration/t_max_encrypted_data_keys.cpp b/aws-encryption-sdk-cpp/tests/integration/t_max_encrypted_data_keys.cpp new file mode 100644 index 000000000..a0c39b465 --- /dev/null +++ b/aws-encryption-sdk-cpp/tests/integration/t_max_encrypted_data_keys.cpp @@ -0,0 +1,202 @@ +/* + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use + * this file except in compliance with the License. A copy of the License is + * located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "edks_utils.h" +#include "logutils.h" +#include "test_crypto.h" +#include "testutil.h" + +#define BUFFER_SIZE (1 << 20) + +using namespace Aws::Cryptosdk; +using namespace Aws::Utils::Json; + +using Aws::SDKOptions; + +const char *CLASS_CTAG = "Test KMS"; + +/* This special test key has been configured to allow Encrypt, Decrypt, and GenerateDataKey operations from any + * AWS principal and should be used when adding new KMS tests. + * You should never use it in production! + */ +const char *KEY_ARN = "arn:aws:kms:us-west-2:658956600833:key/b3537ef1-d8dc-4780-9f5a-55776cbb2f7f"; +// const char *KEY_ARN_REGION = Aws::Region::US_WEST_2; + +static const Aws::String PT_BYTES = "foobar"; + +static uint8_t encrypt_output[BUFFER_SIZE]; +static uint8_t decrypt_output[BUFFER_SIZE]; + +static void setup_test() { + memset(encrypt_output, 0, sizeof(encrypt_output)); + memset(decrypt_output, 0, sizeof(decrypt_output)); +} + +static int do_encrypt(struct aws_cryptosdk_session *session, uint8_t *encrypt_output, size_t *output_len) { + size_t input_len = PT_BYTES.size(); + size_t input_consumed; + TEST_ASSERT_SUCCESS(aws_cryptosdk_session_set_message_size(session, input_len)); + int rv = aws_cryptosdk_session_process( + session, + encrypt_output, + BUFFER_SIZE, + output_len, + (const uint8_t *)PT_BYTES.c_str(), + input_len, + &input_consumed); + if (rv) return rv; + TEST_ASSERT_INT_EQ(input_len, input_consumed); + TEST_ASSERT(aws_cryptosdk_session_is_done(session)); + return 0; +} + +static int do_decrypt( + struct aws_cryptosdk_session *session, uint8_t *encrypt_output, size_t input_len, uint8_t *decrypt_output) { + size_t output_len; + size_t input_consumed; + int rv = aws_cryptosdk_session_process( + session, decrypt_output, BUFFER_SIZE, &output_len, encrypt_output, input_len, &input_consumed); + if (rv) return rv; + TEST_ASSERT_INT_EQ(input_len, input_consumed); + TEST_ASSERT(aws_cryptosdk_session_is_done(session)); + TEST_ASSERT_INT_EQ(0, PT_BYTES.compare((const char *)decrypt_output)); + return 0; +} + +static aws_cryptosdk_keyring *kms_keyring_with_n_edks(size_t num_edks) { + Aws::Vector extra_key_arns; + for (size_t edk_idx = 1; edk_idx < num_edks; ++edk_idx) { + extra_key_arns.push_back(KEY_ARN); + } + struct aws_cryptosdk_keyring *kr = Aws::Cryptosdk::KmsKeyring::Builder().Build(KEY_ARN, extra_key_arns); + if (!kr) abort(); + return kr; +} + +static int do_encrypt_with_n_edks(size_t num_edks) { + struct aws_cryptosdk_keyring *kr = kms_keyring_with_n_edks(num_edks); + struct aws_cryptosdk_session *session = + aws_cryptosdk_session_new_from_keyring_2(aws_default_allocator(), AWS_CRYPTOSDK_ENCRYPT, kr); + TEST_ASSERT_ADDR_NOT_NULL(session); + aws_cryptosdk_keyring_release(kr); + TEST_ASSERT_SUCCESS( + aws_cryptosdk_session_set_commitment_policy(session, COMMITMENT_POLICY_FORBID_ENCRYPT_ALLOW_DECRYPT)); + TEST_ASSERT_SUCCESS(aws_cryptosdk_session_set_max_encrypted_data_keys(session, 3)); + + size_t output_len; + int rv = do_encrypt(session, encrypt_output, &output_len); + aws_cryptosdk_session_destroy(session); + return rv; +} + +static int do_decrypt_with_n_edks(size_t num_edks) { + struct aws_cryptosdk_keyring *kr = kms_keyring_with_n_edks(num_edks); + struct aws_cryptosdk_session *session = + aws_cryptosdk_session_new_from_keyring_2(aws_default_allocator(), AWS_CRYPTOSDK_ENCRYPT, kr); + TEST_ASSERT_ADDR_NOT_NULL(session); + aws_cryptosdk_keyring_release(kr); + TEST_ASSERT_SUCCESS( + aws_cryptosdk_session_set_commitment_policy(session, COMMITMENT_POLICY_FORBID_ENCRYPT_ALLOW_DECRYPT)); + + size_t output_len; + TEST_ASSERT_SUCCESS(do_encrypt(session, encrypt_output, &output_len)); + TEST_ASSERT_SUCCESS(aws_cryptosdk_session_reset(session, AWS_CRYPTOSDK_DECRYPT)); + TEST_ASSERT_SUCCESS(aws_cryptosdk_session_set_max_encrypted_data_keys(session, 3)); + int rv = do_decrypt(session, encrypt_output, output_len, decrypt_output); + aws_cryptosdk_session_destroy(session); + return rv; +} + +static int encrypt_less_than_max_edks() { + setup_test(); + TEST_ASSERT_SUCCESS(do_encrypt_with_n_edks(2)); + return 0; +} + +static int encrypt_equal_to_max_edks() { + setup_test(); + TEST_ASSERT_SUCCESS(do_encrypt_with_n_edks(3)); + return 0; +} + +static int encrypt_more_than_max_edks() { + setup_test(); + TEST_ASSERT_ERROR(AWS_CRYPTOSDK_ERR_LIMIT_EXCEEDED, do_encrypt_with_n_edks(4)); + return 0; +} + +static int decrypt_less_than_max_edks() { + setup_test(); + TEST_ASSERT_SUCCESS(do_decrypt_with_n_edks(2)); + return 0; +} + +static int decrypt_equal_to_max_edks() { + setup_test(); + TEST_ASSERT_SUCCESS(do_decrypt_with_n_edks(3)); + return 0; +} + +static int decrypt_more_than_max_edks() { + setup_test(); + TEST_ASSERT_ERROR(AWS_CRYPTOSDK_ERR_LIMIT_EXCEEDED, do_decrypt_with_n_edks(4)); + return 0; +} + +static int run_tests() { + RUN_TEST(encrypt_less_than_max_edks()); + RUN_TEST(encrypt_equal_to_max_edks()); + RUN_TEST(encrypt_more_than_max_edks()); + RUN_TEST(decrypt_less_than_max_edks()); + RUN_TEST(decrypt_equal_to_max_edks()); + RUN_TEST(decrypt_more_than_max_edks()); + return 0; +} + +int main(int argc, char **argv) { + aws_common_library_init(aws_default_allocator()); + aws_cryptosdk_load_error_strings(); + + Aws::Cryptosdk::Testing::LoggingRAII logging; + + SDKOptions options; + Aws::InitAPI(options); + + int error = run_tests(); + if (!error) { + logging.clear(); + } + + Aws::ShutdownAPI(options); + + return error; +} diff --git a/aws-encryption-sdk-cpp/tests/lib/logutils.h b/aws-encryption-sdk-cpp/tests/lib/logutils.h new file mode 100644 index 000000000..ebc5adfb9 --- /dev/null +++ b/aws-encryption-sdk-cpp/tests/lib/logutils.h @@ -0,0 +1,66 @@ +/* + * These RAII-style logging classes will buffer log entries until .clear() is called on the LoggingRAII object. + * If a test fails, RUN_TEST will return from main without calling clear, and the destructor on LoggingRAII will dump + * the buffered log entries for the specific failed test to stderr before exiting. + */ +namespace Aws { +namespace Cryptosdk { +namespace Testing { + +class BufferedLogSystem : public Aws::Utils::Logging::FormattedLogSystem { + private: + std::mutex logMutex; + std::vector buffer; + + public: + void clear() { + std::lock_guard guard(logMutex); + + buffer.clear(); + } + + void dump() { + std::lock_guard guard(logMutex); + + for (auto &str : buffer) { + std::cerr << str; + } + } + + void Flush() {} + + BufferedLogSystem(Aws::Utils::Logging::LogLevel logLevel) : FormattedLogSystem(logLevel) {} + + protected: + // Overrides FormattedLogSystem pure virtual function + virtual void ProcessFormattedStatement(Aws::String &&statement) { + std::lock_guard guard(logMutex); + + buffer.push_back(std::move(statement)); + } +}; + +class LoggingRAII { + std::shared_ptr logSystem; + + public: + LoggingRAII() { + logSystem = Aws::MakeShared("LoggingRAII", Aws::Utils::Logging::LogLevel::Info); + + Aws::Utils::Logging::InitializeAWSLogging(logSystem); + } + + void clear() { + logSystem->clear(); + } + + ~LoggingRAII() { + Aws::Utils::Logging::ShutdownAWSLogging(); + + logSystem->dump(); + } +}; + +} // namespace Testing +} // namespace Cryptosdk +} // namespace Aws diff --git a/aws-encryption-sdk-cpp/tests/test_vectors/aws-encryption-sdk-test-vectors b/aws-encryption-sdk-cpp/tests/test_vectors/aws-encryption-sdk-test-vectors index a28851a18..3fa37419d 160000 --- a/aws-encryption-sdk-cpp/tests/test_vectors/aws-encryption-sdk-test-vectors +++ b/aws-encryption-sdk-cpp/tests/test_vectors/aws-encryption-sdk-test-vectors @@ -1 +1 @@ -Subproject commit a28851a188163e45b8cbf94c1d5a1e67e9622aa8 +Subproject commit 3fa37419d39db9dd12aee18c773bc6d55a86281c diff --git a/aws-encryption-sdk-cpp/tests/test_vectors/static_test_vectors.cpp b/aws-encryption-sdk-cpp/tests/test_vectors/static_test_vectors.cpp index 1957b3bf4..8cf28b645 100644 --- a/aws-encryption-sdk-cpp/tests/test_vectors/static_test_vectors.cpp +++ b/aws-encryption-sdk-cpp/tests/test_vectors/static_test_vectors.cpp @@ -32,7 +32,7 @@ #include "testing.h" #include "testutil.h" -#define MANIFEST_VERSION 1 +#define MANIFEST_VERSION 2 #define KEYS_MANIFEST_VERSION 3 using namespace Aws::Cryptosdk; @@ -45,6 +45,12 @@ enum test_type { AWS_CRYPTOSDK_KMS, }; +struct expected_outcome { + bool success; + std::string pt_filename; // only meaningful if success + std::string error_desc; // only meaningful if !success +}; + int passed, failed, encrypt_only, not_yet_supported, test_type_passed[3]; static int cmp_jsonstr_with_cstr(json_object *jso, const char *str) { @@ -121,10 +127,86 @@ static bool get_padding_mode( return true; } +static int run_test_decryption( + struct aws_cryptosdk_session *session, + std::string ct_filename, + uint8_t *ciphertext, + size_t ct_len, + uint8_t *plaintext, + size_t pt_len, + struct expected_outcome expected) { + uint8_t *output_buffer = (uint8_t *)malloc(pt_len); + if (!output_buffer) abort(); + size_t out_produced = 0; + if (!expected.success) { + // If the test case is expected to fail, populate the output buffer + // so we can assert it is zeroed out. + memset(output_buffer, 0xFF, pt_len); + } + + int result = AWS_OP_SUCCESS; + + int process_result = + aws_cryptosdk_session_process_full(session, output_buffer, pt_len, &out_produced, ciphertext, ct_len); + if (!expected.success) { + if (process_result == AWS_OP_SUCCESS) { + fprintf( + stderr, + "Unexpected success while processing aws_crytosdk_session. Expected error description: %s\n", + expected.error_desc.c_str()); + result = AWS_OP_ERR; + goto cleanup; + } + if (!aws_is_mem_zeroed(output_buffer, pt_len)) { + fprintf( + stderr, "Output not zeroed after expected decryption failure for test case %s\n", ct_filename.c_str()); + result = AWS_OP_ERR; + goto cleanup; + } + } else { + if (process_result != AWS_OP_SUCCESS) { + fprintf(stderr, "Error while processing aws_crytosdk_session, %s\n", aws_error_str(aws_last_error())); + result = AWS_OP_ERR; + goto cleanup; + } + + if (!aws_cryptosdk_session_is_done(session)) { + fprintf( + stderr, + "Error while processing aws_crytosdk_session, decryption not complete, %s\n", + aws_error_str(aws_last_error())); + result = AWS_OP_ERR; + goto cleanup; + } + + if (pt_len != out_produced) { + fprintf( + stderr, + "Wrong output size, PlainText length = %zu, Produced output length = %zu\n", + pt_len, + out_produced); + result = AWS_OP_ERR; + goto cleanup; + } + + if (memcmp(output_buffer, plaintext, pt_len)) { + fprintf(stderr, "Plaintext mismatch for test case %s\n", ct_filename.c_str()); + result = AWS_OP_ERR; + goto cleanup; + } + } + +cleanup: + if (output_buffer) free(output_buffer); + + return result; +} + static int process_test_scenarios( struct aws_allocator *alloc, - std::string pt_filename, + struct expected_outcome expected, std::string ct_filename, + enum aws_cryptosdk_mode mode, json_object *master_keys_obj, json_object *keys_obj) { json_object *key_type_obj = NULL; @@ -141,15 +223,12 @@ static int process_test_scenarios( struct aws_cryptosdk_keyring *kr = NULL; struct aws_cryptosdk_session *session = NULL; struct aws_cryptosdk_cmm *cmm = NULL; - uint8_t *output_buffer = NULL; uint8_t *ciphertext = NULL; uint8_t *plaintext = NULL; aws_string *key_namespace = NULL; aws_string *key_name = NULL; size_t ct_len = 0; size_t pt_len = 0; - size_t out_produced = 0; - size_t in_consumed = 0; enum test_type test_type_idx; json_object *json_obj_mk_obj = json_object_array_get_idx(master_keys_obj, j); @@ -305,7 +384,7 @@ static int process_test_scenarios( goto next_test_scenario; } - if (!(session = aws_cryptosdk_session_new_from_cmm(alloc, AWS_CRYPTOSDK_DECRYPT, cmm))) { + if (!(session = aws_cryptosdk_session_new_from_cmm(alloc, mode, cmm))) { failed++; fprintf(stderr, "Failed to initialize aws_cryptosdk_session, %s\n", aws_error_str(aws_last_error())); goto next_test_scenario; @@ -321,65 +400,27 @@ static int process_test_scenarios( goto next_test_scenario; } - if (test_loadfile(pt_filename.c_str(), &plaintext, &pt_len)) { - failed++; - fprintf( - stderr, "Failed to load plaintext file %s, %s\n", pt_filename.c_str(), aws_error_str(aws_last_error())); - goto next_test_scenario; - } - - output_buffer = (uint8_t *)malloc(pt_len); - if (!output_buffer) { - abort(); - } - - if (aws_cryptosdk_session_process( - session, output_buffer, pt_len, &out_produced, ciphertext, ct_len, &in_consumed) != AWS_OP_SUCCESS) { - failed++; - fprintf(stderr, "Error while processing aws_crytosdk_session, %s\n", aws_error_str(aws_last_error())); - goto next_test_scenario; - } - - if (!aws_cryptosdk_session_is_done(session)) { - failed++; - fprintf( - stderr, - "Error while processing aws_crytosdk_session, decryption not complete, %s\n", - aws_error_str(aws_last_error())); - goto next_test_scenario; - } - - if (in_consumed != ct_len) { + if (expected.success && test_loadfile(expected.pt_filename.c_str(), &plaintext, &pt_len)) { failed++; fprintf( stderr, - "Error while processing aws_crytosdk_session, entire input not consumed, %s\n", + "Failed to load plaintext file %s, %s\n", + expected.pt_filename.c_str(), aws_error_str(aws_last_error())); goto next_test_scenario; } - if (pt_len != out_produced) { + if (run_test_decryption(session, ct_filename, ciphertext, ct_len, plaintext, pt_len, expected)) { failed++; - fprintf( - stderr, - "Wrong output size, PlainText length = %zu, Produced output length = %zu\n", - pt_len, - out_produced); goto next_test_scenario; } - if (memcmp(output_buffer, plaintext, pt_len)) { - failed++; - fprintf(stderr, "Plaintext mismatch for test case %s\n", ct_filename.c_str()); - } else { - test_type_passed[test_type_idx]++; - passed++; - } + test_type_passed[test_type_idx]++; + passed++; next_test_scenario: if (key_namespace) aws_string_destroy(key_namespace); if (key_name) aws_string_destroy(key_name); - if (output_buffer) free(output_buffer); if (ciphertext) free(ciphertext); if (plaintext) free(plaintext); if (session) aws_cryptosdk_session_destroy(session); @@ -396,15 +437,21 @@ static int test_vector_runner(const char *path) { struct json_object *val; struct lh_entry *entry; struct aws_allocator *alloc = aws_default_allocator(); - - json_object *manifest_obj = NULL; - json_object *tests_obj = NULL; - json_object *keys_obj = NULL; - json_object *test_case_obj = NULL; - json_object *pt_filename_obj = NULL; - json_object *ct_filename_obj = NULL; - json_object *master_keys_obj = NULL; - json_object *keys_manifest_obj = NULL; + struct expected_outcome expected; + + json_object *manifest_obj = NULL; + json_object *tests_obj = NULL; + json_object *keys_obj = NULL; + json_object *test_case_obj = NULL; + json_object *result_obj = NULL; + json_object *result_output_obj = NULL; + json_object *result_error_obj = NULL; + json_object *pt_filename_obj = NULL; + json_object *err_desc_obj = NULL; + json_object *ct_filename_obj = NULL; + json_object *master_keys_obj = NULL; + json_object *keys_manifest_obj = NULL; + json_object *decryption_method_obj = NULL; if (snprintf(manifest_filename, sizeof(manifest_filename), "%s/manifest.json", path) >= sizeof(manifest_filename)) { fprintf(stderr, "Path too long\n"); @@ -433,17 +480,36 @@ static int test_vector_runner(const char *path) { (entry ? (key = (char *)entry->k, val = (struct json_object *)entry->v, entry) : 0); entry = entry->next) { TEST_ASSERT(json_object_object_get_ex(tests_obj, key, &test_case_obj)); - TEST_ASSERT(json_object_object_get_ex(val, "plaintext", &pt_filename_obj)); TEST_ASSERT(json_object_object_get_ex(val, "ciphertext", &ct_filename_obj)); + TEST_ASSERT(json_object_object_get_ex(val, "result", &result_obj)); + if (json_object_object_get_ex(result_obj, "output", &result_output_obj)) { + expected.success = true; + TEST_ASSERT(json_object_object_get_ex(result_output_obj, "plaintext", &pt_filename_obj)); + std::string pt_filename = json_object_get_string(pt_filename_obj); + pt_filename.replace(pt_filename.find(find_str), find_str.length(), path); + expected.pt_filename = pt_filename; + } else if (json_object_object_get_ex(result_obj, "error", &result_error_obj)) { + expected.success = false; + TEST_ASSERT(json_object_object_get_ex(result_error_obj, "error-description", &err_desc_obj)); + expected.error_desc = json_object_get_string(err_desc_obj); + } else { + fprintf(stderr, "Unrecognized result type\n"); + return AWS_OP_ERR; + } - std::string pt_filename = json_object_get_string(pt_filename_obj); std::string ct_filename = json_object_get_string(ct_filename_obj); - - pt_filename.replace(pt_filename.find(find_str), find_str.length(), path); ct_filename.replace(ct_filename.find(find_str), find_str.length(), path); + enum aws_cryptosdk_mode mode = AWS_CRYPTOSDK_DECRYPT; + if (json_object_object_get_ex(val, "decryption-method", &decryption_method_obj)) { + std::string decryption_method = json_object_get_string(decryption_method_obj); + if (decryption_method == "streaming-unsigned-only") { + mode = AWS_CRYPTOSDK_DECRYPT_UNSIGNED; + } + } + TEST_ASSERT(json_object_object_get_ex(val, "master-keys", &master_keys_obj)); - TEST_ASSERT_SUCCESS(process_test_scenarios(alloc, pt_filename, ct_filename, master_keys_obj, keys_obj)); + TEST_ASSERT_SUCCESS(process_test_scenarios(alloc, expected, ct_filename, mode, master_keys_obj, keys_obj)); } printf("Decryption successfully completed for %d test cases and failed for %d.\n", passed, failed); printf( diff --git a/codebuild/bin/codebuild-test.sh b/codebuild/bin/codebuild-test.sh index fc12470a7..4e1502262 100755 --- a/codebuild/bin/codebuild-test.sh +++ b/codebuild/bin/codebuild-test.sh @@ -54,7 +54,7 @@ run_test() { #TODO: EC2 metadata service fails; fix an re-enable end2end tests. cmake -DBUILD_AWS_ENC_SDK_CPP=ON -DAWS_ENC_SDK_END_TO_END_TESTS=${E2E} \ -DAWS_ENC_SDK_KNOWN_GOOD_TESTS=ON \ - -DAWS_ENC_SDK_TEST_VECTORS_ZIP="$ROOT/aws-encryption-sdk-cpp/tests/test_vectors/aws-encryption-sdk-test-vectors/vectors/awses-decrypt/python-1.3.8.zip" \ + -DAWS_ENC_SDK_TEST_VECTORS_ZIP="$ROOT/aws-encryption-sdk-cpp/tests/test_vectors/aws-encryption-sdk-test-vectors/vectors/awses-decrypt/python-2.2.0.zip" \ -DAWS_ENC_SDK_END_TO_END_EXAMPLES=${E2E} \ -DCMAKE_C_FLAGS="$CFLAGS" \ -DCMAKE_CXX_FLAGS="$CXXFLAGS" \ diff --git a/examples/caching_cmm.cpp b/examples/caching_cmm.cpp index 9608efc1b..b5a90eaf4 100644 --- a/examples/caching_cmm.cpp +++ b/examples/caching_cmm.cpp @@ -34,6 +34,11 @@ void error(const char *description) { abort(); } +/** + * Note that this method buffers all output locally, and only returns it to the caller after all processing + * completes. This ensures that when decrypting a signed message the signature will be verified before any + * plaintext can be processed. + */ std::vector process_loop(struct aws_cryptosdk_session *session, const uint8_t *input, size_t in_len) { size_t out_needed = 1; size_t out_offset = 0, in_offset = 0; diff --git a/examples/file_streaming.cpp b/examples/file_streaming.cpp index 890808284..2b6a9ae13 100644 --- a/examples/file_streaming.cpp +++ b/examples/file_streaming.cpp @@ -20,6 +20,7 @@ #include #include +#include #include #include @@ -52,18 +53,41 @@ static int process_file( /* Initialize a KMS keyring using the provided ARN. */ auto kms_keyring = Aws::Cryptosdk::KmsKeyring::Builder().Build(key_arn); + /* To set an alternate algorithm suite, we must explicitly create a Crypto Materials Manager. + */ + struct aws_cryptosdk_cmm *cmm = aws_cryptosdk_default_cmm_new(aws_default_allocator(), kms_keyring); + + /* The CMM has a reference to the keyring now. We release our reference so that + * the keyring will be destroyed when the cMM is. If CMM failed to allocate, + * this causes an immediate deallocation of the keyring and prevents a memory leak + * when this function exits. + */ + aws_cryptosdk_keyring_release(kms_keyring); + + /* Select the ALG_AES256_GCM_HKDF_SHA512_COMMIT_KEY algorithm. + * We can safely use an unsigned algorithm suite in this case under + */ + if (mode == AWS_CRYPTOSDK_ENCRYPT) { + if (AWS_OP_SUCCESS != aws_cryptosdk_default_cmm_set_alg_id(cmm, ALG_AES256_GCM_HKDF_SHA512_COMMIT_KEY)) { + abort(); + } + } + /* Initialize the session object. */ - struct aws_cryptosdk_session *session = aws_cryptosdk_session_new_from_keyring_2(allocator, mode, kms_keyring); - if (!session) abort(); + struct aws_cryptosdk_session *session = aws_cryptosdk_session_new_from_cmm_2(allocator, mode, cmm); + + /* Similarly to the keyring, the session now has a reference to the CMM, which we can now release. */ + aws_cryptosdk_cmm_release(cmm); + + if (!session) { + abort(); + } if (aws_cryptosdk_session_set_commitment_policy(session, COMMITMENT_POLICY_FORBID_ENCRYPT_ALLOW_DECRYPT)) { fprintf(stderr, "set_commitment_policy failed: %s", aws_error_debug_str(aws_last_error())); abort(); } - /* Since the session now holds a reference to the keyring, we can release the local reference. */ - aws_cryptosdk_keyring_release(kms_keyring); - /* Allocate buffers for input and output. Note that the initial size is not critical, as we will resize * and reallocate if more space is needed to make progress. */ @@ -202,7 +226,9 @@ int main(int argc, char *argv[]) { int ret = process_file(encrypted_filename, input_filename, AWS_CRYPTOSDK_ENCRYPT, key_arn, allocator); if (!ret) { - ret = process_file(decrypted_filename, encrypted_filename, AWS_CRYPTOSDK_DECRYPT, key_arn, allocator); + // Encryption uses an unsigned algorithm suite (see above), so we can use the recommended + // AWS_CRYPTOSDK_DECRYPT_UNSIGNED mode here. + ret = process_file(decrypted_filename, encrypted_filename, AWS_CRYPTOSDK_DECRYPT_UNSIGNED, key_arn, allocator); } Aws::ShutdownAPI(options); diff --git a/examples/kms_discovery.cpp b/examples/kms_discovery.cpp index a401ea1ad..aac54150b 100644 --- a/examples/kms_discovery.cpp +++ b/examples/kms_discovery.cpp @@ -55,23 +55,11 @@ void encrypt_string( abort(); } - if (AWS_OP_SUCCESS != aws_cryptosdk_session_set_message_size(session, in_plaintext_len)) { + if (AWS_OP_SUCCESS != + aws_cryptosdk_session_process_full( + session, out_ciphertext, out_ciphertext_buf_sz, out_ciphertext_len, in_plaintext, in_plaintext_len)) { abort(); } - - size_t in_plaintext_consumed; - if (AWS_OP_SUCCESS != aws_cryptosdk_session_process( - session, - out_ciphertext, - out_ciphertext_buf_sz, - out_ciphertext_len, - in_plaintext, - in_plaintext_len, - &in_plaintext_consumed)) { - abort(); - } - if (!aws_cryptosdk_session_is_done(session)) abort(); - if (in_plaintext_consumed != in_plaintext_len) abort(); aws_cryptosdk_session_destroy(session); } @@ -92,19 +80,11 @@ void decrypt_string( abort(); } - size_t in_ciphertext_consumed; - if (AWS_OP_SUCCESS != aws_cryptosdk_session_process( - session, - out_plaintext, - out_plaintext_buf_sz, - out_plaintext_len, - in_ciphertext, - in_ciphertext_len, - &in_ciphertext_consumed)) { + if (AWS_OP_SUCCESS != + aws_cryptosdk_session_process_full( + session, out_plaintext, out_plaintext_buf_sz, out_plaintext_len, in_ciphertext, in_ciphertext_len)) { abort(); } - if (!aws_cryptosdk_session_is_done(session)) abort(); - if (in_ciphertext_consumed != in_ciphertext_len) abort(); aws_cryptosdk_session_destroy(session); } diff --git a/examples/multi_keyring.cpp b/examples/multi_keyring.cpp index 371cd7a87..833667299 100644 --- a/examples/multi_keyring.cpp +++ b/examples/multi_keyring.cpp @@ -37,16 +37,9 @@ void encrypt_or_decrypt( abort(); } - if (mode == AWS_CRYPTOSDK_ENCRYPT) { - if (AWS_OP_SUCCESS != aws_cryptosdk_session_set_message_size(session, input_len)) abort(); - } - - size_t input_consumed; if (AWS_OP_SUCCESS != - aws_cryptosdk_session_process(session, output, output_buf_sz, output_len, input, input_len, &input_consumed)) + aws_cryptosdk_session_process_full(session, output, output_buf_sz, output_len, input, input_len)) abort(); - if (!aws_cryptosdk_session_is_done(session)) abort(); - if (input_consumed != input_len) abort(); /* This destroys the session but not the keyring, since the keyring pointer was not released. */ aws_cryptosdk_session_destroy(session); diff --git a/examples/raw_aes_keyring.c b/examples/raw_aes_keyring.c index a576d0f20..1290e10a0 100644 --- a/examples/raw_aes_keyring.c +++ b/examples/raw_aes_keyring.c @@ -38,16 +38,9 @@ void encrypt_or_decrypt_with_keyring( abort(); } - if (mode == AWS_CRYPTOSDK_ENCRYPT) { - if (AWS_OP_SUCCESS != aws_cryptosdk_session_set_message_size(session, input_len)) abort(); - } - - size_t input_consumed; if (AWS_OP_SUCCESS != - aws_cryptosdk_session_process(session, output, output_buf_sz, output_len, input, input_len, &input_consumed)) + aws_cryptosdk_session_process_full(session, output, output_buf_sz, output_len, input, input_len)) abort(); - if (!aws_cryptosdk_session_is_done(session)) abort(); - if (input_consumed != input_len) abort(); aws_cryptosdk_session_destroy(session); } diff --git a/examples/raw_rsa_keyring.c b/examples/raw_rsa_keyring.c index 201b81f83..081b03daf 100644 --- a/examples/raw_rsa_keyring.c +++ b/examples/raw_rsa_keyring.c @@ -38,16 +38,9 @@ void encrypt_or_decrypt_with_keyring( abort(); } - if (mode == AWS_CRYPTOSDK_ENCRYPT) { - if (AWS_OP_SUCCESS != aws_cryptosdk_session_set_message_size(session, input_len)) abort(); - } - - size_t input_consumed; if (AWS_OP_SUCCESS != - aws_cryptosdk_session_process(session, output, output_buf_sz, output_len, input, input_len, &input_consumed)) + aws_cryptosdk_session_process_full(session, output, output_buf_sz, output_len, input, input_len)) abort(); - if (!aws_cryptosdk_session_is_done(session)) abort(); - if (input_consumed != input_len) abort(); aws_cryptosdk_session_destroy(session); } diff --git a/examples/string.cpp b/examples/string.cpp index da13724c4..e277d380c 100644 --- a/examples/string.cpp +++ b/examples/string.cpp @@ -51,11 +51,6 @@ int encrypt_string( return AWS_OP_ERR; } - if (AWS_OP_SUCCESS != aws_cryptosdk_session_set_message_size(session, plaintext_len)) { - aws_cryptosdk_session_destroy(session); - return AWS_OP_ERR; - } - /* The encryption context is an AWS hash table where both the key and value * types are AWS strings. Both AWS hash tables and AWS strings are defined in * the aws-c-common library. @@ -74,19 +69,11 @@ int encrypt_string( } /* We encrypt the data. */ - size_t plaintext_consumed; - if (AWS_OP_SUCCESS != - aws_cryptosdk_session_process( - session, ciphertext, ciphertext_buf_sz, ciphertext_len, plaintext, plaintext_len, &plaintext_consumed)) { - aws_cryptosdk_session_destroy(session); - return AWS_OP_ERR; - } - - if (!aws_cryptosdk_session_is_done(session)) { + if (AWS_OP_SUCCESS != aws_cryptosdk_session_process_full( + session, ciphertext, ciphertext_buf_sz, ciphertext_len, plaintext, plaintext_len)) { aws_cryptosdk_session_destroy(session); return AWS_OP_ERR; } - if (plaintext_consumed != plaintext_len) abort(); /* This call deallocates all of the memory allocated in this function, including * the keyring, since we already released its pointer. @@ -123,19 +110,11 @@ int decrypt_string_and_verify_encryption_context( return AWS_OP_ERR; } - size_t ciphertext_consumed; - if (AWS_OP_SUCCESS != - aws_cryptosdk_session_process( - session, plaintext, plaintext_buf_sz, plaintext_len, ciphertext, ciphertext_len, &ciphertext_consumed)) { - aws_cryptosdk_session_destroy(session); - return AWS_OP_ERR; - } - - if (!aws_cryptosdk_session_is_done(session)) { + if (AWS_OP_SUCCESS != aws_cryptosdk_session_process_full( + session, plaintext, plaintext_buf_sz, plaintext_len, ciphertext, ciphertext_len)) { aws_cryptosdk_session_destroy(session); return AWS_OP_ERR; } - if (ciphertext_consumed != ciphertext_len) abort(); /* The encryption context is stored in plaintext in the encrypted message, and the * AWS Encryption SDK detects it and uses it for decryption, so there is no need to diff --git a/include/aws/cryptosdk/error.h b/include/aws/cryptosdk/error.h index 15d3a9978..2ab9461f9 100644 --- a/include/aws/cryptosdk/error.h +++ b/include/aws/cryptosdk/error.h @@ -51,6 +51,8 @@ enum aws_cryptosdk_err { AWS_CRYPTOSDK_ERR_UNSUPPORTED_FORMAT, /** Attempted encrypt/decrypt that would violate the configured key commitment policy */ AWS_CRYPTOSDK_ERR_COMMITMENT_POLICY_VIOLATION, + /** Attempted to decrypt a signed message when configured to disallow doing so */ + AWS_CRYPTOSDK_ERR_DECRYPT_SIGNED_MESSAGE_NOT_ALLOWED, AWS_CRYPTOSDK_ERR_END_RANGE = 0x2400 }; diff --git a/include/aws/cryptosdk/private/header.h b/include/aws/cryptosdk/private/header.h index 1daf29fb9..dc39e138b 100644 --- a/include/aws/cryptosdk/private/header.h +++ b/include/aws/cryptosdk/private/header.h @@ -74,8 +74,12 @@ void aws_cryptosdk_hdr_clear(struct aws_cryptosdk_hdr *hdr); * * This function will clear the header before parsing, and will leave the header in a cleared * state on failure. + * + * Raises AWS_CRYPTOSDK_ERR_LIMIT_EXCEEDED if the EDK count is greater than max_encrypted_data_keys + * (and max_encrypted_data_keys is nonzero). */ -int aws_cryptosdk_hdr_parse(struct aws_cryptosdk_hdr *hdr, struct aws_byte_cursor *cursor); +int aws_cryptosdk_hdr_parse( + struct aws_cryptosdk_hdr *hdr, struct aws_byte_cursor *cursor, size_t max_encrypted_data_keys); /** * Parses the header version from the cursor into *header_version. @@ -116,7 +120,8 @@ int aws_cryptosdk_priv_hdr_parse_aad(struct aws_cryptosdk_hdr *hdr, struct aws_b * Parses the EDK count and EDKs' raw data from the cursor, deserializing the * raw data into hdr->edk_list. */ -int aws_cryptosdk_priv_hdr_parse_edks(struct aws_cryptosdk_hdr *hdr, struct aws_byte_cursor *cur); +int aws_cryptosdk_priv_hdr_parse_edks( + struct aws_cryptosdk_hdr *hdr, struct aws_byte_cursor *cur, size_t max_encrypted_data_keys); /** * Parses the content type from the cursor into *content_type. diff --git a/include/aws/cryptosdk/private/session.h b/include/aws/cryptosdk/private/session.h index 66fc8f3d5..94d3f4069 100644 --- a/include/aws/cryptosdk/private/session.h +++ b/include/aws/cryptosdk/private/session.h @@ -93,6 +93,9 @@ struct aws_cryptosdk_session { * of keyring trace and--in the case of decryption--the encryption context. */ bool cmm_success; + + /* Max allowed encrypted data keys, 0 for no limit */ + size_t max_encrypted_data_keys; }; AWS_CRYPTOSDK_STATIC_INLINE bool aws_cryptosdk_session_is_valid(const struct aws_cryptosdk_session *session) { @@ -147,4 +150,10 @@ bool aws_cryptosdk_priv_algorithm_allowed_for_encrypt( extern bool unit_test_only_allow_encrypt_with_commitment; #endif +/* Helpers */ + +/** Returns 1 if the given mode is a valid mode, or 0 otherwise. */ +bool aws_cryptosdk_priv_is_valid_mode(enum aws_cryptosdk_mode mode); +/** Returns 1 if the given mode is a decrypting mode, or 0 otherwise. */ +bool aws_cryptosdk_priv_is_decrypt_mode(enum aws_cryptosdk_mode mode); #endif diff --git a/include/aws/cryptosdk/session.h b/include/aws/cryptosdk/session.h index 991fdf603..e5ed358de 100644 --- a/include/aws/cryptosdk/session.h +++ b/include/aws/cryptosdk/session.h @@ -24,7 +24,17 @@ * To encrypt or decrypt data, configure your CMM, create a session object, * and process your plaintext or ciphertext through this session object. * - * Typically using a session object will proceed through the following phases: + * If encrypting or decrypting data all at once (i.e. with the entire + * plaintext/ciphertext in a single buffer), using a session object will + * proceed through the following phases: + * + * 1. Create and configure a session object (or reuse an existing session that you have + * reset and configured) + * 2. Invoke @ref aws_cryptosdk_session_process_full providing all of the input + * plaintext or ciphertext, to produce the output data + * + * If encrypting or decrypting streamed input, using a session object will + * instead proceed through the following phases: * * 1. Create and configure a session object (or reuse an existing session that you have * reset and configured) @@ -54,7 +64,21 @@ extern "C" { struct aws_cryptosdk_session; -enum aws_cryptosdk_mode { AWS_CRYPTOSDK_ENCRYPT = 0x9000, AWS_CRYPTOSDK_DECRYPT = 0x9001 }; +/** + * Note that the current signed message format requires reading the full encrypted message before + * the signature can be verified, so it is important to avoid processing any plaintext + * until that point. + * + * AWS_CRYPTOSDK_DECRYPT_UNSIGNED is recommended if you only need to process unsigned messages, + * and calling aws_cryptosdk_session_process_full is recommended instead of calling + * aws_cryptosdk_session_process directly if you are able to provide the complete encrypted message + * in a single buffer. + */ +enum aws_cryptosdk_mode { + AWS_CRYPTOSDK_ENCRYPT = 0x9000, + AWS_CRYPTOSDK_DECRYPT = 0x9001, + AWS_CRYPTOSDK_DECRYPT_UNSIGNED = 0x9002 +}; /** * Creates a new encryption or decryption session from an underlying keyring. @@ -89,8 +113,8 @@ struct aws_cryptosdk_session *aws_cryptosdk_session_new_from_keyring( * * @param allocator The allocator to use for the session object and any temporary * data allocated for the session - * @param mode The mode (AWS_CRYPTOSDK_ENCRYPT or AWS_CRYPTOSDK_DECRYPT) to start - * in. This can be changed later with @ref aws_cryptosdk_session_reset + * @param mode The aws_cryptosdk_mode to start in. This can be changed later + * with @ref aws_cryptosdk_session_reset * @param keyring The keyring which will encrypt or decrypt data keys for this session. * This function uses a default CMM to link the session and keyring. */ @@ -131,8 +155,8 @@ struct aws_cryptosdk_session *aws_cryptosdk_session_new_from_cmm( * * @param allocator The allocator to use for the session object and any temporary * data allocated for the session - * @param mode The mode (AWS_CRYPTOSDK_ENCRYPT or AWS_CRYPTOSDK_DECRYPT) to start - * in. This can be changed later with @ref aws_cryptosdk_session_reset + * @param mode The aws_cryptosdk_mode to start in. This can be changed later + * with @ref aws_cryptosdk_session_reset * @param cmm The crypto material manager which will provide key material for this * session. */ @@ -147,8 +171,8 @@ void aws_cryptosdk_session_destroy(struct aws_cryptosdk_session *session); /** * Resets the session, preparing it for a new message. This function can also change * a session from encrypt to decrypt, or vice versa. After reset, the currently - * configured allocator, CMM, key commitment policy, and frame size to use for - * encryption are preserved. + * configured allocator, CMM, key commitment policy, max encrypted data keys, and + * frame size to use for encryption are preserved. * * @param session The session to reset * @param mode The new mode of the session @@ -215,6 +239,14 @@ AWS_CRYPTOSDK_API int aws_cryptosdk_session_set_commitment_policy( struct aws_cryptosdk_session *session, enum aws_cryptosdk_commitment_policy commitment_policy); +/** + * Sets the maximum number of encrypted data keys allowed during encryption and + * decryption. This must be set before encryption or decryption. + */ +AWS_CRYPTOSDK_API +int aws_cryptosdk_session_set_max_encrypted_data_keys( + struct aws_cryptosdk_session *session, size_t max_encrypted_data_keys); + /** * Attempts to process some data through the cryptosdk session. * This method may do any combination of @@ -249,6 +281,32 @@ int aws_cryptosdk_session_process( size_t inlen, size_t *in_bytes_read); +/** + * Attempts to process an entire message through the cryptosdk session. The + * session must not have processed any data (e.g. using + * `aws_cryptosdk_session_process`) or be in an error state. + * + * The data referenced by the input and output cursors must not overlap. + * If this method raises an error, the contents of the output buffer will + * be zeroed. The buffer referenced by the input buffer will never be modified. + * If there is insufficient output space and/or insufficient input data, this + * method raises an error. + * + * Upon return, *out_bytes_written will report the number of bytes written to + * the output buffer. + * + * This method will return successfully if it processed an entire message and + * did not enter an error state. + */ +AWS_CRYPTOSDK_API +int aws_cryptosdk_session_process_full( + struct aws_cryptosdk_session *session, + uint8_t *outp, + size_t outlen, + size_t *out_bytes_written, + const uint8_t *inp, + size_t inlen); + /** * Returns true if the session has finished processing the entire message. * diff --git a/source/default_cmm.c b/source/default_cmm.c index fc3cdead0..29b08f618 100644 --- a/source/default_cmm.c +++ b/source/default_cmm.c @@ -119,10 +119,10 @@ static int default_cmm_decrypt_materials( } const struct aws_cryptosdk_alg_properties *props = aws_cryptosdk_alg_props(request->alg); + struct aws_hash_element *pElement = NULL; + aws_hash_table_find(request->enc_ctx, EC_PUBLIC_KEY_FIELD, &pElement); if (props->signature_len) { - struct aws_hash_element *pElement = NULL; - - if (aws_hash_table_find(request->enc_ctx, EC_PUBLIC_KEY_FIELD, &pElement) || !pElement || !pElement->key) { + if (!pElement || !pElement->key) { aws_raise_error(AWS_CRYPTOSDK_ERR_BAD_CIPHERTEXT); goto err; } @@ -130,6 +130,10 @@ static int default_cmm_decrypt_materials( if (aws_cryptosdk_sig_verify_start(&dec_mat->signctx, request->alloc, pElement->value, props)) { goto err; } + } else if (pElement) { + // If alg suite is unsigned, enc_ctx must not contain EC_PUBLIC_KEY_FIELD + aws_raise_error(AWS_CRYPTOSDK_ERR_BAD_CIPHERTEXT); + goto err; } *output = dec_mat; diff --git a/source/error.c b/source/error.c index 868284661..4547e05f8 100644 --- a/source/error.c +++ b/source/error.c @@ -29,6 +29,10 @@ static const struct aws_error_info error_info[] = { "Configuration conflict: Cannot encrypt or decrypt because the algorithm suite is forbidden under the " "configured key commitment policy. See: " "https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/troubleshooting-migration.html", + "cryptosdk"), + AWS_DEFINE_ERROR_INFO( + AWS_CRYPTOSDK_ERR_DECRYPT_SIGNED_MESSAGE_NOT_ALLOWED, + "Not allowed to decrypt signed message in AWS_CRYPTOSDK_DECRYPT_UNSIGNED mode", "cryptosdk") }; diff --git a/source/framefmt.c b/source/framefmt.c index 2481c1d7b..b33093d9b 100644 --- a/source/framefmt.c +++ b/source/framefmt.c @@ -106,23 +106,24 @@ struct aws_cryptosdk_framestate { * the cursor at 'cursorptr' to refer to a block of 'size' bytes within the ciphertext * buffer. */ -#define field_sized(state, bufptr, size) \ - do { \ - (state)->ciphertext_size += (size); \ - memset((bufptr), 0, sizeof(*(bufptr))); \ - if ((state)->writing) { \ - if ((state)->u.buffer.capacity - (state)->u.buffer.len >= (size)) { \ - (bufptr)->buffer = (state)->u.buffer.buffer + (state)->u.buffer.len; \ - (bufptr)->len = 0; \ - (bufptr)->capacity = (size); \ - (state)->u.buffer.len += (size); \ - } \ - } else { \ - struct aws_byte_cursor cursor = aws_byte_cursor_advance(&(state)->u.cursor, (size_t)(size)); \ - (bufptr)->buffer = cursor.ptr; \ - (bufptr)->len = (bufptr)->capacity = cursor.len; \ - } \ - (state)->too_small = (state)->too_small || !(bufptr)->buffer; \ +#define field_sized(state, bufptr, size) \ + do { \ + (state)->ciphertext_size += (size); \ + memset((bufptr), 0, sizeof(*(bufptr))); \ + if ((state)->writing) { \ + if ((state)->u.buffer.capacity - (state)->u.buffer.len >= (size)) { \ + (bufptr)->buffer = \ + ((state)->u.buffer.buffer) ? ((state)->u.buffer.buffer + (state)->u.buffer.len) : NULL; \ + (bufptr)->len = 0; \ + (bufptr)->capacity = (size); \ + (state)->u.buffer.len += (size); \ + } \ + } else { \ + struct aws_byte_cursor cursor = aws_byte_cursor_advance(&(state)->u.cursor, (size_t)(size)); \ + (bufptr)->buffer = cursor.ptr; \ + (bufptr)->len = (bufptr)->capacity = cursor.len; \ + } \ + (state)->too_small = (state)->too_small || !(bufptr)->buffer; \ } while (0) static int serde_last_frame( diff --git a/source/header.c b/source/header.c index 994bb3041..e46df920c 100644 --- a/source/header.c +++ b/source/header.c @@ -186,7 +186,8 @@ static inline int parse_edk( return AWS_OP_ERR; } -int aws_cryptosdk_hdr_parse(struct aws_cryptosdk_hdr *hdr, struct aws_byte_cursor *pcursor) { +int aws_cryptosdk_hdr_parse( + struct aws_cryptosdk_hdr *hdr, struct aws_byte_cursor *pcursor, size_t max_encrypted_data_keys) { struct aws_byte_cursor cur = *pcursor; int field_err; @@ -204,7 +205,7 @@ int aws_cryptosdk_hdr_parse(struct aws_cryptosdk_hdr *hdr, struct aws_byte_curso if ((field_err = aws_cryptosdk_priv_hdr_parse_alg_id(hdr, &alg_props, header_version, &cur))) return field_err; if ((field_err = aws_cryptosdk_priv_hdr_parse_message_id(hdr, alg_props, &cur))) return field_err; if ((field_err = aws_cryptosdk_priv_hdr_parse_aad(hdr, &cur))) return field_err; - if ((field_err = aws_cryptosdk_priv_hdr_parse_edks(hdr, &cur))) return field_err; + if ((field_err = aws_cryptosdk_priv_hdr_parse_edks(hdr, &cur, max_encrypted_data_keys))) return field_err; if ((field_err = aws_cryptosdk_priv_hdr_parse_content_type(hdr, &content_type, &cur))) return field_err; if (header_version == AWS_CRYPTOSDK_HEADER_VERSION_1_0) { if ((field_err = aws_cryptosdk_priv_hdr_parse_reserved(hdr, &cur))) return field_err; @@ -297,12 +298,17 @@ int aws_cryptosdk_priv_hdr_parse_aad(struct aws_cryptosdk_hdr *hdr, struct aws_b return AWS_OP_SUCCESS; } -int aws_cryptosdk_priv_hdr_parse_edks(struct aws_cryptosdk_hdr *hdr, struct aws_byte_cursor *cur) { +int aws_cryptosdk_priv_hdr_parse_edks( + struct aws_cryptosdk_hdr *hdr, struct aws_byte_cursor *cur, size_t max_encrypted_data_keys) { AWS_PRECONDITION(aws_cryptosdk_hdr_is_valid(hdr)); AWS_PRECONDITION(aws_byte_cursor_is_valid(cur)); uint16_t edk_count; if (!aws_byte_cursor_read_be16(cur, &edk_count)) return aws_cryptosdk_priv_hdr_parse_err_short_buf(hdr); if (!edk_count) return aws_cryptosdk_priv_hdr_parse_err_generic(hdr); + if (max_encrypted_data_keys && (size_t)edk_count > max_encrypted_data_keys) { + aws_raise_error(AWS_CRYPTOSDK_ERR_LIMIT_EXCEEDED); + return aws_cryptosdk_priv_hdr_parse_err_rethrow(hdr); + } for (uint16_t i = 0; i < edk_count; ++i) { struct aws_cryptosdk_edk edk; diff --git a/source/session.c b/source/session.c index d69c5d29b..e1e5616fa 100644 --- a/source/session.c +++ b/source/session.c @@ -59,7 +59,7 @@ int aws_cryptosdk_session_reset(struct aws_cryptosdk_session *session, enum aws_ } session->signctx = NULL; - if (mode != AWS_CRYPTOSDK_ENCRYPT && mode != AWS_CRYPTOSDK_DECRYPT) { + if (!aws_cryptosdk_priv_is_valid_mode(session->mode)) { // We do this only after clearing all internal state, to ensure that we don't // accidentally leak some secret data return aws_cryptosdk_priv_fail_session(session, AWS_ERROR_UNIMPLEMENTED); @@ -238,6 +238,23 @@ int aws_cryptosdk_session_set_commitment_policy( return AWS_OP_SUCCESS; } +int aws_cryptosdk_session_set_max_encrypted_data_keys( + struct aws_cryptosdk_session *session, size_t max_encrypted_data_keys) { + AWS_PRECONDITION(session != NULL); + + if (session->state != ST_CONFIG) { + return aws_cryptosdk_priv_fail_session(session, AWS_CRYPTOSDK_ERR_BAD_STATE); + } + + if (!max_encrypted_data_keys) { + return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + } + + session->max_encrypted_data_keys = max_encrypted_data_keys; + + return AWS_OP_SUCCESS; +} + int aws_cryptosdk_session_process( struct aws_cryptosdk_session *session, uint8_t *outp, @@ -325,6 +342,60 @@ int aws_cryptosdk_session_process( return result; } +int aws_cryptosdk_session_process_full( + struct aws_cryptosdk_session *session, + uint8_t *outp, + size_t outlen, + size_t *out_bytes_written, + const uint8_t *inp, + size_t inlen) { + if (session->state != ST_CONFIG) { + return aws_raise_error(AWS_CRYPTOSDK_ERR_BAD_STATE); + } + + if (session->mode == AWS_CRYPTOSDK_ENCRYPT && aws_cryptosdk_session_set_message_size(session, inlen)) { + return AWS_OP_ERR; + } + + size_t in_bytes_read = 0; + *out_bytes_written = 0; + + int result = AWS_OP_SUCCESS; + while (!aws_cryptosdk_session_is_done(session)) { + size_t bytes_written_this_time, bytes_read_this_time; + result = aws_cryptosdk_session_process( + session, + outp + *out_bytes_written, + outlen - *out_bytes_written, + &bytes_written_this_time, + inp + in_bytes_read, + inlen - in_bytes_read, + &bytes_read_this_time); + + if (result != AWS_OP_SUCCESS || (bytes_read_this_time == 0 && bytes_written_this_time == 0)) { + break; + } + + *out_bytes_written += bytes_written_this_time; + in_bytes_read += bytes_read_this_time; + } + + // Convert success to failure if all input was not processed or not enough input was provided. + if (result == AWS_OP_SUCCESS && !(aws_cryptosdk_session_is_done(session) && in_bytes_read == inlen)) { + session->error = AWS_CRYPTOSDK_ERR_BAD_CIPHERTEXT; + aws_cryptosdk_priv_session_change_state(session, ST_ERROR); + result = aws_raise_error(session->error); + } + + if (result != AWS_OP_SUCCESS) { + // Destroy any incomplete (and possibly corrupt) plaintext + aws_secure_zero(outp, outlen); + *out_bytes_written = 0; + } + + return result; +} + bool aws_cryptosdk_session_is_done(const struct aws_cryptosdk_session *session) { return session->state == ST_DONE; } @@ -377,7 +448,7 @@ void aws_cryptosdk_priv_session_change_state(struct aws_cryptosdk_session *sessi // Illegal transition abort(); } - if (session->mode != AWS_CRYPTOSDK_DECRYPT) { + if (!aws_cryptosdk_priv_is_decrypt_mode(session->mode)) { // wrong mode abort(); } @@ -387,7 +458,7 @@ void aws_cryptosdk_priv_session_change_state(struct aws_cryptosdk_session *sessi // Illegal transition abort(); } - if (session->mode != AWS_CRYPTOSDK_DECRYPT) { + if (!aws_cryptosdk_priv_is_decrypt_mode(session->mode)) { // wrong mode abort(); } @@ -509,7 +580,7 @@ int aws_cryptosdk_priv_fail_session(struct aws_cryptosdk_session *session, int e } const struct aws_hash_table *aws_cryptosdk_session_get_enc_ctx_ptr(const struct aws_cryptosdk_session *session) { - if (session->mode == AWS_CRYPTOSDK_DECRYPT && !session->cmm_success) { + if (aws_cryptosdk_priv_is_decrypt_mode(session->mode) && !session->cmm_success) { /* In decrypt mode, we want to wait until after CMM call to * return encryption context. This assures both that the * encryption context has already been deserialized from the @@ -559,3 +630,20 @@ bool aws_cryptosdk_priv_algorithm_allowed_for_decrypt( default: return false; } } + +bool aws_cryptosdk_priv_is_valid_mode(enum aws_cryptosdk_mode mode) { + switch (mode) { + case AWS_CRYPTOSDK_ENCRYPT: + case AWS_CRYPTOSDK_DECRYPT: + case AWS_CRYPTOSDK_DECRYPT_UNSIGNED: return true; + default: return false; + } +} + +bool aws_cryptosdk_priv_is_decrypt_mode(enum aws_cryptosdk_mode mode) { + switch (mode) { + case AWS_CRYPTOSDK_DECRYPT: + case AWS_CRYPTOSDK_DECRYPT_UNSIGNED: return true; + default: return false; + } +} diff --git a/source/session_decrypt.c b/source/session_decrypt.c index 792d394e2..dcafaecf1 100644 --- a/source/session_decrypt.c +++ b/source/session_decrypt.c @@ -143,6 +143,18 @@ int aws_cryptosdk_priv_unwrap_keys(struct aws_cryptosdk_session *AWS_RESTRICT se aws_cryptosdk_transfer_list(&session->keyring_trace, &materials->keyring_trace); session->cmm_success = true; + const struct aws_cryptosdk_alg_properties *materials_alg_props = aws_cryptosdk_alg_props(materials->alg); + if (!materials_alg_props) { + aws_raise_error(AWS_CRYPTOSDK_ERR_BAD_STATE); + goto out; + } + // In AWS_CRYPTOSDK_DECRYPT_UNSIGNED mode, the operation must fail if the CMM + // returns decryption materials with a signing algorithm suite + if (session->mode == AWS_CRYPTOSDK_DECRYPT_UNSIGNED && materials_alg_props->signature_len) { + aws_raise_error(AWS_CRYPTOSDK_ERR_DECRYPT_SIGNED_MESSAGE_NOT_ALLOWED); + goto out; + } + if (derive_data_key(session, materials)) goto out; if (validate_header(session)) goto out; @@ -178,7 +190,7 @@ int aws_cryptosdk_priv_unwrap_keys(struct aws_cryptosdk_session *AWS_RESTRICT se int aws_cryptosdk_priv_try_parse_header( struct aws_cryptosdk_session *AWS_RESTRICT session, struct aws_byte_cursor *AWS_RESTRICT input) { const uint8_t *header_start = input->ptr; - int rv = aws_cryptosdk_hdr_parse(&session->header, input); + int rv = aws_cryptosdk_hdr_parse(&session->header, input, session->max_encrypted_data_keys); if (rv != AWS_OP_SUCCESS) { if (aws_last_error() == AWS_ERROR_SHORT_BUFFER) { diff --git a/source/session_encrypt.c b/source/session_encrypt.c index e6de3cbdd..bb3592222 100644 --- a/source/session_encrypt.c +++ b/source/session_encrypt.c @@ -78,7 +78,14 @@ int aws_cryptosdk_priv_try_gen_key(struct aws_cryptosdk_session *session) { if (!session->alg_props) goto out; if (materials->unencrypted_data_key.len != session->alg_props->data_key_len) goto out; - if (!aws_array_list_length(&materials->encrypted_data_keys)) goto out; + + size_t num_encrypted_data_keys = aws_array_list_length(&materials->encrypted_data_keys); + if (!num_encrypted_data_keys) goto out; + if (session->max_encrypted_data_keys && num_encrypted_data_keys > session->max_encrypted_data_keys) { + result = AWS_CRYPTOSDK_ERR_LIMIT_EXCEEDED; + goto out; + } + // We should have a signature context iff this is a signed alg suite if (!!session->alg_props->signature_len != !!materials->signctx) goto out; if (!aws_cryptosdk_priv_algorithm_allowed_for_encrypt(materials->alg, session->commitment_policy) && diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index a6d1d74f9..1563e16e3 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -102,6 +102,7 @@ aws_add_test(trailing_sig ${VALGRIND} ${CMAKE_CURRENT_BINARY_DIR}/unit-test-suit aws_add_test(local_cache ${VALGRIND} ${CMAKE_CURRENT_BINARY_DIR}/unit-test-suite local_cache) aws_add_test(caching_cmm ${VALGRIND} ${CMAKE_CURRENT_BINARY_DIR}/unit-test-suite caching_cmm) aws_add_test(keyring_trace ${VALGRIND} ${CMAKE_CURRENT_BINARY_DIR}/unit-test-suite keyring_trace) +aws_add_test(max_edks ${VALGRIND} ${CMAKE_CURRENT_BINARY_DIR}/unit-test-suite max_edks) set(TEST_DATA ${CMAKE_CURRENT_SOURCE_DIR}/data) diff --git a/tests/unit/runner.c b/tests/unit/runner.c index dd2ad54d3..5748db628 100644 --- a/tests/unit/runner.c +++ b/tests/unit/runner.c @@ -46,6 +46,7 @@ struct test_case *test_groups[] = { header_test_cases, local_cache_test_cases, caching_cmm_test_cases, keyring_trace_test_cases, + max_edks_test_cases, NULL }; struct test_case *test_cases; diff --git a/tests/unit/t_encrypt.c b/tests/unit/t_encrypt.c index 952805adc..10c52c44e 100644 --- a/tests/unit/t_encrypt.c +++ b/tests/unit/t_encrypt.c @@ -545,6 +545,207 @@ int test_committing_algorithm_suites_fail() { return 0; } +int test_session_process_full_simple_roundtrip() { + size_t pt_len = 512; + init_bufs(pt_len); + grow_buf(&ct_buf, &ct_buf_size, pt_len * 2); + + size_t decrypted_pt_size = pt_len * 2; + uint8_t *decrypted_pt_buf = aws_mem_acquire(aws_default_allocator(), decrypted_pt_size); + TEST_ASSERT_ADDR_NOT_NULL(decrypted_pt_buf); + + struct aws_cryptosdk_keyring *kr = aws_cryptosdk_zero_keyring_new(aws_default_allocator()); + TEST_ASSERT_ADDR_NOT_NULL(kr); + + size_t ct_len, decrypted_pt_len; + + // Encrypt + create_session(AWS_CRYPTOSDK_ENCRYPT, kr); + TEST_ASSERT_SUCCESS(aws_cryptosdk_session_process_full(session, ct_buf, ct_buf_size, &ct_len, pt_buf, pt_size)); + + // Decrypt + aws_cryptosdk_session_reset(session, AWS_CRYPTOSDK_DECRYPT); + TEST_ASSERT_SUCCESS(aws_cryptosdk_session_process_full( + session, decrypted_pt_buf, decrypted_pt_size, &decrypted_pt_len, ct_buf, ct_len)); + + TEST_ASSERT(strncmp(pt_buf, decrypted_pt_buf, pt_size) == 0); + + aws_mem_release(aws_default_allocator(), decrypted_pt_buf); + free_bufs(); + return 0; +} + +int test_session_process_full_simple_roundtrip_empty_plaintext() { + size_t pt_len = 0; + // init_bufs(0) is invalid + init_bufs(1); + grow_buf(&ct_buf, &ct_buf_size, 1024); + + size_t decrypted_pt_size = 1024; + uint8_t *decrypted_pt_buf = aws_mem_acquire(aws_default_allocator(), decrypted_pt_size); + TEST_ASSERT_ADDR_NOT_NULL(decrypted_pt_buf); + + struct aws_cryptosdk_keyring *kr = aws_cryptosdk_zero_keyring_new(aws_default_allocator()); + TEST_ASSERT_ADDR_NOT_NULL(kr); + + size_t ct_len, decrypted_pt_len; + + // Encrypt + create_session(AWS_CRYPTOSDK_ENCRYPT, kr); + TEST_ASSERT_SUCCESS(aws_cryptosdk_session_process_full(session, ct_buf, ct_buf_size, &ct_len, pt_buf, pt_size)); + + // Decrypt + aws_cryptosdk_session_reset(session, AWS_CRYPTOSDK_DECRYPT); + TEST_ASSERT_SUCCESS(aws_cryptosdk_session_process_full( + session, decrypted_pt_buf, decrypted_pt_size, &decrypted_pt_len, ct_buf, ct_len)); + + TEST_ASSERT(strncmp(pt_buf, decrypted_pt_buf, pt_size) == 0); + + aws_mem_release(aws_default_allocator(), decrypted_pt_buf); + free_bufs(); + return 0; +} + +// aws_cryptosdk_session_process_full must fail if it's not in state ST_CONFIG +int test_session_process_full_initial_state() { + size_t pt_len = 512; + init_bufs(pt_len); + grow_buf(&ct_buf, &ct_buf_size, pt_len * 2); + + size_t decrypted_pt_size = pt_len * 2; + uint8_t *decrypted_pt_buf = aws_mem_acquire(aws_default_allocator(), decrypted_pt_size); + TEST_ASSERT_ADDR_NOT_NULL(decrypted_pt_buf); + + struct aws_cryptosdk_keyring *kr = aws_cryptosdk_zero_keyring_new(aws_default_allocator()); + TEST_ASSERT_ADDR_NOT_NULL(kr); + create_session(AWS_CRYPTOSDK_ENCRYPT, kr); + + size_t ct_len, pt_consumed; + + size_t first_round_len = 8; + TEST_ASSERT_SUCCESS( + aws_cryptosdk_session_process(session, ct_buf, ct_buf_size, &ct_len, pt_buf, first_round_len, &pt_consumed)); + + // Must fail because session state is in the middle of encryption + TEST_ASSERT_ERROR( + AWS_CRYPTOSDK_ERR_BAD_STATE, + aws_cryptosdk_session_process_full( + session, ct_buf, ct_buf_size, &ct_len, pt_buf + first_round_len, pt_size - first_round_len)); + + // Must fail because session is in an error state + TEST_ASSERT_ERROR( + AWS_CRYPTOSDK_ERR_BAD_STATE, + aws_cryptosdk_session_process_full( + session, ct_buf, ct_buf_size, &ct_len, pt_buf + first_round_len, pt_size - first_round_len)); + + aws_mem_release(aws_default_allocator(), decrypted_pt_buf); + free_bufs(); + return 0; +} + +// aws_cryptosdk_session_process_full must fail to decrypt a partial message +int test_session_process_full_cant_decrypt_partial() { + size_t pt_len = 512; + init_bufs(pt_len); + grow_buf(&ct_buf, &ct_buf_size, pt_len * 2); + + size_t decrypted_pt_size = pt_len * 2; + uint8_t *decrypted_pt_buf = aws_mem_acquire(aws_default_allocator(), decrypted_pt_size); + TEST_ASSERT_ADDR_NOT_NULL(decrypted_pt_buf); + + struct aws_cryptosdk_keyring *kr = aws_cryptosdk_zero_keyring_new(aws_default_allocator()); + TEST_ASSERT_ADDR_NOT_NULL(kr); + + size_t ct_len, decrypted_pt_len; + + // Encrypt + create_session(AWS_CRYPTOSDK_ENCRYPT, kr); + TEST_ASSERT_SUCCESS(aws_cryptosdk_session_process_full(session, ct_buf, ct_buf_size, &ct_len, pt_buf, pt_size)); + + // Decrypt + aws_cryptosdk_session_reset(session, AWS_CRYPTOSDK_DECRYPT); + TEST_ASSERT_ERROR( + AWS_CRYPTOSDK_ERR_BAD_CIPHERTEXT, + aws_cryptosdk_session_process_full( + session, decrypted_pt_buf, decrypted_pt_size, &decrypted_pt_len, ct_buf, ct_len - 1)); + TEST_ASSERT(aws_is_mem_zeroed(decrypted_pt_buf, decrypted_pt_size)); + + aws_mem_release(aws_default_allocator(), decrypted_pt_buf); + free_bufs(); + return 0; +} + +// Happy path for AWS_CRYPTOSDK_DECRYPT_UNSIGNED +int test_decrypt_unsigned_success() { + size_t pt_len = 512; + init_bufs(pt_len); + struct aws_cryptosdk_keyring *kr = aws_cryptosdk_zero_keyring_new(aws_default_allocator()); + TEST_ASSERT_ADDR_NOT_NULL(kr); + struct aws_cryptosdk_cmm *cmm = create_session_with_cmm(AWS_CRYPTOSDK_ENCRYPT, kr); + TEST_ASSERT_SUCCESS(aws_cryptosdk_default_cmm_set_alg_id(cmm, ALG_AES256_GCM_IV12_TAG16_HKDF_SHA256)); + TEST_ASSERT_SUCCESS( + aws_cryptosdk_session_set_commitment_policy(session, COMMITMENT_POLICY_FORBID_ENCRYPT_ALLOW_DECRYPT)); + + size_t decrypted_pt_size = pt_len * 2; + uint8_t *decrypted_pt_buf = aws_mem_acquire(aws_default_allocator(), decrypted_pt_size); + TEST_ASSERT_ADDR_NOT_NULL(decrypted_pt_buf); + + size_t pt_consumed, ct_len, decrypted_pt_len; + aws_cryptosdk_session_set_message_size(session, pt_size); + precise_size_set = true; + + // Encrypt + if (pump_ciphertext(pt_len * 2, &ct_len, pt_size, &pt_consumed)) return 1; + TEST_ASSERT(aws_cryptosdk_session_is_done(session)); + + // Decrypt + aws_cryptosdk_session_reset(session, AWS_CRYPTOSDK_DECRYPT_UNSIGNED); + TEST_ASSERT_SUCCESS(aws_cryptosdk_session_process_full( + session, decrypted_pt_buf, decrypted_pt_size, &decrypted_pt_len, ct_buf, ct_len)); + + aws_cryptosdk_cmm_release(cmm); + aws_mem_release(aws_default_allocator(), decrypted_pt_buf); + free_bufs(); + return 0; +} + +// In AWS_CRYPTOSDK_DECRYPT_UNSIGNED mode, the session must fail if the CMM +// returns decryption materials with a signing algorithm suite. +int test_decrypt_unsigned_fails_on_signed_materials() { + size_t pt_len = 512; + init_bufs(pt_len); + struct aws_cryptosdk_keyring *kr = aws_cryptosdk_zero_keyring_new(aws_default_allocator()); + TEST_ASSERT_ADDR_NOT_NULL(kr); + struct aws_cryptosdk_cmm *cmm = create_session_with_cmm(AWS_CRYPTOSDK_ENCRYPT, kr); + TEST_ASSERT_SUCCESS(aws_cryptosdk_default_cmm_set_alg_id(cmm, ALG_AES256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384)); + TEST_ASSERT_SUCCESS( + aws_cryptosdk_session_set_commitment_policy(session, COMMITMENT_POLICY_FORBID_ENCRYPT_ALLOW_DECRYPT)); + + size_t decrypted_pt_size = pt_len * 2; + uint8_t *decrypted_pt_buf = aws_mem_acquire(aws_default_allocator(), decrypted_pt_size); + TEST_ASSERT_ADDR_NOT_NULL(decrypted_pt_buf); + + size_t ct_len, pt_consumed, decrypted_pt_len; + aws_cryptosdk_session_set_message_size(session, pt_size); + precise_size_set = true; + + // Encrypt + if (pump_ciphertext(pt_len * 2, &ct_len, pt_size, &pt_consumed)) return 1; + TEST_ASSERT(aws_cryptosdk_session_is_done(session)); + + // Decrypt + aws_cryptosdk_session_reset(session, AWS_CRYPTOSDK_DECRYPT_UNSIGNED); + TEST_ASSERT_ERROR( + AWS_CRYPTOSDK_ERR_DECRYPT_SIGNED_MESSAGE_NOT_ALLOWED, + aws_cryptosdk_session_process_full( + session, decrypted_pt_buf, decrypted_pt_size, &decrypted_pt_len, ct_buf, ct_len)); + + aws_cryptosdk_cmm_release(cmm); + aws_mem_release(aws_default_allocator(), decrypted_pt_buf); + free_bufs(); + return 0; +} + struct test_case encrypt_test_cases[] = { { "encrypt", "test_simple_roundtrip", test_simple_roundtrip }, { "encrypt", "test_small_buffers", test_small_buffers }, @@ -554,5 +755,13 @@ struct test_case encrypt_test_cases[] = { { "encrypt", "test_null_estimates", &test_null_estimates }, { "encrypt", "test_using_estimates", &test_using_estimates }, { "encrypt", "test_committing_algorithm_suites_fail", &test_committing_algorithm_suites_fail }, + { "encrypt", "test_session_process_full_simple_roundtrip", &test_session_process_full_simple_roundtrip }, + { "encrypt", + "test_session_process_full_simple_roundtrip_empty_plaintext", + &test_session_process_full_simple_roundtrip_empty_plaintext }, + { "encrypt", "test_session_process_full_initial_state", &test_session_process_full_initial_state }, + { "encrypt", "test_session_process_full_cant_decrypt_partial", &test_session_process_full_cant_decrypt_partial }, + { "encrypt", "test_decrypt_unsigned_success", &test_decrypt_unsigned_success }, + { "encrypt", "test_decrypt_unsigned_fails_on_signed_materials", &test_decrypt_unsigned_fails_on_signed_materials }, { NULL } }; diff --git a/tests/unit/t_header.c b/tests/unit/t_header.c index 37e6bf628..02242067e 100644 --- a/tests/unit/t_header.c +++ b/tests/unit/t_header.c @@ -449,7 +449,7 @@ int simple_header_parse() { struct aws_byte_cursor cursor = aws_byte_cursor_from_array(test_header_1, sizeof(test_header_1) - 1); TEST_ASSERT_SUCCESS(aws_cryptosdk_hdr_init(&hdr, aws_default_allocator())); - TEST_ASSERT_INT_EQ(AWS_OP_SUCCESS, aws_cryptosdk_hdr_parse(&hdr, &cursor)); + TEST_ASSERT_INT_EQ(AWS_OP_SUCCESS, aws_cryptosdk_hdr_parse(&hdr, &cursor, 0)); TEST_ASSERT_INT_EQ(cursor.len, 0); TEST_ASSERT_ADDR_EQ(cursor.ptr, test_header_1 + sizeof(test_header_1) - 1); @@ -514,7 +514,7 @@ int simple_headerV2_parse() { struct aws_byte_cursor cursor = aws_byte_cursor_from_array(test_headerV2_1, sizeof(test_headerV2_1) - 1); TEST_ASSERT_SUCCESS(aws_cryptosdk_hdr_init(&hdr, aws_default_allocator())); - TEST_ASSERT_INT_EQ(AWS_OP_SUCCESS, aws_cryptosdk_hdr_parse(&hdr, &cursor)); + TEST_ASSERT_INT_EQ(AWS_OP_SUCCESS, aws_cryptosdk_hdr_parse(&hdr, &cursor, 0)); TEST_ASSERT_INT_EQ(cursor.len, 0); TEST_ASSERT_ADDR_EQ(cursor.ptr, test_headerV2_1 + sizeof(test_headerV2_1) - 1); @@ -628,7 +628,7 @@ int simple_header_parse2() { struct aws_byte_cursor cursor = aws_byte_cursor_from_array(test_header_2, sizeof(test_header_2)); TEST_ASSERT_SUCCESS(aws_cryptosdk_hdr_init(&hdr, aws_default_allocator())); - TEST_ASSERT_INT_EQ(AWS_OP_SUCCESS, aws_cryptosdk_hdr_parse(&hdr, &cursor)); + TEST_ASSERT_INT_EQ(AWS_OP_SUCCESS, aws_cryptosdk_hdr_parse(&hdr, &cursor, 0)); // There should be one byte of trailing data left over TEST_ASSERT_INT_EQ(cursor.len, 1); @@ -681,7 +681,7 @@ int failed_parse() { TEST_ASSERT_SUCCESS(aws_cryptosdk_hdr_init(&hdr, aws_default_allocator())); cursor = aws_byte_cursor_from_array(test_header_1, sizeof(test_header_1) - 5); - TEST_ASSERT_ERROR(AWS_ERROR_SHORT_BUFFER, aws_cryptosdk_hdr_parse(&hdr, &cursor)); + TEST_ASSERT_ERROR(AWS_ERROR_SHORT_BUFFER, aws_cryptosdk_hdr_parse(&hdr, &cursor, 0)); TEST_ASSERT_ADDR_EQ(cursor.ptr, test_header_1); TEST_ASSERT_INT_EQ(0, hdr.alg_id); @@ -692,7 +692,7 @@ int failed_parse() { for (size_t hdr_idx = 0; hdr_idx < num_bad_hdrs; ++hdr_idx) { cursor = aws_byte_cursor_from_array(bad_headers[hdr_idx], bad_headers_sz[hdr_idx]); - TEST_ASSERT_ERROR(AWS_CRYPTOSDK_ERR_BAD_CIPHERTEXT, aws_cryptosdk_hdr_parse(&hdr, &cursor)); + TEST_ASSERT_ERROR(AWS_CRYPTOSDK_ERR_BAD_CIPHERTEXT, aws_cryptosdk_hdr_parse(&hdr, &cursor, 0)); TEST_ASSERT_ADDR_EQ(cursor.ptr, bad_headers[hdr_idx]); TEST_ASSERT_INT_EQ(0, hdr.alg_id); @@ -749,7 +749,7 @@ static void overread_once(const uint8_t *inbuf, size_t inlen, ssize_t flip_bit_i aws_cryptosdk_hdr_init(&hdr, aws_default_allocator()); // We don't care about the return value as long as we don't actually crash. - aws_cryptosdk_hdr_parse(&hdr, &cursor); + aws_cryptosdk_hdr_parse(&hdr, &cursor, 0); aws_cryptosdk_hdr_clean_up(&hdr); // This is only necessary when aws_cryptosdk_hdr_parse_init succeeds, diff --git a/tests/unit/t_max_encrypted_data_keys.c b/tests/unit/t_max_encrypted_data_keys.c new file mode 100644 index 000000000..668baf0ae --- /dev/null +++ b/tests/unit/t_max_encrypted_data_keys.c @@ -0,0 +1,280 @@ +/* + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use + * this file except in compliance with the License. A copy of the License is + * located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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. + */ + +#include +#include +#include +#include +#include +#include +#include "testing.h" +#include "testutil.h" +#include "zero_keyring.h" + +#define BUFFER_SIZE (1 << 20) + +static const char *DATA_KEY_B64 = "+p6+whPVw9kOrYLZFMRBJ2n6Vli6T/7TkjDouS+25s0="; +static const enum aws_cryptosdk_alg_id ALG_ID = ALG_AES256_GCM_IV12_TAG16_NO_KDF; + +static uint8_t encrypt_output[BUFFER_SIZE]; +static uint8_t decrypt_output[BUFFER_SIZE]; + +struct multi_edk_cmm { + struct aws_cryptosdk_cmm base; + struct aws_allocator *alloc; + size_t num_edks; +}; + +static void setup_test() { + memset(encrypt_output, 0, sizeof(encrypt_output)); + memset(decrypt_output, 0, sizeof(decrypt_output)); +} + +// Add cmm->num_edks many all-0 EDKs, and set a static data key +int multi_edk_cmm_generate_enc_materials( + struct aws_cryptosdk_cmm *cmm, + struct aws_cryptosdk_enc_materials **output, + struct aws_cryptosdk_enc_request *request) { + request->requested_alg = ALG_ID; + struct aws_cryptosdk_enc_materials *materials = + aws_cryptosdk_enc_materials_new(request->alloc, request->requested_alg); + TEST_ASSERT_ADDR_NOT_NULL(materials); + + struct multi_edk_cmm *self = (struct multi_edk_cmm *)cmm; + + TEST_ASSERT_SUCCESS(aws_array_list_ensure_capacity(&materials->encrypted_data_keys, self->num_edks)); + memset(materials->encrypted_data_keys.data, 0, materials->encrypted_data_keys.current_size); + materials->encrypted_data_keys.length = self->num_edks; + + materials->unencrypted_data_key = easy_b64_decode(DATA_KEY_B64); + *output = materials; + return AWS_OP_SUCCESS; +} + +// Set static data key +int multi_edk_cmm_decrypt_materials( + struct aws_cryptosdk_cmm *cmm, + struct aws_cryptosdk_dec_materials **output, + struct aws_cryptosdk_dec_request *request) { + struct aws_cryptosdk_dec_materials *materials = aws_cryptosdk_dec_materials_new(request->alloc, request->alg); + TEST_ASSERT_ADDR_NOT_NULL(materials); + materials->unencrypted_data_key = easy_b64_decode(DATA_KEY_B64); + *output = materials; + return AWS_OP_SUCCESS; +} + +static void multi_edk_cmm_destroy(struct aws_cryptosdk_cmm *cmm) { + struct multi_edk_cmm *self = (struct multi_edk_cmm *)cmm; + aws_mem_release(self->alloc, self); +} + +static const struct aws_cryptosdk_cmm_vt multi_edk_cmm_vt = { + .vt_size = sizeof(struct aws_cryptosdk_cmm_vt), + .name = "multi_edk_cmm", + .destroy = multi_edk_cmm_destroy, + .generate_enc_materials = multi_edk_cmm_generate_enc_materials, + .decrypt_materials = multi_edk_cmm_decrypt_materials, +}; + +static struct aws_cryptosdk_cmm *multi_edk_cmm_new(size_t num_edks) { + struct aws_allocator *alloc = aws_default_allocator(); + struct multi_edk_cmm *cmm = aws_mem_acquire(alloc, sizeof(*cmm)); + aws_cryptosdk_cmm_base_init(&cmm->base, &multi_edk_cmm_vt); + cmm->alloc = alloc; + cmm->num_edks = num_edks; + return (struct aws_cryptosdk_cmm *)cmm; +} + +static int set_max_edks_0() { + setup_test(); + + struct aws_cryptosdk_keyring *kr = aws_cryptosdk_zero_keyring_new(aws_default_allocator()); + struct aws_cryptosdk_session *session = + aws_cryptosdk_session_new_from_keyring_2(aws_default_allocator(), AWS_CRYPTOSDK_DECRYPT, kr); + TEST_ASSERT_ERROR(AWS_ERROR_INVALID_ARGUMENT, aws_cryptosdk_session_set_max_encrypted_data_keys(session, 0)); + aws_cryptosdk_keyring_release(kr); + aws_cryptosdk_session_destroy(session); + return 0; +} + +static int set_max_edks_1() { + setup_test(); + + struct aws_cryptosdk_keyring *kr = aws_cryptosdk_zero_keyring_new(aws_default_allocator()); + struct aws_cryptosdk_session *session = + aws_cryptosdk_session_new_from_keyring_2(aws_default_allocator(), AWS_CRYPTOSDK_DECRYPT, kr); + TEST_ASSERT_SUCCESS(aws_cryptosdk_session_set_max_encrypted_data_keys(session, 1)); + aws_cryptosdk_keyring_release(kr); + aws_cryptosdk_session_destroy(session); + return 0; +} + +static int set_max_edks_10() { + setup_test(); + + struct aws_cryptosdk_keyring *kr = aws_cryptosdk_zero_keyring_new(aws_default_allocator()); + struct aws_cryptosdk_session *session = + aws_cryptosdk_session_new_from_keyring_2(aws_default_allocator(), AWS_CRYPTOSDK_DECRYPT, kr); + TEST_ASSERT_SUCCESS(aws_cryptosdk_session_set_max_encrypted_data_keys(session, 10)); + aws_cryptosdk_keyring_release(kr); + aws_cryptosdk_session_destroy(session); + return 0; +} + +static int set_max_edks_uint16_max() { + setup_test(); + + struct aws_cryptosdk_keyring *kr = aws_cryptosdk_zero_keyring_new(aws_default_allocator()); + struct aws_cryptosdk_session *session = + aws_cryptosdk_session_new_from_keyring_2(aws_default_allocator(), AWS_CRYPTOSDK_DECRYPT, kr); + TEST_ASSERT_SUCCESS(aws_cryptosdk_session_set_max_encrypted_data_keys(session, UINT16_MAX)); + aws_cryptosdk_keyring_release(kr); + aws_cryptosdk_session_destroy(session); + return 0; +} + +static const char *PT_BYTES = "foobar"; + +static int do_encrypt(struct aws_cryptosdk_session *session, uint8_t *encrypt_output, size_t *output_len) { + size_t input_len = strlen(PT_BYTES); + size_t input_consumed; + TEST_ASSERT_SUCCESS(aws_cryptosdk_session_set_message_size(session, input_len)); + int rv = aws_cryptosdk_session_process( + session, encrypt_output, BUFFER_SIZE, output_len, (const uint8_t *)PT_BYTES, input_len, &input_consumed); + if (rv) return rv; + TEST_ASSERT_INT_EQ(input_len, input_consumed); + TEST_ASSERT(aws_cryptosdk_session_is_done(session)); + return 0; +} + +static int do_decrypt( + struct aws_cryptosdk_session *session, uint8_t *encrypt_output, size_t input_len, uint8_t *decrypt_output) { + size_t output_len; + size_t input_consumed; + int rv = aws_cryptosdk_session_process( + session, decrypt_output, BUFFER_SIZE, &output_len, encrypt_output, input_len, &input_consumed); + if (rv) return rv; + TEST_ASSERT_INT_EQ(input_len, input_consumed); + TEST_ASSERT(aws_cryptosdk_session_is_done(session)); + TEST_ASSERT_INT_EQ(strncmp(PT_BYTES, (const char *)decrypt_output, BUFFER_SIZE), 0); + return 0; +} + +static int encrypt_and_decrypt_no_max_edks() { + setup_test(); + + struct aws_cryptosdk_cmm *cmm = multi_edk_cmm_new(UINT16_MAX); + struct aws_cryptosdk_session *session = + aws_cryptosdk_session_new_from_cmm_2(aws_default_allocator(), AWS_CRYPTOSDK_ENCRYPT, cmm); + aws_cryptosdk_cmm_release(cmm); + TEST_ASSERT_SUCCESS( + aws_cryptosdk_session_set_commitment_policy(session, COMMITMENT_POLICY_FORBID_ENCRYPT_ALLOW_DECRYPT)); + + size_t output_len; + TEST_ASSERT_SUCCESS(do_encrypt(session, encrypt_output, &output_len)); + aws_cryptosdk_session_reset(session, AWS_CRYPTOSDK_DECRYPT); + TEST_ASSERT_SUCCESS(do_decrypt(session, encrypt_output, output_len, decrypt_output)); + aws_cryptosdk_session_destroy(session); + return 0; +} + +static int do_encrypt_with_n_edks(size_t num_edks) { + struct aws_cryptosdk_cmm *cmm = multi_edk_cmm_new(num_edks); + struct aws_cryptosdk_session *session = + aws_cryptosdk_session_new_from_cmm_2(aws_default_allocator(), AWS_CRYPTOSDK_ENCRYPT, cmm); + aws_cryptosdk_cmm_release(cmm); + TEST_ASSERT_SUCCESS( + aws_cryptosdk_session_set_commitment_policy(session, COMMITMENT_POLICY_FORBID_ENCRYPT_ALLOW_DECRYPT)); + TEST_ASSERT_SUCCESS(aws_cryptosdk_session_set_max_encrypted_data_keys(session, 3)); + + size_t output_len; + int rv = do_encrypt(session, encrypt_output, &output_len); + aws_cryptosdk_session_destroy(session); + return rv; +} + +static int do_decrypt_with_n_edks(size_t num_edks) { + struct aws_cryptosdk_cmm *cmm = multi_edk_cmm_new(num_edks); + struct aws_cryptosdk_session *session = + aws_cryptosdk_session_new_from_cmm_2(aws_default_allocator(), AWS_CRYPTOSDK_ENCRYPT, cmm); + aws_cryptosdk_cmm_release(cmm); + TEST_ASSERT_SUCCESS( + aws_cryptosdk_session_set_commitment_policy(session, COMMITMENT_POLICY_FORBID_ENCRYPT_ALLOW_DECRYPT)); + + size_t output_len; + TEST_ASSERT_SUCCESS(do_encrypt(session, encrypt_output, &output_len)); + TEST_ASSERT_SUCCESS(aws_cryptosdk_session_reset(session, AWS_CRYPTOSDK_DECRYPT)); + TEST_ASSERT_SUCCESS(aws_cryptosdk_session_set_max_encrypted_data_keys(session, 3)); + int rv = do_decrypt(session, encrypt_output, output_len, decrypt_output); + aws_cryptosdk_session_destroy(session); + return rv; +} + +static int encrypt_less_than_max_edks() { + setup_test(); + + TEST_ASSERT_SUCCESS(do_encrypt_with_n_edks(2)); + return 0; +} + +static int encrypt_equal_to_max_edks() { + setup_test(); + + TEST_ASSERT_SUCCESS(do_encrypt_with_n_edks(3)); + return 0; +} + +static int encrypt_more_than_max_edks() { + setup_test(); + + TEST_ASSERT_ERROR(AWS_CRYPTOSDK_ERR_LIMIT_EXCEEDED, do_encrypt_with_n_edks(4)); + return 0; +} + +static int decrypt_less_than_max_edks() { + setup_test(); + + TEST_ASSERT_SUCCESS(do_decrypt_with_n_edks(2)); + return 0; +} + +static int decrypt_equal_to_max_edks() { + setup_test(); + + TEST_ASSERT_SUCCESS(do_decrypt_with_n_edks(3)); + return 0; +} + +static int decrypt_more_than_max_edks() { + setup_test(); + + TEST_ASSERT_ERROR(AWS_CRYPTOSDK_ERR_LIMIT_EXCEEDED, do_decrypt_with_n_edks(4)); + return 0; +} + +struct test_case max_edks_test_cases[] = { + { "max_edks", "set_max_edks_0", set_max_edks_0 }, + { "max_edks", "set_max_edks_1", set_max_edks_1 }, + { "max_edks", "set_max_edks_10", set_max_edks_10 }, + { "max_edks", "set_max_edks_uint16_max", set_max_edks_uint16_max }, + { "max_edks", "encrypt_and_decrypt_no_max_edks", encrypt_and_decrypt_no_max_edks }, + { "max_edks", "encrypt_less_than_max_edks", encrypt_less_than_max_edks }, + { "max_edks", "encrypt_equal_to_max_edks", encrypt_equal_to_max_edks }, + { "max_edks", "encrypt_more_than_max_edks", encrypt_more_than_max_edks }, + { "max_edks", "decrypt_less_than_max_edks", decrypt_less_than_max_edks }, + { "max_edks", "decrypt_equal_to_max_edks", decrypt_equal_to_max_edks }, + { "max_edks", "decrypt_more_than_max_edks", decrypt_more_than_max_edks }, + { NULL } +}; diff --git a/tests/unit/testing.h b/tests/unit/testing.h index 585c67e79..b710e3302 100644 --- a/tests/unit/testing.h +++ b/tests/unit/testing.h @@ -41,6 +41,7 @@ extern struct test_case local_cache_test_cases[]; extern struct test_case caching_cmm_test_cases[]; extern struct test_case keyring_trace_test_cases[]; extern struct test_case version_test_cases[]; +extern struct test_case max_edks_test_cases[]; #define TEST_ASSERT(cond) \ do { \ diff --git a/verification/cbmc/proofs/aws_cryptosdk_enc_ctx_clone/Makefile b/verification/cbmc/proofs/aws_cryptosdk_enc_ctx_clone/Makefile index 0551f7f5f..482098350 100644 --- a/verification/cbmc/proofs/aws_cryptosdk_enc_ctx_clone/Makefile +++ b/verification/cbmc/proofs/aws_cryptosdk_enc_ctx_clone/Makefile @@ -23,6 +23,9 @@ MAX_TABLE_SIZE ?= 4 # Time on my laptop: 5m10s ######### +# Disable pointer overflow check +CBMC_FLAG_POINTER_OVERFLOW_CHECK= + PROOF_UID = aws_cryptosdk_enc_ctx_clone HARNESS_ENTRY = $(PROOF_UID)_harness diff --git a/verification/cbmc/proofs/aws_cryptosdk_enc_ctx_serialize/Makefile b/verification/cbmc/proofs/aws_cryptosdk_enc_ctx_serialize/Makefile index 3864a93f3..90b081df3 100644 --- a/verification/cbmc/proofs/aws_cryptosdk_enc_ctx_serialize/Makefile +++ b/verification/cbmc/proofs/aws_cryptosdk_enc_ctx_serialize/Makefile @@ -28,6 +28,9 @@ TABLE_SIZE_IN_WORDS=$(shell echo $$(($$((3 * $(MAX_TABLE_SIZE))) + 10))) ######### # Vars for Makefile.common +# Disable pointer overflow check +CBMC_FLAG_POINTER_OVERFLOW_CHECK= + PROOF_UID = aws_cryptosdk_enc_ctx_serialize HARNESS_ENTRY = $(PROOF_UID)_harness diff --git a/verification/cbmc/proofs/aws_cryptosdk_hdr_write/Makefile b/verification/cbmc/proofs/aws_cryptosdk_hdr_write/Makefile index fbf734cac..323ebe3d7 100644 --- a/verification/cbmc/proofs/aws_cryptosdk_hdr_write/Makefile +++ b/verification/cbmc/proofs/aws_cryptosdk_hdr_write/Makefile @@ -17,6 +17,9 @@ sinclude ../Makefile.local include ../Makefile.local_default include ../Makefile.aws_byte_buf +# Disable pointer overflow check +CBMC_FLAG_POINTER_OVERFLOW_CHECK= + PROOF_UID = aws_cryptosdk_hdr_write HARNESS_ENTRY = $(PROOF_UID)_harness diff --git a/verification/cbmc/proofs/aws_cryptosdk_priv_hdr_parse_edks/aws_cryptosdk_priv_hdr_parse_edks_harness.c b/verification/cbmc/proofs/aws_cryptosdk_priv_hdr_parse_edks/aws_cryptosdk_priv_hdr_parse_edks_harness.c index f903a47a1..9ae627377 100644 --- a/verification/cbmc/proofs/aws_cryptosdk_priv_hdr_parse_edks/aws_cryptosdk_priv_hdr_parse_edks_harness.c +++ b/verification/cbmc/proofs/aws_cryptosdk_priv_hdr_parse_edks/aws_cryptosdk_priv_hdr_parse_edks_harness.c @@ -56,6 +56,7 @@ void aws_cryptosdk_priv_hdr_parse_edks_harness() { /* Nondet Input */ struct aws_cryptosdk_hdr *hdr = hdr_setup(MAX_TABLE_SIZE, MAX_EDK_LIST_ITEMS, MAX_BUFFER_SIZE); struct aws_byte_cursor *pcursor = malloc(sizeof(*pcursor)); + size_t max_encrypted_data_keys; /* Assumptions */ __CPROVER_assume(pcursor != NULL); @@ -84,7 +85,7 @@ void aws_cryptosdk_priv_hdr_parse_edks_harness() { save_byte_from_hash_table(&hdr->enc_ctx, &old_enc_ctx); /* Operation under verification */ - if (aws_cryptosdk_priv_hdr_parse_edks(hdr, pcursor) == AWS_OP_SUCCESS) { + if (aws_cryptosdk_priv_hdr_parse_edks(hdr, pcursor, max_encrypted_data_keys) == AWS_OP_SUCCESS) { /* Postconditions */ assert_byte_buf_equivalence(&hdr->iv, &old_iv, &old_byte_from_iv); assert_byte_buf_equivalence(&hdr->auth_tag, &old_auth_tag, &old_byte_from_auth_tag); diff --git a/verification/cbmc/proofs/aws_cryptosdk_serialize_frame/Makefile b/verification/cbmc/proofs/aws_cryptosdk_serialize_frame/Makefile index 49766d0bb..243c255cf 100644 --- a/verification/cbmc/proofs/aws_cryptosdk_serialize_frame/Makefile +++ b/verification/cbmc/proofs/aws_cryptosdk_serialize_frame/Makefile @@ -11,7 +11,6 @@ # implied. See the License for the specific language governing permissions and # limitations under the License. -########### # if Makefile.local exists, use it. This provides a way to override the defaults sinclude ../Makefile.local #otherwise, use the default values @@ -31,8 +30,14 @@ PROJECT_SOURCES += $(SRCDIR)/source/framefmt.c PROOF_SOURCES += $(COMMON_PROOF_SOURCE)/make_common_data_structures.c PROOF_SOURCES += $(COMMON_PROOF_SOURCE)/proof_allocators.c +PROOF_SOURCES += $(COMMON_PROOF_SOURCE)/utils.c PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE) PROOF_SOURCES += $(PROOF_SOURCE)/make_common_data_structures.c -########### + +# We abstract this function because manual inspection demonstrates it is unreachable. +# Improves coverage metrics. +REMOVE_FUNCTION_BODY += aws_byte_cursor_advance +REMOVE_FUNCTION_BODY += aws_byte_cursor_read_be32 +REMOVE_FUNCTION_BODY += aws_byte_cursor_read_be64 include ../Makefile.common diff --git a/verification/cbmc/proofs/aws_cryptosdk_serialize_frame/aws_cryptosdk_serialize_frame_harness.c b/verification/cbmc/proofs/aws_cryptosdk_serialize_frame/aws_cryptosdk_serialize_frame_harness.c index d017e6a22..c0d4d36e3 100644 --- a/verification/cbmc/proofs/aws_cryptosdk_serialize_frame/aws_cryptosdk_serialize_frame_harness.c +++ b/verification/cbmc/proofs/aws_cryptosdk_serialize_frame/aws_cryptosdk_serialize_frame_harness.c @@ -29,9 +29,7 @@ void aws_cryptosdk_serialize_frame_harness() { /* Assumptions about the function input */ ensure_byte_buf_has_allocated_buffer_member(&ciphertext_buf); __CPROVER_assume(aws_byte_buf_is_valid(&ciphertext_buf)); - __CPROVER_assume(aws_cryptosdk_frame_has_valid_type(&frame)); - __CPROVER_assume(aws_cryptosdk_alg_properties_is_valid(props)); /* Save the old state of the ciphertext buffer */