Skip to content

Commit

Permalink
finalizing new protocol with tests
Browse files Browse the repository at this point in the history
8 minute of timeout for a single connection can lead to DOS attacks. May need further improvements but creating a new connection every time is not the solution
  • Loading branch information
virgula0 committed Jan 27, 2025
1 parent 178dbbc commit eb7cf48
Show file tree
Hide file tree
Showing 9 changed files with 279 additions and 83 deletions.
31 changes: 18 additions & 13 deletions raspberry-pi/internal/daemon/communication.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,37 @@ package daemon
import (
"bufio"
"encoding/json"
"errors"
"fmt"
"github.com/Virgula0/progetto-dp/raspberrypi/internal/entities"
"github.com/Virgula0/progetto-dp/raspberrypi/internal/enums"
"github.com/Virgula0/progetto-dp/raspberrypi/internal/utils"
log "github.com/sirupsen/logrus"
"strings"
"time"
)

type Communicate struct {
Client *Client
}

/*
Authenticator
Uses provided credentials for authenticating the user via REST/API each hour
Uses provided credentials for authenticating the user via TCP each hour
*/
func (r *RaspberryPiInfo) Authenticator(wr *Communicate) {
ticker := time.NewTicker(1 * time.Hour) // every hour

for {
// 1. Send login request
err := wr.writeToServerCommand(enums.LOGIN)
err := wr.Client.writeToServerCommand(enums.LOGIN)

if err != nil {
log.Fatalf("[RSP-PI] Failed to write command to the server: %s", err.Error())
}

if jwt, err := wr.writeToServerAuthRequest(r.Credentials); err == nil {
if jwt, err := wr.Client.writeToServerAuthRequest(r.Credentials); err == nil {
*r.JWT = jwt
r.FirstLogin <- true
} else {
Expand All @@ -38,10 +44,6 @@ func (r *RaspberryPiInfo) Authenticator(wr *Communicate) {
}
}

type Communicate struct {
*Client
}

func (c *Client) readFromServer() (string, error) {
// bufio.NewReader(c.Conn).ReadString('\n') this includes the \n at the end as result
ss, err := bufio.NewReader(c.Conn).ReadString('\n')
Expand Down Expand Up @@ -125,14 +127,17 @@ func (c *Client) writeToServerAuthRequest(request *entities.AuthRequest) (string
return "", err
}

// read token
token, err := c.readFromServer()
// read token or error
msg, err := c.readFromServer()
if err != nil {
return "", err
}

if !utils.IsJWT(msg) {
return "", errors.New(msg)
}

return token, nil
return msg, nil
}

func (c *Client) writeToServerHandshake(request entities.TCPCreateRaspberryPIRequest) (int, error) {
Expand Down Expand Up @@ -192,7 +197,7 @@ func (c *Client) readACKFromServer() error {
// HandleServerCommunication handles data exchange with the server.
func (c *Communicate) HandleServerCommunication(instance *RaspberryPiInfo, machineID string, handshakes []*entities.Handshake) error {
// 1. Send hadnshake request
err := c.writeToServerCommand(enums.HANDSHAKE)
err := c.Client.writeToServerCommand(enums.HANDSHAKE)

if err != nil {
return fmt.Errorf("[RSP-PI] Failed to write command to the server: %s", err.Error())
Expand All @@ -205,13 +210,13 @@ func (c *Communicate) HandleServerCommunication(instance *RaspberryPiInfo, machi
EncryptionKey: "",
}

wrote, err := c.writeToServerHandshake(request)
wrote, err := c.Client.writeToServerHandshake(request)
if err != nil {
return fmt.Errorf("[RSP-PI] Failed to write to server: %s", err.Error())
}
log.Printf("[RSP-PI] Wrote %v bytes", wrote)

response, err := c.readFromServer()
response, err := c.Client.readFromServer()
if err != nil {
return fmt.Errorf("[RSP-PI] Failed to write to server: %s", err.Error())
}
Expand Down
5 changes: 5 additions & 0 deletions raspberry-pi/internal/daemon/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@ import (
"github.com/Virgula0/progetto-dp/raspberrypi/internal/constants"
"github.com/Virgula0/progetto-dp/raspberrypi/internal/entities"
"net"
"time"
)

var timeout = time.Second * 30

type RaspberryPiInfo struct {
JWT *string
FirstLogin chan bool
Expand All @@ -19,9 +22,11 @@ type Client struct {

func InitClientConnection() (*Client, error) {
conn, err := net.Dial("tcp", fmt.Sprintf("%s:%s", constants.TCPAddress, constants.TCPPort))

if err != nil {
return nil, err
}

return &Client{
Conn: conn,
}, nil
Expand Down
19 changes: 19 additions & 0 deletions raspberry-pi/internal/utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/Virgula0/progetto-dp/raspberrypi/internal/constants"
"os"
"path/filepath"
"strings"
)

func BytesToBase64String(b []byte) string {
Expand Down Expand Up @@ -77,3 +78,21 @@ func MachineID() (string, error) {

return fmt.Sprintf("%x", md5.Sum(bytes)), nil // #nosec G401
}

// IsJWT checks if a string is in valid JWT format.
func IsJWT(t string) bool {
parts := strings.Split(t, ".")
if len(parts) != 3 {
return false
}

for _, part := range parts {
// base64 decode should not return an error.
_, err := base64.RawURLEncoding.DecodeString(part)
if err != nil {
return false
}
}

return true
}
1 change: 1 addition & 0 deletions raspberry-pi/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ func main() {
log.Errorf("error while sending handshake to the server %s", err.Error())
return
}

<-ticker.C
}
}
3 changes: 3 additions & 0 deletions server/backend/internal/errors/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ var ErrOnUpdateTask = errors.New("[GRPC]: HashcatChat -> Cannot update client ta
var ErrCannotAnswerToClient = errors.New("[GRPC]: HashcatChat -> Cannot reply to the client -> ")
var ErrGetHandshakeStatus = errors.New("[GRPC]: HashcatChat GetHandshakesByStatus -> ")

// Daemon
var ErrHandshakeAlreadyPresent = errors.New("error creating handshake: handshake already present")

// SQL
const (
ErrCodeDuplicateEntry = 1062 // MySQL error code for duplicate entry
Expand Down
43 changes: 32 additions & 11 deletions server/backend/internal/raspberrypi/components.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,21 @@ import (
"github.com/Virgula0/progetto-dp/server/entities"
"github.com/go-sql-driver/mysql"
log "github.com/sirupsen/logrus"
"golang.org/x/crypto/bcrypt"
"io"
"net"
"strconv"
"strings"
"sync"
)

var readMessageMutex sync.Mutex
var writeMessageMutex sync.Mutex

// readMessageSize the first message from the client is the length of the content will be sent so we can initialize a buffer
func (wr *TCPServer) readMessageSize(reader *bufio.Reader) (int64, error) {
readMessageMutex.Lock()
defer readMessageMutex.Unlock()
lengthOfMessage, err := reader.ReadString('\n')
if err != nil {
return 0, err
Expand All @@ -29,11 +36,23 @@ func (wr *TCPServer) readMessageSize(reader *bufio.Reader) (int64, error) {

// readMessageContent read the real message from the client
func (wr *TCPServer) readMessageContent(reader *bufio.Reader, size int64) ([]byte, error) {
readMessageMutex.Lock()
defer readMessageMutex.Unlock()
buffer := make([]byte, size)
_, err := io.ReadFull(reader, buffer)
return buffer, err
}

// writeErrorToClient refactored function to send error whenever happens to the client
func (wr *TCPServer) writeErrorToClient(client net.Conn, message string) {
writeMessageMutex.Lock()
defer writeMessageMutex.Unlock()
_, err := client.Write([]byte(message + "\n"))
if err != nil {
log.Errorf("[TCP/IP] Error writing to client: %s", err.Error())
}
}

func (wr *TCPServer) processLoginMessage(buffer []byte, client net.Conn) error {
var loginRequest entities.AuthRequest

Expand All @@ -56,10 +75,17 @@ func (wr *TCPServer) processLoginMessage(buffer []byte, client net.Conn) error {
return err
}

// Compare password
err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(loginRequest.Password))
if err != nil {
wr.writeErrorToClient(client, fmt.Sprintf("login failed: %s", customErrors.ErrInvalidCredentials))
return fmt.Errorf("login failed, wrong password for user %s", user.Username)
}

// Create the auth token
token, err := wr.usecase.CreateAuthToken(user.UserUUID, role.RoleString)
if err != nil {
wr.writeErrorToClient(client, fmt.Sprintf("login failed: %s", err.Error()))
wr.writeErrorToClient(client, fmt.Sprintf("login failed: %s", customErrors.ErrInvalidCredentials))
return err
}

Expand Down Expand Up @@ -114,7 +140,10 @@ func (wr *TCPServer) processHandshakes(request TCPCreateRaspberryPIRequest) ([]s
}
handshakeID, err := wr.createHandshake(request.Jwt, handshake)
if err != nil {
return nil, fmt.Errorf("error creating handshake: %w", err)
if errors.Is(err, customErrors.ErrHandshakeAlreadyPresent) {
return nil, err
}
return nil, fmt.Errorf("error creating handshake: %l", err)
}
handshakeSavedIDs = append(handshakeSavedIDs, handshakeID)
}
Expand Down Expand Up @@ -142,14 +171,6 @@ func (wr *TCPServer) handleCreationError(err error, client net.Conn) error {
return err
}

// writeErrorToClient refactored function to send error whenever happens to the client
func (wr *TCPServer) writeErrorToClient(client net.Conn, message string) {
_, err := client.Write([]byte(message + "\n"))
if err != nil {
log.Errorf("[TCP/IP] Error writing to client: %s", err.Error())
}
}

// createRaspberryPI create a raspberrypi entity in the database if it does not exist
func (wr *TCPServer) createRaspberryPI(request *TCPCreateRaspberryPIRequest) (result []byte, err error) {

Expand Down Expand Up @@ -184,7 +205,7 @@ func (wr *TCPServer) createHandshake(jwt string, handshake *entities.Handshake)
}

if saved > 0 { // we don't save the handshake if already saved
return "", fmt.Errorf("handshake already present")
return "", customErrors.ErrHandshakeAlreadyPresent
}

// TODO: use encryption key of the raspberryPI for exchanging handshakes bytes securely
Expand Down
14 changes: 8 additions & 6 deletions server/backend/internal/raspberrypi/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ import (
)

type TCPServer struct {
w net.Listener
timeout time.Duration
usecase *usecase.Usecase
l net.Listener
timeout time.Duration
sleepTime time.Duration
usecase *usecase.Usecase
TCPHandler
}

Expand All @@ -24,8 +25,9 @@ func NewTCPServer(service *handlers.ServiceHandler, address, port string) (*TCPS
}

return &TCPServer{
w: conn,
usecase: service.Usecase,
timeout: 30 * time.Second,
l: conn,
usecase: service.Usecase,
timeout: 8 * time.Minute, // if client latest operation was greater than timeout min, server closes connection
sleepTime: 200 * time.Millisecond,
}, nil
}
Loading

0 comments on commit eb7cf48

Please sign in to comment.