Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Add computation and verification of previous layers' hashes #44

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions blockcipher/blockcipher.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
82 changes: 61 additions & 21 deletions encryption.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -97,20 +112,30 @@ 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 {
annotation := desc.Annotations[annotationsID]
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
Expand All @@ -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
}
}

Expand All @@ -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")
Expand Down Expand Up @@ -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
Expand All @@ -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) {
Expand Down Expand Up @@ -301,23 +327,37 @@ 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
}
}

/* calculate the next PreviousLayerDigest on the *decrypted* layer's digest */
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")
}
}

Expand All @@ -328,10 +368,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'
Expand Down
14 changes: 12 additions & 2 deletions encryption_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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 {
Expand All @@ -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)
}
Expand Down
26 changes: 26 additions & 0 deletions scripts/calc_next_previous_layers_digest
Original file line number Diff line number Diff line change
@@ -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
48 changes: 48 additions & 0 deletions utils/hashes.go
Original file line number Diff line number Diff line change
@@ -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
}