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

Side by Side Diff: reviewbot/third_party/google-api-python-client/oauth2client/crypt.py

Issue 20515002: Add google-api-python-client in third_party/ (Closed) Base URL: https://src.chromium.org/chrome/trunk/tools/
Patch Set: Created 7 years, 5 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
Property Changes:
Added: svn:eol-style
+ LF
OLDNEW
(Empty)
1 #!/usr/bin/python2.4
2 # -*- coding: utf-8 -*-
3 #
4 # Copyright (C) 2011 Google Inc.
5 #
6 # Licensed under the Apache License, Version 2.0 (the "License");
7 # you may not use this file except in compliance with the License.
8 # You may obtain a copy of the License at
9 #
10 # http://www.apache.org/licenses/LICENSE-2.0
11 #
12 # Unless required by applicable law or agreed to in writing, software
13 # distributed under the License is distributed on an "AS IS" BASIS,
14 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 # See the License for the specific language governing permissions and
16 # limitations under the License.
17
18 import base64
19 import hashlib
20 import logging
21 import time
22
23 from anyjson import simplejson
24
25
26 CLOCK_SKEW_SECS = 300 # 5 minutes in seconds
27 AUTH_TOKEN_LIFETIME_SECS = 300 # 5 minutes in seconds
28 MAX_TOKEN_LIFETIME_SECS = 86400 # 1 day in seconds
29
30
31 logger = logging.getLogger(__name__)
32
33
34 class AppIdentityError(Exception):
35 pass
36
37
38 try:
39 from OpenSSL import crypto
40
41
42 class OpenSSLVerifier(object):
43 """Verifies the signature on a message."""
44
45 def __init__(self, pubkey):
46 """Constructor.
47
48 Args:
49 pubkey, OpenSSL.crypto.PKey, The public key to verify with.
50 """
51 self._pubkey = pubkey
52
53 def verify(self, message, signature):
54 """Verifies a message against a signature.
55
56 Args:
57 message: string, The message to verify.
58 signature: string, The signature on the message.
59
60 Returns:
61 True if message was signed by the private key associated with the public
62 key that this object was constructed with.
63 """
64 try:
65 crypto.verify(self._pubkey, signature, message, 'sha256')
66 return True
67 except:
68 return False
69
70 @staticmethod
71 def from_string(key_pem, is_x509_cert):
72 """Construct a Verified instance from a string.
73
74 Args:
75 key_pem: string, public key in PEM format.
76 is_x509_cert: bool, True if key_pem is an X509 cert, otherwise it is
77 expected to be an RSA key in PEM format.
78
79 Returns:
80 Verifier instance.
81
82 Raises:
83 OpenSSL.crypto.Error if the key_pem can't be parsed.
84 """
85 if is_x509_cert:
86 pubkey = crypto.load_certificate(crypto.FILETYPE_PEM, key_pem)
87 else:
88 pubkey = crypto.load_privatekey(crypto.FILETYPE_PEM, key_pem)
89 return OpenSSLVerifier(pubkey)
90
91
92 class OpenSSLSigner(object):
93 """Signs messages with a private key."""
94
95 def __init__(self, pkey):
96 """Constructor.
97
98 Args:
99 pkey, OpenSSL.crypto.PKey (or equiv), The private key to sign with.
100 """
101 self._key = pkey
102
103 def sign(self, message):
104 """Signs a message.
105
106 Args:
107 message: string, Message to be signed.
108
109 Returns:
110 string, The signature of the message for the given key.
111 """
112 return crypto.sign(self._key, message, 'sha256')
113
114 @staticmethod
115 def from_string(key, password='notasecret'):
116 """Construct a Signer instance from a string.
117
118 Args:
119 key: string, private key in PKCS12 or PEM format.
120 password: string, password for the private key file.
121
122 Returns:
123 Signer instance.
124
125 Raises:
126 OpenSSL.crypto.Error if the key can't be parsed.
127 """
128 if key.startswith('-----BEGIN '):
129 pkey = crypto.load_privatekey(crypto.FILETYPE_PEM, key)
130 else:
131 pkey = crypto.load_pkcs12(key, password).get_privatekey()
132 return OpenSSLSigner(pkey)
133
134 except ImportError:
135 OpenSSLVerifier = None
136 OpenSSLSigner = None
137
138
139 try:
140 from Crypto.PublicKey import RSA
141 from Crypto.Hash import SHA256
142 from Crypto.Signature import PKCS1_v1_5
143
144
145 class PyCryptoVerifier(object):
146 """Verifies the signature on a message."""
147
148 def __init__(self, pubkey):
149 """Constructor.
150
151 Args:
152 pubkey, OpenSSL.crypto.PKey (or equiv), The public key to verify with.
153 """
154 self._pubkey = pubkey
155
156 def verify(self, message, signature):
157 """Verifies a message against a signature.
158
159 Args:
160 message: string, The message to verify.
161 signature: string, The signature on the message.
162
163 Returns:
164 True if message was signed by the private key associated with the public
165 key that this object was constructed with.
166 """
167 try:
168 return PKCS1_v1_5.new(self._pubkey).verify(
169 SHA256.new(message), signature)
170 except:
171 return False
172
173 @staticmethod
174 def from_string(key_pem, is_x509_cert):
175 """Construct a Verified instance from a string.
176
177 Args:
178 key_pem: string, public key in PEM format.
179 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.
181
182 Returns:
183 Verifier instance.
184
185 Raises:
186 NotImplementedError if is_x509_cert is true.
187 """
188 if is_x509_cert:
189 raise NotImplementedError(
190 'X509 certs are not supported by the PyCrypto library. '
191 'Try using PyOpenSSL if native code is an option.')
192 else:
193 pubkey = RSA.importKey(key_pem)
194 return PyCryptoVerifier(pubkey)
195
196
197 class PyCryptoSigner(object):
198 """Signs messages with a private key."""
199
200 def __init__(self, pkey):
201 """Constructor.
202
203 Args:
204 pkey, OpenSSL.crypto.PKey (or equiv), The private key to sign with.
205 """
206 self._key = pkey
207
208 def sign(self, message):
209 """Signs a message.
210
211 Args:
212 message: string, Message to be signed.
213
214 Returns:
215 string, The signature of the message for the given key.
216 """
217 return PKCS1_v1_5.new(self._key).sign(SHA256.new(message))
218
219 @staticmethod
220 def from_string(key, password='notasecret'):
221 """Construct a Signer instance from a string.
222
223 Args:
224 key: string, private key in PEM format.
225 password: string, password for private key file. Unused for PEM files.
226
227 Returns:
228 Signer instance.
229
230 Raises:
231 NotImplementedError if they key isn't in PEM format.
232 """
233 if key.startswith('-----BEGIN '):
234 pkey = RSA.importKey(key)
235 else:
236 raise NotImplementedError(
237 'PKCS12 format is not supported by the PyCrpto library. '
238 'Try converting to a "PEM" '
239 '(openssl pkcs12 -in xxxxx.p12 -nodes -nocerts > privatekey.pem) '
240 'or using PyOpenSSL if native code is an option.')
241 return PyCryptoSigner(pkey)
242
243 except ImportError:
244 PyCryptoVerifier = None
245 PyCryptoSigner = None
246
247
248 if OpenSSLSigner:
249 Signer = OpenSSLSigner
250 Verifier = OpenSSLVerifier
251 elif PyCryptoSigner:
252 Signer = PyCryptoSigner
253 Verifier = PyCryptoVerifier
254 else:
255 raise ImportError('No encryption library found. Please install either '
256 'PyOpenSSL, or PyCrypto 2.6 or later')
257
258
259 def _urlsafe_b64encode(raw_bytes):
260 return base64.urlsafe_b64encode(raw_bytes).rstrip('=')
261
262
263 def _urlsafe_b64decode(b64string):
264 # Guard against unicode strings, which base64 can't handle.
265 b64string = b64string.encode('ascii')
266 padded = b64string + '=' * (4 - len(b64string) % 4)
267 return base64.urlsafe_b64decode(padded)
268
269
270 def _json_encode(data):
271 return simplejson.dumps(data, separators = (',', ':'))
272
273
274 def make_signed_jwt(signer, payload):
275 """Make a signed JWT.
276
277 See http://self-issued.info/docs/draft-jones-json-web-token.html.
278
279 Args:
280 signer: crypt.Signer, Cryptographic signer.
281 payload: dict, Dictionary of data to convert to JSON and then sign.
282
283 Returns:
284 string, The JWT for the payload.
285 """
286 header = {'typ': 'JWT', 'alg': 'RS256'}
287
288 segments = [
289 _urlsafe_b64encode(_json_encode(header)),
290 _urlsafe_b64encode(_json_encode(payload)),
291 ]
292 signing_input = '.'.join(segments)
293
294 signature = signer.sign(signing_input)
295 segments.append(_urlsafe_b64encode(signature))
296
297 logger.debug(str(segments))
298
299 return '.'.join(segments)
300
301
302 def verify_signed_jwt_with_certs(jwt, certs, audience):
303 """Verify a JWT against public certs.
304
305 See http://self-issued.info/docs/draft-jones-json-web-token.html.
306
307 Args:
308 jwt: string, A JWT.
309 certs: dict, Dictionary where values of public keys in PEM format.
310 audience: string, The audience, 'aud', that this JWT should contain. If
311 None then the JWT's 'aud' parameter is not verified.
312
313 Returns:
314 dict, The deserialized JSON payload in the JWT.
315
316 Raises:
317 AppIdentityError if any checks are failed.
318 """
319 segments = jwt.split('.')
320
321 if (len(segments) != 3):
322 raise AppIdentityError(
323 'Wrong number of segments in token: %s' % jwt)
324 signed = '%s.%s' % (segments[0], segments[1])
325
326 signature = _urlsafe_b64decode(segments[2])
327
328 # Parse token.
329 json_body = _urlsafe_b64decode(segments[1])
330 try:
331 parsed = simplejson.loads(json_body)
332 except:
333 raise AppIdentityError('Can\'t parse token: %s' % json_body)
334
335 # Check signature.
336 verified = False
337 for (keyname, pem) in certs.items():
338 verifier = Verifier.from_string(pem, True)
339 if (verifier.verify(signed, signature)):
340 verified = True
341 break
342 if not verified:
343 raise AppIdentityError('Invalid token signature: %s' % jwt)
344
345 # Check creation timestamp.
346 iat = parsed.get('iat')
347 if iat is None:
348 raise AppIdentityError('No iat field in token: %s' % json_body)
349 earliest = iat - CLOCK_SKEW_SECS
350
351 # Check expiration timestamp.
352 now = long(time.time())
353 exp = parsed.get('exp')
354 if exp is None:
355 raise AppIdentityError('No exp field in token: %s' % json_body)
356 if exp >= now + MAX_TOKEN_LIFETIME_SECS:
357 raise AppIdentityError(
358 'exp field too far in future: %s' % json_body)
359 latest = exp + CLOCK_SKEW_SECS
360
361 if now < earliest:
362 raise AppIdentityError('Token used too early, %d < %d: %s' %
363 (now, earliest, json_body))
364 if now > latest:
365 raise AppIdentityError('Token used too late, %d > %d: %s' %
366 (now, latest, json_body))
367
368 # Check audience.
369 if audience is not None:
370 aud = parsed.get('aud')
371 if aud is None:
372 raise AppIdentityError('No aud field in token: %s' % json_body)
373 if aud != audience:
374 raise AppIdentityError('Wrong recipient, %s != %s: %s' %
375 (aud, audience, json_body))
376
377 return parsed
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698