github.com/Psiphon-Labs/psiphon-tunnel-core@v2.0.28+incompatible/psiphon/common/crypto/ssh/server.go (about) 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 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 33 // or not supported. 34 CriticalOptions map[string]string 35 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 44 // application layer. 45 Extensions map[string]string 46 } 47 48 type GSSAPIWithMICConfig struct { 49 // AllowLogin, must be set, is called when gssapi-with-mic 50 // authentication is selected (RFC 4462 section 3). The srcName is from the 51 // results of the GSS-API authentication. The format is username@DOMAIN. 52 // GSSAPI just guarantees to the server who the user is, but not if they can log in, and with what permissions. 53 // This callback is called after the user identity is established with GSSAPI to decide if the user can login with 54 // which permissions. If the user is allowed to login, it should return a nil error. 55 AllowLogin func(conn ConnMetadata, srcName string) (*Permissions, error) 56 57 // Server must be set. It's the implementation 58 // of the GSSAPIServer interface. See GSSAPIServer interface for details. 59 Server GSSAPIServer 60 } 61 62 // ServerConfig holds server specific configuration data. 63 type ServerConfig struct { 64 // Config contains configuration shared between client and server. 65 Config 66 67 hostKeys []Signer 68 69 // NoClientAuth is true if clients are allowed to connect without 70 // authenticating. 71 NoClientAuth bool 72 73 // MaxAuthTries specifies the maximum number of authentication attempts 74 // permitted per connection. If set to a negative number, the number of 75 // attempts are unlimited. If set to zero, the number of attempts are limited 76 // to 6. 77 MaxAuthTries int 78 79 // PasswordCallback, if non-nil, is called when a user 80 // attempts to authenticate using a password. 81 PasswordCallback func(conn ConnMetadata, password []byte) (*Permissions, error) 82 83 // PublicKeyCallback, if non-nil, is called when a client 84 // offers a public key for authentication. It must return a nil error 85 // if the given public key can be used to authenticate the 86 // given user. For example, see CertChecker.Authenticate. A 87 // call to this function does not guarantee that the key 88 // offered is in fact used to authenticate. To record any data 89 // depending on the public key, store it inside a 90 // Permissions.Extensions entry. 91 PublicKeyCallback func(conn ConnMetadata, key PublicKey) (*Permissions, error) 92 93 // KeyboardInteractiveCallback, if non-nil, is called when 94 // keyboard-interactive authentication is selected (RFC 95 // 4256). The client object's Challenge function should be 96 // used to query the user. The callback may offer multiple 97 // Challenge rounds. To avoid information leaks, the client 98 // should be presented a challenge even if the user is 99 // unknown. 100 KeyboardInteractiveCallback func(conn ConnMetadata, client KeyboardInteractiveChallenge) (*Permissions, error) 101 102 // AuthLogCallback, if non-nil, is called to log all authentication 103 // attempts. 104 AuthLogCallback func(conn ConnMetadata, method string, err error) 105 106 // ServerVersion is the version identification string to announce in 107 // the public handshake. 108 // If empty, a reasonable default is used. 109 // Note that RFC 4253 section 4.2 requires that this string start with 110 // "SSH-2.0-". 111 ServerVersion string 112 113 // BannerCallback, if present, is called and the return string is sent to 114 // the client after key exchange completed but before authentication. 115 BannerCallback func(conn ConnMetadata) string 116 117 // GSSAPIWithMICConfig includes gssapi server and callback, which if both non-nil, is used 118 // when gssapi-with-mic authentication is selected (RFC 4462 section 3). 119 GSSAPIWithMICConfig *GSSAPIWithMICConfig 120 } 121 122 // AddHostKey adds a private key as a host key. If an existing host 123 // key exists with the same algorithm, it is overwritten. Each server 124 // config must have at least one host key. 125 func (s *ServerConfig) AddHostKey(key Signer) { 126 for i, k := range s.hostKeys { 127 if k.PublicKey().Type() == key.PublicKey().Type() { 128 s.hostKeys[i] = key 129 return 130 } 131 } 132 133 s.hostKeys = append(s.hostKeys, key) 134 } 135 136 // cachedPubKey contains the results of querying whether a public key is 137 // acceptable for a user. 138 type cachedPubKey struct { 139 user string 140 pubKeyData []byte 141 result error 142 perms *Permissions 143 } 144 145 const maxCachedPubKeys = 16 146 147 // pubKeyCache caches tests for public keys. Since SSH clients 148 // will query whether a public key is acceptable before attempting to 149 // authenticate with it, we end up with duplicate queries for public 150 // key validity. The cache only applies to a single ServerConn. 151 type pubKeyCache struct { 152 keys []cachedPubKey 153 } 154 155 // get returns the result for a given user/algo/key tuple. 156 func (c *pubKeyCache) get(user string, pubKeyData []byte) (cachedPubKey, bool) { 157 for _, k := range c.keys { 158 if k.user == user && bytes.Equal(k.pubKeyData, pubKeyData) { 159 return k, true 160 } 161 } 162 return cachedPubKey{}, false 163 } 164 165 // add adds the given tuple to the cache. 166 func (c *pubKeyCache) add(candidate cachedPubKey) { 167 if len(c.keys) < maxCachedPubKeys { 168 c.keys = append(c.keys, candidate) 169 } 170 } 171 172 // ServerConn is an authenticated SSH connection, as seen from the 173 // server 174 type ServerConn struct { 175 Conn 176 177 // If the succeeding authentication callback returned a 178 // non-nil Permissions pointer, it is stored here. 179 Permissions *Permissions 180 } 181 182 // NewServerConn starts a new SSH server with c as the underlying 183 // transport. It starts with a handshake and, if the handshake is 184 // unsuccessful, it closes the connection and returns an error. The 185 // Request and NewChannel channels must be serviced, or the connection 186 // will hang. 187 // 188 // The returned error may be of type *ServerAuthError for 189 // authentication errors. 190 func NewServerConn(c net.Conn, config *ServerConfig) (*ServerConn, <-chan NewChannel, <-chan *Request, error) { 191 fullConf := *config 192 fullConf.SetDefaults() 193 if fullConf.MaxAuthTries == 0 { 194 fullConf.MaxAuthTries = 6 195 } 196 // Check if the config contains any unsupported key exchanges 197 for _, kex := range fullConf.KeyExchanges { 198 if _, ok := serverForbiddenKexAlgos[kex]; ok { 199 return nil, nil, nil, fmt.Errorf("ssh: unsupported key exchange %s for server", kex) 200 } 201 } 202 203 s := &connection{ 204 sshConn: sshConn{conn: c}, 205 } 206 perms, err := s.serverHandshake(&fullConf) 207 if err != nil { 208 c.Close() 209 return nil, nil, nil, err 210 } 211 return &ServerConn{s, perms}, s.mux.incomingChannels, s.mux.incomingRequests, nil 212 } 213 214 // signAndMarshal signs the data with the appropriate algorithm, 215 // and serializes the result in SSH wire format. 216 func signAndMarshal(k Signer, rand io.Reader, data []byte) ([]byte, error) { 217 sig, err := k.Sign(rand, data) 218 if err != nil { 219 return nil, err 220 } 221 222 return Marshal(sig), nil 223 } 224 225 // handshake performs key exchange and user authentication. 226 func (s *connection) serverHandshake(config *ServerConfig) (*Permissions, error) { 227 if len(config.hostKeys) == 0 { 228 return nil, errors.New("ssh: server has no host keys") 229 } 230 231 if !config.NoClientAuth && config.PasswordCallback == nil && config.PublicKeyCallback == nil && 232 config.KeyboardInteractiveCallback == nil && (config.GSSAPIWithMICConfig == nil || 233 config.GSSAPIWithMICConfig.AllowLogin == nil || config.GSSAPIWithMICConfig.Server == nil) { 234 return nil, errors.New("ssh: no authentication methods configured but NoClientAuth is also false") 235 } 236 237 if config.ServerVersion != "" { 238 s.serverVersion = []byte(config.ServerVersion) 239 } else { 240 s.serverVersion = []byte(packageVersion) 241 } 242 var err error 243 s.clientVersion, err = exchangeVersions(s.sshConn.conn, s.serverVersion) 244 if err != nil { 245 return nil, err 246 } 247 248 tr := newTransport(s.sshConn.conn, config.Rand, false /* not client */) 249 s.transport = newServerTransport(tr, s.clientVersion, s.serverVersion, config) 250 251 if err := s.transport.waitSession(); err != nil { 252 return nil, err 253 } 254 255 // We just did the key change, so the session ID is established. 256 s.sessionID = s.transport.getSessionID() 257 258 var packet []byte 259 if packet, err = s.transport.readPacket(); err != nil { 260 return nil, err 261 } 262 263 var serviceRequest serviceRequestMsg 264 if err = Unmarshal(packet, &serviceRequest); err != nil { 265 return nil, err 266 } 267 if serviceRequest.Service != serviceUserAuth { 268 return nil, errors.New("ssh: requested service '" + serviceRequest.Service + "' before authenticating") 269 } 270 serviceAccept := serviceAcceptMsg{ 271 Service: serviceUserAuth, 272 } 273 if err := s.transport.writePacket(Marshal(&serviceAccept)); err != nil { 274 return nil, err 275 } 276 277 perms, err := s.serverAuthenticate(config) 278 if err != nil { 279 return nil, err 280 } 281 s.mux = newMux(s.transport) 282 return perms, err 283 } 284 285 func isAcceptableAlgo(algo string) bool { 286 switch algo { 287 case SigAlgoRSA, SigAlgoRSASHA2256, SigAlgoRSASHA2512, KeyAlgoDSA, KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521, KeyAlgoSKECDSA256, KeyAlgoED25519, KeyAlgoSKED25519, 288 CertAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01, CertAlgoECDSA384v01, CertAlgoECDSA521v01, CertAlgoSKECDSA256v01, CertAlgoED25519v01, CertAlgoSKED25519v01: 289 return true 290 } 291 return false 292 } 293 294 func checkSourceAddress(addr net.Addr, sourceAddrs string) error { 295 if addr == nil { 296 return errors.New("ssh: no address known for client, but source-address match required") 297 } 298 299 tcpAddr, ok := addr.(*net.TCPAddr) 300 if !ok { 301 return fmt.Errorf("ssh: remote address %v is not an TCP address when checking source-address match", addr) 302 } 303 304 for _, sourceAddr := range strings.Split(sourceAddrs, ",") { 305 if allowedIP := net.ParseIP(sourceAddr); allowedIP != nil { 306 if allowedIP.Equal(tcpAddr.IP) { 307 return nil 308 } 309 } else { 310 _, ipNet, err := net.ParseCIDR(sourceAddr) 311 if err != nil { 312 return fmt.Errorf("ssh: error parsing source-address restriction %q: %v", sourceAddr, err) 313 } 314 315 if ipNet.Contains(tcpAddr.IP) { 316 return nil 317 } 318 } 319 } 320 321 return fmt.Errorf("ssh: remote address %v is not allowed because of source-address restriction", addr) 322 } 323 324 func gssExchangeToken(gssapiConfig *GSSAPIWithMICConfig, firstToken []byte, s *connection, 325 sessionID []byte, userAuthReq userAuthRequestMsg) (authErr error, perms *Permissions, err error) { 326 gssAPIServer := gssapiConfig.Server 327 defer gssAPIServer.DeleteSecContext() 328 var srcName string 329 for { 330 var ( 331 outToken []byte 332 needContinue bool 333 ) 334 outToken, srcName, needContinue, err = gssAPIServer.AcceptSecContext(firstToken) 335 if err != nil { 336 return err, nil, nil 337 } 338 if len(outToken) != 0 { 339 if err := s.transport.writePacket(Marshal(&userAuthGSSAPIToken{ 340 Token: outToken, 341 })); err != nil { 342 return nil, nil, err 343 } 344 } 345 if !needContinue { 346 break 347 } 348 packet, err := s.transport.readPacket() 349 if err != nil { 350 return nil, nil, err 351 } 352 userAuthGSSAPITokenReq := &userAuthGSSAPIToken{} 353 if err := Unmarshal(packet, userAuthGSSAPITokenReq); err != nil { 354 return nil, nil, err 355 } 356 } 357 packet, err := s.transport.readPacket() 358 if err != nil { 359 return nil, nil, err 360 } 361 userAuthGSSAPIMICReq := &userAuthGSSAPIMIC{} 362 if err := Unmarshal(packet, userAuthGSSAPIMICReq); err != nil { 363 return nil, nil, err 364 } 365 mic := buildMIC(string(sessionID), userAuthReq.User, userAuthReq.Service, userAuthReq.Method) 366 if err := gssAPIServer.VerifyMIC(mic, userAuthGSSAPIMICReq.MIC); err != nil { 367 return err, nil, nil 368 } 369 perms, authErr = gssapiConfig.AllowLogin(s, srcName) 370 return authErr, perms, nil 371 } 372 373 // ServerAuthError represents server authentication errors and is 374 // sometimes returned by NewServerConn. It appends any authentication 375 // errors that may occur, and is returned if all of the authentication 376 // methods provided by the user failed to authenticate. 377 type ServerAuthError struct { 378 // Errors contains authentication errors returned by the authentication 379 // callback methods. The first entry is typically ErrNoAuth. 380 Errors []error 381 } 382 383 func (l ServerAuthError) Error() string { 384 var errs []string 385 for _, err := range l.Errors { 386 errs = append(errs, err.Error()) 387 } 388 return "[" + strings.Join(errs, ", ") + "]" 389 } 390 391 // ErrNoAuth is the error value returned if no 392 // authentication method has been passed yet. This happens as a normal 393 // part of the authentication loop, since the client first tries 394 // 'none' authentication to discover available methods. 395 // It is returned in ServerAuthError.Errors from NewServerConn. 396 var ErrNoAuth = errors.New("ssh: no auth passed yet") 397 398 func (s *connection) serverAuthenticate(config *ServerConfig) (*Permissions, error) { 399 sessionID := s.transport.getSessionID() 400 var cache pubKeyCache 401 var perms *Permissions 402 403 authFailures := 0 404 var authErrs []error 405 var displayedBanner bool 406 407 userAuthLoop: 408 for { 409 if authFailures >= config.MaxAuthTries && config.MaxAuthTries > 0 { 410 discMsg := &disconnectMsg{ 411 Reason: 2, 412 Message: "too many authentication failures", 413 } 414 415 if err := s.transport.writePacket(Marshal(discMsg)); err != nil { 416 return nil, err 417 } 418 419 return nil, discMsg 420 } 421 422 var userAuthReq userAuthRequestMsg 423 if packet, err := s.transport.readPacket(); err != nil { 424 if err == io.EOF { 425 return nil, &ServerAuthError{Errors: authErrs} 426 } 427 return nil, err 428 } else if err = Unmarshal(packet, &userAuthReq); err != nil { 429 return nil, err 430 } 431 432 if userAuthReq.Service != serviceSSH { 433 return nil, errors.New("ssh: client attempted to negotiate for unknown service: " + userAuthReq.Service) 434 } 435 436 s.user = userAuthReq.User 437 438 if !displayedBanner && config.BannerCallback != nil { 439 displayedBanner = true 440 msg := config.BannerCallback(s) 441 if msg != "" { 442 bannerMsg := &userAuthBannerMsg{ 443 Message: msg, 444 } 445 if err := s.transport.writePacket(Marshal(bannerMsg)); err != nil { 446 return nil, err 447 } 448 } 449 } 450 451 perms = nil 452 authErr := ErrNoAuth 453 454 switch userAuthReq.Method { 455 case "none": 456 if config.NoClientAuth { 457 authErr = nil 458 } 459 460 // allow initial attempt of 'none' without penalty 461 if authFailures == 0 { 462 authFailures-- 463 } 464 case "password": 465 if config.PasswordCallback == nil { 466 authErr = errors.New("ssh: password auth not configured") 467 break 468 } 469 payload := userAuthReq.Payload 470 if len(payload) < 1 || payload[0] != 0 { 471 return nil, parseError(msgUserAuthRequest) 472 } 473 payload = payload[1:] 474 password, payload, ok := parseString(payload) 475 if !ok || len(payload) > 0 { 476 return nil, parseError(msgUserAuthRequest) 477 } 478 479 perms, authErr = config.PasswordCallback(s, password) 480 case "keyboard-interactive": 481 if config.KeyboardInteractiveCallback == nil { 482 authErr = errors.New("ssh: keyboard-interactive auth not configured") 483 break 484 } 485 486 prompter := &sshClientKeyboardInteractive{s} 487 perms, authErr = config.KeyboardInteractiveCallback(s, prompter.Challenge) 488 case "publickey": 489 if config.PublicKeyCallback == nil { 490 authErr = errors.New("ssh: publickey auth not configured") 491 break 492 } 493 payload := userAuthReq.Payload 494 if len(payload) < 1 { 495 return nil, parseError(msgUserAuthRequest) 496 } 497 isQuery := payload[0] == 0 498 payload = payload[1:] 499 algoBytes, payload, ok := parseString(payload) 500 if !ok { 501 return nil, parseError(msgUserAuthRequest) 502 } 503 algo := string(algoBytes) 504 if !isAcceptableAlgo(algo) { 505 authErr = fmt.Errorf("ssh: algorithm %q not accepted", algo) 506 break 507 } 508 509 pubKeyData, payload, ok := parseString(payload) 510 if !ok { 511 return nil, parseError(msgUserAuthRequest) 512 } 513 514 pubKey, err := ParsePublicKey(pubKeyData) 515 if err != nil { 516 return nil, err 517 } 518 519 candidate, ok := cache.get(s.user, pubKeyData) 520 if !ok { 521 candidate.user = s.user 522 candidate.pubKeyData = pubKeyData 523 candidate.perms, candidate.result = config.PublicKeyCallback(s, pubKey) 524 if candidate.result == nil && candidate.perms != nil && candidate.perms.CriticalOptions != nil && candidate.perms.CriticalOptions[sourceAddressCriticalOption] != "" { 525 candidate.result = checkSourceAddress( 526 s.RemoteAddr(), 527 candidate.perms.CriticalOptions[sourceAddressCriticalOption]) 528 } 529 cache.add(candidate) 530 } 531 532 if isQuery { 533 // The client can query if the given public key 534 // would be okay. 535 536 if len(payload) > 0 { 537 return nil, parseError(msgUserAuthRequest) 538 } 539 540 if candidate.result == nil { 541 okMsg := userAuthPubKeyOkMsg{ 542 Algo: algo, 543 PubKey: pubKeyData, 544 } 545 if err = s.transport.writePacket(Marshal(&okMsg)); err != nil { 546 return nil, err 547 } 548 continue userAuthLoop 549 } 550 authErr = candidate.result 551 } else { 552 sig, payload, ok := parseSignature(payload) 553 if !ok || len(payload) > 0 { 554 return nil, parseError(msgUserAuthRequest) 555 } 556 // Ensure the public key algo and signature algo 557 // are supported. Compare the private key 558 // algorithm name that corresponds to algo with 559 // sig.Format. This is usually the same, but 560 // for certs, the names differ. 561 if !isAcceptableAlgo(sig.Format) { 562 authErr = fmt.Errorf("ssh: algorithm %q not accepted", sig.Format) 563 break 564 } 565 signedData := buildDataSignedForAuth(sessionID, userAuthReq, algoBytes, pubKeyData) 566 567 if err := pubKey.Verify(signedData, sig); err != nil { 568 return nil, err 569 } 570 571 authErr = candidate.result 572 perms = candidate.perms 573 } 574 case "gssapi-with-mic": 575 if config.GSSAPIWithMICConfig == nil { 576 authErr = errors.New("ssh: gssapi-with-mic auth not configured") 577 break 578 } 579 gssapiConfig := config.GSSAPIWithMICConfig 580 userAuthRequestGSSAPI, err := parseGSSAPIPayload(userAuthReq.Payload) 581 if err != nil { 582 return nil, parseError(msgUserAuthRequest) 583 } 584 // OpenSSH supports Kerberos V5 mechanism only for GSS-API authentication. 585 if userAuthRequestGSSAPI.N == 0 { 586 authErr = fmt.Errorf("ssh: Mechanism negotiation is not supported") 587 break 588 } 589 var i uint32 590 present := false 591 for i = 0; i < userAuthRequestGSSAPI.N; i++ { 592 if userAuthRequestGSSAPI.OIDS[i].Equal(krb5Mesh) { 593 present = true 594 break 595 } 596 } 597 if !present { 598 authErr = fmt.Errorf("ssh: GSSAPI authentication must use the Kerberos V5 mechanism") 599 break 600 } 601 // Initial server response, see RFC 4462 section 3.3. 602 if err := s.transport.writePacket(Marshal(&userAuthGSSAPIResponse{ 603 SupportMech: krb5OID, 604 })); err != nil { 605 return nil, err 606 } 607 // Exchange token, see RFC 4462 section 3.4. 608 packet, err := s.transport.readPacket() 609 if err != nil { 610 return nil, err 611 } 612 userAuthGSSAPITokenReq := &userAuthGSSAPIToken{} 613 if err := Unmarshal(packet, userAuthGSSAPITokenReq); err != nil { 614 return nil, err 615 } 616 authErr, perms, err = gssExchangeToken(gssapiConfig, userAuthGSSAPITokenReq.Token, s, sessionID, 617 userAuthReq) 618 if err != nil { 619 return nil, err 620 } 621 default: 622 authErr = fmt.Errorf("ssh: unknown method %q", userAuthReq.Method) 623 } 624 625 authErrs = append(authErrs, authErr) 626 627 if config.AuthLogCallback != nil { 628 config.AuthLogCallback(s, userAuthReq.Method, authErr) 629 } 630 631 if authErr == nil { 632 break userAuthLoop 633 } 634 635 authFailures++ 636 637 var failureMsg userAuthFailureMsg 638 if config.PasswordCallback != nil { 639 failureMsg.Methods = append(failureMsg.Methods, "password") 640 } 641 if config.PublicKeyCallback != nil { 642 failureMsg.Methods = append(failureMsg.Methods, "publickey") 643 } 644 if config.KeyboardInteractiveCallback != nil { 645 failureMsg.Methods = append(failureMsg.Methods, "keyboard-interactive") 646 } 647 if config.GSSAPIWithMICConfig != nil && config.GSSAPIWithMICConfig.Server != nil && 648 config.GSSAPIWithMICConfig.AllowLogin != nil { 649 failureMsg.Methods = append(failureMsg.Methods, "gssapi-with-mic") 650 } 651 652 if len(failureMsg.Methods) == 0 { 653 return nil, errors.New("ssh: no authentication methods configured but NoClientAuth is also false") 654 } 655 656 if err := s.transport.writePacket(Marshal(&failureMsg)); err != nil { 657 return nil, err 658 } 659 } 660 661 if err := s.transport.writePacket([]byte{msgUserAuthSuccess}); err != nil { 662 return nil, err 663 } 664 return perms, nil 665 } 666 667 // sshClientKeyboardInteractive implements a ClientKeyboardInteractive by 668 // asking the client on the other side of a ServerConn. 669 type sshClientKeyboardInteractive struct { 670 *connection 671 } 672 673 func (c *sshClientKeyboardInteractive) Challenge(user, instruction string, questions []string, echos []bool) (answers []string, err error) { 674 if len(questions) != len(echos) { 675 return nil, errors.New("ssh: echos and questions must have equal length") 676 } 677 678 var prompts []byte 679 for i := range questions { 680 prompts = appendString(prompts, questions[i]) 681 prompts = appendBool(prompts, echos[i]) 682 } 683 684 if err := c.transport.writePacket(Marshal(&userAuthInfoRequestMsg{ 685 Instruction: instruction, 686 NumPrompts: uint32(len(questions)), 687 Prompts: prompts, 688 })); err != nil { 689 return nil, err 690 } 691 692 packet, err := c.transport.readPacket() 693 if err != nil { 694 return nil, err 695 } 696 if packet[0] != msgUserAuthInfoResponse { 697 return nil, unexpectedMessageError(msgUserAuthInfoResponse, packet[0]) 698 } 699 packet = packet[1:] 700 701 n, packet, ok := parseUint32(packet) 702 if !ok || int(n) != len(questions) { 703 return nil, parseError(msgUserAuthInfoResponse) 704 } 705 706 for i := uint32(0); i < n; i++ { 707 ans, rest, ok := parseString(packet) 708 if !ok { 709 return nil, parseError(msgUserAuthInfoResponse) 710 } 711 712 answers = append(answers, string(ans)) 713 packet = rest 714 } 715 if len(packet) != 0 { 716 return nil, errors.New("ssh: junk at end of message") 717 } 718 719 return answers, nil 720 }