github.com/apache/arrow/go/v14@v14.0.1/parquet/encryption_properties.go (about)

     1  // Licensed to the Apache Software Foundation (ASF) under one
     2  // or more contributor license agreements.  See the NOTICE file
     3  // distributed with this work for additional information
     4  // regarding copyright ownership.  The ASF licenses this file
     5  // to you under the Apache License, Version 2.0 (the
     6  // "License"); you may not use this file except in compliance
     7  // with the License.  You may obtain a copy of the License at
     8  //
     9  // http://www.apache.org/licenses/LICENSE-2.0
    10  //
    11  // Unless required by applicable law or agreed to in writing, software
    12  // distributed under the License is distributed on an "AS IS" BASIS,
    13  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14  // See the License for the specific language governing permissions and
    15  // limitations under the License.
    16  
    17  package parquet
    18  
    19  import (
    20  	"crypto/rand"
    21  	"unicode/utf8"
    22  
    23  	format "github.com/apache/arrow/go/v14/parquet/internal/gen-go/parquet"
    24  )
    25  
    26  // Constants that will be used as the default values with encryption/decryption
    27  const (
    28  	// By default we'll use AesGCM as our encryption algorithm
    29  	DefaultEncryptionAlgorithm       = AesGcm
    30  	MaximalAadMetadataLength   int32 = 256
    31  	// if encryption is turned on, we will default to also encrypting the footer
    32  	DefaultEncryptedFooter = true
    33  	DefaultCheckSignature  = true
    34  	// by default if you set the file decryption properties, we will error
    35  	// on any plaintext files unless otherwise specified.
    36  	DefaultAllowPlaintextFiles       = false
    37  	AadFileUniqueLength        int32 = 8
    38  )
    39  
    40  // ColumnPathToDecryptionPropsMap maps column paths to decryption properties
    41  type ColumnPathToDecryptionPropsMap map[string]*ColumnDecryptionProperties
    42  
    43  // ColumnPathToEncryptionPropsMap maps column paths to encryption properties
    44  type ColumnPathToEncryptionPropsMap map[string]*ColumnEncryptionProperties
    45  
    46  // ColumnEncryptionProperties specifies how to encrypt a given column
    47  type ColumnEncryptionProperties struct {
    48  	columnPath             string
    49  	encrypted              bool
    50  	encryptedWithFooterKey bool
    51  	key                    string
    52  	keyMetadata            string
    53  	utilized               bool
    54  }
    55  
    56  // ColumnPath returns which column these properties are for
    57  func (ce *ColumnEncryptionProperties) ColumnPath() string {
    58  	return ce.columnPath
    59  }
    60  
    61  // IsEncrypted returns true if this column is encrypted.
    62  func (ce *ColumnEncryptionProperties) IsEncrypted() bool { return ce.encrypted }
    63  
    64  // IsEncryptedWithFooterKey returns if this column was encrypted with the footer key itself, or false if a separate
    65  // key was used for encrypting this column.
    66  func (ce *ColumnEncryptionProperties) IsEncryptedWithFooterKey() bool {
    67  	return ce.encryptedWithFooterKey
    68  }
    69  
    70  // Key returns the key used for encrypting this column if it isn't encrypted by the footer key
    71  func (ce *ColumnEncryptionProperties) Key() string { return ce.key }
    72  
    73  // KeyMetadata returns the key identifier which is used with a KeyRetriever to get the key for this column if it is not
    74  // encrypted using the footer key
    75  func (ce *ColumnEncryptionProperties) KeyMetadata() string { return ce.keyMetadata }
    76  
    77  // WipeOutEncryptionKey Clears the encryption key, used after completion of file writing
    78  func (ce *ColumnEncryptionProperties) WipeOutEncryptionKey() { ce.key = "" }
    79  
    80  // IsUtilized returns whether or not these properties have already been used, if the key is empty
    81  // then this is always false
    82  func (ce *ColumnEncryptionProperties) IsUtilized() bool {
    83  	if ce.key == "" {
    84  		return false
    85  	}
    86  	return ce.utilized
    87  }
    88  
    89  // SetUtilized is used for marking it as utilized once it is used in FileEncryptionProperties
    90  // as the encryption key will be wiped out on completion of writing
    91  func (ce *ColumnEncryptionProperties) SetUtilized() {
    92  	ce.utilized = true
    93  }
    94  
    95  // Clone returns a instance of ColumnEncryptionProperties with the same key and metadata
    96  func (ce *ColumnEncryptionProperties) Clone() *ColumnEncryptionProperties {
    97  	copy := ce.key
    98  	return NewColumnEncryptionProperties(ce.columnPath, WithKey(copy), WithKeyMetadata(ce.keyMetadata))
    99  }
   100  
   101  type colEncryptConfig struct {
   102  	key         string
   103  	keyMetadata string
   104  	encrypted   bool
   105  }
   106  
   107  // ColumnEncryptOption how to specify options to the the NewColumnEncryptionProperties function.
   108  type ColumnEncryptOption func(*colEncryptConfig)
   109  
   110  // WithKey sets a column specific key.
   111  // If key is not set on an encrypted column, the column will be encrypted with the footer key.
   112  // key length must be either 16, 24, or 32 bytes
   113  // the key is cloned and will be wiped out (array values set to 0) upon completion of file writing.
   114  // Caller is responsible for wiping out input key array
   115  func WithKey(key string) ColumnEncryptOption {
   116  	return func(c *colEncryptConfig) {
   117  		if key != "" {
   118  			c.key = key
   119  		}
   120  	}
   121  }
   122  
   123  // WithKeyMetadata sets the key retrieval metadata, use either KeyMetadata or KeyID but not both
   124  func WithKeyMetadata(keyMeta string) ColumnEncryptOption {
   125  	return func(c *colEncryptConfig) {
   126  		c.keyMetadata = keyMeta
   127  	}
   128  }
   129  
   130  // WithKeyID is a convenience function to set the key metadata using a string id.
   131  // Set a key retrieval metadata (converted from String). and use either KeyMetadata or KeyID, not both.
   132  // KeyID will be converted to metadata (UTF-8 Array)
   133  func WithKeyID(keyID string) ColumnEncryptOption {
   134  	if !utf8.ValidString(keyID) {
   135  		panic("parquet: key id should be UTF8 encoded")
   136  	}
   137  	return WithKeyMetadata(keyID)
   138  }
   139  
   140  // NewColumnEncryptionProperties constructs properties for the provided column path, modified by the options provided
   141  func NewColumnEncryptionProperties(name string, opts ...ColumnEncryptOption) *ColumnEncryptionProperties {
   142  	var cfg colEncryptConfig
   143  	cfg.encrypted = true
   144  	for _, o := range opts {
   145  		o(&cfg)
   146  	}
   147  	return &ColumnEncryptionProperties{
   148  		utilized:               false,
   149  		encrypted:              cfg.encrypted,
   150  		encryptedWithFooterKey: cfg.encrypted && cfg.key == "",
   151  		keyMetadata:            cfg.keyMetadata,
   152  		key:                    cfg.key,
   153  		columnPath:             name,
   154  	}
   155  }
   156  
   157  // ColumnDecryptionProperties are the specifications for how to decrypt a given column.
   158  type ColumnDecryptionProperties struct {
   159  	columnPath string
   160  	key        string
   161  	utilized   bool
   162  }
   163  
   164  // NewColumnDecryptionProperties constructs a new ColumnDecryptionProperties for the given column path, modified by
   165  // the provided options
   166  func NewColumnDecryptionProperties(column string, opts ...ColumnDecryptOption) *ColumnDecryptionProperties {
   167  	var cfg columnDecryptConfig
   168  	for _, o := range opts {
   169  		o(&cfg)
   170  	}
   171  
   172  	return &ColumnDecryptionProperties{
   173  		columnPath: column,
   174  		utilized:   false,
   175  		key:        cfg.key,
   176  	}
   177  }
   178  
   179  // ColumnPath returns which column these properties describe how to decrypt
   180  func (cd *ColumnDecryptionProperties) ColumnPath() string { return cd.columnPath }
   181  
   182  // Key returns the key specified to decrypt this column, or is empty if the Footer Key should be used.
   183  func (cd *ColumnDecryptionProperties) Key() string { return cd.key }
   184  
   185  // IsUtilized returns whether or not these properties have been used for decryption already
   186  func (cd *ColumnDecryptionProperties) IsUtilized() bool { return cd.utilized }
   187  
   188  // SetUtilized is used by the reader to specify when we've decrypted the column and have used the key so we know
   189  // to wipe out the keys.
   190  func (cd *ColumnDecryptionProperties) SetUtilized() { cd.utilized = true }
   191  
   192  // WipeOutDecryptionKey is called after decryption to ensure the key doesn't stick around and get re-used.
   193  func (cd *ColumnDecryptionProperties) WipeOutDecryptionKey() { cd.key = "" }
   194  
   195  // Clone returns a new instance of ColumnDecryptionProperties with the same key and column
   196  func (cd *ColumnDecryptionProperties) Clone() *ColumnDecryptionProperties {
   197  	return NewColumnDecryptionProperties(cd.columnPath, WithDecryptKey(cd.key))
   198  }
   199  
   200  type columnDecryptConfig struct {
   201  	key string
   202  }
   203  
   204  // ColumnDecryptOption is the type of the options passed for constructing Decryption Properties
   205  type ColumnDecryptOption func(*columnDecryptConfig)
   206  
   207  // WithDecryptKey specifies the key to utilize for decryption
   208  func WithDecryptKey(key string) ColumnDecryptOption {
   209  	return func(cfg *columnDecryptConfig) {
   210  		if key != "" {
   211  			cfg.key = key
   212  		}
   213  	}
   214  }
   215  
   216  // AADPrefixVerifier is an interface for any object that can be used to verify the identity of the file being decrypted.
   217  // It should panic if the provided AAD identity is bad.
   218  //
   219  // In a data set, AAD Prefixes should be collected, and then checked for missing files.
   220  type AADPrefixVerifier interface {
   221  	// Verify identity of file. panic if bad
   222  	Verify(string)
   223  }
   224  
   225  // DecryptionKeyRetriever is an interface for getting the desired key for decryption from metadata. It should take in
   226  // some metadata identifier and return the actual Key to use for decryption.
   227  type DecryptionKeyRetriever interface {
   228  	GetKey(keyMetadata []byte) string
   229  }
   230  
   231  // FileDecryptionProperties define the File Level configuration for decrypting a parquet file. Once constructed they are
   232  // read only.
   233  type FileDecryptionProperties struct {
   234  	footerKey                     string
   235  	aadPrefix                     string
   236  	checkPlaintextFooterIntegrity bool
   237  	plaintextAllowed              bool
   238  	utilized                      bool
   239  	columnDecryptProps            ColumnPathToDecryptionPropsMap
   240  	Verifier                      AADPrefixVerifier
   241  	KeyRetriever                  DecryptionKeyRetriever
   242  }
   243  
   244  // NewFileDecryptionProperties takes in the options for constructing a new FileDecryptionProperties object, otherwise
   245  // it will use the default configuration which will check footer integrity of a plaintext footer for an encrypted file
   246  // for unencrypted parquet files, the decryption properties should not be set.
   247  func NewFileDecryptionProperties(opts ...FileDecryptionOption) *FileDecryptionProperties {
   248  	var cfg fileDecryptConfig
   249  	cfg.checkFooterIntegrity = DefaultCheckSignature
   250  	cfg.plaintextAllowed = DefaultAllowPlaintextFiles
   251  	for _, o := range opts {
   252  		o(&cfg)
   253  	}
   254  	return &FileDecryptionProperties{
   255  		Verifier:                      cfg.verifier,
   256  		footerKey:                     cfg.footerKey,
   257  		checkPlaintextFooterIntegrity: cfg.checkFooterIntegrity,
   258  		KeyRetriever:                  cfg.retriever,
   259  		aadPrefix:                     cfg.aadPrefix,
   260  		columnDecryptProps:            cfg.colDecrypt,
   261  		plaintextAllowed:              cfg.plaintextAllowed,
   262  		utilized:                      false,
   263  	}
   264  }
   265  
   266  // ColumnKey returns the key to be used for decrypting the provided column.
   267  func (fd *FileDecryptionProperties) ColumnKey(path string) string {
   268  	if d, ok := fd.columnDecryptProps[path]; ok {
   269  		if d != nil {
   270  			return d.Key()
   271  		}
   272  	}
   273  	return ""
   274  }
   275  
   276  // FooterKey returns the key utilized for decrypting the Footer if encrypted and any columns that are encrypted with
   277  // the footer key.
   278  func (fd *FileDecryptionProperties) FooterKey() string { return fd.footerKey }
   279  
   280  // AadPrefix returns the prefix to be supplied for constructing the identification strings when decrypting
   281  func (fd *FileDecryptionProperties) AadPrefix() string { return fd.aadPrefix }
   282  
   283  // PlaintextFooterIntegrity returns whether or not an integrity check will be performed on a plaintext footer for an
   284  // encrypted file.
   285  func (fd *FileDecryptionProperties) PlaintextFooterIntegrity() bool {
   286  	return fd.checkPlaintextFooterIntegrity
   287  }
   288  
   289  // PlaintextFilesAllowed returns whether or not this instance of decryption properties are allowed on a plaintext file.
   290  func (fd *FileDecryptionProperties) PlaintextFilesAllowed() bool { return fd.plaintextAllowed }
   291  
   292  // SetUtilized is called to mark this instance as utilized once it is used to read a file. A single instance
   293  // can be used for reading one file only. Setting this ensures the keys will be wiped out upon completion of file reading.
   294  func (fd *FileDecryptionProperties) SetUtilized() { fd.utilized = true }
   295  
   296  // IsUtilized returns whether or not this instance has been used to decrypt a file. If the footer key and prefix are
   297  // empty and there are no column decryption properties, then this is always false.
   298  func (fd *FileDecryptionProperties) IsUtilized() bool {
   299  	if fd.footerKey == "" && len(fd.columnDecryptProps) == 0 && fd.aadPrefix == "" {
   300  		return false
   301  	}
   302  	return fd.utilized
   303  }
   304  
   305  // WipeOutDecryptionKeys will clear all the keys for this instance including the column level ones, this will be called
   306  // after this instance has been utilized.
   307  func (fd *FileDecryptionProperties) WipeOutDecryptionKeys() {
   308  	fd.footerKey = ""
   309  	for _, cd := range fd.columnDecryptProps {
   310  		cd.WipeOutDecryptionKey()
   311  	}
   312  }
   313  
   314  // Clone returns a new instance of these properties, changing the prefix if set (keeping the same prefix if left empty)
   315  func (fd *FileDecryptionProperties) Clone(newAadPrefix string) *FileDecryptionProperties {
   316  	keyCopy := fd.footerKey
   317  	colDecryptMapCopy := make(ColumnPathToDecryptionPropsMap)
   318  	for k, v := range fd.columnDecryptProps {
   319  		colDecryptMapCopy[k] = v.Clone()
   320  	}
   321  	if newAadPrefix == "" {
   322  		newAadPrefix = fd.aadPrefix
   323  	}
   324  	return &FileDecryptionProperties{
   325  		footerKey:                     keyCopy,
   326  		KeyRetriever:                  fd.KeyRetriever,
   327  		checkPlaintextFooterIntegrity: fd.checkPlaintextFooterIntegrity,
   328  		Verifier:                      fd.Verifier,
   329  		columnDecryptProps:            colDecryptMapCopy,
   330  		aadPrefix:                     newAadPrefix,
   331  		plaintextAllowed:              fd.plaintextAllowed,
   332  		utilized:                      false,
   333  	}
   334  }
   335  
   336  type fileDecryptConfig struct {
   337  	footerKey            string
   338  	aadPrefix            string
   339  	verifier             AADPrefixVerifier
   340  	colDecrypt           ColumnPathToDecryptionPropsMap
   341  	retriever            DecryptionKeyRetriever
   342  	checkFooterIntegrity bool
   343  	plaintextAllowed     bool
   344  }
   345  
   346  // FileDecryptionOption is how to supply options to constructing a new FileDecryptionProperties instance.
   347  type FileDecryptionOption func(*fileDecryptConfig)
   348  
   349  // WithFooterKey sets an explicit footer key. If Applied on a file that contains footer key
   350  // metadata the metadata will be ignored, the footer will be decrypted/verified with this key.
   351  //
   352  // If the explicit key is not set, footer key will be fetched from the key retriever.
   353  // With explcit keys or AAD prefix, new encryption properties object must be created for each
   354  // encrypted file.
   355  //
   356  // Explicit encryption keys (footer and column) are cloned.
   357  // Upon completion of file reading, the cloned encryption keys in the properties will be wiped out
   358  // Caller is responsible for wiping out the input key array
   359  // footer key length must be either 16, 24, or 32 bytes
   360  func WithFooterKey(key string) FileDecryptionOption {
   361  	return func(cfg *fileDecryptConfig) {
   362  		if key != "" {
   363  			cfg.footerKey = key
   364  		}
   365  	}
   366  }
   367  
   368  // WithPrefixVerifier supplies a verifier object to use for verifying the AAD Prefixes stored in the file.
   369  func WithPrefixVerifier(verifier AADPrefixVerifier) FileDecryptionOption {
   370  	return func(cfg *fileDecryptConfig) {
   371  		if verifier != nil {
   372  			cfg.verifier = verifier
   373  		}
   374  	}
   375  }
   376  
   377  // WithColumnKeys sets explicit column keys.
   378  //
   379  // It's also possible to set a key retriever on this property object.
   380  //
   381  // Upon file decryption, availability of explicit keys is checked before invocation
   382  // of the retreiver callback.
   383  //
   384  // If an explicit key is available for a footer or a column, its key metadata will be ignored.
   385  func WithColumnKeys(decrypt ColumnPathToDecryptionPropsMap) FileDecryptionOption {
   386  	return func(cfg *fileDecryptConfig) {
   387  		if len(decrypt) == 0 {
   388  			return
   389  		}
   390  		if len(cfg.colDecrypt) != 0 {
   391  			panic("column properties already set")
   392  		}
   393  		for _, v := range decrypt {
   394  			if v.IsUtilized() {
   395  				panic("parquet: column properties utilized in another file")
   396  			}
   397  			v.SetUtilized()
   398  		}
   399  		cfg.colDecrypt = decrypt
   400  	}
   401  }
   402  
   403  // WithKeyRetriever sets a key retriever callback. It's also possible to set explicit footer or column keys.
   404  func WithKeyRetriever(retriever DecryptionKeyRetriever) FileDecryptionOption {
   405  	return func(cfg *fileDecryptConfig) {
   406  		if retriever != nil {
   407  			cfg.retriever = retriever
   408  		}
   409  	}
   410  }
   411  
   412  // DisableFooterSignatureVerification skips integrity verification of plaintext footers.
   413  //
   414  // If not called, integrity of plaintext footers will be checked in runtime, and will panic
   415  // if the footer signing key is not available
   416  // or if the footer content and signature don't match
   417  func DisableFooterSignatureVerification() FileDecryptionOption {
   418  	return func(cfg *fileDecryptConfig) {
   419  		cfg.checkFooterIntegrity = false
   420  	}
   421  }
   422  
   423  // WithPlaintextAllowed sets allowing plaintext files.
   424  //
   425  // By default, reading plaintext (unencrypted) files is not allowed when using
   426  // a decryptor.
   427  //
   428  // In order to detect files that were not encrypted by mistake.
   429  // However the default behavior can be overridden by using this method.
   430  func WithPlaintextAllowed() FileDecryptionOption {
   431  	return func(cfg *fileDecryptConfig) {
   432  		cfg.plaintextAllowed = true
   433  	}
   434  }
   435  
   436  // WithDecryptAadPrefix explicitly supplies the file aad prefix.
   437  //
   438  // A must when a prefix is used for file encryption, but not stored in the file.
   439  func WithDecryptAadPrefix(prefix string) FileDecryptionOption {
   440  	return func(cfg *fileDecryptConfig) {
   441  		if prefix != "" {
   442  			cfg.aadPrefix = prefix
   443  		}
   444  	}
   445  }
   446  
   447  // Algorithm describes how something was encrypted, representing the EncryptionAlgorithm object from the
   448  // parquet.thrift file.
   449  type Algorithm struct {
   450  	Algo Cipher
   451  	Aad  struct {
   452  		AadPrefix       []byte
   453  		AadFileUnique   []byte
   454  		SupplyAadPrefix bool
   455  	}
   456  }
   457  
   458  // ToThrift returns an instance to be used for serializing when writing a file.
   459  func (e Algorithm) ToThrift() *format.EncryptionAlgorithm {
   460  	if e.Algo == AesGcm {
   461  		return &format.EncryptionAlgorithm{
   462  			AES_GCM_V1: &format.AesGcmV1{
   463  				AadPrefix:       e.Aad.AadPrefix,
   464  				AadFileUnique:   e.Aad.AadFileUnique,
   465  				SupplyAadPrefix: &e.Aad.SupplyAadPrefix,
   466  			},
   467  		}
   468  	}
   469  	return &format.EncryptionAlgorithm{
   470  		AES_GCM_CTR_V1: &format.AesGcmCtrV1{
   471  			AadPrefix:       e.Aad.AadPrefix,
   472  			AadFileUnique:   e.Aad.AadFileUnique,
   473  			SupplyAadPrefix: &e.Aad.SupplyAadPrefix,
   474  		},
   475  	}
   476  }
   477  
   478  // AlgorithmFromThrift converts the thrift object to the Algorithm struct for easier usage.
   479  func AlgorithmFromThrift(enc *format.EncryptionAlgorithm) (ret Algorithm) {
   480  	if enc.IsSetAES_GCM_V1() {
   481  		ret.Algo = AesGcm
   482  		ret.Aad.AadFileUnique = enc.AES_GCM_V1.AadFileUnique
   483  		ret.Aad.AadPrefix = enc.AES_GCM_V1.AadPrefix
   484  		ret.Aad.SupplyAadPrefix = *enc.AES_GCM_V1.SupplyAadPrefix
   485  		return
   486  	}
   487  	ret.Algo = AesCtr
   488  	ret.Aad.AadFileUnique = enc.AES_GCM_CTR_V1.AadFileUnique
   489  	ret.Aad.AadPrefix = enc.AES_GCM_CTR_V1.AadPrefix
   490  	ret.Aad.SupplyAadPrefix = *enc.AES_GCM_CTR_V1.SupplyAadPrefix
   491  	return
   492  }
   493  
   494  // FileEncryptionProperties describe how to encrypt a parquet file when writing data.
   495  type FileEncryptionProperties struct {
   496  	alg                  Algorithm
   497  	footerKey            string
   498  	footerKeyMetadata    string
   499  	encryptedFooter      bool
   500  	fileAad              string
   501  	utilized             bool
   502  	storeAadPrefixInFile bool
   503  	aadPrefix            string
   504  	encryptedCols        ColumnPathToEncryptionPropsMap
   505  }
   506  
   507  // EncryptedFooter returns if the footer for this file should be encrypted or left in plaintext.
   508  func (fe *FileEncryptionProperties) EncryptedFooter() bool { return fe.encryptedFooter }
   509  
   510  // Algorithm returns the description of how we will perform the encryption, the algorithm, prefixes, and so on.
   511  func (fe *FileEncryptionProperties) Algorithm() Algorithm { return fe.alg }
   512  
   513  // FooterKey returns the actual key used to encrypt the footer if it is encrypted, or to encrypt any columns which
   514  // will be encrypted with it rather than their own keys.
   515  func (fe *FileEncryptionProperties) FooterKey() string { return fe.footerKey }
   516  
   517  // FooterKeyMetadata is used for retrieving a key from the key retriever in order to set the footer key
   518  func (fe *FileEncryptionProperties) FooterKeyMetadata() string { return fe.footerKeyMetadata }
   519  
   520  // FileAad returns the aad identification to be used at the file level which gets concatenated with the row and column
   521  // information for encrypting data.
   522  func (fe *FileEncryptionProperties) FileAad() string { return fe.fileAad }
   523  
   524  // IsUtilized returns whether or not this instance has been used to encrypt a file
   525  func (fe *FileEncryptionProperties) IsUtilized() bool { return fe.utilized }
   526  
   527  // SetUtilized is called after writing a file. A FileEncryptionProperties object can be used for writing one file only,
   528  // the encryption keys will be wiped out upon completion of writing the file.
   529  func (fe *FileEncryptionProperties) SetUtilized() { fe.utilized = true }
   530  
   531  // EncryptedColumns returns the mapping of column paths to column encryption properties
   532  func (fe *FileEncryptionProperties) EncryptedColumns() ColumnPathToEncryptionPropsMap {
   533  	return fe.encryptedCols
   534  }
   535  
   536  // ColumnEncryptionProperties returns the properties for encrypting a given column.
   537  //
   538  // This may be nil for columns that aren't encrypted or may be default properties.
   539  func (fe *FileEncryptionProperties) ColumnEncryptionProperties(path string) *ColumnEncryptionProperties {
   540  	if len(fe.encryptedCols) == 0 {
   541  		return NewColumnEncryptionProperties(path)
   542  	}
   543  	if c, ok := fe.encryptedCols[path]; ok {
   544  		return c
   545  	}
   546  	return nil
   547  }
   548  
   549  // Clone allows returning an identical property setup for another file with the option to update the aadPrefix,
   550  // (if given the empty string, the current aad prefix will be used) since a single instance can only be used
   551  // to encrypt one file before wiping out the keys.
   552  func (fe *FileEncryptionProperties) Clone(newAadPrefix string) *FileEncryptionProperties {
   553  	footerKeyCopy := fe.footerKey
   554  	encryptedColsCopy := make(ColumnPathToEncryptionPropsMap)
   555  	for k, v := range fe.encryptedCols {
   556  		encryptedColsCopy[k] = v.Clone()
   557  	}
   558  	if newAadPrefix == "" {
   559  		newAadPrefix = fe.aadPrefix
   560  	}
   561  
   562  	opts := []EncryptOption{
   563  		WithAlg(fe.alg.Algo), WithFooterKeyMetadata(fe.footerKeyMetadata),
   564  		WithAadPrefix(newAadPrefix), WithEncryptedColumns(encryptedColsCopy),
   565  	}
   566  	if !fe.encryptedFooter {
   567  		opts = append(opts, WithPlaintextFooter())
   568  	}
   569  	if !fe.storeAadPrefixInFile {
   570  		opts = append(opts, DisableAadPrefixStorage())
   571  	}
   572  	return NewFileEncryptionProperties(footerKeyCopy, opts...)
   573  }
   574  
   575  // WipeOutEncryptionKeys clears all of the encryption keys for this and the columns
   576  func (fe *FileEncryptionProperties) WipeOutEncryptionKeys() {
   577  	fe.footerKey = ""
   578  	for _, elem := range fe.encryptedCols {
   579  		elem.WipeOutEncryptionKey()
   580  	}
   581  }
   582  
   583  type configEncrypt struct {
   584  	cipher               Cipher
   585  	encryptFooter        bool
   586  	keyMetadata          string
   587  	aadprefix            string
   588  	storeAadPrefixInFile bool
   589  	encryptedCols        ColumnPathToEncryptionPropsMap
   590  }
   591  
   592  // EncryptOption is used for specifying values when building FileEncryptionProperties
   593  type EncryptOption func(*configEncrypt)
   594  
   595  // WithPlaintextFooter sets the writer to write the footer in plain text, otherwise the footer will be encrypted
   596  // too (which is the default behavior).
   597  func WithPlaintextFooter() EncryptOption {
   598  	return func(cfg *configEncrypt) {
   599  		cfg.encryptFooter = false
   600  	}
   601  }
   602  
   603  // WithAlg sets the encryption algorithm to utilize. (default is AesGcm)
   604  func WithAlg(cipher Cipher) EncryptOption {
   605  	return func(cfg *configEncrypt) {
   606  		cfg.cipher = cipher
   607  	}
   608  }
   609  
   610  // WithFooterKeyID sets a key retrieval metadata to use (converted from string), this must be a utf8 string.
   611  //
   612  // use either WithFooterKeyID or WithFooterKeyMetadata, not both.
   613  func WithFooterKeyID(key string) EncryptOption {
   614  	if !utf8.ValidString(key) {
   615  		panic("parquet: footer key id should be UTF8 encoded")
   616  	}
   617  	return WithFooterKeyMetadata(key)
   618  }
   619  
   620  // WithFooterKeyMetadata sets a key retrieval metadata to use for getting the key.
   621  //
   622  // Use either WithFooterKeyID or WithFooterKeyMetadata, not both.
   623  func WithFooterKeyMetadata(keyMeta string) EncryptOption {
   624  	return func(cfg *configEncrypt) {
   625  		if keyMeta != "" {
   626  			cfg.keyMetadata = keyMeta
   627  		}
   628  	}
   629  }
   630  
   631  // WithAadPrefix sets the AAD prefix to use for encryption and by default will store it in the file
   632  func WithAadPrefix(aadPrefix string) EncryptOption {
   633  	return func(cfg *configEncrypt) {
   634  		if aadPrefix != "" {
   635  			cfg.aadprefix = aadPrefix
   636  			cfg.storeAadPrefixInFile = true
   637  		}
   638  	}
   639  }
   640  
   641  // DisableAadPrefixStorage will set the properties to not store the AadPrefix in the file. If this isn't called
   642  // and the AadPrefix is set, then it will be stored. This needs to in the options *after* WithAadPrefix to have an effect.
   643  func DisableAadPrefixStorage() EncryptOption {
   644  	return func(cfg *configEncrypt) {
   645  		cfg.storeAadPrefixInFile = false
   646  	}
   647  }
   648  
   649  // WithEncryptedColumns sets the map of columns and their properties (keys etc.) If not called, then all columns will
   650  // be encrypted with the footer key. If called, then columns not in the map will be left unencrypted.
   651  func WithEncryptedColumns(encrypted ColumnPathToEncryptionPropsMap) EncryptOption {
   652  	none := func(*configEncrypt) {}
   653  	if len(encrypted) == 0 {
   654  		return none
   655  	}
   656  	return func(cfg *configEncrypt) {
   657  		if len(cfg.encryptedCols) != 0 {
   658  			panic("column properties already set")
   659  		}
   660  		for _, v := range encrypted {
   661  			if v.IsUtilized() {
   662  				panic("column properties utilized in another file")
   663  			}
   664  			v.SetUtilized()
   665  		}
   666  		cfg.encryptedCols = encrypted
   667  	}
   668  }
   669  
   670  // NewFileEncryptionProperties returns a new File Encryption description object using the options provided.
   671  func NewFileEncryptionProperties(footerKey string, opts ...EncryptOption) *FileEncryptionProperties {
   672  	var cfg configEncrypt
   673  	cfg.cipher = DefaultEncryptionAlgorithm
   674  	cfg.encryptFooter = DefaultEncryptedFooter
   675  	for _, o := range opts {
   676  		o(&cfg)
   677  	}
   678  
   679  	props := &FileEncryptionProperties{
   680  		footerKey:            footerKey,
   681  		footerKeyMetadata:    cfg.keyMetadata,
   682  		encryptedFooter:      cfg.encryptFooter,
   683  		aadPrefix:            cfg.aadprefix,
   684  		storeAadPrefixInFile: cfg.storeAadPrefixInFile,
   685  		encryptedCols:        cfg.encryptedCols,
   686  		utilized:             false,
   687  	}
   688  
   689  	aadFileUnique := [AadFileUniqueLength]uint8{}
   690  	_, err := rand.Read(aadFileUnique[:])
   691  	if err != nil {
   692  		panic(err)
   693  	}
   694  
   695  	supplyAadPrefix := false
   696  	if props.aadPrefix == "" {
   697  		props.fileAad = string(aadFileUnique[:])
   698  	} else {
   699  		props.fileAad = props.aadPrefix + string(aadFileUnique[:])
   700  		if !props.storeAadPrefixInFile {
   701  			supplyAadPrefix = true
   702  		}
   703  	}
   704  	props.alg.Algo = cfg.cipher
   705  	props.alg.Aad.AadFileUnique = aadFileUnique[:]
   706  	props.alg.Aad.SupplyAadPrefix = supplyAadPrefix
   707  	if cfg.aadprefix != "" && cfg.storeAadPrefixInFile {
   708  		props.alg.Aad.AadPrefix = []byte(props.aadPrefix)
   709  	}
   710  	return props
   711  }