Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Tab to spaces, no functional changes.

  • Loading branch information...
commit 8c8f40287986f9cc76beb728c737ce39133fec66 1 parent b38e555
Tomaz Muraus authored November 18, 2011
28  demo/example.py
@@ -8,28 +8,28 @@
8 8
 token = raw_input('Enter OTP token: ')
9 9
 
10 10
 if not secret_key:
11  
-	secret_key = None
12  
-	
  11
+    secret_key = None
  12
+
13 13
 if use_https == 'n':
14  
-	https = False
  14
+    https = False
15 15
 else:
16  
-	https = True
  16
+    https = True
17 17
 
18 18
 client = yubico.Yubico(client_id, secret_key, https)
19 19
 
20 20
 try:
21  
-	status = client.verify(token)
  21
+    status = client.verify(token)
22 22
 except yubico_exceptions.InvalidClientIdError, e:
23  
-	print 'Client with id %s does not exist' % (e.client_id)
24  
-	sys.exit(1)
  23
+    print 'Client with id %s does not exist' % (e.client_id)
  24
+    sys.exit(1)
25 25
 except yubico_exceptions.SignatureVerificationError:
26  
-	print 'Signature verification failed'
27  
-	sys.exit(1)
  26
+    print 'Signature verification failed'
  27
+    sys.exit(1)
28 28
 except yubico_exceptions.StatusCodeError, e:
29  
-	print 'Negative status code was returned: %s' % (e.status_code)
30  
-	sys.exit(1)
31  
-	
  29
+    print 'Negative status code was returned: %s' % (e.status_code)
  30
+    sys.exit(1)
  31
+
32 32
 if status:
33  
-	print 'Success, the provided OTP is valid'
  33
+    print 'Success, the provided OTP is valid'
34 34
 else:
35  
-	print 'No response from the servers or received other negative status code'
  35
+    print 'No response from the servers or received other negative status code'
44  setup.py
@@ -21,25 +21,25 @@
21 21
 fp.close()
22 22
 
23 23
 setup(name = 'yubico',
24  
-	  version = '.' . join(map(str, version)),
25  
-	  description = 'Python Yubico Client',
26  
-	  author = 'Tomaž Muraus',
27  
-	  author_email = 'kami@k5-storitve.net',
28  
-	  license = 'BSD',
29  
-	  url = 'http://github.com/Kami/python-yubico-client/',
30  
-	  download_url = 'http://github.com/Kami/python-yubico-client/downloads/',
31  
-	  packages = ['yubico'],
32  
-	  provides = ['yubico'],
33  
-	  
34  
-	  classifiers = [
35  
-		  'Development Status :: 4 - Beta',
36  
-		  'Environment :: Console',
37  
-		  'Intended Audience :: Developers',
38  
-		  'Intended Audience :: System Administrators',
39  
-		  'License :: OSI Approved :: BSD License',
40  
-		  'Operating System :: OS Independent',
41  
-		  'Programming Language :: Python',
42  
-		  'Topic :: Security',
43  
-		  'Topic :: Software Development :: Libraries :: Python Modules',
44  
-	],
45  
-)
  24
+      version = '.' . join(map(str, version)),
  25
+      description = 'Python Yubico Client',
  26
+      author = 'Tomaž Muraus',
  27
+      author_email = 'kami@k5-storitve.net',
  28
+      license = 'BSD',
  29
+      url = 'http://github.com/Kami/python-yubico-client/',
  30
+      download_url = 'http://github.com/Kami/python-yubico-client/downloads/',
  31
+      packages = ['yubico'],
  32
+      provides = ['yubico'],
  33
+
  34
+      classifiers = [
  35
+          'Development Status :: 4 - Beta',
  36
+          'Environment :: Console',
  37
+          'Intended Audience :: Developers',
  38
+          'Intended Audience :: System Administrators',
  39
+          'License :: OSI Approved :: BSD License',
  40
+          'Operating System :: OS Independent',
  41
+          'Programming Language :: Python',
  42
+          'Topic :: Security',
  43
+          'Topic :: Software Development :: Libraries :: Python Modules',
  44
+    ]
  45
+)
96  yubico/httplib_ssl.py
@@ -10,54 +10,54 @@
10 10
 CA_CERTS = ''
