122 lines
2.6 KiB
Go
122 lines
2.6 KiB
Go
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
|
|
}
|