Coverage for /pythoncovmergedfiles/medio/medio/src/paramiko/paramiko/auth_handler.py: 12%

564 statements  

« prev     ^ index     » next       coverage.py v7.2.2, created at 2023-03-26 06:36 +0000

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""" 

20`.AuthHandler` 

21""" 

22 

23import weakref 

24import time 

25import re 

26 

27from paramiko.common import ( 

28 cMSG_SERVICE_REQUEST, 

29 cMSG_DISCONNECT, 

30 DISCONNECT_SERVICE_NOT_AVAILABLE, 

31 DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE, 

32 cMSG_USERAUTH_REQUEST, 

33 cMSG_SERVICE_ACCEPT, 

34 DEBUG, 

35 AUTH_SUCCESSFUL, 

36 INFO, 

37 cMSG_USERAUTH_SUCCESS, 

38 cMSG_USERAUTH_FAILURE, 

39 AUTH_PARTIALLY_SUCCESSFUL, 

40 cMSG_USERAUTH_INFO_REQUEST, 

41 WARNING, 

42 AUTH_FAILED, 

43 cMSG_USERAUTH_PK_OK, 

44 cMSG_USERAUTH_INFO_RESPONSE, 

45 MSG_SERVICE_REQUEST, 

46 MSG_SERVICE_ACCEPT, 

47 MSG_USERAUTH_REQUEST, 

48 MSG_USERAUTH_SUCCESS, 

49 MSG_USERAUTH_FAILURE, 

50 MSG_USERAUTH_BANNER, 

51 MSG_USERAUTH_INFO_REQUEST, 

52 MSG_USERAUTH_INFO_RESPONSE, 

53 cMSG_USERAUTH_GSSAPI_RESPONSE, 

54 cMSG_USERAUTH_GSSAPI_TOKEN, 

55 cMSG_USERAUTH_GSSAPI_MIC, 

56 MSG_USERAUTH_GSSAPI_RESPONSE, 

57 MSG_USERAUTH_GSSAPI_TOKEN, 

58 MSG_USERAUTH_GSSAPI_ERROR, 

59 MSG_USERAUTH_GSSAPI_ERRTOK, 

60 MSG_USERAUTH_GSSAPI_MIC, 

61 MSG_NAMES, 

62 cMSG_USERAUTH_BANNER, 

63) 

64from paramiko.message import Message 

65from paramiko.util import b, u 

66from paramiko.ssh_exception import ( 

67 SSHException, 

68 AuthenticationException, 

69 BadAuthenticationType, 

70 PartialAuthentication, 

71) 

72from paramiko.server import InteractiveQuery 

73from paramiko.ssh_gss import GSSAuth, GSS_EXCEPTIONS 

74 

75 

76class AuthHandler: 

77 """ 

78 Internal class to handle the mechanics of authentication. 

79 """ 

80 

81 def __init__(self, transport): 

82 self.transport = weakref.proxy(transport) 

83 self.username = None 

84 self.authenticated = False 

85 self.auth_event = None 

86 self.auth_method = "" 

87 self.banner = None 

88 self.password = None 

89 self.private_key = None 

90 self.interactive_handler = None 

91 self.submethods = None 

92 # for server mode: 

93 self.auth_username = None 

94 self.auth_fail_count = 0 

95 # for GSSAPI 

96 self.gss_host = None 

97 self.gss_deleg_creds = True 

98 

99 def _log(self, *args): 

100 return self.transport._log(*args) 

101 

102 def is_authenticated(self): 

103 return self.authenticated 

104 

105 def get_username(self): 

106 if self.transport.server_mode: 

107 return self.auth_username 

108 else: 

109 return self.username 

110 

111 def auth_none(self, username, event): 

112 self.transport.lock.acquire() 

113 try: 

114 self.auth_event = event 

115 self.auth_method = "none" 

116 self.username = username 

117 self._request_auth() 

118 finally: 

119 self.transport.lock.release() 

120 

121 def auth_publickey(self, username, key, event): 

122 self.transport.lock.acquire() 

123 try: 

124 self.auth_event = event 

125 self.auth_method = "publickey" 

126 self.username = username 

127 self.private_key = key 

128 self._request_auth() 

129 finally: 

130 self.transport.lock.release() 

131 

132 def auth_password(self, username, password, event): 

133 self.transport.lock.acquire() 

134 try: 

135 self.auth_event = event 

136 self.auth_method = "password" 

137 self.username = username 

138 self.password = password 

139 self._request_auth() 

140 finally: 

141 self.transport.lock.release() 

142 

