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

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

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

Powered by Google App Engine
This is Rietveld 408576698