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

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

Issue 1260293009: make version of ts_mon compatible with appengine (Closed) Base URL: https://chromium.googlesource.com/infra/infra.git@master
Patch Set: clean up code Created 5 years, 4 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
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 imp
19 import hashlib 19 import json
20 import logging 20 import logging
21 import os
21 import time 22 import time
22 23
23 from OpenSSL import crypto 24 from oauth2client._helpers import _json_encode
24 from anyjson import simplejson 25 from oauth2client._helpers import _urlsafe_b64decode
26 from oauth2client._helpers import _urlsafe_b64encode
25 27
26 28
27 CLOCK_SKEW_SECS = 300 # 5 minutes in seconds 29 CLOCK_SKEW_SECS = 300 # 5 minutes in seconds
28 AUTH_TOKEN_LIFETIME_SECS = 300 # 5 minutes in seconds 30 AUTH_TOKEN_LIFETIME_SECS = 300 # 5 minutes in seconds
29 MAX_TOKEN_LIFETIME_SECS = 86400 # 1 day in seconds 31 MAX_TOKEN_LIFETIME_SECS = 86400 # 1 day in seconds
30 32
31 33
34 logger = logging.getLogger(__name__)
35
36
32 class AppIdentityError(Exception): 37 class AppIdentityError(Exception):
33 pass 38 pass
34 39
35 40
36 class Verifier(object): 41 def _TryOpenSslImport():
37 """Verifies the signature on a message.""" 42 """Import OpenSSL, avoiding the explicit import where possible.
38 43
39 def __init__(self, pubkey): 44 Importing OpenSSL 0.14 can take up to 0.5s, which is a large price
40 """Constructor. 45 to pay at module import time. However, it's also possible for
46 ``imp.find_module`` to fail to find the module, even when it's
47 installed. (This is the case in various exotic environments,
48 including some relevant for Google.) So we first try a fast-path,
49 and fall back to the slow import as needed.
41 50
42 Args: 51 Args:
43 pubkey, OpenSSL.crypto.PKey, The public key to verify with. 52 None
44 """ 53 Returns:
45 self._pubkey = pubkey 54 None
55 Raises:
56 ImportError if OpenSSL is unavailable.
46 57
47 def verify(self, message, signature): 58 """
48 """Verifies a message against a signature. 59 try:
49 60 _, _package_dir, _ = imp.find_module('OpenSSL')
50 Args: 61 if not (os.path.isfile(os.path.join(_package_dir, 'crypto.py')) or
51 message: string, The message to verify. 62 os.path.isfile(os.path.join(_package_dir, 'crypto.so')) or
52 signature: string, The signature on the message. 63 os.path.isdir(os.path.join(_package_dir, 'crypto'))):
53 64 raise ImportError('No module named OpenSSL.crypto')
54 Returns: 65 return
55 True if message was singed by the private key associated with the public 66 except ImportError:
56 key that this object was constructed with. 67 import OpenSSL.crypto
57 """
58 try:
59 crypto.verify(self._pubkey, signature, message, 'sha256')
60 return True
61 except:
62 return False
63
64 @staticmethod
65 def from_string(key_pem, is_x509_cert):
66 """Construct a Verified instance from a string.
67
68 Args:
69 key_pem: string, public key in PEM format.
70 is_x509_cert: bool, True if key_pem is an X509 cert, otherwise it is
71 expected to be an RSA key in PEM format.
72
73 Returns:
74 Verifier instance.
75
76 Raises:
77 OpenSSL.crypto.Error if the key_pem can't be parsed.
78 """
79 if is_x509_cert:
80 pubkey = crypto.load_certificate(crypto.FILETYPE_PEM, key_pem)
81 else:
82 pubkey = crypto.load_privatekey(crypto.FILETYPE_PEM, key_pem)
83 return Verifier(pubkey)
84 68
85 69
86 class Signer(object): 70 try:
87 """Signs messages with a private key.""" 71 _TryOpenSslImport()
88 72 from oauth2client._openssl_crypt import OpenSSLVerifier
89 def __init__(self, pkey): 73 from oauth2client._openssl_crypt import OpenSSLSigner
90 """Constructor. 74 from oauth2client._openssl_crypt import pkcs12_key_as_pem
91 75 except ImportError:
92 Args: 76 OpenSSLVerifier = None
93 pkey, OpenSSL.crypto.PKey, The private key to sign with. 77 OpenSSLSigner = None
94 """ 78 def pkcs12_key_as_pem(*args, **kwargs):
95 self._key = pkey 79 raise NotImplementedError('pkcs12_key_as_pem requires OpenSSL.')
96
97 def sign(self, message):
98 """Signs a message.
99
100 Args:
101 message: string, Message to be signed.
102
103 Returns:
104 string, The signature of the message for the given key.
105 """
106 return crypto.sign(self._key, message, 'sha256')
107
108 @staticmethod
109 def from_string(key, password='notasecret'):
110 """Construct a Signer instance from a string.
111
112 Args:
113 key: string, private key in P12 format.
114 password: string, password for the private key file.
115
116 Returns:
117 Signer instance.
118
119 Raises:
120 OpenSSL.crypto.Error if the key can't be parsed.
121 """
122 pkey = crypto.load_pkcs12(key, password).get_privatekey()
123 return Signer(pkey)
124 80
125 81
126 def _urlsafe_b64encode(raw_bytes): 82 try:
127 return base64.urlsafe_b64encode(raw_bytes).rstrip('=') 83 from oauth2client._pycrypto_crypt import PyCryptoVerifier
84 from oauth2client._pycrypto_crypt import PyCryptoSigner
85 except ImportError:
86 PyCryptoVerifier = None
87 PyCryptoSigner = None
128 88
129 89
130 def _urlsafe_b64decode(b64string): 90 if OpenSSLSigner:
131 # Guard against unicode strings, which base64 can't handle. 91 Signer = OpenSSLSigner
132 b64string = b64string.encode('ascii') 92 Verifier = OpenSSLVerifier
133 padded = b64string + '=' * (4 - len(b64string) % 4) 93 elif PyCryptoSigner:
134 return base64.urlsafe_b64decode(padded) 94 Signer = PyCryptoSigner
135 95 Verifier = PyCryptoVerifier
136 96 else:
137 def _json_encode(data): 97 raise ImportError('No encryption library found. Please install either '
138 return simplejson.dumps(data, separators = (',', ':')) 98 'PyOpenSSL, or PyCrypto 2.6 or later')
139 99
140 100
141 def make_signed_jwt(signer, payload): 101 def make_signed_jwt(signer, payload):
142 """Make a signed JWT. 102 """Make a signed JWT.
143 103
144 See http://self-issued.info/docs/draft-jones-json-web-token.html. 104 See http://self-issued.info/docs/draft-jones-json-web-token.html.
145 105
146 Args: 106 Args:
147 signer: crypt.Signer, Cryptographic signer. 107 signer: crypt.Signer, Cryptographic signer.
148 payload: dict, Dictionary of data to convert to JSON and then sign. 108 payload: dict, Dictionary of data to convert to JSON and then sign.
149 109
150 Returns: 110 Returns:
151 string, The JWT for the payload. 111 string, The JWT for the payload.
152 """ 112 """
153 header = {'typ': 'JWT', 'alg': 'RS256'} 113 header = {'typ': 'JWT', 'alg': 'RS256'}
154 114
155 segments = [ 115 segments = [
156 _urlsafe_b64encode(_json_encode(header)), 116 _urlsafe_b64encode(_json_encode(header)),
157 _urlsafe_b64encode(_json_encode(payload)), 117 _urlsafe_b64encode(_json_encode(payload)),
158 ] 118 ]
159 signing_input = '.'.join(segments) 119 signing_input = '.'.join(segments)
160 120
161 signature = signer.sign(signing_input) 121 signature = signer.sign(signing_input)
162 segments.append(_urlsafe_b64encode(signature)) 122 segments.append(_urlsafe_b64encode(signature))
163 123
164 logging.debug(str(segments)) 124 logger.debug(str(segments))
165 125
166 return '.'.join(segments) 126 return '.'.join(segments)
167 127
168 128
169 def verify_signed_jwt_with_certs(jwt, certs, audience): 129 def verify_signed_jwt_with_certs(jwt, certs, audience):
170 """Verify a JWT against public certs. 130 """Verify a JWT against public certs.
171 131
172 See http://self-issued.info/docs/draft-jones-json-web-token.html. 132 See http://self-issued.info/docs/draft-jones-json-web-token.html.
173 133
174 Args: 134 Args:
175 jwt: string, A JWT. 135 jwt: string, A JWT.
176 certs: dict, Dictionary where values of public keys in PEM format. 136 certs: dict, Dictionary where values of public keys in PEM format.
177 audience: string, The audience, 'aud', that this JWT should contain. If 137 audience: string, The audience, 'aud', that this JWT should contain. If
178 None then the JWT's 'aud' parameter is not verified. 138 None then the JWT's 'aud' parameter is not verified.
179 139
180 Returns: 140 Returns:
181 dict, The deserialized JSON payload in the JWT. 141 dict, The deserialized JSON payload in the JWT.
182 142
183 Raises: 143 Raises:
184 AppIdentityError if any checks are failed. 144 AppIdentityError if any checks are failed.
185 """ 145 """
186 segments = jwt.split('.') 146 segments = jwt.split('.')
187 147
188 if (len(segments) != 3): 148 if len(segments) != 3:
189 raise AppIdentityError( 149 raise AppIdentityError('Wrong number of segments in token: %s' % jwt)
190 'Wrong number of segments in token: %s' % jwt)
191 signed = '%s.%s' % (segments[0], segments[1]) 150 signed = '%s.%s' % (segments[0], segments[1])
192 151
193 signature = _urlsafe_b64decode(segments[2]) 152 signature = _urlsafe_b64decode(segments[2])
194 153
195 # Parse token. 154 # Parse token.
196 json_body = _urlsafe_b64decode(segments[1]) 155 json_body = _urlsafe_b64decode(segments[1])
197 try: 156 try:
198 parsed = simplejson.loads(json_body) 157 parsed = json.loads(json_body.decode('utf-8'))
199 except: 158 except:
200 raise AppIdentityError('Can\'t parse token: %s' % json_body) 159 raise AppIdentityError('Can\'t parse token: %s' % json_body)
201 160
202 # Check signature. 161 # Check signature.
203 verified = False 162 verified = False
204 for (keyname, pem) in certs.items(): 163 for pem in certs.values():
205 verifier = Verifier.from_string(pem, True) 164 verifier = Verifier.from_string(pem, True)
206 if (verifier.verify(signed, signature)): 165 if verifier.verify(signed, signature):
207 verified = True 166 verified = True
208 break 167 break
209 if not verified: 168 if not verified:
210 raise AppIdentityError('Invalid token signature: %s' % jwt) 169 raise AppIdentityError('Invalid token signature: %s' % jwt)
211 170
212 # Check creation timestamp. 171 # Check creation timestamp.
213 iat = parsed.get('iat') 172 iat = parsed.get('iat')
214 if iat is None: 173 if iat is None:
215 raise AppIdentityError('No iat field in token: %s' % json_body) 174 raise AppIdentityError('No iat field in token: %s' % json_body)
216 earliest = iat - CLOCK_SKEW_SECS 175 earliest = iat - CLOCK_SKEW_SECS
217 176
218 # Check expiration timestamp. 177 # Check expiration timestamp.
219 now = long(time.time()) 178 now = int(time.time())
220 exp = parsed.get('exp') 179 exp = parsed.get('exp')
221 if exp is None: 180 if exp is None:
222 raise AppIdentityError('No exp field in token: %s' % json_body) 181 raise AppIdentityError('No exp field in token: %s' % json_body)
223 if exp >= now + MAX_TOKEN_LIFETIME_SECS: 182 if exp >= now + MAX_TOKEN_LIFETIME_SECS:
224 raise AppIdentityError( 183 raise AppIdentityError('exp field too far in future: %s' % json_body)
225 'exp field too far in future: %s' % json_body)
226 latest = exp + CLOCK_SKEW_SECS 184 latest = exp + CLOCK_SKEW_SECS
227 185
228 if now < earliest: 186 if now < earliest:
229 raise AppIdentityError('Token used too early, %d < %d: %s' % 187 raise AppIdentityError('Token used too early, %d < %d: %s' %
230 (now, earliest, json_body)) 188 (now, earliest, json_body))
231 if now > latest: 189 if now > latest:
232 raise AppIdentityError('Token used too late, %d > %d: %s' % 190 raise AppIdentityError('Token used too late, %d > %d: %s' %
233 (now, latest, json_body)) 191 (now, latest, json_body))
234 192
235 # Check audience. 193 # Check audience.
236 if audience is not None: 194 if audience is not None:
237 aud = parsed.get('aud') 195 aud = parsed.get('aud')
238 if aud is None: 196 if aud is None:
239 raise AppIdentityError('No aud field in token: %s' % json_body) 197 raise AppIdentityError('No aud field in token: %s' % json_body)
240 if aud != audience: 198 if aud != audience:
241 raise AppIdentityError('Wrong recipient, %s != %s: %s' % 199 raise AppIdentityError('Wrong recipient, %s != %s: %s' %
242 (aud, audience, json_body)) 200 (aud, audience, json_body))
243 201
244 return parsed 202 return parsed
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698