├── README ├── demos ├── broadcast │ ├── bcast │ │ ├── __init__.py │ │ ├── manage.py │ │ ├── server │ │ │ ├── __init__.py │ │ │ ├── admin.py │ │ │ ├── jsonresponse.py │ │ │ ├── models.py │ │ │ ├── tests.py │ │ │ └── views.py │ │ ├── settings.py │ │ ├── site_media │ │ │ └── js │ │ │ │ ├── broadcast.js │ │ │ │ ├── create-account-ready.js │ │ │ │ ├── create-account.js │ │ │ │ ├── index-ready.js │ │ │ │ ├── jquery.js │ │ │ │ └── notifier.js │ │ ├── tmpl │ │ │ ├── base.html │ │ │ ├── create_account.html │ │ │ └── index.html │ │ └── urls.py │ ├── broadcast.html │ └── broadcast.js ├── dcserver │ ├── __init__.py │ ├── manage.py │ ├── msgdrp │ │ ├── __init__.py │ │ ├── models.py │ │ ├── tests.py │ │ ├── tmpl │ │ │ ├── addressbook.html │ │ │ ├── base.html │ │ │ ├── begin_create_addressbook_entry.html │ │ │ ├── compose.html │ │ │ ├── display_addressbook_entry.html │ │ │ ├── entry_search_list.html │ │ │ ├── index.html │ │ │ └── messages.html │ │ ├── urls.py │ │ └── views.py │ ├── settings.py │ ├── site_media │ │ ├── css │ │ │ └── style.css │ │ └── js │ │ │ ├── jquery.js │ │ │ └── msgdrp.js │ └── urls.py ├── demo.html ├── demo.js ├── get-pub-key.html ├── get-pub-key.js └── sign.html ├── extension ├── build.sh ├── built │ └── domcrypt.xpi └── domcrypt │ ├── chrome.manifest │ ├── components │ ├── domcrypt.js │ └── domcrypt.manifest │ ├── content │ ├── DOMCryptMethods.jsm │ ├── addressbookManager.js │ ├── browser.xul │ └── domcrypt_worker.js │ ├── install.rdf │ └── locale │ └── en-US │ └── domcrypt.properties ├── index.html └── tests ├── test-domcrypt.html └── test-domcrypt.js /README: -------------------------------------------------------------------------------- 1 | DOMCrypt 2 | 3 | DOMCrypt is a Firefox extension that adds 'window.mozCipher' javascript object to any webpage. 4 | 5 | REQUIRES Firefox 9 or nightly: http://www.mozilla.com/en-US/firefox/beta/ or http://nightly.mozilla.org 6 | 7 | All of the underlying encryption is handled by NSS, so it is fast native code 8 | 9 | See demos/ and tests/ for actual usage. 10 | 11 | * latest spec: https://wiki.mozilla.org/Privacy/Features/DOMCryptAPISpec/Latest 12 | * bugzilla: https://bugzilla.mozilla.org/show_bug.cgi?id=649154 13 | * webkit: https://bugs.webkit.org/show_bug.cgi?id=62010 14 | * W3C Web Crypto WG: www.w3.org/2011/11/webcryptography-charter.html 15 | -------------------------------------------------------------------------------- /demos/broadcast/bcast/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daviddahl/domcrypt/e8238956626bd235657eeb00f2bc1bfb92c916e0/demos/broadcast/bcast/__init__.py -------------------------------------------------------------------------------- /demos/broadcast/bcast/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | from django.core.management import execute_manager 3 | try: 4 | import settings # Assumed to be in the same directory. 5 | except ImportError: 6 | import sys 7 | sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__) 8 | sys.exit(1) 9 | 10 | if __name__ == "__main__": 11 | execute_manager(settings) 12 | -------------------------------------------------------------------------------- /demos/broadcast/bcast/server/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daviddahl/domcrypt/e8238956626bd235657eeb00f2bc1bfb92c916e0/demos/broadcast/bcast/server/__init__.py -------------------------------------------------------------------------------- /demos/broadcast/bcast/server/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from bcast.server.models import TimelineMessage, Account, Hierarchy 3 | 4 | admin.site.register(TimelineMessage) 5 | admin.site.register(Account) 6 | admin.site.register(Hierarchy) 7 | -------------------------------------------------------------------------------- /demos/broadcast/bcast/server/jsonresponse.py: -------------------------------------------------------------------------------- 1 | # stolen from http://djangosnippets.org/snippets/2411/ 2 | # Author: guetux 3 | from django.db.models import Model 4 | from django.db.models.query import QuerySet 5 | from django.http import HttpResponse 6 | from django.utils.encoding import force_unicode 7 | from django.utils.simplejson import dumps, JSONEncoder 8 | 9 | def jsonify_model(model): 10 | model_dict = model.__dict__ 11 | for key, value in model_dict.items(): 12 | if key.startswith('_'): 13 | del model_dict[key] 14 | else: 15 | model_dict[key] = force_unicode(value) 16 | return model_dict 17 | 18 | class API_JSONEncoder(JSONEncoder): 19 | def default(self, obj): 20 | if isinstance(obj, QuerySet): 21 | return [jsonify_model(o) for o in obj] 22 | if isinstance(obj, Model): 23 | return jsonify_model(obj) 24 | return JSONEncoder.default(self,obj) 25 | 26 | class JSONResponse(HttpResponse): 27 | status_code = 200 28 | 29 | def __init__(self, data): 30 | json_response = dumps(data, ensure_ascii=False, indent=2, cls=API_JSONEncoder) 31 | HttpResponse.__init__(self, json_response, mimetype="text/javascript") 32 | -------------------------------------------------------------------------------- /demos/broadcast/bcast/server/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | from datetime import datetime 4 | import hashlib 5 | import random 6 | 7 | class TimelineMessage(models.Model): 8 | """ 9 | timeline messages 10 | 11 | """ 12 | recipient = models.ForeignKey('server.Account', related_name="recipient") 13 | author = models.ForeignKey('server.Account', related_name="author") 14 | author_display_name = models.CharField(max_length=255, null=True, blank=True) 15 | date_time = models.DateTimeField(auto_now_add=True) 16 | content = models.TextField(null=False, blank=False) 17 | wrapped_key = models.TextField(null=False, blank=False) 18 | iv = models.CharField(max_length=255) 19 | fetched = models.BooleanField(default=False) 20 | parent_message_id = models.IntegerField(null=True, blank=True) 21 | 22 | def __unicode__(self): 23 | return u"To: %s" % (self.recipient,) 24 | 25 | def save(self, *args, **kwargs): 26 | resave = False 27 | self.author_display_name = self.author.display_name 28 | if self.parent_message_id: 29 | super(TimelineMessage, self).save(*args, **kwargs) 30 | return 31 | if not self.id: 32 | resave = True 33 | super(TimelineMessage, self).save(*args, **kwargs) 34 | if resave: 35 | self.parent_message_id = self.id 36 | super(TimelineMessage, self).save(*args, **kwargs) 37 | 38 | 39 | class Account(models.Model): 40 | identifier = models.CharField(unique=True, max_length=255) 41 | login_token = models.CharField(max_length=255) 42 | bio = models.TextField(null=True) 43 | url = models.CharField(max_length=255) 44 | display_name = models.CharField(max_length=255) 45 | ctime = models.DateTimeField(auto_now_add=True) 46 | pub_key = models.TextField(null=True) 47 | 48 | def __unicode__(self): 49 | return u"%s" % (self.display_name,) 50 | 51 | def save(self, *args, **kwargs): 52 | if not self.id: 53 | # create identifier 54 | token = "%s-%s" % (self.display_name, str(datetime.now())) 55 | self.identifier = hashlib.sha256(token).hexdigest() 56 | 57 | super(Account, self).save(*args, **kwargs) 58 | 59 | class Hierarchy(models.Model): 60 | leader = models.ForeignKey('server.Account', related_name="leader") 61 | follower = models.ForeignKey('server.Account', related_name="follower") 62 | ctime = models.DateTimeField(auto_now_add=True) 63 | approved = models.BooleanField(default=True) 64 | atime = models.DateTimeField(auto_now_add=True) 65 | blocked = models.BooleanField(default=False) 66 | btime = models.DateTimeField(auto_now_add=False, null=True, blank=True) 67 | # TODO: need to add an approved boolean and date as well as a 68 | # blocked boolean and date 69 | 70 | def __unicode__(self): 71 | return u"%s is following %s" % (self.follower, self.leader,) 72 | -------------------------------------------------------------------------------- /demos/broadcast/bcast/server/tests.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file demonstrates two different styles of tests (one doctest and one 3 | unittest). These will both pass when you run "manage.py test". 4 | 5 | Replace these with more appropriate tests for your application. 6 | """ 7 | 8 | from django.test import TestCase 9 | 10 | class SimpleTest(TestCase): 11 | def test_basic_addition(self): 12 | """ 13 | Tests that 1 + 1 always equals 2. 14 | """ 15 | self.failUnlessEqual(1 + 1, 2) 16 | 17 | __test__ = {"doctest": """ 18 | Another way to test that 1 + 1 is equal to 2. 19 | 20 | >>> 1 + 1 == 2 21 | True 22 | """} 23 | 24 | -------------------------------------------------------------------------------- /demos/broadcast/bcast/server/views.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | import sys, traceback 3 | from pprint import pprint 4 | 5 | from django.http import Http404, HttpResponse, HttpResponseRedirect 6 | from django.template import Context, loader 7 | from django.shortcuts import render_to_response, get_object_or_404 8 | from django import forms 9 | from django.utils import simplejson 10 | from django.utils.translation import ugettext_lazy as _ 11 | from django.contrib.syndication import feeds 12 | from django.http import Http404 13 | from django.views.decorators.csrf import csrf_protect 14 | from django.template import RequestContext 15 | from django.db import connection, transaction 16 | 17 | from django.core.serializers import serialize 18 | from django.db.models.query import QuerySet 19 | 20 | from bcast.server.models import Account, TimelineMessage, Hierarchy 21 | from bcast.server.jsonresponse import JSONResponse 22 | 23 | class JsonResponse(HttpResponse): 24 | content = None 25 | 26 | def __init__(self, object): 27 | if isinstance(object, QuerySet): 28 | content = serialize('json', object) 29 | else: 30 | content = simplejson.dumps(object) 31 | self.content = simplejson.loads(content) 32 | super(JsonResponse, self).__init__(content, mimetype='application/json') 33 | 34 | def auth(identifier, login_token): 35 | """ 36 | make sure the user is authorized to connect 37 | """ 38 | # check for user credentials in the Account model 39 | # if no cigar, redirect to an error page 40 | try: 41 | seed = "%s-%s" % (identifier, login_token,) 42 | hashed = hashlib.sha256(seed).hexdigest() 43 | print hashed 44 | acct = Account.objects.get(identifier=identifier) 45 | seed = "%s-%s" % (acct.identifier, acct.login_token,) 46 | authkey = hashlib.sha256(seed).hexdigest() 47 | print authkey 48 | return True 49 | if seed == authkey: 50 | return True 51 | else: 52 | return False 53 | except Exception, e: 54 | print e 55 | return False 56 | 57 | def index(request): 58 | """ 59 | an index page 60 | """ 61 | return render_to_response("index.html", {}, context_instance=RequestContext(request)) 62 | 63 | def login(request): 64 | """ 65 | a login page 66 | """ 67 | return render_to_response("login.html", {}, context_instance=RequestContext(request)) 68 | 69 | def create_account(request): 70 | """ 71 | Create account screen 72 | """ 73 | return render_to_response("create_account.html", {}, context_instance=RequestContext(request)) 74 | 75 | 76 | def destroy_account(request): 77 | """ 78 | Destroy account screen 79 | """ 80 | return render_to_response("destroy_account.html", {}) 81 | 82 | def x_create_acct(request): 83 | """ 84 | Try to tell the server to create an account 85 | """ 86 | try: 87 | # TODO: regex check for name formatting - cannot deviate 88 | # from base64urlencode 89 | 90 | # create login_token 91 | seed = request.POST["password"] 92 | login_token = hashlib.sha256(seed).hexdigest() 93 | 94 | acct = Account(display_name=request.GET["n"], 95 | login_token=login_token, 96 | pub_key=request.POST["pub_key"]) 97 | acct.save() 98 | return JsonResponse({"status": "success", 99 | "login_token": acct.login_token, 100 | "identifier": acct.identifier}) 101 | except Exception, e: 102 | print e 103 | return JsonResponse({"status": "failure", 104 | "msg": e}) 105 | 106 | def x_destroy_acct(request): 107 | """ 108 | destroy an account 109 | """ 110 | 111 | def x_post_msg(request): 112 | """ 113 | XHR method for posting updates 114 | """ 115 | # validate user credentials 116 | if not auth(request.POST["a1"], request.POST["a2"]): 117 | return JsonResponse({"status": "failure", "msg": "NOT_AUTHORIZED"}) 118 | # validate input 119 | # worry about this later:) 120 | try: 121 | # insert author's timeline message 122 | message_bundle = simplejson.loads(request.POST["bundle"]) 123 | print "ORIG MSG BUNDLE" 124 | pprint(message_bundle) 125 | print message_bundle['identifier'] 126 | author = Account.objects.get(identifier__exact=message_bundle['identifier']) 127 | pprint(author) 128 | timeline_msg = TimelineMessage(recipient=author, 129 | author=author, 130 | content=message_bundle['cipherMsg']['cipherText'], 131 | wrapped_key=message_bundle['cipherMsg']['wrappedKey'], 132 | iv=message_bundle['cipherMsg']['iv']) 133 | timeline_msg.save() 134 | print "timeline_msg saved..." 135 | # insert all follower's timeline messages parented by the author's message 136 | # pprint(message_bundle['messages']) 137 | # messages = message_bundle['messages'] 138 | messages = simplejson.loads(request.POST["messages"]); 139 | pprint(messages) 140 | for msg in messages: 141 | pprint(msg) 142 | recipient = Account.objects.get(display_name__exact=msg['follower']) 143 | pprint(recipient) 144 | tmsg = TimelineMessage(recipient=recipient, 145 | author=author, 146 | content=msg['cipherText'], 147 | wrapped_key=msg['wrappedKey'], 148 | iv=msg['iv'], 149 | parent_message_id=timeline_msg.id) 150 | tmsg.save() 151 | # return the original parent message id, etc 152 | return JsonResponse({"status": "success", 153 | "msg":"MESSAGE_SENT", 154 | "msgId": timeline_msg.id} 155 | ) 156 | except Exception, e: 157 | print e 158 | return JsonResponse({"status": "failure", "msg": e}); 159 | 160 | def x_get_msgs(request): 161 | """ 162 | get messages 163 | """ 164 | try: 165 | id = request.GET["a1"] 166 | token = request.GET["a2"] 167 | lastid = None 168 | if request.GET.has_key("lastid"): 169 | try: 170 | # lastid = int(request.GET["lastid"]) 171 | lastid = None 172 | except Exception, e: 173 | lastid = None 174 | # TODO fix the auth function 175 | acct = Account.objects.get(identifier__exact=id, login_token__exact=token) 176 | pprint(acct) 177 | if lastid is None: 178 | msgs = TimelineMessage.objects.filter(recipient=acct)[:100] 179 | else: 180 | msgs = TimelineMessage.objects.filter(recipient=acct, id__gt=lastid)[:100] 181 | 182 | return JSONResponse({"status": "success", "msg": msgs}) 183 | except Exception, e: 184 | print e 185 | return JsonResponse({"status": "failure", "msg": "SERVER_ERROR"}) 186 | 187 | def x_remove_msg(request): 188 | """ 189 | Remove a message and it's child messages from the timeline 190 | """ 191 | 192 | def x_check_display_name(request): 193 | """ 194 | check if a display name is available 195 | """ 196 | try: 197 | acct = Account.objects.filter(display_name__exact=request.GET['n']) 198 | print acct.count() 199 | if acct.count() > 0: 200 | return JsonResponse({"status": "failure", "available": 0}) 201 | else: 202 | return JsonResponse({"status": "success", "available": 1}) 203 | except Exception, e: 204 | print e 205 | return JsonResponse({"status": "failure", "available": 0, "msg": "SEVER_ERROR"}) 206 | 207 | def x_search_accts(request): 208 | """ 209 | search for friends 210 | """ 211 | try: 212 | if request.GET["n"]: 213 | # TODO: better validation 214 | print request.GET["n"] 215 | accts = Account.objects.filter(display_name__icontains=request.GET["n"]) 216 | if accts.count() > 0: 217 | _accts = [] 218 | for acct in accts: 219 | _accts.append({"id": acct.identifier, 220 | "display_name": acct.display_name}) 221 | pprint(_accts) 222 | return JsonResponse({"status": "success", "msg": _accts}) 223 | else: 224 | return JsonResponse({"status": "failure", "msg": "NONE_FOUND"}) 225 | except Exception, e: 226 | print e 227 | return JsonResponse({"status": "failure", "msg": "SERVER_ERROR"}) 228 | 229 | def x_follow(request): 230 | """ 231 | send a follow request 232 | """ 233 | try: 234 | # TODO: do not allow following yourself 235 | if request.GET["follower"] and request.GET["leader"]: 236 | print request.GET["follower"] 237 | print request.GET["leader"] 238 | # get both users 239 | follower = Account.objects.get(identifier=request.GET["follower"]) 240 | leader = Account.objects.get(identifier=request.GET["leader"]) 241 | print follower 242 | print leader 243 | f = Hierarchy.objects.filter(leader=leader, follower=follower); 244 | if f.count() == 1: 245 | return JsonResponse({"status": "failure", "msg": "FOLLOWING", "leader": leader.display_name}); 246 | if follower == leader: 247 | return JsonResponse({"status": "failure", "msg": "CANNOT_FOLLOW_YOURSELF", "leader": leader.display_name}); 248 | f = Hierarchy(leader=leader, follower=follower) 249 | f.save() 250 | return JsonResponse({"status": "success", "msg": "FOLLOW_COMPLETED"}) 251 | except Exception, e: 252 | print e 253 | return JsonResponse({"status": "failure", "msg": "FOLLOW_FAILED"}) 254 | 255 | 256 | def x_get_following(request): 257 | """ 258 | get the display_names of those you are following 259 | """ 260 | try: 261 | if auth(request.GET["a1"], request.GET["a2"]): 262 | user = Account.objects.get(identifier=request.GET["a1"], login_token=request.GET["a2"]) 263 | cursor = connection.cursor() 264 | # TODO: write a join here 265 | cursor.execute("SELECT follower_id, leader_id FROM server_hierarchy WHERE follower_id=%s", [user.id]) 266 | following = cursor.fetchall() 267 | _following = [] 268 | for f in following: 269 | a = Account.objects.get(id=f[1]) 270 | _following.append({"handle": a.display_name, "pubKey": a.pub_key}) 271 | return JsonResponse({"status": "success", "msg": "FOLLOWING", "following": _following}) 272 | else: 273 | return JsonResponse({"status": "failure", "msg": "SERVER_ERROR", "following":[]}) 274 | except Exception, e: 275 | print e 276 | return JsonResponse({"status": "failure", "msg": "SERVER_ERROR", "followers":[]}) 277 | def x_get_followers(request): 278 | """ 279 | get followers 280 | """ 281 | try: 282 | if auth(request.GET["a1"], request.GET["a2"]): 283 | user = Account.objects.get(identifier=request.GET["a1"], login_token=request.GET["a2"]) 284 | cursor = connection.cursor() 285 | 286 | cursor.execute("SELECT follower_id, leader_id FROM server_hierarchy WHERE leader_id=%s", [user.id]) 287 | followers = cursor.fetchall() 288 | _followers = [] 289 | for f in followers: 290 | a = Account.objects.get(id=f[0]) 291 | _followers.append({"handle": a.display_name, "pubKey": a.pub_key}) 292 | return JsonResponse({"status": "success", "msg": "FOLLOWERS", "followers": _followers}) 293 | else: 294 | return JsonResponse({"status": "failure", "msg": "SERVER_ERROR", "followers":[]}) 295 | except Exception, e: 296 | print e 297 | return JsonResponse({"status": "failure", "msg": "SERVER_ERROR", "followers":[]}) 298 | 299 | def x_block(request): 300 | """ 301 | send a block request 302 | """ 303 | 304 | def x_approve_follow(request): 305 | """ 306 | approve a follow request 307 | """ 308 | 309 | def x_fetch_console(request): 310 | """ 311 | fetch console messages 312 | """ 313 | 314 | def x_login(request): 315 | """ 316 | login to existing account 317 | """ 318 | if request.GET.has_key("user") and request.POST.has_key("password"): 319 | try: 320 | seed = request.POST["password"] 321 | login_token = hashlib.sha256(seed).hexdigest() 322 | a = Account.objects.get(login_token=login_token, 323 | display_name=request.GET["user"]) 324 | return JsonResponse({"status": "success", 325 | "login_token": a.login_token, 326 | "identifier": a.identifier, 327 | "display_name": a.display_name}) 328 | except Exception, e: 329 | print e 330 | return JsonResponse({"status": "failure", "msg": "Server Error"}) 331 | else: 332 | return JsonResponse({"status": "failure", "msg": "Server Error"}) 333 | 334 | 335 | -------------------------------------------------------------------------------- /demos/broadcast/bcast/settings.py: -------------------------------------------------------------------------------- 1 | # Django settings for bcast project. 2 | import os 3 | 4 | DEBUG = True 5 | TEMPLATE_DEBUG = DEBUG 6 | 7 | ADMINS = ( 8 | ('Dr. Zhivago', 'drzhivago@domcrypt.org'), 9 | ) 10 | 11 | MANAGERS = ADMINS 12 | 13 | DATABASES = { 14 | 'default': { 15 | 'ENGINE': 'django.db.backends.mysql', 16 | 'NAME': os.environ["BCAST_DB_PATH"], 17 | 'USER': 'root', 18 | 'PASSWORD': os.environ["BCAST_DB_PASS"], 19 | 'HOST': '', 20 | 'PORT': '', 21 | } 22 | } 23 | 24 | # Local time zone for this installation. Choices can be found here: 25 | # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name 26 | # although not all choices may be available on all operating systems. 27 | # On Unix systems, a value of None will cause Django to use the same 28 | # timezone as the operating system. 29 | # If running in a Windows environment this must be set to the same as your 30 | # system time zone. 31 | TIME_ZONE = 'America/Chicago' 32 | 33 | # Language code for this installation. All choices can be found here: 34 | # http://www.i18nguy.com/unicode/language-identifiers.html 35 | LANGUAGE_CODE = 'en-us' 36 | 37 | SITE_ID = 1 38 | 39 | # If you set this to False, Django will make some optimizations so as not 40 | # to load the internationalization machinery. 41 | USE_I18N = True 42 | 43 | # If you set this to False, Django will not format dates, numbers and 44 | # calendars according to the current locale 45 | USE_L10N = True 46 | 47 | # Absolute filesystem path to the directory that will hold user-uploaded files. 48 | # Example: "/home/media/media.lawrence.com/" 49 | MEDIA_ROOT = '' 50 | 51 | # URL that handles the media served from MEDIA_ROOT. Make sure to use a 52 | # trailing slash if there is a path component (optional in other cases). 53 | # Examples: "http://media.lawrence.com", "http://example.com/media/" 54 | MEDIA_URL = '' 55 | 56 | # URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a 57 | # trailing slash. 58 | # Examples: "http://foo.com/media/", "/media/". 59 | ADMIN_MEDIA_PREFIX = '/media/' 60 | 61 | # Make this unique, and don't share it with anybody. 62 | SECRET_KEY = 'jz+*g1a&1+ic@zj@m&tk$exj%k_x_g037&mz*cab^5(ic5%$ap' 63 | 64 | # List of callables that know how to import templates from various sources. 65 | TEMPLATE_LOADERS = ( 66 | 'django.template.loaders.filesystem.Loader', 67 | 'django.template.loaders.app_directories.Loader', 68 | # 'django.template.loaders.eggs.Loader', 69 | ) 70 | 71 | MIDDLEWARE_CLASSES = ( 72 | 'django.middleware.common.CommonMiddleware', 73 | 'django.contrib.sessions.middleware.SessionMiddleware', 74 | 'django.middleware.csrf.CsrfViewMiddleware', 75 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 76 | 'django.contrib.messages.middleware.MessageMiddleware', 77 | ) 78 | 79 | ROOT_URLCONF = 'bcast.urls' 80 | 81 | TEMPLATE_DIRS = ( 82 | # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". 83 | # Always use forward slashes, even on Windows. 84 | # Don't forget to use absolute paths, not relative paths. 85 | os.environ["BCAST_TMPL_PATH"] 86 | ) 87 | 88 | INSTALLED_APPS = ( 89 | 'django.contrib.auth', 90 | 'django.contrib.contenttypes', 91 | 'django.contrib.sessions', 92 | 'django.contrib.sites', 93 | 'django.contrib.messages', 94 | # Uncomment the next line to enable the admin: 95 | 'django.contrib.admin', 96 | # Uncomment the next line to enable admin documentation: 97 | 'django.contrib.admindocs', 98 | 'bcast.server', 99 | ) 100 | -------------------------------------------------------------------------------- /demos/broadcast/bcast/site_media/js/broadcast.js: -------------------------------------------------------------------------------- 1 | var DEBUG = true; 2 | var jsonMessages = []; 3 | var timelineData = []; 4 | var followers = []; 5 | var timelineIndex = {}; 6 | var timeline; 7 | 8 | const LOGIN_URL = "/bcast/login/"; 9 | const CREATE_ACCOUNT_URL = "/bcast/create/account/"; 10 | const BCAST_URL = "/bcast/"; 11 | 12 | function Timeline () { } 13 | 14 | Timeline.prototype = { 15 | 16 | messages: [], 17 | 18 | index: {}, 19 | 20 | get lastFetchedID() { 21 | return 0 || localStorage.getItem("lastFetchedID"); 22 | }, 23 | 24 | get lastFetchedTime(){ 25 | return 0 || localStorage.getItem("lastFetchedTime"); 26 | }, 27 | 28 | fetch: function TL_fetch() 29 | { 30 | getMessages(this.lastFetchedID); 31 | } 32 | }; 33 | 34 | $(document).ready(function() { 35 | // check for DOMCrypt 36 | checkDOMCrypt(); 37 | // key bindings 38 | $("#search").keyup(function(e) { 39 | if (e.keyCode == '13') { 40 | var str = $("#search")[0].value; 41 | if (str) { 42 | new SearchAccounts(str); 43 | } 44 | } 45 | if (e.keyCode == 27) { 46 | $("#search")[0].value = "find people..."; 47 | $("#results").children().remove(); 48 | $("#search").blur(); 49 | } 50 | }); 51 | 52 | // focus events 53 | $("#search").focus(function (){ 54 | if (this.value == "find people...") { 55 | this.value = ""; 56 | } 57 | }); 58 | 59 | $("#search").blur(function (){ 60 | if (!(this.value == "find people...")) { 61 | this.value = "find people..."; 62 | } 63 | }); 64 | 65 | try { 66 | // TODO: account login/create account flow needed 67 | // // need to check for credentials, decrypt them and configure the application 68 | var _credentials = localStorage.getItem("credentials"); 69 | 70 | if (!_credentials && document.location.pathname != CREATE_ACCOUNT_URL && 71 | document.location.pathname != LOGIN_URL) { 72 | notify("account error", "It looks like you do not have an account yet, redirecting momentarily...", 5000, true); 73 | window.setTimeout(function (){ 74 | document.location = CREATE_ACCOUNT_URL; 75 | }, 5000); 76 | return; 77 | } 78 | 79 | // only try to load the credentials if we are at the main page! 80 | if (document.location.pathname != "/bcast/") { 81 | return; 82 | } 83 | 84 | var credentials = JSON.parse(_credentials); 85 | // decrypt credentials 86 | mozCipher.pk.decrypt(credentials, function (plaintext){ 87 | var credentialsPlainObj = JSON.parse(plaintext); 88 | // TODO: need to get follower_ids from the server 89 | 90 | messageComposer = new MessageComposer(credentialsPlainObj, []); 91 | timeline = new Timeline(); 92 | timeline.fetch(); 93 | 94 | // fetch updates every 5 minutes... 95 | window.setInterval(function (){ timeline.fetch(); }, 300000); 96 | }); 97 | } 98 | catch (ex) { 99 | console.log(ex); 100 | console.log(ex.stack); 101 | } 102 | }); 103 | 104 | function checkDOMCrypt() 105 | { 106 | return; 107 | if (window.mozCipher) { 108 | // check for a pub key: 109 | mozCipher.pk.getPublicKey(function (aPubKey){ 110 | if (!aPubKey) { 111 | notify("additional configuration is required", "Please create a passphrase that will secure your data"); 112 | mozCipher.pk.generateKeypair(function (aPubKey){ 113 | if (aPubKey) { 114 | alert("bcast configuration complete, next: create a server account..."); 115 | document.location = "/bcast/create/account/?t=" + Date.now(); 116 | } 117 | }); 118 | } 119 | }); 120 | } 121 | else { 122 | alert("DOMCrypt API (window.mozCipher) not detected. DOMCrypt is required to use bcast. Please visit http://domcrypt.org/ for installation instructions."); 123 | } 124 | } 125 | 126 | function MessageDisplay(aMessageData) 127 | { 128 | aMessageData._id = aMessageData.id; 129 | // TODO: use the same keys for both db and locally created messages 130 | var tmpl; 131 | if (aMessageData.content) { 132 | // database template 133 | tmpl = '
' 134 | + '
{date_time} ' 135 | + '' 136 | + '
' 137 | + '
{author_display_name}
' 138 | + '
{content}
' 139 | + '
'; 140 | } 141 | else { 142 | tmpl = '
' 143 | + '
{date} ' 144 | + '' 145 | + '
' 146 | + '
{author}
' 147 | + '
{cipherText}
' 148 | + '
'; 149 | } 150 | 151 | var node = $(tmpl.printf(aMessageData)); 152 | $("#msg-input")[0].value = ""; 153 | $("#messages").prepend(node); 154 | } 155 | 156 | function DisplayPlainText(aNode) 157 | { 158 | var id = aNode.parentNode.parentNode.getAttribute("id"); 159 | new MessageReader(id); 160 | } 161 | 162 | function log(aMsg) 163 | { 164 | if (DEBUG) { 165 | console.log(aMsg); 166 | } 167 | } 168 | 169 | function MessageComposer(aCredentials, aFollowers) 170 | { 171 | // normally in the MessageComposer, you would: 172 | // 1. encrypt text 173 | // 2. get a list of all followers' hashIds and pubKeys 174 | // 3. wrapKeys for all followers 175 | // 4. push the message bundle to the server 176 | // In this demo we will just add the message to the timeline of the current user 177 | this.author = aCredentials.displayName; 178 | this.token = aCredentials.token; 179 | this.ID = aCredentials.ID; 180 | $("#display-name")[0].innerHTML = this.author; 181 | this.followers = aFollowers; 182 | var self = this; 183 | mozCipher.pk.getPublicKey(function (aPubKey){ 184 | self.authorPubKey = aPubKey; 185 | self.followers.push({handle: self.author, pubKey: self.authorPubKey }); 186 | }); 187 | /////////////////////////////////////////////////////////////////////////////// 188 | // get followers... 189 | var url = "/bcast/_xhr/get/followers/?a1={a1}&a2={a2}&t={t}". 190 | printf({a1: self.ID, a2: self.token, t: Date.now()}); 191 | var config = { 192 | url: url, 193 | dataType: "json", 194 | success: function success(data) 195 | { 196 | var len = data.followers.length; 197 | if (len > 0) { 198 | for (var i = 0; i < len; i++) { 199 | console.log("messageComposer: follower fetched: " + data.followers[i]); 200 | console.log(data.followers); 201 | window.followers.push(data.followers[i]); 202 | } 203 | } 204 | else { 205 | // just write to the console... 206 | log("messageComposer: no followers found"); 207 | } 208 | } 209 | }; 210 | $.ajax(config); 211 | // followers got! 212 | //////////////////////////////////////////////////////////////////////////////// 213 | } 214 | 215 | MessageComposer.prototype = { 216 | author: null, 217 | 218 | token: null, 219 | 220 | ID: null, 221 | 222 | authorPubKey: null, 223 | 224 | followers: [], 225 | 226 | // get followers() { return window.followers; }, 227 | 228 | bundle: function mc_bundle(aCipherMessage) 229 | { 230 | try { 231 | 232 | console.log("bundle --->"); 233 | console.log(aCipherMessage.idx); 234 | var self = this; 235 | var messages = []; 236 | var idx; 237 | var bundle = { cipherMsg: aCipherMessage, 238 | identifier: self.ID, 239 | author: self.author, 240 | authorPubKey: self.authorPubKey 241 | }; 242 | // TODO: add authentication token and password to the bundle 243 | var len = window.followers.length; 244 | console.log(len); 245 | console.log("WINDOW FOLLOWERS:"); 246 | console.log(window.followers); 247 | // need to re-wrap the key for each follower 248 | var lastIdx = window.followers.length; 249 | var _followers = window.followers; 250 | for (var i = 1; i < _followers.length; i++) { 251 | console.log(_followers[i].handle); 252 | mozCipher.sym.wrapKey(aCipherMessage, _followers[i].pubKey, 253 | function wrapCallback(aCipherObj) { 254 | console.log(_followers[i].handle); 255 | aCipherObj.follower = _followers[i].handle; 256 | console.log("follower: " + aCipherObj.follower); 257 | messages.push(aCipherObj); 258 | if ((parseInt(i)) == parseInt(lastIdx)) { 259 | console.log("sending..."); 260 | window.setTimeout(function (){ 261 | self.send(bundle, messages); 262 | }, 2000); 263 | } 264 | }); 265 | } 266 | 267 | } catch (ex) { 268 | console.log(ex); 269 | console.log(ex.stack); 270 | } 271 | }, 272 | 273 | encrypt: function mc_encrypt() 274 | { 275 | var self = this; 276 | var followersLen = this.followers.length; 277 | mozCipher.sym.encrypt($("#msg-input")[0].value, function (aCipherMsg) { 278 | aCipherMsg.author = this.author; 279 | // TODO: send the bundle to the server... 280 | var date = new Date(); 281 | var message = {author: self.author, 282 | id: date.getTime(), 283 | date: date.toString(), 284 | cipherText: aCipherMsg.cipherText, 285 | wrappedKey: aCipherMsg.wrappedKey, 286 | iv: aCipherMsg.iv, 287 | pubKey: aCipherMsg.pubKey, 288 | idx: followersLen}; 289 | 290 | var bundle = self.bundle(message); 291 | }); 292 | }, 293 | 294 | send: function mc_send(bundle, messages) 295 | { 296 | var self = this; 297 | var bundleStr = JSON.stringify(bundle); 298 | var messagesStr = JSON.stringify(messages); 299 | // TODO: HTTP POST to server 300 | console.log("SEND--->"); 301 | var url = "/bcast/_xhr/post/msg/"; 302 | var csrf_token = $('#csrf_token >div >input').attr("value"); 303 | var config = { 304 | data: { 305 | a1: self.ID, 306 | a2: self.token, 307 | bundle: bundleStr, 308 | messages: messagesStr, 309 | csrfmiddlewaretoken: csrf_token 310 | }, 311 | url: url, 312 | type: "POST", 313 | dataType: "json", 314 | success: function success(data) 315 | { 316 | if (data.msgId) { 317 | notify("success", "message sent"); 318 | } 319 | else { 320 | console.log(data.msg); 321 | // TODO: keep a cache of the bundled message or ajax config in case 322 | // of a problem for re-sending 323 | notify("whoops", "message was not sent"); 324 | } 325 | } 326 | }; 327 | $.ajax(config); 328 | 329 | }, 330 | 331 | validate: function mc_validate() 332 | { 333 | var txt = $("#msg-input")[0].value; 334 | if (txt.length > 0 && txt.length < 4096) { 335 | this.encrypt(); 336 | } 337 | else { 338 | // XXX: notify user of error 339 | } 340 | } 341 | }; 342 | 343 | function MessageReader(aMessageID) 344 | { 345 | this.id = aMessageID; 346 | this.decrypt(); 347 | } 348 | 349 | MessageReader.prototype = { 350 | decrypt: function mr_decrypt() 351 | { 352 | var self = this; 353 | var msg = timeline.index[this.id]; 354 | var _msg; 355 | if (msg.content) { // this object came from the server, reconfigure it 356 | _msg = { 357 | cipherText: msg.content, 358 | iv: msg.iv, 359 | wrappedKey: msg.wrapped_key 360 | }; 361 | } 362 | else { 363 | _msg = msg; 364 | } 365 | mozCipher.sym.decrypt(_msg, function (plainText) { 366 | var id = "#" + self.id; 367 | $(id)[0].childNodes[2].innerHTML = 368 | '
{plainText}
'.printf({plainText: plainText}); 369 | // disable read button 370 | $("#read-one-" + self.id)[0].disabled = true; 371 | }); 372 | } 373 | }; 374 | 375 | // Account format and creation 376 | 377 | function Account(aAccountData) 378 | { 379 | if (!("display_name" in aAccountData)) { 380 | throw new Error("Display Name is required"); 381 | } 382 | var url, bio = null; 383 | if (aAccountData.url) { 384 | url = aAccountData.url; 385 | } 386 | if (aAccountData.bio) { 387 | bio = aAccountData.bio; 388 | } 389 | this.accountData = {identifier: null, 390 | login_token: null, 391 | bio: bio, 392 | url: url, 393 | display_name: aAccountData.display_name, 394 | pub_key: null 395 | }; 396 | this.configureAccount(); 397 | } 398 | 399 | Account.prototype = { 400 | accountData: null, 401 | 402 | accountStatus: function a_accountStatus() 403 | { 404 | // check if this is a valid account and user can login 405 | // HTTP post the bcastAcct data that we have 406 | // the server will tell us if the account displayname is available 407 | // if it is we can create the account 408 | }, 409 | 410 | configureAccount: function a_configureAccount() 411 | { 412 | var bcastAcct; 413 | var _bcast_acct = localStorage.getItem("BCAST_ACCT"); 414 | if (_bcast_acct) { 415 | bcastAcct = JSON.parse(_bcast_acct); 416 | if (bcastAcct.login_token && bcastAcct.identifier) { 417 | // looks like this account is already ready to go 418 | console.log("Account is ready"); 419 | return; 420 | } 421 | else if (bcastAcct.display_name) { 422 | // we need to see if the account can be created 423 | this.accountStatus(bcastAcct); 424 | } 425 | } 426 | var self = this; 427 | // TODO detect missing publickey, generate one 428 | mozCipher.pk.getPublicKey(function (aPubKey){ 429 | self.accountData.pub_key = aPubKey; 430 | mozCipher.hash.SHA256(aPubKey, function (aHash){ 431 | self.accountData.identifier = aHash; 432 | console.log("account configured"); 433 | }); 434 | }); 435 | }, 436 | 437 | createAccount: function a_createAccount() 438 | { 439 | 440 | }, 441 | 442 | destroyAccount: function a_destroyAccount() 443 | { 444 | 445 | } 446 | }; 447 | 448 | function SearchAccounts(aNameFragment) 449 | { 450 | // search for display names to follow people 451 | var url = "/bcast/_xhr/search/accounts/?n=" + aNameFragment + "&rndm=" + Date.now(); 452 | var config = { 453 | url: url, 454 | dataType: "json", 455 | success: function success(data) 456 | { 457 | var errTmpl = '

