File: auth.go

package info (click to toggle)
golang-go-dbus 1~bzr20150122-3
  • links: PTS, VCS
  • area: main
  • in suites: bullseye, buster, sid
  • size: 328 kB
  • sloc: makefile: 13
file content (191 lines) | stat: -rw-r--r-- 4,771 bytes parent folder | download
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
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
package dbus

import (
	"bufio"
	"bytes"
	"crypto/rand"
	"crypto/sha1"
	"encoding/hex"
	"errors"
	"io"
	"net"
	"os"
	"strconv"
)

type authenticator interface {
	Mechanism() []byte
	InitialResponse() []byte
	ProcessData(challenge []byte) (response []byte, err error)
}

type authExternal struct {
}

func (p *authExternal) Mechanism() []byte {
	return []byte("EXTERNAL")
}

func (p *authExternal) InitialResponse() []byte {
	uid := []byte(strconv.Itoa(os.Geteuid()))
	uidHex := make([]byte, hex.EncodedLen(len(uid)))
	hex.Encode(uidHex, uid)
	return uidHex
}

func (p *authExternal) ProcessData([]byte) ([]byte, error) {
	return nil, errors.New("Unexpected Response")
}

type authDbusCookieSha1 struct {
}

func (p *authDbusCookieSha1) Mechanism() []byte {
	return []byte("DBUS_COOKIE_SHA1")
}

func (p *authDbusCookieSha1) InitialResponse() []byte {
	user := []byte(os.Getenv("USER"))
	userHex := make([]byte, hex.EncodedLen(len(user)))
	hex.Encode(userHex, user)
	return userHex
}

func (p *authDbusCookieSha1) ProcessData(mesg []byte) ([]byte, error) {
	decodedLen, err := hex.Decode(mesg, mesg)
	if err != nil {
		return nil, err
	}
	mesgTokens := bytes.SplitN(mesg[:decodedLen], []byte(" "), 3)

	file, err := os.Open(os.Getenv("HOME") + "/.dbus-keyrings/" + string(mesgTokens[0]))
	if err != nil {
		return nil, err
	}
	defer file.Close()
	fileStream := bufio.NewReader(file)

	var cookie []byte
	for {
		line, _, err := fileStream.ReadLine()
		if err == io.EOF {
			return nil, errors.New("SHA1 Cookie not found")
		} else if err != nil {
			return nil, err
		}
		cookieTokens := bytes.SplitN(line, []byte(" "), 3)
		if bytes.Compare(cookieTokens[0], mesgTokens[1]) == 0 {
			cookie = cookieTokens[2]
			break
		}
	}

	challenge := make([]byte, len(mesgTokens[2]))
	if _, err = rand.Read(challenge); err != nil {
		return nil, err
	}

	for temp := challenge; ; {
		if index := bytes.IndexAny(temp, " \t"); index == -1 {
			break
		} else if _, err := rand.Read(temp[index : index+1]); err != nil {
			return nil, err
		} else {
			temp = temp[index:]
		}
	}

	hash := sha1.New()
	if _, err := hash.Write(bytes.Join([][]byte{mesgTokens[2], challenge, cookie}, []byte(":"))); err != nil {
		return nil, err
	}

	resp := bytes.Join([][]byte{challenge, []byte(hex.EncodeToString(hash.Sum(nil)))}, []byte(" "))
	respHex := make([]byte, hex.EncodedLen(len(resp)))
	hex.Encode(respHex, resp)
	return respHex, nil
}

func authenticate(conn net.Conn, authenticators []authenticator) error {
	// If no authenticators are provided, try them all
	if authenticators == nil {
		authenticators = []authenticator{
			new(authExternal),
			new(authDbusCookieSha1)}
	}

	// The authentication process starts by writing a nul byte
	// writing at this point does not need to be synced as the connection
	// is not shared at this point.
	if _, err := conn.Write([]byte{0}); err != nil {
		return err
	}

	inStream := bufio.NewReader(conn)
	send := func(command ...[]byte) ([][]byte, error) {
		msg := bytes.Join(command, []byte(" "))
		// writing at this point does not need to be synced as the connection
		// is not shared at this point.
		_, err := conn.Write(append(msg, []byte("\r\n")...))
		if err != nil {
			return nil, err
		}
		line, isPrefix, err := inStream.ReadLine()
		if err != nil {
			return nil, err
		}
		if isPrefix {
			return nil, errors.New("Received line is too long")
		}
		return bytes.Split(line, []byte(" ")), err
	}
	success := false
	for _, auth := range authenticators {
		reply, err := send([]byte("AUTH"), auth.Mechanism(), auth.InitialResponse())
	StatementLoop:
		for {
			if err != nil {
				return err
			}
			if len(reply) < 1 {
				return errors.New("No response command from server")
			}
			switch string(reply[0]) {
			case "OK":
				success = true
				break StatementLoop
			case "REJECTED":
				// XXX: should note the list of
				// supported mechanisms
				break StatementLoop
			case "ERROR":
				return errors.New("Received error from server: " + string(bytes.Join(reply, []byte(" "))))
			case "DATA":
				var response []byte
				response, err = auth.ProcessData(reply[1])
				if err == nil {
					reply, err = send([]byte("DATA"), response)
				} else {
					// Cancel so we can move on to
					// the next mechanism.
					reply, err = send([]byte("CANCEL"))
				}
			default:
				return errors.New("Unknown response from server: " + string(bytes.Join(reply, []byte(" "))))
			}
		}
		if success {
			break
		}
	}
	if !success {
		return errors.New("Could not authenticate with any mechanism")
	}
	// XXX: UNIX FD negotiation would go here.
	// writing at this point does not need to be synced as the connection
	// is not shared at this point.
	if _, err := conn.Write([]byte("BEGIN\r\n")); err != nil {
		return err
	}
	return nil
}