update v1.1.1

This commit is contained in:
Number571 2021-03-19 13:39:24 +07:00
parent 472748b7dd
commit 6d7ff3cc89
12 changed files with 273 additions and 44 deletions

2
.gitignore vendored
View File

@ -1,4 +1,6 @@
server
gclient
gclient.exe
server.exe
server.db
client.db

View File

@ -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

View File

@ -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
);

View File

@ -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()

View File

@ -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) {

View File

@ -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)

View File

@ -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{},
)))

View File

@ -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 {

View File

@ -3,19 +3,99 @@
{{ end }}
{{ define "main" }}
<div id="message_copy" class="text-light mx-auto"></div>
<div class="form-group">
<button type="button" class="btn btn-secondary divtext w-100" disabled>{{ .Auth }}</button>
<button type="button" class="btn btn-secondary divtext w-100" disabled><h5>{{ .Auth }}</h5></button>
</div>
<div class="form-group row">
<div class="col-md-6 w-50">
<form class="text-center" method="POST" action="/account/private_key">
<input type="submit" name="submit" value="Look private key" class="btn btn-success w-100">
<button type="button" class="btn btn-success w-100" onclick="copy_public_key()">CP public key</button>
</div>
<div class="col-md-6 w-50">
<button type="button" class="btn btn-success w-100" onclick="copy_private_key()">CP private key</button>
</div>
</div>
<div class="form-group row">
<div class="col-md-6 w-50">
<form class="text-center" method="POST" action="/account/public_key">
<input type="submit" name="submit" value="QR public key" class="btn btn-info w-100">
</form>
</div>
<div class="col-md-6 w-50">
<form class="text-center" method="POST" action="/account/public_key">
<input type="submit" name="submit" value="Look public key" class="btn btn-success w-100">
<form class="text-center" method="POST" action="/account/private_key">
<input type="submit" name="submit" value="QR private key" class="btn btn-info w-100">
</form>
</div>
</div>
<div class="card text-white bg-dark mb-3">
<h5 class="card-header bg-secondary">Public key</h5>
<div class="card-body">
<h6 class="card-text">{{ .PublicKey }}</h6>
</div>
</div>
<div class="card text-white bg-dark mb-3">
<h5 class="card-header bg-secondary">Private key</h5>
<div class="card-body">
<h6 class="card-text">{{ .PrivateKey }}</h6>
</div>
</div>
<div class="form-group">
<button type="button" class="btn btn-danger w-100" onclick="hide_input()">Delete account</button>
</div>
<div id="input_password" class="form-group"></div>
<div style="opacity:0">
<input id="private_key" type="text" value="{{ .PrivateKey }}">
<input id="public_key" type="text" value="{{ .PublicKey }}">
</div>
<script type="text/javascript">
function copy_public_key() {
var copyText = document.getElementById("public_key");
copyText.select();
document.execCommand("copy");
let newInput = `
<div class="alert alert-success" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
Public key copied
</div>
`
document.getElementById("message_copy").innerHTML = newInput;
}
function copy_private_key() {
var copyText = document.getElementById("private_key");
copyText.select();
document.execCommand("copy");
let newInput = `
<div class="alert alert-success" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
Private key copied
</div>
`
document.getElementById("message_copy").innerHTML = newInput;
}
function hide_input() {
let elem = document.getElementById("password-form");
if (elem) {
document.getElementById("password-form").remove();
} else {
let newInput = `
<form id='password-form' class="text-center" method="POST" action="/account">
<div class="form-group">
<input type='text' class='form-control bg-dark text-light' name='username' placeholder='Username'>
</div>
<div class="form-group">
<input type='password' class='form-control bg-dark text-light' name='password' placeholder='Password'>
</div>
<div class="form-group">
<input type="submit" name="delete" value="Confirm deletion " class="btn btn-warning w-100">
</div>
</form>
`;
document.getElementById("input_password").innerHTML = newInput;
}
}
</script>
{{ end }}

View File

@ -6,7 +6,7 @@
<div class="card text-white bg-dark mb-3">
<h5 class="card-header bg-secondary">HES project</h5>
<div class="card-body">
<h6 class="card-text">version: 1.1.0;</h6>
<h6 class="card-text">version: 1.1.1;</h6>
<h6 class="card-text">project: <a target="_blank" class="text-info" href="https://github.com/Number571/HES">hes.git</a>;</h6>
<h6 class="card-text">author: <a target="_blank" class="text-info" href="https://github.com/Number571">number571</a>;</h6>
</div>
@ -17,6 +17,7 @@
<h6 class="card-text">protocol: <a target="_blank" class="text-info" href="https://github.com/Number571/gopeer">gopeer.git</a>;</h6>
<h6 class="card-text">database: <a target="_blank" class="text-info" href="https://github.com/mattn/go-sqlite3">go-sqlite3.git</a>;</h6>
<h6 class="card-text">design: <a target="_blank" class="text-info" href="https://github.com/twbs/bootstrap">bootstrap.git</a>;</h6>
<h6 class="card-text">qrcode: <a target="_blank" class="text-info" href="https://github.com/boombuler/barcode">barcode.git</a>;</h6>
</div>
</div>
{{ end }}

View File

@ -3,6 +3,18 @@
{{ end }}
{{ define "main" }}
<div id="message_copy" class="text-light mx-auto"></div>
<div class="form-group row">
<div class="col-md-6 w-50">
<button type="button" class="btn btn-success w-100" onclick="copy_public_key()">Copy public key</button>
</div>
<div class="col-md-6 w-50">
<form class="text-center" method="POST" action="/network">
<input type="hidden" name="email" value="{{ .Email.Hash }}">
<input type="submit" name="delete" value="Delete" class="btn btn-danger w-100">
</form>
</div>
</div>
<div class="card text-white bg-dark mb-3">
<h5 class="card-header bg-secondary">Email</h5>
<div class="card-body">
@ -17,18 +29,35 @@
<h6 class="card-text">{{ .Email.SenderPubl }}</h6>
</div>
</div>
<div class="form-group">
<button type="button" class="btn btn-success w-100" onclick="copy()">Copy public key</button>
<div class="card text-white bg-dark mb-3">
<h5 class="card-header bg-secondary">Time</h5>
<div class="card-body">
<h6 class="card-text">{{ .Email.Time }}</h6>
</div>
</div>
<div class="card text-white bg-dark mb-3">
<h5 class="card-header bg-secondary">Hash</h5>
<div class="card-body">
<h6 class="card-text">{{ .Email.Hash }}</h6>
</div>
</div>
<div style="opacity:0">
<input id="public_key" type="text" value="{{ .Email.SenderPubl }}">
</div>
<script type="text/javascript">
function copy() {
function copy_public_key() {
var copyText = document.getElementById("public_key");
copyText.select();
document.execCommand("copy");
alert('Public key copied');
let newInput = `
<div class="alert alert-success" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
Public key copied
</div>
`
document.getElementById("message_copy").innerHTML = newInput;
}
</script>
{{ end }}

View File

@ -3,6 +3,16 @@
{{ end }}
{{ define "main" }}
<div class="alert alert-warning" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<div>
The server stores all received information, including private keys.
<br>
Be careful when logging in to the global network.
</div>
</div>
<form id="feedbackForm" class="text-center" method="POST" action="/signup">
<div class="form-group">
<input type="text" class="form-control bg-dark text-light" name="username" placeholder="Username">
@ -14,21 +24,21 @@
<input type="password" class="form-control bg-dark text-light" name="password_repeat" placeholder="Repeat Password">
</div>
<div class="form-group">
<button type="button" class="btn btn-danger w-100" onclick="hide()">Load private key</button>
<button type="button" class="btn btn-danger w-100" onclick="hide_input()">Load private key</button>
</div>
<div id="input-priv-key" class="form-group"></div>
<div id="input_private_key" class="form-group"></div>
<div class="form-group">
<input type="submit" name="submit" value="Create account" id="feedbackSubmit" class="btn btn-success w-100">
</div>
</form>
<script type="text/javascript">
function hide() {
let elem = document.getElementById("priv-key");
function hide_input() {
let elem = document.getElementById("private-key");
if (elem) {
document.getElementById("priv-key").remove();
document.getElementById("private-key").remove();
} else {
let newInput = "<textarea id='priv-key' class='form-control bg-dark text-light' name='private_key' placeholder='Private key'></textarea>";
document.getElementById("input-priv-key").innerHTML = newInput;
let newInput = "<textarea id='private-key' class='form-control bg-dark text-light' name='private_key' placeholder='Private key'></textarea>";
document.getElementById("input_private_key").innerHTML = newInput;
}
}
</script>