{err}

'; 458 | if (data.status == "success") { 459 | var tmpl = '

' 460 | + '{display_name} ' 461 | + 'follow... ' 462 | + '*block*' 463 | + '

'; 464 | $("#results").children().remove(); 465 | // display the names found in the results div 466 | for (var i = 0; i < data.msg.length; i++) { 467 | // TODO: do not allow following of yourself:) 468 | $("#results").append($(tmpl.printf(data.msg[i]))); 469 | } 470 | } 471 | else { 472 | $("#results").children().remove(); 473 | notify(null, "not found", null, true); 474 | } 475 | } 476 | }; 477 | $.ajax(config); 478 | } 479 | 480 | function follow(aNode) 481 | { 482 | // get the id via the node's parent.parent.id 483 | var id = aNode.parentNode.getAttribute("id"); 484 | 485 | // xhr that request up to the server 486 | var url = "/bcast/_xhr/follow/?leader=" + id + "&follower=" + messageComposer.ID; 487 | var config = { 488 | url: url, 489 | dataType: "json", 490 | success: function success(data) 491 | { 492 | var name = aNode.parentNode.childNodes[0].innerHTML; 493 | if (data.status == "success") { 494 | var msg = "follow request sent to '" + name + "'"; 495 | notify("success", msg); 496 | // TODO: add the user to a 'following' list 497 | } 498 | else if (data.msg == "FOLLOWING") { 499 | notify("already following", name); 500 | } 501 | else if (data.msg == "CANNOT_FOLLOW_YOURSELF") { 502 | notify("you cannot follow", "you"); 503 | } 504 | else { 505 | notify("server error", "'follow' request failed"); 506 | } 507 | } 508 | }; 509 | $.ajax(config); 510 | } 511 | 512 | function block(aNode) 513 | { 514 | 515 | } 516 | 517 | function showFollowing() 518 | { 519 | var url = "/bcast/_xhr/get/following/?a1={a1}&a2={a2}&t={t}". 520 | printf({a1: messageComposer.ID, a2: messageComposer.token, t: Date.now()}); 521 | var config = { 522 | url: url, 523 | dataType: "json", 524 | success: function success(data) 525 | { 526 | var len = data.following.length; 527 | if (len > 0) { 528 | $("#results").children().remove(); 529 | $("#results").append($("

... following ...

")); 530 | var tmpl = '
{handle}
'; 531 | for (var i = 0; i < len; i++) { 532 | $("#results").append($(tmpl.printf(data.following[i]))); 533 | } 534 | } 535 | else { 536 | notify("You are not following", "anyone"); 537 | } 538 | } 539 | }; 540 | $.ajax(config); 541 | } 542 | 543 | function showFollowers() 544 | { 545 | var url = "/bcast/_xhr/get/followers/?a1={a1}&a2={a2}&t={t}". 546 | printf({a1: messageComposer.ID, a2: messageComposer.token, t: Date.now()}); 547 | var config = { 548 | url: url, 549 | dataType: "json", 550 | success: function success(data) 551 | { 552 | var len = data.followers.length; 553 | if (len > 0) { 554 | $("#results").children().remove(); 555 | $("#results").append($("

... current followers ...

")); 556 | var tmpl = '
{handle}
'; 557 | for (var i = 0; i < len; i++) { 558 | $("#results").append($(tmpl.printf(data.followers[i]))); 559 | } 560 | } 561 | else { 562 | notify("No one is following", "you"); 563 | } 564 | } 565 | }; 566 | $.ajax(config); 567 | } 568 | 569 | function getMessages(aLastFetchedID) 570 | { 571 | if (!localStorage.getItem("credentials")) { 572 | // user has never logged in yet, do not attempt to fetch messages 573 | return; 574 | } 575 | var url = "/bcast/_xhr/get/msgs/?a1={a1}&a2={a2}". 576 | printf({a1: messageComposer.ID, a2: messageComposer.token}); 577 | 578 | if (aLastFetchedID) { 579 | url = url + "&lastid={lastfetched}".printf({lastfetched: aLastFetchedID}); 580 | } 581 | var config = { 582 | url: url, 583 | dataType: "json", 584 | success: function success (data) 585 | { 586 | var len = data.msg.length; 587 | var lastMsgIdx; 588 | if (data.status == "success") { 589 | // display the messages by prepending them to #messages 590 | var _id; 591 | for (var i = 0; i < data.msg.length; i++) { 592 | MessageDisplay(data.msg[i]); 593 | _id = data.msg[i].id; 594 | timeline.index[_id] = data.msg[i]; 595 | timeline.messages.push(data.msg[i]); 596 | // TODO: do not write this on every message pull! 597 | localStorage.setItem("lastFetchedID", data.msg[i].id); 598 | localStorage.setItem("lastFetchedTime", Date.now()); 599 | } 600 | } 601 | if (len < 0) { 602 | notify("no timeline messages to fetch", null); 603 | } 604 | } 605 | }; 606 | $.ajax(config); 607 | } 608 | 609 | // Utilities 610 | 611 | function notify(aTitle, aMsg, aDuration, aError) { 612 | var skin = "rounded"; 613 | var duration; 614 | if (aDuration) { 615 | duration = aDuration; 616 | } 617 | if (aError) { 618 | skin = skin + ",red"; 619 | } 620 | $.notifier.broadcast( 621 | { 622 | ttl: aTitle, 623 | msg: aMsg, 624 | skin: skin, 625 | duration: duration 626 | } 627 | ); 628 | } 629 | 630 | 631 | // printf 632 | String.prototype.printf = function (obj) { 633 | var useArguments = false; 634 | var _arguments = arguments; 635 | var i = -1; 636 | if (typeof _arguments[0] == "string") { 637 | useArguments = true; 638 | } 639 | if (obj instanceof Array || useArguments) { 640 | return this.replace(/\%s/g, 641 | function (a, b) { 642 | i++; 643 | if (useArguments) { 644 | if (typeof _arguments[i] == 'string') { 645 | return _arguments[i]; 646 | } 647 | else { 648 | throw new Error("Arguments element is an invalid type"); 649 | } 650 | } 651 | return obj[i]; 652 | }); 653 | } 654 | else { 655 | return this.replace(/{([^{}]*)}/g, 656 | function (a, b) { 657 | var r = obj[b]; 658 | return typeof r === 'string' || typeof r === 'number' ? r : a; 659 | }); 660 | } 661 | }; 662 | -------------------------------------------------------------------------------- /demos/broadcast/bcast/site_media/js/create-account-ready.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | if (localStorage.getItem("credentials")) { 3 | alert("You already have an account, multiple accounts are not supported yet;)"); 4 | document.location = "/bcast/?t=" + Date.now(); 5 | return; 6 | } 7 | mozCipher.pk.getPublicKey(function (aPubKey) { 8 | window._pubKey = aPubKey; 9 | $("#create-account-btn").click(function (){ new CreateAccount(); }); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /demos/broadcast/bcast/site_media/js/create-account.js: -------------------------------------------------------------------------------- 1 | function CreateAccount() 2 | { 3 | // validate input 4 | var name = $("#chosen-name")[0].value; 5 | console.log(name); 6 | if (!this.checkName(name)) { 7 | alert("Chosen name is formatted incorrectly. Letters, numbers, - and _ are allowed."); 8 | } 9 | else { 10 | this.checkNameAvailable(name); 11 | } 12 | } 13 | 14 | CreateAccount.prototype = { 15 | base64URLRegex: /^[-abcdefghijklmnopqrstuvwxyz0123456789_]{1}$/i, 16 | 17 | checkName: function CA_checkName(name) { 18 | for (var i = 0; i < name.length; i++) { 19 | console.log(name[i]); 20 | if (!(!!name && this.base64URLRegex.test(name[i]))) { 21 | return false; 22 | } 23 | } 24 | console.log("name is ok to check!"); 25 | return true; 26 | }, 27 | 28 | checkNameAvailable: function CA_checkNameAvailable(aName) 29 | { 30 | var self = this; 31 | var url = "/bcast/_xhr/check/display/name/?n=" + aName; 32 | var config = { 33 | url: url, 34 | data: null, 35 | success: function success(data) 36 | { 37 | if (data.available) { 38 | self.submitChosenName(aName); 39 | } 40 | }, 41 | error: function error(jqXHR, testStatus, err) 42 | { 43 | // TODO: better error notification 44 | alert(err); 45 | }, 46 | dataType: "json" 47 | }; 48 | $.ajax(config); 49 | }, 50 | 51 | submitChosenName: function CA_submitChosenName(aName) 52 | { 53 | var password = $("#user-password")[0].value; 54 | if (password.length < 6) { 55 | notify("error:", "server password must be 6 characters", true); 56 | return; 57 | } 58 | console.log("submitChosenName"); 59 | var self = this; 60 | var url = "/bcast/_xhr/create/acct/?n=" + aName; 61 | var csrf_token = $('#csrf_token >div >input').attr("value"); 62 | console.log(csrf_token); 63 | var config = { 64 | url: url, 65 | type: "POST", 66 | data: { pub_key: window._pubKey, 67 | password: password, 68 | csrfmiddlewaretoken: csrf_token}, 69 | dataType: "json", 70 | success: function success(data) 71 | { 72 | if (data.status == "success") { 73 | $("#chosen-name")[0].value = ""; 74 | // get the login token and identifier and save them to localstorage 75 | self.saveCredentials(data.login_token, data.identifier, aName); 76 | } 77 | } 78 | }; 79 | $.ajax(config); 80 | }, 81 | 82 | saveCredentials: function CA_saveCredentials(aLoginToken, aID, aName) 83 | { 84 | var credentials = { 85 | token: aLoginToken, 86 | ID: aID, 87 | displayName: aName 88 | }; 89 | 90 | var _credentials = JSON.stringify(credentials); 91 | 92 | mozCipher.pk.encrypt(_credentials, window._pubKey, function(aCryptoMsg){ 93 | var credentials = JSON.stringify(aCryptoMsg); 94 | localStorage.setItem("credentials", credentials); 95 | alert("account created. redirecting..."); 96 | var t = Date.now(); 97 | document.location = "/bcast/?t=" + t; 98 | }); 99 | } 100 | }; 101 | -------------------------------------------------------------------------------- /demos/broadcast/bcast/site_media/js/index-ready.js: -------------------------------------------------------------------------------- 1 | var jsonMessages = []; 2 | // need to populate this with sample messages once we have 3 | // a working message composer 4 | var timelineData = []; 5 | var followers = []; 6 | var timelineIndex = {}; 7 | 8 | $(document).ready(function() { 9 | // check for DOMCrypt 10 | checkDOMCrypt(); 11 | // key bindings 12 | $("#search").keydown(function (event){ 13 | if (event.keyCode == '13') { 14 | var str = $("#search")[0].value; 15 | if (str) { 16 | new SearchAccounts(str); 17 | } 18 | } 19 | }); 20 | 21 | // focus events 22 | $("#search").focus(function (){ 23 | if (this.value == "find people...") { 24 | this.value = ""; 25 | } 26 | }); 27 | 28 | try { 29 | // need to check for credentials, decrypt them and configure the application 30 | var _credentials = localStorage.getItem("credentials"); 31 | if (!_credentials) { 32 | alert("It looks like you do not have an account yet, redirecting momentarily..."); 33 | document.location = "/bcast/create/account/"; 34 | return; 35 | } 36 | // if (!_credentials) { 37 | // // display login page 38 | // document.location = "/bcast/login/"; 39 | // return; 40 | // } 41 | mozCipher.pk.getPublicKey(function callback(aPublicKey) { 42 | if (!aPublicKey) { 43 | notify("error: no public key detected", 44 | "a public key is required to use bcast", 45 | true); 46 | // create public key 47 | return; 48 | } 49 | window._pubKey = aPublicKey; 50 | var credentials = JSON.parse(_credentials); 51 | // decrypt credentials 52 | mozCipher.pk.decrypt(credentials, function (plaintext) { 53 | var credentialsPlainObj = JSON.parse(plaintext); 54 | // TODO: need to get follower_ids from the server 55 | window.messageComposer = new MessageComposer(credentialsPlainObj, []); 56 | }); 57 | }); 58 | } 59 | catch (ex) { 60 | console.log(ex); 61 | console.log(ex.stack); 62 | } 63 | }); 64 | -------------------------------------------------------------------------------- /demos/broadcast/bcast/site_media/js/notifier.js: -------------------------------------------------------------------------------- 1 | (function($) 2 | { 3 | $.extend( 4 | { 5 | notifier:{ 6 | // variables 7 | defaults:{ 8 | core:"notifier", 9 | duration:5000 10 | }, 11 | skins:{ 12 | base:"#_{position:fixed;top:5px;right:5px;font-family:Arial;z-index:9999;}\n", 13 | box:"#_ #-{float:left;clear:both;padding:10px 0;color:#FFFFFF;position:relative;width:250px;margin-bottom:5px;}\n#_ #- .bg{z-index:-1;position:absolute;top:0;width:100%;height:100%;background:#000000;opacity:.5;opacity:0.5;filter:alpha(opacity=50);}\n#_ #- h1, #_ #- p{clear:both;padding:0 10px;margin:0;font-size:24px;font-family:Arial;color:#FFFFFF;letter-spacing:0;}\n#_ #- p{font-size:16px;margin:5px 0}\n#_ #- p.time{font-size:10px;}\n#_ #- button{position:absolute;right:5px;top:5px;}", 14 | rounded:"#_ #- > div{border-radius:5px;-moz-border-radius:5px;-webkit-border-radius:5px;}\n", 15 | red:"#_ #-{color:#CC0000;}\n#_ #- .bg{background:#CC0000;}\n" 16 | }, 17 | notices:{}, 18 | 19 | // methods 20 | broadcast:function(properties) 21 | { 22 | this.core(); 23 | 24 | var id; 25 | id = "notice-" + this.timestamp(); 26 | 27 | // set notices object 28 | var notice = this.notices[id] = {id:id}; 29 | for(i in properties){notice[i] = properties[i]} 30 | 31 | // skin 32 | this.css(notice); 33 | 34 | // box 35 | $("#" + this.defaults.core).append(this.box(notice)); 36 | }, 37 | 38 | // core 39 | core:function() 40 | { 41 | var core = this.defaults.core; 42 | return $("#" + core).length == 0 ? $('body').append("
") : $("#" + core); 43 | }, 44 | 45 | // box 46 | box:function(notice) 47 | { 48 | var box = $("
"); 49 | box.append($("").append("x")); 50 | box.append($("

