// Copyright 2022 Google LLC. All Rights Reserved. This file and proprietary // source code may only be used and distributed under the Widevine // License Agreement. // #include "oemcrypto_basic_test.h" #include #include #include #include #include #include #include #include #include #include "OEMCryptoCENC.h" #include "clock.h" #include "log.h" #include "oemcrypto_corpus_generator_helper.h" #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(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& tokens, int32_t root_index) { if (root_index >= static_cast(tokens.size())) return 0; int32_t count = 0; int32_t iter = root_index; int32_t remainder = 1; while (remainder > 0 && iter < static_cast(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(); const ::testing::TestInfo* const test_info = ::testing::UnitTest::GetInstance()->current_test_info(); LOGD("Running test %s.%s", test_info->test_case_name(), test_info->name()); OEMCrypto_SetSandbox(kTestSandbox, sizeof(kTestSandbox)); ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_Initialize()); const OEMCryptoResult api_status = OEMCrypto_SetMaxAPIVersion(kCurrentAPI); OEMCrypto_EnterTestMode(); if (api_status != OEMCrypto_SUCCESS && api_status != OEMCrypto_ERROR_NOT_IMPLEMENTED) { // Log error, but continue assuming no error. LOGE("OEMCrypto_SetMaxAPIVersion returned %d", api_status); } } void OEMCryptoClientTest::TearDown() { OEMCrypto_Terminate(); ::testing::Test::TearDown(); } const uint8_t* OEMCryptoClientTest::find(const vector& message, const vector& substring) { vector::const_iterator pos = search( message.begin(), message.end(), substring.begin(), substring.end()); if (pos == message.end()) { return nullptr; } return &(*pos); } OEMCryptoResult OEMCryptoClientTest::CopyBuffer( OEMCrypto_SESSION session, OEMCrypto_SharedMemory* input_buffer, size_t input_buffer_size, const OEMCrypto_DestBufferDesc* dest_buffer_descriptor, uint8_t subsample_flags) { if (ShouldGenerateCorpus() && input_buffer != nullptr && dest_buffer_descriptor != nullptr) { const std::string file_name = GetFileName("oemcrypto_copy_buffer_fuzz_seed_corpus"); OEMCrypto_Copy_Buffer_Fuzz fuzzed_structure; fuzzed_structure.dest_buffer_desc.type = dest_buffer_descriptor->type; switch (fuzzed_structure.dest_buffer_desc.type) { case OEMCrypto_BufferType_Clear: fuzzed_structure.dest_buffer_desc.buffer_config = dest_buffer_descriptor->buffer.clear.clear_buffer_length; break; case OEMCrypto_BufferType_Secure: fuzzed_structure.dest_buffer_desc.buffer_config = dest_buffer_descriptor->buffer.secure.secure_buffer_length; break; case OEMCrypto_BufferType_Direct: fuzzed_structure.dest_buffer_desc.buffer_config = dest_buffer_descriptor->buffer.direct.is_video; break; } fuzzed_structure.subsample_flags = subsample_flags; // Corpus for copy buffer fuzzer should be in the format: // (dest_buffer_descriptor | subsample_flags | input_buffer). AppendToFile(file_name, reinterpret_cast(&fuzzed_structure), sizeof(fuzzed_structure)); AppendToFile(file_name, reinterpret_cast(&input_buffer), input_buffer_size); } return OEMCrypto_CopyBuffer(session, input_buffer, input_buffer_size, dest_buffer_descriptor, subsample_flags); } const char* HDCPCapabilityAsString(OEMCrypto_HDCP_Capability value) { switch (value) { case HDCP_NONE: return "No HDCP supported, no secure data path"; case HDCP_V1: return "HDCP version 1.x"; case HDCP_V1_0: return "HDCP version 1.0"; case HDCP_V1_1: return "HDCP version 1.1"; case HDCP_V1_2: return "HDCP version 1.2"; case HDCP_V1_3: return "HDCP version 1.3"; case HDCP_V1_4: return "HDCP version 1.4"; case HDCP_V2: return "HDCP version 2.0"; case HDCP_V2_1: return "HDCP version 2.1"; case HDCP_V2_2: return "HDCP version 2.2"; case HDCP_V2_3: return "HDCP version 2.3"; case HDCP_NO_DIGITAL_OUTPUT: return "No HDCP device attached/using local display with secure path"; default: return ""; } } // Return a printable string from data. If all the characters are printable, // then just use the string. Otherwise, convert to hex. std::string MaybeHex(const uint8_t* data, size_t length) { // Check for a early null termination. This is common for the device // id in a keybox, which is padded with 0s. const size_t c_len = strnlen(reinterpret_cast(data), length); // If there is any nonzero after the first zero, then just use hex. for (size_t i = c_len; i < length; i++) { if (data[i] != 0) return "0x" + wvutil::HexEncode(data, length); } for (size_t i = 0; i < c_len; i++) { if (!isprint(data[i])) return "0x" + wvutil::HexEncode(data, length); } return std::string(reinterpret_cast(data), c_len); } std::string MaybeHex(const std::vector& data) { return MaybeHex(data.data(), data.size()); } // Get the Device's ID and return it in a printable format. std::string GetDeviceId() { OEMCryptoResult sts; std::vector dev_id(128, 0); size_t dev_id_len = dev_id.size(); sts = OEMCrypto_GetDeviceID(dev_id.data(), &dev_id_len); if (sts == OEMCrypto_ERROR_SHORT_BUFFER) { if (dev_id_len <= 0) return "NO DEVICE ID"; dev_id.resize(dev_id_len); sts = OEMCrypto_GetDeviceID(dev_id.data(), &dev_id_len); } if (sts != OEMCrypto_SUCCESS) return "NO DEVICE ID"; dev_id.resize(dev_id_len); return MaybeHex(dev_id); } /// @addtogroup basic /// @{ TEST_F(OEMCryptoClientTest, FreeUnallocatedSecureBufferNoFailure) { Session s; s.open(); OEMCrypto_DestBufferDesc output_descriptor = {}; int secure_fd = kHugeRandomNumber; ASSERT_NE(OEMCrypto_SUCCESS, OEMCrypto_FreeSecureBuffer(s.session_id(), &output_descriptor, secure_fd)); s.close(); } /** * Verifies initialization and logs version information. * This test is first, because it might give an idea why other * tests are failing when the device has the wrong keybox installed. * * The log message should be updated by Widevine with every release so that it * is easier to verify which version of the tests a partner is running. Widevine * should change the API version number when the API changes, but the unit tests * might be updated more frequently, and are only tracked by the date of the * last change. */ TEST_F(OEMCryptoClientTest, VersionNumber) { const std::string log_message = "OEMCrypto unit tests for API 19.1. Tests last updated 2024-03-25"; cout << " " << log_message << "\n"; cout << " " << "These tests are part of Android U." << "\n"; LOGI("%s", log_message.c_str()); // If any of the following fail, then it is time to update the log message // above. EXPECT_EQ(ODK_MAJOR_VERSION, 19); EXPECT_EQ(ODK_MINOR_VERSION, 1); EXPECT_EQ(kCurrentAPI, static_cast(ODK_MAJOR_VERSION)); OEMCrypto_Security_Level level = OEMCrypto_SecurityLevel(); EXPECT_GT(level, OEMCrypto_Level_Unknown); EXPECT_LE(level, OEMCrypto_Level3); cout << " OEMCrypto Security Level is L" << level << endl; uint32_t version = OEMCrypto_APIVersion(); uint32_t minor_version = OEMCrypto_MinorAPIVersion(); cout << " OEMCrypto API version is " << version << "." << minor_version << endl; cout << " OEMCrypto Device ID is '" << GetDeviceId() << "'" << endl; if (OEMCrypto_SupportsUsageTable()) { cout << " OEMCrypto supports usage tables" << endl; } else { cout << " OEMCrypto does not support usage tables" << endl; } if (version >= 15) { const uint32_t tier = OEMCrypto_ResourceRatingTier(); cout << " Resource Rating Tier: " << tier << endl; } if (version >= 17) { OEMCryptoResult sts = OEMCrypto_ProductionReady(); if (sts != OEMCrypto_SUCCESS) { LOGW("Device is not production ready, returns %d", sts); } std::string build_info; size_t buf_length = 0; sts = OEMCrypto_BuildInformation(&build_info[0], &buf_length); if (sts == OEMCrypto_ERROR_SHORT_BUFFER) { build_info.resize(buf_length); sts = OEMCrypto_BuildInformation(&build_info[0], &buf_length); } ASSERT_EQ(OEMCrypto_SUCCESS, sts); if (build_info.size() != buf_length) { build_info.resize(buf_length); } const std::string comma = ","; const std::string pretty_comma = ",\n "; std::string::size_type pos = 0; while ((pos = build_info.find(comma, pos)) != std::string::npos) { build_info.replace(pos, comma.size(), pretty_comma); pos += pretty_comma.size(); } cout << " BuildInformation: " << build_info << endl; OEMCrypto_WatermarkingSupport support = OEMCrypto_GetWatermarkingSupport(); cout << " WatermarkingSupport: " << support << endl; } ASSERT_GE(version, 8u); ASSERT_LE(version, kCurrentAPI); } /** * The resource rating is a number from 1 to 4. The first three levels * were initially defined in API 15 and they were expanded in API 16. */ TEST_F(OEMCryptoClientTest, ResourceRatingAPI15) { ASSERT_GE(OEMCrypto_ResourceRatingTier(), 1u); ASSERT_LE(OEMCrypto_ResourceRatingTier(), 4u); } /** * OEMCrypto must declare what type of provisioning scheme it uses. */ TEST_F(OEMCryptoClientTest, ProvisioningDeclaredAPI12) { OEMCrypto_ProvisioningMethod provisioning_method = OEMCrypto_GetProvisioningMethod(); cout << " Provisioning method = " << ProvisioningMethodName(provisioning_method) << endl; ASSERT_NE(OEMCrypto_ProvisioningError, provisioning_method); } TEST_F(OEMCryptoClientTest, CheckHDCPCapabilityAPI09) { OEMCryptoResult sts; OEMCrypto_HDCP_Capability current, maximum; sts = OEMCrypto_GetHDCPCapability(¤t, &maximum); ASSERT_EQ(OEMCrypto_SUCCESS, sts); printf(" Current HDCP Capability: 0x%02x = %s.\n", static_cast(current), HDCPCapabilityAsString(current)); printf(" Maximum HDCP Capability: 0x%02x = %s.\n", static_cast(maximum), HDCPCapabilityAsString(maximum)); } TEST_F(OEMCryptoClientTest, CheckSRMCapabilityV13) { // This just tests some trivial functionality of the SRM update functions. uint16_t version = 0; OEMCryptoResult current_result = OEMCrypto_GetCurrentSRMVersion(&version); if (current_result == OEMCrypto_SUCCESS) { printf(" Current SRM Version: %d.\n", version); EXPECT_NE(OEMCrypto_SUCCESS, OEMCrypto_GetCurrentSRMVersion(nullptr)); } else if (current_result == OEMCrypto_LOCAL_DISPLAY_ONLY) { printf(" Current SRM Status: Local Display Only.\n"); } else { EXPECT_EQ(OEMCrypto_ERROR_NOT_IMPLEMENTED, current_result); } } TEST_F(OEMCryptoClientTest, CheckNullBuildInformationAPI17) { if (wvoec::global_features.api_version < 17) { GTEST_SKIP() << "Test for versions 17 and up only."; } OEMCryptoResult sts; std::string build_info; sts = OEMCrypto_BuildInformation(&build_info[0], nullptr); ASSERT_EQ(OEMCrypto_ERROR_INVALID_CONTEXT, sts); size_t buf_length = 0; sts = OEMCrypto_BuildInformation(nullptr, &buf_length); // Previous versions of the test expected the wrong error code. // Although OEMCrypto_ERROR_INVALID_CONTEXT is still accepted by // the tests, vendors should return OEMCrypto_ERROR_SHORT_BUFFER if // |buffer| is null and |buf_length| is zero, assigning // the correct length to |buf_length|. // TODO(231514699): Remove case for ERROR_INVALID_CONTEXT. ASSERT_TRUE(OEMCrypto_ERROR_SHORT_BUFFER == sts || OEMCrypto_ERROR_INVALID_CONTEXT == sts); if (sts == OEMCrypto_ERROR_INVALID_CONTEXT) { printf( "Warning: OEMCrypto_BuildInformation should return " "ERROR_SHORT_BUFFER.\n"); } if (sts == OEMCrypto_ERROR_SHORT_BUFFER) { constexpr size_t kZero = 0; ASSERT_GT(buf_length, kZero); } } // 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'; // 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"; // Try again using the size they provided, ensuring that it // is successful. const size_t initial_estimate_length = build_info_length; build_info.assign(build_info_length, kNullChar); result = OEMCrypto_BuildInformation(&build_info[0], &build_info_length); ASSERT_EQ(result, OEMCrypto_SUCCESS) << "initial_estimate_length = " << initial_estimate_length << ", build_info_length (output) = " << build_info_length; ASSERT_GT(build_info_length, kZero) << "Build info cannot be empty"; // Ensure the real length is within the size originally specified. // OK if final length is smaller than estimated length. ASSERT_LE(build_info_length, initial_estimate_length); const size_t expected_length = build_info_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 short_length = (expected_length >= 2) ? expected_length / 2 : 1; build_info.assign(short_length, kNullChar); build_info_length = build_info.size(); result = OEMCrypto_BuildInformation(&build_info[0], &build_info_length); ASSERT_EQ(result, OEMCrypto_ERROR_SHORT_BUFFER) << "short_length = " << short_length << ", expected_length = " << expected_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, short_length); // Final attempt with a buffer large enough buffer, padding to // ensure the caller truncates. constexpr size_t kBufferPadSize = 42; const size_t oversize_length = expected_length + kBufferPadSize; build_info.assign(oversize_length, kNullChar); build_info_length = build_info.size(); result = OEMCrypto_BuildInformation(&build_info[0], &build_info_length); ASSERT_EQ(result, OEMCrypto_SUCCESS) << "oversize_length = " << oversize_length << ", expected_length = " << expected_length << ", build_info_length (output) = " << 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, oversize_length) << "Should have truncated from oversized buffer: expected_length = " << expected_length; // Ensure that length is equal to the length of the previous // successful call. ASSERT_EQ(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) { 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'; // 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) { if (wvoec::global_features.api_version < 18) { GTEST_SKIP() << "Test for versions 18 and up only."; } 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. 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 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 " << num_tokens << "for following build info: " << build_info; tokens.resize(num_tokens); jsmn_init(&p); 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; // 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; // 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 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 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 found_required_fields; // Stores the tokens of the "ree" field, if set, used to // validate its content. std::vector 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); } // 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() << "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 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(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) { size_t sessions_count; ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_GetNumberOfOpenSessions(&sessions_count)); ASSERT_EQ(0u, sessions_count); size_t maximum; OEMCryptoResult sts = OEMCrypto_GetMaxNumberOfSessions(&maximum); ASSERT_EQ(OEMCrypto_SUCCESS, sts); printf(" Max Number of Sessions: %zu.\n", maximum); size_t required_max = GetResourceValue(kMaxConcurrentSession); ASSERT_GE(maximum, required_max); } TEST_F(OEMCryptoClientTest, CheckUsageTableSizeAPI16) { const size_t maximum = OEMCrypto_MaximumUsageTableHeaderSize(); printf(" Max Usage Table Size: %zu.\n", maximum); // A maximum of 0 means the table is constrained by dynamic memory allocation. if (maximum > 0) { ASSERT_GE(maximum, RequiredUsageSize()); } } // // initialization tests // TEST_F(OEMCryptoClientTest, NormalInitTermination) { // Should be able to terminate OEMCrypto, and then restart it. ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_Terminate()); OEMCrypto_SetSandbox(kTestSandbox, sizeof(kTestSandbox)); ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_Initialize()); (void)OEMCrypto_SetMaxAPIVersion(kCurrentAPI); (void)OEMCrypto_EnterTestMode(); } TEST_F(OEMCryptoClientTest, CheckDTCP2CapabilityAPI17) { if (wvoec::global_features.api_version < 17) { GTEST_SKIP() << "Test for versions 17 and up only."; } OEMCryptoResult sts; OEMCrypto_DTCP2_Capability capability; sts = OEMCrypto_GetDTCP2Capability(&capability); ASSERT_EQ(OEMCrypto_SUCCESS, sts); switch (capability) { case OEMCrypto_NO_DTCP2: printf(" Current DTCP Support: DTCP2 not supported.\n"); break; case OEMCrypto_DTCP2_V1: printf( " Current DTCP Support: Version 1 (or higher) of " "DTCP2 is supported.\n"); break; } } // // Session Tests // TEST_F(OEMCryptoClientTest, NormalSessionOpenClose) { Session s; ASSERT_NO_FATAL_FAILURE(s.open()); ASSERT_NO_FATAL_FAILURE(s.close()); } TEST_F(OEMCryptoClientTest, TwoSessionsOpenClose) { Session s1; Session s2; ASSERT_NO_FATAL_FAILURE(s1.open()); ASSERT_NO_FATAL_FAILURE(s2.open()); ASSERT_NO_FATAL_FAILURE(s1.close()); ASSERT_NO_FATAL_FAILURE(s2.close()); } // This test verifies that OEMCrypto can open approximately as many sessions as // it claims. TEST_F(OEMCryptoClientTest, MaxSessionsOpenCloseAPI10) { size_t sessions_count; ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_GetNumberOfOpenSessions(&sessions_count)); ASSERT_EQ(0u, sessions_count); size_t max_sessions; ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_GetMaxNumberOfSessions(&max_sessions)); // We expect OEMCrypto implementations support at least this many sessions. size_t required_number = GetResourceValue(kMaxConcurrentSession); ASSERT_GE(max_sessions, required_number); // We allow GetMaxNumberOfSessions to return an estimate. This tests with a // pad of 5%. Even if it's just an estimate, we still require 8 sessions. size_t max_sessions_with_pad = max(max_sessions * 19 / 20, required_number); vector sessions; // Limit the number of sessions for testing. const size_t kMaxNumberOfSessionsForTesting = 0x100u; for (size_t i = 0; i < kMaxNumberOfSessionsForTesting; i++) { OEMCrypto_SESSION session_id; OEMCryptoResult sts = OEMCrypto_OpenSession(&session_id); // GetMaxNumberOfSessions might be an estimate. We allow OEMCrypto to report // a max that is less than what is actually supported. Assume the number // returned is |max|. OpenSessions shall not fail if number of active // sessions is less than |max|; OpenSessions should fail with // OEMCrypto_ERROR_TOO_MANY_SESSIONS if too many sessions are open. if (sts != OEMCrypto_SUCCESS) { ASSERT_EQ(OEMCrypto_ERROR_TOO_MANY_SESSIONS, sts); ASSERT_GE(i, max_sessions_with_pad); break; } ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_GetNumberOfOpenSessions(&sessions_count)); ASSERT_EQ(i + 1, sessions_count); sessions.push_back(session_id); } for (size_t i = 0; i < sessions.size(); i++) { ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_CloseSession(sessions[i])); ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_GetNumberOfOpenSessions(&sessions_count)); ASSERT_EQ(sessions.size() - i - 1, sessions_count); } if (sessions.size() == kMaxNumberOfSessionsForTesting) { printf( " MaxSessionsOpenClose: reaches " "kMaxNumberOfSessionsForTesting(%zu). GetMaxNumberOfSessions = %zu. " "ERROR_TOO_MANY_SESSIONS not tested.", kMaxNumberOfSessionsForTesting, max_sessions); } } TEST_F(OEMCryptoClientTest, GenerateNonce) { Session s; ASSERT_NO_FATAL_FAILURE(s.open()); s.GenerateNonce(); } // Prevent a nonce flood even if each nonce is in a different session. TEST_F(OEMCryptoClientTest, PreventNonceFlood2API16) { int error_counter = 0; const int64_t test_start = wvutil::Clock().GetCurrentTime(); // More than 200 nonces per second should generate an error. // To allow for some slop, we actually test for more. const int flood_cutoff = 200; const int loop_count = flood_cutoff * 2; for (int i = 0; i < loop_count; i++) { Session s; ASSERT_NO_FATAL_FAILURE(s.open()); s.GenerateNonce(&error_counter); } const int64_t test_end = wvutil::Clock().GetCurrentTime(); int valid_counter = loop_count - error_counter; // Either oemcrypto should enforce a delay, or it should return an error from // GenerateNonce -- in either case the number of valid nonces is rate // limited. We add two seconds to allow for round off error in both // test_start and test_end. EXPECT_LE(valid_counter, flood_cutoff * (test_end - test_start + 2)); error_counter = 0; // After a pause, we should be able to regenerate nonces. wvutil::TestSleep::Sleep(2); Session s; ASSERT_NO_FATAL_FAILURE(s.open()); s.GenerateNonce(&error_counter); EXPECT_EQ(0, error_counter); } // Prevent a nonce flood even if some nonces are in a different session. This // is different from the test above because there are several session open at // the same time. We want to make sure you can't get a flood of nonces by // opening a flood of sessions. TEST_F(OEMCryptoClientTest, PreventNonceFlood3API16) { int request_counter = 0; int error_counter = 0; const int64_t test_start = wvutil::Clock().GetCurrentTime(); // More than 200 nonces per second should generate an error. // To allow for some slop, we actually test for more. const int flood_cutoff = 200; const size_t session_count = GetResourceValue(kMaxConcurrentSession); const size_t loop_count = 2 * flood_cutoff / session_count + 1; for (size_t i = 0; i < loop_count; i++) { std::vector s(session_count); for (size_t j = 0; j < session_count; j++) { ASSERT_NO_FATAL_FAILURE(s[j].open()); request_counter++; s[j].GenerateNonce(&error_counter); } } const int64_t test_end = wvutil::Clock().GetCurrentTime(); int valid_counter = request_counter - error_counter; // Either oemcrypto should enforce a delay, or it should return an error from // GenerateNonce -- in either case the number of valid nonces is rate // limited. We add two seconds to allow for round off error in both // test_start and test_end. EXPECT_LE(valid_counter, flood_cutoff * (test_end - test_start + 2)); error_counter = 0; // After a pause, we should be able to regenerate nonces. wvutil::TestSleep::Sleep(2); Session s; ASSERT_NO_FATAL_FAILURE(s.open()); s.GenerateNonce(&error_counter); EXPECT_EQ(0, error_counter); } // This verifies that CopyBuffer works, even before a license has been loaded. TEST_F(OEMCryptoClientTest, ClearCopyTestAPI10) { Session s; ASSERT_NO_FATAL_FAILURE(s.open()); const int kDataSize = 256; vector input_buffer(kDataSize); GetRandBytes(input_buffer.data(), input_buffer.size()); vector output_buffer(kDataSize); OEMCrypto_DestBufferDesc dest_buffer_descriptor; dest_buffer_descriptor.type = OEMCrypto_BufferType_Clear; dest_buffer_descriptor.buffer.clear.clear_buffer = output_buffer.data(); dest_buffer_descriptor.buffer.clear.clear_buffer_length = output_buffer.size(); ASSERT_EQ(OEMCrypto_SUCCESS, CopyBuffer(s.session_id(), input_buffer.data(), input_buffer.size(), &dest_buffer_descriptor, OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample)); ASSERT_EQ(input_buffer, output_buffer); ASSERT_EQ(OEMCrypto_ERROR_INVALID_CONTEXT, CopyBuffer(s.session_id(), nullptr, input_buffer.size(), &dest_buffer_descriptor, OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample)); ASSERT_EQ( OEMCrypto_ERROR_INVALID_CONTEXT, CopyBuffer(s.session_id(), input_buffer.data(), input_buffer.size(), nullptr, OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample)); dest_buffer_descriptor.buffer.clear.clear_buffer = nullptr; ASSERT_EQ(OEMCrypto_ERROR_INVALID_CONTEXT, CopyBuffer(s.session_id(), input_buffer.data(), input_buffer.size(), &dest_buffer_descriptor, OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample)); dest_buffer_descriptor.buffer.clear.clear_buffer = output_buffer.data(); dest_buffer_descriptor.buffer.clear.clear_buffer_length = output_buffer.size() - 1; ASSERT_NE(OEMCrypto_SUCCESS, CopyBuffer(s.session_id(), input_buffer.data(), input_buffer.size(), &dest_buffer_descriptor, OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample)); } // This verifies that CopyBuffer works on the maximum required buffer size. TEST_F(OEMCryptoClientTest, ClearCopyTestLargeSubsample) { Session s; ASSERT_NO_FATAL_FAILURE(s.open()); size_t max_size = GetResourceValue(kMaxSubsampleSize); vector input_buffer(max_size); GetRandBytes(input_buffer.data(), input_buffer.size()); vector output_buffer(max_size); OEMCrypto_DestBufferDesc dest_buffer_descriptor; dest_buffer_descriptor.type = OEMCrypto_BufferType_Clear; dest_buffer_descriptor.buffer.clear.clear_buffer = output_buffer.data(); dest_buffer_descriptor.buffer.clear.clear_buffer_length = output_buffer.size(); ASSERT_EQ(OEMCrypto_SUCCESS, CopyBuffer(s.session_id(), input_buffer.data(), input_buffer.size(), &dest_buffer_descriptor, OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample)); ASSERT_EQ(input_buffer, output_buffer); } TEST_F(OEMCryptoClientTest, OEMCryptoMemoryCopyBufferForOutOfRangeHandleLength) { Session s; ASSERT_NO_FATAL_FAILURE(s.open()); vector input_buffer; OEMCrypto_DestBufferDesc dest_buffer_descriptor; dest_buffer_descriptor.type = OEMCrypto_BufferType_Secure; size_t buffer_length = KiB; input_buffer.resize(buffer_length); int secure_fd; if (OEMCrypto_AllocateSecureBuffer(s.session_id(), buffer_length, &dest_buffer_descriptor, &secure_fd) != OEMCrypto_SUCCESS) { LOGI("Secure buffers are not supported."); return; } dest_buffer_descriptor.buffer.secure.secure_buffer_length = kHugeInputBufferLength; ASSERT_NO_FATAL_FAILURE( OEMCrypto_CopyBuffer(s.session_id(), input_buffer.data(), buffer_length, &dest_buffer_descriptor, OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample)); OEMCrypto_FreeSecureBuffer(s.session_id(), &dest_buffer_descriptor, secure_fd); } TEST_F(OEMCryptoClientTest, ClearCopyTestInvalidSubsampleFlag) { uint8_t oemcrypto_invalid_subsample_flag = 85; Session s; ASSERT_NO_FATAL_FAILURE(s.open()); size_t max_size = GetResourceValue(kMaxSubsampleSize); vector input_buffer(max_size); GetRandBytes(input_buffer.data(), input_buffer.size()); vector output_buffer(max_size); OEMCrypto_DestBufferDesc dest_buffer_descriptor; dest_buffer_descriptor.type = OEMCrypto_BufferType_Clear; dest_buffer_descriptor.buffer.clear.clear_buffer = output_buffer.data(); dest_buffer_descriptor.buffer.clear.clear_buffer_length = output_buffer.size(); ASSERT_NO_FATAL_FAILURE( CopyBuffer(s.session_id(), input_buffer.data(), input_buffer.size(), &dest_buffer_descriptor, oemcrypto_invalid_subsample_flag)); } TEST_F(OEMCryptoClientTest, CanLoadTestKeys) { ASSERT_NE(DeviceFeatures::NO_METHOD, global_features.derive_key_method) << "Session tests cannot run with out a test keybox or RSA cert."; } /// @} } // namespace wvoec