diff --git a/.gitignore b/.gitignore index 9fe57a7..1661cb6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ server gclient +gclient.exe +server.exe server.db client.db diff --git a/Makefile b/Makefile index 9da6355..5449c92 100644 --- a/Makefile +++ b/Makefile @@ -5,6 +5,7 @@ default: build install: $(GI) github.com/number571/gopeer $(GI) github.com/mattn/go-sqlite3 + $(GI) github.com/boombuler/barcode $(GI) golang.org/x/net/proxy build: gclient.go server.go database.go $(GC) gclient.go diff --git a/README.md b/README.md index 86dc65d..c0f244d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # HES -> Hidden email service. Version 1.1.0s. +> Hidden email service. Version 1.1.1s. ### Characteristics: 1. End to end encryption; @@ -17,6 +17,7 @@ $ make install > go get github.com/number571/gopeer > go get github.com/mattn/go-sqlite3 +> go get github.com/boombuler/barcode > go get golang.org/x/net/proxy ``` @@ -55,7 +56,8 @@ CREATE TABLE IF NOT EXISTS emails ( #### Client side ```sql /* !key_pasw = hash(password, salt)^20 */ -/* pasw = hash(!key_pasw) */ +/* name = hash(name) */ +/* pasw = hash(!key_pasw, name) */ /* priv = encrypt[!key_pasw](private_key) */ CREATE TABLE IF NOT EXISTS users ( id INTEGER, @@ -65,7 +67,7 @@ CREATE TABLE IF NOT EXISTS users ( priv TEXT, PRIMARY KEY(id) ); -/* hash = hash(host, !key_pasw, salt) */ +/* hash = hash(host, !key_pasw) */ /* host = encrypt[!key_pasw](host) */ CREATE TABLE IF NOT EXISTS connects ( id INTEGER, @@ -75,19 +77,22 @@ CREATE TABLE IF NOT EXISTS connects ( PRIMARY KEY(id), FOREIGN KEY(id_user) REFERENCES users(id) ON DELETE CASCADE ); -/* hash = hash(pack_hash, !key_pasw, salt) */ -/* spubl = encrypt[!key_pasw](public_key) */ -/* sname = encrypt[!key_pasw](nickname) */ -/* head = encrypt[!key_pasw](title) */ -/* body = encrypt[!key_pasw](message) */ +/* hash = hash(pack_hash, !key_pasw) */ +/* spubl = encrypt[!key_pasw](public_key) */ +/* sname = encrypt[!key_pasw](nickname) */ +/* head = encrypt[!key_pasw](title) */ +/* body = encrypt[!key_pasw](message) */ +/* addtime = encrypt[!key_pasw](time_rec) */ CREATE TABLE IF NOT EXISTS emails ( id INTEGER, id_user INTEGER, + deleted BOOLEAN DEFAULT 0, hash VARCHAR(255) UNIQUE, spubl TEXT, sname VARCHAR(255), head VARCHAR(255), body TEXT, + addtime TEXT, PRIMARY KEY(id), FOREIGN KEY(id_user) REFERENCES users(id) ON DELETE CASCADE ); diff --git a/database.go b/database.go index f07e149..a6c513b 100644 --- a/database.go +++ b/database.go @@ -22,6 +22,7 @@ CREATE TABLE IF NOT EXISTS emails ( recv VARCHAR(255), hash VARCHAR(255) UNIQUE, data TEXT, + addtime DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY(id) ); `) @@ -58,6 +59,16 @@ func (db *DB) GetEmail(id int, recv string) string { return data } +func (db *DB) DelEmailsByDay(day int) error { + db.mtx.Lock() + defer db.mtx.Unlock() + _, err := db.ptr.Exec( + "DELETE FROM emails WHERE addtime < datetime('now', '-' | $1 | ' days')", + day, + ) + return err +} + func (db *DB) Size(recv string) int { db.mtx.Lock() defer db.mtx.Unlock() diff --git a/gclient.go b/gclient.go index 6f2fe15..f101796 100644 --- a/gclient.go +++ b/gclient.go @@ -6,9 +6,12 @@ import ( "encoding/json" "flag" "fmt" + "image/png" "strconv" gp "github.com/number571/gopeer" "golang.org/x/net/proxy" + "github.com/boombuler/barcode" + "github.com/boombuler/barcode/qr" "html/template" "net/http" "net/url" @@ -186,6 +189,12 @@ func signoutPage(w http.ResponseWriter, r *http.Request) { } func accountPage(w http.ResponseWriter, r *http.Request) { + type AccountTemplateResult struct { + TemplateResult + PublicKey string + PrivateKey string + } + retcod, result := makeResult(RET_SUCCESS, "") t, err := template.ParseFiles( PATH_VIEWS+"base.html", PATH_VIEWS+"account.html", @@ -193,12 +202,33 @@ func accountPage(w http.ResponseWriter, r *http.Request) { if err != nil { panic("error: load account.html") } - if SESSIONS.Get(r) == nil { + user := SESSIONS.Get(r) + if user == nil { http.Redirect(w, r, "/", 302) return } - t.Execute(w, TemplateResult{ - Auth: getName(SESSIONS.Get(r)), + if r.Method == "POST" && r.FormValue("delete") != "" { + name := r.FormValue("username") + pasw := r.FormValue("password") + cuser := DATABASE.GetUser(name, pasw) + switch { + case cuser == nil || cuser.Id != user.Id: + retcod, result = makeResult(RET_DANGER, "username of password incorrect") + default: + SESSIONS.Del(w, r) + DATABASE.DelUser(cuser) + http.Redirect(w, r, "/", 302) + return + } + } + t.Execute(w, AccountTemplateResult{ + TemplateResult: TemplateResult{ + Auth: getName(SESSIONS.Get(r)), + Result: result, + Return: retcod, + }, + PublicKey: gp.PublicKeyToString(&user.Priv.PublicKey), + PrivateKey: gp.PrivateKeyToString(user.Priv), }) } @@ -208,7 +238,18 @@ func accountPublicKeyPage(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "session is nil") return } - fmt.Fprint(w, gp.PublicKeyToString(&user.Priv.PublicKey)) + dataString := gp.PublicKeyToString(&user.Priv.PublicKey) + qrCode, err := qr.Encode(dataString, qr.Q, qr.Auto) + if err != nil { + fmt.Fprint(w, "qrcode generate error") + return + } + qrCode, err = barcode.Scale(qrCode, 768, 768) + if err != nil { + fmt.Fprint(w, "qrcode scale error") + return + } + png.Encode(w, qrCode) } func accountPrivateKeyPage(w http.ResponseWriter, r *http.Request) { @@ -217,7 +258,18 @@ func accountPrivateKeyPage(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "session is nil") return } - fmt.Fprint(w, gp.PrivateKeyToString(user.Priv)) + dataString := gp.PrivateKeyToString(user.Priv) + qrCode, err := qr.Encode(dataString, qr.Q, qr.Auto) + if err != nil { + fmt.Fprint(w, "qrcode generate error") + return + } + qrCode, err = barcode.Scale(qrCode, 768, 768) + if err != nil { + fmt.Fprint(w, "qrcode scale error") + return + } + png.Encode(w, qrCode) } func networkPage(w http.ResponseWriter, r *http.Request) { diff --git a/server.go b/server.go index e7b93fb..13a56c9 100644 --- a/server.go +++ b/server.go @@ -3,6 +3,7 @@ package main import ( "encoding/json" "flag" + "time" "fmt" gp "github.com/number571/gopeer" "net/http" @@ -30,6 +31,12 @@ func init() { } func main() { + go func() { + for { + DATABASE.DelEmailsByDay(1) + time.Sleep(6 * time.Hour) + } + }() http.HandleFunc("/", indexPage) http.HandleFunc("/send", emailSendPage) http.HandleFunc("/recv", emailRecvPage) diff --git a/userside/database.go b/userside/database.go index ab9c9f2..20e6cdd 100644 --- a/userside/database.go +++ b/userside/database.go @@ -3,6 +3,7 @@ package userside import ( "database/sql" "fmt" + "time" "bytes" "strings" "crypto/rsa" @@ -11,7 +12,7 @@ import ( ) const ( - DIFF_ENTR = 20 // bits + DIFF_ENTR = 20 // bits SEPARATOR = "\005\007\001" ) @@ -21,6 +22,8 @@ func NewDB(name string) *DB { return nil } _, err = db.Exec(` +PRAGMA foreign_keys=ON; +PRAGMA secure_delete=ON; CREATE TABLE IF NOT EXISTS users ( id INTEGER, name NVARCHAR(255) UNIQUE, @@ -40,11 +43,13 @@ CREATE TABLE IF NOT EXISTS connects ( CREATE TABLE IF NOT EXISTS emails ( id INTEGER, id_user INTEGER, + deleted BOOLEAN DEFAULT 0, hash VARCHAR(255) UNIQUE, spubl TEXT, sname VARCHAR(255), head VARCHAR(255), body TEXT, + addtime TEXT, PRIMARY KEY(id), FOREIGN KEY(id_user) REFERENCES users(id) ON DELETE CASCADE ); @@ -60,15 +65,22 @@ CREATE TABLE IF NOT EXISTS emails ( func (db *DB) SetUser(name, pasw string, priv *rsa.PrivateKey) error { db.mtx.Lock() defer db.mtx.Unlock() + name = strings.TrimSpace(name) if db.userExist(name) { return fmt.Errorf("user already exist") } salt := gp.GenerateBytes(32) bpasw := RaiseEntropy([]byte(pasw), salt, DIFF_ENTR) - hpasw := gp.HashSum(bpasw) + hpasw := gp.HashSum(bytes.Join( + [][]byte{ + bpasw, + []byte(name), + }, + []byte{}, + )) _, err := db.ptr.Exec( "INSERT INTO users (name, pasw, salt, priv) VALUES ($1, $2, $3, $4)", - name, + gp.Base64Encode(gp.HashSum([]byte(name))), gp.Base64Encode(hpasw), gp.Base64Encode(salt), gp.Base64Encode(gp.EncryptAES(bpasw, gp.PrivateKeyToBytes(priv))), @@ -80,14 +92,15 @@ func (db *DB) GetUser(name, pasw string) *User { db.mtx.Lock() defer db.mtx.Unlock() var ( - id int + id int hpasw string ssalt string spriv string ) + name = strings.TrimSpace(name) row := db.ptr.QueryRow( "SELECT id, pasw, salt, priv FROM users WHERE name=$1", - name, + gp.Base64Encode(gp.HashSum([]byte(name))), ) row.Scan(&id, &hpasw, &ssalt, &spriv) if spriv == "" { @@ -95,7 +108,14 @@ func (db *DB) GetUser(name, pasw string) *User { } salt := gp.Base64Decode(ssalt) bpasw := RaiseEntropy([]byte(pasw), salt, DIFF_ENTR) - if !bytes.Equal(gp.HashSum(bpasw), gp.Base64Decode(hpasw)) { + chpasw := gp.HashSum(bytes.Join( + [][]byte{ + bpasw, + []byte(name), + }, + []byte{}, + )) + if !bytes.Equal(chpasw, gp.Base64Decode(hpasw)) { return nil } priv := gp.BytesToPrivateKey(gp.DecryptAES(bpasw, gp.Base64Decode(spriv))) @@ -106,11 +126,20 @@ func (db *DB) GetUser(name, pasw string) *User { Id: id, Name: name, Pasw: bpasw, - Salt: salt, Priv: priv, } } +func (db *DB) DelUser(user *User) error { + db.mtx.Lock() + defer db.mtx.Unlock() + _, err := db.ptr.Exec( + "DELETE FROM users WHERE id=$1", + user.Id, + ) + return err +} + func (db *DB) GetEmails(user *User, start, quan int) []Email { var ( email *Email @@ -135,13 +164,14 @@ func (db *DB) GetEmail(user *User, id int) *Email { head string body string hash string + atime string ) row := db.ptr.QueryRow( - "SELECT spubl, sname, head, body, hash FROM emails WHERE id_user=$1 ORDER BY id DESC LIMIT 1 OFFSET $2", + "SELECT spubl, sname, head, body, hash, addtime FROM emails WHERE id_user=$1 AND deleted=0 ORDER BY id DESC LIMIT 1 OFFSET $2", user.Id, id, ) - row.Scan(&spubl, &sname, &head, &body, &hash) + row.Scan(&spubl, &sname, &head, &body, &hash, &atime) if spubl == "" { return nil } @@ -152,6 +182,7 @@ func (db *DB) GetEmail(user *User, id int) *Email { SenderName: string(gp.DecryptAES(user.Pasw, gp.Base64Decode(sname))), Head: string(gp.DecryptAES(user.Pasw, gp.Base64Decode(head))), Body: string(gp.DecryptAES(user.Pasw, gp.Base64Decode(body))), + Time: string(gp.DecryptAES(user.Pasw, gp.Base64Decode(atime))), } } @@ -166,13 +197,14 @@ func (db *DB) SetEmail(user *User, pack *gp.Package) error { return fmt.Errorf("len.splited != 2") } _, err := db.ptr.Exec( - "INSERT INTO emails (id_user, hash, spubl, sname, head, body) VALUES ($1, $2, $3, $4, $5, $6)", + "INSERT INTO emails (id_user, hash, spubl, sname, head, body, addtime) VALUES ($1, $2, $3, $4, $5, $6, $7)", user.Id, hashWithSecret(user, pack.Body.Hash), gp.Base64Encode(gp.EncryptAES(user.Pasw, []byte(pack.Head.Sender))), gp.Base64Encode(gp.EncryptAES(user.Pasw, []byte(splited[0]))), gp.Base64Encode(gp.EncryptAES(user.Pasw, []byte(splited[1]))), gp.Base64Encode(gp.EncryptAES(user.Pasw, []byte(pack.Body.Data))), + gp.Base64Encode(gp.EncryptAES(user.Pasw, []byte(time.Now().Format(time.RFC850)))), ) return err } @@ -181,7 +213,7 @@ func (db *DB) DelEmail(user *User, hash string) error { db.mtx.Lock() defer db.mtx.Unlock() _, err := db.ptr.Exec( - "DELETE FROM emails WHERE id_user=$1 AND hash=$2", + "UPDATE emails SET deleted=1, spubl=NULL, sname=NULL, head=NULL, body=NULL, addtime=NULL WHERE id_user=$1 AND hash=$2", user.Id, hash, ) @@ -247,7 +279,7 @@ func (db *DB) userExist(name string) bool { ) row := db.ptr.QueryRow( "SELECT name FROM users WHERE name=$1", - name, + gp.Base64Encode(gp.HashSum([]byte(name))), ) row.Scan(&namee) return namee != "" @@ -284,7 +316,6 @@ func hashWithSecret(user *User, data string) string { [][]byte{ []byte(data), user.Pasw, - user.Salt, }, []byte{}, ))) diff --git a/userside/models.go b/userside/models.go index ba75bda..d451e84 100644 --- a/userside/models.go +++ b/userside/models.go @@ -16,7 +16,6 @@ type User struct { Id int Name string Pasw []byte - Salt []byte Priv *rsa.PrivateKey } @@ -27,6 +26,7 @@ type Email struct { Head string Body string Hash string + Time string } type Sessions struct { diff --git a/userside/views/account.html b/userside/views/account.html index fcc8402..9210692 100644 --- a/userside/views/account.html +++ b/userside/views/account.html @@ -3,19 +3,99 @@ {{ end }} {{ define "main" }} +
- +
-
- + +
+
+ +
+
+
+
+ +
-
- + +
+
+
Public key
+
+
{{ .PublicKey }}
+
+
+
+
Private key
+
+
{{ .PrivateKey }}
+
+
+
+ +
+
+
+ + +
+ {{ end }} diff --git a/userside/views/index.html b/userside/views/index.html index bf6d261..6b939e5 100644 --- a/userside/views/index.html +++ b/userside/views/index.html @@ -6,7 +6,7 @@
HES project
-
version: 1.1.0;
+
version: 1.1.1;
project: hes.git;
author: number571;
@@ -17,6 +17,7 @@
protocol: gopeer.git;
database: go-sqlite3.git;
design: bootstrap.git;
+
qrcode: barcode.git;
{{ end }} diff --git a/userside/views/read.html b/userside/views/read.html index dbb6861..8a03142 100644 --- a/userside/views/read.html +++ b/userside/views/read.html @@ -3,6 +3,18 @@ {{ end }} {{ define "main" }} +
+
+
+ +
+
+
+ + +
+
+
Email
@@ -17,18 +29,35 @@
{{ .Email.SenderPubl }}
-
- +
+
Time
+
+
{{ .Email.Time }}
+
+
+
+
Hash
+
+
{{ .Email.Hash }}
+
{{ end }} diff --git a/userside/views/signup.html b/userside/views/signup.html index d11e662..097551d 100644 --- a/userside/views/signup.html +++ b/userside/views/signup.html @@ -3,6 +3,16 @@ {{ end }} {{ define "main" }} +
@@ -14,21 +24,21 @@
- +
-
+