143 def auth_interactive(self, username, handler, event, submethods=""): 

144 """ 

145 response_list = handler(title, instructions, prompt_list) 

146 """ 

147 self.transport.lock.acquire() 

148 try: 

149 self.auth_event = event 

150 self.auth_method = "keyboard-interactive" 

151 self.username = username 

152 self.interactive_handler = handler 

153 self.submethods = submethods 

154 self._request_auth() 

155 finally: 

156 self.transport.lock.release() 

157 

158 def auth_gssapi_with_mic(self, username, gss_host, gss_deleg_creds, event): 

159 self.transport.lock.acquire() 

160 try: 

161 self.auth_event = event 

162 self.auth_method = "gssapi-with-mic" 

163 self.username = username 

164 self.gss_host = gss_host 

165 self.gss_deleg_creds = gss_deleg_creds 

166 self._request_auth() 

167 finally: 

168 self.transport.lock.release() 

169 

170 def auth_gssapi_keyex(self, username, event): 

171 self.transport.lock.acquire() 

172 try: 

173 self.auth_event = event 

174 self.auth_method = "gssapi-keyex" 

175 self.username = username 

176 self._request_auth() 

177 finally: 

178 self.transport.lock.release() 

179 

180 def abort(self): 

181 if self.auth_event is not None: 

182 self.auth_event.set() 

183 

184 # ...internals... 

185 

186 def _request_auth(self): 

187 m = Message() 

188 m.add_byte(cMSG_SERVICE_REQUEST) 

189 m.add_string("ssh-userauth") 

190 self.transport._send_message(m) 

191 

192 def _disconnect_service_not_available(self): 

193 m = Message() 

194 m.add_byte(cMSG_DISCONNECT) 

195 m.add_int(DISCONNECT_SERVICE_NOT_AVAILABLE) 

196 m.add_string("Service not available") 

197 m.add_string("en") 

198 self.transport._send_message(m) 

199 self.transport.close() 

200 

201 def _disconnect_no_more_auth(self): 

202 m = Message() 

203 m.add_byte(cMSG_DISCONNECT) 

204 m.add_int(DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE) 

205 m.add_string("No more auth methods available") 

206 m.add_string("en") 

207 self.transport._send_message(m) 

208 self.transport.close() 

209 

210 def _get_key_type_and_bits(self, key): 

211 """ 

212 Given any key, return its type/algorithm & bits-to-sign. 

213 

214 Intended for input to or verification of, key signatures. 

215 """ 

216 # Use certificate contents, if available, plain pubkey otherwise 

217 if key.public_blob: 

218 return key.public_blob.key_type, key.public_blob.key_blob 

219 else: 

220 return key.get_name(), key 

221 

222 def _get_session_blob(self, key, service, username, algorithm): 

223 m = Message() 

224 m.add_string(self.transport.session_id) 

225 m.add_byte(cMSG_USERAUTH_REQUEST) 

226 m.add_string(username) 

227 m.add_string(service) 

228 m.add_string("publickey") 

229 m.add_boolean(True) 

230 _, bits = self._get_key_type_and_bits(key) 

231 m.add_string(algorithm) 

232 m.add_string(bits) 

233 return m.asbytes() 

234 

235 def wait_for_response(self, event): 

236 max_ts = None 

237 if self.transport.auth_timeout is not None: 

238 max_ts = time.time() + self.transport.auth_timeout 

239 while True: 

240 event.wait(0.1) 

241 if not self.transport.is_active(): 

242 e = self.transport.get_exception() 

243 if (e is None) or issubclass(e.__class__, EOFError): 

244 e = AuthenticationException("Authentication failed.") 

245 raise e 

246 if event.is_set(): 

247 break 

248 if max_ts is not None and max_ts <= time.time(): 

249 raise AuthenticationException("Authentication timeout.") 

250 

251 if not self.is_authenticated(): 

252 e = self.transport.get_exception() 

253 if e is None: 

254 e = AuthenticationException("Authentication failed.") 

255 # this is horrible. Python Exception isn't yet descended from 

256 # object, so type(e) won't work. :( 

257 if issubclass(e.__class__, PartialAuthentication): 

258 return e.allowed_types 

259 raise e 

260 return [] 

261 

262 def _parse_service_request(self, m): 

263 service = m.get_text() 

264 if self.transport.server_mode and (service == "ssh-userauth"): 

265 # accepted 

266 m = Message() 

267 m.add_byte(cMSG_SERVICE_ACCEPT) 

268 m.add_string(service) 

269 self.transport._send_message(m) 

270 banner, language = self.transport.server_object.get_banner() 

