package handlers import "crypto/aes" import "crypto/cipher" import "crypto/rand" import "encoding/base64" import "errors" import "os" import "path/filepath" import "strings" const sshSecretPrefix string = "enc:v1:" func (api *API) encryptSSHSecretPayload(payload string) (string, error) { var key []byte var block cipher.Block var gcm cipher.AEAD var nonce []byte var ciphertext []byte var raw []byte var err error if strings.TrimSpace(payload) == "" { return "", nil } key, err = api.sshBrokerSecretKey() if err != nil { return "", err } block, err = aes.NewCipher(key) if err != nil { return "", err } gcm, err = cipher.NewGCM(block) if err != nil { return "", err } nonce = make([]byte, gcm.NonceSize()) _, err = rand.Read(nonce) if err != nil { return "", err } ciphertext = gcm.Seal(nil, nonce, []byte(payload), nil) raw = append(nonce, ciphertext...) return sshSecretPrefix + base64.StdEncoding.EncodeToString(raw), nil } func (api *API) decryptSSHSecretPayload(payload string) (string, error) { var key []byte var encoded string var raw []byte var block cipher.Block var gcm cipher.AEAD var nonce []byte var ciphertext []byte var plaintext []byte var err error if !strings.HasPrefix(payload, sshSecretPrefix) { return payload, nil } key, err = api.sshBrokerSecretKey() if err != nil { return "", err } encoded = strings.TrimSpace(strings.TrimPrefix(payload, sshSecretPrefix)) raw, err = base64.StdEncoding.DecodeString(encoded) if err != nil { return "", err } block, err = aes.NewCipher(key) if err != nil { return "", err } gcm, err = cipher.NewGCM(block) if err != nil { return "", err } if len(raw) < gcm.NonceSize() { return "", errors.New("invalid encrypted ssh secret") } nonce = raw[:gcm.NonceSize()] ciphertext = raw[gcm.NonceSize():] plaintext, err = gcm.Open(nil, nonce, ciphertext, nil) if err != nil { return "", err } return string(plaintext), nil } func (api *API) sshBrokerSecretKey() ([]byte, error) { var path string var data []byte var err error path = filepath.Join(api.Cfg.DataDir, "ssh-broker.secret") data, err = os.ReadFile(path) if err == nil { if len(data) != 32 { return nil, errors.New("invalid ssh broker secret key length") } return data, nil } if !errors.Is(err, os.ErrNotExist) { return nil, err } data = make([]byte, 32) _, err = rand.Read(data) if err != nil { return nil, err } err = os.MkdirAll(api.Cfg.DataDir, 0o755) if err != nil { return nil, err } err = os.WriteFile(path, data, 0o600) if err != nil { return nil, err } return data, nil }