mirror of
				https://github.com/minio/minio.git
				synced 2025-11-04 10:11:09 +01:00 
			
		
		
		
	* SSE-C: Add support in Bucket Post Policy * Rename isSSECustomerRequest & isSSECopyCustomerRequest to hasSSECustomerHeader hasSSECopyCustomerHeader
		
			
				
	
	
		
			827 lines
		
	
	
		
			30 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			827 lines
		
	
	
		
			30 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
/*
 | 
						|
 * Minio Cloud Storage, (C) 2017, 2018 Minio, Inc.
 | 
						|
 *
 | 
						|
 * 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 cmd
 | 
						|
 | 
						|
import (
 | 
						|
	"bytes"
 | 
						|
	"crypto/hmac"
 | 
						|
	"crypto/md5"
 | 
						|
	"crypto/rand"
 | 
						|
	"crypto/subtle"
 | 
						|
	"encoding/base64"
 | 
						|
	"encoding/binary"
 | 
						|
	"errors"
 | 
						|
	"io"
 | 
						|
	"net/http"
 | 
						|
 | 
						|
	"github.com/minio/minio/pkg/ioutil"
 | 
						|
	sha256 "github.com/minio/sha256-simd"
 | 
						|
	"github.com/minio/sio"
 | 
						|
)
 | 
						|
 | 
						|
var (
 | 
						|
	// AWS errors for invalid SSE-C requests.
 | 
						|
	errInsecureSSERequest  = errors.New("Requests specifying Server Side Encryption with Customer provided keys must be made over a secure connection")
 | 
						|
	errEncryptedObject     = errors.New("The object was stored using a form of Server Side Encryption. The correct parameters must be provided to retrieve the object")
 | 
						|
	errInvalidSSEAlgorithm = errors.New("Requests specifying Server Side Encryption with Customer provided keys must provide a valid encryption algorithm")
 | 
						|
	errMissingSSEKey       = errors.New("Requests specifying Server Side Encryption with Customer provided keys must provide an appropriate secret key")
 | 
						|
	errInvalidSSEKey       = errors.New("The secret key was invalid for the specified algorithm")
 | 
						|
	errMissingSSEKeyMD5    = errors.New("Requests specifying Server Side Encryption with Customer provided keys must provide the client calculated MD5 of the secret key")
 | 
						|
	errSSEKeyMD5Mismatch   = errors.New("The calculated MD5 hash of the key did not match the hash that was provided")
 | 
						|
	errSSEKeyMismatch      = errors.New("The client provided key does not match the key provided when the object was encrypted") // this msg is not shown to the client
 | 
						|
 | 
						|
	// 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.
 | 
						|
	SSECustomerKeySize = 32
 | 
						|
 | 
						|
	// 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
 | 
						|
 | 
						|
	// SSE dare package meta padding bytes.
 | 
						|
	sseDAREPackageMetaSize = 32 // 32 bytes
 | 
						|
 | 
						|
)
 | 
						|
 | 
						|
// 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"
 | 
						|
)
 | 
						|
 | 
						|
// SSESealAlgorithmDareSha256 specifies DARE as authenticated en/decryption scheme and SHA256 as cryptographic
 | 
						|
// hash function.
 | 
						|
const SSESealAlgorithmDareSha256 = "DARE-SHA256"
 | 
						|
 | 
						|
// 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) != ""
 | 
						|
}
 | 
						|
 | 
						|
// 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) {
 | 
						|
	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.
 | 
						|
		// Minio uses a BufConn (wrapping a tls.Conn) so the type check within the http package
 | 
						|
		// 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 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
 | 
						|
}
 | 
						|
 | 
						|
// ParseSSECustomerRequest parses the SSE-C header fields of the provided request.
 | 
						|
// It returns the client provided key on success.
 | 
						|
func ParseSSECustomerRequest(r *http.Request) (key []byte, err error) {
 | 
						|
	return ParseSSECustomerHeader(r.Header)
 | 
						|
}
 | 
						|
 | 
						|
// ParseSSECustomerHeader parses the SSE-C header fields and returns
 | 
						|
// the client provided key on success.
 | 
						|
func ParseSSECustomerHeader(header http.Header) (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.
 | 
						|
		// Minio uses a BufConn (wrapping a tls.Conn) so the type check within the http package
 | 
						|
		// 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
 | 
						|
	}
 | 
						|
 | 
						|
	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
 | 
						|
}
 | 
						|
 | 
						|
// This function rotates old to new key.
 | 
						|
func rotateKey(oldKey []byte, newKey []byte, metadata map[string]string) error {
 | 
						|
	if subtle.ConstantTimeCompare(oldKey, newKey) == 1 {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
	delete(metadata, SSECustomerKey) // make sure we do not save the key by accident
 | 
						|
 | 
						|
	if metadata[ServerSideEncryptionSealAlgorithm] != SSESealAlgorithmDareSha256 { // currently DARE-SHA256 is the only option
 | 
						|
		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
 | 
						|
	}
 | 
						|
 | 
						|
	sha := sha256.New() // derive key encryption key
 | 
						|
	sha.Write(oldKey)
 | 
						|
	sha.Write(iv)
 | 
						|
	keyEncryptionKey := sha.Sum(nil)
 | 
						|
 | 
						|
	objectEncryptionKey := bytes.NewBuffer(nil) // decrypt object encryption key
 | 
						|
	n, err := sio.Decrypt(objectEncryptionKey, bytes.NewReader(sealedKey), sio.Config{
 | 
						|
		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 errSSEKeyMismatch
 | 
						|
	}
 | 
						|
 | 
						|
	nonce := make([]byte, 32) // generate random values for key derivation
 | 
						|
	if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	niv := sha256.Sum256(nonce[:]) // derive key encryption key
 | 
						|
	sha = sha256.New()
 | 
						|
	sha.Write(newKey)
 | 
						|
	sha.Write(niv[:])
 | 
						|
	keyEncryptionKey = sha.Sum(nil)
 | 
						|
 | 
						|
	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 ?)
 | 
						|
	}
 | 
						|
 | 
						|
	metadata[ServerSideEncryptionIV] = base64.StdEncoding.EncodeToString(niv[:])
 | 
						|
	metadata[ServerSideEncryptionSealAlgorithm] = SSESealAlgorithmDareSha256
 | 
						|
	metadata[ServerSideEncryptionSealedKey] = base64.StdEncoding.EncodeToString(sealedKeyW.Bytes())
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func newEncryptMetadata(key []byte, metadata map[string]string) ([]byte, error) {
 | 
						|
	delete(metadata, SSECustomerKey) // make sure we do not save the key by accident
 | 
						|
 | 
						|
	// security notice:
 | 
						|
	//  - If the first 32 bytes of the random value are ever repeated under the same client-provided
 | 
						|
	//    key the encrypted object will not be tamper-proof. [ P(coll) ~= 1 / 2^(256 / 2)]
 | 
						|
	//  - If the last 32 bytes of the random value are ever repeated under the same client-provided
 | 
						|
	//    key an adversary may be able to extract the object encryption key. This depends on the
 | 
						|
	//    authenticated en/decryption scheme. The DARE format will generate an 8 byte nonce which must
 | 
						|
	//    be repeated in addition to reveal the object encryption key.
 | 
						|
	//    [ P(coll) ~= 1 / 2^((256 + 64) / 2) ]
 | 
						|
	nonce := make([]byte, 32+SSEIVSize) // generate random values for key derivation
 | 
						|
	if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	sha := sha256.New() // derive object encryption key
 | 
						|
	sha.Write(key)
 | 
						|
	sha.Write(nonce[:32])
 | 
						|
	objectEncryptionKey := sha.Sum(nil)
 | 
						|
 | 
						|
	iv := sha256.Sum256(nonce[32:]) // derive key encryption key
 | 
						|
	sha = sha256.New()
 | 
						|
	sha.Write(key)
 | 
						|
	sha.Write(iv[:])
 | 
						|
	keyEncryptionKey := sha.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] = SSESealAlgorithmDareSha256
 | 
						|
	metadata[ServerSideEncryptionSealedKey] = base64.StdEncoding.EncodeToString(sealedKey.Bytes())
 | 
						|
 | 
						|
	return objectEncryptionKey, nil
 | 
						|
}
 | 
						|
 | 
						|
func newEncryptReader(content io.Reader, key []byte, metadata map[string]string) (io.Reader, error) {
 | 
						|
	objectEncryptionKey, err := newEncryptMetadata(key, metadata)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	reader, err := sio.EncryptReader(content, sio.Config{Key: objectEncryptionKey})
 | 
						|
	if err != nil {
 | 
						|
		return nil, errInvalidSSEKey
 | 
						|
	}
 | 
						|
 | 
						|
	return reader, nil
 | 
						|
}
 | 
						|
 | 
						|
// 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, metadata map[string]string) (io.Reader, error) {
 | 
						|
	key, err := ParseSSECustomerRequest(r)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	return newEncryptReader(content, key, metadata)
 | 
						|
}
 | 
						|
 | 
						|
// 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, metadata map[string]string) (io.WriteCloser, error) {
 | 
						|
	key, err := ParseSSECopyCustomerRequest(r)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	delete(metadata, SSECopyCustomerKey) // make sure we do not save the key by accident
 | 
						|
	return newDecryptWriter(client, key, 0, metadata)
 | 
						|
}
 | 
						|
 | 
						|
func decryptObjectInfo(key []byte, metadata map[string]string) ([]byte, error) {
 | 
						|
	if metadata[ServerSideEncryptionSealAlgorithm] != SSESealAlgorithmDareSha256 { // currently DARE-SHA256 is the only option
 | 
						|
		return nil, errObjectTampered
 | 
						|
	}
 | 
						|
	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
 | 
						|
	}
 | 
						|
 | 
						|
	sha := sha256.New() // derive key encryption key
 | 
						|
	sha.Write(key)
 | 
						|
	sha.Write(iv)
 | 
						|
	keyEncryptionKey := sha.Sum(nil)
 | 
						|
 | 
						|
	objectEncryptionKey := bytes.NewBuffer(nil) // decrypt object encryption key
 | 
						|
	n, err := sio.Decrypt(objectEncryptionKey, bytes.NewReader(sealedKey), sio.Config{
 | 
						|
		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
 | 
						|
	}
 | 
						|
	return objectEncryptionKey.Bytes(), nil
 | 
						|
}
 | 
						|
 | 
						|
func newDecryptWriter(client io.Writer, key []byte, seqNumber uint32, metadata map[string]string) (io.WriteCloser, error) {
 | 
						|
	objectEncryptionKey, err := decryptObjectInfo(key, metadata)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
 | 
						|
	}
 | 
						|
	return newDecryptWriterWithObjectKey(client, objectEncryptionKey, seqNumber, metadata)
 | 
						|
}
 | 
						|
 | 
						|
func newDecryptWriterWithObjectKey(client io.Writer, objectEncryptionKey []byte, seqNumber uint32, metadata map[string]string) (io.WriteCloser, error) {
 | 
						|
	writer, err := sio.DecryptWriter(client, sio.Config{
 | 
						|
		Key:            objectEncryptionKey,
 | 
						|
		SequenceNumber: seqNumber,
 | 
						|
	})
 | 
						|
	if err != nil {
 | 
						|
		return nil, errInvalidSSEKey
 | 
						|
	}
 | 
						|
 | 
						|
	delete(metadata, ServerSideEncryptionIV)
 | 
						|
	delete(metadata, ServerSideEncryptionSealAlgorithm)
 | 
						|
	delete(metadata, ServerSideEncryptionSealedKey)
 | 
						|
	delete(metadata, ReservedMetadataPrefix+"Encrypted-Multipart")
 | 
						|
	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, seqNumber uint32, metadata map[string]string) (io.WriteCloser, error) {
 | 
						|
	key, err := ParseSSECustomerRequest(r)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	delete(metadata, SSECustomerKey) // make sure we do not save the key by accident
 | 
						|
	return newDecryptWriter(client, key, 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.
 | 
						|
func DecryptRequest(client io.Writer, r *http.Request, metadata map[string]string) (io.WriteCloser, error) {
 | 
						|
	return DecryptRequestWithSequenceNumber(client, r, 0, metadata)
 | 
						|
}
 | 
						|
 | 
						|
// DecryptBlocksWriter - decrypts multipart parts, while implementing a io.Writer compatible interface.
 | 
						|
type DecryptBlocksWriter struct {
 | 
						|
	// Original writer where the plain data will be written
 | 
						|
	writer io.Writer
 | 
						|
	// Current decrypter for the current encrypted data block
 | 
						|
	decrypter io.WriteCloser
 | 
						|
	// Start sequence number
 | 
						|
	startSeqNum uint32
 | 
						|
	// Current part index
 | 
						|
	partIndex int
 | 
						|
	// Parts information
 | 
						|
	parts    []objectPartInfo
 | 
						|
	req      *http.Request
 | 
						|
	metadata map[string]string
 | 
						|
 | 
						|
	partEncRelOffset int64
 | 
						|
 | 
						|
	copySource bool
 | 
						|
	// Customer Key
 | 
						|
	customerKeyHeader string
 | 
						|
}
 | 
						|
 | 
						|
func (w *DecryptBlocksWriter) buildDecrypter(partID int) error {
 | 
						|
	m := make(map[string]string)
 | 
						|
	for k, v := range w.metadata {
 | 
						|
		m[k] = v
 | 
						|
	}
 | 
						|
	// Initialize the first decrypter, new decrypters will be initialized in Write() operation as needed.
 | 
						|
	var key []byte
 | 
						|
	var err error
 | 
						|
	if w.copySource {
 | 
						|
		w.req.Header.Set(SSECopyCustomerKey, w.customerKeyHeader)
 | 
						|
		key, err = ParseSSECopyCustomerRequest(w.req)
 | 
						|
	} else {
 | 
						|
		w.req.Header.Set(SSECustomerKey, w.customerKeyHeader)
 | 
						|
		key, err = ParseSSECustomerRequest(w.req)
 | 
						|
	}
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	objectEncryptionKey, err := decryptObjectInfo(key, m)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	var partIDbin [4]byte
 | 
						|
	binary.LittleEndian.PutUint32(partIDbin[:], uint32(partID)) // marshal part ID
 | 
						|
 | 
						|
	mac := hmac.New(sha256.New, objectEncryptionKey) // derive part encryption key from part ID and object key
 | 
						|
	mac.Write(partIDbin[:])
 | 
						|
	partEncryptionKey := mac.Sum(nil)
 | 
						|
 | 
						|
	// make sure we do not save the key by accident
 | 
						|
	if w.copySource {
 | 
						|
		delete(m, SSECopyCustomerKey)
 | 
						|
	} else {
 | 
						|
		delete(m, SSECustomerKey)
 | 
						|
	}
 | 
						|
 | 
						|
	// make sure to provide a NopCloser such that a Close
 | 
						|
	// on sio.decryptWriter doesn't close the underlying writer's
 | 
						|
	// close which perhaps can close the stream prematurely.
 | 
						|
	decrypter, err := newDecryptWriterWithObjectKey(ioutil.NopCloser(w.writer), partEncryptionKey, w.startSeqNum, m)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	if w.decrypter != nil {
 | 
						|
		// Pro-actively close the writer such that any pending buffers
 | 
						|
		// are flushed already before we allocate a new decrypter.
 | 
						|
		err = w.decrypter.Close()
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	w.decrypter = decrypter
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (w *DecryptBlocksWriter) Write(p []byte) (int, error) {
 | 
						|
	var err error
 | 
						|
	var n1 int
 | 
						|
	if int64(len(p)) < w.parts[w.partIndex].Size-w.partEncRelOffset {
 | 
						|
		n1, err = w.decrypter.Write(p)
 | 
						|
		if err != nil {
 | 
						|
			return 0, err
 | 
						|
		}
 | 
						|
		w.partEncRelOffset += int64(n1)
 | 
						|
	} else {
 | 
						|
		n1, err = w.decrypter.Write(p[:w.parts[w.partIndex].Size-w.partEncRelOffset])
 | 
						|
		if err != nil {
 | 
						|
			return 0, err
 | 
						|
		}
 | 
						|
 | 
						|
		// We should now proceed to next part, reset all values appropriately.
 | 
						|
		w.partEncRelOffset = 0
 | 
						|
		w.startSeqNum = 0
 | 
						|
 | 
						|
		w.partIndex++
 | 
						|
 | 
						|
		err = w.buildDecrypter(w.partIndex + 1)
 | 
						|
		if err != nil {
 | 
						|
			return 0, err
 | 
						|
		}
 | 
						|
 | 
						|
		n1, err = w.decrypter.Write(p[n1:])
 | 
						|
		if err != nil {
 | 
						|
			return 0, err
 | 
						|
		}
 | 
						|
 | 
						|
		w.partEncRelOffset += int64(n1)
 | 
						|
	}
 | 
						|
 | 
						|
	return len(p), nil
 | 
						|
}
 | 
						|
 | 
						|
// Close closes the LimitWriter. It behaves like io.Closer.
 | 
						|
func (w *DecryptBlocksWriter) Close() error {
 | 
						|
	if w.decrypter != nil {
 | 
						|
		err := w.decrypter.Close()
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if closer, ok := w.writer.(io.Closer); ok {
 | 
						|
		return closer.Close()
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// DecryptAllBlocksCopyRequest - setup a struct which can decrypt many concatenated encrypted data
 | 
						|
// parts information helps to know the boundaries of each encrypted data block, this function decrypts
 | 
						|
// all parts starting from part-1.
 | 
						|
func DecryptAllBlocksCopyRequest(client io.Writer, r *http.Request, objInfo ObjectInfo) (io.WriteCloser, int64, error) {
 | 
						|
	w, _, size, err := DecryptBlocksRequest(client, r, 0, objInfo.Size, objInfo, true)
 | 
						|
	return w, size, err
 | 
						|
}
 | 
						|
 | 
						|
// DecryptBlocksRequest - setup a struct which can decrypt many concatenated encrypted data
 | 
						|
// parts information helps to know the boundaries of each encrypted data block.
 | 
						|
func DecryptBlocksRequest(client io.Writer, r *http.Request, startOffset, length int64, objInfo ObjectInfo, copySource bool) (io.WriteCloser, int64, int64, error) {
 | 
						|
	seqNumber, encStartOffset, encLength := getEncryptedStartOffset(startOffset, length)
 | 
						|
 | 
						|
	// Encryption length cannot be bigger than the file size, if it is
 | 
						|
	// which is allowed in AWS S3, we simply default to EncryptedSize().
 | 
						|
	if encLength+encStartOffset > objInfo.EncryptedSize() {
 | 
						|
		encLength = objInfo.EncryptedSize() - encStartOffset
 | 
						|
	}
 | 
						|
 | 
						|
	if len(objInfo.Parts) == 0 || !objInfo.IsEncryptedMultipart() {
 | 
						|
		var writer io.WriteCloser
 | 
						|
		var err error
 | 
						|
		if copySource {
 | 
						|
			writer, err = DecryptCopyRequest(client, r, objInfo.UserDefined)
 | 
						|
		} else {
 | 
						|
			writer, err = DecryptRequestWithSequenceNumber(client, r, seqNumber, objInfo.UserDefined)
 | 
						|
		}
 | 
						|
		if err != nil {
 | 
						|
			return nil, 0, 0, err
 | 
						|
		}
 | 
						|
		return writer, encStartOffset, encLength, nil
 | 
						|
	}
 | 
						|
 | 
						|
	var partStartIndex int
 | 
						|
	var partStartOffset = startOffset
 | 
						|
	// Skip parts until final offset maps to a particular part offset.
 | 
						|
	for i, part := range objInfo.Parts {
 | 
						|
		decryptedSize, err := decryptedSize(part.Size)
 | 
						|
		if err != nil {
 | 
						|
			return nil, -1, -1, err
 | 
						|
		}
 | 
						|
 | 
						|
		partStartIndex = i
 | 
						|
 | 
						|
		// Offset is smaller than size we have reached the
 | 
						|
		// proper part offset, break out we start from
 | 
						|
		// this part index.
 | 
						|
		if partStartOffset < decryptedSize {
 | 
						|
			break
 | 
						|
		}
 | 
						|
 | 
						|
		// Continue to look for next part.
 | 
						|
		partStartOffset -= decryptedSize
 | 
						|
	}
 | 
						|
 | 
						|
	startSeqNum := partStartOffset / sseDAREPackageBlockSize
 | 
						|
	partEncRelOffset := int64(startSeqNum) * (sseDAREPackageBlockSize + sseDAREPackageMetaSize)
 | 
						|
 | 
						|
	w := &DecryptBlocksWriter{
 | 
						|
		writer:            client,
 | 
						|
		startSeqNum:       uint32(startSeqNum),
 | 
						|
		partEncRelOffset:  partEncRelOffset,
 | 
						|
		parts:             objInfo.Parts,
 | 
						|
		partIndex:         partStartIndex,
 | 
						|
		req:               r,
 | 
						|
		customerKeyHeader: r.Header.Get(SSECustomerKey),
 | 
						|
		copySource:        copySource,
 | 
						|
	}
 | 
						|
 | 
						|
	w.metadata = map[string]string{}
 | 
						|
	// Copy encryption metadata for internal use.
 | 
						|
	for k, v := range objInfo.UserDefined {
 | 
						|
		w.metadata[k] = v
 | 
						|
	}
 | 
						|
 | 
						|
	// Purge all the encryption headers.
 | 
						|
	delete(objInfo.UserDefined, ServerSideEncryptionIV)
 | 
						|
	delete(objInfo.UserDefined, ServerSideEncryptionSealAlgorithm)
 | 
						|
	delete(objInfo.UserDefined, ServerSideEncryptionSealedKey)
 | 
						|
	delete(objInfo.UserDefined, ReservedMetadataPrefix+"Encrypted-Multipart")
 | 
						|
 | 
						|
	if w.copySource {
 | 
						|
		w.customerKeyHeader = r.Header.Get(SSECopyCustomerKey)
 | 
						|
	}
 | 
						|
 | 
						|
	if err := w.buildDecrypter(partStartIndex + 1); err != nil {
 | 
						|
		return nil, 0, 0, err
 | 
						|
	}
 | 
						|
 | 
						|
	return w, encStartOffset, encLength, nil
 | 
						|
}
 | 
						|
 | 
						|
// getEncryptedStartOffset - fetch sequence number, encrypted start offset and encrypted length.
 | 
						|
func getEncryptedStartOffset(offset, length int64) (seqNumber uint32, encOffset int64, encLength int64) {
 | 
						|
	onePkgSize := int64(sseDAREPackageBlockSize + sseDAREPackageMetaSize)
 | 
						|
 | 
						|
	seqNumber = uint32(offset / sseDAREPackageBlockSize)
 | 
						|
	encOffset = int64(seqNumber) * onePkgSize
 | 
						|
	// The math to compute the encrypted length is always
 | 
						|
	// originalLength i.e (offset+length-1) to be divided under
 | 
						|
	// 64KiB blocks which is the payload size for each encrypted
 | 
						|
	// block. This is then multiplied by final package size which
 | 
						|
	// is basically 64KiB + 32. Finally negate the encrypted offset
 | 
						|
	// to get the final encrypted length on disk.
 | 
						|
	encLength = ((offset+length)/sseDAREPackageBlockSize)*onePkgSize - encOffset
 | 
						|
 | 
						|
	// Check for the remainder, to figure if we need one extract package to read from.
 | 
						|
	if (offset+length)%sseDAREPackageBlockSize > 0 {
 | 
						|
		encLength += onePkgSize
 | 
						|
	}
 | 
						|
 | 
						|
	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
 | 
						|
}
 | 
						|
 | 
						|
func decryptedSize(encryptedSize int64) (int64, error) {
 | 
						|
	if encryptedSize == 0 {
 | 
						|
		return encryptedSize, nil
 | 
						|
	}
 | 
						|
	size := (encryptedSize / (sseDAREPackageBlockSize + sseDAREPackageMetaSize)) * sseDAREPackageBlockSize
 | 
						|
	if mod := encryptedSize % (sseDAREPackageBlockSize + sseDAREPackageMetaSize); mod > 0 {
 | 
						|
		if mod < sseDAREPackageMetaSize+1 {
 | 
						|
			return -1, errObjectTampered // object is not 0 size but smaller than the smallest valid encrypted object
 | 
						|
		}
 | 
						|
		size += mod - sseDAREPackageMetaSize
 | 
						|
	}
 | 
						|
	return size, nil
 | 
						|
}
 | 
						|
 | 
						|
// 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.
 | 
						|
// DecryptedSize panics if the referred object is not encrypted.
 | 
						|
func (o *ObjectInfo) DecryptedSize() (int64, error) {
 | 
						|
	if !o.IsEncrypted() {
 | 
						|
		panic("cannot compute decrypted size of an object which is not encrypted")
 | 
						|
	}
 | 
						|
 | 
						|
	return decryptedSize(o.Size)
 | 
						|
}
 | 
						|
 | 
						|
// EncryptedSize returns the size of the object after encryption.
 | 
						|
// An encrypted object is always larger than a plain object
 | 
						|
// except for zero size objects.
 | 
						|
func (o *ObjectInfo) EncryptedSize() int64 {
 | 
						|
	size := (o.Size / sseDAREPackageBlockSize) * (sseDAREPackageBlockSize + sseDAREPackageMetaSize)
 | 
						|
	if mod := o.Size % (sseDAREPackageBlockSize); mod > 0 {
 | 
						|
		size += mod + sseDAREPackageMetaSize
 | 
						|
	}
 | 
						|
	return size
 | 
						|
}
 | 
						|
 | 
						|
// DecryptCopyObjectInfo tries to decrypt the provided object if it is encrypted.
 | 
						|
// It fails if the object is encrypted and the HTTP headers don't contain
 | 
						|
// SSE-C headers or the object is not encrypted but SSE-C headers are provided. (AWS behavior)
 | 
						|
// DecryptObjectInfo returns 'ErrNone' if the object is not encrypted or the
 | 
						|
// decryption succeeded.
 | 
						|
//
 | 
						|
// DecryptCopyObjectInfo also returns whether the object is encrypted or not.
 | 
						|
func DecryptCopyObjectInfo(info *ObjectInfo, headers http.Header) (apiErr APIErrorCode, encrypted bool) {
 | 
						|
	// Directories are never encrypted.
 | 
						|
	if info.IsDir {
 | 
						|
		return ErrNone, false
 | 
						|
	}
 | 
						|
	if apiErr, encrypted = ErrNone, info.IsEncrypted(); !encrypted && hasSSECopyCustomerHeader(headers) {
 | 
						|
		apiErr = ErrInvalidEncryptionParameters
 | 
						|
	} else if encrypted {
 | 
						|
		if !hasSSECopyCustomerHeader(headers) {
 | 
						|
			apiErr = ErrSSEEncryptedObject
 | 
						|
			return
 | 
						|
		}
 | 
						|
		var err error
 | 
						|
		if info.Size, err = info.DecryptedSize(); err != nil {
 | 
						|
			apiErr = toAPIErrorCode(err)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return
 | 
						|
}
 | 
						|
 | 
						|
// DecryptObjectInfo tries to decrypt the provided object if it is encrypted.
 | 
						|
// It fails if the object is encrypted and the HTTP headers don't contain
 | 
						|
// SSE-C headers or the object is not encrypted but SSE-C headers are provided. (AWS behavior)
 | 
						|
// DecryptObjectInfo returns 'ErrNone' if the object is not encrypted or the
 | 
						|
// decryption succeeded.
 | 
						|
//
 | 
						|
// DecryptObjectInfo also returns whether the object is encrypted or not.
 | 
						|
func DecryptObjectInfo(info *ObjectInfo, headers http.Header) (apiErr APIErrorCode, encrypted bool) {
 | 
						|
	// Directories are never encrypted.
 | 
						|
	if info.IsDir {
 | 
						|
		return ErrNone, false
 | 
						|
	}
 | 
						|
	if apiErr, encrypted = ErrNone, info.IsEncrypted(); !encrypted && hasSSECustomerHeader(headers) {
 | 
						|
		apiErr = ErrInvalidEncryptionParameters
 | 
						|
	} else if encrypted {
 | 
						|
		if !hasSSECustomerHeader(headers) {
 | 
						|
			apiErr = ErrSSEEncryptedObject
 | 
						|
			return
 | 
						|
		}
 | 
						|
		var err error
 | 
						|
		if info.Size, err = info.DecryptedSize(); err != nil {
 | 
						|
			apiErr = toAPIErrorCode(err)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return
 | 
						|
}
 |