From d5157c536dc7e0631efd0c0c8faebbe818186741 Mon Sep 17 00:00:00 2001 From: Vicky Min Date: Tue, 5 Dec 2023 01:36:58 +0000 Subject: [PATCH] OEMCrypto unit tests for license release Bug: 295956275 Change-Id: I3c8fc5fcadeae051cc734a64378e473492437c34 --- .../core/src/oemcrypto_adapter_dynamic.cpp | 26 +++++ .../oemcrypto/include/OEMCryptoCENC.h | 48 +++++++++ libwvdrmengine/oemcrypto/include/level3.h | 7 ++ .../oemcrypto/test/oec_session_util.cpp | 98 +++++++++++++++---- .../oemcrypto/test/oec_session_util.h | 42 ++++++++ .../oemcrypto/test/oemcrypto_license_test.h | 11 +++ .../test/oemcrypto_usage_table_test.cpp | 22 +++++ 7 files changed, 233 insertions(+), 21 deletions(-) diff --git a/libwvdrmengine/cdm/core/src/oemcrypto_adapter_dynamic.cpp b/libwvdrmengine/cdm/core/src/oemcrypto_adapter_dynamic.cpp index 692ce811..af9929ba 100644 --- a/libwvdrmengine/cdm/core/src/oemcrypto_adapter_dynamic.cpp +++ b/libwvdrmengine/cdm/core/src/oemcrypto_adapter_dynamic.cpp @@ -105,6 +105,12 @@ typedef OEMCryptoResult (*L1_LoadRenewal_t)(OEMCrypto_SESSION session, size_t core_message_length, const uint8_t* signature, size_t signature_length); +typedef OEMCryptoResult (*L1_LoadRelease_t)(OEMCrypto_SESSION session, + const uint8_t* message, + size_t message_length, + size_t core_message_length, + const uint8_t* signature, + size_t signature_length); typedef OEMCryptoResult (*L1_QueryKeyControl_t)( OEMCrypto_SESSION session, const uint8_t* key_id, size_t key_id_length, uint8_t* key_control_block, size_t* key_control_block_length); @@ -365,6 +371,7 @@ struct FunctionPointers { L1_LoadEntitledContentKeys_t LoadEntitledContentKeys; L1_LoadEntitledContentKeys_V16_t LoadEntitledContentKeys_V16; L1_LoadRenewal_t LoadRenewal; + L1_LoadRelease_t LoadRelease; L1_QueryKeyControl_t QueryKeyControl; L1_SelectKey_t SelectKey; L1_DecryptCENC_V17_t DecryptCENC_V17; @@ -1078,6 +1085,7 @@ class Adapter { LOOKUP_ALL(18, EnterTestMode, OEMCrypto_EnterTestMode); LOOKUP_ALL(19, SetDecryptHash, OEMCrypto_SetDecryptHash); + LOOKUP_ALL(19, LoadRelease, OEMCrypto_LoadRelease); // clang-format on @@ -1119,6 +1127,7 @@ class Adapter { level3_.LoadLicense_V18 = Level3_LoadLicense; level3_.LoadEntitledContentKeys = Level3_LoadEntitledContentKeys; level3_.LoadRenewal = Level3_LoadRenewal; + level3_.LoadRelease = Leve3_LoadRelease; level3_.QueryKeyControl = Level3_QueryKeyControl; level3_.SelectKey = Level3_SelectKey; level3_.DecryptCENC_V17 = Level3_DecryptCENC_V17; @@ -2268,6 +2277,23 @@ extern "C" OEMCryptoResult OEMCrypto_LoadRenewal(OEMCrypto_SESSION session, signature_length); } +extern "C" OEMCryptoResult OEMCrypto_LoadRelease(OEMCrypto_SESSION session, + const uint8_t* message, + size_t message_length, + size_t core_message_length, + const uint8_t* signature, + size_t signature_length) { + if (!gAdapter) return OEMCrypto_ERROR_UNKNOWN_FAILURE; + LevelSession pair = gAdapter->GetSession(session); + if (!pair.fcn) return OEMCrypto_ERROR_INVALID_SESSION; + if (pair.fcn->LoadRelease == nullptr) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; + } + return pair.fcn->LoadRelease(pair.session, message, message_length, + core_message_length, signature, + signature_length); +} + extern "C" OEMCryptoResult OEMCrypto_QueryKeyControl( OEMCrypto_SESSION session, const uint8_t* key_id, size_t key_id_length, uint8_t* key_control_block, size_t* key_control_block_length) { diff --git a/libwvdrmengine/oemcrypto/include/OEMCryptoCENC.h b/libwvdrmengine/oemcrypto/include/OEMCryptoCENC.h index 46d1649d..c4f3b884 100644 --- a/libwvdrmengine/oemcrypto/include/OEMCryptoCENC.h +++ b/libwvdrmengine/oemcrypto/include/OEMCryptoCENC.h @@ -732,6 +732,7 @@ typedef enum OEMCrypto_SignatureHashAlgorithm { #define OEMCrypto_PrepAndSignReleaseRequest _oecc147 #define OEMCrypto_GetUsageEntryInfo _oecc148 #define OEMCrypto_GetBCCType _oecc149 +#define OEMCrypto_LoadRelease _oecc150 // clang-format on /// @addtogroup initcontrol @@ -1582,6 +1583,53 @@ OEMCryptoResult OEMCrypto_LoadRenewal(OEMCrypto_SESSION session, const uint8_t* signature, size_t signature_length); +/** + * If the signature passes, OEMCrypto shall use the function + * ODK_ParseRelease, as described in the document "Widevine Core Message + * Serialization" to parse and verify the message. If ODK_ParseRelease + * returns an error, OEMCrypto returns the error to the CDM layer. + * + * NOTE: OEMCrypto_LoadLicense() must be called first to load the keys into + * the session. + * + * @verification + * The signature of the message shall be computed using mac_key[server], and + * the API shall verify the computed signature matches the signature passed + * in. If not, return OEMCrypto_ERROR_SIGNATURE_FAILURE. The signature + * verification shall use a constant-time algorithm (a signature mismatch + * will always take the same time as a successful comparison). + * + * @param[in] session: handle for the session to be used. + * @param[in] message: pointer to memory containing message to be verified. + * @param[in] message_length: length of the message, in bytes. + * @param[in] core_message_length: length of the core submessage, in bytes. + * @param[in] signature: pointer to memory containing the signature. + * @param[in] signature_length: length of the signature, in bytes. + * + * @retval OEMCrypto_SUCCESS success + * @retval OEMCrypto_ERROR_INVALID_SESSION + * @retval OEMCrypto_ERROR_INVALID_CONTEXT + * @retval OEMCrypto_ERROR_UNKNOWN_FAILURE + * @retval OEMCrypto_ERROR_BUFFER_TOO_LARGE + * @retval OEMCrypto_ERROR_SIGNATURE_FAILURE + * + * @threading + * This is a "Session Function" and may be called simultaneously with session + * functions for other sessions but not simultaneously with other functions + * for this session. It will not be called simultaneously with initialization + * or usage table functions. It is as if the CDM holds a write lock for this + * session, and a read lock on the OEMCrypto system. + * + * @version + * This method is new in API version 19. + */ +OEMCryptoResult OEMCrypto_LoadRelease(OEMCrypto_SESSION session, + const uint8_t* message, + size_t message_length, + size_t core_message_length, + const uint8_t* signature, + size_t signature_length); + /** * Returns the decrypted key control block for the given content_key_id. This * function is for application developers to debug license server and key diff --git a/libwvdrmengine/oemcrypto/include/level3.h b/libwvdrmengine/oemcrypto/include/level3.h index ecc99fe4..08789c83 100644 --- a/libwvdrmengine/oemcrypto/include/level3.h +++ b/libwvdrmengine/oemcrypto/include/level3.h @@ -121,6 +121,9 @@ namespace wvoec3 { #define Level3_EnterTestMode _lcc140 #define Level3_GetDeviceSignedCsrPayload _lcc141 #define Level3_SetDecryptHash _lcc143 +#define Level3_PrepAndSignReleaseRequest _lcc147 +#define Level3_GetUsageEntryInfo _lcc148 +#define Level3_LoadRelease _lcc150 #else #define Level3_Initialize _oecc01 #define Level3_Terminate _oecc02 @@ -223,6 +226,10 @@ namespace wvoec3 { #define Level3_EnterTestMode _oecc140 #define Level3_GetDeviceSignedCsrPayload _oecc141 #define Level3_SetDecryptHash _oecc143 +#define Level3_PrepAndSignReleaseRequest _oecc147 +#define Level3_GetUsageEntryInfo _oecc148 +#define Level3_LoadRelease _oecc150 + #endif #define Level3_GetInitializationState _oecl3o01 diff --git a/libwvdrmengine/oemcrypto/test/oec_session_util.cpp b/libwvdrmengine/oemcrypto/test/oec_session_util.cpp index 5620e94a..4a92297f 100644 --- a/libwvdrmengine/oemcrypto/test/oec_session_util.cpp +++ b/libwvdrmengine/oemcrypto/test/oec_session_util.cpp @@ -1513,27 +1513,9 @@ void RenewalRoundTrip::FillAndVerifyCoreRequest( } } -void RenewalRoundTrip::CreateDefaultResponse() { - if (is_release_) { - uint32_t control = 0; - uint32_t nonce = 0; - // A single key object with no key id should update all keys. - constexpr size_t index = 0; - response_data_.keys[index].key_id_length = 0; - response_data_.keys[index].key_id[0] = '\0'; - const uint32_t renewal_api = - std::max(core_request_.api_major_version, 15u); - std::string kcVersion = "kc" + std::to_string(renewal_api); - memcpy(response_data_.keys[index].control.verification, kcVersion.c_str(), - 4); - const uint32_t duration = static_cast( - license_messages_->core_response() - .timer_limits.initial_renewal_duration_seconds); - response_data_.keys[index].control.duration = htonl(duration); - response_data_.keys[index].control.nonce = htonl(nonce); - response_data_.keys[index].control.control_bits = htonl(control); - } -} +// Nothing is needed for this function but it needs a definition since it's +// declared as a virtual function in the RoundTrip class. +void RenewalRoundTrip::CreateDefaultResponse() {} void RenewalRoundTrip::EncryptAndSignResponse() { // Renewal messages are not encrypted. @@ -1628,6 +1610,80 @@ OEMCryptoResult RenewalRoundTrip::LoadResponse(Session* session) { response_signature_.data(), response_signature_.size()); } +void ReleaseRoundTrip::VerifyRequestSignature( + const vector& data, const vector& generated_signature, + size_t core_message_length) { + ASSERT_EQ(HMAC_SHA256_SIGNATURE_SIZE, generated_signature.size()); + std::vector expected_signature; + session()->key_deriver().ClientSignBuffer(data, &expected_signature); + ASSERT_EQ(expected_signature, generated_signature); +} + +void ReleaseRoundTrip::FillAndVerifyCoreRequest( + const std::string& core_message_string) { + EXPECT_TRUE( + oemcrypto_core_message::deserialize::CoreReleaseRequestFromMessage( + core_message_string, &core_request_)); + EXPECT_EQ(license_messages_->api_version(), core_request_.api_major_version); + EXPECT_EQ(license_messages_->core_request().nonce, core_request_.nonce); + EXPECT_EQ(license_messages_->core_request().session_id, + core_request_.session_id); +} + +// Nothing is needed for this function but it needs a definition since it's +// declared as a virtual function in the RoundTrip class. +void ReleaseRoundTrip::CreateDefaultResponse() {} + +void ReleaseRoundTrip::EncryptAndSignResponse() { + // Release messages are not encrypted. + encrypted_response_data_ = response_data_; + // Create a core response for a call to LoadRelease. + // TODO(b/191724203): Test release server has different version from license + // server. + ASSERT_NE(license_messages_, nullptr); + CoreMessageFeatures features = + CoreMessageFeatures::DefaultFeatures(license_messages_->api_version()); + ASSERT_TRUE(oemcrypto_core_message::serialize::CreateCoreReleaseResponse( + features, core_request_, seconds_since_license_received_, + seconds_since_first_decrypt_, &serialized_core_message_)); + // Resize serialize core message to be just big enough or required core + // message size, whichever is larger. + serialized_core_message_.resize( + std::max(required_core_message_size_, serialized_core_message_.size())); + // Make the message buffer a just big enough, or the + // required size, whichever is larger. + const size_t message_size = + std::max(required_message_size_, serialized_core_message_.size() + + sizeof(encrypted_response_data_)); + // Stripe the encrypted message. + encrypted_response_.resize(message_size); + for (size_t i = 0; i < encrypted_response_.size(); i++) { + encrypted_response_[i] = i % 0x100; + } + // Concatenate the core message and the response. + ASSERT_GE(encrypted_response_.size(), serialized_core_message_.size()); + memcpy(encrypted_response_.data(), serialized_core_message_.data(), + serialized_core_message_.size()); + ASSERT_GE(encrypted_response_.size(), + serialized_core_message_.size() + sizeof(encrypted_response_data_)); + memcpy(encrypted_response_.data() + serialized_core_message_.size(), + reinterpret_cast(&encrypted_response_data_), + sizeof(encrypted_response_data_)); + session()->key_deriver().ServerSignBuffer(encrypted_response_.data(), + encrypted_response_.size(), + &response_signature_); + SetEncryptAndSignResponseLengths(); +} + +OEMCryptoResult ReleaseRoundTrip::LoadResponse(Session* session) { + // TODO(vickymin): Write corpus for oemcrypto_load_release_fuzz. + VerifyEncryptAndSignResponseLengths(); + return OEMCrypto_LoadRelease( + session->session_id(), encrypted_response_.data(), + encrypted_response_.size(), serialized_core_message_.size(), + response_signature_.data(), response_signature_.size()); +} + std::unordered_map, std::hash> Session::server_ephemeral_keys_; diff --git a/libwvdrmengine/oemcrypto/test/oec_session_util.h b/libwvdrmengine/oemcrypto/test/oec_session_util.h index 9025ef96..55370370 100644 --- a/libwvdrmengine/oemcrypto/test/oec_session_util.h +++ b/libwvdrmengine/oemcrypto/test/oec_session_util.h @@ -609,6 +609,48 @@ class RenewalRoundTrip bool is_release_; // If this is a license release, and not a real renewal. }; +class ReleaseRoundTrip + : public RoundTrip< + /* CoreRequest */ oemcrypto_core_message::ODK_ReleaseRequest, + OEMCrypto_PrepAndSignReleaseRequest, + // Release response info is same as request: + /* CoreResponse */ oemcrypto_core_message::ODK_ReleaseRequest, + /* ResponseData */ MessageData> { + public: + ReleaseRoundTrip(LicenseRoundTrip* license_messages) + : RoundTrip(license_messages->session()), + license_messages_(license_messages) {} + void CreateDefaultResponse() override; + void EncryptAndSignResponse() override; + OEMCryptoResult LoadResponse() override { return LoadResponse(session_); } + OEMCryptoResult LoadResponse(Session* session) override; + int64_t seconds_since_license_received() const { + return seconds_since_license_received_; + } + void set_seconds_since_license_received( + int64_t seconds_since_license_received) { + seconds_since_license_received_ = seconds_since_license_received; + } + int64_t seconds_since_first_decrypt() const { + return seconds_since_first_decrypt_; + } + void set_seconds_since_first_decrypt(int64_t seconds_since_first_decrypt) { + seconds_since_first_decrypt_ = seconds_since_first_decrypt; + } + + protected: + bool RequestHasNonce() override { return false; } + void VerifyRequestSignature(const vector& data, + const vector& generated_signature, + size_t core_message_length) override; + // Verify the values of the core response. + virtual void FillAndVerifyCoreRequest( + const std::string& core_message_string) override; + LicenseRoundTrip* license_messages_; + int64_t seconds_since_license_received_; + int64_t seconds_since_first_decrypt_; +}; + class EntitledMessage { public: EntitledMessage(LicenseRoundTrip* license_messages) diff --git a/libwvdrmengine/oemcrypto/test/oemcrypto_license_test.h b/libwvdrmengine/oemcrypto/test/oemcrypto_license_test.h index 6e185c32..e80c22cb 100644 --- a/libwvdrmengine/oemcrypto/test/oemcrypto_license_test.h +++ b/libwvdrmengine/oemcrypto/test/oemcrypto_license_test.h @@ -400,6 +400,17 @@ class OEMCryptoRefreshTest : public OEMCryptoLicenseTest { ASSERT_EQ(expected_result, renewal_messages->LoadResponse()); } + void MakeReleaseRequest(ReleaseRoundTrip* release_messages) { + ASSERT_NO_FATAL_FAILURE(release_messages->SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(release_messages->CreateDefaultResponse()); + } + + void LoadRelease(ReleaseRoundTrip* release_messages, + OEMCryptoResult expected_result) { + ASSERT_NO_FATAL_FAILURE(release_messages->EncryptAndSignResponse()); + ASSERT_EQ(expected_result, release_messages->LoadResponse()); + } + ODK_TimerLimits timer_limits_; }; diff --git a/libwvdrmengine/oemcrypto/test/oemcrypto_usage_table_test.cpp b/libwvdrmengine/oemcrypto/test/oemcrypto_usage_table_test.cpp index 658726b5..fc6bd176 100644 --- a/libwvdrmengine/oemcrypto/test/oemcrypto_usage_table_test.cpp +++ b/libwvdrmengine/oemcrypto/test/oemcrypto_usage_table_test.cpp @@ -651,6 +651,28 @@ TEST_P(OEMCryptoUsageTableTest, OfflineLicenseRefresh) { ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive)); } +// Test that an offline license can be loaded and that the license can be +// released +TEST_P(OEMCryptoUsageTableTest, OfflineLicenseReleaseAPI19) { + // License release is new in OEMCrypto v19. + if (wvoec::global_features.api_version < 19 || license_api_version_ < 19) { + GTEST_SKIP() << "Test for versions 19 and up only."; + } + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + entry.MakeAndLoad(this, wvoec::kControlNonceOrEntry); + Session& s = entry.session(); + + ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR()); + // License release message is signed by client and verified by the server. + ReleaseRoundTrip release_messages(&entry.license_messages()); + MakeReleaseRequest(&release_messages); + LoadRelease(&release_messages, OEMCrypto_SUCCESS); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE( + entry.TestDecryptCTR(true, OEMCrypto_ERROR_UNKNOWN_FAILURE)); +} + // Test that an offline license can be reloaded in a new session. TEST_P(OEMCryptoUsageTableTest, ReloadOfflineLicense) { LicenseWithUsageEntry entry;