github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/internal/kms/single-key.go (about) 1 // Copyright (c) 2015-2021 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 kms 19 20 import ( 21 "context" 22 "crypto/aes" 23 "crypto/cipher" 24 "crypto/hmac" 25 "encoding/base64" 26 "errors" 27 "fmt" 28 "net/http" 29 "strconv" 30 "strings" 31 32 jsoniter "github.com/json-iterator/go" 33 "github.com/secure-io/sio-go/sioutil" 34 "golang.org/x/crypto/chacha20" 35 "golang.org/x/crypto/chacha20poly1305" 36 37 "github.com/minio/kms-go/kes" 38 "github.com/minio/minio/internal/hash/sha256" 39 ) 40 41 // Parse parses s as single-key KMS. The given string 42 // is expected to have the following format: 43 // 44 // <key-id>:<base64-key> 45 // 46 // The returned KMS implementation uses the parsed 47 // key ID and key to derive new DEKs and decrypt ciphertext. 48 func Parse(s string) (KMS, error) { 49 v := strings.SplitN(s, ":", 2) 50 if len(v) != 2 { 51 return nil, errors.New("kms: invalid master key format") 52 } 53 54 keyID, b64Key := v[0], v[1] 55 key, err := base64.StdEncoding.DecodeString(b64Key) 56 if err != nil { 57 return nil, err 58 } 59 return New(keyID, key) 60 } 61 62 // New returns a single-key KMS that derives new DEKs from the 63 // given key. 64 func New(keyID string, key []byte) (KMS, error) { 65 if len(key) != 32 { 66 return nil, errors.New("kms: invalid key length " + strconv.Itoa(len(key))) 67 } 68 return secretKey{ 69 keyID: keyID, 70 key: key, 71 }, nil 72 } 73 74 // secretKey is a KMS implementation that derives new DEKs 75 // from a single key. 76 type secretKey struct { 77 keyID string 78 key []byte 79 } 80 81 var _ KMS = secretKey{} // compiler check 82 83 const ( // algorithms used to derive and encrypt DEKs 84 algorithmAESGCM = "AES-256-GCM-HMAC-SHA-256" 85 algorithmChaCha20Poly1305 = "ChaCha20Poly1305" 86 ) 87 88 func (kms secretKey) Stat(context.Context) (Status, error) { 89 return Status{ 90 Name: "SecretKey", 91 DefaultKey: kms.keyID, 92 }, nil 93 } 94 95 // IsLocal returns true if the KMS is a local implementation 96 func (kms secretKey) IsLocal() bool { 97 return true 98 } 99 100 // List returns an array of local KMS Names 101 func (kms secretKey) List() []kes.KeyInfo { 102 kmsSecret := []kes.KeyInfo{ 103 { 104 Name: kms.keyID, 105 }, 106 } 107 return kmsSecret 108 } 109 110 func (secretKey) Metrics(ctx context.Context) (kes.Metric, error) { 111 return kes.Metric{}, Error{ 112 HTTPStatusCode: http.StatusNotImplemented, 113 APICode: "KMS.NotImplemented", 114 Err: errors.New("metrics are not supported"), 115 } 116 } 117 118 func (kms secretKey) CreateKey(_ context.Context, keyID string) error { 119 if keyID == kms.keyID { 120 return nil 121 } 122 return Error{ 123 HTTPStatusCode: http.StatusNotImplemented, 124 APICode: "KMS.NotImplemented", 125 Err: fmt.Errorf("creating custom key %q is not supported", keyID), 126 } 127 } 128 129 func (kms secretKey) GenerateKey(_ context.Context, keyID string, context Context) (DEK, error) { 130 if keyID == "" { 131 keyID = kms.keyID 132 } 133 if keyID != kms.keyID { 134 return DEK{}, Error{ 135 HTTPStatusCode: http.StatusBadRequest, 136 APICode: "KMS.NotFoundException", 137 Err: fmt.Errorf("key %q does not exist", keyID), 138 } 139 } 140 iv, err := sioutil.Random(16) 141 if err != nil { 142 return DEK{}, err 143 } 144 145 var algorithm string 146 if sioutil.NativeAES() { 147 algorithm = algorithmAESGCM 148 } else { 149 algorithm = algorithmChaCha20Poly1305 150 } 151 152 var aead cipher.AEAD 153 switch algorithm { 154 case algorithmAESGCM: 155 mac := hmac.New(sha256.New, kms.key) 156 mac.Write(iv) 157 sealingKey := mac.Sum(nil) 158 159 var block cipher.Block 160 block, err = aes.NewCipher(sealingKey) 161 if err != nil { 162 return DEK{}, err 163 } 164 aead, err = cipher.NewGCM(block) 165 if err != nil { 166 return DEK{}, err 167 } 168 case algorithmChaCha20Poly1305: 169 var sealingKey []byte 170 sealingKey, err = chacha20.HChaCha20(kms.key, iv) 171 if err != nil { 172 return DEK{}, err 173 } 174 aead, err = chacha20poly1305.New(sealingKey) 175 if err != nil { 176 return DEK{}, err 177 } 178 default: 179 return DEK{}, Error{ 180 HTTPStatusCode: http.StatusBadRequest, 181 APICode: "KMS.InternalException", 182 Err: errors.New("invalid algorithm: " + algorithm), 183 } 184 } 185 186 nonce, err := sioutil.Random(aead.NonceSize()) 187 if err != nil { 188 return DEK{}, err 189 } 190 191 plaintext, err := sioutil.Random(32) 192 if err != nil { 193 return DEK{}, err 194 } 195 associatedData, _ := context.MarshalText() 196 ciphertext := aead.Seal(nil, nonce, plaintext, associatedData) 197 198 json := jsoniter.ConfigCompatibleWithStandardLibrary 199 ciphertext, err = json.Marshal(encryptedKey{ 200 Algorithm: algorithm, 201 IV: iv, 202 Nonce: nonce, 203 Bytes: ciphertext, 204 }) 205 if err != nil { 206 return DEK{}, err 207 } 208 return DEK{ 209 KeyID: keyID, 210 Plaintext: plaintext, 211 Ciphertext: ciphertext, 212 }, nil 213 } 214 215 func (kms secretKey) DecryptKey(keyID string, ciphertext []byte, context Context) ([]byte, error) { 216 if keyID != kms.keyID { 217 return nil, Error{ 218 HTTPStatusCode: http.StatusBadRequest, 219 APICode: "KMS.NotFoundException", 220 Err: fmt.Errorf("key %q does not exist", keyID), 221 } 222 } 223 224 var encryptedKey encryptedKey 225 json := jsoniter.ConfigCompatibleWithStandardLibrary 226 if err := json.Unmarshal(ciphertext, &encryptedKey); err != nil { 227 return nil, Error{ 228 HTTPStatusCode: http.StatusBadRequest, 229 APICode: "KMS.InternalException", 230 Err: err, 231 } 232 } 233 234 if n := len(encryptedKey.IV); n != 16 { 235 return nil, Error{ 236 HTTPStatusCode: http.StatusBadRequest, 237 APICode: "KMS.InternalException", 238 Err: fmt.Errorf("invalid iv size: %d", n), 239 } 240 } 241 242 var aead cipher.AEAD 243 switch encryptedKey.Algorithm { 244 case algorithmAESGCM: 245 mac := hmac.New(sha256.New, kms.key) 246 mac.Write(encryptedKey.IV) 247 sealingKey := mac.Sum(nil) 248 249 block, err := aes.NewCipher(sealingKey) 250 if err != nil { 251 return nil, err 252 } 253 aead, err = cipher.NewGCM(block) 254 if err != nil { 255 return nil, err 256 } 257 case algorithmChaCha20Poly1305: 258 sealingKey, err := chacha20.HChaCha20(kms.key, encryptedKey.IV) 259 if err != nil { 260 return nil, err 261 } 262 aead, err = chacha20poly1305.New(sealingKey) 263 if err != nil { 264 return nil, err 265 } 266 default: 267 return nil, Error{ 268 HTTPStatusCode: http.StatusBadRequest, 269 APICode: "KMS.InternalException", 270 Err: fmt.Errorf("invalid algorithm: %q", encryptedKey.Algorithm), 271 } 272 } 273 274 if n := len(encryptedKey.Nonce); n != aead.NonceSize() { 275 return nil, Error{ 276 HTTPStatusCode: http.StatusBadRequest, 277 APICode: "KMS.InternalException", 278 Err: fmt.Errorf("invalid nonce size %d", n), 279 } 280 } 281 282 associatedData, _ := context.MarshalText() 283 plaintext, err := aead.Open(nil, encryptedKey.Nonce, encryptedKey.Bytes, associatedData) 284 if err != nil { 285 return nil, Error{ 286 HTTPStatusCode: http.StatusBadRequest, 287 APICode: "KMS.InternalException", 288 Err: fmt.Errorf("encrypted key is not authentic"), 289 } 290 } 291 return plaintext, nil 292 } 293 294 func (kms secretKey) DecryptAll(_ context.Context, keyID string, ciphertexts [][]byte, contexts []Context) ([][]byte, error) { 295 plaintexts := make([][]byte, 0, len(ciphertexts)) 296 for i := range ciphertexts { 297 plaintext, err := kms.DecryptKey(keyID, ciphertexts[i], contexts[i]) 298 if err != nil { 299 return nil, err 300 } 301 plaintexts = append(plaintexts, plaintext) 302 } 303 return plaintexts, nil 304 } 305 306 // Verify verifies all KMS endpoints and returns details 307 func (kms secretKey) Verify(cxt context.Context) []VerifyResult { 308 return []VerifyResult{ 309 {Endpoint: "self"}, 310 } 311 } 312 313 type encryptedKey struct { 314 Algorithm string `json:"aead"` 315 IV []byte `json:"iv"` 316 Nonce []byte `json:"nonce"` 317 Bytes []byte `json:"bytes"` 318 }