Today's Web applications provide many benefits for
online storage, access, and collaboration. Although some
applications offer encryption of user data, most do not.
This article provides tools and code needed to add basic
encryption support for user data in one of the most
popular online calendar applications. Building on the
incredible flexibility of Firefox extensions and the Gnu
Privacy Guard, this article shows you how to store only
encrypted event descriptions in Google's Calendar
application, while displaying a plain text version to
anyone with the appropriate decryption keys.
Beginning with Elias Torres' excellent "Google Calendar
Quick Add" extension, this article walks you through the
extraction, changes, and insertion of various components to
enable encrypted events, not just forcing an encrypted TLS
data channel. After following the instructions in this
article, Figure 1,
shown below, is an example of what the server operators will
see on the left, and what the Web surfer will see on the
right.
Figure 1. Google Calendar encrypted
Requirements
Hardware
Any hardware capable of providing a post-2002 browsing
experience should be sufficient for the code presented in
this article. Encryption algorithms are highly
processor-intensive, so if dozens or hundreds of calendar
events need to be decrypted per page, you'll need fast
hardware for a useful browsing experience.
Software
Firefox 1.5 or greater is required, as well as GnuPG -
the Gnu Privacy Guard. Various Firefox extension development
tools are also a good idea. Check the
Resources
section below for links to these software packages.
Although this article was developed on an Ubuntu 7.10
system, the concepts are easily transferable to a wide
variety of operating environments. Make sure your system
supports Perl, GnuPG, and Firefox before beginning
development.
General program approach
This article assumes you have basic familiarity with the
Firefox extension development process. No specific compiler
or environment configuration is required, but you should be
familiar with software development in order to diagnose
problems or configuration issues specific to their setup.
For development purposes, creating a new Firefox profile
is recommended. Consult the
Resources
section below for information on how to create a new
profile, as well as links to helpful extension development
sites.
GnuPG will be used to handle all of the encryption
functions for this extension. This approach frees the
JavaScript from less efficient algorithm implementation as
well as providing robust cross-platform key management. You
will need a fully functional GnuPG installation, with access
to your encryption keys of choice. The gpg-agent
program (usually part of the GnuPG package) is also required
to handle the temporary storage and expiration of remembered
passphrases.
With a functional extension development environment and
GnuPG to handle the encryption, modifying the interface will
be done by changing some code in a previously developed
extension. The Google Quick Add extension by Elias Torres
provides a good starting point to insert the encryption
modifications. In the encryption stage, the currently
entered event text will be stored on disk, an external
program will encrypt it, and then the encrypted file will be
read in and sent to Google's servers. On decryption, each
event is written to disk, unencrypted, and the plain text
read back in to be displayed to the user. Alternatives to
this approach involve writing new protocols for the Firefox
program to manage. Although this process can provide a more
robust data pipeline, the approach of output, processing,
and reading is a relatively simple method of interprocess
communication suitable for this class of extensions.
Before continuing with the code presented in this
article, make sure you have a functional Firefox GnuPG and
gpg-agent set up.
Building on the Google Calendar
Quick Add extension
The Google Calendar Quick Add extension makes use of the
Google Calendar SOAP API to add events to your calendar from
any page. Payload interception and encryption of this
process is what this article uses to add encrypted events to
your calendar. An alternate approach is to simply add
ASCII-armored entries to your Google Calendar, but the
approach here will automate this process for you.
Begin by making a directory to hold the extension
directories and code, such as mkdir ~/calendarEncrypt .
Change to this directory and download the Quick Google
Calendar Quick Add extension xpi from the link specified in
the Resources
section below.
Unpack the xpi extension with the command: unzip
quickgooglecal.xpi . Change to the newly created
chrome directory, and run the command unzip
quickgooglecal.jar . You should now have a directory
tree like the one shown in
Listing 1
below:
Listing 1. Google Calendar Quick Add
directory structure
calendarEncrypt/chrome.manifest
calendarEncrypt/readme.txt
calendarEncrypt/chrome
calendarEncrypt/chrome/content
calendarEncrypt/chrome/content/hello.xul
calendarEncrypt/chrome/content/overlay.xul
calendarEncrypt/chrome/content/overlay.js
calendarEncrypt/chrome/skin
calendarEncrypt/chrome/skin/overlay.css
calendarEncrypt/chrome/quickgooglecal.jar
calendarEncrypt/chrome/locale
calendarEncrypt/chrome/locale/en-US
calendarEncrypt/chrome/locale/en-US/hello.dtd
calendarEncrypt/chrome/locale/en-US/overlay.dtd
calendarEncrypt/install.rdf
calendarEncrypt/quickgooglecal.xpi
|
Change to the ~/calendarEncrypt directory and edit the install.rdf
file. Make the changes to identifier and creator as shown in
Listing 2
below:
Listing 2. install.rdf identifier and
creator changes
Change the identifier:
<em:id>{E31AE5B1-3E5B-4927-9B48-76C0A701F105}</em:id>
to:
<em:id>calendarEncrypt@devWorks_IBM.com</em:id>
Also, change the creator:
<em:creator>Elias Torres</em:creator>
to:
<em:creator>Elias Torres with modifications from devWorks</em:creator>
|
Edit the chrome.manifest file to change the jar-based extension
packaging to a more developer-friendly directory structure
packaging. Listing
3 below shows the changes required.
Listing 3. chrome.manifest jar to
directory structure changes
Original chrome.manifest
content quickgooglecal jar:chrome/quickgooglecal.jar!/content/
overlay chrome://browser/content/browser.xul chrome://quickgooglecal/content/overlay.xul
locale quickgooglecal en-US jar:chrome/quickgooglecal.jar!/locale/en-US/
skin quickgooglecal classic/1.0 jar:chrome/quickgooglecal.jar!/skin/
style chrome://global/content/customizeToolbar.xul
chrome://quickgooglecal/skin/overlay.css
Change to directory based chrome.manifest:
content quickgooglecal chrome/content/
overlay chrome://browser/content/browser.xul chrome://quickgooglecal/content/overlay.xul
locale quickgooglecal en-US chrome/locale/en-US/
skin quickgooglecal classic/1.0 chrome/skin/
style chrome://global/content/customizeToolbar.xul
chrome://quickgooglecal/skin/overlay.css
|
Now set a link to the current extension directory in your Firefox
development profile. For example if your profile is
~/.mozilla/firefox/b2w2sglj.development , create a
link in
~/.mozilla/firefox/b2w2sglj.development/extensions/
called calendarEncrypt@devWorks_IBM.com . Place
the current Google Quick Add development directory path in
the calendarEncrypt@devWorks_IBM.com file,
which for this example would be:
/home/username/calendarEncrypt .
Log in to Google Calendar and then press ctrl+; to
activate the Google Calendar Quick Add extension. Verify
that events can be added correctly by typing Test
unencrypted event tomorrow 15:30 . Verify that the
event appears on your calendar correctly.
With the Google Calendar Quick Add extension in place,
you can now being inserting the modifications to the
extension that support encrypting and decrypting events.
Modifying the Quick Add extension
to support transparent encryption
Encrypting
events as they are sent
Interception and encryption of the Quick Add Event
payloads are the processes used by this article to help
automate the addition of encrypted events to your calendar.
Modifying the existing extension to support the interception
is shown starting below in
Listing 4. Edit
the chrome/content/hello.xul file and remove
line 69: var quickAddText =
number_html(document.getElementById("quickText").value); .
Insert the following code at line 69:
Listing 4. Encryption variables, date
time extraction
var words = document.getElementById("quickText").value.split(' ');
var dayVal = words[words.length-2];
var timeVal = words[words.length-1];
var elementData = "";
for( var n=0; n < words.length-2; n++ ){
elementData = elementData + " " + words[n];
}
|
The code presented above assumes that the date and time are always the
last two words in the quick event text. To ensure accurate
placement in the calendar, all future quick add events must
be of the format "message text date time", where date is
"friday/monday/tomorrow/etc", and time is "05:30/10:30/etc."
With the event description text isolated from the event
data and time, the next step is to write the event text to
disk, and then encrypt it. Add the code shown below in
Listing 5 at
line 77:
Listing 5. Encryption write to disk,
call program
// Write the quick event text to disk
var fileOut = Components.classes["@mozilla.org/file/local;1"]
.createInstance(Components.interfaces.nsILocalFile);
fileOut.initWithPath("/tmp/calendarEvent");
var foStream = Components.classes["@mozilla.org/network/file-output-stream;1"]
.createInstance(Components.interfaces.nsIFileOutputStream);
foStream.init(fileOut, 0x02 | 0x08 | 0x20, 0666, 0);
foStream.write(elementData, elementData.length);
foStream.close();
// Run the external encryption process
var fileExe = Components.classes["@mozilla.org/file/local;1"]
.createInstance(Components.interfaces.nsILocalFile);
fileExe.initWithPath("/tmp/CalendarCrypt.pl");
var process = Components.classes["@mozilla.org/process/util;1"]
.createInstance(Components.interfaces.nsIProcess);
process.init(fileExe);
var args = ["encrypt"];
process.run(true, args, args.length);
|
The first section shown above in Listing 5 configures a file
"/tmp/calendarEvent" for writing the intercepted
event text to disk. This file contains plain text data, but
will be shredded and removed after the encryption is
complete. The second section in Listing 5 configures a file
to execute using the recommended nsIProcess component.
/tmp/CalendarCrypt.pl is a Perl program described in
a later section that handles the encryption, decryption, and
secure delete functions. After encrypting the data, the code
in Listing 6,
below, reads the encrypted event text back in. Place the
following code at line 98 in chrome/content/hello.xul .
Listing 6. Encryption read file, build
event text
// Read the encrypted text back in
var fileIn = Components.classes["@mozilla.org/file/local;1"]
.createInstance(Components.interfaces.nsILocalFile);
fileIn.initWithPath("/tmp/calendarEvent.asc");
var istream = Components.classes["@mozilla.org/network/file-input-stream;1"]
.createInstance(Components.interfaces.nsIFileInputStream);
istream.init(fileIn, 0x01, 0444, 0);
istream.QueryInterface(Components.interfaces.nsILineInputStream);
var data = "";
var fstream = Components.classes["@mozilla.org/network/file-input-stream;1"]
.createInstance(Components.interfaces.nsIFileInputStream);
var sstream = Components.classes["@mozilla.org/scriptableinputstream;1"]
.createInstance(Components.\
interfaces.nsIScriptableInputStream);
// above line split on \ for formatting, do not include the line break
fstream.init(fileIn, -1, 0, 0);
sstream.init(fstream);
var str = sstream.read(4096);
while (str.length > 0) {
data += str;
str = sstream.read(4096);
}
sstream.close();
fstream.close();
quickAddText = data + " " + dayVal + " " + timeVal;
|
Code sections in Listing 5 demonstrated writing the text event to a
file and running the encryption process. The code in Listing
6 shows reading the ASCII-armor encrypted text file in,
appending the stored date and time information, and resuming
the quick-add text process.
Each event added using the Google Calendar Quick Add
procedure will now be intercepted, encrypted, and posted to
Google's servers in an encrypted form.
Modifying
overlay.js for encrypted event reading
After completing the code for encrypting events in
chrome/content/hello.xul , insert the code to decrypt
the events in chrome/content/overlay.js . Insert
the lines shown below in
Listing 7,
starting at the end of the file.
Listing 7. Decryption event listener,
component definition
window.addEventListener("load", function() { calendarDecryptExtension.init(); }, false);
var calendarDecryptExtension = {
init: function() {
var appcontent = document.getElementById("appcontent"); // browser
if(appcontent)
appcontent.addEventListener("DOMContentLoaded", this.onPageLoad, true);
var messagepane = document.getElementById("messagepane"); // mail
if(messagepane)
messagepane.addEventListener("load", function () \
{ calendarDecryptExtension.onPageLoad(); }, false);
// above line split on \ for formatting, do not include the line break
},
|
Every time the event display page is loaded, a call to the
calendarDecryptExtension function is made using the
addEventListener . Beginning the
calendarDecryptionExtension function are the various
definitions and hooks required to ensure the code is started
on each page load, and that the appropriate data is
accessible to the function. Place the code shown in
Listing 8,
below, underneath the Listing 7 lines to continue the
document decryption processing:
Listing 8. onPageLoad function,
writing events
onPageLoad: function(aEvent) {
dump("pre elemenet \n");
var elementData = "NODATA";
var allSpans = content.document.getElementsByTagName("span");
for (var n = 0; n < allSpans.length; n++){
if( allSpans[n].innerHTML.indexOf("BEGIN PGP") > -1 ){
// file output for encrypted event text
elementData = allSpans[n].innerHTML;
var fileOut = Components.classes["@mozilla.org/file/local;1"]
.createInstance(Components.interfaces.nsILocalFile);
fileOut.initWithPath("/tmp/calendarEvent.temp");
var foStream = Components.classes["@mozilla.org/network/file-output-stream;1"]
.createInstance(Components. \
interfaces.nsIFileOutputStream);
// above line split on \ for formatting, do not include the line break
foStream.init(fileOut, 0x02 | 0x08 | 0x20, 0666, 0);
foStream.write(elementData, elementData.length);
foStream.close();
|
Each calendar entry exists in a span element in the HTML.
After setting up some temporary variables a for
loop will process each of those span elements,
and the contents will be written out if the text is
encrypted. Listing
9 below calls the CalendarCrypt.pl program
with the "decrypt" option to extract the event
text.
Listing 9. Running encryption script
// create an nsILocalFile for the executable
var fileExe = Components.classes["@mozilla.org/file/local;1"]
.createInstance(Components.interfaces.nsILocalFile);
fileExe.initWithPath("/tmp/CalendarCrypt.pl");
var process = Components.classes["@mozilla.org/process/util;1"]
.createInstance(Components.interfaces.nsIProcess);
process.init(fileExe);
var args = ["decrypt"];
process.run(true, args, args.length);
|
Writing the encrypted event text to disk and running the decrypt
process will create a file with the unencrypted event text.
Add the code shown below in
Listing 10 to
read the data back in and change the presented information.
Listing 10. Reading decrypted text
// file input for decrypted event text
var data = "";
var fileIn = Components.classes["@mozilla.org/file/local;1"]
.createInstance(Components.interfaces.nsILocalFile);
fileIn.initWithPath("/tmp/calendarEvent.decrypted");
var istream = Components.classes["@mozilla.org/network/file-input-stream;1"]
.createInstance(Components.interfaces.nsIFileInputStream);
istream.init(fileIn, 0x01, 0444, 0);
istream.QueryInterface(Components.interfaces.nsILineInputStream);
var fstream = Components.classes["@mozilla.org/network/file-input-stream;1"]
.createInstance(Components.interfaces.nsIFileInputStream);
var sstream = Components.classes["@mozilla.org/scriptableinputstream;1"]
.createInstance(Components. \
interfaces.nsIScriptableInputStream);
// above line split on \ for formatting, do not include the line break
fstream.init(fileIn, -1, 0, 0);
sstream.init(fstream);
var str = sstream.read(4096);
while (str.length > 0) {
data += str;
str = sstream.read(4096);
}
sstream.close();
fstream.close();
allSpans[n].innerHTML = data;
|
This reading from a file is similar to the process performed during the
encryption step. Note the last line in Listing 10 which sets
the current span data to the unencrypted text
instead of the "BEGIN PGP..." original text. Add the code
shown below in
Listing 11 to complete the decryption process.
Listing 11. Shred text on disk, loop
closure
// sanitize the data stored on disk
args = ["shred"];
process.run(true, args, args.length);
}//if the span item is encrypted
}//for each span
}//on page load
}//calendarDecryptExtension
|
Changing the option to "shred" and re-using the
nsiProcess component ensures the decrypted event text
is securely deleted from its temporary location in the file
system. The next section shows the CalendarCrypt.pl
program called in the sections above.
CalendarCrypt.pl program
To complete the encryption support modifications, the
CalendarCrypt.pl program is created according
to the listings below. Note that the implementation shown
makes a few assumptions about the most common
encryption/decryption setups likely for the typical GnuPG
user. If desired, consider replacing the external program
calls and settings with other options enabled by the use of
GnuPG::Encrypt . For example if you want to use
multiple keys, or gpg-agent is incompatible
with your configuration, the GnuPG::Encrypt
Perl module offers many options to help fit your
environment. Begin by placing the Perl program
calendarEncrypt.pl in /tmp with the text
in Listing 12
below.
Listing 12. calendarEncrypt.pl header
and encryption
#!/usr/bin/perl -w
# calendarEncrypt.pl - encrypt/decrypt/shred files
use strict;
die "specify a mode " unless @ARGV == 1;
my $mode = $ARGV[0];
chomp(my $userName = `whoami`);
if( $mode eq "encrypt" )
{
my $res = `gpg --yes --armor --encrypt -r $userName /tmp/calendarEvent`;
$res = `shred /tmp/calendarEvent; rm /tmp/calendarEvent`;
|
After checking the options and setting up the default username, the
/tmp/calendarEvent file is encrypted. Shredding
and removing the original plain text event file ensures that
no sensitive data remains on disk.
Listing 13
shown below describes the decrypt mode:
Listing 13. File processing,
decryption
}elsif( $mode eq "decrypt" )
{
open(INFILE,"/tmp/calendarEvent.temp") or die "no in file";
open(OUTFILE,"> /tmp/calendarEvent.encrypted" ) or die "no file out";
while(my $line =<INFILE>)
{
my $begin = substr( $line, 0, 23 );
print OUTFILE "-----$begin\n";
my $version = substr( $line, 23, 34 );
print OUTFILE "$version\n";
print OUTFILE "\n";
my $body = substr( $line, 57 );
$body = substr($body, 0, length($body)-26);
my @parts = split " ", $body;
for my $piece( @parts )
{
print OUTFILE "$piece\n";
}
print OUTFILE "-----END PGP MESSAGE-----\n";
}#while each line
close(OUTFILE);
close(INFILE);
my $cmd = qq{ gpg --yes --decrypt /tmp/calendarEvent.encrypted };
$cmd .= qq{ > /tmp/calendarEvent.decrypted };
my $res = `$cmd`;
|
At some point during the upload, processing, or display of a calendar
event, some of the original formatting is lost. Specifically
the "-----" prefix on the "BEGIN PGP" message, as well as
the new line indicators, are stripped out. The string
manipulation functions in Listing 13 replace the lost
formatting before the decryption command is called. Finally
the code in
Listing 14 handles the secure deletion of the decrypted
text file to remove any clear text information stored on
disk.
Listing 14. Shred plain text files
}elsif( $mode eq "shred" )
{
my $res = `shred /tmp/calendarEvent.decrypted`;
$res = `rm /tmp/calendarEvent.decrypted`;
}
# EOF
|
After saving the file as /tmp/CalendarCrypt.pl , make sure
the file is executable by issuing the command: chmod
a+x /tmp/CalendarCrypt.pl .
Usage examples
Each event added using the Google Calendar Quick Add
procedure will now be intercepted, encrypted, and posted to
Google's servers in an encrypted form. Reload all chrome
events using the Extension Developer's Extension, or restart
Firefox. Use the Ctrl+; key combination, and add an event
such as "Private doctor appointment tomorrow 16:30". Open
your Google calendar in "regular" mode, and you should see
an event description like that shown on the left of
Figure 1.
To show decrypted events, go to the link:
http://www.google.com/calendar/htmlembed?src=<yourCalendarName>%40gmail.com ,
Where <yourCalendarName> is your account username, such as
"developer.works" or "bob_smith". As the page begins to
load, you should see a gpg-agent pop-up requesting your
passphrase. Enter the passphrase for the user identified by
the "uname" command in section 1 of the
CalendarCrypt.pl program, and your events will be
decrypted and shown on your calendar.
Conclusion, further examples
With the tools and code presented in this article, you
can now store encrypted event text only in Google Calendar.
Using the modifications to Elias Torres' Google Calendar
Quick Add Firefox extension, each event added and viewed
will be seamlessly encrypted or decrypted. Regain control of
some of your data, while still reaping the benefits of the
best of Web 2.0 applications.
Consider creating an obfuscation layer to change the
stored time of events to reduce the effectiveness of traffic
analysis. Write your own program using the Google Calendar
SOAP API's to extract, encrypt, and store every calendar
event in the past and future. Take on the challenge of
creating a transparent encryption extension for the default
Google Calendar interface in all its Ajax-ishness. |
No comments:
Post a Comment