bine/control/cmd_authenticate.go

109 lines
3.5 KiB
Go

package control
import (
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"encoding/hex"
"io/ioutil"
"strings"
)
// Authenticate authenticates with the Tor instance using the "best" possible
// authentication method if not already authenticated and sets the Authenticated
// field. The password argument is optional, and will only be used if the
// "SAFECOOKIE" and "NULL" authentication methods are not available and
// "HASHEDPASSWORD" is.
func (c *Conn) Authenticate(password string) error {
if c.Authenticated {
return nil
}
// Determine the supported authentication methods, and the cookie path.
pi, err := c.ProtocolInfo()
if err != nil {
return err
}
// Get the bytes to pass to with authenticate
var authBytes []byte
if pi.HasAuthMethod("NULL") {
// No auth bytes
} else if pi.HasAuthMethod("SAFECOOKIE") {
if pi.CookieFile == "" {
return c.protoErr("Invalid (empty) COOKIEFILE")
}
cookie, err := ioutil.ReadFile(pi.CookieFile)
if err != nil {
return c.protoErr("Failed to read COOKIEFILE: %v", err)
} else if len(cookie) != 32 {
return c.protoErr("Invalid cookie file length: %v", len(cookie))
}
// Send an AUTHCHALLENGE command, and parse the response.
var clientNonce [32]byte
if _, err := rand.Read(clientNonce[:]); err != nil {
return c.protoErr("Failed to generate clientNonce: %v", err)
}
resp, err := c.SendRequest("AUTHCHALLENGE %s %s", "SAFECOOKIE", hex.EncodeToString(clientNonce[:]))
if err != nil {
return err
}
splitResp := strings.Split(resp.Reply, " ")
if len(splitResp) != 3 || !strings.HasPrefix(splitResp[1], "SERVERHASH=") ||
!strings.HasPrefix(splitResp[2], "SERVERNONCE=") {
return c.protoErr("Invalid AUTHCHALLENGE response")
}
serverHash, err := hex.DecodeString(splitResp[1][11:])
if err != nil {
return c.protoErr("Failed to decode ServerHash: %v", err)
}
if len(serverHash) != 32 {
return c.protoErr("Invalid ServerHash length: %d", len(serverHash))
}
serverNonce, err := hex.DecodeString(splitResp[2][12:])
if err != nil {
return c.protoErr("Failed to decode ServerNonce: %v", err)
}
if len(serverNonce) != 32 {
return c.protoErr("Invalid ServerNonce length: %d", len(serverNonce))
}
// Validate the ServerHash.
m := hmac.New(sha256.New, []byte("Tor safe cookie authentication server-to-controller hash"))
m.Write(cookie)
m.Write(clientNonce[:])
m.Write(serverNonce)
dervServerHash := m.Sum(nil)
if !hmac.Equal(serverHash, dervServerHash) {
return c.protoErr("invalid ServerHash: mismatch")
}
// Calculate the ClientHash, and issue the AUTHENTICATE.
m = hmac.New(sha256.New, []byte("Tor safe cookie authentication controller-to-server hash"))
m.Write(cookie)
m.Write(clientNonce[:])
m.Write(serverNonce)
authBytes = m.Sum(nil)
} else if pi.HasAuthMethod("HASHEDPASSWORD") {
// 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 c.protoErr("password auth needs a password")
}
authBytes = []byte(password)
} else {
return c.protoErr("No supported authentication methods")
}
// Send it
if err = c.sendAuthenticate(authBytes); err == nil {
c.Authenticated = true
}
return err
}
func (c *Conn) sendAuthenticate(byts []byte) error {
if len(byts) == 0 {
return c.sendRequestIgnoreResponse("AUTHENTICATE")
}
return c.sendRequestIgnoreResponse("AUTHENTICATE %v", hex.EncodeToString(byts))
}