This repository has been archived on 2019-09-10. You can view files and clone it, but cannot push or open issues or pull requests.
asaur/cmd_authenticate.go

125 lines
3.8 KiB
Go
Raw Normal View History

2015-03-18 15:10:49 +00:00
// 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.
func (c *Conn) Authenticate() error {
if c.isAuthenticated {
return nil
}
// Determine the supported authentication methods, and the cookie path.
pi, err := c.ProtocolInfo()
if err != nil {
return err
}
// TODO: Add support for password authentication. "COOKIE" auth is
// superceded by "SAFECOOKIE" on all reasonable versions of Tor.
const (
cmdAuthenticate = "AUTHENTICATE"
authMethodNull = "NULL"
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 {
return newProtocolError("no supported authentication methods")
}
}