negotiator.go
5.3 KB
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
192
// Copyright 2016 The Mellium Contributors.
// Use of this source code is governed by the BSD 2-clause license that can be
// found in the LICENSE file.
package sasl
import (
"crypto/rand"
"crypto/tls"
"strings"
)
// State represents the current state of a Negotiator.
// The first two bits represent the actual state of the state machine and the
// last 3 bits are a bitmask that define the machines behavior.
// The remaining bits should not be used.
type State uint8
// The current step of the Server or Client (represented by the first two bits
// of the state byte).
const (
Initial State = iota
AuthTextSent
ResponseSent
ValidServerResponse
// Bitmask used for extracting the step from the state byte.
StepMask = 0x3
)
const (
// RemoteCB bit is on if the remote client or server supports channel binding.
RemoteCB State = 1 << (iota + 3)
// Errored bit is on if the machine has errored.
Errored
// Receiving bit is on if the machine is a server.
Receiving
)
// NewClient creates a new SASL Negotiator that supports creating authentication
// requests using the given mechanism.
func NewClient(m Mechanism, opts ...Option) *Negotiator {
machine := &Negotiator{
mechanism: m,
nonce: nonce(noncerandlen, rand.Reader),
}
getOpts(machine, opts...)
for _, rname := range machine.remoteMechanisms {
lname := m.Name
if lname == rname && strings.HasSuffix(lname, "-PLUS") {
machine.state |= RemoteCB
return machine
}
}
return machine
}
// NewServer creates a new SASL Negotiator that supports receiving
// authentication requests using the given mechanism.
// A nil permissions function is the same as a function that always returns
// false.
func NewServer(m Mechanism, permissions func(*Negotiator) bool, opts ...Option) *Negotiator {
machine := &Negotiator{
mechanism: m,
nonce: nonce(noncerandlen, rand.Reader),
state: AuthTextSent | Receiving,
}
getOpts(machine, opts...)
if permissions != nil {
machine.permissions = permissions
}
for _, rname := range machine.remoteMechanisms {
lname := m.Name
if lname == rname && strings.HasSuffix(lname, "-PLUS") {
machine.state |= RemoteCB
return machine
}
}
return machine
}
// A Negotiator represents a SASL client or server state machine that can
// attempt to negotiate auth. Negotiators should not be used from multiple
// goroutines, and must be reset between negotiation attempts.
type Negotiator struct {
tlsState *tls.ConnectionState
remoteMechanisms []string
credentials func() (Username, Password, Identity []byte)
permissions func(*Negotiator) bool
mechanism Mechanism
state State
nonce []byte
cache interface{}
}
// Nonce returns a unique nonce that is reset for each negotiation attempt. It
// is used by SASL Mechanisms and should generally not be called directly.
func (c *Negotiator) Nonce() []byte {
return c.nonce
}
// Step attempts to transition the state machine to its next state. If Step is
// called after a previous invocation generates an error (and the state machine
// has not been reset to its initial state), Step panics.
func (c *Negotiator) Step(challenge []byte) (more bool, resp []byte, err error) {
if c.state&Errored == Errored {
panic("sasl: Step called on a SASL state machine that has errored")
}
defer func() {
if err != nil {
c.state |= Errored
}
}()
switch c.state & StepMask {
case Initial:
more, resp, c.cache, err = c.mechanism.Start(c)
c.state = c.state&^StepMask | AuthTextSent
case AuthTextSent:
more, resp, c.cache, err = c.mechanism.Next(c, challenge, c.cache)
c.state = c.state&^StepMask | ResponseSent
case ResponseSent:
more, resp, c.cache, err = c.mechanism.Next(c, challenge, c.cache)
c.state = c.state&^StepMask | ValidServerResponse
case ValidServerResponse:
more, resp, c.cache, err = c.mechanism.Next(c, challenge, c.cache)
}
if err != nil {
return false, nil, err
}
return more, resp, err
}
// State returns the internal state of the SASL state machine.
func (c *Negotiator) State() State {
return c.state
}
// Reset resets the state machine to its initial state so that it can be reused
// in another SASL exchange.
func (c *Negotiator) Reset() {
c.state = c.state & (Receiving | RemoteCB)
// Skip the start step for servers
if c.state&Receiving == Receiving {
c.state = c.state&^StepMask | AuthTextSent
}
c.nonce = nonce(noncerandlen, rand.Reader)
c.cache = nil
}
// Credentials returns a username, and password for authentication and optional
// identity for authorization.
func (c *Negotiator) Credentials() (username, password, identity []byte) {
if c.credentials != nil {
return c.credentials()
}
return
}
// Permissions is the callback used by the server to authenticate the user.
func (c *Negotiator) Permissions(opts ...Option) bool {
if c.permissions != nil {
nn := *c
getOpts(&nn, opts...)
return c.permissions(&nn)
}
return false
}
// TLSState is the state of any TLS connections being used to negotiate SASL
// (it can be used for channel binding).
func (c *Negotiator) TLSState() *tls.ConnectionState {
if c.tlsState != nil {
return c.tlsState
}
return nil
}
// RemoteMechanisms is a list of mechanisms as advertised by the other side of a
// SASL negotiation.
func (c *Negotiator) RemoteMechanisms() []string {
if c.remoteMechanisms != nil {
return c.remoteMechanisms
}
return nil
}