11 11
 
12 12
 class VerifiedHTTPSConnection(httplib.HTTPSConnection):
13  
-	def connect(self):
14  
-		sock = socket.create_connection((self.host, self.port),
15  
-										self.timeout)
16  
-		if self._tunnel_host:
17  
-			self.sock = sock
18  
-			self._tunnel()
19  
-
20  
-		self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file, \
21  
-									cert_reqs = ssl.CERT_REQUIRED, ca_certs = CA_CERTS, \
22  
-									ssl_version = ssl.PROTOCOL_TLSv1)
23  
-		
24  
-		cert = self.sock.getpeercert()  
25  
-		if not self._verify_hostname(self.host, cert):
26  
-			raise ssl.SSLError('Failed to verify hostname')
27  
-		
28  
-	def _verify_hostname(self, hostname, cert):
29  
-		common_name = self._get_commonName(cert)
30  
-		alt_names = self._get_subjectAltName(cert)
31  
-		
32  
-		if (hostname == common_name) or hostname in alt_names:
33  
-			return True
34  
-		
35  
-		return False
36  
-			
37  
-	def _get_subjectAltName(self, cert):
38  
-		if not cert.has_key('subjectAltName'):
39  
-			return None
40  
-		
41  
-		alt_names = []
42  
-		for value in cert['subjectAltName']:
43  
-			if value[0].lower() == 'dns':
44  
-				alt_names.append(value[0])
45  
-		
46  
-		return alt_names				
47  
-	
48  
-	def _get_commonName(self, cert):
49  
-		if not cert.has_key('subject'):
50  
-			return None
51  
-		
52  
-		for value in cert['subject']:
53  
-			if value[0][0].lower() == 'commonname':
54  
-				return value[0][1]
55  
-		return None
  13
+    def connect(self):
  14
+        sock = socket.create_connection((self.host, self.port),
  15
+                                        self.timeout)
  16
+        if self._tunnel_host:
  17
+            self.sock = sock
  18
+            self._tunnel()
  19
+
  20
+        self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file, \
  21
+                                    cert_reqs = ssl.CERT_REQUIRED, ca_certs = CA_CERTS, \
  22
+                                    ssl_version = ssl.PROTOCOL_TLSv1)
  23
+
  24
+        cert = self.sock.getpeercert()
  25
+        if not self._verify_hostname(self.host, cert):
  26
+            raise ssl.SSLError('Failed to verify hostname')
  27
+
  28
+    def _verify_hostname(self, hostname, cert):
  29
+        common_name = self._get_commonName(cert)
  30
+        alt_names = self._get_subjectAltName(cert)
  31
+
  32
+        if (hostname == common_name) or hostname in alt_names:
  33
+            return True
  34
+
  35
+        return False
  36
+
  37
+    def _get_subjectAltName(self, cert):
  38
+        if not cert.has_key('subjectAltName'):
  39
+            return None
  40
+
  41
+        alt_names = []
  42
+        for value in cert['subjectAltName']:
  43
+            if value[0].lower() == 'dns':
  44
+                alt_names.append(value[0])
  45
+
  46
+        return alt_names
  47
+
  48
+    def _get_commonName(self, cert):
  49
+        if not cert.has_key('subject'):
  50
+            return None
  51
+
  52
+        for value in cert['subject']:
  53
+            if value[0][0].lower() == 'commonname':
  54
+                return value[0][1]
  55
+        return None
56 56
 
57 57
 class VerifiedHTTPSHandler(urllib2.HTTPSHandler):
58  
-	def __init__(self, connection_class = VerifiedHTTPSConnection):
59  
-		self.specialized_conn_class = connection_class
60  
-		urllib2.HTTPSHandler.__init__(self)
  58
+    def __init__(self, connection_class = VerifiedHTTPSConnection):
  59
+        self.specialized_conn_class = connection_class
  60
+        urllib2.HTTPSHandler.__init__(self)
61 61
 
62  
-	def https_open(self, req):
63  
-		return self.do_open(self.specialized_conn_class, req)
  62
+    def https_open(self, req):
  63
+        return self.do_open(self.specialized_conn_class, req)
44  yubico/otp.py
@@ -5,25 +5,25 @@
5 5
 import modhex
