| // 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 <iostream> |
| |
| #include "arrow/util/utf8.h" |
| |
| #include "parquet/encryption/file_key_unwrapper.h" |
| #include "parquet/encryption/key_metadata.h" |
| |
| using ::arrow::util::SecureString; |
| |
| namespace parquet::encryption { |
| |
| FileKeyUnwrapper::FileKeyUnwrapper( |
| std::shared_ptr<KeyToolkit> key_toolkit, |
| const KmsConnectionConfig& kms_connection_config, double cache_lifetime_seconds, |
| const std::string& file_path, |
| const std::shared_ptr<::arrow::fs::FileSystem>& file_system) |
| : FileKeyUnwrapper(std::move(key_toolkit), /*key_toolkit=*/nullptr, |
| kms_connection_config, cache_lifetime_seconds, |
| /*key_material_store=*/nullptr, file_path, file_system) {} |
| |
| FileKeyUnwrapper::FileKeyUnwrapper( |
| KeyToolkit* key_toolkit, const KmsConnectionConfig& kms_connection_config, |
| double cache_lifetime_seconds, const std::string& file_path, |
| const std::shared_ptr<::arrow::fs::FileSystem>& file_system) |
| : FileKeyUnwrapper(/*key_toolkit_owner=*/nullptr, key_toolkit, kms_connection_config, |
| cache_lifetime_seconds, /*key_material_store=*/nullptr, file_path, |
| file_system) {} |
| |
| FileKeyUnwrapper::FileKeyUnwrapper( |
| KeyToolkit* key_toolkit, const KmsConnectionConfig& kms_connection_config, |
| double cache_lifetime_seconds, |
| std::shared_ptr<FileKeyMaterialStore> key_material_store) |
| : FileKeyUnwrapper(/*key_toolkit_owner=*/nullptr, key_toolkit, kms_connection_config, |
| cache_lifetime_seconds, std::move(key_material_store), |
| /*file_path=*/"", |
| /*file_system=*/nullptr) {} |
| |
| FileKeyUnwrapper::FileKeyUnwrapper( |
| std::shared_ptr<KeyToolkit> key_toolkit_owner, KeyToolkit* key_toolkit, |
| const KmsConnectionConfig& kms_connection_config, double cache_lifetime_seconds, |
| std::shared_ptr<FileKeyMaterialStore> key_material_store, |
| const std::string& file_path, |
| const std::shared_ptr<::arrow::fs::FileSystem>& file_system) |
| : key_toolkit_owner_(std::move(key_toolkit_owner)), |
| key_toolkit_(key_toolkit ? key_toolkit : key_toolkit_owner_.get()), |
| kms_connection_config_(kms_connection_config), |
| cache_entry_lifetime_seconds_(cache_lifetime_seconds), |
| key_material_store_(std::move(key_material_store)), |
| file_path_(file_path), |
| file_system_(file_system) { |
| kek_per_kek_id_ = key_toolkit_->kek_read_cache_per_token().GetOrCreateInternalCache( |
| kms_connection_config.key_access_token(), cache_entry_lifetime_seconds_); |
| } |
| |
| SecureString FileKeyUnwrapper::GetKey(const std::string& key_metadata_bytes) { |
| // key_metadata is expected to be in UTF8 encoding |
| ::arrow::util::InitializeUTF8(); |
| if (!::arrow::util::ValidateUTF8( |
| reinterpret_cast<const uint8_t*>(key_metadata_bytes.data()), |
| key_metadata_bytes.size())) { |
| throw ParquetException("key metadata should be in UTF8 encoding"); |
| } |
| KeyMetadata key_metadata = KeyMetadata::Parse(key_metadata_bytes); |
| |
| KeyMaterial key_material; |
| if (key_metadata.key_material_stored_internally()) { |
| key_material = key_metadata.key_material(); |
| } else { |
| if (key_material_store_ == nullptr) { |
| key_material_store_ = |
| FileSystemKeyMaterialStore::Make(file_path_, file_system_, false); |
| } |
| // External key material storage: key metadata contains a reference |
| // to a key in the material store |
| std::string key_id_in_file = key_metadata.key_reference(); |
| std::string key_material_string = key_material_store_->GetKeyMaterial(key_id_in_file); |
| if (key_material_string.empty()) { |
| throw ParquetException("Could not find key material with ID '" + key_id_in_file + |
| "' in external key material file"); |
| } |
| key_material = KeyMaterial::Parse(key_material_string); |
| } |
| |
| return GetDataEncryptionKey(key_material).data_key(); |
| } |
| |
| KeyWithMasterId FileKeyUnwrapper::GetDataEncryptionKey(const KeyMaterial& key_material) { |
| auto kms_client = GetKmsClientFromConfigOrKeyMaterial(key_material); |
| |
| bool double_wrapping = key_material.is_double_wrapped(); |
| const std::string& master_key_id = key_material.master_key_id(); |
| const std::string& encoded_wrapped_dek = key_material.wrapped_dek(); |
| |
| SecureString data_key; |
| if (!double_wrapping) { |
| data_key = kms_client->UnwrapKey(encoded_wrapped_dek, master_key_id); |
| } else { |
| // Get Key Encryption Key |
| const std::string& encoded_kek_id = key_material.kek_id(); |
| const std::string& encoded_wrapped_kek = key_material.wrapped_kek(); |
| |
| const SecureString kek_bytes = kek_per_kek_id_->GetOrInsert( |
| encoded_kek_id, [kms_client, encoded_wrapped_kek, master_key_id]() { |
| return kms_client->UnwrapKey(encoded_wrapped_kek, master_key_id); |
| }); |
| |
| // Decrypt the data key |
| std::string aad = ::arrow::util::base64_decode(encoded_kek_id); |
| data_key = internal::DecryptKeyLocally(encoded_wrapped_dek, kek_bytes, aad); |
| } |
| |
| return KeyWithMasterId(std::move(data_key), master_key_id); |
| } |
| |
| std::shared_ptr<KmsClient> FileKeyUnwrapper::GetKmsClientFromConfigOrKeyMaterial( |
| const KeyMaterial& key_material) { |
| std::string& kms_instance_id = kms_connection_config_.kms_instance_id; |
| if (kms_instance_id.empty()) { |
| kms_instance_id = key_material.kms_instance_id(); |
| if (kms_instance_id.empty()) { |
| throw ParquetException( |
| "KMS instance ID is missing both in both kms connection configuration and file " |
| "key material"); |
| } |
| } |
| |
| std::string& kms_instance_url = kms_connection_config_.kms_instance_url; |
| if (kms_instance_url.empty()) { |
| kms_instance_url = key_material.kms_instance_url(); |
| if (kms_instance_url.empty()) { |
| throw ParquetException( |
| "KMS instance ID is missing both in both kms connection configuration and file " |
| "key material"); |
| } |
| } |
| |
| return key_toolkit_->GetKmsClient(kms_connection_config_, |
| cache_entry_lifetime_seconds_); |
| } |
| |
| } // namespace parquet::encryption |