Initial Code Drop
This is the initial code drop of the reference implementation and test cases for the Widevine Whitebox API. In this drop, the full reference implementation for the AEAD white-box is provided and all test cases verifying the top-level behave have are enabled. Since the implementations can vary so much the testing is mostly left to verifying the return codes for specific parameter conditions. A full reference implementation for the license white-box is provided, however not all tests are implemented or enabled. A number of tests have been disabled as they required a loaded license and test licenses are still being worked on. The two license white-box API functions that are the further from competition are ProcessLicenseResponse() and MaskedDecryt(). ProcessLicenseResponse() is still being worked on and MaskedDecrypt() is waiting on Decrypt() to be fully functional. Most tests focus on verifying return code for specific parameter conditions, but as test licenses are created, tests looking to test the internal behaviour of license management will be added to ProcessLicenseResponse(), Decrypt(), and MaskedDecrypt().
This commit is contained in:
89
impl/reference/BUILD
Normal file
89
impl/reference/BUILD
Normal file
@@ -0,0 +1,89 @@
|
||||
# Copyright 2020 Google LLC. All Rights Reserved.
|
||||
|
||||
package(default_visibility = [
|
||||
"//visibility:private",
|
||||
])
|
||||
|
||||
cc_library(
|
||||
name = "aead_whitebox",
|
||||
srcs = [
|
||||
"aead_whitebox_impl.cc",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
":memory_util",
|
||||
":string_view_util",
|
||||
"//api:aead_whitebox",
|
||||
"//api:result",
|
||||
"//chromium_deps/base",
|
||||
"//chromium_deps/third_party/boringssl",
|
||||
"//crypto_utils:crypto_util",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "license_whitebox",
|
||||
srcs = [
|
||||
"license_whitebox_impl.cc",
|
||||
],
|
||||
deps = [
|
||||
":memory_util",
|
||||
":string_view_util",
|
||||
"//api:license_whitebox",
|
||||
"//api:result",
|
||||
"//chromium_deps/base",
|
||||
"//chromium_deps/cdm/keys:dev_certs",
|
||||
"//chromium_deps/cdm/protos:license_protocol_proto",
|
||||
"//crypto_utils:aes_cbc_decryptor",
|
||||
"//crypto_utils:aes_ctr_encryptor",
|
||||
"//crypto_utils:crypto_util",
|
||||
"//crypto_utils:rsa_key",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "test_data",
|
||||
testonly = True,
|
||||
srcs = [
|
||||
"test_data.cc",
|
||||
],
|
||||
deps = [
|
||||
"//api:test_data",
|
||||
"//crypto_utils:rsa_test_keys",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "aead_whitebox_test",
|
||||
size = "small",
|
||||
deps = [
|
||||
":aead_whitebox",
|
||||
":test_data",
|
||||
"//api:aead_whitebox_test",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "license_whitebox_test",
|
||||
size = "small",
|
||||
deps = [
|
||||
":license_whitebox",
|
||||
":test_data",
|
||||
"//api:license_whitebox_test",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "string_view_util",
|
||||
srcs = ["string_view_util.cc"],
|
||||
hdrs = ["string_view_util.h"],
|
||||
deps = [
|
||||
"@abseil_repo//absl/strings",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "memory_util",
|
||||
srcs = ["memory_util.cc"],
|
||||
hdrs = ["memory_util.h"],
|
||||
)
|
||||
216
impl/reference/aead_whitebox_impl.cc
Normal file
216
impl/reference/aead_whitebox_impl.cc
Normal file
@@ -0,0 +1,216 @@
|
||||
// Copyright 2020 Google LLC. All Rights Reserved.
|
||||
#include "api/aead_whitebox.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "base/logging.h"
|
||||
#include "base/strings/string_number_conversions.h"
|
||||
#include "crypto_utils/crypto_util.h"
|
||||
#include "impl/reference/memory_util.h"
|
||||
#include "impl/reference/string_view_util.h"
|
||||
#include "third_party/boringssl/src/include/openssl/aead.h"
|
||||
#include "third_party/boringssl/src/include/openssl/rand.h"
|
||||
|
||||
struct WB_Aead_Whitebox {
|
||||
const EVP_AEAD* algorithm;
|
||||
size_t nonce_size;
|
||||
|
||||
EVP_AEAD_CTX context;
|
||||
};
|
||||
|
||||
namespace {
|
||||
// EVP_AEAD_CTX_init(), EVP_AEAD_CTX_open(), and EVP_AEAD_CTX_seal() return 1
|
||||
// for success and 0 for failure.
|
||||
const int kAeadSuccess = 1;
|
||||
|
||||
void InitAeadMetadata(WB_Aead_Whitebox* whitebox) {
|
||||
const EVP_AEAD* algorithm = EVP_aead_aes_128_gcm_siv();
|
||||
|
||||
whitebox->algorithm = algorithm;
|
||||
whitebox->nonce_size = EVP_AEAD_nonce_length(algorithm);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> DeriveKey(const WB_Aead_Whitebox* whitebox,
|
||||
const uint8_t* init_data,
|
||||
size_t init_data_size,
|
||||
const uint8_t* context,
|
||||
size_t context_size) {
|
||||
// To derive a key, we need a label to introduce some entropy. The label only
|
||||
// needs to be some fixed, arbitrary non-trivial string.
|
||||
constexpr char kLabel[] = "Covfefe";
|
||||
|
||||
const auto init_data_view = widevine::AsStringView(init_data, init_data_size);
|
||||
const auto context_view = widevine::AsStringView(context, context_size);
|
||||
|
||||
// While we get the key size in bytes, DeriveKey() needs the key size in bits.
|
||||
const auto derived_key = widevine::crypto_util::DeriveKey(
|
||||
init_data_view, kLabel, context_view,
|
||||
EVP_AEAD_key_length(whitebox->algorithm) * 8);
|
||||
|
||||
return std::vector<uint8_t>(derived_key.begin(), derived_key.end());
|
||||
}
|
||||
} // namespace
|
||||
|
||||
WB_Result WB_Aead_Create(const uint8_t* whitebox_init_data,
|
||||
size_t whitebox_init_data_size,
|
||||
const uint8_t* context,
|
||||
size_t context_size,
|
||||
::WB_Aead_Whitebox** whitebox) {
|
||||
if (!whitebox_init_data || !context || !whitebox) {
|
||||
DVLOG(1) << "Invalid parameter: null pointer.";
|
||||
return WB_RESULT_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
if (whitebox_init_data_size == 0 || context_size == 0) {
|
||||
DVLOG(1) << "Invalid parameter: array size 0.";
|
||||
return WB_RESULT_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
// Use a unique pointer internally so that we can return early and
|
||||
// automatically release the pointer. Should always be non-null on modern
|
||||
// compilers (https://isocpp.org/wiki/faq/freestore-mgmt).
|
||||
std::unique_ptr<WB_Aead_Whitebox> aead_whitebox(new WB_Aead_Whitebox());
|
||||
InitAeadMetadata(aead_whitebox.get());
|
||||
|
||||
// Rather than using a fixed key, shared across all instances, derive a key
|
||||
// for each instance using the provided context. This will allow each instance
|
||||
// to have its own unique key (assuming they provide a unique context) while
|
||||
// still allowing a specific key to be recreated when needed (e.g.
|
||||
// device-locked key).
|
||||
const std::vector<uint8_t> key =
|
||||
DeriveKey(aead_whitebox.get(), whitebox_init_data,
|
||||
whitebox_init_data_size, context, context_size);
|
||||
|
||||
const int result = EVP_AEAD_CTX_init(
|
||||
&aead_whitebox->context, aead_whitebox->algorithm, key.data(), key.size(),
|
||||
EVP_AEAD_DEFAULT_TAG_LENGTH, nullptr);
|
||||
|
||||
if (result != kAeadSuccess) {
|
||||
DVLOG(1) << "Invalid parameter: invalid init data.";
|
||||
return WB_RESULT_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
// release() will release ownership of the pointer and return the unmanaged
|
||||
// raw pointer.
|
||||
*whitebox = aead_whitebox.release();
|
||||
return WB_RESULT_OK;
|
||||
}
|
||||
|
||||
void WB_Aead_Delete(WB_Aead_Whitebox* whitebox) {
|
||||
if (whitebox != nullptr) {
|
||||
EVP_AEAD_CTX_cleanup(&whitebox->context);
|
||||
}
|
||||
|
||||
// Safe to delete nullptr (https://isocpp.org/wiki/faq/freestore-mgmt).
|
||||
delete whitebox;
|
||||
}
|
||||
WB_Result WB_Aead_Encrypt(const WB_Aead_Whitebox* whitebox,
|
||||
const uint8_t* input_data,
|
||||
size_t input_data_size,
|
||||
uint8_t* output_data,
|
||||
size_t* output_data_size) {
|
||||
if (!whitebox || !input_data || !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;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> nonce(whitebox->nonce_size);
|
||||
RAND_bytes(nonce.data(), nonce.size());
|
||||
|
||||
std::vector<uint8_t> output(input_data_size +
|
||||
EVP_AEAD_max_overhead(whitebox->algorithm));
|
||||
size_t sealed_size;
|
||||
CHECK_EQ(EVP_AEAD_CTX_seal(&whitebox->context, output.data(), &sealed_size,
|
||||
output.size(), nonce.data(), nonce.size(),
|
||||
input_data, input_data_size, nullptr, 0),
|
||||
kAeadSuccess);
|
||||
output.resize(sealed_size);
|
||||
|
||||
// At this point, |output| will have the encrypted data and authentication
|
||||
// tag, but in order to decrypt later, we will need the nonce, so append the
|
||||
// nonce to the output.
|
||||
output.insert(output.end(), nonce.begin(), nonce.end());
|
||||
|
||||
if (!widevine::MemCopy(output.data(), output.size(), output_data,
|
||||
*output_data_size)) {
|
||||
DVLOG(1) << "Buffer too small: output needs " << output.size() << ".";
|
||||
*output_data_size = output.size();
|
||||
return WB_RESULT_BUFFER_TOO_SMALL;
|
||||
}
|
||||
|
||||
*output_data_size = output.size();
|
||||
return WB_RESULT_OK;
|
||||
}
|
||||
|
||||
WB_Result WB_Aead_Decrypt(const WB_Aead_Whitebox* whitebox,
|
||||
const uint8_t* input_data,
|
||||
size_t input_data_size,
|
||||
uint8_t* output_data,
|
||||
size_t* output_data_size) {
|
||||
if (!whitebox || !input_data || !output_data || !output_data_size) {
|
||||
DVLOG(1) << "Invalid parameter: null pointer.";
|
||||
return WB_RESULT_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
// If we were to encrypt nothing, we would still get the nonce, so the input
|
||||
// must at least be large enough to contain the nonce.
|
||||
if (input_data_size <= whitebox->nonce_size) {
|
||||
DVLOG(1) << "Invalid parameter: invalid input size.";
|
||||
return WB_RESULT_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
// The input follows the structure:
|
||||
// |--------input---------|
|
||||
// |---payload---||-nonce-|
|
||||
//
|
||||
// The payload is the combination of authentication tag and ciphertext
|
||||
// created by our previous call to EVP_AEAD_CTX_seal().
|
||||
//
|
||||
// To use EVP_AEAD_CTX_open(), we need to separate the payload and nonce as
|
||||
// they must be passed to EVP_AEAD_CTX_open() separately. The result of
|
||||
// calling EVP_AEAD_CTX_open() will be the plaintext, which will be smaller
|
||||
// than the payload as the payload included the ciphertext and authentication
|
||||
// tag.
|
||||
|
||||
const uint8_t* payload_start = input_data;
|
||||
const size_t payload_size = input_data_size - whitebox->nonce_size;
|
||||
|
||||
const uint8_t* nonce_start = payload_start + payload_size;
|
||||
const size_t nonce_size = whitebox->nonce_size;
|
||||
|
||||
// Remember, the plaintext will be smaller than the payload, but we are going
|
||||
// to start with the payload size as it is our best estimate and will scale it
|
||||
// down when we have the final size.
|
||||
std::vector<uint8_t> plaintext(payload_size);
|
||||
size_t plaintext_size;
|
||||
|
||||
const int result = EVP_AEAD_CTX_open(
|
||||
&whitebox->context, plaintext.data(), &plaintext_size, plaintext.size(),
|
||||
nonce_start, nonce_size, payload_start, payload_size, nullptr, 0);
|
||||
|
||||
if (result != kAeadSuccess) {
|
||||
DVLOG(1) << "Data verification error: failed to verify input data.";
|
||||
return WB_RESULT_DATA_VERIFICATION_ERROR;
|
||||
}
|
||||
|
||||
// Know that EVP_AEAD_CTX_open() has opened the payload, we know the actual
|
||||
// plaintext size and can now shrink the vector to the correct size.
|
||||
plaintext.resize(plaintext_size);
|
||||
|
||||
if (!widevine::MemCopy(plaintext.data(), plaintext.size(), output_data,
|
||||
*output_data_size)) {
|
||||
DVLOG(1) << "Buffer too small: output needs " << plaintext.size() << ".";
|
||||
*output_data_size = plaintext.size();
|
||||
return WB_RESULT_BUFFER_TOO_SMALL;
|
||||
}
|
||||
|
||||
*output_data_size = plaintext.size();
|
||||
return WB_RESULT_OK;
|
||||
}
|
||||
614
impl/reference/license_whitebox_impl.cc
Normal file
614
impl/reference/license_whitebox_impl.cc
Normal file
@@ -0,0 +1,614 @@
|
||||
// Copyright 2020 Google LLC. All Rights Reserved.
|
||||
|
||||
#include "api/license_whitebox.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "base/logging.h"
|
||||
#include "base/strings/string_number_conversions.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 "impl/reference/string_view_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 KeyContainer = video_widevine::License_KeyContainer;
|
||||
using RsaPrivateKey = widevine::RsaPrivateKey;
|
||||
using AesCbcDecryptor = widevine::AesCbcDecryptor;
|
||||
using AesCtrDecryptor = widevine::AesCtrEncryptor;
|
||||
using SecurityLevel = video_widevine::License_KeyContainer_SecurityLevel;
|
||||
|
||||
struct ContentKey {
|
||||
// Minimum security level that this key requires in order to be used.
|
||||
SecurityLevel level;
|
||||
|
||||
// Key used to decrypt content.
|
||||
std::vector<uint8_t> 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<RsaPrivateKey> key;
|
||||
|
||||
// Keys used for license renewal.
|
||||
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<std::string> all_key_ids;
|
||||
std::map<std::string, ContentKey> 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.");
|
||||
|
||||
std::string AsString(const uint8_t* buffer, size_t buffer_size) {
|
||||
return std::string(reinterpret_cast<const char*>(buffer), buffer_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];
|
||||
}
|
||||
}
|
||||
|
||||
WB_Result FindKey(const WB_License_Whitebox* whitebox,
|
||||
const uint8_t* id,
|
||||
size_t id_size,
|
||||
ContentKey* key) {
|
||||
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;
|
||||
}
|
||||
|
||||
WB_Result DecryptBuffer(WB_CipherMode mode,
|
||||
const std::vector<uint8_t>& 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;
|
||||
}
|
||||
} // 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<RsaPrivateKey> key(RsaPrivateKey::Create(
|
||||
AsString(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(
|
||||
AsString(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;
|
||||
}
|
||||
|
||||
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) {
|
||||
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) {
|
||||
DVLOG(1) << "Invalid parameter: array size 0.";
|
||||
return WB_RESULT_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
std::string decrypted_session_key;
|
||||
if (!whitebox->key->Decrypt(AsString(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,
|
||||
widevine::AsStringView(license_request, license_request_size),
|
||||
widevine::crypto_util::kSigningKeySizeBits * 2);
|
||||
|
||||
if (signing_key_material.size() <
|
||||
widevine::crypto_util::kSigningKeySizeBytes * 2) {
|
||||
DVLOG(1) << "Invalid parameter: invalid session key size.";
|
||||
return WB_RESULT_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
if (!widevine::crypto_util::VerifySignatureHmacSha256(
|
||||
signing_key_material,
|
||||
widevine::AsStringView(signature, signature_size),
|
||||
widevine::AsStringView(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,
|
||||
widevine::AsStringView(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<const uint8_t*>(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;
|
||||
}
|
||||
|
||||
CHECK(!license.key().empty());
|
||||
for (const auto& key : license.key()) {
|
||||
const std::string wrapped_key = key.key();
|
||||
std::vector<uint8_t> unwrapped_key(wrapped_key.size());
|
||||
|
||||
if (!decryptor.Decrypt(reinterpret_cast<const uint8_t*>(key.iv().data()),
|
||||
key.iv().size(),
|
||||
reinterpret_cast<const uint8_t*>(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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
unwrapped_key.resize(kContentKeySizeBytes);
|
||||
whitebox->content_keys[key.id()] = {key.level(),
|
||||
std::move(unwrapped_key)};
|
||||
break;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// License request must have been processed at least once before calling this
|
||||
// function.
|
||||
if (whitebox->client_signing_key.empty()) {
|
||||
DVLOG(1) << __func__ << " called before license received.";
|
||||
return WB_RESULT_INVALID_STATE;
|
||||
}
|
||||
|
||||
const std::string computed_signature =
|
||||
widevine::crypto_util::CreateSignatureHmacSha256(
|
||||
whitebox->client_signing_key,
|
||||
widevine::AsStringView(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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// License request must have been processed at least once before calling this
|
||||
// function.
|
||||
if (whitebox->server_signing_key.empty()) {
|
||||
DVLOG(1) << __func__ << " called before license received.";
|
||||
return WB_RESULT_INVALID_STATE;
|
||||
}
|
||||
|
||||
const std::string computed_signature =
|
||||
widevine::crypto_util::CreateSignatureHmacSha256(
|
||||
whitebox->server_signing_key,
|
||||
widevine::AsStringView(message, message_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_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 (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.
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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 (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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 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) {
|
||||
DVLOG(1) << "Invalid parameter: null pointer.";
|
||||
return WB_RESULT_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 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<uint8_t> 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);
|
||||
}
|
||||
20
impl/reference/memory_util.cc
Normal file
20
impl/reference/memory_util.cc
Normal file
@@ -0,0 +1,20 @@
|
||||
// Copyright 2020 Google LLC. All Rights Reserved.
|
||||
|
||||
#include "impl/reference/memory_util.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
namespace widevine {
|
||||
|
||||
bool MemCopy(const void* src, size_t src_size, void* dest, size_t dest_size) {
|
||||
if (dest_size < src_size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If we could use memcpy_s(), we would not need this function, but memcpy_s()
|
||||
// is not available on some platforms (e.g. mac).
|
||||
memcpy(dest, src, src_size);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace widevine
|
||||
28
impl/reference/memory_util.h
Normal file
28
impl/reference/memory_util.h
Normal file
@@ -0,0 +1,28 @@
|
||||
// Copyright 2020 Google LLC. All Rights Reserved.
|
||||
|
||||
#ifndef WHITEBOX_IMPL_REFERENCE_MEMORY_UTIL_H_
|
||||
#define WHITEBOX_IMPL_REFERENCE_MEMORY_UTIL_H_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace widevine {
|
||||
|
||||
// Copied from the documentation for memcpy() with minor modifications:
|
||||
//
|
||||
// Copies |src_size| bytes from |src| to |dest| if |dest_size| is greater or
|
||||
// equal to |src_size|.
|
||||
//
|
||||
// The underlying type of the objects pointed to by |src| and |dest| are
|
||||
// irrelevant for this function; the result is a binary copy of the data.
|
||||
//
|
||||
// To avoid overflows, |src_size| is compared against |dest_size|. If there
|
||||
// won't be enough room, the copy is not attempted and false is returned.
|
||||
//
|
||||
// |src| and |dest| should not overlap. For overlapping memory blocks,
|
||||
// memmove is a safer approach).
|
||||
bool MemCopy(const void* src, size_t src_size, void* dest, size_t dest_size);
|
||||
|
||||
} // namespace widevine
|
||||
|
||||
#endif // WHITEBOX_IMPL_REFERENCE_MEMORY_UTIL_H_
|
||||
11
impl/reference/string_view_util.cc
Normal file
11
impl/reference/string_view_util.cc
Normal file
@@ -0,0 +1,11 @@
|
||||
// Copyright 2020 Google LLC. All Rights Reserved.
|
||||
|
||||
#include "impl/reference/string_view_util.h"
|
||||
|
||||
namespace widevine {
|
||||
|
||||
absl::string_view AsStringView(const uint8_t* buffer, size_t buffer_size) {
|
||||
return absl::string_view(reinterpret_cast<const char*>(buffer), buffer_size);
|
||||
}
|
||||
|
||||
} // namespace widevine
|
||||
17
impl/reference/string_view_util.h
Normal file
17
impl/reference/string_view_util.h
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright 2020 Google LLC. All Rights Reserved.
|
||||
|
||||
#ifndef WHITEBOX_IMPL_REFERENCE_STRING_VIEW_UTIL_H_
|
||||
#define WHITEBOX_IMPL_REFERENCE_STRING_VIEW_UTIL_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include "absl/strings/string_view.h"
|
||||
|
||||
namespace widevine {
|
||||
|
||||
// Create a string view out of a uint8_t array. String view won't create a copy
|
||||
// copy of the data.
|
||||
absl::string_view AsStringView(const uint8_t* buffer, size_t buffer_size);
|
||||
|
||||
} // namespace widevine
|
||||
|
||||
#endif // WHITEBOX_IMPL_REFERENCE_STRING_VIEW_UTIL_H_
|
||||
42
impl/reference/test_data.cc
Normal file
42
impl/reference/test_data.cc
Normal file
@@ -0,0 +1,42 @@
|
||||
// Copyright 2020 Google LLC. All Rights Reserved.
|
||||
|
||||
#include "api/test_data.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "crypto_utils/rsa_test_keys.h"
|
||||
|
||||
std::vector<uint8_t> GetValidAeadInitData() {
|
||||
// Valid init data for our AEAD implementation is any AES key, so it just
|
||||
// needs to be 16 bytes.
|
||||
return {
|
||||
0x00, 0x00, 0x00, 0x0, 0x00, 0x00, 0x00, 0x0,
|
||||
0x00, 0x00, 0x00, 0x0, 0x00, 0x00, 0x00, 0x0,
|
||||
};
|
||||
}
|
||||
|
||||
std::vector<uint8_t> GetInvalidAeadInitData() {
|
||||
// Valid init data would be any 16 bytes. To avoid returning a length of
|
||||
// zero, just return one byte, that way we are still returning "some data".
|
||||
return {0x00};
|
||||
}
|
||||
|
||||
std::vector<uint8_t> GetLicenseInitData() {
|
||||
// For the OpenSSL implementation |init_data| is simply a RSA 2048-bit
|
||||
// private key. Matching public key is public_test_key_2_2048_bits().
|
||||
widevine::RsaTestKeys key_generator;
|
||||
std::string init_data = key_generator.private_test_key_2_2048_bits();
|
||||
return std::vector<uint8_t>(init_data.begin(), init_data.end());
|
||||
}
|
||||
|
||||
std::vector<uint8_t> GetMatchingLicensePublicKey() {
|
||||
widevine::RsaTestKeys key_generator;
|
||||
std::string init_data = key_generator.public_test_key_2_2048_bits();
|
||||
return std::vector<uint8_t>(init_data.begin(), init_data.end());
|
||||
}
|
||||
|
||||
std::vector<uint8_t> GetInvalidLicenseInitData() {
|
||||
// For the OpenSSL implementation |init_data| is a private key. So a random
|
||||
// collection of bytes should not be accepted.
|
||||
return {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
|
||||
}
|
||||
Reference in New Issue
Block a user