6 6
 
7 7
 class OTP():
8  
-	def __init__(self, otp, translate_otp = True):
9  
-		self.otp = self.get_otp_modehex_interpretation(otp) \
10  
-							 if translate_otp else otp
11  
-		
12  
-		self.device_id = self.otp[:12]
13  
-		self.session_counter = None
14  
-		self.timestamp = None
15  
-		self.session_user = None
16  
-		
17  
-	def get_otp_modehex_interpretation(self, otp):
18  
-		# We only use the first interpretation, because
19  
-		# if the OTP uses all 16 characters in its alphabet
20  
-		# there is only one possible interpretation of that otp
21  
-		try:
22  
-			otp_translated = modhex.translate(unicode(otp)).pop()
23  
-		except Exception:
24  
-			otp_translated = otp
25  
-		
26  
-		return otp_translated
27  
-		
28  
-	def __repr__(self):
29  
-		return '%s, %s, %s' % (self.otp, self.device_id, self.timestamp)
  8
+    def __init__(self, otp, translate_otp = True):
  9
+        self.otp = self.get_otp_modehex_interpretation(otp) \
  10
+                             if translate_otp else otp
  11
+
  12
+        self.device_id = self.otp[:12]
  13
+        self.session_counter = None
  14
+        self.timestamp = None
  15
+        self.session_user = None
  16
+
  17
+    def get_otp_modehex_interpretation(self, otp):
  18
+        # We only use the first interpretation, because
  19
+        # if the OTP uses all 16 characters in its alphabet
  20
+        # there is only one possible interpretation of that otp
  21
+        try:
  22
+            otp_translated = modhex.translate(unicode(otp)).pop()
  23
+        except Exception:
  24
+            otp_translated = otp
  25
+
  26
+        return otp_translated
  27
+
  28
+    def __repr__(self):
  29
+        return '%s, %s, %s' % (self.otp, self.device_id, self.timestamp)
482  yubico/yubico.py
@@ -5,9 +5,9 @@
5 5
 #
6 6
 # This client is based on the Validation Protocol Version 2.0 so it sends
7 7
 # the verification request to all the servers in parallel and it returns when
8  
-# the first positive (STATUS=OK) or negative (STATUS=REPLAYED_OTP)response is
  8
+# the first positive (STATUS=OK) or negative (STATUS=REPLAYED_OTP) response is
9 9
 # received.
10  
-#            
  10
+#
11 11
 # Author: Tomaž Muraus (http://www.tomaz-muraus.info)
12 12
 # License: BSD
13 13
 # Version: 1.4
@@ -31,248 +31,248 @@
31 31
 from yubico_exceptions import *
32 32
 
33 33
 try:
34  
-	import httplib_ssl
  34
+    import httplib_ssl
35 35
 except ImportError:
36  
-	httplib_ssl = None
  36
+    httplib_ssl = None
37 37
 
38 38
 API_URLS = ('api.yubico.com/wsapi/2.0/verify',
39  
-			'api2.yubico.com/wsapi/2.0/verify',
40  
-			'api3.yubico.com/wsapi/2.0/verify',
41  
-			'api4.yubico.com/wsapi/2.0/verify',
42  
-			'api5.yubico.com/wsapi/2.0/verify')
43  
-TIMEOUT = 10 			# How long to wait before the time out occurs
44  
-MAX_TIME_WINDOW = 40	# How many seconds can pass between the first and last OTP generations
45  
-						# so the OTP is still considered valid (only used in the multi mode)
46  
-						# default is 5 seconds (40 / 0.125 = 5)
  39
+            'api2.yubico.com/wsapi/2.0/verify',
  40
+            'api3.yubico.com/wsapi/2.0/verify',
  41
+            'api4.yubico.com/wsapi/2.0/verify',
  42
+            'api5.yubico.com/wsapi/2.0/verify')
  43
+TIMEOUT = 10            # How long to wait before the time out occurs
  44
+MAX_TIME_WINDOW = 40    # How many seconds can pass between the first and last OTP generations
  45
+                        # so the OTP is still considered valid (only used in the multi mode)
  46
+                        # default is 5 seconds (40 / 0.125 = 5)
47 47
 
48 48
 class Yubico():
