storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/pkg/kms/single-key.go (about) 1 // MinIO Cloud Storage, (C) 2021 MinIO, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package kms 16 17 import ( 18 "crypto/aes" 19 "crypto/cipher" 20 "crypto/hmac" 21 "crypto/sha256" 22 "encoding/base64" 23 "encoding/json" 24 "errors" 25 "fmt" 26 "strconv" 27 "strings" 28 29 "github.com/secure-io/sio-go/sioutil" 30 "golang.org/x/crypto/chacha20" 31 "golang.org/x/crypto/chacha20poly1305" 32 ) 33 34 // Parse parses s as single-key KMS. The given string 35 // is expected to have the following format: 36 // <key-id>:<base64-key> 37 // 38 // The returned KMS implementation uses the parsed 39 // key ID and key to derive new DEKs and decrypt ciphertext. 40 func Parse(s string) (KMS, error) { 41 v := strings.SplitN(s, ":", 2) 42 if len(v) != 2 { 43 return nil, errors.New("kms: invalid master key format") 44 } 45 46 var keyID, b64Key = v[0], v[1] 47 key, err := base64.StdEncoding.DecodeString(b64Key) 48 if err != nil { 49 return nil, err 50 } 51 return New(keyID, key) 52 } 53 54 // New returns a single-key KMS that derives new DEKs from the 55 // given key. The given key must always be 32 bytes. 56 func New(keyID string, key []byte) (KMS, error) { 57 if len(key) != 32 { 58 return nil, errors.New("kms: invalid key length " + strconv.Itoa(len(key))) 59 } 60 return secretKey{ 61 keyID: keyID, 62 key: key, 63 }, nil 64 } 65 66 // secretKey is a KMS implementation that derives new DEKs 67 // from a single key. 68 type secretKey struct { 69 keyID string 70 key []byte 71 } 72 73 var _ KMS = secretKey{} // compiler check 74 75 const ( // algorithms used to derive and encrypt DEKs 76 algorithmAESGCM = "AES-256-GCM-HMAC-SHA-256" 77 algorithmChaCha20Poly1305 = "ChaCha20Poly1305" 78 ) 79 80 func (kms secretKey) Stat() (Status, error) { 81 return Status{ 82 Name: "SecretKey", 83 DefaultKey: kms.keyID, 84 }, nil 85 } 86 87 func (secretKey) CreateKey(string) error { 88 return errors.New("kms: creating keys is not supported") 89 } 90 91 func (kms secretKey) GenerateKey(keyID string, context Context) (DEK, error) { 92 if keyID == "" { 93 keyID = kms.keyID 94 } 95 if keyID != kms.keyID { 96 return DEK{}, fmt.Errorf("kms: key %q does not exist", keyID) 97 } 98 iv, err := sioutil.Random(16) 99 if err != nil { 100 return DEK{}, err 101 } 102 103 var algorithm string 104 if sioutil.NativeAES() { 105 algorithm = algorithmAESGCM 106 } else { 107 algorithm = algorithmChaCha20Poly1305 108 } 109 110 var aead cipher.AEAD 111 switch algorithm { 112 case algorithmAESGCM: 113 mac := hmac.New(sha256.New, kms.key) 114 mac.Write(iv) 115 sealingKey := mac.Sum(nil) 116 117 var block cipher.Block 118 block, err = aes.NewCipher(sealingKey) 119 if err != nil { 120 return DEK{}, err 121 } 122 aead, err = cipher.NewGCM(block) 123 if err != nil { 124 return DEK{}, err 125 } 126 case algorithmChaCha20Poly1305: 127 var sealingKey []byte 128 sealingKey, err = chacha20.HChaCha20(kms.key, iv) 129 if err != nil { 130 return DEK{}, err 131 } 132 aead, err = chacha20poly1305.New(sealingKey) 133 if err != nil { 134 return DEK{}, err 135 } 136 default: 137 return DEK{}, errors.New("invalid algorithm: " + algorithm) 138 } 139 140 nonce, err := sioutil.Random(aead.NonceSize()) 141 if err != nil { 142 return DEK{}, err 143 } 144 145 plaintext, err := sioutil.Random(32) 146 if err != nil { 147 return DEK{}, err 148 } 149 associatedData, _ := context.MarshalText() 150 ciphertext := aead.Seal(nil, nonce, plaintext, associatedData) 151 152 ciphertext, err = json.Marshal(encryptedKey{ 153 Algorithm: algorithm, 154 IV: iv, 155 Nonce: nonce, 156 Bytes: ciphertext, 157 }) 158 if err != nil { 159 return DEK{}, err 160 } 161 return DEK{ 162 KeyID: keyID, 163 Plaintext: plaintext, 164 Ciphertext: ciphertext, 165 }, nil 166 } 167 168 func (kms secretKey) DecryptKey(keyID string, ciphertext []byte, context Context) ([]byte, error) { 169 if keyID != kms.keyID { 170 return nil, fmt.Errorf("kms: key %q does not exist", keyID) 171 } 172 173 var encryptedKey encryptedKey 174 if err := json.Unmarshal(ciphertext, &encryptedKey); err != nil { 175 return nil, err 176 } 177 if n := len(encryptedKey.IV); n != 16 { 178 return nil, fmt.Errorf("kms: invalid iv size") 179 } 180 181 var aead cipher.AEAD 182 switch encryptedKey.Algorithm { 183 case algorithmAESGCM: 184 mac := hmac.New(sha256.New, kms.key) 185 mac.Write(encryptedKey.IV) 186 sealingKey := mac.Sum(nil) 187 188 block, err := aes.NewCipher(sealingKey[:]) 189 if err != nil { 190 return nil, err 191 } 192 aead, err = cipher.NewGCM(block) 193 if err != nil { 194 return nil, err 195 } 196 case algorithmChaCha20Poly1305: 197 sealingKey, err := chacha20.HChaCha20(kms.key, encryptedKey.IV) 198 if err != nil { 199 return nil, err 200 } 201 aead, err = chacha20poly1305.New(sealingKey) 202 if err != nil { 203 return nil, err 204 } 205 default: 206 return nil, fmt.Errorf("kms: invalid algorithm: %q", encryptedKey.Algorithm) 207 } 208 209 if n := len(encryptedKey.Nonce); n != aead.NonceSize() { 210 return nil, fmt.Errorf("kms: invalid nonce size %d", n) 211 } 212 213 associatedData, _ := context.MarshalText() 214 plaintext, err := aead.Open(nil, encryptedKey.Nonce, encryptedKey.Bytes, associatedData) 215 if err != nil { 216 return nil, fmt.Errorf("kms: encrypted key is not authentic") 217 } 218 return plaintext, nil 219 } 220 221 type encryptedKey struct { 222 Algorithm string `json:"aead"` 223 IV []byte `json:"iv"` 224 Nonce []byte `json:"nonce"` 225 Bytes []byte `json:"bytes"` 226 }