github.com/minio/mc@v0.0.0-20240503112107-b471de8d1882/cmd/encryption-methods.go (about)

     1  // Copyright (c) 2015-2024 MinIO, Inc.
     2  //
     3  // # This file is part of MinIO Object Storage stack
     4  //
     5  // This program is free software: you can redistribute it and/or modify
     6  // it under the terms of the GNU Affero General Public License as published by
     7  // the Free Software Foundation, either version 3 of the License, or
     8  // (at your option) any later version.
     9  //
    10  // This program is distributed in the hope that it will be useful
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13  // GNU Affero General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Affero General Public License
    16  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17  
    18  package cmd
    19  
    20  import (
    21  	"encoding/base64"
    22  	"sort"
    23  	"strconv"
    24  	"strings"
    25  
    26  	"github.com/minio/cli"
    27  	"github.com/minio/mc/pkg/probe"
    28  	"github.com/minio/minio-go/v7/pkg/encrypt"
    29  )
    30  
    31  type sseKeyType int
    32  
    33  const (
    34  	sseNone sseKeyType = iota
    35  	sseC
    36  	sseKMS
    37  	sseS3
    38  )
    39  
    40  // struct representing object prefix and sse keys association.
    41  type prefixSSEPair struct {
    42  	Prefix string
    43  	SSE    encrypt.ServerSide
    44  }
    45  
    46  // byPrefixLength implements sort.Interface.
    47  type byPrefixLength []prefixSSEPair
    48  
    49  func (p byPrefixLength) Len() int { return len(p) }
    50  func (p byPrefixLength) Less(i, j int) bool {
    51  	if len(p[i].Prefix) != len(p[j].Prefix) {
    52  		return len(p[i].Prefix) > len(p[j].Prefix)
    53  	}
    54  	return p[i].Prefix < p[j].Prefix
    55  }
    56  
    57  func (p byPrefixLength) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
    58  
    59  // get SSE Key if object prefix matches with given resource.
    60  func getSSE(resource string, encKeys []prefixSSEPair) encrypt.ServerSide {
    61  	for _, k := range encKeys {
    62  		if strings.HasPrefix(resource, k.Prefix) {
    63  			return k.SSE
    64  		}
    65  	}
    66  	return nil
    67  }
    68  
    69  func validateAndCreateEncryptionKeys(ctx *cli.Context) (encMap map[string][]prefixSSEPair, err *probe.Error) {
    70  	encMap = make(map[string][]prefixSSEPair, 0)
    71  
    72  	for _, v := range ctx.StringSlice("enc-kms") {
    73  		prefixPair, alias, err := validateAndParseKey(ctx, v, sseKMS)
    74  		if err != nil {
    75  			return nil, err
    76  		}
    77  		encMap[alias] = append(encMap[alias], *prefixPair)
    78  	}
    79  
    80  	for _, v := range ctx.StringSlice("enc-s3") {
    81  		prefixPair, alias, err := validateAndParseKey(ctx, v, sseS3)
    82  		if err != nil {
    83  			return nil, err
    84  		}
    85  		encMap[alias] = append(encMap[alias], *prefixPair)
    86  	}
    87  
    88  	for _, v := range ctx.StringSlice("enc-c") {
    89  		prefixPair, alias, err := validateAndParseKey(ctx, v, sseC)
    90  		if err != nil {
    91  			return nil, err
    92  		}
    93  		encMap[alias] = append(encMap[alias], *prefixPair)
    94  	}
    95  
    96  	for i := range encMap {
    97  		err = validateOverLappingSSEKeys(encMap[i])
    98  		if err != nil {
    99  			return nil, err
   100  		}
   101  	}
   102  
   103  	for alias, ps := range encMap {
   104  		if hostCfg := mustGetHostConfig(alias); hostCfg == nil {
   105  			for _, p := range ps {
   106  				return nil, errSSEInvalidAlias(p.Prefix)
   107  			}
   108  		}
   109  	}
   110  
   111  	for _, encKeys := range encMap {
   112  		sort.Sort(byPrefixLength(encKeys))
   113  	}
   114  
   115  	return encMap, nil
   116  }
   117  
   118  func validateAndParseKey(ctx *cli.Context, key string, keyType sseKeyType) (SSEPair *prefixSSEPair, alias string, perr *probe.Error) {
   119  	matchedCount := 0
   120  	alias, prefix, encKey, keyErr := parseSSEKey(key, keyType)
   121  	if keyErr != nil {
   122  		return nil, "", keyErr
   123  	}
   124  	if alias == "" {
   125  		return nil, "", errSSEInvalidAlias(prefix).Trace(key)
   126  	}
   127  
   128  	if (keyType == sseKMS || keyType == sseC) && encKey == "" {
   129  		return nil, "", errSSEClientKeyFormat("SSE-C/KMS key should be of the form alias/prefix=key,... ").Trace(key)
   130  	}
   131  
   132  	ssePairPrefix := alias + "/" + prefix
   133  
   134  	for _, arg := range ctx.Args() {
   135  		if strings.HasPrefix(arg, ssePairPrefix) {
   136  			matchedCount++
   137  		} else if strings.HasPrefix(ssePairPrefix, arg) {
   138  			matchedCount++
   139  		}
   140  	}
   141  
   142  	if matchedCount == 0 {
   143  		return nil, "", errSSEPrefixMatch()
   144  	}
   145  
   146  	var sse encrypt.ServerSide
   147  	var err error
   148  
   149  	switch keyType {
   150  	case sseC:
   151  		sse, err = encrypt.NewSSEC([]byte(encKey))
   152  	case sseKMS:
   153  		sse, err = encrypt.NewSSEKMS(encKey, nil)
   154  	case sseS3:
   155  		sse = encrypt.NewSSE()
   156  	}
   157  
   158  	if err != nil {
   159  		return nil, "", probe.NewError(err).Trace(key)
   160  	}
   161  
   162  	return &prefixSSEPair{
   163  		Prefix: ssePairPrefix,
   164  		SSE:    sse,
   165  	}, alias, nil
   166  }
   167  
   168  func validateOverLappingSSEKeys(keyMap []prefixSSEPair) (err *probe.Error) {
   169  	for i := 0; i < len(keyMap); i++ {
   170  		for j := i + 1; j < len(keyMap); j++ {
   171  			if strings.HasPrefix(keyMap[i].Prefix, keyMap[j].Prefix) ||
   172  				strings.HasPrefix(keyMap[j].Prefix, keyMap[i].Prefix) {
   173  				return errSSEOverlappingAlias(keyMap[i].Prefix, keyMap[j].Prefix)
   174  			}
   175  		}
   176  	}
   177  	return
   178  }
   179  
   180  func splitKey(sseKey string) (alias, prefix string) {
   181  	x := strings.SplitN(sseKey, "/", 2)
   182  	switch len(x) {
   183  	case 2:
   184  		return x[0], x[1]
   185  	case 1:
   186  		return x[0], ""
   187  	}
   188  	return "", ""
   189  }
   190  
   191  func parseSSEKey(sseKey string, keyType sseKeyType) (
   192  	alias string,
   193  	prefix string,
   194  	key string,
   195  	err *probe.Error,
   196  ) {
   197  	if keyType == sseS3 {
   198  		alias, prefix = splitKey(sseKey)
   199  		return
   200  	}
   201  
   202  	var path string
   203  	alias, path = splitKey(sseKey)
   204  	splitPath := strings.Split(path, "=")
   205  	if len(splitPath) == 0 {
   206  		err = errSSEKeyMissing().Trace(sseKey)
   207  		return
   208  	}
   209  
   210  	aliasPlusPrefix := strings.Join(splitPath[:len(splitPath)-1], "=")
   211  	prefix = strings.Replace(aliasPlusPrefix, alias+"/", "", 1)
   212  	key = splitPath[len(splitPath)-1]
   213  
   214  	if keyType == sseC {
   215  		keyB, de := base64.RawStdEncoding.DecodeString(key)
   216  		if de != nil {
   217  			err = errSSEClientKeyFormat("One of the inserted keys was " + strconv.Itoa(len(key)) + " bytes and did not have valid base64 raw encoding.").Trace(sseKey)
   218  			return
   219  		}
   220  		key = string(keyB)
   221  		if len(key) != 32 {
   222  			err = errSSEClientKeyFormat("The plain text key was " + strconv.Itoa(len(key)) + " bytes but should be 32 bytes long").Trace(sseKey)
   223  			return
   224  		}
   225  	}
   226  
   227  	if keyType == sseKMS {
   228  		if !validKMSKeyName(key) {
   229  			err = errSSEKMSKeyFormat("One of the inserted keys was " + strconv.Itoa(len(key)) + " bytes and did not have a valid KMS key name.").Trace(sseKey)
   230  			return
   231  		}
   232  	}
   233  
   234  	return
   235  }
   236  
   237  func validKMSKeyName(s string) bool {
   238  	if s == "" || s == "_" {
   239  		return false
   240  	}
   241  
   242  	n := len(s) - 1
   243  	for i, r := range s {
   244  		switch {
   245  		case r >= '0' && r <= '9':
   246  		case r >= 'A' && r <= 'Z':
   247  		case r >= 'a' && r <= 'z':
   248  		case r == '-' && i > 0 && i < n:
   249  		case r == '_':
   250  		default:
   251  			return false
   252  		}
   253  	}
   254  	return true
   255  }