49  
-	def __init__(self, client_id, key = None, use_https = True, verify_cert = False, \
50  
-				 translate_otp = True):
51  
-							 	
52  
-		if use_https and not httplib_ssl:
53  
-			raise Exception('SSL support not available')
54  
-			
55  
-		if use_https and httplib_ssl and httplib_ssl.CA_CERTS == '':
56  
-			raise Exception('If you want to validate server certificate, you need to set CA_CERTS '
57  
-							'variable in the httplib_ssl.py file pointing to a file which '
58  
-							'contains a list of trusted CA certificates')
59  
-		
60  
-		self.client_id = client_id
61  
-		self.key = base64.b64decode(key) if key is not None else None
62  
-		self.use_https = use_https
63  
-		self.verify_cert = verify_cert
64  
-		self.translate_otp = translate_otp
65  
-	
66  
-	def verify(self, otp, timestamp = False, sl = None, timeout = None, return_response = False):
67  
-		"""
68  
-		Returns True is the provided OTP is valid,
69  
-		False if the REPLAYED_OTP status value is returned or the response
70  
-		message signature verification failed and None for the rest of the status values.
71  
-		"""
72  
-		otp = OTP(otp, self.translate_otp)
73  
-		nonce = base64.b64encode(os.urandom(30), 'xz')[:25]
74  
-		query_string = self.generate_query_string(otp.otp, nonce, timestamp, sl, timeout)
75  
-		request_urls = self.generate_request_urls()
76  
-
77  
-		threads = []
78  
-		timeout = timeout or TIMEOUT
79  
-		for url in request_urls:
80  
-			thread = URLThread('%s?%s' % (url, query_string), timeout, self.verify_cert)
81  
-			thread.start()
82  
-			threads.append(thread)
83  
-
84  
-		# Wait for a first positive or negative response
85  
-		start_time = time.time()
86  
-		while threads and (start_time + timeout) > time.time():
87  
-			for thread in threads:
88  
-				if not thread.is_alive() and thread.response:
89  
-					status = self.verify_response(thread.response, return_response)
90  
-					
91  
-					if status:
92  
-						if return_response:
93  
-							return status
94  
-						else:
95  
-							return True	
96  
-					threads.remove(thread)
97  
-
98  
-		return None
99  
-	
100  
-	def verify_multi(self, otp_list = None, max_time_window = None, sl = None, timeout = None):
101  
-		# Create the OTP objects
102  
-		otps = []
103  
-		for otp in otp_list:
104  
-			otps.append(OTP(otp, self.translate_otp))
105  
-		
106  
-		device_ids = set()
107  
-		for otp in otps:
108  
-			device_ids.add(otp.device_id)
109  
-			
110  
-		# Check that all the OTPs contain same device id
111  
-		if len (device_ids) != 1:
112  
-			return False
113  
-		
114  
-		# Now we verify the OTPs and save the server response for each OTP.
115  
-		# We need the server response, to retrieve the timestamp.
116  
-		# It's possible to retrieve this value locally, without querying the server
117  
-		# but in this case, user would need to provide his AES key.
118  
-		for otp in otps:
119  
-			response = self.verify(otp.otp, True, sl, timeout, return_response = True)
120  
-			
121  
-			if not response:
122  
-				return False
123  
-
124  
-			otp.timestamp = int(response['timestamp'])
125  
-		
126  
-		count = len(otps)
127  
-		delta = otps[count - 1].timestamp - otps[0].timestamp
128  
-		
129  
-		max_time_window = (max_time_window / 0.125) if max_time_window else None
130  
-		max_time_window = max_time_window or MAX_TIME_WINDOW
131  
-		if delta > max_time_window:
132  
-			return False
133  
-		
134  
-		return True
135  
-				
136  
-	def verify_response(self, response, return_response = False):
137  
-		"""
138  
-		Returns True if the OTP is valid (status=OK) and return_response = False,
139  
-		otherwise (return_response = True) it returns the server response as a dictionary.
140  
-		
141  
-		Throws an exception if the OTP is replayed, the server response message
142  
-		verification failed or the client id is invalid, returns False otherwise.
143  
-		"""
144  
-		try:
145  
-			try:
146  
-				status = re.search(r'status=([a-zA-Z0-9_]+)', response) \
147  
-									 .groups()[0]
148  
-			except AttributeError, IndexError:
149  
-				return False
150  
-			
151  
-			# Secret key is specified, so we verify the response message
152  
-			# signature
153  
-			if self.key != None:
154  
-				signature, parameters = self.parse_parameters_from_response(response)
155  
-				generated_signature = self.generate_message_signature(parameters)
156  
-				
157  
-				# Signature located in the response does not match the one we have
158  
-				# generated
159  
-				if signature != generated_signature:
160  
-					raise SignatureVerificationError(generated_signature, signature)
161  
-		except KeyError:
162  
-			# Missing status code, malformed response?
163  
-			return False
164  
-		
165  
-		if status == 'OK':
166  
-			if return_response:
167  
-				query_string = self.parse_parameters_from_response(response)[1]
168  
-				response = self.get_parameters_as_dictionary(query_string)
169  
-				
170  
-				return response
171  
-			else:
172  
-				return True
173  
-		elif status == 'NO_SUCH_CLIENT':
174  
-			raise InvalidClientIdError(self.client_id)
175  
-		elif status == 'REPLAYED_OTP':
176  
-			raise StatusCodeError('REPLAYED_OTP')
177  
-		
178  
-		return False
179  
-		
180  
-	def generate_query_string(self, otp, nonce, timestamp = False, sl = None, timeout = None):
181  
-		"""
182  
-		Returns a query string which is sent to the validation servers.
183  
-		"""
184  
-		data = [('id', self.client_id),
185  
-				('otp', otp),
186  
-				('nonce', nonce)]
187  
-		
188  
-		if timestamp:
189  
-			data.append(('timestamp', '1'))
190  
-			
191  
-		if sl:
192  
-			if sl not in range(0,101) and sl not in ['fast', 'secure']:
193  
-				raise Exception('sl parameter value must be between 0 and 100 or string "fast" or "secure"')
194  
-			
195  
-			data.append(('sl', sl))
196  
-			
197  
-		if timeout:
198  
-			data.append(('timeout', timeout))
199  
-		
200  
-		query_string = urllib.urlencode(data)
201  
-		
202  
-		if self.key:
203  
-			hmac_signature = self.generate_message_signature(query_string)
204  
-			query_string += '&h=%s' % (hmac_signature.replace('+', '%2B'))
205  
-		
206  
-		return query_string
207  
-	
208  
-	def generate_message_signature(self, query_string):
209  
-		"""
210  
-		Returns a HMAC-SHA-1 signature for the given query string.
211  
-		http://code.google.com/p/yubikey-val-server-php/wiki/ValidationProtocolV20
212  
-		"""
213  
-		pairs = query_string.split('&')
214  
-		pairs = [pair.split('=') for pair in pairs]
215  
-		pairs_sorted = sorted(pairs)
216  
-		pairs_string = '&' . join(['=' . join(pair) for pair in pairs_sorted])
217  
-
218  
-		digest = hmac.new(self.key, pairs_string, hashlib.sha1).digest()
219  
-		signature = base64.b64encode(digest)
220  
-		
221  
-		return signature
222  
-	
223  
-	def parse_parameters_from_response(self, response):
224  
-		"""
225  
-		Returns a response signature and query string generated from the server response.
226  
-		"""
227  
-		splitted = [pair.strip() for pair in response.split('\n') if pair.strip() != '']
228  
-		signature = splitted[0].replace('h=', '')
229  
-		query_string = '&' . join(splitted[1:])
230  
-
231  
-		return (signature, query_string)
232  
-	
233  
-	def get_parameters_as_dictionary(self, query_string):
234  
-		""" Returns query string parameters as a dictionary. """
235  
-		dictionary = dict([parameter.split('=') for parameter \
236  
-					in query_string.split('&')])
237  
-		
238  
-		return dictionary
239  
-	
240  
-	def generate_request_urls(self):
241  
-		"""
242  
-		Returns a list of the API URLs.
243  
-		"""
244  
-		urls = []
245  
-		for url in API_URLS:
246  
-			if self.use_https:
247  
-				url = 'https://%s' % (url)
248  
-			else:
249  
-				url = 'http://%s' % (url)
250  
-			urls.append(url)
251  
-				
252  
-		return urls
253  
-		
  49