").append(notice.ttl)); 51 | box.append($("

").append(notice.msg)); 52 | box.append($("

").addClass("time").append(Date())); 53 | box.append($("
").addClass("bg")); 54 | box.hide().fadeIn(); 55 | this.life(box, notice.id); 56 | this.events(box, notice.id); 57 | return box; 58 | }, 59 | 60 | // events 61 | events:function(box, seed) 62 | { 63 | $(box).children("button").bind( 64 | 'click', 65 | function() 66 | { 67 | var seed = $(this).parent().attr("id"); 68 | $.notifier.destroy(seed, true); 69 | } 70 | ) 71 | $(box).bind( 72 | 'mouseover', 73 | function() 74 | { 75 | if($.notifier.notices[$(this).attr("id")].interval) 76 | { 77 | var seed = $(this).attr("id"); 78 | $.notifier.destroy(seed) 79 | } 80 | } 81 | ) 82 | 83 | $(box).bind( 84 | 'mouseout', 85 | function() 86 | { 87 | $.notifier.life(this, $(this).attr("id")); 88 | } 89 | ) 90 | }, 91 | 92 | // life 93 | life:function(box, seed) 94 | { 95 | if(!this.notices[seed].duration){this.notices[seed].duration = this.defaults.duration} 96 | this.notices[seed].interval = {}; 97 | this.notices[seed].interval = setInterval( 98 | function() 99 | { 100 | (function(seed) 101 | { 102 | $.notifier.destroy(seed, true) 103 | }) 104 | (seed) 105 | }, 106 | this.notices[seed].duration 107 | ) 108 | }, 109 | 110 | // destroy 111 | destroy:function(seed, remove) 112 | { 113 | clearInterval($.notifier.notices[seed].interval); 114 | delete $.notifier.notices[seed].interval; 115 | if(remove == true){$("#" + seed).slideUp(250, function(){$(this).remove()});} 116 | }, 117 | 118 | // css 119 | css:function(notice) 120 | { 121 | var css="" 122 | var skin; 123 | var style = $(""); 124 | 125 | skin = !notice.skin ? ['base', 'box'] : ('base,box,' + notice.skin).split(","); 126 | for(var n = 0; n < skin.length; n++){css += this.skins[skin[n]].replace(/#_/g, "#" + this.defaults.core).replace(/#-/g, "#" + notice.id);} 127 | $("#" + this.defaults.core).prepend(style.append(css)); 128 | }, 129 | 130 | // timestamp 131 | timestamp:function() 132 | { 133 | return new Date().getTime(); 134 | } 135 | } 136 | } 137 | ) 138 | })(jQuery); 139 | -------------------------------------------------------------------------------- /demos/broadcast/bcast/tmpl/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CryptoBroadcast 6 | 23 | 24 | 25 | 26 | 27 |
28 |

CryptoBroadcast

29 |
30 | 31 | 32 |
33 |
34 |
35 |
36 | 37 | 38 | -------------------------------------------------------------------------------- /demos/broadcast/bcast/tmpl/create_account.html: -------------------------------------------------------------------------------- 1 | {% extends "index.html" %} 2 | {% block subtitle %} 3 | : create account 4 | {% endblock subtitle %} 5 | {% block script %} 6 | 7 | 8 | {% endblock script %} 9 | {% block wraptitle %} 10 | : create account 11 | {% endblock wraptitle %} 12 | {% block main %} 13 | 14 |
15 |

16 |

17 |

18 |

19 |

20 |
21 | {% endblock main %} 22 | -------------------------------------------------------------------------------- /demos/broadcast/bcast/tmpl/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | bcast {% block subtitle %}{% endblock subtitle %} 6 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | {% block script %} 44 | {% endblock script %} 45 | 46 | 47 |
48 |
49 |

bcast {% block wraptitle %}{% endblock wraptitle %}

50 |

51 | {% block main %} 52 |
53 | 54 | 55 |
56 | 57 | 58 | followers 59 | 60 | 61 | following 62 | 63 |
64 |
65 |
66 |
67 | {% endblock main %} 68 |
69 |
{% csrf_token %}
70 | 71 | 72 | -------------------------------------------------------------------------------- /demos/broadcast/bcast/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls.defaults import * 2 | import os 3 | 4 | # Uncomment the next two lines to enable the admin: 5 | from django.contrib import admin 6 | admin.autodiscover() 7 | 8 | urlpatterns = patterns('bcast.server.views', 9 | (r'^bcast/$', 'index'), 10 | (r'^bcast/create/account/$', 'create_account'), 11 | (r'^bcast/destroy/account/$', 'destroy_account'), 12 | (r'^bcast/login/$', 'login'), 13 | (r'^bcast/_xhr/check/display/name/$', 'x_check_display_name'), 14 | (r'^bcast/_xhr/create/acct/$', 'x_create_acct'), 15 | (r'^bcast/_xhr/destroy/acct/$', 'x_destroy_acct'), 16 | (r'^bcast/_xhr/post/msg/$', 'x_post_msg'), 17 | (r'^bcast/_xhr/get/msgs/$', 'x_get_msgs'), 18 | (r'^bcast/_xhr/remove/msg/$', 'x_remove_msg'), 19 | (r'^bcast/_xhr/search/accounts/$', 'x_search_accts'), 20 | (r'^bcast/_xhr/follow/$', 'x_follow'), 21 | (r'^bcast/_xhr/block/$', 'x_block'), 22 | (r'^bcast/_xhr/login/$', 'x_login'), 23 | (r'^bcast/_xhr/get/followers/$', 'x_get_followers'), 24 | (r'^bcast/_xhr/get/following/$', 'x_get_following'), 25 | (r'^bcast/_xhr/fetch/console/msgs/$', 'x_fetch_console'), 26 | (r'^admin/doc/', include('django.contrib.admindocs.urls')), 27 | (r'^admin/', include(admin.site.urls)), 28 | ) 29 | 30 | urlpatterns += patterns('', 31 | (r'^site_media/(.*)$', 32 | 'django.views.static.serve', 33 | {'document_root': os.environ.get('BCAST_MEDIA_ROOT', 34 | '/home/ddahl/code/domcrypt/demos/broadcast/bcast/site_media/'), 35 | 'show_indexes': False}), 36 | ) 37 | -------------------------------------------------------------------------------- /demos/broadcast/broadcast.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CryptoBroadcast 6 | 23 | 24 | 25 | 26 | 27 | 28 |
29 |

CryptoBroadcast

30 |
31 | 32 | 33 |
34 |
35 |
36 |
37 | 38 | 39 | -------------------------------------------------------------------------------- /demos/broadcast/broadcast.js: -------------------------------------------------------------------------------- 1 | var jsonMessages = []; 2 | // need to populate this with sample messages once we have 3 | // a working message composer 4 | var timelineData = []; 5 | var followers = []; 6 | var messageComposer; 7 | var timelineIndex = {}; 8 | 9 | $(document).ready(function() { 10 | // create message store 11 | var idx; 12 | for (idx in jsonMessages) { 13 | var message = JSON.parse(jsonMessages[idx]); 14 | timelineData.push(message); 15 | } 16 | // now we need to populate the messages div with messages 17 | for (idx in timelineData) { 18 | MessageDisplay(timelineData[idx]); 19 | } 20 | messageComposer = new MessageComposer("drzhivago", []); 21 | }); 22 | 23 | function MessageDisplay(aMessageData) 24 | { 25 | var tmpl = '
' 26 | + '
{date}
' 27 | + '
{author}
' 28 | + '
{cipherText}
' 29 | + '' 30 | + '
'; 31 | var node = $(tmpl.printf(aMessageData)); 32 | $("#msg-input")[0].value = ""; 33 | $("#messages").prepend(node); 34 | timelineIndex[aMessageData.id] = aMessageData; 35 | } 36 | 37 | function DisplayPlainText(aNode) 38 | { 39 | console.log(aNode); 40 | var id = aNode.parentNode.getAttribute("id"); 41 | console.log(id); 42 | new MessageReader(id); 43 | } 44 | 45 | function MessageComposer(aDisplayName, aFollowers) 46 | { 47 | // normally in the MessageComposer, you would: 48 | // 1. encrypt text 49 | // 2. get a list of all followers' hashIds and pubKeys 50 | // 3. wrapKeys for all followers 51 | // 4. push the message bundle to the server 52 | // In this demo we will just add the message to the timeline of the current user 53 | this.author = aDisplayName; 54 | this.followers = aFollowers; 55 | var self = this; 56 | mozCipher.pk.getPublicKey(function (aPubKey){ 57 | self.authorPubKey = aPubKey; 58 | self.followers.push({handle: self.author, pubKey: self.authorPubKey }); 59 | }); 60 | } 61 | 62 | MessageComposer.prototype = { 63 | author: null, 64 | 65 | authorPubKey: null, 66 | 67 | followers: [], 68 | 69 | bundle: function mc_bundle(aCipherMessage) 70 | { 71 | console.log("bundle--->"); 72 | console.log(aCipherMessage.idx); 73 | var self = this; 74 | var idx; 75 | var messages = []; 76 | var bundle = { author: self.author, authorPubKey: self.authorPubKey }; 77 | // TODO: add authentication token and password to the bundle 78 | var len = self.followers.length; 79 | 80 | // need to re-wrap the key for each follower 81 | function wrapCallback(aCipherObj) { 82 | console.log("idx: " + idx); 83 | console.log("cm.idx: " + aCipherMessage.idx); 84 | messages.push(aCipherObj); 85 | if (aCipherMessage.idx == (idx + 1)) { 86 | bundle.messages = messages; 87 | self.send(bundle); 88 | } 89 | } 90 | 91 | for (idx in self.followers) { 92 | mozCipher.sym.wrapKey(aCipherMessage, self.followers[idx].pubKey, wrapCallback); 93 | } 94 | }, 95 | 96 | encrypt: function mc_encrypt() 97 | { 98 | var self = this; 99 | var followersLen = this.followers.length; 100 | console.log(followersLen); 101 | mozCipher.sym.encrypt($("#msg-input")[0].value, function (aCipherMsg) { 102 | aCipherMsg.author = this.author; 103 | // TODO: send the bundle to the server... 104 | var date = new Date(); 105 | console.log(self.author); 106 | var message = {author: self.author, 107 | id: date.getTime(), 108 | date: date.toString(), 109 | cipherText: aCipherMsg.cipherText, 110 | wrappedKey: aCipherMsg.wrappedKey, 111 | iv: aCipherMsg.iv, 112 | pubKey: aCipherMsg.pubKey, 113 | idx: followersLen}; 114 | 115 | var bundle = self.bundle(message); 116 | 117 | MessageDisplay(message); 118 | }); 119 | }, 120 | 121 | send: function mc_send(bundle) 122 | { 123 | // TODO: HTTP POST to server 124 | console.log("SEND--->"); 125 | console.log(bundle); 126 | }, 127 | 128 | validate: function mc_validate() 129 | { 130 | var txt = $("#msg-input")[0].value; 131 | if (txt.length > 0 && txt.length < 4096) { 132 | this.encrypt(); 133 | } 134 | else { 135 | // XXX: notify user of error 136 | } 137 | } 138 | }; 139 | 140 | function MessageReader(aMessageID) 141 | { 142 | this.id = aMessageID; 143 | this.decrypt(); 144 | } 145 | 146 | MessageReader.prototype = { 147 | decrypt: function mr_decrypt() 148 | { 149 | var self = this; 150 | var msg = timelineIndex[this.id]; 151 | mozCipher.sym.decrypt(msg, function (plainText) { 152 | var id = "#" + self.id; 153 | $(id)[0].childNodes[2].innerHTML = 154 | '
{plainText}
'.printf({plainText: plainText}); 155 | // disable read button 156 | $(id + " > .read-one")[0].disabled = true; 157 | }); 158 | } 159 | }; 160 | 161 | String.prototype.printf = function (obj) { 162 | var useArguments = false; 163 | var _arguments = arguments; 164 | var i = -1; 165 | if (typeof _arguments[0] == "string") { 166 | useArguments = true; 167 | } 168 | if (obj instanceof Array || useArguments) { 169 | return this.replace(/\%s/g, 170 | function (a, b) { 171 | i++; 172 | if (useArguments) { 173 | if (typeof _arguments[i] == 'string') { 174 | return _arguments[i]; 175 | } 176 | else { 177 | throw new Error("Arguments element is an invalid type"); 178 | } 179 | } 180 | return obj[i]; 181 | }); 182 | } 183 | else { 184 | return this.replace(/{([^{}]*)}/g, 185 | function (a, b) { 186 | var r = obj[b]; 187 | return typeof r === 'string' || typeof r === 'number' ? r : a; 188 | }); 189 | } 190 | }; 191 | -------------------------------------------------------------------------------- /demos/dcserver/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daviddahl/domcrypt/e8238956626bd235657eeb00f2bc1bfb92c916e0/demos/dcserver/__init__.py -------------------------------------------------------------------------------- /demos/dcserver/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from django.core.management import execute_manager 3 | try: 4 | import settings # Assumed to be in the same directory. 5 | except ImportError: 6 | import sys 7 | sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__) 8 | sys.exit(1) 9 | 10 | if __name__ == "__main__": 11 | execute_manager(settings) 12 | -------------------------------------------------------------------------------- /demos/dcserver/msgdrp/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daviddahl/domcrypt/e8238956626bd235657eeb00f2bc1bfb92c916e0/demos/dcserver/msgdrp/__init__.py -------------------------------------------------------------------------------- /demos/dcserver/msgdrp/models.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | import hashlib 3 | import random 4 | 5 | from django.db import models 6 | 7 | class Message(models.Model): 8 | """ 9 | A Secure message 10 | """ 11 | _hash = models.CharField(max_length=255) 12 | _from = models.CharField(max_length=255) 13 | date_time = models.DateTimeField(auto_now_add=True) 14 | content = models.TextField(null=True,blank=True) 15 | fetched = models.BooleanField(default=False) 16 | 17 | def __unicode__(self): 18 | return u"To: %s" % (self._hash,) 19 | 20 | class Addressbook(models.Model): 21 | """ 22 | The server/domain addressbook. 23 | The addressbook provides the back end data to views where a 24 | user's Addressbook entry is displayed 25 | """ 26 | handle = models.CharField(max_length=255, unique=True) 27 | _hash = models.CharField(max_length=255, unique=True) 28 | date_created = models.DateTimeField(auto_now_add=True) 29 | date_modified = models.DateTimeField(auto_now=True, auto_now_add=True) 30 | service_key = models.CharField(max_length=255) 31 | pub_key = models.TextField() 32 | domain = models.CharField(max_length=255) 33 | 34 | def __unicode__(self): 35 | return u"%s" % (self.handle,) 36 | 37 | def generate_service_key(_hash): 38 | """ 39 | Generate a service key that a user needs to fetch and send messages 40 | """ 41 | # hash the hash + a date_time string 42 | rndm = random.randint(0, 1000000) 43 | seed = "%s-%s-%s" % (_hash, 44 | str(datetime.now()), 45 | str(rndm),) 46 | hashed = hashlib.sha256(seed).hexdigest() 47 | return hashed 48 | -------------------------------------------------------------------------------- /demos/dcserver/msgdrp/tests.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file demonstrates two different styles of tests (one doctest and one 3 | unittest). These will both pass when you run "manage.py test". 4 | 5 | Replace these with more appropriate tests for your application. 6 | """ 7 | 8 | from django.test import TestCase 9 | 10 | class SimpleTest(TestCase): 11 | def test_basic_addition(self): 12 | """ 13 | Tests that 1 + 1 always equals 2. 14 | """ 15 | self.failUnlessEqual(1 + 1, 2) 16 | 17 | __test__ = {"doctest": """ 18 | Another way to test that 1 + 1 is equal to 2. 19 | 20 | >>> 1 + 1 == 2 21 | True 22 | """} 23 | 24 | -------------------------------------------------------------------------------- /demos/dcserver/msgdrp/tmpl/addressbook.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block main %} 3 |

Addressbook entry lookup

4 |
Search for Addressbook entries of your contacts
5 |
6 |

7 | 8 | 9 |

10 |
11 |

12 |
{% csrf_token %}
13 | {% endblock main %} 14 | -------------------------------------------------------------------------------- /demos/dcserver/msgdrp/tmpl/base.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | DOMCrypt: Messages Demo 6 | {% block metatags %} 7 | {% endblock metatags %} 8 | 9 | 10 | 11 | 12 | 13 |
14 |

Messages 15 | 16 | 17 | 18 | 19 | 20 |

21 | 22 |
23 | {% block main %} 24 | 25 | {% endblock main %} 26 |
27 | 28 |
29 | 30 | 31 | -------------------------------------------------------------------------------- /demos/dcserver/msgdrp/tmpl/begin_create_addressbook_entry.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block main %} 3 |

Create an addressbook entry

4 |
An Addressbook Entry is required to communicate with your contacts.
5 |
6 |

7 |

8 | You need to choose a 'Handle' to begin using Messages, which is a username. 9 |

10 |

11 | This system provides some psuedo-anonymity, using your name is not encouraged.

12 | 13 | 14 |

15 |

16 | A web page will be created for you on this server, which your contacts can visit to record your adressbook entry. 17 |

18 |

19 | It is automatic, your contacts only need to visit the page one time. 20 |

21 |
22 |

23 |
24 |

25 | 26 |
27 |
{% csrf_token %}
28 | {% endblock main %} 29 | -------------------------------------------------------------------------------- /demos/dcserver/msgdrp/tmpl/compose.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block main %} 3 |

Compose

4 |

5 |
6 |

Message:

7 |
8 | 9 |
10 |

11 |
{% csrf_token %}
12 | {% endblock main %} 13 | -------------------------------------------------------------------------------- /demos/dcserver/msgdrp/tmpl/display_addressbook_entry.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block metatags %} 3 | {% if entry %} 4 | 9 | {% endif %} 10 | {% endblock metatags %} 11 | {% block main %} 12 |

Handle searched for: {{ handle_lookup }}

13 |
14 |

15 | {% if entry %} 16 |

{{ msg }}

17 | {% else %} 18 |

Please

19 | {% endif %} 20 |

21 |
22 |
{% csrf_token %}
23 | {% endblock main %} 24 | -------------------------------------------------------------------------------- /demos/dcserver/msgdrp/tmpl/entry_search_list.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block main %} 3 |

Handle searched for: {{ handle_lookup }}

4 |
5 |

6 |

{{ msg }}

7 | {% if entry %} 8 | {% for e in entry %} 9 |

{{ e.handle }}

10 | {% endfor %} 11 | {% else %} 12 |

Please

13 | {% endif %} 14 |

15 |
16 |
{% csrf_token %}
17 | {% endblock main %} 18 | -------------------------------------------------------------------------------- /demos/dcserver/msgdrp/tmpl/index.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block main %} 3 |

Exchange pseudo-anonymous encrypted messages with your contacts

4 |
5 |
6 |

In order to use this system you need the following:

