support for elliptic curve signing methods

This commit is contained in:
Sebastian Mancke
2018-02-16 11:11:41 +01:00
parent 0d99e0462c
commit a5733ef860
5 changed files with 66 additions and 7 deletions
+1
View File
@@ -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 |
+3
View File
@@ -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")
+4
View File
@@ -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
View File
@@ -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)
+15
View File
@@ -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"}