commit
b9e06a23aa
@ -0,0 +1 @@ |
|||||||
|
*.db |
||||||
@ -0,0 +1,22 @@ |
|||||||
|
The MIT License |
||||||
|
|
||||||
|
Copyright (c) 2025 Bob Strutts |
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining |
||||||
|
a copy of this software and associated documentation files (the |
||||||
|
"Software"), to deal in the Software without restriction, including |
||||||
|
without limitation the rights to use, copy, modify, merge, publish, |
||||||
|
distribute, sublicense, and/or sell copies of the Software, and to |
||||||
|
permit persons to whom the Software is furnished to do so, subject to |
||||||
|
the following conditions: |
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be |
||||||
|
included in all copies or substantial portions of the Software. |
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE |
||||||
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION |
||||||
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
||||||
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
||||||
@ -0,0 +1,80 @@ |
|||||||
|
package main |
||||||
|
|
||||||
|
import ( |
||||||
|
"crypto/rand" |
||||||
|
"encoding/gob" |
||||||
|
"encoding/hex" |
||||||
|
"fmt" |
||||||
|
"net" |
||||||
|
"time" |
||||||
|
) |
||||||
|
|
||||||
|
type Request struct { |
||||||
|
Operation string |
||||||
|
Site string |
||||||
|
Password string |
||||||
|
Timestamp int64 |
||||||
|
Nonce string |
||||||
|
} |
||||||
|
|
||||||
|
type Response struct { |
||||||
|
Message 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 main() { |
||||||
|
conn, err := net.Dial("tcp", "localhost:9898") |
||||||
|
if err != nil { |
||||||
|
panic(err) |
||||||
|
} |
||||||
|
defer conn.Close() |
||||||
|
|
||||||
|
var op, site, pwd string |
||||||
|
|
||||||
|
fmt.Print("Operation (store/get): ") |
||||||
|
fmt.Scanln(&op) |
||||||
|
fmt.Print("Site: ") |
||||||
|
fmt.Scanln(&site) |
||||||
|
|
||||||
|
if op == "store" { |
||||||
|
fmt.Print("Password: ") |
||||||
|
fmt.Scanln(&pwd) |
||||||
|
} |
||||||
|
|
||||||
|
nonce, err := generateNonce() |
||||||
|
if err != nil { |
||||||
|
panic(err) |
||||||
|
} |
||||||
|
|
||||||
|
req := Request{ |
||||||
|
Operation: op, |
||||||
|
Site: site, |
||||||
|
Password: pwd, |
||||||
|
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) |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
var res Response |
||||||
|
if err := dec.Decode(&res); err != nil { |
||||||
|
fmt.Println("Failed to receive response:", err) |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
fmt.Println("Server response:", res.Message) |
||||||
|
} |
||||||
@ -0,0 +1,5 @@ |
|||||||
|
module cliVault |
||||||
|
|
||||||
|
go 1.24.3 |
||||||
|
|
||||||
|
require github.com/mattn/go-sqlite3 v1.14.28 // indirect |
||||||
@ -0,0 +1,2 @@ |
|||||||
|
github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A= |
||||||
|
github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= |
||||||
@ -0,0 +1,25 @@ |
|||||||
|
package main |
||||||
|
|
||||||
|
import ( |
||||||
|
"crypto/rand" |
||||||
|
"encoding/hex" |
||||||
|
"fmt" |
||||||
|
) |
||||||
|
|
||||||
|
func generateKey() ([]byte, error) { |
||||||
|
key := make([]byte, 16) |
||||||
|
_, err := rand.Read(key) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
return key, nil |
||||||
|
} |
||||||
|
|
||||||
|
func main() { |
||||||
|
key, err := generateKey() |
||||||
|
if err != nil { |
||||||
|
panic(err) |
||||||
|
} |
||||||
|
|
||||||
|
fmt.Println("Generated 32-byte key (hex):", hex.EncodeToString(key)) |
||||||
|
} |
||||||
@ -0,0 +1,198 @@ |
|||||||
|
package main |
||||||
|
|
||||||
|
import ( |
||||||
|
"crypto/aes" |
||||||
|
"crypto/cipher" |
||||||
|
"database/sql" |
||||||
|
"encoding/gob" |
||||||
|
"fmt" |
||||||
|
"net" |
||||||
|
"sync" |
||||||
|
"time" |
||||||
|
|
||||||
|
_ "github.com/mattn/go-sqlite3" |
||||||
|
) |
||||||
|
|
||||||
|
var key = []byte("2f5680a7fb57ce83e8e83dbb1114ee31") // 32 bytes
|
||||||
|
|
||||||
|
var nonceStore = struct { |
||||||
|
sync.Mutex |
||||||
|
data map[string]int64 |
||||||
|
}{data: make(map[string]int64)} |
||||||
|
|
||||||
|
const nonceExpiry = 30 * time.Second |
||||||
|
|
||||||
|
type Request struct { |
||||||
|
Operation string |
||||||
|
Site string |
||||||
|
Password string |
||||||
|
Timestamp int64 // Unix time (seconds)
|
||||||
|
Nonce string // Random client-supplied nonce
|
||||||
|
} |
||||||
|
|
||||||
|
type Response struct { |
||||||
|
Message string |
||||||
|
} |
||||||
|
|
||||||
|
var ipAttempts = struct { |
||||||
|
sync.Mutex |
||||||
|
data map[string][]int64 |
||||||
|
}{data: make(map[string][]int64)} |
||||||
|
|
||||||
|
const maxAttemptsPerMinute = 5 |
||||||
|
|
||||||
|
func isRateLimited(ip string) bool { |
||||||
|
now := time.Now().Unix() |
||||||
|
ipAttempts.Lock() |
||||||
|
defer ipAttempts.Unlock() |
||||||
|
|
||||||
|
attempts := ipAttempts.data[ip] |
||||||
|
// Keep only the last 60 seconds of attempts
|
||||||
|
recent := make([]int64, 0) |
||||||
|
for _, t := range attempts { |
||||||
|
if now-t <= 60 { |
||||||
|
recent = append(recent, t) |
||||||
|
} |
||||||
|
} |
||||||
|
ipAttempts.data[ip] = append(recent, now) |
||||||
|
return len(recent) >= maxAttemptsPerMinute |
||||||
|
} |
||||||
|
|
||||||
|
func isValidNonce(nonce string, timestamp int64) bool { |
||||||
|
now := time.Now().Unix() |
||||||
|
if now-timestamp > int64(nonceExpiry.Seconds()) { |
||||||
|
return false // Too old
|
||||||
|
} |
||||||
|
|
||||||
|
nonceStore.Lock() |
||||||
|
defer nonceStore.Unlock() |
||||||
|
|
||||||
|
if t, exists := nonceStore.data[nonce]; exists { |
||||||
|
if now-t < int64(nonceExpiry.Seconds()) { |
||||||
|
return false // Replay detected
|
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
nonceStore.data[nonce] = now |
||||||
|
return true |
||||||
|
} |
||||||
|
|
||||||
|
func encrypt(text string) ([]byte, error) { |
||||||
|
block, err := aes.NewCipher(key) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
gcm, err := cipher.NewGCM(block) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
nonce := make([]byte, gcm.NonceSize()) |
||||||
|
return gcm.Seal(nonce, nonce, []byte(text), nil), nil |
||||||
|
} |
||||||
|
|
||||||
|
func decrypt(data []byte) (string, error) { |
||||||
|
block, err := aes.NewCipher(key) |
||||||
|
if err != nil { |
||||||
|
return "", err |
||||||
|
} |
||||||
|
gcm, err := cipher.NewGCM(block) |
||||||
|
if err != nil { |
||||||
|
return "", err |
||||||
|
} |
||||||
|
nonceSize := gcm.NonceSize() |
||||||
|
nonce, ciphertext := data[:nonceSize], data[nonceSize:] |
||||||
|
plaintext, err := gcm.Open(nil, nonce, ciphertext, nil) |
||||||
|
if err != nil { |
||||||
|
return "", err |
||||||
|
} |
||||||
|
return string(plaintext), nil |
||||||
|
} |
||||||
|
|
||||||
|
func handleConnection(conn net.Conn, db *sql.DB) { |
||||||
|
defer conn.Close() |
||||||
|
dec := gob.NewDecoder(conn) |
||||||
|
enc := gob.NewEncoder(conn) |
||||||
|
|
||||||
|
remoteAddr := conn.RemoteAddr().String() |
||||||
|
|
||||||
|
var req Request |
||||||
|
if err := dec.Decode(&req); err != nil { |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
if isRateLimited(remoteAddr) { |
||||||
|
enc.Encode(Response{"Too many requests. Try later."}) |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
if !isValidNonce(req.Nonce, req.Timestamp) { |
||||||
|
enc.Encode(Response{"Invalid or replayed request"}) |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
switch req.Operation { |
||||||
|
case "store": |
||||||
|
encrypted, err := encrypt(req.Password) |
||||||
|
if err != nil { |
||||||
|
enc.Encode(Response{"Encryption failed"}) |
||||||
|
return |
||||||
|
} |
||||||
|
_, err = db.Exec("INSERT OR REPLACE INTO accounts (site, password) VALUES (?, ?)", req.Site, encrypted) |
||||||
|
if err != nil { |
||||||
|
enc.Encode(Response{"Database error"}) |
||||||
|
return |
||||||
|
} |
||||||
|
enc.Encode(Response{"Password stored successfully"}) |
||||||
|
|
||||||
|
case "get": |
||||||
|
var encrypted []byte |
||||||
|
err := db.QueryRow("SELECT password FROM accounts WHERE site = ?", req.Site).Scan(&encrypted) |
||||||
|
if err == sql.ErrNoRows { |
||||||
|
enc.Encode(Response{"Site not found"}) |
||||||
|
return |
||||||
|
} else if err != nil { |
||||||
|
enc.Encode(Response{"Database error"}) |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
pwd, err := decrypt(encrypted) |
||||||
|
if err != nil { |
||||||
|
enc.Encode(Response{"Decryption failed"}) |
||||||
|
return |
||||||
|
} |
||||||
|
enc.Encode(Response{pwd}) |
||||||
|
|
||||||
|
default: |
||||||
|
enc.Encode(Response{"Unknown operation"}) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func main() { |
||||||
|
db, err := sql.Open("sqlite3", "vault.db") |
||||||
|
if err != nil { |
||||||
|
panic(err) |
||||||
|
} |
||||||
|
defer db.Close() |
||||||
|
|
||||||
|
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS accounts ( |
||||||
|
site TEXT PRIMARY KEY, |
||||||
|
password BLOB |
||||||
|
)`) |
||||||
|
if err != nil { |
||||||
|
panic(err) |
||||||
|
} |
||||||
|
|
||||||
|
ln, err := net.Listen("tcp", ":9898") |
||||||
|
if err != nil { |
||||||
|
panic(err) |
||||||
|
} |
||||||
|
fmt.Println("Server started on port 9898") |
||||||
|
|
||||||
|
for { |
||||||
|
conn, err := ln.Accept() |
||||||
|
if err != nil { |
||||||
|
continue |
||||||
|
} |
||||||
|
go handleConnection(conn, db) |
||||||
|
} |
||||||
|
} |
||||||
Loading…
Reference in new issue