271 if banner: 

272 m = Message() 

273 m.add_byte(cMSG_USERAUTH_BANNER) 

274 m.add_string(banner) 

275 m.add_string(language) 

276 self.transport._send_message(m) 

277 return 

278 # dunno this one 

279 self._disconnect_service_not_available() 

280 

281 def _generate_key_from_request(self, algorithm, keyblob): 

282 # For use in server mode. 

283 options = self.transport.preferred_pubkeys 

284 if algorithm.replace("-cert-v01@openssh.com", "") not in options: 

285 err = ( 

286 "Auth rejected: pubkey algorithm '{}' unsupported or disabled" 

287 ) 

288 self._log(INFO, err.format(algorithm)) 

289 return None 

290 return self.transport._key_info[algorithm](Message(keyblob)) 

291 

292 def _finalize_pubkey_algorithm(self, key_type): 

293 # Short-circuit for non-RSA keys 

294 if "rsa" not in key_type: 

295 return key_type 

296 self._log( 

297 DEBUG, 

298 "Finalizing pubkey algorithm for key of type {!r}".format( 

299 key_type 

300 ), 

301 ) 

302 # NOTE re #2017: When the key is an RSA cert and the remote server is 

303 # OpenSSH 7.7 or earlier, always use ssh-rsa-cert-v01@openssh.com. 

304 # Those versions of the server won't support rsa-sha2 family sig algos 

305 # for certs specifically, and in tandem with various server bugs 

306 # regarding server-sig-algs, it's impossible to fit this into the rest 

307 # of the logic here. 

308 if key_type.endswith("-cert-v01@openssh.com") and re.search( 

309 r"-OpenSSH_(?:[1-6]|7\.[0-7])", self.transport.remote_version 

310 ): 

311 pubkey_algo = "ssh-rsa-cert-v01@openssh.com" 

312 self.transport._agreed_pubkey_algorithm = pubkey_algo 

313 self._log(DEBUG, "OpenSSH<7.8 + RSA cert = forcing ssh-rsa!") 

314 self._log( 

315 DEBUG, "Agreed upon {!r} pubkey algorithm".format(pubkey_algo) 

316 ) 

317 return pubkey_algo 

318 # Normal attempts to handshake follow from here. 

319 # Only consider RSA algos from our list, lest we agree on another! 

320 my_algos = [x for x in self.transport.preferred_pubkeys if "rsa" in x] 

321 self._log(DEBUG, "Our pubkey algorithm list: {}".format(my_algos)) 

322 # Short-circuit negatively if user disabled all RSA algos (heh) 

323 if not my_algos: 

324 raise SSHException( 

325 "An RSA key was specified, but no RSA pubkey algorithms are configured!" # noqa 

326 ) 

327 # Check for server-sig-algs if supported & sent 

328 server_algo_str = u( 

329 self.transport.server_extensions.get("server-sig-algs", b("")) 

330 ) 

331 pubkey_algo = None 

332 if server_algo_str: 

333 server_algos = server_algo_str.split(",") 

334 self._log( 

335 DEBUG, "Server-side algorithm list: {}".format(server_algos) 

336 ) 

337 # Only use algos from our list that the server likes, in our own 

338 # preference order. (NOTE: purposefully using same style as in 

339 # Transport...expect to refactor later) 

340 agreement = list(filter(server_algos.__contains__, my_algos)) 

341 if agreement: 

342 pubkey_algo = agreement[0] 

343 self._log( 

344 DEBUG, 

345 "Agreed upon {!r} pubkey algorithm".format(pubkey_algo), 

346 ) 

347 else: 

348 self._log(DEBUG, "No common pubkey algorithms exist! Dying.") 

349 # TODO: MAY want to use IncompatiblePeer again here but that's 

350 # technically for initial key exchange, not pubkey auth. 

351 err = "Unable to agree on a pubkey algorithm for signing a {!r} key!" # noqa 

352 raise AuthenticationException(err.format(key_type)) 

353 else: 

354 # Fallback: first one in our (possibly tweaked by caller) list 

355 pubkey_algo = my_algos[0] 

356 msg = "Server did not send a server-sig-algs list; defaulting to our first preferred algo ({!r})" # noqa 

357 self._log(DEBUG, msg.format(pubkey_algo)) 

358 self._log( 

359 DEBUG, 

360 "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 

361 ) 

362 if key_type.endswith("-cert-v01@openssh.com"): 

363 pubkey_algo += "-cert-v01@openssh.com" 

364 self.transport._agreed_pubkey_algorithm = pubkey_algo 

