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

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.
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.
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.
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.
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.
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.
| Description | Name | Size | Download method |
|---|---|---|---|
| Sample code | calendarEncrypt_0.1.zip | 27KB | HTTP |
Information about download methods
Learn
- Mozilla has many excellent articles
about Extension Development.
- Get the Gnu
Privacy Guard to manage encryption.
- The code in this article is based on the Google Calendar Quick
Add extension by Elias Torrez.
- For working
with Firefox, the Extension
Developer's Extension is very useful.
- There are many excellent extension-related articles at BornGeek.com.
- To listen to interesting interviews and discussions for software
developers, check out developerWorks podcasts.
- Stay current with developerWorks' technical events and webcasts.
- Check out upcoming conferences, trade shows, webcasts, and other
events around the world that are of interest to
IBM open source developers.
- Expand your Web development skills with articles and
tutorials that specialize in Web technologies in the developerWorks Web development zone.
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
- Participate in developerWorks blogs and get involved in the developerWorks
community.





