Add support for SSE-S3 server side encryption with vault (#6192)

Add support for sse-s3 encryption with vault as KMS.

Also refactoring code to make use of headers and functions defined in
crypto package and clean up duplicated code.
This commit is contained in:
poornas
2018-08-17 12:52:14 -07:00
committed by kannappanr
parent 3d197c1449
commit e71ef905f9
236 changed files with 23463 additions and 608 deletions

View File

@@ -17,13 +17,10 @@
package cmd
import (
"bytes"
"context"
"crypto/hmac"
"crypto/md5"
"crypto/rand"
"crypto/subtle"
"encoding/base64"
"encoding/binary"
"errors"
"io"
@@ -31,6 +28,7 @@ import (
"path"
"strconv"
"github.com/minio/minio/cmd/crypto"
"github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/ioutil"
sha256 "github.com/minio/sha256-simd"
@@ -41,34 +39,12 @@ var (
// AWS errors for invalid SSE-C requests.
errInsecureSSERequest = errors.New("SSE-C requests require TLS connections")
errEncryptedObject = errors.New("The object was stored using a form of SSE")
errInvalidSSEAlgorithm = errors.New("The SSE-C algorithm is not valid")
errMissingSSEKey = errors.New("The SSE-C request is missing the customer key")
errInvalidSSEKey = errors.New("The SSE-C key is invalid")
errMissingSSEKeyMD5 = errors.New("The SSE-C request is missing the customer key MD5")
errSSEKeyMD5Mismatch = errors.New("The key MD5 does not match the SSE-C key")
errSSEKeyMismatch = errors.New("The SSE-C key is not correct") // access denied
errInvalidSSEParameters = errors.New("The SSE-C key for key-rotation is not correct") // special access denied
errKMSNotConfigured = errors.New("KMS not configured for a server side encrypted object")
// Additional Minio errors for SSE-C requests.
errObjectTampered = errors.New("The requested object was modified and may be compromised")
)
const (
// SSECustomerAlgorithm is the AWS SSE-C algorithm HTTP header key.
SSECustomerAlgorithm = "X-Amz-Server-Side-Encryption-Customer-Algorithm"
// SSECustomerKey is the AWS SSE-C encryption key HTTP header key.
SSECustomerKey = "X-Amz-Server-Side-Encryption-Customer-Key"
// SSECustomerKeyMD5 is the AWS SSE-C encryption key MD5 HTTP header key.
SSECustomerKeyMD5 = "X-Amz-Server-Side-Encryption-Customer-Key-MD5"
// SSECopyCustomerAlgorithm is the AWS SSE-C algorithm HTTP header key for CopyObject API.
SSECopyCustomerAlgorithm = "X-Amz-Copy-Source-Server-Side-Encryption-Customer-Algorithm"
// SSECopyCustomerKey is the AWS SSE-C encryption key HTTP header key for CopyObject API.
SSECopyCustomerKey = "X-Amz-Copy-Source-Server-Side-Encryption-Customer-Key"
// SSECopyCustomerKeyMD5 is the AWS SSE-C encryption key MD5 HTTP header key for CopyObject API.
SSECopyCustomerKeyMD5 = "X-Amz-Copy-Source-Server-Side-Encryption-Customer-Key-MD5"
)
const (
// SSECustomerKeySize is the size of valid client provided encryption keys in bytes.
// Currently AWS supports only AES256. So the SSE-C key size is fixed to 32 bytes.
@@ -77,9 +53,6 @@ const (
// SSEIVSize is the size of the IV data
SSEIVSize = 32 // 32 bytes
// SSECustomerAlgorithmAES256 the only valid S3 SSE-C encryption algorithm identifier.
SSECustomerAlgorithmAES256 = "AES256"
// SSE dare package block size.
sseDAREPackageBlockSize = 64 * 1024 // 64KiB bytes
@@ -88,63 +61,6 @@ const (
)
// SSE-C key derivation, key verification and key update:
// H: Hash function [32 = |H(m)|]
// AE: authenticated encryption scheme, AD: authenticated decryption scheme [m = AD(k, AE(k, m))]
//
// Key derivation:
// Input:
// key := 32 bytes # client provided key
// Re, Rm := 32 bytes, 32 bytes # uniformly random
//
// Seal:
// k := H(key || Re) # object encryption key
// r := H(Rm) # save as object metadata [ServerSideEncryptionIV]
// KeK := H(key || r) # key encryption key
// K := AE(KeK, k) # save as object metadata [ServerSideEncryptionSealedKey]
// ------------------------------------------------------------------------------------------------
// Key verification:
// Input:
// key := 32 bytes # client provided key
// r := 32 bytes # object metadata [ServerSideEncryptionIV]
// K := 32 bytes # object metadata [ServerSideEncryptionSealedKey]
//
// Open:
// KeK := H(key || r) # key encryption key
// k := AD(Kek, K) # object encryption key
// -------------------------------------------------------------------------------------------------
// Key update:
// Input:
// key := 32 bytes # old client provided key
// key' := 32 bytes # new client provided key
// Rm := 32 bytes # uniformly random
// r := 32 bytes # object metadata [ServerSideEncryptionIV]
// K := 32 bytes # object metadata [ServerSideEncryptionSealedKey]
//
// Update:
// 1. open:
// KeK := H(key || r) # key encryption key
// k := AD(Kek, K) # object encryption key
// 2. seal:
// r' := H(Rm) # save as object metadata [ServerSideEncryptionIV]
// KeK' := H(key' || r') # new key encryption key
// K' := AE(KeK', k) # save as object metadata [ServerSideEncryptionSealedKey]
const (
// ServerSideEncryptionIV is a 32 byte randomly generated IV used to derive an
// unique key encryption key from the client provided key. The combination of this value
// and the client-provided key MUST be unique.
ServerSideEncryptionIV = ReservedMetadataPrefix + "Server-Side-Encryption-Iv"
// ServerSideEncryptionSealAlgorithm identifies a combination of a cryptographic hash function and
// an authenticated en/decryption scheme to seal the object encryption key.
ServerSideEncryptionSealAlgorithm = ReservedMetadataPrefix + "Server-Side-Encryption-Seal-Algorithm"
// ServerSideEncryptionSealedKey is the sealed object encryption key. The sealed key can be decrypted
// by the key encryption key derived from the client provided key and the server-side-encryption IV.
ServerSideEncryptionSealedKey = ReservedMetadataPrefix + "Server-Side-Encryption-Sealed-Key"
)
const (
// SSESealAlgorithmDareSha256 specifies DARE as authenticated en/decryption scheme and SHA256 as cryptographic
// hash function. The key derivation of DARE-SHA256 is not optimal and does not include the object path.
@@ -154,27 +70,17 @@ const (
// SSESealAlgorithmDareV2HmacSha256 specifies DAREv2 as authenticated en/decryption scheme and SHA256 as cryptographic
// hash function for the HMAC PRF.
SSESealAlgorithmDareV2HmacSha256 = "DAREv2-HMAC-SHA256"
// SSEDomain specifies the domain for the derived key - in this case the
// key should be used for SSE-C.
SSEDomain = "SSE-C"
)
// hasSSECustomerHeader returns true if the given HTTP header
// contains server-side-encryption with customer provided key fields.
func hasSSECustomerHeader(header http.Header) bool {
return header.Get(SSECustomerAlgorithm) != "" || header.Get(SSECustomerKey) != "" || header.Get(SSECustomerKeyMD5) != ""
}
// hasSSECopyCustomerHeader returns true if the given HTTP header
// contains copy source server-side-encryption with customer provided key fields.
func hasSSECopyCustomerHeader(header http.Header) bool {
return header.Get(SSECopyCustomerAlgorithm) != "" || header.Get(SSECopyCustomerKey) != "" || header.Get(SSECopyCustomerKeyMD5) != ""
// hasServerSideEncryptionHeader returns true if the given HTTP header
// contains server-side-encryption.
func hasServerSideEncryptionHeader(header http.Header) bool {
return crypto.S3.IsRequested(header) || crypto.SSEC.IsRequested(header)
}
// ParseSSECopyCustomerRequest parses the SSE-C header fields of the provided request.
// It returns the client provided key on success.
func ParseSSECopyCustomerRequest(r *http.Request) (key []byte, err error) {
func ParseSSECopyCustomerRequest(r *http.Request, metadata map[string]string) (key []byte, err error) {
if !globalIsSSL { // minio only supports HTTP or HTTPS requests not both at the same time
// we cannot use r.TLS == nil here because Go's http implementation reflects on
// the net.Conn and sets the TLS field of http.Request only if it's an tls.Conn.
@@ -182,36 +88,11 @@ func ParseSSECopyCustomerRequest(r *http.Request) (key []byte, err error) {
// will always fail -> r.TLS is always nil even for TLS requests.
return nil, errInsecureSSERequest
}
header := r.Header
if algorithm := header.Get(SSECopyCustomerAlgorithm); algorithm != SSECustomerAlgorithmAES256 {
return nil, errInvalidSSEAlgorithm
if crypto.S3.IsEncrypted(metadata) && crypto.SSECopy.IsRequested(r.Header) {
return nil, crypto.ErrIncompatibleEncryptionMethod
}
if header.Get(SSECopyCustomerKey) == "" {
return nil, errMissingSSEKey
}
if header.Get(SSECopyCustomerKeyMD5) == "" {
return nil, errMissingSSEKeyMD5
}
key, err = base64.StdEncoding.DecodeString(header.Get(SSECopyCustomerKey))
if err != nil {
return nil, errInvalidSSEKey
}
if len(key) != SSECustomerKeySize {
return nil, errInvalidSSEKey
}
// Make sure we purged the keys from http headers by now.
header.Del(SSECopyCustomerKey)
keyMD5, err := base64.StdEncoding.DecodeString(header.Get(SSECopyCustomerKeyMD5))
if err != nil {
return nil, errSSEKeyMD5Mismatch
}
if md5Sum := md5.Sum(key); !bytes.Equal(md5Sum[:], keyMD5) {
return nil, errSSEKeyMD5Mismatch
}
return key, nil
k, err := crypto.SSECopy.ParseHTTP(r.Header)
return k[:], err
}
// ParseSSECustomerRequest parses the SSE-C header fields of the provided request.
@@ -230,228 +111,176 @@ func ParseSSECustomerHeader(header http.Header) (key []byte, err error) {
// will always fail -> r.TLS is always nil even for TLS requests.
return nil, errInsecureSSERequest
}
if algorithm := header.Get(SSECustomerAlgorithm); algorithm != SSECustomerAlgorithmAES256 {
return nil, errInvalidSSEAlgorithm
}
if header.Get(SSECustomerKey) == "" {
return nil, errMissingSSEKey
}
if header.Get(SSECustomerKeyMD5) == "" {
return nil, errMissingSSEKeyMD5
if crypto.S3.IsRequested(header) && crypto.SSEC.IsRequested(header) {
return key, crypto.ErrIncompatibleEncryptionMethod
}
key, err = base64.StdEncoding.DecodeString(header.Get(SSECustomerKey))
if err != nil {
return nil, errInvalidSSEKey
}
if len(key) != SSECustomerKeySize {
return nil, errInvalidSSEKey
}
// Make sure we purged the keys from http headers by now.
header.Del(SSECustomerKey)
keyMD5, err := base64.StdEncoding.DecodeString(header.Get(SSECustomerKeyMD5))
if err != nil {
return nil, errSSEKeyMD5Mismatch
}
if md5Sum := md5.Sum(key); !bytes.Equal(md5Sum[:], keyMD5) {
return nil, errSSEKeyMD5Mismatch
}
return key, nil
k, err := crypto.SSEC.ParseHTTP(header)
return k[:], err
}
// This function rotates old to new key.
func rotateKey(oldKey []byte, newKey []byte, bucket, object string, metadata map[string]string) error {
delete(metadata, SSECustomerKey) // make sure we do not save the key by accident
delete(metadata, crypto.SSECKey) // make sure we do not save the key by accident
algorithm := metadata[ServerSideEncryptionSealAlgorithm]
if algorithm != SSESealAlgorithmDareSha256 && algorithm != SSESealAlgorithmDareV2HmacSha256 {
return errObjectTampered
}
iv, err := base64.StdEncoding.DecodeString(metadata[ServerSideEncryptionIV])
if err != nil || len(iv) != SSEIVSize {
return errObjectTampered
}
sealedKey, err := base64.StdEncoding.DecodeString(metadata[ServerSideEncryptionSealedKey])
if err != nil || len(sealedKey) != 64 {
return errObjectTampered
}
var (
minDAREVersion byte
keyEncryptionKey [32]byte
)
switch algorithm {
switch {
default:
return errObjectTampered
case SSESealAlgorithmDareSha256: // legacy key-encryption-key derivation
minDAREVersion = sio.Version10
sha := sha256.New()
sha.Write(oldKey)
sha.Write(iv)
sha.Sum(keyEncryptionKey[:0])
case SSESealAlgorithmDareV2HmacSha256: // key-encryption-key derivation - See: crypto/doc.go
minDAREVersion = sio.Version20
mac := hmac.New(sha256.New, oldKey)
mac.Write(iv)
mac.Write([]byte(SSEDomain))
mac.Write([]byte(SSESealAlgorithmDareV2HmacSha256))
mac.Write([]byte(path.Join(bucket, object)))
mac.Sum(keyEncryptionKey[:0])
}
objectEncryptionKey := bytes.NewBuffer(nil) // decrypt object encryption key
n, err := sio.Decrypt(objectEncryptionKey, bytes.NewReader(sealedKey), sio.Config{
MinVersion: minDAREVersion,
Key: keyEncryptionKey[:],
})
if n != 32 || err != nil { // Either the provided key does not match or the object was tampered.
if subtle.ConstantTimeCompare(oldKey, newKey) == 1 {
return errInvalidSSEParameters // AWS returns special error for equal but invalid keys.
case crypto.SSEC.IsEncrypted(metadata):
sealedKey, err := crypto.SSEC.ParseMetadata(metadata)
if err != nil {
return err
}
return errSSEKeyMismatch // To provide strict AWS S3 compatibility we return: access denied.
}
if subtle.ConstantTimeCompare(oldKey, newKey) == 1 && algorithm != SSESealAlgorithmDareSha256 {
return nil // we don't need to rotate keys if newKey == oldKey but we may have to upgrade KDF algorithm
}
mac := hmac.New(sha256.New, newKey) // key-encryption-key derivation - See: crypto/doc.go
mac.Write(iv)
mac.Write([]byte(SSEDomain))
mac.Write([]byte(SSESealAlgorithmDareV2HmacSha256))
mac.Write([]byte(path.Join(bucket, object)))
mac.Sum(keyEncryptionKey[:0])
var objectKey crypto.ObjectKey
var extKey [32]byte
copy(extKey[:], oldKey)
if err = objectKey.Unseal(extKey, sealedKey, crypto.SSEC.String(), bucket, object); err != nil {
if subtle.ConstantTimeCompare(oldKey, newKey) == 1 {
return errInvalidSSEParameters // AWS returns special error for equal but invalid keys.
}
return crypto.ErrInvalidCustomerKey // To provide strict AWS S3 compatibility we return: access denied.
sealedKeyW := bytes.NewBuffer(nil) // sealedKey := 16 byte header + 32 byte payload + 16 byte tag
n, err = sio.Encrypt(sealedKeyW, bytes.NewReader(objectEncryptionKey.Bytes()), sio.Config{
Key: keyEncryptionKey[:],
})
if n != 64 || err != nil {
return errors.New("failed to seal object encryption key") // if this happens there's a bug in the code (may panic ?)
}
if subtle.ConstantTimeCompare(oldKey, newKey) == 1 && sealedKey.Algorithm == crypto.SealAlgorithm {
return nil // don't rotate on equal keys if seal algorithm is latest
}
copy(extKey[:], newKey)
sealedKey = objectKey.Seal(extKey, sealedKey.IV, crypto.SSEC.String(), bucket, object)
return nil
}
metadata[ServerSideEncryptionIV] = base64.StdEncoding.EncodeToString(iv[:])
metadata[ServerSideEncryptionSealAlgorithm] = SSESealAlgorithmDareV2HmacSha256
metadata[ServerSideEncryptionSealedKey] = base64.StdEncoding.EncodeToString(sealedKeyW.Bytes())
return nil
}
func newEncryptMetadata(key []byte, bucket, object string, metadata map[string]string) ([]byte, error) {
delete(metadata, SSECustomerKey) // make sure we do not save the key by accident
// See crypto/doc.go for detailed description
nonce := make([]byte, 32+SSEIVSize) // generate random values for key derivation
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return nil, err
func newEncryptMetadata(key []byte, bucket, object string, metadata map[string]string, sseS3 bool) ([]byte, error) {
delete(metadata, crypto.SSECKey) // make sure we do not save the key by accident
var sealedKey crypto.SealedKey
if sseS3 {
if globalKMS == nil {
return nil, errKMSNotConfigured
}
key, encKey, err := globalKMS.GenerateKey(globalKMSKeyID, crypto.Context{bucket: path.Join(bucket, object)})
if err != nil {
return nil, err
}
objectKey := crypto.GenerateKey(key, rand.Reader)
sealedKey = objectKey.Seal(key, crypto.GenerateIV(rand.Reader), crypto.S3.String(), bucket, object)
crypto.S3.CreateMetadata(metadata, globalKMSKeyID, encKey, sealedKey)
return objectKey[:], nil
}
sha := sha256.New() // derive object encryption key
sha.Write(key)
sha.Write(nonce[:32])
objectEncryptionKey := sha.Sum(nil)
var extKey [32]byte
copy(extKey[:], key)
objectKey := crypto.GenerateKey(extKey, rand.Reader)
sealedKey = objectKey.Seal(extKey, crypto.GenerateIV(rand.Reader), crypto.SSEC.String(), bucket, object)
crypto.SSEC.CreateMetadata(metadata, sealedKey)
return objectKey[:], nil
iv := sha256.Sum256(nonce[32:]) // key-encryption-key derivation - See: crypto/doc.go
mac := hmac.New(sha256.New, key)
mac.Write(iv[:])
mac.Write([]byte(SSEDomain))
mac.Write([]byte(SSESealAlgorithmDareV2HmacSha256))
mac.Write([]byte(path.Join(bucket, object)))
keyEncryptionKey := mac.Sum(nil)
sealedKey := bytes.NewBuffer(nil) // sealedKey := 16 byte header + 32 byte payload + 16 byte tag
n, err := sio.Encrypt(sealedKey, bytes.NewReader(objectEncryptionKey), sio.Config{
Key: keyEncryptionKey,
})
if n != 64 || err != nil {
return nil, errors.New("failed to seal object encryption key") // if this happens there's a bug in the code (may panic ?)
}
metadata[ServerSideEncryptionIV] = base64.StdEncoding.EncodeToString(iv[:])
metadata[ServerSideEncryptionSealAlgorithm] = SSESealAlgorithmDareV2HmacSha256
metadata[ServerSideEncryptionSealedKey] = base64.StdEncoding.EncodeToString(sealedKey.Bytes())
return objectEncryptionKey, nil
}
func newEncryptReader(content io.Reader, key []byte, bucket, object string, metadata map[string]string) (io.Reader, error) {
objectEncryptionKey, err := newEncryptMetadata(key, bucket, object, metadata)
func newEncryptReader(content io.Reader, key []byte, bucket, object string, metadata map[string]string, sseS3 bool) (io.Reader, error) {
objectEncryptionKey, err := newEncryptMetadata(key, bucket, object, metadata, sseS3)
if err != nil {
return nil, err
}
reader, err := sio.EncryptReader(content, sio.Config{Key: objectEncryptionKey})
reader, err := sio.EncryptReader(content, sio.Config{Key: objectEncryptionKey[:], MinVersion: sio.Version20})
if err != nil {
return nil, errInvalidSSEKey
return nil, crypto.ErrInvalidCustomerKey
}
return reader, nil
}
// set new encryption metadata from http request headers for SSE-C and generated key from KMS in the case of
// SSE-S3
func setEncryptionMetadata(r *http.Request, bucket, object string, metadata map[string]string) (err error) {
var (
key []byte
)
if crypto.SSEC.IsRequested(r.Header) {
key, err = ParseSSECustomerRequest(r)
if err != nil {
return
}
}
_, err = newEncryptMetadata(key, bucket, object, metadata, crypto.S3.IsRequested(r.Header))
return
}
// EncryptRequest takes the client provided content and encrypts the data
// with the client provided key. It also marks the object as client-side-encrypted
// and sets the correct headers.
func EncryptRequest(content io.Reader, r *http.Request, bucket, object string, metadata map[string]string) (io.Reader, error) {
key, err := ParseSSECustomerRequest(r)
if err != nil {
return nil, err
var (
key []byte
err error
)
if crypto.S3.IsRequested(r.Header) && crypto.SSEC.IsRequested(r.Header) {
return nil, crypto.ErrIncompatibleEncryptionMethod
}
return newEncryptReader(content, key, bucket, object, metadata)
if crypto.SSEC.IsRequested(r.Header) {
key, err = ParseSSECustomerRequest(r)
if err != nil {
return nil, err
}
}
return newEncryptReader(content, key, bucket, object, metadata, crypto.S3.IsRequested(r.Header))
}
// DecryptCopyRequest decrypts the object with the client provided key. It also removes
// the client-side-encryption metadata from the object and sets the correct headers.
func DecryptCopyRequest(client io.Writer, r *http.Request, bucket, object string, metadata map[string]string) (io.WriteCloser, error) {
key, err := ParseSSECopyCustomerRequest(r)
if err != nil {
return nil, err
var (
key []byte
err error
)
if crypto.SSECopy.IsRequested(r.Header) {
key, err = ParseSSECopyCustomerRequest(r, metadata)
if err != nil {
return nil, err
}
}
delete(metadata, SSECopyCustomerKey) // make sure we do not save the key by accident
delete(metadata, crypto.SSECopyKey) // make sure we do not save the key by accident
return newDecryptWriter(client, key, bucket, object, 0, metadata)
}
func decryptObjectInfo(key []byte, bucket, object string, metadata map[string]string) ([]byte, error) {
iv, err := base64.StdEncoding.DecodeString(metadata[ServerSideEncryptionIV])
if err != nil || len(iv) != SSEIVSize {
return nil, errObjectTampered
}
sealedKey, err := base64.StdEncoding.DecodeString(metadata[ServerSideEncryptionSealedKey])
if err != nil || len(sealedKey) != 64 {
return nil, errObjectTampered
}
var (
minDAREVersion byte
keyEncryptionKey [32]byte
)
switch algorithm := metadata[ServerSideEncryptionSealAlgorithm]; algorithm {
switch {
default:
return nil, errObjectTampered
case SSESealAlgorithmDareSha256: // legacy key-encryption-key derivation
minDAREVersion = sio.Version10
sha := sha256.New()
sha.Write(key)
sha.Write(iv)
sha.Sum(keyEncryptionKey[:0])
case SSESealAlgorithmDareV2HmacSha256: // key-encryption-key derivation - See: crypto/doc.go
minDAREVersion = sio.Version20
mac := hmac.New(sha256.New, key)
mac.Write(iv)
mac.Write([]byte(SSEDomain))
mac.Write([]byte(SSESealAlgorithmDareV2HmacSha256))
mac.Write([]byte(path.Join(bucket, object)))
mac.Sum(keyEncryptionKey[:0])
}
case crypto.S3.IsEncrypted(metadata):
if globalKMS == nil {
return nil, errKMSNotConfigured
}
keyID, kmsKey, sealedKey, err := crypto.S3.ParseMetadata(metadata)
objectEncryptionKey := bytes.NewBuffer(nil) // decrypt object encryption key
n, err := sio.Decrypt(objectEncryptionKey, bytes.NewReader(sealedKey), sio.Config{
MinVersion: minDAREVersion,
Key: keyEncryptionKey[:],
})
if n != 32 || err != nil {
// Either the provided key does not match or the object was tampered.
// To provide strict AWS S3 compatibility we return: access denied.
return nil, errSSEKeyMismatch
if err != nil {
return nil, err
}
extKey, err := globalKMS.UnsealKey(keyID, kmsKey, crypto.Context{bucket: path.Join(bucket, object)})
if err != nil {
return nil, err
}
var objectKey crypto.ObjectKey
if err = objectKey.Unseal(extKey, sealedKey, crypto.S3.String(), bucket, object); err != nil {
return nil, err
}
return objectKey[:], nil
case crypto.SSEC.IsEncrypted(metadata):
var extKey [32]byte
copy(extKey[:], key)
sealedKey, err := crypto.SSEC.ParseMetadata(metadata)
if err != nil {
return nil, err
}
var objectKey crypto.ObjectKey
if err = objectKey.Unseal(extKey, sealedKey, crypto.SSEC.String(), bucket, object); err != nil {
return nil, err
}
return objectKey[:], nil
}
return objectEncryptionKey.Bytes(), nil
}
func newDecryptWriter(client io.Writer, key []byte, bucket, object string, seqNumber uint32, metadata map[string]string) (io.WriteCloser, error) {
@@ -468,29 +297,35 @@ func newDecryptWriterWithObjectKey(client io.Writer, objectEncryptionKey []byte,
SequenceNumber: seqNumber,
})
if err != nil {
return nil, errInvalidSSEKey
return nil, crypto.ErrInvalidCustomerKey
}
delete(metadata, ServerSideEncryptionIV)
delete(metadata, ServerSideEncryptionSealAlgorithm)
delete(metadata, ServerSideEncryptionSealedKey)
delete(metadata, ReservedMetadataPrefix+"Encrypted-Multipart")
delete(metadata, crypto.SSEIV)
delete(metadata, crypto.SSESealAlgorithm)
delete(metadata, crypto.SSECSealedKey)
delete(metadata, crypto.SSEMultipart)
delete(metadata, crypto.S3SealedKey)
delete(metadata, crypto.S3KMSSealedKey)
delete(metadata, crypto.S3KMSKeyID)
return writer, nil
}
// DecryptRequestWithSequenceNumber decrypts the object with the client provided key. It also removes
// the client-side-encryption metadata from the object and sets the correct headers.
func DecryptRequestWithSequenceNumber(client io.Writer, r *http.Request, bucket, object string, seqNumber uint32, metadata map[string]string) (io.WriteCloser, error) {
if crypto.S3.IsEncrypted(metadata) {
return newDecryptWriter(client, nil, bucket, object, seqNumber, metadata)
}
key, err := ParseSSECustomerRequest(r)
if err != nil {
return nil, err
}
delete(metadata, SSECustomerKey) // make sure we do not save the key by accident
delete(metadata, crypto.SSECKey) // make sure we do not save the key by accident
return newDecryptWriter(client, key, bucket, object, seqNumber, metadata)
}
// DecryptRequest decrypts the object with the client provided key. It also removes
// the client-side-encryption metadata from the object and sets the correct headers.
// DecryptRequest decrypts the object with client provided key for SSE-C and SSE-S3. It also removes
// the encryption metadata from the object and sets the correct headers.
func DecryptRequest(client io.Writer, r *http.Request, bucket, object string, metadata map[string]string) (io.WriteCloser, error) {
return DecryptRequestWithSequenceNumber(client, r, bucket, object, 0, metadata)
}
@@ -527,11 +362,15 @@ func (w *DecryptBlocksWriter) buildDecrypter(partID int) error {
var key []byte
var err error
if w.copySource {
w.req.Header.Set(SSECopyCustomerKey, w.customerKeyHeader)
key, err = ParseSSECopyCustomerRequest(w.req)
if crypto.SSEC.IsEncrypted(w.metadata) {
w.req.Header.Set(crypto.SSECopyKey, w.customerKeyHeader)
key, err = ParseSSECopyCustomerRequest(w.req, w.metadata)
}
} else {
w.req.Header.Set(SSECustomerKey, w.customerKeyHeader)
key, err = ParseSSECustomerRequest(w.req)
if crypto.SSEC.IsEncrypted(w.metadata) {
w.req.Header.Set(crypto.SSECKey, w.customerKeyHeader)
key, err = ParseSSECustomerRequest(w.req)
}
}
if err != nil {
return err
@@ -551,9 +390,9 @@ func (w *DecryptBlocksWriter) buildDecrypter(partID int) error {
// make sure we do not save the key by accident
if w.copySource {
delete(m, SSECopyCustomerKey)
delete(m, crypto.SSECopyKey)
} else {
delete(m, SSECustomerKey)
delete(m, crypto.SSECKey)
}
// make sure to provide a NopCloser such that a Close
@@ -643,7 +482,7 @@ func DecryptBlocksRequest(client io.Writer, r *http.Request, bucket, object stri
var seqNumber uint32
var encStartOffset, encLength int64
if len(objInfo.Parts) == 0 || !objInfo.IsEncryptedMultipart() {
if len(objInfo.Parts) == 0 || !crypto.IsMultiPart(objInfo.UserDefined) {
seqNumber, encStartOffset, encLength = getEncryptedSinglePartOffsetLength(startOffset, length, objInfo)
var writer io.WriteCloser
@@ -694,7 +533,7 @@ func DecryptBlocksRequest(client io.Writer, r *http.Request, bucket, object stri
req: r,
bucket: bucket,
object: object,
customerKeyHeader: r.Header.Get(SSECustomerKey),
customerKeyHeader: r.Header.Get(crypto.SSECKey),
copySource: copySource,
}
@@ -705,13 +544,18 @@ func DecryptBlocksRequest(client io.Writer, r *http.Request, bucket, object stri
}
// Purge all the encryption headers.
delete(objInfo.UserDefined, ServerSideEncryptionIV)
delete(objInfo.UserDefined, ServerSideEncryptionSealAlgorithm)
delete(objInfo.UserDefined, ServerSideEncryptionSealedKey)
delete(objInfo.UserDefined, ReservedMetadataPrefix+"Encrypted-Multipart")
delete(objInfo.UserDefined, crypto.SSEIV)
delete(objInfo.UserDefined, crypto.SSESealAlgorithm)
delete(objInfo.UserDefined, crypto.SSECSealedKey)
delete(objInfo.UserDefined, crypto.SSEMultipart)
if crypto.S3.IsEncrypted(objInfo.UserDefined) {
delete(objInfo.UserDefined, crypto.S3SealedKey)
delete(objInfo.UserDefined, crypto.S3KMSKeyID)
delete(objInfo.UserDefined, crypto.S3KMSSealedKey)
}
if w.copySource {
w.customerKeyHeader = r.Header.Get(SSECopyCustomerKey)
w.customerKeyHeader = r.Header.Get(crypto.SSECopyKey)
}
if err := w.buildDecrypter(w.parts[w.partIndex].Number); err != nil {
@@ -793,48 +637,14 @@ func getEncryptedSinglePartOffsetLength(offset, length int64, objInfo ObjectInfo
return seqNumber, encOffset, encLength
}
// IsEncryptedMultipart - is the encrypted content multiparted?
func (o *ObjectInfo) IsEncryptedMultipart() bool {
_, ok := o.UserDefined[ReservedMetadataPrefix+"Encrypted-Multipart"]
return ok
}
// IsEncrypted returns true if the object is marked as encrypted.
func (o *ObjectInfo) IsEncrypted() bool {
if _, ok := o.UserDefined[ServerSideEncryptionIV]; ok {
return true
}
if _, ok := o.UserDefined[ServerSideEncryptionSealAlgorithm]; ok {
return true
}
if _, ok := o.UserDefined[ServerSideEncryptionSealedKey]; ok {
return true
}
return false
}
// IsEncrypted returns true if the object is marked as encrypted.
func (li *ListPartsInfo) IsEncrypted() bool {
if _, ok := li.UserDefined[ServerSideEncryptionIV]; ok {
return true
}
if _, ok := li.UserDefined[ServerSideEncryptionSealAlgorithm]; ok {
return true
}
if _, ok := li.UserDefined[ServerSideEncryptionSealedKey]; ok {
return true
}
return false
}
// DecryptedSize returns the size of the object after decryption in bytes.
// It returns an error if the object is not encrypted or marked as encrypted
// but has an invalid size.
func (o *ObjectInfo) DecryptedSize() (int64, error) {
if !o.IsEncrypted() {
if !crypto.IsEncrypted(o.UserDefined) {
return 0, errors.New("Cannot compute decrypted size of an unencrypted object")
}
if len(o.Parts) == 0 || !o.IsEncryptedMultipart() {
if len(o.Parts) == 0 || !crypto.IsMultiPart(o.UserDefined) {
size, err := sio.DecryptedSize(uint64(o.Size))
if err != nil {
err = errObjectTampered // assign correct error type
@@ -880,10 +690,11 @@ func DecryptCopyObjectInfo(info *ObjectInfo, headers http.Header) (apiErr APIErr
if info.IsDir {
return ErrNone, false
}
if apiErr, encrypted = ErrNone, info.IsEncrypted(); !encrypted && hasSSECopyCustomerHeader(headers) {
if apiErr, encrypted = ErrNone, crypto.IsEncrypted(info.UserDefined); !encrypted && crypto.SSECopy.IsRequested(headers) {
apiErr = ErrInvalidEncryptionParameters
} else if encrypted {
if !hasSSECopyCustomerHeader(headers) {
if (!crypto.SSECopy.IsRequested(headers) && crypto.SSEC.IsEncrypted(info.UserDefined)) ||
(crypto.SSECopy.IsRequested(headers) && crypto.S3.IsEncrypted(info.UserDefined)) {
apiErr = ErrSSEEncryptedObject
return
}
@@ -907,10 +718,16 @@ func DecryptObjectInfo(info *ObjectInfo, headers http.Header) (apiErr APIErrorCo
if info.IsDir {
return ErrNone, false
}
if apiErr, encrypted = ErrNone, info.IsEncrypted(); !encrypted && hasSSECustomerHeader(headers) {
// disallow X-Amz-Server-Side-Encryption header on HEAD and GET
if crypto.S3.IsRequested(headers) {
apiErr = ErrInvalidEncryptionParameters
return
}
if apiErr, encrypted = ErrNone, crypto.IsEncrypted(info.UserDefined); !encrypted && crypto.SSEC.IsRequested(headers) {
apiErr = ErrInvalidEncryptionParameters
} else if encrypted {
if !hasSSECustomerHeader(headers) {
if (crypto.SSEC.IsEncrypted(info.UserDefined) && !crypto.SSEC.IsRequested(headers)) ||
(crypto.S3.IsEncrypted(info.UserDefined) && crypto.SSEC.IsRequested(headers)) {
apiErr = ErrSSEEncryptedObject
return
}