Skip to content

Commit 77249b3

Browse files
committed
Add computation and verification of previous layers' hashes
This patch adds the computation of previous layers accumulated hashes on the encryption side and writes this computed hash into the private options of a layer. The private options will be encrypted then. On the decryption side it also performs the computations and, if the private options contain the previous layers' hash, which may not be the case for older images but will be the case for newer ones, it compares the expected hash against the computed one and errors if they don't match. The previous layers' digest needs to be passed from one layer encrytion step to the next. The sequence must begin with the bottom-most layer getting sha256.Sum256(nil) passed so that no other layer can be slid underneath the bottom-most one. This patch at least helps fulfill the requirement that previous layers cannot be manipulated assuming the attacker can access the registry but of course not manipulate the decryption code. Signed-off-by: Stefan Berger <[email protected]>
1 parent 059f6b1 commit 77249b3

File tree

4 files changed

+97
-23
lines changed

4 files changed

+97
-23
lines changed

blockcipher/blockcipher.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ type PrivateLayerBlockCipherOptions struct {
4545
// CipherOptions contains the cipher metadata used for encryption/decryption
4646
// This field should be populated by Encrypt/Decrypt calls
4747
CipherOptions map[string][]byte `json:"cipheroptions"`
48+
49+
// PreviousLayersDigest is the accumulated digest of all previous layers
50+
PreviousLayersDigest digest.Digest `json:"previouslayersdigest"`
4851
}
4952

5053
// PublicLayerBlockCipherOptions includes the information required to encrypt/decrypt

encryption.go

Lines changed: 60 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -17,25 +17,28 @@
1717
package ocicrypt
1818

1919
import (
20+
"bytes"
2021
"encoding/base64"
22+
"encoding/hex"
2123
"encoding/json"
2224
"fmt"
23-
keyproviderconfig "github.com/containers/ocicrypt/config/keyprovider-config"
24-
"github.com/containers/ocicrypt/keywrap/keyprovider"
2525
"io"
2626
"strings"
2727

2828
"github.com/containers/ocicrypt/blockcipher"
2929
"github.com/containers/ocicrypt/config"
30+
keyproviderconfig "github.com/containers/ocicrypt/config/keyprovider-config"
3031
"github.com/containers/ocicrypt/keywrap"
3132
"github.com/containers/ocicrypt/keywrap/jwe"
33+
"github.com/containers/ocicrypt/keywrap/keyprovider"
3234
"github.com/containers/ocicrypt/keywrap/pgp"
3335
"github.com/containers/ocicrypt/keywrap/pkcs11"
3436
"github.com/containers/ocicrypt/keywrap/pkcs7"
37+
"github.com/containers/ocicrypt/utils"
3538
"github.com/opencontainers/go-digest"
36-
log "github.com/sirupsen/logrus"
3739
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
3840
"github.com/pkg/errors"
41+
log "github.com/sirupsen/logrus"
3942
)
4043

4144
// EncryptLayerFinalizer is a finalizer run to return the annotations to set for
@@ -86,8 +89,20 @@ func GetWrappedKeysMap(desc ocispec.Descriptor) map[string]string {
8689
return wrappedKeysMap
8790
}
8891

