@ -28,6 +28,7 @@ import (
"errors"
"io"
"net/http"
"path"
"strconv"
"github.com/minio/minio/cmd/logger"
@ -144,9 +145,20 @@ const (
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"
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.
// It is considered legacy and should not be used anymore.
SSESealAlgorithmDareSha256 = "DARE-SHA256"
// 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.
@ -250,10 +262,11 @@ func ParseSSECustomerHeader(header http.Header) (key []byte, err error) {
}
// This function rotates old to new key.
func rotateKey ( oldKey [ ] byte , newKey [ ] byte , metadata map [ string ] string ) error {
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
if metadata [ ServerSideEncryptionSealAlgorithm ] != SSESealAlgorithmDareSha256 { // currently DARE-SHA256 is the only option
algorithm := metadata [ ServerSideEncryptionSealAlgorithm ]
if algorithm != SSESealAlgorithmDareSha256 && algorithm != SSESealAlgorithmDareV2HmacSha256 {
return errObjectTampered
}
iv , err := base64 . StdEncoding . DecodeString ( metadata [ ServerSideEncryptionIV ] )
@ -265,14 +278,33 @@ func rotateKey(oldKey []byte, newKey []byte, metadata map[string]string) error {
return errObjectTampered
}
sha := sha256 . New ( ) // derive key encryption key
var (
minDAREVersion byte
keyEncryptionKey [ 32 ] byte
)
switch algorithm {
default :
return errObjectTampered
case SSESealAlgorithmDareSha256 : // legacy key-encryption-key derivation
minDAREVersion = sio . Version10
sha := sha256 . New ( )
sha . Write ( oldKey )
sha . Write ( iv )
keyEncryptionKey := sha . Sum ( nil )
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 {
Key : keyEncryptionKey ,
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 {
@ -280,46 +312,34 @@ func rotateKey(oldKey []byte, newKey []byte, metadata map[string]string) error {
}
return errSSEKeyMismatch // To provide strict AWS S3 compatibility we return: access denied.
}
if subtle . ConstantTimeCompare ( oldKey , newKey ) == 1 {
return nil // we don't need to rotate keys if newKey == oldKey
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
}
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 )
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 ] )
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 ,
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 ( n iv[ : ] )
metadata [ ServerSideEncryptionSealAlgorithm ] = SSESealAlgorithmDareSha256
metadata [ ServerSideEncryptionIV ] = base64 . StdEncoding . EncodeToString ( iv [ : ] )
metadata [ ServerSideEncryptionSealAlgorithm ] = SSESealAlgorithmDareV2Hmac Sha256
metadata [ ServerSideEncryptionSealedKey ] = base64 . StdEncoding . EncodeToString ( sealedKeyW . Bytes ( ) )
return nil
}
func newEncryptMetadata ( key [ ] byte , metadata map [ string ] string ) ( [ ] byte , error ) {
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
// 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) ]
// 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
@ -329,11 +349,13 @@ func newEncryptMetadata(key []byte, metadata map[string]string) ([]byte, error)
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 )
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 {
@ -344,14 +366,14 @@ func newEncryptMetadata(key []byte, metadata map[string]string) ([]byte, error)
}
metadata [ ServerSideEncryptionIV ] = base64 . StdEncoding . EncodeToString ( iv [ : ] )
metadata [ ServerSideEncryptionSealAlgorithm ] = SSESealAlgorithmDareSha256
metadata [ ServerSideEncryptionSealAlgorithm ] = SSESealAlgorithmDareV2Hmac Sha256
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 )
func newEncryptReader ( content io . Reader , key [ ] byte , bucket , object string , metadata map [ string ] string ) ( io . Reader , error ) {
objectEncryptionKey , err := newEncryptMetadata ( key , bucket , object , metadata )
if err != nil {
return nil , err
}
@ -367,29 +389,26 @@ func newEncryptReader(content io.Reader, key []byte, metadata map[string]string)
// 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 ) {
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
}
return newEncryptReader ( content , key , metadata )
return newEncryptReader ( content , key , bucket , object , 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 ) {
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
}
delete ( metadata , SSECopyCustomerKey ) // make sure we do not save the key by accident
return newDecryptWriter ( client , key , 0 , metadata )
return newDecryptWriter ( client , key , bucket , object , 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
}
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
@ -399,14 +418,33 @@ func decryptObjectInfo(key []byte, metadata map[string]string) ([]byte, error) {
return nil , errObjectTampered
}
sha := sha256 . New ( ) // derive key encryption key
var (
minDAREVersion byte
keyEncryptionKey [ 32 ] byte
)
switch algorithm := metadata [ ServerSideEncryptionSealAlgorithm ] ; algorithm {
default :
return nil , errObjectTampered
case SSESealAlgorithmDareSha256 : // legacy key-encryption-key derivation
minDAREVersion = sio . Version10
sha := sha256 . New ( )
sha . Write ( key )
sha . Write ( iv )
keyEncryptionKey := sha . Sum ( nil )
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 ] )
}
objectEncryptionKey := bytes . NewBuffer ( nil ) // decrypt object encryption key
n , err := sio . Decrypt ( objectEncryptionKey , bytes . NewReader ( sealedKey ) , sio . Config {
Key : keyEncryptionKey ,
MinVersion : minDAREVersion ,
Key : keyEncryptionKey [ : ] ,
} )
if n != 32 || err != nil {
// Either the provided key does not match or the object was tampered.
@ -416,11 +454,10 @@ func decryptObjectInfo(key []byte, metadata map[string]string) ([]byte, error) {
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 )
func newDecryptWriter ( client io . Writer , key [ ] byte , bucket , object string , seqNumber uint32 , metadata map [ string ] string ) ( io . WriteCloser , error ) {
objectEncryptionKey , err := decryptObjectInfo ( key , bucket , object , metadata )
if err != nil {
return nil , err
}
return newDecryptWriterWithObjectKey ( client , objectEncryptionKey , seqNumber , metadata )
}
@ -443,19 +480,19 @@ func newDecryptWriterWithObjectKey(client io.Writer, objectEncryptionKey []byte,
// 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 ) {
func DecryptRequestWithSequenceNumber ( client io . Writer , r * http . Request , bucket , object string , 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 )
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.
func DecryptRequest ( client io . Writer , r * http . Request , metadata map [ string ] string ) ( io . WriteCloser , error ) {
return DecryptRequestWithSequenceNumber ( client , r , 0 , metadata )
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 )
}
// DecryptBlocksWriter - decrypts multipart parts, while implementing a io.Writer compatible interface.
@ -471,6 +508,7 @@ type DecryptBlocksWriter struct {
// Parts information
parts [ ] objectPartInfo
req * http . Request
bucket , object string
metadata map [ string ] string
partEncRelOffset int64
@ -499,7 +537,7 @@ func (w *DecryptBlocksWriter) buildDecrypter(partID int) error {
return err
}
objectEncryptionKey , err := decryptObjectInfo ( key , m )
objectEncryptionKey , err := decryptObjectInfo ( key , w . bucket , w . object , m )
if err != nil {
return err
}
@ -594,14 +632,14 @@ func (w *DecryptBlocksWriter) Close() error {
// 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 )
func DecryptAllBlocksCopyRequest ( client io . Writer , r * http . Request , bucket , object string , objInfo ObjectInfo ) ( io . WriteCloser , int64 , error ) {
w , _ , size , err := DecryptBlocksRequest ( client , r , bucket , object , 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 ) {
func DecryptBlocksRequest ( client io . Writer , r * http . Request , bucket , object string , 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
@ -614,9 +652,9 @@ func DecryptBlocksRequest(client io.Writer, r *http.Request, startOffset, length
var writer io . WriteCloser
var err error
if copySource {
writer , err = DecryptCopyRequest ( client , r , objInfo . UserDefined )
writer , err = DecryptCopyRequest ( client , r , bucket , object , objInfo . UserDefined )
} else {
writer , err = DecryptRequestWithSequenceNumber ( client , r , seqNumber , objInfo . UserDefined )
writer , err = DecryptRequestWithSequenceNumber ( client , r , bucket , object , seqNumber , objInfo . UserDefined )
}
if err != nil {
return nil , 0 , 0 , err
@ -656,6 +694,8 @@ func DecryptBlocksRequest(client io.Writer, r *http.Request, startOffset, length
parts : objInfo . Parts ,
partIndex : partStartIndex ,
req : r ,
bucket : bucket ,
object : object ,
customerKeyHeader : r . Header . Get ( SSECustomerKey ) ,
copySource : copySource ,
}