From 365ea19c9a00632736ce37f5b96ed6f73529906e Mon Sep 17 00:00:00 2001 From: "John W. Bruce" Date: Thu, 5 Sep 2024 07:21:15 +0000 Subject: [PATCH] OEMCrypto and OPK v19.3 --- CHANGELOG.md | 44 + docs/Widevine_Open_Source_License_Terms.pdf | Bin 0 -> 108844 bytes oemcrypto/include/OEMCryptoCENC.h | 48 +- oemcrypto/include/level3.h | 4 + oemcrypto/odk/include/core_message_features.h | 4 +- oemcrypto/odk/include/odk_structs.h | 4 +- oemcrypto/odk/src/core_message_features.cpp | 2 +- oemcrypto/odk/src/odk_timer.c | 2 +- oemcrypto/odk/test/odk_test.cpp | 4 +- oemcrypto/opk/oemcrypto_ta/oemcrypto.c | 37 +- .../opk/oemcrypto_ta/oemcrypto_api_macros.h | 3 +- .../opk/oemcrypto_ta/oemcrypto_session.c | 21 +- .../opk/oemcrypto_ta/oemcrypto_usage_table.c | 49 +- oemcrypto/opk/oemcrypto_ta/wtpi/README.md | 1 + .../wtpi_root_of_trust_interface_layer1.h | 11 +- .../oemcrypto_ta/wtpi_reference/cose_util.c | 2 +- .../wtpi_reference/device_info_util.c | 17 +- .../oemcrypto_ta/wtpi_reference/ecc_util.c | 2 + .../wtpi_reference/prov30_factory_util.c | 2 + .../wtpi_reference/wtpi_clock_and_gn_layer1.c | 3 +- ...wtpi_crypto_and_key_management_layer1_hw.c | 20 +- ...crypto_and_key_management_layer1_openssl.c | 6 +- .../wtpi_crypto_wrap_asymmetric.c | 13 +- .../wtpi_reference/wtpi_decrypt_sample.c | 28 +- .../wtpi_reference/wtpi_device_key.c | 35 +- .../wtpi_reference/wtpi_provisioning_4.c | 5 + .../wtpi_root_of_trust_layer1.c | 16 +- .../linux/cas/tee/tuner_tee_serialization.c | 9 +- oemcrypto/opk/ports/optee/Makefile | 5 + oemcrypto/opk/ports/optee/README.md | 6 + .../optee/host/oemcrypto_unittests/Makefile | 6 + .../install_prov30_rot.cpp | 148 ++ .../oemcrypto_unittests/install_prov30_rot.h | 21 + .../oemcrypto_unittests_main.cpp | 80 + .../optee/ta/common/wtpi_impl/sources.mk | 13 +- .../ta/common/wtpi_impl/util/crypto_util.h | 5 +- .../wtpi_impl/util/crypto_util_mbedtls.c | 6 + .../wtpi_impl/util/crypto_util_mbedtls_v3.c | 870 +++++++++ .../util/prov30_factory_util_mbedtls_v3.c | 1551 +++++++++++++++++ .../wtpi_crypto_and_key_management_layer1.c | 2 +- .../common/wtpi_impl/wtpi_crypto_asymmetric.c | 17 + .../ta/common/wtpi_impl/wtpi_decrypt_sample.c | 4 +- .../wtpi_impl/wtpi_root_of_trust_layer1.c | 195 ++- .../include/user_ta_header_defines.h | 2 +- .../opk/ports/optee/ta/oemcrypto_ta/sub.mk | 21 +- .../opk/ports/optee/ta/wtpi_test_ta/sub.mk | 18 + .../common/GEN_common_serializer.c | 67 - .../common/GEN_common_serializer.h | 4 - .../common/common_special_cases.c | 68 + .../common/common_special_cases.h | 5 + .../common/include/marshaller_base.h | 2 + .../serialization/common/marshaller_base.c | 11 +- .../opk/serialization/ree/GEN_oemcrypto_api.c | 33 + .../serialization/ree/GEN_ree_serializer.c | 32 + .../serialization/ree/GEN_ree_serializer.h | 4 + .../opk/serialization/tee/GEN_dispatcher.c | 90 +- .../serialization/tee/GEN_tee_serializer.c | 26 + .../serialization/tee/GEN_tee_serializer.h | 4 + .../tee/special_case_request_handlers.c | 12 +- .../tee/special_case_request_handlers.h | 3 +- oemcrypto/test/GEN_api_lock_file.c | 4 + oemcrypto/test/common.mk | 2 +- oemcrypto/test/extract_bcc_tool.cpp | 131 +- .../test/install_prov30_oem_cert_tool.cpp | 2 + oemcrypto/test/oec_decrypt_fallback_chain.cpp | 16 +- oemcrypto/test/oec_device_features.cpp | 18 +- oemcrypto/test/oec_device_features.h | 12 + oemcrypto/test/oec_session_util.cpp | 11 +- oemcrypto/test/oemcrypto_basic_test.cpp | 424 ++++- oemcrypto/test/oemcrypto_decrypt_test.cpp | 32 - .../test/oemcrypto_session_tests_helper.cpp | 3 + oemcrypto/test/oemcrypto_usage_table_test.cpp | 6 + oemcrypto/util/include/bcc_validator.h | 11 +- oemcrypto/util/include/cbor_validator.h | 11 +- oemcrypto/util/include/cmac.h | 10 +- .../util/include/device_info_validator.h | 12 +- oemcrypto/util/include/hmac.h | 2 +- oemcrypto/util/include/oemcrypto_drm_key.h | 6 +- oemcrypto/util/include/oemcrypto_ecc_key.h | 29 +- .../util/include/oemcrypto_key_deriver.h | 10 +- oemcrypto/util/include/oemcrypto_oem_cert.h | 13 +- oemcrypto/util/include/oemcrypto_rsa_key.h | 27 +- oemcrypto/util/include/scoped_object.h | 7 +- .../include/signed_csr_payload_validator.h | 7 +- oemcrypto/util/include/wvcrc32.h | 2 +- oemcrypto/util/src/bcc_validator.cpp | 7 +- oemcrypto/util/src/cbor_validator.cpp | 1 - oemcrypto/util/src/oemcrypto_ecc_key.cpp | 2 +- oemcrypto/util/src/oemcrypto_oem_cert.cpp | 13 +- oemcrypto/util/test/oem_cert_test.h | 2 +- .../util/test/oemcrypto_ref_test_utils.h | 2 +- util/include/string_conversions.h | 14 +- util/include/wv_class_utils.h | 112 ++ util/src/string_conversions.cpp | 65 +- util/test/base64_test.cpp | 111 ++ 95 files changed, 4332 insertions(+), 528 deletions(-) create mode 100644 docs/Widevine_Open_Source_License_Terms.pdf create mode 100644 oemcrypto/opk/ports/optee/host/oemcrypto_unittests/install_prov30_rot.cpp create mode 100644 oemcrypto/opk/ports/optee/host/oemcrypto_unittests/install_prov30_rot.h create mode 100644 oemcrypto/opk/ports/optee/host/oemcrypto_unittests/oemcrypto_unittests_main.cpp create mode 100644 oemcrypto/opk/ports/optee/ta/common/wtpi_impl/util/crypto_util_mbedtls_v3.c create mode 100644 oemcrypto/opk/ports/optee/ta/common/wtpi_impl/util/prov30_factory_util_mbedtls_v3.c create mode 100644 util/include/wv_class_utils.h diff --git a/CHANGELOG.md b/CHANGELOG.md index 57a4724..b21a118 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,49 @@ [TOC] +## [Version 19.3][v19.3] + +This release adds new tests that provide stricter enforcement of the existing +OEMCrypto specification. + +This release of OPK fixes some rare but possible null dereference bugs and +improves on the Provisioning 3.0 support introduced in v19.2, including support +for OP-TEE. + +### Tests + +- Added new tests to better validate the behavior of + `OEMCrypto_BuildInformation()` + - Verifies output length is set correctly + - Verifies content is ASCII JSON without trailing null bytes + - Verifies documented JSON fields: required fields are present, and optional + and required fields are the correct JSON types +- Removed OEMCryptoLicenseTest.RejectCbc1API16 +- Fixed erroneous failures on devices with low TEE memory caused by sending an + output buffer to decrypt that was much larger than necessary + +### API + +- Added a new API for CAS: `OEMCrypto_SetSessionUsage()` +- Clarified the expected handling of the pattern (0,0) in cbcs mode. For more + information, please check the [OEMCrypto v19 Delta Document][delta-19]. + +### OPK + +- Clarified how RNG should be configured if using OP-TEE +- Fixed several potential null dereferences that could occur if the allocator + runs out of memory +- Fixed incorrect behavior of `OEMCrypto_RemoveEntitledKeySession()` when the + session is already closed +- Provisioning 3.0 support is now gated by a `USE_PROVISIONING_30` compiler flag + so that it can be excluded from builds on devices that do not use Provisioning + 3.0 +- Added Provisioning 3.0 support on OP-TEE +- Added optional support for backwards-compatibility with certificates and + usage tables wrapped by the old OEMCrypto Reference Code + +[delta-19]: https://developers.google.com/widevine/drm/client/oemcrypto/v19/delta + ## [Version 19.2][v19.2] This patch provides provisioning 3.0 for OEMs using OPK. Also includes bug @@ -639,3 +682,4 @@ Public release for OEMCrypto API and ODK library version 16.4. [v19.0]: https://widevine-partner.googlesource.com/oemcrypto/+/refs/tags/v19.0 [v19.1]: https://widevine-partner.googlesource.com/oemcrypto/+/refs/tags/v19.1 [v19.2]: https://widevine-partner.googlesource.com/oemcrypto/+/refs/tags/v19.2 +[v19.3]: https://widevine-partner.googlesource.com/oemcrypto/+/refs/tags/v19.3 diff --git a/docs/Widevine_Open_Source_License_Terms.pdf b/docs/Widevine_Open_Source_License_Terms.pdf new file mode 100644 index 0000000000000000000000000000000000000000..eee3844c77695ab7fda0c74522fb80a1280b2726 GIT binary patch literal 108844 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 = 4; break; case 19: - nonce_values->api_minor_version = 2; + nonce_values->api_minor_version = 3; break; default: nonce_values->api_minor_version = 0; diff --git a/oemcrypto/odk/test/odk_test.cpp b/oemcrypto/odk/test/odk_test.cpp index 1cc2f6c..b0cc632 100644 --- a/oemcrypto/odk/test/odk_test.cpp +++ b/oemcrypto/odk/test/odk_test.cpp @@ -1275,7 +1275,7 @@ std::vector TestCases() { {16, ODK_MAJOR_VERSION, ODK_MINOR_VERSION, 16, 5}, {17, ODK_MAJOR_VERSION, ODK_MINOR_VERSION, 17, 2}, {18, ODK_MAJOR_VERSION, ODK_MINOR_VERSION, 18, 4}, - {19, ODK_MAJOR_VERSION, ODK_MINOR_VERSION, 19, 2}, + {19, ODK_MAJOR_VERSION, ODK_MINOR_VERSION, 19, 3}, // Here are some known good versions. Make extra sure they work. {ODK_MAJOR_VERSION, 16, 3, 16, 3}, {ODK_MAJOR_VERSION, 16, 4, 16, 4}, @@ -1288,6 +1288,7 @@ std::vector TestCases() { {ODK_MAJOR_VERSION, 19, 0, 19, 0}, {ODK_MAJOR_VERSION, 19, 1, 19, 1}, {ODK_MAJOR_VERSION, 19, 2, 19, 2}, + {ODK_MAJOR_VERSION, 19, 3, 19, 3}, {0, 16, 3, 16, 3}, {0, 16, 4, 16, 4}, {0, 16, 5, 16, 5}, @@ -1298,6 +1299,7 @@ std::vector TestCases() { {0, 19, 0, 19, 0}, {0, 19, 1, 19, 1}, {0, 19, 2, 19, 2}, + {0, 19, 3, 19, 3}, }; return test_cases; } diff --git a/oemcrypto/opk/oemcrypto_ta/oemcrypto.c b/oemcrypto/opk/oemcrypto_ta/oemcrypto.c index c81b758..b3291d3 100644 --- a/oemcrypto/opk/oemcrypto_ta/oemcrypto.c +++ b/oemcrypto/opk/oemcrypto_ta/oemcrypto.c @@ -710,6 +710,11 @@ static OEMCryptoResult DeriveKeysFromSessionKey( OEMCryptoSession* session_context, const uint8_t* context, size_t context_length, const uint8_t* enc_session_key, size_t enc_session_key_length) { + ABORT_IF_NULL(session_context); + ABORT_IF_NULL(context); + ABORT_IF_ZERO(context_length); + ABORT_IF_NULL(enc_session_key); + ABORT_IF_ZERO(enc_session_key_length); WTPI_K1_SymmetricKey_Handle session_key_handle = NULL; OEMCryptoResult result; @@ -1318,7 +1323,6 @@ static OEMCryptoResult LoadKeysNoSignature( ABORT_IF_NULL(session); ABORT_IF_NULL(message); ABORT_IF_ZERO(message_length); - ABORT_IF_NULL(key_array); RETURN_INVALID_CONTEXT_IF_NULL(key_array); RETURN_INVALID_CONTEXT_IF_ZERO(num_keys); @@ -2063,6 +2067,9 @@ static OEMCryptoResult DecryptCENCInternal( ABORT_IF(g_opk_system_state != SYSTEM_INITIALIZED, "The system is uninitialized, and this was not caught at a higher " "level."); + ABORT_IF_NULL(samples); + ABORT_IF_ZERO(samples_length); + ABORT_IF_NULL(pattern); OEMCryptoSession* session_context = NULL; OEMCryptoEntitledKeySession* key_session = NULL; OEMCryptoResult result = @@ -2072,10 +2079,6 @@ static OEMCryptoResult DecryptCENCInternal( "GetSessionContext() provided invalid output."); result = OPKI_CheckStatePreCall(session_context, API_DECRYPTCENC); if (result != OEMCrypto_SUCCESS) return result; - RETURN_INVALID_CONTEXT_IF_NULL(samples); - RETURN_INVALID_CONTEXT_IF_ZERO(samples_length); - RETURN_INVALID_CONTEXT_IF_NULL(pattern); - result = OPKI_DecryptSamples(session_context, key_session, samples, samples_length, pattern); if (result == OEMCrypto_ERROR_SHORT_BUFFER) { @@ -2820,6 +2823,7 @@ OEMCryptoResult OEMCrypto_BuildInformation(char* buffer, XSTR(OPK_PATCH_VERSION) OPK_IS_DEBUG_STR "+" __DATE__ __TIME__ "\"," "\"uses_opk\":true," + "\"opk_build\":\"" OPK_BUILD_ID "\"," "\"tee_os\":\"" XSTR(OPK_CONFIG_TEE_OS_NAME) "\"," "\"tee_os_ver\":\"" XSTR(OPK_CONFIG_TEE_OS_VERSION) "\"," "\"form_factor\":\"" XSTR(OPK_CONFIG_DEVICE_FORM_FACTOR) "\"," @@ -3231,14 +3235,14 @@ static OEMCryptoResult LoadProvisioningCommon( size_t message_length, size_t core_message_length, const uint8_t* signature, size_t signature_length, uint8_t* wrapped_private_key, size_t* wrapped_private_key_length) { + ABORT_IF_NULL(message); + ABORT_IF_ZERO(message_length); + ABORT_IF_NULL(signature); + ABORT_IF_ZERO(signature_length); + ABORT_IF_NULL(wrapped_private_key_length); OEMCryptoResult result = OPKI_CheckStatePreCall(session_context, API_LOADPROVISIONING); if (result != OEMCrypto_SUCCESS) return result; - - RETURN_INVALID_CONTEXT_IF_NULL(message); - RETURN_INVALID_CONTEXT_IF_ZERO(message_length); - RETURN_INVALID_CONTEXT_IF_NULL(signature); - RETURN_INVALID_CONTEXT_IF_ZERO(signature_length); const OEMCrypto_ProvisioningMethod provisioning_method = WTPI_GetProvisioningMethod(); if ((provisioning_method == OEMCrypto_Keybox || @@ -3247,7 +3251,6 @@ static OEMCryptoResult LoadProvisioningCommon( LOGE("signature_length is not the length of a SHA256 digest"); return OEMCrypto_ERROR_INVALID_CONTEXT; } - RETURN_INVALID_CONTEXT_IF_NULL(wrapped_private_key_length); uint8_t device_id[ODK_DEVICE_ID_LEN_MAX] = {0}; ODK_ParsedProvisioning parsed_response; @@ -3332,6 +3335,11 @@ OEMCryptoResult OEMCrypto_LoadProvisioning( LOGE("OEMCrypto is not yet initialized"); return OEMCrypto_ERROR_UNKNOWN_FAILURE; } + RETURN_INVALID_CONTEXT_IF_NULL(message); + RETURN_INVALID_CONTEXT_IF_ZERO(message_length); + RETURN_INVALID_CONTEXT_IF_NULL(signature); + RETURN_INVALID_CONTEXT_IF_ZERO(signature_length); + RETURN_INVALID_CONTEXT_IF_NULL(wrapped_private_key_length); if (OPKI_GetSessionType(session) != SESSION_TYPE_OEMCRYPTO) { LOGE("Unexpected session type."); return OEMCrypto_ERROR_INVALID_SESSION; @@ -4698,6 +4706,13 @@ OEMCryptoResult OEMCrypto_GetOEMKeyToken(OEMCrypto_SESSION key_session UNUSED, } #endif +// CAS only, currently not implemented +OEMCryptoResult OEMCrypto_SetSessionUsage(OEMCrypto_SESSION session UNUSED, + uint32_t intent UNUSED, + uint32_t mode UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + OEMCryptoResult OEMCrypto_GetEmbeddedDrmCertificate( uint8_t* public_cert UNUSED, size_t* public_cert_length UNUSED) { return OEMCrypto_ERROR_NOT_IMPLEMENTED; diff --git a/oemcrypto/opk/oemcrypto_ta/oemcrypto_api_macros.h b/oemcrypto/opk/oemcrypto_ta/oemcrypto_api_macros.h index 79f40c6..56e94e6 100644 --- a/oemcrypto/opk/oemcrypto_ta/oemcrypto_api_macros.h +++ b/oemcrypto/opk/oemcrypto_ta/oemcrypto_api_macros.h @@ -34,7 +34,8 @@ // version bumps to v17.1, the first released OPK implementation would be // v17.1.0 #define API_MAJOR_VERSION 19 -#define API_MINOR_VERSION 2 +#define API_MINOR_VERSION 3 #define OPK_PATCH_VERSION 0 +#define OPK_BUILD_ID "19.3.0" #endif /* OEMCRYPTO_TA_OEMCRYPTO_API_MACROS_H_ */ diff --git a/oemcrypto/opk/oemcrypto_ta/oemcrypto_session.c b/oemcrypto/opk/oemcrypto_ta/oemcrypto_session.c index 81d4284..d77336c 100644 --- a/oemcrypto/opk/oemcrypto_ta/oemcrypto_session.c +++ b/oemcrypto/opk/oemcrypto_ta/oemcrypto_session.c @@ -534,7 +534,11 @@ NO_IGNORE_RESULT static OEMCryptoResult DeriveKey( size_t context_length, const uint8_t* context_suffix, size_t context_suffix_length, SymmetricKeyType out_key_type, KeySize out_key_size, SymmetricKey** new_key) { - if (new_key == NULL) return OEMCrypto_ERROR_UNKNOWN_FAILURE; + ABORT_IF_NULL(key_label); + ABORT_IF_ZERO(key_label_length); + ABORT_IF_NULL(context); + ABORT_IF_ZERO(context_length); + ABORT_IF_NULL(new_key); // TODO(fredgc): remove this from the key table, too. OEMCryptoResult result = OPKI_CreateKey(new_key, out_key_type, out_key_size); if (result != OEMCrypto_SUCCESS) return result; @@ -569,8 +573,8 @@ OEMCryptoResult OPKI_DeriveMacAndEncryptionKeys( counter += 2; // increment counter by 2 because 2 blocks used for server key. result = DeriveKey(master_key, counter, ODK_MacKeyLabelWithZero, ODK_MacKeyLabelWithZeroLength, context, context_length, - ODK_MacKeySuffix, ODK_MacKeySuffixLength, - MAC_KEY_CLIENT, MAC_KEY_SIZE, &session->mac_key_client); + ODK_MacKeySuffix, ODK_MacKeySuffixLength, MAC_KEY_CLIENT, + MAC_KEY_SIZE, &session->mac_key_client); if (result != OEMCrypto_SUCCESS) { OPKI_FreeKeyFromTable(&session->mac_key_server); return result; @@ -578,8 +582,8 @@ OEMCryptoResult OPKI_DeriveMacAndEncryptionKeys( counter = 1; // Start over with encryption context. result = DeriveKey(master_key, counter, ODK_EncKeyLabelWithZero, ODK_EncKeyLabelWithZeroLength, context, context_length, - ODK_EncKeySuffix, ODK_EncKeySuffixLength, - ENCRYPTION_KEY, KEY_SIZE_128, &session->encryption_key); + ODK_EncKeySuffix, ODK_EncKeySuffixLength, ENCRYPTION_KEY, + KEY_SIZE_128, &session->encryption_key); if (result != OEMCrypto_SUCCESS) { OPKI_FreeKeyFromTable(&session->mac_key_server); OPKI_FreeKeyFromTable(&session->mac_key_client); @@ -627,6 +631,7 @@ OEMCryptoResult OPKI_GenerateSignatureWithMacKeyClient( } NO_IGNORE_RESULT static bool DRMKeyMaySign(const AsymmetricKey* key) { + ABORT_IF_NULL(key); switch (key->key_type) { case DRM_RSA_PRIVATE_KEY: return (key->allowed_schemes & kSign_RSASSA_PSS) == kSign_RSASSA_PSS; @@ -754,7 +759,7 @@ NO_IGNORE_RESULT OEMCryptoResult OPKI_GetSessionSignatureHashAlgorithm( NO_IGNORE_RESULT static OEMCryptoResult CheckStatusOnline( OEMCryptoSession* session, uint32_t nonce, uint32_t control UNUSED) { - if (!session) return OEMCrypto_ERROR_UNKNOWN_FAILURE; + ABORT_IF_NULL(session); if (session->nonce_values.nonce != nonce) { return OEMCrypto_ERROR_INVALID_NONCE; } @@ -772,7 +777,7 @@ NO_IGNORE_RESULT static OEMCryptoResult CheckStatusOnline( NO_IGNORE_RESULT static OEMCryptoResult CheckStatusOffline( OEMCryptoSession* session, uint32_t nonce, uint32_t control UNUSED) { - if (!session) return OEMCrypto_ERROR_UNKNOWN_FAILURE; + ABORT_IF_NULL(session); switch (OPKI_GetUsageEntryStatus(session->session_id)) { default: /* Invalid status. */ case USAGE_ENTRY_NONE: @@ -791,6 +796,7 @@ NO_IGNORE_RESULT static OEMCryptoResult CheckStatusOffline( NO_IGNORE_RESULT static OEMCryptoResult check_nonce_or_entry( OEMCryptoSession* session, KeyControlBlock key_control_block) { + ABORT_IF_NULL(session); /* Note: we only check the nonce if the bit is enabled. */ uint32_t replay_bits = key_control_block.control_bits & CONTROL_REPLAY_MASK; switch (replay_bits) { @@ -1173,6 +1179,7 @@ OEMCryptoResult OPKI_GetKeyControlBlock( static bool PrioritizeHdcpLevel(OEMCrypto_HDCP_Capability level, uint8_t* priority) { + ABORT_IF_NULL(priority); // Since OEMCrypto_HDCP_Capability is no longer sorted, we need to convert all // values to a sorted priority so we can efficiently compare them. // diff --git a/oemcrypto/opk/oemcrypto_ta/oemcrypto_usage_table.c b/oemcrypto/opk/oemcrypto_ta/oemcrypto_usage_table.c index 09ee33b..d996e38 100644 --- a/oemcrypto/opk/oemcrypto_ta/oemcrypto_usage_table.c +++ b/oemcrypto/opk/oemcrypto_ta/oemcrypto_usage_table.c @@ -153,8 +153,7 @@ static OEMCryptoResult InitHeader(SavedUsageHeader* header, } static OEMCryptoResult InitSignedHeader(SignedSavedUsageHeader* signed_header) { - if (!signed_header) return OEMCrypto_ERROR_INVALID_CONTEXT; - + ABORT_IF_NULL(signed_header); *signed_header = (SignedSavedUsageHeader){ .common_info = { @@ -170,8 +169,9 @@ static OEMCryptoResult InitSignedHeader(SignedSavedUsageHeader* signed_header) { static OEMCryptoResult WrapHeader(SavedUsageHeader* header, uint8_t* out_buffer, size_t out_buffer_length) { - if (!header) return OEMCrypto_ERROR_INVALID_CONTEXT; - + ABORT_IF_NULL(header); + ABORT_IF_NULL(out_buffer); + ABORT_IF_ZERO(out_buffer_length); uint8_t temp_buffer[PADDED_HEADER_BUFFER_SIZE] = {0}; OEMCryptoResult result = OPKI_PackUsageHeader(temp_buffer, sizeof(temp_buffer), header); @@ -199,6 +199,9 @@ static OEMCryptoResult WrapHeader(SavedUsageHeader* header, uint8_t* out_buffer, NO_IGNORE_RESULT static OEMCryptoResult EncryptAndSignHeader( uint8_t* header_buffer, size_t header_buffer_length, const UsageTable* usage_table) { + ABORT_IF_NULL(header_buffer); + ABORT_IF_ZERO(header_buffer_length); + ABORT_IF_NULL(usage_table); SavedUsageHeader header; OEMCryptoResult result = InitHeader(&header, usage_table); if (result != OEMCrypto_SUCCESS) return result; @@ -213,9 +216,8 @@ NO_IGNORE_RESULT static OEMCryptoResult EncryptAndSignHeader( NO_IGNORE_RESULT static OEMCryptoResult DecryptAndVerifyHeader_Legacy( const uint8_t* header_buffer, size_t header_buffer_length, SavedUsageHeader* header) { - if (!header_buffer || !header) { - return OEMCrypto_ERROR_UNKNOWN_FAILURE; - } + ABORT_IF_NULL(header_buffer); + ABORT_IF_NULL(header); if (header_buffer_length < sizeof(SignedSavedUsageHeaderLegacy)) { return OEMCrypto_ERROR_SHORT_BUFFER; } @@ -270,9 +272,8 @@ NO_IGNORE_RESULT static OEMCryptoResult DecryptAndVerifyHeader_Legacy( NO_IGNORE_RESULT static OEMCryptoResult DecryptAndVerifyHeader( const uint8_t* header_buffer, size_t header_buffer_length, SavedUsageHeader* header) { - if (!header_buffer || !header) { - return OEMCrypto_ERROR_UNKNOWN_FAILURE; - } + ABORT_IF_NULL(header_buffer); + ABORT_IF_NULL(header); if (header_buffer_length < sizeof(SavedCommonInfo)) { LOGE("Invalid header buffer size: %zu", header_buffer_length); return OEMCrypto_ERROR_SHORT_BUFFER; @@ -374,6 +375,8 @@ NO_IGNORE_RESULT static OEMCryptoResult DecryptAndVerifyHeader( */ NO_IGNORE_RESULT static OEMCryptoResult EncryptAndSignEntry( SavedUsageEntry* entry, uint8_t* entry_buffer, size_t entry_buffer_length) { + ABORT_IF_NULL(entry); + ABORT_IF_NULL(entry_buffer); entry->common_info.file_type = USAGE_TABLE_ENTRY; entry->common_info.format_version = CURRENT_FILE_FORMAT_VERSION; @@ -406,9 +409,8 @@ NO_IGNORE_RESULT static OEMCryptoResult EncryptAndSignEntry( NO_IGNORE_RESULT static OEMCryptoResult DecryptAndVerifyEntry_Legacy( const uint8_t* entry_buffer, size_t entry_buffer_length, SavedUsageEntry* entry) { - if (!entry_buffer || !entry) { - return OEMCrypto_ERROR_UNKNOWN_FAILURE; - } + ABORT_IF_NULL(entry_buffer); + ABORT_IF_NULL(entry); if (entry_buffer_length < sizeof(SignedSavedUsageEntryLegacy)) { return OEMCrypto_ERROR_SHORT_BUFFER; } @@ -457,9 +459,8 @@ NO_IGNORE_RESULT static OEMCryptoResult DecryptAndVerifyEntry_Legacy( NO_IGNORE_RESULT static OEMCryptoResult DecryptAndVerifyEntry( const uint8_t* entry_buffer, size_t entry_buffer_length, SavedUsageEntry* entry) { - if (!entry_buffer || !entry) { - return OEMCrypto_ERROR_UNKNOWN_FAILURE; - } + ABORT_IF_NULL(entry_buffer); + ABORT_IF_NULL(entry); if (entry_buffer_length < sizeof(SavedCommonInfo)) { LOGE("Invalid entry buffer size: %zu", entry_buffer_length); return OEMCrypto_ERROR_SHORT_BUFFER; @@ -524,8 +525,7 @@ NO_IGNORE_RESULT static OEMCryptoResult DecryptAndVerifyEntry( return OEMCrypto_ERROR_SHORT_BUFFER; } SignedSavedUsageEntry signed_entry; - result = - OPKI_UnpackSignedUsageEntry(buffer, buffer_length, &signed_entry); + result = OPKI_UnpackSignedUsageEntry(buffer, buffer_length, &signed_entry); if (result != OEMCrypto_SUCCESS) return result; if (signed_entry.buffer_size > sizeof(signed_entry.buffer)) { return OEMCrypto_ERROR_UNKNOWN_FAILURE; @@ -554,7 +554,8 @@ NO_IGNORE_RESULT static OEMCryptoResult DecryptAndVerifyEntry( NO_IGNORE_RESULT static OEMCryptoResult RollGenerationNumber( UsageEntry* entry) { - if (!entry || entry->data.index >= MAX_NUMBER_OF_USAGE_ENTRIES) { + ABORT_IF_NULL(entry); + if (entry->data.index >= MAX_NUMBER_OF_USAGE_ENTRIES) { return OEMCrypto_ERROR_UNKNOWN_FAILURE; } uint64_t new_generation_number = g_usage_table.master_generation_number + 1; @@ -574,8 +575,8 @@ NO_IGNORE_RESULT static OEMCryptoResult RollGenerationNumber( NO_IGNORE_RESULT static OEMCryptoResult UpdateClockValues( UsageEntry* entry, ODK_ClockValues* clock_values) { - if (!entry || entry->data.index >= MAX_NUMBER_OF_USAGE_ENTRIES || - !clock_values) { + ABORT_IF_NULL(entry); + if (entry->data.index >= MAX_NUMBER_OF_USAGE_ENTRIES || !clock_values) { return OEMCrypto_ERROR_UNKNOWN_FAILURE; } if (entry->recent_decrypt) { @@ -621,6 +622,7 @@ OEMCryptoResult OPKI_TerminateUsageTable(void) { static NO_IGNORE_RESULT UsageEntryStatus GetUsageEntryStatusFromEntry(UsageEntry* entry) { + ABORT_IF_NULL(entry); if (entry->data.status == kInactive || entry->data.status == kInactiveUsed || entry->data.status == kInactiveUnused) { return USAGE_ENTRY_DEACTIVATED; @@ -696,6 +698,8 @@ OEMCryptoResult OPKI_CreateUsageTableHeader(uint8_t* header_buffer, /* Load a usage table header. */ OEMCryptoResult OPKI_LoadUsageTableHeader(const uint8_t* buffer, size_t buffer_length) { + RETURN_INVALID_CONTEXT_IF_NULL(buffer); + RETURN_INVALID_CONTEXT_IF_ZERO(buffer_length); /* Clear the table before we check the state -- we want to have an empty * table before we load a new one, or we want an empty one if there is an * error. */ @@ -761,7 +765,8 @@ OEMCryptoResult OPKI_LoadUsageTableHeader(const uint8_t* buffer, NO_IGNORE_RESULT static OEMCryptoResult GrabEntry(OEMCrypto_SESSION session_id, uint32_t usage_entry_number, UsageEntry** entry_ptr) { - if (!entry_ptr || usage_entry_number >= MAX_NUMBER_OF_USAGE_ENTRIES) { + ABORT_IF_NULL(entry_ptr); + if (usage_entry_number >= MAX_NUMBER_OF_USAGE_ENTRIES) { return OEMCrypto_ERROR_UNKNOWN_FAILURE; } if (g_usage_table_state != USAGE_TABLE_ACTIVE_STATE) { diff --git a/oemcrypto/opk/oemcrypto_ta/wtpi/README.md b/oemcrypto/opk/oemcrypto_ta/wtpi/README.md index 1aaa299..066082d 100644 --- a/oemcrypto/opk/oemcrypto_ta/wtpi/README.md +++ b/oemcrypto/opk/oemcrypto_ta/wtpi/README.md @@ -31,6 +31,7 @@ WTPI interfaces that are currently covered by wtpi_test: * wtpi_crypto_and_key_management_interface_layer1.h, * wtpi_crypto_asymmetric_interface.h, * wtpi_crc32_interface.h, +* wtpi_provisioning_4_interface.h, Please be cautious when updating parameter names in these interfaces. It can potentially break the auto-generated serialization functions used by the WTPI diff --git a/oemcrypto/opk/oemcrypto_ta/wtpi/wtpi_root_of_trust_interface_layer1.h b/oemcrypto/opk/oemcrypto_ta/wtpi/wtpi_root_of_trust_interface_layer1.h index 9d1ce70..f2b2538 100644 --- a/oemcrypto/opk/oemcrypto_ta/wtpi/wtpi_root_of_trust_interface_layer1.h +++ b/oemcrypto/opk/oemcrypto_ta/wtpi/wtpi_root_of_trust_interface_layer1.h @@ -17,7 +17,8 @@ extern "C" { #endif /** @defgroup layer1-rot Layer 1 Root Of Trust - * Keybox access functions called directly by OPK code. + * Keybox(Provisioning 2.0) or OEMCertificate(Provisioning 3.0) access + * functions called directly by OPK code. * * This is the top layer of the porting layer. The OPK directly calls * functions in this file. Partners have the option to implement these functions @@ -192,9 +193,9 @@ OEMCryptoResult WTPI_WrapOEMCert(const uint8_t* input, size_t input_length, * @retval OEMCrypto_SUCCESS success * @retval OEMCrypto_ERROR_INVALID_CONTEXT if |wrapped_cert| is NULL, or if * |wrapped_cert| can not be parsed - * @retal OEMCrypto_ERROR_INVALID_OEM_CERTIFICATE if the OEM cert is not + * @retval OEMCrypto_ERROR_INVALID_OEM_CERTIFICATE if the OEM cert is not * valid - * @retal OEMCrypto_ERROR_INVALID_KEY if the OEM private key is not + * @retval OEMCrypto_ERROR_INVALID_KEY if the OEM private key is not * valid * @retval OEMCrypto_ERROR_UNKNOWN_FAILURE otherwise */ @@ -226,9 +227,9 @@ OEMCryptoResult WTPI_LoadOEMPublicCertificate(uint8_t* output, * Caller retains ownership of all parameters. * * @retval OEMCrypto_SUCCESS success - * @retal OEMCrypto_ERROR_INVALID_OEM_CERTIFICATE if the OEM cert is not + * @retval OEMCrypto_ERROR_INVALID_OEM_CERTIFICATE if the OEM cert is not * valid - * @retal OEMCrypto_ERROR_INVALID_KEY if the OEM private key is not + * @retval OEMCrypto_ERROR_INVALID_KEY if the OEM private key is not * valid * @retval OEMCrypto_ERROR_NOT_IMPLEMENTED if Provisioning 3.0 is not enabled */ diff --git a/oemcrypto/opk/oemcrypto_ta/wtpi_reference/cose_util.c b/oemcrypto/opk/oemcrypto_ta/wtpi_reference/cose_util.c index 09de797..84da4d2 100644 --- a/oemcrypto/opk/oemcrypto_ta/wtpi_reference/cose_util.c +++ b/oemcrypto/opk/oemcrypto_ta/wtpi_reference/cose_util.c @@ -384,7 +384,7 @@ static OEMCryptoResult GenerateEncodedBccPayload( // be empty. CborWriteInt(kIssuerLabel, &cbor_out); if (entry_index == 0) { - strcpy(str_buf, "issuer 0"); + snprintf(str_buf, sizeof(str_buf), "issuer 0"); } else { // The issuer of current entry is the subject of its parent entry. snprintf(str_buf, sizeof(str_buf), "entry %u", entry_index); diff --git a/oemcrypto/opk/oemcrypto_ta/wtpi_reference/device_info_util.c b/oemcrypto/opk/oemcrypto_ta/wtpi_reference/device_info_util.c index 8e37e23..a9ad7af 100644 --- a/oemcrypto/opk/oemcrypto_ta/wtpi_reference/device_info_util.c +++ b/oemcrypto/opk/oemcrypto_ta/wtpi_reference/device_info_util.c @@ -30,9 +30,10 @@ static OEMCryptoResult ReadDeviceInfoMapTstrWithKey(struct CborIn* in, const char* key, size_t max_length, char* val) { - RETURN_INVALID_CONTEXT_IF_NULL(in); - RETURN_INVALID_CONTEXT_IF_NULL(key); - RETURN_INVALID_CONTEXT_IF_NULL(val); + ABORT_IF_NULL(in); + ABORT_IF_NULL(key); + ABORT_IF_ZERO(max_length); + ABORT_IF_NULL(val); const char* val_str; size_t val_str_size = 0; if (CborReadTstr(in, &val_str_size, &val_str) != CBOR_READ_RESULT_OK) { @@ -55,6 +56,11 @@ static OEMCryptoResult ReadDeviceInfoMapBstrWithKey(struct CborIn* in, size_t max_length, uint8_t* val, size_t* val_size) { + ABORT_IF_NULL(in); + ABORT_IF_NULL(key); + ABORT_IF_ZERO(max_length); + ABORT_IF_NULL(val); + ABORT_IF_NULL(val_size); const uint8_t* val_bstr; size_t val_bstr_size = 0; if (CborReadBstr(in, &val_bstr_size, &val_bstr) != CBOR_READ_RESULT_OK) { @@ -76,6 +82,9 @@ static OEMCryptoResult ReadDeviceInfoMapBstrWithKey(struct CborIn* in, static OEMCryptoResult ReadDeviceInfoMapUintWithKey(struct CborIn* in, const char* key, uint64_t* val) { + ABORT_IF_NULL(in); + ABORT_IF_NULL(key); + ABORT_IF_NULL(val); if (CborReadUint(in, val) != CBOR_READ_RESULT_OK) { LOGE("Failed to read %s value from device_info map", key); return OEMCrypto_ERROR_UNKNOWN_FAILURE; @@ -86,6 +95,8 @@ static OEMCryptoResult ReadDeviceInfoMapUintWithKey(struct CborIn* in, // Write DeviceInfo struct to Cbor map. Caller ensures the pointer is not NULL. static OEMCryptoResult WriteDeviceInfoToCbor(const DeviceInfo* device_info, struct CborOut* cbor_out) { + ABORT_IF_NULL(device_info); + ABORT_IF_NULL(cbor_out); CborWriteMap(DEVICE_INFO_ENTRY_COUNT_V3, cbor_out); // Write key-values in canonical order, which is required when the device info // map is validated by the server. Current CBOR C library doesn't provide diff --git a/oemcrypto/opk/oemcrypto_ta/wtpi_reference/ecc_util.c b/oemcrypto/opk/oemcrypto_ta/wtpi_reference/ecc_util.c index fb1d83f..907ff7a 100644 --- a/oemcrypto/opk/oemcrypto_ta/wtpi_reference/ecc_util.c +++ b/oemcrypto/opk/oemcrypto_ta/wtpi_reference/ecc_util.c @@ -6,6 +6,7 @@ #include +#include "oemcrypto_check_macros.h" #include "oemcrypto_key_types.h" #include "openssl/bio.h" #include "openssl/crypto.h" @@ -33,6 +34,7 @@ /* Returns the OpenSSL defined curve ID. Otherwise 0 is returned. Caller may assume that any error details will be logged. */ static int GetCurveId(const EC_KEY* key) { + ABORT_IF_NULL(key); const EC_GROUP* const group = EC_KEY_get0_group(key); if (group == NULL) { LOGE("EC_KEY_get0_group returned NULL"); diff --git a/oemcrypto/opk/oemcrypto_ta/wtpi_reference/prov30_factory_util.c b/oemcrypto/opk/oemcrypto_ta/wtpi_reference/prov30_factory_util.c index c42975f..f8c89dc 100644 --- a/oemcrypto/opk/oemcrypto_ta/wtpi_reference/prov30_factory_util.c +++ b/oemcrypto/opk/oemcrypto_ta/wtpi_reference/prov30_factory_util.c @@ -24,6 +24,8 @@ Input format for |certs_and_key|: +-----------------------+----------------------+--------------------------+ | Private Key | +-----------------------+ +| (DER-encoded PKCS#8) | ++-----------------------+ */ OEMCryptoResult ParseCertificateChainAndKey(const uint8_t* certs_and_key, size_t certs_and_key_length, diff --git a/oemcrypto/opk/oemcrypto_ta/wtpi_reference/wtpi_clock_and_gn_layer1.c b/oemcrypto/opk/oemcrypto_ta/wtpi_reference/wtpi_clock_and_gn_layer1.c index 3fd587a..15cdcdf 100644 --- a/oemcrypto/opk/oemcrypto_ta/wtpi_reference/wtpi_clock_and_gn_layer1.c +++ b/oemcrypto/opk/oemcrypto_ta/wtpi_reference/wtpi_clock_and_gn_layer1.c @@ -131,7 +131,7 @@ static OEMCryptoResult SaveData(void) { static OEMCryptoResult GetTrustedTimeAndSave(uint64_t* time_in_s, bool force_save) { - RETURN_INVALID_CONTEXT_IF_NULL(time_in_s); + ABORT_IF_NULL(time_in_s); OEMCryptoResult status = OEMCrypto_SUCCESS; if (!gInitialized) { status = InitializeData(); @@ -211,5 +211,6 @@ OEMCryptoResult WTPI_TerminateClock(void) { OEMCrypto_Clock_Security_Level WTPI_GetClockType(void) { return kSecureTimer; } OEMCryptoResult WTPI_GetTrustedTime(uint64_t* time_in_s) { + RETURN_INVALID_CONTEXT_IF_NULL(time_in_s); return GetTrustedTimeAndSave(time_in_s, false); } diff --git a/oemcrypto/opk/oemcrypto_ta/wtpi_reference/wtpi_crypto_and_key_management_layer1_hw.c b/oemcrypto/opk/oemcrypto_ta/wtpi_reference/wtpi_crypto_and_key_management_layer1_hw.c index bda8123..65232c9 100644 --- a/oemcrypto/opk/oemcrypto_ta/wtpi_reference/wtpi_crypto_and_key_management_layer1_hw.c +++ b/oemcrypto/opk/oemcrypto_ta/wtpi_reference/wtpi_crypto_and_key_management_layer1_hw.c @@ -63,8 +63,9 @@ DEFINE_OBJECT_TABLE(key_table, WTPI_K1_SymmetricKey, MAX_NUMBER_OF_KEYS, NULL); static OEMCryptoResult EncryptAndSignKey(WTPI_K2_SymmetricKey_Handle key_handle, uint32_t context, uint8_t* out, size_t out_size) { - if (key_handle == NULL || out == NULL || - out_size < sizeof(SignedWrappedKeyData)) { + ABORT_IF_NULL(key_handle); + ABORT_IF_NULL(out); + if (out_size < sizeof(SignedWrappedKeyData)) { return OEMCrypto_ERROR_INVALID_CONTEXT; } @@ -102,9 +103,8 @@ static OEMCryptoResult EncryptAndSignKey(WTPI_K2_SymmetricKey_Handle key_handle, static OEMCryptoResult VerifyAndDecryptKey( uint32_t context, const uint8_t* wrapped_data, SymmetricKeyType key_type, KeySize key_size, WTPI_K2_SymmetricKey_Handle* out_key_handle) { - if (wrapped_data == NULL || out_key_handle == NULL) { - return OEMCrypto_ERROR_INVALID_CONTEXT; - } + ABORT_IF_NULL(wrapped_data); + ABORT_IF_NULL(out_key_handle); const SignedWrappedKeyData* wrapped = (const SignedWrappedKeyData*)wrapped_data; @@ -161,9 +161,8 @@ static bool IsKeyHandleValid(WTPI_K1_SymmetricKey_Handle key_handle) { static OEMCryptoResult GetKeyType(WTPI_K1_SymmetricKey_Handle key_handle, SymmetricKeyType* type) { - if (key_handle == NULL || type == NULL) { - return OEMCrypto_ERROR_INVALID_CONTEXT; - } + ABORT_IF_NULL(key_handle); + ABORT_IF_NULL(type); if (!IsKeyHandleValid(key_handle)) { return OEMCrypto_ERROR_INVALID_CONTEXT; } @@ -441,9 +440,8 @@ OEMCryptoResult WTPI_K1_TerminateKeyManagement(void) { static OEMCryptoResult K1_CreateKeyHandle( WTPI_K2_SymmetricKey_Handle key_handle, SymmetricKeyType key_type, KeySize key_size, WTPI_K1_SymmetricKey_Handle* out_key_handle) { - if (key_handle == NULL || out_key_handle == NULL) { - return OEMCrypto_ERROR_INVALID_CONTEXT; - } + ABORT_IF_NULL(key_handle); + ABORT_IF_NULL(out_key_handle); if (!WTPI_K2_IsKeyHandleValid(key_handle)) { return OEMCrypto_ERROR_INVALID_CONTEXT; } diff --git a/oemcrypto/opk/oemcrypto_ta/wtpi_reference/wtpi_crypto_and_key_management_layer1_openssl.c b/oemcrypto/opk/oemcrypto_ta/wtpi_reference/wtpi_crypto_and_key_management_layer1_openssl.c index c3a77eb..dfc37d5 100644 --- a/oemcrypto/opk/oemcrypto_ta/wtpi_reference/wtpi_crypto_and_key_management_layer1_openssl.c +++ b/oemcrypto/opk/oemcrypto_ta/wtpi_reference/wtpi_crypto_and_key_management_layer1_openssl.c @@ -94,9 +94,8 @@ static bool IsKeyHandleValid(WTPI_K1_SymmetricKey_Handle key_handle) { static OEMCryptoResult GetKeyType(WTPI_K1_SymmetricKey_Handle key_handle, SymmetricKeyType* type) { - if (key_handle == NULL || type == NULL) { - return OEMCrypto_ERROR_INVALID_CONTEXT; - } + ABORT_IF_NULL(key_handle); + ABORT_IF_NULL(type); if (!IsKeyHandleValid(key_handle)) { return OEMCrypto_ERROR_INVALID_CONTEXT; } @@ -208,6 +207,7 @@ cleanup: /* Caller to ensure the inputs are valid. */ static OEMCryptoResult PrepareCachedKey( WTPI_K1_SymmetricKey_Handle key_handle) { + ABORT_IF_NULL(key_handle); if (key_handle->is_key_cached) return OEMCrypto_SUCCESS; OEMCryptoResult result = VerifyAndDecryptKey( key_handle, key_handle->cached_key, sizeof(key_handle->cached_key)); diff --git a/oemcrypto/opk/oemcrypto_ta/wtpi_reference/wtpi_crypto_wrap_asymmetric.c b/oemcrypto/opk/oemcrypto_ta/wtpi_reference/wtpi_crypto_wrap_asymmetric.c index 816200d..5b9518c 100644 --- a/oemcrypto/opk/oemcrypto_ta/wtpi_reference/wtpi_crypto_wrap_asymmetric.c +++ b/oemcrypto/opk/oemcrypto_ta/wtpi_reference/wtpi_crypto_wrap_asymmetric.c @@ -28,6 +28,9 @@ OEMCryptoResult WTPI_GetWrappedAsymmetricKeySize( static OEMCryptoResult LoadRSAKeyIntoAsymmetricKeyHandle( const uint8_t* unwrapped_key, size_t unwrapped_key_length, WTPI_AsymmetricKey_Handle* key_handle, uint32_t* allowed_schemes) { + ABORT_IF_NULL(unwrapped_key); + ABORT_IF_NULL(key_handle); + ABORT_IF_NULL(allowed_schemes); size_t offset = 0; if (unwrapped_key_length >= 8 && crypto_memcmp(unwrapped_key, "SIGN", 4) == 0) { @@ -45,6 +48,10 @@ static OEMCryptoResult LoadRSAKeyIntoAsymmetricKeyHandle( static OEMCryptoResult LoadECCKeyInfoAsymmetricKeyHandle( const uint8_t* unwrapped_key, size_t unwrapped_key_length, WTPI_AsymmetricKey_Handle* key_handle, uint32_t* allowed_schemes) { + ABORT_IF_NULL(unwrapped_key); + ABORT_IF_ZERO(unwrapped_key_length); + ABORT_IF_NULL(key_handle); + ABORT_IF_NULL(allowed_schemes); *allowed_schemes = 0; return WTPI_CreateAsymmetricKeyHandle(unwrapped_key, unwrapped_key_length, DRM_ECC_PRIVATE_KEY, key_handle); @@ -64,9 +71,9 @@ OEMCryptoResult WTPI_UnwrapIntoAsymmetricKeyHandle( /* Unwrap encrypted key. */ uint8_t unwrapped_key[PKCS8_DRM_KEY_MAX_SIZE]; size_t unwrapped_key_length = sizeof(unwrapped_key); - OEMCryptoResult result = WTPI_VerifyAndDecrypt( - context, wrapped_key, wrapped_key_length, unwrapped_key, - &unwrapped_key_length); + OEMCryptoResult result = + WTPI_VerifyAndDecrypt(context, wrapped_key, wrapped_key_length, + unwrapped_key, &unwrapped_key_length); if (result == OEMCrypto_ERROR_SHORT_BUFFER) { result = OEMCrypto_ERROR_UNKNOWN_FAILURE; } diff --git a/oemcrypto/opk/oemcrypto_ta/wtpi_reference/wtpi_decrypt_sample.c b/oemcrypto/opk/oemcrypto_ta/wtpi_reference/wtpi_decrypt_sample.c index 62b1143..79510c4 100644 --- a/oemcrypto/opk/oemcrypto_ta/wtpi_reference/wtpi_decrypt_sample.c +++ b/oemcrypto/opk/oemcrypto_ta/wtpi_reference/wtpi_decrypt_sample.c @@ -8,6 +8,7 @@ #include #include "odk_endian.h" +#include "oemcrypto_check_macros.h" #include "oemcrypto_compiler_attributes.h" #include "oemcrypto_key_types.h" #include "oemcrypto_math.h" @@ -28,10 +29,11 @@ NO_IGNORE_RESULT static OEMCryptoResult DecryptToOutputBuffer_CBC( size_t cipher_data_length, const OPK_OutputBuffer* output_buffer, size_t output_offset) { if (cipher_data_length == 0) return OEMCrypto_SUCCESS; - if (key_handle == NULL || initial_iv == NULL || pattern == NULL || - cipher_data == NULL || output_buffer == NULL) { - return OEMCrypto_ERROR_INVALID_CONTEXT; - } + ABORT_IF_NULL(key_handle); + ABORT_IF_NULL(initial_iv); + ABORT_IF_NULL(pattern); + ABORT_IF_NULL(cipher_data); + ABORT_IF_NULL(output_buffer); OEMCryptoResult result = OPK_CheckOutputBounds(output_buffer, output_offset, cipher_data_length); if (result != OEMCrypto_SUCCESS) { @@ -96,9 +98,11 @@ NO_IGNORE_RESULT static OEMCryptoResult DecryptToOutputBuffer_CTR( size_t block_offset, const uint8_t* cipher_data, size_t cipher_data_length, const OPK_OutputBuffer* output_buffer, size_t output_offset) { if (cipher_data_length == 0) return OEMCrypto_SUCCESS; - if (key_handle == NULL || initial_iv == NULL || - block_offset >= AES_BLOCK_SIZE || cipher_data == NULL || - output_buffer == NULL) { + ABORT_IF_NULL(key_handle); + ABORT_IF_NULL(initial_iv); + ABORT_IF_NULL(cipher_data); + ABORT_IF_NULL(output_buffer); + if (block_offset >= AES_BLOCK_SIZE) { return OEMCrypto_ERROR_INVALID_CONTEXT; } OEMCryptoResult result = @@ -127,10 +131,12 @@ static OEMCryptoResult DecryptSubsample( const OEMCrypto_CENCEncryptPatternDesc* pattern, const uint8_t* cipher_data, const uint8_t* iv, const OPK_OutputBuffer* output_buffer, size_t output_offset, OEMCryptoCipherMode cipher_mode) { - if (key_handle == NULL || subsample == NULL || pattern == NULL || - cipher_data == NULL || iv == NULL || output_buffer == NULL) { - return OEMCrypto_ERROR_INVALID_CONTEXT; - } + ABORT_IF_NULL(key_handle); + ABORT_IF_NULL(subsample); + ABORT_IF_NULL(pattern); + ABORT_IF_NULL(cipher_data); + ABORT_IF_NULL(iv); + ABORT_IF_NULL(output_buffer); size_t subsample_length; if (OPK_AddOverflowUX(subsample->num_bytes_clear, subsample->num_bytes_encrypted, &subsample_length)) { diff --git a/oemcrypto/opk/oemcrypto_ta/wtpi_reference/wtpi_device_key.c b/oemcrypto/opk/oemcrypto_ta/wtpi_reference/wtpi_device_key.c index cff68e3..5670aea 100644 --- a/oemcrypto/opk/oemcrypto_ta/wtpi_reference/wtpi_device_key.c +++ b/oemcrypto/opk/oemcrypto_ta/wtpi_reference/wtpi_device_key.c @@ -6,6 +6,7 @@ #include #include "odk_endian.h" +#include "oemcrypto_check_macros.h" #include "oemcrypto_overflow.h" #include "wtpi_crypto_and_key_management_interface_layer1.h" @@ -49,9 +50,8 @@ typedef struct WrappedData_V2 { static OEMCryptoResult ReadWrapProtocolVersion(const WrappedData* data, uint32_t* version) { - if (data == NULL || version == NULL) { - return OEMCrypto_ERROR_INVALID_CONTEXT; - } + ABORT_IF_NULL(data); + ABORT_IF_NULL(version); uint32_t value = UINT32_MAX; memcpy(&value, data->version, sizeof(value)); @@ -73,9 +73,7 @@ OEMCryptoResult WTPI_GetEncryptAndSignSize(UNUSED uint32_t context, static OEMCryptoResult EncryptAndSign_V2(uint32_t context, const uint8_t* data, size_t data_size, uint8_t* out, size_t* out_size) { - if (!out_size) { - return OEMCrypto_ERROR_INVALID_CONTEXT; - } + ABORT_IF_NULL(out_size); size_t needed_size; OEMCryptoResult result = WTPI_GetEncryptAndSignSize(context, data_size, &needed_size); @@ -85,7 +83,8 @@ static OEMCryptoResult EncryptAndSign_V2(uint32_t context, const uint8_t* data, return OEMCrypto_ERROR_SHORT_BUFFER; } // Allow querying size without these buffers. - if (!data || !out) return OEMCrypto_ERROR_INVALID_CONTEXT; + ABORT_IF_NULL(data); + ABORT_IF_NULL(out); *out_size = needed_size; WrappedData* const wrapped_header = (WrappedData*)out; @@ -126,9 +125,10 @@ static OEMCryptoResult VerifyAndDecrypt_V1(uint32_t context, const uint8_t* data, size_t data_size, uint8_t* out, size_t* out_size) { - if (data == NULL || out == NULL || - data_size < sizeof(WrappedData) + sizeof(WrappedData_V1) || - out_size == NULL) { + ABORT_IF_NULL(data); + ABORT_IF_NULL(out); + ABORT_IF_NULL(out_size); + if (data_size < sizeof(WrappedData) + sizeof(WrappedData_V1)) { return OEMCrypto_ERROR_INVALID_CONTEXT; } @@ -185,9 +185,10 @@ static OEMCryptoResult VerifyAndDecrypt_V2(uint32_t context, const uint8_t* data, size_t data_size, uint8_t* out, size_t* out_size) { - if (data == NULL || out == NULL || - data_size < sizeof(WrappedData) + sizeof(WrappedData_V2) || - out_size == NULL) { + ABORT_IF_NULL(data); + ABORT_IF_NULL(out); + ABORT_IF_NULL(out_size); + if (data_size < sizeof(WrappedData) + sizeof(WrappedData_V2)) { return OEMCrypto_ERROR_INVALID_CONTEXT; } @@ -248,12 +249,20 @@ static OEMCryptoResult VerifyAndDecrypt_V2(uint32_t context, OEMCryptoResult WTPI_EncryptAndSign(uint32_t context, const uint8_t* data, size_t data_size, uint8_t* out, size_t* out_size) { + RETURN_INVALID_CONTEXT_IF_NULL(data); + RETURN_INVALID_CONTEXT_IF_ZERO(data_size); + RETURN_INVALID_CONTEXT_IF_NULL(out); + RETURN_INVALID_CONTEXT_IF_NULL(out_size); return EncryptAndSign_V2(context, data, data_size, out, out_size); } OEMCryptoResult WTPI_VerifyAndDecrypt(uint32_t context, const uint8_t* data, size_t data_size, uint8_t* out, size_t* out_size) { + RETURN_INVALID_CONTEXT_IF_NULL(data); + RETURN_INVALID_CONTEXT_IF_ZERO(data_size); + RETURN_INVALID_CONTEXT_IF_NULL(out); + RETURN_INVALID_CONTEXT_IF_NULL(out_size); if (data_size < sizeof(WrappedData)) return OEMCrypto_ERROR_INVALID_CONTEXT; uint32_t version = UINT32_MAX; OEMCryptoResult res = diff --git a/oemcrypto/opk/oemcrypto_ta/wtpi_reference/wtpi_provisioning_4.c b/oemcrypto/opk/oemcrypto_ta/wtpi_reference/wtpi_provisioning_4.c index 7e398a3..d3d015f 100644 --- a/oemcrypto/opk/oemcrypto_ta/wtpi_reference/wtpi_provisioning_4.c +++ b/oemcrypto/opk/oemcrypto_ta/wtpi_reference/wtpi_provisioning_4.c @@ -34,6 +34,7 @@ static const uint8_t kFakeBccEntrySecrets[BCC_ENTRY_COUNT][KEY_SIZE_128] = { #define DEVICE_KEY_DERIVATION_CONTEXT_SIZE 20 static void CreateDeviceKeyDerivationContext(uint8_t* out) { + ABORT_IF_NULL(out); const int32_t context_seed = DEVICE_KEY_PROV40_SEED; const uint32_t type_32 = (uint32_t)DERIVING_KEY; // Build a full context that is unique to this starting context / key type @@ -98,6 +99,10 @@ static OEMCryptoResult BuildPhase2BootCertificateChain( AsymmetricKeyType device_key_type, WTPI_AsymmetricKey_Handle device_private_key, uint8_t* out, size_t* out_length) { + ABORT_IF_NULL(device_public_key); + ABORT_IF_ZERO(device_public_key_length); + ABORT_IF_NULL(device_private_key); + ABORT_IF_NULL(out_length); AsymmetricKeyType entry_key_types[BCC_ENTRY_COUNT]; for (int i = 0; i < BCC_ENTRY_COUNT; ++i) { entry_key_types[i] = PROV40_ED25519_PRIVATE_KEY; diff --git a/oemcrypto/opk/oemcrypto_ta/wtpi_reference/wtpi_root_of_trust_layer1.c b/oemcrypto/opk/oemcrypto_ta/wtpi_reference/wtpi_root_of_trust_layer1.c index da47497..415d1de 100644 --- a/oemcrypto/opk/oemcrypto_ta/wtpi_reference/wtpi_root_of_trust_layer1.c +++ b/oemcrypto/opk/oemcrypto_ta/wtpi_reference/wtpi_root_of_trust_layer1.c @@ -11,15 +11,10 @@ #include #include -#include "openssl/bio.h" -#include "openssl/err.h" -#include "openssl/pem.h" -#include "openssl/rsa.h" -#include "openssl/x509.h" - #include "config/default.h" #include "cose_util.h" #include "odk_endian.h" +#include "oemcrypto_check_macros.h" #include "oemcrypto_compiler_attributes.h" #include "oemcrypto_key_types.h" #ifdef USE_PROVISIONING_30 @@ -48,6 +43,7 @@ static bool perform_renewal; static uint8_t renewed_key_data[sizeof(gKeybox.data)]; static uint32_t GetSystemId(const uint8_t* key_data) { + ABORT_IF_NULL(key_data); uint32_t nbo_system_id; memcpy(&nbo_system_id, &key_data[4], sizeof(nbo_system_id)); return oemcrypto_be32toh(nbo_system_id); @@ -200,7 +196,7 @@ OEMCryptoResult WTPI_ValidateKeybox(void) { static OEMCryptoResult GetProv4DeviceID(uint8_t* device_id, size_t device_id_length) { - if (device_id == NULL) return OEMCrypto_ERROR_INVALID_CONTEXT; + ABORT_IF_NULL(device_id); if (device_id_length < SHA256_DIGEST_LENGTH) { return OEMCrypto_ERROR_INVALID_CONTEXT; } @@ -248,7 +244,7 @@ static OEMCryptoResult GetProv4DeviceID(uint8_t* device_id, #ifdef USE_PROVISIONING_30 static OEMCryptoResult GetProv3DeviceID(uint8_t* device_id, size_t device_id_length) { - if (device_id == NULL) return OEMCrypto_ERROR_INVALID_CONTEXT; + ABORT_IF_NULL(device_id); if (device_id_length < SHA256_DIGEST_LENGTH) { return OEMCrypto_ERROR_INVALID_CONTEXT; } @@ -379,9 +375,7 @@ OEMCryptoResult WTPI_StripKeyboxAndReinstall(void) { // Load Provisioning 3.0 OEM private key to output buffer static OEMCryptoResult LoadOEMPrivateKey(uint8_t* output, size_t* output_length) { - if (output_length == NULL) { - return OEMCrypto_ERROR_INVALID_CONTEXT; - } + ABORT_IF_NULL(output_length); if (output == NULL || *output_length < MAX_PROV30_OEM_KEY_SIZE) { *output_length = MAX_PROV30_OEM_KEY_SIZE; return OEMCrypto_ERROR_SHORT_BUFFER; diff --git a/oemcrypto/opk/ports/linux/cas/tee/tuner_tee_serialization.c b/oemcrypto/opk/ports/linux/cas/tee/tuner_tee_serialization.c index e75233b..1c681df 100644 --- a/oemcrypto/opk/ports/linux/cas/tee/tuner_tee_serialization.c +++ b/oemcrypto/opk/ports/linux/cas/tee/tuner_tee_serialization.c @@ -67,10 +67,9 @@ ODK_MessageStatus Tuner_DispatchMessage(ODK_Message* request, OEMCrypto_CENCEncryptPatternDesc* pattern = (OEMCrypto_CENCEncryptPatternDesc*)OPK_VarAlloc( sizeof(OEMCrypto_CENCEncryptPatternDesc)); + if (!pattern) goto handle_out_of_memory; OPK_Init_OEMCrypto_CENCEncryptPatternDesc( (OEMCrypto_CENCEncryptPatternDesc*)pattern); - // OPK_Unpack_DecryptCENC_Request(request, &session, &samples, - // &samples_length, &pattern); OPK_Unpack_TunerHal_Decrypt_Request(request, &key_token, &key_token_length, &key_parity, &samples, &samples_length, &pattern); @@ -90,6 +89,12 @@ handle_invalid_request: LOGE("invalid request"); *response = CreateEmptyMessage(); return MESSAGE_STATUS_OK; + +handle_out_of_memory: + LOGE("out of memory"); + ODK_Message_SetStatus(request, MESSAGE_STATUS_OUT_OF_MEMORY); + *response = CreateEmptyMessage(); + return MESSAGE_STATUS_OK; } void OPK_Unpack_TunerHal_Decrypt_Request( diff --git a/oemcrypto/opk/ports/optee/Makefile b/oemcrypto/opk/ports/optee/Makefile index 82aac33..67d064a 100644 --- a/oemcrypto/opk/ports/optee/Makefile +++ b/oemcrypto/opk/ports/optee/Makefile @@ -45,6 +45,11 @@ IMPLEMENTER := your-name-here # This is copied to the OPK_CONFIG_PROVISIONING_METHOD macro OPTEE_PROVISIONING_METHOD ?= OEMCrypto_Keybox +ifeq ($(OPTEE_PROVISIONING_METHOD),OEMCrypto_OEMCertificate) +USE_PROVISIONING_30 := y +# Prov30 reference implementation requires OP-TEE 4.0.0 and after +USE_OPTEE_4 := y +endif # Key type generated by OEMCrypto_GenerateCertificateKeyPair() # Options are "RSA" or "ECC" diff --git a/oemcrypto/opk/ports/optee/README.md b/oemcrypto/opk/ports/optee/README.md index 472166b..f397a72 100644 --- a/oemcrypto/opk/ports/optee/README.md +++ b/oemcrypto/opk/ports/optee/README.md @@ -19,6 +19,12 @@ manifest when cloning and building OP-TEE). - Build OP-TEE with `CFG_WITH_SOFTWARE_PRNG=n` and, if not provided by the platform, implement `hw_get_random_bytes` to generate random data using a cryptographically-secure hardware RNG instead of the software PRNG. + - Current OP-TEE version 3.18.0 builds with `crypto_util_mbedtls.c` under + `wtpi_impl/util/`. OP-TEE version 4.0.0 or later is required to build with + `crypto_util_mbedtls_v3.c`. This is because OP-TEE version 4 includes a + version upgrade of the MBEDTLS library v3, which introduced some + non-backward compatible changes: + https://github.com/OP-TEE/optee_os/tree/4.0.0/lib/libmbedtls 2. Set the OP-TEE download destination to the environment variable OPTEE_DIR. 3. If the GCC toolchain is separate from the one included in $OPTEE_DIR/toolchains, set that path to OPTEE_TOOLCHAIN_DIR. diff --git a/oemcrypto/opk/ports/optee/host/oemcrypto_unittests/Makefile b/oemcrypto/opk/ports/optee/host/oemcrypto_unittests/Makefile index 466a045..810f0e7 100644 --- a/oemcrypto/opk/ports/optee/host/oemcrypto_unittests/Makefile +++ b/oemcrypto/opk/ports/optee/host/oemcrypto_unittests/Makefile @@ -22,8 +22,14 @@ include $(srcdir)/oemcrypto/opk/build/ree-sources.mk srcs += \ $(oemcrypto_unittests_sources) \ +srcs := $(filter-out $(oemcrypto_unittests_dir)/oemcrypto_test_main.cpp, $(srcs)) +srcs += \ + $(OPK_REPO_TOP)/oemcrypto/opk/ports/optee/host/oemcrypto_unittests/oemcrypto_unittests_main.cpp \ + $(OPK_REPO_TOP)/oemcrypto/opk/ports/optee/host/oemcrypto_unittests/install_prov30_rot.cpp \ + incs += \ $(oemcrypto_unittests_includes) \ + $(OPK_REPO_TOP)/oemcrypto/opk/ports/optee/host/oemcrypto_unittests \ ldflags = \ -lpthread \ diff --git a/oemcrypto/opk/ports/optee/host/oemcrypto_unittests/install_prov30_rot.cpp b/oemcrypto/opk/ports/optee/host/oemcrypto_unittests/install_prov30_rot.cpp new file mode 100644 index 0000000..81f9518 --- /dev/null +++ b/oemcrypto/opk/ports/optee/host/oemcrypto_unittests/install_prov30_rot.cpp @@ -0,0 +1,148 @@ +// Copyright 2024 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine +// License Agreement. + +#include "install_prov30_rot.h" + +#include +#include +#include + +#include "OEMCryptoCENC.h" +#include "oec_test_data.h" +#include "platform.h" +#include "string_conversions.h" + +namespace { + +constexpr size_t kAesBlockSize = 16; + +/* +This function concatenates the test Prov30 OEM certificate chain and key to the +format below: + ++-----------------------+----------------------+--------------------------+ +| Cert Chain Length | Certificate Chain | Key Length | ++-----------------------+----------------------+--------------------------+ +| (4 bytes, big-endian) | (DER-encoded PKCS#7) | (4 bytes, big-endian) | ++-----------------------+----------------------+--------------------------+ +| Private Key | ++-----------------------+ + +|oem_private_key| should be a RSA key in PKCS#8 PrivateKeyInfo format. +|oem_public_cert| should be a DER-encoded PKCS#7 certificate chain. + +The output will be consumed by OEMCrypto Prov30 factory functions: +1. It is wrapped by OEMCrypto_WrapKeyboxOrOEMCert(), and +2. The wrapped root of trust will be installed by +OEMCrypto_InstallKeyboxOrOEMCert(). Therefore, the OEMCrypto implementation of +the factory functions and the tool must have an agreement on the format above. +*/ +std::vector PrepareProv30OEMCertAndKey(const uint8_t* oem_public_cert, + size_t oem_public_cert_size, + const uint8_t* oem_private_key, + size_t oem_private_key_size) { + std::vector oem_cert_and_key; + // Calculate total size + size_t total_size = sizeof(uint32_t) + oem_public_cert_size + + sizeof(uint32_t) + oem_private_key_size; + oem_cert_and_key.resize(total_size); + + // Offset to track where to write in the output vector + size_t offset = 0; + // 1. Store public cert size (big-endian) + uint32_t networkOrderCertSize = htonl((uint32_t)oem_public_cert_size); + std::copy(reinterpret_cast(&networkOrderCertSize), + reinterpret_cast(&networkOrderCertSize) + + sizeof(uint32_t), + oem_cert_and_key.begin()); + offset += sizeof(uint32_t); + + // 2. Store public cert content + std::copy(oem_public_cert, oem_public_cert + oem_public_cert_size, + oem_cert_and_key.begin() + offset); + offset += oem_public_cert_size; + + // 3. Store private key size (big-endian) + uint32_t networkOrderKeySize = htonl((uint32_t)oem_private_key_size); + std::copy( + reinterpret_cast(&networkOrderKeySize), + reinterpret_cast(&networkOrderKeySize) + sizeof(uint32_t), + oem_cert_and_key.begin() + offset); + offset += sizeof(uint32_t); + + // 4. Store private key content + std::copy(oem_private_key, oem_private_key + oem_private_key_size, + oem_cert_and_key.begin() + offset); + return oem_cert_and_key; +} +} // namespace + +namespace wvoec { + +OEMCryptoResult InstallTestProv30RootOfTrust( + const uint8_t* oem_public_cert, const size_t oem_public_cert_size, + const uint8_t* oem_private_key, const size_t oem_private_key_size) { + if (oem_public_cert == nullptr || oem_private_key == nullptr || + oem_public_cert_size == 0 || oem_private_key_size == 0) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + + // 1. Prepare OEM cert and key. + std::vector oem_cert_and_key = + PrepareProv30OEMCertAndKey(oem_public_cert, oem_public_cert_size, + oem_private_key, oem_private_key_size); + if (oem_cert_and_key.empty()) { + std::cerr << "Failed to prepare OEM cert and key" << std::endl; + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + // Add padding. + const uint8_t padding = + kAesBlockSize - (oem_cert_and_key.size() % kAesBlockSize); + for (size_t i = 0; i < padding; i++) { + oem_cert_and_key.push_back(padding); + } + + // 2: Initialize OEMCrypto. + OEMCryptoResult sts = OEMCrypto_Initialize(); + if (sts != OEMCrypto_SUCCESS) { + std::cerr << "Failed to initialize: result = " << sts << std::endl; + return sts; + } + + // 3: Wrap OEM cert and key before calling install function. + const OEMCrypto_ProvisioningMethod method = OEMCrypto_GetProvisioningMethod(); + if (method != OEMCrypto_OEMCertificate) { + std::cerr << "OEMCrypto is not OEMCrypto_OEMCertificate: method = "; + std::cerr << method << std::endl; + OEMCrypto_Terminate(); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + std::vector wrapped_oem_cert_and_key; + size_t wrapped_oem_cert_and_key_size = 0; + sts = OEMCrypto_WrapKeyboxOrOEMCert( + oem_cert_and_key.data(), oem_cert_and_key.size(), + wrapped_oem_cert_and_key.data(), &wrapped_oem_cert_and_key_size, nullptr, + 0); + if (sts != OEMCrypto_ERROR_SHORT_BUFFER) { + OEMCrypto_Terminate(); + return sts; + } + wrapped_oem_cert_and_key.resize(wrapped_oem_cert_and_key_size); + sts = OEMCrypto_WrapKeyboxOrOEMCert( + oem_cert_and_key.data(), oem_cert_and_key.size(), + wrapped_oem_cert_and_key.data(), &wrapped_oem_cert_and_key_size, nullptr, + 0); + if (sts != OEMCrypto_SUCCESS) { + OEMCrypto_Terminate(); + return sts; + } + + // 4: Install the wrapped OEM cert and key. + sts = OEMCrypto_InstallKeyboxOrOEMCert(wrapped_oem_cert_and_key.data(), + wrapped_oem_cert_and_key_size); + OEMCrypto_Terminate(); + return sts; +} + +} // namespace wvoec diff --git a/oemcrypto/opk/ports/optee/host/oemcrypto_unittests/install_prov30_rot.h b/oemcrypto/opk/ports/optee/host/oemcrypto_unittests/install_prov30_rot.h new file mode 100644 index 0000000..4e9f2fc --- /dev/null +++ b/oemcrypto/opk/ports/optee/host/oemcrypto_unittests/install_prov30_rot.h @@ -0,0 +1,21 @@ +/* + * Copyright 2024 Google LLC. All Rights Reserved. This file and proprietary + * source code may only be used and distributed under the Widevine + * License Agreement. + */ + +#ifndef INSTALL_PROV30_ROT_H_ +#define INSTALL_PROV30_ROT_H_ + +#include "OEMCryptoCENC.h" + +namespace wvoec { + +OEMCryptoResult InstallTestProv30RootOfTrust(const uint8_t* oem_public_cert, + size_t oem_public_cert_size, + const uint8_t* oem_private_key, + size_t oem_private_key_size); + +} // namespace wvoec + +#endif diff --git a/oemcrypto/opk/ports/optee/host/oemcrypto_unittests/oemcrypto_unittests_main.cpp b/oemcrypto/opk/ports/optee/host/oemcrypto_unittests/oemcrypto_unittests_main.cpp new file mode 100644 index 0000000..56b3817 --- /dev/null +++ b/oemcrypto/opk/ports/optee/host/oemcrypto_unittests/oemcrypto_unittests_main.cpp @@ -0,0 +1,80 @@ +#include +#include + +#include "OEMCryptoCENC.h" +#include "install_prov30_rot.h" +#include "log.h" +#include "oec_device_features.h" +#include "oec_test_data.h" +#include "oemcrypto_corpus_generator_helper.h" +#include "test_sleep.h" + +static void acknowledge_cast() { + std::cout + << "==================================================================\n" + << "= This device is expected to load x509 certs as a cast receiver. =\n" + << "==================================================================\n"; +} + +int CheckAndInstallProv30ROT() { + OEMCryptoResult sts = OEMCrypto_Initialize(); + if (sts != OEMCrypto_SUCCESS) { + std::cerr << "Failed to initialize OEMCrypto: result = " << sts + << std::endl; + return 1; + } + const OEMCrypto_ProvisioningMethod method = OEMCrypto_GetProvisioningMethod(); + if (method == OEMCrypto_OEMCertificate) { + const OEMCryptoResult result = wvoec::InstallTestProv30RootOfTrust( + wvoec::kTestOEMPublicCertInfo2, sizeof(wvoec::kTestOEMPublicCertInfo2), + wvoec::kTestRSAPKCS8PrivateKeyInfo2_2048, + sizeof(wvoec::kTestRSAPKCS8PrivateKeyInfo2_2048)); + if (result != OEMCrypto_SUCCESS) { + std::cerr << "Failed to install prov30 test root of trust.\n"; + return 1; + } + } + OEMCrypto_Terminate(); + return 0; +} + +// This special main procedure is used instead of the standard GTest main, +// because we need to initialize the list of features supported by the device. +// Also, the test filter is updated based on the feature list. +int main(int argc, char** argv) { + bool is_cast_receiver = false; + int verbosity = 0; + // Skip the first element, which is the program name. + const std::vector args(argv + 1, argv + argc); + for (const std::string& arg : args) { + if (arg == "--generate_corpus") { + wvoec::SetGenerateCorpus(true); + } + if (arg == "--verbose" || arg == "-v") { + ++verbosity; + } else if (arg == "--cast") { + acknowledge_cast(); + is_cast_receiver = true; + } + if (arg == "--force_load_test_keybox") { + std::cerr << "The argument --force_load_test_keybox is obsolete.\n"; + return 1; + } + if (arg == "--fake_sleep") { + wvutil::TestSleep::set_real_sleep(false); + } + } + wvutil::g_cutoff = static_cast(verbosity); + wvoec::global_features.Initialize(); + if (is_cast_receiver) { + // Turn it on if passed in on the command line. Do not turn these tests off + // automtically -- instead, we'll let the caller filter them out if they + // need to. These tests will normally only run if the device claims to + // support being a cast receiver. + wvoec::global_features.set_cast_receiver(is_cast_receiver); + } + if (CheckAndInstallProv30ROT() != 0) return 1; + // Init GTest after device properties has been initialized. + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/oemcrypto/opk/ports/optee/ta/common/wtpi_impl/sources.mk b/oemcrypto/opk/ports/optee/ta/common/wtpi_impl/sources.mk index 9af604d..540c829 100644 --- a/oemcrypto/opk/ports/optee/ta/common/wtpi_impl/sources.mk +++ b/oemcrypto/opk/ports/optee/ta/common/wtpi_impl/sources.mk @@ -8,7 +8,7 @@ # well as stubs from `wtpi_useless` that must be replaced for a production # build. # -# This is not necessary for other ports. This only exists to consoldiate all of +# This is not necessary for other ports. This only exists to consolidate all of # the OPTEE-specific code in one place for use in sub.mk OPK_REPO_TOP ?= $(CDM_DIR) @@ -37,7 +37,6 @@ dice_includes += \ wtpi_impl_sources += \ $(wtpi_impl_dir)/util/ta_log.c \ - $(wtpi_impl_dir)/util/crypto_util_mbedtls.c \ $(wtpi_impl_dir)/util/device_info.c \ $(wtpi_ref_dir)/odk_endian.c \ $(wtpi_ref_dir)/cose_util.c \ @@ -80,6 +79,16 @@ else $(error OEMCRYPTO_GEN_KEYPAIR_TYPE make variable must be either ECC or RSA) endif +ifdef USE_OPTEE_4 +wtpi_impl_sources += $(wtpi_impl_dir)/util/crypto_util_mbedtls_v3.c +else +wtpi_impl_sources += $(wtpi_impl_dir)/util/crypto_util_mbedtls.c +endif + +ifdef USE_PROVISIONING_30 +wtpi_impl_sources += $(wtpi_impl_dir)/util/prov30_factory_util_mbedtls_v3.c +endif + boringssl_dir ?= $(OPK_REPO_TOP)/third_party/boringssl wtpi_impl_includes += \ $(wtpi_impl_dir) \ diff --git a/oemcrypto/opk/ports/optee/ta/common/wtpi_impl/util/crypto_util.h b/oemcrypto/opk/ports/optee/ta/common/wtpi_impl/util/crypto_util.h index 359ca62..8f52af5 100644 --- a/oemcrypto/opk/ports/optee/ta/common/wtpi_impl/util/crypto_util.h +++ b/oemcrypto/opk/ports/optee/ta/common/wtpi_impl/util/crypto_util.h @@ -35,6 +35,10 @@ typedef struct pkcs1_rsa { size_t coefficient_len; } pkcs1_rsa; +// An alternative to mbedtls_entropy_func due to some instability issues. +// https://github.com/OP-TEE/optee_os/issues/2856 +int EntropyFunc(void* data, unsigned char* output, size_t size); + /* * Free and erase a pkcs1_rsa. Does nothing if |key| is NULL. */ @@ -135,7 +139,6 @@ OEMCryptoResult EncodeECDSASignature(const uint8_t* sig, size_t sig_length, OEMCryptoResult DeriveEccKey(const uint8_t* seed, size_t seed_length, uint32_t curve_id, rfc5915_eckey* output); - /* * Encodes the EC key data as a SubjectPublicKeyData struct. The input data must * have the public X,Y keys and private key set, as well as the curve type. diff --git a/oemcrypto/opk/ports/optee/ta/common/wtpi_impl/util/crypto_util_mbedtls.c b/oemcrypto/opk/ports/optee/ta/common/wtpi_impl/util/crypto_util_mbedtls.c index 81e1fce..bed5447 100644 --- a/oemcrypto/opk/ports/optee/ta/common/wtpi_impl/util/crypto_util_mbedtls.c +++ b/oemcrypto/opk/ports/optee/ta/common/wtpi_impl/util/crypto_util_mbedtls.c @@ -27,6 +27,12 @@ b += ret; \ } while (0) +int EntropyFunc(void* data, unsigned char* output, size_t size) { + ((void)data); + TEE_GenerateRandom(output, size); + return 0; +} + /* * Takes an mbedtls_mpi pointer |src|, an empty buffer pointer |dest|, and * a 0 value |dest_len|. diff --git a/oemcrypto/opk/ports/optee/ta/common/wtpi_impl/util/crypto_util_mbedtls_v3.c b/oemcrypto/opk/ports/optee/ta/common/wtpi_impl/util/crypto_util_mbedtls_v3.c new file mode 100644 index 0000000..f20296a --- /dev/null +++ b/oemcrypto/opk/ports/optee/ta/common/wtpi_impl/util/crypto_util_mbedtls_v3.c @@ -0,0 +1,870 @@ +/* + * Copyright 2024 Google LLC. All Rights Reserved. This file and proprietary + * source code may only be used and distributed under the Widevine + * License Agreement. + */ + +#include "crypto_util.h" + +#include +#include + +#include "asn1write.h" +#include "bignum.h" +#include "ctr_drbg.h" +#include "ecp.h" +#include "entropy.h" +#include "odk_endian.h" +#include "pk.h" +#include "rsa.h" +#include "sha256.h" +#include "wtpi_memory_interface.h" + +// Helper for the mbedtls_asn1_write_* functions. Assumes the existence of +// a variable named `ret` with type `int`. +#define CHK_ASN1_WRITE(b, f) \ + do { \ + if ((ret = f) < 0) \ + return OEMCrypto_ERROR_UNKNOWN_FAILURE; \ + else \ + b += ret; \ + } while (0) + +int EntropyFunc(void* data, unsigned char* output, size_t size) { + ((void)data); + TEE_GenerateRandom(output, size); + return 0; +} + +/* + * Takes an mbedtls_mpi pointer |src|, an empty buffer pointer |dest|, and + * a 0 value |dest_len|. + * + * Extracts the length value from |src| and places that into |dest_len| + * + * Malloc's a buffer with size |dest_len| and assigns the buffer pointer to + * |dest| + * + * Extracts the data from |src| to |dest| with the mbed_mpi_write_binary + * function + * + */ +static OEMCryptoResult extract_mbedtls_mpi_param(mbedtls_mpi* src, + uint8_t** dest, + size_t* dest_len) { + if (src == NULL || dest == NULL || dest_len == NULL) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + + size_t len = mbedtls_mpi_size(src); + + uint8_t* data = TEE_Malloc(len, TEE_MALLOC_FILL_ZERO); + if (data == NULL) { + EMSG("Malloc failed, out of memory"); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + + int ret = mbedtls_mpi_write_binary(src, data, len); + if (ret != 0 || len == 0) { + WTPI_SecureZeroMemory(data, len); + TEE_Free(data); + EMSG("mbedtls_mpi_write_binary failed"); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + + *dest = data; + *dest_len = len; + + return OEMCrypto_SUCCESS; +} + +void Free_pkcs1_rsa(pkcs1_rsa* key) { + if (key == NULL) { + return; + } + + if (key->private_exp != NULL) { + WTPI_SecureZeroMemory(key->private_exp, key->private_exp_len); + } + + TEE_Free(key->modulus); + TEE_Free(key->public_exp); + TEE_Free(key->private_exp); + TEE_Free(key->prime1); + TEE_Free(key->prime2); + TEE_Free(key->exp1); + TEE_Free(key->exp2); + TEE_Free(key->coefficient); + + TEE_MemFill(key, 0, sizeof(*key)); +} + +OEMCryptoResult DecodePKCS8RSAPrivateKey(const uint8_t* input, + size_t input_length, + pkcs1_rsa* output) { + mbedtls_pk_context pk_ctx; + mbedtls_pk_init(&pk_ctx); + mbedtls_entropy_context entropy; + mbedtls_entropy_init(&entropy); + mbedtls_ctr_drbg_context ctr_drbg; + mbedtls_ctr_drbg_init(&ctr_drbg); + + const char* pers = "pk_parse_key"; + int ret = mbedtls_ctr_drbg_seed(&ctr_drbg, EntropyFunc, &entropy, + (const unsigned char*)pers, strlen(pers)); + if (ret != 0) { + mbedtls_pk_free(&pk_ctx); + mbedtls_entropy_free(&entropy); + mbedtls_ctr_drbg_free(&ctr_drbg); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + + ret = mbedtls_pk_parse_key(&pk_ctx, input, input_length, NULL, 0, + mbedtls_ctr_drbg_random, &ctr_drbg); + if (ret != 0) { + mbedtls_pk_free(&pk_ctx); + mbedtls_entropy_free(&entropy); + mbedtls_ctr_drbg_free(&ctr_drbg); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + + OEMCryptoResult res; + TEE_MemFill(output, 0, sizeof(*output)); + + mbedtls_mpi N, P, Q, D, E, DP, DQ, QP; + mbedtls_mpi_init(&N); + mbedtls_mpi_init(&P); + mbedtls_mpi_init(&Q); + mbedtls_mpi_init(&D); + mbedtls_mpi_init(&E); + mbedtls_mpi_init(&DP); + mbedtls_mpi_init(&DQ); + mbedtls_mpi_init(&QP); + + mbedtls_rsa_context* rsa_ctx = mbedtls_pk_rsa(pk_ctx); + + ret = mbedtls_rsa_export(rsa_ctx, &N, &P, &Q, &D, &E); + if (ret != 0) { + res = OEMCrypto_ERROR_UNKNOWN_FAILURE; + goto cleanup; + } + + ret = mbedtls_rsa_export_crt(rsa_ctx, &DP, &DQ, &QP); + if (ret != 0) { + res = OEMCrypto_ERROR_UNKNOWN_FAILURE; + goto cleanup; + } + + res = extract_mbedtls_mpi_param(&N, &output->modulus, &output->modulus_len); + if (res != OEMCrypto_SUCCESS) { + goto cleanup; + } + + res = extract_mbedtls_mpi_param(&E, &output->public_exp, + &output->public_exp_len); + if (res != OEMCrypto_SUCCESS) { + goto cleanup; + } + + res = extract_mbedtls_mpi_param(&D, &output->private_exp, + &output->private_exp_len); + if (res != OEMCrypto_SUCCESS) { + goto cleanup; + } + + res = extract_mbedtls_mpi_param(&P, &output->prime1, &output->prime1_len); + if (res != OEMCrypto_SUCCESS) { + goto cleanup; + } + + res = extract_mbedtls_mpi_param(&Q, &output->prime2, &output->prime2_len); + if (res != OEMCrypto_SUCCESS) { + goto cleanup; + } + + res = extract_mbedtls_mpi_param(&DP, &output->exp1, &output->exp1_len); + if (res != OEMCrypto_SUCCESS) { + goto cleanup; + } + + res = extract_mbedtls_mpi_param(&DQ, &output->exp2, &output->exp2_len); + if (res != OEMCrypto_SUCCESS) { + goto cleanup; + } + + res = extract_mbedtls_mpi_param(&QP, &output->coefficient, + &output->coefficient_len); + +cleanup: + + mbedtls_pk_free(&pk_ctx); + mbedtls_entropy_free(&entropy); + mbedtls_ctr_drbg_free(&ctr_drbg); + + mbedtls_mpi_free(&N); + mbedtls_mpi_free(&P); + mbedtls_mpi_free(&Q); + mbedtls_mpi_free(&D); + mbedtls_mpi_free(&E); + mbedtls_mpi_free(&DP); + mbedtls_mpi_free(&DQ); + mbedtls_mpi_free(&QP); + + if (res != OEMCrypto_SUCCESS) { + Free_pkcs1_rsa(output); + } + + return res; +} + +static OEMCryptoResult Helper_EncodeRSAKey( + const pkcs1_rsa* key, uint8_t* output, size_t* output_length, + int (*mbedtls_write_fn)(const mbedtls_pk_context* ctx, unsigned char* buf, + size_t size)) { + // import RSA data as raw values into mbedtls_rsa_context + mbedtls_pk_context pk_ctx; + mbedtls_pk_init(&pk_ctx); + int result = + mbedtls_pk_setup(&pk_ctx, mbedtls_pk_info_from_type(MBEDTLS_PK_RSA)); + if (result != 0) { + mbedtls_pk_free(&pk_ctx); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + + mbedtls_rsa_init(mbedtls_pk_rsa(pk_ctx)); + + result = mbedtls_rsa_import_raw(mbedtls_pk_rsa(pk_ctx), key->modulus, + key->modulus_len, NULL, 0, NULL, 0, + key->private_exp, key->private_exp_len, + key->public_exp, key->public_exp_len); + if (result < 0) { + mbedtls_pk_free(&pk_ctx); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + + // calculate remaining RSA parameters (P, Q) + result = mbedtls_rsa_complete(mbedtls_pk_rsa(pk_ctx)); + if (result < 0) { + mbedtls_pk_free(&pk_ctx); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + + // write RSA data in DER encoding to output + size_t original_output_length = *output_length; + int bytes_written = mbedtls_write_fn(&pk_ctx, output, *output_length); + if (bytes_written <= 0) { + mbedtls_pk_free(&pk_ctx); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + *output_length = bytes_written; + + // mbedtls ASN1 write functions write backwards from the end of the buffer. + // Re-align memory to the beginning of the buffer. + TEE_MemMove(output, output + original_output_length - *output_length, + *output_length); + + mbedtls_pk_free(&pk_ctx); + return OEMCrypto_SUCCESS; +} + +OEMCryptoResult EncodeRSAPrivateKey(const pkcs1_rsa* key, uint8_t* output, + size_t* output_length) { + if (key == NULL || key->modulus == NULL || key->private_exp == NULL || + key->public_exp == NULL || key->modulus_len == 0 || + key->private_exp_len == 0 || key->public_exp_len == 0 || output == NULL || + output_length == NULL) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + + return Helper_EncodeRSAKey(key, output, output_length, + mbedtls_pk_write_key_der); +} + +OEMCryptoResult EncodeRSAPublicKey(const pkcs1_rsa* key, uint8_t* output, + size_t* output_length) { + if (key == NULL || key->modulus == NULL || key->public_exp == NULL || + key->modulus_len == 0 || key->public_exp_len == 0 || output == NULL || + output_length == NULL) { + EMSG("Failing because of something null or 0"); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + + return Helper_EncodeRSAKey(key, output, output_length, + mbedtls_pk_write_pubkey_der); +} + +static uint32_t GlobalPlatformCurveId(mbedtls_ecp_group_id id) { + switch (id) { + case MBEDTLS_ECP_DP_SECP192R1: + return TEE_ECC_CURVE_NIST_P192; + + case MBEDTLS_ECP_DP_SECP224R1: + return TEE_ECC_CURVE_NIST_P224; + + case MBEDTLS_ECP_DP_SECP256R1: + return TEE_ECC_CURVE_NIST_P256; + + case MBEDTLS_ECP_DP_SECP384R1: + return TEE_ECC_CURVE_NIST_P384; + + case MBEDTLS_ECP_DP_SECP521R1: + return TEE_ECC_CURVE_NIST_P521; + + default: + return TEE_CRYPTO_ELEMENT_NONE; + } + + return TEE_CRYPTO_ELEMENT_NONE; +} + +static mbedtls_ecp_group_id MbedTlsCurveId(uint32_t id) { + switch (id) { + case TEE_ECC_CURVE_NIST_P192: + return MBEDTLS_ECP_DP_SECP192R1; + + case TEE_ECC_CURVE_NIST_P224: + return MBEDTLS_ECP_DP_SECP224R1; + + case TEE_ECC_CURVE_NIST_P256: + return MBEDTLS_ECP_DP_SECP256R1; + + case TEE_ECC_CURVE_NIST_P384: + return MBEDTLS_ECP_DP_SECP384R1; + + case TEE_ECC_CURVE_NIST_P521: + return MBEDTLS_ECP_DP_SECP521R1; + + default: + return MBEDTLS_ECP_DP_NONE; + } + + return MBEDTLS_ECP_DP_NONE; +} + +static size_t CurveNumBits(mbedtls_ecp_group_id id) { + switch (id) { + case MBEDTLS_ECP_DP_SECP192R1: + return 192; + + case MBEDTLS_ECP_DP_SECP224R1: + return 224; + + case MBEDTLS_ECP_DP_SECP256R1: + return 256; + + case MBEDTLS_ECP_DP_SECP384R1: + return 384; + + case MBEDTLS_ECP_DP_SECP521R1: + return 521; + + default: + return 0; + } + + return 0; +} + +static size_t ECCSignatureSize(size_t num_bits) { + // ECC signature size is the length of the ASN1-encoded SEQUENCE containing + // two INTEGER elements, each large enough to contain |num_bits| of data. + // These integers represent the curve point and proof portions of the + // signature. + + if (num_bits == 0) { + return 0; + } + + size_t len = 0; + + // start with INTEGER tag portions + // tag itself is 1 byte + len++; + + // add enough space to hold num_bits + size_t num_bytes = (num_bits + 7) / 8; + len += num_bytes; + + // we have to encode this size value in ASN1, so figure out how many bytes + // that requires + while (num_bytes > 0) { + len++; + num_bytes = num_bytes >> 8; + } + + // plus 1 in case of a negative number + len++; + + // there are two such INTEGER portions. + len += len; + + // encode sequence size information + num_bytes = len; + + while (num_bytes > 0) { + len++; + num_bytes = num_bytes >> 8; + } + + // one last byte for the SEQ tag + len++; + + return len; +} + +// If src size is smaller than desired size, reallocate src and front pad with +// 0s. Set src_size to desired size if successful. +static void zero_pad_and_realloc(uint8_t** src, size_t* src_size, + size_t desired_size) { + if (*src_size >= desired_size) return; + uint8_t* const temp = TEE_Malloc(desired_size, TEE_MALLOC_FILL_ZERO); + const size_t difference = desired_size - *src_size; + TEE_MemMove(temp + difference, *src, *src_size); + WTPI_SecureZeroMemory(*src, *src_size); + TEE_Free(*src); + *src = temp; + *src_size = desired_size; +} + +void Free_rfc5915_eckey(rfc5915_eckey* key) { + if (key == NULL) { + return; + } + + if (key->private_val != NULL) { + WTPI_SecureZeroMemory(key->private_val, key->private_val_len); + } + + TEE_Free(key->private_val); + TEE_Free(key->public_x); + TEE_Free(key->public_y); + + TEE_MemFill(key, 0, sizeof(*key)); +} + +OEMCryptoResult DecodePKCS8ECCPrivateKey(const uint8_t* input, + size_t input_length, + rfc5915_eckey* output) { + mbedtls_pk_context pk_ctx; + mbedtls_pk_init(&pk_ctx); + mbedtls_entropy_context entropy; + mbedtls_entropy_init(&entropy); + mbedtls_ctr_drbg_context ctr_drbg; + mbedtls_ctr_drbg_init(&ctr_drbg); + + const char* pers = "pk_parse_key"; + int ret = mbedtls_ctr_drbg_seed(&ctr_drbg, EntropyFunc, &entropy, + (const unsigned char*)pers, strlen(pers)); + if (ret != 0) { + mbedtls_pk_free(&pk_ctx); + mbedtls_entropy_free(&entropy); + mbedtls_ctr_drbg_free(&ctr_drbg); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + + ret = mbedtls_pk_parse_key(&pk_ctx, input, input_length, NULL, 0, + mbedtls_ctr_drbg_random, &ctr_drbg); + if (ret != 0) { + mbedtls_pk_free(&pk_ctx); + mbedtls_entropy_free(&entropy); + mbedtls_ctr_drbg_free(&ctr_drbg); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + + mbedtls_ecp_keypair* ec_ctx = mbedtls_pk_ec(pk_ctx); + + TEE_MemFill(output, 0, sizeof(*output)); + output->ecc_curve_type = + GlobalPlatformCurveId(ec_ctx->MBEDTLS_PRIVATE(grp).id); + output->ecc_curve_bits = CurveNumBits(ec_ctx->MBEDTLS_PRIVATE(grp).id); + output->max_signature_size = ECCSignatureSize(output->ecc_curve_bits); + + OEMCryptoResult res = + extract_mbedtls_mpi_param(&ec_ctx->MBEDTLS_PRIVATE(d), + &output->private_val, &output->private_val_len); + if (res != OEMCrypto_SUCCESS) { + goto cleanup; + } + + res = + extract_mbedtls_mpi_param(&ec_ctx->MBEDTLS_PRIVATE(Q).MBEDTLS_PRIVATE(X), + &output->public_x, &output->public_x_len); + if (res != OEMCrypto_SUCCESS) { + goto cleanup; + } + + res = + extract_mbedtls_mpi_param(&ec_ctx->MBEDTLS_PRIVATE(Q).MBEDTLS_PRIVATE(Y), + &output->public_y, &output->public_y_len); + if (res != OEMCrypto_SUCCESS) { + goto cleanup; + } + + const size_t ecc_curve_bytes = (output->ecc_curve_bits + 7) / 8; + zero_pad_and_realloc(&output->private_val, &output->private_val_len, + ecc_curve_bytes); + zero_pad_and_realloc(&output->public_x, &output->public_x_len, + ecc_curve_bytes); + zero_pad_and_realloc(&output->public_y, &output->public_y_len, + ecc_curve_bytes); + +cleanup: + mbedtls_pk_free(&pk_ctx); + mbedtls_entropy_free(&entropy); + mbedtls_ctr_drbg_free(&ctr_drbg); + + if (res != OEMCrypto_SUCCESS) { + Free_rfc5915_eckey(output); + } + + return res; +} + +OEMCryptoResult DecodeECCPublicKey(const uint8_t* input, size_t input_length, + rfc5915_eckey* output) { + mbedtls_pk_context pk_ctx; + mbedtls_pk_init(&pk_ctx); + int ret = mbedtls_pk_parse_public_key(&pk_ctx, input, input_length); + if (ret != 0) { + mbedtls_pk_free(&pk_ctx); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + + mbedtls_ecp_keypair* ec_ctx = mbedtls_pk_ec(pk_ctx); + + TEE_MemFill(output, 0, sizeof(*output)); + output->ecc_curve_type = + GlobalPlatformCurveId(ec_ctx->MBEDTLS_PRIVATE(grp).id); + output->ecc_curve_bits = CurveNumBits(ec_ctx->MBEDTLS_PRIVATE(grp).id); + output->max_signature_size = ECCSignatureSize(output->ecc_curve_bits); + + OEMCryptoResult res = + extract_mbedtls_mpi_param(&ec_ctx->MBEDTLS_PRIVATE(Q).MBEDTLS_PRIVATE(X), + &output->public_x, &output->public_x_len); + if (res != OEMCrypto_SUCCESS) { + goto cleanup; + } + + res = + extract_mbedtls_mpi_param(&ec_ctx->MBEDTLS_PRIVATE(Q).MBEDTLS_PRIVATE(Y), + &output->public_y, &output->public_y_len); + if (res != OEMCrypto_SUCCESS) { + goto cleanup; + } + + const size_t ecc_curve_bytes = (output->ecc_curve_bits + 7) / 8; + zero_pad_and_realloc(&output->public_x, &output->public_x_len, + ecc_curve_bytes); + zero_pad_and_realloc(&output->public_y, &output->public_y_len, + ecc_curve_bytes); + +cleanup: + mbedtls_pk_free(&pk_ctx); + + if (res != OEMCrypto_SUCCESS) { + Free_rfc5915_eckey(output); + } + + return res; +} + +// OP-TEE does not DER-encode the ECDSA signature. Instead it writes the raw +// R and S values of the signature to a buffer of key_size*2 length. The values +// are front-padded with zero so that they are each key_size in length. +OEMCryptoResult EncodeECDSASignature(const uint8_t* sig, size_t sig_length, + uint8_t* output, size_t* output_length) { + // Signature is assumed to be evenly split between R and S values. + if (sig_length % 2 != 0 || sig == NULL || output == NULL || + output_length == NULL) { + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + + // mbedtls_asn1_write_* functions work backwards from the end of the output + // buffer. Therefore, start with the last elements and work forward. + int ret = 0; + uint8_t* p = output + *output_length; + size_t b = 0; // bytes written; + size_t element_len = sig_length / 2; + + size_t s_len = element_len; + size_t r_len = element_len; + + // Write S value raw data, then length, and integer tag. + CHK_ASN1_WRITE(b, mbedtls_asn1_write_raw_buffer(&p, output, sig + element_len, + element_len)); + if ((sig[element_len] & 0x80) != 0) { + CHK_ASN1_WRITE(b, mbedtls_asn1_write_tag(&p, output, 0)); + s_len++; + } + + CHK_ASN1_WRITE(b, mbedtls_asn1_write_len(&p, output, s_len)); + CHK_ASN1_WRITE(b, mbedtls_asn1_write_tag(&p, output, MBEDTLS_ASN1_INTEGER)); + + // Write R value raw data, then length, and integer tag + CHK_ASN1_WRITE(b, + mbedtls_asn1_write_raw_buffer(&p, output, sig, element_len)); + if ((sig[0] & 0x80) != 0) { + CHK_ASN1_WRITE(b, mbedtls_asn1_write_tag(&p, output, 0)); + r_len++; + } + CHK_ASN1_WRITE(b, mbedtls_asn1_write_len(&p, output, r_len)); + CHK_ASN1_WRITE(b, mbedtls_asn1_write_tag(&p, output, MBEDTLS_ASN1_INTEGER)); + + // Write length of data so far, then final sequence tag. "Constructed" flags + // needed for X509 format + CHK_ASN1_WRITE(b, mbedtls_asn1_write_len(&p, output, b)); + CHK_ASN1_WRITE( + b, mbedtls_asn1_write_tag( + &p, output, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE)); + + // Since we've been writing backwards in the buffer, we might have written + // less than we initially thought. Realign everything to the beginning of the + // buffer. + TEE_MemMove(output, p, b); + *output_length = b; + + return OEMCrypto_SUCCESS; +} + +OEMCryptoResult DeriveEccKey(const uint8_t* seed, size_t seed_length, + uint32_t curve_id, rfc5915_eckey* output) { + if (seed == NULL || seed_length == 0 || output == NULL) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + + // grp will contain ECC generator point G + mbedtls_ecp_group grp; + mbedtls_ecp_group_init(&grp); + + // privkey will be the derived seed used to calculate the public key + mbedtls_mpi privkey; + mbedtls_mpi_init(&privkey); + + // pubkey will be the (X,Y) point calculated by multiplying privkey*G + mbedtls_ecp_point pubkey; + mbedtls_ecp_point_init(&pubkey); + + mbedtls_entropy_context entropy; + mbedtls_entropy_init(&entropy); + mbedtls_ctr_drbg_context ctr_drbg; + mbedtls_ctr_drbg_init(&ctr_drbg); + + // Derive 64 bytes worth of key material. + uint8_t derived[64]; +#if OPK_OPTEE_CONFIG_DEVICEKEY_NON_NIST_KDF == 1 + // Legacy method. Non-NIST. Should only be used on old devices looking to + // update to OPK v19. + mbedtls_sha256(seed, seed_length, derived, 0); + mbedtls_sha256(seed, seed_length, derived + 32, 0); +#else + // Using NIST SP 800 56Cr2 "One-step Key Derivation", where H=sha256 and + // FixedInfo=curve_id + { + uint32_t counter = 0; + size_t input_length = sizeof(counter) + seed_length + sizeof(curve_id); + uint8_t* input = TEE_Malloc(input_length, TEE_MALLOC_FILL_ZERO); + if (input == NULL) { + return OEMCrypto_ERROR_INSUFFICIENT_RESOURCES; + } + + counter++; + uint8_t* addr = input; + uint32_t counter_be = oemcrypto_htobe32(counter); + TEE_MemMove(addr, &counter_be, sizeof(counter_be)); + addr = addr + sizeof(counter); + TEE_MemMove(addr, seed, seed_length); + addr = addr + seed_length; + TEE_MemMove(addr, &curve_id, sizeof(curve_id)); + mbedtls_sha256(input, input_length, derived, 0); + + counter++; + counter_be = oemcrypto_htobe32(counter); + addr = input; + TEE_MemMove(addr, &counter_be, sizeof(counter_be)); + mbedtls_sha256(input, input_length, derived + 32, 0); + TEE_Free(input); + } +#endif + + OEMCryptoResult res; + TEE_MemFill(output, 0, sizeof(*output)); + + // Copy derived result into mbedtls mpi struct, treat it as the private key + mbedtls_ecp_group_id id = MbedTlsCurveId(curve_id); + size_t curve_len_bits = CurveNumBits(id); + size_t curve_len = (curve_len_bits + 7) / 8; + int mbedtls_res = mbedtls_mpi_read_binary(&privkey, derived, curve_len); + if (mbedtls_res < 0) { + res = OEMCrypto_ERROR_UNKNOWN_FAILURE; + goto cleanup; + } + + // Use a static generator point G for the provided curve + // mbedtls_ecp_group_load loads a known set of fields for a given curve + mbedtls_res = mbedtls_ecp_group_load(&grp, id); + if (mbedtls_res < 0) { + res = OEMCrypto_ERROR_UNKNOWN_FAILURE; + goto cleanup; + } + + // Initialize the Random Number Generator + const char* pers = "ecp_mul"; + mbedtls_res = mbedtls_ctr_drbg_seed(&ctr_drbg, EntropyFunc, &entropy, + (const unsigned char*)pers, strlen(pers)); + if (mbedtls_res < 0) { + res = OEMCrypto_ERROR_UNKNOWN_FAILURE; + goto cleanup; + } + + // Multiply private val by G + mbedtls_res = mbedtls_ecp_mul(&grp, &pubkey, &privkey, &grp.G, + mbedtls_ctr_drbg_random, &ctr_drbg); + if (mbedtls_res < 0) { + res = OEMCrypto_ERROR_UNKNOWN_FAILURE; + goto cleanup; + } + + // Copy results into output struct + output->ecc_curve_type = curve_id; + output->ecc_curve_bits = curve_len_bits; + output->max_signature_size = ECCSignatureSize(curve_len_bits); + + res = extract_mbedtls_mpi_param(&privkey, &output->private_val, + &output->private_val_len); + if (res != OEMCrypto_SUCCESS) { + goto cleanup; + } + + res = extract_mbedtls_mpi_param(&pubkey.MBEDTLS_PRIVATE(X), &output->public_x, + &output->public_x_len); + if (res != OEMCrypto_SUCCESS) { + goto cleanup; + } + + res = extract_mbedtls_mpi_param(&pubkey.MBEDTLS_PRIVATE(Y), &output->public_y, + &output->public_y_len); + if (res != OEMCrypto_SUCCESS) { + goto cleanup; + } + + zero_pad_and_realloc(&output->private_val, &output->private_val_len, + curve_len); + zero_pad_and_realloc(&output->public_x, &output->public_x_len, curve_len); + zero_pad_and_realloc(&output->public_y, &output->public_y_len, curve_len); + +cleanup: + mbedtls_ecp_group_free(&grp); + mbedtls_ecp_point_free(&pubkey); + mbedtls_mpi_free(&privkey); + mbedtls_entropy_free(&entropy); + mbedtls_ctr_drbg_free(&ctr_drbg); + WTPI_SecureZeroMemory(derived, sizeof(derived)); + + if (res != OEMCrypto_SUCCESS) { + Free_rfc5915_eckey(output); + } + + return res; +} + +static OEMCryptoResult Helper_EncodeECCKey( + const rfc5915_eckey* key, uint8_t* output, size_t* output_length, + int (*mbedtls_write_fn)(const mbedtls_pk_context* ctx, unsigned char* buf, + size_t size)) { + mbedtls_pk_context pk_ctx; + mbedtls_pk_init(&pk_ctx); + int mbedtls_res = + mbedtls_pk_setup(&pk_ctx, mbedtls_pk_info_from_type(MBEDTLS_PK_ECKEY)); + if (mbedtls_res != 0) { + mbedtls_pk_free(&pk_ctx); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + + mbedtls_ecp_keypair_init(mbedtls_pk_ec(pk_ctx)); + + mbedtls_res = + mbedtls_ecp_group_load(&(mbedtls_pk_ec(pk_ctx)->MBEDTLS_PRIVATE(grp)), + MbedTlsCurveId(key->ecc_curve_type)); + if (mbedtls_res < 0) { + mbedtls_pk_free(&pk_ctx); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + + // Priv key + mbedtls_res = + mbedtls_mpi_read_binary(&(mbedtls_pk_ec(pk_ctx)->MBEDTLS_PRIVATE(d)), + key->private_val, key->private_val_len); + if (mbedtls_res < 0) { + mbedtls_pk_free(&pk_ctx); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + + // Public x, y, z. Set Z to const 1. + mbedtls_res = mbedtls_mpi_read_binary( + &(mbedtls_pk_ec(pk_ctx)->MBEDTLS_PRIVATE(Q).MBEDTLS_PRIVATE(X)), + key->public_x, key->public_x_len); + if (mbedtls_res < 0) { + mbedtls_pk_free(&pk_ctx); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + + mbedtls_res = mbedtls_mpi_read_binary( + &(mbedtls_pk_ec(pk_ctx)->MBEDTLS_PRIVATE(Q).MBEDTLS_PRIVATE(Y)), + key->public_y, key->public_y_len); + if (mbedtls_res < 0) { + mbedtls_pk_free(&pk_ctx); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + + mbedtls_res = mbedtls_mpi_lset( + &(mbedtls_pk_ec(pk_ctx)->MBEDTLS_PRIVATE(Q).MBEDTLS_PRIVATE(Z)), 1); + if (mbedtls_res < 0) { + mbedtls_pk_free(&pk_ctx); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + + // write EC data in DER encoding to output + size_t original_output_length = *output_length; + int bytes_written = mbedtls_write_fn(&pk_ctx, output, *output_length); + if (bytes_written <= 0) { + mbedtls_pk_free(&pk_ctx); + EMSG("mbedtls write function returned %x", bytes_written); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + *output_length = bytes_written; + + // mbedtls ASN1 write functions write backwards from the end of the buffer. + // Re-align memory to the beginning of the buffer. + TEE_MemMove(output, output + original_output_length - *output_length, + *output_length); + + mbedtls_pk_free(&pk_ctx); + return OEMCrypto_SUCCESS; +} + +// Encodes ECC public key as X509 SubjectPublicKeyInfo +OEMCryptoResult EncodeECCPublicKey(const rfc5915_eckey* key, uint8_t* output, + size_t* output_length) { + if (key == NULL || key->private_val == NULL || key->public_x == NULL || + key->public_y == NULL || key->private_val_len == 0 || + key->public_x_len == 0 || key->public_y_len == 0 || output == NULL || + output_length == NULL) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + + return Helper_EncodeECCKey(key, output, output_length, + mbedtls_pk_write_pubkey_der); +} + +OEMCryptoResult EncodeECCPrivateKey(const rfc5915_eckey* key, uint8_t* output, + size_t* output_length) { + if (key == NULL || key->private_val == NULL || key->public_x == NULL || + key->public_y == NULL || key->private_val_len == 0 || + key->public_x_len == 0 || key->public_y_len == 0 || output == NULL || + output_length == NULL) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + + return Helper_EncodeECCKey(key, output, output_length, + mbedtls_pk_write_key_der); +} diff --git a/oemcrypto/opk/ports/optee/ta/common/wtpi_impl/util/prov30_factory_util_mbedtls_v3.c b/oemcrypto/opk/ports/optee/ta/common/wtpi_impl/util/prov30_factory_util_mbedtls_v3.c new file mode 100644 index 0000000..0a669f1 --- /dev/null +++ b/oemcrypto/opk/ports/optee/ta/common/wtpi_impl/util/prov30_factory_util_mbedtls_v3.c @@ -0,0 +1,1551 @@ +/* + * Copyright 2024 Google LLC. All Rights Reserved. This file and proprietary + * source code may only be used and distributed under the Widevine + * License Agreement. + */ + +#include "prov30_factory_util.h" + +#include +#include + +#include "asn1.h" +#include "crypto_util.h" +#include "ctr_drbg.h" +#include "entropy.h" +#include "error.h" +#include "oemcrypto_check_macros.h" +#include "oid.h" +#include "pk.h" +#include "pkcs7.h" +#include "platform.h" +#include "x509.h" +#include "x509_crl.h" +#include "x509_crt.h" + +/****************************************************************************** + The following functions are copied from MBEDTLS library 3.4.0 + https://github.com/Mbed-TLS/mbedtls/tree/v3.4.0 + + Known issues in version v3.4.0: + 1. pkcs7_get_certificates(): + This function does not support parsing multiple certificates in a + certificate chain and will return an error. As a workaround, a locally + modified copy of this function is provided, which only parses the first + certificate and does not return an error if the parsing is successful. + See details at: + https://github.com/Mbed-TLS/mbedtls/blob/v3.4.0/library/pkcs7.c#L216 + 2. pkcs7_get_digest_algorithm_set() and pkcs7_get_signed_data(): + These functions assume that the DigestAlgorithmIdentifiers set in the signed + data of the certificate is always non-empty. However, this set can be empty + in some cases. Locally modified versions of these functions skip the + DigestAlgorithmIdentifiers set if it is empty. + + For all local changes made, search for "TODO(OPK, b/349676714)" in this file. + Functions with a leading comment "Copied from mbedtls-*" and not marked as + "modified" are taken from the MBEDTLS library without any changes. +*******************************************************************************/ + +// Copied from mbedtls-3.4.0 +static void pkcs7_free_signer_info(mbedtls_pkcs7_signer_info* signer) { + mbedtls_x509_name* name_cur; + mbedtls_x509_name* name_prv; + + if (signer == NULL) { + return; + } + + name_cur = signer->MBEDTLS_PRIVATE(issuer).next; + while (name_cur != NULL) { + name_prv = name_cur; + name_cur = name_cur->next; + mbedtls_free(name_prv); + } + signer->MBEDTLS_PRIVATE(issuer).next = NULL; +} + +// Copied from mbedtls-3.4.0 +static int pkcs7_get_signature(unsigned char** p, unsigned char* end, + mbedtls_pkcs7_buf* signature) { + int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; + size_t len = 0; + + ret = mbedtls_asn1_get_tag(p, end, &len, MBEDTLS_ASN1_OCTET_STRING); + if (ret != 0) { + return ret; + } + + signature->tag = MBEDTLS_ASN1_OCTET_STRING; + signature->len = len; + signature->p = *p; + + *p = *p + len; + + return 0; +} + +// Copied from mbedtls-3.4.0 +static int pkcs7_get_digest_algorithm(unsigned char** p, unsigned char* end, + mbedtls_x509_buf* alg) { + int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; + + if ((ret = mbedtls_asn1_get_alg_null(p, end, alg)) != 0) { + ret = MBEDTLS_ERROR_ADD(MBEDTLS_ERR_PKCS7_INVALID_ALG, ret); + } + + return ret; +} + +// Copied from mbedtls-3.4.0 +static int pkcs7_get_version(unsigned char** p, unsigned char* end, int* ver) { + int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; + + ret = mbedtls_asn1_get_int(p, end, ver); + if (ret != 0) { + ret = MBEDTLS_ERROR_ADD(MBEDTLS_ERR_PKCS7_INVALID_VERSION, ret); + } + + /* If version != 1, return invalid version */ + if (*ver != MBEDTLS_PKCS7_SUPPORTED_VERSION) { + ret = MBEDTLS_ERR_PKCS7_INVALID_VERSION; + } + + return ret; +} + +// Copied from mbedtls-3.4.0 +static int pkcs7_get_content_info_type(unsigned char** p, unsigned char* end, + unsigned char** seq_end, + mbedtls_pkcs7_buf* pkcs7) { + size_t len = 0; + int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; + unsigned char* start = *p; + + ret = mbedtls_asn1_get_tag(p, end, &len, + MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE); + if (ret != 0) { + *p = start; + return MBEDTLS_ERROR_ADD(MBEDTLS_ERR_PKCS7_INVALID_CONTENT_INFO, ret); + } + *seq_end = *p + len; + ret = mbedtls_asn1_get_tag(p, *seq_end, &len, MBEDTLS_ASN1_OID); + if (ret != 0) { + *p = start; + return MBEDTLS_ERROR_ADD(MBEDTLS_ERR_PKCS7_INVALID_CONTENT_INFO, ret); + } + + pkcs7->tag = MBEDTLS_ASN1_OID; + pkcs7->len = len; + pkcs7->p = *p; + *p += len; + + return ret; +} + +// Copied from mbedtls-3.4.0 +static int pkcs7_get_signer_info(unsigned char** p, unsigned char* end, + mbedtls_pkcs7_signer_info* signer, + mbedtls_x509_buf* alg) { + unsigned char *end_signer, *end_issuer_and_sn; + int asn1_ret = 0, ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; + size_t len = 0; + + asn1_ret = mbedtls_asn1_get_tag( + p, end, &len, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE); + if (asn1_ret != 0) { + goto out; + } + + end_signer = *p + len; + + ret = pkcs7_get_version(p, end_signer, &signer->MBEDTLS_PRIVATE(version)); + if (ret != 0) { + goto out; + } + + asn1_ret = mbedtls_asn1_get_tag( + p, end_signer, &len, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE); + if (asn1_ret != 0) { + goto out; + } + + end_issuer_and_sn = *p + len; + /* Parsing IssuerAndSerialNumber */ + signer->MBEDTLS_PRIVATE(issuer_raw).p = *p; + + asn1_ret = + mbedtls_asn1_get_tag(p, end_issuer_and_sn, &len, + MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE); + if (asn1_ret != 0) { + goto out; + } + + ret = mbedtls_x509_get_name(p, *p + len, &signer->MBEDTLS_PRIVATE(issuer)); + if (ret != 0) { + goto out; + } + + signer->MBEDTLS_PRIVATE(issuer_raw).len = + *p - signer->MBEDTLS_PRIVATE(issuer_raw).p; + + ret = mbedtls_x509_get_serial(p, end_issuer_and_sn, + &signer->MBEDTLS_PRIVATE(serial)); + if (ret != 0) { + goto out; + } + + /* ensure no extra or missing bytes */ + if (*p != end_issuer_and_sn) { + ret = MBEDTLS_ERR_PKCS7_INVALID_SIGNER_INFO; + goto out; + } + + ret = pkcs7_get_digest_algorithm(p, end_signer, + &signer->MBEDTLS_PRIVATE(alg_identifier)); + if (ret != 0) { + goto out; + } + + /* Check that the digest algorithm used matches the one provided earlier */ + if (signer->MBEDTLS_PRIVATE(alg_identifier).tag != alg->tag || + signer->MBEDTLS_PRIVATE(alg_identifier).len != alg->len || + TEE_MemCompare(signer->MBEDTLS_PRIVATE(alg_identifier).p, alg->p, + alg->len) != 0) { + ret = MBEDTLS_ERR_PKCS7_INVALID_SIGNER_INFO; + goto out; + } + + /* Assume authenticatedAttributes is nonexistent */ + ret = pkcs7_get_digest_algorithm( + p, end_signer, &signer->MBEDTLS_PRIVATE(sig_alg_identifier)); + if (ret != 0) { + goto out; + } + + ret = pkcs7_get_signature(p, end_signer, &signer->MBEDTLS_PRIVATE(sig)); + if (ret != 0) { + goto out; + } + + /* Do not permit any unauthenticated attributes */ + if (*p != end_signer) { + ret = MBEDTLS_ERR_PKCS7_INVALID_SIGNER_INFO; + } + +out: + if (asn1_ret != 0 || ret != 0) { + pkcs7_free_signer_info(signer); + ret = MBEDTLS_ERROR_ADD(MBEDTLS_ERR_PKCS7_INVALID_SIGNER_INFO, asn1_ret); + } + + return ret; +} + +// Copied from mbedtls-3.4.0 +static int pkcs7_get_signers_info_set(unsigned char** p, unsigned char* end, + mbedtls_pkcs7_signer_info* signers_set, + mbedtls_x509_buf* digest_alg) { + unsigned char* end_set; + int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; + int count = 0; + size_t len = 0; + + ret = mbedtls_asn1_get_tag(p, end, &len, + MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SET); + if (ret != 0) { + return MBEDTLS_ERROR_ADD(MBEDTLS_ERR_PKCS7_INVALID_SIGNER_INFO, ret); + } + + /* Detect zero signers */ + if (len == 0) { + return 0; + } + + end_set = *p + len; + + ret = pkcs7_get_signer_info(p, end_set, signers_set, digest_alg); + if (ret != 0) { + return ret; + } + count++; + + mbedtls_pkcs7_signer_info* prev = signers_set; + while (*p != end_set) { + mbedtls_pkcs7_signer_info* signer = + mbedtls_calloc(1, sizeof(mbedtls_pkcs7_signer_info)); + if (!signer) { + ret = MBEDTLS_ERR_PKCS7_ALLOC_FAILED; + goto cleanup; + } + + ret = pkcs7_get_signer_info(p, end_set, signer, digest_alg); + if (ret != 0) { + mbedtls_free(signer); + goto cleanup; + } + prev->MBEDTLS_PRIVATE(next) = signer; + prev = signer; + count++; + } + + return count; + +cleanup: + pkcs7_free_signer_info(signers_set); + mbedtls_pkcs7_signer_info* signer = signers_set->MBEDTLS_PRIVATE(next); + while (signer != NULL) { + prev = signer; + signer = signer->MBEDTLS_PRIVATE(next); + pkcs7_free_signer_info(prev); + mbedtls_free(prev); + } + signers_set->MBEDTLS_PRIVATE(next) = NULL; + return ret; +} + +// Copied from mbedtls-3.4.0 +static int x509_get_uid(unsigned char** p, const unsigned char* end, + mbedtls_x509_buf* uid, int n) { + int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; + + if (*p == end) { + return 0; + } + + uid->tag = **p; + + if ((ret = mbedtls_asn1_get_tag(p, end, &uid->len, + MBEDTLS_ASN1_CONTEXT_SPECIFIC | + MBEDTLS_ASN1_CONSTRUCTED | n)) != 0) { + if (ret == MBEDTLS_ERR_ASN1_UNEXPECTED_TAG) { + return 0; + } + + return MBEDTLS_ERROR_ADD(MBEDTLS_ERR_X509_INVALID_FORMAT, ret); + } + + uid->p = *p; + *p += uid->len; + + return 0; +} + +// Copied from mbedtls-3.4.0 +static int x509_get_basic_constraints(unsigned char** p, + const unsigned char* end, int* ca_istrue, + int* max_pathlen) { + int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; + size_t len; + + /* + * BasicConstraints ::= SEQUENCE { + * cA BOOLEAN DEFAULT FALSE, + * pathLenConstraint INTEGER (0..MAX) OPTIONAL } + */ + *ca_istrue = 0; /* DEFAULT FALSE */ + *max_pathlen = 0; /* endless */ + + if ((ret = mbedtls_asn1_get_tag( + p, end, &len, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE)) != + 0) { + return MBEDTLS_ERROR_ADD(MBEDTLS_ERR_X509_INVALID_EXTENSIONS, ret); + } + + if (*p == end) { + return 0; + } + + if ((ret = mbedtls_asn1_get_bool(p, end, ca_istrue)) != 0) { + if (ret == MBEDTLS_ERR_ASN1_UNEXPECTED_TAG) { + ret = mbedtls_asn1_get_int(p, end, ca_istrue); + } + + if (ret != 0) { + return MBEDTLS_ERROR_ADD(MBEDTLS_ERR_X509_INVALID_EXTENSIONS, ret); + } + + if (*ca_istrue != 0) { + *ca_istrue = 1; + } + } + + if (*p == end) { + return 0; + } + + if ((ret = mbedtls_asn1_get_int(p, end, max_pathlen)) != 0) { + return MBEDTLS_ERROR_ADD(MBEDTLS_ERR_X509_INVALID_EXTENSIONS, ret); + } + + if (*p != end) { + return MBEDTLS_ERROR_ADD(MBEDTLS_ERR_X509_INVALID_EXTENSIONS, + MBEDTLS_ERR_ASN1_LENGTH_MISMATCH); + } + + /* Do not accept max_pathlen equal to INT_MAX to avoid a signed integer + * overflow, which is an undefined behavior. */ + if (*max_pathlen == INT_MAX) { + return MBEDTLS_ERROR_ADD(MBEDTLS_ERR_X509_INVALID_EXTENSIONS, + MBEDTLS_ERR_ASN1_INVALID_LENGTH); + } + + (*max_pathlen)++; + + return 0; +} + +// Copied from mbedtls-3.4.0 +static int x509_get_ext_key_usage(unsigned char** p, const unsigned char* end, + mbedtls_x509_sequence* ext_key_usage) { + int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; + + if ((ret = mbedtls_asn1_get_sequence_of(p, end, ext_key_usage, + MBEDTLS_ASN1_OID)) != 0) { + return MBEDTLS_ERROR_ADD(MBEDTLS_ERR_X509_INVALID_EXTENSIONS, ret); + } + + /* Sequence length must be >= 1 */ + if (ext_key_usage->buf.p == NULL) { + return MBEDTLS_ERROR_ADD(MBEDTLS_ERR_X509_INVALID_EXTENSIONS, + MBEDTLS_ERR_ASN1_INVALID_LENGTH); + } + + return 0; +} + +// Copied from mbedtls-3.4.0 +static int x509_get_dates(unsigned char** p, const unsigned char* end, + mbedtls_x509_time* from, mbedtls_x509_time* to) { + int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; + size_t len; + + if ((ret = mbedtls_asn1_get_tag( + p, end, &len, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE)) != + 0) { + return MBEDTLS_ERROR_ADD(MBEDTLS_ERR_X509_INVALID_DATE, ret); + } + + end = *p + len; + + if ((ret = mbedtls_x509_get_time(p, end, from)) != 0) { + return ret; + } + + if ((ret = mbedtls_x509_get_time(p, end, to)) != 0) { + return ret; + } + + if (*p != end) { + return MBEDTLS_ERROR_ADD(MBEDTLS_ERR_X509_INVALID_DATE, + MBEDTLS_ERR_ASN1_LENGTH_MISMATCH); + } + + return 0; +} + +// Copied from mbedtls-3.4.0 +static int x509_get_certificate_policies( + unsigned char** p, const unsigned char* end, + mbedtls_x509_sequence* certificate_policies) { + int ret, parse_ret = 0; + size_t len; + mbedtls_asn1_buf* buf; + mbedtls_asn1_sequence* cur = certificate_policies; + + /* Get main sequence tag */ + ret = mbedtls_asn1_get_tag(p, end, &len, + MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE); + if (ret != 0) { + return MBEDTLS_ERROR_ADD(MBEDTLS_ERR_X509_INVALID_EXTENSIONS, ret); + } + + if (*p + len != end) { + return MBEDTLS_ERROR_ADD(MBEDTLS_ERR_X509_INVALID_EXTENSIONS, + MBEDTLS_ERR_ASN1_LENGTH_MISMATCH); + } + + /* + * Cannot be an empty sequence. + */ + if (len == 0) { + return MBEDTLS_ERROR_ADD(MBEDTLS_ERR_X509_INVALID_EXTENSIONS, + MBEDTLS_ERR_ASN1_LENGTH_MISMATCH); + } + + while (*p < end) { + mbedtls_x509_buf policy_oid; + const unsigned char* policy_end; + + /* + * Get the policy sequence + */ + if ((ret = mbedtls_asn1_get_tag( + p, end, &len, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE)) != + 0) { + return MBEDTLS_ERROR_ADD(MBEDTLS_ERR_X509_INVALID_EXTENSIONS, ret); + } + + policy_end = *p + len; + + if ((ret = mbedtls_asn1_get_tag(p, policy_end, &len, MBEDTLS_ASN1_OID)) != + 0) { + return MBEDTLS_ERROR_ADD(MBEDTLS_ERR_X509_INVALID_EXTENSIONS, ret); + } + + policy_oid.tag = MBEDTLS_ASN1_OID; + policy_oid.len = len; + policy_oid.p = *p; + + /* + * Only AnyPolicy is currently supported when enforcing policy. + */ + if (MBEDTLS_OID_CMP(MBEDTLS_OID_ANY_POLICY, &policy_oid) != 0) { + /* + * Set the parsing return code but continue parsing, in case this + * extension is critical. + */ + parse_ret = MBEDTLS_ERR_X509_FEATURE_UNAVAILABLE; + } + + /* Allocate and assign next pointer */ + if (cur->buf.p != NULL) { + if (cur->next != NULL) { + return MBEDTLS_ERR_X509_INVALID_EXTENSIONS; + } + + cur->next = mbedtls_calloc(1, sizeof(mbedtls_asn1_sequence)); + + if (cur->next == NULL) { + return MBEDTLS_ERROR_ADD(MBEDTLS_ERR_X509_INVALID_EXTENSIONS, + MBEDTLS_ERR_ASN1_ALLOC_FAILED); + } + + cur = cur->next; + } + + buf = &(cur->buf); + buf->tag = policy_oid.tag; + buf->p = policy_oid.p; + buf->len = policy_oid.len; + + *p += len; + + /* + * If there is an optional qualifier, then *p < policy_end + * Check the Qualifier len to verify it doesn't exceed policy_end. + */ + if (*p < policy_end) { + if ((ret = mbedtls_asn1_get_tag( + p, policy_end, &len, + MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE)) != 0) { + return MBEDTLS_ERROR_ADD(MBEDTLS_ERR_X509_INVALID_EXTENSIONS, ret); + } + /* + * Skip the optional policy qualifiers. + */ + *p += len; + } + + if (*p != policy_end) { + return MBEDTLS_ERROR_ADD(MBEDTLS_ERR_X509_INVALID_EXTENSIONS, + MBEDTLS_ERR_ASN1_LENGTH_MISMATCH); + } + } + + /* Set final sequence entry's next pointer to NULL */ + cur->next = NULL; + + if (*p != end) { + return MBEDTLS_ERROR_ADD(MBEDTLS_ERR_X509_INVALID_EXTENSIONS, + MBEDTLS_ERR_ASN1_LENGTH_MISMATCH); + } + + return parse_ret; +} + +// Copied from mbedtls-3.4.0 +static int x509_get_version(unsigned char** p, const unsigned char* end, + int* ver) { + int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; + size_t len; + + if ((ret = mbedtls_asn1_get_tag(p, end, &len, + MBEDTLS_ASN1_CONTEXT_SPECIFIC | + MBEDTLS_ASN1_CONSTRUCTED | 0)) != 0) { + if (ret == MBEDTLS_ERR_ASN1_UNEXPECTED_TAG) { + *ver = 0; + return 0; + } + + return MBEDTLS_ERROR_ADD(MBEDTLS_ERR_X509_INVALID_FORMAT, ret); + } + + end = *p + len; + + if ((ret = mbedtls_asn1_get_int(p, end, ver)) != 0) { + return MBEDTLS_ERROR_ADD(MBEDTLS_ERR_X509_INVALID_VERSION, ret); + } + + if (*p != end) { + return MBEDTLS_ERROR_ADD(MBEDTLS_ERR_X509_INVALID_VERSION, + MBEDTLS_ERR_ASN1_LENGTH_MISMATCH); + } + + return 0; +} + +// Copied from mbedtls-3.4.0 +static int x509_get_crt_ext(unsigned char** p, const unsigned char* end, + mbedtls_x509_crt* crt, mbedtls_x509_crt_ext_cb_t cb, + void* p_ctx) { + int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; + size_t len; + unsigned char *end_ext_data, *start_ext_octet, *end_ext_octet; + + if (*p == end) { + return 0; + } + + if ((ret = mbedtls_x509_get_ext(p, end, &crt->v3_ext, 3)) != 0) { + return ret; + } + + end = crt->v3_ext.p + crt->v3_ext.len; + while (*p < end) { + /* + * Extension ::= SEQUENCE { + * extnID OBJECT IDENTIFIER, + * critical BOOLEAN DEFAULT FALSE, + * extnValue OCTET STRING } + */ + mbedtls_x509_buf extn_oid = {0, 0, NULL}; + int is_critical = 0; /* DEFAULT FALSE */ + int ext_type = 0; + + if ((ret = mbedtls_asn1_get_tag( + p, end, &len, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE)) != + 0) { + return MBEDTLS_ERROR_ADD(MBEDTLS_ERR_X509_INVALID_EXTENSIONS, ret); + } + + end_ext_data = *p + len; + + /* Get extension ID */ + if ((ret = mbedtls_asn1_get_tag(p, end_ext_data, &extn_oid.len, + MBEDTLS_ASN1_OID)) != 0) { + return MBEDTLS_ERROR_ADD(MBEDTLS_ERR_X509_INVALID_EXTENSIONS, ret); + } + + extn_oid.tag = MBEDTLS_ASN1_OID; + extn_oid.p = *p; + *p += extn_oid.len; + + /* Get optional critical */ + if ((ret = mbedtls_asn1_get_bool(p, end_ext_data, &is_critical)) != 0 && + (ret != MBEDTLS_ERR_ASN1_UNEXPECTED_TAG)) { + return MBEDTLS_ERROR_ADD(MBEDTLS_ERR_X509_INVALID_EXTENSIONS, ret); + } + + /* Data should be octet string type */ + if ((ret = mbedtls_asn1_get_tag(p, end_ext_data, &len, + MBEDTLS_ASN1_OCTET_STRING)) != 0) { + return MBEDTLS_ERROR_ADD(MBEDTLS_ERR_X509_INVALID_EXTENSIONS, ret); + } + + start_ext_octet = *p; + end_ext_octet = *p + len; + + if (end_ext_octet != end_ext_data) { + return MBEDTLS_ERROR_ADD(MBEDTLS_ERR_X509_INVALID_EXTENSIONS, + MBEDTLS_ERR_ASN1_LENGTH_MISMATCH); + } + + /* + * Detect supported extensions + */ + ret = mbedtls_oid_get_x509_ext_type(&extn_oid, &ext_type); + + if (ret != 0) { + /* Give the callback (if any) a chance to handle the extension */ + if (cb != NULL) { + ret = cb(p_ctx, crt, &extn_oid, is_critical, *p, end_ext_octet); + if (ret != 0 && is_critical) { + return ret; + } + *p = end_ext_octet; + continue; + } + + /* No parser found, skip extension */ + *p = end_ext_octet; + + if (is_critical) { + /* Data is marked as critical: fail */ + return MBEDTLS_ERROR_ADD(MBEDTLS_ERR_X509_INVALID_EXTENSIONS, + MBEDTLS_ERR_ASN1_UNEXPECTED_TAG); + } + continue; + } + + /* Forbid repeated extensions */ + if ((crt->MBEDTLS_PRIVATE(ext_types) & ext_type) != 0) { + return MBEDTLS_ERR_X509_INVALID_EXTENSIONS; + } + + crt->MBEDTLS_PRIVATE(ext_types) |= ext_type; + + switch (ext_type) { + case MBEDTLS_X509_EXT_BASIC_CONSTRAINTS: + /* Parse basic constraints */ + if ((ret = x509_get_basic_constraints( + p, end_ext_octet, &crt->MBEDTLS_PRIVATE(ca_istrue), + &crt->MBEDTLS_PRIVATE(max_pathlen))) != 0) { + return ret; + } + break; + + case MBEDTLS_X509_EXT_KEY_USAGE: + /* Parse key usage */ + if ((ret = mbedtls_x509_get_key_usage( + p, end_ext_octet, &crt->MBEDTLS_PRIVATE(key_usage))) != 0) { + return ret; + } + break; + + case MBEDTLS_X509_EXT_EXTENDED_KEY_USAGE: + /* Parse extended key usage */ + if ((ret = x509_get_ext_key_usage(p, end_ext_octet, + &crt->ext_key_usage)) != 0) { + return ret; + } + break; + + case MBEDTLS_X509_EXT_SUBJECT_ALT_NAME: + /* Parse subject alt name */ + if ((ret = mbedtls_x509_get_subject_alt_name( + p, end_ext_octet, &crt->subject_alt_names)) != 0) { + return ret; + } + break; + + case MBEDTLS_X509_EXT_NS_CERT_TYPE: + /* Parse netscape certificate type */ + if ((ret = mbedtls_x509_get_ns_cert_type( + p, end_ext_octet, &crt->MBEDTLS_PRIVATE(ns_cert_type))) != 0) { + return ret; + } + break; + + case MBEDTLS_OID_X509_EXT_CERTIFICATE_POLICIES: + /* Parse certificate policies type */ + if ((ret = x509_get_certificate_policies( + p, end_ext_octet, &crt->certificate_policies)) != 0) { + /* Give the callback (if any) a chance to handle the extension + * if it contains unsupported policies */ + if (ret == MBEDTLS_ERR_X509_FEATURE_UNAVAILABLE && cb != NULL && + cb(p_ctx, crt, &extn_oid, is_critical, start_ext_octet, + end_ext_octet) == 0) { + break; + } + + if (is_critical) { + return ret; + } else + /* + * If MBEDTLS_ERR_X509_FEATURE_UNAVAILABLE is returned, then we + * cannot interpret or enforce the policy. However, it is up to + * the user to choose how to enforce the policies, + * unless the extension is critical. + */ + if (ret != MBEDTLS_ERR_X509_FEATURE_UNAVAILABLE) { + return ret; + } + } + break; + + default: + /* + * If this is a non-critical extension, which the oid layer + * supports, but there isn't an x509 parser for it, + * skip the extension. + */ + if (is_critical) { + return MBEDTLS_ERR_X509_FEATURE_UNAVAILABLE; + } else { + *p = end_ext_octet; + } + } + } + + if (*p != end) { + return MBEDTLS_ERROR_ADD(MBEDTLS_ERR_X509_INVALID_EXTENSIONS, + MBEDTLS_ERR_ASN1_LENGTH_MISMATCH); + } + + return 0; +} + +// Copied from mbedtls-3.4.0 +static int x509_crt_parse_der_core(mbedtls_x509_crt* crt, + const unsigned char* buf, size_t buflen, + int make_copy, mbedtls_x509_crt_ext_cb_t cb, + void* p_ctx) { + int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; + size_t len; + unsigned char *p, *end, *crt_end; + mbedtls_x509_buf sig_params1, sig_params2, sig_oid2; + + TEE_MemFill(&sig_params1, 0, sizeof(mbedtls_x509_buf)); + TEE_MemFill(&sig_params2, 0, sizeof(mbedtls_x509_buf)); + TEE_MemFill(&sig_oid2, 0, sizeof(mbedtls_x509_buf)); + + /* + * Check for valid input + */ + if (crt == NULL || buf == NULL) { + return MBEDTLS_ERR_X509_BAD_INPUT_DATA; + } + + /* Use the original buffer until we figure out actual length. */ + p = (unsigned char*)buf; + len = buflen; + end = p + len; + + /* + * Certificate ::= SEQUENCE { + * tbsCertificate TBSCertificate, + * signatureAlgorithm AlgorithmIdentifier, + * signatureValue BIT STRING } + */ + if (mbedtls_asn1_get_tag(&p, end, &len, + MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE) != + 0) { + mbedtls_x509_crt_free(crt); + return MBEDTLS_ERR_X509_INVALID_FORMAT; + } + + end = crt_end = p + len; + crt->raw.len = crt_end - buf; + + if (make_copy != 0) { + /* Create and populate a new buffer for the raw field. */ + crt->raw.p = p = mbedtls_calloc(1, crt->raw.len); + if (crt->raw.p == NULL) { + return MBEDTLS_ERR_X509_ALLOC_FAILED; + } + + TEE_MemMove(crt->raw.p, buf, crt->raw.len); + crt->MBEDTLS_PRIVATE(own_buffer) = 1; + + p += crt->raw.len - len; + end = crt_end = p + len; + } else { + crt->raw.p = (unsigned char*)buf; + crt->MBEDTLS_PRIVATE(own_buffer) = 0; + } + + /* + * TBSCertificate ::= SEQUENCE { + */ + crt->tbs.p = p; + + if ((ret = mbedtls_asn1_get_tag( + &p, end, &len, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE)) != + 0) { + mbedtls_x509_crt_free(crt); + return MBEDTLS_ERROR_ADD(MBEDTLS_ERR_X509_INVALID_FORMAT, ret); + } + + end = p + len; + crt->tbs.len = end - crt->tbs.p; + + /* + * Version ::= INTEGER { v1(0), v2(1), v3(2) } + * + * CertificateSerialNumber ::= INTEGER + * + * signature AlgorithmIdentifier + */ + if ((ret = x509_get_version(&p, end, &crt->version)) != 0 || + (ret = mbedtls_x509_get_serial(&p, end, &crt->serial)) != 0 || + (ret = mbedtls_x509_get_alg(&p, end, &crt->sig_oid, &sig_params1)) != 0) { + mbedtls_x509_crt_free(crt); + return ret; + } + + if (crt->version < 0 || crt->version > 2) { + mbedtls_x509_crt_free(crt); + return MBEDTLS_ERR_X509_UNKNOWN_VERSION; + } + + crt->version++; + + if ((ret = mbedtls_x509_get_sig_alg( + &crt->sig_oid, &sig_params1, &crt->MBEDTLS_PRIVATE(sig_md), + &crt->MBEDTLS_PRIVATE(sig_pk), &crt->MBEDTLS_PRIVATE(sig_opts))) != + 0) { + mbedtls_x509_crt_free(crt); + return ret; + } + + /* + * issuer Name + */ + crt->issuer_raw.p = p; + + if ((ret = mbedtls_asn1_get_tag( + &p, end, &len, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE)) != + 0) { + mbedtls_x509_crt_free(crt); + return MBEDTLS_ERROR_ADD(MBEDTLS_ERR_X509_INVALID_FORMAT, ret); + } + + if ((ret = mbedtls_x509_get_name(&p, p + len, &crt->issuer)) != 0) { + mbedtls_x509_crt_free(crt); + return ret; + } + + crt->issuer_raw.len = p - crt->issuer_raw.p; + + /* + * Validity ::= SEQUENCE { + * notBefore Time, + * notAfter Time } + * + */ + if ((ret = x509_get_dates(&p, end, &crt->valid_from, &crt->valid_to)) != 0) { + mbedtls_x509_crt_free(crt); + return ret; + } + + /* + * subject Name + */ + crt->subject_raw.p = p; + + if ((ret = mbedtls_asn1_get_tag( + &p, end, &len, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE)) != + 0) { + mbedtls_x509_crt_free(crt); + return MBEDTLS_ERROR_ADD(MBEDTLS_ERR_X509_INVALID_FORMAT, ret); + } + + if (len && (ret = mbedtls_x509_get_name(&p, p + len, &crt->subject)) != 0) { + mbedtls_x509_crt_free(crt); + return ret; + } + + crt->subject_raw.len = p - crt->subject_raw.p; + + /* + * SubjectPublicKeyInfo + */ + crt->pk_raw.p = p; + if ((ret = mbedtls_pk_parse_subpubkey(&p, end, &crt->pk)) != 0) { + mbedtls_x509_crt_free(crt); + return ret; + } + crt->pk_raw.len = p - crt->pk_raw.p; + + /* + * issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL, + * -- If present, version shall be v2 or v3 + * subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL, + * -- If present, version shall be v2 or v3 + * extensions [3] EXPLICIT Extensions OPTIONAL + * -- If present, version shall be v3 + */ + if (crt->version == 2 || crt->version == 3) { + ret = x509_get_uid(&p, end, &crt->issuer_id, 1); + if (ret != 0) { + mbedtls_x509_crt_free(crt); + return ret; + } + } + + if (crt->version == 2 || crt->version == 3) { + ret = x509_get_uid(&p, end, &crt->subject_id, 2); + if (ret != 0) { + mbedtls_x509_crt_free(crt); + return ret; + } + } + + if (crt->version == 3) { + ret = x509_get_crt_ext(&p, end, crt, cb, p_ctx); + if (ret != 0) { + mbedtls_x509_crt_free(crt); + return ret; + } + } + + if (p != end) { + mbedtls_x509_crt_free(crt); + return MBEDTLS_ERROR_ADD(MBEDTLS_ERR_X509_INVALID_FORMAT, + MBEDTLS_ERR_ASN1_LENGTH_MISMATCH); + } + + end = crt_end; + + /* + * } + * -- end of TBSCertificate + * + * signatureAlgorithm AlgorithmIdentifier, + * signatureValue BIT STRING + */ + if ((ret = mbedtls_x509_get_alg(&p, end, &sig_oid2, &sig_params2)) != 0) { + mbedtls_x509_crt_free(crt); + return ret; + } + + if (crt->sig_oid.len != sig_oid2.len || + TEE_MemCompare(crt->sig_oid.p, sig_oid2.p, crt->sig_oid.len) != 0 || + sig_params1.tag != sig_params2.tag || + sig_params1.len != sig_params2.len || + (sig_params1.len != 0 && + TEE_MemCompare(sig_params1.p, sig_params2.p, sig_params1.len) != 0)) { + mbedtls_x509_crt_free(crt); + return MBEDTLS_ERR_X509_SIG_MISMATCH; + } + + if ((ret = mbedtls_x509_get_sig(&p, end, &crt->MBEDTLS_PRIVATE(sig))) != 0) { + mbedtls_x509_crt_free(crt); + return ret; + } + + if (p != end) { + mbedtls_x509_crt_free(crt); + return MBEDTLS_ERROR_ADD(MBEDTLS_ERR_X509_INVALID_FORMAT, + MBEDTLS_ERR_ASN1_LENGTH_MISMATCH); + } + + return 0; +} + +// Copied from mbedtls-3.4.0 +static int mbedtls_x509_crt_parse_der_internal(mbedtls_x509_crt* chain, + const unsigned char* buf, + size_t buflen, int make_copy, + mbedtls_x509_crt_ext_cb_t cb, + void* p_ctx) { + int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; + mbedtls_x509_crt *crt = chain, *prev = NULL; + + /* + * Check for valid input + */ + if (crt == NULL || buf == NULL) { + return MBEDTLS_ERR_X509_BAD_INPUT_DATA; + } + + while (crt->version != 0 && crt->next != NULL) { + prev = crt; + crt = crt->next; + } + + /* + * Add new certificate on the end of the chain if needed. + */ + if (crt->version != 0 && crt->next == NULL) { + crt->next = mbedtls_calloc(1, sizeof(mbedtls_x509_crt)); + + if (crt->next == NULL) { + return MBEDTLS_ERR_X509_ALLOC_FAILED; + } + + prev = crt; + mbedtls_x509_crt_init(crt->next); + crt = crt->next; + } + + ret = x509_crt_parse_der_core(crt, buf, buflen, make_copy, cb, p_ctx); + if (ret != 0) { + if (prev) { + prev->next = NULL; + } + + if (crt != chain) { + mbedtls_free(crt); + } + + return ret; + } + + return 0; +} + +// Copied from mbedtls-3.4.0 +static int pkcs7_get_next_content_len(unsigned char** p, unsigned char* end, + size_t* len) { + int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; + + ret = mbedtls_asn1_get_tag( + p, end, len, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_CONTEXT_SPECIFIC); + if (ret != 0) { + ret = MBEDTLS_ERROR_ADD(MBEDTLS_ERR_PKCS7_INVALID_CONTENT_INFO, ret); + } else if ((size_t)(end - *p) != *len) { + ret = MBEDTLS_ERROR_ADD(MBEDTLS_ERR_PKCS7_INVALID_CONTENT_INFO, + MBEDTLS_ERR_ASN1_LENGTH_MISMATCH); + } + + return ret; +} + +// Copied from mbedtls-3.4.0, modified +static int pkcs7_get_certificates(unsigned char** p, unsigned char* end, + mbedtls_x509_crt* certs) { + int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; + size_t len1 = 0; + size_t len2 = 0; + unsigned char *end_set, *end_cert, *start; + + ret = mbedtls_asn1_get_tag( + p, end, &len1, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_CONTEXT_SPECIFIC); + if (ret == MBEDTLS_ERR_ASN1_UNEXPECTED_TAG) { + return 0; + } + if (ret != 0) { + return MBEDTLS_ERROR_ADD(MBEDTLS_ERR_PKCS7_INVALID_FORMAT, ret); + } + start = *p; + end_set = *p + len1; + + // CertificateChoices SEQUENCE + ret = mbedtls_asn1_get_tag(p, end_set, &len2, + MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE); + if (ret != 0) { + return MBEDTLS_ERROR_ADD(MBEDTLS_ERR_PKCS7_INVALID_CERT, ret); + } + + end_cert = *p + len2; + + /* + * This is to verify that there is only one signer certificate. It seems it is + * not easy to differentiate between the chain vs different signer's + * certificate. So, we support only the root certificate and the single + * signer. The behaviour would be improved with addition of multiple signer + * support. + */ + + // TODO(OPK, b/349676714): To add the support of parsing the entire cert + // chain. Currently it parses the first cert from the chain if there are + // multiple. + // https://github.com/Mbed-TLS/mbedtls/blob/v3.4.0/library/pkcs7.c#L216 + if (end_cert != end_set) { + // Workaround for parsing the first cert in the chain of multiple certs. + // Do not return an error. Skip to the end_set once the first cert is + // parsed. + // return MBEDTLS_ERR_PKCS7_FEATURE_UNAVAILABLE; + if (mbedtls_x509_crt_parse_der_internal(certs, start, end_cert - start, 1, + NULL, NULL) < 0) { + return MBEDTLS_ERR_PKCS7_INVALID_CERT; + } + *p = end_set; + } else { + // From the original mbedtls which assumes only one cert in the chain. + if (mbedtls_x509_crt_parse_der(certs, start, len1) < 0) { + return MBEDTLS_ERR_PKCS7_INVALID_CERT; + } + *p = end_cert; + } + + /* + * Since in this version we strictly support single certificate, and reaching + * here implies we have parsed successfully, we return 1. + */ + return 1; +} + +// Copied from mbedtls-3.4.0, modified +static int pkcs7_get_digest_algorithm_set(unsigned char** p, unsigned char* end, + mbedtls_x509_buf* alg) { + size_t len = 0; + int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; + + ret = mbedtls_asn1_get_tag(p, end, &len, + MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SET); + if (ret != 0) { + return MBEDTLS_ERROR_ADD(MBEDTLS_ERR_PKCS7_INVALID_ALG, ret); + } + + // TODO(OPK, b/349676714): mbedtls doesn't allow empty digest algorithm set + // although it is a valid case. Return OK when it is empty for now. + // https://github.com/Mbed-TLS/mbedtls/blob/v3.4.0/library/pkcs7.c#L155 + if (len == 0) { + return 0; + } + + end = *p + len; + + ret = mbedtls_asn1_get_alg_null(p, end, alg); + if (ret != 0) { + return MBEDTLS_ERROR_ADD(MBEDTLS_ERR_PKCS7_INVALID_ALG, ret); + } + + /** For now, it assumes there is only one digest algorithm specified **/ + if (*p != end) { + return MBEDTLS_ERR_PKCS7_FEATURE_UNAVAILABLE; + } + + return 0; +} + +// Copied from mbedtls-3.4.0, modified +static int pkcs7_get_signed_data(unsigned char* buf, size_t buflen, + mbedtls_pkcs7_signed_data* signed_data) { + unsigned char* p = buf; + unsigned char* end = buf + buflen; + unsigned char* end_content_info = NULL; + size_t len = 0; + int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; + + ret = mbedtls_asn1_get_tag(&p, end, &len, + MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE); + if (ret != 0) { + return MBEDTLS_ERROR_ADD(MBEDTLS_ERR_PKCS7_INVALID_FORMAT, ret); + } + + if (p + len != end) { + return MBEDTLS_ERROR_ADD(MBEDTLS_ERR_PKCS7_INVALID_FORMAT, + MBEDTLS_ERR_ASN1_LENGTH_MISMATCH); + } + + /* Get version of signed data */ + ret = pkcs7_get_version(&p, end, &signed_data->MBEDTLS_PRIVATE(version)); + + if (ret != 0) { + return ret; + } + + /* Get digest algorithm */ + ret = pkcs7_get_digest_algorithm_set( + &p, end, &signed_data->MBEDTLS_PRIVATE(digest_alg_identifiers)); + if (ret != 0) { + return ret; + } + + // TODO(OPK, b/349676714): pkcs7_get_digest_algorithm_set() doesn't handle + // empty digest_alg_identifiers. Below check will fail if the parsed + // digest_alg_identifiers is empty. + // https://github.com/Mbed-TLS/mbedtls/blob/v3.4.0/library/pkcs7.c#L494 + /* + mbedtls_md_type_t md_alg; + ret = mbedtls_oid_get_md_alg( + &signed_data->MBEDTLS_PRIVATE(digest_alg_identifiers), &md_alg); + if (ret != 0) { + return MBEDTLS_ERR_PKCS7_INVALID_ALG; + } + */ + + mbedtls_pkcs7_buf content_type; + TEE_MemFill(&content_type, 0, sizeof(content_type)); + ret = pkcs7_get_content_info_type(&p, end, &end_content_info, &content_type); + if (ret != 0) { + return ret; + } + if (MBEDTLS_OID_CMP(MBEDTLS_OID_PKCS7_DATA, &content_type)) { + return MBEDTLS_ERR_PKCS7_INVALID_CONTENT_INFO; + } + + if (p != end_content_info) { + /* Determine if valid content is present */ + ret = mbedtls_asn1_get_tag( + &p, end_content_info, &len, + MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_CONTEXT_SPECIFIC); + if (ret != 0) { + return MBEDTLS_ERROR_ADD(MBEDTLS_ERR_PKCS7_INVALID_CONTENT_INFO, ret); + } + p += len; + if (p != end_content_info) { + return MBEDTLS_ERROR_ADD(MBEDTLS_ERR_PKCS7_INVALID_CONTENT_INFO, ret); + } + /* Valid content is present - this is not supported */ + // TODO(OPK, b/349676714): To add support for this. Ignore the extra content + // for now. + // https://github.com/Mbed-TLS/mbedtls/blob/v3.4.0/library/pkcs7.c#L526 + // return MBEDTLS_ERR_PKCS7_FEATURE_UNAVAILABLE; + } + + /* Look for certificates, there may or may not be any */ + mbedtls_x509_crt_init(&signed_data->MBEDTLS_PRIVATE(certs)); + ret = pkcs7_get_certificates(&p, end, &signed_data->MBEDTLS_PRIVATE(certs)); + if (ret < 0) { + return ret; + } + + signed_data->MBEDTLS_PRIVATE(no_of_certs) = ret; + + /* + * Currently CRLs are not supported. If CRL exist, the parsing will fail + * at next step of getting signers info and return error as invalid + * signer info. + */ + + signed_data->MBEDTLS_PRIVATE(no_of_crls) = 0; + + /* Get signers info */ + ret = pkcs7_get_signers_info_set( + &p, end, &signed_data->MBEDTLS_PRIVATE(signers), + &signed_data->MBEDTLS_PRIVATE(digest_alg_identifiers)); + if (ret < 0) { + return ret; + } + + signed_data->MBEDTLS_PRIVATE(no_of_signers) = ret; + + /* Don't permit trailing data */ + if (p != end) { + return MBEDTLS_ERR_PKCS7_INVALID_FORMAT; + } + + return 0; +} + +// A copy of mbedtls_pkcs7_parse_der() from mbedtls-3.4.0 +// It calls the modified pkcs7_get_signed_data() +static int mbedtls_pkcs7_parse_der_ref(mbedtls_pkcs7* pkcs7, + const unsigned char* buf, + const size_t buflen) { + unsigned char* p; + unsigned char* end; + size_t len = 0; + int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; + + if (pkcs7 == NULL) { + return MBEDTLS_ERR_PKCS7_BAD_INPUT_DATA; + } + + /* make an internal copy of the buffer for parsing */ + pkcs7->MBEDTLS_PRIVATE(raw).p = p = mbedtls_calloc(1, buflen); + if (pkcs7->MBEDTLS_PRIVATE(raw).p == NULL) { + ret = MBEDTLS_ERR_PKCS7_ALLOC_FAILED; + goto out; + } + TEE_MemMove(p, buf, buflen); + pkcs7->MBEDTLS_PRIVATE(raw).len = buflen; + end = p + buflen; + + ret = mbedtls_asn1_get_tag(&p, end, &len, + MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE); + if (ret != 0) { + ret = MBEDTLS_ERROR_ADD(MBEDTLS_ERR_PKCS7_INVALID_FORMAT, ret); + goto out; + } + + if ((size_t)(end - p) != len) { + ret = MBEDTLS_ERROR_ADD(MBEDTLS_ERR_PKCS7_INVALID_FORMAT, + MBEDTLS_ERR_ASN1_LENGTH_MISMATCH); + goto out; + } + + if ((ret = mbedtls_asn1_get_tag(&p, end, &len, MBEDTLS_ASN1_OID)) != 0) { + if (ret != MBEDTLS_ERR_ASN1_UNEXPECTED_TAG) { + goto out; + } + p = pkcs7->MBEDTLS_PRIVATE(raw).p; + len = buflen; + goto try_data; + } + + if (MBEDTLS_OID_CMP_RAW(MBEDTLS_OID_PKCS7_SIGNED_DATA, p, len)) { + /* OID is not MBEDTLS_OID_PKCS7_SIGNED_DATA, which is the only supported + * feature */ + if (!MBEDTLS_OID_CMP_RAW(MBEDTLS_OID_PKCS7_DATA, p, len) || + !MBEDTLS_OID_CMP_RAW(MBEDTLS_OID_PKCS7_ENCRYPTED_DATA, p, len) || + !MBEDTLS_OID_CMP_RAW(MBEDTLS_OID_PKCS7_ENVELOPED_DATA, p, len) || + !MBEDTLS_OID_CMP_RAW(MBEDTLS_OID_PKCS7_SIGNED_AND_ENVELOPED_DATA, p, + len) || + !MBEDTLS_OID_CMP_RAW(MBEDTLS_OID_PKCS7_DIGESTED_DATA, p, len)) { + /* OID is valid according to the spec, but unsupported */ + ret = MBEDTLS_ERR_PKCS7_FEATURE_UNAVAILABLE; + } else { + /* OID is invalid according to the spec */ + ret = MBEDTLS_ERR_PKCS7_BAD_INPUT_DATA; + } + goto out; + } + + p += len; + + ret = pkcs7_get_next_content_len(&p, end, &len); + if (ret != 0) { + goto out; + } + + /* ensure no extra/missing data */ + if (p + len != end) { + ret = MBEDTLS_ERR_PKCS7_BAD_INPUT_DATA; + goto out; + } + +try_data: + ret = pkcs7_get_signed_data(p, len, &pkcs7->MBEDTLS_PRIVATE(signed_data)); + if (ret != 0) { + goto out; + } + + ret = MBEDTLS_PKCS7_SIGNED_DATA; + +out: + if (ret < 0) { + mbedtls_pkcs7_free(pkcs7); + } + + return ret; +} + +/****************************************************************************** + End of the MBEDTLS library +*******************************************************************************/ + +/* +This code assumes the certs_and_key always contains a certificate chain (one +leaf and one intermediate cert) followed by a private key. + 1. Leaf certificate + 2. Intermediate certificate + 3. Private leaf key +Input format for |certs_and_key|: ++-----------------------+----------------------+--------------------------+ +| Cert Chain Length | Certificate Chain | Key Length | ++-----------------------+----------------------+--------------------------+ +| (4 bytes, big-endian) | (DER-encoded PKCS#7) | (4 bytes, big-endian) | ++-----------------------+----------------------+--------------------------+ +| Private Key | ++-----------------------+ +| (DER-encoded PKCS#8) | ++-----------------------+ +*/ +OEMCryptoResult ParseCertificateChainAndKey(const uint8_t* certs_and_key, + size_t certs_and_key_length, + uint8_t* cert_chain_out, + size_t* cert_chain_out_length, + uint8_t* key_out, + size_t* key_out_length) { + RETURN_INVALID_CONTEXT_IF_NULL(certs_and_key); + RETURN_INVALID_CONTEXT_IF_ZERO(certs_and_key_length); + RETURN_INVALID_CONTEXT_IF_NULL(cert_chain_out); + RETURN_INVALID_CONTEXT_IF_NULL(cert_chain_out_length); + RETURN_INVALID_CONTEXT_IF_NULL(key_out); + RETURN_INVALID_CONTEXT_IF_NULL(key_out_length); + // Read cert chain length from the first 4 bytes of the input buffer + if (certs_and_key_length < sizeof(uint32_t)) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + const uint32_t cert_chain_length = (certs_and_key[0] << 24) | + (certs_and_key[1] << 16) | + (certs_and_key[2] << 8) | certs_and_key[3]; + // Read key length from the next 4 bytes after the cert chain + if (certs_and_key_length < + sizeof(uint32_t) + cert_chain_length + sizeof(uint32_t)) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + const uint8_t* key_data = + certs_and_key + sizeof(uint32_t) + cert_chain_length; + const uint32_t key_length = (key_data[0] << 24) | (key_data[1] << 16) | + (key_data[2] << 8) | key_data[3]; + if (certs_and_key_length < + sizeof(uint32_t) + cert_chain_length + sizeof(uint32_t) + key_length) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + // Check if output buffers are large enough + if (*cert_chain_out_length < cert_chain_length || + *key_out_length < key_length) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + // Copy certificate chain + TEE_MemMove(cert_chain_out, certs_and_key + sizeof(uint32_t), + cert_chain_length); + *cert_chain_out_length = (size_t)cert_chain_length; + // Copy private key + TEE_MemMove(key_out, key_data + sizeof(uint32_t), key_length); + *key_out_length = (size_t)key_length; + return OEMCrypto_SUCCESS; +} + +OEMCryptoResult ValidateCertificateChainAndKey(const uint8_t* cert_chain, + size_t cert_chain_length, + const uint8_t* key, + size_t key_length) { + // 1. PKCS#7 certificate chain parsing + OEMCryptoResult result = OEMCrypto_SUCCESS; + mbedtls_pkcs7 pkcs7; + mbedtls_pkcs7_init(&pkcs7); + mbedtls_pk_context pk; + mbedtls_pk_init(&pk); + mbedtls_entropy_context entropy; + mbedtls_entropy_init(&entropy); + mbedtls_ctr_drbg_context ctr_drbg; + mbedtls_ctr_drbg_init(&ctr_drbg); + + // Seed the random number generator + const char* pers = "pk_parse_and_check"; + int ret = mbedtls_ctr_drbg_seed(&ctr_drbg, EntropyFunc, &entropy, + (const unsigned char*)pers, strlen(pers)); + if (ret != 0) { + result = OEMCrypto_ERROR_UNKNOWN_FAILURE; + goto cleanup; + } + + // Parse the PKCS#7 structure + ret = mbedtls_pkcs7_parse_der_ref(&pkcs7, cert_chain, cert_chain_length); + if (ret < 0) { + result = OEMCrypto_ERROR_UNKNOWN_FAILURE; + goto cleanup; + } + + // 2. Verify the certificate chain + // The PKCS#7 structure can contain multiple certificates. Assuming the cert + // chain contains an intermediate cert followed by a leaf cert. + mbedtls_x509_crt* intermediate_cert = + &pkcs7.MBEDTLS_PRIVATE(signed_data).MBEDTLS_PRIVATE(certs); + if (intermediate_cert == NULL) { + result = OEMCrypto_ERROR_INVALID_OEM_CERTIFICATE; + goto cleanup; + } + // TODO: Only the first cert in the chain is parsed for now due to the + // limitations of the mbedtls library: + // https://github.com/Mbed-TLS/mbedtls/blob/v3.4.0/library/pkcs7.c#L216 + /* + mbedtls_x509_crt* leaf_cert = intermediate_cert->next; + if (leaf_cert == NULL) { + result = OEMCrypto_ERROR_INVALID_OEM_CERTIFICATE; + goto cleanup; + } + + // Verify leaf certificate against intermediate + ret = mbedtls_x509_crt_verify(leaf_cert, intermediate_cert, NULL, NULL, NULL, + NULL, NULL); + if (ret != 0) { + result = OEMCrypto_ERROR_INVALID_OEM_CERTIFICATE; + goto cleanup; + } + */ + + // 3. Parse the private key + ret = mbedtls_pk_parse_key(&pk, key, key_length, NULL, 0, + mbedtls_ctr_drbg_random, &ctr_drbg); + if (ret != 0) { + result = OEMCrypto_ERROR_INVALID_KEY; + goto cleanup; + } + + // TODO: Only the first cert in the chain is parsed for now due to the + // limitations of the mbedtls library: + // https://github.com/Mbed-TLS/mbedtls/blob/v3.4.0/library/pkcs7.c#L216 + /* + // 4. Verify private key matches public key in leaf certificate + + ret = mbedtls_pk_check_pair(&leaf_cert->pk, &pk, mbedtls_ctr_drbg_random, + &ctr_drbg); + if (ret != 0) { + result = OEMCrypto_ERROR_INVALID_KEY; + } + */ +cleanup: + mbedtls_pkcs7_free(&pkcs7); + mbedtls_pk_free(&pk); + mbedtls_entropy_free(&entropy); + mbedtls_ctr_drbg_free(&ctr_drbg); + return result; +} diff --git a/oemcrypto/opk/ports/optee/ta/common/wtpi_impl/wtpi_crypto_and_key_management_layer1.c b/oemcrypto/opk/ports/optee/ta/common/wtpi_impl/wtpi_crypto_and_key_management_layer1.c index b5afc77..ddd561f 100644 --- a/oemcrypto/opk/ports/optee/ta/common/wtpi_impl/wtpi_crypto_and_key_management_layer1.c +++ b/oemcrypto/opk/ports/optee/ta/common/wtpi_impl/wtpi_crypto_and_key_management_layer1.c @@ -430,7 +430,7 @@ OEMCryptoResult WTPI_C1_CopyToOutputBuffer(const uint8_t* in, size_t size, uint8_t* dest = NULL; if (out->type == OPK_SECURE_OUTPUT_BUFFER) { - OEMCryptoResult result = + const OEMCryptoResult result = WTPI_GetSecureBufferAddress(out->buffer.secure, output_offset, &dest); if (result != OEMCrypto_SUCCESS) return result; } else if (out->type == OPK_CLEAR_INSECURE_OUTPUT_BUFFER) { diff --git a/oemcrypto/opk/ports/optee/ta/common/wtpi_impl/wtpi_crypto_asymmetric.c b/oemcrypto/opk/ports/optee/ta/common/wtpi_impl/wtpi_crypto_asymmetric.c index 4143877..369ba45 100644 --- a/oemcrypto/opk/ports/optee/ta/common/wtpi_impl/wtpi_crypto_asymmetric.c +++ b/oemcrypto/opk/ports/optee/ta/common/wtpi_impl/wtpi_crypto_asymmetric.c @@ -258,9 +258,15 @@ OEMCryptoResult WTPI_RSASign(WTPI_AsymmetricKey_Handle key, TEE_FreeOperation(sha1_op); TEE_OperationHandle sign_op_handle = TEE_HANDLE_NULL; +#ifdef USE_OPTEE_4 + res = TEE_AllocateOperation(&sign_op_handle, + TEE_ALG_RSASSA_PKCS1_PSS_MGF1_SHA1, + TEE_MODE_SIGN, key_info.objectSize); +#else res = TEE_AllocateOperation(&sign_op_handle, TEE_ALG_RSASSA_PKCS1_PSS_MGF1_SHA1, TEE_MODE_SIGN, key_info.keySize); +#endif if (res != TEE_SUCCESS) { EMSG("TEE_AllocateOperation() failed with result 0x%x", res); return OEMCrypto_ERROR_UNKNOWN_FAILURE; @@ -365,9 +371,15 @@ OEMCryptoResult WTPI_RSADecrypt(WTPI_AsymmetricKey_Handle key, } TEE_OperationHandle decrypt_op_handle = TEE_HANDLE_NULL; +#ifdef USE_OPTEE_4 + res = TEE_AllocateOperation(&decrypt_op_handle, + TEE_ALG_RSAES_PKCS1_OAEP_MGF1_SHA1, + TEE_MODE_DECRYPT, key_info.objectSize); +#else res = TEE_AllocateOperation(&decrypt_op_handle, TEE_ALG_RSAES_PKCS1_OAEP_MGF1_SHA1, TEE_MODE_DECRYPT, key_info.keySize); +#endif if (res != TEE_SUCCESS) { EMSG("TEE_AllocateOperation() failed with result 0x%x", res); return OEMCrypto_ERROR_UNKNOWN_FAILURE; @@ -478,8 +490,13 @@ OEMCryptoResult Helper_ECCSign(WTPI_AsymmetricKey_Handle key, // Sign digest TEE_OperationHandle sign_op = TEE_HANDLE_NULL; +#ifdef USE_OPTEE_4 + res = TEE_AllocateOperation(&sign_op, sign_algo, TEE_MODE_SIGN, + key_info.objectSize); +#else res = TEE_AllocateOperation(&sign_op, sign_algo, TEE_MODE_SIGN, key_info.keySize); +#endif if (res != TEE_SUCCESS) { EMSG("TEE_AllocateOperation() failed with result 0x%x", res); return OEMCrypto_ERROR_UNKNOWN_FAILURE; diff --git a/oemcrypto/opk/ports/optee/ta/common/wtpi_impl/wtpi_decrypt_sample.c b/oemcrypto/opk/ports/optee/ta/common/wtpi_impl/wtpi_decrypt_sample.c index b1f65cf..b57ca69 100644 --- a/oemcrypto/opk/ports/optee/ta/common/wtpi_impl/wtpi_decrypt_sample.c +++ b/oemcrypto/opk/ports/optee/ta/common/wtpi_impl/wtpi_decrypt_sample.c @@ -41,7 +41,7 @@ static OEMCryptoResult WTPI_DecryptToOutputBuffer_CTR( uint8_t* dest = NULL; if (out->type == OPK_SECURE_OUTPUT_BUFFER) { - OEMCryptoResult result = + const OEMCryptoResult result = WTPI_GetSecureBufferAddress(out->buffer.secure, output_offset, &dest); if (result != OEMCrypto_SUCCESS) return result; } else if (out->type == OPK_CLEAR_INSECURE_OUTPUT_BUFFER) { @@ -95,7 +95,7 @@ static OEMCryptoResult WTPI_DecryptToOutputBuffer_CBC( uint8_t* dest = NULL; if (out->type == OPK_SECURE_OUTPUT_BUFFER) { - OEMCryptoResult result = + const OEMCryptoResult result = WTPI_GetSecureBufferAddress(out->buffer.secure, output_offset, &dest); if (result != OEMCrypto_SUCCESS) return result; } else if (out->type == OPK_CLEAR_INSECURE_OUTPUT_BUFFER) { diff --git a/oemcrypto/opk/ports/optee/ta/common/wtpi_impl/wtpi_root_of_trust_layer1.c b/oemcrypto/opk/ports/optee/ta/common/wtpi_impl/wtpi_root_of_trust_layer1.c index be9ab93..e17af25 100644 --- a/oemcrypto/opk/ports/optee/ta/common/wtpi_impl/wtpi_root_of_trust_layer1.c +++ b/oemcrypto/opk/ports/optee/ta/common/wtpi_impl/wtpi_root_of_trust_layer1.c @@ -12,12 +12,18 @@ #include "cose_util.h" #include "odk_endian.h" +#include "oemcrypto_check_macros.h" #include "oemcrypto_key_types.h" +#include "opk_config.h" +#ifdef USE_PROVISIONING_30 +# include "prov30_factory_util.h" +#endif #include "renewal_util.h" #include "wtpi_abort_interface.h" #include "wtpi_config_interface.h" #include "wtpi_crc32_interface.h" #include "wtpi_crypto_and_key_management_interface_layer1.h" +#include "wtpi_device_key_interface.h" #include "wtpi_device_renewal_interface_layer1.h" #include "wtpi_logging_interface.h" #include "wtpi_memory_interface.h" @@ -227,14 +233,40 @@ static OEMCryptoResult GetProv4DeviceID(uint8_t* device_id, return result; } +#ifdef USE_PROVISIONING_30 +static OEMCryptoResult GetProv3DeviceID(uint8_t* device_id, + size_t device_id_length) { + ABORT_IF_NULL(device_id); + if (device_id_length < SHA256_DIGEST_LENGTH) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + uint8_t public_cert_buffer[MAX_PROV30_OEM_CERT_CHAIN_PKCS7_SIZE]; + size_t public_cert_buffer_size = sizeof(public_cert_buffer); + OEMCryptoResult result = WTPI_LoadOEMPublicCertificate( + public_cert_buffer, &public_cert_buffer_size); + if (result != OEMCrypto_SUCCESS) return result; + // Device ID with provisioning 3 in this reference implementation is hash of + // (PKCS#7 DER encoded) OEM public certificate. + result = + WTPI_C1_SHA256(public_cert_buffer, public_cert_buffer_size, device_id); + return result; +} +#endif + OEMCryptoResult WTPI_GetDeviceID(uint8_t* device_id, size_t device_id_length) { if (device_id == NULL) return OEMCrypto_ERROR_INVALID_CONTEXT; - if (WTPI_GetProvisioningMethod() == OEMCrypto_BootCertificateChain) { + const OEMCrypto_ProvisioningMethod provisioning_method = + WTPI_GetProvisioningMethod(); + if (provisioning_method == OEMCrypto_BootCertificateChain) { return GetProv4DeviceID(device_id, device_id_length); } - - if (WTPI_GetProvisioningMethod() != OEMCrypto_Keybox) { +#ifdef USE_PROVISIONING_30 + if (provisioning_method == OEMCrypto_OEMCertificate) { + return GetProv3DeviceID(device_id, device_id_length); + } +#endif + if (provisioning_method != OEMCrypto_Keybox) { return OEMCrypto_ERROR_NOT_IMPLEMENTED; } @@ -249,7 +281,8 @@ OEMCryptoResult WTPI_GetDeviceIDLength(size_t* device_id_length) { if (device_id_length == NULL) { return OEMCrypto_ERROR_INVALID_CONTEXT; } - if (WTPI_GetProvisioningMethod() == OEMCrypto_BootCertificateChain) { + if (WTPI_GetProvisioningMethod() == OEMCrypto_OEMCertificate || + WTPI_GetProvisioningMethod() == OEMCrypto_BootCertificateChain) { *device_id_length = SHA256_DIGEST_LENGTH; } else if (WTPI_GetProvisioningMethod() == OEMCrypto_Keybox) { *device_id_length = KEYBOX_DEVICE_ID_SIZE; @@ -305,28 +338,164 @@ OEMCryptoResult WTPI_K1_CreateKeyHandleFromKeybox( } } -OEMCryptoResult WTPI_WrapOEMCert(const uint8_t* input UNUSED, - size_t input_length UNUSED, - uint8_t* wrapped_cert UNUSED, - size_t* wrapped_cert_length UNUSED) { +OEMCryptoResult WTPI_StripKeyboxAndReinstall(void) { return OEMCrypto_ERROR_NOT_IMPLEMENTED; } -OEMCryptoResult WTPI_UnwrapValidateAndInstallOEMCert( - const uint8_t* input UNUSED, size_t input_length UNUSED) { +#ifdef USE_PROVISIONING_30 +// Load Provisioning 3.0 OEM private key to output buffer +static OEMCryptoResult LoadOEMPrivateKey(uint8_t* output, + size_t* output_length) { + ABORT_IF_NULL(output); + ABORT_IF_NULL(output_length); + ABORT_IF(*output_length < MAX_PROV30_OEM_KEY_SIZE, "Invalid output length"); + return WTPI_LoadOEMPrivateKey30(output, output_length); +} +#endif + +OEMCryptoResult WTPI_WrapOEMCert(const uint8_t* input, size_t input_length, + uint8_t* wrapped_cert, + size_t* wrapped_cert_length) { +#ifdef USE_PROVISIONING_30 + if (input == NULL || input_length == 0 || wrapped_cert_length == NULL) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + size_t required_size = 0; + OEMCryptoResult result = WTPI_GetEncryptAndSignSize( + DEVICE_KEY_WRAP_OEM_CERT, input_length, &required_size); + if (result != OEMCrypto_SUCCESS) { + LOGE("Failed to get wrapped OEM cert and key size with result: %u", result); + return result; + } + if (wrapped_cert == NULL || *wrapped_cert_length < required_size) { + *wrapped_cert_length = required_size; + return OEMCrypto_ERROR_SHORT_BUFFER; + } + /* Tell caller how much space we used. */ + *wrapped_cert_length = required_size; + return WTPI_WrapRootOfTrust30(input, input_length, wrapped_cert, + wrapped_cert_length); +#else + (void)input; + (void)input_length; + (void)wrapped_cert; + (void)wrapped_cert_length; return OEMCrypto_ERROR_NOT_IMPLEMENTED; +#endif } -OEMCryptoResult WTPI_LoadOEMPublicCertificate(uint8_t* output UNUSED, - size_t* output_length UNUSED) { +OEMCryptoResult WTPI_UnwrapValidateAndInstallOEMCert(const uint8_t* input, + size_t input_length) { +#ifdef USE_PROVISIONING_30 + if (input == NULL || input_length == 0) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + uint8_t certs_and_key[MAX_PROV30_ROT_SIZE] = {0}; + size_t certs_and_key_length = MAX_PROV30_ROT_SIZE; + OEMCryptoResult result = WTPI_UnwrapRootOfTrust30( + input, input_length, certs_and_key, &certs_and_key_length); + if (result != OEMCrypto_SUCCESS) { + goto cleanup; + } + + uint8_t cert_chain[MAX_PROV30_OEM_CERT_CHAIN_PKCS7_SIZE] = {0}; + size_t cert_chain_length = MAX_PROV30_OEM_CERT_CHAIN_PKCS7_SIZE; + uint8_t private_key[MAX_PROV30_OEM_KEY_SIZE] = {0}; + size_t private_key_length = MAX_PROV30_OEM_KEY_SIZE; + result = ParseCertificateChainAndKey(certs_and_key, certs_and_key_length, + cert_chain, &cert_chain_length, + private_key, &private_key_length); + if (result != OEMCrypto_SUCCESS) { + goto cleanup; + } + result = ValidateCertificateChainAndKey(cert_chain, cert_chain_length, + private_key, private_key_length); + if (result != OEMCrypto_SUCCESS) { + LOGE("OEM certificate chain or key is not valid"); + goto cleanup; + } + result = WTPI_SaveOEMPrivateKey30(private_key, private_key_length); + if (result != OEMCrypto_SUCCESS) { + goto cleanup; + } + result = WTPI_SaveOEMPublicCertificate30(cert_chain, cert_chain_length); +cleanup: + WTPI_SecureZeroMemory(certs_and_key, sizeof(certs_and_key)); + WTPI_SecureZeroMemory(private_key, sizeof(private_key)); + return result; +#else + (void)input; + (void)input_length; return OEMCrypto_ERROR_NOT_IMPLEMENTED; +#endif +} + +OEMCryptoResult WTPI_LoadOEMPublicCertificate(uint8_t* output, + size_t* output_length) { +#ifdef USE_PROVISIONING_30 + if (output_length == NULL) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + if (output == NULL || *output_length < MAX_PROV30_OEM_CERT_CHAIN_PKCS7_SIZE) { + *output_length = MAX_PROV30_OEM_CERT_CHAIN_PKCS7_SIZE; + return OEMCrypto_ERROR_SHORT_BUFFER; + } + return WTPI_LoadOEMPublicCertificate30(output, output_length); +#else + (void)output; + (void)output_length; + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +#endif } OEMCryptoResult WTPI_ValidateOEMCertAndKey(void) { +#ifdef USE_PROVISIONING_30 + uint8_t cert_chain[MAX_PROV30_OEM_CERT_CHAIN_PKCS7_SIZE] = {0}; + size_t cert_chain_length = MAX_PROV30_OEM_CERT_CHAIN_PKCS7_SIZE; + OEMCryptoResult result = + WTPI_LoadOEMPublicCertificate(cert_chain, &cert_chain_length); + if (result != OEMCrypto_SUCCESS) return result; + uint8_t private_key[MAX_PROV30_OEM_KEY_SIZE] = {0}; + size_t private_key_length = MAX_PROV30_OEM_KEY_SIZE; + result = LoadOEMPrivateKey(private_key, &private_key_length); + if (result != OEMCrypto_SUCCESS) { + WTPI_SecureZeroMemory(private_key, sizeof(private_key)); + return result; + } + result = ValidateCertificateChainAndKey(cert_chain, cert_chain_length, + private_key, private_key_length); + WTPI_SecureZeroMemory(private_key, sizeof(private_key)); + return result; +#else return OEMCrypto_ERROR_NOT_IMPLEMENTED; +#endif } OEMCryptoResult WTPI_CreateAsymmetricKeyHandleFromOEMKey( - WTPI_AsymmetricKey_Handle* key_handle UNUSED) { + WTPI_AsymmetricKey_Handle* key_handle) { +#ifdef USE_PROVISIONING_30 + uint8_t oem_key[MAX_PROV30_OEM_KEY_SIZE]; + size_t oem_key_length = MAX_PROV30_OEM_KEY_SIZE; + OEMCryptoResult result = LoadOEMPrivateKey(oem_key, &oem_key_length); + if (result != OEMCrypto_SUCCESS) { + WTPI_SecureZeroMemory(oem_key, sizeof(oem_key)); + LOGE("Failed to load OEM private key with result: %u", result); + return result; + } + result = WTPI_CreateAsymmetricKeyHandle(oem_key, oem_key_length, + DRM_RSA_PRIVATE_KEY, key_handle); + if (result != OEMCrypto_SUCCESS) { + WTPI_SecureZeroMemory(oem_key, sizeof(oem_key)); + LOGE( + "Failed to create asymmetric key handle for OEM private key with " + "result: %u", + result); + return result; + } + WTPI_SecureZeroMemory(oem_key, sizeof(oem_key)); + return OEMCrypto_SUCCESS; +#else + (void)key_handle; return OEMCrypto_ERROR_NOT_IMPLEMENTED; +#endif } diff --git a/oemcrypto/opk/ports/optee/ta/oemcrypto_ta/include/user_ta_header_defines.h b/oemcrypto/opk/ports/optee/ta/oemcrypto_ta/include/user_ta_header_defines.h index 03a9841..f554be3 100644 --- a/oemcrypto/opk/ports/optee/ta/oemcrypto_ta/include/user_ta_header_defines.h +++ b/oemcrypto/opk/ports/optee/ta/oemcrypto_ta/include/user_ta_header_defines.h @@ -23,7 +23,7 @@ #define TA_FLAGS TA_FLAG_EXEC_DDR /* Provisioned stack size */ -#define TA_STACK_SIZE (16 * 1024) +#define TA_STACK_SIZE (32 * 1024) /* Provisioned heap size for TEE_Malloc() and friends */ #define TA_DATA_SIZE (64 * 1024) diff --git a/oemcrypto/opk/ports/optee/ta/oemcrypto_ta/sub.mk b/oemcrypto/opk/ports/optee/ta/oemcrypto_ta/sub.mk index b91c48e..8987322 100644 --- a/oemcrypto/opk/ports/optee/ta/oemcrypto_ta/sub.mk +++ b/oemcrypto/opk/ports/optee/ta/oemcrypto_ta/sub.mk @@ -44,9 +44,26 @@ cppflags-y += \ -DOPK_CONFIG_PROVISIONING_METHOD=$(OPTEE_PROVISIONING_METHOD) \ -DOPK_OPTEE_CONFIG_DEVICEKEY_NON_NIST_KDF=$(DEVICEKEY_NON_NIST_KDF) \ -Wno-switch-default - #-D_DEBUG +# Check if FACTORY_BUILD_ONLY is defined and has a value +ifneq ($(origin FACTORY_BUILD_ONLY), undefined) +ifneq ($(FACTORY_BUILD_ONLY),) +cppflags-y += -DFACTORY_BUILD_ONLY +endif +endif + +ifneq ($(origin USE_PROVISIONING_30), undefined) +ifneq ($(USE_PROVISIONING_30),) +cppflags-y += -DUSE_PROVISIONING_30 +endif +endif + +ifneq ($(origin USE_OPTEE_4), undefined) +ifneq ($(USE_OPTEE_4),) +cppflags-y += -DUSE_OPTEE_4 +endif +endif + libnames += \ $(wtpi_impl_libs) - diff --git a/oemcrypto/opk/ports/optee/ta/wtpi_test_ta/sub.mk b/oemcrypto/opk/ports/optee/ta/wtpi_test_ta/sub.mk index a959e26..4de99e6 100644 --- a/oemcrypto/opk/ports/optee/ta/wtpi_test_ta/sub.mk +++ b/oemcrypto/opk/ports/optee/ta/wtpi_test_ta/sub.mk @@ -51,3 +51,21 @@ cppflags-y += \ -DOPK_OPTEE_CONFIG_DEVICEKEY_NON_NIST_KDF=$(DEVICEKEY_NON_NIST_KDF) \ -Wno-switch-default \ +# Check if FACTORY_BUILD_ONLY is defined and has a value +ifneq ($(origin FACTORY_BUILD_ONLY), undefined) +ifneq ($(FACTORY_BUILD_ONLY),) +cppflags-y += -DFACTORY_BUILD_ONLY +endif +endif + +ifneq ($(origin USE_PROVISIONING_30), undefined) +ifneq ($(USE_PROVISIONING_30),) +cppflags-y += -DUSE_PROVISIONING_30 +endif +endif + +ifneq ($(origin USE_OPTEE_4), undefined) +ifneq ($(USE_OPTEE_4),) +cppflags-y += -DUSE_OPTEE_4 +endif +endif diff --git a/oemcrypto/opk/serialization/common/GEN_common_serializer.c b/oemcrypto/opk/serialization/common/GEN_common_serializer.c index 76e9e60..4cbac92 100644 --- a/oemcrypto/opk/serialization/common/GEN_common_serializer.c +++ b/oemcrypto/opk/serialization/common/GEN_common_serializer.c @@ -381,29 +381,6 @@ void OPK_Pack_OEMCrypto_SubSampleDescription( OPK_Pack_size_t(msg, (const size_t*)&obj->block_offset); } -void OPK_Pack_OEMCrypto_SampleDescription( - ODK_Message* msg, OEMCrypto_SampleDescription const* obj) { - OPK_Pack_size_t(msg, (const size_t*)&obj->subsamples_length); - OPK_Pack_OEMCrypto_InputOutputPair( - msg, (const OEMCrypto_InputOutputPair*)&obj->buffers); - OPK_PackArray(msg, (const uint8_t*)&obj->iv[0], 16); - /* pack object array with packer function - * OPK_Pack_OEMCrypto_SubSampleDescription */ - ODK_Message* const odk_message = msg; - const void* const objs = (const void*)obj->subsamples; - const LengthType count = OPK_ToLengthType(obj->subsamples_length); - const size_t size = sizeof(OEMCrypto_SubSampleDescription); - const bool is_null = objs == NULL || OPK_LengthIsNull(count); - if (!OPK_PackBoolValue(odk_message, is_null)) { - for (size_t i = 0; i < OPK_ToSizeT(count); i++) { - const uint8_t* new_address = (const uint8_t*)objs + i * size; - OPK_Pack_OEMCrypto_SubSampleDescription( - odk_message, - (const OEMCrypto_SubSampleDescription*)(const void*)new_address); - } - } -} - void OPK_Pack_OEMCrypto_CENCEncryptPatternDesc( ODK_Message* msg, OEMCrypto_CENCEncryptPatternDesc const* obj) { OPK_Pack_size_t(msg, (const size_t*)&obj->encrypt); @@ -536,50 +513,6 @@ void OPK_Unpack_OEMCrypto_SubSampleDescription( OPK_Unpack_size_t(msg, &obj->block_offset); } -void OPK_Unpack_OEMCrypto_SampleDescription(ODK_Message* msg, - OEMCrypto_SampleDescription* obj) { - OEMCrypto_SampleDescription tmp_obj; - if (obj == NULL) { - obj = &tmp_obj; - } - OPK_Unpack_size_t(msg, &obj->subsamples_length); - OPK_Unpack_OEMCrypto_InputOutputPair(msg, &obj->buffers); - OPK_UnpackArray(msg, &obj->iv[0], sizeof(obj->iv)); - OEMCrypto_SubSampleDescription* subsamples = NULL; - - /* unpack object array with unpacker function - * OPK_Unpack_OEMCrypto_SubSampleDescription */ - ODK_Message* odk_message = msg; - void** address = (void**)&subsamples; - LengthType count = OPK_ToLengthType(obj->subsamples_length); - size_t size = sizeof(OEMCrypto_SubSampleDescription); - if (address) { - *address = NULL; - } - if (!OPK_UnpackIsNull(odk_message)) { - if (address && !OPK_LengthIsNull(count)) { - size_t bytes_to_unpack = 0; - if (odk_mul_overflow_ux(OPK_ToSizeT(count), size, &bytes_to_unpack)) { - ODK_MESSAGE_SETSTATUS(odk_message, MESSAGE_STATUS_PARSE_ERROR); - } else { - *address = OPK_BumpAllocate(bytes_to_unpack); - if (!*address) { - ODK_MESSAGE_SETSTATUS(odk_message, MESSAGE_STATUS_OUT_OF_MEMORY); - } else { - for (size_t i = 0; i < OPK_ToSizeT(count); i++) { - uint8_t* new_address = (uint8_t*)(*address) + size * i; - OPK_Unpack_OEMCrypto_SubSampleDescription( - odk_message, - (OEMCrypto_SubSampleDescription*)((void*)new_address)); - } - } - } - } - } - - memcpy(&obj->subsamples, &subsamples, sizeof(subsamples)); -} - void OPK_Unpack_OEMCrypto_CENCEncryptPatternDesc( ODK_Message* msg, OEMCrypto_CENCEncryptPatternDesc* obj) { OEMCrypto_CENCEncryptPatternDesc tmp_obj; diff --git a/oemcrypto/opk/serialization/common/GEN_common_serializer.h b/oemcrypto/opk/serialization/common/GEN_common_serializer.h index cf9b281..4242946 100644 --- a/oemcrypto/opk/serialization/common/GEN_common_serializer.h +++ b/oemcrypto/opk/serialization/common/GEN_common_serializer.h @@ -50,8 +50,6 @@ void OPK_Pack_OEMCrypto_KeyObject(ODK_Message* msg, OEMCrypto_KeyObject const* obj); void OPK_Pack_OEMCrypto_SubSampleDescription( ODK_Message* msg, OEMCrypto_SubSampleDescription const* obj); -void OPK_Pack_OEMCrypto_SampleDescription( - ODK_Message* msg, OEMCrypto_SampleDescription const* obj); void OPK_Pack_OEMCrypto_CENCEncryptPatternDesc( ODK_Message* msg, OEMCrypto_CENCEncryptPatternDesc const* obj); void OPK_Pack_OEMCrypto_EntitledContentKeyObject( @@ -74,8 +72,6 @@ void OPK_Unpack_OEMCrypto_DTCP2_CMI_Packet(ODK_Message* msg, void OPK_Unpack_OEMCrypto_KeyObject(ODK_Message* msg, OEMCrypto_KeyObject* obj); void OPK_Unpack_OEMCrypto_SubSampleDescription( ODK_Message* msg, OEMCrypto_SubSampleDescription* obj); -void OPK_Unpack_OEMCrypto_SampleDescription(ODK_Message* msg, - OEMCrypto_SampleDescription* obj); void OPK_Unpack_OEMCrypto_CENCEncryptPatternDesc( ODK_Message* msg, OEMCrypto_CENCEncryptPatternDesc* obj); void OPK_Unpack_OEMCrypto_EntitledContentKeyObject( diff --git a/oemcrypto/opk/serialization/common/common_special_cases.c b/oemcrypto/opk/serialization/common/common_special_cases.c index 035bba1..9748104 100644 --- a/oemcrypto/opk/serialization/common/common_special_cases.c +++ b/oemcrypto/opk/serialization/common/common_special_cases.c @@ -13,6 +13,7 @@ #include "GEN_common_serializer.h" #include "OEMCryptoCENC.h" +#include "bump_allocator.h" #include "log_macros.h" #include "odk_overflow.h" #include "opk_serialization_base.h" @@ -120,3 +121,70 @@ void OPK_Unpack_OEMCrypto_InputOutputPair(ODK_Message* msg, OPK_Unpack_OEMCrypto_DestBufferDesc(msg, &obj->output_descriptor); memcpy(&obj->input_data, &input_data, sizeof(input_data)); } + +void OPK_Pack_OEMCrypto_SampleDescription( + ODK_Message* msg, OEMCrypto_SampleDescription const* obj) { + OPK_Pack_size_t(msg, (const size_t*)&obj->subsamples_length); + OPK_Pack_OEMCrypto_InputOutputPair( + msg, (const OEMCrypto_InputOutputPair*)&obj->buffers); + OPK_PackArray(msg, (const uint8_t*)&obj->iv[0], 16); + /* pack object array with packer function + * OPK_Pack_OEMCrypto_SubSampleDescription */ + ODK_Message* const odk_message = msg; + const void* const objs = (const void*)obj->subsamples; + const LengthType count = OPK_ToLengthType(obj->subsamples_length); + const size_t size = sizeof(OEMCrypto_SubSampleDescription); + const bool is_null = objs == NULL || OPK_LengthIsNull(count); + if (!OPK_PackBoolValue(odk_message, is_null)) { + for (size_t i = 0; i < OPK_ToSizeT(count); i++) { + const uint8_t* new_address = (const uint8_t*)objs + i * size; + OPK_Pack_OEMCrypto_SubSampleDescription( + odk_message, + (const OEMCrypto_SubSampleDescription*)(const void*)new_address); + } + } +} + +void OPK_Unpack_OEMCrypto_SampleDescription(ODK_Message* msg, + OEMCrypto_SampleDescription* obj) { + OEMCrypto_SampleDescription tmp_obj; + if (obj == NULL) { + obj = &tmp_obj; + } + OPK_Unpack_size_t(msg, &obj->subsamples_length); + OPK_Unpack_OEMCrypto_InputOutputPair(msg, &obj->buffers); + OPK_UnpackArray(msg, &obj->iv[0], sizeof(obj->iv)); + OEMCrypto_SubSampleDescription* subsamples = NULL; + + /* unpack object array with unpacker function + * OPK_Unpack_OEMCrypto_SubSampleDescription */ + ODK_Message* odk_message = msg; + void** address = (void**)&subsamples; + LengthType count = OPK_ToLengthType(obj->subsamples_length); + size_t size = sizeof(OEMCrypto_SubSampleDescription); + if (address) { + *address = NULL; + } + if (!OPK_UnpackIsNull(odk_message)) { + if (address && !OPK_LengthIsNull(count)) { + size_t bytes_to_unpack = 0; + if (odk_mul_overflow_ux(OPK_ToSizeT(count), size, &bytes_to_unpack)) { + ODK_MESSAGE_SETSTATUS(odk_message, MESSAGE_STATUS_PARSE_ERROR); + } else { + *address = OPK_BumpAllocate(bytes_to_unpack); + if (!*address) { + ODK_MESSAGE_SETSTATUS(odk_message, MESSAGE_STATUS_OUT_OF_MEMORY); + } else { + for (size_t i = 0; i < OPK_ToSizeT(count); i++) { + uint8_t* new_address = (uint8_t*)(*address) + size * i; + OPK_Unpack_OEMCrypto_SubSampleDescription( + odk_message, + (OEMCrypto_SubSampleDescription*)((void*)new_address)); + } + } + } + } + } + + memcpy(&obj->subsamples, &subsamples, sizeof(subsamples)); +} diff --git a/oemcrypto/opk/serialization/common/common_special_cases.h b/oemcrypto/opk/serialization/common/common_special_cases.h index a3a8a2d..2385856 100644 --- a/oemcrypto/opk/serialization/common/common_special_cases.h +++ b/oemcrypto/opk/serialization/common/common_special_cases.h @@ -30,6 +30,11 @@ void OPK_Pack_OEMCrypto_InputOutputPair(ODK_Message* msg, void OPK_Unpack_OEMCrypto_InputOutputPair(ODK_Message* msg, OEMCrypto_InputOutputPair* obj); +void OPK_Pack_OEMCrypto_SampleDescription( + ODK_Message* msg, OEMCrypto_SampleDescription const* obj); +void OPK_Unpack_OEMCrypto_SampleDescription(ODK_Message* msg, + OEMCrypto_SampleDescription* obj); + #ifdef __cplusplus } // extern "C" #endif diff --git a/oemcrypto/opk/serialization/common/include/marshaller_base.h b/oemcrypto/opk/serialization/common/include/marshaller_base.h index b0ca99d..bb376ce 100644 --- a/oemcrypto/opk/serialization/common/include/marshaller_base.h +++ b/oemcrypto/opk/serialization/common/include/marshaller_base.h @@ -49,6 +49,8 @@ void OPK_Init_OEMCrypto_DestBufferDesc(OEMCrypto_DestBufferDesc* desc); void OPK_Init_OEMCrypto_InputOutputPair(OEMCrypto_InputOutputPair* obj); +void OPK_Init_OEMCrypto_SampleDescription(OEMCrypto_SampleDescription* obj); + #ifdef __cplusplus } // extern "C" #endif diff --git a/oemcrypto/opk/serialization/common/marshaller_base.c b/oemcrypto/opk/serialization/common/marshaller_base.c index b7b9810..cb72321 100644 --- a/oemcrypto/opk/serialization/common/marshaller_base.c +++ b/oemcrypto/opk/serialization/common/marshaller_base.c @@ -4,8 +4,8 @@ * License Agreement. */ -#include "marshaller_base.h" #include "bump_allocator.h" +#include "marshaller_base.h" /* * Initialize variables to non-zero values to trigger failures if they are not @@ -94,8 +94,15 @@ void OPK_Init_OEMCrypto_DestBufferDesc(OEMCrypto_DestBufferDesc* d) { } void OPK_Init_OEMCrypto_InputOutputPair(OEMCrypto_InputOutputPair* obj) { - OPK_Init_size_t((size_t*)&obj->input_data_length); obj->input_data = NULL; + OPK_Init_size_t((size_t*)&obj->input_data_length); OPK_Init_OEMCrypto_DestBufferDesc( (OEMCrypto_DestBufferDesc*)&obj->output_descriptor); } + +void OPK_Init_OEMCrypto_SampleDescription(OEMCrypto_SampleDescription* obj) { + obj->subsamples = NULL; + OPK_Init_size_t((size_t*)&obj->subsamples_length); + OPK_Init_OEMCrypto_InputOutputPair((OEMCrypto_InputOutputPair*)&obj->buffers); + OPK_InitMemory(&obj->iv[0], sizeof(obj->iv)); +} diff --git a/oemcrypto/opk/serialization/ree/GEN_oemcrypto_api.c b/oemcrypto/opk/serialization/ree/GEN_oemcrypto_api.c index 878bae5..0d401c2 100644 --- a/oemcrypto/opk/serialization/ree/GEN_oemcrypto_api.c +++ b/oemcrypto/opk/serialization/ree/GEN_oemcrypto_api.c @@ -830,6 +830,39 @@ cleanup_and_return: return result; } +OEMCRYPTO_API OEMCryptoResult OEMCrypto_SetSessionUsage( + OEMCrypto_SESSION session, uint32_t intent, uint32_t mode) { + pthread_mutex_lock(&api_lock); + OEMCryptoResult result = OEMCrypto_ERROR_UNKNOWN_FAILURE; + ODK_Message request = ODK_Message_Create(NULL, 0); + ODK_Message response = ODK_Message_Create(NULL, 0); + + API_Initialize(); + request = OPK_Pack_SetSessionUsage_Request(session, intent, mode); + if (ODK_Message_GetStatus(&request) != MESSAGE_STATUS_OK) { + if (ODK_Message_GetStatus(&request) == MESSAGE_STATUS_BUFFER_TOO_LARGE) { + api_result = OEMCrypto_ERROR_BUFFER_TOO_LARGE; + } else { + api_result = OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + goto cleanup_and_return; + } + response = API_Transact(&request); + if (api_result != OEMCrypto_SUCCESS) goto cleanup_and_return; + OPK_Unpack_SetSessionUsage_Response(&response, &result); + + if (ODK_Message_GetStatus(&response) != MESSAGE_STATUS_OK) { + api_result = OEMCrypto_ERROR_UNKNOWN_FAILURE; + } +cleanup_and_return: + TOS_Transport_ReleaseMessage(&request); + TOS_Transport_ReleaseMessage(&response); + + result = API_CheckResult(result); + pthread_mutex_unlock(&api_lock); + return result; +} + OEMCRYPTO_API OEMCryptoResult OEMCrypto_GetKeyHandle( OEMCrypto_SESSION session, const uint8_t* content_key_id, size_t content_key_id_length, OEMCryptoCipherMode cipher_mode, diff --git a/oemcrypto/opk/serialization/ree/GEN_ree_serializer.c b/oemcrypto/opk/serialization/ree/GEN_ree_serializer.c index 596324a..942d19e 100644 --- a/oemcrypto/opk/serialization/ree/GEN_ree_serializer.c +++ b/oemcrypto/opk/serialization/ree/GEN_ree_serializer.c @@ -833,6 +833,38 @@ void OPK_Unpack_GetOEMKeyToken_Response(ODK_Message* msg, } } +ODK_Message OPK_Pack_SetSessionUsage_Request(OEMCrypto_SESSION session, + uint32_t intent, uint32_t mode) { + uint32_t api_value = 155; /* from _oecc155 */ + ODK_Message msg = TOS_Transport_GetRequest(); + OPK_Pack_uint32_t(&msg, &api_value); + uint64_t timestamp = time(0); + OPK_Pack_uint64_t(&msg, ×tamp); + OPK_Pack_uint32_t(&msg, &session); + OPK_Pack_uint32_t(&msg, &intent); + OPK_Pack_uint32_t(&msg, &mode); + OPK_PackEOM(&msg); + OPK_SharedBuffer_FinalizePacking(); + return msg; +} + +void OPK_Unpack_SetSessionUsage_Response(ODK_Message* msg, + OEMCryptoResult* result) { + uint32_t api_value = UINT32_MAX; + OPK_Unpack_uint32_t(msg, &api_value); + if (api_value != 155) + ODK_MESSAGE_SETSTATUS(msg, MESSAGE_STATUS_API_VALUE_ERROR); + OPK_Unpack_uint32_t(msg, result); + if (!Is_Valid_OEMCryptoResult(*result)) { + ODK_MESSAGE_SETSTATUS(msg, MESSAGE_STATUS_INVALID_ENUM_VALUE); + } + OPK_UnpackEOM(msg); + + if (SuccessResult(*result)) { + OPK_SharedBuffer_FinalizeUnpacking(); + } +} + ODK_Message OPK_Pack_GetKeyHandle_Request(OEMCrypto_SESSION session, const uint8_t* content_key_id, size_t content_key_id_length, diff --git a/oemcrypto/opk/serialization/ree/GEN_ree_serializer.h b/oemcrypto/opk/serialization/ree/GEN_ree_serializer.h index 1e66a34..702cdf7 100644 --- a/oemcrypto/opk/serialization/ree/GEN_ree_serializer.h +++ b/oemcrypto/opk/serialization/ree/GEN_ree_serializer.h @@ -120,6 +120,10 @@ void OPK_Unpack_GetOEMKeyToken_Response(ODK_Message* msg, OEMCryptoResult* result, uint8_t** key_token, size_t** key_token_length); +ODK_Message OPK_Pack_SetSessionUsage_Request(OEMCrypto_SESSION session, + uint32_t intent, uint32_t mode); +void OPK_Unpack_SetSessionUsage_Response(ODK_Message* msg, + OEMCryptoResult* result); ODK_Message OPK_Pack_GetKeyHandle_Request(OEMCrypto_SESSION session, const uint8_t* content_key_id, size_t content_key_id_length, diff --git a/oemcrypto/opk/serialization/tee/GEN_dispatcher.c b/oemcrypto/opk/serialization/tee/GEN_dispatcher.c index d12658f..31ac2f8 100644 --- a/oemcrypto/opk/serialization/tee/GEN_dispatcher.c +++ b/oemcrypto/opk/serialization/tee/GEN_dispatcher.c @@ -89,22 +89,6 @@ void OPK_Init_OEMCrypto_SubSampleDescription( OPK_Init_size_t((size_t*)&obj->block_offset); } -void OPK_Init_OEMCrypto_SampleDescription(OEMCrypto_SampleDescription* obj) { - OPK_Init_size_t((size_t*)&obj->subsamples_length); - OPK_Init_OEMCrypto_InputOutputPair((OEMCrypto_InputOutputPair*)&obj->buffers); - OPK_InitMemory(&obj->iv[0], 16); - OEMCrypto_SubSampleDescription* subsamples = NULL; - subsamples = (OEMCrypto_SubSampleDescription*)OPK_VarAlloc( - obj->subsamples_length * sizeof(OEMCrypto_SubSampleDescription)); - if (subsamples) { - for (size_t i = 0; i < obj->subsamples_length; i++) { - OPK_Init_OEMCrypto_SubSampleDescription( - (OEMCrypto_SubSampleDescription*)&subsamples[i]); - } - } - memcpy(&obj->subsamples, &subsamples, sizeof(subsamples)); -} - void OPK_Init_OEMCrypto_CENCEncryptPatternDesc( OEMCrypto_CENCEncryptPatternDesc* obj) { OPK_Init_size_t((size_t*)&obj->encrypt); @@ -279,12 +263,14 @@ ODK_MessageStatus OPK_DispatchMessage(ODK_Message* request, size_t message_length; OPK_Init_size_t((size_t*)&message_length); size_t* signature_length = (size_t*)OPK_VarAlloc(sizeof(size_t)); + if (signature_length == NULL) goto handle_out_of_memory; OPK_Init_size_t(signature_length); OEMCrypto_SESSION session; OPK_Init_uint32_t((uint32_t*)&session); uint8_t* message; OPK_InitPointer((uint8_t**)&message); size_t* core_message_size = (size_t*)OPK_VarAlloc(sizeof(size_t)); + if (core_message_size == NULL) goto handle_out_of_memory; OPK_Init_size_t(core_message_size); uint8_t* signature; OPK_InitPointer((uint8_t**)&signature); @@ -308,12 +294,14 @@ ODK_MessageStatus OPK_DispatchMessage(ODK_Message* request, size_t message_length; OPK_Init_size_t((size_t*)&message_length); size_t* signature_length = (size_t*)OPK_VarAlloc(sizeof(size_t)); + if (signature_length == NULL) goto handle_out_of_memory; OPK_Init_size_t(signature_length); OEMCrypto_SESSION session; OPK_Init_uint32_t((uint32_t*)&session); uint8_t* message; OPK_InitPointer((uint8_t**)&message); size_t* core_message_size = (size_t*)OPK_VarAlloc(sizeof(size_t)); + if (core_message_size == NULL) goto handle_out_of_memory; OPK_Init_size_t(core_message_size); uint8_t* signature; OPK_InitPointer((uint8_t**)&signature); @@ -337,12 +325,14 @@ ODK_MessageStatus OPK_DispatchMessage(ODK_Message* request, size_t message_length; OPK_Init_size_t((size_t*)&message_length); size_t* signature_length = (size_t*)OPK_VarAlloc(sizeof(size_t)); + if (signature_length == NULL) goto handle_out_of_memory; OPK_Init_size_t(signature_length); OEMCrypto_SESSION session; OPK_Init_uint32_t((uint32_t*)&session); uint8_t* message; OPK_InitPointer((uint8_t**)&message); size_t* core_message_size = (size_t*)OPK_VarAlloc(sizeof(size_t)); + if (core_message_size == NULL) goto handle_out_of_memory; OPK_Init_size_t(core_message_size); uint8_t* signature; OPK_InitPointer((uint8_t**)&signature); @@ -457,6 +447,7 @@ ODK_MessageStatus OPK_DispatchMessage(ODK_Message* request, size_t content_key_id_length; OPK_Init_size_t((size_t*)&content_key_id_length); size_t* key_control_block_length = (size_t*)OPK_VarAlloc(sizeof(size_t)); + if (key_control_block_length == NULL) goto handle_out_of_memory; OPK_Init_size_t(key_control_block_length); OEMCrypto_SESSION session; OPK_Init_uint32_t((uint32_t*)&session); @@ -551,13 +542,20 @@ ODK_MessageStatus OPK_DispatchMessage(ODK_Message* request, } case 120: /* OEMCrypto_LoadCasECMKeys */ { - if (!Handle_OEMCrypto_LoadCasECMKeys(request, response)) - goto handle_invalid_request; + ODK_MessageStatus status = MESSAGE_STATUS_OK; + if (!Handle_OEMCrypto_LoadCasECMKeys(request, response, &status)) { + if (status == MESSAGE_STATUS_OUT_OF_MEMORY) { + goto handle_out_of_memory; + } else { + goto handle_invalid_request; + } + } break; } case 130: /* OEMCrypto_GetOEMKeyToken */ { size_t* key_token_length = (size_t*)OPK_VarAlloc(sizeof(size_t)); + if (key_token_length == NULL) goto handle_out_of_memory; OPK_Init_size_t(key_token_length); OEMCrypto_SESSION key_session; OPK_Init_uint32_t((uint32_t*)&key_session); @@ -575,11 +573,29 @@ ODK_MessageStatus OPK_DispatchMessage(ODK_Message* request, OPK_Pack_GetOEMKeyToken_Response(result, key_token, key_token_length); break; } + case 155: /* OEMCrypto_SetSessionUsage */ + { + OEMCrypto_SESSION session; + OPK_Init_uint32_t((uint32_t*)&session); + uint32_t intent; + OPK_Init_uint32_t((uint32_t*)&intent); + uint32_t mode; + OPK_Init_uint32_t((uint32_t*)&mode); + OPK_Unpack_SetSessionUsage_Request(request, &session, &intent, &mode); + if (!ODK_Message_IsValid(request)) goto handle_invalid_request; + OEMCryptoResult result; + OPK_Init_uint32_t((uint32_t*)&result); + LOGD("SetSessionUsage"); + result = OEMCrypto_SetSessionUsage(session, intent, mode); + *response = OPK_Pack_SetSessionUsage_Response(result); + break; + } case 133: /* OEMCrypto_GetKeyHandle */ { size_t content_key_id_length; OPK_Init_size_t((size_t*)&content_key_id_length); size_t* key_handle_length = (size_t*)OPK_VarAlloc(sizeof(size_t)); + if (key_handle_length == NULL) goto handle_out_of_memory; OPK_Init_size_t(key_handle_length); OEMCrypto_SESSION session; OPK_Init_uint32_t((uint32_t*)&session); @@ -616,6 +632,7 @@ ODK_MessageStatus OPK_DispatchMessage(ODK_Message* request, OEMCrypto_CENCEncryptPatternDesc* pattern = (OEMCrypto_CENCEncryptPatternDesc*)OPK_VarAlloc( sizeof(OEMCrypto_CENCEncryptPatternDesc)); + if (pattern == NULL) goto handle_out_of_memory; OPK_Init_OEMCrypto_CENCEncryptPatternDesc( (OEMCrypto_CENCEncryptPatternDesc*)pattern); OPK_Unpack_DecryptCENC_Request(request, &key_handle, &key_handle_length, @@ -639,6 +656,7 @@ ODK_MessageStatus OPK_DispatchMessage(ODK_Message* request, OEMCrypto_DestBufferDesc* out_buffer_descriptor = (OEMCrypto_DestBufferDesc*)OPK_VarAlloc( sizeof(OEMCrypto_DestBufferDesc)); + if (out_buffer_descriptor == NULL) goto handle_out_of_memory; OPK_Init_OEMCrypto_DestBufferDesc( (OEMCrypto_DestBufferDesc*)out_buffer_descriptor); uint8_t subsample_flags; @@ -721,6 +739,7 @@ ODK_MessageStatus OPK_DispatchMessage(ODK_Message* request, size_t buffer_length; OPK_Init_size_t((size_t*)&buffer_length); size_t* signature_length = (size_t*)OPK_VarAlloc(sizeof(size_t)); + if (signature_length == NULL) goto handle_out_of_memory; OPK_Init_size_t(signature_length); uint8_t* key_handle; OPK_InitPointer((uint8_t**)&key_handle); @@ -779,6 +798,7 @@ ODK_MessageStatus OPK_DispatchMessage(ODK_Message* request, OPK_Init_size_t((size_t*)&keybox_or_cert_length); size_t* wrapped_keybox_or_cert_length = (size_t*)OPK_VarAlloc(sizeof(size_t)); + if (wrapped_keybox_or_cert_length == NULL) goto handle_out_of_memory; OPK_Init_size_t(wrapped_keybox_or_cert_length); size_t transport_key_length; OPK_Init_size_t((size_t*)&transport_key_length); @@ -875,6 +895,7 @@ ODK_MessageStatus OPK_DispatchMessage(ODK_Message* request, case 7: /* OEMCrypto_GetDeviceID */ { size_t* device_id_length = (size_t*)OPK_VarAlloc(sizeof(size_t)); + if (device_id_length == NULL) goto handle_out_of_memory; OPK_Init_size_t(device_id_length); uint8_t* device_id; OPK_InitPointer((uint8_t**)&device_id); @@ -892,9 +913,11 @@ ODK_MessageStatus OPK_DispatchMessage(ODK_Message* request, { size_t* wrapped_private_key_length = (size_t*)OPK_VarAlloc(sizeof(size_t)); + if (wrapped_private_key_length == NULL) goto handle_out_of_memory; OPK_Init_size_t(wrapped_private_key_length); uint8_t* clear_private_key_bytes = (uint8_t*)OPK_VarAlloc(sizeof(uint8_t)); + if (clear_private_key_bytes == NULL) goto handle_out_of_memory; OPK_Init_uint8_t((uint8_t*)clear_private_key_bytes); size_t clear_private_key_length; OPK_Init_size_t((size_t*)&clear_private_key_length); @@ -917,6 +940,7 @@ ODK_MessageStatus OPK_DispatchMessage(ODK_Message* request, case 4: /* OEMCrypto_GetKeyData */ { size_t* key_data_length = (size_t*)OPK_VarAlloc(sizeof(size_t)); + if (key_data_length == NULL) goto handle_out_of_memory; OPK_Init_size_t(key_data_length); uint8_t* key_data; OPK_InitPointer((uint8_t**)&key_data); @@ -961,6 +985,7 @@ ODK_MessageStatus OPK_DispatchMessage(ODK_Message* request, case 104: /* OEMCrypto_GetOEMPublicCertificate */ { size_t* public_cert_length = (size_t*)OPK_VarAlloc(sizeof(size_t)); + if (public_cert_length == NULL) goto handle_out_of_memory; OPK_Init_size_t(public_cert_length); uint8_t* public_cert; OPK_InitPointer((uint8_t**)&public_cert); @@ -1001,6 +1026,7 @@ ODK_MessageStatus OPK_DispatchMessage(ODK_Message* request, case 125: /* OEMCrypto_BuildInformation */ { size_t* buffer_length = (size_t*)OPK_VarAlloc(sizeof(size_t)); + if (buffer_length == NULL) goto handle_out_of_memory; OPK_Init_size_t(buffer_length); char* buffer; OPK_InitPointer((uint8_t**)&buffer); @@ -1218,6 +1244,7 @@ ODK_MessageStatus OPK_DispatchMessage(ODK_Message* request, OPK_Init_size_t((size_t*)&signature_length); size_t* wrapped_private_key_length = (size_t*)OPK_VarAlloc(sizeof(size_t)); + if (wrapped_private_key_length == NULL) goto handle_out_of_memory; OPK_Init_size_t(wrapped_private_key_length); OEMCrypto_SESSION session; OPK_Init_uint32_t((uint32_t*)&session); @@ -1259,6 +1286,7 @@ ODK_MessageStatus OPK_DispatchMessage(ODK_Message* request, OPK_Init_size_t((size_t*)&signature_length); size_t* wrapped_private_key_length = (size_t*)OPK_VarAlloc(sizeof(size_t)); + if (wrapped_private_key_length == NULL) goto handle_out_of_memory; OPK_Init_size_t(wrapped_private_key_length); OEMCrypto_SESSION session; OPK_Init_uint32_t((uint32_t*)&session); @@ -1330,6 +1358,7 @@ ODK_MessageStatus OPK_DispatchMessage(ODK_Message* request, size_t message_length; OPK_Init_size_t((size_t*)&message_length); size_t* signature_length = (size_t*)OPK_VarAlloc(sizeof(size_t)); + if (signature_length == NULL) goto handle_out_of_memory; OPK_Init_size_t(signature_length); OEMCrypto_SESSION session; OPK_Init_uint32_t((uint32_t*)&session); @@ -1358,12 +1387,14 @@ ODK_MessageStatus OPK_DispatchMessage(ODK_Message* request, size_t message_length; OPK_Init_size_t((size_t*)&message_length); size_t* signature_length = (size_t*)OPK_VarAlloc(sizeof(size_t)); + if (signature_length == NULL) goto handle_out_of_memory; OPK_Init_size_t(signature_length); OEMCrypto_SESSION session; OPK_Init_uint32_t((uint32_t*)&session); uint8_t* message; OPK_InitPointer((uint8_t**)&message); size_t* core_message_size = (size_t*)OPK_VarAlloc(sizeof(size_t)); + if (core_message_size == NULL) goto handle_out_of_memory; OPK_Init_size_t(core_message_size); uint8_t* signature; OPK_InitPointer((uint8_t**)&signature); @@ -1385,6 +1416,7 @@ ODK_MessageStatus OPK_DispatchMessage(ODK_Message* request, case 61: /* OEMCrypto_CreateUsageTableHeader */ { size_t* header_buffer_length = (size_t*)OPK_VarAlloc(sizeof(size_t)); + if (header_buffer_length == NULL) goto handle_out_of_memory; OPK_Init_size_t(header_buffer_length); uint8_t* header_buffer; OPK_InitPointer((uint8_t**)&header_buffer); @@ -1472,8 +1504,10 @@ ODK_MessageStatus OPK_DispatchMessage(ODK_Message* request, case 65: /* OEMCrypto_UpdateUsageEntry */ { size_t* header_buffer_length = (size_t*)OPK_VarAlloc(sizeof(size_t)); + if (header_buffer_length == NULL) goto handle_out_of_memory; OPK_Init_size_t(header_buffer_length); size_t* entry_buffer_length = (size_t*)OPK_VarAlloc(sizeof(size_t)); + if (entry_buffer_length == NULL) goto handle_out_of_memory; OPK_Init_size_t(entry_buffer_length); OEMCrypto_SESSION session; OPK_Init_uint32_t((uint32_t*)&session); @@ -1519,6 +1553,7 @@ ODK_MessageStatus OPK_DispatchMessage(ODK_Message* request, size_t pst_length; OPK_Init_size_t((size_t*)&pst_length); size_t* buffer_length = (size_t*)OPK_VarAlloc(sizeof(size_t)); + if (buffer_length == NULL) goto handle_out_of_memory; OPK_Init_size_t(buffer_length); OEMCrypto_SESSION session; OPK_Init_uint32_t((uint32_t*)&session); @@ -1580,6 +1615,7 @@ ODK_MessageStatus OPK_DispatchMessage(ODK_Message* request, case 67: /* OEMCrypto_ShrinkUsageTableHeader */ { size_t* header_buffer_length = (size_t*)OPK_VarAlloc(sizeof(size_t)); + if (header_buffer_length == NULL) goto handle_out_of_memory; OPK_Init_size_t(header_buffer_length); uint32_t new_entry_count; OPK_Init_uint32_t((uint32_t*)&new_entry_count); @@ -1600,9 +1636,11 @@ ODK_MessageStatus OPK_DispatchMessage(ODK_Message* request, case 116: /* OEMCrypto_GetBootCertificateChain */ { size_t* bcc_length = (size_t*)OPK_VarAlloc(sizeof(size_t)); + if (bcc_length == NULL) goto handle_out_of_memory; OPK_Init_size_t(bcc_length); size_t* additional_signature_length = (size_t*)OPK_VarAlloc(sizeof(size_t)); + if (additional_signature_length == NULL) goto handle_out_of_memory; OPK_Init_size_t(additional_signature_length); uint8_t* bcc; OPK_InitPointer((uint8_t**)&bcc); @@ -1625,12 +1663,15 @@ ODK_MessageStatus OPK_DispatchMessage(ODK_Message* request, case 117: /* OEMCrypto_GenerateCertificateKeyPair */ { size_t* public_key_length = (size_t*)OPK_VarAlloc(sizeof(size_t)); + if (public_key_length == NULL) goto handle_out_of_memory; OPK_Init_size_t(public_key_length); size_t* public_key_signature_length = (size_t*)OPK_VarAlloc(sizeof(size_t)); + if (public_key_signature_length == NULL) goto handle_out_of_memory; OPK_Init_size_t(public_key_signature_length); size_t* wrapped_private_key_length = (size_t*)OPK_VarAlloc(sizeof(size_t)); + if (wrapped_private_key_length == NULL) goto handle_out_of_memory; OPK_Init_size_t(wrapped_private_key_length); OEMCrypto_SESSION session; OPK_Init_uint32_t((uint32_t*)&session); @@ -1663,6 +1704,7 @@ ODK_MessageStatus OPK_DispatchMessage(ODK_Message* request, case 131: /* OEMCrypto_GetDeviceInformation */ { size_t* device_info_length = (size_t*)OPK_VarAlloc(sizeof(size_t)); + if (device_info_length == NULL) goto handle_out_of_memory; OPK_Init_size_t(device_info_length); uint8_t* device_info; OPK_InitPointer((uint8_t**)&device_info); @@ -1684,6 +1726,7 @@ ODK_MessageStatus OPK_DispatchMessage(ODK_Message* request, size_t encoded_device_info_length; OPK_Init_size_t((size_t*)&encoded_device_info_length); size_t* signed_csr_payload_length = (size_t*)OPK_VarAlloc(sizeof(size_t)); + if (signed_csr_payload_length == NULL) goto handle_out_of_memory; OPK_Init_size_t(signed_csr_payload_length); uint8_t* challenge; OPK_InitPointer((uint8_t**)&challenge); @@ -1815,6 +1858,7 @@ ODK_MessageStatus OPK_DispatchMessage(ODK_Message* request, OEMCrypto_DestBufferDesc* output_descriptor = (OEMCrypto_DestBufferDesc*)OPK_VarAlloc( sizeof(OEMCrypto_DestBufferDesc)); + if (output_descriptor == NULL) goto handle_out_of_memory; OPK_Init_OEMCrypto_DestBufferDesc(output_descriptor); int secure_fd; OPK_Init_int((int*)&secure_fd); @@ -1832,8 +1876,10 @@ ODK_MessageStatus OPK_DispatchMessage(ODK_Message* request, case 115: /* OEMCrypto_OPK_SerializationVersion */ { uint32_t* ree_major = (uint32_t*)OPK_VarAlloc(sizeof(uint32_t)); + if (ree_major == NULL) goto handle_out_of_memory; OPK_Init_uint32_t(ree_major); uint32_t* ree_minor = (uint32_t*)OPK_VarAlloc(sizeof(uint32_t)); + if (ree_minor == NULL) goto handle_out_of_memory; OPK_Init_uint32_t(ree_minor); uint32_t* tee_major; OPK_InitPointer((uint8_t**)&tee_major); @@ -1854,6 +1900,7 @@ ODK_MessageStatus OPK_DispatchMessage(ODK_Message* request, case 113: /* OEMCrypto_GenerateOTARequest */ { size_t* buffer_length = (size_t*)OPK_VarAlloc(sizeof(size_t)); + if (buffer_length == NULL) goto handle_out_of_memory; OPK_Init_size_t(buffer_length); OEMCrypto_SESSION session; OPK_Init_uint32_t((uint32_t*)&session); @@ -1897,6 +1944,7 @@ ODK_MessageStatus OPK_DispatchMessage(ODK_Message* request, case 151: /* OEMCrypto_GetEmbeddedDrmCertificate */ { size_t* public_cert_length = (size_t*)OPK_VarAlloc(sizeof(size_t)); + if (public_cert_length == NULL) goto handle_out_of_memory; OPK_Init_size_t(public_cert_length); uint8_t* public_cert; OPK_InitPointer((uint8_t**)&public_cert); @@ -1921,4 +1969,10 @@ handle_invalid_request: LOGE("invalid request"); *response = CreateEmptyMessage(); return MESSAGE_STATUS_OK; + +handle_out_of_memory: + LOGE("out of memory"); + ODK_Message_SetStatus(request, MESSAGE_STATUS_OUT_OF_MEMORY); + *response = CreateEmptyMessage(); + return MESSAGE_STATUS_OK; } diff --git a/oemcrypto/opk/serialization/tee/GEN_tee_serializer.c b/oemcrypto/opk/serialization/tee/GEN_tee_serializer.c index 16962e8..9e13e2a 100644 --- a/oemcrypto/opk/serialization/tee/GEN_tee_serializer.c +++ b/oemcrypto/opk/serialization/tee/GEN_tee_serializer.c @@ -683,6 +683,32 @@ ODK_Message OPK_Pack_GetOEMKeyToken_Response(OEMCryptoResult result, return msg; } +void OPK_Unpack_SetSessionUsage_Request(ODK_Message* msg, + OEMCrypto_SESSION* session, + uint32_t* intent, uint32_t* mode) { + uint32_t api_value = UINT32_MAX; + OPK_Unpack_uint32_t(msg, &api_value); + if (api_value != 155) + ODK_MESSAGE_SETSTATUS(msg, MESSAGE_STATUS_API_VALUE_ERROR); + uint64_t timestamp; + OPK_Unpack_uint64_t(msg, ×tamp); + OPK_Unpack_uint32_t(msg, session); + OPK_Unpack_uint32_t(msg, intent); + OPK_Unpack_uint32_t(msg, mode); + OPK_UnpackEOM(msg); + OPK_SharedBuffer_FinalizeUnpacking(); +} + +ODK_Message OPK_Pack_SetSessionUsage_Response(OEMCryptoResult result) { + uint32_t api_value = 155; /* from _oecc155 */ + ODK_Message msg = TOS_Transport_GetResponse(); + OPK_Pack_uint32_t(&msg, &api_value); + OPK_Pack_uint32_t(&msg, &result); + OPK_PackEOM(&msg); + OPK_SharedBuffer_FinalizePacking(); + return msg; +} + void OPK_Unpack_GetKeyHandle_Request( ODK_Message* msg, OEMCrypto_SESSION* session, uint8_t** content_key_id, size_t* content_key_id_length, OEMCryptoCipherMode* cipher_mode, diff --git a/oemcrypto/opk/serialization/tee/GEN_tee_serializer.h b/oemcrypto/opk/serialization/tee/GEN_tee_serializer.h index a376ddd..abdf124 100644 --- a/oemcrypto/opk/serialization/tee/GEN_tee_serializer.h +++ b/oemcrypto/opk/serialization/tee/GEN_tee_serializer.h @@ -122,6 +122,10 @@ void OPK_Unpack_GetOEMKeyToken_Request(ODK_Message* msg, ODK_Message OPK_Pack_GetOEMKeyToken_Response(OEMCryptoResult result, const uint8_t* key_token, const size_t* key_token_length); +void OPK_Unpack_SetSessionUsage_Request(ODK_Message* msg, + OEMCrypto_SESSION* session, + uint32_t* intent, uint32_t* mode); +ODK_Message OPK_Pack_SetSessionUsage_Response(OEMCryptoResult result); void OPK_Unpack_GetKeyHandle_Request( ODK_Message* msg, OEMCrypto_SESSION* session, uint8_t** content_key_id, size_t* content_key_id_length, OEMCryptoCipherMode* cipher_mode, diff --git a/oemcrypto/opk/serialization/tee/special_case_request_handlers.c b/oemcrypto/opk/serialization/tee/special_case_request_handlers.c index ebbfd05..97339a7 100644 --- a/oemcrypto/opk/serialization/tee/special_case_request_handlers.c +++ b/oemcrypto/opk/serialization/tee/special_case_request_handlers.c @@ -18,7 +18,8 @@ void OPK_Init_OEMCrypto_EntitledContentKeyObject( OEMCrypto_EntitledContentKeyObject* obj); bool Handle_OEMCrypto_LoadCasECMKeys(ODK_Message* request, - ODK_Message* response) { + ODK_Message* response, + ODK_MessageStatus* status) { size_t message_length; OPK_Init_size_t((size_t*)&message_length); OEMCrypto_SESSION session; @@ -28,11 +29,19 @@ bool Handle_OEMCrypto_LoadCasECMKeys(ODK_Message* request, OEMCrypto_EntitledContentKeyObject* even_key = (OEMCrypto_EntitledContentKeyObject*)OPK_VarAlloc( sizeof(OEMCrypto_EntitledContentKeyObject)); + if (!even_key) { + *status = MESSAGE_STATUS_OUT_OF_MEMORY; + return false; + } OPK_Init_OEMCrypto_EntitledContentKeyObject( (OEMCrypto_EntitledContentKeyObject*)even_key); OEMCrypto_EntitledContentKeyObject* odd_key = (OEMCrypto_EntitledContentKeyObject*)OPK_VarAlloc( sizeof(OEMCrypto_EntitledContentKeyObject)); + if (!odd_key) { + *status = MESSAGE_STATUS_OUT_OF_MEMORY; + return false; + } OPK_Init_OEMCrypto_EntitledContentKeyObject( (OEMCrypto_EntitledContentKeyObject*)odd_key); OPK_Unpack_LoadCasECMKeys_Request(request, &session, &message, @@ -44,5 +53,6 @@ bool Handle_OEMCrypto_LoadCasECMKeys(ODK_Message* request, result = OEMCrypto_LoadCasECMKeys(session, message, message_length, even_key, odd_key); *response = OPK_Pack_LoadCasECMKeys_Response(result); + *status = MESSAGE_STATUS_OK; return true; } diff --git a/oemcrypto/opk/serialization/tee/special_case_request_handlers.h b/oemcrypto/opk/serialization/tee/special_case_request_handlers.h index 4c7273e..e48c49e 100644 --- a/oemcrypto/opk/serialization/tee/special_case_request_handlers.h +++ b/oemcrypto/opk/serialization/tee/special_case_request_handlers.h @@ -16,7 +16,8 @@ extern "C" { #include "odk_message.h" bool Handle_OEMCrypto_LoadCasECMKeys(ODK_Message* request, - ODK_Message* response); + ODK_Message* response, + ODK_MessageStatus* status); #ifdef __cplusplus } // extern "C" diff --git a/oemcrypto/test/GEN_api_lock_file.c b/oemcrypto/test/GEN_api_lock_file.c index af60124..63165bd 100644 --- a/oemcrypto/test/GEN_api_lock_file.c +++ b/oemcrypto/test/GEN_api_lock_file.c @@ -434,3 +434,7 @@ OEMCryptoResult _oecc154(const uint8_t* clear_private_key_bytes, // OEMCrypto_MarkOfflineSession defined in v19.2 OEMCryptoResult _oecc153(OEMCrypto_SESSION session); + +// OEMCrypto_SetSessionUsage defined in v18.7 +OEMCryptoResult _oecc155(OEMCrypto_SESSION session, uint32_t intent, + uint32_t mode); diff --git a/oemcrypto/test/common.mk b/oemcrypto/test/common.mk index 9c0b351..8f8b7d7 100644 --- a/oemcrypto/test/common.mk +++ b/oemcrypto/test/common.mk @@ -49,6 +49,7 @@ LOCAL_C_INCLUDES += \ LOCAL_STATIC_LIBRARIES := \ libcdm \ + libcppbor \ libjsmn \ libgmock \ libgtest \ @@ -62,7 +63,6 @@ LOCAL_STATIC_LIBRARIES := \ LOCAL_SHARED_LIBRARIES := \ libbase \ - libcppbor \ libcrypto \ libdl \ libbinder_ndk \ diff --git a/oemcrypto/test/extract_bcc_tool.cpp b/oemcrypto/test/extract_bcc_tool.cpp index a469ea6..ad34d5e 100644 --- a/oemcrypto/test/extract_bcc_tool.cpp +++ b/oemcrypto/test/extract_bcc_tool.cpp @@ -12,6 +12,7 @@ #include "OEMCryptoCENC.h" #include "string_conversions.h" +#include "wv_factory_extractor.h" namespace { // Make and Model for system ID resolution. @@ -24,116 +25,8 @@ const std::string kDeviceName = "prov40 test client"; const std::string kDeviceProduct = "prov40 test"; const std::string kDeviceBuildInfo = "prov40 test build"; -// == Utils == - -std::string StringMapToJson( - const std::map& string_map) { - std::string json = "{"; - for (const auto& value_pair : string_map) { - std::string escaped_value = - std::regex_replace(value_pair.second, std::regex("\""), "\\\""); - json.append("\"" + value_pair.first + "\": " + "\"" + escaped_value + - "\","); - } - json.resize(json.size() - 1); // Remove the last comma. - json.append("}"); - return json; -} // == Primary == - -bool GetBccAndBuildInfo(std::vector* bcc, - std::string* oemcrypto_build_info) { - // Step 1: Initialize. - OEMCryptoResult result = OEMCrypto_Initialize(); - if (result != OEMCrypto_SUCCESS) { - std::cerr << "Failed to initialize: result = " << result << std::endl; - return false; - } - - // Step 2: Get BCC. - const OEMCrypto_ProvisioningMethod method = OEMCrypto_GetProvisioningMethod(); - if (method != OEMCrypto_BootCertificateChain) { - std::cerr << "ProvisioningMethod is not BCC type: method = "; - std::cerr << method << std::endl; - OEMCrypto_Terminate(); - return false; - } - - bcc->resize(0); - size_t bcc_size = 0; - std::vector additional_signature; // It should be empty. - size_t additional_signature_size = 0; - result = OEMCrypto_GetBootCertificateChain(bcc->data(), &bcc_size, - additional_signature.data(), - &additional_signature_size); - if (additional_signature_size != 0) { - std::cerr << "The additional_signature_size required by OEMCrypto is " - << additional_signature_size - << ", while it is expected to be zero." << std::endl; - OEMCrypto_Terminate(); - return false; - } - if (result == OEMCrypto_ERROR_SHORT_BUFFER) { - bcc->resize(bcc_size); - additional_signature.resize(additional_signature_size); - result = OEMCrypto_GetBootCertificateChain(bcc->data(), &bcc_size, - additional_signature.data(), - &additional_signature_size); - } - if (result != OEMCrypto_SUCCESS) { - std::cerr << "Failed to get BCC: result = " << result << std::endl; - OEMCrypto_Terminate(); - return false; - } - bcc->resize(bcc_size); - - // Step 3: Get oemcrypto build info. - oemcrypto_build_info->resize(0); - size_t oemcrypto_build_info_size = 0; - result = OEMCrypto_BuildInformation(oemcrypto_build_info->data(), - &oemcrypto_build_info_size); - if (result == OEMCrypto_ERROR_SHORT_BUFFER) { - oemcrypto_build_info->resize(oemcrypto_build_info_size); - result = OEMCrypto_BuildInformation(oemcrypto_build_info->data(), - &oemcrypto_build_info_size); - } - if (result != OEMCrypto_SUCCESS) { - std::cerr << "Failed to get build information: result = " << result - << std::endl; - OEMCrypto_Terminate(); - return false; - } - oemcrypto_build_info->resize(oemcrypto_build_info_size); - - // Step 4: Cleanup. - result = OEMCrypto_Terminate(); - if (result != OEMCrypto_SUCCESS) { - std::cerr << "Failed to terminate: result = " << result << std::endl; - return false; - } - return true; -} - -bool GenerateBccRecord(const std::vector& bcc, - const std::string& oemcrypto_build_info, - std::string* bcc_record) { - std::map record; - record["company"] = kDeviceMake; - record["model"] = kDeviceModel; - - record["architecture"] = kDeviceArchitecture; - record["name"] = kDeviceName; - record["product"] = kDeviceProduct; - record["build_info"] = kDeviceBuildInfo; - record["bcc"] = wvutil::Base64Encode(bcc); - record["oemcrypto_build_info"] = oemcrypto_build_info; - - const std::string record_json = StringMapToJson(record); - bcc_record->assign(record_json.begin(), record_json.end()); - return true; -} - bool OutputBccRecord(const std::string& path, const std::string& record) { std::cout << "Writing BCC record to file " << path << std::endl; std::cout << record << std::endl; @@ -154,17 +47,27 @@ int main(int argc, char** argv) { } const std::string bcc_path = argv[1]; - std::vector bcc; - std::string oemcrypto_build_info; - if (!GetBccAndBuildInfo(&bcc, &oemcrypto_build_info)) { - std::cerr << "Failed to get BCC or OEMCrypto build info" << std::endl; + widevine::ClientInfo client_info; + client_info.company_name = kDeviceMake; + client_info.arch_name = kDeviceArchitecture; + client_info.device_name = kDeviceName; + client_info.model_name = kDeviceModel; + client_info.product_name = kDeviceProduct; + client_info.build_info = kDeviceBuildInfo; + + auto extractor = widevine::WidevineFactoryExtractor::Create(client_info); + if (extractor == nullptr) { + std::cerr << "Failed to create WidevineFactoryExtractor" << std::endl; return 1; } + std::string bcc_record; - if (!GenerateBccRecord(bcc, oemcrypto_build_info, &bcc_record)) { - std::cerr << "Failed to generate BCC record" << std::endl; + widevine::Status status = extractor->GenerateUploadRequest(bcc_record); + if (status != widevine::Status::kSuccess) { + std::cerr << "Fail to generate BCC record: " << status << std::endl; return 1; } + if (!OutputBccRecord(bcc_path, bcc_record)) { std::cerr << "Failed to output BCC record" << std::endl; return 1; diff --git a/oemcrypto/test/install_prov30_oem_cert_tool.cpp b/oemcrypto/test/install_prov30_oem_cert_tool.cpp index 25eac6d..f4fd408 100644 --- a/oemcrypto/test/install_prov30_oem_cert_tool.cpp +++ b/oemcrypto/test/install_prov30_oem_cert_tool.cpp @@ -26,6 +26,8 @@ format below: +-----------------------+----------------------+--------------------------+ | Private Key | +-----------------------+ +| (DER-encoded PKCS#8) | ++-----------------------+ |oem_private_key| should be a RSA key in PKCS#8 PrivateKeyInfo format. |oem_public_cert| should be a DER-encoded PKCS#7 certificate chain. diff --git a/oemcrypto/test/oec_decrypt_fallback_chain.cpp b/oemcrypto/test/oec_decrypt_fallback_chain.cpp index 6cb7aac..fe303fc 100644 --- a/oemcrypto/test/oec_decrypt_fallback_chain.cpp +++ b/oemcrypto/test/oec_decrypt_fallback_chain.cpp @@ -17,7 +17,6 @@ void advance_dest_buffer(OEMCrypto_DestBufferDesc* dest_buffer, size_t bytes) { switch (dest_buffer->type) { case OEMCrypto_BufferType_Clear: dest_buffer->buffer.clear.clear_buffer += bytes; - dest_buffer->buffer.clear.clear_buffer_length -= bytes; break; case OEMCrypto_BufferType_Secure: @@ -99,6 +98,11 @@ OEMCryptoResult DecryptFallbackChain::DecryptSample( const size_t length = subsample.num_bytes_clear + subsample.num_bytes_encrypted; fake_sample.buffers.input_data_length = length; + if (fake_sample.buffers.output_descriptor.type == + OEMCrypto_BufferType_Clear) { + fake_sample.buffers.output_descriptor.buffer.clear.clear_buffer_length = + length; + } fake_sample.subsamples = &subsample; fake_sample.subsamples_length = 1; @@ -144,6 +148,11 @@ OEMCryptoResult DecryptFallbackChain::DecryptSubsample( if (subsample.num_bytes_clear > 0) { fake_sample.buffers.input_data_length = subsample.num_bytes_clear; + if (fake_sample.buffers.output_descriptor.type == + OEMCrypto_BufferType_Clear) { + fake_sample.buffers.output_descriptor.buffer.clear.clear_buffer_length = + subsample.num_bytes_clear; + } fake_subsample.num_bytes_clear = subsample.num_bytes_clear; fake_subsample.num_bytes_encrypted = 0; fake_subsample.block_offset = 0; @@ -167,6 +176,11 @@ OEMCryptoResult DecryptFallbackChain::DecryptSubsample( if (subsample.num_bytes_encrypted > 0) { fake_sample.buffers.input_data_length = subsample.num_bytes_encrypted; + if (fake_sample.buffers.output_descriptor.type == + OEMCrypto_BufferType_Clear) { + fake_sample.buffers.output_descriptor.buffer.clear.clear_buffer_length = + subsample.num_bytes_encrypted; + } fake_subsample.num_bytes_clear = 0; fake_subsample.num_bytes_encrypted = subsample.num_bytes_encrypted; fake_subsample.block_offset = subsample.block_offset; diff --git a/oemcrypto/test/oec_device_features.cpp b/oemcrypto/test/oec_device_features.cpp index b70b3c6..276e52d 100644 --- a/oemcrypto/test/oec_device_features.cpp +++ b/oemcrypto/test/oec_device_features.cpp @@ -10,7 +10,9 @@ #include +#include "log.h" #include "oec_test_data.h" +#include "string_conversions.h" #include "test_sleep.h" namespace wvoec { @@ -68,6 +70,12 @@ void DeviceFeatures::Initialize() { provisioning_method == OEMCrypto_BootCertificateChain || provisioning_method == OEMCrypto_DrmReprovisioning; printf("loads_certificate = %s.\n", loads_certificate ? "true" : "false"); + if (rsa_test_key().empty()) { + set_rsa_test_key( + std::vector(kTestRSAPKCS8PrivateKeyInfo2_2048, + kTestRSAPKCS8PrivateKeyInfo2_2048 + + sizeof(kTestRSAPKCS8PrivateKeyInfo2_2048))); + } generic_crypto = (OEMCrypto_ERROR_NOT_IMPLEMENTED != OEMCrypto_Generic_Encrypt(buffer, 0, buffer, 0, iv, @@ -129,6 +137,9 @@ void DeviceFeatures::Initialize() { case LOAD_TEST_RSA_KEY: printf("LOAD_TEST_RSA_KEY: Call LoadTestRSAKey before deriving keys.\n"); break; + case PRELOADED_RSA_KEY: + printf("PRELOADED_RSA_KEY: Device has test RSA key baked in.\n"); + break; case TEST_PROVISION_30: printf("TEST_PROVISION_30: Device provisioned with OEM Cert.\n"); break; @@ -153,9 +164,10 @@ void DeviceFeatures::PickDerivedKey() { return; case OEMCrypto_DrmCertificate: case OEMCrypto_DrmReprovisioning: - if (OEMCrypto_ERROR_NOT_IMPLEMENTED != OEMCrypto_LoadTestRSAKey()) { - derive_key_method = LOAD_TEST_RSA_KEY; - } + derive_key_method = + (OEMCrypto_ERROR_NOT_IMPLEMENTED == OEMCrypto_LoadTestRSAKey()) + ? PRELOADED_RSA_KEY + : LOAD_TEST_RSA_KEY; return; case OEMCrypto_Keybox: if (OEMCrypto_ERROR_NOT_IMPLEMENTED != diff --git a/oemcrypto/test/oec_device_features.h b/oemcrypto/test/oec_device_features.h index 78d1cf6..2eb1dd9 100644 --- a/oemcrypto/test/oec_device_features.h +++ b/oemcrypto/test/oec_device_features.h @@ -38,6 +38,7 @@ class DeviceFeatures { LOAD_TEST_RSA_KEY, // Call LoadTestRSAKey before deriving keys. TEST_PROVISION_30, // Device has OEM Certificate installed. TEST_PROVISION_40, // Device has Boot Certificate Chain installed. + PRELOADED_RSA_KEY, // Device has test RSA key baked in. }; enum DeriveMethod derive_key_method; @@ -65,6 +66,16 @@ class DeviceFeatures { // Get a list of output types that should be tested. const std::vector& GetOutputTypes(); + // If the device has a baked in cert, then this is the public key that should + // be used for testing. + const std::vector& rsa_test_key() const { return rsa_test_key_; }; + void set_rsa_test_key(const std::vector& rsa_test_key) { + rsa_test_key_ = rsa_test_key; + } + void set_rsa_test_key(std::vector&& rsa_test_key) { + rsa_test_key_ = std::move(rsa_test_key); + } + private: // Decide which method should be used to derive session keys, based on // supported featuers. @@ -77,6 +88,7 @@ class DeviceFeatures { // A list of possible output types. std::vector output_types_; bool initialized_ = false; + std::vector rsa_test_key_; }; // There is one global set of features for the version of OEMCrypto being diff --git a/oemcrypto/test/oec_session_util.cpp b/oemcrypto/test/oec_session_util.cpp index c23bd7d..70297a8 100644 --- a/oemcrypto/test/oec_session_util.cpp +++ b/oemcrypto/test/oec_session_util.cpp @@ -582,7 +582,7 @@ void ProvisioningRoundTrip::VerifyLoadFailed() { } void Provisioning40RoundTrip::PrepareSession(bool is_oem_key) { - const size_t buffer_size = 5000; // Make sure it is large enough. + const size_t buffer_size = 10240; // Make sure it is large enough. std::vector public_key(buffer_size); size_t public_key_size = buffer_size; std::vector public_key_signature(buffer_size); @@ -644,7 +644,7 @@ OEMCryptoResult Provisioning40RoundTrip::LoadDRMCertResponse() { } void Provisioning40CastRoundTrip::PrepareSession() { - const size_t buffer_size = 5000; // Make sure it is large enough. + const size_t buffer_size = 10240; // Make sure it is large enough. std::vector public_key(buffer_size); size_t public_key_size = buffer_size; std::vector public_key_signature(buffer_size); @@ -2041,10 +2041,9 @@ void Session::LoadOEMCert(bool verify_cert) { void Session::SetTestRsaPublicKey() { public_ec_.reset(); - public_rsa_ = util::RsaPublicKey::LoadPrivateKeyInfo( - kTestRSAPKCS8PrivateKeyInfo2_2048, - sizeof(kTestRSAPKCS8PrivateKeyInfo2_2048)); - ASSERT_TRUE(public_rsa_) << "Could not parse test RSA public key #2"; + public_rsa_ = + util::RsaPublicKey::LoadPrivateKeyInfo(global_features.rsa_test_key()); + ASSERT_TRUE(public_rsa_) << "Could not parse test RSA public key"; } void Session::SetPublicKeyFromPrivateKeyInfo(OEMCrypto_PrivateKeyType key_type, diff --git a/oemcrypto/test/oemcrypto_basic_test.cpp b/oemcrypto/test/oemcrypto_basic_test.cpp index ef43884..36dccb9 100644 --- a/oemcrypto/test/oemcrypto_basic_test.cpp +++ b/oemcrypto/test/oemcrypto_basic_test.cpp @@ -2,17 +2,79 @@ // source code may only be used and distributed under the Widevine // License Agreement. // - #include "oemcrypto_basic_test.h" +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include "OEMCryptoCENC.h" #include "clock.h" -#include "jsmn.h" #include "log.h" #include "oemcrypto_corpus_generator_helper.h" #include "oemcrypto_resource_test.h" #include "test_sleep.h" +void PrintTo(const jsmntype_t& type, std::ostream* out) { + switch (type) { + case JSMN_UNDEFINED: + *out << "Undefined"; + return; + case JSMN_OBJECT: + *out << "Object"; + return; + case JSMN_ARRAY: + *out << "Array"; + return; + case JSMN_STRING: + *out << "String"; + return; + case JSMN_PRIMITIVE: + *out << "Primitive"; + return; + } + *out << "Unknown(" << static_cast(type) << ')'; +} + namespace wvoec { +namespace { +// Counts the number of ancestor tokens of the provided |root_index| token. +// The result does not count the root itself. +// +// JSMN tokens specify the count of immediate ancessor tokens, but +// not the total. +// - Primitives never have children +// - Strings have 0 if they are a value, and 1 if they are the +// name of an object member +// - Objects have the count of members (each key-value pair is 1, +// regardless of the value's children elements) +// - Arrays have the count of elements (regardless of the values members) +// +int32_t JsmnAncestorCount(const std::vector& tokens, + int32_t root_index) { + if (root_index >= static_cast(tokens.size())) return 0; + int32_t count = 0; + int32_t iter = root_index; + int32_t remainder = 1; + while (remainder > 0 && iter < static_cast(tokens.size())) { + const int32_t child_count = tokens[iter].size; + remainder += child_count; + count += child_count; + iter++; + remainder--; + } + return count; +} +} // namespace + void OEMCryptoClientTest::SetUp() { ::testing::Test::SetUp(); wvutil::TestSleep::SyncFakeClock(); @@ -180,16 +242,14 @@ TEST_F(OEMCryptoClientTest, FreeUnallocatedSecureBufferNoFailure) { */ TEST_F(OEMCryptoClientTest, VersionNumber) { const std::string log_message = - "OEMCrypto unit tests for API 19.2. Tests last updated 2024-06-24"; + "OEMCrypto unit tests for API 19.3. Tests last updated 2024-09-04"; cout << " " << log_message << "\n"; - cout << " " - << "These tests are part of Android U." - << "\n"; + cout << " " << "These tests are part of Android V." << "\n"; LOGI("%s", log_message.c_str()); // If any of the following fail, then it is time to update the log message // above. EXPECT_EQ(ODK_MAJOR_VERSION, 19); - EXPECT_EQ(ODK_MINOR_VERSION, 2); + EXPECT_EQ(ODK_MINOR_VERSION, 3); EXPECT_EQ(kCurrentAPI, static_cast(ODK_MAJOR_VERSION)); OEMCrypto_Security_Level level = OEMCrypto_SecurityLevel(); EXPECT_GT(level, OEMCrypto_Level_Unknown); @@ -316,29 +376,154 @@ TEST_F(OEMCryptoClientTest, CheckNullBuildInformationAPI17) { } } +// Verifies that OEMCrypto_BuildInformation() is behaving as expected +// by assigning appropriate values to the build info size. +TEST_F(OEMCryptoClientTest, CheckBuildInformation_OutputLengthAPI17) { + if (wvoec::global_features.api_version < 17) { + GTEST_SKIP() << "Test for versions 17 and up only."; + } + + constexpr size_t kZero = 0; + constexpr char kNullChar = '\0'; + + // Allocating single byte to avoid potential null dereference. + std::string build_info(1, kNullChar); + size_t build_info_length = 0; + + OEMCryptoResult result = + OEMCrypto_BuildInformation(&build_info[0], &build_info_length); + + ASSERT_EQ(result, OEMCrypto_ERROR_SHORT_BUFFER); + ASSERT_GT(build_info_length, kZero) + << "Signaling ERROR_SHORT_BUFFER should have assigned a length"; + + // Force a ERROR_SHORT_BUFFER using a non-zero value. + // Note: It is assumed that vendors will provide more than a single + // character of info. + const size_t second_attempt_length = + (build_info_length >= 2) ? build_info_length / 2 : 1; + build_info.assign(second_attempt_length, kNullChar); + build_info_length = build_info.size(); + + result = OEMCrypto_BuildInformation(&build_info[0], &build_info_length); + ASSERT_EQ(result, OEMCrypto_ERROR_SHORT_BUFFER) + << "second_attempt_length = " << second_attempt_length + << ", build_info_length" << build_info_length; + // OEM specified build info length should be larger than the + // original length if returning ERROR_SHORT_BUFFER. + ASSERT_GT(build_info_length, second_attempt_length); + + // Final attempt with a buffer large enough buffer, padding to + // ensure the caller truncates. + constexpr size_t kBufferPadSize = 42; + const size_t expected_length = build_info_length; + const size_t final_attempt_length = expected_length + kBufferPadSize; + build_info.assign(final_attempt_length, kNullChar); + build_info_length = build_info.size(); + + result = OEMCrypto_BuildInformation(&build_info[0], &build_info_length); + + ASSERT_EQ(result, OEMCrypto_SUCCESS) + << "final_attempt_length = " << final_attempt_length + << ", expected_length = " << expected_length + << ", build_info_length = " << build_info_length; + // Ensure not empty. + ASSERT_GT(build_info_length, kZero) << "Build info cannot be empty"; + // Ensure it was truncated down from the padded length. + ASSERT_LT(build_info_length, final_attempt_length) + << "Should have truncated from oversized buffer: expected_length = " + << expected_length; + // Ensure the real length is within the size originally specified. + // OK if final length is smaller than estimated length. + ASSERT_LE(build_info_length, expected_length); +} + +// Verifies that OEMCrypto_BuildInformation() is behaving as expected +// by checking the resulting contents. +// Does not validate whether output if valid JSON for v18. +TEST_F(OEMCryptoClientTest, CheckBuildInformation_OutputContentAPI17) { + if (wvoec::global_features.api_version < 17) { + GTEST_SKIP() << "Test for versions 17 and up only."; + } + + constexpr size_t kZero = 0; + constexpr char kNullChar = '\0'; + + // Allocating single byte to avoid potential null dereference. + std::string build_info(1, kNullChar); + size_t build_info_length = 0; + OEMCryptoResult result = + OEMCrypto_BuildInformation(&build_info[0], &build_info_length); + ASSERT_EQ(result, OEMCrypto_ERROR_SHORT_BUFFER); + ASSERT_GT(build_info_length, kZero) + << "Signaling ERROR_SHORT_BUFFER should have assigned a length"; + + // Expect successful acquisition of build information. + const size_t expected_length = build_info_length; + build_info.assign(expected_length, kNullChar); + result = OEMCrypto_BuildInformation(&build_info[0], &build_info_length); + ASSERT_EQ(result, OEMCrypto_SUCCESS) + << "expected_length = " << expected_length + << ", build_info_length = " << build_info_length; + // Ensure not empty. + ASSERT_GT(build_info_length, kZero) << "Build info cannot be empty"; + // Ensure the real length is within the size originally specified. + ASSERT_LE(build_info_length, expected_length) + << "Cannot specify success if buffer was too small"; + build_info.resize(build_info_length); + + // Ensure there isn't a trailing null byte. + ASSERT_NE(build_info.back(), kNullChar) + << "Build info must not contain trailing null byte"; + + // Ensure all build info characters are printable, or a limited + // set of white space characters (case of JSON build info). + const auto is_valid_build_info_white_space = [](const char& ch) -> bool { + constexpr char kSpace = ' '; + constexpr char kLineFeed = '\n'; + constexpr char kTab = '\t'; + return ch == kLineFeed || ch == kTab || ch == kSpace; + }; + const auto is_valid_build_info_char = [&](const char& ch) -> bool { + return ::isprint(ch) || is_valid_build_info_white_space(ch); + }; + ASSERT_TRUE(std::all_of(build_info.begin(), build_info.end(), + is_valid_build_info_char)) + << "Build info is not printable: " << wvutil::b2a_hex(build_info); + + // Ensure build info isn't just white space. + ASSERT_FALSE(std::all_of(build_info.begin(), build_info.end(), + is_valid_build_info_white_space)) + << "Build info is just white space: " << wvutil::b2a_hex(build_info); +} + TEST_F(OEMCryptoClientTest, CheckJsonBuildInformationAPI18) { if (wvoec::global_features.api_version < 18) { GTEST_SKIP() << "Test for versions 18 and up only."; } - std::string build_info; - OEMCryptoResult sts = OEMCrypto_BuildInformation(&build_info[0], nullptr); - ASSERT_EQ(OEMCrypto_ERROR_INVALID_CONTEXT, sts); - size_t buf_length = 0; + constexpr char kNullChar = '\0'; + constexpr size_t kZero = 0; + + // Step 1: Get Build Info + size_t buffer_length = 0; // OEMCrypto must allow |buffer| to be null so long as |buffer_length| // is provided and initially set to zero. - sts = OEMCrypto_BuildInformation(nullptr, &buf_length); - ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, sts); - build_info.resize(buf_length); - const size_t max_final_size = buf_length; - sts = OEMCrypto_BuildInformation(&build_info[0], &buf_length); - ASSERT_EQ(OEMCrypto_SUCCESS, sts); - ASSERT_LE(buf_length, max_final_size); - build_info.resize(buf_length); + OEMCryptoResult result = OEMCrypto_BuildInformation(nullptr, &buffer_length); + ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, result); + ASSERT_GT(buffer_length, kZero); + std::string build_info(buffer_length, kNullChar); + const size_t max_final_size = buffer_length; + result = OEMCrypto_BuildInformation(&build_info[0], &buffer_length); + ASSERT_EQ(OEMCrypto_SUCCESS, result); + ASSERT_LE(buffer_length, max_final_size); + build_info.resize(buffer_length); + + // Step 2: Parse as JSON jsmn_parser p; jsmn_init(&p); std::vector tokens; - int32_t num_tokens = + const int32_t num_tokens = jsmn_parse(&p, build_info.c_str(), build_info.size(), nullptr, 0); EXPECT_GT(num_tokens, 0) << "Failed to parse BuildInformation as JSON, parse returned " @@ -346,45 +531,186 @@ TEST_F(OEMCryptoClientTest, CheckJsonBuildInformationAPI18) { tokens.resize(num_tokens); jsmn_init(&p); - int32_t jsmn_result = jsmn_parse(&p, build_info.c_str(), build_info.size(), - tokens.data(), num_tokens); + const int32_t jsmn_result = jsmn_parse( + &p, build_info.c_str(), build_info.size(), tokens.data(), num_tokens); EXPECT_GE(jsmn_result, 0) << "Failed to parse BuildInformation as JSON, parse returned " << jsmn_result << "for following build info: " << build_info; - std::map expected; - expected["soc_vendor"] = JSMN_STRING; - expected["soc_model"] = JSMN_STRING; - expected["ta_ver"] = JSMN_STRING; - expected["uses_opk"] = JSMN_PRIMITIVE; - expected["tee_os"] = JSMN_STRING; - expected["tee_os_ver"] = JSMN_STRING; + // Step 3a: Ensure info is a single JSON object. + const jsmntok_t& object_token = tokens[0]; + ASSERT_EQ(object_token.type, JSMN_OBJECT) + << "Build info is not a JSON object: " << build_info; - // for values in token - // build string from start,end - // check for existence in map - // check if value matches expectation - // remove from map - for (int32_t i = 0; i < jsmn_result; i++) { - jsmntok_t token = tokens[i]; - std::string key = build_info.substr(token.start, token.end - token.start); - if (expected.find(key) != expected.end()) { - EXPECT_EQ(expected.find(key)->second, tokens[i + 1].type) - << "Type is incorrect for key " << key; - expected.erase(key); + // Step 3b: Verify schema of defined fields. + + // Required fields must be present in the build information, + // and be of the correct type. + const std::map kRequiredFields = { + // SOC manufacturer name + {"soc_vendor", JSMN_STRING}, + // SOC model name + {"soc_model", JSMN_STRING}, + // TA version in string format eg "1.12.3+tag", "2.0" + {"ta_ver", JSMN_STRING}, + // [bool] Whether TA was built with Widevine's OPK + {"uses_opk", JSMN_PRIMITIVE}, + // Trusted OS intended to run the TA, eg "Trusty", "QSEE", "OP-TEE" + {"tee_os", JSMN_STRING}, + // Version of Trusted OS intended to run the TA + {"tee_os_ver", JSMN_STRING}, + // [bool] Whether this is a debug build of the TA + // Not forcing behavior until implementations fix + // them self + // {"is_debug", JSMN_PRIMITIVE}, + }; + + const std::string kSpecialCaseReeKey = "ree"; + + // Optional fields may be present in the build information; + // if they are, then the must be the correct type. + const std::map kOptionalFields = { + // Name of company or entity that provides OEMCrypto. + {"implementor", JSMN_STRING}, + // Git commit hash of the code repository. + {"git_commit", JSMN_STRING}, + // ISO 8601 formatted timestamp of the time the TA was compiled + {"build_timestamp", JSMN_STRING}, + // Whether this was built with FACTORY_MODE_ONLY defined + {"is_factory_mode", JSMN_PRIMITIVE}, + // ... provide information about liboemcrypto.so + // Special case, see kOptionalReeFields for details. + {kSpecialCaseReeKey, JSMN_OBJECT}, + // Technically required, but several implementations + // do not implement this fields. + {"is_debug", JSMN_PRIMITIVE}, + }; + + // A set of the required fields found when examining the + // build information, use to verify all fields are present. + std::set found_required_fields; + // Stores the tokens of the "ree" field, if set, used to + // validate its content. + std::vector ree_tokens; + bool has_ree_info = false; + + // Start: first object key token + // Condition: key-value pair (2 tokens) + // Iter: next key-value pair (2 tokens) + for (int32_t i = 1; (i + 1) < jsmn_result; i += 2) { + // JSMN objects consist of pairs of key-value pairs (keys are always + // JSMN_STRING). + const jsmntok_t& key_token = tokens[i]; + ASSERT_EQ(key_token.type, JSMN_STRING) + << "Bad object key: i = " << i << ", build_info = " << build_info; + const jsmntok_t& value_token = tokens[i + 1]; + + const std::string key = + build_info.substr(key_token.start, key_token.end - key_token.start); + if (kRequiredFields.find(key) != kRequiredFields.end()) { + ASSERT_EQ(value_token.type, kRequiredFields.at(key)) + << "Unexpected required field type: field = " << key + << ", build_info = " << build_info; + found_required_fields.insert(key); + } else if (kOptionalFields.find(key) != kOptionalFields.end()) { + ASSERT_EQ(value_token.type, kOptionalFields.at(key)) + << "Unexpected optional field type: field = " << key + << ", build_info = " << build_info; + } // Do not validate vendor fields. + + if (key == kSpecialCaseReeKey) { + // Store the tokens of the "ree" field for additional validation. + const int32_t first_ree_field_index = i + 2; + const int32_t ree_token_count = JsmnAncestorCount(tokens, i + 1); + const auto first_ree_field_iter = tokens.begin() + first_ree_field_index; + ree_tokens.assign(first_ree_field_iter, + first_ree_field_iter + ree_token_count); + has_ree_info = true; } + + // Skip potential nested tokens. + i += JsmnAncestorCount(tokens, i + 1); } - // if map is not empty, return false - if (expected.size() > 0) { - std::string missing; - for (const auto& e : expected) { - missing.append(e.first); - missing.append(" "); + // Step 3c: Ensure all required fields were found. + if (found_required_fields.size() != kRequiredFields.size()) { + // Generate a list of all the missing fields. + std::string missing_fields; + for (const auto& required_field : kRequiredFields) { + if (found_required_fields.find(required_field.first) != + found_required_fields.end()) + continue; + if (!missing_fields.empty()) { + missing_fields.append(", "); + } + missing_fields.push_back('"'); + missing_fields.append(required_field.first); + missing_fields.push_back('"'); } - FAIL() << "JSON does not contain all required keys. Missing keys: [" - << missing << "] in string " << build_info; + + FAIL() << "Build info JSON object does not contain all required keys; " + << "missing_fields = [" << missing_fields + << "], build_info = " << build_info; + return; } + + // If no "ree" field tokens, then end here. + if (!has_ree_info) return; + // Step 4a: Verify "ree" object scheme. + ASSERT_FALSE(ree_tokens.empty()) + << "REE field was specified, but contents were empty: build_info = " + << build_info; + + // The optional field "ree", if present, must follow the required + // format. + const std::map kReeRequiredFields = { + // liboemcrypto.so version in string format eg "2.15.0+tag" + {"liboemcrypto_ver", JSMN_STRING}, + // git hash of code that compiled liboemcrypto.so + {"git_commit", JSMN_STRING}, + // ISO 8601 timestamp for when liboemcrypto.so was built + {"build_timestamp", JSMN_STRING}}; + + found_required_fields.clear(); + for (int32_t i = 0; (i + 1) < static_cast(ree_tokens.size()); + i += 2) { + const jsmntok_t& key_token = ree_tokens[i]; + ASSERT_EQ(key_token.type, JSMN_STRING) + << "Bad REE object key: i = " << i << ", build_info = " << build_info; + const jsmntok_t& value_token = ree_tokens[i + 1]; + + const std::string key = + build_info.substr(key_token.start, key_token.end - key_token.start); + if (kReeRequiredFields.find(key) != kReeRequiredFields.end()) { + ASSERT_EQ(value_token.type, kReeRequiredFields.at(key)) + << "Unexpected optional REE field type: ree_field = " << key + << ", build_info = " << build_info; + found_required_fields.insert(key); + } // Do not validate vendor fields. + + // Skip potential nested tokens. + i += JsmnAncestorCount(ree_tokens, i + 1); + } + + // Step 4b: Ensure all required fields of the "ree" object were found. + if (found_required_fields.size() == kReeRequiredFields.size()) return; + // Generate a list of all the missing REE fields. + std::string missing_ree_fields; + for (const auto& required_field : kReeRequiredFields) { + if (found_required_fields.find(required_field.first) != + found_required_fields.end()) + continue; + if (!missing_ree_fields.empty()) { + missing_ree_fields.append(", "); + } + missing_ree_fields.push_back('"'); + missing_ree_fields.append(required_field.first); + missing_ree_fields.push_back('"'); + } + + FAIL() << "REE info JSON object does not contain all required keys; " + << "missing_ree_fields = [" << missing_ree_fields + << "], build_info = " << build_info; } TEST_F(OEMCryptoClientTest, CheckMaxNumberOfSessionsAPI10) { diff --git a/oemcrypto/test/oemcrypto_decrypt_test.cpp b/oemcrypto/test/oemcrypto_decrypt_test.cpp index b9868d0..6dcf901 100644 --- a/oemcrypto/test/oemcrypto_decrypt_test.cpp +++ b/oemcrypto/test/oemcrypto_decrypt_test.cpp @@ -121,38 +121,6 @@ TEST_P(OEMCryptoLicenseTest, RejectCensAPI16) { EXPECT_EQ(OEMCrypto_ERROR_INVALID_CONTEXT, sts); } -// 'cbc1' mode is no longer supported in v16 -TEST_P(OEMCryptoLicenseTest, RejectCbc1API16) { - ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); - ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); - ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); - ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); - - vector key_handle; - OEMCryptoResult sts; - sts = GetKeyHandleIntoVector(session_.session_id(), - session_.license().keys[0].key_id, - session_.license().keys[0].key_id_length, - OEMCrypto_CipherMode_CBCS, key_handle); - ASSERT_EQ(OEMCrypto_SUCCESS, sts); - - vector in_buffer(256); - vector out_buffer(in_buffer.size()); - OEMCrypto_SampleDescription sample_description; - OEMCrypto_SubSampleDescription subsample_description; - - GenerateSimpleSampleDescription(in_buffer, out_buffer, &sample_description, - &subsample_description); - - // Create a zero pattern to indicate this is 'cbc1' - OEMCrypto_CENCEncryptPatternDesc pattern = {0, 0}; - - // Try to decrypt the data - sts = OEMCrypto_DecryptCENC(key_handle.data(), key_handle.size(), - &sample_description, 1, &pattern); - EXPECT_EQ(OEMCrypto_ERROR_INVALID_CONTEXT, sts); -} - TEST_P(OEMCryptoLicenseTest, RejectCbcsWithBlockOffset) { ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); diff --git a/oemcrypto/test/oemcrypto_session_tests_helper.cpp b/oemcrypto/test/oemcrypto_session_tests_helper.cpp index 2a3d525..175bb51 100644 --- a/oemcrypto/test/oemcrypto_session_tests_helper.cpp +++ b/oemcrypto/test/oemcrypto_session_tests_helper.cpp @@ -70,6 +70,9 @@ void SessionUtil::EnsureTestROT() { case DeviceFeatures::TEST_PROVISION_30: // Can use oem certificate to install test rsa key. break; + case DeviceFeatures::PRELOADED_RSA_KEY: + // There is already a key. + break; case wvoec::DeviceFeatures::TEST_PROVISION_40: // OEM certificate is retrieved from the server. break; diff --git a/oemcrypto/test/oemcrypto_usage_table_test.cpp b/oemcrypto/test/oemcrypto_usage_table_test.cpp index fc6bd17..d9ae34f 100644 --- a/oemcrypto/test/oemcrypto_usage_table_test.cpp +++ b/oemcrypto/test/oemcrypto_usage_table_test.cpp @@ -96,6 +96,9 @@ TEST_F(OEMCryptoSessionTests, License_IncrementCounterAPI18) { if (wvoec::global_features.api_version < 18) { GTEST_SKIP() << "Test for versions 18 and up only."; } + if (OEMCrypto_SecurityLevel() == OEMCrypto_Level3) { + GTEST_SKIP() << "L3 does not support license counter."; + } Session s; s.open(); LicenseRoundTrip license_messages(&s); @@ -141,6 +144,9 @@ TEST_F(OEMCryptoSessionTests, MasterGeneration_IncrementCounterAPI18) { GTEST_SKIP() << "Usage table not supported, so master generation number " "does not need to be checked."; } + if (OEMCrypto_SecurityLevel() == OEMCrypto_Level3) { + GTEST_SKIP() << "L3 does not support license counter."; + } Session s1; s1.open(); LicenseRoundTrip license_messages(&s1); diff --git a/oemcrypto/util/include/bcc_validator.h b/oemcrypto/util/include/bcc_validator.h index 94f0786..77c753c 100644 --- a/oemcrypto/util/include/bcc_validator.h +++ b/oemcrypto/util/include/bcc_validator.h @@ -7,12 +7,15 @@ #ifndef WVOEC_UTIL_BCC_VALIDATOR_H_ #define WVOEC_UTIL_BCC_VALIDATOR_H_ +#include + #include #include #include #include "cbor_validator.h" #include "cppbor.h" +#include "wv_class_utils.h" namespace wvoec { namespace util { @@ -45,10 +48,10 @@ struct BccPublicKeyInfo { // Google Dice Profile: go/dice-profile class BccValidator : public CborValidator { public: - explicit BccValidator() {} + BccValidator() = default; virtual ~BccValidator() override = default; - BccValidator(const BccValidator&) = delete; - BccValidator& operator=(const BccValidator&) = delete; + WVCDM_DISALLOW_COPY_AND_MOVE(BccValidator); + // Verifies the Cbor struct of a client generated root of trust. This message // is part of an attestation model conforming to the Google Open Dice Profile. // This message is received from a client device to attest it is a valid @@ -75,7 +78,7 @@ class BccValidator : public CborValidator { const std::vector& signature); // Used to generate formatted message. std::stringstream msg_ss_; -}; +}; // class BccValidator } // namespace util } // namespace wvoec #endif // WVOEC_UTIL_BCC_VALIDATOR_H_ diff --git a/oemcrypto/util/include/cbor_validator.h b/oemcrypto/util/include/cbor_validator.h index 7d4c621..9c67bbf 100644 --- a/oemcrypto/util/include/cbor_validator.h +++ b/oemcrypto/util/include/cbor_validator.h @@ -7,11 +7,15 @@ #ifndef WVOEC_UTIL_CBOR_VALIDATOR_H_ #define WVOEC_UTIL_CBOR_VALIDATOR_H_ +#include + #include +#include #include #include "cppbor.h" #include "cppbor_parse.h" +#include "wv_class_utils.h" namespace wvoec { namespace util { @@ -33,10 +37,9 @@ std::string CborMessageStatusToString(CborMessageStatus status); class CborValidator { public: - explicit CborValidator() {} + CborValidator() = default; + WVCDM_DISALLOW_COPY_AND_MOVE(CborValidator); virtual ~CborValidator() = default; - CborValidator(const CborValidator&) = delete; - CborValidator& operator=(const CborValidator&) = delete; // Decodes |cbor| and sets |message_status_|. virtual CborMessageStatus Parse(const std::vector& cbor); @@ -80,7 +83,7 @@ class CborValidator { // Internal status of parsing and validating. cppbor::ParseResult parse_result_ = {}; std::vector> validate_messages_; -}; +}; // class CborValidator } // namespace util } // namespace wvoec #endif // WVOEC_UTIL_CBOR_VALIDATOR_H_ diff --git a/oemcrypto/util/include/cmac.h b/oemcrypto/util/include/cmac.h index 85ef7c4..e92702d 100644 --- a/oemcrypto/util/include/cmac.h +++ b/oemcrypto/util/include/cmac.h @@ -7,18 +7,22 @@ #ifndef WVOEC_UTIL_CMAC_H_ #define WVOEC_UTIL_CMAC_H_ +#include #include -#include #include #include #include +#include "wv_class_utils.h" + namespace wvoec { namespace util { class Cmac { public: + WVCDM_DISALLOW_COPY_AND_MOVE(Cmac); + // Creates an AES-128-CMAC or an AES-256-CMAC depending on |key_size|. // Returns an empty pointer if the key size is not valid. static std::unique_ptr Create(const uint8_t* key, size_t key_size); @@ -48,14 +52,14 @@ class Cmac { ~Cmac(); private: - Cmac() {} + Cmac() = default; // Assumes |key_size| is a valid AES-128 or AES-256 key. bool Init(const uint8_t* key, size_t key_size); CMAC_CTX* ctx_ = nullptr; bool ready_ = false; -}; +}; // class Cmac } // namespace util } // namespace wvoec #endif // WVOEC_UTIL_CMAC_H_ diff --git a/oemcrypto/util/include/device_info_validator.h b/oemcrypto/util/include/device_info_validator.h index d0d298d..bb3373a 100644 --- a/oemcrypto/util/include/device_info_validator.h +++ b/oemcrypto/util/include/device_info_validator.h @@ -7,12 +7,15 @@ #ifndef WVOEC_UTIL_DEVICE_INFO_VALIDATOR_H_ #define WVOEC_UTIL_DEVICE_INFO_VALIDATOR_H_ +#include + #include #include #include #include "cbor_validator.h" #include "cppbor.h" +#include "wv_class_utils.h" namespace wvoec { namespace util { @@ -21,12 +24,13 @@ namespace util { // https://source.corp.google.com/h/googleplex-android/platform/superproject/main/+/main:hardware/interfaces/security/rkp/aidl/android/hardware/security/keymint/DeviceInfoV3.cddl class DeviceInfoValidator : public CborValidator { public: + DeviceInfoValidator() = delete; + WVCDM_DISALLOW_COPY_AND_MOVE(DeviceInfoValidator); + explicit DeviceInfoValidator(int version_number) : version_number_(version_number) {} - DeviceInfoValidator() = delete; + virtual ~DeviceInfoValidator() override = default; - DeviceInfoValidator(const DeviceInfoValidator&) = delete; - DeviceInfoValidator& operator=(const DeviceInfoValidator&) = delete; // Decodes |device_info| and sets |message_status_|. virtual CborMessageStatus Parse( @@ -48,7 +52,7 @@ class DeviceInfoValidator : public CborValidator { int version_number_; // Saved Cbor-encoded device info. std::vector device_info_bytes_; -}; +}; // class DeviceInfoValidator } // namespace util } // namespace wvoec #endif // WVOEC_UTIL_DEVICE_INFO_VALIDATOR_H_ diff --git a/oemcrypto/util/include/hmac.h b/oemcrypto/util/include/hmac.h index 279cbbf..dd17623 100644 --- a/oemcrypto/util/include/hmac.h +++ b/oemcrypto/util/include/hmac.h @@ -7,8 +7,8 @@ #ifndef WVOEC_UTIL_HMAC_H_ #define WVOEC_UTIL_HMAC_H_ +#include #include -#include #include #include diff --git a/oemcrypto/util/include/oemcrypto_drm_key.h b/oemcrypto/util/include/oemcrypto_drm_key.h index 257429d..a02b94e 100644 --- a/oemcrypto/util/include/oemcrypto_drm_key.h +++ b/oemcrypto/util/include/oemcrypto_drm_key.h @@ -14,6 +14,7 @@ #include "OEMCryptoCENCCommon.h" #include "oemcrypto_ecc_key.h" #include "oemcrypto_rsa_key.h" +#include "wv_class_utils.h" namespace wvoec { namespace util { @@ -21,6 +22,9 @@ namespace util { // OEMCrypto session's RSA/ECC private key. class DrmPrivateKey { public: + WVCDM_DISALLOW_COPY_AND_MOVE(DrmPrivateKey); + ~DrmPrivateKey() = default; + // Create an RSA-based DRM key. static std::unique_ptr Create( std::shared_ptr&& rsa_key); @@ -71,8 +75,6 @@ class DrmPrivateKey { std::vector GenerateRsaSignature( const std::vector& message) const; - ~DrmPrivateKey() {} - private: DrmPrivateKey() {} diff --git a/oemcrypto/util/include/oemcrypto_ecc_key.h b/oemcrypto/util/include/oemcrypto_ecc_key.h index 3542eb2..ac69505 100644 --- a/oemcrypto/util/include/oemcrypto_ecc_key.h +++ b/oemcrypto/util/include/oemcrypto_ecc_key.h @@ -7,8 +7,8 @@ #ifndef WVOEC_UTIL_ECC_KEY_H_ #define WVOEC_UTIL_ECC_KEY_H_ +#include #include -#include #include #include @@ -17,6 +17,7 @@ #include #include "OEMCryptoCENCCommon.h" +#include "wv_class_utils.h" namespace wvoec { namespace util { @@ -35,6 +36,9 @@ class EccPrivateKey; class EccPublicKey { public: + ~EccPublicKey(); + WVCDM_DISALLOW_COPY_AND_MOVE(EccPublicKey); + // Creates a new public key equivalent of the provided private key. static std::unique_ptr New(const EccPrivateKey& private_key); @@ -173,15 +177,8 @@ class EccPublicKey { const std::vector& message, const std::vector& signature) const; - ~EccPublicKey(); - - EccPublicKey(const EccPublicKey&) = delete; - EccPublicKey(EccPublicKey&&) = delete; - const EccPublicKey& operator=(const EccPublicKey&) = delete; - EccPublicKey& operator=(EccPublicKey&&) = delete; - private: - EccPublicKey() {} + EccPublicKey() = default; // Initializes the public key object using the provided |buffer|. // In case of any failure, false is return and the key should be @@ -207,6 +204,9 @@ class EccPublicKey { class EccPrivateKey { public: + ~EccPrivateKey(); + WVCDM_DISALLOW_COPY_AND_MOVE(EccPrivateKey); + // Creates a new, pseudorandom ECC private key belonging to the // curve specified. static std::unique_ptr New(EccCurve curve); @@ -318,7 +318,7 @@ class EccPrivateKey { size_t SignatureSize() const; // Special test method used to generate a raw ECDSA signature. - // A raw ECDSA signature is a concatination of a same-width-big-endian + // A raw ECDSA signature is a concatenation of a same-width-big-endian // encoding of the ECDSA signature point components r and s. std::vector GenerateRawSignature( const std::vector& message) const; @@ -339,15 +339,8 @@ class EccPrivateKey { // by DeriveSymmetricKey(). size_t SessionKeyLength() const; - ~EccPrivateKey(); - - EccPrivateKey(const EccPrivateKey&) = delete; - EccPrivateKey(EccPrivateKey&&) = delete; - const EccPrivateKey& operator=(const EccPrivateKey&) = delete; - EccPrivateKey& operator=(EccPrivateKey&&) = delete; - private: - EccPrivateKey() {} + EccPrivateKey() = default; // Initializes the public key object using the provided |buffer|. // In case of any failure, false is return and the key should be diff --git a/oemcrypto/util/include/oemcrypto_key_deriver.h b/oemcrypto/util/include/oemcrypto_key_deriver.h index fdcf3ec..37a6fe5 100644 --- a/oemcrypto/util/include/oemcrypto_key_deriver.h +++ b/oemcrypto/util/include/oemcrypto_key_deriver.h @@ -7,18 +7,22 @@ #ifndef WVOEC_UTIL_KEY_DERIVER_H_ #define WVOEC_UTIL_KEY_DERIVER_H_ +#include #include -#include #include #include #include "cmac.h" +#include "wv_class_utils.h" namespace wvoec { namespace util { class KeyDeriver { public: + ~KeyDeriver() = default; + WVCDM_DISALLOW_COPY_AND_MOVE(KeyDeriver); + // Create a new key deriver using either the session key or the device // key. // Returns an empty pointer if the key size is not valid. @@ -52,15 +56,13 @@ class KeyDeriver { bool DeriveRenewedDeviceKey(const std::vector& context, std::vector* renewed_device_key); - ~KeyDeriver() {} - private: KeyDeriver() {} bool Init(const uint8_t* key, size_t key_size); std::unique_ptr cmac_; -}; +}; // class KeyDeriver } // namespace util } // namespace wvoec #endif // WVOEC_UTIL_KEY_DERIVER_H_ diff --git a/oemcrypto/util/include/oemcrypto_oem_cert.h b/oemcrypto/util/include/oemcrypto_oem_cert.h index f633280..cd17791 100644 --- a/oemcrypto/util/include/oemcrypto_oem_cert.h +++ b/oemcrypto/util/include/oemcrypto_oem_cert.h @@ -7,10 +7,13 @@ #ifndef WVOEC_UTIL_OEM_CERT_H_ #define WVOEC_UTIL_OEM_CERT_H_ +#include + #include #include #include "OEMCryptoCENCCommon.h" +#include "wv_class_utils.h" namespace wvoec { namespace util { @@ -39,6 +42,9 @@ class OemCertificate { kRsa = 1 }; + ~OemCertificate(); + WVCDM_DISALLOW_COPY_AND_MOVE(OemCertificate); + // Creates a new OEM Certificate and performs basic validation // to ensure that the private key and public cert are well-formed. // The |public_cert| provided is parsed as an X.509 Certificate @@ -84,13 +90,6 @@ class OemCertificate { // (ie, same modulos and public exponent). OEMCryptoResult IsCertificateValid() const; - ~OemCertificate(); - - OemCertificate(const OemCertificate&) = delete; - OemCertificate(OemCertificate&&) = delete; - const OemCertificate& operator=(const OemCertificate&) = delete; - OemCertificate& operator=(OemCertificate&&) = delete; - private: OemCertificate(); diff --git a/oemcrypto/util/include/oemcrypto_rsa_key.h b/oemcrypto/util/include/oemcrypto_rsa_key.h index 35b93ab..0eb7e77 100644 --- a/oemcrypto/util/include/oemcrypto_rsa_key.h +++ b/oemcrypto/util/include/oemcrypto_rsa_key.h @@ -7,8 +7,8 @@ #ifndef WVOEC_UTIL_RSA_KEY_H_ #define WVOEC_UTIL_RSA_KEY_H_ +#include #include -#include #include #include @@ -17,6 +17,7 @@ #include #include "OEMCryptoCENC.h" +#include "wv_class_utils.h" namespace wvoec { namespace util { @@ -61,6 +62,9 @@ class RsaPrivateKey; class RsaPublicKey { public: + ~RsaPublicKey(); + WVCDM_DISALLOW_COPY_AND_MOVE(RsaPublicKey); + // Creates a new public key equivalent of the provided private key. static std::unique_ptr New(const RsaPrivateKey& private_key); @@ -176,15 +180,8 @@ class RsaPublicKey { std::vector EncryptEncryptionKey( const std::string& encryption_key) const; - ~RsaPublicKey(); - - RsaPublicKey(const RsaPublicKey&) = delete; - RsaPublicKey(RsaPublicKey&&) = delete; - const RsaPublicKey& operator=(const RsaPublicKey&) = delete; - RsaPublicKey& operator=(RsaPublicKey&&) = delete; - private: - RsaPublicKey() {} + RsaPublicKey() = default; // Initializes the public key object using the provided |buffer|. // In case of any failure, false is return and the key should be @@ -222,6 +219,9 @@ class RsaPublicKey { class RsaPrivateKey { public: + ~RsaPrivateKey(); + WVCDM_DISALLOW_COPY_AND_MOVE(RsaPrivateKey); + // Creates a new, pseudorandom RSA private key. static std::unique_ptr New(RsaFieldSize field_size); @@ -342,15 +342,8 @@ class RsaPrivateKey { std::vector DecryptEncryptionKey( const std::string& enc_encryption_key) const; - ~RsaPrivateKey(); - - RsaPrivateKey(const RsaPrivateKey&) = delete; - RsaPrivateKey(RsaPrivateKey&&) = delete; - const RsaPrivateKey& operator=(const RsaPrivateKey&) = delete; - RsaPrivateKey& operator=(RsaPrivateKey&&) = delete; - private: - RsaPrivateKey() {} + RsaPrivateKey() = default; // Initializes the public key object using the provided |buffer|. // In case of any failure, false is return and the key should be diff --git a/oemcrypto/util/include/scoped_object.h b/oemcrypto/util/include/scoped_object.h index d9cdc6f..8463124 100644 --- a/oemcrypto/util/include/scoped_object.h +++ b/oemcrypto/util/include/scoped_object.h @@ -7,6 +7,8 @@ #ifndef WVOEC_UTIL_SCOPED_OBJECT_H_ #define WVOEC_UTIL_SCOPED_OBJECT_H_ +#include "wv_class_utils.h" + namespace wvoec { namespace util { // A generic wrapper around pointer. This allows for automatic @@ -25,8 +27,7 @@ class ScopedObject { } // Copy construction and assignment are not allowed. - ScopedObject(const ScopedObject& other) = delete; - ScopedObject& operator=(const ScopedObject& other) = delete; + WVCDM_DISALLOW_COPY(ScopedObject); // Move construction and assignment are allowed. ScopedObject(ScopedObject&& other) : ptr_(other.ptr_) { @@ -65,7 +66,7 @@ class ScopedObject { private: Type* ptr_ = nullptr; -}; +}; // class ScopedObject } // namespace util } // namespace wvoec #endif // WVOEC_UTIL_SCOPED_OBJECT_H_ diff --git a/oemcrypto/util/include/signed_csr_payload_validator.h b/oemcrypto/util/include/signed_csr_payload_validator.h index a2d8127..911943b 100644 --- a/oemcrypto/util/include/signed_csr_payload_validator.h +++ b/oemcrypto/util/include/signed_csr_payload_validator.h @@ -12,6 +12,7 @@ #include "cbor_validator.h" #include "cppbor.h" +#include "wv_class_utils.h" namespace wvoec { namespace util { @@ -23,9 +24,7 @@ class SignedCsrPayloadValidator : public CborValidator { public: explicit SignedCsrPayloadValidator() {} virtual ~SignedCsrPayloadValidator() override = default; - SignedCsrPayloadValidator(const SignedCsrPayloadValidator&) = delete; - SignedCsrPayloadValidator& operator=(const SignedCsrPayloadValidator&) = - delete; + WVCDM_DISALLOW_COPY_AND_MOVE(SignedCsrPayloadValidator); // Verifies the Cbor struct of a client generated SignedData. virtual CborMessageStatus Validate() override; @@ -38,7 +37,7 @@ class SignedCsrPayloadValidator : public CborValidator { CborMessageStatus ValidateDataToBeSigned(const cppbor::Bstr* data); // Used to generate formatted message. std::stringstream msg_ss_; -}; +}; // class SignedCsrPayloadValidator } // namespace util } // namespace wvoec #endif // WVOEC_UTIL_SIGNED_CSR_PAYLOAD_VALIDATOR_H_ diff --git a/oemcrypto/util/include/wvcrc32.h b/oemcrypto/util/include/wvcrc32.h index 3d913ef..67f5ed0 100644 --- a/oemcrypto/util/include/wvcrc32.h +++ b/oemcrypto/util/include/wvcrc32.h @@ -7,7 +7,7 @@ #ifndef WVOEC_UTIL_WVCRC32_H_ #define WVOEC_UTIL_WVCRC32_H_ -#include +#include namespace wvoec { namespace util { diff --git a/oemcrypto/util/src/bcc_validator.cpp b/oemcrypto/util/src/bcc_validator.cpp index e74354f..a09f1a8 100644 --- a/oemcrypto/util/src/bcc_validator.cpp +++ b/oemcrypto/util/src/bcc_validator.cpp @@ -8,7 +8,6 @@ #include -#include #include #include #include @@ -358,7 +357,7 @@ CborMessageStatus BccValidator::ProcessSubjectPublicKeyInfo( "MAP_KEY_DEVICE_KEY_TYPE: " + std::to_string(value)); } - fmt_msgs.push_back(kv); + fmt_msgs.push_back(std::move(kv)); } break; case MAP_KEY_DEVICE_KEY_ALGORITHM: { if (entry.second->type() != cppbor::NINT) { @@ -386,7 +385,7 @@ CborMessageStatus BccValidator::ProcessSubjectPublicKeyInfo( "MAP_KEY_DEVICE_KEY_ALGORITHM: " + std::to_string(value)); } - fmt_msgs.push_back(kv); + fmt_msgs.push_back(std::move(kv)); } break; case MAP_KEY_DEVICE_KEY_OPS: // The OPS is an array. Ignored for now. @@ -417,7 +416,7 @@ CborMessageStatus BccValidator::ProcessSubjectPublicKeyInfo( "MAP_KEY_DEVICE_KEY_CURVE: " + std::to_string(value)); } - fmt_msgs.push_back(kv); + fmt_msgs.push_back(std::move(kv)); } break; case MAP_KEY_DEVICE_KEY_BYTES_0: case MAP_KEY_DEVICE_KEY_BYTES_1: diff --git a/oemcrypto/util/src/cbor_validator.cpp b/oemcrypto/util/src/cbor_validator.cpp index dce72a8..608f287 100644 --- a/oemcrypto/util/src/cbor_validator.cpp +++ b/oemcrypto/util/src/cbor_validator.cpp @@ -8,7 +8,6 @@ #include -#include #include #include #include diff --git a/oemcrypto/util/src/oemcrypto_ecc_key.cpp b/oemcrypto/util/src/oemcrypto_ecc_key.cpp index a311614..7f98687 100644 --- a/oemcrypto/util/src/oemcrypto_ecc_key.cpp +++ b/oemcrypto/util/src/oemcrypto_ecc_key.cpp @@ -785,7 +785,7 @@ OEMCryptoResult EccPublicKey::VerifyRawSignature( if (!s || !r) { LOGE("Failed to parse R/S values: r = %s, s = %s, r_s_size = %d", BoolToStatus(r.ok()), BoolToStatus(s.ok()), r_s_size); - // Technically, any byte string should be convertable to an + // Technically, any byte string should be convertible to an // integer. This must be an OpenSSL/BoringSSL error. return OEMCrypto_ERROR_UNKNOWN_FAILURE; } diff --git a/oemcrypto/util/src/oemcrypto_oem_cert.cpp b/oemcrypto/util/src/oemcrypto_oem_cert.cpp index 619ad68..e4ec3f9 100644 --- a/oemcrypto/util/src/oemcrypto_oem_cert.cpp +++ b/oemcrypto/util/src/oemcrypto_oem_cert.cpp @@ -15,6 +15,7 @@ #include "log.h" #include "oemcrypto_rsa_key.h" #include "scoped_object.h" +#include "wv_class_utils.h" namespace wvoec { namespace util { @@ -53,6 +54,9 @@ OEMCryptoResult VerifyRsaKey(const RSA* public_key, // Certificate. class OemPublicCertificate { public: + ~OemPublicCertificate() = default; + WVCDM_DISALLOW_COPY_AND_MOVE(OemPublicCertificate); + // Loads a PKCS #7 signedData message with certificate chain. // Minimum validation is performed. Only checks that the // device's public key is of a known type (RSA). @@ -81,15 +85,8 @@ class OemPublicCertificate { return EVP_PKEY_get0_RSA(device_public_key_.get()); } - ~OemPublicCertificate() = default; - - OemPublicCertificate(const OemPublicCertificate&) = delete; - OemPublicCertificate(OemPublicCertificate&&) = delete; - const OemPublicCertificate& operator=(const OemPublicCertificate&) = delete; - OemPublicCertificate& operator=(OemPublicCertificate&&) = delete; - private: - OemPublicCertificate() {} + OemPublicCertificate() = default; bool InitFromBuffer(const uint8_t* public_cert, size_t public_cert_size) { // Step 1: Parse the PKCS7 certificate chain as signedData. diff --git a/oemcrypto/util/test/oem_cert_test.h b/oemcrypto/util/test/oem_cert_test.h index a19b952..35661b8 100644 --- a/oemcrypto/util/test/oem_cert_test.h +++ b/oemcrypto/util/test/oem_cert_test.h @@ -6,8 +6,8 @@ #ifndef OEM_CERT_TEST_H_ #define OEM_CERT_TEST_H_ +#include #include -#include namespace wvoec { namespace util { diff --git a/oemcrypto/util/test/oemcrypto_ref_test_utils.h b/oemcrypto/util/test/oemcrypto_ref_test_utils.h index 3f62567..2edd664 100644 --- a/oemcrypto/util/test/oemcrypto_ref_test_utils.h +++ b/oemcrypto/util/test/oemcrypto_ref_test_utils.h @@ -7,8 +7,8 @@ #ifndef WVOEC_UTIL_REF_TEST_UTILS_H_ #define WVOEC_UTIL_REF_TEST_UTILS_H_ +#include #include -#include #include diff --git a/util/include/string_conversions.h b/util/include/string_conversions.h index 461ef41..7c480c7 100644 --- a/util/include/string_conversions.h +++ b/util/include/string_conversions.h @@ -56,6 +56,18 @@ inline int64_t ntohll64(int64_t x) { return htonll64(x); } // Encode unsigned integer into a big endian formatted string. std::string EncodeUint32(uint32_t u); +// Converts a byte string representing an ID into a log-friendly form. +// +// Conversion rules: +// 1) empty - returns +// 2) printable ASCII only - original content, surrounded by double +// quotes; double quotes and backslashes +// are escaped, like C/C++ string literals. +// 3) otherwise - Unquoted, hexadecimal encoded string. +// +// Intended to be used on ID strings which are provided/generated +// from sources outside of the CDM. +std::string SafeByteIdToString(const std::string& id); +std::string SafeByteIdToString(const std::vector& id); } // namespace wvutil - #endif // WVCDM_UTIL_STRING_CONVERSIONS_H_ diff --git a/util/include/wv_class_utils.h b/util/include/wv_class_utils.h new file mode 100644 index 0000000..aae285f --- /dev/null +++ b/util/include/wv_class_utils.h @@ -0,0 +1,112 @@ +// Copyright 2024 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. +#ifndef WVCDM_UTIL_CLASS_UTILS_HPP_ +#define WVCDM_UTIL_CLASS_UTILS_HPP_ + +// ==== Disallow Operators ==== + +#define WVCDM_DISALLOW_COPY(ClassName) \ + ClassName(const ClassName&) = delete; \ + ClassName& operator=(const ClassName&) = delete + +#define WVCDM_DISALLOW_MOVE(ClassName) \ + ClassName(ClassName&&) = delete; \ + ClassName& operator=(ClassName&&) = delete + +#define WVCDM_DISALLOW_COPY_AND_MOVE(ClassName) \ + WVCDM_DISALLOW_COPY(ClassName); \ + WVCDM_DISALLOW_MOVE(ClassName) + +// ==== Default Operators ==== + +#define WVCDM_DEFAULT_COPY(ClassName) \ + ClassName(const ClassName&) = default; \ + ClassName& operator=(const ClassName&) = default + +#define WVCDM_DEFAULT_MOVE(ClassName) \ + ClassName(ClassName&&) = default; \ + ClassName& operator=(ClassName&&) = default + +#define WVCDM_DEFAULT_COPY_AND_MOVE(ClassName) \ + WVCDM_DEFAULT_COPY(ClassName); \ + WVCDM_DEFAULT_MOVE(ClassName) + +// ==== Constexpr Default Operators ==== + +#define WVCDM_CONSTEXPR_DEFAULT_COPY(ClassName) \ + constexpr ClassName(const ClassName&) = default; \ + constexpr ClassName& operator=(const ClassName&) = default + +#define WVCDM_CONSTEXPR_DEFAULT_MOVE(ClassName) \ + constexpr ClassName(ClassName&&) = default; \ + constexpr ClassName& operator=(ClassName&&) = default + +#define WVCDM_CONSTEXPR_DEFAULT_COPY_AND_MOVE(ClassName) \ + WVCDM_CONSTEXPR_DEFAULT_COPY(ClassName); \ + WVCDM_CONSTEXPR_DEFAULT_MOVE(ClassName) + +// ==== Comparison Operators ==== + +// If a class defines the comparison method "bool IsEqualTo() const", +// this macro will define the == and != operators for that class. +#define WVCDM_DEFINE_EQ_OPERATORS(ClassName) \ + bool operator==(const ClassName& other) const { return IsEqualTo(other); } \ + bool operator!=(const ClassName& other) const { return !IsEqualTo(other); } + +// If a class defines the comparison method "int CompareTo() const", +// this macro will define the < <= != == >= > operators for that class. +#define WVCDM_DEFINE_CMP_OPERATORS(ClassName) \ + bool operator<(const ClassName& other) const { \ + return CompareTo(other) < 0; \ + } \ + bool operator<=(const ClassName& other) const { \ + return CompareTo(other) <= 0; \ + } \ + bool operator>(const ClassName& other) const { \ + return CompareTo(other) > 0; \ + } \ + bool operator>=(const ClassName& other) const { \ + return CompareTo(other) >= 0; \ + } + +// If a class defines the comparison methods "bool IsEqualTo() const" +// and "int CompareTo() const", this macro will define the < <= != ==, +// >= > operators. +#define WVCDM_DEFINE_EQ_AND_CMP_OPERATORS(ClassName) \ + WVCDM_DEFINE_EQ_OPERATORS(ClassName) \ + WVCDM_DEFINE_CMP_OPERATORS(ClassName) + +// ==== Constexpr Comparison Operators ==== + +// Same as WVCDM_DEFINE_EQ_OPERATORS, but requires class to define +// "constexpr bool IsEqualTo() const" method. +#define WVCDM_DEFINE_CONSTEXPR_EQ_OPERATORS(ClassName) \ + constexpr bool operator==(const ClassName& other) const { \ + return IsEqualTo(other); \ + } \ + constexpr bool operator!=(const ClassName& other) const { \ + return !IsEqualTo(other); \ + } + +// Same as WVCDM_DEFINE_CMP_OPERATORS, but requires class to define +// "constexpr int CompareTo() const" method. +#define WVCDM_DEFINE_CONSTEXPR_CMP_OPERATORS(ClassName) \ + constexpr bool operator<(const ClassName& other) const { \ + return CompareTo(other) < 0; \ + } \ + constexpr bool operator<=(const ClassName& other) const { \ + return CompareTo(other) <= 0; \ + } \ + constexpr bool operator>(const ClassName& other) const { \ + return CompareTo(other) > 0; \ + } \ + constexpr bool operator>=(const ClassName& other) const { \ + return CompareTo(other) >= 0; \ + } + +#define WVCDM_DEFINE_CONSTEXPR_EQ_AND_CMP_OPERATORS(ClassName) \ + WVCDM_DEFINE_CONSTEXPR_EQ_OPERATORS(ClassName) \ + WVCDM_DEFINE_CONSTEXPR_CMP_OPERATORS(ClassName) + +#endif // WVCDM_UTIL_CLASS_UTILS_HPP_ diff --git a/util/src/string_conversions.cpp b/util/src/string_conversions.cpp index 0470d3a..bb3427f 100644 --- a/util/src/string_conversions.cpp +++ b/util/src/string_conversions.cpp @@ -5,10 +5,11 @@ #include "string_conversions.h" #include -#include +#include #include #include +#include #include #include "log.h" @@ -155,6 +156,61 @@ std::vector Base64DecodeInternal(const char* encoded, size_t length, result.resize(out_index); return result; } + +// To prevent ID character type conversion errors, these +// NormalizeIdChar() functions will safely return the +// a char type without changing the byte values. +constexpr char NormalizeIdChar(char ch) { return ch; } +constexpr char NormalizeIdChar(uint8_t ch) { return static_cast(ch); } + +// An escapable ID char is a character which is considered +// "human readable" and can be escaped by inserting a +// backslash character before it. +template +constexpr bool IsEscapableIdChar(CharType ch) { + const char nch = NormalizeIdChar(ch); + return nch == '"' || nch == '\\'; +} + +// A "human readable" ID character is any ASCII character +// which produces a glyph or a space character. +template +bool IsHumanReadableIdChar(CharType ch) { + const char nch = NormalizeIdChar(ch); + return isgraph(nch) || nch == ' '; +} + +template +std::string SafeByteIdToStringInternal(const Container& id) { + using CharType = typename Container::value_type; + if (id.empty()) { + return ""; + } + + // Check if already human readable. + const bool human_readable = + std::all_of(id.begin(), id.end(), IsHumanReadableIdChar); + if (!human_readable) { + // For non-human readable IDs, just return the hex encoded version. + return b2a_hex(id); + } + + // For human readable IDs, add quotes, and escape any double quotes + // and backslashes. + const size_t escape_count = + std::count_if(id.begin(), id.end(), IsEscapableIdChar); + std::string result; + result.reserve(id.size() + escape_count + 2); + result.push_back('"'); + for (const auto& ch : id) { + if (IsEscapableIdChar(ch)) { + result.push_back('\\'); + } + result.push_back(ch); + } + result.push_back('"'); + return result; +} } // namespace // converts an ascii hex string(2 bytes per digit) into a decimal byte string @@ -340,4 +396,11 @@ std::string EncodeUint32(uint32_t u) { return s; } +std::string SafeByteIdToString(const std::string& id) { + return SafeByteIdToStringInternal(id); +} + +std::string SafeByteIdToString(const std::vector& id) { + return SafeByteIdToStringInternal(id); +} } // namespace wvutil diff --git a/util/test/base64_test.cpp b/util/test/base64_test.cpp index d96b076..be75196 100644 --- a/util/test/base64_test.cpp +++ b/util/test/base64_test.cpp @@ -189,4 +189,115 @@ TEST_F(HtoNLL64Test, NegativeNumber) { int64_t host_byte_order = htonll64(*network_byte_order); EXPECT_EQ(-0x01FdFcFbFaF9F8F8, host_byte_order); } + +TEST(SafeByteIdToString, EmptyId) { + const std::string kEmptyString; + EXPECT_EQ(SafeByteIdToString(kEmptyString), ""); + + const std::vector kEmptyVector; + EXPECT_EQ(SafeByteIdToString(kEmptyVector), ""); +} + +TEST(SafeByteIdToString, AllAlphaNumeric_NoEscape) { + const std::string sid("Hello, World!"); + const std::vector vid(sid.begin(), sid.end()); + EXPECT_EQ(SafeByteIdToString(sid), "\"Hello, World!\""); + EXPECT_EQ(SafeByteIdToString(vid), "\"Hello, World!\""); +} + +TEST(SafeByteIdToString, AllAlphaNumeric_NoEscape_Exhaustive) { + for (char ch = 'a'; ch <= 'z'; ch++) { + const std::string sid(1, ch); + const std::vector vid(1, ch); + std::string expected(1, '"'); + expected.push_back(ch); + expected.push_back('"'); + EXPECT_EQ(SafeByteIdToString(sid), expected); + EXPECT_EQ(SafeByteIdToString(vid), expected); + } + + for (char ch = 'A'; ch <= 'Z'; ch++) { + const std::string sid(1, ch); + const std::vector vid(1, ch); + std::string expected(1, '"'); + expected.push_back(ch); + expected.push_back('"'); + EXPECT_EQ(SafeByteIdToString(sid), expected); + EXPECT_EQ(SafeByteIdToString(vid), expected); + } + + for (char ch = '0'; ch <= '9'; ch++) { + const std::string sid(1, ch); + const std::vector vid(1, ch); + std::string expected(1, '"'); + expected.push_back(ch); + expected.push_back('"'); + EXPECT_EQ(SafeByteIdToString(sid), expected); + EXPECT_EQ(SafeByteIdToString(vid), expected); + } +} + +TEST(SafeByteIdToString, AllSymbols_NoEscape) { + for (char ch = ' '; ch <= '/'; ch++) { + if (ch == '"') continue; + const std::string sid(1, ch); + const std::vector vid(1, ch); + std::string expected(1, '"'); + expected.push_back(ch); + expected.push_back('"'); + EXPECT_EQ(SafeByteIdToString(sid), expected); + EXPECT_EQ(SafeByteIdToString(vid), expected); + } + + for (char ch = ':'; ch <= '@'; ch++) { + const std::string sid(1, ch); + const std::vector vid(1, ch); + std::string expected(1, '"'); + expected.push_back(ch); + expected.push_back('"'); + EXPECT_EQ(SafeByteIdToString(sid), expected); + EXPECT_EQ(SafeByteIdToString(vid), expected); + } + + for (char ch = '['; ch <= '`'; ch++) { + if (ch == '\\') continue; + const std::string sid(1, ch); + const std::vector vid(1, ch); + std::string expected(1, '"'); + expected.push_back(ch); + expected.push_back('"'); + EXPECT_EQ(SafeByteIdToString(sid), expected); + EXPECT_EQ(SafeByteIdToString(vid), expected); + } + + for (char ch = '{'; ch <= '~'; ch++) { + const std::string sid(1, ch); + const std::vector vid(1, ch); + std::string expected(1, '"'); + expected.push_back(ch); + expected.push_back('"'); + EXPECT_EQ(SafeByteIdToString(sid), expected); + EXPECT_EQ(SafeByteIdToString(vid), expected); + } +} + +TEST(SafeByteIdToString, Escapable_NoEscape) { + const std::string quote_sid(1, '"'); + const std::vector quote_vid(1, '"'); + EXPECT_EQ(SafeByteIdToString(quote_sid), "\"\\\"\""); + EXPECT_EQ(SafeByteIdToString(quote_vid), "\"\\\"\""); + + const std::string backslash_sid(1, '\\'); + const std::vector backslash_vid(1, '\\'); + EXPECT_EQ(SafeByteIdToString(backslash_sid), "\"\\\\\""); + EXPECT_EQ(SafeByteIdToString(backslash_vid), "\"\\\\\""); +} + +TEST(SafeByteIdToString, AllNonPrintable) { + const std::vector vid = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + const std::string sid(vid.begin(), vid.end()); + + EXPECT_EQ(SafeByteIdToString(vid), "00010203040506070809"); + EXPECT_EQ(SafeByteIdToString(sid), "00010203040506070809"); +} } // namespace wvutil