| // Copyright 2023 The ChromiumOS Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "arc/keymint/context/openssl_utils.h" |
| |
| #include <base/check_op.h> |
| #include <base/stl_util.h> |
| #include <openssl/aes.h> |
| #include <openssl/rand.h> |
| |
| #include <optional> |
| #include <utility> |
| |
| namespace arc::keymint::context { |
| |
| namespace { |
| |
| constexpr size_t kKeySize = 32; |
| constexpr size_t kAes256GcmPadding = 16; |
| constexpr uint32_t kAffinePointLength = 32; |
| constexpr uint32_t kSeedSize = 32; |
| constexpr uint32_t kP256EcdsaPrivateKeyLength = 32; |
| |
| // Encrypts a given |input| using AES-GCM-256 with |key|, |auth_data|, and |iv|. |
| // Returns std::nullopt if there's an error in the OpenSSL operation. |
| std::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 std::nullopt; |
| } |
| |
| // Update operation with |auth_data|, out pointer must be null. |
| int auth_update_len = 0; |
| if (1 != EVP_EncryptUpdate(ctx.get(), /* out */ nullptr, &auth_update_len, |
| auth_data.data(), auth_data.size())) { |
| return std::nullopt; |
| } |
| |
| // Update operation with |input|. |
| int update_len = 0; |
| brillo::Blob output(input.size() + kAes256GcmPadding, 0); |
| if (1 != EVP_EncryptUpdate(ctx.get(), output.data(), &update_len, |
| input.data(), input.size())) { |
| return std::nullopt; |
| } |
| |
| // Finish operation, accumulate results in |output|. |
| int finish_len = 0; |
| if (1 != |
| EVP_EncryptFinal_ex(ctx.get(), output.data() + update_len, &finish_len)) { |
| return std::nullopt; |
| } |
| CHECK_GE(output.size(), update_len + finish_len); |
| output.resize(update_len + finish_len); |
| |
| // Retrieve tag. |
| brillo::Blob tag(kTagSize, 0); |
| if (1 != EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_GET_TAG, tag.size(), |
| tag.data())) { |
| return std::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 std::nullopt if there's an error in the OpenSSL operation. |
| std::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 std::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 std::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 std::nullopt; |
| } |
| |
| // Update operation with |auth_data|, out pointer must be null. |
| int auth_update_len = 0; |
| if (1 != EVP_DecryptUpdate(ctx.get(), /* out */ nullptr, &auth_update_len, |
| auth_data.data(), auth_data.size())) { |
| return std::nullopt; |
| } |
| |
| // Update operation with |input|. |
| int update_len = 0; |
| brillo::SecureBlob output(input_len + kAes256GcmPadding, 0); |
| if (1 != EVP_DecryptUpdate(ctx.get(), output.data(), &update_len, |
| input.data(), input_len)) { |
| return std::nullopt; |
| } |
| |
| // Finish operation, accumulate results in |output|. |
| int finish_len = 0; |
| if (1 != |
| EVP_CipherFinal_ex(ctx.get(), output.data() + update_len, &finish_len)) { |
| return std::nullopt; |
| } |
| CHECK_GE(output.size(), update_len + finish_len); |
| output.resize(update_len + finish_len); |
| |
| // Return decrypted blob; |
| return output; |
| } |
| |
| keymaster_error_t extractEcdsaPEMKey(bssl::UniquePtr<EC_KEY> ec_key, |
| std::string& private_key_pem) { |
| // Convert EC_KEY to EVP_PKEY |
| bssl::UniquePtr<EVP_PKEY> evp_pkey(EVP_PKEY_new()); |
| if (!evp_pkey.get()) { |
| return ::keymaster::TranslateLastOpenSslError(); |
| } |
| // This function will take ownership of |ec_key|. |
| if (EVP_PKEY_assign_EC_KEY(evp_pkey.get(), ec_key.release()) != 1) { |
| return ::keymaster::TranslateLastOpenSslError(); |
| } |
| |
| // Convert private key to PEM format |
| bssl::UniquePtr<BIO> priv_bio(BIO_new(BIO_s_mem())); |
| if (!priv_bio.get()) { |
| return ::keymaster::TranslateLastOpenSslError(); |
| } |
| |
| if (!PEM_write_bio_PrivateKey(priv_bio.get(), evp_pkey.get(), nullptr, |
| nullptr, 0, nullptr, nullptr)) { |
| return ::keymaster::TranslateLastOpenSslError(); |
| } |
| |
| // Extract private key PEM data. |
| BUF_MEM* buf; |
| BIO_get_mem_ptr(priv_bio.get(), &buf); |
| std::string private_key_string(buf->data, buf->length); |
| private_key_pem = private_key_string; |
| |
| return KM_ERROR_OK; |
| } |
| } // anonymous namespace |
| |
| std::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, 0); |
| if (1 != RAND_bytes(iv.data(), iv.size())) { |
| return std::nullopt; |
| } |
| |
| // Encrypt the input. |
| std::optional<brillo::Blob> encrypted = |
| DoAes256GcmEncrypt(key, auth_data, iv, input); |
| if (!encrypted.has_value()) { |
| return std::nullopt; |
| } |
| |
| // Append the random IV used for encryption to the output. |
| encrypted->insert(encrypted->end(), iv.begin(), iv.end()); |
| return encrypted; |
| } |
| |
| std::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 std::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); |
| } |
| |
| // This function is based upon AOSP Keymaster's |GetEcdsa256KeyFromCert| |
| // function. |
| keymaster_error_t GetEcdsa256KeyFromCertBlob(brillo::Blob& certData, |
| absl::Span<uint8_t> x_coord, |
| absl::Span<uint8_t> y_coord) { |
| // Input Validation. |
| if (certData.empty() || x_coord.size() != kAffinePointLength || |
| y_coord.size() != kAffinePointLength) { |
| return KM_ERROR_INVALID_ARGUMENT; |
| } |
| |
| // Create BIO from Vector Data. |
| bssl::UniquePtr<BIO> certbio( |
| BIO_new_mem_buf(certData.data(), certData.size())); |
| if (!certbio.get()) { |
| // Handle BIO creation error. |
| return ::keymaster::TranslateLastOpenSslError(); |
| } |
| |
| // Read Certificate from BIO. |
| ::keymaster::X509_Ptr cert( |
| PEM_read_bio_X509(certbio.get(), nullptr, nullptr, nullptr)); |
| if (!cert || !cert.get()) { |
| return ::keymaster::TranslateLastOpenSslError(); |
| } |
| |
| // Extract Jacobian coordinates from X509 Cert. |
| ::keymaster::EVP_PKEY_Ptr pubKey(X509_get_pubkey(cert.get())); |
| if (!pubKey.get()) { |
| return ::keymaster::TranslateLastOpenSslError(); |
| } |
| EC_KEY* ecKey = EVP_PKEY_get0_EC_KEY(pubKey.get()); |
| if (!ecKey) { |
| return ::keymaster::TranslateLastOpenSslError(); |
| } |
| const EC_POINT* jacobian_coords = EC_KEY_get0_public_key(ecKey); |
| if (!jacobian_coords) { |
| return ::keymaster::TranslateLastOpenSslError(); |
| } |
| |
| // Extract Affine coordinates. |
| bssl::UniquePtr<BIGNUM> x(BN_new()); |
| bssl::UniquePtr<BIGNUM> y(BN_new()); |
| ::keymaster::BN_CTX_Ptr ctx(BN_CTX_new()); |
| if (!ctx.get()) { |
| return ::keymaster::TranslateLastOpenSslError(); |
| } |
| if (!EC_POINT_get_affine_coordinates_GFp(EC_KEY_get0_group(ecKey), |
| jacobian_coords, x.get(), y.get(), |
| ctx.get())) { |
| return ::keymaster::TranslateLastOpenSslError(); |
| } |
| uint8_t* tmp_x = x_coord.data(); |
| if (BN_bn2binpad(x.get(), tmp_x, kAffinePointLength) != kAffinePointLength) { |
| return ::keymaster::TranslateLastOpenSslError(); |
| } |
| uint8_t* tmp_y = y_coord.data(); |
| if (BN_bn2binpad(y.get(), tmp_y, kAffinePointLength) != kAffinePointLength) { |
| return ::keymaster::TranslateLastOpenSslError(); |
| } |
| return KM_ERROR_OK; |
| } |
| |
| keymaster_error_t GenerateEcdsa256KeyFromSeed(bool test_mode, |
| absl::Span<uint8_t> seed, |
| absl::Span<uint8_t> private_key, |
| std::string& private_key_pem, |
| absl::Span<uint8_t> x_coord, |
| absl::Span<uint8_t> y_coord) { |
| // This function is intended to work only in test mode. |
| CHECK(test_mode == true); |
| |
| // Seed Input Validation. |
| if (seed.size() != kSeedSize) { |
| LOG(ERROR) << "Invalid seed size found"; |
| return KM_ERROR_INVALID_ARGUMENT; |
| } |
| |
| // Size Input Validation. |
| if (x_coord.size() != kAffinePointLength || |
| y_coord.size() != kAffinePointLength || |
| private_key.size() != kP256EcdsaPrivateKeyLength) { |
| return KM_ERROR_INVALID_ARGUMENT; |
| } |
| |
| // EC Group Setup. |
| const EC_GROUP* group = nullptr; |
| if (!(group = EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1))) { |
| return ::keymaster::TranslateLastOpenSslError(); |
| } |
| |
| // Key and Context creation. |
| bssl::UniquePtr<EC_KEY> ec_key(EC_KEY_new()); |
| if (!ec_key.get()) { |
| return ::keymaster::TranslateLastOpenSslError(); |
| } |
| bssl::UniquePtr<BN_CTX> ctx(BN_CTX_new()); |
| if (!ctx.get()) { |
| return ::keymaster::TranslateLastOpenSslError(); |
| } |
| |
| // Extract private key from seed. |
| bssl::UniquePtr<BIGNUM> priv_key( |
| BN_bin2bn(seed.data(), seed.size(), nullptr)); |
| if (!priv_key.get()) { |
| return ::keymaster::TranslateLastOpenSslError(); |
| } |
| |
| // Set Group and Private Key. |
| if (EC_KEY_set_group(ec_key.get(), group) != 1) { |
| return ::keymaster::TranslateLastOpenSslError(); |
| } |
| if (EC_KEY_set_private_key(ec_key.get(), priv_key.get()) != 1) { |
| return ::keymaster::TranslateLastOpenSslError(); |
| } |
| |
| // Calculate and Set Public Key. |
| bssl::UniquePtr<EC_POINT> pub_key_point( |
| EC_POINT_new(EC_KEY_get0_group(ec_key.get()))); |
| if (!EC_POINT_mul(EC_KEY_get0_group(ec_key.get()), pub_key_point.get(), |
| priv_key.get(), nullptr, nullptr, nullptr)) { |
| return ::keymaster::TranslateLastOpenSslError(); |
| } |
| EC_KEY_set_public_key(ec_key.get(), pub_key_point.get()); |
| |
| // Derive Public Key. |
| const EC_POINT* jacobian_coords = EC_KEY_get0_public_key(ec_key.get()); |
| if (!jacobian_coords) { |
| return ::keymaster::TranslateLastOpenSslError(); |
| } |
| EC_POINT* pub_key_copy = EC_POINT_new(group); |
| EC_POINT_copy(pub_key_copy, jacobian_coords); |
| if (EC_POINT_mul(group, pub_key_copy, priv_key.get(), nullptr, nullptr, |
| ctx.get()) != 1) { |
| return ::keymaster::TranslateLastOpenSslError(); |
| } |
| |
| // Extract Affine coordinates. |
| bssl::UniquePtr<BIGNUM> x(BN_new()); |
| bssl::UniquePtr<BIGNUM> y(BN_new()); |
| if (!ctx.get()) { |
| return ::keymaster::TranslateLastOpenSslError(); |
| } |
| if (!EC_POINT_get_affine_coordinates_GFp(EC_KEY_get0_group(ec_key.get()), |
| jacobian_coords, x.get(), y.get(), |
| ctx.get())) { |
| return ::keymaster::TranslateLastOpenSslError(); |
| } |
| uint8_t* tmp_x = x_coord.data(); |
| if (BN_bn2binpad(x.get(), tmp_x, kAffinePointLength) != kAffinePointLength) { |
| return ::keymaster::TranslateLastOpenSslError(); |
| } |
| uint8_t* tmp_y = y_coord.data(); |
| if (BN_bn2binpad(y.get(), tmp_y, kAffinePointLength) != kAffinePointLength) { |
| return ::keymaster::TranslateLastOpenSslError(); |
| } |
| |
| // Extract Private Key in PEM format. |
| BN_bn2binpad(priv_key.get(), private_key.data(), private_key.size()); |
| auto error_pem_key = extractEcdsaPEMKey(std::move(ec_key), private_key_pem); |
| if (error_pem_key != KM_ERROR_OK) { |
| return error_pem_key; |
| } |
| |
| return KM_ERROR_OK; |
| } |
| |
| } // namespace arc::keymint::context |