| // Copyright 2021 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 "cryptohome/crypto/aes.h" |
| |
| #include <limits> |
| #include <openssl/err.h> |
| #include <openssl/evp.h> |
| |
| #include <crypto/scoped_openssl_types.h> |
| |
| #include "cryptohome/crypto/secure_blob_util.h" |
| |
| #include <base/logging.h> |
| #include <base/notreached.h> |
| |
| namespace cryptohome { |
| |
| // AES block size in bytes. |
| constexpr unsigned int kAesBlockSize = 16; |
| |
| // The size of the AES-GCM IV (96-bits). |
| constexpr unsigned int kAesGcmIVSize = 96 / (sizeof(uint8_t) * CHAR_BIT); |
| |
| // The size of an AES-GCM key in cryptohome code (256-bits). |
| constexpr unsigned int kAesGcm256KeySize = 256 / (sizeof(uint8_t) * CHAR_BIT); |
| |
| // The size of the AES-GCM tag. |
| constexpr unsigned int kAesGcmTagSize = 16; |
| |
| // AES key size in bytes (256-bit). This key size is used for all key creation, |
| // though we currently only use 128 bits for the eCryptfs File Encryption Key |
| // (FEK). Larger than 128-bit has too great of a CPU overhead on unaccelerated |
| // architectures. |
| constexpr unsigned int kDefaultAesKeySize = 32; |
| |
| size_t GetAesBlockSize() { |
| return EVP_CIPHER_block_size(EVP_aes_256_cbc()); |
| } |
| |
| bool PasskeyToAesKey(const brillo::SecureBlob& passkey, |
| const brillo::SecureBlob& salt, |
| unsigned int rounds, |
| brillo::SecureBlob* key, |
| brillo::SecureBlob* iv) { |
| if (salt.size() != PKCS5_SALT_LEN) { |
| LOG(ERROR) << "Bad salt size."; |
| return false; |
| } |
| |
| const EVP_CIPHER* cipher = EVP_aes_256_cbc(); |
| brillo::SecureBlob aes_key(EVP_CIPHER_key_length(cipher)); |
| brillo::SecureBlob local_iv(EVP_CIPHER_iv_length(cipher)); |
| |
| // Convert the passkey to a key |
| if (!EVP_BytesToKey(cipher, EVP_sha1(), salt.data(), passkey.data(), |
| passkey.size(), rounds, aes_key.data(), |
| local_iv.data())) { |
| LOG(ERROR) << "Failure converting bytes to key"; |
| return false; |
| } |
| |
| key->swap(aes_key); |
| if (iv) { |
| iv->swap(local_iv); |
| } |
| |
| return true; |
| } |
| |
| bool AesEncryptDeprecated(const brillo::SecureBlob& plaintext, |
| const brillo::SecureBlob& key, |
| const brillo::SecureBlob& iv, |
| brillo::SecureBlob* ciphertext) { |
| return AesEncryptSpecifyBlockMode( |
| plaintext, 0, plaintext.size(), key, iv, |
| PaddingScheme::kPaddingCryptohomeDefaultDeprecated, BlockMode::kCbc, |
| ciphertext); |
| } |
| |
| bool AesDecryptDeprecated(const brillo::SecureBlob& ciphertext, |
| const brillo::SecureBlob& key, |
| const brillo::SecureBlob& iv, |
| brillo::SecureBlob* plaintext) { |
| return AesDecryptSpecifyBlockMode( |
| ciphertext, 0, ciphertext.size(), key, iv, |
| PaddingScheme::kPaddingCryptohomeDefaultDeprecated, BlockMode::kCbc, |
| plaintext); |
| } |
| |
| bool AesGcmDecrypt(const brillo::SecureBlob& ciphertext, |
| const base::Optional<brillo::SecureBlob>& ad, |
| const brillo::SecureBlob& tag, |
| const brillo::SecureBlob& key, |
| const brillo::SecureBlob& iv, |
| brillo::SecureBlob* plaintext) { |
| if (ciphertext.empty()) { |
| NOTREACHED() << "Empty ciphertext passed to AesGcmDecrypt."; |
| return false; |
| } |
| if (tag.size() != kAesGcmTagSize) { |
| NOTREACHED() << "Wrong tag size passed to AesGcmDecrypt: " << tag.size() |
| << ", expected " << kAesGcmTagSize << "."; |
| return false; |
| } |
| if (key.size() != kAesGcm256KeySize) { |
| NOTREACHED() << "Wrong key size passed to AesGcmDecrypt: " << key.size() |
| << ", expected " << kAesGcm256KeySize << "."; |
| return false; |
| } |
| if (iv.size() != kAesGcmIVSize) { |
| NOTREACHED() << "Wrong iv size passed to AesGcmDecrypt: " << iv.size() |
| << ", expected " << kAesGcmIVSize << "."; |
| return false; |
| } |
| |
| crypto::ScopedEVP_CIPHER_CTX ctx(EVP_CIPHER_CTX_new()); |
| if (ctx.get() == nullptr) { |
| LOG(ERROR) << "Failed to create cipher ctx."; |
| return false; |
| } |
| |
| if (EVP_DecryptInit_ex(ctx.get(), EVP_aes_256_gcm(), nullptr, nullptr, |
| nullptr) != 1) { |
| LOG(ERROR) << "Failed to init decrypt."; |
| return false; |
| } |
| |
| if (EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_IVLEN, kAesGcmIVSize, |
| nullptr) != 1) { |
| LOG(ERROR) << "Failed to set iv size."; |
| return false; |
| } |
| |
| if (EVP_DecryptInit_ex(ctx.get(), nullptr, nullptr, key.data(), iv.data()) != |
| 1) { |
| LOG(ERROR) << "Failed to add key and iv to decrypt operation."; |
| return false; |
| } |
| |
| if (ad.has_value()) { |
| if (ad.value().empty()) { |
| NOTREACHED() << "Empty associated data passed to AesGcmDecrypt."; |
| return false; |
| } |
| int out_size = 0; |
| if (EVP_DecryptUpdate(ctx.get(), nullptr, &out_size, ad.value().data(), |
| ad.value().size()) != 1) { |
| LOG(ERROR) << "Failed to add additional authentication data."; |
| return false; |
| } |
| if (out_size != ad.value().size()) { |
| LOG(ERROR) << "Failed to process entire ad."; |
| return false; |
| } |
| } |
| brillo::SecureBlob result; |
| result.resize(ciphertext.size()); |
| int output_size = 0; |
| if (EVP_DecryptUpdate(ctx.get(), result.data(), &output_size, |
| ciphertext.data(), ciphertext.size()) != 1) { |
| LOG(ERROR) << "Failed to decrypt the plaintext."; |
| return false; |
| } |
| |
| if (output_size != ciphertext.size()) { |
| LOG(ERROR) << "Failed to process entire ciphertext."; |
| return false; |
| } |
| |
| uint8_t* tag_ptr = const_cast<uint8_t*>(tag.data()); |
| if (EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_TAG, tag.size(), |
| tag_ptr) != 1) { |
| LOG(ERROR) << "Failed to set the tag."; |
| return false; |
| } |
| |
| output_size = 0; |
| int ret_val = EVP_DecryptFinal_ex(ctx.get(), nullptr, &output_size); |
| bool success = output_size == 0 && ret_val > 0; |
| if (success) { |
| *plaintext = result; |
| } |
| return success; |
| } |
| |
| bool AesGcmEncrypt(const brillo::SecureBlob& plaintext, |
| const base::Optional<brillo::SecureBlob>& ad, |
| const brillo::SecureBlob& key, |
| brillo::SecureBlob* iv, |
| brillo::SecureBlob* tag, |
| brillo::SecureBlob* ciphertext) { |
| if (plaintext.empty()) { |
| NOTREACHED() << "Empty plaintext passed to AesGcmEncrypt."; |
| return false; |
| } |
| if (key.size() != kAesGcm256KeySize) { |
| NOTREACHED() << "Wrong key size passed to AesGcmEncrypt: " << key.size() |
| << ", expected " << kAesGcm256KeySize << "."; |
| return false; |
| } |
| |
| iv->resize(kAesGcmIVSize); |
| GetSecureRandom(iv->data(), kAesGcmIVSize); |
| |
| crypto::ScopedEVP_CIPHER_CTX ctx(EVP_CIPHER_CTX_new()); |
| if (ctx.get() == nullptr) { |
| LOG(ERROR) << "Failed to create context."; |
| return false; |
| } |
| |
| if (EVP_EncryptInit_ex(ctx.get(), EVP_aes_256_gcm(), nullptr, nullptr, |
| nullptr) != 1) { |
| LOG(ERROR) << "Failed to init aes-gcm-256."; |
| return false; |
| } |
| |
| if (EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_IVLEN, kAesGcmIVSize, |
| nullptr) != 1) { |
| LOG(ERROR) << "Failed to set IV length."; |
| return false; |
| } |
| |
| if (EVP_EncryptInit_ex(ctx.get(), nullptr, nullptr, key.data(), iv->data()) != |
| 1) { |
| LOG(ERROR) << "Failed to init key and iv."; |
| return false; |
| } |
| |
| if (ad.has_value()) { |
| if (ad.value().empty()) { |
| NOTREACHED() << "Empty associated data passed to AesGcmEncrypt."; |
| return false; |
| } |
| int out_size = 0; |
| if (EVP_EncryptUpdate(ctx.get(), nullptr, &out_size, ad.value().data(), |
| ad.value().size()) != 1) { |
| LOG(ERROR) << "Failed to add additional authentication data."; |
| return false; |
| } |
| if (out_size != ad.value().size()) { |
| LOG(ERROR) << "Failed to process entire ad."; |
| return false; |
| } |
| } |
| brillo::SecureBlob result; |
| result.resize(plaintext.size()); |
| int processed_bytes = 0; |
| if (EVP_EncryptUpdate(ctx.get(), result.data(), &processed_bytes, |
| plaintext.data(), plaintext.size()) != 1) { |
| LOG(ERROR) << "Failed to encrypt plaintext."; |
| return false; |
| } |
| |
| if (plaintext.size() != processed_bytes) { |
| LOG(ERROR) << "Did not process the entire plaintext."; |
| return false; |
| } |
| |
| int unused_output_length; |
| if (EVP_EncryptFinal_ex(ctx.get(), nullptr, &unused_output_length) != 1) { |
| LOG(ERROR) << "Failed to finalize encryption."; |
| return false; |
| } |
| |
| tag->resize(kAesGcmTagSize); |
| if (EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_GET_TAG, kAesGcmTagSize, |
| tag->data()) != 1) { |
| LOG(ERROR) << "Failed to retrieve tag."; |
| return false; |
| } |
| |
| *ciphertext = result; |
| return true; |
| } |
| |
| // This is the reverse operation of AesEncryptSpecifyBlockMode above. See that |
| // method for a description of how padding and block_mode affect the crypto |
| // operations. This method automatically removes and verifies the padding, so |
| // plain_text (on success) will contain the original data. |
| // |
| // Note that a call to AesDecryptSpecifyBlockMode needs to have the same padding |
| // and block_mode as the corresponding encrypt call. Changing the block mode |
| // will drastically alter the decryption. And an incorrect PaddingScheme will |
| // result in the padding verification failing, for which the method call fails, |
| // even if the key and initialization vector were correct. |
| bool AesDecryptSpecifyBlockMode(const brillo::SecureBlob& encrypted, |
| unsigned int start, |
| unsigned int count, |
| const brillo::SecureBlob& key, |
| const brillo::SecureBlob& iv, |
| PaddingScheme padding, |
| BlockMode block_mode, |
| brillo::SecureBlob* plain_text) { |
| if ((start > encrypted.size()) || ((start + count) > encrypted.size()) || |
| ((start + count) < start)) { |
| return false; |
| } |
| brillo::SecureBlob local_plain_text(count); |
| |
| if (local_plain_text.size() > |
| static_cast<unsigned int>(std::numeric_limits<int>::max())) { |
| // EVP_DecryptUpdate takes a signed int |
| return false; |
| } |
| int final_size = 0; |
| int decrypt_size = local_plain_text.size(); |
| |
| const EVP_CIPHER* cipher; |
| switch (block_mode) { |
| case BlockMode::kCbc: |
| cipher = EVP_aes_256_cbc(); |
| break; |
| case BlockMode::kEcb: |
| cipher = EVP_aes_256_ecb(); |
| break; |
| case BlockMode::kCtr: |
| cipher = EVP_aes_256_ctr(); |
| break; |
| default: |
| LOG(ERROR) << "Invalid block mode specified: " |
| << static_cast<int>(block_mode); |
| return false; |
| } |
| if (key.size() != static_cast<unsigned int>(EVP_CIPHER_key_length(cipher))) { |
| LOG(ERROR) << "Invalid key length of " << key.size() << ", expected " |
| << EVP_CIPHER_key_length(cipher); |
| return false; |
| } |
| // ECB ignores the IV, so only check the IV length if we are using a different |
| // block mode. |
| if ((block_mode != BlockMode::kEcb) && |
| (iv.size() != static_cast<unsigned int>(EVP_CIPHER_iv_length(cipher)))) { |
| LOG(ERROR) << "Invalid iv length of " << iv.size() << ", expected " |
| << EVP_CIPHER_iv_length(cipher); |
| return false; |
| } |
| |
| crypto::ScopedEVP_CIPHER_CTX decryption_context(EVP_CIPHER_CTX_new()); |
| if (!decryption_context) { |
| LOG(ERROR) << "Failed to allocate EVP_CIPHER_CTX"; |
| return false; |
| } |
| EVP_DecryptInit_ex(decryption_context.get(), cipher, nullptr, key.data(), |
| iv.data()); |
| if (padding == PaddingScheme::kPaddingNone) { |
| EVP_CIPHER_CTX_set_padding(decryption_context.get(), 0); |
| } |
| |
| // Make sure we're not pointing into an empty buffer or past the end. |
| const unsigned char* encrypted_buf = NULL; |
| if (start < encrypted.size()) |
| encrypted_buf = &encrypted[start]; |
| |
| if (!EVP_DecryptUpdate(decryption_context.get(), local_plain_text.data(), |
| &decrypt_size, encrypted_buf, count)) { |
| LOG(ERROR) << "DecryptUpdate failed"; |
| return false; |
| } |
| |
| // In the case of local_plain_text being full, we must avoid trying to |
| // point past the end of the buffer when calling EVP_DecryptFinal_ex(). |
| unsigned char* final_buf = NULL; |
| if (static_cast<unsigned int>(decrypt_size) < local_plain_text.size()) |
| final_buf = &local_plain_text[decrypt_size]; |
| |
| if (!EVP_DecryptFinal_ex(decryption_context.get(), final_buf, &final_size)) { |
| unsigned long err = ERR_get_error(); // NOLINT openssl types |
| ERR_load_ERR_strings(); |
| ERR_load_crypto_strings(); |
| |
| LOG(ERROR) << "DecryptFinal Error: " << err << ": " |
| << ERR_lib_error_string(err) << ", " |
| << ERR_func_error_string(err) << ", " |
| << ERR_reason_error_string(err); |
| |
| return false; |
| } |
| final_size += decrypt_size; |
| |
| if (padding == PaddingScheme::kPaddingCryptohomeDefaultDeprecated) { |
| if (final_size < SHA_DIGEST_LENGTH) { |
| LOG(ERROR) << "Plain text was too small."; |
| return false; |
| } |
| |
| final_size -= SHA_DIGEST_LENGTH; |
| |
| SHA_CTX sha_context; |
| unsigned char md_value[SHA_DIGEST_LENGTH]; |
| |
| SHA1_Init(&sha_context); |
| SHA1_Update(&sha_context, local_plain_text.data(), final_size); |
| SHA1_Final(md_value, &sha_context); |
| |
| const unsigned char* md_ptr = local_plain_text.data(); |
| md_ptr += final_size; |
| if (brillo::SecureMemcmp(md_ptr, md_value, SHA_DIGEST_LENGTH)) { |
| LOG(ERROR) << "Digest verification failed."; |
| return false; |
| } |
| } |
| |
| local_plain_text.resize(final_size); |
| plain_text->swap(local_plain_text); |
| return true; |
| } |
| |
| // AesEncryptSpecifyBlockMode encrypts the bytes in plain_text using AES, |
| // placing the output into encrypted. Aside from range constraints (start and |
| // count) and the key and initialization vector, this method has two parameters |
| // that control how the ciphertext is generated and are useful in encrypting |
| // specific types of data in cryptohome. |
| // |
| // First, padding specifies whether and how the plaintext is padded before |
| // encryption. The three options, described in the PaddingScheme enumeration |
| // are used as such: |
| // - PaddingScheme::kPaddingNone is used to mix the user's passkey (derived |
| // from the |
| // password) into the encrypted blob storing the vault keyset when the TPM |
| // is used. This is described in more detail in the README file. There is |
| // no padding in this case, and the size of plain_text needs to be a |
| // multiple of the AES block size (16 bytes). |
| // - PaddingScheme::kPaddingStandard uses standard PKCS padding, which is the |
| // default for |
| // OpenSSL. |
| // - PaddingScheme::kPaddingCryptohomeDefaultDeprecated appends a SHA1 hash of |
| // the plaintext |
| // in plain_text before passing it to OpenSSL, which still uses PKCS padding |
| // so that we do not have to re-implement block-multiple padding ourselves. |
| // This padding scheme allows us to strongly verify the plaintext on |
| // decryption, which is essential when, for example, test decrypting a nonce |
| // to test whether a password was correct (we do this in user_session.cc). |
| // This padding is now deprecated and a standard integrity checking |
| // algorithm such as AES-GCM should be used instead. |
| // |
| // The block mode switches between ECB and CBC. Generally, CBC is used for most |
| // AES crypto that we perform, since it is a better mode for us for data that is |
| // larger than the block size. We use ECB only when mixing the user passkey |
| // into the TPM-encrypted blob, since we only encrypt a single block of that |
| // data. |
| bool AesEncryptSpecifyBlockMode(const brillo::SecureBlob& plain_text, |
| unsigned int start, |
| unsigned int count, |
| const brillo::SecureBlob& key, |
| const brillo::SecureBlob& iv, |
| PaddingScheme padding, |
| BlockMode block_mode, |
| brillo::SecureBlob* encrypted) { |
| // Verify that the range is within the data passed |
| if ((start > plain_text.size()) || ((start + count) > plain_text.size()) || |
| ((start + count) < start)) { |
| return false; |
| } |
| if (count > static_cast<unsigned int>(std::numeric_limits<int>::max())) { |
| // EVP_EncryptUpdate takes a signed int |
| return false; |
| } |
| |
| // First set the output size based on the padding scheme. No padding means |
| // that the input needs to be a multiple of the block size, and the output |
| // size is equal to the input size. Standard padding means we should allocate |
| // up to a full block additional for the PKCS padding. Cryptohome default |
| // means we should allocate a full block additional for the PKCS padding and |
| // enough for a SHA1 hash. |
| unsigned int block_size = GetAesBlockSize(); |
| unsigned int needed_size = count; |
| switch (padding) { |
| case PaddingScheme::kPaddingCryptohomeDefaultDeprecated: |
| // The AES block size and SHA digest length are not enough for this to |
| // overflow, as needed_size is initialized to count, which must be <= |
| // INT_MAX, but needed_size is itself an unsigned. The block size and |
| // digest length are fixed by the algorithm. |
| needed_size += block_size + SHA_DIGEST_LENGTH; |
| break; |
| case PaddingScheme::kPaddingStandard: |
| needed_size += block_size; |
| break; |
| case PaddingScheme::kPaddingNone: |
| if (count % block_size) { |
| LOG(ERROR) << "Data size (" << count << ") was not a multiple " |
| << "of the block size (" << block_size << ")"; |
| return false; |
| } |
| break; |
| default: |
| LOG(ERROR) << "Invalid padding specified"; |
| return false; |
| break; |
| } |
| brillo::SecureBlob cipher_text(needed_size); |
| |
| // Set the block mode |
| const EVP_CIPHER* cipher; |
| switch (block_mode) { |
| case BlockMode::kCbc: |
| cipher = EVP_aes_256_cbc(); |
| break; |
| case BlockMode::kEcb: |
| cipher = EVP_aes_256_ecb(); |
| break; |
| case BlockMode::kCtr: |
| cipher = EVP_aes_256_ctr(); |
| break; |
| default: |
| LOG(ERROR) << "Invalid block mode specified"; |
| return false; |
| } |
| if (key.size() != static_cast<unsigned int>(EVP_CIPHER_key_length(cipher))) { |
| LOG(ERROR) << "Invalid key length of " << key.size() << ", expected " |
| << EVP_CIPHER_key_length(cipher); |
| return false; |
| } |
| |
| // ECB ignores the IV, so only check the IV length if we are using a different |
| // block mode. |
| if ((block_mode != BlockMode::kEcb) && |
| (iv.size() != static_cast<unsigned int>(EVP_CIPHER_iv_length(cipher)))) { |
| LOG(ERROR) << "Invalid iv length of " << iv.size() << ", expected " |
| << EVP_CIPHER_iv_length(cipher); |
| return false; |
| } |
| |
| // Initialize the OpenSSL crypto context |
| crypto::ScopedEVP_CIPHER_CTX encryption_context(EVP_CIPHER_CTX_new()); |
| if (!encryption_context) { |
| LOG(ERROR) << "Failed to allocate EVP_CIPHER_CTX"; |
| return false; |
| } |
| |
| EVP_EncryptInit_ex(encryption_context.get(), cipher, nullptr, key.data(), |
| iv.data()); |
| if (padding == PaddingScheme::kPaddingNone) { |
| EVP_CIPHER_CTX_set_padding(encryption_context.get(), 0); |
| } |
| |
| // First, encrypt the plain_text data |
| unsigned int current_size = 0; |
| int encrypt_size = 0; |
| |
| // Make sure we're not pointing into an empty buffer or past the end. |
| const unsigned char* plain_buf = NULL; |
| if (start < plain_text.size()) |
| plain_buf = &plain_text[start]; |
| |
| if (!EVP_EncryptUpdate(encryption_context.get(), &cipher_text[current_size], |
| &encrypt_size, plain_buf, count)) { |
| LOG(ERROR) << "EncryptUpdate failed"; |
| return false; |
| } |
| current_size += encrypt_size; |
| encrypt_size = 0; |
| |
| // Next, if the padding uses the cryptohome default scheme, encrypt a SHA1 |
| // hash of the preceding plain_text into the output data |
| if (padding == PaddingScheme::kPaddingCryptohomeDefaultDeprecated) { |
| SHA_CTX sha_context; |
| unsigned char md_value[SHA_DIGEST_LENGTH]; |
| |
| SHA1_Init(&sha_context); |
| SHA1_Update(&sha_context, &plain_text[start], count); |
| SHA1_Final(md_value, &sha_context); |
| if (!EVP_EncryptUpdate(encryption_context.get(), &cipher_text[current_size], |
| &encrypt_size, md_value, sizeof(md_value))) { |
| LOG(ERROR) << "EncryptUpdate failed"; |
| return false; |
| } |
| current_size += encrypt_size; |
| encrypt_size = 0; |
| } |
| |
| // In the case of cipher_text being full, we must avoid trying to |
| // point past the end of the buffer when calling EVP_EncryptFinal_ex(). |
| unsigned char* final_buf = NULL; |
| if (static_cast<unsigned int>(current_size) < cipher_text.size()) |
| final_buf = &cipher_text[current_size]; |
| |
| // Finally, finish the encryption |
| if (!EVP_EncryptFinal_ex(encryption_context.get(), final_buf, |
| &encrypt_size)) { |
| LOG(ERROR) << "EncryptFinal failed"; |
| return false; |
| } |
| current_size += encrypt_size; |
| cipher_text.resize(current_size); |
| |
| encrypted->swap(cipher_text); |
| return true; |
| } |
| |
| } // namespace cryptohome |