7 | 25 |
DOMCrypt source code: https://github.com/daviddahl/domcrypt
26 |
27 | {% endblock main %} 28 | -------------------------------------------------------------------------------- /demos/dcserver/msgdrp/tmpl/messages.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block main %} 3 |
4 |
5 | 0 Messages
6 |
7 |
8 |
9 | {% endblock main %} 10 | -------------------------------------------------------------------------------- /demos/dcserver/msgdrp/urls.py: -------------------------------------------------------------------------------- 1 | urlpatterns = patterns('dcserver.msgdrp.views', 2 | (r'^/$', 'index'), 3 | (r'^fetch/', 'fetch'), 4 | (r'^messages/', 'messages'), 5 | (r'^compose/', 'compose'), 6 | (r'^send/', 'send') 7 | ) 8 | -------------------------------------------------------------------------------- /demos/dcserver/msgdrp/views.py: -------------------------------------------------------------------------------- 1 | from django.http import Http404, HttpResponse, HttpResponseRedirect 2 | from django.template import Context, loader 3 | from django.shortcuts import render_to_response, get_object_or_404 4 | from django import forms 5 | from django.utils import simplejson 6 | from django.utils.translation import ugettext_lazy as _ 7 | from django.contrib.syndication import feeds 8 | from django.http import Http404 9 | from django.views.decorators.csrf import csrf_protect 10 | from django.template import RequestContext 11 | 12 | from django.core.serializers import serialize 13 | from django.db.models.query import QuerySet 14 | 15 | from dcserver.msgdrp.models import Message, Addressbook, generate_service_key 16 | from dcserver.settings import DEFAULT_DOMAIN 17 | 18 | class JsonResponse(HttpResponse): 19 | content = None 20 | 21 | def __init__(self, object): 22 | if isinstance(object, QuerySet): 23 | content = serialize('json', object) 24 | else: 25 | content = simplejson.dumps(object) 26 | self.content = simplejson.loads(content) 27 | super(JsonResponse, self).__init__(content, mimetype='application/json') 28 | 29 | def index(request): 30 | """ 31 | an index page with links to the extension, messages pages and compose page 32 | """ 33 | return render_to_response("index.html", {}) 34 | 35 | def fetch(request): 36 | """ 37 | pull up all messages delivered to request.GET[h] (hash) 38 | """ 39 | # TODO require svc_key for all fecthing and sending 40 | # TODO add fetched=False to query 41 | try: 42 | if request.GET['h'] is None: 43 | raise Exception("No messages for %s" % request.GET['h']) 44 | messages = Message.objects.filter(_hash=request.GET['h']) 45 | _msgs = [] 46 | for message in messages: 47 | _msgs.append({'id': message.id, 48 | 'from': message._from, 49 | 'hash': message._hash, 50 | 'content': message.content, 51 | 'dateTime': str(message.date_time)}) 52 | message.fetched = True 53 | message.save() 54 | # TODO: delete fetched messages on cronjob 55 | msg = { 'status': 'success', 'msg': _msgs } 56 | 57 | except Exception, e: 58 | msg = {'status': 'failure', 'msg': e} 59 | 60 | return JsonResponse(msg); 61 | 62 | def messages(request): 63 | """ 64 | Read messages 65 | """ 66 | return render_to_response("messages.html", {}, context_instance=RequestContext(request)) 67 | 68 | def compose(request): 69 | """ 70 | Compose a message page 71 | """ 72 | return render_to_response("compose.html", {}, context_instance=RequestContext(request)) 73 | 74 | def send(request): 75 | """ 76 | Send the message 77 | """ 78 | 79 | try: 80 | if request.POST['message'] and request.POST['_hash']: 81 | """the message will just be a JSON string to be stored lin the database""" 82 | message = Message(_hash=request.POST['_hash'], content=request.POST['message'], _from=request.POST['_from']) 83 | message.save() 84 | msg = {'status': 'success', 'msg': "Message sent", "id": message.id } 85 | 86 | except Exception, e: 87 | print e 88 | msg = {'status': 'failure', 'msg': "Could not send message"} 89 | 90 | return JsonResponse(msg); 91 | 92 | def begin_create_addressbook_entry(request): 93 | """ 94 | display create addressbook entry page 95 | """ 96 | return render_to_response("begin_create_addressbook_entry.html", {}, context_instance=RequestContext(request)) 97 | 98 | def created_addressbook_entry(request): 99 | """ 100 | create new entry and show user what the addressbook entry pagel looks like 101 | """ 102 | return render_to_response("show_new_entry.html", {}, context_instance=RequestContext(request)) 103 | 104 | def display_addressbook_entry(request, handle): 105 | """ 106 | lookup entry based on handle, display entry page 107 | """ 108 | try: 109 | msg = "Not Found" 110 | if len(handle) < 3: 111 | msg = "Please enter at least 3 characters of the handle you seek"; 112 | entry = None 113 | else: 114 | entry = Addressbook.objects.filter(handle__contains=handle) 115 | if entry.count() > 1: 116 | msg = "addressbook entries were found" 117 | return render_to_response("entry_search_list.html", 118 | {"entry": entry, 119 | "handle_lookup": handle, 120 | "msg": msg }, 121 | context_instance=RequestContext(request)) 122 | if entry.count() == 1: 123 | msg = entry[0].handle + " addressbook entry was found" 124 | return render_to_response("display_addressbook_entry.html", 125 | {"entry": entry[0], 126 | "handle_lookup": handle, 127 | "msg": msg }, 128 | context_instance=RequestContext(request)) 129 | except Exception, e: 130 | print e 131 | entry = None 132 | msg = "Addressbook entry was not found" 133 | return render_to_response("entry_search_list.html", 134 | {"entry": entry, 135 | "handle_lookup": handle, 136 | "msg": msg }, 137 | context_instance=RequestContext(request)) 138 | 139 | def addressbook(request): 140 | """ 141 | search the addressbook by handle 142 | """ 143 | return render_to_response("addressbook.html", {}, context_instance=RequestContext(request)) 144 | 145 | def process_addressbook_creation(request): 146 | """ 147 | Check for a handle that can be used 148 | """ 149 | try: 150 | if request.POST['pubKey'] and request.POST['handle'] and request.POST['hash']: 151 | # make service key 152 | svc_key = generate_service_key(request.POST['hash']) 153 | # we have the requisite data, let's store it 154 | entry = Addressbook.objects.create(handle=request.GET['handle'], 155 | _hash=request.POST['hash'], 156 | pub_key=request.POST['pubKey'], 157 | service_key=svc_key, 158 | domain=DEFAULT_DOMAIN) 159 | msg = "Addressbook entry was created"; 160 | results = { 'serviceKey': entry.service_key, 161 | 'entryURL': "/addressbook/" + entry.handle + "/", 162 | 'msg': msg, 163 | 'status': 'success' } 164 | else: 165 | results = { 'status': 'failure', 166 | 'msg': 'Error creating addressbook entry' } 167 | 168 | except Exception, e: 169 | print e 170 | msg = "Server Failure: Addressbook entry was not created" 171 | results = {'status': "failure", 'msg': msg + " " + str(e)} 172 | 173 | return JsonResponse(results); 174 | 175 | -------------------------------------------------------------------------------- /demos/dcserver/settings.py: -------------------------------------------------------------------------------- 1 | # Django settings for dcserver project. 2 | import os 3 | 4 | TMPL_DIR = os.environ["DC_TMPL_DIR"] 5 | DB_PATH = os.environ["DC_DB_PATH"] 6 | 7 | DEFAULT_DOMAIN = "domcrypt.com" 8 | 9 | DEBUG = True 10 | TEMPLATE_DEBUG = DEBUG 11 | 12 | ADMINS = ( 13 | ('Admin', 'ddahl@pobox.com'), 14 | ) 15 | 16 | MANAGERS = ADMINS 17 | 18 | DATABASES = { 19 | 'default': { 20 | 'ENGINE': 'django.db.backends.sqlite3', 21 | 'NAME': DB_PATH, # Or path to database file if using sqlite3. 22 | 'USER': '', # Not used with sqlite3. 23 | 'PASSWORD': '', # Not used with sqlite3. 24 | 'HOST': '', # Set to empty string for localhost. Not used with sqlite3. 25 | 'PORT': '', # Set to empty string for default. Not used with sqlite3. 26 | } 27 | } 28 | 29 | # Local time zone for this installation. Choices can be found here: 30 | # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name 31 | # although not all choices may be available on all operating systems. 32 | # On Unix systems, a value of None will cause Django to use the same 33 | # timezone as the operating system. 34 | # If running in a Windows environment this must be set to the same as your 35 | # system time zone. 36 | TIME_ZONE = 'America/Chicago' 37 | 38 | # Language code for this installation. All choices can be found here: 39 | # http://www.i18nguy.com/unicode/language-identifiers.html 40 | LANGUAGE_CODE = 'en-us' 41 | 42 | SITE_ID = 1 43 | 44 | # If you set this to False, Django will make some optimizations so as not 45 | # to load the internationalization machinery. 46 | USE_I18N = True 47 | 48 | # If you set this to False, Django will not format dates, numbers and 49 | # calendars according to the current locale 50 | USE_L10N = True 51 | 52 | # Absolute filesystem path to the directory that will hold user-uploaded files. 53 | # Example: "/home/media/media.lawrence.com/" 54 | MEDIA_ROOT = '' 55 | 56 | # URL that handles the media served from MEDIA_ROOT. Make sure to use a 57 | # trailing slash if there is a path component (optional in other cases). 58 | # Examples: "http://media.lawrence.com", "http://example.com/media/" 59 | MEDIA_URL = '' 60 | 61 | # URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a 62 | # trailing slash. 63 | # Examples: "http://foo.com/media/", "/media/". 64 | ADMIN_MEDIA_PREFIX = '/media/' 65 | 66 | # Make this unique, and don't share it with anybody. 67 | SECRET_KEY = '$x0nxac0zff#y*+qtj7jj(z-fvx@gilzhy5)pmoa@5j8kz&a4%' 68 | 69 | # List of callables that know how to import templates from various sources. 70 | TEMPLATE_LOADERS = ( 71 | 'django.template.loaders.filesystem.Loader', 72 | 'django.template.loaders.app_directories.Loader', 73 | # 'django.template.loaders.eggs.Loader', 74 | ) 75 | 76 | MIDDLEWARE_CLASSES = ( 77 | 'django.middleware.common.CommonMiddleware', 78 | 'django.contrib.sessions.middleware.SessionMiddleware', 79 | 'django.middleware.csrf.CsrfViewMiddleware', 80 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 81 | 'django.contrib.messages.middleware.MessageMiddleware', 82 | ) 83 | 84 | ROOT_URLCONF = 'dcserver.urls' 85 | 86 | TEMPLATE_DIRS = ( 87 | # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". 88 | # Always use forward slashes, even on Windows. 89 | # Don't forget to use absolute paths, not relative paths. 90 | TMPL_DIR 91 | ) 92 | 93 | INSTALLED_APPS = ( 94 | 'django.contrib.auth', 95 | 'django.contrib.contenttypes', 96 | 'django.contrib.sessions', 97 | 'django.contrib.sites', 98 | 'django.contrib.messages', 99 | 'dcserver.msgdrp', 100 | # Uncomment the next line to enable the admin: 101 | 'django.contrib.admin', 102 | # Uncomment the next line to enable admin documentation: 103 | # 'django.contrib.admindocs', 104 | ) 105 | -------------------------------------------------------------------------------- /demos/dcserver/site_media/css/style.css: -------------------------------------------------------------------------------- 1 | /* Styles */ 2 | body { font-family: sans-serif; font-size: 1em; } 3 | .key { font-family: monospace; font-size: 0.8em; color: green; } 4 | #decrypted { color: blue; } 5 | #encrypted { color: red; } 6 | div { margin: 0.3em; padding: 0.2em; } 7 | h1 { font-size: 1.3em; } 8 | h2 { font-size: 1.2em; } 9 | h3 { font-size: 1em; } 10 | li { margin-top: 0.4em; } 11 | .bold { font-weight: bold; } 12 | #addressbook-data {color: green; font-family: monospace; word-wrap: break-word; width: 75%; } 13 | .footnote { font-size: 70%; } 14 | #base-title { margin-right: 5%; } 15 | #write-message { width: 50%; height: 10em; } 16 | #inbox { border-radius: 5px; border: 1px solid #ccc; background: #eee; min-height: 100px; max-width: 75%; } 17 | #inbox-link { cursor: pointer; } 18 | #outbox-link { cursor: pointer; } 19 | .message-counter { margin-left: 1em; } 20 | .msg-content { display: none; } 21 | #cipher-text { display: none; font-size: 60%; color: #ccc;} 22 | #results { color: red; font-weight: bold; } 23 | .plain-message { max-width: 90%; scroll: auto; word-wrap: break-word; } 24 | pre { white-space: pre-wrap; } 25 | .msg-list-msg { border: solid 1px #000; border-radius: 5px; } 26 | .msg-title { font-size: 60%; } 27 | .setup-status { color: red; font-weight: bold; } 28 | #credits { font-size: 60%; margin-top: 10em; } 29 | #remind-repeat-click { font-weight: bold; color: blue; } 30 | -------------------------------------------------------------------------------- /demos/dcserver/site_media/js/msgdrp.js: -------------------------------------------------------------------------------- 1 | // MessageDrop script 2 | var MessageData = { 3 | saveMessageBox: function MD_saveMessageBox(aName) 4 | { 5 | if (!localStorage.getItem(aName)) { 6 | throw new Error("localStorage: Cannot save to " + aName); 7 | } 8 | localStorage.setItem(aName, JSON.stringify(MessageData[aName])); 9 | }, 10 | 11 | inBox: {}, 12 | 13 | outBox: {} 14 | }; 15 | 16 | function firstRun() 17 | { 18 | // TODO: convert this app to a single page web app so all of this 19 | // data loading is kept to a minimum 20 | if (!localStorage.getItem("first-run")) { 21 | // create mailboxes 22 | var _date = Date.now(); 23 | _date = parseInt(_date); 24 | localStorage.setItem("outBox", '{"index": []}'); 25 | localStorage.setItem("inBox", '{"index": []}'); 26 | localStorage.setItem("first-run", Date.now()); 27 | } 28 | // load dbs 29 | MessageData.inBox = JSON.parse(localStorage.getItem("inBox")); 30 | MessageData.outBox = JSON.parse(localStorage.getItem("outBox")); 31 | } 32 | 33 | function fetchMessages() 34 | { 35 | var url = "/fetch/?h=" + window.mozCipher.hash.SHA256(window.mozCipher.pk.getPublicKey()); 36 | // get all messages on server, store in localStorage, display list "inbox" 37 | $.get(url, function (aData){ 38 | if (aData.status == 'success') { 39 | displayMessages(aData.msg); 40 | } 41 | else { 42 | alert(aData.msg); 43 | } 44 | }); 45 | } 46 | 47 | function displayInBoxMessages() 48 | { 49 | // TODO load messages from MessageData.inBox 50 | } 51 | 52 | function displayMessages(aMessages) 53 | { 54 | // each message looks like 55 | // { hash: hsjfdshjdhjshjdhjsd, content: {...}, dateTime: 123456789 } 56 | // TODO: add TIME_ZONE to each message as it is composed 57 | var len = aMessages.length; 58 | var parent = $("#inbox-messages"); 59 | for (var i = 0; i < len; i++) { 60 | // save incoming message to localStorage 61 | var id = aMessages[i].id; 62 | MessageData.inBox[id] = aMessages[i]; 63 | var dt = aMessages[i].dateTime; 64 | var content = aMessages[i].content; 65 | var hash = aMessages[i].hash; 66 | var from = aMessages[i].from; 67 | var node = '
From ' + from + 70 | " @" + dt + 71 | '' + 72 | '
' + content + '
' + 73 | '
' +
 74 |                '
'; 75 | parent.prepend(node); 76 | } 77 | $("#msg-count").text(aMessages.length); 78 | // TODO: localStorage is slow, maybe move this to indexedDB 79 | // save messages to localStorage 80 | // MessageData.saveMessageBox("inBox"); 81 | } 82 | 83 | function displayMessage(aNode, aID) 84 | { 85 | // show the contents of the message 86 | aNode.parentNode.scrollIntoView(true); 87 | var messageContent = aNode.parentNode.childNodes[3].textContent; 88 | console.log(messageContent); 89 | var messageObj = JSON.parse(messageContent); 90 | try { 91 | var message = window.mozCipher.pk.promptDecrypt(messageObj); 92 | console.log(message); 93 | $("#msg-plain-" + aID)[0].innerHTML = message; 94 | } 95 | catch (ex) { 96 | alert(ex); 97 | } 98 | } 99 | 100 | function deliver() 101 | { 102 | encryptMessage(); 103 | send(); 104 | } 105 | 106 | function send(aMsg) 107 | { 108 | // post the message to the server, alert user of status 109 | if ($("#recipient-picker")[0].value && $("#cipher-text").text()) { 110 | var message = $("#cipher-text").text(); 111 | var recipient = $("#recipient-picker")[0].value; 112 | var pubKey = window.mozCipher.pk.getAddressbook()[recipient].pubKey; 113 | var hash = window.mozCipher.hash.SHA256(pubKey); 114 | var url = "/send/"; 115 | var csrf_token = $('#csrf_token >div >input').attr("value"); 116 | // TODO: add server SVC_KEY to all post and get requests 117 | $.post(url, 118 | { _hash: hash, 119 | _from: localStorage.getItem("handle"), 120 | message: message, 121 | recipient: recipient, 122 | csrfmiddlewaretoken: csrf_token }, 123 | function(aData){ 124 | if (aData.status == 'success') { 125 | // save the message to localStorage: 126 | var outMsg = { to: recipient, 127 | plain: $("#write-message")[0].value, 128 | cipher: $("#cipher-text").text() }; 129 | var _outMsg = JSON.stringify(outMsg); 130 | MessageData.outBox[aData.id] = _outMsg; 131 | MessageData.saveMessageBox("outBox"); 132 | 133 | $("#results").children().remove(); 134 | $("#results").append($('

Message Sent. Compose another message?

')); 135 | } 136 | else { 137 | alert("Could not send message"); 138 | } 139 | }); 140 | } 141 | } 142 | 143 | function encryptMessage() 144 | { 145 | if ($("#recipient-picker")[0].value && $("#write-message")[0].value) { 146 | var plainText = $("#write-message")[0].value; 147 | var recipient = $("#recipient-picker")[0].value; 148 | var addressbookEntry = window.mozCipher.pk.getAddressbook()[recipient]; 149 | var hash = window.mozCipher.hash.SHA256(addressbookEntry.pubKey); 150 | var message = window.mozCipher.pk.encrypt(plainText, addressbookEntry.pubKey); 151 | var messageText = JSON.stringify(message); 152 | $("#write-message").fadeOut("slow"); 153 | $("#cipher-text").text(messageText).fadeIn("slow"); 154 | } 155 | else { 156 | alert("You must select a recipient and enter a message"); 157 | } 158 | } 159 | 160 | function chooseRecipientUI() 161 | { 162 | // display UI to pick recipient from Addressbook 163 | // get addressbook entries: 164 | var entries = window.mozCipher.pk.getAddressbook(); 165 | var entriesArr = []; 166 | for (var prop in entries) { 167 | entriesArr.push(entries[prop]); 168 | } 169 | var picker = $(''); 170 | for (var i = 0; i < entriesArr.length; i++) { 171 | var opt = $(''); 173 | picker.append(opt); 174 | } 175 | 176 | $("#place-picker").children().remove(); 177 | $("#place-picker").append(picker); 178 | } 179 | 180 | function onLoad() 181 | { 182 | // setup the app... 183 | $("#go-messages").click(function () { 184 | var t = Date.now(); 185 | document.location = "/messages/?t=" + t; 186 | firstRun(); 187 | }); 188 | 189 | $("#go-home").click(function () { 190 | document.location = "/"; 191 | firstRun(); 192 | }); 193 | 194 | $("#go-compose").click(function () { 195 | var t = Date.now(); 196 | document.location = "/compose/?t=" + t; 197 | firstRun(); 198 | }); 199 | 200 | $("#send").click(function () { 201 | try { 202 | $("#send").attr("disabled", true); 203 | deliver(); 204 | } 205 | catch (ex) { 206 | $("#send").attr("disabled", false); 207 | } 208 | }); 209 | 210 | $("#go-addressbook").click(function () { 211 | document.location = "/addressbook/"; 212 | }); 213 | 214 | $("#go-addressbook-create").click(function () { 215 | document.location = "/create/addressbook/entry/"; 216 | }); 217 | 218 | if (window.location.pathname == "/messages/") { 219 | // check the mesages again 220 | fetchMessages(); 221 | } 222 | 223 | if (window.location.pathname == "/compose/") { 224 | // check the mesages again 225 | $("#select-contact").click(chooseRecipientUI); 226 | } 227 | 228 | if (window.location.pathname == "/") { 229 | // check for DOMCrypt and check for addressbook 230 | var _crypt = false; 231 | var _pubKey = false; 232 | var _addressBook = false; 233 | var loadMessage = []; 234 | if (!window.mozCipher) { 235 | loadMessage.push($("
You will need to install the DOMCrypt Extension to use this site
")); 236 | } 237 | else { 238 | _crypt = true; 239 | } 240 | 241 | if (window.mozCipher) { 242 | if (window.mozCipher.pk.getAddressbook() == {}) { 243 | loadMessage.push($("
Your local addressbook is empty, you will need to create an addressbook entry to use this site
")); 244 | } 245 | else { 246 | _addressBook = true; 247 | } 248 | } 249 | 250 | if (window.mozCipher) { 251 | if (window.mozCipher.pk.getPubKey()) { 252 | _pubKey = true; 253 | } 254 | } 255 | 256 | if (_pubKey && _crypt) { 257 | loadMessage.push($("
Your browser is setup to use this site :)
")); 258 | } 259 | if (loadMessage.length) { 260 | for (var i = 0; i < loadMessage.length; i++) { 261 | $("#setup-status-messages").append(loadMessage[i]); 262 | } 263 | } 264 | } 265 | 266 | if (window.location.pathname == "/addressbook/") { 267 | // set the addressbook search button onclick 268 | $("#search-addressbook").click(function(){ 269 | var handle = $("#enter-handle")[0].value; 270 | if (handle) { 271 | document.location = "/addressbook/" + handle + "/"; 272 | } 273 | else { 274 | alert("Please enter a Handle"); 275 | } 276 | }); 277 | } 278 | 279 | if (window.location.pathname == "/create/addressbook/entry/") { 280 | $("#create-message-password").click(function (){ 281 | // start generateKeypair() 282 | window.mozCipher.pk.generateKeypair(); 283 | window.setTimeout(function() { 284 | if (window.mozCipher.pk.getPublicKey()) { 285 | // we have a key, we can proceed 286 | alert("Your password was successfully created"); 287 | } 288 | else { 289 | alert("There was an error creating your password"); 290 | } 291 | } 292 | , 2000); 293 | }); 294 | 295 | $("#create-addressbook-entry").click(function() { 296 | 297 | // TODO: validate handle against regex 298 | var handle = $("#handle")[0].value; 299 | if (!handle) { 300 | alert("Please enter a handle"); 301 | return; 302 | } 303 | // check for a pubKey already created 304 | var pubKey = window.mozCipher.pk.getPublicKey(); 305 | if (pubKey) { 306 | // gather bits for addressbook entry: 307 | var csrf_token = $('#csrf_token >div >input').attr("value"); 308 | var addressbookInput = { 309 | pubKey: pubKey, 310 | handle: handle, 311 | hash: window.mozCipher.hash.SHA256(pubKey), 312 | date: parseInt(Date.now()), 313 | csrfmiddlewaretoken: csrf_token 314 | }; 315 | // XHR post this object 316 | var url = "/create/addressbook/entry/process/?handle=" + handle; 317 | $.post(url, addressbookInput, function(aData) { 318 | if (aData.status == "success") { 319 | // store Service Key in localStorage 320 | localStorage.setItem("SERVICE_KEY", aData.serviceKey); 321 | localStorage.setItem("ADDRESSBOOK_URL", aData.entryURL); 322 | localStorage.setItem("handle", handle); 323 | // show a confirmation that the addressbook entry was created 324 | $("#results").children().remove(); 325 | $("#results").text(aData.msg); 326 | $("#addressbook-url").attr({href: aData.entryURL}).text(aData.entryURL); 327 | $("#handle")[0].value = ""; 328 | } 329 | else { 330 | alert(aData.msg); 331 | } 332 | }); 333 | } 334 | else { 335 | // need to create the pubKey 336 | alert("In order to use Messages, you will need to protect the messages you recieve with a passphrase."); 337 | window.mozCipher.pk.generateKeypair(); 338 | $("#remind-repeat-click")[0].innerHTML = " <- Password Saved, please click again to create your Addressbook Entry "; 339 | } 340 | }); 341 | } 342 | } 343 | 344 | window.addEventListener("load", onLoad, false); 345 | 346 | $.ajaxSetup({ 347 | beforeSend: function(xhr, settings) { 348 | if (!(/^http:.*/.test(settings.url) || /^https:.*/.test(settings.url))) { 349 | // Only send the token to relative URLs i.e. locally. 350 | xhr.setRequestHeader("X-CSRFToken", 351 | $("#csrfmiddlewaretoken").val()); 352 | } 353 | } 354 | }); 355 | -------------------------------------------------------------------------------- /demos/dcserver/urls.py: -------------------------------------------------------------------------------- 1 | import os 2 | from django.conf.urls.defaults import * 3 | 4 | # Uncomment the next two lines to enable the admin: 5 | from django.contrib import admin 6 | admin.autodiscover() 7 | 8 | urlpatterns = patterns('dcserver.msgdrp.views', 9 | (r'^$', 'index'), 10 | (r'^fetch/$', 'fetch'), 11 | (r'^messages/$', 'messages'), 12 | (r'^compose/$', 'compose'), 13 | (r'^send/$', 'send'), 14 | (r'^create/addressbook/entry/$', 'begin_create_addressbook_entry'), 15 | (r'^create/addressbook/entry/process/$', 'process_addressbook_creation'), 16 | (r'^created/addressbook/entry/$', 'created_addressbook_entry'), 17 | (r'^addressbook/(?P[a-zA-Z0-9-_]+)/$', 'display_addressbook_entry'), 18 | (r'^addressbook/$', 'addressbook'), 19 | # Uncomment the next line to enable the admin: 20 | (r'^admin/', include(admin.site.urls)), 21 | ) 22 | 23 | urlpatterns += patterns('', 24 | (r'^site_media/(.*)$', 25 | 'django.views.static.serve', 26 | {'document_root': os.environ.get('DC_MEDIA_ROOT', 27 | '/home/ddahl/code/domcrypt/demos/dcserver/site_media/'), 28 | 'show_indexes': False}), 29 | ) 30 | -------------------------------------------------------------------------------- /demos/demo.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 19 | 20 | 21 |

DOMCrypt Demo

22 |
DOMCrypt is a Firefox extension that adds 'window.mozCipher' to each browser window. With 'mozCipher', you can generate a public and private key pair, encrypt data and decrypt data. All of the encryption operations are handled by low-level NSS libraries written in C. This is not a javascript-in-content solution. (NSS handles all of the SSL operations in many modern browsers.) 23 |
See https://github.com/daviddahl/domcrypt for source code
24 |

Try out the demo:

25 | 30 |
31 |
32 |

1. Generate the key pair

33 |
34 | 35 |
36 |

Public key

37 |
...
38 |
39 |
40 | 41 |
42 |
43 |

2. Encrypt the message

44 | 45 |

Encrypted text

46 |
...
47 |
48 | 49 |
50 |

3. Decrypt the cipher text

51 |

Decrypted text

52 | 53 |
54 |
55 |
Run the Addressbook Demo
56 |
Run the Sign and Verify Demo
57 |
58 |

Latest Developments

59 | 62 |
63 | 72 |
Last update: 2011-03-22
73 | 74 | 75 | -------------------------------------------------------------------------------- /demos/demo.js: -------------------------------------------------------------------------------- 1 | // DOMCrypt demo script 2 | 3 | window.addEventListener("load", function (){ 4 | // if the user already has a key pair - fetch the public key to display on this page 5 | try { 6 | document.getElementById("pubKey").innerHTML = window.mozCipher.pk.getPublicKey(); 7 | } 8 | catch (ex) { 9 | // noop 10 | } 11 | }, false); 12 | 13 | function getPubKey() 14 | { 15 | window.mozCipher.pk.getPublicKey(function (aPubKey){ 16 | document.getElementById("pubKey").innerHTML = aPubKey; 17 | }); 18 | } 19 | 20 | function generate() 21 | { 22 | // begins the key pair generation routine, user is prompted by chrome-privileged passphrase prompt 23 | window.mozCipher.pk.generateKeypair(function(aPubKey){ 24 | document.getElementById("pubKey").innerHTML = aPubKey; 25 | }); 26 | } 27 | 28 | function encrypt() 29 | { 30 | // encrypts the message with the current user's public key - this demo is quite simplistic in that there is only one user 31 | var msg = document.getElementById("plaintext").value; 32 | var pubKey = document.getElementById("pubKey").innerHTML; 33 | window.mozCipher.pk.encrypt(msg, pubKey, 34 | function (aCipherMessage) { 35 | document.currentMessage = aCipherMessage; 36 | document.getElementById("encrypted").innerHTML = aCipherMessage.content; 37 | }); 38 | } 39 | 40 | function decrypt() 41 | { 42 | window.mozCipher.pk.decrypt(document.currentMessage, function (aPlainText){ 43 | document.getElementById("decrypted").innerHTML = aPlainText; 44 | }); 45 | } 46 | 47 | function signMessage() 48 | { 49 | var msg = document.getElementById("message").textContent; 50 | window.mozCipher.pk.sign(msg, function (aSig){ 51 | document.getElementById("results").textContent = aSig; 52 | }); 53 | } 54 | 55 | function verifySignature() 56 | { 57 | var msgTxt = document.getElementById("message").innerHTML; 58 | var sig = document.getElementById("results").innerHTML; 59 | if (sig) { 60 | console.log(sig); 61 | window.mozCipher.pk.getPublicKey(function callback (aPubKey){ 62 | if (!aPubKey) { 63 | throw new Error("Verify Signature: Could not get the publicKey"); 64 | } 65 | console.log(aPubKey); 66 | window.mozCipher.pk.verify(msgTxt, sig, aPubKey, function verifyCallback(aVer){ 67 | console.log(aVer); 68 | if (aVer) { 69 | document.getElementById("results-verify").innerHTML = aVer.toString(); 70 | alert("The message signature has been verified as authentic"); 71 | } 72 | else { 73 | alert("ERROR: The message signature has NOT been verified as authentic"); 74 | } 75 | }); 76 | }); 77 | } 78 | else { 79 | document.getElementById("results-verify").innerHTML = "You must sign the message first"; 80 | } 81 | } 82 | 83 | function symEncrypt() 84 | { 85 | mozCipher.pk.getPublicKey(function (aPubKey){ 86 | console.log("public key: ", aPubKey); 87 | console.log("The public key is used to wrap the symmetric key after the data is encrypted"); 88 | var text = "It was a bright cold day in April"; 89 | console.log("encrypting: ", text); 90 | var callback = function (cipherObj){ 91 | document.symEncryptResults = { pubKey: aPubKey, 92 | cipherObj: cipherObj }; 93 | console.log("cipher text: "); 94 | console.log(cipherObj.cipherText); 95 | console.log("ok, time to decrypt"); 96 | symDecrypt(cipherObj); 97 | }; 98 | console.log(typeof callback); 99 | mozCipher.sym.encrypt(text, 100 | callback, 101 | aPubKey); 102 | }); 103 | } 104 | 105 | function symDecrypt(aCipherObject) 106 | { 107 | var cipherObj; 108 | if (aCipherObject) { 109 | cipherObj = aCipherObject; 110 | } 111 | else { 112 | cipherObj = document.symEncryptResults.cipherObj; 113 | } 114 | console.log("decrypting data..."); 115 | mozCipher.sym.decrypt(cipherObj, 116 | function (plainText){ 117 | console.log("plain text: "); 118 | console.log(plainText); 119 | }); 120 | } 121 | -------------------------------------------------------------------------------- /demos/get-pub-key.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 11 | 12 | 13 | 27 | 28 | 29 |

