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 "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();
|
||||||
@@ -330,6 +381,10 @@ TEST_F(OEMCryptoClientTest, CheckNullBuildInformationAPI17) {
|
|||||||
// Verifies that OEMCrypto_BuildInformation() is behaving as expected
|
// Verifies that OEMCrypto_BuildInformation() is behaving as expected
|
||||||
// by assigning appropriate values to the build info size.
|
// by assigning appropriate values to the build info size.
|
||||||
TEST_F(OEMCryptoClientTest, CheckBuildInformation_OutputLengthAPI17) {
|
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 size_t kZero = 0;
|
||||||
constexpr char kNullChar = '\0';
|
constexpr char kNullChar = '\0';
|
||||||
|
|
||||||
@@ -389,6 +444,10 @@ TEST_F(OEMCryptoClientTest, CheckBuildInformation_OutputLengthAPI17) {
|
|||||||
// by checking the resulting contents.
|
// by checking the resulting contents.
|
||||||
// Does not validate whether output if valid JSON for v18.
|
// Does not validate whether output if valid JSON for v18.
|
||||||
TEST_F(OEMCryptoClientTest, CheckBuildInformation_OutputContentAPI17) {
|
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 size_t kZero = 0;
|
||||||
constexpr char kNullChar = '\0';
|
constexpr char kNullChar = '\0';
|
||||||
|
|
||||||
@@ -444,25 +503,29 @@ TEST_F(OEMCryptoClientTest, CheckJsonBuildInformationAPI18) {
|
|||||||
if (wvoec::global_features.api_version < 18) {
|
if (wvoec::global_features.api_version < 18) {
|
||||||
GTEST_SKIP() << "Test for versions 18 and up only.";
|
GTEST_SKIP() << "Test for versions 18 and up only.";
|
||||||
}
|
}
|
||||||
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 "
|
||||||
@@ -470,45 +533,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 (int32_t 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 (const 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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user