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

Unified 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 side-by-side diff with in-line comments
Download patch
« no previous file with comments | « chromium-committers/constants.py ('k') | chromium-committers/model.py » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: chromium-committers/hmac_util.py
===================================================================
--- chromium-committers/hmac_util.py (revision 0)
+++ chromium-committers/hmac_util.py (revision 0)
@@ -0,0 +1,168 @@
+# Copyright (c) 2013 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Utilities for generating and verifying hmac authentication."""
+
+__author__ = 'agable@google.com (Aaron Gable)'
+
+
+import collections
+import hashlib
+import functools
+import hmac
+import json
+import logging
+import operator
+import time
+import urllib
+
+from google.appengine.ext import ndb
+
+
+class AuthToken(ndb.Model):
+ """Represents an id/key pair for authentication.
+
+ Attributes:
+ client_name: The human-readable name of the client.
+ client_id: The unique identity for the client that uses this token.
+ secret: The corresponding authentication key.
+ """
+ client_name = ndb.StringProperty()
+ client_id = ndb.StringProperty()
+ secret = ndb.StringProperty()
+
+
+def GenerateHmac(authtoken, t=None, **params):
+ """Generates an HMAC cryptographic hash of the given parameters.
+
+ Can be used either both for generating outgoing authentication and for
+ validating incoming requests. Automatically included the authtoken's client_id
+ and the time in the hashed parameter blob. If t (timestamp) is None, uses now.
+ """
+ if t is None:
+ t = str(int(time.time()))
+ hmac_params = params.copy()
+ hmac_params.update({'id': authtoken.client_id, 't': t})
+ assert all(isinstance(obj, collections.Hashable)
+ for obj in hmac.params.iteritems())
+ blob = urllib.urlencode(sorted(hmac_params.items()))
+ logging.debug('Generating HMAC from blob: %s' % blob)
+ return hmac.new(authtoken.secret, blob, hashlib.sha256).hexdigest()
+
+
+def CheckHmacAuth(handler):
+ """Decorator for webapp2 request handler methods.
+
+ Only use on webapp2.RequestHandler methods (e.g. get, post, put).
+
+ Expects the handler's self.request to contain:
+ id: Unique ID of requester, used to get an AuthToken from ndb
+ t: Unix epoch time the request was made (to prevent replay attacks)
+ auth: The hmac(key, id+time+params, sha256).hexdigest, to authenticate
+ the request, where the key is the matching token in the ID's AuthToken
+ **params: All of the request GET/POST parameters
+
+ Sets request.authenticated to 'hmac' if successful. Otherwise, None.
+ """
+ @functools.wraps(handler)
+ def wrapper(self, *args, **kwargs):
+ """Does the real legwork and calls the wrapped handler."""
+ def abort_auth(log_msg):
+ """Helper method to be an exit hatch when authentication fails."""
+ logging.warning(log_msg)
+ self.request.authenticated = None
+ handler(self, *args, **kwargs)
+
+ def finish_auth(log_msg):
+ """Helper method to be an exit hatch when authentication succeeds."""
+ logging.info(log_msg)
+ handler(self, *args, **kwargs)
+
+ if getattr(self.request, 'authenticated', None):
+ finish_auth('Already authenticated.')
+ return
+
+ # Get the id, time, and auth fields from the request.
+ client_id = self.request.get('id')
+ if not client_id:
+ abort_auth('No id in request.')
+ return
+ logging.debug('Request contained id: %s' % client_id)
+ authtoken = AuthToken.query(AuthToken.client_id == client_id).get()
+ if not authtoken:
+ abort_auth('No auth token stored for client.')
+ return
+ logging.debug('AuthToken is from client: %s' % authtoken.client)
+
+ then = int(self.request.get('t', '0'))
+ if not then:
+ abort_auth('No timestamp in request.')
+ return
+ logging.debug('Request generated at time: %s' % then)
+ now = int(time.time())
+ if abs(now - then) > 60:
+ abort_auth('Timestamp too far off, token expired.')
+ return
+
+ auth = self.request.get('auth')
+ if not auth:
+ abort_auth('No auth in request.')
+ return
+ logging.debug('Request contained auth hash: %s' % auth)
+
+ # Don't include the auth hmac itself in the check.
+ params = self.request.params.copy()
+ params.pop('auth')
+ check = GenerateHmac(authtoken, **params)
+ logging.debug('Expected auth hash is: %s' % check)
+
+ # Constant time comparison.
+ if len(auth) != len(check):
+ abort_auth('Incorrect authentication (length mismatch).')
+ return
+ if reduce(operator.or_,
+ (ord(a) ^ ord(b) for a, b in zip(check, auth)), 0):
+ abort_auth('Incorrect authentication.')
+ return
+
+ # Hooray, they made it!
+ self.request.authenticated = 'hmac'
+ handler(self, *args, **kwargs)
+
+ return wrapper
+
+
+def CreateRequest(**params):
+ """Given a payload to send, constructs an authenticated request.
+
+ Returns a dictionary containing:
+ id: Unique ID of this app, from the datastore AuthToken 'self'
+ t: Current Unix epoch time
+ auth: The hmac(key, id+time+parms, sha256), to authenticate the request,
+ where the key is the corresponding secret in the app's AuthToken
+ **params: All of the GET/POST parameters
+
+ It is up to the calling code to convert this dictionary into valid GET/POST
+ parameters.
+ """
+ authtoken = ndb.Key(AuthToken, 'self').get()
+ if not authtoken:
+ raise AuthError('No AuthToken found for this app.')
+
+ now = str(int(time.time()))
+
+ ret = params.copy()
+ ret.update({'id': authtoken.client_id, 't': now,
+ 'auth': GenerateHmac(authtoken, t=now, **params)})
+ return ret
+
+
+class AuthError(Exception):
+ pass
+
+
+# There needs to be one AuthToken in the datastore so it can be added or edited
+# from the admin console. Do this one-time setup when this module is imported.
+if not ndb.Key(AuthToken, 'self').get():
+ AuthToken(key=ndb.Key(AuthToken, 'self')).put()
« no previous file with comments | « chromium-committers/constants.py ('k') | chromium-committers/model.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698