Skip to content

Commit

Permalink
use hash of password rather than plaintext password
Browse files Browse the repository at this point in the history
  • Loading branch information
danlaine committed Jun 30, 2020
1 parent e144b10 commit fe3e1a3
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 45 deletions.
27 changes: 15 additions & 12 deletions api/auth/auth.go
@@ -1,6 +1,7 @@
package auth

import (
"bytes"
"errors"
"fmt"
"io"
Expand All @@ -10,6 +11,8 @@ import (
"sync"
"time"

"github.com/ava-labs/gecko/utils/hashing"

"github.com/ava-labs/gecko/utils/timer"
jwt "github.com/dgrijalva/jwt-go"
)
Expand All @@ -34,11 +37,11 @@ var (

// Auth handles HTTP API authorization for this node
type Auth struct {
lock sync.RWMutex // Prevent race condition when accessing password
Enabled bool // True iff API calls need auth token
clock timer.Clock // Tells the time. Can be faked for testing
Password string // The password. Can be changed via API call.
revoked []string // List of tokens that have been revoked
lock sync.RWMutex // Prevent race condition when accessing password
Enabled bool // True iff API calls need auth token
clock timer.Clock // Tells the time. Can be faked for testing
HashedPassword []byte // Hash of the password. Can be changed via API call.
revoked []string // List of tokens that have been revoked
}

// Custom claim type used for API access token
Expand Down Expand Up @@ -70,7 +73,7 @@ func getToken(r *http.Request) (string, error) {
func (auth *Auth) newToken(password string, endpoints []string) (string, error) {
auth.lock.RLock()
defer auth.lock.RUnlock()
if password != auth.Password {
if !bytes.Equal(hashing.ComputeHash256([]byte(password)), auth.HashedPassword) {
return "", errWrongPassword
}
canAccessAll := false
Expand All @@ -91,7 +94,7 @@ func (auth *Auth) newToken(password string, endpoints []string) (string, error)
claims.Endpoints = endpoints
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString([]byte(auth.Password)) // Sign the token and return its string repr.
return token.SignedString(auth.HashedPassword) // Sign the token and return its string repr.

}

Expand All @@ -104,12 +107,12 @@ func (auth *Auth) newToken(password string, endpoints []string) (string, error)
func (auth *Auth) revokeToken(tokenStr string, password string) error {
auth.lock.Lock()
defer auth.lock.Unlock()
if auth.Password != password {
if !bytes.Equal(auth.HashedPassword, hashing.ComputeHash256([]byte(password))) {
return errWrongPassword
}

token, err := jwt.Parse(tokenStr, func(*jwt.Token) (interface{}, error) { // See if token is well-formed and signature is right
return []byte(auth.Password), nil
return auth.HashedPassword, nil
})
if err == nil && token.Valid { // Only need to revoke if the token is valid
auth.revoked = append(auth.revoked, tokenStr)
Expand All @@ -124,14 +127,14 @@ func (auth *Auth) revokeToken(tokenStr string, password string) error {
func (auth *Auth) changePassword(oldPassword, newPassword string) error {
auth.lock.Lock()
defer auth.lock.Unlock()
if auth.Password != oldPassword {
if !bytes.Equal(auth.HashedPassword, hashing.ComputeHash256([]byte(oldPassword))) {
return errWrongPassword
} else if len(newPassword) == 0 || len(newPassword) > maxPasswordLen {
return fmt.Errorf("new password length exceeds maximum length, %d", maxPasswordLen)
} else if oldPassword == newPassword {
return errors.New("new password can't be same as old password")
}
auth.Password = newPassword
auth.HashedPassword = hashing.ComputeHash256([]byte(newPassword))
auth.revoked = []string{} // All the revoked tokens are now invalid; no need to mark specifically as revoked
return nil
}
Expand Down Expand Up @@ -162,7 +165,7 @@ func (auth *Auth) WrapHandler(h http.Handler) http.Handler {
token, err := jwt.ParseWithClaims(tokenStr, &endpointClaims{}, func(*jwt.Token) (interface{}, error) { // See if token is well-formed and signature is right
auth.lock.RLock()
defer auth.lock.RUnlock()
return []byte(auth.Password), nil
return auth.HashedPassword, nil
})
if err != nil { // Probably because signature wrong
w.WriteHeader(http.StatusUnauthorized)
Expand Down
66 changes: 35 additions & 31 deletions api/auth/auth_test.go
@@ -1,6 +1,7 @@
package auth

import (
"bytes"
"fmt"
"net/http"
"net/http/httptest"
Expand All @@ -9,13 +10,16 @@ import (
"testing"
"time"

"github.com/ava-labs/gecko/utils/hashing"

"github.com/ava-labs/gecko/utils/timer"

jwt "github.com/dgrijalva/jwt-go"
)

const (
password = "password"
var (
password = "password"
hashedPassword = hashing.ComputeHash256([]byte(password))
)

var (
Expand All @@ -25,8 +29,8 @@ var (

func TestNewTokenWrongPassword(t *testing.T) {
auth := Auth{
Enabled: true,
Password: password,
Enabled: true,
HashedPassword: hashedPassword,
}
if _, err := auth.newToken("", []string{"endpoint1, endpoint2"}); err == nil {
t.Fatal("should have failed because password is wrong")
Expand All @@ -37,8 +41,8 @@ func TestNewTokenWrongPassword(t *testing.T) {

func TestNewTokenHappyPath(t *testing.T) {
auth := Auth{
Enabled: true,
Password: password,
Enabled: true,
HashedPassword: hashedPassword,
}
now := time.Now()
auth.clock.Set(now)
Expand All @@ -54,7 +58,7 @@ func TestNewTokenHappyPath(t *testing.T) {
token, err := jwt.ParseWithClaims(tokenStr, &endpointClaims{}, func(*jwt.Token) (interface{}, error) {
auth.lock.RLock()
defer auth.lock.RUnlock()
return []byte(auth.Password), nil
return auth.HashedPassword, nil
})
if err != nil {
t.Fatalf("couldn't parse new token: %s", err)
Expand All @@ -74,8 +78,8 @@ func TestNewTokenHappyPath(t *testing.T) {

func TestTokenHasWrongSig(t *testing.T) {
auth := Auth{
Enabled: true,
Password: password,
Enabled: true,
HashedPassword: hashedPassword,
}

// Make a token
Expand Down Expand Up @@ -106,8 +110,8 @@ func TestTokenHasWrongSig(t *testing.T) {

func TestChangePassword(t *testing.T) {
auth := Auth{
Enabled: true,
Password: password,
Enabled: true,
HashedPassword: hashedPassword,
}

password2 := "password2"
Expand All @@ -121,7 +125,7 @@ func TestChangePassword(t *testing.T) {
t.Fatal("should have succeeded")
}

if auth.Password != password2 {
if !bytes.Equal(auth.HashedPassword, hashing.ComputeHash256([]byte(password2))) {
t.Fatal("password should have been changed")
}

Expand Down Expand Up @@ -161,8 +165,8 @@ func TestGetToken(t *testing.T) {

func TestRevokeToken(t *testing.T) {
auth := Auth{
Enabled: true,
Password: password,
Enabled: true,
HashedPassword: hashedPassword,
}

// Make a token
Expand All @@ -181,8 +185,8 @@ func TestRevokeToken(t *testing.T) {

func TestWrapHandlerHappyPath(t *testing.T) {
auth := Auth{
Enabled: true,
Password: password,
Enabled: true,
HashedPassword: hashedPassword,
}

// Make a token
Expand All @@ -207,8 +211,8 @@ func TestWrapHandlerHappyPath(t *testing.T) {

func TestWrapHandlerRevokedToken(t *testing.T) {
auth := Auth{
Enabled: true,
Password: password,
Enabled: true,
HashedPassword: hashedPassword,
}

// Make a token
Expand Down Expand Up @@ -236,9 +240,9 @@ func TestWrapHandlerRevokedToken(t *testing.T) {

func TestWrapHandlerExpiredToken(t *testing.T) {
auth := Auth{
Enabled: true,
Password: password,
clock: timer.Clock{},
Enabled: true,
HashedPassword: hashedPassword,
clock: timer.Clock{},
}
auth.clock.Set(time.Now().Add(-2 * TokenLifespan))

Expand All @@ -264,8 +268,8 @@ func TestWrapHandlerExpiredToken(t *testing.T) {

func TestWrapHandlerNoAuthToken(t *testing.T) {
auth := Auth{
Enabled: true,
Password: password,
Enabled: true,
HashedPassword: hashedPassword,
}

endpoints := []string{"/ext/info", "/ext/bc/X", "/ext/metrics"}
Expand All @@ -282,8 +286,8 @@ func TestWrapHandlerNoAuthToken(t *testing.T) {

func TestWrapHandlerUnauthorizedEndpoint(t *testing.T) {
auth := Auth{
Enabled: true,
Password: password,
Enabled: true,
HashedPassword: hashedPassword,
}

// Make a token
Expand All @@ -309,8 +313,8 @@ func TestWrapHandlerUnauthorizedEndpoint(t *testing.T) {

func TestWrapHandlerAuthEndpoint(t *testing.T) {
auth := Auth{
Enabled: true,
Password: password,
Enabled: true,
HashedPassword: hashedPassword,
}

// Make a token
Expand All @@ -332,8 +336,8 @@ func TestWrapHandlerAuthEndpoint(t *testing.T) {

func TestWrapHandlerAccessAll(t *testing.T) {
auth := Auth{
Enabled: true,
Password: password,
Enabled: true,
HashedPassword: hashedPassword,
}

// Make a token that allows access to all endpoints
Expand All @@ -357,8 +361,8 @@ func TestWrapHandlerAccessAll(t *testing.T) {

func TestWrapHandlerAuthDisabled(t *testing.T) {
auth := Auth{
Enabled: false,
Password: password,
Enabled: false,
HashedPassword: hashedPassword,
}

endpoints := []string{"/ext/info", "/ext/bc/X", "/ext/metrics", "", "/foo", "/ext/foo/info", "/ext/auth"}
Expand Down
5 changes: 3 additions & 2 deletions api/server.go
Expand Up @@ -19,6 +19,7 @@ import (
"github.com/ava-labs/gecko/api/auth"
"github.com/ava-labs/gecko/snow"
"github.com/ava-labs/gecko/snow/engine/common"
"github.com/ava-labs/gecko/utils/hashing"
"github.com/ava-labs/gecko/utils/logging"
)

Expand Down Expand Up @@ -52,8 +53,8 @@ func (s *Server) Initialize(log logging.Logger, factory logging.Factory, host st
s.listenAddress = fmt.Sprintf("%s:%d", host, port)
s.router = newRouter()
s.auth = &auth.Auth{
Enabled: authEnabled,
Password: authPassword,
Enabled: authEnabled,
HashedPassword: hashing.ComputeHash256([]byte(authPassword)),
}
if authEnabled { // only create auth service if token authorization is required
s.log.Info("API authorization is enabled. Auth token must be passed in header of API requests (except requests to auth service.)")
Expand Down

0 comments on commit fe3e1a3

Please sign in to comment.