diff options
Diffstat (limited to 'http-client')
| -rw-r--r-- | http-client/client.go | 57 | ||||
| -rw-r--r-- | http-client/cryptography.go | 60 | ||||
| -rw-r--r-- | http-client/main.go | 97 |
3 files changed, 191 insertions, 23 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() } |