| // 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::LOG_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; |
| } |