github.com/opentofu/opentofu@v1.7.1/internal/encryption/method/aesgcm/aesgcm.go (about)

     1  // Copyright (c) The OpenTofu Authors
     2  // SPDX-License-Identifier: MPL-2.0
     3  // Copyright (c) 2023 HashiCorp, Inc.
     4  // SPDX-License-Identifier: MPL-2.0
     5  
     6  package aesgcm
     7  
     8  import (
     9  	"crypto/aes"
    10  	"crypto/cipher"
    11  	"crypto/rand"
    12  	"errors"
    13  
    14  	"github.com/opentofu/opentofu/internal/encryption/method"
    15  )
    16  
    17  // aesgcm contains the encryption/decryption methods according to AES-GCM (NIST SP 800-38D).
    18  type aesgcm struct {
    19  	encryptionKey []byte
    20  	decryptionKey []byte
    21  	aad           []byte
    22  }
    23  
    24  // Encrypt encrypts the passed data with AES-GCM. If the data the encryption fails, it returns an error.
    25  func (a aesgcm) Encrypt(data []byte) ([]byte, error) {
    26  	result, err := handlePanic(
    27  		func() ([]byte, error) {
    28  			gcm, err := a.getGCM(a.encryptionKey)
    29  			if err != nil {
    30  				return nil, &method.ErrEncryptionFailed{Cause: err}
    31  			}
    32  
    33  			nonce := make([]byte, gcm.NonceSize())
    34  			if _, err := rand.Read(nonce); err != nil {
    35  				return nil, &method.ErrEncryptionFailed{Cause: &method.ErrCryptoFailure{
    36  					Message: "could not generate nonce",
    37  					Cause:   err,
    38  				}}
    39  			}
    40  
    41  			encrypted := gcm.Seal(nil, nonce, data, a.aad)
    42  
    43  			return append(nonce, encrypted...), nil
    44  		},
    45  	)
    46  	if err != nil {
    47  		var encryptionFailed *method.ErrEncryptionFailed
    48  		if errors.As(err, &encryptionFailed) {
    49  			return nil, err
    50  		}
    51  		return nil, &method.ErrEncryptionFailed{Cause: &method.ErrCryptoFailure{Message: "unexpected error", Cause: err}}
    52  	}
    53  	return result, nil
    54  }
    55  
    56  // Decrypt decrypts an AES-GCM-encrypted data set. If the data set fails decryption, it returns an error.
    57  func (a aesgcm) Decrypt(data []byte) ([]byte, error) {
    58  	if len(a.decryptionKey) == 0 {
    59  		return nil, &method.ErrDecryptionKeyUnavailable{}
    60  	}
    61  	result, err := handlePanic(
    62  		func() ([]byte, error) {
    63  			if len(data) == 0 {
    64  				return nil, &method.ErrDecryptionFailed{
    65  					Cause: method.ErrCryptoFailure{
    66  						Message: "cannot decrypt empty data",
    67  						Cause:   nil,
    68  					},
    69  				}
    70  			}
    71  
    72  			gcm, err := a.getGCM(a.decryptionKey)
    73  			if err != nil {
    74  				return nil, &method.ErrDecryptionFailed{Cause: err}
    75  			}
    76  
    77  			if len(data) < gcm.NonceSize() {
    78  				return nil, &method.ErrDecryptionFailed{
    79  					Cause: method.ErrCryptoFailure{
    80  						Message: "cannot decrypt data because it is too small (likely data corruption)",
    81  						Cause:   nil,
    82  					},
    83  				}
    84  			}
    85  
    86  			nonce := data[:gcm.NonceSize()]
    87  			data = data[gcm.NonceSize():]
    88  
    89  			decrypted, err := gcm.Open(nil, nonce, data, a.aad)
    90  			if err != nil {
    91  				return nil, &method.ErrDecryptionFailed{Cause: err}
    92  			}
    93  			return decrypted, nil
    94  		},
    95  	)
    96  	if err != nil {
    97  		var decryptionFailed *method.ErrDecryptionFailed
    98  		if errors.As(err, &decryptionFailed) {
    99  			return nil, err
   100  		}
   101  		return nil, &method.ErrDecryptionFailed{
   102  			Cause: &method.ErrCryptoFailure{Message: "unexpected error", Cause: err},
   103  		}
   104  	}
   105  	return result, nil
   106  }
   107  
   108  func (a aesgcm) getGCM(key []byte) (cipher.AEAD, error) {
   109  	cipherBlock, err := aes.NewCipher(key)
   110  	if err != nil {
   111  		return nil, &method.ErrCryptoFailure{
   112  			Message: "failed to create AES cypher block",
   113  			Cause:   err,
   114  		}
   115  	}
   116  
   117  	gcm, err := cipher.NewGCM(cipherBlock)
   118  	if err != nil {
   119  		return nil, &method.ErrCryptoFailure{
   120  			Message: "failed to create AES GCM",
   121  			Cause:   err,
   122  		}
   123  	}
   124  	return gcm, nil
   125  }