| // Copyright 2018 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 <base/files/file_path.h> |
| #include <base/files/file_util.h> |
| #include <base/logging.h> |
| #include <base/strings/stringprintf.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 "cryptohome/cryptolib.h" |
| #include "cryptohome/fuzzers/blob_mutator.h" |
| |
| using brillo::Blob; |
| using brillo::BlobFromString; |
| using brillo::SecureBlob; |
| using crypto::ScopedRSA; |
| using cryptohome::CryptoLib; |
| |
| namespace { |
| |
| constexpr char kStaticFilesPath[] = "/usr/libexec/fuzzers/"; |
| |
| constexpr int kMaxPlaintextLength = 5; |
| constexpr int kMaxOaepLabelLength = 5; |
| |
| struct Environment { |
| Environment(); |
| |
| // The RSA keys loaded from the data files installed next to the fuzzer. |
| static constexpr int kRsaKeyCount = 8; |
| ScopedRSA rsa_keys[kRsaKeyCount]; |
| }; |
| |
| 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() { |
| logging::SetMinLogLevel(logging::LOG_FATAL); |
| |
| int key_index = 0; |
| for (int key_size : {512, 1024, 2048, 4096}) { |
| for (int current_key_number : {1, 2}) { |
| const base::FilePath key_file_path = |
| base::FilePath(kStaticFilesPath) |
| .AppendASCII(base::StringPrintf("cryptohome_fuzzer_key_rsa_%d_%d", |
| key_size, current_key_number)); |
| CHECK_LT(key_index, kRsaKeyCount); |
| rsa_keys[key_index] = LoadRsaPrivateKeyFromPemFile(key_file_path); |
| ++key_index; |
| } |
| } |
| } |
| |
| // 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), fuzzed_data_provider); |
| } |
| |
| } // namespace |
| |
| 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); |
| |
| RSA* const encryption_rsa = |
| environment |
| .rsa_keys[fuzzed_data_provider.ConsumeIntegralInRange( |
| 0, Environment::kRsaKeyCount - 1)] |
| .get(); |
| RSA* const decryption_rsa = |
| environment |
| .rsa_keys[fuzzed_data_provider.ConsumeIntegralInRange( |
| 0, Environment::kRsaKeyCount - 1)] |
| .get(); |
| |
| // Prepare fuzzed parameters for the tested function, based off real |
| // RSA-encoded blobs. |
| const Blob plaintext = BlobFromString( |
| fuzzed_data_provider.ConsumeRandomLengthString(kMaxPlaintextLength)); |
| const Blob oaep_label = BlobFromString( |
| fuzzed_data_provider.ConsumeRandomLengthString(kMaxOaepLabelLength)); |
| const Blob fuzzed_ciphertext = FuzzedRsaOaepEncrypt( |
| plaintext, oaep_label, encryption_rsa, &fuzzed_data_provider); |
| |
| const Blob fuzzed_oaep_label = |
| MutateBlob(oaep_label, kMaxOaepLabelLength, &fuzzed_data_provider); |
| |
| // Run the fuzzed function. |
| SecureBlob decrypted_data; |
| if (CryptoLib::RsaOaepDecrypt(SecureBlob(fuzzed_ciphertext), |
| SecureBlob(fuzzed_oaep_label), decryption_rsa, |
| &decrypted_data)) { |
| // Assert that the decryption result must be equal to the plaintext that was |
| // encrypted above - it's unrealistic for the fuzzer to find a blob that is |
| // a valid ciphertext of some different blob. |
| CHECK(SecureBlob(plaintext) == decrypted_data); |
| } |
| return 0; |
| } |