feat: jwks endpoint + rsa keys use

This commit is contained in:
2025-05-25 14:53:19 +02:00
parent e88980e64f
commit 2caef38ce6

View File

@ -1,52 +1,105 @@
package auth package auth
import ( import (
"crypto/ecdsa" "crypto/rsa"
"crypto/x509" "crypto/x509"
"encoding/base64" "encoding/base64"
"encoding/json"
"fmt" "fmt"
"net/http"
"os" "os"
"gitea.local/admin/hspguard/internal/types" "gitea.local/admin/hspguard/internal/types"
"gitea.local/admin/hspguard/internal/web"
"github.com/golang-jwt/jwt/v5" "github.com/golang-jwt/jwt/v5"
) )
func parseBase64PrivateKey(envVar string) (*ecdsa.PrivateKey, error) { func WriteJWKS(w http.ResponseWriter, r *http.Request) {
b64 := os.Getenv(envVar) pubKey, err := parseBase64PublicKey("JWT_PUBLIC_KEY")
if b64 == "" { if err != nil {
return nil, fmt.Errorf("env var %s is empty", envVar) web.Error(w, "failed to parse public key", http.StatusInternalServerError)
} }
decoded, err := base64.StdEncoding.DecodeString(b64) n := base64.RawURLEncoding.EncodeToString(pubKey.N.Bytes())
if err != nil { e := base64.RawURLEncoding.EncodeToString([]byte{1, 0, 1}) // 65537 = 0x010001
return nil, fmt.Errorf("failed to decode base64 key: %v", err)
}
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) { func OpenIdConfiguration(w http.ResponseWriter, r *http.Request) {
b64 := os.Getenv(envVar) type Response struct {
if b64 == "" { TokenEndpoint string `json:"token_endpoint"`
return nil, fmt.Errorf("env var %s is empty", envVar) 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) w.Header().Set("Content-Type", "application/json")
if err != nil {
return nil, fmt.Errorf("failed to decode base64 key: %v", err)
}
pubInterface, err := x509.ParsePKIXPublicKey(decoded) encoder := json.NewEncoder(w)
if err != nil { if err := encoder.Encode(Response{
return nil, fmt.Errorf("failed to parse public key: %v", err) 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) func parseBase64PrivateKey(envVar string) (*rsa.PrivateKey, error) {
if !ok { b64 := os.Getenv(envVar)
return nil, fmt.Errorf("not an ECDSA public key") 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) { func SignJwtToken(claims jwt.Claims) (string, error) {
@ -55,7 +108,10 @@ func SignJwtToken(claims jwt.Claims) (string, error) {
return "", err 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) s, err := token.SignedString(privateKey)
if err != nil { if err != nil {
return "", err return "", err
@ -72,7 +128,7 @@ func VerifyToken(token string) (*jwt.Token, *types.UserClaims, error) {
claims := &types.UserClaims{} claims := &types.UserClaims{}
parsed, err := jwt.ParseWithClaims(token, claims, func(t *jwt.Token) (any, error) { 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 nil, fmt.Errorf("unexpected signing method: %v", t.Header["alg"])
} }
return publicKey, nil return publicKey, nil
@ -88,4 +144,3 @@ func VerifyToken(token string) (*jwt.Token, *types.UserClaims, error) {
return parsed, claims, nil return parsed, claims, nil
} }