[PD2] Gateway login, logout and auth middleware done.
Co-authored-by: tsousa111 <tiagao2001@hotmail.com>
This commit is contained in:
parent
aa90bfddce
commit
69559f41ca
7 changed files with 277 additions and 28 deletions
|
@ -4,6 +4,7 @@ go 1.22.2
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/gin-gonic/gin v1.10.0
|
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
|
github.com/mattn/go-sqlite3 v1.14.22
|
||||||
golang.org/x/crypto v0.23.0
|
golang.org/x/crypto v0.23.0
|
||||||
software.sslmate.com/src/go-pkcs12 v0.4.0
|
software.sslmate.com/src/go-pkcs12 v0.4.0
|
||||||
|
|
|
@ -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/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 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
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 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
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=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
|
52
Projs/PD2/internal/gateway/datastore.go
Normal file
52
Projs/PD2/internal/gateway/datastore.go
Normal file
|
@ -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
|
||||||
|
}
|
|
@ -2,41 +2,135 @@ package gateway
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"PD1/internal/protocol"
|
"PD1/internal/protocol"
|
||||||
|
"PD1/internal/utils/cryptoUtils"
|
||||||
|
"crypto/x509"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
func HandleGetMessage(c *gin.Context){
|
func HandleGetMessage(c *gin.Context) {
|
||||||
fmt.Println("Get Message Handler")
|
fmt.Println("Get Message Handler")
|
||||||
|
|
||||||
}
|
}
|
||||||
func HandleGetUnreadMsgsInfo(c *gin.Context){
|
func HandleGetUnreadMsgsInfo(c *gin.Context) {
|
||||||
fmt.Println("Get Unread Messages Info Handler")
|
fmt.Println("Get Unread Messages Info Handler")
|
||||||
|
|
||||||
}
|
}
|
||||||
func HandleGetUserCert(c *gin.Context){
|
func HandleGetUserCert(c *gin.Context) {
|
||||||
fmt.Println("Get User Cert Handler")
|
fmt.Println("Get User Cert Handler")
|
||||||
|
|
||||||
}
|
}
|
||||||
func HandleSendMessage(c *gin.Context){
|
func HandleSendMessage(c *gin.Context) {
|
||||||
fmt.Println("Send Message Handler")
|
fmt.Println("Send Message Handler")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func HandleRegister(c *gin.Context){
|
func HandleRegister(c *gin.Context, dataStore DataStore, keyStore cryptoUtils.KeyStore) {
|
||||||
var postRegister protocol.PostRegister
|
var postRegister protocol.PostRegister
|
||||||
c.Bind(postRegister)
|
err := c.Bind(postRegister)
|
||||||
//Hash the password (using salt) and store it on the db
|
if err != nil {
|
||||||
//Get the username from the certificate
|
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){
|
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 AuthMiddleware(c *gin.Context) {
|
||||||
fmt.Println("Authentication Middleware")
|
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(){
|
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()
|
router := gin.Default()
|
||||||
|
|
||||||
auth := router.Group("/", func(c *gin.Context) {
|
auth := router.Group("/", func(c *gin.Context) {
|
||||||
|
@ -59,19 +153,22 @@ func Run(){
|
||||||
HandleSendMessage(c)
|
HandleSendMessage(c)
|
||||||
})
|
})
|
||||||
|
|
||||||
router.POST("/register",func(c *gin.Context) {
|
router.POST("/register", func(c *gin.Context) {
|
||||||
HandleRegister(c)
|
HandleRegister(c, dataStore, keyStore)
|
||||||
|
})
|
||||||
|
|
||||||
|
router.POST("/login", func(c *gin.Context) {
|
||||||
|
HandleLogin(c, dataStore, keyStore)
|
||||||
})
|
})
|
||||||
|
|
||||||
server := http.Server{
|
server := http.Server{
|
||||||
Addr: "0.0.0.0:8080",
|
Addr: "0.0.0.0:8080",
|
||||||
Handler: router,
|
Handler: router,
|
||||||
//TODO: Verify if it's the gateway
|
|
||||||
TLSConfig: serverKeyStore.GetServerTLSConfig(),
|
TLSConfig: serverKeyStore.GetServerTLSConfig(),
|
||||||
}
|
}
|
||||||
|
|
||||||
err = server.ListenAndServeTLS("", "")
|
err := server.ListenAndServeTLS("", "")
|
||||||
if err!=nil {
|
if err != nil {
|
||||||
log.Fatal(err.Error())
|
log.Fatal(err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
14
Projs/PD2/internal/gateway/interface.go
Normal file
14
Projs/PD2/internal/gateway/interface.go
Normal file
|
@ -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()
|
||||||
|
}
|
62
Projs/PD2/internal/gateway/jwt.go
Normal file
62
Projs/PD2/internal/gateway/jwt.go
Normal file
|
@ -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")
|
||||||
|
}
|
||||||
|
}
|
|
@ -53,10 +53,16 @@ type (
|
||||||
}
|
}
|
||||||
|
|
||||||
PostRegister struct {
|
PostRegister struct {
|
||||||
|
UID string `json:"uid"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
Certificate []byte `json:"certificate"`
|
Certificate []byte `json:"certificate"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PostLogin struct {
|
||||||
|
UID string `json:"uid"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
}
|
||||||
|
|
||||||
ReportError struct {
|
ReportError struct {
|
||||||
ErrorMessage string `json:"error"`
|
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 {
|
func NewAnswerGetMsg(fromUID, toUID string, subject []byte, body []byte, timestamp time.Time, last bool) AnswerGetMsg {
|
||||||
return AnswerGetMsg{
|
return AnswerGetMsg{
|
||||||
FromUID: fromUID,
|
FromUID: fromUID,
|
||||||
|
|
Loading…
Reference in a new issue