From 2caef38ce63abe2fbf9a19a16e69990c78b61d5d Mon Sep 17 00:00:00 2001 From: LandaMm Date: Sun, 25 May 2025 14:53:19 +0200 Subject: [PATCH] feat: jwks endpoint + rsa keys use --- internal/auth/jwt.go | 119 +++++++++++++++++++++++++++++++------------ 1 file changed, 87 insertions(+), 32 deletions(-) diff --git a/internal/auth/jwt.go b/internal/auth/jwt.go index 854c955..50fb115 100644 --- a/internal/auth/jwt.go +++ b/internal/auth/jwt.go @@ -1,52 +1,105 @@ package auth import ( - "crypto/ecdsa" + "crypto/rsa" "crypto/x509" "encoding/base64" + "encoding/json" "fmt" + "net/http" "os" "gitea.local/admin/hspguard/internal/types" + "gitea.local/admin/hspguard/internal/web" "github.com/golang-jwt/jwt/v5" ) -func parseBase64PrivateKey(envVar string) (*ecdsa.PrivateKey, error) { - b64 := os.Getenv(envVar) - if b64 == "" { - return nil, fmt.Errorf("env var %s is empty", envVar) - } +func WriteJWKS(w http.ResponseWriter, r *http.Request) { + pubKey, err := parseBase64PublicKey("JWT_PUBLIC_KEY") + if err != nil { + web.Error(w, "failed to parse public key", http.StatusInternalServerError) + } - decoded, err := base64.StdEncoding.DecodeString(b64) - if err != nil { - return nil, fmt.Errorf("failed to decode base64 key: %v", err) - } + n := base64.RawURLEncoding.EncodeToString(pubKey.N.Bytes()) + e := base64.RawURLEncoding.EncodeToString([]byte{1, 0, 1}) // 65537 = 0x010001 - return x509.ParseECPrivateKey(decoded) + jwks := map[string]interface{}{ + "keys": []map[string]string{ + { + "kty": "RSA", + "kid": "my-rsa-key-1", + "use": "sig", + "alg": "RS256", + "n": n, + "e": e, + }, + }, + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(jwks) } -func parseBase64PublicKey(envVar string) (*ecdsa.PublicKey, error) { - b64 := os.Getenv(envVar) - if b64 == "" { - return nil, fmt.Errorf("env var %s is empty", envVar) - } +func OpenIdConfiguration(w http.ResponseWriter, r *http.Request) { + type Response struct { + TokenEndpoint string `json:"token_endpoint"` + AuthorizationEndpoint string `json:"authorization_endpoint"` + JwksURI string `json:"jwks_uri"` + Issuer string `json:"issuer"` + EndSessionEndpoint string `json:"end_session_endpoint"` + } - decoded, err := base64.StdEncoding.DecodeString(b64) - if err != nil { - return nil, fmt.Errorf("failed to decode base64 key: %v", err) - } + w.Header().Set("Content-Type", "application/json") - pubInterface, err := x509.ParsePKIXPublicKey(decoded) - if err != nil { - return nil, fmt.Errorf("failed to parse public key: %v", err) - } + encoder := json.NewEncoder(w) + if err := encoder.Encode(Response{ + TokenEndpoint: "https://cb5f-2a00-10-5b00-c801-e955-5c68-63d0-b777.ngrok-free.app/api/v1/oauth/token", + AuthorizationEndpoint: "https://cb5f-2a00-10-5b00-c801-e955-5c68-63d0-b777.ngrok-free.app/authorize", + JwksURI: "https://cb5f-2a00-10-5b00-c801-e955-5c68-63d0-b777.ngrok-free.app/.well-known/jwks.json", + Issuer: "https://cb5f-2a00-10-5b00-c801-e955-5c68-63d0-b777.ngrok-free.app", + EndSessionEndpoint: "https://cb5f-2a00-10-5b00-c801-e955-5c68-63d0-b777.ngrok-free.app/api/v1/oauth/logout", + }); err != nil { + web.Error(w, "failed to encode response", http.StatusInternalServerError) + } +} - pubKey, ok := pubInterface.(*ecdsa.PublicKey) - if !ok { - return nil, fmt.Errorf("not an ECDSA public key") - } +func parseBase64PrivateKey(envVar string) (*rsa.PrivateKey, error) { + b64 := os.Getenv(envVar) + if b64 == "" { + return nil, fmt.Errorf("env var %s is empty", envVar) + } - return pubKey, nil + decoded, err := base64.StdEncoding.DecodeString(b64) + if err != nil { + return nil, fmt.Errorf("failed to decode base64 key: %v", err) + } + + key, err := x509.ParsePKCS8PrivateKey(decoded) + return key.(*rsa.PrivateKey), err +} + +func parseBase64PublicKey(envVar string) (*rsa.PublicKey, error) { + b64 := os.Getenv(envVar) + if b64 == "" { + return nil, fmt.Errorf("env var %s is empty", envVar) + } + + decoded, err := base64.StdEncoding.DecodeString(b64) + if err != nil { + return nil, fmt.Errorf("failed to decode base64 key: %v", err) + } + + pubInterface, err := x509.ParsePKIXPublicKey(decoded) + if err != nil { + return nil, fmt.Errorf("failed to parse public key: %v", err) + } + + pubKey, ok := pubInterface.(*rsa.PublicKey) + if !ok { + return nil, fmt.Errorf("not an RSA public key") + } + + return pubKey, nil } func SignJwtToken(claims jwt.Claims) (string, error) { @@ -55,7 +108,10 @@ func SignJwtToken(claims jwt.Claims) (string, error) { return "", err } - token := jwt.NewWithClaims(jwt.SigningMethodES256, claims) + token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims) + + token.Header["kid"] = "my-rsa-key-1" + s, err := token.SignedString(privateKey) if err != nil { return "", err @@ -72,7 +128,7 @@ func VerifyToken(token string) (*jwt.Token, *types.UserClaims, error) { claims := &types.UserClaims{} parsed, err := jwt.ParseWithClaims(token, claims, func(t *jwt.Token) (any, error) { - if _, ok := t.Method.(*jwt.SigningMethodECDSA); !ok { + if _, ok := t.Method.(*jwt.SigningMethodRSA); !ok { return nil, fmt.Errorf("unexpected signing method: %v", t.Header["alg"]) } return publicKey, nil @@ -88,4 +144,3 @@ func VerifyToken(token string) (*jwt.Token, *types.UserClaims, error) { return parsed, claims, nil } -