Move to RSA and add verification

This commit is contained in:
Andrew-71 2024-10-12 16:59:47 +03:00
parent 8fa3424a65
commit 170416e791
5 changed files with 65 additions and 49 deletions

2
.gitignore vendored
View file

@ -1,3 +1,3 @@
pye-auth pye-auth
key private.key
data.json data.json

View file

@ -1,4 +1,4 @@
# PYE Auth # Auth microservice
**Mission**: Science compels us to create a microservice! **Mission**: Science compels us to create a microservice!
@ -14,9 +14,9 @@ obviously I'd use **SQL** for production
## Current functionality ## Current functionality
* Port 7102 * Port `7102`
* `POST /register` - register a user with Basic Auth * `POST /register` - register a user with Basic Auth
* `POST /login` - get a JWT token by Basic Auth * `POST /login` - get a JWT token by Basic Auth
* `GET /public-key` - get PEM-encoded public HS256 key * `GET /pem` - get PEM-encoded public RS256 key
* Data persistently stored in... `data.json`, for convenience * Data persistently stored in... `data.json`, for convenience
* HS256 key loaded from `key` file or generated on startup if missing * RS256 key loaded from `private.key` file or generated on startup if missing

10
auth.go
View file

@ -13,11 +13,10 @@ func ValidEmail(email string) bool {
} }
func ValidPass(pass string) bool { func ValidPass(pass string) bool {
// TODO: Obviously, we *might* want something more sophisticated here // TODO: Obviously, we *might* want something more sophisticated here
return true return len(pass) >= 8
//return len(pass) >= 8
} }
func EmailTaken(email string) bool { func EmailTaken(email string) bool {
// TODO: Implement properly // FIXME: Implement properly
return EmailExists(email) return EmailExists(email)
} }
func Register(w http.ResponseWriter, r *http.Request) { func Register(w http.ResponseWriter, r *http.Request) {
@ -27,12 +26,11 @@ func Register(w http.ResponseWriter, r *http.Request) {
email = strings.TrimSpace(email) email = strings.TrimSpace(email)
password = strings.TrimSpace(password) password = strings.TrimSpace(password)
if !(ValidEmail(email) && ValidPass(password) && !EmailTaken(email)) { if !(ValidEmail(email) && ValidPass(password) && !EmailTaken(email)) {
// TODO: Provide descriptive error and check if 400 is best code?
slog.Info("Outcome", slog.Info("Outcome",
"email", ValidEmail(email), "email", ValidEmail(email),
"pass", ValidPass(password), "pass", ValidPass(password),
"taken", !EmailTaken(email)) "taken", !EmailTaken(email))
http.Error(w, "Invalid auth credentials", http.StatusBadRequest) http.Error(w, "invalid auth credentials", http.StatusBadRequest)
return return
} }
user, err := NewUser(email, password) user, err := NewUser(email, password)
@ -59,7 +57,7 @@ func Login(w http.ResponseWriter, r *http.Request) {
password = strings.TrimSpace(password) password = strings.TrimSpace(password)
user, ok := ByEmail(email) user, ok := ByEmail(email)
if !ok || !user.PasswordFits(password) { if !ok || !user.PasswordFits(password) {
http.Error(w, "You did something wrong", http.StatusUnauthorized) http.Error(w, "you did something wrong", http.StatusUnauthorized)
return return
} }

90
jwt.go
View file

@ -1,9 +1,8 @@
package main package main
import ( import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand" "crypto/rand"
"crypto/rsa"
"crypto/x509" "crypto/x509"
"encoding/pem" "encoding/pem"
"errors" "errors"
@ -15,10 +14,10 @@ import (
"github.com/golang-jwt/jwt/v5" "github.com/golang-jwt/jwt/v5"
) )
var KeyFile = "key" var KeyFile = "private.key"
var ( var (
key *ecdsa.PrivateKey key *rsa.PrivateKey
) )
// LoadKey attempts to load a private key from KeyFile. // LoadKey attempts to load a private key from KeyFile.
@ -26,17 +25,25 @@ var (
func LoadKey() { func LoadKey() {
// If the key doesn't exist, create it // If the key doesn't exist, create it
if _, err := os.Stat(KeyFile); errors.Is(err, os.ErrNotExist) { if _, err := os.Stat(KeyFile); errors.Is(err, os.ErrNotExist) {
key, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader) key, err = rsa.GenerateKey(rand.Reader, 4096)
if err != nil { if err != nil {
slog.Error("error generating key", "error", err) slog.Error("error generating key", "error", err)
os.Exit(1) os.Exit(1)
} }
km, err := x509.MarshalECPrivateKey(key) // Save private key to disk
// Save key to disk
km := x509.MarshalPKCS1PrivateKey(key)
block := pem.Block{Bytes: km, Type: "RSA PRIVATE KEY"}
f, err := os.OpenFile(KeyFile, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
if err != nil { if err != nil {
slog.Error("error marshalling key", "error", err) slog.Error("error opening/creating file", "error", err)
os.Exit(1)
}
f.Write(pem.EncodeToMemory(&block))
if err := f.Close(); err != nil {
slog.Error("error closing file", "error", err)
os.Exit(1) os.Exit(1)
} }
os.WriteFile(KeyFile, km, 0644)
slog.Info("generated new key") slog.Info("generated new key")
} else { } else {
km, err := os.ReadFile(KeyFile) km, err := os.ReadFile(KeyFile)
@ -44,47 +51,56 @@ func LoadKey() {
slog.Error("error reading key", "error", err) slog.Error("error reading key", "error", err)
os.Exit(1) os.Exit(1)
} }
key, err = x509.ParseECPrivateKey(km) key, err = jwt.ParseRSAPrivateKeyFromPEM(km)
if err != nil { if err != nil {
slog.Error("error parsing key", "error", err) slog.Error("error parsing key", "error", err)
os.Exit(1) os.Exit(1)
} }
slog.Info("loaded private key") slog.Info("loaded private key")
} }
slog.Debug("private key", "key", key)
} }
// publicKey returns our public key in PKIX, ASN.1 DER form // publicKey returns our public key as PEM block
func publicKey(w http.ResponseWriter, r *http.Request) { func publicKey(w http.ResponseWriter, r *http.Request) {
key_marshalled, err := x509.MarshalPKIXPublicKey(&key.PublicKey) key_marshalled := x509.MarshalPKCS1PublicKey(&key.PublicKey)
if err != nil { block := pem.Block{Bytes: key_marshalled, Type: "RSA PUBLIC KEY"}
slog.Error("error marshalling public key", "error", err)
http.Error(w, "error marshalling public key", http.StatusInternalServerError)
return
}
// w.Write(key_marshalled)
block := pem.Block{Bytes: key_marshalled, Type: "ECDSA PUBLIC KEY"}
// slog.Info("public key", "orig", key_marshalled, "block", block)
pem.Encode(w, &block) pem.Encode(w, &block)
} }
func CreateJWT(usr User) (string, error) {
t := jwt.NewWithClaims(jwt.SigningMethodRS256,
jwt.MapClaims{
"iss": "pye",
"uid": usr.Uuid,
"sub": usr.Email,
"iat": time.Now().Unix(),
"exp": time.Now().Add(time.Hour * 24 * 7).Unix(),
})
s, err := t.SignedString(key)
if err != nil {
slog.Error("error creating JWT", "error", err)
return "", err
}
return s, nil
}
// VerifyToken receives a JWT and PEM-encoded public key,
// then returns whether the token is valid
func VerifyJWT(token string, publicKey []byte) bool {
_, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) {
key, err := jwt.ParseRSAPublicKeyFromPEM(publicKey)
if err != nil {
return nil, err
}
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
return nil, err
}
return key, nil
})
slog.Info("Error check", "err", err)
return err == nil
}
func init() { func init() {
LoadKey() LoadKey()
} }
func CreateJWT(usr User) (string, error) {
t := jwt.NewWithClaims(jwt.SigningMethodES256,
jwt.MapClaims{
"iss": "pye",
"uid": usr.Uuid,
"sub": usr.Email,
"iat": time.Now(),
"exp": time.Now().Add(time.Hour * 24 * 7),
})
s, err := t.SignedString(key)
if err != nil {
slog.Error("Error creating JWT", "error", err)
return "", err
}
return s, nil
}

View file

@ -10,10 +10,12 @@ func main() {
router := http.NewServeMux() router := http.NewServeMux()
router.HandleFunc("GET /public-key", publicKey) router.HandleFunc("GET /pem", publicKey)
router.HandleFunc("POST /register", Register) router.HandleFunc("POST /register", Register)
router.HandleFunc("POST /login", Login) router.HandleFunc("POST /login", Login)
router.HandleFunc("GET /login", Login) // TODO: temp
http.ListenAndServe(":7102", router) http.ListenAndServe(":7102", router)
} }