Skip to content

Commit

Permalink
Hash admin password using SHA-256
Browse files Browse the repository at this point in the history
  • Loading branch information
ukane-philemon authored and ukane-philemon committed Jun 10, 2022
1 parent 84bc9f9 commit 499d19a
Show file tree
Hide file tree
Showing 8 changed files with 75 additions and 28 deletions.
1 change: 0 additions & 1 deletion config.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ type config struct {
BackupInterval time.Duration `long:"backupinterval" ini-name:"backupinterval" description:"Time period between automatic database backups. Valid time units are {s,m,h}. Minimum 30 seconds."`
VspClosed bool `long:"vspclosed" ini-name:"vspclosed" description:"Closed prevents the VSP from accepting new tickets."`
VspClosedMsg string `long:"vspclosedmsg" ini-name:"vspclosedmsg" description:"A short message displayed on the webpage and returned by the status API endpoint if vspclosed is true."`
AdminPass string `long:"adminpass" ini-name:"adminpass" description:"Password for accessing admin page. INSECURE. Do not set unless absolutely necessary."`
Designation string `long:"designation" ini-name:"designation" description:"Short name for the VSP. Customizes the logo in the top toolbar."`

// The following flags should be set on CLI only, not via config file.
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,6 @@ require (
github.com/jrick/logrotate v1.0.0
github.com/jrick/wsrpc/v2 v2.3.4
go.etcd.io/bbolt v1.3.6
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 // indirect
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
)
10 changes: 7 additions & 3 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -159,8 +159,9 @@ go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=
go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
Expand All @@ -173,8 +174,9 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
Expand All @@ -195,15 +197,17 @@ golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
Expand Down
47 changes: 42 additions & 5 deletions prompt.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package main

import (
"bufio"
"context"
"crypto/sha256"
"fmt"
Expand Down Expand Up @@ -61,21 +62,57 @@ func passwordPrompt(ctx context.Context, prompt string) ([]byte, error) {

// passwordHashPrompt prompts the user to enter a password and returns its
// SHA256 hash. Password must not be an empty string.
func passwordHashPrompt(ctx context.Context, prompt string) ([sha256.Size]byte, error) {
func passwordHashPrompt(ctx context.Context, prompt string) ([]byte, error) {
var passBytes []byte
var err error
var authSHA [sha256.Size]byte

// Ensure passBytes is not empty.
for len(passBytes) == 0 {
passBytes, err = passwordPrompt(ctx, prompt)
if err != nil {
return authSHA, err
return nil, err
}
}

authSHA = sha256.Sum256(passBytes)
authHash := sha256.Sum256(passBytes)
// Zero password bytes.
clearBytes(passBytes)
return authSHA, nil
return authHash[:], nil
}

// readPassHashFromFile reads admin password hash from provided file.
func readPassHashFromFile(passwordDir string) ([]byte, error) {
passwordFile, err := os.Open(passwordDir)
if err != nil {
return nil, err
}
defer passwordFile.Close()

reader := bufio.NewReader(passwordFile)
adminAuthHash, _, err := reader.ReadLine()
if err != nil {
return nil, err
}

return adminAuthHash, nil
}

// createPassHashFile prompts user for password,
// hashes the provided password and saves the hashed password to a file.
func createPassHashFile(ctx context.Context, passwordDir string) ([]byte, error) {
adminAuthHash, err := passwordHashPrompt(ctx, "Enter admin Password:")
if err != nil {
return nil, err
}
passwordFile, err := os.Create(passwordDir)
if err != nil {
return nil, err
}
defer passwordFile.Close()
// Length of byte is ignored
_, err = passwordFile.Write(adminAuthHash)
if err != nil {
return nil, err
}
return adminAuthHash, nil
}
34 changes: 20 additions & 14 deletions vspd.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ package main

import (
"context"
"crypto/sha256"
"errors"
"fmt"
"os"
"path/filepath"
"runtime"
"sync"

Expand All @@ -20,11 +20,15 @@ import (
"github.com/decred/vspd/webapi"
)

// maxVoteChangeRecords defines how many vote change records will be stored for
// each ticket. The limit is in place to mitigate DoS attacks on server storage
// space. When storing a new record breaches this limit, the oldest record in
// the database is deleted.
const maxVoteChangeRecords = 10
const (
// maxVoteChangeRecords defines how many vote change records will be stored for
// each ticket. The limit is in place to mitigate DoS attacks on server storage
// space. When storing a new record breaches this limit, the oldest record in
// the database is deleted.
maxVoteChangeRecords = 10
// passwordHashFileName is the name of the file containing admin password hash.
passwordHashFileName = "password.hash"
)

func main() {
// Create a context that is cancelled when a shutdown request is received
Expand Down Expand Up @@ -52,17 +56,19 @@ func run(ctx context.Context) error {
return err
}

// Request admin password if admin password is not set in config.
var adminAuthSHA [32]byte
if cfg.AdminPass == "" {
adminAuthSHA, err = passwordHashPrompt(ctx, "Admin password for accessing admin page: ")
// Request admin password if admin password hash file is not found.
var adminAuthHash []byte
passwordDir := filepath.Join(cfg.HomeDir, passwordHashFileName)
if fileExists(passwordDir) {
adminAuthHash, err = readPassHashFromFile(passwordDir)
if err != nil {
return fmt.Errorf("cannot use password: %v", err)
}
} else {
adminAuthSHA = sha256.Sum256([]byte(cfg.AdminPass))
// Clear password string
cfg.AdminPass = ""
adminAuthHash, err = createPassHashFile(ctx, passwordDir)
if err != nil {
return fmt.Errorf("cannot use password: %v", err)
}
}

// Show version at startup.
Expand Down Expand Up @@ -112,7 +118,7 @@ func run(ctx context.Context) error {
SupportEmail: cfg.SupportEmail,
VspClosed: cfg.VspClosed,
VspClosedMsg: cfg.VspClosedMsg,
AdminAuthSHA: adminAuthSHA,
AdminAuthHash: adminAuthHash,
Debug: cfg.WebServerDebug,
Designation: cfg.Designation,
MaxVoteChangeRecords: maxVoteChangeRecords,
Expand Down
4 changes: 2 additions & 2 deletions webapi/admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,8 +198,8 @@ func ticketSearch(c *gin.Context) {
// the current session will be authenticated as an admin.
func adminLogin(c *gin.Context) {
password := c.PostForm("password")
authSHA := sha256.Sum256([]byte(password))
if subtle.ConstantTimeCompare(cfg.AdminAuthSHA[:], authSHA[:]) != 1 {
passwordHash := sha256.Sum256([]byte(password))
if subtle.ConstantTimeCompare(cfg.AdminAuthHash[:], passwordHash[:]) != 1 {
log.Warnf("Failed login attempt from %s", c.ClientIP())
c.HTML(http.StatusUnauthorized, "login.html", gin.H{
"WebApiCache": getCache(),
Expand Down
4 changes: 2 additions & 2 deletions webapi/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -392,8 +392,8 @@ func authMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// User is ignored
_, password, ok := c.Request.BasicAuth()
passAuthSHA := sha256.Sum256([]byte(password))
if !ok || subtle.ConstantTimeCompare(passAuthSHA[:], cfg.AdminAuthSHA[:]) != 1 {
passwordHash := sha256.Sum256([]byte(password))
if !ok || subtle.ConstantTimeCompare(cfg.AdminAuthHash[:], passwordHash[:]) != 1 {
// Credentials doesn't match, we return 401 and abort handlers chain.
c.Header("WWW-Authenticate", `Basic realm="Authorization Required"`)
c.AbortWithStatus(http.StatusUnauthorized)
Expand Down
2 changes: 1 addition & 1 deletion webapi/webapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ type Config struct {
SupportEmail string
VspClosed bool
VspClosedMsg string
AdminAuthSHA [32]byte
AdminAuthHash []byte
Debug bool
Designation string
MaxVoteChangeRecords int
Expand Down

0 comments on commit 499d19a

Please sign in to comment.