Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(204)

Side by Side Diff: third_party/oauth2client/crypt.py

Issue 1085893002: Upgrade 3rd packages (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/depot_tools
Patch Set: rebase Created 5 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 #!/usr/bin/python2.4
2 # -*- coding: utf-8 -*- 1 # -*- coding: utf-8 -*-
3 # 2 #
4 # Copyright (C) 2011 Google Inc. 3 # Copyright 2014 Google Inc. All rights reserved.
5 # 4 #
6 # Licensed under the Apache License, Version 2.0 (the "License"); 5 # Licensed under the Apache License, Version 2.0 (the "License");
7 # you may not use this file except in compliance with the License. 6 # you may not use this file except in compliance with the License.
8 # You may obtain a copy of the License at 7 # You may obtain a copy of the License at
9 # 8 #
10 # http://www.apache.org/licenses/LICENSE-2.0 9 # http://www.apache.org/licenses/LICENSE-2.0
11 # 10 #
12 # Unless required by applicable law or agreed to in writing, software 11 # Unless required by applicable law or agreed to in writing, software
13 # distributed under the License is distributed on an "AS IS" BASIS, 12 # distributed under the License is distributed on an "AS IS" BASIS,
14 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 # See the License for the specific language governing permissions and 14 # See the License for the specific language governing permissions and
16 # limitations under the License. 15 # limitations under the License.
16 """Crypto-related routines for oauth2client."""
17 17
18 import base64 18 import base64
19 import hashlib 19 import json
20 import logging 20 import logging
21 import sys
21 import time 22 import time
22 23
23 from anyjson import simplejson 24 from third_party import six
24 25
25 26
26 CLOCK_SKEW_SECS = 300 # 5 minutes in seconds 27 CLOCK_SKEW_SECS = 300 # 5 minutes in seconds
27 AUTH_TOKEN_LIFETIME_SECS = 300 # 5 minutes in seconds 28 AUTH_TOKEN_LIFETIME_SECS = 300 # 5 minutes in seconds
28 MAX_TOKEN_LIFETIME_SECS = 86400 # 1 day in seconds 29 MAX_TOKEN_LIFETIME_SECS = 86400 # 1 day in seconds
29 30
30 31
31 logger = logging.getLogger(__name__) 32 logger = logging.getLogger(__name__)
32 33
33 34
34 class AppIdentityError(Exception): 35 class AppIdentityError(Exception):
35 pass 36 pass
36 37
37 38
38 try: 39 try:
39 from OpenSSL import crypto 40 from OpenSSL import crypto
40 41
41
42 class OpenSSLVerifier(object): 42 class OpenSSLVerifier(object):
43 """Verifies the signature on a message.""" 43 """Verifies the signature on a message."""
44 44
45 def __init__(self, pubkey): 45 def __init__(self, pubkey):
46 """Constructor. 46 """Constructor.
47 47
48 Args: 48 Args:
49 pubkey, OpenSSL.crypto.PKey, The public key to verify with. 49 pubkey, OpenSSL.crypto.PKey, The public key to verify with.
50 """ 50 """
51 self._pubkey = pubkey 51 self._pubkey = pubkey
52 52
53 def verify(self, message, signature): 53 def verify(self, message, signature):
54 """Verifies a message against a signature. 54 """Verifies a message against a signature.
55 55
56 Args: 56 Args:
57 message: string, The message to verify. 57 message: string, The message to verify.
58 signature: string, The signature on the message. 58 signature: string, The signature on the message.
59 59
60 Returns: 60 Returns:
61 True if message was signed by the private key associated with the public 61 True if message was signed by the private key associated with the public
62 key that this object was constructed with. 62 key that this object was constructed with.
63 """ 63 """
64 try: 64 try:
65 if isinstance(message, six.text_type):
66 message = message.encode('utf-8')
65 crypto.verify(self._pubkey, signature, message, 'sha256') 67 crypto.verify(self._pubkey, signature, message, 'sha256')
66 return True 68 return True
67 except: 69 except:
68 return False 70 return False
69 71
70 @staticmethod 72 @staticmethod
71 def from_string(key_pem, is_x509_cert): 73 def from_string(key_pem, is_x509_cert):
72 """Construct a Verified instance from a string. 74 """Construct a Verified instance from a string.
73 75
74 Args: 76 Args:
(...skipping 22 matching lines...) Expand all
97 99
98 Args: 100 Args:
99 pkey, OpenSSL.crypto.PKey (or equiv), The private key to sign with. 101 pkey, OpenSSL.crypto.PKey (or equiv), The private key to sign with.
100 """ 102 """
101 self._key = pkey 103 self._key = pkey
102 104
103 def sign(self, message): 105 def sign(self, message):
104 """Signs a message. 106 """Signs a message.
105 107
106 Args: 108 Args:
107 message: string, Message to be signed. 109 message: bytes, Message to be signed.
108 110
109 Returns: 111 Returns:
110 string, The signature of the message for the given key. 112 string, The signature of the message for the given key.
111 """ 113 """
114 if isinstance(message, six.text_type):
115 message = message.encode('utf-8')
112 return crypto.sign(self._key, message, 'sha256') 116 return crypto.sign(self._key, message, 'sha256')
113 117
114 @staticmethod 118 @staticmethod
115 def from_string(key, password='notasecret'): 119 def from_string(key, password=b'notasecret'):
116 """Construct a Signer instance from a string. 120 """Construct a Signer instance from a string.
117 121
118 Args: 122 Args:
119 key: string, private key in PKCS12 or PEM format. 123 key: string, private key in PKCS12 or PEM format.
120 password: string, password for the private key file. 124 password: string, password for the private key file.
121 125
122 Returns: 126 Returns:
123 Signer instance. 127 Signer instance.
124 128
125 Raises: 129 Raises:
126 OpenSSL.crypto.Error if the key can't be parsed. 130 OpenSSL.crypto.Error if the key can't be parsed.
127 """ 131 """
128 if key.startswith('-----BEGIN '): 132 parsed_pem_key = _parse_pem_key(key)
129 pkey = crypto.load_privatekey(crypto.FILETYPE_PEM, key) 133 if parsed_pem_key:
134 pkey = crypto.load_privatekey(crypto.FILETYPE_PEM, parsed_pem_key)
130 else: 135 else:
136 if isinstance(password, six.text_type):
137 password = password.encode('utf-8')
131 pkey = crypto.load_pkcs12(key, password).get_privatekey() 138 pkey = crypto.load_pkcs12(key, password).get_privatekey()
132 return OpenSSLSigner(pkey) 139 return OpenSSLSigner(pkey)
133 140
141
142 def pkcs12_key_as_pem(private_key_text, private_key_password):
143 """Convert the contents of a PKCS12 key to PEM using OpenSSL.
144
145 Args:
146 private_key_text: String. Private key.
147 private_key_password: String. Password for PKCS12.
148
149 Returns:
150 String. PEM contents of ``private_key_text``.
151 """
152 decoded_body = base64.b64decode(private_key_text)
153 if isinstance(private_key_password, six.string_types):
154 private_key_password = private_key_password.encode('ascii')
155
156 pkcs12 = crypto.load_pkcs12(decoded_body, private_key_password)
157 return crypto.dump_privatekey(crypto.FILETYPE_PEM,
158 pkcs12.get_privatekey())
134 except ImportError: 159 except ImportError:
135 OpenSSLVerifier = None 160 OpenSSLVerifier = None
136 OpenSSLSigner = None 161 OpenSSLSigner = None
162 def pkcs12_key_as_pem(*args, **kwargs):
163 raise NotImplementedError('pkcs12_key_as_pem requires OpenSSL.')
137 164
138 165
139 try: 166 try:
140 from Crypto.PublicKey import RSA 167 from Crypto.PublicKey import RSA
141 from Crypto.Hash import SHA256 168 from Crypto.Hash import SHA256
142 from Crypto.Signature import PKCS1_v1_5 169 from Crypto.Signature import PKCS1_v1_5
170 from Crypto.Util.asn1 import DerSequence
143 171
144 172
145 class PyCryptoVerifier(object): 173 class PyCryptoVerifier(object):
146 """Verifies the signature on a message.""" 174 """Verifies the signature on a message."""
147 175
148 def __init__(self, pubkey): 176 def __init__(self, pubkey):
149 """Constructor. 177 """Constructor.
150 178
151 Args: 179 Args:
152 pubkey, OpenSSL.crypto.PKey (or equiv), The public key to verify with. 180 pubkey, OpenSSL.crypto.PKey (or equiv), The public key to verify with.
(...skipping 21 matching lines...) Expand all
174 def from_string(key_pem, is_x509_cert): 202 def from_string(key_pem, is_x509_cert):
175 """Construct a Verified instance from a string. 203 """Construct a Verified instance from a string.
176 204
177 Args: 205 Args:
178 key_pem: string, public key in PEM format. 206 key_pem: string, public key in PEM format.
179 is_x509_cert: bool, True if key_pem is an X509 cert, otherwise it is 207 is_x509_cert: bool, True if key_pem is an X509 cert, otherwise it is
180 expected to be an RSA key in PEM format. 208 expected to be an RSA key in PEM format.
181 209
182 Returns: 210 Returns:
183 Verifier instance. 211 Verifier instance.
184
185 Raises:
186 NotImplementedError if is_x509_cert is true.
187 """ 212 """
188 if is_x509_cert: 213 if is_x509_cert:
189 raise NotImplementedError( 214 if isinstance(key_pem, six.text_type):
190 'X509 certs are not supported by the PyCrypto library. ' 215 key_pem = key_pem.encode('ascii')
191 'Try using PyOpenSSL if native code is an option.') 216 pemLines = key_pem.replace(b' ', b'').split()
217 certDer = _urlsafe_b64decode(b''.join(pemLines[1:-1]))
218 certSeq = DerSequence()
219 certSeq.decode(certDer)
220 tbsSeq = DerSequence()
221 tbsSeq.decode(certSeq[0])
222 pubkey = RSA.importKey(tbsSeq[6])
192 else: 223 else:
193 pubkey = RSA.importKey(key_pem) 224 pubkey = RSA.importKey(key_pem)
194 return PyCryptoVerifier(pubkey) 225 return PyCryptoVerifier(pubkey)
195 226
196 227
197 class PyCryptoSigner(object): 228 class PyCryptoSigner(object):
198 """Signs messages with a private key.""" 229 """Signs messages with a private key."""
199 230
200 def __init__(self, pkey): 231 def __init__(self, pkey):
201 """Constructor. 232 """Constructor.
202 233
203 Args: 234 Args:
204 pkey, OpenSSL.crypto.PKey (or equiv), The private key to sign with. 235 pkey, OpenSSL.crypto.PKey (or equiv), The private key to sign with.
205 """ 236 """
206 self._key = pkey 237 self._key = pkey
207 238
208 def sign(self, message): 239 def sign(self, message):
209 """Signs a message. 240 """Signs a message.
210 241
211 Args: 242 Args:
212 message: string, Message to be signed. 243 message: string, Message to be signed.
213 244
214 Returns: 245 Returns:
215 string, The signature of the message for the given key. 246 string, The signature of the message for the given key.
216 """ 247 """
248 if isinstance(message, six.text_type):
249 message = message.encode('utf-8')
217 return PKCS1_v1_5.new(self._key).sign(SHA256.new(message)) 250 return PKCS1_v1_5.new(self._key).sign(SHA256.new(message))
218 251
219 @staticmethod 252 @staticmethod
220 def from_string(key, password='notasecret'): 253 def from_string(key, password='notasecret'):
221 """Construct a Signer instance from a string. 254 """Construct a Signer instance from a string.
222 255
223 Args: 256 Args:
224 key: string, private key in PEM format. 257 key: string, private key in PEM format.
225 password: string, password for private key file. Unused for PEM files. 258 password: string, password for private key file. Unused for PEM files.
226 259
227 Returns: 260 Returns:
228 Signer instance. 261 Signer instance.
229 262
230 Raises: 263 Raises:
231 NotImplementedError if they key isn't in PEM format. 264 NotImplementedError if they key isn't in PEM format.
232 """ 265 """
233 if key.startswith('-----BEGIN '): 266 parsed_pem_key = _parse_pem_key(key)
234 pkey = RSA.importKey(key) 267 if parsed_pem_key:
268 pkey = RSA.importKey(parsed_pem_key)
235 else: 269 else:
236 raise NotImplementedError( 270 raise NotImplementedError(
237 'PKCS12 format is not supported by the PyCrpto library. ' 271 'PKCS12 format is not supported by the PyCrypto library. '
238 'Try converting to a "PEM" ' 272 'Try converting to a "PEM" '
239 '(openssl pkcs12 -in xxxxx.p12 -nodes -nocerts > privatekey.pem) ' 273 '(openssl pkcs12 -in xxxxx.p12 -nodes -nocerts > privatekey.pem) '
240 'or using PyOpenSSL if native code is an option.') 274 'or using PyOpenSSL if native code is an option.')
241 return PyCryptoSigner(pkey) 275 return PyCryptoSigner(pkey)
242 276
243 except ImportError: 277 except ImportError:
244 PyCryptoVerifier = None 278 PyCryptoVerifier = None
245 PyCryptoSigner = None 279 PyCryptoSigner = None
246 280
247 281
248 if OpenSSLSigner: 282 if OpenSSLSigner:
249 Signer = OpenSSLSigner 283 Signer = OpenSSLSigner
250 Verifier = OpenSSLVerifier 284 Verifier = OpenSSLVerifier
251 elif PyCryptoSigner: 285 elif PyCryptoSigner:
252 Signer = PyCryptoSigner 286 Signer = PyCryptoSigner
253 Verifier = PyCryptoVerifier 287 Verifier = PyCryptoVerifier
254 else: 288 else:
255 raise ImportError('No encryption library found. Please install either ' 289 raise ImportError('No encryption library found. Please install either '
256 'PyOpenSSL, or PyCrypto 2.6 or later') 290 'PyOpenSSL, or PyCrypto 2.6 or later')
257 291
258 292
293 def _parse_pem_key(raw_key_input):
294 """Identify and extract PEM keys.
295
296 Determines whether the given key is in the format of PEM key, and extracts
297 the relevant part of the key if it is.
298
299 Args:
300 raw_key_input: The contents of a private key file (either PEM or PKCS12).
301
302 Returns:
303 string, The actual key if the contents are from a PEM file, or else None.
304 """
305 offset = raw_key_input.find(b'-----BEGIN ')
306 if offset != -1:
307 return raw_key_input[offset:]
308
309
259 def _urlsafe_b64encode(raw_bytes): 310 def _urlsafe_b64encode(raw_bytes):
260 return base64.urlsafe_b64encode(raw_bytes).rstrip('=') 311 if isinstance(raw_bytes, six.text_type):
312 raw_bytes = raw_bytes.encode('utf-8')
313 return base64.urlsafe_b64encode(raw_bytes).decode('ascii').rstrip('=')
261 314
262 315
263 def _urlsafe_b64decode(b64string): 316 def _urlsafe_b64decode(b64string):
264 # Guard against unicode strings, which base64 can't handle. 317 # Guard against unicode strings, which base64 can't handle.
265 b64string = b64string.encode('ascii') 318 if isinstance(b64string, six.text_type):
266 padded = b64string + '=' * (4 - len(b64string) % 4) 319 b64string = b64string.encode('ascii')
320 padded = b64string + b'=' * (4 - len(b64string) % 4)
267 return base64.urlsafe_b64decode(padded) 321 return base64.urlsafe_b64decode(padded)
268 322
269 323
270 def _json_encode(data): 324 def _json_encode(data):
271 return simplejson.dumps(data, separators = (',', ':')) 325 return json.dumps(data, separators=(',', ':'))
272 326
273 327
274 def make_signed_jwt(signer, payload): 328 def make_signed_jwt(signer, payload):
275 """Make a signed JWT. 329 """Make a signed JWT.
276 330
277 See http://self-issued.info/docs/draft-jones-json-web-token.html. 331 See http://self-issued.info/docs/draft-jones-json-web-token.html.
278 332
279 Args: 333 Args:
280 signer: crypt.Signer, Cryptographic signer. 334 signer: crypt.Signer, Cryptographic signer.
281 payload: dict, Dictionary of data to convert to JSON and then sign. 335 payload: dict, Dictionary of data to convert to JSON and then sign.
282 336
283 Returns: 337 Returns:
284 string, The JWT for the payload. 338 string, The JWT for the payload.
285 """ 339 """
286 header = {'typ': 'JWT', 'alg': 'RS256'} 340 header = {'typ': 'JWT', 'alg': 'RS256'}
287 341
288 segments = [ 342 segments = [
289 _urlsafe_b64encode(_json_encode(header)), 343 _urlsafe_b64encode(_json_encode(header)),
290 _urlsafe_b64encode(_json_encode(payload)), 344 _urlsafe_b64encode(_json_encode(payload)),
291 ] 345 ]
292 signing_input = '.'.join(segments) 346 signing_input = '.'.join(segments)
293 347
294 signature = signer.sign(signing_input) 348 signature = signer.sign(signing_input)
295 segments.append(_urlsafe_b64encode(signature)) 349 segments.append(_urlsafe_b64encode(signature))
296 350
297 logger.debug(str(segments)) 351 logger.debug(str(segments))
298 352
299 return '.'.join(segments) 353 return '.'.join(segments)
300 354
(...skipping 10 matching lines...) Expand all
311 None then the JWT's 'aud' parameter is not verified. 365 None then the JWT's 'aud' parameter is not verified.
312 366
313 Returns: 367 Returns:
314 dict, The deserialized JSON payload in the JWT. 368 dict, The deserialized JSON payload in the JWT.
315 369
316 Raises: 370 Raises:
317 AppIdentityError if any checks are failed. 371 AppIdentityError if any checks are failed.
318 """ 372 """
319 segments = jwt.split('.') 373 segments = jwt.split('.')
320 374
321 if (len(segments) != 3): 375 if len(segments) != 3:
322 raise AppIdentityError( 376 raise AppIdentityError('Wrong number of segments in token: %s' % jwt)
323 'Wrong number of segments in token: %s' % jwt)
324 signed = '%s.%s' % (segments[0], segments[1]) 377 signed = '%s.%s' % (segments[0], segments[1])
325 378
326 signature = _urlsafe_b64decode(segments[2]) 379 signature = _urlsafe_b64decode(segments[2])
327 380
328 # Parse token. 381 # Parse token.
329 json_body = _urlsafe_b64decode(segments[1]) 382 json_body = _urlsafe_b64decode(segments[1])
330 try: 383 try:
331 parsed = simplejson.loads(json_body) 384 parsed = json.loads(json_body.decode('utf-8'))
332 except: 385 except:
333 raise AppIdentityError('Can\'t parse token: %s' % json_body) 386 raise AppIdentityError('Can\'t parse token: %s' % json_body)
334 387
335 # Check signature. 388 # Check signature.
336 verified = False 389 verified = False
337 for (keyname, pem) in certs.items(): 390 for pem in certs.values():
338 verifier = Verifier.from_string(pem, True) 391 verifier = Verifier.from_string(pem, True)
339 if (verifier.verify(signed, signature)): 392 if verifier.verify(signed, signature):
340 verified = True 393 verified = True
341 break 394 break
342 if not verified: 395 if not verified:
343 raise AppIdentityError('Invalid token signature: %s' % jwt) 396 raise AppIdentityError('Invalid token signature: %s' % jwt)
344 397
345 # Check creation timestamp. 398 # Check creation timestamp.
346 iat = parsed.get('iat') 399 iat = parsed.get('iat')
347 if iat is None: 400 if iat is None:
348 raise AppIdentityError('No iat field in token: %s' % json_body) 401 raise AppIdentityError('No iat field in token: %s' % json_body)
349 earliest = iat - CLOCK_SKEW_SECS 402 earliest = iat - CLOCK_SKEW_SECS
350 403
351 # Check expiration timestamp. 404 # Check expiration timestamp.
352 now = long(time.time()) 405 now = int(time.time())
353 exp = parsed.get('exp') 406 exp = parsed.get('exp')
354 if exp is None: 407 if exp is None:
355 raise AppIdentityError('No exp field in token: %s' % json_body) 408 raise AppIdentityError('No exp field in token: %s' % json_body)
356 if exp >= now + MAX_TOKEN_LIFETIME_SECS: 409 if exp >= now + MAX_TOKEN_LIFETIME_SECS:
357 raise AppIdentityError( 410 raise AppIdentityError('exp field too far in future: %s' % json_body)
358 'exp field too far in future: %s' % json_body)
359 latest = exp + CLOCK_SKEW_SECS 411 latest = exp + CLOCK_SKEW_SECS
360 412
361 if now < earliest: 413 if now < earliest:
362 raise AppIdentityError('Token used too early, %d < %d: %s' % 414 raise AppIdentityError('Token used too early, %d < %d: %s' %
363 (now, earliest, json_body)) 415 (now, earliest, json_body))
364 if now > latest: 416 if now > latest:
365 raise AppIdentityError('Token used too late, %d > %d: %s' % 417 raise AppIdentityError('Token used too late, %d > %d: %s' %
366 (now, latest, json_body)) 418 (now, latest, json_body))
367 419
368 # Check audience. 420 # Check audience.
369 if audience is not None: 421 if audience is not None:
370 aud = parsed.get('aud') 422 aud = parsed.get('aud')
371 if aud is None: 423 if aud is None:
372 raise AppIdentityError('No aud field in token: %s' % json_body) 424 raise AppIdentityError('No aud field in token: %s' % json_body)
373 if aud != audience: 425 if aud != audience:
374 raise AppIdentityError('Wrong recipient, %s != %s: %s' % 426 raise AppIdentityError('Wrong recipient, %s != %s: %s' %
375 (aud, audience, json_body)) 427 (aud, audience, json_body))
376 428
377 return parsed 429 return parsed
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698