DOMCrypt Demo 2: Add a 'Contact' to an 'Addressbook'

30 |
See https://github.com/daviddahl/domcrypt for source code
31 |

Try out the demo:

32 | 37 | 38 |
39 | A 'contact' is the Public Key and associated data of one of the people you communicate with. This data is published on a web page inside of a meta tag, e.g:
<meta name="addressbook-entry" 
40 |             pubkey="MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1vW1laRyBkIfdeB2GQT+rz4dRwYUMtQJQ4Z8/QJCQj5qFuYKqcUn+8UssedWMjygRME1Eamcv5X5HLvphYMaRufk4PvKXLNq0Xh7cmNLcpQT639v+RjWpvHNWsdtYfd80nKCf1S46TlbH2/aw/+tcdLdj8MOTDtzII2oCcXU8B8PXNf49rcNMv8KagjC6LMQDrgvmZ56T1J3wHtQAH/QXGvh4WjQc2sWC/V+2xGkQL4+4yeP7STJBQXKmmqanExsqmwii1rV0Rd2GQnJRaSj+56HMDbZkLnZsxJExul5vu6ec+nBfACxWDMVCeVWbYxBpfURgC5nDsznkgT5VhXOJwIDAQAB",
41 |             handle="natasha",
42 |             domain="droplettr.com"
43 |             date="1298322911812">
44 | </meta>
45 |
46 |
47 |

DOMCrypt will detect this addressbook-entry tag

48 | The user is prompted to save the entry to the addressbook database, which is just a JSON file in the profile folder. 49 |
50 |
51 |

Getting the addressbook entries

52 |
window.pk.mozCipher.getAddressbook(function callback (addressbook){});
53 |

54 |

55 |       
[1] An approval and whitelist process for pages that request the public key and addressbook is planned
56 |
57 | 58 | 59 | -------------------------------------------------------------------------------- /demos/get-pub-key.js: -------------------------------------------------------------------------------- 1 | function getAddressbook() 2 | { 3 | window.mozCipher.pk.getAddressbook(function (ab){ 4 | var _ab = ""; 5 | for (var idx in ab) { 6 | _ab += idx + " " + "\n"; 7 | for (var _idx in ab[idx]) { 8 | _ab += _idx + ": " + ab[idx][_idx] + "\n"; 9 | } 10 | } 11 | document.getElementById('addressbook-data').innerHTML = _ab.toString(); 12 | }); 13 | } 14 | -------------------------------------------------------------------------------- /demos/sign.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 21 | 22 | 23 |

DOMCrypt Signing Demo

24 | 25 |
See https://github.com/daviddahl/domcrypt for source code
26 |
27 | Sign a message via NSS PK11_Sign() 28 |
29 | 34 |
35 |

Message to sign

36 |

This is a pretend super-s3kr3t m355ag3, for yr eyes only and stuff. This message will self destruct in 42 minutes.

37 | 38 |

Signature

39 |

40 |     
41 | 42 |
43 |

Verify Signature

44 |
45 | Verify a message was sent by sender via NSS PK11_Sign() 46 |
47 | 48 |

Results

49 |

