2021-04-18 15:41:13 -04:00
// Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
2020-12-22 12:19:32 -05:00
package crypto
import (
"context"
"encoding/base64"
"errors"
"net/http"
"path"
"strings"
jsoniter "github.com/json-iterator/go"
2021-06-01 17:59:40 -04:00
xhttp "github.com/minio/minio/internal/http"
"github.com/minio/minio/internal/kms"
"github.com/minio/minio/internal/logger"
2020-12-22 12:19:32 -05:00
)
type ssekms struct { }
var (
// S3KMS represents AWS SSE-KMS. It provides functionality to
// handle SSE-KMS requests.
S3KMS = ssekms { }
_ Type = S3KMS
)
// String returns the SSE domain as string. For SSE-KMS the
// domain is "SSE-KMS".
func ( ssekms ) String ( ) string { return "SSE-KMS" }
// IsRequested returns true if the HTTP headers contains
// at least one SSE-KMS header.
func ( ssekms ) IsRequested ( h http . Header ) bool {
if _ , ok := h [ xhttp . AmzServerSideEncryptionKmsID ] ; ok {
return true
}
if _ , ok := h [ xhttp . AmzServerSideEncryptionKmsContext ] ; ok {
return true
}
if _ , ok := h [ xhttp . AmzServerSideEncryption ] ; ok {
return strings . ToUpper ( h . Get ( xhttp . AmzServerSideEncryption ) ) != xhttp . AmzEncryptionAES // Return only true if the SSE header is specified and does not contain the SSE-S3 value
}
return false
}
// ParseHTTP parses the SSE-KMS headers and returns the SSE-KMS key ID
// and the KMS context on success.
2021-05-10 21:15:11 -04:00
func ( ssekms ) ParseHTTP ( h http . Header ) ( string , kms . Context , error ) {
2020-12-22 12:19:32 -05:00
algorithm := h . Get ( xhttp . AmzServerSideEncryption )
if algorithm != xhttp . AmzEncryptionKMS {
return "" , nil , ErrInvalidEncryptionMethod
}
2021-05-10 21:15:11 -04:00
var ctx kms . Context
2020-12-22 12:19:32 -05:00
if context , ok := h [ xhttp . AmzServerSideEncryptionKmsContext ] ; ok {
2021-05-06 18:24:01 -04:00
b , err := base64 . StdEncoding . DecodeString ( context [ 0 ] )
if err != nil {
return "" , nil , err
}
2020-12-22 12:19:32 -05:00
var json = jsoniter . ConfigCompatibleWithStandardLibrary
2021-05-06 18:24:01 -04:00
if err := json . Unmarshal ( b , & ctx ) ; err != nil {
2020-12-22 12:19:32 -05:00
return "" , nil , err
}
}
return h . Get ( xhttp . AmzServerSideEncryptionKmsID ) , ctx , nil
}
// IsEncrypted returns true if the object metadata indicates
// that the object was uploaded using SSE-KMS.
func ( ssekms ) IsEncrypted ( metadata map [ string ] string ) bool {
if _ , ok := metadata [ MetaSealedKeyKMS ] ; ok {
return true
}
return false
}
// UnsealObjectKey extracts and decrypts the sealed object key
// from the metadata using KMS and returns the decrypted object
// key.
2021-05-07 12:16:49 -04:00
func ( s3 ssekms ) UnsealObjectKey ( KMS kms . KMS , metadata map [ string ] string , bucket , object string ) ( key ObjectKey , err error ) {
2021-02-03 18:19:08 -05:00
keyID , kmsKey , sealedKey , ctx , err := s3 . ParseMetadata ( metadata )
2020-12-22 12:19:32 -05:00
if err != nil {
return key , err
}
2021-05-07 12:16:49 -04:00
if ctx == nil {
ctx = kms . Context { bucket : path . Join ( bucket , object ) }
} else if _ , ok := ctx [ bucket ] ; ! ok {
2021-02-03 18:19:08 -05:00
ctx [ bucket ] = path . Join ( bucket , object )
}
2021-05-07 12:16:49 -04:00
unsealKey , err := KMS . DecryptKey ( keyID , kmsKey , ctx )
2020-12-22 12:19:32 -05:00
if err != nil {
return key , err
}
2021-11-16 12:28:29 -05:00
err = key . Unseal ( unsealKey , sealedKey , s3 . String ( ) , bucket , object )
2020-12-22 12:19:32 -05:00
return key , err
}
// CreateMetadata encodes the sealed object key into the metadata and returns
// the modified metadata. If the keyID and the kmsKey is not empty it encodes
// both into the metadata as well. It allocates a new metadata map if metadata
// is nil.
2021-05-10 21:15:11 -04:00
func ( ssekms ) CreateMetadata ( metadata map [ string ] string , keyID string , kmsKey [ ] byte , sealedKey SealedKey , ctx kms . Context ) map [ string ] string {
2020-12-22 12:19:32 -05:00
if sealedKey . Algorithm != SealAlgorithm {
logger . CriticalIf ( context . Background ( ) , Errorf ( "The seal algorithm '%s' is invalid for SSE-S3" , sealedKey . Algorithm ) )
}
// There are two possibilites:
// - We use a KMS -> There must be non-empty key ID and a KMS data key.
// - We use a K/V -> There must be no key ID and no KMS data key.
// Otherwise, the caller has passed an invalid argument combination.
if keyID == "" && len ( kmsKey ) != 0 {
logger . CriticalIf ( context . Background ( ) , errors . New ( "The key ID must not be empty if a KMS data key is present" ) )
}
if keyID != "" && len ( kmsKey ) == 0 {
logger . CriticalIf ( context . Background ( ) , errors . New ( "The KMS data key must not be empty if a key ID is present" ) )
}
if metadata == nil {
metadata = make ( map [ string ] string , 5 )
}
metadata [ MetaAlgorithm ] = sealedKey . Algorithm
metadata [ MetaIV ] = base64 . StdEncoding . EncodeToString ( sealedKey . IV [ : ] )
metadata [ MetaSealedKeyKMS ] = base64 . StdEncoding . EncodeToString ( sealedKey . Key [ : ] )
2021-05-06 18:24:01 -04:00
if len ( ctx ) > 0 {
b , _ := ctx . MarshalText ( )
metadata [ MetaContext ] = base64 . StdEncoding . EncodeToString ( b )
}
2020-12-22 12:19:32 -05:00
if len ( kmsKey ) > 0 && keyID != "" { // We use a KMS -> Store key ID and sealed KMS data key.
metadata [ MetaKeyID ] = keyID
metadata [ MetaDataEncryptionKey ] = base64 . StdEncoding . EncodeToString ( kmsKey )
}
return metadata
}
// ParseMetadata extracts all SSE-KMS related values from the object metadata
// and checks whether they are well-formed. It returns the sealed object key
// on success. If the metadata contains both, a KMS master key ID and a sealed
// KMS data key it returns both. If the metadata does not contain neither a
// KMS master key ID nor a sealed KMS data key it returns an empty keyID and
// KMS data key. Otherwise, it returns an error.
2021-05-10 21:15:11 -04:00
func ( ssekms ) ParseMetadata ( metadata map [ string ] string ) ( keyID string , kmsKey [ ] byte , sealedKey SealedKey , ctx kms . Context , err error ) {
2020-12-22 12:19:32 -05:00
// Extract all required values from object metadata
b64IV , ok := metadata [ MetaIV ]
if ! ok {
2021-02-03 18:19:08 -05:00
return keyID , kmsKey , sealedKey , ctx , errMissingInternalIV
2020-12-22 12:19:32 -05:00
}
algorithm , ok := metadata [ MetaAlgorithm ]
if ! ok {
2021-02-03 18:19:08 -05:00
return keyID , kmsKey , sealedKey , ctx , errMissingInternalSealAlgorithm
2020-12-22 12:19:32 -05:00
}
b64SealedKey , ok := metadata [ MetaSealedKeyKMS ]
if ! ok {
2021-02-03 18:19:08 -05:00
return keyID , kmsKey , sealedKey , ctx , Errorf ( "The object metadata is missing the internal sealed key for SSE-S3" )
2020-12-22 12:19:32 -05:00
}
// There are two possibilites:
// - We use a KMS -> There must be a key ID and a KMS data key.
// - We use a K/V -> There must be no key ID and no KMS data key.
// Otherwise, the metadata is corrupted.
keyID , idPresent := metadata [ MetaKeyID ]
b64KMSSealedKey , kmsKeyPresent := metadata [ MetaDataEncryptionKey ]
if ! idPresent && kmsKeyPresent {
2021-02-03 18:19:08 -05:00
return keyID , kmsKey , sealedKey , ctx , Errorf ( "The object metadata is missing the internal KMS key-ID for SSE-S3" )
2020-12-22 12:19:32 -05:00
}
if idPresent && ! kmsKeyPresent {
2021-02-03 18:19:08 -05:00
return keyID , kmsKey , sealedKey , ctx , Errorf ( "The object metadata is missing the internal sealed KMS data key for SSE-S3" )
2020-12-22 12:19:32 -05:00
}
// Check whether all extracted values are well-formed
iv , err := base64 . StdEncoding . DecodeString ( b64IV )
if err != nil || len ( iv ) != 32 {
2021-02-03 18:19:08 -05:00
return keyID , kmsKey , sealedKey , ctx , errInvalidInternalIV
2020-12-22 12:19:32 -05:00
}
if algorithm != SealAlgorithm {
2021-02-03 18:19:08 -05:00
return keyID , kmsKey , sealedKey , ctx , errInvalidInternalSealAlgorithm
2020-12-22 12:19:32 -05:00
}
encryptedKey , err := base64 . StdEncoding . DecodeString ( b64SealedKey )
if err != nil || len ( encryptedKey ) != 64 {
2021-02-03 18:19:08 -05:00
return keyID , kmsKey , sealedKey , ctx , Errorf ( "The internal sealed key for SSE-KMS is invalid" )
2020-12-22 12:19:32 -05:00
}
if idPresent && kmsKeyPresent { // We are using a KMS -> parse the sealed KMS data key.
kmsKey , err = base64 . StdEncoding . DecodeString ( b64KMSSealedKey )
if err != nil {
2021-02-03 18:19:08 -05:00
return keyID , kmsKey , sealedKey , ctx , Errorf ( "The internal sealed KMS data key for SSE-KMS is invalid" )
}
}
b64Ctx , ok := metadata [ MetaContext ]
if ok {
b , err := base64 . StdEncoding . DecodeString ( b64Ctx )
if err != nil {
return keyID , kmsKey , sealedKey , ctx , Errorf ( "The internal KMS context is not base64-encoded" )
}
var json = jsoniter . ConfigCompatibleWithStandardLibrary
2021-09-14 15:52:46 -04:00
if err = json . Unmarshal ( b , & ctx ) ; err != nil {
return keyID , kmsKey , sealedKey , ctx , Errorf ( "The internal sealed KMS context is invalid %w" , err )
2020-12-22 12:19:32 -05:00
}
}
sealedKey . Algorithm = algorithm
copy ( sealedKey . IV [ : ] , iv )
copy ( sealedKey . Key [ : ] , encryptedKey )
2021-02-03 18:19:08 -05:00
return keyID , kmsKey , sealedKey , ctx , nil
2020-12-22 12:19:32 -05:00
}