转载:通过 Firefox 扩展为 Google Calendar 加密

来源:互联网 发布:冰川网络有手游吗 编辑:程序博客网 时间:2024/05/22 00:16

通过 Firefox 扩展为 Google Calendar 加密

在 Google Calendar 中存储表示事件名称和描述的加密数据


developerWorks

文档选项


未显示需要 JavaScript 的文档选项


将打印机的版面设置成横向打印模式

打印本页


将此页作为电子邮件发送

将此页作为电子邮件发送


样例代码


英文原文

英文原文

级别: 初级

Nathan Harrington, I/T 专家, IBM

2008 年 8 月 04 日

当今的 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 配置文件。下面的 参考资料 小节提供了如何创建新的配置文件的信息,同时也提供了有用的关于扩展开发站点的链接。

跨平台兼容性
本文的开发基于 Ubuntu 7.10 Linux® 发行版。如果要严格地遵循本文的说明进行操作,必须使用较新的 Linux 发行版。不过这里提到的所有软件(Firefox、GnuPG 和 Perl)都有对应的 Microsoft® Windows® 版本。只要稍微修改路径名和外部程序,这里所用的代码将可以在其他各种平台上使用。

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.manifestcalendarEncrypt/readme.txtcalendarEncrypt/chromecalendarEncrypt/chrome/contentcalendarEncrypt/chrome/content/hello.xulcalendarEncrypt/chrome/content/overlay.xulcalendarEncrypt/chrome/content/overlay.jscalendarEncrypt/chrome/skincalendarEncrypt/chrome/skin/overlay.csscalendarEncrypt/chrome/quickgooglecal.jarcalendarEncrypt/chrome/localecalendarEncrypt/chrome/locale/en-UScalendarEncrypt/chrome/locale/en-US/hello.dtdcalendarEncrypt/chrome/locale/en-US/overlay.dtdcalendarEncrypt/install.rdfcalendarEncrypt/quickgooglecal.xpi

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

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

Change the identifier:  {E31AE5B1-3E5B-4927-9B48-76C0A701F105}to:  calendarEncrypt@devWorks_IBM.comAlso, change the creator:    Elias Torres  to:    Elias Torres with modifications from devWorks

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

清单 3. 将 chrome.manifest jar 更改为目录结构

Original chrome.manifestcontent quickgooglecal  jar:chrome/quickgooglecal.jar!/content/ overlay chrome://browser/content/browser.xul chrome://quickgooglecal/content/overlay.xullocale  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.cssChange to directory based chrome.manifest:content quickgooglecal  chrome/content/overlay chrome://browser/content/browser.xul chrome://quickgooglecal/content/overlay.xullocale  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 filesuse 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 =)    {      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=%40gmail.com,在这里, 是您的帐户名,比如 “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 风格的透明加密扩展。

 

from http://www.ibm.com/developerworks/cn/web/wa-googlecal/index.html