| // 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 <gen_cpp/cloud.pb.h> | |
| #include <gtest/gtest.h> | |
| #include "common/config.h" | |
| #include "common/defer.h" | |
| #include "common/encryption_util.h" | |
| #include "common/logging.h" | |
| #include "common/util.h" | |
| #include "cpp/sync_point.h" | |
| #include "meta-store/keys.h" | |
| #include "meta-store/mem_txn_kv.h" | |
| #include "meta-store/txn_kv.h" | |
| #include "meta-store/txn_kv_error.h" | |
| using namespace doris; | |
| int main(int argc, char** argv) { | |
| auto conf_file = "doris_cloud.conf"; | |
| if (!cloud::config::init(conf_file, true)) { | |
| std::cerr << "failed to init config file, conf=" << conf_file << std::endl; | |
| return -1; | |
| } | |
| if (!cloud::init_glog("encrypt")) { | |
| std::cerr << "failed to init glog" << std::endl; | |
| return -1; | |
| } | |
| ::testing::InitGoogleTest(&argc, argv); | |
| return RUN_ALL_TESTS(); | |
| } | |
| TEST(EncryptionTest, EncryptTest) { | |
| std::string mock_ak = "AKIDOsfsagadsgdfaadgdsgdf"; | |
| std::string mock_sk = "Hx60p12123af234541nsVsffdfsdfghsdfhsdf34t"; | |
| cloud::config::encryption_method = "AES_256_ECB"; | |
| // "selectdbselectdbselectdbselectdb" -> "c2VsZWN0ZGJzZWxlY3RkYnNlbGVjdGRic2VsZWN0ZGI=" | |
| cloud::config::encryption_key = "c2VsZWN0ZGJzZWxlY3RkYnNlbGVjdGRic2VsZWN0ZGI="; | |
| { | |
| std::string decoded_text(cloud::config::encryption_key.length(), '0'); | |
| int decoded_text_len = | |
| cloud::base64_decode(cloud::config::encryption_key.c_str(), | |
| cloud::config::encryption_key.length(), decoded_text.data()); | |
| ASSERT_TRUE(decoded_text_len > 0); | |
| decoded_text.assign(decoded_text.data(), decoded_text_len); | |
| std::cout << "decoded_string: " << decoded_text << std::endl; | |
| ASSERT_EQ(decoded_text, "selectdbselectdbselectdbselectdb"); | |
| int ret; | |
| cloud::AkSkPair cipher_ak_sk_pair; | |
| ret = cloud::encrypt_ak_sk({mock_ak, mock_sk}, cloud::config::encryption_method, | |
| decoded_text, &cipher_ak_sk_pair); | |
| ASSERT_EQ(ret, 0); | |
| std::cout << "cipher ak: " << cipher_ak_sk_pair.first << std::endl; | |
| std::cout << "cipher sk: " << cipher_ak_sk_pair.second << std::endl; | |
| cloud::AkSkPair plain_ak_sk_pair; | |
| ret = cloud::decrypt_ak_sk(cipher_ak_sk_pair, cloud::config::encryption_method, | |
| decoded_text, &plain_ak_sk_pair); | |
| ASSERT_EQ(ret, 0); | |
| std::cout << "plain ak: " << plain_ak_sk_pair.first << std::endl; | |
| std::cout << "plain sk: " << plain_ak_sk_pair.second << std::endl; | |
| ASSERT_EQ(mock_ak, plain_ak_sk_pair.first); | |
| ASSERT_EQ(mock_sk, plain_ak_sk_pair.second); | |
| } | |
| } | |
| TEST(EncryptionTest, RootKeyTestWithoutKms) { | |
| using namespace doris::cloud; | |
| config::enable_kms = false; | |
| // generate new root key | |
| global_encryption_key_info_map.clear(); | |
| auto mem_kv = std::make_shared<MemTxnKv>(); | |
| ASSERT_EQ(mem_kv->init(), 0); | |
| auto ret = init_global_encryption_key_info_map(mem_kv.get()); | |
| ASSERT_EQ(ret, 0); | |
| ASSERT_EQ(global_encryption_key_info_map.size(), 1); | |
| std::string key = system_meta_service_encryption_key_info_key(); | |
| std::string val; | |
| std::unique_ptr<Transaction> txn; | |
| TxnErrorCode err = mem_kv->create_txn(&txn); | |
| ASSERT_EQ(err, TxnErrorCode::TXN_OK); | |
| err = txn->get(key, &val); | |
| ASSERT_EQ(err, TxnErrorCode::TXN_OK); | |
| EncryptionKeyInfoPB key_info; | |
| key_info.ParseFromString(val); | |
| ASSERT_EQ(key_info.items_size(), 1); | |
| std::cout << proto_to_json(key_info) << std::endl; | |
| const auto& item = key_info.items().at(0); | |
| ASSERT_EQ(item.key_id(), 1); | |
| std::string decoded_string(item.key().length(), '0'); | |
| int decoded_text_len = | |
| base64_decode(item.key().c_str(), item.key().length(), decoded_string.data()); | |
| decoded_string.assign(decoded_string.data(), decoded_text_len); | |
| ASSERT_EQ(global_encryption_key_info_map.at(item.key_id()), decoded_string); | |
| // get existed root key | |
| global_encryption_key_info_map.clear(); | |
| ret = init_global_encryption_key_info_map(mem_kv.get()); | |
| ASSERT_EQ(ret, 0); | |
| ASSERT_EQ(global_encryption_key_info_map.size(), 1); | |
| ASSERT_EQ(global_encryption_key_info_map.at(item.key_id()), decoded_string); | |
| } | |
| TEST(EncryptionTest, DecryptKmsInfo) { | |
| std::string plaintext = "AKIDsZHqgyhdDSRBpfaNtHPHN0Wnpci"; | |
| std::string encryption_key = "cloud"; | |
| // use `echo -n "$plaintext" | openssl enc -aes-256-ecb -e -a -pbkdf2 -pass pass:"$encryption_key" -p -nosalt` | |
| // to create hex_encryption_key and ciphertext | |
| // then use `echo -n "$hex_encryption_key" | xxd -r -p | base64` to create encoded_encryption_key | |
| std::string ciphertext = "DRdlYbJmyEPJ9q1KggTCjBErv/9GzyjTFKXBgGR7X4I="; | |
| std::string encoded_encryption_key = "uwPXjGTuFJXyDZJBuYG52kdMxrWB24952HkXSa2v3Vw="; | |
| std::string decoded_key(encoded_encryption_key.length(), '0'); | |
| int decoded_key_len = cloud::base64_decode(encoded_encryption_key.c_str(), | |
| encoded_encryption_key.length(), decoded_key.data()); | |
| ASSERT_TRUE(decoded_key_len > 0); | |
| decoded_key.assign(decoded_key.data(), decoded_key_len); | |
| std::cout << "decoded_string: " << cloud::hex(decoded_key) << std::endl; | |
| cloud::AkSkPair out; | |
| auto ret = cloud::decrypt_ak_sk({"", ciphertext}, "AES_256_ECB", decoded_key, &out); | |
| ASSERT_EQ(ret, 0); | |
| ASSERT_EQ(out.second, plaintext); | |
| } | |
| TEST(EncryptionTest, RootKeyTestWithKms1) { | |
| using namespace doris::cloud; | |
| config::enable_kms = true; | |
| // incorrect kms conf | |
| // case1: empty param | |
| config::kms_ak = ""; | |
| config::kms_sk = "DRdlYbJmyEPJ9q1KggTCjBErv/9GzyjTFKXBgGR7X4I="; | |
| config::kms_cmk = "2"; | |
| config::kms_endpoint = "3"; | |
| config::kms_region = "4"; | |
| config::kms_provider = "ali"; | |
| config::kms_info_encryption_key = "uwPXjGTuFJXyDZJBuYG52kdMxrWB24952HkXSa2v3Vw="; | |
| global_encryption_key_info_map.clear(); | |
| auto mem_kv = std::make_shared<MemTxnKv>(); | |
| ASSERT_EQ(mem_kv->init(), 0); | |
| auto ret = init_global_encryption_key_info_map(mem_kv.get()); | |
| ASSERT_EQ(ret, -1); | |
| ASSERT_TRUE(global_encryption_key_info_map.empty()); | |
| // case2: only support ali cloud | |
| config::kms_ak = "DRdlYbJmyEPJ9q1KggTCjBErv/9GzyjTFKXBgGR7X4I="; | |
| config::kms_provider = "tx"; // only support ali cloud | |
| ret = init_global_encryption_key_info_map(mem_kv.get()); | |
| ASSERT_EQ(ret, -1); | |
| ASSERT_TRUE(global_encryption_key_info_map.empty()); | |
| } | |
| TEST(EncryptionTest, RootKeyTestWithKms2) { | |
| using namespace doris::cloud; | |
| config::enable_kms = true; | |
| config::kms_ak = "DRdlYbJmyEPJ9q1KggTCjBErv/9GzyjTFKXBgGR7X4I="; | |
| config::kms_sk = "DRdlYbJmyEPJ9q1KggTCjBErv/9GzyjTFKXBgGR7X4I="; | |
| config::kms_cmk = "2"; | |
| config::kms_endpoint = "3"; | |
| config::kms_region = "4"; | |
| config::kms_provider = "ali"; | |
| config::kms_info_encryption_key = "uwPXjGTuFJXyDZJBuYG52kdMxrWB24952HkXSa2v3Vw="; | |
| auto mem_kv = std::make_shared<MemTxnKv>(); | |
| ASSERT_EQ(mem_kv->init(), 0); | |
| // clear for generating new root key | |
| global_encryption_key_info_map.clear(); | |
| // Generate data key failed | |
| { | |
| // mock falied to generate key | |
| auto sp = SyncPoint::get_instance(); | |
| DORIS_CLOUD_DEFER { | |
| SyncPoint::get_instance()->clear_all_call_backs(); | |
| }; | |
| sp->set_call_back("alikms::generate_data_key", [](auto&& args) { | |
| auto* ret = try_any_cast_ret<int>(args); | |
| ret->first = -1; | |
| ret->second = true; | |
| }); | |
| sp->enable_processing(); | |
| auto ret = init_global_encryption_key_info_map(mem_kv.get()); | |
| ASSERT_EQ(ret, -1); | |
| ASSERT_TRUE(global_encryption_key_info_map.empty()); | |
| } | |
| // "selectdbselectdbselectdbselectdb" -> "c2VsZWN0ZGJzZWxlY3RkYnNlbGVjdGRic2VsZWN0ZGI=" | |
| std::string plaintext = "selectdbselectdbselectdbselectdb"; | |
| std::string mock_encoded_plaintext = "c2VsZWN0ZGJzZWxlY3RkYnNlbGVjdGRic2VsZWN0ZGI="; | |
| std::string mock_encoded_ciphertext = mock_encoded_plaintext; | |
| // Generate data key succeeded | |
| { | |
| // mock succ to generate key | |
| auto sp = SyncPoint::get_instance(); | |
| DORIS_CLOUD_DEFER { | |
| SyncPoint::get_instance()->clear_all_call_backs(); | |
| }; | |
| sp->set_call_back("alikms::generate_data_key", [&](auto&& args) { | |
| auto* ciphertext = try_any_cast<std::string*>(args[0]); | |
| *ciphertext = mock_encoded_ciphertext; | |
| auto* plaintext = try_any_cast<std::string*>(args[1]); | |
| *plaintext = mock_encoded_plaintext; | |
| auto* ret = try_any_cast_ret<int>(args); | |
| ret->first = 0; | |
| ret->second = true; | |
| }); | |
| sp->enable_processing(); | |
| auto ret = init_global_encryption_key_info_map(mem_kv.get()); | |
| ASSERT_EQ(ret, 0); | |
| ASSERT_EQ(global_encryption_key_info_map.size(), 1); | |
| std::string key = system_meta_service_encryption_key_info_key(); | |
| std::string val; | |
| std::unique_ptr<Transaction> txn; | |
| TxnErrorCode err = mem_kv->create_txn(&txn); | |
| ASSERT_EQ(err, TxnErrorCode::TXN_OK); | |
| err = txn->get(key, &val); | |
| ASSERT_EQ(err, TxnErrorCode::TXN_OK); | |
| ASSERT_EQ(ret, 0); | |
| EncryptionKeyInfoPB key_info; | |
| key_info.ParseFromString(val); | |
| ASSERT_EQ(key_info.items_size(), 1); | |
| std::cout << proto_to_json(key_info) << std::endl; | |
| const auto& item = key_info.items().at(0); | |
| ASSERT_EQ(item.key_id(), 1); | |
| ASSERT_EQ(global_encryption_key_info_map.at(item.key_id()), plaintext); | |
| } | |
| // Decryption failed | |
| { | |
| // clear for getting existed root key from memkv | |
| // do not need to mock kms encryption | |
| global_encryption_key_info_map.clear(); | |
| // mock abnormal decryption | |
| auto* sp = SyncPoint::get_instance(); | |
| DORIS_CLOUD_DEFER { | |
| SyncPoint::get_instance()->clear_all_call_backs(); | |
| }; | |
| sp->set_call_back("alikms::decrypt", [](auto&& args) { | |
| auto* ret = try_any_cast_ret<int>(args); | |
| ret->first = -1; | |
| ret->second = true; | |
| }); | |
| sp->enable_processing(); | |
| // memkv already has key info | |
| auto ret = init_global_encryption_key_info_map(mem_kv.get()); | |
| ASSERT_EQ(ret, -1); | |
| ASSERT_TRUE(global_encryption_key_info_map.empty()); | |
| } | |
| // Decryption succeeded | |
| { | |
| auto sp = SyncPoint::get_instance(); | |
| DORIS_CLOUD_DEFER { | |
| SyncPoint::get_instance()->clear_all_call_backs(); | |
| }; | |
| sp->set_call_back("alikms::decrypt", [&](auto&& args) { | |
| auto* output = try_any_cast<std::string*>(args[0]); | |
| *output = mock_encoded_plaintext; | |
| auto* ret = try_any_cast_ret<int>(args); | |
| ret->first = 0; | |
| ret->second = true; | |
| }); | |
| sp->enable_processing(); | |
| auto ret = init_global_encryption_key_info_map(mem_kv.get()); | |
| ASSERT_EQ(ret, 0); | |
| ASSERT_EQ(global_encryption_key_info_map.size(), 1); | |
| ASSERT_EQ(global_encryption_key_info_map.at(1), plaintext); | |
| } | |
| } | |
| TEST(EncryptionTest, RootKeyTestWithKms3) { | |
| // test focus to add kms data key | |
| using namespace doris::cloud; | |
| config::enable_kms = false; | |
| config::enable_kms = false; | |
| // generate new root key without kms | |
| global_encryption_key_info_map.clear(); | |
| auto mem_kv = std::make_shared<MemTxnKv>(); | |
| ASSERT_EQ(mem_kv->init(), 0); | |
| auto ret = init_global_encryption_key_info_map(mem_kv.get()); | |
| ASSERT_EQ(ret, 0); | |
| ASSERT_EQ(global_encryption_key_info_map.size(), 1); | |
| // enable kms but not focus to add kms data key | |
| config::enable_kms = true; | |
| config::focus_add_kms_data_key = false; | |
| config::kms_ak = "DRdlYbJmyEPJ9q1KggTCjBErv/9GzyjTFKXBgGR7X4I="; | |
| config::kms_sk = "DRdlYbJmyEPJ9q1KggTCjBErv/9GzyjTFKXBgGR7X4I="; | |
| config::kms_cmk = "2"; | |
| config::kms_endpoint = "3"; | |
| config::kms_region = "4"; | |
| config::kms_provider = "ali"; | |
| config::kms_info_encryption_key = "uwPXjGTuFJXyDZJBuYG52kdMxrWB24952HkXSa2v3Vw="; | |
| global_encryption_key_info_map.clear(); | |
| ret = init_global_encryption_key_info_map(mem_kv.get()); | |
| ASSERT_EQ(ret, 0); | |
| ASSERT_EQ(global_encryption_key_info_map.size(), 1); | |
| // enable kms and focus to add kms data key | |
| config::focus_add_kms_data_key = true; | |
| std::string plaintext = "test123456test"; | |
| std::string mock_encoded_plaintext = "dGVzdDEyMzQ1NnRlc3Q="; | |
| std::string mock_encoded_ciphertext = mock_encoded_plaintext; | |
| // mock succ to generate key | |
| auto sp = SyncPoint::get_instance(); | |
| DORIS_CLOUD_DEFER { | |
| SyncPoint::get_instance()->clear_all_call_backs(); | |
| }; | |
| sp->set_call_back("alikms::generate_data_key", [&](auto&& args) { | |
| auto* ciphertext = try_any_cast<std::string*>(args[0]); | |
| *ciphertext = mock_encoded_ciphertext; | |
| auto* plaintext = try_any_cast<std::string*>(args[1]); | |
| *plaintext = mock_encoded_plaintext; | |
| auto* ret = try_any_cast_ret<int>(args); | |
| ret->first = 0; | |
| ret->second = true; | |
| }); | |
| sp->set_call_back("alikms::decrypt", [&](auto&& args) { | |
| auto* output = try_any_cast<std::string*>(args[0]); | |
| *output = mock_encoded_plaintext; | |
| auto* ret = try_any_cast_ret<int>(args); | |
| ret->first = 0; | |
| ret->second = true; | |
| }); | |
| sp->enable_processing(); | |
| global_encryption_key_info_map.clear(); | |
| ret = init_global_encryption_key_info_map(mem_kv.get()); | |
| ASSERT_EQ(ret, 0); | |
| ASSERT_EQ(global_encryption_key_info_map.size(), 2); | |
| // get again | |
| global_encryption_key_info_map.clear(); | |
| ret = init_global_encryption_key_info_map(mem_kv.get()); | |
| ASSERT_EQ(global_encryption_key_info_map.size(), 2); | |
| // finally check kv | |
| std::string key = system_meta_service_encryption_key_info_key(); | |
| std::string val; | |
| std::unique_ptr<Transaction> txn; | |
| TxnErrorCode err = mem_kv->create_txn(&txn); | |
| ASSERT_EQ(err, TxnErrorCode::TXN_OK); | |
| err = txn->get(key, &val); | |
| ASSERT_EQ(err, TxnErrorCode::TXN_OK); | |
| EncryptionKeyInfoPB key_info; | |
| key_info.ParseFromString(val); | |
| ASSERT_EQ(key_info.items_size(), 2); | |
| std::cout << proto_to_json(key_info) << std::endl; | |
| const auto& item = key_info.items().at(0); | |
| ASSERT_EQ(item.key_id(), 1); | |
| std::string decoded_string(item.key().length(), '0'); | |
| int decoded_text_len = | |
| base64_decode(item.key().c_str(), item.key().length(), decoded_string.data()); | |
| decoded_string.assign(decoded_string.data(), decoded_text_len); | |
| ASSERT_EQ(decoded_string, "selectdbselectdbselectdbselectdb"); // from config | |
| ASSERT_EQ(global_encryption_key_info_map.at(item.key_id()), decoded_string); | |
| const auto& item2 = key_info.items().at(1); | |
| ASSERT_EQ(item2.key_id(), 2); | |
| std::string decoded_string2(item.key().length(), '0'); | |
| int decoded_text_len2 = | |
| base64_decode(item2.key().c_str(), item2.key().length(), decoded_string2.data()); | |
| decoded_string2.assign(decoded_string2.data(), decoded_text_len2); | |
| ASSERT_EQ(decoded_string2, plaintext); | |
| ASSERT_EQ(global_encryption_key_info_map.at(item2.key_id()), decoded_string2); | |
| } |