| // 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 "arc/keymaster/context/openssl_utils.h" |
| |
| #include <base/stl_util.h> |
| #include <openssl/aes.h> |
| #include <openssl/rand.h> |
| |
| namespace arc { |
| namespace keymaster { |
| namespace context { |
| |
| namespace { |
| |
| constexpr size_t kKeySize = 32; |
| constexpr size_t kAes256GcmPadding = 16; |
| |
| // Encrypts a given |input| using AES-GCM-256 with |key|, |auth_data|, and |iv|. |
| // Returns base::nullopt if there's an error in the OpenSSL operation. |
| base::Optional<brillo::Blob> DoAes256GcmEncrypt( |
| const brillo::SecureBlob& key, |
| const brillo::Blob& auth_data, |
| const brillo::Blob& iv, |
| const brillo::SecureBlob& input) { |
| CHECK_EQ(key.size(), kKeySize); |
| CHECK_EQ(iv.size(), kIvSize); |
| // Initialize cipher. |
| crypto::ScopedEVP_CIPHER_CTX ctx(EVP_CIPHER_CTX_new()); |
| if (1 != EVP_EncryptInit_ex(ctx.get(), EVP_aes_256_gcm(), |
| /* engine */ nullptr, |
| /* key */ key.data(), /* iv */ iv.data())) { |
| return base::nullopt; |
| } |
| |
| // Update operation with |auth_data|, out pointer must be null. |
| int auth_update_len; |
| if (1 != EVP_EncryptUpdate(ctx.get(), /* out */ nullptr, &auth_update_len, |
| auth_data.data(), auth_data.size())) { |
| return base::nullopt; |
| } |
| |
| // Update operation with |input|. |
| int update_len; |
| brillo::Blob output(input.size() + kAes256GcmPadding); |
| if (1 != EVP_EncryptUpdate(ctx.get(), output.data(), &update_len, |
| input.data(), input.size())) { |
| return base::nullopt; |
| } |
| |
| // Finish operation, accumulate results in |output|. |
| int finish_len; |
| if (1 != |
| EVP_EncryptFinal_ex(ctx.get(), output.data() + update_len, &finish_len)) { |
| return base::nullopt; |
| } |
| CHECK_GE(output.size(), update_len + finish_len); |
| output.resize(update_len + finish_len); |
| |
| // Retrieve tag. |
| brillo::Blob tag(kTagSize); |
| if (1 != EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_GET_TAG, tag.size(), |
| tag.data())) { |
| return base::nullopt; |
| } |
| |
| // Append tag to |output| and return the encrypted blob. |
| output.insert(output.end(), tag.begin(), tag.end()); |
| return output; |
| } |
| |
| // Decrypts a given |input| using AES-GCM-256 with |key|, |auth_data|, and |iv|. |
| // Returns base::nullopt if there's an error in the OpenSSL operation. |
| base::Optional<brillo::SecureBlob> DoAes256GcmDecrypt( |
| const brillo::SecureBlob& key, |
| const brillo::Blob& auth_data, |
| const brillo::Blob& iv, |
| const brillo::Blob& input) { |
| CHECK_EQ(key.size(), kKeySize); |
| CHECK_EQ(iv.size(), kIvSize); |
| |
| // Input must have a tag appended to it. |
| if (input.size() < kTagSize) |
| return base::nullopt; |
| |
| // Initialize cipher. |
| crypto::ScopedEVP_CIPHER_CTX ctx(EVP_CIPHER_CTX_new()); |
| if (1 != EVP_DecryptInit_ex(ctx.get(), EVP_aes_256_gcm(), |
| /* engine */ nullptr, key.data(), iv.data())) { |
| return base::nullopt; |
| } |
| |
| // Set expected tag. |
| brillo::Blob tag(input.end() - kTagSize, input.end()); |
| size_t input_len = input.size() - tag.size(); |
| if (1 != EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_TAG, tag.size(), |
| tag.data())) { |
| return base::nullopt; |
| } |
| |
| // Update operation with |auth_data|, out pointer must be null. |
| int auth_update_len; |
| if (1 != EVP_DecryptUpdate(ctx.get(), /* out */ nullptr, &auth_update_len, |
| auth_data.data(), auth_data.size())) { |
| return base::nullopt; |
| } |
| |
| // Update operation with |input|. |
| int update_len; |
| brillo::SecureBlob output(input_len + kAes256GcmPadding); |
| if (1 != EVP_DecryptUpdate(ctx.get(), output.data(), &update_len, |
| input.data(), input_len)) { |
| return base::nullopt; |
| } |
| |
| // Finish operation, accumulate results in |output|. |
| int finish_len; |
| if (1 != |
| EVP_CipherFinal_ex(ctx.get(), output.data() + update_len, &finish_len)) { |
| return base::nullopt; |
| } |
| CHECK_GE(output.size(), update_len + finish_len); |
| output.resize(update_len + finish_len); |
| |
| // Return decrypted blob; |
| return output; |
| } |
| |
| } // anonymous namespace |
| |
| base::Optional<brillo::Blob> Aes256GcmEncrypt(const brillo::SecureBlob& key, |
| const brillo::Blob& auth_data, |
| const brillo::SecureBlob& input) { |
| // Compute a random IV. |
| brillo::Blob iv(kIvSize); |
| if (1 != RAND_bytes(iv.data(), iv.size())) |
| return base::nullopt; |
| |
| // Encrypt the input. |
| base::Optional<brillo::Blob> encrypted = |
| DoAes256GcmEncrypt(key, auth_data, iv, input); |
| if (!encrypted.has_value()) |
| return base::nullopt; |
| |
| // Append the random IV used for encryption to the output. |
| encrypted->insert(encrypted->end(), iv.begin(), iv.end()); |
| return encrypted; |
| } |
| |
| base::Optional<brillo::SecureBlob> Aes256GcmDecrypt( |
| const brillo::SecureBlob& key, |
| const brillo::Blob& auth_data, |
| const brillo::Blob& input) { |
| // Input must have an IV appended to it. |
| if (input.size() < kIvSize) |
| return base::nullopt; |
| |
| // Split the input between the encrypted portion and the IV. |
| brillo::Blob encrypted(input.begin(), input.end() - kIvSize); |
| brillo::Blob iv(input.end() - kIvSize, input.end()); |
| |
| // Decrypt the input. |
| return DoAes256GcmDecrypt(key, auth_data, iv, encrypted); |
| } |
| |
| } // namespace context |
| } // namespace keymaster |
| } // namespace arc |