92+
// comparePreviousLayersDigests compares the given digests and returns an error if they do not match
93+
func comparePreviousLayersDigests(previousLayersDigest []byte, expPreviousLayersDigest digest.Digest) error {
94+
digest, err := hex.DecodeString(expPreviousLayersDigest.Encoded())
95+
if err != nil {
96+
return errors.Wrapf(err, "Hex-decoding expected previous layers hash failed")
97+
}
98+
if !bytes.Equal(digest, previousLayersDigest) {
99+
return errors.Errorf("Previous layer digest '%x' does not match expected one '%x'", previousLayersDigest, digest)
100+
}
101+
return nil
102+
}
103+
89104
// EncryptLayer encrypts the layer by running one encryptor after the other
90-
func EncryptLayer(ec *config.EncryptConfig, encOrPlainLayerReader io.Reader, desc ocispec.Descriptor) (io.Reader, EncryptLayerFinalizer, error) {
105+
func EncryptLayer(ec *config.EncryptConfig, encOrPlainLayerReader io.Reader, desc ocispec.Descriptor, previousLayersDigest []byte) (io.Reader, EncryptLayerFinalizer, []byte, error) {
91106
var (
92107
encLayerReader io.Reader
93108
err error
@@ -97,20 +112,30 @@ func EncryptLayer(ec *config.EncryptConfig, encOrPlainLayerReader io.Reader, des
97112
pubOptsData []byte
98113
)
99114

115+
if len(previousLayersDigest) == 0 {
116+
/* bottom-most layer MUST start with sha256.Sum(nil) */
117+
return nil, nil, nil, errors.New("previousLayersDigest must not be nil")
118+
}
119+
100120
if ec == nil {
101-
return nil, nil, errors.New("EncryptConfig must not be nil")
121+
return nil, nil, nil, errors.New("EncryptConfig must not be nil")
122+
}
123+
124+
newLayersDigest, err := utils.GetNewLayersDigest(previousLayersDigest, desc.Digest)
125+
if err != nil {
126+
return nil, nil, nil, err
102127
}
103128

104129
for annotationsID := range keyWrapperAnnotations {
105130
annotation := desc.Annotations[annotationsID]
106131
if annotation != "" {
107132
privOptsData, err = decryptLayerKeyOptsData(&ec.DecryptConfig, desc)
108133
if err != nil {
109-
return nil, nil, err
134+
return nil, nil, nil, err
110135
}
111136
pubOptsData, err = getLayerPubOpts(desc)
112137
if err != nil {
113-
return nil, nil, err
138+
return nil, nil, nil, err
114139
}
115140
// already encrypted!
116141
encrypted = true
@@ -120,7 +145,7 @@ func EncryptLayer(ec *config.EncryptConfig, encOrPlainLayerReader io.Reader, des
120145
if !encrypted {
121146
encLayerReader, bcFin, err = commonEncryptLayer(encOrPlainLayerReader, desc.Digest, blockcipher.AES256CTR)
122147
if err != nil {
123-
return nil, nil, err
148+
return nil, nil, nil, err
124149
}
125150
}
126151

@@ -131,6 +156,8 @@ func EncryptLayer(ec *config.EncryptConfig, encOrPlainLayerReader io.Reader, des
131156
if err != nil {
132157
return nil, err
133158
}
159+
160+
opts.Private.PreviousLayersDigest = digest.NewDigestFromBytes(digest.SHA256, previousLayersDigest)
134161
privOptsData, err = json.Marshal(opts.Private)
135162
if err != nil {
136163
return nil, errors.Wrapf(err, "could not JSON marshal opts")
@@ -169,8 +196,7 @@ func EncryptLayer(ec *config.EncryptConfig, encOrPlainLayerReader io.Reader, des
169196
}
170197

171198
// if nothing was encrypted, we just return encLayer = nil
172-
return encLayerReader, encLayerFinalizer, err
173-
199+
return encLayerReader, encLayerFinalizer, newLayersDigest, err
174200
}
175201

176202
// 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
190216
// DecryptLayer decrypts a layer trying one keywrap.KeyWrapper after the other to see whether it
191217
// can apply the provided private key
192218
// If unwrapOnly is set we will only try to decrypt the layer encryption key and return
193-
func DecryptLayer(dc *config.DecryptConfig, encLayerReader io.Reader, desc ocispec.Descriptor, unwrapOnly bool) (io.Reader, digest.Digest, error) {
219+
func DecryptLayer(dc *config.DecryptConfig, encLayerReader io.Reader, desc ocispec.Descriptor, unwrapOnly bool, previousLayersDigest []byte) (io.Reader, digest.Digest, []byte, error) {
194220
if dc == nil {
195-
return nil, "", errors.New("DecryptConfig must not be nil")
221+
return nil, "", nil, errors.New("DecryptConfig must not be nil")
196222
}
197223
privOptsData, err := decryptLayerKeyOptsData(dc, desc)
198224
if err != nil || unwrapOnly {
199-
return nil, "", err
225+
return nil, "", nil, err
200226
}
201227

202228
var pubOptsData []byte
203229
pubOptsData, err = getLayerPubOpts(desc)
204230
if err != nil {
205-
return nil, "", err
231+
return nil, "", nil, err
206232
}
207233

208-
return commonDecryptLayer(encLayerReader, privOptsData, pubOptsData)
234+
return commonDecryptLayer(encLayerReader, privOptsData, pubOptsData, previousLayersDigest)
209235
}
210236

211237
func decryptLayerKeyOptsData(dc *config.DecryptConfig, desc ocispec.Descriptor) ([]byte, error) {
@@ -301,23 +327,36 @@ func commonEncryptLayer(plainLayerReader io.Reader, d digest.Digest, typ blockci
301327

302328
// commonDecryptLayer decrypts an encrypted layer previously encrypted with commonEncryptLayer
303329
// by passing along the optsData
304-
func commonDecryptLayer(encLayerReader io.Reader, privOptsData []byte, pubOptsData []byte) (io.Reader, digest.Digest, error) {
330+
func commonDecryptLayer(encLayerReader io.Reader, privOptsData []byte, pubOptsData []byte, previousLayersDigest []byte) (io.Reader, digest.Digest, []byte, error) {
305331
privOpts := blockcipher.PrivateLayerBlockCipherOptions{}
306332
err := json.Unmarshal(privOptsData, &privOpts)
307333
if err != nil {
308-
return nil, "", errors.Wrapf(err, "could not JSON unmarshal privOptsData")
334+
return nil, "", nil, errors.Wrapf(err, "could not JSON unmarshal privOptsData")
335+
}
336+
337+
if len(privOpts.PreviousLayersDigest) > 0 {
338+
/* older images do not have this */
339+
err = comparePreviousLayersDigests(previousLayersDigest, privOpts.PreviousLayersDigest)
340+
if err != nil {
341+
return nil, "", nil, err
342+
}
343+
}
344+
345+
newLayersDigest, err := utils.GetNewLayersDigest(previousLayersDigest, privOpts.Digest)
346+
if err != nil {
347+
return nil, "", nil, err
309348
}
310349

311350
lbch, err := blockcipher.NewLayerBlockCipherHandler()
312351
if err != nil {
313-
return nil, "", err
352+
return nil, "", nil, err
314353
}
315354

316355
pubOpts := blockcipher.PublicLayerBlockCipherOptions{}
317356
if len(pubOptsData) > 0 {
318357
err := json.Unmarshal(pubOptsData, &pubOpts)
319358
if err != nil {
320-
return nil, "", errors.Wrapf(err, "could not JSON unmarshal pubOptsData")
359+
return nil, "", nil, errors.Wrapf(err, "could not JSON unmarshal pubOptsData")
321360
}
322361
}
323362

@@ -328,10 +367,10 @@ func commonDecryptLayer(encLayerReader io.Reader, privOptsData []byte, pubOptsDa
328367

329368
plainLayerReader, opts, err := lbch.Decrypt(encLayerReader, opts)
330369
if err != nil {
331-
return nil, "", err
370+
return nil, "", nil, err
332371
}
333372

334-
return plainLayerReader, opts.Private.Digest, nil
373+
return plainLayerReader, opts.Private.Digest, newLayersDigest, nil
335374
}
336375

337376
// FilterOutAnnotations filters out the annotations belonging to the image encryption 'namespace'

encryption_test.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,13 @@ package ocicrypt
1818

1919
import (
2020
"bytes"
21+
"crypto/sha256"
2122
"io"
2223
"reflect"
2324
"testing"
2425

2526
"github.com/containers/ocicrypt/config"
27+
"github.com/containers/ocicrypt/utils"
2628
digest "github.com/opencontainers/go-digest"
2729
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
2830
)
@@ -100,12 +102,22 @@ func TestEncryptLayer(t *testing.T) {
100102
}
101103

102104
dataReader := bytes.NewReader(data)
105+
digest := sha256.Sum256(nil)
106+
previousLayersDigest := digest[:]
103107

104-
encLayerReader, encLayerFinalizer, err := EncryptLayer(ec, dataReader, desc)
108+
encLayerReader, encLayerFinalizer, newLayersDigest, err := EncryptLayer(ec, dataReader, desc, previousLayersDigest)
105109
if err != nil {
106110
t.Fatal(err)
107111
}
108112

113+
expDigest, err := utils.GetNewLayersDigest(previousLayersDigest, desc.Digest)
114+
if err != nil {
115+
t.Fatal(err)
116+
}
117+
if !bytes.Equal(expDigest, newLayersDigest) {
118+
t.Fatal("Previous layer digest is wrong")
119+
}
120+
109121
encLayer := make([]byte, 1024)
110122
encsize, err := encLayerReader.Read(encLayer)
111123
if err != io.EOF {
@@ -126,7 +138,7 @@ func TestEncryptLayer(t *testing.T) {
126138
Annotations: annotations,
127139
}
128140

129-
decLayerReader, _, err := DecryptLayer(dc, encLayerReaderAt, newDesc, false)
141+
decLayerReader, _, _, err := DecryptLayer(dc, encLayerReaderAt, newDesc, false, previousLayersDigest)
130142
if err != nil {
131143
t.Fatal(err)
132144
}

utils/utils.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,17 @@ package utils
1818

1919
import (
2020
"bytes"
21+
"crypto/sha256"
2122
"crypto/x509"
2223
"encoding/base64"
24+
"encoding/hex"
2325
"encoding/pem"
2426
"fmt"
2527
"strings"
2628

2729
"github.com/containers/ocicrypt/crypto/pkcs11"
2830

31+
"github.com/opencontainers/go-digest"
2932
"github.com/pkg/errors"
3033
"golang.org/x/crypto/openpgp"
3134
json "gopkg.in/square/go-jose.v2"
@@ -248,3 +251,20 @@ func SortDecryptionKeys(b64ItemList string) (map[string][][]byte, error) {
248251

249252
return dcparameters, nil
250253
}
254+
255+
// GetNewLayersDigest calculates the new layer digest from the previousLayersDigest and the layerDigest.
256+
func GetNewLayersDigest(previousLayersDigest []byte, layerDigest digest.Digest) ([]byte, error) {
257+
newDigest := sha256.New()
258+
// never returns an error but linter requires us to look at it
259+
_, err := newDigest.Write(previousLayersDigest)
260+
if err != nil {
261+
return nil, err
262+
}
263+
264+
digest, err := hex.DecodeString(layerDigest.Encoded())
265+
if err != nil {
266+
return nil, errors.Wrap(err, "Hex decoding digest failed")
267+
}
268+
_, err = newDigest.Write(digest)
269+
return newDigest.Sum(nil), err
270+
}

0 commit comments

Comments
 (0)