diff --git a/api/BUILD b/api/BUILD index 8d43902..244fe12 100644 --- a/api/BUILD +++ b/api/BUILD @@ -1,12 +1,13 @@ # Copyright 2020 Google LLC. All Rights Reserved. -package(default_visibility = ["//visibility:public"]) +package(default_visibility = ["//visibility:private"]) cc_library( name = "result", hdrs = [ "result.h", ], + visibility = ["//visibility:public"], ) cc_library( @@ -14,6 +15,7 @@ cc_library( hdrs = [ "aead_whitebox.h", ], + visibility = ["//visibility:public"], deps = [ ":result", ], @@ -24,6 +26,7 @@ cc_library( hdrs = [ "license_whitebox.h", ], + visibility = ["//visibility:public"], deps = [ ":result", ], @@ -35,13 +38,28 @@ cc_library( hdrs = [ "test_data.h", ], + visibility = ["//visibility:public"], ) cc_library( - name = "license_test_helper", + name = "golden_data", testonly = True, - srcs = ["license_test_helper.cc"], - hdrs = ["license_test_helper.h"], + srcs = [ + "golden_data.cc", + ], + hdrs = [ + "golden_data.h", + ], + deps = [ + "//chromium_deps/cdm/protos:license_protocol_proto", + ], +) + +cc_library( + name = "license_builder", + testonly = True, + srcs = ["license_builder.cc"], + hdrs = ["license_builder.h"], deps = [ "//chromium_deps/base", "//chromium_deps/cdm/keys:dev_certs", @@ -57,9 +75,11 @@ cc_library( testonly = True, srcs = [ "aead_whitebox_create_test.cc", + "aead_whitebox_cross_instance_test.cc", "aead_whitebox_decrypt_test.cc", "aead_whitebox_encrypt_test.cc", ], + visibility = ["//visibility:public"], deps = [ ":aead_whitebox", ":test_data", @@ -74,11 +94,20 @@ cc_library( "license_whitebox_create_test.cc", "license_whitebox_decrypt_test.cc", "license_whitebox_get_secret_string_test.cc", + "license_whitebox_masked_decrypt_test.cc", + "license_whitebox_process_license_response_test.cc", + "license_whitebox_sign_license_request_test.cc", "license_whitebox_sign_renewal_request_test.cc", + "license_whitebox_test_base.cc", "license_whitebox_verify_renewal_response_test.cc", ], + hdrs = [ + "license_whitebox_test_base.h", + ], + visibility = ["//visibility:public"], deps = [ - ":license_test_helper", + ":golden_data", + ":license_builder", ":license_whitebox", ":test_data", "//chromium_deps/cdm/keys:api", diff --git a/api/aead_whitebox_cross_instance_test.cc b/api/aead_whitebox_cross_instance_test.cc new file mode 100644 index 0000000..5fa1e84 --- /dev/null +++ b/api/aead_whitebox_cross_instance_test.cc @@ -0,0 +1,125 @@ +// Copyright 2020 Google LLC. All Rights Reserved. + +#include + +#include "api/aead_whitebox.h" +#include "api/test_data.h" +#include "testing/include/gtest/gtest.h" + +namespace { +// These tests focus on functionality that should and should not exist between +// different Aead White-box instances. They assume that all other functionality +// of the white-box has already been verified. +class AeadWhiteboxCrossInstanceTest : public ::testing::Test { + protected: + void SetUp() override { init_data_ = GetValidAeadInitData(); } + + void TearDown() override { + // Even if the pointer is null, as per the API, it should be safe to call + // WB_Aead_Delete(). + WB_Aead_Delete(whitebox_a_); + WB_Aead_Delete(whitebox_b_); + } + + WB_Aead_Whitebox* whitebox_a_ = nullptr; + WB_Aead_Whitebox* whitebox_b_ = nullptr; + + // Save a copy of the init data so that it is easier to create instances. It + // would be nice if this could be const, but we need to set it in SetUp(). + std::vector init_data_; + + // We need two different contexts so that we can have two unique instances. + // Each context was generated using a random number generator. There is no + // meaning to the bytes, the only condition is that they be different from + // each other. + const std::vector context_a_ = { + 0xa0, 0xaf, 0x75, 0xbf, 0x6b, 0xff, 0x2f, 0x76, + 0x90, 0x47, 0xc4, 0x21, 0xb5, 0xf2, 0xb9, 0x62, + }; + + const std::vector context_b_ = { + 0xfe, 0x6d, 0x01, 0x45, 0xbd, 0xa9, 0xcc, 0xd4, + 0x74, 0xaf, 0xca, 0xf8, 0xf9, 0x79, 0x2d, 0xf4, + }; + + // Both instances will always use the same plaintext. Since we are going to + // verify decryption, we need the plaintext to be recognizable after it is + // encrypted and then decrypted. To make it easier to read, we also avoided + // random data. + const std::vector common_plaintext_ = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + }; + + // We don't know how big the ciphertext will be, so we don't know how much + // memory to allocate for our buffers. Since our plaintext is small, 256 + // should be large enough. + const size_t safe_buffer_size_ = 256; +}; + +// Create two instances, each with unique contexts. Since they have different +// contexts, they should not be able to decrypt each other's data. +TEST_F(AeadWhiteboxCrossInstanceTest, + DataVerificationErrorForDifferentContext) { + ASSERT_EQ(WB_Aead_Create(init_data_.data(), init_data_.size(), + context_a_.data(), context_a_.size(), &whitebox_a_), + WB_RESULT_OK); + ASSERT_EQ(WB_Aead_Create(init_data_.data(), init_data_.size(), + context_b_.data(), context_b_.size(), &whitebox_b_), + WB_RESULT_OK); + + size_t ciphertext_size = safe_buffer_size_; + std::vector ciphertext(ciphertext_size); + + size_t plaintext_size = safe_buffer_size_; + std::vector plaintext(plaintext_size); + + // The flow looks like: + // |common plaintext| --> [WB A] --> ciphertext --> [WB B] --> plaintext + // + // But it should fail after white-box tries to decrypt it, so |plaintext| + // should never get written to. + ASSERT_EQ(WB_Aead_Encrypt(whitebox_a_, common_plaintext_.data(), + common_plaintext_.size(), ciphertext.data(), + &ciphertext_size), + WB_RESULT_OK); + ciphertext.resize(ciphertext_size); + + ASSERT_EQ(WB_Aead_Decrypt(whitebox_b_, ciphertext.data(), ciphertext.size(), + plaintext.data(), &plaintext_size), + WB_RESULT_DATA_VERIFICATION_ERROR); + plaintext.resize(plaintext_size); +} + +// Create two instances, each using the same contexts. Since they have the same +// context, they should be able to decrypt each other's data. +TEST_F(AeadWhiteboxCrossInstanceTest, DataVerificationErrorForCommonContext) { + ASSERT_EQ(WB_Aead_Create(init_data_.data(), init_data_.size(), + context_a_.data(), context_a_.size(), &whitebox_a_), + WB_RESULT_OK); + ASSERT_EQ(WB_Aead_Create(init_data_.data(), init_data_.size(), + context_a_.data(), context_a_.size(), &whitebox_b_), + WB_RESULT_OK); + + size_t ciphertext_size = safe_buffer_size_; + std::vector ciphertext(ciphertext_size); + + size_t plaintext_size = safe_buffer_size_; + std::vector plaintext(plaintext_size); + + // The flow looks like: + // |common plaintext| --> [WB A] --> ciphertext --> [WB B] --> plaintext + ASSERT_EQ(WB_Aead_Encrypt(whitebox_a_, common_plaintext_.data(), + common_plaintext_.size(), ciphertext.data(), + &ciphertext_size), + WB_RESULT_OK); + ciphertext.resize(ciphertext_size); + + ASSERT_EQ(WB_Aead_Decrypt(whitebox_b_, ciphertext.data(), ciphertext.size(), + plaintext.data(), &plaintext_size), + WB_RESULT_OK); + plaintext.resize(plaintext_size); + + ASSERT_EQ(plaintext, common_plaintext_); +} +} // namespace diff --git a/api/golden_data.cc b/api/golden_data.cc new file mode 100644 index 0000000..f75e470 --- /dev/null +++ b/api/golden_data.cc @@ -0,0 +1,92 @@ +// Copyright 2020 Google LLC. All Rights Reserved. + +#include "api/golden_data.h" + +namespace widevine { + +GoldenData::GoldenData() { + // Content generated with: + // openssl aes-128-cbc -e -in data.txt + // -K EBDD62F16814D27B68EF122AFCE4AE3C + // -iv 30313233343536373839303132333435 | xxd -i + // Extra padding was stripped off. + cbc_content_ = { + /* plaintext */ {'t', 'h', 'i', 's', ' ', 'i', 's', ' ', 't', 'h', 'e', + ' ', 'p', 'l', 'a', 'i', 'n', 't', 'e', 'x', 't', ' ', + ':', ' ', '3', '2', ' ', 'b', 'y', 't', 'e', 's'}, + /* ciphertext */ + {0x5e, 0x60, 0x0d, 0x3c, 0x29, 0xb9, 0x49, 0x4c, 0x65, 0x67, 0x7e, + 0x87, 0x82, 0x9d, 0x47, 0x58, 0xb9, 0x86, 0xd0, 0x39, 0x6a, 0x67, + 0x2c, 0x53, 0xe9, 0xbc, 0x99, 0x5b, 0x23, 0x34, 0x9f, 0xf8}, + /* key */ + {0xEB, 0xDD, 0x62, 0xF1, 0x68, 0x14, 0xD2, 0x7B, 0x68, 0xEF, 0x12, 0x2A, + 0xFC, 0xE4, 0xAE, 0x3C}, + /* iv */ + {0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30, 0x31, + 0x32, 0x33, 0x34, 0x35}, + }; + + cbc_crypto_key_ = { + video_widevine::License_KeyContainer_SecurityLevel_SW_SECURE_CRYPTO, + {0xFF, 0, 0, 0}, + &cbc_content_, + }; + + cbc_decode_key_ = { + video_widevine::License_KeyContainer_SecurityLevel_SW_SECURE_DECODE, + {0xFF, 1, 0, 0}, + &cbc_content_, + }; + + cbc_hardware_key_ = { + video_widevine::License_KeyContainer_SecurityLevel_HW_SECURE_CRYPTO, + {0xFF, 2, 0, 0}, + &cbc_content_, + }; + + // Content generated with: + // openssl aes-128-ctr -e -in data.txt + // -K dd3c6cd4ea73b99d55f2e0357e1f560f + // -iv d50c08b31fc09e9e748431ca972334e6 | xxd -i + ctr_content_ = { + /* plaintext */ {'T', 'h', 'i', 'r', 't', 'y', '-', 't', 'w', 'o', ' ', + 'b', 'y', 't', 'e', 's', ' ', 'o', 'f', ' ', 'r', 'a', + 'n', 'd', 'o', 'm', ' ', 'd', 'a', 't', 'a', '.'}, + /* ciphertext */ + {0x5d, 0x83, 0xdd, 0xb9, 0xed, 0x18, 0x2f, 0x10, 0xbf, 0x6f, 0x4d, + 0xb0, 0xb3, 0xeb, 0x0d, 0x20, 0xd7, 0x7e, 0x9a, 0x3a, 0xc4, 0x41, + 0xcf, 0x0a, 0xb3, 0xae, 0x02, 0x01, 0x0a, 0xf2, 0x72, 0x72}, + /* key */ + {0xdd, 0x3c, 0x6c, 0xd4, 0xea, 0x73, 0xb9, 0x9d, 0x55, 0xf2, 0xe0, 0x35, + 0x7e, 0x1f, 0x56, 0x0f}, + /* iv */ + {0xd5, 0x0c, 0x08, 0xb3, 0x1f, 0xc0, 0x9e, 0x9e, 0x74, 0x84, 0x31, 0xca, + 0x97, 0x23, 0x34, 0xe6}, + }; + + ctr_crypto_key_ = { + video_widevine::License_KeyContainer_SecurityLevel_SW_SECURE_CRYPTO, + {0xFF, 3, 0, 0}, + &ctr_content_, + }; + + ctr_decode_key_ = { + video_widevine::License_KeyContainer_SecurityLevel_SW_SECURE_DECODE, + {0xFF, 4, 0, 0}, + &ctr_content_, + }; + + ctr_hardware_key_ = { + video_widevine::License_KeyContainer_SecurityLevel_HW_SECURE_CRYPTO, + {0xFF, 5, 0, 0}, + &ctr_content_, + }; +} + +void GoldenData::MakeKeyIdDifferent(std::vector* key_id) const { + // All our internal key ids start with 0xFF, so pushing something that is not + // 0xFF to the front will ensure that they don't collide. + key_id->insert(key_id->begin(), 0xAB); +} + +} // namespace widevine diff --git a/api/golden_data.h b/api/golden_data.h new file mode 100644 index 0000000..dfb2b9d --- /dev/null +++ b/api/golden_data.h @@ -0,0 +1,61 @@ +// Copyright 2020 Google LLC. All Rights Reserved. + +#ifndef WHITEBOX_API_GOLDEN_DATA_H_ +#define WHITEBOX_API_GOLDEN_DATA_H_ + +#include +#include +#include + +#include "cdm/protos/license_protocol.pb.h" + +namespace widevine { + +class GoldenData { + public: + struct Content { + std::vector plaintext; + std::vector ciphertext; + std::vector key; + std::vector iv; + }; + + struct Key { + video_widevine::License_KeyContainer_SecurityLevel level; + std::vector id; + const Content* content; + }; + + GoldenData(); + + const Content& CBCContent() const { return cbc_content_; } + const Key& CBCCryptoKey() const { return cbc_crypto_key_; } + const Key& CBCDecodeKey() const { return cbc_decode_key_; } + const Key& CBCHardwareKey() const { return cbc_hardware_key_; } + + const Content& CTRContent() const { return ctr_content_; } + const Key& CTRCryptoKey() const { return ctr_crypto_key_; } + const Key& CTRDecodeKey() const { return ctr_decode_key_; } + const Key& CTRHardwareKey() const { return ctr_hardware_key_; } + + // When a test needs to define a key id that does not conflict with any key + // ids defined in the golden data, it should use this to update their key id + // by prepending a single byte to ensure it won't collide with any of the + // internal key ids. + void MakeKeyIdDifferent(std::vector* key_id) const; + + private: + Content cbc_content_; + Key cbc_crypto_key_; + Key cbc_decode_key_; + Key cbc_hardware_key_; + + Content ctr_content_; + Key ctr_crypto_key_; + Key ctr_decode_key_; + Key ctr_hardware_key_; +}; + +} // namespace widevine + +#endif // WHITEBOX_API_GOLDEN_DATA_H_ diff --git a/api/license_builder.cc b/api/license_builder.cc new file mode 100644 index 0000000..ec412ca --- /dev/null +++ b/api/license_builder.cc @@ -0,0 +1,233 @@ +// Copyright 2020 Google LLC. All Rights Reserved. + +#include "api/license_builder.h" + +#include + +#include "base/logging.h" +#include "cdm/keys/certs.h" +#include "crypto_utils/aes_cbc_encryptor.h" +#include "crypto_utils/crypto_util.h" +#include "crypto_utils/sha_util.h" + +namespace widevine { + +namespace { + +void InitializeRequest(video_widevine::LicenseRequest* request) { + request->set_request_time(std::time(nullptr)); // Use time=now. + + auto* client_id = request->mutable_client_id(); + auto* capabilities = client_id->mutable_client_capabilities(); + capabilities->set_video_resolution_constraints(true); + capabilities->set_client_token(true); + capabilities->set_session_token(false); + capabilities->set_max_hdcp_version( + video_widevine::ClientIdentification::ClientCapabilities::HDCP_V1); + + auto* client_info = client_id->add_client_info(); + client_info->set_name("architecture_name"); + client_info->set_value("x86-64"); + client_info = client_id->add_client_info(); + client_info->set_name("company_name"); + client_info->set_value("Google"); + client_info = client_id->add_client_info(); + client_info->set_name("model_name"); + client_info->set_value("ChromeCDM"); + client_info = client_id->add_client_info(); + client_info->set_name("platform_name"); + client_info->set_value("Windows"); + client_info = client_id->add_client_info(); + client_info->set_name("widevine_cdm_version"); + client_info->set_value("4.10.1686.29"); + + client_id->set_type( + video_widevine::ClientIdentification_TokenType_DRM_DEVICE_CERTIFICATE); + client_id->set_token(wvcdm::kRsaDrmCertificate, + wvcdm::kRsaDrmCertificateSize); + + auto* content_id = request->mutable_content_id(); + auto* webm_key_id = content_id->mutable_webm_key_id(); + webm_key_id->set_license_type(video_widevine::STREAMING); + webm_key_id->set_request_id("REQUEST_ID"); + webm_key_id->set_header("01234567890123456"); + + request->set_protocol_version(video_widevine::VERSION_2_1); + request->set_type(video_widevine::LicenseRequest::NEW); +} + +void InitializeResponse(const video_widevine::LicenseRequest& request, + video_widevine::License* response) { + auto* id = response->mutable_id(); + id->set_request_id("REQUEST_ID"); + id->set_session_id("SESSION_ID"); + id->set_type(video_widevine::STREAMING); + id->set_version(0); + + auto* policy = response->mutable_policy(); + policy->set_can_play(true); + policy->set_can_persist(false); + policy->set_can_renew(true); + policy->set_license_duration_seconds(600); + policy->set_renewal_delay_seconds(30); + policy->set_renewal_retry_interval_seconds(10); + policy->set_renew_with_usage(false); + + response->set_license_start_time(request.request_time()); + response->set_remote_attestation_verified(false); + response->set_platform_verification_status( + video_widevine::PlatformVerificationStatus::PLATFORM_UNVERIFIED); +} + +std::string EncryptKey(const std::string& key, + const std::string& iv, + const std::vector& plaintext) { + AesCbcEncryptor encryptor; + encryptor.SetKey(reinterpret_cast(key.data()), key.size()); + + std::vector ciphertext(plaintext.size()); + CHECK(encryptor.Encrypt(reinterpret_cast(iv.data()), + iv.size(), plaintext.data(), plaintext.size(), + ciphertext.data())); + + return std::string(ciphertext.begin(), ciphertext.end()); +} + +std::string DeriveIV(const std::vector& context) { + const std::string context_str(context.begin(), context.end()); + return crypto_util::DeriveIv(context_str); +} + +} // namespace + +// static +std::vector LicenseBuilder::NoPadding() { + return {}; +} + +// static +std::vector LicenseBuilder::PKSC8Padding() { + return { + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + }; +} + +// static +std::vector LicenseBuilder::DefaultSigningKey() { + return { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + }; +} + +LicenseBuilder::LicenseBuilder() { + DCHECK_EQ(session_key_.size(), 16u); + + // Initialize the request and the response with the static fields that will + // be common across all licences. + InitializeRequest(&request_); + InitializeResponse(request_, &response_); + + serialized_request_ = request_.SerializeAsString(); + container_key_ = crypto_util::DeriveKey( + session_key_, crypto_util::kWrappingKeyLabel, serialized_request_, + crypto_util::kWrappingKeySizeBits); +} + +void LicenseBuilder::AddSigningKey(const std::vector& key, + const std::vector& padding) { + DCHECK_EQ(key.size(), 64u); // 512 bits + + auto* container = response_.add_key(); + container->set_type(video_widevine::License_KeyContainer_KeyType_SIGNING); + + // To avoid having to define a key iv for each key, derive a key iv from the + // key. This will allows us to have a different IVs between keys but keep it + // deterministic. + const auto key_iv = DeriveIV(key); + container->set_iv(key_iv); + + std::vector final_key = key; + final_key.insert(final_key.end(), padding.begin(), padding.end()); + container->set_key(EncryptKey(container_key_, key_iv, final_key)); +} + +void LicenseBuilder::AddStubbedContentKey() { + auto* container = response_.add_key(); + + container->set_type(video_widevine::License_KeyContainer_KeyType_CONTENT); + container->set_id("stubbed-content-key"); + container->set_level( + video_widevine::License_KeyContainer_SecurityLevel_SW_SECURE_CRYPTO); + container->set_iv("0000000000000000"); + + // We don't bother encrypting the key, it should never be used and there is no + // way to verify it. + container->set_key("0000000000000000"); +} + +void LicenseBuilder::AddContentKey( + video_widevine::License_KeyContainer_SecurityLevel level, + const std::vector& key_id, + const std::vector& key, + const std::vector& padding) { + DCHECK_GT(key_id.size(), 0u); + DCHECK_EQ(key.size(), 16u); + + auto* container = response_.add_key(); + + container->set_type(video_widevine::License_KeyContainer_KeyType_CONTENT); + container->set_id(key_id.data(), key_id.size()); + container->set_level(level); + + // To avoid having to define a key iv for each key, derive a key iv from the + // key. This will allows us to have a different IVs between keys but keep it + // deterministic. + const auto key_iv = DeriveIV(key); + container->set_iv(key_iv); + + std::vector final_key = key; + final_key.insert(final_key.end(), padding.begin(), padding.end()); + container->set_key(EncryptKey(container_key_, key_iv, final_key)); +} + +void LicenseBuilder::AddOperatorSessionKey(const std::vector& key_id) { + DCHECK_GT(key_id.size(), 0u); + + // We only set the type and id because the key should not actually be used. + auto* container = response_.add_key(); + container->set_type( + video_widevine::License_KeyContainer_KeyType_OPERATOR_SESSION); + container->set_id(key_id.data(), key_id.size()); +} + +void LicenseBuilder::Build(const RsaPublicKey& public_key, + License* license) const { + DCHECK(license); + + const std::string message_str = response_.SerializeAsString(); + + std::string signing_key = crypto_util::DeriveKey( + session_key_, crypto_util::kSigningKeyLabel, serialized_request_, + crypto_util::kSigningKeySizeBits * 2); + signing_key.resize(crypto_util::kSigningKeySizeBytes); + + const std::string signature_str = + crypto_util::CreateSignatureHmacSha256(signing_key, message_str); + + std::string session_key_str; + CHECK(public_key.Encrypt(session_key_, &session_key_str)); + + license->request = std::vector(serialized_request_.begin(), + serialized_request_.end()); + license->message = + std::vector(message_str.begin(), message_str.end()); + license->signature = + std::vector(signature_str.begin(), signature_str.end()); + license->session_key = + std::vector(session_key_str.begin(), session_key_str.end()); +} +} // namespace widevine diff --git a/api/license_builder.h b/api/license_builder.h new file mode 100644 index 0000000..93fa525 --- /dev/null +++ b/api/license_builder.h @@ -0,0 +1,70 @@ +// Copyright 2020 Google LLC. All Rights Reserved. + +#ifndef WHITEBOX_API_LICENSE_BUILDER_H_ +#define WHITEBOX_API_LICENSE_BUILDER_H_ + +#include +#include +#include + +#include "cdm/protos/license_protocol.pb.h" +#include "crypto_utils/rsa_key.h" + +namespace widevine { + +struct License { + std::vector request; + std::vector message; + std::vector signature; + + // |session_key_| encrypted using the public key. The white-box expects the + // session key to be encrypted, so we use the name "session_key_" (even if it + // is encrypted), we omit the term "encrypted" to match the naming in the API. + std::vector session_key; +}; + +class LicenseBuilder { + public: + // Returns padding data the can be used as |padding| when calling + // AddSigningKey() or AddContentKey(). + static std::vector NoPadding(); + static std::vector PKSC8Padding(); + + // Returns a default signing key that can be used with AddSigningKey(). + static std::vector DefaultSigningKey(); + + LicenseBuilder(); + + void AddSigningKey(const std::vector& key, + const std::vector& padding = NoPadding()); + + // Add a content key so that there is some key in the license. This should not + // be used with AddContentKey(). + void AddStubbedContentKey(); + + void AddContentKey(video_widevine::License_KeyContainer_SecurityLevel level, + const std::vector& key_id, + const std::vector& key, + const std::vector& padding = NoPadding()); + + // The key id will matter as we will need to reference it, but the key won't + // matter since we are only using it as a means to verify that a non-content + // key can't be used as a content key. + void AddOperatorSessionKey(const std::vector& key_id); + + // Gets the serialized license request and response (in components) that would + // have been used in the license exchange. + void Build(const RsaPublicKey& public_key, License* license) const; + + private: + const std::string session_key_ = "0123456789ABCDEF"; + + video_widevine::LicenseRequest request_; + video_widevine::License response_; + std::string serialized_request_; + std::string container_key_; +}; + +} // namespace widevine + +#endif // WHITEBOX_API_LICENSE_BUILDER_H_ diff --git a/api/license_whitebox.h b/api/license_whitebox.h index 9a6ebd8..3071fe6 100644 --- a/api/license_whitebox.h +++ b/api/license_whitebox.h @@ -80,8 +80,12 @@ WB_Result WB_License_SignLicenseRequest(const WB_License_Whitebox* whitebox, uint8_t* signature, size_t* signature_size); -// Verifies a license response using HMAC and the server signing key, extract -// the content keys and signing keys, and load them into the white-box. +// Verifies a license response using HMAC and the server signing key. +// +// Extracts and loads content and signing keys for use. Any content keys that +// exceed the security levels permitted by the instance, will be thrown away, +// but the key ids are retained (see WB_RESULT_INSUFFICIENT_SECURITY_LEVEL). All +// non-content keys and non-signing keys will be thrown away. // // This function can only be called once per white-box instance. To parse a new // license response, a new white-box instance must be used. @@ -110,16 +114,17 @@ WB_Result WB_License_SignLicenseRequest(const WB_License_Whitebox* whitebox, // WB_RESULT_OK if the response was verified and the keys were loaded into // |whitebox|. // -// WB_RESULT_INVALID_SIGNATURE if |message|'s signature does not match -// |signature|. -// // WB_RESULT_INVALID_PARAMETER if |whitebox| was null, if |message| was null, // if |message_size| was zero, if |message| did not conform to the expected // format, if |signature| was null, if |signature_size| was incorrect, if // |session_key| was null, if |session_key_size| was incorrect, if // |session_key| could not be unwrapped correctly, if |license_request| was -// null, if |license_request_size| was zero, or if |license_request| was -// malformed. +// null, or if |license_request_size| was zero. +// +// WB_RESULT_INVALID_SIGNATURE if |message|'s signature does not match +// |signature|. +// +// WB_RESULT_INVALID_STATE if a license has already been loaded. WB_Result WB_License_ProcessLicenseResponse(WB_License_Whitebox* whitebox, const uint8_t* message, size_t message_size, @@ -186,7 +191,7 @@ WB_Result WB_License_SignRenewalRequest(const WB_License_Whitebox* whitebox, // WB_RESULT_INVALID_SIGNATURE if |message|'s signature did not match // |signature|. // -// WB_RESULT_INVALID_STATE if |whitebox| had no keys. +// WB_RESULT_INVALID_STATE if |whitebox| had not loaded a license. WB_Result WB_License_VerifyRenewalResponse(const WB_License_Whitebox* whitebox, const uint8_t* message, size_t message_size, @@ -225,19 +230,16 @@ WB_Result WB_License_VerifyRenewalResponse(const WB_License_Whitebox* whitebox, // if |key_id| was null, if |key_id_size| was zero, if |secret_string| was // null, or if |secret_string_size| was null. // -// WB_RESULT_NO_SUCH_KEY if |key_id| did not match any keys in |whitebox|. +// WB_RESULT_INSUFFICIENT_SECURITY_LEVEL if |key_id| referred to a key from +// the license, but the |whitebox| was not allowed to use it. // -// WB_RESULT_WRONG_KEY_TYPE if |key_id| referred to a key in |whitebox| but the -// key was not a content key. -// -// WB_RESULT_INSUFFICIENT_SECURITY_LEVEL if |key_id| refers to a key in -// |whitebox| but the key's security level was neither SW_SECURE_CRYPTO nor -// SW_SECURE_DECODE. +// WB_RESULT_KEY_UNAVAILABLE if |key_id| did not match a content key from the +// loaded license. // // WB_RESULT_BUFFER_TOO_SMALL if |secret_string_size| (as input) was less than // the required size. // -// WB_RESULT_INVALID_STATE if |whitebox| had no keys. +// WB_RESULT_INVALID_STATE if |whitebox| had not loaded a license. WB_Result WB_License_GetSecretString(const WB_License_Whitebox* whitebox, WB_CipherMode mode, const uint8_t* key_id, @@ -283,18 +285,16 @@ WB_Result WB_License_GetSecretString(const WB_License_Whitebox* whitebox, // if |input_data_size| was invalid, if |iv| was null, if |iv_size| was // invalid, if |output_data| was null, or if |output_data_size| was null. // -// WB_RESULT_NO_SUCH_KEY if |key_id| matches no key in |whitebox|. +// WB_RESULT_INSUFFICIENT_SECURITY_LEVEL if |key_id| referred to a key from +// the license, but the |whitebox| was not allowed to use it. // -// WB_RESULT_WRONG_KEY_TYPE if |key_id| referred to a key in |whitebox| but -// the key was not a content key. -// -// WB_RESULT_INSUFFICIENT_SECURITY_LEVEL if |key_id| referred to a key in -// |whitebox| but the key's security level was not SW_SECURE_CRYPTO. -// -// WB_RESULT_INVALID_STATE if |whitebox| had no keys. +// WB_RESULT_KEY_UNAVAILABLE if |key_id| did not match a content key from the +// loaded license. // // WB_RESULT_BUFFER_TOO_SMALL if |output_data_size| (as input) was less than // the required size. +// +// WB_RESULT_INVALID_STATE if |whitebox| had not loaded a license. WB_Result WB_License_Decrypt(const WB_License_Whitebox* whitebox, WB_CipherMode mode, const uint8_t* key_id, @@ -346,21 +346,19 @@ WB_Result WB_License_Decrypt(const WB_License_Whitebox* whitebox, // WB_RESULT_INVALID_PARAMETER if |whitebox| was null, if |mode| was invalid, // if |key_id| was null, if |key_id_size| was zero, if |input_data| was null, // if |input_data_size| was invalid, if |iv| was null, if |iv_size| was -// invalid, if |output_data| was null, or if |output_data_size| was null. +// invalid, if |masked_output_data| was null, or if |masked_output_data_size| +// was null. // -// WB_RESULT_NO_SUCH_KEY if |key_id| matches no key in |whitebox|. +// WB_RESULT_INSUFFICIENT_SECURITY_LEVEL if |key_id| referred to a key from +// the license, but the |whitebox| was not allowed to use it. // -// WB_RESULT_WRONG_KEY_TYPE if |key_id| referred to a key in |whitebox| but the -// key was not a content key. -// -// WB_RESULT_INSUFFICIENT_SECURITY_LEVEL if |key_id| referred to a key in -// |whitebox| but the key's security level was neither SW_SECURE_CRYPTO nor -// SW_SECURE_DECODE. -// -// WB_RESULT_INVALID_STATE if |whitebox| had no keys. +// WB_RESULT_KEY_UNAVAILABLE if |key_id| did not match a content key from the +// loaded license. // // WB_RESULT_BUFFER_TOO_SMALL if |masked_output_data_size| (as input) was less // than the required size. +// +// WB_RESULT_INVALID_STATE if |whitebox| had not loaded a license. WB_Result WB_License_MaskedDecrypt(const WB_License_Whitebox* whitebox, WB_CipherMode mode, const uint8_t* key_id, diff --git a/api/license_whitebox_decrypt_test.cc b/api/license_whitebox_decrypt_test.cc index 33e3fbc..365349f 100644 --- a/api/license_whitebox_decrypt_test.cc +++ b/api/license_whitebox_decrypt_test.cc @@ -6,315 +6,470 @@ #include #include +#include "api/golden_data.h" +#include "api/license_builder.h" +#include "api/license_whitebox_test_base.h" #include "api/test_data.h" +#include "crypto_utils/rsa_key.h" #include "testing/include/gtest/gtest.h" -namespace { +namespace widevine { -// All decrypt tests require a valid (initialized) white-box. However, not all -// tests need to load a license, so that is left out of SetUp(). -class LicenseWhiteboxDecryptTest : public ::testing::Test { +class LicenseWhiteboxDecryptTest : public LicenseWhiteboxTestBase { protected: void SetUp() override { - // All of our tests here require a valid whitebox. - std::vector init_data = GetLicenseInitData(); - ASSERT_GT(init_data.size(), 0u); - ASSERT_EQ(WB_License_Create(init_data.data(), init_data.size(), &whitebox_), + LicenseWhiteboxTestBase::SetUp(); + + // Because we are using the same buffer for both CTR and CBC, need to make + // sure that it is large enough for either one. + plaintext_size_ = std::max(golden_data_.CBCContent().ciphertext.size(), + golden_data_.CTRContent().ciphertext.size()); + plaintext_.resize(plaintext_size_); + + golden_data_.MakeKeyIdDifferent(&non_content_key_id_); + golden_data_.MakeKeyIdDifferent(&missing_key_id_); + } + + void LoadLicense(const std::vector& padding) { + LicenseBuilder builder; + + builder.AddContentKey(golden_data_.CBCCryptoKey().level, + golden_data_.CBCCryptoKey().id, + golden_data_.CBCCryptoKey().content->key); + + builder.AddContentKey(golden_data_.CTRCryptoKey().level, + golden_data_.CTRCryptoKey().id, + golden_data_.CTRCryptoKey().content->key); + + builder.AddContentKey(golden_data_.CBCDecodeKey().level, + golden_data_.CBCDecodeKey().id, + golden_data_.CBCDecodeKey().content->key); + + builder.AddContentKey(golden_data_.CBCHardwareKey().level, + golden_data_.CBCHardwareKey().id, + golden_data_.CBCHardwareKey().content->key); + + builder.AddOperatorSessionKey(non_content_key_id_); + + License license; + builder.Build(*public_key_, &license); + + ASSERT_EQ(WB_License_ProcessLicenseResponse( + whitebox_, license.message.data(), license.message.size(), + license.signature.data(), license.signature.size(), + license.session_key.data(), license.session_key.size(), + license.request.data(), license.request.size()), WB_RESULT_OK); - ASSERT_TRUE(whitebox_); } - void TearDown() override { WB_License_Delete(whitebox_); } + // We need two special keys for this test, one that will be used for a + // non-content key and one that will never be in the license. + std::vector non_content_key_id_ = {0, 0, 0}; + std::vector missing_key_id_ = {1, 0, 0}; - void LoadLicense() { - // TODO: Load the license here. It would be nice if we could do it in - // SetUp(), but since we need to support the WB_RESULT_INVALID_STATE test - // case, we need a way to not load a license. - } - - WB_License_Whitebox* whitebox_ = nullptr; - - // TODO(vaage): Replace key ids with the key ids in the license. Since we - // don't have a license right now, any id will do. - const std::vector crypto_key_id_ = {0xAA, 0xBB, 0xAA, 0xBB}; - const std::vector decode_key_id_ = {0xAA, 0xBB, 0xAA, 0xBB}; - const std::vector renewal_key_id_ = {0xAA, 0xBB, 0xAA, 0xBB}; - - // TODO(vaage): Replace with golden ciphertext that will match the golden - // plaintext. - const std::vector ciphertext_ = { - 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, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - }; - - const std::vector iv_ = { - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - }; - - // TODO(vaage): Replace with golden plaintext that will match the golden - // ciphertext. - const std::vector plaintext_ = { - 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, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - }; + // This is the buffer used to store the output of each decrypt call. + size_t plaintext_size_; + std::vector plaintext_; }; -TEST_F(LicenseWhiteboxDecryptTest, Success) { - // TODO(vaage): This test will fail for now because it will attempt to look-up - // the key before attempting to decrypt anything. Since |plaintext_| and - // |ciphertext_| are not even set correctly, this wouldn't pass even if it had - // a license. - GTEST_SKIP(); +TEST_F(LicenseWhiteboxDecryptTest, CryptoKeyWithCbcDataInCbcMode) { + LoadLicense(LicenseBuilder::NoPadding()); - LoadLicense(); + ASSERT_EQ( + WB_License_Decrypt(whitebox_, WB_CIPHER_MODE_CBC, + golden_data_.CBCCryptoKey().id.data(), + golden_data_.CBCCryptoKey().id.size(), + golden_data_.CBCCryptoKey().content->ciphertext.data(), + golden_data_.CBCCryptoKey().content->ciphertext.size(), + golden_data_.CBCCryptoKey().content->iv.data(), + golden_data_.CBCCryptoKey().content->iv.size(), + plaintext_.data(), &plaintext_size_), + WB_RESULT_OK); + plaintext_.resize(plaintext_size_); + ASSERT_EQ(plaintext_, golden_data_.CBCCryptoKey().content->plaintext); +} - size_t plaintext_size = ciphertext_.size(); - std::vector plaintext(plaintext_size); +TEST_F(LicenseWhiteboxDecryptTest, CryptoKeyWithCtrDataInCtrMode) { + LoadLicense(LicenseBuilder::NoPadding()); - ASSERT_EQ(WB_License_Decrypt( - whitebox_, WB_CIPHER_MODE_CBC, crypto_key_id_.data(), - crypto_key_id_.size(), ciphertext_.data(), ciphertext_.size(), - iv_.data(), iv_.size(), plaintext.data(), &plaintext_size), - WB_RESULT_OK); + ASSERT_EQ( + WB_License_Decrypt(whitebox_, WB_CIPHER_MODE_CTR, + golden_data_.CTRCryptoKey().id.data(), + golden_data_.CTRCryptoKey().id.size(), + golden_data_.CTRCryptoKey().content->ciphertext.data(), + golden_data_.CTRCryptoKey().content->ciphertext.size(), + golden_data_.CTRCryptoKey().content->iv.data(), + golden_data_.CTRCryptoKey().content->iv.size(), + plaintext_.data(), &plaintext_size_), + WB_RESULT_OK); + plaintext_.resize(plaintext_size_); + ASSERT_EQ(plaintext_, golden_data_.CTRCryptoKey().content->plaintext); +} - // Since we are not using any padding, the ciphertext and plaintext should be - // the same size. However, in the case that something went wrong, resize the - // plaintext so that ASSERT_EQ will operate on the correct size. - plaintext.resize(plaintext_size); +// We try to decrypt CBC encrypted data in CTR mode. All operations should be +// successful, but the resulting plaintext should not match. +TEST_F(LicenseWhiteboxDecryptTest, CryptoKeyWithCbcDataInCtrMode) { + LoadLicense(LicenseBuilder::NoPadding()); - ASSERT_EQ(plaintext, plaintext_); + ASSERT_EQ( + WB_License_Decrypt(whitebox_, WB_CIPHER_MODE_CTR, + golden_data_.CBCCryptoKey().id.data(), + golden_data_.CBCCryptoKey().id.size(), + golden_data_.CBCCryptoKey().content->ciphertext.data(), + golden_data_.CBCCryptoKey().content->ciphertext.size(), + golden_data_.CBCCryptoKey().content->iv.data(), + golden_data_.CBCCryptoKey().content->iv.size(), + plaintext_.data(), &plaintext_size_), + WB_RESULT_OK); + plaintext_.resize(plaintext_size_); + ASSERT_NE(plaintext_, golden_data_.CBCCryptoKey().content->plaintext); +} + +// We try to decrypt CTR encrypted data in CBC mode. All operations should be +// successful, but the resulting plaintext should not match. +TEST_F(LicenseWhiteboxDecryptTest, CryptoKeyWithCtrDataInCbcMode) { + LoadLicense(LicenseBuilder::NoPadding()); + + ASSERT_EQ( + WB_License_Decrypt(whitebox_, WB_CIPHER_MODE_CBC, + golden_data_.CTRCryptoKey().id.data(), + golden_data_.CTRCryptoKey().id.size(), + golden_data_.CTRCryptoKey().content->ciphertext.data(), + golden_data_.CTRCryptoKey().content->ciphertext.size(), + golden_data_.CTRCryptoKey().content->iv.data(), + golden_data_.CTRCryptoKey().content->iv.size(), + plaintext_.data(), &plaintext_size_), + WB_RESULT_OK); + plaintext_.resize(plaintext_size_); + ASSERT_NE(plaintext_, golden_data_.CTRCryptoKey().content->plaintext); +} + +TEST_F(LicenseWhiteboxDecryptTest, CryptoKeyWithCbcDataAndPKCS8Padding) { + LoadLicense(LicenseBuilder::PKSC8Padding()); + + ASSERT_EQ( + WB_License_Decrypt(whitebox_, WB_CIPHER_MODE_CBC, + golden_data_.CBCCryptoKey().id.data(), + golden_data_.CBCCryptoKey().id.size(), + golden_data_.CBCCryptoKey().content->ciphertext.data(), + golden_data_.CBCCryptoKey().content->ciphertext.size(), + golden_data_.CBCCryptoKey().content->iv.data(), + golden_data_.CBCCryptoKey().content->iv.size(), + plaintext_.data(), &plaintext_size_), + WB_RESULT_OK); + plaintext_.resize(plaintext_size_); + ASSERT_EQ(plaintext_, golden_data_.CBCCryptoKey().content->plaintext); +} + +TEST_F(LicenseWhiteboxDecryptTest, CryptoKeyWithCtrDataAndPKCS8Padding) { + LoadLicense(LicenseBuilder::PKSC8Padding()); + + ASSERT_EQ( + WB_License_Decrypt(whitebox_, WB_CIPHER_MODE_CTR, + golden_data_.CTRCryptoKey().id.data(), + golden_data_.CTRCryptoKey().id.size(), + golden_data_.CTRCryptoKey().content->ciphertext.data(), + golden_data_.CTRCryptoKey().content->ciphertext.size(), + golden_data_.CTRCryptoKey().content->iv.data(), + golden_data_.CTRCryptoKey().content->iv.size(), + plaintext_.data(), &plaintext_size_), + WB_RESULT_OK); + plaintext_.resize(plaintext_size_); + ASSERT_EQ(plaintext_, golden_data_.CTRCryptoKey().content->plaintext); +} + +// Try decrypting two different sets of content to make sure that two +// different keys can be used at the same time. +TEST_F(LicenseWhiteboxDecryptTest, SuccessWithMultipleKeys) { + LoadLicense(LicenseBuilder::NoPadding()); + + ASSERT_EQ( + WB_License_Decrypt(whitebox_, WB_CIPHER_MODE_CBC, + golden_data_.CBCCryptoKey().id.data(), + golden_data_.CBCCryptoKey().id.size(), + golden_data_.CBCCryptoKey().content->ciphertext.data(), + golden_data_.CBCCryptoKey().content->ciphertext.size(), + golden_data_.CBCCryptoKey().content->iv.data(), + golden_data_.CBCCryptoKey().content->iv.size(), + plaintext_.data(), &plaintext_size_), + WB_RESULT_OK); + plaintext_.resize(plaintext_size_); + ASSERT_EQ(plaintext_, golden_data_.CBCCryptoKey().content->plaintext); + + // Reset our output buffer. + plaintext_.clear(); + plaintext_size_ = golden_data_.CTRDecodeKey().content->plaintext.size(); + plaintext_.resize(plaintext_size_); + + ASSERT_EQ( + WB_License_Decrypt(whitebox_, WB_CIPHER_MODE_CTR, + golden_data_.CTRCryptoKey().id.data(), + golden_data_.CTRCryptoKey().id.size(), + golden_data_.CTRCryptoKey().content->ciphertext.data(), + golden_data_.CTRCryptoKey().content->ciphertext.size(), + golden_data_.CTRCryptoKey().content->iv.data(), + golden_data_.CTRCryptoKey().content->iv.size(), + plaintext_.data(), &plaintext_size_), + WB_RESULT_OK); + + plaintext_.resize(plaintext_size_); + ASSERT_EQ(plaintext_, golden_data_.CTRCryptoKey().content->plaintext); } TEST_F(LicenseWhiteboxDecryptTest, InvalidParameterForNullWhitebox) { - LoadLicense(); + LoadLicense(LicenseBuilder::NoPadding()); - size_t plaintext_size = ciphertext_.size(); - std::vector plaintext(plaintext_size); - - ASSERT_EQ(WB_License_Decrypt( - nullptr, WB_CIPHER_MODE_CBC, crypto_key_id_.data(), - crypto_key_id_.size(), ciphertext_.data(), ciphertext_.size(), - iv_.data(), iv_.size(), plaintext.data(), &plaintext_size), - WB_RESULT_INVALID_PARAMETER); + ASSERT_EQ( + WB_License_Decrypt(nullptr, WB_CIPHER_MODE_CBC, + golden_data_.CBCCryptoKey().id.data(), + golden_data_.CBCCryptoKey().id.size(), + golden_data_.CBCCryptoKey().content->ciphertext.data(), + golden_data_.CBCCryptoKey().content->ciphertext.size(), + golden_data_.CBCCryptoKey().content->iv.data(), + golden_data_.CBCCryptoKey().content->iv.size(), + plaintext_.data(), &plaintext_size_), + WB_RESULT_INVALID_PARAMETER); } TEST_F(LicenseWhiteboxDecryptTest, InvalidParameterForInvalidCipherMode) { - // TODO(vaage): This test will fail for now because it will attempt to - // look-up the key before attempting to decrypt anything. - GTEST_SKIP(); - - LoadLicense(); + LoadLicense(LicenseBuilder::NoPadding()); // In order to trick the compiler into letting us pass an invalid enum value // to WB__License_Decrypt(), we need to cast it. If we don't do this, the // compiler tries to save us. const WB_CipherMode invalid_mode = static_cast(0xFF); - size_t plaintext_size = ciphertext_.size(); - std::vector plaintext(plaintext_size); - - ASSERT_EQ(WB_License_Decrypt(whitebox_, invalid_mode, crypto_key_id_.data(), - crypto_key_id_.size(), ciphertext_.data(), - ciphertext_.size(), iv_.data(), iv_.size(), - plaintext.data(), &plaintext_size), + ASSERT_EQ(WB_License_Decrypt( + whitebox_, invalid_mode, golden_data_.CBCCryptoKey().id.data(), + golden_data_.CBCCryptoKey().id.size(), + golden_data_.CBCCryptoKey().content->ciphertext.data(), + golden_data_.CBCCryptoKey().content->ciphertext.size(), + golden_data_.CBCCryptoKey().content->iv.data(), + golden_data_.CBCCryptoKey().content->iv.size(), + plaintext_.data(), &plaintext_size_), WB_RESULT_INVALID_PARAMETER); } TEST_F(LicenseWhiteboxDecryptTest, InvalidParameterForNullKeyId) { - LoadLicense(); - - size_t plaintext_size = ciphertext_.size(); - std::vector plaintext(plaintext_size); - - ASSERT_EQ(WB_License_Decrypt(whitebox_, WB_CIPHER_MODE_CBC, nullptr, - crypto_key_id_.size(), ciphertext_.data(), - ciphertext_.size(), iv_.data(), iv_.size(), - plaintext.data(), &plaintext_size), - WB_RESULT_INVALID_PARAMETER); -} - -TEST_F(LicenseWhiteboxDecryptTest, InvalidParameterForNullZeroKeyIdSize) { - LoadLicense(); - - size_t plaintext_size = ciphertext_.size(); - std::vector plaintext(plaintext_size); + LoadLicense(LicenseBuilder::NoPadding()); ASSERT_EQ( - WB_License_Decrypt(whitebox_, WB_CIPHER_MODE_CBC, crypto_key_id_.data(), - 0, ciphertext_.data(), ciphertext_.size(), iv_.data(), - iv_.size(), plaintext.data(), &plaintext_size), + WB_License_Decrypt(whitebox_, WB_CIPHER_MODE_CBC, nullptr, + golden_data_.CBCCryptoKey().id.size(), + golden_data_.CBCCryptoKey().content->ciphertext.data(), + golden_data_.CBCCryptoKey().content->ciphertext.size(), + golden_data_.CBCCryptoKey().content->iv.data(), + golden_data_.CBCCryptoKey().content->iv.size(), + plaintext_.data(), &plaintext_size_), + WB_RESULT_INVALID_PARAMETER); +} + +TEST_F(LicenseWhiteboxDecryptTest, InvalidParameterForZeroKeyIdSize) { + LoadLicense(LicenseBuilder::NoPadding()); + + ASSERT_EQ( + WB_License_Decrypt(whitebox_, WB_CIPHER_MODE_CBC, + golden_data_.CBCCryptoKey().id.data(), 0, + golden_data_.CBCCryptoKey().content->ciphertext.data(), + golden_data_.CBCCryptoKey().content->ciphertext.size(), + golden_data_.CBCCryptoKey().content->iv.data(), + golden_data_.CBCCryptoKey().content->iv.size(), + plaintext_.data(), &plaintext_size_), WB_RESULT_INVALID_PARAMETER); } TEST_F(LicenseWhiteboxDecryptTest, InvalidParameterForNullInputData) { - // TODO(vaage): This test will fail for now because it will attempt to - // look-up the key before attempting to decrypt anything. - GTEST_SKIP(); + LoadLicense(LicenseBuilder::NoPadding()); - LoadLicense(); - - size_t plaintext_size = ciphertext_.size(); - std::vector plaintext(plaintext_size); - - ASSERT_EQ(WB_License_Decrypt(whitebox_, WB_CIPHER_MODE_CBC, - crypto_key_id_.data(), crypto_key_id_.size(), - nullptr, ciphertext_.size(), iv_.data(), - iv_.size(), plaintext.data(), &plaintext_size), - WB_RESULT_INVALID_PARAMETER); + ASSERT_EQ( + WB_License_Decrypt(whitebox_, WB_CIPHER_MODE_CBC, + golden_data_.CBCCryptoKey().id.data(), + golden_data_.CBCCryptoKey().id.size(), nullptr, + golden_data_.CBCCryptoKey().content->ciphertext.size(), + golden_data_.CBCCryptoKey().content->iv.data(), + golden_data_.CBCCryptoKey().content->iv.size(), + plaintext_.data(), &plaintext_size_), + WB_RESULT_INVALID_PARAMETER); } // AES CBC requires that the input be block aligned (multiple of 16). CTR does -// not care. Size zero input is not considered invalid input. -TEST_F(LicenseWhiteboxDecryptTest, - InvalidParameterForCBCAndInvalidInputDataSize) { - // TODO(vaage): This test will fail for now because it will attempt to - // look-up the key before attempting to decrypt anything. - GTEST_SKIP(); +// not care. +TEST_F(LicenseWhiteboxDecryptTest, InvalidParameterForInvalidCBCInputDataSize) { + LoadLicense(LicenseBuilder::NoPadding()); - LoadLicense(); + ASSERT_EQ( + WB_License_Decrypt(whitebox_, WB_CIPHER_MODE_CBC, + golden_data_.CBCCryptoKey().id.data(), + golden_data_.CBCCryptoKey().id.size(), + golden_data_.CBCCryptoKey().content->ciphertext.data(), + 14, golden_data_.CBCCryptoKey().content->iv.data(), + golden_data_.CBCCryptoKey().content->iv.size(), + plaintext_.data(), &plaintext_size_), + WB_RESULT_INVALID_PARAMETER); +} - size_t plaintext_size = ciphertext_.size(); - std::vector plaintext(plaintext_size); +// The white-box (using any cipher mode) should reject input with size zero. +TEST_F(LicenseWhiteboxDecryptTest, InvalidParameterForZeroInputDataSize) { + LoadLicense(LicenseBuilder::NoPadding()); - ASSERT_EQ(WB_License_Decrypt(whitebox_, WB_CIPHER_MODE_CBC, - crypto_key_id_.data(), crypto_key_id_.size(), - ciphertext_.data(), 14, iv_.data(), iv_.size(), - plaintext.data(), &plaintext_size), - WB_RESULT_INVALID_PARAMETER); + ASSERT_EQ( + WB_License_Decrypt(whitebox_, WB_CIPHER_MODE_CBC, + golden_data_.CBCCryptoKey().id.data(), + golden_data_.CBCCryptoKey().id.size(), + golden_data_.CBCCryptoKey().content->ciphertext.data(), + 0, golden_data_.CBCCryptoKey().content->iv.data(), + golden_data_.CBCCryptoKey().content->iv.size(), + plaintext_.data(), &plaintext_size_), + WB_RESULT_INVALID_PARAMETER); } TEST_F(LicenseWhiteboxDecryptTest, InvalidParameterForNullIV) { - // TODO(vaage): This test will fail for now because it will attempt to - // look-up the key before attempting to decrypt anything. - GTEST_SKIP(); + LoadLicense(LicenseBuilder::NoPadding()); - LoadLicense(); - - size_t plaintext_size = ciphertext_.size(); - std::vector plaintext(plaintext_size); - - ASSERT_EQ(WB_License_Decrypt(whitebox_, WB_CIPHER_MODE_CBC, - crypto_key_id_.data(), crypto_key_id_.size(), - ciphertext_.data(), ciphertext_.size(), nullptr, - iv_.size(), plaintext.data(), &plaintext_size), - WB_RESULT_INVALID_PARAMETER); + ASSERT_EQ( + WB_License_Decrypt( + whitebox_, WB_CIPHER_MODE_CBC, golden_data_.CBCCryptoKey().id.data(), + golden_data_.CBCCryptoKey().id.size(), + golden_data_.CBCCryptoKey().content->ciphertext.data(), + golden_data_.CBCCryptoKey().content->ciphertext.size(), nullptr, + golden_data_.CBCCryptoKey().content->iv.size(), plaintext_.data(), + &plaintext_size_), + WB_RESULT_INVALID_PARAMETER); } // IV size should be 16. Any number other than 16 should fail. TEST_F(LicenseWhiteboxDecryptTest, InvalidParameterForInvalidIVSize) { - // TODO(vaage): This test will fail for now because it will attempt to - // look-up the key before attempting to decrypt anything. - GTEST_SKIP(); + LoadLicense(LicenseBuilder::NoPadding()); - LoadLicense(); - - size_t plaintext_size = ciphertext_.size(); - std::vector plaintext(plaintext_size); - - ASSERT_EQ(WB_License_Decrypt( - whitebox_, WB_CIPHER_MODE_CBC, crypto_key_id_.data(), - crypto_key_id_.size(), ciphertext_.data(), ciphertext_.size(), - iv_.data(), 9, plaintext.data(), &plaintext_size), - WB_RESULT_INVALID_PARAMETER); + ASSERT_EQ( + WB_License_Decrypt(whitebox_, WB_CIPHER_MODE_CBC, + golden_data_.CBCCryptoKey().id.data(), + golden_data_.CBCCryptoKey().id.size(), + golden_data_.CBCCryptoKey().content->ciphertext.data(), + golden_data_.CBCCryptoKey().content->ciphertext.size(), + golden_data_.CBCCryptoKey().content->iv.data(), 9, + plaintext_.data(), &plaintext_size_), + WB_RESULT_INVALID_PARAMETER); } TEST_F(LicenseWhiteboxDecryptTest, InvalidParameterForNullOutput) { - // TODO(vaage): This test will fail for now because it will attempt to - // look-up the key before attempting to decrypt anything. - GTEST_SKIP(); + LoadLicense(LicenseBuilder::NoPadding()); - LoadLicense(); - - size_t plaintext_size = ciphertext_.size(); - - ASSERT_EQ(WB_License_Decrypt( - whitebox_, WB_CIPHER_MODE_CBC, crypto_key_id_.data(), - crypto_key_id_.size(), ciphertext_.data(), ciphertext_.size(), - iv_.data(), iv_.size(), nullptr, &plaintext_size), - WB_RESULT_INVALID_PARAMETER); + ASSERT_EQ( + WB_License_Decrypt(whitebox_, WB_CIPHER_MODE_CBC, + golden_data_.CBCCryptoKey().id.data(), + golden_data_.CBCCryptoKey().id.size(), + golden_data_.CBCCryptoKey().content->ciphertext.data(), + golden_data_.CBCCryptoKey().content->ciphertext.size(), + golden_data_.CBCCryptoKey().content->iv.data(), + golden_data_.CBCCryptoKey().content->iv.size(), + nullptr, &plaintext_size_), + WB_RESULT_INVALID_PARAMETER); } TEST_F(LicenseWhiteboxDecryptTest, InvalidParameterForNullOutputSize) { - // TODO(vaage): This test will fail for now because it will attempt to - // look-up the key before attempting to decrypt anything. - GTEST_SKIP(); + LoadLicense(LicenseBuilder::NoPadding()); - LoadLicense(); - - size_t plaintext_size = ciphertext_.size(); - std::vector plaintext(plaintext_size); - - ASSERT_EQ(WB_License_Decrypt( - whitebox_, WB_CIPHER_MODE_CBC, crypto_key_id_.data(), - crypto_key_id_.size(), ciphertext_.data(), ciphertext_.size(), - iv_.data(), iv_.size(), plaintext.data(), nullptr), - WB_RESULT_INVALID_PARAMETER); + ASSERT_EQ( + WB_License_Decrypt(whitebox_, WB_CIPHER_MODE_CBC, + golden_data_.CBCCryptoKey().id.data(), + golden_data_.CBCCryptoKey().id.size(), + golden_data_.CBCCryptoKey().content->ciphertext.data(), + golden_data_.CBCCryptoKey().content->ciphertext.size(), + golden_data_.CBCCryptoKey().content->iv.data(), + golden_data_.CBCCryptoKey().content->iv.size(), + plaintext_.data(), nullptr), + WB_RESULT_INVALID_PARAMETER); } -TEST_F(LicenseWhiteboxDecryptTest, NoSuchKey) { - // TODO(vaage): This test will fail for now because it will attempt to - // look-up the key and see that there are no keys, which will result in - // WB_RESULT_INVALID_STATE. - GTEST_SKIP(); +// For this test, "missing key id" specifically means a key id that was never +// in the license to start with. This is different than "non content key" +// and "dropped content key", as those keys were in the license but ignored. +TEST_F(LicenseWhiteboxDecryptTest, KeyUnavailableForMissingKeyId) { + LoadLicense(LicenseBuilder::NoPadding()); - LoadLicense(); - - // For our fake key id, it is uncommon for key ids to be short. So using a one - // byte key id should safe as they are allowed, just not likely to appear in - // any test license. - std::vector not_a_key_id = {0xFF}; - - size_t plaintext_size = ciphertext_.size(); - std::vector plaintext(plaintext_size); - - ASSERT_EQ(WB_License_Decrypt( - whitebox_, WB_CIPHER_MODE_CBC, not_a_key_id.data(), - not_a_key_id.size(), ciphertext_.data(), ciphertext_.size(), - iv_.data(), iv_.size(), plaintext.data(), &plaintext_size), - WB_RESULT_NO_SUCH_KEY); + ASSERT_EQ( + WB_License_Decrypt(whitebox_, WB_CIPHER_MODE_CBC, missing_key_id_.data(), + missing_key_id_.size(), + golden_data_.CBCCryptoKey().content->ciphertext.data(), + golden_data_.CBCCryptoKey().content->ciphertext.size(), + golden_data_.CBCCryptoKey().content->iv.data(), + golden_data_.CBCCryptoKey().content->iv.size(), + plaintext_.data(), &plaintext_size_), + WB_RESULT_KEY_UNAVAILABLE); } -TEST_F(LicenseWhiteboxDecryptTest, WrongKeyType) { - // TODO(vaage): This test will fail for now because it will attempt to - // look-up the key and see that there are no keys, which will result in - // WB_RESULT_INVALID_STATE. - GTEST_SKIP(); +TEST_F(LicenseWhiteboxDecryptTest, KeyUnavailableForNonContentKey) { + LoadLicense(LicenseBuilder::NoPadding()); - LoadLicense(); - - size_t plaintext_size = ciphertext_.size(); - std::vector plaintext(plaintext_size); - - ASSERT_EQ(WB_License_Decrypt( - whitebox_, WB_CIPHER_MODE_CBC, renewal_key_id_.data(), - renewal_key_id_.size(), ciphertext_.data(), ciphertext_.size(), - iv_.data(), iv_.size(), plaintext.data(), &plaintext_size), - WB_RESULT_WRONG_KEY_TYPE); + ASSERT_EQ( + WB_License_Decrypt(whitebox_, WB_CIPHER_MODE_CBC, + non_content_key_id_.data(), non_content_key_id_.size(), + golden_data_.CBCCryptoKey().content->ciphertext.data(), + golden_data_.CBCCryptoKey().content->ciphertext.size(), + golden_data_.CBCCryptoKey().content->iv.data(), + golden_data_.CBCCryptoKey().content->iv.size(), + plaintext_.data(), &plaintext_size_), + WB_RESULT_KEY_UNAVAILABLE); } -TEST_F(LicenseWhiteboxDecryptTest, InsufficientSecurityLevel) { - // TODO(vaage): This test will fail for now because it will attempt to - // look-up the key and see that there are no keys, which will result in - // WB_RESULT_INVALID_STATE. - GTEST_SKIP(); +// Under normal circumstances, a hardware key should be dropped. The exception +// to this rule is on ChromeOS with a special license. +TEST_F(LicenseWhiteboxDecryptTest, + InsufficientSecurityLevelForHardwareContentKey) { + LoadLicense(LicenseBuilder::NoPadding()); - LoadLicense(); + ASSERT_EQ(WB_License_Decrypt( + whitebox_, WB_CIPHER_MODE_CBC, + golden_data_.CBCHardwareKey().id.data(), + golden_data_.CBCHardwareKey().id.size(), + golden_data_.CBCHardwareKey().content->ciphertext.data(), + golden_data_.CBCHardwareKey().content->ciphertext.size(), + golden_data_.CBCHardwareKey().content->iv.data(), + golden_data_.CBCHardwareKey().content->iv.size(), + plaintext_.data(), &plaintext_size_), + WB_RESULT_INSUFFICIENT_SECURITY_LEVEL); +} - size_t plaintext_size = ciphertext_.size(); - std::vector plaintext(plaintext_size); +TEST_F(LicenseWhiteboxDecryptTest, InsufficientSecurityLevelForDecodeKey) { + LoadLicense(LicenseBuilder::NoPadding()); // Use the software decode key as they are limited to - // WB_License_MaskedDecrypt(). - ASSERT_EQ(WB_License_Decrypt( - whitebox_, WB_CIPHER_MODE_CBC, decode_key_id_.data(), - decode_key_id_.size(), ciphertext_.data(), ciphertext_.size(), - iv_.data(), iv_.size(), plaintext.data(), &plaintext_size), - WB_RESULT_INSUFFICIENT_SECURITY_LEVEL); + // WB_License_Decrypt(). + ASSERT_EQ( + WB_License_Decrypt(whitebox_, WB_CIPHER_MODE_CBC, + golden_data_.CBCDecodeKey().id.data(), + golden_data_.CBCDecodeKey().id.size(), + golden_data_.CBCDecodeKey().content->ciphertext.data(), + golden_data_.CBCDecodeKey().content->ciphertext.size(), + golden_data_.CBCDecodeKey().content->iv.data(), + golden_data_.CBCDecodeKey().content->iv.size(), + plaintext_.data(), &plaintext_size_), + WB_RESULT_INSUFFICIENT_SECURITY_LEVEL); +} + +TEST_F(LicenseWhiteboxDecryptTest, BufferTooSmall) { + LoadLicense(LicenseBuilder::NoPadding()); + + // Our ciphertext will be large enough that we should not need to worry about + // using a constant here. + plaintext_size_ = 8; + + ASSERT_EQ( + WB_License_Decrypt(whitebox_, WB_CIPHER_MODE_CBC, + golden_data_.CBCCryptoKey().id.data(), + golden_data_.CBCCryptoKey().id.size(), + golden_data_.CBCCryptoKey().content->ciphertext.data(), + golden_data_.CBCCryptoKey().content->ciphertext.size(), + golden_data_.CBCCryptoKey().content->iv.data(), + golden_data_.CBCCryptoKey().content->iv.size(), + plaintext_.data(), &plaintext_size_), + WB_RESULT_BUFFER_TOO_SMALL); + + // We don't use padding so the reported plaintext size should be the same as + // the cipher text size. + ASSERT_EQ(plaintext_size_, + golden_data_.CBCCryptoKey().content->ciphertext.size()); } TEST_F(LicenseWhiteboxDecryptTest, InvalidState) { @@ -322,34 +477,16 @@ TEST_F(LicenseWhiteboxDecryptTest, InvalidState) { // WB_RESULT_INVALID_STATE is that no key can be found and keys are provided // via a license. - size_t plaintext_size = ciphertext_.size(); - std::vector plaintext(plaintext_size); - - ASSERT_EQ(WB_License_Decrypt( - whitebox_, WB_CIPHER_MODE_CBC, crypto_key_id_.data(), - crypto_key_id_.size(), ciphertext_.data(), ciphertext_.size(), - iv_.data(), iv_.size(), plaintext.data(), &plaintext_size), - WB_RESULT_INVALID_STATE); + ASSERT_EQ( + WB_License_Decrypt(whitebox_, WB_CIPHER_MODE_CBC, + golden_data_.CBCCryptoKey().id.data(), + golden_data_.CBCCryptoKey().id.size(), + golden_data_.CBCCryptoKey().content->ciphertext.data(), + golden_data_.CBCCryptoKey().content->ciphertext.size(), + golden_data_.CBCCryptoKey().content->iv.data(), + golden_data_.CBCCryptoKey().content->iv.size(), + plaintext_.data(), &plaintext_size_), + WB_RESULT_INVALID_STATE); } -TEST_F(LicenseWhiteboxDecryptTest, BufferTooSmall) { - // TODO(vaage): This test will fail for now because it will attempt to - // look-up the key before attempting to decrypt anything. - GTEST_SKIP(); - - // Our ciphertext will be large enough that we should not need to worry about - // using a constant here. - size_t plaintext_size = 8; - std::vector plaintext(plaintext_size); - - ASSERT_EQ(WB_License_Decrypt( - whitebox_, WB_CIPHER_MODE_CBC, crypto_key_id_.data(), - crypto_key_id_.size(), ciphertext_.data(), ciphertext_.size(), - iv_.data(), iv_.size(), plaintext.data(), &plaintext_size), - WB_RESULT_BUFFER_TOO_SMALL); - - // We don't use padding so the reported plaintext size should be the same as - // the cipher text size. - ASSERT_EQ(plaintext_size, ciphertext_.size()); -} -} // namespace +} // namespace widevine diff --git a/api/license_whitebox_get_secret_string_test.cc b/api/license_whitebox_get_secret_string_test.cc index 679247e..d1e3fa9 100644 --- a/api/license_whitebox_get_secret_string_test.cc +++ b/api/license_whitebox_get_secret_string_test.cc @@ -5,148 +5,221 @@ #include #include +#include "api/golden_data.h" +#include "api/license_builder.h" +#include "api/license_whitebox_test_base.h" #include "api/test_data.h" +#include "crypto_utils/rsa_key.h" #include "testing/include/gtest/gtest.h" -namespace { +namespace widevine { -class LicenseWhiteboxGetSecretStringTest : public ::testing::Test { +class LicenseWhiteboxGetSecretStringTest : public LicenseWhiteboxTestBase { protected: void SetUp() override { + LicenseWhiteboxTestBase::SetUp(); + // The secret string size is implementation specific, so this number just // needs to be reasonably large to accommodate different implementations. secret_string_size_ = 256; secret_string_.resize(secret_string_size_); - const std::vector init_data = GetLicenseInitData(); - - ASSERT_EQ(WB_License_Create(init_data.data(), init_data.size(), &whitebox_), - WB_RESULT_OK); - ASSERT_TRUE(whitebox_); + golden_data_.MakeKeyIdDifferent(&non_content_key_id_); + golden_data_.MakeKeyIdDifferent(&missing_key_id_); } - void TearDown() override { WB_License_Delete(whitebox_); } - void LoadLicense() { - // TODO: We will need to load a license for most tests as they will require - // a key id look-up and we get keys from licenses. + LicenseBuilder builder; + + builder.AddContentKey(golden_data_.CBCCryptoKey().level, + golden_data_.CBCCryptoKey().id, + golden_data_.CBCCryptoKey().content->key); + + builder.AddContentKey(golden_data_.CBCDecodeKey().level, + golden_data_.CBCDecodeKey().id, + golden_data_.CBCDecodeKey().content->key); + + builder.AddContentKey(golden_data_.CBCHardwareKey().level, + golden_data_.CBCHardwareKey().id, + golden_data_.CBCHardwareKey().content->key); + + builder.AddOperatorSessionKey(non_content_key_id_); + + License license; + builder.Build(*public_key_, &license); + + ASSERT_EQ(WB_License_ProcessLicenseResponse( + whitebox_, license.message.data(), license.message.size(), + license.signature.data(), license.signature.size(), + license.session_key.data(), license.session_key.size(), + license.request.data(), license.request.size()), + WB_RESULT_OK); } - // TODO: These needs to be replaced with the actual keys in the loaded - // license. - const std::vector crypto_key_id_ = {1, 2, 3}; - const std::vector hardware_key_id_ = {2, 3, 4}; - const std::vector bad_key_id_ = {3, 4, 5}; - const std::vector non_content_key_id_ = {4, 5, 6}; + // We need two special keys for this test, one that will be used for a + // non-content key and one that will never be in the license. + std::vector non_content_key_id_ = {0, 0, 0}; + std::vector missing_key_id_ = {1, 0, 0}; size_t secret_string_size_; std::vector secret_string_; - - WB_License_Whitebox* whitebox_; }; -TEST_F(LicenseWhiteboxGetSecretStringTest, Success) { - // TODO: This test needs to be skipped right now because we can't load a - // license and we need a license to have key ids. - GTEST_SKIP(); - +TEST_F(LicenseWhiteboxGetSecretStringTest, SuccessForCBCWithCryptoKey) { LoadLicense(); + ASSERT_EQ( WB_License_GetSecretString(whitebox_, WB_CIPHER_MODE_CBC, - crypto_key_id_.data(), crypto_key_id_.size(), + golden_data_.CBCCryptoKey().id.data(), + golden_data_.CBCCryptoKey().id.size(), secret_string_.data(), &secret_string_size_), WB_RESULT_OK); - secret_string_.resize(secret_string_size_); - ASSERT_GT(secret_string_.size(), 0); + ASSERT_GT(secret_string_size_, 0); +} + +TEST_F(LicenseWhiteboxGetSecretStringTest, SuccessForCTRWithCryptoKey) { + LoadLicense(); + + ASSERT_EQ( + WB_License_GetSecretString(whitebox_, WB_CIPHER_MODE_CTR, + golden_data_.CBCCryptoKey().id.data(), + golden_data_.CBCCryptoKey().id.size(), + secret_string_.data(), &secret_string_size_), + WB_RESULT_OK); + ASSERT_GT(secret_string_size_, 0); +} + +TEST_F(LicenseWhiteboxGetSecretStringTest, SuccessForCBCWithDecodeKey) { + LoadLicense(); + + ASSERT_EQ( + WB_License_GetSecretString(whitebox_, WB_CIPHER_MODE_CBC, + golden_data_.CBCDecodeKey().id.data(), + golden_data_.CBCDecodeKey().id.size(), + secret_string_.data(), &secret_string_size_), + WB_RESULT_OK); + ASSERT_GT(secret_string_size_, 0); +} + +TEST_F(LicenseWhiteboxGetSecretStringTest, SuccessForCTRWithDecodeKey) { + LoadLicense(); + + ASSERT_EQ( + WB_License_GetSecretString(whitebox_, WB_CIPHER_MODE_CTR, + golden_data_.CBCDecodeKey().id.data(), + golden_data_.CBCDecodeKey().id.size(), + secret_string_.data(), &secret_string_size_), + WB_RESULT_OK); + ASSERT_GT(secret_string_size_, 0); } TEST_F(LicenseWhiteboxGetSecretStringTest, InvalidParameterForNullWhitebox) { LoadLicense(); + ASSERT_EQ( WB_License_GetSecretString(nullptr, WB_CIPHER_MODE_CBC, - crypto_key_id_.data(), crypto_key_id_.size(), + golden_data_.CBCCryptoKey().id.data(), + golden_data_.CBCCryptoKey().id.size(), secret_string_.data(), &secret_string_size_), WB_RESULT_INVALID_PARAMETER); } +TEST_F(LicenseWhiteboxGetSecretStringTest, + InvalidParameterForInvalidCipherMode) { + LoadLicense(); + + // In order to trick the compiler into letting us pass an invalid enum value + // to WB__License_Decrypt(), we need to cast it. If we don't do this, the + // compiler tries to save us. + const WB_CipherMode invalid_mode = static_cast(0xFF); + + ASSERT_EQ(WB_License_GetSecretString( + whitebox_, invalid_mode, golden_data_.CBCCryptoKey().id.data(), + golden_data_.CBCCryptoKey().id.size(), secret_string_.data(), + &secret_string_size_), + WB_RESULT_INVALID_PARAMETER); +} + TEST_F(LicenseWhiteboxGetSecretStringTest, InvalidParameterForNullKeyId) { LoadLicense(); - ASSERT_EQ(WB_License_GetSecretString( - whitebox_, WB_CIPHER_MODE_CBC, nullptr, crypto_key_id_.size(), - secret_string_.data(), &secret_string_size_), - WB_RESULT_INVALID_PARAMETER); + + ASSERT_EQ( + WB_License_GetSecretString(whitebox_, WB_CIPHER_MODE_CBC, nullptr, + golden_data_.CBCCryptoKey().id.size(), + secret_string_.data(), &secret_string_size_), + WB_RESULT_INVALID_PARAMETER); } TEST_F(LicenseWhiteboxGetSecretStringTest, InvalidParameterForZeroKeyIdSize) { LoadLicense(); - ASSERT_EQ(WB_License_GetSecretString( - whitebox_, WB_CIPHER_MODE_CBC, crypto_key_id_.data(), 0, - secret_string_.data(), &secret_string_size_), - WB_RESULT_INVALID_PARAMETER); + + ASSERT_EQ( + WB_License_GetSecretString(whitebox_, WB_CIPHER_MODE_CBC, + golden_data_.CBCCryptoKey().id.data(), 0, + secret_string_.data(), &secret_string_size_), + WB_RESULT_INVALID_PARAMETER); } TEST_F(LicenseWhiteboxGetSecretStringTest, InvalidParameterForNullSecretString) { LoadLicense(); - ASSERT_EQ(WB_License_GetSecretString( - whitebox_, WB_CIPHER_MODE_CBC, crypto_key_id_.data(), - crypto_key_id_.size(), nullptr, &secret_string_size_), + + ASSERT_EQ(WB_License_GetSecretString(whitebox_, WB_CIPHER_MODE_CBC, + golden_data_.CBCCryptoKey().id.data(), + golden_data_.CBCCryptoKey().id.size(), + nullptr, &secret_string_size_), WB_RESULT_INVALID_PARAMETER); } TEST_F(LicenseWhiteboxGetSecretStringTest, InvalidParameterForNullSecretStringSize) { LoadLicense(); - ASSERT_EQ(WB_License_GetSecretString( - whitebox_, WB_CIPHER_MODE_CBC, crypto_key_id_.data(), - crypto_key_id_.size(), secret_string_.data(), nullptr), + + ASSERT_EQ(WB_License_GetSecretString(whitebox_, WB_CIPHER_MODE_CBC, + golden_data_.CBCCryptoKey().id.data(), + golden_data_.CBCCryptoKey().id.size(), + secret_string_.data(), nullptr), WB_RESULT_INVALID_PARAMETER); } -TEST_F(LicenseWhiteboxGetSecretStringTest, NoSuchKey) { - // TODO: This test needs to be skipped right now because we can't load a - // license and we need a license to have key ids. - GTEST_SKIP(); - +// For this test, "missing key id" specifically means a key id that was never +// in the license to start with. This is different than "non content key" +// and "dropped content key", as those keys were in the license but ignored. +TEST_F(LicenseWhiteboxGetSecretStringTest, KeyUnavailableForMissingKeyId) { LoadLicense(); + ASSERT_EQ( WB_License_GetSecretString(whitebox_, WB_CIPHER_MODE_CBC, - bad_key_id_.data(), bad_key_id_.size(), + missing_key_id_.data(), missing_key_id_.size(), secret_string_.data(), &secret_string_size_), - WB_RESULT_NO_SUCH_KEY); + WB_RESULT_KEY_UNAVAILABLE); } -TEST_F(LicenseWhiteboxGetSecretStringTest, WrongKeyType) { - // TODO: This test needs to be skipped right now because we can't load a - // license and we need a license to have key ids. - GTEST_SKIP(); - +TEST_F(LicenseWhiteboxGetSecretStringTest, KeyUnavailableForNonContentKey) { LoadLicense(); + ASSERT_EQ(WB_License_GetSecretString( whitebox_, WB_CIPHER_MODE_CBC, non_content_key_id_.data(), non_content_key_id_.size(), secret_string_.data(), &secret_string_size_), - WB_RESULT_NO_SUCH_KEY); + WB_RESULT_KEY_UNAVAILABLE); } -TEST_F(LicenseWhiteboxGetSecretStringTest, InsufficientSecurityLevel) { - // TODO: This test needs to be skipped right now because we can't load a - // license and we need a license to have key ids. - GTEST_SKIP(); - +// Under normal circumstances, a hardware key should be dropped. The exception +// to this rule is on ChromeOS with a special license. +TEST_F(LicenseWhiteboxGetSecretStringTest, + InsufficientSecurityLevelForHardwareContentKey) { LoadLicense(); + ASSERT_EQ( - WB_License_GetSecretString( - whitebox_, WB_CIPHER_MODE_CBC, hardware_key_id_.data(), - hardware_key_id_.size(), secret_string_.data(), &secret_string_size_), - WB_RESULT_NO_SUCH_KEY); + WB_License_GetSecretString(whitebox_, WB_CIPHER_MODE_CBC, + golden_data_.CBCHardwareKey().id.data(), + golden_data_.CBCHardwareKey().id.size(), + secret_string_.data(), &secret_string_size_), + WB_RESULT_INSUFFICIENT_SECURITY_LEVEL); } TEST_F(LicenseWhiteboxGetSecretStringTest, BufferTooSmall) { - // TODO: This test needs to be skipped right now because we can't load a - // license and we need a license to have key ids. - GTEST_SKIP(); - LoadLicense(); // Since the secret string is implementation specific, we don't want to make @@ -156,7 +229,8 @@ TEST_F(LicenseWhiteboxGetSecretStringTest, BufferTooSmall) { secret_string_size_ = 1; ASSERT_EQ( WB_License_GetSecretString(whitebox_, WB_CIPHER_MODE_CBC, - crypto_key_id_.data(), crypto_key_id_.size(), + golden_data_.CBCCryptoKey().id.data(), + golden_data_.CBCCryptoKey().id.size(), secret_string_.data(), &secret_string_size_), WB_RESULT_BUFFER_TOO_SMALL); @@ -172,9 +246,10 @@ TEST_F(LicenseWhiteboxGetSecretStringTest, InvalidState) { ASSERT_EQ( WB_License_GetSecretString(whitebox_, WB_CIPHER_MODE_CBC, - crypto_key_id_.data(), crypto_key_id_.size(), + golden_data_.CBCCryptoKey().id.data(), + golden_data_.CBCCryptoKey().id.size(), secret_string_.data(), &secret_string_size_), WB_RESULT_INVALID_STATE); } -} // namespace +} // namespace widevine diff --git a/api/license_whitebox_masked_decrypt_test.cc b/api/license_whitebox_masked_decrypt_test.cc new file mode 100644 index 0000000..f6a69db --- /dev/null +++ b/api/license_whitebox_masked_decrypt_test.cc @@ -0,0 +1,766 @@ +// Copyright 2020 Google LLC. All Rights Reserved. + +#include "api/license_whitebox.h" + +#include +#include +#include +#include + +#include "api/golden_data.h" +#include "api/license_builder.h" +#include "api/license_whitebox_test_base.h" +#include "api/test_data.h" +#include "base/logging.h" +#include "crypto_utils/crypto_util.h" +#include "crypto_utils/rsa_key.h" +#include "testing/include/gtest/gtest.h" + +namespace widevine { + +class LicenseWhiteboxMaskedDecryptTest : public LicenseWhiteboxTestBase { + protected: + void SetUp() override { + LicenseWhiteboxTestBase::SetUp(); + + // Because we are going to use the same buffer for both tests, make sure it + // will be large enough for either. + plaintext_size_ = std::max(golden_data_.CBCContent().ciphertext.size(), + golden_data_.CTRContent().ciphertext.size()); + plaintext_.resize(plaintext_size_); + + // We have no idea how big the secret string will be, but it should be safe + // to assume it won't be larger than the plaintext. + secret_string_size_ = plaintext_size_; + secret_string_.resize(secret_string_size_); + + golden_data_.MakeKeyIdDifferent(&non_content_key_id_); + golden_data_.MakeKeyIdDifferent(&missing_key_id_); + } + + void LoadLicense(const std::vector& padding) { + LicenseBuilder builder; + + builder.AddContentKey(golden_data_.CBCCryptoKey().level, + golden_data_.CBCCryptoKey().id, + golden_data_.CBCCryptoKey().content->key); + + builder.AddContentKey(golden_data_.CTRCryptoKey().level, + golden_data_.CTRCryptoKey().id, + golden_data_.CTRCryptoKey().content->key); + + builder.AddContentKey(golden_data_.CBCDecodeKey().level, + golden_data_.CBCDecodeKey().id, + golden_data_.CBCDecodeKey().content->key); + + builder.AddContentKey(golden_data_.CTRDecodeKey().level, + golden_data_.CTRDecodeKey().id, + golden_data_.CTRDecodeKey().content->key); + + builder.AddContentKey(golden_data_.CBCHardwareKey().level, + golden_data_.CBCHardwareKey().id, + golden_data_.CBCHardwareKey().content->key); + + builder.AddOperatorSessionKey(non_content_key_id_); + + License license; + builder.Build(*public_key_, &license); + + ASSERT_EQ(WB_License_ProcessLicenseResponse( + whitebox_, license.message.data(), license.message.size(), + license.signature.data(), license.signature.size(), + license.session_key.data(), license.session_key.size(), + license.request.data(), license.request.size()), + WB_RESULT_OK); + } + + // We need two special keys for this test, one that will be used for a + // non-content key and one that will never be in the license. + std::vector non_content_key_id_ = {0, 0, 0}; + std::vector missing_key_id_ = {1, 0, 0}; + + size_t secret_string_size_; + std::vector secret_string_; + + size_t plaintext_size_; + std::vector plaintext_; +}; + +TEST_F(LicenseWhiteboxMaskedDecryptTest, DecodeKeyWithCbcDataInCbcMode) { + LoadLicense(LicenseBuilder::NoPadding()); + + ASSERT_EQ( + WB_License_MaskedDecrypt( + whitebox_, WB_CIPHER_MODE_CBC, golden_data_.CBCDecodeKey().id.data(), + golden_data_.CBCDecodeKey().id.size(), + golden_data_.CBCDecodeKey().content->ciphertext.data(), + golden_data_.CBCDecodeKey().content->ciphertext.size(), + golden_data_.CBCDecodeKey().content->iv.data(), + golden_data_.CBCDecodeKey().content->iv.size(), plaintext_.data(), + &plaintext_size_), + WB_RESULT_OK); + plaintext_.resize(plaintext_size_); + + // Returned data is masked, so it should be the correct size but not + // match the original text. + ASSERT_EQ(plaintext_.size(), + golden_data_.CBCDecodeKey().content->plaintext.size()); + ASSERT_NE(plaintext_, golden_data_.CBCDecodeKey().content->plaintext); + + // Now unmask the data. + ASSERT_EQ( + WB_License_GetSecretString(whitebox_, WB_CIPHER_MODE_CBC, + golden_data_.CBCDecodeKey().id.data(), + golden_data_.CBCDecodeKey().id.size(), + secret_string_.data(), &secret_string_size_), + WB_RESULT_OK); + secret_string_.resize(secret_string_size_); + WB_License_Unmask(secret_string_.data(), secret_string_.size(), + plaintext_.data(), plaintext_.size()); + ASSERT_EQ(plaintext_, golden_data_.CBCDecodeKey().content->plaintext); +} + +TEST_F(LicenseWhiteboxMaskedDecryptTest, DecodeKeyWithCtrDataInCtrMode) { + LoadLicense(LicenseBuilder::NoPadding()); + + ASSERT_EQ( + WB_License_MaskedDecrypt( + whitebox_, WB_CIPHER_MODE_CTR, golden_data_.CTRDecodeKey().id.data(), + golden_data_.CTRDecodeKey().id.size(), + golden_data_.CTRDecodeKey().content->ciphertext.data(), + golden_data_.CTRDecodeKey().content->ciphertext.size(), + golden_data_.CTRDecodeKey().content->iv.data(), + golden_data_.CTRDecodeKey().content->iv.size(), plaintext_.data(), + &plaintext_size_), + WB_RESULT_OK); + plaintext_.resize(plaintext_size_); + + // Returned data is masked, so it should be the correct size but not + // match the original text. + ASSERT_EQ(plaintext_.size(), + golden_data_.CTRDecodeKey().content->plaintext.size()); + ASSERT_NE(plaintext_, golden_data_.CTRDecodeKey().content->plaintext); + + // Now unmask the data. + ASSERT_EQ( + WB_License_GetSecretString(whitebox_, WB_CIPHER_MODE_CTR, + golden_data_.CTRDecodeKey().id.data(), + golden_data_.CTRDecodeKey().id.size(), + secret_string_.data(), &secret_string_size_), + WB_RESULT_OK); + secret_string_.resize(secret_string_size_); + WB_License_Unmask(secret_string_.data(), secret_string_.size(), + plaintext_.data(), plaintext_.size()); + ASSERT_EQ(plaintext_, golden_data_.CTRDecodeKey().content->plaintext); +} + +// We try to decrypt CBC encrypted data in CTR mode. All operations should be +// successful, but the resulting plaintext should not match. +TEST_F(LicenseWhiteboxMaskedDecryptTest, DecodeKeyWithCbcDataInCtrMode) { + LoadLicense(LicenseBuilder::NoPadding()); + + ASSERT_EQ( + WB_License_MaskedDecrypt( + whitebox_, WB_CIPHER_MODE_CTR, golden_data_.CBCDecodeKey().id.data(), + golden_data_.CBCDecodeKey().id.size(), + golden_data_.CBCDecodeKey().content->ciphertext.data(), + golden_data_.CBCDecodeKey().content->ciphertext.size(), + golden_data_.CBCDecodeKey().content->iv.data(), + golden_data_.CBCDecodeKey().content->iv.size(), plaintext_.data(), + &plaintext_size_), + WB_RESULT_OK); + plaintext_.resize(plaintext_size_); + + // Whatever is returned must not be the original text. + ASSERT_EQ(plaintext_.size(), + golden_data_.CBCDecodeKey().content->plaintext.size()); + ASSERT_NE(plaintext_, golden_data_.CBCDecodeKey().content->plaintext); + + // Now unmask the data. Still should not match. + ASSERT_EQ( + WB_License_GetSecretString(whitebox_, WB_CIPHER_MODE_CTR, + golden_data_.CBCDecodeKey().id.data(), + golden_data_.CBCDecodeKey().id.size(), + secret_string_.data(), &secret_string_size_), + WB_RESULT_OK); + secret_string_.resize(secret_string_size_); + WB_License_Unmask(secret_string_.data(), secret_string_.size(), + plaintext_.data(), plaintext_.size()); + ASSERT_NE(plaintext_, golden_data_.CBCDecodeKey().content->plaintext); +} + +// We try to decrypt CTR encrypted data in CBC mode. All operations should be +// successful, but the resulting plaintext should not match. +TEST_F(LicenseWhiteboxMaskedDecryptTest, DecodeKeyWithCtrDataInCbcMode) { + LoadLicense(LicenseBuilder::NoPadding()); + + ASSERT_EQ( + WB_License_MaskedDecrypt( + whitebox_, WB_CIPHER_MODE_CBC, golden_data_.CTRDecodeKey().id.data(), + golden_data_.CTRDecodeKey().id.size(), + golden_data_.CTRDecodeKey().content->ciphertext.data(), + golden_data_.CTRDecodeKey().content->ciphertext.size(), + golden_data_.CTRDecodeKey().content->iv.data(), + golden_data_.CTRDecodeKey().content->iv.size(), plaintext_.data(), + &plaintext_size_), + WB_RESULT_OK); + plaintext_.resize(plaintext_size_); + + // Whatever is returned must not be the original text. + ASSERT_EQ(plaintext_.size(), + golden_data_.CTRDecodeKey().content->plaintext.size()); + ASSERT_NE(plaintext_, golden_data_.CTRDecodeKey().content->plaintext); + + // Now unmask the data. Still should not match. + ASSERT_EQ( + WB_License_GetSecretString(whitebox_, WB_CIPHER_MODE_CBC, + golden_data_.CTRDecodeKey().id.data(), + golden_data_.CTRDecodeKey().id.size(), + secret_string_.data(), &secret_string_size_), + WB_RESULT_OK); + secret_string_.resize(secret_string_size_); + WB_License_Unmask(secret_string_.data(), secret_string_.size(), + plaintext_.data(), plaintext_.size()); + ASSERT_NE(plaintext_, golden_data_.CTRDecodeKey().content->plaintext); +} + +TEST_F(LicenseWhiteboxMaskedDecryptTest, CryptoKeyWithCbcDataInCbcMode) { + LoadLicense(LicenseBuilder::NoPadding()); + + ASSERT_EQ( + WB_License_MaskedDecrypt( + whitebox_, WB_CIPHER_MODE_CBC, golden_data_.CBCCryptoKey().id.data(), + golden_data_.CBCCryptoKey().id.size(), + golden_data_.CBCDecodeKey().content->ciphertext.data(), + golden_data_.CBCDecodeKey().content->ciphertext.size(), + golden_data_.CBCDecodeKey().content->iv.data(), + golden_data_.CBCDecodeKey().content->iv.size(), plaintext_.data(), + &plaintext_size_), + WB_RESULT_OK); + plaintext_.resize(plaintext_size_); + + // Returned data is masked, so it should be the correct size but not + // match the original text. + ASSERT_EQ(plaintext_.size(), + golden_data_.CBCDecodeKey().content->plaintext.size()); + ASSERT_NE(plaintext_, golden_data_.CBCDecodeKey().content->plaintext); + + // Now unmask the data. + ASSERT_EQ( + WB_License_GetSecretString(whitebox_, WB_CIPHER_MODE_CBC, + golden_data_.CBCCryptoKey().id.data(), + golden_data_.CBCCryptoKey().id.size(), + secret_string_.data(), &secret_string_size_), + WB_RESULT_OK); + secret_string_.resize(secret_string_size_); + WB_License_Unmask(secret_string_.data(), secret_string_.size(), + plaintext_.data(), plaintext_.size()); + ASSERT_EQ(plaintext_, golden_data_.CBCDecodeKey().content->plaintext); +} + +TEST_F(LicenseWhiteboxMaskedDecryptTest, CryptoKeyWithCtrDataInCtrMode) { + LoadLicense(LicenseBuilder::NoPadding()); + + ASSERT_EQ( + WB_License_MaskedDecrypt( + whitebox_, WB_CIPHER_MODE_CTR, golden_data_.CTRCryptoKey().id.data(), + golden_data_.CTRCryptoKey().id.size(), + golden_data_.CTRDecodeKey().content->ciphertext.data(), + golden_data_.CTRDecodeKey().content->ciphertext.size(), + golden_data_.CTRDecodeKey().content->iv.data(), + golden_data_.CTRDecodeKey().content->iv.size(), plaintext_.data(), + &plaintext_size_), + WB_RESULT_OK); + plaintext_.resize(plaintext_size_); + + // Returned data is masked, so it should be the correct size but not + // match the original text. + ASSERT_EQ(plaintext_.size(), + golden_data_.CTRDecodeKey().content->plaintext.size()); + ASSERT_NE(plaintext_, golden_data_.CTRDecodeKey().content->plaintext); + + // Now unmask the data. + ASSERT_EQ( + WB_License_GetSecretString(whitebox_, WB_CIPHER_MODE_CTR, + golden_data_.CTRCryptoKey().id.data(), + golden_data_.CTRCryptoKey().id.size(), + secret_string_.data(), &secret_string_size_), + WB_RESULT_OK); + secret_string_.resize(secret_string_size_); + WB_License_Unmask(secret_string_.data(), secret_string_.size(), + plaintext_.data(), plaintext_.size()); + ASSERT_EQ(plaintext_, golden_data_.CTRDecodeKey().content->plaintext); +} + +// We try to decrypt CBC encrypted data in CTR mode. All operations should be +// successful, but the resulting plaintext should not match. +TEST_F(LicenseWhiteboxMaskedDecryptTest, CryptoKeyWithCbcDataInCtrMode) { + LoadLicense(LicenseBuilder::NoPadding()); + + ASSERT_EQ( + WB_License_MaskedDecrypt( + whitebox_, WB_CIPHER_MODE_CTR, golden_data_.CBCCryptoKey().id.data(), + golden_data_.CBCCryptoKey().id.size(), + golden_data_.CBCDecodeKey().content->ciphertext.data(), + golden_data_.CBCDecodeKey().content->ciphertext.size(), + golden_data_.CBCDecodeKey().content->iv.data(), + golden_data_.CBCDecodeKey().content->iv.size(), plaintext_.data(), + &plaintext_size_), + WB_RESULT_OK); + plaintext_.resize(plaintext_size_); + + // Whatever is returned must not be the original text. + ASSERT_EQ(plaintext_.size(), + golden_data_.CBCDecodeKey().content->plaintext.size()); + ASSERT_NE(plaintext_, golden_data_.CBCDecodeKey().content->plaintext); + + // Now unmask the data. Still should not match. + ASSERT_EQ( + WB_License_GetSecretString(whitebox_, WB_CIPHER_MODE_CTR, + golden_data_.CBCCryptoKey().id.data(), + golden_data_.CBCCryptoKey().id.size(), + secret_string_.data(), &secret_string_size_), + WB_RESULT_OK); + secret_string_.resize(secret_string_size_); + WB_License_Unmask(secret_string_.data(), secret_string_.size(), + plaintext_.data(), plaintext_.size()); + ASSERT_NE(plaintext_, golden_data_.CBCDecodeKey().content->plaintext); +} + +// We try to decrypt CTR encrypted data in CBC mode. All operations should be +// successful, but the resulting plaintext should not match. +TEST_F(LicenseWhiteboxMaskedDecryptTest, CryptoKeyWithCtrDataInCbcMode) { + LoadLicense(LicenseBuilder::NoPadding()); + + ASSERT_EQ( + WB_License_MaskedDecrypt( + whitebox_, WB_CIPHER_MODE_CBC, golden_data_.CTRCryptoKey().id.data(), + golden_data_.CTRCryptoKey().id.size(), + golden_data_.CTRCryptoKey().content->ciphertext.data(), + golden_data_.CTRCryptoKey().content->ciphertext.size(), + golden_data_.CTRCryptoKey().content->iv.data(), + golden_data_.CTRCryptoKey().content->iv.size(), plaintext_.data(), + &plaintext_size_), + WB_RESULT_OK); + plaintext_.resize(plaintext_size_); + + // Whatever is returned must not be the original text. + ASSERT_EQ(plaintext_.size(), + golden_data_.CTRCryptoKey().content->plaintext.size()); + ASSERT_NE(plaintext_, golden_data_.CTRCryptoKey().content->plaintext); + + // Now unmask the data. Still should not match. + ASSERT_EQ( + WB_License_GetSecretString(whitebox_, WB_CIPHER_MODE_CBC, + golden_data_.CTRCryptoKey().id.data(), + golden_data_.CTRCryptoKey().id.size(), + secret_string_.data(), &secret_string_size_), + WB_RESULT_OK); + secret_string_.resize(secret_string_size_); + WB_License_Unmask(secret_string_.data(), secret_string_.size(), + plaintext_.data(), plaintext_.size()); + ASSERT_NE(plaintext_, golden_data_.CTRCryptoKey().content->plaintext); +} + +TEST_F(LicenseWhiteboxMaskedDecryptTest, CryptoKeyWithCbcDataAndPKCS8Padding) { + LoadLicense(LicenseBuilder::PKSC8Padding()); + + ASSERT_EQ( + WB_License_MaskedDecrypt( + whitebox_, WB_CIPHER_MODE_CBC, golden_data_.CBCCryptoKey().id.data(), + golden_data_.CBCCryptoKey().id.size(), + golden_data_.CBCCryptoKey().content->ciphertext.data(), + golden_data_.CBCCryptoKey().content->ciphertext.size(), + golden_data_.CBCCryptoKey().content->iv.data(), + golden_data_.CBCCryptoKey().content->iv.size(), plaintext_.data(), + &plaintext_size_), + WB_RESULT_OK); + plaintext_.resize(plaintext_size_); + + // Returned data is masked, so it should be the correct size but not + // match the original text. + ASSERT_EQ(plaintext_.size(), + golden_data_.CBCCryptoKey().content->plaintext.size()); + ASSERT_NE(plaintext_, golden_data_.CBCCryptoKey().content->plaintext); + + // Now unmask the data. + ASSERT_EQ( + WB_License_GetSecretString(whitebox_, WB_CIPHER_MODE_CBC, + golden_data_.CBCCryptoKey().id.data(), + golden_data_.CBCCryptoKey().id.size(), + secret_string_.data(), &secret_string_size_), + WB_RESULT_OK); + secret_string_.resize(secret_string_size_); + WB_License_Unmask(secret_string_.data(), secret_string_.size(), + plaintext_.data(), plaintext_.size()); + ASSERT_EQ(plaintext_, golden_data_.CBCCryptoKey().content->plaintext); +} + +TEST_F(LicenseWhiteboxMaskedDecryptTest, CryptoKeyWithCtrDataAndPKCS8Padding) { + LoadLicense(LicenseBuilder::PKSC8Padding()); + + ASSERT_EQ( + WB_License_MaskedDecrypt( + whitebox_, WB_CIPHER_MODE_CTR, golden_data_.CTRCryptoKey().id.data(), + golden_data_.CTRCryptoKey().id.size(), + golden_data_.CTRCryptoKey().content->ciphertext.data(), + golden_data_.CTRCryptoKey().content->ciphertext.size(), + golden_data_.CTRCryptoKey().content->iv.data(), + golden_data_.CTRCryptoKey().content->iv.size(), plaintext_.data(), + &plaintext_size_), + WB_RESULT_OK); + plaintext_.resize(plaintext_size_); + + // Returned data is masked, so it should be the correct size but not + // match the original text. + ASSERT_EQ(plaintext_.size(), + golden_data_.CTRCryptoKey().content->plaintext.size()); + ASSERT_NE(plaintext_, golden_data_.CTRCryptoKey().content->plaintext); + + // Now unmask the data. + ASSERT_EQ( + WB_License_GetSecretString(whitebox_, WB_CIPHER_MODE_CTR, + golden_data_.CTRCryptoKey().id.data(), + golden_data_.CTRCryptoKey().id.size(), + secret_string_.data(), &secret_string_size_), + WB_RESULT_OK); + secret_string_.resize(secret_string_size_); + WB_License_Unmask(secret_string_.data(), secret_string_.size(), + plaintext_.data(), plaintext_.size()); + ASSERT_EQ(plaintext_, golden_data_.CTRCryptoKey().content->plaintext); +} + +// Try decrypting two different sets of content to make sure that two +// different keys can be used at the same time. +// +// Since we have two CBC keys, try using the decode key and then the crypto +// key. +TEST_F(LicenseWhiteboxMaskedDecryptTest, SuccessWithMultipleKeys) { + LoadLicense(LicenseBuilder::NoPadding()); + + ASSERT_EQ( + WB_License_MaskedDecrypt( + whitebox_, WB_CIPHER_MODE_CBC, golden_data_.CBCDecodeKey().id.data(), + golden_data_.CBCDecodeKey().id.size(), + golden_data_.CBCDecodeKey().content->ciphertext.data(), + golden_data_.CBCDecodeKey().content->ciphertext.size(), + golden_data_.CBCDecodeKey().content->iv.data(), + golden_data_.CBCDecodeKey().content->iv.size(), plaintext_.data(), + &plaintext_size_), + WB_RESULT_OK); + plaintext_.resize(plaintext_size_); + + // Returned data is masked, so it should be the correct size but not + // match the original text. + ASSERT_EQ(plaintext_.size(), + golden_data_.CBCDecodeKey().content->plaintext.size()); + ASSERT_NE(plaintext_, golden_data_.CBCDecodeKey().content->plaintext); + + // Now unmask the data. + ASSERT_EQ( + WB_License_GetSecretString(whitebox_, WB_CIPHER_MODE_CBC, + golden_data_.CBCDecodeKey().id.data(), + golden_data_.CBCDecodeKey().id.size(), + secret_string_.data(), &secret_string_size_), + WB_RESULT_OK); + secret_string_.resize(secret_string_size_); + WB_License_Unmask(secret_string_.data(), secret_string_.size(), + plaintext_.data(), plaintext_.size()); + ASSERT_EQ(plaintext_, golden_data_.CBCDecodeKey().content->plaintext); + + // Reset our output buffer. + plaintext_.clear(); + plaintext_size_ = golden_data_.CTRDecodeKey().content->plaintext.size(); + plaintext_.resize(plaintext_size_); + + ASSERT_EQ( + WB_License_MaskedDecrypt( + whitebox_, WB_CIPHER_MODE_CTR, golden_data_.CTRDecodeKey().id.data(), + golden_data_.CTRDecodeKey().id.size(), + golden_data_.CTRDecodeKey().content->ciphertext.data(), + golden_data_.CTRDecodeKey().content->ciphertext.size(), + golden_data_.CTRDecodeKey().content->iv.data(), + golden_data_.CTRDecodeKey().content->iv.size(), plaintext_.data(), + &plaintext_size_), + WB_RESULT_OK); + plaintext_.resize(plaintext_size_); + + // Returned data is masked, so it should be the correct size but not + // match the original text. + ASSERT_EQ(plaintext_.size(), + golden_data_.CTRDecodeKey().content->plaintext.size()); + ASSERT_NE(plaintext_, golden_data_.CTRDecodeKey().content->plaintext); + + // Now unmask the data. + secret_string_.clear(); + secret_string_size_ = plaintext_.size(); + secret_string_.resize(secret_string_size_); + ASSERT_EQ( + WB_License_GetSecretString(whitebox_, WB_CIPHER_MODE_CTR, + golden_data_.CTRDecodeKey().id.data(), + golden_data_.CTRDecodeKey().id.size(), + secret_string_.data(), &secret_string_size_), + WB_RESULT_OK); + secret_string_.resize(secret_string_size_); + WB_License_Unmask(secret_string_.data(), secret_string_.size(), + plaintext_.data(), plaintext_.size()); + ASSERT_EQ(plaintext_, golden_data_.CTRDecodeKey().content->plaintext); +} + +TEST_F(LicenseWhiteboxMaskedDecryptTest, InvalidParameterForNullWhitebox) { + LoadLicense(LicenseBuilder::NoPadding()); + + ASSERT_EQ( + WB_License_MaskedDecrypt( + nullptr, WB_CIPHER_MODE_CBC, golden_data_.CBCDecodeKey().id.data(), + golden_data_.CBCDecodeKey().id.size(), + golden_data_.CBCDecodeKey().content->ciphertext.data(), + golden_data_.CBCDecodeKey().content->ciphertext.size(), + golden_data_.CBCDecodeKey().content->iv.data(), + golden_data_.CBCDecodeKey().content->iv.size(), plaintext_.data(), + &plaintext_size_), + WB_RESULT_INVALID_PARAMETER); +} + +TEST_F(LicenseWhiteboxMaskedDecryptTest, InvalidParameterForInvalidCipherMode) { + LoadLicense(LicenseBuilder::NoPadding()); + + // In order to trick the compiler into letting us pass an invalid enum value + // to WB__License_MaskedDecrypt(), we need to cast it. If we don't do this, + // the compiler tries to save us. + const WB_CipherMode invalid_mode = static_cast(0xFF); + + ASSERT_EQ(WB_License_MaskedDecrypt( + whitebox_, invalid_mode, golden_data_.CBCDecodeKey().id.data(), + golden_data_.CBCDecodeKey().id.size(), + golden_data_.CBCDecodeKey().content->ciphertext.data(), + golden_data_.CBCDecodeKey().content->ciphertext.size(), + golden_data_.CBCDecodeKey().content->iv.data(), + golden_data_.CBCDecodeKey().content->iv.size(), + plaintext_.data(), &plaintext_size_), + WB_RESULT_INVALID_PARAMETER); +} + +TEST_F(LicenseWhiteboxMaskedDecryptTest, InvalidParameterForNullKeyId) { + LoadLicense(LicenseBuilder::NoPadding()); + + ASSERT_EQ(WB_License_MaskedDecrypt( + whitebox_, WB_CIPHER_MODE_CBC, nullptr, + golden_data_.CBCDecodeKey().id.size(), + golden_data_.CBCDecodeKey().content->ciphertext.data(), + golden_data_.CBCDecodeKey().content->ciphertext.size(), + golden_data_.CBCDecodeKey().content->iv.data(), + golden_data_.CBCDecodeKey().content->iv.size(), + plaintext_.data(), &plaintext_size_), + WB_RESULT_INVALID_PARAMETER); +} + +TEST_F(LicenseWhiteboxMaskedDecryptTest, InvalidParameterForNullZeroKeyIdSize) { + LoadLicense(LicenseBuilder::NoPadding()); + + ASSERT_EQ( + WB_License_MaskedDecrypt( + whitebox_, WB_CIPHER_MODE_CBC, golden_data_.CBCDecodeKey().id.data(), + 0, golden_data_.CBCDecodeKey().content->ciphertext.data(), + golden_data_.CBCDecodeKey().content->ciphertext.size(), + golden_data_.CBCDecodeKey().content->iv.data(), + golden_data_.CBCDecodeKey().content->iv.size(), plaintext_.data(), + &plaintext_size_), + WB_RESULT_INVALID_PARAMETER); +} + +TEST_F(LicenseWhiteboxMaskedDecryptTest, InvalidParameterForNullInputData) { + LoadLicense(LicenseBuilder::NoPadding()); + + ASSERT_EQ( + WB_License_MaskedDecrypt( + whitebox_, WB_CIPHER_MODE_CBC, golden_data_.CBCDecodeKey().id.data(), + golden_data_.CBCDecodeKey().id.size(), nullptr, + golden_data_.CBCDecodeKey().content->ciphertext.size(), + golden_data_.CBCDecodeKey().content->iv.data(), + golden_data_.CBCDecodeKey().content->iv.size(), plaintext_.data(), + &plaintext_size_), + WB_RESULT_INVALID_PARAMETER); +} + +// AES CBC requires that the input be block aligned (multiple of 16). CTR does +// not care. +TEST_F(LicenseWhiteboxMaskedDecryptTest, + InvalidParameterForInvalidCBCInputDataSize) { + LoadLicense(LicenseBuilder::NoPadding()); + + ASSERT_EQ( + WB_License_MaskedDecrypt( + whitebox_, WB_CIPHER_MODE_CBC, golden_data_.CBCDecodeKey().id.data(), + golden_data_.CBCDecodeKey().id.size(), + golden_data_.CBCDecodeKey().content->ciphertext.data(), 14, + golden_data_.CBCDecodeKey().content->iv.data(), + golden_data_.CBCDecodeKey().content->iv.size(), plaintext_.data(), + &plaintext_size_), + WB_RESULT_INVALID_PARAMETER); +} + +// The white-box (using any cipher mode) should reject input with size zero. +TEST_F(LicenseWhiteboxMaskedDecryptTest, InvalidParameterForZeroInputDataSize) { + LoadLicense(LicenseBuilder::NoPadding()); + + ASSERT_EQ( + WB_License_MaskedDecrypt( + whitebox_, WB_CIPHER_MODE_CBC, golden_data_.CBCDecodeKey().id.data(), + golden_data_.CBCDecodeKey().id.size(), + golden_data_.CBCDecodeKey().content->ciphertext.data(), 0, + golden_data_.CBCDecodeKey().content->iv.data(), + golden_data_.CBCDecodeKey().content->iv.size(), plaintext_.data(), + &plaintext_size_), + WB_RESULT_INVALID_PARAMETER); +} + +TEST_F(LicenseWhiteboxMaskedDecryptTest, InvalidParameterForNullIV) { + LoadLicense(LicenseBuilder::NoPadding()); + + ASSERT_EQ( + WB_License_MaskedDecrypt( + whitebox_, WB_CIPHER_MODE_CBC, golden_data_.CBCDecodeKey().id.data(), + golden_data_.CBCDecodeKey().id.size(), + golden_data_.CBCDecodeKey().content->ciphertext.data(), + golden_data_.CBCDecodeKey().content->ciphertext.size(), nullptr, + golden_data_.CBCDecodeKey().content->iv.size(), plaintext_.data(), + &plaintext_size_), + WB_RESULT_INVALID_PARAMETER); +} + +// IV size should be 16. Any number other than 16 should fail. +TEST_F(LicenseWhiteboxMaskedDecryptTest, InvalidParameterForInvalidIVSize) { + LoadLicense(LicenseBuilder::NoPadding()); + + ASSERT_EQ( + WB_License_MaskedDecrypt( + whitebox_, WB_CIPHER_MODE_CBC, golden_data_.CBCDecodeKey().id.data(), + golden_data_.CBCDecodeKey().id.size(), + golden_data_.CBCDecodeKey().content->ciphertext.data(), + golden_data_.CBCDecodeKey().content->ciphertext.size(), + golden_data_.CBCDecodeKey().content->iv.data(), 9, plaintext_.data(), + &plaintext_size_), + WB_RESULT_INVALID_PARAMETER); +} + +TEST_F(LicenseWhiteboxMaskedDecryptTest, InvalidParameterForNullOutput) { + LoadLicense(LicenseBuilder::NoPadding()); + + ASSERT_EQ( + WB_License_MaskedDecrypt( + whitebox_, WB_CIPHER_MODE_CBC, golden_data_.CBCDecodeKey().id.data(), + golden_data_.CBCDecodeKey().id.size(), + golden_data_.CBCDecodeKey().content->ciphertext.data(), + golden_data_.CBCDecodeKey().content->ciphertext.size(), + golden_data_.CBCDecodeKey().content->iv.data(), + golden_data_.CBCDecodeKey().content->iv.size(), nullptr, + &plaintext_size_), + WB_RESULT_INVALID_PARAMETER); +} + +TEST_F(LicenseWhiteboxMaskedDecryptTest, InvalidParameterForNullOutputSize) { + LoadLicense(LicenseBuilder::NoPadding()); + + ASSERT_EQ( + WB_License_MaskedDecrypt( + whitebox_, WB_CIPHER_MODE_CBC, golden_data_.CBCDecodeKey().id.data(), + golden_data_.CBCDecodeKey().id.size(), + golden_data_.CBCDecodeKey().content->ciphertext.data(), + golden_data_.CBCDecodeKey().content->ciphertext.size(), + golden_data_.CBCDecodeKey().content->iv.data(), + golden_data_.CBCDecodeKey().content->iv.size(), plaintext_.data(), + nullptr), + WB_RESULT_INVALID_PARAMETER); +} + +// For this test, "missing key id" specifically means a key id that was never +// in the license to start with. This is different than "non content key" +// and "dropped content key", as those keys were in the license but ignored. +TEST_F(LicenseWhiteboxMaskedDecryptTest, KeyUnavailableForMissingKeyId) { + LoadLicense(LicenseBuilder::NoPadding()); + + ASSERT_EQ(WB_License_MaskedDecrypt( + whitebox_, WB_CIPHER_MODE_CBC, missing_key_id_.data(), + missing_key_id_.size(), + golden_data_.CBCDecodeKey().content->ciphertext.data(), + golden_data_.CBCDecodeKey().content->ciphertext.size(), + golden_data_.CBCDecodeKey().content->iv.data(), + golden_data_.CBCDecodeKey().content->iv.size(), + plaintext_.data(), &plaintext_size_), + WB_RESULT_KEY_UNAVAILABLE); +} + +TEST_F(LicenseWhiteboxMaskedDecryptTest, KeyUnavailableForNonContentKey) { + LoadLicense(LicenseBuilder::NoPadding()); + + ASSERT_EQ(WB_License_MaskedDecrypt( + whitebox_, WB_CIPHER_MODE_CBC, non_content_key_id_.data(), + non_content_key_id_.size(), + golden_data_.CBCDecodeKey().content->ciphertext.data(), + golden_data_.CBCDecodeKey().content->ciphertext.size(), + golden_data_.CBCDecodeKey().content->iv.data(), + golden_data_.CBCDecodeKey().content->iv.size(), + plaintext_.data(), &plaintext_size_), + WB_RESULT_KEY_UNAVAILABLE); +} + +// Under normal circumstances, a hardware key should be dropped. The exception +// to this rule is on ChromeOS with a special license. +TEST_F(LicenseWhiteboxMaskedDecryptTest, + InsufficientSecurityLevelForHardwareContentKey) { + LoadLicense(LicenseBuilder::NoPadding()); + + ASSERT_EQ(WB_License_MaskedDecrypt( + whitebox_, WB_CIPHER_MODE_CBC, + golden_data_.CBCHardwareKey().id.data(), + golden_data_.CBCHardwareKey().id.size(), + golden_data_.CBCHardwareKey().content->ciphertext.data(), + golden_data_.CBCHardwareKey().content->ciphertext.size(), + golden_data_.CBCHardwareKey().content->iv.data(), + golden_data_.CBCHardwareKey().content->iv.size(), + plaintext_.data(), &plaintext_size_), + WB_RESULT_INSUFFICIENT_SECURITY_LEVEL); +} + +// Unlike the other tests, we do not call LoadLicense() as the criteria for +// WB_RESULT_INVALID_STATE is that no key can be found and keys are provided +// via a license. +TEST_F(LicenseWhiteboxMaskedDecryptTest, InvalidState) { + ASSERT_EQ( + WB_License_MaskedDecrypt( + whitebox_, WB_CIPHER_MODE_CBC, golden_data_.CBCDecodeKey().id.data(), + golden_data_.CBCDecodeKey().id.size(), + golden_data_.CBCDecodeKey().content->ciphertext.data(), + golden_data_.CBCDecodeKey().content->ciphertext.size(), + golden_data_.CBCDecodeKey().content->iv.data(), + golden_data_.CBCDecodeKey().content->iv.size(), plaintext_.data(), + &plaintext_size_), + WB_RESULT_INVALID_STATE); +} + +TEST_F(LicenseWhiteboxMaskedDecryptTest, BufferTooSmall) { + LoadLicense(LicenseBuilder::NoPadding()); + + // Our ciphertext will be large enough that we should not need to worry about + // using a constant here. + plaintext_size_ = 8; + + ASSERT_EQ( + WB_License_MaskedDecrypt( + whitebox_, WB_CIPHER_MODE_CBC, golden_data_.CBCDecodeKey().id.data(), + golden_data_.CBCDecodeKey().id.size(), + golden_data_.CBCDecodeKey().content->ciphertext.data(), + golden_data_.CBCDecodeKey().content->ciphertext.size(), + golden_data_.CBCDecodeKey().content->iv.data(), + golden_data_.CBCDecodeKey().content->iv.size(), plaintext_.data(), + &plaintext_size_), + WB_RESULT_BUFFER_TOO_SMALL); + + // We don't use padding so the reported plaintext size should be the same as + // the cipher text size. + ASSERT_EQ(plaintext_size_, + golden_data_.CBCDecodeKey().content->ciphertext.size()); +} + +} // namespace widevine diff --git a/api/license_whitebox_process_license_response_test.cc b/api/license_whitebox_process_license_response_test.cc new file mode 100644 index 0000000..83aac8e --- /dev/null +++ b/api/license_whitebox_process_license_response_test.cc @@ -0,0 +1,284 @@ +// Copyright 2020 Google LLC. All Rights Reserved. + +#include "api/license_whitebox.h" + +#include +#include + +#include "api/golden_data.h" +#include "api/license_builder.h" +#include "api/license_whitebox_test_base.h" +#include "api/test_data.h" +#include "crypto_utils/crypto_util.h" +#include "crypto_utils/rsa_key.h" +#include "testing/include/gtest/gtest.h" + +namespace widevine { + +class LicenseWhiteboxProcessLicenseResponseTest + : public LicenseWhiteboxTestBase { + protected: + void UseLicenseWithoutSigningKey() { + LicenseBuilder builder; + builder.AddStubbedContentKey(); + builder.Build(*public_key_, &license_); + } + + void UseLicenseWithSigningKey(const std::vector& padding) { + LicenseBuilder builder; + builder.AddSigningKey(LicenseBuilder::DefaultSigningKey(), padding); + builder.AddStubbedContentKey(); + builder.Build(*public_key_, &license_); + } + + License license_; +}; + +TEST_F(LicenseWhiteboxProcessLicenseResponseTest, SuccessWithoutSigningKey) { + UseLicenseWithoutSigningKey(); + + ASSERT_EQ(WB_License_ProcessLicenseResponse( + whitebox_, license_.message.data(), license_.message.size(), + license_.signature.data(), license_.signature.size(), + license_.session_key.data(), license_.session_key.size(), + license_.request.data(), license_.request.size()), + WB_RESULT_OK); +} + +TEST_F(LicenseWhiteboxProcessLicenseResponseTest, + SuccessWithSigningKeyNoPadding) { + UseLicenseWithSigningKey(LicenseBuilder::NoPadding()); + + ASSERT_EQ(WB_License_ProcessLicenseResponse( + whitebox_, license_.message.data(), license_.message.size(), + license_.signature.data(), license_.signature.size(), + license_.session_key.data(), license_.session_key.size(), + license_.request.data(), license_.request.size()), + WB_RESULT_OK); +} + +TEST_F(LicenseWhiteboxProcessLicenseResponseTest, + SuccessWithSigningKeyPKSC8Padding) { + UseLicenseWithSigningKey(LicenseBuilder::PKSC8Padding()); + + ASSERT_EQ(WB_License_ProcessLicenseResponse( + whitebox_, license_.message.data(), license_.message.size(), + license_.signature.data(), license_.signature.size(), + license_.session_key.data(), license_.session_key.size(), + license_.request.data(), license_.request.size()), + WB_RESULT_OK); +} + +class LicenseWhiteboxProcessLicenseResponseErrorTest + : public LicenseWhiteboxProcessLicenseResponseTest { + protected: + void SetUp() override { + LicenseWhiteboxProcessLicenseResponseTest::SetUp(); + + // For these tests, we don't care what license we use, it just needs to be + // a valid license. + UseLicenseWithoutSigningKey(); + } +}; + +TEST_F(LicenseWhiteboxProcessLicenseResponseErrorTest, + InvalidSignatureForModifedMessage) { + Modify(&license_.message); + + ASSERT_EQ(WB_License_ProcessLicenseResponse( + whitebox_, license_.message.data(), license_.message.size(), + license_.signature.data(), license_.signature.size(), + license_.session_key.data(), license_.session_key.size(), + license_.request.data(), license_.request.size()), + WB_RESULT_INVALID_SIGNATURE); +} + +TEST_F(LicenseWhiteboxProcessLicenseResponseErrorTest, + InvalidSignatureForModifedSignature) { + Modify(&license_.signature); + + ASSERT_EQ(WB_License_ProcessLicenseResponse( + whitebox_, license_.message.data(), license_.message.size(), + license_.signature.data(), license_.signature.size(), + license_.session_key.data(), license_.session_key.size(), + license_.request.data(), license_.request.size()), + WB_RESULT_INVALID_SIGNATURE); +} + +// The license request is used to derive the signing key. If the request was +// modified, then the wrong signing key should be generated. +TEST_F(LicenseWhiteboxProcessLicenseResponseErrorTest, + InvalidSignatureForModifedLicenseRequest) { + Modify(&license_.request); + + ASSERT_EQ(WB_License_ProcessLicenseResponse( + whitebox_, license_.message.data(), license_.message.size(), + license_.signature.data(), license_.signature.size(), + license_.session_key.data(), license_.session_key.size(), + license_.request.data(), license_.request.size()), + WB_RESULT_INVALID_SIGNATURE); +} + +TEST_F(LicenseWhiteboxProcessLicenseResponseErrorTest, + InvalidParameterForNullWhitebox) { + ASSERT_EQ(WB_License_ProcessLicenseResponse( + nullptr, license_.message.data(), license_.message.size(), + license_.signature.data(), license_.signature.size(), + license_.session_key.data(), license_.session_key.size(), + license_.request.data(), license_.request.size()), + WB_RESULT_INVALID_PARAMETER); +} + +TEST_F(LicenseWhiteboxProcessLicenseResponseErrorTest, + InvalidParameterForNullMessage) { + ASSERT_EQ(WB_License_ProcessLicenseResponse( + whitebox_, nullptr, license_.message.size(), + license_.signature.data(), license_.signature.size(), + license_.session_key.data(), license_.session_key.size(), + license_.request.data(), license_.request.size()), + WB_RESULT_INVALID_PARAMETER); +} + +TEST_F(LicenseWhiteboxProcessLicenseResponseErrorTest, + InvalidParameterForZeroMessageSize) { + ASSERT_EQ(WB_License_ProcessLicenseResponse( + whitebox_, license_.message.data(), 0, + license_.signature.data(), license_.signature.size(), + license_.session_key.data(), license_.session_key.size(), + license_.request.data(), license_.request.size()), + WB_RESULT_INVALID_PARAMETER); +} + +TEST_F(LicenseWhiteboxProcessLicenseResponseErrorTest, + InvalidParameterForNullSignature) { + ASSERT_EQ(WB_License_ProcessLicenseResponse( + whitebox_, license_.message.data(), license_.message.size(), + nullptr, license_.signature.size(), license_.session_key.data(), + license_.session_key.size(), license_.request.data(), + license_.request.size()), + WB_RESULT_INVALID_PARAMETER); +} + +TEST_F(LicenseWhiteboxProcessLicenseResponseErrorTest, + InvalidParameterForInvalidSignatureSize) { + ASSERT_EQ(WB_License_ProcessLicenseResponse( + whitebox_, license_.message.data(), license_.message.size(), + license_.signature.data(), 5, license_.session_key.data(), + license_.session_key.size(), license_.request.data(), + license_.request.size()), + WB_RESULT_INVALID_PARAMETER); +} + +TEST_F(LicenseWhiteboxProcessLicenseResponseErrorTest, + InvalidParameterForNullSessionKey) { + ASSERT_EQ(WB_License_ProcessLicenseResponse( + whitebox_, license_.message.data(), license_.message.size(), + license_.signature.data(), license_.signature.size(), nullptr, + license_.session_key.size(), license_.request.data(), + license_.request.size()), + WB_RESULT_INVALID_PARAMETER); +} + +TEST_F(LicenseWhiteboxProcessLicenseResponseErrorTest, + InvalidParameterForInvalidSessionKeySize) { + ASSERT_EQ(WB_License_ProcessLicenseResponse( + whitebox_, license_.message.data(), license_.message.size(), + license_.signature.data(), license_.signature.size(), + license_.session_key.data(), 5, license_.request.data(), + license_.request.size()), + WB_RESULT_INVALID_PARAMETER); +} + +// If the session key is modified, unwrapping it will fail. Therefore, we will +// know that the parameter is invalid (compared to a modified license request). +TEST_F(LicenseWhiteboxProcessLicenseResponseErrorTest, + InvalidParameterForModifedSessionKey) { + Modify(&license_.session_key); + + ASSERT_EQ(WB_License_ProcessLicenseResponse( + whitebox_, license_.message.data(), license_.message.size(), + license_.signature.data(), license_.signature.size(), + license_.session_key.data(), license_.session_key.size(), + license_.request.data(), license_.request.size()), + WB_RESULT_INVALID_PARAMETER); +} + +TEST_F(LicenseWhiteboxProcessLicenseResponseErrorTest, + InvalidParameterForNullLicenseRequest) { + ASSERT_EQ(WB_License_ProcessLicenseResponse( + whitebox_, license_.message.data(), license_.message.size(), + license_.signature.data(), license_.signature.size(), + license_.session_key.data(), license_.session_key.size(), + nullptr, license_.request.size()), + WB_RESULT_INVALID_PARAMETER); +} + +TEST_F(LicenseWhiteboxProcessLicenseResponseErrorTest, + InvalidParameterForZeroLienseRequestSize) { + ASSERT_EQ(WB_License_ProcessLicenseResponse( + whitebox_, license_.message.data(), license_.message.size(), + license_.signature.data(), license_.signature.size(), + license_.session_key.data(), license_.session_key.size(), + license_.request.data(), 0), + WB_RESULT_INVALID_PARAMETER); +} + +class LicenseWhiteboxMultiLicenseTest + : public LicenseWhiteboxProcessLicenseResponseTest { + protected: + void SetUp() override { + LicenseWhiteboxProcessLicenseResponseTest::SetUp(); + + // For these tests, we don't care what license we use, it just needs to be + // a valid license. + UseLicenseWithoutSigningKey(); + } +}; + +// A whitebox can only process a license once. If it has loaded a license +// (successfully) it should reject later calls with WB_RESULT_INVALID_STATE. +TEST_F(LicenseWhiteboxMultiLicenseTest, InvalidState) { + // Load the first license. This one is expected to succeed as the whitebox has + // not loaded a license yet. + ASSERT_EQ(WB_License_ProcessLicenseResponse( + whitebox_, license_.message.data(), license_.message.size(), + license_.signature.data(), license_.signature.size(), + license_.session_key.data(), license_.session_key.size(), + license_.request.data(), license_.request.size()), + WB_RESULT_OK); + + // Attempt to load the same license again. This should fail as it already has + // a license (even though it is the same license). + ASSERT_EQ(WB_License_ProcessLicenseResponse( + whitebox_, license_.message.data(), license_.message.size(), + license_.signature.data(), license_.signature.size(), + license_.session_key.data(), license_.session_key.size(), + license_.request.data(), license_.request.size()), + WB_RESULT_INVALID_STATE); +} + +// Even though a whitebox can only load a license once, if it fails to load a +// license, it should still be able to try again. +TEST_F(LicenseWhiteboxMultiLicenseTest, SuccessAfterFailure) { + // Force this one to fail my changing the request, this will cause an error + // in key derivation which is a later step of license parsing. + std::vector bad_request = license_.request; + Modify(&bad_request); + ASSERT_NE(WB_License_ProcessLicenseResponse( + whitebox_, license_.message.data(), license_.message.size(), + license_.signature.data(), license_.signature.size(), + license_.session_key.data(), license_.session_key.size(), + bad_request.data(), bad_request.size()), + WB_RESULT_OK); + + // Attempt to load the license again, but use the correct (unmodified) + // request. + ASSERT_EQ(WB_License_ProcessLicenseResponse( + whitebox_, license_.message.data(), license_.message.size(), + license_.signature.data(), license_.signature.size(), + license_.session_key.data(), license_.session_key.size(), + license_.request.data(), license_.request.size()), + WB_RESULT_OK); +} + +} // namespace widevine diff --git a/api/license_whitebox_sign_license_request_test.cc b/api/license_whitebox_sign_license_request_test.cc new file mode 100644 index 0000000..7bfdef6 --- /dev/null +++ b/api/license_whitebox_sign_license_request_test.cc @@ -0,0 +1,128 @@ +// Copyright 2020 Google LLC. All Rights Reserved. + +#include "api/license_whitebox.h" + +#include +#include +#include + +#include "api/license_builder.h" +#include "api/license_whitebox_test_base.h" +#include "api/test_data.h" +#include "crypto_utils/rsa_key.h" +#include "testing/include/gtest/gtest.h" + +namespace widevine { + +class LicenseWhiteboxSignLicenseRequestTest : public LicenseWhiteboxTestBase { + protected: + void SetUp() override { + LicenseWhiteboxTestBase::SetUp(); + + LicenseBuilder builder; + builder.Build(*public_key_, &license_); + + // We must make the default size large to hold the signature returned by + // WB_License_SignLicenseRequest(). + signature_size_ = 256; + signature_.resize(signature_size_); + } + + std::vector invalid_license_request_ = { + 0x1e, 0x70, 0xbd, 0xeb, 0x24, 0xf2, 0x9d, 0x05, 0xc5, 0xb5, + 0xf4, 0xca, 0xe6, 0x1d, 0x01, 0x97, 0x29, 0xf4, 0xe0, 0x7c, + 0xfd, 0xcc, 0x97, 0x8d, 0xc2, 0xbb, 0x2d, 0x9b, 0x6b, 0x45, + 0x06, 0xbd, 0x2c, 0x66, 0x10, 0x42, 0x73, 0x8d, 0x88, 0x9b, + 0x18, 0xcc, 0xcb, 0x7e, 0x43, 0x23, 0x06, 0xe9, 0x8f, 0x8f, + }; + + License license_; + + // These will be the output from each test case. + size_t signature_size_; + std::vector signature_; +}; + +TEST_F(LicenseWhiteboxSignLicenseRequestTest, SuccessForInvalidLicenseRequest) { + ASSERT_EQ( + WB_License_SignLicenseRequest(whitebox_, invalid_license_request_.data(), + invalid_license_request_.size(), + signature_.data(), &signature_size_), + WB_RESULT_OK); + + signature_.resize(signature_size_); + + ASSERT_TRUE(public_key_->VerifySignature( + std::string(invalid_license_request_.begin(), + invalid_license_request_.end()), + std::string(signature_.begin(), signature_.end()))); +} + +TEST_F(LicenseWhiteboxSignLicenseRequestTest, SuccessForValidLicenseRequest) { + ASSERT_EQ(WB_License_SignLicenseRequest(whitebox_, license_.request.data(), + license_.request.size(), + signature_.data(), &signature_size_), + WB_RESULT_OK); + + signature_.resize(signature_size_); + + ASSERT_TRUE(public_key_->VerifySignature( + std::string(license_.request.begin(), license_.request.end()), + std::string(signature_.begin(), signature_.end()))); +} + +TEST_F(LicenseWhiteboxSignLicenseRequestTest, InvalidParameterForNullWhitebox) { + ASSERT_EQ(WB_License_SignLicenseRequest(nullptr, license_.request.data(), + license_.request.size(), + signature_.data(), &signature_size_), + WB_RESULT_INVALID_PARAMETER); +} + +TEST_F(LicenseWhiteboxSignLicenseRequestTest, + InvalidParameterForNullLicenseRequest) { + ASSERT_EQ( + WB_License_SignLicenseRequest(whitebox_, nullptr, license_.request.size(), + signature_.data(), &signature_size_), + WB_RESULT_INVALID_PARAMETER); +} + +TEST_F(LicenseWhiteboxSignLicenseRequestTest, + InvalidParameterForZeroLicenseRequestSize) { + ASSERT_EQ(WB_License_SignLicenseRequest(whitebox_, license_.request.data(), 0, + signature_.data(), &signature_size_), + WB_RESULT_INVALID_PARAMETER); +} + +TEST_F(LicenseWhiteboxSignLicenseRequestTest, + InvalidParameterForNullSignature) { + ASSERT_EQ(WB_License_SignLicenseRequest(whitebox_, license_.request.data(), + license_.request.size(), nullptr, + &signature_size_), + WB_RESULT_INVALID_PARAMETER); +} + +TEST_F(LicenseWhiteboxSignLicenseRequestTest, + InvalidParameterForNullSignatureSize) { + ASSERT_EQ(WB_License_SignLicenseRequest(whitebox_, license_.request.data(), + license_.request.size(), + signature_.data(), nullptr), + WB_RESULT_INVALID_PARAMETER); +} + +TEST_F(LicenseWhiteboxSignLicenseRequestTest, BufferTooSmall) { + // We could test zero, but zero is just a subset of "smaller" and we want to + // make sure that they zero is not the only check. So use something larger + // than zero. + signature_size_ = 1; + + ASSERT_EQ(WB_License_SignLicenseRequest(whitebox_, license_.request.data(), + license_.request.size(), + signature_.data(), &signature_size_), + WB_RESULT_BUFFER_TOO_SMALL); + + // When WB_RESULT_BUFFER_TOO_SMALL is returned, the required buffer size + // should be returned via |signature_size|. Since we don't know what it is, we + // must rely on it being larger than the original "too small" size. + ASSERT_GT(signature_size_, 1); +} +} // namespace widevine diff --git a/api/license_whitebox_sign_renewal_request_test.cc b/api/license_whitebox_sign_renewal_request_test.cc index 3f0739c..25088a3 100644 --- a/api/license_whitebox_sign_renewal_request_test.cc +++ b/api/license_whitebox_sign_renewal_request_test.cc @@ -6,144 +6,196 @@ #include #include +#include "api/license_builder.h" +#include "api/license_whitebox_test_base.h" #include "api/test_data.h" +#include "crypto_utils/crypto_util.h" +#include "crypto_utils/rsa_key.h" #include "testing/include/gtest/gtest.h" -namespace { +namespace widevine { -const uint8_t kDummyMessage[] = { - 0x1e, 0x70, 0xbd, 0xeb, 0x24, 0xf2, 0x9d, 0x05, 0xc5, 0xb5, - 0xf4, 0xca, 0xe6, 0x1d, 0x01, 0x97, 0x29, 0xf4, 0xe0, 0x7c, - 0xfd, 0xcc, 0x97, 0x8d, 0xc2, 0xbb, 0x2d, 0x9b, 0x6b, 0x45, - 0x06, 0xbd, 0x2c, 0x66, 0x10, 0x42, 0x73, 0x8d, 0x88, 0x9b, - 0x18, 0xcc, 0xcb, 0x7e, 0x43, 0x23, 0x06, 0xe9, 0x8f, 0x8f, -}; -const size_t kDummyMessageSize = sizeof(kDummyMessage); - -// Size of a license signature. This must be big enough to hold the signature -// returned by WB_License_SignRenewalRequest(). -constexpr size_t kSignatureSize = 256; - -// These tests assume that WB_License_Create() and -// WB_License_ProcessLicenseResponse() work as all tests will require a valid -// white-box instance with a valid license already loaded. -class LicenseWhiteboxSignRenewalRequestTest : public ::testing::Test { +class LicenseWhiteboxSignRenewalRequestTest : public LicenseWhiteboxTestBase { protected: void SetUp() override { - const std::vector init_data_ = GetLicenseInitData(); - ASSERT_EQ( - WB_License_Create(init_data_.data(), init_data_.size(), &whitebox_), - WB_RESULT_OK); + LicenseWhiteboxTestBase::SetUp(); + + // We don't know the actual signature size, so make the buffer large enough + // that it should accomidate any change in signature size. + signature_size_ = 256; + signature_.resize(signature_size_); } - void TearDown() override { WB_License_Delete(whitebox_); } + void LoadLicense(const std::vector& padding) { + LicenseBuilder builder; + builder.AddSigningKey(signing_key_, padding); + // Add a throw away key. We just need a key in the license since a license + // should always have a content key. + builder.AddStubbedContentKey(); - void LoadLicense() { - // TODO: Load the license here. It would be nice if we could do it in - // SetUp(), but since we need to support the WB_RESULT_INVALID_STATE test - // case, we need a way to not load a license. + License license; + builder.Build(*public_key_, &license); + + ASSERT_EQ(WB_License_ProcessLicenseResponse( + whitebox_, license.message.data(), license.message.size(), + license.signature.data(), license.signature.size(), + license.session_key.data(), license.session_key.size(), + license.request.data(), license.request.size()), + WB_RESULT_OK); } - WB_License_Whitebox* whitebox_ = nullptr; + // Get the expected signature for |message|. By returning the message, this + // will allow us to assert in the test, giving us better output in the case of + // a failure. + std::vector GetSignature(const std::vector& message) { + // The client key is the second half of the signing key. + std::string key_str(signing_key_.begin(), signing_key_.end()); + key_str.erase(0, crypto_util::kSigningKeySizeBytes); + + const std::string signature = crypto_util::CreateSignatureHmacSha256( + key_str, std::string(message.begin(), message.end())); + + return std::vector(signature.begin(), signature.end()); + } + + const std::string session_key_ = "0123456789ABCDEF"; + + const std::vector signing_key_ = LicenseBuilder::DefaultSigningKey(); + + size_t signature_size_; + std::vector signature_; + + const std::vector garbage_request_ = { + 0x1e, 0x70, 0xbd, 0xeb, 0x24, 0xf2, 0x9d, 0x05, 0xc5, 0xb5, + 0xf4, 0xca, 0xe6, 0x1d, 0x01, 0x97, 0x29, 0xf4, 0xe0, 0x7c, + 0xfd, 0xcc, 0x97, 0x8d, 0xc2, 0xbb, 0x2d, 0x9b, 0x6b, 0x45, + 0x06, 0xbd, 0x2c, 0x66, 0x10, 0x42, 0x73, 0x8d, 0x88, 0x9b, + 0x18, 0xcc, 0xcb, 0x7e, 0x43, 0x23, 0x06, 0xe9, 0x8f, 0x8f, + }; }; -// TODO: Implement the success test case (ideally with a real renewal request). +TEST_F(LicenseWhiteboxSignRenewalRequestTest, SuccessWithInvalidRequest) { + LoadLicense(LicenseBuilder::NoPadding()); + + ASSERT_EQ(WB_License_SignRenewalRequest(whitebox_, garbage_request_.data(), + garbage_request_.size(), + signature_.data(), &signature_size_), + WB_RESULT_OK); + + signature_.resize(signature_size_); + ASSERT_EQ(signature_, GetSignature(garbage_request_)); +} + +TEST_F(LicenseWhiteboxSignRenewalRequestTest, + SuccessWithSigningKeyPKSC8Padding) { + LoadLicense(LicenseBuilder::PKSC8Padding()); + + ASSERT_EQ(WB_License_SignRenewalRequest(whitebox_, garbage_request_.data(), + garbage_request_.size(), + signature_.data(), &signature_size_), + WB_RESULT_OK); + + signature_.resize(signature_size_); + ASSERT_EQ(signature_, GetSignature(garbage_request_)); +} TEST_F(LicenseWhiteboxSignRenewalRequestTest, InvalidParameterForNullWhitebox) { - LoadLicense(); + LoadLicense(LicenseBuilder::NoPadding()); - size_t signature_size = kSignatureSize; - std::vector signature(signature_size); - - ASSERT_EQ( - WB_License_SignRenewalRequest(nullptr, kDummyMessage, kDummyMessageSize, - signature.data(), &signature_size), - WB_RESULT_INVALID_PARAMETER); + ASSERT_EQ(WB_License_SignRenewalRequest(nullptr, garbage_request_.data(), + garbage_request_.size(), + signature_.data(), &signature_size_), + WB_RESULT_INVALID_PARAMETER); } TEST_F(LicenseWhiteboxSignRenewalRequestTest, InvalidParameterForNullMessage) { - LoadLicense(); + LoadLicense(LicenseBuilder::NoPadding()); - size_t signature_size = kSignatureSize; - std::vector signature(signature_size); - - ASSERT_EQ(WB_License_SignRenewalRequest(whitebox_, nullptr, kDummyMessageSize, - signature.data(), &signature_size), - WB_RESULT_INVALID_PARAMETER); + ASSERT_EQ( + WB_License_SignRenewalRequest(whitebox_, nullptr, garbage_request_.size(), + signature_.data(), &signature_size_), + WB_RESULT_INVALID_PARAMETER); } TEST_F(LicenseWhiteboxSignRenewalRequestTest, InvalidParameterForZeroMessageSize) { - LoadLicense(); + LoadLicense(LicenseBuilder::NoPadding()); - size_t signature_size = kSignatureSize; - std::vector signature(signature_size); - - ASSERT_EQ(WB_License_SignRenewalRequest(whitebox_, kDummyMessage, 0, - signature.data(), &signature_size), + ASSERT_EQ(WB_License_SignRenewalRequest(whitebox_, garbage_request_.data(), 0, + signature_.data(), &signature_size_), WB_RESULT_INVALID_PARAMETER); } TEST_F(LicenseWhiteboxSignRenewalRequestTest, InvalidParameterForNullSignature) { - LoadLicense(); + LoadLicense(LicenseBuilder::NoPadding()); - size_t signature_size = kSignatureSize; - - ASSERT_EQ( - WB_License_SignRenewalRequest(whitebox_, kDummyMessage, kDummyMessageSize, - nullptr, &signature_size), - WB_RESULT_INVALID_PARAMETER); + ASSERT_EQ(WB_License_SignRenewalRequest(whitebox_, garbage_request_.data(), + garbage_request_.size(), nullptr, + &signature_size_), + WB_RESULT_INVALID_PARAMETER); } TEST_F(LicenseWhiteboxSignRenewalRequestTest, InvalidParameterForNullSignatureSize) { - LoadLicense(); + LoadLicense(LicenseBuilder::NoPadding()); - size_t signature_size = kSignatureSize; - std::vector signature(signature_size); - - ASSERT_EQ( - WB_License_SignRenewalRequest(whitebox_, kDummyMessage, kDummyMessageSize, - signature.data(), nullptr), - WB_RESULT_INVALID_PARAMETER); + ASSERT_EQ(WB_License_SignRenewalRequest(whitebox_, garbage_request_.data(), + garbage_request_.size(), + signature_.data(), nullptr), + WB_RESULT_INVALID_PARAMETER); } TEST_F(LicenseWhiteboxSignRenewalRequestTest, BufferTooSmall) { - // TODO: This test must be skipped as the "too small" check takes place after - // we check for a key. - GTEST_SKIP(); - - LoadLicense(); + LoadLicense(LicenseBuilder::NoPadding()); // We need the signature to be too small. While it would be possible to use // zero, using a non-zero value ensures that we are not combining "empty" and // "too small". - size_t signature_size = 1; - std::vector signature(signature_size); + signature_size_ = 1; - ASSERT_EQ( - WB_License_SignRenewalRequest(whitebox_, kDummyMessage, kDummyMessageSize, - signature.data(), &signature_size), - WB_RESULT_BUFFER_TOO_SMALL); + ASSERT_EQ(WB_License_SignRenewalRequest(whitebox_, garbage_request_.data(), + garbage_request_.size(), + signature_.data(), &signature_size_), + WB_RESULT_BUFFER_TOO_SMALL); // Since the API does not limit the signature size, we can't specify the // actual expected size, however, it should at least be greater than our "too // small" size. - ASSERT_GT(signature_size, 1); + ASSERT_GT(signature_size_, 1); } -TEST_F(LicenseWhiteboxSignRenewalRequestTest, InvalidState) { +TEST_F(LicenseWhiteboxSignRenewalRequestTest, InvalidStateForNoLicense) { // Unlike the other tests, we do not call LoadLicense() because we need to // have no license loaded in order to have no renewal key, which is the // criteria WB_RESULT_INVALID_STATE. - size_t signature_size = kSignatureSize; - std::vector signature(signature_size); - - ASSERT_EQ( - WB_License_SignRenewalRequest(whitebox_, kDummyMessage, kDummyMessageSize, - signature.data(), &signature_size), - WB_RESULT_INVALID_STATE); + ASSERT_EQ(WB_License_SignRenewalRequest(whitebox_, garbage_request_.data(), + garbage_request_.size(), + signature_.data(), &signature_size_), + WB_RESULT_INVALID_STATE); } -} // namespace + +TEST_F(LicenseWhiteboxSignRenewalRequestTest, InvalidStateForNoSigningKey) { + // Make a license with no signing key but has a content key. Every license + // must have a content key. + LicenseBuilder builder; + builder.AddStubbedContentKey(); + + License license; + builder.Build(*public_key_, &license); + + ASSERT_EQ(WB_License_ProcessLicenseResponse( + whitebox_, license.message.data(), license.message.size(), + license.signature.data(), license.signature.size(), + license.session_key.data(), license.session_key.size(), + license.request.data(), license.request.size()), + WB_RESULT_OK); + + ASSERT_EQ(WB_License_SignRenewalRequest(whitebox_, garbage_request_.data(), + garbage_request_.size(), + signature_.data(), &signature_size_), + WB_RESULT_INVALID_STATE); +} + +} // namespace widevine diff --git a/api/license_whitebox_test_base.cc b/api/license_whitebox_test_base.cc new file mode 100644 index 0000000..4276ab9 --- /dev/null +++ b/api/license_whitebox_test_base.cc @@ -0,0 +1,36 @@ +// Copyright 2020 Google LLC. All Rights Reserved. + +#include "api/license_whitebox_test_base.h" + +#include + +#include "api/test_data.h" + +namespace widevine { + +void LicenseWhiteboxTestBase::SetUp() { + const std::vector init_data = GetLicenseInitData(); + ASSERT_EQ(WB_License_Create(init_data.data(), init_data.size(), &whitebox_), + WB_RESULT_OK); + + const auto public_key_data = GetMatchingLicensePublicKey(); + public_key_.reset(RsaPublicKey::Create( + std::string(public_key_data.begin(), public_key_data.end()))); + + ASSERT_TRUE(public_key_); +} + +void LicenseWhiteboxTestBase::TearDown() { + WB_License_Delete(whitebox_); +} + +void LicenseWhiteboxTestBase::Modify(std::vector* data) const { + ASSERT_TRUE(data); + ASSERT_GT(data->size(), 0); + + // Bitwise-not the first byte so that we are guaranteed to have at least one + // byte different from the original data. + data->data()[0] = ~data->data()[0]; +} + +} // namespace widevine diff --git a/api/license_whitebox_test_base.h b/api/license_whitebox_test_base.h new file mode 100644 index 0000000..0733c70 --- /dev/null +++ b/api/license_whitebox_test_base.h @@ -0,0 +1,34 @@ +// Copyright 2020 Google LLC. All Rights Reserved. + +#ifndef WHITEBOX_API_LICENSE_WHITEBOX_TEST_BASE_H_ +#define WHITEBOX_API_LICENSE_WHITEBOX_TEST_BASE_H_ + +#include + +#include "api/golden_data.h" +#include "api/license_whitebox.h" +#include "crypto_utils/rsa_key.h" +#include "testing/include/gtest/gtest.h" + +namespace widevine { + +class LicenseWhiteboxTestBase : public ::testing::Test { + protected: + void SetUp() override; + + void TearDown() override; + + // Modify a buffer so that it won't be the exact same as it was before. This + // to make it easier to invalidate signatures. + void Modify(std::vector* data) const; + + std::unique_ptr public_key_; + + GoldenData golden_data_; + + WB_License_Whitebox* whitebox_; +}; + +} // namespace widevine + +#endif // WHITEBOX_API_LICENSE_WHITEBOX_TEST_BASE_H_ diff --git a/api/license_whitebox_verify_renewal_response_test.cc b/api/license_whitebox_verify_renewal_response_test.cc index 51fbd99..933a1fd 100644 --- a/api/license_whitebox_verify_renewal_response_test.cc +++ b/api/license_whitebox_verify_renewal_response_test.cc @@ -6,127 +6,186 @@ #include #include +#include "api/license_builder.h" +#include "api/license_whitebox_test_base.h" #include "api/test_data.h" +#include "crypto_utils/crypto_util.h" +#include "crypto_utils/rsa_key.h" #include "testing/include/gtest/gtest.h" -namespace { +namespace widevine { -// TODO: This needs to be the real response. -const uint8_t kResponse[] = { - 0x1e, 0x70, 0xbd, 0xeb, 0x24, 0xf2, 0x9d, 0x05, 0xc5, 0xb5, - 0xf4, 0xca, 0xe6, 0x1d, 0x01, 0x97, 0x29, 0xf4, 0xe0, 0x7c, - 0xfd, 0xcc, 0x97, 0x8d, 0xc2, 0xbb, 0x2d, 0x9b, 0x6b, 0x45, - 0x06, 0xbd, 0x2c, 0x66, 0x10, 0x42, 0x73, 0x8d, 0x88, 0x9b, - 0x18, 0xcc, 0xcb, 0x7e, 0x43, 0x23, 0x06, 0xe9, 0x8f, 0x8f, -}; -const size_t kResponseSize = sizeof(kResponse); - -// TODO: This needs to be the actual signature. -const uint8_t kSignature[] = { - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -}; - -const uint8_t kSignatureSize = sizeof(kSignature); - -// These tests assume that WB_License_Create() and -// WB_License_ProcessLicenseResponse() work as all tests will require a valid -// white-box instance with a valid license already loaded. -class LicenseWhiteboxVerifyRenewalResponseTest : public ::testing::Test { +class LicenseWhiteboxVerifyRenewalResponseTest + : public LicenseWhiteboxTestBase { protected: void SetUp() override { - const std::vector init_data_ = GetLicenseInitData(); - ASSERT_EQ( - WB_License_Create(init_data_.data(), init_data_.size(), &whitebox_), - WB_RESULT_OK); + LicenseWhiteboxTestBase::SetUp(); + garbage_renewal_signature_ = Sign(garbage_renewal_message_); } - void TearDown() override { WB_License_Delete(whitebox_); } + void LoadLicense(const std::vector& padding) { + const auto signing_key = LicenseBuilder::DefaultSigningKey(); - void LoadLicense() { - // TODO: Load the license here. It would be nice if we could do it in - // SetUp(), but since we need to support the WB_RESULT_INVALID_STATE test - // case, we need a way to not load a license. + // We need a license so that we can always have a valid signature for our + // message(s), but don't load the license as some test will need no + // license loaded. + LicenseBuilder builder; + builder.AddSigningKey(signing_key, padding); + builder.AddStubbedContentKey(); + + License license; + builder.Build(*public_key_, &license); + + ASSERT_EQ(WB_License_ProcessLicenseResponse( + whitebox_, license.message.data(), license.message.size(), + license.signature.data(), license.signature.size(), + license.session_key.data(), license.session_key.size(), + license.request.data(), license.request.size()), + WB_RESULT_OK); } - WB_License_Whitebox* whitebox_ = nullptr; + std::vector Sign(const std::vector& message) { + const auto key = LicenseBuilder::DefaultSigningKey(); + + // The server signing key is the first half of the signing key. + std::string server_key = std::string( + key.begin(), key.begin() + crypto_util::kSigningKeySizeBytes); + + const auto signature = crypto_util::CreateSignatureHmacSha256( + server_key, std::string(message.begin(), message.end())); + + return std::vector(signature.begin(), signature.end()); + } + + // Allow this to be mutable so that a test can corrupt it. This data is random + // and has no meaning. + std::vector garbage_renewal_message_ = { + 0xf1, 0x5c, 0xf1, 0x92, 0x73, 0x0c, 0xf9, 0x5d, 0x2b, 0x1e, 0x3f, 0x51, + 0xb2, 0x75, 0xa1, 0xb3, 0xd3, 0xa8, 0x16, 0x83, 0x08, 0xf1, 0xe2, 0x47, + 0x7b, 0x80, 0x37, 0xed, 0xf8, 0x8b, 0x1d, 0x79, 0x7f, 0xb0, 0xa1, 0xde, + 0xcd, 0xba, 0xd4, 0x8f, 0xb7, 0x3c, 0x1a, 0x3f, 0x3e, 0x3a, 0xb4, 0xea, + 0xd8, 0xd7, 0xa4, 0x65, 0xa1, 0x40, 0x87, 0xf6, 0xaa, 0xf4, 0xb1, 0x24, + 0x17, 0xed, 0xf4, 0xca, 0x18, 0x51, 0x4a, 0x54, 0x3c, 0x73, 0xca, 0x45, + 0x3e, 0xef, 0x39, 0x49, 0x65, 0xdd, 0x62, 0x11, 0x99, 0x13, 0x40, 0x67, + 0x7f, 0xfb, 0x07, 0x09, 0x1e, 0xfe, 0x0e, 0xdc, 0xda, 0x0a, 0x85, 0x91, + 0x15, 0x40, 0xa8, 0x7a, 0x0e, 0x76, 0xf6, 0xbe, 0x94, 0x2c, 0x70, 0xe9, + 0x07, 0xea, 0xf8, 0x7a, 0xc3, 0x48, 0xe1, 0xcf, 0xf4, 0x7b, 0xd6, 0x27, + 0xd7, 0x30, 0x6f, 0x18, 0xb3, 0x2d, 0x6a, 0x23, + }; + + // Allow this to be mutable so that we can initialize it in SetUp() but also + // so a test can corrupt it. + std::vector garbage_renewal_signature_; }; -// TODO: Implement the success test case. +// TODO: Implement a test that uses a real serialized response. Once we have a +// real serialized response, we should update all the tests - except the +// SuccessForGarbageMessage - to use the real serialized response. + +TEST_F(LicenseWhiteboxVerifyRenewalResponseTest, SuccessForGarbageMessage) { + LoadLicense(LicenseBuilder::NoPadding()); + + ASSERT_EQ(WB_License_VerifyRenewalResponse(whitebox_, + garbage_renewal_message_.data(), + garbage_renewal_message_.size(), + garbage_renewal_signature_.data(), + garbage_renewal_signature_.size()), + WB_RESULT_OK); +} + +TEST_F(LicenseWhiteboxVerifyRenewalResponseTest, + SuccessWithSigningKeyPKSC8Padding) { + LoadLicense(LicenseBuilder::PKSC8Padding()); + + ASSERT_EQ(WB_License_VerifyRenewalResponse(whitebox_, + garbage_renewal_message_.data(), + garbage_renewal_message_.size(), + garbage_renewal_signature_.data(), + garbage_renewal_signature_.size()), + WB_RESULT_OK); +} TEST_F(LicenseWhiteboxVerifyRenewalResponseTest, InvalidParameterForNullWhitebox) { - LoadLicense(); + LoadLicense(LicenseBuilder::NoPadding()); - ASSERT_EQ(WB_License_VerifyRenewalResponse(nullptr, kResponse, kResponseSize, - kSignature, kSignatureSize), - WB_RESULT_INVALID_PARAMETER); + ASSERT_EQ( + WB_License_VerifyRenewalResponse(nullptr, garbage_renewal_message_.data(), + garbage_renewal_message_.size(), + garbage_renewal_signature_.data(), + garbage_renewal_signature_.size()), + WB_RESULT_INVALID_PARAMETER); } TEST_F(LicenseWhiteboxVerifyRenewalResponseTest, InvalidParameterForNullMessage) { - LoadLicense(); + LoadLicense(LicenseBuilder::NoPadding()); - ASSERT_EQ(WB_License_VerifyRenewalResponse(whitebox_, nullptr, kResponseSize, - kSignature, kSignatureSize), + ASSERT_EQ(WB_License_VerifyRenewalResponse(whitebox_, nullptr, + garbage_renewal_message_.size(), + garbage_renewal_signature_.data(), + garbage_renewal_signature_.size()), WB_RESULT_INVALID_PARAMETER); } TEST_F(LicenseWhiteboxVerifyRenewalResponseTest, InvalidParameterForZeroMessageSize) { - LoadLicense(); + LoadLicense(LicenseBuilder::NoPadding()); - ASSERT_EQ(WB_License_VerifyRenewalResponse(whitebox_, kResponse, 0, - kSignature, kSignatureSize), + ASSERT_EQ(WB_License_VerifyRenewalResponse(whitebox_, + garbage_renewal_message_.data(), 0, + garbage_renewal_signature_.data(), + garbage_renewal_signature_.size()), WB_RESULT_INVALID_PARAMETER); } TEST_F(LicenseWhiteboxVerifyRenewalResponseTest, InvalidParameterForNullSignature) { - LoadLicense(); + LoadLicense(LicenseBuilder::NoPadding()); ASSERT_EQ(WB_License_VerifyRenewalResponse( - whitebox_, kResponse, kResponseSize, nullptr, kSignatureSize), + whitebox_, garbage_renewal_message_.data(), + garbage_renewal_message_.size(), nullptr, + garbage_renewal_signature_.size()), WB_RESULT_INVALID_PARAMETER); } TEST_F(LicenseWhiteboxVerifyRenewalResponseTest, InvalidParameterForInvalidSignatureSize) { - // TODO: This test needs to be skipped for now as the check for signature size - // is done after we compute the signature. This requires a valid license to - // have been loaded. - GTEST_SKIP(); + LoadLicense(LicenseBuilder::NoPadding()); - // We need an invalid signature size, the easiest way to do this is to take - // the expected signature size and trim a bit off the end. - const size_t invalid_signature_size = kSignatureSize - 1; - - LoadLicense(); - - ASSERT_EQ( - WB_License_VerifyRenewalResponse(whitebox_, kResponse, kResponseSize, - kSignature, invalid_signature_size), - WB_RESULT_INVALID_PARAMETER); + ASSERT_EQ(WB_License_VerifyRenewalResponse( + whitebox_, garbage_renewal_message_.data(), + garbage_renewal_message_.size(), + garbage_renewal_signature_.data(), 14), + WB_RESULT_INVALID_PARAMETER); } TEST_F(LicenseWhiteboxVerifyRenewalResponseTest, - InvalidSignatureForInvalidSignature) { - // TODO: This test needs to be skipped for now as comparing signatures - // requires a key provided by a valid license. - GTEST_SKIP(); + InvalidSignatureForModifiedMessage) { + LoadLicense(LicenseBuilder::NoPadding()); - // In order to create an invalid signature, copy the valid signature and flip - // one byte. This will ensure that our invalid signature is always different - // from the valid signature. - std::vector invalid_signature(kSignature, - kSignature + kSignatureSize); - invalid_signature[0] = ~invalid_signature[0]; + Modify(&garbage_renewal_message_); - LoadLicense(); + ASSERT_EQ(WB_License_VerifyRenewalResponse(whitebox_, + garbage_renewal_message_.data(), + garbage_renewal_message_.size(), + garbage_renewal_signature_.data(), + garbage_renewal_signature_.size()), + WB_RESULT_INVALID_SIGNATURE); +} - ASSERT_EQ(WB_License_VerifyRenewalResponse( - whitebox_, kResponse, kResponseSize, invalid_signature.data(), - invalid_signature.size()), +TEST_F(LicenseWhiteboxVerifyRenewalResponseTest, + InvalidSignatureForModifiedSignature) { + LoadLicense(LicenseBuilder::NoPadding()); + + Modify(&garbage_renewal_signature_); + + ASSERT_EQ(WB_License_VerifyRenewalResponse(whitebox_, + garbage_renewal_message_.data(), + garbage_renewal_message_.size(), + garbage_renewal_signature_.data(), + garbage_renewal_signature_.size()), WB_RESULT_INVALID_SIGNATURE); } @@ -134,9 +193,36 @@ TEST_F(LicenseWhiteboxVerifyRenewalResponseTest, InvalidStateForNoLicense) { // Unlike the other tests, we do not call LoadLicense() as the criteria for // WB_RESULT_INVALID_STATE is that no key can be found and keys are provided // via a license. - ASSERT_EQ( - WB_License_VerifyRenewalResponse(whitebox_, kResponse, kResponseSize, - kSignature, kSignatureSize), - WB_RESULT_INVALID_STATE); + ASSERT_EQ(WB_License_VerifyRenewalResponse(whitebox_, + garbage_renewal_message_.data(), + garbage_renewal_message_.size(), + garbage_renewal_signature_.data(), + garbage_renewal_signature_.size()), + WB_RESULT_INVALID_STATE); } -} // namespace + +TEST_F(LicenseWhiteboxVerifyRenewalResponseTest, InvalidStateForNoSigningKey) { + // Create a license with no signing key and one content key (every license + // must have a content key). + widevine::LicenseBuilder builder; + builder.AddStubbedContentKey(); + + License license; + builder.Build(*public_key_, &license); + + ASSERT_EQ(WB_License_ProcessLicenseResponse( + whitebox_, license.message.data(), license.message.size(), + license.signature.data(), license.signature.size(), + license.session_key.data(), license.session_key.size(), + license.request.data(), license.request.size()), + WB_RESULT_OK); + + ASSERT_EQ(WB_License_VerifyRenewalResponse(whitebox_, + garbage_renewal_message_.data(), + garbage_renewal_message_.size(), + garbage_renewal_signature_.data(), + garbage_renewal_signature_.size()), + WB_RESULT_INVALID_STATE); +} + +} // namespace widevine diff --git a/api/result.h b/api/result.h index fd8fed0..0d9935c 100644 --- a/api/result.h +++ b/api/result.h @@ -29,23 +29,20 @@ typedef enum { // The message and signature did not pass signature verification. WB_RESULT_INVALID_SIGNATURE = 5, - // A key was requested that had not been loaded into the white-box. - WB_RESULT_NO_SUCH_KEY = 6, - - // A key was requested that was found in the white-box, but is being used - // improperly. - WB_RESULT_WRONG_KEY_TYPE = 7, + // The request key is not available for the operations. For example, no key + // exists for the given key id. + WB_RESULT_KEY_UNAVAILABLE = 6, // The requested key's security level is not sufficient for the operation. - WB_RESULT_INSUFFICIENT_SECURITY_LEVEL = 8, + WB_RESULT_INSUFFICIENT_SECURITY_LEVEL = 7, // The input data failed to be verified. This may happen if the data was // corrupted or tampered. - WB_RESULT_DATA_VERIFICATION_ERROR = 9, + WB_RESULT_DATA_VERIFICATION_ERROR = 8, // The padding at the end of the decrypted data does not match the // specification. - WB_RESULT_INVALID_PADDING = 10, + WB_RESULT_INVALID_PADDING = 9, } WB_Result; #ifdef __cplusplus diff --git a/impl/reference/license_whitebox_impl.cc b/impl/reference/license_whitebox_impl.cc index 8612ddc..2d1f142 100644 --- a/impl/reference/license_whitebox_impl.cc +++ b/impl/reference/license_whitebox_impl.cc @@ -26,15 +26,17 @@ #include "third_party/boringssl/src/include/openssl/sha.h" namespace { -using KeyContainer = video_widevine::License_KeyContainer; -using RsaPrivateKey = widevine::RsaPrivateKey; using AesCbcDecryptor = widevine::AesCbcDecryptor; using AesCtrDecryptor = widevine::AesCtrEncryptor; -using SecurityLevel = video_widevine::License_KeyContainer_SecurityLevel; +using KeyContainer = video_widevine::License_KeyContainer; +using RsaPrivateKey = widevine::RsaPrivateKey; struct ContentKey { - // Minimum security level that this key requires in order to be used. - SecurityLevel level; + // When we store a key, we create our own little policy for the key saying + // what functions may use it. This allows us to "blacklist" a key by setting + // all "allow_*" to false. + bool allow_decrypt; + bool allow_masked_decrypt; // Key used to decrypt content. std::vector key; @@ -50,10 +52,6 @@ struct WB_License_Whitebox { std::string server_signing_key; std::string client_signing_key; - // We use two data structures to track keys. We track all the key ids seen - // when loading the license and the content keys. This allows us to tell the - // difference between a key missing and a key being misused. - std::set all_key_ids; std::map content_keys; }; @@ -88,36 +86,15 @@ void ApplyPattern(const uint8_t* input_buffer, } } -WB_Result FindKey(const WB_License_Whitebox* whitebox, - const uint8_t* id, - size_t id_size, - ContentKey* key) { +const ContentKey* FindKey(const WB_License_Whitebox* whitebox, + const uint8_t* id, + size_t id_size) { DCHECK(whitebox); DCHECK(id); DCHECK_GT(id_size, 0u); - DCHECK(key); - const std::string key_id = AsString(id, id_size); - - if (whitebox->all_key_ids.empty()) { - DVLOG(1) << "Invalid state: no keys have been loaded."; - return WB_RESULT_INVALID_STATE; - } - - if (whitebox->all_key_ids.find(key_id) == whitebox->all_key_ids.end()) { - DVLOG(1) << "No such key: " << base::HexEncode(id, id_size) << "."; - return WB_RESULT_NO_SUCH_KEY; - } - const auto found = whitebox->content_keys.find(key_id); - - if (found == whitebox->content_keys.end()) { - DVLOG(1) << "Wrong key type: " << base::HexEncode(id, id_size) << "."; - return WB_RESULT_WRONG_KEY_TYPE; - } - - *key = found->second; - return WB_RESULT_OK; + return found == whitebox->content_keys.end() ? nullptr : &found->second; } WB_Result DecryptBuffer(WB_CipherMode mode, @@ -246,6 +223,7 @@ WB_Result WB_License_SignLicenseRequest(const WB_License_Whitebox* whitebox, return WB_RESULT_BUFFER_TOO_SMALL; } + *signature_size = result.size(); return WB_RESULT_OK; } @@ -258,16 +236,33 @@ WB_Result WB_License_ProcessLicenseResponse(WB_License_Whitebox* whitebox, size_t session_key_size, const uint8_t* license_request, size_t license_request_size) { + const size_t kSigningKeySizeBytes = + widevine::crypto_util::kSigningKeySizeBytes; + if (!whitebox || !message || !signature || !session_key || !license_request) { DVLOG(1) << "Invalid parameter: null pointer."; return WB_RESULT_INVALID_PARAMETER; } - if (message_size == 0 || license_request_size == 0) { + // If we have already loaded a license, ProcessLicenseResponse() may not be + // called again. The white-box needs to be destroyed and a new one needs + // to be created. + if (whitebox->content_keys.size() > 0) { + DVLOG(1) << "Invalid state: already loaded a license."; + return WB_RESULT_INVALID_STATE; + } + + if (message_size == 0 || session_key_size == 0 || license_request_size == 0) { DVLOG(1) << "Invalid parameter: array size 0."; return WB_RESULT_INVALID_PARAMETER; } + // Because we use SHA256, the hash will be 32 bytes (256 bits). + if (signature_size != 32) { + DVLOG(1) << "Invalid parameter: invalid signature size."; + return WB_RESULT_INVALID_PARAMETER; + } + std::string decrypted_session_key; if (!whitebox->key->Decrypt(AsString(session_key, session_key_size), &decrypted_session_key)) { @@ -280,15 +275,15 @@ WB_Result WB_License_ProcessLicenseResponse(WB_License_Whitebox* whitebox, widevine::AsStringView(license_request, license_request_size), widevine::crypto_util::kSigningKeySizeBits * 2); - if (signing_key_material.size() < - widevine::crypto_util::kSigningKeySizeBytes * 2) { + if (signing_key_material.size() < kSigningKeySizeBytes * 2) { DVLOG(1) << "Invalid parameter: invalid session key size."; return WB_RESULT_INVALID_PARAMETER; } + const std::string server_signing_key = + signing_key_material.substr(0, kSigningKeySizeBytes); if (!widevine::crypto_util::VerifySignatureHmacSha256( - signing_key_material, - widevine::AsStringView(signature, signature_size), + server_signing_key, widevine::AsStringView(signature, signature_size), widevine::AsStringView(message, message_size))) { DVLOG(1) << "Failed to verify signed message."; return WB_RESULT_INVALID_SIGNATURE; @@ -314,8 +309,18 @@ WB_Result WB_License_ProcessLicenseResponse(WB_License_Whitebox* whitebox, return WB_RESULT_INVALID_PARAMETER; } - CHECK(!license.key().empty()); + std::string server_renewal_key; + std::string client_renewal_key; + std::map content_keys; + for (const auto& key : license.key()) { + // If this is not a key we're interested in, skip it as soon as possible. + // Don't even bother unwrapping it. + if (key.type() != KeyContainer::SIGNING && + key.type() != KeyContainer::CONTENT) { + continue; + } + const std::string wrapped_key = key.key(); std::vector unwrapped_key(wrapped_key.size()); @@ -329,47 +334,64 @@ WB_Result WB_License_ProcessLicenseResponse(WB_License_Whitebox* whitebox, return WB_RESULT_INVALID_PARAMETER; } - whitebox->all_key_ids.insert(key.id()); - - switch (key.type()) { - case KeyContainer::SIGNING: { - const size_t kSigningKeySizeBytes = - widevine::crypto_util::kSigningKeySizeBytes; - if (unwrapped_key.size() < kSigningKeySizeBytes * 2) { - DVLOG(1) << "Invalid parameter: Invalid signing key."; - return WB_RESULT_INVALID_PARAMETER; - } - - const std::string signing_key = - AsString(unwrapped_key.data(), unwrapped_key.size()); - whitebox->server_signing_key = - signing_key.substr(0, kSigningKeySizeBytes); - whitebox->client_signing_key = - signing_key.substr(kSigningKeySizeBytes, kSigningKeySizeBytes); - break; + if (key.type() == KeyContainer::SIGNING) { + if (unwrapped_key.size() < kSigningKeySizeBytes * 2) { + DVLOG(1) << "Invalid parameter: Invalid signing key."; + return WB_RESULT_INVALID_PARAMETER; } - case KeyContainer::CONTENT: { - constexpr size_t kContentKeySizeBytes = 16; - if (unwrapped_key.size() < kContentKeySizeBytes) { - DVLOG(1) << "Invalid parameter: Invalid content key."; - return WB_RESULT_INVALID_PARAMETER; - } + const std::string signing_key = + AsString(unwrapped_key.data(), unwrapped_key.size()); + server_renewal_key = signing_key.substr(0, kSigningKeySizeBytes); + client_renewal_key = + signing_key.substr(kSigningKeySizeBytes, kSigningKeySizeBytes); + } else if (key.type() == KeyContainer::CONTENT) { + constexpr size_t kContentKeySizeBytes = 16; - unwrapped_key.resize(kContentKeySizeBytes); - whitebox->content_keys[key.id()] = {key.level(), - std::move(unwrapped_key)}; - break; + if (unwrapped_key.size() < kContentKeySizeBytes) { + DVLOG(1) << "Invalid parameter: Invalid content key."; + return WB_RESULT_INVALID_PARAMETER; } - default: - // We purposefully ignore the other cases. In the case that any other - // key appeared in the license, the key id would have been tracked in - // |all_key_ids|. - break; + unwrapped_key.resize(kContentKeySizeBytes); + + ContentKey content_key; + + switch (key.level()) { + case video_widevine:: + License_KeyContainer_SecurityLevel_SW_SECURE_CRYPTO: + content_key.allow_decrypt = true; + content_key.allow_masked_decrypt = true; + content_key.key = std::move(unwrapped_key); + break; + case video_widevine:: + License_KeyContainer_SecurityLevel_SW_SECURE_DECODE: + content_key.allow_decrypt = false; + content_key.allow_masked_decrypt = true; + content_key.key = std::move(unwrapped_key); + break; + default: + content_key.allow_decrypt = false; + content_key.allow_masked_decrypt = false; + // Don't set key. We don't want to save this key as we should never + // be using it. We only have an entry so that we can handle errors + // correctly. + break; + } + + content_keys[key.id()] = content_key; + } else { + // We should have already skipped over this key. + CHECK(false); } } + // Copy the loaded state over to the white-box instance now that we know we + // have a valid state. + whitebox->server_signing_key.swap(server_renewal_key); + whitebox->client_signing_key.swap(client_renewal_key); + whitebox->content_keys.swap(content_keys); + return WB_RESULT_OK; } @@ -388,10 +410,13 @@ WB_Result WB_License_SignRenewalRequest(const WB_License_Whitebox* whitebox, return WB_RESULT_INVALID_PARAMETER; } - // License request must have been processed at least once before calling this - // function. + if (whitebox->content_keys.empty()) { + DVLOG(1) << "Invalid state: missing license."; + return WB_RESULT_INVALID_STATE; + } + if (whitebox->client_signing_key.empty()) { - DVLOG(1) << __func__ << " called before license received."; + DVLOG(1) << "Invalid state: license does not support renewals."; return WB_RESULT_INVALID_STATE; } @@ -408,6 +433,7 @@ WB_Result WB_License_SignRenewalRequest(const WB_License_Whitebox* whitebox, return WB_RESULT_BUFFER_TOO_SMALL; } + *signature_size = computed_signature.size(); return WB_RESULT_OK; } @@ -426,10 +452,13 @@ WB_Result WB_License_VerifyRenewalResponse(const WB_License_Whitebox* whitebox, return WB_RESULT_INVALID_PARAMETER; } - // License request must have been processed at least once before calling this - // function. + if (whitebox->content_keys.empty()) { + DVLOG(1) << "Invalid state: missing license."; + return WB_RESULT_INVALID_STATE; + } + if (whitebox->server_signing_key.empty()) { - DVLOG(1) << __func__ << " called before license received."; + DVLOG(1) << "Invalid state: license does not support renewals."; return WB_RESULT_INVALID_STATE; } @@ -438,15 +467,14 @@ WB_Result WB_License_VerifyRenewalResponse(const WB_License_Whitebox* whitebox, whitebox->server_signing_key, widevine::AsStringView(message, message_size)); - if (signature_size < computed_signature.size()) { - DVLOG(1) << "Invalid parameters: invalid signature " - "size."; + if (signature_size != computed_signature.size()) { + DVLOG(1) << "Invalid parameters: invalid signature size."; return WB_RESULT_INVALID_PARAMETER; } if (computed_signature != widevine::AsStringView(signature, signature_size)) { DVLOG(1) << "Data verification error: signatures do not match."; - return WB_RESULT_DATA_VERIFICATION_ERROR; + return WB_RESULT_INVALID_SIGNATURE; } return WB_RESULT_OK; @@ -463,6 +491,11 @@ WB_Result WB_License_GetSecretString(const WB_License_Whitebox* whitebox, return WB_RESULT_INVALID_PARAMETER; } + if (whitebox->content_keys.empty()) { + DVLOG(1) << "Invalid state: missing license."; + return WB_RESULT_INVALID_STATE; + } + if (mode != WB_CIPHER_MODE_CTR && mode != WB_CIPHER_MODE_CBC) { DVLOG(1) << "Invalid parameter: invalid cipher mode."; return WB_RESULT_INVALID_PARAMETER; @@ -475,11 +508,17 @@ WB_Result WB_License_GetSecretString(const WB_License_Whitebox* whitebox, // The secret string can differ between keys, so we need to make sure that // the key id is actually a content key. - ContentKey content_key; - const WB_Result key_lookup_result = - FindKey(whitebox, key_id, key_id_size, &content_key); - if (key_lookup_result != WB_RESULT_OK) { - return key_lookup_result; + const ContentKey* content_key = FindKey(whitebox, key_id, key_id_size); + + if (content_key == nullptr) { + DVLOG(1) << "Key unavailable: could not find key."; + return WB_RESULT_KEY_UNAVAILABLE; + } + + if (!content_key->allow_masked_decrypt) { + DVLOG(1) << "Insufficient security level: key policy does not allow use " + "with MaskedDecrypt()."; + return WB_RESULT_INSUFFICIENT_SECURITY_LEVEL; } if (!widevine::MemCopy(kSecretStringPattern, kSecretStringPatternSize, @@ -489,6 +528,7 @@ WB_Result WB_License_GetSecretString(const WB_License_Whitebox* whitebox, return WB_RESULT_BUFFER_TOO_SMALL; } + *secret_string_size = kSecretStringPatternSize; return WB_RESULT_OK; } @@ -507,31 +547,31 @@ WB_Result WB_License_Decrypt(const WB_License_Whitebox* whitebox, return WB_RESULT_INVALID_PARAMETER; } + if (whitebox->content_keys.empty()) { + DVLOG(1) << "Invalid state: missing license."; + return WB_RESULT_INVALID_STATE; + } + if (key_id_size == 0) { DVLOG(1) << "Invalid parameter: array size 0."; return WB_RESULT_INVALID_PARAMETER; } - ContentKey content_key; - const WB_Result key_lookup_result = - FindKey(whitebox, key_id, key_id_size, &content_key); - if (key_lookup_result != WB_RESULT_OK) { - return key_lookup_result; + const ContentKey* content_key = FindKey(whitebox, key_id, key_id_size); + + if (content_key == nullptr) { + DVLOG(1) << "Key unavailable: could not find key."; + return WB_RESULT_KEY_UNAVAILABLE; } - // Check if decryption is allowed by the policy. - switch (content_key.level) { - case video_widevine::License_KeyContainer_SecurityLevel_SW_SECURE_CRYPTO: - break; - case video_widevine::License_KeyContainer_SecurityLevel_SW_SECURE_DECODE: - case video_widevine::License_KeyContainer_SecurityLevel_HW_SECURE_CRYPTO: - case video_widevine::License_KeyContainer_SecurityLevel_HW_SECURE_DECODE: - case video_widevine::License_KeyContainer_SecurityLevel_HW_SECURE_ALL: - return WB_RESULT_INSUFFICIENT_SECURITY_LEVEL; + if (!content_key->allow_decrypt) { + DVLOG(1) << "Insufficient security level: key policy does not allow use " + "with Decrypt()."; + return WB_RESULT_INSUFFICIENT_SECURITY_LEVEL; } // DecryptBuffer() will validate the remaining decryption parameters. - return DecryptBuffer(mode, content_key.key, input_data, input_data_size, iv, + return DecryptBuffer(mode, content_key->key, input_data, input_data_size, iv, iv_size, output_data, output_data_size); } @@ -545,32 +585,32 @@ WB_Result WB_License_MaskedDecrypt(const WB_License_Whitebox* whitebox, size_t iv_size, uint8_t* masked_output_data, size_t* masked_output_data_size) { - if (!whitebox || !key_id) { + if (!whitebox || !key_id || !masked_output_data || !masked_output_data_size) { DVLOG(1) << "Invalid parameter: null pointer."; return WB_RESULT_INVALID_PARAMETER; } + if (whitebox->content_keys.empty()) { + DVLOG(1) << "Invalid state: missing license."; + return WB_RESULT_INVALID_STATE; + } + if (key_id_size == 0) { DVLOG(1) << "Invalid parameter: array size 0."; return WB_RESULT_INVALID_PARAMETER; } - ContentKey content_key; - const WB_Result key_lookup_result = - FindKey(whitebox, key_id, key_id_size, &content_key); - if (key_lookup_result != WB_RESULT_OK) { - return key_lookup_result; + const ContentKey* content_key = FindKey(whitebox, key_id, key_id_size); + + if (content_key == nullptr) { + DVLOG(1) << "Key unavailable: could not find key."; + return WB_RESULT_KEY_UNAVAILABLE; } - // Check if decryption is allowed by the policy. - switch (content_key.level) { - case video_widevine::License_KeyContainer_SecurityLevel_SW_SECURE_CRYPTO: - case video_widevine::License_KeyContainer_SecurityLevel_SW_SECURE_DECODE: - break; - case video_widevine::License_KeyContainer_SecurityLevel_HW_SECURE_CRYPTO: - case video_widevine::License_KeyContainer_SecurityLevel_HW_SECURE_DECODE: - case video_widevine::License_KeyContainer_SecurityLevel_HW_SECURE_ALL: - return WB_RESULT_INSUFFICIENT_SECURITY_LEVEL; + if (!content_key->allow_masked_decrypt) { + DVLOG(1) << "Insufficient security level: key policy does not allow use " + "with MaskedDecrypt()."; + return WB_RESULT_INSUFFICIENT_SECURITY_LEVEL; } // DecryptBuffer() will validate all the parameters, so just make sure it is @@ -584,7 +624,7 @@ WB_Result WB_License_MaskedDecrypt(const WB_License_Whitebox* whitebox, // DecryptBuffer() will validate the remaining decryption parameters and set // |masked_output_data_size|. const WB_Result result = - DecryptBuffer(mode, content_key.key, input_data, input_data_size, iv, + DecryptBuffer(mode, content_key->key, input_data, input_data_size, iv, iv_size, output.data(), masked_output_data_size); if (result != WB_RESULT_OK) {