+    def __init__(self, client_id, key = None, use_https = True, verify_cert = False, \
  50
+                 translate_otp = True):
  51
+
  52
+        if use_https and not httplib_ssl:
  53
+            raise Exception('SSL support not available')
  54
+
  55
+        if use_https and httplib_ssl and httplib_ssl.CA_CERTS == '':
  56
+            raise Exception('If you want to validate server certificate, you need to set CA_CERTS '
  57
+                            'variable in the httplib_ssl.py file pointing to a file which '
  58
+                            'contains a list of trusted CA certificates')
  59
+
  60
+        self.client_id = client_id
  61
+        self.key = base64.b64decode(key) if key is not None else None
  62
+        self.use_https = use_https
  63
+        self.verify_cert = verify_cert
  64
+        self.translate_otp = translate_otp
  65
+
  66
+    def verify(self, otp, timestamp = False, sl = None, timeout = None, return_response = False):
  67
+        """
  68
+        Returns True is the provided OTP is valid,
  69
+        False if the REPLAYED_OTP status value is returned or the response
  70
+        message signature verification failed and None for the rest of the status values.
  71
+        """
  72
+        otp = OTP(otp, self.translate_otp)
  73
+        nonce = base64.b64encode(os.urandom(30), 'xz')[:25]
  74
