diff options
| author | Andrew <saintruler@gmail.com> | 2021-04-28 20:31:16 +0400 |
|---|---|---|
| committer | Andrew <saintruler@gmail.com> | 2021-04-28 20:31:16 +0400 |
| commit | 8ebbdab5079f803567297af842c87ce012b7ea11 (patch) | |
| tree | ffe28a45797dab60b036eb05e419532cae329171 | |
| parent | 7d6270f64b1dc00d91230b5c793bc49991f0fcf8 (diff) | |
Added cryptography functions and completed client and server.
| -rw-r--r-- | http-client/client.go | 57 | ||||
| -rw-r--r-- | http-client/cryptography.go | 60 | ||||
| -rw-r--r-- | http-client/main.go | 97 | ||||
| -rw-r--r-- | http-server/cryptography.go | 49 | ||||
| -rw-r--r-- | http-server/main.go | 47 | ||||
| -rw-r--r-- | http-tester.py | 32 |
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 |