Friday, February 15, 2008

Integrate encryption into Google Calendar with Firefox extensions

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
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: