forked from Yawning/bulb
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathcmd_authenticate.go
137 lines (126 loc) · 4.49 KB
/
cmd_authenticate.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
// cmd_authenticate.go - AUTHENTICATE/AUTHCHALLENGE commands.
//
// To the extent possible under law, Yawning Angel waived all copyright
// and related or neighboring rights to bulb, using the creative
// commons "cc0" public domain dedication. See LICENSE or
// <http://creativecommons.org/publicdomain/zero/1.0/> for full details.
package bulb
import (
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"encoding/hex"
"io/ioutil"
"strings"
)
// Authenticate authenticates with the Tor instance using the "best" possible
// authentication method. The password argument is entirely optional, and will
// only be used if the "SAFECOOKE" and "NULL" authentication methods are not
// available and "HASHEDPASSWORD" is.
func (c *Conn) Authenticate(password string) error {
if c.isAuthenticated {
return nil
}
// Determine the supported authentication methods, and the cookie path.
pi, err := c.ProtocolInfo()
if err != nil {
return err
}
// "COOKIE" authentication exists, but anything modern supports
// "SAFECOOKIE".
const (
cmdAuthenticate = "AUTHENTICATE"
authMethodNull = "NULL"
authMethodPassword = "HASHEDPASSWORD"
authMethodSafeCookie = "SAFECOOKIE"
)
if pi.AuthMethods[authMethodNull] {
_, err = c.Request(cmdAuthenticate)
c.isAuthenticated = err == nil
return err
} else if pi.AuthMethods[authMethodSafeCookie] {
const (
authCookieLength = 32
authNonceLength = 32
authHashLength = 32
authServerHashKey = "Tor safe cookie authentication server-to-controller hash"
authClientHashKey = "Tor safe cookie authentication controller-to-server hash"
)
if pi.CookieFile == "" {
return newProtocolError("invalid (empty) COOKIEFILE")
}
cookie, err := ioutil.ReadFile(pi.CookieFile)
if err != nil {
return newProtocolError("failed to read COOKIEFILE: %v", err)
} else if len(cookie) != authCookieLength {
return newProtocolError("invalid cookie file length: %d", len(cookie))
}
// Send an AUTHCHALLENGE command, and parse the response.
var clientNonce [authNonceLength]byte
if _, err := rand.Read(clientNonce[:]); err != nil {
return newProtocolError("failed to generate clientNonce: %v", err)
}
clientNonceStr := hex.EncodeToString(clientNonce[:])
resp, err := c.Request("AUTHCHALLENGE %s %s", authMethodSafeCookie, clientNonceStr)
if err != nil {
return err
}
splitResp := strings.Split(resp.Reply, " ")
if len(splitResp) != 3 {
return newProtocolError("invalid AUTHCHALLENGE response")
}
serverHashStr := strings.TrimPrefix(splitResp[1], "SERVERHASH=")
if serverHashStr == splitResp[1] {
return newProtocolError("missing SERVERHASH")
}
serverHash, err := hex.DecodeString(serverHashStr)
if err != nil {
return newProtocolError("failed to decode ServerHash: %v", err)
}
if len(serverHash) != authHashLength {
return newProtocolError("invalid ServerHash length: %d", len(serverHash))
}
serverNonceStr := strings.TrimPrefix(splitResp[2], "SERVERNONCE=")
if serverNonceStr == splitResp[2] {
return newProtocolError("missing SERVERNONCE")
}
serverNonce, err := hex.DecodeString(serverNonceStr)
if err != nil {
return newProtocolError("failed to decode ServerNonce: %v", err)
}
if len(serverNonce) != authNonceLength {
return newProtocolError("invalid ServerNonce length: %d", len(serverNonce))
}
// Validate the ServerHash.
m := hmac.New(sha256.New, []byte(authServerHashKey))
m.Write(cookie)
m.Write(clientNonce[:])
m.Write(serverNonce)
dervServerHash := m.Sum(nil)
if !hmac.Equal(serverHash, dervServerHash) {
return newProtocolError("invalid ServerHash: mismatch")
}
// Calculate the ClientHash, and issue the AUTHENTICATE.
m = hmac.New(sha256.New, []byte(authClientHashKey))
m.Write(cookie)
m.Write(clientNonce[:])
m.Write(serverNonce)
clientHash := m.Sum(nil)
clientHashStr := hex.EncodeToString(clientHash)
_, err = c.Request("%s %s", cmdAuthenticate, clientHashStr)
c.isAuthenticated = err == nil
return err
} else if pi.AuthMethods[authMethodPassword] {
// Despite the name HASHEDPASSWORD, the raw password is actually sent.
// According to the code, this can either be a QuotedString, or base16
// encoded, so go with the later since it's easier to handle.
if password == "" {
return newProtocolError("password auth needs a password")
}
passwordStr := hex.EncodeToString([]byte(password))
_, err = c.Request("%s %s", cmdAuthenticate, passwordStr)
c.isAuthenticated = err == nil
return err
}
return newProtocolError("no supported authentication methods")
}