50 |     
51 | 52 | 53 | -------------------------------------------------------------------------------- /extension/build.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | mv built/domcrypt.xpi /tmp 4 | cd domcrypt/ 5 | zip -r domcrypt.xpi * 6 | mv domcrypt.xpi ../built 7 | 8 | echo "the domcrypt .xpi was built" 9 | -------------------------------------------------------------------------------- /extension/built/domcrypt.xpi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daviddahl/domcrypt/e8238956626bd235657eeb00f2bc1bfb92c916e0/extension/built/domcrypt.xpi -------------------------------------------------------------------------------- /extension/domcrypt/chrome.manifest: -------------------------------------------------------------------------------- 1 | resource domcrypt content/ 2 | content domcrypt content/ 3 | locale domcrypt en-US locale/en-US/ 4 | overlay chrome://browser/content/browser.xul chrome://domcrypt/content/browser.xul 5 | manifest components/domcrypt.manifest 6 | -------------------------------------------------------------------------------- /extension/domcrypt/components/domcrypt.js: -------------------------------------------------------------------------------- 1 | /* ***** BEGIN LICENSE BLOCK ***** 2 | * Version: MPL 1.1/GPL 2.0/LGPL 2.1 3 | * 4 | * The contents of this file are subject to the Mozilla Public License Version 5 | * 1.1 (the "License"); you may not use this file except in compliance with 6 | * the License. You may obtain a copy of the License at 7 | * http://www.mozilla.org/MPL/ 8 | * 9 | * Software distributed under the License is distributed on an "AS IS" basis, 10 | * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 11 | * for the specific language governing rights and limitations under the 12 | * License. 13 | * 14 | * The Original Code is DOMCrypt API code. 15 | * 16 | * The Initial Developer of the Original Code is 17 | * the Mozilla Foundation. 18 | * Portions created by the Initial Developer are Copyright (C) 2011 19 | * the Initial Developer. All Rights Reserved. 20 | * 21 | * Contributor(s): 22 | * David Dahl (Original Author) 23 | * 24 | * Alternatively, the contents of this file may be used under the terms of 25 | * either the GNU General Public License Version 2 or later (the "GPL"), or 26 | * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 27 | * in which case the provisions of the GPL or the LGPL are applicable instead 28 | * of those above. If you wish to allow use of your version of this file only 29 | * under the terms of either the GPL or the LGPL, and not to allow others to 30 | * use your version of this file under the terms of the MPL, indicate your 31 | * decision by deleting the provisions above and replace them with the notice 32 | * and other provisions required by the GPL or the LGPL. If you do not delete 33 | * the provisions above, a recipient may use your version of this file under 34 | * the terms of any one of the MPL, the GPL or the LGPL. 35 | * 36 | * ***** END LICENSE BLOCK ***** */ 37 | 38 | let Cu = Components.utils; 39 | let Ci = Components.interfaces; 40 | let Cc = Components.classes; 41 | let Cr = Components.results; 42 | 43 | Cu.import("resource://gre/modules/XPCOMUtils.jsm"); 44 | Cu.import("resource://gre/modules/Services.jsm"); 45 | 46 | function log(aMessage) { 47 | var _msg = "DOMCryptAPI: " + aMessage + "\n"; 48 | dump(_msg); 49 | } 50 | 51 | XPCOMUtils.defineLazyGetter(this, "crypto", function (){ 52 | Cu.import("resource://domcrypt/DOMCryptMethods.jsm"); 53 | return DOMCryptMethods; 54 | }); 55 | 56 | XPCOMUtils.defineLazyGetter(this, "promptSvc", function() { 57 | return Cc["@mozilla.org/embedcomp/prompt-service;1"].getService(Ci.nsIPromptService); 58 | }); 59 | 60 | XPCOMUtils.defineLazyGetter(this, "Addressbook", function (){ 61 | Cu.import("resource://domcrypt/addressbookManager.js"); 62 | return addressbook; 63 | }); 64 | 65 | /** 66 | * DOMCrypt/mozCipher API 67 | * 68 | * This is a shim (nsIDOMGlobalPropertyInitializer) object that wraps the 69 | * DOMCryptMethods.jsm 'crypto' object 70 | * 71 | * DOMCrypt's init method returns the API that is content JS accessible. 72 | * 73 | * DOMCryptAPI imports DOMCryptMethods, DOMCryptMethods generates the ChromeWorker 74 | * that runs all WeaveCrypto (NSS) functions off main thread via ctypes 75 | */ 76 | 77 | function DOMCryptAPI() {} 78 | 79 | DOMCryptAPI.prototype = { 80 | 81 | classID: Components.ID("{66af630d-6d6d-4d29-9562-9f1de90c1798}"), 82 | 83 | QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMGlobalPropertyInitializer, 84 | Ci.nsIObserver,]), 85 | 86 | sandbox: null, 87 | 88 | /** 89 | * We must free the sandbox and window references every time an 90 | * innerwindow is destroyed 91 | * TODO: must listen for back/forward events to reinstate the window object 92 | * 93 | * @param object aSubject 94 | * @param string aTopic 95 | * @param string aData 96 | * 97 | * @returns void 98 | */ 99 | observe: function DA_observe(aSubject, aTopic, aData) 100 | { 101 | if (aTopic == "inner-window-destroyed") { 102 | let windowID = aSubject.QueryInterface(Ci.nsISupportsPRUint64).data; 103 | let innerWindowID = this.window.QueryInterface(Ci.nsIInterfaceRequestor). 104 | getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID; 105 | if (windowID == innerWindowID) { 106 | crypto.shutdown(); 107 | delete this.sandbox; 108 | delete this.window; 109 | Services.obs.removeObserver(this, "inner-window-destroyed"); 110 | } 111 | } 112 | }, 113 | 114 | /** 115 | * This method sets up the crypto API and returns the object that is 116 | * accessible from the DOM 117 | * 118 | * @param nsIDOMWindow aWindow 119 | * @returns object 120 | * The object returned is the API object called 'window.mozCipher' 121 | */ 122 | init: function DA_init(aWindow) { 123 | 124 | let self = this; 125 | 126 | this.window = XPCNativeWrapper.unwrap(aWindow); 127 | 128 | this.sandbox = Cu.Sandbox(this.window, 129 | { sandboxPrototype: this.window, wantXrays: false }); 130 | 131 | // we need a xul window reference for the DOMCryptMethods 132 | this.xulWindow = aWindow.QueryInterface(Ci.nsIDOMWindow) 133 | .QueryInterface(Ci.nsIInterfaceRequestor) 134 | .getInterface(Ci.nsIWebNavigation) 135 | .QueryInterface(Ci.nsIDocShellTreeItem) 136 | .rootTreeItem 137 | .QueryInterface(Ci.nsIInterfaceRequestor) 138 | .getInterface(Ci.nsIDOMWindow) 139 | .QueryInterface(Ci.nsIDOMChromeWindow); 140 | 141 | crypto.setXULWindow(this.xulWindow); 142 | 143 | Services.obs.addObserver(this, "inner-window-destroyed", false); 144 | 145 | let api = { 146 | 147 | // "pk": Public Key encryption namespace 148 | pk: { 149 | encrypt: self.encrypt.bind(self), 150 | decrypt: self.promptDecrypt.bind(self), 151 | sign: self.sign.bind(self), 152 | verify: self.verify.bind(self), 153 | generateKeypair: self.beginGenerateKeypair.bind(self), 154 | getPublicKey: self.getPublicKey.bind(self), 155 | getAddressbook: self.getAddressbook.bind(self), 156 | __exposedProps__: { 157 | generateKeypair: "r", 158 | getPublicKey: "r", 159 | encrypt: "r", 160 | decrypt: "r", 161 | sign: "r", 162 | verify: "r", 163 | } 164 | }, 165 | 166 | sym: { 167 | generateKey: self.generateSymKey.bind(self), 168 | wrapKey: self.wrapKey.bind(self), 169 | encrypt: self.symEncrypt.bind(self), 170 | decrypt: self.symDecrypt.bind(self), 171 | __exposedProps__: { 172 | generateKey: "r", 173 | wrapKey: "r", 174 | encrypt: "r", 175 | decrypt: "r", 176 | }, 177 | }, 178 | 179 | hash: { 180 | SHA256: self.SHA256.bind(self), 181 | __exposedProps__: { 182 | SHA256: "r", 183 | }, 184 | }, 185 | 186 | __exposedProps__: { 187 | pk: "r", 188 | sym: "r", 189 | hash: "r", 190 | }, 191 | }; 192 | 193 | return api; 194 | }, 195 | 196 | getAddressbook: function DAPI_getAddressbook(aCallback) 197 | { 198 | if (!(typeof aCallback == "function")) { 199 | let exception = 200 | new Components.Exception("First argument should be a function", 201 | Cr.NS_ERROR_INVALID_ARG, 202 | Components.stack.caller); 203 | throw exception; 204 | } 205 | // TODO: whitelist urls before giving access to the addressbook 206 | let contacts = Addressbook.getContactsObj(); 207 | crypto.getAddressbook(contacts, aCallback, this.sandbox); 208 | }, 209 | 210 | // Symmetric API 211 | 212 | generateSymKey: function DA_generateSymKey(aCallback, aPublicKey) 213 | { 214 | // XXXddahl: should maybe have a time created/modified and SHA256 hash of the 215 | // public key for the utility of it 216 | if (!(typeof aCallback == "function")) { 217 | let exception = 218 | new Components.Exception("First argument should be a function", 219 | Cr.NS_ERROR_INVALID_ARG, 220 | Components.stack.caller); 221 | throw exception; 222 | } 223 | 224 | var pubKey; 225 | if (typeof aPublicKey == "string") { 226 | pubKey = aPublicKey; 227 | } 228 | else { 229 | pubKey = null; 230 | } 231 | crypto.generateSymKey(aCallback, pubKey, this.sandbox); 232 | }, 233 | 234 | /** 235 | * re-wrap the symmetric key inside a CryptoObject to allow other 236 | * keypairs access to the encrypted content 237 | * A CryptoObject is either some encrypted data with associated wrapped symKey 238 | * or, just a symKey object 239 | * 240 | * @param object aCipherObject 241 | * @param string aPublicKey 242 | * @param function aCallback 243 | * @returns void 244 | */ 245 | wrapKey: function DA_wrapKey(aCipherObject, aPublicKey, aCallback) 246 | { 247 | // XXXddahl: also accept an array of public keys in case we are 248 | // updating a single cipher object many times over?? Or create wrapKeys()?? 249 | // unwrap, then re-wrap the cipher object's symmetric key with a new publicKey 250 | 251 | if ((!aCipherObject.iv) || (!aCipherObject.wrappedKey) || 252 | (!aCipherObject.pubKey)) { 253 | // this is not a stand-alone key or a cipher object, reject it 254 | let exception = 255 | new Components.Exception("Invalid input: First argument is not a Symmetric Key or Cipher Object", 256 | Cr.NS_ERROR_INVALID_ARG, 257 | Components.stack.caller); 258 | throw exception; 259 | } 260 | 261 | if (!(typeof aCallback == "function")) { 262 | let exception = 263 | new Components.Exception("Third argument should be a function", 264 | Cr.NS_ERROR_INVALID_ARG, 265 | Components.stack.caller); 266 | throw exception; 267 | } 268 | // we don't re-encrypt anything, we just unwrap and then wrap the key 269 | crypto.wrapKey(aCipherObject, aPublicKey, aCallback, this.sandbox); 270 | }, 271 | 272 | // aPublicKey is optional: the current user's pub key is used by default 273 | // to protect the symmetric key 274 | symEncrypt: function DA_symEncrypt(aPlainText, aCallback, aPublicKey) 275 | { 276 | // XXXddahl: add aSymmetricKey as an optional arg to allow for independent 277 | // key generation - it may also be that generateSymKey is unneeded as this 278 | // method is starting to look more complicated than wanted 279 | if (!(typeof aCallback == "function")) { 280 | let exception = 281 | new Components.Exception("Second argument should be a function", 282 | Cr.NS_ERROR_INVALID_ARG, 283 | Components.stack.caller); 284 | throw exception; 285 | } 286 | 287 | if (!(typeof aPlainText == "string")) { 288 | let exception = 289 | new Components.Exception("First argument should be a String", 290 | Cr.NS_ERROR_INVALID_ARG, 291 | Components.stack.caller); 292 | throw exception; 293 | } 294 | 295 | var pubKey; 296 | if (typeof aPublicKey == "string") { 297 | pubKey = aPublicKey; 298 | } 299 | else { 300 | pubKey = null; 301 | } 302 | crypto.symEncrypt(aPlainText, pubKey, aCallback, this.sandbox); 303 | }, 304 | 305 | symDecrypt: function DA_symDecrypt(aCipherObject, aCallback) 306 | { 307 | if (!(typeof aCallback == "function")) { 308 | let exception = 309 | new Components.Exception("Second argument should be a function", 310 | Cr.NS_ERROR_INVALID_ARG, 311 | Components.stack.caller); 312 | throw exception; 313 | } 314 | 315 | if (!(typeof aCipherObject == "object")) { 316 | let exception = 317 | new Components.Exception("First argument should be a CipherObject", 318 | Cr.NS_ERROR_INVALID_ARG, 319 | Components.stack.caller); 320 | throw exception; 321 | } 322 | crypto.symDecrypt(aCipherObject, aCallback, this.sandbox); 323 | }, 324 | 325 | /** 326 | * Prompt the enduser to create a keypair 327 | * 328 | * @param function aCallback 329 | * This callback will run in the content sandbox when the operation 330 | * is complete 331 | * @returns void 332 | */ 333 | beginGenerateKeypair: function DA_beginGenerateKeypair(aCallback) 334 | { 335 | if (!(typeof aCallback == "function")) { 336 | let exception = 337 | new Components.Exception("First argument should be a function", 338 | Cr.NS_ERROR_INVALID_ARG, 339 | Components.stack.caller); 340 | throw exception; 341 | } 342 | // this is a prompt-driven routine. 343 | // passphrase is typed in, confirmed, then the generation begins 344 | crypto.beginGenerateKeypair(aCallback, this.sandbox); 345 | }, 346 | 347 | /** 348 | * A wrapper that calls DOMCryptMethods.encrypt() 349 | * 350 | * @param string aPlainText 351 | * A string that will be encrypted 352 | * @param string aPublicKey 353 | * The public key of the recipient of the encrypted text 354 | * @param function aCallback 355 | * This callback will run in the content sandbox when the operation 356 | * is complete 357 | * @returns void 358 | */ 359 | encrypt: function DA_encrypt(aPlainText, aPublicKey, aCallback) 360 | { 361 | if (!(typeof aCallback == "function")) { 362 | let exception = 363 | new Components.Exception("Third argument should be a Function", 364 | Cr.NS_ERROR_INVALID_ARG, 365 | Components.stack.caller); 366 | throw exception; 367 | } 368 | if (!(typeof aPlainText == "string") || !(typeof aPublicKey == "string")) { 369 | let exception = 370 | new Components.Exception("First and second arguments should be Strings", 371 | Cr.NS_ERROR_INVALID_ARG, 372 | Components.stack.caller); 373 | throw exception; 374 | } 375 | crypto.encrypt(aPlainText, aPublicKey, aCallback, this.sandbox); 376 | }, 377 | 378 | /** 379 | * A wrapper that calls DOMCryptMethods.decrypt() 380 | * 381 | * @param object aCipherMessage 382 | * An object literal much like: 383 | * { content: , 384 | * pubKey: , 385 | * wrappedKey: , 386 | * iv: 387 | * } 388 | * @param function aCallback 389 | * This callback will run in the content sandbox when the operation 390 | * is complete 391 | * @returns void 392 | */ 393 | promptDecrypt: function DA_promptDecrypt(aCipherMessage, aCallback) 394 | { 395 | if (!(typeof aCallback == "function")) { 396 | let exception = 397 | new Components.Exception("Second argument should be a Function", 398 | Cr.NS_ERROR_INVALID_ARG, 399 | Components.stack.caller); 400 | throw exception; 401 | } 402 | if (!(typeof aCipherMessage == "object")) { 403 | let exception = 404 | new Components.Exception("First argument should be a mozCipher Message Object", 405 | Cr.NS_ERROR_INVALID_ARG, 406 | Components.stack.caller); 407 | throw exception; 408 | } 409 | 410 | crypto.promptDecrypt(aCipherMessage, aCallback, this.sandbox); 411 | }, 412 | 413 | /** 414 | * A wrapper that calls DOMCryptMethods.getPublicKey() 415 | * 416 | * @param function aCallback 417 | * This callback will run in the content sandbox when the operation 418 | * is complete 419 | * @returns void 420 | */ 421 | getPublicKey: function DA_getPublicKey(aCallback) 422 | { 423 | if (!(typeof aCallback == "function")) { 424 | let exception = 425 | new Components.Exception("First argument should be a Function", 426 | Cr.NS_ERROR_INVALID_ARG, 427 | Components.stack.caller); 428 | throw exception; 429 | } 430 | crypto.getPublicKey(aCallback, this.sandbox); 431 | }, 432 | 433 | /** 434 | * A wrapper that calls DOMCryptMethods.sign() 435 | * 436 | * @param string aMessage 437 | * The plaintext message before it is encrypted 438 | * @param function aCallback 439 | * This callback will run in the content sandbox when the operation 440 | * is complete 441 | * @returns void 442 | */ 443 | sign: function DA_sign(aMessage, aCallback) 444 | { 445 | if (!(typeof aMessage == "string")) { 446 | let exception = 447 | new Components.Exception("First argument should be a String", 448 | Cr.NS_ERROR_INVALID_ARG, 449 | Components.stack.caller); 450 | throw exception; 451 | } 452 | 453 | if (!(typeof aCallback == "function")) { 454 | let exception = 455 | new Components.Exception("Second argument should be a Function", 456 | Cr.NS_ERROR_INVALID_ARG, 457 | Components.stack.caller); 458 | throw exception; 459 | } 460 | crypto.sign(aMessage, aCallback, this.sandbox); 461 | }, 462 | 463 | /** 464 | * A wrapper that calls DOMCryptMethods.verify() 465 | * 466 | * @param string aMessage 467 | * A plaintext decrypted message 468 | * @param string aSignature 469 | * The signature of the encrypted message 470 | * @param string aPublicKey 471 | * The recipient's public key 472 | * @param function aCallback 473 | * This callback will run in the content sandbox when the operation 474 | * is complete 475 | * @returns void 476 | */ 477 | verify: function DA_verify(aMessage, aSignature, aPublicKey, aCallback) 478 | { 479 | if (!(typeof aMessage == "string")) { 480 | let exception = 481 | new Components.Exception("First argument (aMessage) should be a String", 482 | Cr.NS_ERROR_INVALID_ARG, 483 | Components.stack.caller); 484 | throw exception; 485 | } 486 | 487 | if (!(typeof aSignature == "string")) { 488 | let exception = 489 | new Components.Exception("Second argument (aSignature) should be a String", 490 | Cr.NS_ERROR_INVALID_ARG, 491 | Components.stack.caller); 492 | throw exception; 493 | } 494 | 495 | if (!(typeof aPublicKey == "string")) { 496 | let exception = 497 | new Components.Exception("Third argument (aPublicKey) should be a String", 498 | Cr.NS_ERROR_INVALID_ARG, 499 | Components.stack.caller); 500 | throw exception; 501 | } 502 | 503 | if (!(typeof aCallback == "function")) { 504 | let exception = 505 | new Components.Exception("Fourth argument should be a Function", 506 | Cr.NS_ERROR_INVALID_ARG, 507 | Components.stack.caller); 508 | throw exception; 509 | } 510 | crypto.verify(aMessage, aSignature, aPublicKey, aCallback, this.sandbox); 511 | }, 512 | 513 | /** 514 | * A wrapper that calls DOMCryptMethods.verifyPassphrase() 515 | * 516 | * @param function aCallback 517 | * This callback will run in the content sandbox when the operation 518 | * is complete 519 | * @returns void 520 | */ 521 | verifyPassphrase: function DA_verifyPassphrase(aCallback) 522 | { 523 | if (!(typeof aCallback == "function")) { 524 | let exception = 525 | new Components.Exception("First argument should be a Function", 526 | Cr.NS_ERROR_INVALID_ARG, 527 | Components.stack.caller); 528 | throw exception; 529 | } 530 | crypto.verifyPassphrase(aCallback, this.sandbox); 531 | }, 532 | 533 | /** 534 | * A wrapper that calls DOMCryptMethods.SHA256() 535 | * 536 | * @param string aPlainText 537 | * The plaintext string to be hashed 538 | * @param function aCallback 539 | * This callback will run in the content sandbox when the operation 540 | * is complete 541 | * @returns void 542 | */ 543 | SHA256: function DA_SHA256(aPlainText, aCallback) 544 | { 545 | if (!(typeof aPlainText == "string")) { 546 | let exception = 547 | new Components.Exception("First argument (aPlainText) should be a String", 548 | Cr.NS_ERROR_INVALID_ARG, 549 | Components.stack.caller); 550 | throw exception; 551 | } 552 | 553 | if (!(typeof aCallback == "function")) { 554 | let exception = 555 | new Components.Exception("Second argument should be a Function", 556 | Cr.NS_ERROR_INVALID_ARG, 557 | Components.stack.caller); 558 | throw exception; 559 | } 560 | crypto.SHA256(aPlainText, aCallback, this.sandbox); 561 | }, 562 | }; 563 | 564 | 565 | var NSGetFactory = XPCOMUtils.generateNSGetFactory([DOMCryptAPI]); 566 | 567 | -------------------------------------------------------------------------------- /extension/domcrypt/components/domcrypt.manifest: -------------------------------------------------------------------------------- 1 | component {66af630d-6d6d-4d29-9562-9f1de90c1798} domcrypt.js 2 | contract @droplettr.com/domcrypt;1 {66af630d-6d6d-4d29-9562-9f1de90c1798} 3 | category JavaScript-global-property mozCipher @droplettr.com/domcrypt;1 4 | category app-startup domcrypt @droplettr.com/domcrypt;1 5 | -------------------------------------------------------------------------------- /extension/domcrypt/content/DOMCryptMethods.jsm: -------------------------------------------------------------------------------- 1 | /* ***** BEGIN LICENSE BLOCK ***** 2 | * Version: MPL 1.1/GPL 2.0/LGPL 2.1 3 | * 4 | * The contents of this file are subject to the Mozilla Public License Version 5 | * 1.1 (the "License"); you may not use this file except in compliance with 6 | * the License. You may obtain a copy of the License at 7 | * http://www.mozilla.org/MPL/ 8 | * 9 | * Software distributed under the License is distributed on an "AS IS" basis, 10 | * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 11 | * for the specific language governing rights and limitations under the 12 | * License. 13 | * 14 | * The Original Code is DOMCrypt API code. 15 | * 16 | * The Initial Developer of the Original Code is 17 | * the Mozilla Foundation. 18 | * Portions created by the Initial Developer are Copyright (C) 2011 19 | * the Initial Developer. All Rights Reserved. 20 | * 21 | * Contributor(s): 22 | * David Dahl (Original Author) 23 | * 24 | * Alternatively, the contents of this file may be used under the terms of 25 | * either the GNU General Public License Version 2 or later (the "GPL"), or 26 | * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 27 | * in which case the provisions of the GPL or the LGPL are applicable instead 28 | * of those above. If you wish to allow use of your version of this file only 29 | * under the terms of either the GPL or the LGPL, and not to allow others to 30 | * use your version of this file under the terms of the MPL, indicate your 31 | * decision by deleting the provisions above and replace them with the notice 32 | * and other provisions required by the GPL or the LGPL. If you do not delete 33 | * the provisions above, a recipient may use your version of this file under 34 | * the terms of any one of the MPL, the GPL or the LGPL. 35 | * 36 | * ***** END LICENSE BLOCK ***** */ 37 | 38 | let Cu = Components.utils; 39 | let Ci = Components.interfaces; 40 | let Cc = Components.classes; 41 | 42 | Cu.import("resource://gre/modules/XPCOMUtils.jsm"); 43 | Cu.import("resource://gre/modules/Services.jsm"); 44 | Cu.import("resource://gre/modules/NetUtil.jsm"); 45 | Cu.import("resource://gre/modules/FileUtils.jsm"); 46 | Cu.import("resource://gre/modules/ctypes.jsm"); 47 | 48 | XPCOMUtils.defineLazyServiceGetter(this, "promptSvc", 49 | "@mozilla.org/embedcomp/prompt-service;1", 50 | "nsIPromptService"); 51 | 52 | XPCOMUtils.defineLazyServiceGetter(this, "secretDecoderRing", 53 | "@mozilla.org/security/sdr;1", 54 | "nsISecretDecoderRing"); 55 | 56 | var PASSPHRASE_TTL = 3600000; 57 | 58 | const CONFIG_FILE_PATH = ".mozCipher.json"; 59 | const PROFILE_DIR = "ProfD"; 60 | const STRINGS_URI = "chrome://domcrypt/locale/domcrypt.properties";; 61 | 62 | XPCOMUtils.defineLazyGetter(this, "stringBundle", function () { 63 | return Services.strings.createBundle(STRINGS_URI); 64 | }); 65 | 66 | /** 67 | * This string object keeps track of all of the string names used here 68 | */ 69 | const MOZ_CIPHER_STRINGS = { 70 | enterPassphraseTitle: "enterPassphraseTitle", 71 | enterPassphraseText: "enterPassphraseText", 72 | confirmPassphraseTitle: "confirmPassphraseTitle", 73 | confirmPassphraseText: "confirmPassphraseText", 74 | passphrasesDoNotMatchTitle: "passphrasesDoNotMatchTitle", 75 | passphrasesDoNotMatchText: "passphrasesDoNotMatchText", 76 | signErrorTitle: "signErrorTitle", 77 | signErrorMessage: "signErrorMessage", 78 | noPassphraseEntered: "noPassphraseEntered", 79 | }; 80 | 81 | /** 82 | * Memoize and return all strings used by this JSM 83 | */ 84 | function _stringStorage(aName) { } 85 | 86 | _stringStorage.prototype = { 87 | 88 | /** 89 | * Internally memoizes and gets the string via aName 90 | * 91 | * @param string aName 92 | * @returns string 93 | */ 94 | getStr: function SS_getStr(aName) { 95 | if (MOZ_CIPHER_STRINGS[aName]) { 96 | if (this[aName]) { 97 | return this[aName]; 98 | } 99 | else { 100 | this[aName] = stringBundle.GetStringFromName(aName); 101 | return this[aName]; 102 | } 103 | } 104 | else { 105 | Cu.reportError("Cannot get " + aName + " from stringBundle"); 106 | return ""; 107 | } 108 | }, 109 | }; 110 | 111 | // Initialize the stringStorage object 112 | var stringStorage = new _stringStorage(); 113 | 114 | /** 115 | * StringBundle shortcut function 116 | * 117 | * @param string aName 118 | * @returns string 119 | */ 120 | function getStr(aName) 121 | { 122 | return stringStorage.getStr(aName); 123 | } 124 | 125 | function log(aMessage) { 126 | var _msg = "*** DOMCryptMethods: " + aMessage + "\n"; 127 | dump(_msg); 128 | } 129 | 130 | var EXPORTED_SYMBOLS = ["DOMCryptMethods"]; 131 | 132 | // A new blank configuration object 133 | var BLANK_CONFIG_OBJECT = { 134 | default: { 135 | created: null, 136 | privKey: null, 137 | pubKey: null, 138 | salt: null, 139 | iv: null 140 | } 141 | }; 142 | 143 | // A blank configuration object as a string 144 | var BLANK_CONFIG_OBJECT_STR = "{default: {created: null,privKey: null,pubKey: null,salt: null,iv: null}};"; 145 | 146 | // We use NSS for the crypto ops, which needs to be initialized before 147 | // use. By convention, PSM is required to be the module that 148 | // initializes NSS. So, make sure PSM is initialized in order to 149 | // implicitly initialize NSS. 150 | Cc["@mozilla.org/psm;1"].getService(Ci.nsISupports); 151 | 152 | // We can call ChromeWorkers from this JSM 153 | XPCOMUtils.defineLazyGetter(this, "worker", function (){ 154 | return new ChromeWorker("domcrypt_worker.js"); 155 | }); 156 | 157 | const KEYPAIR_GENERATED = "keypairGenerated"; 158 | const DATA_ENCRYPTED = "dataEncrypted"; 159 | const DATA_DECRYPTED = "dataDecrypted"; 160 | const MESSAGE_SIGNED = "messageSigned"; 161 | const MESSAGE_VERIFIED = "messageVerified"; 162 | const SYM_KEY_GENERATED = "symKeyGenerated"; 163 | const SYM_ENCRYPTED = "symEncrypted"; 164 | const SYM_DECRYPTED = "symDecrypted"; 165 | const SYM_KEY_WRAPPED = "symKeyWrapped"; 166 | const SHA256_COMPLETE = "SHA256Complete"; 167 | const PASSPHRASE_VERIFIED = "passphraseVerified"; 168 | const WORKER_ERROR = "error"; 169 | 170 | worker.onmessage = function DCM_worker_onmessage(aEvent) { 171 | switch (aEvent.data.action) { 172 | case KEYPAIR_GENERATED: 173 | Callbacks.handleGenerateKeypair(aEvent.data.keypairData); 174 | break; 175 | case DATA_ENCRYPTED: 176 | Callbacks.handleEncrypt(aEvent.data.cipherMessage); 177 | break; 178 | case DATA_DECRYPTED: 179 | Callbacks.handleDecrypt(aEvent.data.plainText); 180 | break; 181 | case MESSAGE_SIGNED: 182 | Callbacks.handleSign(aEvent.data.signature); 183 | break; 184 | case MESSAGE_VERIFIED: 185 | Callbacks.handleVerify(aEvent.data.verification); 186 | break; 187 | case SYM_KEY_GENERATED: 188 | Callbacks.handleGenerateSymKey(aEvent.data.wrappedKeyObject); 189 | break; 190 | case SYM_ENCRYPTED: 191 | Callbacks.handleSymEncrypt(aEvent.data.cipherObject); 192 | break; 193 | case SYM_DECRYPTED: 194 | Callbacks.handleSymDecrypt(aEvent.data.plainText); 195 | break; 196 | case SYM_KEY_WRAPPED: 197 | Callbacks.handleWrapSymKey(aEvent.data.cipherObject); 198 | break; 199 | case SHA256_COMPLETE: 200 | Callbacks.handleSHA256(aEvent.data.hashedString); 201 | break; 202 | case PASSPHRASE_VERIFIED: 203 | Callbacks.handleVerifyPassphrase(aEvent.data.verification); 204 | case WORKER_ERROR: 205 | if (aEvent.data.notify) { 206 | notifyUser(aEvent.data); 207 | } 208 | default: 209 | break; 210 | } 211 | }; 212 | 213 | worker.onerror = function DCM_onerror(aError) { 214 | log("Worker Error: " + aError.message); 215 | log("Worker Error filename: " + aError.filename); 216 | log("Worker Error line no: " + aError.lineno); 217 | }; 218 | 219 | // Constants to describe all operations 220 | const GENERATE_KEYPAIR = "generateKeypair"; 221 | const ENCRYPT = "encrypt"; 222 | const DECRYPT = "decrypt"; 223 | const SIGN = "sign"; 224 | const VERIFY = "verify"; 225 | const VERIFY_PASSPHRASE = "verifyPassphrase"; 226 | const GENERATE_SYM_KEY = "generateSymKey"; 227 | const SYM_ENCRYPT = "symEncrypt"; 228 | const SYM_DECRYPT = "symDecrypt"; 229 | const WRAP_SYM_KEY = "wrapSymKey"; 230 | const GET_PUBLIC_KEY = "getPublicKey"; 231 | const SHA256 = "SHA256"; 232 | const GET_ADDRESSBOOK = "getAddressbook"; 233 | const INITIALIZE_WORKER = "init"; 234 | 235 | /** 236 | * DOMCryptMethods 237 | * 238 | * This Object handles all input from content scripts via the DOMCrypt 239 | * nsIDOMGlobalPropertyInitializer and sends calls to the Worker that 240 | * handles all NSS calls 241 | * 242 | * The basic work flow: 243 | * 244 | * A content script calls one of the DOMCrypt window API methods, at minimum, 245 | * a callback function is passed into the window API method. 246 | * 247 | * The window API method calls the corresponding method in this JSM 248 | * (DOMCryptMethods), which sets up the callback and sandbox. 249 | * 250 | * The DOMCryptMethod API calls into the ChromeWorker which initializes NSS and 251 | * provides the js-ctypes wrapper obejct which is a slightly edited and expanded 252 | * WeaveCrypto Object. 253 | * 254 | * The crypto operations are run in the worker, and the return value sent back to 255 | * the DOMCryptMethods object via a postMessage. 256 | * 257 | * DOMCryptMethods' onmessage chooses which callback to execute in the original 258 | * content window's sandbox. 259 | */ 260 | var DOMCryptMethods = { 261 | 262 | xullWindow: null, 263 | 264 | setXULWindow: function DCM_setXULWindow(aWindow) 265 | { 266 | this.xulWindow = aWindow; 267 | }, 268 | 269 | /** 270 | * The config object that is created by reading the contents of 271 | * /.mozCipher.json 272 | */ 273 | config: BLANK_CONFIG_OBJECT, 274 | 275 | /** 276 | * Initialize the DOMCryptMethods object: set the callback and 277 | * configuration objects 278 | * 279 | * @param Object aConfigObject 280 | * @param String aSharedObjectPath 281 | * The full path to the NSS shared object 282 | * @param String aSharedObjectName 283 | * The name of the NSS shared object 284 | * @returns void 285 | */ 286 | init: function DCM_init(aConfigObject, aSharedObjectPath, aSharedObjectName) 287 | { 288 | this.config = aConfigObject; 289 | worker.postMessage({ action: INITIALIZE_WORKER, 290 | fullPath: aSharedObjectPath, 291 | libName: aSharedObjectName }); 292 | }, 293 | 294 | /** 295 | * Remove all references to windows on window close or browser shutdown 296 | * 297 | * @returns void 298 | */ 299 | shutdown: function DCM_shutdown() 300 | { 301 | worker.postMessage({ action: "shutdown" }); 302 | 303 | this.sandbox = null; 304 | this.xulWindow = null; 305 | 306 | for (let prop in Callbacks) { 307 | Callbacks[prop].callback = null; 308 | Callbacks[prop].sandbox = null; 309 | } 310 | Callbacks = null; 311 | }, 312 | 313 | callbacks: null, 314 | 315 | ///////////////////////////////////////////////////////////////////////// 316 | // DOMCrypt API methods exposed via the nsIDOMGlobalPropertyInitializer 317 | ///////////////////////////////////////////////////////////////////////// 318 | 319 | /** 320 | * Begin the generate keypair process 321 | * 322 | * 1. Prompt user for passphrase and confirm passphrase 323 | * 2. Pass the passphrase off to the worker to generate a keypair 324 | * 325 | * @returns void 326 | */ 327 | beginGenerateKeypair: function DCM_beginGenerateKeypair(aCallback, aSandbox) 328 | { 329 | // TODO: check if the user already has a keypair and confirm they 330 | // would like to overwrite it 331 | 332 | Callbacks.register(GENERATE_KEYPAIR, aCallback, aSandbox); 333 | 334 | let passphrase = {}; 335 | let prompt = 336 | promptSvc.promptPassword(Callbacks.generateKeypair.sandbox.window, 337 | getStr("enterPassphraseTitle"), 338 | getStr("enterPassphraseText"), 339 | passphrase, null, { value: false }); 340 | if (prompt && passphrase.value) { 341 | let passphraseConfirm = {}; 342 | let prompt = 343 | promptSvc.promptPassword(Callbacks.generateKeypair.sandbox.window, 344 | getStr("confirmPassphraseTitle"), 345 | getStr("confirmPassphraseText"), 346 | passphraseConfirm, 347 | null, { value: false }); 348 | if (prompt && passphraseConfirm.value && 349 | (passphraseConfirm.value == passphrase.value)) { 350 | this.generateKeypair(passphrase.value); 351 | } 352 | else { 353 | promptSvc.alert(Callbacks.generateKeypair.sandbox.window, 354 | getStr("passphrasesDoNotMatchTitle"), 355 | getStr("passphrasesDoNotMatchText")); 356 | } 357 | } 358 | }, 359 | 360 | /** 361 | * The internal 'generateKeypair' method that calls the worker 362 | * 363 | * @param string aPassphrase 364 | * @returns void 365 | */ 366 | generateKeypair: function DCM_generateKeypair(aPassphrase) 367 | { 368 | worker.postMessage({ action: GENERATE_KEYPAIR, passphrase: aPassphrase }); 369 | this.passphraseCache.encryptedPassphrase = secretDecoderRing.encryptString(aPassphrase); 370 | this.passphraseCache.lastEntered = Date.now(); 371 | 372 | }, 373 | 374 | /** 375 | * The internal 'getPublicKey' method 376 | * 377 | * @returns void 378 | */ 379 | getPublicKey: function DCM_getPublicKey(aCallback, aSandbox) 380 | { 381 | Callbacks.register(GET_PUBLIC_KEY, aCallback, aSandbox); 382 | // TODO: need a gatekeeper function/prompt to allow access to your publicKey 383 | // TODO: looks like we can get this async via FileUtils 384 | Callbacks.handleGetPublicKey(this.config.default.pubKey); 385 | }, 386 | 387 | /** 388 | * The internal 'encrypt' method which calls the worker to do the encrypting 389 | * 390 | * @param string aPlainText 391 | * @param string aPublicKey 392 | * @param function aCallback 393 | * @param sandbox aSandbox 394 | * @returns void 395 | */ 396 | encrypt: function DCM_encrypt(aPlainText, aPublicKey, aCallback, aSandbox) 397 | { 398 | Callbacks.register(ENCRYPT, aCallback, aSandbox); 399 | 400 | worker.postMessage({ action: ENCRYPT, 401 | pubKey: aPublicKey, 402 | plainText: aPlainText 403 | }); 404 | }, 405 | 406 | /** 407 | * The internal 'decrypt' method which calls the worker to do the decrypting 408 | * 409 | * @param Object aCipherMessage 410 | * @param string aPassphrase 411 | * @returns void 412 | */ 413 | decrypt: 414 | function DCM_decrypt(aCipherMessage, aPassphrase) 415 | { 416 | 417 | let userIV = secretDecoderRing.decryptString(this.config.default.iv); 418 | let userSalt = secretDecoderRing.decryptString(this.config.default.salt); 419 | let userPrivKey = this.config.default.privKey; 420 | let cipherMessage = XPCNativeWrapper.unwrap(aCipherMessage); 421 | 422 | worker.postMessage({ action: DECRYPT, 423 | // cipherMessage: aCipherMessage, 424 | cipherContent: cipherMessage.content, 425 | cipherWrappedKey: cipherMessage.wrappedKey, 426 | cipherPubKey: cipherMessage.pubKey, 427 | cipherIV: cipherMessage.iv, 428 | passphrase: aPassphrase, 429 | privKey: userPrivKey, 430 | salt: userSalt, 431 | iv: userIV 432 | }); 433 | }, 434 | 435 | passphraseCache: { 436 | encryptedPassphrase: null, 437 | lastEntered: null, 438 | }, 439 | 440 | /** 441 | * Get the passphrase 442 | * 443 | * @returns string 444 | */ 445 | get passphrase() { 446 | let passphrase = this.checkPassphraseCache(); 447 | return passphrase; 448 | }, 449 | 450 | /** 451 | * Check to see if the cached (encrypted) passphrase needs to be re-entered 452 | * 453 | * @returns void 454 | */ 455 | checkPassphraseCache: function DCM_checkPassphraseCache() 456 | { 457 | let passphrase; 458 | // check if the passphrase has ever been entered 459 | if (!this.passphraseCache.encryptedPassphrase) { 460 | passphrase = this.enterPassphrase(); 461 | } 462 | // check if the passphrase is outdated and needs to be re-entered 463 | else if ((Date.now() - this.passphraseCache.lastEntered) > PASSPHRASE_TTL) { 464 | passphrase = this.enterPassphrase(); 465 | } 466 | else { 467 | return secretDecoderRing.decryptString(this.passphraseCache.encryptedPassphrase); 468 | } 469 | return passphrase; 470 | }, 471 | 472 | /** 473 | * Enter the passphrase via a prompt 474 | * 475 | * @returns void 476 | */ 477 | enterPassphrase: function DCM_enterPassphrase() 478 | { 479 | // accept the passphrase and store it in memory - encrypted via SDR 480 | // remember the passphrase for 1 hour 481 | let passphrase = {}; 482 | let prompt = promptSvc.promptPassword(this.xulWindow, 483 | getStr("enterPassphraseTitle"), 484 | getStr("enterPassphraseText"), 485 | passphrase, null, { value: false }); 486 | if (passphrase.value) { 487 | // TODO validate passphrase 488 | this.passphraseCache.encryptedPassphrase = 489 | secretDecoderRing.encryptString(passphrase.value); 490 | this.passphraseCache.lastEntered = Date.now(); 491 | return passphrase.value; 492 | } 493 | else { 494 | throw new Error(getStr("noPassphraseEntered")); 495 | } 496 | }, 497 | 498 | /** 499 | * Make sure the passphrase is the one used to generate the keypair 500 | * 501 | * @param function aCallback 502 | * @param sandbox aSandbox 503 | * @returns void 504 | */ 505 | verifyPassphrase: function DCM_verifyPassphrase(aCallback, aSandbox) 506 | { 507 | Callbacks.register(VERIFY_PASSPHRASE, aCallback, aSandbox); 508 | let passphrase = this.passphrase; 509 | let userPrivKey = this.config.default.privKey; 510 | let userIV = secretDecoderRing.decryptString(this.config.default.iv); 511 | let userSalt = secretDecoderRing.decryptString(this.config.default.salt); 512 | 513 | worker.postMessage({ action: VERIFY_PASSPHRASE, 514 | privKey: userPrivKey, 515 | passphrase: passphrase, 516 | salt: userSalt, 517 | iv: userIV 518 | }); 519 | }, 520 | 521 | /** 522 | * Prompt the user for a passphrase to begin the decryption process 523 | * 524 | * @param object aCipherMessage 525 | * @param function aCallback 526 | * @param sandbox aSandbox 527 | * @returns void 528 | */ 529 | promptDecrypt: function DCM_promptDecrypt(aCipherMessage, aCallback, aSandbox) 530 | { 531 | Callbacks.register(DECRYPT, aCallback, aSandbox); 532 | let passphrase = this.passphrase; 533 | 534 | if (passphrase) { 535 | this.decrypt(aCipherMessage, passphrase); 536 | return; 537 | } 538 | 539 | throw new Error(getStr("noPassphraseEntered")); 540 | }, 541 | 542 | /** 543 | * Front-end 'sign' method prompts user for passphrase then 544 | * calls the internal _sign message 545 | * 546 | * @param string aPlainTextMessage 547 | * @param function aCallback 548 | * @param sandbox aSandbox 549 | * @returns void 550 | */ 551 | sign: function DCM_sign(aPlainTextMessage, aCallback, aSandbox) 552 | { 553 | Callbacks.register(SIGN, aCallback, aSandbox); 554 | let passphrase = this.passphrase; 555 | if (passphrase) { 556 | this._sign(aPlainTextMessage, passphrase); 557 | } 558 | else { 559 | throw new Error(getStr("noPassphraseEntered")); 560 | } 561 | }, 562 | 563 | /** 564 | * Internal backend '_sign' method calls the worker to do the actual signing 565 | * 566 | * @param string aPlainTextMessage 567 | * @param string aPassphrase 568 | * @returns void 569 | */ 570 | _sign: function DCM__sign(aPlainTextMessage, aPassphrase) 571 | { 572 | let userIV = secretDecoderRing.decryptString(this.config.default.iv); 573 | let userSalt = secretDecoderRing.decryptString(this.config.default.salt); 574 | let userPrivKey = this.config.default.privKey; 575 | let hash = this._SHA256(aPlainTextMessage); 576 | 577 | worker.postMessage({ action: SIGN, 578 | hash: hash, 579 | passphrase: aPassphrase, 580 | iv: userIV, 581 | salt: userSalt, 582 | privKey: userPrivKey 583 | }); 584 | }, 585 | 586 | /** 587 | * The 'verify' method which calls the worker to do signature verification 588 | * 589 | * @param string aPlainTextMessage 590 | * @param string aSignature 591 | * @param string aPublicKey 592 | * @param function aCallback 593 | * @param sandbox aSandbox 594 | * @returns void 595 | */ 596 | verify: 597 | function 598 | DCM_verify(aPlainTextMessage, aSignature, aPublicKey, aCallback, aSandbox) 599 | { 600 | Callbacks.register(VERIFY, aCallback, aSandbox); 601 | let hash = this._SHA256(aPlainTextMessage); 602 | 603 | // Create a hash in the worker for verification 604 | worker.postMessage({ action: VERIFY, 605 | hash: hash, 606 | signature: aSignature, 607 | pubKey: aPublicKey 608 | }); 609 | }, 610 | 611 | generateSymKey: function DCM_generateSymKey(aCallback, aPublicKey, aSandbox) 612 | { 613 | Callbacks.register(GENERATE_SYM_KEY, aCallback, aSandbox); 614 | 615 | var pubKey; 616 | if (!aPublicKey) { 617 | pubKey = this.config.default.pubKey; 618 | } 619 | else { 620 | pubKey = aPublicKey; 621 | } 622 | 623 | worker.postMessage({ action: GENERATE_SYM_KEY, 624 | pubKey: pubKey 625 | }); 626 | }, 627 | 628 | wrapKey: function DCM_wrapKey(aCipherObject, aPublicKey, aCallback, aSandbox) 629 | { 630 | // unwrap then re-wrap the symmetric key inside aCipherObject, return a new 631 | // cipherObject that can be unlocked by another keypair 632 | Callbacks.register(WRAP_SYM_KEY, aCallback, aSandbox); 633 | 634 | let passphrase = this.passphrase; 635 | var userIV = secretDecoderRing.decryptString(this.config.default.iv); 636 | var userSalt = secretDecoderRing.decryptString(this.config.default.salt); 637 | var userPrivKey = this.config.default.privKey; 638 | 639 | var cipherObj = XPCNativeWrapper.unwrap(aCipherObject); 640 | var cipherText = null; 641 | if (cipherObj.cipherText) { 642 | cipherText = cipherObj.cipherText; 643 | } 644 | 645 | worker.postMessage({ action: WRAP_SYM_KEY, 646 | // cipherObject: cipherObj, 647 | cipherText: cipherText, 648 | cipherWrappedKey: cipherObj.wrappedKey, 649 | cipherPubKey: cipherObj.pubKey, 650 | cipherIV: cipherObj.iv, 651 | iv: userIV, 652 | salt: userSalt, 653 | privKey: userPrivKey, 654 | passphrase: passphrase, 655 | pubKey: aPublicKey 656 | }); 657 | }, 658 | 659 | /** 660 | * SymEncrypt (symmetric) 661 | * @param string aPlaintext 662 | * @param string aPublicKey 663 | * @param function aCallback 664 | * @param sandbox aSandbox 665 | * @returns void 666 | */ 667 | symEncrypt: function DCM_SymEncrypt(aPlainText, aPublicKey, aCallback, aSandbox) 668 | { 669 | Callbacks.register(SYM_ENCRYPT, aCallback, aSandbox); 670 | 671 | var pubKey; 672 | if (!aPublicKey) { 673 | pubKey = this.config.default.pubKey; 674 | } 675 | else { 676 | pubKey = aPublicKey; 677 | } 678 | 679 | worker.postMessage({ action: SYM_ENCRYPT, 680 | plainText: aPlainText, 681 | pubKey: pubKey 682 | }); 683 | }, 684 | 685 | symDecrypt: 686 | function DCM_SymDecrypt(aCipherObject, aCallback, aSandbox) 687 | { 688 | var passphrase = this.passphrase; // this getter will throw if nothing entered 689 | var userIV = secretDecoderRing.decryptString(this.config.default.iv); 690 | var userSalt = secretDecoderRing.decryptString(this.config.default.salt); 691 | var userPrivKey = this.config.default.privKey; 692 | 693 | Callbacks.register(SYM_DECRYPT, aCallback, aSandbox); 694 | 695 | var cipherObj = XPCNativeWrapper.unwrap(aCipherObject); 696 | 697 | worker.postMessage({ action: SYM_DECRYPT, 698 | // cipherObject: cipherObj, 699 | // XXX: work around for bug 667388 700 | cipherText: cipherObj.cipherText, 701 | cipherWrappedKey: cipherObj.wrappedKey, 702 | cipherPubKey: cipherObj.pubKey, 703 | cipherIV: cipherObj.iv, 704 | iv: userIV, 705 | salt: userSalt, 706 | privKey: userPrivKey, 707 | passphrase: passphrase 708 | }); 709 | }, 710 | 711 | /** 712 | * This is the internal SHA256 hash function, it does the actual hashing 713 | * 714 | * @param string aPlainText 715 | * @returns string 716 | */ 717 | _SHA256: function DCM__SHA256(aPlainText) 718 | { 719 | // stolen from weave/util.js 720 | let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]. 721 | createInstance(Ci.nsIScriptableUnicodeConverter); 722 | converter.charset = "UTF-8"; 723 | 724 | let hasher = Cc["@mozilla.org/security/hash;1"]. 725 | createInstance(Ci.nsICryptoHash); 726 | hasher.init(hasher.SHA256); 727 | 728 | let data = converter.convertToByteArray(aPlainText, {}); 729 | hasher.update(data, data.length); 730 | let rawHash = hasher.finish(false); 731 | 732 | // return the two-digit hexadecimal code for a byte 733 | function toHexString(charCode) { 734 | return ("0" + charCode.toString(16)).slice(-2); 735 | } 736 | 737 | let hash = [toHexString(rawHash.charCodeAt(i)) for (i in rawHash)].join(""); 738 | return hash; 739 | }, 740 | 741 | /** 742 | * SHA256 API hash function 743 | * This is synchronous for the time being. TODO: wrap NSS SHA* functions 744 | * with js-ctypes so we can run in a worker 745 | * 746 | * @param string aPlainTextMessage 747 | * @returns void 748 | */ 749 | SHA256: function DCM_SHA256(aPlainText, aCallback, aSandbox) 750 | { 751 | Callbacks.register(SHA256, aCallback, aSandbox); 752 | let hash = this._SHA256(aPlainText); 753 | let callback = Callbacks.makeSHA256Callback(hash); 754 | let sandbox = Callbacks.SHA256.sandbox; 755 | sandbox.importFunction(callback, "SHA256Callback"); 756 | Cu.evalInSandbox("SHA256Callback();", sandbox, "1.8", "DOMCrypt", 1); 757 | }, 758 | 759 | getAddressbook: function DCM_getAddressbook(aAddressbook, aCallback, aSandbox) 760 | { 761 | // XXX: we are faking async here 762 | Callbacks.register(GET_ADDRESSBOOK, aCallback, aSandbox); 763 | let callback = Callbacks.makeGetAddressbookCallback(aAddressbook); 764 | let sandbox = Callbacks.getAddressbook.sandbox; 765 | sandbox.importFunction(callback, "getAddressbookCallback"); 766 | Cu.evalInSandbox("getAddressbookCallback();", sandbox, "1.8", "DOMCrypt", 1); 767 | }, 768 | 769 | 770 | /** 771 | * Get the configuration file from the filesystem. 772 | * The file is a JSON file in the user's profile named ".mozCipher.json" 773 | * @param boolean aFileCreated 774 | * @returns nsIFile 775 | */ 776 | configurationFile: function DCM_configFile(aFileCreated) 777 | { 778 | // get profile directory 779 | let file = FileUtils.getFile(PROFILE_DIR, [CONFIG_FILE_PATH], true); 780 | if (!file.exists()) { 781 | file.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0600); 782 | aFileCreated.value = true; 783 | } 784 | else { 785 | aFileCreated.value = false; 786 | } 787 | return file; 788 | }, 789 | 790 | /** 791 | * write an updated or new configuration to /.mozCipher.json 792 | * 793 | * @param Object aConfigObj 794 | * @returns void 795 | */ 796 | writeConfigurationToDisk: function DCM_writeConfigurationToDisk(aConfigObj) 797 | { 798 | if (!aConfigObj) { 799 | throw new Error("aConfigObj is null"); 800 | } 801 | 802 | let data; 803 | 804 | if (typeof aConfigObj == "object") { 805 | // convert aConfigObj to JSON string 806 | data = JSON.stringify(aConfigObj); 807 | } 808 | else { 809 | data = aConfigObj; 810 | } 811 | let foStream = Cc["@mozilla.org/network/file-output-stream;1"]. 812 | createInstance(Ci.nsIFileOutputStream); 813 | let fileCreated = {}; 814 | let file = this.configurationFile(fileCreated); 815 | 816 | // use 0x02 | 0x10 to open file for appending. 817 | foStream.init(file, 0x02 | 0x08 | 0x20, 0666, 0); 818 | let converter = Cc["@mozilla.org/intl/converter-output-stream;1"]. 819 | createInstance(Ci.nsIConverterOutputStream); 820 | converter.init(foStream, "UTF-8", 0, 0); 821 | converter.writeString(data); 822 | converter.close(); 823 | }, 824 | 825 | config: BLANK_CONFIG_OBJECT, 826 | }; 827 | 828 | /** 829 | * Creates a unique callback registry for each DOMCryptMethods object 830 | * 831 | * @returns Object 832 | */ 833 | function GenerateCallbackObject() { 834 | log("GenerateCallbackObject() constructor"); 835 | } 836 | 837 | GenerateCallbackObject.prototype = { 838 | 839 | encrypt: { callback: null, sandbox: null }, 840 | 841 | decrypt: { callback: null, sandbox: null }, 842 | 843 | generateKeypair: { callback: null, sandbox: null }, 844 | 845 | getPublicKey: { callback: null, sandbox: null }, 846 | 847 | sign: { callback: null, sandbox: null }, 848 | 849 | verify: { callback: null, sandbox: null }, 850 | 851 | verifyPassphrase: { callback: null, sandbox: null }, 852 | 853 | generateSymKey: { callback: null, sandbox: null }, 854 | 855 | symEncrypt: { callback: null, sandbox: null }, 856 | 857 | symDecrypt: { callback: null, sandbox: null }, 858 | 859 | wrapSymKey: { callback: null, sandbox: null }, 860 | 861 | SHA256: { callback: null, sandbox: null }, 862 | 863 | getAddressbook: { callback: null, sandbox: null }, 864 | 865 | sandbox: null, 866 | 867 | /** 868 | * Register a callback for any API method 869 | * 870 | * @param string aLabel 871 | * @param function aCallback 872 | * @param Object aSandbox 873 | * @returns void 874 | */ 875 | register: function GCO_register(aLabel, aCallback, aSandbox) 876 | { 877 | // we need a 'fall back' sandbox for prompts, etc. when we are unsure what 878 | // method is in play 879 | this.sandbox = aSandbox; 880 | 881 | this[aLabel].callback = aCallback; 882 | this[aLabel].sandbox = aSandbox; 883 | }, 884 | 885 | /** 886 | * wrap the content-provided script in order to make it easier 887 | * to import and run in the sandbox 888 | * 889 | * @param string aPubKey 890 | * @returns function 891 | */ 892 | makeGenerateKeypairCallback: 893 | function DA_makeGenerateKeypairCallback(aPubKey) 894 | { 895 | let self = this; 896 | let callback = function generateKeypair_callback() 897 | { 898 | self.generateKeypair.callback(aPubKey); 899 | }; 900 | return callback; 901 | }, 902 | 903 | /** 904 | * Wraps the content callback script, imports it into the sandbox and 905 | * calls it in the sandbox 906 | * @param Object aKeypair 907 | * @returns void 908 | */ 909 | handleGenerateKeypair: function GCO_handleGenerateKeypair(aKeypairData) 910 | { 911 | // set memory config data from generateKeypair 912 | DOMCryptMethods.config.default.pubKey = aKeypairData.pubKey; 913 | DOMCryptMethods.config.default.privKey = aKeypairData.privKey; 914 | DOMCryptMethods.config.default.created = aKeypairData.created; 915 | DOMCryptMethods.config.default.iv = 916 | secretDecoderRing.encryptString(aKeypairData.iv); 917 | DOMCryptMethods.config.default.salt = 918 | secretDecoderRing.encryptString(aKeypairData.salt); 919 | 920 | // make a string of the config 921 | let strConfig = JSON.stringify(DOMCryptMethods.config); 922 | // write the new config to disk 923 | DOMCryptMethods.writeConfigurationToDisk(strConfig); 924 | // XXXddahl: This function is not working properly 925 | // writeConfigObjectToDisk(strConfig); 926 | 927 | let sandbox = this.generateKeypair.sandbox; 928 | let callback = this.makeGenerateKeypairCallback(aKeypairData.pubKey); 929 | sandbox.importFunction(callback, "generateKeypairCallback"); 930 | Cu.evalInSandbox("generateKeypairCallback();", sandbox, "1.8", "DOMCrypt", 1); 931 | }, 932 | 933 | /** 934 | * Create the callback that will be called after getting the publicKey 935 | * 936 | * @param string aPublicKey 937 | * @returns void 938 | */ 939 | makeGetPublicKeyCallback: function GCO_makeGetPublicKeyCallback(aPublicKey) 940 | { 941 | let self = this; 942 | let callback = function getPublicKey_callback() 943 | { 944 | self.getPublicKey.callback(aPublicKey); 945 | }; 946 | return callback; 947 | }, 948 | 949 | /** 950 | * Wraps the content callback script which deals with getting the publicKey 951 | * 952 | * @param string aPublicKey 953 | * @returns void 954 | */ 955 | handleGetPublicKey: function GCO_handleGetPublicKey(aPublicKey) 956 | { 957 | let callback = this.makeGetPublicKeyCallback(aPublicKey); 958 | let sandbox = this.getPublicKey.sandbox; 959 | sandbox.importFunction(callback, "getPublicKeyCallback"); 960 | Cu.evalInSandbox("getPublicKeyCallback();", 961 | sandbox, "1.8", "DOMCrypt", 1); 962 | }, 963 | 964 | /** 965 | * wrap the content-provided encrypt callback script in order to make it easier 966 | * to import and run in the sandbox 967 | * 968 | * @param Object aCipherMessage 969 | * @returns JS function 970 | */ 971 | makeEncryptCallback: 972 | function DA_encryptCallback(aCipherMessage) 973 | { 974 | let self = this; 975 | let callback = function encrypt_callback() 976 | { 977 | self.encrypt.callback(aCipherMessage); 978 | }; 979 | return callback; 980 | }, 981 | 982 | /** 983 | * Wraps the content callback script which deals with encrypted message objects 984 | * 985 | * @param Object aCipherMessage 986 | * @returns void 987 | */ 988 | handleEncrypt: function GCO_handleEncrypt(aCipherMessage) 989 | { 990 | log("handleEncrypt..."); 991 | log(aCipherMessage); 992 | log(this.encrypt.sandbox); 993 | aCipherMessage.__exposedProps__ = { 994 | iv: "r", 995 | content: "r", 996 | pubKey: "r", 997 | wrappedKey: "r", 998 | }; 999 | 1000 | let callback = this.makeEncryptCallback(aCipherMessage); 1001 | let sandbox = this.encrypt.sandbox; 1002 | sandbox.importFunction(callback, "encryptCallback"); 1003 | Cu.evalInSandbox("encryptCallback();", 1004 | sandbox, "1.8", "DOMCrypt", 1); 1005 | }, 1006 | 1007 | /** 1008 | * wrap the content-provided decrypt callback script in order to make it easier 1009 | * to import and run in the sandbox 1010 | * 1011 | * @param string aPlainText 1012 | * @returns JS function 1013 | */ 1014 | makeDecryptCallback: 1015 | function DA_decryptCallback(aPlainText) 1016 | { 1017 | let self = this; 1018 | let callback = function decrypt_callback() 1019 | { 1020 | self.decrypt.callback(aPlainText); 1021 | }; 1022 | return callback; 1023 | }, 1024 | 1025 | /** 1026 | * Wraps the content callback script which deals with the decrypted string 1027 | * 1028 | * @param string aPlainText 1029 | * @returns void 1030 | */ 1031 | handleDecrypt: function GCO_handleDecrypt(aPlainText) 1032 | { 1033 | let callback = this.makeDecryptCallback(aPlainText); 1034 | let sandbox = this.decrypt.sandbox; 1035 | sandbox.importFunction(callback, "decryptCallback"); 1036 | Cu.evalInSandbox("decryptCallback();", 1037 | sandbox, "1.8", "DOMCrypt", 1); 1038 | }, 1039 | 1040 | /** 1041 | * Wraps the content callback script which deals with the signature 1042 | * 1043 | * @param string aSignature 1044 | * @returns void 1045 | */ 1046 | makeSignCallback: function GCO_makeSignCallback(aSignature) 1047 | { 1048 | let self = this; 1049 | let callback = function sign_callback() 1050 | { 1051 | self.sign.callback(aSignature); 1052 | }; 1053 | return callback; 1054 | }, 1055 | 1056 | /** 1057 | * Executes the signature callback function in the sandbox 1058 | * 1059 | * @param string aSignature 1060 | * @returns void 1061 | */ 1062 | handleSign: function GCO_handleSign(aSignature) 1063 | { 1064 | let callback = this.makeSignCallback(aSignature); 1065 | let sandbox = this.sign.sandbox; 1066 | sandbox.importFunction(callback, "signCallback"); 1067 | Cu.evalInSandbox("signCallback();", 1068 | sandbox, "1.8", "DOMCrypt", 1); 1069 | }, 1070 | 1071 | /** 1072 | * Wraps the content callback script which deals with the signature verification 1073 | * 1074 | * @param boolean aVerification 1075 | * @returns void 1076 | */ 1077 | makeVerifyCallback: function GCO_makeVerifyCallback(aVerification) 1078 | { 1079 | let self = this; 1080 | let callback = function verify_callback() 1081 | { 1082 | self.verify.callback(aVerification); 1083 | }; 1084 | return callback; 1085 | }, 1086 | 1087 | /** 1088 | * Executes the verification callback function in the sandbox 1089 | * 1090 | * @param boolean aVerification 1091 | * @returns void 1092 | */ 1093 | handleVerify: function GCO_handleVerify(aVerification) 1094 | { 1095 | let callback = this.makeVerifyCallback(aVerification); 1096 | let sandbox = this.verify.sandbox; 1097 | sandbox.importFunction(callback, "verifyCallback"); 1098 | Cu.evalInSandbox("verifyCallback();", 1099 | sandbox, "1.8", "DOMCrypt", 1); 1100 | }, 1101 | 1102 | /** 1103 | * Executes the generateSymKey callback function in the sandbox 1104 | * 1105 | * @param boolean aWrappedSymKey 1106 | * @returns void 1107 | */ 1108 | makeGenerateSymKeyCallback: 1109 | function GCO_makeGenerateSymKeyCallback(aWrappedSymKeyObj) 1110 | { 1111 | let self = this; 1112 | let callback = function genSymKey_callback() 1113 | { 1114 | self.generateSymKey.callback(aWrappedSymKeyObj); 1115 | }; 1116 | return callback; 1117 | }, 1118 | 1119 | /** 1120 | * Executes the generateSymKey callback function in the sandbox 1121 | * 1122 | * @param string aWrappedSymKey 1123 | * @returns void 1124 | */ 1125 | handleGenerateSymKey: function GCO_handleGenerateSymKey(aWrappedSymKeyObj) 1126 | { 1127 | let callback = this.makeGenerateSymKeyCallback(aWrappedSymKeyObj); 1128 | let sandbox = this.generateSymKey.sandbox; 1129 | sandbox.importFunction(callback, "generateSymKeyCallback"); 1130 | Cu.evalInSandbox("generateSymKeyCallback();", 1131 | sandbox, "1.8", "DOMCrypt", 1); 1132 | }, 1133 | 1134 | /** 1135 | * Wraps the SymEncrypt callback function in the sandbox 1136 | * 1137 | * @param object aCipherObject 1138 | * @returns void 1139 | */ 1140 | makeSymEncryptCallback: 1141 | function GCO_makeSymEncryptCallback(aCipherObject) 1142 | { 1143 | let self = this; 1144 | let callback = function makeSymEncrypt_callback() 1145 | { 1146 | self.symEncrypt.callback(aCipherObject); 1147 | }; 1148 | return callback; 1149 | }, 1150 | 1151 | /** 1152 | * Executes the SymEncrypt callback function in the sandbox 1153 | * 1154 | * @param object aCipherObject 1155 | * @returns void 1156 | */ 1157 | handleSymEncrypt: function GCO_handleSymEncryptCallback(aCipherObject) 1158 | { 1159 | aCipherObject.__exposedProps__ = { 1160 | iv: "r", 1161 | cipherText: "r", 1162 | pubKey: "r", 1163 | wrappedKey: "r", 1164 | }; 1165 | let callback = this.makeSymEncryptCallback(aCipherObject); 1166 | let sandbox = this.symEncrypt.sandbox; 1167 | sandbox.importFunction(callback, "symEncryptCallback"); 1168 | Cu.evalInSandbox("symEncryptCallback();", 1169 | sandbox, "1.8", "DOMCrypt", 1); 1170 | }, 1171 | 1172 | 1173 | 1174 | /** 1175 | * Wraps the SymDecrypt callback function in the sandbox 1176 | * 1177 | * @param string aPlainText 1178 | * @returns void 1179 | */ 1180 | makeSymDecryptCallback: 1181 | function GCO_makeSymDecryptCallback(aPlainText) 1182 | { 1183 | let self = this; 1184 | let callback = function makeSymDecrypt_callback() 1185 | { 1186 | self.symDecrypt.callback(aPlainText); 1187 | }; 1188 | return callback; 1189 | }, 1190 | 1191 | /** 1192 | * Executes the SymDecrypt callback function in the sandbox 1193 | * 1194 | * @param string aPlainText 1195 | * @returns void 1196 | */ 1197 | handleSymDecrypt: function GCO_handleSymDecrypt(aPlainText) 1198 | { 1199 | let callback = this.makeSymDecryptCallback(aPlainText); 1200 | let sandbox = this.symDecrypt.sandbox; 1201 | sandbox.importFunction(callback, "symDecryptCallback"); 1202 | Cu.evalInSandbox("symDecryptCallback();", 1203 | sandbox, "1.8", "DOMCrypt", 1); 1204 | }, 1205 | 1206 | 1207 | 1208 | 1209 | /** 1210 | * Wraps the wrapSymKey callback function in the sandbox 1211 | * 1212 | * @param object aCipherObject 1213 | * @returns void 1214 | */ 1215 | makeWrapSymKeyCallback: 1216 | function GCO_makeWrapSymKeyCallback(aCipherObject) 1217 | { 1218 | let self = this; 1219 | let callback = function makeWrapSymKey_callback() 1220 | { 1221 | self.wrapSymKey.callback(aCipherObject); 1222 | }; 1223 | return callback; 1224 | }, 1225 | 1226 | /** 1227 | * Executes the wrapSymKey callback function in the sandbox 1228 | * 1229 | * @param object aCipherObject 1230 | * @returns void 1231 | */ 1232 | handleWrapSymKey: function GCO_handleWrapSymKey(aCipherObject) 1233 | { 1234 | aCipherObject.__exposedProps__ = { 1235 | iv: "r", 1236 | cipherText: "r", 1237 | pubKey: "r", 1238 | wrappedKey: "r", 1239 | }; 1240 | let callback = this.makeWrapSymKeyCallback(aCipherObject); 1241 | let sandbox = this.wrapSymKey.sandbox; 1242 | sandbox.importFunction(callback, "wrapSymKeyCallback"); 1243 | Cu.evalInSandbox("wrapSymKeyCallback();", 1244 | sandbox, "1.8", "DOMCrypt", 1); 1245 | }, 1246 | 1247 | /** 1248 | * Wraps the content callback script which deals with SHA256 hashing 1249 | * 1250 | * @param string aHash 1251 | * @returns void 1252 | */ 1253 | makeSHA256Callback: function GCO_makeSHA256Callback(aHash) 1254 | { 1255 | let callback = function hash256_callback() 1256 | { 1257 | this.SHA256.callback(aHash); 1258 | }; 1259 | return callback.bind(this); 1260 | // Note: we don't need a handleSHA256Callback function as there is 1261 | // no round trip to the worker yet, we are using the callback in the 1262 | // same manner in order to mock an async API for the time being 1263 | }, 1264 | 1265 | makeGetAddressbookCallback: function GCO_makeGetAddressbookCallback(aAddressbook) 1266 | { 1267 | let callback = function _callback() 1268 | { 1269 | this.getAddressbook.callback(aAddressbook); 1270 | }; 1271 | return callback.bind(this); 1272 | // Note: we don't need a handleGetAddressbookCallback function as there is 1273 | // no round trip to the worker yet, we are using the callback in the 1274 | // same manner in order to mock an async API for the time being 1275 | }, 1276 | 1277 | /** 1278 | * Wraps the content callback script which deals with passphrase verification 1279 | * 1280 | * @param boolean aVerification 1281 | * @returns void 1282 | */ 1283 | makeVerifyPassphraseCallback: 1284 | function GCO_makeVerifyPassphraseCallback(aVerification) 1285 | { 1286 | let self = this; 1287 | let callback = function verify_callback() 1288 | { 1289 | self.verifyPassphrase.callback(aVerification); 1290 | }; 1291 | return callback; 1292 | }, 1293 | 1294 | /** 1295 | * Executes the verifyPassphrase callback function in the sandbox 1296 | * 1297 | * @param boolean aVerification 1298 | * @returns void 1299 | */ 1300 | handleVerifyPassphrase: 1301 | function GCO_handleVerifyPassphrase(aVerification) 1302 | { 1303 | let callback = this.makeVerifyPassphraseCallback(aVerification); 1304 | let sandbox = this.verifyPassphrase.sandbox; 1305 | sandbox.importFunction(callback, "verifyPassphraseCallback"); 1306 | Cu.evalInSandbox("verifyPassphraseCallback();", 1307 | sandbox, "1.8", "DOMCrypt", 1); 1308 | }, 1309 | }; 1310 | 1311 | 1312 | var Callbacks = new GenerateCallbackObject(); 1313 | 1314 | /** 1315 | * Initialize the DOMCryptMethods object by getting the configuration object 1316 | * and creating the callbacks object 1317 | * @param outparam aDOMCrypt 1318 | * @returns void 1319 | */ 1320 | function initializeDOMCrypt() 1321 | { 1322 | // Full path to NSS via js-ctypes 1323 | let path = Services.dirsvc.get("GreD", Ci.nsILocalFile); 1324 | let libName = ctypes.libraryName("nss3"); // platform specific library name 1325 | path.append(libName); 1326 | let fullPath = path.path; 1327 | 1328 | let fileCreated = {}; 1329 | let file = DOMCryptMethods.configurationFile(fileCreated); 1330 | 1331 | NetUtil.asyncFetch(file, function(inputStream, status) { 1332 | if (!Components.isSuccessCode(status)) { 1333 | throw new Error("Cannot access DOMCrypt configuration file"); 1334 | } 1335 | 1336 | var data; 1337 | if (fileCreated.value) { 1338 | data = JSON.stringify(BLANK_CONFIG_OBJECT); 1339 | writeConfigObjectToDisk(data, function writeCallback (status) { 1340 | if (!Components.isSuccessCode(status)) { 1341 | throw new Error("Cannot write config object file to disk"); 1342 | } 1343 | let configObj = JSON.parse(data); 1344 | DOMCryptMethods.init(configObj, fullPath, libName); 1345 | }); 1346 | } 1347 | else { 1348 | data = NetUtil.readInputStreamToString(inputStream, inputStream.available()); 1349 | let configObj = JSON.parse(data); 1350 | DOMCryptMethods.init(configObj, fullPath, libName); 1351 | } 1352 | }); 1353 | } 1354 | 1355 | /** 1356 | * Write the configuration to disk 1357 | * 1358 | * @param string aData 1359 | * @param function aCallback 1360 | * @returns void 1361 | */ 1362 | function writeConfigObjectToDisk(aData, aCallback) 1363 | { 1364 | let fileCreated = {}; 1365 | let file = DOMCryptMethods.configurationFile(fileCreated); 1366 | 1367 | let ostream = Cc["@mozilla.org/network/file-output-stream;1"]. 1368 | createInstance(Ci.nsIFileOutputStream); 1369 | 1370 | let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]. 1371 | createInstance(Ci.nsIScriptableUnicodeConverter); 1372 | converter.charset = "UTF-8"; 1373 | let istream = converter.convertToInputStream(aData); 1374 | 1375 | NetUtil.asyncCopy(istream, ostream, aCallback); 1376 | } 1377 | 1378 | initializeDOMCrypt(); 1379 | -------------------------------------------------------------------------------- /extension/domcrypt/content/addressbookManager.js: -------------------------------------------------------------------------------- 1 | /* ***** BEGIN LICENSE BLOCK ***** 2 | * Version: MPL 1.1/GPL 2.0/LGPL 2.1 3 | * 4 | * The contents of this file are subject to the Mozilla Public License Version 5 | * 1.1 (the "License"); you may not use this file except in compliance with 6 | * the License. You may obtain a copy of the License at 7 | * http://www.mozilla.org/MPL/ 8 | * 9 | * Software distributed under the License is distributed on an "AS IS" basis, 10 | * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 11 | * for the specific language governing rights and limitations under the 12 | * License. 13 | * 14 | * The Original Code is DOMCrypt Addressbook Code. 15 | * 16 | * The Initial Developer of the Original Code is David Dahl. 17 | * 18 | * Portions created by the Initial Developer are Copyright (C) 2011 19 | * the Initial Developer. All Rights Reserved. 20 | * 21 | * Contributor(s): 22 | * David Dahl (Original Author) 23 | * 24 | * Alternatively, the contents of this file may be used under the terms of 25 | * either the GNU General Public License Version 2 or later (the "GPL"), or 26 | * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 27 | * in which case the provisions of the GPL or the LGPL are applicable instead 28 | * of those above. If you wish to allow use of your version of this file only 29 | * under the terms of either the GPL or the LGPL, and not to allow others to 30 | * use your version of this file under the terms of the MPL, indicate your 31 | * decision by deleting the provisions above and replace them with the notice 32 | * and other provisions required by the GPL or the LGPL. If you do not delete 33 | * the provisions above, a recipient may use your version of this file under 34 | * the terms of any one of the MPL, the GPL or the LGPL. 35 | * 36 | * ***** END LICENSE BLOCK ***** */ 37 | 38 | const Cc = Components.classes; 39 | const Ci = Components.interfaces; 40 | const Cu = Components.utils; 41 | 42 | Cu.import("resource://gre/modules/XPCOMUtils.jsm"); 43 | Cu.import("resource://gre/modules/Services.jsm"); 44 | 45 | function LogFactory(aMessagePrefix) 46 | { 47 | function log(aMessage) { 48 | var _msg = aMessagePrefix + " " + aMessage + "\n"; 49 | dump(_msg); 50 | } 51 | return log; 52 | } 53 | 54 | var log = LogFactory("*** DOMCrypt Addressbook:"); 55 | 56 | const PROMPT_PUB_KEY_FOUND_BUTTON_LABEL = "Save Addressbook Entry"; 57 | 58 | let EXPORTED_SYMBOLS = ["addressbook"]; 59 | 60 | let AddressbookManager = { 61 | 62 | classID: Components.ID("{66af630d-6d6d-4d29-9562-9f1de90c1799}"), 63 | 64 | QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]), 65 | 66 | init: function AM_init() 67 | { 68 | this.getContactsObj(); 69 | 70 | if (this.running) { 71 | return this; 72 | } 73 | 74 | Services.obs.addObserver(this, "content-document-global-created", false); 75 | Services.obs.addObserver(this, "quit-application-granted", false); 76 | this.running = true; 77 | return this; 78 | }, 79 | 80 | running: false, 81 | 82 | observe: function AM_observe(aSubject, aTopic, aData) 83 | { 84 | if (aTopic == "final-ui-startup") { 85 | Services.obs.addObserver(this, "content-document-global-created", false); 86 | } 87 | if (aTopic == "content-document-global-created") { 88 | let self = this; 89 | // check if there is a public key in the document 90 | let window = XPCNativeWrapper.unwrap(aSubject); 91 | window.addEventListener("DOMContentLoaded", self.windowParser, false); 92 | } 93 | if (aTopic == "quit-application-granted") { 94 | Services.obs.removeObserver(this, "content-document-global-created"); 95 | } 96 | }, 97 | 98 | windowParser: function AM_makeWindowParseFunction(aEvent) 99 | { 100 | AddressbookManager.discoverAddressbookEntry(aEvent.target); 101 | }, 102 | 103 | parseWindow: function AM_parseWindow(aDocument) 104 | { 105 | aDocument = XPCNativeWrapper.unwrap(aDocument); 106 | // look for an addressbook entry meta tag in the document 107 | let metaTags = aDocument.querySelectorAll("meta"); 108 | for (let i = 0; i < metaTags.length; i++) { 109 | let node = metaTags[i]; 110 | if (node.getAttribute("name") == "addressbook-entry") { 111 | try { 112 | let entryObj = { 113 | pubKey: node.getAttribute("pubkey"), 114 | handle: node.getAttribute("handle"), 115 | domain: node.getAttribute("domain"), 116 | date: node.getAttribute("date") 117 | }; 118 | return entryObj; 119 | } 120 | catch (ex) { 121 | Cu.reportError("DOMCrypt: Could not get Addressbook entry. " + ex); 122 | } 123 | } 124 | } 125 | return null; 126 | }, 127 | 128 | discoverAddressbookEntry: function AM_discoverAddressbookEntry(aDocument) 129 | { 130 | let entry = this.parseWindow(aDocument); 131 | 132 | if (!entry) { 133 | return; 134 | } 135 | if (!entry.handle && !entry.domain && !entry.date && !entry.pubKey) { 136 | Cu.reportError("DOMCrypt Addressbook entry is malformed"); 137 | return; 138 | } 139 | let idx = "@" + entry.domain + "/" + entry.handle; 140 | if (this.contacts[idx]) { 141 | // check if the key has changed 142 | if (parseInt(this.contacts[idx].date) == parseInt(entry.date)) { 143 | // key is the same, ignore published entry 144 | Cu.reportError("DOMCrypt: Addressbook entry already in storage"); 145 | return; 146 | } 147 | } 148 | if (entry) { 149 | // show a notification telling the user about this discovered entry 150 | var xulWindow = aDocument.defaultView 151 | .QueryInterface(Ci.nsIInterfaceRequestor) 152 | .getInterface(Ci.nsIWebNavigation) 153 | .QueryInterface(Ci.nsIDocShell) 154 | .chromeEventHandler.ownerDocument.defaultView; 155 | let gBrowser = xulWindow.gBrowser; 156 | let docElem = xulWindow.document.documentElement; 157 | if (!docElem || docElem.getAttribute("windowtype") != "navigator:browser" || 158 | !xulWindow.gBrowser) { 159 | // one last check to bail out 160 | return; 161 | } 162 | let browser = gBrowser.getBrowserForDocument(aDocument); 163 | var nb = gBrowser.getNotificationBox(browser); 164 | var notification = nb.getNotificationWithValue('addressbook-entry-found'); 165 | if (notification) { 166 | notification.label = message; 167 | } 168 | else { 169 | let self = this; 170 | var buttons = [{ 171 | label: PROMPT_PUB_KEY_FOUND_BUTTON_LABEL, 172 | accessKey: 'S', 173 | popup: null, 174 | callback: 175 | function(){self.saveContactIntoAddressbook(entry);} 176 | }]; 177 | 178 | const priority = nb.PRIORITY_WARNING_MEDIUM; 179 | nb.appendNotification(idx + " has published a DOMCrypt Addressbook Entry on this page", 180 | 'addressbook-entry-found', 181 | 'chrome://global/skin/icons/Question.png', 182 | priority, 183 | buttons); 184 | } 185 | } 186 | }, 187 | 188 | saveContactIntoAddressbook: function AM_saveContactIntoAddressbook(aContact) 189 | { 190 | // get .mozCipher_contacts.json if the data is not loaded 191 | if (!this.contacts) { 192 | this.getContactsObj(); 193 | } 194 | // TODO: check to see if we have this one yet before saving as we might be 195 | // overwriting an old key we might want to hang on to 196 | let idx = "@" + aContact.domain + "/" + aContact.handle; 197 | 198 | this.contacts[idx] = aContact; 199 | // this.contacts[idx]['hash'] = sha2(aContact.pubKey); // no worky 200 | 201 | this.writeContactsToDisk(); 202 | }, 203 | 204 | removeContact: function AM_removeContact(aHandle) 205 | { 206 | // TODO: remove contact from this.contacts and JSON file 207 | // TODO: expose to DOM API 208 | }, 209 | 210 | contactsFile: function AM_contactFile() 211 | { 212 | // get profile directory 213 | let file = Cc["@mozilla.org/file/directory_service;1"]. 214 | getService(Ci.nsIProperties). 215 | get("ProfD", Ci.nsIFile); 216 | file.append(".mozCipher_contacts.json"); 217 | 218 | if (!file.exists()) { 219 | file.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0600); 220 | this.contactsFileCreated = Date.now(); 221 | } 222 | return file; 223 | }, 224 | 225 | writeContactsToDisk: function AM_writeContactsToDisk() 226 | { 227 | let data; 228 | 229 | try { 230 | // convert this.contacts to JSON string 231 | data = JSON.stringify(this.contacts); 232 | 233 | let foStream = Cc["@mozilla.org/network/file-output-stream;1"]. 234 | createInstance(Ci.nsIFileOutputStream); 235 | let file = this.contactsFile(); 236 | 237 | // use 0x02 | 0x10 to open file for appending. 238 | foStream.init(file, 0x02 | 0x08 | 0x20, 0666, 0); 239 | 240 | let converter = Cc["@mozilla.org/intl/converter-output-stream;1"]. 241 | createInstance(Ci.nsIConverterOutputStream); 242 | converter.init(foStream, "UTF-8", 0, 0); 243 | converter.writeString(data); 244 | converter.close(); 245 | } 246 | catch (ex) { 247 | log(ex); 248 | log(ex.stack); 249 | } 250 | }, 251 | 252 | getContactsObj: function AM_getContactsObj() 253 | { 254 | let newContacts = false; 255 | let jsonObj; 256 | 257 | try { 258 | // get file, convert to JSON 259 | let file = this.contactsFile(); 260 | if (this.contactsFileCreated) { 261 | this.contacts = {}; 262 | this.writeContactsToDisk(this.contacts); 263 | this.contactsFileCreated = false; 264 | } 265 | else { 266 | var str = this.getFileAsString(file); 267 | jsonObj = JSON.parse(str); 268 | this.contacts = jsonObj; 269 | } 270 | return this.contacts; 271 | } 272 | catch (ex) { 273 | log(ex); 274 | log(ex.stack); 275 | return {}; 276 | } 277 | }, 278 | 279 | getFileAsString: function AM_getFileAsString(aFile) 280 | { 281 | if (!aFile.exists()) { 282 | throw new Error("File does not exist"); 283 | } 284 | // read file data 285 | let data = ""; 286 | let fstream = Cc["@mozilla.org/network/file-input-stream;1"]. 287 | createInstance(Ci.nsIFileInputStream); 288 | let cstream = Cc["@mozilla.org/intl/converter-input-stream;1"]. 289 | createInstance(Ci.nsIConverterInputStream); 290 | fstream.init(aFile, -1, 0, 0); 291 | cstream.init(fstream, "UTF-8", 0, 0); 292 | 293 | let (str = {}) { 294 | let read = 0; 295 | do { 296 | read = cstream.readString(0xffffffff, str); 297 | data += str.value; 298 | } while (read != 0); 299 | }; 300 | cstream.close(); 301 | return data; 302 | } 303 | }; 304 | 305 | function sha2(string) { 306 | // stolen from weave/util.js 307 | let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]. 308 | createInstance(Ci.nsIScriptableUnicodeConverter); 309 | converter.charset = "UTF-8"; 310 | 311 | let hasher = Cc["@mozilla.org/security/hash;1"] 312 | .createInstance(Ci.nsICryptoHash); 313 | hasher.init(hasher.SHA256); 314 | 315 | let data = converter.convertToByteArray(string, {}); 316 | hasher.update(data, data.length); 317 | let rawHash = hasher.finish(false); 318 | 319 | // return the two-digit hexadecimal code for a byte 320 | function toHexString(charCode) { 321 | return ("0" + charCode.toString(16)).slice(-2); 322 | } 323 | 324 | let hash = [toHexString(rawHash.charCodeAt(i)) for (i in rawHash)].join(""); 325 | return hash; 326 | } 327 | 328 | let Addressbook = AddressbookManager.init(); 329 | 330 | XPCOMUtils.defineLazyGetter(this, "addressbook", 331 | function (){ 332 | return AddressbookManager.init(); 333 | }); 334 | -------------------------------------------------------------------------------- /extension/domcrypt/content/browser.xul: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 12 | 13 | -------------------------------------------------------------------------------- /extension/domcrypt/install.rdf: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | domcrypt@ddahl.com 6 | DOMCrypt 7 | 0.4.3 8 | Provides window.mozCipher crypto tools to each window 9 | David Dahl 10 | http://domcrypt.org/ 11 | 2 12 | 13 | 14 | 15 | 16 | {ec8030f7-c20a-464f-9b0e-13a3a9e97384} 17 | 8.0 18 | 10.0 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /extension/domcrypt/locale/en-US/domcrypt.properties: -------------------------------------------------------------------------------- 1 | # ***** BEGIN LICENSE BLOCK ***** 2 | # Version: MPL 1.1/GPL 2.0/LGPL 2.1 3 | # 4 | # The contents of this file are subject to the Mozilla Public License Version 5 | # 1.1 (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # http://www.mozilla.org/MPL/ 8 | # 9 | # Software distributed under the License is distributed on an "AS IS" basis, 10 | # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 11 | # for the specific language governing rights and limitations under the 12 | # License. 13 | # 14 | # The Original Code is mozilla.org code. 15 | # 16 | # The Initial Developer of the Original Code is 17 | # Mozilla Foundation. 18 | # Portions created by the Initial Developer are Copyright (C) 2011 19 | # the Initial Developer. All Rights Reserved. 20 | # 21 | # Contributor(s): 22 | # David Dahl (original author) 23 | # 24 | # Alternatively, the contents of this file may be used under the terms of 25 | # either of the GNU General Public License Version 2 or later (the "GPL"), 26 | # or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 27 | # in which case the provisions of the GPL or the LGPL are applicable instead 28 | # of those above. If you wish to allow use of your version of this file only 29 | # under the terms of either the GPL or the LGPL, and not to allow others to 30 | # use your version of this file under the terms of the MPL, indicate your 31 | # decision by deleting the provisions above and replace them with the notice 32 | # and other provisions required by the GPL or the LGPL. If you do not delete 33 | # the provisions above, a recipient may use your version of this file under 34 | # the terms of any one of the MPL, the GPL or the LGPL. 35 | # 36 | # ***** END LICENSE BLOCK ***** 37 | 38 | enterPassphraseTitle=Enter Passphrase 39 | enterPassphraseText=Enter a passphrase that will be used to keep your data secure 40 | confirmPassphraseTitle=Confirm Passphrase 41 | confirmPassphraseText=Confirm you know the passphrase you just entered 42 | passphrasesDoNotMatchTitle=Passphrases do not match 43 | passphrasesDoNotMatchText=Error: The passphrase and confirmation do not match 44 | signErrorTitle=Signature generation failed 45 | signErrorMessage=Could not sign the data. Passphrase may be incorrectly entered 46 | noPassphraseEntered=No passphrase was entered 47 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | domcrypt.org 5 | 17 | 18 | 19 |

