From 22759672a84092e24f0bb92361d7f8348628a3e2 Mon Sep 17 00:00:00 2001 From: Vicky Min Date: Wed, 27 Nov 2024 00:07:23 +0000 Subject: [PATCH] Source release 19.4.0 --- CHANGELOG.md | 43 + LICENSE | 8 + README.md | 57 +- Widevine_Open_Source_License_Terms.pdf | Bin 0 -> 108844 bytes cdm/cdm_reboot_tests.gyp | 1 + cdm/cdm_unittests.gyp | 2 +- cdm/include/cdm_version.h | 2 +- cdm/test/cdm_test.cpp | 43 +- cdm/test/test_host.cpp | 4 +- core/include/client_identification.h | 3 + core/include/crypto_session.h | 20 +- core/include/license.h | 332 ++++- core/include/oemcrypto_adapter.h | 3 + core/include/wv_cdm_constants.h | 4 + core/include/wv_cdm_types.h | 2 + core/src/cdm_engine.cpp | 40 + core/src/cdm_session.cpp | 9 +- core/src/client_identification.cpp | 40 + core/src/crypto_session.cpp | 95 +- core/src/initialization_data.cpp | 14 +- core/src/license.cpp | 194 +-- core/src/license_protocol.proto | 14 + core/src/oemcrypto_adapter_static.cpp | 7 + core/src/wv_cdm_types.cpp | 16 + .../certificate_provisioning_unittest.cpp | 3 +- core/test/core_integration_test.cpp | 7 +- core/test/duration_use_case_test.cpp | 50 +- core/test/initialization_data_unittest.cpp | 23 + core/test/license_holder.cpp | 24 +- core/test/license_unittest.cpp | 14 +- core/test/policy_integration_test.cpp | 27 +- oemcrypto/include/OEMCryptoCENC.h | 89 +- 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/test/GEN_api_lock_file.c | 3 + oemcrypto/test/oec_decrypt_fallback_chain.cpp | 16 +- oemcrypto/test/oec_session_util.h | 1 + oemcrypto/test/oemcrypto_basic_test.cpp | 139 +- oemcrypto/test/oemcrypto_basic_test.h | 1 + oemcrypto/test/oemcrypto_cast_test.cpp | 17 +- oemcrypto/test/oemcrypto_cast_test.h | 31 +- oemcrypto/test/oemcrypto_decrypt_test.cpp | 4 + oemcrypto/test/oemcrypto_license_test.cpp | 3 + .../test/oemcrypto_provisioning_test.cpp | 52 +- oemcrypto/test/oemcrypto_security_test.cpp | 1292 ++++++++++++++--- oemcrypto/test/oemcrypto_test.cpp | 1152 +-------------- oemcrypto/test/oemcrypto_test_android.cpp | 6 +- oemcrypto/test/oemcrypto_unittests.gypi | 1 + oemcrypto/test/oemcrypto_usage_table_test.cpp | 4 + oemcrypto/util/include/bcc_validator.h | 138 +- oemcrypto/util/include/cbor_validator.h | 17 +- .../util/include/device_info_validator.h | 51 +- .../util/include/prov4_validation_helper.h | 96 ++ .../include/signed_csr_payload_validator.h | 82 +- oemcrypto/util/oec_ref_util.gypi | 1 + oemcrypto/util/src/bcc_validator.cpp | 1082 ++++++++++---- oemcrypto/util/src/cbor_validator.cpp | 39 +- oemcrypto/util/src/device_info_validator.cpp | 601 ++++++-- .../util/src/prov4_validation_helper.cpp | 25 + .../util/src/signed_csr_payload_validator.cpp | 647 ++++++--- .../util/test/bcc_validator_unittest.cpp | 293 +++- .../test/device_info_validator_unittest.cpp | 165 ++- .../signed_csr_payload_validator_unittest.cpp | 54 +- platforms/example/no_oemcrypto.cpp | 5 + util/include/file_store.h | 99 +- util/test/file_store_unittest.cpp | 592 +++++--- util/test/test_clock.cpp | 4 +- util/test/test_sleep.cpp | 13 +- util/test/test_sleep.h | 11 +- 72 files changed, 5321 insertions(+), 2622 deletions(-) create mode 100644 LICENSE create mode 100644 Widevine_Open_Source_License_Terms.pdf create mode 100644 oemcrypto/util/include/prov4_validation_helper.h create mode 100644 oemcrypto/util/src/prov4_validation_helper.cpp diff --git a/CHANGELOG.md b/CHANGELOG.md index 190e15ba..e60013f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,49 @@ [TOC] +## 19.4.0 (2024-11-27) + +This is a minor release with bug fixes and test improvements. + +### Features + + - Add support for new OEMCrypto_GetBCCSignatureType() API. This API is optional + and only used on devices that support Provisioning 4 with a Phase 3 DICE + chain. + - Add credential types to client identification protobuf + - Return BCC signature type into client identification protobuf + - BCC extraction tool updates and refactoring + - Rewrite BCC, DeviceInfo and CSR Payload validators to cover info parsing, + validating and updating unit tests + - Update error codes returned by cbor/provisioning 4.0 validators to be less + severe when possible + - Add a few required fields for test-generated BCC + - Include licensing files with this release and future CE CDM releases + +### Tests + + - Update license release tests to accomodate differences in behavior for CE + CDM and Android + - Update some CAST tests to enforce format of the message signed by + OEMCrypto_GenerateRSASignature() + - Add tool to extract BCC and build info for BCC uploading test + - Skip usage table tests on devices that don't support usage tables + - Fix key type used in InstallOemPrivateKeyCanBeUsed test + - Re-enable OEMCrypto security tests + - Allow multiple callbacks in TestSleep class to prevent multiple classes + trying to register a callback + - Improve error logging for tests + - Add log statement on failure when device with a TEST_ONLY system ID cannot + play production content + +### Bug Fixes + + - Fix HLS parsing of bad content IDs + - Revert change to limit output buffer size during decrypt fallback due to + failures seen in 19.3 because the output buffer was not big enough + - Update blank OEMCrypto devsite test pages + - Small fixes to reduce compiler warnings + ## 19.3.0 (2024-09-04) This is a minor release with bug fixes and test improvements, as well as diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..e711887a --- /dev/null +++ b/LICENSE @@ -0,0 +1,8 @@ +Google LLC and its affiliates ("Google") own all legal right, title and +interest in and to the content decryption module software ("Software") and +related documentation, including any intellectual property rights in the +Software. You may not use, modify, sell, or otherwise distribute the Software +without a separate license agreement with Google. The Software is not open +source software. + +If you are interested in licensing the Software, please contact www.widevine.com diff --git a/README.md b/README.md index 045cd1cf..d657436d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# Widevine CE CDM 19.3.0 +# Widevine CE CDM 19.4.0 -Released 2024-09-04 +Released 2024-11-27 ## Getting Started @@ -10,37 +10,48 @@ following to learn more about the contents of this project and how to use them: The [Widevine Developer Site][wv-devsite] documents the CDM API and describes how to integrate the CDM into a system. -## New in v19.3.0 +## New in v19.4.0 -This is a minor release with bug fixes and test improvements, as well as -internal code-quality cleanups that do not affect the CDM's behavior. However, -because of improvements to the BCC Factory Upload Tool, we recommend that all -partners who use this tool upgrade to version 19.3.0. +This is a minor release with bug fixes and test improvements. ### Features - - Added workaround for OEMCrypto implementations with slightly corrupted build - information - - The BCC Factory Upload Tool supports new command-line options for dry runs, - batch checks, version-checking, and verbose output. + - Add support for new OEMCrypto_GetBCCSignatureType() API. This API is optional + and only used on devices that support Provisioning 4 with a Phase 3 DICE + chain. + - Add credential types to client identification protobuf + - Return BCC signature type into client identification protobuf + - BCC extraction tool updates and refactoring + - Rewrite BCC, DeviceInfo and CSR Payload validators to cover info parsing, + validating and updating unit tests + - Update error codes returned by cbor/provisioning 4.0 validators to be less + severe when possible + - Add a few required fields for test-generated BCC + - Include licensing files with this release and future CE CDM releases ### 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 + - Update license release tests to accomodate differences in behavior for CE + CDM and Android + - Update some CAST tests to enforce format of the message signed by + OEMCrypto_GenerateRSASignature() + - Add tool to extract BCC and build info for BCC uploading test + - Skip usage table tests on devices that don't support usage tables + - Fix key type used in InstallOemPrivateKeyCanBeUsed test + - Re-enable OEMCrypto security tests + - Allow multiple callbacks in TestSleep class to prevent multiple classes + trying to register a callback + - Improve error logging for tests + - Add log statement on failure when device with a TEST_ONLY system ID cannot + play production content ### Bug Fixes - - Fixed decrypt failures on devices with low TEE memory caused by sending an - output buffer to decrypt that was much larger than necessary - - Several BCC Factory Upload Tool fixes: - - Added the missing `FileSystem::Exists()` function - - Fixed a bug causing the output to be unnecessarily padded - - Fixed an issue where fields containing JSON were not properly escaped + - Fix HLS parsing of bad content IDs + - Revert change to limit output buffer size during decrypt fallback due to + failures seen in 19.3 because the output buffer was not big enough + - Update blank OEMCrypto devsite test pages + - Small fixes to reduce compiler warnings [CHANGELOG.md](./CHANGELOG.md) lists the major changes for each past release. diff --git a/Widevine_Open_Source_License_Terms.pdf b/Widevine_Open_Source_License_Terms.pdf new file mode 100644 index 0000000000000000000000000000000000000000..eee3844c77695ab7fda0c74522fb80a1280b2726 GIT binary patch literal 108844 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 cdm_; }; @@ -618,22 +634,45 @@ TEST_F(CdmTest, PrintClientInformation) { const time_t c_now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); cout << "CE CDM Unit Tests for version " CDM_VERSION << endl; + RecordWvProperty("test_version", CDM_VERSION); cout << "Tests run at " << std::put_time(localtime(&c_now), "%F %T %Z") << endl; // Collect CDM info Cdm::ClientInfo client_info; ASSERT_EQ(Cdm::getClientInfo(&client_info), Cdm::kSuccess); + const char* const version = Cdm::version(); cout << endl << "CDM Information:" << endl; - cout << " CDM Version = " << Cdm::version() << endl; + cout << " CDM Version = " << version << endl; + RecordWvProperty("version", version); cout << " Company Name = " << client_info.company_name << endl; + RecordWvProperty("company_name", client_info.company_name); cout << " Model Name = " << client_info.model_name << endl; + RecordWvProperty("model_name", client_info.model_name); cout << " Model Year = " << client_info.model_year << endl; + RecordWvProperty("model_year", client_info.model_year); cout << " Device Name = " << client_info.device_name << endl; + RecordWvProperty("device_name", client_info.device_name); cout << " Product Name = " << client_info.product_name << endl; + RecordWvProperty("product_name", client_info.product_name); cout << " Arch Name = " << client_info.arch_name << endl; + RecordWvProperty("arch_name", client_info.arch_name); cout << " Build Info = " << client_info.build_info << endl; + RecordWvProperty("build_info", client_info.build_info); + + // Parse out and log the pieces of the build info + size_t start = 0; + for (const std::string& field : kBuildInfoFields) { + ASSERT_LT(start, client_info.build_info.size()); + size_t end = client_info.build_info.find(kBuildInfoDelimiter, start); + if (end == std::string::npos) end = client_info.build_info.size(); + const std::string value = client_info.build_info.substr(start, end - start); + EXPECT_FALSE(value.empty()); + RecordWvProperty("build_info_" + field, value); + start = end + kBuildInfoDelimiter.length(); + } + ASSERT_GE(start, client_info.build_info.size()); // Collect OEMCrypto info ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_Initialize()); @@ -659,6 +698,8 @@ TEST_F(CdmTest, PrintClientInformation) { std::string oec_build_info; EXPECT_EQ(cdm_->getOemCryptoBuildInfo(&oec_build_info), Cdm::kSuccess); + // We don't log the OEMCrypto info through RecordProperty() because the + // OEMCrypto tests are responsible for that. cout << endl << "OEMCrypto Information:" << endl; cout << " OEMCrypto Version = " << oec_major << "." << oec_minor << endl; cout << " Robustness Level = " << describe(robustness_level) << endl; diff --git a/cdm/test/test_host.cpp b/cdm/test/test_host.cpp index 7fdcf1af..7c480dc4 100644 --- a/cdm/test/test_host.cpp +++ b/cdm/test/test_host.cpp @@ -49,12 +49,12 @@ const std::unordered_set kGlobalFilenames = { TestHost::TestHost() : global_storage_(true), per_origin_storage_(false) { Reset(); } -TestHost::~TestHost() { wvutil::TestSleep::set_callback(nullptr); } +TestHost::~TestHost() { wvutil::TestSleep::RemoveCallback(this); } void TestHost::Reset() { auto now = std::chrono::system_clock().now(); now_ = now.time_since_epoch() / std::chrono::milliseconds(1); - wvutil::TestSleep::set_callback(this); + wvutil::TestSleep::AddCallback(this); // Surprisingly, std::priority_queue has no clear(). while (!timers_.empty()) { diff --git a/core/include/client_identification.h b/core/include/client_identification.h index 588e45a4..55eddb79 100644 --- a/core/include/client_identification.h +++ b/core/include/client_identification.h @@ -54,6 +54,9 @@ class ClientIdentification { private: bool GetProvisioningTokenType( video_widevine::ClientIdentification::TokenType* token_type); + bool GetProvisioning40TokenSignatureType( + video_widevine::ClientIdentification::ClientCredentials::CredentialType* + token_signature_type); bool is_license_request_ = false; bool is_okp_request_ = false; diff --git a/core/include/crypto_session.h b/core/include/crypto_session.h index e4f94b10..c5b84a83 100644 --- a/core/include/crypto_session.h +++ b/core/include/crypto_session.h @@ -99,6 +99,13 @@ class CryptoSession { virtual CdmResponseType GetProvisioning40TokenType( OEMCrypto_BCCType* bcc_type); + virtual CdmResponseType GetProvisioning40TokenSignatureType( + RequestedSecurityLevel requested_security_level, + OEMCrypto_BCCSignatureType* bcc_signature_type); + // Must be called after session is open. + virtual CdmResponseType GetProvisioning40TokenSignatureType( + OEMCrypto_BCCSignatureType* bcc_signature_type); + virtual CdmClientTokenType GetPreProvisionTokenType() { return pre_provision_token_type_; } @@ -213,6 +220,11 @@ class CryptoSession { std::string* additional_signature); virtual CdmResponseType GetBootCertificateChain( std::string* bcc, std::string* additional_signature); + virtual CdmResponseType GetBootCertificateChainSignatureType( + RequestedSecurityLevel requested_security_level, + OEMCrypto_BCCSignatureType* bcc_signature_type); + virtual CdmResponseType GetBootCertificateChainSignatureType( + OEMCrypto_BCCSignatureType* bcc_signature_type); virtual CdmResponseType GetDeviceInformation( RequestedSecurityLevel requested_security_level, std::string* device_info); @@ -494,12 +506,12 @@ class CryptoSession { // otherwise, such as making two calls into OEMCrypto immediately after each // other. template - static auto WithStaticFieldWriteLock(const char* tag, - Func body) -> decltype(body()); + static auto WithStaticFieldWriteLock(const char* tag, Func body) + -> decltype(body()); template - static auto WithStaticFieldReadLock(const char* tag, - Func body) -> decltype(body()); + static auto WithStaticFieldReadLock(const char* tag, Func body) + -> decltype(body()); template static auto WithOecWriteLock(const char* tag, Func body) -> decltype(body()); diff --git a/core/include/license.h b/core/include/license.h index f3a5b0c0..45c920f5 100644 --- a/core/include/license.h +++ b/core/include/license.h @@ -62,39 +62,256 @@ class CdmLicense { // == License Restoring API == + // Restores an offline license for continued use. + // + // Parameters: + // |client_token| + // Client's DRM certificate used as the "token" when + // authenticating the device and client with the license + // server. + // |license_request| + // Original license request generated by this device. + // Expected to be a serialized SignedMessage, with type + // LICENSE_REQUEST. + // |license_response| + // Original license response generated by the license + // server, associated with the |license_response|. + // Expected to be a serialized SignedMessage, with type + // LICENSE. + // |license_renewal_response| + // Last renewal response received from the renewal license + // server. + // Expected to be a serialized SignedMessage, with type + // LICENSE. + // |playback_start_time|, |last_playback_time|, |grace_period_end_time| + // License timers from the CDM, not necessarily secure timers. + // + // Important Results: + // NO_ERROR + // Successfully restored license for playback. virtual CdmResponseType RestoreOfflineLicense( const std::string& client_token, const CdmKeyMessage& license_request, const CdmKeyResponse& license_response, const CdmKeyResponse& license_renewal_response, int64_t playback_start_time, int64_t last_playback_time, int64_t grace_period_end_time, CdmSession* cdm_session); + + // Restores an offline license for release. + // + // License is loaded into OEMCrypto with the intention of + // generating a release request. + // Attempting playback will cause undefined behavior. + // + // Parameters: + // |client_token| + // Client's DRM certificate used as the "token" when + // authenticating the device and client with the license + // server. + // |license_request| + // Original license request generated by this device. + // Expected to be a serialized SignedMessage, with type + // LICENSE_REQUEST. + // |license_response| + // Original license response generated by the license + // server, associated with the |license_response|. + // Expected to be a serialized SignedMessage, with type + // LICENSE. + // + // Important Results: + // NO_ERROR + // Successfully restored license for release. virtual CdmResponseType RestoreLicenseForRelease( const std::string& client_token, const CdmKeyMessage& license_request, const CdmKeyResponse& license_response); // == Request/Response API == + // Generates a license or service certificate request. + // + // If the license does not have a service certificate, then the + // first time calling this method on a newly initialized instance + // will generate a service certificate request. + // + // Parameters: + // |init_data| + // App provided initialization data. Should be of a supported + // format. + // |client_token| + // Client's DRM certificate used as the "token" when + // authenticating the device and client with the license + // server. + // |license_type| + // Type of license to be requested, used in the generation + // of the content ID. + // |app_parameters| + // Optional key-value pair of parameters to be inserted into + // the client ID of the license request. + // Note: Certain key's are reserved for Widevine use and + // cannot be specified by the app. + // |signed_request| (out) + // Serialized license request to be sent to the server. + // Only set if generating license is successful. + // This message is a serialized SignedMessage, with type + // NEW. + // |server_url| (out) + // Server URL, never specified by the CDM for new licenses. + // Legacy field for older revisions. + // + // Important Results: + // KEY_MESSAGE + // Successfully generated a request. + // PRIVACY_MODE_ERROR_1 + // Privacy mode is enabled, but no service certificate has + // been provided, and requesting service certificates are + // forbidden. + // GENERATE_SIGNATURE_ERROR (legacy name) + // OEMCrypto was unable to sign the request. virtual CdmResponseType PrepareKeyRequest( const InitializationData& init_data, const std::string& client_token, CdmLicenseType license_type, const CdmAppParameterMap& app_parameters, CdmKeyMessage* signed_request, std::string* server_url); + // Generates a license renewal or release request. + // + // For license renewals (|is_renewal| = true), will attempt to + // generate a renewal request using the policies specified in the + // original license. Playback will continue to be allowed so + // long as the policy timers have not expired. + // + // For license release (|is_renewal| = false), will attempt to + // generate a release request using the policies specified in the + // original license. Playback will be halted, and not allowed to + // resume. For licenses with a usage entry, OEMCrypto will enforce + // playback halting, and a usage report will be generated to be + // sent with the release. + // + // Parameters: + // |is_renewal| + // Flag to indicate whether the generated request is a + // renewal (true) or a release (false). + // |app_parameters| + // Optional key-value pair of parameters to be inserted into + // the client ID of the license request. + // Note: Certain key's are reserved for Widevine use and + // cannot be specified by the app. + // |cdm_session| + // Handle to the calling CdmSession to allow for usage + // reporting. + // |signed_request| (out) + // Serialized renewal or release request to be sent to the + // server. Only set if generating license is successful. + // This message is a serialized SignedMessage, with type + // RENEWAL or RELEASE. + // |server_url| (out) + // Server URL to be used for sending renewals. The value + // provided is extracted from the license policy received + // in the original license response. + // + // Important Results: + // KEY_MESSAGE + // Successfully generated a renewal or release request. + // LICENSE_RENEWAL_PROHIBITED + // License policy forbids license renewals. + // GENERATE_SIGNATURE_ERROR (legacy name) + // OEMCrypto was unable to sign the request. virtual CdmResponseType PrepareKeyUpdateRequest( bool is_renewal, const CdmAppParameterMap& app_parameters, CdmSession* cdm_session, CdmKeyMessage* signed_request, std::string* server_url); + // Parses and loads license or service certificate response, + // or handles license errors. + // + // For license response, the content of the license is parsed; + // extracting and verifying keys, initializing the license's + // policy engine, and updating other policy rules. If license + // contains entitlement keys, changes the license type + // to entitlement. + // + // For service certificate response, the service certificate + // is parsed and loaded into the |service_certificate_| + // field for next call to generate request. + // + // For error response, parses the error message, producing logs + // and returning an appropriate error. + // + // Parameters: + // |license_response| + // Serialized license response. + // Expected to be a serialized SignedMessage, with type + // LICENSE. + // + // Important Results: + // KEY_ADDED + // Successfully loaded license. + // NEED_KEY + // Successfully loaded service certificate, and signals + // to the app that another request is required to generate + // a license request. + // NO_CONTENT_KEY + // License was received, but did not contain any content + // or operator session keys. + // LOAD_LICENSE_ERROR + // OEMCrypto rejected the license, see logs for details. virtual CdmResponseType HandleKeyResponse( - bool is_restore, const CdmKeyResponse& license_response); + const CdmKeyResponse& license_response) { + return HandleKeyResponseInternal(/* is_restore = */ false, + license_response); + } + // Parses and loads renewal response, or handles license + // errors. + // + // For license renewal response, the content of the renewal is + // parsed; updating policy timers and other license policy + // variables. + // + // For error response, parses the error message, producing logs + // and returning an appropriate error. + // + // Parameters: + // |license_response| + // Serialized license renewal response. + // Expected to be a serialized SignedMessage, with type + // LICENSE. + // + // Important Results: + // KEY_ADDED + // Successfully loaded renewal. + // NO_CONTENT_KEY + // License was received, but did not contain any content + // or operator session keys. + // LOAD_LICENSE_ERROR + // OEMCrypto rejected the license, see logs for details. virtual CdmResponseType HandleKeyUpdateResponse( - bool is_renewal, bool is_restore, const CdmKeyResponse& license_response); + bool is_renewal, const CdmKeyResponse& license_response) { + return HandleKeyUpdateResponseInternal(is_renewal, /* is_restore = */ false, + license_response); + } + // Parses and loads new embedded keys. + // + // Updates the license's key session with new entitled keys + // for which the license has entitlement keys for. Storing + // those keys internally as new content keys. + // + // Used exclusively for entitlement licenses. + // + // Parameters: + // |init_data| + // Initialization data containing entitled keys within + // the PSSH data. + // + // Important Results: + // KEY_ADDED + // Successfully loaded new entitled keys. virtual CdmResponseType HandleEmbeddedKeyData( const InitializationData& init_data); // == Utilities == + // Utility method for extracting the provider session token + // from the license response. static bool ExtractProviderSessionToken( const CdmKeyResponse& license_response, std::string* provider_session_token); @@ -110,11 +327,22 @@ class CdmLicense { policy_engine_ = policy_engine; } - private: + protected: // Test Constructor. // CdmLicense takes ownership of the clock. CdmLicense(const CdmSessionId& session_id, wvutil::Clock* clock); + // Inserts an entitlement key ID from test data. + void InsertEntitlementKeyIdForTest(const KeyId& entitlement_key_id) { + entitlement_key_ids_.insert(entitlement_key_id); + } + + CdmResponseType HandleNewEntitledKeysForTest( + const std::vector& packaged_entitled_keys) { + return HandleNewEntitledKeysInternal(packaged_entitled_keys); + } + + private: // == Internal Request/Response API == // Prepare to reload a key update message. Some special code is needed to work @@ -122,9 +350,25 @@ class CdmLicense { // TODO(b/166007195): Remove this. CdmResponseType PrepareKeyUpdateReload(CdmSession* cdm_session); + // Used internally to handle license responses for both newly acquired + // licenses and for restoring of offline licenses. + CdmResponseType HandleKeyResponseInternal( + bool is_restore, const CdmKeyResponse& license_response); + + // Used internally to handle renewal responses for both newly acquired + // renewals and for restoring of offline licenses which had received + // renewals. + CdmResponseType HandleKeyUpdateResponseInternal( + bool is_renewal, bool is_restore, const CdmKeyResponse& license_response); + + // Used internally to handle license error responses for both + // license requests and renewal requests. Maps the error code + // from the license protocol to a CDM error code. CdmResponseType HandleKeyErrorResponse( const video_widevine::SignedMessage& signed_message); + // Used internally to load the content license into OEMCrypto + // and update the license data if successful. CdmResponseType HandleContentKeyResponse( bool is_restore, const std::string& session_key, const std::string& msg, const std::string& core_message, const std::string& signature, @@ -142,7 +386,9 @@ class CdmLicense { const std::vector& license_keys, const video_widevine::License& license); - CdmResponseType HandleNewEntitledKeys( + // Used internally to load the entitlement keys from + // outside the license into the CryptoSession. + CdmResponseType HandleNewEntitledKeysInternal( const std::vector& packaged_entitled_keys); // == Internal Utilities == @@ -163,60 +409,116 @@ class CdmLicense { // == Creation-time Variables == + // CDM session ID associated with this license. CdmLicense should + // only be associated with a single CDM session. const CdmSessionId session_id_; + // Internal clock used to get REE system-time. + // For production, uses the default Clock implementation; + // for testing, uses a MockClock. std::unique_ptr clock_; // == Initialization-time Variables == + // Flag to indicate that the CdmLicense has been initialized + // correctly via a call to Init(). bool initialized_ = false; + // License's crypto session, owned by the CdmSession which owns + // this instance. CryptoSession* crypto_session_ = nullptr; + // License's policy engine, owned by the CdmSession which owns + // this instance. PolicyEngine* policy_engine_ = nullptr; - // Associated with ClientIdentification encryption + // The flag |use_privacy_mode_| is used to determine whether + // the client identification field should be encrypted when + // generating license / renewal request. bool use_privacy_mode_ = false; + // The service certificate used to encrypt client + // identification. Must be initialized if |use_privacy_mode_| + // is true. ServiceCertificate service_certificate_; + // Assume the latest, and downgrade later. + // May be downgraded based on the OEMCrypto level, or the + // license response. video_widevine::ProtocolVersion protocol_version_ = video_widevine::VERSION_2_2; // == License Request/Response variables == + // Device-side token used to authenticate the license request + // with the license server. + // Always contains a serialized DRM certificate associated + // with the client. std::string client_token_; + + // Contains the initialization data that was provided by the app + // when making the first request. + // Used for certain devices which may perform a service certificate + // request. The app will only provided the license init data on + // the first request, but may not provide initialization data on + // the follow up request. std::unique_ptr stored_init_data_; // The nonce used in the original license request. uint32_t license_nonce_ = 0; + // Serialized LicenseRequest proto. + // Either originates internally from a license request generated + // since the opening of this session; or provided while restoring + // of a license. CdmKeyMessage license_request_; - // For entitlement key licensing. This holds the keys from the init_data. - // These keys are extracted from the PSSH when we generate a license request. - // It is used to load content keys after we have received a license and - // entitlement keys. It is also used in updating the key status info. + // These are extracted from the PSSH from the initialization data. + // They are used to load entitled content keys after the license + // has been received. It is also used in updating the key status + // info. + // Used exclusively for entitlement key licensing. + // These entitled keys are extracted at license request time. std::vector request_entitled_keys_; + // Content-provider token for identifying a client's device across + // multiple sessions/licenses. std::string provider_client_token_; + // Content-provider token for identifying a client's session for + // a single persistent license, and potentially across multiple + // restores. std::string provider_session_token_; + // Flag to indicate that a renewal request requires a client ID. + // This is specified in the license. bool renew_with_client_id_ = false; + // Server URL which the client app should use when requesting + // a renewal. std::string renewal_server_url_; - // This is the latest version info extracted from the SignedMessage in - // HandleKeyResponse + // This is the latest version info extracted from the SignedMessage + // when loading a received license. video_widevine::VersionInfo latest_service_version_; // == License Life-Time Variables == + // A license is assumed to be content type until the license + // response changes that. CdmLicenseKeyType license_key_type_ = kLicenseKeyTypeContent; + + // Flag to indicate that the license is offline. + // A license is assumed to be streaming/online until a response is + // received. This flag is determined by the license + // type (OFFLINE) and the policy's "can persist" flag. bool is_offline_ = false; + // A list of content keys (either basic content keys, or entitled + // content keys) which have been received. + // Note: for entitlement licenses, this is updated when new entitled + // content keys are received. std::set content_key_ids_; - std::set entitlement_key_ids_; -#if defined(UNIT_TEST) - friend class CdmLicenseTestPeer; -#endif + // A list of entitlement key IDs from the license. Used to filter + // out the any entitled keys tied to an entitlement key which are + // not specified in the license response. + std::set entitlement_key_ids_; }; // class CdmLicense } // namespace wvcdm #endif // WVCDM_CORE_LICENSE_H_ diff --git a/core/include/oemcrypto_adapter.h b/core/include/oemcrypto_adapter.h index aec88230..e8172453 100644 --- a/core/include/oemcrypto_adapter.h +++ b/core/include/oemcrypto_adapter.h @@ -97,5 +97,8 @@ OEMCryptoResult OEMCrypto_Generic_Verify( const OEMCrypto_SharedMemory* signature, size_t signature_length); OEMCryptoResult OEMCrypto_GetBCCType(RequestedSecurityLevel level, OEMCrypto_BCCType* bcc_type); +OEMCryptoResult OEMCrypto_GetBCCSignatureType( + RequestedSecurityLevel level, + OEMCrypto_BCCSignatureType* bcc_signature_type); } // namespace wvcdm #endif // WVCDM_CORE_OEMCRYPTO_ADAPTER_H_ diff --git a/core/include/wv_cdm_constants.h b/core/include/wv_cdm_constants.h index 7bd5cb6a..0b2af7cf 100644 --- a/core/include/wv_cdm_constants.h +++ b/core/include/wv_cdm_constants.h @@ -125,6 +125,10 @@ static const std::string QUERY_KEY_PRODUCTION_READY = "ProductionReady"; // Internal query key. Should not be exposed to Android apps. static const std::string QUERY_KEY_DEBUG_BOOT_CERTIFICATE_CHAIN = "DebugBootCertificateChain"; +static const std::string QUERY_KEY_DEBUG_BOOT_CERTIFICATE_CHAIN_SIGNATURE = + "DebugBootCertificateChainSignature"; +static const std::string QUERY_KEY_DEBUG_BOOT_CERTIFICATE_CHAIN_SIGNATURE_TYPE = + "DebugBootCertificateChainSignatureType"; static const std::string QUERY_KEY_DEVICE_INFORMATION = "DeviceInformation"; static const std::string QUERY_VALUE_TRUE = "True"; diff --git a/core/include/wv_cdm_types.h b/core/include/wv_cdm_types.h index 69cd21dc..98441bbe 100644 --- a/core/include/wv_cdm_types.h +++ b/core/include/wv_cdm_types.h @@ -463,6 +463,7 @@ enum CdmResponseEnum : int32_t { GET_DEVICE_INFORMATION_ERROR = 398, GET_DEVICE_SIGNED_CSR_PAYLOAD_ERROR = 399, GET_TOKEN_FROM_EMBEDDED_CERT_ERROR = 400, + GET_BCC_SIGNATURE_TYPE_ERROR = 401, // Don't forget to add new values to // * core/src/wv_cdm_types.cpp // * android/include/mapErrors-inl.h @@ -958,5 +959,6 @@ const char* BoolToString(bool value); // Logging utilities for OEMCrypto types. const char* OemCryptoResultToString(OEMCryptoResult result); +const char* OemCryptoBccSignatureTypeToString(OEMCrypto_BCCSignatureType type); } // namespace wvcdm #endif // WVCDM_CORE_WV_CDM_TYPES_H_ diff --git a/core/src/cdm_engine.cpp b/core/src/cdm_engine.cpp index 7d059de4..49691827 100644 --- a/core/src/cdm_engine.cpp +++ b/core/src/cdm_engine.cpp @@ -903,6 +903,46 @@ CdmResponseType CdmEngine::QueryStatus(RequestedSecurityLevel security_level, LOGE("Failed to extract BCC: status = %d", status.ToInt()); return status; } + if (query_token == QUERY_KEY_DEBUG_BOOT_CERTIFICATE_CHAIN_SIGNATURE) { + std::string bcc_unused; + std::string signature; + const CdmResponseType status = crypto_session->GetBootCertificateChain( + security_level, &bcc_unused, &signature); + if (status == NO_ERROR) { + LOGV("BCC signature length: %zu", signature.size()); + *query_response = std::move(signature); + return CdmResponseType(NO_ERROR); + } + if (status == NOT_IMPLEMENTED_ERROR || + status == PROVISIONING_TYPE_IS_NOT_BOOT_CERTIFICATE_CHAIN_ERROR) { + LOGD("BCC signature not available: %s", status.ToString().c_str()); + *query_response = QUERY_VALUE_NONE; + return CdmResponseType(NO_ERROR); + } + LOGE("Failed to extract BCC signature: status = %s", + status.ToString().c_str()); + return status; + } + if (query_token == QUERY_KEY_DEBUG_BOOT_CERTIFICATE_CHAIN_SIGNATURE_TYPE) { + OEMCrypto_BCCSignatureType bcc_signature_type = + OEMCrypto_BCCSigType_Unknown; + const CdmResponseType status = + crypto_session->GetBootCertificateChainSignatureType( + security_level, &bcc_signature_type); + if (status == NO_ERROR) { + *query_response = OemCryptoBccSignatureTypeToString(bcc_signature_type); + return CdmResponseType(NO_ERROR); + } + if (status == NOT_IMPLEMENTED_ERROR || + status == PROVISIONING_TYPE_IS_NOT_BOOT_CERTIFICATE_CHAIN_ERROR) { + LOGD("BCC signature type not available: %s", status.ToString().c_str()); + *query_response = QUERY_VALUE_NONE; + return CdmResponseType(NO_ERROR); + } + LOGE("Failed to extract BCC signature type: status = %s", + status.ToString().c_str()); + return status; + } if (query_token == QUERY_KEY_DEVICE_INFORMATION) { std::string device_info; const CdmResponseType status = diff --git a/core/src/cdm_session.cpp b/core/src/cdm_session.cpp index 73f826eb..0d5b61f0 100644 --- a/core/src/cdm_session.cpp +++ b/core/src/cdm_session.cpp @@ -570,8 +570,7 @@ CdmResponseType CdmSession::AddKeyInternal(const CdmKeyResponse& key_response) { if (sts != NO_ERROR) return sts; } } - sts = - license_parser_->HandleKeyResponse(/* is restore */ false, key_response); + sts = license_parser_->HandleKeyResponse(key_response); // Update the license sdk and service versions. const video_widevine::VersionInfo& version_info = @@ -779,8 +778,7 @@ CdmResponseType CdmSession::RenewKey(const CdmKeyResponse& key_response) { return CdmResponseType(NOT_INITIALIZED_ERROR); } CdmResponseType sts = license_parser_->HandleKeyUpdateResponse( - /* is renewal */ true, - /* is restore */ false, key_response); + /* is renewal */ true, key_response); // Record the timing on success. UpdateRequestLatencyTiming(sts); @@ -844,8 +842,7 @@ CdmResponseType CdmSession::ReleaseKey(const CdmKeyResponse& key_response) { return CdmResponseType(NOT_INITIALIZED_ERROR); } CdmResponseType sts = license_parser_->HandleKeyUpdateResponse( - /* is renewal */ false, - /* is restore */ false, key_response); + /* is renewal */ false, key_response); // Record the timing on success. UpdateRequestLatencyTiming(sts); diff --git a/core/src/client_identification.cpp b/core/src/client_identification.cpp index 94bb8720..d3c4817d 100644 --- a/core/src/client_identification.cpp +++ b/core/src/client_identification.cpp @@ -60,6 +60,8 @@ bool IsPropertyKeyReserved(const std::string& prop_name) { // Protobuf generated classes. using ClientCapabilities = video_widevine::ClientIdentification::ClientCapabilities; +using ClientCredentials = + video_widevine::ClientIdentification::ClientCredentials; using AnalogOutputCapabilities = ClientCapabilities::AnalogOutputCapabilities; using video_widevine::ClientIdentification_NameValue; @@ -141,7 +143,17 @@ CdmResponseType ClientIdentification::Prepare( } client_id->set_token(token); if (!additional_token.empty()) { + // additional_token is only available for Provisioning 4.0 request, it + // holds the BCC signature. client_id->mutable_device_credentials()->set_token(additional_token); + ClientCredentials::CredentialType token_signature_type; + if (!GetProvisioning40TokenSignatureType(&token_signature_type)) { + client_id->mutable_device_credentials()->set_credential_type( + ClientCredentials::CREDENTIAL_TYPE_UNKNOWN); + } else { + client_id->mutable_device_credentials()->set_credential_type( + token_signature_type); + } } } @@ -416,4 +428,32 @@ bool ClientIdentification::GetProvisioningTokenType( } } +bool ClientIdentification::GetProvisioning40TokenSignatureType( + video_widevine::ClientIdentification::ClientCredentials::CredentialType* + token_signature_type) { + OEMCrypto_BCCSignatureType bcc_signature_type = OEMCrypto_BCCSigType_Unknown; + const CdmResponseType status = + crypto_session_->GetProvisioning40TokenSignatureType(&bcc_signature_type); + if (status != NO_ERROR) { + LOGE("Failed to get provisioning token signature type: status = %s", + status.ToString().c_str()); + return false; + } + switch (bcc_signature_type) { + case OEMCrypto_BCCSigType_CBOR: + *token_signature_type = + ClientCredentials::CREDENTIAL_TYPE_BCC_SIGNATURE_CBOR; + return true; + case OEMCrypto_BCCSigType_PKCS7: + *token_signature_type = + ClientCredentials::CREDENTIAL_TYPE_BCC_SIGNATURE_PKCS7; + return true; + default: + // shouldn't happen + LOGE("Unexpected provisioning token signature type: %d", + static_cast(bcc_signature_type)); + return false; + } +} + } // namespace wvcdm diff --git a/core/src/crypto_session.cpp b/core/src/crypto_session.cpp index a8ad13ce..be0846e6 100644 --- a/core/src/crypto_session.cpp +++ b/core/src/crypto_session.cpp @@ -151,6 +151,7 @@ void AdvanceDestBuffer(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; return; case OEMCrypto_BufferType_Secure: @@ -681,6 +682,26 @@ CdmResponseType CryptoSession::GetProvisioning40TokenType( "GetProvisioning40TokenType"); } +CdmResponseType CryptoSession::GetProvisioning40TokenSignatureType( + OEMCrypto_BCCSignatureType* bcc_signature_type) { + RETURN_IF_NOT_OPEN(CRYPTO_SESSION_NOT_OPEN); + return GetProvisioning40TokenSignatureType(requested_security_level_, + bcc_signature_type); +} + +CdmResponseType CryptoSession::GetProvisioning40TokenSignatureType( + RequestedSecurityLevel requested_security_level, + OEMCrypto_BCCSignatureType* bcc_signature_type) { + RETURN_IF_NULL(bcc_signature_type, PARAMETER_NULL); + RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED); + const OEMCryptoResult result = WithOecReadLock("GetBCCSignatureType", [&] { + return OEMCrypto_GetBCCSignatureType(requested_security_level, + bcc_signature_type); + }); + return MapOEMCryptoResult(result, GET_BCC_SIGNATURE_TYPE_ERROR, + "GetProvisioning40TokenSignatureType"); +} + CdmSecurityLevel CryptoSession::GetSecurityLevel() { LOGV("Getting security level"); RETURN_IF_NOT_OPEN(kSecurityLevelUninitialized); @@ -1444,6 +1465,41 @@ CdmResponseType CryptoSession::GetBootCertificateChain( return CdmResponseType(NO_ERROR); } +CdmResponseType CryptoSession::GetBootCertificateChainSignatureType( + OEMCrypto_BCCSignatureType* bcc_signature_type) { + RETURN_IF_NOT_OPEN(CRYPTO_SESSION_NOT_OPEN); + return GetBootCertificateChainSignatureType(requested_security_level_, + bcc_signature_type); +} + +CdmResponseType CryptoSession::GetBootCertificateChainSignatureType( + RequestedSecurityLevel requested_security_level, + OEMCrypto_BCCSignatureType* bcc_signature_type) { + RETURN_IF_NULL(bcc_signature_type, PARAMETER_NULL); + RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED); + if (GetSecurityLevel(requested_security_level) != kSecurityLevelL1) { + LOGE("CDM only supports L1 provisioning40 token type"); + return CdmResponseType(NOT_IMPLEMENTED_ERROR); + } + CdmClientTokenType token_type = kClientTokenUninitialized; + const CdmResponseType status = + GetProvisioningMethod(requested_security_level, &token_type); + if (status != NO_ERROR) { + LOGE("Failed to get token type"); + return status; + } + if (token_type != kClientTokenBootCertChain) { + return CdmResponseType( + PROVISIONING_TYPE_IS_NOT_BOOT_CERTIFICATE_CHAIN_ERROR); + } + const OEMCryptoResult result = WithOecReadLock("GetBCCSignatureType", [&] { + return OEMCrypto_GetBCCSignatureType(requested_security_level, + bcc_signature_type); + }); + return MapOEMCryptoResult(result, UNKNOWN_CLIENT_TOKEN_TYPE, + "GetBootCertificateChainSignatureType"); +} + CdmResponseType CryptoSession::GetTokenFromEmbeddedCertificate( RequestedSecurityLevel requested_security_level, std::string* token) { RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED); @@ -3263,11 +3319,6 @@ OEMCryptoResult CryptoSession::DecryptSample( } 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 = &clear_subsample; fake_sample.subsamples_length = 1; @@ -3295,11 +3346,6 @@ OEMCryptoResult CryptoSession::DecryptSample( } 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 = &encrypted_subsample; fake_sample.subsamples_length = 1; @@ -3392,10 +3438,6 @@ OEMCryptoResult CryptoSession::LegacyCopyBufferInChunks( // Calculate the size of the next chunk. const size_t chunk_size = std::min(remaining_input_data, max_chunk_size); - if (output_descriptor.type == OEMCrypto_BufferType_Clear) { - output_descriptor.buffer.clear.clear_buffer_length = chunk_size; - } - // Re-add "last subsample" flag if this is the last subsample. if (chunk_size == remaining_input_data) { subsample_flags |= OEMCrypto_LastSubsample; @@ -3443,11 +3485,6 @@ OEMCryptoResult CryptoSession::LegacyDecryptInChunks( // Calculate the size of the next chunk. const size_t chunk_size = std::min(remaining_input_data, max_chunk_size); fake_sample.buffers.input_data_length = chunk_size; - if (fake_sample.buffers.output_descriptor.type == - OEMCrypto_BufferType_Clear) { - fake_sample.buffers.output_descriptor.buffer.clear.clear_buffer_length = - chunk_size; - } if (is_protected) { fake_subsample.num_bytes_encrypted = chunk_size; } else { @@ -3573,40 +3610,40 @@ CdmResponseType CryptoSession::LoadOtaProvisioning( } template -auto CryptoSession::WithStaticFieldWriteLock(const char* tag, - Func body) -> decltype(body()) { +auto CryptoSession::WithStaticFieldWriteLock(const char* tag, Func body) + -> decltype(body()) { LOGV("Static field write lock: %s", tag); std::unique_lock auto_lock(static_field_mutex_); return body(); } template -auto CryptoSession::WithStaticFieldReadLock(const char* tag, - Func body) -> decltype(body()) { +auto CryptoSession::WithStaticFieldReadLock(const char* tag, Func body) + -> decltype(body()) { LOGV("Static field read lock: %s", tag); wvutil::shared_lock auto_lock(static_field_mutex_); return body(); } template -auto CryptoSession::WithOecWriteLock(const char* tag, - Func body) -> decltype(body()) { +auto CryptoSession::WithOecWriteLock(const char* tag, Func body) + -> decltype(body()) { LOGV("OEMCrypto write lock: %s", tag); std::unique_lock auto_lock(oem_crypto_mutex_); return body(); } template -auto CryptoSession::WithOecReadLock(const char* tag, - Func body) -> decltype(body()) { +auto CryptoSession::WithOecReadLock(const char* tag, Func body) + -> decltype(body()) { LOGV("OEMCrypto read lock: %s", tag); wvutil::shared_lock auto_lock(oem_crypto_mutex_); return body(); } template -auto CryptoSession::WithOecSessionLock(const char* tag, - Func body) -> decltype(body()) { +auto CryptoSession::WithOecSessionLock(const char* tag, Func body) + -> decltype(body()) { LOGV("OEMCrypto session lock: %s", tag); wvutil::shared_lock oec_auto_lock(oem_crypto_mutex_); std::unique_lock session_auto_lock(oem_crypto_session_mutex_); diff --git a/core/src/initialization_data.cpp b/core/src/initialization_data.cpp index cf87d24a..2c0312ca 100644 --- a/core/src/initialization_data.cpp +++ b/core/src/initialization_data.cpp @@ -454,8 +454,7 @@ bool InitializationData::ConstructWidevineInitData( LOGV("Base64 decode of json data failed"); return false; } - std::string json_string((const char*)(&json_init_data[0]), - json_init_data.size()); + const std::string json_string(json_init_data.begin(), json_init_data.end()); // Parse the Json string using jsmn jsmn_parser parser; @@ -513,12 +512,13 @@ bool InitializationData::ConstructWidevineInitData( break; case kContentIdState: if (tokens[i].type == JSMN_STRING) { - std::string base64_content_id(json_string, tokens[i].start, - tokens[i].end - tokens[i].start); - std::vector content_id_data = + const std::string base64_content_id = json_string.substr( + tokens[i].start, tokens[i].end - tokens[i].start); + const std::vector content_id_data = wvutil::Base64Decode(base64_content_id); - content_id.assign(reinterpret_cast(&content_id_data[0]), - content_id_data.size()); + if (!content_id_data.empty()) { + content_id.assign(content_id_data.begin(), content_id_data.end()); + } } state = kParseState; break; diff --git a/core/src/license.cpp b/core/src/license.cpp index 42a9da8e..0c88655d 100644 --- a/core/src/license.cpp +++ b/core/src/license.cpp @@ -43,7 +43,6 @@ using ContentIdentification = using video_widevine::HashAlgorithmProto; using video_widevine::License; using video_widevine::LicenseError; -using video_widevine::LicenseIdentification; using video_widevine::LicenseRequest; using KeyContainer = video_widevine::License::KeyContainer; using video_widevine::SignedMessage; @@ -259,7 +258,12 @@ CdmResponseType CdmLicense::PrepareKeyRequest( } client_token_ = client_token; if (init_data.IsEmpty() && stored_init_data_) { - InitializationData restored_init_data = *stored_init_data_; + // In the event that the first call the PrepareKeyRequest() + // was a service certificate request, |stored_init_data_| + // was set. App may not provide the init data on the second + // call. + const InitializationData restored_init_data(std::move(*stored_init_data_)); + // Clear to prevent re-use. stored_init_data_.reset(); return PrepareKeyRequest(restored_init_data, client_token, license_type, app_parameters, signed_request, server_url); @@ -306,10 +310,9 @@ CdmResponseType CdmLicense::PrepareKeyRequest( const std::string& request_id = crypto_session_->request_id(); LicenseRequest license_request; - CdmResponseType status; - status = PrepareClientId(app_parameters, - /* provider_client_token = */ kEmptyString, - &license_request); + CdmResponseType status = PrepareClientId( + app_parameters, + /* provider_client_token = */ kEmptyString, &license_request); if (NO_ERROR != status) return status; status = @@ -322,7 +325,6 @@ CdmResponseType CdmLicense::PrepareKeyRequest( // Get/set the nonce. This value will be reflected in the Key Control Block // of the license response. status = crypto_session_->GenerateNonce(&license_nonce_); - switch (status.code()) { case NO_ERROR: break; @@ -416,7 +418,7 @@ CdmResponseType CdmLicense::PrepareKeyUpdateRequest( LOGE("Output parameter |server_url| not provided"); return CdmResponseType(INVALID_PARAMETERS_LIC_2); } - + // If |is_renewal| is false, then this is a release request. if (is_renewal && !policy_engine_->CanRenew()) { LOGE("License renewal prohibited"); return CdmResponseType(LICENSE_RENEWAL_PROHIBITED); @@ -430,24 +432,20 @@ CdmResponseType CdmLicense::PrepareKeyUpdateRequest( } LicenseRequest license_request; - if (is_renewal) - license_request.set_type(LicenseRequest::RENEWAL); - else - license_request.set_type(LicenseRequest::RELEASE); - + license_request.set_type(is_renewal ? LicenseRequest::RENEWAL + : LicenseRequest::RELEASE); license_request.set_request_time(clock_->GetCurrentTime()); license_request.set_protocol_version(protocol_version_); if (renew_with_client_id_) { - CdmResponseType status = PrepareClientId( + const CdmResponseType status = PrepareClientId( app_parameters, provider_client_token_, &license_request); if (NO_ERROR != status) return status; } ContentIdentification::ExistingLicense* current_license = license_request.mutable_content_id()->mutable_existing_license(); - const LicenseIdentification& license_id = policy_engine_->license_id(); - current_license->mutable_license_id()->CopyFrom(license_id); + current_license->mutable_license_id()->CopyFrom(policy_engine_->license_id()); int64_t seconds_since_started = 0; int64_t seconds_since_last_played = 0; @@ -455,6 +453,8 @@ CdmResponseType CdmLicense::PrepareKeyUpdateRequest( CryptoSession::kUsageDurationsInvalid; if (!provider_session_token_.empty()) { if (!is_renewal) { + // On release, must deactivate the usage entry + // to prevent further playback. const CdmResponseType status = crypto_session_->DeactivateUsageInformation(provider_session_token_); if (NO_ERROR != status) return status; @@ -470,23 +470,31 @@ CdmResponseType CdmLicense::PrepareKeyUpdateRequest( const CdmResponseType status = crypto_session_->GenerateUsageReport( provider_session_token_, &usage_report, &usage_duration_status, &seconds_since_started, &seconds_since_last_played); - if (!is_renewal) { - if (NO_ERROR == status) - current_license->set_session_usage_table_entry(usage_report); - else - return CdmResponseType(GENERATE_USAGE_REPORT_ERROR); + if (status == NO_ERROR && !is_renewal) { + current_license->set_session_usage_table_entry(usage_report); + } else if (status != NO_ERROR && !is_renewal) { + // Usage report is required for license release. + return CdmResponseType(GENERATE_USAGE_REPORT_ERROR); + } else if (status != NO_ERROR) { // && is_renewal + // For renewals, failing to generate the usage report is + // not a serious issue. + LOGW("Failed to generate usage report, continuing without: status = %s", + status.ToString().c_str()); } } - if (CryptoSession::kUsageDurationsValid != usage_duration_status) { - if (policy_engine_->GetSecondsSinceStarted(&seconds_since_started) && - policy_engine_->GetSecondsSinceLastPlayed(&seconds_since_last_played)) - usage_duration_status = CryptoSession::kUsageDurationsValid; - } - if (CryptoSession::kUsageDurationsValid == usage_duration_status) { + // Set timers from usage report. current_license->set_seconds_since_started(seconds_since_started); current_license->set_seconds_since_last_played(seconds_since_last_played); + } else if (policy_engine_->GetSecondsSinceStarted(&seconds_since_started) && + policy_engine_->GetSecondsSinceLastPlayed( + &seconds_since_last_played)) { + // Set timers from license policy engine. + current_license->set_seconds_since_started(seconds_since_started); + current_license->set_seconds_since_last_played(seconds_since_last_played); + } else { + LOGW("Failed to obtain license durations"); } // License request is complete. Serialize it. @@ -517,7 +525,7 @@ CdmResponseType CdmLicense::PrepareKeyUpdateRequest( return CdmResponseType(KEY_MESSAGE); } -CdmResponseType CdmLicense::HandleKeyResponse( +CdmResponseType CdmLicense::HandleKeyResponseInternal( bool is_restore, const CdmKeyResponse& license_response) { if (!initialized_) { LOGE("CdmLicense not initialized"); @@ -580,27 +588,26 @@ CdmResponseType CdmLicense::HandleKeyResponse( return CdmResponseType(SESSION_KEYS_NOT_FOUND); } - // Extract mac key - std::string mac_key_iv; - std::string mac_keys; - for (int i = 0; i < license.key_size(); ++i) { - if (license.key(i).type() == KeyContainer::SIGNING) { - mac_key_iv.assign(license.key(i).iv()); - - // Strip off PKCS#5 padding - mac_keys.assign( - license.key(i).key().data(), - std::min(kLicenseMacKeySize, license.key(i).key().size())); - } + // Verify signing key data and IV length. + size_t signing_key_data_length = 0; + size_t signing_key_iv_length = 0; + for (const auto& key_container : license.key()) { + if (key_container.type() != KeyContainer::SIGNING) continue; + // To maintain backwards compatibility, in the case of multiple + // signing keys, use the last one found. + signing_key_data_length = + std::min(kLicenseMacKeySize, key_container.key().size()); + signing_key_iv_length = key_container.iv().size(); } if (license.policy().can_renew() || - (!mac_key_iv.empty() || !mac_keys.empty())) { - if (mac_key_iv.size() != KEY_IV_SIZE || - mac_keys.size() != kLicenseMacKeySize) { + (signing_key_data_length != 0 || signing_key_iv_length != 0)) { + if (signing_key_iv_length != KEY_IV_SIZE || + signing_key_data_length != kLicenseMacKeySize) { LOGE( "MAC key/IV size error: expected = %zu/%zu, " "actual = %zu/%zu (key/iv)", - kLicenseMacKeySize, KEY_IV_SIZE, mac_keys.size(), mac_key_iv.size()); + kLicenseMacKeySize, KEY_IV_SIZE, signing_key_data_length, + signing_key_iv_length); return CdmResponseType(KEY_SIZE_ERROR_1); } } @@ -651,20 +658,17 @@ CdmResponseType CdmLicense::HandleKeyResponse( crypto_session_->UseSecondaryKey(signed_response.using_secondary_key()); if (status != NO_ERROR) return status; - CdmResponseType resp(NO_CONTENT_KEY); if (kLicenseKeyTypeEntitlement == key_type) { - resp = HandleEntitlementKeyResponse( + return HandleEntitlementKeyResponse( is_restore, signed_response.session_key(), signed_message, core_message, signature, license_keys, license); - } else if (kLicenseKeyTypeContent == key_type) { - resp = HandleContentKeyResponse(is_restore, signed_response.session_key(), - signed_message, core_message, signature, - license_keys, license); } - return resp; + return HandleContentKeyResponse(is_restore, signed_response.session_key(), + signed_message, core_message, signature, + license_keys, license); } -CdmResponseType CdmLicense::HandleKeyUpdateResponse( +CdmResponseType CdmLicense::HandleKeyUpdateResponseInternal( bool is_renewal, bool is_restore, const CdmKeyResponse& license_response) { if (!initialized_) { LOGE("CdmLicense not initialized"); @@ -681,15 +685,13 @@ CdmResponseType CdmLicense::HandleKeyUpdateResponse( return CdmResponseType(LICENSE_RESPONSE_PARSE_ERROR_2); } - switch (signed_response.type()) { - case SignedMessage::LICENSE: - break; - case SignedMessage::ERROR_RESPONSE: - return HandleKeyErrorResponse(signed_response); - default: - LOGE("Unrecognized signed message type: type = %d", - static_cast(signed_response.type())); - return CdmResponseType(INVALID_LICENSE_TYPE); + if (signed_response.type() == SignedMessage::ERROR_RESPONSE) { + return HandleKeyErrorResponse(signed_response); + } + if (signed_response.type() != SignedMessage::LICENSE) { + LOGE("Unrecognized signed message type: type = %d", + static_cast(signed_response.type())); + return CdmResponseType(INVALID_LICENSE_TYPE); } const std::string& signed_message = signed_response.msg(); @@ -722,35 +724,35 @@ CdmResponseType CdmLicense::HandleKeyUpdateResponse( } if (!is_renewal) { - if (!license.id().has_provider_session_token()) - return CdmResponseType(KEY_ADDED); - provider_session_token_ = license.id().provider_session_token(); + // For non-renewals, the response is assumed to be a release + // response. + if (license.id().has_provider_session_token()) { + provider_session_token_ = license.id().provider_session_token(); + } return CdmResponseType(KEY_ADDED); } if (license.policy().has_renewal_server_url() && - license.policy().renewal_server_url().size() > 0) { + !license.policy().renewal_server_url().empty()) { renewal_server_url_ = license.policy().renewal_server_url(); } - // If the field is not set, it will default to false. + // If |using_secondary_key| is not set, it will default to false. CdmResponseType status = crypto_session_->UseSecondaryKey(signed_response.using_secondary_key()); if (status != NO_ERROR) return status; status = crypto_session_->LoadRenewal(signed_message, core_message, signature); + if (status != KEY_ADDED) return status; - if (status == KEY_ADDED) { - policy_engine_->UpdateLicense(license, is_restore); - } - - return status; + policy_engine_->UpdateLicense(license, is_restore); + return CdmResponseType(KEY_ADDED); } CdmResponseType CdmLicense::HandleEmbeddedKeyData( const InitializationData& init_data) { - return HandleNewEntitledKeys(init_data.ExtractWrappedKeys()); + return HandleNewEntitledKeysInternal(init_data.ExtractWrappedKeys()); } CdmResponseType CdmLicense::RestoreOfflineLicense( @@ -794,16 +796,16 @@ CdmResponseType CdmLicense::RestoreOfflineLicense( } CdmResponseType status = - HandleKeyResponse(/* is_restore = */ true, license_response); + HandleKeyResponseInternal(/* is_restore = */ true, license_response); if (status != KEY_ADDED) return status; if (!license_renewal_response.empty()) { status = PrepareKeyUpdateReload(cdm_session); if (status != KEY_MESSAGE && status != NO_ERROR) return status; - status = HandleKeyUpdateResponse(/* is_renewal = */ true, - /* is_restore = */ true, - license_renewal_response); + status = HandleKeyUpdateResponseInternal(/* is_renewal = */ true, + /* is_restore = */ true, + license_renewal_response); if (status != KEY_ADDED) return status; } @@ -926,7 +928,7 @@ CdmResponseType CdmLicense::RestoreLicenseForRelease( if (!license.id().has_provider_session_token()) { const CdmResponseType result = - HandleKeyResponse(/* is_restore = */ false, license_response); + HandleKeyResponseInternal(/* is_restore = */ false, license_response); return result == KEY_ADDED ? CdmResponseType(NO_ERROR) : result; } @@ -1095,19 +1097,21 @@ CdmResponseType CdmLicense::HandleContentKeyResponse( LOGE("No content keys provided"); return CdmResponseType(NO_CONTENT_KEY); } - const CdmResponseType resp = crypto_session_->LoadLicense( + const CdmResponseType status = crypto_session_->LoadLicense( protocol_version_ <= video_widevine::VERSION_2_1 ? license_request_ : Sha512Hash(license_request_), session_key, msg, core_message, signature, kLicenseKeyTypeContent); - if (KEY_ADDED == resp) { - content_key_ids_.clear(); - for (const CryptoKey& key : license_keys) { - content_key_ids_.insert(key.key_id()); - } - policy_engine_->SetLicense(license, is_restore); + if (status != KEY_ADDED) return status; + + content_key_ids_.clear(); + for (const CryptoKey& key : license_keys) { + // The CdmLicense handles content and operator session keys + // the same. + content_key_ids_.insert(key.key_id()); } - return resp; + policy_engine_->SetLicense(license, is_restore); + return CdmResponseType(KEY_ADDED); } CdmResponseType CdmLicense::HandleEntitlementKeyResponse( @@ -1119,27 +1123,26 @@ CdmResponseType CdmLicense::HandleEntitlementKeyResponse( LOGE("No entitlement keys provided"); return CdmResponseType(NO_CONTENT_KEY); } - const CdmResponseType resp = crypto_session_->LoadLicense( + const CdmResponseType status = crypto_session_->LoadLicense( protocol_version_ <= video_widevine::VERSION_2_1 ? license_request_ : Sha512Hash(license_request_), session_key, msg, core_message, signature, kLicenseKeyTypeEntitlement); - - if (KEY_ADDED != resp) { - return resp; - } + if (status != KEY_ADDED) return status; // Save the entitlement keys for future use to handle key changes, - // and for call to HandleNewEntitledKeys(). + // and for call to HandleNewEntitledKeysInternal(). for (const auto& key_container : license.key()) { if (key_container.type() != KeyContainer::ENTITLEMENT) continue; entitlement_key_ids_.insert(key_container.id()); } policy_engine_->SetLicense(license, is_restore); - return HandleNewEntitledKeys(request_entitled_keys_); + // Now load any entitled keys that were in the PSSH provided + // when generated the license request. + return HandleNewEntitledKeysInternal(request_entitled_keys_); } -CdmResponseType CdmLicense::HandleNewEntitledKeys( +CdmResponseType CdmLicense::HandleNewEntitledKeysInternal( const std::vector& packaged_entitled_keys) { std::vector entitled_keys; entitled_keys.reserve(packaged_entitled_keys.size()); @@ -1169,9 +1172,9 @@ CdmResponseType CdmLicense::HandleNewEntitledKeys( entitled_keys.push_back(std::move(entitled_key)); } - const CdmResponseType resp = + const CdmResponseType status = crypto_session_->LoadEntitledContentKeys(entitled_keys); - if (resp != KEY_ADDED) return resp; + if (status != KEY_ADDED) return status; // Loaded entitled keys can be accessed like regular content keys // by the license. @@ -1201,5 +1204,4 @@ bool CdmLicense::SetTypeAndId(CdmLicenseType license_type, content_id->set_request_id(request_id); return true; } - } // namespace wvcdm diff --git a/core/src/license_protocol.proto b/core/src/license_protocol.proto index 748b5860..2f07f052 100644 --- a/core/src/license_protocol.proto +++ b/core/src/license_protocol.proto @@ -1192,8 +1192,22 @@ message ClientIdentification { } message ClientCredentials { + // Deprecated. Use credential_type instead. optional TokenType type = 1 [default = KEYBOX]; optional bytes token = 2; + + // Additional types of credentials that may be present in the client + // identification. + enum CredentialType { + CREDENTIAL_TYPE_UNKNOWN = 0; + // CBOR format used by the Provisioning 4.0 phase 3 uploading model. + CREDENTIAL_TYPE_BCC_SIGNATURE_CBOR = 1; + // PKCS7 format, used by Provisioning 4.0 signing model. + CREDENTIAL_TYPE_BCC_SIGNATURE_PKCS7 = 2; + } + + // The type of the token. + optional CredentialType credential_type = 3; } // Type of factory-provisioned device root of trust. Optional. diff --git a/core/src/oemcrypto_adapter_static.cpp b/core/src/oemcrypto_adapter_static.cpp index e34114b5..1832a4c2 100644 --- a/core/src/oemcrypto_adapter_static.cpp +++ b/core/src/oemcrypto_adapter_static.cpp @@ -224,6 +224,13 @@ OEMCryptoResult OEMCrypto_GetBCCType(RequestedSecurityLevel level, (void)level; return ::OEMCrypto_GetBCCType(bcc_type); } + +OEMCryptoResult OEMCrypto_GetBCCSignatureType( + RequestedSecurityLevel level, + OEMCrypto_BCCSignatureType* bcc_signature_type) { + (void)level; + return ::OEMCrypto_GetBCCSignatureType(bcc_signature_type); +} } // namespace wvcdm // Provide default implementation of L3-only functions. WEAK allows them to be diff --git a/core/src/wv_cdm_types.cpp b/core/src/wv_cdm_types.cpp index 44a7af20..a458d0b8 100644 --- a/core/src/wv_cdm_types.cpp +++ b/core/src/wv_cdm_types.cpp @@ -889,6 +889,8 @@ const char* CdmResponseEnumToString(CdmResponseEnum cdm_response_enum) { return "SESSION_NOT_FOUND_GENERIC_CRYPTO"; case SESSION_NOT_FOUND_24: return "SESSION_NOT_FOUND_24"; + case GET_BCC_SIGNATURE_TYPE_ERROR: + return "GET_BCC_SIGNATURE_TYPE_ERROR"; } return UnknownValueRep(cdm_response_enum); } @@ -1075,4 +1077,18 @@ const char* OemCryptoResultToString(OEMCryptoResult result) { return UnknownValueRep(result); } +const char* OemCryptoBccSignatureTypeToString(OEMCrypto_BCCSignatureType type) { + switch (type) { + case OEMCrypto_BCCSigType_Unknown: + return "Unknown"; + case OEMCrypto_BCCSigType_CBOR: + return "CBOR"; + case OEMCrypto_BCCSigType_PKCS7: + return "PKCS7"; + case OEMCrypto_BCCSigType_Keybox: + return "Keybox"; + } + return UnknownValueRep(type); +} + } // namespace wvcdm diff --git a/core/test/certificate_provisioning_unittest.cpp b/core/test/certificate_provisioning_unittest.cpp index 01328861..fcc62117 100644 --- a/core/test/certificate_provisioning_unittest.cpp +++ b/core/test/certificate_provisioning_unittest.cpp @@ -131,6 +131,7 @@ namespace wvcdm { using ::testing::_; using ::testing::ByMove; using ::testing::DoAll; +using ::testing::HasSubstr; using ::testing::NiceMock; using ::testing::NotNull; using ::testing::Return; @@ -468,7 +469,7 @@ TEST_P(CertificateProvisioningTest, ProvisioningResponseSuccess) { .WillOnce(DoAll(SaveArg<0>(&stored_certificate), ReturnArg<1>())); MockFileSystem file_system; - EXPECT_CALL(file_system, Open(StrEq(wvutil::kLegacyCertificateFileName), _)) + EXPECT_CALL(file_system, Open(HasSubstr(wvutil::kLegacyCertificateFileName), _)) .Times(1) .WillOnce(Return(ByMove(std::unique_ptr(file)))); diff --git a/core/test/core_integration_test.cpp b/core/test/core_integration_test.cpp index 1f716b41..ebcbae1f 100644 --- a/core/test/core_integration_test.cpp +++ b/core/test/core_integration_test.cpp @@ -145,8 +145,11 @@ TEST_F(CoreIntegrationTest, ProvisioningStableSpoidTest) { cdm_engine_.QueryStatus(kLevelDefault, QUERY_KEY_SECURITY_LEVEL, &level) .code()); - ASSERT_TRUE(level == QUERY_VALUE_SECURITY_LEVEL_L1 || - level == QUERY_VALUE_SECURITY_LEVEL_L3) + if (level == QUERY_VALUE_SECURITY_LEVEL_L3) { + GTEST_SKIP(); // SPOID is not expected to be stable for L3 + } + + ASSERT_TRUE(level == QUERY_VALUE_SECURITY_LEVEL_L1) << "Unknown security level: " << level; CdmSecurityLevel security_level = level == QUERY_VALUE_SECURITY_LEVEL_L1 diff --git a/core/test/duration_use_case_test.cpp b/core/test/duration_use_case_test.cpp index da6d2765..61592770 100644 --- a/core/test/duration_use_case_test.cpp +++ b/core/test/duration_use_case_test.cpp @@ -1046,9 +1046,12 @@ class CdmUseCase_LicenseWithRenewal : public RenewalTest { initial_policy_.renewal_recovery_duration; } - void SetUp() override { + void SetUp() override final { RenewalTest::SetUp(); - if(Test::IsSkipped()) return; + if (Test::IsSkipped()) return; + if (!wvoec::global_features.usage_table) { + GTEST_SKIP() << "Usage tables are not supported."; + } const uint64_t next_renewal = start_of_playback_ + initial_policy_.renewal_delay; // Allow playback within the initial renewal window. @@ -1252,9 +1255,12 @@ class CdmUseCase_LicenseWithRenewalPlayback : public RenewalTest { initial_policy_.renewal_recovery_duration; } - void SetUp() override { + void SetUp() override final { RenewalTest::SetUp(); - if(Test::IsSkipped()) return; + if (Test::IsSkipped()) return; + if (!wvoec::global_features.usage_table) { + GTEST_SKIP() << "Usage tables are not supported."; + } uint64_t next_renewal = start_of_playback_ + initial_policy_.renewal_delay; // Allow playback within the initial renewal window. SleepUntil(start_of_playback_); @@ -1466,6 +1472,14 @@ class CdmUseCase_LimitedDurationLicense : public RenewalTest { renewal_load_time_ = start_of_playback_ + renewal_delay_ + renewal_recovery_ - 1; } + + void SetUp() override final { + RenewalTest::SetUp(); + if (!wvoec::global_features.usage_table) { + GTEST_SKIP() << "Usage tables are not supported."; + } + } + uint64_t renewal_delay_; uint64_t renewal_load_time_; uint64_t renewal_recovery_; @@ -1718,9 +1732,12 @@ class CdmUseCase_Heartbeat : public RenewalTest { initial_policy_.renewal_recovery_duration; } - void SetUp() override { + void SetUp() override final { RenewalTest::SetUp(); - if(Test::IsSkipped()) return; + if (Test::IsSkipped()) return; + if (!wvoec::global_features.usage_table) { + GTEST_SKIP() << "Usage tables are not supported."; + } const uint64_t next_renewal = start_of_playback_ + initial_policy_.renewal_delay; // Allow playback within the initial renewal window. @@ -1818,6 +1835,13 @@ class CdmUseCase_LicenseDuration : public CdmDurationTest { timer_limits_.soft_enforce_playback_duration = false; timer_limits_.total_playback_duration_seconds = 40u; } + + void SetUp() override final { + CdmDurationTest::SetUp(); + if (!wvoec::global_features.usage_table) { + GTEST_SKIP() << "Usage tables are not supported."; + } + } }; // Playback within rental duration. @@ -1876,6 +1900,13 @@ class CdmUseCase_InfiniteRenewal : public RenewalTest { timer_limits_.rental_duration_seconds = 50u; timer_limits_.total_playback_duration_seconds = 0; } + + void SetUp() override final { + RenewalTest::SetUp(); + if (!wvoec::global_features.usage_table) { + GTEST_SKIP() << "Usage tables are not supported."; + } + } }; // The renewal interval is infinite. We never need to load the renewal. @@ -1918,6 +1949,13 @@ class CdmUseCase_LicenseDurationWithRenewal : public RenewalTest { timer_limits_.rental_duration_seconds = 30u; timer_limits_.total_playback_duration_seconds = 0; } + + void SetUp() override final { + RenewalTest::SetUp(); + if (!wvoec::global_features.usage_table) { + GTEST_SKIP() << "Usage tables are not supported."; + } + } }; // If we do load the renewal, we may continue playback past original window. diff --git a/core/test/initialization_data_unittest.cpp b/core/test/initialization_data_unittest.cpp index 57d2265f..7d072853 100644 --- a/core/test/initialization_data_unittest.cpp +++ b/core/test/initialization_data_unittest.cpp @@ -869,4 +869,27 @@ INSTANTIATE_TEST_SUITE_P( HlsAttributeVariant(kHlsAttributeListInvalidIv, HLS_IV_ATTRIBUTE, kHlsTestHexValueWithOddBytes, false))); +TEST_F(HlsParseTest, BadHlsData_InvalidContentId) { + std::ostringstream hls_uri_json_stream; + hls_uri_json_stream << "{"; + hls_uri_json_stream << "\"provider\": \"HlsParseTest.BadHlsData\", "; + // Intentionally bad Base64 content ID. + hls_uri_json_stream << "\"content_id\": \"$$$$\", "; + hls_uri_json_stream << "\"key_ids\": [\"00000000000000000000000000000000\"]"; + hls_uri_json_stream << "}"; + const std::string hls_uri_json = hls_uri_json_stream.str(); + + std::ostringstream hls_stream; + hls_stream << "#EXT-X-KEY:"; + hls_stream << "METHOD=AES-128,"; + hls_stream << "URI=\"data:text/plain;base64," + << wvutil::Base64Encode(hls_uri_json) << "\","; + hls_stream << "IV=0x00000000000000000000000000000000,"; + hls_stream << "KEYFORMAT=\"com.widevine\","; + hls_stream << "KEYFORMATVERSIONS=\"1\""; + const std::string hls_data = hls_stream.str(); + // std::cout << "HLS Data:" << std::endl << hls_data << std::endl; + InitializationData init_data(HLS_INIT_DATA_FORMAT, hls_data); + EXPECT_TRUE(init_data.is_hls()); +} } // namespace wvcdm diff --git a/core/test/license_holder.cpp b/core/test/license_holder.cpp index 2f8ae2c5..176054b0 100644 --- a/core/test/license_holder.cpp +++ b/core/test/license_holder.cpp @@ -7,6 +7,7 @@ #include "license_request.h" #include "message_dumper.h" #include "oec_device_features.h" +#include "properties.h" #include "test_base.h" namespace wvcdm { @@ -126,10 +127,27 @@ void LicenseHolder::GenerateAndPostReleaseRequest( const std::string init_data_string = MakePSSH(pssh); const InitializationData init_data(kCencMimeType, init_data_string); init_data.DumpToLogs(); - const CdmResponseType result = cdm_engine_->GenerateKeyRequest( - session_id_, key_set_id_, init_data, kLicenseTypeRelease, - empty_app_parameters, &request); + + CdmSessionId session_id; + CdmKeySetId key_set_id; + CdmResponseType result; + // For Android when key set IDs are used, the key set ID passed in should have + // a value and the session ID should be empty. + if (!Properties::AlwaysUseKeySetIds()) { + key_set_id = key_set_id_; + result = cdm_engine_->OpenKeySetSession(key_set_id_, nullptr, nullptr); + ASSERT_EQ(NO_ERROR, result) << "Failed for " << content_id(); + // For CE CDM, we only need the session ID to be valid. + } else { + session_id = session_id_; + } + result = cdm_engine_->GenerateKeyRequest(session_id, key_set_id, init_data, + kLicenseTypeRelease, + empty_app_parameters, &request); ASSERT_EQ(KEY_MESSAGE, result) << "Failed for " << content_id(); + if (!Properties::AlwaysUseKeySetIds()) { + cdm_engine_->CloseKeySetSession(key_set_id_); + } if (config_.dump_golden_data()) { // TODO (b/295956275) vickymin: write DumpReleaseRequest function // MessageDumper::DumpReleaseRequest(request); diff --git a/core/test/license_unittest.cpp b/core/test/license_unittest.cpp index b8ecf17a..4ae1e816 100644 --- a/core/test/license_unittest.cpp +++ b/core/test/license_unittest.cpp @@ -201,12 +201,15 @@ class CdmLicenseTestPeer : public CdmLicense { CdmLicenseTestPeer(const CdmSessionId& session_id, wvutil::Clock* clock) : CdmLicense(session_id, clock) {} - using CdmLicense::HandleNewEntitledKeys; + CdmResponseType HandleNewEntitledKeys( + const std::vector& packaged_entitled_keys) { + return HandleNewEntitledKeysForTest(packaged_entitled_keys); + } - void set_entitlement_keys(const License& license) { + void SetEntitlementKeys(const License& license) { for (const auto& key_container : license.key()) { if (key_container.type() != KeyContainer::ENTITLEMENT) continue; - entitlement_key_ids_.insert(key_container.id()); + InsertEntitlementKeyIdForTest(key_container.id()); } } }; @@ -608,10 +611,11 @@ TEST_P(CdmLicenseEntitledKeyTest, LoadsEntitledKeys) { // Set up the CdmLicense with the mocks and fake entitlement key ASSERT_TRUE(cdm_license_->Init(true, kDefaultServiceCertificate, crypto_session_.get(), policy_engine_.get())); - cdm_license_->set_entitlement_keys(entitlement_license); + cdm_license_->SetEntitlementKeys(entitlement_license); // Call the function under test and check its return value - CdmResponseType ret = cdm_license_->HandleNewEntitledKeys(entitled_keys); + const CdmResponseType ret = + cdm_license_->HandleNewEntitledKeys(entitled_keys); if (variant.should_succeed) { EXPECT_EQ(KEY_ADDED, ret); diff --git a/core/test/policy_integration_test.cpp b/core/test/policy_integration_test.cpp index 814b6d07..0249aafe 100644 --- a/core/test/policy_integration_test.cpp +++ b/core/test/policy_integration_test.cpp @@ -19,6 +19,7 @@ #include "license_holder.h" #include "log.h" #include "oec_device_features.h" +#include "properties.h" #include "provisioning_holder.h" #include "test_base.h" #include "test_printers.h" @@ -193,13 +194,24 @@ TEST_F(CorePIGTest, LicenseRelease1) { ASSERT_NO_FATAL_FAILURE(holder.FetchLicense()); ASSERT_NO_FATAL_FAILURE(holder.LoadLicense()); EXPECT_EQ(NO_ERROR, holder.Decrypt(key_id)); + // For Android where AlwaysUseKeySetIds() is false, the CDM engine generates + // a session separately. Thus, we close the session and only for CE CDM reopen + // it for the license release. + ASSERT_NO_FATAL_FAILURE(holder.CloseSession()); + if (Properties::AlwaysUseKeySetIds()) { + ASSERT_NO_FATAL_FAILURE(holder.OpenSession()); + ASSERT_NO_FATAL_FAILURE(holder.ReloadLicense()); + } ASSERT_NO_FATAL_FAILURE(holder.GenerateAndPostReleaseRequest( "CDM_UnlimitedStreaming_can_persist")); EXPECT_NE(NO_ERROR, holder.Decrypt(key_id)); ASSERT_NO_FATAL_FAILURE(holder.FetchRelease()); ASSERT_NO_FATAL_FAILURE(holder.LoadRelease()); EXPECT_NE(NO_ERROR, holder.Decrypt(key_id)); - ASSERT_NO_FATAL_FAILURE(holder.CloseSession()); + // For CE CDM, we can close the session after we have gotten the release. + if (Properties::AlwaysUseKeySetIds()) { + ASSERT_NO_FATAL_FAILURE(holder.CloseSession()); + } } /** @@ -219,11 +231,22 @@ TEST_F(CorePIGTest, LicenseRelease2) { ASSERT_NO_FATAL_FAILURE(holder.FetchLicense()); ASSERT_NO_FATAL_FAILURE(holder.LoadLicense()); wvutil::TestSleep::Sleep(10); + // For Android where AlwaysUseKeySetIds() is false, the CDM engine generates + // a session separately. Thus, we close the session and only for CE CDM reopen + // it for the license release. + ASSERT_NO_FATAL_FAILURE(holder.CloseSession()); + if (Properties::AlwaysUseKeySetIds()) { + ASSERT_NO_FATAL_FAILURE(holder.OpenSession()); + ASSERT_NO_FATAL_FAILURE(holder.ReloadLicense()); + } ASSERT_NO_FATAL_FAILURE(holder.GenerateAndPostReleaseRequest( "CDM_UnlimitedStreaming_can_persist")); ASSERT_NO_FATAL_FAILURE(holder.FetchRelease()); ASSERT_NO_FATAL_FAILURE(holder.LoadRelease()); - ASSERT_NO_FATAL_FAILURE(holder.CloseSession()); + // For CE CDM, we can close the session after we have gotten the release. + if (Properties::AlwaysUseKeySetIds()) { + ASSERT_NO_FATAL_FAILURE(holder.CloseSession()); + } } TEST_F(CorePIGTest, CastReceiverProvisioningUsingCdm) { diff --git a/oemcrypto/include/OEMCryptoCENC.h b/oemcrypto/include/OEMCryptoCENC.h index a234c35a..559c42aa 100644 --- a/oemcrypto/include/OEMCryptoCENC.h +++ b/oemcrypto/include/OEMCryptoCENC.h @@ -3,7 +3,7 @@ // License Agreement. /** - * @mainpage OEMCrypto API v19.3 + * @mainpage OEMCrypto API v19.4 * * OEMCrypto is the low level library implemented by the OEM to provide key and * content protection, usually in a separate secure memory or process space. The @@ -527,6 +527,7 @@ typedef enum OEMCrypto_ProvisioningMethod { /** Return value for OEMCrypto_GetBCCType(). + Provisioning 4.0 only. */ typedef enum OEMCrypto_BCCType { // Boot certificate chain in CBOR format. @@ -535,6 +536,21 @@ typedef enum OEMCrypto_BCCType { OEMCrypto_X509 = 1, } OEMCrypto_BCCType; +/** + Return value for OEMCrypto_GetBCCSignatureType(). + Provisioning 4.0 only. + */ +typedef enum OEMCrypto_BCCSignatureType { + // BCC signature is not supported. + OEMCrypto_BCCSigType_Unknown = 0, + // CBOR format used by the Provisioning 4.0 phase 3 uploading model. + OEMCrypto_BCCSigType_CBOR = 1, + // PKCS7 format, used by signing model. + OEMCrypto_BCCSigType_PKCS7 = 2, + // Signature generated by Keybox, reserved. + OEMCrypto_BCCSigType_Keybox = 3, +} OEMCrypto_BCCSignatureType; + /** Return value for OEMCrypto_GetWatermarkingSupport(). */ @@ -747,6 +763,7 @@ typedef enum OEMCrypto_SignatureHashAlgorithm { #define OEMCrypto_MarkOfflineSession _oecc153 #define OEMCrypto_WrapClearPrivateKey _oecc154 #define OEMCrypto_SetSessionUsage _oecc155 +#define OEMCrypto_GetBCCSignatureType _oecc156 // clang-format on /// @addtogroup initcontrol @@ -3256,9 +3273,77 @@ OEMCryptoResult OEMCrypto_GetDeviceID(uint8_t* device_id, * This method is new in API version 19.2. */ OEMCryptoResult OEMCrypto_WrapClearPrivateKey( - const uint8_t* clear_private_key_bytes, size_t clear_private_key_length, + const uint8_t* clear_private_key, size_t clear_private_key_length, uint8_t* wrapped_private_key, size_t* wrapped_private_key_length); +/** + * This function is for OEMCrypto to tell the layer above what type of BCC + * signature it uses. This function is for Provisioning 4.0 signing model, or + * Provisioning 4.0 uploading model with Phase 3 enabled. + * + * The returned bcc_signature_type corresponds to the + * additional_signature parameter in the OEMCrypto_GetBootCertificateChain() + * function and specifies the type of signature returned by that function. + * + * Valid values for OEMCrypto_BCCSignatureType are: + * + * OEMCrypto_BCCSigType_CBOR, required by Android Remote Key Provisioning + * phase 3. The signature is CBOR encoded, which shall follow the IETF CBOR Web + * Token (CWT) specification. The format is described in Android + * generateCertificateRequestV2.cddl: + * UdsCerts = { + * * SignerName => UdsCertChain + * } + * + * SignerName = tstr + * + * UdsCertChain = [ + * + X509Certificate ; Root -> ... -> Leaf. + * ; "Root" is the vendor self-signed cert, + * ; "Leaf" contains Unique Device Secret public key. + * ; It's recommended to have at least 3 certificates + * ; in the chain. The Root certificate is recommended + * ; to be generated in an air-gapped, HSM-based secure + * ; environment. + * ] + * + * ; A bstr containing a DER-encoded X.509 certificate (RSA, NIST P-curve, or + * ; EdDSA) + * X509Certificate = bstr + * + * OEMCrypto_BCCSigType_PKCS7, the signature is a PKCS#7 format containing a + * chain of X.509 certificates encoded with DER. The leaf certificate of the + * chain contains the Unique Device Secret public key (UDS_Pub). The signature + * shall be generated off-device for enhanced security. + * + * OEMCrypto_BCCSigType_Keybox, the signature is generated using Keybox on the + * device. Please work with your Widevine Partner Engineer to ensure the + * signature format meets the required specifications. + * + * OEMCrypto_BCCSigTypeUnknown, the signature is not supported. + * + * @param[out] bcc_signature_type: the type of the boot certificate chain. + * + * @retval OEMCrypto_SUCCESS + * @retval OEMCrypto_ERROR_INVALID_CONTEXT if any pointer is NULL. + * @retval OEMCrypto_ERROR_NOT_IMPLEMENTED: if the provisioning method is not + * Provisioning 4.0 or Provisioning 4.0 Phase 3 is not supported. The + * output bcc_signature_type may be set to OEMCrypto_BCCSigTypeUnknown + * in this case. + * @retval OEMCrypto_ERROR_UNKNOWN_FAILURE any other failure. + * + * @threading + * This is a "Property Function" and may be called simultaneously with any + * other property function or session function, but not any initialization or + * usage table function, as if the CDM holds a read lock on the OEMCrypto + * system. + * + * @version + * This method is new API version 19.4. + */ +OEMCryptoResult OEMCrypto_GetBCCSignatureType( + OEMCrypto_BCCSignatureType* bcc_signature_type); + /// @} /// @addtogroup keybox diff --git a/oemcrypto/odk/include/core_message_features.h b/oemcrypto/odk/include/core_message_features.h index 93ee31f6..1d87cd95 100644 --- a/oemcrypto/odk/include/core_message_features.h +++ b/oemcrypto/odk/include/core_message_features.h @@ -26,9 +26,9 @@ struct CoreMessageFeatures { // This is the published version of the ODK Core Message library. The default // behavior is for the server to restrict messages to at most this version - // number. The default is 19.3. + // number. The default is 19.4. uint32_t maximum_major_version = 19; - uint32_t maximum_minor_version = 3; + uint32_t maximum_minor_version = 4; bool operator==(const CoreMessageFeatures &other) const; bool operator!=(const CoreMessageFeatures &other) const { diff --git a/oemcrypto/odk/include/odk_structs.h b/oemcrypto/odk/include/odk_structs.h index 10efc8d4..6e1edc57 100644 --- a/oemcrypto/odk/include/odk_structs.h +++ b/oemcrypto/odk/include/odk_structs.h @@ -16,10 +16,10 @@ extern "C" { /* The version of this library. */ #define ODK_MAJOR_VERSION 19 -#define ODK_MINOR_VERSION 3 +#define ODK_MINOR_VERSION 4 /* ODK Version string. Date changed automatically on each release. */ -#define ODK_RELEASE_DATE "ODK v19.3 2024-09-04" +#define ODK_RELEASE_DATE "ODK v19.4 2024-11-04" /* The lowest version number for an ODK message. */ #define ODK_FIRST_VERSION 16 diff --git a/oemcrypto/odk/src/core_message_features.cpp b/oemcrypto/odk/src/core_message_features.cpp index d0e6b282..0b4cfb61 100644 --- a/oemcrypto/odk/src/core_message_features.cpp +++ b/oemcrypto/odk/src/core_message_features.cpp @@ -33,7 +33,7 @@ CoreMessageFeatures CoreMessageFeatures::DefaultFeatures( features.maximum_minor_version = 4; // 18.4 break; case 19: - features.maximum_minor_version = 3; // 19.3 + features.maximum_minor_version = 4; // 19.4 break; default: features.maximum_minor_version = 0; diff --git a/oemcrypto/odk/src/odk_timer.c b/oemcrypto/odk/src/odk_timer.c index 822b2665..ec9e25e8 100644 --- a/oemcrypto/odk/src/odk_timer.c +++ b/oemcrypto/odk/src/odk_timer.c @@ -277,7 +277,7 @@ OEMCryptoResult ODK_InitializeSessionValues(ODK_TimerLimits* timer_limits, nonce_values->api_minor_version = 4; break; case 19: - nonce_values->api_minor_version = 3; + nonce_values->api_minor_version = 4; 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 b0cc6326..7a0d6459 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, 3}, + {19, ODK_MAJOR_VERSION, ODK_MINOR_VERSION, 19, 4}, // 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}, @@ -1289,6 +1289,7 @@ std::vector TestCases() { {ODK_MAJOR_VERSION, 19, 1, 19, 1}, {ODK_MAJOR_VERSION, 19, 2, 19, 2}, {ODK_MAJOR_VERSION, 19, 3, 19, 3}, + {ODK_MAJOR_VERSION, 19, 4, 19, 4}, {0, 16, 3, 16, 3}, {0, 16, 4, 16, 4}, {0, 16, 5, 16, 5}, @@ -1300,6 +1301,7 @@ std::vector TestCases() { {0, 19, 1, 19, 1}, {0, 19, 2, 19, 2}, {0, 19, 3, 19, 3}, + {0, 19, 4, 19, 4}, }; return test_cases; } diff --git a/oemcrypto/test/GEN_api_lock_file.c b/oemcrypto/test/GEN_api_lock_file.c index 63165bdb..59852067 100644 --- a/oemcrypto/test/GEN_api_lock_file.c +++ b/oemcrypto/test/GEN_api_lock_file.c @@ -438,3 +438,6 @@ OEMCryptoResult _oecc153(OEMCrypto_SESSION session); // OEMCrypto_SetSessionUsage defined in v18.7 OEMCryptoResult _oecc155(OEMCrypto_SESSION session, uint32_t intent, uint32_t mode); + +// OEMCrypto_GetBCCSignatureType defined in v19.4 +OEMCryptoResult _oecc156(OEMCrypto_BCCSignatureType* bcc_signature_type); diff --git a/oemcrypto/test/oec_decrypt_fallback_chain.cpp b/oemcrypto/test/oec_decrypt_fallback_chain.cpp index fe303fc6..6cb7aac0 100644 --- a/oemcrypto/test/oec_decrypt_fallback_chain.cpp +++ b/oemcrypto/test/oec_decrypt_fallback_chain.cpp @@ -17,6 +17,7 @@ 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: @@ -98,11 +99,6 @@ 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; @@ -148,11 +144,6 @@ 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; @@ -176,11 +167,6 @@ 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_session_util.h b/oemcrypto/test/oec_session_util.h index 41419427..3f2ee9a0 100644 --- a/oemcrypto/test/oec_session_util.h +++ b/oemcrypto/test/oec_session_util.h @@ -287,6 +287,7 @@ class ProvisioningRoundTrip OEMCryptoResult LoadResponse() override { return LoadResponse(session_); } OEMCryptoResult LoadResponse(Session* session) override; void VerifyLoadFailed(); + const std::vector& request() { return request_; } const std::vector& encoded_rsa_key() { return encoded_rsa_key_; } const std::vector& wrapped_rsa_key() { return wrapped_rsa_key_; } void set_allowed_schemes(uint32_t allowed_schemes) { diff --git a/oemcrypto/test/oemcrypto_basic_test.cpp b/oemcrypto/test/oemcrypto_basic_test.cpp index 36dccb95..879aab87 100644 --- a/oemcrypto/test/oemcrypto_basic_test.cpp +++ b/oemcrypto/test/oemcrypto_basic_test.cpp @@ -73,6 +73,76 @@ int32_t JsmnAncestorCount(const std::vector& tokens, } return count; } + +const char* BoolName(bool value) { return value ? "true" : "false"; } +const char* SecurityLevelName(OEMCrypto_Security_Level value) { + switch (value) { + case OEMCrypto_Level_Unknown: + return "OEMCrypto_Level_Unknown"; + case OEMCrypto_Level1: + return "OEMCrypto_Level1"; + case OEMCrypto_Level2: + return "OEMCrypto_Level2"; + case OEMCrypto_Level3: + return "OEMCrypto_Level3"; + } + // Not reachable unless the enum value is invalid + return ""; +} +const char* HDCPCapabilityName(OEMCrypto_HDCP_Capability value) { + switch (value) { + case HDCP_NONE: + return "HDCP_NONE"; + case HDCP_V1: + return "HDCP_V1"; + case HDCP_V1_0: + return "HDCP_V1_0"; + case HDCP_V1_1: + return "HDCP_V1_1"; + case HDCP_V1_2: + return "HDCP_V1_2"; + case HDCP_V1_3: + return "HDCP_V1_3"; + case HDCP_V1_4: + return "HDCP_V1_4"; + case HDCP_V2: + return "HDCP_V2"; + case HDCP_V2_1: + return "HDCP_V2_1"; + case HDCP_V2_2: + return "HDCP_V2_2"; + case HDCP_V2_3: + return "HDCP_V2_3"; + case HDCP_NO_DIGITAL_OUTPUT: + return "HDCP_NO_DIGITAL_OUTPUT"; + } + // Not reachable unless the enum value is invalid + return ""; +} +const char* WatermarkingSupportName(OEMCrypto_WatermarkingSupport value) { + switch (value) { + case OEMCrypto_WatermarkingError: + return "OEMCrypto_WatermarkingError"; + case OEMCrypto_WatermarkingNotSupported: + return "OEMCrypto_WatermarkingNotSupported"; + case OEMCrypto_WatermarkingConfigurable: + return "OEMCrypto_WatermarkingConfigurable"; + case OEMCrypto_WatermarkingAlwaysOn: + return "OEMCrypto_WatermarkingAlwaysOn"; + } + // Not reachable unless the enum value is invalid + return ""; +} +const char* DTCP2CapabiityName(OEMCrypto_DTCP2_Capability value) { + switch (value) { + case OEMCrypto_NO_DTCP2: + return "OEMCrypto_NO_DTCP2"; + case OEMCrypto_DTCP2_V1: + return "OEMCrypto_DTCP2_V1"; + } + // Not reachable unless the enum value is invalid + return ""; +} } // namespace void OEMCryptoClientTest::SetUp() { @@ -148,6 +218,11 @@ OEMCryptoResult OEMCryptoClientTest::CopyBuffer( dest_buffer_descriptor, subsample_flags); } +void OEMCryptoClientTest::RecordWvProperty(const std::string& key, + const std::string& value) { + RecordProperty("widevine_metadata_oec_" + key, value); +} + const char* HDCPCapabilityAsString(OEMCrypto_HDCP_Capability value) { switch (value) { case HDCP_NONE: @@ -174,9 +249,9 @@ const char* HDCPCapabilityAsString(OEMCrypto_HDCP_Capability value) { return "HDCP version 2.3"; case HDCP_NO_DIGITAL_OUTPUT: return "No HDCP device attached/using local display with secure path"; - default: - return ""; } + // Not reachable unless the enum value is invalid + return ""; } // Return a printable string from data. If all the characters are printable, @@ -242,40 +317,55 @@ TEST_F(OEMCryptoClientTest, FreeUnallocatedSecureBufferNoFailure) { */ TEST_F(OEMCryptoClientTest, VersionNumber) { const std::string log_message = - "OEMCrypto unit tests for API 19.3. Tests last updated 2024-09-04"; + "OEMCrypto unit tests for API 19.4. Tests last updated 2024-11-04"; cout << " " << log_message << "\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, 3); + EXPECT_EQ(ODK_MINOR_VERSION, 4); EXPECT_EQ(kCurrentAPI, static_cast(ODK_MAJOR_VERSION)); + RecordWvProperty("test_major_version", std::to_string(ODK_MAJOR_VERSION)); + RecordWvProperty("test_minor_version", std::to_string(ODK_MINOR_VERSION)); + OEMCrypto_Security_Level level = OEMCrypto_SecurityLevel(); EXPECT_GT(level, OEMCrypto_Level_Unknown); EXPECT_LE(level, OEMCrypto_Level3); cout << " OEMCrypto Security Level is L" << level << endl; + RecordWvProperty("security_level", SecurityLevelName(level)); + uint32_t version = OEMCrypto_APIVersion(); uint32_t minor_version = OEMCrypto_MinorAPIVersion(); cout << " OEMCrypto API version is " << version << "." << minor_version << endl; + RecordWvProperty("major_version", std::to_string(version)); + RecordWvProperty("minor_version", std::to_string(minor_version)); + cout << " OEMCrypto Device ID is '" << GetDeviceId() << "'" << endl; - if (OEMCrypto_SupportsUsageTable()) { + const bool supports_usage_tables = OEMCrypto_SupportsUsageTable(); + if (supports_usage_tables) { cout << " OEMCrypto supports usage tables" << endl; } else { cout << " OEMCrypto does not support usage tables" << endl; } + RecordWvProperty("supports_usage_tables", BoolName(supports_usage_tables)); + if (version >= 15) { const uint32_t tier = OEMCrypto_ResourceRatingTier(); cout << " Resource Rating Tier: " << tier << endl; + RecordWvProperty("resource_rating_tier", std::to_string(tier)); } + if (version >= 17) { OEMCryptoResult sts = OEMCrypto_ProductionReady(); if (sts != OEMCrypto_SUCCESS) { LOGW("Device is not production ready, returns %d", sts); } + RecordWvProperty("is_production_ready", BoolName(sts == OEMCrypto_SUCCESS)); + std::string build_info; size_t buf_length = 0; sts = OEMCrypto_BuildInformation(&build_info[0], &buf_length); @@ -287,6 +377,7 @@ TEST_F(OEMCryptoClientTest, VersionNumber) { if (build_info.size() != buf_length) { build_info.resize(buf_length); } + RecordWvProperty("build_info", build_info); const std::string comma = ","; const std::string pretty_comma = ",\n "; std::string::size_type pos = 0; @@ -295,9 +386,12 @@ TEST_F(OEMCryptoClientTest, VersionNumber) { pos += pretty_comma.size(); } cout << " BuildInformation: " << build_info << endl; + OEMCrypto_WatermarkingSupport support = OEMCrypto_GetWatermarkingSupport(); cout << " WatermarkingSupport: " << support << endl; + RecordWvProperty("watermarking_support", WatermarkingSupportName(support)); } + ASSERT_GE(version, 8u); ASSERT_LE(version, kCurrentAPI); } @@ -317,8 +411,9 @@ TEST_F(OEMCryptoClientTest, ResourceRatingAPI15) { TEST_F(OEMCryptoClientTest, ProvisioningDeclaredAPI12) { OEMCrypto_ProvisioningMethod provisioning_method = OEMCrypto_GetProvisioningMethod(); - cout << " Provisioning method = " - << ProvisioningMethodName(provisioning_method) << endl; + const char* const name = ProvisioningMethodName(provisioning_method); + cout << " Provisioning method = " << name << endl; + RecordWvProperty("provisioning_method", name); ASSERT_NE(OEMCrypto_ProvisioningError, provisioning_method); } @@ -331,6 +426,8 @@ TEST_F(OEMCryptoClientTest, CheckHDCPCapabilityAPI09) { static_cast(current), HDCPCapabilityAsString(current)); printf(" Maximum HDCP Capability: 0x%02x = %s.\n", static_cast(maximum), HDCPCapabilityAsString(maximum)); + RecordWvProperty("hdcp_current", HDCPCapabilityName(current)); + RecordWvProperty("hdcp_max", HDCPCapabilityName(maximum)); } TEST_F(OEMCryptoClientTest, CheckSRMCapabilityV13) { @@ -339,11 +436,15 @@ TEST_F(OEMCryptoClientTest, CheckSRMCapabilityV13) { OEMCryptoResult current_result = OEMCrypto_GetCurrentSRMVersion(&version); if (current_result == OEMCrypto_SUCCESS) { printf(" Current SRM Version: %d.\n", version); + RecordWvProperty("srm_supported", BoolName(true)); + RecordWvProperty("srm_version", std::to_string(version)); EXPECT_NE(OEMCrypto_SUCCESS, OEMCrypto_GetCurrentSRMVersion(nullptr)); } else if (current_result == OEMCrypto_LOCAL_DISPLAY_ONLY) { printf(" Current SRM Status: Local Display Only.\n"); + RecordWvProperty("srm_supported", BoolName(false)); } else { EXPECT_EQ(OEMCrypto_ERROR_NOT_IMPLEMENTED, current_result); + RecordWvProperty("srm_supported", BoolName(false)); } } @@ -586,6 +687,9 @@ TEST_F(OEMCryptoClientTest, CheckJsonBuildInformationAPI18) { {"is_debug", JSMN_PRIMITIVE}, }; + // The prefix of every field name when logged to RecordWvProperty. + const std::string kBuildInfoRecordPrefix = "build_info_"; + // A set of the required fields found when examining the // build information, use to verify all fields are present. std::set found_required_fields; @@ -612,11 +716,17 @@ TEST_F(OEMCryptoClientTest, CheckJsonBuildInformationAPI18) { << "Unexpected required field type: field = " << key << ", build_info = " << build_info; found_required_fields.insert(key); + RecordWvProperty(kBuildInfoRecordPrefix + key, + build_info.substr(value_token.start, + value_token.end - value_token.start)); } 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. + RecordWvProperty(kBuildInfoRecordPrefix + key, + build_info.substr(value_token.start, + value_token.end - value_token.start)); + } // Do not validate or record vendor fields. if (key == kSpecialCaseReeKey) { // Store the tokens of the "ree" field for additional validation. @@ -657,6 +767,11 @@ TEST_F(OEMCryptoClientTest, CheckJsonBuildInformationAPI18) { // If no "ree" field tokens, then end here. if (!has_ree_info) return; // Step 4a: Verify "ree" object scheme. + + // The prefix of every REE field name when logged to RecordWvProperty. + const std::string kReeBuildInfoRecordPrefix = + kBuildInfoRecordPrefix + kSpecialCaseReeKey + "_"; + ASSERT_FALSE(ree_tokens.empty()) << "REE field was specified, but contents were empty: build_info = " << build_info; @@ -686,7 +801,10 @@ TEST_F(OEMCryptoClientTest, CheckJsonBuildInformationAPI18) { << "Unexpected optional REE field type: ree_field = " << key << ", build_info = " << build_info; found_required_fields.insert(key); - } // Do not validate vendor fields. + RecordWvProperty(kReeBuildInfoRecordPrefix + key, + build_info.substr(value_token.start, + value_token.end - value_token.start)); + } // Do not validate or record vendor fields. // Skip potential nested tokens. i += JsmnAncestorCount(ree_tokens, i + 1); @@ -722,6 +840,7 @@ TEST_F(OEMCryptoClientTest, CheckMaxNumberOfSessionsAPI10) { OEMCryptoResult sts = OEMCrypto_GetMaxNumberOfSessions(&maximum); ASSERT_EQ(OEMCrypto_SUCCESS, sts); printf(" Max Number of Sessions: %zu.\n", maximum); + RecordWvProperty("max_number_of_sessions", std::to_string(maximum)); size_t required_max = GetResourceValue(kMaxConcurrentSession); ASSERT_GE(maximum, required_max); } @@ -729,6 +848,7 @@ TEST_F(OEMCryptoClientTest, CheckMaxNumberOfSessionsAPI10) { TEST_F(OEMCryptoClientTest, CheckUsageTableSizeAPI16) { const size_t maximum = OEMCrypto_MaximumUsageTableHeaderSize(); printf(" Max Usage Table Size: %zu.\n", maximum); + RecordWvProperty("max_usage_table_size", std::to_string(maximum)); // A maximum of 0 means the table is constrained by dynamic memory allocation. if (maximum > 0) { ASSERT_GE(maximum, RequiredUsageSize()); @@ -765,6 +885,7 @@ TEST_F(OEMCryptoClientTest, CheckDTCP2CapabilityAPI17) { "DTCP2 is supported.\n"); break; } + RecordWvProperty("dtcp2_capability", DTCP2CapabiityName(capability)); } // diff --git a/oemcrypto/test/oemcrypto_basic_test.h b/oemcrypto/test/oemcrypto_basic_test.h index 2cbe235b..303ee4a2 100644 --- a/oemcrypto/test/oemcrypto_basic_test.h +++ b/oemcrypto/test/oemcrypto_basic_test.h @@ -35,6 +35,7 @@ class OEMCryptoClientTest : public ::testing::Test, public SessionUtil { size_t input_buffer_size, const OEMCrypto_DestBufferDesc* dest_buffer_descriptor, uint8_t subsample_flags); + void RecordWvProperty(const std::string& key, const std::string& value); }; } // namespace wvoec diff --git a/oemcrypto/test/oemcrypto_cast_test.cpp b/oemcrypto/test/oemcrypto_cast_test.cpp index 2a6e0197..6cf2be36 100644 --- a/oemcrypto/test/oemcrypto_cast_test.cpp +++ b/oemcrypto/test/oemcrypto_cast_test.cpp @@ -5,8 +5,6 @@ #include "oemcrypto_cast_test.h" -#include "oemcrypto_usage_table_test.h" - using ::testing::Range; namespace wvoec { @@ -260,18 +258,8 @@ class OEMCryptoCastReceiverTest : public OEMCryptoLoadsCertificateAlternates { ASSERT_NO_FATAL_FAILURE(s.open()); ASSERT_NO_FATAL_FAILURE(s.LoadWrappedRsaDrmKey(wrapped_drm_key_)); - // The application will compute the SHA-1 Hash of the message, so this - // test must do that also. - uint8_t hash[SHA_DIGEST_LENGTH]; - if (!SHA1(message.data(), message.size(), hash)) { - dump_boringssl_error(); - FAIL() << "boringssl error creating SHA1 hash."; - } - - // The application will prepend the digest info to the hash. - // SHA-1 digest info prefix = 0x30 0x21 0x30 ... - vector digest = wvutil::a2b_hex("3021300906052b0e03021a05000414"); - digest.insert(digest.end(), hash, hash + SHA_DIGEST_LENGTH); + vector digest; + ASSERT_NO_FATAL_FAILURE(PrepareCastDigestedMessage(message, digest)); // OEMCrypto will apply the padding, and encrypt to generate the // signature. @@ -1019,5 +1007,6 @@ TEST_P(OEMCryptoSessionTestLoadCasKeysWithHDCP, CasOnlyLoadCasKeysAPI17) { } INSTANTIATE_TEST_SUITE_P(TestHDCP, OEMCryptoSessionTestLoadCasKeysWithHDCP, Range(1, 6)); + /// @} } // namespace wvoec diff --git a/oemcrypto/test/oemcrypto_cast_test.h b/oemcrypto/test/oemcrypto_cast_test.h index 3317fb7b..0aa04910 100644 --- a/oemcrypto/test/oemcrypto_cast_test.h +++ b/oemcrypto/test/oemcrypto_cast_test.h @@ -14,6 +14,7 @@ #include "OEMCryptoCENC.h" #include "oemcrypto_provisioning_test.h" #include "oemcrypto_session_tests_helper.h" +#include "oemcrypto_usage_table_test.h" namespace wvoec { @@ -22,6 +23,25 @@ const char* HDCPCapabilityAsString(OEMCrypto_HDCP_Capability value); // This test attempts to use alternate algorithms for loaded device certs. class OEMCryptoLoadsCertificateAlternates : public OEMCryptoLoadsCertificate { protected: + // The message to be signed by OEMCrypto_GenerateRSASignature() starts with a + // constant digest info prefix followed by a SHA-1 hash of the message. + void PrepareCastDigestedMessage(const std::vector& message, + std::vector& digest) { + // The application will compute the SHA-1 Hash of the message, so this + // test must do that also. + uint8_t hash[SHA_DIGEST_LENGTH]; + if (!SHA1(message.data(), message.size(), hash)) { + dump_boringssl_error(); + FAIL() << "boringssl error creating SHA1 hash."; + } + // The application will prepend the digest info to the hash. + // SHA-1 digest info prefix = 0x30 0x21 0x30 ... + static const std::vector prefix = + wvutil::a2b_hex("3021300906052b0e03021a05000414"); + digest.insert(digest.end(), prefix.begin(), prefix.end()); + digest.insert(digest.end(), hash, hash + SHA_DIGEST_LENGTH); + } + void TestSignature(RSA_Padding_Scheme scheme, size_t size) { Session s; ASSERT_NO_FATAL_FAILURE(s.open()); @@ -29,16 +49,19 @@ class OEMCryptoLoadsCertificateAlternates : public OEMCryptoLoadsCertificate { vector licenseRequest(size); GetRandBytes(licenseRequest.data(), licenseRequest.size()); + vector digested_message; + ASSERT_NO_FATAL_FAILURE( + PrepareCastDigestedMessage(licenseRequest, digested_message)); size_t signature_length = 0; OEMCryptoResult sts = OEMCrypto_GenerateRSASignature( - s.session_id(), licenseRequest.data(), licenseRequest.size(), nullptr, - &signature_length, scheme); + s.session_id(), digested_message.data(), digested_message.size(), + nullptr, &signature_length, scheme); ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, sts); ASSERT_NE(static_cast(0), signature_length); std::vector signature(signature_length, 0); sts = OEMCrypto_GenerateRSASignature( - s.session_id(), licenseRequest.data(), licenseRequest.size(), + s.session_id(), digested_message.data(), digested_message.size(), signature.data(), &signature_length, scheme); ASSERT_EQ(OEMCrypto_SUCCESS, sts) @@ -48,7 +71,7 @@ class OEMCryptoLoadsCertificateAlternates : public OEMCryptoLoadsCertificate { ASSERT_NO_FATAL_FAILURE(s.SetRsaPublicKeyFromPrivateKeyInfo( encoded_rsa_key_.data(), encoded_rsa_key_.size())); ASSERT_NO_FATAL_FAILURE(s.VerifyRsaSignature( - licenseRequest, signature.data(), signature_length, scheme)); + digested_message, signature.data(), signature_length, scheme)); } // If force is true, we assert that the key loads successfully. diff --git a/oemcrypto/test/oemcrypto_decrypt_test.cpp b/oemcrypto/test/oemcrypto_decrypt_test.cpp index 6dcf901c..34a26474 100644 --- a/oemcrypto/test/oemcrypto_decrypt_test.cpp +++ b/oemcrypto/test/oemcrypto_decrypt_test.cpp @@ -13,6 +13,9 @@ using ::testing::Values; namespace wvoec { +/// @addtogroup decrypt +/// @{ + // Cannot decrypt without first getting a key handle. TEST_P(OEMCryptoLicenseTest, FailDecryptWithoutGettingAHandle) { ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); @@ -658,4 +661,5 @@ TEST_P(OEMCryptoLicenseTest, KeyDuration) { INSTANTIATE_TEST_SUITE_P(TestAll, OEMCryptoLicenseTest, Range(kCurrentAPI - 2, kCurrentAPI + 1)); +/// @} } // namespace wvoec diff --git a/oemcrypto/test/oemcrypto_license_test.cpp b/oemcrypto/test/oemcrypto_license_test.cpp index 18031c43..9ad55427 100644 --- a/oemcrypto/test/oemcrypto_license_test.cpp +++ b/oemcrypto/test/oemcrypto_license_test.cpp @@ -5,6 +5,8 @@ #include "oemcrypto_license_test.h" +#include + #include "platform.h" #include "test_sleep.h" @@ -769,6 +771,7 @@ TEST_P(OEMCryptoLicenseTest, MaxTotalKeysManySessions) { TEST_F(OEMCryptoSessionTests, CheckMinimumPatchLevel) { uint8_t patch_level = OEMCrypto_Security_Patch_Level(); printf(" Current Patch Level: %u.\n", patch_level); + RecordWvProperty("security_patch_level", std::to_string(patch_level)); { Session s; ASSERT_NO_FATAL_FAILURE(s.open()); diff --git a/oemcrypto/test/oemcrypto_provisioning_test.cpp b/oemcrypto/test/oemcrypto_provisioning_test.cpp index 37b2777d..a34a5348 100644 --- a/oemcrypto/test/oemcrypto_provisioning_test.cpp +++ b/oemcrypto/test/oemcrypto_provisioning_test.cpp @@ -5,6 +5,10 @@ #include "oemcrypto_provisioning_test.h" +#include + +#include + #include "bcc_validator.h" #include "device_info_validator.h" #include "log.h" @@ -15,6 +19,9 @@ namespace wvoec { +/// @addtogroup provision +/// @{ + // This test is used to print the device ID to stdout. TEST_F(OEMCryptoKeyboxTest, NormalGetDeviceId) { OEMCryptoResult sts; @@ -24,6 +31,7 @@ TEST_F(OEMCryptoKeyboxTest, NormalGetDeviceId) { ASSERT_EQ(OEMCrypto_SUCCESS, sts); cout << " NormalGetDeviceId: dev_id = " << MaybeHex(dev_id, dev_id_len) << " len = " << dev_id_len << endl; + RecordWvProperty("device_id", wvutil::HexEncode(dev_id, dev_id_len)); } TEST_F(OEMCryptoKeyboxTest, GetDeviceIdShortBuffer) { @@ -53,8 +61,12 @@ TEST_F(OEMCryptoKeyboxTest, NormalGetKeyData) { sts = OEMCrypto_GetKeyData(key_data, &key_data_len); uint32_t* data = reinterpret_cast(key_data); + const uint32_t system_id = htonl(data[1]); + const uint32_t version = htonl(data[0]); printf(" NormalGetKeyData: system_id = %u = 0x%04X, version=%u\n", - htonl(data[1]), htonl(data[1]), htonl(data[0])); + system_id, system_id, version); + RecordWvProperty("system_id", std::to_string(system_id)); + RecordWvProperty("key_data_version", std::to_string(version)); ASSERT_EQ(OEMCrypto_SUCCESS, sts); } @@ -91,6 +103,8 @@ TEST_F(OEMCryptoProv30Test, GetDeviceId) { dev_id.resize(dev_id_len); cout << " NormalGetDeviceId: dev_id = " << MaybeHex(dev_id) << " len = " << dev_id_len << endl; + RecordWvProperty("device_id", + wvutil::HexEncode(dev_id.data(), dev_id.size())); } // The OEM certificate must be valid. @@ -241,6 +255,35 @@ TEST_F(OEMCryptoProv40Test, GetBootCertificateChainSuccess) { EXPECT_EQ(util::CborMessageStatus::kCborValidateOk, validator.Validate()); } +// Verifies BCC signature and its type if they are available. +TEST_F(OEMCryptoProv40Test, AdditionalBccSignature) { + std::vector bcc; + size_t bcc_size = 0; + std::vector additional_signature; + size_t additional_signature_size = 0; + ASSERT_EQ(OEMCrypto_GetBootCertificateChain(bcc.data(), &bcc_size, + additional_signature.data(), + &additional_signature_size), + OEMCrypto_ERROR_SHORT_BUFFER); + + bcc.resize(bcc_size); + additional_signature.resize(additional_signature_size); + ASSERT_EQ(OEMCrypto_GetBootCertificateChain(bcc.data(), &bcc_size, + additional_signature.data(), + &additional_signature_size), + OEMCrypto_SUCCESS); + OEMCrypto_BCCSignatureType bcc_signature_type; + const OEMCryptoResult result = + OEMCrypto_GetBCCSignatureType(&bcc_signature_type); + if (result == OEMCrypto_ERROR_NOT_IMPLEMENTED) return; + ASSERT_EQ(result, OEMCrypto_SUCCESS); + if (!additional_signature.empty()) { + ASSERT_NE(bcc_signature_type, OEMCrypto_BCCSigType_Unknown); + } else { + ASSERT_EQ(bcc_signature_type, OEMCrypto_BCCSigType_Unknown); + } +} + // Verifies that short buffer error returns when the buffer is short. TEST_F(OEMCryptoProv40Test, GenerateCertificateKeyPairShortBuffer) { Session s; @@ -546,13 +589,13 @@ TEST_F(OEMCryptoProv40Test, InstallOemPrivateKeyCanBeUsed) { wrapped_private_key2.resize(wrapped_private_key_size2); // Verify public_key_signature2 with public_key1. - if (key_type2 == OEMCrypto_PrivateKeyType::OEMCrypto_RSA_Private_Key) { + if (key_type1 == OEMCrypto_PrivateKeyType::OEMCrypto_RSA_Private_Key) { ASSERT_NO_FATAL_FAILURE(s.SetRsaPublicKeyFromSubjectPublicKey( public_key1.data(), public_key1.size())); ASSERT_NO_FATAL_FAILURE( s.VerifyRsaSignature(public_key2, public_key_signature2.data(), public_key_signature2.size(), kSign_RSASSA_PSS)); - } else if (key_type2 == OEMCrypto_PrivateKeyType::OEMCrypto_ECC_Private_Key) { + } else if (key_type1 == OEMCrypto_PrivateKeyType::OEMCrypto_ECC_Private_Key) { ASSERT_NO_FATAL_FAILURE(s.SetEccPublicKeyFromSubjectPublicKey( public_key1.data(), public_key1.size())); ASSERT_NO_FATAL_FAILURE(s.VerifyEccSignature(public_key2, @@ -620,6 +663,8 @@ TEST_F(OEMCryptoProv40Test, GetDeviceId) { dev_id.resize(dev_id_len); cout << " NormalGetDeviceId: dev_id = " << MaybeHex(dev_id) << " len = " << dev_id_len << endl; + RecordWvProperty("device_id", + wvutil::HexEncode(dev_id.data(), dev_id.size())); // Device id should be stable. Query again. std::vector dev_id2(dev_id_len); sts = OEMCrypto_GetDeviceID(dev_id2.data(), &dev_id_len); @@ -1282,4 +1327,5 @@ TEST_F(OEMCryptoLoadsCertificate, SupportsCertificatesAPI13) { << "Supported certificates is only " << OEMCrypto_SupportedCertificates(); } +/// @} } // namespace wvoec diff --git a/oemcrypto/test/oemcrypto_security_test.cpp b/oemcrypto/test/oemcrypto_security_test.cpp index ca853d40..5f080008 100644 --- a/oemcrypto/test/oemcrypto_security_test.cpp +++ b/oemcrypto/test/oemcrypto_security_test.cpp @@ -23,14 +23,14 @@ #include #include -// TODO(b/253779846) Change it to include a header instead -#include "oemcrypto_test.cpp" +#include "oemcrypto_basic_test.h" +#include "oemcrypto_cast_test.h" +#include "oemcrypto_decrypt_test.h" +#include "oemcrypto_license_test.h" +#include "oemcrypto_provisioning_test.h" +#include "oemcrypto_usage_table_test.h" -using ::testing::Bool; -using ::testing::Combine; using ::testing::Range; -using ::testing::tuple; -using ::testing::Values; using ::testing::WithParamInterface; using namespace std; @@ -39,6 +39,136 @@ namespace wvoec { /// @addtogroup security /// @{ +class OEMCryptoLicenseOverflowTest : public OEMCryptoSessionTests, + public WithParamInterface { + public: + OEMCryptoLicenseOverflowTest() : license_api_version_(kCurrentAPI) {} + + void SetUp() override { + OEMCryptoSessionTests::SetUp(); + license_api_version_ = GetParam(); + } + + void TearDown() override { OEMCryptoSessionTests::TearDown(); } + + void TestLoadLicenseForHugeBufferLengths( + const std::function f, bool check_status, + bool update_core_message_substring_values) { + auto oemcrypto_function = [&](size_t message_length) { + Session s; + LicenseRoundTrip license_messages(&s); + license_messages.set_api_version(license_api_version_); + s.open(); + InstallTestDrmKey(&s); + bool verify_keys_loaded = true; + license_messages.SignAndVerifyRequest(); + license_messages.CreateDefaultResponse(); + if (update_core_message_substring_values) { + // Make the license message big enough so that updated core message + // substring offset and length values from tests are still able to read + // data from license_message buffer rather than reading some garbage + // data. + license_messages.set_message_size( + sizeof(license_messages.response_data()) + message_length); + } + f(message_length, &license_messages); + if (update_core_message_substring_values) { + // We will be updating offset for these tests, which will cause verify + // keys to fail with an assertion. Hence skipping verification. + verify_keys_loaded = false; + } + license_messages.EncryptAndSignResponse(); + OEMCryptoResult result = + license_messages.LoadResponse(&s, verify_keys_loaded); + s.close(); + return result; + }; + TestHugeLengthDoesNotCrashAPI(oemcrypto_function, check_status); + } + + void TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths( + const std::function f) { + Session s; + LicenseRoundTrip license_messages(&s); + license_messages.set_api_version(license_api_version_); + s.open(); + InstallTestDrmKey(&s); + license_messages.SignAndVerifyRequest(); + license_messages.CreateDefaultResponse(); + size_t message_length = sizeof(license_messages.response_data()); + f(message_length, &license_messages); + license_messages.EncryptAndSignResponse(); + OEMCryptoResult result = license_messages.LoadResponse(); + s.close(); + // Verifying error is not due to signature failure which can be caused due + // to test code. + ASSERT_NE(OEMCrypto_ERROR_SIGNATURE_FAILURE, result); + ASSERT_NE(OEMCrypto_SUCCESS, result); + } + + protected: + uint32_t license_api_version_; +}; + +// This class is for testing a single license with the default API version +// of 16. Used for buffer overflow tests. +class OEMCryptoMemoryLicenseTest : public OEMCryptoLicenseTestAPI16 { + public: + OEMCryptoMemoryLicenseTest() : entitled_message_(&license_messages_) {} + + void SetUp() override { + OEMCryptoLicenseTestAPI16::SetUp(); + SetUpEntitledMessage(); + entitlement_response_length_ = entitled_message_.entitled_key_data_size(); + } + + void LoadLicense() { + 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()); + } + + void SetUpEntitledMessage() { + license_messages_.set_license_type(OEMCrypto_EntitlementLicense); + LoadLicense(); + entitled_message_.FillKeyArray(); + entitled_message_.EncryptContentKey(); + uint32_t key_session_id = 0; + OEMCryptoResult sts = OEMCrypto_CreateEntitledKeySession( + session_.session_id(), &key_session_id); + if (sts == OEMCrypto_ERROR_NOT_IMPLEMENTED) { + return; + } + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + entitled_message_.SetEntitledKeySession(key_session_id); + } + + void TearDown() override { OEMCryptoLicenseTestAPI16::TearDown(); } + + protected: + EntitledMessage entitled_message_; + size_t entitlement_response_length_; + + void TestLoadEntitledKeysForHugeBufferLengths( + const std::function f, + bool check_status) { + size_t entitled_key_data_size = entitled_message_.entitled_key_data_size(); + vector message(entitled_key_data_size); + memcpy(message.data(), entitled_message_.entitled_key_data(), + entitled_key_data_size); + auto oemcrypto_function = [&](size_t length) { + // Make entitled message big enough so that updated substring offset and + // length fields by core message substring tests can still be able to read + // valid data from entitled message buffer rather than some garbage data. + message.resize(entitled_key_data_size + length); + f(length, &entitled_message_); + return entitled_message_.LoadKeys(message); + }; + TestHugeLengthDoesNotCrashAPI(oemcrypto_function, check_status); + } +}; + /** Test that OEMCrypto_FreeSecureBuffer fails gracefully on a huge buffer. */ TEST_F(OEMCryptoClientTest, @@ -102,7 +232,7 @@ TEST_F(OEMCryptoClientTest, TEST_F(OEMCryptoClientTest, OEMCryptoMemoryWrapKeyboxOrOEMCertForHugeTransportKey) { auto oemcrypto_function = [](size_t transport_key_length) { - size_t wrapped_keybox_length = sizeof(&kTestKeybox) + 50; + size_t wrapped_keybox_length = sizeof(kTestKeybox) + 50; vector wrapped_keybox_buffer(wrapped_keybox_length); vector transport_key_buffer(transport_key_length); return OEMCrypto_WrapKeyboxOrOEMCert( @@ -315,51 +445,11 @@ TEST_F(OEMCryptoKeyboxTest, OEMCryptoMemoryGetKeyIdForHugeIdLength) { TestHugeLengthDoesNotCrashAPI(oemcrypto_function, !kCheckStatus); } -/** Test that OEMCrypto_GenerateDerivedKeys fails gracefully on a huge buffer. - */ -TEST_F(OEMCryptoKeyboxTest, - OEMCryptoMemoryGenerateDerivedKeysForHugeMacContextLength) { - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - vector mac_context; - vector enc_context; - s.FillDefaultContext(&mac_context, &enc_context); - - auto oemcrypto_function = [&s, &mac_context, - &enc_context](size_t buffer_length) { - mac_context.resize(buffer_length); - return OEMCrypto_GenerateDerivedKeys(s.session_id(), mac_context.data(), - mac_context.size(), enc_context.data(), - enc_context.size()); - }; - TestHugeLengthDoesNotCrashAPI(oemcrypto_function, kCheckStatus); -} - -/** Test that OEMCrypto_GenerateDerivedKeys fails gracefully on a huge buffer. - */ -TEST_F(OEMCryptoKeyboxTest, - OEMCryptoMemoryGenerateDerivedKeysForHugeEncContextLength) { - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - vector mac_context; - vector enc_context; - s.FillDefaultContext(&mac_context, &enc_context); - - auto oemcrypto_function = [&s, &mac_context, - &enc_context](size_t buffer_length) { - enc_context.resize(buffer_length); - return OEMCrypto_GenerateDerivedKeys(s.session_id(), mac_context.data(), - mac_context.size(), enc_context.data(), - enc_context.size()); - }; - TestHugeLengthDoesNotCrashAPI(oemcrypto_function, kCheckStatus); -} - /** Test that OEMCrypto_GetOEMPublicCertificate fails gracefully on a huge * buffer. */ TEST_F(OEMCryptoProv30Test, OEMCryptoMemoryGetOEMPublicCertForHugeCertLength) { - if (wrapped_rsa_key_.size() == 0) { + if (wrapped_drm_key_.size() == 0) { // If we don't have a wrapped key yet, create one. // This wrapped key will be shared by all sessions in the test. ASSERT_NO_FATAL_FAILURE(CreateWrappedDRMKey()); @@ -367,7 +457,7 @@ TEST_F(OEMCryptoProv30Test, OEMCryptoMemoryGetOEMPublicCertForHugeCertLength) { Session s; ASSERT_NO_FATAL_FAILURE(s.open()); // Install the DRM Cert's RSA key. - ASSERT_NO_FATAL_FAILURE(s.LoadWrappedRsaDrmKey(wrapped_rsa_key_)); + ASSERT_NO_FATAL_FAILURE(s.LoadWrappedRsaDrmKey(wrapped_drm_key_)); ASSERT_NO_FATAL_FAILURE(s.SetTestRsaPublicKey()); auto oemcrypto_function = [](size_t input_length) { @@ -463,9 +553,12 @@ TEST_F(OEMCryptoSessionTests, license_messages.SignAndVerifyRequest(); license_messages.CreateDefaultResponse(); license_messages.EncryptAndSignResponse(); + const std::vector context = s.GetDefaultContext(); vector signature(signature_size); OEMCryptoResult result = OEMCrypto_LoadLicense( - s.session_id(), license_messages.encrypted_response_buffer().data(), + s.session_id(), context.data(), context.size(), + s.enc_session_key().data(), s.enc_session_key().size(), + license_messages.encrypted_response_buffer().data(), license_messages.encrypted_response_buffer().size(), license_messages.serialized_core_message().size(), signature.data(), signature_size); @@ -593,29 +686,12 @@ TEST_F(OEMCryptoSessionTests, TestHugeLengthDoesNotCrashAPI(oemcrypto_function, kCheckStatus); } -/** This test verifies that OEMCrypto_SetDecryptHash doesn't crash for a very - large hash buffer. -*/ -TEST_F(OEMCryptoMemoryLicenseTest, - OEMCryptoMemoryDecryptHashForHugeHashBuffer) { - uint32_t session_id = session_.session_id(); - auto f = [session_id]() { - const uint32_t frame_number = 1; - const uint32_t crc32 = 0; - return OEMCrypto_SetDecryptHash(session_id, frame_number, crc32); - }; - TestHugeLengthDoesNotCrashAPI(f, kCheckStatus); -} - /** Test Decrypt fails gracefully for huge input. */ TEST_P(OEMCryptoSessionTestsDecryptTests, OEMCryptoMemoryDecryptCENCForHugeNumberOfSubSamples) { auto oemcrypto_function = [&](size_t number_of_subsamples) { - std::vector subsample_sizes; - while (number_of_subsamples-- > 0) { - subsample_sizes.push_back({1, 1}); - } - SetSubsampleSizes(subsample_sizes); + std::vector subsample_sizes(number_of_subsamples, {1, 1}); + SetSubsampleSizes(std::move(subsample_sizes)); LoadLicense(); MakeBuffers(); EncryptData(); @@ -639,13 +715,10 @@ TEST_P(OEMCryptoSessionTestsDecryptTests, TEST_P(OEMCryptoSessionTestsDecryptTests, OEMCryptoMemoryDecryptCENCForHugeNumberOfSamples) { auto oemcrypto_function = [&](size_t number_of_samples) { - std::vector> samples; - std::vector subsample_sizes; - subsample_sizes.push_back({1, 1}); - while (number_of_samples-- > 0) { - samples.push_back(subsample_sizes); - } - SetSampleSizes(samples); + std::vector subsample_sizes(1, {1, 1}); + std::vector> sample_sizes(number_of_samples, + subsample_sizes); + SetSampleSizes(std::move(sample_sizes)); LoadLicense(); MakeBuffers(); EncryptData(); @@ -686,18 +759,20 @@ TEST_F(OEMCryptoLoadsCertificate, size_t wrapped_private_key_length = 0; // Find wrapped_private_key_length. OEMCrypto_LoadProvisioning( - s.session_id(), + s.session_id(), provisioning_messages.request().data(), + provisioning_messages.request().size(), provisioning_messages.encrypted_response_buffer().data(), provisioning_messages.encrypted_response_buffer().size(), provisioning_messages.serialized_core_message().size(), signature.data(), signature_size, nullptr, &wrapped_private_key_length); - std::vector wrapped_rsa_key(wrapped_private_key_length); + std::vector wrapped_private_key(wrapped_private_key_length); OEMCryptoResult result = OEMCrypto_LoadProvisioning( - s.session_id(), + s.session_id(), provisioning_messages.request().data(), + provisioning_messages.request().size(), provisioning_messages.encrypted_response_buffer().data(), provisioning_messages.encrypted_response_buffer().size(), provisioning_messages.serialized_core_message().size(), - signature.data(), signature_size, wrapped_rsa_key.data(), + signature.data(), signature_size, wrapped_private_key.data(), &wrapped_private_key_length); s.close(); return result; @@ -725,7 +800,8 @@ TEST_F(OEMCryptoLoadsCertificate, size_t wrapped_private_key_length = buffer_length; vector wrapped_private_key(wrapped_private_key_length); OEMCryptoResult result = OEMCrypto_LoadProvisioning( - s.session_id(), + s.session_id(), provisioning_messages.request().data(), + provisioning_messages.request().size(), provisioning_messages.encrypted_response_buffer().data(), provisioning_messages.encrypted_response_buffer().size(), provisioning_messages.serialized_core_message().size(), @@ -749,21 +825,21 @@ TEST_F(OEMCryptoLoadsCertificate, GTEST_SKIP() << "Test for non Prov 4.0 devices only."; } ASSERT_NO_FATAL_FAILURE(CreateWrappedDRMKey()); - auto oemcrypto_function = [&](size_t wrapped_rsa_key_length) { + auto oemcrypto_function = [&](size_t wrapped_drm_key_length) { Session s; s.open(); - vector wrapped_rsa_key_buffer = wrapped_rsa_key_; - wrapped_rsa_key_buffer.resize(wrapped_rsa_key_length); + vector wrapped_drm_key_buffer = wrapped_drm_key_; + wrapped_drm_key_buffer.resize(wrapped_drm_key_length); OEMCryptoResult result = OEMCrypto_LoadDRMPrivateKey( s.session_id(), OEMCrypto_RSA_Private_Key, - wrapped_rsa_key_buffer.data(), wrapped_rsa_key_buffer.size()); + wrapped_drm_key_buffer.data(), wrapped_drm_key_buffer.size()); s.close(); return result; }; // It is hard to generate varying length valid wrapped rsa key with valid // signature. Hence we just call function with random data and do not check // status to test API with varying length wrapped rsa key. - TestHugeLengthDoesNotCrashAPI(oemcrypto_function, wrapped_rsa_key_.size(), + TestHugeLengthDoesNotCrashAPI(oemcrypto_function, wrapped_drm_key_.size(), kHugeInputBufferLength, !kCheckStatus); } @@ -779,13 +855,13 @@ TEST_F( GTEST_SKIP() << "Test for non Prov 4.0 devices only."; } ASSERT_NO_FATAL_FAILURE(CreateWrappedDRMKey()); - auto oemcrypto_function = [&](size_t wrapped_rsa_key_length) { + auto oemcrypto_function = [&](size_t wrapped_drm_key_length) { Session s; s.open(); - vector wrapped_rsa_key_buffer(wrapped_rsa_key_length); + vector wrapped_drm_key_buffer(wrapped_drm_key_length); OEMCryptoResult result = OEMCrypto_LoadDRMPrivateKey( s.session_id(), OEMCrypto_RSA_Private_Key, - wrapped_rsa_key_buffer.data(), wrapped_rsa_key_buffer.size()); + wrapped_drm_key_buffer.data(), wrapped_drm_key_buffer.size()); s.close(); return result; }; @@ -795,71 +871,316 @@ TEST_F( TestHugeLengthDoesNotCrashAPI(oemcrypto_function, !kCheckStatus); } -/** Test that OEMCrypto_LoadDRMPrivateKey fails gracefully on a huge buffer. +/** Test that LoadProvisioning fails gracefully on huge buffer. */ +TEST_F(OEMCryptoLoadsCertificate, + OEMCryptoMemoryLoadProvisioningForHugeResponseLength) { + // TODO(b/197141970): Need to revisit OEMCryptoLoadsCert* tests for + // provisioning 4. Disabled here temporarily. + if (!global_features.loads_certificate || + global_features.provisioning_method == OEMCrypto_BootCertificateChain) { + GTEST_SKIP() << "Test for non Prov 4.0 devices only."; + } + TestLoadProvisioningForHugeBufferLengths( + [](size_t message_size, ProvisioningRoundTrip* provisioning_messages) { + provisioning_messages->set_message_size(message_size); + }, + !kCheckStatus, !kUpdateCoreMessageSubstringValues); +} + +/** Test that LoadProvisioning fails gracefully on huge buffer. */ +TEST_F(OEMCryptoLoadsCertificate, + OEMCryptoMemoryLoadProvisioningForHugeCoreMessageLength) { + // TODO(b/197141970): Need to revisit OEMCryptoLoadsCert* tests for + // provisioning 4. Disabled here temporarily. + if (!global_features.loads_certificate || + global_features.provisioning_method == OEMCrypto_BootCertificateChain) { + GTEST_SKIP() << "Test for non Prov 4.0 devices only."; + } + TestLoadProvisioningForHugeBufferLengths( + [](size_t message_size, ProvisioningRoundTrip* provisioning_messages) { + provisioning_messages->set_core_message_size(message_size); + }, + !kCheckStatus, !kUpdateCoreMessageSubstringValues); +} + +/** Test that LoadProvisioning fails gracefully on huge buffer. */ +TEST_F(OEMCryptoLoadsCertificate, + OEMCryptoMemoryLoadProvisioningForHugeCoreMessageEncPrivateKeyLength) { + // TODO(b/197141970): Need to revisit OEMCryptoLoadsCert* tests for + // provisioning 4. Disabled here temporarily. + if (!global_features.loads_certificate || + global_features.provisioning_method == OEMCrypto_BootCertificateChain) { + GTEST_SKIP() << "Test for non Prov 4.0 devices only."; + } + TestLoadProvisioningForHugeBufferLengths( + [](size_t length, ProvisioningRoundTrip* provisioning_messages) { + provisioning_messages->core_response().enc_private_key.length = length; + }, + !kCheckStatus, kUpdateCoreMessageSubstringValues); +} + +/** Test that LoadProvisioning fails gracefully on huge buffer. */ +TEST_F(OEMCryptoLoadsCertificate, + OEMCryptoMemoryLoadProvisioningForHugeCoreMessageEncPrivateKeyOffset) { + // TODO(b/197141970): Need to revisit OEMCryptoLoadsCert* tests for + // provisioning 4. Disabled here temporarily. + if (!global_features.loads_certificate || + global_features.provisioning_method == OEMCrypto_BootCertificateChain) { + GTEST_SKIP() << "Test for non Prov 4.0 devices only."; + } + TestLoadProvisioningForHugeBufferLengths( + [](size_t offset, ProvisioningRoundTrip* provisioning_messages) { + provisioning_messages->core_response().enc_private_key.offset = offset; + }, + !kCheckStatus, kUpdateCoreMessageSubstringValues); +} + +/** Test that LoadProvisioning fails gracefully on out of range substring. */ +TEST_F( + OEMCryptoLoadsCertificate, + OEMCryptoMemoryLoadProvisioningForOutOfRangeCoreMessageEncPrivateKeyLength) { + // TODO(b/197141970): Need to revisit OEMCryptoLoadsCert* tests for + // provisioning 4. Disabled here temporarily. + if (!global_features.loads_certificate || + global_features.provisioning_method == OEMCrypto_BootCertificateChain) { + GTEST_SKIP() << "Test for non Prov 4.0 devices only."; + } + TestLoadProvisioningForOutOfRangeSubstringOffsetAndLengths( + [](size_t response_message_length, + ProvisioningRoundTrip* provisioning_messages) { + auto& enc_private_key = + provisioning_messages->core_response().enc_private_key; + enc_private_key.length = + response_message_length - enc_private_key.offset + 1; + }); +} + +/** Test that LoadProvisioning fails gracefully on out of range substring. */ +TEST_F( + OEMCryptoLoadsCertificate, + OEMCryptoMemoryLoadProvisioningForOutOfRangeCoreMessageEncPrivateKeyOffset) { + // TODO(b/197141970): Need to revisit OEMCryptoLoadsCert* tests for + // provisioning 4. Disabled here temporarily. + if (!global_features.loads_certificate || + global_features.provisioning_method == OEMCrypto_BootCertificateChain) { + GTEST_SKIP() << "Test for non Prov 4.0 devices only."; + } + TestLoadProvisioningForOutOfRangeSubstringOffsetAndLengths( + [](size_t response_message_length, + ProvisioningRoundTrip* provisioning_messages) { + auto& enc_private_key = + provisioning_messages->core_response().enc_private_key; + enc_private_key.offset = + response_message_length - enc_private_key.length + 1; + }); +} + +/** Test that LoadProvisioning fails gracefully on out of range substring. */ +TEST_F(OEMCryptoLoadsCertificate, + OEMCryptoMemoryLoadProvisioningForHugeCoreMessageEncPrivateKeyIvLength) { + // TODO(b/197141970): Need to revisit OEMCryptoLoadsCert* tests for + // provisioning 4. Disabled here temporarily. + if (!global_features.loads_certificate || + global_features.provisioning_method == OEMCrypto_BootCertificateChain) { + GTEST_SKIP() << "Test for non Prov 4.0 devices only."; + } + TestLoadProvisioningForHugeBufferLengths( + [](size_t length, ProvisioningRoundTrip* provisioning_messages) { + provisioning_messages->core_response().enc_private_key_iv.length = + length; + }, + !kCheckStatus, kUpdateCoreMessageSubstringValues); +} + +/** Test that LoadProvisioning fails gracefully on out of range substring. */ +TEST_F(OEMCryptoLoadsCertificate, + OEMCryptoMemoryLoadProvisioningForHugeCoreMessageEncPrivateKeyIvOffset) { + // TODO(b/197141970): Need to revisit OEMCryptoLoadsCert* tests for + // provisioning 4. Disabled here temporarily. + if (!global_features.loads_certificate || + global_features.provisioning_method == OEMCrypto_BootCertificateChain) { + GTEST_SKIP() << "Test for non Prov 4.0 devices only."; + } + TestLoadProvisioningForHugeBufferLengths( + [](size_t offset, ProvisioningRoundTrip* provisioning_messages) { + provisioning_messages->core_response().enc_private_key_iv.offset = + offset; + }, + !kCheckStatus, kUpdateCoreMessageSubstringValues); +} + +/** Test that LoadProvisioning fails gracefully on out of range substring. */ +TEST_F( + OEMCryptoLoadsCertificate, + OEMCryptoMemoryLoadProvisioningForOutOfRangeCoreMessageEncPrivateKeyIvLengthAPI16) { + // TODO(b/197141970): Need to revisit OEMCryptoLoadsCert* tests for + // provisioning 4. Disabled here temporarily. + if (!global_features.loads_certificate || + global_features.provisioning_method == OEMCrypto_BootCertificateChain) { + GTEST_SKIP() << "Test for non Prov 4.0 devices only."; + } + TestLoadProvisioningForOutOfRangeSubstringOffsetAndLengths( + [](size_t response_message_length, + ProvisioningRoundTrip* provisioning_messages) { + auto& enc_private_key_iv = + provisioning_messages->core_response().enc_private_key_iv; + enc_private_key_iv.length = + response_message_length - enc_private_key_iv.offset + 1; + }); +} + +/** Test that LoadProvisioning fails gracefully on out of range substring. */ +TEST_F( + OEMCryptoLoadsCertificate, + OEMCryptoMemoryLoadProvisioningForOutOfRangeCoreMessageEncPrivateKeyIvOffset) { + // TODO(b/197141970): Need to revisit OEMCryptoLoadsCert* tests for + // provisioning 4. Disabled here temporarily. + if (!global_features.loads_certificate || + global_features.provisioning_method == OEMCrypto_BootCertificateChain) { + GTEST_SKIP() << "Test for non Prov 4.0 devices only."; + } + TestLoadProvisioningForOutOfRangeSubstringOffsetAndLengths( + [](size_t response_message_length, + ProvisioningRoundTrip* provisioning_messages) { + auto& enc_private_key_iv = + provisioning_messages->core_response().enc_private_key_iv; + enc_private_key_iv.offset = + response_message_length - enc_private_key_iv.length + 1; + }); +} + +/** Test that LoadProvisioning fails gracefully on out of range substring. */ +TEST_F(OEMCryptoLoadsCertificate, + OEMCryptoMemoryLoadProvisioningForHugeCoreMessageEncMessageKeyLength) { + // TODO(b/197141970): Need to revisit OEMCryptoLoadsCert* tests for + // provisioning 4. Disabled here temporarily. + if (!global_features.loads_certificate || + global_features.provisioning_method == OEMCrypto_BootCertificateChain) { + GTEST_SKIP() << "Test for non Prov 4.0 devices only."; + } + TestLoadProvisioningForHugeBufferLengths( + [](size_t length, ProvisioningRoundTrip* provisioning_messages) { + provisioning_messages->core_response().encrypted_message_key.length = + length; + }, + !kCheckStatus, kUpdateCoreMessageSubstringValues); +} + +/** Test that LoadProvisioning fails gracefully on out of range substring. */ +TEST_F(OEMCryptoLoadsCertificate, + OEMCryptoMemoryLoadProvisioningForHugeCoreMessageEncMessageKeyOffset) { + // TODO(b/197141970): Need to revisit OEMCryptoLoadsCert* tests for + // provisioning 4. Disabled here temporarily. + if (!global_features.loads_certificate || + global_features.provisioning_method == OEMCrypto_BootCertificateChain) { + GTEST_SKIP() << "Test for non Prov 4.0 devices only."; + } + TestLoadProvisioningForHugeBufferLengths( + [](size_t offset, ProvisioningRoundTrip* provisioning_messages) { + provisioning_messages->core_response().encrypted_message_key.offset = + offset; + }, + !kCheckStatus, kUpdateCoreMessageSubstringValues); +} + +/** Test that LoadProvisioning fails gracefully on out of range substring. */ +TEST_F( + OEMCryptoLoadsCertificate, + OEMCryptoMemoryLoadProvisioningForOutOfRangeCoreMessageEncMessageKeyLengthProv30) { + // TODO(b/197141970): Need to revisit OEMCryptoLoadsCert* tests for + // provisioning 4. Disabled here temporarily. + if (!global_features.loads_certificate || + global_features.provisioning_method == OEMCrypto_BootCertificateChain) { + GTEST_SKIP() << "Test for non Prov 4.0 devices only."; + } + if (global_features.provisioning_method != OEMCrypto_OEMCertificate) { + GTEST_SKIP() << "Test for Prov 3.0 devices only."; + } + TestLoadProvisioningForOutOfRangeSubstringOffsetAndLengths( + [](size_t response_message_length, + ProvisioningRoundTrip* provisioning_messages) { + auto& encrypted_message_key = + provisioning_messages->core_response().encrypted_message_key; + encrypted_message_key.length = + response_message_length - encrypted_message_key.offset + 1; + }); +} + +/** Test that LoadProvisioning fails gracefully on out of range substring. */ +TEST_F( + OEMCryptoLoadsCertificate, + OEMCryptoMemoryLoadProvisioningForOutOfRangeCoreMessageEncMessageKeyOffsetProv30) { + // TODO(b/197141970): Need to revisit OEMCryptoLoadsCert* tests for + // provisioning 4. Disabled here temporarily. + if (!global_features.loads_certificate || + global_features.provisioning_method == OEMCrypto_BootCertificateChain) { + GTEST_SKIP() << "Test for non Prov 4.0 devices only."; + } + if (global_features.provisioning_method != OEMCrypto_OEMCertificate) { + GTEST_SKIP() << "Test for Prov 3.0 devices only."; + } + TestLoadProvisioningForOutOfRangeSubstringOffsetAndLengths( + [](size_t response_message_length, + ProvisioningRoundTrip* provisioning_messages) { + auto& encrypted_message_key = + provisioning_messages->core_response().encrypted_message_key; + encrypted_message_key.offset = + response_message_length - encrypted_message_key.length + 1; + }); +} + +/** Test that OEMCrypto_PrepAndSignProvisioningRequest fails gracefully on a + * huge buffer. */ -TEST_F(OEMCryptoUsesCertificate, - OEMCryptoMemoryDeriveKeysFromSessionKeyForHugeMacContext) { - vector session_key; - vector enc_session_key; - ASSERT_TRUE(session_.GenerateSessionKey(&session_key, &enc_session_key)); - vector mac_context; - vector enc_context; - session_.FillDefaultContext(&mac_context, &enc_context); - OEMCrypto_SESSION session_id = session_.session_id(); - auto oemcrypto_function = [&session_id, &enc_context, &mac_context, - &enc_session_key](size_t buffer_length) { - mac_context.resize(buffer_length); - return OEMCrypto_DeriveKeysFromSessionKey( - session_id, enc_session_key.data(), enc_session_key.size(), - mac_context.data(), mac_context.size(), enc_context.data(), - enc_context.size()); - }; - TestHugeLengthDoesNotCrashAPI(oemcrypto_function, kCheckStatus); +TEST_F(OEMCryptoLoadsCertificate, + OEMCryptoMemoryPrepareProvisioningRequestForHugeRequestMessageLength) { + // TODO(b/197141970): Need to revisit OEMCryptoLoadsCert* tests for + // provisioning 4. Disabled here temporarily. + if (!global_features.loads_certificate || + global_features.provisioning_method == OEMCrypto_BootCertificateChain) { + GTEST_SKIP() << "Test for non Prov 4.0 devices only."; + } + TestPrepareProvisioningRequestForHugeBufferLengths( + [](size_t message_size, ProvisioningRoundTrip* provisioning_messages) { + provisioning_messages->set_message_size(message_size); + }, + kCheckStatus); } -/** Test that OEMCrypto_DeriveKeysFromSessionKey fails gracefully on a huge - * buffer. */ -TEST_F(OEMCryptoUsesCertificate, - OEMCryptoMemoryDeriveKeysFromSessionKeyForHugeEncContext) { - vector session_key; - vector enc_session_key; - ASSERT_TRUE(session_.GenerateSessionKey(&session_key, &enc_session_key)); - vector mac_context; - vector enc_context; - session_.FillDefaultContext(&mac_context, &enc_context); - OEMCrypto_SESSION session_id = session_.session_id(); - auto oemcrypto_function = [&session_id, &enc_context, &mac_context, - &enc_session_key](size_t buffer_length) { - enc_context.resize(buffer_length); - return OEMCrypto_DeriveKeysFromSessionKey( - session_id, enc_session_key.data(), enc_session_key.size(), - mac_context.data(), mac_context.size(), enc_context.data(), - enc_context.size()); - }; - TestHugeLengthDoesNotCrashAPI(oemcrypto_function, kCheckStatus); +/** Test that OEMCrypto_PrepAndSignProvisioningRequest fails gracefully on a + * huge buffer. + */ +TEST_F(OEMCryptoLoadsCertificate, + OEMCryptoMemoryPrepareProvisioningRequestForHugeSignatureLength) { + // TODO(b/197141970): Need to revisit OEMCryptoLoadsCert* tests for + // provisioning 4. Disabled here temporarily. + if (!global_features.loads_certificate || + global_features.provisioning_method == OEMCrypto_BootCertificateChain) { + GTEST_SKIP() << "Test for non Prov 4.0 devices only."; + } + TestPrepareProvisioningRequestForHugeBufferLengths( + [](size_t message_size, ProvisioningRoundTrip* provisioning_messages) { + provisioning_messages->set_request_signature_size(message_size); + }, + !kCheckStatus); } -/** Test that OEMCrypto_DeriveKeysFromSessionKey fails gracefully on a huge - * buffer. */ -TEST_F(OEMCryptoUsesCertificate, - OEMCryptoMemoryDeriveKeysFromSessionKeyForHugeEncSessionKey) { - vector session_key; - vector enc_session_key; - ASSERT_TRUE(session_.GenerateSessionKey(&session_key, &enc_session_key)); - vector mac_context; - vector enc_context; - session_.FillDefaultContext(&mac_context, &enc_context); - OEMCrypto_SESSION session_id = session_.session_id(); - auto oemcrypto_function = [&session_id, &session_key, &enc_context, - &mac_context, - &enc_session_key](size_t buffer_length) { - session_key.resize(buffer_length); - return OEMCrypto_DeriveKeysFromSessionKey( - session_id, enc_session_key.data(), enc_session_key.size(), - mac_context.data(), mac_context.size(), enc_context.data(), - enc_context.size()); - }; - TestHugeLengthDoesNotCrashAPI(oemcrypto_function, !kCheckStatus); +/** Test that OEMCrypto_PrepAndSignProvisioningRequest fails gracefully on a + * huge buffer. + */ +TEST_F(OEMCryptoLoadsCertificate, + OEMCryptoMemoryPrepareProvisioningRequestForHugeCoreMessageLength) { + // TODO(b/197141970): Need to revisit OEMCryptoLoadsCert* tests for + // provisioning 4. Disabled here temporarily. + if (!global_features.loads_certificate || + global_features.provisioning_method == OEMCrypto_BootCertificateChain) { + GTEST_SKIP() << "Test for non Prov 4.0 devices only."; + } + TestPrepareProvisioningRequestForHugeBufferLengths( + [](size_t message_size, ProvisioningRoundTrip* provisioning_messages) { + provisioning_messages->set_core_message_size(message_size); + }, + kCheckStatus); } /** Test that OEMCrypto_GenerateRSASignature fails gracefully on a huge @@ -882,7 +1203,7 @@ TEST_F(OEMCryptoLoadsCertificateAlternates, // If the key did load, then it should be processed correctly. Session s; ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(s.LoadWrappedRsaDrmKey(wrapped_rsa_key_)); + ASSERT_NO_FATAL_FAILURE(s.LoadWrappedRsaDrmKey(wrapped_drm_key_)); vector message_buffer(10); size_t signature_length = 0; @@ -924,7 +1245,7 @@ TEST_F(OEMCryptoLoadsCertificateAlternates, // If the key did load, then it should be processed correctly. Session s; ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(s.LoadWrappedRsaDrmKey(wrapped_rsa_key_)); + ASSERT_NO_FATAL_FAILURE(s.LoadWrappedRsaDrmKey(wrapped_drm_key_)); vector message_buffer(50); vector signature; @@ -965,9 +1286,7 @@ TEST_P(OEMCryptoGenericCryptoTest, session_.license().keys[key_index].key_id, session_.license().keys[key_index].key_id_length, OEMCrypto_CipherMode_CENC, key_handle)); - OEMCrypto_SESSION session_id = session_.session_id(); - auto& iv = iv_; - auto oemcrypto_function = [&session_id, &iv](size_t buffer_length) mutable { + auto oemcrypto_function = [&key_handle, &iv = iv_](size_t buffer_length) { vector buffer(buffer_length); return OEMCrypto_Generic_Encrypt( key_handle.data(), key_handle.size(), buffer.data(), buffer.size(), iv, @@ -987,9 +1306,7 @@ TEST_P(OEMCryptoGenericCryptoTest, session_.license().keys[key_index].key_id, session_.license().keys[key_index].key_id_length, OEMCrypto_CipherMode_CENC, key_handle); - OEMCrypto_SESSION session_id = session_.session_id(); - auto iv = iv_; - auto oemcrypto_function = [&session_id, &iv](size_t buffer_length) { + auto oemcrypto_function = [&key_handle, &iv = iv_](size_t buffer_length) { vector encrypted(buffer_length); vector resultant(encrypted.size()); @@ -1013,12 +1330,10 @@ TEST_P(OEMCryptoGenericCryptoTest, OEMCryptoMemoryGenericKeySignForHugeBuffer) { GetKeyHandleIntoVector(session_.session_id(), session_.license().keys[key_index].key_id, session_.license().keys[key_index].key_id_length, - OEMCrypto_CipherMode_CENC), - key_handle); + OEMCrypto_CipherMode_CENC, key_handle)); vector signature(SHA256_DIGEST_LENGTH); size_t signature_length = signature.size(); - OEMCrypto_SESSION session_id = session_.session_id(); - auto oemcrypto_function = [&session_id, &signature, + auto oemcrypto_function = [&key_handle, &signature, &signature_length](size_t buffer_length) { vector buffer(buffer_length); return OEMCrypto_Generic_Sign( @@ -1039,19 +1354,16 @@ TEST_P(OEMCryptoGenericCryptoTest, GetKeyHandleIntoVector(session_.session_id(), session_.license().keys[key_index].key_id, session_.license().keys[key_index].key_id_length, - OEMCrypto_CipherMode_CENC), - key_handle); - OEMCrypto_SESSION session_id = session_.session_id(); - auto clear_buffer = clear_buffer_; - auto oemcrypto_function = [&session_id, - &clear_buffer](size_t signature_length) { - vector signature(signature_length); - size_t gen_signature_length = signature_length; - return OEMCrypto_Generic_Sign(key_handle.data(), key_handle.size(), - clear_buffer.data(), clear_buffer.size(), - OEMCrypto_HMAC_SHA256, signature.data(), - &gen_signature_length); - }; + OEMCrypto_CipherMode_CENC, key_handle)); + auto oemcrypto_function = + [&key_handle, &clear_buffer = clear_buffer_](size_t signature_length) { + vector signature(signature_length); + size_t gen_signature_length = signature_length; + return OEMCrypto_Generic_Sign(key_handle.data(), key_handle.size(), + clear_buffer.data(), clear_buffer.size(), + OEMCrypto_HMAC_SHA256, signature.data(), + &gen_signature_length); + }; TestHugeLengthDoesNotCrashAPI(oemcrypto_function, kCheckStatus); } @@ -1093,9 +1405,7 @@ TEST_P(OEMCryptoGenericCryptoTest, session_.license().keys[key_index].key_id, session_.license().keys[key_index].key_id_length, OEMCrypto_CipherMode_CENC, key_handle)); - OEMCrypto_SESSION session_id = session_.session_id(); - auto clear_buffer = clear_buffer_; - auto oemcrypto_function = [&session_id, &clear_buffer, + auto oemcrypto_function = [&key_handle, &clear_buffer = clear_buffer_, &signature](size_t signature_length) { return OEMCrypto_Generic_Verify(key_handle.data(), key_handle.size(), clear_buffer.data(), clear_buffer.size(), @@ -1340,6 +1650,632 @@ TEST_P(OEMCryptoUsageTableTest, }; TestHugeLengthDoesNotCrashAPI(oemcrypto_function, kCheckStatus); } -/// @} +/** Test that LoadEntitledContentKeys fails gracefully on huge buffer. */ +TEST_F(OEMCryptoMemoryLicenseTest, + OEMCryptoMemoryLoadEntitledKeysForHugeSubstringContentKeyIdLength) { + TestLoadEntitledKeysForHugeBufferLengths( + [](size_t key_id_length, EntitledMessage* entitled_message) { + entitled_message->entitled_key_array()[0].content_key_id.length = + key_id_length; + }, + !kCheckStatus); +} + +/** Test that LoadEntitledContentKeys fails gracefully on huge buffer. */ +TEST_F(OEMCryptoMemoryLicenseTest, + OEMCryptoMemoryLoadEntitledKeysForHugeSubstringContentKeyIdOffset) { + TestLoadEntitledKeysForHugeBufferLengths( + [](size_t key_id_offset, EntitledMessage* entitled_message) { + entitled_message->entitled_key_array()[0].content_key_id.offset = + key_id_offset; + }, + !kCheckStatus); +} + +/** Test that LoadEntitledContentKeys fails gracefully on huge buffer. */ +TEST_F( + OEMCryptoMemoryLicenseTest, + OEMCryptoMemoryLoadEntitledKeysForOutOfRangeSubstringContentKeyIdLength) { + auto& content_key_id = + entitled_message_.entitled_key_array()[0].content_key_id; + content_key_id.length = + entitlement_response_length_ - content_key_id.offset + 1; + ASSERT_NE(OEMCrypto_SUCCESS, entitled_message_.LoadKeys()); +} + +/** Test LoadEntitledContentKeys rejects out of range substring. */ +TEST_F( + OEMCryptoMemoryLicenseTest, + OEMCryptoMemoryLoadEntitledKeysForOutOfRangeSubstringContentKeyIdOffset) { + auto& content_key_id = + entitled_message_.entitled_key_array()[0].content_key_id; + content_key_id.offset = + entitlement_response_length_ - content_key_id.length + 1; + ASSERT_NE(OEMCrypto_SUCCESS, entitled_message_.LoadKeys()); +} + +/** Test that LoadEntitledContentKeys fails gracefully on huge substring. */ +TEST_F(OEMCryptoMemoryLicenseTest, + OEMCryptoMemoryLoadEntitledKeysForHugeSubstringEntitlementKeyIdLength) { + TestLoadEntitledKeysForHugeBufferLengths( + [](size_t key_id_length, EntitledMessage* entitled_message) { + entitled_message->entitled_key_array()[0].entitlement_key_id.length = + key_id_length; + }, + !kCheckStatus); +} + +/** Test LoadEntitledContentKeys rejects out of range substring. */ +TEST_F(OEMCryptoMemoryLicenseTest, + OEMCryptoMemoryLoadEntitledKeysForHugeSubstringEntitlementKeyIdOffset) { + TestLoadEntitledKeysForHugeBufferLengths( + [](size_t key_id_offset, EntitledMessage* entitled_message) { + entitled_message->entitled_key_array()[0].entitlement_key_id.offset = + key_id_offset; + }, + !kCheckStatus); +} + +/** Test LoadEntitledContentKeys rejects out of range substring. */ +TEST_F( + OEMCryptoMemoryLicenseTest, + OEMCryptoMemoryLoadEntitledKeysForOutOfRangeSubstringEntitlementKeyIdLength) { + auto& entitlement_key_id = + entitled_message_.entitled_key_array()[0].entitlement_key_id; + entitlement_key_id.length = + entitlement_response_length_ - entitlement_key_id.offset + 1; + ASSERT_NE(OEMCrypto_SUCCESS, entitled_message_.LoadKeys()); +} + +/** Test LoadEntitledContentKeys rejects out of range substring. */ +TEST_F( + OEMCryptoMemoryLicenseTest, + OEMCryptoMemoryLoadEntitledKeysForOutOfRangeSubstringEntitlementKeyIdOffset) { + auto& entitlement_key_id = + entitled_message_.entitled_key_array()[0].entitlement_key_id; + entitlement_key_id.offset = + entitlement_response_length_ - entitlement_key_id.length + 1; + ASSERT_NE(OEMCrypto_SUCCESS, entitled_message_.LoadKeys()); +} + +/** Test LoadEntitledContentKeys rejects out of range substring. */ +TEST_F(OEMCryptoMemoryLicenseTest, + OEMCryptoMemoryLoadEntitledKeysForHugeSubstringContentKeyDataIvLength) { + TestLoadEntitledKeysForHugeBufferLengths( + [](size_t content_key_data_iv_length, EntitledMessage* entitled_message) { + entitled_message->entitled_key_array()[0].content_key_data_iv.length = + content_key_data_iv_length; + }, + !kCheckStatus); +} + +/** Test LoadEntitledContentKeys rejects out of range substring. */ +TEST_F(OEMCryptoMemoryLicenseTest, + OEMCryptoMemoryLoadEntitledKeysForHugeSubstringContentKeyDataIvOffset) { + TestLoadEntitledKeysForHugeBufferLengths( + [](size_t content_key_data_iv_offset, EntitledMessage* entitled_message) { + entitled_message->entitled_key_array()[0].content_key_data_iv.offset = + content_key_data_iv_offset; + }, + !kCheckStatus); +} + +/** Test LoadEntitledContentKeys rejects out of range substring. */ +TEST_F( + OEMCryptoMemoryLicenseTest, + OEMCryptoMemoryLoadEntitledKeysForOutOfRangeSubstringContentKeyDataIvLength) { + auto& content_key_data_iv = + entitled_message_.entitled_key_array()[0].content_key_data_iv; + content_key_data_iv.length = + entitlement_response_length_ - content_key_data_iv.offset + 1; + ASSERT_NE(OEMCrypto_SUCCESS, entitled_message_.LoadKeys()); +} + +/** Test LoadEntitledContentKeys rejects out of range substring. */ +TEST_F( + OEMCryptoMemoryLicenseTest, + OEMCryptoMemoryLoadEntitledKeysForOutOfRangeSubstringContentKeyDataIvOffset) { + auto& content_key_data_iv = + entitled_message_.entitled_key_array()[0].content_key_data_iv; + content_key_data_iv.offset = + entitlement_response_length_ - content_key_data_iv.length + 1; + ASSERT_NE(OEMCrypto_SUCCESS, entitled_message_.LoadKeys()); +} + +/** Test LoadEntitledContentKeys rejects out of range substring. */ +TEST_F(OEMCryptoMemoryLicenseTest, + OEMCryptoMemoryLoadEntitledKeysForHugeSubstringContentKeyDataLength) { + TestLoadEntitledKeysForHugeBufferLengths( + [](size_t content_key_data_length, EntitledMessage* entitled_message) { + entitled_message->entitled_key_array()[0].content_key_data.length = + content_key_data_length; + }, + !kCheckStatus); +} + +/** Test LoadEntitledContentKeys rejects out of range substring. */ +TEST_F(OEMCryptoMemoryLicenseTest, + OEMCryptoMemoryLoadEntitledKeysForHugeSubstringContentKeyDataOffset) { + TestLoadEntitledKeysForHugeBufferLengths( + [](size_t content_key_data_offset, EntitledMessage* entitled_message) { + entitled_message->entitled_key_array()[0].content_key_data.offset = + content_key_data_offset; + }, + !kCheckStatus); +} + +/** Test LoadEntitledContentKeys rejects out of range substring. */ +TEST_F( + OEMCryptoMemoryLicenseTest, + OEMCryptoMemoryLoadEntitledKeysForOutOfRangeSubstringContentKeyDataLength) { + auto& content_key_data = + entitled_message_.entitled_key_array()[0].content_key_data; + content_key_data.length = + entitlement_response_length_ - content_key_data.offset + 1; + ASSERT_NE(OEMCrypto_SUCCESS, entitled_message_.LoadKeys()); +} + +/** Test LoadEntitledContentKeys rejects out of range substring. */ +TEST_F( + OEMCryptoMemoryLicenseTest, + OEMCryptoMemoryLoadEntitledKeysForOutOfRangeSubstringContentKeyDataOffset) { + auto& content_key_data = + entitled_message_.entitled_key_array()[0].content_key_data; + content_key_data.offset = + entitlement_response_length_ - content_key_data.length + 1; + ASSERT_NE(OEMCrypto_SUCCESS, entitled_message_.LoadKeys()); +} + +/** Test LoadEntitledContentKeys rejects out of range substring. */ +TEST_F(OEMCryptoMemoryLicenseTest, + OEMCryptoMemoryLoadEntitledKeysForHugeEntitlementKeyIdLength) { + TestLoadEntitledKeysForHugeBufferLengths( + [](size_t key_id_length, EntitledMessage* entitled_message) { + entitled_message->entitled_key_data()->entitlement_key_id_length = + key_id_length; + }, + !kCheckStatus); +} + +/** Test LoadEntitledContentKeys rejects out of range substring. */ +TEST_F(OEMCryptoMemoryLicenseTest, + OEMCryptoMemoryLoadEntitledKeysForHugeContentKeyIdLength) { + TestLoadEntitledKeysForHugeBufferLengths( + [](size_t key_id_length, EntitledMessage* entitled_message) { + entitled_message->entitled_key_data()->content_key_id_length = + key_id_length; + }, + !kCheckStatus); +} + +/** Test that LoadLicense fails gracefully on huge buffer. */ +TEST_P(OEMCryptoLicenseOverflowTest, + OEMCryptoMemoryLoadLicenseForHugeCoreMessageSubstringKeyIdLength) { + TestLoadLicenseForHugeBufferLengths( + [](size_t length, LicenseRoundTrip* license_messages) { + license_messages->core_response().key_array[0].key_id.length = length; + license_messages->response_data().keys[0].key_id_length = length; + }, + !kCheckStatus, kUpdateCoreMessageSubstringValues); +} + +/** Test that LoadLicense fails gracefully on out of range substring. */ +TEST_P(OEMCryptoLicenseOverflowTest, + OEMCryptoMemoryLoadLicenseForHugeCoreMessageSubstringKeyIdOffset) { + TestLoadLicenseForHugeBufferLengths( + [](size_t offset, LicenseRoundTrip* license_messages) { + license_messages->core_response().key_array[0].key_id.offset = offset; + }, + !kCheckStatus, kUpdateCoreMessageSubstringValues); +} + +/** Test that LoadLicense fails gracefully on out of range substring. */ +TEST_P(OEMCryptoLicenseOverflowTest, + OEMCryptoMemoryLoadLicenseForOutOfRangeCoreMessageSubstringKeyIdLength) { + TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths( + [](size_t response_message_length, LicenseRoundTrip* license_messages) { + auto& key_id = license_messages->core_response().key_array[0].key_id; + key_id.length = response_message_length - key_id.offset + 1; + }); +} + +/** Test that LoadLicense fails gracefully on out of range substring. */ +TEST_P(OEMCryptoLicenseOverflowTest, + OEMCryptoMemoryLoadLicenseForOutOfRangeCoreMessageSubstringKeyIdOffset) { + TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths( + [](size_t response_message_length, LicenseRoundTrip* license_messages) { + auto& key_id = license_messages->core_response().key_array[0].key_id; + key_id.offset = response_message_length - key_id.length + 1; + }); +} + +/** Test that LoadLicense fails gracefully on out of range substring. */ +TEST_P(OEMCryptoLicenseOverflowTest, + OEMCryptoMemoryLoadLicenseForHugeCoreMessageSubstringKeyDataIvLength) { + TestLoadLicenseForHugeBufferLengths( + [](size_t length, LicenseRoundTrip* license_messages) { + license_messages->core_response().key_array[0].key_data_iv.length = + length; + }, + !kCheckStatus, kUpdateCoreMessageSubstringValues); +} + +/** Test that LoadLicense fails gracefully on out of range substring. */ +TEST_P(OEMCryptoLicenseOverflowTest, + OEMCryptoMemoryLoadLicenseForHugeCoreMessageSubstringKeyDataIvOffset) { + TestLoadLicenseForHugeBufferLengths( + [](size_t offset, LicenseRoundTrip* license_messages) { + license_messages->core_response().key_array[0].key_data_iv.offset = + offset; + }, + !kCheckStatus, kUpdateCoreMessageSubstringValues); +} + +/** Test that LoadLicense fails gracefully on out of range substring. */ +TEST_P( + OEMCryptoLicenseOverflowTest, + OEMCryptoMemoryLoadLicenseForOutOfRangeCoreMessageSubstringKeyDataIvLength) { + TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths( + [](size_t response_message_length, LicenseRoundTrip* license_messages) { + auto& key_data_iv = + license_messages->core_response().key_array[0].key_data_iv; + key_data_iv.length = response_message_length - key_data_iv.offset + 1; + }); +} + +/** Test that LoadLicense fails gracefully on out of range substring. */ +TEST_P( + OEMCryptoLicenseOverflowTest, + OEMCryptoMemoryLoadLicenseForOutOfRangeCoreMessageSubstringKeyDataIvOffset) { + TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths( + [](size_t response_message_length, LicenseRoundTrip* license_messages) { + auto& key_data_iv = + license_messages->core_response().key_array[0].key_data_iv; + key_data_iv.offset = response_message_length - key_data_iv.length + 1; + }); +} + +/** Test that LoadLicense fails gracefully on out of range substring. */ +TEST_P(OEMCryptoLicenseOverflowTest, + OEMCryptoMemoryLoadLicenseForHugeCoreMessageSubstringKeyDataLength) { + TestLoadLicenseForHugeBufferLengths( + [](size_t length, LicenseRoundTrip* license_messages) { + license_messages->core_response().key_array[0].key_data.length = length; + license_messages->response_data().keys[0].key_data_length = length; + }, + !kCheckStatus, kUpdateCoreMessageSubstringValues); +} + +/** Test that LoadLicense fails gracefully on out of range substring. */ +TEST_P(OEMCryptoLicenseOverflowTest, + OEMCryptoMemoryLoadLicenseForHugeCoreMessageSubstringKeyDataOffset) { + TestLoadLicenseForHugeBufferLengths( + [](size_t offset, LicenseRoundTrip* license_messages) { + license_messages->core_response().key_array[0].key_data.offset = offset; + }, + !kCheckStatus, kUpdateCoreMessageSubstringValues); +} + +/** Test that LoadLicense fails gracefully on out of range substring. */ +TEST_P( + OEMCryptoLicenseOverflowTest, + OEMCryptoMemoryLoadLicenseForOutOfRangeCoreMessageSubstringKeyDataLength) { + TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths( + [](size_t response_message_length, LicenseRoundTrip* license_messages) { + auto& key_data = + license_messages->core_response().key_array[0].key_data; + key_data.length = response_message_length - key_data.offset + 1; + }); +} + +/** Test that LoadLicense fails gracefully on out of range substring. */ +TEST_P( + OEMCryptoLicenseOverflowTest, + OEMCryptoMemoryLoadLicenseForOutOfRangeCoreMessageSubstringKeyDataOffset) { + TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths( + [](size_t response_message_length, LicenseRoundTrip* license_messages) { + auto& key_data = + license_messages->core_response().key_array[0].key_data; + key_data.offset = response_message_length - key_data.length + 1; + }); +} + +/** Test that LoadLicense fails gracefully on out of range substring. */ +TEST_P( + OEMCryptoLicenseOverflowTest, + OEMCryptoMemoryLoadLicenseForHugeCoreMessageSubstringKeyControlIvLength) { + TestLoadLicenseForHugeBufferLengths( + [](size_t length, LicenseRoundTrip* license_messages) { + license_messages->core_response().key_array[0].key_control_iv.length = + length; + }, + !kCheckStatus, kUpdateCoreMessageSubstringValues); +} + +/** Test that LoadLicense fails gracefully on out of range substring. */ +TEST_P( + OEMCryptoLicenseOverflowTest, + OEMCryptoMemoryLoadLicenseForHugeCoreMessageSubstringKeyControlIvOffset) { + TestLoadLicenseForHugeBufferLengths( + [](size_t offset, LicenseRoundTrip* license_messages) { + license_messages->core_response().key_array[0].key_control_iv.offset = + offset; + }, + !kCheckStatus, kUpdateCoreMessageSubstringValues); +} + +/** Test that LoadLicense fails gracefully on out of range substring. */ +TEST_P( + OEMCryptoLicenseOverflowTest, + OEMCryptoMemoryLoadLicenseForOutOfRangeCoreMessageSubstringKeyControlIvLengthAPI16) { + TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths( + [](size_t response_message_length, LicenseRoundTrip* license_messages) { + auto& key_control_iv = + license_messages->core_response().key_array[0].key_control_iv; + key_control_iv.length = + response_message_length - key_control_iv.offset + 1; + }); +} + +/** Test that LoadLicense fails gracefully on out of range substring. */ +TEST_P( + OEMCryptoLicenseOverflowTest, + OEMCryptoMemoryLoadLicenseForOutOfRangeCoreMessageSubstringKeyControlIvOffset) { + TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths( + [](size_t response_message_length, LicenseRoundTrip* license_messages) { + auto& key_control_iv = + license_messages->core_response().key_array[0].key_control_iv; + key_control_iv.offset = + response_message_length - key_control_iv.length + 1; + }); +} + +/** Test that LoadLicense fails gracefully on out of range substring. */ +TEST_P(OEMCryptoLicenseOverflowTest, + OEMCryptoMemoryLoadLicenseForHugeCoreMessageSubstringKeyControlLength) { + TestLoadLicenseForHugeBufferLengths( + [](size_t length, LicenseRoundTrip* license_messages) { + license_messages->core_response().key_array[0].key_control.length = + length; + }, + !kCheckStatus, kUpdateCoreMessageSubstringValues); +} + +/** Test that LoadLicense fails gracefully on out of range substring. */ +TEST_P(OEMCryptoLicenseOverflowTest, + OEMCryptoMemoryLoadLicenseForHugeCoreMessageSubstringKeyControlOffset) { + TestLoadLicenseForHugeBufferLengths( + [](size_t offset, LicenseRoundTrip* license_messages) { + license_messages->core_response().key_array[0].key_control.offset = + offset; + }, + !kCheckStatus, kUpdateCoreMessageSubstringValues); +} + +/** Test that LoadLicense fails gracefully on out of range substring. */ +TEST_P( + OEMCryptoLicenseOverflowTest, + OEMCryptoMemoryLoadLicenseForOutOfRangeCoreMessageSubstringKeyControlLengthAPI16) { + TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths( + [](size_t response_message_length, LicenseRoundTrip* license_messages) { + auto& key_control = + license_messages->core_response().key_array[0].key_control; + key_control.length = response_message_length - key_control.offset + 1; + }); +} + +/** Test that LoadLicense fails gracefully on out of range substring. */ +TEST_P( + OEMCryptoLicenseOverflowTest, + OEMCryptoMemoryLoadLicenseForOutOfRangeCoreMessageSubstringKeyControlOffset) { + TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths( + [](size_t response_message_length, LicenseRoundTrip* license_messages) { + auto& key_control = + license_messages->core_response().key_array[0].key_control; + key_control.offset = response_message_length - key_control.length + 1; + }); +} + +/** Test that LoadLicense fails gracefully on out of range substring. */ +TEST_P(OEMCryptoLicenseOverflowTest, + OEMCryptoMemoryLoadLicenseForHugeCoreMessageSubstringEncMacKeyIvLength) { + TestLoadLicenseForHugeBufferLengths( + [](size_t length, LicenseRoundTrip* license_messages) { + license_messages->core_response().enc_mac_keys_iv.length = length; + }, + !kCheckStatus, kUpdateCoreMessageSubstringValues); +} + +/** Test that LoadLicense fails gracefully on out of range substring. */ +TEST_P(OEMCryptoLicenseOverflowTest, + OEMCryptoMemoryLoadLicenseForHugeCoreMessageSubstringEncMacKeyIvOffset) { + TestLoadLicenseForHugeBufferLengths( + [](size_t offset, LicenseRoundTrip* license_messages) { + license_messages->core_response().enc_mac_keys_iv.offset = offset; + }, + !kCheckStatus, kUpdateCoreMessageSubstringValues); +} + +/** Test that LoadLicense fails gracefully on out of range substring. */ +TEST_P( + OEMCryptoLicenseOverflowTest, + OEMCryptoMemoryLoadLicenseForOutOfRangeCoreMessageSubstringEncMacKeyIvLength) { + TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths( + [](size_t response_message_length, LicenseRoundTrip* license_messages) { + auto& enc_mac_keys_iv = + license_messages->core_response().enc_mac_keys_iv; + enc_mac_keys_iv.length = + response_message_length - enc_mac_keys_iv.offset + 1; + }); +} + +/** Test that LoadLicense fails gracefully on out of range substring. */ +TEST_P( + OEMCryptoLicenseOverflowTest, + OEMCryptoMemoryLoadLicenseForOutOfRangeCoreMessageSubstringEncMacKeyIvOffset) { + TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths( + [](size_t response_message_length, LicenseRoundTrip* license_messages) { + auto& enc_mac_keys_iv = + license_messages->core_response().enc_mac_keys_iv; + enc_mac_keys_iv.offset = + response_message_length - enc_mac_keys_iv.length + 1; + }); +} + +/** Test that LoadLicense fails gracefully on out of range substring. */ +TEST_P(OEMCryptoLicenseOverflowTest, + OEMCryptoMemoryLoadLicenseForHugeCoreMessageSubstringEncMacKeyLength) { + TestLoadLicenseForHugeBufferLengths( + [](size_t length, LicenseRoundTrip* license_messages) { + license_messages->core_response().enc_mac_keys.length = length; + }, + !kCheckStatus, kUpdateCoreMessageSubstringValues); +} + +/** Test that LoadLicense fails gracefully on out of range substring. */ +TEST_P(OEMCryptoLicenseOverflowTest, + OEMCryptoMemoryLoadLicenseForHugeCoreMessageSubstringEncMacKeyOffset) { + TestLoadLicenseForHugeBufferLengths( + [](size_t offset, LicenseRoundTrip* license_messages) { + license_messages->core_response().enc_mac_keys.offset = offset; + }, + !kCheckStatus, kUpdateCoreMessageSubstringValues); +} + +/** Test that LoadLicense fails gracefully on out of range substring. */ +TEST_P( + OEMCryptoLicenseOverflowTest, + OEMCryptoMemoryLoadLicenseForOutOfRangeCoreMessageSubstringEncMacKeyLength) { + TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths( + [](size_t response_message_length, LicenseRoundTrip* license_messages) { + auto& enc_mac_keys = license_messages->core_response().enc_mac_keys; + enc_mac_keys.length = response_message_length - enc_mac_keys.offset + 1; + }); +} + +/** Test that LoadLicense fails gracefully on out of range substring. */ +TEST_P( + OEMCryptoLicenseOverflowTest, + OEMCryptoMemoryLoadLicenseForOutOfRangeCoreMessageSubstringEncMacKeyOffset) { + TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths( + [](size_t response_message_length, LicenseRoundTrip* license_messages) { + auto& enc_mac_keys = license_messages->core_response().enc_mac_keys; + enc_mac_keys.offset = response_message_length - enc_mac_keys.length + 1; + }); +} + +/** Test that LoadLicense fails gracefully on out of range substring. */ +TEST_P(OEMCryptoLicenseOverflowTest, + OEMCryptoMemoryLoadLicenseForHugeCoreMessageSubstringPstLength) { + TestLoadLicenseForHugeBufferLengths( + [](size_t length, LicenseRoundTrip* license_messages) { + license_messages->core_response().pst.length = length; + }, + !kCheckStatus, kUpdateCoreMessageSubstringValues); +} + +/** Test that LoadLicense fails gracefully on out of range substring. */ +TEST_P(OEMCryptoLicenseOverflowTest, + OEMCryptoMemoryLoadLicenseForHugeCoreMessageSubstringPstOffset) { + TestLoadLicenseForHugeBufferLengths( + [](size_t offset, LicenseRoundTrip* license_messages) { + license_messages->core_response().pst.offset = offset; + }, + !kCheckStatus, kUpdateCoreMessageSubstringValues); +} + +/** Test that LoadLicense fails gracefully on out of range substring. */ +TEST_P(OEMCryptoLicenseOverflowTest, + OEMCryptoMemoryLoadLicenseForOutOfRangeCoreMessageSubstringPstLength) { + TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths( + [](size_t response_message_length, LicenseRoundTrip* license_messages) { + auto& pst = license_messages->core_response().pst; + pst.length = response_message_length - pst.offset + 1; + }); +} + +/** Test that LoadLicense fails gracefully on out of range substring. */ +TEST_P(OEMCryptoLicenseOverflowTest, + OEMCryptoMemoryLoadLicenseForOutOfRangeCoreMessageSubstringPstOffset) { + TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths( + [](size_t response_message_length, LicenseRoundTrip* license_messages) { + auto& pst = license_messages->core_response().pst; + pst.offset = response_message_length; + if (pst.length == 0) pst.length = 1; + }); +} + +/** Test that LoadLicense fails gracefully on out of range substring. */ +TEST_P( + OEMCryptoLicenseOverflowTest, + OEMCryptoMemoryLoadLicenseForHugeCoreMessageSubstringSrmRestrictionDataLength) { + TestLoadLicenseForHugeBufferLengths( + [](size_t length, LicenseRoundTrip* license_messages) { + license_messages->core_response().srm_restriction_data.length = length; + }, + !kCheckStatus, kUpdateCoreMessageSubstringValues); +} + +/** Test that LoadLicense fails gracefully on out of range substring. */ +TEST_P( + OEMCryptoLicenseOverflowTest, + OEMCryptoMemoryLoadLicenseForHugeCoreMessageSubstringSrmRestrictionDataOffset) { + TestLoadLicenseForHugeBufferLengths( + [](size_t offset, LicenseRoundTrip* license_messages) { + license_messages->core_response().srm_restriction_data.offset = offset; + }, + !kCheckStatus, kUpdateCoreMessageSubstringValues); +} + +/** Test that LoadLicense fails gracefully on out of range substring. */ +TEST_P( + OEMCryptoLicenseOverflowTest, + OEMCryptoMemoryLoadLicenseForOutOfRangeCoreMessageSubstringSrmRestrictionDataLength) { + TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths( + [](size_t response_message_length, LicenseRoundTrip* license_messages) { + auto& srm_restriction_data = + license_messages->core_response().srm_restriction_data; + srm_restriction_data.length = + response_message_length - srm_restriction_data.offset + 1; + }); +} + +/** Test that LoadLicense fails gracefully on out of range substring. */ +TEST_P( + OEMCryptoLicenseOverflowTest, + OEMCryptoMemoryLoadLicenseForOutOfRangeCoreMessageSubstringSrmRestrictionDataOffset) { + TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths( + [](size_t response_message_length, LicenseRoundTrip* license_messages) { + auto& srm_restriction_data = + license_messages->core_response().srm_restriction_data; + srm_restriction_data.offset = response_message_length; + if (srm_restriction_data.length == 0) srm_restriction_data.length = 1; + }); +} + +/** Test that LoadLicense fails gracefully on huge buffer. */ +TEST_P(OEMCryptoLicenseOverflowTest, + OEMCryptoMemoryLoadLicenseForHugeResponseLength) { + TestLoadLicenseForHugeBufferLengths( + [](size_t message_size, LicenseRoundTrip* license_messages) { + license_messages->set_message_size(message_size); + }, + !kCheckStatus, !kUpdateCoreMessageSubstringValues); +} + +/** Test that LoadLicense fails gracefully on huge buffer. */ +TEST_P(OEMCryptoLicenseOverflowTest, + OEMCryptoMemoryLoadLicenseForHugeCoreMessageLength) { + TestLoadLicenseForHugeBufferLengths( + [](size_t message_size, LicenseRoundTrip* license_messages) { + license_messages->set_core_message_size(message_size); + }, + !kCheckStatus, !kUpdateCoreMessageSubstringValues); +} + +INSTANTIATE_TEST_SUITE_P(TestAll, OEMCryptoLicenseOverflowTest, + Range(kCurrentAPI - 1, kCurrentAPI + 1)); + +/// @} } // namespace wvoec diff --git a/oemcrypto/test/oemcrypto_test.cpp b/oemcrypto/test/oemcrypto_test.cpp index ab245cee..cba7379e 100644 --- a/oemcrypto/test/oemcrypto_test.cpp +++ b/oemcrypto/test/oemcrypto_test.cpp @@ -18,36 +18,39 @@ * be skipped. * * @defgroup license License Request Tests - * Test for requesting and loading licenses. + * Test for requesting and loading licenses. * - * @defgroup renewal License Renewal Tests - * Tests for renewing licenses. + * @defgroup renewal License Renewal Tests + * Tests for renewing licenses. * - * @defgroup decrypt Decrypt Tests - * Tests for decrypting content. + * @defgroup decrypt Decrypt Tests + * Tests for decrypting content. * - * @defgroup usage_table Usage Table Tests - * Tests that use the usage table. + * @defgroup usage_table Usage Table Tests + * Tests that use the usage table. * - * @defgroup entitle Entitlement License tests - * Tests for entitlement licenses. + * @defgroup entitle Entitlement License tests + * Tests for entitlement licenses. * - * @defgroup cas Conditional Access System Tests - * Tests for OEMCrypto implementations that support MediaCAS. + * @defgroup cas Conditional Access System Tests + * Tests for OEMCrypto implementations that support MediaCAS. * - * @defgroup cast Cast Test - * Tests for OEMCrypto implementations that support being a Cast receiver. + * @defgroup cast Cast Test + * Tests for OEMCrypto implementations that support being a Cast receiver. * - * @defgroup generic Generic Crypto Tests - * Tests for the Generic Crypto functionality. + * @defgroup android Android Tests + * Tests that enforce requirements that are specific to Android. * - * @defgroup security Security Tests - * Buffer overflow tests, off-by-one tests, and other security tests. + * @defgroup generic Generic Crypto Tests + * Tests for the Generic Crypto functionality. * - * The way the huge buffer tests work is to create a large buffer and then call - * the API. The test then loops and doubles the buffer until the API returns an - * error. An error is considered a passing test. We expect OEMCrypto to fail - * gracefully on a huge buffer rather than crashing. + * @defgroup security Security Tests + * Buffer overflow tests, off-by-one tests, and other security tests. + * + * The way the huge buffer tests work is to create a large buffer and then call + * the API. The test then loops and doubles the buffer until the API returns an + * error. An error is considered a passing test. We expect OEMCrypto to fail + * gracefully on a huge buffer rather than crashing. */ #include @@ -94,7 +97,6 @@ using ::testing::Range; using ::testing::tuple; -using ::testing::WithParamInterface; using namespace std; namespace std { // GTest wants PrintTo to be in the std namespace. @@ -129,141 +131,6 @@ void PrintTo(const tuple { - public: - OEMCryptoLicenseOverflowTest() : license_api_version_(kCurrentAPI) {} - - void SetUp() override { - OEMCryptoSessionTests::SetUp(); - license_api_version_ = GetParam(); - } - - void TearDown() override { OEMCryptoSessionTests::TearDown(); } - - void TestLoadLicenseForHugeBufferLengths( - const std::function f, bool check_status, - bool update_core_message_substring_values) { - auto oemcrypto_function = [&](size_t message_length) { - Session s; - LicenseRoundTrip license_messages(&s); - license_messages.set_api_version(license_api_version_); - s.open(); - InstallTestDrmKey(&s); - bool verify_keys_loaded = true; - license_messages.SignAndVerifyRequest(); - license_messages.CreateDefaultResponse(); - if (update_core_message_substring_values) { - // Make the license message big enough so that updated core message - // substring offset and length values from tests are still able to read - // data from license_message buffer rather than reading some garbage - // data. - license_messages.set_message_size( - sizeof(license_messages.response_data()) + message_length); - } - f(message_length, &license_messages); - if (update_core_message_substring_values) { - // We will be updating offset for these tests, which will cause verify - // keys to fail with an assertion. Hence skipping verification. - verify_keys_loaded = false; - } - license_messages.EncryptAndSignResponse(); - OEMCryptoResult result = - license_messages.LoadResponse(&s, verify_keys_loaded); - s.close(); - return result; - }; - TestHugeLengthDoesNotCrashAPI(oemcrypto_function, check_status); - } - - void TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths( - const std::function f) { - Session s; - LicenseRoundTrip license_messages(&s); - license_messages.set_api_version(license_api_version_); - s.open(); - InstallTestDrmKey(&s); - license_messages.SignAndVerifyRequest(); - license_messages.CreateDefaultResponse(); - size_t message_length = sizeof(license_messages.response_data()); - f(message_length, &license_messages); - license_messages.EncryptAndSignResponse(); - OEMCryptoResult result = license_messages.LoadResponse(); - s.close(); - // Verifying error is not due to signature failure which can be caused due - // to test code. - ASSERT_NE(OEMCrypto_ERROR_SIGNATURE_FAILURE, result); - ASSERT_NE(OEMCrypto_SUCCESS, result); - } - - protected: - uint32_t license_api_version_; -}; - -// This class is for testing a single license with the default API version -// of 16. Used for buffer overflow tests. -class OEMCryptoMemoryLicenseTest : public OEMCryptoLicenseTestAPI16 { - public: - OEMCryptoMemoryLicenseTest() : entitled_message_(&license_messages_) {} - - void SetUp() override { - OEMCryptoLicenseTestAPI16::SetUp(); - SetUpEntitledMessage(); - entitlement_response_length_ = entitled_message_.entitled_key_data_size(); - } - - void LoadLicense() { - 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()); - } - - void SetUpEntitledMessage() { - license_messages_.set_license_type(OEMCrypto_EntitlementLicense); - LoadLicense(); - entitled_message_.FillKeyArray(); - entitled_message_.EncryptContentKey(); - uint32_t key_session_id = 0; - OEMCryptoResult sts = OEMCrypto_CreateEntitledKeySession( - session_.session_id(), &key_session_id); - if (sts == OEMCrypto_ERROR_NOT_IMPLEMENTED) { - return; - } - ASSERT_EQ(OEMCrypto_SUCCESS, sts); - entitled_message_.SetEntitledKeySession(key_session_id); - } - - void TearDown() override { OEMCryptoLicenseTestAPI16::TearDown(); } - - protected: - EntitledMessage entitled_message_; - size_t entitlement_response_length_; - - void TestLoadEntitledKeysForHugeBufferLengths( - const std::function f, - bool check_status) { - size_t entitled_key_data_size = entitled_message_.entitled_key_data_size(); - vector message(entitled_key_data_size); - memcpy(message.data(), entitled_message_.entitled_key_data(), - entitled_key_data_size); - auto oemcrypto_function = [&](size_t length) { - // Make entitled message big enough so that updated substring offset and - // length fields by core message substring tests can still be able to read - // valid data from entitled message buffer rather than some garbage data. - message.resize(entitled_key_data_size + length); - f(length, &entitled_message_); - return entitled_message_.LoadKeys(message); - }; - TestHugeLengthDoesNotCrashAPI(oemcrypto_function, check_status); - } -}; - -/// @} - /// @addtogroup entitle /// @{ @@ -367,214 +234,6 @@ TEST_P(OEMCryptoEntitlementLicenseTest, INSTANTIATE_TEST_SUITE_P(TestAll, OEMCryptoEntitlementLicenseTest, Range(kCoreMessagesAPI, kCurrentAPI + 1)); -/// @} - -/// @addtogroup security -/// @{ - -/** Test that LoadEntitledContentKeys fails gracefully on huge buffer. */ -TEST_F(OEMCryptoMemoryLicenseTest, - OEMCryptoMemoryLoadEntitledKeysForHugeSubstringContentKeyIdLength) { - TestLoadEntitledKeysForHugeBufferLengths( - [](size_t key_id_length, EntitledMessage* entitled_message) { - entitled_message->entitled_key_array()[0].content_key_id.length = - key_id_length; - }, - !kCheckStatus); -} - -/** Test that LoadEntitledContentKeys fails gracefully on huge buffer. */ -TEST_F(OEMCryptoMemoryLicenseTest, - OEMCryptoMemoryLoadEntitledKeysForHugeSubstringContentKeyIdOffset) { - TestLoadEntitledKeysForHugeBufferLengths( - [](size_t key_id_offset, EntitledMessage* entitled_message) { - entitled_message->entitled_key_array()[0].content_key_id.offset = - key_id_offset; - }, - !kCheckStatus); -} - -/** Test that LoadEntitledContentKeys fails gracefully on huge buffer. */ -TEST_F( - OEMCryptoMemoryLicenseTest, - OEMCryptoMemoryLoadEntitledKeysForOutOfRangeSubstringContentKeyIdLength) { - auto& content_key_id = - entitled_message_.entitled_key_array()[0].content_key_id; - content_key_id.length = - entitlement_response_length_ - content_key_id.offset + 1; - ASSERT_NE(OEMCrypto_SUCCESS, entitled_message_.LoadKeys()); -} - -/** Test LoadEntitledContentKeys rejects out of range substring. */ -TEST_F( - OEMCryptoMemoryLicenseTest, - OEMCryptoMemoryLoadEntitledKeysForOutOfRangeSubstringContentKeyIdOffset) { - auto& content_key_id = - entitled_message_.entitled_key_array()[0].content_key_id; - content_key_id.offset = - entitlement_response_length_ - content_key_id.length + 1; - ASSERT_NE(OEMCrypto_SUCCESS, entitled_message_.LoadKeys()); -} - -/** Test that LoadEntitledContentKeys fails gracefully on huge substring. */ -TEST_F(OEMCryptoMemoryLicenseTest, - OEMCryptoMemoryLoadEntitledKeysForHugeSubstringEntitlementKeyIdLength) { - TestLoadEntitledKeysForHugeBufferLengths( - [](size_t key_id_length, EntitledMessage* entitled_message) { - entitled_message->entitled_key_array()[0].entitlement_key_id.length = - key_id_length; - }, - !kCheckStatus); -} - -/** Test LoadEntitledContentKeys rejects out of range substring. */ -TEST_F(OEMCryptoMemoryLicenseTest, - OEMCryptoMemoryLoadEntitledKeysForHugeSubstringEntitlementKeyIdOffset) { - TestLoadEntitledKeysForHugeBufferLengths( - [](size_t key_id_offset, EntitledMessage* entitled_message) { - entitled_message->entitled_key_array()[0].entitlement_key_id.offset = - key_id_offset; - }, - !kCheckStatus); -} - -/** Test LoadEntitledContentKeys rejects out of range substring. */ -TEST_F( - OEMCryptoMemoryLicenseTest, - OEMCryptoMemoryLoadEntitledKeysForOutOfRangeSubstringEntitlementKeyIdLength) { - auto& entitlement_key_id = - entitled_message_.entitled_key_array()[0].entitlement_key_id; - entitlement_key_id.length = - entitlement_response_length_ - entitlement_key_id.offset + 1; - ASSERT_NE(OEMCrypto_SUCCESS, entitled_message_.LoadKeys()); -} - -/** Test LoadEntitledContentKeys rejects out of range substring. */ -TEST_F( - OEMCryptoMemoryLicenseTest, - OEMCryptoMemoryLoadEntitledKeysForOutOfRangeSubstringEntitlementKeyIdOffset) { - auto& entitlement_key_id = - entitled_message_.entitled_key_array()[0].entitlement_key_id; - entitlement_key_id.offset = - entitlement_response_length_ - entitlement_key_id.length + 1; - ASSERT_NE(OEMCrypto_SUCCESS, entitled_message_.LoadKeys()); -} - -/** Test LoadEntitledContentKeys rejects out of range substring. */ -TEST_F(OEMCryptoMemoryLicenseTest, - OEMCryptoMemoryLoadEntitledKeysForHugeSubstringContentKeyDataIvLength) { - TestLoadEntitledKeysForHugeBufferLengths( - [](size_t content_key_data_iv_length, EntitledMessage* entitled_message) { - entitled_message->entitled_key_array()[0].content_key_data_iv.length = - content_key_data_iv_length; - }, - !kCheckStatus); -} - -/** Test LoadEntitledContentKeys rejects out of range substring. */ -TEST_F(OEMCryptoMemoryLicenseTest, - OEMCryptoMemoryLoadEntitledKeysForHugeSubstringContentKeyDataIvOffset) { - TestLoadEntitledKeysForHugeBufferLengths( - [](size_t content_key_data_iv_offset, EntitledMessage* entitled_message) { - entitled_message->entitled_key_array()[0].content_key_data_iv.offset = - content_key_data_iv_offset; - }, - !kCheckStatus); -} - -/** Test LoadEntitledContentKeys rejects out of range substring. */ -TEST_F( - OEMCryptoMemoryLicenseTest, - OEMCryptoMemoryLoadEntitledKeysForOutOfRangeSubstringContentKeyDataIvLength) { - auto& content_key_data_iv = - entitled_message_.entitled_key_array()[0].content_key_data_iv; - content_key_data_iv.length = - entitlement_response_length_ - content_key_data_iv.offset + 1; - ASSERT_NE(OEMCrypto_SUCCESS, entitled_message_.LoadKeys()); -} - -/** Test LoadEntitledContentKeys rejects out of range substring. */ -TEST_F( - OEMCryptoMemoryLicenseTest, - OEMCryptoMemoryLoadEntitledKeysForOutOfRangeSubstringContentKeyDataIvOffset) { - auto& content_key_data_iv = - entitled_message_.entitled_key_array()[0].content_key_data_iv; - content_key_data_iv.offset = - entitlement_response_length_ - content_key_data_iv.length + 1; - ASSERT_NE(OEMCrypto_SUCCESS, entitled_message_.LoadKeys()); -} - -/** Test LoadEntitledContentKeys rejects out of range substring. */ -TEST_F(OEMCryptoMemoryLicenseTest, - OEMCryptoMemoryLoadEntitledKeysForHugeSubstringContentKeyDataLength) { - TestLoadEntitledKeysForHugeBufferLengths( - [](size_t content_key_data_length, EntitledMessage* entitled_message) { - entitled_message->entitled_key_array()[0].content_key_data.length = - content_key_data_length; - }, - !kCheckStatus); -} - -/** Test LoadEntitledContentKeys rejects out of range substring. */ -TEST_F(OEMCryptoMemoryLicenseTest, - OEMCryptoMemoryLoadEntitledKeysForHugeSubstringContentKeyDataOffset) { - TestLoadEntitledKeysForHugeBufferLengths( - [](size_t content_key_data_offset, EntitledMessage* entitled_message) { - entitled_message->entitled_key_array()[0].content_key_data.offset = - content_key_data_offset; - }, - !kCheckStatus); -} - -/** Test LoadEntitledContentKeys rejects out of range substring. */ -TEST_F( - OEMCryptoMemoryLicenseTest, - OEMCryptoMemoryLoadEntitledKeysForOutOfRangeSubstringContentKeyDataLength) { - auto& content_key_data = - entitled_message_.entitled_key_array()[0].content_key_data; - content_key_data.length = - entitlement_response_length_ - content_key_data.offset + 1; - ASSERT_NE(OEMCrypto_SUCCESS, entitled_message_.LoadKeys()); -} - -/** Test LoadEntitledContentKeys rejects out of range substring. */ -TEST_F( - OEMCryptoMemoryLicenseTest, - OEMCryptoMemoryLoadEntitledKeysForOutOfRangeSubstringContentKeyDataOffset) { - auto& content_key_data = - entitled_message_.entitled_key_array()[0].content_key_data; - content_key_data.offset = - entitlement_response_length_ - content_key_data.length + 1; - ASSERT_NE(OEMCrypto_SUCCESS, entitled_message_.LoadKeys()); -} - -/** Test LoadEntitledContentKeys rejects out of range substring. */ -TEST_F(OEMCryptoMemoryLicenseTest, - OEMCryptoMemoryLoadEntitledKeysForHugeEntitlementKeyIdLength) { - TestLoadEntitledKeysForHugeBufferLengths( - [](size_t key_id_length, EntitledMessage* entitled_message) { - entitled_message->entitled_key_data()->entitlement_key_id_length = - key_id_length; - }, - !kCheckStatus); -} - -/** Test LoadEntitledContentKeys rejects out of range substring. */ -TEST_F(OEMCryptoMemoryLicenseTest, - OEMCryptoMemoryLoadEntitledKeysForHugeContentKeyIdLength) { - TestLoadEntitledKeysForHugeBufferLengths( - [](size_t key_id_length, EntitledMessage* entitled_message) { - entitled_message->entitled_key_data()->content_key_id_length = - key_id_length; - }, - !kCheckStatus); -} - -/// @} - -/// @addtogroup entitle -/// @{ - TEST_P(OEMCryptoLicenseTest, GetKeyHandleEntitledKeyAPI17) { if (wvoec::global_features.api_version < 17) { GTEST_SKIP() << "Test for versions 17 and up only."; @@ -849,441 +508,9 @@ TEST_P(OEMCryptoEntitlementLicenseTest, ReassociateEntitledKeySessionAPI17) { /// @} -/// @addtogroup security -/// @{ - -/** Test that LoadLicense fails gracefully on huge buffer. */ -TEST_P(OEMCryptoLicenseOverflowTest, - OEMCryptoMemoryLoadLicenseForHugeCoreMessageSubstringKeyIdLength) { - TestLoadLicenseForHugeBufferLengths( - [](size_t length, LicenseRoundTrip* license_messages) { - license_messages->core_response().key_array[0].key_id.length = length; - license_messages->response_data().keys[0].key_id_length = length; - }, - !kCheckStatus, kUpdateCoreMessageSubstringValues); -} - -/** Test that LoadLicense fails gracefully on out of range substring. */ -TEST_P(OEMCryptoLicenseOverflowTest, - OEMCryptoMemoryLoadLicenseForHugeCoreMessageSubstringKeyIdOffset) { - TestLoadLicenseForHugeBufferLengths( - [](size_t offset, LicenseRoundTrip* license_messages) { - license_messages->core_response().key_array[0].key_id.offset = offset; - }, - !kCheckStatus, kUpdateCoreMessageSubstringValues); -} - -/** Test that LoadLicense fails gracefully on out of range substring. */ -TEST_P(OEMCryptoLicenseOverflowTest, - OEMCryptoMemoryLoadLicenseForOutOfRangeCoreMessageSubstringKeyIdLength) { - TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths( - [](size_t response_message_length, LicenseRoundTrip* license_messages) { - auto& key_id = license_messages->core_response().key_array[0].key_id; - key_id.length = response_message_length - key_id.offset + 1; - }); -} - -/** Test that LoadLicense fails gracefully on out of range substring. */ -TEST_P(OEMCryptoLicenseOverflowTest, - OEMCryptoMemoryLoadLicenseForOutOfRangeCoreMessageSubstringKeyIdOffset) { - TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths( - [](size_t response_message_length, LicenseRoundTrip* license_messages) { - auto& key_id = license_messages->core_response().key_array[0].key_id; - key_id.offset = response_message_length - key_id.length + 1; - }); -} - -/** Test that LoadLicense fails gracefully on out of range substring. */ -TEST_P(OEMCryptoLicenseOverflowTest, - OEMCryptoMemoryLoadLicenseForHugeCoreMessageSubstringKeyDataIvLength) { - TestLoadLicenseForHugeBufferLengths( - [](size_t length, LicenseRoundTrip* license_messages) { - license_messages->core_response().key_array[0].key_data_iv.length = - length; - }, - !kCheckStatus, kUpdateCoreMessageSubstringValues); -} - -/** Test that LoadLicense fails gracefully on out of range substring. */ -TEST_P(OEMCryptoLicenseOverflowTest, - OEMCryptoMemoryLoadLicenseForHugeCoreMessageSubstringKeyDataIvOffset) { - TestLoadLicenseForHugeBufferLengths( - [](size_t offset, LicenseRoundTrip* license_messages) { - license_messages->core_response().key_array[0].key_data_iv.offset = - offset; - }, - !kCheckStatus, kUpdateCoreMessageSubstringValues); -} - -/** Test that LoadLicense fails gracefully on out of range substring. */ -TEST_P( - OEMCryptoLicenseOverflowTest, - OEMCryptoMemoryLoadLicenseForOutOfRangeCoreMessageSubstringKeyDataIvLength) { - TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths( - [](size_t response_message_length, LicenseRoundTrip* license_messages) { - auto& key_data_iv = - license_messages->core_response().key_array[0].key_data_iv; - key_data_iv.length = response_message_length - key_data_iv.offset + 1; - }); -} - -/** Test that LoadLicense fails gracefully on out of range substring. */ -TEST_P( - OEMCryptoLicenseOverflowTest, - OEMCryptoMemoryLoadLicenseForOutOfRangeCoreMessageSubstringKeyDataIvOffset) { - TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths( - [](size_t response_message_length, LicenseRoundTrip* license_messages) { - auto& key_data_iv = - license_messages->core_response().key_array[0].key_data_iv; - key_data_iv.offset = response_message_length - key_data_iv.length + 1; - }); -} - -/** Test that LoadLicense fails gracefully on out of range substring. */ -TEST_P(OEMCryptoLicenseOverflowTest, - OEMCryptoMemoryLoadLicenseForHugeCoreMessageSubstringKeyDataLength) { - TestLoadLicenseForHugeBufferLengths( - [](size_t length, LicenseRoundTrip* license_messages) { - license_messages->core_response().key_array[0].key_data.length = length; - license_messages->response_data().keys[0].key_data_length = length; - }, - !kCheckStatus, kUpdateCoreMessageSubstringValues); -} - -/** Test that LoadLicense fails gracefully on out of range substring. */ -TEST_P(OEMCryptoLicenseOverflowTest, - OEMCryptoMemoryLoadLicenseForHugeCoreMessageSubstringKeyDataOffset) { - TestLoadLicenseForHugeBufferLengths( - [](size_t offset, LicenseRoundTrip* license_messages) { - license_messages->core_response().key_array[0].key_data.offset = offset; - }, - !kCheckStatus, kUpdateCoreMessageSubstringValues); -} - -/** Test that LoadLicense fails gracefully on out of range substring. */ -TEST_P( - OEMCryptoLicenseOverflowTest, - OEMCryptoMemoryLoadLicenseForOutOfRangeCoreMessageSubstringKeyDataLength) { - TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths( - [](size_t response_message_length, LicenseRoundTrip* license_messages) { - auto& key_data = - license_messages->core_response().key_array[0].key_data; - key_data.length = response_message_length - key_data.offset + 1; - }); -} - -/** Test that LoadLicense fails gracefully on out of range substring. */ -TEST_P( - OEMCryptoLicenseOverflowTest, - OEMCryptoMemoryLoadLicenseForOutOfRangeCoreMessageSubstringKeyDataOffset) { - TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths( - [](size_t response_message_length, LicenseRoundTrip* license_messages) { - auto& key_data = - license_messages->core_response().key_array[0].key_data; - key_data.offset = response_message_length - key_data.length + 1; - }); -} - -/** Test that LoadLicense fails gracefully on out of range substring. */ -TEST_P( - OEMCryptoLicenseOverflowTest, - OEMCryptoMemoryLoadLicenseForHugeCoreMessageSubstringKeyControlIvLength) { - TestLoadLicenseForHugeBufferLengths( - [](size_t length, LicenseRoundTrip* license_messages) { - license_messages->core_response().key_array[0].key_control_iv.length = - length; - }, - !kCheckStatus, kUpdateCoreMessageSubstringValues); -} - -/** Test that LoadLicense fails gracefully on out of range substring. */ -TEST_P( - OEMCryptoLicenseOverflowTest, - OEMCryptoMemoryLoadLicenseForHugeCoreMessageSubstringKeyControlIvOffset) { - TestLoadLicenseForHugeBufferLengths( - [](size_t offset, LicenseRoundTrip* license_messages) { - license_messages->core_response().key_array[0].key_control_iv.offset = - offset; - }, - !kCheckStatus, kUpdateCoreMessageSubstringValues); -} - -/** Test that LoadLicense fails gracefully on out of range substring. */ -TEST_P( - OEMCryptoLicenseOverflowTest, - OEMCryptoMemoryLoadLicenseForOutOfRangeCoreMessageSubstringKeyControlIvLengthAPI16) { - TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths( - [](size_t response_message_length, LicenseRoundTrip* license_messages) { - auto& key_control_iv = - license_messages->core_response().key_array[0].key_control_iv; - key_control_iv.length = - response_message_length - key_control_iv.offset + 1; - }); -} - -/** Test that LoadLicense fails gracefully on out of range substring. */ -TEST_P( - OEMCryptoLicenseOverflowTest, - OEMCryptoMemoryLoadLicenseForOutOfRangeCoreMessageSubstringKeyControlIvOffset) { - TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths( - [](size_t response_message_length, LicenseRoundTrip* license_messages) { - auto& key_control_iv = - license_messages->core_response().key_array[0].key_control_iv; - key_control_iv.offset = - response_message_length - key_control_iv.length + 1; - }); -} - -/** Test that LoadLicense fails gracefully on out of range substring. */ -TEST_P(OEMCryptoLicenseOverflowTest, - OEMCryptoMemoryLoadLicenseForHugeCoreMessageSubstringKeyControlLength) { - TestLoadLicenseForHugeBufferLengths( - [](size_t length, LicenseRoundTrip* license_messages) { - license_messages->core_response().key_array[0].key_control.length = - length; - }, - !kCheckStatus, kUpdateCoreMessageSubstringValues); -} - -/** Test that LoadLicense fails gracefully on out of range substring. */ -TEST_P(OEMCryptoLicenseOverflowTest, - OEMCryptoMemoryLoadLicenseForHugeCoreMessageSubstringKeyControlOffset) { - TestLoadLicenseForHugeBufferLengths( - [](size_t offset, LicenseRoundTrip* license_messages) { - license_messages->core_response().key_array[0].key_control.offset = - offset; - }, - !kCheckStatus, kUpdateCoreMessageSubstringValues); -} - -/** Test that LoadLicense fails gracefully on out of range substring. */ -TEST_P( - OEMCryptoLicenseOverflowTest, - OEMCryptoMemoryLoadLicenseForOutOfRangeCoreMessageSubstringKeyControlLengthAPI16) { - TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths( - [](size_t response_message_length, LicenseRoundTrip* license_messages) { - auto& key_control = - license_messages->core_response().key_array[0].key_control; - key_control.length = response_message_length - key_control.offset + 1; - }); -} - -/** Test that LoadLicense fails gracefully on out of range substring. */ -TEST_P( - OEMCryptoLicenseOverflowTest, - OEMCryptoMemoryLoadLicenseForOutOfRangeCoreMessageSubstringKeyControlOffset) { - TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths( - [](size_t response_message_length, LicenseRoundTrip* license_messages) { - auto& key_control = - license_messages->core_response().key_array[0].key_control; - key_control.offset = response_message_length - key_control.length + 1; - }); -} - -/** Test that LoadLicense fails gracefully on out of range substring. */ -TEST_P(OEMCryptoLicenseOverflowTest, - OEMCryptoMemoryLoadLicenseForHugeCoreMessageSubstringEncMacKeyIvLength) { - TestLoadLicenseForHugeBufferLengths( - [](size_t length, LicenseRoundTrip* license_messages) { - license_messages->core_response().enc_mac_keys_iv.length = length; - }, - !kCheckStatus, kUpdateCoreMessageSubstringValues); -} - -/** Test that LoadLicense fails gracefully on out of range substring. */ -TEST_P(OEMCryptoLicenseOverflowTest, - OEMCryptoMemoryLoadLicenseForHugeCoreMessageSubstringEncMacKeyIvOffset) { - TestLoadLicenseForHugeBufferLengths( - [](size_t offset, LicenseRoundTrip* license_messages) { - license_messages->core_response().enc_mac_keys_iv.offset = offset; - }, - !kCheckStatus, kUpdateCoreMessageSubstringValues); -} - -/** Test that LoadLicense fails gracefully on out of range substring. */ -TEST_P( - OEMCryptoLicenseOverflowTest, - OEMCryptoMemoryLoadLicenseForOutOfRangeCoreMessageSubstringEncMacKeyIvLength) { - TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths( - [](size_t response_message_length, LicenseRoundTrip* license_messages) { - auto& enc_mac_keys_iv = - license_messages->core_response().enc_mac_keys_iv; - enc_mac_keys_iv.length = - response_message_length - enc_mac_keys_iv.offset + 1; - }); -} - -/** Test that LoadLicense fails gracefully on out of range substring. */ -TEST_P( - OEMCryptoLicenseOverflowTest, - OEMCryptoMemoryLoadLicenseForOutOfRangeCoreMessageSubstringEncMacKeyIvOffset) { - TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths( - [](size_t response_message_length, LicenseRoundTrip* license_messages) { - auto& enc_mac_keys_iv = - license_messages->core_response().enc_mac_keys_iv; - enc_mac_keys_iv.offset = - response_message_length - enc_mac_keys_iv.length + 1; - }); -} - -/** Test that LoadLicense fails gracefully on out of range substring. */ -TEST_P(OEMCryptoLicenseOverflowTest, - OEMCryptoMemoryLoadLicenseForHugeCoreMessageSubstringEncMacKeyLength) { - TestLoadLicenseForHugeBufferLengths( - [](size_t length, LicenseRoundTrip* license_messages) { - license_messages->core_response().enc_mac_keys.length = length; - }, - !kCheckStatus, kUpdateCoreMessageSubstringValues); -} - -/** Test that LoadLicense fails gracefully on out of range substring. */ -TEST_P(OEMCryptoLicenseOverflowTest, - OEMCryptoMemoryLoadLicenseForHugeCoreMessageSubstringEncMacKeyOffset) { - TestLoadLicenseForHugeBufferLengths( - [](size_t offset, LicenseRoundTrip* license_messages) { - license_messages->core_response().enc_mac_keys.offset = offset; - }, - !kCheckStatus, kUpdateCoreMessageSubstringValues); -} - -/** Test that LoadLicense fails gracefully on out of range substring. */ -TEST_P( - OEMCryptoLicenseOverflowTest, - OEMCryptoMemoryLoadLicenseForOutOfRangeCoreMessageSubstringEncMacKeyLength) { - TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths( - [](size_t response_message_length, LicenseRoundTrip* license_messages) { - auto& enc_mac_keys = license_messages->core_response().enc_mac_keys; - enc_mac_keys.length = response_message_length - enc_mac_keys.offset + 1; - }); -} - -/** Test that LoadLicense fails gracefully on out of range substring. */ -TEST_P( - OEMCryptoLicenseOverflowTest, - OEMCryptoMemoryLoadLicenseForOutOfRangeCoreMessageSubstringEncMacKeyOffset) { - TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths( - [](size_t response_message_length, LicenseRoundTrip* license_messages) { - auto& enc_mac_keys = license_messages->core_response().enc_mac_keys; - enc_mac_keys.offset = response_message_length - enc_mac_keys.length + 1; - }); -} - -/** Test that LoadLicense fails gracefully on out of range substring. */ -TEST_P(OEMCryptoLicenseOverflowTest, - OEMCryptoMemoryLoadLicenseForHugeCoreMessageSubstringPstLength) { - TestLoadLicenseForHugeBufferLengths( - [](size_t length, LicenseRoundTrip* license_messages) { - license_messages->core_response().pst.length = length; - }, - !kCheckStatus, kUpdateCoreMessageSubstringValues); -} - -/** Test that LoadLicense fails gracefully on out of range substring. */ -TEST_P(OEMCryptoLicenseOverflowTest, - OEMCryptoMemoryLoadLicenseForHugeCoreMessageSubstringPstOffset) { - TestLoadLicenseForHugeBufferLengths( - [](size_t offset, LicenseRoundTrip* license_messages) { - license_messages->core_response().pst.offset = offset; - }, - !kCheckStatus, kUpdateCoreMessageSubstringValues); -} - -/** Test that LoadLicense fails gracefully on out of range substring. */ -TEST_P(OEMCryptoLicenseOverflowTest, - OEMCryptoMemoryLoadLicenseForOutOfRangeCoreMessageSubstringPstLength) { - TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths( - [](size_t response_message_length, LicenseRoundTrip* license_messages) { - auto& pst = license_messages->core_response().pst; - pst.length = response_message_length - pst.offset + 1; - }); -} - -/** Test that LoadLicense fails gracefully on out of range substring. */ -TEST_P(OEMCryptoLicenseOverflowTest, - OEMCryptoMemoryLoadLicenseForOutOfRangeCoreMessageSubstringPstOffset) { - TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths( - [](size_t response_message_length, LicenseRoundTrip* license_messages) { - auto& pst = license_messages->core_response().pst; - pst.offset = response_message_length; - if (pst.length == 0) pst.length = 1; - }); -} - -/** Test that LoadLicense fails gracefully on out of range substring. */ -TEST_P( - OEMCryptoLicenseOverflowTest, - OEMCryptoMemoryLoadLicenseForHugeCoreMessageSubstringSrmRestrictionDataLength) { - TestLoadLicenseForHugeBufferLengths( - [](size_t length, LicenseRoundTrip* license_messages) { - license_messages->core_response().srm_restriction_data.length = length; - }, - !kCheckStatus, kUpdateCoreMessageSubstringValues); -} - -/** Test that LoadLicense fails gracefully on out of range substring. */ -TEST_P( - OEMCryptoLicenseOverflowTest, - OEMCryptoMemoryLoadLicenseForHugeCoreMessageSubstringSrmRestrictionDataOffset) { - TestLoadLicenseForHugeBufferLengths( - [](size_t offset, LicenseRoundTrip* license_messages) { - license_messages->core_response().srm_restriction_data.offset = offset; - }, - !kCheckStatus, kUpdateCoreMessageSubstringValues); -} - -/** Test that LoadLicense fails gracefully on out of range substring. */ -TEST_P( - OEMCryptoLicenseOverflowTest, - OEMCryptoMemoryLoadLicenseForOutOfRangeCoreMessageSubstringSrmRestrictionDataLength) { - TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths( - [](size_t response_message_length, LicenseRoundTrip* license_messages) { - auto& srm_restriction_data = - license_messages->core_response().srm_restriction_data; - srm_restriction_data.length = - response_message_length - srm_restriction_data.offset + 1; - }); -} - -/** Test that LoadLicense fails gracefully on out of range substring. */ -TEST_P( - OEMCryptoLicenseOverflowTest, - OEMCryptoMemoryLoadLicenseForOutOfRangeCoreMessageSubstringSrmRestrictionDataOffset) { - TestLoadLicenseForOutOfRangeSubStringOffSetAndLengths( - [](size_t response_message_length, LicenseRoundTrip* license_messages) { - auto& srm_restriction_data = - license_messages->core_response().srm_restriction_data; - srm_restriction_data.offset = response_message_length; - if (srm_restriction_data.length == 0) srm_restriction_data.length = 1; - }); -} - -/** Test that LoadLicense fails gracefully on huge buffer. */ -TEST_P(OEMCryptoLicenseOverflowTest, - OEMCryptoMemoryLoadLicenseForHugeResponseLength) { - TestLoadLicenseForHugeBufferLengths( - [](size_t message_size, LicenseRoundTrip* license_messages) { - license_messages->set_message_size(message_size); - }, - !kCheckStatus, !kUpdateCoreMessageSubstringValues); -} - -/** Test that LoadLicense fails gracefully on huge buffer. */ -TEST_P(OEMCryptoLicenseOverflowTest, - OEMCryptoMemoryLoadLicenseForHugeCoreMessageLength) { - TestLoadLicenseForHugeBufferLengths( - [](size_t message_size, LicenseRoundTrip* license_messages) { - license_messages->set_core_message_size(message_size); - }, - !kCheckStatus, !kUpdateCoreMessageSubstringValues); -} - -INSTANTIATE_TEST_SUITE_P(TestAll, OEMCryptoLicenseOverflowTest, - Range(kCurrentAPI - 1, kCurrentAPI + 1)); - -/// @} - /// @addtogroup cas /// @{ + TEST_P(OEMCryptoEntitlementLicenseTest, CasOnlyLoadCasKeysAPI17) { if (wvoec::global_features.api_version < 17) { GTEST_SKIP() << "Test for versions 17 and up only."; @@ -1538,335 +765,12 @@ TEST_P(OEMCryptoLicenseTest, EntitledKeySessionMultipleKeySessionsAPI17) { reinterpret_cast(content_key_id_2), strlen(content_key_id_2))); } + /// @} -/// @addtogroup security +/// @addtogroup cas /// @{ -/** Test that LoadProvisioning fails gracefully on huge buffer. */ -TEST_F(OEMCryptoLoadsCertificate, - OEMCryptoMemoryLoadProvisioningForHugeResponseLength) { - // TODO(b/197141970): Need to revisit OEMCryptoLoadsCert* tests for - // provisioning 4. Disabled here temporarily. - if (!global_features.loads_certificate || - global_features.provisioning_method == OEMCrypto_BootCertificateChain) { - GTEST_SKIP() << "Test for non Prov 4.0 devices only."; - } - TestLoadProvisioningForHugeBufferLengths( - [](size_t message_size, ProvisioningRoundTrip* provisioning_messages) { - provisioning_messages->set_message_size(message_size); - }, - !kCheckStatus, !kUpdateCoreMessageSubstringValues); -} - -/** Test that LoadProvisioning fails gracefully on huge buffer. */ -TEST_F(OEMCryptoLoadsCertificate, - OEMCryptoMemoryLoadProvisioningForHugeCoreMessageLength) { - // TODO(b/197141970): Need to revisit OEMCryptoLoadsCert* tests for - // provisioning 4. Disabled here temporarily. - if (!global_features.loads_certificate || - global_features.provisioning_method == OEMCrypto_BootCertificateChain) { - GTEST_SKIP() << "Test for non Prov 4.0 devices only."; - } - TestLoadProvisioningForHugeBufferLengths( - [](size_t message_size, ProvisioningRoundTrip* provisioning_messages) { - provisioning_messages->set_core_message_size(message_size); - }, - !kCheckStatus, !kUpdateCoreMessageSubstringValues); -} - -/** Test that LoadProvisioning fails gracefully on huge buffer. */ -TEST_F(OEMCryptoLoadsCertificate, - OEMCryptoMemoryLoadProvisioningForHugeCoreMessageEncPrivateKeyLength) { - // TODO(b/197141970): Need to revisit OEMCryptoLoadsCert* tests for - // provisioning 4. Disabled here temporarily. - if (!global_features.loads_certificate || - global_features.provisioning_method == OEMCrypto_BootCertificateChain) { - GTEST_SKIP() << "Test for non Prov 4.0 devices only."; - } - TestLoadProvisioningForHugeBufferLengths( - [](size_t length, ProvisioningRoundTrip* provisioning_messages) { - provisioning_messages->core_response().enc_private_key.length = length; - }, - !kCheckStatus, kUpdateCoreMessageSubstringValues); -} - -/** Test that LoadProvisioning fails gracefully on huge buffer. */ -TEST_F(OEMCryptoLoadsCertificate, - OEMCryptoMemoryLoadProvisioningForHugeCoreMessageEncPrivateKeyOffset) { - // TODO(b/197141970): Need to revisit OEMCryptoLoadsCert* tests for - // provisioning 4. Disabled here temporarily. - if (!global_features.loads_certificate || - global_features.provisioning_method == OEMCrypto_BootCertificateChain) { - GTEST_SKIP() << "Test for non Prov 4.0 devices only."; - } - TestLoadProvisioningForHugeBufferLengths( - [](size_t offset, ProvisioningRoundTrip* provisioning_messages) { - provisioning_messages->core_response().enc_private_key.offset = offset; - }, - !kCheckStatus, kUpdateCoreMessageSubstringValues); -} - -/** Test that LoadProvisioning fails gracefully on out of range substring. */ -TEST_F( - OEMCryptoLoadsCertificate, - OEMCryptoMemoryLoadProvisioningForOutOfRangeCoreMessageEncPrivateKeyLength) { - // TODO(b/197141970): Need to revisit OEMCryptoLoadsCert* tests for - // provisioning 4. Disabled here temporarily. - if (!global_features.loads_certificate || - global_features.provisioning_method == OEMCrypto_BootCertificateChain) { - GTEST_SKIP() << "Test for non Prov 4.0 devices only."; - } - TestLoadProvisioningForOutOfRangeSubstringOffsetAndLengths( - [](size_t response_message_length, - ProvisioningRoundTrip* provisioning_messages) { - auto& enc_private_key = - provisioning_messages->core_response().enc_private_key; - enc_private_key.length = - response_message_length - enc_private_key.offset + 1; - }); -} - -/** Test that LoadProvisioning fails gracefully on out of range substring. */ -TEST_F( - OEMCryptoLoadsCertificate, - OEMCryptoMemoryLoadProvisioningForOutOfRangeCoreMessageEncPrivateKeyOffset) { - // TODO(b/197141970): Need to revisit OEMCryptoLoadsCert* tests for - // provisioning 4. Disabled here temporarily. - if (!global_features.loads_certificate || - global_features.provisioning_method == OEMCrypto_BootCertificateChain) { - GTEST_SKIP() << "Test for non Prov 4.0 devices only."; - } - TestLoadProvisioningForOutOfRangeSubstringOffsetAndLengths( - [](size_t response_message_length, - ProvisioningRoundTrip* provisioning_messages) { - auto& enc_private_key = - provisioning_messages->core_response().enc_private_key; - enc_private_key.offset = - response_message_length - enc_private_key.length + 1; - }); -} - -/** Test that LoadProvisioning fails gracefully on out of range substring. */ -TEST_F(OEMCryptoLoadsCertificate, - OEMCryptoMemoryLoadProvisioningForHugeCoreMessageEncPrivateKeyIvLength) { - // TODO(b/197141970): Need to revisit OEMCryptoLoadsCert* tests for - // provisioning 4. Disabled here temporarily. - if (!global_features.loads_certificate || - global_features.provisioning_method == OEMCrypto_BootCertificateChain) { - GTEST_SKIP() << "Test for non Prov 4.0 devices only."; - } - TestLoadProvisioningForHugeBufferLengths( - [](size_t length, ProvisioningRoundTrip* provisioning_messages) { - provisioning_messages->core_response().enc_private_key_iv.length = - length; - }, - !kCheckStatus, kUpdateCoreMessageSubstringValues); -} - -/** Test that LoadProvisioning fails gracefully on out of range substring. */ -TEST_F(OEMCryptoLoadsCertificate, - OEMCryptoMemoryLoadProvisioningForHugeCoreMessageEncPrivateKeyIvOffset) { - // TODO(b/197141970): Need to revisit OEMCryptoLoadsCert* tests for - // provisioning 4. Disabled here temporarily. - if (!global_features.loads_certificate || - global_features.provisioning_method == OEMCrypto_BootCertificateChain) { - GTEST_SKIP() << "Test for non Prov 4.0 devices only."; - } - TestLoadProvisioningForHugeBufferLengths( - [](size_t offset, ProvisioningRoundTrip* provisioning_messages) { - provisioning_messages->core_response().enc_private_key_iv.offset = - offset; - }, - !kCheckStatus, kUpdateCoreMessageSubstringValues); -} - -/** Test that LoadProvisioning fails gracefully on out of range substring. */ -TEST_F( - OEMCryptoLoadsCertificate, - OEMCryptoMemoryLoadProvisioningForOutOfRangeCoreMessageEncPrivateKeyIvLengthAPI16) { - // TODO(b/197141970): Need to revisit OEMCryptoLoadsCert* tests for - // provisioning 4. Disabled here temporarily. - if (!global_features.loads_certificate || - global_features.provisioning_method == OEMCrypto_BootCertificateChain) { - GTEST_SKIP() << "Test for non Prov 4.0 devices only."; - } - TestLoadProvisioningForOutOfRangeSubstringOffsetAndLengths( - [](size_t response_message_length, - ProvisioningRoundTrip* provisioning_messages) { - auto& enc_private_key_iv = - provisioning_messages->core_response().enc_private_key_iv; - enc_private_key_iv.length = - response_message_length - enc_private_key_iv.offset + 1; - }); -} - -/** Test that LoadProvisioning fails gracefully on out of range substring. */ -TEST_F( - OEMCryptoLoadsCertificate, - OEMCryptoMemoryLoadProvisioningForOutOfRangeCoreMessageEncPrivateKeyIvOffset) { - // TODO(b/197141970): Need to revisit OEMCryptoLoadsCert* tests for - // provisioning 4. Disabled here temporarily. - if (!global_features.loads_certificate || - global_features.provisioning_method == OEMCrypto_BootCertificateChain) { - GTEST_SKIP() << "Test for non Prov 4.0 devices only."; - } - TestLoadProvisioningForOutOfRangeSubstringOffsetAndLengths( - [](size_t response_message_length, - ProvisioningRoundTrip* provisioning_messages) { - auto& enc_private_key_iv = - provisioning_messages->core_response().enc_private_key_iv; - enc_private_key_iv.offset = - response_message_length - enc_private_key_iv.length + 1; - }); -} - -/** Test that LoadProvisioning fails gracefully on out of range substring. */ -TEST_F(OEMCryptoLoadsCertificate, - OEMCryptoMemoryLoadProvisioningForHugeCoreMessageEncMessageKeyLength) { - // TODO(b/197141970): Need to revisit OEMCryptoLoadsCert* tests for - // provisioning 4. Disabled here temporarily. - if (!global_features.loads_certificate || - global_features.provisioning_method == OEMCrypto_BootCertificateChain) { - GTEST_SKIP() << "Test for non Prov 4.0 devices only."; - } - TestLoadProvisioningForHugeBufferLengths( - [](size_t length, ProvisioningRoundTrip* provisioning_messages) { - provisioning_messages->core_response().encrypted_message_key.length = - length; - }, - !kCheckStatus, kUpdateCoreMessageSubstringValues); -} - -/** Test that LoadProvisioning fails gracefully on out of range substring. */ -TEST_F(OEMCryptoLoadsCertificate, - OEMCryptoMemoryLoadProvisioningForHugeCoreMessageEncMessageKeyOffset) { - // TODO(b/197141970): Need to revisit OEMCryptoLoadsCert* tests for - // provisioning 4. Disabled here temporarily. - if (!global_features.loads_certificate || - global_features.provisioning_method == OEMCrypto_BootCertificateChain) { - GTEST_SKIP() << "Test for non Prov 4.0 devices only."; - } - TestLoadProvisioningForHugeBufferLengths( - [](size_t offset, ProvisioningRoundTrip* provisioning_messages) { - provisioning_messages->core_response().encrypted_message_key.offset = - offset; - }, - !kCheckStatus, kUpdateCoreMessageSubstringValues); -} - -/** Test that LoadProvisioning fails gracefully on out of range substring. */ -TEST_F( - OEMCryptoLoadsCertificate, - OEMCryptoMemoryLoadProvisioningForOutOfRangeCoreMessageEncMessageKeyLengthProv30) { - // TODO(b/197141970): Need to revisit OEMCryptoLoadsCert* tests for - // provisioning 4. Disabled here temporarily. - if (!global_features.loads_certificate || - global_features.provisioning_method == OEMCrypto_BootCertificateChain) { - GTEST_SKIP() << "Test for non Prov 4.0 devices only."; - } - if (global_features.provisioning_method != OEMCrypto_OEMCertificate) { - GTEST_SKIP() << "Test for Prov 3.0 devices only."; - } - TestLoadProvisioningForOutOfRangeSubstringOffsetAndLengths( - [](size_t response_message_length, - ProvisioningRoundTrip* provisioning_messages) { - auto& encrypted_message_key = - provisioning_messages->core_response().encrypted_message_key; - encrypted_message_key.length = - response_message_length - encrypted_message_key.offset + 1; - }); -} - -/** Test that LoadProvisioning fails gracefully on out of range substring. */ -TEST_F( - OEMCryptoLoadsCertificate, - OEMCryptoMemoryLoadProvisioningForOutOfRangeCoreMessageEncMessageKeyOffsetProv30) { - // TODO(b/197141970): Need to revisit OEMCryptoLoadsCert* tests for - // provisioning 4. Disabled here temporarily. - if (!global_features.loads_certificate || - global_features.provisioning_method == OEMCrypto_BootCertificateChain) { - GTEST_SKIP() << "Test for non Prov 4.0 devices only."; - } - if (global_features.provisioning_method != OEMCrypto_OEMCertificate) { - GTEST_SKIP() << "Test for Prov 3.0 devices only."; - } - TestLoadProvisioningForOutOfRangeSubstringOffsetAndLengths( - [](size_t response_message_length, - ProvisioningRoundTrip* provisioning_messages) { - auto& encrypted_message_key = - provisioning_messages->core_response().encrypted_message_key; - encrypted_message_key.offset = - response_message_length - encrypted_message_key.length + 1; - }); -} - -/// @} - -/// @addtogroup security -/// @{ - -/** Test that OEMCrypto_PrepAndSignProvisioningRequest fails gracefully on a - * huge buffer. - */ -TEST_F(OEMCryptoLoadsCertificate, - OEMCryptoMemoryPrepareProvisioningRequestForHugeRequestMessageLength) { - // TODO(b/197141970): Need to revisit OEMCryptoLoadsCert* tests for - // provisioning 4. Disabled here temporarily. - if (!global_features.loads_certificate || - global_features.provisioning_method == OEMCrypto_BootCertificateChain) { - GTEST_SKIP() << "Test for non Prov 4.0 devices only."; - } - TestPrepareProvisioningRequestForHugeBufferLengths( - [](size_t message_size, ProvisioningRoundTrip* provisioning_messages) { - provisioning_messages->set_message_size(message_size); - }, - kCheckStatus); -} - -/** Test that OEMCrypto_PrepAndSignProvisioningRequest fails gracefully on a - * huge buffer. - */ -TEST_F(OEMCryptoLoadsCertificate, - OEMCryptoMemoryPrepareProvisioningRequestForHugeSignatureLength) { - // TODO(b/197141970): Need to revisit OEMCryptoLoadsCert* tests for - // provisioning 4. Disabled here temporarily. - if (!global_features.loads_certificate || - global_features.provisioning_method == OEMCrypto_BootCertificateChain) { - GTEST_SKIP() << "Test for non Prov 4.0 devices only."; - } - TestPrepareProvisioningRequestForHugeBufferLengths( - [](size_t message_size, ProvisioningRoundTrip* provisioning_messages) { - provisioning_messages->set_request_signature_size(message_size); - }, - !kCheckStatus); -} - -/** Test that OEMCrypto_PrepAndSignProvisioningRequest fails gracefully on a - * huge buffer. - */ -TEST_F(OEMCryptoLoadsCertificate, - OEMCryptoMemoryPrepareProvisioningRequestForHugeCoreMessageLength) { - // TODO(b/197141970): Need to revisit OEMCryptoLoadsCert* tests for - // provisioning 4. Disabled here temporarily. - if (!global_features.loads_certificate || - global_features.provisioning_method == OEMCrypto_BootCertificateChain) { - GTEST_SKIP() << "Test for non Prov 4.0 devices only."; - } - TestPrepareProvisioningRequestForHugeBufferLengths( - [](size_t message_size, ProvisioningRoundTrip* provisioning_messages) { - provisioning_messages->set_core_message_size(message_size); - }, - kCheckStatus); -} - -/// @} - -/// @addtogroup cast -/// @{ - -/// @} - #ifdef CAS_TEST # include "tuner_hal.h" @@ -1952,4 +856,6 @@ INSTANTIATE_TEST_SUITE_P(TestAll, OEMCryptoCasDemoTest, Range(kCoreMessagesAPI, kCurrentAPI + 1)); #endif + +/// @} } // namespace wvoec diff --git a/oemcrypto/test/oemcrypto_test_android.cpp b/oemcrypto/test/oemcrypto_test_android.cpp index 9f1e8910..b23a8413 100644 --- a/oemcrypto/test/oemcrypto_test_android.cpp +++ b/oemcrypto/test/oemcrypto_test_android.cpp @@ -21,6 +21,9 @@ namespace wvoec { +/// @addtogroup android +/// @{ + /** These tests are required for LollyPop Android devices.*/ class OEMCryptoAndroidLMPTest : public ::testing::Test { protected: @@ -102,7 +105,7 @@ TEST_F(OEMCryptoAndroidMNCTest, LoadsTestKeyboxImplemented) { } /** Android requires implementation of functions that report how many open - * sesions are available. */ + * sessions are available. */ TEST_F(OEMCryptoAndroidMNCTest, NumberOfSessionsImplemented) { ASSERT_NE(OEMCrypto_ERROR_NOT_IMPLEMENTED, OEMCrypto_GetNumberOfOpenSessions(nullptr)); @@ -126,4 +129,5 @@ TEST_F(OEMCryptoAndroidRVCTest, MinVersionNumber16) { ASSERT_GE(version, 16u); } +/// @} } // namespace wvoec diff --git a/oemcrypto/test/oemcrypto_unittests.gypi b/oemcrypto/test/oemcrypto_unittests.gypi index 3ccd422e..7b2c8ba9 100644 --- a/oemcrypto/test/oemcrypto_unittests.gypi +++ b/oemcrypto/test/oemcrypto_unittests.gypi @@ -23,6 +23,7 @@ 'oemcrypto_generic_crypto_test.cpp', 'oemcrypto_license_test.cpp', 'oemcrypto_provisioning_test.cpp', + 'oemcrypto_security_test.cpp', 'oemcrypto_usage_table_test.cpp', 'oemcrypto_test.cpp', '<(jsmn_dir)/jsmn.c', diff --git a/oemcrypto/test/oemcrypto_usage_table_test.cpp b/oemcrypto/test/oemcrypto_usage_table_test.cpp index d9ae34f5..bae0c9cf 100644 --- a/oemcrypto/test/oemcrypto_usage_table_test.cpp +++ b/oemcrypto/test/oemcrypto_usage_table_test.cpp @@ -10,6 +10,9 @@ using ::testing::Values; namespace wvoec { +/// @addtogroup usage_table +/// @{ + // Test that successive calls to PrepAndSignProvisioningRequest only increase // the provisioning count in the ODK message TEST_F(OEMCryptoSessionTests, Provisioning_IncrementCounterAPI18) { @@ -1810,4 +1813,5 @@ INSTANTIATE_TEST_SUITE_P(TestAPI16, OEMCryptoUsageTableDefragTest, INSTANTIATE_TEST_SUITE_P(TestAPI16, OEMCryptoUsageTableTestWallClock, Values(kCurrentAPI)); +/// @} } // namespace wvoec diff --git a/oemcrypto/util/include/bcc_validator.h b/oemcrypto/util/include/bcc_validator.h index 77c753c2..adc84a6f 100644 --- a/oemcrypto/util/include/bcc_validator.h +++ b/oemcrypto/util/include/bcc_validator.h @@ -7,15 +7,10 @@ #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" +#include "prov4_validation_helper.h" namespace wvoec { namespace util { @@ -34,11 +29,99 @@ enum BccCurve { kBccP384 = 3 }; +// Android/Widevine Dice Attestation allows two signing models. This is +// identified using MAP_KEY_DEVICE_KEY_ALGORITHM. +enum { + DEVICE_KEY_ALGORITHM_ES256 = -7, // EC key with SHA-256 + DEVICE_KEY_ALGORITHM_EDDSA = -8, // Pure ED25519. + DEVICE_KEY_ALGORITHM_ES384 = -35, // EC key with SHA-384 +}; + +// BCC definition: +// https://source.corp.google.com/h/googleplex-android/platform/superproject/main/+/main:hardware/interfaces/security/rkp/aidl/android/hardware/security/keymint/generateCertificateRequestV2.cddl + +// See PubKeyEd25519/PubKeyECDSA256/PubKeyECDSA384 in BCC definition. struct BccPublicKeyInfo { - BccSignatureAlgorithm signature_algorithm; - BccCurve curve; + std::pair key_type; + std::pair signature_algorithm; + std::pair curve; // Raw EC key bytes extracted from BCC - std::vector key_bytes; + std::pair> key_bytes; + std::string ToString() const; + CborMessageStatus Validate( + std::vector>& msgs) const; +}; + +// protected : bstr .cbor { 1 : AlgorithmEdDSA / AlgorithmES256 / AlgorithmES384 +// } +struct BccEntryProtected { + std::pair algorithm; + std::string ToString() const; + CborMessageStatus Validate( + std::vector>& msgs) const; +}; + +// See ConfigurationDescriptor in BCC definition. +struct ConfigurationDescriptor { + std::pair component_name; + std::pair component_version; + std::pair resettable; // null string + std::pair security_version; + std::pair vm_marker; // null string + std::string ToString() const; + // Validate ConfigurationDescriptor and set |is_widevine_entry| to true if the + // component_name is "widevine". Caller ensures that |is_widevine_entry| is + // not null. + CborMessageStatus Validate( + std::vector>& msgs, + bool* is_widevine_entry) const; +}; + +// See DiceChainEntryPayload in BCC definition. +struct BccEntryPayload { + std::pair issuer; + std::pair subject; + std::pair profile_name; + std::pair subject_public_key; + std::pair> key_usage; + std::pair> code_hash; + std::pair> code_descriptor; + std::pair> config_hash; + std::pair config_descriptor; + std::pair> authority_hash; + std::pair> authority_descriptor; + std::pair> mode; + std::string ToString() const; + // Validate BccEntryPayload and set |is_widevine_entry| to true if the payload + // contains a Widevine certificate. Caller ensures that |is_widevine_entry| is + // not null. + CborMessageStatus Validate( + std::vector>& msgs, + bool is_degenerated, bool* is_widevine_entry) const; +}; + +// See DiceChainEntry in BCC definition. +struct BccEntry { + std::pair protected_data; + std::pair unprotected; + std::pair payload; + std::pair> signature; + std::string ToString() const; + // Validate BccEntryPayload and set |is_widevine_entry| to true if the BCC + // entry contains a Widevine certificate. Caller ensures that + // |is_widevine_entry| is not null. + CborMessageStatus Validate( + std::vector>& msgs, + bool is_degenerated, bool* is_widevine_entry) const; +}; + +struct Bcc { + BccPublicKeyInfo dk_pub; + std::vector entries; + std::string ToString() const; + CborMessageStatus Validate( + std::vector>& msgs, + bool is_degenerated) const; }; // BccValidator processes a Provisioning 4.0 device root of trust. It extracts @@ -52,25 +135,34 @@ class BccValidator : public CborValidator { virtual ~BccValidator() override = default; 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 - // Widevine device. + // Verifies the Cbor struct of a client generated root of trust. virtual CborMessageStatus Validate() override; - // Outputs BCC in YAML. + // Outputs formatted BCC. virtual std::string GetFormattedMessage() const override; private: - // Processes CoseKey PubKeyEd25519 / PubKeyECDSA256, prints into |fmt_msgs|, - // and extracts the PubKey to *|public_key_info|. + // Processes CoseKey PubKeyEd25519 / PubKeyECDSA256 / PubKeyECDSA384, which + // contains subject public key, and extracts the PubKey to *|public_key_info|. + // Caller ensures that all pointers are not null. CborMessageStatus ProcessSubjectPublicKeyInfo( - const cppbor::Map& public_key_info_map, - std::vector& fmt_msgs, BccPublicKeyInfo* public_key_info); - // Processes DiceChainEntryPayload, which contains subject public key, prints - // into |fmt_msgs|, and extracts the PubKey to *|public_key_info|. - CborMessageStatus ProcessDiceChainEntryPayload( - const std::vector& payload, std::vector& fmt_msgs, - BccPublicKeyInfo* public_key_info); + const cppbor::Map* public_key_map, BccPublicKeyInfo* public_key_info); + + // Processes protected field in Bcc entry and extracts it *|protected_data|. + // Caller ensures that all pointers are not null. + CborMessageStatus ProcessBccEntryProtected(const cppbor::Map* protected_map, + BccEntryProtected* protected_data); + + // Processes DiceChainEntryPayload and extracts the payload to *|payload|. + // Caller ensures that all pointers are not null. + CborMessageStatus ProcessDiceChainEntryPayload(const cppbor::Map* payload_map, + BccEntryPayload* payload); + + // Processes ConfigurationDescriptor in DiceChainEntryPayload and extracts the + // ConfigurationDescriptor to *|cd|. Caller ensures that all pointers are not + // null. + CborMessageStatus ProcessConfigurationDescriptor( + const cppbor::Map* config_descriptor_map, ConfigurationDescriptor* cd); + // Verifies the raw EC signature |signature| with the public key // |signing_key|. |signature| extracted from BCC is not ASN.1 DER encoded. bool VerifySignature(const BccPublicKeyInfo& signing_key, diff --git a/oemcrypto/util/include/cbor_validator.h b/oemcrypto/util/include/cbor_validator.h index 9c67bbf9..ddc9dce3 100644 --- a/oemcrypto/util/include/cbor_validator.h +++ b/oemcrypto/util/include/cbor_validator.h @@ -7,12 +7,6 @@ #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" @@ -28,8 +22,9 @@ enum CborMessageStatus { kCborParseError = 2, kCborValidateOk = 3, kCborValidateWarning = 4, - kCborValidateError = 5, - kCborValidateFatal = 6 + kCborValidateError = 5, // e.g. unexpected value, signature error, etc. + kCborValidateFatal = + 6, // e.g. unexpected data type, key size, or missing required field }; std::string CppborMajorTypeToString(cppbor::MajorType type); @@ -61,10 +56,6 @@ class CborValidator { // first and |parse_result_| contains a valid CBOR message. virtual std::string GetFormattedMessage() const; const cppbor::ParseResult& parse_result() const { return parse_result_; } - const std::vector>& - validate_messages() { - return validate_messages_; - } protected: void Reset(); @@ -77,6 +68,8 @@ class CborValidator { static std::string CheckMapEntry(const cppbor::Map& map, cppbor::MajorType major_type, const std::string& entry_name); + // Formats the parsed CBOR |input| and adds identation for readability. + static std::string FormatString(const std::string& input); CborMessageStatus message_status_ = kCborUninitialized; private: diff --git a/oemcrypto/util/include/device_info_validator.h b/oemcrypto/util/include/device_info_validator.h index bb3373a4..618c7b95 100644 --- a/oemcrypto/util/include/device_info_validator.h +++ b/oemcrypto/util/include/device_info_validator.h @@ -15,10 +15,47 @@ #include "cbor_validator.h" #include "cppbor.h" +#include "prov4_validation_helper.h" #include "wv_class_utils.h" namespace wvoec { namespace util { +struct DeviceInfo { + // Version 2 and 3 fields + std::pair brand; + std::pair manufacturer; + std::pair product; + std::pair model; + std::pair device; + std::pair + vb_state; // "green" / "yellow" / "orange" + std::pair + bootloader_state; // "locked" / "unlocked" + std::pair> vbmeta_digest; + std::pair os_version; + std::pair system_patch_level; // YYYYMM + std::pair boot_patch_level; // YYYYMMDD + std::pair vendor_patch_level; // YYYYMMDD + std::pair security_level; // "tee" / "strongbox" + std::pair fused; // 1 / 0 + // Version 1 fields + std::pair board; + std::pair version; + std::pair att_id_state; + std::string ToString() const; + CborMessageStatus Validate( + std::vector>& msgs, bool is_gms, + int version_number) const; + CborMessageStatus ValidateV3Fields( + bool is_tee_device_info, + std::vector>& msgs) const; + CborMessageStatus ValidateV2Fields( + bool is_tee_device_info, + std::vector>& msgs) const; + CborMessageStatus ValidateV1Fields( + std::vector>& msgs) const; +}; + // DeviceInfoValidator parses and validates a Cbor struct of DeviceInfo used by // Provisioning 4.0. DeviceInfo definition: // https://source.corp.google.com/h/googleplex-android/platform/superproject/main/+/main:hardware/interfaces/security/rkp/aidl/android/hardware/security/keymint/DeviceInfoV3.cddl @@ -27,8 +64,8 @@ class DeviceInfoValidator : public CborValidator { DeviceInfoValidator() = delete; WVCDM_DISALLOW_COPY_AND_MOVE(DeviceInfoValidator); - explicit DeviceInfoValidator(int version_number) - : version_number_(version_number) {} + explicit DeviceInfoValidator(int version_number = 3, bool is_gms = false) + : version_number_(version_number), is_gms_(is_gms) {} virtual ~DeviceInfoValidator() override = default; @@ -41,15 +78,15 @@ class DeviceInfoValidator : public CborValidator { virtual std::string GetFormattedMessage() const override; private: - // Checks whether a device info entry with |entry_name| and |major_type| - // exists in |device_info| map. - void CheckDeviceInfoMapEntry(const cppbor::Map& device_info, - cppbor::MajorType major_type, - const std::string& entry_name); + // Builds a struct of DeviceInfo from input CBOR map |device_info_map|. + CborMessageStatus BuildDeviceInfo(DeviceInfo& device_info, + const cppbor::Map* device_info_map); // Used to generate formatted message. std::stringstream msg_ss_; // Device info version. Validations are done based on the version number. int version_number_; + // Whether the device is a GMS device. + bool is_gms_; // Saved Cbor-encoded device info. std::vector device_info_bytes_; }; // class DeviceInfoValidator diff --git a/oemcrypto/util/include/prov4_validation_helper.h b/oemcrypto/util/include/prov4_validation_helper.h new file mode 100644 index 00000000..0667f909 --- /dev/null +++ b/oemcrypto/util/include/prov4_validation_helper.h @@ -0,0 +1,96 @@ +// 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 WVOEC_UTIL_PROV4_VALIDATION_HELPER_H_ +#define WVOEC_UTIL_PROV4_VALIDATION_HELPER_H_ + +#include + +#include "cbor_validator.h" +#include "string_conversions.h" + +namespace wvoec { +namespace util { + +enum FieldStatus { + kAbsent = 0, // field key doesn't exist + kEmpty = 1, // field value is empty, e.g. empty string, map, array, etc. + kPresent = 2 // present and non-empty +}; + +std::string StatusToString(FieldStatus status); +// Apply a new status to current status if it is more severe. +void ApplyStatus(CborMessageStatus& status, CborMessageStatus new_status); + +// Validates that the given field name is present, and prints error messages +// if not. +template +CborMessageStatus ValidateRequiredField( + const std::string& name, const std::string& component, + const std::pair& p, + std::vector>& msgs) { + if (p.first != kPresent) { + msgs.push_back(std::make_pair( + kCborValidateError, component + ": missing required field " + name)); + return kCborValidateError; + } + return kCborValidateOk; +} + +// Validates that the given field name is present, and prints warning messages +// if not. +template +CborMessageStatus ValidateImportantField( + const std::string& name, const std::string& component, + const std::pair& p, + std::vector>& msgs) { + if (p.first != kPresent) { + msgs.push_back(std::make_pair( + kCborValidateWarning, component + ": missing important field " + name)); + return kCborValidateWarning; + } + return kCborValidateOk; +} + +// Print a field value with a built-in type in component with |name| to +// stringstream. +template +void PrintField(std::stringstream& ss, const std::string& name, + const std::pair& p) { + ss << " " << name << ":"; + if (p.first != kPresent) { + ss << StatusToString(p.first) << ","; + } else { + ss << p.second << ","; + } +} + +// Print a field encoded as a CBOR bstr in component with |name| to +// stringstream. +template +void PrintBstrField(std::stringstream& ss, const std::string& name, + const std::pair& p) { + ss << " " << name << ":"; + if (p.first != kPresent) { + ss << StatusToString(p.first) << ","; + } else { + ss << wvutil::b2a_hex(p.second) << ","; + } +} + +// Print a field encoded as CBOR structure in component with |name| to +// stringstream. +template +void PrintCborField(std::stringstream& ss, const std::string& name, + const std::pair& p) { + ss << " " << name << ":"; + if (p.first != kPresent) { + ss << StatusToString(p.first) << ","; + } else { + ss << p.second.ToString() << ","; + } +} +} // namespace util +} // namespace wvoec +#endif // WVOEC_UTIL_PROV4_VALIDATION_HELPER_H_ diff --git a/oemcrypto/util/include/signed_csr_payload_validator.h b/oemcrypto/util/include/signed_csr_payload_validator.h index 911943bc..f43f2987 100644 --- a/oemcrypto/util/include/signed_csr_payload_validator.h +++ b/oemcrypto/util/include/signed_csr_payload_validator.h @@ -8,10 +8,12 @@ #define WVOEC_UTIL_SIGNED_CSR_PAYLOAD_VALIDATOR_H_ #include -#include +#include "bcc_validator.h" #include "cbor_validator.h" #include "cppbor.h" +#include "device_info_validator.h" +#include "prov4_validation_helper.h" #include "wv_class_utils.h" namespace wvoec { @@ -20,6 +22,67 @@ namespace util { // SignedData. The definition of SignedData and CsrPayload can be // found at: // https://source.corp.google.com/h/googleplex-android/platform/superproject/main/+/main:hardware/interfaces/security/rkp/aidl/android/hardware/security/keymint/generateCertificateRequestV2.cddl +struct CertificateType { + std::pair type; + std::string ToString() const; + CborMessageStatus Validate( + std::vector>& msgs) const; +}; + +// CsrPayload = [ ; CBOR Array defining the payload for Csr +// version: 3, ; The CsrPayload CDDL Schema version. +// CertificateType, ; The type of certificate being requested. +// DeviceInfo, ; Defined in the relevant DeviceInfoV*.cddl file. +// KeysToSign, ; Provided by the method parameters +// ] +struct CsrPayload { + std::pair version; + std::pair certificate_type; + std::pair device_info; + std::vector keys_to_sign; // always empty + std::string ToString() const; + CborMessageStatus Validate( + std::vector>& msgs) const; +}; + +struct SignedDataProtected { + std::pair algorithm; + std::string ToString() const; + CborMessageStatus Validate( + std::vector>& msgs) const; +}; + +// SignedData<[ +// challenge: bstr .size (0..64), ; Provided by the method parameters +// bstr .cbor T, +// ]>, +struct DataToBeSigned { + std::pair> challenge; + std::pair csr_payload; + std::string ToString() const; + CborMessageStatus Validate( + std::vector>& msgs) const; +}; + +// clang-format off +// SignedData = [ +// protected: bstr .cbor { 1 : AlgorithmEdDSA / AlgorithmES256 / AlgorithmES384 }, +// unprotected: {}, +// payload: bstr .cbor Data / nil, +// signature: bstr ; PureEd25519(CDI_Leaf_Priv, SignedDataSigStruct) / +// ; ECDSA(CDI_Leaf_Priv, SignedDataSigStruct) +// ] +// clang-format on +struct SignedCsrPayload { + std::pair protected_data; + std::pair unprotected; + std::pair payload; + std::pair> signature; + std::string ToString() const; + CborMessageStatus Validate( + std::vector>& msgs) const; +}; + class SignedCsrPayloadValidator : public CborValidator { public: explicit SignedCsrPayloadValidator() {} @@ -32,9 +95,20 @@ class SignedCsrPayloadValidator : public CborValidator { virtual std::string GetFormattedMessage() const override; private: - CborMessageStatus ValidateProtectedParams( - const cppbor::Bstr* protected_params); - CborMessageStatus ValidateDataToBeSigned(const cppbor::Bstr* data); + // Processes protected field in signed csr payload and extracts it to + // *|protected_data|. + // Caller ensures that all pointers are not null. + CborMessageStatus ProcessSignedDataProtected( + const cppbor::Map* protected_map, SignedDataProtected* protected_data); + // Processes the data to be signed and extracts it to *|payload_to_be_signed|. + // Caller ensures that all pointers are not null. + CborMessageStatus ProcessDataToBeSigned( + const cppbor::Array* payload_to_be_signed_array, + DataToBeSigned* payload_to_be_signed); + // Processes csr payload field and extracts it to *|csr_payload|. + // Caller ensures that all pointers are not null. + CborMessageStatus ProcessCsrPayload(const cppbor::Array* csr_payload_array, + CsrPayload* csr_payload); // Used to generate formatted message. std::stringstream msg_ss_; }; // class SignedCsrPayloadValidator diff --git a/oemcrypto/util/oec_ref_util.gypi b/oemcrypto/util/oec_ref_util.gypi index 1196f5a6..1a098f93 100644 --- a/oemcrypto/util/oec_ref_util.gypi +++ b/oemcrypto/util/oec_ref_util.gypi @@ -29,6 +29,7 @@ '<(oemcrypto_dir)/util/src/oemcrypto_key_deriver.cpp', '<(oemcrypto_dir)/util/src/oemcrypto_oem_cert.cpp', '<(oemcrypto_dir)/util/src/oemcrypto_rsa_key.cpp', + '<(oemcrypto_dir)/util/src/prov4_validation_helper.cpp', '<(oemcrypto_dir)/util/src/signed_csr_payload_validator.cpp', '<(oemcrypto_dir)/util/src/wvcrc.cpp', ], diff --git a/oemcrypto/util/src/bcc_validator.cpp b/oemcrypto/util/src/bcc_validator.cpp index a09f1a8c..74a538a8 100644 --- a/oemcrypto/util/src/bcc_validator.cpp +++ b/oemcrypto/util/src/bcc_validator.cpp @@ -6,14 +6,8 @@ // #include "bcc_validator.h" -#include - -#include -#include -#include -#include - #include +#include #include "oemcrypto_ecc_key.h" #include "string_conversions.h" @@ -47,14 +41,6 @@ enum { DEVICE_KEY_OCTET_PAIR = 2, }; -// Android/Widevine Dice Attestation allows two signing models. This is -// identified using MAP_KEY_DEVICE_KEY_ALGORITHM. -enum { - DEVICE_KEY_ALGORITHM_ES256 = -7, // EC key with SHA-256 - DEVICE_KEY_ALGORITHM_EDDSA = -8, // Pure ED25519. - DEVICE_KEY_ALGORITHM_ES384 = -35, // EC key with SHA-384 -}; - // The curve used to generate the device public key is identified using the // MAP_KEY_DEVICE_KEY_CURVE. enum { @@ -76,73 +62,348 @@ constexpr int kMarshaledP384KeySize = kP384KeyComponentSize * 2 + 1; constexpr char kMarshaledECKeyZValue = 0x04; constexpr int kED25519KeyDataItemSize = 32; // The Issuer field key in BccEntryPayload. -constexpr int64_t kIssuer = 1; +constexpr int64_t kIssuerLabel = 1; // The Subject field key in BccEntryPayload. -constexpr int64_t kSubject = 2; +constexpr int64_t kSubjectLabel = 2; +// The Profile Name field key in BccEntryPayload. +constexpr int64_t kProfileNameLabel = -4670554; // The SubjectPublicKey field key in BccEntryPayload. -constexpr int64_t kSubjectPublicKey = -4670552; +constexpr int64_t kSubjectPublicKeyLabel = -4670552; +// The KeyUsage field key in BccEntryPayload. +constexpr int64_t kKeyUsageLabel = -4670553; +// The CodeHash field key in BccEntryPayload. +constexpr int64_t kCodeHashLabel = -4670545; +// The CodeDescriptorLabel field key in BccEntryPayload. +constexpr int64_t kCodeDescriptorLabel = -4670546; +// The ConfigurationHashLabel field key in BccEntryPayload. +constexpr int64_t kConfigurationHashLabel = -4670547; +// The ConfigurationDescriptor field key in BccEntryPayload. +constexpr int64_t kConfigurationDescriptorLabel = -4670548; +constexpr int64_t kComponentNameLabel = -70002; +constexpr int64_t kComponentVersionLabel = -70003; +constexpr int64_t kResettableLabel = -70004; +constexpr int64_t kSecurityVersionLabel = -70005; +constexpr int64_t kVmMarkerLabel = -70006; +// The AuthorityHash field key in BccEntryPayload. +constexpr int64_t kAuthorityHashLabel = -4670549; +// The AuthorityDescriptor field key in BccEntryPayload. +constexpr int64_t kAuthorityDescriptorLabel = -4670550; +// The Mode field key in BccEntryPayload. +constexpr int64_t kModeLabel = -4670551; // This signature context is defined by COSE SIGN1. constexpr char kSignatureContextString[] = "Signature1"; - -struct IssuerSubject { - std::string issuer; - std::string subject; - bool IsValid() const { return !issuer.empty() && !subject.empty(); } - void PrintTo(std::vector& fmt_msgs) const { - fmt_msgs.push_back("Issuer: "); - fmt_msgs.back().append(issuer.empty() ? "" : issuer); - fmt_msgs.push_back("Subject: "); - fmt_msgs.back().append(subject.empty() ? "" : subject); - } -}; - -IssuerSubject GetIssuerSubjectFromBccEntryPayload( - const cppbor::Map* bcc_entry_payload) { - IssuerSubject ret; - for (size_t i = 0; i < bcc_entry_payload->size(); ++i) { - const auto& entry = (*bcc_entry_payload)[i]; - if (entry.first == nullptr || entry.first->asInt() == nullptr || - entry.second == nullptr || entry.second->asTstr() == nullptr) { - continue; - } - const auto& value = entry.second->asTstr()->value(); - if (entry.first->asInt()->value() == kIssuer) { - ret.issuer = value.empty() ? "" : value; - } else if (entry.first->asInt()->value() == kSubject) { - ret.subject = value.empty() ? "" : value; - } - } - return ret; -} - -const cppbor::Bstr* GetSubjectPublicKeyFromBccEntryPayload( - const cppbor::Map* bcc_entry_payload) { - for (size_t i = 0; i < bcc_entry_payload->size(); ++i) { - const auto& entry = (*bcc_entry_payload)[i]; - if (entry.first == nullptr || entry.first->asInt() == nullptr || - entry.second == nullptr) { - continue; - } - if (entry.first->asInt()->value() == kSubjectPublicKey) { - return entry.second->asBstr(); - } - } - return nullptr; -} - -void AddMessages(std::stringstream& ss, - const std::vector& fmt_msgs, int indent) { - const std::string spaces = std::string(indent * 2, ' '); - for (auto& msg : fmt_msgs) { - ss << spaces << msg << "\n"; - } -} } // namespace +std::string BccPublicKeyInfo::ToString() const { + std::stringstream ss; + ss << "{"; + ss << " key_type:"; + if (key_type.first != kPresent) { + ss << StatusToString(key_type.first) << ","; + } else { + if (key_type.second == DEVICE_KEY_BYTE_STRING) { + ss << "byte_string" << ","; + } else if (key_type.second == DEVICE_KEY_OCTET_PAIR) { + ss << "octet_pair" << ","; + } else { + ss << "unknown(" << key_type.second << ")" << ","; + } + } + ss << " signature_algorithm:"; + if (signature_algorithm.first != kPresent) { + ss << StatusToString(signature_algorithm.first) << ","; + } else { + if (signature_algorithm.second == kBccEdDsa) { + ss << "EdDSA" << ","; + } else if (signature_algorithm.second == kBccEcdsaSha256) { + ss << "ES256" << ","; + } else if (signature_algorithm.second == kBccEcdsaSha384) { + ss << "ES384" << ","; + } else { + ss << "unknown" << ","; + } + } + ss << " curve:"; + if (curve.first != kPresent) { + ss << StatusToString(curve.first) << ","; + } else { + if (curve.second == kBccEd25519) { + ss << "Ed25519" << ","; + } else if (curve.second == kBccP256) { + ss << "P256" << ","; + } else if (curve.second == kBccP384) { + ss << "P384" << ","; + } else { + ss << "unknown(" << curve.second << ")" << ","; + } + } + PrintBstrField(ss, "key_bytes", key_bytes); + ss << " }"; + return ss.str(); +} + +CborMessageStatus BccPublicKeyInfo::Validate( + std::vector>& msgs) const { + CborMessageStatus status = kCborParseOk; + const std::string component = "BccPublicKey"; + CborMessageStatus cur_status = + ValidateRequiredField("key_type", component, key_type, msgs); + ApplyStatus(status, cur_status); + if (cur_status == kCborValidateOk) { + if (key_type.second != DEVICE_KEY_OCTET_PAIR && + key_type.second != DEVICE_KEY_BYTE_STRING) { + msgs.push_back(std::make_pair( + kCborValidateError, + component + ": Invalid key type " + std::to_string(key_type.second))); + ApplyStatus(status, kCborValidateError); + } + } + cur_status = ValidateRequiredField("signature_algorithm", component, + signature_algorithm, msgs); + ApplyStatus(status, cur_status); + if (cur_status == kCborValidateOk) { + if (signature_algorithm.second != kBccEdDsa && + signature_algorithm.second != kBccEcdsaSha256 && + signature_algorithm.second != kBccEcdsaSha384) { + msgs.push_back(std::make_pair( + kCborValidateError, component + ": Invalid signature algorithm " + + std::to_string(signature_algorithm.second))); + ApplyStatus(status, kCborValidateError); + } + } + cur_status = ValidateRequiredField("curve", component, curve, msgs); + ApplyStatus(status, cur_status); + if (cur_status == kCborValidateOk) { + if (curve.second != kBccEd25519 && curve.second != kBccP256 && + curve.second != kBccP384) { + msgs.push_back(std::make_pair( + kCborValidateError, + component + ": Invalid curve " + std::to_string(curve.second))); + ApplyStatus(status, kCborValidateError); + } + } + cur_status = ValidateRequiredField("key_bytes", component, curve, msgs); + ApplyStatus(status, cur_status); + return status; +} + +std::string BccEntryProtected::ToString() const { + std::stringstream ss; + ss << "{"; + ss << " algorithm:"; + if (algorithm.first != kPresent) { + ss << StatusToString(algorithm.first) << ","; + } else { + if (algorithm.second == DEVICE_KEY_ALGORITHM_EDDSA) { + ss << "EdDSA" << ","; + } else if (algorithm.second == DEVICE_KEY_ALGORITHM_ES256) { + ss << "ECDSA_SHA256" << ","; + } else if (algorithm.second == DEVICE_KEY_ALGORITHM_ES384) { + ss << "ECDSA_SHA384" << ","; + } else { + ss << "unknown(" << algorithm.second << ")" << ","; + } + } + ss << " }"; + return ss.str(); +} + +CborMessageStatus BccEntryProtected::Validate( + std::vector>& msgs) const { + CborMessageStatus status = kCborParseOk; + const std::string component = "BccEntryProtected"; + CborMessageStatus cur_status = + ValidateRequiredField("algorithm", component, algorithm, msgs); + ApplyStatus(status, cur_status); + if (cur_status == kCborValidateOk) { + if (algorithm.second != DEVICE_KEY_ALGORITHM_EDDSA && + algorithm.second != DEVICE_KEY_ALGORITHM_ES256 && + algorithm.second != DEVICE_KEY_ALGORITHM_ES384) { + msgs.push_back(std::make_pair(kCborValidateError, + component + ": Invalid algorithm " + + std::to_string(algorithm.second))); + ApplyStatus(status, kCborValidateError); + } + } + return status; +} + +std::string ConfigurationDescriptor::ToString() const { + std::stringstream ss; + ss << "{"; + PrintField(ss, "component_name", component_name); + PrintField(ss, "component_version", component_version); + PrintField(ss, "resettable", resettable); + PrintField(ss, "security_version", security_version); + PrintField(ss, "vm_marker", vm_marker); + ss << " }"; + return ss.str(); +} + +CborMessageStatus ConfigurationDescriptor::Validate( + std::vector>& msgs, + bool* is_widevine_entry) const { + CborMessageStatus status = kCborParseOk; + const std::string component = "ConfigurationDescriptor"; + CborMessageStatus cur_status = + ValidateRequiredField("component_name", component, component_name, msgs); + ApplyStatus(status, cur_status); + if (cur_status == kCborValidateOk) { + std::string name = component_name.second; + std::transform(name.begin(), name.end(), name.begin(), + [](unsigned char c) { return std::tolower(c); }); + *is_widevine_entry = (name == "widevine"); + } + return status; +} + +std::string BccEntryPayload::ToString() const { + std::stringstream ss; + ss << "{"; + PrintField(ss, "issuer", issuer); + PrintField(ss, "subject", subject); + PrintField(ss, "profile_name", profile_name); + PrintCborField(ss, "subject_public_key", subject_public_key); + PrintBstrField(ss, "key_usage(hex)", key_usage); + PrintBstrField(ss, "code_hash", code_hash); + PrintBstrField(ss, "code_descriptor", code_descriptor); + PrintBstrField(ss, "config_hash", config_hash); + PrintCborField(ss, "config_descriptor", config_descriptor); + PrintBstrField(ss, "authority_hash", authority_hash); + PrintBstrField(ss, "authority_descriptor", authority_descriptor); + PrintBstrField(ss, "mode(hex)", mode); + ss << " }"; + return ss.str(); +} + +CborMessageStatus BccEntryPayload::Validate( + std::vector>& msgs, + bool is_degenerated, bool* is_widevine_entry) const { + CborMessageStatus status = kCborParseOk; + const std::string component = "BccEntryPayload"; + CborMessageStatus cur_status = + ValidateRequiredField("issuer", component, issuer, msgs); + ApplyStatus(status, cur_status); + cur_status = ValidateRequiredField("subject", component, subject, msgs); + ApplyStatus(status, cur_status); + cur_status = ValidateRequiredField("subject_public_key", component, + subject_public_key, msgs); + ApplyStatus(status, cur_status); + if (cur_status == kCborValidateOk) { + cur_status = subject_public_key.second.Validate(msgs); + ApplyStatus(status, cur_status); + } + cur_status = ValidateRequiredField("key_usage", component, key_usage, msgs); + ApplyStatus(status, cur_status); + // Validates more fields for non-degenerated BCC payload. + if (!is_degenerated) { + cur_status = ValidateRequiredField("code_hash", component, code_hash, msgs); + ApplyStatus(status, cur_status); + cur_status = + ValidateRequiredField("config_hash", component, config_hash, msgs); + ApplyStatus(status, cur_status); + if (config_descriptor.first == kAbsent) { + msgs.push_back(std::make_pair( + kCborValidateError, + component + ": missing required field config_descriptor")); + ApplyStatus(status, kCborValidateError); + } else { + cur_status = config_descriptor.second.Validate(msgs, is_widevine_entry); + ApplyStatus(status, cur_status); + } + cur_status = ValidateRequiredField("authority_hash", component, + authority_hash, msgs); + ApplyStatus(status, cur_status); + cur_status = ValidateRequiredField("mode", component, mode, msgs); + ApplyStatus(status, cur_status); + if (cur_status == kCborValidateOk && *is_widevine_entry) { + uint32_t mode_value = 0; + for (uint8_t byte : mode.second) { + mode_value <<= 8; + mode_value |= byte; + } + if (mode_value == 0x0) { + msgs.push_back(std::make_pair(kCborValidateError, + component + ": mode can not be 0")); + ApplyStatus(status, kCborValidateError); + } + } + } + return status; +} + +std::string BccEntry::ToString() const { + std::stringstream ss; + ss << "["; + PrintCborField(ss, "protected", protected_data); + PrintField(ss, "unprotected", unprotected); + PrintCborField(ss, "payload", payload); + PrintBstrField(ss, "signature", signature); + ss << " ]"; + return ss.str(); +} + +CborMessageStatus BccEntry::Validate( + std::vector>& msgs, + bool is_degenerated, bool* is_widevine_entry) const { + CborMessageStatus status = kCborParseOk; + const std::string component = "BccEntry"; + CborMessageStatus cur_status = + ValidateRequiredField("protected", component, protected_data, msgs); + ApplyStatus(status, cur_status); + if (cur_status == kCborValidateOk) { + cur_status = protected_data.second.Validate(msgs); + ApplyStatus(status, cur_status); + } + cur_status = ValidateRequiredField("payload", component, payload, msgs); + ApplyStatus(status, cur_status); + if (cur_status == kCborValidateOk) { + cur_status = + payload.second.Validate(msgs, is_degenerated, is_widevine_entry); + ApplyStatus(status, cur_status); + } + cur_status = ValidateRequiredField("signature", component, signature, msgs); + ApplyStatus(status, cur_status); + return status; +} + +std::string Bcc::ToString() const { + std::stringstream ss; + ss << "BCC = ["; + ss << "dk_pub:" << dk_pub.ToString() << ","; + for (size_t i = 0; i < entries.size(); ++i) { + ss << "entry:" << entries[i].ToString() << ","; + } + ss << "]"; + return ss.str(); +} + +CborMessageStatus Bcc::Validate( + std::vector>& msgs, + bool is_degenerated) const { + CborMessageStatus status = kCborParseOk; + const std::string component = "Bcc"; + CborMessageStatus cur_status = dk_pub.Validate(msgs); + ApplyStatus(status, cur_status); + bool found_widevine_entry = false; + for (size_t i = 0; i < entries.size(); ++i) { + bool is_widevine_entry = false; + cur_status = entries[i].Validate(msgs, is_degenerated, &is_widevine_entry); + ApplyStatus(status, cur_status); + if (is_widevine_entry) found_widevine_entry = true; + } + if (!is_degenerated && !found_widevine_entry) { + msgs.push_back(std::make_pair(kCborValidateError, + component + ": Widevine cert not found.")); + ApplyStatus(status, kCborValidateError); + } + return status; +} + bool BccValidator::VerifySignature(const BccPublicKeyInfo& signing_key, const std::vector& message, const std::vector& signature) { - if (signing_key.signature_algorithm == kBccEdDsa) { + if (signing_key.signature_algorithm.second == kBccEdDsa) { constexpr size_t kEd25519SignatureLength = 64; // ED25519 incorporates SHA512 into the signing algorithm. if (signature.size() != kEd25519SignatureLength) { @@ -154,8 +415,9 @@ bool BccValidator::VerifySignature(const BccPublicKeyInfo& signing_key, EVP_PKEY* pkey = nullptr; if ((pkey = EVP_PKEY_new_raw_public_key( EVP_PKEY_ED25519, nullptr, - reinterpret_cast(signing_key.key_bytes.data()), - signing_key.key_bytes.size())) == nullptr) { + reinterpret_cast( + signing_key.key_bytes.second.data()), + signing_key.key_bytes.second.size())) == nullptr) { AddValidationMessage( kCborValidateError, "Can not create EVP_PKEY_ED25519 from the public key info."); @@ -170,14 +432,16 @@ bool BccValidator::VerifySignature(const BccPublicKeyInfo& signing_key, EVP_PKEY_free(pkey); return res; } - if (signing_key.signature_algorithm == kBccEcdsaSha256 || - signing_key.signature_algorithm == kBccEcdsaSha384) { - const EccCurve curve = (signing_key.signature_algorithm == kBccEcdsaSha256) - ? EccCurve::kEccSecp256r1 - : EccCurve::kEccSecp384r1; + if (signing_key.signature_algorithm.second == kBccEcdsaSha256 || + signing_key.signature_algorithm.second == kBccEcdsaSha384) { + const EccCurve curve = + (signing_key.signature_algorithm.second == kBccEcdsaSha256) + ? EccCurve::kEccSecp256r1 + : EccCurve::kEccSecp384r1; std::unique_ptr key = EccPublicKey::LoadKeyPoint( - curve, reinterpret_cast(signing_key.key_bytes.data()), - signing_key.key_bytes.size()); + curve, + reinterpret_cast(signing_key.key_bytes.second.data()), + signing_key.key_bytes.second.size()); if (!key) { AddValidationMessage(kCborValidateError, "Can not create ECPublicKey from raw EC KeyPoint."); @@ -187,9 +451,10 @@ bool BccValidator::VerifySignature(const BccPublicKeyInfo& signing_key, message.data(), message.size(), signature.data(), signature.size()); return (res == OEMCrypto_SUCCESS); } - AddValidationMessage(kCborValidateError, - "Unknown signature algorithm: " + - std::to_string(signing_key.signature_algorithm)); + AddValidationMessage( + kCborValidateError, + "Unknown signature algorithm: " + + std::to_string(signing_key.signature_algorithm.second)); return false; } @@ -207,6 +472,12 @@ CborMessageStatus BccValidator::Validate() { return message_status_; } const cppbor::Array* bcc_array = parsed_bcc->asArray(); + /* + * Bcc = [ + * PubKeyEd25519 / PubKeyECDSA256 / PubKeyECDSA384, ; DK_pub + * + BccEntry, ; Root -> leaf (KM_pub) + * ] + */ if (bcc_array->size() < 2) { AddValidationMessage(kCborValidateFatal, "BCC should contain at least two elements. Actual: " + @@ -214,14 +485,9 @@ CborMessageStatus BccValidator::Validate() { return message_status_; } - // Writes YAML-formatted output to |msg_ss_| during validation. - msg_ss_.str(std::string()); - msg_ss_ << "---" - << "\n"; - msg_ss_ << "DEVICE PUBLIC KEY:\n"; - // The first element in the array contains the root device public key - // definition. + // definition: + // PubKeyEd25519 / PubKeyECDSA256 / PubKeyECDSA384; DK_pub, cbor map const cppbor::Map* device_public_key_map = (*bcc_array)[0]->asMap(); if (device_public_key_map == nullptr) { AddValidationMessage( @@ -231,161 +497,348 @@ CborMessageStatus BccValidator::Validate() { return message_status_; } BccPublicKeyInfo root_pub_key; - std::vector key_value_texts; // for pretty print - CborMessageStatus status = ProcessSubjectPublicKeyInfo( - *device_public_key_map, key_value_texts, &root_pub_key); - AddMessages(msg_ss_, key_value_texts, 1); + CborMessageStatus status = + ProcessSubjectPublicKeyInfo(device_public_key_map, &root_pub_key); if (status == kCborValidateFatal) return status; + Bcc bcc; + bcc.dk_pub = root_pub_key; BccPublicKeyInfo leaf_pub_key = root_pub_key; - msg_ss_ << "BCC ENTRY:\n"; - // Parse and verify each certificate in the chain. The structure of thr - // entries are COSE_Sign1 (untagged). leaf_pub_key is being updated while we - // process the chain. - for (size_t i = 1; i < bcc_array->size(); ++i) { - msg_ss_ << "- CDI PUBLIC KEY INDEX: " << i << "\n"; - const cppbor::Array* bcc_entry = (*bcc_array)[i]->asArray(); - if (bcc_entry == nullptr) { - AddValidationMessage(kCborValidateFatal, - "BCC entry is empty at index " + std::to_string(i)); - return message_status_; - } - if (bcc_entry->size() != 4) { - AddValidationMessage(kCborValidateFatal, - "BCC entry should contain 4 items. Actual: " + - std::to_string(bcc_entry->size())); - return message_status_; - } - // Skip CoseSign1 signature verification here, only extract pub keys - if ((*bcc_entry)[0]->type() != cppbor::BSTR || - (*bcc_entry)[1]->type() != cppbor::MAP || - (*bcc_entry)[2]->type() != cppbor::BSTR || - (*bcc_entry)[3]->type() != cppbor::BSTR) { - AddValidationMessage(kCborValidateFatal, "Invalid BCC entry type."); - return message_status_; - } - // Signature verification Step 1: construct and encode signature input - const std::vector& protected_bytes = - (*bcc_entry)[0]->asBstr()->value(); - // Index 1 is unprotected parameters, which is ignored. - const std::vector& payload = (*bcc_entry)[2]->asBstr()->value(); - const std::vector& actual_signature = - (*bcc_entry)[3]->asBstr()->value(); - - const std::vector signature_input = - cppbor::Array() - .add(kSignatureContextString) - .add(protected_bytes) - .add(/* AAD */ std::vector()) - .add(payload) - .encode(); - - // Signature verification Step 2: verify - if (!VerifySignature(leaf_pub_key, signature_input, actual_signature)) { - AddValidationMessage( - kCborValidateError, - "Failed to verify the signature for BCC entry index: " + - std::to_string(i)); - } - - key_value_texts.clear(); - BccPublicKeyInfo entry_pub_key; - status = - ProcessDiceChainEntryPayload(payload, key_value_texts, &entry_pub_key); - AddMessages(msg_ss_, key_value_texts, 1); - if (status == kCborValidateFatal) return status; - leaf_pub_key = std::move(entry_pub_key); - } // If the size of the BCC array (including device pub key) is 2, then it // must be a de-generated BCC, which means the second element in the array // is a self-signed entry. The entry's public key should be identical to the // device's public key. + bool is_degenerated = false; if (bcc_array->size() == 2) { // self-signed BCC entry - if (leaf_pub_key.key_bytes != root_pub_key.key_bytes) { + if (leaf_pub_key.key_bytes.second != root_pub_key.key_bytes.second) { AddValidationMessage(kCborValidateError, "The public key of a self-signed entry should be " "identical to its device public key."); } + is_degenerated = true; } - msg_ss_ << "...\n"; + + // Parse and verify each entry in the chain. The structure of the + // signature in the entry is COSE_Sign1 (untagged). leaf_pub_key is being + // updated while we process the chain. + /* + * BccEntry = [ ; COSE_Sign1 (untagged) + * protected : bstr .cbor { + * 1 : AlgorithmEdDSA / AlgorithmES256, ; Algorithm + * }, + * unprotected: {}, + * payload: bstr .cbor BccPayload, + * signature: bstr ; PureEd25519(SigningKey, bstr .cbor BccEntryInput) + * / ; ECDSA(SigningKey, bstr .cbor BccEntryInput) ; See RFC 8032 for details + * of how to encode the signature value for Ed25519. + * ] + */ + for (size_t i = 1; i < bcc_array->size(); ++i) { + const cppbor::Array* bcc_entry = (*bcc_array)[i]->asArray(); + if (bcc_entry == nullptr) { + AddValidationMessage(kCborValidateError, + "BCC entry is empty at index " + std::to_string(i)); + continue; + } + if (bcc_entry->size() != 4) { + AddValidationMessage(kCborValidateError, + "BCC entry index " + std::to_string(i) + + " should contain 4 items. Actual: " + + std::to_string(bcc_entry->size())); + continue; + } + if ((*bcc_entry)[0]->type() != cppbor::BSTR) { + AddValidationMessage(kCborValidateError, + "BCC entry index " + std::to_string(i) + + " protected field is not a CBOR bstr."); + continue; + } + if ((*bcc_entry)[1]->type() != cppbor::MAP) { + AddValidationMessage(kCborValidateError, + "BCC entry index " + std::to_string(i) + + " unprotected field is not a CBOR map."); + continue; + } + if ((*bcc_entry)[2]->type() != cppbor::BSTR) { + AddValidationMessage(kCborValidateError, + "BCC entry index " + std::to_string(i) + + " payload field is not a CBOR bstr."); + continue; + } + if ((*bcc_entry)[3]->type() != cppbor::BSTR) { + AddValidationMessage(kCborValidateError, + "BCC entry index " + std::to_string(i) + + " signature field is not a CBOR bstr."); + continue; + } + + // Parse and verify the protected data + const std::vector& encoded_protected_data = + (*bcc_entry)[0]->asBstr()->value(); + if (encoded_protected_data.empty()) { + AddValidationMessage( + kCborValidateError, + "BCC entry index " + std::to_string(i) + " empty protected data."); + continue; + } + auto parse_result = cppbor::parse(encoded_protected_data); + std::unique_ptr sub_item = + std::move(std::get<0>(parse_result)); + std::string error_message = std::move(std::get<2>(parse_result)); + if (sub_item == nullptr || !error_message.empty()) { + AddValidationMessage(kCborValidateError, + "BCC entry index " + std::to_string(i) + + " unable to parse protected: " + error_message); + continue; + } + if (sub_item->type() != cppbor::MAP) { + AddValidationMessage(kCborValidateError, + "BCC entry index " + std::to_string(i) + + " unexpected protected type: " + + CppborMajorTypeToString(sub_item->type())); + continue; + } + const cppbor::Map* protected_map = sub_item->asMap(); + if (protected_map == nullptr) { + AddValidationMessage( + kCborValidateError, + "BCC entry index " + std::to_string(i) + " protected map is null."); + continue; + } + BccEntry entry; + if (protected_map->size() == 0) { + entry.protected_data.first = kEmpty; + } else { + status = + ProcessBccEntryProtected(protected_map, &entry.protected_data.second); + if (status == kCborValidateFatal) return status; + if (status != kCborValidateError) { + entry.protected_data.first = kPresent; + } + } + // Unprotected data in BCC entry is always empty, which is ignored. + entry.unprotected.first = kEmpty; + entry.unprotected.second = "{}"; + + // Signature verification + const std::vector& encoded_payload = + (*bcc_entry)[2]->asBstr()->value(); + const std::vector& actual_signature = + (*bcc_entry)[3]->asBstr()->value(); + entry.signature.second = actual_signature; + entry.signature.first = entry.signature.second.empty() ? kEmpty : kPresent; + + const std::vector signature_input = + cppbor::Array() + .add(kSignatureContextString) + .add(encoded_protected_data) + .add(/* AAD */ std::vector()) + .add(encoded_payload) + .encode(); + if (!VerifySignature(leaf_pub_key, signature_input, actual_signature)) { + AddValidationMessage(kCborValidateError, + "BCC entry index " + std::to_string(i) + + " failed to verify the signature."); + } + + // Parse and verify the payload + if (encoded_payload.empty()) { + AddValidationMessage( + kCborValidateError, + "BCC entry index " + std::to_string(i) + " empty payload."); + continue; + } + parse_result = cppbor::parse(encoded_payload); + std::unique_ptr sub_item_payload = + std::move(std::get<0>(parse_result)); + error_message = std::move(std::get<2>(parse_result)); + if (sub_item_payload == nullptr || !error_message.empty()) { + AddValidationMessage(kCborValidateError, + "BCC entry index " + std::to_string(i) + + " unable to parse payload: " + error_message); + continue; + } + if (sub_item_payload->type() != cppbor::MAP) { + AddValidationMessage( + kCborValidateError, + "BCC entry index " + std::to_string(i) + + " unexpected payload type: " + + CppborMajorTypeToString(sub_item_payload->type())); + continue; + } + const cppbor::Map* payload_map = sub_item_payload->asMap(); + if (payload_map == nullptr) { + AddValidationMessage( + kCborValidateError, + "BCC entry index " + std::to_string(i) + " payload is empty."); + continue; + } + if (payload_map->size() == 0) { + entry.payload.first = kEmpty; + } else { + status = ProcessDiceChainEntryPayload(payload_map, &entry.payload.second); + if (status == kCborValidateFatal) return status; + if (status != kCborValidateError) { + entry.payload.first = kPresent; + leaf_pub_key = entry.payload.second.subject_public_key.second; + } + } + bcc.entries.push_back(std::move(entry)); + } + + msg_ss_ << bcc.ToString() << "\n"; + + // More validations on the BCC + std::vector> msgs; + CborMessageStatus validate_status = bcc.Validate(msgs, is_degenerated); + ApplyStatus(message_status_, validate_status); + for (const auto& msg : msgs) { + AddValidationMessage(msg.first, msg.second); + } + if (message_status_ == kCborParseOk) message_status_ = kCborValidateOk; return message_status_; } -CborMessageStatus BccValidator::ProcessSubjectPublicKeyInfo( - const cppbor::Map& public_key_info_map, std::vector& fmt_msgs, - BccPublicKeyInfo* public_key_info) { - int key_encoding_format = DEVICE_KEY_ENCODING_UNKNOWN; - std::vector device_key_bytes_0; - std::vector device_key_bytes_1; - std::unordered_set key_set; - for (size_t index = 0; index < public_key_info_map.size(); ++index) { +CborMessageStatus BccValidator::ProcessConfigurationDescriptor( + const cppbor::Map* config_descriptor_map, ConfigurationDescriptor* cd) { + for (size_t index = 0; index < config_descriptor_map->size(); ++index) { std::pair&, const std::unique_ptr&> - entry = public_key_info_map[index]; + entry = (*config_descriptor_map)[index]; if (entry.first->type() != cppbor::NINT && entry.first->type() != cppbor::UINT) { - AddValidationMessage(kCborValidateFatal, + AddValidationMessage( + kCborValidateError, + "Invalid key type in configuration descriptor map: " + + CppborMajorTypeToString(entry.first->type())); + continue; + } + const int64_t map_key = entry.first->asInt()->value(); + switch (map_key) { + case kComponentNameLabel: { + if (entry.second->type() != cppbor::TSTR) { + AddValidationMessage( + kCborValidateError, + "Invalid value type in configuration descriptor map for " + "key component name: " + + CppborMajorTypeToString(entry.second->type())); + continue; + } + cd->component_name.second = entry.second->asTstr()->value(); + cd->component_name.first = + cd->component_name.second.empty() ? kEmpty : kPresent; + } break; + case kComponentVersionLabel: { + if (entry.second->type() == cppbor::NINT || + entry.second->type() == cppbor::UINT) { + cd->component_version.second = + std::to_string(entry.second->asInt()->value()); + cd->component_version.first = kPresent; + } else if (entry.second->type() == cppbor::TSTR) { + cd->component_version.second = entry.second->asTstr()->value(); + cd->component_version.first = kPresent; + } else { + AddValidationMessage( + kCborValidateError, + "Invalid value type in configuration descriptor map for " + "component version: " + + CppborMajorTypeToString(entry.second->type())); + continue; + } + } break; + case kResettableLabel: { + // null string, do nothing + break; + } + case kSecurityVersionLabel: { + if (entry.second->type() != cppbor::UINT) { + AddValidationMessage( + kCborValidateError, + "Invalid value type in configuration descriptor map for " + "security version: " + + CppborMajorTypeToString(entry.second->type())); + continue; + } else { + cd->security_version.second = entry.second->asUint()->value(); + cd->security_version.first = kPresent; + } + } break; + case kVmMarkerLabel: { + // null string, do nothing + break; + } + } + } + return message_status_; +} + +CborMessageStatus BccValidator::ProcessSubjectPublicKeyInfo( + const cppbor::Map* public_key_map, BccPublicKeyInfo* public_key) { + std::vector device_key_bytes_0; + std::vector device_key_bytes_1; + for (size_t index = 0; index < public_key_map->size(); ++index) { + std::pair&, + const std::unique_ptr&> + entry = (*public_key_map)[index]; + if (entry.first->type() != cppbor::NINT && + entry.first->type() != cppbor::UINT) { + AddValidationMessage(kCborValidateError, "Invalid key type in public key info map: " + CppborMajorTypeToString(entry.first->type())); - return kCborValidateFatal; + continue; } const int64_t map_key = entry.first->asInt()->value(); switch (map_key) { case MAP_KEY_DEVICE_KEY_TYPE: { if (entry.second->type() != cppbor::UINT) { AddValidationMessage( - kCborValidateFatal, + kCborValidateError, "Invalid value type in public key info map for " "key MAP_KEY_DEVICE_KEY_TYPE: " + CppborMajorTypeToString(entry.second->type())); - return kCborValidateFatal; + continue; } - std::string kv = "key encoding format: "; + public_key->key_type.first = kPresent; const int64_t value = entry.second->asUint()->value(); if (value == DEVICE_KEY_OCTET_PAIR) { - key_encoding_format = DEVICE_KEY_OCTET_PAIR; - kv += "DEVICE_KEY_OCTET_PAIR"; + public_key->key_type.second = DEVICE_KEY_OCTET_PAIR; } else if (value == DEVICE_KEY_BYTE_STRING) { - key_encoding_format = DEVICE_KEY_BYTE_STRING; - kv += "DEVICE_KEY_BYTE_STRING"; + public_key->key_type.second = DEVICE_KEY_BYTE_STRING; } else { + public_key->key_type.second = DEVICE_KEY_ENCODING_UNKNOWN; AddValidationMessage(kCborValidateError, "Invalid value in public key info map for key " "MAP_KEY_DEVICE_KEY_TYPE: " + std::to_string(value)); } - fmt_msgs.push_back(std::move(kv)); } break; case MAP_KEY_DEVICE_KEY_ALGORITHM: { if (entry.second->type() != cppbor::NINT) { AddValidationMessage( - kCborValidateFatal, + kCborValidateError, "Invalid value type in public key info map for " "key MAP_KEY_DEVICE_KEY_ALGORITHM: " + CppborMajorTypeToString(entry.second->type())); - return kCborValidateFatal; + continue; } - std::string kv = "key algorithm type: "; + public_key->signature_algorithm.first = kPresent; const int64_t value = entry.second->asNint()->value(); if (value == DEVICE_KEY_ALGORITHM_ES256) { - kv += "ECDSA_SHA256"; - public_key_info->signature_algorithm = kBccEcdsaSha256; + public_key->signature_algorithm.second = kBccEcdsaSha256; } else if (value == DEVICE_KEY_ALGORITHM_ES384) { - kv += "ECDSA_SHA384"; - public_key_info->signature_algorithm = kBccEcdsaSha384; + public_key->signature_algorithm.second = kBccEcdsaSha384; } else if (value == DEVICE_KEY_ALGORITHM_EDDSA) { - kv += "EDDSA"; - public_key_info->signature_algorithm = kBccEdDsa; + public_key->signature_algorithm.second = kBccEdDsa; } else { + public_key->signature_algorithm.second = kBccDefaultSignature; AddValidationMessage(kCborValidateError, "Invalid value in public key info map for key " "MAP_KEY_DEVICE_KEY_ALGORITHM: " + std::to_string(value)); } - fmt_msgs.push_back(std::move(kv)); } break; case MAP_KEY_DEVICE_KEY_OPS: // The OPS is an array. Ignored for now. @@ -393,30 +846,27 @@ CborMessageStatus BccValidator::ProcessSubjectPublicKeyInfo( case MAP_KEY_DEVICE_KEY_CURVE: { if (entry.second->type() != cppbor::UINT) { AddValidationMessage( - kCborValidateFatal, + kCborValidateError, "Invalid value type in public key info map for " "key MAP_KEY_DEVICE_KEY_CURVE: " + CppborMajorTypeToString(entry.second->type())); - return kCborValidateFatal; + continue; } - std::string kv = "curve: "; + public_key->curve.first = kPresent; const int64_t value = entry.second->asUint()->value(); if (value == DEVICE_KEY_CURVE_P256) { - public_key_info->curve = kBccP256; - kv += "P256"; + public_key->curve.second = kBccP256; } else if (value == DEVICE_KEY_CURVE_P384) { - public_key_info->curve = kBccP384; - kv += "P384"; + public_key->curve.second = kBccP384; } else if (value == DEVICE_KEY_CURVE_ED25519) { - public_key_info->curve = kBccEd25519; - kv += "ED25519"; + public_key->curve.second = kBccEd25519; } else { + public_key->curve.second = kBccDefaultCurve; AddValidationMessage(kCborValidateError, "Invalid value in public key info map for key " "MAP_KEY_DEVICE_KEY_CURVE: " + std::to_string(value)); } - fmt_msgs.push_back(std::move(kv)); } break; case MAP_KEY_DEVICE_KEY_BYTES_0: case MAP_KEY_DEVICE_KEY_BYTES_1: @@ -424,52 +874,40 @@ CborMessageStatus BccValidator::ProcessSubjectPublicKeyInfo( // octet string. The format used depends on the key type. if (entry.second->type() != cppbor::BSTR) { AddValidationMessage( - kCborValidateFatal, + kCborValidateError, "Invalid value type in public key info map for " "key MAP_KEY_DEVICE_KEY_BYTES_0/1: " + CppborMajorTypeToString(entry.second->type())); - return kCborValidateFatal; + continue; } const std::vector& key_bytes = entry.second->asBstr()->value(); // Key byte length depends upon the key type. if (key_bytes.size() != kED25519KeyDataItemSize && key_bytes.size() != kP256KeyComponentSize && key_bytes.size() != kP384KeyComponentSize) { - AddValidationMessage(kCborValidateFatal, + AddValidationMessage(kCborValidateError, "Malformed public key data size of: " + std::to_string(key_bytes.size())); - return kCborValidateFatal; + continue; } + public_key->key_bytes.first = kPresent; if (map_key == MAP_KEY_DEVICE_KEY_BYTES_0) { device_key_bytes_0 = key_bytes; } else { device_key_bytes_1 = key_bytes; } } - key_set.insert(map_key); - } - if (key_set.find(MAP_KEY_DEVICE_KEY_TYPE) == key_set.end()) { - AddValidationMessage(kCborValidateError, - "Missing MAP_KEY_DEVICE_KEY_TYPE."); - } - if (key_set.find(MAP_KEY_DEVICE_KEY_ALGORITHM) == key_set.end()) { - AddValidationMessage(kCborValidateError, - "Missing MAP_KEY_DEVICE_KEY_ALGORITHM."); - } - if (key_set.find(MAP_KEY_DEVICE_KEY_CURVE) == key_set.end()) { - AddValidationMessage(kCborValidateError, - "Missing MAP_KEY_DEVICE_KEY_CURVE."); } if (device_key_bytes_0.empty() || - (key_encoding_format == DEVICE_KEY_OCTET_PAIR && + (public_key->key_type.second == DEVICE_KEY_OCTET_PAIR && device_key_bytes_1.empty())) { AddValidationMessage( - kCborValidateFatal, + kCborValidateError, "Malformed public key definition. Missing device public key bytes."); - return kCborValidateFatal; + return message_status_; } std::vector device_key_bytes; - if (key_encoding_format == DEVICE_KEY_OCTET_PAIR) { + if (public_key->key_type.second == DEVICE_KEY_OCTET_PAIR) { // Key is an ECDSA elliptic key. We need to return the ANSI X9.62 // marshaled public key. Generate the marshaled key if needed. The // marshaled key is needed to create an ECPublicKey object. @@ -480,72 +918,172 @@ CborMessageStatus BccValidator::ProcessSubjectPublicKeyInfo( device_key_bytes_1.end()); if (device_key_bytes.size() != kMarshaledP384KeySize && device_key_bytes.size() != kMarshaledP256KeySize) { - AddValidationMessage(kCborValidateFatal, + AddValidationMessage(kCborValidateError, "Invalid ECDSA public key size: " + std::to_string(device_key_bytes.size())); - return kCborValidateFatal; + return message_status_; } } else { device_key_bytes = std::move(device_key_bytes_0); } - fmt_msgs.push_back("public key bytes: " + wvutil::b2a_hex(device_key_bytes)); - public_key_info->key_bytes = std::move(device_key_bytes); + public_key->key_bytes.second = std::move(device_key_bytes); + if (public_key->key_bytes.second.empty()) { + public_key->key_bytes.first = kEmpty; + } + return message_status_; +} + +CborMessageStatus BccValidator::ProcessBccEntryProtected( + const cppbor::Map* protected_map, BccEntryProtected* protected_data) { + for (size_t index = 0; index < protected_map->size(); ++index) { + std::pair&, + const std::unique_ptr&> + entry = (*protected_map)[index]; + if (entry.first->type() != cppbor::NINT && + entry.first->type() != cppbor::UINT) { + AddValidationMessage( + kCborValidateError, + "Invalid key type in protected data map in bcc entry: " + + CppborMajorTypeToString(entry.first->type())); + continue; + } + const int64_t map_key = entry.first->asInt()->value(); + switch (map_key) { + case 1: { + if (entry.second->type() != cppbor::NINT && + entry.second->type() != cppbor::UINT) { + AddValidationMessage( + kCborValidateError, + "Invalid value type in protected data map for " + "key 1: " + + CppborMajorTypeToString(entry.second->type())); + continue; + } + protected_data->algorithm.first = kPresent; + protected_data->algorithm.second = entry.second->asInt()->value(); + } break; + } + } return message_status_; } CborMessageStatus BccValidator::ProcessDiceChainEntryPayload( - const std::vector& payload, std::vector& fmt_msgs, - BccPublicKeyInfo* entry_public_key_info) { - if (payload.empty()) { - AddValidationMessage(kCborValidateFatal, "Empty bcc entry payload."); - return kCborValidateFatal; + const cppbor::Map* payload_map, BccEntryPayload* payload) { + for (size_t i = 0; i < payload_map->size(); ++i) { + const auto& entry = (*payload_map)[i]; + if (entry.first == nullptr || entry.first->asInt() == nullptr || + entry.second == nullptr) { + continue; + } + int64_t key = entry.first->asInt()->value(); + if (key == kIssuerLabel) { + const auto& value = entry.second->asTstr()->value(); + payload->issuer.second = value; + payload->issuer.first = value.empty() ? kEmpty : kPresent; + } else if (key == kSubjectLabel) { + const auto& value = entry.second->asTstr()->value(); + payload->subject.second = value; + payload->subject.first = value.empty() ? kEmpty : kPresent; + } else if (key == kProfileNameLabel) { + const auto& value = entry.second->asTstr()->value(); + payload->profile_name.second = value; + payload->profile_name.first = value.empty() ? kEmpty : kPresent; + } else if (key == kSubjectPublicKeyLabel) { + const auto& value = entry.second->asBstr()->value(); + if (value.empty()) { + AddValidationMessage(kCborValidateError, "Empty public key."); + continue; + } + auto parse_result = cppbor::parse(value); + std::unique_ptr sub_item = + std::move(std::get<0>(parse_result)); + std::string error_message = std::move(std::get<2>(parse_result)); + if (sub_item == nullptr || !error_message.empty()) { + AddValidationMessage(kCborValidateError, + "Unable to parse public key: " + error_message); + continue; + } + if (sub_item->type() != cppbor::MAP || sub_item->asMap() == nullptr) { + AddValidationMessage(kCborValidateError, + "Unexpected public key type: " + + CppborMajorTypeToString(sub_item->type())); + continue; + } + const cppbor::Map* public_key_map = sub_item->asMap(); + if (public_key_map->size() == 0) { + payload->subject_public_key.first = kEmpty; + } else { + payload->subject_public_key.first = kPresent; + CborMessageStatus status = ProcessSubjectPublicKeyInfo( + public_key_map, &(payload->subject_public_key.second)); + if (status == kCborValidateFatal) return status; + } + } else if (key == kKeyUsageLabel) { + payload->key_usage.second = entry.second->asBstr()->value(); + payload->key_usage.first = + payload->key_usage.second.empty() ? kEmpty : kPresent; + } else if (key == kCodeHashLabel) { + payload->code_hash.second = entry.second->asBstr()->value(); + payload->code_hash.first = + payload->code_hash.second.empty() ? kEmpty : kPresent; + } else if (key == kCodeDescriptorLabel) { + payload->code_descriptor.second = entry.second->asBstr()->value(); + payload->code_descriptor.first = + payload->code_descriptor.second.empty() ? kEmpty : kPresent; + } else if (key == kConfigurationHashLabel) { + payload->config_hash.second = entry.second->asBstr()->value(); + payload->config_hash.first = + payload->config_hash.second.empty() ? kEmpty : kPresent; + } else if (key == kConfigurationDescriptorLabel) { + const auto& encoded_cd = entry.second->asBstr()->value(); + if (encoded_cd.empty()) { + AddValidationMessage(kCborValidateError, + "Empty configuration descriptor."); + continue; + } + auto parse_result = cppbor::parse(encoded_cd); + std::unique_ptr sub_item = + std::move(std::get<0>(parse_result)); + std::string error_message = std::move(std::get<2>(parse_result)); + if (sub_item == nullptr || !error_message.empty()) { + AddValidationMessage( + kCborValidateError, + "Unable to parse configuration descriptor: " + error_message); + continue; + } + if (sub_item->type() != cppbor::MAP) { + AddValidationMessage(kCborValidateError, + "Unexpected configuration descriptor type: " + + CppborMajorTypeToString(sub_item->type())); + continue; + } + const cppbor::Map* config_descriptor_map = sub_item->asMap(); + if (config_descriptor_map == nullptr || + config_descriptor_map->size() == 0) { + // OK, configuration descriptor is optional. + payload->config_descriptor.first = kEmpty; + } else { + CborMessageStatus status = ProcessConfigurationDescriptor( + config_descriptor_map, &(payload->config_descriptor.second)); + if (status == kCborValidateFatal) return status; + if (status != kCborValidateError) { + payload->config_descriptor.first = kPresent; + } + } + } else if (key == kAuthorityHashLabel) { + payload->authority_hash.second = entry.second->asBstr()->value(); + payload->authority_hash.first = + payload->authority_hash.second.empty() ? kEmpty : kPresent; + } else if (key == kAuthorityDescriptorLabel) { + payload->authority_descriptor.second = entry.second->asBstr()->value(); + payload->authority_descriptor.first = + payload->authority_descriptor.second.empty() ? kEmpty : kPresent; + } else if (key == kModeLabel) { + payload->mode.second = entry.second->asBstr()->value(); + payload->mode.first = payload->mode.second.empty() ? kEmpty : kPresent; + } } - auto parse_result = cppbor::parse(payload); - std::unique_ptr item = std::move(std::get<0>(parse_result)); - std::string error_message = std::move(std::get<2>(parse_result)); - if (item == nullptr || !error_message.empty()) { - AddValidationMessage(kCborValidateFatal, - "Unable to parse bcc entry payload: " + error_message); - return kCborValidateFatal; - } - if (item->type() != cppbor::MAP) { - AddValidationMessage(kCborValidateFatal, - "Unexpected bcc entry payload type: " + - CppborMajorTypeToString(item->type())); - return kCborValidateFatal; - } - const IssuerSubject issuer_subject = - GetIssuerSubjectFromBccEntryPayload(item->asMap()); - if (!issuer_subject.IsValid()) { - AddValidationMessage(kCborValidateError, "Missing Issuer or Subject."); - } - issuer_subject.PrintTo(fmt_msgs); - const cppbor::Bstr* subject_public_key = - GetSubjectPublicKeyFromBccEntryPayload(item->asMap()); - if (subject_public_key == nullptr) { - AddValidationMessage(kCborValidateFatal, - "Bcc entry payload has no subject public key."); - return kCborValidateFatal; - } - - // Now parse the serialized subject public key. - parse_result = cppbor::parse(subject_public_key->value()); - item = std::move(std::get<0>(parse_result)); - error_message = std::move(std::get<2>(parse_result)); - if (item == nullptr || !error_message.empty()) { - AddValidationMessage( - kCborValidateFatal, - "Unable to parse serialized subject public key: " + error_message); - return kCborValidateFatal; - } - const cppbor::Map* subject_public_key_info = item->asMap(); - if (subject_public_key_info == nullptr) { - AddValidationMessage(kCborValidateFatal, - "Invalid subject public key type. Expected Map."); - return kCborValidateFatal; - } - return ProcessSubjectPublicKeyInfo(*subject_public_key_info, fmt_msgs, - entry_public_key_info); + return message_status_; } std::string BccValidator::GetFormattedMessage() const { @@ -557,7 +1095,7 @@ std::string BccValidator::GetFormattedMessage() const { if (parsed_item == nullptr) { return ""; } - return msg_ss_.str(); + return FormatString(msg_ss_.str()); } } // namespace util } // namespace wvoec diff --git a/oemcrypto/util/src/cbor_validator.cpp b/oemcrypto/util/src/cbor_validator.cpp index 608f2875..aff3939a 100644 --- a/oemcrypto/util/src/cbor_validator.cpp +++ b/oemcrypto/util/src/cbor_validator.cpp @@ -6,12 +6,7 @@ // #include "cbor_validator.h" -#include - -#include -#include -#include -#include +#include namespace wvoec { namespace util { @@ -135,5 +130,37 @@ std::string CborValidator::CheckMapEntry(const cppbor::Map& map, } return ""; } + +std::string CborValidator::FormatString(const std::string& input) { + std::stringstream ss; + int indent = 0; + for (size_t i = 0; i < input.length(); ++i) { + char current = input[i]; + if (current == '[' || current == '{') { + if (i > 0 && input[i - 1] != ':') { + ss << std::string(indent, ' ') << current << std::endl; + } else { + ss << std::string(1, ' ') << current << std::endl; + } + indent += 4; + } else if (current == ']' || current == '}') { + indent -= 4; + ss << std::string(indent, ' ') << current; + } else if (current == ',') { + ss << ',' << std::endl; + } else { + // Handle key-value pairs + if (current != ' ') { // skip any spaces + ss << std::string(indent, ' ') << current; + while (i + 1 < input.length() && input[i + 1] != ',' && + input[i + 1] != '{' && input[i + 1] != '[' && + input[i + 1] != ']' && input[i + 1] != '}') { + ss << input[++i]; + } + } + } + } + return ss.str(); +} } // namespace util } // namespace wvoec diff --git a/oemcrypto/util/src/device_info_validator.cpp b/oemcrypto/util/src/device_info_validator.cpp index 9877f43b..fafdf5b6 100644 --- a/oemcrypto/util/src/device_info_validator.cpp +++ b/oemcrypto/util/src/device_info_validator.cpp @@ -8,14 +8,15 @@ #include +#include "prov4_validation_helper.h" #include "string_conversions.h" namespace wvoec { namespace util { namespace { -// Number of required device info properties returned from TEE for DeviceInfo -// version v3. -constexpr uint32_t kNumTeeDeviceInfoEntriesV3 = 14; +const std::string kComponent = "DeviceInfo"; + +// Device info properties returned from TEE for DeviceInfo version v3. const std::vector kDeviceInfoKeysV3 = {"brand", "manufacturer", "product", @@ -31,19 +32,441 @@ const std::vector kDeviceInfoKeysV3 = {"brand", "security_level", "fused"}; -struct AttestationIdEntry { - const char* id; - bool alwaysValidate; -}; +bool isValidYYYYMM(const std::string& date) { + // Check if the string has exactly 6 characters + if (date.size() != 6) { + return false; + } + // Check if all characters are digits + for (char c : date) { + if (!std::isdigit(c)) { + return false; + } + } + // Extract year and month as integers + int year = std::stoi(date.substr(0, 4)); // YYYY + int month = std::stoi(date.substr(4, 2)); // MM + // Check if year is within a reasonable range (e.g., 1000 to 9999) + if (year < 1000 || year > 9999) { + return false; + } + // Check if month is valid (01-12) + if (month < 1 || month > 12) { + return false; + } + return true; +} -// Attestation Id and whether it is required. -constexpr AttestationIdEntry kAttestationIdEntrySet[] = {{"brand", false}, - {"manufacturer", true}, - {"product", true}, - {"model", true}, - {"device", false}}; +bool isValidYYYYMMDD(const std::string& date) { + // Check if the string has exactly 8 characters + if (date.size() != 8) { + return false; + } + // Check if all characters are digits + for (char c : date) { + if (!std::isdigit(c)) { + return false; + } + } + // Extract year, month, and day as integers + int year = std::stoi(date.substr(0, 4)); // YYYY + int month = std::stoi(date.substr(4, 2)); // MM + int day = std::stoi(date.substr(6, 2)); // DD + if (!isValidYYYYMM(date.substr(0, 6))) return false; + // Check if year is within a reasonable range (e.g., 1000 to 9999) + if (year < 1000 || year > 9999) { + return false; + } + // Check if month is valid (01-12) + if (month < 1 || month > 12) { + return false; + } + // Check if day is valid based on the month + if (day < 1 || day > 31) { + return false; + } + // Additional check for days in specific months + if ((month == 4 || month == 6 || month == 9 || month == 11) && day > 30) { + return false; // April, June, September, November have 30 days + } + if (month == 2) { // February + // Simple leap year check + bool isLeapYear = (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0); + if (day > 29 || (day == 29 && !isLeapYear)) { + return false; // February can't have more than 29 days, and only 29 in + // leap years + } + } + return true; +} } // namespace +std::string DeviceInfo::ToString() const { + std::stringstream ss; + ss << "{"; + PrintField(ss, "brand", brand); + PrintField(ss, "manufacturer", manufacturer); + PrintField(ss, "product", product); + PrintField(ss, "model", model); + PrintField(ss, "device", device); + PrintField(ss, "vb_state", vb_state); + PrintField(ss, "bootloader_state", bootloader_state); + PrintBstrField(ss, "vbmeta_digest", vbmeta_digest); + PrintField(ss, "os_version", os_version); + PrintField(ss, "system_patch_level", system_patch_level); + PrintField(ss, "boot_patch_level", boot_patch_level); + PrintField(ss, "vendor_patch_level", vendor_patch_level); + PrintField(ss, "security_level", security_level); + PrintField(ss, "fused", fused); + PrintField(ss, "board", board); + PrintField(ss, "version", version); + PrintField(ss, "att_id_state", att_id_state); + ss << " }"; + return ss.str(); +} + +CborMessageStatus DeviceInfoValidator::BuildDeviceInfo( + DeviceInfo& device_info, const cppbor::Map* device_info_map) { + std::set previous_keys; + for (auto const& entry : *device_info_map) { + if (!entry.first->asTstr()) { + AddValidationMessage( + kCborValidateError, + "Unexpected entry key type. Expected TSTR, but got " + + CppborMajorTypeToString(entry.first->type())); + continue; + } + const std::string& key = entry.first->asTstr()->value(); + if (previous_keys.find(key) != previous_keys.end()) { + AddValidationMessage(kCborValidateError, + "Duplicate device info entry: " + key); + continue; + } + previous_keys.insert(key); + if (key == "brand") { + if (entry.second->asTstr() == nullptr) { + device_info.brand.first = kEmpty; + } else { + device_info.brand.second = entry.second->asTstr()->value(); + device_info.brand.first = + device_info.brand.second.empty() ? kEmpty : kPresent; + } + } else if (key == "manufacturer") { + if (entry.second->asTstr() == nullptr) { + device_info.manufacturer.first = kEmpty; + } else { + device_info.manufacturer.second = entry.second->asTstr()->value(); + device_info.manufacturer.first = + device_info.manufacturer.second.empty() ? kEmpty : kPresent; + } + } else if (key == "product") { + if (entry.second->asTstr() == nullptr) { + device_info.product.first = kEmpty; + } else { + device_info.product.second = entry.second->asTstr()->value(); + device_info.product.first = + device_info.product.second.empty() ? kEmpty : kPresent; + } + } else if (key == "model") { + if (entry.second->asTstr() == nullptr) { + device_info.model.first = kEmpty; + } else { + device_info.model.second = entry.second->asTstr()->value(); + device_info.model.first = + device_info.model.second.empty() ? kEmpty : kPresent; + } + } else if (key == "device") { + if (entry.second->asTstr() == nullptr) { + device_info.device.first = kEmpty; + } else { + device_info.device.second = entry.second->asTstr()->value(); + device_info.device.first = + device_info.device.second.empty() ? kEmpty : kPresent; + } + } else if (key == "vb_state") { + if (entry.second->asTstr() == nullptr) { + device_info.vb_state.first = kEmpty; + } else { + device_info.vb_state.second = entry.second->asTstr()->value(); + device_info.vb_state.first = + device_info.vb_state.second.empty() ? kEmpty : kPresent; + } + } else if (key == "bootloader_state") { + if (entry.second->asTstr() == nullptr) { + device_info.bootloader_state.first = kEmpty; + } else { + device_info.bootloader_state.second = entry.second->asTstr()->value(); + device_info.bootloader_state.first = + device_info.bootloader_state.second.empty() ? kEmpty : kPresent; + } + } else if (key == "vbmeta_digest") { + if (entry.second->asBstr() == nullptr) { + device_info.vbmeta_digest.first = kEmpty; + } else { + device_info.vbmeta_digest.second = entry.second->asBstr()->value(); + device_info.vbmeta_digest.first = + device_info.vbmeta_digest.second.empty() ? kEmpty : kPresent; + } + } else if (key == "os_version") { + if (entry.second->asTstr() == nullptr) { + device_info.os_version.first = kEmpty; + } else { + device_info.os_version.second = entry.second->asTstr()->value(); + device_info.os_version.first = + device_info.os_version.second.empty() ? kEmpty : kPresent; + } + } else if (key == "system_patch_level") { + if (entry.second->asUint() == nullptr) { + device_info.system_patch_level.first = kEmpty; + } else { + device_info.system_patch_level.second = + std::to_string(entry.second->asUint()->value()); + device_info.system_patch_level.first = + device_info.system_patch_level.second.empty() ? kEmpty : kPresent; + } + } else if (key == "boot_patch_level") { + if (entry.second->asUint() == nullptr) { + device_info.boot_patch_level.first = kEmpty; + } else { + device_info.boot_patch_level.second = + std::to_string(entry.second->asUint()->value()); + device_info.boot_patch_level.first = + device_info.boot_patch_level.second.empty() ? kEmpty : kPresent; + } + } else if (key == "vendor_patch_level") { + if (entry.second->asUint() == nullptr) { + device_info.vendor_patch_level.first = kEmpty; + } else { + device_info.vendor_patch_level.second = + std::to_string(entry.second->asUint()->value()); + device_info.vendor_patch_level.first = + device_info.vendor_patch_level.second.empty() ? kEmpty : kPresent; + } + } else if (key == "security_level") { + if (entry.second->asTstr() == nullptr) { + device_info.security_level.first = kEmpty; + } else { + device_info.security_level.second = entry.second->asTstr()->value(); + device_info.security_level.first = + device_info.security_level.second.empty() ? kEmpty : kPresent; + } + } else if (key == "fused") { + if (entry.second->asUint() == nullptr) { + device_info.fused.first = kEmpty; + } else { + device_info.fused.second = + std::to_string(entry.second->asUint()->value()); + device_info.fused.first = + device_info.fused.second.empty() ? kEmpty : kPresent; + } + } else if (key == "board") { + if (entry.second->asTstr() == nullptr) { + device_info.board.first = kEmpty; + } else { + device_info.board.second = entry.second->asTstr()->value(); + device_info.board.first = + device_info.board.second.empty() ? kEmpty : kPresent; + } + } else if (key == "version") { + if (entry.second->asUint() == nullptr) { + device_info.version.first = kEmpty; + } else { + device_info.version.second = + std::to_string(entry.second->asUint()->value()); + device_info.version.first = + device_info.version.second.empty() ? kEmpty : kPresent; + } + } else if (key == "att_id_state") { + if (entry.second->asTstr() == nullptr) { + device_info.att_id_state.first = kEmpty; + } else { + device_info.att_id_state.second = entry.second->asTstr()->value(); + device_info.att_id_state.first = + device_info.att_id_state.second.empty() ? kEmpty : kPresent; + } + } + } + return kCborValidateOk; +} + +CborMessageStatus DeviceInfo::ValidateV1Fields( + std::vector>& msgs) const { + CborMessageStatus status = kCborParseOk; + CborMessageStatus cur_status = + ValidateRequiredField("security_level", kComponent, security_level, msgs); + ApplyStatus(status, cur_status); + cur_status = + ValidateRequiredField("att_id_state", kComponent, att_id_state, msgs); + ApplyStatus(status, cur_status); + return status; +} + +CborMessageStatus DeviceInfo::ValidateV2Fields( + bool is_tee_device_info, + std::vector>& msgs) const { + CborMessageStatus status = kCborParseOk; + CborMessageStatus cur_status; + // TEE IRPC instances require all entries to be present in device info. + // Non-TEE instances may omit `os_version`. + if (is_tee_device_info) { + cur_status = + ValidateRequiredField("os_version", kComponent, os_version, msgs); + ApplyStatus(status, cur_status); + } + cur_status = ValidateRequiredField("brand", kComponent, brand, msgs); + ApplyStatus(status, cur_status); + cur_status = ValidateRequiredField("product", kComponent, product, msgs); + ApplyStatus(status, cur_status); + cur_status = ValidateRequiredField("device", kComponent, device, msgs); + ApplyStatus(status, cur_status); + cur_status = ValidateRequiredField("vb_state", kComponent, vb_state, msgs); + ApplyStatus(status, cur_status); + if (cur_status == kCborValidateOk) { + const std::string& value = vb_state.second; + if (value != "green" && value != "yellow" && value != "orange") { + msgs.push_back(std::make_pair( + kCborValidateError, + kComponent + ": unexpected value for vb_state (" + value + ")")); + ApplyStatus(status, kCborValidateError); + } + } + cur_status = ValidateRequiredField("bootloader_state", kComponent, + bootloader_state, msgs); + ApplyStatus(status, cur_status); + if (cur_status == kCborValidateOk) { + const std::string& value = bootloader_state.second; + if (value != "locked" && value != "unlocked") { + msgs.push_back(std::make_pair( + kCborValidateError, kComponent + + ": unexpected value for bootloader_state (" + + value + ")")); + ApplyStatus(status, kCborValidateError); + } + } + cur_status = + ValidateRequiredField("vbmeta_digest", kComponent, vbmeta_digest, msgs); + ApplyStatus(status, cur_status); + + cur_status = + ValidateRequiredField("security_level", kComponent, security_level, msgs); + ApplyStatus(status, cur_status); + if (cur_status == kCborValidateOk) { + const std::string& value = security_level.second; + if (value != "tee" && value != "strongbox") { + msgs.push_back(std::make_pair( + kCborValidateError, kComponent + + ": unexpected value for security_level (" + + value + ")")); + ApplyStatus(status, kCborValidateError); + } + } + cur_status = ValidateRequiredField("fused", kComponent, fused, msgs); + ApplyStatus(status, cur_status); + if (cur_status == kCborValidateOk) { + const std::string& value = fused.second; + if (value != "1" && value != "0") { + msgs.push_back(std::make_pair( + kCborValidateError, + kComponent + ": unexpected value for fused (" + value + ")")); + ApplyStatus(status, kCborValidateError); + } + } + return status; +} + +CborMessageStatus DeviceInfo::ValidateV3Fields( + bool is_tee_device_info, + std::vector>& msgs) const { + CborMessageStatus status = kCborParseOk; + CborMessageStatus cur_status; + // Checks for the required fields that only apply to v3: system_patch_level, + // boot_patch_level, vendor_patch_level + cur_status = ValidateRequiredField("system_patch_level", kComponent, + system_patch_level, msgs); + ApplyStatus(status, cur_status); + if (cur_status == kCborValidateOk) { + const std::string& value = system_patch_level.second; + if (!isValidYYYYMM(value)) { + msgs.push_back(std::make_pair( + kCborValidateError, kComponent + + ": invalid value for system_patch_level (" + + value + "), should be YYYYMM")); + ApplyStatus(status, kCborValidateError); + } + } + cur_status = ValidateRequiredField("boot_patch_level", kComponent, + boot_patch_level, msgs); + ApplyStatus(status, cur_status); + if (cur_status == kCborValidateOk) { + const std::string& value = boot_patch_level.second; + if (!isValidYYYYMMDD(value)) { + msgs.push_back(std::make_pair( + kCborValidateError, kComponent + + ": invalid value for boot_patch_level (" + + value + "), should be YYYYMMDD")); + ApplyStatus(status, kCborValidateError); + } + } + cur_status = ValidateRequiredField("vendor_patch_level", kComponent, + vendor_patch_level, msgs); + ApplyStatus(status, cur_status); + if (cur_status == kCborValidateOk) { + const std::string& value = vendor_patch_level.second; + if (!isValidYYYYMMDD(value)) { + msgs.push_back(std::make_pair( + kCborValidateError, kComponent + + ": invalid value for vendor_patch_level (" + + value + "), should be YYYYMMDD")); + ApplyStatus(status, kCborValidateError); + } + } + cur_status = ValidateV2Fields(is_tee_device_info, msgs); + ApplyStatus(status, cur_status); + return status; +} + +CborMessageStatus DeviceInfo::Validate( + std::vector>& msgs, bool is_gms, + int version_number) const { + CborMessageStatus status = kCborParseOk; + CborMessageStatus cur_status; + // AOSP and CE devices. + if (!is_gms) { + cur_status = + ValidateImportantField("manufacturer", kComponent, manufacturer, msgs); + ApplyStatus(status, cur_status); + cur_status = ValidateImportantField("model", kComponent, model, msgs); + ApplyStatus(status, cur_status); + if (fused.first != kPresent) { + msgs.push_back(std::make_pair( + kCborValidateWarning, + "DeviceInfo: missing field fused. Treat this as an error if it is " + "from a device that uses a de-generated BCC, in which case fused is " + "a required field.")); + ApplyStatus(status, kCborValidateWarning); + } + return status; + } + // GMS device requires more fields. + bool is_tee_device_info = + (security_level.first == kPresent && security_level.second == "tee"); + if (version_number == 3) { + cur_status = ValidateV3Fields(is_tee_device_info, msgs); + ApplyStatus(status, cur_status); + } else if (version_number == 2) { + cur_status = ValidateV2Fields(is_tee_device_info, msgs); + ApplyStatus(status, cur_status); + } else if (version_number == 1) { + cur_status = ValidateV1Fields(msgs); + ApplyStatus(status, cur_status); + } else { + msgs.push_back({kCborValidateFatal, "Unrecognized device info version: " + + std::to_string(version_number)}); + return kCborValidateFatal; + } + return status; +} + CborMessageStatus DeviceInfoValidator::Parse( const std::vector& device_info) { message_status_ = CborValidator::Parse(device_info); @@ -71,157 +494,33 @@ CborMessageStatus DeviceInfoValidator::Validate() { AddValidationMessage(kCborValidateError, "Device info ordering is non-canonical."); } - const cppbor::Item* security_level = - GetMapEntry(*device_info_map, "security_level"); - const bool is_tee_device_info = security_level && security_level->asTstr() && - security_level->asTstr()->value() == "tee"; - std::set previous_keys; - switch (version_number_) { - case 3: - if (is_tee_device_info && - device_info_map->size() != kNumTeeDeviceInfoEntriesV3) { - AddValidationMessage( - kCborValidateError, - "Incorrect number of TEE device info entries. Expected " + - std::to_string(kNumTeeDeviceInfoEntriesV3) + " but got " + - std::to_string(device_info_map->size())); - } - // TEE IRPC instances require all entries to be present in device info. - // Non-TEE instances may omit `os_version`. - if (!is_tee_device_info && - (device_info_map->size() != kNumTeeDeviceInfoEntriesV3 && - device_info_map->size() != kNumTeeDeviceInfoEntriesV3 - 1)) { - AddValidationMessage( - kCborValidateError, - "Incorrect number of non-TEE device info entries. Expected " + - std::to_string(kNumTeeDeviceInfoEntriesV3 - 1) + " but got " + - std::to_string(device_info_map->size())); - } - for (auto const& entry : *device_info_map) { - if (!entry.first->asTstr()) { - AddValidationMessage( - kCborValidateError, - "Unexpected entry key type. Expected TSTR, but got " + - CppborMajorTypeToString(entry.first->type())); - continue; - } - const std::string& key = entry.first->asTstr()->value(); - if (previous_keys.find(key) != previous_keys.end()) { - AddValidationMessage(kCborValidateError, - "Duplicate device info entry: " + key); - } - previous_keys.insert(key); - if (std::find(kDeviceInfoKeysV3.begin(), kDeviceInfoKeysV3.end(), - key) == kDeviceInfoKeysV3.end()) { - AddValidationMessage(kCborValidateError, - "Unrecognized device info entry: " + key); - } - } - // Checks for the required fields that only apply to v3. - CheckDeviceInfoMapEntry(*device_info_map, cppbor::UINT, - "system_patch_level"); - CheckDeviceInfoMapEntry(*device_info_map, cppbor::UINT, - "boot_patch_level"); - CheckDeviceInfoMapEntry(*device_info_map, cppbor::UINT, - "vendor_patch_level"); - // Fall through - CORE_UTIL_FALLTHROUGH; - case 2: - for (const auto& entry : kAttestationIdEntrySet) { - if (entry.alwaysValidate) { - CheckDeviceInfoMapEntry(*device_info_map, cppbor::TSTR, entry.id); - } - } - CheckDeviceInfoMapEntry(*device_info_map, cppbor::TSTR, "vb_state"); - CheckDeviceInfoMapEntry(*device_info_map, cppbor::TSTR, - "bootloader_state"); - CheckDeviceInfoMapEntry(*device_info_map, cppbor::BSTR, "vbmeta_digest"); - CheckDeviceInfoMapEntry(*device_info_map, cppbor::UINT, - "system_patch_level"); - CheckDeviceInfoMapEntry(*device_info_map, cppbor::UINT, - "boot_patch_level"); - CheckDeviceInfoMapEntry(*device_info_map, cppbor::UINT, - "vendor_patch_level"); - CheckDeviceInfoMapEntry(*device_info_map, cppbor::UINT, "fused"); - CheckDeviceInfoMapEntry(*device_info_map, cppbor::TSTR, "security_level"); - if (is_tee_device_info) { - CheckDeviceInfoMapEntry(*device_info_map, cppbor::TSTR, "os_version"); - } - break; - case 1: - CheckDeviceInfoMapEntry(*device_info_map, cppbor::TSTR, "security_level"); - CheckDeviceInfoMapEntry(*device_info_map, cppbor::TSTR, "att_id_state"); - break; - default: - AddValidationMessage( - kCborValidateFatal, - "Unrecognized version: " + std::to_string(version_number_)); - return message_status_; - } + DeviceInfo device_info; + CborMessageStatus cur_status = BuildDeviceInfo(device_info, device_info_map); + if (cur_status == kCborValidateFatal) return cur_status; + // Print the parse device info. + msg_ss_ << device_info.ToString() << "\n"; + + std::vector> msgs; + cur_status = device_info.Validate(msgs, is_gms_, version_number_); + ApplyStatus(message_status_, cur_status); + for (const auto& msg : msgs) { + AddValidationMessage(msg.first, msg.second); + } if (message_status_ == kCborParseOk) message_status_ = kCborValidateOk; return message_status_; } -void DeviceInfoValidator::CheckDeviceInfoMapEntry( - const cppbor::Map& device_info, cppbor::MajorType major_type, - const std::string& entry_name) { - const std::string error = CheckMapEntry(device_info, major_type, entry_name); - if (!error.empty()) { - AddValidationMessage(kCborValidateError, error); - } -} - std::string DeviceInfoValidator::GetFormattedMessage() const { if (message_status_ == kCborUninitialized || - message_status_ == kCborParseError || - message_status_ == kCborValidateFatal) { + message_status_ == kCborParseError) { return std::string(); } const cppbor::Item* parsed_item = std::get<0>(parse_result()).get(); if (parsed_item == nullptr) { return ""; } - // Writes YAML-formatted output to |msg_ss_|. - std::stringstream msg_ss; - msg_ss << "---\n"; - msg_ss << "DEVICE INFO MAP:\n"; - - for (auto const& entry : *(parsed_item->asMap())) { - auto const& entry_value = entry.second; - // Device info map only allows TSTR key type. - if (!entry.first->asTstr()) continue; - const std::string& name = entry.first->asTstr()->value(); - msg_ss << " " << name << ": "; - switch (entry_value->type()) { - case cppbor::TSTR: { - const std::string val = entry_value->asTstr()->value().empty() - ? "" - : entry_value->asTstr()->value(); - msg_ss << val << "\n"; - break; - } - case cppbor::UINT: - msg_ss << std::to_string(entry_value->asUint()->value()) << "\n"; - break; - case cppbor::NINT: - msg_ss << std::to_string(entry_value->asNint()->value()) << "\n"; - break; - case cppbor::BSTR: { - const std::vector& bytes = entry_value->asBstr()->value(); - const std::string val = - bytes.empty() ? "" : wvutil::b2a_hex(bytes); - msg_ss << val << "\n"; - break; - } - default: - msg_ss << "Unsupported type (" - << CppborMajorTypeToString(entry_value->type()) << ")\n"; - break; - } - } - msg_ss << "...\n"; - return msg_ss.str(); + return FormatString(msg_ss_.str()); } } // namespace util } // namespace wvoec diff --git a/oemcrypto/util/src/prov4_validation_helper.cpp b/oemcrypto/util/src/prov4_validation_helper.cpp new file mode 100644 index 00000000..447db251 --- /dev/null +++ b/oemcrypto/util/src/prov4_validation_helper.cpp @@ -0,0 +1,25 @@ +// 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 "prov4_validation_helper.h" + +namespace wvoec { +namespace util { +std::string StatusToString(FieldStatus status) { + if (status == FieldStatus::kAbsent) { + return ""; + } + if (status == FieldStatus::kEmpty) { + return ""; + } + return "present"; +} + +void ApplyStatus(CborMessageStatus& status, CborMessageStatus new_status) { + if (new_status > status) { + status = new_status; + } +} +} // namespace util +} // namespace wvoec diff --git a/oemcrypto/util/src/signed_csr_payload_validator.cpp b/oemcrypto/util/src/signed_csr_payload_validator.cpp index c3923d5a..020d958f 100644 --- a/oemcrypto/util/src/signed_csr_payload_validator.cpp +++ b/oemcrypto/util/src/signed_csr_payload_validator.cpp @@ -13,24 +13,164 @@ namespace wvoec { namespace util { namespace { -enum CoseKeyAlgorithm : int { - AES_GCM_256 = 3, - HMAC_256 = 5, - ES256 = -7, // ECDSA with SHA-256 - EDDSA = -8, - ECDH_ES_HKDF_256 = -25, - ES384 = -35, // ECDSA with SHA-384 -}; - -void AddMessages(std::stringstream& ss, - const std::vector& fmt_msgs, int indent) { - const std::string spaces = std::string(indent * 2, ' '); - for (auto& msg : fmt_msgs) { - ss << spaces << msg << "\n"; - } -} +const std::string kCsrPayloadVersion = "3"; +constexpr size_t kMaxChallengeSize = 64; } // namespace +std::string CertificateType::ToString() const { + std::stringstream ss; + ss << "{"; + ss << " type:"; + if (type.first != kPresent) { + ss << StatusToString(type.first) << ","; + } else { + ss << type.second << ","; + } + ss << " }"; + return ss.str(); +} + +CborMessageStatus CertificateType::Validate( + std::vector>& msgs) const { + CborMessageStatus status = kCborParseOk; + const std::string component = "CertificateType"; + CborMessageStatus cur_status = + ValidateRequiredField("type", component, type, msgs); + ApplyStatus(status, cur_status); + if (cur_status == kCborValidateOk) { + if (type.second != "widevine" && type.second != "keymint" && + type.second != "rkp-vm") { + msgs.push_back(std::make_pair( + kCborValidateError, + component + ": Invalid certificate type " + type.second)); + ApplyStatus(status, kCborValidateError); + } + } + return status; +} + +std::string SignedDataProtected::ToString() const { + std::stringstream ss; + ss << "{"; + ss << " algorithm:"; + if (algorithm.first != kPresent) { + ss << StatusToString(algorithm.first) << ","; + } else { + if (algorithm.second == DEVICE_KEY_ALGORITHM_EDDSA) { + ss << "EdDSA" << ","; + } else if (algorithm.second == DEVICE_KEY_ALGORITHM_ES256) { + ss << "ECDSA_SHA256" << ","; + } else if (algorithm.second == DEVICE_KEY_ALGORITHM_ES384) { + ss << "ECDSA_SHA384" << ","; + } else { + ss << "unknown(" << algorithm.second << ")" << ","; + } + } + ss << " }"; + return ss.str(); +} + +CborMessageStatus SignedDataProtected::Validate( + std::vector>& msgs) const { + CborMessageStatus status = kCborParseOk; + const std::string component = "SignedDataProtected"; + CborMessageStatus cur_status = + ValidateRequiredField("algorithm", component, algorithm, msgs); + ApplyStatus(status, cur_status); + if (cur_status == kCborValidateOk) { + if (algorithm.second != DEVICE_KEY_ALGORITHM_EDDSA && + algorithm.second != DEVICE_KEY_ALGORITHM_ES256 && + algorithm.second != DEVICE_KEY_ALGORITHM_ES384) { + msgs.push_back(std::make_pair(kCborValidateError, + component + ": Invalid algorithm " + + std::to_string(algorithm.second))); + ApplyStatus(status, kCborValidateError); + } + } + return status; +} + +// CsrPayload = [ ; CBOR Array defining the payload for CSR. +// version: 3, ; The CsrPayload CDDL Schema version. +// CertificateType: "widevine" ; The type of certificate being requested. +// DeviceInfo, ; Defined in Android DeviceInfo.aidl +// KeysToSign: [] ; Empty list +// ] +std::string CsrPayload::ToString() const { + std::stringstream ss; + ss << "["; + PrintField(ss, "version", version); + PrintCborField(ss, "certificate_type", certificate_type); + PrintCborField(ss, "device_info", device_info); + ss << " keys_to_sign:["; + for (auto& key : keys_to_sign) { + PrintCborField(ss, "keys_to_sign", std::make_pair(kAbsent, key)); + } + ss << " ],"; + ss << " ]"; + return ss.str(); +} + +CborMessageStatus CsrPayload::Validate( + std::vector>& msgs) const { + CborMessageStatus status = kCborParseOk; + const std::string component = "CsrPayload"; + CborMessageStatus cur_status = + ValidateRequiredField("version", component, version, msgs); + ApplyStatus(status, cur_status); + if (cur_status == kCborValidateOk && version.second != kCsrPayloadVersion) { + msgs.push_back(std::make_pair( + kCborValidateError, component + ": Invalid version " + version.second + + ". Expected " + kCsrPayloadVersion)); + ApplyStatus(status, kCborValidateError); + } + cur_status = + ValidateRequiredField("device_info", component, device_info, msgs); + ApplyStatus(status, cur_status); + // Validation of device_info is done in DeviceInfoValidator separately for + // now. + return status; +} + +// DataToBeSigned = [ +// challenge: bstr .size (0..64), +// bstr .cbor CsrPayload, +// ] +std::string DataToBeSigned::ToString() const { + std::stringstream ss; + ss << "["; + PrintBstrField(ss, "challenge", challenge); + PrintCborField(ss, "csr_payload", csr_payload); + ss << " ]"; + return ss.str(); +} + +CborMessageStatus DataToBeSigned::Validate( + std::vector>& msgs) const { + CborMessageStatus status = kCborParseOk; + const std::string component = "DataToBeSigned"; + CborMessageStatus cur_status = + ValidateRequiredField("challenge", component, challenge, msgs); + ApplyStatus(status, cur_status); + if (cur_status == kCborValidateOk && + challenge.second.size() > kMaxChallengeSize) { + msgs.push_back(std::make_pair(kCborValidateError, + "Unexpected challenge size: " + + std::to_string(challenge.second.size()) + + ". Expected no more than " + + std::to_string(kMaxChallengeSize))); + ApplyStatus(status, kCborValidateError); + } + cur_status = + ValidateRequiredField("csr_payload", component, csr_payload, msgs); + ApplyStatus(status, cur_status); + if (cur_status == kCborValidateOk) { + cur_status = csr_payload.second.Validate(msgs); + ApplyStatus(status, cur_status); + } + return status; +} + // clang-format off // Signed CSR payload CBOR data to be verified: // SignedData = [ @@ -41,134 +181,154 @@ void AddMessages(std::stringstream& ss, // signature: bstr ; PureEd25519(priv_key, Sig_structure) / // ; ECDSA(priv_key, Sig_structure) // ] -// -// DataToBeSigned = [ -// challenge: bstr .size (0..64), -// bstr .cbor CsrPayload, -// ] -// -// CsrPayload = [ ; CBOR Array defining the payload for CSR. -// version: 3, ; The CsrPayload CDDL Schema version. -// CertificateType: "widevine" ; The type of certificate being requested. -// DeviceInfo, ; Defined in Android DeviceInfo.aidl -// KeysToSign: [] ; Empty list -// ] -// -// Sig_structure = [ -// context: "Signature1", -// protected: bstr .cbor { 1 : AlgorithmEdDSA / AlgorithmES256 / AlgorithmES384 }, -// external_aad: bstr .size 0, -// payload: bstr .cbor DataToBeSigned / nil, -// ] // clang-format on +std::string SignedCsrPayload::ToString() const { + std::stringstream ss; + ss << "SignedCSRpayload = ["; + PrintCborField(ss, "protected", protected_data); + PrintField(ss, "unprotected", unprotected); + PrintCborField(ss, "payload", payload); + PrintBstrField(ss, "signature", signature); + ss << " ]"; + return ss.str(); +} + +CborMessageStatus SignedCsrPayload::Validate( + std::vector>& msgs) const { + CborMessageStatus status = kCborParseOk; + const std::string component = "SignedCsrPayload"; + CborMessageStatus cur_status = + ValidateRequiredField("protected", component, protected_data, msgs); + ApplyStatus(status, cur_status); + if (cur_status == kCborValidateOk) { + cur_status = protected_data.second.Validate(msgs); + ApplyStatus(status, cur_status); + } + cur_status = ValidateRequiredField("payload", component, payload, msgs); + ApplyStatus(status, cur_status); + if (cur_status == kCborValidateOk) { + cur_status = payload.second.Validate(msgs); + ApplyStatus(status, cur_status); + } + cur_status = ValidateRequiredField("signature", component, signature, msgs); + ApplyStatus(status, cur_status); + // Skip CoseSign1 signature verification since the validator doesn't have + // verifying keys. + return status; +} // Caller ensures the pointer is not NULL. -CborMessageStatus SignedCsrPayloadValidator::ValidateProtectedParams( - const cppbor::Bstr* protected_params) { - auto parse_result = cppbor::parse(protected_params); - std::unique_ptr parsed_protected_params = - std::move(std::get<0>(parse_result)); - const std::string error_message = std::move(std::get<2>(parse_result)); - if (!parsed_protected_params || !error_message.empty()) { - AddValidationMessage(kCborValidateFatal, - "Unable to parse protectedParams: " + error_message); - return message_status_; - } - // |parsed_protected_params| is a CBOR map of 1 entry - const cppbor::Map* protected_params_map = parsed_protected_params->asMap(); - if (!protected_params_map) { - AddValidationMessage( - kCborValidateFatal, - "ProtectedParams must be a CBOR map. Actual type: " + - CppborMajorTypeToString(parsed_protected_params->type())); - return message_status_; - } - if (protected_params_map->size() == 0) { - AddValidationMessage(kCborValidateFatal, "ProtectedParams is empty."); - return message_status_; - } - if (protected_params_map->size() > 1) { +CborMessageStatus SignedCsrPayloadValidator::ProcessSignedDataProtected( + const cppbor::Map* protected_map, SignedDataProtected* protected_data) { + if (protected_map->size() > 1) { AddValidationMessage(kCborValidateWarning, - "ProtectedParams expects 1 entry, but got " + - std::to_string(protected_params_map->size())); + "Protected data map expects 1 entry, but got " + + std::to_string(protected_map->size())); } - // TODO(b/314141962): Replace this with the map lookup function in cppbor - // library - bool algo_found = false; - for (auto const& entry : *protected_params_map) { - if (!entry.first->asInt()) { - AddValidationMessage(kCborValidateWarning, - "Unsupported key type: " + - CppborMajorTypeToString(entry.first->type())); - } else if (entry.first->asInt()->value() != 1) { - AddValidationMessage(kCborValidateWarning, - "Unsupported key value: " + - std::to_string(entry.first->asInt()->value())); - } else { - auto& algorithm = entry.second; - if (!algorithm) { - AddValidationMessage(kCborValidateFatal, "Algorithm value is missing."); - return message_status_; - } - if (!algorithm->asInt()) { - AddValidationMessage(kCborValidateFatal, - "Unsupported signature algorithm value type: " + - CppborMajorTypeToString(algorithm->type())); - return message_status_; - } - std::string kv = "1: "; - const int64_t algo = algorithm->asInt()->value(); - if (algo == EDDSA) - kv += "EDDSA"; - else if (algo == ES256) - kv += "ES256"; - else if (algo == ES384) - kv += "ES384"; - else { - kv += std::to_string(algo); - AddValidationMessage( - kCborValidateFatal, - "Unsupported signature algorithm value: " + std::to_string(algo)); - return message_status_; - } - AddMessages(msg_ss_, {std::move(kv)}, 1); - algo_found = true; + for (size_t index = 0; index < protected_map->size(); ++index) { + std::pair&, + const std::unique_ptr&> + entry = (*protected_map)[index]; + if (entry.first->type() != cppbor::NINT && + entry.first->type() != cppbor::UINT) { + AddValidationMessage( + kCborValidateWarning, + "Invalid key type in protected data map in signed CSR payload: " + + CppborMajorTypeToString(entry.first->type())); + continue; + } + const int64_t map_key = entry.first->asInt()->value(); + if (map_key == 1) { + if (entry.second->type() != cppbor::NINT && + entry.second->type() != cppbor::UINT) { + AddValidationMessage(kCborValidateError, + "Invalid value type in protected data map for " + "key 1: " + + CppborMajorTypeToString(entry.second->type())); + continue; + } + protected_data->algorithm.first = kPresent; + protected_data->algorithm.second = entry.second->asInt()->value(); + } else { + AddValidationMessage(kCborValidateWarning, + "Unsupported key value in protected data map: " + + std::to_string(map_key)); } - } - if (!algo_found) { - AddValidationMessage(kCborValidateFatal, - "ProtectedParams has no signature algorithm."); } return message_status_; } // Caller ensures the pointer is not NULL. -CborMessageStatus SignedCsrPayloadValidator::ValidateDataToBeSigned( - const cppbor::Bstr* data) { - if (data->value().empty()) { - AddValidationMessage(kCborValidateFatal, "Payload to be signed is empty."); - return message_status_; +CborMessageStatus SignedCsrPayloadValidator::ProcessCsrPayload( + const cppbor::Array* csr_payload_array, CsrPayload* csr_payload) { + if (csr_payload_array->size() < 4U) { + AddValidationMessage(kCborValidateFatal, + "CSR payload must contain 4 elements. Actual size: " + + std::to_string(csr_payload_array->size())); + return kCborValidateFatal; } - auto parse_result = cppbor::parse(data); - std::unique_ptr parsed_payload_to_be_signed = - std::move(std::get<0>(parse_result)); - std::string error_message = std::move(std::get<2>(parse_result)); - if (!parsed_payload_to_be_signed || !error_message.empty()) { + // |csr_payload_array| is an array of 4 elements. + const cppbor::Uint* version = csr_payload_array->get(0)->asUint(); + if (version != nullptr) { + csr_payload->version.first = kPresent; + csr_payload->version.second = std::to_string(version->value()); + } else { AddValidationMessage( - kCborValidateFatal, - "Unable to parse the payload to be signed: " + error_message); - return message_status_; + kCborValidateError, + "CSR payload version must be an unsigned integer. Actual type: " + + CppborMajorTypeToString(csr_payload_array->get(0)->type())); } - // Verify that |parsed_payload_to_be_signed| is a valid array. - const cppbor::Array* payload_to_be_signed_array = - parsed_payload_to_be_signed->asArray(); - if (!payload_to_be_signed_array) { + const cppbor::Tstr* certificate_type = csr_payload_array->get(1)->asTstr(); + if (certificate_type != nullptr) { + if (certificate_type->value().empty()) { + csr_payload->certificate_type.first = kEmpty; + } else { + csr_payload->certificate_type.first = kPresent; + CertificateType& certificate_type_ref = + csr_payload->certificate_type.second; + certificate_type_ref.type.second = certificate_type->value(); + certificate_type_ref.type.first = + certificate_type_ref.type.second.empty() ? kEmpty : kPresent; + } + } else { AddValidationMessage( - kCborValidateFatal, - "Payload to be signed must be a CBOR array. Actual type: " + - CppborMajorTypeToString(parsed_payload_to_be_signed->type())); - return message_status_; + kCborValidateError, + "Certificate type must be Tstr. Actual type: " + + CppborMajorTypeToString(csr_payload_array->get(1)->type())); } + + const cppbor::Map* device_info_map = csr_payload_array->get(2)->asMap(); + if (device_info_map != nullptr) { + if (device_info_map->size() == 0) { + csr_payload->device_info.first = kEmpty; + } else { + // Dummy device info as a placeholder for now. + // TODO: parse the device info. + DeviceInfo device_info; + csr_payload->device_info.first = kPresent; + csr_payload->device_info.second = std::move(device_info); + } + } else { + AddValidationMessage( + kCborValidateError, + "Device info must be a CBOR map. Actual type: " + + CppborMajorTypeToString(csr_payload_array->get(2)->type())); + } + + const cppbor::Array* keys = csr_payload_array->get(3)->asArray(); + if (!keys) { + AddValidationMessage( + kCborValidateError, + "Keys must be a CBOR array. Actual type: " + + CppborMajorTypeToString(csr_payload_array->get(3)->type())); + } + return message_status_; +} + +// Caller ensures the pointer is not NULL. +CborMessageStatus SignedCsrPayloadValidator::ProcessDataToBeSigned( + const cppbor::Array* payload_to_be_signed_array, + DataToBeSigned* payload_to_be_signed) { if (payload_to_be_signed_array->size() != 2U) { AddValidationMessage( kCborValidateFatal, @@ -177,39 +337,27 @@ CborMessageStatus SignedCsrPayloadValidator::ValidateDataToBeSigned( std::to_string(payload_to_be_signed_array->size())); return message_status_; } - // Element 0: challenge. - std::string msg = "- Challenge: "; const cppbor::Bstr* challenge = payload_to_be_signed_array->get(0)->asBstr(); - if (!challenge) { - AddValidationMessage(kCborValidateFatal, - "Challenge must be Bstr. Actual type: " + - CppborMajorTypeToString( - payload_to_be_signed_array->get(0)->type())); - return message_status_; + if (challenge != nullptr) { + if (!challenge->value().empty()) { + payload_to_be_signed->challenge.first = kPresent; + payload_to_be_signed->challenge.second = challenge->value(); + } else { + payload_to_be_signed->challenge.first = kEmpty; + } } - if (challenge->value().size() > 64) { - AddValidationMessage( - kCborValidateError, - "Challenge size must be between 0 and 64 bytes inclusive. " - "However, challenge is " + - std::to_string(challenge->value().size()) + " bytes long."); - } - msg += wvutil::b2a_hex(challenge->value()); - AddMessages(msg_ss_, {std::move(msg)}, 1); - // Element 1: CsrPayload. - AddMessages(msg_ss_, {"- CsrPayload:"}, 1); const cppbor::Bstr* csr_payload = payload_to_be_signed_array->get(1)->asBstr(); if (!csr_payload) { AddValidationMessage(kCborValidateFatal, "CSR payload is missing."); return message_status_; } - parse_result = cppbor::parse(csr_payload); + auto parse_result = cppbor::parse(csr_payload); std::unique_ptr parsed_csr_payload = std::move(std::get<0>(parse_result)); - error_message = std::move(std::get<2>(parse_result)); + auto error_message = std::move(std::get<2>(parse_result)); if (!parsed_csr_payload) { AddValidationMessage(kCborValidateFatal, "Unable to parse CSR payload: " + error_message); @@ -222,63 +370,15 @@ CborMessageStatus SignedCsrPayloadValidator::ValidateDataToBeSigned( CppborMajorTypeToString(parsed_csr_payload->type())); return message_status_; } - if (parsed_csr_payload->asArray()->size() != 4U) { - AddValidationMessage( - kCborValidateFatal, - "CSR payload must contain 4 elements. Actual size: " + - std::to_string(parsed_csr_payload->asArray()->size())); - return message_status_; + if (parsed_csr_payload->asArray()->size() == 0) { + payload_to_be_signed->csr_payload.first = kEmpty; + } else { + payload_to_be_signed->csr_payload.first = kPresent; + CborMessageStatus status = + ProcessCsrPayload(parsed_csr_payload->asArray(), + &payload_to_be_signed->csr_payload.second); + ApplyStatus(message_status_, status); } - // |parsed_csr_payload| is an array of 4 elements. - const cppbor::Uint* version = parsed_csr_payload->asArray()->get(0)->asUint(); - if (!version) { - AddValidationMessage(kCborValidateFatal, - "CSR payload version must be an unsigned integer."); - return message_status_; - } - AddMessages(msg_ss_, {"- version: " + std::to_string(version->value())}, 2); - if (version->value() != 3U) { - AddValidationMessage(kCborValidateError, - "CSR payload version must be must be equal to 3."); - } - - const cppbor::Tstr* certificate_type = - parsed_csr_payload->asArray()->get(1)->asTstr(); - if (!certificate_type) { - // Certificate type is allowed to be extended by vendor, i.e. we can't - // enforce its value. - AddValidationMessage( - kCborValidateFatal, - "Certificate type must be Tstr. Actual type: " + - CppborMajorTypeToString( - parsed_csr_payload->asArray()->get(1)->type())); - return message_status_; - } - AddMessages(msg_ss_, {"- certificate_type: " + certificate_type->value()}, 2); - - const cppbor::Map* device_info = - parsed_csr_payload->asArray()->get(2)->asMap(); - if (!device_info) { - AddValidationMessage( - kCborValidateFatal, - "Device info must be a CBOR map. Actual type: " + - CppborMajorTypeToString( - parsed_csr_payload->asArray()->get(2)->type())); - return message_status_; - } - AddMessages(msg_ss_, {"- device_info: " + cppbor::prettyPrint(device_info)}, - 2); - - const cppbor::Array* keys = parsed_csr_payload->asArray()->get(3)->asArray(); - if (!keys) { - AddValidationMessage( - kCborValidateFatal, - "Keys must be a CBOR array. Actual type: " + - CppborMajorTypeToString( - parsed_csr_payload->asArray()->get(3)->type())); - return message_status_; - } - AddMessages(msg_ss_, {"- keys_to_sign: " + cppbor::prettyPrint(keys)}, 2); return message_status_; } @@ -288,16 +388,15 @@ CborMessageStatus SignedCsrPayloadValidator::Validate() { std::get<0>(parse_result()).get(); if (!parsed_signed_csr_payload) { AddValidationMessage(kCborValidateFatal, "Signed CSR payload is empty."); - return message_status_; + return kCborValidateFatal; } - // Verify that |parsed_signed_csr_payload| is a valid array. if (!parsed_signed_csr_payload->asArray()) { AddValidationMessage( kCborValidateFatal, "Signed CSR payload must be a CBOR array. Actual type: " + CppborMajorTypeToString(parsed_signed_csr_payload->type())); - return message_status_; + return kCborValidateFatal; } const cppbor::Array* signed_csr_payload_array = parsed_signed_csr_payload->asArray(); @@ -305,43 +404,78 @@ CborMessageStatus SignedCsrPayloadValidator::Validate() { AddValidationMessage(kCborValidateFatal, "Invalid CoseSign1 size. Actual: " + std::to_string(signed_csr_payload_array->size())); - return message_status_; + return kCborValidateFatal; } - // Writes YAML-formatted output to |msg_ss_| during validation. - msg_ss_.str(std::string()); - msg_ss_ << "---" - << "\n"; - + // Parse each element of the array. + CborMessageStatus status = kCborValidateOk; + SignedCsrPayload signed_csr_payload; // Element 0: protected params. - msg_ss_ << "- Protected params:\n"; const cppbor::Bstr* protected_params = signed_csr_payload_array->get(0)->asBstr(); - CborMessageStatus status = ValidateProtectedParams(protected_params); - if (status == kCborValidateFatal) return kCborValidateFatal; + if (!protected_params) { + AddValidationMessage( + kCborValidateFatal, + "Signed csr payload protected data type must be Bstr. Actual type: " + + CppborMajorTypeToString(signed_csr_payload_array->get(0)->type())); + return kCborValidateFatal; + } + // Parse and verify the protected data. + const std::vector& encoded_protected_data = + signed_csr_payload_array->get(0)->asBstr()->value(); + if (encoded_protected_data.empty()) { + AddValidationMessage(kCborValidateFatal, + "Empty signed csr payload protected data."); + return kCborValidateFatal; + } + auto parse_protected_data_result = cppbor::parse(encoded_protected_data); + std::unique_ptr protected_data_item = + std::move(std::get<0>(parse_protected_data_result)); + std::string error_message = + std::move(std::get<2>(parse_protected_data_result)); + if (protected_data_item == nullptr || !error_message.empty()) { + AddValidationMessage( + kCborValidateFatal, + "Unable to parse signed csr payload protected: " + error_message); + return kCborValidateFatal; + } + if (protected_data_item->type() != cppbor::MAP) { + AddValidationMessage( + kCborValidateFatal, + "Unexpected signed csr payload protected type: " + + CppborMajorTypeToString(protected_data_item->type())); + return kCborValidateFatal; + } + const cppbor::Map* protected_map = protected_data_item->asMap(); + if (protected_map == nullptr) { + AddValidationMessage(kCborValidateFatal, + "Signed csr payload protected map is null."); + return kCborValidateFatal; + } + if (protected_map->size() == 0) { + signed_csr_payload.protected_data.first = kEmpty; + } else { + signed_csr_payload.protected_data.first = kPresent; + status = ProcessSignedDataProtected( + protected_map, &signed_csr_payload.protected_data.second); + if (status == kCborValidateFatal) return status; + } // Element 1: unprotected params map. - std::string msg = "- Unprotected params: "; const cppbor::Map* unprotected_params = signed_csr_payload_array->get(1)->asMap(); if (!unprotected_params) { AddValidationMessage( - kCborValidateFatal, + kCborValidateError, "UnprotectedParams must be a CBOR map. Actual type: " + CppborMajorTypeToString(signed_csr_payload_array->get(1)->type())); - return message_status_; - } - if (unprotected_params->size() == 0) { - msg += ""; } else { - msg += "non-null map"; - AddValidationMessage(kCborValidateWarning, - "UnprotectedParams is expected to be empty."); + // Unprotected data is supposed to be empty and can be ignored. + signed_csr_payload.unprotected.first = kEmpty; + signed_csr_payload.unprotected.second = "{}"; } - msg_ss_ << msg << "\n"; // Element 2: payload (DataToBeSigned). - msg_ss_ << "- Payload:\n"; const cppbor::Bstr* payload_to_be_signed = signed_csr_payload_array->get(2)->asBstr(); if (!payload_to_be_signed) { @@ -351,29 +485,58 @@ CborMessageStatus SignedCsrPayloadValidator::Validate() { CppborMajorTypeToString(signed_csr_payload_array->get(2)->type())); return message_status_; } - status = ValidateDataToBeSigned(payload_to_be_signed); - if (status == kCborValidateFatal) return kCborValidateFatal; + auto parse_payload_to_be_signed_result = cppbor::parse(payload_to_be_signed); + std::unique_ptr parsed_payload_to_be_signed_item = + std::move(std::get<0>(parse_payload_to_be_signed_result)); + error_message = std::move(std::get<2>(parse_payload_to_be_signed_result)); + if (!parsed_payload_to_be_signed_item || !error_message.empty()) { + AddValidationMessage( + kCborValidateFatal, + "Unable to parse the payload to be signed: " + error_message); + return kCborValidateFatal; + } + // Verify that |parsed_payload_to_be_signed_item| is a valid array. + const cppbor::Array* payload_to_be_signed_array = + parsed_payload_to_be_signed_item->asArray(); + if (!payload_to_be_signed_array) { + AddValidationMessage( + kCborValidateFatal, + "Payload to be signed must be a CBOR array. Actual type: " + + CppborMajorTypeToString(parsed_payload_to_be_signed_item->type())); + return kCborValidateFatal; + } + if (payload_to_be_signed_array->size() == 0) { + signed_csr_payload.payload.first = kEmpty; + } else { + signed_csr_payload.payload.first = kPresent; + status = ProcessDataToBeSigned(payload_to_be_signed_array, + &signed_csr_payload.payload.second); + if (status == kCborValidateFatal) return kCborValidateFatal; + } // Element 3: signature. - std::string sig_msg = "- Signature: "; const cppbor::Bstr* signature = signed_csr_payload_array->get(3)->asBstr(); if (!signature) { AddValidationMessage( kCborValidateFatal, "CoseSign1 signature must be Bstr. Actual type: " + CppborMajorTypeToString(signed_csr_payload_array->get(3)->type())); - return message_status_; + return kCborValidateFatal; } - // Skip CoseSign1 signature verification since the validator doesn't have - // verifying keys. - if (signature->value().empty()) { - sig_msg += ""; - AddValidationMessage(kCborValidateError, "CoseSign1 signature is missing."); - } else { - sig_msg += wvutil::b2a_hex(signature->value()); + signed_csr_payload.signature.second = signature->value(); + signed_csr_payload.signature.first = + signed_csr_payload.signature.second.empty() ? kEmpty : kPresent; + + // Print the parsed signed csr payload. + msg_ss_ << signed_csr_payload.ToString() << "\n"; + + // Start validation. + std::vector> msgs; + status = signed_csr_payload.Validate(msgs); + ApplyStatus(message_status_, status); + for (const auto& msg : msgs) { + AddValidationMessage(msg.first, msg.second); } - msg_ss_ << sig_msg << "\n"; - msg_ss_ << "...\n"; if (message_status_ == kCborParseOk) message_status_ = kCborValidateOk; return message_status_; } @@ -387,7 +550,7 @@ std::string SignedCsrPayloadValidator::GetFormattedMessage() const { if (parsed_item == nullptr) { return ""; } - return msg_ss_.str(); + return FormatString(msg_ss_.str()); } } // namespace util } // namespace wvoec diff --git a/oemcrypto/util/test/bcc_validator_unittest.cpp b/oemcrypto/util/test/bcc_validator_unittest.cpp index ca92e6d6..d05e1d6f 100644 --- a/oemcrypto/util/test/bcc_validator_unittest.cpp +++ b/oemcrypto/util/test/bcc_validator_unittest.cpp @@ -8,6 +8,7 @@ #include #include "bcc_validator.h" +#include "log.h" using ::testing::AllOf; using ::testing::Ge; @@ -17,60 +18,227 @@ using ::testing::Le; namespace wvoec { namespace util { namespace { -// Self-signed phase 1 BCC generated by OPK reference implementation. +// Phase 2 DICE BCC generated by OPK reference implementation. const std::vector kBcc = { + 0x83, 0xa5, 0x01, 0x01, 0x03, 0x27, 0x04, 0x81, 0x02, 0x20, 0x06, 0x21, + 0x58, 0x20, 0x02, 0xab, 0xbe, 0x80, 0x02, 0x41, 0xf1, 0x0b, 0x40, 0xff, + 0xd5, 0xf0, 0xd0, 0x26, 0x65, 0x70, 0xdc, 0x5a, 0x97, 0xa6, 0x80, 0xee, + 0x15, 0x95, 0xec, 0x26, 0x36, 0x70, 0x77, 0xe5, 0xfb, 0x10, 0x84, 0x43, + 0xa1, 0x01, 0x27, 0xa0, 0x58, 0xf8, 0xaa, 0x01, 0x68, 0x69, 0x73, 0x73, + 0x75, 0x65, 0x72, 0x20, 0x30, 0x02, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, + 0x20, 0x31, 0x3a, 0x00, 0x47, 0x44, 0x59, 0x6a, 0x61, 0x6e, 0x64, 0x72, + 0x6f, 0x69, 0x64, 0x2e, 0x31, 0x35, 0x3a, 0x00, 0x47, 0x44, 0x57, 0x58, + 0x2d, 0xa5, 0x01, 0x01, 0x03, 0x27, 0x04, 0x81, 0x02, 0x20, 0x06, 0x21, + 0x58, 0x20, 0x3c, 0xfe, 0x6e, 0x28, 0x8b, 0x53, 0x31, 0x64, 0xb2, 0x16, + 0x26, 0x4c, 0x5f, 0x8e, 0x76, 0xfd, 0xb9, 0x25, 0x8e, 0x8c, 0xf5, 0x80, + 0xfe, 0xa5, 0x74, 0xbb, 0x0c, 0x5a, 0xef, 0xe3, 0x21, 0xce, 0x3a, 0x00, + 0x47, 0x44, 0x58, 0x41, 0x20, 0x3a, 0x00, 0x47, 0x44, 0x50, 0x58, 0x20, + 0xb3, 0x16, 0x96, 0xda, 0x0e, 0x6c, 0x46, 0x26, 0x1e, 0x52, 0x9b, 0x43, + 0x9b, 0x68, 0xd0, 0x32, 0x5e, 0x31, 0x39, 0xf9, 0x69, 0x28, 0xec, 0x03, + 0xeb, 0x06, 0x4c, 0xf3, 0x60, 0xf1, 0xd3, 0x0e, 0x3a, 0x00, 0x47, 0x44, + 0x52, 0x58, 0x20, 0x38, 0x0c, 0x43, 0xc2, 0xc5, 0x36, 0x5a, 0x91, 0xb4, + 0x2d, 0x85, 0x22, 0xb4, 0xb9, 0x6e, 0x43, 0xd2, 0xd0, 0x3e, 0x2f, 0xc4, + 0x1f, 0xb7, 0xb3, 0x4c, 0x6b, 0xfd, 0x90, 0xc7, 0x80, 0x42, 0x84, 0x3a, + 0x00, 0x47, 0x44, 0x53, 0x57, 0xa2, 0x3a, 0x00, 0x01, 0x11, 0x71, 0x68, + 0x57, 0x69, 0x64, 0x65, 0x76, 0x69, 0x6e, 0x65, 0x3a, 0x00, 0x01, 0x11, + 0x72, 0x62, 0x31, 0x39, 0x3a, 0x00, 0x47, 0x44, 0x54, 0x58, 0x20, 0x2d, + 0xd1, 0xac, 0x94, 0xbe, 0x4b, 0x89, 0x98, 0xba, 0xec, 0xb6, 0x01, 0xaf, + 0xaf, 0xda, 0x01, 0x8f, 0xeb, 0x03, 0xd2, 0x8f, 0x42, 0x12, 0x55, 0xe4, + 0x4b, 0xe2, 0xdf, 0x47, 0x68, 0x27, 0x6a, 0x3a, 0x00, 0x47, 0x44, 0x56, + 0x41, 0x01, 0x58, 0x40, 0xd7, 0xe8, 0x9b, 0x9b, 0x8f, 0x3f, 0xe7, 0x66, + 0x24, 0x87, 0x48, 0xf4, 0xbd, 0x47, 0x3b, 0x85, 0xfb, 0x2c, 0x0e, 0xed, + 0x7d, 0xf4, 0xa6, 0xb5, 0x30, 0x77, 0xea, 0x92, 0x63, 0xc0, 0x1e, 0x8b, + 0x40, 0xec, 0x71, 0x66, 0xf8, 0xe2, 0x92, 0x07, 0x67, 0x08, 0x25, 0x70, + 0x4a, 0x06, 0x41, 0x72, 0x21, 0xdf, 0xd4, 0xa7, 0x1a, 0xeb, 0xe9, 0x5f, + 0x11, 0x30, 0x5b, 0xaa, 0x09, 0xe3, 0x24, 0x00, 0x84, 0x43, 0xa1, 0x01, + 0x27, 0xa0, 0x58, 0xf7, 0xaa, 0x01, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, + 0x20, 0x31, 0x02, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x20, 0x32, 0x3a, + 0x00, 0x47, 0x44, 0x59, 0x6a, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, + 0x2e, 0x31, 0x35, 0x3a, 0x00, 0x47, 0x44, 0x57, 0x58, 0x2d, 0xa5, 0x01, + 0x01, 0x03, 0x27, 0x04, 0x81, 0x02, 0x20, 0x06, 0x21, 0x58, 0x20, 0xe6, + 0xc1, 0x67, 0xa8, 0x6d, 0x92, 0x16, 0xb8, 0x9c, 0xd1, 0xc4, 0xd4, 0xd3, + 0xf3, 0xc1, 0x22, 0x95, 0x80, 0x49, 0xd0, 0xb3, 0x50, 0x9f, 0x1d, 0xec, + 0x6f, 0xa8, 0x6c, 0xd2, 0xbd, 0x1a, 0x68, 0x3a, 0x00, 0x47, 0x44, 0x58, + 0x41, 0x20, 0x3a, 0x00, 0x47, 0x44, 0x50, 0x58, 0x20, 0x8d, 0x76, 0x93, + 0x9d, 0x2f, 0x0e, 0x5f, 0xbd, 0x36, 0x58, 0x51, 0x53, 0xc4, 0xa0, 0xaf, + 0xdb, 0x91, 0x41, 0x6a, 0x62, 0xf0, 0x03, 0x6a, 0x77, 0x8b, 0x2e, 0x5f, + 0xa7, 0x29, 0x94, 0x69, 0x9d, 0x3a, 0x00, 0x47, 0x44, 0x52, 0x58, 0x20, + 0xc0, 0x0b, 0xa4, 0x78, 0xac, 0xa9, 0xb6, 0x96, 0x64, 0xaa, 0x55, 0xf6, + 0x52, 0xe2, 0xa6, 0x7b, 0xd7, 0x6f, 0xd5, 0xe5, 0xc1, 0xbd, 0xef, 0x97, + 0xf3, 0x0e, 0xea, 0xbc, 0x4b, 0x9e, 0xb7, 0xec, 0x3a, 0x00, 0x47, 0x44, + 0x53, 0x57, 0xa2, 0x3a, 0x00, 0x01, 0x11, 0x71, 0x68, 0x57, 0x69, 0x64, + 0x65, 0x76, 0x69, 0x6e, 0x65, 0x3a, 0x00, 0x01, 0x11, 0x72, 0x62, 0x31, + 0x39, 0x3a, 0x00, 0x47, 0x44, 0x54, 0x58, 0x20, 0x35, 0x65, 0xd2, 0xaf, + 0x10, 0xf0, 0x67, 0x3e, 0x58, 0xf9, 0xfc, 0xc0, 0x6d, 0x46, 0xd2, 0x61, + 0x34, 0xa6, 0xf1, 0xd5, 0x0d, 0x7b, 0x6e, 0x4f, 0xd7, 0x13, 0xcb, 0x1d, + 0xd6, 0xef, 0x2a, 0x23, 0x3a, 0x00, 0x47, 0x44, 0x56, 0x41, 0x01, 0x58, + 0x40, 0xa6, 0x67, 0x96, 0xf6, 0x31, 0x68, 0x45, 0x49, 0x7c, 0x38, 0x3f, + 0xde, 0x91, 0x02, 0xe3, 0x2a, 0x91, 0xc8, 0x0b, 0x3c, 0xdf, 0x2b, 0x18, + 0xcf, 0x9f, 0x06, 0xc0, 0xbe, 0x58, 0xbe, 0x12, 0x2c, 0xaa, 0x32, 0xa1, + 0x34, 0x0d, 0xf2, 0x8b, 0xa5, 0x87, 0x17, 0x66, 0x61, 0xd9, 0xdc, 0x08, + 0x52, 0x86, 0x51, 0x6f, 0x63, 0xfc, 0xaf, 0x7e, 0xc7, 0xeb, 0xa6, 0x73, + 0x19, 0xb1, 0x1a, 0xc9, 0x06}; +// Phase 1 self-signed BCC generated by OPK reference implementation. +const std::vector kDegeneratedBcc = { 0x82, 0xa5, 0x01, 0x01, 0x03, 0x27, 0x04, 0x81, 0x02, 0x20, 0x06, 0x21, 0x58, 0x20, 0x02, 0xab, 0xbe, 0x80, 0x02, 0x41, 0xf1, 0x0b, 0x40, 0xff, 0xd5, 0xf0, 0xd0, 0x26, 0x65, 0x70, 0xdc, 0x5a, 0x97, 0xa6, 0x80, 0xee, 0x15, 0x95, 0xec, 0x26, 0x36, 0x70, 0x77, 0xe5, 0xfb, 0x10, 0x84, 0x43, - 0xa1, 0x01, 0x27, 0xa0, 0x58, 0x40, 0xa4, 0x01, 0x60, 0x02, 0x60, 0x3a, - 0x00, 0x47, 0x44, 0x57, 0x58, 0x2d, 0xa5, 0x01, 0x01, 0x03, 0x27, 0x04, - 0x81, 0x02, 0x20, 0x06, 0x21, 0x58, 0x20, 0x02, 0xab, 0xbe, 0x80, 0x02, - 0x41, 0xf1, 0x0b, 0x40, 0xff, 0xd5, 0xf0, 0xd0, 0x26, 0x65, 0x70, 0xdc, - 0x5a, 0x97, 0xa6, 0x80, 0xee, 0x15, 0x95, 0xec, 0x26, 0x36, 0x70, 0x77, - 0xe5, 0xfb, 0x10, 0x3a, 0x00, 0x47, 0x44, 0x58, 0x41, 0x20, 0x58, 0x40, - 0x73, 0x02, 0x36, 0xaa, 0x6d, 0x52, 0x50, 0x67, 0x43, 0xc4, 0x0b, 0xf8, - 0x3f, 0x35, 0x2a, 0xd8, 0x44, 0x09, 0xf4, 0x1d, 0xca, 0x91, 0x12, 0x27, - 0x01, 0xdf, 0x73, 0xb7, 0x9b, 0x31, 0x28, 0x8e, 0xae, 0x9b, 0xc6, 0x7a, - 0xdc, 0x07, 0xab, 0x69, 0xd2, 0x85, 0x9a, 0x15, 0x8b, 0xe3, 0x5b, 0xf2, - 0x94, 0x95, 0xee, 0x49, 0x74, 0xc5, 0x85, 0x62, 0x3d, 0x46, 0x4c, 0xeb, - 0x11, 0x89, 0x68, 0x02}; + 0xa1, 0x01, 0x27, 0xa0, 0x58, 0xf8, 0xaa, 0x01, 0x68, 0x69, 0x73, 0x73, + 0x75, 0x65, 0x72, 0x20, 0x30, 0x02, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, + 0x20, 0x31, 0x3a, 0x00, 0x47, 0x44, 0x59, 0x6a, 0x61, 0x6e, 0x64, 0x72, + 0x6f, 0x69, 0x64, 0x2e, 0x31, 0x35, 0x3a, 0x00, 0x47, 0x44, 0x57, 0x58, + 0x2d, 0xa5, 0x01, 0x01, 0x03, 0x27, 0x04, 0x81, 0x02, 0x20, 0x06, 0x21, + 0x58, 0x20, 0x02, 0xab, 0xbe, 0x80, 0x02, 0x41, 0xf1, 0x0b, 0x40, 0xff, + 0xd5, 0xf0, 0xd0, 0x26, 0x65, 0x70, 0xdc, 0x5a, 0x97, 0xa6, 0x80, 0xee, + 0x15, 0x95, 0xec, 0x26, 0x36, 0x70, 0x77, 0xe5, 0xfb, 0x10, 0x3a, 0x00, + 0x47, 0x44, 0x58, 0x41, 0x20, 0x3a, 0x00, 0x47, 0x44, 0x50, 0x58, 0x20, + 0xb3, 0x16, 0x96, 0xda, 0x0e, 0x6c, 0x46, 0x26, 0x1e, 0x52, 0x9b, 0x43, + 0x9b, 0x68, 0xd0, 0x32, 0x5e, 0x31, 0x39, 0xf9, 0x69, 0x28, 0xec, 0x03, + 0xeb, 0x06, 0x4c, 0xf3, 0x60, 0xf1, 0xd3, 0x0e, 0x3a, 0x00, 0x47, 0x44, + 0x52, 0x58, 0x20, 0x38, 0x0c, 0x43, 0xc2, 0xc5, 0x36, 0x5a, 0x91, 0xb4, + 0x2d, 0x85, 0x22, 0xb4, 0xb9, 0x6e, 0x43, 0xd2, 0xd0, 0x3e, 0x2f, 0xc4, + 0x1f, 0xb7, 0xb3, 0x4c, 0x6b, 0xfd, 0x90, 0xc7, 0x80, 0x42, 0x84, 0x3a, + 0x00, 0x47, 0x44, 0x53, 0x57, 0xa2, 0x3a, 0x00, 0x01, 0x11, 0x71, 0x68, + 0x57, 0x69, 0x64, 0x65, 0x76, 0x69, 0x6e, 0x65, 0x3a, 0x00, 0x01, 0x11, + 0x72, 0x62, 0x31, 0x39, 0x3a, 0x00, 0x47, 0x44, 0x54, 0x58, 0x20, 0x2d, + 0xd1, 0xac, 0x94, 0xbe, 0x4b, 0x89, 0x98, 0xba, 0xec, 0xb6, 0x01, 0xaf, + 0xaf, 0xda, 0x01, 0x8f, 0xeb, 0x03, 0xd2, 0x8f, 0x42, 0x12, 0x55, 0xe4, + 0x4b, 0xe2, 0xdf, 0x47, 0x68, 0x27, 0x6a, 0x3a, 0x00, 0x47, 0x44, 0x56, + 0x41, 0x01, 0x58, 0x40, 0x3a, 0xda, 0xff, 0x7f, 0x11, 0xc4, 0xd8, 0x62, + 0x15, 0x06, 0x03, 0x1a, 0x9b, 0x4d, 0x23, 0xf1, 0x97, 0x33, 0x94, 0x67, + 0xfa, 0xef, 0x29, 0xe4, 0x18, 0x34, 0x38, 0xad, 0x1d, 0x0d, 0x91, 0x17, + 0x3a, 0xe3, 0x6b, 0xd0, 0x50, 0xc9, 0x86, 0x8c, 0x9b, 0x62, 0x6b, 0xac, + 0x8e, 0xa4, 0xa7, 0x4b, 0x94, 0x6b, 0xc7, 0xce, 0xf4, 0xe4, 0xe5, 0x12, + 0x1d, 0xf6, 0x19, 0xf9, 0x4d, 0x5b, 0x9a, 0x00}; +// Key bytes all zero const std::vector kBccWrongEntryKey = { - 0x82, 0xa5, 0x01, 0x01, 0x03, 0x27, 0x04, 0x81, 0x02, 0x20, 0x06, 0x21, - 0x58, 0x20, 0x02, 0xab, 0xbe, 0x80, 0x02, 0x41, 0xf1, 0x0b, 0x40, 0xff, - 0xd5, 0xf0, 0xd0, 0x26, 0x65, 0x70, 0xdc, 0x5a, 0x97, 0xa6, 0x80, 0xee, - 0x15, 0x95, 0xec, 0x26, 0x36, 0x70, 0x77, 0xe5, 0xfb, 0x10, 0x84, 0x43, - 0xa1, 0x01, 0x27, 0xa0, 0x58, 0x40, 0xa4, 0x01, 0x60, 0x02, 0x60, 0x3a, - 0x00, 0x47, 0x44, 0x57, 0x58, 0x2d, 0xa5, 0x01, 0x01, 0x03, 0x27, 0x04, - 0x81, 0x02, 0x20, 0x06, 0x21, 0x58, 0x20, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, - 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, - 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, - 0xaa, 0xaa, 0xaa, 0x3a, 0x00, 0x47, 0x44, 0x58, 0x41, 0x20, 0x58, 0x40, - 0x89, 0x1d, 0xff, 0xb3, 0x3b, 0xe2, 0xdc, 0xc6, 0xbc, 0xbd, 0xc7, 0xcd, - 0x3f, 0x9c, 0x43, 0xf6, 0xdd, 0xea, 0x58, 0x53, 0x45, 0x8f, 0x87, 0x17, - 0x0a, 0xe4, 0x06, 0xf2, 0xbe, 0x14, 0x69, 0x13, 0x3d, 0x1d, 0xd0, 0x52, - 0x8f, 0x56, 0x4b, 0x0f, 0xad, 0x2e, 0xf0, 0xbf, 0xbb, 0xd1, 0x35, 0x9c, - 0x5a, 0xe8, 0x67, 0xbe, 0xec, 0xff, 0x9d, 0xfe, 0xac, 0x8d, 0x47, 0x4e, - 0x6d, 0xd1, 0xd3, 0x02}; + 0x83, 0xa5, 0x01, 0x01, 0x03, 0x27, 0x04, 0x81, 0x02, 0x20, 0x06, 0x21, + 0x58, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x84, 0x43, + 0xa1, 0x01, 0x27, 0xa0, 0x58, 0xf8, 0xaa, 0x01, 0x68, 0x69, 0x73, 0x73, + 0x75, 0x65, 0x72, 0x20, 0x30, 0x02, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, + 0x20, 0x31, 0x3a, 0x00, 0x47, 0x44, 0x59, 0x6a, 0x61, 0x6e, 0x64, 0x72, + 0x6f, 0x69, 0x64, 0x2e, 0x31, 0x35, 0x3a, 0x00, 0x47, 0x44, 0x57, 0x58, + 0x2d, 0xa5, 0x01, 0x01, 0x03, 0x27, 0x04, 0x81, 0x02, 0x20, 0x06, 0x21, + 0x58, 0x20, 0x3c, 0xfe, 0x6e, 0x28, 0x8b, 0x53, 0x31, 0x64, 0xb2, 0x16, + 0x26, 0x4c, 0x5f, 0x8e, 0x76, 0xfd, 0xb9, 0x25, 0x8e, 0x8c, 0xf5, 0x80, + 0xfe, 0xa5, 0x74, 0xbb, 0x0c, 0x5a, 0xef, 0xe3, 0x21, 0xce, 0x3a, 0x00, + 0x47, 0x44, 0x58, 0x41, 0x20, 0x3a, 0x00, 0x47, 0x44, 0x50, 0x58, 0x20, + 0xb3, 0x16, 0x96, 0xda, 0x0e, 0x6c, 0x46, 0x26, 0x1e, 0x52, 0x9b, 0x43, + 0x9b, 0x68, 0xd0, 0x32, 0x5e, 0x31, 0x39, 0xf9, 0x69, 0x28, 0xec, 0x03, + 0xeb, 0x06, 0x4c, 0xf3, 0x60, 0xf1, 0xd3, 0x0e, 0x3a, 0x00, 0x47, 0x44, + 0x52, 0x58, 0x20, 0x38, 0x0c, 0x43, 0xc2, 0xc5, 0x36, 0x5a, 0x91, 0xb4, + 0x2d, 0x85, 0x22, 0xb4, 0xb9, 0x6e, 0x43, 0xd2, 0xd0, 0x3e, 0x2f, 0xc4, + 0x1f, 0xb7, 0xb3, 0x4c, 0x6b, 0xfd, 0x90, 0xc7, 0x80, 0x42, 0x84, 0x3a, + 0x00, 0x47, 0x44, 0x53, 0x57, 0xa2, 0x3a, 0x00, 0x01, 0x11, 0x71, 0x68, + 0x57, 0x69, 0x64, 0x65, 0x76, 0x69, 0x6e, 0x65, 0x3a, 0x00, 0x01, 0x11, + 0x72, 0x62, 0x31, 0x39, 0x3a, 0x00, 0x47, 0x44, 0x54, 0x58, 0x20, 0x2d, + 0xd1, 0xac, 0x94, 0xbe, 0x4b, 0x89, 0x98, 0xba, 0xec, 0xb6, 0x01, 0xaf, + 0xaf, 0xda, 0x01, 0x8f, 0xeb, 0x03, 0xd2, 0x8f, 0x42, 0x12, 0x55, 0xe4, + 0x4b, 0xe2, 0xdf, 0x47, 0x68, 0x27, 0x6a, 0x3a, 0x00, 0x47, 0x44, 0x56, + 0x41, 0x01, 0x58, 0x40, 0xd7, 0xe8, 0x9b, 0x9b, 0x8f, 0x3f, 0xe7, 0x66, + 0x24, 0x87, 0x48, 0xf4, 0xbd, 0x47, 0x3b, 0x85, 0xfb, 0x2c, 0x0e, 0xed, + 0x7d, 0xf4, 0xa6, 0xb5, 0x30, 0x77, 0xea, 0x92, 0x63, 0xc0, 0x1e, 0x8b, + 0x40, 0xec, 0x71, 0x66, 0xf8, 0xe2, 0x92, 0x07, 0x67, 0x08, 0x25, 0x70, + 0x4a, 0x06, 0x41, 0x72, 0x21, 0xdf, 0xd4, 0xa7, 0x1a, 0xeb, 0xe9, 0x5f, + 0x11, 0x30, 0x5b, 0xaa, 0x09, 0xe3, 0x24, 0x00, 0x84, 0x43, 0xa1, 0x01, + 0x27, 0xa0, 0x58, 0xf7, 0xaa, 0x01, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, + 0x20, 0x31, 0x02, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x20, 0x32, 0x3a, + 0x00, 0x47, 0x44, 0x59, 0x6a, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, + 0x2e, 0x31, 0x35, 0x3a, 0x00, 0x47, 0x44, 0x57, 0x58, 0x2d, 0xa5, 0x01, + 0x01, 0x03, 0x27, 0x04, 0x81, 0x02, 0x20, 0x06, 0x21, 0x58, 0x20, 0xe6, + 0xc1, 0x67, 0xa8, 0x6d, 0x92, 0x16, 0xb8, 0x9c, 0xd1, 0xc4, 0xd4, 0xd3, + 0xf3, 0xc1, 0x22, 0x95, 0x80, 0x49, 0xd0, 0xb3, 0x50, 0x9f, 0x1d, 0xec, + 0x6f, 0xa8, 0x6c, 0xd2, 0xbd, 0x1a, 0x68, 0x3a, 0x00, 0x47, 0x44, 0x58, + 0x41, 0x20, 0x3a, 0x00, 0x47, 0x44, 0x50, 0x58, 0x20, 0x8d, 0x76, 0x93, + 0x9d, 0x2f, 0x0e, 0x5f, 0xbd, 0x36, 0x58, 0x51, 0x53, 0xc4, 0xa0, 0xaf, + 0xdb, 0x91, 0x41, 0x6a, 0x62, 0xf0, 0x03, 0x6a, 0x77, 0x8b, 0x2e, 0x5f, + 0xa7, 0x29, 0x94, 0x69, 0x9d, 0x3a, 0x00, 0x47, 0x44, 0x52, 0x58, 0x20, + 0xc0, 0x0b, 0xa4, 0x78, 0xac, 0xa9, 0xb6, 0x96, 0x64, 0xaa, 0x55, 0xf6, + 0x52, 0xe2, 0xa6, 0x7b, 0xd7, 0x6f, 0xd5, 0xe5, 0xc1, 0xbd, 0xef, 0x97, + 0xf3, 0x0e, 0xea, 0xbc, 0x4b, 0x9e, 0xb7, 0xec, 0x3a, 0x00, 0x47, 0x44, + 0x53, 0x57, 0xa2, 0x3a, 0x00, 0x01, 0x11, 0x71, 0x68, 0x57, 0x69, 0x64, + 0x65, 0x76, 0x69, 0x6e, 0x65, 0x3a, 0x00, 0x01, 0x11, 0x72, 0x62, 0x31, + 0x39, 0x3a, 0x00, 0x47, 0x44, 0x54, 0x58, 0x20, 0x35, 0x65, 0xd2, 0xaf, + 0x10, 0xf0, 0x67, 0x3e, 0x58, 0xf9, 0xfc, 0xc0, 0x6d, 0x46, 0xd2, 0x61, + 0x34, 0xa6, 0xf1, 0xd5, 0x0d, 0x7b, 0x6e, 0x4f, 0xd7, 0x13, 0xcb, 0x1d, + 0xd6, 0xef, 0x2a, 0x23, 0x3a, 0x00, 0x47, 0x44, 0x56, 0x41, 0x01, 0x58, + 0x40, 0xa6, 0x67, 0x96, 0xf6, 0x31, 0x68, 0x45, 0x49, 0x7c, 0x38, 0x3f, + 0xde, 0x91, 0x02, 0xe3, 0x2a, 0x91, 0xc8, 0x0b, 0x3c, 0xdf, 0x2b, 0x18, + 0xcf, 0x9f, 0x06, 0xc0, 0xbe, 0x58, 0xbe, 0x12, 0x2c, 0xaa, 0x32, 0xa1, + 0x34, 0x0d, 0xf2, 0x8b, 0xa5, 0x87, 0x17, 0x66, 0x61, 0xd9, 0xdc, 0x08, + 0x52, 0x86, 0x51, 0x6f, 0x63, 0xfc, 0xaf, 0x7e, 0xc7, 0xeb, 0xa6, 0x73, + 0x19, 0xb1, 0x1a, 0xc9, 0x06}; const std::vector kBccMissingIssuer = { - 0x82, 0xa5, 0x01, 0x01, 0x03, 0x27, 0x04, 0x81, 0x02, 0x20, 0x06, 0x21, + 0x83, 0xa5, 0x01, 0x01, 0x03, 0x27, 0x04, 0x81, 0x02, 0x20, 0x06, 0x21, 0x58, 0x20, 0x02, 0xab, 0xbe, 0x80, 0x02, 0x41, 0xf1, 0x0b, 0x40, 0xff, 0xd5, 0xf0, 0xd0, 0x26, 0x65, 0x70, 0xdc, 0x5a, 0x97, 0xa6, 0x80, 0xee, 0x15, 0x95, 0xec, 0x26, 0x36, 0x70, 0x77, 0xe5, 0xfb, 0x10, 0x84, 0x43, - 0xa1, 0x01, 0x27, 0xa0, 0x58, 0x3e, 0xa3, 0x02, 0x60, 0x3a, 0x00, 0x47, - 0x44, 0x57, 0x58, 0x2d, 0xa5, 0x01, 0x01, 0x03, 0x27, 0x04, 0x81, 0x02, - 0x20, 0x06, 0x21, 0x58, 0x20, 0x02, 0xab, 0xbe, 0x80, 0x02, 0x41, 0xf1, - 0x0b, 0x40, 0xff, 0xd5, 0xf0, 0xd0, 0x26, 0x65, 0x70, 0xdc, 0x5a, 0x97, - 0xa6, 0x80, 0xee, 0x15, 0x95, 0xec, 0x26, 0x36, 0x70, 0x77, 0xe5, 0xfb, - 0x10, 0x3a, 0x00, 0x47, 0x44, 0x58, 0x41, 0x20, 0x58, 0x40, 0xf9, 0x46, - 0x36, 0xbd, 0x95, 0x75, 0xc2, 0x3d, 0xf9, 0xa2, 0xbe, 0x60, 0x8e, 0xbf, - 0x64, 0x89, 0xdf, 0xb9, 0x9c, 0x3c, 0x17, 0x36, 0x23, 0x9a, 0x68, 0x1a, - 0x34, 0x36, 0x51, 0x89, 0x59, 0xf2, 0x54, 0x62, 0xd3, 0x8f, 0xeb, 0x9b, - 0x75, 0x3e, 0xe9, 0xfc, 0xe3, 0xc2, 0x8f, 0x84, 0xb1, 0x71, 0xcd, 0x29, - 0x12, 0x65, 0xeb, 0xab, 0x28, 0x4b, 0xe2, 0x3e, 0x1b, 0xd8, 0x17, 0xdb, - 0x97, 0x0f}; + 0xa1, 0x01, 0x27, 0xa0, 0x58, 0xf8, 0xaa, 0x01, 0x68, 0x69, 0x73, 0x73, + 0x75, 0x65, 0x72, 0x20, 0x30, 0x02, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, + 0x20, 0x31, 0x3a, 0x00, 0x47, 0x44, 0x59, 0x6a, 0x61, 0x6e, 0x64, 0x72, + 0x6f, 0x69, 0x64, 0x2e, 0x31, 0x35, 0x3a, 0x00, 0x47, 0x44, 0x57, 0x58, + 0x2d, 0xa5, 0x01, 0x01, 0x03, 0x27, 0x04, 0x81, 0x02, 0x20, 0x06, 0x21, + 0x58, 0x20, 0x3c, 0xfe, 0x6e, 0x28, 0x8b, 0x53, 0x31, 0x64, 0xb2, 0x16, + 0x26, 0x4c, 0x5f, 0x8e, 0x76, 0xfd, 0xb9, 0x25, 0x8e, 0x8c, 0xf5, 0x80, + 0xfe, 0xa5, 0x74, 0xbb, 0x0c, 0x5a, 0xef, 0xe3, 0x21, 0xce, 0x3a, 0x00, + 0x47, 0x44, 0x58, 0x41, 0x20, 0x3a, 0x00, 0x47, 0x44, 0x50, 0x58, 0x20, + 0xb3, 0x16, 0x96, 0xda, 0x0e, 0x6c, 0x46, 0x26, 0x1e, 0x52, 0x9b, 0x43, + 0x9b, 0x68, 0xd0, 0x32, 0x5e, 0x31, 0x39, 0xf9, 0x69, 0x28, 0xec, 0x03, + 0xeb, 0x06, 0x4c, 0xf3, 0x60, 0xf1, 0xd3, 0x0e, 0x3a, 0x00, 0x47, 0x44, + 0x52, 0x58, 0x20, 0x38, 0x0c, 0x43, 0xc2, 0xc5, 0x36, 0x5a, 0x91, 0xb4, + 0x2d, 0x85, 0x22, 0xb4, 0xb9, 0x6e, 0x43, 0xd2, 0xd0, 0x3e, 0x2f, 0xc4, + 0x1f, 0xb7, 0xb3, 0x4c, 0x6b, 0xfd, 0x90, 0xc7, 0x80, 0x42, 0x84, 0x3a, + 0x00, 0x47, 0x44, 0x53, 0x57, 0xa2, 0x3a, 0x00, 0x01, 0x11, 0x71, 0x68, + 0x57, 0x69, 0x64, 0x65, 0x76, 0x69, 0x6e, 0x65, 0x3a, 0x00, 0x01, 0x11, + 0x72, 0x62, 0x31, 0x39, 0x3a, 0x00, 0x47, 0x44, 0x54, 0x58, 0x20, 0x2d, + 0xd1, 0xac, 0x94, 0xbe, 0x4b, 0x89, 0x98, 0xba, 0xec, 0xb6, 0x01, 0xaf, + 0xaf, 0xda, 0x01, 0x8f, 0xeb, 0x03, 0xd2, 0x8f, 0x42, 0x12, 0x55, 0xe4, + 0x4b, 0xe2, 0xdf, 0x47, 0x68, 0x27, 0x6a, 0x3a, 0x00, 0x47, 0x44, 0x56, + 0x41, 0x01, 0x58, 0x40, 0xd7, 0xe8, 0x9b, 0x9b, 0x8f, 0x3f, 0xe7, 0x66, + 0x24, 0x87, 0x48, 0xf4, 0xbd, 0x47, 0x3b, 0x85, 0xfb, 0x2c, 0x0e, 0xed, + 0x7d, 0xf4, 0xa6, 0xb5, 0x30, 0x77, 0xea, 0x92, 0x63, 0xc0, 0x1e, 0x8b, + 0x40, 0xec, 0x71, 0x66, 0xf8, 0xe2, 0x92, 0x07, 0x67, 0x08, 0x25, 0x70, + 0x4a, 0x06, 0x41, 0x72, 0x21, 0xdf, 0xd4, 0xa7, 0x1a, 0xeb, 0xe9, 0x5f, + 0x11, 0x30, 0x5b, 0xaa, 0x09, 0xe3, 0x24, 0x00, 0x84, 0x43, 0xa1, 0x01, + 0x27, 0xa0, 0x58, 0xf0, 0xaa, 0x01, 0x60, 0x02, 0x67, 0x65, 0x6e, 0x74, + 0x72, 0x79, 0x20, 0x32, 0x3a, 0x00, 0x47, 0x44, 0x59, 0x6a, 0x61, 0x6e, + 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x31, 0x35, 0x3a, 0x00, 0x47, 0x44, + 0x57, 0x58, 0x2d, 0xa5, 0x01, 0x01, 0x03, 0x27, 0x04, 0x81, 0x02, 0x20, + 0x06, 0x21, 0x58, 0x20, 0xe6, 0xc1, 0x67, 0xa8, 0x6d, 0x92, 0x16, 0xb8, + 0x9c, 0xd1, 0xc4, 0xd4, 0xd3, 0xf3, 0xc1, 0x22, 0x95, 0x80, 0x49, 0xd0, + 0xb3, 0x50, 0x9f, 0x1d, 0xec, 0x6f, 0xa8, 0x6c, 0xd2, 0xbd, 0x1a, 0x68, + 0x3a, 0x00, 0x47, 0x44, 0x58, 0x41, 0x20, 0x3a, 0x00, 0x47, 0x44, 0x50, + 0x58, 0x20, 0x8d, 0x76, 0x93, 0x9d, 0x2f, 0x0e, 0x5f, 0xbd, 0x36, 0x58, + 0x51, 0x53, 0xc4, 0xa0, 0xaf, 0xdb, 0x91, 0x41, 0x6a, 0x62, 0xf0, 0x03, + 0x6a, 0x77, 0x8b, 0x2e, 0x5f, 0xa7, 0x29, 0x94, 0x69, 0x9d, 0x3a, 0x00, + 0x47, 0x44, 0x52, 0x58, 0x20, 0xc0, 0x0b, 0xa4, 0x78, 0xac, 0xa9, 0xb6, + 0x96, 0x64, 0xaa, 0x55, 0xf6, 0x52, 0xe2, 0xa6, 0x7b, 0xd7, 0x6f, 0xd5, + 0xe5, 0xc1, 0xbd, 0xef, 0x97, 0xf3, 0x0e, 0xea, 0xbc, 0x4b, 0x9e, 0xb7, + 0xec, 0x3a, 0x00, 0x47, 0x44, 0x53, 0x57, 0xa2, 0x3a, 0x00, 0x01, 0x11, + 0x71, 0x68, 0x57, 0x69, 0x64, 0x65, 0x76, 0x69, 0x6e, 0x65, 0x3a, 0x00, + 0x01, 0x11, 0x72, 0x62, 0x31, 0x39, 0x3a, 0x00, 0x47, 0x44, 0x54, 0x58, + 0x20, 0x35, 0x65, 0xd2, 0xaf, 0x10, 0xf0, 0x67, 0x3e, 0x58, 0xf9, 0xfc, + 0xc0, 0x6d, 0x46, 0xd2, 0x61, 0x34, 0xa6, 0xf1, 0xd5, 0x0d, 0x7b, 0x6e, + 0x4f, 0xd7, 0x13, 0xcb, 0x1d, 0xd6, 0xef, 0x2a, 0x23, 0x3a, 0x00, 0x47, + 0x44, 0x56, 0x41, 0x01, 0x58, 0x40, 0x5a, 0x77, 0x3b, 0x51, 0x81, 0xad, + 0x48, 0x54, 0x39, 0x86, 0x89, 0xd9, 0x0c, 0x87, 0x0a, 0x1b, 0x8c, 0x17, + 0xd8, 0x14, 0xad, 0xfd, 0x64, 0x46, 0x71, 0x97, 0xa2, 0xd5, 0x15, 0x52, + 0x1f, 0x87, 0xee, 0x48, 0x55, 0x4c, 0xba, 0x01, 0xcb, 0x18, 0xa4, 0x26, + 0x89, 0xb7, 0x6e, 0x91, 0xc8, 0x21, 0x68, 0x1e, 0xad, 0x4a, 0x95, 0x33, + 0xe7, 0xaa, 0x68, 0xe5, 0x06, 0x4d, 0xfa, 0xcf, 0x73, 0x03}; } // namespace +static void DumpValidatorOutput(const BccValidator& validator) { + const std::string out = validator.GetFormattedMessage(); + LOGI("%s", out.c_str()); + for (auto& msg : validator.GetValidateMessages()) { + LOGE("Error code %d: %s", msg.first, msg.second.c_str()); + } +} + TEST(OEMCryptoBccValidatorTest, BccParseError) { const std::vector bcc_bad(kBcc.begin(), kBcc.end() - 1); BccValidator validator; @@ -89,8 +257,25 @@ TEST(OEMCryptoBccValidatorTest, Bcc) { result = validator.Validate(); EXPECT_THAT(result, AllOf(Ge(kCborValidateOk), Le(kCborValidateWarning))); const std::string out = validator.GetFormattedMessage(); - EXPECT_THAT(out, HasSubstr("key encoding format: DEVICE_KEY_BYTE_STRING")); - EXPECT_THAT(out, HasSubstr("key algorithm type: EDDSA")); + EXPECT_THAT(out, HasSubstr("key_type:byte_string")); + EXPECT_THAT(out, HasSubstr("signature_algorithm:EdDSA")); + if (result >= kCborValidateWarning) { + DumpValidatorOutput(validator); + } +} + +TEST(OEMCryptoBccValidatorTest, DegeneratedBcc) { + BccValidator validator; + CborMessageStatus result = validator.Parse(kDegeneratedBcc); + EXPECT_EQ(kCborParseOk, result); + result = validator.Validate(); + EXPECT_THAT(result, AllOf(Ge(kCborValidateOk), Le(kCborValidateWarning))); + const std::string out = validator.GetFormattedMessage(); + EXPECT_THAT(out, HasSubstr("key_type:byte_string")); + EXPECT_THAT(out, HasSubstr("signature_algorithm:EdDSA")); + if (result >= kCborValidateWarning) { + DumpValidatorOutput(validator); + } } TEST(OEMCryptoBccValidatorTest, BccWrongEntryKey) { @@ -101,12 +286,17 @@ TEST(OEMCryptoBccValidatorTest, BccWrongEntryKey) { EXPECT_EQ(result, kCborValidateError); // Non-fatal validation error should be able to return formatted output. const std::string out = validator.GetFormattedMessage(); - EXPECT_THAT(out, HasSubstr("key encoding format: DEVICE_KEY_BYTE_STRING")); - EXPECT_THAT(out, HasSubstr("key algorithm type: EDDSA")); + EXPECT_THAT(out, HasSubstr("key_type:byte_string")); + EXPECT_THAT(out, HasSubstr("key_bytes:000000")); const std::vector> msgs = validator.GetValidateMessages(); - EXPECT_EQ(1u, msgs.size()); + // Expect more than 1 validation errors caused by wrong entry key including + // invalid key, signature error, etc + EXPECT_GE(msgs.size(), 1u); EXPECT_EQ(kCborValidateError, msgs[0].first); + if (result >= kCborValidateWarning) { + DumpValidatorOutput(validator); + } } TEST(OEMCryptoBccValidatorTest, BccParseThreeTimes) { @@ -135,13 +325,16 @@ TEST(OEMCryptoBccValidatorTest, BccMissingIssuer) { result = validator.Validate(); EXPECT_EQ(result, kCborValidateError); const std::string out = validator.GetFormattedMessage(); - EXPECT_THAT(out, HasSubstr("key encoding format: DEVICE_KEY_BYTE_STRING")); - EXPECT_THAT(out, HasSubstr("key algorithm type: EDDSA")); + EXPECT_THAT(out, HasSubstr("key_type:byte_string")); + EXPECT_THAT(out, HasSubstr("signature_algorithm:EdDSA")); const std::vector> msgs = validator.GetValidateMessages(); EXPECT_EQ(1u, msgs.size()); EXPECT_EQ(kCborValidateError, msgs[0].first); - EXPECT_THAT(msgs[0].second, HasSubstr("Missing Issuer")); + EXPECT_THAT(msgs[0].second, HasSubstr("missing required field issuer")); + if (result >= kCborValidateWarning) { + DumpValidatorOutput(validator); + } } } // namespace util } // namespace wvoec diff --git a/oemcrypto/util/test/device_info_validator_unittest.cpp b/oemcrypto/util/test/device_info_validator_unittest.cpp index 2e4e022c..9454d974 100644 --- a/oemcrypto/util/test/device_info_validator_unittest.cpp +++ b/oemcrypto/util/test/device_info_validator_unittest.cpp @@ -8,6 +8,7 @@ #include #include "device_info_validator.h" +#include "log.h" using ::testing::AllOf; using ::testing::Ge; @@ -30,7 +31,7 @@ cppbor::Map BuildDeviceInfoMap(int version) { .add("model", "model") .add("vb_state", "green") .add("bootloader_state", "unlocked") - .add("vbmeta_digest", cppbor::Bstr(std::vector())) + .add("vbmeta_digest", cppbor::Bstr(std::vector(32, 0xCC))) .add("os_version", "os_version") .add("system_patch_level", 202312) .add("boot_patch_level", 20231201) @@ -61,6 +62,14 @@ std::vector BuildDeviceInfo(int version) { } } // namespace +static void DumpValidatorOutput(const DeviceInfoValidator& validator) { + const std::string out = validator.GetFormattedMessage(); + LOGI("%s", out.c_str()); + for (auto& msg : validator.GetValidateMessages()) { + LOGE("Error code %d: %s", msg.first, msg.second.c_str()); + } +} + TEST(OEMCryptoDeviceInfoValidatorTest, DeviceInfoParseError) { const std::vector device_info = BuildDeviceInfo(kDeviceVersion3); const std::vector device_info_bad(device_info.begin(), @@ -87,6 +96,9 @@ TEST(OEMCryptoDeviceInfoValidatorTest, DeviceInfoNotMap) { EXPECT_EQ(1u, msgs.size()); EXPECT_EQ(kCborValidateFatal, msgs[0].first); EXPECT_THAT(msgs[0].second, HasSubstr("Device info is not a CBOR map")); + if (result >= kCborValidateWarning) { + DumpValidatorOutput(validator); + } } TEST(OEMCryptoDeviceInfoValidatorTest, @@ -95,8 +107,7 @@ TEST(OEMCryptoDeviceInfoValidatorTest, cppbor::Map() .add("brand", "brand") .add("manufacturer", "manufacturer") - .add(123, 456) // Non-Tstr key type - .add("system_patch_level", "not a uint") // Non-uint value type + .add(123, 456) // Non-Tstr key type .canonicalize() .encode(); DeviceInfoValidator validator(kDeviceVersion3); @@ -112,25 +123,22 @@ TEST(OEMCryptoDeviceInfoValidatorTest, return p.second.find("Unexpected entry key type") != std::string::npos; }); EXPECT_EQ(true, unexpected_key_type_found); - const bool unexpected_value_type_found = std::any_of( - msgs.begin(), msgs.end(), - [](const std::pair& p) { - return p.second.find("system_patch_level has the wrong type") != - std::string::npos; - }); - EXPECT_EQ(true, unexpected_value_type_found); - const bool missing_model_found = std::any_of( - msgs.begin(), msgs.end(), - [](const std::pair& p) { - return p.second.find("model is missing") != std::string::npos; - }); + const bool missing_model_found = + std::any_of(msgs.begin(), msgs.end(), + [](const std::pair& p) { + return p.second.find("missing important field model") != + std::string::npos; + }); EXPECT_EQ(true, missing_model_found); + if (result >= kCborValidateWarning) { + DumpValidatorOutput(validator); + } } TEST(OEMCryptoDeviceInfoValidatorTest, DeviceInfoV3NonCanonical) { const cppbor::Map map = BuildDeviceInfoMap(kDeviceVersion3); const std::vector device_info = map.encode(); - DeviceInfoValidator validator(kDeviceVersion3); + DeviceInfoValidator validator(kDeviceVersion3, true /* is_gms */); CborMessageStatus result = validator.Parse(device_info); EXPECT_EQ(kCborParseOk, result); result = validator.Validate(); @@ -141,43 +149,97 @@ TEST(OEMCryptoDeviceInfoValidatorTest, DeviceInfoV3NonCanonical) { EXPECT_EQ(kCborValidateError, msgs[0].first); EXPECT_THAT(msgs[0].second, HasSubstr("Device info ordering is non-canonical")); + if (result >= kCborValidateWarning) { + DumpValidatorOutput(validator); + } } TEST(OEMCryptoDeviceInfoValidatorTest, DeviceInfoV3) { const std::vector device_info = BuildDeviceInfo(kDeviceVersion3); - DeviceInfoValidator validator(kDeviceVersion3); + DeviceInfoValidator validator(kDeviceVersion3, true /* is_gms */); CborMessageStatus result = validator.Parse(device_info); EXPECT_EQ(kCborParseOk, result); result = validator.Validate(); EXPECT_THAT(result, AllOf(Ge(kCborValidateOk), Le(kCborValidateWarning))); const std::string out = validator.GetFormattedMessage(); - EXPECT_THAT(out, HasSubstr("manufacturer: manufacturer")); - EXPECT_THAT(out, HasSubstr("model: model")); - EXPECT_THAT(out, HasSubstr("fused: 0")); + EXPECT_THAT(out, HasSubstr("manufacturer:manufacturer")); + EXPECT_THAT(out, HasSubstr("model:model")); + EXPECT_THAT(out, HasSubstr("fused:0")); + if (result >= kCborValidateWarning) { + DumpValidatorOutput(validator); + } +} + +TEST(OEMCryptoDeviceInfoValidatorTest, DeviceInfoV3InvalidFields) { + cppbor::Map device_info_map = + cppbor::Map() + .add("brand", "brand") + .add("manufacturer", "manufacturer") + .add("product", "product") + .add("model", "model") + .add("vb_state", "invalid_green") // invalid value + .add("bootloader_state", "invalid_unlocked") // invalid value + .add("vbmeta_digest", cppbor::Bstr(std::vector(32, 0xCC))) + .add("os_version", "os_version") + .add("system_patch_level", 100) // invalid value, expects "YYYYMM" + .add("boot_patch_level", + 12345678) // invalid value, expectes "YYYYMMDD" + .add("vendor_patch_level", + "20231201") // invalid value, expects YYYYMMDD in int + .add("security_level", "tee") + .add("device", "device") + .add("fused", 9); // invalid value, expects 0 or 1 + auto device_info = device_info_map.canonicalize().encode(); + DeviceInfoValidator validator(kDeviceVersion3, true /* is_gms */); + CborMessageStatus result = validator.Parse(device_info); + EXPECT_EQ(kCborParseOk, result); + result = validator.Validate(); + EXPECT_THAT(result, Ge(kCborValidateError)); + const std::vector> msgs = + validator.GetValidateMessages(); + std::string out = ""; + for (auto& msg : msgs) { + out += (msg.second + "\n"); + } + EXPECT_THAT(out, HasSubstr("invalid value for system_patch_level")); + EXPECT_THAT(out, HasSubstr("invalid value for boot_patch_level")); + EXPECT_THAT(out, HasSubstr("missing required field vendor_patch_level")); + EXPECT_THAT(out, HasSubstr("unexpected value for vb_state")); + EXPECT_THAT(out, HasSubstr("unexpected value for bootloader_state")); + EXPECT_THAT(out, HasSubstr("unexpected value for fused")); + if (result >= kCborValidateWarning) { + DumpValidatorOutput(validator); + } } TEST(OEMCryptoDeviceInfoValidatorTest, DeviceInfoV2) { const std::vector device_info = BuildDeviceInfo(kDeviceVersion2); - DeviceInfoValidator validator(kDeviceVersion2); + DeviceInfoValidator validator(kDeviceVersion2, true /* is_gms */); CborMessageStatus result = validator.Parse(device_info); EXPECT_EQ(kCborParseOk, result); result = validator.Validate(); EXPECT_THAT(result, AllOf(Ge(kCborValidateOk), Le(kCborValidateWarning))); const std::string out = validator.GetFormattedMessage(); - EXPECT_THAT(out, HasSubstr("manufacturer: manufacturer")); - EXPECT_THAT(out, HasSubstr("model: model")); - EXPECT_THAT(out, HasSubstr("fused: 0")); - EXPECT_THAT(out, HasSubstr("version: 2")); + EXPECT_THAT(out, HasSubstr("manufacturer:manufacturer")); + EXPECT_THAT(out, HasSubstr("model:model")); + EXPECT_THAT(out, HasSubstr("fused:0")); + EXPECT_THAT(out, HasSubstr("version:2")); + if (result >= kCborValidateWarning) { + DumpValidatorOutput(validator); + } } TEST(OEMCryptoDeviceInfoValidatorTest, DeviceInfoV1MissingField) { - const std::vector device_info = cppbor::Map() - .add("brand", "brand") - .add("security_level", "tee") - .add("version", 1) - .canonicalize() - .encode(); - DeviceInfoValidator validator(kDeviceVersion1); + const std::vector device_info = + cppbor::Map() + .add("manufacturer", "manufacturer") + .add("model", "model") + .add("brand", "brand") + .add("security_level", "tee") + .add("version", 1) + .canonicalize() + .encode(); + DeviceInfoValidator validator(kDeviceVersion1, true /* is_gms */); CborMessageStatus result = validator.Parse(device_info); EXPECT_EQ(kCborParseOk, result); result = validator.Validate(); @@ -186,19 +248,50 @@ TEST(OEMCryptoDeviceInfoValidatorTest, DeviceInfoV1MissingField) { validator.GetValidateMessages(); EXPECT_EQ(1u, msgs.size()); EXPECT_EQ(kCborValidateError, msgs[0].first); - EXPECT_THAT(msgs[0].second, HasSubstr("att_id_state is missing")); + EXPECT_THAT(msgs[0].second, HasSubstr("missing required field att_id_state")); + if (result >= kCborValidateWarning) { + DumpValidatorOutput(validator); + } } TEST(OEMCryptoDeviceInfoValidatorTest, DeviceInfoV1) { - DeviceInfoValidator validator(kDeviceVersion1); + DeviceInfoValidator validator(kDeviceVersion1, true /* is_gms */); const std::vector device_info = BuildDeviceInfo(kDeviceVersion1); CborMessageStatus result = validator.Parse(device_info); EXPECT_EQ(kCborParseOk, result); result = validator.Validate(); EXPECT_THAT(result, AllOf(Ge(kCborValidateOk), Le(kCborValidateWarning))); const std::string out = validator.GetFormattedMessage(); - EXPECT_THAT(out, HasSubstr("board: board")); - EXPECT_THAT(out, HasSubstr("version: 1")); + EXPECT_THAT(out, HasSubstr("board:board")); + EXPECT_THAT(out, HasSubstr("version:1")); + if (result >= kCborValidateWarning) { + DumpValidatorOutput(validator); + } +} + +TEST(OEMCryptoDeviceInfoValidatorTest, DeviceInfoMissingFused) { + const std::vector device_info_bad = + cppbor::Map() + .add("model", "model") + .add("manufacturer", "manufacturer") + .canonicalize() + .encode(); + DeviceInfoValidator validator(kDeviceVersion1, false /* is_gms */); + CborMessageStatus result = validator.Parse(device_info_bad); + EXPECT_EQ(kCborParseOk, result); + result = validator.Validate(); + EXPECT_EQ(kCborValidateWarning, result); + const std::vector> msgs = + validator.GetValidateMessages(); + const bool missing_fused_found = std::any_of( + msgs.begin(), msgs.end(), + [](const std::pair& p) { + return p.second.find("missing field fused") != std::string::npos; + }); + EXPECT_EQ(true, missing_fused_found); + if (result >= kCborValidateWarning) { + DumpValidatorOutput(validator); + } } } // namespace util } // namespace wvoec diff --git a/oemcrypto/util/test/signed_csr_payload_validator_unittest.cpp b/oemcrypto/util/test/signed_csr_payload_validator_unittest.cpp index f5c4c7f8..2eccb70d 100644 --- a/oemcrypto/util/test/signed_csr_payload_validator_unittest.cpp +++ b/oemcrypto/util/test/signed_csr_payload_validator_unittest.cpp @@ -7,6 +7,7 @@ #include #include +#include "log.h" #include "signed_csr_payload_validator.h" using ::testing::AllOf; @@ -86,6 +87,14 @@ std::vector GetDefaultSignedCsrPayload() { .add(cppbor::Bstr(GetDefaultSignature())) .encode(); } + +static void DumpValidatorOutput(const SignedCsrPayloadValidator& validator) { + const std::string out = validator.GetFormattedMessage(); + LOGI("%s", out.c_str()); + for (auto& msg : validator.GetValidateMessages()) { + LOGE("Error code %d: %s", msg.first, msg.second.c_str()); + } +} } // namespace TEST(OEMCryptoSignedCsrPayloadValidatorTest, SignedCsrPayloadParseError) { @@ -119,7 +128,8 @@ TEST(OEMCryptoSignedCsrPayloadValidatorTest, ProtectedDataNotMap) { validator.GetValidateMessages(); EXPECT_EQ(1u, msgs.size()); EXPECT_EQ(kCborValidateFatal, msgs[0].first); - EXPECT_THAT(msgs[0].second, HasSubstr("ProtectedParams must be a CBOR map")); + EXPECT_THAT(msgs[0].second, + HasSubstr("Unexpected signed csr payload protected type: ARRAY")); } TEST(OEMCryptoSignedCsrPayloadValidatorTest, ProtectedDataMapMissingKey) { @@ -135,12 +145,13 @@ TEST(OEMCryptoSignedCsrPayloadValidatorTest, ProtectedDataMapMissingKey) { CborMessageStatus result = validator.Parse(signed_csr_payload); EXPECT_EQ(kCborParseOk, result); result = validator.Validate(); - EXPECT_THAT(result, kCborValidateFatal); + EXPECT_THAT(result, kCborValidateError); const std::vector> msgs = validator.GetValidateMessages(); EXPECT_EQ(1u, msgs.size()); - EXPECT_EQ(kCborValidateFatal, msgs[0].first); - EXPECT_THAT(msgs[0].second, HasSubstr("ProtectedParams is empty")); + EXPECT_EQ(kCborValidateError, msgs[0].first); + EXPECT_THAT(msgs[0].second, HasSubstr("missing required field protected")); + DumpValidatorOutput(validator); } TEST(OEMCryptoSignedCsrPayloadValidatorTest, ProtectedDataMapKeyWarnings) { @@ -163,7 +174,7 @@ TEST(OEMCryptoSignedCsrPayloadValidatorTest, ProtectedDataMapKeyWarnings) { const bool unexpected_key_type_found = std::any_of( msgs.begin(), msgs.end(), [](const std::pair& p) { - return p.second.find("Unsupported key type") != std::string::npos; + return p.second.find("Invalid key type") != std::string::npos; }); EXPECT_EQ(true, unexpected_key_type_found); const bool unexpected_key_value_found = std::any_of( @@ -172,13 +183,14 @@ TEST(OEMCryptoSignedCsrPayloadValidatorTest, ProtectedDataMapKeyWarnings) { return p.second.find("Unsupported key value") != std::string::npos; }); EXPECT_EQ(true, unexpected_key_value_found); - const bool unexpected_entry_found = - std::any_of(msgs.begin(), msgs.end(), - [](const std::pair& p) { - return p.second.find("ProtectedParams expects 1 entry") != - std::string::npos; - }); + const bool unexpected_entry_found = std::any_of( + msgs.begin(), msgs.end(), + [](const std::pair& p) { + return p.second.find("Protected data map expects 1 entry") != + std::string::npos; + }); EXPECT_EQ(true, unexpected_entry_found); + DumpValidatorOutput(validator); } TEST(OEMCryptoSignedCsrPayloadValidatorTest, InvalidChallengeAndPayload) { @@ -212,16 +224,13 @@ TEST(OEMCryptoSignedCsrPayloadValidatorTest, InvalidChallengeAndPayload) { const bool challenge_error_found = std::any_of( msgs.begin(), msgs.end(), [](const std::pair& p) { - return p.second.find("Challenge size must be between 0 and 64 bytes") != - std::string::npos; + return p.second.find("Unexpected challenge size") != std::string::npos; }); EXPECT_EQ(true, challenge_error_found); const bool csr_payload_error_found = std::any_of( msgs.begin(), msgs.end(), [](const std::pair& p) { - return p.second.find( - "CSR payload version must be must be equal to 3") != - std::string::npos; + return p.second.find("Invalid version") != std::string::npos; }); EXPECT_EQ(true, csr_payload_error_found); } @@ -250,11 +259,11 @@ TEST(OEMCryptoSignedCsrPayloadValidatorTest, KeysToSignEmptyList) { CborMessageStatus result = validator.Parse(signed_csr_payload); EXPECT_EQ(kCborParseOk, result); result = validator.Validate(); - EXPECT_THAT(result, kCborValidateFatal); + EXPECT_THAT(result, kCborValidateError); const std::vector> msgs = validator.GetValidateMessages(); EXPECT_EQ(1u, msgs.size()); - EXPECT_EQ(kCborValidateFatal, msgs[0].first); + EXPECT_EQ(kCborValidateError, msgs[0].first); EXPECT_THAT(msgs[0].second, HasSubstr("Keys must be a CBOR array")); } @@ -275,7 +284,7 @@ TEST(OEMCryptoSignedCsrPayloadValidatorTest, SignatureMissing) { validator.GetValidateMessages(); EXPECT_EQ(1u, msgs.size()); EXPECT_EQ(kCborValidateError, msgs[0].first); - EXPECT_THAT(msgs[0].second, HasSubstr("CoseSign1 signature is missing")); + EXPECT_THAT(msgs[0].second, HasSubstr("missing required field signature")); } TEST(OEMCryptoSignedCsrPayloadValidatorTest, ValidateOk) { @@ -285,10 +294,9 @@ TEST(OEMCryptoSignedCsrPayloadValidatorTest, ValidateOk) { result = validator.Validate(); EXPECT_THAT(result, AllOf(Ge(kCborValidateOk), Le(kCborValidateWarning))); const std::string out = validator.GetFormattedMessage(); - EXPECT_THAT(out, HasSubstr("1: ES256")); - EXPECT_THAT(out, HasSubstr("version: 3")); - EXPECT_THAT(out, HasSubstr("certificate_type: widevine")); - EXPECT_THAT(out, HasSubstr("keys_to_sign: []")); + EXPECT_THAT(out, HasSubstr("algorithm:ECDSA_SHA256")); + EXPECT_THAT(out, HasSubstr("version:3")); + EXPECT_THAT(out, HasSubstr("type:widevine")); } } // namespace util } // namespace wvoec diff --git a/platforms/example/no_oemcrypto.cpp b/platforms/example/no_oemcrypto.cpp index 45d4c430..9de489ea 100644 --- a/platforms/example/no_oemcrypto.cpp +++ b/platforms/example/no_oemcrypto.cpp @@ -586,6 +586,11 @@ OEMCryptoResult OEMCrypto_GetBCCType(OEMCrypto_BCCType* bcc_type UNUSED) { return OEMCrypto_ERROR_NOT_IMPLEMENTED; } +OEMCryptoResult OEMCrypto_GetBCCSignatureType( + OEMCrypto_BCCSignatureType* bcc_signature_type UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + OEMCryptoResult OEMCrypto_LoadRelease(OEMCrypto_SESSION session UNUSED, const uint8_t* message UNUSED, size_t message_length UNUSED, diff --git a/util/include/file_store.h b/util/include/file_store.h index 9563b5eb..26dbf784 100644 --- a/util/include/file_store.h +++ b/util/include/file_store.h @@ -7,63 +7,118 @@ #ifndef WVCDM_UTIL_FILE_STORE_H_ #define WVCDM_UTIL_FILE_STORE_H_ -#include +#include + #include #include #include -#include "disallow_copy_and_assign.h" #include "platform.h" #include "util_common.h" +#include "wv_class_utils.h" namespace wvutil { +// Fixed filename for ATSC DRM certificate pre-installed +// on ATSC devices for ATSC licenses. static const std::string kAtscCertificateFileName = "atsccert.bin"; +// General filename for either global or unmapped app-origin +// DRM certificates. static const std::string kCertificateFileName = "cert1.bin"; +// File extension for DRM and OEM certificate files. static const std::string kCertificateFileNameExt = ".bin"; -static const std::string kCertificateFileNamePrefix = "cert1_"; +// Filename prefix for mapped (scoped) DRM certificate filenames +// specific to a particular app-origin. +static const std::string kScopedCertificateFilenamePrefix = "cert1_"; +// TODO(b/376533901): Replace this constant with +// kScopedCertificateFilenamePrefix in source code.. +static const std::string kCertificateFileNamePrefix = + kScopedCertificateFilenamePrefix; +// Legacy general filename for either global or unmapped app-origin +// DRM certificates. static const std::string kLegacyCertificateFileName = "cert.bin"; -static const std::string kLegacyCertificateFileNamePrefix = "cert"; +// Legacy filename prefix for mapped (scoped) DRM certificate filenames +// specific to a particular app-origin. +static const std::string kLegacyScopedCertificateFilenamePrefix = "cert"; +// TODO(b/376533901): Replace this constant with +// kLegacyScopedCertificateFilenamePrefix in source code.. +static const std::string kLegacyCertificateFileNamePrefix = + kLegacyScopedCertificateFilenamePrefix; +// Filename for global OEM certificates. static const std::string kOemCertificateFileName = "oemcert.bin"; -static const std::string kOemCertificateFileNamePrefix = "oemcert_"; -// File class. The implementation is platform dependent. +// File interface. The implementation is platform dependent. class File { public: + WVCDM_DISALLOW_COPY_AND_MOVE(File); File() {} virtual ~File() {} virtual ssize_t Read(char* buffer, size_t bytes) = 0; virtual ssize_t Write(const char* buffer, size_t bytes) = 0; - - friend class FileSystem; - CORE_DISALLOW_COPY_AND_ASSIGN(File); }; +// File system base class. The implementation is platform dependent. class FileSystem { public: + WVCDM_DISALLOW_COPY_AND_MOVE(FileSystem); FileSystem(); FileSystem(const std::string& origin, void* extra_data); virtual ~FileSystem(); + // Concreate implementation of FileSystem. + // Depending on the platform, this may be vendor or Widevine implemented. class Impl; - // defines as bit flag - enum OpenFlags { - kNoFlags = 0, - kCreate = 1, - kReadOnly = 2, // defaults to read and write access - kTruncate = 4 - }; + // Flags for calls to Open. + static constexpr int kNoFlags = 0; + // Create file if does not already exist, open file if it does exist. + static constexpr int kCreate = (1 << 0); + // Open file as read-only; typically should not be used with kCreate. + static constexpr int kReadOnly = (1 << 1); + // Open file and truncated. May be used with kCreate; should not + // be used with kReadOnly. + static constexpr int kTruncate = (1 << 2); virtual std::unique_ptr Open(const std::string& file_path, int flags); - virtual bool Exists(const std::string& file_path); - virtual bool Exists(const std::string& file_path, int* errno_value); - virtual bool Remove(const std::string& file_path); + // Checks if the |path| exists. The |path| may be a file or directory. + // Return true if an entry in the file system exists; false otherwise. + virtual bool Exists(const std::string& path); + // Same as above, except the optional parameter of |errno_value| should + // be set to 0 or the value of C errno when attempting to check + // the existence of a file. + virtual bool Exists(const std::string& path, int* errno_value); + + // Removes the specified |path|. + // + // If |path| is a regular file, the file should be removed. + // If |path| is a directory, both the directory and the directory + // contents should be removed. + // + // Implementation must support a |path| containing a single wildcard + // character in the filename component of the path. + // + // Return value: + // - true : File/directory was removed, or file/directory did not exist + // - false : File/directory could not be removed, or other error. + virtual bool Remove(const std::string& path); + + // Obtain the size of a file in bytes. |file_path| must be a file, + // and not a directory. + // + // Return value: + // - non-negative : size of file in bytes if file exists + // - negative : file does not exist, or error occurred. virtual ssize_t FileSize(const std::string& file_path); - // Return the filenames stored at dir_path. - // dir_path will be stripped from the returned names. + // Return the entries stored at |dir_path| (includes both files + // and directories). + // + // Return value: + // - true : Directory exists, and directory entry names are stored + // in |names|; |names| may be empty if directory was empty. + // - false : Directory does not exist, |dir_path| is not a directory, + // or error was encountered. virtual bool List(const std::string& dir_path, std::vector* names); @@ -78,8 +133,6 @@ class FileSystem { std::unique_ptr impl_; std::string origin_; std::string identifier_; - - CORE_DISALLOW_COPY_AND_ASSIGN(FileSystem); }; } // namespace wvutil diff --git a/util/test/file_store_unittest.cpp b/util/test/file_store_unittest.cpp index a62bfc03..ad30ac26 100644 --- a/util/test/file_store_unittest.cpp +++ b/util/test/file_store_unittest.cpp @@ -1,9 +1,14 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary // source code may only be used and distributed under the Widevine License // Agreement. - #include "file_store.h" +#include + +#include +#include +#include + #include #include @@ -11,18 +16,20 @@ #include "test_vectors.h" namespace wvutil { - namespace { -const std::string kTestDirName = "test_dir"; -const std::string kTestFileName = "test.txt"; -const std::string kTestFileName2 = "test2.txt"; -const std::string kTestFileName3 = "test3.other"; -const std::string kTestFileNameExt = ".txt"; -const std::string kTestFileNameExt3 = ".other"; -const std::string kTestIdentifier1 = "some_identifier"; -const std::string kTestIdentifier2 = "some_other_identifier"; -const std::string kWildcard = "*"; -const std::string kUnderscore = "_"; +constexpr char kTestFilename[] = "sample.txt"; + +constexpr char kTestIdentifier1[] = "some_identifier"; +constexpr char kTestIdentifier2[] = "some_other_identifier"; + +constexpr int kNoError = 0; +constexpr int kEntryDoesNotExist = ENOENT; + +bool StartsWith(const std::string& haystack, const std::string& needle) { + if (needle.empty()) return true; + if (haystack.size() < needle.size()) return false; + return haystack.find(needle) == 0; +} } // namespace class FileTest : public testing::Test { @@ -32,7 +39,19 @@ class FileTest : public testing::Test { void TearDown() override { RemoveTestDir(); } void RemoveTestDir() { - EXPECT_TRUE(file_system_.Remove(wvcdm::test_vectors::kTestDir)); + ASSERT_TRUE(file_system_.Remove(wvcdm::test_vectors::kTestDir)) + << "Failed to update test directory: " << wvcdm::test_vectors::kTestDir; + } + + std::string PathJoin(const std::string& base_path, + const std::string& add_path) { + if (base_path.empty()) return add_path; + std::string path = base_path; + if (path.back() != '/') { + path.push_back('/'); + } + path.append(add_path); + return path; } FileSystem file_system_; @@ -40,336 +59,505 @@ class FileTest : public testing::Test { TEST_F(FileTest, FileExists) { int errno_value = -1; - EXPECT_TRUE(file_system_.Exists(wvcdm::test_vectors::kExistentFile)); + EXPECT_TRUE(file_system_.Exists(wvcdm::test_vectors::kExistentFile)) + << "path = " << wvcdm::test_vectors::kExistentFile; EXPECT_TRUE( - file_system_.Exists(wvcdm::test_vectors::kExistentFile, &errno_value)); - EXPECT_EQ(0, errno_value); - EXPECT_TRUE(file_system_.Exists(wvcdm::test_vectors::kExistentDir)); + file_system_.Exists(wvcdm::test_vectors::kExistentFile, &errno_value)) + << "path = " << wvcdm::test_vectors::kExistentFile; + EXPECT_EQ(kNoError, errno_value); +} - EXPECT_FALSE(file_system_.Exists(wvcdm::test_vectors::kNonExistentFile)); +TEST_F(FileTest, FileDoesNotExist) { + int errno_value = -1; + EXPECT_FALSE(file_system_.Exists(wvcdm::test_vectors::kNonExistentFile)) + << "path = " << wvcdm::test_vectors::kNonExistentFile; EXPECT_FALSE( - file_system_.Exists(wvcdm::test_vectors::kNonExistentFile, &errno_value)); - EXPECT_EQ(ENOENT, errno_value); - EXPECT_FALSE(file_system_.Exists(wvcdm::test_vectors::kNonExistentDir)); + file_system_.Exists(wvcdm::test_vectors::kNonExistentFile, &errno_value)) + << "path = " << wvcdm::test_vectors::kNonExistentFile; + EXPECT_EQ(kEntryDoesNotExist, errno_value); +} + +TEST_F(FileTest, DirectoryExists) { + int errno_value = -1; + EXPECT_TRUE(file_system_.Exists(wvcdm::test_vectors::kExistentDir)) + << "path = " << wvcdm::test_vectors::kExistentDir; + EXPECT_TRUE( + file_system_.Exists(wvcdm::test_vectors::kExistentDir, &errno_value)) + << "path = " << wvcdm::test_vectors::kExistentDir; + EXPECT_EQ(kNoError, errno_value); +} + +TEST_F(FileTest, DirectoryDoesNotExist) { + int errno_value = -1; + EXPECT_FALSE(file_system_.Exists(wvcdm::test_vectors::kNonExistentDir)) + << "path = " << wvcdm::test_vectors::kNonExistentDir; + EXPECT_FALSE( + file_system_.Exists(wvcdm::test_vectors::kNonExistentDir, &errno_value)) + << "path = " << wvcdm::test_vectors::kNonExistentDir; + EXPECT_EQ(kEntryDoesNotExist, errno_value); } TEST_F(FileTest, RemoveDir) { - EXPECT_TRUE(file_system_.Remove(wvcdm::test_vectors::kTestDir)); - EXPECT_FALSE(file_system_.Exists(wvcdm::test_vectors::kTestDir)); + EXPECT_TRUE(file_system_.Remove(wvcdm::test_vectors::kTestDir)) + << "path = " << wvcdm::test_vectors::kTestDir; + EXPECT_FALSE(file_system_.Exists(wvcdm::test_vectors::kTestDir)) + << "path = " << wvcdm::test_vectors::kTestDir; } TEST_F(FileTest, OpenFile) { - std::string path = wvcdm::test_vectors::kTestDir + kTestFileName; - EXPECT_TRUE(file_system_.Remove(path)); + const std::string path = + PathJoin(wvcdm::test_vectors::kTestDir, kTestFilename); + EXPECT_TRUE(file_system_.Remove(path)) << "path = " << path; std::unique_ptr file = file_system_.Open(path, FileSystem::kCreate); - ASSERT_TRUE(file); + ASSERT_TRUE(file) << "Failed to create file: " << path; - EXPECT_TRUE(file_system_.Exists(path)); + EXPECT_TRUE(file_system_.Exists(path)) << "path = " << path; } TEST_F(FileTest, RemoveDirAndFile) { - std::string path = wvcdm::test_vectors::kTestDir + kTestFileName; + const std::string path = + PathJoin(wvcdm::test_vectors::kTestDir, kTestFilename); std::unique_ptr file = file_system_.Open(path, FileSystem::kCreate); - ASSERT_TRUE(file); + ASSERT_TRUE(file) << "Failed to create file: " << path; - EXPECT_TRUE(file_system_.Exists(path)); - EXPECT_TRUE(file_system_.Remove(path)); - EXPECT_FALSE(file_system_.Exists(path)); + EXPECT_TRUE(file_system_.Exists(path)) << "path = " << path; + EXPECT_TRUE(file_system_.Remove(path)) << "path = " << path; + EXPECT_FALSE(file_system_.Exists(path)) << "path = " << path; file = file_system_.Open(path, FileSystem::kCreate); - ASSERT_TRUE(file); + ASSERT_TRUE(file) << "Failed to create file: " << path; - EXPECT_TRUE(file_system_.Exists(path)); - RemoveTestDir(); - EXPECT_FALSE(file_system_.Exists(wvcdm::test_vectors::kTestDir)); - EXPECT_FALSE(file_system_.Exists(path)); + EXPECT_TRUE(file_system_.Exists(path)) << "path = " << path; + ASSERT_NO_FATAL_FAILURE(RemoveTestDir()); + EXPECT_FALSE(file_system_.Exists(wvcdm::test_vectors::kTestDir)) + << "path = " << path; + EXPECT_FALSE(file_system_.Exists(path)) << "path = " << path; } TEST_F(FileTest, RemoveWildcardFiles) { - std::string path1 = wvcdm::test_vectors::kTestDir + kTestFileName; - std::string path2 = wvcdm::test_vectors::kTestDir + kTestFileName2; - std::string wildcard_path = - wvcdm::test_vectors::kTestDir + kWildcard + kTestFileNameExt; + const std::string path1 = + PathJoin(wvcdm::test_vectors::kTestDir, "first.txt"); + const std::string path2 = + PathJoin(wvcdm::test_vectors::kTestDir, "second.txt"); + const std::string wildcard_path = + PathJoin(wvcdm::test_vectors::kTestDir, "*.txt"); std::unique_ptr file = file_system_.Open(path1, FileSystem::kCreate); - ASSERT_TRUE(file); + ASSERT_TRUE(file) << "Failed to create file (1): " << path1; file = file_system_.Open(path2, FileSystem::kCreate); - ASSERT_TRUE(file); + ASSERT_TRUE(file) << "Failed to create file (2): " << path2; - EXPECT_TRUE(file_system_.Exists(path1)); - EXPECT_TRUE(file_system_.Exists(path2)); - EXPECT_TRUE(file_system_.Remove(wildcard_path)); - EXPECT_FALSE(file_system_.Exists(path1)); - EXPECT_FALSE(file_system_.Exists(path2)); + EXPECT_TRUE(file_system_.Exists(path1)) << "path = " << path1; + EXPECT_TRUE(file_system_.Exists(path2)) << "path = " << path2; + EXPECT_TRUE(file_system_.Remove(wildcard_path)) + << "wildcard_path = " << wildcard_path; + EXPECT_FALSE(file_system_.Exists(path1)) << "path = " << path1; + EXPECT_FALSE(file_system_.Exists(path2)) << "path = " << path2; } TEST_F(FileTest, FileSize) { - std::string path = wvcdm::test_vectors::kTestDir + kTestFileName; + const std::string path = + PathJoin(wvcdm::test_vectors::kTestDir, kTestFilename); file_system_.Remove(path); - std::string write_data = CdmRandom::RandomData(600); - size_t write_data_size = write_data.size(); - std::unique_ptr file = file_system_.Open(path, FileSystem::kCreate); - ASSERT_TRUE(file); - EXPECT_EQ(file->Write(write_data.data(), write_data_size), write_data_size); - EXPECT_TRUE(file_system_.Exists(path)); + constexpr size_t kDataSize = 600; + const std::string write_data = CdmRandom::RandomData(kDataSize); + ASSERT_EQ(write_data.size(), kDataSize); - EXPECT_EQ(static_cast(write_data_size), file_system_.FileSize(path)); + std::unique_ptr file = file_system_.Open(path, FileSystem::kCreate); + ASSERT_TRUE(file) << "Failed to create file: " << path; + + EXPECT_EQ(file->Write(write_data.data(), write_data.size()), + write_data.size()); + file.reset(); // Close file. + + EXPECT_TRUE(file_system_.Exists(path)) << "path = " << path; + + EXPECT_EQ(static_cast(kDataSize), file_system_.FileSize(path)) + << "path = " << path; } TEST_F(FileTest, WriteReadBinaryFile) { - std::string path = wvcdm::test_vectors::kTestDir + kTestFileName; + const std::string path = + PathJoin(wvcdm::test_vectors::kTestDir, kTestFilename); file_system_.Remove(path); - std::string write_data = CdmRandom::RandomData(600); - size_t write_data_size = write_data.size(); - std::unique_ptr file = file_system_.Open(path, FileSystem::kCreate); - ASSERT_TRUE(file); - EXPECT_EQ(file->Write(write_data.data(), write_data_size), write_data_size); - EXPECT_TRUE(file_system_.Exists(path)); + constexpr size_t kDataSize = 600; + const std::string write_data = CdmRandom::RandomData(kDataSize); + ASSERT_EQ(write_data.size(), kDataSize); + + std::unique_ptr file = file_system_.Open(path, FileSystem::kCreate); + ASSERT_TRUE(file) << "Failed to create file: " << path; + + constexpr ssize_t kExpectedFileSizeResult = static_cast(kDataSize); + EXPECT_EQ(file->Write(write_data.data(), write_data.size()), + kExpectedFileSizeResult) + << "path = " << path; + file.reset(); // Close file. + + EXPECT_TRUE(file_system_.Exists(path)) << "path = " << path; - std::string read_data; - read_data.resize(file_system_.FileSize(path)); - size_t read_data_size = read_data.size(); file = file_system_.Open(path, FileSystem::kReadOnly); - ASSERT_TRUE(file); - EXPECT_EQ(file->Read(&read_data[0], read_data_size), read_data_size); + ASSERT_TRUE(file) << "Failed to re-open file: " << path; + + std::string read_data(kDataSize, '\0'); + ; + ASSERT_EQ(file->Read(&read_data[0], read_data.size()), + kExpectedFileSizeResult) + << "path = " << path; EXPECT_EQ(write_data, read_data); } TEST_F(FileTest, ListFiles) { - std::vector names; + const std::string kTxtFilename1 = "data.txt"; + const std::string kTxtFilename2 = "other.txt"; + const std::string kBinFilename = "sample.bin"; - std::string not_path("zzz"); - std::string path1 = wvcdm::test_vectors::kTestDir + kTestFileName; - std::string path2 = wvcdm::test_vectors::kTestDir + kTestFileName2; - std::string path3 = wvcdm::test_vectors::kTestDir + kTestFileName3; - std::string path_dir = wvcdm::test_vectors::kTestDir; + const std::string dir_path = wvcdm::test_vectors::kTestDir; + const std::string path1 = PathJoin(dir_path, kTxtFilename1); + const std::string path2 = PathJoin(dir_path, kTxtFilename2); + const std::string path3 = PathJoin(dir_path, kBinFilename); + // Create files. std::unique_ptr file = file_system_.Open(path1, FileSystem::kCreate); - ASSERT_TRUE(file); + ASSERT_TRUE(file) << "Failed to create file (1): " << path1; file = file_system_.Open(path2, FileSystem::kCreate); - ASSERT_TRUE(file); + ASSERT_TRUE(file) << "Failed to create file (2): " << path2; file = file_system_.Open(path3, FileSystem::kCreate); - ASSERT_TRUE(file); + ASSERT_TRUE(file) << "Failed to create file (3): " << path3; + file.reset(); // Close file EXPECT_TRUE(file_system_.Exists(path1)); EXPECT_TRUE(file_system_.Exists(path2)); EXPECT_TRUE(file_system_.Exists(path3)); - // Ask for non-existent path. - EXPECT_FALSE(file_system_.List(not_path, &names)); + std::vector names; + ASSERT_TRUE(file_system_.List(dir_path, &names)) << "dir_path = " << dir_path; - // Valid path, but no way to return names. - EXPECT_FALSE(file_system_.List(path_dir, nullptr)); + size_t expected_file_count = 3; + EXPECT_EQ(names.size(), expected_file_count); - // Valid path, valid return. - EXPECT_TRUE(file_system_.List(path_dir, &names)); - - // Should find three files. Order not important. - EXPECT_EQ(3u, names.size()); + // Should find the three files. Order not important. EXPECT_THAT(names, ::testing::UnorderedElementsAre( - kTestFileName, kTestFileName2, kTestFileName3)); + kTxtFilename1, kTxtFilename2, kBinFilename)); - std::string wild_card_path = path_dir + kWildcard + kTestFileNameExt; - EXPECT_TRUE(file_system_.Remove(wild_card_path)); - EXPECT_TRUE(file_system_.List(path_dir, &names)); + // Remove .txt files. + const std::string txt_wildcard_path = PathJoin(dir_path, "*.txt"); - EXPECT_EQ(1u, names.size()); - EXPECT_TRUE(names[0].compare(kTestFileName3) == 0); + EXPECT_TRUE(file_system_.Remove(txt_wildcard_path)) + << "txt_wildcard_path = " << txt_wildcard_path; + EXPECT_TRUE(file_system_.List(dir_path, &names)) << "dir_path = " << dir_path; - std::string wild_card_path2 = path_dir + kWildcard + kTestFileNameExt3; - EXPECT_TRUE(file_system_.Remove(wild_card_path2)); - EXPECT_TRUE(file_system_.List(path_dir, &names)); + expected_file_count = 1; + ASSERT_EQ(names.size(), expected_file_count); + EXPECT_EQ(names.front(), kBinFilename); - EXPECT_EQ(0u, names.size()); + const std::string bin_wildcard_path = PathJoin(dir_path, "*.bin"); + EXPECT_TRUE(file_system_.Remove(bin_wildcard_path)) + << "bin_wildcard_path = " << bin_wildcard_path; + + // All files should be removed, but listing should still succeed. + EXPECT_TRUE(file_system_.List(dir_path, &names)) << "dir_path = " << dir_path; + expected_file_count = 0; + EXPECT_EQ(expected_file_count, names.size()); } -TEST_F(FileTest, CreateGlobalCertificates) { - // Clear directory +TEST_F(FileTest, ListFiles_NotAPath) { + const std::string not_path("zzz/xxx"); std::vector names; - std::string path_dir = wvcdm::test_vectors::kTestDir; - std::string wild_card_path = path_dir + kWildcard; - file_system_.Remove(wild_card_path); - if (file_system_.List(path_dir, &names)) { - EXPECT_EQ(0u, names.size()); + // Ask for non-existent path. + EXPECT_FALSE(file_system_.List(not_path, &names)); +} + +TEST_F(FileTest, ListFiles_NullParameter) { + const std::string dir_path = wvcdm::test_vectors::kTestDir; + const std::string path = PathJoin(dir_path, kTestFilename); + + std::unique_ptr file = file_system_.Open(path, FileSystem::kCreate); + ASSERT_TRUE(file) << "Failed to create file: " << path; + file.reset(); // Close file. + + // Valid path, but no way to return names. + EXPECT_FALSE(file_system_.List(dir_path, nullptr)); +} + +// On certain platforms, the FileSystem may perform special +// name translations on certificate filenames which make them behave +// differently from non-certificate filenames. +TEST_F(FileTest, CreateGlobalCertificates) { + ASSERT_TRUE(file_system_.IsGlobal()) + << "Test case requires global file system"; + + const std::string dir_path = wvcdm::test_vectors::kTestDir; + + // Clear directory + const std::string all_file_wildcard_path = PathJoin(dir_path, "*"); + file_system_.Remove(all_file_wildcard_path); + // Ensure directory is empty. + std::vector names; + if (file_system_.List(dir_path, &names)) { + constexpr size_t kZero = 0; + ASSERT_EQ(kZero, names.size()) << "Test requires empty directory"; } - // Create certificates and verify that they exist - std::string certificate_path = - wvcdm::test_vectors::kTestDir + kCertificateFileName; - std::string legacy_certificate_path = - wvcdm::test_vectors::kTestDir + kLegacyCertificateFileName; + const std::string certificate_path = PathJoin(dir_path, kCertificateFileName); + const std::string legacy_certificate_path = + PathJoin(dir_path, kLegacyCertificateFileName); + // Create certificates and verify that they exist std::unique_ptr file = file_system_.Open(certificate_path, FileSystem::kCreate); - ASSERT_TRUE(file); + ASSERT_TRUE(file) << "Failed to create certificate file: " + << certificate_path; + file = file_system_.Open(legacy_certificate_path, FileSystem::kCreate); - ASSERT_TRUE(file); - EXPECT_TRUE(file_system_.IsGlobal()); + ASSERT_TRUE(file) << "Failed to create legacy certificate file: " + << legacy_certificate_path; + file.reset(); // Close file. - EXPECT_TRUE(file_system_.Exists(certificate_path)); - EXPECT_TRUE(file_system_.Exists(legacy_certificate_path)); + EXPECT_TRUE(file_system_.Exists(certificate_path)) + << "certificate_path = " << certificate_path; + EXPECT_TRUE(file_system_.Exists(legacy_certificate_path)) + << "legacy_certificate_path = " << legacy_certificate_path; - EXPECT_TRUE(file_system_.List(path_dir, &names)); + ASSERT_TRUE(file_system_.List(dir_path, &names)); // Should find two files. Order not important. - EXPECT_EQ(2u, names.size()); + constexpr size_t kExpectedCount = 2; + EXPECT_EQ(kExpectedCount, names.size()); EXPECT_THAT(names, ::testing::UnorderedElementsAre( kCertificateFileName, kLegacyCertificateFileName)); } +// On certain platforms, the FileSystem may perform special +// name translations on certificate filenames which make them behave +// differently from non-certificate filenames. TEST_F(FileTest, CreateCertificates) { + ASSERT_TRUE(file_system_.IsGlobal()) + << "Test case requires starting with a global file system"; + const std::string dir_path = wvcdm::test_vectors::kTestDir; + // Clear directory + const std::string all_file_wildcard_path = PathJoin(dir_path, "*"); + file_system_.Remove(all_file_wildcard_path); + // Ensure directory is empty. std::vector names; - std::string path_dir = wvcdm::test_vectors::kTestDir; - std::string wild_card_path = path_dir + kWildcard; - file_system_.Remove(wild_card_path); - if (file_system_.List(path_dir, &names)) { - EXPECT_EQ(0u, names.size()); + if (file_system_.List(dir_path, &names)) { + constexpr size_t kZero = 0; + ASSERT_EQ(kZero, names.size()) << "Test requires empty directory"; } - std::string certificate_path = - wvcdm::test_vectors::kTestDir + kCertificateFileName; - std::string legacy_certificate_path = - wvcdm::test_vectors::kTestDir + kLegacyCertificateFileName; + const std::string certificate_path = PathJoin(dir_path, kCertificateFileName); + const std::string legacy_certificate_path = + PathJoin(dir_path, kLegacyCertificateFileName); - // Create Global certificates + // Create Global certificates. std::unique_ptr file = file_system_.Open(certificate_path, FileSystem::kCreate); - ASSERT_TRUE(file); + ASSERT_TRUE(file) << "Failed to create global certificate: " + << certificate_path; file = file_system_.Open(legacy_certificate_path, FileSystem::kCreate); - ASSERT_TRUE(file); - EXPECT_TRUE(file_system_.IsGlobal()); + ASSERT_TRUE(file) << "Failed to create global legacy certificate: " + << legacy_certificate_path; + file.reset(); // Close file. + + EXPECT_TRUE(file_system_.Exists(certificate_path)) + << "Global certificate: " << certificate_path; + EXPECT_TRUE(file_system_.Exists(legacy_certificate_path)) + << "Global legacy certificate: " << legacy_certificate_path; + + // Switch to first identifier. + file_system_.set_identifier(kTestIdentifier1); + ASSERT_FALSE(file_system_.IsGlobal()) << "identifier = " << kTestIdentifier1; + + // Global certificates should not be visible once identifier has been + // specified. + EXPECT_FALSE(file_system_.Exists(certificate_path)) + << kTestIdentifier1 << " certificate: " << certificate_path; + EXPECT_FALSE(file_system_.Exists(legacy_certificate_path)) + << kTestIdentifier1 << " legacy certificate: " << legacy_certificate_path; // Create certificates with first identifier - file_system_.set_identifier(kTestIdentifier1); file = file_system_.Open(certificate_path, FileSystem::kCreate); - ASSERT_TRUE(file); + ASSERT_TRUE(file) << "Failed to create " << kTestIdentifier1 + << " certificate: " << certificate_path; file = file_system_.Open(legacy_certificate_path, FileSystem::kCreate); - ASSERT_TRUE(file); - EXPECT_TRUE(!file_system_.IsGlobal()); + ASSERT_TRUE(file) << "Failed to create " << kTestIdentifier1 + << " legacy certificate: " << legacy_certificate_path; + file.reset(); // Close file. + + // Verify they now exist. + EXPECT_TRUE(file_system_.Exists(certificate_path)) + << kTestIdentifier1 << " certificate: " << certificate_path; + EXPECT_TRUE(file_system_.Exists(legacy_certificate_path)) + << kTestIdentifier1 << " legacy certificate: " << legacy_certificate_path; + + // Switch to second identifier. + file_system_.set_identifier(kTestIdentifier2); + ASSERT_FALSE(file_system_.IsGlobal()) << "identifier = " << kTestIdentifier2; + + // Global and first identifier certificates should not be + // visible. + EXPECT_FALSE(file_system_.Exists(certificate_path)) + << kTestIdentifier2 << " certificate: " << certificate_path; + EXPECT_FALSE(file_system_.Exists(legacy_certificate_path)) + << kTestIdentifier2 << " legacy certificate: " << legacy_certificate_path; // Create certificates with second identifier - file_system_.set_identifier(kTestIdentifier2); file = file_system_.Open(certificate_path, FileSystem::kCreate); - ASSERT_TRUE(file); + ASSERT_TRUE(file) << "Failed to create " << kTestIdentifier2 + << " certificate: " << certificate_path; file = file_system_.Open(legacy_certificate_path, FileSystem::kCreate); - ASSERT_TRUE(file); - EXPECT_TRUE(!file_system_.IsGlobal()); + ASSERT_TRUE(file) << "Failed to create " << kTestIdentifier2 + << " legacy certificate: " << legacy_certificate_path; + file.reset(); // Close file. - EXPECT_TRUE(file_system_.Exists(certificate_path)); - EXPECT_TRUE(file_system_.Exists(legacy_certificate_path)); + // Verify they now exist. + EXPECT_TRUE(file_system_.Exists(certificate_path)) + << kTestIdentifier2 << " certificate: " << certificate_path; + EXPECT_TRUE(file_system_.Exists(legacy_certificate_path)) + << kTestIdentifier2 << " legacy certificate: " << legacy_certificate_path; - EXPECT_TRUE(file_system_.List(path_dir, &names)); + // FileSystem::List is expected to still return all certificate files + // (both global and scoped). + ASSERT_TRUE(file_system_.List(dir_path, &names)) << "dir_path = " << dir_path; // Should find six files. Order not important. + constexpr size_t kExpectedTotalCertCount = 6; + ASSERT_EQ(names.size(), kExpectedTotalCertCount); + bool is_global_certificate_present = false; bool is_global_legacy_certificate_present = false; size_t certificate_count = 0; size_t legacy_certificate_count = 0; - EXPECT_EQ(6u, names.size()); - for (size_t i = 0; i < names.size(); ++i) { - if (names[i].size() > kCertificateFileName.size()) { - if (names[i].compare(0, kCertificateFileNamePrefix.size(), - kCertificateFileNamePrefix) == 0) - ++certificate_count; - else if (names[i].compare(0, kLegacyCertificateFileNamePrefix.size(), - kLegacyCertificateFileNamePrefix) == 0) - ++legacy_certificate_count; - } else if (names[i].compare(kCertificateFileName) == 0) { - is_global_certificate_present = true; - } else if (names[i].compare(kLegacyCertificateFileName) == 0) { + for (const auto& filename : names) { + if (filename == kLegacyCertificateFileName) { is_global_legacy_certificate_present = true; + } else if (filename == kCertificateFileName) { + is_global_certificate_present = true; + } else if (StartsWith(filename, kScopedCertificateFilenamePrefix)) { + certificate_count++; + } else if (StartsWith(filename, kLegacyScopedCertificateFilenamePrefix)) { + legacy_certificate_count++; } else { - EXPECT_TRUE(false); + ADD_FAILURE() << "Unexpected filename: " << filename; } } - EXPECT_EQ(2, certificate_count); - EXPECT_EQ(2, legacy_certificate_count); - EXPECT_TRUE(is_global_certificate_present); - EXPECT_TRUE(is_global_legacy_certificate_present); + constexpr size_t kExpectedScopedCertCount = 2; + EXPECT_EQ(certificate_count, kExpectedScopedCertCount) + << "Missing certificates"; + EXPECT_EQ(legacy_certificate_count, kExpectedScopedCertCount) + << "Missing legacy certificates"; + EXPECT_TRUE(is_global_certificate_present) + << "Missing global certificate: " << kCertificateFileName; + EXPECT_TRUE(is_global_legacy_certificate_present) + << "Missing legacy global certificate: " << kLegacyCertificateFileName; } +// On certain platforms, the FileSystem may perform special +// name translations on certificate file names which make them behave +// differently from non-certificate file names. TEST_F(FileTest, RemoveCertificates) { + ASSERT_TRUE(file_system_.IsGlobal()) + << "Test case requires starting with a global file system"; + const std::string dir_path = wvcdm::test_vectors::kTestDir; + // Clear directory + const std::string all_file_wildcard_path = PathJoin(dir_path, "*"); + file_system_.Remove(all_file_wildcard_path); + // Ensure directory is empty. std::vector names; - std::string path_dir = wvcdm::test_vectors::kTestDir; - std::string wild_card_path = path_dir + kWildcard; - file_system_.Remove(wild_card_path); - if (file_system_.List(path_dir, &names)) { - EXPECT_EQ(0u, names.size()); + if (file_system_.List(dir_path, &names)) { + constexpr size_t kZero = 0; + ASSERT_EQ(kZero, names.size()) << "Test requires empty directory"; } - std::string certificate_path = - wvcdm::test_vectors::kTestDir + kCertificateFileName; - std::string legacy_certificate_path = - wvcdm::test_vectors::kTestDir + kLegacyCertificateFileName; + const std::string certificate_path = PathJoin(dir_path, kCertificateFileName); + const std::string legacy_certificate_path = + PathJoin(dir_path, kLegacyCertificateFileName); - // Create Global certificates + // Create Global certificates. std::unique_ptr file = file_system_.Open(certificate_path, FileSystem::kCreate); - ASSERT_TRUE(file); + ASSERT_TRUE(file) << "Failed to create global certificate: " + << certificate_path; file = file_system_.Open(legacy_certificate_path, FileSystem::kCreate); - ASSERT_TRUE(file); - EXPECT_TRUE(file_system_.IsGlobal()); + ASSERT_TRUE(file) << "Failed to create global legacy certificate: " + << legacy_certificate_path; + file.reset(); // Close file. + + // Switch to first identifier. + file_system_.set_identifier(kTestIdentifier1); + ASSERT_FALSE(file_system_.IsGlobal()) << "identifier = " << kTestIdentifier1; // Create certificates with first identifier - file_system_.set_identifier(kTestIdentifier1); file = file_system_.Open(certificate_path, FileSystem::kCreate); - ASSERT_TRUE(file); + ASSERT_TRUE(file) << "Failed to create " << kTestIdentifier1 + << " certificate: " << certificate_path; file = file_system_.Open(legacy_certificate_path, FileSystem::kCreate); - ASSERT_TRUE(file); - EXPECT_TRUE(!file_system_.IsGlobal()); + ASSERT_TRUE(file) << "Failed to create " << kTestIdentifier1 + << " legacy certificate: " << legacy_certificate_path; + file.reset(); // Close file. + + // Switch to second identifier. + file_system_.set_identifier(kTestIdentifier2); + ASSERT_FALSE(file_system_.IsGlobal()) << "identifier = " << kTestIdentifier2; // Create certificates with second identifier - file_system_.set_identifier(kTestIdentifier2); file = file_system_.Open(certificate_path, FileSystem::kCreate); - ASSERT_TRUE(file); + ASSERT_TRUE(file) << "Failed to create " << kTestIdentifier2 + << " certificate: " << certificate_path; file = file_system_.Open(legacy_certificate_path, FileSystem::kCreate); - ASSERT_TRUE(file); - EXPECT_TRUE(!file_system_.IsGlobal()); + ASSERT_TRUE(file) << "Failed to create " << kTestIdentifier2 + << " legacy certificate: " << legacy_certificate_path; + file.reset(); // Close file. - EXPECT_TRUE(file_system_.Exists(certificate_path)); - EXPECT_TRUE(file_system_.Exists(legacy_certificate_path)); - - EXPECT_TRUE(file_system_.List(path_dir, &names)); - - EXPECT_EQ(6u, names.size()); + // FileSystem::List is expected to still return all certificate files + // (both global and scoped). + ASSERT_TRUE(file_system_.List(dir_path, &names)) << "dir_path = " << dir_path; + // Should find six files. Order not important. + constexpr size_t kExpectedTotalCertCount = 6; + ASSERT_EQ(names.size(), kExpectedTotalCertCount); + std::set removed_certs; // Remove all even number listed files for (size_t i = 0; i < names.size(); ++i) { - if (i % 2 == 0) { - EXPECT_TRUE( - file_system_.Remove(wvcdm::test_vectors::kTestDir + names[i])); - } + if ((i % 2) != 0) continue; + const std::string& cert_filename = names[i]; + const std::string cert_path = PathJoin(dir_path, cert_filename); + ASSERT_TRUE(file_system_.Remove(cert_path)) + << "Failed to remove cert: " << cert_path; + removed_certs.insert(cert_filename); } // Verify that they have been removed - for (size_t i = 0; i < names.size(); ++i) { - if (i % 2 == 1) { - EXPECT_TRUE( - file_system_.Exists(wvcdm::test_vectors::kTestDir + names[i])); + for (const std::string& cert_filename : names) { + const std::string cert_path = PathJoin(dir_path, cert_filename); + if (removed_certs.find(cert_filename) == removed_certs.end()) { + // Ensure still exists. + ASSERT_TRUE(file_system_.Exists(cert_path)) + << "Cert missing: " << cert_filename; } else { - EXPECT_FALSE( - file_system_.Exists(wvcdm::test_vectors::kTestDir + names[i])); + ASSERT_FALSE(file_system_.Exists(cert_path)) + << "Cert not removed: " << cert_filename; } } - // Remove all odd number listed files - for (size_t i = 0; i < names.size(); ++i) { - if (i % 2 == 1) { - EXPECT_TRUE( - file_system_.Remove(wvcdm::test_vectors::kTestDir + names[i])); - } + // Remove all remaining. + for (const std::string& cert_filename : names) { + if (removed_certs.find(cert_filename) != removed_certs.end()) continue; + const std::string cert_path = PathJoin(dir_path, cert_filename); + ASSERT_TRUE(file_system_.Remove(cert_path)) + << "Failed to remove cert: " << cert_path; } // Verify that all have been removed - for (size_t i = 0; i < names.size(); ++i) { - EXPECT_FALSE(file_system_.Exists(wvcdm::test_vectors::kTestDir + names[i])); + for (const std::string& cert_filename : names) { + const std::string cert_path = PathJoin(dir_path, cert_filename); + EXPECT_FALSE(file_system_.Exists(cert_path)) + << "Cert not removed: " << cert_filename; } } - } // namespace wvutil diff --git a/util/test/test_clock.cpp b/util/test/test_clock.cpp index 33edd9c3..61fec1f7 100644 --- a/util/test/test_clock.cpp +++ b/util/test/test_clock.cpp @@ -20,9 +20,9 @@ class FakeClock : public TestSleep::CallBack { FakeClock() { auto now = std::chrono::system_clock().now(); now_ = now.time_since_epoch() / std::chrono::milliseconds(1); - TestSleep::set_callback(this); + TestSleep::AddCallback(this); } - ~FakeClock() { TestSleep::set_callback(nullptr); } + ~FakeClock() { TestSleep::RemoveCallback(this); } void ElapseTime(int64_t milliseconds) { now_ += milliseconds; } int64_t now() const { return now_; } diff --git a/util/test/test_sleep.cpp b/util/test/test_sleep.cpp index f52e5715..300461c1 100644 --- a/util/test/test_sleep.cpp +++ b/util/test/test_sleep.cpp @@ -26,7 +26,7 @@ namespace wvutil { bool TestSleep::real_sleep_ = true; -TestSleep::CallBack* TestSleep::callback_ = nullptr; +std::unordered_set TestSleep::callbacks_; int TestSleep::total_clock_rollback_seconds_ = 0; void TestSleep::Sleep(unsigned int seconds) { @@ -52,7 +52,7 @@ void TestSleep::Sleep(unsigned int seconds) { milliseconds = total_real - fake_clock; fake_clock += milliseconds; } - if (callback_ != nullptr) callback_->ElapseTime(milliseconds); + for (auto* cb : callbacks_) cb->ElapseTime(milliseconds); } void TestSleep::SleepUntil(int64_t desired_time) { @@ -80,9 +80,8 @@ void TestSleep::SetFakeClock(int64_t time_seconds) { // by the current time on a real clock, and then the command line // re-initializes it to 0, then delta is negative. int64_t delta = time_seconds - Clock().GetCurrentTime(); - if (callback_ != nullptr) { - callback_->ElapseTime(delta * 1000); - } else { + for (auto* cb : callbacks_) cb->ElapseTime(delta * 1000); + if (callbacks_.empty()) { LOGE("Setting fake clock with no callback. This won't work."); } } @@ -130,8 +129,8 @@ bool TestSleep::RollbackSystemTime(int seconds) { // For both real and fake sleep we still update the callback and we still keep // track of the total amount of time slept. total_clock_rollback_seconds_ += seconds; - if (callback_ != nullptr) { - callback_->ElapseTime(-1000 * static_cast(seconds)); + for (auto* cb : callbacks_) { + cb->ElapseTime(-1000 * static_cast(seconds)); } return true; } diff --git a/util/test/test_sleep.h b/util/test/test_sleep.h index f20e27a7..2c0ebdd4 100644 --- a/util/test/test_sleep.h +++ b/util/test/test_sleep.h @@ -9,6 +9,8 @@ #include +#include + namespace wvutil { class TestSleep { @@ -67,13 +69,18 @@ class TestSleep { static bool real_sleep() { return real_sleep_; } // The callback is notified whenever sleep is called. - static void set_callback(CallBack* callback) { callback_ = callback; } + static void AddCallback(CallBack* callback) { + callbacks_.insert(callback); + } + static void RemoveCallback(CallBack* callback) { + callbacks_.erase(callback); + } private: // Controls if the test sleep should use real sleep. static bool real_sleep_; // Called when the clock should advance. - static CallBack* callback_; + static std::unordered_set callbacks_; // The sum of all calls to RollBackSystemTime. Kept so we can undo all changes // at the end of a test. static int total_clock_rollback_seconds_;