Client/Server Password Vault
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
cliVault/client.go

254 lines
5.5 KiB

package main
import (
"crypto/sha256"
"crypto/rand"
"golang.org/x/crypto/chacha20poly1305"
"github.com/atotto/clipboard"
"encoding/base64"
"encoding/gob"
"encoding/hex"
"fmt"
"net"
"time"
"golang.org/x/term"
"syscall"
"flag"
"os"
"log"
"io/ioutil"
"gopkg.in/yaml.v2"
)
var ChaKey = []byte("")
type Config struct {
Auth struct {
ChaKey string `yaml:"ChaKey"`
} `yaml:"auth"`
}
type Request struct {
KeyPwd string
Username string
Password string
Operation string
Site string
VaultData string
Timestamp int64
Nonce string
}
type Response struct {
Message string
Enc string
}
func generateNonce() (string, error) {
b := make([]byte, 12) // 96-bit nonce
_, err := rand.Read(b)
if err != nil {
return "", err
}
return hex.EncodeToString(b), nil
}
func getKey()([]byte) {
// Hash it to 32 bytes using SHA-256
hashedKey := sha256.Sum256(ChaKey)
theKey := hashedKey[:] // Convert [32]byte to []byte
return theKey
}
func chEnc(p string)(string) {
// Create cipher
aead, err := chacha20poly1305.NewX(getKey())
if err != nil {
log.Fatalf("ChaKey enc error")
return ""
}
// Create nonce
nonce := make([]byte, aead.NonceSize())
if _, err := rand.Read(nonce); err != nil {
log.Fatalf("Cha Nonce Error")
return ""
}
// Encrypt the encoded data
encrypted := aead.Seal(nil, nonce, []byte(p), nil)
// Send nonce + encrypted data
fullMessage := append(nonce, encrypted...)
encoded := base64.StdEncoding.EncodeToString(fullMessage)
return encoded
}
func chDec(eText string)(string) {
if eText == "" {
return ""
}
// Decoding from base64
decoded, err := base64.StdEncoding.DecodeString(eText)
if err != nil {
log.Fatal("Base64 decoding error:", err)
}
// Create cipher instance (XChaCha20-Poly1305 for longer nonces)
aead, err := chacha20poly1305.NewX(getKey())
if err != nil {
log.Fatalf("Client: ChaKey failed")
return ""
}
encryptedMsg := decoded
// Decrypt: Split nonce and ciphertext
decryptedNonce := encryptedMsg[:aead.NonceSize()]
decryptedCiphertext := encryptedMsg[aead.NonceSize():]
decrypted, err := aead.Open(nil, decryptedNonce, decryptedCiphertext, nil)
if err != nil {
log.Fatalf("Client decryption failed")
return ""
}
return string(decrypted)
}
func copyText(text string) {
err := clipboard.WriteAll(text)
if err != nil {
fmt.Println("Failed to copy to clipboard:", err)
return
}
fmt.Println("Text copied to clipboard!")
}
func pause() {
var input string
fmt.Print("Press Enter to continue...")
fmt.Scanln(&input)
}
func main() {
configFilePtr := flag.String("config", "config.yaml", "Path to the YAML configuration file")
flag.Parse()
if *configFilePtr == "" {
fmt.Println("Please specify a configuration file using -config")
flag.Usage()
pause()
os.Exit(1)
}
// Read the config file
yamlFile, err := ioutil.ReadFile(*configFilePtr)
if err != nil {
log.Fatalf("Error reading YAML file: %v\n", err)
pause()
}
// Parse the YAML
var config Config
err = yaml.Unmarshal(yamlFile, &config)
if err != nil {
log.Fatalf("Error parsing YAML file: %v\n", err)
pause()
}
// Print the ChaKey
if config.Auth.ChaKey == "" {
fmt.Println("Warning: ChaKey not found in configuration file")
pause()
}
ChaKey = []byte(config.Auth.ChaKey)
var host string
fmt.Print("Enter host or IP: ")
fmt.Scanln(&host)
conn, err := net.Dial("tcp", host+":9898")
if err != nil {
panic(err)
}
defer conn.Close()
fmt.Print("Key Passphrase: ")
byteKeyPwd, err := term.ReadPassword(int(syscall.Stdin))
fmt.Println()
if err != nil {
panic(err)
}
keypassphrase := string(byteKeyPwd)
var username, op, site, vaultData string
fmt.Print("Username: ")
fmt.Scanln(&username)
fmt.Print("Password: ")
bytePassword, err := term.ReadPassword(int(syscall.Stdin))
fmt.Println()
if err != nil {
panic(err)
}
password := string(bytePassword)
fmt.Print("Operation (register/store/get): ")
fmt.Scanln(&op)
if op == "store" || op == "get" {
fmt.Print("Site: ")
fmt.Scanln(&site)
}
if op == "store" {
fmt.Print("Password to store: ")
fmt.Scanln(&vaultData)
}
nonce, err := generateNonce()
if err != nil {
panic(err)
}
var myPwd string
if vaultData == "" {
myPwd = ""
} else {
myPwd = chEnc(vaultData)
}
req := Request{
KeyPwd: chEnc(keypassphrase),
Username: chEnc(username),
Password: chEnc(password),
Operation: op,
Site: chEnc(site),
VaultData: myPwd,
Timestamp: time.Now().Unix(),
Nonce: nonce,
}
enc := gob.NewEncoder(conn)
dec := gob.NewDecoder(conn)
if err := enc.Encode(req); err != nil {
fmt.Println("Failed to send request:", err)
pause()
return
}
var res Response
if err := dec.Decode(&res); err != nil {
fmt.Println("Failed to receive response:", err)
pause()
return
}
if res.Message != "" {
fmt.Println("Server response:", res.Message)
} else {
passwd := chDec(res.Enc)
fmt.Println("Server response:", passwd)
copyText(passwd)
}
pause()
}