ssh_gss.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778
  1. # Copyright (C) 2013-2014 science + computing ag
  2. # Author: Sebastian Deiss <sebastian.deiss@t-online.de>
  3. #
  4. #
  5. # This file is part of paramiko.
  6. #
  7. # Paramiko is free software; you can redistribute it and/or modify it under the
  8. # terms of the GNU Lesser General Public License as published by the Free
  9. # Software Foundation; either version 2.1 of the License, or (at your option)
  10. # any later version.
  11. #
  12. # Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY
  13. # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  14. # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
  15. # details.
  16. #
  17. # You should have received a copy of the GNU Lesser General Public License
  18. # along with Paramiko; if not, write to the Free Software Foundation, Inc.,
  19. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  20. """
  21. This module provides GSS-API / SSPI authentication as defined in :rfc:`4462`.
  22. .. note:: Credential delegation is not supported in server mode.
  23. .. seealso:: :doc:`/api/kex_gss`
  24. .. versionadded:: 1.15
  25. """
  26. import struct
  27. import os
  28. import sys
  29. #: A boolean constraint that indicates if GSS-API / SSPI is available.
  30. GSS_AUTH_AVAILABLE = True
  31. #: A tuple of the exception types used by the underlying GSSAPI implementation.
  32. GSS_EXCEPTIONS = ()
  33. #: :var str _API: Constraint for the used API
  34. _API = None
  35. try:
  36. import gssapi
  37. if hasattr(gssapi, "__title__") and gssapi.__title__ == "python-gssapi":
  38. # old, unmaintained python-gssapi package
  39. _API = "MIT" # keep this for compatibility
  40. GSS_EXCEPTIONS = (gssapi.GSSException,)
  41. else:
  42. _API = "PYTHON-GSSAPI-NEW"
  43. GSS_EXCEPTIONS = (
  44. gssapi.exceptions.GeneralError,
  45. gssapi.raw.misc.GSSError,
  46. )
  47. except (ImportError, OSError):
  48. try:
  49. import pywintypes
  50. import sspicon
  51. import sspi
  52. _API = "SSPI"
  53. GSS_EXCEPTIONS = (pywintypes.error,)
  54. except ImportError:
  55. GSS_AUTH_AVAILABLE = False
  56. _API = None
  57. from paramiko.common import MSG_USERAUTH_REQUEST
  58. from paramiko.ssh_exception import SSHException
  59. from paramiko._version import __version_info__
  60. def GSSAuth(auth_method, gss_deleg_creds=True):
  61. """
  62. Provide SSH2 GSS-API / SSPI authentication.
  63. :param str auth_method: The name of the SSH authentication mechanism
  64. (gssapi-with-mic or gss-keyex)
  65. :param bool gss_deleg_creds: Delegate client credentials or not.
  66. We delegate credentials by default.
  67. :return: Either an `._SSH_GSSAPI_OLD` or `._SSH_GSSAPI_NEW` (Unix)
  68. object or an `_SSH_SSPI` (Windows) object
  69. :rtype: object
  70. :raises: ``ImportError`` -- If no GSS-API / SSPI module could be imported.
  71. :see: `RFC 4462 <http://www.ietf.org/rfc/rfc4462.txt>`_
  72. :note: Check for the available API and return either an `._SSH_GSSAPI_OLD`
  73. (MIT GSSAPI using python-gssapi package) object, an
  74. `._SSH_GSSAPI_NEW` (MIT GSSAPI using gssapi package) object
  75. or an `._SSH_SSPI` (MS SSPI) object.
  76. If there is no supported API available,
  77. ``None`` will be returned.
  78. """
  79. if _API == "MIT":
  80. return _SSH_GSSAPI_OLD(auth_method, gss_deleg_creds)
  81. elif _API == "PYTHON-GSSAPI-NEW":
  82. return _SSH_GSSAPI_NEW(auth_method, gss_deleg_creds)
  83. elif _API == "SSPI" and os.name == "nt":
  84. return _SSH_SSPI(auth_method, gss_deleg_creds)
  85. else:
  86. raise ImportError("Unable to import a GSS-API / SSPI module!")
  87. class _SSH_GSSAuth(object):
  88. """
  89. Contains the shared variables and methods of `._SSH_GSSAPI_OLD`,
  90. `._SSH_GSSAPI_NEW` and `._SSH_SSPI`.
  91. """
  92. def __init__(self, auth_method, gss_deleg_creds):
  93. """
  94. :param str auth_method: The name of the SSH authentication mechanism
  95. (gssapi-with-mic or gss-keyex)
  96. :param bool gss_deleg_creds: Delegate client credentials or not
  97. """
  98. self._auth_method = auth_method
  99. self._gss_deleg_creds = gss_deleg_creds
  100. self._gss_host = None
  101. self._username = None
  102. self._session_id = None
  103. self._service = "ssh-connection"
  104. """
  105. OpenSSH supports Kerberos V5 mechanism only for GSS-API authentication,
  106. so we also support the krb5 mechanism only.
  107. """
  108. self._krb5_mech = "1.2.840.113554.1.2.2"
  109. # client mode
  110. self._gss_ctxt = None
  111. self._gss_ctxt_status = False
  112. # server mode
  113. self._gss_srv_ctxt = None
  114. self._gss_srv_ctxt_status = False
  115. self.cc_file = None
  116. def set_service(self, service):
  117. """
  118. This is just a setter to use a non default service.
  119. I added this method, because RFC 4462 doesn't specify "ssh-connection"
  120. as the only service value.
  121. :param str service: The desired SSH service
  122. """
  123. if service.find("ssh-"):
  124. self._service = service
  125. def set_username(self, username):
  126. """
  127. Setter for C{username}. If GSS-API Key Exchange is performed, the
  128. username is not set by C{ssh_init_sec_context}.
  129. :param str username: The name of the user who attempts to login
  130. """
  131. self._username = username
  132. def ssh_gss_oids(self, mode="client"):
  133. """
  134. This method returns a single OID, because we only support the
  135. Kerberos V5 mechanism.
  136. :param str mode: Client for client mode and server for server mode
  137. :return: A byte sequence containing the number of supported
  138. OIDs, the length of the OID and the actual OID encoded with
  139. DER
  140. :note: In server mode we just return the OID length and the DER encoded
  141. OID.
  142. """
  143. from pyasn1.type.univ import ObjectIdentifier
  144. from pyasn1.codec.der import encoder
  145. OIDs = self._make_uint32(1)
  146. krb5_OID = encoder.encode(ObjectIdentifier(self._krb5_mech))
  147. OID_len = self._make_uint32(len(krb5_OID))
  148. if mode == "server":
  149. return OID_len + krb5_OID
  150. return OIDs + OID_len + krb5_OID
  151. def ssh_check_mech(self, desired_mech):
  152. """
  153. Check if the given OID is the Kerberos V5 OID (server mode).
  154. :param str desired_mech: The desired GSS-API mechanism of the client
  155. :return: ``True`` if the given OID is supported, otherwise C{False}
  156. """
  157. from pyasn1.codec.der import decoder
  158. mech, __ = decoder.decode(desired_mech)
  159. if mech.__str__() != self._krb5_mech:
  160. return False
  161. return True
  162. # Internals
  163. # -------------------------------------------------------------------------
  164. def _make_uint32(self, integer):
  165. """
  166. Create a 32 bit unsigned integer (The byte sequence of an integer).
  167. :param int integer: The integer value to convert
  168. :return: The byte sequence of an 32 bit integer
  169. """
  170. return struct.pack("!I", integer)
  171. def _ssh_build_mic(self, session_id, username, service, auth_method):
  172. """
  173. Create the SSH2 MIC filed for gssapi-with-mic.
  174. :param str session_id: The SSH session ID
  175. :param str username: The name of the user who attempts to login
  176. :param str service: The requested SSH service
  177. :param str auth_method: The requested SSH authentication mechanism
  178. :return: The MIC as defined in RFC 4462. The contents of the
  179. MIC field are:
  180. string session_identifier,
  181. byte SSH_MSG_USERAUTH_REQUEST,
  182. string user-name,
  183. string service (ssh-connection),
  184. string authentication-method
  185. (gssapi-with-mic or gssapi-keyex)
  186. """
  187. mic = self._make_uint32(len(session_id))
  188. mic += session_id
  189. mic += struct.pack("B", MSG_USERAUTH_REQUEST)
  190. mic += self._make_uint32(len(username))
  191. mic += username.encode()
  192. mic += self._make_uint32(len(service))
  193. mic += service.encode()
  194. mic += self._make_uint32(len(auth_method))
  195. mic += auth_method.encode()
  196. return mic
  197. class _SSH_GSSAPI_OLD(_SSH_GSSAuth):
  198. """
  199. Implementation of the GSS-API MIT Kerberos Authentication for SSH2,
  200. using the older (unmaintained) python-gssapi package.
  201. :see: `.GSSAuth`
  202. """
  203. def __init__(self, auth_method, gss_deleg_creds):
  204. """
  205. :param str auth_method: The name of the SSH authentication mechanism
  206. (gssapi-with-mic or gss-keyex)
  207. :param bool gss_deleg_creds: Delegate client credentials or not
  208. """
  209. _SSH_GSSAuth.__init__(self, auth_method, gss_deleg_creds)
  210. if self._gss_deleg_creds:
  211. self._gss_flags = (
  212. gssapi.C_PROT_READY_FLAG,
  213. gssapi.C_INTEG_FLAG,
  214. gssapi.C_MUTUAL_FLAG,
  215. gssapi.C_DELEG_FLAG,
  216. )
  217. else:
  218. self._gss_flags = (
  219. gssapi.C_PROT_READY_FLAG,
  220. gssapi.C_INTEG_FLAG,
  221. gssapi.C_MUTUAL_FLAG,
  222. )
  223. def ssh_init_sec_context(
  224. self, target, desired_mech=None, username=None, recv_token=None
  225. ):
  226. """
  227. Initialize a GSS-API context.
  228. :param str username: The name of the user who attempts to login
  229. :param str target: The hostname of the target to connect to
  230. :param str desired_mech: The negotiated GSS-API mechanism
  231. ("pseudo negotiated" mechanism, because we
  232. support just the krb5 mechanism :-))
  233. :param str recv_token: The GSS-API token received from the Server
  234. :raises:
  235. `.SSHException` -- Is raised if the desired mechanism of the client
  236. is not supported
  237. :return: A ``String`` if the GSS-API has returned a token or
  238. ``None`` if no token was returned
  239. """
  240. from pyasn1.codec.der import decoder
  241. self._username = username
  242. self._gss_host = target
  243. targ_name = gssapi.Name(
  244. "host@" + self._gss_host, gssapi.C_NT_HOSTBASED_SERVICE
  245. )
  246. ctx = gssapi.Context()
  247. ctx.flags = self._gss_flags
  248. if desired_mech is None:
  249. krb5_mech = gssapi.OID.mech_from_string(self._krb5_mech)
  250. else:
  251. mech, __ = decoder.decode(desired_mech)
  252. if mech.__str__() != self._krb5_mech:
  253. raise SSHException("Unsupported mechanism OID.")
  254. else:
  255. krb5_mech = gssapi.OID.mech_from_string(self._krb5_mech)
  256. token = None
  257. try:
  258. if recv_token is None:
  259. self._gss_ctxt = gssapi.InitContext(
  260. peer_name=targ_name,
  261. mech_type=krb5_mech,
  262. req_flags=ctx.flags,
  263. )
  264. token = self._gss_ctxt.step(token)
  265. else:
  266. token = self._gss_ctxt.step(recv_token)
  267. except gssapi.GSSException:
  268. message = "{} Target: {}".format(sys.exc_info()[1], self._gss_host)
  269. raise gssapi.GSSException(message)
  270. self._gss_ctxt_status = self._gss_ctxt.established
  271. return token
  272. def ssh_get_mic(self, session_id, gss_kex=False):
  273. """
  274. Create the MIC token for a SSH2 message.
  275. :param str session_id: The SSH session ID
  276. :param bool gss_kex: Generate the MIC for GSS-API Key Exchange or not
  277. :return: gssapi-with-mic:
  278. Returns the MIC token from GSS-API for the message we created
  279. with ``_ssh_build_mic``.
  280. gssapi-keyex:
  281. Returns the MIC token from GSS-API with the SSH session ID as
  282. message.
  283. """
  284. self._session_id = session_id
  285. if not gss_kex:
  286. mic_field = self._ssh_build_mic(
  287. self._session_id,
  288. self._username,
  289. self._service,
  290. self._auth_method,
  291. )
  292. mic_token = self._gss_ctxt.get_mic(mic_field)
  293. else:
  294. # for key exchange with gssapi-keyex
  295. mic_token = self._gss_srv_ctxt.get_mic(self._session_id)
  296. return mic_token
  297. def ssh_accept_sec_context(self, hostname, recv_token, username=None):
  298. """
  299. Accept a GSS-API context (server mode).
  300. :param str hostname: The servers hostname
  301. :param str username: The name of the user who attempts to login
  302. :param str recv_token: The GSS-API Token received from the server,
  303. if it's not the initial call.
  304. :return: A ``String`` if the GSS-API has returned a token or ``None``
  305. if no token was returned
  306. """
  307. # hostname and username are not required for GSSAPI, but for SSPI
  308. self._gss_host = hostname
  309. self._username = username
  310. if self._gss_srv_ctxt is None:
  311. self._gss_srv_ctxt = gssapi.AcceptContext()
  312. token = self._gss_srv_ctxt.step(recv_token)
  313. self._gss_srv_ctxt_status = self._gss_srv_ctxt.established
  314. return token
  315. def ssh_check_mic(self, mic_token, session_id, username=None):
  316. """
  317. Verify the MIC token for a SSH2 message.
  318. :param str mic_token: The MIC token received from the client
  319. :param str session_id: The SSH session ID
  320. :param str username: The name of the user who attempts to login
  321. :return: None if the MIC check was successful
  322. :raises: ``gssapi.GSSException`` -- if the MIC check failed
  323. """
  324. self._session_id = session_id
  325. self._username = username
  326. if self._username is not None:
  327. # server mode
  328. mic_field = self._ssh_build_mic(
  329. self._session_id,
  330. self._username,
  331. self._service,
  332. self._auth_method,
  333. )
  334. self._gss_srv_ctxt.verify_mic(mic_field, mic_token)
  335. else:
  336. # for key exchange with gssapi-keyex
  337. # client mode
  338. self._gss_ctxt.verify_mic(self._session_id, mic_token)
  339. @property
  340. def credentials_delegated(self):
  341. """
  342. Checks if credentials are delegated (server mode).
  343. :return: ``True`` if credentials are delegated, otherwise ``False``
  344. """
  345. if self._gss_srv_ctxt.delegated_cred is not None:
  346. return True
  347. return False
  348. def save_client_creds(self, client_token):
  349. """
  350. Save the Client token in a file. This is used by the SSH server
  351. to store the client credentials if credentials are delegated
  352. (server mode).
  353. :param str client_token: The GSS-API token received form the client
  354. :raises:
  355. ``NotImplementedError`` -- Credential delegation is currently not
  356. supported in server mode
  357. """
  358. raise NotImplementedError
  359. if __version_info__ < (2, 5):
  360. # provide the old name for strict backward compatibility
  361. _SSH_GSSAPI = _SSH_GSSAPI_OLD
  362. class _SSH_GSSAPI_NEW(_SSH_GSSAuth):
  363. """
  364. Implementation of the GSS-API MIT Kerberos Authentication for SSH2,
  365. using the newer, currently maintained gssapi package.
  366. :see: `.GSSAuth`
  367. """
  368. def __init__(self, auth_method, gss_deleg_creds):
  369. """
  370. :param str auth_method: The name of the SSH authentication mechanism
  371. (gssapi-with-mic or gss-keyex)
  372. :param bool gss_deleg_creds: Delegate client credentials or not
  373. """
  374. _SSH_GSSAuth.__init__(self, auth_method, gss_deleg_creds)
  375. if self._gss_deleg_creds:
  376. self._gss_flags = (
  377. gssapi.RequirementFlag.protection_ready,
  378. gssapi.RequirementFlag.integrity,
  379. gssapi.RequirementFlag.mutual_authentication,
  380. gssapi.RequirementFlag.delegate_to_peer,
  381. )
  382. else:
  383. self._gss_flags = (
  384. gssapi.RequirementFlag.protection_ready,
  385. gssapi.RequirementFlag.integrity,
  386. gssapi.RequirementFlag.mutual_authentication,
  387. )
  388. def ssh_init_sec_context(
  389. self, target, desired_mech=None, username=None, recv_token=None
  390. ):
  391. """
  392. Initialize a GSS-API context.
  393. :param str username: The name of the user who attempts to login
  394. :param str target: The hostname of the target to connect to
  395. :param str desired_mech: The negotiated GSS-API mechanism
  396. ("pseudo negotiated" mechanism, because we
  397. support just the krb5 mechanism :-))
  398. :param str recv_token: The GSS-API token received from the Server
  399. :raises: `.SSHException` -- Is raised if the desired mechanism of the
  400. client is not supported
  401. :raises: ``gssapi.exceptions.GSSError`` if there is an error signaled
  402. by the GSS-API implementation
  403. :return: A ``String`` if the GSS-API has returned a token or ``None``
  404. if no token was returned
  405. """
  406. from pyasn1.codec.der import decoder
  407. self._username = username
  408. self._gss_host = target
  409. targ_name = gssapi.Name(
  410. "host@" + self._gss_host,
  411. name_type=gssapi.NameType.hostbased_service,
  412. )
  413. if desired_mech is not None:
  414. mech, __ = decoder.decode(desired_mech)
  415. if mech.__str__() != self._krb5_mech:
  416. raise SSHException("Unsupported mechanism OID.")
  417. krb5_mech = gssapi.MechType.kerberos
  418. token = None
  419. if recv_token is None:
  420. self._gss_ctxt = gssapi.SecurityContext(
  421. name=targ_name,
  422. flags=self._gss_flags,
  423. mech=krb5_mech,
  424. usage="initiate",
  425. )
  426. token = self._gss_ctxt.step(token)
  427. else:
  428. token = self._gss_ctxt.step(recv_token)
  429. self._gss_ctxt_status = self._gss_ctxt.complete
  430. return token
  431. def ssh_get_mic(self, session_id, gss_kex=False):
  432. """
  433. Create the MIC token for a SSH2 message.
  434. :param str session_id: The SSH session ID
  435. :param bool gss_kex: Generate the MIC for GSS-API Key Exchange or not
  436. :return: gssapi-with-mic:
  437. Returns the MIC token from GSS-API for the message we created
  438. with ``_ssh_build_mic``.
  439. gssapi-keyex:
  440. Returns the MIC token from GSS-API with the SSH session ID as
  441. message.
  442. :rtype: str
  443. """
  444. self._session_id = session_id
  445. if not gss_kex:
  446. mic_field = self._ssh_build_mic(
  447. self._session_id,
  448. self._username,
  449. self._service,
  450. self._auth_method,
  451. )
  452. mic_token = self._gss_ctxt.get_signature(mic_field)
  453. else:
  454. # for key exchange with gssapi-keyex
  455. mic_token = self._gss_srv_ctxt.get_signature(self._session_id)
  456. return mic_token
  457. def ssh_accept_sec_context(self, hostname, recv_token, username=None):
  458. """
  459. Accept a GSS-API context (server mode).
  460. :param str hostname: The servers hostname
  461. :param str username: The name of the user who attempts to login
  462. :param str recv_token: The GSS-API Token received from the server,
  463. if it's not the initial call.
  464. :return: A ``String`` if the GSS-API has returned a token or ``None``
  465. if no token was returned
  466. """
  467. # hostname and username are not required for GSSAPI, but for SSPI
  468. self._gss_host = hostname
  469. self._username = username
  470. if self._gss_srv_ctxt is None:
  471. self._gss_srv_ctxt = gssapi.SecurityContext(usage="accept")
  472. token = self._gss_srv_ctxt.step(recv_token)
  473. self._gss_srv_ctxt_status = self._gss_srv_ctxt.complete
  474. return token
  475. def ssh_check_mic(self, mic_token, session_id, username=None):
  476. """
  477. Verify the MIC token for a SSH2 message.
  478. :param str mic_token: The MIC token received from the client
  479. :param str session_id: The SSH session ID
  480. :param str username: The name of the user who attempts to login
  481. :return: None if the MIC check was successful
  482. :raises: ``gssapi.exceptions.GSSError`` -- if the MIC check failed
  483. """
  484. self._session_id = session_id
  485. self._username = username
  486. if self._username is not None:
  487. # server mode
  488. mic_field = self._ssh_build_mic(
  489. self._session_id,
  490. self._username,
  491. self._service,
  492. self._auth_method,
  493. )
  494. self._gss_srv_ctxt.verify_signature(mic_field, mic_token)
  495. else:
  496. # for key exchange with gssapi-keyex
  497. # client mode
  498. self._gss_ctxt.verify_signature(self._session_id, mic_token)
  499. @property
  500. def credentials_delegated(self):
  501. """
  502. Checks if credentials are delegated (server mode).
  503. :return: ``True`` if credentials are delegated, otherwise ``False``
  504. :rtype: bool
  505. """
  506. if self._gss_srv_ctxt.delegated_creds is not None:
  507. return True
  508. return False
  509. def save_client_creds(self, client_token):
  510. """
  511. Save the Client token in a file. This is used by the SSH server
  512. to store the client credentials if credentials are delegated
  513. (server mode).
  514. :param str client_token: The GSS-API token received form the client
  515. :raises: ``NotImplementedError`` -- Credential delegation is currently
  516. not supported in server mode
  517. """
  518. raise NotImplementedError
  519. class _SSH_SSPI(_SSH_GSSAuth):
  520. """
  521. Implementation of the Microsoft SSPI Kerberos Authentication for SSH2.
  522. :see: `.GSSAuth`
  523. """
  524. def __init__(self, auth_method, gss_deleg_creds):
  525. """
  526. :param str auth_method: The name of the SSH authentication mechanism
  527. (gssapi-with-mic or gss-keyex)
  528. :param bool gss_deleg_creds: Delegate client credentials or not
  529. """
  530. _SSH_GSSAuth.__init__(self, auth_method, gss_deleg_creds)
  531. if self._gss_deleg_creds:
  532. self._gss_flags = (
  533. sspicon.ISC_REQ_INTEGRITY
  534. | sspicon.ISC_REQ_MUTUAL_AUTH
  535. | sspicon.ISC_REQ_DELEGATE
  536. )
  537. else:
  538. self._gss_flags = (
  539. sspicon.ISC_REQ_INTEGRITY | sspicon.ISC_REQ_MUTUAL_AUTH
  540. )
  541. def ssh_init_sec_context(
  542. self, target, desired_mech=None, username=None, recv_token=None
  543. ):
  544. """
  545. Initialize a SSPI context.
  546. :param str username: The name of the user who attempts to login
  547. :param str target: The FQDN of the target to connect to
  548. :param str desired_mech: The negotiated SSPI mechanism
  549. ("pseudo negotiated" mechanism, because we
  550. support just the krb5 mechanism :-))
  551. :param recv_token: The SSPI token received from the Server
  552. :raises:
  553. `.SSHException` -- Is raised if the desired mechanism of the client
  554. is not supported
  555. :return: A ``String`` if the SSPI has returned a token or ``None`` if
  556. no token was returned
  557. """
  558. from pyasn1.codec.der import decoder
  559. self._username = username
  560. self._gss_host = target
  561. error = 0
  562. targ_name = "host/" + self._gss_host
  563. if desired_mech is not None:
  564. mech, __ = decoder.decode(desired_mech)
  565. if mech.__str__() != self._krb5_mech:
  566. raise SSHException("Unsupported mechanism OID.")
  567. try:
  568. if recv_token is None:
  569. self._gss_ctxt = sspi.ClientAuth(
  570. "Kerberos", scflags=self._gss_flags, targetspn=targ_name
  571. )
  572. error, token = self._gss_ctxt.authorize(recv_token)
  573. token = token[0].Buffer
  574. except pywintypes.error as e:
  575. e.strerror += ", Target: {}".format(self._gss_host)
  576. raise
  577. if error == 0:
  578. """
  579. if the status is GSS_COMPLETE (error = 0) the context is fully
  580. established an we can set _gss_ctxt_status to True.
  581. """
  582. self._gss_ctxt_status = True
  583. token = None
  584. """
  585. You won't get another token if the context is fully established,
  586. so i set token to None instead of ""
  587. """
  588. return token
  589. def ssh_get_mic(self, session_id, gss_kex=False):
  590. """
  591. Create the MIC token for a SSH2 message.
  592. :param str session_id: The SSH session ID
  593. :param bool gss_kex: Generate the MIC for Key Exchange with SSPI or not
  594. :return: gssapi-with-mic:
  595. Returns the MIC token from SSPI for the message we created
  596. with ``_ssh_build_mic``.
  597. gssapi-keyex:
  598. Returns the MIC token from SSPI with the SSH session ID as
  599. message.
  600. """
  601. self._session_id = session_id
  602. if not gss_kex:
  603. mic_field = self._ssh_build_mic(
  604. self._session_id,
  605. self._username,
  606. self._service,
  607. self._auth_method,
  608. )
  609. mic_token = self._gss_ctxt.sign(mic_field)
  610. else:
  611. # for key exchange with gssapi-keyex
  612. mic_token = self._gss_srv_ctxt.sign(self._session_id)
  613. return mic_token
  614. def ssh_accept_sec_context(self, hostname, username, recv_token):
  615. """
  616. Accept a SSPI context (server mode).
  617. :param str hostname: The servers FQDN
  618. :param str username: The name of the user who attempts to login
  619. :param str recv_token: The SSPI Token received from the server,
  620. if it's not the initial call.
  621. :return: A ``String`` if the SSPI has returned a token or ``None`` if
  622. no token was returned
  623. """
  624. self._gss_host = hostname
  625. self._username = username
  626. targ_name = "host/" + self._gss_host
  627. self._gss_srv_ctxt = sspi.ServerAuth("Kerberos", spn=targ_name)
  628. error, token = self._gss_srv_ctxt.authorize(recv_token)
  629. token = token[0].Buffer
  630. if error == 0:
  631. self._gss_srv_ctxt_status = True
  632. token = None
  633. return token
  634. def ssh_check_mic(self, mic_token, session_id, username=None):
  635. """
  636. Verify the MIC token for a SSH2 message.
  637. :param str mic_token: The MIC token received from the client
  638. :param str session_id: The SSH session ID
  639. :param str username: The name of the user who attempts to login
  640. :return: None if the MIC check was successful
  641. :raises: ``sspi.error`` -- if the MIC check failed
  642. """
  643. self._session_id = session_id
  644. self._username = username
  645. if username is not None:
  646. # server mode
  647. mic_field = self._ssh_build_mic(
  648. self._session_id,
  649. self._username,
  650. self._service,
  651. self._auth_method,
  652. )
  653. # Verifies data and its signature. If verification fails, an
  654. # sspi.error will be raised.
  655. self._gss_srv_ctxt.verify(mic_field, mic_token)
  656. else:
  657. # for key exchange with gssapi-keyex
  658. # client mode
  659. # Verifies data and its signature. If verification fails, an
  660. # sspi.error will be raised.
  661. self._gss_ctxt.verify(self._session_id, mic_token)
  662. @property
  663. def credentials_delegated(self):
  664. """
  665. Checks if credentials are delegated (server mode).
  666. :return: ``True`` if credentials are delegated, otherwise ``False``
  667. """
  668. return self._gss_flags & sspicon.ISC_REQ_DELEGATE and (
  669. self._gss_srv_ctxt_status or self._gss_flags
  670. )
  671. def save_client_creds(self, client_token):
  672. """
  673. Save the Client token in a file. This is used by the SSH server
  674. to store the client credentails if credentials are delegated
  675. (server mode).
  676. :param str client_token: The SSPI token received form the client
  677. :raises:
  678. ``NotImplementedError`` -- Credential delegation is currently not
  679. supported in server mode
  680. """
  681. raise NotImplementedError