365 return pubkey_algo 

366 

367 def _parse_service_accept(self, m): 

368 service = m.get_text() 

369 if service == "ssh-userauth": 

370 # TODO 4.0: this message sucks ass. change it to something more 

371 # obvious. it always appears to mean "we already authed" but no! it 

372 # just means "we are allowed to TRY authing!" 

373 self._log(DEBUG, "userauth is OK") 

374 m = Message() 

375 m.add_byte(cMSG_USERAUTH_REQUEST) 

376 m.add_string(self.username) 

377 m.add_string("ssh-connection") 

378 m.add_string(self.auth_method) 

379 if self.auth_method == "password": 

380 m.add_boolean(False) 

381 password = b(self.password) 

382 m.add_string(password) 

383 elif self.auth_method == "publickey": 

384 m.add_boolean(True) 

385 key_type, bits = self._get_key_type_and_bits(self.private_key) 

386 algorithm = self._finalize_pubkey_algorithm(key_type) 

387 m.add_string(algorithm) 

388 m.add_string(bits) 

389 blob = self._get_session_blob( 

390 self.private_key, 

391 "ssh-connection", 

392 self.username, 

393 algorithm, 

394 ) 

395 sig = self.private_key.sign_ssh_data(blob, algorithm) 

396 m.add_string(sig) 

397 elif self.auth_method == "keyboard-interactive": 

398 m.add_string("") 

399 m.add_string(self.submethods) 

400 elif self.auth_method == "gssapi-with-mic": 

401 sshgss = GSSAuth(self.auth_method, self.gss_deleg_creds) 

402 m.add_bytes(sshgss.ssh_gss_oids()) 

403 # send the supported GSSAPI OIDs to the server 

404 self.transport._send_message(m) 

405 ptype, m = self.transport.packetizer.read_message() 

406 if ptype == MSG_USERAUTH_BANNER: 

407 self._parse_userauth_banner(m) 

408 ptype, m = self.transport.packetizer.read_message() 

409 if ptype == MSG_USERAUTH_GSSAPI_RESPONSE: 

410 # Read the mechanism selected by the server. We send just 

411 # the Kerberos V5 OID, so the server can only respond with 

412 # this OID. 

413 mech = m.get_string() 

414 m = Message() 

415 m.add_byte(cMSG_USERAUTH_GSSAPI_TOKEN) 

416 try: 

417 m.add_string( 

418 sshgss.ssh_init_sec_context( 

419 self.gss_host, mech, self.username 

420 ) 

421 ) 

422 except GSS_EXCEPTIONS as e: 

423 return self._handle_local_gss_failure(e) 

424 self.transport._send_message(m) 

425 while True: 

426 ptype, m = self.transport.packetizer.read_message() 

427 if ptype == MSG_USERAUTH_GSSAPI_TOKEN: 

428 srv_token = m.get_string() 

429 try: 

430 next_token = sshgss.ssh_init_sec_context( 

431 self.gss_host, 

432 mech, 

433 self.username, 

434 srv_token, 

435 ) 

436 except GSS_EXCEPTIONS as e: 

437 return self._handle_local_gss_failure(e) 

438 # After this step the GSSAPI should not return any 

439 # token. If it does, we keep sending the token to 

440 # the server until no more token is returned. 

441 if next_token is None: 

442 break 

443 else: 

444 m = Message() 

445 m.add_byte(cMSG_USERAUTH_GSSAPI_TOKEN) 

446 m.add_string(next_token) 

447 self.transport.send_message(m) 

448 else: 

449 raise SSHException( 

450 "Received Package: {}".format(MSG_NAMES[ptype]) 

451 ) 

452 m = Message() 

453 m.add_byte(cMSG_USERAUTH_GSSAPI_MIC) 

454 # send the MIC to the server 

455 m.add_string(sshgss.ssh_get_mic(self.transport.session_id)) 

456 elif ptype == MSG_USERAUTH_GSSAPI_ERRTOK: 

457 # RFC 4462 says we are not required to implement GSS-API 

458 # error messages. 

459 # See RFC 4462 Section 3.8 in 

460 # http://www.ietf.org/rfc/rfc4462.txt 

461 raise SSHException("Server returned an error token") 

462 elif ptype == MSG_USERAUTH_GSSAPI_ERROR: 

463 maj_status = m.get_int() 

464 min_status = m.get_int() 

465 err_msg = m.get_string() 

466 m.get_string() # Lang tag - discarded 

467 raise SSHException( 

468 """GSS-API Error: 

469Major Status: {} 

470Minor Status: {} 

471Error Message: {} 

472""".format( 

473 maj_status, min_status, err_msg 

474 ) 

475 ) 

