Files
codit/backend/internal/handlers/ssh_broker_secret.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
}