blob: 9a427a756050b984edc54a28b549a6f2e21d3054 [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.
package encryption
import (
"github.com/apache/arrow/go/arrow/memory"
"github.com/apache/arrow/go/parquet"
)
// FileDecryptor is an interface used by the filereader for decrypting an
// entire parquet file as we go, usually constructed from the DecryptionProperties
type FileDecryptor interface {
// Returns the key for decrypting the footer if provided
GetFooterKey() string
// Provides the file level AAD security bytes
FileAad() string
// return which algorithm this decryptor was constructed for
Algorithm() parquet.Cipher
// return the FileDecryptionProperties that were used for this decryptor
Properties() *parquet.FileDecryptionProperties
// Clear out the decryption keys, this is automatically called after every
// successfully decrypted file to ensure that keys aren't kept around.
WipeOutDecryptionKeys()
// GetFooterDecryptor returns a Decryptor interface for use to decrypt the footer
// of a parquet file.
GetFooterDecryptor() Decryptor
// GetFooterDecryptorForColumnMeta returns a Decryptor interface for Column Metadata
// in the file footer using the AAD bytes provided.
GetFooterDecryptorForColumnMeta(aad string) Decryptor
// GetFooterDecryptorForColumnData returns the decryptor that can be used for decrypting
// actual column data footer bytes, not column metadata.
GetFooterDecryptorForColumnData(aad string) Decryptor
// GetColumnMetaDecryptor returns a decryptor for the requested column path, key and AAD bytes
// but only for decrypting the row group level metadata
GetColumnMetaDecryptor(columnPath, columnKeyMetadata, aad string) Decryptor
// GetColumnDataDecryptor returns a decryptor for the requested column path, key, and AAD bytes
// but only for the rowgroup column data.
GetColumnDataDecryptor(columnPath, columnKeyMetadata, aad string) Decryptor
}
type fileDecryptor struct {
// the properties contains the key retriever for us to get keys
// from the key metadata
props *parquet.FileDecryptionProperties
// concatenation of aad_prefix (if exists) and aad_file_unique
fileAad string
columnDataMap map[string]Decryptor
columnMetaDataMap map[string]Decryptor
footerMetadataDecryptor Decryptor
footerDataDecryptor Decryptor
alg parquet.Cipher
footerKeyMetadata string
metaDecryptor *aesDecryptor
dataDecryptor *aesDecryptor
mem memory.Allocator
}
// NewFileDecryptor constructs a decryptor from the provided configuration of properties, cipher and key metadata. Using the provided memory allocator or
// the default allocator if one isn't provided.
func NewFileDecryptor(props *parquet.FileDecryptionProperties, fileAad string, alg parquet.Cipher, keymetadata string, mem memory.Allocator) FileDecryptor {
if mem == nil {
mem = memory.DefaultAllocator
}
return &fileDecryptor{
fileAad: fileAad,
props: props,
alg: alg,
footerKeyMetadata: keymetadata,
mem: mem,
columnDataMap: make(map[string]Decryptor),
columnMetaDataMap: make(map[string]Decryptor),
}
}
func (d *fileDecryptor) FileAad() string { return d.fileAad }
func (d *fileDecryptor) Properties() *parquet.FileDecryptionProperties { return d.props }
func (d *fileDecryptor) Algorithm() parquet.Cipher { return d.alg }
func (d *fileDecryptor) GetFooterKey() string {
footerKey := d.props.FooterKey()
if footerKey == "" {
if d.footerKeyMetadata == "" {
panic("no footer key or key metadata")
}
if d.props.KeyRetriever == nil {
panic("no footer key or key retriever")
}
footerKey = d.props.KeyRetriever.GetKey([]byte(d.footerKeyMetadata))
}
if footerKey == "" {
panic("invalid footer encryption key. Could not parse footer metadata")
}
return footerKey
}
func (d *fileDecryptor) GetFooterDecryptor() Decryptor {
aad := CreateFooterAad(d.fileAad)
return d.getFooterDecryptor(aad, true)
}
func (d *fileDecryptor) GetFooterDecryptorForColumnMeta(aad string) Decryptor {
return d.getFooterDecryptor(aad, true)
}
func (d *fileDecryptor) GetFooterDecryptorForColumnData(aad string) Decryptor {
return d.getFooterDecryptor(aad, false)
}
func (d *fileDecryptor) GetColumnMetaDecryptor(columnPath, columnKeyMetadata, aad string) Decryptor {
return d.getColumnDecryptor(columnPath, columnKeyMetadata, aad, true)
}
func (d *fileDecryptor) GetColumnDataDecryptor(columnPath, columnKeyMetadata, aad string) Decryptor {
return d.getColumnDecryptor(columnPath, columnKeyMetadata, aad, false)
}
func (d *fileDecryptor) WipeOutDecryptionKeys() {
d.props.WipeOutDecryptionKeys()
}
func (d *fileDecryptor) getFooterDecryptor(aad string, metadata bool) Decryptor {
if metadata {
if d.footerMetadataDecryptor != nil {
return d.footerMetadataDecryptor
}
} else {
if d.footerDataDecryptor != nil {
return d.footerDataDecryptor
}
}
footerKey := d.GetFooterKey()
// Create both data and metadata decryptors to avoid redundant retrieval of key
// from the key_retriever.
aesMetaDecrypt := d.getMetaAesDecryptor()
aesDataDecrypt := d.getDataAesDecryptor()
d.footerMetadataDecryptor = &decryptor{
decryptor: aesMetaDecrypt,
key: []byte(footerKey),
fileAad: []byte(d.fileAad),
aad: []byte(aad),
mem: d.mem,
}
d.footerDataDecryptor = &decryptor{
decryptor: aesDataDecrypt,
key: []byte(footerKey),
fileAad: []byte(d.fileAad),
aad: []byte(aad),
mem: d.mem,
}
if metadata {
return d.footerMetadataDecryptor
}
return d.footerDataDecryptor
}
func (d *fileDecryptor) getColumnDecryptor(columnPath, columnMeta, aad string, metadata bool) Decryptor {
if metadata {
if res, ok := d.columnMetaDataMap[columnPath]; ok {
res.UpdateAad(aad)
return res
}
} else {
if res, ok := d.columnDataMap[columnPath]; ok {
res.UpdateAad(aad)
return res
}
}
columnKey := d.props.ColumnKey(columnPath)
// No explicit column key given via API. Retrieve via key metadata.
if columnKey == "" && columnMeta != "" && d.props.KeyRetriever != nil {
columnKey = d.props.KeyRetriever.GetKey([]byte(columnMeta))
}
if columnKey == "" {
panic("hidden column exception, path=" + columnPath)
}
aesDataDecrypt := d.getDataAesDecryptor()
aesMetaDecrypt := d.getMetaAesDecryptor()
d.columnDataMap[columnPath] = &decryptor{
decryptor: aesDataDecrypt,
key: []byte(columnKey),
fileAad: []byte(d.fileAad),
aad: []byte(aad),
mem: d.mem,
}
d.columnMetaDataMap[columnPath] = &decryptor{
decryptor: aesMetaDecrypt,
key: []byte(columnKey),
fileAad: []byte(d.fileAad),
aad: []byte(aad),
mem: d.mem,
}
if metadata {
return d.columnMetaDataMap[columnPath]
}
return d.columnDataMap[columnPath]
}
func (d *fileDecryptor) getMetaAesDecryptor() *aesDecryptor {
if d.metaDecryptor == nil {
d.metaDecryptor = newAesDecryptor(d.alg, true)
}
return d.metaDecryptor
}
func (d *fileDecryptor) getDataAesDecryptor() *aesDecryptor {
if d.dataDecryptor == nil {
d.dataDecryptor = newAesDecryptor(d.alg, false)
}
return d.dataDecryptor
}
// Decryptor is the basic interface for any decryptor generated from a FileDecryptor
type Decryptor interface {
// returns the File Level AAD bytes
FileAad() string
// returns the current allocator that was used for any extra allocations of buffers
Allocator() memory.Allocator
// returns the CiphertextSizeDelta from the decryptor
CiphertextSizeDelta() int
// Decrypt just returns the decrypted plaintext from the src ciphertext
Decrypt(src []byte) []byte
// set the AAD bytes of the decryptor to the provided string
UpdateAad(string)
}
type decryptor struct {
decryptor *aesDecryptor
key []byte
fileAad []byte
aad []byte
mem memory.Allocator
}
func (d *decryptor) Allocator() memory.Allocator { return d.mem }
func (d *decryptor) FileAad() string { return string(d.fileAad) }
func (d *decryptor) UpdateAad(aad string) { d.aad = []byte(aad) }
func (d *decryptor) CiphertextSizeDelta() int { return d.decryptor.CiphertextSizeDelta() }
func (d *decryptor) Decrypt(src []byte) []byte {
return d.decryptor.Decrypt(src, d.key, d.aad)
}