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 }