fixed JWT authentication port (#3689)

* fixed JWT

* jwt.hex

* ops
This commit is contained in:
Giulio rebuffo 2022-03-14 14:47:26 +01:00 committed by GitHub
parent b6f707d811
commit e20506e932
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -4,8 +4,8 @@ import (
"context" "context"
"crypto/rand" "crypto/rand"
"encoding/binary" "encoding/binary"
"errors"
"fmt" "fmt"
"io/ioutil"
"net" "net"
"net/http" "net/http"
"os" "os"
@ -32,6 +32,7 @@ import (
"github.com/ledgerwatch/erigon/cmd/rpcdaemon/services" "github.com/ledgerwatch/erigon/cmd/rpcdaemon/services"
"github.com/ledgerwatch/erigon/cmd/utils" "github.com/ledgerwatch/erigon/cmd/utils"
"github.com/ledgerwatch/erigon/common" "github.com/ledgerwatch/erigon/common"
"github.com/ledgerwatch/erigon/common/hexutil"
"github.com/ledgerwatch/erigon/common/paths" "github.com/ledgerwatch/erigon/common/paths"
"github.com/ledgerwatch/erigon/core/rawdb" "github.com/ledgerwatch/erigon/core/rawdb"
"github.com/ledgerwatch/erigon/eth/ethconfig" "github.com/ledgerwatch/erigon/eth/ethconfig"
@ -517,35 +518,44 @@ func isWebsocket(r *http.Request) bool {
strings.Contains(strings.ToLower(r.Header.Get("Connection")), "upgrade") strings.Contains(strings.ToLower(r.Header.Get("Connection")), "upgrade")
} }
func createHandler(cfg httpcfg.HttpCfg, apiList []rpc.API, httpHandler http.Handler, wsHandler http.Handler, isAuth bool) (http.Handler, error) { // obtainJWTSecret loads the jwt-secret, either from the provided config,
var jwtVerificationKey []byte // or from the default location. If neither of those are present, it generates
var err error // a new secret and stores to the default location.
func obtainJWTSecret(cfg httpcfg.HttpCfg) ([]byte, error) {
if isAuth { var fileName string
// If no file is specified we generate a key in jwt.hex if len(cfg.JWTSecretPath) > 0 {
if cfg.JWTSecretPath == "" { // path provided
jwtVerificationKey := make([]byte, 32) fileName = cfg.JWTSecretPath
rand.Read(jwtVerificationKey) } else {
jwtVerificationKey = []byte(common.Bytes2Hex(jwtVerificationKey)) // no path provided, use default
f, err := os.Create(JwtDefaultFile) fileName = JwtDefaultFile
if err != nil { }
return nil, err // try reading from file
} log.Info("Reading JWT secret", "path", fileName)
defer f.Close() if data, err := os.ReadFile(fileName); err == nil {
jwtSecret := common.FromHex(strings.TrimSpace(string(data)))
_, err = f.Write(jwtVerificationKey) if len(jwtSecret) == 32 {
if err != nil { return jwtSecret, nil
return nil, err
}
} else {
jwtVerificationKey, err = ioutil.ReadFile(cfg.JWTSecretPath)
if err != nil {
return nil, err
}
if len(jwtVerificationKey) != 64 {
return nil, fmt.Errorf("error: invalid size of verification key in %s", cfg.JWTSecretPath)
}
} }
log.Error("Invalid JWT secret", "path", fileName, "length", len(jwtSecret))
return nil, errors.New("invalid JWT secret")
}
// Need to generate one
jwtSecret := make([]byte, 32)
rand.Read(jwtSecret)
if err := os.WriteFile(fileName, []byte(hexutil.Encode(jwtSecret)), 0600); err != nil {
return nil, err
}
log.Info("Generated JWT secret", "path", fileName)
return jwtSecret, nil
}
func createHandler(cfg httpcfg.HttpCfg, apiList []rpc.API, httpHandler http.Handler, wsHandler http.Handler, isAuth bool) (http.Handler, error) {
// Finds jwt secret
jwtVerificationKey, err := obtainJWTSecret(cfg)
if err != nil {
return nil, err
} }
var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@ -559,26 +569,41 @@ func createHandler(cfg httpcfg.HttpCfg, apiList []rpc.API, httpHandler http.Hand
} }
if isAuth { if isAuth {
var tokenStr string
// Check if JWT signature is correct // Check if JWT signature is correct
tokenStr, ok := r.Header["Authorization"] if auth := r.Header.Get("Authorization"); strings.HasPrefix(auth, "Bearer ") {
if !ok { tokenStr = strings.TrimPrefix(auth, "Bearer ")
w.WriteHeader(http.StatusBadRequest) }
if len(tokenStr) == 0 {
http.Error(w, "missing token", http.StatusForbidden)
return return
} }
claims := jwt.StandardClaims{} keyFunc := func(token *jwt.Token) (interface{}, error) {
tkn, err := jwt.ParseWithClaims(strings.Replace(tokenStr[0], "Bearer ", "", 1), &claims, func(token *jwt.Token) (interface{}, error) {
return jwtVerificationKey, nil return jwtVerificationKey, nil
})
if err != nil || !tkn.Valid {
w.WriteHeader(http.StatusUnauthorized)
return
} }
// Validate time of iat claims := jwt.RegisteredClaims{}
now := time.Now().Unix() // We explicitly set only HS256 allowed, and also disables the
if claims.IssuedAt > now+JwtTokenExpiry.Nanoseconds() && claims.IssuedAt < now-JwtTokenExpiry.Nanoseconds() { // claim-check: the RegisteredClaims internally requires 'iat' to
w.WriteHeader(http.StatusUnauthorized) // be no later than 'now', but we allow for a bit of drift.
return token, err := jwt.ParseWithClaims(tokenStr, &claims, keyFunc,
jwt.WithValidMethods([]string{"HS256"}),
jwt.WithoutClaimsValidation())
switch {
case err != nil:
http.Error(w, err.Error(), http.StatusForbidden)
case !token.Valid:
http.Error(w, "invalid token", http.StatusForbidden)
case !claims.VerifyExpiresAt(time.Now(), false): // optional
http.Error(w, "token is expired", http.StatusForbidden)
case claims.IssuedAt == nil:
http.Error(w, "missing issued-at", http.StatusForbidden)
case time.Since(claims.IssuedAt.Time) > JwtTokenExpiry:
http.Error(w, "stale token", http.StatusForbidden)
case time.Until(claims.IssuedAt.Time) > JwtTokenExpiry:
http.Error(w, "future token", http.StatusForbidden)
} }
} }