-rw-r--r-- | backend/python/src/app.yaml | 20 | ||||
-rw-r--r-- | backend/python/src/clipperz.py | 708 |
2 files changed, 728 insertions, 0 deletions
diff --git a/backend/python/src/app.yaml b/backend/python/src/app.yaml new file mode 100644 index 0000000..5e085a9 --- a/dev/null +++ b/backend/python/src/app.yaml @@ -0,0 +1,20 @@ +application: clipperz +version: 1 +runtime: python +api_version: 1 + +handlers: +- url: /json + script: clipperz.py + +- url: /css + static_dir: css + +- url: /js + static_dir: js + +- url: /images + static_dir: images + +- url: /.* + script: clipperz.py diff --git a/backend/python/src/clipperz.py b/backend/python/src/clipperz.py new file mode 100644 index 0000000..c8d91de --- a/dev/null +++ b/backend/python/src/clipperz.py @@ -0,0 +1,708 @@ +# +# Copyright 2008-2011 Clipperz Srl +# +# This file is part of Clipperz's Javascript Crypto Library. +# Javascript Crypto Library provides web developers with an extensive +# and efficient set of cryptographic functions. The library aims to +# obtain maximum execution speed while preserving modularity and +# reusability. +# For further information about its features and functionalities please +# refer to http://www.clipperz.com +# +# * Javascript Crypto Library is free software: you can redistribute +# it and/or modify it under the terms of the GNU Affero General Public +# License as published by the Free Software Foundation, either version +# 3 of the License, or (at your option) any later version. +# +# * Javascript Crypto Library is distributed in the hope that it will +# be useful, but WITHOUT ANY WARRANTY; without even the implied +# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU Affero General Public License for more details. +# +# * You should have received a copy of the GNU Affero General Public +# License along with Javascript Crypto Library. If not, see +# <http://www.gnu.org/licenses/>. +# + +import os +import cgi +import wsgiref.handlers + +import datetime +import uuid +import random +import hashlib + +import logging + +from google.appengine.api import users +from google.appengine.ext import webapp +from google.appengine.ext import db +from google.appengine.ext.webapp import template + +from django.utils import simplejson + +#============================================================================== + +sessionTimeout = datetime.timedelta(minutes=-2) + +def randomSeed(): + return hex(random.getrandbits(32*8))[2:-1] + +def clipperzHash(aString): + #logging.info(">>> string: " + aString) + firstRound = hashlib.sha256() + firstRound.update(aString) + #logging.info("firstRound: " + firstRound.hexdigest() + " - " + firstRound.digest()) + result = hashlib.sha256() + result.update(firstRound.digest()) + #logging.info("<<< finalResul: " + result.hexdigest()) + + return result.hexdigest() + +#============================================================================== + +class User(db.Model): + username = db.StringProperty() + srp_s = db.StringProperty() + srp_v = db.StringProperty() + header = db.TextProperty() + statistics = db.TextProperty() + auth_version= db.StringProperty() + version = db.StringProperty() + lock = db.StringProperty() + + def updateCredentials(self, someCredentials): + self.username = someCredentials['C'] + self.srp_s = someCredentials['s'] + self.srp_v = someCredentials['v'] + self.auth_version = someCredentials['version'] + + def update(self, someData): + self.header = someData['header'] + self.statistics = someData['statistics'] + self.version = someData['version'] + self.lock = someData['lock'] + +#------------------------------------------------------------------------------ + +class Record(db.Model): + user = db.ReferenceProperty(User) + reference = db.StringProperty() + data = db.TextProperty() + version = db.StringProperty() + creation_date = db.DateTimeProperty(auto_now_add=True) + update_date = db.DateTimeProperty(auto_now_add=True) + access_date = db.DateTimeProperty(auto_now_add=True) + +#------------------------------------------------------------------------------ + +class RecordVersion(db.Model): + record = db.ReferenceProperty(Record) + reference = db.StringProperty() + header = db.TextProperty() + data = db.TextProperty() + version = db.StringProperty() + previousVersionKey = db.StringProperty() + previousVersion = db.SelfReferenceProperty() + creation_date = db.DateTimeProperty(auto_now_add=True) + update_date = db.DateTimeProperty(auto_now_add=True) + access_date = db.DateTimeProperty(auto_now_add=True) + + def update(self, someData): + recordData = someData['record']; + self.parent().reference = recordData['reference'] + self.parent().data = recordData['data'] + self.parent().version = recordData['version'] + self.parent().update_date = datetime.datetime.now() + + recordVersionData = someData['currentRecordVersion']; + self.reference = recordVersionData ['reference'] + self.data = recordVersionData ['data'] + self.version = recordVersionData ['version'] + #self.previous_version = #recordVersionData ['previousVersion'] + self.previous_version_key = recordVersionData ['previousVersionKey'] + self.update_date = datetime.datetime.now() + +#------------------------------------------------------------------------------ + +class OneTimePassword(db.Model): + user = db.ReferenceProperty(User) + status = db.StringProperty() + reference = db.StringProperty() + keyValue = db.StringProperty() + keyChecksum = db.StringProperty() + data = db.TextProperty() + version = db.StringProperty() + creation_date = db.DateTimeProperty(auto_now_add=True) + request_date = db.DateTimeProperty() + usage_date = db.DateTimeProperty() + + def update(self, someParameters, aStatus): + self.reference = someParameters['reference'] + self.keyValue = someParameters['key'] + self.keyChecksum = someParameters['keyChecksum'] + self.data = someParameters['data'] + self.version = someParameters['version'] + self.status = aStatus + + def reset(self, aStatus): + self.data = "" + self.status = aStatus + + return self + +#------------------------------------------------------------------------------ + +class Session(db.Expando): + sessionId = db.StringProperty() + access_date = db.DateTimeProperty() + +#============================================================================== + +class MainPage(webapp.RequestHandler): + def get(self): + path = os.path.join(os.path.dirname(__file__), 'static%s' % self.request.path) + self.response.out.write(template.render(path, {})) + +#============================================================================== + +class XHR(webapp.RequestHandler): + + #========================================================================== + + def get(self): + logging.info("self.request.path: " + self.request.path) + if self.request.path == "/dump": + session = self.getSession() + userData = {} + offline_data_placeholder = "" + + user = db.Query(User).filter('username =', session.C).get() + + userData['users'] = { + 'catchAllUser': { + '__masterkey_test_value__': 'masterkey', + 's': '112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00', + 'v': '112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00' + } + } + + records = {} + for currentRecord in db.Query(Record).ancestor(user): + versions = {} + for currentVersion in db.Query(RecordVersion).ancestor(currentRecord): + versions[currentVersion.reference] ={ + 'header': currentVersion.header, + 'data': currentVersion.data, + 'version': currentVersion.version, + 'creationDate': str(currentVersion.creation_date), + 'updateDate': str(currentVersion.update_date), + 'accessDate': str(currentVersion.access_date) + } + + records[currentRecord.reference] = { + 'data': currentRecord.data, + 'version': currentRecord.version, + 'creationDate': str(currentRecord.creation_date), + 'updateDate': str(currentRecord.update_date), + 'accessDate': str(currentRecord.access_date), + 'currentVersion': currentVersion.reference, + 'versions': versions + } + + userData['users'][user.username] = { + 's': user.srp_s, + 'v': user.srp_v, + 'version': user.auth_version, + 'maxNumberOfRecords': '100', + 'userDetails': user.header, + 'statistics': user.statistics, + 'userDetailsVersion': user.version, + 'records': records + } + + offline_data_placeholder = offline_data_placeholder + "_clipperz_dump_data_ = " + simplejson.dumps(userData, indent=4) + "\n" + offline_data_placeholder = offline_data_placeholder + "Clipperz.PM.Proxy.defaultProxy = new Clipperz.PM.Proxy.Offline();" + "\n" + offline_data_placeholder = offline_data_placeholder + "Clipperz.Crypto.PRNG.defaultRandomGenerator().fastEntropyAccumulationForTestingPurpose();" + "\n" + + path = os.path.join(os.path.dirname(__file__), 'static/dump.html') + + self.response.headers.add_header('Content-Type', 'text/html') + self.response.headers.add_header('Content-Disposition', 'attachment', filename='Clipperz.html') + self.response.out.write(template.render(path, {'offline_data_placeholder': offline_data_placeholder})) + + #========================================================================== + + def post(self): + method = self.request.get('method') + parameters = simplejson.loads(self.request.get('parameters')) + session = self.getSession() + result = {}; + + #---------------------------------------------------------------------- + + if method == 'registration': + message = parameters['message']; + + if message == 'completeRegistration': + user = User() + + user.updateCredentials(parameters['credentials']) + user.update(parameters['user']) + user.put() + + result['lock'] = user.lock + result['result'] = "done" + + #---------------------------------------------------------------------- + + elif method == 'handshake': + srp_g = 2L + srp_n = long("0x%s" % "115b8b692e0e045692cf280b436735c77a5a9e8a9e7ed56c965f87db5b2a2ece3", 16) + + message = parameters['message']; + + #------------------------------------------------------------------ + + if message == 'connect': + session.C = parameters['parameters']['C'] + session.A = parameters['parameters']['A'] + + user = db.Query(User).filter('username =', session.C).get() + + if user != None: + try: + optId = session.otpId + + oneTimePassword = db.Query(OneTimePassword).filter('keyValue =', optId).get() + + if oneTimePassword.parent().username != user.username: + oneTimePassword.reset('DISABLED').put() + raise Exception, "User missmatch between the current session and 'One Time Password' user" + elif oneTimePassword.status != 'REQUESTED': + oneTimePassword.reset('DISABLED').put() + raise Exception, "Tring to use an 'One Time Password' in the wrong state" + + oneTimePassword.reset("USED").put() + + result['oneTimePassword'] = oneTimePassword.reference + + except Exception, detail: + logging.error("connect.optId: " + str(detail)) + + session.s = user.srp_s + session.v = user.srp_v + else: + session.s = "112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00" + session.v = "112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00" + + session.b = randomSeed() + session.B = hex(long("0x%s" % session.v, 16) + pow(srp_g, long("0x%s" %session.b, 16), srp_n))[2:-1] + + result['s'] = session.s + result['B'] = session.B + + #------------------------------------------------------------------ + + elif message == 'credentialCheck': + B = long("0x%s" % session.B, 16) + b = long("0x%s" % session.b, 16) + A = long("0x%s" % session.A, 16) + v = long("0x%s" % session.v, 16) + u = long("0x%s" % clipperzHash(str(B)), 16) + n = srp_n + + S = pow((A * pow(v, u, n)), b, n) + K = clipperzHash(str(S)) + M1 = clipperzHash(str(A) + str(B) + K) + + if M1 == parameters['parameters']['M1']: + session.K = K + M2 = clipperzHash(str(A) + M1 + K) + + result['M2'] = M2 + result["connectionId"] = "" + result["loginInfo"] = {} + result["loginInfo"]["latest"] = {} + result["loginInfo"]["current"] = {} + result["offlineCopyNeeded"] = "false"; + result["lock"] = "----"; + else: + result['error'] = "?" + + #------------------------------------------------------------------ + + elif message == 'oneTimePassword': + oneTimePassword = db.Query(OneTimePassword).filter("keyValue =", parameters["parameters"]["oneTimePasswordKey"]).get() + + if oneTimePassword != None: + if oneTimePassword.status == 'ACTIVE': + if oneTimePassword.keyChecksum == parameters['parameters']['oneTimePasswordKeyChecksum']: + #session.userId = str(oneTimePassword.parent().username) + session.otpId = str(oneTimePassword.keyValue) + + result['data'] = oneTimePassword.data + result['version'] = oneTimePassword.version + + oneTimePassword.reset('REQUESTED').put() + + else: + oneTimePassword.reset('DISABLED').put() + raise Exception, "The requested One Time Password has been disabled, due to a wrong keyChecksum" + else: + raise Exception, "The requested One Time Password was not active" + else: + raise Exception, "The requested One Time Password has not been found" + + #---------------------------------------------------------------------- + + elif method == 'message': + if parameters['srpSharedSecret'] == session.K: + message = parameters['message'] + + if message == 'getUserDetails': + # {"message":"getUserDetails", "srpSharedSecret":"f18e5cf7c3a83b67d4db9444af813ee48c13daf4f8f6635397d593e52ba89a08", "parameters":{}} + user = db.Query(User).filter('username =', session.C).get() + + result['header'] = user.header; + result['statistics'] = user.statistics; + result['version'] = user.version; + + elif message == "addNewRecords": + user = db.Query(User).filter('username =', session.C).get() + result = db.run_in_transaction(self.addNewRecords, session, user, parameters) + + """ + user = db.Query(User).filter('username =', session.C).get() + user.update(parameters['parameters']['user']) + + for recordParameter in parameters['parameters']['records']: + record = Record(parent=user) + record.put() + recordVersion = RecordVersion(parent=record) + recordVersion.put() + + recordVersion.update(recordParameter) + + record.put() + recordVersion.put() + + user.put(); + + result['lock'] = user.lock + result['result'] = 'done' + """ + + elif message == 'getRecordDetail': + record = db.Query(Record).ancestor(db.Query(User).filter('username =', session.C).get()).filter('reference =', parameters["parameters"]["reference"]).get() + recordVersion = db.Query(RecordVersion).ancestor(record).get() + + result['currentVersion'] = {} + result['currentVersion']['reference'] = recordVersion.reference + result['currentVersion']['data'] = recordVersion.data + result['currentVersion']['header'] = recordVersion.header + result['currentVersion']['version'] = recordVersion.version + result['currentVersion']['creationDate'] = str(recordVersion.creation_date) + result['currentVersion']['updateDate'] = str(recordVersion.update_date) + result['currentVersion']['accessDate'] = str(recordVersion.access_date) + + result['reference'] = record.reference + result['data'] = record.data + result['version'] = record.version + result['creationDate'] = str(record.creation_date) + result['updateDate'] = str(record.update_date) + result['accessDate'] = str(record.access_date) + result['oldestUsedEncryptedVersion'] = "---" + + elif message == 'updateData': + user = db.Query(User).filter('username =', session.C).get() + user.update(parameters['parameters']['user']) + + for recordParameter in parameters['parameters']['records']: + logging.info('reference =' + recordParameter['record']['reference']) + record = db.Query(Record).ancestor(user).filter('reference =', recordParameter['record']['reference']).get() + recordVersion = db.Query(RecordVersion).ancestor(record).get() + + recordVersion.update(recordParameter) + + recordVersion.put() + recordVersion.parent().put() + + user.put(); + + result['lock'] = user.lock + result['result'] = 'done' + + elif message == 'deleteRecords': + user = db.Query(User).filter('username =', session.C).get() + user.update(parameters['parameters']['user']) + + for recordReference in parameters['parameters']['recordReferences']: + record = db.Query(Record).ancestor(user).filter('reference =', recordReference).get() + #recordVersion = db.Query(RecordVersion).ancestor(record).get() + + db.delete(db.Query(RecordVersion).ancestor(record)) + record.delete() + + user.put() + + result['lock'] = user.lock + result['result'] = 'done' + + elif message == 'deleteUser': + user = db.Query(User).filter('username =', session.C).get() + db.delete(db.Query(RecordVersion).ancestor(user)) + db.delete(db.Query(Record).ancestor(user)) + user.delete() + + elif message == 'addNewOneTimePassword': + user = db.Query(User).filter('username =', session.C).get() + user.update(parameters['parameters']['user']) + + oneTimePassword = OneTimePassword(parent=user) + oneTimePassword.update(parameters['parameters']['oneTimePassword'], "ACTIVE") + oneTimePassword.put() + + user.put() + + result['lock'] = user.lock + result['result'] = 'done' + + elif message == 'updateOneTimePasswords': + user = db.Query(User).filter('username =', session.C).get() + user.update(parameters['parameters']['user']) + + validOtpReferences = parameters['parameters']['oneTimePasswords'] + for currentOtp in db.Query(OneTimePassword).ancestor(user): + if currentOtp.reference in validOtpReferences: + pass + else: + currentOtp.delete() + + user.put() + + result['result'] = user.lock + + elif message == 'getOneTimePasswordsDetails': + pass + + elif message == 'getLoginHistory': + result["result"] = [] + + elif message == 'upgradeUserCredentials': + user = db.Query(User).filter('username =', session.C).get() + + user.updateCredentials(parameters['parameters']['credentials']) + user.update(parameters['parameters']['user']) + + for oneTimePasswordReference in parameters['parameters']['oneTimePasswords']: + oneTimePassword = db.Query(OneTimePassword).ancestor(user).filter("reference =", oneTimePasswordReference).get() + + if oneTimePassword != None: + oneTimePassword.data = parameters['parameters']['oneTimePasswords'][oneTimePasswordReference] + oneTimePassword.put() + + user.put() + + result['lock'] = user.lock + result['result'] = 'done' + + """ + $user = new user(); + $user->Get($_SESSION["userId"]); + + $otp = new onetimepassword(); + + updateUserCredentials($parameters["parameters"]["credentials"], $user); + updateUserData($parameters["parameters"]["user"], $user); + + $otpList = $parameters["parameters"]["oneTimePasswords"]; + foreach($otpList as $otpReference=>$otpData) { + $otpList = $otp->GetList(array(array("reference", "=", $otpReference))); + $currentOtp = $otpList[0]; + $currentOtp->data = $otpData; + $currentOtp->Save(); + } + + $user->Save(); + + $result["lock"] = $user->lock; + $result["result"] = "done"; + """ + + #============================================================= + + """ + java.util.Map result; + + try { + java.util.Map credentials; + + if (someParameters.get("credentials") != null) { + credentials = (java.util.Map)someParameters.get("credentials"); + } else { + credentials = someParameters; + } + + aUser.setUsername((java.lang.String)credentials.get("C")); + aUser.setSrpS((java.lang.String)credentials.get("s")); + aUser.setSrpV((java.lang.String)credentials.get("v")); + aUser.setVersion((java.lang.String)credentials.get("version")); + + if (someParameters.get("user") != null) { + com.clipperz.dataModel.EncoderHelper.updateWithMap(aUser, (java.util.Map)someParameters.get("user")); + } + + if (someParameters.get("oneTimePasswords") != null) { + java.util.Map updatedOneTimePasswords; + java.util.List usersOneTimePasswords; + int i,c; + + updatedOneTimePasswords = (java.util.Map)someParameters.get("oneTimePasswords"); + usersOneTimePasswords = com.clipperz.dataModel.OneTimePassword.oneTimePasswordsForUser(this.user()); + c = usersOneTimePasswords.size(); + for (i=0; i<c; i++) { + com.clipperz.dataModel.OneTimePassword currentOneTimePassword; + + currentOneTimePassword = (com.clipperz.dataModel.OneTimePassword)usersOneTimePasswords.get(i); + + if (updatedOneTimePasswords.get(currentOneTimePassword.getReference()) != null) { + currentOneTimePassword.setData((java.lang.String)updatedOneTimePasswords.get(currentOneTimePassword.getReference())); + } + } + } + + result = new java.util.Hashtable(); + this.dataContext().commitChanges(); + result.put("lock", this.user().getNewLock()); + result.put("result", "done"); + } catch(java.lang.Exception exception) { + this.dataContext().rollbackChanges(); + logger.error(exception); + throw exception; + } + + return result; + """ + + elif message == 'echo': + result['result'] = parameters; + + else: + result['error'] = "Wrong shared secret!" + + #---------------------------------------------------------------------- + + elif method == 'logout': + result['method'] = 'logout' + + #---------------------------------------------------------------------- + + else: + result['method'] = 'PRRRRRR' + + #---------------------------------------------------------------------- + + self.saveSession(session) + self.response.out.write(simplejson.dumps(result)) + + #========================================================================== + + def addNewRecords (self, aSession, aUser, someParameters): + result = {} + + #user = db.Query(User).filter('username =', aSession.C).get() + aUser.update(someParameters['parameters']['user']) + + for recordParameter in someParameters['parameters']['records']: + record = Record(parent=aUser) + record.put() + recordVersion = RecordVersion(parent=record) + recordVersion.put() + + recordVersion.update(recordParameter) + + record.put() + recordVersion.put() + + aUser.put(); + + result['lock'] = aUser.lock + result['result'] = 'done' + + return result + + #========================================================================== + + def getSession(self): + #logging.info(">>> getSession (%d) => %s" % (db.Query(Session).count(), str(map(lambda v: v.sessionId, db.Query(Session).fetch(100)))) ) + result = None + try: + sessionId = self.request.cookies['sessionId'] + except: + sessionId = None + + #logging.info("wannabe sessionId: " + str(sessionId)) + + if sessionId != None: + #query = db.Query(Session) + #query.filter('sessionId =', sessionId) + + #result = query.get() + + #result = db.Query(Session).filter('sessionId =', str(sessionId)).filter('access_date >', (datetime.datetime.utcnow() - sessionTimeout)).get() + result = db.Query(Session).filter('sessionId =', str(sessionId)).get() + #logging.info("searching session on datastore. Found: " + str(result)) + + if result == None: + sessionId = str(uuid.uuid4()) + #logging.info("creating a new session with sessionId=" + str(sessionId)) + result = Session(sessionId=sessionId) + + result.access_date = datetime.datetime.utcnow() + result.put() + + #logging.info("<<< getSession (%d)" % db.Query(Session).count()) + + return result + + #========================================================================== + + def saveSession(self, aSession): + #logging.info(">>> saveSession (%d)" % db.Query(Session).count()) + #self.response.set_cookie('sessionId', aSession.sessionId, max_age=360, path='/', domain='example.org', secure=True) + aSession.put() + self.response.headers.add_header('Set-Cookie', 'sessionId=' + str(aSession.sessionId), path='/') + self.cleanOldSessions() + #logging.info("<<< saveSession (%d)" % db.Query(Session).count()) + + #========================================================================== + + def cleanOldSessions(self): + query = db.Query(Session).filter('accessDate <', (datetime.datetime.utcnow() - sessionTimeout)) + + expiredSessions = query.count(); + if expiredSessions != 0: + #logging.info("deleting %d sessions" % expiredSessions) + pass + + """ + try: + db.delete(query) + except Exception, exception: + logging.error("some issues raised while deleting the expired sessions") + logging.error("exception type: " + str(type(exception))) + logging.error("exception: " + str(exception)) + """ + pass + +#============================================================================== + +def main(): + application = webapp.WSGIApplication([('/xhr', XHR), ('/dump', XHR), ('/.*', MainPage)], debug=True) + wsgiref.handlers.CGIHandler().run(application) + +if __name__ == "__main__": + main() + |