/* Copyright The ocicrypt Authors. Licensed 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 blockcipher import ( "errors" "fmt" "io" "github.com/opencontainers/go-digest" ) // LayerCipherType is the ciphertype as specified in the layer metadata type LayerCipherType string // TODO: Should be obtained from OCI spec once included const ( AES256CTR LayerCipherType = "AES_256_CTR_HMAC_SHA256" ) // PrivateLayerBlockCipherOptions includes the information required to encrypt/decrypt // an image which are sensitive and should not be in plaintext type PrivateLayerBlockCipherOptions struct { // SymmetricKey represents the symmetric key used for encryption/decryption // This field should be populated by Encrypt/Decrypt calls SymmetricKey []byte `json:"symkey"` // Digest is the digest of the original data for verification. // This is NOT populated by Encrypt/Decrypt calls Digest digest.Digest `json:"digest"` // CipherOptions contains the cipher metadata used for encryption/decryption // This field should be populated by Encrypt/Decrypt calls CipherOptions map[string][]byte `json:"cipheroptions"` } // PublicLayerBlockCipherOptions includes the information required to encrypt/decrypt // an image which are public and can be deduplicated in plaintext across multiple // recipients type PublicLayerBlockCipherOptions struct { // CipherType denotes the cipher type according to the list of OCI suppported // cipher types. CipherType LayerCipherType `json:"cipher"` // Hmac contains the hmac string to help verify encryption Hmac []byte `json:"hmac"` // CipherOptions contains the cipher metadata used for encryption/decryption // This field should be populated by Encrypt/Decrypt calls CipherOptions map[string][]byte `json:"cipheroptions"` } // LayerBlockCipherOptions contains the public and private LayerBlockCipherOptions // required to encrypt/decrypt an image type LayerBlockCipherOptions struct { Public PublicLayerBlockCipherOptions Private PrivateLayerBlockCipherOptions } // LayerBlockCipher returns a provider for encrypt/decrypt functionality // for handling the layer data for a specific algorithm type LayerBlockCipher interface { // GenerateKey creates a symmetric key GenerateKey() ([]byte, error) // Encrypt takes in layer data and returns the ciphertext and relevant LayerBlockCipherOptions Encrypt(layerDataReader io.Reader, opt LayerBlockCipherOptions) (io.Reader, Finalizer, error) // Decrypt takes in layer ciphertext data and returns the plaintext and relevant LayerBlockCipherOptions Decrypt(layerDataReader io.Reader, opt LayerBlockCipherOptions) (io.Reader, LayerBlockCipherOptions, error) } // LayerBlockCipherHandler is the handler for encrypt/decrypt for layers type LayerBlockCipherHandler struct { cipherMap map[LayerCipherType]LayerBlockCipher } // Finalizer is called after data blobs are written, and returns the LayerBlockCipherOptions for the encrypted blob type Finalizer func() (LayerBlockCipherOptions, error) // GetOpt returns the value of the cipher option and if the option exists func (lbco LayerBlockCipherOptions) GetOpt(key string) (value []byte, ok bool) { if v, ok := lbco.Public.CipherOptions[key]; ok { return v, ok } else if v, ok := lbco.Private.CipherOptions[key]; ok { return v, ok } else { return nil, false } } func wrapFinalizerWithType(fin Finalizer, typ LayerCipherType) Finalizer { return func() (LayerBlockCipherOptions, error) { lbco, err := fin() if err != nil { return LayerBlockCipherOptions{}, err } lbco.Public.CipherType = typ return lbco, err } } // Encrypt is the handler for the layer decryption routine func (h *LayerBlockCipherHandler) Encrypt(plainDataReader io.Reader, typ LayerCipherType) (io.Reader, Finalizer, error) { if c, ok := h.cipherMap[typ]; ok { sk, err := c.GenerateKey() if err != nil { return nil, nil, err } opt := LayerBlockCipherOptions{ Private: PrivateLayerBlockCipherOptions{ SymmetricKey: sk, }, } encDataReader, fin, err := c.Encrypt(plainDataReader, opt) if err == nil { fin = wrapFinalizerWithType(fin, typ) } return encDataReader, fin, err } return nil, nil, fmt.Errorf("unsupported cipher type: %s", typ) } // Decrypt is the handler for the layer decryption routine func (h *LayerBlockCipherHandler) Decrypt(encDataReader io.Reader, opt LayerBlockCipherOptions) (io.Reader, LayerBlockCipherOptions, error) { typ := opt.Public.CipherType if typ == "" { return nil, LayerBlockCipherOptions{}, errors.New("no cipher type provided") } if c, ok := h.cipherMap[typ]; ok { return c.Decrypt(encDataReader, opt) } return nil, LayerBlockCipherOptions{}, fmt.Errorf("unsupported cipher type: %s", typ) } // NewLayerBlockCipherHandler returns a new default handler func NewLayerBlockCipherHandler() (*LayerBlockCipherHandler, error) { h := LayerBlockCipherHandler{ cipherMap: map[LayerCipherType]LayerBlockCipher{}, } var err error h.cipherMap[AES256CTR], err = NewAESCTRLayerBlockCipher(256) if err != nil { return nil, fmt.Errorf("unable to set up Cipher AES-256-CTR: %w", err) } return &h, nil }