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

Side by Side Diff: chromium-committers/hmac_util.py

Issue 25515004: Add chromium-committers appengine app. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/
Patch Set: Created 7 years, 2 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
(Empty)
1 # Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
4
5 """Utilities for generating and verifying hmac authentication."""
6
7 __author__ = 'agable@google.com (Aaron Gable)'
8
9
10 import datetime
11 import hashlib
12 import functools
13 import hmac
14 import json
15 import logging
16 import operator
17 import time
18 import urllib
19
20 from google.appengine.ext import ndb
21
22
23 class AuthToken(ndb.Model):
24 """Represents an id/key pair for authentication.
25
26 Attributes:
27 client: The human-readable name of the client.
28 client_id: The unique identity for the client that uses this token.
29 secret: The corresponding authentication key.
30 """
31 client = ndb.StringProperty()
32 client_id = ndb.StringProperty()
33 secret = ndb.StringProperty()
34
35
36 def GenerateHmac(authtoken, t=None, **params):
37 """Generates an HMAC cryptographic hash of the given parameters.
38
39 Can be used either both for generating outgoing authentication and for
40 validating incoming requests. Automatically included the authtoken's client_id
41 and the time in the hashed parameter blob. If t (timestamp) is None, uses now.
42 """
43 if t is None:
44 t = str(time.time())
45 hmac_params = params.copy()
46 hmac_params.update({'id': authtoken.client_id, 't': t})
47 blob = urllib.urlencode(sorted(hmac_params.items()))
48 logging.debug('Generating HMAC from blob: %s' % blob)
iannucci 2013/10/09 18:57:30 could blob be superhuge? Is that a problem? DDOS o
agable 2013/10/09 19:12:31 The blob can be pretty big, but it hasn't given ap
49 return hmac.new(authtoken.secret, blob, hashlib.sha256).hexdigest()
50
51
52 def CheckHmacAuth(handler):
53 """Decorator for webapp2 request handler methods.
54
55 Only use on webapp2.RequestHandler methods (e.g. get, post, put).
56
57 Expects the handler's self.request to contain:
58 id: Unique ID of requester, used to get an AuthToken from ndb
59 t: Unix epoch time the request was made (to prevent replay attacks)
60 auth: The hmac(key, id+time+params, sha256).hexdigest, to authenticate
61 the request, where the key is the matching token in the ID's AuthToken
62 **params: All of the request GET/POST parameters
63
64 Sets request.authenticated to 'hmac' if successful. Otherwise, None.
65 """
66 @functools.wraps(handler)
67 def wrapper(self, *args, **kwargs):
68 """Does the real legwork and calls the wrapped handler."""
69 def abort_auth(log_msg):
70 """Helper method to be an exit hatch when authentication fails."""
71 logging.warning(log_msg)
72 self.request.authenticated = None
73 handler(self, *args, **kwargs)
74
75 def finish_auth(log_msg):
76 """Helper method to be an exit hatch when authentication succeeds."""
77 logging.info(log_msg)
78 handler(self, *args, **kwargs)
79
80 if getattr(self.request, 'authenticated', None):
81 finish_auth('Already authenticated.')
82 return
83
84 # Get the id, time, and auth fields from the request.
85 client_id = self.request.get('id')
86 if not client_id:
87 abort_auth('No id in request.')
88 return
89 logging.debug('Request contained id: %s' % client_id)
90 authtoken = AuthToken.query(AuthToken.client_id == client_id).get()
91 if not authtoken:
92 abort_auth('No auth token stored for client.')
93 return
94 logging.debug('AuthToken is from client: %s' % authtoken.client)
95
96 then = float(self.request.get('t', '0'))
97 if not then:
98 abort_auth('No timestamp in request.')
99 return
100 logging.debug('Request generated at time: %s' % then)
101 now = time.time()
102 if (datetime.timedelta(seconds=abs(now - then)) >
103 datetime.timedelta(minutes=1)):
104 abort_auth('Time more than 10 minutes out of sync.')
105 return
106
107 auth = self.request.get('auth')
108 if not auth:
109 abort_auth('No auth in request.')
110 return
111 logging.debug('Request contained auth hash: %s' % auth)
112 if len(auth) != 64: # 256 bits / 4 bits per hexadecimal char
113 abort_auth('Incorrect authentication (length mismatch).')
114 return
115
116 # Don't include the auth hmac itself in the check.
117 params = self.request.params.copy()
118 params.pop('auth')
119 check = GenerateHmac(authtoken, **params)
120 logging.debug('Expected auth hash is: %s' % check)
121
122 # Constant time comparison.
123 if reduce(operator.or_,
124 (ord(a) ^ ord(b) for a, b in zip(check, auth)), 0):
125 abort_auth('Incorrect authentication.')
126 return
127
128 # Hooray, they made it!
129 self.request.authenticated = 'hmac'
130 handler(self, *args, **kwargs)
131
132 return wrapper
133
134
135 def CreateRequest(**params):
136 """Given a payload to send, constructs an authenticated request.
137
138 Returns a dictionary containing:
139 id: Unique ID of this app, from the datastore AuthToken 'self'
140 t: Current Unix epoch time
141 auth: The hmac(key, id+time+parms, sha256), to authenticate the request,
142 where the key is the corresponding secret in the app's AuthToken
143 **params: All of the GET/POST parameters
144
145 It is up to the calling code to convert this dictionary into valid GET/POST
146 parameters.
147 """
148 authtoken = ndb.Key(AuthToken, 'self').get()
149 if not authtoken:
150 raise AuthError('No AuthToken found for this app.')
151
152 now = str(time.time())
153
154 ret = params.copy()
155 ret.update({'id': authtoken.client_id, 't': now,
156 'auth': GenerateHmac(authtoken, t=now, **params)})
157 return ret
158
159
160 class AuthError(Exception):
161 pass
162
163
164 # There needs to be one AuthToken in the datastore so it can be added or edited
165 # from the admin console. Do this one-time setup when this module is imported.
166 if not ndb.Key(AuthToken, 'self').get():
167 AuthToken(key=ndb.Key(AuthToken, 'self')).put()
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698