Merge recent doc changes for OEMCrypto

This is a cherry pick of recent changes to OEMCrypto and ODK. Most of
these are part of the document migration to doxygen.

See http://go/wvgerrit/106005 and its parents for code reviews.

Bug: 144715340
Bug: 148232693
Bug: 167580674
Change-Id: I658f99c8117b974faed97322d61fac0f382283af
This commit is contained in:
Fred Gylys-Colwell
2020-09-11 13:30:58 -07:00
parent 28b13ef65e
commit 20bb84ffee
75 changed files with 5717 additions and 4488 deletions

View File

@@ -31,8 +31,11 @@
#include "core_message_serialize.h"
#include "disallow_copy_and_assign.h"
#include "log.h"
#include "odk_attributes.h"
#include "odk_structs.h"
#include "oec_device_features.h"
#include "oec_test_data.h"
#include "oemcrypto_corpus_generator_helper.h"
#include "oemcrypto_types.h"
#include "platform.h"
#include "string_conversions.h"
@@ -162,6 +165,10 @@ void RoundTrip<CoreRequest, PrepAndSignRequest, CoreResponse,
std::max(required_message_size_, core_message_length + small_size);
data.resize(message_size);
for (size_t i = 0; i < data.size(); i++) data[i] = i & 0xFF;
if (ShouldGenerateCorpus()) {
WriteRequestApiCorpus<CoreRequest>(gen_signature_length,
core_message_length, data);
}
vector<uint8_t> gen_signature(gen_signature_length);
ASSERT_EQ(PrepAndSignRequest(session()->session_id(), data.data(),
@@ -177,6 +184,27 @@ void RoundTrip<CoreRequest, PrepAndSignRequest, CoreResponse,
VerifyRequestSignature(data, gen_signature, core_message_length);
}
template <class CoreRequest, PrepAndSignRequest_t PrepAndSignRequest,
class CoreResponse, class ResponseData>
void RoundTrip<CoreRequest, PrepAndSignRequest, CoreResponse,
ResponseData>::InjectFuzzedRequestData(uint8_t* data,
size_t size) {
OEMCrypto_Request_Fuzz fuzz_structure;
// Copy data into fuzz structure, cap signature length at 1mb as it will be
// used to initialize signature vector.
memcpy(&fuzz_structure, data, sizeof(fuzz_structure));
fuzz_structure.signature_length =
std::min(fuzz_structure.signature_length, MB);
vector<uint8_t> signature(fuzz_structure.signature_length);
// Interpret rest of data as actual message buffer to request APIs.
uint8_t* message_ptr = data + sizeof(fuzz_structure);
size_t message_size = size - sizeof(fuzz_structure);
PrepAndSignRequest(session()->session_id(), message_ptr, message_size,
&fuzz_structure.core_message_length, signature.data(),
&fuzz_structure.signature_length);
}
template <class CoreRequest, PrepAndSignRequest_t PrepAndSignRequest,
class CoreResponse, class ResponseData>
OEMCrypto_Substring RoundTrip<CoreRequest, PrepAndSignRequest, CoreResponse,
@@ -309,8 +337,35 @@ void ProvisioningRoundTrip::EncryptAndSignResponse() {
&response_signature_);
}
void ProvisioningRoundTrip::InjectFuzzedResponseData(const uint8_t* data,
size_t size UNUSED) {
// Interpreting fuzz data as unencrypted core_response + message_data
const size_t core_response_size = sizeof(ODK_ParsedProvisioning);
// Copy core_response from data and serialize.
memcpy(&core_response_, data, core_response_size);
// Copy provisioning message data into response_data.
memcpy(&response_data_, data + core_response_size, sizeof(response_data_));
// Set nonce to one from session to pass nonce checks.
response_data_.nonce = session()->nonce();
}
OEMCryptoResult ProvisioningRoundTrip::LoadResponse(Session* session) {
EXPECT_NE(session, nullptr);
// Write corpus for oemcrypto_load_provisioning_fuzz. Fuzz script expects
// unencrypted response from provisioning server as input corpus data.
// Data will be encrypted and signed again explicitly by fuzzer script after
// mutations.
if (ShouldGenerateCorpus()) {
const std::string file_name =
GetFileName("oemcrypto_load_provisioning_fuzz_seed_corpus");
// Corpus for license response fuzzer should be in the format:
// unencrypted (core_response + response_data).
AppendToFile(file_name, reinterpret_cast<const char*>(&core_response_),
sizeof(ODK_ParsedProvisioning));
AppendToFile(file_name, reinterpret_cast<const char*>(&response_data_),
sizeof(response_data_));
}
size_t wrapped_key_length = 0;
const OEMCryptoResult sts = LoadResponseNoRetry(session, &wrapped_key_length);
if (sts != OEMCrypto_ERROR_SHORT_BUFFER) return sts;
@@ -472,6 +527,69 @@ void LicenseRoundTrip::CreateDefaultResponse() {
FillCoreResponseSubstrings();
}
void LicenseRoundTrip::ConvertDataToValidBools(ODK_ParsedLicense* t) {
t->nonce_required = ConvertByteToValidBoolean(&t->nonce_required);
t->timer_limits.soft_enforce_playback_duration = ConvertByteToValidBoolean(
&t->timer_limits.soft_enforce_playback_duration);
t->timer_limits.soft_enforce_rental_duration =
ConvertByteToValidBoolean(&t->timer_limits.soft_enforce_rental_duration);
}
void LicenseRoundTrip::InjectFuzzedTimerLimits(
OEMCrypto_Renewal_Response_Fuzz& fuzzed_data) {
// Interpreting fuzz data as timer limits.
// Copy timer limits from data.
memcpy(&core_response_.timer_limits, &fuzzed_data.timer_limits,
sizeof(fuzzed_data.timer_limits));
ConvertDataToValidBools(&core_response_);
}
void LicenseRoundTrip::InjectFuzzedResponseData(const uint8_t* data,
size_t size UNUSED) {
// Interpreting fuzz data as unencrypted core_response + message_data
const size_t core_response_size = sizeof(ODK_ParsedLicense);
// Copy core_response from data.
memcpy(&core_response_, data, core_response_size);
// Maximum number of keys could be kMaxNumKeys(30). key_array_length can be
// any random value as it is read from fuzz data.
// Key data array(MessageKeyData keys[kMaxNumKeys]) will be looped over
// key_array_length number of times during LoadLicense. If key_array_length is
// more than kMaxNumKeys, setting it to max value of kMaxNumKeys as we should
// not go out of bounds of this array length. For corpus, this value is
// already hard coded to 4.
if (core_response_.key_array_length > kMaxNumKeys) {
core_response_.key_array_length = kMaxNumKeys;
}
// For corpus data, this value gets set to 4, but we need to test other
// scenarios too, hence reading key_array_length value.
set_num_keys(core_response_.key_array_length);
ConvertDataToValidBools(&core_response_);
// TODO(b/157520981): Once assertion bug is fixed, for loop can be removed.
// Workaround for the above bug: key_data.length and key_id.length are being
// used in AES decryption process and are expected to be a multiple of 16. An
// assertion in AES decryption fails if this condition is not met which will
// crash fuzzer.
for (uint32_t i = 0; i < num_keys_; ++i) {
size_t key_data_length = core_response_.key_array[i].key_data.length;
size_t key_id_length = core_response_.key_array[i].key_id.length;
if (key_data_length % 16 != 0) {
core_response_.key_array[i].key_data.length =
key_data_length - (key_data_length % 16);
}
if (key_id_length % 16 != 0) {
core_response_.key_array[i].key_id.length =
key_id_length - (key_id_length % 16);
}
}
// Copy response_data from data and set nonce to match one in request to pass
// nonce validations.
memcpy(&response_data_, data + core_response_size, sizeof(response_data_));
for (uint32_t i = 0; i < num_keys_; ++i) {
response_data_.keys[i].control.nonce = session()->nonce();
}
}
void LicenseRoundTrip::CreateResponseWithGenericCryptoKeys() {
CreateDefaultResponse();
response_data_.keys[0].control.control_bits |=
@@ -540,17 +658,28 @@ void LicenseRoundTrip::EncryptAndSignResponse() {
2 * MAC_KEY_SIZE, response_data_.mac_key_iv);
for (unsigned int i = 0; i < num_keys_; i++) {
memcpy(iv_buffer, &response_data_.keys[i].control_iv[0], KEY_IV_SIZE);
AES_KEY aes_key;
AES_set_encrypt_key(&response_data_.keys[i].key_data[0], 128, &aes_key);
AES_cbc_encrypt(
reinterpret_cast<const uint8_t*>(&response_data_.keys[i].control),
reinterpret_cast<uint8_t*>(&encrypted_response_data_.keys[i].control),
KEY_SIZE, &aes_key, iv_buffer, AES_ENCRYPT);
session_->key_deriver().CBCEncrypt(
&response_data_.keys[i].key_data[0],
&encrypted_response_data_.keys[i].key_data[0],
response_data_.keys[i].key_data_length, response_data_.keys[i].key_iv);
// OEMCrypto Fuzzing skip encryption: key_data_length can be any number when
// called from fuzzer. We want to skip encryption if that happens and let
// LoadLicense be called with unencrypted data for that key. OEMCrypto
// Fuzzing skip encryption: key_data_length being a random value will
// encrypt data which is not expected to, there by leading to inefficient
// fuzzing.
if (response_data_.keys[i].key_data_length <=
sizeof(response_data_.keys[i].key_data) &&
response_data_.keys[i].key_data_length % 16 == 0) {
memcpy(iv_buffer, &response_data_.keys[i].control_iv[0], KEY_IV_SIZE);
AES_KEY aes_key;
AES_set_encrypt_key(&response_data_.keys[i].key_data[0], 128, &aes_key);
AES_cbc_encrypt(
reinterpret_cast<const uint8_t*>(&response_data_.keys[i].control),
reinterpret_cast<uint8_t*>(&encrypted_response_data_.keys[i].control),
KEY_SIZE, &aes_key, iv_buffer, AES_ENCRYPT);
session_->key_deriver().CBCEncrypt(
&response_data_.keys[i].key_data[0],
&encrypted_response_data_.keys[i].key_data[0],
response_data_.keys[i].key_data_length,
response_data_.keys[i].key_iv);
}
}
if (api_version_ < kCoreMessagesAPI) {
serialized_core_message_.resize(0);
@@ -588,6 +717,21 @@ void LicenseRoundTrip::EncryptAndSignResponse() {
OEMCryptoResult LicenseRoundTrip::LoadResponse(Session* session) {
EXPECT_NE(session, nullptr);
// Write corpus for oemcrypto_load_license_fuzz. Fuzz script expects
// unecnrypted response from license server as input corpus data.
// Data will be encrypted and signed again explicitly by fuzzer script
// after mutations.
if (ShouldGenerateCorpus()) {
const std::string file_name =
GetFileName("oemcrypto_load_license_fuzz_seed_corpus");
// Corpus for license response fuzzer should be in the format:
// core_response + response_data.
AppendToFile(file_name, reinterpret_cast<const char*>(&core_response_),
sizeof(ODK_ParsedLicense));
AppendToFile(file_name, reinterpret_cast<const char*>(&response_data_),
sizeof(response_data_));
}
// Some tests adjust the offset to be beyond the length of the message. Here,
// we create a duplicate of the main message buffer so that these offsets do
// not point to garbage data. The goal is to make sure OEMCrypto is verifying
@@ -757,6 +901,17 @@ void EntitledMessage::LoadKeys(OEMCryptoResult expected_sts) {
key_data->encrypted_content_key_data, KEY_SIZE, &aes_key,
iv, AES_ENCRYPT);
}
if (ShouldGenerateCorpus()) {
const std::string file_name =
GetFileName("oemcrypto_load_entitled_content_keys_fuzz_seed_corpus");
// Corpus for load entitled keys fuzzer should be in the format:
// message buffer to be verified | entitled content key object array.
AppendToFile(file_name, reinterpret_cast<const char*>(entitled_key_data_),
sizeof(entitled_key_data_));
AppendSeparator(file_name);
AppendToFile(file_name, reinterpret_cast<const char*>(entitled_key_array_),
num_keys_);
}
ASSERT_EQ(expected_sts,
OEMCrypto_LoadEntitledContentKeys(
license_messages_->session()->session_id(),
@@ -906,7 +1061,46 @@ void RenewalRoundTrip::EncryptAndSignResponse() {
&response_signature_);
}
void RenewalRoundTrip::InjectFuzzedResponseData(
OEMCrypto_Renewal_Response_Fuzz& fuzzed_data,
const uint8_t* renewal_response, const size_t renewal_response_size) {
// Serializing core message.
// This call also sets nonce in core response to match with session nonce.
oemcrypto_core_message::serialize::CreateCoreRenewalResponse(
fuzzed_data.core_request, fuzzed_data.renewal_duration_seconds,
&serialized_core_message_);
// Copy serialized core message and encrypted response from data and
// calculate signature. Now we will have a valid signature for data generated
// by fuzzer.
encrypted_response_.assign(serialized_core_message_.begin(),
serialized_core_message_.end());
encrypted_response_.insert(encrypted_response_.end(), renewal_response,
renewal_response + renewal_response_size);
session()->key_deriver().ServerSignBuffer(encrypted_response_.data(),
encrypted_response_.size(),
&response_signature_);
}
OEMCryptoResult RenewalRoundTrip::LoadResponse(Session* session) {
// Write corpus for oemcrypto_load_renewal_fuzz. Fuzz script expects
// encrypted response from Renewal server as input corpus data.
// Data will be signed again explicitly by fuzzer script after mutations.
if (ShouldGenerateCorpus()) {
const std::string file_name =
GetFileName("oemcrypto_load_renewal_fuzz_seed_corpus");
// Corpus for renewal response fuzzer should be in the format:
// OEMCrypto_Renewal_Response_Fuzz + license_renewal_response.
OEMCrypto_Renewal_Response_Fuzz renewal_response_fuzz;
renewal_response_fuzz.core_request = core_request_;
renewal_response_fuzz.renewal_duration_seconds = renewal_duration_seconds_;
AppendToFile(file_name,
reinterpret_cast<const char*>(&renewal_response_fuzz),
sizeof(renewal_response_fuzz));
AppendToFile(file_name,
reinterpret_cast<const char*>(&encrypted_response_data_),
sizeof(encrypted_response_data_));
}
if (license_messages_->api_version() < kCoreMessagesAPI) {
return OEMCrypto_RefreshKeys(
session->session_id(), encrypted_response_.data(),
@@ -1129,10 +1323,9 @@ void Session::TestDecryptResult(OEMCryptoResult expected_result,
void Session::TestSelectExpired(unsigned int key_index) {
if (global_features.api_version >= 13) {
OEMCryptoResult status =
OEMCrypto_SelectKey(session_id(), license().keys[key_index].key_id,
license().keys[key_index].key_id_length,
OEMCrypto_CipherMode_CTR);
OEMCryptoResult status = OEMCrypto_SelectKey(
session_id(), license().keys[key_index].key_id,
license().keys[key_index].key_id_length, OEMCrypto_CipherMode_CTR);
// It is OK for SelectKey to succeed with an expired key, but if there is
// an error, it must be OEMCrypto_ERROR_KEY_EXIRED.
if (status != OEMCrypto_SUCCESS) {
@@ -1200,9 +1393,9 @@ void Session::LoadOEMCert(bool verify_cert) {
// TODO(fredgc): Verify cert is signed by Google.
int result = X509_verify_cert(store_ctx.get());
ASSERT_GE(0, result) << " OEM Cert not valid. " <<
X509_verify_cert_error_string(
X509_STORE_CTX_get_error(store_ctx.get()));
ASSERT_GE(0, result) << " OEM Cert not valid. "
<< X509_verify_cert_error_string(
X509_STORE_CTX_get_error(store_ctx.get()));
if (result == 0) {
printf("Cert not verified: %s.\n",
X509_verify_cert_error_string(
@@ -1258,7 +1451,7 @@ bool Session::VerifyPSSSignature(EVP_PKEY* pkey, const uint8_t* message,
}
if (EVP_PKEY_CTX_set_signature_md(pkey_ctx,
const_cast<EVP_MD *>(EVP_sha1())) != 1) {
const_cast<EVP_MD*>(EVP_sha1())) != 1) {
LOGE("EVP_PKEY_CTX_set_signature_md failed in VerifyPSSSignature");
goto err;
}
@@ -1307,18 +1500,17 @@ void Session::VerifyRSASignature(const vector<uint8_t>& message,
boringssl_ptr<EVP_PKEY, EVP_PKEY_free> pkey(EVP_PKEY_new());
ASSERT_EQ(1, EVP_PKEY_set1_RSA(pkey.get(), public_rsa_));
const bool ok = VerifyPSSSignature(
pkey.get(), message.data(), message.size(), signature,
signature_length);
const bool ok =
VerifyPSSSignature(pkey.get(), message.data(), message.size(),
signature, signature_length);
EXPECT_TRUE(ok) << "PSS signature check failed.";
} else if (padding_scheme == kSign_PKCS1_Block1) {
vector<uint8_t> padded_digest(signature_length);
int size;
// RSA_public_decrypt decrypts the signature, and then verifies that
// it was padded with RSA PKCS1 padding.
size = RSA_public_decrypt(
signature_length, signature, padded_digest.data(), public_rsa_,
RSA_PKCS1_PADDING);
size = RSA_public_decrypt(signature_length, signature, padded_digest.data(),
public_rsa_, RSA_PKCS1_PADDING);
EXPECT_GT(size, 0);
padded_digest.resize(size);
EXPECT_EQ(message, padded_digest);
@@ -1384,16 +1576,14 @@ void Session::UpdateUsageEntry(std::vector<uint8_t>* header_buffer) {
void Session::LoadUsageEntry(uint32_t index, const vector<uint8_t>& buffer) {
usage_entry_number_ = index;
encrypted_usage_entry_ = buffer;
ASSERT_EQ(
OEMCrypto_SUCCESS,
OEMCrypto_LoadUsageEntry(
session_id(), index, buffer.data(), buffer.size()));
ASSERT_EQ(OEMCrypto_SUCCESS,
OEMCrypto_LoadUsageEntry(session_id(), index, buffer.data(),
buffer.size()));
}
void Session::MoveUsageEntry(uint32_t new_index,
std::vector<uint8_t>* header_buffer,
OEMCryptoResult expect_result) {
ASSERT_NO_FATAL_FAILURE(open());
ASSERT_NO_FATAL_FAILURE(ReloadUsageEntry());
ASSERT_EQ(expect_result, OEMCrypto_MoveEntry(session_id(), new_index));
@@ -1405,8 +1595,7 @@ void Session::MoveUsageEntry(uint32_t new_index,
}
void Session::GenerateReport(const std::string& pst,
OEMCryptoResult expected_result,
Session* other) {
OEMCryptoResult expected_result, Session* other) {
ASSERT_TRUE(open_);
if (other) { // If other is specified, copy mac keys.
key_deriver_ = other->key_deriver_;
@@ -1442,14 +1631,13 @@ void Session::GenerateReport(const std::string& pst,
void Session::VerifyPST(const Test_PST_Report& expected) {
wvcdm::Unpacked_PST_Report computed = pst_report();
EXPECT_EQ(expected.status, computed.status());
char* pst_ptr = reinterpret_cast<char *>(computed.pst());
char* pst_ptr = reinterpret_cast<char*>(computed.pst());
std::string computed_pst(pst_ptr, pst_ptr + computed.pst_length());
ASSERT_EQ(expected.pst, computed_pst);
int64_t now = wvcdm::Clock().GetCurrentTime();
int64_t age = now - expected.time_created; // How old is this report.
EXPECT_NEAR(expected.seconds_since_license_received + age,
computed.seconds_since_license_received(),
kTimeTolerance);
computed.seconds_since_license_received(), kTimeTolerance);
// Decrypt times only valid on licenses that have been active.
if (expected.status == kActive || expected.status == kInactiveUsed) {
EXPECT_NEAR(expected.seconds_since_first_decrypt + age,
@@ -1461,8 +1649,8 @@ void Session::VerifyPST(const Test_PST_Report& expected) {
}
std::vector<uint8_t> signature(SHA_DIGEST_LENGTH);
key_deriver_.ClientSignPstReport(pst_report_buffer_, &signature);
EXPECT_EQ(0, memcmp(computed.signature(), signature.data(),
SHA_DIGEST_LENGTH));
EXPECT_EQ(0,
memcmp(computed.signature(), signature.data(), SHA_DIGEST_LENGTH));
}
void Session::VerifyReport(Test_PST_Report expected,
@@ -1484,4 +1672,41 @@ void Session::VerifyReport(Test_PST_Report expected,
: 0;
ASSERT_NO_FATAL_FAILURE(VerifyPST(expected));
}
bool ConvertByteToValidBoolean(const bool* in) {
const char* buf = reinterpret_cast<const char*>(in);
for (size_t i = 0; i < sizeof(bool); i++) {
if (buf[i]) {
return true;
}
}
return false;
}
template <class CoreRequest>
void WriteRequestApiCorpus(size_t signature_length, size_t core_message_length,
vector<uint8_t>& data) {
std::string file_name;
if (std::is_same<CoreRequest,
oemcrypto_core_message::ODK_LicenseRequest>::value) {
file_name = GetFileName("oemcrypto_license_request_fuzz_seed_corpus");
} else if (std::is_same<
CoreRequest,
oemcrypto_core_message::ODK_ProvisioningRequest>::value) {
file_name = GetFileName("oemcrypto_provisioning_request_fuzz_seed_corpus");
} else if (std::is_same<CoreRequest,
oemcrypto_core_message::ODK_RenewalRequest>::value) {
file_name = GetFileName("oemcrypto_renewal_request_fuzz_seed_corpus");
} else {
LOGE("Invalid CoreRequest type while writing request api corups.");
}
// Corpus for request APIs should be signature_length + core_message_length +
// data pointer.
AppendToFile(file_name, reinterpret_cast<const char*>(&signature_length),
sizeof(signature_length));
AppendToFile(file_name, reinterpret_cast<const char*>(&core_message_length),
sizeof(core_message_length));
AppendToFile(file_name, reinterpret_cast<const char*>(data.data()),
data.size());
}
} // namespace wvoec