crypto: update SSE-S3 and SSE-C key derivation (#6152)

This commit updates the key derivation to reflect the
latest change of crypto/doc.go. This includes handling
the insecure legacy KDF.

Since #6064 is fixed, the 3. test case for object key
generation is enabled again.
master
Andreas Auernhammer 6 years ago committed by kannappanr
parent 2a12e694f3
commit 289d6ce1d7
  1. 7
      cmd/crypto/error.go
  2. 67
      cmd/crypto/key.go
  3. 47
      cmd/crypto/key_test.go
  4. 12
      cmd/crypto/sse.go

@ -16,6 +16,13 @@ package crypto
import "errors" import "errors"
// Error is the generic type for any error happening during decrypting
// an object. It indicates that the object itself or its metadata was
// modified accidentally or maliciously.
type Error struct{ msg string }
func (e Error) Error() string { return e.msg }
var ( var (
// ErrInvalidEncryptionMethod indicates that the specified SSE encryption method // ErrInvalidEncryptionMethod indicates that the specified SSE encryption method
// is not supported. // is not supported.

@ -21,8 +21,9 @@ import (
"crypto/rand" "crypto/rand"
"encoding/binary" "encoding/binary"
"errors" "errors"
"fmt"
"io" "io"
"path/filepath" "path"
"github.com/minio/minio/cmd/logger" "github.com/minio/minio/cmd/logger"
sha256 "github.com/minio/sha256-simd" sha256 "github.com/minio/sha256-simd"
@ -51,33 +52,69 @@ func GenerateKey(extKey [32]byte, random io.Reader) (key ObjectKey) {
return return
} }
// SealedKey represents a sealed object key. It can be stored
// at an untrusted location.
type SealedKey struct {
Key [64]byte // The encrypted and authenticted object-key.
IV [32]byte // The random IV used to encrypt the object-key.
Algorithm string // The sealing algorithm used to encrypt the object key.
}
// Seal encrypts the ObjectKey using the 256 bit external key and IV. The sealed // Seal encrypts the ObjectKey using the 256 bit external key and IV. The sealed
// key is also cryptographically bound to the object's path (bucket/object). // key is also cryptographically bound to the object's path (bucket/object) and the
func (key ObjectKey) Seal(extKey, iv [32]byte, bucket, object string) []byte { // domain (SSE-C or SSE-S3).
var sealedKey bytes.Buffer func (key ObjectKey) Seal(extKey, iv [32]byte, domain, bucket, object string) SealedKey {
var (
sealingKey [32]byte
encryptedKey bytes.Buffer
)
mac := hmac.New(sha256.New, extKey[:]) mac := hmac.New(sha256.New, extKey[:])
mac.Write(iv[:]) mac.Write(iv[:])
mac.Write([]byte(filepath.Join(bucket, object))) mac.Write([]byte(domain))
mac.Write([]byte(SealAlgorithm))
mac.Write([]byte(path.Join(bucket, object))) // use path.Join for canonical 'bucket/object'
mac.Sum(sealingKey[:0])
if n, err := sio.Encrypt(&sealedKey, bytes.NewReader(key[:]), sio.Config{Key: mac.Sum(nil)}); n != 64 || err != nil { if n, err := sio.Encrypt(&encryptedKey, bytes.NewReader(key[:]), sio.Config{Key: sealingKey[:]}); n != 64 || err != nil {
logger.CriticalIf(context.Background(), errors.New("Unable to generate sealed key")) logger.CriticalIf(context.Background(), errors.New("Unable to generate sealed key"))
} }
return sealedKey.Bytes() sealedKey := SealedKey{
IV: iv,
Algorithm: SealAlgorithm,
}
copy(sealedKey.Key[:], encryptedKey.Bytes())
return sealedKey
} }
// Unseal decrypts a sealed key using the 256 bit external key and IV. Since the sealed key // Unseal decrypts a sealed key using the 256 bit external key. Since the sealed key
// is cryptographically bound to the object's path the same bucket/object as during sealing // may be cryptographically bound to the object's path the same bucket/object as during sealing
// must be provided. On success the ObjectKey contains the decrypted sealed key. // must be provided. On success the ObjectKey contains the decrypted sealed key.
func (key *ObjectKey) Unseal(sealedKey []byte, extKey, iv [32]byte, bucket, object string) error { func (key *ObjectKey) Unseal(extKey [32]byte, sealedKey SealedKey, domain, bucket, object string) error {
var unsealedKey bytes.Buffer var (
unsealConfig sio.Config
decryptedKey bytes.Buffer
)
switch sealedKey.Algorithm {
default:
return Error{fmt.Sprintf("The sealing algorithm '%s' is not supported", sealedKey.Algorithm)}
case SealAlgorithm:
mac := hmac.New(sha256.New, extKey[:]) mac := hmac.New(sha256.New, extKey[:])
mac.Write(iv[:]) mac.Write(sealedKey.IV[:])
mac.Write([]byte(filepath.Join(bucket, object))) mac.Write([]byte(domain))
mac.Write([]byte(SealAlgorithm))
mac.Write([]byte(path.Join(bucket, object))) // use path.Join for canonical 'bucket/object'
unsealConfig = sio.Config{MinVersion: sio.Version20, Key: mac.Sum(nil)}
case InsecureSealAlgorithm:
sha := sha256.New()
sha.Write(extKey[:])
sha.Write(sealedKey.IV[:])
unsealConfig = sio.Config{MinVersion: sio.Version10, Key: sha.Sum(nil)}
}
if n, err := sio.Decrypt(&unsealedKey, bytes.NewReader(sealedKey), sio.Config{Key: mac.Sum(nil)}); n != 32 || err != nil { if n, err := sio.Decrypt(&decryptedKey, bytes.NewReader(sealedKey.Key[:]), unsealConfig); n != 32 || err != nil {
return err // TODO(aead): upgrade sio to use sio.Error return err // TODO(aead): upgrade sio to use sio.Error
} }
copy(key[:], unsealedKey.Bytes()) copy(key[:], decryptedKey.Bytes())
return nil return nil
} }

@ -20,6 +20,8 @@ import (
"encoding/hex" "encoding/hex"
"io" "io"
"testing" "testing"
"github.com/minio/minio/cmd/logger"
) )
var shortRandom = func(limit int64) io.Reader { return io.LimitReader(rand.Reader, limit) } var shortRandom = func(limit int64) io.Reader { return io.LimitReader(rand.Reader, limit) }
@ -40,11 +42,15 @@ var generateKeyTests = []struct {
{ExtKey: [32]byte{}, Random: nil, ShouldPass: true}, // 0 {ExtKey: [32]byte{}, Random: nil, ShouldPass: true}, // 0
{ExtKey: [32]byte{}, Random: rand.Reader, ShouldPass: true}, // 1 {ExtKey: [32]byte{}, Random: rand.Reader, ShouldPass: true}, // 1
{ExtKey: [32]byte{}, Random: shortRandom(32), ShouldPass: true}, // 2 {ExtKey: [32]byte{}, Random: shortRandom(32), ShouldPass: true}, // 2
// {ExtKey: [32]byte{}, Random: shortRandom(31), ShouldPass: false}, // 3 See: https://github.com/minio/minio/issues/6064 {ExtKey: [32]byte{}, Random: shortRandom(31), ShouldPass: false}, // 3
} }
func TestGenerateKey(t *testing.T) { func TestGenerateKey(t *testing.T) {
defer func(disableLog bool) { logger.Disable = disableLog }(logger.Disable)
logger.Disable = true
for i, test := range generateKeyTests { for i, test := range generateKeyTests {
i, test := i, test
func() { func() {
defer recoverTest(i, test.ShouldPass, t) defer recoverTest(i, test.ShouldPass, t)
key := GenerateKey(test.ExtKey, test.Random) key := GenerateKey(test.ExtKey, test.Random)
@ -57,36 +63,36 @@ func TestGenerateKey(t *testing.T) {
var sealUnsealKeyTests = []struct { var sealUnsealKeyTests = []struct {
SealExtKey, SealIV [32]byte SealExtKey, SealIV [32]byte
SealBucket, SealObject string SealDomain, SealBucket, SealObject string
UnsealExtKey, UnsealIV [32]byte UnsealExtKey [32]byte
UnsealBucket, UnsealObject string UnsealDomain, UnsealBucket, UnsealObject string
ShouldPass bool ShouldPass bool
}{ }{
{ {
SealExtKey: [32]byte{}, SealIV: [32]byte{}, SealBucket: "bucket", SealObject: "object", SealExtKey: [32]byte{}, SealIV: [32]byte{}, SealDomain: "SSE-C", SealBucket: "bucket", SealObject: "object",
UnsealExtKey: [32]byte{}, UnsealIV: [32]byte{}, UnsealBucket: "bucket", UnsealObject: "object", UnsealExtKey: [32]byte{}, UnsealDomain: "SSE-C", UnsealBucket: "bucket", UnsealObject: "object",
ShouldPass: true, ShouldPass: true,
}, // 0 }, // 0
{ {
SealExtKey: [32]byte{}, SealIV: [32]byte{}, SealBucket: "bucket", SealObject: "object", SealExtKey: [32]byte{}, SealIV: [32]byte{}, SealDomain: "SSE-C", SealBucket: "bucket", SealObject: "object",
UnsealExtKey: [32]byte{1}, UnsealIV: [32]byte{0}, UnsealBucket: "bucket", UnsealObject: "object", UnsealExtKey: [32]byte{1}, UnsealDomain: "SSE-C", UnsealBucket: "bucket", UnsealObject: "object", // different ext-key
ShouldPass: false, ShouldPass: false,
}, // 1 }, // 1
{ {
SealExtKey: [32]byte{}, SealIV: [32]byte{}, SealBucket: "bucket", SealObject: "object", SealExtKey: [32]byte{}, SealIV: [32]byte{}, SealDomain: "SSE-S3", SealBucket: "bucket", SealObject: "object",
UnsealExtKey: [32]byte{}, UnsealIV: [32]byte{1}, UnsealBucket: "bucket", UnsealObject: "object", UnsealExtKey: [32]byte{}, UnsealDomain: "SSE-C", UnsealBucket: "bucket", UnsealObject: "object", // different domain
ShouldPass: false, ShouldPass: false,
}, // 2 }, // 2
{ {
SealExtKey: [32]byte{}, SealIV: [32]byte{}, SealBucket: "bucket", SealObject: "object", SealExtKey: [32]byte{}, SealIV: [32]byte{}, SealDomain: "SSE-C", SealBucket: "bucket", SealObject: "object",
UnsealExtKey: [32]byte{}, UnsealIV: [32]byte{}, UnsealBucket: "Bucket", UnsealObject: "object", UnsealExtKey: [32]byte{}, UnsealDomain: "SSE-C", UnsealBucket: "Bucket", UnsealObject: "object", // different bucket
ShouldPass: false, ShouldPass: false,
}, // 3 }, // 3
{ {
SealExtKey: [32]byte{}, SealIV: [32]byte{}, SealBucket: "bucket", SealObject: "object", SealExtKey: [32]byte{}, SealIV: [32]byte{}, SealDomain: "SSE-C", SealBucket: "bucket", SealObject: "object",
UnsealExtKey: [32]byte{}, UnsealIV: [32]byte{}, UnsealBucket: "bucket", UnsealObject: "Object", UnsealExtKey: [32]byte{}, UnsealDomain: "SSE-C", UnsealBucket: "bucket", UnsealObject: "Object", // different object
ShouldPass: false, ShouldPass: false,
}, // 4 }, // 4
} }
@ -94,13 +100,22 @@ var sealUnsealKeyTests = []struct {
func TestSealUnsealKey(t *testing.T) { func TestSealUnsealKey(t *testing.T) {
for i, test := range sealUnsealKeyTests { for i, test := range sealUnsealKeyTests {
key := GenerateKey(test.SealExtKey, rand.Reader) key := GenerateKey(test.SealExtKey, rand.Reader)
sealedKey := key.Seal(test.SealExtKey, test.SealIV, test.SealBucket, test.SealObject) sealedKey := key.Seal(test.SealExtKey, test.SealIV, test.SealDomain, test.SealBucket, test.SealObject)
if err := key.Unseal(sealedKey, test.UnsealExtKey, test.UnsealIV, test.UnsealBucket, test.UnsealObject); err == nil && !test.ShouldPass { if err := key.Unseal(test.UnsealExtKey, sealedKey, test.UnsealDomain, test.UnsealBucket, test.UnsealObject); err == nil && !test.ShouldPass {
t.Errorf("Test %d should fail but passed successfully", i) t.Errorf("Test %d should fail but passed successfully", i)
} else if err != nil && test.ShouldPass { } else if err != nil && test.ShouldPass {
t.Errorf("Test %d should pass put failed: %v", i, err) t.Errorf("Test %d should pass put failed: %v", i, err)
} }
} }
// Test legacy InsecureSealAlgorithm
var extKey, iv [32]byte
key := GenerateKey(extKey, rand.Reader)
sealedKey := key.Seal(extKey, iv, "SSE-S3", "bucket", "object")
sealedKey.Algorithm = InsecureSealAlgorithm
if err := key.Unseal(extKey, sealedKey, "SSE-S3", "bucket", "object"); err == nil {
t.Errorf("'%s' test succeeded but it should fail because the legacy algorithm was used", sealedKey.Algorithm)
}
} }
var derivePartKeyTest = []struct { var derivePartKeyTest = []struct {

@ -35,6 +35,18 @@ const (
S3KMSSealedKey = "X-Minio-Internal-Server-Side-Encryption-S3-Kms-Sealed-Key" S3KMSSealedKey = "X-Minio-Internal-Server-Side-Encryption-S3-Kms-Sealed-Key"
) )
const (
// SealAlgorithm is the encryption/sealing algorithm used to derive & seal
// the key-encryption-key and to en/decrypt the object data.
SealAlgorithm = "DAREv2-HMAC-SHA256"
// InsecureSealAlgorithm is the legacy encryption/sealing algorithm used
// to derive & seal the key-encryption-key and to en/decrypt the object data.
// This algorithm should not be used for new objects because its key derivation
// is not optimal. See: https://github.com/minio/minio/pull/6121
InsecureSealAlgorithm = "DARE-SHA256"
)
// EncryptSinglePart encrypts an io.Reader which must be the // EncryptSinglePart encrypts an io.Reader which must be the
// the body of a single-part PUT request. // the body of a single-part PUT request.
func EncryptSinglePart(r io.Reader, key ObjectKey) io.Reader { func EncryptSinglePart(r io.Reader, key ObjectKey) io.Reader {

Loading…
Cancel
Save