2021-04-18 15:41:13 -04:00
|
|
|
// Copyright (c) 2015-2021 MinIO, Inc.
|
2021-04-15 11:47:33 -04:00
|
|
|
//
|
2021-04-18 15:41:13 -04:00
|
|
|
// This file is part of MinIO Object Storage stack
|
2021-04-15 11:47:33 -04:00
|
|
|
//
|
2021-04-18 15:41:13 -04:00
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
|
|
// it under the terms of the GNU Affero General Public License as published by
|
|
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
|
|
// (at your option) any later version.
|
2021-04-15 11:47:33 -04:00
|
|
|
//
|
2021-04-18 15:41:13 -04:00
|
|
|
// This program is distributed in the hope that it will be useful
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
// GNU Affero General Public License for more details.
|
|
|
|
//
|
|
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
2021-04-15 11:47:33 -04:00
|
|
|
|
|
|
|
package kms
|
|
|
|
|
|
|
|
import (
|
2022-04-04 14:42:03 -04:00
|
|
|
"context"
|
2021-04-15 11:47:33 -04:00
|
|
|
"crypto/aes"
|
|
|
|
"crypto/cipher"
|
|
|
|
"crypto/hmac"
|
|
|
|
"encoding/base64"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
2023-03-16 14:59:42 -04:00
|
|
|
"net/http"
|
2021-04-15 11:47:33 -04:00
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
|
2021-04-26 19:01:52 -04:00
|
|
|
jsoniter "github.com/json-iterator/go"
|
2021-04-15 11:47:33 -04:00
|
|
|
"github.com/secure-io/sio-go/sioutil"
|
|
|
|
"golang.org/x/crypto/chacha20"
|
|
|
|
"golang.org/x/crypto/chacha20poly1305"
|
2022-05-27 09:00:19 -04:00
|
|
|
|
2023-02-14 10:19:20 -05:00
|
|
|
"github.com/minio/kes-go"
|
2022-05-27 09:00:19 -04:00
|
|
|
"github.com/minio/minio/internal/hash/sha256"
|
2021-04-15 11:47:33 -04:00
|
|
|
)
|
|
|
|
|
|
|
|
// Parse parses s as single-key KMS. The given string
|
|
|
|
// is expected to have the following format:
|
2022-08-26 15:52:29 -04:00
|
|
|
//
|
|
|
|
// <key-id>:<base64-key>
|
2021-04-15 11:47:33 -04:00
|
|
|
//
|
|
|
|
// The returned KMS implementation uses the parsed
|
|
|
|
// key ID and key to derive new DEKs and decrypt ciphertext.
|
|
|
|
func Parse(s string) (KMS, error) {
|
|
|
|
v := strings.SplitN(s, ":", 2)
|
|
|
|
if len(v) != 2 {
|
|
|
|
return nil, errors.New("kms: invalid master key format")
|
|
|
|
}
|
|
|
|
|
2022-01-02 12:15:06 -05:00
|
|
|
keyID, b64Key := v[0], v[1]
|
2021-04-15 11:47:33 -04:00
|
|
|
key, err := base64.StdEncoding.DecodeString(b64Key)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return New(keyID, key)
|
|
|
|
}
|
|
|
|
|
|
|
|
// New returns a single-key KMS that derives new DEKs from the
|
2021-04-24 22:05:25 -04:00
|
|
|
// given key.
|
2021-04-15 11:47:33 -04:00
|
|
|
func New(keyID string, key []byte) (KMS, error) {
|
|
|
|
if len(key) != 32 {
|
|
|
|
return nil, errors.New("kms: invalid key length " + strconv.Itoa(len(key)))
|
|
|
|
}
|
|
|
|
return secretKey{
|
|
|
|
keyID: keyID,
|
|
|
|
key: key,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// secretKey is a KMS implementation that derives new DEKs
|
|
|
|
// from a single key.
|
|
|
|
type secretKey struct {
|
|
|
|
keyID string
|
|
|
|
key []byte
|
|
|
|
}
|
|
|
|
|
|
|
|
var _ KMS = secretKey{} // compiler check
|
|
|
|
|
|
|
|
const ( // algorithms used to derive and encrypt DEKs
|
|
|
|
algorithmAESGCM = "AES-256-GCM-HMAC-SHA-256"
|
|
|
|
algorithmChaCha20Poly1305 = "ChaCha20Poly1305"
|
|
|
|
)
|
|
|
|
|
2022-07-18 21:54:27 -04:00
|
|
|
func (kms secretKey) Stat(context.Context) (Status, error) {
|
2021-04-15 11:47:33 -04:00
|
|
|
return Status{
|
|
|
|
Name: "SecretKey",
|
|
|
|
DefaultKey: kms.keyID,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2023-02-21 20:43:01 -05:00
|
|
|
// IsLocal returns true if the KMS is a local implementation
|
|
|
|
func (kms secretKey) IsLocal() bool {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
// List returns an array of local KMS Names
|
|
|
|
func (kms secretKey) List() []kes.KeyInfo {
|
|
|
|
kmsSecret := []kes.KeyInfo{
|
|
|
|
{
|
|
|
|
Name: kms.keyID,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
return kmsSecret
|
|
|
|
}
|
|
|
|
|
|
|
|
func (secretKey) Metrics(ctx context.Context) (kes.Metric, error) {
|
2023-03-16 14:59:42 -04:00
|
|
|
return kes.Metric{}, Error{
|
|
|
|
HTTPStatusCode: http.StatusNotImplemented,
|
|
|
|
APICode: "KMS.NotImplemented",
|
|
|
|
Err: errors.New("metrics are not supported"),
|
|
|
|
}
|
2022-07-11 12:17:28 -04:00
|
|
|
}
|
|
|
|
|
2023-02-17 04:40:31 -05:00
|
|
|
func (kms secretKey) CreateKey(_ context.Context, keyID string) error {
|
|
|
|
if keyID == kms.keyID {
|
|
|
|
return nil
|
|
|
|
}
|
2023-03-16 14:59:42 -04:00
|
|
|
return Error{
|
|
|
|
HTTPStatusCode: http.StatusNotImplemented,
|
|
|
|
APICode: "KMS.NotImplemented",
|
|
|
|
Err: fmt.Errorf("creating custom key %q is not supported", keyID),
|
|
|
|
}
|
2021-04-15 11:47:33 -04:00
|
|
|
}
|
|
|
|
|
2022-07-18 21:54:27 -04:00
|
|
|
func (kms secretKey) GenerateKey(_ context.Context, keyID string, context Context) (DEK, error) {
|
2021-04-15 11:47:33 -04:00
|
|
|
if keyID == "" {
|
|
|
|
keyID = kms.keyID
|
|
|
|
}
|
|
|
|
if keyID != kms.keyID {
|
2023-03-16 14:59:42 -04:00
|
|
|
return DEK{}, Error{
|
|
|
|
HTTPStatusCode: http.StatusBadRequest,
|
|
|
|
APICode: "KMS.NotFoundException",
|
|
|
|
Err: fmt.Errorf("key %q does not exist", keyID),
|
|
|
|
}
|
2021-04-15 11:47:33 -04:00
|
|
|
}
|
|
|
|
iv, err := sioutil.Random(16)
|
|
|
|
if err != nil {
|
|
|
|
return DEK{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
var algorithm string
|
|
|
|
if sioutil.NativeAES() {
|
|
|
|
algorithm = algorithmAESGCM
|
|
|
|
} else {
|
|
|
|
algorithm = algorithmChaCha20Poly1305
|
|
|
|
}
|
|
|
|
|
|
|
|
var aead cipher.AEAD
|
|
|
|
switch algorithm {
|
|
|
|
case algorithmAESGCM:
|
|
|
|
mac := hmac.New(sha256.New, kms.key)
|
|
|
|
mac.Write(iv)
|
|
|
|
sealingKey := mac.Sum(nil)
|
|
|
|
|
|
|
|
var block cipher.Block
|
|
|
|
block, err = aes.NewCipher(sealingKey)
|
|
|
|
if err != nil {
|
|
|
|
return DEK{}, err
|
|
|
|
}
|
|
|
|
aead, err = cipher.NewGCM(block)
|
|
|
|
if err != nil {
|
|
|
|
return DEK{}, err
|
|
|
|
}
|
|
|
|
case algorithmChaCha20Poly1305:
|
|
|
|
var sealingKey []byte
|
|
|
|
sealingKey, err = chacha20.HChaCha20(kms.key, iv)
|
|
|
|
if err != nil {
|
|
|
|
return DEK{}, err
|
|
|
|
}
|
|
|
|
aead, err = chacha20poly1305.New(sealingKey)
|
|
|
|
if err != nil {
|
|
|
|
return DEK{}, err
|
|
|
|
}
|
|
|
|
default:
|
2023-03-16 14:59:42 -04:00
|
|
|
return DEK{}, Error{
|
|
|
|
HTTPStatusCode: http.StatusBadRequest,
|
|
|
|
APICode: "KMS.InternalException",
|
|
|
|
Err: errors.New("invalid algorithm: " + algorithm),
|
|
|
|
}
|
2021-04-15 11:47:33 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
nonce, err := sioutil.Random(aead.NonceSize())
|
|
|
|
if err != nil {
|
|
|
|
return DEK{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
plaintext, err := sioutil.Random(32)
|
|
|
|
if err != nil {
|
|
|
|
return DEK{}, err
|
|
|
|
}
|
|
|
|
associatedData, _ := context.MarshalText()
|
|
|
|
ciphertext := aead.Seal(nil, nonce, plaintext, associatedData)
|
|
|
|
|
2022-01-02 12:15:06 -05:00
|
|
|
json := jsoniter.ConfigCompatibleWithStandardLibrary
|
2021-04-15 11:47:33 -04:00
|
|
|
ciphertext, err = json.Marshal(encryptedKey{
|
|
|
|
Algorithm: algorithm,
|
|
|
|
IV: iv,
|
|
|
|
Nonce: nonce,
|
|
|
|
Bytes: ciphertext,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return DEK{}, err
|
|
|
|
}
|
|
|
|
return DEK{
|
|
|
|
KeyID: keyID,
|
|
|
|
Plaintext: plaintext,
|
|
|
|
Ciphertext: ciphertext,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (kms secretKey) DecryptKey(keyID string, ciphertext []byte, context Context) ([]byte, error) {
|
|
|
|
if keyID != kms.keyID {
|
2023-03-16 14:59:42 -04:00
|
|
|
return nil, Error{
|
|
|
|
HTTPStatusCode: http.StatusBadRequest,
|
|
|
|
APICode: "KMS.NotFoundException",
|
|
|
|
Err: fmt.Errorf("key %q does not exist", keyID),
|
|
|
|
}
|
2021-04-15 11:47:33 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
var encryptedKey encryptedKey
|
2022-01-02 12:15:06 -05:00
|
|
|
json := jsoniter.ConfigCompatibleWithStandardLibrary
|
2021-04-15 11:47:33 -04:00
|
|
|
if err := json.Unmarshal(ciphertext, &encryptedKey); err != nil {
|
2023-03-16 14:59:42 -04:00
|
|
|
return nil, Error{
|
|
|
|
HTTPStatusCode: http.StatusBadRequest,
|
|
|
|
APICode: "KMS.InternalException",
|
|
|
|
Err: err,
|
|
|
|
}
|
2021-04-15 11:47:33 -04:00
|
|
|
}
|
2021-04-26 19:01:52 -04:00
|
|
|
|
2021-04-15 11:47:33 -04:00
|
|
|
if n := len(encryptedKey.IV); n != 16 {
|
2023-03-16 14:59:42 -04:00
|
|
|
return nil, Error{
|
|
|
|
HTTPStatusCode: http.StatusBadRequest,
|
|
|
|
APICode: "KMS.InternalException",
|
|
|
|
Err: fmt.Errorf("invalid iv size: %d", n),
|
|
|
|
}
|
2021-04-15 11:47:33 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
var aead cipher.AEAD
|
|
|
|
switch encryptedKey.Algorithm {
|
|
|
|
case algorithmAESGCM:
|
|
|
|
mac := hmac.New(sha256.New, kms.key)
|
|
|
|
mac.Write(encryptedKey.IV)
|
|
|
|
sealingKey := mac.Sum(nil)
|
|
|
|
|
2021-11-16 12:28:29 -05:00
|
|
|
block, err := aes.NewCipher(sealingKey)
|
2021-04-15 11:47:33 -04:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
aead, err = cipher.NewGCM(block)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
case algorithmChaCha20Poly1305:
|
|
|
|
sealingKey, err := chacha20.HChaCha20(kms.key, encryptedKey.IV)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
aead, err = chacha20poly1305.New(sealingKey)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
default:
|
2023-03-16 14:59:42 -04:00
|
|
|
return nil, Error{
|
|
|
|
HTTPStatusCode: http.StatusBadRequest,
|
|
|
|
APICode: "KMS.InternalException",
|
|
|
|
Err: fmt.Errorf("invalid algorithm: %q", encryptedKey.Algorithm),
|
|
|
|
}
|
2021-04-15 11:47:33 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
if n := len(encryptedKey.Nonce); n != aead.NonceSize() {
|
2023-03-16 14:59:42 -04:00
|
|
|
return nil, Error{
|
|
|
|
HTTPStatusCode: http.StatusBadRequest,
|
|
|
|
APICode: "KMS.InternalException",
|
|
|
|
Err: fmt.Errorf("invalid nonce size %d", n),
|
|
|
|
}
|
2021-04-15 11:47:33 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
associatedData, _ := context.MarshalText()
|
|
|
|
plaintext, err := aead.Open(nil, encryptedKey.Nonce, encryptedKey.Bytes, associatedData)
|
|
|
|
if err != nil {
|
2023-03-16 14:59:42 -04:00
|
|
|
return nil, Error{
|
|
|
|
HTTPStatusCode: http.StatusBadRequest,
|
|
|
|
APICode: "KMS.InternalException",
|
|
|
|
Err: fmt.Errorf("encrypted key is not authentic"),
|
|
|
|
}
|
2021-04-15 11:47:33 -04:00
|
|
|
}
|
|
|
|
return plaintext, nil
|
|
|
|
}
|
|
|
|
|
2022-04-04 14:42:03 -04:00
|
|
|
func (kms secretKey) DecryptAll(_ context.Context, keyID string, ciphertexts [][]byte, contexts []Context) ([][]byte, error) {
|
2022-03-25 18:01:41 -04:00
|
|
|
plaintexts := make([][]byte, 0, len(ciphertexts))
|
|
|
|
for i := range ciphertexts {
|
|
|
|
plaintext, err := kms.DecryptKey(keyID, ciphertexts[i], contexts[i])
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
plaintexts = append(plaintexts, plaintext)
|
|
|
|
}
|
|
|
|
return plaintexts, nil
|
|
|
|
}
|
|
|
|
|
2021-04-15 11:47:33 -04:00
|
|
|
type encryptedKey struct {
|
|
|
|
Algorithm string `json:"aead"`
|
|
|
|
IV []byte `json:"iv"`
|
|
|
|
Nonce []byte `json:"nonce"`
|
|
|
|
Bytes []byte `json:"bytes"`
|
|
|
|
}
|