Source release 18.7.0
This commit is contained in:
37
CHANGELOG.md
37
CHANGELOG.md
@@ -2,16 +2,47 @@
|
|||||||
|
|
||||||
[TOC]
|
[TOC]
|
||||||
|
|
||||||
|
## 18.7.0 (2024-09-04)
|
||||||
|
|
||||||
|
This is a minor release with bug fixes and test improvements. However, because
|
||||||
|
of improvements to the BCC Factory Upload Tool, we recommend that all partners
|
||||||
|
who use this tool upgrade to version 18.7.0.
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- Added workaround for OEMCrypto implementations with slightly corrupted build
|
||||||
|
information
|
||||||
|
- The BCC Factory Upload Tool supports new command-line options for dry runs,
|
||||||
|
batch checks, version-checking, and verbose output.
|
||||||
|
|
||||||
|
### Tests
|
||||||
|
|
||||||
|
- Added new tests to better validate the behavior of
|
||||||
|
`OEMCrypto_BuildInformation()`
|
||||||
|
- Verifies output length is set correctly
|
||||||
|
- Verifies content is ASCII JSON without trailing null bytes
|
||||||
|
- Verifies documented JSON fields: required fields are present, and optional
|
||||||
|
and required fields are the correct JSON types
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- Fixed decrypt failures on devices with low TEE memory caused by sending an
|
||||||
|
output buffer to decrypt that was much larger than necessary
|
||||||
|
- Several BCC Factory Upload Tool fixes:
|
||||||
|
- Added the missing `FileSystem::Exists()` function
|
||||||
|
- Fixed a bug causing the output to be unnecessarily padded
|
||||||
|
- Fixed an issue where fields containing JSON were not properly escaped
|
||||||
|
|
||||||
## 18.6.0 (2024-06-24)
|
## 18.6.0 (2024-06-24)
|
||||||
|
|
||||||
This is a minor release with bug fixes and test improvements.
|
This is a minor release with bug fixes and test improvements.
|
||||||
|
|
||||||
## Features
|
### Features
|
||||||
|
|
||||||
- Added new test data for entitled licenses
|
- Added new test data for entitled licenses
|
||||||
- Added new tests for clear lead sample decryption
|
- Added new tests for clear lead sample decryption
|
||||||
|
|
||||||
## Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
- Improved error logging for tests
|
- Improved error logging for tests
|
||||||
- Small fixes to reduce compiler warning
|
- Small fixes to reduce compiler warning
|
||||||
@@ -29,7 +60,7 @@ CE CDM v18.5.0 includes all changes from CE CDM v17.3.0 and v18.1.0.
|
|||||||
address two major bugs in the CE CDM code which could result in lost offline
|
address two major bugs in the CE CDM code which could result in lost offline
|
||||||
licenses or app crashes. See _Bug Fixes_ for 18.5.0 and 17.3.0 for details.
|
licenses or app crashes. See _Bug Fixes_ for 18.5.0 and 17.3.0 for details.
|
||||||
|
|
||||||
## Features
|
### Features
|
||||||
|
|
||||||
- Supports up to OEMCrypto v18.5, including new OEMCrypto tests introduced
|
- Supports up to OEMCrypto v18.5, including new OEMCrypto tests introduced
|
||||||
since OEMCrypto v18.1.
|
since OEMCrypto v18.1.
|
||||||
|
|||||||
39
README.md
39
README.md
@@ -1,6 +1,6 @@
|
|||||||
# Widevine CE CDM 18.6.0
|
# Widevine CE CDM 18.7.0
|
||||||
|
|
||||||
Released 2024-06-24
|
Released 2024-09-04
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
@@ -10,21 +10,36 @@ following to learn more about the contents of this project and how to use them:
|
|||||||
The [Widevine Developer Site][wv-devsite] documents the CDM API and describes
|
The [Widevine Developer Site][wv-devsite] documents the CDM API and describes
|
||||||
how to integrate the CDM into a system.
|
how to integrate the CDM into a system.
|
||||||
|
|
||||||
## New in 18.6.0
|
## New in 18.7.0
|
||||||
|
|
||||||
This is a minor release with bug fixes and test improvements.
|
This is a minor release with bug fixes and test improvements. However, because
|
||||||
|
of improvements to the BCC Factory Upload Tool, we recommend that all partners
|
||||||
|
who use this tool upgrade to version 18.7.0.
|
||||||
|
|
||||||
## Features
|
### Features
|
||||||
|
|
||||||
- Added new test data for entitled licenses
|
- Added workaround for OEMCrypto implementations with slightly corrupted build
|
||||||
- Added new tests for clear lead sample decryption
|
information
|
||||||
|
- The BCC Factory Upload Tool supports new command-line options for dry runs,
|
||||||
|
batch checks, version-checking, and verbose output.
|
||||||
|
|
||||||
## Bug Fixes
|
### Tests
|
||||||
|
|
||||||
- Improved error logging for tests
|
- Added new tests to better validate the behavior of
|
||||||
- Small fixes to reduce compiler warning
|
`OEMCrypto_BuildInformation()`
|
||||||
- Fixed URL error found for tests using different license server SDK
|
- Verifies output length is set correctly
|
||||||
- Skip CAS tests on non-CAS devices
|
- Verifies content is ASCII JSON without trailing null bytes
|
||||||
|
- Verifies documented JSON fields: required fields are present, and optional
|
||||||
|
and required fields are the correct JSON types
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- Fixed decrypt failures on devices with low TEE memory caused by sending an
|
||||||
|
output buffer to decrypt that was much larger than necessary
|
||||||
|
- Several BCC Factory Upload Tool fixes:
|
||||||
|
- Added the missing `FileSystem::Exists()` function
|
||||||
|
- Fixed a bug causing the output to be unnecessarily padded
|
||||||
|
- Fixed an issue where fields containing JSON were not properly escaped
|
||||||
|
|
||||||
[CHANGELOG.md](./CHANGELOG.md) lists the major changes for each past release.
|
[CHANGELOG.md](./CHANGELOG.md) lists the major changes for each past release.
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
# define CDM_VERSION_MAJOR 18
|
# define CDM_VERSION_MAJOR 18
|
||||||
#endif
|
#endif
|
||||||
#ifndef CDM_VERSION_MINOR
|
#ifndef CDM_VERSION_MINOR
|
||||||
# define CDM_VERSION_MINOR 6
|
# define CDM_VERSION_MINOR 7
|
||||||
#endif
|
#endif
|
||||||
#ifndef CDM_VERSION_PATCH
|
#ifndef CDM_VERSION_PATCH
|
||||||
# define CDM_VERSION_PATCH 0
|
# define CDM_VERSION_PATCH 0
|
||||||
|
|||||||
@@ -7,11 +7,14 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "cdm.h"
|
#include "cdm.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
|
#include "oec_device_features.h"
|
||||||
#include "stderr_logger.h"
|
#include "stderr_logger.h"
|
||||||
#include "test_base.h"
|
#include "test_base.h"
|
||||||
#include "test_host.h"
|
#include "test_host.h"
|
||||||
@@ -22,13 +25,56 @@ std::string g_sandbox_id;
|
|||||||
namespace widevine {
|
namespace widevine {
|
||||||
namespace {
|
namespace {
|
||||||
constexpr char kSandboxIdParam[] = "--sandbox_id=";
|
constexpr char kSandboxIdParam[] = "--sandbox_id=";
|
||||||
|
constexpr char kCertPathParam[] = "--cert_path=";
|
||||||
|
constexpr char kCertKeyPathParam[] = "--cert_key_path=";
|
||||||
|
|
||||||
// Following the pattern established by help text in test_base.cpp
|
// Following the pattern established by help text in test_base.cpp
|
||||||
constexpr char kExtraHelpText[] =
|
constexpr char kExtraHelpText[] =
|
||||||
" --sandbox_id=<sandbox_id>\n"
|
" --sandbox_id=<sandbox_id>\n"
|
||||||
" Specifies the Sandbox ID that should be sent to OEMCrypto via\n"
|
" Specifies the Sandbox ID that should be sent to OEMCrypto via\n"
|
||||||
" OEMCrypto_SetSandbox(). On most platforms, since Sandbox IDs are not\n"
|
" OEMCrypto_SetSandbox(). On most platforms, since Sandbox IDs are not\n"
|
||||||
" in use, this parameter should be omitted.\n";
|
" in use, this parameter should be omitted.\n"
|
||||||
|
" --cert_path=<cert_path>\n"
|
||||||
|
" Path to a preloaded DRM certificate. This may speed up some tests\n"
|
||||||
|
" by skipping the provisioning step. On most platforms, this parameter\n"
|
||||||
|
" parameter should be omitted.\n"
|
||||||
|
" --cert_key_path=<cert_key_path>\n"
|
||||||
|
" Path to a key in preloaded DRM certificate. This should only be used\n"
|
||||||
|
" if the device has a baked in cert.\n";
|
||||||
|
|
||||||
|
bool ReadFile(const std::string& path, std::string* output) {
|
||||||
|
output->clear();
|
||||||
|
std::ifstream fs(path, std::ios::in | std::ios::binary);
|
||||||
|
if (!fs) {
|
||||||
|
LOGE("Failed to open %s: %s", path.c_str(), strerror(errno));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::stringstream buffer;
|
||||||
|
buffer << fs.rdbuf();
|
||||||
|
*output = buffer.str();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reads a file from the command line arguments.
|
||||||
|
// The file path is expected to be the first argument after the flag.
|
||||||
|
// For example, if the flag is "--cert_path=" and the command line is
|
||||||
|
// cdm_test_runner --cert_path=/path/to/cert
|
||||||
|
// then the file at /path/to/cert will be read.
|
||||||
|
// The flag will be removed from the command line arguments.
|
||||||
|
bool ReadFileFromArg(const char* path_flag, std::vector<std::string>& args,
|
||||||
|
std::string* data) {
|
||||||
|
auto path_iter = std::find_if(std::begin(args) + 1, std::end(args),
|
||||||
|
[path_flag](const std::string& elem) -> bool {
|
||||||
|
return elem.find(path_flag) == 0;
|
||||||
|
});
|
||||||
|
if (path_iter != std::end(args)) {
|
||||||
|
const std::string path = path_iter->substr(strlen(path_flag));
|
||||||
|
args.erase(path_iter);
|
||||||
|
return ReadFile(path, data);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
int Main(Cdm::IStorage* storage, Cdm::IClock* clock, Cdm::ITimer* timer,
|
int Main(Cdm::IStorage* storage, Cdm::IClock* clock, Cdm::ITimer* timer,
|
||||||
@@ -50,6 +96,15 @@ int Main(Cdm::IStorage* storage, Cdm::IClock* clock, Cdm::ITimer* timer,
|
|||||||
(void)status; // status is now used when assertions are turned off.
|
(void)status; // status is now used when assertions are turned off.
|
||||||
assert(status == Cdm::kSuccess);
|
assert(status == Cdm::kSuccess);
|
||||||
|
|
||||||
|
std::string data;
|
||||||
|
if (ReadFileFromArg(kCertPathParam, args, &data)) {
|
||||||
|
g_host->set_baked_in_cert(data);
|
||||||
|
}
|
||||||
|
if (ReadFileFromArg(kCertKeyPathParam, args, &data)) {
|
||||||
|
wvoec::global_features.set_rsa_test_key(
|
||||||
|
std::vector<uint8_t>(data.begin(), data.end()));
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<const char*> new_argv(args.size());
|
std::vector<const char*> new_argv(args.size());
|
||||||
std::transform(
|
std::transform(
|
||||||
std::begin(args), std::end(args), std::begin(new_argv),
|
std::begin(args), std::end(args), std::begin(new_argv),
|
||||||
|
|||||||
@@ -137,6 +137,11 @@ bool TestHost::Storage::SaveToString(std::string* data) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool TestHost::Storage::read(const std::string& name, std::string* data) {
|
bool TestHost::Storage::read(const std::string& name, std::string* data) {
|
||||||
|
if (wvutil::kLegacyCertificateFileName == name &&
|
||||||
|
!g_host->baked_in_cert().empty()) {
|
||||||
|
*data = g_host->baked_in_cert();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
StorageMap::iterator it = files_.find(name);
|
StorageMap::iterator it = files_.find(name);
|
||||||
bool ok = it != files_.end();
|
bool ok = it != files_.end();
|
||||||
LOGV("read file: %s: %s", name.c_str(), ok ? "ok" : "fail");
|
LOGV("read file: %s: %s", name.c_str(), ok ? "ok" : "fail");
|
||||||
@@ -148,12 +153,21 @@ bool TestHost::Storage::read(const std::string& name, std::string* data) {
|
|||||||
bool TestHost::Storage::write(const std::string& name,
|
bool TestHost::Storage::write(const std::string& name,
|
||||||
const std::string& data) {
|
const std::string& data) {
|
||||||
LOGV("write file: %s", name.c_str());
|
LOGV("write file: %s", name.c_str());
|
||||||
|
if (wvutil::kLegacyCertificateFileName == name &&
|
||||||
|
!g_host->baked_in_cert().empty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (!CheckFilename(name)) return false;
|
if (!CheckFilename(name)) return false;
|
||||||
files_[name] = data;
|
files_[name] = data;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TestHost::Storage::exists(const std::string& name) {
|
bool TestHost::Storage::exists(const std::string& name) {
|
||||||
|
if (wvutil::kLegacyCertificateFileName == name &&
|
||||||
|
!g_host->baked_in_cert().empty()) {
|
||||||
|
LOGV("exists? %s: always", name.c_str());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
StorageMap::iterator it = files_.find(name);
|
StorageMap::iterator it = files_.find(name);
|
||||||
bool ok = it != files_.end();
|
bool ok = it != files_.end();
|
||||||
LOGV("exists? %s: %s", name.c_str(), ok ? "true" : "false");
|
LOGV("exists? %s: %s", name.c_str(), ok ? "true" : "false");
|
||||||
@@ -174,6 +188,11 @@ bool TestHost::Storage::remove(const std::string& name) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int32_t TestHost::Storage::size(const std::string& name) {
|
int32_t TestHost::Storage::size(const std::string& name) {
|
||||||
|
if (wvutil::kLegacyCertificateFileName == name &&
|
||||||
|
!g_host->baked_in_cert().empty()) {
|
||||||
|
LOGV("size? %s: always", name.c_str());
|
||||||
|
return static_cast<int32_t>(g_host->baked_in_cert().size());
|
||||||
|
}
|
||||||
StorageMap::iterator it = files_.find(name);
|
StorageMap::iterator it = files_.find(name);
|
||||||
bool ok = (it != files_.end());
|
bool ok = (it != files_.end());
|
||||||
LOGV("size? %s: %s", name.c_str(), ok ? "ok" : "fail");
|
LOGV("size? %s: %s", name.c_str(), ok ? "ok" : "fail");
|
||||||
|
|||||||
@@ -69,6 +69,13 @@ class TestHost : public widevine::Cdm::IClock,
|
|||||||
void setTimeout(int64_t delay_ms, IClient* client, void* context) override;
|
void setTimeout(int64_t delay_ms, IClient* client, void* context) override;
|
||||||
void cancel(IClient* client) override;
|
void cancel(IClient* client) override;
|
||||||
|
|
||||||
|
// If this is set, then the storage will return this as a baked in cert.
|
||||||
|
// Trying to write a new cert will generate an error.
|
||||||
|
const std::string& baked_in_cert() const { return baked_in_cert_; };
|
||||||
|
void set_baked_in_cert(const std::string& baked_in_cert) {
|
||||||
|
baked_in_cert_ = baked_in_cert;
|
||||||
|
};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct Timer {
|
struct Timer {
|
||||||
Timer(int64_t expiry_time, IClient* client, void* context)
|
Timer(int64_t expiry_time, IClient* client, void* context)
|
||||||
@@ -95,6 +102,7 @@ class TestHost : public widevine::Cdm::IClock,
|
|||||||
|
|
||||||
Storage global_storage_;
|
Storage global_storage_;
|
||||||
Storage per_origin_storage_;
|
Storage per_origin_storage_;
|
||||||
|
std::string baked_in_cert_;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Owned and managed by the test runner.
|
// Owned and managed by the test runner.
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ class SystemIdExtractor {
|
|||||||
// |security_level|
|
// |security_level|
|
||||||
// - Requested security level, uses the |crypto_session| handle
|
// - Requested security level, uses the |crypto_session| handle
|
||||||
// to convert to a concrete security level.
|
// to convert to a concrete security level.
|
||||||
// |crypto_sesssion|
|
// |crypto_session|
|
||||||
// - Handle into the OEMCrypto platform. If handle is open,
|
// - Handle into the OEMCrypto platform. If handle is open,
|
||||||
// then the session's real security level should match
|
// then the session's real security level should match
|
||||||
// |security_level|.
|
// |security_level|.
|
||||||
|
|||||||
@@ -151,7 +151,6 @@ void AdvanceDestBuffer(OEMCrypto_DestBufferDesc* dest_buffer, size_t bytes) {
|
|||||||
switch (dest_buffer->type) {
|
switch (dest_buffer->type) {
|
||||||
case OEMCrypto_BufferType_Clear:
|
case OEMCrypto_BufferType_Clear:
|
||||||
dest_buffer->buffer.clear.clear_buffer += bytes;
|
dest_buffer->buffer.clear.clear_buffer += bytes;
|
||||||
dest_buffer->buffer.clear.clear_buffer_length -= bytes;
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case OEMCrypto_BufferType_Secure:
|
case OEMCrypto_BufferType_Secure:
|
||||||
@@ -2511,6 +2510,17 @@ bool CryptoSession::GetBuildInformation(RequestedSecurityLevel security_level,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
info->resize(info_length);
|
info->resize(info_length);
|
||||||
|
// Some OEMCrypto implementations may include trailing null
|
||||||
|
// bytes in the output. Trim them here.
|
||||||
|
while (!info->empty() && info->back() == '\0') {
|
||||||
|
info->pop_back();
|
||||||
|
}
|
||||||
|
if (info->empty()) {
|
||||||
|
LOGE("BuildInformation() returned corrupted data: length = %zu",
|
||||||
|
info_length);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3219,6 +3229,11 @@ OEMCryptoResult CryptoSession::DecryptSample(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fake_sample.buffers.input_data_length = length;
|
fake_sample.buffers.input_data_length = length;
|
||||||
|
if (fake_sample.buffers.output_descriptor.type ==
|
||||||
|
OEMCrypto_BufferType_Clear) {
|
||||||
|
fake_sample.buffers.output_descriptor.buffer.clear
|
||||||
|
.clear_buffer_length = length;
|
||||||
|
}
|
||||||
fake_sample.subsamples = &clear_subsample;
|
fake_sample.subsamples = &clear_subsample;
|
||||||
fake_sample.subsamples_length = 1;
|
fake_sample.subsamples_length = 1;
|
||||||
|
|
||||||
@@ -3246,6 +3261,11 @@ OEMCryptoResult CryptoSession::DecryptSample(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fake_sample.buffers.input_data_length = length;
|
fake_sample.buffers.input_data_length = length;
|
||||||
|
if (fake_sample.buffers.output_descriptor.type ==
|
||||||
|
OEMCrypto_BufferType_Clear) {
|
||||||
|
fake_sample.buffers.output_descriptor.buffer.clear
|
||||||
|
.clear_buffer_length = length;
|
||||||
|
}
|
||||||
fake_sample.subsamples = &encrypted_subsample;
|
fake_sample.subsamples = &encrypted_subsample;
|
||||||
fake_sample.subsamples_length = 1;
|
fake_sample.subsamples_length = 1;
|
||||||
|
|
||||||
@@ -3338,6 +3358,10 @@ OEMCryptoResult CryptoSession::LegacyCopyBufferInChunks(
|
|||||||
// Calculate the size of the next chunk.
|
// Calculate the size of the next chunk.
|
||||||
const size_t chunk_size = std::min(remaining_input_data, max_chunk_size);
|
const size_t chunk_size = std::min(remaining_input_data, max_chunk_size);
|
||||||
|
|
||||||
|
if (output_descriptor.type == OEMCrypto_BufferType_Clear) {
|
||||||
|
output_descriptor.buffer.clear.clear_buffer_length = chunk_size;
|
||||||
|
}
|
||||||
|
|
||||||
// Re-add "last subsample" flag if this is the last subsample.
|
// Re-add "last subsample" flag if this is the last subsample.
|
||||||
if (chunk_size == remaining_input_data) {
|
if (chunk_size == remaining_input_data) {
|
||||||
subsample_flags |= OEMCrypto_LastSubsample;
|
subsample_flags |= OEMCrypto_LastSubsample;
|
||||||
@@ -3385,6 +3409,11 @@ OEMCryptoResult CryptoSession::LegacyDecryptInChunks(
|
|||||||
// Calculate the size of the next chunk.
|
// Calculate the size of the next chunk.
|
||||||
const size_t chunk_size = std::min(remaining_input_data, max_chunk_size);
|
const size_t chunk_size = std::min(remaining_input_data, max_chunk_size);
|
||||||
fake_sample.buffers.input_data_length = chunk_size;
|
fake_sample.buffers.input_data_length = chunk_size;
|
||||||
|
if (fake_sample.buffers.output_descriptor.type ==
|
||||||
|
OEMCrypto_BufferType_Clear) {
|
||||||
|
fake_sample.buffers.output_descriptor.buffer.clear.clear_buffer_length =
|
||||||
|
chunk_size;
|
||||||
|
}
|
||||||
if (is_protected) {
|
if (is_protected) {
|
||||||
fake_subsample.num_bytes_encrypted = chunk_size;
|
fake_subsample.num_bytes_encrypted = chunk_size;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include "certificate_provisioning.h"
|
#include "certificate_provisioning.h"
|
||||||
#include "license_holder.h"
|
#include "license_holder.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
|
#include "oec_device_features.h"
|
||||||
#include "provisioning_holder.h"
|
#include "provisioning_holder.h"
|
||||||
#include "test_base.h"
|
#include "test_base.h"
|
||||||
#include "wv_cdm_types.h"
|
#include "wv_cdm_types.h"
|
||||||
@@ -135,6 +136,9 @@ class CoreIntegrationTest : public WvCdmTestBaseWithEngine {
|
|||||||
* different apps. Test using two different apps and origins.
|
* different apps. Test using two different apps and origins.
|
||||||
*/
|
*/
|
||||||
TEST_F(CoreIntegrationTest, ProvisioningStableSpoidTest) {
|
TEST_F(CoreIntegrationTest, ProvisioningStableSpoidTest) {
|
||||||
|
if (wvoec::global_features.provisioning_method == OEMCrypto_DrmCertificate) {
|
||||||
|
GTEST_SKIP() << "Device does not provision.";
|
||||||
|
}
|
||||||
std::string level;
|
std::string level;
|
||||||
ASSERT_EQ(
|
ASSERT_EQ(
|
||||||
NO_ERROR,
|
NO_ERROR,
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ void LicenseHolder::GenerateAndPostRenewalRequest(
|
|||||||
void LicenseHolder::FetchRenewal() {
|
void LicenseHolder::FetchRenewal() {
|
||||||
ASSERT_NE(renewal_in_flight_, nullptr) << "Failed for " << content_id();
|
ASSERT_NE(renewal_in_flight_, nullptr) << "Failed for " << content_id();
|
||||||
ASSERT_NO_FATAL_FAILURE(
|
ASSERT_NO_FATAL_FAILURE(
|
||||||
renewal_in_flight_->AssertOkResponse(&renewal_response_))
|
renewal_in_flight_->AssertOkResponseWithRetry(&renewal_response_))
|
||||||
<< "Renewal failed for " << content_id();
|
<< "Renewal failed for " << content_id();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,7 +243,7 @@ void LicenseHolder::GetKeyResponse(const CdmKeyRequest& key_request) {
|
|||||||
|
|
||||||
std::string http_response;
|
std::string http_response;
|
||||||
url_request.PostRequest(key_request.message);
|
url_request.PostRequest(key_request.message);
|
||||||
ASSERT_NO_FATAL_FAILURE(url_request.AssertOkResponse(&http_response))
|
ASSERT_NO_FATAL_FAILURE(url_request.AssertOkResponseWithRetry(&http_response))
|
||||||
<< "Failed for " << content_id();
|
<< "Failed for " << content_id();
|
||||||
LicenseRequest license_request;
|
LicenseRequest license_request;
|
||||||
license_request.GetDrmMessage(http_response, key_response_);
|
license_request.GetDrmMessage(http_response, key_response_);
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ void ProvisioningHolder::Provision(CdmCertificateType cert_type,
|
|||||||
url_request.PostCertRequestInQueryString(request);
|
url_request.PostCertRequestInQueryString(request);
|
||||||
|
|
||||||
// Receive and parse response.
|
// Receive and parse response.
|
||||||
ASSERT_NO_FATAL_FAILURE(url_request.AssertOkResponse(&response_))
|
ASSERT_NO_FATAL_FAILURE(url_request.AssertOkResponseWithRetry(&response_))
|
||||||
<< "Failed to fetch provisioning response. "
|
<< "Failed to fetch provisioning response. "
|
||||||
<< DumpProvAttempt(request, response_, cert_type);
|
<< DumpProvAttempt(request, response_, cert_type);
|
||||||
|
|
||||||
|
|||||||
@@ -329,9 +329,12 @@ void WvCdmTestBase::InstallTestRootOfTrust() {
|
|||||||
sizeof(test_keybox)));
|
sizeof(test_keybox)));
|
||||||
break;
|
break;
|
||||||
case wvoec::DeviceFeatures::LOAD_TEST_RSA_KEY:
|
case wvoec::DeviceFeatures::LOAD_TEST_RSA_KEY:
|
||||||
// Rare case: used by devices with baked in DRM cert.
|
// Rare case: used by devices with baked in production DRM cert.
|
||||||
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_LoadTestRSAKey());
|
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_LoadTestRSAKey());
|
||||||
break;
|
break;
|
||||||
|
case wvoec::DeviceFeatures::PRELOADED_RSA_KEY:
|
||||||
|
// Rare case: used by devices with baked in test DRM cert.
|
||||||
|
break;
|
||||||
case wvoec::DeviceFeatures::TEST_PROVISION_30:
|
case wvoec::DeviceFeatures::TEST_PROVISION_30:
|
||||||
// Can use oem certificate to install test rsa key.
|
// Can use oem certificate to install test rsa key.
|
||||||
break;
|
break;
|
||||||
@@ -361,6 +364,10 @@ void WvCdmTestBase::Provision() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void WvCdmTestBase::EnsureProvisioned() {
|
void WvCdmTestBase::EnsureProvisioned() {
|
||||||
|
if (wvoec::global_features.provisioning_method == OEMCrypto_DrmCertificate) {
|
||||||
|
LOGD("Device is preprovisioned.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
CdmSessionId session_id;
|
CdmSessionId session_id;
|
||||||
std::unique_ptr<wvutil::FileSystem> file_system(CreateTestFileSystem());
|
std::unique_ptr<wvutil::FileSystem> file_system(CreateTestFileSystem());
|
||||||
// OpenSession will check if a DRM certificate exists, while
|
// OpenSession will check if a DRM certificate exists, while
|
||||||
|
|||||||
@@ -5,7 +5,9 @@
|
|||||||
#include "url_request.h"
|
#include "url_request.h"
|
||||||
|
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
@@ -24,11 +26,15 @@ const int kConnectTimeoutMs = 15000;
|
|||||||
const int kWriteTimeoutMs = 12000;
|
const int kWriteTimeoutMs = 12000;
|
||||||
const int kReadTimeoutMs = 12000;
|
const int kReadTimeoutMs = 12000;
|
||||||
constexpr int kHttpOk = 200;
|
constexpr int kHttpOk = 200;
|
||||||
|
const std::vector<int> kRetryCodes = {502, 504};
|
||||||
|
|
||||||
const std::string kGoogleHeaderUpper("X-Google");
|
const std::string kGoogleHeaderUpper("X-Google");
|
||||||
const std::string kGoogleHeaderLower("x-google");
|
const std::string kGoogleHeaderLower("x-google");
|
||||||
const std::string kCrLf("\r\n");
|
const std::string kCrLf("\r\n");
|
||||||
|
|
||||||
|
constexpr unsigned kRetryCount = 3;
|
||||||
|
constexpr unsigned kRetryIntervalSeconds = 1;
|
||||||
|
|
||||||
// Concatenate all chunks into one blob and returns the response with
|
// Concatenate all chunks into one blob and returns the response with
|
||||||
// header information.
|
// header information.
|
||||||
void ConcatenateChunkedResponse(const std::string http_response,
|
void ConcatenateChunkedResponse(const std::string http_response,
|
||||||
@@ -127,13 +133,34 @@ bool UrlRequest::GetResponse(std::string* message) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void UrlRequest::AssertOkResponse(std::string* message) {
|
void UrlRequest::AssertOkResponseWithRetry(std::string* message) {
|
||||||
ASSERT_TRUE(message);
|
ASSERT_TRUE(message);
|
||||||
ASSERT_TRUE(GetResponse(message));
|
int status_code = 0;
|
||||||
const int status_code = GetStatusCode(*message);
|
for (unsigned i = 0; i < kRetryCount; i++) {
|
||||||
ASSERT_EQ(kHttpOk, status_code) << "HTTP response from " << socket_.url()
|
*message = "";
|
||||||
<< ": (" << message->size() << ") :\n"
|
ASSERT_TRUE(GetResponse(message)) << "For attempt " << (i + 1);
|
||||||
<< *message;
|
status_code = GetStatusCode(*message);
|
||||||
|
// If we didn't get a retry status, then we're done.
|
||||||
|
if (std::find(kRetryCodes.begin(), kRetryCodes.end(), status_code) ==
|
||||||
|
kRetryCodes.end()) {
|
||||||
|
ASSERT_EQ(kHttpOk, status_code) << "HTTP response from " << socket_.url()
|
||||||
|
<< ": (" << message->size() << ") :\n"
|
||||||
|
<< *message;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::cerr << "Temporary failure HTTP response from " << socket_.url()
|
||||||
|
<< ": (" << message->size() << ") :\n"
|
||||||
|
<< *message << "\n"
|
||||||
|
<< "Attempt " << (i + 1) << "\n";
|
||||||
|
socket_.CloseSocket();
|
||||||
|
is_connected_ = false;
|
||||||
|
sleep(kRetryIntervalSeconds << i);
|
||||||
|
Reconnect();
|
||||||
|
SendRequestOnce();
|
||||||
|
}
|
||||||
|
GTEST_FAIL() << "HTTP response from " << socket_.url() << ": ("
|
||||||
|
<< message->size() << ") :\n"
|
||||||
|
<< *message;
|
||||||
}
|
}
|
||||||
|
|
||||||
// static
|
// static
|
||||||
@@ -190,36 +217,35 @@ bool UrlRequest::GetDebugHeaderFields(
|
|||||||
|
|
||||||
bool UrlRequest::PostRequestWithPath(const std::string& path,
|
bool UrlRequest::PostRequestWithPath(const std::string& path,
|
||||||
const std::string& data) {
|
const std::string& data) {
|
||||||
std::string request;
|
request_.clear();
|
||||||
|
|
||||||
request.append("POST ");
|
request_.append("POST ");
|
||||||
request.append(path);
|
request_.append(path);
|
||||||
request.append(" HTTP/1.1\r\n");
|
request_.append(" HTTP/1.1\r\n");
|
||||||
|
|
||||||
request.append("Host: ");
|
request_.append("Host: ");
|
||||||
request.append(socket_.domain_name());
|
request_.append(socket_.domain_name());
|
||||||
request.append("\r\n");
|
request_.append("\r\n");
|
||||||
|
|
||||||
request.append("Connection: close\r\n");
|
request_.append("Connection: close\r\n");
|
||||||
request.append("User-Agent: Widevine CDM v1.0\r\n");
|
request_.append("User-Agent: Widevine CDM v1.0\r\n");
|
||||||
request.append("X-Return-Encrypted-Headers: request_and_response\r\n");
|
request_.append("X-Return-Encrypted-Headers: request_and_response\r\n");
|
||||||
|
|
||||||
// buffer to store length of data as a string
|
request_.append("Content-Length: ");
|
||||||
char data_size_buffer[32] = {0};
|
request_.append(std::to_string(data.size()));
|
||||||
snprintf(data_size_buffer, sizeof(data_size_buffer), "%zu", data.size());
|
request_.append("\r\n");
|
||||||
|
|
||||||
request.append("Content-Length: ");
|
request_.append("\r\n"); // empty line to terminate headers
|
||||||
request.append(data_size_buffer); // appends size of data
|
|
||||||
request.append("\r\n");
|
|
||||||
|
|
||||||
request.append("\r\n"); // empty line to terminate headers
|
request_.append(data);
|
||||||
|
return SendRequestOnce();
|
||||||
request.append(data);
|
}
|
||||||
|
|
||||||
|
bool UrlRequest::SendRequestOnce() {
|
||||||
const int ret = socket_.WriteAndLogErrors(
|
const int ret = socket_.WriteAndLogErrors(
|
||||||
request.c_str(), static_cast<int>(request.size()), kWriteTimeoutMs);
|
request_.c_str(), static_cast<int>(request_.size()), kWriteTimeoutMs);
|
||||||
LOGV("HTTP request: (%zu): %s", request.size(), request.c_str());
|
LOGV("HTTP request: (%zu): %s", request_.size(), request_.c_str());
|
||||||
LOGV("HTTP request hex: %s", wvutil::b2a_hex(request).c_str());
|
LOGV("HTTP request hex: %s", wvutil::b2a_hex(request_).c_str());
|
||||||
return ret != -1;
|
return ret != -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,8 @@ class UrlRequest {
|
|||||||
bool GetResponse(std::string* message);
|
bool GetResponse(std::string* message);
|
||||||
static int GetStatusCode(const std::string& response);
|
static int GetStatusCode(const std::string& response);
|
||||||
// Get the response, and expect the status is OK.
|
// Get the response, and expect the status is OK.
|
||||||
void AssertOkResponse(std::string* message);
|
// It will retry if the response code is in the 500 range.
|
||||||
|
void AssertOkResponseWithRetry(std::string* message);
|
||||||
|
|
||||||
static bool GetDebugHeaderFields(
|
static bool GetDebugHeaderFields(
|
||||||
const std::string& response,
|
const std::string& response,
|
||||||
@@ -37,9 +38,11 @@ class UrlRequest {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
bool PostRequestWithPath(const std::string& path, const std::string& data);
|
bool PostRequestWithPath(const std::string& path, const std::string& data);
|
||||||
|
bool SendRequestOnce();
|
||||||
|
|
||||||
bool is_connected_;
|
bool is_connected_;
|
||||||
HttpSocket socket_;
|
HttpSocket socket_;
|
||||||
|
std::string request_;
|
||||||
|
|
||||||
CORE_DISALLOW_COPY_AND_ASSIGN(UrlRequest);
|
CORE_DISALLOW_COPY_AND_ASSIGN(UrlRequest);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,6 +11,39 @@
|
|||||||
|
|
||||||
namespace widevine {
|
namespace widevine {
|
||||||
namespace {
|
namespace {
|
||||||
|
std::string EscapeJson(const std::string& input) {
|
||||||
|
std::string result;
|
||||||
|
for (std::string::const_iterator c = input.begin(); c != input.end(); ++c) {
|
||||||
|
switch (*c) {
|
||||||
|
case '\"':
|
||||||
|
result += "\\\"";
|
||||||
|
break;
|
||||||
|
case '\\':
|
||||||
|
result += "\\\\";
|
||||||
|
break;
|
||||||
|
case '\b':
|
||||||
|
result += "\\b";
|
||||||
|
break;
|
||||||
|
case '\f':
|
||||||
|
result += "\\f";
|
||||||
|
break;
|
||||||
|
case '\n':
|
||||||
|
result += "\\n";
|
||||||
|
break;
|
||||||
|
case '\r':
|
||||||
|
result += "\\r";
|
||||||
|
break;
|
||||||
|
case '\t':
|
||||||
|
result += "\\t";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
result += *c;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
std::string StringMapToJson(
|
std::string StringMapToJson(
|
||||||
const std::map<std::string, std::string>& string_map) {
|
const std::map<std::string, std::string>& string_map) {
|
||||||
std::string json = "{";
|
std::string json = "{";
|
||||||
@@ -72,7 +105,7 @@ Status WidevineFactoryExtractor::GenerateUploadRequest(std::string& request) {
|
|||||||
request_map["model"] = PropertiesCE::GetClientInfo().model_name;
|
request_map["model"] = PropertiesCE::GetClientInfo().model_name;
|
||||||
request_map["product"] = PropertiesCE::GetClientInfo().product_name;
|
request_map["product"] = PropertiesCE::GetClientInfo().product_name;
|
||||||
request_map["build_info"] = PropertiesCE::GetClientInfo().build_info;
|
request_map["build_info"] = PropertiesCE::GetClientInfo().build_info;
|
||||||
request_map["oemcrypto_build_info"] = oemcrypto_build_info;
|
request_map["oemcrypto_build_info"] = EscapeJson(oemcrypto_build_info);
|
||||||
request_map["bcc"] = wvutil::Base64Encode(bcc);
|
request_map["bcc"] = wvutil::Base64Encode(bcc);
|
||||||
std::string request_json = StringMapToJson(request_map);
|
std::string request_json = StringMapToJson(request_map);
|
||||||
|
|
||||||
|
|||||||
249
factory_upload_tool/ce/wv_upload_tool.py
Normal file → Executable file
249
factory_upload_tool/ce/wv_upload_tool.py
Normal file → Executable file
@@ -12,6 +12,7 @@ input, with one JSON string per line of input.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
from http import HTTPStatus
|
||||||
import http.server
|
import http.server
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
@@ -20,16 +21,22 @@ import urllib.parse
|
|||||||
import urllib.request
|
import urllib.request
|
||||||
import uuid
|
import uuid
|
||||||
import webbrowser
|
import webbrowser
|
||||||
|
from google.auth.transport import requests
|
||||||
|
from google.oauth2 import service_account
|
||||||
|
|
||||||
DEFAULT_BASE = 'https://widevine.googleapis.com/v1beta1'
|
DEFAULT_BASE = 'https://widevine.googleapis.com/v1beta1'
|
||||||
UPLOAD_PATH = '/uniqueDeviceInfo:batchUpload'
|
UPLOAD_PATH = '/uniqueDeviceInfo:batchUpload'
|
||||||
|
BATCH_CHECK_PATH = '/uniqueDeviceInfo:batchCheck'
|
||||||
TOKEN_CACHE_FILE = os.path.join(
|
TOKEN_CACHE_FILE = os.path.join(
|
||||||
os.path.expanduser('~'), '.device_info_uploader.token')
|
os.path.expanduser('~'), '.device_info_uploader.token'
|
||||||
|
)
|
||||||
|
|
||||||
OAUTH_SERVICE_BASE = 'https://accounts.google.com/o/oauth2'
|
OAUTH_SERVICE_BASE = 'https://accounts.google.com/o/oauth2'
|
||||||
OAUTH_AUTHN_URL = OAUTH_SERVICE_BASE + '/auth'
|
OAUTH_AUTHN_URL = OAUTH_SERVICE_BASE + '/auth'
|
||||||
OAUTH_TOKEN_URL = OAUTH_SERVICE_BASE + '/token'
|
OAUTH_TOKEN_URL = OAUTH_SERVICE_BASE + '/token'
|
||||||
|
|
||||||
|
OAUTH_SCOPES = ['https://www.googleapis.com/auth/widevine/frontend']
|
||||||
|
|
||||||
|
|
||||||
class OAuthHTTPRequestHandler(http.server.BaseHTTPRequestHandler):
|
class OAuthHTTPRequestHandler(http.server.BaseHTTPRequestHandler):
|
||||||
"""HTTP Handler used to accept the oauth response when the user logs in."""
|
"""HTTP Handler used to accept the oauth response when the user logs in."""
|
||||||
@@ -41,17 +48,24 @@ class OAuthHTTPRequestHandler(http.server.BaseHTTPRequestHandler):
|
|||||||
params = dict(urllib.parse.parse_qsl(parsed_path.query))
|
params = dict(urllib.parse.parse_qsl(parsed_path.query))
|
||||||
if 'error' in params:
|
if 'error' in params:
|
||||||
error = params['error']
|
error = params['error']
|
||||||
self.respond(400, error,
|
self.respond(
|
||||||
f'Error received from the OAuth server: {error}.')
|
400, error, f'Error received from the OAuth server: {error}.'
|
||||||
|
)
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
elif 'code' not in params:
|
elif 'code' not in params:
|
||||||
self.respond(400, 'ERROR',
|
self.respond(
|
||||||
('Response from OAuth server is missing the authorization '
|
400,
|
||||||
f'code. Full response: "{self.path}"'))
|
'ERROR',
|
||||||
|
(
|
||||||
|
'Response from OAuth server is missing the authorization '
|
||||||
|
f'code. Full response: "{self.path}"'
|
||||||
|
),
|
||||||
|
)
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
else:
|
else:
|
||||||
self.respond(200, 'Success!',
|
self.respond(
|
||||||
'Success! You may close this browser window.')
|
200, 'Success!', 'Success! You may close this browser window.'
|
||||||
|
)
|
||||||
self.server.code = params['code']
|
self.server.code = params['code']
|
||||||
|
|
||||||
def do_POST(self): # pylint: disable=invalid-name
|
def do_POST(self): # pylint: disable=invalid-name
|
||||||
@@ -70,20 +84,25 @@ class OAuthHTTPRequestHandler(http.server.BaseHTTPRequestHandler):
|
|||||||
self.send_response(code)
|
self.send_response(code)
|
||||||
self.send_header('Content-type', 'text/html')
|
self.send_header('Content-type', 'text/html')
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
self.wfile.write(('<html>'
|
self.wfile.write(
|
||||||
f' <title>{title}</title>'
|
(
|
||||||
f' <body>'
|
'<html>'
|
||||||
f' <p style="font-size:24px;">{message}</p>'
|
f' <title>{title}</title>'
|
||||||
f' </body>'
|
' <body>'
|
||||||
'</html>').encode('utf-8'))
|
f' <p style="font-size:24px;">{message}</p>'
|
||||||
|
' </body>'
|
||||||
|
'</html>'
|
||||||
|
).encode('utf-8')
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class LocalOAuthReceiver(http.server.HTTPServer):
|
class LocalOAuthReceiver(http.server.HTTPServer):
|
||||||
"""HTTP server that will wait for an OAuth authorization code."""
|
"""HTTP server that will wait for an OAuth authorization code."""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(LocalOAuthReceiver, self).__init__(('127.0.0.1', 0),
|
super(LocalOAuthReceiver, self).__init__(
|
||||||
OAuthHTTPRequestHandler)
|
('127.0.0.1', 0), OAuthHTTPRequestHandler
|
||||||
|
)
|
||||||
self.code = None
|
self.code = None
|
||||||
|
|
||||||
def port(self):
|
def port(self):
|
||||||
@@ -111,25 +130,63 @@ def parse_args():
|
|||||||
Returns:
|
Returns:
|
||||||
An argparse.Namespace object populated with the arguments.
|
An argparse.Namespace object populated with the arguments.
|
||||||
"""
|
"""
|
||||||
parser = argparse.ArgumentParser(description='Upload device info')
|
parser = argparse.ArgumentParser(description='Widevine BCC Batch Upload/Check Tool')
|
||||||
|
|
||||||
|
parser.add_argument("--version", action="version", version="20240822") #yyyymmdd
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--json-csr',
|
'--json-csr',
|
||||||
nargs='+',
|
nargs='+',
|
||||||
required=True,
|
required=True,
|
||||||
help='list of files containing JSON output from rkp_factory_extraction_tool'
|
help='list of files containing JSON output from factory extraction tool',
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument('--credentials', help='JSON credentials file')
|
||||||
'--credentials', required=True, help='JSON credentials file')
|
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--endpoint', default=DEFAULT_BASE, help='destination server URL')
|
'--endpoint', default=DEFAULT_BASE, help='destination server URL'
|
||||||
|
)
|
||||||
|
|
||||||
parser.add_argument('--org-name', required=True, help='orgnization name')
|
parser.add_argument('--org-name', required=True, help='orgnization name')
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--cache-token',
|
'--cache-token',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help='Use a locally cached a refresh token')
|
help='Use a locally cached a refresh token',
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
'--service-credentials', help='JSON credentials file for service account'
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
'--die-on-error',
|
||||||
|
action='store_true',
|
||||||
|
help='exit on error and stop uploading more CSRs',
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
'--dryrun',
|
||||||
|
action='store_true',
|
||||||
|
help=(
|
||||||
|
'Do not upload anything. Instead print out what actions would have'
|
||||||
|
' been taken if the --dryrun flag had not been specified.'
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
'--check',
|
||||||
|
action='store_true',
|
||||||
|
required=False,
|
||||||
|
help='Perform a batch check on the CSRs.',
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
'--verbose',
|
||||||
|
action='store_true',
|
||||||
|
required=False,
|
||||||
|
help='Print request and response details.',
|
||||||
|
)
|
||||||
|
|
||||||
return parser.parse_args()
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
@@ -146,6 +203,7 @@ def parse_json_csrs(filename, batches):
|
|||||||
line_count = 0
|
line_count = 0
|
||||||
for line in open(filename):
|
for line in open(filename):
|
||||||
line_count = line_count + 1
|
line_count = line_count + 1
|
||||||
|
obj = {}
|
||||||
try:
|
try:
|
||||||
obj = json.loads(line)
|
obj = json.loads(line)
|
||||||
except json.JSONDecodeError as e:
|
except json.JSONDecodeError as e:
|
||||||
@@ -159,14 +217,17 @@ def parse_json_csrs(filename, batches):
|
|||||||
'name': obj['name'],
|
'name': obj['name'],
|
||||||
'model': obj['model'],
|
'model': obj['model'],
|
||||||
'product': obj['product'],
|
'product': obj['product'],
|
||||||
'build_info': obj['build_info']
|
'build_info': obj['build_info'],
|
||||||
|
'oemcrypto_build_info': obj['oemcrypto_build_info'],
|
||||||
})
|
})
|
||||||
|
if device_metadata not in batches:
|
||||||
|
batches[device_metadata] = []
|
||||||
|
batches[device_metadata].append(bcc)
|
||||||
except KeyError as e:
|
except KeyError as e:
|
||||||
die(f'Invalid object at {filename}:{line_count}, missing {e}')
|
die(f'Invalid object at {filename}:{line_count}, missing {e}')
|
||||||
|
|
||||||
if device_metadata not in batches:
|
if line_count == 0:
|
||||||
batches[device_metadata] = []
|
die('Empty BCC file!')
|
||||||
batches[device_metadata].append(bcc)
|
|
||||||
|
|
||||||
|
|
||||||
def format_request_body(args, device_metadata, bccs):
|
def format_request_body(args, device_metadata, bccs):
|
||||||
@@ -180,6 +241,17 @@ def format_request_body(args, device_metadata, bccs):
|
|||||||
return json.dumps(request).encode('utf-8')
|
return json.dumps(request).encode('utf-8')
|
||||||
|
|
||||||
|
|
||||||
|
def format_check_request_body(args, bccs):
|
||||||
|
"""Generate a formatted BatchCheck request buffer for the given build and CSRs."""
|
||||||
|
request = {
|
||||||
|
'parent': 'orgs/' + args.org_name,
|
||||||
|
'request_id': uuid.uuid4().hex,
|
||||||
|
'device_info': bccs,
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.dumps(request).encode('utf-8')
|
||||||
|
|
||||||
|
|
||||||
def load_refresh_token():
|
def load_refresh_token():
|
||||||
if not os.path.exists(TOKEN_CACHE_FILE):
|
if not os.path.exists(TOKEN_CACHE_FILE):
|
||||||
return None
|
return None
|
||||||
@@ -258,7 +330,8 @@ def load_and_validate_creds(credfile):
|
|||||||
' The given credentials do not appear to be for a locally installed\n'
|
' The given credentials do not appear to be for a locally installed\n'
|
||||||
' application. Please navigate to the credentials dashboard and\n'
|
' application. Please navigate to the credentials dashboard and\n'
|
||||||
' ensure that the "Type" of your client is "Desktop":\n'
|
' ensure that the "Type" of your client is "Desktop":\n'
|
||||||
' https://console.cloud.google.com/apis/credentials')
|
' https://console.cloud.google.com/apis/credentials'
|
||||||
|
)
|
||||||
|
|
||||||
if 'installed' not in credmap:
|
if 'installed' not in credmap:
|
||||||
die(not_local_app_creds_error)
|
die(not_local_app_creds_error)
|
||||||
@@ -267,10 +340,12 @@ def load_and_validate_creds(credfile):
|
|||||||
|
|
||||||
expected_keys = set(['client_id', 'client_secret', 'redirect_uris'])
|
expected_keys = set(['client_id', 'client_secret', 'redirect_uris'])
|
||||||
if not expected_keys.issubset(creds.keys()):
|
if not expected_keys.issubset(creds.keys()):
|
||||||
die(('ERROR: Invalid credential file.\n'
|
die((
|
||||||
' The given credentials do not appear to be valid. Please\n'
|
'ERROR: Invalid credential file.\n'
|
||||||
' re-download the client credentials file from the dashboard:\n'
|
' The given credentials do not appear to be valid. Please\n'
|
||||||
' https://console.cloud.google.com/apis/credentials'))
|
' re-download the client credentials file from the dashboard:\n'
|
||||||
|
' https://console.cloud.google.com/apis/credentials'
|
||||||
|
))
|
||||||
|
|
||||||
if 'http://localhost' not in creds['redirect_uris']:
|
if 'http://localhost' not in creds['redirect_uris']:
|
||||||
die(not_local_app_creds_error)
|
die(not_local_app_creds_error)
|
||||||
@@ -279,7 +354,23 @@ def load_and_validate_creds(credfile):
|
|||||||
|
|
||||||
|
|
||||||
def authenticate_and_fetch_token(args):
|
def authenticate_and_fetch_token(args):
|
||||||
"""Authenticate the user and fetch an OAUTH2 access token."""
|
"""Authenticate and fetch an OAUTH2 access token."""
|
||||||
|
# Auth for service account
|
||||||
|
if args.service_credentials:
|
||||||
|
if not os.path.exists(args.service_credentials):
|
||||||
|
die('Service account credentials file does not exist.')
|
||||||
|
svc_account = service_account.Credentials.from_service_account_file(
|
||||||
|
args.service_credentials,
|
||||||
|
scopes=OAUTH_SCOPES,
|
||||||
|
)
|
||||||
|
svc_account.refresh(requests.Request())
|
||||||
|
return svc_account.token
|
||||||
|
|
||||||
|
# Auth for user account
|
||||||
|
if args.credentials is None:
|
||||||
|
die('User credentials is not provided.')
|
||||||
|
if not os.path.exists(args.credentials):
|
||||||
|
die('User credentials file does not exist.')
|
||||||
creds = load_and_validate_creds(args.credentials)
|
creds = load_and_validate_creds(args.credentials)
|
||||||
|
|
||||||
access_type = 'online'
|
access_type = 'online'
|
||||||
@@ -292,10 +383,17 @@ def authenticate_and_fetch_token(args):
|
|||||||
httpd = LocalOAuthReceiver()
|
httpd = LocalOAuthReceiver()
|
||||||
redirect_uri = f'http://127.0.0.1:{httpd.port()}'
|
redirect_uri = f'http://127.0.0.1:{httpd.port()}'
|
||||||
url = (
|
url = (
|
||||||
OAUTH_AUTHN_URL + '?response_type=code' + '&client_id=' +
|
OAUTH_AUTHN_URL
|
||||||
creds['client_id'] + '&redirect_uri=' + redirect_uri +
|
+ '?response_type=code'
|
||||||
'&scope=https://www.googleapis.com/auth/widevine/frontend' +
|
+ '&client_id='
|
||||||
'&access_type=' + access_type + '&prompt=select_account')
|
+ creds['client_id']
|
||||||
|
+ '&redirect_uri='
|
||||||
|
+ redirect_uri
|
||||||
|
+ '&scope=https://www.googleapis.com/auth/widevine/frontend'
|
||||||
|
+ '&access_type='
|
||||||
|
+ access_type
|
||||||
|
+ '&prompt=select_account'
|
||||||
|
)
|
||||||
print('Opening your web browser to authenticate...')
|
print('Opening your web browser to authenticate...')
|
||||||
if not webbrowser.open(url, new=1, autoraise=True):
|
if not webbrowser.open(url, new=1, autoraise=True):
|
||||||
print('Error opening the browser. Please open this link in a browser')
|
print('Error opening the browser. Please open this link in a browser')
|
||||||
@@ -312,15 +410,52 @@ def upload_batch(args, device_metadata, bccs):
|
|||||||
device_metadata: The build for which we're uploading CSRs
|
device_metadata: The build for which we're uploading CSRs
|
||||||
bccs: a list of BCCs to be uploaded for the given build
|
bccs: a list of BCCs to be uploaded for the given build
|
||||||
"""
|
"""
|
||||||
print("Uploading {} bcc(s) for build '{}'".format(len(bccs), device_metadata))
|
print('Uploading {} bcc(s)'.format(len(bccs)))
|
||||||
|
if args.verbose:
|
||||||
|
print("Build: '{}'".format(device_metadata))
|
||||||
body = format_request_body(args, device_metadata, bccs)
|
body = format_request_body(args, device_metadata, bccs)
|
||||||
print(body)
|
return batch_action_single_attempt(args, UPLOAD_PATH, body)
|
||||||
print(args.endpoint + UPLOAD_PATH)
|
|
||||||
request = urllib.request.Request(args.endpoint + UPLOAD_PATH)
|
|
||||||
|
def check_batch(args, device_metadata, bccs):
|
||||||
|
"""Batch check all the CSRs.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
args: The parsed command-line arguments
|
||||||
|
device_metadata: The build for which we're checking CSRs
|
||||||
|
bccs: a list of BCCs to be checked for the given build
|
||||||
|
"""
|
||||||
|
print('Checking {} bcc(s)'.format(len(bccs)))
|
||||||
|
if args.verbose:
|
||||||
|
print("Build: '{}'".format(device_metadata))
|
||||||
|
body = format_check_request_body(args, bccs)
|
||||||
|
return batch_action_single_attempt(args, BATCH_CHECK_PATH, body)
|
||||||
|
|
||||||
|
|
||||||
|
def batch_action_single_attempt(args, path, body):
|
||||||
|
"""Batch action (upload or check existence) for all the CSRs in chunks.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
args: The parsed command-line arguments
|
||||||
|
csrs: a list of CSRs to be uploaded/checked for the given build
|
||||||
|
path: The endpoint url for the specific action
|
||||||
|
body: The formatted request body
|
||||||
|
"""
|
||||||
|
if args.verbose:
|
||||||
|
print('Request body:')
|
||||||
|
print(body)
|
||||||
|
print('Request target:')
|
||||||
|
print(args.endpoint + path)
|
||||||
|
request = urllib.request.Request(args.endpoint + path)
|
||||||
request.add_header('Content-Type', 'application/json')
|
request.add_header('Content-Type', 'application/json')
|
||||||
request.add_header('X-GFE-SSL', 'yes')
|
request.add_header('X-GFE-SSL', 'yes')
|
||||||
request.add_header('Authorization',
|
request.add_header(
|
||||||
'Bearer ' + authenticate_and_fetch_token(args))
|
'Authorization', 'Bearer ' + authenticate_and_fetch_token(args)
|
||||||
|
)
|
||||||
|
if args.dryrun:
|
||||||
|
print('dry run: would have reached to ' + request.full_url)
|
||||||
|
return HTTPStatus.OK
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = urllib.request.urlopen(request, body)
|
response = urllib.request.urlopen(request, body)
|
||||||
except urllib.error.HTTPError as e:
|
except urllib.error.HTTPError as e:
|
||||||
@@ -329,18 +464,40 @@ def upload_batch(args, device_metadata, bccs):
|
|||||||
eprint(line.decode('utf-8').rstrip())
|
eprint(line.decode('utf-8').rstrip())
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
while chunk := response.read(1024):
|
response_body = response.read().decode('utf-8')
|
||||||
print(chunk.decode('utf-8'))
|
if args.verbose:
|
||||||
|
print('Response body:')
|
||||||
|
print(response_body)
|
||||||
|
res = json.loads(response_body)
|
||||||
|
if 'failedDeviceInfo' in res:
|
||||||
|
eprint('Failed to upload/check some device info! Response body:')
|
||||||
|
eprint(response_body)
|
||||||
|
eprint('Failed: {} bcc(s)'.format(len(res['failedDeviceInfo'])))
|
||||||
|
if args.die_on_error:
|
||||||
|
sys.exit(1)
|
||||||
|
elif 'successDeviceInfo' in res:
|
||||||
|
print('Success: {} bcc(s)'.format(len(res['successDeviceInfo'])))
|
||||||
|
else:
|
||||||
|
eprint('Failed with unexpected response:')
|
||||||
|
eprint(response_body)
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
args = parse_args()
|
args = parse_args()
|
||||||
|
if args.dryrun:
|
||||||
|
print('Dry run mode enabled. Service APIs will not be called.')
|
||||||
|
|
||||||
batches = {}
|
batches = {}
|
||||||
for filename in args.json_csr:
|
for filename in args.json_csr:
|
||||||
parse_json_csrs(filename, batches)
|
parse_json_csrs(filename, batches)
|
||||||
|
|
||||||
|
if len(batches) > 1:
|
||||||
|
print('WARNING: {} different device metadata'.format(len(batches)))
|
||||||
|
|
||||||
for device_metadata, bccs in batches.items():
|
for device_metadata, bccs in batches.items():
|
||||||
upload_batch(args, device_metadata, bccs)
|
if args.check:
|
||||||
|
check_batch(args, device_metadata, bccs)
|
||||||
|
else:
|
||||||
|
upload_batch(args, device_metadata, bccs)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ std::unique_ptr<File> FileSystem::Open(const std::string&, int) {
|
|||||||
return std::unique_ptr<File>(new FileImpl());
|
return std::unique_ptr<File>(new FileImpl());
|
||||||
}
|
}
|
||||||
bool FileSystem::Exists(const std::string&) { return false; }
|
bool FileSystem::Exists(const std::string&) { return false; }
|
||||||
|
bool FileSystem::Exists(const std::string&, int*) { return false; }
|
||||||
bool FileSystem::Remove(const std::string&) { return false; }
|
bool FileSystem::Remove(const std::string&) { return false; }
|
||||||
ssize_t FileSystem::FileSize(const std::string&) { return false; }
|
ssize_t FileSystem::FileSize(const std::string&) { return false; }
|
||||||
bool FileSystem::List(const std::string&, std::vector<std::string>*) {
|
bool FileSystem::List(const std::string&, std::vector<std::string>*) {
|
||||||
@@ -117,6 +118,9 @@ OEMCryptoResult OEMCryptoInterface::GetBcc(std::vector<uint8_t>& bcc) {
|
|||||||
LOGI("GetBootCertificateChain second attempt result %d", result);
|
LOGI("GetBootCertificateChain second attempt result %d", result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (result == OEMCrypto_SUCCESS) {
|
||||||
|
bcc.resize(bcc_size);
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,7 +140,9 @@ OEMCryptoResult OEMCryptoInterface::GetOEMCryptoBuildInfo(
|
|||||||
result = BuildInformation(&build_info[0], &build_info_size);
|
result = BuildInformation(&build_info[0], &build_info_size);
|
||||||
LOGI("BuildInformation second attempt result %d", result);
|
LOGI("BuildInformation second attempt result %d", result);
|
||||||
}
|
}
|
||||||
|
if (result == OEMCrypto_SUCCESS) {
|
||||||
|
build_info.resize(build_info_size);
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// License Agreement.
|
// License Agreement.
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @mainpage OEMCrypto API v18.6
|
* @mainpage OEMCrypto API v18.7
|
||||||
*
|
*
|
||||||
* OEMCrypto is the low level library implemented by the OEM to provide key and
|
* OEMCrypto is the low level library implemented by the OEM to provide key and
|
||||||
* content protection, usually in a separate secure memory or process space. The
|
* content protection, usually in a separate secure memory or process space. The
|
||||||
@@ -721,6 +721,7 @@ typedef enum OEMCrypto_SignatureHashAlgorithm {
|
|||||||
#define OEMCrypto_UseSecondaryKey _oecc144
|
#define OEMCrypto_UseSecondaryKey _oecc144
|
||||||
#define OEMCrypto_MarkOfflineSession _oecc153
|
#define OEMCrypto_MarkOfflineSession _oecc153
|
||||||
#define OEMCrypto_WrapClearPrivateKey _oecc154
|
#define OEMCrypto_WrapClearPrivateKey _oecc154
|
||||||
|
#define OEMCrypto_SetSessionUsage _oecc155
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
/// @addtogroup initcontrol
|
/// @addtogroup initcontrol
|
||||||
@@ -1941,6 +1942,33 @@ OEMCryptoResult OEMCrypto_GetOEMKeyToken(OEMCrypto_SESSION key_session,
|
|||||||
uint8_t* key_token,
|
uint8_t* key_token,
|
||||||
size_t* key_token_length);
|
size_t* key_token_length);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the session's usage information and scrambling mode, allowing the
|
||||||
|
* descrambler to be set up to decode one or more streams encrypted by the
|
||||||
|
* Conditional Access System (CAS). This method is currently used exclusively by
|
||||||
|
* CAS.
|
||||||
|
*
|
||||||
|
* @param[in] session: session id.
|
||||||
|
* @param[in] intent: session usage information. A constant defined by MediaCaS.
|
||||||
|
* @param[in] mode: scrambling mode. A constant defined by MediaCaS.
|
||||||
|
*
|
||||||
|
* @retval OEMCrypto_SUCCESS on success
|
||||||
|
* @retval OEMCrypto_ERROR_INVALID_SESSION
|
||||||
|
* @retval OEMCrypto_ERROR_INVALID_CONTEXT
|
||||||
|
* @retval OEMCrypto_ERROR_NOT_IMPLEMENTED
|
||||||
|
*
|
||||||
|
* @threading
|
||||||
|
* This is a "Session Function" and may be called simultaneously with session
|
||||||
|
* functions for other sessions but not simultaneously with other functions
|
||||||
|
* for this session. It is as if the CDM holds a write lock for this session,
|
||||||
|
* and a read lock on the OEMCrypto system.
|
||||||
|
*
|
||||||
|
* @version
|
||||||
|
* This method is new in API version 19.
|
||||||
|
*/
|
||||||
|
OEMCryptoResult OEMCrypto_SetSessionUsage(OEMCrypto_SESSION session,
|
||||||
|
uint32_t intent, uint32_t mode);
|
||||||
|
|
||||||
/// @}
|
/// @}
|
||||||
|
|
||||||
/// @addtogroup decryption
|
/// @addtogroup decryption
|
||||||
@@ -2236,10 +2264,20 @@ OEMCryptoResult OEMCrypto_GetKeyHandle(OEMCrypto_SESSION session,
|
|||||||
* usually be non-zero. This mode allows devices to decrypt FMP4 HLS content,
|
* usually be non-zero. This mode allows devices to decrypt FMP4 HLS content,
|
||||||
* SAMPLE-AES HLS content, as well as content using the DASH 'cbcs' scheme.
|
* SAMPLE-AES HLS content, as well as content using the DASH 'cbcs' scheme.
|
||||||
*
|
*
|
||||||
* The skip field of OEMCrypto_CENCEncryptPatternDesc may also be zero. If
|
* The skip field of OEMCrypto_CENCEncryptPatternDesc may be zero. If the skip
|
||||||
* the skip field is zero, then patterns are not in use and all crypto blocks
|
* field is zero, then patterns are not in use and all crypto blocks in the
|
||||||
* in the encrypted part of the subsample are encrypted. It is not valid for
|
* encrypted part of the subsample are encrypted, except for any partial crypto
|
||||||
* the encrypt field to be zero.
|
* blocks at the end. The most common pattern with a skip field of zero is
|
||||||
|
* (10,0), but all patterns with a skip field of zero are functionally the same.
|
||||||
|
*
|
||||||
|
* If the skip field of OEMCrypto_CENCEncryptPatternDesc is zero, the encrypt
|
||||||
|
* field may also be zero. This pattern sometimes appears in content,
|
||||||
|
* particularly in audio tracks. This (0,0) pattern should be treated as
|
||||||
|
* equivalent to the pattern (10,0). e.g. All complete crypto blocks should be
|
||||||
|
* decrypted.
|
||||||
|
*
|
||||||
|
* It is not valid for the encrypt field of OEMCrypto_CENCEncryptPatternDesc to
|
||||||
|
* be zero if the skip field is non-zero.
|
||||||
*
|
*
|
||||||
* The length of a crypto block in AES-128 is 16 bytes. In the 'cbcs' scheme,
|
* The length of a crypto block in AES-128 is 16 bytes. In the 'cbcs' scheme,
|
||||||
* if the encrypted part of a subsample has a length that is not a multiple
|
* if the encrypted part of a subsample has a length that is not a multiple
|
||||||
|
|||||||
@@ -26,9 +26,9 @@ struct CoreMessageFeatures {
|
|||||||
|
|
||||||
// This is the published version of the ODK Core Message library. The default
|
// This is the published version of the ODK Core Message library. The default
|
||||||
// behavior is for the server to restrict messages to at most this version
|
// behavior is for the server to restrict messages to at most this version
|
||||||
// number. The default is 18.6.
|
// number. The default is 18.7.
|
||||||
uint32_t maximum_major_version = 18;
|
uint32_t maximum_major_version = 18;
|
||||||
uint32_t maximum_minor_version = 6;
|
uint32_t maximum_minor_version = 7;
|
||||||
|
|
||||||
bool operator==(const CoreMessageFeatures &other) const;
|
bool operator==(const CoreMessageFeatures &other) const;
|
||||||
bool operator!=(const CoreMessageFeatures &other) const {
|
bool operator!=(const CoreMessageFeatures &other) const {
|
||||||
|
|||||||
@@ -16,10 +16,10 @@ extern "C" {
|
|||||||
|
|
||||||
/* The version of this library. */
|
/* The version of this library. */
|
||||||
#define ODK_MAJOR_VERSION 18
|
#define ODK_MAJOR_VERSION 18
|
||||||
#define ODK_MINOR_VERSION 6
|
#define ODK_MINOR_VERSION 7
|
||||||
|
|
||||||
/* ODK Version string. Date changed automatically on each release. */
|
/* ODK Version string. Date changed automatically on each release. */
|
||||||
#define ODK_RELEASE_DATE "ODK v18.6 2024-06-04"
|
#define ODK_RELEASE_DATE "ODK v18.7 2024-09-04"
|
||||||
|
|
||||||
/* The lowest version number for an ODK message. */
|
/* The lowest version number for an ODK message. */
|
||||||
#define ODK_FIRST_VERSION 16
|
#define ODK_FIRST_VERSION 16
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ CoreMessageFeatures CoreMessageFeatures::DefaultFeatures(
|
|||||||
features.maximum_minor_version = 2; // 17.2
|
features.maximum_minor_version = 2; // 17.2
|
||||||
break;
|
break;
|
||||||
case 18:
|
case 18:
|
||||||
features.maximum_minor_version = 6; // 18.6
|
features.maximum_minor_version = 7; // 18.7
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
features.maximum_minor_version = 0;
|
features.maximum_minor_version = 0;
|
||||||
|
|||||||
@@ -274,7 +274,7 @@ OEMCryptoResult ODK_InitializeSessionValues(ODK_TimerLimits* timer_limits,
|
|||||||
nonce_values->api_minor_version = 2;
|
nonce_values->api_minor_version = 2;
|
||||||
break;
|
break;
|
||||||
case 18:
|
case 18:
|
||||||
nonce_values->api_minor_version = 6;
|
nonce_values->api_minor_version = 7;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
nonce_values->api_minor_version = 0;
|
nonce_values->api_minor_version = 0;
|
||||||
|
|||||||
@@ -1216,7 +1216,7 @@ std::vector<VersionParameters> TestCases() {
|
|||||||
// number.
|
// number.
|
||||||
{16, ODK_MAJOR_VERSION, ODK_MINOR_VERSION, 16, 5},
|
{16, ODK_MAJOR_VERSION, ODK_MINOR_VERSION, 16, 5},
|
||||||
{17, ODK_MAJOR_VERSION, ODK_MINOR_VERSION, 17, 2},
|
{17, ODK_MAJOR_VERSION, ODK_MINOR_VERSION, 17, 2},
|
||||||
{18, ODK_MAJOR_VERSION, ODK_MINOR_VERSION, 18, 6},
|
{18, ODK_MAJOR_VERSION, ODK_MINOR_VERSION, 18, 7},
|
||||||
// Here are some known good versions. Make extra sure they work.
|
// Here are some known good versions. Make extra sure they work.
|
||||||
{ODK_MAJOR_VERSION, 16, 3, 16, 3},
|
{ODK_MAJOR_VERSION, 16, 3, 16, 3},
|
||||||
{ODK_MAJOR_VERSION, 16, 4, 16, 4},
|
{ODK_MAJOR_VERSION, 16, 4, 16, 4},
|
||||||
@@ -1229,6 +1229,7 @@ std::vector<VersionParameters> TestCases() {
|
|||||||
{ODK_MAJOR_VERSION, 18, 4, 18, 4},
|
{ODK_MAJOR_VERSION, 18, 4, 18, 4},
|
||||||
{ODK_MAJOR_VERSION, 18, 5, 18, 5},
|
{ODK_MAJOR_VERSION, 18, 5, 18, 5},
|
||||||
{ODK_MAJOR_VERSION, 18, 6, 18, 6},
|
{ODK_MAJOR_VERSION, 18, 6, 18, 6},
|
||||||
|
{ODK_MAJOR_VERSION, 18, 7, 18, 7},
|
||||||
{0, 16, 3, 16, 3},
|
{0, 16, 3, 16, 3},
|
||||||
{0, 16, 4, 16, 4},
|
{0, 16, 4, 16, 4},
|
||||||
{0, 16, 5, 16, 5},
|
{0, 16, 5, 16, 5},
|
||||||
@@ -1237,6 +1238,7 @@ std::vector<VersionParameters> TestCases() {
|
|||||||
{0, 18, 4, 18, 4},
|
{0, 18, 4, 18, 4},
|
||||||
{0, 18, 5, 18, 5},
|
{0, 18, 5, 18, 5},
|
||||||
{0, 18, 6, 18, 6},
|
{0, 18, 6, 18, 6},
|
||||||
|
{0, 18, 7, 18, 7},
|
||||||
};
|
};
|
||||||
return test_cases;
|
return test_cases;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -384,3 +384,7 @@ OEMCryptoResult _oecc154(const uint8_t* clear_private_key_bytes,
|
|||||||
size_t clear_private_key_length,
|
size_t clear_private_key_length,
|
||||||
uint8_t* wrapped_private_key,
|
uint8_t* wrapped_private_key,
|
||||||
size_t* wrapped_private_key_length);
|
size_t* wrapped_private_key_length);
|
||||||
|
|
||||||
|
// OEMCrypto_SetSessionUsage defined in v18.7
|
||||||
|
OEMCryptoResult _oecc155(OEMCrypto_SESSION session, uint32_t intent,
|
||||||
|
uint32_t mode);
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ format below:
|
|||||||
+-----------------------+----------------------+--------------------------+
|
+-----------------------+----------------------+--------------------------+
|
||||||
| Private Key |
|
| Private Key |
|
||||||
+-----------------------+
|
+-----------------------+
|
||||||
|
| (DER-encoded PKCS#8) |
|
||||||
|
+-----------------------+
|
||||||
|
|
||||||
|oem_private_key| should be a RSA key in PKCS#8 PrivateKeyInfo format.
|
|oem_private_key| should be a RSA key in PKCS#8 PrivateKeyInfo format.
|
||||||
|oem_public_cert| should be a DER-encoded PKCS#7 certificate chain.
|
|oem_public_cert| should be a DER-encoded PKCS#7 certificate chain.
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ void advance_dest_buffer(OEMCrypto_DestBufferDesc* dest_buffer, size_t bytes) {
|
|||||||
switch (dest_buffer->type) {
|
switch (dest_buffer->type) {
|
||||||
case OEMCrypto_BufferType_Clear:
|
case OEMCrypto_BufferType_Clear:
|
||||||
dest_buffer->buffer.clear.clear_buffer += bytes;
|
dest_buffer->buffer.clear.clear_buffer += bytes;
|
||||||
dest_buffer->buffer.clear.clear_buffer_length -= bytes;
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case OEMCrypto_BufferType_Secure:
|
case OEMCrypto_BufferType_Secure:
|
||||||
@@ -99,6 +98,11 @@ OEMCryptoResult DecryptFallbackChain::DecryptSample(
|
|||||||
const size_t length =
|
const size_t length =
|
||||||
subsample.num_bytes_clear + subsample.num_bytes_encrypted;
|
subsample.num_bytes_clear + subsample.num_bytes_encrypted;
|
||||||
fake_sample.buffers.input_data_length = length;
|
fake_sample.buffers.input_data_length = length;
|
||||||
|
if (fake_sample.buffers.output_descriptor.type ==
|
||||||
|
OEMCrypto_BufferType_Clear) {
|
||||||
|
fake_sample.buffers.output_descriptor.buffer.clear.clear_buffer_length =
|
||||||
|
length;
|
||||||
|
}
|
||||||
fake_sample.subsamples = &subsample;
|
fake_sample.subsamples = &subsample;
|
||||||
fake_sample.subsamples_length = 1;
|
fake_sample.subsamples_length = 1;
|
||||||
|
|
||||||
@@ -144,6 +148,11 @@ OEMCryptoResult DecryptFallbackChain::DecryptSubsample(
|
|||||||
|
|
||||||
if (subsample.num_bytes_clear > 0) {
|
if (subsample.num_bytes_clear > 0) {
|
||||||
fake_sample.buffers.input_data_length = subsample.num_bytes_clear;
|
fake_sample.buffers.input_data_length = subsample.num_bytes_clear;
|
||||||
|
if (fake_sample.buffers.output_descriptor.type ==
|
||||||
|
OEMCrypto_BufferType_Clear) {
|
||||||
|
fake_sample.buffers.output_descriptor.buffer.clear.clear_buffer_length =
|
||||||
|
subsample.num_bytes_clear;
|
||||||
|
}
|
||||||
fake_subsample.num_bytes_clear = subsample.num_bytes_clear;
|
fake_subsample.num_bytes_clear = subsample.num_bytes_clear;
|
||||||
fake_subsample.num_bytes_encrypted = 0;
|
fake_subsample.num_bytes_encrypted = 0;
|
||||||
fake_subsample.block_offset = 0;
|
fake_subsample.block_offset = 0;
|
||||||
@@ -167,6 +176,11 @@ OEMCryptoResult DecryptFallbackChain::DecryptSubsample(
|
|||||||
|
|
||||||
if (subsample.num_bytes_encrypted > 0) {
|
if (subsample.num_bytes_encrypted > 0) {
|
||||||
fake_sample.buffers.input_data_length = subsample.num_bytes_encrypted;
|
fake_sample.buffers.input_data_length = subsample.num_bytes_encrypted;
|
||||||
|
if (fake_sample.buffers.output_descriptor.type ==
|
||||||
|
OEMCrypto_BufferType_Clear) {
|
||||||
|
fake_sample.buffers.output_descriptor.buffer.clear.clear_buffer_length =
|
||||||
|
subsample.num_bytes_encrypted;
|
||||||
|
}
|
||||||
fake_subsample.num_bytes_clear = 0;
|
fake_subsample.num_bytes_clear = 0;
|
||||||
fake_subsample.num_bytes_encrypted = subsample.num_bytes_encrypted;
|
fake_subsample.num_bytes_encrypted = subsample.num_bytes_encrypted;
|
||||||
fake_subsample.block_offset = subsample.block_offset;
|
fake_subsample.block_offset = subsample.block_offset;
|
||||||
|
|||||||
@@ -10,7 +10,9 @@
|
|||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
|
#include "log.h"
|
||||||
#include "oec_test_data.h"
|
#include "oec_test_data.h"
|
||||||
|
#include "string_conversions.h"
|
||||||
#include "test_sleep.h"
|
#include "test_sleep.h"
|
||||||
|
|
||||||
namespace wvoec {
|
namespace wvoec {
|
||||||
@@ -68,6 +70,12 @@ void DeviceFeatures::Initialize() {
|
|||||||
provisioning_method == OEMCrypto_BootCertificateChain ||
|
provisioning_method == OEMCrypto_BootCertificateChain ||
|
||||||
provisioning_method == OEMCrypto_DrmReprovisioning;
|
provisioning_method == OEMCrypto_DrmReprovisioning;
|
||||||
printf("loads_certificate = %s.\n", loads_certificate ? "true" : "false");
|
printf("loads_certificate = %s.\n", loads_certificate ? "true" : "false");
|
||||||
|
if (rsa_test_key().empty()) {
|
||||||
|
set_rsa_test_key(
|
||||||
|
std::vector<uint8_t>(kTestRSAPKCS8PrivateKeyInfo2_2048,
|
||||||
|
kTestRSAPKCS8PrivateKeyInfo2_2048 +
|
||||||
|
sizeof(kTestRSAPKCS8PrivateKeyInfo2_2048)));
|
||||||
|
}
|
||||||
generic_crypto =
|
generic_crypto =
|
||||||
(OEMCrypto_ERROR_NOT_IMPLEMENTED !=
|
(OEMCrypto_ERROR_NOT_IMPLEMENTED !=
|
||||||
OEMCrypto_Generic_Encrypt(buffer, 0, buffer, 0, iv,
|
OEMCrypto_Generic_Encrypt(buffer, 0, buffer, 0, iv,
|
||||||
@@ -129,6 +137,9 @@ void DeviceFeatures::Initialize() {
|
|||||||
case LOAD_TEST_RSA_KEY:
|
case LOAD_TEST_RSA_KEY:
|
||||||
printf("LOAD_TEST_RSA_KEY: Call LoadTestRSAKey before deriving keys.\n");
|
printf("LOAD_TEST_RSA_KEY: Call LoadTestRSAKey before deriving keys.\n");
|
||||||
break;
|
break;
|
||||||
|
case PRELOADED_RSA_KEY:
|
||||||
|
printf("PRELOADED_RSA_KEY: Device has test RSA key baked in.\n");
|
||||||
|
break;
|
||||||
case TEST_PROVISION_30:
|
case TEST_PROVISION_30:
|
||||||
printf("TEST_PROVISION_30: Device provisioned with OEM Cert.\n");
|
printf("TEST_PROVISION_30: Device provisioned with OEM Cert.\n");
|
||||||
break;
|
break;
|
||||||
@@ -175,9 +186,10 @@ void DeviceFeatures::PickDerivedKey() {
|
|||||||
return;
|
return;
|
||||||
case OEMCrypto_DrmCertificate:
|
case OEMCrypto_DrmCertificate:
|
||||||
case OEMCrypto_DrmReprovisioning:
|
case OEMCrypto_DrmReprovisioning:
|
||||||
if (OEMCrypto_ERROR_NOT_IMPLEMENTED != OEMCrypto_LoadTestRSAKey()) {
|
derive_key_method =
|
||||||
derive_key_method = LOAD_TEST_RSA_KEY;
|
(OEMCrypto_ERROR_NOT_IMPLEMENTED == OEMCrypto_LoadTestRSAKey())
|
||||||
}
|
? PRELOADED_RSA_KEY
|
||||||
|
: LOAD_TEST_RSA_KEY;
|
||||||
return;
|
return;
|
||||||
case OEMCrypto_Keybox:
|
case OEMCrypto_Keybox:
|
||||||
if (OEMCrypto_ERROR_NOT_IMPLEMENTED !=
|
if (OEMCrypto_ERROR_NOT_IMPLEMENTED !=
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ class DeviceFeatures {
|
|||||||
LOAD_TEST_RSA_KEY, // Call LoadTestRSAKey before deriving keys.
|
LOAD_TEST_RSA_KEY, // Call LoadTestRSAKey before deriving keys.
|
||||||
TEST_PROVISION_30, // Device has OEM Certificate installed.
|
TEST_PROVISION_30, // Device has OEM Certificate installed.
|
||||||
TEST_PROVISION_40, // Device has Boot Certificate Chain installed.
|
TEST_PROVISION_40, // Device has Boot Certificate Chain installed.
|
||||||
|
PRELOADED_RSA_KEY, // Device has test RSA key baked in.
|
||||||
};
|
};
|
||||||
|
|
||||||
enum DeriveMethod derive_key_method;
|
enum DeriveMethod derive_key_method;
|
||||||
@@ -70,6 +71,16 @@ class DeviceFeatures {
|
|||||||
// Get a list of output types that should be tested.
|
// Get a list of output types that should be tested.
|
||||||
const std::vector<OutputType>& GetOutputTypes();
|
const std::vector<OutputType>& GetOutputTypes();
|
||||||
|
|
||||||
|
// If the device has a baked in cert, then this is the public key that should
|
||||||
|
// be used for testing.
|
||||||
|
const std::vector<uint8_t>& rsa_test_key() const { return rsa_test_key_; };
|
||||||
|
void set_rsa_test_key(const std::vector<uint8_t>& rsa_test_key) {
|
||||||
|
rsa_test_key_ = rsa_test_key;
|
||||||
|
}
|
||||||
|
void set_rsa_test_key(std::vector<uint8_t>&& rsa_test_key) {
|
||||||
|
rsa_test_key_ = std::move(rsa_test_key);
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Decide which method should be used to derive session keys, based on
|
// Decide which method should be used to derive session keys, based on
|
||||||
// supported featuers.
|
// supported featuers.
|
||||||
@@ -82,6 +93,7 @@ class DeviceFeatures {
|
|||||||
// A list of possible output types.
|
// A list of possible output types.
|
||||||
std::vector<OutputType> output_types_;
|
std::vector<OutputType> output_types_;
|
||||||
bool initialized_ = false;
|
bool initialized_ = false;
|
||||||
|
std::vector<uint8_t> rsa_test_key_;
|
||||||
};
|
};
|
||||||
|
|
||||||
// There is one global set of features for the version of OEMCrypto being
|
// There is one global set of features for the version of OEMCrypto being
|
||||||
|
|||||||
@@ -554,7 +554,7 @@ void ProvisioningRoundTrip::VerifyLoadFailed() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Provisioning40RoundTrip::PrepareSession(bool is_oem_key) {
|
void Provisioning40RoundTrip::PrepareSession(bool is_oem_key) {
|
||||||
const size_t buffer_size = 5000; // Make sure it is large enough.
|
const size_t buffer_size = 10240; // Make sure it is large enough.
|
||||||
std::vector<uint8_t> public_key(buffer_size);
|
std::vector<uint8_t> public_key(buffer_size);
|
||||||
size_t public_key_size = buffer_size;
|
size_t public_key_size = buffer_size;
|
||||||
std::vector<uint8_t> public_key_signature(buffer_size);
|
std::vector<uint8_t> public_key_signature(buffer_size);
|
||||||
@@ -616,7 +616,7 @@ OEMCryptoResult Provisioning40RoundTrip::LoadDRMCertResponse() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Provisioning40CastRoundTrip::PrepareSession() {
|
void Provisioning40CastRoundTrip::PrepareSession() {
|
||||||
const size_t buffer_size = 5000; // Make sure it is large enough.
|
const size_t buffer_size = 10240; // Make sure it is large enough.
|
||||||
std::vector<uint8_t> public_key(buffer_size);
|
std::vector<uint8_t> public_key(buffer_size);
|
||||||
size_t public_key_size = buffer_size;
|
size_t public_key_size = buffer_size;
|
||||||
std::vector<uint8_t> public_key_signature(buffer_size);
|
std::vector<uint8_t> public_key_signature(buffer_size);
|
||||||
@@ -1918,10 +1918,9 @@ void Session::LoadOEMCert(bool verify_cert) {
|
|||||||
|
|
||||||
void Session::SetTestRsaPublicKey() {
|
void Session::SetTestRsaPublicKey() {
|
||||||
public_ec_.reset();
|
public_ec_.reset();
|
||||||
public_rsa_ = util::RsaPublicKey::LoadPrivateKeyInfo(
|
public_rsa_ =
|
||||||
kTestRSAPKCS8PrivateKeyInfo2_2048,
|
util::RsaPublicKey::LoadPrivateKeyInfo(global_features.rsa_test_key());
|
||||||
sizeof(kTestRSAPKCS8PrivateKeyInfo2_2048));
|
ASSERT_TRUE(public_rsa_) << "Could not parse test RSA public key";
|
||||||
ASSERT_TRUE(public_rsa_) << "Could not parse test RSA public key #2";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Session::SetPublicKeyFromPrivateKeyInfo(OEMCrypto_PrivateKeyType key_type,
|
void Session::SetPublicKeyFromPrivateKeyInfo(OEMCrypto_PrivateKeyType key_type,
|
||||||
|
|||||||
@@ -2,17 +2,79 @@
|
|||||||
// source code may only be used and distributed under the Widevine
|
// source code may only be used and distributed under the Widevine
|
||||||
// License Agreement.
|
// License Agreement.
|
||||||
//
|
//
|
||||||
|
|
||||||
#include "oemcrypto_basic_test.h"
|
#include "oemcrypto_basic_test.h"
|
||||||
|
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <map>
|
||||||
|
#include <ostream>
|
||||||
|
#include <set>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <jsmn.h>
|
||||||
|
|
||||||
|
#include "OEMCryptoCENC.h"
|
||||||
#include "clock.h"
|
#include "clock.h"
|
||||||
#include "jsmn.h"
|
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include "oemcrypto_corpus_generator_helper.h"
|
#include "oemcrypto_corpus_generator_helper.h"
|
||||||
#include "oemcrypto_resource_test.h"
|
#include "oemcrypto_resource_test.h"
|
||||||
#include "test_sleep.h"
|
#include "test_sleep.h"
|
||||||
|
|
||||||
|
void PrintTo(const jsmntype_t& type, std::ostream* out) {
|
||||||
|
switch (type) {
|
||||||
|
case JSMN_UNDEFINED:
|
||||||
|
*out << "Undefined";
|
||||||
|
return;
|
||||||
|
case JSMN_OBJECT:
|
||||||
|
*out << "Object";
|
||||||
|
return;
|
||||||
|
case JSMN_ARRAY:
|
||||||
|
*out << "Array";
|
||||||
|
return;
|
||||||
|
case JSMN_STRING:
|
||||||
|
*out << "String";
|
||||||
|
return;
|
||||||
|
case JSMN_PRIMITIVE:
|
||||||
|
*out << "Primitive";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
*out << "Unknown(" << static_cast<int>(type) << ')';
|
||||||
|
}
|
||||||
|
|
||||||
namespace wvoec {
|
namespace wvoec {
|
||||||
|
namespace {
|
||||||
|
// Counts the number of ancestor tokens of the provided |root_index| token.
|
||||||
|
// The result does not count the root itself.
|
||||||
|
//
|
||||||
|
// JSMN tokens specify the count of immediate ancessor tokens, but
|
||||||
|
// not the total.
|
||||||
|
// - Primitives never have children
|
||||||
|
// - Strings have 0 if they are a value, and 1 if they are the
|
||||||
|
// name of an object member
|
||||||
|
// - Objects have the count of members (each key-value pair is 1,
|
||||||
|
// regardless of the value's children elements)
|
||||||
|
// - Arrays have the count of elements (regardless of the values members)
|
||||||
|
//
|
||||||
|
int32_t JsmnAncestorCount(const std::vector<jsmntok_t>& tokens,
|
||||||
|
int32_t root_index) {
|
||||||
|
if (root_index >= static_cast<int32_t>(tokens.size())) return 0;
|
||||||
|
int32_t count = 0;
|
||||||
|
int32_t iter = root_index;
|
||||||
|
int32_t remainder = 1;
|
||||||
|
while (remainder > 0 && iter < static_cast<int32_t>(tokens.size())) {
|
||||||
|
const int32_t child_count = tokens[iter].size;
|
||||||
|
remainder += child_count;
|
||||||
|
count += child_count;
|
||||||
|
iter++;
|
||||||
|
remainder--;
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
void OEMCryptoClientTest::SetUp() {
|
void OEMCryptoClientTest::SetUp() {
|
||||||
::testing::Test::SetUp();
|
::testing::Test::SetUp();
|
||||||
wvutil::TestSleep::SyncFakeClock();
|
wvutil::TestSleep::SyncFakeClock();
|
||||||
@@ -156,7 +218,7 @@ TEST_F(OEMCryptoClientTest, FreeUnallocatedSecureBufferNoFailure) {
|
|||||||
*/
|
*/
|
||||||
TEST_F(OEMCryptoClientTest, VersionNumber) {
|
TEST_F(OEMCryptoClientTest, VersionNumber) {
|
||||||
const std::string log_message =
|
const std::string log_message =
|
||||||
"OEMCrypto unit tests for API 18.6. Tests last updated 2024-06-04";
|
"OEMCrypto unit tests for API 18.7. Tests last updated 2024-09-04";
|
||||||
cout << " " << log_message << "\n";
|
cout << " " << log_message << "\n";
|
||||||
cout << " "
|
cout << " "
|
||||||
<< "These tests are part of Android U."
|
<< "These tests are part of Android U."
|
||||||
@@ -165,7 +227,7 @@ TEST_F(OEMCryptoClientTest, VersionNumber) {
|
|||||||
// If any of the following fail, then it is time to update the log message
|
// If any of the following fail, then it is time to update the log message
|
||||||
// above.
|
// above.
|
||||||
EXPECT_EQ(ODK_MAJOR_VERSION, 18);
|
EXPECT_EQ(ODK_MAJOR_VERSION, 18);
|
||||||
EXPECT_EQ(ODK_MINOR_VERSION, 6);
|
EXPECT_EQ(ODK_MINOR_VERSION, 7);
|
||||||
EXPECT_EQ(kCurrentAPI, static_cast<unsigned>(ODK_MAJOR_VERSION));
|
EXPECT_EQ(kCurrentAPI, static_cast<unsigned>(ODK_MAJOR_VERSION));
|
||||||
OEMCrypto_Security_Level level = OEMCrypto_SecurityLevel();
|
OEMCrypto_Security_Level level = OEMCrypto_SecurityLevel();
|
||||||
EXPECT_GT(level, OEMCrypto_Level_Unknown);
|
EXPECT_GT(level, OEMCrypto_Level_Unknown);
|
||||||
@@ -286,26 +348,143 @@ TEST_F(OEMCryptoClientTest, CheckNullBuildInformationAPI17) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verifies that OEMCrypto_BuildInformation() is behaving as expected
|
||||||
|
// by assigning appropriate values to the build info size.
|
||||||
|
TEST_F(OEMCryptoClientTest, CheckBuildInformation_OutputLengthAPI17) {
|
||||||
|
constexpr size_t kZero = 0;
|
||||||
|
constexpr char kNullChar = '\0';
|
||||||
|
|
||||||
|
// Allocating single byte to avoid potential null dereference.
|
||||||
|
std::string build_info(1, kNullChar);
|
||||||
|
size_t build_info_length = 0;
|
||||||
|
|
||||||
|
OEMCryptoResult result =
|
||||||
|
OEMCrypto_BuildInformation(&build_info[0], &build_info_length);
|
||||||
|
|
||||||
|
ASSERT_EQ(result, OEMCrypto_ERROR_SHORT_BUFFER);
|
||||||
|
ASSERT_GT(build_info_length, kZero)
|
||||||
|
<< "Signaling ERROR_SHORT_BUFFER should have assigned a length";
|
||||||
|
|
||||||
|
// Force a ERROR_SHORT_BUFFER using a non-zero value.
|
||||||
|
// Note: It is assumed that vendors will provide more than a single
|
||||||
|
// character of info.
|
||||||
|
const size_t second_attempt_length =
|
||||||
|
(build_info_length >= 2) ? build_info_length / 2 : 1;
|
||||||
|
build_info.assign(second_attempt_length, kNullChar);
|
||||||
|
build_info_length = build_info.size();
|
||||||
|
|
||||||
|
result = OEMCrypto_BuildInformation(&build_info[0], &build_info_length);
|
||||||
|
ASSERT_EQ(result, OEMCrypto_ERROR_SHORT_BUFFER)
|
||||||
|
<< "second_attempt_length = " << second_attempt_length
|
||||||
|
<< ", build_info_length" << build_info_length;
|
||||||
|
// OEM specified build info length should be larger than the
|
||||||
|
// original length if returning ERROR_SHORT_BUFFER.
|
||||||
|
ASSERT_GT(build_info_length, second_attempt_length);
|
||||||
|
|
||||||
|
// Final attempt with a buffer large enough buffer, padding to
|
||||||
|
// ensure the caller truncates.
|
||||||
|
constexpr size_t kBufferPadSize = 42;
|
||||||
|
const size_t expected_length = build_info_length;
|
||||||
|
const size_t final_attempt_length = expected_length + kBufferPadSize;
|
||||||
|
build_info.assign(final_attempt_length, kNullChar);
|
||||||
|
build_info_length = build_info.size();
|
||||||
|
|
||||||
|
result = OEMCrypto_BuildInformation(&build_info[0], &build_info_length);
|
||||||
|
|
||||||
|
ASSERT_EQ(result, OEMCrypto_SUCCESS)
|
||||||
|
<< "final_attempt_length = " << final_attempt_length
|
||||||
|
<< ", expected_length = " << expected_length
|
||||||
|
<< ", build_info_length = " << build_info_length;
|
||||||
|
// Ensure not empty.
|
||||||
|
ASSERT_GT(build_info_length, kZero) << "Build info cannot be empty";
|
||||||
|
// Ensure it was truncated down from the padded length.
|
||||||
|
ASSERT_LT(build_info_length, final_attempt_length)
|
||||||
|
<< "Should have truncated from oversized buffer: expected_length = "
|
||||||
|
<< expected_length;
|
||||||
|
// Ensure the real length is within the size originally specified.
|
||||||
|
// OK if final length is smaller than estimated length.
|
||||||
|
ASSERT_LE(build_info_length, expected_length);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verifies that OEMCrypto_BuildInformation() is behaving as expected
|
||||||
|
// by checking the resulting contents.
|
||||||
|
// Does not validate whether output if valid JSON for v18.
|
||||||
|
TEST_F(OEMCryptoClientTest, CheckBuildInformation_OutputContentAPI17) {
|
||||||
|
constexpr size_t kZero = 0;
|
||||||
|
constexpr char kNullChar = '\0';
|
||||||
|
|
||||||
|
// Allocating single byte to avoid potential null dereference.
|
||||||
|
std::string build_info(1, kNullChar);
|
||||||
|
size_t build_info_length = 0;
|
||||||
|
OEMCryptoResult result =
|
||||||
|
OEMCrypto_BuildInformation(&build_info[0], &build_info_length);
|
||||||
|
ASSERT_EQ(result, OEMCrypto_ERROR_SHORT_BUFFER);
|
||||||
|
ASSERT_GT(build_info_length, kZero)
|
||||||
|
<< "Signaling ERROR_SHORT_BUFFER should have assigned a length";
|
||||||
|
|
||||||
|
// Expect successful acquisition of build information.
|
||||||
|
const size_t expected_length = build_info_length;
|
||||||
|
build_info.assign(expected_length, kNullChar);
|
||||||
|
result = OEMCrypto_BuildInformation(&build_info[0], &build_info_length);
|
||||||
|
ASSERT_EQ(result, OEMCrypto_SUCCESS)
|
||||||
|
<< "expected_length = " << expected_length
|
||||||
|
<< ", build_info_length = " << build_info_length;
|
||||||
|
// Ensure not empty.
|
||||||
|
ASSERT_GT(build_info_length, kZero) << "Build info cannot be empty";
|
||||||
|
// Ensure the real length is within the size originally specified.
|
||||||
|
ASSERT_LE(build_info_length, expected_length)
|
||||||
|
<< "Cannot specify success if buffer was too small";
|
||||||
|
build_info.resize(build_info_length);
|
||||||
|
|
||||||
|
// Ensure there isn't a trailing null byte.
|
||||||
|
ASSERT_NE(build_info.back(), kNullChar)
|
||||||
|
<< "Build info must not contain trailing null byte";
|
||||||
|
|
||||||
|
// Ensure all build info characters are printable, or a limited
|
||||||
|
// set of white space characters (case of JSON build info).
|
||||||
|
const auto is_valid_build_info_white_space = [](const char& ch) -> bool {
|
||||||
|
constexpr char kSpace = ' ';
|
||||||
|
constexpr char kLineFeed = '\n';
|
||||||
|
constexpr char kTab = '\t';
|
||||||
|
return ch == kLineFeed || ch == kTab || ch == kSpace;
|
||||||
|
};
|
||||||
|
const auto is_valid_build_info_char = [&](const char& ch) -> bool {
|
||||||
|
return ::isprint(ch) || is_valid_build_info_white_space(ch);
|
||||||
|
};
|
||||||
|
ASSERT_TRUE(std::all_of(build_info.begin(), build_info.end(),
|
||||||
|
is_valid_build_info_char))
|
||||||
|
<< "Build info is not printable: " << wvutil::b2a_hex(build_info);
|
||||||
|
|
||||||
|
// Ensure build info isn't just white space.
|
||||||
|
ASSERT_FALSE(std::all_of(build_info.begin(), build_info.end(),
|
||||||
|
is_valid_build_info_white_space))
|
||||||
|
<< "Build info is just white space: " << wvutil::b2a_hex(build_info);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_F(OEMCryptoClientTest, CheckJsonBuildInformationAPI18) {
|
TEST_F(OEMCryptoClientTest, CheckJsonBuildInformationAPI18) {
|
||||||
std::string build_info;
|
constexpr char kNullChar = '\0';
|
||||||
OEMCryptoResult sts = OEMCrypto_BuildInformation(&build_info[0], nullptr);
|
constexpr size_t kZero = 0;
|
||||||
ASSERT_EQ(OEMCrypto_ERROR_INVALID_CONTEXT, sts);
|
|
||||||
size_t buf_length = 0;
|
// Step 1: Get Build Info
|
||||||
|
size_t buffer_length = 0;
|
||||||
// OEMCrypto must allow |buffer| to be null so long as |buffer_length|
|
// OEMCrypto must allow |buffer| to be null so long as |buffer_length|
|
||||||
// is provided and initially set to zero.
|
// is provided and initially set to zero.
|
||||||
sts = OEMCrypto_BuildInformation(nullptr, &buf_length);
|
OEMCryptoResult result = OEMCrypto_BuildInformation(nullptr, &buffer_length);
|
||||||
ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, sts);
|
ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, result);
|
||||||
build_info.resize(buf_length);
|
ASSERT_GT(buffer_length, kZero);
|
||||||
const size_t max_final_size = buf_length;
|
|
||||||
sts = OEMCrypto_BuildInformation(&build_info[0], &buf_length);
|
|
||||||
ASSERT_EQ(OEMCrypto_SUCCESS, sts);
|
|
||||||
ASSERT_LE(buf_length, max_final_size);
|
|
||||||
build_info.resize(buf_length);
|
|
||||||
|
|
||||||
|
std::string build_info(buffer_length, kNullChar);
|
||||||
|
const size_t max_final_size = buffer_length;
|
||||||
|
result = OEMCrypto_BuildInformation(&build_info[0], &buffer_length);
|
||||||
|
ASSERT_EQ(OEMCrypto_SUCCESS, result);
|
||||||
|
ASSERT_LE(buffer_length, max_final_size);
|
||||||
|
build_info.resize(buffer_length);
|
||||||
|
|
||||||
|
// Step 2: Parse as JSON
|
||||||
jsmn_parser p;
|
jsmn_parser p;
|
||||||
jsmn_init(&p);
|
jsmn_init(&p);
|
||||||
std::vector<jsmntok_t> tokens;
|
std::vector<jsmntok_t> tokens;
|
||||||
int32_t num_tokens =
|
const int32_t num_tokens =
|
||||||
jsmn_parse(&p, build_info.c_str(), build_info.size(), nullptr, 0);
|
jsmn_parse(&p, build_info.c_str(), build_info.size(), nullptr, 0);
|
||||||
EXPECT_GT(num_tokens, 0)
|
EXPECT_GT(num_tokens, 0)
|
||||||
<< "Failed to parse BuildInformation as JSON, parse returned "
|
<< "Failed to parse BuildInformation as JSON, parse returned "
|
||||||
@@ -313,45 +492,186 @@ TEST_F(OEMCryptoClientTest, CheckJsonBuildInformationAPI18) {
|
|||||||
|
|
||||||
tokens.resize(num_tokens);
|
tokens.resize(num_tokens);
|
||||||
jsmn_init(&p);
|
jsmn_init(&p);
|
||||||
int32_t jsmn_result = jsmn_parse(&p, build_info.c_str(), build_info.size(),
|
const int32_t jsmn_result = jsmn_parse(
|
||||||
tokens.data(), num_tokens);
|
&p, build_info.c_str(), build_info.size(), tokens.data(), num_tokens);
|
||||||
EXPECT_GE(jsmn_result, 0)
|
EXPECT_GE(jsmn_result, 0)
|
||||||
<< "Failed to parse BuildInformation as JSON, parse returned "
|
<< "Failed to parse BuildInformation as JSON, parse returned "
|
||||||
<< jsmn_result << "for following build info: " << build_info;
|
<< jsmn_result << "for following build info: " << build_info;
|
||||||
|
|
||||||
std::map<std::string, jsmntype_t> expected;
|
// Step 3a: Ensure info is a single JSON object.
|
||||||
expected["soc_vendor"] = JSMN_STRING;
|
const jsmntok_t& object_token = tokens[0];
|
||||||
expected["soc_model"] = JSMN_STRING;
|
ASSERT_EQ(object_token.type, JSMN_OBJECT)
|
||||||
expected["ta_ver"] = JSMN_STRING;
|
<< "Build info is not a JSON object: " << build_info;
|
||||||
expected["uses_opk"] = JSMN_PRIMITIVE;
|
|
||||||
expected["tee_os"] = JSMN_STRING;
|
|
||||||
expected["tee_os_ver"] = JSMN_STRING;
|
|
||||||
|
|
||||||
// for values in token
|
// Step 3b: Verify schema of defined fields.
|
||||||
// build string from start,end
|
|
||||||
// check for existence in map
|
// Required fields must be present in the build information,
|
||||||
// check if value matches expectation
|
// and be of the correct type.
|
||||||
// remove from map
|
const std::map<std::string, jsmntype_t> kRequiredFields = {
|
||||||
for (int i = 0; i < jsmn_result; i++) {
|
// SOC manufacturer name
|
||||||
jsmntok_t token = tokens[i];
|
{"soc_vendor", JSMN_STRING},
|
||||||
std::string key = build_info.substr(token.start, token.end - token.start);
|
// SOC model name
|
||||||
if (expected.find(key) != expected.end()) {
|
{"soc_model", JSMN_STRING},
|
||||||
EXPECT_EQ(expected.find(key)->second, tokens[i + 1].type)
|
// TA version in string format eg "1.12.3+tag", "2.0"
|
||||||
<< "Type is incorrect for key " << key;
|
{"ta_ver", JSMN_STRING},
|
||||||
expected.erase(key);
|
// [bool] Whether TA was built with Widevine's OPK
|
||||||
|
{"uses_opk", JSMN_PRIMITIVE},
|
||||||
|
// Trusted OS intended to run the TA, eg "Trusty", "QSEE", "OP-TEE"
|
||||||
|
{"tee_os", JSMN_STRING},
|
||||||
|
// Version of Trusted OS intended to run the TA
|
||||||
|
{"tee_os_ver", JSMN_STRING},
|
||||||
|
// [bool] Whether this is a debug build of the TA
|
||||||
|
// Not forcing behavior until implementations fix
|
||||||
|
// them self
|
||||||
|
// {"is_debug", JSMN_PRIMITIVE},
|
||||||
|
};
|
||||||
|
|
||||||
|
const std::string kSpecialCaseReeKey = "ree";
|
||||||
|
|
||||||
|
// Optional fields may be present in the build information;
|
||||||
|
// if they are, then the must be the correct type.
|
||||||
|
const std::map<std::string, jsmntype_t> kOptionalFields = {
|
||||||
|
// Name of company or entity that provides OEMCrypto.
|
||||||
|
{"implementor", JSMN_STRING},
|
||||||
|
// Git commit hash of the code repository.
|
||||||
|
{"git_commit", JSMN_STRING},
|
||||||
|
// ISO 8601 formatted timestamp of the time the TA was compiled
|
||||||
|
{"build_timestamp", JSMN_STRING},
|
||||||
|
// Whether this was built with FACTORY_MODE_ONLY defined
|
||||||
|
{"is_factory_mode", JSMN_PRIMITIVE},
|
||||||
|
// ... provide information about liboemcrypto.so
|
||||||
|
// Special case, see kOptionalReeFields for details.
|
||||||
|
{kSpecialCaseReeKey, JSMN_OBJECT},
|
||||||
|
// Technically required, but several implementations
|
||||||
|
// do not implement this fields.
|
||||||
|
{"is_debug", JSMN_PRIMITIVE},
|
||||||
|
};
|
||||||
|
|
||||||
|
// A set of the required fields found when examining the
|
||||||
|
// build information, use to verify all fields are present.
|
||||||
|
std::set<std::string> found_required_fields;
|
||||||
|
// Stores the tokens of the "ree" field, if set, used to
|
||||||
|
// validate its content.
|
||||||
|
std::vector<jsmntok_t> ree_tokens;
|
||||||
|
bool has_ree_info = false;
|
||||||
|
|
||||||
|
// Start: first object key token
|
||||||
|
// Condition: key-value pair (2 tokens)
|
||||||
|
// Iter: next key-value pair (2 tokens)
|
||||||
|
for (int32_t i = 1; (i + 1) < jsmn_result; i += 2) {
|
||||||
|
// JSMN objects consist of pairs of key-value pairs (keys are always
|
||||||
|
// JSMN_STRING).
|
||||||
|
const jsmntok_t& key_token = tokens[i];
|
||||||
|
ASSERT_EQ(key_token.type, JSMN_STRING)
|
||||||
|
<< "Bad object key: i = " << i << ", build_info = " << build_info;
|
||||||
|
const jsmntok_t& value_token = tokens[i + 1];
|
||||||
|
|
||||||
|
const std::string key =
|
||||||
|
build_info.substr(key_token.start, key_token.end - key_token.start);
|
||||||
|
if (kRequiredFields.find(key) != kRequiredFields.end()) {
|
||||||
|
ASSERT_EQ(value_token.type, kRequiredFields.at(key))
|
||||||
|
<< "Unexpected required field type: field = " << key
|
||||||
|
<< ", build_info = " << build_info;
|
||||||
|
found_required_fields.insert(key);
|
||||||
|
} else if (kOptionalFields.find(key) != kOptionalFields.end()) {
|
||||||
|
ASSERT_EQ(value_token.type, kOptionalFields.at(key))
|
||||||
|
<< "Unexpected optional field type: field = " << key
|
||||||
|
<< ", build_info = " << build_info;
|
||||||
|
} // Do not validate vendor fields.
|
||||||
|
|
||||||
|
if (key == kSpecialCaseReeKey) {
|
||||||
|
// Store the tokens of the "ree" field for additional validation.
|
||||||
|
const int32_t first_ree_field_index = i + 2;
|
||||||
|
const int32_t ree_token_count = JsmnAncestorCount(tokens, i + 1);
|
||||||
|
const auto first_ree_field_iter = tokens.begin() + first_ree_field_index;
|
||||||
|
ree_tokens.assign(first_ree_field_iter,
|
||||||
|
first_ree_field_iter + ree_token_count);
|
||||||
|
has_ree_info = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Skip potential nested tokens.
|
||||||
|
i += JsmnAncestorCount(tokens, i + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// if map is not empty, return false
|
// Step 3c: Ensure all required fields were found.
|
||||||
if (expected.size() > 0) {
|
if (found_required_fields.size() != kRequiredFields.size()) {
|
||||||
std::string missing;
|
// Generate a list of all the missing fields.
|
||||||
for (auto e : expected) {
|
std::string missing_fields;
|
||||||
missing.append(e.first);
|
for (const auto& required_field : kRequiredFields) {
|
||||||
missing.append(" ");
|
if (found_required_fields.find(required_field.first) !=
|
||||||
|
found_required_fields.end())
|
||||||
|
continue;
|
||||||
|
if (!missing_fields.empty()) {
|
||||||
|
missing_fields.append(", ");
|
||||||
|
}
|
||||||
|
missing_fields.push_back('"');
|
||||||
|
missing_fields.append(required_field.first);
|
||||||
|
missing_fields.push_back('"');
|
||||||
}
|
}
|
||||||
FAIL() << "JSON does not contain all required keys. Missing keys: ["
|
|
||||||
<< missing << "] in string " << build_info;
|
FAIL() << "Build info JSON object does not contain all required keys; "
|
||||||
|
<< "missing_fields = [" << missing_fields
|
||||||
|
<< "], build_info = " << build_info;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If no "ree" field tokens, then end here.
|
||||||
|
if (!has_ree_info) return;
|
||||||
|
// Step 4a: Verify "ree" object scheme.
|
||||||
|
ASSERT_FALSE(ree_tokens.empty())
|
||||||
|
<< "REE field was specified, but contents were empty: build_info = "
|
||||||
|
<< build_info;
|
||||||
|
|
||||||
|
// The optional field "ree", if present, must follow the required
|
||||||
|
// format.
|
||||||
|
const std::map<std::string, jsmntype_t> kReeRequiredFields = {
|
||||||
|
// liboemcrypto.so version in string format eg "2.15.0+tag"
|
||||||
|
{"liboemcrypto_ver", JSMN_STRING},
|
||||||
|
// git hash of code that compiled liboemcrypto.so
|
||||||
|
{"git_commit", JSMN_STRING},
|
||||||
|
// ISO 8601 timestamp for when liboemcrypto.so was built
|
||||||
|
{"build_timestamp", JSMN_STRING}};
|
||||||
|
|
||||||
|
found_required_fields.clear();
|
||||||
|
for (int32_t i = 0; (i + 1) < static_cast<int32_t>(ree_tokens.size());
|
||||||
|
i += 2) {
|
||||||
|
const jsmntok_t& key_token = ree_tokens[i];
|
||||||
|
ASSERT_EQ(key_token.type, JSMN_STRING)
|
||||||
|
<< "Bad REE object key: i = " << i << ", build_info = " << build_info;
|
||||||
|
const jsmntok_t& value_token = ree_tokens[i + 1];
|
||||||
|
|
||||||
|
const std::string key =
|
||||||
|
build_info.substr(key_token.start, key_token.end - key_token.start);
|
||||||
|
if (kReeRequiredFields.find(key) != kReeRequiredFields.end()) {
|
||||||
|
ASSERT_EQ(value_token.type, kReeRequiredFields.at(key))
|
||||||
|
<< "Unexpected optional REE field type: ree_field = " << key
|
||||||
|
<< ", build_info = " << build_info;
|
||||||
|
found_required_fields.insert(key);
|
||||||
|
} // Do not validate vendor fields.
|
||||||
|
|
||||||
|
// Skip potential nested tokens.
|
||||||
|
i += JsmnAncestorCount(ree_tokens, i + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 4b: Ensure all required fields of the "ree" object were found.
|
||||||
|
if (found_required_fields.size() == kReeRequiredFields.size()) return;
|
||||||
|
// Generate a list of all the missing REE fields.
|
||||||
|
std::string missing_ree_fields;
|
||||||
|
for (const auto& required_field : kReeRequiredFields) {
|
||||||
|
if (found_required_fields.find(required_field.first) !=
|
||||||
|
found_required_fields.end())
|
||||||
|
continue;
|
||||||
|
if (!missing_ree_fields.empty()) {
|
||||||
|
missing_ree_fields.append(", ");
|
||||||
|
}
|
||||||
|
missing_ree_fields.push_back('"');
|
||||||
|
missing_ree_fields.append(required_field.first);
|
||||||
|
missing_ree_fields.push_back('"');
|
||||||
|
}
|
||||||
|
|
||||||
|
FAIL() << "REE info JSON object does not contain all required keys; "
|
||||||
|
<< "missing_ree_fields = [" << missing_ree_fields
|
||||||
|
<< "], build_info = " << build_info;
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(OEMCryptoClientTest, CheckMaxNumberOfSessionsAPI10) {
|
TEST_F(OEMCryptoClientTest, CheckMaxNumberOfSessionsAPI10) {
|
||||||
|
|||||||
@@ -121,38 +121,6 @@ TEST_P(OEMCryptoLicenseTest, RejectCensAPI16) {
|
|||||||
EXPECT_EQ(OEMCrypto_ERROR_INVALID_CONTEXT, sts);
|
EXPECT_EQ(OEMCrypto_ERROR_INVALID_CONTEXT, sts);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 'cbc1' mode is no longer supported in v16
|
|
||||||
TEST_P(OEMCryptoLicenseTest, RejectCbc1API16) {
|
|
||||||
ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest());
|
|
||||||
ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse());
|
|
||||||
ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse());
|
|
||||||
ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse());
|
|
||||||
|
|
||||||
vector<uint8_t> key_handle;
|
|
||||||
OEMCryptoResult sts;
|
|
||||||
sts = GetKeyHandleIntoVector(session_.session_id(),
|
|
||||||
session_.license().keys[0].key_id,
|
|
||||||
session_.license().keys[0].key_id_length,
|
|
||||||
OEMCrypto_CipherMode_CBCS, key_handle);
|
|
||||||
ASSERT_EQ(OEMCrypto_SUCCESS, sts);
|
|
||||||
|
|
||||||
vector<uint8_t> in_buffer(256);
|
|
||||||
vector<uint8_t> out_buffer(in_buffer.size());
|
|
||||||
OEMCrypto_SampleDescription sample_description;
|
|
||||||
OEMCrypto_SubSampleDescription subsample_description;
|
|
||||||
|
|
||||||
GenerateSimpleSampleDescription(in_buffer, out_buffer, &sample_description,
|
|
||||||
&subsample_description);
|
|
||||||
|
|
||||||
// Create a zero pattern to indicate this is 'cbc1'
|
|
||||||
OEMCrypto_CENCEncryptPatternDesc pattern = {0, 0};
|
|
||||||
|
|
||||||
// Try to decrypt the data
|
|
||||||
sts = OEMCrypto_DecryptCENC(key_handle.data(), key_handle.size(),
|
|
||||||
&sample_description, 1, &pattern);
|
|
||||||
EXPECT_EQ(OEMCrypto_ERROR_INVALID_CONTEXT, sts);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_P(OEMCryptoLicenseTest, RejectCbcsWithBlockOffset) {
|
TEST_P(OEMCryptoLicenseTest, RejectCbcsWithBlockOffset) {
|
||||||
ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest());
|
ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest());
|
||||||
ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse());
|
ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse());
|
||||||
|
|||||||
@@ -70,6 +70,9 @@ void SessionUtil::EnsureTestROT() {
|
|||||||
case DeviceFeatures::TEST_PROVISION_30:
|
case DeviceFeatures::TEST_PROVISION_30:
|
||||||
// Can use oem certificate to install test rsa key.
|
// Can use oem certificate to install test rsa key.
|
||||||
break;
|
break;
|
||||||
|
case DeviceFeatures::PRELOADED_RSA_KEY:
|
||||||
|
// There is already a key.
|
||||||
|
break;
|
||||||
case wvoec::DeviceFeatures::TEST_PROVISION_40:
|
case wvoec::DeviceFeatures::TEST_PROVISION_40:
|
||||||
// OEM certificate is retrieved from the server.
|
// OEM certificate is retrieved from the server.
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -90,6 +90,9 @@ TEST_F(OEMCryptoSessionTests, Provisioning_IncrementCounterAPI18) {
|
|||||||
// Test that successive calls to PrepAndSignLicenseRequest only increase
|
// Test that successive calls to PrepAndSignLicenseRequest only increase
|
||||||
// the license count in the ODK message
|
// the license count in the ODK message
|
||||||
TEST_F(OEMCryptoSessionTests, License_IncrementCounterAPI18) {
|
TEST_F(OEMCryptoSessionTests, License_IncrementCounterAPI18) {
|
||||||
|
if (OEMCrypto_SecurityLevel() == OEMCrypto_Level3) {
|
||||||
|
GTEST_SKIP() << "L3 does not support license counter.";
|
||||||
|
}
|
||||||
Session s;
|
Session s;
|
||||||
s.open();
|
s.open();
|
||||||
LicenseRoundTrip license_messages(&s);
|
LicenseRoundTrip license_messages(&s);
|
||||||
@@ -132,6 +135,9 @@ TEST_F(OEMCryptoSessionTests, MasterGeneration_IncrementCounterAPI18) {
|
|||||||
GTEST_SKIP() << "Usage table not supported, so master generation number "
|
GTEST_SKIP() << "Usage table not supported, so master generation number "
|
||||||
"does not need to be checked.";
|
"does not need to be checked.";
|
||||||
}
|
}
|
||||||
|
if (OEMCrypto_SecurityLevel() == OEMCrypto_Level3) {
|
||||||
|
GTEST_SKIP() << "L3 does not support license counter.";
|
||||||
|
}
|
||||||
Session s1;
|
Session s1;
|
||||||
s1.open();
|
s1.open();
|
||||||
LicenseRoundTrip license_messages(&s1);
|
LicenseRoundTrip license_messages(&s1);
|
||||||
|
|||||||
@@ -502,6 +502,12 @@ OEMCryptoResult OEMCrypto_GetOEMKeyToken(OEMCrypto_SESSION key_session UNUSED,
|
|||||||
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
OEMCryptoResult OEMCrypto_SetSessionUsage(OEMCrypto_SESSION session UNUSED,
|
||||||
|
uint32_t intent UNUSED,
|
||||||
|
uint32_t mode UNUSED) {
|
||||||
|
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
||||||
|
}
|
||||||
|
|
||||||
OEMCryptoResult OEMCrypto_GetDeviceInformation(
|
OEMCryptoResult OEMCrypto_GetDeviceInformation(
|
||||||
uint8_t* device_info UNUSED, size_t* device_info_length UNUSED) {
|
uint8_t* device_info UNUSED, size_t* device_info_length UNUSED) {
|
||||||
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
||||||
|
|||||||
Reference in New Issue
Block a user