通过 Firefox 扩展为 Google Calendar 加密

来源:developerWorks 中国 作者:Nathan Harrington
  
当今的 Web 应用程序为在线存储、访问和协作提供了许多便利。虽然一些应用程序为用户数据提供了加密,但为数不多。本文提供了添加基本加密支持所需的工具和代码,使您可以在一个最流行的在线日程表上加密用户数据。通过运用 Firefox 扩展和 Gnu Privacy Guard 的巨大灵活性,本文展示如何将加密的事件描述存储到 Google Calendar 应用程序,并且只向拥有解密密匙的用户显示纯文本。

本文从 Elias Torres 的出色的 “Google Calendar Quick Add” 扩展开始,逐步向您介绍如何提取、更改和插入各种组件,为事件进行加密,而不仅仅是使用加密的 TLS 数据通道。按照本文的说明进行操作之后,将会得到下面 图 1 所示的示例,在这里,服务器操作人员看到的是左边的内容,而 Web 浏览者看到的是右边的内容。


图 1. 加密的 Google Calendar
加密的 Google Calendar

需求

硬件

任何能够提供 2002 年以后的浏览体验的硬件设备,应该能够运行本文所使用的代码。加密算法属于处理器密集型作业,因此,如果要在一个页面上对十几个,甚至上百个日程表事件进行加密,更快的硬件支持能够提供更好的浏览体验。

软件

需要 Firefox 1.5(或更新的版本)和 GnuPG(Gnu Privacy Guard)。具备各种 Firefox 扩展开发工具也是个不错的主意。查看下面的 参考资料 小节获得这些软件包的链接。

虽然本文的开发基于 Ubuntu 7.10 系统,但所用到的概念适用于各种操作环境。着手开发之前,确保您的系统支持 Perl、GnuPG 和 Firefox。





主要的编程方法

本文要求您基本熟悉 Firefox 扩展的开发过程。本文不需要特定的编译器和环境配置,但您必须熟悉软件开发,因为可能需要诊断与设置有关的问题或配置错误。

出于开发的需要,建议创建一个新的 Firefox 配置文件。下面的 参考资料 小节提供了如何创建新的配置文件的信息,同时也提供了有用的关于扩展开发站点的链接。

GnuPG 将用于处理这个扩展的所有加密函数。这个方法提高了 JavaScript 的算法实现效率,同时也提供了健壮的跨平台密匙管理。您需要一个功能齐全的 GnuPG 安装,并且能够访问您选择的加密密匙。此外,还需要 gpg-agent 程序(通常是 GnuPG 包的一部分),用于处理已记住的 passphrase 的临时存储和过期。

有了处理加密的函数性扩展开发环境和 GnuPG 之后,可以通过更改前面开发的扩展的一些代码来修改接口。Elias Torres 开发的 Google Quick Add 扩展为插入加密修改提供了一个很好的开端。在加密阶段,当前输入的事件文本将存储在磁盘上,然后一个外部程序对它进行加密,加密后的文件将被读入并发送到 Google 服务器。在解密阶段,每个事件都被写到磁盘上,然后进行解密,最后读回纯文本并显示给用户。另一种代替方法涉及到编写新的协议,该协议由 Firefox 程序管理。尽管这个过程能够提供更加健壮的数据管道,输出、处理和读取则是更加简单的进程间通信方法,适合于这类扩展。

在继续使用本文提供的代码之前,确保您设置了函数性 Firefox GnuPG 和 gpg-agent。





构建 Google Calendar Quick Add 扩展

Google Calendar Quick Add 扩展利用 Google Calendar SOAP API 从任意页面上获得事件并添加日程表中。本文通过负载截取和过程加密的方式向日程表添加加密事件。一个代替方法是简单地向 Google Calendar 添加经 ASCII 封装的条目,但这里讨论的方法将自动完成这个过程。

首先,创建一个保存扩展目录和代码的目录,比如 mkdir ~/calendarEncrypt。转到这个目录,然后从 参考资料 小节中指定的链接将 Quick Google Calendar Quick Add 扩展 xpi 下载到这个目录。

用以下命令解压缩 xpi:unzip quickgooglecal.xpi。转到刚才创建的 chrome 目录,然后运行命令 unzip quickgooglecal.jar。现在您会看到一个目录树,类似于 清单 1:


清单 1. Google Calendar Quick Add 目录结构
				
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

转到 ~/calendarEncrypt 目录并编辑 install.rdf 文件。更改标识符和创建者,如 清单 2 所示:


清单 2. 更改 install.rdf 的标识符和创建者
				

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>

编辑 chrome.manifest 文件,将基于 jar 的扩展打包方式更改为更适合开发人员的目录结构打包方式。清单 3 给出了必要的更改。


清单 3. 将 chrome.manifest jar 更改为目录结构
				
				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

现在,在 Firefox 开发配置文件中为当前的扩展目录设置一个链接。例如,如果您的配置文件是 ~/.mozilla/firefox/b2w2sglj.development,就在 ~/.mozilla/firefox/b2w2sglj.development/extensions/ 创建一个称为 calendarEncrypt@devWorks_IBM.com 的链接。将当前的 Google Quick Add 开发目录路径放置到 calendarEncrypt@devWorks_IBM.com 文件中,在这个示例中是:/home/username/calendarEncrypt。

登录 Google Calendar 然后按 ctrl+;激活 Google Calendar Quick Add 扩展。通过输入 Test unencrypted event tomorrow 15:30 验证可以正确添加事件。验证事件可以正确地显示在日程表上。

Google Calendar Quick Add 扩展就绪之后,现在就可以向扩展插入支持加密和解密的修改了。





修改 Quick Add 扩展,使其支持透明加密

对发送的事件进行加密

本文通过拦截和加密 Quick Add Event 负载的方式,帮助实现自动向日程表添加加密事件。修改现有的扩展使其支持拦截,见下面的 清单 4。编辑 chrome/content/hello.xul 文件并删除第 69 行:var quickAddText = number_html(document.getElementById("quickText").value);。将下面的代码插入到第 69 行:


清单 4. 加密变量,提取日期和时间
				
   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];
   }

以上的代码假设日期和时间通常是快速事件文本中的最后两个单词。为了确保能够在日程表中准确放置,所有后来快速添加的事件的格式必须是 “消息 文本 日期 时间”,日期的格式为 “friday/monday/tomorrow/etc”,时间的格式为 “05:30/10:30/etc”。

将事件描述文本从事件日期和时间分离出来后,接下来应该将事件文本写到磁盘,然后对它进行加密。添加 清单 5 中第 77 行的代码:


清单 5. 将加密文本写到磁盘,然后调用程序
				
 
   // 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);

清单 5 的第一部分将配置文件 "/tmp/calendarEvent",将拦截到的事件文本写到磁盘。这个文件包含纯文本数据,但这些数据将在加密完成后被分解并删除。清单 5 的第二部分配置了一个文件,使它能够通过推荐的 nsIProcess 组件执行。/tmp/CalendarCrypt.pl 是一个 Perl 程序(将在后面介绍),它处理加密、解密和安全删除函数。对数据进行加密之后,清单 6 中的代码读回加密后的事件文本。将下面的代码置于 chrome/content/hello.xul 的第 98 行。


清单 6. 读回加密文件,然后构建事件文本
				
   // 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;

清单 5 的代码表示将文本事件写到一个文件中并运行加密程序。清单 6 中的代码表示读入经 ASCII 封装的加密文本文件(附带了存储日期和时间的信息),然后重新启动快速添加文本的过程。

每个通过 Google Calendar Quick Add 程序添加的事件都被拦截、加密,然后以加密的形式发送到 Google 服务器。

为读取的加密事件修改 overlay.js

完成对 chrome/content/hello.xul 中的事件进行加密的代码后,插入这些代码,解密 chrome/content/overlay.js 中的事件。插入 清单 7 中的行,从文件的末尾开始。


清单 7. 解密事件侦听器,组件定义
				

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
},


事件显示页面每次加载时,都会通过 addEventListener 调用 calendarDecryptExtension 函数。calendarDecryptionExtension 函数开始是各种定义和挂钩,保证代码在页面装载时开始运行以及函数可以访问正确的数据。将 清单 8 中的代码置于清单 7 中的代码之下,继续文档解密过程:


清单 8. 写入事件的 onPageLoad 函数
				
  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();
        

每个日程表条目都存在 HTML 的 span 元素中。临时变量设置完成后,一个 for 循环将处理每个 span 元素,并在文本加密后删除这些内容。下面的 清单 9 调用 CalendarCrypt.pl 程序,使用 "decrypt" 选项,提取事件文本。


清单 9. 运行加密脚本
				

        // 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);

将加密后的事件文本写到磁盘,然后运行解密程序,这将创建一个不加密的事件文本文件。添加 清单 10 中的代码将数据读回并更改呈现的信息。


