Skip to content

Commit 9bdcee8

Browse files
Hash admin password using SHA-256
1 parent ae8a407 commit 9bdcee8

File tree

9 files changed

+75
-28
lines changed

9 files changed

+75
-28
lines changed

config.go

-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,6 @@ type config struct {
6262
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."`
6363
VspClosed bool `long:"vspclosed" ini-name:"vspclosed" description:"Closed prevents the VSP from accepting new tickets."`
6464
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."`
65-
AdminPass string `long:"adminpass" ini-name:"adminpass" description:"Password for accessing admin page. INSECURE. Do not set unless absolutely necessary."`
6665
Designation string `long:"designation" ini-name:"designation" description:"Short name for the VSP. Customizes the logo in the top toolbar."`
6766

6867
// The following flags should be set on CLI only, not via config file.

go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,6 @@ require (
2222
github.com/jrick/logrotate v1.0.0
2323
github.com/jrick/wsrpc/v2 v2.3.4
2424
go.etcd.io/bbolt v1.3.6
25+
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 // indirect
2526
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
2627
)

go.sum

+7-3
Original file line numberDiff line numberDiff line change
@@ -159,8 +159,9 @@ go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=
159159
go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
160160
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
161161
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
162-
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
163162
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
163+
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M=
164+
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
164165
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
165166
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
166167
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
@@ -173,8 +174,9 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn
173174
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
174175
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
175176
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
176-
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw=
177177
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
178+
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE=
179+
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
178180
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
179181
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
180182
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -195,15 +197,17 @@ golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7w
195197
golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
196198
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
197199
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
200+
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
198201
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
199202
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
200203
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
201204
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
202205
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
203206
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
204207
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
205-
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
206208
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
209+
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
210+
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
207211
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
208212
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
209213
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=

prompt.go

+42-5
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package main
66

77
import (
8+
"bufio"
89
"context"
910
"crypto/sha256"
1011
"fmt"
@@ -61,21 +62,57 @@ func passwordPrompt(ctx context.Context, prompt string) ([]byte, error) {
6162

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

6969
// Ensure passBytes is not empty.
7070
for len(passBytes) == 0 {
7171
passBytes, err = passwordPrompt(ctx, prompt)
7272
if err != nil {
73-
return authSHA, err
73+
return nil, err
7474
}
7575
}
7676

77-
authSHA = sha256.Sum256(passBytes)
77+
authHash := sha256.Sum256(passBytes)
7878
// Zero password bytes.
7979
clearBytes(passBytes)
80-
return authSHA, nil
80+
return authHash[:], nil
81+
}
82+
83+
// readPassHashFromFile reads admin password hash from provided file.
84+
func readPassHashFromFile(passwordDir string) ([]byte, error) {
85+
passwordFile, err := os.Open(passwordDir)
86+
if err != nil {
87+
return nil, err
88+
}
89+
defer passwordFile.Close()
90+
91+
reader := bufio.NewReader(passwordFile)
92+
adminAuthHash, _, err := reader.ReadLine()
93+
if err != nil {
94+
return nil, err
95+
}
96+
97+
return adminAuthHash, nil
98+
}
99+
100+
// createPassHashFile prompts user for password,
101+
// hashes the provided password and saves the hashed password to a file.
102+
func createPassHashFile(ctx context.Context, passwordDir string) ([]byte, error) {
103+
adminAuthHash, err := passwordHashPrompt(ctx, "Enter admin Password:")
104+
if err != nil {
105+
return nil, err
106+
}
107+
passwordFile, err := os.Create(passwordDir)
108+
if err != nil {
109+
return nil, err
110+
}
111+
defer passwordFile.Close()
112+
// Length of byte is ignored
113+
_, err = passwordFile.Write(adminAuthHash)
114+
if err != nil {
115+
return nil, err
116+
}
117+
return adminAuthHash, nil
81118
}

vspd.exe

-17.4 MB
Binary file not shown.

vspd.go

+20-14
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ package main
66

77
import (
88
"context"
9-
"crypto/sha256"
109
"errors"
1110
"fmt"
1211
"os"
12+
"path/filepath"
1313
"runtime"
1414
"sync"
1515

@@ -20,11 +20,15 @@ import (
2020
"github.com/decred/vspd/webapi"
2121
)
2222

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

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

55-
// Request admin password if admin password is not set in config.
56-
var adminAuthSHA [32]byte
57-
if cfg.AdminPass == "" {
58-
adminAuthSHA, err = passwordHashPrompt(ctx, "Admin password for accessing admin page: ")
59+
// Request admin password if admin password hash file is not found.
60+
var adminAuthHash []byte
61+
passwordDir := filepath.Join(cfg.HomeDir, passwordHashFileName)
62+
if fileExists(passwordDir) {
63+
adminAuthHash, err = readPassHashFromFile(passwordDir)
5964
if err != nil {
6065
return fmt.Errorf("cannot use password: %v", err)
6166
}
6267
} else {
63-
adminAuthSHA = sha256.Sum256([]byte(cfg.AdminPass))
64-
// Clear password string
65-
cfg.AdminPass = ""
68+
adminAuthHash, err = createPassHashFile(ctx, passwordDir)
69+
if err != nil {
70+
return fmt.Errorf("cannot use password: %v", err)
71+
}
6672
}
6773

6874
// Show version at startup.
@@ -112,7 +118,7 @@ func run(ctx context.Context) error {
112118
SupportEmail: cfg.SupportEmail,
113119
VspClosed: cfg.VspClosed,
114120
VspClosedMsg: cfg.VspClosedMsg,
115-
AdminAuthSHA: adminAuthSHA,
121+
AdminAuthHash: adminAuthHash,
116122
Debug: cfg.WebServerDebug,
117123
Designation: cfg.Designation,
118124
MaxVoteChangeRecords: maxVoteChangeRecords,

webapi/admin.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -198,8 +198,8 @@ func ticketSearch(c *gin.Context) {
198198
// the current session will be authenticated as an admin.
199199
func adminLogin(c *gin.Context) {
200200
password := c.PostForm("password")
201-
authSHA := sha256.Sum256([]byte(password))
202-
if subtle.ConstantTimeCompare(cfg.AdminAuthSHA[:], authSHA[:]) != 1 {
201+
passwordHash := sha256.Sum256([]byte(password))
202+
if subtle.ConstantTimeCompare(cfg.AdminAuthHash[:], passwordHash[:]) != 1 {
203203
log.Warnf("Failed login attempt from %s", c.ClientIP())
204204
c.HTML(http.StatusUnauthorized, "login.html", gin.H{
205205
"WebApiCache": getCache(),

webapi/middleware.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -392,8 +392,8 @@ func authMiddleware() gin.HandlerFunc {
392392
return func(c *gin.Context) {
393393
// User is ignored
394394
_, password, ok := c.Request.BasicAuth()
395-
passAuthSHA := sha256.Sum256([]byte(password))
396-
if !ok || subtle.ConstantTimeCompare(passAuthSHA[:], cfg.AdminAuthSHA[:]) != 1 {
395+
passwordHash := sha256.Sum256([]byte(password))
396+
if !ok || subtle.ConstantTimeCompare(cfg.AdminAuthHash[:], passwordHash[:]) != 1 {
397397
// Credentials doesn't match, we return 401 and abort handlers chain.
398398
c.Header("WWW-Authenticate", `Basic realm="Authorization Required"`)
399399
c.AbortWithStatus(http.StatusUnauthorized)

webapi/webapi.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ type Config struct {
3333
SupportEmail string
3434
VspClosed bool
3535
VspClosedMsg string
36-
AdminAuthSHA [32]byte
36+
AdminAuthHash []byte
3737
Debug bool
3838
Designation string
3939
MaxVoteChangeRecords int

0 commit comments

Comments
 (0)