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