清单 10. 读取解密后的文本
				

        // 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;

从文件读取文本类似于加密阶段中执行的过程。注意,清单 10 中的最后一行,它将当前的 span 数据设置为未加密文本,而不是 “BEGIN PGP...” 原始文本。 添加 清单 11 中的代码,完成解密过程。


清单 11. 分解磁盘上的文本,结束循环
				

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

将选项更改为 "shred" 并重用 nsiProcess 组件,确保加密后的事件文本安全地从文件系统的临时位置删除。下一部分给出了前面部分调用的 CalendarCrypt.pl 程序。

CalendarCrypt.pl 程序

为了完成加密支持修改,需要根据下面的清单创建 CalendarCrypt.pl 程序。注意,对于典型 GnuPG 用户,给出的实现假设了一些最可能用到的加密/解密设置。如果愿意,可以考虑用 GnuPG::Encrypt 提供的其他选项替换外部程序调用和设置。例如,如果您希望使用多个键,或者 gpg-agent 与您的配置不兼容,GnuPG::Encrypt Perl 模块提供了许多帮助实现环境兼容的选项。首先,通过 清单 12 中的文本将 Perl 程序 calendarEncrypt.pl 置于 /tmp 中。


清单 12. calendarEncrypt.pl header 和加密
				
#!/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`;
        

在检查选项并设置默认用户名之后,将加密 /tmp/calendarEvent 文件。分解并删除原始的纯文本事件文件,确保磁盘上没有遗留敏感的数据。 下面的 清单 13 描述了解密模式:


清单 13. 文件处理,解密
				
}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`;
      

在上传、处理或显示日程表事件的某个时刻,会丢失一些原始格式。尤其是 “BEGIN PGP” 消息的前缀 “-----” 以及换行指示,将被去除。在调用解密命令之前,清单 13 中的字符串操作函数将替换丢失的格式。最后,清单 14 中的代码负责安全删除解密文本文件,从而去除任何存储在磁盘上的纯文本信息。


清单 14. 分解纯文本文件
				

}elsif( $mode eq "shred" )
{
  my $res = `shred /tmp/calendarEvent.decrypted`;
  $res    = `rm /tmp/calendarEvent.decrypted`;
}

# EOF

在将文件保存为 /tmp/CalendarCrypt.pl 之后,要确保该文件在发出以下命令时执行:chmod a+x /tmp/CalendarCrypt.pl。





用例

现在将拦截、加密使用 Google Calendar Quick Add 程序添加的事件,然后以加密的形式将其发布到 Google 服务器。使用 Extension Developer 的 Extension 重新加载所有的 chrome 事件,或重启 Firefox。使用 Ctrl+; 组合键,然后添加一个事件,比如 “Private doctor appointment tomorrow 16:30”。以 “regular” 模式打开您的 Google 日程表,您将看到一个事件描述,类似于 图 1 左边所示的内容。

要显示解密后的事件,请转到链接:http://www.google.com/calendar/htmlembed?src=<yourCalendarName>%40gmail.com,在这里,<yourCalendarName> 是您的帐户名,比如 “developer.works” 或 “bob_smith”。页面装载开始时,您会看到将弹出一个 gpg-agent,请求您的 passphrase。为 CalendarCrypt.pl 程序的第 1 小节中 “uname” 命令识别出的用户输入 passphrase,您的事件将被解密并显示在日程表上。





结束语

使用本文给出的工具和代码,您现在就可以在 Google 日程表中存储加密事件文本。使用 Elias Torres 的 Google Calendar Quick Add Firefox 扩展的修改,对每个添加和查看的事件进行无缝的加密和解密。在重新掌控数据的同时,也获得了 Web 2.0 应用程序带来的好处。

考虑创建一个模糊层(obfuscation layer)来更改事件存储的时间,降低网络流量分析的效率。编写您自己的程序,借助 Google Calendar SOAP API 提取、加密和存储以前和将来的日程表事件。尝试为默认的 Google Calendar 接口创建一个具有 Ajax 风格的透明加密扩展。

(责任编辑:A6)


时间:2008-08-08 16:27 来源:developerWorks 中国 作者:Nathan Harrington 原文链接

好文,顶一下
(0)
0%
文章真差,踩一下
(0)
0%
------分隔线----------------------------


把开源带在你的身边-精美linux小纪念品
无觅相关文章插件,快速提升流量