diff --git a/Projs/PD2/go.mod b/Projs/PD2/go.mod index 5dd0d53..1d04cdb 100644 --- a/Projs/PD2/go.mod +++ b/Projs/PD2/go.mod @@ -4,6 +4,7 @@ go 1.22.2 require ( github.com/gin-gonic/gin v1.10.0 + github.com/golang-jwt/jwt/v4 v4.5.0 github.com/mattn/go-sqlite3 v1.14.22 golang.org/x/crypto v0.23.0 software.sslmate.com/src/go-pkcs12 v0.4.0 diff --git a/Projs/PD2/go.sum b/Projs/PD2/go.sum index d1fbebb..50a2b12 100644 --- a/Projs/PD2/go.sum +++ b/Projs/PD2/go.sum @@ -25,6 +25,8 @@ github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBEx github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= +github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= diff --git a/Projs/PD2/internal/gateway/datastore.go b/Projs/PD2/internal/gateway/datastore.go new file mode 100644 index 0000000..725b688 --- /dev/null +++ b/Projs/PD2/internal/gateway/datastore.go @@ -0,0 +1,52 @@ +package gateway + +import( + "database/sql" + _ "github.com/mattn/go-sqlite3" +) + +type DataStore struct { + db *sql.DB +} + +func OpenDB() (DataStore, error) { + db, err := sql.Open("sqlite3", "gateway.db") + if err != nil { + return DataStore{}, err + } + ds := DataStore{db: db} + err = ds.CreateTables() + if err != nil { + return DataStore{}, err + } + return ds, nil +} + +func (ds DataStore) CreateTables() error { + // Create users table + _, err := ds.db.Exec(`CREATE TABLE IF NOT EXISTS users ( + UID TEXT PRIMARY KEY, + password BLOB + )`) + if err != nil { + return err + } + return nil +} + +func (ds DataStore) GetPassword(uid string) ([]byte, error) { + var password []byte + err := ds.db.QueryRow("SELECT password FROM users WHERE UID = ?", uid).Scan(&password) + if err != nil { + return nil, err + } + return password, nil +} + +func (ds DataStore) InsertUser(uid string, password []byte) error { + _, err := ds.db.Exec("INSERT INTO users (UID, password) VALUES (?, ?)", uid, password) + if err != nil { + return err + } + return nil +} diff --git a/Projs/PD2/internal/gateway/gateway.go b/Projs/PD2/internal/gateway/gateway.go index 14464d4..cd32e3f 100644 --- a/Projs/PD2/internal/gateway/gateway.go +++ b/Projs/PD2/internal/gateway/gateway.go @@ -2,46 +2,140 @@ package gateway import ( "PD1/internal/protocol" + "PD1/internal/utils/cryptoUtils" + "crypto/x509" "fmt" + "log" "net/http" "github.com/gin-gonic/gin" ) -func HandleGetMessage(c *gin.Context){ - fmt.Println("Get Message Handler") +func HandleGetMessage(c *gin.Context) { + fmt.Println("Get Message Handler") } -func HandleGetUnreadMsgsInfo(c *gin.Context){ - fmt.Println("Get Unread Messages Info Handler") +func HandleGetUnreadMsgsInfo(c *gin.Context) { + fmt.Println("Get Unread Messages Info Handler") } -func HandleGetUserCert(c *gin.Context){ - fmt.Println("Get User Cert Handler") +func HandleGetUserCert(c *gin.Context) { + fmt.Println("Get User Cert Handler") } -func HandleSendMessage(c *gin.Context){ - fmt.Println("Send Message Handler") +func HandleSendMessage(c *gin.Context) { + fmt.Println("Send Message Handler") } -func HandleRegister(c *gin.Context){ - var postRegister protocol.PostRegister - c.Bind(postRegister) - //Hash the password (using salt) and store it on the db - //Get the username from the certificate +func HandleRegister(c *gin.Context, dataStore DataStore, keyStore cryptoUtils.KeyStore) { + var postRegister protocol.PostRegister + err := c.Bind(postRegister) + if err != nil { + c.JSON(http.StatusBadRequest,gin.H{"error": "Request body is not a PostRegister"}) + return + } + + //Check if the certificate pseudonym matches the uid in postRegister + //And if it's signed by the CA + userCert, err := x509.ParseCertificate(postRegister.Certificate) + if err != nil { + c.JSON(http.StatusBadRequest,gin.H{"error": "User certificate is invalid"}) + return + } + + oidMap := cryptoUtils.ExtractAllOIDValues(userCert) + //Check if certificate usage is MSG SERVICE + usage := oidMap["2.5.4.11"] + if usage != "MSG SERVICE" { + c.JSON(http.StatusBadRequest,gin.H{"error": "Certificate usage is not \"MSG SERVICE\""}) + return + } + + err = keyStore.CheckCert(userCert, postRegister.UID) + if err != nil { + c.JSON(http.StatusBadRequest,gin.H{"error": "User certificate is invalid, not trusted or belongs to another user"}) + return + } + + hashedPassword, err := HashPassword(postRegister.Password) + if err != nil { + log.Fatalln("Could not hash the password") + } + + err = dataStore.InsertUser(postRegister.UID, hashedPassword) + if err != nil { + log.Fatalln("Could not insert user into DB") + } + + //TODO: Send the certificate to the server + + c.JSON(http.StatusOK, gin.H{}) + } -func AuthMiddleware(c *gin.Context){ - fmt.Println("Authentication Middleware") +func HandleLogin(c *gin.Context, dataStore DataStore, keyStore cryptoUtils.KeyStore) { + var postLogin protocol.PostLogin + err := c.Bind(postLogin) + if err != nil { + c.AbortWithStatus(http.StatusBadRequest) + } + + hashedPassword, err := dataStore.GetPassword(postLogin.UID) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid user id or password"}) + return + } + err = CheckPassword(hashedPassword, postLogin.Password) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid user id or password"}) + return + } + jwToken, err := GenerateJWT(postLogin.UID) + + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Failed to create token"}) + } + //Send token to user + c.JSON(http.StatusOK, gin.H{"token":jwToken}) } -func Run(){ - router := gin.Default() - auth := router.Group("/", func(c *gin.Context) { - AuthMiddleware(c) - }) +func AuthMiddleware(c *gin.Context) { + fmt.Println("Authentication Middleware") + tokenList := c.Request.Header["Token"] + if tokenList == nil { + c.JSON(http.StatusUnauthorized,gin.H{"error": "No authentication token provided"}) + } + // We only care about the first entry + token := tokenList[0] + + uid, err := ValidateJWT(token) + if err!= nil { + c.JSON(http.StatusUnauthorized,gin.H{"error": "Token is invalid or has expired"}) + } + c.Set("uid", uid) + c.Next() +} + +func Run() { + dataStore, err := OpenDB() + if err != nil { + log.Fatalln("Error opening the database") + } + defer dataStore.db.Close() + + passphrase := readStdin("Insert keystore passphrase") + keyStore, err := cryptoUtils.LoadKeyStore("certs/gateway/gateway.p12", passphrase) + if err != nil { + log.Fatalln(err.Error()) + } + + router := gin.Default() + + auth := router.Group("/", func(c *gin.Context) { + AuthMiddleware(c) + }) auth.GET("/message/:num", func(c *gin.Context) { HandleGetMessage(c) @@ -59,19 +153,22 @@ func Run(){ HandleSendMessage(c) }) - router.POST("/register",func(c *gin.Context) { - HandleRegister(c) - }) + router.POST("/register", func(c *gin.Context) { + HandleRegister(c, dataStore, keyStore) + }) + + router.POST("/login", func(c *gin.Context) { + HandleLogin(c, dataStore, keyStore) + }) server := http.Server{ Addr: "0.0.0.0:8080", Handler: router, - //TODO: Verify if it's the gateway TLSConfig: serverKeyStore.GetServerTLSConfig(), } - err = server.ListenAndServeTLS("", "") - if err!=nil { - log.Fatal(err.Error()) - } + err := server.ListenAndServeTLS("", "") + if err != nil { + log.Fatal(err.Error()) + } } diff --git a/Projs/PD2/internal/gateway/interface.go b/Projs/PD2/internal/gateway/interface.go new file mode 100644 index 0000000..4bb654e --- /dev/null +++ b/Projs/PD2/internal/gateway/interface.go @@ -0,0 +1,14 @@ +package gateway + +import ( + "bufio" + "fmt" + "os" +) + +func readStdin(message string) string { + fmt.Println(message) + scanner := bufio.NewScanner(os.Stdin) + scanner.Scan() + return scanner.Text() +} diff --git a/Projs/PD2/internal/gateway/jwt.go b/Projs/PD2/internal/gateway/jwt.go new file mode 100644 index 0000000..f3bba7e --- /dev/null +++ b/Projs/PD2/internal/gateway/jwt.go @@ -0,0 +1,62 @@ +package gateway + +import ( + "errors" + "os" + "time" + + "github.com/golang-jwt/jwt/v4" + "golang.org/x/crypto/bcrypt" +) + +// HashPassword hashes the given password and returns the hashed password as a byte slice. +func HashPassword(password string) ([]byte, error) { + hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) + if err != nil { + return nil, err + } + return hashedPassword, nil +} + +// CheckPassword compares a bcrypt hashed password with its possible plaintext equivalent. +func CheckPassword(hashedPassword []byte, password string) error { + return bcrypt.CompareHashAndPassword(hashedPassword, []byte(password)) +} + +// GenerateJWT generates a JWT token with a specified user ID and expiry time. +func GenerateJWT(uid string) (string, error) { + claims := &jwt.MapClaims{ + "sub": uid, + "exp": time.Now().Add(time.Hour * 24).Unix(), + } + + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + tokenString, err := token.SignedString([]byte(os.Getenv("SECRET_KEY"))) + if err != nil { + return "", err + } + return tokenString, nil +} + +// ValidateJWT validates the given JWT token and returns the user id if valid. +func ValidateJWT(tokenString string) (string, error) { + token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { + return []byte(os.Getenv("SECRET_KEY")), nil + }) + + if err != nil { + return "", err + } + + if !token.Valid { + return "", errors.New("invalid token") + } + if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { + if time.Now().Unix() > claims["exp"].(int64) { + return "", errors.New("JWT token has expired") + } + return claims["sub"].(string),nil + } else { + return "",errors.New("Failed to get jwt claims") + } +} diff --git a/Projs/PD2/internal/protocol/protocol.go b/Projs/PD2/internal/protocol/protocol.go index fdaf7c2..c0b516a 100644 --- a/Projs/PD2/internal/protocol/protocol.go +++ b/Projs/PD2/internal/protocol/protocol.go @@ -53,10 +53,16 @@ type ( } PostRegister struct { + UID string `json:"uid"` Password string `json:"password"` Certificate []byte `json:"certificate"` } + PostLogin struct { + UID string `json:"uid"` + Password string `json:"password"` + } + ReportError struct { ErrorMessage string `json:"error"` } @@ -107,6 +113,21 @@ func NewMsgInfo(num int, fromUID string, subject []byte, timestamp time.Time) Ms } } +func NewPostRegister(UID string,Password string,Certificate []byte) PostRegister { + return PostRegister{ + UID: UID, + Password: Password, + Certificate: Certificate, + } +} + +func NewPostLogin(UID string,Password string) PostLogin { + return PostLogin{ + UID: UID, + Password: Password, + } +} + func NewAnswerGetMsg(fromUID, toUID string, subject []byte, body []byte, timestamp time.Time, last bool) AnswerGetMsg { return AnswerGetMsg{ FromUID: fromUID,