+        query_string = self.generate_query_string(otp.otp, nonce, timestamp, sl, timeout)
  75
+        request_urls = self.generate_request_urls()
  76
+
  77
+        threads = []
  78
+        timeout = timeout or TIMEOUT
  79
+        for url in request_urls:
  80
+            thread = URLThread('%s?%s' % (url, query_string), timeout, self.verify_cert)
  81
+            thread.start()
  82
+            threads.append(thread)
  83
+
  84
+        # Wait for a first positive or negative response
  85
+        start_time = time.time()
  86
+        while threads and (start_time + timeout) > time.time():
  87
+            for thread in threads:
  88
+                if not thread.is_alive() and thread.response:
  89
+                    status = self.verify_response(thread.response, return_response)
  90
+
  91
+                    if status:
  92
+                        if return_response:
  93
+                            return status
  94
+                        else:
  95
+                            return True
  96
+                    threads.remove(thread)
  97
+
  98
+        return None
  99
+
  100
+    def verify_multi(self, otp_list = None, max_time_window = None, sl = None, timeout = None):
  101
+        # Create the OTP objects
  102
+        otps = []
  103
+        for otp in otp_list:
  104
+            otps.append(OTP(otp, self.translate_otp))
  105
+
  106
+        device_ids = set()
  107
+        for otp in otps:
  108
+            device_ids.add(otp.device_id)
  109
+
  110
+        # Check that all the OTPs contain same device id
  111
+        if len (device_ids) != 1:
  112
+            return False
  113
+
  114
+        # Now we verify the OTPs and save the server response for each OTP.
  115
+        # We need the server response, to retrieve the timestamp.
  116
+        # It's possible to retrieve this value locally, without querying the server
  117
+        # but in this case, user would need to provide his AES key.
  118
+        for otp in otps:
  119
+            response = self.verify(otp.otp, True, sl, timeout, return_response = True)
  120
+
  121
+            if not response:
  122
+                return False
  123
+
  124
+            otp.timestamp = int(response['timestamp'])
  125
+
  126
+        count = len(otps)
  127
+        delta = otps[count - 1].timestamp - otps[0].timestamp
  128
+
  129
+        max_time_window = (max_time_window / 0.125) if max_time_window else None
  130
+        max_time_window = max_time_window or MAX_TIME_WINDOW
  131
+        if delta > max_time_window:
  132
+            return False
  133
+
  134
+        return True
  135
+
  136
+    def verify_response(self, response, return_response = False):
  137
+        """
  138
+        Returns True if the OTP is valid (status=OK) and return_response = False,
  139
+        otherwise (return_response = True) it returns the server response as a dictionary.
  140
+
  141
+        Throws an exception if the OTP is replayed, the server response message
  142
+        verification failed or the client id is invalid, returns False otherwise.
  143
+        """
  144
+        try:
  145
+            try:
  146
