auth_handler.py 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949
  1. # Copyright (C) 2003-2007 Robey Pointer <robeypointer@gmail.com>
  2. #
  3. # This file is part of paramiko.
  4. #
  5. # Paramiko is free software; you can redistribute it and/or modify it under the
  6. # terms of the GNU Lesser General Public License as published by the Free
  7. # Software Foundation; either version 2.1 of the License, or (at your option)
  8. # any later version.
  9. #
  10. # Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY
  11. # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  12. # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
  13. # details.
  14. #
  15. # You should have received a copy of the GNU Lesser General Public License
  16. # along with Paramiko; if not, write to the Free Software Foundation, Inc.,
  17. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  18. """
  19. `.AuthHandler`
  20. """
  21. import weakref
  22. import time
  23. import re
  24. from paramiko.common import (
  25. cMSG_SERVICE_REQUEST,
  26. cMSG_DISCONNECT,
  27. DISCONNECT_SERVICE_NOT_AVAILABLE,
  28. DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE,
  29. cMSG_USERAUTH_REQUEST,
  30. cMSG_SERVICE_ACCEPT,
  31. DEBUG,
  32. AUTH_SUCCESSFUL,
  33. INFO,
  34. cMSG_USERAUTH_SUCCESS,
  35. cMSG_USERAUTH_FAILURE,
  36. AUTH_PARTIALLY_SUCCESSFUL,
  37. cMSG_USERAUTH_INFO_REQUEST,
  38. WARNING,
  39. AUTH_FAILED,
  40. cMSG_USERAUTH_PK_OK,
  41. cMSG_USERAUTH_INFO_RESPONSE,
  42. MSG_SERVICE_REQUEST,
  43. MSG_SERVICE_ACCEPT,
  44. MSG_USERAUTH_REQUEST,
  45. MSG_USERAUTH_SUCCESS,
  46. MSG_USERAUTH_FAILURE,
  47. MSG_USERAUTH_BANNER,
  48. MSG_USERAUTH_INFO_REQUEST,
  49. MSG_USERAUTH_INFO_RESPONSE,
  50. cMSG_USERAUTH_GSSAPI_RESPONSE,
  51. cMSG_USERAUTH_GSSAPI_TOKEN,
  52. cMSG_USERAUTH_GSSAPI_MIC,
  53. MSG_USERAUTH_GSSAPI_RESPONSE,
  54. MSG_USERAUTH_GSSAPI_TOKEN,
  55. MSG_USERAUTH_GSSAPI_ERROR,
  56. MSG_USERAUTH_GSSAPI_ERRTOK,
  57. MSG_USERAUTH_GSSAPI_MIC,
  58. MSG_NAMES,
  59. cMSG_USERAUTH_BANNER,
  60. )
  61. from paramiko.message import Message
  62. from paramiko.py3compat import b, u
  63. from paramiko.ssh_exception import (
  64. SSHException,
  65. AuthenticationException,
  66. BadAuthenticationType,
  67. PartialAuthentication,
  68. )
  69. from paramiko.server import InteractiveQuery
  70. from paramiko.ssh_gss import GSSAuth, GSS_EXCEPTIONS
  71. class AuthHandler(object):
  72. """
  73. Internal class to handle the mechanics of authentication.
  74. """
  75. def __init__(self, transport):
  76. self.transport = weakref.proxy(transport)
  77. self.username = None
  78. self.authenticated = False
  79. self.auth_event = None
  80. self.auth_method = ""
  81. self.banner = None
  82. self.password = None
  83. self.private_key = None
  84. self.interactive_handler = None
  85. self.submethods = None
  86. # for server mode:
  87. self.auth_username = None
  88. self.auth_fail_count = 0
  89. # for GSSAPI
  90. self.gss_host = None
  91. self.gss_deleg_creds = True
  92. def _log(self, *args):
  93. return self.transport._log(*args)
  94. def is_authenticated(self):
  95. return self.authenticated
  96. def get_username(self):
  97. if self.transport.server_mode:
  98. return self.auth_username
  99. else:
  100. return self.username
  101. def auth_none(self, username, event):
  102. self.transport.lock.acquire()
  103. try:
  104. self.auth_event = event
  105. self.auth_method = "none"
  106. self.username = username
  107. self._request_auth()
  108. finally:
  109. self.transport.lock.release()
  110. def auth_publickey(self, username, key, event):
  111. self.transport.lock.acquire()
  112. try:
  113. self.auth_event = event
  114. self.auth_method = "publickey"
  115. self.username = username
  116. self.private_key = key
  117. self._request_auth()
  118. finally:
  119. self.transport.lock.release()
  120. def auth_password(self, username, password, event):
  121. self.transport.lock.acquire()
  122. try:
  123. self.auth_event = event
  124. self.auth_method = "password"
  125. self.username = username
  126. self.password = password
  127. self._request_auth()
  128. finally:
  129. self.transport.lock.release()
  130. def auth_interactive(self, username, handler, event, submethods=""):
  131. """
  132. response_list = handler(title, instructions, prompt_list)
  133. """
  134. self.transport.lock.acquire()
  135. try:
  136. self.auth_event = event
  137. self.auth_method = "keyboard-interactive"
  138. self.username = username
  139. self.interactive_handler = handler
  140. self.submethods = submethods
  141. self._request_auth()
  142. finally:
  143. self.transport.lock.release()
  144. def auth_gssapi_with_mic(self, username, gss_host, gss_deleg_creds, event):
  145. self.transport.lock.acquire()
  146. try:
  147. self.auth_event = event
  148. self.auth_method = "gssapi-with-mic"
  149. self.username = username
  150. self.gss_host = gss_host
  151. self.gss_deleg_creds = gss_deleg_creds
  152. self._request_auth()
  153. finally:
  154. self.transport.lock.release()
  155. def auth_gssapi_keyex(self, username, event):
  156. self.transport.lock.acquire()
  157. try:
  158. self.auth_event = event
  159. self.auth_method = "gssapi-keyex"
  160. self.username = username
  161. self._request_auth()
  162. finally:
  163. self.transport.lock.release()
  164. def abort(self):
  165. if self.auth_event is not None:
  166. self.auth_event.set()
  167. # ...internals...
  168. def _request_auth(self):
  169. m = Message()
  170. m.add_byte(cMSG_SERVICE_REQUEST)
  171. m.add_string("ssh-userauth")
  172. self.transport._send_message(m)
  173. def _disconnect_service_not_available(self):
  174. m = Message()
  175. m.add_byte(cMSG_DISCONNECT)
  176. m.add_int(DISCONNECT_SERVICE_NOT_AVAILABLE)
  177. m.add_string("Service not available")
  178. m.add_string("en")
  179. self.transport._send_message(m)
  180. self.transport.close()
  181. def _disconnect_no_more_auth(self):
  182. m = Message()
  183. m.add_byte(cMSG_DISCONNECT)
  184. m.add_int(DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE)
  185. m.add_string("No more auth methods available")
  186. m.add_string("en")
  187. self.transport._send_message(m)
  188. self.transport.close()
  189. def _get_key_type_and_bits(self, key):
  190. """
  191. Given any key, return its type/algorithm & bits-to-sign.
  192. Intended for input to or verification of, key signatures.
  193. """
  194. # Use certificate contents, if available, plain pubkey otherwise
  195. if key.public_blob:
  196. return key.public_blob.key_type, key.public_blob.key_blob
  197. else:
  198. return key.get_name(), key
  199. def _get_session_blob(self, key, service, username, algorithm):
  200. m = Message()
  201. m.add_string(self.transport.session_id)
  202. m.add_byte(cMSG_USERAUTH_REQUEST)
  203. m.add_string(username)
  204. m.add_string(service)
  205. m.add_string("publickey")
  206. m.add_boolean(True)
  207. _, bits = self._get_key_type_and_bits(key)
  208. m.add_string(algorithm)
  209. m.add_string(bits)
  210. return m.asbytes()
  211. def wait_for_response(self, event):
  212. max_ts = None
  213. if self.transport.auth_timeout is not None:
  214. max_ts = time.time() + self.transport.auth_timeout
  215. while True:
  216. event.wait(0.1)
  217. if not self.transport.is_active():
  218. e = self.transport.get_exception()
  219. if (e is None) or issubclass(e.__class__, EOFError):
  220. e = AuthenticationException("Authentication failed.")
  221. raise e
  222. if event.is_set():
  223. break
  224. if max_ts is not None and max_ts <= time.time():
  225. raise AuthenticationException("Authentication timeout.")
  226. if not self.is_authenticated():
  227. e = self.transport.get_exception()
  228. if e is None:
  229. e = AuthenticationException("Authentication failed.")
  230. # this is horrible. Python Exception isn't yet descended from
  231. # object, so type(e) won't work. :(
  232. if issubclass(e.__class__, PartialAuthentication):
  233. return e.allowed_types
  234. raise e
  235. return []
  236. def _parse_service_request(self, m):
  237. service = m.get_text()
  238. if self.transport.server_mode and (service == "ssh-userauth"):
  239. # accepted
  240. m = Message()
  241. m.add_byte(cMSG_SERVICE_ACCEPT)
  242. m.add_string(service)
  243. self.transport._send_message(m)
  244. banner, language = self.transport.server_object.get_banner()
  245. if banner:
  246. m = Message()
  247. m.add_byte(cMSG_USERAUTH_BANNER)
  248. m.add_string(banner)
  249. m.add_string(language)
  250. self.transport._send_message(m)
  251. return
  252. # dunno this one
  253. self._disconnect_service_not_available()
  254. def _generate_key_from_request(self, algorithm, keyblob):
  255. # For use in server mode.
  256. options = self.transport.preferred_pubkeys
  257. if algorithm.replace("-cert-v01@openssh.com", "") not in options:
  258. err = (
  259. "Auth rejected: pubkey algorithm '{}' unsupported or disabled"
  260. )
  261. self._log(INFO, err.format(algorithm))
  262. return None
  263. return self.transport._key_info[algorithm](Message(keyblob))
  264. def _finalize_pubkey_algorithm(self, key_type):
  265. # Short-circuit for non-RSA keys
  266. if "rsa" not in key_type:
  267. return key_type
  268. self._log(
  269. DEBUG,
  270. "Finalizing pubkey algorithm for key of type {!r}".format(
  271. key_type
  272. ),
  273. )
  274. # NOTE re #2017: When the key is an RSA cert and the remote server is
  275. # OpenSSH 7.7 or earlier, always use ssh-rsa-cert-v01@openssh.com.
  276. # Those versions of the server won't support rsa-sha2 family sig algos
  277. # for certs specifically, and in tandem with various server bugs
  278. # regarding server-sig-algs, it's impossible to fit this into the rest
  279. # of the logic here.
  280. if key_type.endswith("-cert-v01@openssh.com") and re.search(
  281. r"-OpenSSH_(?:[1-6]|7\.[0-7])", self.transport.remote_version
  282. ):
  283. pubkey_algo = "ssh-rsa-cert-v01@openssh.com"
  284. self.transport._agreed_pubkey_algorithm = pubkey_algo
  285. self._log(DEBUG, "OpenSSH<7.8 + RSA cert = forcing ssh-rsa!")
  286. self._log(
  287. DEBUG, "Agreed upon {!r} pubkey algorithm".format(pubkey_algo)
  288. )
  289. return pubkey_algo
  290. # Normal attempts to handshake follow from here.
  291. # Only consider RSA algos from our list, lest we agree on another!
  292. my_algos = [x for x in self.transport.preferred_pubkeys if "rsa" in x]
  293. self._log(DEBUG, "Our pubkey algorithm list: {}".format(my_algos))
  294. # Short-circuit negatively if user disabled all RSA algos (heh)
  295. if not my_algos:
  296. raise SSHException(
  297. "An RSA key was specified, but no RSA pubkey algorithms are configured!" # noqa
  298. )
  299. # Check for server-sig-algs if supported & sent
  300. server_algo_str = u(
  301. self.transport.server_extensions.get("server-sig-algs", b(""))
  302. )
  303. pubkey_algo = None
  304. if server_algo_str:
  305. server_algos = server_algo_str.split(",")
  306. self._log(
  307. DEBUG, "Server-side algorithm list: {}".format(server_algos)
  308. )
  309. # Only use algos from our list that the server likes, in our own
  310. # preference order. (NOTE: purposefully using same style as in
  311. # Transport...expect to refactor later)
  312. agreement = list(filter(server_algos.__contains__, my_algos))
  313. if agreement:
  314. pubkey_algo = agreement[0]
  315. self._log(
  316. DEBUG,
  317. "Agreed upon {!r} pubkey algorithm".format(pubkey_algo),
  318. )
  319. else:
  320. self._log(DEBUG, "No common pubkey algorithms exist! Dying.")
  321. # TODO: MAY want to use IncompatiblePeer again here but that's
  322. # technically for initial key exchange, not pubkey auth.
  323. err = "Unable to agree on a pubkey algorithm for signing a {!r} key!" # noqa
  324. raise AuthenticationException(err.format(key_type))
  325. else:
  326. # Fallback: first one in our (possibly tweaked by caller) list
  327. pubkey_algo = my_algos[0]
  328. msg = "Server did not send a server-sig-algs list; defaulting to our first preferred algo ({!r})" # noqa
  329. self._log(DEBUG, msg.format(pubkey_algo))
  330. self._log(
  331. DEBUG,
  332. "NOTE: you may use the 'disabled_algorithms' SSHClient/Transport init kwarg to disable that or other algorithms if your server does not support them!", # noqa
  333. )
  334. if key_type.endswith("-cert-v01@openssh.com"):
  335. pubkey_algo += "-cert-v01@openssh.com"
  336. self.transport._agreed_pubkey_algorithm = pubkey_algo
  337. return pubkey_algo
  338. def _parse_service_accept(self, m):
  339. service = m.get_text()
  340. if service == "ssh-userauth":
  341. # TODO 3.0: this message sucks ass. change it to something more
  342. # obvious. it always appears to mean "we already authed" but no! it
  343. # just means "we are allowed to TRY authing!"
  344. self._log(DEBUG, "userauth is OK")
  345. m = Message()
  346. m.add_byte(cMSG_USERAUTH_REQUEST)
  347. m.add_string(self.username)
  348. m.add_string("ssh-connection")
  349. m.add_string(self.auth_method)
  350. if self.auth_method == "password":
  351. m.add_boolean(False)
  352. password = b(self.password)
  353. m.add_string(password)
  354. elif self.auth_method == "publickey":
  355. m.add_boolean(True)
  356. key_type, bits = self._get_key_type_and_bits(self.private_key)
  357. algorithm = self._finalize_pubkey_algorithm(key_type)
  358. m.add_string(algorithm)
  359. m.add_string(bits)
  360. blob = self._get_session_blob(
  361. self.private_key,
  362. "ssh-connection",
  363. self.username,
  364. algorithm,
  365. )
  366. sig = self.private_key.sign_ssh_data(blob, algorithm)
  367. m.add_string(sig)
  368. elif self.auth_method == "keyboard-interactive":
  369. m.add_string("")
  370. m.add_string(self.submethods)
  371. elif self.auth_method == "gssapi-with-mic":
  372. sshgss = GSSAuth(self.auth_method, self.gss_deleg_creds)
  373. m.add_bytes(sshgss.ssh_gss_oids())
  374. # send the supported GSSAPI OIDs to the server
  375. self.transport._send_message(m)
  376. ptype, m = self.transport.packetizer.read_message()
  377. if ptype == MSG_USERAUTH_BANNER:
  378. self._parse_userauth_banner(m)
  379. ptype, m = self.transport.packetizer.read_message()
  380. if ptype == MSG_USERAUTH_GSSAPI_RESPONSE:
  381. # Read the mechanism selected by the server. We send just
  382. # the Kerberos V5 OID, so the server can only respond with
  383. # this OID.
  384. mech = m.get_string()
  385. m = Message()
  386. m.add_byte(cMSG_USERAUTH_GSSAPI_TOKEN)
  387. try:
  388. m.add_string(
  389. sshgss.ssh_init_sec_context(
  390. self.gss_host, mech, self.username
  391. )
  392. )
  393. except GSS_EXCEPTIONS as e:
  394. return self._handle_local_gss_failure(e)
  395. self.transport._send_message(m)
  396. while True:
  397. ptype, m = self.transport.packetizer.read_message()
  398. if ptype == MSG_USERAUTH_GSSAPI_TOKEN:
  399. srv_token = m.get_string()
  400. try:
  401. next_token = sshgss.ssh_init_sec_context(
  402. self.gss_host,
  403. mech,
  404. self.username,
  405. srv_token,
  406. )
  407. except GSS_EXCEPTIONS as e:
  408. return self._handle_local_gss_failure(e)
  409. # After this step the GSSAPI should not return any
  410. # token. If it does, we keep sending the token to
  411. # the server until no more token is returned.
  412. if next_token is None:
  413. break
  414. else:
  415. m = Message()
  416. m.add_byte(cMSG_USERAUTH_GSSAPI_TOKEN)
  417. m.add_string(next_token)
  418. self.transport.send_message(m)
  419. else:
  420. raise SSHException(
  421. "Received Package: {}".format(MSG_NAMES[ptype])
  422. )
  423. m = Message()
  424. m.add_byte(cMSG_USERAUTH_GSSAPI_MIC)
  425. # send the MIC to the server
  426. m.add_string(sshgss.ssh_get_mic(self.transport.session_id))
  427. elif ptype == MSG_USERAUTH_GSSAPI_ERRTOK:
  428. # RFC 4462 says we are not required to implement GSS-API
  429. # error messages.
  430. # See RFC 4462 Section 3.8 in
  431. # http://www.ietf.org/rfc/rfc4462.txt
  432. raise SSHException("Server returned an error token")
  433. elif ptype == MSG_USERAUTH_GSSAPI_ERROR:
  434. maj_status = m.get_int()
  435. min_status = m.get_int()
  436. err_msg = m.get_string()
  437. m.get_string() # Lang tag - discarded
  438. raise SSHException(
  439. """GSS-API Error:
  440. Major Status: {}
  441. Minor Status: {}
  442. Error Message: {}
  443. """.format(
  444. maj_status, min_status, err_msg
  445. )
  446. )
  447. elif ptype == MSG_USERAUTH_FAILURE:
  448. self._parse_userauth_failure(m)
  449. return
  450. else:
  451. raise SSHException(
  452. "Received Package: {}".format(MSG_NAMES[ptype])
  453. )
  454. elif (
  455. self.auth_method == "gssapi-keyex"
  456. and self.transport.gss_kex_used
  457. ):
  458. kexgss = self.transport.kexgss_ctxt
  459. kexgss.set_username(self.username)
  460. mic_token = kexgss.ssh_get_mic(self.transport.session_id)
  461. m.add_string(mic_token)
  462. elif self.auth_method == "none":
  463. pass
  464. else:
  465. raise SSHException(
  466. 'Unknown auth method "{}"'.format(self.auth_method)
  467. )
  468. self.transport._send_message(m)
  469. else:
  470. self._log(
  471. DEBUG, 'Service request "{}" accepted (?)'.format(service)
  472. )
  473. def _send_auth_result(self, username, method, result):
  474. # okay, send result
  475. m = Message()
  476. if result == AUTH_SUCCESSFUL:
  477. self._log(INFO, "Auth granted ({}).".format(method))
  478. m.add_byte(cMSG_USERAUTH_SUCCESS)
  479. self.authenticated = True
  480. else:
  481. self._log(INFO, "Auth rejected ({}).".format(method))
  482. m.add_byte(cMSG_USERAUTH_FAILURE)
  483. m.add_string(
  484. self.transport.server_object.get_allowed_auths(username)
  485. )
  486. if result == AUTH_PARTIALLY_SUCCESSFUL:
  487. m.add_boolean(True)
  488. else:
  489. m.add_boolean(False)
  490. self.auth_fail_count += 1
  491. self.transport._send_message(m)
  492. if self.auth_fail_count >= 10:
  493. self._disconnect_no_more_auth()
  494. if result == AUTH_SUCCESSFUL:
  495. self.transport._auth_trigger()
  496. def _interactive_query(self, q):
  497. # make interactive query instead of response
  498. m = Message()
  499. m.add_byte(cMSG_USERAUTH_INFO_REQUEST)
  500. m.add_string(q.name)
  501. m.add_string(q.instructions)
  502. m.add_string(bytes())
  503. m.add_int(len(q.prompts))
  504. for p in q.prompts:
  505. m.add_string(p[0])
  506. m.add_boolean(p[1])
  507. self.transport._send_message(m)
  508. def _parse_userauth_request(self, m):
  509. if not self.transport.server_mode:
  510. # er, uh... what?
  511. m = Message()
  512. m.add_byte(cMSG_USERAUTH_FAILURE)
  513. m.add_string("none")
  514. m.add_boolean(False)
  515. self.transport._send_message(m)
  516. return
  517. if self.authenticated:
  518. # ignore
  519. return
  520. username = m.get_text()
  521. service = m.get_text()
  522. method = m.get_text()
  523. self._log(
  524. DEBUG,
  525. "Auth request (type={}) service={}, username={}".format(
  526. method, service, username
  527. ),
  528. )
  529. if service != "ssh-connection":
  530. self._disconnect_service_not_available()
  531. return
  532. if (self.auth_username is not None) and (
  533. self.auth_username != username
  534. ):
  535. self._log(
  536. WARNING,
  537. "Auth rejected because the client attempted to change username in mid-flight", # noqa
  538. )
  539. self._disconnect_no_more_auth()
  540. return
  541. self.auth_username = username
  542. # check if GSS-API authentication is enabled
  543. gss_auth = self.transport.server_object.enable_auth_gssapi()
  544. if method == "none":
  545. result = self.transport.server_object.check_auth_none(username)
  546. elif method == "password":
  547. changereq = m.get_boolean()
  548. password = m.get_binary()
  549. try:
  550. password = password.decode("UTF-8")
  551. except UnicodeError:
  552. # some clients/servers expect non-utf-8 passwords!
  553. # in this case, just return the raw byte string.
  554. pass
  555. if changereq:
  556. # always treated as failure, since we don't support changing
  557. # passwords, but collect the list of valid auth types from
  558. # the callback anyway
  559. self._log(DEBUG, "Auth request to change passwords (rejected)")
  560. newpassword = m.get_binary()
  561. try:
  562. newpassword = newpassword.decode("UTF-8", "replace")
  563. except UnicodeError:
  564. pass
  565. result = AUTH_FAILED
  566. else:
  567. result = self.transport.server_object.check_auth_password(
  568. username, password
  569. )
  570. elif method == "publickey":
  571. sig_attached = m.get_boolean()
  572. # NOTE: server never wants to guess a client's algo, they're
  573. # telling us directly. No need for _finalize_pubkey_algorithm
  574. # anywhere in this flow.
  575. algorithm = m.get_text()
  576. keyblob = m.get_binary()
  577. try:
  578. key = self._generate_key_from_request(algorithm, keyblob)
  579. except SSHException as e:
  580. self._log(INFO, "Auth rejected: public key: {}".format(str(e)))
  581. key = None
  582. except Exception as e:
  583. msg = (
  584. "Auth rejected: unsupported or mangled public key ({}: {})"
  585. ) # noqa
  586. self._log(INFO, msg.format(e.__class__.__name__, e))
  587. key = None
  588. if key is None:
  589. self._disconnect_no_more_auth()
  590. return
  591. # first check if this key is okay... if not, we can skip the verify
  592. result = self.transport.server_object.check_auth_publickey(
  593. username, key
  594. )
  595. if result != AUTH_FAILED:
  596. # key is okay, verify it
  597. if not sig_attached:
  598. # client wants to know if this key is acceptable, before it
  599. # signs anything... send special "ok" message
  600. m = Message()
  601. m.add_byte(cMSG_USERAUTH_PK_OK)
  602. m.add_string(algorithm)
  603. m.add_string(keyblob)
  604. self.transport._send_message(m)
  605. return
  606. sig = Message(m.get_binary())
  607. blob = self._get_session_blob(
  608. key, service, username, algorithm
  609. )
  610. if not key.verify_ssh_sig(blob, sig):
  611. self._log(INFO, "Auth rejected: invalid signature")
  612. result = AUTH_FAILED
  613. elif method == "keyboard-interactive":
  614. submethods = m.get_string()
  615. result = self.transport.server_object.check_auth_interactive(
  616. username, submethods
  617. )
  618. if isinstance(result, InteractiveQuery):
  619. # make interactive query instead of response
  620. self._interactive_query(result)
  621. return
  622. elif method == "gssapi-with-mic" and gss_auth:
  623. sshgss = GSSAuth(method)
  624. # Read the number of OID mechanisms supported by the client.
  625. # OpenSSH sends just one OID. It's the Kerveros V5 OID and that's
  626. # the only OID we support.
  627. mechs = m.get_int()
  628. # We can't accept more than one OID, so if the SSH client sends
  629. # more than one, disconnect.
  630. if mechs > 1:
  631. self._log(
  632. INFO,
  633. "Disconnect: Received more than one GSS-API OID mechanism",
  634. )
  635. self._disconnect_no_more_auth()
  636. desired_mech = m.get_string()
  637. mech_ok = sshgss.ssh_check_mech(desired_mech)
  638. # if we don't support the mechanism, disconnect.
  639. if not mech_ok:
  640. self._log(
  641. INFO,
  642. "Disconnect: Received an invalid GSS-API OID mechanism",
  643. )
  644. self._disconnect_no_more_auth()
  645. # send the Kerberos V5 GSSAPI OID to the client
  646. supported_mech = sshgss.ssh_gss_oids("server")
  647. # RFC 4462 says we are not required to implement GSS-API error
  648. # messages. See section 3.8 in http://www.ietf.org/rfc/rfc4462.txt
  649. m = Message()
  650. m.add_byte(cMSG_USERAUTH_GSSAPI_RESPONSE)
  651. m.add_bytes(supported_mech)
  652. self.transport.auth_handler = GssapiWithMicAuthHandler(
  653. self, sshgss
  654. )
  655. self.transport._expected_packet = (
  656. MSG_USERAUTH_GSSAPI_TOKEN,
  657. MSG_USERAUTH_REQUEST,
  658. MSG_SERVICE_REQUEST,
  659. )
  660. self.transport._send_message(m)
  661. return
  662. elif method == "gssapi-keyex" and gss_auth:
  663. mic_token = m.get_string()
  664. sshgss = self.transport.kexgss_ctxt
  665. if sshgss is None:
  666. # If there is no valid context, we reject the authentication
  667. result = AUTH_FAILED
  668. self._send_auth_result(username, method, result)
  669. try:
  670. sshgss.ssh_check_mic(
  671. mic_token, self.transport.session_id, self.auth_username
  672. )
  673. except Exception:
  674. result = AUTH_FAILED
  675. self._send_auth_result(username, method, result)
  676. raise
  677. result = AUTH_SUCCESSFUL
  678. self.transport.server_object.check_auth_gssapi_keyex(
  679. username, result
  680. )
  681. else:
  682. result = self.transport.server_object.check_auth_none(username)
  683. # okay, send result
  684. self._send_auth_result(username, method, result)
  685. def _parse_userauth_success(self, m):
  686. self._log(
  687. INFO, "Authentication ({}) successful!".format(self.auth_method)
  688. )
  689. self.authenticated = True
  690. self.transport._auth_trigger()
  691. if self.auth_event is not None:
  692. self.auth_event.set()
  693. def _parse_userauth_failure(self, m):
  694. authlist = m.get_list()
  695. partial = m.get_boolean()
  696. if partial:
  697. self._log(INFO, "Authentication continues...")
  698. self._log(DEBUG, "Methods: " + str(authlist))
  699. self.transport.saved_exception = PartialAuthentication(authlist)
  700. elif self.auth_method not in authlist:
  701. for msg in (
  702. "Authentication type ({}) not permitted.".format(
  703. self.auth_method
  704. ),
  705. "Allowed methods: {}".format(authlist),
  706. ):
  707. self._log(DEBUG, msg)
  708. self.transport.saved_exception = BadAuthenticationType(
  709. "Bad authentication type", authlist
  710. )
  711. else:
  712. self._log(
  713. INFO, "Authentication ({}) failed.".format(self.auth_method)
  714. )
  715. self.authenticated = False
  716. self.username = None
  717. if self.auth_event is not None:
  718. self.auth_event.set()
  719. def _parse_userauth_banner(self, m):
  720. banner = m.get_string()
  721. self.banner = banner
  722. self._log(INFO, "Auth banner: {}".format(banner))
  723. # who cares.
  724. def _parse_userauth_info_request(self, m):
  725. if self.auth_method != "keyboard-interactive":
  726. raise SSHException("Illegal info request from server")
  727. title = m.get_text()
  728. instructions = m.get_text()
  729. m.get_binary() # lang
  730. prompts = m.get_int()
  731. prompt_list = []
  732. for i in range(prompts):
  733. prompt_list.append((m.get_text(), m.get_boolean()))
  734. response_list = self.interactive_handler(
  735. title, instructions, prompt_list
  736. )
  737. m = Message()
  738. m.add_byte(cMSG_USERAUTH_INFO_RESPONSE)
  739. m.add_int(len(response_list))
  740. for r in response_list:
  741. m.add_string(r)
  742. self.transport._send_message(m)
  743. def _parse_userauth_info_response(self, m):
  744. if not self.transport.server_mode:
  745. raise SSHException("Illegal info response from server")
  746. n = m.get_int()
  747. responses = []
  748. for i in range(n):
  749. responses.append(m.get_text())
  750. result = self.transport.server_object.check_auth_interactive_response(
  751. responses
  752. )
  753. if isinstance(result, InteractiveQuery):
  754. # make interactive query instead of response
  755. self._interactive_query(result)
  756. return
  757. self._send_auth_result(
  758. self.auth_username, "keyboard-interactive", result
  759. )
  760. def _handle_local_gss_failure(self, e):
  761. self.transport.saved_exception = e
  762. self._log(DEBUG, "GSSAPI failure: {}".format(e))
  763. self._log(INFO, "Authentication ({}) failed.".format(self.auth_method))
  764. self.authenticated = False
  765. self.username = None
  766. if self.auth_event is not None:
  767. self.auth_event.set()
  768. return
  769. # TODO: do the same to the other tables, in Transport.
  770. # TODO 3.0: MAY make sense to make these tables into actual
  771. # classes/instances that can be fed a mode bool or whatever. Or,
  772. # alternately (both?) make the message types small classes or enums that
  773. # embed this info within themselves (which could also then tidy up the
  774. # current 'integer -> human readable short string' stuff in common.py).
  775. # TODO: if we do that, also expose 'em publicly.
  776. # Messages which should be handled _by_ servers (sent by clients)
  777. _server_handler_table = {
  778. MSG_SERVICE_REQUEST: _parse_service_request,
  779. MSG_USERAUTH_REQUEST: _parse_userauth_request,
  780. MSG_USERAUTH_INFO_RESPONSE: _parse_userauth_info_response,
  781. }
  782. # Messages which should be handled _by_ clients (sent by servers)
  783. _client_handler_table = {
  784. MSG_SERVICE_ACCEPT: _parse_service_accept,
  785. MSG_USERAUTH_SUCCESS: _parse_userauth_success,
  786. MSG_USERAUTH_FAILURE: _parse_userauth_failure,
  787. MSG_USERAUTH_BANNER: _parse_userauth_banner,
  788. MSG_USERAUTH_INFO_REQUEST: _parse_userauth_info_request,
  789. }
  790. # NOTE: prior to the fix for #1283, this was a static dict instead of a
  791. # property. Should be backwards compatible in most/all cases.
  792. @property
  793. def _handler_table(self):
  794. if self.transport.server_mode:
  795. return self._server_handler_table
  796. else:
  797. return self._client_handler_table
  798. class GssapiWithMicAuthHandler(object):
  799. """A specialized Auth handler for gssapi-with-mic
  800. During the GSSAPI token exchange we need a modified dispatch table,
  801. because the packet type numbers are not unique.
  802. """
  803. method = "gssapi-with-mic"
  804. def __init__(self, delegate, sshgss):
  805. self._delegate = delegate
  806. self.sshgss = sshgss
  807. def abort(self):
  808. self._restore_delegate_auth_handler()
  809. return self._delegate.abort()
  810. @property
  811. def transport(self):
  812. return self._delegate.transport
  813. @property
  814. def _send_auth_result(self):
  815. return self._delegate._send_auth_result
  816. @property
  817. def auth_username(self):
  818. return self._delegate.auth_username
  819. @property
  820. def gss_host(self):
  821. return self._delegate.gss_host
  822. def _restore_delegate_auth_handler(self):
  823. self.transport.auth_handler = self._delegate
  824. def _parse_userauth_gssapi_token(self, m):
  825. client_token = m.get_string()
  826. # use the client token as input to establish a secure
  827. # context.
  828. sshgss = self.sshgss
  829. try:
  830. token = sshgss.ssh_accept_sec_context(
  831. self.gss_host, client_token, self.auth_username
  832. )
  833. except Exception as e:
  834. self.transport.saved_exception = e
  835. result = AUTH_FAILED
  836. self._restore_delegate_auth_handler()
  837. self._send_auth_result(self.auth_username, self.method, result)
  838. raise
  839. if token is not None:
  840. m = Message()
  841. m.add_byte(cMSG_USERAUTH_GSSAPI_TOKEN)
  842. m.add_string(token)
  843. self.transport._expected_packet = (
  844. MSG_USERAUTH_GSSAPI_TOKEN,
  845. MSG_USERAUTH_GSSAPI_MIC,
  846. MSG_USERAUTH_REQUEST,
  847. )
  848. self.transport._send_message(m)
  849. def _parse_userauth_gssapi_mic(self, m):
  850. mic_token = m.get_string()
  851. sshgss = self.sshgss
  852. username = self.auth_username
  853. self._restore_delegate_auth_handler()
  854. try:
  855. sshgss.ssh_check_mic(
  856. mic_token, self.transport.session_id, username
  857. )
  858. except Exception as e:
  859. self.transport.saved_exception = e
  860. result = AUTH_FAILED
  861. self._send_auth_result(username, self.method, result)
  862. raise
  863. # TODO: Implement client credential saving.
  864. # The OpenSSH server is able to create a TGT with the delegated
  865. # client credentials, but this is not supported by GSS-API.
  866. result = AUTH_SUCCESSFUL
  867. self.transport.server_object.check_auth_gssapi_with_mic(
  868. username, result
  869. )
  870. # okay, send result
  871. self._send_auth_result(username, self.method, result)
  872. def _parse_service_request(self, m):
  873. self._restore_delegate_auth_handler()
  874. return self._delegate._parse_service_request(m)
  875. def _parse_userauth_request(self, m):
  876. self._restore_delegate_auth_handler()
  877. return self._delegate._parse_userauth_request(m)
  878. __handler_table = {
  879. MSG_SERVICE_REQUEST: _parse_service_request,
  880. MSG_USERAUTH_REQUEST: _parse_userauth_request,
  881. MSG_USERAUTH_GSSAPI_TOKEN: _parse_userauth_gssapi_token,
  882. MSG_USERAUTH_GSSAPI_MIC: _parse_userauth_gssapi_mic,
  883. }
  884. @property
  885. def _handler_table(self):
  886. # TODO: determine if we can cut this up like we did for the primary
  887. # AuthHandler class.
  888. return self.__handler_table