|
|
|
@ -1,6 +1,7 @@ |
|
|
|
package main |
|
|
|
package main |
|
|
|
|
|
|
|
|
|
|
|
import ( |
|
|
|
import ( |
|
|
|
|
|
|
|
"golang.org/x/crypto/bcrypt" |
|
|
|
"crypto/aes" |
|
|
|
"crypto/aes" |
|
|
|
"crypto/cipher" |
|
|
|
"crypto/cipher" |
|
|
|
"database/sql" |
|
|
|
"database/sql" |
|
|
|
@ -13,6 +14,7 @@ import ( |
|
|
|
_ "github.com/mattn/go-sqlite3" |
|
|
|
_ "github.com/mattn/go-sqlite3" |
|
|
|
) |
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const AllowRegistration = true // Disable after users are added!
|
|
|
|
var key = []byte("2f5680a7fb57ce83e8e83dbb1114ee31") // 32 bytes
|
|
|
|
var key = []byte("2f5680a7fb57ce83e8e83dbb1114ee31") // 32 bytes
|
|
|
|
|
|
|
|
|
|
|
|
var nonceStore = struct { |
|
|
|
var nonceStore = struct { |
|
|
|
@ -23,9 +25,11 @@ var nonceStore = struct { |
|
|
|
const nonceExpiry = 30 * time.Second |
|
|
|
const nonceExpiry = 30 * time.Second |
|
|
|
|
|
|
|
|
|
|
|
type Request struct { |
|
|
|
type Request struct { |
|
|
|
|
|
|
|
Username string |
|
|
|
|
|
|
|
Password string |
|
|
|
Operation string |
|
|
|
Operation string |
|
|
|
Site string |
|
|
|
Site string |
|
|
|
Password string |
|
|
|
VaultData string |
|
|
|
Timestamp int64 // Unix time (seconds)
|
|
|
|
Timestamp int64 // Unix time (seconds)
|
|
|
|
Nonce string // Random client-supplied nonce
|
|
|
|
Nonce string // Random client-supplied nonce
|
|
|
|
} |
|
|
|
} |
|
|
|
@ -77,6 +81,15 @@ func isValidNonce(nonce string, timestamp int64) bool { |
|
|
|
return true |
|
|
|
return true |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func hashPassword(pw string) (string, error) { |
|
|
|
|
|
|
|
bytes, err := bcrypt.GenerateFromPassword([]byte(pw), 12) |
|
|
|
|
|
|
|
return string(bytes), err |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func checkPassword(hash, pw string) error { |
|
|
|
|
|
|
|
return bcrypt.CompareHashAndPassword([]byte(hash), []byte(pw)) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func encrypt(text string) ([]byte, error) { |
|
|
|
func encrypt(text string) ([]byte, error) { |
|
|
|
block, err := aes.NewCipher(key) |
|
|
|
block, err := aes.NewCipher(key) |
|
|
|
if err != nil { |
|
|
|
if err != nil { |
|
|
|
@ -130,14 +143,41 @@ func handleConnection(conn net.Conn, db *sql.DB) { |
|
|
|
return |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if req.Operation == "register" { |
|
|
|
|
|
|
|
if AllowRegistration == false { |
|
|
|
|
|
|
|
enc.Encode(Response{"Registration Disabled!"}) |
|
|
|
|
|
|
|
return |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
hashed, err := hashPassword(req.Password) |
|
|
|
|
|
|
|
if err != nil { |
|
|
|
|
|
|
|
enc.Encode(Response{"Failed to hash password"}) |
|
|
|
|
|
|
|
return |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
_, err = db.Exec("INSERT INTO users (username, password) VALUES (?, ?)", req.Username, hashed) |
|
|
|
|
|
|
|
if err != nil { |
|
|
|
|
|
|
|
enc.Encode(Response{"Registration failed (user exists?)"}) |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
enc.Encode(Response{"Registration successful"}) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
// Authenticate all other operations
|
|
|
|
|
|
|
|
var storedHash string |
|
|
|
|
|
|
|
err := db.QueryRow("SELECT password FROM users WHERE username = ?", req.Username).Scan(&storedHash) |
|
|
|
|
|
|
|
if err != nil || checkPassword(storedHash, req.Password) != nil { |
|
|
|
|
|
|
|
enc.Encode(Response{"Authentication failed"}) |
|
|
|
|
|
|
|
return |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
switch req.Operation { |
|
|
|
switch req.Operation { |
|
|
|
case "store": |
|
|
|
case "store": |
|
|
|
encrypted, err := encrypt(req.Password) |
|
|
|
encrypted, err := encrypt(req.VaultData) |
|
|
|
if err != nil { |
|
|
|
if err != nil { |
|
|
|
enc.Encode(Response{"Encryption failed"}) |
|
|
|
enc.Encode(Response{"Encryption failed"}) |
|
|
|
return |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
_, err = db.Exec("INSERT OR REPLACE INTO accounts (site, password) VALUES (?, ?)", req.Site, encrypted) |
|
|
|
_, err = db.Exec("INSERT OR REPLACE INTO accounts (user, site, password) VALUES (?, ?, ?)", |
|
|
|
|
|
|
|
req.Username, req.Site, encrypted) |
|
|
|
if err != nil { |
|
|
|
if err != nil { |
|
|
|
enc.Encode(Response{"Database error"}) |
|
|
|
enc.Encode(Response{"Database error"}) |
|
|
|
return |
|
|
|
return |
|
|
|
@ -146,7 +186,7 @@ func handleConnection(conn net.Conn, db *sql.DB) { |
|
|
|
|
|
|
|
|
|
|
|
case "get": |
|
|
|
case "get": |
|
|
|
var encrypted []byte |
|
|
|
var encrypted []byte |
|
|
|
err := db.QueryRow("SELECT password FROM accounts WHERE site = ?", req.Site).Scan(&encrypted) |
|
|
|
err := db.QueryRow("SELECT password FROM accounts WHERE user = ? AND site = ?", req.Username, req.Site).Scan(&encrypted) |
|
|
|
if err == sql.ErrNoRows { |
|
|
|
if err == sql.ErrNoRows { |
|
|
|
enc.Encode(Response{"Site not found"}) |
|
|
|
enc.Encode(Response{"Site not found"}) |
|
|
|
return |
|
|
|
return |
|
|
|
@ -174,10 +214,20 @@ func main() { |
|
|
|
} |
|
|
|
} |
|
|
|
defer db.Close() |
|
|
|
defer db.Close() |
|
|
|
|
|
|
|
|
|
|
|
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS accounts ( |
|
|
|
_, err = db.Exec(` |
|
|
|
site TEXT PRIMARY KEY, |
|
|
|
CREATE TABLE IF NOT EXISTS users ( |
|
|
|
password BLOB |
|
|
|
username TEXT PRIMARY KEY, |
|
|
|
)`) |
|
|
|
password TEXT |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
CREATE TABLE IF NOT EXISTS accounts ( |
|
|
|
|
|
|
|
user TEXT, |
|
|
|
|
|
|
|
site TEXT, |
|
|
|
|
|
|
|
password BLOB, |
|
|
|
|
|
|
|
PRIMARY KEY(user, site), |
|
|
|
|
|
|
|
FOREIGN KEY(user) REFERENCES users(username) |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
`) |
|
|
|
if err != nil { |
|
|
|
if err != nil { |
|
|
|
panic(err) |
|
|
|
panic(err) |
|
|
|
} |
|
|
|
} |
|
|
|
|