476 elif ptype == MSG_USERAUTH_FAILURE: 

477 self._parse_userauth_failure(m) 

478 return 

479 else: 

480 raise SSHException( 

481 "Received Package: {}".format(MSG_NAMES[ptype]) 

482 ) 

483 elif ( 

484 self.auth_method == "gssapi-keyex" 

485 and self.transport.gss_kex_used 

486 ): 

487 kexgss = self.transport.kexgss_ctxt 

488 kexgss.set_username(self.username) 

489 mic_token = kexgss.ssh_get_mic(self.transport.session_id) 

490 m.add_string(mic_token) 

491 elif self.auth_method == "none": 

492 pass 

493 else: 

494 raise SSHException( 

495 'Unknown auth method "{}"'.format(self.auth_method) 

496 ) 

497 self.transport._send_message(m) 

498 else: 

499 self._log( 

500 DEBUG, 'Service request "{}" accepted (?)'.format(service) 

501 ) 

502 

503 def _send_auth_result(self, username, method, result): 

504 # okay, send result 

505 m = Message() 

506 if result == AUTH_SUCCESSFUL: 

507 self._log(INFO, "Auth granted ({}).".format(method)) 

508 m.add_byte(cMSG_USERAUTH_SUCCESS) 

509 self.authenticated = True 

510 else: 

511 self._log(INFO, "Auth rejected ({}).".format(method)) 

512 m.add_byte(cMSG_USERAUTH_FAILURE) 

513 m.add_string( 

514 self.transport.server_object.get_allowed_auths(username) 

515 ) 

516 if result == AUTH_PARTIALLY_SUCCESSFUL: 

517 m.add_boolean(True) 

518 else: 

519 m.add_boolean(False) 

520 self.auth_fail_count += 1 

521 self.transport._send_message(m) 

522 if self.auth_fail_count >= 10: 

523 self._disconnect_no_more_auth() 

524 if result == AUTH_SUCCESSFUL: 

525 self.transport._auth_trigger() 

526 

527 def _interactive_query(self, q): 

528 # make interactive query instead of response 

529 m = Message() 

530 m.add_byte(cMSG_USERAUTH_INFO_REQUEST) 

531 m.add_string(q.name) 

532 m.add_string(q.instructions) 

533 m.add_string(bytes()) 

534 m.add_int(len(q.prompts)) 

535 for p in q.prompts: 

536 m.add_string(p[0]) 

537 m.add_boolean(p[1]) 

538 self.transport._send_message(m) 

539 

540 def _parse_userauth_request(self, m): 

541 if not self.transport.server_mode: 

542 # er, uh... what? 

543 m = Message() 

544 m.add_byte(cMSG_USERAUTH_FAILURE) 

545 m.add_string("none") 

546 m.add_boolean(False) 

547 self.transport._send_message(m) 

548 return 

549 if self.authenticated: 

550 # ignore 

551 return 

552 username = m.get_text() 

553 service = m.get_text() 

554 method = m.get_text() 

555 self._log( 

556 DEBUG, 

557 "Auth request (type={}) service={}, username={}".format( 

558 method, service, username 

559 ), 

560 ) 

561 if service != "ssh-connection": 

562 self._disconnect_service_not_available() 

563 return 

564 if (self.auth_username is not None) and ( 

565 self.auth_username != username 

566 ): 

567 self._log( 

568 WARNING, 

569 "Auth rejected because the client attempted to change username in mid-flight", # noqa 

570 ) 

571 self._disconnect_no_more_auth() 

572 return 

573 self.auth_username = username 

574 # check if GSS-API authentication is enabled 

575 gss_auth = self.transport.server_object.enable_auth_gssapi() 

576 

577 if method == "none": 

578 result = self.transport.server_object.check_auth_none(username) 

579 elif method == "password": 

580 changereq = m.get_boolean() 

581 password = m.get_binary() 

582 try: 

583 password = password.decode("UTF-8") 

584 except UnicodeError: 

585 # some clients/servers expect non-utf-8 passwords! 

586 # in this case, just return the raw byte string. 

587 pass 

588 if changereq: 

589 # always treated as failure, since we don't support changing 

590 # passwords, but collect the list of valid auth types from 

591 # the callback anyway 

592 self._log(DEBUG, "Auth request to change passwords (rejected)") 

593 newpassword = m.get_binary() 

594 try: 

595 newpassword = newpassword.decode("UTF-8", "replace") 

596 except UnicodeError: 

597 pass 

