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:
Alex Dale
2025-04-18 12:20:22 -07:00
parent 53b1b0ebc0
commit 25380c8832

View File

@@ -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) {