├── 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 | + 'Read '
136 | + '
'
137 | + '
{author_display_name}
'
138 | + '
{content}
'
139 | + '
';
140 | }
141 | else {
142 | tmpl = ''
143 | + '
{date} '
144 | + 'Read '
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 |
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 | If you already have an account and need to
login , please do.
14 |
15 |
choose a name to use:
16 |
17 |
choose a server password:
18 |
19 |
create
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 |
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 |
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 | + '
Read '
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 | Enter a contact's 'Handle' - or portion thereof:
8 |
9 |
10 |
11 | Search
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 | Home
16 | Check Messages
17 | Compose
18 | Addressbook Entry Lookup
19 | Create an Addressbook Entry
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 |
Choose a 'Handle'
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 | Create Addressbook Entry
23 |
27 | {% csrf_token %}
28 | {% endblock main %}
29 |
--------------------------------------------------------------------------------
/demos/dcserver/msgdrp/tmpl/compose.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 | {% block main %}
3 | Compose
4 | Select a Contact...
5 |
6 | Message:
7 |
8 |
9 |
10 | Encrypt & Send
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 Try Again
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 Try Again
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 |
8 |
9 | Firefox version 4
10 |
11 |
12 | DOMCrypt Firefox extension which adds privacy enhancements to Firefox
13 |
14 | An addressbook entry stored on this server.
15 | Click "Create Addressbook Entry" to begin.
16 |
17 | Your addressbook entry contains data about your "handle", server internet domain and cryptographic data used to secure messages sent to you, so that no one else can read them.
18 |
19 |
20 | You need to know the "handle" or username of the contacts you wish to exchange messages with.
21 | Click "Addressbook Entry Lookup" to search for your contacts.
22 | When you find a contact's addressbook entry, you will be prompted to save it to your local addressbook, which is managed by the DOMCrypt extension.
23 |
24 |
25 |
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 = 'Read 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($(''));
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 = $('' + "@" + entriesArr[i].domain + "/" +
172 | entriesArr[i].handle + ' ');
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($(""));
236 | }
237 | else {
238 | _crypt = true;
239 | }
240 |
241 | if (window.mozCipher) {
242 | if (window.mozCipher.pk.getAddressbook() == {}) {
243 | loadMessage.push($(""));
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 |
24 |
Try out the demo:
25 |
30 |
31 |
32 |
1. Generate the key pair
33 |
34 | Generate Key Pair
35 |
36 |
Public key
37 |
...
38 |
39 |
40 | Symmetric Crypto Test
41 |
42 |
43 |
2. Encrypt the message
44 |
Encrypt
45 |
Encrypted text
46 |
...
47 |
48 |
49 |
50 |
3. Decrypt the cipher text
51 |
Decrypted text
52 |
Decrypt
53 |
54 |
55 |
56 |
57 |
58 |
Latest Developments
59 |
60 | JSON data persistence for a user's default encryption credentials - not accessible to the DOM or content JS (only the Public Key is, of course.)
61 |
62 |
63 |
64 |
Next steps
65 |
66 | Data persistence via IndexedDB, JSON, localStorage, Sync, new APIs
67 | Address Book server to make public key discovery trivial
68 | Messaging (secure 'email' and 'status' feeds) client and server
69 | Evangelism: get this functionality into browsers
70 |
71 |
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 |
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 |
Get Addressbook
54 |
55 |
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 |
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 |
1. Sign message
38 |
Signature
39 |
40 |
41 |
42 |
43 |
Verify Signature
44 |
45 | Verify a message was sent by sender via NSS PK11_Sign()
46 |
47 |
2. Verify sender
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 |
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 | Run Tests
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 | Encrypt
47 |
48 |
49 |
50 | Decrypt text
51 | Decrypt
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 |
--------------------------------------------------------------------------------