blob: c8917a3fce256a6d3be7a4cbb605e9bb8c11994c [file] [log] [blame]
// Copyright 2020 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <stdint.h>
#include <cstring>
#include <string>
#include <vector>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/logging.h>
#include <brillo/secure_blob.h>
#include <crypto/scoped_openssl_types.h>
#include <fuzzer/FuzzedDataProvider.h>
#include <openssl/bio.h>
#include <openssl/err.h>
#include <openssl/pem.h>
#include <openssl/rsa.h>
#include <openssl/x509.h>
#include <trousers/trousers.h>
#include <trousers/tss.h>
#include "cryptohome/cryptolib.h"
#include "cryptohome/fuzzers/blob_mutator.h"
#include "cryptohome/signature_sealing_backend_tpm1_impl.h"
using brillo::Blob;
using brillo::BlobFromString;
using brillo::CombineBlobs;
using brillo::SecureBlob;
using crypto::ScopedRSA;
using cryptohome::CryptoLib;
namespace {
// Cryptographic constants:
constexpr int kCmkKeySizeBits = 2048;
constexpr int kCmkKeySizeBytes = kCmkKeySizeBits / 8;
constexpr int kCmkPrivateKeySizeBytes = kCmkKeySizeBytes / 2;
constexpr int kMigratedCmkPrivateKeySeedPartSizeBytes = SHA_DIGEST_LENGTH - 4;
constexpr int kMigratedCmkPrivateKeyRestPartSizeBytes =
kCmkPrivateKeySizeBytes - kMigratedCmkPrivateKeySeedPartSizeBytes;
constexpr int kIntermediateOaepEncodingBytes =
198; // Determined by |kCmkKeySizeBits| and the size of
// |Environment.migration_destination_rsa|.
// The maximum number of additional bytes that the fuzzer can generate when
// doing a blob mutation in the typical case.
constexpr int kFuzzingExtraSizeDelta = 10;
constexpr char kStaticFilesPath[] = "/usr/libexec/fuzzers/";
struct Environment {
Environment();
ScopedRSA cmk_rsa;
ScopedRSA migration_destination_rsa;
};
struct ScopedOpensslErrorClearer {
~ScopedOpensslErrorClearer() { ERR_clear_error(); }
};
ScopedRSA LoadRsaPrivateKeyFromPemFile(const base::FilePath& pem_file_path) {
std::string pem_data;
CHECK(base::ReadFileToString(pem_file_path, &pem_data));
crypto::ScopedBIO pem_data_bio(
BIO_new_mem_buf(pem_data.data(), pem_data.size()));
CHECK(pem_data_bio);
crypto::ScopedRSA rsa(PEM_read_bio_RSAPrivateKey(
pem_data_bio.get(), /*RSA **x=*/nullptr, /*pem_password_cb *cb=*/nullptr,
/*void *u=*/nullptr));
CHECK(rsa);
return rsa;
}
Environment::Environment()
: cmk_rsa(LoadRsaPrivateKeyFromPemFile(
base::FilePath(kStaticFilesPath)
.AppendASCII("cryptohome_fuzzer_key_rsa_2048_1"))),
migration_destination_rsa(LoadRsaPrivateKeyFromPemFile(
base::FilePath(kStaticFilesPath)
.AppendASCII("cryptohome_fuzzer_key_rsa_2048_2"))) {
logging::SetMinLogLevel(logging::LOGGING_FATAL);
}
// Returns a mutated RSA-OAEP encrypted blob of the given plaintext.
Blob FuzzedRsaOaepEncrypt(const Blob& plaintext,
const Blob& oaep_label,
RSA* rsa,
FuzzedDataProvider* fuzzed_data_provider) {
// Explicitly do the padding step first, in order to be able to mutate its
// result before the actual RSA operation.
Blob padded_blob(RSA_size(rsa));
RSA_padding_add_PKCS1_OAEP_mgf1(
padded_blob.data(), padded_blob.size(), plaintext.data(),
plaintext.size(), oaep_label.data(), oaep_label.size(), nullptr, nullptr);
Blob fuzzed_padded_blob =
MutateBlob(padded_blob, RSA_size(rsa), fuzzed_data_provider);
fuzzed_padded_blob.resize(RSA_size(rsa));
Blob ciphertext(RSA_size(rsa));
RSA_public_encrypt(fuzzed_padded_blob.size(), fuzzed_padded_blob.data(),
ciphertext.data(), rsa, RSA_NO_PADDING);
return MutateBlob(ciphertext, RSA_size(rsa) + kFuzzingExtraSizeDelta,
fuzzed_data_provider);
}
// Creates a four-byte blob containing the specified integer in the TPM
// endianness.
Blob EncodeTpmUint32(uint32_t value) {
UINT64 dumping_offset = 0;
Blob encoded_uint32(4);
Trspi_LoadBlob_UINT32(&dumping_offset, value,
const_cast<BYTE*>(encoded_uint32.data()));
CHECK_EQ(4, dumping_offset);
return encoded_uint32;
}
// Creates the blob of the TPM_PUBKEY structure that holds information about the
// given RSA public key.
Blob BuildRsaTpmPubkeyBlob(const RSA& rsa) {
Blob modulus(RSA_size(&rsa));
const BIGNUM* n;
RSA_get0_key(&rsa, &n, nullptr, nullptr);
if (BN_bn2bin(n, modulus.data()) != modulus.size())
return Blob();
// Build the TPM_RSA_KEY_PARMS structure.
TPM_RSA_KEY_PARMS rsa_key_parms;
rsa_key_parms.keyLength = RSA_size(&rsa) * 8;
rsa_key_parms.numPrimes = 2;
// The default exponent is assumed.
rsa_key_parms.exponentSize = 0;
rsa_key_parms.exponent = nullptr;
// Convert the TPM_RSA_KEY_PARMS structure into blob.
UINT64 offset = 0;
Trspi_LoadBlob_RSA_KEY_PARMS(&offset, nullptr, &rsa_key_parms);
Blob rsa_key_parms_blob(offset);
offset = 0;
Trspi_LoadBlob_RSA_KEY_PARMS(&offset, rsa_key_parms_blob.data(),
&rsa_key_parms);
CHECK_EQ(offset, rsa_key_parms_blob.size());
// Build the TPM_PUBKEY structure.
TPM_PUBKEY pubkey;
pubkey.algorithmParms.algorithmID = TPM_ALG_RSA;
pubkey.algorithmParms.encScheme = 0;
pubkey.algorithmParms.sigScheme = 0;
pubkey.algorithmParms.parmSize = rsa_key_parms_blob.size();
pubkey.algorithmParms.parms = rsa_key_parms_blob.data();
pubkey.pubKey.keyLength = modulus.size();
pubkey.pubKey.key = const_cast<BYTE*>(modulus.data());
// Convert the TPM_PUBKEY structure blob into blob.
offset = 0;
Trspi_LoadBlob_PUBKEY(&offset, nullptr, &pubkey);
Blob pubkey_blob(offset);
offset = 0;
Trspi_LoadBlob_PUBKEY(&offset, pubkey_blob.data(), &pubkey);
CHECK_EQ(offset, pubkey_blob.size());
return pubkey_blob;
}
// Returns the MGF1 mask of the given size built from the given input value.
Blob GetOaepMgf1Mask(const Blob& mgf_input_value, size_t mask_size) {
Blob mask(mask_size);
TSS_RESULT tss_result = Trspi_MGF1(TSS_HASH_SHA1, mgf_input_value.size(),
const_cast<BYTE*>(mgf_input_value.data()),
mask.size(), mask.data());
CHECK_EQ(tss_result, TSS_SUCCESS);
return mask;
}
// Performs the mutated RSA OAEP MGF1 encoding of the given |message| using the
// OAEP parameters |oaep_label| and |seed|.
// Note that this custom implementation is used instead of the one from OpenSSL,
// because we need to be able to supply a custom seed.
Blob FuzzedOaepMgf1Encode(const Blob& message,
const Blob& oaep_label,
const Blob& seed,
size_t encoded_message_length,
FuzzedDataProvider* fuzzed_data_provider) {
// The comments in this function below refer to the notation that corresponds
// to the "RSAES-OAEP Encryption Scheme" Algorithm specification and
// supporting documentation (2000), the "EME-OAEP-Decode" section.
// The correspondence between the function parameters and the terms in the
// specification is:
// * |message| - "M";
// * |message.size()| - "mLen";
// * |oaep_label| - "P";
// * |seed| - "seed";
// * |encoded_message_length| - "emLen".
// Note that as the MGF1 mask is used which is based on SHA-1, the "hLen" term
// corresponds to |SHA_DIGEST_LENGTH|.
// Step #1 is omitted as not applicable to our implementation - the length of
// |oaep_label| can't realistically reach the size constraint of SHA-1.
// Step #2. Unlike in the original, truncate the message if it's too long, in
// order to simplify the fuzzer.
size_t message_length = message.size();
if (message.size() + 2 * SHA_DIGEST_LENGTH + 1 > encoded_message_length)
message_length = encoded_message_length - 2 * SHA_DIGEST_LENGTH - 1;
// Step #3. Generate "PS".
const Blob zeroes_padding(encoded_message_length - message_length -
2 * SHA_DIGEST_LENGTH - 1);
// Step #4. Generate "pHash".
const Blob oaep_label_digest = cryptohome::CryptoLib::Sha1(oaep_label);
// Step #5. Generate "DB".
const Blob padded_message =
CombineBlobs({oaep_label_digest, zeroes_padding, Blob(1, 1),
Blob(message.begin(), message.begin() + message_length)});
// Step #6 is skipped since the seed is passed as an input.
// Step #7. Generate "dbMask".
const Blob padded_message_mask =
GetOaepMgf1Mask(/*mgf_input_value=*/seed,
/*mask_size=*/padded_message.size());
// Step #8. Generate "maskedDB".
Blob masked_padded_message = padded_message;
for (size_t i = 0; i < masked_padded_message.size(); ++i)
masked_padded_message[i] ^= padded_message_mask[i];
// Step #9. Generate "seedMask".
const Blob seed_mask =
GetOaepMgf1Mask(/*mgf_input_value=*/masked_padded_message,
/*mask_size=*/seed.size());
// Step #10. Generate "maskedSeed".
Blob masked_seed = seed;
for (size_t i = 0; i < masked_seed.size(); ++i)
masked_seed[i] ^= seed_mask[i];
// Step #11. Generate "EM".
const Blob encoded_message =
CombineBlobs({masked_seed, masked_padded_message});
CHECK_EQ(encoded_message.size(), encoded_message_length);
return MutateBlob(encoded_message, encoded_message_length,
fuzzed_data_provider);
}
// Prepares mutated arguments for the ExtractCmkPrivateKeyFromMigratedBlob()
// function: |key12_blob|, |migration_random_blob|, |cmk_pubkey|,
// |fuzzed_cmk_pubkey_digest|, |fuzzed_msa_composite_digest|. The returned
// values are based off valid values with some mutations applied.
void PrepareMutatedArguments(const RSA& cmk_rsa,
RSA* migration_destination_rsa,
FuzzedDataProvider* fuzzed_data_provider,
Blob* key12_blob,
Blob* migration_random_blob,
Blob* cmk_pubkey,
Blob* fuzzed_cmk_pubkey_digest,
Blob* fuzzed_msa_composite_digest) {
// Build the |fuzzed_cmk_secret_prime| temporary value.
const BIGNUM* cmk_p;
RSA_get0_factors(&cmk_rsa, &cmk_p, nullptr);
Blob cmk_secret_prime(BN_num_bytes(cmk_p));
CHECK_GE(BN_bn2bin(cmk_p, cmk_secret_prime.data()), 0);
Blob fuzzed_cmk_secret_prime = MutateBlob(
cmk_secret_prime, cmk_secret_prime.size() + kFuzzingExtraSizeDelta,
fuzzed_data_provider);
if (fuzzed_cmk_secret_prime.size() < kMigratedCmkPrivateKeySeedPartSizeBytes)
fuzzed_cmk_secret_prime.resize(kMigratedCmkPrivateKeySeedPartSizeBytes);
// Build the |cmk_pubkey| parameter.
// Note: not mutating it, since the tested function assumes the validity of
// the blob.
*cmk_pubkey = BuildRsaTpmPubkeyBlob(cmk_rsa);
// Build the |cmk_pubkey_digest| parameter.
const Blob cmk_pubkey_digest = cryptohome::CryptoLib::Sha1(*cmk_pubkey);
*fuzzed_cmk_pubkey_digest = MutateBlob(
cmk_pubkey_digest, cmk_pubkey_digest.size() + kFuzzingExtraSizeDelta,
fuzzed_data_provider);
// Build the |msa_composite_digest| parameter.
const Blob msa_composite_digest =
fuzzed_data_provider->ConsumeBytes<uint8_t>(SHA_DIGEST_LENGTH);
*fuzzed_msa_composite_digest =
MutateBlob(msa_composite_digest,
msa_composite_digest.size() + kFuzzingExtraSizeDelta,
fuzzed_data_provider);
// Build the |tpm_migrate_asymkey_oaep_label_blob| temporary value.
const Blob tpm_migrate_asymkey_oaep_label_blob =
CombineBlobs({msa_composite_digest, cmk_pubkey_digest});
const Blob fuzzed_tpm_migrate_asymkey_oaep_label_blob = MutateBlob(
tpm_migrate_asymkey_oaep_label_blob,
tpm_migrate_asymkey_oaep_label_blob.size() + kFuzzingExtraSizeDelta,
fuzzed_data_provider);
// Build the |tpm_migrate_asymkey_oaep_seed_blob| temporary value.
const Blob tpm_migrate_asymkey_oaep_seed_blob =
CombineBlobs({EncodeTpmUint32(kCmkPrivateKeySizeBytes),
Blob(fuzzed_cmk_secret_prime.begin(),
fuzzed_cmk_secret_prime.begin() +
kMigratedCmkPrivateKeySeedPartSizeBytes)});
Blob fuzzed_tpm_migrate_asymkey_oaep_seed_blob = MutateBlob(
tpm_migrate_asymkey_oaep_seed_blob,
tpm_migrate_asymkey_oaep_seed_blob.size(), fuzzed_data_provider);
fuzzed_tpm_migrate_asymkey_oaep_seed_blob.resize(
tpm_migrate_asymkey_oaep_seed_blob.size());
// Build the |tpm_migrate_asymkey_blob| temporary value.
Blob auth_data =
fuzzed_data_provider->ConsumeBytes<uint8_t>(SHA_DIGEST_LENGTH);
auth_data.resize(SHA_DIGEST_LENGTH);
Blob pub_data_digest =
fuzzed_data_provider->ConsumeBytes<uint8_t>(SHA_DIGEST_LENGTH);
pub_data_digest.resize(SHA_DIGEST_LENGTH);
const Blob tpm_migrate_asymkey_blob = CombineBlobs({
/*TPM_MIGRATE_ASYMKEY::payload=*/Blob(1, TPM_PT_CMK_MIGRATE),
/*TPM_MIGRATE_ASYMKEY::usageAuth::authdata=*/
auth_data,
/*TPM_MIGRATE_ASYMKEY::pubDataDigest::digest=*/
pub_data_digest,
/*TPM_MIGRATE_ASYMKEY::partPrivKeyLen=*/
EncodeTpmUint32(kMigratedCmkPrivateKeyRestPartSizeBytes),
/*TPM_MIGRATE_ASYMKEY::partPrivKey=*/
Blob(fuzzed_cmk_secret_prime.begin() +
kMigratedCmkPrivateKeySeedPartSizeBytes,
fuzzed_cmk_secret_prime.end()),
});
const Blob fuzzed_tpm_migrate_asymkey_blob =
MutateBlob(tpm_migrate_asymkey_blob, tpm_migrate_asymkey_blob.size(),
fuzzed_data_provider);
// Build the |fuzzed_encoded_tpm_migrate_asymkey_blob| temporary value.
const Blob fuzzed_encoded_tpm_migrate_asymkey_blob = FuzzedOaepMgf1Encode(
/*message=*/fuzzed_tpm_migrate_asymkey_blob,
/*oaep_label=*/fuzzed_tpm_migrate_asymkey_oaep_label_blob,
/*seed=*/fuzzed_tpm_migrate_asymkey_oaep_seed_blob,
/*encoded_message_length=*/kIntermediateOaepEncodingBytes,
fuzzed_data_provider);
// Build the |migration_random_blob| temporary value.
*migration_random_blob = fuzzed_data_provider->ConsumeBytes<uint8_t>(
fuzzed_encoded_tpm_migrate_asymkey_blob.size());
migration_random_blob->resize(fuzzed_encoded_tpm_migrate_asymkey_blob.size());
// Build the |xored_encoded_tpm_migrate_asymkey_blob| temporary value.
Blob xored_encoded_tpm_migrate_asymkey_blob =
fuzzed_encoded_tpm_migrate_asymkey_blob;
for (int i = 0; i < fuzzed_encoded_tpm_migrate_asymkey_blob.size(); ++i)
xored_encoded_tpm_migrate_asymkey_blob[i] ^= (*migration_random_blob)[i];
// Build the |encrypted_tpm_migrate_asymkey_blob| temporary value.
const Blob encrypted_tpm_migrate_asymkey_blob = FuzzedRsaOaepEncrypt(
/*plaintext=*/xored_encoded_tpm_migrate_asymkey_blob,
/*oaep_label=*/BlobFromString("TCPA"), migration_destination_rsa,
fuzzed_data_provider);
// Build the |key12_blob| parameter.
// Note: not mutating it, since the tested function assumes the validity of
// the blob.
TPM_KEY12 key12;
memset(&key12, 0, sizeof(TPM_KEY12));
key12.encSize = encrypted_tpm_migrate_asymkey_blob.size();
key12.encData =
const_cast<uint8_t*>(encrypted_tpm_migrate_asymkey_blob.data());
UINT64 key12_dumping_offset = 0;
Trspi_LoadBlob_KEY12(&key12_dumping_offset, nullptr, &key12);
key12_blob->resize(key12_dumping_offset);
key12_dumping_offset = 0;
Trspi_LoadBlob_KEY12(&key12_dumping_offset, key12_blob->data(), &key12);
CHECK_EQ(key12_dumping_offset, key12_blob->size());
}
} // namespace
// Fuzzer for the ExtractCmkPrivateKeyFromMigratedBlob() function that
// implements parsing/decryption of the migration procedure of a TPM 1.2
// Certified Migratable Key.
//
// The fuzzer contains a complex multi-step data preparation procedure, which
// mirrors the parsing/decryption steps of the tested code, together with mixing
// additional mutations on every step. It's expected that this data preparation
// is stable and won't cause big maintenance burden, since it's, basically, just
// a literal inversed implementation of the steps described in the TPM 1.2
// specification, and that part of the specification is considered stable.
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
static Environment environment;
// Prevent OpenSSL errors from accumulating in the error queue and leaking the
// memory across fuzzer executions.
ScopedOpensslErrorClearer scoped_openssl_error_clearer;
FuzzedDataProvider fuzzed_data_provider(data, size);
Blob key12_blob;
Blob migration_random_blob;
Blob cmk_pubkey;
Blob fuzzed_cmk_pubkey_digest;
Blob fuzzed_msa_composite_digest;
PrepareMutatedArguments(
*environment.cmk_rsa.get(), environment.migration_destination_rsa.get(),
&fuzzed_data_provider, &key12_blob, &migration_random_blob, &cmk_pubkey,
&fuzzed_cmk_pubkey_digest, &fuzzed_msa_composite_digest);
ScopedRSA migrated_blob = cryptohome::ExtractCmkPrivateKeyFromMigratedBlob(
key12_blob, migration_random_blob, cmk_pubkey, fuzzed_cmk_pubkey_digest,
fuzzed_msa_composite_digest, environment.migration_destination_rsa.get());
return 0;
}