diff options
Diffstat (limited to 'vendor/github.com/cretz/bine/control/cmd_authenticate.go')
-rw-r--r-- | vendor/github.com/cretz/bine/control/cmd_authenticate.go | 108 |
1 files changed, 108 insertions, 0 deletions
diff --git a/vendor/github.com/cretz/bine/control/cmd_authenticate.go b/vendor/github.com/cretz/bine/control/cmd_authenticate.go new file mode 100644 index 0000000..c43d864 --- /dev/null +++ b/vendor/github.com/cretz/bine/control/cmd_authenticate.go @@ -0,0 +1,108 @@ +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)) +} |