From bea17d8b3bacb0536c747796d23e864a723978b7 Mon Sep 17 00:00:00 2001 From: Jacob Trimble Date: Tue, 29 Apr 2025 21:09:55 +0000 Subject: [PATCH] Add ODK query func and short-buffer for AEAD --- whitebox/MODULE.bazel | 9 +- whitebox/api/BUILD | 1 + whitebox/api/aead_whitebox.h | 7 +- whitebox/api/aead_whitebox_decrypt_test.cc | 5 +- whitebox/api/aead_whitebox_encrypt_test.cc | 20 +- whitebox/api/license_whitebox.h | 25 +- ...cense_whitebox_get_max_odk_version_test.cc | 29 ++ whitebox/api/license_whitebox_latest.h | 7 + whitebox/api/license_whitebox_wrapper.h | 360 ++++++++++++++++++ whitebox/chromium_deps/MODULE.bazel | 2 +- whitebox/crypto_utils/whitebox_init_helper.h | 41 +- whitebox/reference/impl/aead_whitebox_impl.cc | 71 ++-- .../reference/impl/license_whitebox_impl.cc | 24 +- whitebox/reference/impl/odk.cc | 4 + whitebox/reference/impl/odk_license_parser.cc | 8 + whitebox/reference/impl/odk_license_parser.h | 4 + 16 files changed, 534 insertions(+), 83 deletions(-) create mode 100644 whitebox/api/license_whitebox_get_max_odk_version_test.cc create mode 100644 whitebox/api/license_whitebox_wrapper.h diff --git a/whitebox/MODULE.bazel b/whitebox/MODULE.bazel index 77c492c..6b96404 100644 --- a/whitebox/MODULE.bazel +++ b/whitebox/MODULE.bazel @@ -2,12 +2,13 @@ module(name = "whitebox") -bazel_dep(name = "abseil-cpp", version = "20240722.0.bcr.2") -bazel_dep(name = "boringssl", version = "0.20241209.0") +# https://registry.bazel.build/ +bazel_dep(name = "abseil-cpp", version = "20250127.0") +bazel_dep(name = "boringssl", version = "0.20250212.0") bazel_dep(name = "gflags", version = "2.2.2") bazel_dep(name = "glog", version = "0.7.1") -bazel_dep(name = "googletest", version = "1.15.2") -bazel_dep(name = "protobuf", version = "29.2") +bazel_dep(name = "googletest", version = "1.16.0") +bazel_dep(name = "protobuf", version = "29.3") # The ODK library depends on proto files in the "chromium_deps" folder. Since # this would create a circular dependency, this creates a new module for it. diff --git a/whitebox/api/BUILD b/whitebox/api/BUILD index 451fec9..7f23cb7 100644 --- a/whitebox/api/BUILD +++ b/whitebox/api/BUILD @@ -283,6 +283,7 @@ cc_library( "license_whitebox_entitlement_content_key_test.cc", "license_whitebox_generic_crypto_test.cc", "license_whitebox_get_secret_string_test.cc", + "license_whitebox_get_max_odk_version_test.cc", "license_whitebox_key_control_block_test.cc", "license_whitebox_license_key_mode.cc", "license_whitebox_masked_decrypt_test.cc", diff --git a/whitebox/api/aead_whitebox.h b/whitebox/api/aead_whitebox.h index d2e0e01..d18ab93 100644 --- a/whitebox/api/aead_whitebox.h +++ b/whitebox/api/aead_whitebox.h @@ -96,6 +96,8 @@ WB_Result WB_Aead_Encrypt(const WB_Aead_Whitebox* whitebox, // Decrypts |input_data| and writes the plaintext to |output_data|. |input_data| // must have been encrypted using WB_Aead_Encrypt() with the same |whitebox|. +// |output_data| must be at least as big as |input_data|, this doesn't support +// WB_RESULT_BUFFER_TOO_SMALL. // // This must support |input_data| and |output_data| pointing to the same buffer // (in-place decrypt). @@ -111,7 +113,7 @@ WB_Result WB_Aead_Encrypt(const WB_Aead_Whitebox* whitebox, // // output_data_size (in/out) : As input, this contains the max number of bytes // that can be written to |output_data|. As output, |output_data_size| is set -// to the required size on WB_RESULT_OK and WB_RESULT_BUFFER_TOO_SMALL. +// to the required size on WB_RESULT_OK. // // Returns: // WB_RESULT_OK if |input_data| was successfully decrypted. @@ -120,9 +122,6 @@ WB_Result WB_Aead_Encrypt(const WB_Aead_Whitebox* whitebox, // null, if |input_data_size| was invalid, if |output_data| was null, or if // |output_data_size| was null. // -// WB_RESULT_BUFFER_TOO_SMALL if |output_data_size| (as input) was less than -// the required size. -// // WB_RESULT_DATA_VERIFICATION_ERROR if |input_data| failed data verification. // The state of |output_data| is undefined. WB_Result WB_Aead_Decrypt(const WB_Aead_Whitebox* whitebox, diff --git a/whitebox/api/aead_whitebox_decrypt_test.cc b/whitebox/api/aead_whitebox_decrypt_test.cc index 3949de7..5996fc4 100644 --- a/whitebox/api/aead_whitebox_decrypt_test.cc +++ b/whitebox/api/aead_whitebox_decrypt_test.cc @@ -214,10 +214,9 @@ TEST_F(AeadWhiteboxDecryptTest, BufferTooSmall) { size_t plaintext_size = plaintext_.size() - 1; std::vector plaintext(plaintext_size); - ASSERT_EQ(WB_Aead_Decrypt(whitebox_, ciphertext_.data(), ciphertext_.size(), + ASSERT_NE(WB_Aead_Decrypt(whitebox_, ciphertext_.data(), ciphertext_.size(), plaintext.data(), &plaintext_size), - WB_RESULT_BUFFER_TOO_SMALL); - ASSERT_EQ(plaintext_size, plaintext_.size()); + WB_RESULT_OK); } TEST_F(AeadWhiteboxDecryptTest, DataVerificationError) { diff --git a/whitebox/api/aead_whitebox_encrypt_test.cc b/whitebox/api/aead_whitebox_encrypt_test.cc index fee9c91..4f8612a 100644 --- a/whitebox/api/aead_whitebox_encrypt_test.cc +++ b/whitebox/api/aead_whitebox_encrypt_test.cc @@ -100,15 +100,6 @@ TEST_F(AeadWhiteboxEncryptTest, InvalidParameterForNullInputData) { WB_RESULT_INVALID_PARAMETER); } -TEST_F(AeadWhiteboxEncryptTest, InvalidParameterForZeroInputSize) { - size_t output_size = kDefaultOutputSize; - std::vector output(output_size); - - ASSERT_EQ( - WB_Aead_Encrypt(whitebox_, input_.data(), 0, output.data(), &output_size), - WB_RESULT_INVALID_PARAMETER); -} - TEST_F(AeadWhiteboxEncryptTest, InvalidParameterForNullOutputData) { size_t output_size = kDefaultOutputSize; std::vector output(output_size); @@ -141,4 +132,15 @@ TEST_F(AeadWhiteboxEncryptTest, BufferTooSmallForSmallOutputBuffer) { // only check that |output_size| is larger than |input_.size()|. ASSERT_GT(output_size, input_.size()); } + +TEST_F(AeadWhiteboxEncryptTest, BufferTooSmallWithNullArgs) { + // Should calculate the required size even if 0 or null buffer arguments. + size_t output_size = 0; + + ASSERT_EQ( + WB_Aead_Encrypt(whitebox_, nullptr, input_.size(), nullptr, &output_size), + WB_RESULT_BUFFER_TOO_SMALL); + ASSERT_GT(output_size, input_.size()); +} + } // namespace widevine diff --git a/whitebox/api/license_whitebox.h b/whitebox/api/license_whitebox.h index 2c19458..eb8a04f 100644 --- a/whitebox/api/license_whitebox.h +++ b/whitebox/api/license_whitebox.h @@ -23,14 +23,12 @@ // defined, we will define non-versioned function names. // #define WB_VERSION 28 -#ifdef WB_VERSION // We do this triple-indirection here since the ## operator handles arguments -// a bit weird and we want to expand the WB_RESULT as a value, not an identifier -// +// a bit weird and we want to expand the |version| as a value, not an identifier +#define WB_CONCAT_VERSION__(a, b) a ## b +#define WB_CONCAT_VERSION_(name, version) WB_CONCAT_VERSION__(name##_v, version) +#ifdef WB_VERSION // WB_CONCAT_VERSION(Foo) -> Foo_v28 -# define WB_CONCAT_VERSION__(a, b) a ## b -# define WB_CONCAT_VERSION_(name, version) \ - WB_CONCAT_VERSION__(name ## _v, version) # define WB_CONCAT_VERSION(name) WB_CONCAT_VERSION_(name, WB_VERSION) #else # define WB_CONCAT_VERSION(name) name @@ -44,6 +42,21 @@ extern "C" { typedef struct WB_CONCAT_VERSION(WB_License_Whitebox) WB_CONCAT_VERSION(WB_License_Whitebox); +// Returns the maximum supported version of ODK messages. +// +// Args: +// odk_major_version(out): Where to put the major ODK version that is supported +// +// odk_minor_version(out): Where to put the minor ODK version that is supported +// +// Returns: +// WB_RESULT_OK on success. +// +// WB_RESULT_INVALID_PARAMETER if either argument was null. +WB_Result WB_CONCAT_VERSION(WB_License_GetMaxOdkVersion)( + uint16_t* odk_major_version, + uint16_t* odk_minor_version); + // Creates a new white-box instance using the implementation's internal private // key|. A pointer to the white-box instance will be returned via |whitebox|. // diff --git a/whitebox/api/license_whitebox_get_max_odk_version_test.cc b/whitebox/api/license_whitebox_get_max_odk_version_test.cc new file mode 100644 index 0000000..171e579 --- /dev/null +++ b/whitebox/api/license_whitebox_get_max_odk_version_test.cc @@ -0,0 +1,29 @@ +// Copyright 2025 Google LLC. All Rights Reserved. + +#include "api/license_whitebox_latest.h" +#include "odk.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace widevine { + +TEST(LicenseWhiteboxGetMaxOdkVersionTest, Success) { + uint16_t major, minor; + ASSERT_EQ(WB_License_GetMaxOdkVersion(&major, &minor), WB_RESULT_OK); + EXPECT_GE(major, ODK_FIRST_VERSION); + EXPECT_LE(major, ODK_MAJOR_VERSION); + if (major == ODK_MAJOR_VERSION) { + EXPECT_LE(minor, ODK_MINOR_VERSION); + } +} + +TEST(LicenseWhiteboxGetMaxOdkVersionTest, NullArguments) { + uint16_t major, minor; + EXPECT_EQ(WB_License_GetMaxOdkVersion(nullptr, nullptr), + WB_RESULT_INVALID_PARAMETER); + EXPECT_EQ(WB_License_GetMaxOdkVersion(nullptr, &minor), + WB_RESULT_INVALID_PARAMETER); + EXPECT_EQ(WB_License_GetMaxOdkVersion(&major, nullptr), + WB_RESULT_INVALID_PARAMETER); +} + +} // namespace widevine diff --git a/whitebox/api/license_whitebox_latest.h b/whitebox/api/license_whitebox_latest.h index 5fdda54..b637741 100644 --- a/whitebox/api/license_whitebox_latest.h +++ b/whitebox/api/license_whitebox_latest.h @@ -13,6 +13,13 @@ extern "C" { typedef WB_CONCAT_VERSION(WB_License_Whitebox) WB_License_Whitebox; +inline WB_Result WB_License_GetMaxOdkVersion( + uint16_t* odk_major_version, + uint16_t* odk_minor_version) { + return WB_CONCAT_VERSION(WB_License_GetMaxOdkVersion)(odk_major_version, + odk_minor_version); +} + inline WB_Result WB_License_Create( const uint8_t* whitebox_init_data, size_t whitebox_init_data_size, diff --git a/whitebox/api/license_whitebox_wrapper.h b/whitebox/api/license_whitebox_wrapper.h new file mode 100644 index 0000000..6148736 --- /dev/null +++ b/whitebox/api/license_whitebox_wrapper.h @@ -0,0 +1,360 @@ +// Copyright 2025 Google LLC. All Rights Reserved. + +#ifndef WHITEBOX_API_LICENSE_WHITEBOX_WRAPPER_H_ +#define WHITEBOX_API_LICENSE_WHITEBOX_WRAPPER_H_ + +#include + +#include "api/license_whitebox.h" +#include "api/result.h" + +/// Defines an interface for a wrapper type for interacting with different +/// versions of the whitebox. These wrappers should handle changes between +/// older API versions and will call into the specific versioned-APIs. +/// +/// Creating an instance of a wrapper is done with the T::Create() method, a +/// static method that returns a WB_Result and gives the wrapper instance as +/// a smart pointer. +/// +/// For example: +/// std::unique_ptr whitebox; +/// auto res = LicenseWhiteboxWrapper_v30::Create(nullptr, 0, &whitebox); +/// assert(res == WB_RESULT_OK); +/// whitebox->SignLicenseRequest(...); +class LicenseWhiteboxWrapper { + public: + LicenseWhiteboxWrapper() {} + LicenseWhiteboxWrapper(const LicenseWhiteboxWrapper&) = delete; + LicenseWhiteboxWrapper(LicenseWhiteboxWrapper&&) = delete; + virtual ~LicenseWhiteboxWrapper() {} + + LicenseWhiteboxWrapper& operator=(const LicenseWhiteboxWrapper&) = delete; + LicenseWhiteboxWrapper& operator=(LicenseWhiteboxWrapper&&) = delete; + + //// Implementations should also define the following static functions. + //static void Unmask(const uint8_t* masked_data, + // size_t offset, + // size_t size, + // const uint8_t* secret_string, + // size_t secret_string_size, + // uint8_t* unmasked_data); + //static WB_Result Create(const uint8_t* whitebox_init_data, + // size_t whitebox_init_data_size, + // std::unique_ptr* whitebox); + //static WB_Result Import(const uint8_t* buffer, + // size_t buffer_size, + // std::unique_ptr* whitebox); + //static WB_Result SignLicenseRequest_Init(); + //static WB_Result ProcessLicenseResponse_Init(); + + virtual WB_Result ExportKeys(uint8_t* buffer, size_t* buffer_size) = 0; + virtual WB_Result SignLicenseRequest(const uint8_t* license_request, + size_t license_request_size, + uint8_t* signature, + size_t* signature_size) = 0; + virtual WB_Result ProcessLicenseResponse(WB_LicenseKeyMode license_key_mode, + const uint8_t* core_message, + size_t core_message_size, + const uint8_t* message, + size_t message_size, + const uint8_t* signature, + size_t signature_size, + const uint8_t* session_key, + size_t session_key_size, + size_t provider_key_id, + const uint8_t* license_request, + size_t license_request_size) = 0; + virtual WB_Result LoadEntitledContentKey(const uint8_t* entitlement_key_id, + size_t entitlement_key_id_size, + const uint8_t* content_key_id, + size_t content_key_id_size, + const uint8_t* iv, + size_t iv_size, + const uint8_t* key_data, + size_t key_data_size) = 0; + virtual WB_Result RemoveEntitledContentKey(const uint8_t* content_key_id, + size_t content_key_id_size) = 0; + virtual WB_Result QueryKeyStatus(WB_KeyQueryType type, + const uint8_t* key_id, + size_t key_id_size, + WB_KeyStatus* key_status) = 0; + virtual WB_Result SignRenewalRequest(const uint8_t* message, + size_t message_size, + uint8_t* signature, + size_t* signature_size) = 0; + virtual WB_Result SignPstReport(const uint8_t* message, + size_t message_size, + uint8_t* signature, + size_t* signature_size) = 0; + virtual WB_Result VerifyRenewalResponse(const uint8_t* message, + size_t message_size, + const uint8_t* signature, + size_t signature_size) = 0; + virtual WB_Result GetSecretString(WB_CipherMode mode, + const uint8_t* key_id, + size_t key_id_size, + uint8_t* secret_string, + size_t* secret_string_size) = 0; + virtual WB_Result GenericEncrypt(const uint8_t* key_id, + size_t key_id_size, + const uint8_t* input_data, + size_t input_data_size, + const uint8_t* iv, + size_t iv_size, + uint8_t* output_data, + size_t* output_data_size) = 0; + virtual WB_Result GenericDecrypt(const uint8_t* key_id, + size_t key_id_size, + const uint8_t* input_data, + size_t input_data_size, + const uint8_t* iv, + size_t iv_size, + uint8_t* output_data, + size_t* output_data_size) = 0; + virtual WB_Result GenericSign(const uint8_t* key_id, + size_t key_id_size, + const uint8_t* message, + size_t message_size, + uint8_t* output_data, + size_t* output_data_size) = 0; + virtual WB_Result GenericVerify(const uint8_t* key_id, + size_t key_id_size, + const uint8_t* message, + size_t message_size, + const uint8_t* signature, + size_t signature_size) = 0; + virtual WB_Result Decrypt(WB_CipherMode mode, + const uint8_t* key_id, + size_t key_id_size, + const uint8_t* input_data, + size_t input_data_size, + const uint8_t* iv, + size_t iv_size, + uint8_t* output_data, + size_t* output_data_size) = 0; + virtual WB_Result MaskedDecrypt(WB_CipherMode mode, + const uint8_t* key_id, + size_t key_id_size, + const uint8_t* input_data, + size_t input_data_size, + const uint8_t* iv, + size_t iv_size, + uint8_t* masked_output_data, + size_t* masked_output_data_size) = 0; +}; + +#define WB_DEFINE_DEFAULT_LICENSE_WRAPPER(version) \ + class WB_CONCAT_VERSION_(LicenseWhiteboxWrapper, version) \ + : public LicenseWhiteboxWrapper { \ + public: \ + using this_type = WB_CONCAT_VERSION_(LicenseWhiteboxWrapper, version); \ + ~WB_CONCAT_VERSION_(LicenseWhiteboxWrapper, version)() override { \ + WB_CONCAT_VERSION_(WB_License_Delete, version)(whitebox_); \ + } \ + \ + static void Unmask(const uint8_t* masked_data, \ + size_t offset, \ + size_t size, \ + const uint8_t* secret_string, \ + size_t secret_string_size, \ + uint8_t* unmasked_data) { \ + WB_CONCAT_VERSION_(WB_License_Unmask, version)( \ + masked_data, offset, size, secret_string, secret_string_size, \ + unmasked_data); \ + } \ + static WB_Result Create( \ + const uint8_t* whitebox_init_data, \ + size_t whitebox_init_data_size, \ + std::unique_ptr* whitebox) { \ + std::unique_ptr wb(new this_type); \ + const auto result = WB_CONCAT_VERSION_(WB_License_Create, version)( \ + whitebox_init_data, whitebox_init_data_size, &wb->whitebox_); \ + *whitebox = std::move(wb); \ + return result; \ + } \ + static WB_Result Import( \ + const uint8_t* buffer, \ + size_t buffer_size, \ + std::unique_ptr* whitebox) { \ + std::unique_ptr wb(new this_type); \ + const auto result = WB_CONCAT_VERSION_(WB_License_Import, version)( \ + buffer, buffer_size, &wb->whitebox_); \ + *whitebox = std::move(wb); \ + return result; \ + } \ + static WB_Result SignLicenseRequest_Init() { \ + return WB_CONCAT_VERSION_(WB_License_SignLicenseRequest_Init, \ + version)(); \ + } \ + static WB_Result ProcessLicenseResponse_Init() { \ + return WB_CONCAT_VERSION_(WB_License_ProcessLicenseResponse_Init, \ + version)(); \ + } \ + \ + WB_Result ExportKeys(uint8_t* buffer, size_t* buffer_size) override { \ + return WB_CONCAT_VERSION_(WB_License_ExportKeys, version)( \ + whitebox_, buffer, buffer_size); \ + } \ + WB_Result SignLicenseRequest(const uint8_t* license_request, \ + size_t license_request_size, \ + uint8_t* signature, \ + size_t* signature_size) override { \ + return WB_CONCAT_VERSION_(WB_License_SignLicenseRequest, version)( \ + whitebox_, license_request, license_request_size, signature, \ + signature_size); \ + } \ + WB_Result ProcessLicenseResponse(WB_LicenseKeyMode license_key_mode, \ + const uint8_t* core_message, \ + size_t core_message_size, \ + const uint8_t* message, \ + size_t message_size, \ + const uint8_t* signature, \ + size_t signature_size, \ + const uint8_t* session_key, \ + size_t session_key_size, \ + size_t provider_key_id, \ + const uint8_t* license_request, \ + size_t license_request_size) override { \ + return WB_CONCAT_VERSION_(WB_License_ProcessLicenseResponse, version)( \ + whitebox_, license_key_mode, core_message, core_message_size, \ + message, message_size, signature, signature_size, session_key, \ + session_key_size, provider_key_id, license_request, \ + license_request_size); \ + } \ + WB_Result LoadEntitledContentKey(const uint8_t* entitlement_key_id, \ + size_t entitlement_key_id_size, \ + const uint8_t* content_key_id, \ + size_t content_key_id_size, \ + const uint8_t* iv, \ + size_t iv_size, \ + const uint8_t* key_data, \ + size_t key_data_size) override { \ + return WB_CONCAT_VERSION_(WB_License_LoadEntitledContentKey, version)( \ + whitebox_, entitlement_key_id, entitlement_key_id_size, \ + content_key_id, content_key_id_size, iv, iv_size, key_data, \ + key_data_size); \ + } \ + WB_Result RemoveEntitledContentKey(const uint8_t* content_key_id, \ + size_t content_key_id_size) override { \ + return WB_CONCAT_VERSION_(WB_License_RemoveEntitledContentKey, version)( \ + whitebox_, content_key_id, content_key_id_size); \ + } \ + WB_Result QueryKeyStatus(WB_KeyQueryType type, \ + const uint8_t* key_id, \ + size_t key_id_size, \ + WB_KeyStatus* key_status) override { \ + return WB_CONCAT_VERSION_(WB_License_QueryKeyStatus, version)( \ + whitebox_, type, key_id, key_id_size, key_status); \ + } \ + WB_Result SignRenewalRequest(const uint8_t* message, \ + size_t message_size, \ + uint8_t* signature, \ + size_t* signature_size) override { \ + return WB_CONCAT_VERSION_(WB_License_SignRenewalRequest, version)( \ + whitebox_, message, message_size, signature, signature_size); \ + } \ + WB_Result SignPstReport(const uint8_t* message, \ + size_t message_size, \ + uint8_t* signature, \ + size_t* signature_size) override { \ + return WB_CONCAT_VERSION_(WB_License_SignPstReport, version)( \ + whitebox_, message, message_size, signature, signature_size); \ + } \ + WB_Result VerifyRenewalResponse(const uint8_t* message, \ + size_t message_size, \ + const uint8_t* signature, \ + size_t signature_size) override { \ + return WB_CONCAT_VERSION_(WB_License_VerifyRenewalResponse, version)( \ + whitebox_, message, message_size, signature, signature_size); \ + } \ + WB_Result GetSecretString(WB_CipherMode mode, \ + const uint8_t* key_id, \ + size_t key_id_size, \ + uint8_t* secret_string, \ + size_t* secret_string_size) override { \ + return WB_CONCAT_VERSION_(WB_License_GetSecretString, version)( \ + whitebox_, mode, key_id, key_id_size, secret_string, \ + secret_string_size); \ + } \ + WB_Result GenericEncrypt(const uint8_t* key_id, \ + size_t key_id_size, \ + const uint8_t* input_data, \ + size_t input_data_size, \ + const uint8_t* iv, \ + size_t iv_size, \ + uint8_t* output_data, \ + size_t* output_data_size) override { \ + return WB_CONCAT_VERSION_(WB_License_GenericEncrypt, version)( \ + whitebox_, key_id, key_id_size, input_data, input_data_size, iv, \ + iv_size, output_data, output_data_size); \ + } \ + WB_Result GenericDecrypt(const uint8_t* key_id, \ + size_t key_id_size, \ + const uint8_t* input_data, \ + size_t input_data_size, \ + const uint8_t* iv, \ + size_t iv_size, \ + uint8_t* output_data, \ + size_t* output_data_size) override { \ + return WB_CONCAT_VERSION_(WB_License_GenericDecrypt, version)( \ + whitebox_, key_id, key_id_size, input_data, input_data_size, iv, \ + iv_size, output_data, output_data_size); \ + } \ + WB_Result GenericSign(const uint8_t* key_id, \ + size_t key_id_size, \ + const uint8_t* message, \ + size_t message_size, \ + uint8_t* output_data, \ + size_t* output_data_size) override { \ + return WB_CONCAT_VERSION_(WB_License_GenericSign, version)( \ + whitebox_, key_id, key_id_size, message, message_size, output_data, \ + output_data_size); \ + } \ + WB_Result GenericVerify(const uint8_t* key_id, \ + size_t key_id_size, \ + const uint8_t* message, \ + size_t message_size, \ + const uint8_t* signature, \ + size_t signature_size) override { \ + return WB_CONCAT_VERSION_(WB_License_GenericVerify, version)( \ + whitebox_, key_id, key_id_size, message, message_size, signature, \ + signature_size); \ + } \ + WB_Result Decrypt(WB_CipherMode mode, \ + const uint8_t* key_id, \ + size_t key_id_size, \ + const uint8_t* input_data, \ + size_t input_data_size, \ + const uint8_t* iv, \ + size_t iv_size, \ + uint8_t* output_data, \ + size_t* output_data_size) override { \ + return WB_CONCAT_VERSION_(WB_License_Decrypt, version)( \ + whitebox_, mode, key_id, key_id_size, input_data, input_data_size, \ + iv, iv_size, output_data, output_data_size); \ + } \ + WB_Result MaskedDecrypt(WB_CipherMode mode, \ + const uint8_t* key_id, \ + size_t key_id_size, \ + const uint8_t* input_data, \ + size_t input_data_size, \ + const uint8_t* iv, \ + size_t iv_size, \ + uint8_t* masked_output_data, \ + size_t* masked_output_data_size) override { \ + return WB_CONCAT_VERSION_(WB_License_Decrypt, version)( \ + whitebox_, mode, key_id, key_id_size, input_data, input_data_size, \ + iv, iv_size, masked_output_data, masked_output_data_size); \ + } \ + \ + private: \ + WB_CONCAT_VERSION_(LicenseWhiteboxWrapper, version)() {} \ + WB_CONCAT_VERSION_(WB_License_Whitebox, version) * whitebox_ = nullptr; \ + } + +#ifdef WB_VERSION +WB_DEFINE_DEFAULT_LICENSE_WRAPPER(WB_VERSION); +using LicenseWhiteboxWrapperCurrent = WB_CONCAT_VERSION(LicenseWhiteboxWrapper); +#endif + +#endif // WHITEBOX_API_LICENSE_WHITEBOX_WRAPPER_H_ diff --git a/whitebox/chromium_deps/MODULE.bazel b/whitebox/chromium_deps/MODULE.bazel index 0b4f957..ab49748 100644 --- a/whitebox/chromium_deps/MODULE.bazel +++ b/whitebox/chromium_deps/MODULE.bazel @@ -1,3 +1,3 @@ # Copyright 2025 Google LLC. All Rights Reserved. module(name = "chromium_deps") -bazel_dep(name = "protobuf", version = "29.2") +bazel_dep(name = "protobuf", version = "29.3") diff --git a/whitebox/crypto_utils/whitebox_init_helper.h b/whitebox/crypto_utils/whitebox_init_helper.h index ee4a79a..1d31c08 100644 --- a/whitebox/crypto_utils/whitebox_init_helper.h +++ b/whitebox/crypto_utils/whitebox_init_helper.h @@ -5,6 +5,7 @@ #ifndef WHITEBOX_INIT_HELPER_H_ #define WHITEBOX_INIT_HELPER_H_ +#include #include #include #include @@ -13,18 +14,36 @@ #include #include +#include + +class WhiteboxInitHelperBase { + public: + WhiteboxInitHelperBase () {} + virtual ~WhiteboxInitHelperBase () {} + + virtual void StartThread() = 0; + + /** Blocks the current thread until the sign initialization has finished. */ + virtual WB_Result EnsureSignReady() = 0; + + /** + * Blocks the current thread until the process initialization has finished. + */ + virtual WB_Result EnsureProcessReady() = 0; +}; /** * Defines a helper class to initialize the Zimperium whitebox. This spawns * a background thread to initialize in the background. */ -template ::value, std::condition_variable, std::condition_variable_any>::type> -class WhiteboxInitHelper final { +class WhiteboxInitHelper final : public WhiteboxInitHelperBase { public: enum class State { None, @@ -34,7 +53,7 @@ class WhiteboxInitHelper final { }; WhiteboxInitHelper() {} - ~WhiteboxInitHelper() { + ~WhiteboxInitHelper() override { if (thread_) thread_->join(); } @@ -50,7 +69,7 @@ class WhiteboxInitHelper final { } /** Spawns the background thread and starts initializing. */ - void StartThread() { + void StartThread() override { if (!thread_) { thread_.reset( new Thread(std::bind(&WhiteboxInitHelper::ThreadMain, this))); @@ -58,10 +77,11 @@ class WhiteboxInitHelper final { } /** Blocks the current thread until the sign initialization has finished. */ - WB_Result EnsureSignReady() { + WB_Result EnsureSignReady() override { + StartThread(); std::unique_lock lock(mutex_); while (state_ < State::SignReady) { - cond_.wait(lock); + cond_.wait_for(lock, std::chrono::seconds(1)); } return result_; } @@ -69,17 +89,18 @@ class WhiteboxInitHelper final { /** * Blocks the current thread until the process initialization has finished. */ - WB_Result EnsureProcessReady() { + WB_Result EnsureProcessReady() override { + StartThread(); std::unique_lock lock(mutex_); while (state_ < State::ProcessReady) { - cond_.wait(lock); + cond_.wait_for(lock, std::chrono::seconds(1)); } return result_; } private: void ThreadMain() { - auto result = WB_License_SignLicenseRequest_Init(); + auto result = Wrapper::SignLicenseRequest_Init(); { std::unique_lock lock(mutex_); state_ = result == WB_RESULT_OK ? State::SignReady : State::Error; @@ -89,7 +110,7 @@ class WhiteboxInitHelper final { return; } - result = WB_License_ProcessLicenseResponse_Init(); + result = Wrapper::ProcessLicenseResponse_Init(); std::unique_lock lock(mutex_); state_ = result == WB_RESULT_OK ? State::ProcessReady : State::Error; diff --git a/whitebox/reference/impl/aead_whitebox_impl.cc b/whitebox/reference/impl/aead_whitebox_impl.cc index fc9f956..5e81cae 100644 --- a/whitebox/reference/impl/aead_whitebox_impl.cc +++ b/whitebox/reference/impl/aead_whitebox_impl.cc @@ -110,41 +110,44 @@ WB_Result WB_Aead_Encrypt(const WB_Aead_Whitebox* whitebox, size_t input_data_size, uint8_t* output_data, size_t* output_data_size) { - if (!whitebox || !input_data || !output_data || !output_data_size) { + if (!whitebox || !output_data_size) { DVLOG(1) << "Invalid parameter: null pointer."; return WB_RESULT_INVALID_PARAMETER; } - if (input_data_size == 0) { - DVLOG(1) << "Invalid parameter: array size 0."; + const size_t extra_size = EVP_AEAD_max_overhead(whitebox->algorithm); + const size_t required_size = + input_data_size + whitebox->nonce_size + extra_size; + if (*output_data_size < required_size) { + DVLOG(1) << "Buffer too small: got=" << *output_data_size + << ", needs=" << required_size << "."; + *output_data_size = required_size; + return WB_RESULT_BUFFER_TOO_SMALL; + } + if (!input_data || !output_data) { + DVLOG(1) << "Invalid parameter: null pointer."; return WB_RESULT_INVALID_PARAMETER; } std::vector nonce(whitebox->nonce_size); RAND_bytes(nonce.data(), nonce.size()); - std::vector output(input_data_size + - EVP_AEAD_max_overhead(whitebox->algorithm)); + // This should support in-place seal: + // https://boringssl.googlesource.com/boringssl/+/refs/heads/master/include/openssl/aead.h#84 size_t sealed_size; - CHECK_EQ(EVP_AEAD_CTX_seal(&whitebox->context, output.data(), &sealed_size, - output.size(), nonce.data(), nonce.size(), + CHECK_EQ(EVP_AEAD_CTX_seal(&whitebox->context, output_data, &sealed_size, + *output_data_size, nonce.data(), nonce.size(), input_data, input_data_size, nullptr, 0), kAeadSuccess); - output.resize(sealed_size); - // At this point, |output| will have the encrypted data and authentication - // tag, but in order to decrypt later, we will need the nonce, so append the - // nonce to the output. - output.insert(output.end(), nonce.begin(), nonce.end()); + // At this point, |output_data| will have the encrypted data and + // authentication tag, but in order to decrypt later, we will need the nonce, + // so append the nonce to the output. + CHECK_LT(sealed_size, *output_data_size); + CHECK(widevine::MemCopy(nonce.data(), nonce.size(), output_data + sealed_size, + *output_data_size - sealed_size)); - if (!widevine::MemCopy(output.data(), output.size(), output_data, - *output_data_size)) { - DVLOG(1) << "Buffer too small: output needs " << output.size() << "."; - *output_data_size = output.size(); - return WB_RESULT_BUFFER_TOO_SMALL; - } - - *output_data_size = output.size(); + *output_data_size = sealed_size + nonce.size(); return WB_RESULT_OK; } @@ -184,32 +187,16 @@ WB_Result WB_Aead_Decrypt(const WB_Aead_Whitebox* whitebox, const uint8_t* nonce_start = payload_start + payload_size; const size_t nonce_size = whitebox->nonce_size; - // Remember, the plaintext will be smaller than the payload, but we are going - // to start with the payload size as it is our best estimate and will scale it - // down when we have the final size. - std::vector plaintext(payload_size); - size_t plaintext_size; - + // Since |*output_data_size| is passed by-value, it is a copy and will not + // cause a problem when it modifies |*output_data_size|. + // This should support in-place open: + // https://boringssl.googlesource.com/boringssl/+/refs/heads/master/include/openssl/aead.h#84 const int result = EVP_AEAD_CTX_open( - &whitebox->context, plaintext.data(), &plaintext_size, plaintext.size(), + &whitebox->context, output_data, output_data_size, *output_data_size, nonce_start, nonce_size, payload_start, payload_size, nullptr, 0); - if (result != kAeadSuccess) { - DVLOG(1) << "Data verification error: failed to verify input data."; + DVLOG(1) << "Data verification error: failed to verify input data"; return WB_RESULT_DATA_VERIFICATION_ERROR; } - - // Know that EVP_AEAD_CTX_open() has opened the payload, we know the actual - // plaintext size and can now shrink the vector to the correct size. - plaintext.resize(plaintext_size); - - if (!widevine::MemCopy(plaintext.data(), plaintext.size(), output_data, - *output_data_size)) { - DVLOG(1) << "Buffer too small: output needs " << plaintext.size() << "."; - *output_data_size = plaintext.size(); - return WB_RESULT_BUFFER_TOO_SMALL; - } - - *output_data_size = plaintext.size(); return WB_RESULT_OK; } diff --git a/whitebox/reference/impl/license_whitebox_impl.cc b/whitebox/reference/impl/license_whitebox_impl.cc index fa3f85f..78cc382 100644 --- a/whitebox/reference/impl/license_whitebox_impl.cc +++ b/whitebox/reference/impl/license_whitebox_impl.cc @@ -110,6 +110,11 @@ std::string MakeString(const uint8_t* data, size_t size) { } #endif +#ifdef HAS_IMPORT_EXPORT +# ifndef WB_VERSION +# error Must set WB_VERSION with export +# endif + struct WB_License_ExportedData_Key { std::array key_id; uint8_t key_id_size; @@ -124,8 +129,7 @@ struct WB_License_ExportedData { size_t num_generic_keys; WB_License_ExportedData_Key keys[0]; }; - -constexpr const uint8_t kExportedVersion = 1; +#endif } // namespace @@ -348,6 +352,18 @@ std::vector CreateProviderKeys( } // namespace +WB_Result WB_CONCAT_VERSION(WB_License_GetMaxOdkVersion)( + uint16_t* odk_major_version, + uint16_t* odk_minor_version) { + if (!odk_major_version || !odk_minor_version) { + DVLOG(1) << "Invalid parameter: null pointer."; + return WB_RESULT_INVALID_PARAMETER; + } + *odk_major_version = ODK_MAJOR_VERSION; + *odk_minor_version = ODK_MINOR_VERSION; + return WB_RESULT_OK; +} + WB_Result WB_CONCAT_VERSION(WB_License_Create)( const uint8_t* whitebox_init_data, size_t whitebox_init_data_size, @@ -406,7 +422,7 @@ WB_Result WB_CONCAT_VERSION(WB_License_Import)( } auto* info = reinterpret_cast(buffer); - if (info->version != kExportedVersion) { + if (info->version != WB_VERSION) { DVLOG(1) << "Unsupported version of exported data."; return WB_RESULT_INVALID_PARAMETER; } @@ -483,7 +499,7 @@ WB_Result WB_CONCAT_VERSION(WB_License_ExportKeys)( if (info->has_renewal) { info->renewal_client = whitebox->renewal_key->client; } - info->version = kExportedVersion; + info->version = WB_VERSION; info->num_content_keys = whitebox->content_keys.size(); info->num_entitlement_keys = whitebox->entitlement_keys.size(); info->num_generic_keys = whitebox->generic_keys.size(); diff --git a/whitebox/reference/impl/odk.cc b/whitebox/reference/impl/odk.cc index 1f8a6a6..a37d859 100644 --- a/whitebox/reference/impl/odk.cc +++ b/whitebox/reference/impl/odk.cc @@ -71,7 +71,11 @@ bool HasEncryptedKeyControlBlock(const ODKContext& context) { size_t encrypted_count = 0; for (uint32_t i = 0; i < context.license.key_array_length; i++) { +#if ODK_MAJOR_VERSION >= 20 + const OEMCrypto_KeyObjectV2& key = context.license.key_array[i]; +#else const OEMCrypto_KeyObject& key = context.license.key_array[i]; +#endif if (key.key_control_iv.length > 0) { encrypted_count++; diff --git a/whitebox/reference/impl/odk_license_parser.cc b/whitebox/reference/impl/odk_license_parser.cc index c22f81b..52cdce4 100644 --- a/whitebox/reference/impl/odk_license_parser.cc +++ b/whitebox/reference/impl/odk_license_parser.cc @@ -86,7 +86,11 @@ WB_Result OdkLicenseParser::Parse(const std::string& decryption_key, } for (size_t i = 0; i < odk_context.license.key_array_length; ++i) { +#if ODK_MAJOR_VERSION >= 20 + const OEMCrypto_KeyObjectV2& key = odk_context.license.key_array[i]; +#else const OEMCrypto_KeyObject& key = odk_context.license.key_array[i]; +#endif const std::string key_id = ExtractItem(key.key_id, message); // If there is no key id, we can't add an invalid entry since there would @@ -198,7 +202,11 @@ InternalKey OdkLicenseParser::ParseInternalKey( KeyType key_type, const std::string& decryption_key, const std::string& message, +#if ODK_MAJOR_VERSION >= 20 + const OEMCrypto_KeyObjectV2& key, +#else const OEMCrypto_KeyObject& key, +#endif const std::vector& provider_keys, size_t provider_key_id) const { // This should have been verified before calling the parser. diff --git a/whitebox/reference/impl/odk_license_parser.h b/whitebox/reference/impl/odk_license_parser.h index 7fa1511..c98e571 100644 --- a/whitebox/reference/impl/odk_license_parser.h +++ b/whitebox/reference/impl/odk_license_parser.h @@ -32,7 +32,11 @@ class OdkLicenseParser : public LicenseParser { InternalKey ParseInternalKey(KeyType key_type, const std::string& decryption_key, const std::string& message, +#if ODK_MAJOR_VERSION >= 20 + const OEMCrypto_KeyObjectV2& key, +#else const OEMCrypto_KeyObject& key, +#endif const std::vector& provider_keys, size_t provider_key_id) const;