| // 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); |
| } |