Skip to content

Commit 4ba5825

Browse files
committed
feat: Updates to the AWS Encryption SDK
This change includes fixes for issues that were reported by Thai Duong from Google's Security team, and for issues that were identified by AWS Cryptography. See: https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/migration.html
1 parent a703120 commit 4ba5825

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+5216
-483
lines changed

CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ set(PROJECT_NAME aws-encryption-sdk)
5050

5151
# Version number of the SDK to be consumed by C code and Doxygen
5252
set(MAJOR 1)
53-
set(MINOR 1)
53+
set(MINOR 7)
5454
set(PATCH 0)
5555

5656
# Compiler feature tests and feature flags
@@ -161,6 +161,7 @@ target_link_libraries(aws-encryption-sdk-test PRIVATE ${PLATFORM_LIBS} ${OPENSSL
161161
target_link_libraries(aws-encryption-sdk-test PUBLIC AWS::aws-c-common)
162162
target_compile_definitions(aws-encryption-sdk-test PRIVATE AWS_CRYPTOSDK_TEST_STATIC=)
163163
target_compile_definitions(aws-encryption-sdk-test PUBLIC AWS_ENCRYPTION_SDK_FORCE_STATIC)
164+
target_compile_definitions(aws-encryption-sdk-test PRIVATE UNIT_TEST_ONLY_ALLOW_ENCRYPT_WITH_COMMITMENT)
164165

165166
include(CodeCoverageTargets)
166167

aws-encryption-sdk-cpp/CMakeLists.txt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
# limitations under the License.
1414
#
1515

16+
set(TEST_DATA ${CMAKE_CURRENT_SOURCE_DIR}/tests/data)
17+
1618
file(GLOB AWS_CRYPTOSDK_CPP_HEADERS
1719
# Headers subject to API/ABI stability guarantees
1820
"${CMAKE_CURRENT_SOURCE_DIR}/include/aws/cryptosdk/cpp/*.h"
@@ -72,6 +74,16 @@ if (AWS_ENC_SDK_END_TO_END_TESTS)
7274
)
7375
set_target_properties(t_integration_kms_keyring PROPERTIES CXX_STANDARD 11 C_STANDARD 99)
7476
aws_add_test(integration_kms_mk ${VALGRIND} ${CMAKE_CURRENT_BINARY_DIR}/t_integration_kms_keyring)
77+
78+
add_executable(t_commitment_known_answer tests/integration/t_commitment_known_answer.cpp)
79+
target_link_libraries(t_commitment_known_answer testlibcpp)
80+
target_include_directories(t_commitment_known_answer PUBLIC ${PROJECT_SOURCE_DIR}/tests/lib
81+
${PROJECT_SOURCE_DIR}/tests/unit
82+
${PROJECT_SOURCE_DIR}/tests/integration
83+
$<INSTALL_INTERFACE:include>
84+
)
85+
set_target_properties(t_commitment_known_answer PROPERTIES CXX_STANDARD 11 C_STANDARD 99)
86+
aws_add_test(commitment_known_answer ${VALGRIND} ${CMAKE_CURRENT_BINARY_DIR}/t_commitment_known_answer ${TEST_DATA}/commitment_known_answer_tests.json)
7587
else()
7688
message(STATUS "End to end tests off")
7789
endif()

aws-encryption-sdk-cpp/include/aws/cryptosdk/cpp/kms_keyring.h

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include <aws/core/Aws.h>
2121
#include <aws/core/utils/Outcome.h>
2222
#include <aws/core/utils/memory/stl/AWSMap.h>
23+
#include <aws/core/utils/memory/stl/AWSSet.h>
2324
#include <aws/core/utils/memory/stl/AWSString.h>
2425
#include <aws/core/utils/memory/stl/AWSVector.h>
2526
#include <aws/cryptosdk/materials.h>
@@ -31,6 +32,7 @@ namespace Aws {
3132
namespace Cryptosdk {
3233
namespace KmsKeyring {
3334
class ClientSupplier;
35+
class DiscoveryFilter;
3436

3537
/**
3638
* @defgroup kms_keyring KMS keyring (AWS SDK for C++)
@@ -144,6 +146,28 @@ class AWS_CRYPTOSDK_CPP_API Builder {
144146
*/
145147
aws_cryptosdk_keyring *BuildDiscovery() const;
146148

149+
/**
150+
* Creates a new KmsKeyring object in discovery mode (i.e., no KMS keys
151+
* configured) but with a DiscoveryFilter. This means the following:
152+
*
153+
* (1) As in discovery mode without a DiscoveryFilter, this KmsKeyring will
154+
* not do anything on encryption attempts.
155+
*
156+
* (2) On attempts to decrypt, the AWS Encryption SDK will attempt KMS
157+
* DecryptDataKey calls for every KMS key that was used to encrypt the
158+
* data until it finds one that:
159+
*
160+
* (a) you have permission to use, and
161+
* (b) that is in an account specified by the DiscoveryFilter.
162+
*
163+
* This may include calls to any region, unless prevented by policies
164+
* on the IAM user or role.
165+
*
166+
* The discovery_filter argument must not be nullptr, or else this function
167+
* fails and returns nullptr.
168+
*/
169+
aws_cryptosdk_keyring *BuildDiscovery(std::shared_ptr<KmsKeyring::DiscoveryFilter> discovery_filter) const;
170+
147171
private:
148172
std::shared_ptr<KMS::KMSClient> kms_client;
149173
Aws::Vector<Aws::String> grant_tokens;
@@ -213,6 +237,73 @@ class AWS_CRYPTOSDK_CPP_API SingleClientSupplier : public ClientSupplier {
213237
std::shared_ptr<KMS::KMSClient> kms_client;
214238
};
215239

240+
static const char *AWS_CRYPTO_SDK_DISCOVERY_FILTER_CLASS_TAG = "DiscoveryFilter";
241+
242+
/**
243+
* Builder for DiscoveryFilter objects.
244+
*/
245+
class AWS_CRYPTOSDK_CPP_API DiscoveryFilterBuilder {
246+
public:
247+
/**
248+
* Constructs a builder with no authorized account IDs.
249+
*/
250+
DiscoveryFilterBuilder(Aws::String partition) : partition(partition) {}
251+
252+
/**
253+
* Adds an account ID to the set of authorized account IDs.
254+
*/
255+
DiscoveryFilterBuilder &AddAccount(const Aws::String &account_id);
256+
257+
/**
258+
* Adds account IDs to the set of authorized account IDs.
259+
*/
260+
DiscoveryFilterBuilder &AddAccounts(const Aws::Vector<Aws::String> &account_ids);
261+
262+
/**
263+
* Replaces the set of authorized account IDs.
264+
*/
265+
DiscoveryFilterBuilder &WithAccounts(const Aws::Vector<Aws::String> &account_ids);
266+
267+
/**
268+
* Creates a new DiscoveryFilter object, or returns NULL if no account
269+
* IDs have been added.
270+
*/
271+
std::shared_ptr<DiscoveryFilter> Build() const;
272+
273+
private:
274+
Aws::String partition;
275+
Aws::Set<Aws::String> account_ids;
276+
};
277+
278+
/**
279+
* The KmsKeyring can be configured with a DiscoveryFilter in order to limit
280+
* the key ARNs used to decrypt EDKs in discovery mode.
281+
*/
282+
class AWS_CRYPTOSDK_CPP_API DiscoveryFilter {
283+
public:
284+
/**
285+
* Returns true if the given key ARN is authorized for usage by a
286+
* KmsKeyring in discovery mode, according to the configured partition and
287+
* account IDs, or false otherwise.
288+
*/
289+
bool IsAuthorized(const Aws::String &key_arn) const;
290+
291+
DiscoveryFilter() = delete;
292+
293+
/**
294+
* Constructs a DiscoveryFilterBuilder with no authorized account IDs and no partition.
295+
*/
296+
static DiscoveryFilterBuilder Builder(Aws::String partition);
297+
298+
protected:
299+
DiscoveryFilter(Aws::String partition, Aws::Set<Aws::String> account_ids)
300+
: partition(partition), account_ids(account_ids) {}
301+
302+
private:
303+
Aws::String partition;
304+
Aws::Set<Aws::String> account_ids;
305+
};
306+
216307
/** @} */ // doxygen group kms_keyring
217308

218309
} // namespace KmsKeyring

aws-encryption-sdk-cpp/include/aws/cryptosdk/private/kms_keyring.h

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#ifndef AWS_ENCRYPTION_SDK_PRIVATE_KMS_KEYRING_H
1616
#define AWS_ENCRYPTION_SDK_PRIVATE_KMS_KEYRING_H
1717

18+
#include <assert.h>
1819
#include <aws/cryptosdk/cpp/kms_keyring.h>
1920

2021
namespace Aws {
@@ -47,6 +48,25 @@ class AWS_CRYPTOSDK_CPP_API KmsKeyringImpl : public aws_cryptosdk_keyring {
4748
const Aws::Vector<Aws::String> &grant_tokens,
4849
std::shared_ptr<Aws::Cryptosdk::KmsKeyring::ClientSupplier> supplier);
4950

51+
/**
52+
* Constructor of KmsKeyring for internal use only. Use KmsKeyring::Builder to make a new KmsKeyring.
53+
*
54+
* @param key_ids List of KMS customer master keys (CMK)
55+
* @param grant_tokens A list of grant tokens.
56+
* @param supplier Object that supplies the KMSClient instances to use for each region.
57+
* @param discovery_filter DiscoveryFilter specifying authorized partition
58+
* and account IDs. The stored pointer must not be nullptr.
59+
*/
60+
KmsKeyringImpl(
61+
const Aws::Vector<Aws::String> &key_ids,
62+
const Aws::Vector<Aws::String> &grant_tokens,
63+
std::shared_ptr<Aws::Cryptosdk::KmsKeyring::ClientSupplier> supplier,
64+
std::shared_ptr<Aws::Cryptosdk::KmsKeyring::DiscoveryFilter> discovery_filter)
65+
: KmsKeyringImpl(key_ids, grant_tokens, supplier) {
66+
assert((bool)discovery_filter);
67+
this->discovery_filter = discovery_filter;
68+
}
69+
5070
/**
5171
* Returns the KMS Client for a specific key ID
5272
*/
@@ -57,6 +77,23 @@ class AWS_CRYPTOSDK_CPP_API KmsKeyringImpl : public aws_cryptosdk_keyring {
5777

5878
Aws::Vector<Aws::String> grant_tokens;
5979
Aws::Vector<Aws::String> key_ids;
80+
81+
/**
82+
* This is nullptr if and only if no DiscoveryFilter is configured during
83+
* construction.
84+
*/
85+
std::shared_ptr<KmsKeyring::DiscoveryFilter> discovery_filter;
86+
};
87+
88+
/**
89+
* This just serves to provide a public KmsKeyring::Discovery
90+
* (derived-class-)constructor for use with Aws::MakeShared, without exposing a
91+
* constructor in the public API.
92+
*/
93+
class AWS_CRYPTOSDK_CPP_API DiscoveryFilterImpl : public KmsKeyring::DiscoveryFilter {
94+
public:
95+
DiscoveryFilterImpl(Aws::String partition, Aws::Set<Aws::String> account_ids)
96+
: DiscoveryFilter(partition, account_ids) {}
6097
};
6198

6299
} // namespace Private

aws-encryption-sdk-cpp/source/kms_keyring.cpp

Lines changed: 88 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
*/
1515
#include <aws/cryptosdk/private/kms_keyring.h>
1616

17+
#include <aws/core/utils/ARN.h>
1718
#include <aws/core/utils/Outcome.h>
1819
#include <aws/core/utils/logging/LogMacros.h>
1920
#include <aws/core/utils/memory/MemorySystemInterface.h>
@@ -78,16 +79,26 @@ static int OnDecrypt(
7879
const Aws::String key_arn = Private::aws_string_from_c_aws_byte_buf(&edk->provider_info);
7980

8081
/* If there are no key IDs in the list, keyring is in "discovery" mode and will attempt KMS calls with
81-
* every ARN it comes across in the message. If there are key IDs in the list, it will cross check the
82-
* ARN it reads with that list before attempting KMS calls. Note that if caller provided key IDs in
83-
* anything other than a CMK ARN format, the SDK will not attempt to decrypt those data keys, because
84-
* the EDK data format always specifies the CMK with the full (non-alias) ARN.
82+
* every key ARN it comes across in the message, so long as the key ARN is authorized by the
83+
* DiscoveryFilter (matches the partition and an account ID).
84+
*
85+
* If there are key IDs in the list, it will cross check the ARN it reads with that list
86+
* before attempting KMS calls. Note that if caller provided key IDs in anything other than
87+
* a CMK ARN format, the SDK will not attempt to decrypt those data keys, because the EDK
88+
* data format always specifies the CMK with the full (non-alias) ARN.
8589
*/
8690
if (self->key_ids.size() &&
8791
std::find(self->key_ids.begin(), self->key_ids.end(), key_arn) == self->key_ids.end()) {
8892
// This keyring does not have access to the CMK used to encrypt this data key. Skip.
8993
continue;
9094
}
95+
// self->discovery_filter is non-null only if self was constructed via BuildDiscovery, which
96+
// in turn implies discovery mode
97+
if (self->discovery_filter && !self->discovery_filter->IsAuthorized(key_arn)) {
98+
// The DiscoveryFilter blocks the CMK used to encrypt this data key. Skip.
99+
continue;
100+
}
101+
91102
Aws::String kms_region = Private::parse_region_from_kms_key_arn(key_arn);
92103
if (kms_region.empty()) {
93104
error_buf << "Error: Malformed ciphertext. Provider ID field of KMS EDK is invalid KMS CMK ARN: " << key_arn
@@ -104,6 +115,7 @@ static int OnDecrypt(
104115

105116
Aws::KMS::Model::DecryptRequest kms_request;
106117
kms_request.WithGrantTokens(self->grant_tokens)
118+
.WithKeyId(key_arn)
107119
.WithCiphertextBlob(aws_utils_byte_buffer_from_c_aws_byte_buf(&edk->ciphertext))
108120
.WithEncryptionContext(enc_ctx_cpp);
109121

@@ -131,6 +143,10 @@ static int OnDecrypt(
131143
AWS_CRYPTOSDK_WRAPPING_KEY_DECRYPTED_DATA_KEY | AWS_CRYPTOSDK_WRAPPING_KEY_VERIFIED_ENC_CTX);
132144
}
133145
return ret;
146+
} else {
147+
// Since we specified the key ARN explicitly in the request,
148+
// KMS had better use that key to decrypt
149+
return aws_raise_error(AWS_ERROR_INVALID_STATE);
134150
}
135151
}
136152

@@ -419,6 +435,20 @@ aws_cryptosdk_keyring *KmsKeyring::Builder::BuildDiscovery() const {
419435
BuildClientSupplier(empty_key_ids_list, kms_client, client_supplier));
420436
}
421437

438+
aws_cryptosdk_keyring *KmsKeyring::Builder::BuildDiscovery(std::shared_ptr<DiscoveryFilter> discovery_filter) const {
439+
if (!discovery_filter) {
440+
return nullptr;
441+
}
442+
443+
Aws::Vector<Aws::String> empty_key_ids_list;
444+
return Aws::New<Private::KmsKeyringImpl>(
445+
AWS_CRYPTO_SDK_KMS_CLASS_TAG,
446+
empty_key_ids_list,
447+
grant_tokens,
448+
BuildClientSupplier(empty_key_ids_list, kms_client, client_supplier),
449+
discovery_filter);
450+
}
451+
422452
KmsKeyring::Builder &KmsKeyring::Builder::WithGrantTokens(const Aws::Vector<Aws::String> &grant_tokens) {
423453
this->grant_tokens.insert(this->grant_tokens.end(), grant_tokens.begin(), grant_tokens.end());
424454
return *this;
@@ -440,5 +470,59 @@ KmsKeyring::Builder &KmsKeyring::Builder::WithKmsClient(const std::shared_ptr<KM
440470
return *this;
441471
}
442472

473+
bool KmsKeyring::DiscoveryFilter::IsAuthorized(const Aws::String &key_arn) const {
474+
Utils::ARN arn(key_arn);
475+
if (!arn) {
476+
return false;
477+
}
478+
479+
bool matching_partition = arn.GetPartition() == partition;
480+
bool matching_account = account_ids.find(arn.GetAccountId()) != account_ids.end();
481+
return matching_partition && matching_account;
482+
}
483+
484+
KmsKeyring::DiscoveryFilterBuilder KmsKeyring::DiscoveryFilter::Builder(Aws::String partition) {
485+
KmsKeyring::DiscoveryFilterBuilder builder(partition);
486+
return builder;
487+
}
488+
489+
KmsKeyring::DiscoveryFilterBuilder &KmsKeyring::DiscoveryFilterBuilder::AddAccount(const Aws::String &account_id) {
490+
this->account_ids.insert(account_id);
491+
return *this;
492+
}
493+
494+
KmsKeyring::DiscoveryFilterBuilder &KmsKeyring::DiscoveryFilterBuilder::AddAccounts(
495+
const Aws::Vector<Aws::String> &account_ids) {
496+
this->account_ids.insert(account_ids.begin(), account_ids.end());
497+
return *this;
498+
}
499+
500+
KmsKeyring::DiscoveryFilterBuilder &KmsKeyring::DiscoveryFilterBuilder::WithAccounts(
501+
const Aws::Vector<Aws::String> &account_ids) {
502+
this->account_ids.clear();
503+
return this->AddAccounts(account_ids);
504+
}
505+
506+
std::shared_ptr<KmsKeyring::DiscoveryFilter> KmsKeyring::DiscoveryFilterBuilder::Build() const {
507+
// Must have at least one account ID, and partition and account IDs cannot be the empty string
508+
if (account_ids.empty()) {
509+
AWS_LOGSTREAM_ERROR(
510+
AWS_CRYPTO_SDK_KMS_CLASS_TAG, "Invalid DiscoveryFilterBuilder: account IDs cannot be empty");
511+
return nullptr;
512+
}
513+
if (partition.empty()) {
514+
AWS_LOGSTREAM_ERROR(AWS_CRYPTO_SDK_KMS_CLASS_TAG, "Invalid DiscoveryFilterBuilder: partition cannot be blank");
515+
return nullptr;
516+
}
517+
if (account_ids.find("") != account_ids.end()) {
518+
AWS_LOGSTREAM_ERROR(
519+
AWS_CRYPTO_SDK_KMS_CLASS_TAG, "Invalid DiscoveryFilterBuilder: account IDs cannot be blank");
520+
return nullptr;
521+
}
522+
523+
return Aws::MakeShared<Private::DiscoveryFilterImpl>(
524+
KmsKeyring::AWS_CRYPTO_SDK_DISCOVERY_FILTER_CLASS_TAG, partition, account_ids);
525+
}
526+
443527
} // namespace Cryptosdk
444528
} // namespace Aws

0 commit comments

Comments
 (0)