github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/internal/config/crypto.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 config 19 20 import ( 21 "bytes" 22 "context" 23 "crypto/rand" 24 "encoding/binary" 25 "errors" 26 "fmt" 27 "io" 28 29 jsoniter "github.com/json-iterator/go" 30 "github.com/minio/minio/internal/fips" 31 "github.com/minio/minio/internal/kms" 32 "github.com/secure-io/sio-go" 33 "github.com/secure-io/sio-go/sioutil" 34 ) 35 36 // EncryptBytes encrypts the plaintext with a key managed by KMS. 37 // The context is bound to the returned ciphertext. 38 // 39 // The same context must be provided when decrypting the 40 // ciphertext. 41 func EncryptBytes(k kms.KMS, plaintext []byte, context kms.Context) ([]byte, error) { 42 ciphertext, err := Encrypt(k, bytes.NewReader(plaintext), context) 43 if err != nil { 44 return nil, err 45 } 46 return io.ReadAll(ciphertext) 47 } 48 49 // DecryptBytes decrypts the ciphertext using a key managed by the KMS. 50 // The same context that have been used during encryption must be 51 // provided. 52 func DecryptBytes(k kms.KMS, ciphertext []byte, context kms.Context) ([]byte, error) { 53 plaintext, err := Decrypt(k, bytes.NewReader(ciphertext), context) 54 if err != nil { 55 return nil, err 56 } 57 return io.ReadAll(plaintext) 58 } 59 60 // Encrypt encrypts the plaintext with a key managed by KMS. 61 // The context is bound to the returned ciphertext. 62 // 63 // The same context must be provided when decrypting the 64 // ciphertext. 65 func Encrypt(k kms.KMS, plaintext io.Reader, ctx kms.Context) (io.Reader, error) { 66 algorithm := sio.AES_256_GCM 67 if !fips.Enabled && !sioutil.NativeAES() { 68 algorithm = sio.ChaCha20Poly1305 69 } 70 71 key, err := k.GenerateKey(context.Background(), "", ctx) 72 if err != nil { 73 return nil, err 74 } 75 stream, err := algorithm.Stream(key.Plaintext) 76 if err != nil { 77 return nil, err 78 } 79 nonce := make([]byte, stream.NonceSize()) 80 if _, err := rand.Read(nonce); err != nil { 81 return nil, err 82 } 83 84 const ( 85 MaxMetadataSize = 1 << 20 // max. size of the metadata 86 Version = 1 87 ) 88 var ( 89 header [5]byte 90 buffer bytes.Buffer 91 ) 92 json := jsoniter.ConfigCompatibleWithStandardLibrary 93 metadata, err := json.Marshal(encryptedObject{ 94 KeyID: key.KeyID, 95 KMSKey: key.Ciphertext, 96 Algorithm: algorithm, 97 Nonce: nonce, 98 }) 99 if err != nil { 100 return nil, err 101 } 102 if len(metadata) > MaxMetadataSize { 103 return nil, errors.New("config: encryption metadata is too large") 104 } 105 header[0] = Version 106 binary.LittleEndian.PutUint32(header[1:], uint32(len(metadata))) 107 buffer.Write(header[:]) 108 buffer.Write(metadata) 109 110 return io.MultiReader( 111 &buffer, 112 stream.EncryptReader(plaintext, nonce, nil), 113 ), nil 114 } 115 116 // Decrypt decrypts the ciphertext using a key managed by the KMS. 117 // The same context that have been used during encryption must be 118 // provided. 119 func Decrypt(k kms.KMS, ciphertext io.Reader, context kms.Context) (io.Reader, error) { 120 const ( 121 MaxMetadataSize = 1 << 20 // max. size of the metadata 122 Version = 1 123 ) 124 125 var header [5]byte 126 if _, err := io.ReadFull(ciphertext, header[:]); err != nil { 127 return nil, err 128 } 129 if header[0] != Version { 130 return nil, fmt.Errorf("config: unknown ciphertext version %d", header[0]) 131 } 132 size := binary.LittleEndian.Uint32(header[1:]) 133 if size > MaxMetadataSize { 134 return nil, errors.New("config: encryption metadata is too large") 135 } 136 137 var ( 138 metadataBuffer = make([]byte, size) 139 metadata encryptedObject 140 ) 141 if _, err := io.ReadFull(ciphertext, metadataBuffer); err != nil { 142 return nil, err 143 } 144 json := jsoniter.ConfigCompatibleWithStandardLibrary 145 if err := json.Unmarshal(metadataBuffer, &metadata); err != nil { 146 return nil, err 147 } 148 if fips.Enabled && metadata.Algorithm != sio.AES_256_GCM { 149 return nil, fmt.Errorf("config: unsupported encryption algorithm: %q is not supported in FIPS mode", metadata.Algorithm) 150 } 151 152 key, err := k.DecryptKey(metadata.KeyID, metadata.KMSKey, context) 153 if err != nil { 154 return nil, err 155 } 156 stream, err := metadata.Algorithm.Stream(key) 157 if err != nil { 158 return nil, err 159 } 160 if stream.NonceSize() != len(metadata.Nonce) { 161 return nil, sio.NotAuthentic 162 } 163 return stream.DecryptReader(ciphertext, metadata.Nonce, nil), nil 164 } 165 166 type encryptedObject struct { 167 KeyID string `json:"keyid"` 168 KMSKey []byte `json:"kmskey"` 169 170 Algorithm sio.Algorithm `json:"algorithm"` 171 Nonce []byte `json:"nonce"` 172 }