598 result = AUTH_FAILED 

599 else: 

600 result = self.transport.server_object.check_auth_password( 

601 username, password 

602 ) 

603 elif method == "publickey": 

604 sig_attached = m.get_boolean() 

605 # NOTE: server never wants to guess a client's algo, they're 

606 # telling us directly. No need for _finalize_pubkey_algorithm 

607 # anywhere in this flow. 

608 algorithm = m.get_text() 

609 keyblob = m.get_binary() 

610 try: 

611 key = self._generate_key_from_request(algorithm, keyblob) 

612 except SSHException as e: 

613 self._log(INFO, "Auth rejected: public key: {}".format(str(e))) 

614 key = None 

615 except Exception as e: 

616 msg = "Auth rejected: unsupported or mangled public key ({}: {})" # noqa 

617 self._log(INFO, msg.format(e.__class__.__name__, e)) 

618 key = None 

619 if key is None: 

620 self._disconnect_no_more_auth() 

621 return 

622 # first check if this key is okay... if not, we can skip the verify 

623 result = self.transport.server_object.check_auth_publickey( 

624 username, key 

625 ) 

626 if result != AUTH_FAILED: 

627 # key is okay, verify it 

628 if not sig_attached: 

629 # client wants to know if this key is acceptable, before it 

630 # signs anything... send special "ok" message 

631 m = Message() 

632 m.add_byte(cMSG_USERAUTH_PK_OK) 

633 m.add_string(algorithm) 

634 m.add_string(keyblob) 

635 self.transport._send_message(m) 

636 return 

637 sig = Message(m.get_binary()) 

638 blob = self._get_session_blob( 

639 key, service, username, algorithm 

640 ) 

641 if not key.verify_ssh_sig(blob, sig): 

642 self._log(INFO, "Auth rejected: invalid signature") 

643 result = AUTH_FAILED 

644 elif method == "keyboard-interactive": 

645 submethods = m.get_string() 

646 result = self.transport.server_object.check_auth_interactive( 

647 username, submethods 

648 ) 

649 if isinstance(result, InteractiveQuery): 

650 # make interactive query instead of response 

651 self._interactive_query(result) 

652 return 

653 elif method == "gssapi-with-mic" and gss_auth: 

654 sshgss = GSSAuth(method) 

655 # Read the number of OID mechanisms supported by the client. 

656 # OpenSSH sends just one OID. It's the Kerveros V5 OID and that's 

657 # the only OID we support. 

658 mechs = m.get_int() 

659 # We can't accept more than one OID, so if the SSH client sends 

660 # more than one, disconnect. 

661 if mechs > 1: 

662 self._log( 

663 INFO, 

664 "Disconnect: Received more than one GSS-API OID mechanism", 

665 ) 

666 self._disconnect_no_more_auth() 

667 desired_mech = m.get_string() 

668 mech_ok = sshgss.ssh_check_mech(desired_mech) 

669 # if we don't support the mechanism, disconnect. 

670 if not mech_ok: 

671 self._log( 

672 INFO, 

673 "Disconnect: Received an invalid GSS-API OID mechanism", 

674 ) 

675 self._disconnect_no_more_auth() 

676 # send the Kerberos V5 GSSAPI OID to the client 

677 supported_mech = sshgss.ssh_gss_oids("server") 

678 # RFC 4462 says we are not required to implement GSS-API error 

679 # messages. See section 3.8 in http://www.ietf.org/rfc/rfc4462.txt 

680 m = Message() 

681 m.add_byte(cMSG_USERAUTH_GSSAPI_RESPONSE) 

682 m.add_bytes(supported_mech) 

683 self.transport.auth_handler = GssapiWithMicAuthHandler( 

684 self, sshgss 

685 ) 

686 self.transport._expected_packet = ( 

687 MSG_USERAUTH_GSSAPI_TOKEN, 

688 MSG_USERAUTH_REQUEST, 

689 MSG_SERVICE_REQUEST, 

690 ) 

691 self.transport._send_message(m) 

692 return 

693 elif method == "gssapi-keyex" and gss_auth: 

694 mic_token = m.get_string() 

695 sshgss = self.transport.kexgss_ctxt 

696 if sshgss is None: 

697 # If there is no valid context, we reject the authentication 

698 result = AUTH_FAILED 

699 self._send_auth_result(username, method, result) 

700 try: 

701 sshgss.ssh_check_mic( 

702 mic_token, self.transport.session_id, self.auth_username 

703 ) 

704 except Exception: 

705 result = AUTH_FAILED 

706 self._send_auth_result(username, method, result) 

