diff --git a/internal/httpservers/restapi_auth_jwt.go b/internal/httpservers/restapi_auth_jwt.go index 23b639bf..7d5d6f95 100644 --- a/internal/httpservers/restapi_auth_jwt.go +++ b/internal/httpservers/restapi_auth_jwt.go @@ -15,31 +15,44 @@ var ( pubKey *rsa.PublicKey ) -func parseJwtToken(cookieValue string) (*jwt.Token, error) { - if cfg.AuthJwtPubKeyPath != "" { // activate this path only if pub key is specified - if pubKeyBytes == nil { // keep in memory after first load - var err error - pubKeyBytes, err = os.ReadFile(cfg.AuthJwtPubKeyPath) - if err != nil { - return nil, fmt.Errorf("couldn't read public key from file %s", cfg.AuthJwtPubKeyPath) - } - // Since the token is RSA (which we validated at the start of this function), the return type of this function actually has to be rsa.PublicKey! - pubKey, err = jwt.ParseRSAPublicKeyFromPEM(pubKeyBytes) - if err != nil { - return nil, fmt.Errorf("error parsing public key object (from %s)", cfg.AuthJwtPubKeyPath) - } - } - return jwt.Parse(cookieValue, func(token *jwt.Token) (interface{}, error) { - if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok { - return nil, fmt.Errorf( - "expected token algorithm '%v' but got '%v'", - jwt.SigningMethodRS256.Name, - token.Header) - } - return pubKey, nil - }) +func readPublicKey() error { + if pubKeyBytes != nil { + return nil // Already read. } + pubKeyBytes, err := os.ReadFile(cfg.AuthJwtPubKeyPath) + if err != nil { + return fmt.Errorf("couldn't read public key from file %s", cfg.AuthJwtPubKeyPath) + } + + // Since the token is RSA (which we validated at the start of this function), the return type of this function actually has to be rsa.PublicKey! + pubKey, err = jwt.ParseRSAPublicKeyFromPEM(pubKeyBytes) + if err != nil { + return fmt.Errorf("error parsing public key object (from %s)", cfg.AuthJwtPubKeyPath) + } + + return nil +} + +func parseJwtTokenWithKey(cookieValue string) (*jwt.Token, error) { + err := readPublicKey() + + if err != nil { + return nil, err + } + + return jwt.Parse(cookieValue, func(token *jwt.Token) (interface{}, error) { + if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok { + return nil, fmt.Errorf( + "expected token algorithm '%v' but got '%v'", + jwt.SigningMethodRS256.Name, + token.Header) + } + return pubKey, nil + }) +} + +func parseJwtTokenWithoutKey(cookieValue string) (*jwt.Token, error) { return jwt.Parse(cookieValue, func(token *jwt.Token) (interface{}, error) { // Don't forget to validate the alg is what you expect: if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { @@ -51,6 +64,14 @@ func parseJwtToken(cookieValue string) (*jwt.Token, error) { }) } +func parseJwtToken(cookieValue string) (*jwt.Token, error) { + if cfg.AuthJwtPubKeyPath != "" { // activate this path only if pub key is specified + return parseJwtTokenWithKey(cookieValue) + } else { + return parseJwtTokenWithoutKey(cookieValue) + } +} + func getClaimsFromJwtToken(cookieValue string) (jwt.MapClaims, error) { token, err := parseJwtToken(cookieValue) diff --git a/internal/httpservers/restapi_test.go b/internal/httpservers/restapi_test.go index 478277cc..7c0263e3 100644 --- a/internal/httpservers/restapi_test.go +++ b/internal/httpservers/restapi_test.go @@ -20,7 +20,7 @@ import ( "time" ) -func testBase(t *testing.T, expire int64, expectCode int) { +func createKeys() (*rsa.PrivateKey, string) { tmpFile, _ := os.CreateTemp(os.TempDir(), "olivetin-jwt-") defer os.Remove(tmpFile.Name()) @@ -36,13 +36,20 @@ func testBase(t *testing.T, expire int64, expectCode int) { Bytes: pkixPubKey, }, ) + if err := os.WriteFile(tmpFile.Name(), pubPem, 0755); err != nil { fmt.Printf("error when dumping pubKey: %s \n", err) } + return privateKey, tmpFile.Name() +} + +func testBase(t *testing.T, expire int64, expectCode int) { + privateKey, publicKeyPath := createKeys() + // default config + overrides config := config2.DefaultConfig() - config.AuthJwtPubKeyPath = tmpFile.Name() + config.AuthJwtPubKeyPath = publicKeyPath config.AuthJwtClaimUsername = "sub" config.AuthJwtClaimUserGroup = "olivetinGroup" config.AuthJwtCookieName = "authorization_token" @@ -97,11 +104,16 @@ func testBase(t *testing.T, expire int64, expectCode int) { MaxAge: 300, } req.AddCookie(cookie) - res, _ := client.Do(req) - defer res.Body.Close() - assert.Equal(t, expectCode, res.StatusCode) - body, _ := io.ReadAll(res.Body) - fmt.Println(string(body)) + res, err := client.Do(req) + + if err != nil { + assert.Equal(t, expectCode, -1) + } else { + defer res.Body.Close() + assert.Equal(t, expectCode, res.StatusCode) + body, _ := io.ReadAll(res.Body) + fmt.Println(string(body)) + } } func TestJWTSignatureVerificationSucceeds(t *testing.T) {