| // Licensed to the Apache Software Foundation (ASF) under one |
| // or more contributor license agreements. See the NOTICE file |
| // distributed with this work for additional information |
| // regarding copyright ownership. The ASF licenses this file |
| // to you under the Apache License, Version 2.0 (the |
| // "License"); you may not use this file except in compliance |
| // with the License. You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, |
| // software distributed under the License is distributed on an |
| // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| // KIND, either express or implied. See the License for the |
| // specific language governing permissions and limitations |
| // under the License. |
| |
| #include "common/encryption_util.h" |
| |
| #include <gen_cpp/cloud.pb.h> |
| #include <glog/logging.h> |
| #include <math.h> |
| #include <openssl/err.h> |
| #include <openssl/evp.h> |
| #include <openssl/ossl_typ.h> |
| #include <sys/types.h> |
| |
| #include <cstring> |
| #include <memory> |
| #include <random> |
| #include <string> |
| #include <string_view> |
| #include <thread> |
| #include <unordered_map> |
| |
| #include "common/config.h" |
| #include "common/kms.h" |
| #include "common/logging.h" |
| #include "common/util.h" |
| #include "cpp/sync_point.h" |
| #include "meta-store/keys.h" |
| #include "meta-store/txn_kv.h" |
| #include "meta-store/txn_kv_error.h" |
| |
| namespace doris::cloud { |
| namespace config { |
| extern std::string encryption_key; |
| }; // namespace config |
| |
| enum class EncryptionMode { |
| AES_128_ECB, |
| AES_256_ECB, |
| AES_128_CBC, |
| AES_256_CBC, |
| AES_128_CFB, |
| AES_256_CFB, |
| AES_128_CFB1, |
| AES_256_CFB1, |
| AES_128_CFB8, |
| AES_256_CFB8, |
| AES_128_CFB128, |
| AES_256_CFB128, |
| AES_128_CTR, |
| AES_256_CTR, |
| AES_128_OFB, |
| AES_256_OFB, |
| }; |
| |
| enum EncryptionState { AES_SUCCESS = 0, AES_BAD_DATA = -1 }; |
| |
| class EncryptionUtil { |
| public: |
| static int encrypt(EncryptionMode mode, const unsigned char* source, uint32_t source_length, |
| const unsigned char* key, uint32_t key_length, const char* iv_str, |
| int iv_input_length, bool padding, unsigned char* encrypt); |
| |
| static int decrypt(EncryptionMode mode, const unsigned char* encrypt, uint32_t encrypt_length, |
| const unsigned char* key, uint32_t key_length, const char* iv_str, |
| int iv_input_length, bool padding, unsigned char* decrypt_content); |
| }; |
| |
| // aes encrypt/dencrypt |
| static const int ENCRYPTION_MAX_KEY_LENGTH = 256; |
| |
| const EVP_CIPHER* get_evp_type(const EncryptionMode mode) { |
| switch (mode) { |
| case EncryptionMode::AES_128_ECB: |
| return EVP_aes_128_ecb(); |
| case EncryptionMode::AES_128_CBC: |
| return EVP_aes_128_cbc(); |
| case EncryptionMode::AES_128_CFB: |
| return EVP_aes_128_cfb(); |
| case EncryptionMode::AES_128_CFB1: |
| return EVP_aes_128_cfb1(); |
| case EncryptionMode::AES_128_CFB8: |
| return EVP_aes_128_cfb8(); |
| case EncryptionMode::AES_128_CFB128: |
| return EVP_aes_128_cfb128(); |
| case EncryptionMode::AES_128_CTR: |
| return EVP_aes_128_ctr(); |
| case EncryptionMode::AES_128_OFB: |
| return EVP_aes_128_ofb(); |
| case EncryptionMode::AES_256_ECB: |
| return EVP_aes_256_ecb(); |
| case EncryptionMode::AES_256_CBC: |
| return EVP_aes_256_cbc(); |
| case EncryptionMode::AES_256_CFB: |
| return EVP_aes_256_cfb(); |
| case EncryptionMode::AES_256_CFB1: |
| return EVP_aes_256_cfb1(); |
| case EncryptionMode::AES_256_CFB8: |
| return EVP_aes_256_cfb8(); |
| case EncryptionMode::AES_256_CFB128: |
| return EVP_aes_256_cfb128(); |
| case EncryptionMode::AES_256_CTR: |
| return EVP_aes_256_ctr(); |
| case EncryptionMode::AES_256_OFB: |
| return EVP_aes_256_ofb(); |
| default: |
| return nullptr; |
| } |
| } |
| |
| static uint mode_key_sizes[] = { |
| 128 /* AES_128_ECB */, 256 /* AES_256_ECB */, 128 /* AES_128_CBC */, |
| 256 /* AES_256_CBC */, 128 /* AES_128_CFB */, 256 /* AES_256_CFB */, |
| 128 /* AES_128_CFB1 */, 256 /* AES_256_CFB1 */, 128 /* AES_128_CFB8 */, |
| 256 /* AES_256_CFB8 */, 128 /* AES_128_CFB128 */, 256 /* AES_256_CFB128 */, |
| 128 /* AES_128_CTR */, 256 /* AES_256_CTR */, 128 /* AES_128_OFB */, |
| 256 /* AES_256_OFB */, |
| }; |
| |
| static void create_key(const unsigned char* origin_key, uint32_t key_length, uint8_t* encrypt_key, |
| EncryptionMode mode) { |
| const uint key_size = mode_key_sizes[int(mode)] / 8; |
| uint8_t* origin_key_end = ((uint8_t*)origin_key) + key_length; /* origin key boundary*/ |
| |
| uint8_t* encrypt_key_end; /* encrypt key boundary */ |
| encrypt_key_end = encrypt_key + key_size; |
| |
| std::memset(encrypt_key, 0, key_size); /* initialize key */ |
| |
| uint8_t* ptr; /* Start of the encrypt key*/ |
| uint8_t* origin_ptr; /* Start of the origin key */ |
| for (ptr = encrypt_key, origin_ptr = (uint8_t*)origin_key; origin_ptr < origin_key_end; |
| ptr++, origin_ptr++) { |
| if (ptr == encrypt_key_end) { |
| /* loop over origin key until we used all key */ |
| ptr = encrypt_key; |
| } |
| *ptr ^= *origin_ptr; |
| } |
| } |
| |
| static int do_encrypt(EVP_CIPHER_CTX* cipher_ctx, const EVP_CIPHER* cipher, |
| const unsigned char* source, uint32_t source_length, |
| const unsigned char* encrypt_key, const unsigned char* iv, bool padding, |
| unsigned char* encrypt, int* length_ptr) { |
| int ret = EVP_EncryptInit(cipher_ctx, cipher, encrypt_key, iv); |
| if (ret == 0) { |
| return ret; |
| } |
| ret = EVP_CIPHER_CTX_set_padding(cipher_ctx, padding); |
| if (ret == 0) { |
| return ret; |
| } |
| int u_len = 0; |
| |
| ret = EVP_EncryptUpdate(cipher_ctx, encrypt, &u_len, source, source_length); |
| if (ret == 0) { |
| return ret; |
| } |
| int f_len = 0; |
| ret = EVP_EncryptFinal(cipher_ctx, encrypt + u_len, &f_len); |
| *length_ptr = u_len + f_len; |
| return ret; |
| } |
| |
| int EncryptionUtil::encrypt(EncryptionMode mode, const unsigned char* source, |
| uint32_t source_length, const unsigned char* key, uint32_t key_length, |
| const char* iv_str, int iv_input_length, bool padding, |
| unsigned char* encrypt) { |
| const EVP_CIPHER* cipher = get_evp_type(mode); |
| /* The encrypt key to be used for encryption */ |
| unsigned char encrypt_key[ENCRYPTION_MAX_KEY_LENGTH / 8]; |
| create_key(key, key_length, encrypt_key, mode); |
| |
| int iv_length = EVP_CIPHER_iv_length(cipher); |
| if (cipher == nullptr || (iv_length > 0 && !iv_str)) { |
| return AES_BAD_DATA; |
| } |
| char* init_vec = nullptr; |
| std::string iv_default("SELECTDBCLOUD___"); |
| |
| if (iv_str) { |
| init_vec = iv_default.data(); |
| memcpy(init_vec, iv_str, std::min(iv_input_length, EVP_MAX_IV_LENGTH)); |
| init_vec[iv_length] = '\0'; |
| } |
| EVP_CIPHER_CTX* cipher_ctx = EVP_CIPHER_CTX_new(); |
| EVP_CIPHER_CTX_reset(cipher_ctx); |
| int length = 0; |
| int ret = do_encrypt(cipher_ctx, cipher, source, source_length, encrypt_key, |
| reinterpret_cast<unsigned char*>(init_vec), padding, encrypt, &length); |
| EVP_CIPHER_CTX_free(cipher_ctx); |
| if (ret == 0) { |
| ERR_clear_error(); |
| return AES_BAD_DATA; |
| } else { |
| return length; |
| } |
| } |
| |
| static int do_decrypt(EVP_CIPHER_CTX* cipher_ctx, const EVP_CIPHER* cipher, |
| const unsigned char* encrypt, uint32_t encrypt_length, |
| const unsigned char* encrypt_key, const unsigned char* iv, bool padding, |
| unsigned char* decrypt_content, int* length_ptr) { |
| int ret = EVP_DecryptInit(cipher_ctx, cipher, encrypt_key, iv); |
| if (ret == 0) { |
| return ret; |
| } |
| ret = EVP_CIPHER_CTX_set_padding(cipher_ctx, padding); |
| if (ret == 0) { |
| return ret; |
| } |
| int u_len = 0; |
| ret = EVP_DecryptUpdate(cipher_ctx, decrypt_content, &u_len, encrypt, encrypt_length); |
| if (ret == 0) { |
| return ret; |
| } |
| int f_len = 0; |
| ret = EVP_DecryptFinal_ex(cipher_ctx, decrypt_content + u_len, &f_len); |
| *length_ptr = u_len + f_len; |
| return ret; |
| } |
| |
| int EncryptionUtil::decrypt(EncryptionMode mode, const unsigned char* encrypt, |
| uint32_t encrypt_length, const unsigned char* key, uint32_t key_length, |
| const char* iv_str, int iv_input_length, bool padding, |
| unsigned char* decrypt_content) { |
| const EVP_CIPHER* cipher = get_evp_type(mode); |
| |
| /* The encrypt key to be used for decryption */ |
| unsigned char encrypt_key[ENCRYPTION_MAX_KEY_LENGTH / 8]; |
| create_key(key, key_length, encrypt_key, mode); |
| |
| int iv_length = EVP_CIPHER_iv_length(cipher); |
| if (cipher == nullptr || (iv_length > 0 && !iv_str)) { |
| return AES_BAD_DATA; |
| } |
| char* init_vec = nullptr; |
| std::string iv_default("SELECTDBCLOUD___"); |
| |
| if (iv_str) { |
| init_vec = iv_default.data(); |
| memcpy(init_vec, iv_str, std::min(iv_input_length, EVP_MAX_IV_LENGTH)); |
| init_vec[iv_length] = '\0'; |
| } |
| EVP_CIPHER_CTX* cipher_ctx = EVP_CIPHER_CTX_new(); |
| EVP_CIPHER_CTX_reset(cipher_ctx); |
| int length = 0; |
| int ret = do_decrypt(cipher_ctx, cipher, encrypt, encrypt_length, encrypt_key, |
| reinterpret_cast<unsigned char*>(init_vec), padding, decrypt_content, |
| &length); |
| EVP_CIPHER_CTX_free(cipher_ctx); |
| if (ret > 0) { |
| return length; |
| } else { |
| ERR_clear_error(); |
| return AES_BAD_DATA; |
| } |
| } |
| |
| // base64 endcode/decode |
| |
| static char encoding_table[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', |
| 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', |
| 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', |
| 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', |
| '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'}; |
| |
| static const char base64_pad = '='; |
| |
| static short decoding_table[256] = { |
| -2, -2, -2, -2, -2, -2, -2, -2, -2, -1, -1, -2, -2, -1, -2, -2, -2, -2, -2, -2, -2, -2, |
| -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -1, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, 62, |
| -2, -2, -2, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -2, -2, -2, -2, -2, -2, -2, 0, |
| 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, |
| 23, 24, 25, -2, -2, -2, -2, -2, -2, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, |
| 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -2, -2, -2, -2, -2, -2, -2, -2, -2, |
| -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, |
| -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, |
| -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, |
| -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, |
| -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, |
| -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2}; |
| |
| static int mod_table[] = {0, 2, 1}; |
| |
| size_t base64_encode(const unsigned char* data, size_t length, unsigned char* encoded_data) { |
| size_t output_length = (size_t)(4.0 * ceil((double)length / 3.0)); |
| |
| if (encoded_data == nullptr) { |
| return 0; |
| } |
| |
| for (uint32_t i = 0, j = 0; i < length;) { |
| uint32_t octet_a = i < length ? data[i++] : 0; |
| uint32_t octet_b = i < length ? data[i++] : 0; |
| uint32_t octet_c = i < length ? data[i++] : 0; |
| uint32_t triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c; |
| |
| encoded_data[j++] = encoding_table[(triple >> 3 * 6) & 0x3F]; |
| encoded_data[j++] = encoding_table[(triple >> 2 * 6) & 0x3F]; |
| encoded_data[j++] = encoding_table[(triple >> 1 * 6) & 0x3F]; |
| encoded_data[j++] = encoding_table[(triple >> 0 * 6) & 0x3F]; |
| } |
| |
| for (int i = 0; i < mod_table[length % 3]; i++) { |
| encoded_data[output_length - 1 - i] = '='; |
| } |
| |
| return output_length; |
| } |
| |
| size_t base64_decode(const char* data, size_t length, char* decoded_data) { |
| const char* current = data; |
| size_t ch = 0; |
| size_t i = 0; |
| size_t j = 0; |
| size_t k = 0; |
| |
| // run through the whole string, converting as we go |
| while ((ch = *current++) != '\0' && length-- > 0) { |
| if (ch >= 256 || ch < 0) { |
| return -1; |
| } |
| |
| if (ch == base64_pad) { |
| if (*current != '=' && (i % 4) == 1) { |
| return -1; |
| } |
| continue; |
| } |
| |
| ch = decoding_table[ch]; |
| // a space or some other separator character, we simply skip over |
| if (ch == -1) { |
| continue; |
| } else if (ch == -2) { |
| return -1; |
| } |
| |
| switch (i % 4) { |
| case 0: |
| decoded_data[j] = ch << 2; |
| break; |
| case 1: |
| decoded_data[j++] |= ch >> 4; |
| decoded_data[j] = (ch & 0x0f) << 4; |
| break; |
| case 2: |
| decoded_data[j++] |= ch >> 2; |
| decoded_data[j] = (ch & 0x03) << 6; |
| break; |
| case 3: |
| decoded_data[j++] |= ch; |
| break; |
| default: |
| break; |
| } |
| |
| i++; |
| } |
| |
| k = j; |
| /* mop things up if we ended on a boundary */ |
| if (ch == base64_pad) { |
| switch (i % 4) { |
| case 1: |
| return 0; |
| case 2: |
| k++; |
| case 3: |
| decoded_data[k] = 0; |
| default: |
| break; |
| } |
| } |
| |
| decoded_data[j] = '\0'; |
| |
| return j; |
| } |
| |
| // encrypt/dencrypt with base64 |
| |
| static std::unordered_map<std::string, EncryptionMode> to_encryption_mode { |
| {"AES_128_ECB", EncryptionMode::AES_128_ECB}, |
| {"AES_256_ECB", EncryptionMode::AES_256_ECB}, |
| {"AES_128_CBC", EncryptionMode::AES_128_CBC}, |
| {"AES_256_CBC", EncryptionMode::AES_256_CBC}, |
| {"AES_128_CFB", EncryptionMode::AES_128_CFB}, |
| {"AES_256_CFB", EncryptionMode::AES_256_CFB}, |
| {"AES_128_CFB1", EncryptionMode::AES_128_CFB1}, |
| {"AES_256_CFB1", EncryptionMode::AES_256_CFB1}, |
| {"AES_128_CFB8", EncryptionMode::AES_128_CFB8}, |
| {"AES_256_CFB8", EncryptionMode::AES_256_CFB8}, |
| {"AES_128_CFB128", EncryptionMode::AES_128_CFB128}, |
| {"AES_256_CFB128", EncryptionMode::AES_256_CFB128}, |
| {"AES_128_CTR", EncryptionMode::AES_128_CTR}, |
| {"AES_256_CTR", EncryptionMode::AES_256_CTR}, |
| {"AES_128_OFB", EncryptionMode::AES_128_OFB}, |
| {"AES_256_OFB", EncryptionMode::AES_256_OFB}, |
| }; |
| |
| static inline int encrypt_to_base64_impl(std::string_view source, EncryptionMode mode, |
| const std::string& key, std::string* encrypt) { |
| /* |
| * Buffer for ciphertext. Ensure the buffer is long enough for the |
| * ciphertext which may be longer than the plaintext, depending on the |
| * algorithm and mode. |
| */ |
| int cipher_len = source.length() + 16; |
| std::string cipher_text(cipher_len, '0'); |
| int cipher_text_len = EncryptionUtil::encrypt( |
| mode, (unsigned char*)source.data(), source.length(), (unsigned char*)key.c_str(), |
| key.length(), nullptr, 0, true, (unsigned char*)cipher_text.data()); |
| if (cipher_text_len < 0) { |
| return -1; |
| } |
| |
| int encoded_len = (size_t)(4.0 * ceil(cipher_text_len / 3.0)); |
| std::string encoded_text(encoded_len, '0'); |
| int encoded_text_len = base64_encode((unsigned char*)cipher_text.data(), cipher_text_len, |
| (unsigned char*)encoded_text.data()); |
| if (encoded_text_len < 0) { |
| return -1; |
| } |
| encrypt->assign((char*)encoded_text.data(), encoded_text_len); |
| return 0; |
| } |
| |
| static int encrypt_to_base64(std::string_view source, const std::string& encrypt_method, |
| const std::string& key, std::string* encrypt) { |
| if (source.empty()) { |
| *encrypt = ""; |
| return 0; |
| } |
| auto iter = to_encryption_mode.find(encrypt_method); |
| if (iter == to_encryption_mode.end()) { |
| return -1; |
| } |
| |
| return encrypt_to_base64_impl(source, iter->second, key, encrypt); |
| } |
| |
| static inline int decrypt_with_base64_impl(std::string_view encrypt, EncryptionMode mode, |
| const std::string& key, std::string* source) { |
| // base64 |
| std::unique_ptr<char[]> decoded_text(new char[encrypt.length()]); |
| int decoded_text_len = base64_decode(encrypt.data(), encrypt.length(), decoded_text.get()); |
| if (decoded_text_len < 0) { |
| return -1; |
| } |
| |
| std::unique_ptr<char[]> plain_text(new char[decoded_text_len]); |
| int plain_text_len = EncryptionUtil::decrypt( |
| mode, (unsigned char*)decoded_text.get(), decoded_text_len, (unsigned char*)key.c_str(), |
| key.length(), nullptr, 0, true, (unsigned char*)plain_text.get()); |
| if (plain_text_len < 0) { |
| return -1; |
| } |
| source->assign(plain_text.get(), plain_text_len); |
| return 0; |
| } |
| |
| static int decrypt_with_base64(std::string_view encrypt, const std::string& encrypt_method, |
| const std::string& key, std::string* source) { |
| if (encrypt.empty()) { |
| *source = ""; |
| return 0; |
| } |
| auto iter = to_encryption_mode.find(encrypt_method); |
| if (iter == to_encryption_mode.end()) { |
| return -1; |
| } |
| return decrypt_with_base64_impl(encrypt, iter->second, key, source); |
| } |
| |
| int encrypt_ak_sk(AkSkRef plain_ak_sk, const std::string& encryption_method, |
| const std::string& encryption_key, AkSkPair* cipher_ak_sk) { |
| std::string encrypt_ak; |
| std::string encrypt_sk; |
| if (encrypt_to_base64(plain_ak_sk.second, encryption_method, encryption_key, &encrypt_sk) != |
| 0) { |
| *cipher_ak_sk = {"", ""}; |
| return -1; |
| } |
| *cipher_ak_sk = {std::string(plain_ak_sk.first), std::move(encrypt_sk)}; |
| return 0; |
| } |
| |
| int decrypt_ak_sk(AkSkRef cipher_ak_sk, const std::string& encryption_method, |
| const std::string& encryption_key, AkSkPair* plain_ak_sk) { |
| std::string ak; |
| std::string sk; |
| if (decrypt_with_base64(cipher_ak_sk.second, encryption_method, encryption_key, &sk) != 0) { |
| *plain_ak_sk = {"", ""}; |
| return -1; |
| } |
| *plain_ak_sk = {std::string(cipher_ak_sk.first), std::move(sk)}; |
| return 0; |
| } |
| |
| int decrypt_ak_sk_helper(std::string_view cipher_ak, std::string_view cipher_sk, |
| const EncryptionInfoPB& encryption_info, AkSkPair* plain_ak_sk_pair) { |
| std::string key; |
| int ret = get_encryption_key_for_ak_sk(encryption_info.key_id(), &key); |
| { TEST_SYNC_POINT_CALLBACK("decrypt_ak_sk:get_encryption_key", &key, &ret); } |
| if (ret != 0) { |
| LOG(WARNING) << "failed to get encryption key version_id: " << encryption_info.key_id(); |
| return -1; |
| } |
| ret = decrypt_ak_sk({cipher_ak, cipher_sk}, encryption_info.encryption_method(), key, |
| plain_ak_sk_pair); |
| if (ret != 0) { |
| LOG(WARNING) << "failed to decrypt"; |
| return -1; |
| } |
| return 0; |
| } |
| |
| /** |
| * @brief Generates a random root key. If a root key already exists, returns immediately. |
| * |
| * @param txn_kv |
| * @param kms_client |
| * @param plaintext store the plaintext of the root key |
| * @param encoded_ciphertext store the base64-encoded ciphertext of the root key. |
| * @return int 0 for success to generate, 1 for not need to generate, -1 for failure. |
| */ |
| static int generate_random_root_key(TxnKv* txn_kv, KmsClient* kms_client, std::string* plaintext, |
| std::string* encoded_ciphertext) { |
| /** |
| * 1. If KMS is enabled, use KMS to generate a new key. |
| * 2. If KMS is not enabled, try using the encryption_key from the configuration, which must be in Base64 format. |
| * 3. If no key is found in the configuration, generate a random key in memory. |
| */ |
| std::string key = system_meta_service_encryption_key_info_key(); |
| std::string val; |
| std::unique_ptr<Transaction> txn; |
| TxnErrorCode err = txn_kv->create_txn(&txn); |
| if (err != TxnErrorCode::TXN_OK) { |
| LOG_WARNING("failed to create txn").tag("err", err); |
| return -1; |
| } |
| err = txn->get(key, &val); |
| if (err != TxnErrorCode::TXN_OK && err != TxnErrorCode::TXN_KEY_NOT_FOUND) { |
| LOG_WARNING("failed to get key of encryption_key_info").tag("err", err); |
| return -1; |
| } |
| |
| if (err == TxnErrorCode::TXN_OK) { |
| if (config::enable_kms && config::focus_add_kms_data_key) { |
| EncryptionKeyInfoPB key_info; |
| if (!key_info.ParseFromString(val)) { |
| LOG_WARNING("failed to parse encryption_root_key"); |
| return -1; |
| } |
| for (const auto& item : key_info.items()) { |
| if (item.has_kms_info()) { |
| return 1; |
| } |
| } |
| LOG(INFO) << "focus to create kms data key"; |
| } else { |
| LOG(INFO) << "not need to generate root key"; |
| return 1; |
| } |
| } |
| |
| // 1. use kms to generate a new key |
| if (config::enable_kms) { |
| if (kms_client == nullptr) { |
| LOG_WARNING("no kms client"); |
| return -1; |
| } |
| std::string encoded_root_key_ciphertext; |
| std::string encoded_root_key_plaintext; |
| if (kms_client->generate_data_key(&encoded_root_key_ciphertext, |
| &encoded_root_key_plaintext) != 0) { |
| LOG_WARNING("failed to generate data key"); |
| return -1; |
| } |
| if (encoded_root_key_ciphertext.empty() || encoded_root_key_plaintext.empty()) { |
| LOG_WARNING("empty data key generated"); |
| return -1; |
| } |
| |
| // decode plaintext |
| std::string root_key_plaintext(encoded_root_key_plaintext.length(), '0'); |
| int decoded_len = |
| base64_decode(encoded_root_key_plaintext.c_str(), |
| encoded_root_key_plaintext.length(), root_key_plaintext.data()); |
| if (decoded_len < 0) { |
| LOG_WARNING("failed to decode plaintext of kms"); |
| return -1; |
| } |
| root_key_plaintext.assign(root_key_plaintext.data(), decoded_len); |
| |
| *plaintext = std::move(root_key_plaintext); |
| *encoded_ciphertext = std::move(encoded_root_key_ciphertext); |
| return 0; |
| } |
| |
| // 2. try using the encryption_key from the configuration |
| if (!cloud::config::encryption_key.empty()) { |
| std::string decoded_string(cloud::config::encryption_key.length(), '0'); |
| int decoded_text_len = |
| base64_decode(cloud::config::encryption_key.c_str(), |
| cloud::config::encryption_key.length(), decoded_string.data()); |
| if (decoded_text_len < 0) { |
| LOG_WARNING("fail to decode encryption_key in config"); |
| return -1; |
| } |
| decoded_string.assign(decoded_string.data(), decoded_text_len); |
| *plaintext = std::move(decoded_string); |
| *encoded_ciphertext = cloud::config::encryption_key; |
| return 0; |
| } |
| |
| // 3. otherwise, generate a random data key in memory |
| std::mt19937 rnd(time(nullptr)); |
| std::uniform_int_distribution<short> dist(std::numeric_limits<char>::min(), |
| std::numeric_limits<char>::max()); |
| std::string root_key_plaintext(32, '0'); |
| for (char& i : root_key_plaintext) { |
| i = (char)dist(rnd); |
| } |
| |
| // encode in base64 |
| int key_len = root_key_plaintext.length(); |
| int encoded_len = (size_t)(4.0 * ceil(key_len / 3.0)); |
| std::string encoded_text(encoded_len, '0'); |
| int encoded_text_len = base64_encode((unsigned char*)root_key_plaintext.data(), key_len, |
| (unsigned char*)encoded_text.data()); |
| if (encoded_text_len < 0) { |
| LOG_WARNING("failed to encode encryption_key"); |
| return -1; |
| } |
| std::string encoded_root_key_ciphertext; |
| encoded_root_key_ciphertext.assign(encoded_text.data(), encoded_text_len); |
| |
| *plaintext = std::move(root_key_plaintext); |
| *encoded_ciphertext = std::move(encoded_root_key_ciphertext); |
| return 0; |
| } |
| |
| // Todo: Does not need to be locked now, only generated when the process is initialized |
| std::map<int64_t, std::string> global_encryption_key_info_map; // key_id->encryption_key |
| |
| static int get_current_root_keys(TxnKv* txn_kv, std::map<int64_t, std::string>* keys) { |
| std::unique_ptr<KmsClient> kms_client; |
| if (config::enable_kms) { |
| if (config::kms_info_encryption_key.empty() || config::kms_info_encryption_method.empty() || |
| config::kms_ak.empty() || config::kms_sk.empty()) { |
| LOG_WARNING("incorrect kms conf") |
| .tag("encryption_key", config::kms_info_encryption_key) |
| .tag("encryption_method", config::kms_info_encryption_method) |
| .tag("ak", config::kms_ak) |
| .tag("sk", config::kms_sk); |
| return -1; |
| } |
| std::string decoded_encryption_key(config::kms_info_encryption_key.length(), '0'); |
| int decoded_key_len = cloud::base64_decode(config::kms_info_encryption_key.c_str(), |
| config::kms_info_encryption_key.length(), |
| decoded_encryption_key.data()); |
| decoded_encryption_key.assign(decoded_encryption_key.data(), decoded_key_len); |
| AkSkPair out; |
| if (decrypt_ak_sk({config::kms_ak, config::kms_sk}, config::kms_info_encryption_method, |
| decoded_encryption_key, &out) != 0) { |
| LOG_WARNING("failed to decrypt kms info"); |
| return -1; |
| } |
| |
| KmsConf conf {out.first, out.second, config::kms_endpoint, |
| config::kms_region, config::kms_cmk, config::kms_provider}; |
| |
| auto ret = create_kms_client(std::move(conf), &kms_client); |
| if (ret != 0) { |
| LOG_WARNING("failed to create kms client").tag("ret", ret); |
| return -1; |
| } |
| ret = kms_client->init(); |
| if (ret != 0) { |
| LOG_WARNING("failed to init kms client").tag("ret", ret); |
| return -1; |
| } |
| } |
| |
| // To avoid transaction timeouts, it is necessary to first generate a root key |
| std::string root_key_plaintext; |
| std::string encoded_root_key_ciphertext; |
| int ret = generate_random_root_key(txn_kv, kms_client.get(), &root_key_plaintext, |
| &encoded_root_key_ciphertext); |
| if (ret == -1) { |
| LOG_WARNING("failed to generate random root key"); |
| return -1; |
| } |
| |
| while (true) { |
| std::string key = system_meta_service_encryption_key_info_key(); |
| std::string val; |
| std::unique_ptr<Transaction> txn; |
| TxnErrorCode err = txn_kv->create_txn(&txn); |
| if (err != TxnErrorCode::TXN_OK) { |
| LOG_WARNING("failed to create txn").tag("ret", ret); |
| return -1; |
| } |
| err = txn->get(key, &val); |
| if (ret != 0 && ret != 1) { |
| LOG_WARNING("failed to get key of encryption_key_info").tag("ret", ret); |
| return -1; |
| } |
| |
| bool need_to_focus_add_kms_data_key = true; |
| EncryptionKeyInfoPB key_info; |
| if (err == TxnErrorCode::TXN_OK) { |
| if (!key_info.ParseFromString(val)) { |
| LOG_WARNING("failed to parse encryption_root_key"); |
| return -1; |
| } |
| |
| LOG_INFO("get server encryption_root_key").tag("key_info", proto_to_json(key_info)); |
| |
| for (const auto& item : key_info.items()) { |
| std::string encoded_root_key_plaintext; |
| if (item.has_kms_info()) { |
| need_to_focus_add_kms_data_key = false; |
| // use kms to decrypt |
| if (kms_client == nullptr) { |
| LOG_WARNING("no kms client"); |
| return -1; |
| } |
| if (item.kms_info().endpoint() != kms_client->conf().endpoint || |
| item.kms_info().region() != kms_client->conf().region) { |
| LOG_WARNING("kms info is not match") |
| .tag("kms endpoint", kms_client->conf().endpoint) |
| .tag("kms region", kms_client->conf().region) |
| .tag("saved endpoint", item.kms_info().endpoint()) |
| .tag("saved region", item.kms_info().region()); |
| return -1; |
| } |
| |
| auto ret = kms_client->decrypt(item.key(), &encoded_root_key_plaintext); |
| if (ret != 0) { |
| LOG_WARNING("failed to decrypt encryption_root_key"); |
| return -1; |
| } |
| } else { |
| encoded_root_key_plaintext = item.key(); // Todo: do not copy |
| } |
| |
| std::string root_key_plaintext(encoded_root_key_plaintext.length(), '0'); |
| int decoded_text_len = base64_decode(encoded_root_key_plaintext.c_str(), |
| encoded_root_key_plaintext.length(), |
| root_key_plaintext.data()); |
| if (decoded_text_len < 0) { |
| LOG_WARNING("failed to decode encryption_root_key"); |
| return -1; |
| } |
| root_key_plaintext.assign(root_key_plaintext.data(), decoded_text_len); |
| keys->insert({item.key_id(), std::move(root_key_plaintext)}); |
| } |
| if (config::enable_kms && config::focus_add_kms_data_key && |
| need_to_focus_add_kms_data_key) { |
| // Todo: need to restart other ms to update global_encryption_key_info_map now |
| LOG(INFO) << "focus to add kms data key"; |
| } else { |
| return 0; |
| } |
| } |
| |
| // encryption_root_key not found, need to save a new root key into fdb |
| if (root_key_plaintext.empty() || encoded_root_key_ciphertext.empty()) { |
| LOG_WARNING("empty new root key"); |
| return -1; |
| } |
| |
| int32_t new_key_id = key_info.items().size() + 1; |
| auto* item = key_info.add_items(); |
| item->set_key_id(new_key_id); |
| item->set_key(encoded_root_key_ciphertext); |
| if (config::enable_kms) { |
| item->mutable_kms_info()->set_endpoint(config::kms_endpoint); |
| item->mutable_kms_info()->set_region(config::kms_region); |
| item->mutable_kms_info()->set_cmk(config::kms_cmk); |
| } |
| |
| val = key_info.SerializeAsString(); |
| if (val.empty()) { |
| LOG_WARNING("failed to serialize"); |
| return -1; |
| } |
| txn->put(key, val); |
| LOG_INFO("put server encryption_key") |
| .tag("encryption_key", encoded_root_key_ciphertext) |
| .tag("key_id", new_key_id); |
| err = txn->commit(); |
| if (err == TxnErrorCode::TXN_CONFLICT) { |
| LOG_WARNING("commit encryption_key is conflicted, retry it later"); |
| std::this_thread::sleep_for(std::chrono::milliseconds(100)); |
| continue; |
| } else if (err != TxnErrorCode::TXN_OK) { |
| LOG_WARNING("failed to commit encryption_key"); |
| return -1; |
| } |
| keys->insert({new_key_id, std::move(root_key_plaintext)}); |
| return 0; |
| } |
| return 0; |
| } |
| |
| int init_global_encryption_key_info_map(TxnKv* txn_kv) { |
| if (get_current_root_keys(txn_kv, &global_encryption_key_info_map) != 0) { |
| return -1; |
| } |
| DCHECK(!global_encryption_key_info_map.empty()); |
| return 0; |
| } |
| |
| } // namespace doris::cloud |