707 raise 

708 result = AUTH_SUCCESSFUL 

709 self.transport.server_object.check_auth_gssapi_keyex( 

710 username, result 

711 ) 

712 else: 

713 result = self.transport.server_object.check_auth_none(username) 

714 # okay, send result 

715 self._send_auth_result(username, method, result) 

716 

717 def _parse_userauth_success(self, m): 

718 self._log( 

719 INFO, "Authentication ({}) successful!".format(self.auth_method) 

720 ) 

721 self.authenticated = True 

722 self.transport._auth_trigger() 

723 if self.auth_event is not None: 

724 self.auth_event.set() 

725 

726 def _parse_userauth_failure(self, m): 

727 authlist = m.get_list() 

728 partial = m.get_boolean() 

729 if partial: 

730 self._log(INFO, "Authentication continues...") 

731 self._log(DEBUG, "Methods: " + str(authlist)) 

732 self.transport.saved_exception = PartialAuthentication(authlist) 

733 elif self.auth_method not in authlist: 

734 for msg in ( 

735 "Authentication type ({}) not permitted.".format( 

736 self.auth_method 

737 ), 

738 "Allowed methods: {}".format(authlist), 

739 ): 

740 self._log(DEBUG, msg) 

741 self.transport.saved_exception = BadAuthenticationType( 

742 "Bad authentication type", authlist 

743 ) 

744 else: 

745 self._log( 

746 INFO, "Authentication ({}) failed.".format(self.auth_method) 

747 ) 

748 self.authenticated = False 

749 self.username = None 

750 if self.auth_event is not None: 

751 self.auth_event.set() 

752 

753 def _parse_userauth_banner(self, m): 

754 banner = m.get_string() 

755 self.banner = banner 

756 self._log(INFO, "Auth banner: {}".format(banner)) 

757 # who cares. 

758 

759 def _parse_userauth_info_request(self, m): 

760 if self.auth_method != "keyboard-interactive": 

761 raise SSHException("Illegal info request from server") 

762 title = m.get_text() 

763 instructions = m.get_text() 

764 m.get_binary() # lang 

765 prompts = m.get_int() 

766 prompt_list = [] 

767 for i in range(prompts): 

768 prompt_list.append((m.get_text(), m.get_boolean())) 

769 response_list = self.interactive_handler( 

770 title, instructions, prompt_list 

771 ) 

772 

773 m = Message() 

774 m.add_byte(cMSG_USERAUTH_INFO_RESPONSE) 

775 m.add_int(len(response_list)) 

776 for r in response_list: 

777 m.add_string(r) 

778 self.transport._send_message(m) 

779 

780 def _parse_userauth_info_response(self, m): 

781 if not self.transport.server_mode: 

782 raise SSHException("Illegal info response from server") 

783 n = m.get_int() 

784 responses = [] 

785 for i in range(n): 

786 responses.append(m.get_text()) 

787 result = self.transport.server_object.check_auth_interactive_response( 

788 responses 

789 ) 

790 if isinstance(result, InteractiveQuery): 

791 # make interactive query instead of response 

792 self._interactive_query(result) 

793 return 

794 self._send_auth_result( 

795 self.auth_username, "keyboard-interactive", result 

796 ) 

797 

798 def _handle_local_gss_failure(self, e): 

799 self.transport.saved_exception = e 

800 self._log(DEBUG, "GSSAPI failure: {}".format(e)) 

801 self._log(INFO, "Authentication ({}) failed.".format(self.auth_method)) 

802 self.authenticated = False 

803 self.username = None 

804 if self.auth_event is not None: 

805 self.auth_event.set() 

806 return 

807 

808 # TODO: do the same to the other tables, in Transport. 

809 # TODO 4.0: MAY make sense to make these tables into actual 

810 # classes/instances that can be fed a mode bool or whatever. Or, 

811 # alternately (both?) make the message types small classes or enums that 

812 # embed this info within themselves (which could also then tidy up the 

813 # current 'integer -> human readable short string' stuff in common.py). 

814 # TODO: if we do that, also expose 'em publicly. 

815 

816 # Messages which should be handled _by_ servers (sent by clients) 

817 _server_handler_table = { 

818 MSG_SERVICE_REQUEST: _parse_service_request, 

819 MSG_USERAUTH_REQUEST: _parse_userauth_request, 

820 MSG_USERAUTH_INFO_RESPONSE: _parse_userauth_info_response, 

821 } 

822 

823 # Messages which should be handled _by_ clients (sent by servers) 