DOMCrypt API

20 |

DOMCrypt is a Firefox extension that adds 'window.mozCipher' javascript object to any webpage. DOMCrypt's API is the initial straw man proposal for the W3C's Web Cryptography Working Group.

21 |

22 | If you would like to run the tests or play with the demo, you will need to use Firefox 9+ and install the DOMCrypt extension 23 |

24 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /tests/test-domcrypt.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | DOMCrypt API test page 4 | 5 | 9 | 10 | 11 | 12 |

DOMCrypt PublicKey API Tests

13 |

generateKeypair() results: a public key, with a full keypair saved to disk

14 |
15 |

getPublicKey() results: a public key

16 |
17 |

encrypt() results: an encrypted blob of data

18 |
19 |

decrypt() results: a decrypted string

20 |
21 |

sign() results: a signature

22 |
23 |

verify() results: verification

24 |
25 |

SHA256() results: a hash

26 |
27 | 28 |

DOMCrypt Symmetric API Tests

29 |

sym.generateKey() results

30 |

31 |     

sym.encrypt() results

32 |

33 |     

sym.decrypt() results

34 |

35 |     

sym.wrapKey() results

36 |

Second encryption operation results

37 |

38 |     

wrapped key operation results

39 |

40 |     

wrapped key decrypt operation results

