Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Tab to spaces, no functional changes.

  • Loading branch information...
commit 8c8f40287986f9cc76beb728c737ce39133fec66 1 parent b38e555
@Kami authored
View
28 demo/example.py
@@ -8,28 +8,28 @@
token = raw_input('Enter OTP token: ')
if not secret_key:
- secret_key = None
-
+ secret_key = None
+
if use_https == 'n':
- https = False
+ https = False
else:
- https = True
+ https = True
client = yubico.Yubico(client_id, secret_key, https)
try:
- status = client.verify(token)
+ status = client.verify(token)
except yubico_exceptions.InvalidClientIdError, e:
- print 'Client with id %s does not exist' % (e.client_id)
- sys.exit(1)
+ print 'Client with id %s does not exist' % (e.client_id)
+ sys.exit(1)
except yubico_exceptions.SignatureVerificationError:
- print 'Signature verification failed'
- sys.exit(1)
+ print 'Signature verification failed'
+ sys.exit(1)
except yubico_exceptions.StatusCodeError, e:
- print 'Negative status code was returned: %s' % (e.status_code)
- sys.exit(1)
-
+ print 'Negative status code was returned: %s' % (e.status_code)
+ sys.exit(1)
+
if status:
- print 'Success, the provided OTP is valid'
+ print 'Success, the provided OTP is valid'
else:
- print 'No response from the servers or received other negative status code'
+ print 'No response from the servers or received other negative status code'
View
44 setup.py
@@ -21,25 +21,25 @@
fp.close()
setup(name = 'yubico',
- version = '.' . join(map(str, version)),
- description = 'Python Yubico Client',
- author = 'Tomaž Muraus',
- author_email = 'kami@k5-storitve.net',
- license = 'BSD',
- url = 'http://github.com/Kami/python-yubico-client/',
- download_url = 'http://github.com/Kami/python-yubico-client/downloads/',
- packages = ['yubico'],
- provides = ['yubico'],
-
- classifiers = [
- 'Development Status :: 4 - Beta',
- 'Environment :: Console',
- 'Intended Audience :: Developers',
- 'Intended Audience :: System Administrators',
- 'License :: OSI Approved :: BSD License',
- 'Operating System :: OS Independent',
- 'Programming Language :: Python',
- 'Topic :: Security',
- 'Topic :: Software Development :: Libraries :: Python Modules',
- ],
-)
+ version = '.' . join(map(str, version)),
+ description = 'Python Yubico Client',
+ author = 'Tomaž Muraus',
+ author_email = 'kami@k5-storitve.net',
+ license = 'BSD',
+ url = 'http://github.com/Kami/python-yubico-client/',
+ download_url = 'http://github.com/Kami/python-yubico-client/downloads/',
+ packages = ['yubico'],
+ provides = ['yubico'],
+
+ classifiers = [
+ 'Development Status :: 4 - Beta',
+ 'Environment :: Console',
+ 'Intended Audience :: Developers',
+ 'Intended Audience :: System Administrators',
+ 'License :: OSI Approved :: BSD License',
+ 'Operating System :: OS Independent',
+ 'Programming Language :: Python',
+ 'Topic :: Security',
+ 'Topic :: Software Development :: Libraries :: Python Modules',
+ ]
+)
View
96 yubico/httplib_ssl.py
@@ -10,54 +10,54 @@
CA_CERTS = ''
class VerifiedHTTPSConnection(httplib.HTTPSConnection):
- def connect(self):
- sock = socket.create_connection((self.host, self.port),
- self.timeout)
- if self._tunnel_host:
- self.sock = sock
- self._tunnel()
-
- self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file, \
- cert_reqs = ssl.CERT_REQUIRED, ca_certs = CA_CERTS, \
- ssl_version = ssl.PROTOCOL_TLSv1)
-
- cert = self.sock.getpeercert()
- if not self._verify_hostname(self.host, cert):
- raise ssl.SSLError('Failed to verify hostname')
-
- def _verify_hostname(self, hostname, cert):
- common_name = self._get_commonName(cert)
- alt_names = self._get_subjectAltName(cert)
-
- if (hostname == common_name) or hostname in alt_names:
- return True
-
- return False
-
- def _get_subjectAltName(self, cert):
- if not cert.has_key('subjectAltName'):
- return None
-
- alt_names = []
- for value in cert['subjectAltName']:
- if value[0].lower() == 'dns':
- alt_names.append(value[0])
-
- return alt_names
-
- def _get_commonName(self, cert):
- if not cert.has_key('subject'):
- return None
-
- for value in cert['subject']:
- if value[0][0].lower() == 'commonname':
- return value[0][1]
- return None
+ def connect(self):
+ sock = socket.create_connection((self.host, self.port),
+ self.timeout)
+ if self._tunnel_host:
+ self.sock = sock
+ self._tunnel()
+
+ self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file, \
+ cert_reqs = ssl.CERT_REQUIRED, ca_certs = CA_CERTS, \
+ ssl_version = ssl.PROTOCOL_TLSv1)
+
+ cert = self.sock.getpeercert()
+ if not self._verify_hostname(self.host, cert):
+ raise ssl.SSLError('Failed to verify hostname')
+
+ def _verify_hostname(self, hostname, cert):
+ common_name = self._get_commonName(cert)
+ alt_names = self._get_subjectAltName(cert)
+
+ if (hostname == common_name) or hostname in alt_names:
+ return True
+
+ return False
+
+ def _get_subjectAltName(self, cert):
+ if not cert.has_key('subjectAltName'):
+ return None
+
+ alt_names = []
+ for value in cert['subjectAltName']:
+ if value[0].lower() == 'dns':
+ alt_names.append(value[0])
+
+ return alt_names
+
+ def _get_commonName(self, cert):
+ if not cert.has_key('subject'):
+ return None
+
+ for value in cert['subject']:
+ if value[0][0].lower() == 'commonname':
+ return value[0][1]
+ return None
class VerifiedHTTPSHandler(urllib2.HTTPSHandler):
- def __init__(self, connection_class = VerifiedHTTPSConnection):
- self.specialized_conn_class = connection_class
- urllib2.HTTPSHandler.__init__(self)
+ def __init__(self, connection_class = VerifiedHTTPSConnection):
+ self.specialized_conn_class = connection_class
+ urllib2.HTTPSHandler.__init__(self)
- def https_open(self, req):
- return self.do_open(self.specialized_conn_class, req)
+ def https_open(self, req):
+ return self.do_open(self.specialized_conn_class, req)
View
44 yubico/otp.py
@@ -5,25 +5,25 @@
import modhex
class OTP():
- def __init__(self, otp, translate_otp = True):
- self.otp = self.get_otp_modehex_interpretation(otp) \
- if translate_otp else otp
-
- self.device_id = self.otp[:12]
- self.session_counter = None
- self.timestamp = None
- self.session_user = None
-
- def get_otp_modehex_interpretation(self, otp):
- # We only use the first interpretation, because
- # if the OTP uses all 16 characters in its alphabet
- # there is only one possible interpretation of that otp
- try:
- otp_translated = modhex.translate(unicode(otp)).pop()
- except Exception:
- otp_translated = otp
-
- return otp_translated
-
- def __repr__(self):
- return '%s, %s, %s' % (self.otp, self.device_id, self.timestamp)
+ def __init__(self, otp, translate_otp = True):
+ self.otp = self.get_otp_modehex_interpretation(otp) \
+ if translate_otp else otp
+
+ self.device_id = self.otp[:12]
+ self.session_counter = None
+ self.timestamp = None
+ self.session_user = None
+
+ def get_otp_modehex_interpretation(self, otp):
+ # We only use the first interpretation, because
+ # if the OTP uses all 16 characters in its alphabet
+ # there is only one possible interpretation of that otp
+ try:
+ otp_translated = modhex.translate(unicode(otp)).pop()
+ except Exception:
+ otp_translated = otp
+
+ return otp_translated
+
+ def __repr__(self):
+ return '%s, %s, %s' % (self.otp, self.device_id, self.timestamp)
View
482 yubico/yubico.py
@@ -5,9 +5,9 @@
#
# This client is based on the Validation Protocol Version 2.0 so it sends
# the verification request to all the servers in parallel and it returns when
-# the first positive (STATUS=OK) or negative (STATUS=REPLAYED_OTP)response is
+# the first positive (STATUS=OK) or negative (STATUS=REPLAYED_OTP) response is
# received.
-#
+#
# Author: Tomaž Muraus (http://www.tomaz-muraus.info)
# License: BSD
# Version: 1.4
@@ -31,248 +31,248 @@
from yubico_exceptions import *
try:
- import httplib_ssl
+ import httplib_ssl
except ImportError:
- httplib_ssl = None
+ httplib_ssl = None
API_URLS = ('api.yubico.com/wsapi/2.0/verify',
- 'api2.yubico.com/wsapi/2.0/verify',
- 'api3.yubico.com/wsapi/2.0/verify',
- 'api4.yubico.com/wsapi/2.0/verify',
- 'api5.yubico.com/wsapi/2.0/verify')
-TIMEOUT = 10 # How long to wait before the time out occurs
-MAX_TIME_WINDOW = 40 # How many seconds can pass between the first and last OTP generations
- # so the OTP is still considered valid (only used in the multi mode)
- # default is 5 seconds (40 / 0.125 = 5)
+ 'api2.yubico.com/wsapi/2.0/verify',
+ 'api3.yubico.com/wsapi/2.0/verify',
+ 'api4.yubico.com/wsapi/2.0/verify',
+ 'api5.yubico.com/wsapi/2.0/verify')
+TIMEOUT = 10 # How long to wait before the time out occurs
+MAX_TIME_WINDOW = 40 # How many seconds can pass between the first and last OTP generations
+ # so the OTP is still considered valid (only used in the multi mode)
+ # default is 5 seconds (40 / 0.125 = 5)
class Yubico():
- def __init__(self, client_id, key = None, use_https = True, verify_cert = False, \
- translate_otp = True):
-
- if use_https and not httplib_ssl:
- raise Exception('SSL support not available')
-
- if use_https and httplib_ssl and httplib_ssl.CA_CERTS == '':
- raise Exception('If you want to validate server certificate, you need to set CA_CERTS '
- 'variable in the httplib_ssl.py file pointing to a file which '
- 'contains a list of trusted CA certificates')
-
- self.client_id = client_id
- self.key = base64.b64decode(key) if key is not None else None
- self.use_https = use_https
- self.verify_cert = verify_cert
- self.translate_otp = translate_otp
-
- def verify(self, otp, timestamp = False, sl = None, timeout = None, return_response = False):
- """
- Returns True is the provided OTP is valid,
- False if the REPLAYED_OTP status value is returned or the response
- message signature verification failed and None for the rest of the status values.
- """
- otp = OTP(otp, self.translate_otp)
- nonce = base64.b64encode(os.urandom(30), 'xz')[:25]
- query_string = self.generate_query_string(otp.otp, nonce, timestamp, sl, timeout)
- request_urls = self.generate_request_urls()
-
- threads = []
- timeout = timeout or TIMEOUT
- for url in request_urls:
- thread = URLThread('%s?%s' % (url, query_string), timeout, self.verify_cert)
- thread.start()
- threads.append(thread)
-
- # Wait for a first positive or negative response
- start_time = time.time()
- while threads and (start_time + timeout) > time.time():
- for thread in threads:
- if not thread.is_alive() and thread.response:
- status = self.verify_response(thread.response, return_response)
-
- if status:
- if return_response:
- return status
- else:
- return True
- threads.remove(thread)
-
- return None
-
- def verify_multi(self, otp_list = None, max_time_window = None, sl = None, timeout = None):
- # Create the OTP objects
- otps = []
- for otp in otp_list:
- otps.append(OTP(otp, self.translate_otp))
-
- device_ids = set()
- for otp in otps:
- device_ids.add(otp.device_id)
-
- # Check that all the OTPs contain same device id
- if len (device_ids) != 1:
- return False
-
- # Now we verify the OTPs and save the server response for each OTP.
- # We need the server response, to retrieve the timestamp.
- # It's possible to retrieve this value locally, without querying the server
- # but in this case, user would need to provide his AES key.
- for otp in otps:
- response = self.verify(otp.otp, True, sl, timeout, return_response = True)
-
- if not response:
- return False
-
- otp.timestamp = int(response['timestamp'])
-
- count = len(otps)
- delta = otps[count - 1].timestamp - otps[0].timestamp
-
- max_time_window = (max_time_window / 0.125) if max_time_window else None
- max_time_window = max_time_window or MAX_TIME_WINDOW
- if delta > max_time_window:
- return False
-
- return True
-
- def verify_response(self, response, return_response = False):
- """
- Returns True if the OTP is valid (status=OK) and return_response = False,
- otherwise (return_response = True) it returns the server response as a dictionary.
-
- Throws an exception if the OTP is replayed, the server response message
- verification failed or the client id is invalid, returns False otherwise.
- """
- try:
- try:
- status = re.search(r'status=([a-zA-Z0-9_]+)', response) \
- .groups()[0]
- except AttributeError, IndexError:
- return False
-
- # Secret key is specified, so we verify the response message
- # signature
- if self.key != None:
- signature, parameters = self.parse_parameters_from_response(response)
- generated_signature = self.generate_message_signature(parameters)
-
- # Signature located in the response does not match the one we have
- # generated
- if signature != generated_signature:
- raise SignatureVerificationError(generated_signature, signature)
- except KeyError:
- # Missing status code, malformed response?
- return False
-
- if status == 'OK':
- if return_response:
- query_string = self.parse_parameters_from_response(response)[1]
- response = self.get_parameters_as_dictionary(query_string)
-
- return response
- else:
- return True
- elif status == 'NO_SUCH_CLIENT':
- raise InvalidClientIdError(self.client_id)
- elif status == 'REPLAYED_OTP':
- raise StatusCodeError('REPLAYED_OTP')
-
- return False
-
- def generate_query_string(self, otp, nonce, timestamp = False, sl = None, timeout = None):
- """
- Returns a query string which is sent to the validation servers.
- """
- data = [('id', self.client_id),
- ('otp', otp),
- ('nonce', nonce)]
-
- if timestamp:
- data.append(('timestamp', '1'))
-
- if sl:
- if sl not in range(0,101) and sl not in ['fast', 'secure']:
- raise Exception('sl parameter value must be between 0 and 100 or string "fast" or "secure"')
-
- data.append(('sl', sl))
-
- if timeout:
- data.append(('timeout', timeout))
-
- query_string = urllib.urlencode(data)
-
- if self.key:
- hmac_signature = self.generate_message_signature(query_string)
- query_string += '&h=%s' % (hmac_signature.replace('+', '%2B'))
-
- return query_string
-
- def generate_message_signature(self, query_string):
- """
- Returns a HMAC-SHA-1 signature for the given query string.
- http://code.google.com/p/yubikey-val-server-php/wiki/ValidationProtocolV20
- """
- pairs = query_string.split('&')
- pairs = [pair.split('=') for pair in pairs]
- pairs_sorted = sorted(pairs)
- pairs_string = '&' . join(['=' . join(pair) for pair in pairs_sorted])
-
- digest = hmac.new(self.key, pairs_string, hashlib.sha1).digest()
- signature = base64.b64encode(digest)
-
- return signature
-
- def parse_parameters_from_response(self, response):
- """
- Returns a response signature and query string generated from the server response.
- """
- splitted = [pair.strip() for pair in response.split('\n') if pair.strip() != '']
- signature = splitted[0].replace('h=', '')
- query_string = '&' . join(splitted[1:])
-
- return (signature, query_string)
-
- def get_parameters_as_dictionary(self, query_string):
- """ Returns query string parameters as a dictionary. """
- dictionary = dict([parameter.split('=') for parameter \
- in query_string.split('&')])
-
- return dictionary
-
- def generate_request_urls(self):
- """
- Returns a list of the API URLs.
- """
- urls = []
- for url in API_URLS:
- if self.use_https:
- url = 'https://%s' % (url)
- else:
- url = 'http://%s' % (url)
- urls.append(url)
-
- return urls
-
+ def __init__(self, client_id, key = None, use_https = True, verify_cert = False, \
+ translate_otp = True):
+
+ if use_https and not httplib_ssl:
+ raise Exception('SSL support not available')
+
+ if use_https and httplib_ssl and httplib_ssl.CA_CERTS == '':
+ raise Exception('If you want to validate server certificate, you need to set CA_CERTS '
+ 'variable in the httplib_ssl.py file pointing to a file which '
+ 'contains a list of trusted CA certificates')
+
+ self.client_id = client_id
+ self.key = base64.b64decode(key) if key is not None else None
+ self.use_https = use_https
+ self.verify_cert = verify_cert
+ self.translate_otp = translate_otp
+
+ def verify(self, otp, timestamp = False, sl = None, timeout = None, return_response = False):
+ """
+ Returns True is the provided OTP is valid,
+ False if the REPLAYED_OTP status value is returned or the response
+ message signature verification failed and None for the rest of the status values.
+ """
+ otp = OTP(otp, self.translate_otp)
+ nonce = base64.b64encode(os.urandom(30), 'xz')[:25]
+ query_string = self.generate_query_string(otp.otp, nonce, timestamp, sl, timeout)
+ request_urls = self.generate_request_urls()
+
+ threads = []
+ timeout = timeout or TIMEOUT
+ for url in request_urls:
+ thread = URLThread('%s?%s' % (url, query_string), timeout, self.verify_cert)
+ thread.start()
+ threads.append(thread)
+
+ # Wait for a first positive or negative response
+ start_time = time.time()
+ while threads and (start_time + timeout) > time.time():
+ for thread in threads:
+ if not thread.is_alive() and thread.response:
+ status = self.verify_response(thread.response, return_response)
+
+ if status:
+ if return_response:
+ return status
+ else:
+ return True
+ threads.remove(thread)
+
+ return None
+
+ def verify_multi(self, otp_list = None, max_time_window = None, sl = None, timeout = None):
+ # Create the OTP objects
+ otps = []
+ for otp in otp_list:
+ otps.append(OTP(otp, self.translate_otp))
+
+ device_ids = set()
+ for otp in otps:
+ device_ids.add(otp.device_id)
+
+ # Check that all the OTPs contain same device id
+ if len (device_ids) != 1:
+ return False
+
+ # Now we verify the OTPs and save the server response for each OTP.
+ # We need the server response, to retrieve the timestamp.
+ # It's possible to retrieve this value locally, without querying the server
+ # but in this case, user would need to provide his AES key.
+ for otp in otps:
+ response = self.verify(otp.otp, True, sl, timeout, return_response = True)
+
+ if not response:
+ return False
+
+ otp.timestamp = int(response['timestamp'])
+
+ count = len(otps)
+ delta = otps[count - 1].timestamp - otps[0].timestamp
+
+ max_time_window = (max_time_window / 0.125) if max_time_window else None
+ max_time_window = max_time_window or MAX_TIME_WINDOW
+ if delta > max_time_window:
+ return False
+
+ return True
+
+ def verify_response(self, response, return_response = False):
+ """
+ Returns True if the OTP is valid (status=OK) and return_response = False,
+ otherwise (return_response = True) it returns the server response as a dictionary.
+
+ Throws an exception if the OTP is replayed, the server response message
+ verification failed or the client id is invalid, returns False otherwise.
+ """
+ try:
+ try:
+ status = re.search(r'status=([a-zA-Z0-9_]+)', response) \
+ .groups()[0]
+ except AttributeError, IndexError:
+ return False
+
+ # Secret key is specified, so we verify the response message
+ # signature
+ if self.key != None:
+ signature, parameters = self.parse_parameters_from_response(response)
+ generated_signature = self.generate_message_signature(parameters)
+
+ # Signature located in the response does not match the one we have
+ # generated
+ if signature != generated_signature:
+ raise SignatureVerificationError(generated_signature, signature)
+ except KeyError:
+ # Missing status code, malformed response?
+ return False
+
+ if status == 'OK':
+ if return_response:
+ query_string = self.parse_parameters_from_response(response)[1]
+ response = self.get_parameters_as_dictionary(query_string)
+
+ return response
+ else:
+ return True
+ elif status == 'NO_SUCH_CLIENT':
+ raise InvalidClientIdError(self.client_id)
+ elif status == 'REPLAYED_OTP':
+ raise StatusCodeError('REPLAYED_OTP')
+
+ return False
+
+ def generate_query_string(self, otp, nonce, timestamp = False, sl = None, timeout = None):
+ """
+ Returns a query string which is sent to the validation servers.
+ """
+ data = [('id', self.client_id),
+ ('otp', otp),
+ ('nonce', nonce)]
+
+ if timestamp:
+ data.append(('timestamp', '1'))
+
+ if sl:
+ if sl not in range(0,101) and sl not in ['fast', 'secure']:
+ raise Exception('sl parameter value must be between 0 and 100 or string "fast" or "secure"')
+
+ data.append(('sl', sl))
+
+ if timeout:
+ data.append(('timeout', timeout))
+
+ query_string = urllib.urlencode(data)
+
+ if self.key:
+ hmac_signature = self.generate_message_signature(query_string)
+ query_string += '&h=%s' % (hmac_signature.replace('+', '%2B'))
+
+ return query_string
+
+ def generate_message_signature(self, query_string):
+ """
+ Returns a HMAC-SHA-1 signature for the given query string.
+ http://code.google.com/p/yubikey-val-server-php/wiki/ValidationProtocolV20
+ """
+ pairs = query_string.split('&')
+ pairs = [pair.split('=') for pair in pairs]
+ pairs_sorted = sorted(pairs)
+ pairs_string = '&' . join(['=' . join(pair) for pair in pairs_sorted])
+
+ digest = hmac.new(self.key, pairs_string, hashlib.sha1).digest()
+ signature = base64.b64encode(digest)
+
+ return signature
+
+ def parse_parameters_from_response(self, response):
+ """
+ Returns a response signature and query string generated from the server response.
+ """
+ splitted = [pair.strip() for pair in response.split('\n') if pair.strip() != '']
+ signature = splitted[0].replace('h=', '')
+ query_string = '&' . join(splitted[1:])
+
+ return (signature, query_string)
+
+ def get_parameters_as_dictionary(self, query_string):
+ """ Returns query string parameters as a dictionary. """
+ dictionary = dict([parameter.split('=') for parameter \
+ in query_string.split('&')])
+
+ return dictionary
+
+ def generate_request_urls(self):
+ """
+ Returns a list of the API URLs.
+ """
+ urls = []
+ for url in API_URLS:
+ if self.use_https:
+ url = 'https://%s' % (url)
+ else:
+ url = 'http://%s' % (url)
+ urls.append(url)
+
+ return urls
+
class URLThread(threading.Thread):
- def __init__(self, url, timeout, verify_cert):
- super(URLThread, self).__init__()
- self.url = url
- self.timeout = timeout
- self.verify_cert = verify_cert
- self.request = None
- self.response = None
-
- if int(sys.version[0]) == 2 and int(sys.version[2]) <= 5:
- self.is_alive = self.isAlive
-
- def run(self):
- socket.setdefaulttimeout(self.timeout)
-
- if self.url.startswith('https') and self.verify_cert:
- handler = httplib_ssl.VerifiedHTTPSHandler()
- opener = urllib2.build_opener(handler)
- urllib2.install_opener(opener)
-
- try:
- self.request = urllib2.urlopen(self.url)
- self.response = self.request.read()
- except Exception, e:
- self.response = None
+ def __init__(self, url, timeout, verify_cert):
+ super(URLThread, self).__init__()
+ self.url = url
+ self.timeout = timeout
+ self.verify_cert = verify_cert
+ self.request = None
+ self.response = None
+
+ if int(sys.version[0]) == 2 and int(sys.version[2]) <= 5:
+ self.is_alive = self.isAlive
+
+ def run(self):
+ socket.setdefaulttimeout(self.timeout)
+
+ if self.url.startswith('https') and self.verify_cert:
+ handler = httplib_ssl.VerifiedHTTPSHandler()
+ opener = urllib2.build_opener(handler)
+ urllib2.install_opener(opener)
+
+ try:
+ self.request = urllib2.urlopen(self.url)
+ self.response = self.request.read()
+ except Exception, e:
+ self.response = None
View
42 yubico/yubico_exceptions.py
@@ -1,26 +1,26 @@
class YubicoError(Exception):
- """ Base class for Yubico related exceptions. """
- pass
+ """ Base class for Yubico related exceptions. """
+ pass
class StatusCodeError(YubicoError):
- def __init__(self, status_code):
- self.status_code = status_code
-
- def __str__(self):
- return 'Yubico server returned the following status code: %s' % (self.status_code)
-
+ def __init__(self, status_code):
+ self.status_code = status_code
+
+ def __str__(self):
+ return 'Yubico server returned the following status code: %s' % (self.status_code)
+
class InvalidClientIdError(YubicoError):
- def __init__(self, client_id):
- self.client_id = client_id
-
- def __str__(self):
- return 'The client with ID %s does not exist' % (self.client_id)
-
+ def __init__(self, client_id):
+ self.client_id = client_id
+
+ def __str__(self):
+ return 'The client with ID %s does not exist' % (self.client_id)
+
class SignatureVerificationError(YubicoError):
- def __init__(self, generated_signature, response_signature):
- self.generated_signature = generated_signature
- self.response_signature = response_signature
-
- def __str__(self):
- return repr('Server response message signature verification failed (expected %s, got %s)' \
- % (self.generated_signature, self.response_signature))
+ def __init__(self, generated_signature, response_signature):
+ self.generated_signature = generated_signature
+ self.response_signature = response_signature
+
+ def __str__(self):
+ return repr('Server response message signature verification failed (expected %s, got %s)' \
+ % (self.generated_signature, self.response_signature))
Please sign in to comment.
Something went wrong with that request. Please try again.