1# Copyright 2011-2019, Damian Johnson and The Tor Project 2# See LICENSE for licensing information 3 4""" 5Functions for connecting and authenticating to the tor process. 6 7The :func:`~stem.connection.connect` function give an easy, one line 8method for getting an authenticated control connection. This is handy for CLI 9applications and the python interactive interpreter, but does several things 10that makes it undesirable for applications (uses stdin/stdout, suppresses 11exceptions, etc). 12 13:: 14 15 import sys 16 17 from stem.connection import connect 18 19 if __name__ == '__main__': 20 controller = connect() 21 22 if not controller: 23 sys.exit(1) # unable to get a connection 24 25 print 'Tor is running version %s' % controller.get_version() 26 controller.close() 27 28:: 29 30 % python example.py 31 Tor is running version 0.2.4.10-alpha-dev (git-8be6058d8f31e578) 32 33... or if Tor isn't running... 34 35:: 36 37 % python example.py 38 [Errno 111] Connection refused 39 40The :func:`~stem.connection.authenticate` function, however, gives easy but 41fine-grained control over the authentication process. For instance... 42 43:: 44 45 import sys 46 import getpass 47 import stem.connection 48 import stem.socket 49 50 try: 51 control_socket = stem.socket.ControlPort(port = 9051) 52 except stem.SocketError as exc: 53 print 'Unable to connect to port 9051 (%s)' % exc 54 sys.exit(1) 55 56 try: 57 stem.connection.authenticate(control_socket) 58 except stem.connection.IncorrectSocketType: 59 print 'Please check in your torrc that 9051 is the ControlPort.' 60 print 'Maybe you configured it to be the ORPort or SocksPort instead?' 61 sys.exit(1) 62 except stem.connection.MissingPassword: 63 controller_password = getpass.getpass('Controller password: ') 64 65 try: 66 stem.connection.authenticate_password(control_socket, controller_password) 67 except stem.connection.PasswordAuthFailed: 68 print 'Unable to authenticate, password is incorrect' 69 sys.exit(1) 70 except stem.connection.AuthenticationFailure as exc: 71 print 'Unable to authenticate: %s' % exc 72 sys.exit(1) 73 74**Module Overview:** 75 76:: 77 78 connect - Simple method for getting authenticated control connection 79 80 authenticate - Main method for authenticating to a control socket 81 authenticate_none - Authenticates to an open control socket 82 authenticate_password - Authenticates to a socket supporting password auth 83 authenticate_cookie - Authenticates to a socket supporting cookie auth 84 authenticate_safecookie - Authenticates to a socket supporting safecookie auth 85 86 get_protocolinfo - Issues a PROTOCOLINFO query 87 88 AuthenticationFailure - Base exception raised for authentication failures 89 |- UnrecognizedAuthMethods - Authentication methods are unsupported 90 |- IncorrectSocketType - Socket does not speak the tor control protocol 91 | 92 |- OpenAuthFailed - Failure when authenticating by an open socket 93 | +- OpenAuthRejected - Tor rejected this method of authentication 94 | 95 |- PasswordAuthFailed - Failure when authenticating by a password 96 | |- PasswordAuthRejected - Tor rejected this method of authentication 97 | |- IncorrectPassword - Password was rejected 98 | +- MissingPassword - Socket supports password auth but wasn't attempted 99 | 100 |- CookieAuthFailed - Failure when authenticating by a cookie 101 | |- CookieAuthRejected - Tor rejected this method of authentication 102 | |- IncorrectCookieValue - Authentication cookie was rejected 103 | |- IncorrectCookieSize - Size of the cookie file is incorrect 104 | |- UnreadableCookieFile - Unable to read the contents of the auth cookie 105 | +- AuthChallengeFailed - Failure completing the authchallenge request 106 | |- AuthChallengeUnsupported - Tor doesn't recognize the AUTHCHALLENGE command 107 | |- AuthSecurityFailure - Server provided the wrong nonce credentials 108 | |- InvalidClientNonce - The client nonce is invalid 109 | +- UnrecognizedAuthChallengeMethod - AUTHCHALLENGE does not support the given methods. 110 | 111 +- MissingAuthInfo - Unexpected PROTOCOLINFO response, missing auth info 112 |- NoAuthMethods - Missing any methods for authenticating 113 +- NoAuthCookie - Supports cookie auth but doesn't have its path 114 115.. data:: AuthMethod (enum) 116 117 Enumeration of PROTOCOLINFO responses for supported authentication methods. 118 119 ============== =========== 120 AuthMethod Description 121 ============== =========== 122 **NONE** No authentication required. 123 **PASSWORD** Password required, see tor's HashedControlPassword option. 124 **COOKIE** Contents of the cookie file required, see tor's CookieAuthentication option. 125 **SAFECOOKIE** Need to reply to a hmac challenge using the contents of the cookie file. 126 **UNKNOWN** Tor provided one or more authentication methods that we don't recognize, probably something new. 127 ============== =========== 128""" 129 130import binascii 131import getpass 132import hashlib 133import hmac 134import os 135 136import stem.control 137import stem.response 138import stem.socket 139import stem.util.connection 140import stem.util.enum 141import stem.util.str_tools 142import stem.util.system 143import stem.version 144 145from stem.util import log 146 147AuthMethod = stem.util.enum.Enum('NONE', 'PASSWORD', 'COOKIE', 'SAFECOOKIE', 'UNKNOWN') 148 149CLIENT_HASH_CONSTANT = b'Tor safe cookie authentication controller-to-server hash' 150SERVER_HASH_CONSTANT = b'Tor safe cookie authentication server-to-controller hash' 151 152CRYPTOVARIABLE_EQUALITY_COMPARISON_NONCE = os.urandom(32) 153 154MISSING_PASSWORD_BUG_MSG = """ 155BUG: You provided a password but despite this stem reported that it was 156missing. This shouldn't happen - please let us know about it! 157 158 http://bugs.torproject.org 159""" 160 161UNRECOGNIZED_AUTH_TYPE_MSG = """ 162Tor is using a type of authentication we do not recognize... 163 164 {auth_methods} 165 166Please check that stem is up to date and if there is an existing issue on 167'http://bugs.torproject.org'. If there isn't one then let us know! 168""" 169 170 171UNREADABLE_COOKIE_FILE_MSG = """ 172We were unable to read tor's authentication cookie... 173 174 Path: {path} 175 Issue: {issue} 176""" 177 178WRONG_PORT_TYPE_MSG = """ 179Please check in your torrc that {port} is the ControlPort. Maybe you 180configured it to be the ORPort or SocksPort instead? 181""" 182 183WRONG_SOCKET_TYPE_MSG = """ 184Unable to connect to tor. Are you sure the interface you specified belongs to 185tor? 186""" 187 188CONNECT_MESSAGES = { 189 'general_auth_failure': 'Unable to authenticate: {error}', 190 'incorrect_password': 'Incorrect password', 191 'no_control_port': "Unable to connect to tor. Maybe it's running without a ControlPort?", 192 'password_prompt': 'Tor controller password:', 193 'needs_password': 'Tor requires a password to authenticate', 194 'socket_doesnt_exist': "The socket file you specified ({path}) doesn't exist", 195 'tor_isnt_running': "Unable to connect to tor. Are you sure it's running?", 196 'unable_to_use_port': 'Unable to connect to {address}:{port}: {error}', 197 'unable_to_use_socket': "Unable to connect to '{path}': {error}", 198 'missing_password_bug': MISSING_PASSWORD_BUG_MSG.strip(), 199 'uncrcognized_auth_type': UNRECOGNIZED_AUTH_TYPE_MSG.strip(), 200 'unreadable_cookie_file': UNREADABLE_COOKIE_FILE_MSG.strip(), 201 'wrong_port_type': WRONG_PORT_TYPE_MSG.strip(), 202 'wrong_socket_type': WRONG_SOCKET_TYPE_MSG.strip(), 203} 204 205COMMON_TOR_COMMANDS = ( 206 'tor', 207 'tor.real', # TBB command ran 208 '/usr/local/bin/tor', # FreeBSD expands the whole path, this is the default location 209) 210 211 212def connect(control_port = ('127.0.0.1', 'default'), control_socket = '/var/run/tor/control', password = None, password_prompt = False, chroot_path = None, controller = stem.control.Controller): 213 """ 214 Convenience function for quickly getting a control connection. This is very 215 handy for debugging or CLI setup, handling setup and prompting for a password 216 if necessary (and none is provided). If any issues arise this prints a 217 description of the problem and returns **None**. 218 219 If both a **control_port** and **control_socket** are provided then the 220 **control_socket** is tried first, and this provides a generic error message 221 if they're both unavailable. 222 223 In much the same vein as git porcelain commands, users should not rely on 224 details of how this works. Messages and details of this function's behavior 225 could change in the future. 226 227 If the **port** is **'default'** then this checks on both 9051 (default for 228 relays) and 9151 (default for the Tor Browser). This default may change in 229 the future. 230 231 .. versionadded:: 1.2.0 232 233 .. versionchanged:: 1.5.0 234 Use both port 9051 and 9151 by default. 235 236 :param tuple contol_port: address and port tuple, for instance **('127.0.0.1', 9051)** 237 :param str path: path where the control socket is located 238 :param str password: passphrase to authenticate to the socket 239 :param bool password_prompt: prompt for the controller password if it wasn't 240 supplied 241 :param str chroot_path: path prefix if in a chroot environment 242 :param Class controller: :class:`~stem.control.BaseController` subclass to be 243 returned, this provides a :class:`~stem.socket.ControlSocket` if **None** 244 245 :returns: authenticated control connection, the type based on the controller argument 246 247 :raises: **ValueError** if given an invalid control_port, or both 248 **control_port** and **control_socket** are **None** 249 """ 250 251 if control_port is None and control_socket is None: 252 raise ValueError('Neither a control port nor control socket were provided. Nothing to connect to.') 253 elif control_port: 254 if len(control_port) != 2: 255 raise ValueError('The control_port argument for connect() should be an (address, port) tuple.') 256 elif not stem.util.connection.is_valid_ipv4_address(control_port[0]): 257 raise ValueError("'%s' isn't a vaid IPv4 address" % control_port[0]) 258 elif control_port[1] != 'default' and not stem.util.connection.is_valid_port(control_port[1]): 259 raise ValueError("'%s' isn't a valid port" % control_port[1]) 260 261 control_connection, error_msg = None, '' 262 263 if control_socket: 264 if os.path.exists(control_socket): 265 try: 266 control_connection = stem.socket.ControlSocketFile(control_socket) 267 except stem.SocketError as exc: 268 error_msg = CONNECT_MESSAGES['unable_to_use_socket'].format(path = control_socket, error = exc) 269 else: 270 error_msg = CONNECT_MESSAGES['socket_doesnt_exist'].format(path = control_socket) 271 272 if control_port and not control_connection: 273 address, port = control_port 274 275 try: 276 if port == 'default': 277 control_connection = _connection_for_default_port(address) 278 else: 279 control_connection = stem.socket.ControlPort(address, int(port)) 280 except stem.SocketError as exc: 281 error_msg = CONNECT_MESSAGES['unable_to_use_port'].format(address = address, port = port, error = exc) 282 283 # If unable to connect to either a control socket or port then finally fail 284 # out. If we only attempted to connect to one of them then provide the error 285 # output from that. Otherwise we provide a more generic error message. 286 287 if not control_connection: 288 if control_socket and control_port: 289 is_tor_running = stem.util.system.is_running(COMMON_TOR_COMMANDS) 290 error_msg = CONNECT_MESSAGES['no_control_port'] if is_tor_running else CONNECT_MESSAGES['tor_isnt_running'] 291 292 print(error_msg) 293 return None 294 295 return _connect_auth(control_connection, password, password_prompt, chroot_path, controller) 296 297 298def connect_port(address = '127.0.0.1', port = 9051, password = None, chroot_path = None, controller = stem.control.Controller): 299 """ 300 Convenience function for quickly getting a control connection. This is very 301 handy for debugging or CLI setup, handling setup and prompting for a password 302 if necessary (and none is provided). If any issues arise this prints a 303 description of the problem and returns **None**. 304 305 .. deprecated:: 1.2.0 306 Use :func:`~stem.connection.connect` instead. 307 308 :param str address: ip address of the controller 309 :param int port: port number of the controller 310 :param str password: passphrase to authenticate to the socket 311 :param str chroot_path: path prefix if in a chroot environment 312 :param Class controller: :class:`~stem.control.BaseController` subclass to be 313 returned, this provides a :class:`~stem.socket.ControlSocket` if **None** 314 315 :returns: authenticated control connection, the type based on the controller argument 316 """ 317 318 try: 319 control_port = stem.socket.ControlPort(address, port) 320 except stem.SocketError as exc: 321 print(exc) 322 return None 323 324 return _connect_auth(control_port, password, True, chroot_path, controller) 325 326 327def connect_socket_file(path = '/var/run/tor/control', password = None, chroot_path = None, controller = stem.control.Controller): 328 """ 329 Convenience function for quickly getting a control connection. For more 330 information see the :func:`~stem.connection.connect_port` function. 331 332 In much the same vein as git porcelain commands, users should not rely on 333 details of how this works. Messages or details of this function's behavior 334 might change in the future. 335 336 .. deprecated:: 1.2.0 337 Use :func:`~stem.connection.connect` instead. 338 339 :param str path: path where the control socket is located 340 :param str password: passphrase to authenticate to the socket 341 :param str chroot_path: path prefix if in a chroot environment 342 :param Class controller: :class:`~stem.control.BaseController` subclass to be 343 returned, this provides a :class:`~stem.socket.ControlSocket` if **None** 344 345 :returns: authenticated control connection, the type based on the controller argument 346 """ 347 348 try: 349 control_socket = stem.socket.ControlSocketFile(path) 350 except stem.SocketError as exc: 351 print(exc) 352 return None 353 354 return _connect_auth(control_socket, password, True, chroot_path, controller) 355 356 357def _connect_auth(control_socket, password, password_prompt, chroot_path, controller): 358 """ 359 Helper for the connect_* functions that authenticates the socket and 360 constructs the controller. 361 362 :param stem.socket.ControlSocket control_socket: socket being authenticated to 363 :param str password: passphrase to authenticate to the socket 364 :param bool password_prompt: prompt for the controller password if it wasn't 365 supplied 366 :param str chroot_path: path prefix if in a chroot environment 367 :param Class controller: :class:`~stem.control.BaseController` subclass to be 368 returned, this provides a :class:`~stem.socket.ControlSocket` if **None** 369 370 :returns: authenticated control connection, the type based on the controller argument 371 """ 372 373 try: 374 authenticate(control_socket, password, chroot_path) 375 376 if controller is None: 377 return control_socket 378 else: 379 return controller(control_socket, is_authenticated = True) 380 except IncorrectSocketType: 381 if isinstance(control_socket, stem.socket.ControlPort): 382 print(CONNECT_MESSAGES['wrong_port_type'].format(port = control_socket.port)) 383 else: 384 print(CONNECT_MESSAGES['wrong_socket_type']) 385 386 control_socket.close() 387 return None 388 except UnrecognizedAuthMethods as exc: 389 print(CONNECT_MESSAGES['uncrcognized_auth_type'].format(auth_methods = ', '.join(exc.unknown_auth_methods))) 390 control_socket.close() 391 return None 392 except IncorrectPassword: 393 print(CONNECT_MESSAGES['incorrect_password']) 394 control_socket.close() 395 return None 396 except MissingPassword: 397 if password is not None: 398 control_socket.close() 399 raise ValueError(CONNECT_MESSAGES['missing_password_bug']) 400 401 if password_prompt: 402 try: 403 password = getpass.getpass(CONNECT_MESSAGES['password_prompt'] + ' ') 404 except KeyboardInterrupt: 405 control_socket.close() 406 return None 407 408 return _connect_auth(control_socket, password, password_prompt, chroot_path, controller) 409 else: 410 print(CONNECT_MESSAGES['needs_password']) 411 control_socket.close() 412 return None 413 except UnreadableCookieFile as exc: 414 print(CONNECT_MESSAGES['unreadable_cookie_file'].format(path = exc.cookie_path, issue = str(exc))) 415 control_socket.close() 416 return None 417 except AuthenticationFailure as exc: 418 print(CONNECT_MESSAGES['general_auth_failure'].format(error = exc)) 419 control_socket.close() 420 return None 421 422 423def authenticate(controller, password = None, chroot_path = None, protocolinfo_response = None): 424 """ 425 Authenticates to a control socket using the information provided by a 426 PROTOCOLINFO response. In practice this will often be all we need to 427 authenticate, raising an exception if all attempts to authenticate fail. 428 429 All exceptions are subclasses of AuthenticationFailure so, in practice, 430 callers should catch the types of authentication failure that they care 431 about, then have a :class:`~stem.connection.AuthenticationFailure` catch-all 432 at the end. 433 434 This can authenticate to either a :class:`~stem.control.BaseController` or 435 :class:`~stem.socket.ControlSocket`. 436 437 :param controller: tor controller or socket to be authenticated 438 :param str password: passphrase to present to the socket if it uses password 439 authentication (skips password auth if **None**) 440 :param str chroot_path: path prefix if in a chroot environment 441 :param stem.response.protocolinfo.ProtocolInfoResponse protocolinfo_response: 442 tor protocolinfo response, this is retrieved on our own if **None** 443 444 :raises: If all attempts to authenticate fails then this will raise a 445 :class:`~stem.connection.AuthenticationFailure` subclass. Since this may 446 try multiple authentication methods it may encounter multiple exceptions. 447 If so then the exception this raises is prioritized as follows... 448 449 * :class:`stem.connection.IncorrectSocketType` 450 451 The controller does not speak the tor control protocol. Most often this 452 happened because the user confused the SocksPort or ORPort with the 453 ControlPort. 454 455 * :class:`stem.connection.UnrecognizedAuthMethods` 456 457 All of the authentication methods tor will accept are new and 458 unrecognized. Please upgrade stem and, if that doesn't work, file a 459 ticket on 'trac.torproject.org' and I'd be happy to add support. 460 461 * :class:`stem.connection.MissingPassword` 462 463 We were unable to authenticate but didn't attempt password authentication 464 because none was provided. You should prompt the user for a password and 465 try again via 'authenticate_password'. 466 467 * :class:`stem.connection.IncorrectPassword` 468 469 We were provided with a password but it was incorrect. 470 471 * :class:`stem.connection.IncorrectCookieSize` 472 473 Tor allows for authentication by reading it a cookie file, but that file 474 is the wrong size to be an authentication cookie. 475 476 * :class:`stem.connection.UnreadableCookieFile` 477 478 Tor allows for authentication by reading it a cookie file, but we can't 479 read that file (probably due to permissions). 480 481 * **\\***:class:`stem.connection.IncorrectCookieValue` 482 483 Tor allows for authentication by reading it a cookie file, but rejected 484 the contents of that file. 485 486 * **\\***:class:`stem.connection.AuthChallengeUnsupported` 487 488 Tor doesn't recognize the AUTHCHALLENGE command. This is probably a Tor 489 version prior to SAFECOOKIE being implement, but this exception shouldn't 490 arise because we won't attempt SAFECOOKIE auth unless Tor claims to 491 support it. 492 493 * **\\***:class:`stem.connection.UnrecognizedAuthChallengeMethod` 494 495 Tor couldn't recognize the AUTHCHALLENGE method Stem sent to it. This 496 shouldn't happen at all. 497 498 * **\\***:class:`stem.connection.InvalidClientNonce` 499 500 Tor says that the client nonce provided by Stem during the AUTHCHALLENGE 501 process is invalid. 502 503 * **\\***:class:`stem.connection.AuthSecurityFailure` 504 505 Nonce value provided by the server was invalid. 506 507 * **\\***:class:`stem.connection.OpenAuthRejected` 508 509 Tor says that it allows for authentication without any credentials, but 510 then rejected our authentication attempt. 511 512 * **\\***:class:`stem.connection.MissingAuthInfo` 513 514 Tor provided us with a PROTOCOLINFO reply that is technically valid, but 515 missing the information we need to authenticate. 516 517 * **\\***:class:`stem.connection.AuthenticationFailure` 518 519 There are numerous other ways that authentication could have failed 520 including socket failures, malformed controller responses, etc. These 521 mostly constitute transient failures or bugs. 522 523 **\\*** In practice it is highly unusual for this to occur, being more of a 524 theoretical possibility rather than something you should expect. It's fine 525 to treat these as errors. If you have a use case where this commonly 526 happens, please file a ticket on 'trac.torproject.org'. 527 528 In the future new :class:`~stem.connection.AuthenticationFailure` 529 subclasses may be added to allow for better error handling. 530 """ 531 532 if not protocolinfo_response: 533 try: 534 protocolinfo_response = get_protocolinfo(controller) 535 except stem.ProtocolError: 536 raise IncorrectSocketType('unable to use the control socket') 537 except stem.SocketError as exc: 538 raise AuthenticationFailure('socket connection failed (%s)' % exc) 539 540 auth_methods = list(protocolinfo_response.auth_methods) 541 auth_exceptions = [] 542 543 if len(auth_methods) == 0: 544 raise NoAuthMethods('our PROTOCOLINFO response did not have any methods for authenticating') 545 546 # remove authentication methods that are either unknown or for which we don't 547 # have an input 548 if AuthMethod.UNKNOWN in auth_methods: 549 auth_methods.remove(AuthMethod.UNKNOWN) 550 551 unknown_methods = protocolinfo_response.unknown_auth_methods 552 plural_label = 's' if len(unknown_methods) > 1 else '' 553 methods_label = ', '.join(unknown_methods) 554 555 # we... er, can't do anything with only unrecognized auth types 556 if not auth_methods: 557 exc_msg = 'unrecognized authentication method%s (%s)' % (plural_label, methods_label) 558 auth_exceptions.append(UnrecognizedAuthMethods(exc_msg, unknown_methods)) 559 else: 560 log.debug('Authenticating to a socket with unrecognized auth method%s, ignoring them: %s' % (plural_label, methods_label)) 561 562 if protocolinfo_response.cookie_path is None: 563 for cookie_auth_method in (AuthMethod.COOKIE, AuthMethod.SAFECOOKIE): 564 if cookie_auth_method in auth_methods: 565 auth_methods.remove(cookie_auth_method) 566 567 exc_msg = 'our PROTOCOLINFO response did not have the location of our authentication cookie' 568 auth_exceptions.append(NoAuthCookie(exc_msg, cookie_auth_method == AuthMethod.SAFECOOKIE)) 569 570 if AuthMethod.PASSWORD in auth_methods and password is None: 571 auth_methods.remove(AuthMethod.PASSWORD) 572 auth_exceptions.append(MissingPassword('no passphrase provided')) 573 574 # iterating over AuthMethods so we can try them in this order 575 for auth_type in (AuthMethod.NONE, AuthMethod.PASSWORD, AuthMethod.SAFECOOKIE, AuthMethod.COOKIE): 576 if auth_type not in auth_methods: 577 continue 578 579 try: 580 if auth_type == AuthMethod.NONE: 581 authenticate_none(controller, False) 582 elif auth_type == AuthMethod.PASSWORD: 583 authenticate_password(controller, password, False) 584 elif auth_type in (AuthMethod.COOKIE, AuthMethod.SAFECOOKIE): 585 cookie_path = protocolinfo_response.cookie_path 586 587 if chroot_path: 588 cookie_path = os.path.join(chroot_path, cookie_path.lstrip(os.path.sep)) 589 590 if auth_type == AuthMethod.SAFECOOKIE: 591 authenticate_safecookie(controller, cookie_path, False) 592 else: 593 authenticate_cookie(controller, cookie_path, False) 594 595 if isinstance(controller, stem.control.BaseController): 596 controller._post_authentication() 597 598 return # success! 599 except OpenAuthRejected as exc: 600 auth_exceptions.append(exc) 601 except IncorrectPassword as exc: 602 auth_exceptions.append(exc) 603 except PasswordAuthRejected as exc: 604 # Since the PROTOCOLINFO says password auth is available we can assume 605 # that if PasswordAuthRejected is raised it's being raised in error. 606 log.debug('The authenticate_password method raised a PasswordAuthRejected when password auth should be available. Stem may need to be corrected to recognize this response: %s' % exc) 607 auth_exceptions.append(IncorrectPassword(str(exc))) 608 except AuthSecurityFailure as exc: 609 log.info('Tor failed to provide the nonce expected for safecookie authentication. (%s)' % exc) 610 auth_exceptions.append(exc) 611 except (InvalidClientNonce, UnrecognizedAuthChallengeMethod, AuthChallengeFailed) as exc: 612 auth_exceptions.append(exc) 613 except (IncorrectCookieSize, UnreadableCookieFile, IncorrectCookieValue) as exc: 614 auth_exceptions.append(exc) 615 except CookieAuthRejected as exc: 616 auth_func = 'authenticate_safecookie' if exc.is_safecookie else 'authenticate_cookie' 617 618 log.debug('The %s method raised a CookieAuthRejected when cookie auth should be available. Stem may need to be corrected to recognize this response: %s' % (auth_func, exc)) 619 auth_exceptions.append(IncorrectCookieValue(str(exc), exc.cookie_path, exc.is_safecookie)) 620 except stem.ControllerError as exc: 621 auth_exceptions.append(AuthenticationFailure(str(exc))) 622 623 # All authentication attempts failed. Raise the exception that takes priority 624 # according to our pydocs. 625 626 for exc_type in AUTHENTICATE_EXCEPTIONS: 627 for auth_exc in auth_exceptions: 628 if isinstance(auth_exc, exc_type): 629 raise auth_exc 630 631 # We really, really shouldn't get here. It means that auth_exceptions is 632 # either empty or contains something that isn't an AuthenticationFailure. 633 634 raise AssertionError('BUG: Authentication failed without providing a recognized exception: %s' % str(auth_exceptions)) 635 636 637def authenticate_none(controller, suppress_ctl_errors = True): 638 """ 639 Authenticates to an open control socket. All control connections need to 640 authenticate before they can be used, even if tor hasn't been configured to 641 use any authentication. 642 643 If authentication fails tor will disconnect and we'll make a best effort 644 attempt to re-establish the connection. This may not succeed, so check 645 :func:`~stem.socket.ControlSocket.is_alive` before using the socket further. 646 647 This can authenticate to either a :class:`~stem.control.BaseController` or 648 :class:`~stem.socket.ControlSocket`. 649 650 For general usage use the :func:`~stem.connection.authenticate` function 651 instead. 652 653 :param controller: tor controller or socket to be authenticated 654 :param bool suppress_ctl_errors: reports raised 655 :class:`~stem.ControllerError` as authentication rejection if 656 **True**, otherwise they're re-raised 657 658 :raises: :class:`stem.connection.OpenAuthRejected` if the empty authentication credentials aren't accepted 659 """ 660 661 try: 662 auth_response = _msg(controller, 'AUTHENTICATE') 663 664 # if we got anything but an OK response then error 665 if str(auth_response) != 'OK': 666 try: 667 controller.connect() 668 except: 669 pass 670 671 raise OpenAuthRejected(str(auth_response), auth_response) 672 except stem.ControllerError as exc: 673 try: 674 controller.connect() 675 except: 676 pass 677 678 if not suppress_ctl_errors: 679 raise 680 else: 681 raise OpenAuthRejected('Socket failed (%s)' % exc) 682 683 684def authenticate_password(controller, password, suppress_ctl_errors = True): 685 """ 686 Authenticates to a control socket that uses a password (via the 687 HashedControlPassword torrc option). Quotes in the password are escaped. 688 689 If authentication fails tor will disconnect and we'll make a best effort 690 attempt to re-establish the connection. This may not succeed, so check 691 :func:`~stem.socket.ControlSocket.is_alive` before using the socket further. 692 693 If you use this function directly, rather than 694 :func:`~stem.connection.authenticate`, we may mistakenly raise a 695 PasswordAuthRejected rather than IncorrectPassword. This is because we rely 696 on tor's error messaging which is liable to change in future versions 697 (:trac:`4817`). 698 699 This can authenticate to either a :class:`~stem.control.BaseController` or 700 :class:`~stem.socket.ControlSocket`. 701 702 For general usage use the :func:`~stem.connection.authenticate` function 703 instead. 704 705 :param controller: tor controller or socket to be authenticated 706 :param str password: passphrase to present to the socket 707 :param bool suppress_ctl_errors: reports raised 708 :class:`~stem.ControllerError` as authentication rejection if 709 **True**, otherwise they're re-raised 710 711 :raises: 712 * :class:`stem.connection.PasswordAuthRejected` if the socket doesn't 713 accept password authentication 714 * :class:`stem.connection.IncorrectPassword` if the authentication 715 credentials aren't accepted 716 """ 717 718 # Escapes quotes. Tor can include those in the password hash, in which case 719 # it expects escaped quotes from the controller. For more information see... 720 # https://trac.torproject.org/projects/tor/ticket/4600 721 722 password = password.replace('"', '\\"') 723 724 try: 725 auth_response = _msg(controller, 'AUTHENTICATE "%s"' % password) 726 727 # if we got anything but an OK response then error 728 if str(auth_response) != 'OK': 729 try: 730 controller.connect() 731 except: 732 pass 733 734 # all we have to go on is the error message from tor... 735 # Password did not match HashedControlPassword value value from configuration... 736 # Password did not match HashedControlPassword *or*... 737 738 if 'Password did not match HashedControlPassword' in str(auth_response): 739 raise IncorrectPassword(str(auth_response), auth_response) 740 else: 741 raise PasswordAuthRejected(str(auth_response), auth_response) 742 except stem.ControllerError as exc: 743 try: 744 controller.connect() 745 except: 746 pass 747 748 if not suppress_ctl_errors: 749 raise 750 else: 751 raise PasswordAuthRejected('Socket failed (%s)' % exc) 752 753 754def authenticate_cookie(controller, cookie_path, suppress_ctl_errors = True): 755 """ 756 Authenticates to a control socket that uses the contents of an authentication 757 cookie (generated via the CookieAuthentication torrc option). This does basic 758 validation that this is a cookie before presenting the contents to the 759 socket. 760 761 The :class:`~stem.connection.IncorrectCookieSize` and 762 :class:`~stem.connection.UnreadableCookieFile` exceptions take precedence 763 over the other types. 764 765 If authentication fails tor will disconnect and we'll make a best effort 766 attempt to re-establish the connection. This may not succeed, so check 767 :func:`~stem.socket.ControlSocket.is_alive` before using the socket further. 768 769 If you use this function directly, rather than 770 :func:`~stem.connection.authenticate`, we may mistakenly raise a 771 :class:`~stem.connection.CookieAuthRejected` rather than 772 :class:`~stem.connection.IncorrectCookieValue`. This is because we rely on 773 tor's error messaging which is liable to change in future versions 774 (:trac:`4817`). 775 776 This can authenticate to either a :class:`~stem.control.BaseController` or 777 :class:`~stem.socket.ControlSocket`. 778 779 For general usage use the :func:`~stem.connection.authenticate` function 780 instead. 781 782 :param controller: tor controller or socket to be authenticated 783 :param str cookie_path: path of the authentication cookie to send to tor 784 :param bool suppress_ctl_errors: reports raised 785 :class:`~stem.ControllerError` as authentication rejection if 786 **True**, otherwise they're re-raised 787 788 :raises: 789 * :class:`stem.connection.IncorrectCookieSize` if the cookie file's size 790 is wrong 791 * :class:`stem.connection.UnreadableCookieFile` if the cookie file doesn't 792 exist or we're unable to read it 793 * :class:`stem.connection.CookieAuthRejected` if cookie authentication is 794 attempted but the socket doesn't accept it 795 * :class:`stem.connection.IncorrectCookieValue` if the cookie file's value 796 is rejected 797 """ 798 799 cookie_data = _read_cookie(cookie_path, False) 800 801 try: 802 # binascii.b2a_hex() takes a byte string and returns one too. With python 3 803 # this is a problem because string formatting for byte strings includes the 804 # b'' wrapper... 805 # 806 # >>> "AUTHENTICATE %s" % b'content' 807 # "AUTHENTICATE b'content'" 808 # 809 # This seems dumb but oh well. Converting the result to unicode so it won't 810 # misbehave. 811 812 auth_token_hex = binascii.b2a_hex(stem.util.str_tools._to_bytes(cookie_data)) 813 msg = 'AUTHENTICATE %s' % stem.util.str_tools._to_unicode(auth_token_hex) 814 auth_response = _msg(controller, msg) 815 816 # if we got anything but an OK response then error 817 if str(auth_response) != 'OK': 818 try: 819 controller.connect() 820 except: 821 pass 822 823 # all we have to go on is the error message from tor... 824 # ... Authentication cookie did not match expected value. 825 # ... *or* authentication cookie. 826 827 if '*or* authentication cookie.' in str(auth_response) or \ 828 'Authentication cookie did not match expected value.' in str(auth_response): 829 raise IncorrectCookieValue(str(auth_response), cookie_path, False, auth_response) 830 else: 831 raise CookieAuthRejected(str(auth_response), cookie_path, False, auth_response) 832 except stem.ControllerError as exc: 833 try: 834 controller.connect() 835 except: 836 pass 837 838 if not suppress_ctl_errors: 839 raise 840 else: 841 raise CookieAuthRejected('Socket failed (%s)' % exc, cookie_path, False) 842 843 844def authenticate_safecookie(controller, cookie_path, suppress_ctl_errors = True): 845 """ 846 Authenticates to a control socket using the safe cookie method, which is 847 enabled by setting the CookieAuthentication torrc option on Tor client's which 848 support it. 849 850 Authentication with this is a two-step process... 851 852 1. send a nonce to the server and receives a challenge from the server for 853 the cookie's contents 854 2. generate a hash digest using the challenge received in the first step, and 855 use it to authenticate the controller 856 857 The :class:`~stem.connection.IncorrectCookieSize` and 858 :class:`~stem.connection.UnreadableCookieFile` exceptions take precedence 859 over the other exception types. 860 861 The :class:`~stem.connection.AuthChallengeUnsupported`, 862 :class:`~stem.connection.UnrecognizedAuthChallengeMethod`, 863 :class:`~stem.connection.InvalidClientNonce` and 864 :class:`~stem.connection.CookieAuthRejected` exceptions are next in the order 865 of precedence. Depending on the reason, one of these is raised if the first 866 (AUTHCHALLENGE) step fails. 867 868 In the second (AUTHENTICATE) step, 869 :class:`~stem.connection.IncorrectCookieValue` or 870 :class:`~stem.connection.CookieAuthRejected` maybe raised. 871 872 If authentication fails tor will disconnect and we'll make a best effort 873 attempt to re-establish the connection. This may not succeed, so check 874 :func:`~stem.socket.ControlSocket.is_alive` before using the socket further. 875 876 For general usage use the :func:`~stem.connection.authenticate` function 877 instead. 878 879 :param controller: tor controller or socket to be authenticated 880 :param str cookie_path: path of the authentication cookie to send to tor 881 :param bool suppress_ctl_errors: reports raised 882 :class:`~stem.ControllerError` as authentication rejection if 883 **True**, otherwise they're re-raised 884 885 :raises: 886 * :class:`stem.connection.IncorrectCookieSize` if the cookie file's size 887 is wrong 888 * :class:`stem.connection.UnreadableCookieFile` if the cookie file doesn't 889 exist or we're unable to read it 890 * :class:`stem.connection.CookieAuthRejected` if cookie authentication is 891 attempted but the socket doesn't accept it 892 * :class:`stem.connection.IncorrectCookieValue` if the cookie file's value 893 is rejected 894 * :class:`stem.connection.UnrecognizedAuthChallengeMethod` if the Tor 895 client fails to recognize the AuthChallenge method 896 * :class:`stem.connection.AuthChallengeUnsupported` if AUTHCHALLENGE is 897 unimplemented, or if unable to parse AUTHCHALLENGE response 898 * :class:`stem.connection.AuthSecurityFailure` if AUTHCHALLENGE's response 899 looks like a security attack 900 * :class:`stem.connection.InvalidClientNonce` if stem's AUTHCHALLENGE 901 client nonce is rejected for being invalid 902 """ 903 904 cookie_data = _read_cookie(cookie_path, True) 905 client_nonce = os.urandom(32) 906 907 try: 908 client_nonce_hex = stem.util.str_tools._to_unicode(binascii.b2a_hex(client_nonce)) 909 authchallenge_response = _msg(controller, 'AUTHCHALLENGE SAFECOOKIE %s' % client_nonce_hex) 910 911 if not authchallenge_response.is_ok(): 912 try: 913 controller.connect() 914 except: 915 pass 916 917 authchallenge_response_str = str(authchallenge_response) 918 919 if 'Authentication required.' in authchallenge_response_str: 920 raise AuthChallengeUnsupported("SAFECOOKIE authentication isn't supported", cookie_path) 921 elif 'AUTHCHALLENGE only supports' in authchallenge_response_str: 922 raise UnrecognizedAuthChallengeMethod(authchallenge_response_str, cookie_path) 923 elif 'Invalid base16 client nonce' in authchallenge_response_str: 924 raise InvalidClientNonce(authchallenge_response_str, cookie_path) 925 elif 'Cookie authentication is disabled' in authchallenge_response_str: 926 raise CookieAuthRejected(authchallenge_response_str, cookie_path, True) 927 else: 928 raise AuthChallengeFailed(authchallenge_response, cookie_path) 929 except stem.ControllerError as exc: 930 try: 931 controller.connect() 932 except: 933 pass 934 935 if not suppress_ctl_errors: 936 raise 937 else: 938 raise AuthChallengeFailed('Socket failed (%s)' % exc, cookie_path, True) 939 940 try: 941 stem.response.convert('AUTHCHALLENGE', authchallenge_response) 942 except stem.ProtocolError as exc: 943 if not suppress_ctl_errors: 944 raise 945 else: 946 raise AuthChallengeFailed('Unable to parse AUTHCHALLENGE response: %s' % exc, cookie_path) 947 948 expected_server_hash = _hmac_sha256( 949 SERVER_HASH_CONSTANT, 950 cookie_data + client_nonce + authchallenge_response.server_nonce) 951 952 authchallenge_hmac = _hmac_sha256(CRYPTOVARIABLE_EQUALITY_COMPARISON_NONCE, authchallenge_response.server_hash) 953 expected_hmac = _hmac_sha256(CRYPTOVARIABLE_EQUALITY_COMPARISON_NONCE, expected_server_hash) 954 955 if authchallenge_hmac != expected_hmac: 956 raise AuthSecurityFailure('Tor provided the wrong server nonce', cookie_path) 957 958 try: 959 client_hash = _hmac_sha256( 960 CLIENT_HASH_CONSTANT, 961 cookie_data + client_nonce + authchallenge_response.server_nonce) 962 963 auth_response = _msg(controller, 'AUTHENTICATE %s' % stem.util.str_tools._to_unicode(binascii.b2a_hex(client_hash))) 964 except stem.ControllerError as exc: 965 try: 966 controller.connect() 967 except: 968 pass 969 970 if not suppress_ctl_errors: 971 raise 972 else: 973 raise CookieAuthRejected('Socket failed (%s)' % exc, cookie_path, True, auth_response) 974 975 # if we got anything but an OK response then err 976 if not auth_response.is_ok(): 977 try: 978 controller.connect() 979 except: 980 pass 981 982 # all we have to go on is the error message from tor... 983 # ... Safe cookie response did not match expected value 984 # ... *or* authentication cookie. 985 986 if '*or* authentication cookie.' in str(auth_response) or \ 987 'Safe cookie response did not match expected value' in str(auth_response): 988 raise IncorrectCookieValue(str(auth_response), cookie_path, True, auth_response) 989 else: 990 raise CookieAuthRejected(str(auth_response), cookie_path, True, auth_response) 991 992 993def get_protocolinfo(controller): 994 """ 995 Issues a PROTOCOLINFO query to a control socket, getting information about 996 the tor process running on it. If the socket is already closed then it is 997 first reconnected. 998 999 This can authenticate to either a :class:`~stem.control.BaseController` or 1000 :class:`~stem.socket.ControlSocket`. 1001 1002 :param controller: tor controller or socket to be queried 1003 1004 :returns: :class:`~stem.response.protocolinfo.ProtocolInfoResponse` provided by tor 1005 1006 :raises: 1007 * :class:`stem.ProtocolError` if the PROTOCOLINFO response is 1008 malformed 1009 * :class:`stem.SocketError` if problems arise in establishing or 1010 using the socket 1011 """ 1012 1013 try: 1014 protocolinfo_response = _msg(controller, 'PROTOCOLINFO 1') 1015 except: 1016 protocolinfo_response = None 1017 1018 # Tor hangs up on sockets after receiving a PROTOCOLINFO query if it isn't 1019 # next followed by authentication. Transparently reconnect if that happens. 1020 1021 if not protocolinfo_response or str(protocolinfo_response) == 'Authentication required.': 1022 controller.connect() 1023 1024 try: 1025 protocolinfo_response = _msg(controller, 'PROTOCOLINFO 1') 1026 except stem.SocketClosed as exc: 1027 raise stem.SocketError(exc) 1028 1029 stem.response.convert('PROTOCOLINFO', protocolinfo_response) 1030 return protocolinfo_response 1031 1032 1033def _msg(controller, message): 1034 """ 1035 Sends and receives a message with either a 1036 :class:`~stem.socket.ControlSocket` or :class:`~stem.control.BaseController`. 1037 """ 1038 1039 if isinstance(controller, stem.socket.ControlSocket): 1040 controller.send(message) 1041 return controller.recv() 1042 else: 1043 return controller.msg(message) 1044 1045 1046def _connection_for_default_port(address): 1047 """ 1048 Attempts to provide a controller connection for either port 9051 (default for 1049 relays) or 9151 (default for Tor Browser). If both fail then this raises the 1050 exception for port 9051. 1051 1052 :param str address: address to connect to 1053 1054 :returns: :class:`~stem.socket.ControlPort` for the controller conneciton 1055 1056 :raises: :class:`stem.SocketError` if we're unable to establish a connection 1057 """ 1058 1059 try: 1060 return stem.socket.ControlPort(address, 9051) 1061 except stem.SocketError as exc: 1062 try: 1063 return stem.socket.ControlPort(address, 9151) 1064 except stem.SocketError: 1065 raise exc 1066 1067 1068def _read_cookie(cookie_path, is_safecookie): 1069 """ 1070 Provides the contents of a given cookie file. 1071 1072 :param str cookie_path: absolute path of the cookie file 1073 :param bool is_safecookie: **True** if this was for SAFECOOKIE 1074 authentication, **False** if for COOKIE 1075 1076 :raises: 1077 * :class:`stem.connection.UnreadableCookieFile` if the cookie file is 1078 unreadable 1079 * :class:`stem.connection.IncorrectCookieSize` if the cookie size is 1080 incorrect (not 32 bytes) 1081 """ 1082 1083 if not os.path.exists(cookie_path): 1084 exc_msg = "Authentication failed: '%s' doesn't exist" % cookie_path 1085 raise UnreadableCookieFile(exc_msg, cookie_path, is_safecookie) 1086 1087 # Abort if the file isn't 32 bytes long. This is to avoid exposing arbitrary 1088 # file content to the port. 1089 # 1090 # Without this a malicious socket could, for instance, claim that 1091 # '~/.bash_history' or '~/.ssh/id_rsa' was its authentication cookie to trick 1092 # us into reading it for them with our current permissions. 1093 # 1094 # https://trac.torproject.org/projects/tor/ticket/4303 1095 1096 auth_cookie_size = os.path.getsize(cookie_path) 1097 1098 if auth_cookie_size != 32: 1099 exc_msg = "Authentication failed: authentication cookie '%s' is the wrong size (%i bytes instead of 32)" % (cookie_path, auth_cookie_size) 1100 raise IncorrectCookieSize(exc_msg, cookie_path, is_safecookie) 1101 1102 try: 1103 with open(cookie_path, 'rb', 0) as f: 1104 return f.read() 1105 except IOError as exc: 1106 exc_msg = "Authentication failed: unable to read '%s' (%s)" % (cookie_path, exc) 1107 raise UnreadableCookieFile(exc_msg, cookie_path, is_safecookie) 1108 1109 1110def _hmac_sha256(key, msg): 1111 """ 1112 Generates a sha256 digest using the given key and message. 1113 1114 :param str key: starting key for the hash 1115 :param str msg: message to be hashed 1116 1117 :returns: sha256 digest of msg as bytes, hashed using the given key 1118 """ 1119 1120 return hmac.new(key, msg, hashlib.sha256).digest() 1121 1122 1123class AuthenticationFailure(Exception): 1124 """ 1125 Base error for authentication failures. 1126 1127 :var stem.socket.ControlMessage auth_response: AUTHENTICATE response from the 1128 control socket, **None** if one wasn't received 1129 """ 1130 1131 def __init__(self, message, auth_response = None): 1132 super(AuthenticationFailure, self).__init__(message) 1133 self.auth_response = auth_response 1134 1135 1136class UnrecognizedAuthMethods(AuthenticationFailure): 1137 """ 1138 All methods for authenticating aren't recognized. 1139 1140 :var list unknown_auth_methods: authentication methods that weren't recognized 1141 """ 1142 1143 def __init__(self, message, unknown_auth_methods): 1144 super(UnrecognizedAuthMethods, self).__init__(message) 1145 self.unknown_auth_methods = unknown_auth_methods 1146 1147 1148class IncorrectSocketType(AuthenticationFailure): 1149 'Socket does not speak the control protocol.' 1150 1151 1152class OpenAuthFailed(AuthenticationFailure): 1153 'Failure to authenticate to an open socket.' 1154 1155 1156class OpenAuthRejected(OpenAuthFailed): 1157 'Attempt to connect to an open control socket was rejected.' 1158 1159 1160class PasswordAuthFailed(AuthenticationFailure): 1161 'Failure to authenticate with a password.' 1162 1163 1164class PasswordAuthRejected(PasswordAuthFailed): 1165 'Socket does not support password authentication.' 1166 1167 1168class IncorrectPassword(PasswordAuthFailed): 1169 'Authentication password incorrect.' 1170 1171 1172class MissingPassword(PasswordAuthFailed): 1173 "Password authentication is supported but we weren't provided with one." 1174 1175 1176class CookieAuthFailed(AuthenticationFailure): 1177 """ 1178 Failure to authenticate with an authentication cookie. 1179 1180 :param str cookie_path: location of the authentication cookie we attempted 1181 :param bool is_safecookie: **True** if this was for SAFECOOKIE 1182 authentication, **False** if for COOKIE 1183 :param stem.response.ControlMessage auth_response: reply to our 1184 authentication attempt 1185 """ 1186 1187 def __init__(self, message, cookie_path, is_safecookie, auth_response = None): 1188 super(CookieAuthFailed, self).__init__(message, auth_response) 1189 self.is_safecookie = is_safecookie 1190 self.cookie_path = cookie_path 1191 1192 1193class CookieAuthRejected(CookieAuthFailed): 1194 'Socket does not support password authentication.' 1195 1196 1197class IncorrectCookieValue(CookieAuthFailed): 1198 'Authentication cookie value was rejected.' 1199 1200 1201class IncorrectCookieSize(CookieAuthFailed): 1202 'Aborted because the cookie file is the wrong size.' 1203 1204 1205class UnreadableCookieFile(CookieAuthFailed): 1206 'Error arose in reading the authentication cookie.' 1207 1208 1209class AuthChallengeFailed(CookieAuthFailed): 1210 """ 1211 AUTHCHALLENGE command has failed. 1212 """ 1213 1214 def __init__(self, message, cookie_path): 1215 super(AuthChallengeFailed, self).__init__(message, cookie_path, True) 1216 1217 1218class AuthChallengeUnsupported(AuthChallengeFailed): 1219 """ 1220 AUTHCHALLENGE isn't implemented. 1221 """ 1222 1223 1224class UnrecognizedAuthChallengeMethod(AuthChallengeFailed): 1225 """ 1226 Tor couldn't recognize our AUTHCHALLENGE method. 1227 1228 :var str authchallenge_method: AUTHCHALLENGE method that Tor couldn't recognize 1229 """ 1230 1231 def __init__(self, message, cookie_path, authchallenge_method): 1232 super(UnrecognizedAuthChallengeMethod, self).__init__(message, cookie_path) 1233 self.authchallenge_method = authchallenge_method 1234 1235 1236class AuthSecurityFailure(AuthChallengeFailed): 1237 'AUTHCHALLENGE response is invalid.' 1238 1239 1240class InvalidClientNonce(AuthChallengeFailed): 1241 'AUTHCHALLENGE request contains an invalid client nonce.' 1242 1243 1244class MissingAuthInfo(AuthenticationFailure): 1245 """ 1246 The PROTOCOLINFO response didn't have enough information to authenticate. 1247 These are valid control responses but really shouldn't happen in practice. 1248 """ 1249 1250 1251class NoAuthMethods(MissingAuthInfo): 1252 "PROTOCOLINFO response didn't have any methods for authenticating." 1253 1254 1255class NoAuthCookie(MissingAuthInfo): 1256 """ 1257 PROTOCOLINFO response supports cookie auth but doesn't have its path. 1258 1259 :param bool is_safecookie: **True** if this was for SAFECOOKIE 1260 authentication, **False** if for COOKIE 1261 """ 1262 1263 def __init__(self, message, is_safecookie): 1264 super(NoAuthCookie, self).__init__(message) 1265 self.is_safecookie = is_safecookie 1266 1267 1268# authentication exceptions ordered as per the authenticate function's pydocs 1269 1270AUTHENTICATE_EXCEPTIONS = ( 1271 IncorrectSocketType, 1272 UnrecognizedAuthMethods, 1273 MissingPassword, 1274 IncorrectPassword, 1275 IncorrectCookieSize, 1276 UnreadableCookieFile, 1277 IncorrectCookieValue, 1278 AuthChallengeUnsupported, 1279 UnrecognizedAuthChallengeMethod, 1280 InvalidClientNonce, 1281 AuthSecurityFailure, 1282 OpenAuthRejected, 1283 MissingAuthInfo, 1284 AuthenticationFailure 1285) 1286