/ *
* 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 := sio . DecryptedSize ( uint64 ( part . Size ) )
if err != nil {
return nil , - 1 , - 1 , errObjectTampered
}
partStartIndex = i
// Offset is smaller than size we have reached the
// proper part offset, break out we start from
// this part index.
if partStartOffset < int64 ( decryptedSize ) {
break
}
// Continue to look for next part.
partStartOffset -= int64 ( 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
}
// 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" )
}
size , err := sio . DecryptedSize ( uint64 ( o . Size ) )
if err != nil {
err = errObjectTampered // assign correct error type
}
return int64 ( size ) , err
}
// 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 , err := sio . EncryptedSize ( uint64 ( o . Size ) )
if err != nil {
panic ( err ) // Since AWS S3 allows parts to be 5GB at most this cannot happen - sio max. size is 256 TB
}
return int64 ( 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
}