// Copyright 2020 Google LLC. All Rights Reserved. #include "api/license_whitebox.h" #include #include #include #include #include #include "base/logging.h" #include "cdm/protos/license_protocol.pb.h" #include "crypto_utils/aes_cbc_decryptor.h" #include "crypto_utils/aes_ctr_encryptor.h" #include "crypto_utils/crypto_util.h" #include "crypto_utils/rsa_key.h" #include "impl/reference/memory_util.h" #include "oemcrypto/odk/include/odk.h" #include "oemcrypto/odk/include/odk_structs.h" #include "oemcrypto/odk/src/odk_serialize.h" #include "oemcrypto/odk/src/serialization_base.h" #include "third_party/boringssl/src/include/openssl/aes.h" #include "third_party/boringssl/src/include/openssl/cmac.h" #include "third_party/boringssl/src/include/openssl/err.h" #include "third_party/boringssl/src/include/openssl/evp.h" #include "third_party/boringssl/src/include/openssl/hmac.h" #include "third_party/boringssl/src/include/openssl/rsa.h" #include "third_party/boringssl/src/include/openssl/sha.h" namespace { using AesCbcDecryptor = widevine::AesCbcDecryptor; using AesCtrDecryptor = widevine::AesCtrEncryptor; using KeyContainer = video_widevine::License_KeyContainer; using RsaPrivateKey = widevine::RsaPrivateKey; struct ContentKey { // 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; }; // Helper function to decrypt |encrypted| into |decrypted| using |decryptor| // and |iv|. Done as the protobuf and ODK code use std::string, AesCbcDecryptor // requires uint8_t* + size_t parameters. bool Decrypt(AesCbcDecryptor& decryptor, const std::string& iv, const std::string& encrypted, std::vector* decrypted) { DCHECK_GE(decrypted->size(), encrypted.size()); return decryptor.Decrypt(reinterpret_cast(iv.data()), iv.size(), reinterpret_cast(encrypted.data()), encrypted.size(), decrypted->data()); } bool IsOdkVersionSupported(uint16_t major_version, uint16_t minor_version) { // Only ODK v16.5 and later support the fields needed. constexpr uint16_t first_major_version_supported = 16; constexpr uint16_t first_minor_version_supported = 5; return (major_version > first_major_version_supported) || ((major_version == first_major_version_supported) && (minor_version >= first_minor_version_supported)); } // Helper function to extract the substring |item| from the provided |buffer|. std::string ExtractItem(const OEMCrypto_Substring& item, const std::string& buffer) { return buffer.substr(item.offset, item.length); } video_widevine::License_KeyContainer_SecurityLevel ExtractLevel( const std::string& key_control_block) { // The key control block is an 128 bit structure containing the following // fields. The fields are defined to be in big-endian byte order. // // Bytes 0..3: Verification. // Constant bytes “kctl”, “kc09”, “kc10”, “kc11”, ... “kc15”. // Bytes 4..7: Obsolete. // Bytes 8..11: Nonce. // Bytes 12..15: Control Bits // Bits 27..26: Security_Level (Only for L3 white-box implementations) // 0 = SW_SECURE_CRYPTO // 1 = SW_SECURE_DECODE // 2 = HW_SECURE_CRYPTO // 3 = HW_SECURE_DECODE or HW_SECURE_ALL // Limited checks to verify that this is proper key control block. // If not valid, assume it's the highest level. if ((key_control_block.size() != 16u) || (key_control_block[0] != 'k') || (key_control_block[1] != 'c')) { return video_widevine::License_KeyContainer_SecurityLevel_HW_SECURE_DECODE; } // Extract bits 26..27 from Control Bits. int security_level = (key_control_block[12] & 0x0C) >> 2; switch (security_level) { case 0: return video_widevine:: License_KeyContainer_SecurityLevel_SW_SECURE_CRYPTO; case 1: return video_widevine:: License_KeyContainer_SecurityLevel_SW_SECURE_DECODE; case 2: return video_widevine:: License_KeyContainer_SecurityLevel_HW_SECURE_CRYPTO; default: return video_widevine:: License_KeyContainer_SecurityLevel_HW_SECURE_DECODE; } } // Creates and returns a ContentKey based on the values provided. // |level| determines whether decrypt or masked_decrypt is allowed. // |is_hw_verified|, if set, overrides |level| so that both decrypt and // masked_decrypt is allowed. |Key| is the decryption key, and is only // returned in ContentKey if decrypt or masked_decrypt is allowed. // Otherwise |key| is dropped. ContentKey CreateContentKey( video_widevine::License_KeyContainer_SecurityLevel level, bool is_hw_verified, const std::vector& key) { ContentKey content_key; switch (level) { case video_widevine::License_KeyContainer_SecurityLevel_SW_SECURE_CRYPTO: content_key.allow_decrypt = true; content_key.allow_masked_decrypt = true; break; case video_widevine::License_KeyContainer_SecurityLevel_SW_SECURE_DECODE: content_key.allow_decrypt = false; content_key.allow_masked_decrypt = true; break; default: content_key.allow_decrypt = false; content_key.allow_masked_decrypt = false; break; } content_key.allow_decrypt |= is_hw_verified; content_key.allow_masked_decrypt |= is_hw_verified; // Unless we are going to use the key, we don't want to save this key as // it will only risk exposing it. We only have an entry for it so we can // handle errors correctly. if (content_key.allow_decrypt || content_key.allow_masked_decrypt) { content_key.key = key; } return content_key; } // This function uses 16 non-linear bijections that are applied to a byte. // This is "Example Masking Function 1" from the shared document // https://docs.google.com/document/d/1xWPwlFHyjT8YzWhY3TyaC02SQvclC_dkEpliGPOUk_o#heading=h.j64j2z3b9v99 uint8_t MaskingFunction1(uint8_t input) { const size_t x = 97 * input; const size_t y = x + 96; return y % 257; } // This function performs the reverse of MaskingFunction1(). uint8_t InverseMaskingFunction1(uint8_t input) { // Rather than compute the byte each time, generate a lookup table for all // 256 values the first time this is called. static bool initialized = false; static uint8_t mapping[256]; if (!initialized) { initialized = true; uint8_t byte = 0; do { mapping[MaskingFunction1(byte)] = byte; ++byte; } while (byte != 0); } return mapping[input]; } } // namespace // The white-box type can't be in the namespace as it is defined in the header. struct WB_License_Whitebox { // CDM key, used for license requests. std::unique_ptr key; // Keys used for license renewal. std::string server_signing_key; std::string client_signing_key; std::map content_keys; }; namespace { // For simplicity we use a basic pad but we use a different byte for each // position as we need to support abirtary indexes and we want to make sure that // a wrong index will actually trigger a failure. const uint8_t kSecretStringPattern[] = { 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, }; const size_t kSecretStringPatternSize = sizeof(kSecretStringPattern); 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); const std::string key_id(id, id + id_size); const auto found = whitebox->content_keys.find(key_id); return found == whitebox->content_keys.end() ? nullptr : &found->second; } WB_Result DecryptBuffer(WB_CipherMode mode, const std::vector& key, const uint8_t* input_data, size_t input_data_size, const uint8_t* iv, size_t iv_size, uint8_t* output_data, size_t* output_data_size) { if (!input_data || !iv || !output_data || !output_data_size) { DVLOG(1) << "Invalid parameter: null pointer."; return WB_RESULT_INVALID_PARAMETER; } if (input_data_size == 0) { DVLOG(1) << "Invalid parameter: array size 0."; return WB_RESULT_INVALID_PARAMETER; } // Data must be "block aligned" for CBC. CTR does not have this requirement. if (mode == WB_CIPHER_MODE_CBC && input_data_size % AES_BLOCK_SIZE != 0) { DVLOG(1) << "Invalid parameter: bad block size."; return WB_RESULT_INVALID_PARAMETER; } // We only support 16 byte IVs. CENC allows 8 byte, but it is expected that // the IV will be padded before entering the whitebox. if (iv_size != 16) { DVLOG(1) << "Invalid parameter: invalid iv size."; return WB_RESULT_INVALID_PARAMETER; } // There is no padding, so the output will be the same length as the input. if (*output_data_size < input_data_size) { DVLOG(1) << "Buffer too small: needs " << input_data_size << " but got " << *output_data_size << "."; *output_data_size = input_data_size; return WB_RESULT_BUFFER_TOO_SMALL; } // By this point, we have verified everything that need to be verified. // Decryption should just work. if (mode == WB_CIPHER_MODE_CBC) { AesCbcDecryptor decryptor; CHECK(decryptor.SetKey(key.data(), key.size())); *output_data_size = input_data_size; CHECK(decryptor.Decrypt(iv, iv_size, input_data, input_data_size, output_data)); } else if (mode == WB_CIPHER_MODE_CTR) { AesCtrDecryptor decryptor; CHECK(decryptor.SetKey(key.data(), key.size())); // Encrypt and Decrypt for CBC use the same interface. *output_data_size = input_data_size; CHECK(decryptor.Encrypt(iv, iv_size, input_data, input_data_size, output_data)); } else { DVLOG(1) << "Invalid parameter: invalid cipher mode."; return WB_RESULT_INVALID_PARAMETER; } return WB_RESULT_OK; } // We use "remote_attestation_verified" and "platform_verification_status" to // determine whether the platform is hardware verified. // // Each variable can be in one of three states. Each variable has a missing // value, a true value, and a false value. // // |----------------------------------------------------------| // | | RA N/A | RA VERIFIED | RA NOT VERIFIED | // |----------------------------------------------------------| // | VMP N/A | 0 | 1 | 0 | // | VMP HW_VERIFIED | 1 | 1 | 0 | // | VMP OTHER | 0 | 0 | 0 | // |----------------------------------------------------------| bool IsPlatformHardwareVerified(const video_widevine::License& license) { const bool has_ra = license.has_remote_attestation_verified(); const bool has_vmp = license.has_platform_verification_status(); if (!has_ra && !has_vmp) { return false; } // We trust "missing" more than "false". Now that we know we have some values, // default to "it's okay" and only override it if we have a new value from the // license. bool flags[2] = {true, true}; if (has_ra) { flags[0] = license.remote_attestation_verified(); } if (has_vmp) { flags[1] = license.platform_verification_status() == video_widevine::PLATFORM_HARDWARE_VERIFIED; } // If we were missing a value, that flag will still be true. But if we see any // false values, then we know something was found to be invalid. return flags[0] && flags[1]; } } // namespace WB_Result WB_License_Create(const uint8_t* whitebox_init_data, size_t whitebox_init_data_size, WB_License_Whitebox** whitebox) { if (!whitebox_init_data || !whitebox) { DVLOG(1) << "Invalid parameter: null pointer."; return WB_RESULT_INVALID_PARAMETER; } if (whitebox_init_data_size == 0) { DVLOG(1) << "Invalid parameter: array size 0."; return WB_RESULT_INVALID_PARAMETER; } // |whitebox_init_data| is simply the bytes of a PKCS #8 PrivateKeyInfo block // of a RSA 2048 bit key. std::unique_ptr key(RsaPrivateKey::Create(std::string( whitebox_init_data, whitebox_init_data + whitebox_init_data_size))); if (!key) { DVLOG(1) << "Invalid parameter: invalid init data."; return WB_RESULT_INVALID_PARAMETER; } // Should always be non-null on modern compilers // (https://isocpp.org/wiki/faq/freestore-mgmt). *whitebox = new WB_License_Whitebox(); (*whitebox)->key.swap(key); return WB_RESULT_OK; } void WB_License_Delete(WB_License_Whitebox* whitebox) { // Safe to delete nullptr (https://isocpp.org/wiki/faq/freestore-mgmt). delete whitebox; } WB_Result WB_License_SignLicenseRequest(const WB_License_Whitebox* whitebox, const uint8_t* license_request, size_t license_request_size, uint8_t* signature, size_t* signature_size) { if (!whitebox || !license_request || !signature || !signature_size) { DVLOG(1) << "Invalid parameter: null pointer."; return WB_RESULT_INVALID_PARAMETER; } if (license_request_size == 0) { DVLOG(1) << "Invalid parameter: array size 0."; return WB_RESULT_INVALID_PARAMETER; } std::string result; CHECK(whitebox->key->GenerateSignature( std::string(license_request, license_request + license_request_size), &result)); if (!widevine::MemCopy(result.data(), result.size(), signature, *signature_size)) { DVLOG(1) << "Buffer too small: signature needs " << result.size() << "."; *signature_size = result.size(); return WB_RESULT_BUFFER_TOO_SMALL; } *signature_size = result.size(); return WB_RESULT_OK; } WB_Result WB_License_ProcessLicenseResponse(WB_License_Whitebox* whitebox, const uint8_t* core_message, size_t core_message_size, const uint8_t* message, size_t message_size, const uint8_t* signature, size_t signature_size, const uint8_t* session_key, size_t session_key_size, 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; } // |core_message| is optional. If provided, both |core_message| and // |core_message_size| need to be set. if (core_message_size > 0 && !core_message) { DVLOG(1) << "Invalid parameter: core_message null pointer."; return WB_RESULT_INVALID_PARAMETER; } // 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( std::string(session_key, session_key + session_key_size), &decrypted_session_key)) { DVLOG(1) << "Invalid parameter: invalid session key."; return WB_RESULT_INVALID_PARAMETER; } std::string signing_key_material = widevine::crypto_util::DeriveKey( decrypted_session_key, widevine::crypto_util::kSigningKeyLabel, std::string(license_request, license_request + license_request_size), widevine::crypto_util::kSigningKeySizeBits * 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); std::string decryption_key = widevine::crypto_util::DeriveKey( decrypted_session_key, widevine::crypto_util::kWrappingKeyLabel, std::string(license_request, license_request + license_request_size), widevine::crypto_util::kWrappingKeySizeBits); if (decryption_key.empty()) { DVLOG(1) << "Failed to decrypt decryption key"; return WB_RESULT_INVALID_SIGNATURE; } const std::string message_str(message, message + message_size); std::string core_message_str; if (core_message_size > 0) { core_message_str.assign(reinterpret_cast(core_message), core_message_size); } const std::string combined_message_str = core_message_str + message_str; if (!widevine::crypto_util::VerifySignatureHmacSha256( server_signing_key, std::string(signature, signature + signature_size), combined_message_str)) { DVLOG(1) << "Failed to verify signed message."; return WB_RESULT_INVALID_SIGNATURE; } // If |core_message| is provided, parse it into |parsed_license| and validate // that it's an appropriate version. uint16_t odk_major_version = 0; uint16_t odk_minor_version = 0; ODK_ParsedLicense parsed_license; if (core_message_size > 0) { // Decode |core_message|. Message* msg = NULL; AllocateMessage(&msg, message_block); InitMessage(msg, reinterpret_cast( const_cast(combined_message_str.data())), combined_message_str.size()); // The core message is at the beginning of the buffer, and is the part to be // parsed. SetSize(msg, core_message_size); ODK_LicenseResponse license_response = {{{0}}, &parsed_license, {0}}; Unpack_ODK_LicenseResponse(msg, &license_response); odk_major_version = license_response.request.core_message.nonce_values.api_major_version; odk_minor_version = license_response.request.core_message.nonce_values.api_minor_version; if ((GetStatus(msg) != MESSAGE_STATUS_OK) || (license_response.request.core_message.message_type != ODK_License_Response_Type)) { DVLOG(1) << "Failed to validate core message."; return WB_RESULT_INVALID_SIGNATURE; } } AesCbcDecryptor decryptor; CHECK( decryptor.SetKey(reinterpret_cast(decryption_key.data()), decryption_key.size())); std::string server_renewal_key; std::string client_renewal_key; std::map content_keys; // Even if |core_message| is provided, only ODK v16.5 and later support the // fields needed. If an older API is used, ignore it and use the protobuf // as if |core_message| was not provided. if (IsOdkVersionSupported(odk_major_version, odk_minor_version)) { // Start by extracting the signing key. const std::string signing_key_encrypted = ExtractItem(parsed_license.enc_mac_keys, message_str); const std::string signing_key_iv = ExtractItem(parsed_license.enc_mac_keys_iv, message_str); if (!signing_key_encrypted.empty() && !signing_key_iv.empty()) { std::vector unwrapped_signing_key(signing_key_encrypted.size()); if (!Decrypt(decryptor, signing_key_iv, signing_key_encrypted, &unwrapped_signing_key)) { DVLOG(1) << "Invalid parameter: Invalid enc_mac_keys."; return WB_RESULT_INVALID_PARAMETER; } if (unwrapped_signing_key.size() < kSigningKeySizeBytes * 2) { DVLOG(1) << "Invalid parameter: Invalid signing key."; return WB_RESULT_INVALID_PARAMETER; } const std::string signing_key(unwrapped_signing_key.begin(), unwrapped_signing_key.end()); server_renewal_key = signing_key.substr(0, kSigningKeySizeBytes); client_renewal_key = signing_key.substr(kSigningKeySizeBytes, kSigningKeySizeBytes); } // Now extract all the content keys. for (size_t i = 0; i < parsed_license.key_array_length; ++i) { const OEMCrypto_KeyObject& key = parsed_license.key_array[i]; const std::string key_id = ExtractItem(key.key_id, message_str); const std::string iv = ExtractItem(key.key_data_iv, message_str); const std::string wrapped_key = ExtractItem(key.key_data, message_str); std::vector unwrapped_key(wrapped_key.size()); if (!Decrypt(decryptor, iv, wrapped_key, &unwrapped_key)) { DVLOG(1) << "Invalid parameter: Invalid key.key_data."; return WB_RESULT_INVALID_PARAMETER; } constexpr size_t kContentKeySizeBytes = 16; if (unwrapped_key.size() < kContentKeySizeBytes) { DVLOG(1) << "Invalid parameter: Invalid content key."; return WB_RESULT_INVALID_PARAMETER; } unwrapped_key.resize(kContentKeySizeBytes); // When the platform is hardware verified, all keys are unlocked and are // available to be used with either decrypt function. The license server // adjusts the level returned inside the key control block to handle // this. const std::string key_control_block = ExtractItem(key.key_control, message_str); content_keys[key_id] = CreateContentKey(ExtractLevel(key_control_block), /* is_hw_verified */ false, unwrapped_key); } } else { // Core message not provided or an old version, so extract the keys from // the protobuf. video_widevine::License license; if (!license.ParseFromArray(message, message_size)) { DVLOG(1) << "Invalid parameter: Invalid license."; return WB_RESULT_INVALID_PARAMETER; } // When the platform is hardware verified, all keys are unlocked and are // available to be used with either decrypt function. Use this flag to // overwrite the calculated values for the internal policies to enable // this behaviour. const bool is_verified = IsPlatformHardwareVerified(license); 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()); if (!Decrypt(decryptor, key.iv(), wrapped_key, &unwrapped_key)) { // The input has to be a specific length, so if it is not, it means that // something is wrong with the license. DVLOG(1) << "Invalid parameter: Invalid license."; return WB_RESULT_INVALID_PARAMETER; } if (key.type() == KeyContainer::SIGNING) { if (unwrapped_key.size() < kSigningKeySizeBytes * 2) { DVLOG(1) << "Invalid parameter: Invalid signing key."; return WB_RESULT_INVALID_PARAMETER; } const std::string signing_key(unwrapped_key.begin(), unwrapped_key.end()); 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; if (unwrapped_key.size() < kContentKeySizeBytes) { DVLOG(1) << "Invalid parameter: Invalid content key."; return WB_RESULT_INVALID_PARAMETER; } unwrapped_key.resize(kContentKeySizeBytes); content_keys[key.id()] = CreateContentKey(key.level(), is_verified, unwrapped_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; } WB_Result WB_License_SignRenewalRequest(const WB_License_Whitebox* whitebox, const uint8_t* message, size_t message_size, uint8_t* signature, size_t* signature_size) { if (!whitebox || !message || !signature || !signature_size) { DVLOG(1) << "Invalid parameter: null pointer."; return WB_RESULT_INVALID_PARAMETER; } if (message_size == 0) { DVLOG(1) << "Invalid parameter: array size 0."; return WB_RESULT_INVALID_PARAMETER; } if (whitebox->content_keys.empty()) { DVLOG(1) << "Invalid state: missing license."; return WB_RESULT_INVALID_STATE; } if (whitebox->client_signing_key.empty()) { DVLOG(1) << "Invalid state: license does not support renewals."; return WB_RESULT_INVALID_STATE; } const std::string computed_signature = widevine::crypto_util::CreateSignatureHmacSha256( whitebox->client_signing_key, std::string(message, message + message_size)); if (!widevine::MemCopy(computed_signature.data(), computed_signature.size(), signature, *signature_size)) { DVLOG(1) << "Buffer too small: signature needs " << computed_signature.size() << "."; *signature_size = computed_signature.size(); return WB_RESULT_BUFFER_TOO_SMALL; } *signature_size = computed_signature.size(); return WB_RESULT_OK; } WB_Result WB_License_VerifyRenewalResponse(const WB_License_Whitebox* whitebox, const uint8_t* message, size_t message_size, const uint8_t* signature, size_t signature_size) { if (!whitebox || !message || !signature) { DVLOG(1) << "Invalid parameter: null pointer"; return WB_RESULT_INVALID_PARAMETER; } if (message_size == 0) { DVLOG(1) << "Invalid parameter: array size 0"; return WB_RESULT_INVALID_PARAMETER; } if (whitebox->content_keys.empty()) { DVLOG(1) << "Invalid state: missing license."; return WB_RESULT_INVALID_STATE; } if (whitebox->server_signing_key.empty()) { DVLOG(1) << "Invalid state: license does not support renewals."; return WB_RESULT_INVALID_STATE; } const std::string computed_signature = widevine::crypto_util::CreateSignatureHmacSha256( whitebox->server_signing_key, std::string(message, message + message_size)); if (signature_size != computed_signature.size()) { DVLOG(1) << "Invalid parameters: invalid signature size."; return WB_RESULT_INVALID_PARAMETER; } if (computed_signature != std::string(signature, signature + signature_size)) { DVLOG(1) << "Data verification error: signatures do not match."; return WB_RESULT_INVALID_SIGNATURE; } return WB_RESULT_OK; } WB_Result WB_License_GetSecretString(const WB_License_Whitebox* whitebox, WB_CipherMode mode, const uint8_t* key_id, size_t key_id_size, uint8_t* secret_string, size_t* secret_string_size) { if (!whitebox || !key_id || !secret_string || !secret_string_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 (mode != WB_CIPHER_MODE_CTR && mode != WB_CIPHER_MODE_CBC) { DVLOG(1) << "Invalid parameter: invalid cipher mode."; return WB_RESULT_INVALID_PARAMETER; } if (key_id_size == 0) { DVLOG(1) << "Invalid parameter: array size 0."; return WB_RESULT_INVALID_PARAMETER; } // The secret string can differ between keys, so we need to make sure that // the key id is actually a content key. 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, secret_string, *secret_string_size)) { DVLOG(1) << "Buffer too small: needs " << kSecretStringPatternSize << "."; *secret_string_size = kSecretStringPatternSize; return WB_RESULT_BUFFER_TOO_SMALL; } *secret_string_size = kSecretStringPatternSize; return WB_RESULT_OK; } WB_Result WB_License_Decrypt(const WB_License_Whitebox* whitebox, WB_CipherMode mode, const uint8_t* key_id, size_t key_id_size, const uint8_t* input_data, size_t input_data_size, const uint8_t* iv, size_t iv_size, uint8_t* output_data, size_t* output_data_size) { if (!whitebox || !key_id) { 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; } 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_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, iv_size, output_data, output_data_size); } WB_Result WB_License_MaskedDecrypt(const WB_License_Whitebox* whitebox, WB_CipherMode mode, const uint8_t* key_id, size_t key_id_size, const uint8_t* input_data, size_t input_data_size, const uint8_t* iv, size_t iv_size, uint8_t* masked_output_data, size_t* masked_output_data_size) { 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; } 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; } // DecryptBuffer() will validate all the parameters, so just make sure it is // safe to resize this and let the normal validation process handle anything // wrong with the output size. std::vector output; if (masked_output_data_size) { output.resize(*masked_output_data_size); } // 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, iv_size, output.data(), masked_output_data_size); if (result != WB_RESULT_OK) { return result; } // Now apply the masking function to the data. This logic must be mirrored in // Unmask(). const uint8_t* mask = kSecretStringPattern; const size_t mask_size = kSecretStringPatternSize; for (size_t i = 0; i < output.size(); ++i) { masked_output_data[i] = InverseMaskingFunction1(output[i] ^ mask[i % mask_size]); } return WB_RESULT_OK; } void WB_License_Unmask(const uint8_t* masked_data, size_t offset, size_t size, const uint8_t* secret_string, size_t secret_string_size, uint8_t* unmasked_data) { // No return code, so only check if parameters are valid. DCHECK(masked_data); DCHECK(secret_string); DCHECK(unmasked_data); for (size_t local_i = 0; local_i < size; local_i++) { const size_t global_i = offset + local_i; const uint8_t mask = secret_string[global_i % secret_string_size]; unmasked_data[local_i] = MaskingFunction1(masked_data[global_i]) ^ mask; } }