+                status = re.search(r'status=([a-zA-Z0-9_]+)', response) \
  147
+                                     .groups()[0]
  148
+            except AttributeError, IndexError:
  149
+                return False
  150
+
  151
+            # Secret key is specified, so we verify the response message
  152
+            # signature
  153
+            if self.key != None:
  154
+                signature, parameters = self.parse_parameters_from_response(response)
  155
+                generated_signature = self.generate_message_signature(parameters)
  156
+
  157
+                # Signature located in the response does not match the one we have
  158
+                # generated
  159
+                if signature != generated_signature:
  160
+                    raise SignatureVerificationError(generated_signature, signature)
  161
+        except KeyError:
  162
+            # Missing status code, malformed response?
  163
+            return False
  164
+
  165
+        if status == 'OK':
  166
+            if return_response:
  167
+                query_string = self.parse_parameters_from_response(response)[1]
  168
+                response = self.get_parameters_as_dictionary(query_string)
  169
+
  170
+                return response
  171
+            else:
  172
+                return True
  173
+        elif status == 'NO_SUCH_CLIENT':
  174
+            raise InvalidClientIdError(self.client_id)
  175
+        elif status == 'REPLAYED_OTP':
  176
+            raise StatusCodeError('REPLAYED_OTP')
  177
+
  178
+        return False
  179
+
  180
+    def generate_query_string(self, otp, nonce, timestamp = False, sl = None, timeout = None):
  181
+        """
  182
+        Returns a query string which is sent to the validation servers.
  183
+        """
  184
+        data = [('id', self.client_id),
  185
+                ('otp', otp),
  186
+                ('nonce', nonce)]
  187
+
  188
+        if timestamp:
  189
+            data.append(('timestamp', '1'))
  190
+
  191
+        if sl:
  192
+            if sl not in range(0,101) and sl not in ['fast', 'secure']:
  193
+                raise Exception('sl parameter value must be between 0 and 100 or string "fast" or "secure"')
  194
+
  195
+            data.append(('sl', sl))
  196
+
  197
+        if timeout:
  198
+            data.append(('timeout', timeout))
  199
+
  200
+        query_string = urllib.urlencode(data)
  201
+
  202
+        if self.key:
  203
+            hmac_signature = self.generate_message_signature(query_string)
  204
+            query_string += '&h=%s' % (hmac_signature.replace('+', '%2B'))
  205
+
  206
+        return query_string
  207
+
  208
+    def generate_message_signature(self, query_string):
  209
+        """
  210
+        Returns a HMAC-SHA-1 signature for the given query string.
  211
+        http://code.google.com/p/yubikey-val-server-php/wiki/ValidationProtocolV20
  212
+        """
  213
+        pairs = query_string.split('&')
  214
+        pairs = [pair.split('=') for pair in pairs]
  215
+        pairs_sorted = sorted(pairs)
  216
+        pairs_string = '&' . join(['=' . join(pair) for pair in pairs_sorted])
  217
+
  218
+        digest = hmac.new(self.key, pairs_string, hashlib.sha1).digest()
  219
+        signature = base64.b64encode(digest)
  220
+
  221
+        return signature
  222
+
  223
+    def parse_parameters_from_response(self, response):
  224
+        """
  225
+        Returns a response signature and query string generated from the server response.
  226
+        """
  227
+        splitted = [pair.strip() for pair in response.split('\n') if pair.strip() != '']
  228
+        signature = splitted[0].replace('h=', '')
  229
+        query_string = '&' . join(splitted[1:])
  230
+
  231
+        return (signature, query_string)
  232
+
  233
+    def get_parameters_as_dictionary(self, query_string):
  234
+        """ Returns query string parameters as a dictionary. """
  235
+        dictionary = dict([parameter.split('=') for parameter \
  236
+                    in query_string.split('&')])
  237
+
  238
+        return dictionary
  239
+
  240
+    def generate_request_urls(self):
  241
+        """
  242
+        Returns a list of the API URLs.
  243
+        """
  244
+        urls = []
  245
+        for url in API_URLS:
  246
+            if self.use_https:
  247
+                url = 'https://%s' % (url)
  248
+            else:
  249
+                url = 'http://%s' % (url)
  250
