Compare commits
1 Commits
master
...
key-versio
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
01cb705c36 |
@@ -555,7 +555,7 @@ func encryptBucketMetadata(ctx context.Context, bucket string, input []byte, kms
|
|||||||
outbuf := bytes.NewBuffer(nil)
|
outbuf := bytes.NewBuffer(nil)
|
||||||
objectKey := crypto.GenerateKey(key.Plaintext, rand.Reader)
|
objectKey := crypto.GenerateKey(key.Plaintext, rand.Reader)
|
||||||
sealedKey := objectKey.Seal(key.Plaintext, crypto.GenerateIV(rand.Reader), crypto.S3.String(), bucket, "")
|
sealedKey := objectKey.Seal(key.Plaintext, crypto.GenerateIV(rand.Reader), crypto.S3.String(), bucket, "")
|
||||||
crypto.S3.CreateMetadata(metadata, key.KeyID, key.Ciphertext, sealedKey)
|
crypto.S3.CreateMetadata(metadata, key, sealedKey)
|
||||||
_, err = sio.Encrypt(outbuf, bytes.NewBuffer(input), sio.Config{Key: objectKey[:], MinVersion: sio.Version20, CipherSuites: fips.DARECiphers()})
|
_, err = sio.Encrypt(outbuf, bytes.NewBuffer(input), sio.Config{Key: objectKey[:], MinVersion: sio.Version20, CipherSuites: fips.DARECiphers()})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return output, metabytes, err
|
return output, metabytes, err
|
||||||
|
|||||||
@@ -293,7 +293,7 @@ func rotateKey(ctx context.Context, oldKey []byte, newKeyID string, newKey []byt
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
sealedKey = objectKey.Seal(newKey.Plaintext, crypto.GenerateIV(rand.Reader), crypto.S3.String(), bucket, object)
|
sealedKey = objectKey.Seal(newKey.Plaintext, crypto.GenerateIV(rand.Reader), crypto.S3.String(), bucket, object)
|
||||||
crypto.S3.CreateMetadata(metadata, newKey.KeyID, newKey.Ciphertext, sealedKey)
|
crypto.S3.CreateMetadata(metadata, newKey, sealedKey)
|
||||||
return nil
|
return nil
|
||||||
case crypto.S3KMS:
|
case crypto.S3KMS:
|
||||||
if GlobalKMS == nil {
|
if GlobalKMS == nil {
|
||||||
@@ -333,7 +333,7 @@ func rotateKey(ctx context.Context, oldKey []byte, newKeyID string, newKey []byt
|
|||||||
}
|
}
|
||||||
|
|
||||||
sealedKey := objectKey.Seal(newKey.Plaintext, crypto.GenerateIV(rand.Reader), crypto.S3KMS.String(), bucket, object)
|
sealedKey := objectKey.Seal(newKey.Plaintext, crypto.GenerateIV(rand.Reader), crypto.S3KMS.String(), bucket, object)
|
||||||
crypto.S3KMS.CreateMetadata(metadata, newKey.KeyID, newKey.Ciphertext, sealedKey, cryptoCtx)
|
crypto.S3KMS.CreateMetadata(metadata, newKey, sealedKey, cryptoCtx)
|
||||||
return nil
|
return nil
|
||||||
case crypto.SSEC:
|
case crypto.SSEC:
|
||||||
sealedKey, err := crypto.SSEC.ParseMetadata(metadata)
|
sealedKey, err := crypto.SSEC.ParseMetadata(metadata)
|
||||||
@@ -376,7 +376,7 @@ func newEncryptMetadata(ctx context.Context, kind crypto.Type, keyID string, key
|
|||||||
|
|
||||||
objectKey := crypto.GenerateKey(key.Plaintext, rand.Reader)
|
objectKey := crypto.GenerateKey(key.Plaintext, rand.Reader)
|
||||||
sealedKey = objectKey.Seal(key.Plaintext, crypto.GenerateIV(rand.Reader), crypto.S3.String(), bucket, object)
|
sealedKey = objectKey.Seal(key.Plaintext, crypto.GenerateIV(rand.Reader), crypto.S3.String(), bucket, object)
|
||||||
crypto.S3.CreateMetadata(metadata, key.KeyID, key.Ciphertext, sealedKey)
|
crypto.S3.CreateMetadata(metadata, key, sealedKey)
|
||||||
return objectKey, nil
|
return objectKey, nil
|
||||||
case crypto.S3KMS:
|
case crypto.S3KMS:
|
||||||
if GlobalKMS == nil {
|
if GlobalKMS == nil {
|
||||||
@@ -409,7 +409,7 @@ func newEncryptMetadata(ctx context.Context, kind crypto.Type, keyID string, key
|
|||||||
|
|
||||||
objectKey := crypto.GenerateKey(key.Plaintext, rand.Reader)
|
objectKey := crypto.GenerateKey(key.Plaintext, rand.Reader)
|
||||||
sealedKey = objectKey.Seal(key.Plaintext, crypto.GenerateIV(rand.Reader), crypto.S3KMS.String(), bucket, object)
|
sealedKey = objectKey.Seal(key.Plaintext, crypto.GenerateIV(rand.Reader), crypto.S3KMS.String(), bucket, object)
|
||||||
crypto.S3KMS.CreateMetadata(metadata, key.KeyID, key.Ciphertext, sealedKey, cryptoCtx)
|
crypto.S3KMS.CreateMetadata(metadata, key, sealedKey, cryptoCtx)
|
||||||
return objectKey, nil
|
return objectKey, nil
|
||||||
case crypto.SSEC:
|
case crypto.SSEC:
|
||||||
objectKey := crypto.GenerateKey(key, rand.Reader)
|
objectKey := crypto.GenerateKey(key, rand.Reader)
|
||||||
|
|||||||
@@ -280,6 +280,7 @@ func (a kmsAPIHandlers) KMSKeyStatusHandler(w http.ResponseWriter, r *http.Reque
|
|||||||
// 2. Verify that we can indeed decrypt the (encrypted) key
|
// 2. Verify that we can indeed decrypt the (encrypted) key
|
||||||
decryptedKey, err := GlobalKMS.Decrypt(ctx, &kms.DecryptRequest{
|
decryptedKey, err := GlobalKMS.Decrypt(ctx, &kms.DecryptRequest{
|
||||||
Name: key.KeyID,
|
Name: key.KeyID,
|
||||||
|
Version: key.Version,
|
||||||
Ciphertext: key.Ciphertext,
|
Ciphertext: key.Ciphertext,
|
||||||
AssociatedData: kmsContext,
|
AssociatedData: kmsContext,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -758,7 +758,7 @@ func (r *metacacheReader) Close() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// metacacheBlockWriter collects blocks and provides a callaback to store them.
|
// metacacheBlockWriter collects blocks and provides a callback to store them.
|
||||||
type metacacheBlockWriter struct {
|
type metacacheBlockWriter struct {
|
||||||
wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
streamErr error
|
streamErr error
|
||||||
|
|||||||
2
go.mod
2
go.mod
@@ -56,7 +56,7 @@ require (
|
|||||||
github.com/minio/dnscache v0.1.1
|
github.com/minio/dnscache v0.1.1
|
||||||
github.com/minio/dperf v0.6.3
|
github.com/minio/dperf v0.6.3
|
||||||
github.com/minio/highwayhash v1.0.3
|
github.com/minio/highwayhash v1.0.3
|
||||||
github.com/minio/kms-go/kes v0.3.1
|
github.com/minio/kms-go/kes v0.3.2-0.20250505160844-240efef6bb74
|
||||||
github.com/minio/kms-go/kms v0.5.1-0.20250225090116-4e64ce8d0f35
|
github.com/minio/kms-go/kms v0.5.1-0.20250225090116-4e64ce8d0f35
|
||||||
github.com/minio/madmin-go/v3 v3.0.109
|
github.com/minio/madmin-go/v3 v3.0.109
|
||||||
github.com/minio/minio-go/v7 v7.0.91
|
github.com/minio/minio-go/v7 v7.0.91
|
||||||
|
|||||||
4
go.sum
4
go.sum
@@ -436,8 +436,8 @@ github.com/minio/filepath v1.0.0/go.mod h1:/nRZA2ldl5z6jT9/KQuvZcQlxZIMQoFFQPvEX
|
|||||||
github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY=
|
github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY=
|
||||||
github.com/minio/highwayhash v1.0.3 h1:kbnuUMoHYyVl7szWjSxJnxw11k2U709jqFPPmIUyD6Q=
|
github.com/minio/highwayhash v1.0.3 h1:kbnuUMoHYyVl7szWjSxJnxw11k2U709jqFPPmIUyD6Q=
|
||||||
github.com/minio/highwayhash v1.0.3/go.mod h1:GGYsuwP/fPD6Y9hMiXuapVvlIUEhFhMTh0rxU3ik1LQ=
|
github.com/minio/highwayhash v1.0.3/go.mod h1:GGYsuwP/fPD6Y9hMiXuapVvlIUEhFhMTh0rxU3ik1LQ=
|
||||||
github.com/minio/kms-go/kes v0.3.1 h1:K3sPFAvFbJx33XlCTUBnQo8JRmSZyDvT6T2/MQ2iC3A=
|
github.com/minio/kms-go/kes v0.3.2-0.20250505160844-240efef6bb74 h1:iUY0/rQ66zmowoB94yGBTk3I2ljOoD5jSIKwkhxkwe4=
|
||||||
github.com/minio/kms-go/kes v0.3.1/go.mod h1:Q9Ct0KUAuN9dH0hSVa0eva45Jg99cahbZpPxeqR9rOQ=
|
github.com/minio/kms-go/kes v0.3.2-0.20250505160844-240efef6bb74/go.mod h1:Q9Ct0KUAuN9dH0hSVa0eva45Jg99cahbZpPxeqR9rOQ=
|
||||||
github.com/minio/kms-go/kms v0.5.1-0.20250225090116-4e64ce8d0f35 h1:ISNz42SPD+heeHhpl9bwMRRusPTCsbYKd1YoED265E0=
|
github.com/minio/kms-go/kms v0.5.1-0.20250225090116-4e64ce8d0f35 h1:ISNz42SPD+heeHhpl9bwMRRusPTCsbYKd1YoED265E0=
|
||||||
github.com/minio/kms-go/kms v0.5.1-0.20250225090116-4e64ce8d0f35/go.mod h1:JFQu2srrnWxMn6KcwS5347oTwNKW7nkewgBlrodjF9k=
|
github.com/minio/kms-go/kms v0.5.1-0.20250225090116-4e64ce8d0f35/go.mod h1:JFQu2srrnWxMn6KcwS5347oTwNKW7nkewgBlrodjF9k=
|
||||||
github.com/minio/madmin-go/v3 v3.0.109 h1:hRHlJ6yaIB3tlIj5mz9L9mGcyLC37S9qL1WtFrRtyQ0=
|
github.com/minio/madmin-go/v3 v3.0.109 h1:hRHlJ6yaIB3tlIj5mz9L9mGcyLC37S9qL1WtFrRtyQ0=
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ func Encrypt(k *kms.KMS, plaintext io.Reader, ctx kms.Context) (io.Reader, error
|
|||||||
json := jsoniter.ConfigCompatibleWithStandardLibrary
|
json := jsoniter.ConfigCompatibleWithStandardLibrary
|
||||||
metadata, err := json.Marshal(encryptedObject{
|
metadata, err := json.Marshal(encryptedObject{
|
||||||
KeyID: key.KeyID,
|
KeyID: key.KeyID,
|
||||||
|
Version: key.Version,
|
||||||
KMSKey: key.Ciphertext,
|
KMSKey: key.Ciphertext,
|
||||||
Algorithm: algorithm,
|
Algorithm: algorithm,
|
||||||
Nonce: nonce,
|
Nonce: nonce,
|
||||||
@@ -151,6 +152,7 @@ func Decrypt(k *kms.KMS, ciphertext io.Reader, associatedData kms.Context) (io.R
|
|||||||
|
|
||||||
key, err := k.Decrypt(context.TODO(), &kms.DecryptRequest{
|
key, err := k.Decrypt(context.TODO(), &kms.DecryptRequest{
|
||||||
Name: metadata.KeyID,
|
Name: metadata.KeyID,
|
||||||
|
Version: metadata.Version,
|
||||||
Ciphertext: metadata.KMSKey,
|
Ciphertext: metadata.KMSKey,
|
||||||
AssociatedData: associatedData,
|
AssociatedData: associatedData,
|
||||||
})
|
})
|
||||||
@@ -168,8 +170,9 @@ func Decrypt(k *kms.KMS, ciphertext io.Reader, associatedData kms.Context) (io.R
|
|||||||
}
|
}
|
||||||
|
|
||||||
type encryptedObject struct {
|
type encryptedObject struct {
|
||||||
KeyID string `json:"keyid"`
|
KeyID string `json:"keyid"`
|
||||||
KMSKey []byte `json:"kmskey"`
|
Version string `json:"version"`
|
||||||
|
KMSKey []byte `json:"kmskey"`
|
||||||
|
|
||||||
Algorithm sio.Algorithm `json:"algorithm"`
|
Algorithm sio.Algorithm `json:"algorithm"`
|
||||||
Nonce []byte `json:"nonce"`
|
Nonce []byte `json:"nonce"`
|
||||||
|
|||||||
@@ -48,6 +48,12 @@ const (
|
|||||||
// the KMS.
|
// the KMS.
|
||||||
MetaDataEncryptionKey = "X-Minio-Internal-Server-Side-Encryption-S3-Kms-Sealed-Key"
|
MetaDataEncryptionKey = "X-Minio-Internal-Server-Side-Encryption-S3-Kms-Sealed-Key"
|
||||||
|
|
||||||
|
// MetaKeyVersion is the version of the KMS master key used to generate/encrypt
|
||||||
|
// the data encryption key (DEK). When a MinKMS master key is "rotated", it
|
||||||
|
// adds another key version. MinIO has to remember which key version has been
|
||||||
|
// used to encrypt an object.
|
||||||
|
MetaKeyVersion = "X-Minio-Internal-Server-Side-Encryption-S3-Kms-Key-Version"
|
||||||
|
|
||||||
// MetaSsecCRC is the encrypted checksum of the SSE-C encrypted object.
|
// MetaSsecCRC is the encrypted checksum of the SSE-C encrypted object.
|
||||||
MetaSsecCRC = "X-Minio-Replication-Ssec-Crc"
|
MetaSsecCRC = "X-Minio-Replication-Ssec-Crc"
|
||||||
|
|
||||||
@@ -108,6 +114,7 @@ func RemoveInternalEntries(metadata map[string]string) {
|
|||||||
delete(metadata, MetaSealedKeyS3)
|
delete(metadata, MetaSealedKeyS3)
|
||||||
delete(metadata, MetaSealedKeyKMS)
|
delete(metadata, MetaSealedKeyKMS)
|
||||||
delete(metadata, MetaKeyID)
|
delete(metadata, MetaKeyID)
|
||||||
|
delete(metadata, MetaKeyVersion)
|
||||||
delete(metadata, MetaDataEncryptionKey)
|
delete(metadata, MetaDataEncryptionKey)
|
||||||
delete(metadata, MetaSsecCRC)
|
delete(metadata, MetaSsecCRC)
|
||||||
}
|
}
|
||||||
@@ -150,6 +157,9 @@ func IsEncrypted(metadata map[string]string) (Type, bool) {
|
|||||||
if _, ok := metadata[MetaKeyID]; ok {
|
if _, ok := metadata[MetaKeyID]; ok {
|
||||||
return nil, true
|
return nil, true
|
||||||
}
|
}
|
||||||
|
if _, ok := metadata[MetaKeyVersion]; ok {
|
||||||
|
return nil, true
|
||||||
|
}
|
||||||
if _, ok := metadata[MetaDataEncryptionKey]; ok {
|
if _, ok := metadata[MetaDataEncryptionKey]; ok {
|
||||||
return nil, true
|
return nil, true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import (
|
|||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/minio/minio/internal/kms"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -306,7 +307,7 @@ var s3CreateMetadataTests = []struct {
|
|||||||
SealedDataKey []byte
|
SealedDataKey []byte
|
||||||
SealedKey SealedKey
|
SealedKey SealedKey
|
||||||
}{
|
}{
|
||||||
{KeyID: "", SealedDataKey: nil, SealedKey: SealedKey{Algorithm: SealAlgorithm}},
|
{KeyID: "foo", SealedDataKey: make([]byte, 32), SealedKey: SealedKey{Algorithm: SealAlgorithm}},
|
||||||
{KeyID: "my-minio-key", SealedDataKey: make([]byte, 48), SealedKey: SealedKey{Algorithm: SealAlgorithm}},
|
{KeyID: "my-minio-key", SealedDataKey: make([]byte, 48), SealedKey: SealedKey{Algorithm: SealAlgorithm}},
|
||||||
{KeyID: "cafebabe", SealedDataKey: make([]byte, 48), SealedKey: SealedKey{Algorithm: SealAlgorithm}},
|
{KeyID: "cafebabe", SealedDataKey: make([]byte, 48), SealedKey: SealedKey{Algorithm: SealAlgorithm}},
|
||||||
{KeyID: "deadbeef", SealedDataKey: make([]byte, 32), SealedKey: SealedKey{IV: [32]byte{0xf7}, Key: [64]byte{0xea}, Algorithm: SealAlgorithm}},
|
{KeyID: "deadbeef", SealedDataKey: make([]byte, 32), SealedKey: SealedKey{IV: [32]byte{0xf7}, Key: [64]byte{0xea}, Algorithm: SealAlgorithm}},
|
||||||
@@ -316,7 +317,7 @@ func TestS3CreateMetadata(t *testing.T) {
|
|||||||
defer func(l bool) { logger.DisableLog = l }(logger.DisableLog)
|
defer func(l bool) { logger.DisableLog = l }(logger.DisableLog)
|
||||||
logger.DisableLog = true
|
logger.DisableLog = true
|
||||||
for i, test := range s3CreateMetadataTests {
|
for i, test := range s3CreateMetadataTests {
|
||||||
metadata := S3.CreateMetadata(nil, test.KeyID, test.SealedDataKey, test.SealedKey)
|
metadata := S3.CreateMetadata(nil, kms.DEK{KeyID: test.KeyID, Ciphertext: test.SealedDataKey}, test.SealedKey)
|
||||||
keyID, kmsKey, sealedKey, err := S3.ParseMetadata(metadata)
|
keyID, kmsKey, sealedKey, err := S3.ParseMetadata(metadata)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Test %d: failed to parse metadata: %v", i, err)
|
t.Errorf("Test %d: failed to parse metadata: %v", i, err)
|
||||||
@@ -344,7 +345,7 @@ func TestS3CreateMetadata(t *testing.T) {
|
|||||||
t.Errorf("Expected '%s' panic for invalid seal algorithm but got '%s'", logger.ErrCritical, err)
|
t.Errorf("Expected '%s' panic for invalid seal algorithm but got '%s'", logger.ErrCritical, err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
_ = S3.CreateMetadata(nil, "", []byte{}, SealedKey{Algorithm: InsecureSealAlgorithm})
|
_ = S3.CreateMetadata(nil, kms.DEK{}, SealedKey{Algorithm: InsecureSealAlgorithm})
|
||||||
}
|
}
|
||||||
|
|
||||||
var ssecCreateMetadataTests = []struct {
|
var ssecCreateMetadataTests = []struct {
|
||||||
|
|||||||
@@ -136,20 +136,15 @@ func (s3 ssekms) UnsealObjectKey(k *kms.KMS, metadata map[string]string, bucket,
|
|||||||
// the modified metadata. If the keyID and the kmsKey is not empty it encodes
|
// the modified metadata. If the keyID and the kmsKey is not empty it encodes
|
||||||
// both into the metadata as well. It allocates a new metadata map if metadata
|
// both into the metadata as well. It allocates a new metadata map if metadata
|
||||||
// is nil.
|
// is nil.
|
||||||
func (ssekms) CreateMetadata(metadata map[string]string, keyID string, kmsKey []byte, sealedKey SealedKey, ctx kms.Context) map[string]string {
|
func (ssekms) CreateMetadata(metadata map[string]string, dek kms.DEK, sealedKey SealedKey, ctx kms.Context) map[string]string {
|
||||||
if sealedKey.Algorithm != SealAlgorithm {
|
if sealedKey.Algorithm != SealAlgorithm {
|
||||||
logger.CriticalIf(context.Background(), Errorf("The seal algorithm '%s' is invalid for SSE-S3", sealedKey.Algorithm))
|
logger.CriticalIf(context.Background(), Errorf("The seal algorithm '%s' is invalid for SSE-S3", sealedKey.Algorithm))
|
||||||
}
|
}
|
||||||
|
if dek.KeyID == "" {
|
||||||
// There are two possibilities:
|
logger.CriticalIf(context.Background(), errors.New("The key ID must not be empty"))
|
||||||
// - We use a KMS -> There must be non-empty key ID and a KMS data key.
|
|
||||||
// - We use a K/V -> There must be no key ID and no KMS data key.
|
|
||||||
// Otherwise, the caller has passed an invalid argument combination.
|
|
||||||
if keyID == "" && len(kmsKey) != 0 {
|
|
||||||
logger.CriticalIf(context.Background(), errors.New("The key ID must not be empty if a KMS data key is present"))
|
|
||||||
}
|
}
|
||||||
if keyID != "" && len(kmsKey) == 0 {
|
if len(dek.Ciphertext) == 0 {
|
||||||
logger.CriticalIf(context.Background(), errors.New("The KMS data key must not be empty if a key ID is present"))
|
logger.CriticalIf(context.Background(), errors.New("The DEK must not be empty"))
|
||||||
}
|
}
|
||||||
|
|
||||||
if metadata == nil {
|
if metadata == nil {
|
||||||
@@ -159,13 +154,18 @@ func (ssekms) CreateMetadata(metadata map[string]string, keyID string, kmsKey []
|
|||||||
metadata[MetaAlgorithm] = sealedKey.Algorithm
|
metadata[MetaAlgorithm] = sealedKey.Algorithm
|
||||||
metadata[MetaIV] = base64.StdEncoding.EncodeToString(sealedKey.IV[:])
|
metadata[MetaIV] = base64.StdEncoding.EncodeToString(sealedKey.IV[:])
|
||||||
metadata[MetaSealedKeyKMS] = base64.StdEncoding.EncodeToString(sealedKey.Key[:])
|
metadata[MetaSealedKeyKMS] = base64.StdEncoding.EncodeToString(sealedKey.Key[:])
|
||||||
|
metadata[MetaKeyID] = dek.KeyID
|
||||||
|
metadata[MetaDataEncryptionKey] = base64.StdEncoding.EncodeToString(dek.Ciphertext)
|
||||||
|
|
||||||
if len(ctx) > 0 {
|
if len(ctx) > 0 {
|
||||||
b, _ := ctx.MarshalText()
|
b, err := ctx.MarshalText()
|
||||||
|
if err != nil {
|
||||||
|
logger.CriticalIf(context.Background(), Errorf("crypto: failed to marshal KMS context: %v", err))
|
||||||
|
}
|
||||||
metadata[MetaContext] = base64.StdEncoding.EncodeToString(b)
|
metadata[MetaContext] = base64.StdEncoding.EncodeToString(b)
|
||||||
}
|
}
|
||||||
if len(kmsKey) > 0 && keyID != "" { // We use a KMS -> Store key ID and sealed KMS data key.
|
if dek.Version != "" {
|
||||||
metadata[MetaKeyID] = keyID
|
metadata[MetaKeyVersion] = dek.Version
|
||||||
metadata[MetaDataEncryptionKey] = base64.StdEncoding.EncodeToString(kmsKey)
|
|
||||||
}
|
}
|
||||||
return metadata
|
return metadata
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -119,20 +119,15 @@ func (s3 sses3) UnsealObjectKeys(ctx context.Context, k *kms.KMS, metadata []map
|
|||||||
// the modified metadata. If the keyID and the kmsKey is not empty it encodes
|
// the modified metadata. If the keyID and the kmsKey is not empty it encodes
|
||||||
// both into the metadata as well. It allocates a new metadata map if metadata
|
// both into the metadata as well. It allocates a new metadata map if metadata
|
||||||
// is nil.
|
// is nil.
|
||||||
func (sses3) CreateMetadata(metadata map[string]string, keyID string, kmsKey []byte, sealedKey SealedKey) map[string]string {
|
func (sses3) CreateMetadata(metadata map[string]string, dek kms.DEK, sealedKey SealedKey) map[string]string {
|
||||||
if sealedKey.Algorithm != SealAlgorithm {
|
if sealedKey.Algorithm != SealAlgorithm {
|
||||||
logger.CriticalIf(context.Background(), Errorf("The seal algorithm '%s' is invalid for SSE-S3", sealedKey.Algorithm))
|
logger.CriticalIf(context.Background(), Errorf("The seal algorithm '%s' is invalid for SSE-S3", sealedKey.Algorithm))
|
||||||
}
|
}
|
||||||
|
if dek.KeyID == "" {
|
||||||
// There are two possibilities:
|
logger.CriticalIf(context.Background(), errors.New("The key ID must not be empty"))
|
||||||
// - We use a KMS -> There must be non-empty key ID and a KMS data key.
|
|
||||||
// - We use a K/V -> There must be no key ID and no KMS data key.
|
|
||||||
// Otherwise, the caller has passed an invalid argument combination.
|
|
||||||
if keyID == "" && len(kmsKey) != 0 {
|
|
||||||
logger.CriticalIf(context.Background(), errors.New("The key ID must not be empty if a KMS data key is present"))
|
|
||||||
}
|
}
|
||||||
if keyID != "" && len(kmsKey) == 0 {
|
if len(dek.Ciphertext) == 0 {
|
||||||
logger.CriticalIf(context.Background(), errors.New("The KMS data key must not be empty if a key ID is present"))
|
logger.CriticalIf(context.Background(), errors.New("The DEK must not be empty"))
|
||||||
}
|
}
|
||||||
|
|
||||||
if metadata == nil {
|
if metadata == nil {
|
||||||
@@ -142,9 +137,10 @@ func (sses3) CreateMetadata(metadata map[string]string, keyID string, kmsKey []b
|
|||||||
metadata[MetaAlgorithm] = sealedKey.Algorithm
|
metadata[MetaAlgorithm] = sealedKey.Algorithm
|
||||||
metadata[MetaIV] = base64.StdEncoding.EncodeToString(sealedKey.IV[:])
|
metadata[MetaIV] = base64.StdEncoding.EncodeToString(sealedKey.IV[:])
|
||||||
metadata[MetaSealedKeyS3] = base64.StdEncoding.EncodeToString(sealedKey.Key[:])
|
metadata[MetaSealedKeyS3] = base64.StdEncoding.EncodeToString(sealedKey.Key[:])
|
||||||
if len(kmsKey) > 0 && keyID != "" { // We use a KMS -> Store key ID and sealed KMS data key.
|
metadata[MetaKeyID] = dek.KeyID
|
||||||
metadata[MetaKeyID] = keyID
|
metadata[MetaDataEncryptionKey] = base64.StdEncoding.EncodeToString(dek.Ciphertext)
|
||||||
metadata[MetaDataEncryptionKey] = base64.StdEncoding.EncodeToString(kmsKey)
|
if dek.Version != "" {
|
||||||
|
metadata[MetaKeyVersion] = dek.Version
|
||||||
}
|
}
|
||||||
return metadata
|
return metadata
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,11 +19,8 @@ package kms
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding"
|
|
||||||
"encoding/json"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
jsoniter "github.com/json-iterator/go"
|
|
||||||
"github.com/minio/madmin-go/v3"
|
"github.com/minio/madmin-go/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -121,47 +118,7 @@ type Status struct {
|
|||||||
// storage.
|
// storage.
|
||||||
type DEK struct {
|
type DEK struct {
|
||||||
KeyID string // Name of the master key
|
KeyID string // Name of the master key
|
||||||
Version int // Version of the master key (MinKMS only)
|
Version string // Version of the master key
|
||||||
Plaintext []byte // Paintext of the data encryption key
|
Plaintext []byte // Paintext of the data encryption key
|
||||||
Ciphertext []byte // Ciphertext of the data encryption key
|
Ciphertext []byte // Ciphertext of the data encryption key
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
|
||||||
_ encoding.TextMarshaler = (*DEK)(nil)
|
|
||||||
_ encoding.TextUnmarshaler = (*DEK)(nil)
|
|
||||||
)
|
|
||||||
|
|
||||||
// MarshalText encodes the DEK's key ID and ciphertext
|
|
||||||
// as JSON.
|
|
||||||
func (d DEK) MarshalText() ([]byte, error) {
|
|
||||||
type JSON struct {
|
|
||||||
KeyID string `json:"keyid"`
|
|
||||||
Version uint32 `json:"version,omitempty"`
|
|
||||||
Ciphertext []byte `json:"ciphertext"`
|
|
||||||
}
|
|
||||||
return json.Marshal(JSON{
|
|
||||||
KeyID: d.KeyID,
|
|
||||||
Version: uint32(d.Version),
|
|
||||||
Ciphertext: d.Ciphertext,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalText tries to decode text as JSON representation
|
|
||||||
// of a DEK and sets DEK's key ID and ciphertext to the
|
|
||||||
// decoded values.
|
|
||||||
//
|
|
||||||
// It sets DEK's plaintext to nil.
|
|
||||||
func (d *DEK) UnmarshalText(text []byte) error {
|
|
||||||
type JSON struct {
|
|
||||||
KeyID string `json:"keyid"`
|
|
||||||
Version uint32 `json:"version"`
|
|
||||||
Ciphertext []byte `json:"ciphertext"`
|
|
||||||
}
|
|
||||||
var v JSON
|
|
||||||
json := jsoniter.ConfigCompatibleWithStandardLibrary
|
|
||||||
if err := json.Unmarshal(text, &v); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
d.KeyID, d.Version, d.Plaintext, d.Ciphertext = v.KeyID, int(v.Version), nil, v.Ciphertext
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,79 +0,0 @@
|
|||||||
// Copyright (c) 2015-2021 MinIO, Inc.
|
|
||||||
//
|
|
||||||
// This file is part of MinIO Object Storage stack
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package kms
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/base64"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
var dekEncodeDecodeTests = []struct {
|
|
||||||
Key DEK
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
Key: DEK{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: DEK{
|
|
||||||
Plaintext: nil,
|
|
||||||
Ciphertext: mustDecodeB64("eyJhZWFkIjoiQUVTLTI1Ni1HQ00tSE1BQy1TSEEtMjU2IiwiaXYiOiJ3NmhLUFVNZXVtejZ5UlVZL29pTFVBPT0iLCJub25jZSI6IktMSEU3UE1jRGo2N2UweHkiLCJieXRlcyI6Ik1wUkhjQWJaTzZ1Sm5lUGJGcnpKTkxZOG9pdkxwTmlUcTNLZ0hWdWNGYkR2Y0RlbEh1c1lYT29zblJWVTZoSXIifQ=="),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: DEK{
|
|
||||||
Plaintext: mustDecodeB64("GM2UvLXp/X8lzqq0mibFC0LayDCGlmTHQhYLj7qAy7Q="),
|
|
||||||
Ciphertext: mustDecodeB64("eyJhZWFkIjoiQUVTLTI1Ni1HQ00tSE1BQy1TSEEtMjU2IiwiaXYiOiJ3NmhLUFVNZXVtejZ5UlVZL29pTFVBPT0iLCJub25jZSI6IktMSEU3UE1jRGo2N2UweHkiLCJieXRlcyI6Ik1wUkhjQWJaTzZ1Sm5lUGJGcnpKTkxZOG9pdkxwTmlUcTNLZ0hWdWNGYkR2Y0RlbEh1c1lYT29zblJWVTZoSXIifQ=="),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: DEK{
|
|
||||||
Version: 3,
|
|
||||||
Plaintext: mustDecodeB64("GM2UvLXp/X8lzqq0mibFC0LayDCGlmTHQhYLj7qAy7Q="),
|
|
||||||
Ciphertext: mustDecodeB64("eyJhZWFkIjoiQUVTLTI1Ni1HQ00tSE1BQy1TSEEtMjU2IiwiaXYiOiJ3NmhLUFVNZXVtejZ5UlVZL29pTFVBPT0iLCJub25jZSI6IktMSEU3UE1jRGo2N2UweHkiLCJieXRlcyI6Ik1wUkhjQWJaTzZ1Sm5lUGJGcnpKTkxZOG9pdkxwTmlUcTNLZ0hWdWNGYkR2Y0RlbEh1c1lYT29zblJWVTZoSXIifQ=="),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEncodeDecodeDEK(t *testing.T) {
|
|
||||||
for i, test := range dekEncodeDecodeTests {
|
|
||||||
text, err := test.Key.MarshalText()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Test %d: failed to marshal DEK: %v", i, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var key DEK
|
|
||||||
if err = key.UnmarshalText(text); err != nil {
|
|
||||||
t.Fatalf("Test %d: failed to unmarshal DEK: %v", i, err)
|
|
||||||
}
|
|
||||||
if key.Plaintext != nil {
|
|
||||||
t.Fatalf("Test %d: unmarshaled DEK contains non-nil plaintext", i)
|
|
||||||
}
|
|
||||||
if !bytes.Equal(key.Ciphertext, test.Key.Ciphertext) {
|
|
||||||
t.Fatalf("Test %d: ciphertext mismatch: got %x - want %x", i, key.Ciphertext, test.Key.Ciphertext)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func mustDecodeB64(s string) []byte {
|
|
||||||
b, err := base64.StdEncoding.DecodeString(s)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
@@ -206,6 +206,7 @@ func (c *kesConn) GenerateKey(ctx context.Context, req *GenerateKeyRequest) (DEK
|
|||||||
KeyID: name,
|
KeyID: name,
|
||||||
Plaintext: dek.Plaintext,
|
Plaintext: dek.Plaintext,
|
||||||
Ciphertext: dek.Ciphertext,
|
Ciphertext: dek.Ciphertext,
|
||||||
|
Version: dek.Version,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,7 +236,7 @@ func (c *kesConn) Decrypt(ctx context.Context, req *DecryptRequest) ([]byte, err
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
plaintext, err := c.client.Decrypt(context.Background(), req.Name, req.Ciphertext, aad)
|
plaintext, err := c.client.Decrypt(ctx, req.Name, req.Version, req.Ciphertext, aad)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, kes.ErrKeyNotFound) {
|
if errors.Is(err, kes.ErrKeyNotFound) {
|
||||||
return nil, ErrKeyNotFound
|
return nil, ErrKeyNotFound
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
"slices"
|
"slices"
|
||||||
|
"strconv"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -90,7 +91,7 @@ type DecryptRequest struct {
|
|||||||
// Version is the version of the master used for
|
// Version is the version of the master used for
|
||||||
// decryption. If empty, the latest key version
|
// decryption. If empty, the latest key version
|
||||||
// is used.
|
// is used.
|
||||||
Version int
|
Version string
|
||||||
|
|
||||||
// Ciphertext is the encrypted data that gets
|
// Ciphertext is the encrypted data that gets
|
||||||
// decrypted.
|
// decrypted.
|
||||||
@@ -383,7 +384,7 @@ func (c *kmsConn) GenerateKey(ctx context.Context, req *GenerateKeyRequest) (DEK
|
|||||||
|
|
||||||
return DEK{
|
return DEK{
|
||||||
KeyID: name,
|
KeyID: name,
|
||||||
Version: resp[0].Version,
|
Version: strconv.Itoa(resp[0].Version),
|
||||||
Plaintext: resp[0].Plaintext,
|
Plaintext: resp[0].Plaintext,
|
||||||
Ciphertext: resp[0].Ciphertext,
|
Ciphertext: resp[0].Ciphertext,
|
||||||
}, nil
|
}, nil
|
||||||
@@ -395,9 +396,17 @@ func (c *kmsConn) Decrypt(ctx context.Context, req *DecryptRequest) ([]byte, err
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
version := 1
|
||||||
|
if req.Version != "" {
|
||||||
|
if version, err = strconv.Atoi(req.Version); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ciphertext, _ := parseCiphertext(req.Ciphertext)
|
ciphertext, _ := parseCiphertext(req.Ciphertext)
|
||||||
resp, err := c.client.Decrypt(ctx, c.enclave, &kms.DecryptRequest{
|
resp, err := c.client.Decrypt(ctx, c.enclave, &kms.DecryptRequest{
|
||||||
Name: req.Name,
|
Name: req.Name,
|
||||||
|
Version: version,
|
||||||
Ciphertext: ciphertext,
|
Ciphertext: ciphertext,
|
||||||
AssociatedData: aad,
|
AssociatedData: aad,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -154,7 +154,6 @@ func (s secretKey) GenerateKey(_ context.Context, req *GenerateKeyRequest) (DEK,
|
|||||||
ciphertext = append(ciphertext, random...)
|
ciphertext = append(ciphertext, random...)
|
||||||
return DEK{
|
return DEK{
|
||||||
KeyID: req.Name,
|
KeyID: req.Name,
|
||||||
Version: 0,
|
|
||||||
Plaintext: plaintext,
|
Plaintext: plaintext,
|
||||||
Ciphertext: ciphertext,
|
Ciphertext: ciphertext,
|
||||||
}, nil
|
}, nil
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ func (s StubKMS) GenerateKey(_ context.Context, req *GenerateKeyRequest) (DEK, e
|
|||||||
}
|
}
|
||||||
return DEK{
|
return DEK{
|
||||||
KeyID: req.Name,
|
KeyID: req.Name,
|
||||||
Version: 0,
|
Version: "0",
|
||||||
Plaintext: []byte("stubplaincharswhichare32bytelong"),
|
Plaintext: []byte("stubplaincharswhichare32bytelong"),
|
||||||
Ciphertext: []byte("stubplaincharswhichare32bytelong"),
|
Ciphertext: []byte("stubplaincharswhichare32bytelong"),
|
||||||
}, nil
|
}, nil
|
||||||
|
|||||||
Reference in New Issue
Block a user