summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--http-client/client.go57
-rw-r--r--http-client/cryptography.go60
-rw-r--r--http-client/main.go97
-rw-r--r--http-server/cryptography.go49
-rw-r--r--http-server/main.go47
-rw-r--r--http-tester.py32
6 files changed, 260 insertions, 82 deletions
diff --git a/http-client/client.go b/http-client/client.go
index ee3e044..13baf20 100644
--- a/http-client/client.go
+++ b/http-client/client.go
@@ -2,9 +2,11 @@ package main
import (
"bytes"
+ "crypto/rsa"
"crypto/sha256"
"encoding/base64"
"encoding/json"
+ "errors"
"fmt"
"fyne.io/fyne/v2/data/binding"
"io"
@@ -57,28 +59,71 @@ func signData(data []byte) string {
return fmt.Sprintf("%s.%s", data64, signature)
}
-func makeRequest(request Request, apiMethod string) (*http.Response, error) {
- req, _ := json.Marshal(request)
- signedRequest := signData(req)
+func makeRequest(request Request, apiMethod string, key *rsa.PublicKey) (*http.Response, error) {
+ signedRequest, err := signRequest(request, key)
+ if err != nil {
+ return nil, err
+ }
reader := bytes.NewReader([]byte(signedRequest))
return http.Post(URL+"/"+apiMethod, "application/json", reader)
}
-func sendMessage(user UserData, message string) {
+func sendMessage(user UserData, message string, key *rsa.PublicKey) {
// TODO(andrew): Добавить отображение ошибки в интерфейс
_, _ = makeRequest(Request{
User: user.Username,
Data: message,
- }, SendMessage)
+ }, SendMessage, key)
+}
+
+func register(user UserData, serverKey string, key *rsa.PublicKey) (Response, error) {
+ var resp Response
+ httpResp, err := makeRequest(Request{
+ User: user.Username,
+ Data: serverKey,
+ }, Register, key)
+ if err != nil {
+ return resp, err
+ }
+
+ body, _ := io.ReadAll(httpResp.Body)
+ _ = json.Unmarshal(body, &resp)
+
+ if httpResp.StatusCode == http.StatusOK {
+ return resp, nil
+ } else {
+ return resp, errors.New(resp.Message)
+ }
+}
+
+func tryAuth(user UserData, key *rsa.PublicKey) (bool, error) {
+ var resp Response
+ httpResp, err := makeRequest(Request{
+ User: user.Username,
+ Data: "auth",
+ }, TryAuth, key)
+ if err != nil {
+ return false, err
+ }
+
+ body, _ := io.ReadAll(httpResp.Body)
+ _ = json.Unmarshal(body, &resp)
+
+ if httpResp.StatusCode == http.StatusOK {
+ return true, nil
+ } else {
+ return false, errors.New(resp.Message)
+ }
}
func runClient(user UserData) {
lastPoll := time.Now().UnixNano()
+ key, _ := parseKey(user.UserKey)
for {
httpResp, _ := makeRequest(Request{
User: user.Username,
Data: fmt.Sprint(lastPoll),
- }, PollMessages)
+ }, PollMessages, key)
lastPoll = time.Now().UnixNano()
if httpResp.StatusCode == http.StatusOK {
diff --git a/http-client/cryptography.go b/http-client/cryptography.go
new file mode 100644
index 0000000..175e60b
--- /dev/null
+++ b/http-client/cryptography.go
@@ -0,0 +1,60 @@
+package main
+
+import (
+ "crypto/rand"
+ "crypto/rsa"
+ "crypto/sha256"
+ "crypto/x509"
+ "encoding/asn1"
+ "encoding/base64"
+ "encoding/json"
+ "encoding/pem"
+ "fmt"
+)
+
+const KeyBitSize = 4096
+
+// Приватный ключ - отдаётся серверу
+// Публичный ключ - сохраняется на клиенте
+
+func signRequest(req Request, key *rsa.PublicKey) (string, error) {
+ body, _ := json.Marshal(req)
+ based := base64.StdEncoding.EncodeToString(body)
+ h := sha256.Sum256([]byte(based))
+ requestHash := fmt.Sprintf("%x", h)
+
+ ciphertext, err := rsa.EncryptPKCS1v15(rand.Reader, key, []byte(requestHash))
+ if err != nil {
+ return "", nil
+ }
+
+ signature := base64.StdEncoding.EncodeToString(ciphertext)
+ return fmt.Sprintf("%s.%s", based, signature), nil
+}
+
+func parseKey(keyBytes []byte) (*rsa.PublicKey, error) {
+ block, _ := pem.Decode(keyBytes)
+ return x509.ParsePKCS1PublicKey(block.Bytes)
+}
+
+func generateKeys() (private []byte, public []byte, err error) {
+ key, err := rsa.GenerateKey(rand.Reader, KeyBitSize)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var privateKey = &pem.Block{
+ Type: "PRIVATE KEY",
+ Bytes: x509.MarshalPKCS1PrivateKey(key),
+ }
+
+ asn1Bytes, _ := asn1.Marshal(key.PublicKey)
+ var publicKey = &pem.Block{
+ Type: "PUBLIC KEY",
+ Bytes: asn1Bytes,
+ }
+
+ bytePrivate := pem.EncodeToMemory(privateKey)
+ bytePublic := pem.EncodeToMemory(publicKey)
+ return bytePrivate, bytePublic, nil
+}
diff --git a/http-client/main.go b/http-client/main.go
index 14419e5..7d32e9b 100644
--- a/http-client/main.go
+++ b/http-client/main.go
@@ -1,6 +1,8 @@
package main
import (
+ "crypto/rsa"
+ "fmt"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/canvas"
@@ -13,10 +15,13 @@ import (
)
type UserData struct {
- Username string
+ Username string
+ ServerKey []byte
+ UserKey []byte
+ ParsedUserKey *rsa.PublicKey
}
-func chatLayout(window fyne.Window, user UserData, c chan string) {
+func chatLayout(window fyne.Window, user UserData) {
window.SetTitle("Сообщения")
storage.binding = binding.BindStringList(&storage.data)
go runClient(user)
@@ -31,7 +36,7 @@ func chatLayout(window fyne.Window, user UserData, c chan string) {
entry := widget.NewEntry()
submit := widget.NewButton("Отправить", func() {
- go sendMessage(user, entry.Text)
+ go sendMessage(user, entry.Text, user.ParsedUserKey)
entry.SetText("")
storage.RLock()
@@ -49,9 +54,11 @@ func chatLayout(window fyne.Window, user UserData, c chan string) {
))
}
-func loginLayout(window fyne.Window, c chan string) {
+func loginLayout(window fyne.Window, user UserData) {
window.SetTitle("Войти в чат")
+ keyIsSetup := false
+
label := canvas.NewText("Chat", color.White)
label.TextSize = 40
@@ -63,6 +70,7 @@ func loginLayout(window fyne.Window, c chan string) {
if closer == nil {
return
}
+
bufSize := 4096
data := make([]byte, 0)
buf := make([]byte, bufSize)
@@ -73,19 +81,43 @@ func loginLayout(window fyne.Window, c chan string) {
data = append(data, buf...)
}
+ user.ParsedUserKey, err = parseKey(data)
+ if err != nil {
+ dialog.ShowInformation("Ошибка", "Выбранный файл не является ключом", window)
+ return
+ }
+ user.UserKey = data
+ user.ServerKey = nil
+
+ keyIsSetup = true
+
_ = closer.Close()
}, window)
})
loginButton := widget.NewButton("Войти", func() {
- var user = UserData{
- Username: loginEntry.Text,
+ if !keyIsSetup {
+ dialog.ShowInformation("Ошибка", "Не выбран ключ", window)
+ return
}
- chatLayout(window, user, c)
+
+ if len(loginEntry.Text) == 0 {
+ dialog.ShowInformation("Ошибка", "Логин не может быть пустым", window)
+ return
+ }
+
+ user.Username = loginEntry.Text
+ _, err := tryAuth(user, user.ParsedUserKey)
+ if err != nil {
+ dialog.ShowInformation("Ошибка", fmt.Sprint(err), window)
+ return
+ }
+
+ chatLayout(window, user)
})
back := widget.NewButton("Назад", func() {
- startLayout(window, c)
+ startLayout(window, user)
})
mainContainer := container.NewCenter(container.NewVBox(
@@ -98,46 +130,77 @@ func loginLayout(window fyne.Window, c chan string) {
window.SetContent(mainContainer)
}
-func registerLayout(window fyne.Window, c chan string) {
+func registerLayout(window fyne.Window, user UserData) {
window.SetTitle("Регистрация")
+ keyIsSetup := false
+
label := canvas.NewText("Chat", color.White)
label.TextSize = 40
loginLabel := widget.NewLabel("Логин: ")
loginEntry := widget.NewEntry()
- generateKeys := widget.NewButton("Сгенерировать ключи подписи", func() {
+ generateKeysBtn := widget.NewButton("Сгенерировать ключи подписи", func() {
+ dialog.ShowFileSave(func(closer fyne.URIWriteCloser, err error) {
+ if closer == nil {
+ return
+ }
+ private, public, err := generateKeys()
+ if err != nil {
+ dialog.ShowInformation("Ошибка", fmt.Sprint(err), window)
+ return
+ }
+
+ user.ServerKey = private
+ user.UserKey = public
+ user.ParsedUserKey, _ = parseKey(user.UserKey)
+ keyIsSetup = true
+ _, _ = closer.Write(user.UserKey)
+ _ = closer.Close()
+ }, window)
})
registerButton := widget.NewButton("Зарегистрироваться", func() {
+ if !keyIsSetup {
+ dialog.ShowInformation("Ошибка", "Не выбран ключ", window)
+ return
+ }
+
+ user.Username = loginEntry.Text
+ _, err := register(user, string(user.ServerKey), user.ParsedUserKey)
+ if err != nil {
+ dialog.ShowInformation("Ошибка", fmt.Sprint(err), window)
+ return
+ }
+ chatLayout(window, user)
})
back := widget.NewButton("Назад", func() {
- startLayout(window, c)
+ startLayout(window, user)
})
window.SetContent(container.NewCenter(container.NewVBox(
container.NewCenter(label), layout.NewSpacer(),
container.NewBorder(nil, nil, loginLabel, nil, loginEntry),
- generateKeys,
+ generateKeysBtn,
registerButton,
back,
)))
}
-func startLayout(window fyne.Window, c chan string) {
+func startLayout(window fyne.Window, user UserData) {
window.SetTitle("Chat")
label := canvas.NewText("Chat", color.White)
label.TextSize = 40
login := widget.NewButton("Войти", func() {
- loginLayout(window, c)
+ loginLayout(window, user)
})
signup := widget.NewButton("Зарегистрироваться", func() {
- registerLayout(window, c)
+ registerLayout(window, user)
})
mainContainer := container.NewCenter(container.NewVBox(
@@ -148,8 +211,8 @@ func startLayout(window fyne.Window, c chan string) {
func main() {
myApp := app.New()
window := myApp.NewWindow("")
- channel := make(chan string)
+ var user UserData
- startLayout(window, channel)
+ startLayout(window, user)
window.ShowAndRun()
}
diff --git a/http-server/cryptography.go b/http-server/cryptography.go
new file mode 100644
index 0000000..3340446
--- /dev/null
+++ b/http-server/cryptography.go
@@ -0,0 +1,49 @@
+package main
+
+import (
+ "crypto/rand"
+ "crypto/rsa"
+ "crypto/sha256"
+ "crypto/x509"
+ "encoding/base64"
+ "encoding/json"
+ "encoding/pem"
+ "errors"
+ "fmt"
+)
+
+func decodeMessage(ciphertext []byte, stringKey string) ([]byte, error) {
+ block, _ := pem.Decode([]byte(stringKey))
+ if block == nil {
+ return nil, errors.New("key is not found in given string")
+ }
+
+ key, err := x509.ParsePKCS1PrivateKey(block.Bytes)
+ if err != nil {
+ return nil, err
+ }
+
+ plaintext, err := rsa.DecryptPKCS1v15(rand.Reader, key, ciphertext)
+ if err != nil {
+ return nil, err
+ }
+ return plaintext, err
+}
+
+func checkSignature(req Request, signature string, key string) (bool, error) {
+ reqBytes, _ := json.Marshal(req)
+ req64 := base64.StdEncoding.EncodeToString(reqBytes)
+ h := sha256.Sum256([]byte(req64))
+ requestHash := fmt.Sprintf("%x", h)
+
+ decodedSign, err := base64.StdEncoding.DecodeString(signature)
+ if err != nil {
+ return false, err
+ }
+ signHash, err := decodeMessage(decodedSign, key)
+ if err != nil {
+ return false, err
+ }
+
+ return requestHash == string(signHash), nil
+}
diff --git a/http-server/main.go b/http-server/main.go
index f77c32f..d7b2625 100644
--- a/http-server/main.go
+++ b/http-server/main.go
@@ -1,7 +1,6 @@
package main
import (
- "crypto/sha256"
"encoding/base64"
"encoding/json"
"errors"
@@ -14,8 +13,6 @@ import (
"time"
)
-const TimestampFormat = "2006-01-02T15-01-05.999"
-
type Response struct {
Message string
}
@@ -116,8 +113,8 @@ func register(w http.ResponseWriter, r *http.Request) {
return
}
- // TODO(andrew): Добавить проверку действительности ключа
- if false {
+ checkResult, err := checkSignature(req, signature, req.Data)
+ if err != nil || !checkResult {
w.WriteHeader(http.StatusBadRequest)
_ = jsonResponse(w, Response{
Message: "Указанный ключ не является действительным",
@@ -139,32 +136,27 @@ func register(w http.ResponseWriter, r *http.Request) {
})
}
-func handleAuthentication(req Request, signature string) (error, error) {
+func handleAuthentication(req Request, signature string) (bool, error) {
userRegistered, dbError := db.checkUserRegistered(req.User)
if dbError != nil {
- return nil, dbError
+ return false, dbError
}
if !userRegistered {
- return errors.New("такой пользователь не зарегистрирован"), nil
+ return false, nil
}
key, err := db.getUserKey(req.User)
if err != nil {
- return nil, err
+ return false, err
}
- // TODO(andrew): Добавить проверку подписи signature
- reqBytes, _ := json.Marshal(req)
- req64 := base64.StdEncoding.EncodeToString(reqBytes)
- h := sha256.Sum256([]byte(req64))
- generatedSignature := fmt.Sprintf("%x", h)
- //fmt.Println(generatedSignature)
- //fmt.Println(signature)
- _ = fmt.Sprint(generatedSignature == signature)
- _ = fmt.Sprint(key)
+ check, err := checkSignature(req, signature, key)
+ if err != nil {
+ return false, err
+ }
- return nil, nil
+ return check, nil
}
func sendMessage(w http.ResponseWriter, r *http.Request) {
@@ -178,8 +170,8 @@ func sendMessage(w http.ResponseWriter, r *http.Request) {
return
}
- authErr, dbErr := handleAuthentication(req, signature)
- if authErr != nil {
+ authComplete, dbErr := handleAuthentication(req, signature)
+ if !authComplete {
w.WriteHeader(http.StatusForbidden)
_ = jsonResponse(w, Response{
Message: "Запрос не прошёл аутентификацию",
@@ -224,8 +216,8 @@ func pollMessages(w http.ResponseWriter, r *http.Request) {
return
}
- authErr, dbErr := handleAuthentication(req, signature)
- if authErr != nil {
+ authComplete, dbErr := handleAuthentication(req, signature)
+ if !authComplete {
w.WriteHeader(http.StatusForbidden)
_ = jsonResponse(w, Response{
Message: "Запрос не прошёл аутентификацию",
@@ -273,8 +265,9 @@ func getUserKey(w http.ResponseWriter, r *http.Request) {
return
}
- authErr, dbErr := handleAuthentication(req, signature)
- if authErr != nil {
+ authComplete, dbErr := handleAuthentication(req, signature)
+ if !authComplete {
+
w.WriteHeader(http.StatusForbidden)
_ = jsonResponse(w, Response{
Message: "Запрос не прошёл аутентификацию",
@@ -317,8 +310,8 @@ func tryAuth(w http.ResponseWriter, r *http.Request) {
return
}
- authErr, dbErr := handleAuthentication(req, signature)
- if authErr != nil {
+ authComplete, dbErr := handleAuthentication(req, signature)
+ if !authComplete {
w.WriteHeader(http.StatusForbidden)
_ = jsonResponse(w, Response{
Message: "Запрос не прошёл аутентификацию",
diff --git a/http-tester.py b/http-tester.py
deleted file mode 100644
index 7cb5197..0000000
--- a/http-tester.py
+++ /dev/null
@@ -1,32 +0,0 @@
-from hashlib import sha256
-from base64 import b64encode
-import json
-import requests
-
-
-URL = "http://localhost:8080/api"
-
-
-def sign_data(data):
- dump = json.dumps(data, separators=[",", ":"])
- print(dump)
- payload = b64encode(dump.encode())
- print(payload.decode())
- signature = sha256(payload).hexdigest()
- print(signature)
- return f"{payload.decode()}.{signature}"
-
-
-def test_get_user_key():
- data = {
- "User": "ivan",
- "Data": "andrew"
- }
-
- signed_data = sign_data(data)
- resp = requests.post(f"{URL}/getUserKey", data=signed_data)
- print(resp.status_code)
- print(resp.json())
-
-
-test_get_user_key() \ No newline at end of file