+            urls.append(url)
  251
+
  252
+        return urls
  253
+
254 254
 class URLThread(threading.Thread):
255  
-	def __init__(self, url, timeout, verify_cert):
256  
-		super(URLThread, self).__init__()
257  
-		self.url = url
258  
-		self.timeout = timeout
259  
-		self.verify_cert = verify_cert
260  
-		self.request = None
261  
-		self.response = None
262  
-		
263  
-		if int(sys.version[0]) == 2 and int(sys.version[2]) <= 5:
264  
-			self.is_alive = self.isAlive
265  
-		
266  
-	def run(self):
267  
-		socket.setdefaulttimeout(self.timeout)
268  
-		
269  
-		if self.url.startswith('https') and self.verify_cert:
270  
-			handler = httplib_ssl.VerifiedHTTPSHandler()
271  
-			opener = urllib2.build_opener(handler)
272  
-			urllib2.install_opener(opener)
273  
-		
274  
-		try:
275  
-			self.request = urllib2.urlopen(self.url)
276  
-			self.response = self.request.read()
277  
-		except Exception, e:
278  
-			self.response = None
  255
+    def __init__(self, url, timeout, verify_cert):
  256
+        super(URLThread, self).__init__()
  257
+        self.url = url
  258
+        self.timeout = timeout
  259
+        self.verify_cert = verify_cert
  260
+        self.request = None
  261
+        self.response = None
  262
+
  263
+        if int(sys.version[0]) == 2 and int(sys.version[2]) <= 5:
  264
+            self.is_alive = self.isAlive
  265
+
  266
+    def run(self):
  267
+        socket.setdefaulttimeout(self.timeout)
  268
+
  269
+        if self.url.startswith('https') and self.verify_cert:
  270
+            handler = httplib_ssl.VerifiedHTTPSHandler()
  271
+            opener = urllib2.build_opener(handler)
  272
+            urllib2.install_opener(opener)
  273
+
  274
+        try:
  275
+            self.request = urllib2.urlopen(self.url)
  276
+            self.response = self.request.read()
  277
+        except Exception, e:
  278
+            self.response = None
42  yubico/yubico_exceptions.py
... ...
@@ -1,26 +1,26 @@
1 1
 class YubicoError(Exception):
2  
-	""" Base class for Yubico related exceptions. """
3  
-	pass
  2
+    """ Base class for Yubico related exceptions. """
  3
+    pass
4 4
 
5 5
 class StatusCodeError(YubicoError):
6  
-	def __init__(self, status_code):
7  
-		self.status_code = status_code
8  
-		
9  
-	def __str__(self):
10  
-		return 'Yubico server returned the following status code: %s' % (self.status_code)
11  
-	
  6
+    def __init__(self, status_code):
  7
+        self.status_code = status_code
  8
+
  9
+    def __str__(self):
  10
+        return 'Yubico server returned the following status code: %s' % (self.status_code)
  11
+
12 12
 class InvalidClientIdError(YubicoError):
13  
-	def __init__(self, client_id):
14  
-		self.client_id = client_id
15  
-		
16  
-	def __str__(self):
17  
-		return 'The client with ID %s does not exist' % (self.client_id)
18  
-	
  13
+    def __init__(self, client_id):
  14
+        self.client_id = client_id
  15
+
  16
+    def __str__(self):
  17
+        return 'The client with ID %s does not exist' % (self.client_id)
  18
+
19 19
 class SignatureVerificationError(YubicoError):
20  
-	def __init__(self, generated_signature, response_signature):
21  
-		self.generated_signature = generated_signature
22  
-		self.response_signature = response_signature
23  
-		
24  
-	def __str__(self):
25  
-		return repr('Server response message signature verification failed (expected %s, got %s)' \
26  
-				% (self.generated_signature, self.response_signature))
  20
+    def __init__(self, generated_signature, response_signature):
  21
+        self.generated_signature = generated_signature
  22
+        self.response_signature = response_signature
  23
+
  24
+    def __str__(self):
  25
+        return repr('Server response message signature verification failed (expected %s, got %s)' \
  26
+                % (self.generated_signature, self.response_signature))

0 notes on commit 8c8f402

Please sign in to comment.
Something went wrong with that request. Please try again.