diff --git a/blockcipher/blockcipher.go b/blockcipher/blockcipher.go index da403d9..3cb13b9 100644 --- a/blockcipher/blockcipher.go +++ b/blockcipher/blockcipher.go @@ -45,6 +45,9 @@ type PrivateLayerBlockCipherOptions struct { // CipherOptions contains the cipher metadata used for encryption/decryption // This field should be populated by Encrypt/Decrypt calls CipherOptions map[string][]byte `json:"cipheroptions"` + + // PreviousLayersDigest is the accumulated digest of all previous layers + PreviousLayersDigest digest.Digest `json:"previouslayersdigest"` } // PublicLayerBlockCipherOptions includes the information required to encrypt/decrypt diff --git a/encryption.go b/encryption.go index f5142cc..bcab1fc 100644 --- a/encryption.go +++ b/encryption.go @@ -17,25 +17,28 @@ package ocicrypt import ( + "bytes" "encoding/base64" + "encoding/hex" "encoding/json" "fmt" - keyproviderconfig "github.com/containers/ocicrypt/config/keyprovider-config" - "github.com/containers/ocicrypt/keywrap/keyprovider" "io" "strings" "github.com/containers/ocicrypt/blockcipher" "github.com/containers/ocicrypt/config" + keyproviderconfig "github.com/containers/ocicrypt/config/keyprovider-config" "github.com/containers/ocicrypt/keywrap" "github.com/containers/ocicrypt/keywrap/jwe" + "github.com/containers/ocicrypt/keywrap/keyprovider" "github.com/containers/ocicrypt/keywrap/pgp" "github.com/containers/ocicrypt/keywrap/pkcs11" "github.com/containers/ocicrypt/keywrap/pkcs7" + "github.com/containers/ocicrypt/utils" "github.com/opencontainers/go-digest" - log "github.com/sirupsen/logrus" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" + log "github.com/sirupsen/logrus" ) // EncryptLayerFinalizer is a finalizer run to return the annotations to set for @@ -86,8 +89,20 @@ func GetWrappedKeysMap(desc ocispec.Descriptor) map[string]string { return wrappedKeysMap } +// comparePreviousLayersDigests compares the given digests and returns an error if they do not match +func comparePreviousLayersDigests(previousLayersDigest []byte, expPreviousLayersDigest digest.Digest) error { + digest, err := hex.DecodeString(expPreviousLayersDigest.Encoded()) + if err != nil { + return errors.Wrapf(err, "Hex-decoding expected previous layers hash failed") + } + if !bytes.Equal(digest, previousLayersDigest) { + return errors.Errorf("Previous layer digest '%x' does not match expected one '%x'", previousLayersDigest, digest) + } + return nil +} + // EncryptLayer encrypts the layer by running one encryptor after the other -func EncryptLayer(ec *config.EncryptConfig, encOrPlainLayerReader io.Reader, desc ocispec.Descriptor) (io.Reader, EncryptLayerFinalizer, error) { +func EncryptLayer(ec *config.EncryptConfig, encOrPlainLayerReader io.Reader, desc ocispec.Descriptor, previousLayersDigest []byte) (io.Reader, EncryptLayerFinalizer, []byte, error) { var ( encLayerReader io.Reader err error @@ -97,8 +112,18 @@ func EncryptLayer(ec *config.EncryptConfig, encOrPlainLayerReader io.Reader, des pubOptsData []byte ) + if len(previousLayersDigest) == 0 { + /* bottom-most layer MUST start with sha256.Sum(nil) */ + return nil, nil, nil, errors.New("previousLayersDigest must not be nil") + } + if ec == nil { - return nil, nil, errors.New("EncryptConfig must not be nil") + return nil, nil, nil, errors.New("EncryptConfig must not be nil") + } + + newLayersDigest, err := utils.GetNewLayersDigest(previousLayersDigest, desc.Digest) + if err != nil { + return nil, nil, nil, err } for annotationsID := range keyWrapperAnnotations { @@ -106,11 +131,11 @@ func EncryptLayer(ec *config.EncryptConfig, encOrPlainLayerReader io.Reader, des if annotation != "" { privOptsData, err = decryptLayerKeyOptsData(&ec.DecryptConfig, desc) if err != nil { - return nil, nil, err + return nil, nil, nil, err } pubOptsData, err = getLayerPubOpts(desc) if err != nil { - return nil, nil, err + return nil, nil, nil, err } // already encrypted! encrypted = true @@ -120,7 +145,7 @@ func EncryptLayer(ec *config.EncryptConfig, encOrPlainLayerReader io.Reader, des if !encrypted { encLayerReader, bcFin, err = commonEncryptLayer(encOrPlainLayerReader, desc.Digest, blockcipher.AES256CTR) if err != nil { - return nil, nil, err + return nil, nil, nil, err } } @@ -131,6 +156,8 @@ func EncryptLayer(ec *config.EncryptConfig, encOrPlainLayerReader io.Reader, des if err != nil { return nil, err } + + opts.Private.PreviousLayersDigest = digest.NewDigestFromBytes(digest.SHA256, previousLayersDigest) privOptsData, err = json.Marshal(opts.Private) if err != nil { return nil, errors.Wrapf(err, "could not JSON marshal opts") @@ -169,8 +196,7 @@ func EncryptLayer(ec *config.EncryptConfig, encOrPlainLayerReader io.Reader, des } // if nothing was encrypted, we just return encLayer = nil - return encLayerReader, encLayerFinalizer, err - + return encLayerReader, encLayerFinalizer, newLayersDigest, err } // preWrapKeys calls WrapKeys and handles the base64 encoding and concatenation of the @@ -190,22 +216,22 @@ func preWrapKeys(keywrapper keywrap.KeyWrapper, ec *config.EncryptConfig, b64Ann // DecryptLayer decrypts a layer trying one keywrap.KeyWrapper after the other to see whether it // can apply the provided private key // If unwrapOnly is set we will only try to decrypt the layer encryption key and return -func DecryptLayer(dc *config.DecryptConfig, encLayerReader io.Reader, desc ocispec.Descriptor, unwrapOnly bool) (io.Reader, digest.Digest, error) { +func DecryptLayer(dc *config.DecryptConfig, encLayerReader io.Reader, desc ocispec.Descriptor, unwrapOnly bool, previousLayersDigest []byte) (io.Reader, digest.Digest, []byte, error) { if dc == nil { - return nil, "", errors.New("DecryptConfig must not be nil") + return nil, "", nil, errors.New("DecryptConfig must not be nil") } privOptsData, err := decryptLayerKeyOptsData(dc, desc) if err != nil || unwrapOnly { - return nil, "", err + return nil, "", nil, err } var pubOptsData []byte pubOptsData, err = getLayerPubOpts(desc) if err != nil { - return nil, "", err + return nil, "", nil, err } - return commonDecryptLayer(encLayerReader, privOptsData, pubOptsData) + return commonDecryptLayer(encLayerReader, privOptsData, pubOptsData, previousLayersDigest) } func decryptLayerKeyOptsData(dc *config.DecryptConfig, desc ocispec.Descriptor) ([]byte, error) { @@ -301,23 +327,36 @@ func commonEncryptLayer(plainLayerReader io.Reader, d digest.Digest, typ blockci // commonDecryptLayer decrypts an encrypted layer previously encrypted with commonEncryptLayer // by passing along the optsData -func commonDecryptLayer(encLayerReader io.Reader, privOptsData []byte, pubOptsData []byte) (io.Reader, digest.Digest, error) { +func commonDecryptLayer(encLayerReader io.Reader, privOptsData []byte, pubOptsData []byte, previousLayersDigest []byte) (io.Reader, digest.Digest, []byte, error) { privOpts := blockcipher.PrivateLayerBlockCipherOptions{} err := json.Unmarshal(privOptsData, &privOpts) if err != nil { - return nil, "", errors.Wrapf(err, "could not JSON unmarshal privOptsData") + return nil, "", nil, errors.Wrapf(err, "could not JSON unmarshal privOptsData") + } + + if len(privOpts.PreviousLayersDigest) > 0 { + /* older images do not have this */ + err = comparePreviousLayersDigests(previousLayersDigest, privOpts.PreviousLayersDigest) + if err != nil { + return nil, "", nil, err + } + } + + newLayersDigest, err := utils.GetNewLayersDigest(previousLayersDigest, privOpts.Digest) + if err != nil { + return nil, "", nil, err } lbch, err := blockcipher.NewLayerBlockCipherHandler() if err != nil { - return nil, "", err + return nil, "", nil, err } pubOpts := blockcipher.PublicLayerBlockCipherOptions{} if len(pubOptsData) > 0 { err := json.Unmarshal(pubOptsData, &pubOpts) if err != nil { - return nil, "", errors.Wrapf(err, "could not JSON unmarshal pubOptsData") + return nil, "", nil, errors.Wrapf(err, "could not JSON unmarshal pubOptsData") } } @@ -328,10 +367,10 @@ func commonDecryptLayer(encLayerReader io.Reader, privOptsData []byte, pubOptsDa plainLayerReader, opts, err := lbch.Decrypt(encLayerReader, opts) if err != nil { - return nil, "", err + return nil, "", nil, err } - return plainLayerReader, opts.Private.Digest, nil + return plainLayerReader, opts.Private.Digest, newLayersDigest, nil } // FilterOutAnnotations filters out the annotations belonging to the image encryption 'namespace' diff --git a/encryption_test.go b/encryption_test.go index f76de5d..ff818e9 100644 --- a/encryption_test.go +++ b/encryption_test.go @@ -23,6 +23,7 @@ import ( "testing" "github.com/containers/ocicrypt/config" + "github.com/containers/ocicrypt/utils" digest "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" ) @@ -100,12 +101,21 @@ func TestEncryptLayer(t *testing.T) { } dataReader := bytes.NewReader(data) + previousLayersDigest := utils.GetInitialPreviousLayersDigest() - encLayerReader, encLayerFinalizer, err := EncryptLayer(ec, dataReader, desc) + encLayerReader, encLayerFinalizer, newLayersDigest, err := EncryptLayer(ec, dataReader, desc, previousLayersDigest) if err != nil { t.Fatal(err) } + expDigest, err := utils.GetNewLayersDigest(previousLayersDigest, desc.Digest) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(expDigest, newLayersDigest) { + t.Fatal("Previous layer digest is wrong") + } + encLayer := make([]byte, 1024) encsize, err := encLayerReader.Read(encLayer) if err != io.EOF { @@ -126,7 +136,7 @@ func TestEncryptLayer(t *testing.T) { Annotations: annotations, } - decLayerReader, _, err := DecryptLayer(dc, encLayerReaderAt, newDesc, false) + decLayerReader, _, _, err := DecryptLayer(dc, encLayerReaderAt, newDesc, false, previousLayersDigest) if err != nil { t.Fatal(err) } diff --git a/scripts/calc_next_previous_layers_digest b/scripts/calc_next_previous_layers_digest new file mode 100755 index 0000000..1da390f --- /dev/null +++ b/scripts/calc_next_previous_layers_digest @@ -0,0 +1,26 @@ +#!/usr/bin/env bash + + +echo "Enter PreviousLayersDigest or leave empty for initial one" +read previousLayersDigest +if [ -z "$previousLayersDigest" ]; then + previousLayersDigest=$(echo -en | sha256sum | gawk '{print $1}') +fi +if ! [[ $previousLayersDigest =~ ^[0-9a-fA-F]{64}$ ]]; then + echo "previousLayersDigest '$previousLayersDigest' must be a sha256" + exit 1 +fi +while :; do + echo "Enter current layer's digest" + read currentLayerDigest + if ! [[ $currentLayerDigest =~ ^[0-9a-fA-F]{64}$ ]]; then + echo "current layer digest must be a sha256" + exit 1 + fi + + dig=$(echo -n "${previousLayersDigest}${currentLayerDigest}" | + sed -n 's/[0-9a-fA-F]\{2\}/\\x\0/pg' ) + + previousLayersDigest=$(echo -en "${dig}" | sha256sum | gawk '{print $1}') + echo "digest: ${previousLayersDigest}" +done diff --git a/utils/hashes.go b/utils/hashes.go new file mode 100644 index 0000000..bd641ca --- /dev/null +++ b/utils/hashes.go @@ -0,0 +1,48 @@ +/* + 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 utils + +import ( + "crypto/sha256" + "encoding/hex" + + "github.com/opencontainers/go-digest" + "github.com/pkg/errors" +) + +// GetInitalPreviousLayersDigest returns the initial value for previousLayersDigest +func GetInitialPreviousLayersDigest() []byte { + digest := sha256.Sum256(nil) + return digest[:] +} + +// GetNewLayersDigest calculates the new layer digest from the previousLayersDigest and the layerDigest. +func GetNewLayersDigest(previousLayersDigest []byte, layerDigest digest.Digest) ([]byte, error) { + newDigest := sha256.New() + // never returns an error but linter requires us to look at it + _, err := newDigest.Write(previousLayersDigest) + if err != nil { + return nil, err + } + + digest, err := hex.DecodeString(layerDigest.Encoded()) + if err != nil { + return nil, errors.Wrap(err, "Hex decoding digest failed") + } + _, err = newDigest.Write(digest) + return newDigest.Sum(nil), err +}