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 00000000..eee3844c Binary files /dev/null and b/Widevine_Open_Source_License_Terms.pdf differ diff --git a/cdm/cdm_reboot_tests.gyp b/cdm/cdm_reboot_tests.gyp index 842171bc..97d66e3e 100644 --- a/cdm/cdm_reboot_tests.gyp +++ b/cdm/cdm_reboot_tests.gyp @@ -92,6 +92,7 @@ ], 'xcode_settings': { 'BUNDLE_LOADER': '$(TEST_HOST)', + 'INFOPLIST_FILE': 'test/info.plist', 'TEST_HOST': '<(PRODUCT_DIR)/dummy_app.app/dummy_app', 'WRAPPER_EXTENSION': 'xctest', }, diff --git a/cdm/cdm_unittests.gyp b/cdm/cdm_unittests.gyp index d8a752fc..33aec98f 100644 --- a/cdm/cdm_unittests.gyp +++ b/cdm/cdm_unittests.gyp @@ -200,7 +200,7 @@ ], }], 'conditions': [ - ['supports_dynamic_perf_test', { + ['supports_dynamic_perf_test and prebuilt_cdm_path!=""', { 'targets': [{ 'target_name': 'widevine_perf_test_xctest', 'type': 'loadable_module', diff --git a/cdm/include/cdm_version.h b/cdm/include/cdm_version.h index ed280574..9ff170bd 100644 --- a/cdm/include/cdm_version.h +++ b/cdm/include/cdm_version.h @@ -10,7 +10,7 @@ # define CDM_VERSION_MAJOR 19 #endif #ifndef CDM_VERSION_MINOR -# define CDM_VERSION_MINOR 3 +# define CDM_VERSION_MINOR 4 #endif #ifndef CDM_VERSION_PATCH # define CDM_VERSION_PATCH 0 diff --git a/cdm/test/cdm_test.cpp b/cdm/test/cdm_test.cpp index ca7c92b7..9e4130ab 100644 --- a/cdm/test/cdm_test.cpp +++ b/cdm/test/cdm_test.cpp @@ -53,6 +53,18 @@ using video_widevine::SignedMessage; namespace { +const std::string kBuildInfoFields[] = { + "platform_version", + "platform", + "form_factor", + "arch_name", + "cdm_version", + "compiler_detected_arch_name", + "logging_enabled_message", + "build_flavor", +}; +const std::string kBuildInfoDelimiter = " | "; + const int kHttpOk = 200; const int kRenewalTestDelayMs = 3 * 60 * 1000; @@ -526,6 +538,10 @@ class CdmTest : public WvCdmTestBase, public Cdm::IEventListener { return Cdm::kQuotaExceeded; } + void RecordWvProperty(const std::string& key, const std::string& value) { + RecordProperty("widevine_metadata_cdm_" + key, value); + } + std::unique_ptr 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_;