added tabs to terminal, split single .go to separate files
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
package internals
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// fileEncKeyHex is injected at build time via:
|
||||
//
|
||||
// go build -ldflags "-X gotermix/internals.fileEncKeyHex=$(openssl rand -hex 32)" .
|
||||
// go build -ldflags "-X gotermix/internals.fileEncKeyHex=${GOTERMINAL_ENC}" .
|
||||
//
|
||||
// If empty (dev builds without -ldflags), a random key is generated on first
|
||||
// run and stored in gws.key next to the binary so the creds file stays
|
||||
// readable across restarts without being hardcoded anywhere.
|
||||
var fileEncKeyHex string
|
||||
|
||||
func keyFilePath() string {
|
||||
return filepath.Join(filepath.Dir(credsPath), "gws.key")
|
||||
}
|
||||
|
||||
// fileKey returns the AES-256 key used to encrypt/decrypt the credentials file.
|
||||
//
|
||||
// Priority order:
|
||||
// 1. Build-time injected key (fileEncKeyHex set via -ldflags)
|
||||
// 2. Persisted per-install key stored in gws.key next to the binary
|
||||
// 3. Newly generated random key (written to gws.key for future runs)
|
||||
func fileKey() []byte {
|
||||
if len(fileEncKeyHex) == 64 {
|
||||
if b, err := hex.DecodeString(fileEncKeyHex); err == nil {
|
||||
return b
|
||||
}
|
||||
}
|
||||
kp := keyFilePath()
|
||||
if data, err := os.ReadFile(kp); err == nil {
|
||||
if b, err := hex.DecodeString(strings.TrimSpace(string(data))); err == nil && len(b) == 32 {
|
||||
return b
|
||||
}
|
||||
}
|
||||
key := make([]byte, 32)
|
||||
rand.Read(key)
|
||||
os.WriteFile(kp, []byte(hex.EncodeToString(key)), 0600) //nolint:errcheck
|
||||
return key
|
||||
}
|
||||
|
||||
func encryptJSON(v any) ([]byte, error) {
|
||||
plain, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
block, err := aes.NewCipher(fileKey())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gcm, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nonce := make([]byte, gcm.NonceSize())
|
||||
if _, err := rand.Read(nonce); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return gcm.Seal(nonce, nonce, plain, nil), nil
|
||||
}
|
||||
|
||||
func decryptJSON(data []byte, v any) error {
|
||||
block, err := aes.NewCipher(fileKey())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gcm, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(data) < gcm.NonceSize() {
|
||||
return fmt.Errorf("data too short")
|
||||
}
|
||||
plain, err := gcm.Open(nil, data[:gcm.NonceSize()], data[gcm.NonceSize():], nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return json.Unmarshal(plain, v)
|
||||
}
|
||||
|
||||
func newSalt() string {
|
||||
b := make([]byte, 32)
|
||||
rand.Read(b)
|
||||
return hex.EncodeToString(b)
|
||||
}
|
||||
|
||||
// hashPassword runs 50 000 rounds of SHA-256 so brute-forcing a stolen file is slow.
|
||||
func hashPassword(password, salt string) string {
|
||||
saltBytes, _ := hex.DecodeString(salt)
|
||||
h := sha256.New()
|
||||
h.Write(saltBytes)
|
||||
h.Write([]byte(password))
|
||||
result := h.Sum(nil)
|
||||
for i := 0; i < 50000; i++ {
|
||||
h.Reset()
|
||||
h.Write(result)
|
||||
h.Write(saltBytes)
|
||||
result = h.Sum(nil)
|
||||
}
|
||||
return hex.EncodeToString(result)
|
||||
}
|
||||
|
||||
func defaultCreds() storedCreds {
|
||||
salt := newSalt()
|
||||
return storedCreds{Username: defaultUser, Salt: salt, Hash: hashPassword(defaultPass, salt)}
|
||||
}
|
||||
|
||||
func loadCreds() storedCreds {
|
||||
data, err := os.ReadFile(credsPath)
|
||||
if err != nil {
|
||||
return defaultCreds()
|
||||
}
|
||||
var c storedCreds
|
||||
if err := decryptJSON(data, &c); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "warning: credentials file unreadable — using defaults")
|
||||
return defaultCreds()
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func saveCreds(c storedCreds) error {
|
||||
data, err := encryptJSON(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(credsPath, data, 0600)
|
||||
}
|
||||
Reference in New Issue
Block a user