| Level: Introductory Nathan Harrington (harrington.nathan@gmail.com), I/T Specialist, IBM
15 Jul 2008 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.
|
Cross platform compatibility This article was
developed on the Ubuntu 7.10 Linux® distribution. If you're following this article verbatim,
you'll need to be using a modern distribution of Linux. However, all of the software
components referenced here (Firefox, GnuPG, and Perl) have Microsoft®
Windows® versions. With some
small modifications to the path names and external programs, the code presented here should
work on a wide variety of platforms. |
|
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.
Download Description | Name | Size | Download method |
---|
Sample code | calendarEncrypt_0.1.zip | 27KB | HTTP |
---|
Resources Learn
Get products and technologies
- Download IBM product evaluation versions, and get your hands on application development tools
and middleware products from DB2®, Lotus®, Rational®,
Tivoli®, and WebSphere®.
Discuss
About the author | | | Nathan Harrington is a programmer working with Linux at IBM. |
Rate this page
| |