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  }