blob: 856c9283920acf6a23f24d4120c0239335b73049 [file] [log] [blame]
// 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 <gtest/gtest.h>
#include <string>
#include "parquet/encryption/encryption.h"
#include "parquet/encryption/test_encryption_util.h"
namespace parquet::encryption::test {
TEST(TestColumnEncryptionProperties, ColumnEncryptedWithOwnKey) {
ColumnEncryptionProperties::Builder column_builder_1;
column_builder_1.key(kColumnEncryptionKey1);
column_builder_1.key_id("kc1");
std::shared_ptr<ColumnEncryptionProperties> column_props_1 = column_builder_1.build();
ASSERT_EQ(true, column_props_1->is_encrypted());
ASSERT_EQ(false, column_props_1->is_encrypted_with_footer_key());
ASSERT_EQ(kColumnEncryptionKey1, column_props_1->key());
ASSERT_EQ("kc1", column_props_1->key_metadata());
// Same with shorthand API
column_props_1 =
ColumnEncryptionProperties::WithColumnKey(kColumnEncryptionKey1, "kc1");
ASSERT_EQ(true, column_props_1->is_encrypted());
ASSERT_EQ(false, column_props_1->is_encrypted_with_footer_key());
ASSERT_EQ(kColumnEncryptionKey1, column_props_1->key());
ASSERT_EQ("kc1", column_props_1->key_metadata());
}
TEST(TestColumnEncryptionProperties, ColumnEncryptedWithFooterKey) {
ColumnEncryptionProperties::Builder column_builder_1;
std::shared_ptr<ColumnEncryptionProperties> column_props_1 = column_builder_1.build();
ASSERT_EQ(true, column_props_1->is_encrypted());
ASSERT_EQ(true, column_props_1->is_encrypted_with_footer_key());
// Same with shorthand API
column_props_1 = ColumnEncryptionProperties::WithFooterKey();
ASSERT_EQ(true, column_props_1->is_encrypted());
ASSERT_EQ(true, column_props_1->is_encrypted_with_footer_key());
}
TEST(TestColumnEncryptionProperties, UnencryptedColumn) {
ColumnEncryptionProperties::Builder column_builder_1;
auto column_props_1 = ColumnEncryptionProperties::Unencrypted();
ASSERT_EQ(false, column_props_1->is_encrypted());
ASSERT_EQ(false, column_props_1->is_encrypted_with_footer_key());
}
// Encrypt all columns and the footer with the same key.
// (uniform encryption)
TEST(TestEncryptionProperties, UniformEncryption) {
FileEncryptionProperties::Builder builder(kFooterEncryptionKey);
builder.footer_key_metadata("kf");
std::shared_ptr<FileEncryptionProperties> props = builder.build();
ASSERT_EQ(true, props->encrypted_footer());
ASSERT_EQ(kDefaultEncryptionAlgorithm, props->algorithm().algorithm);
ASSERT_EQ(kFooterEncryptionKey, props->footer_key());
ASSERT_EQ("kf", props->footer_key_metadata());
std::shared_ptr<parquet::schema::ColumnPath> column_path =
parquet::schema::ColumnPath::FromDotString("a_column");
std::shared_ptr<ColumnEncryptionProperties> out_col_props =
props->column_encryption_properties(column_path->ToDotString());
ASSERT_EQ(true, out_col_props->is_encrypted());
ASSERT_EQ(true, out_col_props->is_encrypted_with_footer_key());
}
// Encrypt two columns with their own keys and the same key for
// the footer and other columns
TEST(TestEncryptionProperties, EncryptFooterAndTwoColumns) {
std::shared_ptr<parquet::schema::ColumnPath> column_path_1 =
parquet::schema::ColumnPath::FromDotString("column_1");
std::shared_ptr<parquet::schema::ColumnPath> column_path_2 =
parquet::schema::ColumnPath::FromDotString("column_2");
std::map<std::string, std::shared_ptr<ColumnEncryptionProperties>> encrypted_columns;
encrypted_columns[column_path_1->ToDotString()] =
ColumnEncryptionProperties::WithColumnKey(kColumnEncryptionKey1, "kc1");
encrypted_columns[column_path_2->ToDotString()] =
ColumnEncryptionProperties::WithColumnKey(kColumnEncryptionKey2, "kc2");
FileEncryptionProperties::Builder builder(kFooterEncryptionKey);
builder.footer_key_metadata("kf");
builder.encrypted_columns(std::move(encrypted_columns));
std::shared_ptr<FileEncryptionProperties> props = builder.build();
ASSERT_EQ(true, props->encrypted_footer());
ASSERT_EQ(kDefaultEncryptionAlgorithm, props->algorithm().algorithm);
ASSERT_EQ(kFooterEncryptionKey, props->footer_key());
std::shared_ptr<ColumnEncryptionProperties> out_col_props_1 =
props->column_encryption_properties(column_path_1->ToDotString());
ASSERT_EQ(true, out_col_props_1->is_encrypted());
ASSERT_EQ(false, out_col_props_1->is_encrypted_with_footer_key());
ASSERT_EQ(kColumnEncryptionKey1, out_col_props_1->key());
ASSERT_EQ("kc1", out_col_props_1->key_metadata());
std::shared_ptr<ColumnEncryptionProperties> out_col_props_2 =
props->column_encryption_properties(column_path_2->ToDotString());
ASSERT_EQ(true, out_col_props_2->is_encrypted());
ASSERT_EQ(false, out_col_props_2->is_encrypted_with_footer_key());
ASSERT_EQ(kColumnEncryptionKey2, out_col_props_2->key());
ASSERT_EQ("kc2", out_col_props_2->key_metadata());
std::shared_ptr<parquet::schema::ColumnPath> column_path_3 =
parquet::schema::ColumnPath::FromDotString("column_3");
std::shared_ptr<ColumnEncryptionProperties> out_col_props_3 =
props->column_encryption_properties(column_path_3->ToDotString());
ASSERT_EQ(NULLPTR, out_col_props_3);
}
// Encryption configuration 3: Encrypt two columns, don’t encrypt footer.
// (plaintext footer mode, readable by legacy readers)
TEST(TestEncryptionProperties, EncryptTwoColumnsNotFooter) {
std::shared_ptr<parquet::schema::ColumnPath> column_path_1 =
parquet::schema::ColumnPath::FromDotString("column_1");
std::shared_ptr<parquet::schema::ColumnPath> column_path_2 =
parquet::schema::ColumnPath::FromDotString("column_2");
std::map<std::string, std::shared_ptr<ColumnEncryptionProperties>> encrypted_columns;
encrypted_columns[column_path_1->ToDotString()] =
ColumnEncryptionProperties::WithColumnKey(kColumnEncryptionKey1, "kc1");
encrypted_columns[column_path_2->ToDotString()] =
ColumnEncryptionProperties::WithColumnKey(kColumnEncryptionKey2, "kc2");
FileEncryptionProperties::Builder builder(kFooterEncryptionKey);
builder.footer_key_metadata("kf");
builder.set_plaintext_footer();
builder.encrypted_columns(std::move(encrypted_columns));
std::shared_ptr<FileEncryptionProperties> props = builder.build();
ASSERT_EQ(false, props->encrypted_footer());
ASSERT_EQ(kDefaultEncryptionAlgorithm, props->algorithm().algorithm);
ASSERT_EQ(kFooterEncryptionKey, props->footer_key());
std::shared_ptr<ColumnEncryptionProperties> out_col_props_1 =
props->column_encryption_properties(column_path_1->ToDotString());
ASSERT_EQ(true, out_col_props_1->is_encrypted());
ASSERT_EQ(false, out_col_props_1->is_encrypted_with_footer_key());
ASSERT_EQ(kColumnEncryptionKey1, out_col_props_1->key());
ASSERT_EQ("kc1", out_col_props_1->key_metadata());
std::shared_ptr<ColumnEncryptionProperties> out_col_props_2 =
props->column_encryption_properties(column_path_2->ToDotString());
ASSERT_EQ(true, out_col_props_2->is_encrypted());
ASSERT_EQ(false, out_col_props_2->is_encrypted_with_footer_key());
ASSERT_EQ(kColumnEncryptionKey2, out_col_props_2->key());
ASSERT_EQ("kc2", out_col_props_2->key_metadata());
// other columns: encrypted with footer, footer is not encrypted
// so column is not encrypted as well
std::string column_path_3 = "column_3";
std::shared_ptr<ColumnEncryptionProperties> out_col_props_3 =
props->column_encryption_properties(column_path_3);
ASSERT_EQ(NULLPTR, out_col_props_3);
}
// Encryption configuration 4: Encrypt nested columns by their parent field.
TEST(TestEncryptionProperties, EncryptNestedColumns) {
std::shared_ptr<parquet::schema::ColumnPath> column_path_1 =
parquet::schema::ColumnPath::FromDotString("a_map");
ColumnEncryptionProperties::Builder column_builder_1;
column_builder_1.key(kColumnEncryptionKey1);
column_builder_1.key_id("kc1");
std::shared_ptr<parquet::schema::ColumnPath> column_path_2 =
parquet::schema::ColumnPath::FromDotString("a_list");
ColumnEncryptionProperties::Builder column_builder_2;
column_builder_2.key(kColumnEncryptionKey2);
column_builder_2.key_id("kc2");
std::shared_ptr<parquet::schema::ColumnPath> column_path_3 =
parquet::schema::ColumnPath::FromDotString("a_struct");
ColumnEncryptionProperties::Builder column_builder_3;
column_builder_3.key(kColumnEncryptionKey3);
column_builder_3.key_id("kc3");
std::map<std::string, std::shared_ptr<ColumnEncryptionProperties>> encrypted_columns;
encrypted_columns[column_path_1->ToDotString()] = column_builder_1.build();
encrypted_columns[column_path_2->ToDotString()] = column_builder_2.build();
encrypted_columns[column_path_3->ToDotString()] = column_builder_3.build();
FileEncryptionProperties::Builder builder(kFooterEncryptionKey);
builder.footer_key_metadata("kf");
builder.set_plaintext_footer();
builder.encrypted_columns(std::move(encrypted_columns));
std::shared_ptr<FileEncryptionProperties> props = builder.build();
ASSERT_EQ(false, props->encrypted_footer());
ASSERT_EQ(kDefaultEncryptionAlgorithm, props->algorithm().algorithm);
ASSERT_EQ(kFooterEncryptionKey, props->footer_key());
for (const std::string column_path :
{"a_map", "a_map.key_value.key", "a_map.key_value.value"}) {
std::shared_ptr<ColumnEncryptionProperties> out_col_props =
props->column_encryption_properties(column_path);
ASSERT_EQ(true, out_col_props->is_encrypted());
ASSERT_EQ(false, out_col_props->is_encrypted_with_footer_key());
ASSERT_EQ(kColumnEncryptionKey1, out_col_props->key());
ASSERT_EQ("kc1", out_col_props->key_metadata());
}
for (const std::string column_path : {"a_list", "a_list.list.element"}) {
std::shared_ptr<ColumnEncryptionProperties> out_col_props =
props->column_encryption_properties(column_path);
ASSERT_EQ(true, out_col_props->is_encrypted());
ASSERT_EQ(false, out_col_props->is_encrypted_with_footer_key());
ASSERT_EQ(kColumnEncryptionKey2, out_col_props->key());
ASSERT_EQ("kc2", out_col_props->key_metadata());
}
for (const std::string column_path : {"a_struct", "a_struct.f1", "a_struct.f2"}) {
std::shared_ptr<ColumnEncryptionProperties> out_col_props =
props->column_encryption_properties(column_path);
ASSERT_EQ(true, out_col_props->is_encrypted());
ASSERT_EQ(false, out_col_props->is_encrypted_with_footer_key());
ASSERT_EQ(kColumnEncryptionKey3, out_col_props->key());
ASSERT_EQ("kc3", out_col_props->key_metadata());
}
// other columns: encrypted with footer, footer is not encrypted
// so column is not encrypted as well
// Note: character '-' is lexicographically before '.', and '/' is after
for (const std::string column_path :
{"id", "a_map-column", "a_map/column", "a_list-column", "a_list/column",
"a_struct-column", "a_struct/column"}) {
std::shared_ptr<ColumnEncryptionProperties> out_col_props =
props->column_encryption_properties(column_path);
ASSERT_EQ(NULLPTR, out_col_props);
}
}
// Use aad_prefix
TEST(TestEncryptionProperties, UseAadPrefix) {
FileEncryptionProperties::Builder builder(kFooterEncryptionKey);
builder.aad_prefix(kFileName);
std::shared_ptr<FileEncryptionProperties> props = builder.build();
ASSERT_EQ(kFileName, props->algorithm().aad.aad_prefix);
ASSERT_EQ(false, props->algorithm().aad.supply_aad_prefix);
}
// Use aad_prefix and
// disable_aad_prefix_storage.
TEST(TestEncryptionProperties, UseAadPrefixNotStoreInFile) {
FileEncryptionProperties::Builder builder(kFooterEncryptionKey);
builder.aad_prefix(kFileName);
builder.disable_aad_prefix_storage();
std::shared_ptr<FileEncryptionProperties> props = builder.build();
ASSERT_EQ("", props->algorithm().aad.aad_prefix);
ASSERT_EQ(true, props->algorithm().aad.supply_aad_prefix);
}
// Use AES_GCM_CTR_V1 algorithm
TEST(TestEncryptionProperties, UseAES_GCM_CTR_V1Algorithm) {
FileEncryptionProperties::Builder builder(kFooterEncryptionKey);
builder.algorithm(ParquetCipher::AES_GCM_CTR_V1);
std::shared_ptr<FileEncryptionProperties> props = builder.build();
ASSERT_EQ(ParquetCipher::AES_GCM_CTR_V1, props->algorithm().algorithm);
}
TEST(TestDecryptionProperties, UseKeyRetriever) {
std::shared_ptr<parquet::StringKeyIdRetriever> string_kr1 =
std::make_shared<parquet::StringKeyIdRetriever>();
string_kr1->PutKey("kf", kFooterEncryptionKey);
string_kr1->PutKey("kc1", kColumnEncryptionKey1);
string_kr1->PutKey("kc2", kColumnEncryptionKey2);
std::shared_ptr<parquet::DecryptionKeyRetriever> kr1 =
std::static_pointer_cast<parquet::StringKeyIdRetriever>(string_kr1);
parquet::FileDecryptionProperties::Builder builder;
builder.key_retriever(std::move(kr1));
std::shared_ptr<parquet::FileDecryptionProperties> props = builder.build();
auto out_key_retriever = props->key_retriever();
ASSERT_EQ(kFooterEncryptionKey, out_key_retriever->GetKey("kf"));
ASSERT_EQ(kColumnEncryptionKey1, out_key_retriever->GetKey("kc1"));
ASSERT_EQ(kColumnEncryptionKey2, out_key_retriever->GetKey("kc2"));
}
TEST(TestDecryptionProperties, SupplyAadPrefix) {
parquet::FileDecryptionProperties::Builder builder;
builder.footer_key(kFooterEncryptionKey);
builder.aad_prefix(kFileName);
std::shared_ptr<parquet::FileDecryptionProperties> props = builder.build();
ASSERT_EQ(kFileName, props->aad_prefix());
}
TEST(ColumnDecryptionProperties, SetKey) {
std::shared_ptr<parquet::schema::ColumnPath> column_path_1 =
parquet::schema::ColumnPath::FromDotString("column_1");
ColumnDecryptionProperties::Builder col_builder_1(*column_path_1);
col_builder_1.key(kColumnEncryptionKey1);
auto props = col_builder_1.build();
ASSERT_EQ(kColumnEncryptionKey1, props->key());
}
TEST(TestDecryptionProperties, UsingExplicitFooterAndColumnKeys) {
std::string column_path_1 = "column_1";
std::string column_path_2 = "column_2";
std::map<std::string, std::shared_ptr<parquet::ColumnDecryptionProperties>>
decryption_cols;
parquet::ColumnDecryptionProperties::Builder col_builder_1(column_path_1);
parquet::ColumnDecryptionProperties::Builder col_builder_2(column_path_2);
decryption_cols[column_path_1] = col_builder_1.key(kColumnEncryptionKey1)->build();
decryption_cols[column_path_2] = col_builder_2.key(kColumnEncryptionKey2)->build();
parquet::FileDecryptionProperties::Builder builder;
builder.footer_key(kFooterEncryptionKey);
builder.column_keys(std::move(decryption_cols));
std::shared_ptr<parquet::FileDecryptionProperties> props = builder.build();
ASSERT_EQ(kFooterEncryptionKey, props->footer_key());
ASSERT_EQ(kColumnEncryptionKey1, props->column_key(column_path_1));
ASSERT_EQ(kColumnEncryptionKey2, props->column_key(column_path_2));
}
} // namespace parquet::encryption::test