]>
Commit | Line | Data |
---|---|---|
bae9f6d2 JC |
1 | // Copyright 2011 The Go Authors. All rights reserved. |
2 | // Use of this source code is governed by a BSD-style | |
3 | // license that can be found in the LICENSE file. | |
4 | ||
5 | package ssh | |
6 | ||
7 | import ( | |
8 | "bytes" | |
9 | "errors" | |
10 | "fmt" | |
11 | "io" | |
12 | "net" | |
13 | "strings" | |
14 | ) | |
15 | ||
16 | // The Permissions type holds fine-grained permissions that are | |
17 | // specific to a user or a specific authentication method for a | |
18 | // user. Permissions, except for "source-address", must be enforced in | |
19 | // the server application layer, after successful authentication. The | |
20 | // Permissions are passed on in ServerConn so a server implementation | |
21 | // can honor them. | |
22 | type Permissions struct { | |
23 | // Critical options restrict default permissions. Common | |
24 | // restrictions are "source-address" and "force-command". If | |
25 | // the server cannot enforce the restriction, or does not | |
26 | // recognize it, the user should not authenticate. | |
27 | CriticalOptions map[string]string | |
28 | ||
29 | // Extensions are extra functionality that the server may | |
30 | // offer on authenticated connections. Common extensions are | |
31 | // "permit-agent-forwarding", "permit-X11-forwarding". Lack of | |
32 | // support for an extension does not preclude authenticating a | |
33 | // user. | |
34 | Extensions map[string]string | |
35 | } | |
36 | ||
37 | // ServerConfig holds server specific configuration data. | |
38 | type ServerConfig struct { | |
39 | // Config contains configuration shared between client and server. | |
40 | Config | |
41 | ||
42 | hostKeys []Signer | |
43 | ||
44 | // NoClientAuth is true if clients are allowed to connect without | |
45 | // authenticating. | |
46 | NoClientAuth bool | |
47 | ||
48 | // PasswordCallback, if non-nil, is called when a user | |
49 | // attempts to authenticate using a password. | |
50 | PasswordCallback func(conn ConnMetadata, password []byte) (*Permissions, error) | |
51 | ||
52 | // PublicKeyCallback, if non-nil, is called when a client attempts public | |
53 | // key authentication. It must return true if the given public key is | |
54 | // valid for the given user. For example, see CertChecker.Authenticate. | |
55 | PublicKeyCallback func(conn ConnMetadata, key PublicKey) (*Permissions, error) | |
56 | ||
57 | // KeyboardInteractiveCallback, if non-nil, is called when | |
58 | // keyboard-interactive authentication is selected (RFC | |
59 | // 4256). The client object's Challenge function should be | |
60 | // used to query the user. The callback may offer multiple | |
61 | // Challenge rounds. To avoid information leaks, the client | |
62 | // should be presented a challenge even if the user is | |
63 | // unknown. | |
64 | KeyboardInteractiveCallback func(conn ConnMetadata, client KeyboardInteractiveChallenge) (*Permissions, error) | |
65 | ||
66 | // AuthLogCallback, if non-nil, is called to log all authentication | |
67 | // attempts. | |
68 | AuthLogCallback func(conn ConnMetadata, method string, err error) | |
69 | ||
70 | // ServerVersion is the version identification string to announce in | |
71 | // the public handshake. | |
72 | // If empty, a reasonable default is used. | |
73 | // Note that RFC 4253 section 4.2 requires that this string start with | |
74 | // "SSH-2.0-". | |
75 | ServerVersion string | |
76 | } | |
77 | ||
78 | // AddHostKey adds a private key as a host key. If an existing host | |
79 | // key exists with the same algorithm, it is overwritten. Each server | |
80 | // config must have at least one host key. | |
81 | func (s *ServerConfig) AddHostKey(key Signer) { | |
82 | for i, k := range s.hostKeys { | |
83 | if k.PublicKey().Type() == key.PublicKey().Type() { | |
84 | s.hostKeys[i] = key | |
85 | return | |
86 | } | |
87 | } | |
88 | ||
89 | s.hostKeys = append(s.hostKeys, key) | |
90 | } | |
91 | ||
92 | // cachedPubKey contains the results of querying whether a public key is | |
93 | // acceptable for a user. | |
94 | type cachedPubKey struct { | |
95 | user string | |
96 | pubKeyData []byte | |
97 | result error | |
98 | perms *Permissions | |
99 | } | |
100 | ||
101 | const maxCachedPubKeys = 16 | |
102 | ||
103 | // pubKeyCache caches tests for public keys. Since SSH clients | |
104 | // will query whether a public key is acceptable before attempting to | |
105 | // authenticate with it, we end up with duplicate queries for public | |
106 | // key validity. The cache only applies to a single ServerConn. | |
107 | type pubKeyCache struct { | |
108 | keys []cachedPubKey | |
109 | } | |
110 | ||
111 | // get returns the result for a given user/algo/key tuple. | |
112 | func (c *pubKeyCache) get(user string, pubKeyData []byte) (cachedPubKey, bool) { | |
113 | for _, k := range c.keys { | |
114 | if k.user == user && bytes.Equal(k.pubKeyData, pubKeyData) { | |
115 | return k, true | |
116 | } | |
117 | } | |
118 | return cachedPubKey{}, false | |
119 | } | |
120 | ||
121 | // add adds the given tuple to the cache. | |
122 | func (c *pubKeyCache) add(candidate cachedPubKey) { | |
123 | if len(c.keys) < maxCachedPubKeys { | |
124 | c.keys = append(c.keys, candidate) | |
125 | } | |
126 | } | |
127 | ||
128 | // ServerConn is an authenticated SSH connection, as seen from the | |
129 | // server | |
130 | type ServerConn struct { | |
131 | Conn | |
132 | ||
133 | // If the succeeding authentication callback returned a | |
134 | // non-nil Permissions pointer, it is stored here. | |
135 | Permissions *Permissions | |
136 | } | |
137 | ||
138 | // NewServerConn starts a new SSH server with c as the underlying | |
139 | // transport. It starts with a handshake and, if the handshake is | |
140 | // unsuccessful, it closes the connection and returns an error. The | |
141 | // Request and NewChannel channels must be serviced, or the connection | |
142 | // will hang. | |
143 | func NewServerConn(c net.Conn, config *ServerConfig) (*ServerConn, <-chan NewChannel, <-chan *Request, error) { | |
144 | fullConf := *config | |
145 | fullConf.SetDefaults() | |
146 | s := &connection{ | |
147 | sshConn: sshConn{conn: c}, | |
148 | } | |
149 | perms, err := s.serverHandshake(&fullConf) | |
150 | if err != nil { | |
151 | c.Close() | |
152 | return nil, nil, nil, err | |
153 | } | |
154 | return &ServerConn{s, perms}, s.mux.incomingChannels, s.mux.incomingRequests, nil | |
155 | } | |
156 | ||
157 | // signAndMarshal signs the data with the appropriate algorithm, | |
158 | // and serializes the result in SSH wire format. | |
159 | func signAndMarshal(k Signer, rand io.Reader, data []byte) ([]byte, error) { | |
160 | sig, err := k.Sign(rand, data) | |
161 | if err != nil { | |
162 | return nil, err | |
163 | } | |
164 | ||
165 | return Marshal(sig), nil | |
166 | } | |
167 | ||
168 | // handshake performs key exchange and user authentication. | |
169 | func (s *connection) serverHandshake(config *ServerConfig) (*Permissions, error) { | |
170 | if len(config.hostKeys) == 0 { | |
171 | return nil, errors.New("ssh: server has no host keys") | |
172 | } | |
173 | ||
174 | if !config.NoClientAuth && config.PasswordCallback == nil && config.PublicKeyCallback == nil && config.KeyboardInteractiveCallback == nil { | |
175 | return nil, errors.New("ssh: no authentication methods configured but NoClientAuth is also false") | |
176 | } | |
177 | ||
178 | if config.ServerVersion != "" { | |
179 | s.serverVersion = []byte(config.ServerVersion) | |
180 | } else { | |
181 | s.serverVersion = []byte(packageVersion) | |
182 | } | |
183 | var err error | |
184 | s.clientVersion, err = exchangeVersions(s.sshConn.conn, s.serverVersion) | |
185 | if err != nil { | |
186 | return nil, err | |
187 | } | |
188 | ||
189 | tr := newTransport(s.sshConn.conn, config.Rand, false /* not client */) | |
190 | s.transport = newServerTransport(tr, s.clientVersion, s.serverVersion, config) | |
191 | ||
192 | if err := s.transport.waitSession(); err != nil { | |
193 | return nil, err | |
194 | } | |
195 | ||
196 | // We just did the key change, so the session ID is established. | |
197 | s.sessionID = s.transport.getSessionID() | |
198 | ||
199 | var packet []byte | |
200 | if packet, err = s.transport.readPacket(); err != nil { | |
201 | return nil, err | |
202 | } | |
203 | ||
204 | var serviceRequest serviceRequestMsg | |
205 | if err = Unmarshal(packet, &serviceRequest); err != nil { | |
206 | return nil, err | |
207 | } | |
208 | if serviceRequest.Service != serviceUserAuth { | |
209 | return nil, errors.New("ssh: requested service '" + serviceRequest.Service + "' before authenticating") | |
210 | } | |
211 | serviceAccept := serviceAcceptMsg{ | |
212 | Service: serviceUserAuth, | |
213 | } | |
214 | if err := s.transport.writePacket(Marshal(&serviceAccept)); err != nil { | |
215 | return nil, err | |
216 | } | |
217 | ||
218 | perms, err := s.serverAuthenticate(config) | |
219 | if err != nil { | |
220 | return nil, err | |
221 | } | |
222 | s.mux = newMux(s.transport) | |
223 | return perms, err | |
224 | } | |
225 | ||
226 | func isAcceptableAlgo(algo string) bool { | |
227 | switch algo { | |
228 | case KeyAlgoRSA, KeyAlgoDSA, KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521, KeyAlgoED25519, | |
229 | CertAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01, CertAlgoECDSA384v01, CertAlgoECDSA521v01: | |
230 | return true | |
231 | } | |
232 | return false | |
233 | } | |
234 | ||
235 | func checkSourceAddress(addr net.Addr, sourceAddrs string) error { | |
236 | if addr == nil { | |
237 | return errors.New("ssh: no address known for client, but source-address match required") | |
238 | } | |
239 | ||
240 | tcpAddr, ok := addr.(*net.TCPAddr) | |
241 | if !ok { | |
242 | return fmt.Errorf("ssh: remote address %v is not an TCP address when checking source-address match", addr) | |
243 | } | |
244 | ||
245 | for _, sourceAddr := range strings.Split(sourceAddrs, ",") { | |
246 | if allowedIP := net.ParseIP(sourceAddr); allowedIP != nil { | |
247 | if allowedIP.Equal(tcpAddr.IP) { | |
248 | return nil | |
249 | } | |
250 | } else { | |
251 | _, ipNet, err := net.ParseCIDR(sourceAddr) | |
252 | if err != nil { | |
253 | return fmt.Errorf("ssh: error parsing source-address restriction %q: %v", sourceAddr, err) | |
254 | } | |
255 | ||
256 | if ipNet.Contains(tcpAddr.IP) { | |
257 | return nil | |
258 | } | |
259 | } | |
260 | } | |
261 | ||
262 | return fmt.Errorf("ssh: remote address %v is not allowed because of source-address restriction", addr) | |
263 | } | |
264 | ||
265 | func (s *connection) serverAuthenticate(config *ServerConfig) (*Permissions, error) { | |
266 | sessionID := s.transport.getSessionID() | |
267 | var cache pubKeyCache | |
268 | var perms *Permissions | |
269 | ||
270 | userAuthLoop: | |
271 | for { | |
272 | var userAuthReq userAuthRequestMsg | |
273 | if packet, err := s.transport.readPacket(); err != nil { | |
274 | return nil, err | |
275 | } else if err = Unmarshal(packet, &userAuthReq); err != nil { | |
276 | return nil, err | |
277 | } | |
278 | ||
279 | if userAuthReq.Service != serviceSSH { | |
280 | return nil, errors.New("ssh: client attempted to negotiate for unknown service: " + userAuthReq.Service) | |
281 | } | |
282 | ||
283 | s.user = userAuthReq.User | |
284 | perms = nil | |
285 | authErr := errors.New("no auth passed yet") | |
286 | ||
287 | switch userAuthReq.Method { | |
288 | case "none": | |
289 | if config.NoClientAuth { | |
290 | authErr = nil | |
291 | } | |
292 | case "password": | |
293 | if config.PasswordCallback == nil { | |
294 | authErr = errors.New("ssh: password auth not configured") | |
295 | break | |
296 | } | |
297 | payload := userAuthReq.Payload | |
298 | if len(payload) < 1 || payload[0] != 0 { | |
299 | return nil, parseError(msgUserAuthRequest) | |
300 | } | |
301 | payload = payload[1:] | |
302 | password, payload, ok := parseString(payload) | |
303 | if !ok || len(payload) > 0 { | |
304 | return nil, parseError(msgUserAuthRequest) | |
305 | } | |
306 | ||
307 | perms, authErr = config.PasswordCallback(s, password) | |
308 | case "keyboard-interactive": | |
309 | if config.KeyboardInteractiveCallback == nil { | |
310 | authErr = errors.New("ssh: keyboard-interactive auth not configubred") | |
311 | break | |
312 | } | |
313 | ||
314 | prompter := &sshClientKeyboardInteractive{s} | |
315 | perms, authErr = config.KeyboardInteractiveCallback(s, prompter.Challenge) | |
316 | case "publickey": | |
317 | if config.PublicKeyCallback == nil { | |
318 | authErr = errors.New("ssh: publickey auth not configured") | |
319 | break | |
320 | } | |
321 | payload := userAuthReq.Payload | |
322 | if len(payload) < 1 { | |
323 | return nil, parseError(msgUserAuthRequest) | |
324 | } | |
325 | isQuery := payload[0] == 0 | |
326 | payload = payload[1:] | |
327 | algoBytes, payload, ok := parseString(payload) | |
328 | if !ok { | |
329 | return nil, parseError(msgUserAuthRequest) | |
330 | } | |
331 | algo := string(algoBytes) | |
332 | if !isAcceptableAlgo(algo) { | |
333 | authErr = fmt.Errorf("ssh: algorithm %q not accepted", algo) | |
334 | break | |
335 | } | |
336 | ||
337 | pubKeyData, payload, ok := parseString(payload) | |
338 | if !ok { | |
339 | return nil, parseError(msgUserAuthRequest) | |
340 | } | |
341 | ||
342 | pubKey, err := ParsePublicKey(pubKeyData) | |
343 | if err != nil { | |
344 | return nil, err | |
345 | } | |
346 | ||
347 | candidate, ok := cache.get(s.user, pubKeyData) | |
348 | if !ok { | |
349 | candidate.user = s.user | |
350 | candidate.pubKeyData = pubKeyData | |
351 | candidate.perms, candidate.result = config.PublicKeyCallback(s, pubKey) | |
352 | if candidate.result == nil && candidate.perms != nil && candidate.perms.CriticalOptions != nil && candidate.perms.CriticalOptions[sourceAddressCriticalOption] != "" { | |
353 | candidate.result = checkSourceAddress( | |
354 | s.RemoteAddr(), | |
355 | candidate.perms.CriticalOptions[sourceAddressCriticalOption]) | |
356 | } | |
357 | cache.add(candidate) | |
358 | } | |
359 | ||
360 | if isQuery { | |
361 | // The client can query if the given public key | |
362 | // would be okay. | |
363 | if len(payload) > 0 { | |
364 | return nil, parseError(msgUserAuthRequest) | |
365 | } | |
366 | ||
367 | if candidate.result == nil { | |
368 | okMsg := userAuthPubKeyOkMsg{ | |
369 | Algo: algo, | |
370 | PubKey: pubKeyData, | |
371 | } | |
372 | if err = s.transport.writePacket(Marshal(&okMsg)); err != nil { | |
373 | return nil, err | |
374 | } | |
375 | continue userAuthLoop | |
376 | } | |
377 | authErr = candidate.result | |
378 | } else { | |
379 | sig, payload, ok := parseSignature(payload) | |
380 | if !ok || len(payload) > 0 { | |
381 | return nil, parseError(msgUserAuthRequest) | |
382 | } | |
383 | // Ensure the public key algo and signature algo | |
384 | // are supported. Compare the private key | |
385 | // algorithm name that corresponds to algo with | |
386 | // sig.Format. This is usually the same, but | |
387 | // for certs, the names differ. | |
388 | if !isAcceptableAlgo(sig.Format) { | |
389 | break | |
390 | } | |
391 | signedData := buildDataSignedForAuth(sessionID, userAuthReq, algoBytes, pubKeyData) | |
392 | ||
393 | if err := pubKey.Verify(signedData, sig); err != nil { | |
394 | return nil, err | |
395 | } | |
396 | ||
397 | authErr = candidate.result | |
398 | perms = candidate.perms | |
399 | } | |
400 | default: | |
401 | authErr = fmt.Errorf("ssh: unknown method %q", userAuthReq.Method) | |
402 | } | |
403 | ||
404 | if config.AuthLogCallback != nil { | |
405 | config.AuthLogCallback(s, userAuthReq.Method, authErr) | |
406 | } | |
407 | ||
408 | if authErr == nil { | |
409 | break userAuthLoop | |
410 | } | |
411 | ||
412 | var failureMsg userAuthFailureMsg | |
413 | if config.PasswordCallback != nil { | |
414 | failureMsg.Methods = append(failureMsg.Methods, "password") | |
415 | } | |
416 | if config.PublicKeyCallback != nil { | |
417 | failureMsg.Methods = append(failureMsg.Methods, "publickey") | |
418 | } | |
419 | if config.KeyboardInteractiveCallback != nil { | |
420 | failureMsg.Methods = append(failureMsg.Methods, "keyboard-interactive") | |
421 | } | |
422 | ||
423 | if len(failureMsg.Methods) == 0 { | |
424 | return nil, errors.New("ssh: no authentication methods configured but NoClientAuth is also false") | |
425 | } | |
426 | ||
427 | if err := s.transport.writePacket(Marshal(&failureMsg)); err != nil { | |
428 | return nil, err | |
429 | } | |
430 | } | |
431 | ||
432 | if err := s.transport.writePacket([]byte{msgUserAuthSuccess}); err != nil { | |
433 | return nil, err | |
434 | } | |
435 | return perms, nil | |
436 | } | |
437 | ||
438 | // sshClientKeyboardInteractive implements a ClientKeyboardInteractive by | |
439 | // asking the client on the other side of a ServerConn. | |
440 | type sshClientKeyboardInteractive struct { | |
441 | *connection | |
442 | } | |
443 | ||
444 | func (c *sshClientKeyboardInteractive) Challenge(user, instruction string, questions []string, echos []bool) (answers []string, err error) { | |
445 | if len(questions) != len(echos) { | |
446 | return nil, errors.New("ssh: echos and questions must have equal length") | |
447 | } | |
448 | ||
449 | var prompts []byte | |
450 | for i := range questions { | |
451 | prompts = appendString(prompts, questions[i]) | |
452 | prompts = appendBool(prompts, echos[i]) | |
453 | } | |
454 | ||
455 | if err := c.transport.writePacket(Marshal(&userAuthInfoRequestMsg{ | |
456 | Instruction: instruction, | |
457 | NumPrompts: uint32(len(questions)), | |
458 | Prompts: prompts, | |
459 | })); err != nil { | |
460 | return nil, err | |
461 | } | |
462 | ||
463 | packet, err := c.transport.readPacket() | |
464 | if err != nil { | |
465 | return nil, err | |
466 | } | |
467 | if packet[0] != msgUserAuthInfoResponse { | |
468 | return nil, unexpectedMessageError(msgUserAuthInfoResponse, packet[0]) | |
469 | } | |
470 | packet = packet[1:] | |
471 | ||
472 | n, packet, ok := parseUint32(packet) | |
473 | if !ok || int(n) != len(questions) { | |
474 | return nil, parseError(msgUserAuthInfoResponse) | |
475 | } | |
476 | ||
477 | for i := uint32(0); i < n; i++ { | |
478 | ans, rest, ok := parseString(packet) | |
479 | if !ok { | |
480 | return nil, parseError(msgUserAuthInfoResponse) | |
481 | } | |
482 | ||
483 | answers = append(answers, string(ans)) | |
484 | packet = rest | |
485 | } | |
486 | if len(packet) != 0 { | |
487 | return nil, errors.New("ssh: junk at end of message") | |
488 | } | |
489 | ||
490 | return answers, nil | |
491 | } |