本站源代码
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

640 lines
18KB

  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. package ssh
  5. import (
  6. "bytes"
  7. "errors"
  8. "fmt"
  9. "io"
  10. )
  11. type authResult int
  12. const (
  13. authFailure authResult = iota
  14. authPartialSuccess
  15. authSuccess
  16. )
  17. // clientAuthenticate authenticates with the remote server. See RFC 4252.
  18. func (c *connection) clientAuthenticate(config *ClientConfig) error {
  19. // initiate user auth session
  20. if err := c.transport.writePacket(Marshal(&serviceRequestMsg{serviceUserAuth})); err != nil {
  21. return err
  22. }
  23. packet, err := c.transport.readPacket()
  24. if err != nil {
  25. return err
  26. }
  27. var serviceAccept serviceAcceptMsg
  28. if err := Unmarshal(packet, &serviceAccept); err != nil {
  29. return err
  30. }
  31. // during the authentication phase the client first attempts the "none" method
  32. // then any untried methods suggested by the server.
  33. tried := make(map[string]bool)
  34. var lastMethods []string
  35. sessionID := c.transport.getSessionID()
  36. for auth := AuthMethod(new(noneAuth)); auth != nil; {
  37. ok, methods, err := auth.auth(sessionID, config.User, c.transport, config.Rand)
  38. if err != nil {
  39. return err
  40. }
  41. if ok == authSuccess {
  42. // success
  43. return nil
  44. } else if ok == authFailure {
  45. tried[auth.method()] = true
  46. }
  47. if methods == nil {
  48. methods = lastMethods
  49. }
  50. lastMethods = methods
  51. auth = nil
  52. findNext:
  53. for _, a := range config.Auth {
  54. candidateMethod := a.method()
  55. if tried[candidateMethod] {
  56. continue
  57. }
  58. for _, meth := range methods {
  59. if meth == candidateMethod {
  60. auth = a
  61. break findNext
  62. }
  63. }
  64. }
  65. }
  66. return fmt.Errorf("ssh: unable to authenticate, attempted methods %v, no supported methods remain", keys(tried))
  67. }
  68. func keys(m map[string]bool) []string {
  69. s := make([]string, 0, len(m))
  70. for key := range m {
  71. s = append(s, key)
  72. }
  73. return s
  74. }
  75. // An AuthMethod represents an instance of an RFC 4252 authentication method.
  76. type AuthMethod interface {
  77. // auth authenticates user over transport t.
  78. // Returns true if authentication is successful.
  79. // If authentication is not successful, a []string of alternative
  80. // method names is returned. If the slice is nil, it will be ignored
  81. // and the previous set of possible methods will be reused.
  82. auth(session []byte, user string, p packetConn, rand io.Reader) (authResult, []string, error)
  83. // method returns the RFC 4252 method name.
  84. method() string
  85. }
  86. // "none" authentication, RFC 4252 section 5.2.
  87. type noneAuth int
  88. func (n *noneAuth) auth(session []byte, user string, c packetConn, rand io.Reader) (authResult, []string, error) {
  89. if err := c.writePacket(Marshal(&userAuthRequestMsg{
  90. User: user,
  91. Service: serviceSSH,
  92. Method: "none",
  93. })); err != nil {
  94. return authFailure, nil, err
  95. }
  96. return handleAuthResponse(c)
  97. }
  98. func (n *noneAuth) method() string {
  99. return "none"
  100. }
  101. // passwordCallback is an AuthMethod that fetches the password through
  102. // a function call, e.g. by prompting the user.
  103. type passwordCallback func() (password string, err error)
  104. func (cb passwordCallback) auth(session []byte, user string, c packetConn, rand io.Reader) (authResult, []string, error) {
  105. type passwordAuthMsg struct {
  106. User string `sshtype:"50"`
  107. Service string
  108. Method string
  109. Reply bool
  110. Password string
  111. }
  112. pw, err := cb()
  113. // REVIEW NOTE: is there a need to support skipping a password attempt?
  114. // The program may only find out that the user doesn't have a password
  115. // when prompting.
  116. if err != nil {
  117. return authFailure, nil, err
  118. }
  119. if err := c.writePacket(Marshal(&passwordAuthMsg{
  120. User: user,
  121. Service: serviceSSH,
  122. Method: cb.method(),
  123. Reply: false,
  124. Password: pw,
  125. })); err != nil {
  126. return authFailure, nil, err
  127. }
  128. return handleAuthResponse(c)
  129. }
  130. func (cb passwordCallback) method() string {
  131. return "password"
  132. }
  133. // Password returns an AuthMethod using the given password.
  134. func Password(secret string) AuthMethod {
  135. return passwordCallback(func() (string, error) { return secret, nil })
  136. }
  137. // PasswordCallback returns an AuthMethod that uses a callback for
  138. // fetching a password.
  139. func PasswordCallback(prompt func() (secret string, err error)) AuthMethod {
  140. return passwordCallback(prompt)
  141. }
  142. type publickeyAuthMsg struct {
  143. User string `sshtype:"50"`
  144. Service string
  145. Method string
  146. // HasSig indicates to the receiver packet that the auth request is signed and
  147. // should be used for authentication of the request.
  148. HasSig bool
  149. Algoname string
  150. PubKey []byte
  151. // Sig is tagged with "rest" so Marshal will exclude it during
  152. // validateKey
  153. Sig []byte `ssh:"rest"`
  154. }
  155. // publicKeyCallback is an AuthMethod that uses a set of key
  156. // pairs for authentication.
  157. type publicKeyCallback func() ([]Signer, error)
  158. func (cb publicKeyCallback) method() string {
  159. return "publickey"
  160. }
  161. func (cb publicKeyCallback) auth(session []byte, user string, c packetConn, rand io.Reader) (authResult, []string, error) {
  162. // Authentication is performed by sending an enquiry to test if a key is
  163. // acceptable to the remote. If the key is acceptable, the client will
  164. // attempt to authenticate with the valid key. If not the client will repeat
  165. // the process with the remaining keys.
  166. signers, err := cb()
  167. if err != nil {
  168. return authFailure, nil, err
  169. }
  170. var methods []string
  171. for _, signer := range signers {
  172. ok, err := validateKey(signer.PublicKey(), user, c)
  173. if err != nil {
  174. return authFailure, nil, err
  175. }
  176. if !ok {
  177. continue
  178. }
  179. pub := signer.PublicKey()
  180. pubKey := pub.Marshal()
  181. sign, err := signer.Sign(rand, buildDataSignedForAuth(session, userAuthRequestMsg{
  182. User: user,
  183. Service: serviceSSH,
  184. Method: cb.method(),
  185. }, []byte(pub.Type()), pubKey))
  186. if err != nil {
  187. return authFailure, nil, err
  188. }
  189. // manually wrap the serialized signature in a string
  190. s := Marshal(sign)
  191. sig := make([]byte, stringLength(len(s)))
  192. marshalString(sig, s)
  193. msg := publickeyAuthMsg{
  194. User: user,
  195. Service: serviceSSH,
  196. Method: cb.method(),
  197. HasSig: true,
  198. Algoname: pub.Type(),
  199. PubKey: pubKey,
  200. Sig: sig,
  201. }
  202. p := Marshal(&msg)
  203. if err := c.writePacket(p); err != nil {
  204. return authFailure, nil, err
  205. }
  206. var success authResult
  207. success, methods, err = handleAuthResponse(c)
  208. if err != nil {
  209. return authFailure, nil, err
  210. }
  211. // If authentication succeeds or the list of available methods does not
  212. // contain the "publickey" method, do not attempt to authenticate with any
  213. // other keys. According to RFC 4252 Section 7, the latter can occur when
  214. // additional authentication methods are required.
  215. if success == authSuccess || !containsMethod(methods, cb.method()) {
  216. return success, methods, err
  217. }
  218. }
  219. return authFailure, methods, nil
  220. }
  221. func containsMethod(methods []string, method string) bool {
  222. for _, m := range methods {
  223. if m == method {
  224. return true
  225. }
  226. }
  227. return false
  228. }
  229. // validateKey validates the key provided is acceptable to the server.
  230. func validateKey(key PublicKey, user string, c packetConn) (bool, error) {
  231. pubKey := key.Marshal()
  232. msg := publickeyAuthMsg{
  233. User: user,
  234. Service: serviceSSH,
  235. Method: "publickey",
  236. HasSig: false,
  237. Algoname: key.Type(),
  238. PubKey: pubKey,
  239. }
  240. if err := c.writePacket(Marshal(&msg)); err != nil {
  241. return false, err
  242. }
  243. return confirmKeyAck(key, c)
  244. }
  245. func confirmKeyAck(key PublicKey, c packetConn) (bool, error) {
  246. pubKey := key.Marshal()
  247. algoname := key.Type()
  248. for {
  249. packet, err := c.readPacket()
  250. if err != nil {
  251. return false, err
  252. }
  253. switch packet[0] {
  254. case msgUserAuthBanner:
  255. if err := handleBannerResponse(c, packet); err != nil {
  256. return false, err
  257. }
  258. case msgUserAuthPubKeyOk:
  259. var msg userAuthPubKeyOkMsg
  260. if err := Unmarshal(packet, &msg); err != nil {
  261. return false, err
  262. }
  263. if msg.Algo != algoname || !bytes.Equal(msg.PubKey, pubKey) {
  264. return false, nil
  265. }
  266. return true, nil
  267. case msgUserAuthFailure:
  268. return false, nil
  269. default:
  270. return false, unexpectedMessageError(msgUserAuthSuccess, packet[0])
  271. }
  272. }
  273. }
  274. // PublicKeys returns an AuthMethod that uses the given key
  275. // pairs.
  276. func PublicKeys(signers ...Signer) AuthMethod {
  277. return publicKeyCallback(func() ([]Signer, error) { return signers, nil })
  278. }
  279. // PublicKeysCallback returns an AuthMethod that runs the given
  280. // function to obtain a list of key pairs.
  281. func PublicKeysCallback(getSigners func() (signers []Signer, err error)) AuthMethod {
  282. return publicKeyCallback(getSigners)
  283. }
  284. // handleAuthResponse returns whether the preceding authentication request succeeded
  285. // along with a list of remaining authentication methods to try next and
  286. // an error if an unexpected response was received.
  287. func handleAuthResponse(c packetConn) (authResult, []string, error) {
  288. for {
  289. packet, err := c.readPacket()
  290. if err != nil {
  291. return authFailure, nil, err
  292. }
  293. switch packet[0] {
  294. case msgUserAuthBanner:
  295. if err := handleBannerResponse(c, packet); err != nil {
  296. return authFailure, nil, err
  297. }
  298. case msgUserAuthFailure:
  299. var msg userAuthFailureMsg
  300. if err := Unmarshal(packet, &msg); err != nil {
  301. return authFailure, nil, err
  302. }
  303. if msg.PartialSuccess {
  304. return authPartialSuccess, msg.Methods, nil
  305. }
  306. return authFailure, msg.Methods, nil
  307. case msgUserAuthSuccess:
  308. return authSuccess, nil, nil
  309. default:
  310. return authFailure, nil, unexpectedMessageError(msgUserAuthSuccess, packet[0])
  311. }
  312. }
  313. }
  314. func handleBannerResponse(c packetConn, packet []byte) error {
  315. var msg userAuthBannerMsg
  316. if err := Unmarshal(packet, &msg); err != nil {
  317. return err
  318. }
  319. transport, ok := c.(*handshakeTransport)
  320. if !ok {
  321. return nil
  322. }
  323. if transport.bannerCallback != nil {
  324. return transport.bannerCallback(msg.Message)
  325. }
  326. return nil
  327. }
  328. // KeyboardInteractiveChallenge should print questions, optionally
  329. // disabling echoing (e.g. for passwords), and return all the answers.
  330. // Challenge may be called multiple times in a single session. After
  331. // successful authentication, the server may send a challenge with no
  332. // questions, for which the user and instruction messages should be
  333. // printed. RFC 4256 section 3.3 details how the UI should behave for
  334. // both CLI and GUI environments.
  335. type KeyboardInteractiveChallenge func(user, instruction string, questions []string, echos []bool) (answers []string, err error)
  336. // KeyboardInteractive returns an AuthMethod using a prompt/response
  337. // sequence controlled by the server.
  338. func KeyboardInteractive(challenge KeyboardInteractiveChallenge) AuthMethod {
  339. return challenge
  340. }
  341. func (cb KeyboardInteractiveChallenge) method() string {
  342. return "keyboard-interactive"
  343. }
  344. func (cb KeyboardInteractiveChallenge) auth(session []byte, user string, c packetConn, rand io.Reader) (authResult, []string, error) {
  345. type initiateMsg struct {
  346. User string `sshtype:"50"`
  347. Service string
  348. Method string
  349. Language string
  350. Submethods string
  351. }
  352. if err := c.writePacket(Marshal(&initiateMsg{
  353. User: user,
  354. Service: serviceSSH,
  355. Method: "keyboard-interactive",
  356. })); err != nil {
  357. return authFailure, nil, err
  358. }
  359. for {
  360. packet, err := c.readPacket()
  361. if err != nil {
  362. return authFailure, nil, err
  363. }
  364. // like handleAuthResponse, but with less options.
  365. switch packet[0] {
  366. case msgUserAuthBanner:
  367. if err := handleBannerResponse(c, packet); err != nil {
  368. return authFailure, nil, err
  369. }
  370. continue
  371. case msgUserAuthInfoRequest:
  372. // OK
  373. case msgUserAuthFailure:
  374. var msg userAuthFailureMsg
  375. if err := Unmarshal(packet, &msg); err != nil {
  376. return authFailure, nil, err
  377. }
  378. if msg.PartialSuccess {
  379. return authPartialSuccess, msg.Methods, nil
  380. }
  381. return authFailure, msg.Methods, nil
  382. case msgUserAuthSuccess:
  383. return authSuccess, nil, nil
  384. default:
  385. return authFailure, nil, unexpectedMessageError(msgUserAuthInfoRequest, packet[0])
  386. }
  387. var msg userAuthInfoRequestMsg
  388. if err := Unmarshal(packet, &msg); err != nil {
  389. return authFailure, nil, err
  390. }
  391. // Manually unpack the prompt/echo pairs.
  392. rest := msg.Prompts
  393. var prompts []string
  394. var echos []bool
  395. for i := 0; i < int(msg.NumPrompts); i++ {
  396. prompt, r, ok := parseString(rest)
  397. if !ok || len(r) == 0 {
  398. return authFailure, nil, errors.New("ssh: prompt format error")
  399. }
  400. prompts = append(prompts, string(prompt))
  401. echos = append(echos, r[0] != 0)
  402. rest = r[1:]
  403. }
  404. if len(rest) != 0 {
  405. return authFailure, nil, errors.New("ssh: extra data following keyboard-interactive pairs")
  406. }
  407. answers, err := cb(msg.User, msg.Instruction, prompts, echos)
  408. if err != nil {
  409. return authFailure, nil, err
  410. }
  411. if len(answers) != len(prompts) {
  412. return authFailure, nil, errors.New("ssh: not enough answers from keyboard-interactive callback")
  413. }
  414. responseLength := 1 + 4
  415. for _, a := range answers {
  416. responseLength += stringLength(len(a))
  417. }
  418. serialized := make([]byte, responseLength)
  419. p := serialized
  420. p[0] = msgUserAuthInfoResponse
  421. p = p[1:]
  422. p = marshalUint32(p, uint32(len(answers)))
  423. for _, a := range answers {
  424. p = marshalString(p, []byte(a))
  425. }
  426. if err := c.writePacket(serialized); err != nil {
  427. return authFailure, nil, err
  428. }
  429. }
  430. }
  431. type retryableAuthMethod struct {
  432. authMethod AuthMethod
  433. maxTries int
  434. }
  435. func (r *retryableAuthMethod) auth(session []byte, user string, c packetConn, rand io.Reader) (ok authResult, methods []string, err error) {
  436. for i := 0; r.maxTries <= 0 || i < r.maxTries; i++ {
  437. ok, methods, err = r.authMethod.auth(session, user, c, rand)
  438. if ok != authFailure || err != nil { // either success, partial success or error terminate
  439. return ok, methods, err
  440. }
  441. }
  442. return ok, methods, err
  443. }
  444. func (r *retryableAuthMethod) method() string {
  445. return r.authMethod.method()
  446. }
  447. // RetryableAuthMethod is a decorator for other auth methods enabling them to
  448. // be retried up to maxTries before considering that AuthMethod itself failed.
  449. // If maxTries is <= 0, will retry indefinitely
  450. //
  451. // This is useful for interactive clients using challenge/response type
  452. // authentication (e.g. Keyboard-Interactive, Password, etc) where the user
  453. // could mistype their response resulting in the server issuing a
  454. // SSH_MSG_USERAUTH_FAILURE (rfc4252 #8 [password] and rfc4256 #3.4
  455. // [keyboard-interactive]); Without this decorator, the non-retryable
  456. // AuthMethod would be removed from future consideration, and never tried again
  457. // (and so the user would never be able to retry their entry).
  458. func RetryableAuthMethod(auth AuthMethod, maxTries int) AuthMethod {
  459. return &retryableAuthMethod{authMethod: auth, maxTries: maxTries}
  460. }
  461. // GSSAPIWithMICAuthMethod is an AuthMethod with "gssapi-with-mic" authentication.
  462. // See RFC 4462 section 3
  463. // gssAPIClient is implementation of the GSSAPIClient interface, see the definition of the interface for details.
  464. // target is the server host you want to log in to.
  465. func GSSAPIWithMICAuthMethod(gssAPIClient GSSAPIClient, target string) AuthMethod {
  466. if gssAPIClient == nil {
  467. panic("gss-api client must be not nil with enable gssapi-with-mic")
  468. }
  469. return &gssAPIWithMICCallback{gssAPIClient: gssAPIClient, target: target}
  470. }
  471. type gssAPIWithMICCallback struct {
  472. gssAPIClient GSSAPIClient
  473. target string
  474. }
  475. func (g *gssAPIWithMICCallback) auth(session []byte, user string, c packetConn, rand io.Reader) (authResult, []string, error) {
  476. m := &userAuthRequestMsg{
  477. User: user,
  478. Service: serviceSSH,
  479. Method: g.method(),
  480. }
  481. // The GSS-API authentication method is initiated when the client sends an SSH_MSG_USERAUTH_REQUEST.
  482. // See RFC 4462 section 3.2.
  483. m.Payload = appendU32(m.Payload, 1)
  484. m.Payload = appendString(m.Payload, string(krb5OID))
  485. if err := c.writePacket(Marshal(m)); err != nil {
  486. return authFailure, nil, err
  487. }
  488. // The server responds to the SSH_MSG_USERAUTH_REQUEST with either an
  489. // SSH_MSG_USERAUTH_FAILURE if none of the mechanisms are supported or
  490. // with an SSH_MSG_USERAUTH_GSSAPI_RESPONSE.
  491. // See RFC 4462 section 3.3.
  492. // OpenSSH supports Kerberos V5 mechanism only for GSS-API authentication,so I don't want to check
  493. // selected mech if it is valid.
  494. packet, err := c.readPacket()
  495. if err != nil {
  496. return authFailure, nil, err
  497. }
  498. userAuthGSSAPIResp := &userAuthGSSAPIResponse{}
  499. if err := Unmarshal(packet, userAuthGSSAPIResp); err != nil {
  500. return authFailure, nil, err
  501. }
  502. // Start the loop into the exchange token.
  503. // See RFC 4462 section 3.4.
  504. var token []byte
  505. defer g.gssAPIClient.DeleteSecContext()
  506. for {
  507. // Initiates the establishment of a security context between the application and a remote peer.
  508. nextToken, needContinue, err := g.gssAPIClient.InitSecContext("host@"+g.target, token, false)
  509. if err != nil {
  510. return authFailure, nil, err
  511. }
  512. if len(nextToken) > 0 {
  513. if err := c.writePacket(Marshal(&userAuthGSSAPIToken{
  514. Token: nextToken,
  515. })); err != nil {
  516. return authFailure, nil, err
  517. }
  518. }
  519. if !needContinue {
  520. break
  521. }
  522. packet, err = c.readPacket()
  523. if err != nil {
  524. return authFailure, nil, err
  525. }
  526. switch packet[0] {
  527. case msgUserAuthFailure:
  528. var msg userAuthFailureMsg
  529. if err := Unmarshal(packet, &msg); err != nil {
  530. return authFailure, nil, err
  531. }
  532. if msg.PartialSuccess {
  533. return authPartialSuccess, msg.Methods, nil
  534. }
  535. return authFailure, msg.Methods, nil
  536. case msgUserAuthGSSAPIError:
  537. userAuthGSSAPIErrorResp := &userAuthGSSAPIError{}
  538. if err := Unmarshal(packet, userAuthGSSAPIErrorResp); err != nil {
  539. return authFailure, nil, err
  540. }
  541. return authFailure, nil, fmt.Errorf("GSS-API Error:\n"+
  542. "Major Status: %d\n"+
  543. "Minor Status: %d\n"+
  544. "Error Message: %s\n", userAuthGSSAPIErrorResp.MajorStatus, userAuthGSSAPIErrorResp.MinorStatus,
  545. userAuthGSSAPIErrorResp.Message)
  546. case msgUserAuthGSSAPIToken:
  547. userAuthGSSAPITokenReq := &userAuthGSSAPIToken{}
  548. if err := Unmarshal(packet, userAuthGSSAPITokenReq); err != nil {
  549. return authFailure, nil, err
  550. }
  551. token = userAuthGSSAPITokenReq.Token
  552. }
  553. }
  554. // Binding Encryption Keys.
  555. // See RFC 4462 section 3.5.
  556. micField := buildMIC(string(session), user, "ssh-connection", "gssapi-with-mic")
  557. micToken, err := g.gssAPIClient.GetMIC(micField)
  558. if err != nil {
  559. return authFailure, nil, err
  560. }
  561. if err := c.writePacket(Marshal(&userAuthGSSAPIMIC{
  562. MIC: micToken,
  563. })); err != nil {
  564. return authFailure, nil, err
  565. }
  566. return handleAuthResponse(c)
  567. }
  568. func (g *gssAPIWithMICCallback) method() string {
  569. return "gssapi-with-mic"
  570. }
上海开阖软件有限公司 沪ICP备12045867号-1