loginsrv

Unnamed repository; edit this file 'description' to name the repository.
git clone git@jamesshield.xyz:repos/loginsrv.git
Log | Files | Refs | README | LICENSE

commit 84ee0ad74502592f1cad19518306b71c9f2c9295
parent 1ad2794dee9db4ddd78f00283f0a2f98b9dc7ba6
Author: Jakob Rockenbauch <34475067+J-Rocke@users.noreply.github.com>
Date:   Fri, 15 Nov 2019 10:41:33 +0100

Merge pull request #146 from kernle32dll/support-for-rsa-keys

Add support for RSA signing keys
Diffstat:
Mlogin/config.go | 2+-
Mlogin/handler.go | 13++++++++++++-
Mlogin/handler_test.go | 79+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 92 insertions(+), 2 deletions(-)

diff --git a/login/config.go b/login/config.go @@ -117,7 +117,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.StringVar(&c.JwtAlgo, "jwt-algo", c.JwtAlgo, "The singing algorithm to use (ES256, ES384, ES512, RS256, RS384, RS512, 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") diff --git a/login/handler.go b/login/handler.go @@ -324,7 +324,7 @@ func (h *Handler) signingInfo() (signingMethod jwt.SigningMethod, key, verifyKey keyString := h.config.JwtSecret switch h.config.JwtAlgo { case "ES256", "ES384", "ES512": - if !strings.Contains(string(keyString), "-----") { + if !strings.Contains(keyString, "-----") { keyString = "-----BEGIN EC PRIVATE KEY-----\n" + keyString + "\n-----END EC PRIVATE KEY-----" } @@ -334,6 +334,17 @@ func (h *Handler) signingInfo() (signingMethod jwt.SigningMethod, key, verifyKey } h.signingKey = key h.signingVerifyKey = key.Public() + case "RS256", "RS384", "RS512": + if !strings.Contains(keyString, "-----") { + keyString = "-----BEGIN RSA PRIVATE KEY-----\n" + keyString + "\n-----END RSA PRIVATE KEY-----" + } + + key, err := jwt.ParseRSAPrivateKeyFromPEM([]byte(keyString)) + if err != nil { + return nil, nil, nil, errors.Wrap(err, "can not parse PEM formated RSA private key") + } + h.signingKey = key + h.signingVerifyKey = key.Public() default: h.signingKey = []byte(keyString) h.signingVerifyKey = h.signingKey diff --git a/login/handler_test.go b/login/handler_test.go @@ -1,7 +1,11 @@ package login import ( + "crypto/rand" + "crypto/rsa" + "crypto/x509" "encoding/json" + "encoding/pem" "errors" "fmt" "net/http" @@ -516,6 +520,81 @@ func TestHandler_signAndVerify_ES256(t *testing.T) { Equal(t, input, userInfo) } +func TestHandler_signAndVerify_RSA(t *testing.T) { + tt := []int{ + 256, + 384, + 512, + } + for _, bits := range tt { + jwtAlgo := fmt.Sprintf("RS%d", bits) + t.Run(jwtAlgo, func(t *testing.T) { + t.Parallel() + + key, err := rsa.GenerateKey(rand.Reader, bits*2) + NoError(t, err) + + privateKey := &pem.Block{ + Type: "PRIVATE KEY", + Bytes: x509.MarshalPKCS1PrivateKey(key), + } + + h := testHandler() + h.config.JwtAlgo = jwtAlgo + h.config.JwtSecret = string(pem.EncodeToMemory(privateKey)) + + 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) + }) + } + + t.Run("headerless", func(t *testing.T) { + h := testHandler() + h.config.JwtAlgo = "RS256" + h.config.JwtSecret = "" + + "MIICXAIBAAKBgQDSu7M1jiH06fGywhSw5jdjUdfX6b1yw8j2coVjAgT1oB44vU+S" + + "dgvak/tWoBkqG+Gdrn0m+3H/mRtGXWZDmh6VjQ5mnw91OGVJccL2UGdEbb4ub/9g" + + "4Bobn1ANUcbZvXWpmNP0kqyBwsXiaq6iL4TNW5iKdvnat7SwzLyIkGwPkQIDAQAB" + + "AoGAXpshs1Nh7z/v4F69R0WzbAVcL3SiNpmq6Ok09OP9MgB2UOa8iHYykCiLV7J8" + + "Wak2usGRMiUEYslrs0VPGd5hB9X94fDAh0SYC2wmBOJRBY2tU82pSkN5RjE8A3+f" + + "G6uwlZB2UtpYa/sihf7NkJCQh2ibT3YeelDUvEnfwALB6iECQQDck14kDckwi4mt" + + "LwWPqXTWAdKdTN1i6KGXDBt7Bi5lbVk3XFgQy/Z+GzRiBtjcWmcMw2VOUeFC9d/J" + + "WnRv8NklAkEA9JOsxEgzr7utqw3Zd1dDK5weDhAwXuaHiCIS+bDAsGor7pSgWOtU" + + "k+kpDdPe/TmtxJFhorJOsl+49VtYVoy+/QJAWJnlhcv31cUnL2ak8DkcUl53EHJw" + + "tytExVy6qScpedp7rM4uHckgITWiTAH+GD1ECY9vYQ9o0bHcC5CHFvQC9QJBAOwI" + + "2ONVCwy+A4zhgM472QdtU1QfK49qy8IFoGp4un2G+X720Qj/lFBq5MQDhWC9GYZr" + + "B98MVgavesDPtyFQE8ECQCRZaTDF4d5KBAvu5ogoqEATD5r21V4Zj5uZ/QSeI7+v" + + "UVncBYg6g4CIrczoqYpJ3aBF5MVJ0FEU9XCDO/iDvCU=" + + 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) + }) + + t.Run("garbage key", func(t *testing.T) { + h := testHandler() + h.config.JwtAlgo = "RS256" + h.config.JwtSecret = "-garbage-" + + input := model.UserInfo{Sub: "marvin", Expiry: time.Now().Add(time.Second).Unix()} + _, err := h.createToken(input) + Error(t, err) + }) +} + func TestHandler_getToken_InvalidSecret(t *testing.T) { h := testHandler() input := model.UserInfo{Sub: "marvin"}