forked from panoplyio/pgsrv
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathauth.go
167 lines (139 loc) · 4.62 KB
/
auth.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
package pgsrv
import (
"bytes"
"crypto/md5"
"crypto/rand"
"fmt"
)
// authenticator interface defines objects able to perform user authentication
// that happens at the very beginning of every session.
type authenticator interface {
authenticate() (msg, error)
}
// authenticationNoPassword responds with auth OK immediately.
type authenticationNoPassword struct{}
func (*authenticationNoPassword) authenticate() (msg, error) {
return msg{'R', 0, 0, 0, 8, 0, 0, 0, 0}, nil
}
// messageReadWriter describes objects that handle client-server communication.
// Objects implementing this interface are used to send password requests to users,
// and receive their responses.
type messageReadWriter interface {
Write(m msg) error
Read() (msg, error)
}
// passwordProvider describes objects that are able to provide a password given a user name.
type passwordProvider interface {
getPassword(user string) ([]byte, error)
}
// constantPasswordProvider is a password provider that always returns the same password,
// which it is given during the initialization.
type constantPasswordProvider struct {
password []byte
}
func (cpp *constantPasswordProvider) getPassword(user string) ([]byte, error) {
return cpp.password, nil
}
// authenticationClearText is an authenticator that requests and accepts a clear text password
// from the client. It is not recommended to use it for security reasons.
//
// It requires a messageReadWriter implementation to communicate with the client,
// passwordProvider implementation to verify that the provided password is correct,
// and a map of arguments that were sent at the beginning of the session (user, database, etc)
type authenticationClearText struct {
rw messageReadWriter
args map[string]interface{}
pp passwordProvider
}
func (a *authenticationClearText) authenticate() (msg, error) {
// AuthenticationClearText
passwordRequest := msg{
'R',
0, 0, 0, 8,
0, 0, 0, 3,
}
err := a.rw.Write(passwordRequest)
if err != nil {
return msg{}, err
}
m, err := a.rw.Read()
if err != nil {
return msg{}, err
}
if m.Type() != 'p' {
return msg{},
fmt.Errorf("expected password response, got message type %c", m.Type())
}
user := a.args["user"].(string)
expectedPassword, err := a.pp.getPassword(user)
actualPassword := extractPassword(m)
if !bytes.Equal(expectedPassword, actualPassword) {
return msg{},
fmt.Errorf("Password does not match for user \"%s\"", user)
}
return authOKMsg(), nil
}
// authenticationMD5 is an authenticator that requests and accepts an MD5 hashed password
// from the client.
//
// It requires a messageReadWriter implementation to communicate with the client,
// passwordProvider implementation to verify that the provided password is correct,
// and a map of arguments that were sent at the beginning of the session (user, database, etc)
type authenticationMD5 struct {
rw messageReadWriter
args map[string]interface{}
pp passwordProvider
}
func (a *authenticationMD5) authenticate() (msg, error) {
// AuthenticationMD5Password
passwordRequest := msg{
'R',
0, 0, 0, 12,
0, 0, 0, 5,
}
salt := getRandomSalt()
passwordRequest = append(passwordRequest, salt...)
err := a.rw.Write(passwordRequest)
if err != nil {
return msg{}, err
}
m, err := a.rw.Read()
if err != nil {
return msg{}, err
}
if m.Type() != 'p' {
return msg{},
fmt.Errorf("expected password response, got message type %c", m.Type())
}
user := a.args["user"].(string)
expectedPassword, err := a.pp.getPassword(user)
expectedHash := hashUserPassword(user, expectedPassword, salt)
actualHash := extractPassword(m)
if !bytes.Equal(expectedHash, actualHash) {
return msg{},
fmt.Errorf("Password does not match for user \"%s\"", user)
}
return authOKMsg(), nil
}
// getRandomSalt returns a cryptographically secure random slice of 4 bytes.
func getRandomSalt() []byte {
salt := make([]byte, 4)
rand.Read(salt)
return salt
}
// extractPassword extracts the password from a provided 'p' message.
// It assumes that the message is valid.
func extractPassword(m msg) []byte {
// password starts after the size (4 bytes) and lasts until null-terminator
return m[5 : len(m)-1]
}
// hashUserPassword hashes the provided username and password with the provided salt
// using the same MD5 hashing technique as postgresql MD5 authentication
func hashUserPassword(user string, password, salt []byte) []byte {
// concat('md5', md5(concat(md5(concat(password, username)), random-salt)))
pu := append(password, []byte(user)...)
puHash := fmt.Sprintf("%x", md5.Sum(pu))
puHashSalted := append([]byte(puHash), salt...)
finalHash := fmt.Sprintf("md5%x", md5.Sum(puHashSalted))
return []byte(finalHash)
}