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.
16 // The Permissions type holds fine-grained permissions that are
17 // specific to a user or a specific authentication method for a user.
18 // The Permissions value for a successful authentication attempt is
19 // available in ServerConn, so it can be used to pass information from
20 // the user-authentication phase to the application layer.
21 type Permissions struct {
22 // CriticalOptions indicate restrictions to the default
23 // permissions, and are typically used in conjunction with
24 // user certificates. The standard for SSH certificates
25 // defines "force-command" (only allow the given command to
26 // execute) and "source-address" (only allow connections from
27 // the given address). The SSH package currently only enforces
28 // the "source-address" critical option. It is up to server
29 // implementations to enforce other critical options, such as
30 // "force-command", by checking them after the SSH handshake
31 // is successful. In general, SSH servers should reject
32 // connections that specify critical options that are unknown
34 CriticalOptions map[string]string
36 // Extensions are extra functionality that the server may
37 // offer on authenticated connections. Lack of support for an
38 // extension does not preclude authenticating a user. Common
39 // extensions are "permit-agent-forwarding",
40 // "permit-X11-forwarding". The Go SSH library currently does
41 // not act on any extension, and it is up to server
42 // implementations to honor them. Extensions can be used to
43 // pass data from the authentication callbacks to the server
45 Extensions map[string]string
48 // ServerConfig holds server specific configuration data.
49 type ServerConfig struct {
50 // Config contains configuration shared between client and server.
55 // NoClientAuth is true if clients are allowed to connect without
59 // MaxAuthTries specifies the maximum number of authentication attempts
60 // permitted per connection. If set to a negative number, the number of
61 // attempts are unlimited. If set to zero, the number of attempts are limited
65 // PasswordCallback, if non-nil, is called when a user
66 // attempts to authenticate using a password.
67 PasswordCallback func(conn ConnMetadata, password []byte) (*Permissions, error)
69 // PublicKeyCallback, if non-nil, is called when a client
70 // offers a public key for authentication. It must return a nil error
71 // if the given public key can be used to authenticate the
72 // given user. For example, see CertChecker.Authenticate. A
73 // call to this function does not guarantee that the key
74 // offered is in fact used to authenticate. To record any data
75 // depending on the public key, store it inside a
76 // Permissions.Extensions entry.
77 PublicKeyCallback func(conn ConnMetadata, key PublicKey) (*Permissions, error)
79 // KeyboardInteractiveCallback, if non-nil, is called when
80 // keyboard-interactive authentication is selected (RFC
81 // 4256). The client object's Challenge function should be
82 // used to query the user. The callback may offer multiple
83 // Challenge rounds. To avoid information leaks, the client
84 // should be presented a challenge even if the user is
86 KeyboardInteractiveCallback func(conn ConnMetadata, client KeyboardInteractiveChallenge) (*Permissions, error)
88 // AuthLogCallback, if non-nil, is called to log all authentication
90 AuthLogCallback func(conn ConnMetadata, method string, err error)
92 // ServerVersion is the version identification string to announce in
93 // the public handshake.
94 // If empty, a reasonable default is used.
95 // Note that RFC 4253 section 4.2 requires that this string start with
99 // BannerCallback, if present, is called and the return string is sent to
100 // the client after key exchange completed but before authentication.
101 BannerCallback func(conn ConnMetadata) string
104 // AddHostKey adds a private key as a host key. If an existing host
105 // key exists with the same algorithm, it is overwritten. Each server
106 // config must have at least one host key.
107 func (s *ServerConfig) AddHostKey(key Signer) {
108 for i, k := range s.hostKeys {
109 if k.PublicKey().Type() == key.PublicKey().Type() {
115 s.hostKeys = append(s.hostKeys, key)
118 // cachedPubKey contains the results of querying whether a public key is
119 // acceptable for a user.
120 type cachedPubKey struct {
127 const maxCachedPubKeys = 16
129 // pubKeyCache caches tests for public keys. Since SSH clients
130 // will query whether a public key is acceptable before attempting to
131 // authenticate with it, we end up with duplicate queries for public
132 // key validity. The cache only applies to a single ServerConn.
133 type pubKeyCache struct {
137 // get returns the result for a given user/algo/key tuple.
138 func (c *pubKeyCache) get(user string, pubKeyData []byte) (cachedPubKey, bool) {
139 for _, k := range c.keys {
140 if k.user == user && bytes.Equal(k.pubKeyData, pubKeyData) {
144 return cachedPubKey{}, false
147 // add adds the given tuple to the cache.
148 func (c *pubKeyCache) add(candidate cachedPubKey) {
149 if len(c.keys) < maxCachedPubKeys {
150 c.keys = append(c.keys, candidate)
154 // ServerConn is an authenticated SSH connection, as seen from the
156 type ServerConn struct {
159 // If the succeeding authentication callback returned a
160 // non-nil Permissions pointer, it is stored here.
161 Permissions *Permissions
164 // NewServerConn starts a new SSH server with c as the underlying
165 // transport. It starts with a handshake and, if the handshake is
166 // unsuccessful, it closes the connection and returns an error. The
167 // Request and NewChannel channels must be serviced, or the connection
170 // The returned error may be of type *ServerAuthError for
171 // authentication errors.
172 func NewServerConn(c net.Conn, config *ServerConfig) (*ServerConn, <-chan NewChannel, <-chan *Request, error) {
174 fullConf.SetDefaults()
175 if fullConf.MaxAuthTries == 0 {
176 fullConf.MaxAuthTries = 6
180 sshConn: sshConn{conn: c},
182 perms, err := s.serverHandshake(&fullConf)
185 return nil, nil, nil, err
187 return &ServerConn{s, perms}, s.mux.incomingChannels, s.mux.incomingRequests, nil
190 // signAndMarshal signs the data with the appropriate algorithm,
191 // and serializes the result in SSH wire format.
192 func signAndMarshal(k Signer, rand io.Reader, data []byte) ([]byte, error) {
193 sig, err := k.Sign(rand, data)
198 return Marshal(sig), nil
201 // handshake performs key exchange and user authentication.
202 func (s *connection) serverHandshake(config *ServerConfig) (*Permissions, error) {
203 if len(config.hostKeys) == 0 {
204 return nil, errors.New("ssh: server has no host keys")
207 if !config.NoClientAuth && config.PasswordCallback == nil && config.PublicKeyCallback == nil && config.KeyboardInteractiveCallback == nil {
208 return nil, errors.New("ssh: no authentication methods configured but NoClientAuth is also false")
211 if config.ServerVersion != "" {
212 s.serverVersion = []byte(config.ServerVersion)
214 s.serverVersion = []byte(packageVersion)
217 s.clientVersion, err = exchangeVersions(s.sshConn.conn, s.serverVersion)
222 tr := newTransport(s.sshConn.conn, config.Rand, false /* not client */)
223 s.transport = newServerTransport(tr, s.clientVersion, s.serverVersion, config)
225 if err := s.transport.waitSession(); err != nil {
229 // We just did the key change, so the session ID is established.
230 s.sessionID = s.transport.getSessionID()
233 if packet, err = s.transport.readPacket(); err != nil {
237 var serviceRequest serviceRequestMsg
238 if err = Unmarshal(packet, &serviceRequest); err != nil {
241 if serviceRequest.Service != serviceUserAuth {
242 return nil, errors.New("ssh: requested service '" + serviceRequest.Service + "' before authenticating")
244 serviceAccept := serviceAcceptMsg{
245 Service: serviceUserAuth,
247 if err := s.transport.writePacket(Marshal(&serviceAccept)); err != nil {
251 perms, err := s.serverAuthenticate(config)
255 s.mux = newMux(s.transport)
259 func isAcceptableAlgo(algo string) bool {
261 case KeyAlgoRSA, KeyAlgoDSA, KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521, KeyAlgoED25519,
262 CertAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01, CertAlgoECDSA384v01, CertAlgoECDSA521v01, CertAlgoED25519v01:
268 func checkSourceAddress(addr net.Addr, sourceAddrs string) error {
270 return errors.New("ssh: no address known for client, but source-address match required")
273 tcpAddr, ok := addr.(*net.TCPAddr)
275 return fmt.Errorf("ssh: remote address %v is not an TCP address when checking source-address match", addr)
278 for _, sourceAddr := range strings.Split(sourceAddrs, ",") {
279 if allowedIP := net.ParseIP(sourceAddr); allowedIP != nil {
280 if allowedIP.Equal(tcpAddr.IP) {
284 _, ipNet, err := net.ParseCIDR(sourceAddr)
286 return fmt.Errorf("ssh: error parsing source-address restriction %q: %v", sourceAddr, err)
289 if ipNet.Contains(tcpAddr.IP) {
295 return fmt.Errorf("ssh: remote address %v is not allowed because of source-address restriction", addr)
298 // ServerAuthError represents server authentication errors and is
299 // sometimes returned by NewServerConn. It appends any authentication
300 // errors that may occur, and is returned if all of the authentication
301 // methods provided by the user failed to authenticate.
302 type ServerAuthError struct {
303 // Errors contains authentication errors returned by the authentication
304 // callback methods. The first entry is typically ErrNoAuth.
308 func (l ServerAuthError) Error() string {
310 for _, err := range l.Errors {
311 errs = append(errs, err.Error())
313 return "[" + strings.Join(errs, ", ") + "]"
316 // ErrNoAuth is the error value returned if no
317 // authentication method has been passed yet. This happens as a normal
318 // part of the authentication loop, since the client first tries
319 // 'none' authentication to discover available methods.
320 // It is returned in ServerAuthError.Errors from NewServerConn.
321 var ErrNoAuth = errors.New("ssh: no auth passed yet")
323 func (s *connection) serverAuthenticate(config *ServerConfig) (*Permissions, error) {
324 sessionID := s.transport.getSessionID()
325 var cache pubKeyCache
326 var perms *Permissions
330 var displayedBanner bool
334 if authFailures >= config.MaxAuthTries && config.MaxAuthTries > 0 {
335 discMsg := &disconnectMsg{
337 Message: "too many authentication failures",
340 if err := s.transport.writePacket(Marshal(discMsg)); err != nil {
347 var userAuthReq userAuthRequestMsg
348 if packet, err := s.transport.readPacket(); err != nil {
350 return nil, &ServerAuthError{Errors: authErrs}
353 } else if err = Unmarshal(packet, &userAuthReq); err != nil {
357 if userAuthReq.Service != serviceSSH {
358 return nil, errors.New("ssh: client attempted to negotiate for unknown service: " + userAuthReq.Service)
361 s.user = userAuthReq.User
363 if !displayedBanner && config.BannerCallback != nil {
364 displayedBanner = true
365 msg := config.BannerCallback(s)
367 bannerMsg := &userAuthBannerMsg{
370 if err := s.transport.writePacket(Marshal(bannerMsg)); err != nil {
379 switch userAuthReq.Method {
381 if config.NoClientAuth {
385 // allow initial attempt of 'none' without penalty
386 if authFailures == 0 {
390 if config.PasswordCallback == nil {
391 authErr = errors.New("ssh: password auth not configured")
394 payload := userAuthReq.Payload
395 if len(payload) < 1 || payload[0] != 0 {
396 return nil, parseError(msgUserAuthRequest)
398 payload = payload[1:]
399 password, payload, ok := parseString(payload)
400 if !ok || len(payload) > 0 {
401 return nil, parseError(msgUserAuthRequest)
404 perms, authErr = config.PasswordCallback(s, password)
405 case "keyboard-interactive":
406 if config.KeyboardInteractiveCallback == nil {
407 authErr = errors.New("ssh: keyboard-interactive auth not configured")
411 prompter := &sshClientKeyboardInteractive{s}
412 perms, authErr = config.KeyboardInteractiveCallback(s, prompter.Challenge)
414 if config.PublicKeyCallback == nil {
415 authErr = errors.New("ssh: publickey auth not configured")
418 payload := userAuthReq.Payload
419 if len(payload) < 1 {
420 return nil, parseError(msgUserAuthRequest)
422 isQuery := payload[0] == 0
423 payload = payload[1:]
424 algoBytes, payload, ok := parseString(payload)
426 return nil, parseError(msgUserAuthRequest)
428 algo := string(algoBytes)
429 if !isAcceptableAlgo(algo) {
430 authErr = fmt.Errorf("ssh: algorithm %q not accepted", algo)
434 pubKeyData, payload, ok := parseString(payload)
436 return nil, parseError(msgUserAuthRequest)
439 pubKey, err := ParsePublicKey(pubKeyData)
444 candidate, ok := cache.get(s.user, pubKeyData)
446 candidate.user = s.user
447 candidate.pubKeyData = pubKeyData
448 candidate.perms, candidate.result = config.PublicKeyCallback(s, pubKey)
449 if candidate.result == nil && candidate.perms != nil && candidate.perms.CriticalOptions != nil && candidate.perms.CriticalOptions[sourceAddressCriticalOption] != "" {
450 candidate.result = checkSourceAddress(
452 candidate.perms.CriticalOptions[sourceAddressCriticalOption])
458 // The client can query if the given public key
461 if len(payload) > 0 {
462 return nil, parseError(msgUserAuthRequest)
465 if candidate.result == nil {
466 okMsg := userAuthPubKeyOkMsg{
470 if err = s.transport.writePacket(Marshal(&okMsg)); err != nil {
473 continue userAuthLoop
475 authErr = candidate.result
477 sig, payload, ok := parseSignature(payload)
478 if !ok || len(payload) > 0 {
479 return nil, parseError(msgUserAuthRequest)
481 // Ensure the public key algo and signature algo
482 // are supported. Compare the private key
483 // algorithm name that corresponds to algo with
484 // sig.Format. This is usually the same, but
485 // for certs, the names differ.
486 if !isAcceptableAlgo(sig.Format) {
487 authErr = fmt.Errorf("ssh: algorithm %q not accepted", sig.Format)
490 signedData := buildDataSignedForAuth(sessionID, userAuthReq, algoBytes, pubKeyData)
492 if err := pubKey.Verify(signedData, sig); err != nil {
496 authErr = candidate.result
497 perms = candidate.perms
500 authErr = fmt.Errorf("ssh: unknown method %q", userAuthReq.Method)
503 authErrs = append(authErrs, authErr)
505 if config.AuthLogCallback != nil {
506 config.AuthLogCallback(s, userAuthReq.Method, authErr)
515 var failureMsg userAuthFailureMsg
516 if config.PasswordCallback != nil {
517 failureMsg.Methods = append(failureMsg.Methods, "password")
519 if config.PublicKeyCallback != nil {
520 failureMsg.Methods = append(failureMsg.Methods, "publickey")
522 if config.KeyboardInteractiveCallback != nil {
523 failureMsg.Methods = append(failureMsg.Methods, "keyboard-interactive")
526 if len(failureMsg.Methods) == 0 {
527 return nil, errors.New("ssh: no authentication methods configured but NoClientAuth is also false")
530 if err := s.transport.writePacket(Marshal(&failureMsg)); err != nil {
535 if err := s.transport.writePacket([]byte{msgUserAuthSuccess}); err != nil {
541 // sshClientKeyboardInteractive implements a ClientKeyboardInteractive by
542 // asking the client on the other side of a ServerConn.
543 type sshClientKeyboardInteractive struct {
547 func (c *sshClientKeyboardInteractive) Challenge(user, instruction string, questions []string, echos []bool) (answers []string, err error) {
548 if len(questions) != len(echos) {
549 return nil, errors.New("ssh: echos and questions must have equal length")
553 for i := range questions {
554 prompts = appendString(prompts, questions[i])
555 prompts = appendBool(prompts, echos[i])
558 if err := c.transport.writePacket(Marshal(&userAuthInfoRequestMsg{
559 Instruction: instruction,
560 NumPrompts: uint32(len(questions)),
566 packet, err := c.transport.readPacket()
570 if packet[0] != msgUserAuthInfoResponse {
571 return nil, unexpectedMessageError(msgUserAuthInfoResponse, packet[0])
575 n, packet, ok := parseUint32(packet)
576 if !ok || int(n) != len(questions) {
577 return nil, parseError(msgUserAuthInfoResponse)
580 for i := uint32(0); i < n; i++ {
581 ans, rest, ok := parseString(packet)
583 return nil, parseError(msgUserAuthInfoResponse)
586 answers = append(answers, string(ans))
589 if len(packet) != 0 {
590 return nil, errors.New("ssh: junk at end of message")