// 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 "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; }; } // 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 { // Secret string value. For simplicity the pattern will just be 0xAAAAAAAA which // is xor'd to the buffer, and removed by xor'ing it a second time. const uint8_t kSecretStringPattern[] = { 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, }; const size_t kSecretStringPatternSize = sizeof(kSecretStringPattern); static_assert(kSecretStringPatternSize == AES_BLOCK_SIZE, "Secret string must be AES_BLOCK_SIZE."); void ApplyPattern(const uint8_t* input_buffer, size_t input_buffer_size, const uint8_t* pattern, size_t pattern_size, uint8_t* output_buffer) { DCHECK(input_buffer); DCHECK(pattern); DCHECK(output_buffer); DCHECK_GT(pattern_size, 0u); DCHECK_EQ(input_buffer_size % pattern_size, 0u); for (size_t i = 0; i < input_buffer_size; ++i) { output_buffer[i] = input_buffer[i] ^ pattern[i % pattern_size]; } } 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; DCHECK(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* 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; } // 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); if (!widevine::crypto_util::VerifySignatureHmacSha256( server_signing_key, std::string(signature, signature + signature_size), std::string(message, message + message_size))) { DVLOG(1) << "Failed to verify signed message."; return WB_RESULT_INVALID_SIGNATURE; } 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; } AesCbcDecryptor decryptor; CHECK( decryptor.SetKey(reinterpret_cast(decryption_key.data()), decryption_key.size())); video_widevine::License license; if (!license.ParseFromArray(message, message_size)) { DVLOG(1) << "Invalid parameter: Invalid license."; return WB_RESULT_INVALID_PARAMETER; } std::string server_renewal_key; std::string client_renewal_key; std::map content_keys; // 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 default values internal our 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 (!decryptor.Decrypt(reinterpret_cast(key.iv().data()), key.iv().size(), reinterpret_cast(wrapped_key.data()), wrapped_key.size(), unwrapped_key.data())) { // 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); 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; 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_verified; content_key.allow_masked_decrypt |= is_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 = std::move(unwrapped_key); } 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; } 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; } // Trivial implementation that simply takes the decrypted output and XORs it // with a fixed pattern. |output|'s size is based on |masked_output_data| so // we shouldn't need to worry about overflow. ApplyPattern(output.data(), output.size(), kSecretStringPattern, kSecretStringPatternSize, masked_output_data); return WB_RESULT_OK; } void WB_License_Unmask(const uint8_t* secret_string, size_t secret_string_size, uint8_t* buffer, size_t buffer_size) { // No return code, so only check if parameters are valid. DCHECK(secret_string); DCHECK(secret_string_size); DCHECK(buffer); DCHECK(buffer_size); ApplyPattern(buffer, buffer_size, secret_string, secret_string_size, buffer); }