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  }