github.com/jfrog/jfrog-cli-core/v2@v2.51.0/utils/config/encryption.go (about)

     1  package config
     2  
     3  import (
     4  	"crypto/aes"
     5  	"crypto/cipher"
     6  	"crypto/rand"
     7  	"encoding/base64"
     8  	ioutils "github.com/jfrog/gofrog/io"
     9  	"io"
    10  	"os"
    11  	"strconv"
    12  	"syscall"
    13  
    14  	"github.com/jfrog/jfrog-cli-core/v2/utils/coreutils"
    15  	"github.com/jfrog/jfrog-client-go/utils/errorutils"
    16  	"github.com/jfrog/jfrog-client-go/utils/io/fileutils"
    17  	"github.com/jfrog/jfrog-client-go/utils/log"
    18  	"github.com/spf13/viper"
    19  	"golang.org/x/term"
    20  )
    21  
    22  type SecurityConf struct {
    23  	Version   string `yaml:"version,omitempty"`
    24  	MasterKey string `yaml:"masterKey,omitempty"`
    25  }
    26  
    27  const masterKeyField = "masterKey"
    28  const masterKeyLength = 32
    29  const encryptErrorPrefix = "cannot encrypt config: "
    30  const decryptErrorPrefix = "cannot decrypt config: "
    31  
    32  type secretHandler func(string, string) (string, error)
    33  
    34  // Encrypt config file if security configuration file exists and contains master key.
    35  func (config *Config) encrypt() error {
    36  	key, err := getEncryptionKey()
    37  	if err != nil || key == "" {
    38  		return err
    39  	}
    40  	// Mark config as encrypted.
    41  	config.Enc = true
    42  	return handleSecrets(config, encrypt, key)
    43  }
    44  
    45  // Decrypt config if encrypted and master key exists.
    46  func (config *Config) decrypt() error {
    47  	if !config.Enc {
    48  		return updateEncryptionIfNeeded(config)
    49  	}
    50  	key, err := getEncryptionKey()
    51  	if err != nil {
    52  		return err
    53  	}
    54  	if key == "" {
    55  		return errorutils.CheckErrorf(decryptErrorPrefix+"security configuration file was not found or the '%s' environment variable was not configured", coreutils.EncryptionKey)
    56  	}
    57  	config.Enc = false
    58  	return handleSecrets(config, decrypt, key)
    59  }
    60  
    61  // Encrypt the config file if it is decrypted while security configuration file exists and contains a master key, or if the JFROG_CLI_ENCRYPTION_KEY environment variable exist.
    62  func updateEncryptionIfNeeded(config *Config) error {
    63  	masterKey, err := getEncryptionKey()
    64  	if err != nil || masterKey == "" {
    65  		return err
    66  	}
    67  	// The encryption key exists and will be loaded again in encrypt()
    68  	return saveConfig(config)
    69  }
    70  
    71  // Encrypt/Decrypt all secrets in the provided config, with the provided master key.
    72  func handleSecrets(config *Config, handler secretHandler, key string) error {
    73  	var err error
    74  	for _, serverDetails := range config.Servers {
    75  		serverDetails.Password, err = handler(serverDetails.Password, key)
    76  		if err != nil {
    77  			return err
    78  		}
    79  		serverDetails.AccessToken, err = handler(serverDetails.AccessToken, key)
    80  		if err != nil {
    81  			return err
    82  		}
    83  		serverDetails.SshPassphrase, err = handler(serverDetails.SshPassphrase, key)
    84  		if err != nil {
    85  			return err
    86  		}
    87  		serverDetails.RefreshToken, err = handler(serverDetails.RefreshToken, key)
    88  		if err != nil {
    89  			return err
    90  		}
    91  		serverDetails.ArtifactoryRefreshToken, err = handler(serverDetails.ArtifactoryRefreshToken, key)
    92  		if err != nil {
    93  			return err
    94  		}
    95  	}
    96  	return nil
    97  }
    98  
    99  func getEncryptionKey() (string, error) {
   100  	if key, exist := os.LookupEnv(coreutils.EncryptionKey); exist {
   101  		return key, nil
   102  	}
   103  	return getEncryptionKeyFromSecurityConfFile()
   104  }
   105  
   106  func getEncryptionKeyFromSecurityConfFile() (key string, err error) {
   107  	secFile, err := coreutils.GetJfrogSecurityConfFilePath()
   108  	if err != nil {
   109  		return "", err
   110  	}
   111  	exists, err := fileutils.IsFileExists(secFile, false)
   112  	if err != nil || !exists {
   113  		return "", err
   114  	}
   115  
   116  	config := viper.New()
   117  	config.SetConfigType("yaml")
   118  	f, err := os.Open(secFile)
   119  	defer ioutils.Close(f, &err)
   120  	if err != nil {
   121  		return "", errorutils.CheckError(err)
   122  	}
   123  	err = config.ReadConfig(f)
   124  	if err != nil {
   125  		return "", errorutils.CheckError(err)
   126  	}
   127  	key = config.GetString(masterKeyField)
   128  	if key == "" {
   129  		return "", errorutils.CheckErrorf(decryptErrorPrefix + "security configuration file does not contain an encryption master key")
   130  	}
   131  	return key, nil
   132  }
   133  
   134  func readMasterKeyFromConsole() (string, error) {
   135  	log.Output("Please enter the master key: ")
   136  	bytePassword, err := term.ReadPassword(int(syscall.Stdin)) //nolint:unconvert
   137  	if err != nil {
   138  		return "", errorutils.CheckError(err)
   139  	}
   140  	// New-line required after the input:
   141  	log.Output()
   142  	return string(bytePassword), nil
   143  }
   144  
   145  func encrypt(secret string, key string) (string, error) {
   146  	if secret == "" {
   147  		return "", nil
   148  	}
   149  	if len(key) != 32 {
   150  		return "", errorutils.CheckErrorf(encryptErrorPrefix + "Wrong length for master key. Key should have a length of exactly: " + strconv.Itoa(masterKeyLength) + " bytes")
   151  	}
   152  	c, err := aes.NewCipher([]byte(key))
   153  	if err != nil {
   154  		return "", errorutils.CheckError(err)
   155  	}
   156  
   157  	gcm, err := cipher.NewGCM(c)
   158  	if err != nil {
   159  		return "", errorutils.CheckError(err)
   160  	}
   161  
   162  	nonce := make([]byte, gcm.NonceSize())
   163  	if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
   164  		return "", errorutils.CheckError(err)
   165  	}
   166  
   167  	sealed := gcm.Seal(nonce, nonce, []byte(secret), nil)
   168  	return base64.StdEncoding.EncodeToString(sealed), nil
   169  }
   170  
   171  func decrypt(encryptedSecret string, key string) (string, error) {
   172  	if encryptedSecret == "" {
   173  		return "", nil
   174  	}
   175  
   176  	cipherText, err := base64.StdEncoding.DecodeString(encryptedSecret)
   177  	if err != nil {
   178  		return "", errorutils.CheckError(err)
   179  	}
   180  
   181  	c, err := aes.NewCipher([]byte(key))
   182  	if err != nil {
   183  		return "", errorutils.CheckError(err)
   184  	}
   185  
   186  	gcm, err := cipher.NewGCM(c)
   187  	if err != nil {
   188  		return "", errorutils.CheckError(err)
   189  	}
   190  
   191  	nonceSize := gcm.NonceSize()
   192  	if len(cipherText) < nonceSize {
   193  		return "", errorutils.CheckErrorf(decryptErrorPrefix + "unexpected cipher text size")
   194  	}
   195  
   196  	nonce, cipherText := cipherText[:nonceSize], cipherText[nonceSize:]
   197  	plaintext, err := gcm.Open(nil, nonce, cipherText, nil)
   198  	if err != nil {
   199  		return "", errorutils.CheckError(err)
   200  	}
   201  	return string(plaintext), nil
   202  }