824 _client_handler_table = { 

825 MSG_SERVICE_ACCEPT: _parse_service_accept, 

826 MSG_USERAUTH_SUCCESS: _parse_userauth_success, 

827 MSG_USERAUTH_FAILURE: _parse_userauth_failure, 

828 MSG_USERAUTH_BANNER: _parse_userauth_banner, 

829 MSG_USERAUTH_INFO_REQUEST: _parse_userauth_info_request, 

830 } 

831 

832 # NOTE: prior to the fix for #1283, this was a static dict instead of a 

833 # property. Should be backwards compatible in most/all cases. 

834 @property 

835 def _handler_table(self): 

836 if self.transport.server_mode: 

837 return self._server_handler_table 

838 else: 

839 return self._client_handler_table 

840 

841 

842class GssapiWithMicAuthHandler: 

843 """A specialized Auth handler for gssapi-with-mic 

844 

845 During the GSSAPI token exchange we need a modified dispatch table, 

846 because the packet type numbers are not unique. 

847 """ 

848 

849 method = "gssapi-with-mic" 

850 

851 def __init__(self, delegate, sshgss): 

852 self._delegate = delegate 

853 self.sshgss = sshgss 

854 

855 def abort(self): 

856 self._restore_delegate_auth_handler() 

857 return self._delegate.abort() 

858 

859 @property 

860 def transport(self): 

861 return self._delegate.transport 

862 

863 @property 

864 def _send_auth_result(self): 

865 return self._delegate._send_auth_result 

866 

867 @property 

868 def auth_username(self): 

869 return self._delegate.auth_username 

870 

871 @property 

872 def gss_host(self): 

873 return self._delegate.gss_host 

874 

875 def _restore_delegate_auth_handler(self): 

876 self.transport.auth_handler = self._delegate 

877 

878 def _parse_userauth_gssapi_token(self, m): 

879 client_token = m.get_string() 

880 # use the client token as input to establish a secure 

881 # context. 

882 sshgss = self.sshgss 

883 try: 

884 token = sshgss.ssh_accept_sec_context( 

885 self.gss_host, client_token, self.auth_username 

886 ) 

887 except Exception as e: 

888 self.transport.saved_exception = e 

889 result = AUTH_FAILED 

890 self._restore_delegate_auth_handler() 

891 self._send_auth_result(self.auth_username, self.method, result) 

892 raise 

893 if token is not None: 

894 m = Message() 

895 m.add_byte(cMSG_USERAUTH_GSSAPI_TOKEN) 

896 m.add_string(token) 

897 self.transport._expected_packet = ( 

898 MSG_USERAUTH_GSSAPI_TOKEN, 

899 MSG_USERAUTH_GSSAPI_MIC, 

900 MSG_USERAUTH_REQUEST, 

901 ) 

902 self.transport._send_message(m) 

903 

904 def _parse_userauth_gssapi_mic(self, m): 

905 mic_token = m.get_string() 

906 sshgss = self.sshgss 

907 username = self.auth_username 

908 self._restore_delegate_auth_handler() 

909 try: 

910 sshgss.ssh_check_mic( 

911 mic_token, self.transport.session_id, username 

912 ) 

913 except Exception as e: 

914 self.transport.saved_exception = e 

915 result = AUTH_FAILED 

916 self._send_auth_result(username, self.method, result) 

917 raise 

918 # TODO: Implement client credential saving. 

919 # The OpenSSH server is able to create a TGT with the delegated 

920 # client credentials, but this is not supported by GSS-API. 

921 result = AUTH_SUCCESSFUL 

922 self.transport.server_object.check_auth_gssapi_with_mic( 

923 username, result 

924 ) 

925 # okay, send result 

926 self._send_auth_result(username, self.method, result) 

927 

928 def _parse_service_request(self, m): 

929 self._restore_delegate_auth_handler() 

930 return self._delegate._parse_service_request(m) 

931 

932 def _parse_userauth_request(self, m): 

933 self._restore_delegate_auth_handler() 

934 return self._delegate._parse_userauth_request(m) 

935 

936 __handler_table = { 

937 MSG_SERVICE_REQUEST: _parse_service_request, 

938 MSG_USERAUTH_REQUEST: _parse_userauth_request, 

939 MSG_USERAUTH_GSSAPI_TOKEN: _parse_userauth_gssapi_token, 

940 MSG_USERAUTH_GSSAPI_MIC: _parse_userauth_gssapi_mic, 

941 } 

942 

943 @property 

944 def _handler_table(self): 

945 # TODO: determine if we can cut this up like we did for the primary 

946 # AuthHandler class. 

947 return self.__handler_table