mirror of
https://github.com/wahyd4/loginsrv.git
synced 2026-08-09 04:46:29 +10:00
support for elliptic curve signing methods
This commit is contained in:
@@ -55,6 +55,7 @@ _Note for Caddy users_: Not all parameters are available in Caddy. See the table
|
||||
| -htpasswd | value | | X | Htpasswd login backend opts: file=/path/to/pwdfile |
|
||||
| -jwt-expiry | go duration | 24h | X | The expiry duration for the jwt token, e.g. 2h or 3h30m |
|
||||
| -jwt-secret | string | "random key" | X | The secret to sign the jwt token |
|
||||
| -jwt-algo | string | "HS512" | X | The singing algorithm to use (ES256, ES384, ES512, HS512, HS256, HS384, HS512) |
|
||||
| -log-level | string | "info" | - | The log level |
|
||||
| -login-path | string | "/login" | X | The path of the login resource |
|
||||
| -logout-url | string | | X | The url or path to redirect after logout |
|
||||
|
||||
@@ -27,6 +27,7 @@ func DefaultConfig() *Config {
|
||||
Port: "6789",
|
||||
LogLevel: "info",
|
||||
JwtSecret: jwtDefaultSecret,
|
||||
JwtAlgo: "HS512",
|
||||
JwtExpiry: 24 * time.Hour,
|
||||
JwtRefreshes: 0,
|
||||
SuccessURL: "/",
|
||||
@@ -53,6 +54,7 @@ type Config struct {
|
||||
LogLevel string
|
||||
TextLogging bool
|
||||
JwtSecret string
|
||||
JwtAlgo string
|
||||
JwtExpiry time.Duration
|
||||
JwtRefreshes int
|
||||
SuccessURL string
|
||||
@@ -105,6 +107,7 @@ func (c *Config) ConfigureFlagSet(f *flag.FlagSet) {
|
||||
f.StringVar(&c.LogLevel, "log-level", c.LogLevel, "The log level")
|
||||
f.BoolVar(&c.TextLogging, "text-logging", c.TextLogging, "Log in text format instead of json")
|
||||
f.StringVar(&c.JwtSecret, "jwt-secret", c.JwtSecret, "The secret to sign the jwt token")
|
||||
f.StringVar(&c.JwtAlgo, "jwt-algo", c.JwtAlgo, "The singing algorithm to use (ES256, ES384, ES512, HS512, HS256, HS384, HS512)")
|
||||
f.DurationVar(&c.JwtExpiry, "jwt-expiry", c.JwtExpiry, "The expiry duration for the jwt token, e.g. 2h or 3h30m")
|
||||
f.IntVar(&c.JwtRefreshes, "jwt-refreshes", c.JwtRefreshes, "The maximum amount of jwt refreshes. 0 by Default")
|
||||
f.StringVar(&c.CookieName, "cookie-name", c.CookieName, "The name of the jwt cookie")
|
||||
|
||||
@@ -27,6 +27,7 @@ func TestConfig_ReadConfig(t *testing.T) {
|
||||
"--log-level=loglevel",
|
||||
"--text-logging=true",
|
||||
"--jwt-secret=jwtsecret",
|
||||
"--jwt-algo=algo",
|
||||
"--jwt-expiry=42h42m",
|
||||
"--success-url=successurl",
|
||||
"--redirect=false",
|
||||
@@ -52,6 +53,7 @@ func TestConfig_ReadConfig(t *testing.T) {
|
||||
LogLevel: "loglevel",
|
||||
TextLogging: true,
|
||||
JwtSecret: "jwtsecret",
|
||||
JwtAlgo: "algo",
|
||||
JwtExpiry: 42*time.Hour + 42*time.Minute,
|
||||
SuccessURL: "successurl",
|
||||
Redirect: false,
|
||||
@@ -89,6 +91,7 @@ func TestConfig_ReadConfigFromEnv(t *testing.T) {
|
||||
NoError(t, os.Setenv("LOGINSRV_LOG_LEVEL", "loglevel"))
|
||||
NoError(t, os.Setenv("LOGINSRV_TEXT_LOGGING", "true"))
|
||||
NoError(t, os.Setenv("LOGINSRV_JWT_SECRET", "jwtsecret"))
|
||||
NoError(t, os.Setenv("LOGINSRV_JWT_ALGO", "algo"))
|
||||
NoError(t, os.Setenv("LOGINSRV_JWT_EXPIRY", "42h42m"))
|
||||
NoError(t, os.Setenv("LOGINSRV_SUCCESS_URL", "successurl"))
|
||||
NoError(t, os.Setenv("LOGINSRV_REDIRECT", "false"))
|
||||
@@ -112,6 +115,7 @@ func TestConfig_ReadConfigFromEnv(t *testing.T) {
|
||||
LogLevel: "loglevel",
|
||||
TextLogging: true,
|
||||
JwtSecret: "jwtsecret",
|
||||
JwtAlgo: "algo",
|
||||
JwtExpiry: 42*time.Hour + 42*time.Minute,
|
||||
SuccessURL: "successurl",
|
||||
Redirect: false,
|
||||
|
||||
+43
-7
@@ -2,7 +2,6 @@ package login
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
@@ -10,6 +9,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/tarent/loginsrv/logging"
|
||||
"github.com/tarent/loginsrv/model"
|
||||
"github.com/tarent/loginsrv/oauth2"
|
||||
@@ -22,9 +22,12 @@ const contentTypePlain = "text/plain"
|
||||
// Handler is the mail login handler.
|
||||
// It serves the login ressource and does the authentication against the backends or oauth provider.
|
||||
type Handler struct {
|
||||
backends []Backend
|
||||
oauth oauthManager
|
||||
config *Config
|
||||
backends []Backend
|
||||
oauth oauthManager
|
||||
config *Config
|
||||
signingMethod jwt.SigningMethod
|
||||
signingKey interface{}
|
||||
signingVerifyKey interface{}
|
||||
}
|
||||
|
||||
// NewHandler creates a login handler based on the supplied configuration.
|
||||
@@ -246,8 +249,12 @@ func (h *Handler) respondAuthenticated(w http.ResponseWriter, r *http.Request, u
|
||||
}
|
||||
|
||||
func (h *Handler) createToken(userInfo jwt.Claims) (string, error) {
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS512, userInfo)
|
||||
return token.SignedString([]byte(h.config.JwtSecret))
|
||||
signingMethod, key, _, err := h.signingInfo()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
token := jwt.NewWithClaims(signingMethod, userInfo)
|
||||
return token.SignedString(key)
|
||||
}
|
||||
|
||||
func (h *Handler) GetToken(r *http.Request) (userInfo model.UserInfo, valid bool) {
|
||||
@@ -257,7 +264,8 @@ func (h *Handler) GetToken(r *http.Request) (userInfo model.UserInfo, valid bool
|
||||
}
|
||||
|
||||
token, err := jwt.ParseWithClaims(c.Value, &model.UserInfo{}, func(*jwt.Token) (interface{}, error) {
|
||||
return []byte(h.config.JwtSecret), nil
|
||||
_, _, verifyKey, err := h.signingInfo()
|
||||
return verifyKey, err
|
||||
})
|
||||
if err != nil {
|
||||
return model.UserInfo{}, false
|
||||
@@ -271,6 +279,34 @@ func (h *Handler) GetToken(r *http.Request) (userInfo model.UserInfo, valid bool
|
||||
return *u, u.Valid() == nil
|
||||
}
|
||||
|
||||
func (h *Handler) signingInfo() (signingMethod jwt.SigningMethod, key, verifyKey interface{}, err error) {
|
||||
if h.signingMethod == nil || h.signingKey == nil || h.signingVerifyKey == nil {
|
||||
h.signingMethod = jwt.GetSigningMethod(h.config.JwtAlgo)
|
||||
if h.signingMethod == nil {
|
||||
return nil, nil, nil, errors.New("invalid signing method: " + h.config.JwtAlgo)
|
||||
}
|
||||
|
||||
keyString := h.config.JwtSecret
|
||||
switch h.config.JwtAlgo {
|
||||
case "ES256", "ES384", "ES512":
|
||||
if !strings.Contains(string(keyString), "-----") {
|
||||
keyString = "-----BEGIN EC PRIVATE KEY-----\n" + keyString + "\n-----END EC PRIVATE KEY-----"
|
||||
}
|
||||
|
||||
key, err := jwt.ParseECPrivateKeyFromPEM([]byte(keyString))
|
||||
if err != nil {
|
||||
return nil, nil, nil, errors.Wrap(err, "can not parse PEM formated EC private key")
|
||||
}
|
||||
h.signingKey = key
|
||||
h.signingVerifyKey = key.Public()
|
||||
default:
|
||||
h.signingKey = []byte(keyString)
|
||||
h.signingVerifyKey = h.signingKey
|
||||
}
|
||||
}
|
||||
return h.signingMethod, h.signingKey, h.signingVerifyKey, nil
|
||||
}
|
||||
|
||||
func (h *Handler) respondError(w http.ResponseWriter, r *http.Request) {
|
||||
if wantHTML(r) {
|
||||
username, _, _ := getCredentials(r)
|
||||
|
||||
@@ -399,6 +399,21 @@ func TestHandler_getToken_Valid(t *testing.T) {
|
||||
Equal(t, input, userInfo)
|
||||
}
|
||||
|
||||
func TestHandler_signAndVerify_ES256(t *testing.T) {
|
||||
h := testHandler()
|
||||
h.config.JwtAlgo = "ES256"
|
||||
h.config.JwtSecret = "MHcCAQEEIJKMecdA9ASkZArOu9b+cPmSiVfQaaeErHcvkqG2gVIOoAoGCCqGSM49AwEHoUQDQgAE1gae9/zJDLHeuFteUkKgVhLrwJPoA43goNacgwldOucBvVUzD0EFAcpCR+0UcOfQ99CxUyKxWtnvr9xpDIXU0w=="
|
||||
input := model.UserInfo{Sub: "marvin", Expiry: time.Now().Add(time.Second).Unix()}
|
||||
token, err := h.createToken(input)
|
||||
NoError(t, err)
|
||||
r := &http.Request{
|
||||
Header: http.Header{"Cookie": {h.config.CookieName + "=" + token + ";"}},
|
||||
}
|
||||
userInfo, valid := h.GetToken(r)
|
||||
True(t, valid)
|
||||
Equal(t, input, userInfo)
|
||||
}
|
||||
|
||||
func TestHandler_getToken_InvalidSecret(t *testing.T) {
|
||||
h := testHandler()
|
||||
input := model.UserInfo{Sub: "marvin"}
|
||||
|
||||
Reference in New Issue
Block a user