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 }