Updated OEMCrypto JSON build information validation test.
[ Merge of http://go/wvgerrit/219213 ] Updates CheckJsonBuildInformationAPI18 to better check the contents of the JSON build information introduced in V18. Bug: 348498112 Bug: 348497732 Change-Id: I567700eb2ba451a9b10c52159d5fd30d5ae94841
This commit is contained in:
@@ -23,7 +23,58 @@
|
||||
#include "oemcrypto_resource_test.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 {
|
||||
// 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() {
|
||||
::testing::Test::SetUp();
|
||||
wvutil::TestSleep::SyncFakeClock();
|
||||
@@ -330,6 +381,10 @@ 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) {
|
||||
if (wvoec::global_features.api_version < 17) {
|
||||
GTEST_SKIP() << "Test for versions 17 and up only.";
|
||||
}
|
||||
|
||||
constexpr size_t kZero = 0;
|
||||
constexpr char kNullChar = '\0';
|
||||
|
||||
@@ -389,6 +444,10 @@ TEST_F(OEMCryptoClientTest, CheckBuildInformation_OutputLengthAPI17) {
|
||||
// by checking the resulting contents.
|
||||
// Does not validate whether output if valid JSON for v18.
|
||||
TEST_F(OEMCryptoClientTest, CheckBuildInformation_OutputContentAPI17) {
|
||||
if (wvoec::global_features.api_version < 17) {
|
||||
GTEST_SKIP() << "Test for versions 17 and up only.";
|
||||
}
|
||||
|
||||
constexpr size_t kZero = 0;
|
||||
constexpr char kNullChar = '\0';
|
||||
|
||||
@@ -444,25 +503,29 @@ TEST_F(OEMCryptoClientTest, CheckJsonBuildInformationAPI18) {
|
||||
if (wvoec::global_features.api_version < 18) {
|
||||
GTEST_SKIP() << "Test for versions 18 and up only.";
|
||||
}
|
||||
std::string build_info;
|
||||
OEMCryptoResult sts = OEMCrypto_BuildInformation(&build_info[0], nullptr);
|
||||
ASSERT_EQ(OEMCrypto_ERROR_INVALID_CONTEXT, sts);
|
||||
size_t buf_length = 0;
|
||||
constexpr char kNullChar = '\0';
|
||||
constexpr size_t kZero = 0;
|
||||
|
||||
// Step 1: Get Build Info
|
||||
size_t buffer_length = 0;
|
||||
// OEMCrypto must allow |buffer| to be null so long as |buffer_length|
|
||||
// is provided and initially set to zero.
|
||||
sts = OEMCrypto_BuildInformation(nullptr, &buf_length);
|
||||
ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, sts);
|
||||
build_info.resize(buf_length);
|
||||
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);
|
||||
OEMCryptoResult result = OEMCrypto_BuildInformation(nullptr, &buffer_length);
|
||||
ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, result);
|
||||
ASSERT_GT(buffer_length, kZero);
|
||||
|
||||
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_init(&p);
|
||||
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);
|
||||
EXPECT_GT(num_tokens, 0)
|
||||
<< "Failed to parse BuildInformation as JSON, parse returned "
|
||||
@@ -470,45 +533,186 @@ TEST_F(OEMCryptoClientTest, CheckJsonBuildInformationAPI18) {
|
||||
|
||||
tokens.resize(num_tokens);
|
||||
jsmn_init(&p);
|
||||
int32_t jsmn_result = jsmn_parse(&p, build_info.c_str(), build_info.size(),
|
||||
tokens.data(), num_tokens);
|
||||
const int32_t jsmn_result = jsmn_parse(
|
||||
&p, build_info.c_str(), build_info.size(), tokens.data(), num_tokens);
|
||||
EXPECT_GE(jsmn_result, 0)
|
||||
<< "Failed to parse BuildInformation as JSON, parse returned "
|
||||
<< jsmn_result << "for following build info: " << build_info;
|
||||
|
||||
std::map<std::string, jsmntype_t> expected;
|
||||
expected["soc_vendor"] = JSMN_STRING;
|
||||
expected["soc_model"] = JSMN_STRING;
|
||||
expected["ta_ver"] = JSMN_STRING;
|
||||
expected["uses_opk"] = JSMN_PRIMITIVE;
|
||||
expected["tee_os"] = JSMN_STRING;
|
||||
expected["tee_os_ver"] = JSMN_STRING;
|
||||
// Step 3a: Ensure info is a single JSON object.
|
||||
const jsmntok_t& object_token = tokens[0];
|
||||
ASSERT_EQ(object_token.type, JSMN_OBJECT)
|
||||
<< "Build info is not a JSON object: " << build_info;
|
||||
|
||||
// for values in token
|
||||
// build string from start,end
|
||||
// check for existence in map
|
||||
// check if value matches expectation
|
||||
// remove from map
|
||||
for (int32_t i = 0; i < jsmn_result; i++) {
|
||||
jsmntok_t token = tokens[i];
|
||||
std::string key = build_info.substr(token.start, token.end - token.start);
|
||||
if (expected.find(key) != expected.end()) {
|
||||
EXPECT_EQ(expected.find(key)->second, tokens[i + 1].type)
|
||||
<< "Type is incorrect for key " << key;
|
||||
expected.erase(key);
|
||||
// Step 3b: Verify schema of defined fields.
|
||||
|
||||
// Required fields must be present in the build information,
|
||||
// and be of the correct type.
|
||||
const std::map<std::string, jsmntype_t> kRequiredFields = {
|
||||
// SOC manufacturer name
|
||||
{"soc_vendor", JSMN_STRING},
|
||||
// SOC model name
|
||||
{"soc_model", JSMN_STRING},
|
||||
// TA version in string format eg "1.12.3+tag", "2.0"
|
||||
{"ta_ver", JSMN_STRING},
|
||||
// [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
|
||||
if (expected.size() > 0) {
|
||||
std::string missing;
|
||||
for (const auto& e : expected) {
|
||||
missing.append(e.first);
|
||||
missing.append(" ");
|
||||
// Step 3c: Ensure all required fields were found.
|
||||
if (found_required_fields.size() != kRequiredFields.size()) {
|
||||
// Generate a list of all the missing fields.
|
||||
std::string missing_fields;
|
||||
for (const auto& required_field : kRequiredFields) {
|
||||
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) {
|
||||
|
||||
Reference in New Issue
Block a user