41 |

42 | 
43 |     
44 |

Encrypt text

45 | 46 |

47 |
48 |
49 |
50 |

Decrypt text

51 |

52 |
53 | 54 | 55 | -------------------------------------------------------------------------------- /tests/test-domcrypt.js: -------------------------------------------------------------------------------- 1 | /* ***** BEGIN LICENSE BLOCK ***** 2 | * Version: MPL 1.1/GPL 2.0/LGPL 2.1 3 | * 4 | * The contents of this file are subject to the Mozilla Public License Version 5 | * 1.1 (the "License"); you may not use this file except in compliance with 6 | * the License. You may obtain a copy of the License at 7 | * http://www.mozilla.org/MPL/ 8 | * 9 | * Software distributed under the License is distributed on an "AS IS" basis, 10 | * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 11 | * for the specific language governing rights and limitations under the 12 | * License. 13 | * 14 | * The Original Code is mozilla.org code. 15 | * 16 | * The Initial Developer of the Original Code is 17 | * the Mozilla Foundation. 18 | * Portions created by the Initial Developer are Copyright (C) 2010 19 | * the Initial Developer. All Rights Reserved. 20 | * 21 | * Contributor(s): 22 | * David Dahl 23 | * 24 | * Alternatively, the contents of this file may be used under the terms of 25 | * either the GNU General Public License Version 2 or later (the "GPL"), or 26 | * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 27 | * in which case the provisions of the GPL or the LGPL are applicable instead 28 | * of those above. If you wish to allow use of your version of this file only 29 | * under the terms of either the GPL or the LGPL, and not to allow others to 30 | * use your version of this file under the terms of the MPL, indicate your 31 | * decision by deleting the provisions above and replace them with the notice 32 | * and other provisions required by the GPL or the LGPL. If you do not delete 33 | * the provisions above, a recipient may use your version of this file under 34 | * the terms of any one of the MPL, the GPL or the LGPL. 35 | * 36 | * ***** END LICENSE BLOCK ***** */ 37 | 38 | function testDOMCrypt() 39 | { 40 | var pubkey, cipherMessage, signature; 41 | var _passphrase = "foobar"; 42 | var message = 43 | "This is a message about Mr. Mossop and his penchant for Belgian Doubles"; 44 | // generate a key pair 45 | window.mozCipher.pk.generateKeypair(function (aPublicKey){ 46 | document.getElementById("results").innerHTML = aPublicKey; 47 | 48 | // get the public key just created 49 | window.mozCipher.pk.getPublicKey(function (aPubKey){ 50 | pubkey = aPubKey; 51 | document.getElementById("results-pub-key").innerHTML = aPubKey; 52 | 53 | // encrypt a string 54 | window.mozCipher.pk.encrypt(message, pubkey, function (aCipherMessage){ 55 | document.getElementById("results-encrypt").innerHTML = 56 | aCipherMessage.content; 57 | cipherMessage = aCipherMessage; 58 | 59 | // decrypt a string 60 | window.mozCipher.pk.decrypt(cipherMessage, function (aPlainText){ 61 | document.getElementById("results-decrypt").innerHTML = aPlainText; 62 | 63 | // sign a message 64 | window.mozCipher.pk.sign(message, function (aSignature){ 65 | document.getElementById("results-sign").innerHTML = aSignature; 66 | signature = aSignature; 67 | 68 | // verify a signature 69 | window.mozCipher.pk.verify(message, signature, pubkey, function (aVerification){ 70 | var resultMessage; 71 | if (aVerification) { 72 | resultMessage = "Signature was Verified"; 73 | } 74 | else { 75 | resultMessage = "Verification Failed"; 76 | } 77 | document.getElementById("results-verify").innerHTML = 78 | resultMessage; 79 | 80 | // make a SHA256 HASH 81 | window.mozCipher.hash.SHA256("Belgian Triples Anyone?", 82 | function (aHash){ 83 | document.getElementById("results-sha256").innerHTML = aHash; 84 | generateSymKey(); 85 | symEncrypt(); 86 | }); 87 | }); 88 | }); 89 | }); 90 | }); 91 | }); 92 | }); 93 | } 94 | 95 | var beginMSec, endMSec; 96 | 97 | function startEncrypt() 98 | { 99 | mozCipher.pk.getPublicKey(function (aPubKey){ 100 | encryptText(aPubKey); 101 | }); 102 | } 103 | 104 | function encryptText(aPubKey) 105 | { 106 | var t = document.getElementById("to-encrypt").value; 107 | if (t) { 108 | beginMSec = Date.now(); 109 | mozCipher.pk.encrypt(t, aPubKey , function (cipherMessage) { 110 | endMSec = Date.now(); 111 | document.getElementById("encrypt-seconds").textContent = "encrypt() took: " 112 | + ((endMSec - beginMSec) / 1000) + " Seconds."; 113 | // Callback to handle the encrypted data 114 | document.message = cipherMessage; 115 | document.getElementById("encrypted").textContent = document.message.content; 116 | }); 117 | } 118 | else { 119 | alert("No text to encrypt!"); 120 | } 121 | } 122 | 123 | function decryptText() 124 | { 125 | var cipherMessage = document.message; 126 | if (document.message.content) { 127 | document.getElementById("to-encrypt").value = ""; 128 | beginMSec = Date.now(); 129 | mozCipher.pk.decrypt(document.message, function (plainText) { 130 | endMSec = Date.now(); 131 | // callback to deal with decrypted text 132 | document.getElementById("decrypt-seconds").textContent = "decrypt() took: " 133 | + ((endMSec - beginMSec) / 1000) + " Seconds."; 134 | document.getElementById("encrypted").textContent = plainText; 135 | }); 136 | } 137 | } 138 | 139 | function symEncrypt() 140 | { 141 | mozCipher.pk.getPublicKey(function (aPubKey){ 142 | console.log("public key: ", aPubKey); 143 | console.log("The public key is used to wrap the symmetric key after the data is encrypted"); 144 | var text = "It was a bright cold day in April"; 145 | console.log("encrypting: ", text); 146 | mozCipher.sym.encrypt(text, 147 | function (cipherObj){ 148 | console.log("cipher text: "); 149 | console.log(cipherObj.cipherText); 150 | var props = ""; 151 | for (var prop in cipherObj) { 152 | props = props + prop + ": " + cipherObj[prop] + "\n\n"; 153 | } 154 | document.getElementById("sym-encrypt-results").innerHTML = 155 | props; 156 | console.log("ok, time to decrypt"); 157 | symDecrypt(cipherObj); 158 | }); 159 | }); 160 | } 161 | 162 | function symDecrypt(aCipherObject) 163 | { 164 | var cipherObj; 165 | if (aCipherObject) { 166 | cipherObj = aCipherObject; 167 | } 168 | else { 169 | cipherObj = document.symEncryptResults.cipherObj; 170 | } 171 | console.log("decrypting data..."); 172 | mozCipher.sym.decrypt(cipherObj, 173 | function (plainText) { 174 | console.log("plain text: "); 175 | console.log(plainText); 176 | document.getElementById("sym-decrypt-results").innerHTML = 177 | plainText; 178 | wrapKey(); 179 | }); 180 | } 181 | 182 | function generateSymKey() 183 | { 184 | mozCipher.sym.generateKey(function (wrappedSymKeyObj){ 185 | // The wrappedSymKeyObj has the following format: 186 | // { wrappedKey: [WRAPPED_KEY], iv: [INIT VECTOR], pubKey: [PUBLIC KEY] } 187 | var key = ""; 188 | for (var prop in wrappedSymKeyObj) { 189 | key = key + prop + ": " + wrappedSymKeyObj[prop] + "\n\n"; 190 | } 191 | document.getElementById("sym-generate-key-results").innerHTML = key; 192 | }); 193 | } 194 | 195 | function wrapKey() 196 | { 197 | // encrypt some data, use another public key to generate a new cipher 198 | // object access to the encypted data 199 | 200 | // encrypt the string 201 | mozCipher.pk.getPublicKey(function (aPubKey){ 202 | console.log("public key: ", aPubKey); 203 | console.log("The public key is used to wrap the symmetric key after the data is encrypted"); 204 | var text = "It was a bright cold day in April and whatnot..."; 205 | console.log("encrypting: ", text); 206 | mozCipher.sym.encrypt(text, function (cipherObj){ 207 | console.log("cipher text: "); 208 | console.log(cipherObj.cipherText); 209 | var props = ""; 210 | for (var prop in cipherObj) { 211 | props = props + prop + ": " + cipherObj[prop] + "\n\n"; 212 | } 213 | document.getElementById("sym-encrypt2-results"). 214 | innerHTML = props; 215 | 216 | // Re-wrap the key 217 | console.log("ok, re-wrap the key!"); 218 | mozCipher.sym.wrapKey(cipherObj, aPubKey, function (reWrappedCipherObj) { 219 | // the reWrappedCipher object should be the same 220 | // cipher object, except the symKey has been updated 221 | // and allows another private key acces to the data 222 | console.log(reWrappedCipherObj); 223 | var props = ""; 224 | for (var prop in reWrappedCipherObj) { 225 | props = props + prop + ": " + 226 | reWrappedCipherObj[prop] + "\n\n"; 227 | } 228 | document.getElementById("sym-re-wrap-results"). 229 | innerHTML = props; 230 | 231 | // decrypt the message 232 | mozCipher.sym.decrypt(reWrappedCipherObj, function(plainText) { 233 | document.getElementById("sym-re-wrap-decrypt-results").innerHTML = plainText; 234 | }); 235 | }); 236 | }); 237 | }); 238 | } 239 | 240 | function cryptLocalStorage() 241 | { 242 | mozCipher; 243 | // localStorage demo 244 | // create data to save in localStorage after encrypting it 245 | // decrypt and display the data 246 | 247 | // document.removeEventListener("DOMContentLoaded", cryptLocalStorage); 248 | 249 | var plainText = "It was a bright cold day in April, and the clocks were striking thirteen. Winston Smith, his chin nuzzled into his breast in an effort to escape the vile wind, slipped quickly through the glass doors of Victory Mansions, though not quickly enough to prevent a swirl of gritty dust from entering along with him."; 250 | 251 | mozCipher.sym.encrypt(plainText, function (cryptoObj){ 252 | var cryptoText = JSON.stringify(cryptoObj); 253 | localStorage.setItem("openingText", cryptoText); 254 | // window.setTimeout(function (){ 255 | var openingTxt = localStorage.getItem("openingText"); 256 | console.log("JSON data in localStorage: ", openingTxt); 257 | 258 | // decrypt it: 259 | var _cryptoObj = JSON.parse(openingTxt); 260 | mozCipher.sym.decrypt(_cryptoObj, function (plainText){ 261 | console.log("decrypted: ", plainText); 262 | }); 263 | // }, 500); 264 | 265 | }); 266 | } 267 | 268 | // document.addEventListener("DOMContentLoaded", cryptLocalStorage, false); 269 | --------------------------------------------------------------------------------